feat<chat>: 添加Redis作为对话记忆的持久化存储

This commit is contained in:
2025-10-22 20:06:39 +08:00
parent 86666ff044
commit 2f9923977a
6 changed files with 115 additions and 4 deletions

View File

@@ -57,6 +57,14 @@
<artifactId>jasypt-spring-boot-starter</artifactId> <artifactId>jasypt-spring-boot-starter</artifactId>
<version>3.0.5</version> <version>3.0.5</version>
</dependency> </dependency>
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-memory-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies> </dependencies>
<dependencyManagement> <dependencyManagement>

View File

@@ -1,19 +1,57 @@
package com.hanserwei.snailsai.config; package com.hanserwei.snailsai.config;
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
import com.alibaba.cloud.ai.memory.redis.BaseRedisChatMemoryRepository;
import com.alibaba.cloud.ai.memory.redis.LettuceRedisChatMemoryRepository;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.PromptChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.MessageWindowChatMemory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@Configuration @Configuration
public class ChatClientConfiguration { public class ChatClientConfiguration {
@Value("${spring.ai.memory.redis.host}")
private String redisHost;
@Value("${spring.ai.memory.redis.port}")
private int redisPort;
@Value("${spring.ai.memory.redis.password}")
private String redisPassword;
@Value("${spring.ai.memory.redis.timeout}")
private int redisTimeout;
@Resource @Resource
private DashScopeChatModel dashScopeChatModel; private DashScopeChatModel dashScopeChatModel;
@Bean @Bean
public ChatClient dashScopeChatClient() { public BaseRedisChatMemoryRepository redisChatMemoryRepository() {
return ChatClient.builder(dashScopeChatModel).build(); // 构建RedissonRedisChatMemoryRepository实例
return LettuceRedisChatMemoryRepository.builder()
.host(redisHost)
.port(redisPort)
.password(redisPassword)
.timeout(redisTimeout)
.build();
}
@Bean
public ChatMemory chatMemory(BaseRedisChatMemoryRepository chatMemoryRepository) {
return MessageWindowChatMemory
.builder()
.maxMessages(100000)
.chatMemoryRepository(chatMemoryRepository).build();
}
@Bean
public ChatClient dashScopeChatClient(ChatMemory chatMemory) {
return ChatClient.builder(dashScopeChatModel)
.defaultAdvisors(PromptChatMemoryAdvisor.builder(chatMemory).build(), new SimpleLoggerAdvisor())
.build();
} }
} }

View File

@@ -0,0 +1,19 @@
package com.hanserwei.snailsai.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") // 匹配所有路径
.allowedOriginPatterns("*") // 允许所有域名(生产环境应指定具体域名)
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // 允许的请求方法
.allowedHeaders("*") // 允许所有请求头
.allowCredentials(true) // 允许发送 Cookie
.maxAge(3600); // 预检请求的有效期(秒)
}
}

View File

@@ -1,7 +1,9 @@
package com.hanserwei.snailsai.controller; package com.hanserwei.snailsai.controller;
import com.hanserwei.snailsai.model.ChatMessageDTO;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
@@ -15,9 +17,10 @@ public class DashScopeController {
private ChatClient dashScopeChatClient; private ChatClient dashScopeChatClient;
@PostMapping(value = "/chat",produces = MediaType.TEXT_EVENT_STREAM_VALUE) @PostMapping(value = "/chat",produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> chat(@RequestBody String userPrompt) { public Flux<String> chat(@RequestBody ChatMessageDTO chatMessageDTO) {
return dashScopeChatClient.prompt() return dashScopeChatClient.prompt()
.user(userPrompt) .user(chatMessageDTO.getMessage())
.advisors(e-> e.param(ChatMemory.CONVERSATION_ID,chatMessageDTO.getConversionId()))
.stream() .stream()
.content(); .content();
} }

View File

@@ -0,0 +1,17 @@
package com.hanserwei.snailsai.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class ChatMessageDTO {
private String message;
private Long conversionId;
}

View File

@@ -1,4 +1,9 @@
#file: noinspection SpringBootConfigYamlInspection #file: noinspection SpringBootConfigYamlInspection
server:
servlet:
encoding:
charset: utf-8
force: true
spring: spring:
application: application:
name: snails-ai name: snails-ai
@@ -12,6 +17,20 @@ spring:
hibernate.format_sql: true hibernate.format_sql: true
# 开启 SQL 语法高亮 (重点:让 SQL 醒目) # 开启 SQL 语法高亮 (重点:让 SQL 醒目)
hibernate.highlight_sql: true hibernate.highlight_sql: true
data:
redis:
host: localhost
port: 6379
password: redis
database: 4
lettuce:
pool:
enabled: true
max-active: 20
max-idle: 10
max-wait: 10000
min-idle: 10
time-between-eviction-runs: 10000
datasource: datasource:
driver-class-name: com.mysql.cj.jdbc.Driver driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/snails_ai?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&allowMultiQueries=true&useSSL=false&allowPublicKeyRetrieval=true url: jdbc:mysql://127.0.0.1:3306/snails_ai?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&allowMultiQueries=true&useSSL=false&allowPublicKeyRetrieval=true
@@ -24,6 +43,12 @@ spring:
connection-timeout: 5000 # 获取连接超时 5 秒 connection-timeout: 5000 # 获取连接超时 5 秒
max-lifetime: 28800000 # 8 小时(确保在数据库连接超时前被回收) max-lifetime: 28800000 # 8 小时(确保在数据库连接超时前被回收)
ai: ai:
memory:
redis:
host: localhost
port: 6379
timeout: 5000
password: redis
dashscope: dashscope:
api-key: ENC(cMgcKZkFllyE88DIbGwLKot9Vg02co+gsmY8L8o4/o3UjhcmqO4lJzFU35Sx0n+qFG8pDL0wBjoWrT8X6BuRw9vNlQhY1LgRWHaF9S1zzyM=) api-key: ENC(cMgcKZkFllyE88DIbGwLKot9Vg02co+gsmY8L8o4/o3UjhcmqO4lJzFU35Sx0n+qFG8pDL0wBjoWrT8X6BuRw9vNlQhY1LgRWHaF9S1zzyM=)
chat: chat:
@@ -35,6 +60,7 @@ logging:
org.hibernate.SQL: debug org.hibernate.SQL: debug
# 隐藏掉 Hibernate 冗长的连接池 INFO 信息 # 隐藏掉 Hibernate 冗长的连接池 INFO 信息
org.hibernate.orm.connections.pooling: WARN org.hibernate.orm.connections.pooling: WARN
org.springframework.ai.chat.client.advisor: DEBUG
jasypt: jasypt:
encryptor: encryptor:
password: ${jasypt.encryptor.password} password: ${jasypt.encryptor.password}