Skip to content

短信配置 ​

这篇解决什么 ​

业务模块要发短信时,不要自己接各家 SDK。先在后台配好渠道和模板,再调 SmsSendApi / SmsCodeApi。读完能改渠道账号格式、模板占位符、异步发送链路、验证码限流,以及平台回调。

默认端口 48080,管理端前缀 /admin-api。菜单在系统管理 → 消息中心 → 短信管理。PC 开发页 http://127.0.0.1:5800。

示意图:后台维护渠道和模板;业务只调发送 API。真正打短信平台在异步线程里完成。

名称说明仓库路径
短信渠道平台编码、签名、账号密钥、回调 URLruoyi-office/yudao-module-system/.../dal/dataobject/sms/SmsChannelDO.java
短信模板code、内容 {var}、绑定渠道.../dal/dataobject/sms/SmsTemplateDO.java
短信日志每次发送的手机号、渲染结果、收发状态.../dal/dataobject/sms/SmsLogDO.java
验证码场景、次数、是否已用.../dal/dataobject/sms/SmsCodeDO.java
发送 API给管理员 / 会员各一条ruoyi-office/yudao-module-system-api/.../api/sms/SmsSendApi.java
验证码 API发码、使用、只校验.../api/sms/SmsCodeApi.java
后台页渠道、模板、日志三张列表ruoyi-office-vben/apps/web-antd/src/views/system/sms/
前端 API/system/sms-channel、sms-template、sms-logruoyi-office-vben/apps/web-antd/src/api/system/sms/

官方菜单 id:1093 短信管理,1094 短信渠道,1100 短信模板,1107 短信日志。路由是 /system/messages/sms/sms-channel、sms-template、sms-log。

短信渠道

本系统截图:系统管理 → 消息中心 → 短信管理 → 短信渠道。

短信模板

本系统截图:短信模板列表。行上有编辑、测试、删除。编码如 admin-sms-login 要和场景枚举对齐。

短信日志

本系统截图:短信日志。发送成功后,接收状态常停在「等待结果」,直到平台回调。

生产验证码和短信费用

生产必须打开行为验证码,见 验证码。发短信按条计费,模板页「测试」和登录发码都会打真实平台。调试用「调试(钉钉)」渠道,不要拿正式阿里云 / 腾讯云账号对着自己狂点。

表结构 ​

四张表都带 @TenantIgnore,库里也没有 tenant_id。渠道、模板、日志、验证码全实例共用。

按本仓库生成的 ER:模板必须挂一个渠道;日志同时冗余渠道和模板快照。验证码单独一张表。

名称说明仓库路径
system_sms_channel@TableName("system_sms_channel")。code 是 SmsChannelEnum.../dal/dataobject/sms/SmsChannelDO.java
system_sms_template@TableName("system_sms_template")。code 全局唯一.../dal/dataobject/sms/SmsTemplateDO.java
system_sms_log@TableName("system_sms_log")。发送、接收两套状态.../dal/dataobject/sms/SmsLogDO.java
system_sms_code@TableName("system_sms_code")。按手机号查最近一条.../dal/dataobject/sms/SmsCodeDO.java

SmsTemplateDO.params 不是手填。保存时用正则 \{(.*?)\} 扫 content,写入 JSON 数组。发送时按这个列表校验,缺 key 抛 SMS_SEND_MOBILE_TEMPLATE_PARAM_MISS。

密钥出现在列表里

渠道列表直接展示 apiKey / apiSecret。库备份、导出、越权打开渠道页都能看到。渠道权限只给运维,密钥不要写进文档或提交进仓库。

实现原理 ​

其它模块只依赖 yudao-module-system-api,注入 SmsSendApi。单体里 SmsSendApiImpl 就是本地 Bean;微服务里走 Feign,服务名 system-server。

发送先同步落日志,再异步调平台。接口马上返回日志 id,不堵业务线程。

示意图:SmsProducer 发的是 Spring 事件,不是消息中间件。SmsSendConsumer 上的 @Async 才把 HTTP 调用挪到别的线程。

名称说明仓库路径
管理端测试POST /system/sms-template/send-sms.../controller/admin/sms/SmsTemplateController.java
RPC 发送/rpc-api/system/sms/send/send-single-admin、send-single-member.../api/sms/SmsSendApi.java
同步编排校验模板 / 渠道 / 手机号 / 参数.../service/sms/SmsSendServiceImpl.java
异步投递applicationContext.publishEvent.../mq/producer/sms/SmsProducer.java
真正发送@EventListener + @Async.../mq/consumer/sms/SmsSendConsumer.java
模板缓存Redis sms_template:{code}RedisKeyConstants.SMS_TEMPLATE
统一消息短信渠场景绑定后仍进 SmsSendService.../service/msg/channel/SmsChannelAdapter.java

mobile 为空时,管理员按 userId 读 system_users.mobile,会员走 MemberService#getMemberUserMobile。两边都没有手机号,抛 SMS_SEND_MOBILE_NOT_EXISTS。

