commit a02be6d1eaab88d68161a3466f092019cec9d36c Author: liukai Date: Wed Sep 3 15:08:39 2025 +0800 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..667aaef --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..8104e76 --- /dev/null +++ b/pom.xml @@ -0,0 +1,104 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.5.5 + + + com.ai + spring_ai_app + 1.0.0 + spring_ai_app + spring_ai_app + + 21 + + 1.0.1 + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.springframework.ai + spring-ai-starter-model-deepseek + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + + com.baomidou + mybatis-plus-spring-boot3-starter + 3.5.5 + + + com.baomidou + mybatis-plus-generator + 3.5.5 + + + + + mysql + mysql-connector-java + 8.0.17 + + + + org.projectlombok + lombok + true + + + + com.alibaba + fastjson + 1.2.83 + + + + + org.apache.commons + commons-lang3 + 3.18.0 + + + + + + + + org.springframework.ai + spring-ai-bom + ${spring.ai.bom} + pom + import + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/src/main/java/com/ai/app/SpringAiAppApplication.java b/src/main/java/com/ai/app/SpringAiAppApplication.java new file mode 100644 index 0000000..98c12e9 --- /dev/null +++ b/src/main/java/com/ai/app/SpringAiAppApplication.java @@ -0,0 +1,13 @@ +package com.ai.app; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SpringAiAppApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringAiAppApplication.class, args); + } + +} diff --git a/src/main/java/com/ai/app/config/AIConfig.java b/src/main/java/com/ai/app/config/AIConfig.java new file mode 100644 index 0000000..d76ea84 --- /dev/null +++ b/src/main/java/com/ai/app/config/AIConfig.java @@ -0,0 +1,79 @@ +package com.ai.app.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.InMemoryChatMemoryRepository; +import org.springframework.ai.chat.memory.MessageWindowChatMemory; +import org.springframework.ai.deepseek.DeepSeekChatModel; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @describe ai配置类 + * @Author kai.liu + * @Date 2025/8/28 10:02 + */ +@Configuration +public class AIConfig { + + + @Bean + public InMemoryChatMemoryRepository inMemoryChatMemoryRepository() { + return new InMemoryChatMemoryRepository(); + } + + @Bean + public MessageWindowChatMemory messageWindowChatMemory(InMemoryChatMemoryRepository inMemoryChatMemoryRepository) { + return MessageWindowChatMemory.builder().chatMemoryRepository(inMemoryChatMemoryRepository).build(); + } + + /** + * 配置会话记忆环绕增强Advisor + * + * @param messageWindowChatMemory 消息窗口会话记忆 + * @return: org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor + * @author kai.liu + * @date: 2025/8/29 9:05 + */ + @Bean + public MessageChatMemoryAdvisor messageChatMemoryAdvisor(MessageWindowChatMemory messageWindowChatMemory) { + return MessageChatMemoryAdvisor.builder(messageWindowChatMemory).build(); + } + + /** + * 配置日志环绕增强Advisor + * + * @return: org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor + * @author kai.liu + * @date: 2025/8/29 9:05 + */ + @Bean + public SimpleLoggerAdvisor simpleLoggerAdvisor() { + return new SimpleLoggerAdvisor(); + } + + /** + * 配置AI对话客户端 + * + * @param deepSeekChatModel deepseek大模型 + * @param simpleLoggerAdvisor 日志Advisor + * @param messageChatMemoryAdvisor 会话记忆Advisor + * @return: org.springframework.ai.chat.client.ChatClient + * @author kai.liu + * @date: 2025/8/29 9:06 + */ + @Bean + public ChatClient chatClient(DeepSeekChatModel deepSeekChatModel, + SimpleLoggerAdvisor simpleLoggerAdvisor, + MessageChatMemoryAdvisor messageChatMemoryAdvisor) { + return ChatClient + //模型 + .builder(deepSeekChatModel) + //系统角色 + .defaultSystem("你是一个小团团") + //环绕增强 + .defaultAdvisors(simpleLoggerAdvisor, messageChatMemoryAdvisor) + .build(); + } +} diff --git a/src/main/java/com/ai/app/config/CorsConfig.java b/src/main/java/com/ai/app/config/CorsConfig.java new file mode 100644 index 0000000..edc1cdb --- /dev/null +++ b/src/main/java/com/ai/app/config/CorsConfig.java @@ -0,0 +1,30 @@ +package com.ai.app.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * 跨域配置 + * + * @author shiyong + * 2022/10/24 16:23 + */ +@Configuration +public class CorsConfig { + + @Bean + public WebMvcConfigurer corsConfigurer() { + return new WebMvcConfigurer() { + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedOrigins("*") + .allowedHeaders("*") + .allowedMethods("*") + .maxAge(3600); + } + }; + } +} diff --git a/src/main/java/com/ai/app/config/RedisChatMemoryRepositor.java b/src/main/java/com/ai/app/config/RedisChatMemoryRepositor.java new file mode 100644 index 0000000..536d9cc --- /dev/null +++ b/src/main/java/com/ai/app/config/RedisChatMemoryRepositor.java @@ -0,0 +1,41 @@ +package com.ai.app.config; + +import com.ai.app.service.AIChatMessageService; +import org.springframework.ai.chat.memory.ChatMemoryRepository; +import org.springframework.ai.chat.messages.Message; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * @describe + * @Author kai.liu + * @Date 2025/8/29 9:01 + */ +@Component +public class RedisChatMemoryRepositor implements ChatMemoryRepository { + + @Autowired + private AIChatMessageService aiChatMessageService; + + @Override + public List findConversationIds() { + return aiChatMessageService.getConversationIds(); + } + + @Override + public List findByConversationId(String conversationId) { + return aiChatMessageService.getMessage(conversationId); + } + + @Override + public void saveAll(String conversationId, List messages) { + aiChatMessageService.saveAll(conversationId, messages); + } + + @Override + public void deleteByConversationId(String conversationId) { + aiChatMessageService.delete(conversationId); + } +} diff --git a/src/main/java/com/ai/app/config/RedisConfig.java b/src/main/java/com/ai/app/config/RedisConfig.java new file mode 100644 index 0000000..adabf89 --- /dev/null +++ b/src/main/java/com/ai/app/config/RedisConfig.java @@ -0,0 +1,40 @@ +package com.ai.app.config; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +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.StringRedisSerializer; + +/** + * @describe + * @Author kai.liu + * @Date 2025/9/1 9:33 + */ +@Configuration +public class RedisConfig { + @Bean + @ConditionalOnMissingBean(name = "redisTemplate") + public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(redisConnectionFactory); + + StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); + RedisObjectSerializer redisObjectSerializer = new RedisObjectSerializer(); + + // key采用String的序列化方式 + template.setKeySerializer(stringRedisSerializer); + + // hash的key也采用String的序列化方式 + template.setHashKeySerializer(stringRedisSerializer); + + // value序列化方式采用字节数组 + template.setValueSerializer(redisObjectSerializer); + + // hash的value序列化方式采用字节数组 + template.setHashValueSerializer(redisObjectSerializer); + template.afterPropertiesSet(); + return template; + } +} diff --git a/src/main/java/com/ai/app/config/RedisObjectSerializer.java b/src/main/java/com/ai/app/config/RedisObjectSerializer.java new file mode 100644 index 0000000..c0fe98b --- /dev/null +++ b/src/main/java/com/ai/app/config/RedisObjectSerializer.java @@ -0,0 +1,49 @@ +package com.ai.app.config; + +import org.springframework.core.convert.converter.Converter; +import org.springframework.core.serializer.support.DeserializingConverter; +import org.springframework.core.serializer.support.SerializingConverter; +import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.data.redis.serializer.SerializationException; + +/** + * 自定义Redis序列化类 + * + * @author shiyong + * 2020-01-03 18:01 + */ +public class RedisObjectSerializer implements RedisSerializer { + private Converter serializer = new SerializingConverter(); + private Converter deserializer = new DeserializingConverter(); + static final byte[] EMPTY_ARRAY = new byte[0]; + + /** + * Serialize the given object to binary data. + * + * @param o object to serialize. Can be {@literal null}. + * @return the equivalent binary data. Can be {@literal null}. + */ + @Override + public byte[] serialize(Object o) throws SerializationException { + if (o == null) { + return EMPTY_ARRAY; + } + + return serializer.convert(o); + } + + /** + * Deserialize an object from the given binary data. + * + * @param bytes object binary representation. Can be {@literal null}. + * @return the equivalent object instance. Can be {@literal null}. + */ + @Override + public Object deserialize(byte[] bytes) throws SerializationException { + if (bytes == null || bytes.length <= 0) { + return null; + } + + return deserializer.convert(bytes); + } +} diff --git a/src/main/java/com/ai/app/controller/ChatController.java b/src/main/java/com/ai/app/controller/ChatController.java new file mode 100644 index 0000000..7840b0a --- /dev/null +++ b/src/main/java/com/ai/app/controller/ChatController.java @@ -0,0 +1,55 @@ +package com.ai.app.controller; + +import com.ai.app.dto.MessageDTO; +import com.ai.app.service.AIChatMessageService; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.chat.memory.ChatMemory; +import org.springframework.ai.chat.memory.InMemoryChatMemoryRepository; +import org.springframework.ai.chat.messages.Message; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Flux; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * @describe AI对话控制器 + * @Author kai.liu + * @Date 2025/8/28 10:06 + */ + +@RestController +@RequestMapping("/ai") +public class ChatController { + + @Autowired + private ChatClient chatClient; + + @Autowired + private InMemoryChatMemoryRepository inMemoryChatMemoryRepository; + + @RequestMapping(value = "/chat", produces = "text/stream;charset=UTF-8") + public Flux chat(String prompt, String chatId) { + return chatClient + .prompt() + .user(prompt) + .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, chatId)) + .stream().content(); + } + + @RequestMapping("/history/chat") + public List history() { + return inMemoryChatMemoryRepository.findConversationIds(); + } + + @RequestMapping("/history/chat/{chatId}") + public List history(@PathVariable("chatId") String chatId) { + List messages = inMemoryChatMemoryRepository.findByConversationId(chatId); + return messages.stream().map(e -> { + return new MessageDTO(e.getMessageType().getValue(), e.getText()); + }).collect(Collectors.toList()); + } +} diff --git a/src/main/java/com/ai/app/dto/MessageDTO.java b/src/main/java/com/ai/app/dto/MessageDTO.java new file mode 100644 index 0000000..bacbee1 --- /dev/null +++ b/src/main/java/com/ai/app/dto/MessageDTO.java @@ -0,0 +1,23 @@ +package com.ai.app.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.NonNull; + +import java.io.Serializable; + +/** + * @describe + * @Author kai.liu + * @Date 2025/8/29 15:09 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class MessageDTO implements Serializable { + + private String role; + + private String content; +} diff --git a/src/main/java/com/ai/app/entity/AIChatMessage.java b/src/main/java/com/ai/app/entity/AIChatMessage.java new file mode 100644 index 0000000..b0b01da --- /dev/null +++ b/src/main/java/com/ai/app/entity/AIChatMessage.java @@ -0,0 +1,30 @@ +package com.ai.app.entity; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * @describe + * @Author kai.liu + * @Date 2025/8/29 9:20 + */ +@Data +@TableName("ai_chat_message") +public class AIChatMessage { + + @TableId + private String id; + + private String conversationId; + + private String conversationType; + + private String messageType; + + private String message; + + private LocalDateTime createTime; +} diff --git a/src/main/java/com/ai/app/mapper/AIChatMessageMapper.java b/src/main/java/com/ai/app/mapper/AIChatMessageMapper.java new file mode 100644 index 0000000..0aa7aaf --- /dev/null +++ b/src/main/java/com/ai/app/mapper/AIChatMessageMapper.java @@ -0,0 +1,29 @@ +package com.ai.app.mapper; + +import com.ai.app.entity.AIChatMessage; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Insert; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + +/** + * @describe + * @Author kai.liu + * @Date 2025/8/29 9:18 + */ +@Mapper +public interface AIChatMessageMapper extends BaseMapper { + + @Select("SELECT DISTINCT conversation_id FROM ai_chat_message ") + List selectConversationIds(); + + @Select("SELECT * FROM ai_chat_message WHERE conversation_id = #{conversationId}") + List selectByConversationId(String conversationId); + + void batchInsert(List messageList); + + @Select("DELETE FROM ai_chat_message WHERE conversation_id = #{conversationId}") + void deleteByConversationId(String conversationId); +} diff --git a/src/main/java/com/ai/app/service/AIChatMessageService.java b/src/main/java/com/ai/app/service/AIChatMessageService.java new file mode 100644 index 0000000..704fac7 --- /dev/null +++ b/src/main/java/com/ai/app/service/AIChatMessageService.java @@ -0,0 +1,23 @@ +package com.ai.app.service; + +import org.springframework.ai.chat.messages.Message; + +import java.util.List; + +/** + * @describe + * @Author kai.liu + * @Date 2025/8/29 9:27 + */ +public interface AIChatMessageService { + + List getConversationIds(String type); + + List getConversationIds(); + + List getMessage(String conversationId); + + void saveAll(String conversationId, List messages); + + void delete(String conversationId); +} diff --git a/src/main/java/com/ai/app/service/RedisTemplateService.java b/src/main/java/com/ai/app/service/RedisTemplateService.java new file mode 100644 index 0000000..ace2c73 --- /dev/null +++ b/src/main/java/com/ai/app/service/RedisTemplateService.java @@ -0,0 +1,192 @@ +package com.ai.app.service; + +import java.util.List; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +/** + * @describe + * @Author kai.liu + * @Date 2025/9/1 9:28 + */ +public interface RedisTemplateService { + + /** + * 缓存对象信息 + * + * @param key 键 + * @param value 值 + * @author 施勇 + * 2018年12月11日 上午10:54:49 + */ + void set(String key, Object value); + + /** + * 缓存信息,并设置缓存时间 + * + * @param key 键 + * @param value 值 + * @param time 时间 + * @param timeUnit 时间单位 + * @author 施勇 + * 2018年12月11日 上午10:54:27 + */ + void set(String key, Object value, long time, TimeUnit timeUnit); + + /** + * 原子递增1 + * + * @param key 键 + * @return java.lang.Long + */ + Long increment(String key); + + /** + * 原子递增 + * + * @param key 键 + * @param delta 增量 + * @return java.lang.Long + */ + Long increment(String key, Long delta); + + /** + * 递增 + * + * @param key 键 + * @param delta 递增因子 + * @param time 时间 + * @param timeUnit 时间单位 + * @return java.lang.Long + * @author shiyong + * 2022/10/18 14:58 + */ + Long increment(String key, Long delta, long time, TimeUnit timeUnit); + + /** + * 从缓存中获取信息 + * + * @param key 键 + * @return Object + * @author 施勇 + * 2018年12月11日 上午10:54:10 + */ + Object get(Object key); + + /** + * 从redis中获取key对应的过期时间; + * 如果该值有过期时间,就返回相应的过期时间; + * 如果该值没有设置过期时间,就返回-1; + * 如果没有该值,就返回-2; + */ + Long getExpire(String key); + + /** + * 设置key的失效时间 + * + * @param key 键 + * @param time 时长 + * @param timeUnit 时间单位 + * @return java.lang.Boolean + */ + Boolean expire(String key, long time, TimeUnit timeUnit); + + /** + * 是否存在key + * + * @param key 键 + * @return boolean + * @author 施勇 + * 2018年12月11日 上午10:53:54 + */ + boolean hasKey(String key); + + /** + * 删除KEY对应的对象 + * + * @param key 键 + * @author 施勇 + * 2018年12月24日 下午4:14:26 + */ + void delete(String key); + + /** + * 查找匹配的key + * + * @param pattern 匹配字符串 + * @return java.util.Set + * @author shiyong + * 2020/1/3 13:42 + */ + Set getKeys(String pattern); + + /** + * 在队尾追加元素 + * + * @param key 键 + * @param value 值 + * @return 添加后队列的长度 + */ + Long rightPush(String key, Object value); + + /** + * 从队首移除元素 + * + * @param key 键 + * @return 移除的元素 + */ + Object leftPop(String key); + + /** + * 从队首开始取值,获取指定区间 + * + * @param key 键 + * @param start 开始下标 + * @param stop 结束下标 + * @return 区间内的全部值 + */ + List leftRange(String key, long start, long stop); + + /** + * 将值加到集合中 + * + * @param key 键 + * @param values 值 + * @return 添加是否成功(1:成功,0:失败) + */ + Long addToSet(String key, Object... values); + + /** + * 集合的数量 + * + * @param key 键 + * @return 集合数量 + */ + Long sizeOfSet(String key); + + /** + * 集合列表 + * + * @param key 键 + * @return 集合列表 + */ + Set getOfSet(String key); + + /** + * 集合中是否有该值 + * + * @param key 键 + * @param value 值 + * @return Boolean + */ + Boolean hasOfSet(String key, Object value); + + /** + * 删除集合中值 + * + * @param key 键 + * @param value 值 + * @return 删除是否成功(1:成功,0:失败) + */ + Long removeOfSet(String key, Object value); +} diff --git a/src/main/java/com/ai/app/service/impl/AIChatMessageServiceImpl.java b/src/main/java/com/ai/app/service/impl/AIChatMessageServiceImpl.java new file mode 100644 index 0000000..f0983d7 --- /dev/null +++ b/src/main/java/com/ai/app/service/impl/AIChatMessageServiceImpl.java @@ -0,0 +1,72 @@ +package com.ai.app.service.impl; + +import com.ai.app.service.AIChatMessageService; +import com.ai.app.service.RedisTemplateService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.ai.chat.messages.Message; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** + * @describe + * @Author kai.liu + * @Date 2025/8/29 9:27 + */ +@Slf4j +@Service +public class AIChatMessageServiceImpl implements AIChatMessageService { + + @Resource + private RedisTemplateService redisTemplateService; + + public static final String AI_MESSAGE_CONVERSATION_ID = "ai:message:conversationId:"; + public static final String AI_MESSAGE_CONVERSATION_TYPE = "ai:message:conversationType:"; + + @Override + public List getConversationIds(String type) { + String conversationTypeKey = getConversationTypeKey(type); + if (redisTemplateService.hasKey(conversationTypeKey)) { + return (List) redisTemplateService.get(conversationTypeKey); + } + return List.of(); + } + + @Override + public List getConversationIds() { + Set keys = redisTemplateService.getKeys(AI_MESSAGE_CONVERSATION_ID); + return new ArrayList<>(keys); + } + + @Override + public List getMessage(String conversationId) { + String conversationIdKey = getConversationIdKey(conversationId); + if (redisTemplateService.hasKey(conversationIdKey)) { + return (List) redisTemplateService.get(conversationIdKey); + } + return List.of(); + } + + @Override + public void saveAll(String conversationId, List messages) { + String conversationIdKey = getConversationIdKey(conversationId); + redisTemplateService.set(conversationIdKey, messages); + } + + @Override + public void delete(String conversationId) { + String conversationIdKey = getConversationIdKey(conversationId); + redisTemplateService.delete(conversationIdKey); + } + + private String getConversationIdKey(String conversationId) { + return AI_MESSAGE_CONVERSATION_ID + conversationId; + } + + private String getConversationTypeKey(String type) { + return AI_MESSAGE_CONVERSATION_TYPE + type; + } +} diff --git a/src/main/java/com/ai/app/service/impl/RedisTemplateServiceImpl.java b/src/main/java/com/ai/app/service/impl/RedisTemplateServiceImpl.java new file mode 100644 index 0000000..ac561b6 --- /dev/null +++ b/src/main/java/com/ai/app/service/impl/RedisTemplateServiceImpl.java @@ -0,0 +1,256 @@ +package com.ai.app.service.impl; + +import com.ai.app.service.RedisTemplateService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +/** + * @describe + * @Author kai.liu + * @Date 2025/9/1 9:28 + */ +@Service +@Slf4j +public class RedisTemplateServiceImpl implements RedisTemplateService { + + @Autowired + private RedisTemplate redisTemplate; + + /** + * 缓存对象信息 + * @param key 键 + * @param value 值 + * @author 施勇 + * 2018年12月11日 上午10:54:49 + */ + public void set(String key, Object value) { + redisTemplate.opsForValue().set(key, value); + } + + /** + * 缓存信息,并设置缓存时间 + * @param key 键 + * @param value 值 + * @param time 时间 + * @param timeUnit 时间单位 + * @author 施勇 + * 2018年12月11日 上午10:54:27 + */ + public void set(String key, Object value, long time, TimeUnit timeUnit){ + if(time>0){ + redisTemplate.opsForValue().set(key, value, time, timeUnit); + }else{ + set(key, value); + } + } + + /** + * 原子递增1 + * + * @param key 键 + * @return java.lang.Long + */ + public Long increment(String key) { + return redisTemplate.opsForValue().increment(key); + } + + /** + * 原子递增 + * + * @param key 键 + * @param delta 增量 + * @return java.lang.Long + */ + public Long increment(String key, Long delta) { + if (delta == null || delta < 1) { + delta = 1L; + } + return redisTemplate.opsForValue().increment(key, delta); + } + + /** + * 递增 + * + * @param key 键 + * @param delta 递增因子 + * @param time 时间 + * @param timeUnit 时间单位 + * @return java.lang.Long + * @author shiyong + * 2022/10/18 14:58 + */ + public Long increment(String key, Long delta, long time, TimeUnit timeUnit) { + if (null == delta || delta < 1) { + delta = 1L; + } + Long increment = redisTemplate.opsForValue().increment(key, delta); + redisTemplate.expire(key, time, timeUnit); + return increment; + } + + /** + * 从缓存中获取信息 + * @param key 键 + * @return Object + * @author 施勇 + * 2018年12月11日 上午10:54:10 + */ + public Object get(Object key) { + return key==null?"":redisTemplate.opsForValue().get(key); + } + + /** + * 从redis中获取key对应的过期时间; + * 如果该值有过期时间,就返回相应的过期时间; + * 如果该值没有设置过期时间,就返回-1; + * 如果没有该值,就返回-2; + */ + public Long getExpire(String key) { + return redisTemplate.opsForValue().getOperations().getExpire(key); + } + + /** + * 设置key的失效时间 + * + * @param key 键 + * @param time 时长 + * @param timeUnit 时间单位 + * @return java.lang.Boolean + */ + public Boolean expire(String key, long time, TimeUnit timeUnit) { + return redisTemplate.expire(key, time, timeUnit); + } + + /** + * 是否存在key + * @param key 键 + * @return boolean + * @author 施勇 + * 2018年12月11日 上午10:53:54 + */ + public boolean hasKey(String key) { + if (null == key || "".equals(key)) { + return false; + } + Boolean result = redisTemplate.hasKey(key); + if (null == result) { + return false; + } else { + return result; + } + } + + /** + * 删除KEY对应的对象 + * @param key 键 + * @author 施勇 + * 2018年12月24日 下午4:14:26 + */ + public void delete(String key){ + redisTemplate.delete(key); + } + + /** + * 查找匹配的key + * @param pattern 匹配字符串 + * @return java.util.Set + * @author shiyong + * 2020/1/3 13:42 + */ + public Set getKeys(String pattern) { + return redisTemplate.keys(pattern); + } + + /** + * 在队尾追加元素 + * + * @param key 键 + * @param value 值 + * @return 添加后队列的长度 + */ + public Long rightPush(String key, Object value) { + return redisTemplate.opsForList().rightPush(key, value); + } + + /** + * 从队首移除元素 + * + * @param key 键 + * @return 移除的元素 + */ + public Object leftPop(String key) { + return redisTemplate.opsForList().leftPop(key); + } + + /** + * 从队首开始取值,获取指定区间 + * + * @param key 键 + * @param start 开始下标 + * @param stop 结束下标 + * @return 区间内的全部值 + */ + public List leftRange(String key, long start, long stop) { + return redisTemplate.opsForList().range(key, start, stop); + } + + /** + * 将值加到集合中 + * + * @param key 键 + * @param values 值 + * @return 添加是否成功(1:成功,0:失败) + */ + public Long addToSet(String key, Object... values) { + return redisTemplate.opsForSet().add(key, values); + } + + /** + * 集合的数量 + * + * @param key 键 + * @return 集合数量 + */ + public Long sizeOfSet(String key) { + return redisTemplate.opsForSet().size(key); + } + + /** + * 集合列表 + * + * @param key 键 + * @return 集合列表 + */ + public Set getOfSet(String key) { + return redisTemplate.opsForSet().members(key); + } + + /** + * 集合中是否有该值 + * + * @param key 键 + * @param value 值 + * @return Boolean + */ + public Boolean hasOfSet(String key,Object value) { + return redisTemplate.opsForSet().isMember(key,value); + } + + /** + * 删除集合中值 + * + * @param key 键 + * @param value 值 + * @return 删除是否成功(1:成功,0:失败) + */ + public Long removeOfSet(String key,Object value) { + return redisTemplate.opsForSet().remove(key,value); + } + +} diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml new file mode 100644 index 0000000..e69de29 diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..f101b36 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,95 @@ +server: + port: 8080 + # servlet: + # context-path: /ai + tomcat: + # 等待队列长度 (所有线程忙碌时,排队等待的连接数, 默认: 100) + accept-count: 1000 + # 最大连接数 (默认: 8192) + max-connections: 20000 + # 连接超时 (接受连接后等待请求数据的最大时间, 默认值通常与系统有关) + connection-timeout: 30000 + threads: + # 最大工作线程数 (默认: 200) + max: 2000 + # 最小工作线程数(初始启动的线程数, 默认: 10) + min-spare: 200 + # 限制表单提交(application/x-www-form-urlencoded)的POST请求最大大小 (默认: 2MB) + # 这是 Boot 2 中 'server.tomcat.max-http-post-size' 的替代品 + max-http-form-post-size: 100MB + # 限制整个请求体(包括文件上传)的最大大小。超过此限制的请求将被拒绝 (默认: 2MB) + max-swallow-size: 100MB + # 定义响应的字符编码 (默认: UTF-8) + uri-encoding: UTF-8 + compression: + enabled: true + http2: + enabled: true +spring: + application: + name: spring-ai-app + # 指定运行环境 + profiles: + # 指定默认的运行环境为dev,运行时通过--spring.profiles.active参数切换运行环境 + active: dev + main: + allow-bean-definition-overriding: true + servlet: + multipart: + max-file-size: 100MB + max-request-size: 100MB + ai: + deepseek: + api-key: sk-f970111b34c84a8c812919fb4fad8b65 + chat: + options: + model: deepseek-reasoner + data: + redis: + host: 180.76.119.46 + port: 6379 + password: qwer@1234 + database: 0 + lettuce: + pool: + max-active: 8 + max-idle: 8 + min-idle: 0 + max-wait: 3000 +logging: + level: + org.springframework.ai.chat.client.advisor: debug + com.ai.app: debug + +spring.datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://180.76.119.46:3306/spring_ai_app?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&failOverReadOnly=false&useSSL=false&allowMultiQueries=true + username: lk + password: Qwer@1234 + type: com.zaxxer.hikari.HikariDataSource + # Hikari 连接池配置 + hikari: + # 最小空闲连接数量 + minimum-idle: 10 + # 空闲连接存活最大时间,默认600000(10分钟) + idle-timeout: 180000 + # 连接池最大连接数,默认是10 + maximum-pool-size: 100 + # 此属性控制从池返回的连接的默认自动提交行为,默认值:true + auto-commit: true + # 连接池名称 + pool-name: HikariCP + # 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认1800000即30分钟 + max-lifetime: 1800000 + # 数据库连接超时时间,默认30秒,即30000 + connection-timeout: 30000 + connection-test-query: SELECT 1 +mybatis-plus: + mapper-locations: classpath:mapper/**/*.xml + type-aliases-package: com.ai.app.entity + configuration: + # 日志打印 + log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl + # 开启驼峰命名转换,如:Table(create_time) -> Entity(createTime)。 + # 不需要我们关心怎么进行字段匹配,mybatis会自动识别`大写字母与下划线` + map-underscore-to-camel-case: true diff --git a/src/main/resources/mapper/AIChatMessageMapper.xml b/src/main/resources/mapper/AIChatMessageMapper.xml new file mode 100644 index 0000000..ceb303a --- /dev/null +++ b/src/main/resources/mapper/AIChatMessageMapper.xml @@ -0,0 +1,13 @@ + + + + + INSERT INTO ai_chat_message (id,conversation_id,conversation_type,message_type,message,create_time) + VALUES + + (#{message.id}, #{message.conversationId}, #{message.conversationType}, #{message.messageType},#{message.message}, #{message.createTime}) + + + \ No newline at end of file diff --git a/src/test/java/com/ai/app/SpringAiAppApplicationTests.java b/src/test/java/com/ai/app/SpringAiAppApplicationTests.java new file mode 100644 index 0000000..3735ccb --- /dev/null +++ b/src/test/java/com/ai/app/SpringAiAppApplicationTests.java @@ -0,0 +1,19 @@ +package com.ai.app; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import java.math.BigDecimal; +import java.text.NumberFormat; +import java.text.ParseException; +import java.util.regex.Pattern; + +@SpringBootTest +class SpringAiAppApplicationTests { + + @Test + void contextLoads(){ + System.out.println(new BigDecimal("-0.25")); + } + +}