Jelajahi Sumber

feat(chat): 实现聊天系统核心功能- 新增聊天控制器,提供获取token、会话列表、消息列表等接口
- 实现聊天消息和会话的数据模型及数据库映射
- 添加聊天服务接口及实现,处理用户上线、消息发送、消息已读等业务逻辑
- 集成Redis用于维护用户和客服在线状态- 实现机器人消息处理和未读消息统计功能
- 添加获取客服人员列表的Mapper和SQL查询

nahida 7 bulan lalu
induk
melakukan
2ec67c9416

+ 66 - 0
zksy-admin/src/main/java/com/zksy/web/controller/chat/ChatController.java

@@ -0,0 +1,66 @@
+package com.zksy.web.controller.chat;
+
+import com.zksy.common.annotation.Anonymous;
+import com.zksy.common.core.domain.AjaxResult;
+import com.zksy.service.ChatService;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletRequest;
+
+@RestController
+@RequestMapping("/chat")
+public class ChatController {
+
+    @Autowired
+    private ChatService chatService;
+
+    @GetMapping("/getToken")
+    @ApiOperation("获取聊天token")
+    @Anonymous
+    public AjaxResult getToken(
+            HttpServletRequest request
+    ) {
+
+        return AjaxResult.success("token",chatService.getToken(request));
+    }
+
+    @GetMapping("/getSessionListByUserId")
+    @ApiOperation("根据客服id获取会话列表")
+    public AjaxResult getSessionListByAdminId(
+            String adminId
+    ) {
+        return AjaxResult.success(chatService.getSessionListByAdminId(adminId));
+    }
+
+    @GetMapping("/getChatMessageListBySessionId")
+    @ApiOperation("根据会话id获取聊天消息列表")
+    public AjaxResult getChatMessageListBySessionId(
+            Long sessionId,
+            @RequestParam(required = false, defaultValue = "1") Integer pageNum,
+            @RequestParam(required = false, defaultValue = "20") Integer pageSize
+    ) {
+        return AjaxResult.success(chatService.getChatMessageListBySessionId(sessionId, pageNum, pageSize));
+    }
+
+    @GetMapping("/readMessage")
+    @ApiOperation("标记消息为已读")
+    public AjaxResult readMessage(
+            Long messageId
+    ) {
+        return AjaxResult.success(chatService.readMessage(messageId));
+    }
+
+    @GetMapping("/getRobotMessage")
+    @ApiOperation("获取机器人消息")
+    public AjaxResult getRobotMessage(
+            @RequestParam(required = false, defaultValue = "1") Integer pageNum,
+            @RequestParam(required = false, defaultValue = "20") Integer pageSize
+    ) {
+        return AjaxResult.success(chatService.getRobotMessage(pageNum, pageSize));
+    }
+}

+ 66 - 0
zksy-ws/src/main/java/com/zksy/domain/ChatMessage.java

@@ -0,0 +1,66 @@
+package com.zksy.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+import lombok.Data;
+
+/**
+ * 
+ * @TableName chat_message
+ */
+@TableName(value ="chat_message")
+@Data
+public class ChatMessage implements Serializable {
+    /**
+     * 消息ID
+     */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 所属会话ID
+     */
+    private Long sessionId;
+
+    /**
+     * 发送者ID
+     */
+    private String fromId;
+
+    /**
+     * 发送者角色
+     */
+    private Object fromRole;
+
+    /**
+     * 接收者ID(单聊场景用)
+     */
+    private String toId;
+
+    /**
+     * 消息类型
+     */
+    private Object msgType;
+
+    /**
+     * 消息内容(JSON 或纯文本)
+     */
+    private String content;
+
+    /**
+     * 消息状态
+     */
+    private Object status;
+
+    /**
+     * 发送时间
+     */
+    private LocalDateTime createdAt;
+
+    @TableField(exist = false)
+    private static final long serialVersionUID = 1L;
+}

+ 51 - 0
zksy-ws/src/main/java/com/zksy/domain/ChatSession.java

