基于Spring AI Alibaba的智能知识助手系统:从零到一的RAG实战开发
📖 项目概述
在人工智能快速发展的今天,RAG(Retrieval-Augmented Generation)技术已成为构建智能问答系统的核心技术。本文将详细介绍一个基于Spring AI Alibaba DashScope深度集成的智能知识助手系统的完整开发过程,该系统采用现代化的技术栈,实现了企业级的RAG解决方案。
项目地址:https://github.com/Matthew-Miao/mxy-rag-server
🎯 项目核心价值
技术创新点
- 深度集成Spring AI Alibaba:原生支持阿里云通义千问模型,提供统一的AI接口
- 双模式AI支持:同时支持Spring AI Alibaba DashScope和OpenAI兼容模式
- 企业级RAG架构:完整的检索增强生成系统,支持多种文档格式
- 现代化技术栈:Spring Boot 3.x + PostgreSQL + pgvector + MySQL
- 用户会话管理:基于ThreadLocal的用户上下文管理系统
业务价值
- 智能知识问答:基于用户上传的文档进行精准问答
- 多会话管理:支持多个对话会话,保持上下文连续性
- 实时流式对话:支持流式响应,提升用户体验
- 知识库管理:完整的文档上传、处理、检索功能
🏗️ 系统架构设计
整体架构
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 前端展示层 │ │ 业务逻辑层 │ │ 数据存储层 │
│ │ │ │ │ │
│ HTML5 + CSS3 │◄──►│ Spring Boot │◄──►│ PostgreSQL │
│ 原生JavaScript │ │ Spring AI │ │ + pgvector │
│ Font Awesome │ │ MyBatis │ │ │
│ WebSocket │ │ WebSocket │ │ MySQL │
│ │ │ │ │ │
└─────────────────┘ └─────────────────┘ └─────────────────┘│▼┌─────────────────┐│ AI服务层 ││ ││ 阿里云通义千问 ││ DashScope API ││ 文本嵌入模型 │└─────────────────┘
技术栈详解
后端技术栈
- Spring Boot 3.2.0:现代化的Java企业级框架
- Spring AI Alibaba:阿里云AI服务的Spring集成
- MyBatis:灵活的持久层框架
- PostgreSQL + pgvector:向量数据库,支持相似性搜索
- MySQL:业务数据存储
- ThreadLocal:用户会话管理
- WebSocket:实时通信支持
前端技术栈
- HTML5:现代化的标记语言,支持语义化标签
- CSS3:样式设计,支持Flexbox、Grid、动画等现代特性
- 原生JavaScript (ES6+):无框架依赖的纯JavaScript实现
- Font Awesome 6.0:图标库,提供丰富的矢量图标
- Marked.js:Markdown解析库,支持消息格式化
- Highlight.js:代码高亮库,支持多种编程语言
- Fetch API:现代化的HTTP请求接口
- WebSocket:实时通信支持
🔧 核心功能实现
1. Spring AI Alibaba集成
Maven依赖配置
<dependency><groupId>com.alibaba.cloud.ai</groupId><artifactId>spring-ai-alibaba-starter-dashscope</artifactId><version>1.0.0.2</version>
</dependency>
配置文件设置
spring:ai:vectorstore:pgvector:table-name: mxy_rag_vectorinitialize-schema: truedimensions: 1024index-type: hnswdashscope:api-key: ${AI_DASHSCOPE_API_KEY}chat:options:model: qwen-plus-latestembedding:options:model: text-embedding-v3dimensions: 1024# openai:
# api-key: ${AI_DASHSCOPE_API_KEY}
# base-url: https://dashscope.aliyuncs.com/compatible-mode
# chat:
# options:
# model: qwen-plus-latest
# embedding:
# options:
# model: text-embedding-v3
# dimensions: 1024
核心服务实现
KnowledgeBaseServiceImpl.java - 知识库服务核心实现:
/*** 知识库服务实现类* @author Mxy*/
@Service
public class KnowledgeBaseServiceImpl implements KnowledgeBaseService {private static final Logger logger = LoggerFactory.getLogger(KnowledgeBaseServiceImpl.class);/*** 系统提示词:指导AI智能地处理不同类型的问题*/private static final String SYSTEM_PROMPT = "你是一个智能助手。请始终使用中文回答用户的问题。当回答用户问题时,请遵循以下策略:\n" +"1. 对于基础知识问题(如数学计算、常识问题等),直接使用你的通用知识准确回答\n" +"2. 对于专业或特定领域的问题,优先从向量数据库中检索相关知识来回答\n" +"3. 如果向量数据库中没有找到相关信息,请从聊天记忆中寻找之前讨论过的相关内容\n" +"4. 如果以上都没有相关信息,请基于你的通用知识给出准确、有帮助的回答\n" +"5. 只有在确实无法回答时,才诚实地告知用户并建议他们提供更多信息\n" +"请确保回答准确、相关且有帮助。不要因为向量数据库中没有信息就拒绝回答基础问题。所有回答都必须使用中文。";private final VectorStore vectorStore;private final ChatClient chatClient;/*** 构造函数:初始化知识库服务* * @param vectorStore 向量存储* @param chatModel 聊天模型* @param messageWindowChatMemory 消息窗口聊天记忆*/public KnowledgeBaseServiceImpl(VectorStore vectorStore, @Qualifier("dashscopeChatModel") ChatModel chatModel,MessageWindowChatMemory messageWindowChatMemory) {this.vectorStore = vectorStore;this.chatClient = ChatClient.builder(chatModel).defaultAdvisors(SimpleLoggerAdvisor.builder().build(),MessageChatMemoryAdvisor.builder(messageWindowChatMemory).build()).defaultOptions(DashScopeChatOptions.builder().withTopP(0.7).build()).build();}/*** 与知识库进行对话* * @param query 用户查询* @param conversationId 对话ID* @param topK 检索文档数量* @return 回答内容*/@Overridepublic String chatWithKnowledge(String query, String conversationId, int topK) {Assert.hasText(query, "查询问题不能为空");logger.info("开始知识库对话,查询: '{}', conversationId: {}", query, conversationId);try {String prompt = getRagStr(query, topK);// 调用LLM生成回答String answer = chatClient.prompt(prompt).system(SYSTEM_PROMPT).user(query).advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId)).call().content();logger.info("知识库对话完成,查询: '{}'", query);return answer;} catch (Exception e) {logger.error("知识库对话失败,查询: '{}'", query, e);return "对话过程中发生错误: " + e.getMessage();}}/*** 流式知识库对话* * @param query 用户查询* @param conversationId 对话ID* @param topK 检索文档数量* @return 流式回答内容*/@Overridepublic Flux<String> chatWithKnowledgeStream(String query, String conversationId, int topK) {Assert.hasText(query, "查询问题不能为空");logger.info("开始流式知识库对话,查询: '{}', conversationId: {}", query, conversationId);try {String prompt = getRagStr(query, topK);return chatClient.prompt(prompt).system(SYSTEM_PROMPT).advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId)).user(query).stream().content();} catch (Exception e) {logger.error("流式知识库对话失败,查询: '{}'", query, e);return Flux.just("对话过程中发生错误: " + e.getMessage());}}/*** 相似性搜索* * @param query 查询字符串* @param topK 返回的相似文档数量* @return 相似文档列表*/@Overridepublic List<Document> similaritySearch(String query, int topK) {Assert.hasText(query, "查询不能为空");logger.info("执行相似性搜索: query={}, topK={}", query, topK);SearchRequest searchRequest = SearchRequest.builder().query(query).topK(topK).build();List<Document> results = vectorStore.similaritySearch(searchRequest);logger.info("相似性搜索完成,找到 {} 个相关文档", results.size());return results;}/*** 获取RAG提示词** @param query 用户查询* @param topK 检索文档数量* @return 提示词*/private String getRagStr(String query, int topK) {List<Document> documents = similaritySearch(query, topK);String prompt = "";if (documents != null && !documents.isEmpty()){// 构建提示词String context = documents.stream().map(Document::getText).collect(Collectors.joining("\n\n"));prompt = String.format("知识库内容:\n%s\n\n", context);}return prompt;}
}
2. 用户会话管理系统
用户会话信息类
@Data
public class UserSession {/*** 用户ID*/private String userId;/*** 用户名*/private String username;public UserSession() {}public UserSession(String userId, String username) {this();this.userId = userId;this.username = username;}
}
用户上下文设计(支持TTL)
/*** 用户会话持有者* 使用阿里TTL(TransmittableThreadLocal)存储当前线程的用户会话信息* 支持线程池环境下的上下文传递,确保线程安全* * @author Mxy*/
public class UserSessionHolder {private static final Logger logger = LoggerFactory.getLogger(UserSessionHolder.class);/*** TransmittableThreadLocal存储用户会话信息* 相比ThreadLocal,TTL支持线程池环境下的上下文传递*/private static final TransmittableThreadLocal<UserSession> USER_SESSION_THREAD_LOCAL = new TransmittableThreadLocal<>();/*** 设置当前线程的用户会话* * @param userSession 用户会话信息*/public static void setUserSession(UserSession userSession) {if (userSession != null) {logger.debug("设置用户会话: {}", userSession);USER_SESSION_THREAD_LOCAL.set(userSession);} else {logger.warn("尝试设置空的用户会话");}}/*** 获取当前线程的用户会话* * @return 用户会话信息,如果未设置则返回null*/public static UserSession getUserSession() {return USER_SESSION_THREAD_LOCAL.get();}/*** 获取当前用户ID* * @return 用户ID,如果未设置会话则返回null*/public static String getCurrentUserId() {UserSession session = getUserSession();return session != null ? session.getUserId() : null;}/*** 获取当前用户名* * @return 用户名,如果未设置会话则返回null*/public static String getCurrentUsername() {UserSession session = getUserSession();return session != null ? session.getUsername() : null;}/*** 清除当前线程的用户会话* 重要:在请求处理完成后必须调用此方法,避免内存泄漏*/public static void clearUserSession() {UserSession session = getUserSession();if (session != null) {logger.debug("清除用户会话: userId={}", session.getUserId());}USER_SESSION_THREAD_LOCAL.remove();}
}
用户认证拦截器
/*** 用户认证拦截器* 拦截所有请求,验证请求头中的userId,并将用户信息存储到UserSession中* * @author Mxy*/
@Component
public class UserAuthInterceptor implements HandlerInterceptor {private static final Logger logger = LoggerFactory.getLogger(UserAuthInterceptor.class);/*** 用户ID请求头名称*/private static final String USER_ID_HEADER = "X-User-Id";/*** 备用用户ID请求头名称*/private static final String USER_ID_HEADER_ALT = "userId";@Resourceprivate UsersDAO usersDAO;/*** 请求处理前的拦截方法* 验证用户身份并设置用户会话*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String requestURI = request.getRequestURI();String method = request.getMethod();logger.debug("拦截请求: {} {}", method, requestURI);// 跳过健康检查和静态资源等不需要认证的请求if (shouldSkipAuthentication(requestURI)) {logger.debug("跳过认证检查: {}", requestURI);return true;}// 从请求头获取用户IDString userId = getUserIdFromHeader(request);if (!StringUtils.hasText(userId)) {logger.warn("请求缺少用户ID: {} {}", method, requestURI);sendUnauthorizedResponse(response, "缺少用户ID");return false;}// 验证用户是否存在try {UsersDO user = usersDAO.getByUserId(userId);if (user == null || user.getDeleted() == 1) {logger.warn("用户不存在或已删除: userId={}", userId);sendUnauthorizedResponse(response, "用户不存在或已删除");return false;}// 创建用户会话并设置到ThreadLocalUserSession userSession = createUserSession(user);UserSessionHolder.setUserSession(userSession);logger.debug("用户认证成功: userId={}, username={}", userId, user.getUsername());return true;} catch (Exception e) {logger.error("用户认证过程中发生异常: userId={}, error={}", userId, e.getMessage(), e);sendUnauthorizedResponse(response, "认证失败");return false;}}/*** 请求完成后的清理方法* 清除ThreadLocal中的用户会话,防止内存泄漏*/@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {try {// 清除用户会话UserSessionHolder.clearUserSession();} catch (Exception e) {logger.error("清除用户会话时发生异常", e);}}/*** 从请求头获取用户ID*/private String getUserIdFromHeader(HttpServletRequest request) {// 优先从X-User-Id头获取String userId = request.getHeader(USER_ID_HEADER);if (StringUtils.hasText(userId)) {return userId.trim();}// 备用方案:从userId头获取userId = request.getHeader(USER_ID_HEADER_ALT);if (StringUtils.hasText(userId)) {return userId.trim();}return null;}/*** 创建用户会话对象*/private UserSession createUserSession(UsersDO user) {return new UserSession(user.getUserId(), user.getUsername());}/*** 判断是否应该跳过认证检查*/private boolean shouldSkipAuthentication(String requestURI) {// 健康检查接口if (requestURI.contains("/actuator/")) {return true;}// 静态资源if (requestURI.contains("/static/") || requestURI.contains("/public/")) {return true;}// Swagger文档if (requestURI.contains("/swagger") || requestURI.contains("/v3/api-docs")) {return true;}// 错误页面if (requestURI.contains("/error")) {return true;}return false;}/*** 发送未授权响应*/private void sendUnauthorizedResponse(HttpServletResponse response, String message) throws IOException {response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);response.setContentType("application/json;charset=UTF-8");String jsonResponse = String.format("{\"code\":%d,\"message\":\"%s\",\"data\":null,\"timestamp\":%d}",HttpServletResponse.SC_UNAUTHORIZED,message,System.currentTimeMillis());response.getWriter().write(jsonResponse);response.getWriter().flush();}
}
Web配置类
/*** Web配置类* 配置Spring MVC相关设置,包括拦截器注册和页面重定向* * @author Mxy*/
@Configuration
public class WebConfig implements WebMvcConfigurer {@Resourceprivate UserAuthInterceptor userAuthInterceptor;@Resource(name = "ttlTaskExecutor")private ThreadPoolTaskExecutor ttlTaskExecutor;/*** 添加拦截器配置* 注册用户认证拦截器,拦截所有API请求进行用户身份验证*/@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(userAuthInterceptor)// 拦截所有API请求.addPathPatterns("/api/**")// 排除不需要认证的路径.excludePathPatterns(// 健康检查"/actuator/**",// Swagger文档"/swagger-ui/**","/swagger-ui.html","/v3/api-docs/**","/swagger-resources/**","/webjars/**",// 静态资源"/static/**","/public/**","/favicon.ico",// 错误页面"/error/**",// 用户认证相关接口(无需登录)"/api/v1/user/register","/api/v1/user/login","/api/v1/user/check-username")// 设置拦截器顺序(数字越小优先级越高).order(1);}/*** 配置CORS跨域支持* 允许前端页面跨域访问API接口*/@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**")// 允许所有来源(开发环境).allowedOriginPatterns("*")// 允许的HTTP方法.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")// 允许的请求头.allowedHeaders("*")// 允许发送Cookie.allowCredentials(true)// 预检请求缓存时间(1小时).maxAge(3600);}/*** 配置异步支持* 使用自定义的TTL任务执行器替代默认的SimpleAsyncTaskExecutor* 解决生产环境下的异步处理性能问题,并保持用户上下文传递*/@Overridepublic void configureAsyncSupport(AsyncSupportConfigurer configurer) {// 设置异步请求的任务执行器configurer.setTaskExecutor(ttlTaskExecutor);// 设置异步请求超时时间(30秒)configurer.setDefaultTimeout(30000);}
}
TTL线程池配置
/*** TTL(TransmittableThreadLocal)配置类* 配置支持TTL的线程池,确保异步任务中能够正确传递用户上下文* * @author Mxy*/
@Configuration
public class TtlConfig {private static final Logger logger = LoggerFactory.getLogger(TtlConfig.class);/*** 配置支持TTL的异步任务执行器* 使用TTL装饰器包装线程池,确保异步任务中能够获取到主线程的用户上下文*/@Bean("ttlTaskExecutor")public ThreadPoolTaskExecutor ttlTaskExecutor() {logger.info("初始化支持TTL的线程池执行器");ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();// 核心线程数executor.setCorePoolSize(5);// 最大线程数executor.setMaxPoolSize(20);// 队列容量executor.setQueueCapacity(100);// 线程名前缀executor.setThreadNamePrefix("ttl-async-");// 拒绝策略:由调用线程处理executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());// 等待所有任务结束后再关闭线程池executor.setWaitForTasksToCompleteOnShutdown(true);// 等待时间executor.setAwaitTerminationSeconds(60);executor.initialize();// 使用TTL装饰器包装线程池,支持上下文传递return executor;}/*** 配置支持TTL的调度任务执行器* 用于定时任务等场景*/@Bean("ttlScheduledExecutor")public Executor ttlScheduledExecutor() {logger.info("初始化支持TTL的调度线程池执行器");ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(2);executor.setMaxPoolSize(5);executor.setQueueCapacity(50);executor.setThreadNamePrefix("ttl-scheduled-");executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());executor.setWaitForTasksToCompleteOnShutdown(true);executor.setAwaitTerminationSeconds(30);executor.initialize();return TtlExecutors.getTtlExecutor(executor.getThreadPoolExecutor());}
}
用户上下文工具类
/*** 用户上下文工具类* 提供便捷的方法获取当前用户信息* * @author Mxy*/
public class UserContextUtil {/*** 获取当前用户ID* * @return 当前用户ID*/public static String getCurrentUserId() {return UserSessionHolder.getCurrentUserId();}/*** 获取当前用户名* * @return 当前用户名*/public static String getCurrentUsername() {return UserSessionHolder.getCurrentUsername();}/*** 获取当前用户会话* * @return 当前用户会话*/public static UserSession getCurrentUserSession() {return UserSessionHolder.getUserSession();}
}
3. 控制器层实现
智能对话控制器
/*** 智能对话控制器* 提供智能问答、流式对话、对话历史查询等REST API接口** @author Mxy*/
@Tag(name = "智能对话管理", description = "提供智能问答、流式对话、对话历史查询等功能")
@RestController
@RequestMapping("/api/v1/chat")
public class ChatController {private static final Logger logger = LoggerFactory.getLogger(ChatController.class);@Resourceprivate ChatService chatService;/*** 智能问答(阻塞式)* 基于知识库进行问答,返回完整的回答结果** @param request 问答请求参数* @return 问答结果*/@Operation(summary = "智能问答", description = "基于知识库进行智能问答,返回完整的回答结果")@PostMapping("/ask")public ApiResult<String> askQuestion(@Parameter(description = "问答请求参数", required = true)@Valid @RequestBody ChatAskRequest request) {try {String currentUsername = UserContextUtil.getCurrentUsername();logger.info("接收智能问答请求: sessionId={}, currentUsername={}, question={}",request.getSessionId(), currentUsername, request.getQuestion());// 转换为DTOChatAskDTO dto = new ChatAskDTO();BeanUtils.copyProperties(request, dto);String result = chatService.askQuestion(dto);return ApiResult.success("问答成功", result);} catch (Exception e) {logger.error("智能问答失败: {}", e.getMessage(), e);return ApiResult.error("智能问答失败: " + e.getMessage());}}/*** 流式智能问答* 基于知识库进行问答,以流的形式返回回答内容** @param request 流式问答请求参数* @return 流式回答内容*/@Operation(summary = "流式智能问答", description = "基于知识库进行智能问答,以流的形式返回回答内容")@PostMapping(value = "/stream", produces = MediaType.TEXT_PLAIN_VALUE)public Flux<String> askQuestionStream(@Parameter(description = "流式问答请求参数", required = true)@Valid @RequestBody ChatAskRequest request) {try {String currentUsername = UserContextUtil.getCurrentUsername();logger.info("接收流式智能问答请求: sessionId={}, currentUsername={}, question={}",request.getSessionId(), currentUsername, request.getQuestion());// 转换为DTOChatAskDTO dto = new ChatAskDTO();BeanUtils.copyProperties(request, dto);return chatService.askQuestionStream(dto);} catch (Exception e) {logger.error("流式智能问答失败: {}", e.getMessage(), e);return Flux.error(new RuntimeException("流式智能问答失败: " + e.getMessage()));}}/*** 获取对话历史* 分页查询指定会话的对话历史记录** @return 分页的对话历史*/@Operation(summary = "获取对话历史", description = "分页查询指定会话的对话历史记录")@PostMapping("/getChatHistory")public ApiResult<PageResult<ChatMessageVO>> getChatHistory(@RequestBody ChatMessagePageRequest chatMessagePageRequest) {try {logger.info("获取对话历史: sessionId={}, pageNum={}, pageSize={}",chatMessagePageRequest.getSessionId(),chatMessagePageRequest.getPageNum(), chatMessagePageRequest.getPageSize());ChatMessagePageRequestDTO chatMessagePageRequestDTO = new ChatMessagePageRequestDTO();BeanUtils.copyProperties(chatMessagePageRequest, chatMessagePageRequestDTO);PageResult<ChatMessageVO> result = chatService.getChatHistory(chatMessagePageRequestDTO);return ApiResult.success(result);} catch (Exception e) {logger.error("获取对话历史失败: {}", e.getMessage(), e);return ApiResult.error("获取对话历史失败: " + e.getMessage());}}/*** 用户反馈* 用户对AI回答进行评分和反馈** @param request 反馈请求参数* @return 反馈处理结果*/@Operation(summary = "用户反馈", description = "用户对AI回答进行评分和反馈")@PostMapping("/feedback")public ApiResult<Void> submitFeedback(@Parameter(description = "反馈请求参数", required = true)@Valid @RequestBody ChatFeedbackRequest request) {try {logger.info("接收用户反馈: messageId={}, rating={}",request.getMessageId(), request.getRating());// 转换为DTOChatFeedbackDTO dto = new ChatFeedbackDTO();BeanUtils.copyProperties(request, dto);chatService.submitFeedback(dto);return ApiResult.success();} catch (Exception e) {logger.error("用户反馈失败: {}", e.getMessage(), e);return ApiResult.error("用户反馈失败: " + e.getMessage());}}/*** 自动生成会话标题* 根据会话的聊天记录自动生成合适的标题** @param sessionId 会话ID* @return 生成的标题*/@Operation(summary = "自动生成会话标题", description = "根据会话的聊天记录自动生成合适的标题")@PostMapping("/generateTitle/{sessionId}")public ApiResult<String> generateSessionTitle(@Parameter(description = "会话ID", required = true)@PathVariable Long sessionId) {try {logger.info("生成会话标题: sessionId={}", sessionId);String title = chatService.generateSessionTitle(sessionId);return ApiResult.success("标题生成成功", title);} catch (Exception e) {logger.error("生成会话标题失败: {}", e.getMessage(), e);return ApiResult.error("生成会话标题失败: " + e.getMessage());}}
}
聊天会话管理控制器
/*** 聊天会话管理控制器* 提供会话的创建、查询、更新、删除等REST API接口** @author Mxy*/
@Tag(name = "聊天会话管理", description = "提供聊天会话的创建、查询、更新、删除等功能")
@RestController
@RequestMapping("/api/v1/chat/sessions")
public class ChatSessionController {private static final Logger logger = LoggerFactory.getLogger(ChatSessionController.class);@Resourceprivate ChatSessionService chatSessionService;/*** 创建新的聊天会话** @param request 创建会话请求参数* @return 创建的会话信息*/@Operation(summary = "创建聊天会话", description = "创建一个新的聊天会话")@PostMapping("/create")public ApiResult<Long> createSession(@Parameter(description = "创建会话请求参数", required = true)@Valid @RequestBody CreateSessionRequest request) {try {// 从用户上下文获取当前用户IDString currentUserId = UserContextUtil.getCurrentUserId();logger.info("接收创建会话请求: userId={}, title={}", currentUserId, request.getTitle());// 转换为DTOCreateSessionDTO dto = new CreateSessionDTO();BeanUtils.copyProperties(request, dto);Long sessionId = chatSessionService.createSession(dto);return ApiResult.success(sessionId);} catch (Exception e) {logger.error("创建会话失败: {}", e.getMessage(), e);return ApiResult.error("创建会话失败: " + e.getMessage());}}/*** 根据会话ID获取会话详情** @param sessionId 会话ID* @return 会话详情*/@Operation(summary = "获取会话详情", description = "根据会话ID获取会话的详细信息")@GetMapping("/detail/{sessionId}")public ApiResult<SessionVO> getSessionById(@Parameter(description = "会话ID", required = true) @PathVariable Long sessionId) {try {// 从用户上下文获取当前用户IDString currentUserId = UserContextUtil.getCurrentUserId();logger.info("获取会话详情: sessionId={}, userId={}", sessionId, currentUserId);SessionVO sessionVO = chatSessionService.getSessionById(sessionId);return ApiResult.success(sessionVO);} catch (Exception e) {logger.error("获取会话详情失败: {}", e.getMessage(), e);return ApiResult.error("获取会话详情失败: " + e.getMessage());}}/*** 分页查询用户的会话列表** @param request 查询请求参数* @return 分页查询结果*/@Operation(summary = "查询会话列表", description = "分页查询用户的会话列表,支持关键词搜索和状态过滤")@PostMapping("/list")public ApiResult<PageResult<SessionVO>> getSessionList(@Parameter(description = "查询请求参数", required = true)@Valid @RequestBody SessionQueryRequest request) {try {// 从用户上下文获取当前用户IDString currentUserId = UserContextUtil.getCurrentUserId();logger.info("查询会话列表:pageNum={}, pageSize={}", request.getPageNum(), request.getPageSize());// 转换为DTOSessionQueryDTO dto = new SessionQueryDTO();BeanUtils.copyProperties(request, dto);PageResult<SessionVO> result = chatSessionService.getSessionList(dto);return ApiResult.success(result);} catch (Exception e) {logger.error("查询会话列表失败: {}", e.getMessage(), e);return ApiResult.error("查询会话列表失败: " + e.getMessage());}}/*** 更新会话标题** @param request 更新标题请求参数(包含sessionId、userId和title)* @return 更新后的会话信息*/@Operation(summary = "更新会话标题", description = "更新指定会话的标题")@PostMapping("/update-title")public ApiResult<SessionVO> updateSessionTitle(@Parameter(description = "更新标题请求参数", required = true)@Valid @RequestBody UpdateSessionTitleRequest request) {try {// 从用户上下文获取当前用户IDString currentUserId = UserContextUtil.getCurrentUserId();logger.info("更新会话标题: sessionId={}, userId={}, title={}",request.getSessionId(), currentUserId, request.getTitle());// 转换为DTOUpdateSessionTitleDTO dto = new UpdateSessionTitleDTO();BeanUtils.copyProperties(request, dto);chatSessionService.updateSessionTitle(dto);return ApiResult.success();} catch (Exception e) {logger.error("更新会话标题失败: {}", e.getMessage(), e);return ApiResult.error("更新会话标题失败: " + e.getMessage());}}/*** 删除会话** @param request 删除会话请求参数* @return 删除结果*/@Operation(summary = "删除会话", description = "删除指定的会话")@PostMapping("/delete")public ApiResult<Void> deleteSession(@Parameter(description = "删除会话请求参数", required = true)@Valid @RequestBody DeleteSessionRequest request) {try {// 从用户上下文获取当前用户IDString currentUserId = UserContextUtil.getCurrentUserId();logger.info("删除会话: sessionId={}, userId={}", request.getSessionId(), currentUserId);// 转换为DTODeleteSessionDTO dto = new DeleteSessionDTO();BeanUtils.copyProperties(request, dto);dto.setUserId(currentUserId);chatSessionService.deleteSession(dto);return ApiResult.success();} catch (Exception e) {logger.error("删除会话失败: {}", e.getMessage(), e);return ApiResult.error("删除会话失败: " + e.getMessage());}}
}
4. 向量数据库集成
多数据源配置
/*** 多数据源配置类* 配置MySQL业务数据库和PostgreSQL向量数据库* * @author Mxy*/
@Configuration
@Slf4j
public class MultiDataSourceConfig {/*** 配置向量数据库数据源属性* 使用@ConfigurationProperties注解绑定配置文件中的属性*/@Bean(name = "pgVectorDataSourceProperties")@ConfigurationProperties("spring.datasource.pgvector")public DataSourceProperties pgVectorDataSourceProperties() {return new DataSourceProperties();}/*** 初始化向量数据库数据源* 使用HikariDataSource确保属性正确绑定*/@Bean("pgVectorDataSource")public DataSource vectorDataSource(@Qualifier("pgVectorDataSourceProperties") DataSourceProperties dataSourceProperties) {log.info("初始化向量数据库");return dataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();}/*** 配置向量数据库JdbcTemplate* 用于执行向量相关的SQL操作*/@Bean@Primarypublic JdbcTemplate pgVectorJdbcTemplate(@Qualifier("pgVectorDataSource") DataSource dataSource) {log.info("初始化JdbcTemplate");return new JdbcTemplate(dataSource);}/*** 配置MySQL业务数据库数据源属性*/@Primary@Bean(name = "mysqlDataSourceProperties")@ConfigurationProperties("spring.datasource.mysql")public DataSourceProperties mysqlDataSourceProperties() {return new DataSourceProperties();}/*** 初始化MySQL业务数据源* 使用HikariDataSource确保属性正确绑定*/@Bean("mysqlDataSource")@Primarypublic DataSource masterDataSource(@Qualifier("mysqlDataSourceProperties")DataSourceProperties dataSourceProperties) {log.info("初始化主数据源");return dataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();}/*** 配置MyBatis SqlSessionFactory* 用于MySQL数据库的ORM操作*/@Bean("sqlSessionFactory")@Primarypublic SqlSessionFactory sqlSessionFactory(@Qualifier("mysqlDataSource") DataSource mysqlDataSource) throws Exception {MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean();sessionFactory.setDataSource(mysqlDataSource);// 设置mapper文件位置sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/**/*Mapper*.xml"));// 设置MyBatis Plus插件sessionFactory.setPlugins(mybatisPlusInterceptor());log.info("初始化SqlSessionFactory");return sessionFactory.getObject();}/*** 配置事务管理器* 管理MySQL数据库的事务*/@Bean("transactionManager")@Primarypublic PlatformTransactionManager transactionManager(@Qualifier("mysqlDataSource") DataSource mysqlDataSource) {log.info("初始化事务管理器");return new DataSourceTransactionManager(mysqlDataSource);}/*** 配置MyBatis Plus插件* 包括分页插件等功能增强*/@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();// 分页插件PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();paginationInnerInterceptor.setMaxLimit(1000L); // 设置最大分页限制interceptor.addInnerInterceptor(paginationInnerInterceptor);log.info("初始化MyBatis Plus拦截器");return interceptor;}
}
自定义聊天记忆仓库:
@Component
public class CustomChatMemoryRepository implements ChatMemoryRepository {private final ChatSessionsMapper chatSessionsMapper;private final ChatMessagesMapper chatMessagesMapper;public CustomChatMemoryRepository(ChatSessionsMapper chatSessionsMapper, ChatMessagesMapper chatMessagesMapper) {this.chatSessionsMapper = chatSessionsMapper;this.chatMessagesMapper = chatMessagesMapper;}@Overridepublic List<Message> getMessages(String conversationId) {List<ChatMessagesDO> messageDOs = chatMessagesMapper.findBySessionId(conversationId);return messageDOs.stream().map(this::convertToMessage).collect(Collectors.toList());}@Overridepublic void saveMessages(String conversationId, List<Message> messages) {for (Message message : messages) {ChatMessagesDO messageDO = convertToChatMessagesDO(conversationId, message);chatMessagesMapper.insert(messageDO);}}@Overridepublic void deleteMessages(String conversationId) {chatMessagesMapper.deleteBySessionId(conversationId);}
}
文档处理服务
@Override
public void loadFile(MultipartFile file) {try {String originalFilename = file.getOriginalFilename();if (originalFilename == null) {throw new IllegalArgumentException("文件名不能为空");}logger.info("开始处理文件: {}", originalFilename);List<Document> documents;String fileExtension = getFileExtension(originalFilename).toLowerCase();if ("pdf".equals(fileExtension)) {// PDF文件处理PdfDocumentReader pdfReader = new PdfDocumentReader(file.getResource());documents = pdfReader.get();} else {// 其他文件类型使用Tika处理TikaDocumentReader tikaReader = new TikaDocumentReader(file.getResource());documents = tikaReader.get();}if (documents.isEmpty()) {throw new RuntimeException("未能从文件中提取到任何内容");}// 文本分块处理TextSplitter textSplitter = new TokenTextSplitter(500, 100, 5, 10000, true);List<Document> splitDocuments = textSplitter.apply(documents);// 添加元数据for (Document doc : splitDocuments) {doc.getMetadata().put("source", originalFilename);doc.getMetadata().put("upload_time", System.currentTimeMillis());}// 存储到向量数据库vectorStore.add(splitDocuments);logger.info("文件 {} 处理完成,共生成 {} 个文档块", originalFilename, splitDocuments.size());} catch (Exception e) {logger.error("文件处理失败: {}", file.getOriginalFilename(), e);throw new RuntimeException("文件处理失败: " + e.getMessage(), e);}
}private String getFileExtension(String filename) {int lastDotIndex = filename.lastIndexOf('.');return lastDotIndex > 0 ? filename.substring(lastDotIndex + 1) : "";
}
🎨 前端实现详解
1. 项目结构
前端采用原生HTML+CSS+JavaScript实现,无框架依赖,结构清晰简洁:
src/main/resources/static/
├── css/
│ ├── common.css # 公共样式
│ ├── login.css # 登录页样式
│ ├── chat.css # 聊天页样式
│ └── knowledge.css # 知识库管理样式
├── js/
│ └── api.js # API客户端封装
├── login.html # 登录注册页面
├── chat.html # 智能聊天页面
└── knowledge.html # 知识库管理页面
2. API客户端封装
api.js - 统一的HTTP请求处理:
class ApiClient {constructor() {this.baseURL = '';this.token = localStorage.getItem('token');this.userId = localStorage.getItem('userId');}// 通用请求方法async request(url, options = {}) {const config = {headers: {'Content-Type': 'application/json',...options.headers},...options};// 添加认证头if (this.token) {config.headers['Authorization'] = `Bearer ${this.token}`;}if (this.userId) {config.headers['X-User-Id'] = this.userId;}try {const response = await fetch(this.baseURL + url, config);if (!response.ok) {throw new Error(`HTTP ${response.status}: ${response.statusText}`);}const contentType = response.headers.get('content-type');if (contentType && contentType.includes('application/json')) {return await response.json();}return await response.text();} catch (error) {console.error('API请求失败:', error);throw error;}}// GET请求async get(url, params = {}) {const queryString = new URLSearchParams(params).toString();const fullUrl = queryString ? `${url}?${queryString}` : url;return this.request(fullUrl, { method: 'GET' });}// POST请求async post(url, data = {}) {return this.request(url, {method: 'POST',body: JSON.stringify(data)});}// 文件上传async uploadFile(url, file, onProgress) {return new Promise((resolve, reject) => {const xhr = new XMLHttpRequest();const formData = new FormData();formData.append('file', file);// 设置认证头if (this.token) {xhr.setRequestHeader('Authorization', `Bearer ${this.token}`);}if (this.userId) {xhr.setRequestHeader('X-User-Id', this.userId);}// 上传进度回调if (onProgress) {xhr.upload.onprogress = (e) => {if (e.lengthComputable) {const percentComplete = (e.loaded / e.total) * 100;onProgress(percentComplete);}};}xhr.onload = () => {if (xhr.status === 200) {resolve(xhr.responseText);} else {reject(new Error(`上传失败: ${xhr.statusText}`));}};xhr.onerror = () => reject(new Error('网络错误'));xhr.open('POST', this.baseURL + url);xhr.send(formData);});}
}// 全局API客户端实例
const api = new ApiClient();
3. 智能聊天界面实现
chat.html - 核心聊天功能:
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>智能知识助手</title><link rel="stylesheet" href="css/common.css"><link rel="stylesheet" href="css/chat.css"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"><script src="https://cdnjs.cloudflare.com/ajax/libs/marked/4.3.0/marked.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min.js"></script>
</head>
<body><div class="chat-container"><!-- 聊天消息区域 --><div class="chat-messages" id="chatMessages"><div class="welcome-message"><i class="fas fa-robot"></i><h3>欢迎使用智能知识助手!</h3><p>我可以帮您解答问题,分析文档内容。请输入您的问题开始对话。</p></div></div><!-- 输入区域 --><div class="chat-input-container"><div class="input-wrapper"><textarea id="messageInput" placeholder="请输入您的问题..." rows="1"></textarea><button id="sendButton" class="send-btn"><i class="fas fa-paper-plane"></i></button></div></div></div><script src="js/api.js"></script><script>class ChatApp {constructor() {this.messagesContainer = document.getElementById('chatMessages');this.messageInput = document.getElementById('messageInput');this.sendButton = document.getElementById('sendButton');this.isLoading = false;this.initEventListeners();this.autoResizeTextarea();}initEventListeners() {// 发送按钮点击事件this.sendButton.addEventListener('click', () => this.sendMessage());// 回车发送消息this.messageInput.addEventListener('keydown', (e) => {if (e.key === 'Enter' && !e.shiftKey) {e.preventDefault();this.sendMessage();}});}async sendMessage() {const message = this.messageInput.value.trim();if (!message || this.isLoading) return;// 添加用户消息this.addMessage(message, 'user');this.messageInput.value = '';this.isLoading = true;this.updateSendButton();// 添加加载消息const loadingId = this.addLoadingMessage();try {// 调用聊天APIconst response = await api.post('/api/chat', {message: message,useKnowledgeBase: true});// 移除加载消息this.removeMessage(loadingId);// 添加AI回复this.addMessage(response.content || response, 'assistant');} catch (error) {console.error('发送消息失败:', error);this.removeMessage(loadingId);this.addMessage('抱歉,发生了错误,请稍后重试。', 'assistant', true);} finally {this.isLoading = false;this.updateSendButton();}}addMessage(content, role, isError = false) {const messageId = 'msg-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9);const messageDiv = document.createElement('div');messageDiv.className = `message ${role}-message ${isError ? 'error' : ''}`;messageDiv.id = messageId;const avatar = document.createElement('div');avatar.className = 'message-avatar';avatar.innerHTML = role === 'user' ? '<i class="fas fa-user"></i>' : '<i class="fas fa-robot"></i>';const messageContent = document.createElement('div');messageContent.className = 'message-content';if (role === 'assistant' && !isError) {// 使用marked.js渲染MarkdownmessageContent.innerHTML = marked.parse(content);// 代码高亮messageContent.querySelectorAll('pre code').forEach(block => {hljs.highlightElement(block);});} else {messageContent.textContent = content;}messageDiv.appendChild(avatar);messageDiv.appendChild(messageContent);this.messagesContainer.appendChild(messageDiv);// 滚动到底部this.scrollToBottom();return messageId;}addLoadingMessage() {const messageId = 'loading-' + Date.now();const messageDiv = document.createElement('div');messageDiv.className = 'message assistant-message loading';messageDiv.id = messageId;messageDiv.innerHTML = `<div class="message-avatar"><i class="fas fa-robot"></i></div><div class="message-content"><div class="typing-indicator"><span></span><span></span><span></span></div></div>`;this.messagesContainer.appendChild(messageDiv);this.scrollToBottom();return messageId;}removeMessage(messageId) {const message = document.getElementById(messageId);if (message) {message.remove();}}updateSendButton() {this.sendButton.disabled = this.isLoading;this.sendButton.innerHTML = this.isLoading ? '<i class="fas fa-spinner fa-spin"></i>' : '<i class="fas fa-paper-plane"></i>';}scrollToBottom() {this.messagesContainer.scrollTop = this.messagesContainer.scrollHeight;}autoResizeTextarea() {this.messageInput.addEventListener('input', () => {this.messageInput.style.height = 'auto';this.messageInput.style.height = Math.min(this.messageInput.scrollHeight, 120) + 'px';});}}// 初始化聊天应用document.addEventListener('DOMContentLoaded', () => {new ChatApp();});</script>
</body>
</html>
🗄️ 数据库设计
PostgreSQL(向量数据)
-- 启用pgvector扩展
CREATE EXTENSION IF NOT EXISTS vector;-- 向量存储表(Spring AI默认表结构)
CREATE TABLE vector_store (id UUID PRIMARY KEY DEFAULT gen_random_uuid(),content TEXT NOT NULL,metadata JSON,embedding vector(1536)
);-- 创建向量索引(余弦相似度)
CREATE INDEX vector_store_embedding_idx ON vector_store
USING hnsw (embedding vector_cosine_ops);-- 创建内容全文搜索索引
CREATE INDEX vector_store_content_idx ON vector_store
USING gin (to_tsvector('english', content));
MySQL(业务数据)
-- 用户表
CREATE TABLE users (id VARCHAR(50) PRIMARY KEY COMMENT '用户ID',username VARCHAR(100) NOT NULL UNIQUE COMMENT '用户名',password VARCHAR(255) NOT NULL COMMENT '密码',email VARCHAR(255) COMMENT '邮箱',created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',is_deleted TINYINT(1) DEFAULT 0 COMMENT '是否删除'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';-- 聊天会话表
CREATE TABLE chat_sessions (id VARCHAR(50) PRIMARY KEY COMMENT '会话ID',user_id VARCHAR(50) NOT NULL COMMENT '用户ID',title VARCHAR(255) NOT NULL COMMENT '会话标题',created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',is_deleted TINYINT(1) DEFAULT 0 COMMENT '是否删除',INDEX idx_user_id (user_id),INDEX idx_created_at (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='聊天会话表';-- 聊天消息表
CREATE TABLE chat_messages (id VARCHAR(50) PRIMARY KEY COMMENT '消息ID',session_id VARCHAR(50) NOT NULL COMMENT '会话ID',user_id VARCHAR(50) NOT NULL COMMENT '用户ID',content TEXT NOT NULL COMMENT '消息内容',message_type VARCHAR(20) NOT NULL COMMENT '消息类型:USER/ASSISTANT',created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',INDEX idx_session_id (session_id),INDEX idx_user_id (user_id),INDEX idx_created_at (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='聊天消息表';-- 系统配置表
CREATE TABLE system_config (id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '配置ID',config_key VARCHAR(100) NOT NULL UNIQUE COMMENT '配置键',config_value TEXT COMMENT '配置值',description VARCHAR(255) COMMENT '配置描述',created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统配置表';
🚀 部署与运维
生产环境配置
application-prod.yaml:
spring:datasource:pg-vector:driver-class-name: org.postgresql.Driverurl: jdbc:postgresql://${PG_HOST:localhost}:${PG_PORT:5432}/${PG_DATABASE:rag_vector}username: ${PG_USERNAME:postgres}password: ${PG_PASSWORD:password}hikari:maximum-pool-size: 20minimum-idle: 5idle-timeout: 300000max-lifetime: 1200000connection-timeout: 20000mysql:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306}/${MYSQL_DATABASE:rag_business}?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghaiusername: ${MYSQL_USERNAME:root}password: ${MYSQL_PASSWORD:password}hikari:maximum-pool-size: 30minimum-idle: 10idle-timeout: 300000max-lifetime: 1200000connection-timeout: 20000ai:dashscope:api-key: ${DASHSCOPE_API_KEY}chat:options:model: ${DASHSCOPE_MODEL:qwen-plus}temperature: ${DASHSCOPE_TEMPERATURE:0.7}logging:level:com.mxy.rag: INFOorg.springframework.ai: INFOfile:name: logs/mxy-rag-server.logpattern:file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"server:port: ${SERVER_PORT:8080}servlet:context-path: /tomcat:max-threads: 200min-spare-threads: 10
Docker部署
Dockerfile:
FROM openjdk:17-jdk-slimWORKDIR /app# 复制jar文件
COPY target/mxy-rag-server-*.jar app.jar# 创建日志目录
RUN mkdir -p logs# 暴露端口
EXPOSE 8080# 启动应用
ENTRYPOINT ["java", "-Xms512m", "-Xmx2g", "-jar", "app.jar", "--spring.profiles.active=prod"]
Docker Compose配置
docker-compose.yml:
version: '3.8'
services:app:build: .ports:- "8080:8080"environment:- SPRING_PROFILES_ACTIVE=prod- DASHSCOPE_API_KEY=${DASHSCOPE_API_KEY}- PG_HOST=postgres- PG_PORT=5432- PG_DATABASE=rag_vector- PG_USERNAME=postgres- PG_PASSWORD=postgres123- MYSQL_HOST=mysql- MYSQL_PORT=3306- MYSQL_DATABASE=rag_business- MYSQL_USERNAME=root- MYSQL_PASSWORD=mysql123depends_on:- postgres- mysqlvolumes:- ./logs:/app/logsrestart: unless-stoppedpostgres:image: pgvector/pgvector:pg16environment:- POSTGRES_DB=rag_vector- POSTGRES_USER=postgres- POSTGRES_PASSWORD=postgres123ports:- "5432:5432"volumes:- postgres_data:/var/lib/postgresql/data- ./init-scripts/init-postgres.sql:/docker-entrypoint-initdb.d/init.sqlrestart: unless-stoppedmysql:image: mysql:8.0environment:- MYSQL_ROOT_PASSWORD=mysql123- MYSQL_DATABASE=rag_businessports:- "3306:3306"volumes:- mysql_data:/var/lib/mysql- ./init-scripts/init-mysql.sql:/docker-entrypoint-initdb.d/init.sqlrestart: unless-stoppedvolumes:postgres_data:mysql_data:
📝 总结
本文详细介绍了基于Spring AI和阿里云DashScope构建RAG系统的完整实现方案。主要特点包括:
🎯 核心特性
- 多模型支持:集成阿里云DashScope,支持通义千问系列模型
- 向量检索:基于pgvector的高效向量存储和检索
- 多数据源:PostgreSQL存储向量数据,MySQL存储业务数据
- 聊天记忆:支持多轮对话的上下文记忆
- 文档处理:支持PDF、Word等多种文档格式
- 用户认证:完整的用户管理和会话管理
🛠️ 技术栈
- 后端框架:Spring Boot 3.x + Spring AI Alibaba
- AI模型:阿里云DashScope(通义千问)
- 向量数据库:PostgreSQL + pgvector
- 业务数据库:MySQL 8.0
- ORM框架:MyBatis Plus
- 文档处理:Apache Tika + Spring AI Document Readers
🚀 部署方式
- 容器化部署:Docker + Docker Compose
- 生产环境:支持环境变量配置
- 日志管理:结构化日志输出
- 监控运维:完整的错误处理和日志记录
通过本方案,可以快速构建一个功能完整、性能优异的企业级RAG系统,为知识管理和智能问答提供强有力的技术支撑。
项目地址:https://github.com/Matthew-Miao/mxy-rag-server
欢迎Star和Fork,一起探讨AI应用开发的最佳实践!
如果这篇文章对你有帮助,请点赞、收藏并关注,我会持续分享更多AI开发实战经验!