# 新增需求 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. 验证普通同步、状态变化、手动刷新和连续失败能够在审计中心按资源和触发序列查询。
