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">
        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

后续内容见下章

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何在Shopee 上选择热销商品?shopee应该在哪选品

在如今激烈竞争的电商市场中&#xff0c;如何通过精准的选品策略提升在Shopee平台上的销售业绩成为卖家们关注的焦点。Shopee作为一个蓬勃发展的电商平台&#xff0c;提供了多种资源和工具来帮助卖家做出明智的选品决策。通过深入了解这些渠道和策略&#xff0c;卖家们可以更好…

第2.4章 StarRocks表设计——分区分桶与副本数

目录 一、数据分布 1.1 概述 1.2 数据分布方式 1.2.1 Round-Robin 1.2.2 Range 1.2.3 List 1.2.4 Hash 1.3 StarRocks的数据分布方式 1.3.1 不分区 Hash分桶 1.3.2 Range分区Hash分桶 三、分区 3.1 分区概述 3.2 创建分区 3.2.1 手动创建分区 3.2.2 批量创建分区…

微服务篇之负载均衡

一、Ribbon负载均衡流程 二、Ribbon负载均衡策略 1. RoundRobinRule&#xff1a;简单轮询服务列表来选择服务器。 2. WeightedResponseTimeRule&#xff1a;按照权重来选择服务器&#xff0c;响应时间越长&#xff0c;权重越小。 3. RandomRule&#xff1a;随机选择一个可用的服…

Java 那些诗一般的 数据类型 (1)

本篇会加入个人的所谓‘鱼式疯言’ ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. &#x1f92d;&#x1f92d;&#x1f92d;可能说的不是那么严谨.但小编初心是能让更多人…

如何实现一个K8S DevicePlugin?

什么是device plugin k8s允许限制容器对资源的使用&#xff0c;比如CPU和内存&#xff0c;并以此作为调度的依据。 当其他非官方支持的设备类型需要参与到k8s的工作流程中时&#xff0c;就需要实现一个device plugin。 Kubernetes提供了一个设备插件框架&#xff0c;你可以用…

一文读懂函数式接口、Lambda表达式、Stream

文章目录 前言版本函数式接口定义特点使用 Lambda表达式主要场景用法无参写法有参写法 Lambda 表达式的基础&#xff1a;函数式接口 类型推断自定义函数接口使用 Lambda 表达式底层实现JDK7JDK8 this 的含义JDK7JDK8 Stream特点操作Stream 流创建中间操作和终端操中间操作无状…

vue.js el-tooltip根据文字长度控制是否提示toolTip

一、需求&#xff1a;如何判断当前文本文字是否超出文本长度&#xff0c;是否需要出现提示toolTip。效果图如下&#xff1a; 二、实现&#xff1a; 1、表格字段鼠标放置el-popover出现 “引用主题” 的具体内容&#xff1b; <!-- 表格字段&#xff1a;引用主题 --> <…

006 矢量数据属性表的使用和关联

1 空间属性关系 1.1 数据导入和图层组合 读取数据 除了使用关键项的连接之外&#xff0c;还有一种根据属性组合中的空间位置关系进行组合的方法。 在本节中&#xff0c;我们将介绍一种方法&#xff0c;用于将空间关系中的多边形数据属性与任意点数据属性相结合。 我们使用的是…

文件上传漏洞--Upload-labs--Pass11--(GET)00绕过

一、环境准备&#xff1a; php版本&#xff1a;推荐 php5.2.17&#xff08;官方推荐版本&#xff09;。小于php5.3.4也可以&#xff0c;但是要在 php.ini 配置文件中手动将 magic_quotes_gpc 的状态改为 Off。 magic_quotes_gpc的作用是对 get请求、post请求、cookie...传入的…

DIcom调试Planar configuration

最近和CBCT组同事调dicom图像 这边得图像模块老不兼容对方得dicom文件。 vtk兼容&#xff0c;自己写得原生解析不兼容。 给对方调好了格式&#xff0c;下次生成文件还会有错。 简单记录下&#xff0c;日后备查。 今天对方又加了 个字段&#xff1a;Planar configuration 查…

