SpringBoot+WebSocket实现即时通讯(四)

前言

紧接着上文《SpringBoot+WebSocket实现即时通讯(三)》

本博客姊妹篇

  • SpringBoot+WebSocket实现即时通讯(一)
  • SpringBoot+WebSocket实现即时通讯(二)
  • SpringBoot+WebSocket实现即时通讯(三)
  • SpringBoot+WebSocket实现即时通讯(四)

一、功能描述

  • 用户管理:业务自己实现,暂从数据库添加
  • 好友管理:添加好友、删除好友、修改备注、好友列表等
  • 群组管理:新建群、解散群、编辑群、变更群主、拉人进群、踢出群等
  • 聊天模式:私聊、群聊
  • 消息类型:系统、文本、语音、图片、视频
  • 聊天管理:删除聊天、置顶聊天、查看聊天记录等

二、消息、聊天会话功能实现

2.1 消息

mapper

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qiangesoft.im.mapper.ImMessageMapper"><select id="listMessage" resultType="com.qiangesoft.im.pojo.vo.ImMessageVO">SELECTb.id,b.sender_id,b.message_type,b.message,b.create_time,c.read_flagFROMim_chat aINNER JOIN im_message b ON a.id = b.chat_idINNER JOIN im_message_receiver c ON b.id = c.message_idWHEREa.del_flag = FALSEAND a.id = #{chatId}AND c.receiver_id = #{userId}<if test="messageType != null and messageType != ''">AND b.message_type = #{messageType}</if><if test="message != null and message != ''">AND b.message like concat('%', #{message}, '%')</if>ORDER BY b.id DESC</select><select id="listUnreadMessage" resultType="com.qiangesoft.im.pojo.vo.ImMessageVO">SELECTb.id,b.sender_id,b.message_type,b.message,b.create_time,c.read_flagFROMim_chat aINNER JOIN im_message b ON a.id = b.chat_idINNER JOIN im_message_receiver c ON b.id = c.message_idWHEREa.del_flag = FALSEAND a.id = #{chatId}AND c.receiver_id = #{userId}AND c.read_flag = FALSEORDER BY b.id DESC</select><select id="listChatUnreadMessage" resultType="com.qiangesoft.im.pojo.bo.ImChatMessageBO">SELECTa.chat_id AS chatId,COUNT(a.id) AS unreadNumFROMim_message a INNER JOINim_message_receiver b ON a.id=b.message_idWHEREb.receiver_id=#{userId}AND b.read_flag=FALSEAND a.chat_id IN<foreach collection="chatIdList" item="chatId" open="(" separator="," close=")">#{chatId}</foreach>GROUP BY a.chat_id</select><select id="listLatestMessage" resultType="com.qiangesoft.im.entity.ImMessage">SELECT * FROM im_message WHERE id IN (SELECTmax(a.id)FROMim_message a INNER JOINim_message_receiver b ON a.id=b.message_idWHEREb.receiver_id=#{userId}AND a.chat_id IN<foreach collection="chatIdList" item="chatId" open="(" separator="," close=")">#{chatId}</foreach>GROUP BY a.chat_id)</select></mapper>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qiangesoft.im.mapper.ImMessageReceiverMapper"><update id="updateRead">UPDATE im_message_receiver aINNER JOIN im_message bON b.id = a.message_idINNER JOIN im_chat c ON c.id = b.chat_idSET read_flag = 1WHEREa.read_flag = 0AND a.receiver_id = #{userId}AND c.id = #{chatId}<if test="messageId != null">AND b.id = #{messageId}</if></update>
</mapper>
package com.qiangesoft.im.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.qiangesoft.im.entity.ImMessage;
import com.qiangesoft.im.pojo.bo.ImChatMessageBO;
import com.qiangesoft.im.pojo.vo.ImMessageVO;
import org.apache.ibatis.annotations.Param;import java.util.List;/*** <p>* 消息 Mapper 接口* </p>** @author qiangesoft* @date 2024-02-07*/
public interface ImMessageMapper extends BaseMapper<ImMessage> {/*** 消息列表** @param page* @param userId* @param chatId* @param messageType* @param message* @return*/IPage<ImMessageVO> listMessage(@Param("page") IPage<ImMessage> page, @Param("userId") Long userId, @Param("chatId") Long chatId,@Param("messageType") String messageType, @Param("message") String message);/*** 未读消息列表** @param userId* @param chatId* @return*/List<ImMessageVO> listUnreadMessage(@Param("userId") Long userId, @Param("chatId") Long chatId);/*** 会话未读消息** @param userId* @param chatIdList* @return*/List<ImChatMessageBO> listChatUnreadMessage(@Param("userId") Long userId, @Param("chatIdList") List<Long> chatIdList);/*** 会话最新消息** @param userId* @param chatIdList* @return*/List<ImMessage> listLatestMessage(@Param("userId") Long userId, @Param("chatIdList") List<Long> chatIdList);
}
package com.qiangesoft.im.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.qiangesoft.im.entity.ImMessageReceiver;
import org.apache.ibatis.annotations.Param;/*** <p>* 群用户消息关系 Mapper 接口* </p>** @author qiangesoft* @since 2023-08-23*/
public interface ImMessageReceiverMapper extends BaseMapper<ImMessageReceiver> {/*** 置为已读** @param userId* @param chatId* @param messageId*/void updateRead(@Param("userId") Long userId, @Param("chatId") Long chatId, @Param("messageId") Long messageId);
}

