Files
cmautobuy/docs/admin/07-设备登记联调手册.md
T
chengmaandClaude Opus 5 62d46bf10a docs: 新增设备登记联调手册,并定案 client.name
面向 Client 开发者的实操文档,目标是让一台新设备出现在 Admin 的
客户端列表里。文中所有请求和响应都是在真实运行的 Admin 上跑出来的。

新增 docs/admin/07-设备登记联调手册.md
- 先讲清"没有注册接口,登记是 claim 的副作用"
- **重点提示 204 不是错误**:新设备第一次 claim 必然 204,
  它同时意味着登记成功。这是最容易被 Client 误判成失败的地方
- 设备号必须持久化、永不变——变了 Admin 会当成新机器,
  列表里会堆一串僵尸记录
- 设备名只在首次登记时采纳,之后 Admin 侧改名不会被覆盖。
  这个行为会让人困惑("我改了 Client 怎么 Admin 没变"),先说清楚
- 四步验证流程 + 可直接复制的 curl + 手工插测试任务的 SQL
- 7 条常见问题,含"列表里多出好几台一样的机器"这类实际会碰到的
- 列出 Client 侧待办清单,标明前两条做完就能登记成功

定案 client.name(原 [待定])
Client 设置页本来就有「设备名」输入框(deviceNameInput,50 字上限),
只是没接进 ClientInfo。Admin 仍容忍它缺失,缺了用 X-Client-Id 兜底,
所以 Client 先不发也不影响登记。

同步更新
- docs/client/04-admin-api-contract.md §5:请求示例补 client 字段,
  加"204 不是错误"的必须项,并指向本手册
- docs/README.md:两处导航加入口,Client 侧"对接 Admin"那行也指过来

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 17:08:58 +08:00

281 lines
9.3 KiB
Markdown
Raw 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 设备登记联调手册
- 文档状态:联调用,随实现更新
- 读者:**实现 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 <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` 头 | **是** | 设备号。缺了直接 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`,无响应体。
### 第三步:在界面上确认
浏览器打开 <http://127.0.0.1:8080/clients>,应该看到:
| 名称 | 序列号 | 状态 | 最近活动 |
|---|---|---|---|
| 联调测试机 | 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` 头但不校验 |
| 改派任务 | 未做,需要时直接改库 |