From 898da3a2c6c2a7076a93f8e18fe9bd9908823505 Mon Sep 17 00:00:00 2001 From: wangys <3401275564@qq.com> Date: Wed, 24 Dec 2025 15:02:23 +0800 Subject: [PATCH] =?UTF-8?q?web=E8=81=8A=E5=A4=A9=E5=AE=A4=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=90=8C=E6=AD=A5=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/workcase/service/ChatRoomService.java | 16 +- .../controller/WorkcaseChatContorller.java | 18 +- .../listener/ChatMessageListener.java | 18 +- .../workcase/mapper/TbChatMessageMapper.java | 5 + .../workcase/mapper/TbChatRoomMapper.java | 2 +- .../workcase/service/ChatRoomServiceImpl.java | 49 ++- .../resources/mapper/TbChatMessageMapper.xml | 6 + .../resources/mapper/TbChatRoomMapper.xml | 28 +- .../workcase/src/api/workcase/workcaseChat.ts | 18 +- .../packages/workcase/src/types/shared.d.ts | 2 +- .../views/public/ChatRoom/ChatRoomView.scss | 17 +- .../views/public/ChatRoom/ChatRoomView.vue | 69 +++- .../pages/chatRoom/chatRoom/chatRoom.uvue | 88 ++++- .../chatRoom/chatRoomList/chatRoomList.uvue | 68 +++- .../c94f6ecc8c2e943a1daefff4333af748c5da5830 | 1 - .../workcase_wechat/utils/websocket.ts | 339 ++++++++++++++++++ 16 files changed, 691 insertions(+), 53 deletions(-) delete mode 100644 urbanLifelineWeb/packages/workcase_wechat/unpackage/cache/.mp-weixin/.uts2js/cache/uts_9b1b54d07a7a4d66ee84f54872fe1ae86df34bb1/code/cache/c94f6ecc8c2e943a1daefff4333af748c5da5830 create mode 100644 urbanLifelineWeb/packages/workcase_wechat/utils/websocket.ts diff --git a/urbanLifelineServ/apis/api-workcase/src/main/java/org/xyzh/api/workcase/service/ChatRoomService.java b/urbanLifelineServ/apis/api-workcase/src/main/java/org/xyzh/api/workcase/service/ChatRoomService.java index 13c1e18f..47dae549 100644 --- a/urbanLifelineServ/apis/api-workcase/src/main/java/org/xyzh/api/workcase/service/ChatRoomService.java +++ b/urbanLifelineServ/apis/api-workcase/src/main/java/org/xyzh/api/workcase/service/ChatRoomService.java @@ -64,12 +64,13 @@ public interface ChatRoomService { ResultDomain getChatRoomById(String roomId); /** - * @description 获取聊天室列表/分页 - * @param pageRequest 分页请求 + * @description 分页查询聊天室(含当前用户未读数) + * @param pageRequest 分页请求参数 + * @param userId 当前用户ID * @author cascade * @since 2025-12-22 */ - ResultDomain getChatRoomPage(PageRequest pageRequest); + ResultDomain getChatRoomPage(PageRequest pageRequest, String userId); // ========================= 聊天室成员管理 ========================== @@ -114,6 +115,15 @@ public interface ChatRoomService { */ ResultDomain updateMemberReadStatus(String memberId, String lastReadMsgId); + /** + * @description 获取当前用户在指定聊天室的未读消息数 + * @param roomId 聊天室ID + * @param userId 用户ID + * @author cascade + * @since 2025-12-24 + */ + ResultDomain getUnreadCount(String roomId, String userId); + // ========================= 聊天消息管理 ========================== /** diff --git a/urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/controller/WorkcaseChatContorller.java b/urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/controller/WorkcaseChatContorller.java index 98d9cd9e..2e6d0ff4 100644 --- a/urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/controller/WorkcaseChatContorller.java +++ b/urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/controller/WorkcaseChatContorller.java @@ -106,7 +106,9 @@ public class WorkcaseChatContorller { @Operation(summary = "分页查询聊天室") @PreAuthorize("hasAuthority('workcase:room:view')") @PostMapping("/room/page") - public ResultDomain getChatRoomPage(@RequestBody PageRequest pageRequest) { + public ResultDomain getChatRoomPage( + @RequestBody PageRequest pageRequest, + @RequestParam(value = "userId", required = true) String userId) { ValidationResult vr = ValidationUtils.validate(pageRequest, Arrays.asList( ValidationUtils.requiredNumber("pageParam.page", "页码", 1, null), ValidationUtils.requiredNumber("pageParam.pageSize", "每页数量", 1, 100) @@ -114,7 +116,7 @@ public class WorkcaseChatContorller { if (!vr.isValid()) { return ResultDomain.failure(vr.getAllErrors()); } - return chatRoomService.getChatRoomPage(pageRequest); + return chatRoomService.getChatRoomPage(pageRequest, userId); } // ========================= ChatRoom成员管理 ========================= @@ -147,6 +149,15 @@ public class WorkcaseChatContorller { return chatRoomService.getChatRoomMemberList(roomId); } + @Operation(summary = "获取当前用户在指定聊天室的未读消息数") + @PreAuthorize("hasAuthority('workcase:room:member')") + @GetMapping("/room/{roomId}/unread") + public ResultDomain getUnreadCount( + @PathVariable(value = "roomId") String roomId, + @RequestParam(value = "userId") String userId) { + return chatRoomService.getUnreadCount(roomId, userId); + } + // ========================= ChatRoom消息管理 ========================= @Operation(summary = "发送聊天室消息") @@ -156,7 +167,8 @@ public class WorkcaseChatContorller { ValidationResult vr = ValidationUtils.validate(message, Arrays.asList( ValidationUtils.requiredString("roomId", "聊天室ID"), ValidationUtils.requiredString("senderId", "发送者ID"), - ValidationUtils.requiredString("content", "消息内容") + ValidationUtils.requiredString("content", "消息内容"), + ValidationUtils.requiredString("senderName", "发送者名称") )); if (!vr.isValid()) { return ResultDomain.failure(vr.getAllErrors()); diff --git a/urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/listener/ChatMessageListener.java b/urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/listener/ChatMessageListener.java index 7085944e..3d45502d 100644 --- a/urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/listener/ChatMessageListener.java +++ b/urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/listener/ChatMessageListener.java @@ -12,6 +12,8 @@ import com.alibaba.fastjson2.JSON; import org.xyzh.api.workcase.constant.WorkcaseConstant; import org.xyzh.api.workcase.dto.TbChatRoomMessageDTO; +import org.xyzh.api.workcase.vo.ChatRoomMessageVO; +import org.xyzh.workcase.mapper.TbChatMessageMapper; /** * @description 聊天消息Redis监听器,接收Pub/Sub消息并通过STOMP转发到WebSocket客户端 @@ -27,6 +29,9 @@ public class ChatMessageListener implements MessageListener { @Autowired(required = false) private SimpMessagingTemplate messagingTemplate; + @Autowired + private TbChatMessageMapper chatMessageMapper; + @Override public void onMessage(Message message, byte[] pattern) { try { @@ -46,9 +51,16 @@ public class ChatMessageListener implements MessageListener { // 处理聊天室消息频道: chat:room:{roomId} if (channel.startsWith(WorkcaseConstant.REDIS_CHAT_PREFIX)) { String roomId = channel.substring(WorkcaseConstant.REDIS_CHAT_PREFIX.length()); - // 转发到聊天窗口订阅者 - messagingTemplate.convertAndSend("/topic/chat/" + roomId, chatMessage); - logger.debug("消息已转发到STOMP: /topic/chat/{}", roomId); + + // 查询完整的VO数据(包含senderAvatar等额外字段) + ChatRoomMessageVO messageVO = chatMessageMapper.selectChatMessageVOById(chatMessage.getMessageId()); + if (messageVO != null) { + // 转发完整VO到聊天窗口订阅者 + messagingTemplate.convertAndSend("/topic/chat/" + roomId, messageVO); + logger.debug("消息已转发到STOMP: /topic/chat/{}", roomId); + } else { + logger.warn("未找到消息VO: messageId={}", chatMessage.getMessageId()); + } } // 处理列表更新频道: chat:list:update else if (WorkcaseConstant.REDIS_CHAT_LIST_UPDATE.equals(channel)) { diff --git a/urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/mapper/TbChatMessageMapper.java b/urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/mapper/TbChatMessageMapper.java index e48c8191..dcbded3b 100644 --- a/urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/mapper/TbChatMessageMapper.java +++ b/urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/mapper/TbChatMessageMapper.java @@ -53,4 +53,9 @@ public interface TbChatMessageMapper { */ long countChatMessages(@Param("filter") TbChatRoomMessageDTO filter); + /** + * 根据消息ID查询完整VO + */ + ChatRoomMessageVO selectChatMessageVOById(@Param("messageId") String messageId); + } diff --git a/urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/mapper/TbChatRoomMapper.java b/urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/mapper/TbChatRoomMapper.java index 3fc89f76..d96a4f53 100644 --- a/urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/mapper/TbChatRoomMapper.java +++ b/urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/mapper/TbChatRoomMapper.java @@ -46,7 +46,7 @@ public interface TbChatRoomMapper { /** * 分页查询聊天室 */ - List selectChatRoomPage(@Param("filter") TbChatRoomDTO filter, @Param("pageParam") PageParam pageParam); + List selectChatRoomPage(@Param("filter") TbChatRoomDTO filter, @Param("pageParam") PageParam pageParam, @Param("userId") String userId); /** * 统计聊天室数量 diff --git a/urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/service/ChatRoomServiceImpl.java b/urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/service/ChatRoomServiceImpl.java index 86203509..94047fe0 100644 --- a/urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/service/ChatRoomServiceImpl.java +++ b/urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/service/ChatRoomServiceImpl.java @@ -200,14 +200,14 @@ public class ChatRoomServiceImpl implements ChatRoomService { } @Override - public ResultDomain getChatRoomPage(PageRequest pageRequest) { + public ResultDomain getChatRoomPage(PageRequest pageRequest, String userId) { TbChatRoomDTO filter = pageRequest.getFilter(); if (filter == null) { filter = new TbChatRoomDTO(); } PageParam pageParam = pageRequest.getPageParam(); - List list = chatRoomMapper.selectChatRoomPage(filter, pageParam); + List list = chatRoomMapper.selectChatRoomPage(filter, pageParam, userId); long total = chatRoomMapper.countChatRooms(filter); pageParam.setTotal((int)total); @@ -246,6 +246,7 @@ public class ChatRoomServiceImpl implements ChatRoomService { member.setStatus("active"); } member.setJoinTime(new Date()); + member.setCreator(member.getUserId()); int rows = chatRoomMemberMapper.insertChatRoomMember(member); if (rows > 0) { @@ -332,6 +333,23 @@ public class ChatRoomServiceImpl implements ChatRoomService { return ResultDomain.failure("更新失败"); } + @Override + public ResultDomain getUnreadCount(String roomId, String userId) { + logger.info("查询未读消息数: roomId={}, userId={}", roomId, userId); + + TbChatRoomMemberDTO filter = new TbChatRoomMemberDTO(); + filter.setRoomId(roomId); + filter.setUserId(userId); + List members = chatRoomMemberMapper.selectChatRoomMemberList(filter); + + if (members.isEmpty()) { + return ResultDomain.success("查询成功", 0); + } + + Integer unreadCount = members.get(0).getUnreadCount(); + return ResultDomain.success("查询成功", unreadCount != null ? unreadCount : 0); + } + // ========================= 聊天消息管理 ========================== @Override @@ -357,6 +375,7 @@ public class ChatRoomServiceImpl implements ChatRoomService { if (message.getStatus() == null || message.getStatus().isEmpty()) { message.setStatus("sent"); } + message.setCreator(message.getSenderId()); // 使用Redis保证消息时间戳递增,避免并发乱序 String lockKey = WorkcaseConstant.REDIS_CHAT_LOCK + message.getRoomId(); @@ -402,6 +421,9 @@ public class ChatRoomServiceImpl implements ChatRoomService { updateRoom.setMessageCount(room.getMessageCount() != null ? room.getMessageCount() + 1 : 1); chatRoomMapper.updateChatRoom(updateRoom); + // 更新聊天室成员的未读数(除发送者外的所有成员 +1) + updateMembersUnreadCount(message.getRoomId(), message.getSenderId()); + // 发布消息到Redis Pub/Sub(聊天窗口) publishMessageToRedis(message); @@ -693,6 +715,29 @@ public class ChatRoomServiceImpl implements ChatRoomService { } } + /** + * 更新聊天室成员的未读数(除发送者外的所有成员 +1) + */ + private void updateMembersUnreadCount(String roomId, String senderId) { + try { + TbChatRoomMemberDTO filter = new TbChatRoomMemberDTO(); + filter.setRoomId(roomId); + List members = chatRoomMemberMapper.selectChatRoomMemberList(filter); + + for (ChatMemberVO member : members) { + if (!senderId.equals(member.getUserId())) { + TbChatRoomMemberDTO updateMember = new TbChatRoomMemberDTO(); + updateMember.setMemberId(member.getMemberId()); + updateMember.setUnreadCount((member.getUnreadCount() != null ? member.getUnreadCount() : 0) + 1); + chatRoomMemberMapper.updateChatRoomMember(updateMember); + } + } + logger.debug("已更新聊天室成员未读数: roomId={}, 更新成员数={}", roomId, members.size() - 1); + } catch (Exception e) { + logger.error("更新聊天室成员未读数失败: roomId={}", roomId, e); + } + } + private void publishMessageToRedis(TbChatRoomMessageDTO message) { try { String channel = WorkcaseConstant.REDIS_CHAT_PREFIX + message.getRoomId(); diff --git a/urbanLifelineServ/workcase/src/main/resources/mapper/TbChatMessageMapper.xml b/urbanLifelineServ/workcase/src/main/resources/mapper/TbChatMessageMapper.xml index 8b185b32..8e91cd1a 100644 --- a/urbanLifelineServ/workcase/src/main/resources/mapper/TbChatMessageMapper.xml +++ b/urbanLifelineServ/workcase/src/main/resources/mapper/TbChatMessageMapper.xml @@ -141,4 +141,10 @@ + + diff --git a/urbanLifelineServ/workcase/src/main/resources/mapper/TbChatRoomMapper.xml b/urbanLifelineServ/workcase/src/main/resources/mapper/TbChatRoomMapper.xml index 6ccf2d0b..70fc27db 100644 --- a/urbanLifelineServ/workcase/src/main/resources/mapper/TbChatRoomMapper.xml +++ b/urbanLifelineServ/workcase/src/main/resources/mapper/TbChatRoomMapper.xml @@ -35,6 +35,7 @@ + @@ -118,19 +119,24 @@ diff --git a/urbanLifelineWeb/packages/workcase/src/api/workcase/workcaseChat.ts b/urbanLifelineWeb/packages/workcase/src/api/workcase/workcaseChat.ts index 516d088b..b0d4fcab 100644 --- a/urbanLifelineWeb/packages/workcase/src/api/workcase/workcaseChat.ts +++ b/urbanLifelineWeb/packages/workcase/src/api/workcase/workcaseChat.ts @@ -59,10 +59,12 @@ export const workcaseChatAPI = { }, /** - * 分页查询聊天室 + * 分页查询聊天室(含当前用户未读数) */ - async getChatRoomPage(pageRequest: PageRequest): Promise> { - const response = await api.post(`${this.baseUrl}/room/page`, pageRequest) + async getChatRoomPage(pageRequest: PageRequest, userId: string): Promise> { + const response = await api.post(`${this.baseUrl}/room/page`, pageRequest, { + params: { userId } + }) return response.data }, @@ -92,6 +94,16 @@ export const workcaseChatAPI = { return response.data }, + /** + * 获取当前用户在指定聊天室的未读消息数 + */ + async getUnreadCount(roomId: string, userId: string): Promise> { + const response = await api.get(`${this.baseUrl}/room/${roomId}/unread`, + { userId } + ) + return response.data + }, + // ====================== ChatRoom消息管理 ====================== /** diff --git a/urbanLifelineWeb/packages/workcase/src/types/shared.d.ts b/urbanLifelineWeb/packages/workcase/src/types/shared.d.ts index dd2db806..c3722a53 100644 --- a/urbanLifelineWeb/packages/workcase/src/types/shared.d.ts +++ b/urbanLifelineWeb/packages/workcase/src/types/shared.d.ts @@ -52,7 +52,7 @@ declare module 'shared/api' { import type { AxiosResponse, AxiosRequestConfig } from 'axios' interface ApiInstance { - get(url: string, config?: AxiosRequestConfig): Promise> + get(url: string,data?: any, config?: AxiosRequestConfig): Promise> post(url: string, data?: any, config?: AxiosRequestConfig): Promise> put(url: string, data?: any, config?: AxiosRequestConfig): Promise> delete(url: string, config?: AxiosRequestConfig): Promise> diff --git a/urbanLifelineWeb/packages/workcase/src/views/public/ChatRoom/ChatRoomView.scss b/urbanLifelineWeb/packages/workcase/src/views/public/ChatRoom/ChatRoomView.scss index aebf3adc..ce3e8712 100644 --- a/urbanLifelineWeb/packages/workcase/src/views/public/ChatRoom/ChatRoomView.scss +++ b/urbanLifelineWeb/packages/workcase/src/views/public/ChatRoom/ChatRoomView.scss @@ -235,7 +235,14 @@ $brand-color-hover: #004488; flex-shrink: 0; } + .last-message-row { + display: flex; + align-items: center; + gap: 8px; + } + .last-message { + flex: 1; font-size: 13px; color: #64748b; white-space: nowrap; @@ -244,9 +251,10 @@ $brand-color-hover: #004488; } .unread-badge { - position: absolute; - top: 10px; - right: 10px; + flex-shrink: 0; + display: inline-flex; + align-items: center; + justify-content: center; min-width: 18px; height: 18px; padding: 0 6px; @@ -255,9 +263,6 @@ $brand-color-hover: #004488; border-radius: 9px; font-size: 11px; font-weight: 600; - display: flex; - align-items: center; - justify-content: center; } } diff --git a/urbanLifelineWeb/packages/workcase/src/views/public/ChatRoom/ChatRoomView.vue b/urbanLifelineWeb/packages/workcase/src/views/public/ChatRoom/ChatRoomView.vue index a046770e..6d0b295b 100644 --- a/urbanLifelineWeb/packages/workcase/src/views/public/ChatRoom/ChatRoomView.vue +++ b/urbanLifelineWeb/packages/workcase/src/views/public/ChatRoom/ChatRoomView.vue @@ -68,13 +68,15 @@
{{ room.roomName }}
{{ formatTime(room.lastMessageTime) }}
-
{{ room.lastMessage || '暂无消息' }}
+
+
{{ room.lastMessage || '暂无消息' }}
+ +
+ {{ (room.unreadCount ?? 0) > 99 ? '99+' : room.unreadCount }} +
+
- -
- {{ (room.unreadCount ?? 0) > 99 ? '99+' : room.unreadCount }} -
@@ -86,7 +88,7 @@ -