【开源】JAVA+Vue.js实现高校学生管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 学生管理模块2.2 学院课程模块2.3 学生选课模块2.4 成绩管理模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 学生表3.2.2 学院课程表3.2.3 学生选课表3.2.4 学生成绩表 四、系统展示五、核心代码5.1 查询课程5.2 新…

FPS游戏漫谈弱网环境时延优化

游戏在弱网情况下会变得体验很差&#xff0c;玩家的直观感受就是我的操作怎么没有反应&#xff0c;整个游戏世界都是一卡一顿的。这个就是因为网络问题导致了游戏体验变差。 那什么是弱网环境&#xff1f;弱网环境就是指网络不好的环境&#xff0c;尤其是移动网络下&#xff0…

Java毕业设计-基于springboot的人才招聘管理系统-第68期

获取源码资料&#xff0c;请移步从戎源码网&#xff1a;从戎源码网_专业的计算机毕业设计网站 项目介绍 基于springboot的人才招聘管理系统&#xff1a;前端jquery、easyui&#xff0c;后端 maven、springmvc、spring、jpa、hibernate&#xff0c;集成职位浏览、我的简历、投…

web前端宝藏题库,适用于0-3年前端工程师...

前些天意外认识了一个前端大佬&#xff0c;某大厂在职&#xff0c;我怕打扰就没敢多聊&#xff0c;没想到大佬挺平易近人&#xff0c;很亲切的给我出谋划策&#xff0c;还给了我一套面试题&#xff0c;难度不是很高&#xff0c;但是胜在全面十分契合我现在的状况&#xff0c;所…

在VsCode中通过Cookie登录LeetCode

在vscode中配置好leetcode之后&#xff0c;一般最常用的就是通过cookie登录leetcode ; 首先点击sign in &#xff0c; 然后选择最下面的 &#xff0c; LeetCode Cookie ! 然后输入username(也就是你的lc用户名) 或者 你leetcode绑定的邮箱 ; 输入完成之后 ; 就是要你输入你的l…

UE5 C++ 创建可缩放的相机

一.要将相机设置在Pawn类里 1.在MyPawn头文件里&#xff0c;加上摇臂和相机组件 #include "GameFramework/SpringArmComponent.h" #include "Camera/CameraComponent.h" 2.在Pawm里声明SceneComponet&#xff0c;SpringArmComponent,CameraComponent组件…

6.【架构师成长之路】职场新人:维护一张能力图谱

文章目录 导言一、能力图谱就是技能体系1、时刻提醒你&#xff0c;你有一个目标2、你可以知道靠近目标的具体方式3、你会变得更加自信 二、周期性review自己的能力图谱1、review能力项进度2、review能力项完整度3、固定周期review 本文总结说明 导言 上两篇文章我们讲了&#…

C语言中关于#include的一些小知识

写代码的过程中&#xff0c;因为手误&#xff0c;重复包含了头文件 可以看到没有报错 如果是你自己编写的头文件&#xff0c;那么如果没加唯一包含标识的话&#xff0c;那么编译器会编译报错的。如果是系统自带的头文件&#xff0c;由于其每个头文件都加了特殊标识&#xff0c…

【动态规划】【矩阵快速幂】LeetCode2851. 字符串转换

作者推荐 【深度优先搜索】【树】【有向图】【推荐】685. 冗余连接 II 涉及知识点 【矩阵快速幂】封装类及测试用例及样例 LeetCode 2851. 字符串转换 给你两个长度都为 n 的字符串 s 和 t 。你可以对字符串 s 执行以下操作&#xff1a; 将 s 长度为 l &#xff08;0 <…

安达发|APS排产软件的机台产线任务甘特图功能详解

在现代制造业中&#xff0c;高级计划与排产是制造业运营的关键环节。为了提高生产效率、降低成本并确保产品质量&#xff0c;企业需要对生产过程进行精细化管理。APS&#xff08;高级计划与排产&#xff09;系统作为一种先进的生产计划和调度工具&#xff0c;可以帮助企业实现这…