Skip to content

站内信与消息通知

RuoYi Office 的消息能力分为两层:

说明典型入口
渠道底座各通道的模板、账号、发送记录站内信模板、NotifySendService
统一消息中心(编排层)按「消息定义(场景)」一次调用,自动多渠道派发、定时调度、统一监控MessageSendApi

新业务开发推荐走 MessageSendApi:业务代码只需传「场景编号 + 接收人 + 参数 + 业务单据 ID」,渠道、模板、跳转、定时由消息中心配置决定。站内信、短信、邮件仍分别在各自管理页维护模板,消息中心通过 template_code 引用,不重复维护两套文案。

管理端菜单

路径:系统管理 > 消息中心

消息中心
├─ 消息定义          ← 注册场景、绑定各渠道模板编码
├─ 跳转路由          ← 业务类型 → PC/App 详情页
├─ 消息实例          ← 每次发送请求(含定时待发送)
├─ 发送日志          ← 跨渠道统一监控
├─ 站内信管理        ← 模板 / 消息记录 / 我的站内信
├─ 短信管理
├─ 邮件管理
└─ 通知公告

本地实现位置

位置
站内信模板页面ruoyi-office-vben/apps/web-antd/src/views/system/notify/template
站内信消息页面views/system/notify/message
我的站内信views/system/notify/my
消息定义 / 路由 / 实例 / 日志views/system/msg/{scene,route,instance,log}
渠道发送服务NotifySendServiceSmsSendServiceMailSendService
业务对接 APIyudao-module-system-api/.../api/msg/MessageSendApi

能力链路

推荐:业务模块 → 消息中心 → 各渠道

历史:业务模块直连站内信

管理端:站内信模板与消息

站内信模板

模板页面支持新增、编辑、删除、批量删除、导出和测试发送。

操作接口权限标识
创建模板POST /system/notify-template/createsystem:notify-template:create
更新模板PUT /system/notify-template/updatesystem:notify-template:update
删除模板DELETE /system/notify-template/deletesystem:notify-template:delete
模板分页GET /system/notify-template/page页面查询使用
测试发送POST /system/notify-template/send-notifysystem:notify-template:send-notify

占位符统一为 {paramName}。若模板需要点击跳转业务详情,正文中可引用 {detailUrl}(由消息中心在派发时自动注入,见下文)。

站内信消息 / 我的站内信

操作接口说明
全量消息分页GET /system/notify-message/page管理员排查
我的分页GET /system/notify-message/my-page当前用户列表
标记已读PUT /system/notify-message/update-read单条或多条
全部已读PUT /system/notify-message/update-all-read清空未读

PC 端顶部铃铛轮询未读;用户可在「偏好设置 > Antd 拓展」开启「新消息闪烁提醒」。


业务系统对接指南

本节说明 OA、合同、项目等业务模块如何接入统一消息中心。

两种调用方式对比

维度历史方式(仅站内信)推荐方式(消息中心)
入口NotifyMessageSendApiMessageSendApi
业务传入templateCode + 用户 ID + 参数sceneCode + 接收人 + 参数 + bizType/bizId
多渠道需分别调短信/邮件 API场景配置一次,自动按渠道派发
定时发送业务自建 Job / 延时队列planSendTime,由 MsgScheduledSendJob 统一扫描
跳转业务自行拼 detailUrl配置「跳转路由」后自动注入 {detailUrl}
监控各渠道日志分散「消息实例 / 发送日志」统一查看

存量仅发站内信且逻辑稳定的代码可暂保留 NotifyMessageSendApi新功能默认用 MessageSendApi

业务侧只需关心什么

一次性在管理端(或初始化 SQL)完成配置后,业务代码每次触发只需 4 项:

传入项说明示例
sceneCode消息定义编号OA_MEETING_REMIND
receiverUserIds接收人用户 ID 列表[1, 2, 3]
params模板占位符 Map{ meetingTitle, startTime, ... }
bizType + bizId业务类型 + 单据 ID(跳转、取消、幂等)oa_meeting_room_booking + "123"

渠道列表、各渠道模板、PC/App 跳转路由均由消息定义 + 跳转路由配置,业务代码不必硬编码模板正文或 URL。

接入前准备(四步)

以「会议开始提醒」为例:

