# 新增需求 01:数据同步触发与轮询优化 > 状态:已合并至标准评审稿,本文保留为实施明细。 > 评审主文档:`../7月迭代技术方案-标准评审稿.md` > 范围:IoT 卡实名、流量、网络状态和设备实时数据同步。 ## 一、已确认决策 1. 轮询、运营商回调、业务事件触发是三条相互隔离的自动同步通道。 2. 轮询始终保留,负责回调失效、事件漏网和上游偶发失败时的最终兜底。 3. 资产是否轮询只保留现有全局开关 `enable_polling`;不再设计“实名资格、流量资格、状态资格”等业务资格开关。 4. 轮询优化只区分活跃卡和不活跃卡,通过不同轮询间隔降低无效请求,不因业务类型直接永久排除某类卡。 5. 行业卡是否需要实名不能由 `card_category=industry` 推断,统一以运营商 `realname_link_type` 判断。 6. 业务事件触发不是新增一个显式同步接口,而是埋点到关键查询、停复机、支付、实名和设备操作用例中。 7. 每次事件默认形成三个独立尝试:立即、事件发生后 3 分钟、事件发生后 5 分钟;三次结束后由常规轮询继续兜底。 8. 不设置统一五分钟冷却。重复请求控制拆成同场景触发合并、单卡请求互斥和运营商最小请求间隔;Gateway 超频只记录当前失败,不建立退避状态。 9. 运营商实名回调百分百信任,不做来源真实性校验;但必须经过防腐层完成报文解析、标识转换和状态语义转换。 10. 解除实名第一版不修改业务状态,只保留回调入口、写入统一集成审计并返回运营商要求的成功报文。 11. 三条自动通道和现有手动刷新接口共享同一套“获取上游观测并应用到业务”的 DDD 用例,禁止各入口直接更新卡字段。 12. 同步执行记录属于全局审计的 Integration Log,不在本方案新建 `tb_card_sync_execution` 或独立同步监控系统。 13. 本次迁移触碰到的旧实名、流量和状态写逻辑必须删除或改为调用新用例,不保留长期双实现。 ## 二、现状问题 当前代码存在以下明确问题: - `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,而是先收口写侧用例,再把轮询、回调和事件触发接入同一边界。 ## 三、目标架构 ```mermaid 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 目录 ```text 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 实名要求判断 不得继续使用以下判断: ```go 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 标准化观测值 ```go 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` 是唯一允许把上游数据写入卡领域状态的入口: 1. 加载并锁定卡聚合。 2. 根据观测类型执行实名、流量或网络状态规则。 3. 状态未变化时只更新时间和观测来源,不重复产生业务事件。 4. 状态变化时保存聚合,并在同一事务写 Outbox 和关键 Audit Event。 5. 实名 `0 -> 1` 产生 `CardRealnamed`,触发卡级/设备级套餐激活和停复机评估。 6. 流量正增量产生 `CardTrafficIncreased`,由套餐应用服务扣减套餐流量。 7. 网络状态变化产生 `CardNetworkStatusChanged`,由停复机策略重新评估。 8. 状态变化或流量增加时更新卡活跃标记。 解除实名回调不构造 `Verified=false` 观测,不修改卡状态。 ## 五、轮询优化:只做活跃度调频 ### 5.1 全局开关 `enable_polling` 是资产是否参与自动轮询的唯一业务开关: - `false`:从所有轮询队列移除,事件触发也不因此失效;用户主动操作仍可触发 Best Effort 同步。 - `true`:按照活跃度和运营商能力进入对应 Gateway 查询队列。 - 设备关闭轮询时,继续沿用现有设备与绑定卡级联规则。 不再提供“实名轮询开关、流量轮询开关、状态轮询开关”给业务人员配置。 ### 5.2 活跃标记 卡增加或维护以下轮询调度字段: ```text polling_activity_level active / inactive last_polling_activity_at 最后活跃时间 last_polling_activity_scene 最后活跃场景 last_upstream_change_at 上游观测最后发生变化时间 ``` 以下情况标记为活跃: | 场景 | 说明 | |---|---| | C 端、后台或开放接口查询资产实时信息 | 表明当前有人关心该资产 | | 获取实名链接 | 表明即将发生实名操作 | | 停机、复机、切卡、重启、恢复出厂设置 | 表明上游状态正在变化 | | 套餐支付、激活、失效或流量重置 | 可能影响流量和停复机 | | 运营商回调 | 上游已发生变化 | | 轮询发现实名、流量或网络状态变化 | 卡当前确实活跃 | 卡在配置时间内没有业务活动,且连续轮询未发现上游变化时转为 `inactive`。该判断在每次重排队时完成,不新增全表扫描任务。 第一版建议配置而非写死: ```text 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 调度规则 ```text enable_polling=false -> 不入自动轮询队列 enable_polling=true + active -> 使用全局活跃间隔 enable_polling=true + inactive -> 使用全局不活跃间隔 realname_link_type=none -> 不创建实名查询任务 ``` 已实名卡仍可低频查询实名状态,以发现上游实名逆转;未实名行业卡也不能因卡类别被排除。 ## 六、业务事件触发 ### 6.1 不是新增接口 本需求不新增 `POST /api/admin/card-sync/requests`,也不新增独立“事件同步”按钮。 现有接口保持: ```http POST /api/admin/assets/:identifier/refresh POST /api/c/v1/asset/refresh ``` 现有批量轮询运维接口可以保留,但必须改为调用新的 Application 用例,不再自行维护另一套同步逻辑。 业务事件触发由 Application UseCase 或 Query 完成后调用内部端口: ```go 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 返回超频后不建立额外退避状态。 #### 同场景触发合并 ```text cardsync:series:{scene}:{resource_type}:{resource_id}:{sync_type} ``` - 同一场景、同一资源、同一同步类型已有未结束序列时,新触发合并到原序列。 - 合并只防止页面轮询、开放接口重试等重复创建大量 `0/3/5` 任务。 - 停复机、支付、切卡等不同业务场景不会被一个查询场景长期压制。 - 序列键只覆盖最后一次计划任务和短暂缓冲,不作为全局五分钟冷却。 #### 单卡请求互斥 ```text 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: ```mermaid 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 防腐层职责 “百分百信任回调”表示信任其业务结论,不表示让运营商报文直接进入领域模型。 ```go 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 位解析 回调必须复用现有双列存储和按长度路由规则: ```text 收到 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 路由 ```http 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 | 必须携带: ```text 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 增加可选调度信息: ```json { "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" } } ``` 同步明细查询复用全局审计接口: ```http GET /api/admin/audit/integrations ?provider=gateway &resource_type=iot_card &resource_key=89860... &trigger_scene= &trigger_series= ``` ### 9.2 前端 保留现有资产刷新按钮,不新增第二个同步按钮。 资产详情增加紧凑的轮询状态展示: ```text 自动轮询:已开启 活跃状态:活跃 最后活跃:2分钟前,C端资产详情 下次兜底:约1分钟后 同步轨迹:查看 ``` “查看”跳转: ```text /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 同时切换: 1. 验证 `realname_link_type=none/template/gateway` 分别对应无需实名、模板实名和 Gateway 实名,不再按行业卡统一跳过。 2. 验证 `enable_polling=false` 会移除自动轮询,但业务事件和手动刷新仍能按各自规则工作。 3. 验证活跃卡使用活跃间隔,不活跃卡使用低频间隔,重新活跃后立即恢复。 4. 验证一次业务事件形成立即、3 分钟、5 分钟三个任务。 5. 验证停复机、实名和切卡达到预期状态后,剩余任务不再请求 Gateway。 6. 验证同场景高频查询只合并重复序列,不压制新的停复机或支付事件。 7. 验证单卡互斥只覆盖请求执行时间,不形成五分钟全局冷却。 8. 验证 Gateway 超频时当前尝试直接记为 `rate_limited`,不创建退避状态,后续阶梯任务仍保留。 9. 验证现有后台和 C 端刷新接口契约不变,且不额外创建阶梯序列。 10. 验证移动、联通、电信回调均先经过防腐层,19 位和 20 位 ICCID 分别按双列精确路由,重复成功回调不重复激活套餐。 11. 验证解除实名回调只写 Integration Log,不修改卡实名状态。 12. 验证开放接口仍立即返回本地数据,事件触发失败不改变响应。 13. 验证普通同步、状态变化、手动刷新和连续失败能够在审计中心按资源和触发序列查询。