15 KiB
电信 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 关闭。开启后仅返回交易受理号,不代表已完成运营商状态变更;完成状态须按订单/状态查询确认。本次没有新增订单轮询工作流。
明确保留的限制
- 批量资料
batchQrySimInfo的参数表写 result、逐卡成功1,但示例使用 data.qrySimInfoList、请求示例又写 batchQrySimStatus。暂不调用这个有冲突的端点,基础资料走单卡 getSimInfo。新版基础资料与旧 batch 的全部扩展字段不完全一致,迁移验收须核对订购产品、停机原因码、区域/绑定信息完整性。 - 上层
/sim/query/sim-datausage-monthly-batch仍保留原“仅当月”的校验;v3 适配器可处理近6个月,但对外历史月查询目前继续使用单卡 getDataUsageByMonth。扩展公共 batch 历史窗口须另行调整通道能力校验,不能直接扩大 v1 的窗口。 - 历史日接口要求已结束日期;batch 只查近7个已结束日。当日接口 getDataUsageOfToday 不接入当前历史日契约。文档示例 totalUsageAmount 与参数表 dataAmount 不一致,金额解析兼容两者。
- 指定 APN 开停、单卡多 APN 模板操作与“整卡单独断网”语义不同:
singleCutNet只有整卡数据开停语义,没有“按 APN 定向”维度。与 v1 一致,忽略请求中的ApnName(预警自动断网、批量数据开停都会固定携带apnName="CMIOT",若据此拒绝会直接打断现有主流程)。新增 APN 管理需独立确认业务契约。 (2026-10-14 更正:v1 在 2026-10-10b3f00a0曾被改成按 APN 定向的/api/v3/updateApnFunctionStatus,导致电信数据开停全部失败,已回退为 singleCutNet;此处“与 v1 一致”现重新成立。) - 新版终端开关机仅3G,不能据此宣称4G/5G终端健康;机卡绑定断网状态与分离状态也不能互换语义。
- 不自动从 v3 回退并重发 v1 写操作。网络超时可能已经受理,重复调用会产生重复操作。
- 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 模板或通道记录写入开发库。
切换步骤:
- 保存目标通道的原 temp_key、app_key/app_secret、chan_config、is_normal、同步配置快照到受控存储;快照不写日志。确认账号已获 v3 能力授权、新旧凭证是否相同,以及新版 custNbr 的正确值。
- 新建测试用 v3 通道并保持非运行状态,使用后台通道测试创建临时实例,以完整三码和单码补全验证只读查询。该测试路径允许读取非运行通道,不注册生产实例;开发库目前无关联业务卡,需准备有授权的测试卡。
- 选择一个目标通道灰度。避免正在进行的管理操作和大批量同步;修改 temp_key,必要时同时更新新凭证/custNbr,保持 chan_key。不得顺带调整 sync_enabled 或 is_normal 来扩大操作范围。
- 通过后台保存,使事务完成后投递
QueueChannelRefresh。worker 先调用 gateway 的POST /internal/channel/refresh?chanKey=...,再刷新 proxy,失败会重试。后台“保存成功”只说明消息已投递,仍需核对 worker 及两个上游的刷新结果。 - 核对只读查询、批量拆分、状态/会话映射、流量数值与单位、错误率和响应延迟。需要启用写操作时先在受控测试卡验证,再打开 enableOperations;用交易流水和状态查询确认最终结果。
- 逐通道推广。回退时恢复完整旧快照并重新保存/刷新;如果凭证相同且无其他配置变化,可只恢复 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、频控、运营商订单完成状态及多副本热刷新仍需部署后联调。没有修改数据库、切换通道或调用真实停复机/断网。