Files
cmautobuy/docs/admin/07-设备登记联调手册.md

308 lines
9.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 07 设备登记联调手册
- 文档状态:Admin 侧(#12)已实现并通过 curl 验证,等待 Client 侧(#11)接入
- 读者:Admin 登记接口、Client `HttpAdminGateway` 开发者
- 目标:让 Client 设置页安全登记设备,同时不领取任务
接口权威定义:
- [Client–Admin API 契约](../client/04-admin-api-contract.md) §4.1
- [Admin 侧 Client 接口实现](04-client-api.md) §1.1
- Gitea #12:Admin 新增幂等 Client 登记接口
- Gitea #11:Client 保存当前设备并登记 Client
本文中的登记请求**已在真实运行的 Admin 上验证通过**(#12 已完成),下面 §5 的每条命令都是实跑结果。
---
## 1. 登记、领取和心跳是三件不同的事
### 1.1 显式登记
设置页点击“保存”后调用:
```http
PUT /api/v1/client/registration
```
它只新增或更新 Client,**不读取、领取或修改任务**,也不返回任务。
### 1.2 领取任务
“获取任务”流程调用:
```http
POST /api/v1/client/tasks/claim
```
它可能返回 `204 No Content`,也可能返回 `200 + task`。返回任务时 Admin 已经改变任务状态,Client 必须可靠写入本地 SQLite,不能丢弃响应。
claim 保留隐式登记作为旧 Client 的兼容兜底,但设置保存入口不得用 claim 代替登记。
### 1.3 没有心跳接口
本项目仍然不做定时心跳。登记、claim、result 和 failure 都刷新 `last_seen_at`,Admin 根据最近活动时间计算在线或离线。
客户端执行长任务期间超过在线阈值,Admin 暂时显示离线是已知且接受的取舍。
---
## 2. Client 要准备的数据
### 2.1 设备号:`X-Client-Id`
对应设置页只读“设备号”。
- 首次保存时生成一次;
- 保存到 Client SQLite `app_settings`,键名 `admin.client_id`;
- 重启、升级、修改设置时保持不变;
- 换电脑或清空本地数据后生成新编号;
- 只向 Admin 发送生成后的编号,不发送原始硬件参数。
Admin 使用它作为 `clients.client_id` 主键。设备号变化会产生一条新的 Client 记录。
### 2.2 设备名:`client.name`
对应设置页可编辑“设备名”,最多 50 字,可以为空。
- 显式登记携带非空名称时,Admin 更新名称;
- 显式登记名称为空时:新 Client 使用设备号兜底,已有 Client 保留原名称;
- claim 隐式登记已有 Client 时不覆盖名称;
- Admin 页面仍可人工修改名称。
这意味着用户明确点击“保存”可以同步非空名称,而后台领取不会反复覆盖名称。
### 2.3 设备和能力
```json
{
"supported_types": ["collect", "purchase"],
"device": {
"address": "192.168.0.173:5555",
"platform": "android",
"pdd_package": "com.xunmeng.pinduoduo"
},
"capabilities": {
"purchase_mode": "dry_run",
"schema_versions": [1]
}
}
```
- `supported_types` 必须非空,只允许 `collect`、`purchase`;
- `device` 可选,提供时当前只支持 `android`;
- `purchase_mode` 只允许 `dry_run`、`live`;Client 未选择设备或真实采购执行器未就绪时为 `dry_run`,就绪后自动为 `live`;
- `schema_versions` 只包含正整数,当前使用 `[1]`。
---
## 3. 登记请求
```http
PUT /api/v1/client/registration HTTP/1.1
Host: 127.0.0.1:8080
X-Client-Id: CLIENT-123456
X-Request-Id: 7a5d0000-0000-0000-0000-000000000001
Content-Type: application/json
Authorization: Bearer <token>
```
```json
{
"client": {
"name": "办公室-01"
},
"supported_types": ["collect", "purchase"],
"device": {
"address": "192.168.0.173:5555",
"platform": "android",
"pdd_package": "com.xunmeng.pinduoduo"
},
"capabilities": {
"purchase_mode": "dry_run",
"schema_versions": [1]
}
}
```
| 字段 | 必填 | 说明 |
|---|---|---|
| `X-Client-Id` | 是 | 稳定设备号,Admin Client 唯一键 |
| `X-Request-Id` | 建议 | 每次请求使用新的 UUID,便于排查 |
| `client.name` | 否 | 最多 50 字;非空时显式更新名称 |
| `supported_types` | 是 | 非空任务类型列表 |
| `device` | 否 | 当前选择的 Android 设备信息 |
| `capabilities.purchase_mode` | 是 | MVP 使用 `dry_run` |
| `capabilities.schema_versions` | 是 | 当前使用 `[1]` |
| `Authorization` | 当前可选 | Admin 当前读取但不校验,正式认证待定 |
访问令牌不得出现在普通日志、错误提示或测试固件中。
---
## 4. 登记响应
新增和更新统一返回 `200 OK`:
```json
{
"registered": true,
"client_id": "CLIENT-123456",
"registered_at": "2026-08-06T09:00:00Z"
}
```
Client 必须校验:
- HTTP 状态为 `200`;
- `registered` 为 `true`;
- `client_id` 与请求的 `X-Client-Id` 相同;
- `registered_at` 是带时区 ISO 8601 时间。
常见错误:
| HTTP | code | retryable | 处理 |
|---|---|---|---|
| 400 | `MISSING_CLIENT_ID` | false | 检查本地设备号,不自动重试 |
| 400 | `INVALID_BODY` | false | 修正 JSON,不自动重试 |
| 422 | `INVALID_CLIENT_PROFILE` | false | 修正名称或能力字段 |
| 500 | `CLIENT_REGISTER_FAILED` | true | 保留本地设置,允许稍后重试 |
错误响应使用统一结构:
```json
{
"error": {
"code": "INVALID_CLIENT_PROFILE",
"message": "Client 能力字段无效",
"retryable": false,
"request_id": "7a5d...",
"details": {}
}
}
```
---
## 5. Admin 实现后的 curl 验证
### 5.1 启动 Admin
```powershell
cd D:\chengma\cmautobuy\admin
go run .
```
### 5.2 首次登记
```bash
curl -i -X PUT http://127.0.0.1:8080/api/v1/client/registration \
-H 'X-Client-Id: test-device-001' \
-H 'X-Request-Id: registration-create-001' \
-H 'Content-Type: application/json' \
-d '{
"client": {"name": "联调测试机"},
"supported_types": ["collect", "purchase"],
"device": {"address": "192.168.0.173:5555", "platform": "android",
"pdd_package": "com.xunmeng.pinduoduo"},
"capabilities": {"purchase_mode": "dry_run", "schema_versions": [1]}
}'
```
预期:`200 OK`,`registered: true`,Admin `/clients` 页面出现且只出现一条 `test-device-001`。
### 5.3 重复登记
使用同一 `X-Client-Id`、新的 `X-Request-Id` 再调用一次。预期仍为 `200`,客户端列表记录数不增加。
### 5.4 更新名称
把 `client.name` 改为“联调测试机-已更新”再次调用。预期记录数不增加,名称更新。
### 5.5 空名称更新
发送 `"client": {"name": ""}`。预期已有名称保持不变。
### 5.6 验证无任务副作用
登记前后检查任务表:任务状态、领取时间和领取历史均不得变化。登记响应不得出现 `task` 字段。
### 5.7 错误响应
至少验证缺少 `X-Client-Id`、名称超过 50 字、空 `supported_types` 和非法 `purchase_mode`。
**实跑结果(Go 1.23.0,2026-08-06):**
| 验证项 | 结果 |
|---|---|
| 5.2 首次登记 | `200`,`registered: true` |
| 5.3 重复登记 3 次 | `200`,客户端记录数保持 **1** |
| 5.4 更新名称 | `200`,名称变为"联调测试机-已更新",记录数仍为 1 |
| 5.5 空名称更新 | `200`,**名称保持不变** |
| 5.6 无任务副作用 | 插入任务后登记 3 次,任务的 `status` / `claimed_at` / `updated_at` **完全未变**;响应**不含 `task` 字段**;登记后任务仍能被正常领取 |
| 5.7 缺 `X-Client-Id` | `400 MISSING_CLIENT_ID` |
| 5.7 名称 51 字 | `422 INVALID_CLIENT_PROFILE`,消息指明"client.name 最多 50 个字,收到 51 个" |
| 5.7 `supported_types` 为空 | `422 INVALID_CLIENT_PROFILE` |
| 5.7 `purchase_mode` 非法 | `422 INVALID_CLIENT_PROFILE` |
| 校验不过时不写库 | 三次非法请求后记录数仍为 1 |
| claim 仍可用 | `200`,正常领到任务 |
---
## 6. claim 兼容登记与领取验证
claim 的请求结构与能力字段保持不变:
```http
POST /api/v1/client/tasks/claim
X-Client-Id: test-device-001
```
- `204 No Content`:登记/活动时间更新成功,暂时没有任务;
- `200 OK`:领到一个任务,Client 必须立即写入 SQLite;
- 首次 claim 不保证一定是 204,预先分配过任务时可以直接返回 200;
- claim 更新已有 Client 时不覆盖名称。
设置页保存按钮不得调用 claim。只有“获取任务”流程可以调用它。
---
## 7. Client 保存流程
```text
点击“保存”
→ 从 SQLite 读取 admin.client_id
→ 不存在:生成并保存
→ 已存在:保持不变
→ 保存设备名
→ 页面显示“本地已保存”
→ 后台调用 PUT /registration
├─ 成功:显示“已登记到 Admin”
└─ 失败:显示“本地已保存,Admin 登记失败”
```
实现要求:
- SQLite 是设备号权威来源,不能只看输入框是否显示“待生成”;
- Admin 失败不得回滚本地设置;
- HTTP 使用 `QObject + moveToThread`,不得阻塞 Qt 主线程;
- 保存期间禁用重复点击;
- 窗口关闭时断开信号,忽略迟到结果;
- `MockAdminGateway` 和 `HttpAdminGateway` 使用相同登记契约测试;
- 新增 `requests` 依赖前按项目依赖门禁确认固定版本和许可证。
---
## 8. 能力状态
| 能力 | 状态 |
|---|---|
| 独立登记契约 | **已确认** |
| Admin 独立登记实现 | **已完成(#12)** |
| Client 设置页登记实现 | 等待 #11 和 #12 |
| claim 隐式登记 | 已有,保留兼容 |
| 领取任务 | 已有 |
| 提交结果 / 提交失败 | 已有 |
| 定时心跳 | 不实现 |
| 正式认证 | `[待定]`,当前读取但不校验 |