从零用 Java 实现小红书 SpringBoot Vue UniApp(18)音视频聊天室完整实现
>## [**移动端演示 http://8.146.211.120:8081/#/**](https://gitee.com/link?target=http%3A%2F%2F8.146.211.120%3A8081%2F%23%2F) ## [**管理端演示 http://8.146.211.120:8088/#/**](https://gitee.com/link?target=http%3A%2F%2F8.146.211.120%3A8088%2F%23%2F) ## [**项目整体介绍及演示**](https://gitee.com/link?target=https%3A%2F%2Fblog.csdn.net%2Fqq_35238367%2Farticle%2Fdetails%2F145126222) **前言** 直播是「主播中心」:一个人讲,很多人听。聊天室则是「麦位中心」:多人轮流上麦,更接近 Clubhouse / 语聊房形态。 本章与(17)共用腾讯云 TRTC 与 WebSocket 基础设施,但业务模型完全不同: - 房间号进出 - 1 个房主位 + 8 个麦位 - 语音房 / 视频房双模式 - 锁麦、抱下、强制闭麦、踢人、公屏禁言 - 体验时长倒计时 ----      ### 相对直播,聊天室解决什么问题 |维度|直播|聊天室| |:-:|:-:|:-:| |角色|主播 / 观众|房主 / 麦上用户 / 听众| |推流人数|通常 1 路主播|最多房主 + 8 麦| |治理|禁言、关播|锁麦、抱下、踢人、禁言| |入口|笔记 / 创作者中心|房间号创建或加入| |分享|直播卡片|聊天室卡片| ---- ### 一、业务架构 ``` 创建房间(mediaMode=0语音 / 1视频) ↓ 返回 roomNo + TRTC 凭证 ↓ 房主默认在「房主位」推流;听众点空麦位上麦 ↓ 麦位状态 JSON 存库 + WebSocket 全员同步 ↓ 结束 / 踢出 / 体验到期 → 推送 ended / kicked ``` 麦位槽位字段:`userId` / `muted` / `locked` / `cameraOff`。改麦位不必拆 8 张子表,状态变更后 `pushRoomState` 推给在房用户。 ---- ### 二、创建房间(核心代码) `MemberVoiceRoomServiceImpl#createRoom`: ``` public AppMemberVoiceRoomRespVO createRoom(Long loginUserId, AppMemberVoiceRoomCreateReqVO reqVO) { MemberVoiceRoomDO living = voiceRoomMapper.selectLivingByOwner(loginUserId); if (living != null) { throw exception(VOICE_ROOM_NO_CREATE); } String roomNo = nextRoomNo(); Integer mediaMode = MemberVoiceRoomMediaModeEnum.of(reqVO.getMediaMode()).getMode(); String defaultTitle = MemberVoiceRoomMediaModeEnum.isVideo(mediaMode) ? "视频聊天室" : "语音聊天室"; MemberVoiceRoomDO room = MemberVoiceRoomDO.builder() .roomNo(roomNo) .trtcRoomId("vr_" + roomNo) .title(StrUtil.blankToDefault(StrUtil.trim(reqVO.getTitle()), defaultTitle)) .ownerUserId(loginUserId) .status(MemberVoiceRoomStatusEnum.LIVING.getStatus()) .mediaMode(mediaMode) .seatJson(emptySeats()) // 8 个空麦位 .ownerMuted(false) .ownerCameraOff(false) .memberCount(1) .ownerHeartbeatTime(LocalDateTime.now()) .chatJson(new ArrayList<>()) .build(); voiceRoomMapper.insert(room); // ... return buildResp(room, loginUserId, true); } ``` 加入房间:校验进行中 + 体验是否到期,登记 Redis 在线,推送系统公屏「进入了房间」。 ---- ### 三、上麦 / 下麦 ``` public AppMemberVoiceRoomRespVO takeSeat(Long loginUserId, AppMemberVoiceRoomTakeSeatReqVO reqVO) { MemberVoiceRoomDO room = requireLiving(reqVO.getRoomNo()); if (Objects.equals(room.getOwnerUserId(), loginUserId)) { throw exception(VOICE_ROOM_ALREADY_ON_SEAT); // 房主已在房主位 } List> seats = normalizeSeats(room.getSeatJson()); Integer seatIndex = reqVO.getSeatIndex(); // 校验:已在麦上 / 座位有人 / 锁麦 Map target = seats.get(seatIndex); if (target != null && toBool(target.get("locked"))) { throw exception(VOICE_ROOM_SEAT_LOCKED); } Map seat = new HashMap<>(); seat.put("userId", loginUserId); seat.put("muted", false); seat.put("locked", false); seat.put("cameraOff", false); seats.set(seatIndex, seat); MemberVoiceRoomDO update = new MemberVoiceRoomDO(); update.setId(room.getId()); update.setSeatJson(seats); voiceRoomMapper.updateById(update); pushRoomState(room); // WebSocket 全员同步麦位 return buildResp(room, loginUserId, false); } ``` 前端:上麦成功后切换 TRTC 角色为主播并 `startLocalAudio/Video`;下麦切回观众并停采。 房主治理:锁麦 / 强制闭麦 / 抱下 / 禁言 / 踢人。视频模式下「点击放大预览、长按出管理菜单」,避免误触。 ---- ### 四、接口一览 `AppMemberVoiceRoomController`(`/member/voice-room`): ``` @PostMapping("/create") // 创建 @PostMapping("/join") // 加入 @GetMapping("/get") // 状态 @PostMapping("/seat/take") // 上麦 @PostMapping("/seat/leave") // 下麦 @PostMapping("/mute") // 静音 @PostMapping("/camera") // 摄像头 @PostMapping("/chat") // 公屏 @PostMapping("/leave") // 离开 @PostMapping("/end") // 结束 @PostMapping("/heartbeat") // 房主心跳 @PostMapping("/seat/lock") // 锁麦 @PostMapping("/seat/force-leave") // 抱下 @PostMapping("/kick") // 踢人 @PostMapping("/chat/mute") // 禁言 @PostMapping("/force-mute") // 强制闭麦 ``` WebSocket 类型: ``` String MEMBER_VOICE_ROOM_SUBSCRIBE = "member_voice_room_subscribe_type"; String MEMBER_VOICE_ROOM_CHAT = "member_voice_room_chat_type"; String MEMBER_VOICE_ROOM_STATE = "member_voice_room_state_type"; String MEMBER_VOICE_ROOM_ENDED = "member_voice_room_ended_type"; String MEMBER_VOICE_ROOM_KICKED = "member_voice_room_kicked_type"; ``` ---- ### 五、前端页面结构 **入口页** `pages/voice-room/index.vue` - 创建语音房 / 创建视频房 - 输入房间号加入 **房间页** `pages/voice-room/room.vue` |区域|内容| |:-:|:-:| |顶栏|房主信息、人数、分享、离开| |舞台|房主位 + 8 麦位网格| |中部|体验倒计时浮层| |公屏|系统消息 + 用户消息| |底部|输入、表情、更多(麦、摄像头、耳返、扬声器、下麦)| App 端通过 `hybrid/html/trtc-voice-room.html` 承载 TRTC。体验配置:`member.voice-room.max-minutes`,倒计时与直播共用同一套工具。 ---- ### 关键代码位置 - 后端:`AppMemberVoiceRoomController.java`、`MemberVoiceRoomServiceImpl.java` - 表:`member_voice_room`(见 `xhs-init.sql`) - 移动端:`pages/voice-room/index.vue`、`room.vue` - API:`sheep/api/member/voiceRoom.js` ---- ### 总结 1. 麦位 JSON + 全量状态推送,实现成本低、联调快 2. 与直播共用 TRTC / 倒计时 / 敏感词,模块边界清晰 3. 「点击放大、长按治理」提升视频房可用性 #### 下一章预告 下一章专门讲 **分享卡片**:把房间一键发到私信/群聊,以及体验倒计时与部署 SQL 清单。 ---- >**演示地址** 移动端:[http://8.146.211.120:8081/#/](https://gitee.com/link?target=http%3A%2F%2F8.146.211.120%3A8081%2F%23%2F) 管理端:[http://8.146.211.120:8088/#/](https://gitee.com/link?target=http%3A%2F%2F8.146.211.120%3A8088%2F%23%2F) **代码地址** https://gitee.com/ddeatrr/springboot_vue_xhs