PUT /api/v1/client/registration —— 设置页点"保存"时调用, 只登记客户端,不碰任务。 为什么需要它 原设计"注册就在 claim 里做"有个真问题:设置页保存被迫调 claim, 而 claim 可能真的领到一个任务——Admin 那边已把任务标成 claimed, Client 必须可靠落库否则任务就丢了。一个"保存设置"的动作 不该承担"领取任务并保证不丢"的责任。这违反了本项目自己的原则 (05 §1:界面上只有一个会产生外部后果的命令)。 实现 - ClientProfileRequest + Validate() 由**登记和领取共用**, 避免两个入口的结构和校验各写一份、迟早漂移 - 校验:名称 <=50 字(按字符不按字节,中文一个字三字节)、 supported_types 非空且只含 collect/purchase、platform 只支持 android、 purchase_mode 必填且只允许 dry_run/live、schema_versions 均为正整数 - 非法内容返回 422 INVALID_CLIENT_PROFILE,错误消息指明具体字段 - UpsertClient 加 explicit 参数区分名称规则: 显式登记(用户点保存)带非空名称时更新名称; 隐式登记(claim 顺带)永不更新,否则操作员改的名字会被反复冲掉 已验证(Go 1.23.0) - 单元测试 40 个全过,含"登记不产生任何任务副作用"的快照比对 - 端到端逐条走完手册 §5.2~5.7:重复登记记录数恒为 1; 更新/空名称行为正确;插入任务后登记 3 次任务字段完全未变且仍可领取; 四种非法输入均 422 且不写库;claim 不受影响 一处行为变更需注意 名称归属规则改了:原来是"Admin 操作员永远赢",现在是"最后一次 显式操作赢"——用户在 Client 点保存会覆盖 Admin 侧改的名字。 按 #12 文档实现,已拆成三个独立测试盯住三种情况。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
9.4 KiB
07 设备登记联调手册
- 文档状态:Admin 侧(#12)已实现并通过 curl 验证,等待 Client 侧(#11)接入
- 读者:Admin 登记接口、Client
HttpAdminGateway开发者 - 目标:让 Client 设置页安全登记设备,同时不领取任务
接口权威定义:
- Client–Admin API 契约 §4.1
- Admin 侧 Client 接口实现 §1.1
- Gitea #12:Admin 新增幂等 Client 登记接口
- Gitea #11:Client 保存当前设备并登记 Client
本文中的登记请求已在真实运行的 Admin 上验证通过(#12 已完成),下面 §5 的每条命令都是实跑结果。
1. 登记、领取和心跳是三件不同的事
1.1 显式登记
设置页点击“保存”后调用:
PUT /api/v1/client/registration
它只新增或更新 Client,不读取、领取或修改任务,也不返回任务。
1.2 领取任务
“获取任务”流程调用:
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 设备和能力
{
"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,MVP 固定dry_run;schema_versions只包含正整数,当前使用[1]。
3. 登记请求
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>
{
"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:
{
"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 | 保留本地设置,允许稍后重试 |
错误响应使用统一结构:
{
"error": {
"code": "INVALID_CLIENT_PROFILE",
"message": "Client 能力字段无效",
"retryable": false,
"request_id": "7a5d...",
"details": {}
}
}
5. Admin 实现后的 curl 验证
5.1 启动 Admin
cd D:\chengma\cmautobuy\admin
go run .
5.2 首次登记
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 的请求结构与能力字段保持不变:
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 保存流程
点击“保存”
→ 从 SQLite 读取 admin.client_id
→ 不存在:生成并保存
→ 已存在:保持不变
→ 保存设备名
→ 页面显示“本地已保存”
→ 后台调用 PUT /registration
├─ 成功:显示“已登记到 Admin”
└─ 失败:显示“本地已保存,Admin 登记失败”
实现要求:
- SQLite 是设备号权威来源,不能只看输入框是否显示“待生成”;
- Admin 失败不得回滚本地设置;
- HTTP 使用
QObject + moveToThread,不得阻塞 Qt 主线程; - 保存期间禁用重复点击;
- 窗口关闭时断开信号,忽略迟到结果;
MockAdminGateway和HttpAdminGateway使用相同登记契约测试;- 新增
requests依赖前按项目依赖门禁确认固定版本和许可证。
8. 能力状态
| 能力 | 状态 |
|---|---|
| 独立登记契约 | 已确认 |
| Admin 独立登记实现 | 已完成(#12) |
| Client 设置页登记实现 | 等待 #11 和 #12 |
| claim 隐式登记 | 已有,保留兼容 |
| 领取任务 | 已有 |
| 提交结果 / 提交失败 | 已有 |
| 定时心跳 | 不实现 |
| 正式认证 | [待定],当前读取但不校验 |