service

package com.qiangesoft.im.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.qiangesoft.im.entity.ImMessage;
import com.qiangesoft.im.pojo.bo.ImChatMessageBO;
import com.qiangesoft.im.pojo.dto.ImMessageDTO;
import com.qiangesoft.im.pojo.dto.query.MessageQueryDTO;
import com.qiangesoft.im.pojo.dto.query.PageQueryDTO;
import com.qiangesoft.im.pojo.vo.ImMessageVO;
import com.qiangesoft.im.pojo.vo.PageResultVO;import java.util.List;/*** <p>* 消息 服务类* </p>** @author qiangesoft* @date 2024-02-07*/
public interface IImMessageService extends IService<ImMessage> {/*** 消息列表** @param pageQuery* @param messageQuery* @return*/PageResultVO<ImMessageVO> listMessage(PageQueryDTO pageQuery, MessageQueryDTO messageQuery);/*** 未读消息列表** @param chatId* @return*/List<ImMessageVO> listUnreadMessage(Long chatId);/*** 发送消息** @param messageDTO* @return*/ImMessage send(ImMessageDTO messageDTO);/*** 聊天未读消息** @param userId* @param chatIdList* @return*/List<ImChatMessageBO> listChatUnreadMessage(Long userId, List<Long> chatIdList);/*** 聊天最新消息** @param userId* @param chatIdList* @return*/List<ImMessage> listLatestMessage(Long userId, List<Long> chatIdList);
}
package com.qiangesoft.im.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.qiangesoft.im.entity.ImMessageReceiver;/*** <p>* 群用户消息关系 服务类* </p>** @author qiangesoft* @since 2023-08-23*/
public interface IImMessageReceiverService extends IService<ImMessageReceiver> {/*** 置为已读** @param chatId* @param messageId*/void updateRead(Long chatId, Long messageId);}
package com.qiangesoft.im.service.impl;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qiangesoft.im.auth.UserUtil;
import com.qiangesoft.im.core.constant.ChatTypeEnum;
import com.qiangesoft.im.entity.*;
import com.qiangesoft.im.exception.ServiceException;
import com.qiangesoft.im.mapper.ImMessageMapper;
import com.qiangesoft.im.pojo.bo.ImChatMessageBO;
import com.qiangesoft.im.pojo.dto.ImMessageDTO;
import com.qiangesoft.im.pojo.dto.query.MessageQueryDTO;
import com.qiangesoft.im.pojo.dto.query.PageQueryDTO;
import com.qiangesoft.im.pojo.vo.ImMessageVO;
import com.qiangesoft.im.pojo.vo.PageResultVO;
import com.qiangesoft.im.pojo.vo.SysUserVo;
import com.qiangesoft.im.service.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;import java.util.*;
import java.util.stream.Collectors;/*** <p>* 群消息 服务实现类* </p>** @author qiangesoft* @date 2024-02-07*/
@Service
public class ImMessageServiceImpl extends ServiceImpl<ImMessageMapper, ImMessage> implements IImMessageService {@Lazy@Autowiredprivate IImChatService chatService;@Autowiredprivate IImFriendService friendService;@Autowiredprivate IImGroupUserService groupUserService;@Autowiredprivate IImMessageReceiverService messageReceiverService;@Autowiredprivate ISysUserService sysUserService;@Overridepublic PageResultVO<ImMessageVO> listMessage(PageQueryDTO pageQuery, MessageQueryDTO messageQuery) {Long chatId = messageQuery.getChatId();ImChat chat = chatService.getById(chatId);if (chat == null) {throw new ServiceException("聊天不存在");}Long userId = UserUtil.getUserId();Integer pageNum = pageQuery.getPageNum();Integer pageSize = pageQuery.getPageSize();PageResultVO<ImMessageVO> pageResult = new PageResultVO<>();pageResult.setPageNum(pageNum);pageResult.setPageSize(pageSize);IPage<ImMessageVO> messagePage = baseMapper.listMessage(new Page<>(pageNum, pageSize), userId, messageQuery.getChatId(), messageQuery.getMessageType(), messageQuery.getMessage());pageResult.setTotal(messagePage.getTotal());pageResult.setPages(messagePage.getPages());List<ImMessageVO> records = messagePage.getRecords();if (CollectionUtils.isEmpty(records)) {pageResult.setResults(records);return pageResult;}Set<Long> senderIdList = records.stream().map(ImMessageVO::getSenderId).collect(Collectors.toSet());Long targetId = chat.getTargetId();Map<Long, String> avatarMap = sysUserService.listByIds(senderIdList).stream().collect(Collectors.toMap(SysUser::getId, SysUser::getAvatar));Map<Long, String> nickNameMap = new HashMap<>();if (ChatTypeEnum.GROUP.getCode().equals(chat.getChatType())) {LambdaQueryWrapper<ImGroupUser> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(ImGroupUser::getGroupId, targetId).in(ImGroupUser::getUserId, senderIdList);nickNameMap = groupUserService.list(queryWrapper).stream().collect(Collectors.toMap(ImGroupUser::getUserId, ImGroupUser::getNickName));} else {ImFriend friend = friendService.getById(targetId);nickNameMap.put(userId, UserUtil.getNickName());nickNameMap.put(targetId, friend.getRemark());}for (ImMessageVO record : records) {SysUserVo sysUserVo = new SysUserVo();Long senderId = record.getSenderId();sysUserVo.setId(senderId);sysUserVo.setAvatar(avatarMap.get(senderId));sysUserVo.setNickName(nickNameMap.get(senderId));record.setSender(sysUserVo);}pageResult.setResults(records);return pageResult;}@Overridepublic List<ImMessageVO> listUnreadMessage(Long chatId) {ImChat chat = chatService.getById(chatId);if (chat == null) {throw new ServiceException("聊天不存在");}Long userId = UserUtil.getUserId();List<ImMessageVO> records = baseMapper.listUnreadMessage(userId, chatId);if (CollectionUtils.isEmpty(records)) {return records;}Set<Long> senderIdList = records.stream().map(ImMessageVO::getSenderId).collect(Collectors.toSet());Long targetId = chat.getTargetId();Map<Long, String> avatarMap = sysUserService.listByIds(senderIdList).stream().collect(Collectors.toMap(SysUser::getId, SysUser::getAvatar));Map<Long, String> nickNameMap = new HashMap<>();if (ChatTypeEnum.GROUP.getCode().equals(chat.getChatType())) {LambdaQueryWrapper<ImGroupUser> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(ImGroupUser::getGroupId, targetId).in(ImGroupUser::getUserId, senderIdList);nickNameMap = groupUserService.list(queryWrapper).stream().collect(Collectors.toMap(ImGroupUser::getUserId, ImGroupUser::getNickName));} else {ImFriend friend = friendService.getById(targetId);nickNameMap.put(userId, UserUtil.getNickName());nickNameMap.put(targetId, friend.getRemark());}for (ImMessageVO record : records) {SysUserVo sysUserVo = new SysUserVo();Long senderId = record.getSenderId();sysUserVo.setId(senderId);sysUserVo.setAvatar(avatarMap.get(senderId));sysUserVo.setNickName(nickNameMap.get(senderId));record.setSender(sysUserVo);}return records;}@Transactional(rollbackFor = RuntimeException.class)@Overridepublic ImMessage send(ImMessageDTO messageDTO) {Long userId = UserUtil.getUserId();ImMessage message = null;String chatType = messageDTO.getChatType();if (ChatTypeEnum.GROUP.getCode().equals(chatType)) {message = this.sendGroupMessage(messageDTO, userId);}if (ChatTypeEnum.PERSON.getCode().equals(chatType)) {message = this.sendPersonMessage(messageDTO, userId);}return message;}/*** 私聊消息** @param messageDTO* @param userId* @return*/private ImMessage sendPersonMessage(ImMessageDTO messageDTO, Long userId) {Long friendUserId = messageDTO.getTargetId();LambdaQueryWrapper<ImFriend> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(ImFriend::getFriendUserId, friendUserId).eq(ImFriend::getUserId, userId).eq(ImFriend::getDelFlag, false);ImFriend friend = friendService.getOne(queryWrapper);if (friend == null) {throw new ServiceException("非好友关系");}// 聊天会话Long chatId = messageDTO.getChatId();if (chatId == null) {LambdaQueryWrapper<ImChat> queryWrapper1 = new LambdaQueryWrapper<>();queryWrapper1.eq(ImChat::getUserId, userId).eq(ImChat::getChatType, messageDTO.getChatType()).eq(ImChat::getTargetId, friendUserId).eq(ImChat::getDelFlag, false);ImChat chat = chatService.getOne(queryWrapper1);if (chat == null) {chat = new ImChat();chat.setUserId(userId);chat.setChatType(messageDTO.getChatType());chat.setTargetId(friendUserId);chat.setDelFlag(false);chatService.save(chat);}messageDTO.setChatId(chat.getId());}// 消息ImMessage message = new ImMessage();message.setSenderId(userId);message.setChatId(messageDTO.getChatId());message.setMessageType(messageDTO.getMessageType());message.setMessage(messageDTO.getMessage());message.setDelFlag(false);baseMapper.insert(message);// 发送ImMessageReceiver messageReceiver = new ImMessageReceiver();messageReceiver.setMessageId(message.getId());messageReceiver.setReceiverId(friendUserId);messageReceiver.setDelFlag(false);messageReceiver.setReadFlag(false);messageReceiverService.save(messageReceiver);return message;}/*** 群聊消息** @param messageDTO* @param userId* @return*/private ImMessage sendGroupMessage(ImMessageDTO messageDTO, Long userId) {Long groupId = messageDTO.getTargetId();LambdaQueryWrapper<ImGroupUser> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(ImGroupUser::getGroupId, groupId).eq(ImGroupUser::getUserId, userId).eq(ImGroupUser::getDelFlag, false);ImGroupUser imGroupUser = groupUserService.getOne(queryWrapper);if (imGroupUser == null) {throw new ServiceException("无法发言,您已不在群中");}// 聊天会话Long chatId = messageDTO.getChatId();if (chatId == null) {LambdaQueryWrapper<ImChat> queryWrapper1 = new LambdaQueryWrapper<>();queryWrapper1.eq(ImChat::getUserId, userId).eq(ImChat::getChatType, messageDTO.getChatType()).eq(ImChat::getTargetId, groupId).eq(ImChat::getDelFlag, false);ImChat chat = chatService.getOne(queryWrapper1);if (chat == null) {chat = new ImChat();chat.setUserId(userId);chat.setChatType(messageDTO.getChatType());chat.setTargetId(groupId);chat.setDelFlag(false);chatService.save(chat);}messageDTO.setChatId(chat.getId());}// 消息ImMessage message = new ImMessage();message.setSenderId(userId);message.setChatId(messageDTO.getChatId());message.setMessageType(messageDTO.getMessageType());message.setMessage(messageDTO.getMessage());message.setDelFlag(false);baseMapper.insert(message);// 发给群成员List<SysUserVo> groupUserList = groupUserService.listGroupUser(groupId);List<ImMessageReceiver> messageReceiverList = new ArrayList<>();for (SysUserVo sysUserVo : groupUserList) {ImMessageReceiver messageReceiver = new ImMessageReceiver();messageReceiver.setMessageId(message.getId());messageReceiver.setReceiverId(sysUserVo.getId());messageReceiver.setDelFlag(false);messageReceiver.setReadFlag(false);if (userId.equals(sysUserVo.getId())) {messageReceiver.setReadFlag(true);}messageReceiverList.add(messageReceiver);}messageReceiverService.saveBatch(messageReceiverList);return message;}@Overridepublic List<ImChatMessageBO> listChatUnreadMessage(Long userId, List<Long> chatIdList) {return baseMapper.listChatUnreadMessage(userId, chatIdList);}@Overridepublic List<ImMessage> listLatestMessage(Long userId, List<Long> chatIdList) {return baseMapper.listLatestMessage(userId, chatIdList);}
}
package com.qiangesoft.im.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qiangesoft.im.auth.UserUtil;
import com.qiangesoft.im.entity.ImMessageReceiver;
import com.qiangesoft.im.mapper.ImMessageReceiverMapper;
import com.qiangesoft.im.service.IImMessageReceiverService;
import org.springframework.stereotype.Service;/*** <p>* 群用户消息关系 服务实现类* </p>** @author qiangesoft* @since 2023-08-23*/
@Service
public class ImMessageReceiverServiceImpl extends ServiceImpl<ImMessageReceiverMapper, ImMessageReceiver> implements IImMessageReceiverService {@Overridepublic void updateRead(Long chatId, Long messageId) {baseMapper.updateRead(UserUtil.getUserId(), chatId, messageId);}
}

controller

package com.qiangesoft.im.controller;import com.qiangesoft.im.core.ImWebSocketServer;
import com.qiangesoft.im.entity.ImMessage;
import com.qiangesoft.im.pojo.bo.ImMessageBO;
import com.qiangesoft.im.pojo.dto.ImMessageDTO;
import com.qiangesoft.im.pojo.dto.ImMessageReadDTO;
import com.qiangesoft.im.pojo.dto.query.MessageQueryDTO;
import com.qiangesoft.im.pojo.dto.query.PageQueryDTO;
import com.qiangesoft.im.pojo.vo.ImMessageVO;
import com.qiangesoft.im.pojo.vo.PageResultVO;
import com.qiangesoft.im.pojo.vo.ResultInfo;
import com.qiangesoft.im.service.IImMessageReceiverService;
import com.qiangesoft.im.service.IImMessageService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;import java.util.List;/*** <p>* 群消息 前端控制器* </p>** @author qiangesoft* @date 2024-02-07*/
@Api(tags = "消息")
@RestController
@RequestMapping("/im/message")
public class ImMessageController {@Autowiredprivate IImMessageService messageService;@Autowiredprivate IImMessageReceiverService messageReceiverService;@GetMapping@ApiOperation(value = "消息列表")public ResultInfo<PageResultVO<ImMessageVO>> listMessage(@Validated PageQueryDTO pageQuery, @Validated MessageQueryDTO messageQuery) {PageResultVO<ImMessageVO> pageResult = messageService.listMessage(pageQuery, messageQuery);return ResultInfo.ok(pageResult);}@GetMapping("/unread")@ApiOperation(value = "未读消息列表")public ResultInfo<List<ImMessageVO>> listUnreadMessage(Long chatId) {List<ImMessageVO> messageList = messageService.listUnreadMessage(chatId);return ResultInfo.ok(messageList);}@PostMapping("/send")@ApiOperation(value = "发送消息")public ResultInfo<Void> send(@Validated @RequestBody ImMessageDTO messageDTO) {ImMessage message = messageService.send(messageDTO);// 发送消息ImMessageBO messageBO = new ImMessageBO();messageBO.setId(message.getId());messageBO.setSenderId(message.getSenderId());messageBO.setChatId(message.getChatId());messageBO.setMessageType(message.getMessageType());messageBO.setMessage(message.getMessage());messageBO.setSendTime(message.getCreateTime());messageBO.setChatType(messageDTO.getChatType());messageBO.setTargetId(messageDTO.getTargetId());messageBO.setExtra(messageDTO.getExtra());messageBO.setTimestamp(messageDTO.getTimestamp());ImWebSocketServer.sendMessage(messageBO);return ResultInfo.ok();}@PutMapping("/read")@ApiOperation(value = "阅读消息")public ResultInfo<Void> read(@Validated @RequestBody ImMessageReadDTO messageReadDTO) {messageReceiverService.updateRead(messageReadDTO.getChatId(), messageReadDTO.getId());return ResultInfo.ok();}
}

2.2 聊天会话

mapper

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qiangesoft.im.mapper.ImChatMapper"><select id="listChat" resultType="com.qiangesoft.im.entity.ImChat">SELECT a.id,a.user_id,a.chat_type,a.target_id,a.del_flag,a.top_flag,b.create_timeFROM im_chat aINNER JOIN (SELECT chat_id, max(create_time) create_time FROM im_message GROUP BY chat_id) bON a.id = b.chat_idWHERE a.del_flag = FALSEAND a.user_id = #{userId}ORDER BY a.top_flag, b.create_time</select></mapper>
package com.qiangesoft.im.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.qiangesoft.im.entity.ImChat;
import org.apache.ibatis.annotations.Param;import java.util.List;/*** <p>* 聊天 Mapper 接口* </p>** @author qiangesoft* @date 2024-02-07*/
public interface ImChatMapper extends BaseMapper<ImChat> {/*** 聊天列表** @return*/List<ImChat> listChat(@Param("userId") Long userId);}

service

package com.qiangesoft.im.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.qiangesoft.im.entity.ImChat;
import com.qiangesoft.im.pojo.vo.ImChatVO;import java.util.List;/*** <p>* 聊天 服务类* </p>** @author qiangesoft* @date 2024-02-07*/
public interface IImChatService extends IService<ImChat> {/*** 删除聊天** @param id*/void removeChat(Long id);/*** 聊天列表** @return*/List<ImChatVO> listChat();/*** 置顶聊天** @param id*/void setTop(Long id);/*** 取消置顶聊天** @param id*/void cancelTop(Long id);
}
package com.qiangesoft.im.service.impl;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 com.qiangesoft.im.auth.UserUtil;
import com.qiangesoft.im.core.constant.ChatTypeEnum;
import com.qiangesoft.im.entity.*;
import com.qiangesoft.im.mapper.ImChatMapper;
import com.qiangesoft.im.pojo.bo.ImChatMessageBO;
import com.qiangesoft.im.pojo.vo.ImChatVO;
import com.qiangesoft.im.pojo.vo.ImMessageVO;
import com.qiangesoft.im.pojo.vo.SysUserVo;
import com.qiangesoft.im.service.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;import java.util.*;
import java.util.stream.Collectors;/*** <p>* 聊天 服务实现类* </p>** @author qiangesoft* @date 2024-02-07*/
@Service
public class ImChatServiceImpl extends ServiceImpl<ImChatMapper, ImChat> implements IImChatService {@Autowiredprivate ISysUserService sysUserService;@Autowiredprivate IImFriendService friendService;@Autowiredprivate IImGroupService groupService;@Autowiredprivate IImGroupUserService groupUserService;@Autowiredprivate IImMessageService messageService;@Overridepublic void removeChat(Long id) {LambdaUpdateWrapper<ImChat> updateWrapper = new LambdaUpdateWrapper<>();updateWrapper.eq(ImChat::getId, id).set(ImChat::getDelFlag, true);baseMapper.update(null, updateWrapper);}@Overridepublic List<ImChatVO> listChat() {List<ImChatVO> groupVOList = new ArrayList<>();Long userId = UserUtil.getUserId();List<ImChat> chatList = baseMapper.listChat(userId);if (CollectionUtils.isEmpty(chatList)) {return groupVOList;}// 聊天列表对象信息List<Long> friendIdList = chatList.stream().filter(e -> ChatTypeEnum.PERSON.getCode().equals(e.getChatType())).map(ImChat::getTargetId).collect(Collectors.toList());Map<Long, ImFriend> friendMap = new HashMap<>();Map<Long, SysUser> sysUserMap = new HashMap<>();if (!CollectionUtils.isEmpty(friendIdList)) {List<ImFriend> friendList = friendService.listByIds(friendIdList);friendMap = friendList.stream().collect(Collectors.toMap(ImFriend::getId, imFriend -> imFriend));List<Long> friendUserIdList = friendList.stream().map(ImFriend::getFriendUserId).collect(Collectors.toList());sysUserMap = sysUserService.listByIds(friendUserIdList).stream().collect(Collectors.toMap(SysUser::getId, sysUser -> sysUser));}List<Long> groupIdList = chatList.stream().filter(e -> ChatTypeEnum.GROUP.getCode().equals(e.getChatType())).map(ImChat::getTargetId).collect(Collectors.toList());Map<Long, ImGroup> groupMap = new HashMap<>();if (!CollectionUtils.isEmpty(groupIdList)) {groupMap = groupService.listByIds(groupIdList).stream().collect(Collectors.toMap(ImGroup::getId, imGroup -> imGroup));}// 未读消息List<Long> chatIdList = chatList.stream().map(ImChat::getId).collect(Collectors.toList());List<ImChatMessageBO> unReadMessageList = messageService.listChatUnreadMessage(userId, chatIdList);// 最新消息List<ImMessage> latestMessageList = messageService.listLatestMessage(userId, chatIdList);List<Long> friendChatIdList = chatList.stream().filter(e -> ChatTypeEnum.PERSON.getCode().equals(e.getChatType())).map(ImChat::getId).collect(Collectors.toList());List<Long> groupChatIdList = chatList.stream().filter(e -> ChatTypeEnum.GROUP.getCode().equals(e.getChatType())).map(ImChat::getId).collect(Collectors.toList());List<ImMessage> friendLatestMessageList = latestMessageList.stream().filter(e -> friendChatIdList.contains(e.getChatId())).collect(Collectors.toList());List<ImMessage> groupLatestMessageList = latestMessageList.stream().filter(e -> groupChatIdList.contains(e.getChatId())).collect(Collectors.toList());// 昵称List<Long> sendFriendIdList = friendLatestMessageList.stream().map(ImMessage::getSenderId).collect(Collectors.toList());Map<Long, String> remarkMap = new HashMap<>();if (!CollectionUtils.isEmpty(sendFriendIdList)) {LambdaQueryWrapper<ImFriend> fqueryWrapper = new LambdaQueryWrapper<>();fqueryWrapper.eq(ImFriend::getUserId, userId).in(ImFriend::getFriendUserId, sendFriendIdList);remarkMap = friendService.list(fqueryWrapper).stream().collect(Collectors.toMap(ImFriend::getFriendUserId, ImFriend::getRemark));}List<Long> sendGroupIdList = groupLatestMessageList.stream().map(ImMessage::getSenderId).collect(Collectors.toList());Map<Long, String> nicknameMap = new HashMap<>();if (!CollectionUtils.isEmpty(groupIdList) && !CollectionUtils.isEmpty(sendGroupIdList)) {LambdaQueryWrapper<ImGroupUser> gqueryWrapper = new LambdaQueryWrapper<>();gqueryWrapper.in(ImGroupUser::getGroupId, groupIdList).in(ImGroupUser::getUserId, sendGroupIdList);nicknameMap = groupUserService.list(gqueryWrapper).stream().collect(Collectors.toMap(ImGroupUser::getUserId, ImGroupUser::getNickName));}for (ImChat chat : chatList) {ImChatVO vo = new ImChatVO();vo.setId(chat.getId());
//            vo.setAvatar(avatarMap.get());Long targetId = chat.getTargetId();String chatType = chat.getChatType();vo.setTargetId(targetId);vo.setChatType(chatType);// 未读消息数Optional<ImChatMessageBO> first = unReadMessageList.stream().filter(e -> e.getChatId().equals(chat.getId())).findFirst();Integer unreadNum = first.isPresent() ? first.get().getUnreadNum() : 0;vo.setUnreadNum(unreadNum);String name;String avatar;if (ChatTypeEnum.PERSON.getCode().equals(chatType)) {ImFriend friend = friendMap.get(targetId);name = friend.getRemark();SysUser sysUser = sysUserMap.get(friend.getFriendUserId());avatar = sysUser.getAvatar();Optional<ImMessage> firstLatest = friendLatestMessageList.stream().filter(e -> e.getChatId().equals(chat.getId())).findFirst();if (firstLatest.isPresent()) {ImMessage message = firstLatest.get();ImMessageVO messageVO = new ImMessageVO();messageVO.setId(message.getId());messageVO.setMessageType(message.getMessageType());messageVO.setMessage(message.getMessage());messageVO.setReadFlag(false);messageVO.setSendTime(message.getCreateTime());// 发送人SysUserVo sysUserVo = new SysUserVo();Long senderId = message.getSenderId();sysUserVo.setId(senderId);sysUserVo.setNickName(remarkMap.get(senderId));messageVO.setSender(sysUserVo);vo.setLatestMessage(messageVO);}} else {ImGroup group = groupMap.get(targetId);name = group.getName();avatar = group.getAvatar();Optional<ImMessage> firstLatest = groupLatestMessageList.stream().filter(e -> e.getChatId().equals(chat.getId())).findFirst();if (firstLatest.isPresent()) {ImMessage message = firstLatest.get();ImMessageVO messageVO = new ImMessageVO();messageVO.setId(message.getId());messageVO.setMessageType(message.getMessageType());messageVO.setMessage(message.getMessage());messageVO.setReadFlag(false);messageVO.setSendTime(message.getCreateTime());// 发送人SysUserVo sysUserVo = new SysUserVo();Long senderId = message.getSenderId();sysUserVo.setId(senderId);sysUserVo.setNickName(nicknameMap.get(senderId));messageVO.setSender(sysUserVo);vo.setLatestMessage(messageVO);}}vo.setName(name);vo.setAvatar(avatar);groupVOList.add(vo);}return groupVOList;}@Overridepublic void setTop(Long id) {LambdaUpdateWrapper<ImChat> updateWrapper = new LambdaUpdateWrapper<>();updateWrapper.eq(ImChat::getId, id).set(ImChat::getTopFlag, true);baseMapper.update(null, updateWrapper);}@Overridepublic void cancelTop(Long id) {LambdaUpdateWrapper<ImChat> updateWrapper = new LambdaUpdateWrapper<>();updateWrapper.eq(ImChat::getId, id).set(ImChat::getTopFlag, false);baseMapper.update(null, updateWrapper);}}
package com.qiangesoft.im.controller;import com.qiangesoft.im.pojo.vo.ImChatVO;
import com.qiangesoft.im.pojo.vo.ResultInfo;
import com.qiangesoft.im.service.IImChatService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;import java.util.List;/*** <p>* 群组 前端控制器* </p>** @author qiangesoft* @date 2024-02-07*/
@Api(tags = "聊天")
@RequiredArgsConstructor
@RestController
@RequestMapping("/im/chat")
public class ImChatController {private final IImChatService chatService;@DeleteMapping("/{id}")@ApiOperation(value = "删除聊天")public ResultInfo<Void> removeChat(@PathVariable Long id) {chatService.removeChat(id);return ResultInfo.ok();}@GetMapping@ApiOperation(value = "聊天列表")public ResultInfo<List<ImChatVO>> listChat() {return ResultInfo.ok(chatService.listChat());}@PutMapping("/setTop/{id}")@ApiOperation(value = "置顶聊天")public ResultInfo<Void> setTop(@PathVariable Long id) {chatService.setTop(id);return ResultInfo.ok();}@PutMapping("/cancelTop/{id}")@ApiOperation(value = "取消置顶聊天")public ResultInfo<Void> cancelTop(@PathVariable Long id) {chatService.cancelTop(id);return ResultInfo.ok();}}

三、消息发送接收测试

在这里插入图片描述
在这里插入图片描述

四、源码地址

源码地址:https://gitee.com/qiangesoft/boot-business/tree/master/boot-business-im

后续内容见下章

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/692962.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Visual Studio下载安装教程(非常详细)从零基础入门到精通,看完这一篇就够了

Visual Studio安装教程 一、官网下载 官网下载地址&#xff1a; https://visualstudio.microsoft.com/zh-hans/downloads/ 因为是个人学习用途&#xff0c;所以我这里下载的是社区版本。 下载下来的是一个.exe文件 双击打开后&#xff0c;会加载一些东西。最后出现下面的界面…

架构(十四)动态Groovy脚本

一、引言 最近作者的平台项目需要实现前端输入脚本&#xff0c;后端在用户设置好的一些情况下运行这段脚本。后端是java&#xff0c;所以我们采用Groovy脚本。 所以要实现的功能就是动态的Groovy脚本&#xff01; 二、Groovy介绍 了解groovy和python的就可以直接到第三章了 2…

QGis软件 —— 7、QGis - 绘制操作多边形、多边形与线条互转、多边形经纬度导出csv文件

绘制操作多边形 1、绘制多边形&#xff08;下面附上结果及操作过程&#xff09; 结果如下&#xff1a; 具体操作&#xff1a; 2、对多边形加入字段信息并显示&#xff08;下面附上结果及操作过程&#xff09; 结果如下&#xff1a; 具体操作&#xff1a; 多边形与线条互转 …

加载arcgis切片服务网络请求有大量404错误

需求&#xff1a; 前端访问arcgis切片服务时&#xff0c;在网络请求中出现大量404&#xff08;Not Found&#xff09;错误&#xff0c;切片时设置了感兴趣区域&#xff0c;在感兴趣范围内请求切片时能够正常返回切片。 问题分析&#xff1a; 设置感兴趣区域切片的目的是减少站…

【 JS 进阶 】异常处理与 debugger 调试

异常处理 了解 JavaScript 中程序异常处理的方法&#xff0c;提升代码运行的健壮性。 throw 异常处理是指预估代码执行过程中可能发生的错误&#xff0c;然后最大程度的避免错误的发生导致整个程序无法继续运行 总结&#xff1a; throw 抛出异常信息&#xff0c;程序也会终止…

AD24-开窗

一、PCB阻焊开窗处理 开窗效果图 1、将铜皮选中&#xff0c;复制&#xff0c;来到阻焊层&#xff0c;利用特殊粘贴 2、如出现报错&#xff0c;可利用实心填充在阻焊层进行重新绘制&#xff1b;在3D状态下进行查看 3、放置一块填充&#xff1b;称为露基材&#xff08;PCB材料&am…

弱网演练的小插曲记录

文章目录 1. 写在最前面1.1 关于弱网1.2 关于插曲 2. 模拟弱网的工具2.1 tc2.1.1 原理2.1.2 使用 2.2 blade2.2.1 原理2.2.2 使用 3. tc vs iptable4. 碎碎念5.参考资料 1. 写在最前面 节前在做历史故障演练的 1:1 复现&#xff0c;以验收当前新增的监控告警可以覆盖历史的故障…

HTTPS(超文本传输安全协议)被恶意请求该如何处理。

HTTPS&#xff08;超文本传输安全协议&#xff09;端口攻击通常是指SSL握手中的一些攻击方式&#xff0c;比如SSL握手协商过程中的暴力破解、中间人攻击和SSL剥离攻击等。 攻击原理 攻击者控制受害者发送大量请求&#xff0c;利用压缩算法的机制猜测请求中的关键信息&#xf…

防火墙——计算机网络

前述基于密码的安全机制不能有效解决以下安全问题&#xff1a; 用户入侵&#xff1a; 利用系统漏洞进行未授权登录&#xff1b; 授权用户非法获取更高级别权限等。 软件入侵&#xff1a; 通过网络传播病毒、蠕虫和特洛伊木马。 拒绝服务攻击等。 解决方法&#xff1a; 防火墙&a…

LabVIEW压电驱动迟滞补偿控制

LabVIEW压电驱动迟滞补偿控制 随着精密控制技术的迅速发展&#xff0c;压电陶瓷驱动器因其高精度和快速响应特性&#xff0c;在微纳精密定位系统中得到了广泛应用。然而&#xff0c;压电材料固有的迟滞非线性特性严重影响了其定位精度和重复性。开发了一种基于LabVIEWFPGA的压…

MySQL安装教程(详细版)

今天分享的是Win10系统下MySQL的安装教程&#xff0c;打开MySQL官网&#xff0c;按步骤走呀~ 宝们安装MySQL后&#xff0c;需要简单回顾一下关系型数据库的介绍与历史&#xff08;History of DataBase&#xff09; 和 常见关系型数据库产品介绍 呀&#xff0c;后面就会进入正式…

C语言OJ题——1091装箱问题

装箱问题 1.题目2.解题思路3.代码实现4.细节补充说明 1.题目 题目描述 一个工厂生产的产品形状都是长方体&#xff0c;高度都是h&#xff0c;主要有1 * 1&#xff0c;2 * 2&#xff0c;3 * 3&#xff0c;4 * 4&#xff0c;5 * 5&#xff0c;6 * 6等6种。这些产品在邮寄时被包装…

Cesium for Unreal 从源码编译到应用——插件编译

一、安装环境 Unreal Engine 5.3 CMake 3.17.5 Microsoft Visual Studio 2019 二、源码准备 下载cesium-unreal-samples工程。 git clone https://github.com/CesiumGS/cesium-unreal-samples.git 然后在工程目录创建Plugins文件夹&#xff0c;并下载cesium-unreal工程。 …

keepalived双主模式测试

文章目录 环境准备部署安装keepavlived配置启动测试模拟Nginx宕机重新启动问题分析 环境准备 测试一下keepalived的双主模式&#xff0c;所谓双主模式就是两个keepavlied节点各持有一个/组虚IP&#xff0c;默认情况下&#xff0c;二者互为主备&#xff0c;同时对外提供服务&am…

从宏观到微观——泽攸科技ZEM系列台式扫描电子显微镜在岩石分析中的应用

岩石作为地球地壳的主要构成物质之一&#xff0c;其微观结构对于了解地质过程、资源勘探以及工程建设具有重要意义。按照岩石的成因&#xff0c;可以把它们分为三类&#xff1a;岩浆岩、沉积岩和变质岩。在地球表面&#xff0c;沉积岩占据75%的份额&#xff0c;而在地壳深度&am…

vue2的ElementUI的form表单报错“Error: [ElementForm]unpected width”修复

1. 问题 ElementUI的form表单&#xff0c;当动态切换显示表单时报错 Error: [ElementForm]unpected width。 翻译过来就是form表单的label宽度width出了问题。 2. 分析 参数说明类型可选值默认值label-width表单域标签的宽度&#xff0c;例如 ‘50px’。作为 Form 直接子元…

【Simulink系列】——动态系统仿真 之 连续系统线性连续系统

声明&#xff1a;本系列博客参考有关专业书籍&#xff0c;截图均为自己实操&#xff0c;仅供交流学习&#xff01; 一、连续系统定义 连续系统输出在时间上连续变化&#xff0c;而非间隔采样取值&#xff0c;满足以下条件&#xff1a; ①输出连续变化&#xff0c;变化的间隔…

OpenAI 的 Sora AI 视频生成器太疯狂了

毫不夸张地说&#xff0c;当我第一次看到 Sora 生成的前几个视频时&#xff0c;我的下巴都惊掉了。 Sora是什么&#xff1f; Sora 是一种人工智能模型&#xff0c;可以根据简单的文本提示生成视频。它能够生成一分钟的高保真视频。 Sora 是一种扩散模型&#xff0c;一种先进的…

阿赵UE学习笔记——15、灯光的移动性概念和构建光照信息

阿赵UE学习笔记目录   大家好&#xff0c;我是阿赵。   继续学习虚幻引擎&#xff0c;这次来学习一下UE里面灯光的移动性概念和构建光照信息。 1、灯光移动性 打开一个带有灯光的场景 在大纲面板里面找到其中一个灯光&#xff1a; 会发现灯光的细节面板里面&#xff0c;…

智慧环卫建设方案

三、软件工程的总体构架 3.1框架构建基准 3.2框架设计 四、业务应用层主要功能模块 4.1数据字典管理模块 数据字典主要包含有&#xff08;GIS&#xff09;地理信息管理系统、信息安全管理系统、平台接口管理系统等三个方面的管理应用&#xff1b; 1).&#xff08;GIS&#…