# 设备直接绑定接口联调文档 ## 1. 接口说明 该接口用于设备无法正常获取绑定验证码时,由白名单来源的业务页面发起直接绑定。 接口完成的业务效果与验证码绑定一致: - 创建当前程序用户与设备的有效绑定关系; - 将设备绑定状态更新为 `bound`; - 作废该设备尚未使用的绑定验证码; - 清理绑定验证码缓存; - 写入绑定审计记录。 接口不会自动创建不存在的 ICCID。调用前,设备必须已经登记在后端 `sim_card` 表中,并且设备状态为 `active`。 ## 2. 接口地址 ```http POST /api/v1/program/bindings/by-iccid ``` 生产环境示例: ```text https:///api/v1/program/bindings/by-iccid ``` 将 `` 替换为实际 4G 管理后端域名。 ## 3. 调用前提 ### 3.1 程序用户登录 调用方必须先通过程序用户登录接口获取 `PROGRAM_USER` 的 `accessToken`: ```http POST /api/v1/program/auth/login ``` 后续请求携带: ```http Authorization: Bearer ``` 不能使用 `SUPER_ADMIN`、`FACTORY_PROVISIONER` 或设备 Token 调用此接口。 ### 3.2 来源白名单 当前默认允许以下 Origin: ```text https://banban.api.talkingq.com http://192.168.101.151 ``` 请求必须携带与页面实际来源完全一致的 `Origin` 请求头: ```http Origin: https://banban.api.talkingq.com ``` 或: ```http Origin: http://192.168.101.151 ``` 白名单由环境变量配置: ```bash BINDING_ALLOWED_ORIGINS=https://banban.api.talkingq.com,http://192.168.101.151 ``` 多个来源使用英文逗号分隔。来源匹配包含协议、主机和端口,不能只填写裸域名或裸 IP。 例如,下面这些值不是同一个 Origin: ```text http://192.168.101.151 https://192.168.101.151 http://192.168.101.151:8080 ``` 如果页面实际通过 HTTPS 或非默认端口访问,需要把实际 Origin 加入 `BINDING_ALLOWED_ORIGINS` 后重启后端。 ## 4. 请求格式 ### 4.1 请求头 ```http Origin: https://banban.api.talkingq.com Authorization: Bearer Content-Type: application/json X-Request-Id: direct-bind-20260920-0001 ``` `Origin` 是来源白名单校验字段;`Authorization` 是业务身份认证字段;`X-Request-Id` 用于日志追踪,建议每次请求使用唯一值。 ### 4.2 请求体 ```json { "iccid": "898604E91123D1334901", "requestId": "direct-bind-20260920-0001" } ``` 字段说明: | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `iccid` | string | 是 | 设备 ICCID,18 至 22 位字母或数字,后端会统一转为大写 | | `requestId` | string | 是 | 幂等请求号,最长 64 个字符;同一用户重试必须使用相同值 | ## 5. curl 联调示例 请将 Token 和 ICCID 替换为真实值,不要把 Token 提交到代码仓库或群聊。 ```bash curl -i -X POST \ "https:///api/v1/program/bindings/by-iccid" \ -H "Origin: https://banban.api.talkingq.com" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -H "X-Request-Id: direct-bind-20260920-0001" \ --data '{ "iccid": "898604E91123D1334901", "requestId": "direct-bind-20260920-0001" }' ``` 使用 IP 来源联调时,将 `Origin` 改为: ```http Origin: http://192.168.101.151 ``` ## 6. 成功响应 ```json { "code": "OK", "message": "success", "data": { "iccid": "898604E91123D1334901", "bindingStatus": "active", "idempotent": false, "boundAt": "2026-09-20T15:30:00" }, "requestId": "http-request-id", "timestamp": "2026-09-20T07:30:00Z" } ``` 字段说明: | 字段 | 说明 | | --- | --- | | `data.iccid` | 实际完成绑定的 ICCID | | `data.bindingStatus` | 成功时为 `active`,表示用户绑定关系有效 | | `data.idempotent` | `false` 表示本次新建绑定,`true` 表示重复请求已返回原绑定结果 | | `data.boundAt` | 绑定时间 | ## 7. 幂等重试规则 请求超时或网络断开时,使用同一个 `requestId` 重试,不要生成新的请求号: ```json { "iccid": "898604E91123D1334901", "requestId": "direct-bind-20260920-0001" } ``` 同一程序用户、同一 ICCID、同一 `requestId` 重试时,接口返回 `idempotent=true`。 同一 `requestId` 改用其他 ICCID 时,接口返回 `IDEMPOTENCY_CONFLICT`。 ## 8. 常见失败响应 ### 8.1 来源不在白名单 ```http HTTP/1.1 403 Forbidden ``` 可能原因: - 未携带 `Origin`; - Origin 协议不一致,例如配置了 HTTPS 但请求使用 HTTP; - IP 使用了非默认端口; - 后端环境变量未更新或服务未重启。 ### 8.2 未登录或 Token 无效 ```json { "code": "UNAUTHORIZED" } ``` 请确认使用的是程序用户登录返回的 `accessToken`,并且 Token 尚未过期或撤销。 ### 8.3 设备不存在 ```json { "code": "DEVICE_NOT_FOUND" } ``` 表示 ICCID 尚未登记到后端。该接口不会自动创建设备档案,应先通过设备接入或预置流程登记设备。 ### 8.4 设备已被其他用户绑定 ```json { "code": "DEVICE_ALREADY_BOUND" } ``` 不能通过该接口覆盖其他用户的绑定关系,需要先由当前绑定用户解绑或由管理员执行解绑流程。 ### 8.5 设备已禁用 ```json { "code": "DEVICE_DISABLED" } ``` 需要先恢复设备状态为 `active`。 ### 8.6 参数错误 ```json { "code": "INVALID_REQUEST" } ``` 请检查 `iccid` 是否为 18 至 22 位字母数字,以及 `requestId` 是否为空或超过 64 个字符。 ## 9. 浏览器联调注意事项 - 前端请求必须使用 `Authorization` 请求头携带 Token,不能只依赖 Cookie; - 页面来源必须与白名单完全一致; - 浏览器会先发送 CORS 预检请求,后端已经允许 `POST`、`OPTIONS` 以及 `Authorization`、`Content-Type`、`X-Request-Id` 请求头; - 如果通过后端服务调用,HTTP 客户端通常不会自动添加 `Origin`,需要按接口要求主动设置; - `Origin` 不是加密签名,不能替代登录 Token; - 联调日志中可以使用响应里的 `requestId` 联系后端排查。 ## 10. 验收检查 建议至少验证以下场景: 1. 使用 `https://banban.api.talkingq.com` 来源和有效程序用户 Token,绑定一个未绑定的 active ICCID,返回 `idempotent=false`。 2. 使用相同用户、相同 ICCID、相同 `requestId` 再次调用,返回 `idempotent=true`。 3. 使用其他 Origin 调用,返回 HTTP 403。 4. 不携带 `Authorization`,返回 `UNAUTHORIZED`。 5. 使用已绑定到其他用户的 ICCID,返回 `DEVICE_ALREADY_BOUND`。 6. 使用不存在的 ICCID,返回 `DEVICE_NOT_FOUND`。 7. 确认数据库中 `sim_card.binding_status` 为 `bound`,并且 `user_device_binding.status` 为 `active`。