feat: 完成情绪博物馆项目重构和功能增强 - 新增日记评论和帖子功能 - 重构前端架构,优化用户体验 - 完善WebSocket通信机制 - 更新项目文档和部署配置

This commit is contained in:
2025-07-27 10:05:59 +08:00
parent 6903ac1c0d
commit cc886cd4d5
126 changed files with 21179 additions and 15734 deletions
@@ -262,26 +262,8 @@ public class WebSocketServiceImpl implements WebSocketService {
userId
);
// 构建AI回复消息(不分割,保持完整性)
WebSocketMessage aiMessage = WebSocketMessage.builder()
.messageId(UUID.randomUUID().toString())
.conversationId(conversationId)
.type(WebSocketMessage.MessageType.TEXT)
.content(aiReply)
.senderId("ai")
.senderType(WebSocketMessage.SenderType.AI)
.status(WebSocketMessage.MessageStatus.SENT)
.createTime(LocalDateTime.now())
.build();
// AI回复已经在sendChatMessageForWebSocket中保存了,这里不需要重复保存
// 发送AI回复
messagingTemplate.convertAndSendToUser(userId, "/queue/messages", aiMessage);
if (conversationId != null) {
messagingTemplate.convertAndSend("/topic/conversation/" + conversationId, aiMessage);
}
// 根据换行符分割AI回复并按顺序发送多条消息
sendAiReplyInParts(userId, conversationId, aiReply);
// 更新会话的最后活跃时间和消息数量
updateConversationActivity(conversationId);
@@ -398,4 +380,158 @@ public class WebSocketServiceImpl implements WebSocketService {
log.error("更新会话活跃状态失败: conversationId={}", conversationId, e);
}
}
/**
* 根据换行符分割AI回复并按顺序发送多条消息
*
* @param userId 用户ID
* @param conversationId 会话ID
* @param aiReply AI回复内容
*/
private void sendAiReplyInParts(String userId, String conversationId, String aiReply) {
try {
log.info("开始处理AI回复消息: userId={}, conversationId={}, aiReply长度={}",
userId, conversationId, aiReply != null ? aiReply.length() : 0);
if (aiReply == null || aiReply.trim().isEmpty()) {
log.warn("AI回复内容为空,跳过发送");
return;
}
// 检查是否需要分割
boolean needsSplit = aiReply.contains("\n\n") || aiReply.contains("\n");
if (!needsSplit) {
// 不需要分割,直接发送完整消息
log.info("AI回复无换行符,发送完整消息");
sendSingleAiMessage(userId, conversationId, aiReply.trim());
return;
}
// 需要分割,按换行符分割并发送多条消息
log.info("AI回复包含换行符,开始分割发送");
String[] replyParts = splitAiReply(aiReply);
log.info("AI回复分割完成,共{}个部分", replyParts.length);
// 按顺序发送每个部分
int sentCount = 0;
for (int i = 0; i < replyParts.length; i++) {
String part = replyParts[i].trim();
// 跳过空白部分
if (part.isEmpty()) {
continue;
}
// 发送消息部分
sendSingleAiMessage(userId, conversationId, part);
sentCount++;
log.info("发送AI回复部分 {}/{}: 内容长度={}", sentCount, replyParts.length, part.length());
// 在多个部分之间添加短暂延迟,模拟自然对话节奏
if (i < replyParts.length - 1) {
try {
Thread.sleep(500); // 延迟500毫秒
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.warn("发送AI回复时被中断");
break;
}
}
}
log.info("AI回复发送完成: userId={}, conversationId={}, 实际发送{}条消息",
userId, conversationId, sentCount);
} catch (Exception e) {
log.error("分割发送AI回复失败: userId={}, conversationId={}", userId, conversationId, e);
// 发送错误时,尝试发送完整的原始回复
try {
WebSocketMessage fallbackMessage = WebSocketMessage.builder()
.messageId(UUID.randomUUID().toString())
.conversationId(conversationId)
.type(WebSocketMessage.MessageType.TEXT)
.content(aiReply)
.senderId("ai")
.senderType(WebSocketMessage.SenderType.AI)
.status(WebSocketMessage.MessageStatus.SENT)
.createTime(LocalDateTime.now())
.build();
messagingTemplate.convertAndSendToUser(userId, "/queue/messages", fallbackMessage);
if (conversationId != null) {
messagingTemplate.convertAndSend("/topic/conversation/" + conversationId, fallbackMessage);
}
log.info("已发送完整AI回复作为备用方案");
} catch (Exception fallbackError) {
log.error("发送备用AI回复也失败", fallbackError);
sendErrorMessage(userId, "AI回复发送失败,请稍后重试");
}
}
}
/**
* 发送单条AI消息
*
* @param userId 用户ID
* @param conversationId 会话ID
* @param content 消息内容
*/
private void sendSingleAiMessage(String userId, String conversationId, String content) {
// 构建AI回复消息
WebSocketMessage aiMessage = WebSocketMessage.builder()
.messageId(UUID.randomUUID().toString())
.conversationId(conversationId)
.type(WebSocketMessage.MessageType.TEXT)
.content(content)
.senderId("ai")
.senderType(WebSocketMessage.SenderType.AI)
.status(WebSocketMessage.MessageStatus.SENT)
.createTime(LocalDateTime.now())
.build();
// 发送给用户私有队列
messagingTemplate.convertAndSendToUser(userId, "/queue/messages", aiMessage);
// 发送到会话公共频道
if (conversationId != null) {
messagingTemplate.convertAndSend("/topic/conversation/" + conversationId, aiMessage);
}
}
/**
* 智能分割AI回复内容
*
* @param aiReply AI回复内容
* @return 分割后的内容数组
*/
private String[] splitAiReply(String aiReply) {
if (aiReply == null || aiReply.trim().isEmpty()) {
return new String[0];
}
// 首先尝试按双换行符分割(段落分割)
if (aiReply.contains("\n\n")) {
String[] parts = aiReply.split("\n\n");
log.debug("按双换行符分割,得到{}个部分", parts.length);
return parts;
}
// 如果没有双换行符,按单换行符分割(行分割)
if (aiReply.contains("\n")) {
String[] parts = aiReply.split("\n");
log.debug("按单换行符分割,得到{}个部分", parts.length);
return parts;
}
// 如果没有换行符,返回原始内容(这种情况不应该到达这里)
log.debug("没有换行符,返回原始内容");
return new String[]{aiReply};
}
}