|
@@ -0,0 +1,320 @@
|
|
|
|
|
+package com.zhentao.service;
|
|
|
|
|
+
|
|
|
|
|
+import cn.hutool.json.JSONUtil;
|
|
|
|
|
+import com.baomidou.mybatisplus.core.metadata.IPage;
|
|
|
|
|
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
|
|
+import com.zhentao.entity.MatchmakerChatMessage;
|
|
|
|
|
+import com.zhentao.entity.MatchmakerChatMessageMongo;
|
|
|
|
|
+import com.zhentao.repository.MatchmakerChatMessageMapper;
|
|
|
|
|
+import com.zhentao.repository.MatchmakerChatMessageMongoRepository;
|
|
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
|
|
+import org.springframework.dao.DuplicateKeyException;
|
|
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
|
|
+
|
|
|
|
|
+import java.util.Date;
|
|
|
|
|
+import java.util.UUID;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 红娘与用户聊天消息服务
|
|
|
|
|
+ * 同时存储到MySQL和MongoDB
|
|
|
|
|
+ */
|
|
|
|
|
+@Service
|
|
|
|
|
+public class MatchmakerChatMessageService {
|
|
|
|
|
+
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private MatchmakerChatMessageMapper matchmakerChatMessageMapper;
|
|
|
|
|
+
|
|
|
|
|
+ @Autowired(required = false)
|
|
|
|
|
+ private MatchmakerChatMessageMongoRepository mongoRepository;
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 保存红娘与用户的聊天消息
|
|
|
|
|
+ * 同时存储到MySQL和MongoDB
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param matchmakerId 红娘ID
|
|
|
|
|
+ * @param userId 用户ID
|
|
|
|
|
+ * @param senderType 发送方类型:1-用户发送 2-红娘发送
|
|
|
|
|
+ * @param messageType 消息类型:1-文本 2-图片 3-语音 4-视频 5-文件
|
|
|
|
|
+ * @param content 消息内容
|
|
|
|
|
+ * @param mediaUrl 媒体文件URL(可选)
|
|
|
|
|
+ * @param timMessageId TIM消息ID(可选,如果没有则自动生成)
|
|
|
|
|
+ * @return 保存的消息实体
|
|
|
|
|
+ */
|
|
|
|
|
+ public MatchmakerChatMessage saveMessage(Long matchmakerId, Long userId, Integer senderType,
|
|
|
|
|
+ Integer messageType, String content, String mediaUrl,
|
|
|
|
|
+ String timMessageId) {
|
|
|
|
|
+ // 构建消息实体
|
|
|
|
|
+ MatchmakerChatMessage message = new MatchmakerChatMessage();
|
|
|
|
|
+ message.setMessageId(timMessageId != null ? timMessageId : UUID.randomUUID().toString());
|
|
|
|
|
+ message.setConversationId(MatchmakerChatMessage.generateConversationId(matchmakerId, userId));
|
|
|
|
|
+ message.setMatchmakerId(matchmakerId);
|
|
|
|
|
+ message.setUserId(userId);
|
|
|
|
|
+ message.setSenderType(senderType);
|
|
|
|
|
+ message.setMessageType(messageType);
|
|
|
|
|
+ message.setContent(content);
|
|
|
|
|
+ message.setMediaUrl(mediaUrl);
|
|
|
|
|
+ message.setSendStatus(1); // 发送中
|
|
|
|
|
+ message.setSendTime(new Date());
|
|
|
|
|
+ message.setIsRecalled(0);
|
|
|
|
|
+ message.setIsDeleted(0);
|
|
|
|
|
+ message.setCreateTime(new Date());
|
|
|
|
|
+ message.setUpdateTime(new Date());
|
|
|
|
|
+
|
|
|
|
|
+ // 保存到MySQL和MongoDB
|
|
|
|
|
+ return saveMessage(message);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 保存消息(完整实体)
|
|
|
|
|
+ * 同时存储到MySQL和MongoDB
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param message 消息实体
|
|
|
|
|
+ * @return 保存的消息实体
|
|
|
|
|
+ */
|
|
|
|
|
+ public MatchmakerChatMessage saveMessage(MatchmakerChatMessage message) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 1. 保存到MySQL
|
|
|
|
|
+ matchmakerChatMessageMapper.insert(message);
|
|
|
|
|
+ System.out.println("✅ 红娘消息已保存到MySQL: " + message.getMessageId());
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 同时保存到MongoDB
|
|
|
|
|
+ saveToMongoDB(message);
|
|
|
|
|
+
|
|
|
|
|
+ return message;
|
|
|
|
|
+ } catch (DuplicateKeyException e) {
|
|
|
|
|
+ // 消息已存在,忽略重复插入(幂等性保证)
|
|
|
|
|
+ System.out.println("⚠️ 红娘消息已存在,跳过保存: " + message.getMessageId());
|
|
|
|
|
+ return matchmakerChatMessageMapper.selectByMessageId(message.getMessageId());
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ System.err.println("❌ 保存红娘消息失败: " + message.getMessageId() + ", 错误: " + e.getMessage());
|
|
|
|
|
+ throw e;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 保存消息到MongoDB
|
|
|
|
|
+ */
|
|
|
|
|
+ private void saveToMongoDB(MatchmakerChatMessage message) {
|
|
|
|
|
+ if (mongoRepository == null) {
|
|
|
|
|
+ System.out.println("⚠️ MongoDB未配置,跳过MongoDB存储");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 检查是否已存在
|
|
|
|
|
+ if (mongoRepository.existsByMessageId(message.getMessageId())) {
|
|
|
|
|
+ System.out.println("⚠️ MongoDB中消息已存在,跳过: " + message.getMessageId());
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 转换为MongoDB实体
|
|
|
|
|
+ MatchmakerChatMessageMongo mongoMessage = convertToMongoEntity(message);
|
|
|
|
|
+
|
|
|
|
|
+ // 保存到MongoDB
|
|
|
|
|
+ mongoRepository.save(mongoMessage);
|
|
|
|
|
+ System.out.println("✅ 红娘消息已同步到MongoDB: " + message.getMessageId());
|
|
|
|
|
+
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ // MongoDB存储失败不影响主流程
|
|
|
|
|
+ System.err.println("⚠️ MongoDB存储失败(不影响主流程): " + e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 转换为MongoDB实体
|
|
|
|
|
+ */
|
|
|
|
|
+ private MatchmakerChatMessageMongo convertToMongoEntity(MatchmakerChatMessage message) {
|
|
|
|
|
+ MatchmakerChatMessageMongo mongoMessage = new MatchmakerChatMessageMongo();
|
|
|
|
|
+ mongoMessage.setMessageId(message.getMessageId());
|
|
|
|
|
+ mongoMessage.setConversationId(message.getConversationId());
|
|
|
|
|
+ mongoMessage.setMatchmakerId(message.getMatchmakerId());
|
|
|
|
|
+ mongoMessage.setUserId(message.getUserId());
|
|
|
|
|
+ mongoMessage.setSenderType(message.getSenderType());
|
|
|
|
|
+ mongoMessage.setMessageType(message.getMessageType());
|
|
|
|
|
+ mongoMessage.setContent(message.getContent());
|
|
|
|
|
+ mongoMessage.setMediaUrl(message.getMediaUrl());
|
|
|
|
|
+ mongoMessage.setThumbnailUrl(message.getThumbnailUrl());
|
|
|
|
|
+ mongoMessage.setMediaSize(message.getMediaSize());
|
|
|
|
|
+ mongoMessage.setDuration(message.getDuration());
|
|
|
|
|
+ mongoMessage.setSendStatus(message.getSendStatus());
|
|
|
|
|
+ mongoMessage.setSendTime(message.getSendTime());
|
|
|
|
|
+ mongoMessage.setDeliverTime(message.getDeliverTime());
|
|
|
|
|
+ mongoMessage.setReadTime(message.getReadTime());
|
|
|
|
|
+ mongoMessage.setIsRecalled(message.getIsRecalled());
|
|
|
|
|
+ mongoMessage.setRecallTime(message.getRecallTime());
|
|
|
|
|
+ mongoMessage.setSource("TIM");
|
|
|
|
|
+ mongoMessage.setCreatedAt(new Date());
|
|
|
|
|
+ mongoMessage.setUpdatedAt(new Date());
|
|
|
|
|
+ mongoMessage.setExtraData(message.getExtraData());
|
|
|
|
|
+ return mongoMessage;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 从TIM消息同步保存
|
|
|
|
|
+ * 根据发送者和接收者ID判断是用户发送还是红娘发送
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param fromTimUserId 发送者TIM用户ID(如 "m_123" 或 "456")
|
|
|
|
|
+ * @param toTimUserId 接收者TIM用户ID(如 "m_123" 或 "456")
|
|
|
|
|
+ * @param messageType 消息类型
|
|
|
|
|
+ * @param content 消息内容
|
|
|
|
|
+ * @param mediaUrl 媒体URL
|
|
|
|
|
+ * @param timMessageId TIM消息ID
|
|
|
|
|
+ * @return 保存的消息实体,如果不是红娘相关消息则返回null
|
|
|
|
|
+ */
|
|
|
|
|
+ public MatchmakerChatMessage saveFromTimMessage(String fromTimUserId, String toTimUserId,
|
|
|
|
|
+ Integer messageType, String content,
|
|
|
|
|
+ String mediaUrl, String timMessageId) {
|
|
|
|
|
+ Long matchmakerId = null;
|
|
|
|
|
+ Long userId = null;
|
|
|
|
|
+ Integer senderType = null;
|
|
|
|
|
+
|
|
|
|
|
+ // 判断发送者是否是红娘
|
|
|
|
|
+ if (MatchmakerChatMessage.isMatchmaker(fromTimUserId)) {
|
|
|
|
|
+ // 红娘发送给用户
|
|
|
|
|
+ matchmakerId = MatchmakerChatMessage.parseMatchmakerIdFromTimId(fromTimUserId);
|
|
|
|
|
+ try {
|
|
|
|
|
+ userId = Long.parseLong(toTimUserId.replace("C2C", ""));
|
|
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
|
|
+ System.err.println("❌ 无法解析用户ID: " + toTimUserId);
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ senderType = MatchmakerChatMessage.SENDER_TYPE_MATCHMAKER;
|
|
|
|
|
+ } else if (MatchmakerChatMessage.isMatchmaker(toTimUserId)) {
|
|
|
|
|
+ // 用户发送给红娘
|
|
|
|
|
+ matchmakerId = MatchmakerChatMessage.parseMatchmakerIdFromTimId(toTimUserId);
|
|
|
|
|
+ try {
|
|
|
|
|
+ userId = Long.parseLong(fromTimUserId.replace("C2C", ""));
|
|
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
|
|
+ System.err.println("❌ 无法解析用户ID: " + fromTimUserId);
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ senderType = MatchmakerChatMessage.SENDER_TYPE_USER;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 不是红娘相关消息
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (matchmakerId == null || userId == null) {
|
|
|
|
|
+ System.err.println("❌ 无法解析红娘或用户ID");
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 保存消息
|
|
|
|
|
+ return saveMessage(matchmakerId, userId, senderType, messageType, content, mediaUrl, timMessageId);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 查询红娘与用户的聊天记录(分页)
|
|
|
|
|
+ */
|
|
|
|
|
+ public IPage<MatchmakerChatMessage> getConversationMessages(Long matchmakerId, Long userId, int page, int size) {
|
|
|
|
|
+ String conversationId = MatchmakerChatMessage.generateConversationId(matchmakerId, userId);
|
|
|
|
|
+ Page<MatchmakerChatMessage> pageParam = new Page<>(page + 1, size);
|
|
|
|
|
+ return matchmakerChatMessageMapper.selectByConversationId(pageParam, conversationId);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 查询红娘的所有聊天记录(分页)
|
|
|
|
|
+ */
|
|
|
|
|
+ public IPage<MatchmakerChatMessage> getMatchmakerMessages(Long matchmakerId, int page, int size) {
|
|
|
|
|
+ Page<MatchmakerChatMessage> pageParam = new Page<>(page + 1, size);
|
|
|
|
|
+ return matchmakerChatMessageMapper.selectByMatchmakerId(pageParam, matchmakerId);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 查询用户与红娘的所有聊天记录(分页)
|
|
|
|
|
+ */
|
|
|
|
|
+ public IPage<MatchmakerChatMessage> getUserMessages(Long userId, int page, int size) {
|
|
|
|
|
+ Page<MatchmakerChatMessage> pageParam = new Page<>(page + 1, size);
|
|
|
|
|
+ return matchmakerChatMessageMapper.selectByUserId(pageParam, userId);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 根据消息ID查询
|
|
|
|
|
+ */
|
|
|
|
|
+ public MatchmakerChatMessage getByMessageId(String messageId) {
|
|
|
|
|
+ return matchmakerChatMessageMapper.selectByMessageId(messageId);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 标记消息为已读
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param matchmakerId 红娘ID
|
|
|
|
|
+ * @param userId 用户ID
|
|
|
|
|
+ * @param readerType 阅读者类型:1-用户阅读(红娘发送的消息) 2-红娘阅读(用户发送的消息)
|
|
|
|
|
+ */
|
|
|
|
|
+ public void markMessagesAsRead(Long matchmakerId, Long userId, Integer readerType) {
|
|
|
|
|
+ // 如果是用户阅读,则标记红娘发送的消息为已读
|
|
|
|
|
+ // 如果是红娘阅读,则标记用户发送的消息为已读
|
|
|
|
|
+ Integer senderType = (readerType == 1) ? MatchmakerChatMessage.SENDER_TYPE_MATCHMAKER
|
|
|
|
|
+ : MatchmakerChatMessage.SENDER_TYPE_USER;
|
|
|
|
|
+
|
|
|
|
|
+ matchmakerChatMessageMapper.updateMessagesToRead(matchmakerId, userId, senderType);
|
|
|
|
|
+ System.out.println("✅ 已标记消息为已读: 红娘=" + matchmakerId + ", 用户=" + userId + ", 发送方=" + senderType);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 撤回消息
|
|
|
|
|
+ */
|
|
|
|
|
+ public boolean recallMessage(String messageId, String operatorTimId) {
|
|
|
|
|
+ MatchmakerChatMessage message = matchmakerChatMessageMapper.selectByMessageId(messageId);
|
|
|
|
|
+ if (message == null) {
|
|
|
|
|
+ System.err.println("❌ 消息不存在: " + messageId);
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 验证操作者是否是发送者
|
|
|
|
|
+ boolean isMatchmaker = MatchmakerChatMessage.isMatchmaker(operatorTimId);
|
|
|
|
|
+ if (isMatchmaker) {
|
|
|
|
|
+ Long operatorId = MatchmakerChatMessage.parseMatchmakerIdFromTimId(operatorTimId);
|
|
|
|
|
+ if (!message.getMatchmakerId().equals(operatorId) ||
|
|
|
|
|
+ message.getSenderType() != MatchmakerChatMessage.SENDER_TYPE_MATCHMAKER) {
|
|
|
|
|
+ System.err.println("❌ 只能撤回自己发送的消息");
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ try {
|
|
|
|
|
+ Long operatorId = Long.parseLong(operatorTimId.replace("C2C", ""));
|
|
|
|
|
+ if (!message.getUserId().equals(operatorId) ||
|
|
|
|
|
+ message.getSenderType() != MatchmakerChatMessage.SENDER_TYPE_USER) {
|
|
|
|
|
+ System.err.println("❌ 只能撤回自己发送的消息");
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
|
|
+ System.err.println("❌ 无法解析操作者ID: " + operatorTimId);
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 检查时间限制(2分钟内)
|
|
|
|
|
+ long diff = System.currentTimeMillis() - message.getSendTime().getTime();
|
|
|
|
|
+ if (diff > 2 * 60 * 1000) {
|
|
|
|
|
+ System.err.println("❌ 超过撤回时间限制");
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 执行撤回
|
|
|
|
|
+ matchmakerChatMessageMapper.recallMessage(messageId);
|
|
|
|
|
+ System.out.println("✅ 消息已撤回: " + messageId);
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 统计红娘今日消息数
|
|
|
|
|
+ */
|
|
|
|
|
+ public int countMatchmakerTodayMessages(Long matchmakerId) {
|
|
|
|
|
+ return matchmakerChatMessageMapper.countMatchmakerTodayMessages(matchmakerId);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 统计未读消息数
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param matchmakerId 红娘ID
|
|
|
|
|
+ * @param userId 用户ID
|
|
|
|
|
+ * @param readerType 阅读者类型:1-用户(统计红娘发送的未读) 2-红娘(统计用户发送的未读)
|
|
|
|
|
+ */
|
|
|
|
|
+ public int countUnreadMessages(Long matchmakerId, Long userId, Integer readerType) {
|
|
|
|
|
+ Integer senderType = (readerType == 1) ? MatchmakerChatMessage.SENDER_TYPE_MATCHMAKER
|
|
|
|
|
+ : MatchmakerChatMessage.SENDER_TYPE_USER;
|
|
|
|
|
+ return matchmakerChatMessageMapper.countUnreadMessages(matchmakerId, userId, senderType);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|