Files
iotlcm/docs/plans/telecom-v3-integration-and-switch.md
T
2026-10-29 14:59:32 +08:00

15 KiB
Raw Blame History

电信 v1 现状、v3 并行接入与切换

调查日期:2026-10-10。代码依据为当前本地三个服务仓库;数据库通过 DBX 只读查询 开发环境(127.0.0.1).db_iotlcm。未修改数据库、未部署、未调用电信真实查询或操作接口。

接口依据:用户提供的《中国电信天翼物联5GCMP能力调用参考文档-V3.pdf》,464 页,封面版本 V3.2026.1.7;《iotlcm上游API静态审计明细(41条审计记录).xlsx》,其中 21 条为电信记录,其余为移动记录。推荐路径以 PDF 接口定义为准;示例与参数表冲突的地方单独记录。

1. 原有设计

sim_dx_v1 是协议模板标识,不是某一个账号通道编号。模板表中的名称确实是“电信BOSS卡API”。同一模板被多个独立账号通道复用。

flowchart LR
  CARD["卡: chan_key"] --> CHANNEL["t_sim_channels: chan_key / temp_key / 凭证"]
  CHANNEL --> TEMPLATE["t_sim_channel_template: temp_key / temp_model / temp_config"]
  TEMPLATE --> PROXY["upstream-proxy: 注册模板并转发统一请求"]
  PROXY --> CHAN["upstream-chan: 创建协议实例"]
  CHAN --> V1["sim_dx_v1: 旧接口"]
  CHAN --> V3["sim_dx_v3: 新接口"]

运行时按 chan_key 路由,按模板 temp_key 精确创建实例。temp_model=direct 只是接入类型,不能代替具体协议实现。对外仍然使用统一 SimCardChannelI,批量能力使用 SimCardBatchQueryI,因此业务调用者无需知道电信 URL 或版本。

主要代码位置:

模块 代码 职责
chan internal/logic/upstreamimpl/sim_dx.go、sim_dx_batch.go 原 v1 请求、XML/JSON 解析与状态转换
chan internal/logic/upstreamimpl/dx/client.go、sign.go AppKey、Sign、GMT Timestamp 鉴权
chan/proxy internal/logic/channelconfig/channel_config.go 读取数据库配置、计算版本、缓存快照
chan/proxy internal/logic/upstreamapi/upstreamapi.go 模板注册、按通道创建实例、热刷新
proxy internal/logic/upstreamimpl/sim_dx.go 同路径转发统一接口给 chan,实际运营商调用在 chan
admin server/internal/logic/iot/t_sim_channels.go 保存通道后发布刷新消息
worker internal/consumer/rabbitmq/channel_refresh.go 消费刷新消息,先刷新 gateway,再刷新 proxy

v1 的网关地址优先取 temp_config.api.baseUrl,其次兼容通道配置 baseURL/url,最后取 dependencies.sim_dx_v1.url 或默认 https://cmp-api.ctwing.cn:20164。凭证来自通道列 app_key/app_secret;批量接口客户编码来自 chan_config.custNumber。

开发库现状

  • 仅有电信模板 sim_dx_v1,temp_model=direct,网关地址为上述默认地址;查询时 temp_status=0。运行时创建的主要条件是 is_normal=0,不能只根据模板状态推断是否调用上游。
  • 19 个通道使用 v1,全部有非空 AppKey/AppSecret。16 个 is_normal=0、2 个 is_normal=1、1 个为 NULL。后两类不能创建正常运行实例。
  • 9 个通道 chan_config 含 custNumber,10 个为空对象;未确认这些客户编码是否对应新版 custNbr,不能仅凭名称自动替换实际值。
  • 19 个通道的 sync_enabled=1。当前字典语义为 0=允许轮询、1=不允许,切换模板不应顺带开启批量同步。
  • 17 个通道 operator=3、2 个为 1;开发库 operators/t_operators 字典均为 1=移动、2=联通、3=电信,而 worker 原部分辅助逻辑把 2 当电信。此次只对已知电信模板优先识别,其他运营商字典问题需单独处理。
  • 按租户和 chan_key 关联 t_sim_card_info 后,19 个通道卡量均为 0;这只是开发库快照,不代表生产无卡。真实切换前须单独核实目标环境库存卡、业务卡与在途任务。