步骤做什么在哪里配示例
① 渠道模板文案与占位符站内信管理 > 模板(短信/邮件同理)code=OA_MEETING_REMIND,含 {startTime}{meetingTitle}
② 消息定义场景编号、默认渠道、接收人模式消息中心 > 消息定义code=OA_MEETING_REMIND,渠道=站内信,接收人=仅上游指定
③ 场景×渠道绑定原生模板编码消息定义 > 渠道模板配置站内信 → template_code=OA_MEETING_REMIND
④ 跳转路由业务类型对应详情页消息中心 > 跳转路由biz_type=oa_meeting_room_bookingpc_route=/oa/meetingroom/booking-info?id={bizId}

Maven 依赖与 RPC

1. server 模块引入 system-api(多数模块已有):

xml
<dependency>
    <groupId>cn.iocoder.boot</groupId>
    <artifactId>yudao-module-system-api</artifactId>
    <version>${revision}</version>
</dependency>

2. 微服务模式:在业务模块 RpcConfiguration 注册 Feign 客户端:

java
@EnableFeignClients(clients = {
        // ... 其他 Api
        MessageSendApi.class
})

单体 boot 模式无需额外配置,注入方式相同。

3. Service 注入

java
@Resource
private MessageSendApi messageSendApi;

API 说明

接口类:cn.iocoder.yudao.module.system.api.msg.MessageSendApi

方法说明返回值
send(reqDTO)发送(立即或定时)消息实例 ID
cancelByBiz(bizType, bizId)取消该单据所有待发送实例(幂等)true

MessageSendReqDTO 主要字段

字段必填说明
sceneCode消息场景编号
bizType建议业务类型;用于跳转与取消
bizId建议业务单据 ID(字符串)
receiverUserIds视场景管理员用户 ID 列表
receivers视场景完整接收人(含会员、外部手机/邮箱)
params建议模板占位符;消息中心会自动注入 detailUrl
channels覆盖场景默认渠道
planSendTime计划发送时间;空 = 立即发送

典型接入模式

立即发送(审批通过、状态变更)

java
MessageSendReqDTO req = new MessageSendReqDTO();
req.setSceneCode("OA_SEAL_APPROVED");
req.setBizType("oa_seal_apply");
req.setBizId(String.valueOf(apply.getId()));
req.setReceiverUserIds(List.of(apply.getApproverId()));
req.setParams(Map.of(
    "billCode", apply.getBillCode(),
    "applicantName", apply.getApplicantName()
));
messageSendApi.send(req).getCheckedData();

在 Service 末尾、BPM 监听 onProcessApproved 等时机调用。

定时发送(会议提醒、到期预警)

java
req.setPlanSendTime(meetingStartTime.minusMinutes(10));
messageSendApi.send(req).getCheckedData();

到点后由 XXL-Job MsgScheduledSendJob(需已启用)扫描派发。业务 Job 只负责扫业务数据并调用 send,不必再写独立「到点发消息」逻辑。

取消与重建(改期、撤回、驳回)

java
messageSendApi.cancelByBiz("oa_meeting_room_booking", String.valueOf(bookingId));
messageSendApi.send(newReq).getCheckedData(); // 需要时重新注册

改期场景建议先 cancel 再 send,避免重复定时实例。

定时 Job 批量发送

java
@XxlJob("contractExpireNotifyJob")
@TenantJob
public String execute() {
    for (ContractLedgerDO contract : expiringList) {
        MessageSendReqDTO req = new MessageSendReqDTO();
        req.setSceneCode("CONTRACT_EXPIRE_REMIND");
        req.setBizType("contract_ledger");
        req.setBizId(String.valueOf(contract.getId()));
        req.setReceiverUserIds(List.of(contract.getOwnerUserId()));
        req.setParams(buildParams(contract));
        messageSendApi.send(req).getCheckedData();
    }
    return "ok";
}

参考实现:OA 会议提醒

文件:yudao-module-oa-server/.../MeetingRoomBookingServiceImpl.java

java
private static final String MEETING_REMIND_SCENE_CODE = "OA_MEETING_REMIND";
private static final String MEETING_REMIND_BIZ_TYPE = "oa_meeting_room_booking";

