Files
banban/docs/设备直接绑定接口联调文档.md

6.7 KiB
Raw Permalink Blame History

设备直接绑定接口联调文档

1. 接口说明

该接口用于设备无法正常获取绑定验证码时,由白名单来源的业务页面发起直接绑定。

接口完成的业务效果与验证码绑定一致:

  • 创建当前程序用户与设备的有效绑定关系;
  • 将设备绑定状态更新为 bound;
  • 作废该设备尚未使用的绑定验证码;
  • 清理绑定验证码缓存;
  • 写入绑定审计记录。

接口不会自动创建不存在的 ICCID。调用前,设备必须已经登记在后端 sim_card 表中,并且设备状态为 active。

2. 接口地址

POST /api/v1/program/bindings/by-iccid

生产环境示例:

https://<traffic-api-domain>/api/v1/program/bindings/by-iccid

将 <traffic-api-domain> 替换为实际 4G 管理后端域名。

3. 调用前提

3.1 程序用户登录

调用方必须先通过程序用户登录接口获取 PROGRAM_USER 的 accessToken:

POST /api/v1/program/auth/login

后续请求携带:

Authorization: Bearer <accessToken>

不能使用 SUPER_ADMIN、FACTORY_PROVISIONER 或设备 Token 调用此接口。

3.2 来源白名单

当前默认允许以下 Origin:

https://banban.api.talkingq.com
http://192.168.101.151

请求必须携带与页面实际来源完全一致的 Origin 请求头:

Origin: https://banban.api.talkingq.com

或:

Origin: http://192.168.101.151

白名单由环境变量配置:

BINDING_ALLOWED_ORIGINS=https://banban.api.talkingq.com,http://192.168.101.151

多个来源使用英文逗号分隔。来源匹配包含协议、主机和端口,不能只填写裸域名或裸 IP。

例如,下面这些值不是同一个 Origin:

http://192.168.101.151
https://192.168.101.151
http://192.168.101.151:8080

如果页面实际通过 HTTPS 或非默认端口访问,需要把实际 Origin 加入 BINDING_ALLOWED_ORIGINS 后重启后端。

4. 请求格式

4.1 请求头

Origin: https://banban.api.talkingq.com
Authorization: Bearer <PROGRAM_USER_ACCESS_TOKEN>
Content-Type: application/json
X-Request-Id: direct-bind-20260920-0001

Origin 是来源白名单校验字段;Authorization 是业务身份认证字段;X-Request-Id 用于日志追踪,建议每次请求使用唯一值。

4.2 请求体

{
  "iccid": "898604E91123D1334901",
  "requestId": "direct-bind-20260920-0001"
}

字段说明:

字段 类型 必填 说明
iccid string 是 设备 ICCID,18 至 22 位字母或数字,后端会统一转为大写
requestId string 是 幂等请求号,最长 64 个字符;同一用户重试必须使用相同值

5. curl 联调示例

请将 Token 和 ICCID 替换为真实值,不要把 Token 提交到代码仓库或群聊。

curl -i -X POST \
  "https://<traffic-api-domain>/api/v1/program/bindings/by-iccid" \
  -H "Origin: https://banban.api.talkingq.com" \
  -H "Authorization: Bearer <PROGRAM_USER_ACCESS_TOKEN>" \
  -H "Content-Type: application/json" \
  -H "X-Request-Id: direct-bind-20260920-0001" \
  --data '{
    "iccid": "898604E91123D1334901",
    "requestId": "direct-bind-20260920-0001"
  }'

使用 IP 来源联调时,将 Origin 改为:

Origin: http://192.168.101.151

6. 成功响应

{
  "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 重试,不要生成新的请求号:

{
  "iccid": "898604E91123D1334901",
  "requestId": "direct-bind-20260920-0001"
}

同一程序用户、同一 ICCID、同一 requestId 重试时,接口返回 idempotent=true。

同一 requestId 改用其他 ICCID 时,接口返回 IDEMPOTENCY_CONFLICT。

8. 常见失败响应

8.1 来源不在白名单

HTTP/1.1 403 Forbidden

可能原因:

  • 未携带 Origin;
  • Origin 协议不一致,例如配置了 HTTPS 但请求使用 HTTP;
  • IP 使用了非默认端口;
  • 后端环境变量未更新或服务未重启。

8.2 未登录或 Token 无效

{
  "code": "UNAUTHORIZED"
}

请确认使用的是程序用户登录返回的 accessToken,并且 Token 尚未过期或撤销。

8.3 设备不存在

{
  "code": "DEVICE_NOT_FOUND"
}

表示 ICCID 尚未登记到后端。该接口不会自动创建设备档案,应先通过设备接入或预置流程登记设备。

8.4 设备已被其他用户绑定

{
  "code": "DEVICE_ALREADY_BOUND"
}

不能通过该接口覆盖其他用户的绑定关系,需要先由当前绑定用户解绑或由管理员执行解绑流程。

8.5 设备已禁用

{
  "code": "DEVICE_DISABLED"
}

需要先恢复设备状态为 active。

8.6 参数错误

{
  "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。