2. 新增实现与接口对应

新增 sim_dx_v3.go、sim_dx_v3_batch.go。保留 sim_dx_v1 注册和所有旧路径。proxy 同时注册两个模板;v3 使用独立适配器,不把 v1 路径机械替换成 /v3/。

v3 同样使用 AppKey、Sign、Timestamp。文档第 10-12 页 Java 示例为:排序查询参数,拼接原样 body、SecretKey、GMT 时间戳,先生成小写 MD5 字符串,再把该字符串字节做十六进制编码并转大写。新增 BuildSignV3 显式遵循该例;v1 签名保持原样。业务 envelope 使用 code/message/result,逐卡 batch 的 result=0 表示成功。HTTP 799 的错误码从 gw-errorCode header 保留到统一 errCode。

功能 v1 实际路径/行为 v3 当前接入 PDF 页
单卡资料 /openapi/v1/prodinst/getSIMList GET /api/v3/getSimInfo 14-23
停机原因 复用基础资料 StopTypeList 读取 getSimInfo 的 stopStatusName/stopTypeName,兼容 stopTypeList 15-16
会话 多种 qryOnlineSataus/qryOnlineStatus 组合 GET /api/v3/getOnlineStatus;1=在线、2=离线 31-35
IMEI /openapi/v1/prodinst/gete2einfo GET /api/v3/getImei 44-45
单卡实时当月流量 /api/v1/openbill/queryTraffic,XML GET /api/v3/getDataUsage;dataAmount 单位 MB 107-110
历史月流量 queryTrafficByDate GET /api/v3/getDataUsageByMonth,month=yyyyMM 111-114
历史日流量 queryTrafficByDate GET /api/v3/getDataUsageBetweenDate,beginDate=endDate=yyyyMMdd 129-132
批量日流量 batchQryFlowByDate POST /api/v3/getDataUsageByDayBatch 144-146
批量月流量 batchQryFlowByMonth POST /api/v3/getDataUsageByMonthBatch 147-149
APN 列表 复用会话 ApnId GET /api/v3/getApnInfo,apnInfoList,0停用/1正常 159-161
终端开关机 POST /api/v1/prod/getDevicePowerStatus GET /api/v3/getDevicePowerStatus;仅 3G,保持统一设备健康未知。⚠️网关实测只接受 GET(官方文档写 POST 与网关不符;同一网关下 v1 的同名接口反而只接受 POST) 190-192
整卡数据开停 GET singleCutNet,DEL开/ADD停 POST /api/v3/singleCutNet;平台开0→上游1、停1→上游2 198-200
卡状态 queryCardMainStatus GET /api/v3/getSimStatus 317-321
停复机 disabledNumberService,orderTypeId=19/20 GET /api/v3/updateSimStatus,operType=19/20 321-323
批量卡状态 batchQrySimStatus,旧分块10000 POST /api/v3/getSimStatusBatch,新分块最多100 328-333
机卡绑定 chkDeviceBindingInfo GET /api/v3/getSimBindingInfo 374-376
区域限制 qryAreaLimitInfo GET /api/v3/getSimRegionLimitStatus,含 restrictedArea 381-383
冻结状态 code=0 + “不支持查询” code=0 + “不支持查询”,与 v1 对齐 电信无独立冻结查询接口
达量限速诊断 code=5000 + “暂不提供” code=5000 + “暂不提供”,使诊断页降级为跳过 电信无达量限速档位查询

批量请求使用 msisdnList 和 custNbr,按 MSISDN 匹配返回数据并保持输入顺序;漏卡、重复卡、逐卡失败、日期错配和非 MB 单位返回失败。业务失败保留运营商原始错误码;缺失 code/result、无效金额不会被当成成功或零流量。

