From 52b16ed2b068086a08251dbe1f018c2ed0146576 Mon Sep 17 00:00:00 2001 From: oneao Date: Tue, 30 Dec 2025 22:25:54 +0800 Subject: [PATCH] u --- .../cn/link56/im/controller/ImController.java | 14 ++ .../cn/link56/im/enums/MessageTypeEnums.java | 3 +- .../cn/link56/im/handler/MessageHandler.java | 72 ++++--- .../cn/link56/im/mapper/ImFriendMapper.java | 7 + .../cn/link56/im/mapper/ImGroupMapper.java | 6 +- .../java/cn/link56/im/model/dto/GroupDTO.java | 3 + .../link56/im/model/vo/FriendProfileVO.java | 15 ++ .../cn/link56/im/model/vo/GroupMessageVO.java | 14 ++ .../cn/link56/im/model/vo/GroupProfileVO.java | 16 ++ .../cn/link56/im/model/vo/QueryFriendVO.java | 1 - .../cn/link56/im/model/vo/QueryGroupVO.java | 3 +- .../cn/link56/im/service/ImFriendService.java | 7 + .../cn/link56/im/service/ImGroupService.java | 18 ++ .../link56/im/service/ImGroupUserService.java | 26 ++- .../cn/link56/im/mapper/ImFriendMapper.xml | 15 +- .../cn/link56/im/mapper/ImGroupMapper.xml | 18 +- .../src/request/api/im/index.ts | 12 +- .../src/store/websocket.ts | 2 +- .../link56-frontend-new/src/types/im/index.ts | 20 +- .../views/im/components/GroupChatWindows.vue | 97 +++++++--- .../src/views/im/index.vue | 182 ++++++++++++++---- 21 files changed, 444 insertions(+), 107 deletions(-) create mode 100644 code/link56/link56-backend/src/main/java/cn/link56/im/model/vo/FriendProfileVO.java create mode 100644 code/link56/link56-backend/src/main/java/cn/link56/im/model/vo/GroupMessageVO.java create mode 100644 code/link56/link56-backend/src/main/java/cn/link56/im/model/vo/GroupProfileVO.java diff --git a/code/link56/link56-backend/src/main/java/cn/link56/im/controller/ImController.java b/code/link56/link56-backend/src/main/java/cn/link56/im/controller/ImController.java index 81fb7f00..2c00c036 100644 --- a/code/link56/link56-backend/src/main/java/cn/link56/im/controller/ImController.java +++ b/code/link56/link56-backend/src/main/java/cn/link56/im/controller/ImController.java @@ -89,4 +89,18 @@ public class ImController { return Result.success(); } + + @GetMapping("/friend/{friendId}") + public Result getFriendProfile(@PathVariable("friendId") Long friendId){ + FriendProfileVO friendProfileVO = imFriendService.getFriendProfile(friendId); + + return Result.success(friendProfileVO); + } + + @GetMapping("/group/{groupId}") + public Result getGroupProfile(@PathVariable("groupId") Long groupId){ + GroupProfileVO groupProfileVO = imGroupService.getGroupProfile(groupId); + + return Result.success(groupProfileVO); + } } diff --git a/code/link56/link56-backend/src/main/java/cn/link56/im/enums/MessageTypeEnums.java b/code/link56/link56-backend/src/main/java/cn/link56/im/enums/MessageTypeEnums.java index d60723e7..1381bcba 100644 --- a/code/link56/link56-backend/src/main/java/cn/link56/im/enums/MessageTypeEnums.java +++ b/code/link56/link56-backend/src/main/java/cn/link56/im/enums/MessageTypeEnums.java @@ -13,8 +13,7 @@ public enum MessageTypeEnums { OFFLINE(6), GROUP_OFFLINE(7), PRIVATE(8), - GROUP(9), - FRIEND_UPDATE(10); + GROUP(9); private final int code; diff --git a/code/link56/link56-backend/src/main/java/cn/link56/im/handler/MessageHandler.java b/code/link56/link56-backend/src/main/java/cn/link56/im/handler/MessageHandler.java index fbaf856d..78bd6a8a 100644 --- a/code/link56/link56-backend/src/main/java/cn/link56/im/handler/MessageHandler.java +++ b/code/link56/link56-backend/src/main/java/cn/link56/im/handler/MessageHandler.java @@ -12,12 +12,12 @@ import cn.link56.im.message.MessageResponse; import cn.link56.im.model.dto.GroupDTO; import cn.link56.im.model.dto.LoginDTO; import cn.link56.im.model.dto.PrivateDTO; +import cn.link56.im.model.po.ImGroupMessage; import cn.link56.im.model.po.ImPrivateMessage; +import cn.link56.im.model.vo.GroupMessageVO; import cn.link56.im.model.vo.PrivateMessageVO; import cn.link56.im.model.vo.QueryFriendVO; -import cn.link56.im.service.ImFriendService; -import cn.link56.im.service.ImGroupUserService; -import cn.link56.im.service.ImPrivateMessageService; +import cn.link56.im.service.*; import cn.link56.im.storage.SessionManager; import cn.link56.im.utils.MessageDataConverter; import cn.link56.im.utils.MessageWriter; @@ -40,6 +40,10 @@ public class MessageHandler { private ImPrivateMessageService imPrivateMessageService; @Resource private CompanyUserService companyUserService; + @Resource + private ImGroupMessageService imGroupMessageService; + @Resource + private ImGroupService imGroupService; // 处理消息 public void handleMessage(Channel channel, MessageRequest request) { @@ -104,7 +108,7 @@ public class MessageHandler { MessageWriter.broadcastToUsers(onlineFriends,MessageResponse.success(MessageTypeEnums.ONLINE).setFrom(from)); // 通知群组 - List onlineGroupUsers = imGroupUserService.getOnlineGroupUsers(from); + List onlineGroupUsers = imGroupUserService.getAllOnlineGroupUsers(from); MessageWriter.broadcastToUsers(onlineGroupUsers,MessageResponse.success(MessageTypeEnums.GROUP_ONLINE).setFrom(from)); } @@ -115,7 +119,7 @@ public class MessageHandler { MessageWriter.broadcastToUsers(onlineFriends,MessageResponse.success(MessageTypeEnums.OFFLINE).setFrom(from)); // 通知群组 - List onlineGroupUsers = imGroupUserService.getOnlineGroupUsers(from); + List onlineGroupUsers = imGroupUserService.getAllOnlineGroupUsers(from); MessageWriter.broadcastToUsers(onlineGroupUsers,MessageResponse.success(MessageTypeEnums.GROUP_OFFLINE).setFrom(from)); } @@ -132,22 +136,6 @@ public class MessageHandler { // 好友关系建立 imFriendService.createFriend(to,from); - // 通知建立了好友关系 - if (SessionManager.isOnline(to)) { - CompanyUser companyUser = companyUserService.getById(from); - if (ObjectUtils.isNotEmpty(companyUser)){ - QueryFriendVO queryFriendVO = new QueryFriendVO(); - queryFriendVO.setFriendId(from); - queryFriendVO.setFriendName(companyUser.getName()); - queryFriendVO.setFriendAvatar(companyUser.getAvatar()); - queryFriendVO.setIsOnline(true); - - queryFriendVO.setUnreadCount(0); - - MessageWriter.writeToUser(to,MessageResponse.success(MessageTypeEnums.FRIEND_UPDATE,from).setData(queryFriendVO)); - } - } - // 保存 Long privateMessageID = IdGenerator.nextId(); Long sendTime = TimeUtils.getCurrentTimestampMillis(); @@ -188,11 +176,51 @@ public class MessageHandler { // 群聊逻辑 private void handleGroup(Channel channel, MessageRequest request) { Long from = request.getFrom(); - Long to = request.getTo(); + Long groupId = request.getTo(); GroupDTO dto = MessageDataConverter.convert(request.getData(), GroupDTO.class); String content = dto.getContent(); Integer type = dto.getType(); String tempId = dto.getTempId(); + String senderName = dto.getSenderName(); + String senderAvatar = dto.getSenderAvatar(); + + // 将消息插入到数据库中 + long nextId = IdGenerator.nextId(); + long timestampMillis = TimeUtils.getCurrentTimestampMillis(); + + ImGroupMessage imGroupMessage = new ImGroupMessage(); + imGroupMessage.setId(nextId); + imGroupMessage.setContent(content); + imGroupMessage.setType(type); + imGroupMessage.setSenderId(from); + imGroupMessage.setSendTime(timestampMillis); + imGroupMessage.setGroupId(groupId); + + imGroupMessageService.save(imGroupMessage); + + // 更新未读状态 + imGroupUserService.updateGroupUnreadCount(groupId,from); + // 更新lastMessageId + imGroupService.updateLaseMessageId(groupId,nextId); + + // 构建回复消息 + GroupMessageVO groupMessageVO = new GroupMessageVO(); + groupMessageVO.setTempId(tempId); + groupMessageVO.setId(nextId); + groupMessageVO.setSenderId(from); + groupMessageVO.setContent(content); + groupMessageVO.setType(type); + groupMessageVO.setSendTime(timestampMillis); + groupMessageVO.setGroupId(groupId); + groupMessageVO.setSenderName(senderName); + groupMessageVO.setSenderAvatar(senderAvatar); + + // 通知建立群组关系 + + + // 查询该群在线的用户 + List onlineGroupUsers = imGroupUserService.getGroupOnlineUserIds(groupId); + MessageWriter.broadcastToUsers(onlineGroupUsers,MessageResponse.success(MessageTypeEnums.GROUP,groupId).setData(groupMessageVO)); } } diff --git a/code/link56/link56-backend/src/main/java/cn/link56/im/mapper/ImFriendMapper.java b/code/link56/link56-backend/src/main/java/cn/link56/im/mapper/ImFriendMapper.java index 45290105..f1fb32b7 100644 --- a/code/link56/link56-backend/src/main/java/cn/link56/im/mapper/ImFriendMapper.java +++ b/code/link56/link56-backend/src/main/java/cn/link56/im/mapper/ImFriendMapper.java @@ -1,6 +1,7 @@ package cn.link56.im.mapper; import cn.link56.im.model.po.ImFriend; +import cn.link56.im.model.vo.FriendProfileVO; import cn.link56.im.model.vo.QueryFriendVO; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; @@ -34,4 +35,10 @@ public interface ImFriendMapper extends BaseMapper { @Param("userId") Long userId, @Param("friendId") Long friendId ); + + + FriendProfileVO selectFriendProfile( + @Param("userId") Long userId, + @Param("friendId") Long friendId + ); } diff --git a/code/link56/link56-backend/src/main/java/cn/link56/im/mapper/ImGroupMapper.java b/code/link56/link56-backend/src/main/java/cn/link56/im/mapper/ImGroupMapper.java index 0ce3e71d..fe169d20 100644 --- a/code/link56/link56-backend/src/main/java/cn/link56/im/mapper/ImGroupMapper.java +++ b/code/link56/link56-backend/src/main/java/cn/link56/im/mapper/ImGroupMapper.java @@ -1,7 +1,7 @@ package cn.link56.im.mapper; import cn.link56.im.model.po.ImGroup; -import cn.link56.im.model.vo.QueryFriendVO; +import cn.link56.im.model.vo.GroupProfileVO; import cn.link56.im.model.vo.QueryGroupVO; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; @@ -21,4 +21,8 @@ public interface ImGroupMapper extends BaseMapper { Long selectGroupTotal(@Param("userId") Long userId, @Param("name") String name); + + GroupProfileVO selectGroupProfile( + @Param("groupId") Long groupId, + @Param("userId") Long userId); } diff --git a/code/link56/link56-backend/src/main/java/cn/link56/im/model/dto/GroupDTO.java b/code/link56/link56-backend/src/main/java/cn/link56/im/model/dto/GroupDTO.java index 2b90c247..176b1c1c 100644 --- a/code/link56/link56-backend/src/main/java/cn/link56/im/model/dto/GroupDTO.java +++ b/code/link56/link56-backend/src/main/java/cn/link56/im/model/dto/GroupDTO.java @@ -11,4 +11,7 @@ public class GroupDTO { private String content; private Integer type; private String tempId; + + private String senderAvatar; + private String senderName; } diff --git a/code/link56/link56-backend/src/main/java/cn/link56/im/model/vo/FriendProfileVO.java b/code/link56/link56-backend/src/main/java/cn/link56/im/model/vo/FriendProfileVO.java new file mode 100644 index 00000000..002e5278 --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/im/model/vo/FriendProfileVO.java @@ -0,0 +1,15 @@ +package cn.link56.im.model.vo; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class FriendProfileVO { + private Long friendId; + private String friendName; + private String friendAvatar; + private Integer unreadCount; +} diff --git a/code/link56/link56-backend/src/main/java/cn/link56/im/model/vo/GroupMessageVO.java b/code/link56/link56-backend/src/main/java/cn/link56/im/model/vo/GroupMessageVO.java new file mode 100644 index 00000000..111b02f6 --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/im/model/vo/GroupMessageVO.java @@ -0,0 +1,14 @@ +package cn.link56.im.model.vo; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +@EqualsAndHashCode(callSuper = true) +@Data +@AllArgsConstructor +@NoArgsConstructor +public class GroupMessageVO extends QueryGroupMessageVO { + private String tempId; +} diff --git a/code/link56/link56-backend/src/main/java/cn/link56/im/model/vo/GroupProfileVO.java b/code/link56/link56-backend/src/main/java/cn/link56/im/model/vo/GroupProfileVO.java new file mode 100644 index 00000000..7a55db4a --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/im/model/vo/GroupProfileVO.java @@ -0,0 +1,16 @@ +package cn.link56.im.model.vo; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class GroupProfileVO { + private Long groupId; + private String groupName; + private String groupAvatar; + + private Integer unreadCount; +} diff --git a/code/link56/link56-backend/src/main/java/cn/link56/im/model/vo/QueryFriendVO.java b/code/link56/link56-backend/src/main/java/cn/link56/im/model/vo/QueryFriendVO.java index 0a9b49a2..79a2d8c6 100644 --- a/code/link56/link56-backend/src/main/java/cn/link56/im/model/vo/QueryFriendVO.java +++ b/code/link56/link56-backend/src/main/java/cn/link56/im/model/vo/QueryFriendVO.java @@ -16,7 +16,6 @@ public class QueryFriendVO { private Integer unreadCount; - private Long lastMessageId; private Long lastMessageTime; private Integer lastMessageType; private String lastMessageContent; diff --git a/code/link56/link56-backend/src/main/java/cn/link56/im/model/vo/QueryGroupVO.java b/code/link56/link56-backend/src/main/java/cn/link56/im/model/vo/QueryGroupVO.java index ecc25497..91da9356 100644 --- a/code/link56/link56-backend/src/main/java/cn/link56/im/model/vo/QueryGroupVO.java +++ b/code/link56/link56-backend/src/main/java/cn/link56/im/model/vo/QueryGroupVO.java @@ -14,8 +14,9 @@ public class QueryGroupVO { private Integer unreadCount; - private Long lastMessageId; private Long lastMessageTime; private Integer lastMessageType; private String lastMessageContent; + + private String lastMessageSenderName; } diff --git a/code/link56/link56-backend/src/main/java/cn/link56/im/service/ImFriendService.java b/code/link56/link56-backend/src/main/java/cn/link56/im/service/ImFriendService.java index d3c18a24..f7b5c26b 100644 --- a/code/link56/link56-backend/src/main/java/cn/link56/im/service/ImFriendService.java +++ b/code/link56/link56-backend/src/main/java/cn/link56/im/service/ImFriendService.java @@ -6,6 +6,7 @@ import cn.link56.common.utils.snowflake.idgen.IdGenerator; import cn.link56.im.mapper.ImFriendMapper; import cn.link56.im.model.dto.QueryFriendDTO; import cn.link56.im.model.po.ImFriend; +import cn.link56.im.model.vo.FriendProfileVO; import cn.link56.im.model.vo.QueryFriendVO; import cn.link56.im.storage.SessionManager; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; @@ -103,4 +104,10 @@ public class ImFriendService extends ServiceImpl { public void decreaseUnreadCount(Long userId,Long friendId){ imFriendMapper.decreaseUnreadCount(userId, friendId); } + + public FriendProfileVO getFriendProfile(Long friendId) { + Long userId = UserContext.getUserId(); + + return imFriendMapper.selectFriendProfile(userId, friendId); + } } diff --git a/code/link56/link56-backend/src/main/java/cn/link56/im/service/ImGroupService.java b/code/link56/link56-backend/src/main/java/cn/link56/im/service/ImGroupService.java index 4432f717..7f5f4e0d 100644 --- a/code/link56/link56-backend/src/main/java/cn/link56/im/service/ImGroupService.java +++ b/code/link56/link56-backend/src/main/java/cn/link56/im/service/ImGroupService.java @@ -5,13 +5,17 @@ import cn.link56.common.context.UserContext; import cn.link56.im.mapper.ImGroupMapper; import cn.link56.im.model.dto.QueryGroupDTO; import cn.link56.im.model.po.ImGroup; +import cn.link56.im.model.vo.GroupProfileVO; import cn.link56.im.model.vo.QueryFriendVO; import cn.link56.im.model.vo.QueryGroupVO; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.List; +import java.util.Objects; @Service public class ImGroupService extends ServiceImpl{ @@ -32,4 +36,18 @@ public class ImGroupService extends ServiceImpl{ return new BasePageVO<>(total,queryFriendVOS); } + + + public void updateLaseMessageId(Long groupId,Long lastMessageId){ + LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); + updateWrapper.eq(ImGroup::getId,groupId) + .set(ImGroup::getLastMessageId,lastMessageId); + imGroupMapper.update(updateWrapper); + } + + public GroupProfileVO getGroupProfile(Long groupId) { + Long userId = UserContext.getUserId(); + + return imGroupMapper.selectGroupProfile(groupId,userId); + } } diff --git a/code/link56/link56-backend/src/main/java/cn/link56/im/service/ImGroupUserService.java b/code/link56/link56-backend/src/main/java/cn/link56/im/service/ImGroupUserService.java index 1738d6e5..43a682d0 100644 --- a/code/link56/link56-backend/src/main/java/cn/link56/im/service/ImGroupUserService.java +++ b/code/link56/link56-backend/src/main/java/cn/link56/im/service/ImGroupUserService.java @@ -21,7 +21,7 @@ public class ImGroupUserService extends ServiceImpl getOnlineGroupUsers(Long userId) { + public List getAllOnlineGroupUsers(Long userId) { // 1️⃣ 查询用户所在的群组ID列表 LambdaQueryWrapper groupWrapper = new LambdaQueryWrapper<>(); groupWrapper.eq(ImGroupUser::getUserId, userId) @@ -48,6 +48,19 @@ public class ImGroupUserService extends ServiceImpl getGroupOnlineUserIds(Long groupId) { + LambdaQueryWrapper groupWrapper = new LambdaQueryWrapper<>(); + groupWrapper.eq(ImGroupUser::getGroupId, groupId) + .select(ImGroupUser::getUserId); + List groupMembers = imGroupUserMapper.selectList(groupWrapper); + + return groupMembers.stream() + .map(ImGroupUser::getUserId) + .filter(SessionManager::isOnline) + .distinct() // 去重 + .collect(Collectors.toList()); + } + public void decreaseUnreadCount(Long groupId,Long userId){ LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); @@ -70,4 +83,15 @@ public class ImGroupUserService extends ServiceImpl updateWrapper = new LambdaUpdateWrapper<>(); + updateWrapper + .eq(ImGroupUser::getGroupId, groupId) + .ne(ImGroupUser::getUserId, excludeUserId) + .setSql("b_unread_count = b_unread_count + 1"); + + imGroupUserMapper.update(updateWrapper); + } + } diff --git a/code/link56/link56-backend/src/main/resources/cn/link56/im/mapper/ImFriendMapper.xml b/code/link56/link56-backend/src/main/resources/cn/link56/im/mapper/ImFriendMapper.xml index fbb10ab5..fc46bc13 100644 --- a/code/link56/link56-backend/src/main/resources/cn/link56/im/mapper/ImFriendMapper.xml +++ b/code/link56/link56-backend/src/main/resources/cn/link56/im/mapper/ImFriendMapper.xml @@ -9,13 +9,11 @@ SELECT * FROM ( SELECT - f.b_id AS id, f.b_friend_id AS friendId, u.b_name AS friendName, u.b_avatar AS friendAvatar, f.b_unread_count AS unreadCount, - m.b_id AS lastMessageId, - m.b_send_time AS lastMessageTime, + m.b_send_time AS lastMessageTime, m.b_type AS lastMessageType, m.b_content AS lastMessageContent, ROW_NUMBER() OVER ( @@ -63,4 +61,15 @@ WHERE b_user_id = #{userId} and b_friend_id = #{friendId} + + diff --git a/code/link56/link56-backend/src/main/resources/cn/link56/im/mapper/ImGroupMapper.xml b/code/link56/link56-backend/src/main/resources/cn/link56/im/mapper/ImGroupMapper.xml index d2dff046..75d61b8a 100644 --- a/code/link56/link56-backend/src/main/resources/cn/link56/im/mapper/ImGroupMapper.xml +++ b/code/link56/link56-backend/src/main/resources/cn/link56/im/mapper/ImGroupMapper.xml @@ -14,12 +14,10 @@ g.b_avatar AS groupAvatar, gu.b_unread_count AS unreadCount, - m.b_id AS lastMessageId, m.b_send_time AS lastMessageTime, m.b_type AS lastMessageType, m.b_content AS lastMessageContent, - u.b_id AS lastMessageSenderId, u.b_name AS lastMessageSenderName, ROW_NUMBER() OVER ( @@ -29,7 +27,7 @@ ) AS rn FROM b_im_group g INNER JOIN b_im_group_user gu ON g.b_id = gu.b_group_id - LEFT JOIN b_im_group_message m ON gu.b_last_message_id = m.b_id + LEFT JOIN b_im_group_message m ON g.b_last_message_id = m.b_id LEFT JOIN b_company_user u ON m.b_sender_id = u.b_id WHERE gu.b_user_id = #{userId} @@ -51,4 +49,18 @@ + + + diff --git a/code/link56/link56-frontend-new/src/request/api/im/index.ts b/code/link56/link56-frontend-new/src/request/api/im/index.ts index d68ec076..c388184f 100644 --- a/code/link56/link56-frontend-new/src/request/api/im/index.ts +++ b/code/link56/link56-frontend-new/src/request/api/im/index.ts @@ -1,5 +1,5 @@ -import type { CreateFriendParams, ImFriend, ImGroup, ImGroupMessage, ImGroupUser, ImPrivateMessage, QueryFriendParams, QueryGroupMessageParams, QueryGroupParams, QueryGroupUserParams, QueryPrivateMessageParams, UpdateGroupUnreadParams, UpdatePrivateUnreadParams } from '@/types/im' -import { httpPostJson } from '@/request/utils' +import type { CreateFriendParams, ImFriend, ImFriendProfile, ImGroup, ImGroupMessage, ImGroupProfile, ImGroupUser, ImPrivateMessage, QueryFriendParams, QueryGroupMessageParams, QueryGroupParams, QueryGroupUserParams, QueryPrivateMessageParams, UpdateGroupUnreadParams, UpdatePrivateUnreadParams } from '@/types/im' +import { httpGet, httpPostJson } from '@/request/utils' export function createFriendApi(params: CreateFriendParams) { return httpPostJson(`/im/friend/create`, params) @@ -31,3 +31,11 @@ export function updatePrivateUnreadApi(params: UpdatePrivateUnreadParams) { export function queryGroupUserApi(params: QueryGroupUserParams) { return httpPostJson('/im/group/user/query', params) } + +export function getFriendProfileApi(friendId: number) { + return httpGet(`/im/friend/${friendId}`) +} + +export function getGroupProfileApi(groupId: number) { + return httpGet(`/im/group/${groupId}`) +} diff --git a/code/link56/link56-frontend-new/src/store/websocket.ts b/code/link56/link56-frontend-new/src/store/websocket.ts index 023673ea..2b6ffac1 100644 --- a/code/link56/link56-frontend-new/src/store/websocket.ts +++ b/code/link56/link56-frontend-new/src/store/websocket.ts @@ -256,7 +256,7 @@ function createWebSocket(options?: Partial) { } function sendGroup(groupId: number, content: any) { - send(MessageType.GROUP, { groupId, content }) + send(MessageType.GROUP, content, groupId) } /* ================== 初始化 ================== */ diff --git a/code/link56/link56-frontend-new/src/types/im/index.ts b/code/link56/link56-frontend-new/src/types/im/index.ts index 682c9b44..6eb2dded 100644 --- a/code/link56/link56-frontend-new/src/types/im/index.ts +++ b/code/link56/link56-frontend-new/src/types/im/index.ts @@ -13,7 +13,6 @@ export enum MessageType { PRIVATE = 8, GROUP = 9, - FRIEND_UPDATE = 10, } export interface MessageRequest { @@ -48,15 +47,12 @@ export interface QueryFriendParams { } export interface ImFriend { - id: number - friendId: number friendName: string friendAvatar: string unreadCount: number - lastMessageId: number lastMessageTime: number lastMessageType: number lastMessageContent: string @@ -79,12 +75,10 @@ export interface ImGroup { unreadCount: number - lastMessageId: number lastMessageTime: number lastMessageType: number lastMessageContent: string - lastMessageSenderId: number lastMessageSenderName: string } @@ -152,3 +146,17 @@ export interface ImGroupUser { userName: string isOnline: boolean } + +export interface ImFriendProfile { + friendId: number + friendName: string + friendAvatar: string + unreadCount: number +} + +export interface ImGroupProfile { + groupId: number + groupName: string + groupAvatar: string + unreadCount: number +} diff --git a/code/link56/link56-frontend-new/src/views/im/components/GroupChatWindows.vue b/code/link56/link56-frontend-new/src/views/im/components/GroupChatWindows.vue index 6c41fae7..2ca22f75 100644 --- a/code/link56/link56-frontend-new/src/views/im/components/GroupChatWindows.vue +++ b/code/link56/link56-frontend-new/src/views/im/components/GroupChatWindows.vue @@ -7,6 +7,7 @@ import { useScrollWatcher } from '@/hooks/useScrollWatcher' import { queryGroupMessageApi, queryGroupUserApi } from '@/request/api/im' import { useUserStore } from '@/store/user' import { useWebSocket } from '@/store/websocket' +import { MessageType } from '@/types/im' import { formatImTime } from '@/utils/time' import { generateUUID } from '@/utils/uuid' @@ -22,6 +23,40 @@ onMounted(() => { websocket.onMessage(async (msg) => { const { from, type, data } = msg + + if (type === MessageType.GROUP) { + // 自己发的 + if (userId === data.senderId) { + messages.value.forEach((item) => { + if (item._tempId === data.tempId) { + item._status = 'sent' + item._tempId = '' + item.id = data.id + } + }) + } + else { + if (props.group.groupId === from) { + const newData: ImGroupMessage = { + id: data.id, + groupId: data.groupId, + senderId: data.senderId, + senderName: data.senderName, + senderAvatar: data.senderAvatar, + content: data.content, + type: data.type, + sendTime: data.sendTime, + } + + messages.value.push(newData) + // 更新未读数量,交给外部 + + // 等待 DOM 更新再滚动到底部 + await nextTick() + scrollToLatestMessage() + } + } + } }) }) @@ -52,6 +87,7 @@ watch( pagination.value = { pageSize: 10, loading: false, hasMore: true } loadMessages('down') + groupUsers.value = [] loadGroupUsers() } }, @@ -106,8 +142,6 @@ async function loadMessages(direction = 'down') { pagination.value.loading = true - const userId = userStore.user.id - let beforeTimestamp: number | null = null let beforeId: number | null = null @@ -197,39 +231,44 @@ async function sendMessage() { ElMessage.warning('发送消息不能为空') } - // const userId = userStore.user.id - // const _tempId = generateUUID() + const userId = userStore.user.id + const groupId = props.group.groupId + const _tempId = generateUUID() - // const localMessage: ImPrivateMessage = { - // id: -Date.now(), - // senderId: userId, - // receiverId: props.friend.friendId, - // content, - // type: 0, - // sendTime: Date.now(), - // _tempId, - // _status: 'sending', - // } + const localMessage: ImGroupMessage = { + id: -Date.now(), + groupId, + senderId: userId, + senderAvatar: userStore.user.avatar, + senderName: userStore.user.name, + content, + type: 0, + sendTime: Date.now(), + _tempId, + _status: 'sending', + } - // messages.value.push(localMessage) + messages.value.push(localMessage) - // // 清空输入框 - // if (chatInputRef.value) { - // chatInputRef.value.innerHTML = '' - // } + // 清空输入框 + if (chatInputRef.value) { + chatInputRef.value.innerHTML = '' + } - // const privateData: any = { - // content, - // type: 0, - // tempId: _tempId, - // } + const sentData: any = { + content, + type: 0, + tempId: _tempId, + senderAvatar: userStore.user.avatar, + senderName: userStore.user.name, + } - // websocket.sendPrivate(props.friend.friendId, privateData) + websocket.sendGroup(groupId, sentData) - // chatInputValue.value = '' - // // 等待 DOM 更新再滚动到底部 - // await nextTick() - // scrollToLatestMessage() + chatInputValue.value = '' + // 等待 DOM 更新再滚动到底部 + await nextTick() + scrollToLatestMessage() } diff --git a/code/link56/link56-frontend-new/src/views/im/index.vue b/code/link56/link56-frontend-new/src/views/im/index.vue index 632d0e06..51e1b1a3 100644 --- a/code/link56/link56-frontend-new/src/views/im/index.vue +++ b/code/link56/link56-frontend-new/src/views/im/index.vue @@ -3,7 +3,7 @@ import type { ImFriend, ImGroup } from '@/types/im' import { Search } from '@element-plus/icons-vue' import { computed, onMounted, ref } from 'vue' import { useRoute } from 'vue-router' -import { queryFriendApi, queryGroupApi, updateGroupUnreadApi, updatePrivateUnreadApi } from '@/request/api/im' +import { getFriendProfileApi, getGroupProfileApi, queryFriendApi, queryGroupApi, updateGroupUnreadApi, updatePrivateUnreadApi } from '@/request/api/im' import { useUserStore } from '@/store/user' import { useWebSocket } from '@/store/websocket' import { MessageType } from '@/types/im' @@ -78,7 +78,7 @@ onMounted(() => { loadGroups() loadFriends() - websocket.onMessage((msg) => { + websocket.onMessage(async (msg) => { const { type, from, data } = msg // 更新在线状态 @@ -94,55 +94,164 @@ onMounted(() => { else if (type === MessageType.OFFLINE) { updateOnlineStatus(false) } - else if (type === MessageType.FRIEND_UPDATE) { - const updatedFriend = data as ImFriend - - // 检查是否已经存在 - const exists = friendState.value.list.some( - item => item.friendId === updatedFriend.friendId, - ) - - // 如果不存在,放到第一个 - if (!exists) { - friendState.value.list.unshift(updatedFriend) - friendState.value.total += 1 - } - } else if (type === MessageType.PRIVATE) { const chatFriendId = currentFriendChat.value?.friendId const { senderId, receiverId, type: contentType, content, sendTime } = data - // 遍历好友列表更新最近消息和未读 - friendState.value.list.forEach((item) => { - if (item.friendId === senderId || item.friendId === receiverId) { - // 更新最近消息 - item.lastMessageTime = sendTime - item.lastMessageContent = content - item.lastMessageType = contentType + // 私聊中,对方一定是 sender 或 receiver + const targetFriendId = senderId === userId ? receiverId : senderId - // 如果是别人发的消息并且不是当前聊天好友,增加未读 - if (userId !== senderId && senderId !== chatFriendId && item.friendId === senderId) { - item.unreadCount = (item.unreadCount || 0) + 1 + // 先找本地好友 + const friend = friendState.value.list.find( + item => item.friendId === targetFriendId, + ) + + // 如果本地不存在,按需拉取 + if (!friend) { + try { + const res = await getFriendProfileApi(targetFriendId) + + const newFriend: ImFriend = { + friendId: res.data.friendId, + friendAvatar: res.data.friendAvatar, + friendName: res.data.friendName, + unreadCount: res.data.unreadCount, + + lastMessageTime: sendTime, + lastMessageContent: content, + lastMessageType: contentType, + + isOnline: true, } - } - }) - // 更新当前聊天对象最近消息 - if (currentFriendChat.value && (senderId === chatFriendId || receiverId === chatFriendId)) { + friendState.value.list.unshift(newFriend) + } + catch (e) { + console.error('getFriendProfileApi failed', e) + return + } + } + + if (!friend) { + return + } + + // 更新最近消息 + friend.lastMessageTime = sendTime + friend.lastMessageContent = content + friend.lastMessageType = contentType + + // 未读数处理 + if (userId !== senderId && targetFriendId !== chatFriendId) { + friend.unreadCount = (friend.unreadCount || 0) + 1 + } + + // 当前聊天窗口 + if (currentFriendChat.value && chatFriendId === targetFriendId) { currentFriendChat.value.lastMessageTime = sendTime currentFriendChat.value.lastMessageContent = content currentFriendChat.value.lastMessageType = contentType - // 重置未读数量,如果是别人发的消息且当前聊天窗口打开 if (userId !== senderId) { currentFriendChat.value.unreadCount = 0 - // 调用接口同步重置 updatePrivateUnreadApi({ userId, friendId: chatFriendId!, }) } } + + // 永远置顶 + const index = friendState.value.list.findIndex( + item => item.friendId === targetFriendId, + ) + if (index > -1) { + const [f] = friendState.value.list.splice(index, 1) + friendState.value.list.unshift(f) + } + } + else if (type === MessageType.GROUP) { + const { + groupId, + senderId, + senderName, + type: contentType, + content, + sendTime, + } = data + + const currentGroupId = currentGroupChat.value?.groupId + + // 1️⃣ 查找本地群 + const group = groupState.value.list.find( + item => item.groupId === groupId, + ) + + // 2️⃣ 本地没有 → 懒加载群 Profile + if (!group) { + try { + const res = await getGroupProfileApi(groupId) + + const newGroup: ImGroup = { + groupId: res.data.groupId, + groupAvatar: res.data.groupAvatar, + groupName: res.data.groupName, + unreadCount: res.data.unreadCount, + + lastMessageTime: sendTime, + lastMessageContent: content, + lastMessageType: contentType, + + lastMessageSenderName: senderName, + } + + groupState.value.list.unshift(newGroup) + } + catch (e) { + console.error('getGroupProfileApi failed', e) + return + } + } + + if (!group) { + return + } + // 3️⃣ 更新最近消息 + group.lastMessageTime = sendTime + group.lastMessageContent = content + group.lastMessageType = contentType + group.lastMessageSenderName = senderName + + // 4️⃣ 未读处理(不是自己 & 当前群未打开) + if (senderId !== userId && groupId !== currentGroupId) { + group.unreadCount = (group.unreadCount || 0) + 1 + } + + // 5️⃣ 当前群窗口处理 + if (currentGroupChat.value && currentGroupId === groupId) { + currentGroupChat.value.lastMessageTime = sendTime + currentGroupChat.value.lastMessageContent = content + currentGroupChat.value.lastMessageType = contentType + currentGroupChat.value.lastMessageSenderName = senderName + + if (senderId !== userId) { + currentGroupChat.value.unreadCount = 0 + + updateGroupUnreadApi({ + userId, + groupId, + }) + } + } + + // 6️⃣ 永远置顶 + const index = groupState.value.list.findIndex( + item => item.groupId === groupId, + ) + if (index > -1) { + const [g] = groupState.value.list.splice(index, 1) + groupState.value.list.unshift(g) + } } }) }) @@ -342,7 +451,7 @@ function onGroupChat(group: ImGroup) {
-
- {{ item.lastMessageSenderName }}:{{ item.lastMessageContent }} +
+ + {{ item.lastMessageContent }}