Files
banban/docs/banban-4g-cross-miniapp-handoff-binding.md
T

179 lines
14 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.
# Banban 与 4G 小程序流量关联、联合解绑联调说明
当前契约以 [4G 后端接口文档 (2)](./设备直接绑定接口联调文档(2).md) 为准。本文描述 Banban 后端、Banban 小程序及 `/Users/stu2not/4g-miniprogram` 的实现;第 10 节记录服务器部署版本及验证范围,小程序发布状态需单独确认。
## 1. 业务规则
- Banban NFC 绑定成功即可正常使用设备;4G 关联允许失败、取消或稍后处理。
- 管理页查询原 4G 关系,只有后端确认关联有效才显示查询、充值入口。
- 用户确认联合解绑后,持久化解绑操作,阻止此绑定实例继续申请 handoff。
- Banban 必须调用 4G 关系关闭接口,并取得 `canUnbind=true` 才能解除本地设备绑定。
- 4G 已在其他入口解绑时,可直接确认后完成 Banban 解绑,不必再次跳转小程序。
- `unknown`、403、404、网络失败和不存在记录均不能作为已解绑依据。
- 不把客户端回报或当前用户设备列表中存在同一 ICCID 当作原关系有效的证明。
## 2. 身份与绑定实例
| 小程序 | AppID |
| --- | --- |
| Banban | `wx29ce99ce96b56cd9` |
| 4G 设备管理 | `wx56e1cfc52aab630f` |
两个小程序分别登录。服务密钥仅供后端使用,不进入小程序、页面参数或日志。开发版/体验版跳转目标体验版,正式版跳转目标正式版,两侧需协调发布。
设备每次重新建立 Banban 绑定时生成新的 `device_bindings.binding_instance_id`。加卡、关联宝贝保持原实例。原有 `sourceBindingId=banban:{binding_instance_id}` 保持稳定,查询/关闭路径对整个 ID 做 URL 编码,包括冒号;不能为旧关系换 ID 绕过关闭状态。
## 3. ICCID 来源与保存
设备成功的 `006` 回复:
```json
{
"msg_id": "006",
"status": "success",
"data": {
"uuid": "53D22B6DA20001",
"iccid": "898604E91123D1334901"
}
}
```
ICCID 为 18~22 位字母或数字,归一化为大写,持久化到 `device_settings.iccid`。准备操作时保存到操作表作为快照。不能从 IMEI 推导;设备换卡或其他设备占用同一 ICCID 时不覆盖原记录。
保存最多等待 2 秒;缺失、非法或保存失败不影响 NFC 绑定。日志记录 `006 ICCID present=..., valid=...`,保存成功记录 `006 ICCID persisted`,不打印完整 ICCID。当前仅接入 `006`,没有替固件实现上报或为历史设备自动回填。
## 4. Banban 接口与状态
所有接口使用 Banban 用户登录态,并校验设备归属账号。家庭成员不能执行关联、确认或解绑。
| 方法与路径 | 用途 |
| --- | --- |
| `GET /banban/joint-bindings/devices/{device_id}` | 本地当前绑定实例的操作概况 |
| `POST /banban/joint-bindings` | 创建/复用 `{device_id,action:bind/unbind}` 操作 |
| `GET /banban/joint-bindings/{operation_id}` | 读取本地操作 |
| `POST /banban/joint-bindings/{operation_id}/check` | 向 4G 查询原关系并更新本地确认状态 |
| `POST /banban/joint-bindings/{operation_id}/prepare` | 绑定申请 handoff;解绑先关闭关系,必要时返回 4G 跳转信息 |
| `POST /banban/joint-bindings/{operation_id}/result` | 接收客户端结果;成功仍需服务端查询确认 |
| `POST /banban/joint-bindings/{operation_id}/finish-unbind` | 重新确认关闭结果后,事务性完成 Banban 解绑 |
| `GET /banban/joint-bindings/devices/{device_id}/portal` | 查询原关系后返回查询/充值入口 |
原 `DELETE /banban/bindings/{device_id}` 返回 409,引导联合解绑。旧客户端需要更新才能使用新解绑流程。
操作的 `status=pending/partial_failed/completed` 不代表 Banban 是否已绑定。新增 `traffic_status=server_confirmed` 表示 4G 服务端关系查询/关闭已确认;旧的 `client_confirmed` 只代表客户端回报,不能授权解绑。字段已为 VARCHAR,无需为本次新增状态再加列。
绑定确认要求原关系为 `bound + open`。解绑确认要求 4G 明确返回 `canUnbind=true`,同时校验 `closing`、无待处理凭证、不再接受 handoff、状态为 `unbound/never_bound`。响应必须 HTTP 200、业务码 OK、sourceBindingId 匹配,布尔字段必须是布尔值,checkedAt 必须是合法 UTC 时间。
## 5. 流量关联流程
1. Banban NFC 绑定独立完成,用户自愿继续关联。
2. 后端使用服务凭证向 `POST /internal/v1/handoff-tokens` 申请凭证;sourceBindingId 始终对应同一 ICCID。
3. 4G 小程序使用自身登录态调用 `POST /api/v1/program/bindings/handoff`;原消费请求重试必须沿用 token 与业务 requestId。
4. 客户端返回经过字段匹配的结果,Banban 再使用 GET 关系接口确认原绑定,确认后显示查询/充值入口。
5. 申请返回 `HANDOFF_TOKEN_USED` 时,Banban 直接查询原关系;只有原绑定仍有效才恢复成功,不再跳转后按当前 ICCID 设备列表猜测。
6. 4G 小程序即使保存过成功结果,再次提交也让 4G 后端校验原 token,防止旧回执关联到后来的其他绑定。旧 recoveryOnly 入口提示返回 Banban 确认。
关联失败不回滚 Banban 绑定。管理页刷新和打开查询/充值入口都会检查原关系,4G 侧独立解绑后不会继续把旧缓存当作有效关联。
## 6. 联合解绑流程
1. 用户确认后创建持久化 `action=unbind` 操作;此记录阻止同实例再申请 handoff。
2. 用户点击继续,调用 `POST /internal/v1/binding-relations/{sourceBindingId}/close`,业务请求号为 `bb-close-{operation_id}`。
3. 返回 `canUnbind=true`:无需跳转,直接执行本地解绑。此判断仅依赖原关系,ICCID 缺失也不会挡住已经得到的服务端确认。
4. 返回 `bound`:保留 Banban 绑定,携带原 ICCID 跳转 4G 小程序,用户用自己的 4G 登录态调用现有 unbind 接口。
5. 用户返回、重新打开页面或在 4G 其他入口完成解绑后,可以主动查询/继续处理;最终提交前再次调用幂等 close,只有确认放行才执行本地事务。
6. 本地设备解绑、家庭关联停用、操作完成在同一事务内提交。失败保留已确认状态,重试仍检查原关系;已完成操作可幂等返回。
7. 未知关系、鉴权错误、限流、网络异常均保留绑定,不执行自动猜测或无限重试。
| 4G 状态 | 处理 |
| --- | --- |
| 原绑定仍有效 | 保留 Banban,前往 4G 解绑 |
| 原绑定已解除 | 关闭后确认 canUnbind=true,再解绑 Banban |
| 有记录证明从未完成绑定 | 撤销待消费凭证并确认放行 |
| 没有关系记录或历史不明确 | unknown,不自动放行,需核查 |
关闭不会自动解除 4G 用户绑定或删除套餐。它会持续禁止该 sourceBindingId 签发/消费 handoff,文档没有取消关闭接口。页面明确提示继续后不能恢复本次关联;“稍后处理”只是离开页面,不恢复已关闭关系。
4G 设备详情页对本机留有 Banban 标记的设备引导回 Banban;清缓存/换手机无法依靠该本地标记拦截所有独立解绑,服务端关系查询负责后续确认。
## 7. 有限等待与并发
| 环节 | 上限 |
| --- | --- |
| 006 保存 ICCID | 2 秒,失败继续 NFC 绑定 |
| Banban 新业务接口 | 8 秒总期限 |
| Banban 调用 4G | 默认 6 秒网络超时,外层受 8 秒期限约束 |
| Banban 小程序 HTTP | 10 秒,平台超时及独立计时器 |
| 4G 微信登录 | 10 秒 |
| 4G 每次 HTTP | 15 秒,超时终止请求 |
| 4G 单次操作 | 45 秒 |
| 页面及跨小程序跳转 | 15 秒 |
无自动无限循环或长期轮询,失败后用户主动重试。关闭按 sourceBindingId 幂等;超时不换绑定实例或业务请求号。
Banban 不跨网络持有数据库行锁:先读取、关闭事务,再请求 4G,返回后重新校验当前设备所有者和绑定实例。最终事务再次校验操作。4G 端关闭与签发/消费的互斥依赖新版后端的关系锁;本地模拟测试不能替代这部分 MySQL 并发验收。
## 8. 配置与数据库
```dotenv
TRAFFIC_4G_API_BASE_URL=https://role.api.talkingq.com/traffic-4g
BANBAN_HANDOFF_SERVICE_TOKEN=<两侧一致的服务端密钥>
TRAFFIC_4G_TARGET_APP_ID=wx56e1cfc52aab630f
TRAFFIC_4G_TIMEOUT_SECONDS=6
```
查询和关闭沿用现有密钥。必须先部署 4G 新 JAR、V13 迁移及 Nginx 新路由;仅替换 JAR 不保证公网接口可用。历史关系无法准确回填时保持 unknown。
Banban 首次安装本功能:已有库执行一次 `talkingq-url/mysql/migrations/20260923_joint_binding.sql`;新库使用 `mysql/init/02-init.sql` 与 `03-joint-binding.sql`。已执行迁移的生产库不要重跑 ADD COLUMN。本次查询/关闭接入不需要新增 Banban 数据库迁移。
## 9. 本地验证与发布
```bash
# talkingq-url(既有告警测试断言此测试地址,不需要真实 .env)
SERVER_HOST=192.168.101.78 .venv/bin/python -m pytest tests -q
# banban-mini
npm run test:ui
npm run build:weapp
# 4g-miniprogram
node --test tests/*.test.cjs
```
覆盖:原关系解绑、从未绑定、未知关系、旧客户端成功回执、原关系与后来 ICCID 绑定区别、关闭失败重试、事务回滚、重绑后的旧响应、格式/鉴权/限流/超时、页面恢复及跳转计时。
状态事务测试使用 SQLite 适配,HTTP 使用模拟传输;没有访问生产 4G 接口或验证真实 MySQL 锁竞争。微信登录、真机跨小程序返回、V13 历史回填和真实关闭/消费并发需部署到联调环境后验收。伴伴构建可执行,全量 TypeScript 检查有既有类型错误,不将构建通过等同于全量类型检查通过。
2026-09-23 首次提交内容在独立目录验证:后端 142 项通过,Banban 小程序 30 项通过且微信小程序构建成功,4G 小程序 16 项通过且全部页面脚本/JSON 校验通过。全量 TypeScript 检查的 6 项错误与修改前 HEAD 一致。后续服务器部署及兼容修复验证见第 10 节。
## 10. 2026-09-23 服务器更新记录
按用户授权,于北京时间 14:21 完成 Banban 后端更新,范围为本功能相关的 8 个 Python 文件和已有库迁移脚本。未上传两个小程序,未配置 4G 服务密钥。
服务器:`101.35.224.118`,应用目录:`/home/ubuntu/banban-backend`。
备份目录:`/home/ubuntu/banban-backups/joint-binding-20260923-1411`,仅 root 可读:
- `code-and-config.tar.gz`:更新前代码与配置,排除虚拟环境、资源、运行数据和日志。
- `binding-tables.sql.gz`:`device_settings`、`device_bindings`、`device_family_members`、`device_bind_history` 的一致性快照。
- `replaced-files/`、`manifest-before.json`、`manifest-after.json`:被替换文件和更新前后 SHA-256 清单。
迁移后四表记录数仍为 63、22、33、144;22 条绑定均生成互不重复的绑定实例 UUID。新操作表为空,未为了验证而创建或解除真实绑定。
更新后验证:进程 `511137` 启动成功,MQTT 重新连接;7 个新接口路径已注册;设备所有者状态查询与原绑定查询均为 HTTP 200;未关联设备的查询/充值跳转接口返回预期 409;未登录访问返回 401;公网 HTTPS OpenAPI 和绑定提示音均为 200。
截至 14:22,`TalkingQ_ZHWK00003111` 的 ICCID 仍为空,尚无更新后的 `006 ICCID` 检测日志,需要设备重新上报。`BANBAN_HANDOFF_SERVICE_TOKEN` 仍未配置,申请 4G 凭证暂不可用。
14:25 按用户提供的密钥配置 `BANBAN_HANDOFF_SERVICE_TOKEN`。更新前 `.env` 备份于 `/home/ubuntu/banban-backups/handoff-token-20260923-1424/env.before`;其他配置项保持原值,现有 `.env` 权限收紧为 600。独立进程校验有效配置与提供值一致,重启后进程为 `512483`,HTTPS OpenAPI 为 200,MQTT 连接正常。密钥值不记录到文档或仓库。
4G 申请接口空请求分别在无密钥、有密钥时均返回 400 `INVALID_REQUEST`,说明该检查无法证明密钥鉴权成功;未提交有效 ICCID 或创建交接凭证,实际关联仍待正常业务请求验证。
### 17:58 新版关系查询与关闭流程部署
按用户后续授权部署 `515567f` 的关系校验流程,并补充 `9cb7195` 的 Python 3.10 时间戳兼容修复。与下午旧版本相比,仅替换 `banban/routers/joint_binding.py`、`banban/service/joint_binding.py`、`banban/service/traffic_handoff.py` 三个文件;已有数据库结构满足要求,未重复执行迁移,`.env` 及服务密钥保持不变。
更新前备份位于 `/home/ubuntu/banban-backups/relation-check-20260923-1755`,包含代码与配置归档、五张绑定相关表的一致性 SQL 快照、替换文件及哈希清单;兼容修复前的二次备份和最终 `verification.json` 位于其 `compat-fix/` 子目录。目录仅 root 可访问。
首次线上验证发现 4G Java 服务的 `checkedAt` 使用纳秒精度,服务器 Python 3.10 的 `datetime.fromisoformat` 无法解析;修复为分别校验 UTC 时间格式和日历值,保留原始精度。修复后本地后端 151 项测试通过,服务器 Python 3.10 的 9 个时间戳有效/无效用例通过。
最终进程为 `563609`。本机及公网 HTTPS OpenAPI 均为 200,8 条联合绑定路由已注册;未登录校验请求为 401,已登录不存在操作的校验请求为预期 404 `OPERATION_NOT_FOUND`;现有绑定查询、联合绑定概览及绑定提示音均为 200;启动和 MQTT 连接正常。部署文件哈希与提交一致,配置哈希保持不变。
使用既有服务密钥执行真实 4G 关系 GET,返回 200 `OK`,新版适配器成功解析:3111 当时为 `bound / open / canUnbind=false`。另一个历史关系只读抽查为 `unknown`,不能据此允许伴伴解绑。最终验证前后五表记录数一致,未创建测试绑定、关闭真实关系或执行实际解绑;4G `/close` 的真实关闭效果、消费并发及微信真机往返仍需业务联调验证。本次未部署 4G JAR/V13,也未上传或发布两个小程序。