企业 OA 即时通讯 IM:从审批协同、消息触达到业务上下文闭环的架构设计

🌐 文档地址http://ruoyioffice.com | 📦 源码1https://gitcode.com/zhouzhongyan/ruoyi-office-vben.git | 📦 源码2https://gitcode.com/zhouzhongyan/ruoyi-office.git | 📦 源码3https://github.com/yuqing2026/ruoyi-office.git

很多企业上线 OA 后,表单、流程、通知都做了,却还是逃不开一个现实:审批卡住了,最后靠外部群里 @ 人;材料缺了,审批人在外部聊天里追问;事情办完了,真正的沟通过程却沉在群消息里,回到 OA 只能看到一个"通过"或"驳回"。这说明 OA 的核心不是"提交表单",而是让跨部门协作更快闭环。内置 IM 的价值,也不只是聊天,而是给 OA 增加一层实时通讯与业务上下文连接能力。
oa-im-collaboration-flow.png

▲ OA 内置 IM 协同闭环:业务单据、审批待办、系统通知、工作台入口和即时通讯共用统一用户、租户、权限与上下文,形成从"发起"到"沟通"再到"处理"的完整链路


引言:OA 真正要解决的不是表单,而是协作闭环

很多人理解 OA,会先想到这些功能:

  • 请假、出差、报销、用印、公文等表单。
  • 发起流程、审批节点、抄送节点、流程归档。
  • 通知公告、待办提醒、站内信、工作台。
  • 权限、组织架构、角色、数据范围。

这些都没错,但它们只是 OA 的"静态骨架"。企业真实运转时,更多问题发生在流程节点之间:

场景 表单系统能解决什么 没有实时协同会发生什么
审批人看不懂申请原因 展示表单字段和附件 只能驳回或外部私聊追问
发起人缺材料 提交流程、等待审批 审批人催材料,材料发在外部群里
多部门并行审批 BPM 能分派任务 部门之间沟通结论不回流系统
领导临时变更意见 流程能记录审批动作 口头/群聊意见难以追溯
员工只看工作台 有待办、通知、公告 不知道谁正在处理、该找谁沟通

所以,企业 OA 的关键命题不是"能不能把表单提交上去",而是:

一件事从发起、沟通、补充、审批、通知到归档,能不能在同一个系统里被触达、被理解、被追溯。

这就是内置 IM 的位置。它不是 OA 旁边的一个聊天小工具,而是 OA 协同链路里的实时通讯层。


一、业务场景:为什么外部 IM 很难承接 OA 协作?

1.1 外部 IM 看似方便,实际容易形成"协作断点"

企业常见做法是:OA 里发流程,外部聊天群里催审批。

短期看很方便,因为员工已经在用外部 IM;长期看,问题会越来越明显:

问题 外部 IM 的表现 对 OA 的影响
链接断裂 群里发一个流程链接,过几天就被消息淹没 业务单据与沟通记录脱节
权限不一致 群成员不等于 OA 权限成员 有权限的人收不到,没权限的人可能看到
上下文丢失 只看到一句"帮我看下这个单" 审批人需要重新打开系统理解背景
追溯困难 沟通在外部群、审批在 OA 审计时只能看到审批动作,看不到协商过程
租户边界弱 外部群不天然理解租户、部门、数据权限 SaaS 多租户场景更难治理

尤其是流程审批类业务,“为什么通过”“为什么驳回”“谁补充了材料”"谁确认了口径"往往和审批结果一样重要。如果沟通全部留在外部 IM,OA 里只剩结果,管理链路就是不完整的。

1.2 内置 IM 的真正价值:让沟通附着在业务上下文上

内置 IM 不是为了替代所有聊天软件,而是解决 OA 内部协作的几个关键问题:

能力 说明
统一身份 使用系统用户、部门、岗位、角色,不再维护第二套通讯录
统一租户 会话、成员、消息都带 tenant_id,天然隔离不同企业数据
统一权限 是否能发起沟通、是否能看到入口、是否能打开单据,都走同一套权限体系
统一上下文 从审批详情、通知消息、工作台直接进入会话,携带单据 ID 和流程名称
统一触达 WebSocket、未读数、顶部徽章、通知公告、待办入口形成触达闭环

