# 07 设备登记联调手册 - 文档状态:联调用,随实现更新 - 读者:**实现 Client 侧 `HttpAdminGateway` 的开发者** - 目标:让一台新 Client 出现在 Admin 的「客户端列表」里,并确认登记成功 本文所有请求和响应都是**在真实运行的 Admin 上跑出来的**,不是手写示例。 接口的权威定义在 [Client 侧契约](../client/04-admin-api-contract.md), 本文只讲怎么把它跑通。 --- ## 1. 先理解一件事:没有注册接口 **登记是领取任务的副作用。** Client 调 `claim` 的时候,Admin 顺手就把这台机器记下来了。 没有 `/register`,也没有心跳接口——这是有意的设计, 理由见 [04 Client 接口实现](04-client-api.md) §3。 所以"登记新设备"这件事,Client 侧要做的就是:**调一次 `claim`**。 ```text Client 启动 ↓ 调 POST /api/v1/client/tasks/claim(带上设备号和设备名) ↓ Admin:没见过这个设备号 → 新增一条记录 见过 → 更新设备信息和最近活动时间 ↓ 返回 204(没有分给它的任务)或 200(有任务) ``` > **最容易误判的一点:新设备第一次调 `claim` 必然返回 `204`。** > > 因为它刚登记,还没有人给它分配任务。 > **`204` 不是失败,它同时意味着登记成功了。** > Client 必须把它当正常情况处理——不要报错、不要弹提示、 > 更不要因此触发重试风暴。 --- ## 2. Client 要准备两样东西 ### 2.1 设备号(`X-Client-Id`) 对应 Client 设置页那个只读的**「设备号」**输入框(现在显示"待生成")。 `[必须]` 规则: - **首次启动时生成一次**(UUID 或任何全局唯一的串都行); - 生成后存进 `app_settings`,键名 `admin.client_id`; - **之后永远不变**——重启、升级、改配置都不能变。 **为什么不能变:** Admin 是按设备号认机器的。设备号变了, Admin 会认为这是一台新机器,客户端列表里就会多出一条, 原来那条变成永远离线的僵尸记录。 换电脑、重装系统算新设备,那是对的;但同一台机器重启不能变。 ### 2.2 设备名(`client.name`) 对应设置页那个可编辑的**「设备名」**输入框(50 字上限)。 - 给人看的,比如"办公室-01"、"仓库那台"; - **可以不填**——不填的话 Admin 用设备号当显示名,不影响功能。 > **一个会让人困惑的行为,先说清楚:** > > 设备名**只在第一次登记时被采纳**。之后操作员在 Admin 界面上把它 > 改成别的名字,Client 再上报也**不会覆盖**。 > > 这是故意的——避免操作员精心起的名字被客户端的默认值冲掉。 > 所以你在 Client 那边改了设备名,发现 Admin 没变,**不是 bug**。 --- ## 3. 请求长什么样 ```http POST /api/v1/client/tasks/claim HTTP/1.1 Host: 127.0.0.1:8080 X-Client-Id: 7f3a1c62-9b04-4e8d-a1f2-5c7e9d0b3a11 Content-Type: application/json Authorization: Bearer ``` ```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` 头 | **是** | 设备号。缺了直接 400 | | `client.name` | 否 | 设备名。缺了用设备号当显示名 | | `supported_types` | 是 | 支持的任务类型。只写 `["collect"]` 就不会拿到采购任务 | | `device.*` | 否 | 会存进 Admin 供排查用 | | `capabilities.purchase_mode` | 是 | `dry_run` 或 `live`。演练模式期间固定 `dry_run` | | `Authorization` | 否 | `[待定]` 认证方式未定,当前 Admin **读但不校验** | --- ## 4. 响应怎么处理 ### 4.1 `204 No Content` —— 登记成功,暂时没活干 **没有响应体。** 这是新设备的正常结果,也是引擎空转时的常态。 Client 该做的:按轮询周期退避后再来,**不要当成错误**。 ### 4.2 `200 OK` —— 领到任务了 ```json { "task": { "id": "PDD-TEST-0001", "type": "purchase", "version": 1, "priority": 0, "payload": { "goods_id": "737116531267", "goods_url": "https://mobile.yangkeduo.com/goods.html?goods_id=737116531267", "options": { "color": "黑色", "size": "M码" }, "quantity": 2, "max_price_cent": 4200 }, "created_at": "2026-08-06T08:00:00Z", "updated_at": "2026-08-06T08:53:20Z" } } ``` 注意三点: - **没有租约字段**,也没有 Admin 侧状态——不用找,本来就没有; - `options` 是**对象**不是字符串; - `max_price_cent` 是**人民币分的整数**(4200 = ¥42.00)。 采购任务必然带 `quantity` 和 `max_price_cent`,这是价格保护。 ### 4.3 `400` —— 请求有问题 ```json { "error": { "code": "MISSING_CLIENT_ID", "message": "缺少 X-Client-Id 请求头", "retryable": false, "request_id": "", "details": {} } } ``` `retryable: false` 的错误**不要重试**,重试多少次都是一样的结果。 --- ## 5. 怎么确认登记成功 ### 第一步:起 Admin ```powershell cd D:\chengma\cmautobuy\admin go run . ``` 看到 `Admin 已启动: http://127.0.0.1:8080` 就绪。 ### 第二步:发一次 claim 不用等 Client 写完,先用 curl 打通链路: ```bash curl -i -X POST http://127.0.0.1:8080/api/v1/client/tasks/claim \ -H 'X-Client-Id: test-device-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]} }' ``` 预期:`HTTP/1.1 204 No Content`,无响应体。 ### 第三步:在界面上确认 浏览器打开 ,应该看到: | 名称 | 序列号 | 状态 | 最近活动 | |---|---|---|---| | 联调测试机 | test-device-001 | 在线 | 2026-08-06T09:03:55Z | 底部状态条显示 `共 1 台客户端 · 在线 1 · 离线 0`。 **看到这一行 = 登记打通了。** ### 第四步:让它领到一个任务(可选) 想验证 `200` 那条路径,得先有任务。现在还没有创建任务的界面 (要等 Excel 导入那条链路做完),手工插一条: 用 DB Browser 打开 `admin/data/admin.db`,执行: ```sql INSERT INTO tasks (task_id, task_type, status, assigned_client, order_no, pdd_goods_url, pdd_goods_id, pdd_options, quantity, max_price_cent, created_at, updated_at) VALUES ('PDD-TEST-0001', 'purchase', 'assigned', 'test-device-001', 'TEST-ORDER-001', 'https://mobile.yangkeduo.com/goods.html?goods_id=737116531267', '737116531267', '{"color":"黑色","size":"M码"}', 2, 4200, '2026-08-06T08:00:00Z', '2026-08-06T08:00:00Z'); ``` 注意 `assigned_client` 要填**你自己的设备号**,任务只发给指定的客户端。 再调一次 claim,这次会拿到 `200` 和上面 §4.2 那个结构。 **再调第三次又是 `204`**——任务已经被领走了,这是对的。 --- ## 6. 常见问题 | 现象 | 原因 | 怎么办 | |---|---|---| | 一直 `204`,列表里也没有 | 请求根本没到 Admin | 看 Admin 控制台有没有请求日志;确认端口和地址 | | `400 MISSING_CLIENT_ID` | 没带 `X-Client-Id` 头 | 它是**请求头**不是 body 字段 | | `400 INVALID_BODY` | body 不是合法 JSON | 即使没内容也要发 `{}`,不能发空 | | 列表里多出好几台一样的机器 | **设备号每次启动都在变** | 设备号必须持久化,见 §2.1 | | 改了 Client 的设备名,Admin 没变 | 正常行为,不是 bug | 见 §2.2 | | 状态显示"离线"但客户端在跑 | 超过 10 分钟没调任何接口 | 正常。本项目**有意不做心跳**,执行长任务期间会显示离线 | | 明明有任务却拿到 204 | `assigned_client` 不是你的设备号;或 `supported_types` 不含该任务类型 | 查库确认这两项 | --- ## 7. Client 侧要改什么 按现在的代码(`client/src/admin_gateway.py`),要做这几件事: - [ ] `requirements.txt` 加 HTTP 库(`requests`,版本要过工单确认) - [ ] **`ClientInfo` 加 `name` 字段** —— 设置页的「设备名」输入框已经有了, 只是没接到网关里 - [ ] 设备号生成一次并持久化到 `app_settings`(键 `admin.client_id`), 填进设置页那个只读的「设备号」框 - [ ] 实现 `HttpAdminGateway`,先只做 `claim_next` - [ ] **把 `204` 当正常情况**返回"暂无任务",不要抛异常 - [ ] 契约测试:Mock 和 HTTP 两个实现跑同一套用例 前两条做完就能登记成功了,后面的可以分批。 --- ## 8. 还没做完的部分 | 能力 | 状态 | |---|---| | 登记新设备 | **可用** | | 领取任务 | **可用**(任务需手工插库) | | 提交结果 / 提交失败 | **可用**,含幂等 | | 创建任务的界面 | 未做,等 Excel 导入链路 | | 认证 | `[待定]`,当前读 `Authorization` 头但不校验 | | 改派任务 | 未做,需要时直接改库 |