@@ -0,0 +1,51 @@
+package com.zksy.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+import lombok.Data;
+
+/**
+ * 
+ * @TableName chat_session
+ */
+@TableName(value ="chat_session")
+@Data
+public class ChatSession implements Serializable {
+    /**
+     * 会话ID
+     */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 用户ID
+     */
+    private String userId;
+
+    /**
+     * 分配的客服ID
+     */
+    private String assignedAdminId;
+
+    /**
+     * 会话状态
+     */
+    private String status;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createdAt;
+
+    /**
+     * 更新时间
+     */
+    private LocalDateTime updatedAt;
+
+    @TableField(exist = false)
+    private static final long serialVersionUID = 1L;
+}

+ 20 - 0
zksy-ws/src/main/java/com/zksy/domain/dto/ChatMessageDto.java

@@ -0,0 +1,20 @@
+package com.zksy.domain.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class ChatMessageDto {
+    private String from;      // 发送方 (user_xxx / admin)
+    private String to;        // 接收方 (目标用户 ID 或 admin)
+    private String type;      // 消息类型: chat / system / read
+    private String content;   // 消息内容
+    private String role;      // 角色
+    private long timestamp;   // 时间戳
+    private Long sessionId;   // 会话 ID
+    private Long[] messageIds;   // 消息 ID列表
+    private Long messageId;   // 消息 ID
+}

+ 18 - 0
zksy-ws/src/main/java/com/zksy/domain/vo/ChatSeesionVo.java

@@ -0,0 +1,18 @@
+package com.zksy.domain.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@AllArgsConstructor
+@NoArgsConstructor
+@Data
+public class ChatSeesionVo {
+    private Long id;
+    private String userId;
+    private Boolean isOnline;
+    private Boolean isTyping;
+    private String lastMessage;
+    private String lastMessageTime;
+    private Integer unreadCount;
+}

+ 10 - 0
zksy-ws/src/main/java/com/zksy/mapper/ChatMapper.java

@@ -0,0 +1,10 @@
+package com.zksy.mapper;
+
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+@Mapper
+public interface ChatMapper {
+    List<String> getAllClientService();
+}

+ 18 - 0
zksy-ws/src/main/java/com/zksy/mapper/ChatMessageMapper.java

@@ -0,0 +1,18 @@
+package com.zksy.mapper;
+
+import com.zksy.domain.ChatMessage;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+* @author hxb
+* @description 针对表【chat_message】的数据库操作Mapper
+* @createDate 2025-09-26 11:04:58
+* @Entity com.zksy.domain.ChatMessage
+*/
+public interface ChatMessageMapper extends BaseMapper<ChatMessage> {
+
+}
+
+
+
+

+ 18 - 0
zksy-ws/src/main/java/com/zksy/mapper/ChatSessionMapper.java

@@ -0,0 +1,18 @@
+package com.zksy.mapper;
+
+import com.zksy.domain.ChatSession;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+* @author hxb
+* @description 针对表【chat_session】的数据库操作Mapper
+* @createDate 2025-09-26 11:05:49
+* @Entity com.zksy.domain.ChatSession
+*/
+public interface ChatSessionMapper extends BaseMapper<ChatSession> {
+
+}
+
+
+
+

+ 13 - 0
zksy-ws/src/main/java/com/zksy/service/ChatMessageService.java

@@ -0,0 +1,13 @@
+package com.zksy.service;
+
+import com.zksy.domain.ChatMessage;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+* @author hxb
+* @description 针对表【chat_message】的数据库操作Service
+* @createDate 2025-09-26 11:04:58
+*/
+public interface ChatMessageService extends IService<ChatMessage> {
+
+}

+ 35 - 0
zksy-ws/src/main/java/com/zksy/service/ChatService.java

@@ -0,0 +1,35 @@
+package com.zksy.service;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.zksy.domain.ChatMessage;
+import com.zksy.domain.dto.ChatMessageDto;
+import com.zksy.domain.vo.ChatSeesionVo;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public interface ChatService {
+    void onUserLogin(String userId);
+    void onAdminLogin(String adminId);
+    Map<String,Long> onUserMessage(ChatMessageDto chatMsg);
+    Map<String,Long> onAdminMessage(ChatMessageDto chatMsg);
+    void onUserOffline(String userId);
+    void onAdminOffline(String adminId);
+    void onHeartbeat(ChatMessageDto from);
+    void onNoAvailableAdmin(ChatMessageDto chatMsg);
+    void onMessageSendFailure(ChatMessageDto chatMsg);
+    void onReadMessage(ChatMessageDto chatMsg);
+    boolean isClientService(String from);
+
+
+    String getToken(HttpServletRequest request);
+    List<ChatSeesionVo> getSessionListByAdminId(String adminId);
+    Map<String, Object> getChatMessageListBySessionId(Long sessionId, Integer pageNum, Integer pageSize);
+
+    String readMessage(Long messageId);
+
+
+    Page<ChatMessage> getRobotMessage(Integer pageNum, Integer pageSize);
+}

