ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

从零用 Java 实现小红书 SpringBoot Vue UniApp(16)消息体系:私信、群聊与视频通话

从零用 Java 实现小红书 SpringBoot Vue UniApp(16)消息体系:私信、群聊与视频通话 移动端演示 http://8.146.211.120:8081/#/管理端演示 http://8.146.211.120:8088/#/项目整体介绍及演示前言前十五章已经走通内容、商城以及评论 / IM 基础图文能力。但真正让用户愿意留下来的往往不是商品页而是人与人之间更完整的连接。本章在此基础上补齐完整消息体系私信多媒体文字 / 图片 / 语音 / 视频 / 笔记卡片群聊创建、广场、禁言、撤回、在线人数会话聚合 Inbox置顶、未读社交互动通知赞藏、关注、评论1v1 音视频通话腾讯云 TRTC形成「内容社区 IM」双引擎。相对原教程本章新增了什么能力早期教程当前工程私信文本为主文本/图/语音/视频/笔记/通话记录/直播邀请卡群聊无完整群生命周期 广场发现会话列表简单列表Inbox 聚合、置顶、未读角标实时通道有限全局 WebSocket 单连接多业务复用音视频通话无TRTC 发起/接听/拒绝/挂断全流程一、整体架构消息体系拆成四条线会话线Inbox 汇总私信会话 群会话负责未读与置顶内容线统一contentType content多媒体用 JSON 存 payload实时线WebSocket 推送新消息、已读、在线人数、来电通话线RTC 信令走业务接口媒体走 TRTC消息 Tab ├─ 赞藏 / 关注 / 评论 通知入口 └─ Inbox 会话列表私信 群 ↓ 点进会话 chat.vue / group-chat.vue ↓ 发送 POST /member/dm/message/send ↓ 落库 WebSocket 推对端二、消息类型协议核心后端在MemberDmServiceImpl中定义类型常量前后端约定同一套数字private static final int TYPE_TEXT 1; private static final int TYPE_IMAGE 2; private static final int TYPE_VOICE 3; private static final int TYPE_NOTE 4; private static final int TYPE_VIDEO 5; /** 语音/视频通话记录 */ private static final int TYPE_CALL 6; /** 直播分享卡片 */ private static final int TYPE_LIVE 7; /** 音视频聊天室分享卡片 */ private static final int TYPE_VOICE_ROOM 8;发送时做白名单校验非法类型回落为文本int contentType reqVO.getContentType() null ? TYPE_TEXT : reqVO.getContentType(); if (contentType ! TYPE_TEXT contentType ! TYPE_IMAGE contentType ! TYPE_VOICE contentType ! TYPE_NOTE contentType ! TYPE_VIDEO contentType ! TYPE_LIVE contentType ! TYPE_VOICE_ROOM) { contentType TYPE_TEXT; }会话列表摘要按类型简化展示和早期「评论/IM 加图片」同一思路private String buildSummary(int contentType, String content) { if (contentType TYPE_IMAGE) { return [图片]; } if (contentType TYPE_VOICE) { return [语音]; } if (contentType TYPE_VIDEO) { return [视频]; } if (contentType TYPE_NOTE) { return [笔记] extractNoteTitle(content); } if (contentType TYPE_LIVE) { return [直播] extractNoteTitle(content); } if (contentType TYPE_VOICE_ROOM) { return [聊天室] extractNoteTitle(content); } // ... return StrUtil.maxLength(content, 80); }前端类型常量与组装工具sheep/helper/dmContent.jsexport const DM_TYPE { TEXT: 1, IMAGE: 2, VOICE: 3, NOTE: 4, VIDEO: 5, }; export function buildVoicePayload(url, durationSec) { return JSON.stringify({ url, duration: Math.max(1, Math.round(durationSec || 1)), }); } export function buildNotePayload(note) { const cover note.coverUrl || (note.picUrls note.picUrls.length ? note.picUrls[0] : ) || ; const summary (note.content || note.title || ).replace(/\s/g, ).trim().slice(0, 80); return JSON.stringify({ noteId: note.id, title: note.title || summary || 笔记, coverUrl: cover, nickname: note.nickname || , avatar: note.avatar || , summary, noteType: Number(note.noteType || 0), likeCount: Number(note.likeCount || 0), }); }发送接口// sheep/api/member/dm.js sendMessage: (data) { return request({ url: /member/dm/message/send, method: POST, data, custom: { auth: true, showLoading: false }, }); },三、私信气泡按 contentType 条件渲染pages/message/chat.vue里根据类型切换 UI笔记卡 / 语音条 / 视频封面 / 图片 / 文本!-- 笔记卡片 -- view v-ifitem.contentType DM_TYPE.NOTE classnote-card tapgoNoteCard(item) image classnote-card__cover :srcsheep.$url.cdn(notePayload(item).coverUrl) modeaspectFill / text classnote-card__title{{ notePayload(item)?.title || 笔记 }}/text /view !-- 语音 -- view v-else-ifitem.contentType DM_TYPE.VOICE classbubble bubble--voice tapplayVoice(item) uni-icons typesound size18 / text{{ voiceDuration(item) }}/text /view !-- 视频 -- view v-else-ifitem.contentType DM_TYPE.VIDEO classvideo-card tapopenVideo(item) image classvideo-card__cover :srcvideoCover(item) modeaspectFill / text classvideo-card__dur{{ videoDurationText(item) }}/text /view !-- 图片 / 文本 -- view v-else classbubble :class{ bubble--img: item.contentType DM_TYPE.IMAGE } image v-ifitem.contentType DM_TYPE.IMAGE classbubble__img :srcsheep.$url.cdn(item.content) modewidthFix tappreview(item.content) / text v-else classbubble__text{{ item.content }}/text /view发送封装async function sendContent(content, contentType DM_TYPE.TEXT) { const payload contentType DM_TYPE.TEXT ? (content || ).trim() : content || ; const { code, data } await DmApi.sendMessage({ peerId: state.peerId, contentType, content: payload, }); // 成功后 push 到本地 messageList并滚到底部 }四、WebSocket一条连接多业务复用类型常量集中在WebSocketMessageTypeConstants/** 私信新消息 */ String MEMBER_DM_MESSAGE member_dm_message_type; /** 群聊新消息 */ String MEMBER_GROUP_CHAT_MESSAGE member_group_chat_message_type; /** 消息未读总数变更 */ String MEMBER_UNREAD_COUNT member_unread_count_type; /** 视频通话邀请 / 接听 / 拒绝 / 取消 / 挂断 */ String MEMBER_RTC_CALL_INVITE member_rtc_call_invite_type; String MEMBER_RTC_CALL_ACCEPTED member_rtc_call_accepted_type; String MEMBER_RTC_CALL_REJECTED member_rtc_call_rejected_type; String MEMBER_RTC_CALL_CANCELLED member_rtc_call_cancelled_type; String MEMBER_RTC_CALL_HANGUP member_rtc_call_hangup_type;前端sheep/helper/wsGlobal.js订阅未读与来电进聊天页再处理具体消息推送。这样消息 Tab 角标、聊天页气泡、来电弹窗共用同一条长连接。五、群聊要点群聊覆盖运营侧常见能力创建群、群广场发现、申请加入群公告、成员列表、邀请入群群主禁言、踢人、消息撤回群在线人数实时订阅MEMBER_GROUP_CHAT_ONLINE_*消息体同样走contentType与私信协议对齐前端group-chat.vue复用气泡渲染思路。六、1v1 音视频通话通话信令接口AppMemberRtcControllerPostMapping(/call/create) Operation(summary 发起 1v1 视频通话) public CommonResultAppMemberRtcCallRespVO create(Valid RequestBody AppMemberRtcCallCreateReqVO reqVO) { return success(rtcService.createCall(getLoginUserId(), reqVO)); } PostMapping(/call/accept) public CommonResultAppMemberRtcCallRespVO accept(RequestParam(roomId) String roomId) { return success(rtcService.acceptCall(getLoginUserId(), roomId)); } PostMapping(/call/reject) public CommonResultBoolean reject(RequestParam(roomId) String roomId) { rtcService.rejectCall(getLoginUserId(), roomId); return success(true); }创建通话后通过 WebSocket 推送来电接听返回 TRTC 凭证媒体进独立通话页。结束后写一条TYPE_CALL气泡到私信会话。流程发起方 createCall → 对端 WS 收到 INVITE → accept / reject / timeout → TRTC 进房通话 → hangup → 私信写入通话记录气泡关键代码位置后端私信yudao-module-member/.../service/dm/MemberDmServiceImpl.java群聊.../controller/app/groupchat/会话 Inbox.../controller/app/inbox/通话.../controller/app/rtc/AppMemberRtcController.javaWS 常量.../enums/WebSocketMessageTypeConstants.java移动端消息 Tabpages/index/message.vue私信pages/message/chat.vue内容协议sheep/helper/dmContent.js全局 WSsheep/helper/wsGlobal.js总结用统一contentType扩展多媒体避免每种消息各建一套表Inbox WebSocket 解决多入口未读不一致信令与媒体分离业务状态走后端音视频走 TRTC下一章预告有了 IM 通道之后下一章接入TRTC 直播笔记开播、公屏弹幕、心跳关播、体验时长倒计时。演示地址移动端http://8.146.211.120:8081/#/管理端http://8.146.211.120:8088/#/代码地址https://gitee.com/ddeatrr/springboot_vue_xhs
返回列表