308 lines
9.4 KiB
Markdown
308 lines
9.4 KiB
Markdown
# 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 隐式登记 | 已有,保留兼容 |
|
||
| 领取任务 | 已有 |
|
||
| 提交结果 / 提交失败 | 已有 |
|
||
| 定时心跳 | 不实现 |
|
||
| 正式认证 | `[待定]`,当前读取但不校验 |
|