## MODIFIED Requirements ### Requirement: polling_realname_handler.go——实名数据采集 **文件**:`internal/task/polling_realname_handler.go`(< 220行) **构造函数依赖**(通过构造函数注入,禁止全局变量): ```go func NewPollingRealnameHandler( base *PollingBase, gateway *gateway.Client, iotCardStore *postgres.IotCardStore, asynqClient *asynq.Client, stopResumeSvc iot_card_svc.StopResumeServiceInterface, deviceSimBindingStore *postgres.DeviceSimBindingStore, // 新增:用于查询卡所属设备 ) *PollingRealnameHandler ``` **方法列表**: - `Handle(ctx, task) error`:Asynq 任务入口 - `triggerFirstRealnameActivation(ctx, cardID)`:提交卡级首次实名激活任务(已有,不变) - `triggerDeviceRealnameActivation(ctx, cardID)`:**新增**,查询卡所属设备并提交设备级激活任务 **处理流程(0→1 路径新增步骤)**: 1. 调 `base.acquireConcurrency("realname")`,获取失败则 requeue 后返回 2. 调 `base.getCardWithCache(ctx, cardID)` 获取卡信息 3. 调 Gateway 查询实名状态 4. 写 Store(更新 `real_name_status`、`last_real_name_check_at`,首次实名时写 `first_realname_at`) 5. 若实名状态变为已实名(0→1): a. 调 `triggerFirstRealnameActivation(ctx, cardID)` — 提交卡级激活任务 b. **调 `triggerDeviceRealnameActivation(ctx, cardID)` — 提交设备级激活任务(新增)** c. 调 `stopResumeService.EvaluateAndAct(ctx, freshCard)` — 触发复机判断 6. 调 `base.requeueCard(ctx, cardID, "realname")` 重新入队 7. 调 `base.releaseConcurrency("realname")` #### Scenario: 实名状态由未实名变为已实名——同时触发卡级和设备级激活 - **GIVEN** cardID=4,`real_name_status=0`,该卡通过 `tb_device_sim_binding` 绑定于设备 ID=2 - **WHEN** Gateway 返回实名已完成,Handler 检测到 0→1 变化 - **THEN** Store 更新 `real_name_status=1, first_realname_at=NOW()` - **AND** 提交卡级 Asynq 任务(`carrier_type=iot_card, carrier_id=4`) - **AND** 提交设备级 Asynq 任务(`carrier_type=device, carrier_id=2`) - **AND** 调用 `EvaluateAndAct` 触发复机评估 #### Scenario: 独立卡实名——仅触发卡级激活 - **GIVEN** cardID=10,`real_name_status=0`,未绑定任何设备 - **WHEN** Gateway 返回实名已完成,Handler 检测到 0→1 变化 - **THEN** 提交卡级 Asynq 任务(`carrier_type=iot_card, carrier_id=10`) - **AND** `triggerDeviceRealnameActivation` 调用 `GetActiveBindingByCardID` 返回 not found,静默跳过,不报错 #### Scenario: 实名状态未变化——不触发任何激活 - **GIVEN** cardID=200,`real_name_status=1`(已实名),Gateway 返回仍已实名 - **WHEN** Handler 执行 - **THEN** Store 不执行实名更新;不提交任何激活任务;按正常间隔 requeue ## ADDED Requirements ### Requirement: PollingRealnameHandler 注入 DeviceSimBindingStore `PollingRealnameHandler` 结构体 SHALL 包含 `deviceSimBindingStore *postgres.DeviceSimBindingStore` 字段,由 `NewPollingRealnameHandler` 构造函数注入。`pkg/queue/handler.go` 的 `registerPollingHandlers` SHALL 将已可用的 `h.workerResult.Stores.DeviceSimBinding` 作为最后一个参数传入。 #### Scenario: Worker 启动时 DeviceSimBindingStore 正确注入 - **WHEN** Worker 进程启动,调用 `registerPollingHandlers()` - **THEN** `NewPollingRealnameHandler` 接收 6 个参数,第 6 个为 `h.workerResult.Stores.DeviceSimBinding` - **AND** handler 的 `deviceSimBindingStore` 字段非 nil #### Scenario: DeviceSimBindingStore 为 nil 时 triggerDeviceRealnameActivation 静默跳过 - **GIVEN** handler 的 `deviceSimBindingStore` 为 nil(测试或未注入场景) - **WHEN** 调用 `triggerDeviceRealnameActivation` - **THEN** 函数立即返回,不 panic,不报错