--- schema: "hl-changelog/v2" ticket: "8754" title: "通知中心内部接口「按事件码查通知事件配置」出参新增 smsTemplateReady(短信模板是否已配真实号)" consumer: "internal" author: "jw(GIT)" change_type: "修改接口" backend_status: "deployed" gateway_status: "not_required" frontend_status: "not_required" frontend_owner: "" frontend_ref: "" target_release: "" verified_at: "" status_note: "GET /internal/notification/event-config/{eventCode} 出参纯新增派生字段 smsTemplateReady(模板号非空且非哨兵 TODO_PLACEHOLDER 为 true),首个调用方为 order-v3 补发成团通知。内部接口不经网关,前端无需对接。已合并 dev-v3(PR #8775,merge aac6a6a13)并部署 TEST,经 order-v3 补发链路验证哨兵 / 假模板号 / 还原三态读数正确。管理后台侧变化见同日 04_8754 修改接口·管理后台那份。" updated_at: "2026-10-04" base: "dev-v3" --- # user-service: 内部接口「按事件码查通知事件配置」新增 smsTemplateReady > **服务**: hl-user-service(内部接口,网关不放行) > **PR**: `#8775`(已合入 `dev-v3`,合并提交 `aac6a6a13`) > **Issue**: #8754 --- ## ⚠️ 关键变化 🟢 `GET /internal/notification/event-config/{eventCode}` 出参**纯新增**一个派生字段 `smsTemplateReady`(Boolean):短信模板号非空且不是哨兵 `TODO_PLACEHOLDER` 时为 `true`。入参、其余出参、错误行为不变。 🟢 新增首个调用方:order-v3 补发成团通知(`NotificationSendLogFeignClient#getEventConfig`)据此判断短信能不能真发,补发回执才能如实计数。 --- ## 一、背景 「先开发、模板后补」期间,事件配置的 `sms_enabled=1` 而 `sms_template_code='TODO_PLACEHOLDER'`:通知中心照常进短信通道,但 `SmsChannelSender` 遇哨兵只记 SKIP_NO_TEMPLATE、不真发。调用方只看 `smsEnabled` 会把发不出去的短信算成送达。哨兵判定原只写在 `SmsChannelSender` 里,本单收成 `SmsTemplateCodes.isConfigured` 一处,发送侧与本接口共用,调用方不必知道哨兵字面量。 --- ## 二、变更接口清单 | # | 接口 | 方法 | 路径 | 变更类型 | 说明 | |---|------|------|------|----------|------| | 1 | 按事件码查通知事件配置 | GET | `/internal/notification/event-config/{eventCode}` | 修改 | 出参新增派生字段 `smsTemplateReady` | --- ## 三、接口详情 ### 1. 按事件码查通知事件配置 `GET /internal/notification/event-config/{eventCode}` **VO**: `Result` #### 使用场景 服务间只读查询某通知事件的通道开关与模板配置。order-v3 补发成团通知时调用,按 `inappEnabled`、`smsEnabled && smsTemplateReady` 判断两条客户通道能否真发出。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | eventCode | Path | String | ✅ | 事件编码 | **不变**,如 `GROUP_BATCH_FORMED` | | X-Internal-Token | Header | String | ✅ | 集群内部令牌 | **不变**,与其他 `/internal/**` 相同 | #### 出参 | 字段 | 类型 | 说明 | |------|------|------| | smsTemplateReady | Boolean | 🆕 短信模板是否已就绪:`smsTemplateCode` 非空且不是 `TODO_PLACEHOLDER` 为 `true`。派生字段,不落库。与 `smsEnabled=1` 同时成立短信才会进入真实发送 | | eventCode / eventName / categoryCode | String | **不变** | | inappEnabled / smsEnabled / miniappEnabled / oaEnabled / weworkEnabled | Integer | **不变**,0=关 1=开 | | smsTemplateCode / smsSignName / smsFieldMapping 等模板字段 | String | **不变** | #### 请求示例 ```http GET /internal/notification/event-config/GROUP_BATCH_FORMED HTTP/1.1 Host: hl-user-service X-Internal-Token: <集群内部令牌> ``` #### 响应示例 ```json { "code": 200, "message": "success", "data": { "eventCode": "GROUP_BATCH_FORMED", "eventName": "成团通知", "categoryCode": "GROUP_BATCH", "inappEnabled": 1, "inappTitleTemplate": "您报名的团已成团", "inappContentTemplate": "您报名的${productName}(出发日期${departDate})已成团,请留意后续出行安排。", "inappLinkTemplate": "/packages/order/detail/detail?id=${orderId}", "smsEnabled": 1, "smsTemplateCode": "TODO_PLACEHOLDER", "smsSignName": null, "smsFieldMapping": "{\"productName\":\"${productName}\",\"departureDate\":\"${departDate}\",\"batchNo\":\"${groupBatchNo}\"}", "miniappEnabled": 0, "oaEnabled": 0, "weworkEnabled": 0, "weworkReceiverType": null, "smsTemplateReady": false }, "success": true } ``` #### 空数据 / 降级响应 事件不存在时 `data=null`(不变),调用方应视为所有通道不可达。order-v3 侧降级(调用失败 / 熔断)返回失败结果 `100903`,补发接口据此返回 589595,不会把失败当成「未配置」。 #### 错误响应 缺少或错误的 `X-Internal-Token` 被 `InternalAuthInterceptor` 拦截(不变):**HTTP 403**,响应体如下(注意字段是 `msg` 不是 `message`): ```json { "code": 403, "msg": "内部接口禁止外部访问" } ``` #### 业务边界 - `smsTemplateReady` 只看模板号,不看 `smsEnabled`;调用方须两者同时判断。 - 不判断阿里云凭据是否配置:TEST 未配凭据时短信为模拟成功(只打日志不真发),本字段仍可能为 `true`。 --- ## 四、契约约束与正确调用方式 - 判断「短信能否真发」:`smsEnabled == 1 && smsTemplateReady == true`。不要在调用方写死 `TODO_PLACEHOLDER` 字面量。 - 调用方本地 VO 建议 `@JsonIgnoreProperties(ignoreUnknown = true)`,只取需要的字段。 --- ## 五、数据库行为 - 本接口只读,零写入;`smsTemplateReady` 由 `sms_template_code` 派生,不落库。 - 同批迁移 `V20261003_8754` 改了 `notification_event_config` 中 `GROUP_BATCH_FORMED` 一行(开短信、模板号哨兵、变量映射),与本接口契约无关。 --- ## 六、边界行为 - `smsTemplateCode` 为 `null` / 空串 / `TODO_PLACEHOLDER` → `false`;其他任意值 → `true`(不校验是否为阿里云真实存在的模板号)。 ## 六.6、修改前后对比 | 场景 | 改前 | 改后 | |---|---|---| | 模板号为哨兵 | 出参无此字段,调用方只能自己比对字面量 | `smsTemplateReady=false` | | 模板号为真实值 | 同上 | `smsTemplateReady=true` | ## 六.7、影响评估 - **是否破坏向后兼容**:否,纯新增字段;改前全仓零调用方。 - **回滚**:revert PR #8775 后重新部署 user-service(order-v3 读不到该字段时按「短信未就绪」保守计数)。 --- ## 七、不影响范围 - 其他 `/internal/notification/**` 接口、通知分发逻辑、短信发送行为(哨兵判定结果与改前逐字等价)。 - 管理后台通知配置接口。 --- ## 八、测试环境已验证 **环境**:TEST **验证时间**:2026-10-03 20:43~20:50 **构建身份**:user-service `dev-v3 @ aac6a6a13`。 本接口不经网关,经它唯一的调用方 order-v3 补发链路间接验证: | 配置状态 | order-v3 补发回执 | 说明 | |---|---|---| | `smsEnabled=1`,`smsTemplateCode=TODO_PLACEHOLDER` | `smsReady=false`、`smsCount=0` | order-v3 读到 `smsTemplateReady=false` | | 后台把模板号改成假号 `SMS_TEST_8754` | `smsReady=true`、`smsCount=2` | 读到 `smsTemplateReady=true`;后台改配置时清了缓存,3 秒后即生效 | | 还原为 `TODO_PLACEHOLDER` | `smsReady=false` | 读到 `smsTemplateReady=false` | 本地:`InternalNotificationControllerTest` 15 例(含哨兵 → `false` 且序列化进 JSON)、`SmsTemplateCodesTest` 2 例、`SmsChannelSenderTest` 37 例;user-service 全量 4211 例 0 失败。 --- ## 十、相关文档 - Issue `#8754`;PR `#8775`;管理后台侧变化见同日 `04_8754` 修改接口·管理后台那份 ## 关联 / 联系人 ### 链接 - **Issue**: [#8754](https://git.1814.love/wx/HL/issues/8754) - **PR**: [#8775](https://git.1814.love/wx/HL/pulls/8775) - **Merge commit**: [aac6a6a13](https://git.1814.love/wx/HL/commit/aac6a6a13) ### 联系人 - **后端负责人**: @jw