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 938e1fbb..81fb7f00 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 @@ -3,19 +3,11 @@ package cn.link56.im.controller; import cn.link56.common.BasePageVO; import cn.link56.common.Result; import cn.link56.common.context.UserContext; -import cn.link56.im.model.dto.CreateFriendDTO; -import cn.link56.im.model.dto.QueryFriendDTO; -import cn.link56.im.model.dto.QueryGroupDTO; -import cn.link56.im.model.vo.QueryFriendVO; -import cn.link56.im.model.vo.QueryGroupVO; -import cn.link56.im.service.ImFriendService; -import cn.link56.im.service.ImGroupService; -import org.apache.commons.lang3.ObjectUtils; +import cn.link56.im.model.dto.*; +import cn.link56.im.model.vo.*; +import cn.link56.im.service.*; import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import java.util.List; @@ -28,6 +20,12 @@ public class ImController { private ImFriendService imFriendService; @Resource private ImGroupService imGroupService; + @Resource + private ImPrivateMessageService imPrivateMessageService; + @Resource + private ImGroupUserService imGroupUserService; + @Resource + private ImGroupMessageService imGroupMessageService; @PostMapping("/friend/create") public Result createFriend(@RequestBody @Validated CreateFriendDTO dto){ @@ -52,4 +50,43 @@ public class ImController { BasePageVO basePageVO = imGroupService.queryGroup(dto); return Result.success(basePageVO); } + + @PostMapping("/friend/message/query") + public Result> queryPrivateMessage(@RequestBody QueryPrivateMessageDTO dto){ + List list = imPrivateMessageService.queryPrivateMessages(dto); + return Result.success(list); + } + + @PostMapping("/group/message/query") + public Result> queryGroupMessage(@RequestBody QueryGroupMessageDTO dto){ + List list = imGroupMessageService.queryGroupMessages(dto); + return Result.success(list); + } + + @PostMapping("/group/user/query") + public Result> queryGroupUser(@RequestBody QueryGroupUserDTO dto){ + List list = imGroupUserService.queryGroupUsers(dto); + return Result.success(list); + } + + @PostMapping("/private/update/unread") + public Result updatePrivateUnread(@RequestBody UpdatePrivateUnreadDTO dto){ + Long userId = dto.getUserId(); + Long friendId = dto.getFriendId(); + + imFriendService.decreaseUnreadCount(userId,friendId); + + return Result.success(); + } + + @PostMapping("/group/update/unread") + public Result updateGroup(@RequestBody UpdateGroupUnreadDTO dto){ + Long userId = dto.getUserId(); + Long groupId = dto.getGroupId(); + + imGroupUserService.decreaseUnreadCount(groupId,userId); + + return Result.success(); + } + } 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 1381bcba..d60723e7 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,7 +13,8 @@ public enum MessageTypeEnums { OFFLINE(6), GROUP_OFFLINE(7), PRIVATE(8), - GROUP(9); + GROUP(9), + FRIEND_UPDATE(10); 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 156f401b..fbaf856d 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 @@ -2,12 +2,22 @@ package cn.link56.im.handler; import cn.link56.auth.token.model.TokenPayload; import cn.link56.auth.token.service.AuthTokenService; +import cn.link56.common.utils.TimeUtils; +import cn.link56.common.utils.snowflake.idgen.IdGenerator; +import cn.link56.company.model.po.CompanyUser; +import cn.link56.company.service.CompanyUserService; import cn.link56.im.enums.MessageTypeEnums; import cn.link56.im.message.MessageRequest; 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.ImPrivateMessage; +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.storage.SessionManager; import cn.link56.im.utils.MessageDataConverter; import cn.link56.im.utils.MessageWriter; @@ -26,6 +36,10 @@ public class MessageHandler { private ImFriendService imFriendService; @Resource private ImGroupUserService imGroupUserService; + @Resource + private ImPrivateMessageService imPrivateMessageService; + @Resource + private CompanyUserService companyUserService; // 处理消息 public void handleMessage(Channel channel, MessageRequest request) { @@ -56,7 +70,6 @@ public class MessageHandler { } private void handleLogin(Channel channel, MessageRequest request) { - System.out.println("===LOGIN==="); LoginDTO dto = MessageDataConverter.convert(request.getData(), LoginDTO.class); TokenPayload tokenPayload = authTokenService.parsePayload(dto.getToken()); @@ -77,8 +90,6 @@ public class MessageHandler { } private void handleLogout(Channel channel, MessageRequest request) { - System.out.println("===LOGOUT==="); - Long userId = SessionManager.getUserId(channel); if (ObjectUtils.isNotEmpty(userId)){ handleOffline(channel , userId); @@ -110,9 +121,78 @@ public class MessageHandler { // 私聊逻辑 private void handlePrivate(Channel channel, MessageRequest request) { + Long from = request.getFrom(); + Long to = request.getTo(); + + PrivateDTO dto = MessageDataConverter.convert(request.getData(), PrivateDTO.class); + String content = dto.getContent(); + Integer type = dto.getType(); + String tempId = dto.getTempId(); + + // 好友关系建立 + 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(); + + ImPrivateMessage imPrivateMessage = new ImPrivateMessage(); + imPrivateMessage.setId(privateMessageID); + imPrivateMessage.setContent(content); + imPrivateMessage.setType(type); + imPrivateMessage.setSenderId(from); + imPrivateMessage.setReceiverId(to); + imPrivateMessage.setSendTime(sendTime); + + imPrivateMessageService.save(imPrivateMessage); + + // 更新状态表 + imFriendService.updateLastMessageId(from,to,privateMessageID); + imFriendService.increaseUnreadCount(to,from,privateMessageID); + + // 构建回复消息 + PrivateMessageVO privateMessageVO = new PrivateMessageVO(); + // 通知发送人 + privateMessageVO.setTempId(tempId); + privateMessageVO.setId(privateMessageID); + privateMessageVO.setSenderId(from); + privateMessageVO.setReceiverId(to); + privateMessageVO.setContent(content); + privateMessageVO.setType(type); + privateMessageVO.setSendTime(sendTime); + + MessageWriter.writeToUser(from,MessageResponse.success(MessageTypeEnums.PRIVATE,from).setData(privateMessageVO)); + + // 通知接受人 + if (SessionManager.isOnline(to)){ + MessageWriter.writeToUser(to,MessageResponse.success(MessageTypeEnums.PRIVATE,from).setData(privateMessageVO)); + } } // 群聊逻辑 private void handleGroup(Channel channel, MessageRequest request) { + Long from = request.getFrom(); + Long to = request.getTo(); + + GroupDTO dto = MessageDataConverter.convert(request.getData(), GroupDTO.class); + String content = dto.getContent(); + Integer type = dto.getType(); + String tempId = dto.getTempId(); } } 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 5bbd22a1..45290105 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 @@ -22,5 +22,16 @@ public interface ImFriendMapper extends BaseMapper { Long selectFriendTotal( @Param("userId") Long userId, @Param("name") String name - ); + ); + + void incrementUnreadAndUpdateLastMessage( + @Param("userId") Long userId, + @Param("friendId") Long friendId, + @Param("lastMessageId") Long lastMessageId + ); + + void decreaseUnreadCount( + @Param("userId") Long userId, + @Param("friendId") Long friendId + ); } diff --git a/code/link56/link56-backend/src/main/java/cn/link56/im/mapper/ImGroupMessageMapper.java b/code/link56/link56-backend/src/main/java/cn/link56/im/mapper/ImGroupMessageMapper.java index 6e09b7a5..e61730a7 100644 --- a/code/link56/link56-backend/src/main/java/cn/link56/im/mapper/ImGroupMessageMapper.java +++ b/code/link56/link56-backend/src/main/java/cn/link56/im/mapper/ImGroupMessageMapper.java @@ -1,9 +1,19 @@ package cn.link56.im.mapper; import cn.link56.im.model.po.ImGroupMessage; +import cn.link56.im.model.vo.QueryGroupMessageVO; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; @Mapper public interface ImGroupMessageMapper extends BaseMapper { + List selectGroupMessages( + @Param("groupId") Long groupId, + @Param("pageSize") Integer pageSize, + @Param("beforeId") Long beforeId, + @Param("beforeTimestamp") Long beforeTimestamp + ); } diff --git a/code/link56/link56-backend/src/main/java/cn/link56/im/mapper/ImGroupUserMapper.java b/code/link56/link56-backend/src/main/java/cn/link56/im/mapper/ImGroupUserMapper.java index 80fc7e6e..889d0bd4 100644 --- a/code/link56/link56-backend/src/main/java/cn/link56/im/mapper/ImGroupUserMapper.java +++ b/code/link56/link56-backend/src/main/java/cn/link56/im/mapper/ImGroupUserMapper.java @@ -1,9 +1,17 @@ package cn.link56.im.mapper; import cn.link56.im.model.po.ImGroupUser; +import cn.link56.im.model.vo.QueryGroupUserVO; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; @Mapper public interface ImGroupUserMapper extends BaseMapper { + List selectGroupUsers( + @Param("groupId") Long groupId, + @Param("userName") String userName + ); } diff --git a/code/link56/link56-backend/src/main/java/cn/link56/im/mapper/ImPrivateMessageMapper.java b/code/link56/link56-backend/src/main/java/cn/link56/im/mapper/ImPrivateMessageMapper.java index aeb64386..eb5f2db1 100644 --- a/code/link56/link56-backend/src/main/java/cn/link56/im/mapper/ImPrivateMessageMapper.java +++ b/code/link56/link56-backend/src/main/java/cn/link56/im/mapper/ImPrivateMessageMapper.java @@ -1,9 +1,20 @@ package cn.link56.im.mapper; import cn.link56.im.model.po.ImPrivateMessage; +import cn.link56.im.model.vo.QueryPrivateMessageVO; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; @Mapper public interface ImPrivateMessageMapper extends BaseMapper { + List selectPrivateMessages( + @Param("userId") Long userId, + @Param("friendId") Long friendId, + @Param("beforeId") Long beforeId, + @Param("beforeTimestamp") Long beforeTimestamp, + @Param("pageSize") Integer pageSize + ); } diff --git a/code/link56/link56-backend/src/main/java/cn/link56/im/message/MessageRequest.java b/code/link56/link56-backend/src/main/java/cn/link56/im/message/MessageRequest.java index cff39ea4..900fa90c 100644 --- a/code/link56/link56-backend/src/main/java/cn/link56/im/message/MessageRequest.java +++ b/code/link56/link56-backend/src/main/java/cn/link56/im/message/MessageRequest.java @@ -12,7 +12,7 @@ import lombok.ToString; public class MessageRequest { private Integer type; private Long from; - private Object to; + private Long to; private Long timestamp; private Object data; } 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 new file mode 100644 index 00000000..2b90c247 --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/im/model/dto/GroupDTO.java @@ -0,0 +1,14 @@ +package cn.link56.im.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class GroupDTO { + private String content; + private Integer type; + private String tempId; +} diff --git a/code/link56/link56-backend/src/main/java/cn/link56/im/model/dto/PrivateDTO.java b/code/link56/link56-backend/src/main/java/cn/link56/im/model/dto/PrivateDTO.java new file mode 100644 index 00000000..b6014bec --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/im/model/dto/PrivateDTO.java @@ -0,0 +1,14 @@ +package cn.link56.im.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class PrivateDTO { + private String content; + private Integer type; + private String tempId; +} diff --git a/code/link56/link56-backend/src/main/java/cn/link56/im/model/dto/QueryGroupMessageDTO.java b/code/link56/link56-backend/src/main/java/cn/link56/im/model/dto/QueryGroupMessageDTO.java new file mode 100644 index 00000000..9443dc40 --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/im/model/dto/QueryGroupMessageDTO.java @@ -0,0 +1,15 @@ +package cn.link56.im.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class QueryGroupMessageDTO { + private Long groupId; + private Long beforeTimestamp; + private Long beforeId; + private Integer pageSize; +} diff --git a/code/link56/link56-backend/src/main/java/cn/link56/im/model/dto/QueryGroupUserDTO.java b/code/link56/link56-backend/src/main/java/cn/link56/im/model/dto/QueryGroupUserDTO.java new file mode 100644 index 00000000..8667146c --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/im/model/dto/QueryGroupUserDTO.java @@ -0,0 +1,13 @@ +package cn.link56.im.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class QueryGroupUserDTO { + private Long groupId; + private String userName; +} diff --git a/code/link56/link56-backend/src/main/java/cn/link56/im/model/dto/QueryPrivateMessageDTO.java b/code/link56/link56-backend/src/main/java/cn/link56/im/model/dto/QueryPrivateMessageDTO.java new file mode 100644 index 00000000..9c48d707 --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/im/model/dto/QueryPrivateMessageDTO.java @@ -0,0 +1,16 @@ +package cn.link56.im.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class QueryPrivateMessageDTO { + private Long userId; + private Long friendId; + private Long beforeTimestamp; + private Long beforeId; + private Integer pageSize; +} diff --git a/code/link56/link56-backend/src/main/java/cn/link56/im/model/dto/UpdateGroupUnreadDTO.java b/code/link56/link56-backend/src/main/java/cn/link56/im/model/dto/UpdateGroupUnreadDTO.java new file mode 100644 index 00000000..31e730fd --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/im/model/dto/UpdateGroupUnreadDTO.java @@ -0,0 +1,13 @@ +package cn.link56.im.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class UpdateGroupUnreadDTO { + private Long groupId; + private Long userId; +} diff --git a/code/link56/link56-backend/src/main/java/cn/link56/im/model/dto/UpdatePrivateUnreadDTO.java b/code/link56/link56-backend/src/main/java/cn/link56/im/model/dto/UpdatePrivateUnreadDTO.java new file mode 100644 index 00000000..53c78dc8 --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/im/model/dto/UpdatePrivateUnreadDTO.java @@ -0,0 +1,13 @@ +package cn.link56.im.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class UpdatePrivateUnreadDTO { + private Long userId; + private Long friendId; +} diff --git a/code/link56/link56-backend/src/main/java/cn/link56/im/model/po/ImPrivateMessage.java b/code/link56/link56-backend/src/main/java/cn/link56/im/model/po/ImPrivateMessage.java index 1771d351..5c99ea52 100644 --- a/code/link56/link56-backend/src/main/java/cn/link56/im/model/po/ImPrivateMessage.java +++ b/code/link56/link56-backend/src/main/java/cn/link56/im/model/po/ImPrivateMessage.java @@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; diff --git a/code/link56/link56-backend/src/main/java/cn/link56/im/model/vo/PrivateMessageVO.java b/code/link56/link56-backend/src/main/java/cn/link56/im/model/vo/PrivateMessageVO.java new file mode 100644 index 00000000..f745a680 --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/im/model/vo/PrivateMessageVO.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 PrivateMessageVO extends QueryPrivateMessageVO { + private String tempId; +} 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 b82dfee7..0a9b49a2 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 @@ -17,7 +17,7 @@ public class QueryFriendVO { private Integer unreadCount; private Long lastMessageId; - private Long laseMessageTime; + private Long lastMessageTime; private Integer lastMessageType; private String lastMessageContent; diff --git a/code/link56/link56-backend/src/main/java/cn/link56/im/model/vo/QueryGroupMessageVO.java b/code/link56/link56-backend/src/main/java/cn/link56/im/model/vo/QueryGroupMessageVO.java new file mode 100644 index 00000000..6eb861d5 --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/im/model/vo/QueryGroupMessageVO.java @@ -0,0 +1,22 @@ +package cn.link56.im.model.vo; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class QueryGroupMessageVO { + private Long id; + private Long groupId; + + private Long senderId; + private String senderAvatar; + private String senderName; + + private String content; + private Integer type; + + private Long sendTime; +} diff --git a/code/link56/link56-backend/src/main/java/cn/link56/im/model/vo/QueryGroupUserVO.java b/code/link56/link56-backend/src/main/java/cn/link56/im/model/vo/QueryGroupUserVO.java new file mode 100644 index 00000000..fea7cb9f --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/im/model/vo/QueryGroupUserVO.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 QueryGroupUserVO { + private Long groupId; + private Long userId; + private String userAvatar; + private String userName; + private Boolean isOnline; +} 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 bdde8b4f..ecc25497 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 @@ -15,7 +15,7 @@ public class QueryGroupVO { private Integer unreadCount; private Long lastMessageId; - private Long laseMessageTime; + private Long lastMessageTime; private Integer lastMessageType; private String lastMessageContent; } diff --git a/code/link56/link56-backend/src/main/java/cn/link56/im/model/vo/QueryPrivateMessageVO.java b/code/link56/link56-backend/src/main/java/cn/link56/im/model/vo/QueryPrivateMessageVO.java new file mode 100644 index 00000000..eacc3848 --- /dev/null +++ b/code/link56/link56-backend/src/main/java/cn/link56/im/model/vo/QueryPrivateMessageVO.java @@ -0,0 +1,20 @@ +package cn.link56.im.model.vo; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class QueryPrivateMessageVO { + private Long id; + + private Long senderId; + private Long receiverId; + + private String content; + private Integer type; + + private Long sendTime; +} 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 53e6da2b..d3c18a24 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 @@ -9,6 +9,7 @@ import cn.link56.im.model.po.ImFriend; import cn.link56.im.model.vo.QueryFriendVO; import cn.link56.im.storage.SessionManager; 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.apache.ibatis.annotations.Mapper; import org.springframework.stereotype.Service; @@ -33,7 +34,6 @@ public class ImFriendService extends ServiceImpl { return; } - // 不存在则插入 ImFriend imFriend = new ImFriend(); @@ -87,4 +87,20 @@ public class ImFriendService extends ServiceImpl { } + public void updateLastMessageId(Long userId,Long friendId,Long lastMessageId){ + LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); + updateWrapper.eq(ImFriend::getUserId,userId) + .eq(ImFriend::getFriendId,friendId) + .set(ImFriend::getLastMessageId,lastMessageId); + + imFriendMapper.update(updateWrapper); + } + + public void increaseUnreadCount(Long userId,Long friendId,Long lastMessageId){ + imFriendMapper.incrementUnreadAndUpdateLastMessage(userId, friendId, lastMessageId); + } + + public void decreaseUnreadCount(Long userId,Long friendId){ + imFriendMapper.decreaseUnreadCount(userId, friendId); + } } diff --git a/code/link56/link56-backend/src/main/java/cn/link56/im/service/ImGroupMessageService.java b/code/link56/link56-backend/src/main/java/cn/link56/im/service/ImGroupMessageService.java index f5de34a0..2143de8d 100644 --- a/code/link56/link56-backend/src/main/java/cn/link56/im/service/ImGroupMessageService.java +++ b/code/link56/link56-backend/src/main/java/cn/link56/im/service/ImGroupMessageService.java @@ -1,11 +1,27 @@ package cn.link56.im.service; import cn.link56.im.mapper.ImGroupMessageMapper; +import cn.link56.im.model.dto.QueryGroupMessageDTO; import cn.link56.im.model.po.ImGroupMessage; +import cn.link56.im.model.vo.QueryGroupMessageVO; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.stereotype.Service; +import javax.annotation.Resource; +import java.util.List; + @Service public class ImGroupMessageService extends ServiceImpl { + @Resource + private ImGroupMessageMapper imGroupMessageMapper; + public List queryGroupMessages(QueryGroupMessageDTO dto) { + Long groupId = dto.getGroupId(); + Integer pageSize = dto.getPageSize(); + + Long beforeId = dto.getBeforeId(); + Long beforeTimestamp = dto.getBeforeTimestamp(); + + return imGroupMessageMapper.selectGroupMessages(groupId,pageSize,beforeId,beforeTimestamp); + } } 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 2294712e..1738d6e5 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 @@ -1,9 +1,12 @@ package cn.link56.im.service; import cn.link56.im.mapper.ImGroupUserMapper; +import cn.link56.im.model.dto.QueryGroupUserDTO; import cn.link56.im.model.po.ImGroupUser; +import cn.link56.im.model.vo.QueryGroupUserVO; import cn.link56.im.storage.SessionManager; 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; @@ -44,4 +47,27 @@ public class ImGroupUserService extends ServiceImpl updateWrapper = new LambdaUpdateWrapper<>(); + + updateWrapper.eq(ImGroupUser::getGroupId,groupId) + .eq(ImGroupUser::getUserId,userId) + .set(ImGroupUser::getUnreadCount,0); + + imGroupUserMapper.update(updateWrapper); + } + + public List queryGroupUsers(QueryGroupUserDTO dto) { + Long groupId = dto.getGroupId(); + String userName = dto.getUserName(); + + List queryGroupUserVOS = imGroupUserMapper.selectGroupUsers(groupId, userName); + + queryGroupUserVOS.forEach(item -> { + item.setIsOnline(SessionManager.isOnline(item.getUserId())); + }); + + return queryGroupUserVOS; + } } diff --git a/code/link56/link56-backend/src/main/java/cn/link56/im/service/ImPrivateMessageService.java b/code/link56/link56-backend/src/main/java/cn/link56/im/service/ImPrivateMessageService.java index dab3a364..0129e652 100644 --- a/code/link56/link56-backend/src/main/java/cn/link56/im/service/ImPrivateMessageService.java +++ b/code/link56/link56-backend/src/main/java/cn/link56/im/service/ImPrivateMessageService.java @@ -1,10 +1,28 @@ package cn.link56.im.service; import cn.link56.im.mapper.ImPrivateMessageMapper; +import cn.link56.im.model.dto.QueryPrivateMessageDTO; import cn.link56.im.model.po.ImPrivateMessage; +import cn.link56.im.model.vo.QueryPrivateMessageVO; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.stereotype.Service; +import javax.annotation.Resource; +import java.util.List; + @Service public class ImPrivateMessageService extends ServiceImpl { + @Resource + private ImPrivateMessageMapper imPrivateMessageMapper; + + public List queryPrivateMessages(QueryPrivateMessageDTO dto) { + Long userId = dto.getUserId(); + Long friendId = dto.getFriendId(); + Long beforeId = dto.getBeforeId(); + Long beforeTimestamp = dto.getBeforeTimestamp(); + + Integer pageSize = dto.getPageSize(); + + return imPrivateMessageMapper.selectPrivateMessages(userId,friendId,beforeId,beforeTimestamp,pageSize); + } } 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 d5f146e3..fbb10ab5 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 @@ -46,4 +46,21 @@ + + UPDATE b_im_friend + SET + b_last_message_id = #{lastMessageId}, + b_unread_count = b_unread_count + 1, + b_update_time = GETDATE() + WHERE b_user_id = #{userId} and b_friend_id = #{friendId} + + + + UPDATE b_im_friend + SET + b_unread_count = 0, + b_update_time = GETDATE() + 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 ec61e3ca..d2dff046 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 @@ -8,28 +8,25 @@ + + SELECT TOP (#{pageSize}) + gm.b_id AS id, + gm.b_group_id AS groupId, + gm.b_sender_id AS senderId, + u.b_name AS senderName, + u.b_avatar AS senderAvatar, + gm.b_content AS content, + gm.b_type AS type, + gm.b_send_time AS sendTime + FROM b_im_group_message gm + LEFT JOIN b_company_user u ON gm.b_sender_id = u.b_id + WHERE gm.b_group_id = #{groupId} + + AND ( + gm.b_send_time < #{beforeTimestamp} + OR (gm.b_send_time = #{beforeTimestamp} AND gm.b_id < #{beforeId}) + ) + + ORDER BY gm.b_send_time DESC, gm.b_id DESC + + + diff --git a/code/link56/link56-backend/src/main/resources/cn/link56/im/mapper/ImGroupUserMapper.xml b/code/link56/link56-backend/src/main/resources/cn/link56/im/mapper/ImGroupUserMapper.xml new file mode 100644 index 00000000..4c85b6f5 --- /dev/null +++ b/code/link56/link56-backend/src/main/resources/cn/link56/im/mapper/ImGroupUserMapper.xml @@ -0,0 +1,27 @@ + + + + + + + + + diff --git a/code/link56/link56-backend/src/main/resources/cn/link56/im/mapper/ImPrivateMessageMapper.xml b/code/link56/link56-backend/src/main/resources/cn/link56/im/mapper/ImPrivateMessageMapper.xml new file mode 100644 index 00000000..b120616a --- /dev/null +++ b/code/link56/link56-backend/src/main/resources/cn/link56/im/mapper/ImPrivateMessageMapper.xml @@ -0,0 +1,32 @@ + + + + + + + + diff --git a/code/link56/link56-backend/src/main/resources/cn/link56/post/mapper/PostStatMapper.xml b/code/link56/link56-backend/src/main/resources/cn/link56/post/mapper/PostStatMapper.xml index 2bcb41e6..1f1df167 100644 --- a/code/link56/link56-backend/src/main/resources/cn/link56/post/mapper/PostStatMapper.xml +++ b/code/link56/link56-backend/src/main/resources/cn/link56/post/mapper/PostStatMapper.xml @@ -23,11 +23,12 @@ UPDATE b_post_stat SET - b_comment_count = b_comment_count - #{count}, + b_comment_count = CASE + WHEN b_comment_count - #{count} < 0 THEN 0 + ELSE b_comment_count - #{count} + END, b_update_time = GETDATE() WHERE b_post_id = #{postId} - AND b_comment_count >= #{count} - - \ No newline at end of file + diff --git a/code/link56/link56-frontend-new/eslint.config.js b/code/link56/link56-frontend-new/eslint.config.js index 5def99ee..b7c3d02c 100644 --- a/code/link56/link56-frontend-new/eslint.config.js +++ b/code/link56/link56-frontend-new/eslint.config.js @@ -15,5 +15,6 @@ export default antfu({ 'node/prefer-global/process': 'off', '@typescript-eslint/no-require-imports': 'off', 'no-useless-catch': 'off', + '@typescript-eslint/no-use-before-define': 'off', }, }) diff --git a/code/link56/link56-frontend-new/src/hooks/useScrollWatcher.ts b/code/link56/link56-frontend-new/src/hooks/useScrollWatcher.ts new file mode 100644 index 00000000..b88562bd --- /dev/null +++ b/code/link56/link56-frontend-new/src/hooks/useScrollWatcher.ts @@ -0,0 +1,106 @@ +import type { Ref } from 'vue' +import { onUnmounted, watch } from 'vue' + +interface UseScrollWatcherOptions { + target: Ref + thresholdPercent?: number + delay?: number + onReachTop?: () => void + onReachBottom?: () => void +} + +export function useScrollWatcher({ + target, + thresholdPercent = 0.05, + delay = 0, + onReachTop, + onReachBottom, +}: UseScrollWatcherOptions) { + if (!target) + throw new Error('target 必须传入一个 ref') + + let lastScrollTop = 0 + let timeout: ReturnType | null = null + const topTriggered = { value: false } + const bottomTriggered = { value: false } + + const handleScroll = (e: Event) => { + const el = e.target as HTMLElement + const scrollTop = el.scrollTop + const scrollHeight = el.scrollHeight + const clientHeight = el.clientHeight + const scrollRange = scrollHeight - clientHeight + if (scrollRange <= 0) + return + + const isScrollingUp = scrollTop < lastScrollTop + const isScrollingDown = scrollTop > lastScrollTop + lastScrollTop = scrollTop + + const topThreshold = scrollRange * thresholdPercent + const bottomThreshold = scrollRange * thresholdPercent + + if (timeout) + clearTimeout(timeout) + + timeout = setTimeout(() => { + if (isScrollingUp && scrollTop <= topThreshold && !topTriggered.value) { + onReachTop?.() + topTriggered.value = true + } + else if (scrollTop > topThreshold) { + topTriggered.value = false + } + + if (isScrollingDown && scrollHeight - scrollTop - clientHeight <= bottomThreshold && !bottomTriggered.value) { + onReachBottom?.() + bottomTriggered.value = true + } + else if (scrollHeight - scrollTop - clientHeight > bottomThreshold) { + bottomTriggered.value = false + } + }, delay) + } + + const bindScroll = (el: HTMLElement | null) => { + if (!el) + return + el.addEventListener('scroll', handleScroll) + lastScrollTop = el.scrollTop + } + + const unbindScroll = (el: HTMLElement | null) => { + if (!el) + return + el.removeEventListener('scroll', handleScroll) + } + + // 监听 ref + watch(target, (el, oldEl) => { + if (oldEl) + unbindScroll(oldEl) + if (el) + bindScroll(el) + }) + + onUnmounted(() => { + if (target.value) + unbindScroll(target.value) + if (timeout) + clearTimeout(timeout) + }) + + const scrollToTop = () => { + if (target.value) + target.value.scrollTop = 0 + } + + const scrollToBottom = () => { + if (target.value) { + const el = target.value + el.scrollTop = el.scrollHeight - el.clientHeight + } + } + + return { scrollToTop, scrollToBottom } +} 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 5ff3fe65..d68ec076 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,4 +1,4 @@ -import type { CreateFriendParams, ImFriend, ImGroup, QueryFriendParams, QueryGroupParams } from '@/types/im' +import type { CreateFriendParams, ImFriend, ImGroup, ImGroupMessage, ImGroupUser, ImPrivateMessage, QueryFriendParams, QueryGroupMessageParams, QueryGroupParams, QueryGroupUserParams, QueryPrivateMessageParams, UpdateGroupUnreadParams, UpdatePrivateUnreadParams } from '@/types/im' import { httpPostJson } from '@/request/utils' export function createFriendApi(params: CreateFriendParams) { @@ -12,3 +12,22 @@ export function queryFriendApi(params: QueryFriendParams) { export function queryGroupApi(params: QueryGroupParams) { return httpPostJson>('/im/group/query', params) } + +export function queryPrivateMessageApi(params: QueryPrivateMessageParams) { + return httpPostJson('/im/friend/message/query', params) +} +export function queryGroupMessageApi(params: QueryGroupMessageParams) { + return httpPostJson('/im/group/message/query', params) +} + +export function updateGroupUnreadApi(params: UpdateGroupUnreadParams) { + return httpPostJson('/im/group/update/unread', params) +} + +export function updatePrivateUnreadApi(params: UpdatePrivateUnreadParams) { + return httpPostJson('/im/private/update/unread', params) +} + +export function queryGroupUserApi(params: QueryGroupUserParams) { + return httpPostJson('/im/group/user/query', params) +} diff --git a/code/link56/link56-frontend-new/src/store/websocket.ts b/code/link56/link56-frontend-new/src/store/websocket.ts index 1c143e00..023673ea 100644 --- a/code/link56/link56-frontend-new/src/store/websocket.ts +++ b/code/link56/link56-frontend-new/src/store/websocket.ts @@ -190,6 +190,9 @@ function createWebSocket(options?: Partial) { data: data ?? null, } + console.log('===发送消息===') + console.log(msg) + ws.send(JSON.stringify(msg)) } @@ -248,11 +251,11 @@ function createWebSocket(options?: Partial) { /* ================== 业务 API ================== */ - function sendPrivate(toUserId: number, content: string) { - send(MessageType.PRIVATE, { content }, toUserId) + function sendPrivate(toUserId: number, content: any) { + send(MessageType.PRIVATE, content, toUserId) } - function sendGroup(groupId: number, content: string) { + function sendGroup(groupId: number, content: any) { send(MessageType.GROUP, { groupId, content }) } 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 79209afc..682c9b44 100644 --- a/code/link56/link56-frontend-new/src/types/im/index.ts +++ b/code/link56/link56-frontend-new/src/types/im/index.ts @@ -13,6 +13,7 @@ export enum MessageType { PRIVATE = 8, GROUP = 9, + FRIEND_UPDATE = 10, } export interface MessageRequest { @@ -82,4 +83,72 @@ export interface ImGroup { lastMessageTime: number lastMessageType: number lastMessageContent: string + + lastMessageSenderId: number + lastMessageSenderName: string +} + +export interface QueryPrivateMessageParams { + userId: number + friendId: number + beforeId: number | null + beforeTimestamp: number | null + pageSize: number +} + +export interface ImPrivateMessage { + id: number + senderId: number + receiverId: number + content: string + type: number + sendTime: number + + _showTime?: boolean + _tempId?: string + _status?: 'sent' | 'sending' | 'failed' +} + +export interface QueryGroupMessageParams { + groupId: number + beforeId: number | null + beforeTimestamp: number | null + pageSize: number +} + +export interface ImGroupMessage { + id: number + groupId: number + senderId: number + senderAvatar: string + senderName: string + content: string + type: number + sendTime: number + + _showTime?: boolean + _tempId?: string + _status?: 'sent' | 'sending' | 'failed' +} + +export interface UpdatePrivateUnreadParams { + userId: number + friendId: number +} +export interface UpdateGroupUnreadParams { + groupId: number + userId: number +} + +export interface QueryGroupUserParams { + groupId: number + userName: string +} + +export interface ImGroupUser { + groupId: number + userId: number + userAvatar: string + userName: string + isOnline: boolean } diff --git a/code/link56/link56-frontend-new/src/utils/time.ts b/code/link56/link56-frontend-new/src/utils/time.ts index f3bb96f6..24e25dc7 100644 --- a/code/link56/link56-frontend-new/src/utils/time.ts +++ b/code/link56/link56-frontend-new/src/utils/time.ts @@ -164,3 +164,4 @@ export function formatImTime(timestamp: number | null | undefined, mode: 'list' return ymd } + diff --git a/code/link56/link56-frontend-new/src/utils/uuid.ts b/code/link56/link56-frontend-new/src/utils/uuid.ts new file mode 100644 index 00000000..f19e9e44 --- /dev/null +++ b/code/link56/link56-frontend-new/src/utils/uuid.ts @@ -0,0 +1,7 @@ +export function generateUUID(): string { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { + const r = Math.random() * 16 | 0 + const v = c === 'x' ? r : (r & 0x3 | 0x8) + return v.toString(16) + }) +} 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 new file mode 100644 index 00000000..6c41fae7 --- /dev/null +++ b/code/link56/link56-frontend-new/src/views/im/components/GroupChatWindows.vue @@ -0,0 +1,583 @@ + + + + + diff --git a/code/link56/link56-frontend-new/src/views/im/components/PrivateChatWindow.vue b/code/link56/link56-frontend-new/src/views/im/components/PrivateChatWindow.vue new file mode 100644 index 00000000..c05defdf --- /dev/null +++ b/code/link56/link56-frontend-new/src/views/im/components/PrivateChatWindow.vue @@ -0,0 +1,570 @@ + + + + + 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 0d47c49f..632d0e06 100644 --- a/code/link56/link56-frontend-new/src/views/im/index.vue +++ b/code/link56/link56-frontend-new/src/views/im/index.vue @@ -1,55 +1,38 @@ @@ -280,7 +345,7 @@ function onGroupChat(group: ImGroup) { v-for="item in friendState.list" :key="item.id" class="im__chat-item" :class="{ 'im__chat-item--selected': - currentChat?.type === 'private' && item.friendId === currentChat.id, + isPrivateChat && item.friendId === currentFriendChat?.friendId, }" @click="onPrivateChat(item)" > @@ -324,7 +389,14 @@ function onGroupChat(group: ImGroup) {
@@ -340,9 +412,8 @@ function onGroupChat(group: ImGroup) {
- + {{ item.lastMessageSenderName }}:{{ item.lastMessageContent }}
-
@@ -351,21 +422,21 @@ function onGroupChat(group: ImGroup) {
-
+
- {{ currentChat.type === 'private' ? currentChat.data?.friendName : currentChat.data?.groupName }} + {{ isPrivateChat ? currentFriendChat?.friendName : currentGroupChat?.groupName }}
-
+
[
- {{ currentChat.data?.isOnline ? '在线' : '离线' }} + {{ currentFriendChat?.isOnline ? '在线' : '离线' }} ]
@@ -376,6 +447,11 @@ function onGroupChat(group: ImGroup) {
+ +
+ + +
@@ -583,7 +659,7 @@ function onGroupChat(group: ImGroup) { } .im__chat-item-name { - font-size: 16px; + font-size: 14px; font-weight: 500; } .im__chat-item-last-time, @@ -707,7 +783,7 @@ function onGroupChat(group: ImGroup) { scrollbar-gutter: stable both-edges; overflow-y: auto; min-width: 0; - background-color: #f8fafc; + background-color: #ffffff; height: 100%; .im__chat-message-loading { display: flex; @@ -908,6 +984,12 @@ function onGroupChat(group: ImGroup) { user-select: text; cursor: text; } + .im__chat-editor-input:empty::before { + content: attr(data-placeholder); + color: #aaa; + pointer-events: none; + display: block; + } } .im__chat-sendarea { diff --git a/code/link56/link56-frontend-new/test.html b/code/link56/link56-frontend-new/test.html index 41cbccd7..3f495a21 100644 --- a/code/link56/link56-frontend-new/test.html +++ b/code/link56/link56-frontend-new/test.html @@ -1,118 +1,246 @@ - - - - + + + - 公司信息卡片 + + 私聊界面示例 - + - - -
- - -
-

远航国际物流有限公司

- -
- 成立于 2012 年 · 上海 -
- -
- 专注于海运、空运及多式联运服务,为客户提供高效、稳定、 - 可视化的国际物流解决方案,覆盖全球主要港口。 -
- -
- 海运 - 国际物流 - 货运代理 - 跨境贸易 -
+ + +
+
+ 头像 + +
+
+ +
+ 头像 + +
+
+ +
+ 头像 + +
+
- + +
+ +
+ 头像 +
+

张三

+

前端工程师

+
+ + ABC科技公司 +
+
+
- \ No newline at end of file + +
+
你好!这是张三的消息。
+
你好,我是自己发的消息。
+
我们下午开会吗?
+
好的,下午三点。
+
+ + +
+ + +
+
+ +