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

前言

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

本博客姊妹篇

一、功能描述

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

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

2.1 消息

mapper

xml 复制代码
<?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 复制代码
<?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>
java 复制代码
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);
}
java 复制代码
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

java 复制代码
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);
}
java 复制代码
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);

}
java 复制代码
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);
    }
}
java 复制代码
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

java 复制代码
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 复制代码
<?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>
java 复制代码
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

java 复制代码
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);
}
java 复制代码
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);
    }

}
java 复制代码
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

后续内容见下章

相关推荐
javadaydayup6 分钟前
Apollo 凭什么能 “干掉” 本地配置?
spring boot·后端·spring
FFF-X42 分钟前
Vue3 路由缓存实战:从基础到进阶的完整指南
vue.js·spring boot·缓存
smileNicky13 小时前
SpringBoot系列之从繁琐配置到一键启动之旅
java·spring boot·后端
柏油16 小时前
Spring @TransactionalEventListener 解读
spring boot·后端·spring
小小工匠17 小时前
Maven - Spring Boot 项目打包本地 jar 的 3 种方法
spring boot·maven·jar·system scope
板板正19 小时前
Spring Boot 整合MongoDB
spring boot·后端·mongodb
泉城老铁20 小时前
在高并发场景下,如何优化线程池参数配置
spring boot·后端·架构
泉城老铁20 小时前
Spring Boot中实现多线程6种方式,提高架构性能
spring boot·后端·spring cloud
hrrrrb21 小时前
【Java Web 快速入门】九、事务管理
java·spring boot·后端
布朗克1681 天前
Spring Boot项目通过RestTemplate调用三方接口详细教程
java·spring boot·后端·resttemplate