private void registerMeetingReminder(MeetingRoomBookingDO booking) {
    try {
        // ... 计算 planSendTime、接收人、params
        messageSendApi.cancelByBiz(MEETING_REMIND_BIZ_TYPE, String.valueOf(booking.getId()));

        MessageSendReqDTO req = new MessageSendReqDTO();
        req.setSceneCode(MEETING_REMIND_SCENE_CODE);
        req.setBizType(MEETING_REMIND_BIZ_TYPE);
        req.setBizId(String.valueOf(booking.getId()));
        req.setReceiverUserIds(receiverUserIds);
        req.setParams(params);
        req.setPlanSendTime(planSendTime);
        messageSendApi.send(req).getCheckedData();
    } catch (Exception e) {
        log.error("[registerMeetingReminder] 失败 id={}", booking.getId(), e);
    }
}

private void cancelMeetingReminder(Long id) {
    messageSendApi.cancelByBiz(MEETING_REMIND_BIZ_TYPE, String.valueOf(id));
}

审批通过注册提醒;撤回/驳回/取消时调用 cancelMeetingReminder

占位符与跳转

约定
占位符{paramName},与站内信/短信/邮件模板一致
业务 paramskey 须与模板声明的参数名一致
{detailUrl}无需业务传入;配置跳转路由后由消息中心按渠道注入
站内信点击前端读取 templateParams.detailUrl 做站内路由跳转

站内信模板示例:

您有一场会议将于 {startTime} 开始。
会议主题:{meetingTitle}
点击查看:{detailUrl}

异常处理与排查

实践说明
不阻断主流程send / cancelByBiz 建议 try-catch,失败只记日志
统一监控管理端「消息实例」「发送日志」
渠道明细「站内信消息 / 短信日志 / 邮件记录」可对照 ref_biz_id
定时未发查实例是否「待发送」、plan_send_time 是否已到、Job 是否启用、业务侧是否因「会议已开始」等条件跳过注册

接入自检清单

  • [ ] yudao-module-system-api 依赖已引入
  • [ ] RpcConfiguration 已注册 MessageSendApi(cloud 模式)
  • [ ] 站内信(及需要的短信/邮件)模板已建,template_code 与消息定义一致
  • [ ] 消息定义已启用,渠道与接收人模式正确
  • [ ] 跳转路由 biz_type 与代码中 bizType 一致
  • [ ] 立即发送:发送日志有记录,用户收到消息
  • [ ] 定时发送:到点或手动触发 Job 后实例变为成功
  • [ ] 取消:cancelByBiz 后待发送实例变为已取消
  • [ ] 发送失败不影响主业务事务

历史方式:直连站内信(存量代码)

仅需站内信、且尚未迁移的场景,可继续调用 NotifyMessageSendApi

java
NotifySendSingleToUserReqDTO req = new NotifySendSingleToUserReqDTO();
req.setUserId(userId);
req.setTemplateCode("contract-expire-notify");
req.setTemplateParams(Map.of("contractName", name));
notifyMessageSendApi.sendSingleMessageToAdmin(req);

迁移到消息中心时:保留原模板 code → 新建消息定义并绑定该 template_code → 业务改调 MessageSendApi → 在发送日志验收。

二开建议

  1. 模板 code / 场景 code 作为稳定契约:上线后不要随意修改。
  2. 参数 key 语义清晰:如 meetingTitlebillCode,便于日志回溯。
  3. 跳转走跳转路由:不要在前端或业务里硬编码详情 URL。
  4. 站内信不替代短信/邮件:强触达场景在消息定义里配置多渠道。
  5. 新业务默认 MessageSendApi:多渠道、定时、统一监控一次到位。

排查清单

现象排查方向
用户没收到站内信模板 code、场景×渠道 template_code、用户 ID、发送日志状态
内容参数没替换模板变量名与 params key 是否一致
点击无法进详情跳转路由是否配置;模板是否含 {detailUrl}bizType/bizId 是否正确
定时消息未发送消息实例状态、plan_send_time、XXL-Job MsgScheduledSendJob、业务注册条件
改期后收到 duplicate改期前是否调用 cancelByBiz
消息已发送但仍未读用户是否打开「我的站内信」或调用全部已读
联系我们

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

微信咨询二维码

微信咨询

17156169080

添加时备注「RuoYi Office」

在线体验商业版