SpringBoot+Vue3 企业消息通知中心设计:站内信+短信+邮件+WebSocket 多通道统一推送 SpringBootVue3 企业消息通知中心设计站内信短信邮件WebSocket 多通道统一推送演示地址http://ruoyioffice.com | 源码1·GitHubruoyi-office | 源码2·GitCoderuoyi-office | 源码3·Giteeruoyi-office | 微信17156169080备注「RuoYi Office」“审批到你了”“验证码 6 位”“您有一条新公告”——企业系统里消息无处不在。但很多项目的消息发送是这样的登录发验证码的代码里写死阿里云 SDK审批通知的代码里又拼了一段站内信 SQL改个短信文案要改 Java 代码、重新发版想换个短信服务商更是牵一发动全身。消息散落、硬编码、不可配、发了没记录、用户收不到也查不到是绝大多数后台系统的通病。RuoYi Office 用模板 渠道 日志三件套统一抽象站内信、短信、邮件三大通道业务只管喊一句sendSingleNotify(userId, templateCode, params)剩下的渲染、发送、落库、实时推送全由通知中心兜底把硬编码发消息收敛成一个可配置、可复用、可追溯的通知引擎。▲ 消息通知中心架构全景业务侧统一调用发送 API → 模板渲染{key} 占位符→ 三通道分发站内信落库WebSocket / 短信走 MQ 异步 / 邮件 SMTP→ 发送日志全程留痕引言企业消息推送到底难在哪“不就是发条消息吗”——直到你维护一个有几十处发消息的系统才会明白它有多乱硬编码满天飞验证码短信的内容、审批通知的文案全写死在各个 Service 里。运营想把您好改成亲得提需求、改代码、走发版——一句文案改动要折腾一周。渠道绑死发短信的代码直接new了阿里云的 client。哪天阿里云涨价想换腾讯云得把全项目搜一遍AliyunSmsClient挨个改风险极高。发了查不到用户投诉我没收到验证码但系统里没有任何发送记录——发没发、发给谁、成功还是失败、服务商返回了啥一概不知根本没法排查。通道各自为政站内信一套代码、短信一套代码、邮件又一套代码参数格式、模板机制、调用方式全不一样。新人接手要学三套加个钉钉通知还得再造一套。实时性差站内信写进数据库了但用户的浏览器不知道必须刷新页面才看到红点。审批催办这类强时效消息体验很差。本文以 RuoYi Office 的yudao-module-system通知模块为例完整拆解其模板 渠道 日志统一抽象、templateCode触发、{key}占位符渲染、MQ 异步发送、WebSocket 实时推送的设计方案。所有结论均来自真实源码。一、业务设计模板 渠道 日志三件套1.1 核心理念把发消息抽象成统一三段式消息通知中心是什么它是一个把业务事件翻译成用户可感知消息的统一引擎。无论站内信、短信还是邮件都遵循同一套抽象业务事件 ──▶ ① 选模板(templateCode) ──▶ ② 填参数(templateParams) │ ▼ ③ 渲染内容({key} 占位符替换) │ ┌───────────────┬───────────┴───────────┐ ▼ ▼ ▼ 站内信(落库 短信(走 MQ 邮件(SMTP WebSocket) 异步发送) 发送) │ │ │ └───────────────┴───────────────────────┘ ▼ ④ 发送日志全程留痕三件套站内信短信邮件模板NotifyTemplateDOSmsTemplateDOMailTemplateDO渠道/账号内部无外部渠道SmsChannelDO多服务商MailAccountDOSMTP 账号消息/日志NotifyMessageDOSmsLogDOMailLogDO这套抽象的价值业务方永远只跟模板编码 参数打交道至于内容长什么样、走哪个服务商、怎么异步、怎么记日志全部对业务透明。1.2 模板机制内容与代码彻底分离三大通道的模板都用同一套占位符机制——内容里用{key}标记变量发送时传一个MapString, Object把变量填进去。站内信模板NotifyTemplateDO的核心字段字段说明code模板编码业务调用的唯一标识如bpm_task_assignedname模板名称type模板类型字典system_notify_template_typecontent模板内容含{key}占位符params参数数组JSON用于发送前校验必填参数status启用 / 禁用举例模板内容你收到了一条待办任务【{taskName}】请及时处理发送时传{taskName: 请假审批}渲染出你收到了一条待办任务【请假审批】请及时处理。改文案 改数据库里的模板不用动代码、不用发版。1.3 短信渠道一套代码切换多家服务商短信渠道SmsChannelDO把服务商抽象成可配置的渠道code字段对应SmsChannelEnum枚举支持主流服务商渠道编码服务商ALIYUN阿里云TENCENT腾讯云HUAWEI华为云QINIU七牛云DEBUG_DING_TALK调试钉钉本地联调用每个渠道配signature签名、apiKey、apiSecret、callbackUrl。换服务商只需在后台新建一个渠道、把模板关联过去零代码改动——这就是渠道抽象的威力。二、系统设计模块职责与设计决策2.1 模块组成通知中心位于系统管理 → 消息中心 / 通知公告目录下子模块页面功能面向角色站内信模板管理 / 消息记录维护站内信模板、查看发送记录、手动发送管理员我的站内信用户侧收件箱查看/标记已读、跳转业务详情全体用户短信渠道 / 模板 / 日志配置服务商、维护短信模板、查发送日志管理员邮件账号 / 模板 / 日志配置 SMTP 账号、维护邮件模板、查日志管理员通知公告公告管理发布公司公告、规章制度标记重要管理员2.2 核心设计决策决策点方案理由统一抽象模板 渠道 日志三件套三通道同构新增通道成本低内容配置化{key}占位符 模板落库改文案不改代码、不发版渠道可插拔SmsChannelEnumSmsClient换服务商零代码改动发送方式站内信同步落库短信走 MQ 异步短信有外部 IO异步不阻塞主流程全程留痕每次发送写 Log成功/失败/返回可排查、可统计、可审计实时触达站内信落库 WebSocket 推送既能查历史又能实时弹角标模板缓存模板按 code 走缓存读取高频发送不打数据库三、PC 端功能实现3.1 站内信模板与发送站内信模板管理页维护模板编码、名称、类型、内容含{key}占位符和参数列表。管理员可在模板页直接发送测试选择接收人、填入参数即可。▲ 站内信模板管理逐行展示模板编码如 bpm_task_assigned、名称、类型、内容预览、状态右侧操作含发送站内信可选接收人并填充占位符参数站内信设计要点模板缓存读取getNotifyTemplateByCodeFromCache(code)从缓存取模板避免高频发送频繁查库。发送前参数校验validateTemplateParams遍历模板声明的params缺参数直接抛NOTIFY_SEND_TEMPLATE_PARAM_MISS杜绝发出空白消息。内容快照落库站内信落库时把渲染后的内容存进NotifyMessageDO.templateContent即使日后模板改了历史消息内容不变。3.2 短信渠道、模板与日志短信模块三页联动先在「短信渠道」配置服务商阿里云/腾讯云…和 API 密钥再在「短信模板」维护内容并关联渠道与服务商模板 IDapiTemplateId最后所有发送记录都进「短信日志」可查。▲ 短信日志逐行记录手机号、模板编码、短信渠道、渲染后内容、发送状态成功/失败、服务商回执状态与时间用户投诉没收到验证码时一查便知短信设计要点渠道与模板分离模板关联channelId换渠道改关联即可模板params自动根据内容里的{key}生成。有序参数部分服务商如腾讯云用数组下标而非 key 传参buildTemplateParams把 Map 转成有序KeyValue数组兼容。禁用也记日志模板或渠道被禁用时不真正发送但仍写一条日志isSendfalse保留痕迹。3.3 通知公告通知公告面向全员发布——公司动态、规章制度、行业资讯等支持标记重要通知配合阅读记录NoticeReadDO统计谁已读。公告设计要点类型字典化type关联字典system_notice_type通知公告/公司动态/规章制度等前端统一渲染标签。重要标记isImportant为重要公告前端可置顶或强提醒。富文本内容content支持富文本编辑器图文混排。四、实时推送WebSocket 让红点秒亮4.1 统一发送器接口站内信落库只解决了可查要做到实时弹角标还得靠 WebSocket。框架抽象了统一发送器WebSocketMessageSender支持按用户、按用户类型、按 Session 三种粒度推送publicinterfaceWebSocketMessageSender{/** 发送消息给指定用户 */voidsend(IntegeruserType,LonguserId,StringmessageType,StringmessageContent);/** 发送消息给指定用户类型广播 */voidsend(IntegeruserType,StringmessageType,StringmessageContent);/** 发送消息给指定 Session */voidsend(StringsessionId,StringmessageType,StringmessageContent);/** 便捷方法对象自动转 JSON 后发送 */defaultvoidsendObject(IntegeruserType,LonguserId,StringmessageType,ObjectmessageContent){send(userType,userId,messageType,JsonUtils.toJsonString(messageContent));}}4.2 落库 推送双写发站内信时先落库保证可查、离线也能看再通过sendObject给在线用户推一条 WebSocket 消息让前端红点立刻 1。落库是可靠性WebSocket 是实时性双写兼得。前端建立 WebSocket 长连接后收到消息即更新未读角标无需刷新页面。五、后端核心实现5.1 站内信统一发送核心NotifySendServiceImpl.sendSingleNotify是站内信的统一入口——校验模板、校验参数、渲染内容、落库一气呵成。业务方只需提供userId templateCode paramsOverridepublicLongsendSingleNotify(LonguserId,IntegeruserType,StringtemplateCode,MapString,ObjecttemplateParams){// 1. 校验模板缓存读取模板禁用则直接返回不发送NotifyTemplateDOtemplatevalidateNotifyTemplate(templateCode);if(Objects.equals(template.getStatus(),CommonStatusEnum.DISABLE.getStatus())){log.info([sendSingleNotify][模版({})已关闭无法发送],templateCode);returnnull;}// 2. 校验必填参数validateTemplateParams(template,templateParams);// 3. 渲染内容{key} 占位符替换StringcontentnotifyTemplateService.formatNotifyTemplateContent(template.getContent(),templateParams);// 4. 落库同时可触发 WebSocket 推送returnnotifyMessageService.createNotifyMessage(userId,userType,template,content,templateParams);}占位符渲染本身极简——直接复用 Hutool 的StrUtil.format把{key}替换成 Map 里的值OverridepublicStringformatNotifyTemplateContent(Stringcontent,MapString,Objectparams){returnStrUtil.format(content,params);}5.2 短信发送同步建日志 异步发 MQ短信涉及外部网络 IO不能阻塞业务主流程。sendSingleSms的设计很有代表性同步部分只做校验和建日志真正的发送通过 MQ 异步执行OverridepublicLongsendSingleSms(Stringmobile,LonguserId,IntegeruserType,StringtemplateCode,MapString,ObjecttemplateParams){// 1. 校验模板、渠道、手机号SmsTemplateDOtemplatevalidateSmsTemplate(templateCode);SmsChannelDOsmsChannelvalidateSmsChannel(template.getChannelId());mobilevalidateMobile(mobile);// 2. 构建有序参数兼容腾讯云等用数组下标的服务商ListKeyValueString,ObjectnewTemplateParamsbuildTemplateParams(template,templateParams);// 3. 模板/渠道都启用才真发否则只记日志BooleanisSendCommonStatusEnum.ENABLE.getStatus().equals(template.getStatus())CommonStatusEnum.ENABLE.getStatus().equals(smsChannel.getStatus());StringcontentsmsTemplateService.formatSmsTemplateContent(template.getContent(),templateParams);LongsendLogIdsmsLogService.createSmsLog(mobile,userId,userType,isSend,template,content,templateParams);// 4. 发 MQ 消息异步真正发送短信if(isSend){smsProducer.sendSmsSendMessage(sendLogId,mobile,template.getChannelId(),template.getApiTemplateId(),newTemplateParams);}returnsendLogId;// 返回日志 ID便于追踪}设计巧思先建日志拿到sendLogId再把它随 MQ 消息一起发出去消费端真正发完后用这个 ID 回填发送结果与服务商回执形成建日志 → 异步发 → 回填结果的完整闭环。5.3 站内信落库内容快照createNotifyMessage落库时把模板信息和渲染后的内容一并冗余进消息记录这样历史消息不受模板后续修改影响OverridepublicLongcreateNotifyMessage(LonguserId,IntegeruserType,NotifyTemplateDOtemplate,StringtemplateContent,MapString,ObjecttemplateParams){NotifyMessageDOmessagenewNotifyMessageDO().setUserId(userId).setUserType(userType).setTemplateId(template.getId()).setTemplateCode(template.getCode()).setTemplateType(template.getType()).setTemplateNickname(template.getNickname()).setTemplateContent(templateContent)// 渲染后内容快照.setTemplateParams(templateParams).setReadStatus(false);// 默认未读notifyMessageMapper.insert(message);returnmessage.getId();}六、RuoYi Office 通知中心创新设计6.1 三通道同构新增通道成本极低站内信、短信、邮件都遵循模板 渠道 日志 统一发送 Service的同一套骨架。要新增钉钉/企业微信通知照着这套骨架加一组类即可业务调用方式完全一致无需重新设计。6.2 业务零感知的统一 API业务方发消息只写一行sendSingleNotifyToAdmin(userId, templateCode, params)完全不关心模板内容、服务商、异步、日志。业务与消息基础设施彻底解耦这是大型系统可维护性的关键。6.3 异步 日志外部 IO 不拖垮主流程短信、邮件这类有外部网络调用的通道发送走 MQ 异步主流程拿到日志 ID 立即返回。即使服务商抖动、超时也不会阻塞用户的登录、审批等核心操作且每次发送都有日志可查。6.4 落库 WebSocket 双写站内信既写库可靠、可查、离线可见又推 WebSocket实时弹角标。鱼和熊掌兼得既不丢消息又能秒级触达在线用户。七、数据结构7.1 站内信system_notify_template/system_notify_message表关键字段说明模板code / name / type / content / params / status模板编码、内容、占位符参数、状态消息user_id / user_type接收用户与类型管理端/会员端消息template_code / template_content冗余模板编码与渲染后内容快照消息template_params(JSON)渲染参数消息read_status / read_time是否已读 / 阅读时间7.2 短信system_sms_channel/system_sms_template/system_sms_log表关键字段说明渠道code / signature / api_key / api_secret服务商编码、签名、密钥模板code / content / params(JSON)模板编码、内容、参数模板channel_id / api_template_id关联渠道、服务商侧模板 ID日志mobile / content / send_status / receive_status手机号、内容、发送/回执状态7.3 邮件system_mail_account/system_mail_template/system_mail_log与通知公告system_notice表关键字段说明邮件账号mail / username / password / host / portSMTP 发件账号邮件模板code / title / content / params模板编码、标题、内容通知公告title / type / content / status / is_important标题、类型、内容、重要标记设计要点站内信、短信模板都把渲染后内容或有序参数冗余落库配合发送日志实现发了什么、发给谁、成没成功全程可追溯。八、技术亮点总结设计要点实现方式价值统一三件套抽象模板 渠道 日志三通道同构新增通道成本低内容配置化{key}占位符 StrUtil.format改文案不改代码、不发版渠道可插拔SmsChannelEnumSmsClient换服务商零代码改动统一发送 APIsendSingleNotify/Sms/Mail业务零感知一行调用参数校验前置按模板params校验必填杜绝发出空白/错误消息异步发送短信走 RocketMQ Async外部 IO 不阻塞主流程全程日志每次发送写 Log含禁用场景可排查、可统计、可审计内容快照落库存渲染后内容模板变更不影响历史消息实时推送落库 WebSocket 双写红点秒亮又不丢消息模板缓存按 code 缓存读取高频发送不打数据库九、快速体验在线演示http://ruoyioffice.com/web/账号admin/admin123操作路径系统管理 → 站内信管理模板/消息、短信管理渠道/模板/日志、邮件管理、通知公告推荐体验流程进入「站内信模板」新建一个模板内容里写上{name}占位符点击发送站内信选择接收人、填入参数发送切到右上角通知图标 / 「我的站内信」查看刚收到的消息与未读角标进入「短信渠道」配置一个服务商本地可用 DEBUG 钉钉渠道联调在「短信模板」维护模板并关联渠道发送后到「短信日志」查看发送记录发布一条「通知公告」并标记为重要观察全员公告效果。源码仓库仓库地址GitHubgithub.com/yuqing2026/ruoyi-officeGitCodegitcode.com/zhouzhongyan/ruoyi-officeGiteegitee.com/yqzy1688/ruoyi-office结语消息通知中心的核心设计思想是把发消息这件到处都在做的小事抽象成模板 渠道 日志的统一三件套让业务方只跟模板编码和参数打交道把渲染、分发、异步、留痕、实时推送全部下沉到基础设施。这套统一抽象 配置化 可插拔 异步留痕的模式是所有横切关注点日志、文件、支付、消息的通用解法——一旦抽象到位新增一个通道、换一个服务商、改一句文案都不再是伤筋动骨的大事。你们项目的消息发送还在硬编码吗换过短信服务商踩过哪些坑欢迎在评论区聊聊。常见问题FAQRuoYi Office 的消息通知中心是开源免费的吗是。基于 RuoYi-Vue-Pro / Yudao 架构后端 Spring Boot 3.5 前端 Vue3开源可商用。通知模块位于yudao-module-system本地约 10 分钟即可启动。支持哪些短信服务商内置阿里云、腾讯云、华为云、七牛云四家以及本地联调用的调试钉钉渠道。换服务商只需在后台新建渠道并关联模板无需改代码。改短信/站内信文案需要改代码、重新发版吗不需要。所有文案都是数据库里的模板内容用{key}占位符发送时传参渲染。改文案 改后台模板即时生效。站内信怎么做到实时弹未读角标采用落库 WebSocket 双写消息落库保证可查、离线可见同时通过WebSocketMessageSender给在线用户推送前端长连接收到后立即更新红点无需刷新。发送失败了怎么排查每次发送都会写日志SmsLogDO/MailLogDO/ 站内信记录包含接收人、渲染内容、发送状态、服务商回执。即使模板被禁用未真发也会记一条isSendfalse的日志全程可追溯。想要体验 RuoYi Office 的强大功能在线演示http://ruoyioffice.com/web/账号 admin / admin123源码仓库GitHub | GitCode | Gitee技术咨询添加微信17156169080备注「RuoYi Office」⭐如果觉得不错请给个 Star 支持一下