SpringAI实现保存聊天记录到redis中

发布于:2025-07-13 ⋅ 阅读:(18) ⋅ 点赞:(0)

redis相关准备

添加依赖

我利用redission来实现

<dependency>
  <groupId>org.redisson</groupId>
  <artifactId>redisson</artifactId>
  <version>3.37.0</version>
</dependency>

添加配置文件

spring:
  redis:
    database: 5
    host: 127.0.0.1
    port: 6379
    password: 1234

创建RedisProperties文件

@Data
@Component
@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {

    private String host;
    private int port;
    private String password;
    private int database;
}

创建redissionConfig配置文件

@Configuration
@RequiredArgsConstructor
public class RedisConfig {

    private final RedisProperties redisProperties;

    @Bean
    public RedissonClient redissonClient(){
        Config config = new Config();
        config.useSingleServer()
                .setAddress("redis://" +  redisProperties.getHost() + ":" + redisProperties.getPort())
                .setDatabase(redisProperties.getDatabase())
                .setPassword(redisProperties.getPassword());
        config.setCodec(new StringCodec(StandardCharsets.UTF_8));

        return Redisson.create(config);
    }
}

创建RedissionUtil工具类

package com.sde.util;

import org.redisson.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.jackson.JacksonProperties;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.StreamSupport;

@Component
public class RedissonUtil {
    private static RedissonClient redissonClient;

    @Autowired
    public void setRedissonClient(RedissonClient client) {
        redissonClient = client;
    }

    /**
     * 设置数据的过期时间
     * @param key 要设置的键
     * @param seconds 过期时间(秒)
     */
    public static boolean expire(String key, long seconds) {
        return redissonClient.getKeys().expire(key, seconds, TimeUnit.SECONDS);
    }

    /**
     * 设置数据的过期时间
     * @param key 要设置的键
     * @param duration 时长
     * @param unit 时间单位
     * @return
     */
    public static boolean expire(String key, long duration, TimeUnit unit) {
        return redissonClient.getKeys().expire(key, duration, unit);
    }

    /**
     * 将字符串添加到 Redis List 的尾部
     *
     * @param key Redis 键
     * @param value 要添加的值
     */
    public static void addToList(String key, String value) {
        RList<String> list = redissonClient.getList(key);
        list.add(value);
    }

    /**
     * 获取列表
     * @param key
     * @return
     */
    public static RList<String> getList(String key) {
        return redissonClient.getList(key);
    }

    /**
     * 获取 RList 对象,用于后续操作如 trim、remove 等
     */
    public static RList<String> getRList(String key) {
        return redissonClient.getList(key);
    }

    /**
     * 删除数据
     * @param key 要删除的键
     * @return
     */
    public static boolean remove(final String key) {
        return redissonClient.getKeys().delete(key) >= 0;
    }

    /**
     * 检查键是否存在
     * @param key 键
     * @return 是否存在
     */
    public static boolean exists(String key) {
        RBucket<String> bucket = redissonClient.getBucket(key);
        return bucket.isExists();
    }

}

SpringAI相关准备

不用阿里云的maven仓库,用默认的maven中央仓库。

