diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index 5e89e00..665003a 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -11,4 +11,9 @@
+
+
+
\ No newline at end of file
diff --git a/unilife-server/AI_MEMORY_INTEGRATION.md b/unilife-server/AI_MEMORY_INTEGRATION.md
new file mode 100644
index 0000000..3682b3d
--- /dev/null
+++ b/unilife-server/AI_MEMORY_INTEGRATION.md
@@ -0,0 +1,137 @@
+# Spring AI ChatMemory MySQL 集成说明
+
+## 概述
+
+本项目已成功集成Spring AI的ChatMemory功能,使用MySQL作为持久化存储,实现了真正的AI会话记忆功能。
+
+## 主要改进
+
+### 1. 删除的错误实现
+- ❌ 删除了 `AiChatSession.java` - 自定义会话实体(与Spring AI不兼容)
+- ❌ 删除了 `AiChatMessage.java` - 自定义消息实体(与Spring AI不兼容)
+- ❌ 删除了基于TODO注释的伪实现
+
+### 2. 新增的正确实现
+- ✅ 添加了 `spring-ai-starter-model-chat-memory-repository-jdbc` 依赖
+- ✅ 配置了基于MySQL的JdbcChatMemoryRepository
+- ✅ 集成了MessageChatMemoryAdvisor,实现自动会话记忆
+- ✅ 使用Spring AI标准的ChatMemory接口
+
+## 核心功能
+
+### 会话记忆机制
+- **自动记忆**: 每次对话自动保存到MySQL数据库
+- **上下文保持**: 支持最多20条消息的上下文窗口
+- **会话隔离**: 不同conversationId的会话完全隔离
+- **持久化存储**: 重启服务后会话记忆不丢失
+
+### 数据库表结构
+```sql
+CREATE TABLE SPRING_AI_CHAT_MEMORY (
+ conversation_id VARCHAR(36) NOT NULL, -- 会话ID
+ content TEXT NOT NULL, -- 消息内容
+ type VARCHAR(10) NOT NULL, -- 消息类型:USER/ASSISTANT/SYSTEM/TOOL
+ timestamp TIMESTAMP NOT NULL, -- 时间戳
+ INDEX idx_conversation_id_timestamp (conversation_id, timestamp)
+);
+```
+
+## API使用说明
+
+### 1. 发送消息(带会话记忆)
+```http
+POST /ai/chat?prompt=你好&sessionId=my-session-001
+```
+- 自动将用户消息和AI回复保存到数据库
+- 后续对话会自动加载历史上下文
+
+### 2. 测试记忆功能
+```http
+GET /ai/test/memory?conversationId=test-session&message=我的名字是张三
+GET /ai/test/memory?conversationId=test-session&message=我的名字是什么?
+```
+- 第二次询问AI会记住之前说过的名字
+
+### 3. 查看会话历史
+```http
+GET /ai/test/history/test-session
+```
+
+### 4. 清空会话记忆
+```http
+DELETE /ai/test/memory/test-session
+```
+
+## 配置说明
+
+### application.yml 关键配置
+```yaml
+spring:
+ ai:
+ chat:
+ memory:
+ repository:
+ jdbc:
+ initialize-schema: always # 自动初始化表结构
+ schema: classpath:schema-mysql.sql
+```
+
+### AiConfig.java 关键配置
+```java
+@Bean
+public ChatMemory chatMemory(ChatMemoryRepository chatMemoryRepository) {
+ return MessageWindowChatMemory.builder()
+ .chatMemoryRepository(chatMemoryRepository)
+ .maxMessages(20) // 保留最近20条消息
+ .build();
+}
+
+@Bean
+public ChatClient chatClient(OpenAiChatModel model, ChatMemory chatMemory) {
+ return ChatClient.builder(model)
+ .defaultAdvisors(
+ new SimpleLoggerAdvisor(),
+ MessageChatMemoryAdvisor.builder(chatMemory).build() // 自动记忆
+ )
+ .build();
+}
+```
+
+## 使用最佳实践
+
+### 1. 会话ID管理
+- 前端生成唯一的sessionId(如:`session_${timestamp}_${random}`)
+- 同一会话的所有消息使用相同的sessionId
+- 新建会话时生成新的sessionId
+
+### 2. 会话记忆策略
+- 系统自动保留最近20条消息作为上下文
+- 超过20条消息时,旧消息会被自动清理(仅从内存中,数据库保留完整历史)
+- 可根据需要调整`maxMessages`参数
+
+### 3. 数据库维护
+- 定期清理过期的会话数据
+- 监控`SPRING_AI_CHAT_MEMORY`表的大小
+- 考虑为长期存储添加分区策略
+
+## 注意事项
+
+1. **会话列表功能限制**: Spring AI ChatMemory专注于消息存储,不提供会话元数据管理。如需要会话列表、标题管理等功能,建议维护单独的会话管理表。
+
+2. **性能优化**: 对于高并发场景,考虑:
+ - 数据库连接池配置
+ - 消息内容压缩
+ - 定期清理策略
+
+3. **错误处理**: 当数据库连接失败时,ChatMemory会降级为内存模式,重启后会话记忆将丢失。
+
+## 测试验证
+
+启动应用后,可以通过以下步骤验证功能:
+
+1. 访问 Swagger UI: `http://localhost:8087/doc.html`
+2. 找到 "AI测试接口" 分组
+3. 使用测试接口验证会话记忆功能
+4. 检查MySQL数据库中的`SPRING_AI_CHAT_MEMORY`表
+
+这样就完成了Spring AI ChatMemory与MySQL的完整集成,实现了真正的AI会话记忆功能!
\ No newline at end of file
diff --git a/unilife-server/CHAT_HISTORY_SETUP.md b/unilife-server/CHAT_HISTORY_SETUP.md
new file mode 100644
index 0000000..52460ea
--- /dev/null
+++ b/unilife-server/CHAT_HISTORY_SETUP.md
@@ -0,0 +1,250 @@
+# AI聊天会话历史功能设置指南
+
+## 概述
+
+本文档详细说明如何在您的UniLife项目中设置和使用AI聊天会话历史功能。该功能结合了Spring AI的ChatMemory(会话记忆)和MySQL数据库存储,实现了完整的会话历史管理。
+
+## 功能特性
+
+### 🔥 核心功能
+- **双重存储架构**: Spring AI ChatMemory(短期记忆)+ MySQL(长期历史)
+- **自动同步**: ChatMemory消息自动同步到MySQL历史表
+- **会话管理**: 支持会话列表、标题管理、删除等操作
+- **匿名支持**: 支持匿名会话和用户会话
+- **性能优化**: 历史表查询优化,支持分页
+- **兼容性**: 完全兼容现有的Spring AI ChatMemory功能
+
+### 📊 数据库表结构
+
+1. **SPRING_AI_CHAT_MEMORY** - Spring AI原生记忆表
+2. **ai_chat_sessions** - 会话元数据管理表
+3. **ai_chat_messages_history** - 消息历史详情表
+
+## 设置步骤
+
+### 1. 数据库初始化
+
+运行以下SQL脚本来创建必要的表结构:
+
+```sql
+-- Spring AI Chat Memory表(自动创建)
+CREATE TABLE IF NOT EXISTS SPRING_AI_CHAT_MEMORY (
+ conversation_id VARCHAR(36) NOT NULL COMMENT '会话ID',
+ content TEXT NOT NULL COMMENT '消息内容',
+ type VARCHAR(10) NOT NULL COMMENT '消息类型:USER、ASSISTANT、SYSTEM、TOOL',
+ `timestamp` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '消息时间戳',
+ INDEX idx_conversation_id_timestamp (conversation_id, `timestamp`),
+ CONSTRAINT chk_message_type CHECK (type IN ('USER', 'ASSISTANT', 'SYSTEM', 'TOOL'))
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- 会话历史管理表
+CREATE TABLE IF NOT EXISTS ai_chat_sessions (
+ id VARCHAR(64) PRIMARY KEY COMMENT '会话ID(前端生成)',
+ user_id BIGINT NULL COMMENT '用户ID(可选,支持匿名会话)',
+ title VARCHAR(200) NOT NULL DEFAULT '新对话' COMMENT '会话标题',
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ last_message_time TIMESTAMP NULL COMMENT '最后消息时间',
+ message_count INT NOT NULL DEFAULT 0 COMMENT '消息总数',
+ INDEX idx_user_id (user_id),
+ INDEX idx_created_at (created_at),
+ INDEX idx_updated_at (updated_at)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- 会话消息历史详情表
+CREATE TABLE IF NOT EXISTS ai_chat_messages_history (
+ id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '消息历史ID',
+ session_id VARCHAR(64) NOT NULL COMMENT '会话ID',
+ conversation_id VARCHAR(36) NOT NULL COMMENT 'Spring AI会话ID',
+ role ENUM('user', 'assistant', 'system', 'tool') NOT NULL COMMENT '消息角色',
+ content TEXT NOT NULL COMMENT '消息内容',
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ INDEX idx_session_id (session_id),
+ INDEX idx_conversation_id (conversation_id),
+ INDEX idx_created_at (created_at),
+ FOREIGN KEY (session_id) REFERENCES ai_chat_sessions(id) ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+```
+
+### 2. 配置验证
+
+确保`application.yml`中包含以下配置:
+
+```yaml
+spring:
+ ai:
+ chat:
+ memory:
+ repository:
+ jdbc:
+ initialize-schema: always
+ schema: classpath:schema-mysql.sql
+ datasource:
+ url: jdbc:mysql://localhost:3306/UniLife?allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=UTC&characterEncoding=UTF-8
+ username: root
+ password: 123456
+ driver-class-name: com.mysql.cj.jdbc.Driver
+```
+
+### 3. Maven依赖检查
+
+确保`pom.xml`中包含Spring AI JDBC ChatMemory依赖:
+
+```xml
+
+ org.springframework.ai
+ spring-ai-starter-model-chat-memory-repository-jdbc
+
+```
+
+## 使用指南
+
+### API接口说明
+
+#### 1. 发送消息(带会话记忆)
+```http
+GET /ai/chat?prompt=你好&sessionId=session_001
+```
+
+#### 2. 获取会话列表
+```http
+GET /ai/sessions?page=1&size=20
+```
+
+#### 3. 获取会话消息历史
+```http
+GET /ai/sessions/{sessionId}/messages?page=1&size=50
+```
+
+#### 4. 创建会话
+```http
+POST /ai/sessions
+{
+ "sessionId": "session_123456789",
+ "title": "关于Spring Boot的讨论"
+}
+```
+
+#### 5. 更新会话标题
+```http
+PUT /ai/sessions/{sessionId}
+{
+ "title": "新的会话标题"
+}
+```
+
+#### 6. 删除会话
+```http
+DELETE /ai/sessions/{sessionId}
+```
+
+### 测试接口
+
+系统提供了专门的测试接口来验证功能:
+
+#### 1. 测试会话记忆
+```http
+GET /ai/test/memory?conversationId=test-001&message=我的名字是张三
+GET /ai/test/memory?conversationId=test-001&message=我的名字是什么?
+```
+
+#### 2. 查看会话历史
+```http
+GET /ai/test/history/test-001
+```
+
+#### 3. 查看Spring AI原始记忆
+```http
+GET /ai/test/raw-memory/test-001
+```
+
+#### 4. 同步记忆到历史表
+```http
+POST /ai/test/sync/test-001
+```
+
+## 架构说明
+
+### 数据流程
+
+1. **用户发送消息** → ChatClient处理 → Spring AI ChatMemory自动存储
+2. **消息处理完成** → 自动同步到MySQL历史表
+3. **查询历史** → 优先从历史表查询(性能更好)
+4. **会话管理** → 独立的会话元数据管理
+
+### 关键组件
+
+- **AiChatSessionHistoryService**: 会话历史管理服务
+- **AiChatSessionMapper**: 会话数据访问层
+- **AiChatMessageHistoryMapper**: 消息历史数据访问层
+- **AiServiceImpl**: 整合Spring AI和历史管理的主服务
+
+## 最佳实践
+
+### 1. 会话ID管理
+- 使用格式:`session_${timestamp}_${random}`
+- 前端生成,确保唯一性
+- 示例:`session_1703827200000_abc123`
+
+### 2. 性能优化
+- 定期清理过期会话数据
+- 监控`SPRING_AI_CHAT_MEMORY`表大小
+- 使用分页查询大量历史数据
+
+### 3. 错误处理
+- 数据库连接失败时ChatMemory会降级为内存模式
+- 同步失败不影响主要聊天功能
+- 提供手动同步接口
+
+## 监控和维护
+
+### 1. 数据库监控
+```sql
+-- 查看会话统计
+SELECT COUNT(*) as total_sessions FROM ai_chat_sessions;
+
+-- 查看消息统计
+SELECT COUNT(*) as total_messages FROM ai_chat_messages_history;
+
+-- 查看Spring AI记忆表大小
+SELECT COUNT(*) as memory_records FROM SPRING_AI_CHAT_MEMORY;
+```
+
+### 2. 清理过期数据
+```sql
+-- 删除30天前的会话
+DELETE FROM ai_chat_sessions
+WHERE updated_at < DATE_SUB(NOW(), INTERVAL 30 DAY);
+```
+
+## 故障排除
+
+### 常见问题
+
+1. **表不存在错误**
+ - 检查`schema-mysql.sql`是否正确
+ - 确认`initialize-schema: always`配置
+
+2. **同步失败**
+ - 检查数据库连接
+ - 查看日志中的错误信息
+
+3. **会话列表为空**
+ - 确认会话已创建:`POST /ai/test/session`
+ - 检查数据库中`ai_chat_sessions`表
+
+### 调试命令
+
+```bash
+# 查看应用日志
+tail -f logs/unilife.log | grep -i "chat\|memory\|session"
+
+# 检查数据库连接
+mysql -u root -p -e "USE UniLife; SHOW TABLES LIKE '%chat%';"
+```
+
+## 总结
+
+该实现提供了一个完整的AI聊天会话历史管理解决方案,结合了Spring AI的强大记忆功能和MySQL的持久化存储。通过这种双重存储架构,您既享受了Spring AI的智能记忆管理,又获得了完整的会话历史功能。
+
+启动应用后,访问Swagger UI(`http://localhost:8087/doc.html`)查看所有可用的API接口并进行测试。
\ No newline at end of file
diff --git a/unilife-server/SIMPLIFIED_CHAT_HISTORY.md b/unilife-server/SIMPLIFIED_CHAT_HISTORY.md
new file mode 100644
index 0000000..92520cf
--- /dev/null
+++ b/unilife-server/SIMPLIFIED_CHAT_HISTORY.md
@@ -0,0 +1,72 @@
+# 简化的AI聊天会话历史方案
+
+## 现状分析
+
+您的项目已经有了完整的Spring AI ChatMemory + MySQL实现:
+- ✅ Spring AI自动将消息存储到 `SPRING_AI_CHAT_MEMORY` 表
+- ✅ 自动会话记忆功能(20条消息窗口)
+- ✅ 消息持久化到MySQL
+
+## 缺失的功能
+
+Spring AI ChatMemory 专注于消息存储,但缺少:
+- ❌ 会话列表管理
+- ❌ 会话标题管理
+- ❌ 会话创建时间等元数据
+
+## 建议的简化方案
+
+### 1. 保留的表结构
+
+只需要一个会话管理表:
+
+```sql
+-- 会话元数据管理表(补充Spring AI ChatMemory)
+CREATE TABLE IF NOT EXISTS ai_chat_sessions (
+ id VARCHAR(64) PRIMARY KEY COMMENT '会话ID',
+ user_id BIGINT NULL COMMENT '用户ID(可选)',
+ title VARCHAR(200) NOT NULL DEFAULT '新对话' COMMENT '会话标题',
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ INDEX idx_user_id (user_id),
+ INDEX idx_created_at (created_at)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+```
+
+### 2. 删除冗余的表
+
+可以删除:
+- `ai_chat_messages_history` 表(与 SPRING_AI_CHAT_MEMORY 重复)
+
+### 3. 简化的服务层
+
+只需要会话元数据管理,消息历史直接从 `SPRING_AI_CHAT_MEMORY` 查询:
+
+```java
+// 获取会话消息历史 - 直接查询Spring AI表
+@Override
+public Result getSessionMessages(String sessionId, Integer page, Integer size) {
+ // 直接从Spring AI ChatMemory获取
+ List messages = chatMemory.get(sessionId);
+
+ // 转换为VO并返回
+ // ... 转换逻辑
+}
+```
+
+### 4. 保留的核心功能
+
+- 会话列表管理
+- 会话标题管理
+- 会话创建/删除
+- 从 SPRING_AI_CHAT_MEMORY 表直接查询消息历史
+
+## 实际需要的修改
+
+1. **删除冗余表**: 移除 `ai_chat_messages_history`
+2. **简化服务**: 移除消息同步逻辑,直接使用Spring AI ChatMemory
+3. **保留会话管理**: 只管理会话元数据
+
+## 结论
+
+您的担心是对的,大部分功能确实是冗余的。Spring AI ChatMemory已经提供了强大的消息存储和记忆功能,我们只需要补充会话元数据管理即可。
\ No newline at end of file
diff --git a/unilife-server/pom.xml b/unilife-server/pom.xml
index 77a66aa..b66ab5e 100644
--- a/unilife-server/pom.xml
+++ b/unilife-server/pom.xml
@@ -189,6 +189,12 @@
org.springframework.ai
spring-ai-starter-model-openai
+
+
+
+ org.springframework.ai
+ spring-ai-starter-model-chat-memory-repository-jdbc
+
@@ -202,6 +208,7 @@
${java.version}
${java.version}
UTF-8
+ true
@@ -210,6 +217,15 @@
org.springframework.boot
spring-boot-maven-plugin
${spring-boot.version}
+
+
+
+
+ org.projectlombok
+ lombok
+
+
+
diff --git a/unilife-server/src/main/java/com/unilife/common/result/Result.java b/unilife-server/src/main/java/com/unilife/common/result/Result.java
index 1e91ff2..cc21d68 100644
--- a/unilife-server/src/main/java/com/unilife/common/result/Result.java
+++ b/unilife-server/src/main/java/com/unilife/common/result/Result.java
@@ -53,6 +53,10 @@ public class Result{
return new Result<>(code, message, null);
}
+ public static Result error(String message) {
+ return new Result<>(500, message, null);
+ }
+
public static Result error(T data,String message){
return new Result<>(200,message,null);
}
diff --git a/unilife-server/src/main/java/com/unilife/config/AiConfig.java b/unilife-server/src/main/java/com/unilife/config/AiConfig.java
index 5444222..afc43a4 100644
--- a/unilife-server/src/main/java/com/unilife/config/AiConfig.java
+++ b/unilife-server/src/main/java/com/unilife/config/AiConfig.java
@@ -1,20 +1,40 @@
package com.unilife.config;
import org.springframework.ai.chat.client.ChatClient;
+import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
+import org.springframework.ai.chat.memory.ChatMemory;
+import org.springframework.ai.chat.memory.ChatMemoryRepository;
+import org.springframework.ai.chat.memory.MessageWindowChatMemory;
+
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AiConfig {
+
+ /**
+ * 配置ChatMemory,使用JDBC存储库实现持久化
+ */
+ @Bean
+ public ChatMemory chatMemory(ChatMemoryRepository chatMemoryRepository) {
+ return MessageWindowChatMemory.builder()
+ .chatMemoryRepository(chatMemoryRepository)
+ .maxMessages(20) // 保留最近20条消息作为上下文
+ .build();
+ }
+
+ /**
+ * 配置ChatClient,集成Chat Memory功能
+ */
@Bean
- public ChatClient chatClient(OpenAiChatModel model) {
+ public ChatClient chatClient(OpenAiChatModel model, ChatMemory chatMemory) {
return ChatClient.builder(model)
- .defaultAdvisors(new SimpleLoggerAdvisor())
+ .defaultAdvisors(
+ new SimpleLoggerAdvisor(),
+ MessageChatMemoryAdvisor.builder(chatMemory).build()
+ )
.build();
}
-
-
-
}
diff --git a/unilife-server/src/main/java/com/unilife/config/WebMvcConfig.java b/unilife-server/src/main/java/com/unilife/config/WebMvcConfig.java
index 96b6916..2f3c8d9 100644
--- a/unilife-server/src/main/java/com/unilife/config/WebMvcConfig.java
+++ b/unilife-server/src/main/java/com/unilife/config/WebMvcConfig.java
@@ -24,7 +24,6 @@ public class WebMvcConfig implements WebMvcConfigurer {
"/users/register",
"/users/code",
"/users/login/code",
- "/ai/**",
// 静态资源访问
"/api/files/**",
diff --git a/unilife-server/src/main/java/com/unilife/mapper/AiChatSessionMapper.java b/unilife-server/src/main/java/com/unilife/mapper/AiChatSessionMapper.java
new file mode 100644
index 0000000..fcc5f88
--- /dev/null
+++ b/unilife-server/src/main/java/com/unilife/mapper/AiChatSessionMapper.java
@@ -0,0 +1,70 @@
+package com.unilife.mapper;
+
+import com.unilife.model.entity.AiChatSession;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * AI聊天会话Mapper
+ */
+@Mapper
+public interface AiChatSessionMapper {
+
+ /**
+ * 插入会话
+ */
+ int insert(AiChatSession session);
+
+ /**
+ * 根据ID查询会话
+ */
+ AiChatSession selectById(@Param("id") String id);
+
+ /**
+ * 根据用户ID分页查询会话列表
+ */
+ List selectByUserId(@Param("userId") Long userId,
+ @Param("offset") int offset,
+ @Param("limit") int limit);
+
+ /**
+ * 查询匿名会话列表(用户ID为空)
+ */
+ List selectAnonymousSessions(@Param("offset") int offset,
+ @Param("limit") int limit);
+
+ /**
+ * 统计用户会话总数
+ */
+ long countByUserId(@Param("userId") Long userId);
+
+ /**
+ * 统计匿名会话总数
+ */
+ long countAnonymousSessions();
+
+ /**
+ * 更新会话标题
+ */
+ int updateTitle(@Param("id") String id, @Param("title") String title);
+
+ /**
+ * 更新会话的最后消息时间和消息数量
+ */
+ int updateMessageInfo(@Param("id") String id,
+ @Param("lastMessageTime") LocalDateTime lastMessageTime,
+ @Param("messageCount") Integer messageCount);
+
+ /**
+ * 删除会话
+ */
+ int deleteById(@Param("id") String id);
+
+ /**
+ * 批量删除过期会话
+ */
+ int deleteExpiredSessions(@Param("expireTime") LocalDateTime expireTime);
+}
\ No newline at end of file
diff --git a/unilife-server/src/main/java/com/unilife/model/entity/AiChatMessage.java b/unilife-server/src/main/java/com/unilife/model/entity/AiChatMessage.java
deleted file mode 100644
index a492337..0000000
--- a/unilife-server/src/main/java/com/unilife/model/entity/AiChatMessage.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package com.unilife.model.entity;
-
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-import java.time.LocalDateTime;
-
-@Data
-@AllArgsConstructor
-@NoArgsConstructor
-public class AiChatMessage {
- /**
- * 消息ID(前端生成)
- */
- private String id;
-
- /**
- * 会话ID
- */
- private String sessionId;
-
- /**
- * 角色 (user, assistant, system)
- */
- private String role;
-
- /**
- * 消息内容
- */
- private String content;
-
- /**
- * 创建时间
- */
- private LocalDateTime createdAt;
-}
\ No newline at end of file
diff --git a/unilife-server/src/main/java/com/unilife/model/entity/AiChatSession.java b/unilife-server/src/main/java/com/unilife/model/entity/AiChatSession.java
index 1ffe0e2..a4826e5 100644
--- a/unilife-server/src/main/java/com/unilife/model/entity/AiChatSession.java
+++ b/unilife-server/src/main/java/com/unilife/model/entity/AiChatSession.java
@@ -6,6 +6,10 @@ import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
+/**
+ * AI聊天会话实体
+ * 只管理会话元数据,消息存储由Spring AI ChatMemory处理
+ */
@Data
@AllArgsConstructor
@NoArgsConstructor
@@ -16,7 +20,7 @@ public class AiChatSession {
private String id;
/**
- * 用户ID
+ * 用户ID(可选,支持匿名会话)
*/
private Long userId;
diff --git a/unilife-server/src/main/java/com/unilife/model/vo/AiSessionVO.java b/unilife-server/src/main/java/com/unilife/model/vo/AiSessionVO.java
index ceb8b1b..62317de 100644
--- a/unilife-server/src/main/java/com/unilife/model/vo/AiSessionVO.java
+++ b/unilife-server/src/main/java/com/unilife/model/vo/AiSessionVO.java
@@ -27,14 +27,5 @@ public class AiSessionVO {
* 更新时间
*/
private String updatedAt;
-
- /**
- * 最后消息时间
- */
- private String lastMessageTime;
-
- /**
- * 消息总数
- */
- private Integer messageCount;
+
}
\ No newline at end of file
diff --git a/unilife-server/src/main/java/com/unilife/service/AiChatSessionHistoryService.java b/unilife-server/src/main/java/com/unilife/service/AiChatSessionHistoryService.java
new file mode 100644
index 0000000..f73fd6f
--- /dev/null
+++ b/unilife-server/src/main/java/com/unilife/service/AiChatSessionHistoryService.java
@@ -0,0 +1,52 @@
+package com.unilife.service;
+
+import com.unilife.common.result.Result;
+import com.unilife.model.entity.AiChatSession;
+import com.unilife.model.vo.AiSessionListVO;
+
+/**
+ * AI聊天会话元数据管理服务
+ * 只处理会话元数据,消息存储和查询由Spring AI ChatMemory处理
+ */
+public interface AiChatSessionHistoryService {
+
+ /**
+ * 创建或更新会话
+ * @param sessionId 会话ID
+ * @param userId 用户ID(可选,支持匿名会话)
+ * @param title 会话标题
+ * @return 创建结果
+ */
+ Result createOrUpdateSession(String sessionId, Long userId, String title);
+
+ /**
+ * 获取会话列表(支持用户会话和匿名会话)
+ * @param userId 用户ID(为null时查询匿名会话)
+ * @param page 页码
+ * @param size 每页大小
+ * @return 会话列表
+ */
+ Result getSessionList(Long userId, Integer page, Integer size);
+
+ /**
+ * 获取会话详细信息
+ * @param sessionId 会话ID
+ * @return 会话信息
+ */
+ Result getSessionDetail(String sessionId);
+
+ /**
+ * 更新会话标题
+ * @param sessionId 会话ID
+ * @param title 新标题
+ * @return 更新结果
+ */
+ Result updateSessionTitle(String sessionId, String title);
+
+ /**
+ * 删除会话(会话元数据和Spring AI ChatMemory中的消息)
+ * @param sessionId 会话ID
+ * @return 删除结果
+ */
+ Result deleteSession(String sessionId);
+}
\ No newline at end of file
diff --git a/unilife-server/src/main/java/com/unilife/service/impl/AiChatSessionHistoryServiceImpl.java b/unilife-server/src/main/java/com/unilife/service/impl/AiChatSessionHistoryServiceImpl.java
new file mode 100644
index 0000000..1931bfd
--- /dev/null
+++ b/unilife-server/src/main/java/com/unilife/service/impl/AiChatSessionHistoryServiceImpl.java
@@ -0,0 +1,169 @@
+package com.unilife.service.impl;
+
+import com.unilife.common.result.Result;
+import com.unilife.mapper.AiChatSessionMapper;
+import com.unilife.model.entity.AiChatSession;
+import com.unilife.model.vo.AiSessionListVO;
+import com.unilife.model.vo.AiSessionVO;
+import com.unilife.service.AiChatSessionHistoryService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * AI聊天会话元数据管理服务实现
+ * 只处理会话元数据,消息存储和查询由Spring AI ChatMemory处理
+ */
+@Service
+@Slf4j
+public class AiChatSessionHistoryServiceImpl implements AiChatSessionHistoryService {
+
+ @Autowired
+ private AiChatSessionMapper sessionMapper;
+
+ private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+
+ @Override
+ @Transactional
+ public Result createOrUpdateSession(String sessionId, Long userId, String title) {
+ log.info("创建或更新会话: sessionId={}, userId={}, title={}", sessionId, userId, title);
+
+ try {
+ // 检查会话是否已存在
+ AiChatSession existingSession = sessionMapper.selectById(sessionId);
+
+ if (existingSession != null) {
+ // 更新现有会话
+ if (title != null && !title.equals(existingSession.getTitle())) {
+ sessionMapper.updateTitle(sessionId, title);
+ existingSession.setTitle(title);
+ existingSession.setUpdatedAt(LocalDateTime.now());
+ }
+ return Result.success(existingSession);
+ } else {
+ // 创建新会话
+ AiChatSession newSession = new AiChatSession();
+ newSession.setId(sessionId);
+ newSession.setUserId(userId);
+ newSession.setTitle(title != null ? title : "新对话");
+ newSession.setCreatedAt(LocalDateTime.now());
+ newSession.setUpdatedAt(LocalDateTime.now());
+
+ sessionMapper.insert(newSession);
+ log.info("成功创建新会话: {}", sessionId);
+ return Result.success(newSession);
+ }
+ } catch (Exception e) {
+ log.error("创建或更新会话失败: {}", e.getMessage(), e);
+ return Result.error("会话操作失败");
+ }
+ }
+
+ @Override
+ public Result getSessionList(Long userId, Integer page, Integer size) {
+ log.info("获取会话列表: userId={}, page={}, size={}", userId, page, size);
+
+ try {
+ int offset = (page - 1) * size;
+
+ List sessions;
+ long total;
+
+ if (userId != null) {
+ // 查询用户会话
+ sessions = sessionMapper.selectByUserId(userId, offset, size);
+ total = sessionMapper.countByUserId(userId);
+ } else {
+ // 查询匿名会话
+ sessions = sessionMapper.selectAnonymousSessions(offset, size);
+ total = sessionMapper.countAnonymousSessions();
+ }
+
+ // 转换为VO
+ List sessionVOs = sessions.stream()
+ .map(this::convertToSessionVO)
+ .collect(Collectors.toList());
+
+ AiSessionListVO result = new AiSessionListVO();
+ result.setSessions(sessionVOs);
+ result.setTotal(total);
+
+ return Result.success(result);
+ } catch (Exception e) {
+ log.error("获取会话列表失败: {}", e.getMessage(), e);
+ return Result.error("获取会话列表失败");
+ }
+ }
+
+ @Override
+ public Result getSessionDetail(String sessionId) {
+ log.info("获取会话详细信息: {}", sessionId);
+
+ try {
+ AiChatSession session = sessionMapper.selectById(sessionId);
+ if (session == null) {
+ return Result.error("会话不存在");
+ }
+ return Result.success(session);
+ } catch (Exception e) {
+ log.error("获取会话详细信息失败: {}", e.getMessage(), e);
+ return Result.error("获取会话信息失败");
+ }
+ }
+
+ @Override
+ @Transactional
+ public Result updateSessionTitle(String sessionId, String title) {
+ log.info("更新会话标题: sessionId={}, title={}", sessionId, title);
+
+ try {
+ int updated = sessionMapper.updateTitle(sessionId, title);
+ if (updated > 0) {
+ return Result.success();
+ } else {
+ return Result.error("会话不存在或更新失败");
+ }
+ } catch (Exception e) {
+ log.error("更新会话标题失败: {}", e.getMessage(), e);
+ return Result.error("更新会话标题失败");
+ }
+ }
+
+ @Override
+ @Transactional
+ public Result deleteSession(String sessionId) {
+ log.info("删除会话: {}", sessionId);
+
+ try {
+ // 只删除会话元数据,Spring AI ChatMemory中的消息由AiServiceImpl处理
+ int deleted = sessionMapper.deleteById(sessionId);
+ if (deleted > 0) {
+ log.info("成功删除会话: {}", sessionId);
+ return Result.success();
+ } else {
+ return Result.error("会话不存在");
+ }
+ } catch (Exception e) {
+ log.error("删除会话失败: {}", e.getMessage(), e);
+ return Result.error("删除会话失败");
+ }
+ }
+
+ /**
+ * 转换为SessionVO
+ */
+ private AiSessionVO convertToSessionVO(AiChatSession session) {
+ AiSessionVO vo = new AiSessionVO();
+ vo.setId(session.getId());
+ vo.setTitle(session.getTitle());
+ vo.setCreatedAt(session.getCreatedAt() != null ? session.getCreatedAt().format(FORMATTER) : null);
+ vo.setUpdatedAt(session.getUpdatedAt() != null ? session.getUpdatedAt().format(FORMATTER) : null);
+ return vo;
+ }
+}
\ No newline at end of file
diff --git a/unilife-server/src/main/java/com/unilife/service/impl/AiServiceImpl.java b/unilife-server/src/main/java/com/unilife/service/impl/AiServiceImpl.java
index fc97c86..7d31900 100644
--- a/unilife-server/src/main/java/com/unilife/service/impl/AiServiceImpl.java
+++ b/unilife-server/src/main/java/com/unilife/service/impl/AiServiceImpl.java
@@ -8,13 +8,20 @@ import com.unilife.model.vo.AiCreateSessionVO;
import com.unilife.model.vo.AiMessageHistoryVO;
import com.unilife.model.vo.AiSessionListVO;
import com.unilife.service.AiService;
+import com.unilife.service.AiChatSessionHistoryService;
+import com.unilife.utils.BaseContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
+import org.springframework.ai.chat.memory.ChatMemory;
+import org.springframework.ai.chat.messages.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
+import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
@Service
@Slf4j
@@ -22,77 +29,149 @@ public class AiServiceImpl implements AiService {
@Autowired
private ChatClient chatClient;
+
+ @Autowired
+ private ChatMemory chatMemory;
+
+ @Autowired
+ private AiChatSessionHistoryService sessionHistoryService;
@Override
public Flux sendMessage(AiSendMessageDTO sendMessageDTO) {
- log.info("发送消息给AI: {}", sendMessageDTO.getMessage());
+ log.info("发送消息给AI: {}, 会话ID: {}", sendMessageDTO.getMessage(), sendMessageDTO.getSessionId());
+
+ String sessionId = sendMessageDTO.getSessionId();
+ if (sessionId == null || sessionId.trim().isEmpty()) {
+ // 如果没有提供会话ID,生成一个新的
+ sessionId = "session_" + System.currentTimeMillis();
+ log.info("生成新的会话ID: {}", sessionId);
+ }
+
+ // 确保会话元数据存在
+ sessionHistoryService.createOrUpdateSession(sessionId, BaseContext.getId(), "新对话");
- // 使用ChatClient的流式响应
- return chatClient.prompt(sendMessageDTO.getMessage())
+ // 使用ChatClient的流式响应,Spring AI会自动处理记忆
+ return chatClient.prompt()
+ .user(sendMessageDTO.getMessage())
+ .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, sendMessageDTO.getSessionId()))
.stream()
.content();
}
@Override
public Result getSessionList(Integer page, Integer size) {
- log.info("获取会话列表,页码: {}, 每页大小: {}", page, size);
+ log.info("获取会话列表: page={}, size={}", page, size);
- // TODO: 实现从数据库获取会话列表的逻辑
- AiSessionListVO sessionList = new AiSessionListVO();
- sessionList.setSessions(new ArrayList<>());
- sessionList.setTotal(0L);
-
- return Result.success(sessionList);
+ // 使用会话历史服务获取会话列表(支持匿名会话)
+ return sessionHistoryService.getSessionList(null, page, size);
}
@Override
public Result getSessionMessages(String sessionId, Integer page, Integer size) {
- log.info("获取会话消息历史,会话ID: {}, 页码: {}, 每页大小: {}", sessionId, page, size);
-
- // TODO: 实现从数据库获取消息历史的逻辑
- AiMessageHistoryVO messageHistory = new AiMessageHistoryVO();
- messageHistory.setMessages(new ArrayList<>());
- messageHistory.setTotal(0L);
+ log.info("获取会话消息历史,会话ID: {}", sessionId);
- return Result.success(messageHistory);
+ try {
+ // 直接从Spring AI ChatMemory获取消息历史
+ List messages = chatMemory.get(sessionId);
+
+ AiMessageHistoryVO messageHistory = new AiMessageHistoryVO();
+
+ if (messages != null && !messages.isEmpty()) {
+ // 转换Message为VO对象
+ List messageVOs = messages.stream()
+ .map(message -> {
+ com.unilife.model.vo.AiMessageVO vo = new com.unilife.model.vo.AiMessageVO();
+ vo.setId(String.valueOf(System.currentTimeMillis() + Math.random()));
+ vo.setRole(message.getMessageType().getValue().toLowerCase());
+ vo.setContent(message.getText());
+ vo.setTimestamp(java.time.LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
+ return vo;
+ })
+ .collect(Collectors.toList());
+
+ messageHistory.setMessages(messageVOs);
+ messageHistory.setTotal((long) messageVOs.size());
+ } else {
+ messageHistory.setMessages(new ArrayList<>());
+ messageHistory.setTotal(0L);
+ }
+
+ return Result.success(messageHistory);
+ } catch (Exception e) {
+ log.error("获取会话消息历史失败: {}", e.getMessage(), e);
+ return Result.error("获取消息历史失败");
+ }
}
@Override
public Result createSession(AiCreateSessionDTO createSessionDTO) {
log.info("创建聊天会话: {}", createSessionDTO.getSessionId());
- // TODO: 实现在数据库中创建会话的逻辑
- AiCreateSessionVO response = new AiCreateSessionVO();
- response.setSessionId(createSessionDTO.getSessionId());
- response.setTitle(createSessionDTO.getTitle() != null ? createSessionDTO.getTitle() : "新对话");
-
- return Result.success(response);
+ try {
+ // 使用会话历史服务创建会话元数据
+ Result> result = sessionHistoryService.createOrUpdateSession(
+ createSessionDTO.getSessionId(),
+ BaseContext.getId(), // 暂时支持匿名会话
+ createSessionDTO.getTitle()
+ );
+
+ if (result.getCode() == 200) {
+ AiCreateSessionVO response = new AiCreateSessionVO();
+ response.setSessionId(createSessionDTO.getSessionId());
+ response.setTitle(createSessionDTO.getTitle() != null ? createSessionDTO.getTitle() : "新对话");
+ return Result.success(response);
+ } else {
+ return Result.error(result.getMessage());
+ }
+ } catch (Exception e) {
+ log.error("创建会话失败: {}", e.getMessage(), e);
+ return Result.error("创建会话失败");
+ }
}
@Override
public Result updateSessionTitle(String sessionId, AiUpdateSessionDTO updateSessionDTO) {
- log.info("更新会话标题,会话ID: {}, 新标题: {}", sessionId, updateSessionDTO.getTitle());
+ log.info("更新会话标题: sessionId={}, title={}", sessionId, updateSessionDTO.getTitle());
- // TODO: 实现在数据库中更新会话标题的逻辑
-
- return Result.success();
+ // 使用会话历史服务更新标题
+ return sessionHistoryService.updateSessionTitle(sessionId, updateSessionDTO.getTitle());
}
@Override
public Result clearSessionMessages(String sessionId) {
log.info("清空会话消息,会话ID: {}", sessionId);
- // TODO: 实现在数据库中清空会话消息的逻辑
-
- return Result.success();
+ try {
+ // 清空Spring AI ChatMemory中的消息
+ chatMemory.clear(sessionId);
+ log.info("成功清空会话 {} 的消息", sessionId);
+ return Result.success();
+ } catch (Exception e) {
+ log.error("清空会话消息失败: {}", e.getMessage(), e);
+ return Result.error("清空会话消息失败");
+ }
}
@Override
public Result deleteSession(String sessionId) {
log.info("删除会话,会话ID: {}", sessionId);
- // TODO: 实现在数据库中删除会话的逻辑
-
- return Result.success();
+ try {
+ // 删除Spring AI ChatMemory中的消息
+ chatMemory.clear(sessionId);
+
+ // 删除会话元数据
+ Result result = sessionHistoryService.deleteSession(sessionId);
+
+ if (result.getCode() == 200) {
+ log.info("成功删除会话 {}", sessionId);
+ return Result.success();
+ } else {
+ return result;
+ }
+ } catch (Exception e) {
+ log.error("删除会话失败: {}", e.getMessage(), e);
+ return Result.error("删除会话失败");
+ }
}
}
diff --git a/unilife-server/src/main/resources/application.yml b/unilife-server/src/main/resources/application.yml
index 8bbb2f9..a3951be 100644
--- a/unilife-server/src/main/resources/application.yml
+++ b/unilife-server/src/main/resources/application.yml
@@ -12,6 +12,12 @@ spring:
options:
model: text-embedding-v3
dimensions: 1024
+ chat:
+ memory:
+ repository:
+ jdbc:
+ initialize-schema: always # 自动初始化表结构
+ schema: classpath:schema-mysql.sql
datasource:
url: jdbc:mysql://localhost:3306/UniLife?allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=UTC&characterEncoding=UTF-8
username: root
diff --git a/unilife-server/src/main/resources/mappers/AiChatSessionMapper.xml b/unilife-server/src/main/resources/mappers/AiChatSessionMapper.xml
new file mode 100644
index 0000000..afb5af7
--- /dev/null
+++ b/unilife-server/src/main/resources/mappers/AiChatSessionMapper.xml
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ INSERT INTO ai_chat_sessions (
+ id, user_id, title, created_at, updated_at
+ ) VALUES (
+ #{id}, #{userId}, #{title}, #{createdAt}, #{updatedAt}
+ )
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ UPDATE ai_chat_sessions
+ SET title = #{title}, updated_at = CURRENT_TIMESTAMP
+ WHERE id = #{id}
+
+
+
+
+ UPDATE ai_chat_sessions
+ SET updated_at = CURRENT_TIMESTAMP
+ WHERE id = #{id}
+
+
+
+
+ DELETE FROM ai_chat_sessions WHERE id = #{id}
+
+
+
+
+ DELETE FROM ai_chat_sessions
+ WHERE updated_at < #{expireTime}
+
+
+
\ No newline at end of file
diff --git a/unilife-server/src/main/resources/schema-mysql.sql b/unilife-server/src/main/resources/schema-mysql.sql
new file mode 100644
index 0000000..0e4325f
--- /dev/null
+++ b/unilife-server/src/main/resources/schema-mysql.sql
@@ -0,0 +1,24 @@
+-- Spring AI Chat Memory MySQL Schema
+-- 此表用于存储AI聊天会话的消息记忆
+
+CREATE TABLE IF NOT EXISTS SPRING_AI_CHAT_MEMORY (
+ conversation_id VARCHAR(36) NOT NULL COMMENT '会话ID',
+ content TEXT NOT NULL COMMENT '消息内容',
+ type VARCHAR(10) NOT NULL COMMENT '消息类型:USER、ASSISTANT、SYSTEM、TOOL',
+ `timestamp` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '消息时间戳',
+ INDEX idx_conversation_id_timestamp (conversation_id, `timestamp`),
+ CONSTRAINT chk_message_type CHECK (type IN ('USER', 'ASSISTANT', 'SYSTEM', 'TOOL'))
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Spring AI聊天记忆表';
+
+-- 会话元数据管理表(补充Spring AI ChatMemory功能)
+-- 只管理会话的元数据信息,消息存储由Spring AI ChatMemory处理
+CREATE TABLE IF NOT EXISTS ai_chat_sessions (
+ id VARCHAR(64) PRIMARY KEY COMMENT '会话ID(前端生成)',
+ user_id BIGINT NULL COMMENT '用户ID(可选,支持匿名会话)',
+ title VARCHAR(200) NOT NULL DEFAULT '新对话' COMMENT '会话标题',
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ INDEX idx_user_id (user_id),
+ INDEX idx_created_at (created_at),
+ INDEX idx_updated_at (updated_at)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='AI聊天会话元数据表';
\ No newline at end of file