腾讯云这类平台按数组下标填参,不认 {name}。buildTemplateParams 按模板 params 的顺序排成 List<KeyValue>,再交给客户端。

模板或渠道关掉也不会报错

任一 status 不是启用时,只写日志,状态 IGNORE = 30,不发事件。平台故障时改模板的渠道编号和 API 模板编号即可,不用改调用方、不用重启。

短信渠道 ​

菜单:系统管理 → 消息中心 → 短信管理 → 短信渠道。接口 @RequestMapping("system/sms-channel")。

新增短信渠道

本系统截图:新增短信渠道。渠道编码来自字典 system_sms_channel_code。

名称说明仓库路径
列表 / 新增GET /page,POST /createSmsChannelController
精简列表模板下拉用 GET /simple-list同上
权限system:sms-channel:create 等同上
请求体signature / code / apiKey / apiSecret / callbackUrl.../vo/channel/SmsChannelSaveReqVO.java
枚举DEBUG_DING_TALK / ALIYUN / TENCENT / HUAWEI / QINIU.../framework/sms/core/enums/SmsChannelEnum.java

本地没有短信账号时,选「调试(钉钉)」:apiKey 填机器人 Webhook 的 access_token,apiSecret 填加签密钥。机器人接入见 钉钉开放平台。这个客户端不会真发短信,只往群里推一条模拟文本。

有模板挂着的渠道删不掉,错误码 SMS_CHANNEL_HAS_CHILDREN。

保存渠道后,SmsChannelServiceImpl#getSmsClient 会 createOrUpdateSmsClient。改密钥立刻生效,不用重启。

短信模板 ​

菜单:短信模板。接口 @RequestMapping("/system/sms-template")。

新增短信模板

本系统截图:新增短信模板。模板编码是 API 识别号,短信 API 的模板编号是平台侧审核通过的 ID。

名称说明仓库路径
短信类型1 验证码,2 通知,3 营销SmsTemplateTypeEnum
模板编码API 只认这个 code,不是主键SmsTemplateDO.code
短信渠道下拉来自渠道精简列表channelId → SmsChannelDO.id
模板内容占位符写成 {code}、{name}content
API 模板编号平台审核通过后的模板 IDapiTemplateId
开启状态停用后只记日志status

保存时会调 SmsClient#getSmsTemplate,确认平台侧模板存在且审核通过。调试(钉钉)客户端直接返回审核成功,方便联调。正式渠道审核中或被拒,分别抛 SMS_TEMPLATE_API_AUDIT_CHECKING、SMS_TEMPLATE_API_AUDIT_FAIL。

改渠道只要改 channelId 和 apiTemplateId。channelCode 是冗余字段,保存时从渠道带过来。

测试发送 ​

行上「测试」走 POST /system/sms-template/send-sms,权限 system:sms-template:send-sms。内部调 sendSingleSmsToAdmin。

测试短信

本系统截图:测试发送。模板内容只读,占位符会拆成独立输入框。

正式渠道会把短信打到填写的手机号。钉钉渠道只进机器人群。

短信日志 ​

菜单:短信日志。接口 @RequestMapping("/system/sms-log")。

名称说明仓库路径
列表 / 导出GET /page,GET /export-excelSmsLogController
发送状态0 初始化,10 成功,20 失败,30 忽略SmsSendStatusEnum
接收状态0 初始化,10 成功,20 失败SmsReceiveStatusEnum
平台回执apiSendCode / apiRequestId / apiSerialNoSmsLogDO

接收状态一直停在初始化,是因为平台回不到本系统。回调入口 @PermitAll、@TenantIgnore:

名称说明仓库路径
阿里云POST /system/sms/callback/aliyunSmsCallbackController#receiveAliyunSmsStatus
腾讯云POST /system/sms/callback/tencent#receiveTencentSmsStatus
华为云POST /system/sms/callback/huawei#receiveHuaweiSmsStatus
七牛云POST /system/sms/callback/qiniu#receiveQiniuSmsStatus

公网地址形如 https://你的域名/admin-api/system/sms/callback/aliyun。华为云还可把同一地址填进渠道的「短信发送回调 URL」。

短信发送 ​

业务模块注入 SmsSendApi,不要直接 new 客户端。

java
@Resource
private SmsSendApi smsSendApi;

smsSendApi.sendSingleSmsToAdmin(new SmsSendSingleToUserReqDTO()
        .setUserId(userId)
        .setTemplateCode("bpm_task_assigned")
        .setTemplateParams(Map.of("name", nickname)));
名称说明仓库路径
给管理员mobile 空则按 userId 查SmsSendApi#sendSingleSmsToAdmin
给会员同上,查会员手机号#sendSingleSmsToMember
请求体userId / mobile / templateCode / templateParams.../api/sms/dto/send/SmsSendSingleToUserReqDTO.java
返回短信日志编号CommonResult<Long>

工作流待办、审批结果这类通知,模板编码自己起名(如 bpm_task_assigned),和验证码场景枚举无关。消息中心绑定短信渠后,也会进同一套 SmsSendService。

验证码发送 ​

