diff --git a/src/main/java/com/bao/dating/config/RedisConfig.java b/src/main/java/com/bao/dating/config/RedisConfig.java index 93dcbbd..358649a 100644 --- a/src/main/java/com/bao/dating/config/RedisConfig.java +++ b/src/main/java/com/bao/dating/config/RedisConfig.java @@ -14,9 +14,9 @@ import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration public class RedisConfig { @Bean - public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { + public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { // 创建RedisTemplate对象 - RedisTemplate redisTemplate = new RedisTemplate<>(); + RedisTemplate redisTemplate = new RedisTemplate<>(); // 设置redis的连接工厂对象 redisTemplate.setConnectionFactory(redisConnectionFactory); diff --git a/src/main/java/com/bao/dating/config/WebSocketConfig.java b/src/main/java/com/bao/dating/config/WebSocketConfig.java new file mode 100644 index 0000000..774b3aa --- /dev/null +++ b/src/main/java/com/bao/dating/config/WebSocketConfig.java @@ -0,0 +1,35 @@ +package com.bao.dating.config; + + +import com.bao.dating.handler.ChatWebSocketHandler; +import com.bao.dating.interceptor.WsAuthInterceptor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.config.annotation.EnableWebSocket; +import org.springframework.web.socket.config.annotation.WebSocketConfigurer; +import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; + +/** + * WebSocket 配置类 + * @author lenovo + */ +@Configuration +@EnableWebSocket +public class WebSocketConfig implements WebSocketConfigurer { + @Autowired + private ChatWebSocketHandler chatWebSocketHandler; + + @Autowired + private WsAuthInterceptor wsAuthInterceptor; + + /** + * 注册 WebSocket 处理器 + * @param registry + */ + @Override + public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { + registry.addHandler(chatWebSocketHandler, "/ws/chat") + .addInterceptors(wsAuthInterceptor) + .setAllowedOrigins("*"); + } +} diff --git a/src/main/java/com/bao/dating/handler/ChatWebSocketHandler.java b/src/main/java/com/bao/dating/handler/ChatWebSocketHandler.java new file mode 100644 index 0000000..64288c6 --- /dev/null +++ b/src/main/java/com/bao/dating/handler/ChatWebSocketHandler.java @@ -0,0 +1,107 @@ +package com.bao.dating.handler; + +import com.bao.dating.message.WsMessage; +import com.bao.dating.pojo.dto.ChatRecordSendDTO; +import com.bao.dating.pojo.vo.ChatRecordsVO; +import com.bao.dating.service.ChatService; +import com.bao.dating.session.WsSessionManager; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.handler.TextWebSocketHandler; + +/** + * WebSocket处理器类 + * @author lenovo + */ +@Slf4j +@Component +public class ChatWebSocketHandler extends TextWebSocketHandler { + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private ChatService chatService; + + @Autowired + private WsSessionManager sessionManager; + + + /** + * 用户建立连接(上线) + * @param session + */ + @Override + public void afterConnectionEstablished(WebSocketSession session) { + Long userId = (Long) session.getAttributes().get("userId"); + sessionManager.addSession(userId, session); + log.info("用户 " + userId + " 已上线"); + } + + /** + * 接收并处理消息 + * @param session + * @param message + */ + @Override + protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception{ + // 获取当前用户ID + Long senderUserId = (Long) session.getAttributes().get("userId"); + if (senderUserId == null) { + log.error("WebSocket session 中未找到 userId"); + return; + } + // 解析消息 + WsMessage wsMessage = + objectMapper.readValue(message.getPayload(), + new TypeReference>(){}); + + // 处理私聊消息 + if ("chat".equals(wsMessage.getType())) { + handlePrivateChat(senderUserId, wsMessage.getData()); + } + } + + /** + * 私聊处理 + */ + private void handlePrivateChat(Long senderUserId, ChatRecordSendDTO dto) throws Exception { + + // 1. 消息入库 + 会话更新 + ChatRecordsVO chatRecordsVO = chatService.createSession(senderUserId, dto); + + // 2. 推送给接收方 + WebSocketSession receiverSession = + sessionManager.getSession(dto.getReceiverUserId()); + + if (receiverSession != null && receiverSession.isOpen()) { + WsMessage pushMsg = new WsMessage<>(); + pushMsg.setType("chat"); + pushMsg.setData(chatRecordsVO); + + receiverSession.sendMessage( + new TextMessage(objectMapper.writeValueAsString(pushMsg)) + ); + } + } + + + /** + * 用户断开连接(下线) + * @param session + * @param status + */ + @Override + public void afterConnectionClosed(WebSocketSession session, CloseStatus status){ + // 下线处理 + Long userId = (Long) session.getAttributes().get("userId"); + sessionManager.removeSession(userId); + log.info("用户 " + userId + " 已下线"); + } +} \ No newline at end of file diff --git a/src/main/java/com/bao/dating/interceptor/TokenInterceptor.java b/src/main/java/com/bao/dating/interceptor/TokenInterceptor.java index 8e11f59..3203fc9 100644 --- a/src/main/java/com/bao/dating/interceptor/TokenInterceptor.java +++ b/src/main/java/com/bao/dating/interceptor/TokenInterceptor.java @@ -23,7 +23,7 @@ import org.springframework.web.servlet.HandlerInterceptor; public class TokenInterceptor implements HandlerInterceptor { @Autowired - private RedisTemplate redisTemplate; + private RedisTemplate redisTemplate; /** * 在请求处理之前进行拦截 @@ -68,7 +68,7 @@ public class TokenInterceptor implements HandlerInterceptor { } // 解析 token - String userId = JwtUtil.getSubjectFromToken(token); + Long userId = Long.valueOf(JwtUtil.getSubjectFromToken(token)); // 从Redis获取存储的token进行比对 Object redisTokenObj = redisTemplate.opsForValue().get("login:token:" + userId); @@ -85,7 +85,7 @@ public class TokenInterceptor implements HandlerInterceptor { log.info("用户: {}", userId); // 保存 userId 到 ThreadLocal - UserContext.setUserId(Long.valueOf(userId)); + UserContext.setUserId(userId); return true; } catch (Exception e) { log.error("Token 校验失败: {}", e.getMessage()); diff --git a/src/main/java/com/bao/dating/interceptor/WsAuthInterceptor.java b/src/main/java/com/bao/dating/interceptor/WsAuthInterceptor.java new file mode 100644 index 0000000..4b29ca8 --- /dev/null +++ b/src/main/java/com/bao/dating/interceptor/WsAuthInterceptor.java @@ -0,0 +1,113 @@ +package com.bao.dating.interceptor; + +import com.bao.dating.util.JwtUtil; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.http.server.ServletServerHttpRequest; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.server.HandshakeInterceptor; + +import javax.servlet.http.HttpServletRequest; +import java.util.Map; + +/** + * WebSocket 认证拦截器 + */ +@Slf4j +@Component +public class WsAuthInterceptor implements HandshakeInterceptor { + + @Autowired + private RedisTemplate redisTemplate; + /** + * 拦截WebSocket连接请求 + */ + @Override + public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map attributes) throws Exception { + log.info("开始WebSocket握手认证"); + + // 获取请求参数 + if (!(request instanceof ServletServerHttpRequest)) { + log.error("WebSocket握手失败:非HTTP请求"); + return false; + } + + // 获取HttpServletRequest对象 + HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest(); + + // 从URL参数中获取token + String token = servletRequest.getParameter("token"); + log.info("从URL参数获取到的token: {}", token != null ? "存在" : "不存在"); + + if (StringUtils.isBlank(token)) { + log.error("WebSocket握手失败:令牌丢失"); + return false; + } + + try { + // 验证 token 是否有效(包括是否过期) + if (!JwtUtil.validateToken(token)) { + log.error("Token无效或已过期: {}", token); + return false; + } + + // 检查 token 是否在黑名单中 + String blacklistKey = "jwt:blacklist:" + token; + Object blacklistToken = redisTemplate.opsForValue().get(blacklistKey); + if (blacklistToken != null) { + log.error("Token已在黑名单中: {}", token); + return false; + } + + // 验证token并获取用户ID + String userIdStr = JwtUtil.getSubjectFromToken(token); + log.info("从token解析出的用户ID: {}", userIdStr); + + if (StringUtils.isBlank(userIdStr) || !userIdStr.matches("\\d+")) { + log.error("无效的用户ID格式: {}", userIdStr); + return false; + } + + Long userId = Long.valueOf(userIdStr); + + // 从Redis获取存储的token进行比对 + String redisTokenKey = "login:token:" + userId; + Object redisTokenObj = redisTemplate.opsForValue().get(redisTokenKey); + String redisToken = redisTokenObj != null ? redisTokenObj.toString() : null; + log.info("Redis中存储的token: {}", redisToken != null ? "存在" : "不存在"); + + // 验证Redis中的token是否存在且匹配 + if (redisToken == null || !redisToken.equals(token)) { + log.error("登录已失效 - Redis中token不存在或不匹配"); + return false; + } + + log.info("WebSocket认证成功,用户ID: {}", userId); + // 将用户ID保存到attributes中 + attributes.put("userId", userId); + return true; + } + catch (NumberFormatException e) { + log.error("用户ID格式转换异常: {}", e.getMessage()); + return false; + } + catch (Exception e) { + log.error("WebSocket握手失败:{}", e.getMessage(), e); + return false; + } + } + + @Override + public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) { + if (exception != null) { + log.error("WebSocket握手后出现异常:", exception); + } else { + log.info("WebSocket握手完成"); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/bao/dating/mapper/ChatRecordsMapper.java b/src/main/java/com/bao/dating/mapper/ChatRecordsMapper.java new file mode 100644 index 0000000..49b21ab --- /dev/null +++ b/src/main/java/com/bao/dating/mapper/ChatRecordsMapper.java @@ -0,0 +1,13 @@ +package com.bao.dating.mapper; + + +import com.bao.dating.pojo.entity.ChatRecords; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface ChatRecordsMapper { + /** + * 插入聊天记录 + */ + int insert(ChatRecords chatRecords); +} diff --git a/src/main/java/com/bao/dating/mapper/ChatSessionsMapper.java b/src/main/java/com/bao/dating/mapper/ChatSessionsMapper.java new file mode 100644 index 0000000..3cc92a5 --- /dev/null +++ b/src/main/java/com/bao/dating/mapper/ChatSessionsMapper.java @@ -0,0 +1,39 @@ +package com.bao.dating.mapper; + + +import com.bao.dating.pojo.entity.ChatRecords; +import com.bao.dating.pojo.entity.ChatSessions; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +@Mapper +public interface ChatSessionsMapper { + /** + * 如果发送方不存在会话则创建 + * @param chatSessions 会话 + * @return 影响行数 + */ + int insertIfNotExistsForSender(ChatSessions chatSessions); + + /** + * 如果接收方不存在会话则创建 + * @param chatSessions 会话 + * @return 影响行数 + */ + int insertIfNotExistsForReceiver(ChatSessions chatSessions); + + /** + * 更新发送方的会话信息 + * @param chatSessions 会话 + * @return 影响行数 + */ + int updateSessionForSender(ChatSessions chatSessions); + + /** + * 更新接收方的会话信息 + * @param chatSessions 会话 + * @return 影响行数 + */ + int updateSessionForReceiver(ChatSessions chatSessions); + +} diff --git a/src/main/java/com/bao/dating/message/WsMessage.java b/src/main/java/com/bao/dating/message/WsMessage.java new file mode 100644 index 0000000..a9df3fb --- /dev/null +++ b/src/main/java/com/bao/dating/message/WsMessage.java @@ -0,0 +1,17 @@ +package com.bao.dating.message; + +import lombok.Data; + +/** + * WebSocket 消息 + * @author KilLze + */ +@Data +public class WsMessage { + + /** 消息类型:chat / read / system */ + private String type; + + /** 消息体 */ + private T data; +} diff --git a/src/main/java/com/bao/dating/pojo/dto/ChatRecordSendDTO.java b/src/main/java/com/bao/dating/pojo/dto/ChatRecordSendDTO.java new file mode 100644 index 0000000..68e8efd --- /dev/null +++ b/src/main/java/com/bao/dating/pojo/dto/ChatRecordSendDTO.java @@ -0,0 +1,17 @@ +package com.bao.dating.pojo.dto; + +import lombok.Data; + +/** + * 聊天记录发送数据传输对象 + * @author KilLze + */ +@Data +public class ChatRecordSendDTO { + /** 接收者用户ID */ + private Long receiverUserId; + /** 消息内容 */ + private String messageContent; + /** 消息类型 (1-文本消息,2-文件消息) */ + private Integer messageType; +} diff --git a/src/main/java/com/bao/dating/pojo/dto/ChatRecordsDTO.java b/src/main/java/com/bao/dating/pojo/dto/ChatRecordsDTO.java new file mode 100644 index 0000000..dc2d6b8 --- /dev/null +++ b/src/main/java/com/bao/dating/pojo/dto/ChatRecordsDTO.java @@ -0,0 +1,32 @@ +package com.bao.dating.pojo.dto; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 聊天记录数据传输对象 + * @author KilLze + */ +@Data +public class ChatRecordsDTO { + + /** 聊天记录ID */ + private Long chatId; + /** 发送者用户ID */ + private Long senderUserId; + /** 接收者用户ID */ + private Long receiverUserId; + /** 消息内容 */ + private String messageContent; + /** 消息类型 (1-文本消息,2-文件消息) */ + private Integer messageType; + /** 阅读状态 (0-未读,1-已读) */ + private Integer readStatus; + /** 阅读时间 */ + private LocalDateTime readTime; + /** 发送时间 */ + private LocalDateTime sendTime; + /** 消息状态 (1-正常,2-已撤回,3-已删除) */ + private Integer messageStatus; +} diff --git a/src/main/java/com/bao/dating/pojo/dto/ChatSessionCreateDTO.java b/src/main/java/com/bao/dating/pojo/dto/ChatSessionCreateDTO.java new file mode 100644 index 0000000..51ff00a --- /dev/null +++ b/src/main/java/com/bao/dating/pojo/dto/ChatSessionCreateDTO.java @@ -0,0 +1,20 @@ +package com.bao.dating.pojo.dto; + +import lombok.Data; + +/** + * 创建会话数据传输对象 + * @author KilLze + */ +@Data +public class ChatSessionCreateDTO { + + /** 所属用户ID */ + private Long userId; + + /** 目标用户ID */ + private Long targetUserId; + + /** 会话名称 */ + private String sessionName; +} diff --git a/src/main/java/com/bao/dating/pojo/dto/ChatSessionUpdateDTO.java b/src/main/java/com/bao/dating/pojo/dto/ChatSessionUpdateDTO.java new file mode 100644 index 0000000..b020868 --- /dev/null +++ b/src/main/java/com/bao/dating/pojo/dto/ChatSessionUpdateDTO.java @@ -0,0 +1,23 @@ +package com.bao.dating.pojo.dto; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 会话更新数据传输对象 + * @author KilLze + */ +@Data +public class ChatSessionUpdateDTO { + /** 会话ID */ + private Long sessionId; + /** 最后一条消息ID (关联chat_records.chat_id) */ + private Long lastMessageId; + /** 最后一条消息内容 */ + private String lastMessageContent; + /** 最后一条消息时间 */ + private LocalDateTime lastMessageTime; + /** 未读消息数量 */ + private Integer unreadCount; +} diff --git a/src/main/java/com/bao/dating/pojo/entity/ChatRecords.java b/src/main/java/com/bao/dating/pojo/entity/ChatRecords.java new file mode 100644 index 0000000..6bd8237 --- /dev/null +++ b/src/main/java/com/bao/dating/pojo/entity/ChatRecords.java @@ -0,0 +1,35 @@ +package com.bao.dating.pojo.entity; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 聊天记录表 + * @author lenovo + */ +@Data +public class ChatRecords { + /** 聊天记录ID */ + private Long chatId; + /** 发送者用户ID */ + private Long senderUserId; + /** 接收者用户ID */ + private Long receiverUserId; + /** 消息内容 */ + private String messageContent; + /** 消息类型 (1-文本消息,2-文件消息) */ + private Integer messageType; + /** 阅读状态 (0-未读,1-已读) */ + private Integer readStatus; + /** 阅读时间 */ + private LocalDateTime readTime; + /** 发送时间 */ + private LocalDateTime sendTime; + /** 消息状态 (1-正常,2-已撤回,3-已删除) */ + private Integer messageStatus; + /** 创建时间 */ + private LocalDateTime createdAt; + /** 更新时间 */ + private LocalDateTime updatedAt; +} diff --git a/src/main/java/com/bao/dating/pojo/entity/ChatSessions.java b/src/main/java/com/bao/dating/pojo/entity/ChatSessions.java new file mode 100644 index 0000000..5b85c72 --- /dev/null +++ b/src/main/java/com/bao/dating/pojo/entity/ChatSessions.java @@ -0,0 +1,40 @@ +package com.bao.dating.pojo.entity; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 会话表 + * @author KilLze + */ +@Data +public class ChatSessions { + /** 会话ID */ + private Long sessionId; + /** 所属用户ID */ + private Long userId; + /** 目标用户ID */ + private Long targetUserId; + /** 会话名称 */ + private String sessionName; + /** 最后一条消息ID (关联chat_records.chat_id) */ + private Long lastMessageId; + /** 最后一条消息内容 */ + private String lastMessageContent; + /** 最后一条消息时间 */ + private LocalDateTime lastMessageTime; + /** 未读消息数量 */ + private Integer unreadCount; + /** 会话状态 (1-正常,2-已隐藏,3-已删除) */ + private Integer sessionStatus; + /** 置顶状态 (0-未置顶,1-已置顶) */ + private Integer topStatus; + /** 免打扰状态 (0-正常提醒,1-免打扰) */ + private Integer muteStatus; + /** 创建时间 */ + private LocalDateTime createdAt; + /** 更新时间 */ + private LocalDateTime updatedAt; + +} diff --git a/src/main/java/com/bao/dating/pojo/vo/ChatRecordsVO.java b/src/main/java/com/bao/dating/pojo/vo/ChatRecordsVO.java new file mode 100644 index 0000000..deb9f92 --- /dev/null +++ b/src/main/java/com/bao/dating/pojo/vo/ChatRecordsVO.java @@ -0,0 +1,29 @@ +package com.bao.dating.pojo.vo; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 聊天记录返回数据 + * @author KilLze + */ +@Data +public class ChatRecordsVO { + /** 聊天记录ID */ + private Long chatId; + /** 发送者用户ID */ + private Long senderUserId; + /** 接收者用户ID */ + private Long receiverUserId; + /** 消息内容 */ + private String messageContent; + /** 消息类型 (1-文本消息,2-文件消息) */ + private Integer messageType; + /** 阅读状态 (0-未读,1-已读) */ + private Integer readStatus; + /** 发送时间 */ + private LocalDateTime sendTime; + /** 消息状态 (1-正常,2-已撤回,3-已删除) */ + private Integer messageStatus; +} diff --git a/src/main/java/com/bao/dating/pojo/vo/ChatSessionDetailVO.java b/src/main/java/com/bao/dating/pojo/vo/ChatSessionDetailVO.java new file mode 100644 index 0000000..2ede2d8 --- /dev/null +++ b/src/main/java/com/bao/dating/pojo/vo/ChatSessionDetailVO.java @@ -0,0 +1,15 @@ +package com.bao.dating.pojo.vo; + +import lombok.Data; + +import java.util.List; + +@Data +public class ChatSessionDetailVO { + + /** 会话信息 */ + private ChatSessionsVO session; + + /** 聊天记录列表 */ + private List records; +} diff --git a/src/main/java/com/bao/dating/pojo/vo/ChatSessionsVO.java b/src/main/java/com/bao/dating/pojo/vo/ChatSessionsVO.java new file mode 100644 index 0000000..22fa0ed --- /dev/null +++ b/src/main/java/com/bao/dating/pojo/vo/ChatSessionsVO.java @@ -0,0 +1,30 @@ +package com.bao.dating.pojo.vo; + +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class ChatSessionsVO { + + /** 会话ID */ + private Long sessionId; + /** 当前用户ID */ + private Long userId; + /** 对方用户ID */ + private Long targetUserId; + /** 会话名称 */ + private String sessionName; + /** 最后一条消息内容 */ + private String lastMessageContent; + /** 最后一条消息时间 */ + private LocalDateTime lastMessageTime; + /** 未读消息数量 */ + private Integer unreadCount; + /** 会话状态 */ + private Integer sessionStatus; + /** 置顶状态 */ + private Integer topStatus; + /** 免打扰状态 */ + private Integer muteStatus; +} diff --git a/src/main/java/com/bao/dating/service/ChatService.java b/src/main/java/com/bao/dating/service/ChatService.java new file mode 100644 index 0000000..a4042a2 --- /dev/null +++ b/src/main/java/com/bao/dating/service/ChatService.java @@ -0,0 +1,14 @@ +package com.bao.dating.service; + +import com.bao.dating.pojo.dto.ChatRecordSendDTO; +import com.bao.dating.pojo.vo.ChatRecordsVO; + +public interface ChatService { + /** + * 消息入库,如果会话不存在则创建会话 + * @param senderUserId 发送方用户ID + * @param dto 发送参数 + * @return 聊天记录VO + */ + ChatRecordsVO createSession(Long senderUserId, ChatRecordSendDTO dto); +} diff --git a/src/main/java/com/bao/dating/service/impl/ChatServiceImpl.java b/src/main/java/com/bao/dating/service/impl/ChatServiceImpl.java new file mode 100644 index 0000000..153215d --- /dev/null +++ b/src/main/java/com/bao/dating/service/impl/ChatServiceImpl.java @@ -0,0 +1,83 @@ +package com.bao.dating.service.impl; + +import com.bao.dating.mapper.ChatRecordsMapper; +import com.bao.dating.mapper.ChatSessionsMapper; +import com.bao.dating.pojo.dto.ChatRecordSendDTO; +import com.bao.dating.pojo.entity.ChatRecords; +import com.bao.dating.pojo.entity.ChatSessions; +import com.bao.dating.pojo.vo.ChatRecordsVO; +import com.bao.dating.service.ChatService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; + + + + +/** + * 聊天服务实现类 + * @author lenovo + */ +@Slf4j +@Service +public class ChatServiceImpl implements ChatService { + @Autowired + private ChatRecordsMapper chatRecordsMapper; + + @Autowired + private ChatSessionsMapper chatSessionsMapper; + + /** + * 消息入库,如果会话不存在则创建会话 + * @param senderUserId 发送者用户ID + * @param dto 消息 + * @return 聊天记录 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public ChatRecordsVO createSession(Long senderUserId, ChatRecordSendDTO dto) { + ChatRecords record = new ChatRecords(); + record.setSenderUserId(senderUserId); + record.setReceiverUserId(dto.getReceiverUserId()); + record.setMessageContent(dto.getMessageContent()); + record.setMessageType(dto.getMessageType()); + record.setReadStatus(0); + record.setMessageStatus(1); + record.setSendTime(LocalDateTime.now()); + + // 插入消息记录 + chatRecordsMapper.insert(record); + + // 创建接收方会话 + ChatSessions sessions = new ChatSessions(); + sessions.setUserId(senderUserId); + sessions.setTargetUserId(dto.getReceiverUserId()); + sessions.setSessionName(""); + sessions.setLastMessageId(record.getChatId()); + sessions.setLastMessageContent(record.getMessageContent()); + sessions.setLastMessageTime(record.getSendTime()); + + chatSessionsMapper.insertIfNotExistsForSender(sessions); + chatSessionsMapper.updateSessionForSender(sessions); + + // 创建接收方会话 + sessions.setUserId(dto.getReceiverUserId()); + sessions.setTargetUserId(senderUserId); + sessions.setSessionName(""); + + chatSessionsMapper.insertIfNotExistsForReceiver(sessions); + chatSessionsMapper.updateSessionForReceiver(sessions); + + + + // 3. 返回 VO + ChatRecordsVO vo = new ChatRecordsVO(); + BeanUtils.copyProperties(record, vo); + return vo; + } + +} diff --git a/src/main/java/com/bao/dating/session/WsSessionManager.java b/src/main/java/com/bao/dating/session/WsSessionManager.java new file mode 100644 index 0000000..90eea45 --- /dev/null +++ b/src/main/java/com/bao/dating/session/WsSessionManager.java @@ -0,0 +1,51 @@ +package com.bao.dating.session; + +import org.springframework.stereotype.Component; +import org.springframework.web.socket.WebSocketSession; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * WebSocketSession 管理类 + * @author KilLze + */ +@Component +public class WsSessionManager { + + private final Map SESSION_MAP = new ConcurrentHashMap<>(); + /** + * 添加 WebSocketSession + * @param userId 用户ID + * @param session WebSocketSession + */ + public void addSession(Long userId, WebSocketSession session) { + SESSION_MAP.put(userId, session); + } + + /** + * 移除 WebSocketSession + * @param userId 用户ID + */ + public void removeSession(Long userId) { + SESSION_MAP.remove(userId); + } + + /** + * 获取 WebSocketSession + * @param userId 用户ID + * @return WebSocketSession + */ + public WebSocketSession getSession(Long userId) { + return SESSION_MAP.get(userId); + } + + /** + * 判断用户是否在线 + * @param userId 用户ID + * @return true-在线,false-离线 + */ + public boolean isOnline(Long userId) { + return SESSION_MAP.containsKey(userId); + } +} diff --git a/src/main/resources/com/bao/dating/mapper/ChatRecordsMapper.xml b/src/main/resources/com/bao/dating/mapper/ChatRecordsMapper.xml new file mode 100644 index 0000000..91be40d --- /dev/null +++ b/src/main/resources/com/bao/dating/mapper/ChatRecordsMapper.xml @@ -0,0 +1,34 @@ + + + + + + + INSERT INTO chat_records + ( + sender_user_id, + receiver_user_id, + message_content, + message_type, + read_status, + send_time, + message_status, + created_at, + updated_at + ) + VALUES + ( + #{senderUserId}, + #{receiverUserId}, + #{messageContent}, + #{messageType}, + #{readStatus}, + #{sendTime}, + #{messageStatus}, + NOW(), + NOW() + ) + + + \ No newline at end of file diff --git a/src/main/resources/com/bao/dating/mapper/ChatSessionsMapper.xml b/src/main/resources/com/bao/dating/mapper/ChatSessionsMapper.xml new file mode 100644 index 0000000..d3d829c --- /dev/null +++ b/src/main/resources/com/bao/dating/mapper/ChatSessionsMapper.xml @@ -0,0 +1,62 @@ + + + + + + + INSERT INTO chat_sessions + (user_id, target_user_id, session_name, last_message_id, last_message_content, last_message_time, + unread_count, session_status, top_status, mute_status, created_at, updated_at) + SELECT + #{userId}, #{targetUserId}, #{sessionName}, #{lastMessageId}, #{lastMessageContent}, #{lastMessageTime}, + 0, 1, 0, 0, NOW(), NOW() + FROM DUAL + WHERE NOT EXISTS ( + SELECT 1 FROM chat_sessions + WHERE user_id = #{userId} AND target_user_id = #{targetUserId} + ); + + + + + INSERT INTO chat_sessions + (user_id, target_user_id, session_name, last_message_id, last_message_content, last_message_time, + unread_count, session_status, top_status, mute_status, created_at, updated_at) + SELECT + #{userId}, #{targetUserId}, #{sessionName}, #{lastMessageId}, #{lastMessageContent}, #{lastMessageTime}, + 0, 1, 0, 0, NOW(), NOW() + FROM DUAL + WHERE NOT EXISTS ( + SELECT 1 FROM chat_sessions + WHERE user_id = #{userId} AND target_user_id = #{targetUserId} + ) + + + + + UPDATE chat_sessions + SET + last_message_id = #{lastMessageId}, + last_message_content = #{lastMessageContent}, + last_message_time = #{lastMessageTime}, + unread_count = 0, + updated_at = NOW() + WHERE user_id = #{userId} + AND target_user_id = #{targetUserId}; + + + + + UPDATE chat_sessions + SET + last_message_id = #{lastMessageId}, + last_message_content = #{lastMessageContent}, + last_message_time = #{lastMessageTime}, + unread_count = unread_count + 1, + updated_at = NOW() + WHERE user_id = #{userId} + AND target_user_id = #{targetUserId} + + + \ No newline at end of file