refactor(project):重构项目结构并迁移至snails-chat模块- 将项目主模块更名为snails-chat,调整包结构

- 移除JPA相关依赖,替换为MyBatis-Plus- 数据库从MySQL迁移至PostgreSQL- 移除QueryTool工具类及相关依赖- 更新Redis配置,使用JSON序列化- 移除DashScopeController及AIResponse类
- 添加User实体类及Mapper接口
- 调整ChatClientConfiguration配置类- 更新pom.xml依赖管理及模块配置
This commit is contained in:
2025-10-25 10:06:37 +08:00
parent 40c05838f7
commit 177dfff3c7
25 changed files with 203 additions and 338 deletions

View File

@@ -0,0 +1,13 @@
package com.hanserwei.chat;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.hanserwei.chat.domain.mapper")
public class SnailsChatApplication {
public static void main(String[] args) {
SpringApplication.run(SnailsChatApplication.class, args);
}
}

View File

@@ -0,0 +1,57 @@
package com.hanserwei.chat.config;
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 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.Configuration;
@Configuration
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
private DashScopeChatModel dashScopeChatModel;
@Bean
public BaseRedisChatMemoryRepository redisChatMemoryRepository() {
// 构建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.chat.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

@@ -0,0 +1,30 @@
package com.hanserwei.chat.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
// 设置 RedisTemplate 的连接工厂
redisTemplate.setConnectionFactory(connectionFactory);
// 使用 StringRedisSerializer 来序列化和反序列化 redis 的 key 值,确保 key 是可读的字符串
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
// 使用 Jackson2JsonRedisSerializer 来序列化和反序列化 redis 的 value 值, 确保存储的是 JSON 格式
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
redisTemplate.setValueSerializer(serializer);
redisTemplate.setHashValueSerializer(serializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}

View File

@@ -0,0 +1,37 @@
package com.hanserwei.chat.domain.dataobject;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "t_user")
public class User {
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
@TableField(value = "\"name\"")
private String name;
@TableField(value = "email")
private String email;
@TableField(value = "age")
private Integer age;
@TableField(value = "created_at")
private LocalDateTime createdAt;
@TableField(value = "is_active")
private Boolean isActive;
}

View File

@@ -0,0 +1,9 @@
package com.hanserwei.chat.domain.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.hanserwei.chat.domain.dataobject.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper extends BaseMapper<User> {
}

View File

@@ -0,0 +1,9 @@
package com.hanserwei.chat.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.hanserwei.chat.domain.dataobject.User;
public interface UserService extends IService<User> {
}

View File

@@ -0,0 +1,12 @@
package com.hanserwei.chat.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hanserwei.chat.domain.dataobject.User;
import com.hanserwei.chat.domain.mapper.UserMapper;
import com.hanserwei.chat.service.UserService;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}

View File

@@ -0,0 +1,11 @@
package com.hanserwei.chat.utils;
import org.jasypt.util.text.AES256TextEncryptor;
public class EncryptorUtil {
public static void main(String[] args) {
AES256TextEncryptor textEncryptor = new AES256TextEncryptor();
textEncryptor.setPassword("yyyyyyyyyyyy");
System.out.println(textEncryptor.encrypt("xxxxxxxxxxxxxxxxxxxxxx"));
}
}

View File

@@ -0,0 +1,66 @@
#file: noinspection SpringBootConfigYamlInspection
server:
servlet:
encoding:
charset: utf-8
force: true
spring:
application:
name: snails-ai
banner:
location: config/banner.txt
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:
driver-class-name: org.postgresql.Driver
url: jdbc:postgresql://localhost:5432/postgres
username: postgres
password: postgressql
# HikariCP 连接池配置
hikari:
maximum-pool-size: 20 # 最大连接数设置为 20
minimum-idle: 20 # 保持 20 个空闲连接(与最大连接数一致)
connection-timeout: 5000 # 获取连接超时 5 秒
max-lifetime: 28800000 # 8 小时(确保在数据库连接超时前被回收)
ai:
memory:
redis:
host: localhost
port: 6379
timeout: 5000
password: redis
dashscope:
api-key: ENC(cMgcKZkFllyE88DIbGwLKot9Vg02co+gsmY8L8o4/o3UjhcmqO4lJzFU35Sx0n+qFG8pDL0wBjoWrT8X6BuRw9vNlQhY1LgRWHaF9S1zzyM=)
chat:
options:
model: qwen-plus
temperature: 0.5
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
global-config:
banner: false
mapper-locations: classpath*:/mapperxml/*.xml
logging:
level:
# 隐藏掉 Hibernate 冗长的连接池 INFO 信息
org.hibernate.orm.connections.pooling: WARN
org.springframework.ai.chat.client.advisor: DEBUG
jasypt:
encryptor:
password: ${jasypt.encryptor.password}
algorithm: PBEWithHMACSHA512AndAES_256
iv-generator-classname: org.jasypt.iv.RandomIvGenerator

View File

@@ -0,0 +1,11 @@
___ ___ ___ ___ ___ ___
/\ \ /\__\ /\ \ ___ /\__\ /\ \ /\ \ ___
/::\ \ /::| | /::\ \ /\ \ /:/ / /::\ \ /::\ \ /\ \
/:/\ \ \ /:|:| | /:/\:\ \ \:\ \ /:/ / /:/\ \ \ /:/\:\ \ \:\ \
_\:\~\ \ \ /:/|:| |__ /::\~\:\ \ /::\__\ /:/ / _\:\~\ \ \ /::\~\:\ \ /::\__\
/\ \:\ \ \__\ /:/ |:| /\__\ /:/\:\ \:\__\ __/:/\/__/ /:/__/ /\ \:\ \ \__\ /:/\:\ \:\__\ __/:/\/__/
\:\ \:\ \/__/ \/__|:|/:/ / \/__\:\/:/ / /\/:/ / \:\ \ \:\ \:\ \/__/ \/__\:\/:/ / /\/:/ /
\:\ \:\__\ |:/:/ / \::/ / \::/__/ \:\ \ \:\ \:\__\ \::/ / \::/__/
\:\/:/ / |::/ / /:/ / \:\__\ \:\ \ \:\/:/ / /:/ / \:\__\
\::/ / /:/ / /:/ / \/__/ \:\__\ \::/ / /:/ / \/__/
\/__/ \/__/ \/__/ \/__/ \/__/ \/__/

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hanserwei.chat.domain.mapper.UserMapper">
<resultMap id="BaseResultMap" type="com.hanserwei.chat.domain.dataobject.User">
<!--@mbg.generated-->
<!--@Table t_user-->
<id column="id" jdbcType="INTEGER" property="id" />
<result column="name" jdbcType="VARCHAR" property="name" />
<result column="email" jdbcType="VARCHAR" property="email" />
<result column="age" jdbcType="INTEGER" property="age" />
<result column="created_at" jdbcType="TIMESTAMP" property="createdAt" />
<result column="is_active" jdbcType="BOOLEAN" property="isActive" />
</resultMap>
<sql id="Base_Column_List">
<!--@mbg.generated-->
id, "name", email, age, created_at, is_active
</sql>
</mapper>