登录、注册、改密不要自己生成数字再调 SmsSendApi。用 SmsCodeApi,它会写 system_sms_code、按场景限流,再以 {code} 发出。

名称说明仓库路径
发码POST /rpc-api/system/oauth2/sms/code/sendSmsCodeApi#sendSmsCode
使用PUT .../use,校验后把 used 置真#useSmsCode
只校验GET .../validate,不消耗#validateSmsCode
管理端发码POST /system/auth/send-sms-codeAuthController
会员发码POST /member/auth/send-sms-codeAppAuthController

场景号在 SmsSceneEnum。templateCode 必须和后台模板编码一致,否则发送阶段找不到模板。

名称说明仓库路径
1会员登录,模板 user-sms-loginSmsSceneEnum.MEMBER_LOGIN
2会员改手机,user-update-mobileMEMBER_UPDATE_MOBILE
3会员改密,user-update-passwordMEMBER_UPDATE_PASSWORD
4会员忘记密码,user-reset-passwordMEMBER_RESET_PASSWORD
21管理员登录,admin-sms-loginADMIN_MEMBER_LOGIN
22管理员注册,admin-sms-registerADMIN_MEMBER_REGISTER
23管理员忘记密码,admin-reset-passwordADMIN_MEMBER_RESET_PASSWORD

管理员发码前会先查 system_users 有没有这个手机号,没有直接失败,不会顺手开户。会员短信登录可以 createUserIfAbsent。细节见 用户体系。

限流写在 yudao.sms-code:

yaml
yudao:
  sms-code:
    expire-times: 10m
    send-frequency: 1m
    send-maximum-quantity-per-day: 10
    begin-code: 9999
    end-code: 9999
名称说明仓库路径
expire-times有效期,默认 10 分钟SmsCodeProperties
send-frequency同一手机最短间隔,默认 1 分钟同上
send-maximum-quantity-per-day当天上限,默认 10 条同上
begin-code / end-code随机区间。两端都是 9999 时验证码恒为 9999application.yaml

超频抛 SMS_CODE_SEND_TOO_FAST,超日限抛 SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY。过期、已用、找不到分别是 SMS_CODE_EXPIRED、SMS_CODE_USED、SMS_CODE_NOT_FOUND。

生产不要留下 9999

begin-code 和 end-code 都配成 9999 是为了本地好测。正式环境改成四位或六位区间,例如 100000~999999。否则任何人猜 9999 就能过短信登录。

新增场景时:在 SmsSceneEnum 加一项,后台建同名模板,调用方传新的 scene。不要复用别人的模板编码。

短信客户端 ​

framework/sms 封装各平台 HTTP。业务层只拿 SmsClient。

名称说明仓库路径
接口sendSms / parseSmsReceiveStatus / getSmsTemplate.../framework/sms/core/client/SmsClient.java
工厂按渠道编码 new 实现类,按渠道 id 缓存.../client/impl/SmsClientFactoryImpl.java
阿里云dysmsapi.aliyuncs.com,SignName 取渠道签名AliyunSmsClient
腾讯云sms.tencentcloudapi.com,地域 ap-guangzhouTencentSmsClient
华为云华北四接入地址,账号要带通道号HuaweiSmsClient
七牛云sms.qiniuapi.comQiniuSmsClient
调试钉钉Webhook 模拟发送DebugDingTalkSmsClient

接新平台:写一个 AbstractSmsClient 子类,在 SmsClientFactoryImpl#createSmsClient 的 switch 里加分支,并在 SmsChannelEnum、字典 system_sms_channel_code 补编码。

建议至少备两个正式渠道。验证码渠道挂了,把对应模板改到另一条渠道即可。

各平台账号 ​

阿里云:apiKey / apiSecret 用 AccessKey。签名填渠道的「短信签名」,必须和控台已审签名一致。回执在 短信服务 · 通用设置 配到 /system/sms/callback/aliyun。

腾讯云:apiSecret 填 SecretKey。apiKey 必须是 secretId sdkAppId,中间一个空格。例如 SecretId 为 A、SDKAppID 为 1400xxxx,则账号填 A 1400xxxx。密钥在 API 密钥,应用编号在 应用管理。回执在 基础配置 配到 /system/sms/callback/tencent。

华为云:apiKey 必须是 accessKeyId sender,sender 是短信通道号。密钥在 访问密钥。回执可填渠道表单的回调 URL,对应 /system/sms/callback/huawei。

七牛云:账号密钥在 密钥管理。回执在 云短信设置 配到 /system/sms/callback/qiniu。

配置与操作 ​

管理员先在 消息中心 → 短信渠道 选厂商、填密钥,再在模板里写 code(如 bpm_*)并绑渠道。保存后 SmsSendApi 按编码找模板。

回执 URL 填到云厂商控制台,对应 /system/sms/callback/{channel}。调试钉钉走同一套渠道,见 Webhook。开启见 系统功能设计。截图见上文渠道和模板。

相关篇 ​

联系我们

获取报价、演示和二开方案

微信咨询二维码

微信咨询

17156169080

添加时备注「RuoYi Office」

在线体验商业版