+ 13 - 0
zksy-ws/src/main/java/com/zksy/service/ChatSessionService.java

@@ -0,0 +1,13 @@
+package com.zksy.service;
+
+import com.zksy.domain.ChatSession;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+* @author hxb
+* @description 针对表【chat_session】的数据库操作Service
+* @createDate 2025-09-26 11:05:49
+*/
+public interface ChatSessionService extends IService<ChatSession> {
+
+}

+ 22 - 0
zksy-ws/src/main/java/com/zksy/service/impl/ChatMessageServiceImpl.java

@@ -0,0 +1,22 @@
+package com.zksy.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.zksy.domain.ChatMessage;
+import com.zksy.service.ChatMessageService;
+import com.zksy.mapper.ChatMessageMapper;
+import org.springframework.stereotype.Service;
+
+/**
+* @author hxb
+* @description 针对表【chat_message】的数据库操作Service实现
+* @createDate 2025-09-26 11:04:58
+*/
+@Service
+public class ChatMessageServiceImpl extends ServiceImpl<ChatMessageMapper, ChatMessage>
+    implements ChatMessageService{
+
+}
+
+
+
+

+ 316 - 0
zksy-ws/src/main/java/com/zksy/service/impl/ChatServiceImpl.java

@@ -0,0 +1,316 @@
+package com.zksy.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.zksy.common.exception.BusinessException;
+import com.zksy.domain.ChatMessage;
+import com.zksy.domain.ChatSession;
+import com.zksy.domain.dto.ChatMessageDto;
+import com.zksy.domain.vo.ChatSeesionVo;
+import com.zksy.mapper.ChatMapper;
+import com.zksy.mapper.ChatMessageMapper;
+import com.zksy.mapper.ChatSessionMapper;
+import com.zksy.service.ChatService;
+import lombok.RequiredArgsConstructor;
+
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+import javax.servlet.http.HttpServletRequest;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+@Service
+@RequiredArgsConstructor
+public class ChatServiceImpl implements ChatService {
+
+    private final ChatMessageMapper chatMessageMapper;
+    private final ChatSessionMapper chatSessionMapper;
+    private final RedisTemplate<Object, Object> redisTemplate;
+    private final ChatMapper chatMapper;
+
+
+    @PostConstruct
+    public void clearOnlineStatusOnStartup() {
+        redisTemplate.delete("online:users");
+        redisTemplate.delete("online:admins");
+    }
+
+    @Override
+    public void onUserLogin(String userId) {
+        System.out.println("业务逻辑:记录用户上线日志 -> " + userId);
+        redisTemplate.opsForSet().add("online:users", userId);
+    }
+
+    @Override
+    public void onAdminLogin(String adminId) {
+        System.out.println("业务逻辑:记录客服上线日志 -> " + adminId);
+        redisTemplate.opsForSet().add("online:admins", adminId);
+    }
+
+    @Override
+    public Map<String, Long> onUserMessage(ChatMessageDto chatMsg) {
+        Long sessionId = getOrCreateSession(chatMsg.getFrom(), chatMsg.getTo());
+        Long messageId = saveMessage(sessionId, chatMsg, "sent"); // 用户发出消息,默认 sent
+        return Map.of("sessionId", sessionId, "messageId", messageId);
+    }
+
+    @Override
+    public Map<String, Long> onAdminMessage(ChatMessageDto chatMsg) {
+        Long sessionId = getOrCreateSession(chatMsg.getTo(), chatMsg.getFrom());
+        Long messageId = saveMessage(sessionId, chatMsg, "sent");// 客服发出消息,默认 sent
+        return Map.of("sessionId", sessionId, "messageId", messageId);
+    }
+
+    @Override
+    public void onUserOffline(String userId) {
+        System.out.println("业务逻辑:标记用户离线 -> " + userId);
+        redisTemplate.opsForSet().remove("online:users", userId);
+    }
+
+    @Override
+    public void onAdminOffline(String adminId) {
+        System.out.println("业务逻辑:标记客服离线 -> " + adminId);
+        redisTemplate.opsForSet().remove("online:admins", adminId);
+    }
+
+    @Override
+    public void onHeartbeat(ChatMessageDto chatMsg) {
+        if (!chatMsg.getRole().equals("admin")) {
+            redisTemplate.opsForSet().add("online:users", chatMsg.getFrom());
+        } else {
+            redisTemplate.opsForSet().add("online:admins", chatMsg.getFrom());
+        }
+    }
+
+    @Override
+    public void onNoAvailableAdmin(ChatMessageDto chatMsg) {
+        chatMsg.setTo("robot");
+        System.out.println("业务逻辑:用户无客服可联系 -> " + chatMsg.getFrom());
+        // 可以在这里生成一个临时消息
+        saveMessage(0L, chatMsg, "sent"); // 系统消息
+    }
+
+    @Override
+    public void onMessageSendFailure(ChatMessageDto chatMsg) {
+        Long sessionId = getOrCreateSession(chatMsg.getFrom(), chatMsg.getTo());
+        saveMessage(sessionId, chatMsg, "failed"); // 标记失败
+    }
+
+    @Override
+    public void onReadMessage(ChatMessageDto chatMsg) {
+        if (chatMsg.getMessageIds() == null || chatMsg.getMessageIds().length == 0) {
+            return;
+        }
+
+        try {
+            // 批量更新消息状态为已读
+            LambdaUpdateWrapper<ChatMessage> updateWrapper = new LambdaUpdateWrapper<ChatMessage>()
+                    .in(ChatMessage::getId, Arrays.asList(chatMsg.getMessageIds()))
+                    .ne(ChatMessage::getStatus, "read"); // 只更新状态不是已读的消息
+
+            ChatMessage updateMessage = new ChatMessage();
+            updateMessage.setStatus("read");
+
+            int updatedCount = chatMessageMapper.update(updateMessage, updateWrapper);
+
+            if (updatedCount > 0) {
+                System.out.println("成功标记 " + updatedCount + " 条消息为已读状态");
+            }
+        } catch (Exception e) {
+            System.err.println("标记消息已读失败: " + e.getMessage());
+            // 可以选择抛出异常或记录日志
+            throw new BusinessException("标记消息已读失败");
+        }
+    }
+
+    @Override
+    public boolean isClientService(String from) {
+        var clients = chatMapper.getAllClientService();
+        return clients.contains(from);
+    }
+
+    @Override
+    public String getToken(HttpServletRequest request) {
+        //获取ip地址 计数
+        String ip = request.getRemoteAddr();
+        Integer count = (Integer) redisTemplate.opsForValue().get(ip);
+
+        if (count != null && count >= 999) {
+            throw new BusinessException("您已访问次数过多,稍后再试");
+        }
+        if (count == null) {
+            count = 0;
+        }
+        redisTemplate.opsForValue().set(ip, count + 1, 1, TimeUnit.HOURS);
+
+        String token = java.util.UUID.randomUUID().toString().replaceAll("-", "");
+        redisTemplate.opsForValue().set("ws:token:" + token, 1, 30, TimeUnit.SECONDS);
+        return token;
+    }
+
+    @Override
+    public List<ChatSeesionVo> getSessionListByAdminId(String adminId) {
+        // 1. 先获取所有会话
+        var list = chatSessionMapper.selectList(
+                new LambdaQueryWrapper<ChatSession>()
+                        .eq(ChatSession::getAssignedAdminId, adminId));
+
+        if (list.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        // 2. 批量获取所有会话ID
+        List<Long> sessionIds = list.stream()
+                .map(ChatSession::getId)
+                .collect(Collectors.toList());
+
+        // 3. 批量查询未读消息数量 - 按会话分组统计
+        Map<Long, Long> unreadCountMap = chatMessageMapper.selectList(
+                        new LambdaQueryWrapper<ChatMessage>()
+                                .in(ChatMessage::getSessionId, sessionIds)
+                                .ne(ChatMessage::getStatus, "read"))
+                .stream()
+                .collect(Collectors.groupingBy(
+                        ChatMessage::getSessionId,
+                        Collectors.counting()
+                ));
+
+        // 4. 批量查询最后一条消息 - 按会话获取最新消息
+        Map<Long, ChatMessage> lastMessageMap = chatMessageMapper.selectList(
+                        new LambdaQueryWrapper<ChatMessage>()
+                                .in(ChatMessage::getSessionId, sessionIds)
+                                .orderByDesc(ChatMessage::getCreatedAt))
+                .stream()
+                .collect(Collectors.toMap(
+                        ChatMessage::getSessionId,
+                        message -> message,
+                        (existing, replacement) -> existing // 保留第一个(最新的)
+                ));
+
+        // 5. 构建结果
+        return list.stream().map(session -> {
+            var userId = session.getUserId();
+            var userOnline = redisTemplate.opsForSet().isMember("online:users", userId);
+
+            // 获取未读数量
+            Long unreadCount = unreadCountMap.getOrDefault(session.getId(), 0L);
+            var unRead = unreadCount > 100 ? 100 : unreadCount.intValue();
+
+            // 获取最后一条消息
+            ChatMessage lastMessageEntity = lastMessageMap.get(session.getId());
+            String lastMessage = "";
+            String lastMessageTime = "";
+
+            if (lastMessageEntity != null) {
+                lastMessage = lastMessageEntity.getContent();
+                if (lastMessageEntity.getCreatedAt() != null) {
+                    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM-dd HH:mm");
+                    lastMessageTime = lastMessageEntity.getCreatedAt().format(formatter);
+                }
+            }
+
+            return new ChatSeesionVo(
+                    session.getId(),
+                    session.getUserId(),
+                    userOnline,
+                    false,
+                    lastMessage,
+                    lastMessageTime,
+                    unRead
+            );
+        }).collect(Collectors.toList());
+    }
+
+    @Override
+    public HashMap<String, Object> getChatMessageListBySessionId(Long sessionId, Integer pageNum, Integer pageSize) {
+        //分页根据时间倒序
+        Page<ChatMessage> page = chatMessageMapper.selectPage(
+                new Page<>(pageNum, pageSize),
+                new LambdaQueryWrapper<ChatMessage>()
+                        .eq(ChatMessage::getSessionId, sessionId)
+                        .orderByDesc(ChatMessage::getCreatedAt)
+        );
+
+        HashMap<String, Object> result = new HashMap<>();
+        result.put("messages", page.getRecords());
+        result.put("total", page.getTotal());
+        return result;
+    }
+
+    @Override
+    public String readMessage(Long messageId) {
+        // 检查消息是否存在
+        ChatMessage message = chatMessageMapper.selectById(messageId);
+        if (message == null) {
+            throw new BusinessException("消息不存在");
+        }
+        chatMessageMapper.update(
+                new ChatMessage(),
+                new LambdaUpdateWrapper<ChatMessage>()
+                        .eq(ChatMessage::getId, messageId)
+                        .set(ChatMessage::getStatus, "read")
+        );
+        return "消息已标记为已读";
+    }
+
+    @Override
+    public Page<ChatMessage> getRobotMessage(Integer pageNum, Integer pageSize) {
+        return chatMessageMapper.selectPage(
+                new Page<>(pageNum, pageSize),
+                new LambdaQueryWrapper<ChatMessage>()
+                        .eq(ChatMessage::getToId, "robot")
+                        .ne(ChatMessage::getStatus, "read")
+                        .orderByDesc(ChatMessage::getCreatedAt)
+        );
+    }
+
+    /**
+     * 获取会话,没有则新建
+     */
+    private Long getOrCreateSession(String userId, String adminId) {
+        ChatSession session = chatSessionMapper.selectOne(
+                new LambdaQueryWrapper<ChatSession>()
+                        .eq(ChatSession::getUserId, userId)
+                        .eq(ChatSession::getAssignedAdminId, adminId)
+                        .last("LIMIT 1")
+        );
+
+        if (session == null) {
+            session = new ChatSession();
+            session.setUserId(userId);
+            session.setAssignedAdminId(adminId);
+            session.setStatus("active");
+            session.setCreatedAt(LocalDateTime.now());
+            session.setUpdatedAt(LocalDateTime.now());
+            chatSessionMapper.insert(session);
+        } else {
+            session.setUpdatedAt(LocalDateTime.now());
+            chatSessionMapper.updateById(session);
+        }
+
+        return session.getId();
+    }
+
+    /**
+     * 保存消息
+     */
+    private Long saveMessage(Long sessionId, ChatMessageDto chatMsg, String status) {
+        ChatMessage msg = new ChatMessage();
+        msg.setSessionId(sessionId);
+        msg.setFromId(chatMsg.getFrom());
+        msg.setFromRole(chatMsg.getRole());
+        msg.setToId(chatMsg.getTo());
+        msg.setMsgType(chatMsg.getType());
+        msg.setContent(chatMsg.getContent());
+        msg.setStatus(status);
+        msg.setCreatedAt(LocalDateTime.now());
+        chatMessageMapper.insert(msg);
+        return msg.getId();
+    }
+}

+ 22 - 0
zksy-ws/src/main/java/com/zksy/service/impl/ChatSessionServiceImpl.java

@@ -0,0 +1,22 @@
+package com.zksy.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.zksy.domain.ChatSession;
+import com.zksy.service.ChatSessionService;
+import com.zksy.mapper.ChatSessionMapper;
+import org.springframework.stereotype.Service;
+
+/**
+* @author hxb
+* @description 针对表【chat_session】的数据库操作Service实现
+* @createDate 2025-09-26 11:05:49
+*/
+@Service
+public class ChatSessionServiceImpl extends ServiceImpl<ChatSessionMapper, ChatSession>
+    implements ChatSessionService{
+
+}
+
+
+
+

+ 16 - 0
zksy-ws/src/main/resources/mapper/ChatMapper.xml

@@ -0,0 +1,16 @@
+<?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.zksy.mapper.ChatMapper">
+
+    <select id="getAllClientService" resultType="java.lang.String">
+        SELECT DISTINCT
+            u.user_id
+        FROM sys_user u
+                 INNER JOIN sys_user_role ur ON u.user_id = ur.user_id
+                 INNER JOIN sys_role r ON ur.role_id = r.role_id
+        WHERE r.role_key = 'clientService'
+           OR r.role_key = 'admin'
+    </select>
+</mapper>

+ 24 - 0
zksy-ws/src/main/resources/mapper/ChatMessageMapper.xml

@@ -0,0 +1,24 @@
+<?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.zksy.mapper.ChatMessageMapper">
+
+    <resultMap id="BaseResultMap" type="com.zksy.domain.ChatMessage">
+            <id property="id" column="id" jdbcType="BIGINT"/>
+            <result property="sessionId" column="session_id" jdbcType="BIGINT"/>
+            <result property="fromId" column="from_id" jdbcType="VARCHAR"/>
+            <result property="fromRole" column="from_role" jdbcType="OTHER"/>
+            <result property="toId" column="to_id" jdbcType="VARCHAR"/>
+            <result property="msgType" column="msg_type" jdbcType="OTHER"/>
+            <result property="content" column="content" jdbcType="VARCHAR"/>
+            <result property="status" column="status" jdbcType="OTHER"/>
+            <result property="createdAt" column="created_at" jdbcType="TIMESTAMP"/>
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        id,session_id,from_id,
+        from_role,to_id,msg_type,
+        content,status,created_at
+    </sql>
+</mapper>

+ 20 - 0
zksy-ws/src/main/resources/mapper/ChatSessionMapper.xml

@@ -0,0 +1,20 @@
+<?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.zksy.mapper.ChatSessionMapper">
+
+    <resultMap id="BaseResultMap" type="com.zksy.domain.ChatSession">
+            <id property="id" column="id" jdbcType="BIGINT"/>
+            <result property="userId" column="user_id" jdbcType="VARCHAR"/>
+            <result property="assignedAdminId" column="assigned_admin_id" jdbcType="VARCHAR"/>
+            <result property="status" column="status" jdbcType="OTHER"/>
+            <result property="createdAt" column="created_at" jdbcType="TIMESTAMP"/>
+            <result property="updatedAt" column="updated_at" jdbcType="TIMESTAMP"/>
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        id,user_id,assigned_admin_id,
+        status,created_at,updated_at
+    </sql>
+</mapper>