docs: 移除开发过程文档,开源版文档统一见文档站
- docs/apika-shared-token-integration.md(Apika 共享 Token 通道对接说明) - docs/mingqian-integration.md(明千 v3 三方接入操作手册,含本地工作区路径与部署步骤) - docs/mobile-public-token-integration.md(移动公共 Token 通道配置与签名说明) - docs/telecom-v3-apn-integration.md(电信 V3 APN 状态与开停适配,含内部回退记录) - docs/plans/channel-template-model-design.md(通道模板模型设计方案) - docs/plans/telecom-v3-integration-and-switch.md(电信 v1/v3 切换与回退方案,含开发库现状) - docs/scripts/query_sim_card_status.sh、docs/scripts/query_sim_session.sh(内部联调脚本) - docs/sql/*.sql(共享模板迁移与种子 SQL) 保留 docs/config/*.json 通道模板样例(已脱敏,对使用者有参考价值)。 文档站:https://git.5iot.com/iotlcm-public/iotlcm-docs
This commit is contained in:
@@ -1,25 +0,0 @@
|
|||||||
# Apika mobile shared-token channel
|
|
||||||
|
|
||||||
Use `temp_model=shared_token` with template key `sim_onelink_kedang_v1`.
|
|
||||||
|
|
||||||
Set `token.baseUrl` to `http://test.iotapi.apika.cn` in the test environment
|
|
||||||
or `https://iotapi.apika.cn` in production. The token path is
|
|
||||||
`/iot-api/iot-card-info/getRefreshToken`.
|
|
||||||
|
|
||||||
Channel fields:
|
|
||||||
|
|
||||||
- `app_key`: Apika `appKey`, sent in the request header and body.
|
|
||||||
- `app_secret`: Apika `appSecret`, sent in the request header and body and used
|
|
||||||
to calculate the signature.
|
|
||||||
- `chan_config`: `{"appId":"..."}`; `appId` is sent as JSON field `appid`.
|
|
||||||
|
|
||||||
The request sends `appKey`, `appSecret`, and `signature` in both the headers
|
|
||||||
and JSON body. The JSON body also contains `appid`.
|
|
||||||
|
|
||||||
The signature is calculated by building `{appKey}:{appSecret}` with the
|
|
||||||
literal braces, hashing its UTF-8 bytes with SHA-256, converting the digest to
|
|
||||||
uppercase hexadecimal, and Base64-encoding that hexadecimal string.
|
|
||||||
|
|
||||||
The returned `data.shareToken` is cached for five minutes and refreshed thirty
|
|
||||||
seconds before expiry. The returned `transid` is not used as the OneLink
|
|
||||||
business request transaction ID.
|
|
||||||
@@ -1,214 +0,0 @@
|
|||||||
# Mingqian v3 integration
|
|
||||||
|
|
||||||
## Scope and deployment
|
|
||||||
|
|
||||||
This is a third-party platform adapter, not a shared-token adapter. Use
|
|
||||||
`temp_key=sim_mingqian_v3` and `temp_model=direct`. Deploy BOTH the gateway
|
|
||||||
and proxy builds containing this key, then save/refresh the channel in both
|
|
||||||
services. A configuration refresh cannot load a template into an old binary.
|
|
||||||
|
|
||||||
Worktrees for this feature:
|
|
||||||
|
|
||||||
- Gateway: `F:\Work\iotlcm-upstream-chan`
|
|
||||||
- Proxy: `F:\Work\iotlcm-upstream-proxy`
|
|
||||||
- Branch in each repository: `feat/mingqian-api-integration`
|
|
||||||
|
|
||||||
The LCM response model update lives in `F:\Work\lcm-mingqian`, branch
|
|
||||||
`feat/mingqian-flow-mapping`, based on `origin/master`. Deploy this LCM build
|
|
||||||
as well to preserve package flow fields in the channel test page. The page
|
|
||||||
already renders the returned JSON; no frontend changes are required. Without
|
|
||||||
the LCM model update, its typed response parser discards the new fields.
|
|
||||||
|
|
||||||
Do not deploy the old checkout by mistake. This feature does not edit the
|
|
||||||
existing runtime service address, production database, or operator settings.
|
|
||||||
Point the proxy's `gateway.address` at the actual gateway HTTP listener.
|
|
||||||
Do not configure the proxy to call Mingqian directly.
|
|
||||||
|
|
||||||
## Admin configuration
|
|
||||||
|
|
||||||
Create a template named Mingqian third-party v3:
|
|
||||||
|
|
||||||
- Template key: `sim_mingqian_v3`
|
|
||||||
- Model: `direct`
|
|
||||||
- Status: Enabled (select the UI option; do not assume the numeric enum)
|
|
||||||
- Template JSON: copy `docs/config/mingqian_v3.json`
|
|
||||||
|
|
||||||
Create a channel:
|
|
||||||
|
|
||||||
- Channel key: a unique value such as `mingqian_001`
|
|
||||||
- Template: select the template above
|
|
||||||
- AppKey: the upstream **appId**
|
|
||||||
- AppSecret: the upstream signing **APPKEY** (NOT a shared token)
|
|
||||||
- Channel JSON: `{}` (`docs/config/mingqian_channel.json`)
|
|
||||||
- Status: Enabled
|
|
||||||
- Carrier: select the actual carrier of the cards/account; this document
|
|
||||||
covers three carriers and does not establish which one your account uses.
|
|
||||||
|
|
||||||
Credentials are read from channel AppKey/AppSecret only. Channel JSON does
|
|
||||||
not override template configuration. Never commit real credentials.
|
|
||||||
|
|
||||||
## Supported standard gateway capabilities
|
|
||||||
|
|
||||||
| Capability | Upstream relative path | Behavior |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| Basic information | `/simlist/get-state-by-number` | ICCID, MSISDN, raw state and description only |
|
|
||||||
| Card lifecycle status | `/simlist/get-state-by-number` | Convert upstream states to gateway enums |
|
|
||||||
| Current-month usage | `/simlist/get-flow-use-by-number` | `use` KB / 1024 -> `dataUsage` MB |
|
|
||||||
| Package flow | `/use/get-card-use-by-number` | `totalpackage` and `targetpackageusage` MB -> separate `packageFlow` in the same real-time flow response |
|
|
||||||
| Stop | `/simlist/get-sim-switchoff-by-number` | Disabled unless `api.enableOperations=true` |
|
|
||||||
| Resume | Confirmed `api.resumePath` | Disabled by default; no guessed endpoint |
|
|
||||||
|
|
||||||
Queries split into at most 50 identifiers. Status changes split into at most
|
|
||||||
20 identifiers. Responses are matched by the requested ICCID or MSISDN, not
|
|
||||||
by array position. Missing/duplicate cards, missing/negative usage, missing
|
|
||||||
status and per-card operation failures are returned as failures, never as
|
|
||||||
successful zero usage. IMSI-only requests are rejected before network access.
|
|
||||||
|
|
||||||
Basic information is partial: no fake IMSI, activation date, or opening date.
|
|
||||||
The upstream `activedate` is a PACKAGE effective date, not card activation.
|
|
||||||
Use the admin test's direct-single-identifier mode. Three-code completion
|
|
||||||
cannot succeed unless all three codes are available from another source.
|
|
||||||
|
|
||||||
State mapping: 1 -> pending(1), 2 -> active(2), 3 -> stopped(4),
|
|
||||||
4 -> test(6), 5 -> stock(7), 6 -> cancelled(9). States 4 and 5 appear in the
|
|
||||||
inventory example; the global table omits them. State 7 (expired) and other
|
|
||||||
unmapped states return an explicit error rather than treating expiry as
|
|
||||||
permanent cancellation. Raw state remains available in basic information.
|
|
||||||
|
|
||||||
Not implemented as standard gateway capabilities: account-wide inventory
|
|
||||||
sync, IMEI lookup, APN/session/device/binding/stop-reason diagnostics, historical
|
|
||||||
daily/monthly usage, real-name status, voice-number
|
|
||||||
whitelists, SMS, account balance, recharge and order operations. Unsupported
|
|
||||||
standard methods return explicit errors without contacting upstream. An IMEI
|
|
||||||
field in the account inventory must not imply that binding is enabled.
|
|
||||||
|
|
||||||
The inventory endpoint is account-wide and paginated; this adapter deliberately
|
|
||||||
does not scan all pages on every single-card query.
|
|
||||||
|
|
||||||
## Flow mapping and partial failures
|
|
||||||
|
|
||||||
Select the admin test's real-time flow item (`simDataUsage`), or POST to the
|
|
||||||
gateway/proxy `/sim/query/sim-datausage` with the normal `simKeys` and empty
|
|
||||||
`reqData`. For each batch of at most 50 identifiers the adapter calls both
|
|
||||||
read-only flow endpoints, sharing the same account rate limiter. Results from
|
|
||||||
each endpoint are matched independently by ICCID/MSISDN, not by response order.
|
|
||||||
For 51 cards this makes four upstream calls, not two.
|
|
||||||
|
|
||||||
Example `resData`, all units MB:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"dataUsage": 1,
|
|
||||||
"packageFlow": {
|
|
||||||
"totalFlow": 10240,
|
|
||||||
"dataUsage": 8192,
|
|
||||||
"remainingFlow": 2048
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- `dataUsage`: current-month usage, from `use / 1024`. Existing monthly
|
|
||||||
synchronization continues using this field only.
|
|
||||||
- `packageFlow.totalFlow`: upstream `totalpackage`, already MB.
|
|
||||||
- `packageFlow.dataUsage`: upstream `targetpackageusage`, already MB and
|
|
||||||
cumulative over the package period, not necessarily the current month.
|
|
||||||
- `packageFlow.remainingFlow`: derived as `max(totalpackage - targetpackageusage, 0)`.
|
|
||||||
Overage is retained in cumulative usage, but remaining flow never becomes negative.
|
|
||||||
- The legacy top-level `totalFlow` remains absent for Mingqian. Do not subtract
|
|
||||||
current-month usage from a cross-month package total.
|
|
||||||
|
|
||||||
Missing/null/negative/non-numeric amounts are failures, not successful zeroes.
|
|
||||||
Actual zero values are valid. When monthly usage succeeds but package flow
|
|
||||||
fails, the card retains its successful monthly reading, omits `packageFlow`,
|
|
||||||
and includes `packageFlowError` plus a warning in the per-card `message`.
|
|
||||||
If monthly usage fails, the per-card code remains failure even if a package
|
|
||||||
snapshot is present; never write the package cumulative amount as monthly usage.
|
|
||||||
|
|
||||||
The channel test's monthly and daily items request historical periods. The
|
|
||||||
current Mingqian documentation provides neither historical endpoint, so these
|
|
||||||
remain explicitly unsupported rather than returning current-period numbers.
|
|
||||||
Real-time sessions, online status, APN and IP likewise remain unsupported;
|
|
||||||
neither lifecycle state nor nonzero usage proves a currently active session.
|
|
||||||
|
|
||||||
This update exposes package flow in API/channel-test results. It does not
|
|
||||||
persist package snapshots, change card-list balance calculations, or change
|
|
||||||
charging, quotas, or monthly synchronization semantics.
|
|
||||||
|
|
||||||
## Wire contract and safety
|
|
||||||
|
|
||||||
JSON POST envelope:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"body": {
|
|
||||||
"appId": "YOUR_APP_ID",
|
|
||||||
"reqtime": "2026-09-17",
|
|
||||||
"timestamp": "CURRENT_UNIX_MILLISECONDS_PLUS_60000",
|
|
||||||
"transid": "yyyyMMddHHmmssfff_PLUS_SIX_RANDOM_HEX_CHARACTERS",
|
|
||||||
"version": "3.0",
|
|
||||||
"sign": "COMPUTED_SIGNATURE"
|
|
||||||
},
|
|
||||||
"data": "{\"numberArray\":[\"YOUR_TEST_ICCID\"]}"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The `data` value is a serialized JSON STRING. Dates use UTC+8. The timestamp
|
|
||||||
is current time plus one minute, in milliseconds. Sign excludes `sign` itself:
|
|
||||||
sort common parameter keys, concatenate `key_value` for each without additional
|
|
||||||
separators, prepend/append APPKEY, and produce lowercase MD5.
|
|
||||||
|
|
||||||
This is the literal interpretation of the signing prose and example casing;
|
|
||||||
no official complete credential/sign test vector was supplied. Local tests
|
|
||||||
verify this interpretation, not acceptance by the upstream server. Confirm
|
|
||||||
against one official sample or a permitted read-only query before release.
|
|
||||||
|
|
||||||
Successful business code is 200 (not zero). HTTP errors, non-200 business
|
|
||||||
codes, malformed envelopes and absent data fail closed. No automatic retries
|
|
||||||
or HTTP redirects, especially for state-changing requests. Raw response bodies
|
|
||||||
and secrets are not logged. Client response size is bounded at 4 MiB.
|
|
||||||
|
|
||||||
An in-process limiter shared by base URL + appId spaces calls at least 60ms
|
|
||||||
apart (~166/10s, below the documented 200/10s). It does NOT coordinate separate
|
|
||||||
gateway replicas or other applications using the same account. Use a shared
|
|
||||||
egress limit or allocate a per-replica budget before scaling those accounts.
|
|
||||||
|
|
||||||
## Documentation contradictions requiring upstream confirmation
|
|
||||||
|
|
||||||
1. Stop and resume pages both list `/simlist/get-sim-switchoff-by-number`.
|
|
||||||
Never enable resume using that path. Obtain the real resume path, configure
|
|
||||||
`api.resumePath`, then explicitly enable operations when authorized.
|
|
||||||
2. The state page example says `state=1` with an active description; the global
|
|
||||||
table says 1=pending, 2=active. Mapping follows the table, not that example.
|
|
||||||
3. Confirm signature concatenation/casing with an official sample.
|
|
||||||
|
|
||||||
## Verification
|
|
||||||
|
|
||||||
Use only a sandbox or local `httptest` for stop/resume. No real stop, resume,
|
|
||||||
SMS, purchase or recharge request was sent during implementation.
|
|
||||||
|
|
||||||
Gateway targeted tests:
|
|
||||||
|
|
||||||
```text
|
|
||||||
go test ./internal/service/mingqian
|
|
||||||
go test ./internal/logic/upstreamimpl -run TestMingqianRegistration
|
|
||||||
go build ./...
|
|
||||||
```
|
|
||||||
|
|
||||||
Proxy targeted tests:
|
|
||||||
|
|
||||||
```text
|
|
||||||
go test ./internal/logic/upstreamimpl -run TestMingqianProxyTemplate
|
|
||||||
go test ./internal/logic/upstreamimpl -run TestMingqianProxyPreservesFlowFields
|
|
||||||
go build ./...
|
|
||||||
```
|
|
||||||
|
|
||||||
LCM response-model tests (run in `F:\Work\lcm-mingqian\server`):
|
|
||||||
|
|
||||||
```text
|
|
||||||
go test ./internal/model -run SimDataUsage
|
|
||||||
go build ./...
|
|
||||||
```
|
|
||||||
|
|
||||||
Initial live verification should select ONLY basic info, card status and
|
|
||||||
current-month usage using a permitted ICCID/MSISDN. Do not use real operations
|
|
||||||
to validate connectivity. A successful mock test is not proof of live access.
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
# 移动公共 Token 共享通道
|
|
||||||
|
|
||||||
## 模板
|
|
||||||
|
|
||||||
- 模板编号:`sim_onelink_daliuliang_v1`(开发库当前使用的标识)
|
|
||||||
- 模板名称:移动公共 Token 共享 OneLink v1
|
|
||||||
- 模板模型:`shared_token`
|
|
||||||
- 模板配置:`docs/config/onelink_mobile_public_v1.json`
|
|
||||||
|
|
||||||
`token.baseUrl` 必须填写上游提供的 Token 服务主机地址;当前协议只提供路径 `/api/get/yd/api/msg`,不能使用移动 OneLink 业务地址代替。
|
|
||||||
|
|
||||||
## 通道管理
|
|
||||||
|
|
||||||
- 通道名称:移动大流量插拔实名卡-OU2003457135923
|
|
||||||
- 通道标识(`chan_key`):`daliuliang_001`
|
|
||||||
- `temp_key`:`sim_onelink_daliuliang_v1`
|
|
||||||
- `chan_config`:`{"userId":"102369"}`
|
|
||||||
- `app_secret`:上游提供的签名密钥
|
|
||||||
|
|
||||||
创建实例前,需要启用通道和模板,并确保通道的 `is_normal=0`;`is_normal` 为 `NULL` 或 `1` 时,服务不会创建运行实例。通道服务与代理服务都需要部署包含该模板编号注册的版本。
|
|
||||||
|
|
||||||
不要把签名密钥写入 `chan_config`、模板配置或日志。Token 获取请求使用当前 Unix 秒时间戳,并按 `md5(secret + userid + time + secret)` 计算小写签名。
|
|
||||||
|
|
||||||
上游响应的 `expire_time` 作为 Unix 秒绝对过期时间保存;服务会在到期前 30 秒刷新 Token,并在同一通道的并发请求间合并刷新动作。
|
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
# 通道模板模型设计
|
|
||||||
|
|
||||||
## 1. 设计结论
|
|
||||||
|
|
||||||
通道配置分成三层:
|
|
||||||
|
|
||||||
```text
|
|
||||||
temp_model 接入方式大类
|
|
||||||
temp_key 具体上游协议模板
|
|
||||||
chan_config 具体通道的账号凭证和运行时状态
|
|
||||||
```
|
|
||||||
|
|
||||||
`temp_model` 只使用两个值:
|
|
||||||
|
|
||||||
```text
|
|
||||||
direct 直接调用上游 API
|
|
||||||
shared_token 先获取共享 Token,再调用业务 API
|
|
||||||
```
|
|
||||||
|
|
||||||
`direct` 是分类值,不是通用实现。普通模板必须通过 `temp_key` 找到精确注册的协议实现;不能仅凭 `direct` 推断运营商或请求协议。
|
|
||||||
`shared_token` 是共享 Token 模型的路由值,可以由共享实现工厂创建具体通道。
|
|
||||||
|
|
||||||
## 2. Mermaid 架构图
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
flowchart TB
|
|
||||||
DB["模板数据"] --> TEMPLATE["通道模板"]
|
|
||||||
TEMPLATE --> KEY["temp_key\n具体协议模板"]
|
|
||||||
TEMPLATE --> MODEL["temp_model\n接入方式"]
|
|
||||||
|
|
||||||
MODEL --> DIRECT["direct\n直接调用上游 API"]
|
|
||||||
MODEL --> SHARED["shared_token\n先获取共享 Token"]
|
|
||||||
|
|
||||||
KEY --> TY["sim_recharge_sdtywl_v1\n腾宇直连"]
|
|
||||||
KEY --> YD["sim_yd_v5\n中国移动官方"]
|
|
||||||
KEY --> DX["sim_dx_v1\n中国电信官方"]
|
|
||||||
KEY --> TY_SHARED["sim_onelink_tengyu_v1\n腾宇获取 Token + OneLink"]
|
|
||||||
KEY --> XZ_SHARED["sim_onelink_xuzhou_mobile_v1\n徐州移动获取 Token + OneLink"]
|
|
||||||
|
|
||||||
TY --> DIRECT_IMPL["精确 temp_key 注册的 direct 实现"]
|
|
||||||
YD --> DIRECT_IMPL
|
|
||||||
DX --> DIRECT_IMPL
|
|
||||||
|
|
||||||
TY_SHARED --> SHARED_IMPL["shared_token 模型工厂\n复用 OneLink 业务能力"]
|
|
||||||
XZ_SHARED --> SHARED_IMPL
|
|
||||||
SHARED_IMPL --> PROVIDER["Token Provider"]
|
|
||||||
PROVIDER --> TOKEN["共享 Token"]
|
|
||||||
TOKEN --> ONELINK["OneLink 业务 API"]
|
|
||||||
|
|
||||||
CREATE["新建通道"] --> SELECT["选择已有 temp_key"]
|
|
||||||
SELECT --> CREDENTIALS["填写通道凭证"]
|
|
||||||
CREDENTIALS --> CHANNEL["t_sim_channels"]
|
|
||||||
|
|
||||||
RULES["规则"] --> R1["temp_model 只有 direct / shared_token"]
|
|
||||||
RULES --> R2["temp_key 决定具体协议"]
|
|
||||||
RULES --> R3["direct 必须有精确注册"]
|
|
||||||
RULES --> R4["shared_token 使用共享工厂"]
|
|
||||||
```
|
|
||||||
|
|
||||||
## 3. 模板示例
|
|
||||||
|
|
||||||
```text
|
|
||||||
sim_yd_v5
|
|
||||||
temp_model = direct
|
|
||||||
中国移动官方 OneLink
|
|
||||||
|
|
||||||
sim_yd_mock_v5
|
|
||||||
temp_model = direct
|
|
||||||
中国移动 Mock OneLink
|
|
||||||
|
|
||||||
sim_dx_v1
|
|
||||||
temp_model = direct
|
|
||||||
中国电信 CMP
|
|
||||||
|
|
||||||
sim_recharge_sdtywl_v1
|
|
||||||
temp_model = direct
|
|
||||||
腾宇直连 API
|
|
||||||
|
|
||||||
sim_onelink_tengyu_v1
|
|
||||||
temp_model = shared_token
|
|
||||||
腾宇获取 Token + OneLink
|
|
||||||
|
|
||||||
sim_onelink_xuzhou_mobile_v1
|
|
||||||
temp_model = shared_token
|
|
||||||
徐州移动获取 Token + OneLink
|
|
||||||
```
|
|
||||||
|
|
||||||
## 4. 路由规则
|
|
||||||
|
|
||||||
运行时始终先按 `temp_key` 查找精确实现。精确注册优先于模型工厂,因此现有直连通道的协议行为不会因为模型分类改变。
|
|
||||||
|
|
||||||
当没有精确 `temp_key` 注册时:
|
|
||||||
|
|
||||||
- `temp_model=shared_token` 使用共享 Token 模型工厂。
|
|
||||||
- `temp_model=direct` 返回明确错误,因为 direct 本身不描述具体协议。
|
|
||||||
- 空值、未知值或旧的具体实现名称返回明确配置错误。
|
|
||||||
|
|
||||||
## 5. 新增三方协议流程
|
|
||||||
|
|
||||||
1. 实现三方协议的请求、签名、响应映射、错误处理和业务能力。
|
|
||||||
2. 增加新的 `temp_key` 并注册精确实现。
|
|
||||||
3. 为模板设置 `temp_model=direct`;如果需要共享 Token,则设置 `temp_model=shared_token`。
|
|
||||||
4. 在模板数据中保存公共配置,通道数据中保存账号凭证。
|
|
||||||
5. 业务人员创建通道时选择已有 `temp_key`,只填写该通道自己的凭证。
|
|
||||||
|
|
||||||
## 6. 配置边界
|
|
||||||
|
|
||||||
模板配置适合保存公共 API 地址、固定路径、超时时间和其他非账号协议参数。
|
|
||||||
通道配置适合保存 appKey、appSecret、pwdKey、channelNo、appId、账号密码及该通道独立的 Token 状态。
|
|
||||||
|
|
||||||
配置不能试图描述完整协议;新增协议通常仍需要修改代码。
|
|
||||||
|
|
||||||
## 7. 迁移约束
|
|
||||||
|
|
||||||
- 新代码只接受 `direct` 和 `shared_token` 两个模型值。
|
|
||||||
- 部署前通过 DBX 将现有普通模板修正为 `direct`,共享 Token 模板修正为 `shared_token`。
|
|
||||||
- 旧的 `onelink`、`onelink_mock`、`telecom`、`shared_token_onelink` 不作为新配置值保留。
|
|
||||||
- 发现无法根据 `temp_key` 判断类型的模板时,停止自动迁移并人工确认。
|
|
||||||
- 本仓库不提交数据库修复 SQL;数据库变更由 DBX 直接执行。
|
|
||||||
@@ -1,130 +0,0 @@
|
|||||||
# 电信 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”。同一模板被多个独立账号通道复用。
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
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 协议,仍需单独排查,不能宣称全量测试通过。
|
|
||||||
|
|
||||||
验证命令:
|
|
||||||
|
|
||||||
```text
|
|
||||||
# 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、频控、运营商订单完成状态及多副本热刷新仍需部署后联调。没有修改数据库、切换通道或调用真实停复机/断网。
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# 物联网卡卡状态查询 — 请求体与 internal/model/sim_chan_api.go 中 SimCardStatusInput 一致(simKeys)
|
|
||||||
# 请仅使用获得授权的测试卡
|
|
||||||
# 用法:在工程根目录执行
|
|
||||||
# chmod +x docs/scripts/query_sim_card_status.sh
|
|
||||||
# ./docs/scripts/query_sim_card_status.sh
|
|
||||||
# 可选环境变量:BASE_URL、CHAN_KEY、ICCID(多个用逗号分隔)
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
BASE_URL="${BASE_URL:-http://127.0.0.1:8110}"
|
|
||||||
PATH_QUERY="/query/sim-status"
|
|
||||||
CHAN_KEY="${CHAN_KEY:-YiDong_EC}"
|
|
||||||
|
|
||||||
if [[ -z "${ICCID:-}" ]]; then
|
|
||||||
echo "请设置 ICCID(仅获授权的测试卡)" >&2
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
IFS=',' read -r -a ICCIDS <<< "${ICCID}"
|
|
||||||
|
|
||||||
json_simkeys="["
|
|
||||||
sep=""
|
|
||||||
for id in "${ICCIDS[@]}"; do
|
|
||||||
id="$(echo -n "$id" | tr -d '[:space:]')"
|
|
||||||
[[ -z "$id" ]] && continue
|
|
||||||
json_simkeys+="${sep}{\"chanKey\":\"${CHAN_KEY}\",\"iccid\":\"${id}\"}"
|
|
||||||
sep=","
|
|
||||||
done
|
|
||||||
json_simkeys+="]"
|
|
||||||
|
|
||||||
body="{\"simKeys\":${json_simkeys}}"
|
|
||||||
|
|
||||||
echo "# POST ${BASE_URL}${PATH_QUERY}" >&2
|
|
||||||
echo "# body: ${body}" >&2
|
|
||||||
echo >&2
|
|
||||||
|
|
||||||
curl -sS -X POST "${BASE_URL}${PATH_QUERY}" \
|
|
||||||
-H 'Content-Type: application/json; charset=utf-8' \
|
|
||||||
-d "${body}"
|
|
||||||
echo
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# 查询 SIM 卡会话 — 请求体与 internal/model/sim_chan_api.go 中 SimSessionInput 一致(simKeys)
|
|
||||||
# 请仅使用获得授权的测试卡
|
|
||||||
# 用法:在工程根目录执行
|
|
||||||
# chmod +x docs/scripts/query_sim_session.sh
|
|
||||||
# ./docs/scripts/query_sim_session.sh
|
|
||||||
# 可选环境变量:BASE_URL、CHAN_KEY、ICCID(多个用逗号分隔)
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
BASE_URL="${BASE_URL:-http://127.0.0.1:8110}"
|
|
||||||
PATH_QUERY="/query/sim-session"
|
|
||||||
CHAN_KEY="${CHAN_KEY:-YiDong_EC}"
|
|
||||||
|
|
||||||
# 不内置真实卡号;调用者必须提供 ICCID
|
|
||||||
if [[ -z "${ICCID:-}" ]]; then
|
|
||||||
echo "请设置 ICCID(仅获授权的测试卡)" >&2
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
IFS=',' read -r -a ICCIDS <<< "${ICCID}"
|
|
||||||
|
|
||||||
json_simkeys="["
|
|
||||||
sep=""
|
|
||||||
for id in "${ICCIDS[@]}"; do
|
|
||||||
id="$(echo -n "$id" | tr -d '[:space:]')"
|
|
||||||
[[ -z "$id" ]] && continue
|
|
||||||
json_simkeys+="${sep}{\"chanKey\":\"${CHAN_KEY}\",\"iccid\":\"${id}\"}"
|
|
||||||
sep=","
|
|
||||||
done
|
|
||||||
json_simkeys+="]"
|
|
||||||
|
|
||||||
body="{\"simKeys\":${json_simkeys}}"
|
|
||||||
|
|
||||||
echo "# POST ${BASE_URL}${PATH_QUERY}" >&2
|
|
||||||
echo "# body: ${body}" >&2
|
|
||||||
echo >&2
|
|
||||||
|
|
||||||
curl -sS -X POST "${BASE_URL}${PATH_QUERY}" \
|
|
||||||
-H 'Content-Type: application/json; charset=utf-8' \
|
|
||||||
-d "${body}"
|
|
||||||
echo
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
-- Migration guide for the shared-token template split.
|
|
||||||
-- Review rows before running against a production database. This script does
|
|
||||||
-- not move or retain Henan Lelian credentials.
|
|
||||||
SET NAMES utf8mb4;
|
|
||||||
|
|
||||||
-- 1. Inspect the archived template and its channels.
|
|
||||||
SELECT temp_key, temp_model, temp_config
|
|
||||||
FROM t_sim_channel_template
|
|
||||||
WHERE temp_key = 'sim_onelink_shared_v5';
|
|
||||||
|
|
||||||
SELECT chan_key, chan_config, chan_status
|
|
||||||
FROM t_sim_channels
|
|
||||||
WHERE temp_key = 'sim_onelink_shared_v5';
|
|
||||||
|
|
||||||
-- 2. Seed the new templates first (see seed_onelink_shared_channel.sql),
|
|
||||||
-- then migrate only Tengyu rows after reviewing their provider value.
|
|
||||||
-- The JSON functions require MySQL 5.7+ and intentionally preserve only the
|
|
||||||
-- channel credentials. Public API/provider settings live in the new template.
|
|
||||||
UPDATE t_sim_channels
|
|
||||||
SET temp_key = 'sim_onelink_tengyu_v1',
|
|
||||||
chan_config = JSON_OBJECT(
|
|
||||||
'credentials', JSON_OBJECT(
|
|
||||||
'pwdKey', JSON_UNQUOTE(JSON_EXTRACT(chan_config, '$.auth.pwdKey')),
|
|
||||||
'channelNo', JSON_UNQUOTE(JSON_EXTRACT(chan_config, '$.auth.channelNo'))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
WHERE temp_key = 'sim_onelink_shared_v5'
|
|
||||||
AND LOWER(JSON_UNQUOTE(JSON_EXTRACT(chan_config, '$.auth.provider'))) = 'tengyu';
|
|
||||||
|
|
||||||
-- 3. Henan Lelian rows must be disabled and manually migrated or removed.
|
|
||||||
UPDATE t_sim_channels
|
|
||||||
SET chan_status = 0
|
|
||||||
WHERE temp_key = 'sim_onelink_shared_v5'
|
|
||||||
AND LOWER(JSON_UNQUOTE(JSON_EXTRACT(chan_config, '$.auth.provider'))) = 'henan_lelian';
|
|
||||||
|
|
||||||
-- 4. After all rows are reviewed and migrated, remove the archived template.
|
|
||||||
-- DELETE FROM t_sim_channel_template WHERE temp_key = 'sim_onelink_shared_v5';
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
-- Shared OneLink template/channel examples. Token endpoint and credentials
|
|
||||||
-- belong to the shared-token template configuration.
|
|
||||||
SET NAMES utf8mb4;
|
|
||||||
|
|
||||||
INSERT INTO `t_sim_channel_template` (
|
|
||||||
`temp_key`, `temp_name`, `temp_model`, `temp_config`, `temp_status`,
|
|
||||||
`tenant_id`, `created_dept`, `created_by`, `updated_by`
|
|
||||||
) VALUES (
|
|
||||||
'sim_onelink_tengyu_v1',
|
|
||||||
'Tengyu shared OneLink token v1',
|
|
||||||
'shared_token',
|
|
||||||
'{"token":{"provider":"tengyu","baseUrl":"http://www.sdtywl.cn/","path":"/prod-api/open/api/oneLinkToken"}}',
|
|
||||||
1, '000000', 103, 1, 1
|
|
||||||
)
|
|
||||||
ON DUPLICATE KEY UPDATE
|
|
||||||
`temp_name` = VALUES(`temp_name`), `temp_model` = VALUES(`temp_model`),
|
|
||||||
`temp_config` = VALUES(`temp_config`), `temp_status` = VALUES(`temp_status`),
|
|
||||||
`updated_by` = VALUES(`updated_by`);
|
|
||||||
|
|
||||||
INSERT INTO `t_sim_channels` (
|
|
||||||
`temp_key`, `chan_key`, `chan_name`, `chan_config`, `chan_status`,
|
|
||||||
`chan_alias`, `region_code`, `app_key`, `app_secret`, `access_token`,
|
|
||||||
`refresh_token`, `tenant_id`, `created_dept`, `created_by`, `updated_by`
|
|
||||||
) VALUES (
|
|
||||||
'sim_onelink_tengyu_v1',
|
|
||||||
'OneLink_TENGYU_EXAMPLE',
|
|
||||||
'OneLink Tengyu shared-token example',
|
|
||||||
'{"credentials":{"pwdKey":"REPLACE_WITH_PWDKEY","channelNo":"REPLACE_WITH_CHANNEL_NO"}}',
|
|
||||||
1, '', '', '', '', '', '', '000000', 103, 1, 1
|
|
||||||
)
|
|
||||||
ON DUPLICATE KEY UPDATE
|
|
||||||
`temp_key` = VALUES(`temp_key`), `chan_key` = VALUES(`chan_key`),
|
|
||||||
`chan_config` = VALUES(`chan_config`), `chan_status` = VALUES(`chan_status`),
|
|
||||||
`updated_by` = VALUES(`updated_by`);
|
|
||||||
|
|
||||||
INSERT INTO `t_sim_channel_template` (
|
|
||||||
`temp_key`, `temp_name`, `temp_model`, `temp_config`, `temp_status`,
|
|
||||||
`tenant_id`, `created_dept`, `created_by`, `updated_by`
|
|
||||||
) VALUES (
|
|
||||||
'sim_onelink_xuzhou_mobile_v1',
|
|
||||||
'Xuzhou Mobile shared OneLink token v1',
|
|
||||||
'shared_token',
|
|
||||||
'{"token":{"provider":"xuzhou_mobile","baseUrl":"http://iot.yuanjianiot.com","path":"/admin/api/get/token","carrierType":1}}',
|
|
||||||
1, '000000', 103, 1, 1
|
|
||||||
)
|
|
||||||
ON DUPLICATE KEY UPDATE
|
|
||||||
`temp_name` = VALUES(`temp_name`), `temp_model` = VALUES(`temp_model`),
|
|
||||||
`temp_config` = VALUES(`temp_config`), `temp_status` = VALUES(`temp_status`),
|
|
||||||
`updated_by` = VALUES(`updated_by`);
|
|
||||||
|
|
||||||
INSERT INTO `t_sim_channels` (
|
|
||||||
`temp_key`, `chan_key`, `chan_name`, `chan_config`, `chan_status`,
|
|
||||||
`chan_alias`, `region_code`, `app_key`, `app_secret`, `access_token`,
|
|
||||||
`refresh_token`, `tenant_id`, `created_dept`, `created_by`, `updated_by`
|
|
||||||
) VALUES (
|
|
||||||
'sim_onelink_xuzhou_mobile_v1',
|
|
||||||
'OneLink_XUZHOU_MOBILE_EXAMPLE',
|
|
||||||
'OneLink Xuzhou Mobile shared-token example',
|
|
||||||
'{"credentials":{"appId":"REPLACE_WITH_APP_ID"}}',
|
|
||||||
1, '', '', '', '', '', '', '000000', 103, 1, 1
|
|
||||||
)
|
|
||||||
ON DUPLICATE KEY UPDATE
|
|
||||||
`temp_key` = VALUES(`temp_key`), `chan_name` = VALUES(`chan_name`),
|
|
||||||
`chan_config` = VALUES(`chan_config`), `chan_status` = VALUES(`chan_status`),
|
|
||||||
`updated_by` = VALUES(`updated_by`);
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
SET NAMES utf8mb4;
|
|
||||||
|
|
||||||
-- 腾宇(综合充值平台)通道初始化。
|
|
||||||
-- 字段复用约定(internal/logic/upstreamimpl/sim_recharge.go):
|
|
||||||
-- t_sim_channels.app_key = pwdkey(个人中心 > 密钥 Key)
|
|
||||||
-- t_sim_channels.app_secret = pwdiv(签名 IV,sign=md5(pwdkey+pwdiv+timestamp))
|
|
||||||
-- 执行前必须替换以下三个占位符。
|
|
||||||
SET @recharge_pwdkey = 'REPLACE_WITH_PWDKEY';
|
|
||||||
SET @recharge_pwdiv = 'REPLACE_WITH_PWDIV_OR_IV';
|
|
||||||
SET @recharge_chan_key = 'REPLACE_WITH_LOCAL_CHAN_KEY';
|
|
||||||
|
|
||||||
-- 上线前需在腾宇后台配置网关出口 IP 白名单,否则上游返回 407。
|
|
||||||
-- 上游单接口限流 60 次/分;全量同步触发 429 时由下轮重试兜底。
|
|
||||||
|
|
||||||
START TRANSACTION;
|
|
||||||
|
|
||||||
INSERT INTO `t_sim_channel_template` (
|
|
||||||
`temp_key`,
|
|
||||||
`temp_name`,
|
|
||||||
`temp_model`,
|
|
||||||
`temp_config`,
|
|
||||||
`temp_status`,
|
|
||||||
`tenant_id`,
|
|
||||||
`created_dept`,
|
|
||||||
`created_by`,
|
|
||||||
`updated_by`
|
|
||||||
) VALUES (
|
|
||||||
'sim_recharge_sdtywl_v1',
|
|
||||||
'综合充值平台 API',
|
|
||||||
'{}',
|
|
||||||
'{"baseUrl":"http://www.sdtywl.cn/"}',
|
|
||||||
0,
|
|
||||||
'000000',
|
|
||||||
100,
|
|
||||||
1,
|
|
||||||
1
|
|
||||||
)
|
|
||||||
ON DUPLICATE KEY UPDATE
|
|
||||||
`temp_name` = VALUES(`temp_name`),
|
|
||||||
`temp_model` = VALUES(`temp_model`),
|
|
||||||
`temp_config` = VALUES(`temp_config`),
|
|
||||||
`temp_status` = VALUES(`temp_status`),
|
|
||||||
`updated_by` = VALUES(`updated_by`);
|
|
||||||
|
|
||||||
INSERT INTO `t_sim_channels` (
|
|
||||||
`temp_key`,
|
|
||||||
`chan_key`,
|
|
||||||
`chan_name`,
|
|
||||||
`chan_config`,
|
|
||||||
`chan_status`,
|
|
||||||
`chan_alias`,
|
|
||||||
`region_code`,
|
|
||||||
`app_key`,
|
|
||||||
`app_secret`,
|
|
||||||
`access_token`,
|
|
||||||
`access_token_created`,
|
|
||||||
`access_token_expire`,
|
|
||||||
`refresh_token`,
|
|
||||||
`login_url`,
|
|
||||||
`tenant_id`,
|
|
||||||
`created_dept`,
|
|
||||||
`created_by`,
|
|
||||||
`updated_by`
|
|
||||||
) VALUES (
|
|
||||||
'sim_recharge_sdtywl_v1',
|
|
||||||
@recharge_chan_key,
|
|
||||||
'仟所腾宇',
|
|
||||||
'{}',
|
|
||||||
0,
|
|
||||||
'腾宇',
|
|
||||||
'',
|
|
||||||
@recharge_pwdkey,
|
|
||||||
@recharge_pwdiv,
|
|
||||||
NULL,
|
|
||||||
NULL,
|
|
||||||
NULL,
|
|
||||||
NULL,
|
|
||||||
'http://www.sdtywl.cn/',
|
|
||||||
'000000',
|
|
||||||
100,
|
|
||||||
1,
|
|
||||||
1
|
|
||||||
)
|
|
||||||
ON DUPLICATE KEY UPDATE
|
|
||||||
`temp_key` = VALUES(`temp_key`),
|
|
||||||
`chan_name` = VALUES(`chan_name`),
|
|
||||||
`chan_config` = VALUES(`chan_config`),
|
|
||||||
`chan_status` = VALUES(`chan_status`),
|
|
||||||
`chan_alias` = VALUES(`chan_alias`),
|
|
||||||
`region_code` = VALUES(`region_code`),
|
|
||||||
`app_key` = VALUES(`app_key`),
|
|
||||||
`app_secret` = VALUES(`app_secret`),
|
|
||||||
`access_token` = NULL,
|
|
||||||
`access_token_created` = NULL,
|
|
||||||
`access_token_expire` = NULL,
|
|
||||||
`refresh_token` = NULL,
|
|
||||||
`login_url` = VALUES(`login_url`),
|
|
||||||
`updated_by` = VALUES(`updated_by`);
|
|
||||||
|
|
||||||
COMMIT;
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
-- Public protocol configuration for the built-in templates.
|
|
||||||
-- Run after the corresponding template rows exist.
|
|
||||||
SET NAMES utf8mb4;
|
|
||||||
|
|
||||||
INSERT INTO t_sim_channel_template
|
|
||||||
(temp_key, temp_name, temp_model, temp_config, temp_status, tenant_id, created_dept, created_by, updated_by)
|
|
||||||
VALUES
|
|
||||||
('sim_yd_v5', 'China Mobile OneLink v5', 'direct',
|
|
||||||
'{"api":{"baseUrl":"https://api.iot.10086.cn/v5/","timeout":"10s"},"token":{"path":"/ec/get/token"}}',
|
|
||||||
1, '000000', 103, 1, 1),
|
|
||||||
('sim_yd_mock_v5', 'China Mobile OneLink mock v5', 'direct',
|
|
||||||
'{"api":{"baseUrl":"http://iotlcm.example.com/onelink-mock/v5/"}}',
|
|
||||||
1, '000000', 103, 1, 1),
|
|
||||||
('sim_dx_v1', 'China Telecom API v1', 'direct',
|
|
||||||
'{"api":{"baseUrl":"https://cmp-api.ctwing.cn:20164"}}',
|
|
||||||
1, '000000', 103, 1, 1)
|
|
||||||
ON DUPLICATE KEY UPDATE
|
|
||||||
temp_model = VALUES(temp_model), temp_config = VALUES(temp_config),
|
|
||||||
temp_status = VALUES(temp_status), updated_by = VALUES(updated_by);
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
# 电信 V3 APN 状态与开停适配
|
|
||||||
|
|
||||||
## 接口依据
|
|
||||||
|
|
||||||
查询依据《中国电信天翼物联5GCMP能力调用参考文档-V3 (1).pdf》159–164 页;
|
|
||||||
开停依据 CTIOT_5GCMP_BA004(添加或取消单独断网,PDF p217-219)。
|
|
||||||
|
|
||||||
### 查询:GET /api/v3/getApnInfo
|
|
||||||
|
|
||||||
- msisdn/iccid/imsi 至少一项;读取 result.apnInfoList。
|
|
||||||
- 查询表格使用 status,示例使用 apnStatus,网关兼容两者;0 表示停用,1 表示正常。
|
|
||||||
- 两字段冲突、缺失或不在 0/1 范围时,状态保留为空,不推断为正常。
|
|
||||||
|
|
||||||
### 开停:GET /5gcmp/openapi/v1/common/singleCutNet
|
|
||||||
|
|
||||||
- 参数 access_number(物联网接入号,必传)+ action(ADD/DEL)。
|
|
||||||
- **整卡数据开停语义**,电信侧没有「按 APN 定向」这一维度。
|
|
||||||
- action 语义与平台相反,必须转换、禁止透传(见 `convertDxApnOperType`):
|
|
||||||
- 平台 0(开通数据) → `DEL`(取消单独断网,恢复联网)
|
|
||||||
- 平台 1(停用数据) → `ADD`(单独断网)
|
|
||||||
- 忽略调用方携带的 ApnName:预警自动断网、批量数据开停都会固定带 `apnName="CMIOT"`,
|
|
||||||
电信卡不含该 APN,若据此拒绝会直接打断主流程。
|
|
||||||
- 响应为 XML:`SvcCont > Response > RspCode`,`0000` 表示成功接收;
|
|
||||||
`GROUP_TRANSACTIONID` 为流水号,透传为 orderNum。
|
|
||||||
- 缺返回码、非 `0000`、缺流水号、HTTP 失败均不视为成功,不自动重试以免重复操作。
|
|
||||||
|
|
||||||
> 注意:`/api/v3/updateApnFunctionStatus` 是另一个接口(单卡 APN 开停,按 APN 定向),
|
|
||||||
> 与「数据服务开停」业务语义不匹配,v1 不再使用。
|
|
||||||
> 历史:2026-10-10 `b3f00a0` 曾把 v1 改成该接口,导致电信数据开停因 APN 名不匹配而全部失败,已回退。
|
|
||||||
|
|
||||||
## 读取链路
|
|
||||||
|
|
||||||
智能诊断展示与 APN 开关查询分离:
|
|
||||||
|
|
||||||
- 电信:小程序/Web 后端复用卡状态查询 → 请求代理 → 网关 sim_dx_v1 → /openapi/v1/prodinst/queryCardMainStatus。
|
|
||||||
- 网关透传独立 netBlockStatus(0 未断网、1 已断网)和 netBlockTypes,诊断返回 dataServiceSource=network_block、netBlockStatus、netBlockReason。
|
|
||||||
- 电信 dataStatus 保持空值,不将断网状态转换成 APN 开关,也不以此驱动 APN 开停按钮;电信卡片排查不展示 APN 开停操作。
|
|
||||||
- 未返回有效断网状态时显示 -;已断网时展示全部有效断网原因并去重,不推测原因。
|
|
||||||
- 卡主状态查询失败时数据服务模块同步标记失败,不能展示本地旧状态。
|
|
||||||
- 电信在线状态继续独立查询 /api/v1/onlineSessionOpen/qryOnlineSataus;APN、IP、IMEI、时间等会话字段不能推断 APN 开启。
|
|
||||||
- 电信会话 netModel=1/2 统一为 4G/5G,不再透传数字让前端按移动编码误解释。
|
|
||||||
- 移动数据服务仍使用 ec/query/apn-info 的 CMIOT 状态;移动和电信的会话状态都从会话接口获取。
|
|
||||||
- 独立 APN 查询 /sim/query/sim-apn-info 仍使用 CM001 /api/v3/getApnInfo,但不再作为电信智能诊断数据服务断网展示的来源。
|
|
||||||
|
|
||||||
两端对非移动 APN 列表采用保守一致性判定:单个 APN 或所有 APN 的有效状态一致时才给出开/停;空列表、缺名称、缺状态、混合状态均不选第一项代替整体状态,界面无值显示 -。
|
|
||||||
这不是多 APN 的业务目标选择规则;若要显示某个专用业务 APN,需进一步明确目标并让查询和操作使用同一配置。
|
|
||||||
|
|
||||||
## 开停保护与剩余事项
|
|
||||||
|
|
||||||
- 开停走整卡语义的 singleCutNet,只要求物联网接入号(MSISDN);缺接入号时明确失败。
|
|
||||||
- 不将 CMIOT 自动改成 ctnet 或 CTIOT,也不猜选唯一 APN:
|
|
||||||
指定 APN 开停需要另接 `/api/v3/updateApnFunctionStatus`,属独立业务契约,未接入。
|
|
||||||
- 当前小程序/Web 批次入口仍默认写入 CMIOT;singleCutNet 忽略该字段,不影响执行。
|
|
||||||
- 小程序操作预检仍读取本地 data_service_status,可能与实时状态不同;后续应与上游校验统一。
|
|
||||||
- Worker 目前依据网关 code=0 判成功并更新本地状态。电信返回流水号表示订单受理,最终生效状态需要后续查单或实时 APN 查询核实,尚未做真实业务验收。
|
|
||||||
- 若启停请求发送后网络中断或成功响应缺流水号,结果未确认,不应盲目重试以免重复操作;先核对上游状态。
|
|
||||||
- singleCutNet 的 GET/POST 以网关实测为准:文档写 POST/GET,现网 v1 走 GET;
|
|
||||||
v3 适配器走 POST(`sim_dx_v3.go`),尚未实卡验收。
|
|
||||||
|
|
||||||
## 验证边界
|
|
||||||
|
|
||||||
回归测试采用 httptest 本地模拟上游,覆盖:查询字段兼容/缺字段/错误码;
|
|
||||||
开停的 ADD/DEL 方向、参数编码、XML 解析、缺结果码、缺流水号、非 0000、HTTP 失败保护,
|
|
||||||
并断言不携带 APN 定向参数。
|
|
||||||
测试不对真实卡发起启停,不替代生产通道权限、配置及 Worker 全链路验收。
|
|
||||||
Reference in New Issue
Block a user