停复机与整卡数据开停默认由 api.enableOperations=false 关闭。开启后仅返回交易受理号,不代表已完成运营商状态变更;完成状态须按订单/状态查询确认。本次没有新增订单轮询工作流。

明确保留的限制

  1. 批量资料 batchQrySimInfo 的参数表写 result、逐卡成功1,但示例使用 data.qrySimInfoList、请求示例又写 batchQrySimStatus。暂不调用这个有冲突的端点,基础资料走单卡 getSimInfo。新版基础资料与旧 batch 的全部扩展字段不完全一致,迁移验收须核对订购产品、停机原因码、区域/绑定信息完整性。
  2. 上层 /sim/query/sim-datausage-monthly-batch 仍保留原“仅当月”的校验;v3 适配器可处理近6个月,但对外历史月查询目前继续使用单卡 getDataUsageByMonth。扩展公共 batch 历史窗口须另行调整通道能力校验,不能直接扩大 v1 的窗口。
  3. 历史日接口要求已结束日期;batch 只查近7个已结束日。当日接口 getDataUsageOfToday 不接入当前历史日契约。文档示例 totalUsageAmount 与参数表 dataAmount 不一致,金额解析兼容两者。
  4. 指定 APN 开停、单卡多 APN 模板操作与“整卡单独断网”语义不同:singleCutNet 只有整卡数据开停语义,没有“按 APN 定向”维度。与 v1 一致,忽略请求中的 ApnName(预警自动断网、批量数据开停都会固定携带 apnName="CMIOT",若据此拒绝会直接打断现有主流程)。新增 APN 管理需独立确认业务契约。 (2026-10-14 更正:v1 在 2026-10-10 b3f00a0 曾被改成按 APN 定向的 /api/v3/updateApnFunctionStatus,导致电信数据开停全部失败,已回退为 singleCutNet;此处“与 v1 一致”现重新成立。)
  5. 新版终端开关机仅3G,不能据此宣称4G/5G终端健康;机卡绑定断网状态与分离状态也不能互换语义。
  6. 不自动从 v3 回退并重发 v1 写操作。网络超时可能已经受理,重复调用会产生重复操作。
  7. getSimInfo 参数表含 ICCID/IMSI,但示例只返回 MSISDN。基础资料查询允许返回部分三码且不编造缺失值;现有后台单码补全流程仍要求完整三码,真实响应不足时会明确失败。必须通过授权测试卡核实这项差异后再迁移依赖三码补全的流程。

3. 快速切换与回退

复用通道模板作为版本开关:保留同一个 chan_key,把目标通道的 temp_key 从 sim_dx_v1 改成 sim_dx_v3。不用迁移卡表、库存关联、ICCID→通道 Redis 映射,也不需要在切换时重新部署。不同通道可选择不同版本,但同一通道全部卡共用该版本。

上线前一次性部署两个模板都存在的新 chan/proxy 及 worker。随后在后台新增模板:temp_key=sim_dx_v3、temp_model=direct,配置采用 docs/config/sim_dx_v3.json。通道凭证仍放 app_key/app_secret;客户编码用 chan_config.custNbr,代码兼容旧字段 custNumber。未把任何 v3 模板或通道记录写入开发库。