这也是本文的核心观点:

OA 的核心不是"提交表单",而是让跨部门协作更快闭环;IM 的核心也不是"聊天",而是把实时沟通嵌入业务流。

image.png

image.png
▲ RuoyiOffice实现单据有IM联动:形成从"发起"到"沟通"再到"处理"的完整链路


二、系统设计:OA 内置 IM 应该放在哪一层?

2.1 不是 OA 模块私有能力,而是 infra 基础设施

RuoYi Office 的 IM 前端入口放在 OA → 即时通讯,但后端实现并没有塞进 oa 模块,而是放在 infra 基础设施模块下:

层级 位置 职责
前端页面 apps/web-antd/src/views/oa/im/index.vue IM 聊天界面、会话列表、消息区、上传入口
前端 API apps/web-antd/src/api/infra/im/index.ts 会话分页、创建单聊/群聊、消息分页、已读、未读数
后端服务 yudao-module-infra-server/service/im 会话、成员、消息、未读数、WebSocket 推送
WebSocket /infra/ws 双向消息通道,承载发送、接收、会话更新
数据表 infra_im_conversation 等三表 会话主数据、成员配置、消息明细

为什么这样设计?

因为 IM 不是只服务一个 OA 页面。它未来可以连接审批、通知、工单、项目、CRM 客户跟进、ERP 采购协同等模块。放在 infra 层,才能成为企业协同平台的公共实时通讯能力。

2.2 架构全景:会话、消息、在线状态、未读数

RuoYi Office 的 IM MVP 已经具备企业内部聊天的核心骨架:

功能 实现方式
单聊 create-single 创建,使用 single_key=minUserId:maxUserId 防重复
群聊 create-group 创建,成员写入会话成员表
消息发送 前端通过 WebSocket 发送 im.message.send
消息接收 后端推送 im.message.receive
会话更新 后端推送 im.conversation.update
未读数 成员表维护 unread_count,布局顶部轮询读取
置顶/免打扰 成员维度维护 top_flagmute_flag
在线状态 通过 WebSocket Session 管理器判断目标用户是否在线
图片/文件 前端复用统一文件上传接口,消息内容存 URL 或文件 JSON

这套设计里,有一个非常重要的边界:会话是多人共享的,未读数和置顶免打扰是个人私有的。

所以系统拆成三张表,而不是一张大表:

  • infra_im_conversation:描述会话本身。
  • infra_im_conversation_member:描述某个用户在某个会话里的个人状态。
  • infra_im_message:描述会话里的消息流水。

三、流程设计:从审批单据到 IM 会话的上下文传递

3.1 审批协同为什么需要"一键沟通"

审批系统最常见的低效,不是流程引擎跑不动,而是审批人理解成本太高。

例如一张出差报销单:

  1. 发起人提交报销。
  2. 部门经理发现发票附件不清晰。
  3. 财务想确认是否属于项目费用。
  4. 发起人补充解释。
  5. 财务确认后审批通过。

如果系统只有"同意/驳回",那审批人遇到问题只有两种选择:

  • 直接驳回,让发起人重新提交。
  • 去外部 IM 私聊,再回到 OA 审批。

这两种都不理想。更好的方式是:审批详情页识别当前流程上下文,一键发起与发起人或待办人的单聊,并把 processInstanceIdprocessInstanceName 带到 IM 页面。

3.2 URL/query 设计:业务上下文如何进入 IM?

RuoYi Office 前端已经有一个很清晰的上下文跳转模型。审批详情页可以创建单聊,然后跳转到 IM 页面:

export async function startConversationFromProcessInstance(options: {
  processInstanceId: number | string;
  processInstanceName?: string;
}) {
  const currentUserId = Number(useUserStore().userInfo?.id || 0) || undefined;
  const detail = await getApprovalDetail({
    processInstanceId: String(options.processInstanceId),
  });
  const targetUserId = resolveConversationTargetUserId(detail, currentUserId);
  if (!targetUserId) {
    message.warning('暂未识别出可沟通对象');
    return;
  }
  const conversation = await createSingleConversation({ targetUserId });
  await router.push({
    name: 'OaImIndex',
    query: {
      conversationId: conversation.id,
      processInstanceId: String(options.processInstanceId),
      processInstanceName:
        options.processInstanceName || detail.processInstance?.name,
    },
  });
}

这段代码的价值不在于"跳转页面",而在于它定义了 OA 单据和 IM 会话之间的连接协议:

query 含义 价值
conversationId 进入哪个会话 打开 IM 后直接定位沟通对象
processInstanceId 对应哪条流程实例 后续可反向打开审批详情
processInstanceName 流程实例名称 在聊天区展示当前讨论的业务主题

这就是业务上下文闭环的第一步:沟通不是从空白聊天框开始,而是从一个明确的业务对象开始。

3.3 通知、待办、工作台与 IM 的触达闭环

OA 内置 IM 不应该孤立在菜单里,而应该和已有触达体系联动:

入口 触达方式 与 IM 的关系
审批待办 流程详情页 发现问题时一键沟通
系统通知 站内信、通知下拉 通知可携带业务详情 URL
工作台 待办、公告、统计卡片 员工登录后第一时间看到待处理事项
顶部 IM 图标 未读数徽章 实时提醒有人发来消息
IM 会话页 单聊、群聊、附件 承接业务沟通过程

这套设计让 OA 不再是"我要主动去看有没有事",而是变成:

业务发生 → 系统通知 → 工作台/顶部徽章触达 → 进入详情 → 发起沟通 → 回到业务处理```

四、功能实现:IM 页面为什么要兼顾聊天体验和企业属性?

4.1 IM 即时通讯页面

im-chat-page.png

▲ IM 即时通讯页面:左侧是会话列表和未读数,右侧是消息区、输入区、图片/文件上传入口,并支持单聊、群聊、置顶、免打扰、在线状态等企业内部协作能力

一个企业 IM 页面,不只是把消息发出去,还要处理很多"协作细节":

页面区域 功能 设计要点
会话列表 搜索、排序、未读数 置顶优先,其次按最后消息时间排序
会话标题 单聊昵称、群聊名称、在线状态 单聊显示对方在线状态,群聊显示成员数
消息区域 文本、图片、文件 根据 messageType 渲染不同内容
输入区域 文本输入、发送、上传 文件先上传,再发送文件类型消息
成员配置 置顶、免打扰 用户级配置,不影响其他成员

4.2 前端 WebSocket 消息接收

IM 的实时性来自 WebSocket。前端连接 /infra/ws 后,监听服务端推送的消息类型:

const socketServer = buildWebSocketUrl('/infra/ws', refreshToken);

const { status, data, send, open } = useWebSocket(socketServer, {
  autoReconnect: true,
  heartbeat: true,
});

watchEffect(() => {
  if (!data.value || data.value === 'pong') {
    return;
  }
  const jsonMessage = JSON.parse(data.value);
  const content = parseMessageContent(jsonMessage.content);
  if (jsonMessage.type === 'im.message.receive') {
    handleIncomingMessage(content as InfraImApi.ImMessage);
    return;
  }
  if (jsonMessage.type === 'im.conversation.update') {
    upsertConversation(content as InfraImApi.ImConversation);
  }
});

这里有两个消息类型非常关键:

类型 触发时机 前端动作
im.message.receive 有新消息写入后 当前会话追加消息,必要时滚动到底部
im.conversation.update 会话最后消息、未读数、置顶等变化 更新左侧会话列表并重新排序

为什么要拆成两类?

因为"消息明细"和"会话摘要"是两个不同视图。当前聊天窗口需要新消息,左侧会话列表需要最后消息、未读数和排序。拆开推送,前端状态会更清晰。

4.3 顶部未读数:让 IM 进入工作台触达体系

在布局层,RuoYi Office 会读取 IM 未读数,并显示在顶部图标上:

async function handleImUnreadCount() {
  imUnreadCount.value = await getImUnreadCount();
}

onMounted(() => {
  handleNotificationGetUnreadCount();
  handleImUnreadCount();
  setInterval(
    () => {
      if (userStore.userInfo) {
        handleNotificationGetUnreadCount();
        handleImUnreadCount();
      }
    },
    1000 * 60 * 2,
  );
});

这里体现了一个产品设计细节:IM 未读数不是藏在菜单里,而是和站内通知一起进入顶部触达区域。员工不需要点开"OA → 即时通讯"才知道有人找他。
image.png
▲ RuoyiOffice:工作台顶部消息通知提醒


五、后端核心实现:发送一条消息背后发生了什么?

5.1 WebSocket 输入消息模型

前端发送消息时,后端接收的是一个非常克制的消息对象:

@Data
@Accessors(chain = true)
public class ImMessageSendMessage {

    private Long conversationId;

    private String messageType;

    private String content;

    private String clientMsgId;
}

这四个字段分别解决四个问题:

字段 作用
conversationId 消息发到哪个会话
messageType 文本、图片、文件的渲染方式
content 文本内容、图片 URL 或文件 JSON
clientMsgId 客户端幂等 ID,防止重发产生重复消息

5.2 发送消息:校验、落库、更新会话、增加未读

后端发送消息的主流程集中在 sendMessage()

@Override
@Transactional(rollbackFor = Exception.class)
public void sendMessage(Long loginUserId, Long tenantId, ImMessageSendMessage request) {
    if (!ImMessageTypeEnum.isSupported(request.getMessageType())) {
        throw exception(IM_MESSAGE_TYPE_INVALID);
    }
    if (StrUtil.isBlank(request.getContent())) {
        throw exception(IM_MESSAGE_CONTENT_EMPTY);
    }
    ImConversationDO conversation = validateConversationMember(
            loginUserId, tenantId, request.getConversationId());

    ImMessageDO message = ImMessageDO.builder()
            .conversationId(request.getConversationId())
            .senderId(loginUserId)
            .messageType(request.getMessageType())
            .content(StrUtil.trim(request.getContent()))
            .clientMsgId(StrUtil.blankToDefault(request.getClientMsgId(), null))
            .sendTime(LocalDateTime.now())
            .status(MESSAGE_STATUS_NORMAL)
            .build();
    message.setTenantId(tenantId);
    messageMapper.insert(message);
}

这段代码先做两类校验:

  • 消息类型必须是系统支持的 textimagefile
  • 当前用户必须是会话成员,否则不能往这个会话发消息。

也就是说,IM 的权限不是"知道会话 ID 就能发",而是会检查 tenant_id + conversation_id + user_id 的成员关系。

5.3 会话摘要更新:为什么消息表之外还要冗余 lastMessage?

消息写入后,系统会同步更新会话主表:

conversation.setLastMessageId(message.getId());
conversation.setLastMessagePreview(buildPreview(message.getMessageType(), message.getContent()));
conversation.setLastMessageType(message.getMessageType());
conversation.setLastSenderId(loginUserId);
conversation.setLastMessageTime(now);
conversationMapper.updateById(conversation);

List<ImConversationMemberDO> members =
        conversationMemberMapper.selectListByConversationId(tenantId, conversation.getId());
for (ImConversationMemberDO member : members) {
    if (!Objects.equals(member.getUserId(), loginUserId)) {
        member.setUnreadCount(ObjectUtil.defaultIfNull(member.getUnreadCount(), 0) + 1);
    }
    conversationMemberMapper.updateById(member);
}

会话列表是 IM 高频页面,如果每次都从消息表聚合最后一条消息,查询成本会越来越高。因此把最后消息摘要冗余到 infra_im_conversation 是合理的:

冗余字段 用途
last_message_id 定位最后一条消息
last_message_preview 左侧列表展示摘要
last_message_type 显示 [图片][文件] 等预览
last_sender_id 展示最后发言人
last_message_time 会话排序

而未读数放在成员表里,是因为同一条消息对发送人、接收人、群成员的状态不同。

5.4 后端会话更新推送:让列表和消息同时刷新

消息写入、会话摘要更新、未读数更新之后,后端会向所有成员推送两类事件:

for (ImConversationMemberDO member : members) {
    pushMessage(member.getUserId(), buildMessageResp(member.getUserId(), message, sender));
    pushConversationUpdate(member.getUserId(), tenantId, conversation.getId());
}

private void pushMessage(Long userId, ImMessageRespVO message) {
    if (webSocketMessageSender == null) {
        return;
    }
    webSocketMessageSender.sendObject(UserTypeEnum.ADMIN.getValue(), userId,
            ImWebSocketMessageTypeConstants.MESSAGE_RECEIVE, message);
}

private void pushConversationUpdate(Long userId, Long tenantId, Long conversationId) {
    ImConversationRespVO conversation = getConversation(userId, tenantId, conversationId);
    webSocketMessageSender.sendObject(UserTypeEnum.ADMIN.getValue(), userId,
            ImWebSocketMessageTypeConstants.CONVERSATION_UPDATE, conversation);
}

这也是 RuoYi Office IM 设计里很值得借鉴的一点:服务端不只推消息,还推更新后的会话视图。

这样前端不需要收到消息后再猜未读数、最后消息、会话标题、在线状态怎么变化,而是直接拿到后端构造好的 ImConversationRespVO


六、数据结构:三张表撑起企业内部 IM

6.1 会话表 infra_im_conversation

infra_im_conversation 记录会话本体,关注"这是什么会话"以及"最后一条消息是什么"。

字段 含义 设计说明
id 会话编号 主键
tenant_id 租户编号 多租户隔离
type 会话类型 1=单聊2=群聊
single_key 单聊唯一键 minUserId:maxUserId,防止重复单聊
name 群聊名称 单聊不需要,群聊需要
avatar 群头像 群聊展示
status 会话状态 预留禁用、归档等状态
last_message_id 最后一条消息 ID 会话列表快速展示
last_message_preview 最后一条消息预览 左侧摘要
last_message_type 最后一条消息类型 文本、图片、文件
last_sender_id 最后发送人 群聊摘要可用
last_message_time 最后消息时间 会话排序

关键索引:

UNIQUE KEY `uk_infra_im_conversation_single`
  (`tenant_id`, `single_key`, `deleted`),
KEY `idx_infra_im_conversation_last_time`
  (`tenant_id`, `last_message_time`)

single_key 的唯一约束很重要。否则 A 找 B 聊天创建一个会话,B 找 A 又创建一个会话,单聊历史就被拆散了。

6.2 成员表 infra_im_conversation_member

成员表记录"谁在这个会话里",以及这个用户自己的会话配置。

字段 含义 为什么放在成员表
conversation_id 会话编号 关联会话
user_id 用户编号 关联系统用户
top_flag 是否置顶 每个用户的置顶不同
mute_flag 是否免打扰 每个用户的免打扰不同
last_read_message_id 最后已读消息 ID 每个用户阅读进度不同
unread_count 未读数量 每个用户未读数不同

关键索引:

UNIQUE KEY `uk_infra_im_member_user`
  (`tenant_id`, `conversation_id`, `user_id`, `deleted`),
KEY `idx_infra_im_member_unread`
  (`tenant_id`, `user_id`, `unread_count`)

这张表是企业 IM 的关键。很多系统会把未读数存在缓存里,但 RuoYi Office 选择把它落在成员表中,优点是简单、可恢复、易查询,也适合企业内部 IM 这种消息规模。

6.3 消息表 infra_im_message

消息表记录真正的聊天流水。

字段 含义 设计说明
conversation_id 会话编号 消息归属
sender_id 发送人 关联系统用户
message_type 消息类型 textimagefile
content 消息内容 文本、图片 URL、文件 JSON
client_msg_id 客户端消息 ID 幂等去重
send_time 发送时间 展示与审计
status 消息状态 预留撤回、删除等扩展

关键索引:

UNIQUE KEY `uk_infra_im_message_client`
  (`tenant_id`, `conversation_id`, `sender_id`, `client_msg_id`, `deleted`),
KEY `idx_infra_im_message_time`
  (`tenant_id`, `conversation_id`, `id`)

其中 client_msg_id 的唯一索引用于处理网络抖动下的重复发送。前端发送失败重试时,只要客户端消息 ID 不变,后端就能避免插入重复消息。


七、RuoyiOffice 创新设计:把 IM 做成 OA 协同层,而不是聊天插件

7.1 创新一:IM 入口在 OA,能力沉到 infra

很多项目会把聊天功能做成一个独立页面,和业务模块毫无关系。RuoYi Office 的设计更像一层协同基础设施:

设计 价值
OA 菜单下有"即时通讯" 员工认知上属于办公协同
后端放在 infra 模块 可以被 OA、BPM、CRM、项目等模块复用
API 路径为 /infra/im/* 明确它是基础能力而非单一业务功能

这符合企业平台的演进规律:一开始是 OA 内部聊天,后面会自然扩展成全平台沟通能力。

7.2 创新二:审批详情可以直接发起业务沟通

startConversationFromProcessInstance() 的设计,让审批系统和 IM 建立了第一条业务链路:

流程详情 → 识别发起人/审批人 → 创建单聊 → 跳转 IM → 携带流程上下文

这个能力看似很小,但对实际办公效率影响很大:

  • 审批人不需要离开系统找人。
  • 发起人收到消息时能知道在讨论哪张单。
  • 后续可以扩展为聊天面板内直接打开审批详情。
  • 业务沟通不再从系统外部开始。

7.3 创新三:会话更新作为服务端事实推送

前端收到新消息后,如果自己计算会话列表状态,很容易出现多个页面状态不一致:

  • 当前聊天窗口有新消息。
  • 左侧会话列表摘要没更新。
  • 顶部未读数没变化。
  • 置顶排序没有刷新。

RuoYi Office 后端每次发送消息都会推送 im.conversation.update,把会话视图作为服务端事实下发。这样前端只负责渲染,不需要复制后端规则。

7.4 创新四:统一文件上传支撑图片和附件消息

企业聊天里,文本只是基础,更多协同发生在附件里:

  • 审批补充材料。
  • 发票、合同、制度文件。
  • 公文正文、附件、扫描件。
  • 项目交付文档。

RuoYi Office 前端 IM 页面复用 uploadFile,消息类型支持 imagefile。这样 IM 不需要单独实现一套文件存储,也能和系统统一文件能力保持一致。

7.5 创新五:和顶部工作台形成未读触达闭环

顶部 IM 图标 + 未读数徽章,是一个非常关键的产品设计。

它让 IM 不再依赖用户主动进入菜单,而是进入工作台级触达:

触达对象 触达入口
审批待办 工作台、流程中心
系统公告 通知下拉、首页组件
私聊/群聊 顶部 IM 图标、未读数

这三者组合起来,才是企业 OA 真正需要的"消息触达体系"。


八、扩展设计:内置 IM 还能如何继续演进?

当前 RuoYi Office IM 已经具备基础可用能力。如果继续向企业级深水区演进,可以沿着以下方向扩展。

8.1 业务对象消息卡片

现在 IM 通过 query 携带 processInstanceIdprocessInstanceName。下一步可以把业务对象变成结构化消息卡片:

{
  "type": "business_card",
  "bizType": "bpm_process",
  "bizId": "187654321",
  "title": "出差报销审批",
  "summary": "张三提交了 3 天上海出差报销,金额 2680 元",
  "url": "/bpm/process-instance/detail?id=187654321"
}

这样聊天里就不只是文本链接,而是可读、可点击、可追溯的业务卡片。

8.2 会话与单据关系表

如果要让一张审批单长期绑定一个讨论组,可以增加业务关系表:

字段 含义
biz_type 业务类型,如 bpm_processoa_sealcrm_customer
biz_id 业务主键
conversation_id 绑定会话
tenant_id 租户编号

这样可以支持:

  • 每张流程单自动创建协作群。
  • 单据详情页展示相关沟通记录。
  • 归档时把会话作为审计材料的一部分。

8.3 消息已读明细

当前成员表通过 last_read_message_idunread_count 表达阅读进度,适合 MVP。群聊规模变大后,可以增加消息已读明细:

能力 价值
单条消息已读人数 群聊中知道谁看过
重要消息确认 制度公告、任务通知需要确认
审批沟通留痕 谁何时读取过关键意见

8.4 与 BPM 评论/审批意见联动

审批意见和 IM 消息不是一回事:

  • 审批意见是正式结论。
  • IM 消息是沟通过程。

但两者可以互相引用。比如审批人可以把某条 IM 消息"引用为审批说明",或者在审批详情里展示"与该流程相关的沟通记录"。

这样既不污染正式审批意见,又能保留必要的协商上下文。


九、技术亮点总结

设计点 实现方式 业务价值
内置 IM OA 菜单入口 + infra 后端能力 沟通留在企业系统内部
统一身份 复用系统用户、部门、租户 不维护第二套通讯录
单聊去重 single_key=minUserId:maxUserId 避免重复会话
群聊支持 会话表 + 成员表 支持多人协同
实时推送 WebSocket /infra/ws 消息即时触达
双事件模型 im.message.receive + im.conversation.update 消息区和会话列表同步刷新
未读数 成员表 unread_count 顶部徽章、会话列表可直接展示
个人配置 top_flagmute_flag 置顶和免打扰不影响其他人
图片/文件 复用统一上传接口 附件协同不重复造轮子
业务上下文 query 携带流程实例信息 从审批自然进入沟通
多租户隔离 三张表都继承租户字段 SaaS 场景数据边界清晰

十、快速体验路径

如果你想体验 RuoYi Office 的内置 IM,可以按下面路径操作:

  1. 登录后台系统。
  2. 进入 OA → 即时通讯
  3. 点击发起单聊,选择一个系统用户。
  4. 发送文本消息,观察左侧会话列表最后消息变化。
  5. 上传图片或文件,查看消息类型渲染。
  6. 创建群聊,选择多个成员。
  7. 使用置顶、免打扰,观察会话排序和个人配置。
  8. 回到顶部导航,查看 IM 未读数徽章。

如果从审批场景体验,可以从流程详情页发起沟通,系统会自动创建单聊并跳转到 IM 页面,携带流程实例上下文。


结语:IM 不是聊天功能,而是 OA 的实时协同层

企业 OA 发展到一定阶段后,真正拉开差距的不是"表单数量",而是协作链路是否完整。

外部 IM 可以解决临时沟通,却很难解决企业系统需要的身份一致、权限一致、租户隔离、上下文回流和过程追溯。内置 IM 的意义,是把"谁在处理、为什么卡住、需要谁补充、沟通结果是什么"这些协作过程,重新拉回 OA 的业务闭环里。

RuoYi Office 当前的 IM 设计,用三张表、两类 WebSocket 事件、一个 OA 入口和一套上下文跳转机制,完成了从"聊天页面"到"协同基础设施"的第一步。

一套代码,通吃多端;一层 IM,打通协作上下文。这才是企业 OA 内置即时通讯真正值得做的原因。


💡 RuoYi Office —— 一个平台,管好整个企业

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

📦 源码1https://gitcode.com/zhouzhongyan/ruoyi-office-vben.git

📦 源码2https://gitcode.com/zhouzhongyan/ruoyi-office.git

📦 源码3https://github.com/yuqing2026/ruoyi-office.git

💬 微信:添加 17156169080,备注「RuoYi Office」

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


Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