基于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開發實戰經驗!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/91719.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/91719.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/91719.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

VirtualBox + CentOS:啟用 DHCP 獲取 IPv4 地址

標題&#xff1a; VirtualBox CentOS&#xff1a;啟用 DHCP 獲取 IPv4 地址 日期&#xff1a; 2025-07-18 一、問題現象 最小化安裝的 CentOS 7 虛擬機里敲&#xff1a; ip addr輸出只有 lo 的 127.0.0.1 以及 enp0s3 的 IPv6 鏈路本地地址&#xff0c;沒有 IPv4&#xff0…

Git

Git簡介Git 是一個分布式版本控制工具&#xff0c;通常用來對軟件開發過程中的源代碼文件進行管理。通過Git 倉庫來存儲和管理這些文件&#xff0c;Git 倉庫分為兩種:本地倉庫:開發人員自己電腦上的 Git倉庫。遠程倉庫:遠程服務器上的 Git 倉庫。commit: 提交, 將本地文件和版本…

通信算法之294:LTE系統中的整數倍頻偏估計

在LTE系統中&#xff0c;整數倍頻偏估計主要通過以下方法實現&#xff1a;一、最大似然估計法&#xff08;ML&#xff09;通過遍歷預設的整數倍頻偏范圍&#xff08;如30kHz&#xff09;&#xff0c;將接收信號與本地的PSS序列在不同頻偏點上進行相關運算&#xff0c;選擇相關峰…

數字人直播:開啟直播行業新紀元?

?原始尺寸更換圖片p9-flow-imagex-sign.byteimg.com??在科技日新月異的當下&#xff0c;直播行業正經歷著一場深刻變革&#xff0c;數字人直播的興起&#xff0c;宛如一顆璀璨新星&#xff0c;照亮了直播領域的新征程。數字人直播&#xff0c;是利用先進的人工智能技術&…

朝鮮升級供應鏈惡意軟件XORIndex,再次瞄準npm生態系統

Socket威脅研究團隊最新披露&#xff0c;朝鮮國家支持的黑客組織在"傳染性面試"攻擊活動中采用了新型惡意軟件加載器XORIndex&#xff0c;該惡意程序專門通過npm軟件包注冊表滲透軟件供應鏈。攻擊規模與持續性此次攻擊并非孤立事件&#xff0c;而是針對開發者、求職者…

Windows 下 VS2019 編譯 libevent-2.1.10 庫

1. 你需要VS2019 編譯好openssl-1.1.1 &#xff0c;這個具體編譯或者下載可以參考我的博客openssl生成的庫是這兩個文件接下來&#xff0c;打開CMake &#xff0c;主要是下面的需要設置好最后Config Generate即可&#xff1b;全部成功生成 22個然后INSTALL右鍵生成 最后看下生…

Vim多列操作指南

我們在使用 Vim 時&#xff0c;經常需要同時編輯多個文件&#xff0c;或者同一個文件的不同部分。Vim 提供了分割窗口&#xff08;split&#xff09;和垂直分割窗口&#xff08;vsplit&#xff09;的功能&#xff0c;允許我們在同一個 Vim 會話中查看多個緩沖區&#xff08;buf…

Python網絡爬蟲實現selenium對百度識圖二次開發以及批量保存Excel

一.百度識圖自動上傳圖片from selenium import webdriver from selenium.webdriver.edge.options import Options from selenium.webdriver.common.by import By edge_options Options() edge_options.binary_location r"C:\Program Files (x86)\Microsoft\Edge\Applica…

Vue中的refs字段使用記錄

這段代碼是 Vue.js 中結合 Element UI 等 UI 庫的典型表單驗證寫法&#xff0c;具體含義如下&#xff1a;代碼拆解 this.$refs.fromData.validate((valid) > {// 驗證后的回調邏輯 })this.$refs.fromData $refs 是 Vue 提供的特殊屬性&#xff0c;用于訪問模板中通過 ref&qu…

多方案對比分析:后端數據加密策略及實踐