版本控制

    <properties>
        <java.version>21</java.version>
        <spring-ai.version>1.0.0-SNAPSHOT</spring-ai.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.ai</groupId>
                <artifactId>spring-ai-bom</artifactId>
                <version>${spring-ai.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

添加依赖

        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-model-openai</artifactId>
        </dependency>

因为我对接的阿里云百炼平台

添加配置

spring:
  ai:
    openai:
      base-url: https://dashscope.aliyuncs.com/compatible-mode/
      api-key: ${ALIYUN_AK}
      chat:
        options:
          model: qwen-max

我的api-key 存储在我环境变量中

编写代码

创建保存到redis中的实体

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class StoredMessage implements Serializable {

    @JSONField(ordinal = 0)
    private String role;
    @JSONField(ordinal = 1)
    private String content;

    public static StoredMessage from(Message message) {
        return new StoredMessage(message.getMessageType().name(), message.getText());
    }

    public Message toMessage() {
        return switch (role) {
            case "USER" -> new UserMessage(content);
            case "ASSISTANT" -> new AssistantMessage(content);
            default -> new SystemMessage(content);
        };
    }
}

创建RedisChatMemory

@Component
public class RedisChatMemory implements ChatMemory {
    // redis key 的前缀
    private static final String CHAT_MEMORY_KEY = "chat:conversationid:";
    private static final long TTL_MILLIS = 2 * 24 * 60 * 60 * 1000; // 2天
    private final int maxMessage;
    public RedisChatMemory() {
        this(10); // 默认保留 10 条消息
    }
    public RedisChatMemory(int maxMessages) {
        this.maxMessage = maxMessages;
    }

    @Override
    public void add(String conversationId, Message message) {
        String key = getKey(conversationId);
        StoredMessage storedMessage = StoredMessage.from(message);
        String json = toJson(storedMessage);

        RedissonUtil.addToList(key, json);
        trimMessages(key);
        //刷新过期时间为2天
        RedissonUtil.expire(key, TTL_MILLIS, TimeUnit.MILLISECONDS);
    }

    @Override
    public void add(String conversationId, List<Message> messages) {
        messages.forEach(message -> add(conversationId, message));
    }

    @Override
    public List<Message> get(String conversationId) {
        String key = getKey(conversationId);

        //检查key是否存在
        if (!RedissonUtil.exists(key)) {
            return Collections.emptyList();
        }

        //获取所有 json字符串格式的消息
        List<String> messages = RedissonUtil.getList(key);

        //转换成 Message 列表
        return messages.stream()
                .map(this::fromJson)
                .filter(Objects::nonNull)
                .map(StoredMessage::toMessage)
                .toList();
    }

    @Override
    public void clear(String conversationId) {
        String key = getKey(conversationId);
        RedissonUtil.remove(key);
    }


    /**
     * 构建redis的key
     * @param conversationId
     * @return
     */
    private String getKey(String conversationId){
        return CHAT_MEMORY_KEY + conversationId;
    }


    /**
     * 将消息转换为 JSON 字符串
     */
    private String toJson(StoredMessage msg) {
        SerializeConfig config = new SerializeConfig();
        return JSON.toJSONString(msg,config, SerializerFeature.SortField);
    }

    /**
     * 将 JSON 字符串转换为 StoredMessage 对象
     */
    private StoredMessage fromJson(String json) {
        return JSON.parseObject(json, StoredMessage.class);
    }


    /**
     * 控制消息数量,保留最近 maxMessages 条
     */
    private void trimMessages(String key) {
        RList<String> list = RedissonUtil.getRList(key);
        if (list.size() > maxMessage) {
            int toRemove = list.size() - maxMessage;
            for (int i = 0; i < toRemove; i++) {
                list.remove(0);
            }
        }
    }

}

创建Controller

@Slf4j
@RestController
@RequestMapping("/memory")
public class ChatMemoryController {

  private final ChatClient chatClient;

  public ChatMemoryController(ChatModel qwen) {
    this.chatClient = ChatClient.builder(qwen).build();
  }

  @Autowired
  private RedisChatMemory redisChatMemory;

    @GetMapping("/redis")
    public Flux<String> redis(String question, String conversationId){
      MessageChatMemoryAdvisor advisor = MessageChatMemoryAdvisor.builder(redisChatMemory)
                .conversationId(conversationId)
                .order(5)
                .build();

                return chatClient.prompt()
                        .system("你是我的学习助手名字叫小菜包")
                        .user(question)
                        .advisors(advisor)
                        .stream()
                        .content();
    }
}

测试


网站公告

今日签到

点亮在社区的每一天
去签到