Appearance
发信服务(短信/邮件)
状态:BYOA(商户自备账号)改造已完成(2026-07-22~23) | 范围:短信+邮件统一发送能力,代码横跨
hiapi-fast-frame/hiapi-fast-core/hiapi-core-sender(boot 期协议层+发送记录)、hiapi-cloud-private/hiapi-core-public(受保护模块,实际发送逻辑)、hiapi-cloud-public(通用配置表)、hiapi-cloud-admin-ts(商户/平台后台页面)。
一、架构决策:为什么是"复用通用配置表",不是专用表
这次改造中间走过一条弯路,记录下来避免以后重复讨论:最初设计了一张专用的 SenderConfig 表(mid+type+providerCode+config JSON + 独立 Controller 做脱敏/upsert),结果发现商户之前用的、体验更好的模板编辑器就是基于已有的通用配置系统做的,而且商户明确要求"别搬新的,把我原来那套挪过来"。最终方案:短信/邮件配置直接复用 hiapi_core_system_config 通用配置表(group=config_sms/config_email),跟 config_site/config_copy/config_login(商户网站设置)是同一套机制,只是 group 不同。专用表方案的代码(SenderConfig/SenderConfigController/SenderConfigVo 等)已经全部删除,不要再从 git 历史里翻出来抄。
二、BYOA:为什么不能平台代管
早期讨论过"平台账号统一帮商户申请短信签名",最后否掉了——短信签名审核在国内是强合规要求,必须跟实名认证的企业资质一一对应,任何靠谱的短信转售模式都是"商户自备通道账号(BYOA)"或"云厂商分销商子账号"两种,不存在"平台一个资质挂多个商户签名"这种模式。所以现在的架构是:每个商户自己去阿里云/SMTP服务商开户、自己配置自己的 AccessKey/签名/模板,平台不介入、不代管,一个商户的内容违规不会连坐到其他商户或平台自身账号。
同理,计费/额度这块也明确排除了(商户自己给自己的云账号充值,跟平台无关),不要重新提这个方案除非产品明确要做。
三、整体链路
商户后台填 AccessKey/签名/模板 → PUT /merchant/system-basic/config/config_sms|config_email(通用配置接口,写 hiapi_core_system_config,mid=当前商户)
│
业务方调用(如登录/注册验证码) │
SenderFactory.sender(SenderRequest) │
→ AbsSenderService.getConfig(mid) ─────────────┘ (按 mid+group 查同一张表,ICloudConfigService.getConfig(mid, key))
→ SmsSenderService / MailSenderService 实际调厂商 SDK 发送
→ 落一条 SenderRecord(hiapi_core_sender_record,按时间分表)关键文件:
hiapi-fast-frame/hiapi-fast-core/hiapi-core-sender/—— boot 期模块,SenderRecord实体+Jpa+Service、ISenderService/SenderType/MsgType/SenderRequest/VerifyRequest协议层hiapi-cloud-private/hiapi-core-public/.../cn/hiapi/core/sender/—— 受保护模块,AbsSenderService(读配置)、SenderFactory(编排+限流+验证码校验)、SmsSenderService(阿里云)、MailSenderService(SMTP)、SenderController(/public/sender、/feign/sender)hiapi-cloud-public/hiapi-system-basic-core/.../api/merchant/ConfigController.java—— 商户侧通用配置读写(/merchant/system-basic/config/{group}),短信邮件配置复用的就是这个,不是专门为 sender 写的hiapi-cloud-admin-ts/src/views/system/config/{sms,email}/index.vue—— 商户配置页hiapi-cloud-admin-ts/src/views/platform/sender/record/index.vue—— 平台侧发送明细(PlatformSenderRecordController,/platform/sender/record,不强制按 mid 过滤,传 mid 才筛选)
四、配置字段与模板机制
短信(config_sms,阿里云)
accessKeyId、accessKeySecret、sms_sign(签名)、register_template(验证码模板 Code)。只有一个通用验证码模板——SmsSenderService.resolveTemplateCode() 不传 templateId 时固定回退 register_template,所有验证码场景(登录/注册/找回密码/绑定手机号...)共用这一个模板,这是产品明确决定的,不做多模板映射(讨论过要不要给短信也做成"场景→模板Code"的多行映射,决定不做,除非以后真的有业务要用不同文案)。
邮件(config_email,SMTP)
server/port/type/ssl/from/user/password/sign(签名,配模板里的 ${sign} 变量用)+ template(数组,{id,title,content},后台是 el-table 编辑器,新增模板自动生成 email_xxx 形式的ID)。template 是选填的——不填时,MsgType.VerifyCode 类型的邮件会自动回退到内置的 DefaultTemplate.getEmailVerifyCode(),商户什么都不用配就有一份可用的验证码邮件模板。想自定义验证码邮件文案,加一行模板、ID 固定用约定值(如 verify_code),不需要给每个验证码场景各建一行——所有验证码场景共用一个模板,跟短信是同一个产品决定。
模板内容支持的变量:${sign}(签名)、${code}(验证码,仅 VerifyCode 类型)、${minutes}(验证码有效分钟数,仅 VerifyCode 类型)、以及业务方调用时 data 里传的任意其他参数。
建议的业务场景 templateId 约定(尚未全部实现调用方,先定规范)
验证码类(共用一个模板,不需要分别指定 templateId,由 resolveTemplateCode/DefaultTemplate 兜底):登录、注册、找回密码、绑定/换绑手机号或邮箱、支付密码设置/重置、提现。 结果通知类(各业务模块自己定 templateId,不算 sender 基础设施的一部分):注册成功欢迎、密码修改成功提醒、订单/资金变动类通知等。
五、平台侧原有的"短信设置"/"邮件设置"已删除
platform/config/sms、platform/config/email(平台级共享配置,mid=0)连同对应菜单已经整体删除——这两个页面写的数据从改造完成那一刻起就没有代码在读了,继续留着只会误导管理员。平台侧现在只剩"发送记录"一个菜单(platform/sender/record),看全商户的发送明细。
六、部署提醒
改动涉及 hiapi-core-public(受保护模块),需要 report-public-module skill 重新构建+上报到 nuwa。菜单入库 SQL 见 hiapi-cloud-public/docs/sender-menus.sql(商户侧「短信设置」「邮件设置」两条 + 平台侧「发送记录」一条),需要手工在数据库执行(没有现成的菜单同步机制,详见该 SQL 文件注释)。
七、已知限制 / 后续 TODO
- 商户配置页(
system/config/sms|email)没有"测试发送"按钮——早期版本做过一版带测试发送的商户级独立配置(专用表方案里),回滚到复用通用配置表后没有带过来,如果要加,可以参考通用配置系统之外单独开一个/merchant/sender/test接口。 - 邮件独立发信域名(DKIM/SPF)、短信多服务商(腾讯云等)都还没做,现状只有阿里云短信 + SMTP 邮件,
ISenderService每种SenderType只有一个实现,SenderFactory.getService()按 type 取第一个匹配,不支持多服务商路由(明确的产品决定,不要在没有第二个服务商实现之前加路由复杂度)。 - 微信公众号模板消息/小程序订阅消息未接入,远期规划,复用同一套
SenderType/模板框架。
相关:[登录注册体系](验证码发送/校验是登录注册体系依赖的能力)