切换步骤:

  1. 保存目标通道的原 temp_key、app_key/app_secret、chan_config、is_normal、同步配置快照到受控存储;快照不写日志。确认账号已获 v3 能力授权、新旧凭证是否相同,以及新版 custNbr 的正确值。
  2. 新建测试用 v3 通道并保持非运行状态,使用后台通道测试创建临时实例,以完整三码和单码补全验证只读查询。该测试路径允许读取非运行通道,不注册生产实例;开发库目前无关联业务卡,需准备有授权的测试卡。
  3. 选择一个目标通道灰度。避免正在进行的管理操作和大批量同步;修改 temp_key,必要时同时更新新凭证/custNbr,保持 chan_key。不得顺带调整 sync_enabled 或 is_normal 来扩大操作范围。
  4. 通过后台保存,使事务完成后投递 QueueChannelRefresh。worker 先调用 gateway 的 POST /internal/channel/refresh?chanKey=...,再刷新 proxy,失败会重试。后台“保存成功”只说明消息已投递,仍需核对 worker 及两个上游的刷新结果。
  5. 核对只读查询、批量拆分、状态/会话映射、流量数值与单位、错误率和响应延迟。需要启用写操作时先在受控测试卡验证,再打开 enableOperations;用交易流水和状态查询确认最终结果。
  6. 逐通道推广。回退时恢复完整旧快照并重新保存/刷新;如果凭证相同且无其他配置变化,可只恢复 temp_key=sim_dx_v1。

运行中的实例以互斥锁替换,已取得旧实例的请求可继续完成,新请求使用新实例。但 gateway/proxy 两个服务之间不是分布式原子切换,RabbitMQ 消息也不是多实例广播。多副本部署须刷新每个实例并确认配置版本一致,当前地址的一次 HTTP 调用不能证明所有副本已经更新。刷新端点只能在现有内部控制网络调用。

配置缓存默认全量重读2小时,实例扫描默认60秒。直接执行数据库 UPDATE 不会投递刷新消息,不能用于即时切换;必须走后台保存或显式内部刷新。此次把凭证和 token 内容加入 chan/proxy 的配置版本哈希,防止仅更换凭证时实例版本不变;哈希异常日志不输出配置及凭证。

队列任务持有投递时的 temp_key 快照,而上游运行时按最新 chan_key 配置执行;切换前宜处理在途任务,新任务重新读取通道配置。本次统一 v1/v3 的 worker 电信模板分类,涵盖限速、月流量失败分类、状态重试、失败记录和入库限速;未知未来模板仍是 generic。v3 的 S00004 错误策略沿用原 v1 策略,真实账号联调需确认上游语义和 QPS,不把默认25 QPS视作电信承诺限额。

4. 验证与上线边界

新增测试覆盖 v1/v3 模板并存、配置校验、签名、在线枚举、实时流量 MB、异常金额/缺失结果/卡号错配、HTTP799与业务码、101张卡拆两批、乱序/漏卡/逐卡失败、错月份、历史日参数、部分三码、APN/绑定/区域/开关机字段、操作默认关闭及新参数映射。chan/proxy 的配置版本及刷新测试通过,worker 的入库限速/月流量/状态/失败记录测试通过;三个服务入口均完成编译验证。

worker 的完整 ZSet 测试曾出现租约定时测试失败(单独重跑通过)及 TestMonthlyChannelBlockKeepsExistingCountingPolicy 的 Redis Lua整数参数失败。通过 Go overlay 加载 HEAD:internal/queue/zset/run.go 原始代码后,后者同样复现,确认不是本次队列分类改动引入。本次未修改其 Lua 协议,仍需单独排查,不能宣称全量测试通过。

验证命令:

# iotlcm-upstream-chan
go test ./internal/logic/upstreamimpl ./internal/logic/upstreamimpl/dx ./internal/logic/channelconfig ./internal/logic/upstreamapi
# iotlcm-upstream-proxy
go test ./internal/logic/channelconfig ./internal/logic/upstreamapi ./internal/logic/upstreamimpl
# iotlcm-worker-svr
go test ./internal/service/inbound ./internal/service/monthlyusage ./internal/service/simstatus ./internal/queue/zset
go test ./internal/consumer/zset -run 'TestPolicyForTemplate|TestLostBatchLeasePreventsApplyAndAck' -count=1
# 三个仓库分别编译入口,不启动服务
go test -run '^$' .

本次为代码实现及本地契约验证。真实电信 API 的鉴权授权、字段差异、custNbr、频控、运营商订单完成状态及多副本热刷新仍需部署后联调。没有修改数据库、切换通道或调用真实停复机/断网。