## 1. 只读表与存储 - [x] 1.1 新增成对迁移:建 `tb_gateway_readonly_card`(`id`、`identifier` 唯一、`identifier_type`(iccid / imei)、`source`、`remark`、`created_at`),注释写明清理顺序文档;down 删表;不写入数据。 - [x] 1.2 新增 model 与 `internal/store/postgres` 存储:按卡号(大写)判断是否只读资产;批量写入(`ON CONFLICT DO NOTHING`)供导入脚本复用。 ## 2. 网关拦截 - [x] 2.1 `internal/gateway/readonly_guard.go` 改为函数接缝 `func(ctx, cardNo) (bool, error)`:查询类路径(`/flow-card/status`、`/flow-card/realName`、`/flow-card/flow`、`/device/info`、`/device/slot-info`、`/device/sync-info`)直接放行不查表;其余路径取请求 `cardNo`(大写)查表,命中或查表失败则本地拦截,返回 `upstream` 类别的明确失败并记录 `gateway.readonly_card.blocked` 日志。 - [x] 2.2 删除 `WithReadonlyCards` 与内存名单;`doRequest` / `doRequestWithoutPayloadLog` 改调新判断(需 ctx)。 ## 3. 配置与部署 - [x] 3.1 `pkg/config`:删除 `ReadonlyICCIDs` 与 `gateway.readonly_iccids` 绑定,新增 `ReadonlyEnabled`(`gateway.readonly_enabled` → `JUNHONG_GATEWAY_READONLY_ENABLED`)。 - [x] 3.2 `cmd/api/main.go`、`cmd/worker/main.go`:开关开启时注入只读表判断,并在启动日志说明只读模式已开启。 - [x] 3.3 `docker-compose.prod.yml` 传递 `JUNHONG_GATEWAY_READONLY_ENABLED`、删除旧变量;`.gitea/workflows/deploy.yaml` 改为 `JUNHONG_GATEWAY_READONLY_ENABLED=true`。 ## 4. 切换(测试环境,先于推送) - [x] 4.1 按 ENG-TEST-001 在测试库执行迁移 up/down/up,核对表结构。(10-29 执行 268→269→268→269,版本 269) - [x] 4.2 测试库写入现有两张只读卡(`89861590172420410612`、`89861590172420420169`)。 - [x] 4.3 `gofmt`、`go build ./cmd/api ./cmd/worker`、`openspec validate add-gateway-readonly-prod-import --strict`;提交并推送,以 Gitea Actions 运行成功确认部署。(提交 5cf67b98,Gitea Actions 运行 1313 于 17:05:33 成功) - [x] 4.4 部署后核对:只读卡的流量、卡状态、实名查询正常,只读设备详情实时状态正常;对只读卡发起一次停机(后台按钮)被拦截、未发出请求、卡状态未变;对非只读测试卡的查询不受影响。(10-29 17:06:只读假卡 89860000000000009999 后台停机返回"测试环境只读资产,已拦截调用:/flow-card/cardStop"、卡状态未变;两张只读卡查询正常、无停复机记录;假卡已删除) ## 5. 全量导入(临时脚本 `cmd/zz_tmp_*`,不提交) - [x] 5.1 生产只读导出:卡、设备、有效绑定、卡套餐与设备套餐(待生效 / 生效中 / 已用完)、通道编码与重置日。(10-29 经 dbhub 分片导出:卡 98,567、设备 20,144、绑定 38,667、套餐 28,681、通道 36) - [x] 5.2 通道映射:按 `carrier_code` 映射到测试库通道,缺失的按生产配置补建;输出映射与未映射清单。(SBK01 重置日按生产改为 27;新建 28 个通道;36 个全部映射) - [x] 5.3 写只读表:全部卡 ICCID 与设备 IMEI;核对行数与导出一致。(ICCID 98,567、IMEI 20,139 去重后全部写入,缺失 0) - [x] 5.4 导入卡与资产标识(ICCID、虚拟号)、设备与绑定,全部挂测试代理 9「测试采购部」,手机号、虚拟号按原值;同卡号已存在则跳过并记录;把测试卡 12244、12245 也挂到代理 9。(新卡 98,558、设备 20,143、绑定 38,664;已存在的 9 张卡与 1 台设备、3 条绑定跳过;12244、12245 挂代理 9) - [x] 5.5 导入套餐使用记录:保留开始与结束时间、真实已用量、真总量、虚总量、展示倍率、是否启用虚流量、重置周期与下次重置时间、优先级、主套餐 / 加油包关系;订单号 `PROD-<原订单号>`、订单 ID 占位、`package_id` 指向测试占位套餐。(导入 28,676;4,242 条生产计时快照为空,按同名套餐口径补 from_purchase + by_day 30/365 天;订单号前缀 P-;122 个加油包关联主套餐) - [x] 5.6 全部导入卡置"待建起点";触发轮询重新加载并确认导入卡进入调度。(重新保存轮询配置 29 触发加载;配置 29 只匹配通道 1587 已激活普通卡,导入卡中 3,943 张进入调度,19:05 已有 213 张完成流量检查) - [ ] 5.7 导入后核对:卡、设备、绑定、套餐行数与导出一致;抽查 10 张卡的套餐时间与已用量与生产一致;导入后 1 小时内停复机记录全部为拦截失败、无发出的写请求。 ## 6. 差距报表 - [x] 6.1 记录导入时刻生产各套餐真实已用量作为起点。(起点 = 导出时生产套餐真实已用量,已随套餐导入测试库 data_usage_mb;原始导出与导入脚本留存在 ~/prod-import-20261029/,测试库按 order_id − 800000000 对应生产套餐 ID) - [ ] 6.2 运行至少 24 小时后,按套餐(设备套餐按设备合并)输出:测试环境新增扣减、生产同期新增扣减、差额、是否已到停机线;汇总卡住卡数、总差额(GB)、按新逻辑已到停机线但生产未停的套餐数。 - [ ] 6.3 用 flowcollector 本月读数给出导入前差距上限,与报表一并提交。