27 KiB
新增需求 01:数据同步触发与轮询优化
状态:已合并至标准评审稿,本文保留为实施明细。 评审主文档:
../../7月迭代技术方案-标准评审稿.md范围:IoT 卡实名、流量、网络状态和设备实时数据同步。
一、已确认决策
- 轮询、运营商回调、业务事件触发是三条相互隔离的自动同步通道。
- 轮询始终保留,负责回调失效、事件漏网和上游偶发失败时的最终兜底。
- 资产是否轮询只保留现有全局开关
enable_polling;不再设计“实名资格、流量资格、状态资格”等业务资格开关。 - 轮询优化只区分活跃卡和不活跃卡,通过不同轮询间隔降低无效请求,不因业务类型直接永久排除某类卡。
- 行业卡是否需要实名不能由
card_category=industry推断,统一以运营商realname_link_type判断。 - 业务事件触发不是新增一个显式同步接口,而是埋点到关键查询、停复机、支付、实名和设备操作用例中。
- 每次事件默认形成三个独立尝试:立即、事件发生后 3 分钟、事件发生后 5 分钟;三次结束后由常规轮询继续兜底。
- 不设置统一五分钟冷却。重复请求控制拆成同场景触发合并、单卡请求互斥和运营商最小请求间隔;Gateway 超频只记录当前失败,不建立退避状态。
- 运营商实名回调百分百信任,不做来源真实性校验;但必须经过防腐层完成报文解析、标识转换和状态语义转换。
- 解除实名第一版不修改业务状态,只保留回调入口、写入统一集成审计并返回运营商要求的成功报文。
- 三条自动通道和现有手动刷新接口共享同一套“获取上游观测并应用到业务”的 DDD 用例,禁止各入口直接更新卡字段。
- 同步执行记录属于全局审计的 Integration Log,不在本方案新建
tb_card_sync_execution或独立同步监控系统。 - 本次迁移触碰到的旧实名、流量和状态写逻辑必须删除或改为调用新用例,不保留长期双实现。
二、现状问题
当前代码存在以下明确问题:
internal/task/polling_realname_handler.go直接跳过所有行业卡,与“是否实名由运营商特性决定”冲突。PollingRealnameHandler、PollingCarddataHandler和PollingCardStatusHandler同时承担 Gateway 查询、字段更新和业务联动。internal/service/iot_card/service.go:RefreshCardDataFromGateway又实现一套实名、流量和网络状态同步。ManualUpdateRealnameStatus单独实现实名状态变更联动,无法保证与轮询、回调行为一致。ClientRealnameHandler、部分设备 Handler 直接调用 Gateway,业务事件无法在统一用例边界埋点。- 现有获取实名链接后的自动检查借用了
ManualTriggerService,把系统事件伪装成平台用户手工操作。 - 调度任务载荷只有
card_id,缺少触发场景、来源、序列和关联链路。 last_sync_time被实名、流量和状态共同覆盖,无法说明最后同步了什么。- 现有显式刷新接口已经存在,再增加
/card-sync/requests只会形成重复入口。
因此,本次不是增加更多同步 Service,而是先收口写侧用例,再把轮询、回调和事件触发接入同一边界。
三、目标架构
flowchart LR
Polling[轮询调度] --> Activity[按活跃度计算下次间隔]
Activity --> Request[RequestCardObservation]
Business[关键业务用例] --> Trigger[CreateSyncTriggerSeries]
Query[关键查询用例] --> Trigger
Trigger --> A1[立即尝试]
Trigger --> A2[3分钟后尝试]
Trigger --> A3[5分钟后尝试]
A1 --> Gate[请求协调器]
A2 --> Gate
A3 --> Gate
Gate --> Request
Manual[现有手动刷新接口] --> Request
Callback[运营商实名回调] --> ACL[运营商防腐层]
ACL --> RealObs[标准实名观测]
Request --> Gateway[Gateway Adapter]
Gateway --> Observation[标准化观测值]
RealObs --> Apply[ApplyCardObservation]
Observation --> Apply
Apply --> Card[保存卡状态]
Apply --> Events[记录领域事件和 Outbox]
Events --> Package[套餐激活与流量扣减]
Events --> StopResume[停复机评估]
Events --> ActivityMark[更新活跃标记]
Request --> Integration[统一 Integration Log]
ACL --> Integration
Events --> Audit[关键业务 Audit Event]
各入口边界:
| 入口 | 是否调用 Gateway | 执行方式 | 作用 |
|---|---|---|---|
| 轮询 | 是 | 异步、持续重排 | 最终一致性兜底 |
| 运营商回调 | 否 | 同步应用状态,联动异步可靠投递 | 实名成功急速通道 |
| 业务事件触发 | 是 | 异步 0/3/5 三次 Best Effort |
提高正在操作资产的命中概率 |
| 现有手动刷新 | 是 | 保持现有响应契约 | 用户明确要求立即刷新 |
手动刷新不是第四种自动策略,也不能被业务埋点调用。
四、DDD 设计
4.1 目录
internal/
├── domain/cardstate/
│ ├── card.go 卡状态聚合及状态转换
│ ├── observation.go 实名、流量、网络状态观测值
│ ├── events.go 状态变化领域事件
│ └── repository.go 聚合仓储接口
├── application/cardsync/
│ ├── request_observation.go 查询 Gateway 并获取标准观测
│ ├── apply_observation.go 在事务内应用观测和领域事件
│ ├── create_trigger_series.go 创建事件触发序列
│ ├── execute_trigger_attempt.go 执行单次阶梯尝试
│ ├── mark_activity.go 更新活跃标记
│ └── refresh_asset.go 承接现有显式刷新接口
├── infrastructure/adapter/gateway/
│ └── card_observation_adapter.go 复用现有 Gateway Client
├── infrastructure/adapter/carrier_callback/
│ ├── translator.go 防腐层统一接口
│ ├── cmcc.go
│ ├── cucc.go
│ └── ctcc.go
├── infrastructure/messaging/cardsync/
│ ├── trigger_publisher.go Outbox/Asynq 任务发布
│ └── trigger_handler.go 阶梯任务处理器
└── query/cardsync/
└── activity_query.go 活跃状态和下次轮询查询
4.2 实名要求判断
不得继续使用以下判断:
if card.CardCategory == constants.CardCategoryIndustry {
// 跳过实名
}
现有 tb_carrier.realname_link_type 可以直接决定该运营商接入是否需要实名,不新增重复能力字段:
realname_link_type |
实名要求 | 实名入口 |
|---|---|---|
none |
不需要实名 | 不提供实名链接,不创建实名查询任务 |
template |
需要实名 | 使用模板生成实名链接 |
gateway |
需要实名 | 调用 Gateway 获取实名链接 |
- 资产
realname_policy只决定“先实名后购买”或“先购买后实名”的业务顺序。 realname_link_type=none时,资产有效实名策略统一视为none。realname_link_type!=none时,资产realname_policy=none属于冲突数据,发布前列出并修正,禁止运行时静默选择其中一方。card_category只保留业务分类和展示用途,不参与实名轮询、复机或套餐激活判断。
运营商是否配置回调 Adapter 只影响有没有回调急速通道,不改变 realname_link_type 对实名要求的判断。
4.3 标准化观测值
type SyncSource string
const (
SyncSourcePolling SyncSource = "polling"
SyncSourceBusinessEvent SyncSource = "business_event"
SyncSourceOpenAPI SyncSource = "open_api"
SyncSourceManualRefresh SyncSource = "manual_refresh"
SyncSourceCarrierCallback SyncSource = "carrier_callback"
)
type ObservationMeta struct {
Source SyncSource
TriggerScene string
TriggerSeries string
Attempt int
Provider string
ObservedAt time.Time
RequestID string
CorrelationID string
}
type RealnameObservation struct {
CardID uint
ICCID string
Verified bool
Meta ObservationMeta
}
type TrafficObservation struct {
CardID uint
GatewayReadingMB float64
Meta ObservationMeta
}
type NetworkObservation struct {
CardID uint
CardStatus string
Extend string
GatewayIMEI string
Meta ObservationMeta
}
领域层不使用 map[string]any 传递核心状态,防止不同入口遗漏字段或混淆单位。
4.4 观测应用规则
ApplyCardObservation 是唯一允许把上游数据写入卡领域状态的入口:
- 加载并锁定卡聚合。
- 根据观测类型执行实名、流量或网络状态规则。
- 状态未变化时只更新时间和观测来源,不重复产生业务事件。
- 状态变化时保存聚合,并在同一事务写 Outbox 和关键 Audit Event。
- 实名
0 -> 1产生CardRealnamed,触发卡级/设备级套餐激活和停复机评估。 - 流量正增量产生
CardTrafficIncreased,由套餐应用服务扣减套餐流量。 - 网络状态变化产生
CardNetworkStatusChanged,由停复机策略重新评估。 - 状态变化或流量增加时更新卡活跃标记。
解除实名回调不构造 Verified=false 观测,不修改卡状态。
五、轮询优化:只做活跃度调频
5.1 全局开关
enable_polling 是资产是否参与自动轮询的唯一业务开关:
false:从所有轮询队列移除,事件触发也不因此失效;用户主动操作仍可触发 Best Effort 同步。true:按照活跃度和运营商能力进入对应 Gateway 查询队列。- 设备关闭轮询时,继续沿用现有设备与绑定卡级联规则。
不再提供“实名轮询开关、流量轮询开关、状态轮询开关”给业务人员配置。
5.2 活跃标记
卡增加或维护以下轮询调度字段:
polling_activity_level active / inactive
last_polling_activity_at 最后活跃时间
last_polling_activity_scene 最后活跃场景
last_upstream_change_at 上游观测最后发生变化时间
以下情况标记为活跃:
| 场景 | 说明 |
|---|---|
| C 端、后台或开放接口查询资产实时信息 | 表明当前有人关心该资产 |
| 获取实名链接 | 表明即将发生实名操作 |
| 停机、复机、切卡、重启、恢复出厂设置 | 表明上游状态正在变化 |
| 套餐支付、激活、失效或流量重置 | 可能影响流量和停复机 |
| 运营商回调 | 上游已发生变化 |
| 轮询发现实名、流量或网络状态变化 | 卡当前确实活跃 |
卡在配置时间内没有业务活动,且连续轮询未发现上游变化时转为 inactive。该判断在每次重排队时完成,不新增全表扫描任务。
第一版建议配置而非写死:
inactive_after = 30m
active_realname_interval = 现有实名默认间隔
active_traffic_interval = 现有流量默认间隔
active_network_interval = 现有状态默认间隔
inactive_realname_interval = 15m
inactive_traffic_interval = 15m
inactive_network_interval = 15m
现有 tb_polling_config 的有效间隔迁移为活跃卡默认值,但不再按 card_condition/card_category 匹配多套业务资格。第一版调整为一套全局活跃/不活跃间隔;运营商配置只描述接口能力和上游限频,不再决定某张卡是否“有资格”轮询。上线前使用生产数据做只读测算,确认 Gateway QPS 和最长兜底延迟后再调整数值。
5.3 调度规则
enable_polling=false
-> 不入自动轮询队列
enable_polling=true + active
-> 使用全局活跃间隔
enable_polling=true + inactive
-> 使用全局不活跃间隔
realname_link_type=none
-> 不创建实名查询任务
已实名卡仍可低频查询实名状态,以发现上游实名逆转;未实名行业卡也不能因卡类别被排除。
六、业务事件触发
6.1 不是新增接口
本需求不新增 POST /api/admin/card-sync/requests,也不新增独立“事件同步”按钮。
现有接口保持:
POST /api/admin/assets/:identifier/refresh
POST /api/c/v1/asset/refresh
现有批量轮询运维接口可以保留,但必须改为调用新的 Application 用例,不再自行维护另一套同步逻辑。
业务事件触发由 Application UseCase 或 Query 完成后调用内部端口:
type SyncTriggerCommand struct {
Scene string
ResourceType string
ResourceID uint
CardIDs []uint
SyncTypes []string
ExpectedState map[string]any
Source SyncSource
RequestID string
CorrelationID string
}
写操作在业务成功后触发;有数据库事务的关键写操作通过 Outbox 发布触发事件,避免提交成功后进程退出造成埋点丢失。读操作只查询本地快照,不等待 Gateway;在返回前 Best Effort 写入 Asynq,入队失败不得改变原接口响应。
6.2 阶梯式尝试
每个触发序列默认创建三个 Asynq 任务:
| 尝试 | 计划时间 | Asynq 自动重试 |
|---|---|---|
| 1 | 立即 | 0 |
| 2 | 事件发生后 3 分钟 | 0 |
| 3 | 事件发生后 5 分钟 | 0 |
每次任务都有确定性的 series_id + attempt 幂等键。单次失败不会额外重试,也不会取消后续两次;三次结束后由常规轮询兜底。
存在明确预期状态时允许提前完成序列:
- 复机后已观测为开机。
- 停机后已观测为停机。
- 获取实名链接后已通过回调或查询确认实名。
- 切卡后设备当前卡已经是目标 ICCID。
序列提前完成后,剩余任务启动时检查状态并直接记为 completed,不再请求 Gateway。
6.3 关键埋点
埋点放在应用用例成功边界,不散落在 Handler 的 response.Success 前后。现有 Handler 直接调用 Gateway 的路径在迁移时收口到 Application。
| 现有入口/用例 | 触发内容 | 预期状态 |
|---|---|---|
GET /api/c/v1/asset/info |
资产绑定卡的实名、流量、网络状态 | 无 |
GET /api/admin/assets/:identifier/realtime-status |
对应卡或设备绑定卡的实时数据 | 无 |
GET /api/open/v1/cards/traffic |
流量 | 无 |
GET /api/open/v1/cards/status |
网络状态 | 无 |
GET /api/open/v1/cards/realname-status |
实名 | 无 |
GET /api/open/v1/devices/traffic |
设备信息、绑定卡流量 | 无 |
| C 端和后台获取实名链接 | 实名 | 已实名 |
| 后台、C 端、开放接口停机/复机成功 | 网络状态 | 停机或开机 |
| 自动停复机 Gateway 调用成功 | 网络状态 | 停机或开机 |
| 订单支付、钱包购买套餐、套餐激活成功 | 实名、流量、网络状态 | 无 |
| 设备切卡或切换模式成功 | 设备信息、源卡和目标卡网络状态、目标卡流量 | 目标 ICCID |
| 设备重启、恢复出厂设置、WiFi 设置成功 | 设备信息、绑定卡网络状态 | 无 |
| 卡绑定/解绑、设备或卡分配/回收 | 只标记活跃;确有上游操作时再创建同步序列 | 无 |
补充规则:
POST /api/admin/assets/:identifier/refresh和POST /api/c/v1/asset/refresh已经直接同步,不再额外创建0/3/5序列。- 运营商实名回调直接应用观测,不再反查 Gateway;回调成功后终止相同卡的待执行实名序列。
- 轮询发现状态变化只更新活跃标记,不反向创建新的事件序列。
- 同一次设备操作涉及多张绑定卡时使用同一
correlation_id,但每张卡独立限流和记录结果。
6.4 冷却、合并与限流
不使用一个固定五分钟 SET NX 键拦截所有事件。请求协调器只处理同场景合并、单卡互斥和最小请求间隔;Gateway 返回超频后不建立额外退避状态。
同场景触发合并
cardsync:series:{scene}:{resource_type}:{resource_id}:{sync_type}
- 同一场景、同一资源、同一同步类型已有未结束序列时,新触发合并到原序列。
- 合并只防止页面轮询、开放接口重试等重复创建大量
0/3/5任务。 - 停复机、支付、切卡等不同业务场景不会被一个查询场景长期压制。
- 序列键只覆盖最后一次计划任务和短暂缓冲,不作为全局五分钟冷却。
单卡请求互斥
cardsync:inflight:{provider}:{sync_type}:{card_id}
- 只覆盖一次 Gateway 请求的执行时间,TTL 为请求超时加安全余量。
- 防止轮询、手动刷新和事件任务同时请求并重复应用同一观测。
- 轮询命中互斥时延迟短时间重排;事件尝试命中互斥时只跳过当前尝试,后续阶梯任务仍存在。
运营商最小请求间隔
最小间隔按运营商接入和接口类型配置,例如实名、流量、状态可以不同,不能统一写死为五分钟。
- 默认兜底值建议为 10 秒,仅用于阻止近乎同时的重复调用。
- 如果上游明确给出更严格限制,以运营商配置为准。
- 高价值状态变更事件命中最小间隔时,延迟到最近允许时间,不直接丢弃整个序列。
超频结果处理
- Gateway 返回超频时,当前尝试记录为
rate_limited后直接结束。 - 不记录
blocked_until,不读取Retry-After,也不执行30s/60s/120s退避。 - 不为当前尝试额外补发任务,原定 3 分钟、5 分钟尝试保持不变。
- 后续轮询仍按正常调度时间继续,是否再次超频由当时上游实际情况决定。
- 轮询、事件和手动刷新仍共享本系统的并发控制,避免本系统自身在同一时刻并发打满 Gateway。
因此,“5 分钟”是第三次尝试的计划时间,不是禁止新业务事件同步的冷却时间。
6.5 开放接口行为
开放接口继续返回本地快照,不等待 Gateway:
sequenceDiagram
participant Agent as 代理系统
participant API as Open API
participant DB as PostgreSQL
participant Trigger as CreateSyncTriggerSeries
participant Worker as Sync Attempt Worker
Agent->>API: 查询实名/状态/流量
API->>DB: 查询当前本地快照
DB-->>API: 当前数据
API-->>Agent: 按现有结构立即返回
API->>Trigger: Best Effort 创建 0/3/5 序列
Trigger-->>Worker: 同场景重复请求自动合并
七、运营商实名回调防腐层
7.1 防腐层职责
“百分百信任回调”表示信任其业务结论,不表示让运营商报文直接进入领域模型。
type CarrierRealnameCallbackTranslator interface {
TranslateSuccess(body []byte) (CarrierRealnameNotice, error)
TranslateRemoval(body []byte) (CarrierRealnameRemovalNotice, error)
SuccessResponse() CallbackResponse
FailureResponse(err error) CallbackResponse
}
各运营商 Adapter 负责:
- 解析 JSON、XML 或表单字段。
- 提取并校验 19/20 位 ICCID、MSISDN 等标识。
- 把运营商状态码翻译为“实名成功”或“解除实名通知”。
- 屏蔽运营商字段名、状态码和成功响应格式。
- 生成脱敏 Integration Log 摘要。
领域层只接收标准 RealnameObservation,不依赖移动、联通、电信的报文结构。
ICCID 19/20 位解析
回调必须复用现有双列存储和按长度路由规则:
收到 19 位 ICCID
-> 原值查询 tb_iot_card.iccid_19
收到 20 位 ICCID
-> 原值查询 tb_iot_card.iccid_20
收到其他长度
-> invalid_payload,不进入实名用例
- 先去除首尾空白,再调用现有 ICCID Validator,只接受 19 位或 20 位字母数字值。
- 禁止把 20 位回调值截成 19 位后降级查询。
- 禁止为 19 位值自行补第 20 位校验位。
- 查询 Miss 时不切换另一列重试,记录原始长度、运营商和脱敏 ICCID 摘要。
- 复用
IotCardStore.GetByICCID的长度路由语义;防腐层只负责提取和校验,不自己拼接模糊 SQL。 iccid_19必须在未删除卡中保持唯一。发布前检查重复前缀并建立 Partial Unique Index,避免 19 位回调错误命中任意一张卡。- Repository 查询若发现多条匹配必须返回冲突,不允许使用
First静默选择。 - Integration Log 的
resource_key保存回调原始 19/20 位值,展示时按审计权限脱敏。
7.2 路由
POST /api/callback/carriers/cmcc/realname
POST /api/callback/carriers/cucc/realname
POST /api/callback/carriers/ctcc/realname
POST /api/callback/carriers/cmcc/realname/remove
POST /api/callback/carriers/cucc/realname/remove
POST /api/callback/carriers/ctcc/realname/remove
- 成功回调:防腐层转换后直接调用
ApplyCardObservation(Verified=true)。 - 解除回调:只写统一 Integration Log,状态记为
ignored,不修改卡状态。 - 广电或其他没有回调能力的接入继续依赖事件和轮询。
- 不验证回调是否真的来自运营商,不执行 Gateway 二次查询。
- 不调用旧平台
inner_callback、第三方推送或删除实名接口。
7.3 响应原则
- 找到卡并应用成功:返回运营商要求的成功报文。
- 重复成功回调:幂等返回成功,不重复激活套餐。
- 找不到卡:Integration Log 记
not_found,仍返回成功,避免无意义高频重推。 - 报文无法解析:记录
invalid_payload;若运营商有固定成功应答要求,仍按接入约定返回,避免不可控重试风暴。 - 状态已落库但异步联动失败:返回成功,联动通过 Outbox 重试。
八、统一审计契约
同步运行数据不在本方案定义独立表和独立页面,统一由 04-全局多视角审计方案.md 管理。
本模块需要向审计系统提供:
| 记录 | 进入位置 |
|---|---|
| 每次 Gateway 实际请求或被限流、合并的尝试 | Integration Log |
| 每次运营商实名/解除实名回调 | Integration Log |
| 实名、流量、网络状态发生业务变化 | Audit Event + Integration Log |
| 手动刷新、手动实名修改 | Audit Event + Integration Log |
| 连续失败达到阈值、超频持续异常 | 风险 Audit Event |
| 普通高频轮询成功且数据未变化 | 仅 Integration Log |
必须携带:
provider / operation / resource / trigger_scene / trigger_series
attempt / result / duration / request_id / correlation_id
state_changed / provider_code / error_summary
资产详情的“查看同步轨迹”跳转审计中心外部集成视角,不再建设 /operations/card-sync 独立监控页。
九、接口和前端调整
9.1 后端接口
本需求不新增显式同步接口。
现有手动刷新接口内部改为调用 RefreshAssetUseCase,响应结构和权限保持不变。现有轮询监控接口增加:
- 活跃卡数量、不活跃卡数量。
- 各活跃级别的实际轮询 QPS。
- 因互斥或运营商最小间隔而延迟的任务数,以及 Gateway 超频失败次数。
资产实时状态或详情 Query 增加可选调度信息:
{
"polling": {
"enabled": true,
"activity_level": "active",
"last_activity_at": "2026-07-15T10:00:00+08:00",
"last_activity_scene": "client_asset_info",
"next_poll_at": "2026-07-15T10:01:00+08:00"
}
}
同步明细查询复用全局审计接口:
GET /api/admin/audit/integrations
?provider=gateway
&resource_type=iot_card
&resource_key=89860...
&trigger_scene=
&trigger_series=
9.2 前端
保留现有资产刷新按钮,不新增第二个同步按钮。
资产详情增加紧凑的轮询状态展示:
自动轮询:已开启
活跃状态:活跃
最后活跃:2分钟前,C端资产详情
下次兜底:约1分钟后
同步轨迹:查看
“查看”跳转:
/operations/audit?tab=integrations&resource_type=iot_card&resource_key={identifier}
轮询监控页只增加活跃/不活跃分布和限流状态,不重复实现 Integration Log 表格、详情抽屉和导出。
十、代码迁移范围
10.1 必须删除或收口
- 删除
PollingRealnameHandler按CardCategoryIndustry跳过实名的判断。 - 修正
pkg/constants/iot.go中“普通卡必需实名、行业卡无需实名”的绝对化注释和依赖逻辑。 PollingRealnameHandler、PollingCarddataHandler、PollingCardStatusHandler只调用 Application 用例,不再直接更新卡或执行业务联动。- 删除重复流量增量算法,只保留领域实现。
RefreshCardDataFromGateway改为调用RequestCardObservation + ApplyCardObservation。ManualUpdateRealnameStatus改为调用领域用例。- 获取实名链接后的
ManualTriggerService.TriggerSingle改为CreateSyncTriggerSeries。 - C 端和后台设备操作中直接调用 Gateway 的路径迁入 Application,再在用例成功边界创建触发序列。
10.2 保留并复用
- Gateway Client 的加密、签名和 HTTP 封装。
- Redis 分片 Sorted Set 调度基础设施。
- Asynq Worker。
- 卡流量同步锁,迁移为通用请求互斥实现。
- 现有资产手动刷新接口和权限契约。
- 现有批量轮询运维能力;其执行逻辑和记录迁入新用例与统一审计后,再下线旧专用日志表。
10.3 数据变更
- 不新增实名要求字段,继续以
tb_carrier.realname_link_type判断是否需要实名及链接生成方式。 - 发布前检查
iccid_19重复数据并建立未删除数据范围内的唯一索引,保证 19 位回调精确命中。 - 卡增加轮询活跃标记字段,或由独立调度状态表保存;实现阶段根据写入频率决定,不能把高频调度心跳写入核心卡表。
- 现有
tb_polling_config条件匹配迁移为单一全局活跃/不活跃策略,不再使用card_condition/card_category形成隐式轮询资格。 - 事件任务载荷增加
scene/series_id/attempt/source/request_id/correlation_id/expected_state。 - 不创建
tb_card_sync_execution。
十一、发布与人工验证
停机发布,API 与 Worker 同时切换:
- 验证
realname_link_type=none/template/gateway分别对应无需实名、模板实名和 Gateway 实名,不再按行业卡统一跳过。 - 验证
enable_polling=false会移除自动轮询,但业务事件和手动刷新仍能按各自规则工作。 - 验证活跃卡使用活跃间隔,不活跃卡使用低频间隔,重新活跃后立即恢复。
- 验证一次业务事件形成立即、3 分钟、5 分钟三个任务。
- 验证停复机、实名和切卡达到预期状态后,剩余任务不再请求 Gateway。
- 验证同场景高频查询只合并重复序列,不压制新的停复机或支付事件。
- 验证单卡互斥只覆盖请求执行时间,不形成五分钟全局冷却。
- 验证 Gateway 超频时当前尝试直接记为
rate_limited,不创建退避状态,后续阶梯任务仍保留。 - 验证现有后台和 C 端刷新接口契约不变,且不额外创建阶梯序列。
- 验证移动、联通、电信回调均先经过防腐层,19 位和 20 位 ICCID 分别按双列精确路由,重复成功回调不重复激活套餐。
- 验证解除实名回调只写 Integration Log,不修改卡实名状态。
- 验证开放接口仍立即返回本地数据,事件触发失败不改变响应。
- 验证普通同步、状态变化、手动刷新和连续失败能够在审计中心按资源和触发序列查询。