news 2026/6/18 14:51:22

SpringBoot+Vue3 企业消息通知中心设计:站内信+短信+邮件+WebSocket 多通道统一推送

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot+Vue3 企业消息通知中心设计:站内信+短信+邮件+WebSocket 多通道统一推送

SpringBoot+Vue3 企业消息通知中心设计:站内信+短信+邮件+WebSocket 多通道统一推送

🌐演示地址:http://ruoyioffice.com | 📦源码1·GitHub:ruoyi-office | 📦源码2·GitCode:ruoyi-office | 📦源码3·Gitee:ruoyi-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(多服务商)MailAccountDO(SMTP 账号)
消息/日志NotifyMessageDOSmsLogDOMailLogDO

这套抽象的价值:业务方永远只跟"模板编码 + 参数"打交道,至于内容长什么样、走哪个服务商、怎么异步、怎么记日志,全部对业务透明。

1.2 模板机制:内容与代码彻底分离

三大通道的模板都用同一套占位符机制——内容里用{key}标记变量,发送时传一个Map<String, Object>把变量填进去。站内信模板NotifyTemplateDO的核心字段:

字段说明
code模板编码(业务调用的唯一标识,如bpm_task_assigned
name模板名称
type模板类型(字典system_notify_template_type
content模板内容,含{key}占位符
params参数数组(JSON),用于发送前校验必填参数
status启用 / 禁用

举例:模板内容你收到了一条待办任务【{taskName}】,请及时处理,发送时传{"taskName": "请假审批"},渲染出你收到了一条待办任务【请假审批】,请及时处理改文案 = 改数据库里的模板,不用动代码、不用发版。

1.3 短信渠道:一套代码切换多家服务商

短信渠道SmsChannelDO把"服务商"抽象成可配置的渠道,code字段对应SmsChannelEnum枚举,支持主流服务商:

渠道编码服务商
ALIYUN阿里云
TENCENT腾讯云
HUAWEI华为云
QINIU七牛云
DEBUG_DING_TALK调试(钉钉,本地联调用)

每个渠道配signature(签名)、apiKeyapiSecretcallbackUrl。换服务商只需在后台新建一个渠道、把模板关联过去,零代码改动——这就是"渠道抽象"的威力。


二、系统设计:模块职责与设计决策

2.1 模块组成

通知中心位于系统管理 → 消息中心 / 通知公告目录下:

子模块页面功能面向角色
站内信模板管理 / 消息记录维护站内信模板、查看发送记录、手动发送管理员
我的站内信用户侧收件箱查看/标记已读、跳转业务详情全体用户
短信渠道 / 模板 / 日志配置服务商、维护短信模板、查发送日志管理员
邮件账号 / 模板 / 日志配置 SMTP 账号、维护邮件模板、查日志管理员
通知公告公告管理发布公司公告、规章制度,标记重要管理员

2.2 核心设计决策

决策点方案理由
统一抽象模板 + 渠道 + 日志三件套三通道同构,新增通道成本低
内容配置化{key}占位符 + 模板落库改文案不改代码、不发版
渠道可插拔SmsChannelEnum+SmsClient换服务商零代码改动
发送方式站内信同步落库,短信走 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 密钥,再在「短信模板」维护内容并关联渠道与服务商模板 ID(apiTemplateId),最后所有发送记录都进「短信日志」可查。

▲ 短信日志:逐行记录手机号、模板编码、短信渠道、渲染后内容、发送状态(成功/失败)、服务商回执状态与时间,用户投诉"没收到验证码"时一查便知

短信设计要点

  • 渠道与模板分离:模板关联channelId,换渠道改关联即可;模板params自动根据内容里的{key}生成。
  • 有序参数:部分服务商(如腾讯云)用数组下标而非 key 传参,buildTemplateParams把 Map 转成有序KeyValue数组兼容。
  • 禁用也记日志:模板或渠道被禁用时,不真正发送,但仍写一条日志(isSend=false),保留痕迹。

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 + params

@OverridepublicLongsendSingleNotify(LonguserId,IntegeruserType,StringtemplateCode,Map<String,Object>templateParams){// 1. 校验模板(缓存读取),模板禁用则直接返回不发送NotifyTemplateDOtemplate=validateNotifyTemplate(templateCode);if(Objects.equals(template.getStatus(),CommonStatusEnum.DISABLE.getStatus())){log.info("[sendSingleNotify][模版({})已关闭,无法发送]",templateCode);returnnull;}// 2. 校验必填参数validateTemplateParams(template,templateParams);// 3. 渲染内容({key} 占位符替换)Stringcontent=notifyTemplateService.formatNotifyTemplateContent(template.getContent(),templateParams);// 4. 落库(同时可触发 WebSocket 推送)returnnotifyMessageService.createNotifyMessage(userId,userType,template,content,templateParams);}

占位符渲染本身极简——直接复用 Hutool 的StrUtil.format,把{key}替换成 Map 里的值:

@OverridepublicStringformatNotifyTemplateContent(Stringcontent,Map<String,Object>params){returnStrUtil.format(content,params);}

5.2 短信发送:同步建日志 + 异步发 MQ

短信涉及外部网络 IO,不能阻塞业务主流程。sendSingleSms的设计很有代表性:同步部分只做校验和建日志,真正的发送通过 MQ 异步执行

@OverridepublicLongsendSingleSms(Stringmobile,LonguserId,IntegeruserType,StringtemplateCode,Map<String,Object>templateParams){// 1. 校验模板、渠道、手机号SmsTemplateDOtemplate=validateSmsTemplate(templateCode);SmsChannelDOsmsChannel=validateSmsChannel(template.getChannelId());mobile=validateMobile(mobile);// 2. 构建有序参数(兼容腾讯云等用数组下标的服务商)List<KeyValue<String,Object>>newTemplateParams=buildTemplateParams(template,templateParams);// 3. 模板/渠道都启用才真发;否则只记日志BooleanisSend=CommonStatusEnum.ENABLE.getStatus().equals(template.getStatus())&&CommonStatusEnum.ENABLE.getStatus().equals(smsChannel.getStatus());Stringcontent=smsTemplateService.formatSmsTemplateContent(template.getContent(),templateParams);LongsendLogId=smsLogService.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,Map<String,Object>templateParams){NotifyMessageDOmessage=newNotifyMessageDO().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改文案不改代码、不发版
渠道可插拔SmsChannelEnum+SmsClient换服务商零代码改动
统一发送 APIsendSingleNotify/Sms/Mail业务零感知,一行调用
参数校验前置按模板params校验必填杜绝发出空白/错误消息
异步发送短信走 RocketMQ +@Async外部 IO 不阻塞主流程
全程日志每次发送写 Log(含禁用场景)可排查、可统计、可审计
内容快照落库存渲染后内容模板变更不影响历史消息
实时推送落库 + WebSocket 双写红点秒亮,又不丢消息
模板缓存按 code 缓存读取高频发送不打数据库

九、快速体验

在线演示:http://ruoyioffice.com/web/(账号admin/admin123

操作路径:系统管理 → 站内信管理(模板/消息)、短信管理(渠道/模板/日志)、邮件管理、通知公告

推荐体验流程

  1. 进入「站内信模板」,新建一个模板,内容里写上{name}占位符;
  2. 点击"发送站内信",选择接收人、填入参数,发送;
  3. 切到右上角通知图标 / 「我的站内信」,查看刚收到的消息与未读角标;
  4. 进入「短信渠道」配置一个服务商(本地可用 DEBUG 钉钉渠道联调);
  5. 在「短信模板」维护模板并关联渠道,发送后到「短信日志」查看发送记录;
  6. 发布一条「通知公告」并标记为重要,观察全员公告效果。

源码仓库

仓库地址
GitHubgithub.com/yuqing2026/ruoyi-office
GitCodegitcode.com/zhouzhongyan/ruoyi-office
Giteegitee.com/yqzy1688/ruoyi-office

结语

消息通知中心的核心设计思想是:把"发消息"这件到处都在做的小事,抽象成"模板 + 渠道 + 日志"的统一三件套,让业务方只跟模板编码和参数打交道,把渲染、分发、异步、留痕、实时推送全部下沉到基础设施。这套"统一抽象 + 配置化 + 可插拔 + 异步留痕"的模式,是所有"横切关注点"(日志、文件、支付、消息)的通用解法——一旦抽象到位,新增一个通道、换一个服务商、改一句文案,都不再是伤筋动骨的大事。

你们项目的消息发送还在硬编码吗?换过短信服务商踩过哪些坑?欢迎在评论区聊聊。


常见问题(FAQ)

RuoYi Office 的消息通知中心是开源免费的吗?

是。基于 RuoYi-Vue-Pro / Yudao 架构,后端 Spring Boot 3.5 + 前端 Vue3,开源可商用。通知模块位于yudao-module-system,本地约 10 分钟即可启动。

支持哪些短信服务商?

内置阿里云、腾讯云、华为云、七牛云四家,以及本地联调用的"调试(钉钉)"渠道。换服务商只需在后台新建渠道并关联模板,无需改代码。

改短信/站内信文案需要改代码、重新发版吗?

不需要。所有文案都是数据库里的模板,内容用{key}占位符,发送时传参渲染。改文案 = 改后台模板,即时生效。

站内信怎么做到实时弹未读角标?

采用"落库 + WebSocket 双写":消息落库保证可查、离线可见,同时通过WebSocketMessageSender给在线用户推送,前端长连接收到后立即更新红点,无需刷新。

发送失败了怎么排查?

每次发送都会写日志(SmsLogDO/MailLogDO/ 站内信记录),包含接收人、渲染内容、发送状态、服务商回执。即使模板被禁用未真发,也会记一条isSend=false的日志,全程可追溯。


💡想要体验 RuoYi Office 的强大功能?

🌐在线演示:http://ruoyioffice.com/web/(账号 admin / admin123)

📦源码仓库:GitHub | GitCode | Gitee

💬技术咨询:添加微信17156169080,备注「RuoYi Office」

如果觉得不错,请给个 Star 支持一下!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/18 14:50:14

1688 API接口并非全免费?这些增值服务你需要知道(附python源码)

用户问的是1688 API接口并非全免费&#xff0c;哪些增值服务收费&#xff0c;并要附Python源码。之前对话中已经多次讲过基础API免费、资源包提QPS、增值接口收钱&#xff0c;这里需要聚焦澄清免费边界 明确列举收费/增值服务 给一个资源包/增值接口检测的Python示例&#xf…

作者头像 李华
网站建设 2026/6/18 14:48:54

Tkinter布局助手:告别复杂代码,用拖拽方式创建Python界面

Tkinter布局助手&#xff1a;告别复杂代码&#xff0c;用拖拽方式创建Python界面 【免费下载链接】tkinter-helper 为tkinter打造的可视化拖拽布局界面设计小工具 项目地址: https://gitcode.com/gh_mirrors/tk/tkinter-helper 你是否曾为Python GUI开发而头疼&#xff…

作者头像 李华
网站建设 2026/6/18 14:35:25

告别手动标注!Semi-Utils专业级照片批量处理工具终极指南

告别手动标注&#xff01;Semi-Utils专业级照片批量处理工具终极指南 【免费下载链接】semi-utils 一个批量添加相机机型和拍摄参数的工具&#xff0c;后续「可能」添加其他功能。 项目地址: https://gitcode.com/gh_mirrors/se/semi-utils 还在为数百张照片手动添加相机…

作者头像 李华
网站建设 2026/6/18 14:21:14

Windows系统文件stobject.dll丢失找不到问题解决

在使用电脑系统时经常会出现丢失找不到某些文件的情况&#xff0c;由于很多常用软件都是采用 Microsoft Visual Studio 编写的&#xff0c;所以这类软件的运行需要依赖微软Visual C运行库&#xff0c;比如像 QQ、迅雷、Adobe 软件等等&#xff0c;如果没有安装VC运行库或者安装…

作者头像 李华