
SpringBoot3Vue3UniApp OA 即时通讯单聊群聊、图片文件、PC 与手机同一会话文档地址https://ruoyioffice.com源码1·GitHubhttps://github.com/yuqing2026/ruoyi-office源码2·GitCodehttps://gitcode.com/zhouzhongyan/ruoyi-office源码3·Giteehttps://gitee.com/yqzy1688/ruoyi-office微信17156169080备注「RuoYi Office」一句话OA 即时通讯要验收的是审批旁边能聊、群里能发图发文件、手机打开还是同一条会话。RuoYi Office 把它嵌在/oa/imUniApp 走消息页两端共用conversationId和/infra/ws。▲ 四步都发生在 OA 会话上组织选人 → text/image/file 组包 → clientMsgId 入库推送 → PC/oa/im与手机消息页同一未读引言聊天窗要长在 OA 里企业沟通最怕切出系统催审批去微信传截图再回单据。RuoYi Office 的即时通讯放在 OA 菜单里路由是/oa/im和用车、用印、云盘、邮箱同一套顶栏。本文只写这一条已经跑通的产品线端入口你在页面上看到的PCOA → 即时通讯/oa/im左会话、右气泡可发图发文件手机消息 Tab → 聊天页pages/message/chat同一conversationId可发图发文件不讲独立工作台式的 IM 产品形态。那条线还在打磨验收和配图都以 OA 会话为准。现场常卡在这四件事现场原话OA 即时通讯要给出的能力「审批里催一句就行」办理页可一键打开单聊带上流程名「项目组要拉个群」组织选人创建群聊能看成员「把现场照片发回来」图片先上传再发 URL「电脑发的附件手机要能下」文件消息两端都是{name,size,url}一、业务设计会话是组织关系不是加好友1.1 单聊用 pair key群聊用成员表单聊不按「谁先点谁」建两条。后端把两个用户 id 排成min:max写成singleKey同一对同事永远进同一条会话。群聊至少三人含发起人成员写在会话成员表上。发消息前校验你还在这个会话里不能拿别人的conversationId灌水。维度单聊type1群聊type2怎么建组织选一个同事选多名同事并填群名唯一性singleKey minUserId:maxUserId每次新建一条会话列表标题对方昵称群名称成员两人三人及以上推送两个成员当前成员列表每人一条PC 上单聊、群聊长在同一个窗口里。群会话标题带蓝色「群聊」标签点成员图标能看到工号。1.2 三种消息通道只走元数据OA 即时通讯当前支持三种messageTypemessageTypecontent列表预览text纯文本原文截断image图片 URL[图片]fileJSON{name,size,url,type}[文件] 文件名图片和文件都先走统一文件上传目录im/image、im/fileWebSocket 帧里只带类型和地址。不要把 PNG 字节塞进 WS。1.3 审批可以「找他聊聊」OA 即时通讯嵌在办公里价值是单据上下文。审批详情可以调createSingleConversation再router.push到/oa/im?conversationIdprocessInstanceName。聊天顶栏会标「来自审批流程名」不用口头说「那个单」。二、PC 端就在 OA 菜单里聊打开 OA → 即时通讯。左侧是会话未读、在线/离线、最后一条预览右侧是气泡。连接正常时底部有绿色「连接正常」。▲/oa/im和 OA 云盘、邮箱同一布局。右侧已发出文件卡片和图片Enter 发送支持粘贴图片群聊「小蜜蜂」点成员能看到群里有谁角色只区分「我」和成员——够办公协同不搞社交主页。▲ 群成员头像、姓名、工号。群治理在这里看人不在输入框里堆一排按钮2.1 发图上传成功再发 WS 帧PC 选图后调用uploadFile({ directory: im/image, file })拿到 URL 再发im.message.sendasyncfunctionsendImageMessage(file:File){constimageUrlawaituploadFile({directory:im/image,file});send(JSON.stringify({type:im.message.send,content:JSON.stringify({conversationId:activeConversationId.value,messageType:image,content:imageUrl,clientMsgId:web-image-${Date.now()},}),}));}文件同理content再包一层{ name, size, type, url }。前提是 WebSocket 已连通否则提示「实时连接未建立」。2.2 会话列表走 REST实时走 WS历史消息、会话分页用 RESTgetConversationPage、getConversationMessagePage。新消息、会话摘要更新走/infra/ws前端识别im.message.receive和im.conversation.update。刷新浏览器不会丢记录因为气泡已经落库。三、移动端消息 Tab 就是 OA 会话UniApp 底部「消息」不是通知列表的别名。pages/message/index上半是会话下半可以切到系统通知。点进聊天是pages/message/chat/index?conversationId和 PC 用同一个会话主键。▲ 手机「消息」会话列表。全文只这一张列表形态群聊「小蜜蜂」在手机上能看到文本、表情和文件卡片。加号里选图、选文件协议与 PC 相同。▲pages/message/chat群聊文件 1.9MB。PC 发过的附件手机这条会话里能点开下载发图同样是先上传再发functionsendImageMessage(imageUrl:string){constclientMsgIduni-image-${Date.now()}sendSocketPayload({type:im.message.send,content:JSON.stringify({conversationId:conversationId.value,messageType:image,content:imageUrl,clientMsgId,}),},(){appendLocalMessage({messageType:image,content:imageUrl,clientMsgId})})}相册走uni.chooseImage上传目录仍是im/image。文件走im/file。不要给手机另编一套 type 名。能力PC/oa/imUniApp 消息页文本Enter 发送点发送图片粘贴 / 选文件上传相册 / 相机文件{name,size,url}同一 JSON群成员成员弹窗顶栏群图标审批跳转query 带 conversationId从待办进会话同一 id四、后端三张表撑起 OA 会话4.1 表职责表作用infra_im_conversation会话type、singleKey、群名、最后一条预览infra_im_conversation_member成员置顶、免打扰、未读数infra_im_message消息messageType、content、clientMsgId未读按人记在成员表上。同一条群消息你已读、同事未读互不影响。4.2 发送在群才能发clientMsgId 去重WebSocket 监听im.message.send进sendMessagepublicvoidsendMessage(LongloginUserId,LongtenantId,ImMessageSendMessagerequest){if(!ImMessageTypeEnum.isSupported(request.getMessageType())){throwexception(IM_MESSAGE_TYPE_INVALID);}ImConversationDOconversationvalidateConversationMember(loginUserId,tenantId,request.getConversationId());if(StrUtil.isNotBlank(request.getClientMsgId())){ImMessageDOexistingmessageMapper.selectByClientMsgId(tenantId,request.getConversationId(),loginUserId,request.getClientMsgId());if(existing!null){return;}}messageMapper.insert(message);// 更新会话预览其他人 unreadCount 1再 push receive / conversation.update}要点不是会话成员直接拒绝clientMsgId命中则直接返回弱网连点不会插两条预览按类型生成[图片]/[文件] 文件名列表不用再解析正文在线状态看该用户有没有 WS Session不靠前端自己报心跳。单聊创建时的 pair keyprivateStringbuildSingleKey(LongfirstUserId,LongsecondUserId){longminUserIdMath.min(firstUserId,secondUserId);longmaxUserIdMath.max(firstUserId,secondUserId);returnminUserId:maxUserId;}A 点 B、B 点 A查到的是同一行。4.3 审批入口只多两个 queryconstconversationawaitcreateSingleConversation({targetUserId});awaitrouter.push({name:OaImIndex,query:{conversationId:conversation.id,processInstanceName:options.processInstanceName,},});IM 页读到conversationId就打开对应会话不必再在通讯录里找人。五、几个值得留下的取舍嵌在 OA不另开软件。催办、传图、看群成员都在办公顶栏下面完成。和微信群的差别不是聊得欢不欢而是会话 id 能跟审批单走。先上传再发。大图走 HTTP进度和鉴权都复用文件服务。WS 只同步「这条是图、地址在哪」。三张表不按端拆库。PC 和手机没有各自的消息表。手机历史接口和 PC 是同一套分页。clientMsgId 放在会话发送人维度。幂等键带上 conversationId避免不同会话碰巧用了同一个客户端 id。六、快速体验在线演示https://ruoyioffice.com/web/账号 admin / admin123移动端 H5https://ruoyioffice.com/app/源码仓库GitHubhttps://github.com/yuqing2026/ruoyi-office GitCodehttps://gitcode.com/zhouzhongyan/ruoyi-office Giteehttps://gitee.com/yqzy1688/ruoyi-office建议路径PC 登录后打开OA → 即时通讯/oa/im发起单聊发一段文字、一张图、一个小文件创建或进入群聊打开成员面板手机或 H5 进消息点同一会话确认图和文件都在可选从审批详情「找他聊聊」看顶栏是否带流程名。本地PC5800/oa/imUniApp H59000/#/pages/message/API48080。两端必须连同一后端否则会出现「电脑有图、手机没这条会话」。常见问题FAQOA 即时通讯和独立 IM 工作台是一回事吗不是。本文写的是嵌在 OA 菜单里的/oa/im以及手机消息页。两端共用infra_im_*三张表和/infra/ws。独立 IM 工作台不在本次验收范围。单聊会不会 A 找 B、B 找 A 变成两条不会。后端用minUserId:maxUserId做singleKey命中则复用。图片为什么不直接走 WebSocket帧会膨胀断线重传也难。先上传拿 URLmessageTypeimage只带地址。PC 粘贴图片、手机相册都走这条。手机消息页和系统通知会不会混在一起消息页上有「消息 / 通知」分段。会话是即时通讯通知是站内信。未读角标分开计。审批里怎么进到聊天创建或复用单聊后跳转/oa/im并带conversationId、可选processInstanceName。人还是组织里的人只是入口从单据过来。结语OA 即时通讯的完成态是单聊、群聊、图片、文件都发生在办公系统里并且 PC 与手机指向同一conversationId。RuoYi Office 用组织选人代替加好友用三张表记下未读和预览用「先上传再发」承接截图和附件用审批 query 把催办留在单据旁边。先把「电脑发图、手机能看、群里文件找得到」跑通即时通讯才配写进 OA 菜单。独立大而全的聊天工作台等这条主路径稳定后再谈。你们现在催审批是微信群人还是已经能从单据跳进会话评论区说说卡在哪一步。想要体验 RuoYi Office 的强大功能在线演示https://ruoyioffice.com/web/账号 admin / admin123源码仓库GitHubhttps://github.com/yuqing2026/ruoyi-office GitCodehttps://gitcode.com/zhouzhongyan/ruoyi-office Giteehttps://gitee.com/yqzy1688/ruoyi-office技术咨询添加微信17156169080备注「RuoYi Office」⭐如果觉得不错请给个 Star 支持一下