前言
紧接着上文《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">
SELECT
b.id,
b.sender_id,
b.message_type,
b.message,
b.create_time,
c.read_flag
FROM
im_chat a
INNER JOIN im_message b ON a.id = b.chat_id
INNER JOIN im_message_receiver c ON b.id = c.message_id
WHERE
a.del_flag = FALSE
AND 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">
SELECT
b.id,
b.sender_id,
b.message_type,
b.message,
b.create_time,
c.read_flag
FROM
im_chat a
INNER JOIN im_message b ON a.id = b.chat_id
INNER JOIN im_message_receiver c ON b.id = c.message_id
WHERE
a.del_flag = FALSE
AND a.id = #{chatId}
AND c.receiver_id = #{userId}
AND c.read_flag = FALSE
ORDER BY b.id DESC
</select>
<select id="listChatUnreadMessage" resultType="com.qiangesoft.im.pojo.bo.ImChatMessageBO">
SELECT
a.chat_id AS chatId,
COUNT(a.id) AS unreadNum
FROM
im_message a INNER JOIN
im_message_receiver b ON a.id=b.message_id
WHERE
b.receiver_id=#{userId}
AND b.read_flag=FALSE
AND 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 (SELECT
max(a.id)
FROM
im_message a INNER JOIN
im_message_receiver b ON a.id=b.message_id
WHERE
b.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 a
INNER JOIN im_message b
ON b.id = a.message_id
INNER JOIN im_chat c ON c.id = b.chat_id
SET read_flag = 1
WHERE
a.read_flag = 0
AND 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
@Autowired
private IImChatService chatService;
@Autowired
private IImFriendService friendService;
@Autowired
private IImGroupUserService groupUserService;
@Autowired
private IImMessageReceiverService messageReceiverService;
@Autowired
private ISysUserService sysUserService;
@Override
public 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;
}
@Override
public 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)
@Override
public 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;
}
@Override
public List<ImChatMessageBO> listChatUnreadMessage(Long userId, List<Long> chatIdList) {
return baseMapper.listChatUnreadMessage(userId, chatIdList);
}
@Override
public 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 {
@Override
public 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 {
@Autowired
private IImMessageService messageService;
@Autowired
private 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_time
FROM im_chat a
INNER JOIN (SELECT chat_id, max(create_time) create_time FROM im_message GROUP BY chat_id) b
ON a.id = b.chat_id
WHERE a.del_flag = FALSE
AND 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 {
@Autowired
private ISysUserService sysUserService;
@Autowired
private IImFriendService friendService;
@Autowired
private IImGroupService groupService;
@Autowired
private IImGroupUserService groupUserService;
@Autowired
private IImMessageService messageService;
@Override
public void removeChat(Long id) {
LambdaUpdateWrapper<ImChat> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(ImChat::getId, id)
.set(ImChat::getDelFlag, true);
baseMapper.update(null, updateWrapper);
}
@Override
public 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;
}
@Override
public void setTop(Long id) {
LambdaUpdateWrapper<ImChat> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(ImChat::getId, id)
.set(ImChat::getTopFlag, true);
baseMapper.update(null, updateWrapper);
}
@Override
public 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
后续内容见下章