多方案對比分析&#xff1a;后端數據加密策略及實踐 隨著互聯網業務對用戶隱私和數據安全的要求不斷提升&#xff0c;后端系統中對敏感數據的加密保護已成為必備需求。從對稱加密、非對稱加密到數據庫透明加密、應用層字段加密&#xff0c;各種方案各有特點。本文將以方案對比分…

《Java語言程序設計》1.4 復習題

1.4.1 什么是操作系統?列出一些流行的操作系統?操作系統(Operating System)是運行在計算機上的最重要的程序。操作系統管理和控制計算機的活動。通用計算機的流行操作系統有Microsoft Windows、Mac OS以及Linux。如果沒有在計算機上安裝和運行操作系統&#xff0c;像Web瀏覽器…

OpenCV圖像自動縮放(Autoscaling)函數autoscaling()

操作系統&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 編程語言&#xff1a;C11 算法描述 該函數用于對輸入圖像進行自動縮放&#xff08;Autoscaling&#xff09;&#xff0c;即根據輸入圖像的像素值范圍將其線性映射到一個新的范圍內&am…

多線程的認識

競爭與協作在單核 CPU 系統里&#xff0c;為了實現多個程序同時運行的假象&#xff0c;操作系統通常以時間片調度的方式&#xff0c;讓每個進程執行每次執行一個時間片&#xff0c;時間片用完了&#xff0c;就切換下一個進程運行&#xff0c;由于這個時間片的時間很短&#xff…

SpringCloud相關總結

SpringCloud相關總結 1. 權威文檔推薦&#xff1a; 官方文檔&#xff1a;https://spring.io/cloud 玩的時候&#xff0c;注意SpringBoot與SpringCloud的版本兼容問題,推薦參考&#xff1a;https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%…

NW983NW988美光固態閃存NW991NW992

美光NW系列固態閃存深度解析&#xff1a;技術、對比與應用指南一、技術根基與架構創新美光NW系列固態閃存的技術突破源于其先進的G9 NAND架構&#xff0c;該架構采用5納米制程工藝和多層3D堆疊技術&#xff0c;在單位面積內實現了高達256層的存儲單元堆疊&#xff0c;存儲密度較…

pytest + requests 接口自動化測試框架

??親愛的技術愛好者們,熱烈歡迎來到 Kant2048 的博客!我是 Thomas Kant,很開心能在CSDN上與你們相遇~?? 本博客的精華專欄: 【自動化測試】 【測試經驗】 【人工智能】 【Python】 使用 pytest + req

Android性能優化之網絡優化

一、網絡性能瓶頸深度解析 1. 網絡請求全鏈路耗時分析 #mermaid-svg-3cXlC9wERu99EHQH {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-3cXlC9wERu99EHQH .error-icon{fill:#552222;}#mermaid-svg-3cXlC9wERu99EHQH …

實現庫存顯示和狀態按鈕的Question

首先實現顯示圖書庫存 1 if條件標簽里的test""要和實體類的屬性名保持一致 否則會報找不到get方法的錯誤org.apache.ibatis.reflection.ReflectionException: There is no getter for property named stock in class2后端已經把bookStock傳到前端了&#xff0c;但是顯…

vue + Cesium 實現 3D 地圖水面效果詳解

一、引言Cesium 是一個強大的開源 JavaScript 庫&#xff0c;用于創建基于 Web 的 3D 地理信息系統 (GIS) 應用程序。它提供了豐富的 API&#xff0c;可以實現各種復雜的地理可視化效果&#xff0c;包括地形渲染、建筑物建模、矢量數據顯示等。本文將詳細介紹如何使用 Cesium 實…

統信 UOS 運行 Windows 應用新利器!彩虹虛擬化軟件 V3.2 全新上線,限時30天免費體驗

原文鏈接&#xff1a;統信 UOS 運行 Windows 應用新利器&#xff01;彩虹虛擬化軟件 V3.2 全新上線&#xff0c;限時30天免費體驗 在國產操作系統逐漸普及的今天&#xff0c;許多用戶仍面臨一個實際問題——一些辦公軟件或行業工具仍然僅支持 Windows 系統。對于已全面部署統信…