本域总览见《账号计费中心 · 结构大纲》。本文为该域完整设计:定位与边界 / 角色与依赖 / 数据模型 / 核心流程 / 接口契约 / 关键约束与验收标准。 排序理由:先数据模型(事实结构)→ 再核心流程(运行路径)→ 最后接口契约(流程的对外投影)。
本域回答四件事:通道怎么配、模板怎么管、消息怎么发、发得怎么样。触达形态为短信、邮件、站内信三种。
| 口径 | 定论 |
|---|---|
| 配置唯一真相 | sms_configs / email_configs;发送前实时读启用行,改库即生效、消费方零重启 |
| 每通道一行 | 唯一索引 ux_sms_configs_provider / ux_email_configs_provider;「启用」是多行共存的开关,不是互斥位 |
| 选路顺序 | 启用行按 priority 升序,并列按 created_at、id 升序,顺序稳定可复现 |
| 默认通道 | 启用行中优先级最高者;priority 默认 100(队尾档位),默认通道档位 10,取值 1~10000 |
| 场景与模板 | 场景代码枚举 verify_code / invite / security_notice;短信以 sms_template_bindings(通道 × 场景)为准,邮件 email_templates 按场景单表、缺失回落内置 |
| 站内信与凭据 | notifications 承载告警送达,已读态组织共享;凭据只在发送或探活瞬间解密成内存快照,响应只出 *_configured,明文永不回传,只写不读 |
本域不管什么:资金与账务属 D04;计量聚合属 D05;账户状态与准入属 D06;风控与实名属 D09;支付渠道属 D03。运营面端点的语义细节写在本文,索引统一登记在 D11;门户侧消费体验见 D12。
| 角色 | 能力 | 边界 |
|---|---|---|
| 运营面(渠道运营权限) | 通道新建 / 改配 / 启停 / 优先级重排 / 删除 / 探活 / 测试发送;场景绑定与在线模板读写、预览、删除;健康概览与事件查询 | 不能改冷却态与失败计数(进程内自动态);不能绕过联调白名单实发 |
| 计费路由(router)与统一登录服务 | 经内部发送端点发验证码短信与邮件、读状态概要 | 不接触凭据,不做选路决策 |
| 账户主体 | 站内信查询与已读、告警阈值订阅开关 | 订阅开关即阈值列,阈值 0 = 关闭 |
| 部署侧定时任务 | 触发低频维护扫描(告警扫描 + 过期一次性凭证清理) | 幂等可重跑 |
上游依赖:
| 上游 | 依赖内容 | 语义 |
|---|---|---|
| D04 钱包与账务 | customers.cash_balance、customers.balance_alert_threshold |
判据为「现金余额 < 阈值」;不读欠费派生标记、不读实时可用额度 |
平台级运行时配置(platform_settings) |
验证码与邀请有效期、手机认证开关 | 模板正文的有效期以该配置为准,非硬编码常量 |
| 对称加密 + 密钥环境注入 | access_key_id_encrypted、access_key_secret_encrypted、password_encrypted |
明文不入日志、不进响应 |
下游被引用:
| 引用方 | 引用内容 | 语义 |
|---|---|---|
| D12 用户自助与门户 | notifications 列表与已读、订阅开关 |
门户是站内信的消费方 |
| D11 运营管理 | 运营面渠道端点的索引登记 | 语义细节在本文,索引不重复展开 |
| D09 风控与合规 | 规则命中后的告警送达面 | 同为 notifications,type 含 usage_spike |
customers 与 users 由本域只读引用,不新增外键到本域;授权主体与权限范围由 D11 定义。
| 表 | 职责 | 关键字段 |
|---|---|---|
sms_configs |
短信通道配置,每通道标识一行 | provider、is_active、priority、sign_name、template_code、access_key_id_encrypted、sdk_app_id、region、daily_limit、interval_seconds |
email_configs |
邮件通道配置,每通道标识一行 | provider、is_active、priority、smtp_host、smtp_port、encryption、username、password_encrypted、from_email、daily_limit |
sms_template_bindings |
(通道 × 场景)→ 渠道侧模板 ID | config_id、scenario、template_code、is_enabled |
email_templates |
场景维度在线邮件模板,与通道无关 | scenario、subject_template、body_template、is_enabled |
channel_send_events |
降级 / 失败 / 用量可观测的唯一事实流 | channel_type、config_id、provider、scenario、target_masked、outcome、fallback_from、error_kind、latency_ms、is_test |
notifications |
站内信(余额 / 用量 / 关户类告警送达面) | customer_id、recipient_user_id、type、group_id、title、content、read_at |
provider_in_use,非唯一冲突原样抛出、不伪装成「已存在」。ux_sms_template_bindings_config_scenario 约束绑定维度;绑定随通道级联删除。email_templates.scenario 唯一,一个场景至多一份在线模板。ix_channel_events_cfg 聚合,事件倒序浏览走 ix_channel_events_created。target_masked 在写事件前完成脱敏,本表不存任何可直接触达的地址。ix_notif_customer_created、ix_notif_recipient、ix_notif_group_created 分别服务组织级、个人级、组级视角。operation_logs 同事务落库;只读动作不留痕。created_by 指向本库 users,跨服务操作人标识无法满足外键时降级记空值。outcome=fallback_ok 与 fallback_from。rejected 用于凭据不全与配额耗尽。rejected 供排障,但不计入熔断窗口,避免若干条邀请消息缺绑定就把通道冷却 60 秒、连带验证码一起被跳过。disabled;启用且用量达 daily_limit → quota_exhausted;启用且冷却中 → cooldown;启用且为默认通道 → active;其余启用行 → standby。priority 最小者(并列取 created_at、id 更小者);无启用行时无默认通道。ok 与 fallback_ok 计入;rejected / failed 不计;测试发送(is_test)一律不计。channel_type + config_id 维度聚合,批量读一次算全部通道;事件经独立短会话即时提交,聚合读已提交真值。daily_limit 1~100000(短信默认 1000、邮件默认 200);interval_seconds 30~3600;并发下允许 1~2 笔超限竞态,不上分布式锁。priority_invalid;未提及的行保持现值。删除启用中的行 → 409 active_config_delete_forbidden;行不存在 → 404 sms_config_not_found / email_config_not_found。| 场景 | 规则 |
|---|---|
error_kind 值域 |
auth / business_rejected / timeout / network,另有 quota(配额耗尽)与 rejected(业务拒绝 / 配置不全)两个非异常分类 |
| 归类顺序与日志 | 凭据类先判,其次 4xx 业务拒绝,最后兜底网络;原始异常完整入日志,事件表只存分类 |
| 事件写入 | 经独立短会话即时落库(不随调用方事务回滚丢失),写入失败只记日志、不阻塞主流程 |
| 脱敏(写事件前) | 手机号取数字位,前 3 位 + 四掩码 + 后 4 位(不足 7 位则全掩码);邮箱本地名首字符 + 三掩码 + 域名;事件、日志、查询响应一律为脱敏形态 |
| 探活与失败文案 | 只做只读列真与登录类探测,不实际发送、不产生事件、不耗配额;失败文案按特征归一为凭据无效 / 签名不符 / 无权限 / 地址解析失败 / 超时 / 认证失败 / 连接失败,可行动但不透内部细节 |
/internal/sms,16 条)| 方法 | 路径 | 语义 |
|---|---|---|
| GET | /internal/sms/status |
默认通道概要:启用态 + 通道标识 + 配额上限 + 限速间隔 |
| GET | /internal/sms/configs |
通道列表:启用行在前按优先级升序,附 health 与 today_sent |
| POST | /internal/sms/configs |
新建通道:不自动启用、落队尾档位;标识占用 → 409 provider_in_use |
| PATCH | /internal/sms/configs/{config_id} |
改配:未传不改,凭据传了才替换;不存在 → 404 sms_config_not_found |
| POST | /internal/sms/configs/{config_id}/enable |
启用参与选路,保持现优先级落位;幂等 |
| POST | /internal/sms/configs/{config_id}/disable |
停用(允许零行启用);无可用通道时自动关闭手机认证开关 |
| PUT | /internal/sms/configs/priorities |
优先级整体重排(items:id + priority);重复值 → 422 priority_invalid |
| POST | /internal/sms/configs/{config_id}/activate |
一期兼容转发,等价启用 |
| POST | /internal/sms/configs/{config_id}/deactivate |
一期兼容转发,等价停用 |
| DELETE | /internal/sms/configs/{config_id} |
删除;启用中 → 409 active_config_delete_forbidden;成功 204 |
| POST | /internal/sms/configs/{config_id}/probe |
只读探活,不发短信;返回 ok 与归一化 error |
| GET | /internal/sms/configs/{config_id}/templates |
场景绑定列表(含全部场景),附 required_placeholders、template_example、placeholder_values |
| PUT | /internal/sms/configs/{config_id}/templates/{scenario} |
按场景 upsert 模板 ID 与绑定开关;场景非法 → 404 scenario_not_found |
| DELETE | /internal/sms/configs/{config_id}/templates/{scenario} |
删除场景绑定,该通道对此场景回到配置不全降级态;成功 204 |
| POST | /internal/sms/configs/{config_id}/test-send |
指定通道真发测试短信;白名单外 → 403 target_not_allowed;事件带 is_test,不计熔断与配额 |
| POST | /internal/sms/send |
统一发送入口:任一通道成功即 sent=true;全耗尽无兜底 → sent=false;异常 → 503 code_send_failed;场景非法 → 422 scenario_invalid |
/internal/email,17 条)| 方法 | 路径 | 语义 |
|---|---|---|
| GET | /internal/email/status |
默认通道概要:启用态 + 通道标识 + 发件地址 + 配额上限 |
| GET | /internal/email/configs |
通道列表:启用行在前按优先级升序,附 health 与 today_sent |
| POST | /internal/email/configs |
新建通道:按标识分别校验凭据组;占用 → 409 provider_in_use;成功 201 |
| PATCH | /internal/email/configs/{config_id} |
改配:留空或未传不改,凭据传了才替换;不存在 → 404 email_config_not_found |
| POST | /internal/email/configs/{config_id}/enable |
启用参与选路,保持现优先级落位;幂等 |
| POST | /internal/email/configs/{config_id}/disable |
停用(允许零行启用,回落部署侧兜底);幂等 |
| PUT | /internal/email/configs/priorities |
优先级整体重排;重复值 → 422 priority_invalid |
| POST | /internal/email/configs/{config_id}/activate |
一期兼容转发,等价启用 |
| POST | /internal/email/configs/{config_id}/deactivate |
一期兼容转发,等价停用 |
| DELETE | /internal/email/configs/{config_id} |
删除;启用中 → 409 active_config_delete_forbidden;成功 204 |
| POST | /internal/email/configs/{config_id}/probe |
只读探活:连接加登录判定,不实际发信;返回 ok 与归一化 error |
| GET | /internal/email/templates |
在线模板列表(全部场景,未配置为 None 表示回落内置),附契约、示例、内置文案、placeholder_values |
| PUT | /internal/email/templates/{scenario} |
按场景 upsert 在线模板;缺必需占位符 → 422 template_placeholder_missing;未知场景 → 404 scenario_not_found |
| POST | /internal/email/templates/{scenario}/preview |
假数据渲染返回主题与正文,有效期取真实值;未知场景 → 404 scenario_not_found |
| DELETE | /internal/email/templates/{scenario} |
删除在线模板,该场景回落代码内置;成功 204 |
| POST | /internal/email/configs/{config_id}/test-send |
指定通道真发测试邮件;白名单外 → 403 target_not_allowed;事件带 is_test,不计熔断与配额 |
| POST | /internal/email/send |
统一发送入口:直发或发码(按模板渲染);无启用行无兜底 → 409 no_active_provider;场景非法 → 422 scenario_invalid;异常 → 503 code_send_failed |
/internal/channels、/internal/alerts)| 方法 | 路径 | 语义 |
|---|---|---|
| GET | /internal/channels/health |
两类通道合并健康概览:徽标、当日用量、配额上限、冷却态与窗口内失败数,附近 24 小时降级次数与最近错误(脱敏) |
| GET | /internal/channels/events |
发送事件查询:创建时间倒序取最近 N 条(上限 200),可按通道类型过滤;目标一律脱敏 |
| POST | /internal/alerts/scan |
低频维护扫描:余额阈值告警(24 小时去重)+ 过期一次性凭证清理;返回新建通知数与清理数,幂等 |
/api/account/ops/channels,32 条)| 方法 | 路径 | 语义 |
|---|---|---|
| GET | /api/account/ops/channels/sms |
短信通道列表(附徽标与当日用量) |
| POST | /api/account/ops/channels/sms |
新建短信通道 |
| PATCH | /api/account/ops/channels/sms/{config_id} |
编辑短信通道(凭据留空不改) |
| POST | /api/account/ops/channels/sms/{config_id}/enable |
启用短信通道 |
| POST | /api/account/ops/channels/sms/{config_id}/disable |
停用短信通道 |
| PUT | /api/account/ops/channels/sms/priorities |
短信通道优先级整体重排 |
| POST | /api/account/ops/channels/sms/{config_id}/activate |
一期兼容转发,等价启用 |
| POST | /api/account/ops/channels/sms/{config_id}/deactivate |
一期兼容转发,等价停用 |
| POST | /api/account/ops/channels/sms/{config_id}/probe |
短信通道只读探活 |
| DELETE | /api/account/ops/channels/sms/{config_id} |
删除短信通道(启用中拒绝) |
| POST | /api/account/ops/channels/sms/test-send |
一期兼容路径:经统一发送入口真发一条验证码短信 |
| POST | /api/account/ops/channels/sms/{config_id}/test-send |
对指定短信通道真实发测试短信(可选场景) |
| GET | /api/account/ops/channels/sms/{config_id}/templates |
该通道的场景绑定列表(含契约与变量当前值) |
| PUT | /api/account/ops/channels/sms/{config_id}/templates/{scenario} |
按场景 upsert 渠道侧模板 ID |
| DELETE | /api/account/ops/channels/sms/{config_id}/templates/{scenario} |
删除短信场景绑定 |
| GET | /api/account/ops/channels/email |
邮件通道列表(附徽标与当日用量) |
| POST | /api/account/ops/channels/email |
新建邮件通道 |
| PATCH | /api/account/ops/channels/email/{config_id} |
编辑邮件通道(凭据留空不改) |
| POST | /api/account/ops/channels/email/{config_id}/enable |
启用邮件通道 |
| POST | /api/account/ops/channels/email/{config_id}/disable |
停用邮件通道 |
| PUT | /api/account/ops/channels/email/priorities |
邮件通道优先级整体重排 |
| POST | /api/account/ops/channels/email/{config_id}/activate |
一期兼容转发,等价启用 |
| POST | /api/account/ops/channels/email/{config_id}/deactivate |
一期兼容转发,等价停用 |
| POST | /api/account/ops/channels/email/{config_id}/probe |
邮件通道只读探活(连接加登录,不实际发信) |
| DELETE | /api/account/ops/channels/email/{config_id} |
删除邮件通道(启用中拒绝) |
| POST | /api/account/ops/channels/email/{config_id}/test-send |
对指定邮件通道真实发一封测试邮件 |
| GET | /api/account/ops/channels/email/templates |
邮件在线模板列表(未配置场景以代码内置兜底) |
| PUT | /api/account/ops/channels/email/templates/{scenario} |
按场景 upsert 在线模板(占位符契约校验,缺失 422) |
| POST | /api/account/ops/channels/email/templates/{scenario}/preview |
渲染预览(后端假数据渲染,只读) |
| DELETE | /api/account/ops/channels/email/templates/{scenario} |
删除在线模板(回落代码内置) |
| GET | /api/account/ops/channels/health |
两类通道合并健康概览 |
| GET | /api/account/ops/channels/events |
发送事件查询(最近 N 条倒序,目标脱敏) |
运营面与内部面共用同一套实现(单一真相不双轨):运营面以门户令牌与渠道运营权限码鉴权,内部面以内部令牌鉴权;语义以 5.1~5.3 为准,端点索引登记见 D11。
fallback_ok 并记录原定通道;每次跳过(冷却、配额、缺绑定)都能被事件或徽标解释。| # | 验收口径(可实测) |
|---|---|
| 1 | 建两条短信通道并分别启用,二者均进入选路;以同一标识再次新建返回 409 provider_in_use |
| 2 | 两条启用通道优先级为 20 与 10,列表中优先级 10 的行 health 为 active,另一行为 standby |
| 3 | 改错首选通道凭据后发码,事件流出现 outcome=rejected,最终成功事件 outcome=fallback_ok 且带 fallback_from |
| 4 | 对首选通道连续制造 5 次失败后,60 秒内其 health 为 cooldown 且选路跳到备用通道;冷却到期自动恢复为 active |
| 5 | 某通道当日上限设为 2,成功发送 2 条后第 3 条不再走该通道并出现 error_kind=quota;测试发送 3 次后 today_sent 不变 |
| 6 | 停用全部短信通道且无兜底配置时发送接口返回未发送;开发环境取码只在日志出现掩码手机号与场景,正文不出现 |
| 7 | 白名单外号码走发送与测试发送都不实发:测试发送返回 403 target_not_allowed,发送返回未发送且事件流无新增 |
| 8 | 优先级重排提交重复值返回 422 priority_invalid;提交合法列表后越靠前的通道先被尝试 |
| 9 | 删除启用中的通道返回 409 active_config_delete_forbidden;停用后再删返回 204,其场景绑定随通道级联消失 |
| 10 | 删除某通道的验证码场景绑定后,该通道对该场景记 rejected 并降级,但失败计数不增加、不进入冷却 |
| 11 | 保存缺少必需占位符的邮件模板返回 422 template_placeholder_missing 并列出缺失变量名;补齐后保存成功,预览返回渲染结果;删除在线模板后该场景回落代码内置模板 |
| 12 | 事件与查询响应中的地址全为脱敏形态,任何接口都不回传凭据明文、只回 *_configured 布尔 |
| 13 | 探活返回成功率与归一化错误文案,不产生事件、不改当日用量;客户阈值为 0 时不产生余额告警,阈值大于零且现金余额低于阈值时产生一条余额不足站内信,24 小时内重复扫描不再新增,同一扫描端点连续调用两次第二次新建通知数为 0 |