從 0 到 1:Spring Boot 與 Spring AI 深度實戰(基于深度求索 DeepSeek)

????????在人工智能技術與企業級開發深度融合的今天,傳統軟件開發模式與 AI 工程化開發的差異日益顯著。作為 Spring 生態體系中專注于 AI 工程化的核心框架,Spring AI通過標準化集成方案大幅降低 AI 應用開發門檻。本文將以國產大模型代表 ** 深度求索(DeepSeek)** 為例,完整演示從環境搭建到核心機制解析的全流程,帶您掌握企業級 AI 應用開發的核心能力。

一、傳統開發 vs AI 工程化:范式革命與技術挑戰

1. 開發模式對比

維度傳統軟件開發AI 工程化開發
核心驅動業務邏輯與算法實現數據驅動的模型訓練與推理
輸出特性確定性結果(基于固定規則)概率性結果(基于統計學習)
核心資產業務代碼與數據結構高質量數據集與訓練好的模型
迭代方式功能模塊增量開發數據標注→模型訓練→推理優化的閉環迭代

2. AI 工程化核心挑戰

  • 數據治理難題:需解決數據采集(如爬蟲反爬)、清洗(異常值處理)、標注(實體識別)等全鏈路問題
  • 模型工程復雜度:涉及模型選型(如選擇 DeepSeek-R1 還是 Llama 系列)、訓練調優(超參數搜索)、量化壓縮(模型輕量化)
  • 生產級部署要求:需支持高并發推理(如 Token 級流輸出)、多模型管理(A/B 測試)、實時監控(延遲 / 成功率指標)

????????傳統 Spring Boot 的 MVC 架構難以直接應對這些挑戰,而Spring AI通過標準化接口封裝與生態整合,將 AI 能力轉化為可插拔的工程組件。

二、Spring AI x DeepSeek:國產化 AI 工程解決方案

1. DeepSeek 模型優勢

作為國內領先的 AGI 公司,深度求索(DeepSeek)提供:

  • 高性能推理引擎:支持長上下文(8K/32K tokens 可選)與流式輸出
  • 企業級安全合規:數據本地化部署方案(支持私有化云)
  • 多模態能力擴展:后續可無縫集成圖像 / 語音處理模塊

????????通過spring-ai-deepseek模塊,Spring Boot 應用可通過注解驅動方式調用 DeepSeek 模型,底層自動處理 HTTP 連接池管理、請求重試、響應解析等工程化問題。

三、實戰開發:基于 DeepSeek 的智能文本生成系統

1. 項目搭建

目錄結構

通過 Spring Initializr 創建項目時,添加?DeepSeek 專用依賴

<dependency>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-ai-deepseek</artifactId>  
</dependency>  

或在?pom.xml?中手動添加上述依賴,Maven 會自動解析 DeepSeek 集成所需的全部組件。

2. 配置 DeepSeek

在?application.yml?中配置 DeepSeek 服務信息(含注冊指引):

# DeepSeek 服務配置(官方文檔:https://docs.spring.io/spring-ai/reference/api/chat/deepseek-chat.html)
spring:ai:deepseek:# 必需:在DeepSeek控制臺申請的API密鑰(注冊地址:https://platform.deepseek.com/register)api-key: ${DEEPSEEK_API_KEY:your-deepseek-api-key}# API基礎地址(私有化部署需修改)base-url: https://api.deepseek.com# 聊天模型配置chat:enabled: trueoptions:model: deepseek-chat  # 使用deepseek-chat模型temperature: 0.8  # 生成隨機性控制(0.0-1.0,值越高越隨機)max-tokens: 512  # 單次生成最大Token數top-p: 0.9  # Nucleus采樣參數(0.0-1.0,控制生成詞匯的概率分布)frequency-penalty: 0.0  # 頻率懲罰(-2.0到2.0)presence-penalty: 0.0  # 存在懲罰(-2.0到2.0)stop: ["###", "END"]  # 生成停止序列# 重試配置retry:max-attempts: 3  # 最大重試次數backoff:initial-interval: 2s  # 初始重試間隔multiplier: 2  # 重試間隔倍數max-interval: 10s  # 最大重試間隔on-client-errors: false  # 是否對4xx錯誤重試# 應用服務器配置
server:port: 8080  # 服務端口servlet:context-path: /  # 上下文路徑encoding:charset: UTF-8  # 字符編碼force: true  # 強制編碼# 日志配置
logging:level:root: INFOcom.example.demo: DEBUGorg.springframework.ai: DEBUGorg.springframework.ai.deepseek: DEBUGpattern:console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"# 管理端點配置
management:endpoints:web:exposure:include: health,info,metrics,envbase-path: /actuatorendpoint:health:show-details: alwaysserver:port: 8080

3. 編寫代碼

(1)DeepSeek 服務封裝(SmartGeneratorService.java
package com.example.demo.service;import com.example.demo.dto.AiRequest;
import com.example.demo.dto.AiResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.ai.deepseek.DeepSeekChatOptions;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;import java.util.Map;/*** 智能生成服務* 提供營銷文案生成、代碼生成、智能問答等功能* * @author Spring AI Demo*/
@Service
public class SmartGeneratorService {private static final Logger logger = LoggerFactory.getLogger(SmartGeneratorService.class);private final ChatModel chatModel;public SmartGeneratorService(ChatModel chatModel) {this.chatModel = chatModel;}/*** 生成營銷文案* * @param request 請求參數* @return AI響應*/public AiResponse generateMarketingContent(AiRequest request) {logger.info("開始生成營銷文案,輸入:{}", request.getContent());long startTime = System.currentTimeMillis();try {String systemPrompt = """你是一位專業的營銷文案專家,擅長創作吸引人的營銷內容。請根據用戶的需求,生成具有以下特點的營銷文案:1. 吸引眼球的標題2. 突出產品/服務的核心價值3. 使用情感化的語言4. 包含明確的行動號召5. 語言簡潔有力,易于理解請用中文回復,格式清晰,內容富有創意。""";PromptTemplate promptTemplate = new PromptTemplate(systemPrompt + "\n\n用戶需求:{content}");Prompt prompt = promptTemplate.create(Map.of("content", request.getContent()));// 設置營銷文案生成的參數(創意性較高)DeepSeekChatOptions options = DeepSeekChatOptions.builder().temperature(request.getTemperature() != null ? request.getTemperature() : 1.3).maxTokens(request.getMaxTokens() != null ? request.getMaxTokens() : 800).build();var response = chatModel.call(new Prompt(prompt.getInstructions(), options));String content = response.getResult().getOutput().getText();long processingTime = System.currentTimeMillis() - startTime;logger.info("營銷文案生成完成,耗時:{}ms", processingTime);AiResponse aiResponse = AiResponse.success(content, "deepseek-chat");aiResponse.setProcessingTimeMs(processingTime);return aiResponse;} catch (Exception e) {logger.error("營銷文案生成失敗", e);return AiResponse.error("營銷文案生成失敗:" + e.getMessage());}}/*** 生成代碼* * @param request 請求參數* @return AI響應*/public AiResponse generateCode(AiRequest request) {logger.info("開始生成代碼,需求:{}", request.getContent());long startTime = System.currentTimeMillis();try {String systemPrompt = """你是一位資深的軟件工程師,精通多種編程語言和技術棧。請根據用戶的需求,生成高質量的代碼,要求:1. 代碼結構清晰,邏輯合理2. 包含必要的注釋說明3. 遵循最佳實踐和編碼規范4. 考慮錯誤處理和邊界情況5. 如果需要,提供使用示例請用中文注釋,代碼要完整可運行。""";PromptTemplate promptTemplate = new PromptTemplate(systemPrompt + "\n\n編程需求:{content}");Prompt prompt = promptTemplate.create(Map.of("content", request.getContent()));// 設置代碼生成的參數(準確性優先)DeepSeekChatOptions options = DeepSeekChatOptions.builder().temperature(request.getTemperature() != null ? request.getTemperature() : 0.1).maxTokens(request.getMaxTokens() != null ? request.getMaxTokens() : 1500).build();var response = chatModel.call(new Prompt(prompt.getInstructions(), options));String content = response.getResult().getOutput().getText();long processingTime = System.currentTimeMillis() - startTime;logger.info("代碼生成完成,耗時:{}ms", processingTime);AiResponse aiResponse = AiResponse.success(content, "deepseek-chat");aiResponse.setProcessingTimeMs(processingTime);return aiResponse;} catch (Exception e) {logger.error("代碼生成失敗", e);return AiResponse.error("代碼生成失敗:" + e.getMessage());}}/*** 智能問答* * @param request 請求參數* @return AI響應*/public AiResponse answerQuestion(AiRequest request) {logger.info("開始智能問答,問題:{}", request.getContent());long startTime = System.currentTimeMillis();try {String systemPrompt = """你是一位知識淵博的AI助手,能夠回答各種領域的問題。請根據用戶的問題,提供準確、詳細、有用的回答:1. 回答要準確可靠,基于事實2. 解釋要清晰易懂,層次分明3. 如果涉及專業術語,請適當解釋4. 如果問題復雜,可以分步驟說明5. 如果不確定答案,請誠實說明請用中文回復,語言友好專業。""";PromptTemplate promptTemplate = new PromptTemplate(systemPrompt + "\n\n用戶問題:{content}");Prompt prompt = promptTemplate.create(Map.of("content", request.getContent()));// 設置問答的參數(平衡準確性和流暢性)DeepSeekChatOptions options = DeepSeekChatOptions.builder().temperature(request.getTemperature() != null ? request.getTemperature() : 0.7).maxTokens(request.getMaxTokens() != null ? request.getMaxTokens() : 1000).build();var response = chatModel.call(new Prompt(prompt.getInstructions(), options));String content = response.getResult().getOutput().getText();long processingTime = System.currentTimeMillis() - startTime;logger.info("智能問答完成,耗時:{}ms", processingTime);AiResponse aiResponse = AiResponse.success(content, "deepseek-chat");aiResponse.setProcessingTimeMs(processingTime);return aiResponse;} catch (Exception e) {logger.error("智能問答失敗", e);return AiResponse.error("智能問答失敗:" + e.getMessage());}}/*** 通用聊天* * @param request 請求參數* @return AI響應*/public AiResponse chat(AiRequest request) {logger.info("開始聊天對話,消息:{}", request.getContent());long startTime = System.currentTimeMillis();try {String systemPrompt = request.getSystemPrompt() != null ? request.getSystemPrompt() : """你是一位友好、有幫助的AI助手。請以自然、親切的方式與用戶對話:1. 保持友好和禮貌的語調2. 根據上下文提供有用的回復3. 如果用戶需要幫助,盡力提供支持4. 保持對話的連貫性和趣味性請用中文回復,語言自然流暢。""";PromptTemplate promptTemplate = new PromptTemplate(systemPrompt + "\n\n用戶:{content}");Prompt prompt = promptTemplate.create(Map.of("content", request.getContent()));// 設置聊天的參數(自然對話)DeepSeekChatOptions options = DeepSeekChatOptions.builder().temperature(request.getTemperature() != null ? request.getTemperature() : 0.9).maxTokens(request.getMaxTokens() != null ? request.getMaxTokens() : 800).build();var response = chatModel.call(new Prompt(prompt.getInstructions(), options));String content = response.getResult().getOutput().getText();long processingTime = System.currentTimeMillis() - startTime;logger.info("聊天對話完成,耗時:{}ms", processingTime);AiResponse aiResponse = AiResponse.success(content, "deepseek-chat");aiResponse.setProcessingTimeMs(processingTime);return aiResponse;} catch (Exception e) {logger.error("聊天對話失敗", e);return AiResponse.error("聊天對話失敗:" + e.getMessage());}}/*** 流式聊天* * @param message 用戶消息* @return 流式響應*/public Flux<String> streamChat(String message) {logger.info("開始流式聊天,消息:{}", message);try {String systemPrompt = """你是一位友好、有幫助的AI助手。請以自然、親切的方式與用戶對話,用中文回復。""";PromptTemplate promptTemplate = new PromptTemplate(systemPrompt + "\n\n用戶:{content}");Prompt prompt = promptTemplate.create(Map.of("content", message));DeepSeekChatOptions options = DeepSeekChatOptions.builder().temperature(0.9).maxTokens(800).build();return chatModel.stream(new Prompt(prompt.getInstructions(), options)).map(response -> response.getResult().getOutput().getText()).doOnNext(chunk -> logger.debug("流式響應塊:{}", chunk)).doOnComplete(() -> logger.info("流式聊天完成")).doOnError(error -> logger.error("流式聊天失敗", error));} catch (Exception e) {logger.error("流式聊天啟動失敗", e);return Flux.error(e);}}
} 
(2)Web 控制器實現(AiController.java
package com.example.demo.controller;import com.example.demo.dto.AiRequest;
import com.example.demo.dto.AiResponse;
import com.example.demo.service.SmartGeneratorService;
import jakarta.validation.Valid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;/*** AI功能控制器* 提供營銷文案生成、代碼生成、智能問答、聊天對話等API* * @author Spring AI Demo*/
@RestController
@RequestMapping("/api/ai")
@CrossOrigin(origins = "*")
public class AiController {private static final Logger logger = LoggerFactory.getLogger(AiController.class);private final SmartGeneratorService smartGeneratorService;public AiController(SmartGeneratorService smartGeneratorService) {this.smartGeneratorService = smartGeneratorService;}/*** 營銷文案生成API* * @param request 請求參數* @return 生成的營銷文案*/@PostMapping("/marketing")public ResponseEntity<AiResponse> generateMarketingContent(@Valid @RequestBody AiRequest request) {logger.info("收到營銷文案生成請求:{}", request.getContent());try {AiResponse response = smartGeneratorService.generateMarketingContent(request);return ResponseEntity.ok(response);} catch (Exception e) {logger.error("營銷文案生成API調用失敗", e);return ResponseEntity.internalServerError().body(AiResponse.error("服務器內部錯誤:" + e.getMessage()));}}/*** 代碼生成API* * @param request 請求參數* @return 生成的代碼*/@PostMapping("/code")public ResponseEntity<AiResponse> generateCode(@Valid @RequestBody AiRequest request) {logger.info("收到代碼生成請求:{}", request.getContent());try {AiResponse response = smartGeneratorService.generateCode(request);return ResponseEntity.ok(response);} catch (Exception e) {logger.error("代碼生成API調用失敗", e);return ResponseEntity.internalServerError().body(AiResponse.error("服務器內部錯誤:" + e.getMessage()));}}/*** 智能問答API* * @param request 請求參數* @return 問題的答案*/@PostMapping("/qa")public ResponseEntity<AiResponse> answerQuestion(@Valid @RequestBody AiRequest request) {logger.info("收到智能問答請求:{}", request.getContent());try {AiResponse response = smartGeneratorService.answerQuestion(request);return ResponseEntity.ok(response);} catch (Exception e) {logger.error("智能問答API調用失敗", e);return ResponseEntity.internalServerError().body(AiResponse.error("服務器內部錯誤:" + e.getMessage()));}}/*** 聊天對話API* * @param request 請求參數* @return 聊天回復*/@PostMapping("/chat")public ResponseEntity<AiResponse> chat(@Valid @RequestBody AiRequest request) {logger.info("收到聊天對話請求:{}", request.getContent());try {AiResponse response = smartGeneratorService.chat(request);return ResponseEntity.ok(response);} catch (Exception e) {logger.error("聊天對話API調用失敗", e);return ResponseEntity.internalServerError().body(AiResponse.error("服務器內部錯誤:" + e.getMessage()));}}/*** 簡單文本生成API(GET方式,用于快速測試)* * @param message 用戶消息* @param temperature 溫度參數(可選)* @return 生成的回復*/@GetMapping("/simple")public ResponseEntity<AiResponse> simpleChat(@RequestParam String message,@RequestParam(required = false) Double temperature) {logger.info("收到簡單聊天請求:{}", message);try {AiRequest request = new AiRequest(message, temperature);AiResponse response = smartGeneratorService.chat(request);return ResponseEntity.ok(response);} catch (Exception e) {logger.error("簡單聊天API調用失敗", e);return ResponseEntity.internalServerError().body(AiResponse.error("服務器內部錯誤:" + e.getMessage()));}}/*** 健康檢查API* * @return 服務狀態*/@GetMapping("/health")public ResponseEntity<String> health() {return ResponseEntity.ok("AI服務運行正常 ?");}/*** 獲取支持的功能列表* * @return 功能列表*/@GetMapping("/features")public ResponseEntity<Object> getFeatures() {var features = new Object() {public final String[] supportedFeatures = {"營銷文案生成 (POST /api/ai/marketing)","代碼生成 (POST /api/ai/code)", "智能問答 (POST /api/ai/qa)","聊天對話 (POST /api/ai/chat)","簡單對話 (GET /api/ai/simple?message=你好)","流式聊天 (GET /api/stream/chat?message=你好)"};public final String model = "deepseek-chat";public final String version = "1.0.0";public final String description = "Spring AI + DeepSeek 智能文本生成服務";};return ResponseEntity.ok(features);}
} 
(3)流式響應處理(StreamController.java
package com.example.demo.controller;import com.example.demo.service.SmartGeneratorService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;import java.time.Duration;
import java.util.HashMap;
import java.util.Map;/*** 流式響應控制器* 提供Server-Sent Events (SSE) 流式聊天功能* * @author Spring AI Demo*/
@RestController
@RequestMapping("/api/stream")
@CrossOrigin(origins = "*")
public class StreamController {private static final Logger logger = LoggerFactory.getLogger(StreamController.class);private final SmartGeneratorService smartGeneratorService;public StreamController(SmartGeneratorService smartGeneratorService) {this.smartGeneratorService = smartGeneratorService;}/*** 流式聊天API* 使用Server-Sent Events (SSE) 實現實時流式響應* * @param message 用戶消息* @return 流式響應*/@GetMapping(value = "/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)public Flux<String> streamChat(@RequestParam String message) {logger.info("收到流式聊天請求:{}", message);return smartGeneratorService.streamChat(message).filter(chunk -> chunk != null && !chunk.trim().isEmpty()) // 過濾空內容.doOnNext(chunk -> logger.debug("原始數據塊: '{}'", chunk)).map(chunk -> chunk.trim()) // 只清理空白字符.filter(chunk -> !chunk.isEmpty()) // 再次過濾空內容.concatWith(Flux.just("[DONE]")).doOnSubscribe(subscription -> logger.info("開始流式響應")).doOnComplete(() -> logger.info("流式響應完成")).doOnError(error -> logger.error("流式響應出錯", error)).onErrorReturn("[ERROR] 流式響應出現錯誤");}/*** 流式聊天API(JSON格式)* 返回JSON格式的流式數據* * @param message 用戶消息* @return JSON格式的流式響應*/@GetMapping(value = "/chat-json", produces = MediaType.APPLICATION_NDJSON_VALUE)public Flux<Map<String, Object>> streamChatJson(@RequestParam String message) {logger.info("收到JSON流式聊天請求:{}", message);// 創建完成響應Map<String, Object> doneResponse = new HashMap<>();doneResponse.put("type", "done");doneResponse.put("content", "");doneResponse.put("timestamp", System.currentTimeMillis());// 創建錯誤響應Map<String, Object> errorResponse = new HashMap<>();errorResponse.put("type", "error");errorResponse.put("content", "流式響應出現錯誤");errorResponse.put("timestamp", System.currentTimeMillis());return smartGeneratorService.streamChat(message).map(chunk -> {Map<String, Object> response = new HashMap<>();response.put("type", "chunk");response.put("content", chunk);response.put("timestamp", System.currentTimeMillis());return response;}).concatWith(Flux.just(doneResponse)).doOnSubscribe(subscription -> logger.info("開始JSON流式響應")).doOnComplete(() -> logger.info("JSON流式響應完成")).doOnError(error -> logger.error("JSON流式響應出錯", error)).onErrorReturn(errorResponse);}/*** 模擬打字機效果的流式響應* * @param message 用戶消息* @return 帶延遲的流式響應*/@GetMapping(value = "/typewriter", produces = MediaType.TEXT_EVENT_STREAM_VALUE)public Flux<String> typewriterChat(@RequestParam String message) {logger.info("收到打字機效果聊天請求:{}", message);return smartGeneratorService.streamChat(message).delayElements(Duration.ofMillis(50)) // 添加50ms延遲模擬打字機效果.map(chunk -> "data: " + chunk + "\n\n").concatWith(Flux.just("data: [DONE]\n\n")).doOnSubscribe(subscription -> logger.info("開始打字機效果流式響應")).doOnComplete(() -> logger.info("打字機效果流式響應完成")).doOnError(error -> logger.error("打字機效果流式響應出錯", error)).onErrorReturn("data: [ERROR] 流式響應出現錯誤\n\n");}/*** 流式響應健康檢查* * @return 測試流式響應*/@GetMapping(value = "/health", produces = MediaType.TEXT_EVENT_STREAM_VALUE)public Flux<String> streamHealth() {return Flux.interval(Duration.ofSeconds(1)).take(5).map(i -> "data: 流式服務正常運行 - " + (i + 1) + "/5\n\n").concatWith(Flux.just("data: [DONE] 健康檢查完成\n\n")).doOnSubscribe(subscription -> logger.info("開始流式健康檢查")).doOnComplete(() -> logger.info("流式健康檢查完成"));}/*** 測試用的簡單流式聊天(修復版本)* * @param message 用戶消息* @return 流式響應*/@GetMapping(value = "/chat-fixed", produces = MediaType.TEXT_EVENT_STREAM_VALUE)public Flux<String> streamChatFixed(@RequestParam String message) {logger.info("收到修復版流式聊天請求:{}", message);return smartGeneratorService.streamChat(message).filter(chunk -> chunk != null && !chunk.trim().isEmpty()).doOnNext(chunk -> logger.debug("修復版數據塊: '{}'", chunk)).map(chunk -> chunk.trim()).filter(chunk -> !chunk.isEmpty()).concatWith(Flux.just("[DONE]")).doOnSubscribe(subscription -> logger.info("開始修復版流式響應")).doOnComplete(() -> logger.info("修復版流式響應完成")).doOnError(error -> logger.error("修復版流式響應出錯", error)).onErrorReturn("[ERROR] 修復版流式響應出現錯誤");}/*** 獲取流式API使用說明* * @return 使用說明*/@GetMapping("/info")public Map<String, Object> getStreamInfo() {Map<String, Object> info = new HashMap<>();info.put("description", "Spring AI DeepSeek 流式響應服務");info.put("endpoints", new String[]{"GET /api/stream/chat?message=你好 - 基礎流式聊天","GET /api/stream/chat-fixed?message=你好 - 修復版流式聊天","GET /api/stream/chat-json?message=你好 - JSON格式流式聊天","GET /api/stream/typewriter?message=你好 - 打字機效果流式聊天","GET /api/stream/health - 流式服務健康檢查"});info.put("usage", "使用curl測試: curl -N 'http://localhost:8080/api/stream/chat-fixed?message=你好'");info.put("browser", "瀏覽器訪問: http://localhost:8080/api/stream/chat-fixed?message=你好");info.put("contentType", "text/event-stream");return info;}
} 
(4)主頁控制器(HomeController.java
package com.example.demo.controller;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;/*** 主頁控制器* 處理根路徑訪問和頁面跳轉* * @author Spring AI Demo*/
@Controller
public class HomeController {/*** 根路徑重定向到主頁* * @return 重定向到index.html*/@GetMapping("/")public String home() {return "redirect:/index.html";}/*** 主頁訪問* * @return index頁面*/@GetMapping("/index")public String index() {return "redirect:/index.html";}/*** 演示頁面訪問* * @return index頁面*/@GetMapping("/demo")public String demo() {return "redirect:/index.html";}
} 
(5)自定義錯誤處理控制器(CustomErrorController.java
package com.example.demo.controller;import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;import jakarta.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;/*** 自定義錯誤處理控制器* 提供友好的錯誤頁面和API錯誤響應* * @author Spring AI Demo*/
@Controller
public class CustomErrorController implements ErrorController {/*** 處理錯誤請求* * @param request HTTP請求* @return 錯誤響應*/@RequestMapping("/error")@ResponseBodypublic Map<String, Object> handleError(HttpServletRequest request) {Map<String, Object> errorResponse = new HashMap<>();// 獲取錯誤狀態碼Integer statusCode = (Integer) request.getAttribute("jakarta.servlet.error.status_code");String requestUri = (String) request.getAttribute("jakarta.servlet.error.request_uri");if (statusCode == null) {statusCode = HttpStatus.INTERNAL_SERVER_ERROR.value();}errorResponse.put("status", statusCode);errorResponse.put("error", getErrorMessage(statusCode));errorResponse.put("path", requestUri);errorResponse.put("timestamp", System.currentTimeMillis());// 根據錯誤類型提供幫助信息switch (statusCode) {case 404:errorResponse.put("message", "頁面未找到");errorResponse.put("suggestions", new String[]{"訪問主頁: http://localhost:8080","查看API文檔: http://localhost:8080/api/ai/features","健康檢查: http://localhost:8080/actuator/health"});break;case 500:errorResponse.put("message", "服務器內部錯誤");errorResponse.put("suggestions", new String[]{"檢查應用日志","確認API密鑰配置正確","重啟應用服務"});break;default:errorResponse.put("message", "請求處理失敗");errorResponse.put("suggestions", new String[]{"檢查請求格式","查看API文檔","聯系技術支持"});}return errorResponse;}/*** 根據狀態碼獲取錯誤消息* * @param statusCode HTTP狀態碼* @return 錯誤消息*/private String getErrorMessage(int statusCode) {switch (statusCode) {case 400:return "Bad Request";case 401:return "Unauthorized";case 403:return "Forbidden";case 404:return "Not Found";case 500:return "Internal Server Error";case 502:return "Bad Gateway";case 503:return "Service Unavailable";default:return "Unknown Error";}}
} 
(6)Web配置類(WebConfig.java
package com.example.demo.config;import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;/*** Web配置類* 配置靜態資源處理* * @author Spring AI Demo*/
@Configuration
public class WebConfig implements WebMvcConfigurer {/*** 配置靜態資源處理器* * @param registry 資源處理器注冊表*/@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {// 配置靜態資源路徑registry.addResourceHandler("/**").addResourceLocations("classpath:/static/").setCachePeriod(3600); // 緩存1小時// 確保index.html可以被訪問registry.addResourceHandler("/index.html").addResourceLocations("classpath:/static/index.html").setCachePeriod(0); // 不緩存主頁}
} 
(7)AI服務請求DTO(AiRequest.java
package com.example.demo.dto;import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import jakarta.validation.constraints.DecimalMax;
import jakarta.validation.constraints.DecimalMin;/*** AI服務請求DTO* * @author Spring AI Demo*/
public class AiRequest {/*** 用戶輸入內容*/@NotBlank(message = "輸入內容不能為空")@Size(max = 2000, message = "輸入內容不能超過2000個字符")private String content;/*** 溫度參數(可選)* 控制生成文本的隨機性,0.0表示確定性,1.0表示最大隨機性*/@DecimalMin(value = "0.0", message = "溫度參數不能小于0.0")@DecimalMax(value = "2.0", message = "溫度參數不能大于2.0")private Double temperature;/*** 最大生成Token數(可選)*/private Integer maxTokens;/*** 系統提示詞(可選)*/private String systemPrompt;// 構造函數public AiRequest() {}public AiRequest(String content) {this.content = content;}public AiRequest(String content, Double temperature) {this.content = content;this.temperature = temperature;}// Getter和Setter方法public String getContent() {return content;}public void setContent(String content) {this.content = content;}public Double getTemperature() {return temperature;}public void setTemperature(Double temperature) {this.temperature = temperature;}public Integer getMaxTokens() {return maxTokens;}public void setMaxTokens(Integer maxTokens) {this.maxTokens = maxTokens;}public String getSystemPrompt() {return systemPrompt;}public void setSystemPrompt(String systemPrompt) {this.systemPrompt = systemPrompt;}@Overridepublic String toString() {return "AiRequest{" +"content='" + content + '\'' +", temperature=" + temperature +", maxTokens=" + maxTokens +", systemPrompt='" + systemPrompt + '\'' +'}';}
} 
(8)AI服務響應DTO(AiResponse.java
package com.example.demo.dto;import java.time.LocalDateTime;/*** AI服務響應DTO* * @author Spring AI Demo*/
public class AiResponse {/*** 生成的內容*/private String content;/*** 請求是否成功*/private boolean success;/*** 錯誤信息(如果有)*/private String errorMessage;/*** 響應時間戳*/private LocalDateTime timestamp;/*** 使用的模型名稱*/private String model;/*** 消耗的Token數量*/private Integer tokensUsed;/*** 處理耗時(毫秒)*/private Long processingTimeMs;// 構造函數public AiResponse() {this.timestamp = LocalDateTime.now();}public AiResponse(String content) {this();this.content = content;this.success = true;}public AiResponse(String content, String model) {this(content);this.model = model;}// 靜態工廠方法public static AiResponse success(String content) {return new AiResponse(content);}public static AiResponse success(String content, String model) {return new AiResponse(content, model);}public static AiResponse error(String errorMessage) {AiResponse response = new AiResponse();response.success = false;response.errorMessage = errorMessage;return response;}// Getter和Setter方法public String getContent() {return content;}public void setContent(String content) {this.content = content;}public boolean isSuccess() {return success;}public void setSuccess(boolean success) {this.success = success;}public String getErrorMessage() {return errorMessage;}public void setErrorMessage(String errorMessage) {this.errorMessage = errorMessage;}public LocalDateTime getTimestamp() {return timestamp;}public void setTimestamp(LocalDateTime timestamp) {this.timestamp = timestamp;}public String getModel() {return model;}public void setModel(String model) {this.model = model;}public Integer getTokensUsed() {return tokensUsed;}public void setTokensUsed(Integer tokensUsed) {this.tokensUsed = tokensUsed;}public Long getProcessingTimeMs() {return processingTimeMs;}public void setProcessingTimeMs(Long processingTimeMs) {this.processingTimeMs = processingTimeMs;}@Overridepublic String toString() {return "AiResponse{" +"content='" + content + '\'' +", success=" + success +", errorMessage='" + errorMessage + '\'' +", timestamp=" + timestamp +", model='" + model + '\'' +", tokensUsed=" + tokensUsed +", processingTimeMs=" + processingTimeMs +'}';}
} 

(5)Spring Boot與Spring AI集成DeepSeek的主應用類(DeepSeekApplication.java

package com.example.demo;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;/*** Spring Boot與Spring AI集成DeepSeek的主應用類* * @author Spring AI Demo* @version 1.0.0*/
@SpringBootApplication
public class DeepSeekApplication {public static void main(String[] args) {SpringApplication.run(DeepSeekApplication.class, args);}/*** 應用啟動完成后的事件處理*/@EventListener(ApplicationReadyEvent.class)public void onApplicationReady() {System.out.println("\n" +"=================================================================\n" +"🚀 Spring AI DeepSeek 演示應用啟動成功!\n" +"=================================================================\n" +"📖 API文檔地址:\n" +"   ? 測試頁面:POST http://localhost:8080\n" +"   ? 營銷文案生成:POST http://localhost:8080/api/ai/marketing\n" +"   ? 代碼生成:    POST http://localhost:8080/api/ai/code\n" +"   ? 智能問答:    POST http://localhost:8080/api/ai/qa\n" +"   ? 聊天對話:    POST http://localhost:8080/api/ai/chat\n" +"   ? 流式聊天:    GET  http://localhost:8080/api/stream/chat?message=你好\n" +"=================================================================\n" +"💡 使用提示:\n" +"   1. 請確保在application.yml中配置了有效的DeepSeek API密鑰\n" +"   2. 或者設置環境變量:DEEPSEEK_API_KEY=your-api-key\n" +"   3. 訪問 http://localhost:8080/actuator/health 檢查應用健康狀態\n" +"=================================================================\n");}
} 

(5)前段展示頁面(index.html

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Spring AI DeepSeek 演示</title><style>* {margin: 0;padding: 0;box-sizing: border-box;}body {font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);min-height: 100vh;padding: 20px;}.container {max-width: 1200px;margin: 0 auto;background: white;border-radius: 15px;box-shadow: 0 20px 40px rgba(0,0,0,0.1);overflow: hidden;}.header {background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);color: white;padding: 30px;text-align: center;}.header h1 {font-size: 2.5em;margin-bottom: 10px;}.header p {font-size: 1.2em;opacity: 0.9;}.main-content {padding: 30px;}.api-section {padding: 0;}.api-title {font-size: 1.5em;color: #333;margin-bottom: 15px;display: flex;align-items: center;}.api-title::before {content: "🚀";margin-right: 10px;font-size: 1.2em;}.stream-section {background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);color: white;padding: 30px;border-radius: 15px;box-shadow: 0 10px 30px rgba(102, 126, 234, 0.3);}.stream-section .api-title {color: white;font-size: 1.8em;margin-bottom: 20px;}.stream-section .api-title::before {content: "🌊";}.input-group {margin-bottom: 20px;}.input-group label {display: block;margin-bottom: 8px;font-weight: 600;color: #555;}.stream-section .input-group label {color: white;}.input-group textarea,.input-group input {width: 100%;padding: 12px;border: 2px solid #e0e0e0;border-radius: 8px;font-size: 14px;transition: border-color 0.3s ease;}.input-group textarea:focus,.input-group input:focus {outline: none;border-color: #4facfe;}.input-group textarea {min-height: 100px;resize: vertical;}.btn {background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);color: white;border: none;padding: 12px 25px;border-radius: 8px;cursor: pointer;font-size: 16px;font-weight: 600;transition: all 0.3s ease;margin-right: 10px;margin-bottom: 10px;}.btn:hover {transform: translateY(-2px);box-shadow: 0 5px 15px rgba(79, 172, 254, 0.3);}.btn:disabled {opacity: 0.6;cursor: not-allowed;transform: none;}.btn-danger {background: linear-gradient(135deg, #ff6b6b 0%, #ee5a52 100%);}.btn-success {background: linear-gradient(135deg, #51cf66 0%, #40c057 100%);}.response-area {margin-top: 20px;padding: 20px;background: #f8f9fa;border-radius: 8px;border-left: 4px solid #4facfe;min-height: 100px;white-space: pre-wrap;font-family: 'Courier New', monospace;font-size: 14px;line-height: 1.5;}.loading {display: none;text-align: center;padding: 20px;color: #666;}.loading::after {content: "";display: inline-block;width: 20px;height: 20px;border: 3px solid #f3f3f3;border-top: 3px solid #4facfe;border-radius: 50%;animation: spin 1s linear infinite;margin-left: 10px;}@keyframes spin {0% { transform: rotate(0deg); }100% { transform: rotate(360deg); }}.stream-output {background: #1a202c;color: #e2e8f0;padding: 25px;border-radius: 12px;min-height: 300px;font-family: 'Courier New', monospace;font-size: 15px;line-height: 1.8;overflow-y: auto;max-height: 500px;border: 2px solid rgba(255,255,255,0.1);position: relative;}.stream-output::-webkit-scrollbar {width: 8px;}.stream-output::-webkit-scrollbar-track {background: #2d3748;border-radius: 4px;}.stream-output::-webkit-scrollbar-thumb {background: #4a5568;border-radius: 4px;}.stream-output::-webkit-scrollbar-thumb:hover {background: #718096;}.stream-status {position: absolute;top: 10px;right: 15px;padding: 5px 10px;background: rgba(0,0,0,0.3);border-radius: 15px;font-size: 12px;color: #a0aec0;}.stream-status.connecting {color: #fbb6ce;}.stream-status.streaming {color: #9ae6b4;animation: pulse 2s infinite;}.stream-status.completed {color: #90cdf4;}.stream-status.error {color: #feb2b2;}@keyframes pulse {0%, 100% { opacity: 1; }50% { opacity: 0.5; }}.stream-controls {display: flex;gap: 10px;flex-wrap: wrap;margin-top: 15px;}.footer {background: #f8f9fa;padding: 20px;text-align: center;color: #666;border-top: 1px solid #e0e0e0;}.tab-container {background: white;border-radius: 15px;overflow: hidden;box-shadow: 0 5px 15px rgba(0,0,0,0.1);}.tab-nav {display: flex;background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);border-bottom: 2px solid #e0e0e0;overflow-x: auto;}.tab-btn {flex: 1;min-width: 150px;padding: 15px 20px;border: none;background: transparent;color: #666;font-size: 14px;font-weight: 600;cursor: pointer;transition: all 0.3s ease;border-bottom: 3px solid transparent;white-space: nowrap;}.tab-btn:hover {background: rgba(79, 172, 254, 0.1);color: #4facfe;}.tab-btn.active {background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);color: white;border-bottom-color: #0066cc;}.tab-content {display: none;padding: 30px;min-height: 500px;}.tab-content.active {display: block;}.typing-indicator {display: inline-block;color: #9ae6b4;}.typing-indicator::after {content: '|';animation: blink 1s infinite;}@keyframes blink {0%, 50% { opacity: 1; }51%, 100% { opacity: 0; }}.stream-message {margin-bottom: 15px;padding: 10px 0;border-bottom: 1px solid rgba(255,255,255,0.1);}.stream-message:last-child {border-bottom: none;}.message-timestamp {color: #a0aec0;font-size: 12px;margin-bottom: 5px;}.message-content {color: #e2e8f0;line-height: 1.6;}</style>
</head>
<body><div class="container"><div class="header"><h1>🤖 Spring AI DeepSeek 演示</h1><p>智能文本生成系統 - 營銷文案、代碼生成、智能問答、聊天對話</p></div><div class="main-content"><!-- Tab導航 --><div class="tab-container"><div class="tab-nav"><button class="tab-btn active" onclick="switchTab('stream')">🌊 實時流式聊天</button><button class="tab-btn" onclick="switchTab('marketing')">📝 營銷文案生成</button><button class="tab-btn" onclick="switchTab('code')">💻 代碼生成</button><button class="tab-btn" onclick="switchTab('qa')">? 智能問答</button><button class="tab-btn" onclick="switchTab('chat')">💬 聊天對話</button></div><!-- 實時流式聊天演示 --><div id="stream-tab" class="tab-content active"><div class="stream-section"><div class="api-title">實時流式聊天演示</div><p style="margin-bottom: 20px; opacity: 0.9;">體驗AI實時生成文本的魅力,支持打字機效果和流式響應</p><div class="input-group"><label for="stream-input">💬 輸入您的消息:</label><textarea id="stream-input" placeholder="例如:講一個有趣的科幻故事,或者解釋一下量子計算的原理" style="background: rgba(255,255,255,0.95); color: #333;"></textarea></div><div class="stream-controls"><button class="btn btn-success" onclick="startStream()">🚀 開始流式對話</button><button class="btn" onclick="pauseStream()" id="pauseBtn" disabled>?? 暫停</button><button class="btn btn-danger" onclick="stopStream()">?? 停止</button><button class="btn" onclick="clearStream()">🗑? 清空</button><button class="btn" onclick="saveStream()">💾 保存對話</button><button class="btn" onclick="testStreamEndpoint()" style="background: #ffa726;">🔧 測試端點</button></div><div class="stream-output" id="stream-output"><div class="stream-status" id="stream-status">等待開始...</div><div id="stream-content"><div class="message-content">🌟 歡迎使用流式聊天演示!<br><br>? 特色功能:<br>? 實時流式響應,逐字顯示<br>? 支持暫停/繼續/停止控制<br>? 自動滾動到最新內容<br>? 對話歷史保存<br><br>💡 請在上方輸入框中輸入您的問題,然后點擊"開始流式對話"按鈕開始體驗!</div></div></div></div></div><!-- 營銷文案生成 --><div id="marketing-tab" class="tab-content"><div class="api-section"><div class="api-title">營銷文案生成</div><div class="input-group"><label for="marketing-input">產品描述或需求:</label><textarea id="marketing-input" placeholder="例如:為智能手表的心率監測功能生成營銷文案"></textarea></div><div class="input-group"><label for="marketing-temp">創意度 (0.0-2.0):</label><input type="number" id="marketing-temp" value="1.2" min="0" max="2" step="0.1"></div><button class="btn" onclick="generateMarketing()">生成營銷文案</button><div class="loading" id="marketing-loading">生成中...</div><div class="response-area" id="marketing-response">點擊按鈕開始生成營銷文案...</div></div></div><!-- 代碼生成 --><div id="code-tab" class="tab-content"><div class="api-section"><div class="api-title">代碼生成</div><div class="input-group"><label for="code-input">編程需求:</label><textarea id="code-input" placeholder="例如:用Java實現一個簡單的計算器類"></textarea></div><div class="input-group"><label for="code-temp">精確度 (0.0-1.0):</label><input type="number" id="code-temp" value="0.1" min="0" max="1" step="0.1"></div><button class="btn" onclick="generateCode()">生成代碼</button><div class="loading" id="code-loading">生成中...</div><div class="response-area" id="code-response">點擊按鈕開始生成代碼...</div></div></div><!-- 智能問答 --><div id="qa-tab" class="tab-content"><div class="api-section"><div class="api-title">智能問答</div><div class="input-group"><label for="qa-input">您的問題:</label><textarea id="qa-input" placeholder="例如:什么是Spring Boot的自動配置原理?"></textarea></div><button class="btn" onclick="answerQuestion()">獲取答案</button><div class="loading" id="qa-loading">思考中...</div><div class="response-area" id="qa-response">輸入問題獲取智能回答...</div></div></div><!-- 聊天對話 --><div id="chat-tab" class="tab-content"><div class="api-section"><div class="api-title">聊天對話</div><div class="input-group"><label for="chat-input">聊天消息:</label><textarea id="chat-input" placeholder="例如:你好,今天天氣怎么樣?"></textarea></div><button class="btn" onclick="chat()">發送消息</button><div class="loading" id="chat-loading">回復中...</div><div class="response-area" id="chat-response">開始與AI聊天...</div></div></div></div></div><div class="footer"><p>🚀 Spring AI + DeepSeek 智能文本生成演示 | 版本 1.0.1</p><p>💡 提示:請確保已配置有效的DeepSeek API密鑰</p></div></div><script>// 全局變量let currentEventSource = null;let isPaused = false;let streamBuffer = '';let conversationHistory = [];// Tab切換功能function switchTab(tabName) {// 隱藏所有tab內容const allTabs = document.querySelectorAll('.tab-content');allTabs.forEach(tab => tab.classList.remove('active'));// 移除所有tab按鈕的active狀態const allBtns = document.querySelectorAll('.tab-btn');allBtns.forEach(btn => btn.classList.remove('active'));// 顯示選中的tab內容document.getElementById(tabName + '-tab').classList.add('active');// 激活對應的tab按鈕event.target.classList.add('active');console.log(`切換到 ${tabName} 標簽頁`);}// 通用API調用函數async function callAPI(endpoint, data, loadingId, responseId) {const loading = document.getElementById(loadingId);const response = document.getElementById(responseId);loading.style.display = 'block';response.textContent = '處理中...';try {const result = await fetch(endpoint, {method: 'POST',headers: {'Content-Type': 'application/json',},body: JSON.stringify(data)});const jsonResponse = await result.json();if (jsonResponse.success) {response.textContent = jsonResponse.content;} else {response.textContent = `錯誤: ${jsonResponse.errorMessage || '請求失敗'}`;}} catch (error) {response.textContent = `網絡錯誤: ${error.message}`;} finally {loading.style.display = 'none';}}// 營銷文案生成function generateMarketing() {const content = document.getElementById('marketing-input').value;const temperature = parseFloat(document.getElementById('marketing-temp').value);if (!content.trim()) {alert('請輸入產品描述或需求');return;}callAPI('/api/ai/marketing', {content: content,temperature: temperature,maxTokens: 800}, 'marketing-loading', 'marketing-response');}// 代碼生成function generateCode() {const content = document.getElementById('code-input').value;const temperature = parseFloat(document.getElementById('code-temp').value);if (!content.trim()) {alert('請輸入編程需求');return;}callAPI('/api/ai/code', {content: content,temperature: temperature,maxTokens: 1500}, 'code-loading', 'code-response');}// 智能問答function answerQuestion() {const content = document.getElementById('qa-input').value;if (!content.trim()) {alert('請輸入您的問題');return;}callAPI('/api/ai/qa', {content: content,temperature: 0.7,maxTokens: 1000}, 'qa-loading', 'qa-response');}// 聊天對話function chat() {const content = document.getElementById('chat-input').value;if (!content.trim()) {alert('請輸入聊天消息');return;}callAPI('/api/ai/chat', {content: content,temperature: 0.9,maxTokens: 800}, 'chat-loading', 'chat-response');}// 更新流式狀態function updateStreamStatus(status, message) {const statusElement = document.getElementById('stream-status');statusElement.className = `stream-status ${status}`;statusElement.textContent = message;}// 添加消息到流式輸出function addStreamMessage(content, isUser = false) {const streamContent = document.getElementById('stream-content');const timestamp = new Date().toLocaleTimeString();const messageDiv = document.createElement('div');messageDiv.className = 'stream-message';messageDiv.innerHTML = `<div class="message-timestamp">${timestamp} ${isUser ? '👤 您' : '🤖 AI'}</div><div class="message-content">${content}</div>`;streamContent.appendChild(messageDiv);// 滾動到底部const output = document.getElementById('stream-output');output.scrollTop = output.scrollHeight;}// 流式聊天function startStream() {const message = document.getElementById('stream-input').value;if (!message.trim()) {alert('請輸入流式消息');return;}// 停止之前的連接if (currentEventSource) {currentEventSource.close();}// 添加用戶消息addStreamMessage(message, true);// 清空輸入框document.getElementById('stream-input').value = '';// 重置狀態isPaused = false;streamBuffer = '';// 更新狀態和按鈕updateStreamStatus('connecting', '連接中...');document.querySelector('button[onclick="startStream()"]').disabled = true;document.getElementById('pauseBtn').disabled = false;// 創建新的EventSource連接const encodedMessage = encodeURIComponent(message);const streamUrl = `/api/stream/chat-fixed?message=${encodedMessage}`;console.log('連接流式端點:', streamUrl);currentEventSource = new EventSource(streamUrl);// 添加AI響應容器const aiMessageDiv = document.createElement('div');aiMessageDiv.className = 'stream-message';aiMessageDiv.innerHTML = `<div class="message-timestamp">${new Date().toLocaleTimeString()} 🤖 AI</div><div class="message-content"><span class="typing-indicator"></span></div>`;document.getElementById('stream-content').appendChild(aiMessageDiv);const aiContentDiv = aiMessageDiv.querySelector('.message-content');currentEventSource.onopen = function() {console.log('SSE連接已建立');updateStreamStatus('streaming', '正在接收...');};currentEventSource.onmessage = function(event) {if (isPaused) return;console.log('收到SSE數據:', event.data);// 檢查是否是完成信號if (event.data === '[DONE]') {console.log('流式響應完成');updateStreamStatus('completed', '完成');// 移除打字指示器const typingIndicator = aiContentDiv.querySelector('.typing-indicator');if (typingIndicator) {typingIndicator.remove();}// 保存到歷史記錄conversationHistory.push({user: message,ai: streamBuffer,timestamp: new Date().toISOString()});// 清理連接currentEventSource.close();currentEventSource = null;document.querySelector('button[onclick="startStream()"]').disabled = false;document.getElementById('pauseBtn').disabled = true;return;}// 檢查是否是錯誤信號if (event.data.startsWith('[ERROR]')) {console.log('流式響應錯誤:', event.data);updateStreamStatus('error', '錯誤');const errorMsg = event.data.replace('[ERROR]', '').trim();aiContentDiv.innerHTML = `? ${errorMsg || '流式響應出現錯誤'}`;// 清理連接currentEventSource.close();currentEventSource = null;document.querySelector('button[onclick="startStream()"]').disabled = false;document.getElementById('pauseBtn').disabled = true;return;}// 處理正常的流式數據if (event.data && event.data.trim() !== '') {console.log('處理流式數據塊:', event.data);// 累積響應內容streamBuffer += event.data;// 移除打字指示器并更新內容const typingIndicator = aiContentDiv.querySelector('.typing-indicator');if (typingIndicator) {typingIndicator.remove();}// 轉義HTML內容并保持換行const escapedContent = streamBuffer.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;').replace(/\n/g, '<br>');aiContentDiv.innerHTML = escapedContent + '<span class="typing-indicator"></span>';// 滾動到底部const output = document.getElementById('stream-output');output.scrollTop = output.scrollHeight;}};currentEventSource.onerror = function(event) {console.error('SSE連接錯誤:', event);// 如果連接已經被正常關閉,不處理錯誤if (!currentEventSource) {console.log('連接已正常關閉,忽略錯誤事件');return;}console.log('連接狀態:', currentEventSource.readyState);updateStreamStatus('error', '連接錯誤');// 檢查連接狀態if (currentEventSource.readyState === EventSource.CONNECTING) {aiContentDiv.innerHTML = '? 正在重新連接...';} else if (currentEventSource.readyState === EventSource.CLOSED) {aiContentDiv.innerHTML = '? 連接已關閉,請檢查網絡或API配置';} else {aiContentDiv.innerHTML = '? 連接錯誤,請檢查服務器狀態';}// 清理連接if (currentEventSource) {currentEventSource.close();currentEventSource = null;}// 重置按鈕狀態document.querySelector('button[onclick="startStream()"]').disabled = false;document.getElementById('pauseBtn').disabled = true;};}// 暫停/繼續流式響應function pauseStream() {const pauseBtn = document.getElementById('pauseBtn');if (isPaused) {isPaused = false;pauseBtn.textContent = '?? 暫停';updateStreamStatus('streaming', '繼續接收...');} else {isPaused = true;pauseBtn.textContent = '?? 繼續';updateStreamStatus('paused', '已暫停');}}// 停止流式響應function stopStream() {if (currentEventSource) {currentEventSource.close();currentEventSource = null;}updateStreamStatus('completed', '已停止');document.querySelector('button[onclick="startStream()"]').disabled = false;document.getElementById('pauseBtn').disabled = true;isPaused = false;document.getElementById('pauseBtn').textContent = '?? 暫停';}// 清空流式輸出function clearStream() {document.getElementById('stream-content').innerHTML = `<div class="message-content">🌟 歡迎使用流式聊天演示!<br><br>? 特色功能:<br>? 實時流式響應,逐字顯示<br>? 支持暫停/繼續/停止控制<br>? 自動滾動到最新內容<br>? 對話歷史保存<br><br>💡 請在上方輸入框中輸入您的問題,然后點擊"開始流式對話"按鈕開始體驗!</div>`;updateStreamStatus('ready', '等待開始...');streamBuffer = '';}// 保存對話歷史function saveStream() {if (conversationHistory.length === 0) {alert('暫無對話歷史可保存');return;}const content = conversationHistory.map(item => `時間: ${new Date(item.timestamp).toLocaleString()}\n用戶: ${item.user}\nAI: ${item.ai}\n${'='.repeat(50)}\n`).join('\n');const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });const url = URL.createObjectURL(blob);const a = document.createElement('a');a.href = url;a.download = `AI對話歷史_${new Date().toISOString().slice(0,10)}.txt`;document.body.appendChild(a);a.click();document.body.removeChild(a);URL.revokeObjectURL(url);alert('對話歷史已保存到文件');}// 測試流式端點async function testStreamEndpoint() {updateStreamStatus('connecting', '測試中...');try {// 測試基礎健康檢查console.log('測試基礎健康檢查...');const healthResponse = await fetch('/api/ai/health');const healthText = await healthResponse.text();console.log('健康檢查結果:', healthText);// 測試流式信息端點console.log('測試流式信息端點...');const infoResponse = await fetch('/api/stream/info');const infoData = await infoResponse.json();console.log('流式信息:', infoData);// 測試流式健康檢查console.log('測試流式健康檢查...');const streamHealthResponse = await fetch('/api/stream/health');const streamHealthText = await streamHealthResponse.text();console.log('流式健康檢查結果:', streamHealthText);// 顯示測試結果const output = document.getElementById('stream-content');output.innerHTML = `<div class="message-content">🔧 端點測試結果:<br><br>? 基礎健康檢查: ${healthText}<br><br>? 流式信息端點: 正常<br>? 描述: ${infoData.description}<br>? 可用端點: ${infoData.endpoints.length} 個<br><br>? 流式健康檢查: 正常<br>? 響應長度: ${streamHealthText.length} 字符<br><br>💡 所有端點測試通過,流式聊天應該可以正常工作!</div>`;updateStreamStatus('completed', '測試完成');} catch (error) {console.error('端點測試失敗:', error);const output = document.getElementById('stream-content');output.innerHTML = `<div class="message-content">? 端點測試失敗:<br><br>錯誤信息: ${error.message}<br><br>💡 可能的原因:<br>? 應用未完全啟動<br>? API密鑰未正確配置<br>? 網絡連接問題<br>? 服務器內部錯誤<br><br>🔧 建議解決方案:<br>1. 檢查控制臺日志<br>2. 運行 test-stream-endpoint.bat<br>3. 確認API密鑰配置<br>4. 重啟應用</div>`;updateStreamStatus('error', '測試失敗');}}// 頁面加載完成后的初始化document.addEventListener('DOMContentLoaded', function() {console.log('🚀 Spring AI DeepSeek 演示頁面加載完成');// 檢查服務狀態fetch('/api/ai/health').then(response => response.text()).then(data => {console.log('? 服務狀態:', data);updateStreamStatus('ready', '服務就緒');}).catch(error => {console.warn('?? 服務檢查失敗:', error);updateStreamStatus('error', '服務異常');});// 添加鍵盤快捷鍵document.getElementById('stream-input').addEventListener('keydown', function(e) {if (e.ctrlKey && e.key === 'Enter') {startStream();}});});</script>
</body>
</html> 

3. 預覽(http://localhost:8080/index.html)

四、核心機制解析:從自動裝配到接口設計

1. Spring AI 自動裝配原理

當引入spring-boot-starter-ai-deepseek后,Spring Boot 會自動加載以下組件:

  1. DeepSeekProperties 配置類
    讀取application.yml中以spring.ai.deepseek開頭的配置,轉換為可注入的DeepSeekProperties?Bean

  2. DeepSeekChatCompletionService 客戶端
    基于配置信息創建 HTTP 客戶端,支持:

    1. 連接池管理(默認最大連接數 100)
    2. 請求簽名自動生成(針對 DeepSeek API 認證機制)
    3. 響應反序列化(將 JSON 響應轉為 Java 對象)
  3. 錯誤處理 Advice
    自動捕獲DeepSeekApiException,轉換為 Spring MVC 可處理的ResponseEntity,包含:

    • 401 Unauthorized(API 密鑰錯誤)
    • 429 Too Many Requests(速率限制處理)
    • 500 Internal Server Error(模型服務異常)

五、總結

通過本文實踐,您已掌握:

  1. Spring AI 與 DeepSeek 的工程化集成方法
  2. 文本生成的同步 / 流式兩種實現方式
  3. 自動裝配機制與核心接口設計原理

后續可探索的方向:

  • 多模型管理:通過@Primary注解實現模型切換,支持 A/B 測試
  • 上下文管理:維護對話歷史(List<ChatMessage>),實現多輪對話
  • 插件擴展:自定義請求攔截器(添加業務參數)或響應處理器(數據清洗)

????????Spring AI 與 DeepSeek 的組合,為企業級 AI 應用開發提供了穩定高效的工程化解決方案。隨著更多國產化模型的接入,這一生態將持續釋放 AI 與傳統業務融合的巨大潛力。立即嘗試在您的項目中引入這套方案,開啟智能開發新征程!

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

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

相關文章

[Windows] 摸魚小工具:隱藏軟件(重制版)

由吾愛大神寫的摸魚工具&#xff1a; 數據存放路徑為C:\Users\用戶名\AppData\Local\HideSoft&#xff0c;如果不想用時&#xff0c;刪除軟件及此路徑下的HideSoft文件夾。如添加了開機啟動&#xff0c;刪除啟動菜單文件夾的快捷方式即可&#xff0c;或者刪除前在軟件中取消設置…

C++ 判斷文件的編碼類型

大多數文本編輯器&#xff0c;都會在文本文件的頭部插入一部分特殊的字節&#xff0c;用于輔助文本編輯器來判斷該文件的字符集編碼類型。 如&#xff1a;記事本 目前支持的字符集類型&#xff0c;通常為三種&#xff1a; Unicode、UTF8、UnicodeBIG、CP_ACP&#xff08;默認…

時間序列噪聲模型分析軟件推薦與使用經驗

最近在論文大修2024年投稿的一篇文章&#xff0c;大修了2輪&#xff0c;最后一次還是重新投稿&#xff0c;其中有一個問題一直被審稿人懟&#xff0c;他認為我計算時間序列的趨勢的時候&#xff0c;沒有考慮時間的相關性&#xff0c;即對噪聲模型的估計不合理&#xff0c;會影響…

【redis實戰篇】第六天

摘要&#xff1a; 本文介紹了基于Redis的秒殺系統優化方案&#xff0c;主要包含兩部分&#xff1a;1&#xff09;通過Lua腳本校驗用戶秒殺資格&#xff0c;結合Java異步處理訂單提升性能&#xff1b;2&#xff09;使用Redis Stream實現消息隊列處理訂單。方案采用Lua腳本保證庫…

【Java Web】速通HTML

參考筆記: JavaWeb 速通HTML_java html頁面-CSDN博客 目錄 一、前言 1.網頁組成 1 結構 2 表現 3 行為 2.HTML入門 1 基本介紹 2 基本結構 3. HTML標簽 1 基本說明 2 注意事項 4. HTML概念名詞解釋 二、HTML常用標簽匯總 + 案例演示 1. 字體標簽 font (1)定義 (2)案例 2…

Oracle/openGauss中,DATE/TIMESTAMP與數字日期/字符日期比較

ORACLE 運行環境 openGauss 運行環境 0、前置知識 ORACLE&#xff1a;DUMP()函數用于返回指定表達式的數據類型、字節長度及內部存儲表示的詳細信息 SELECT DUMP(123) FROM DUAL; -- Typ2 Len3: 194,2,24 SELECT DUMP(123) FROM DUAL;-- Typ96 Len3: 49,50,51 -- ASCII值&am…

[學習]C++ 模板探討(代碼示例)

C 模板探討 文章目錄 C 模板探討一、模板基礎概念二、函數模板三、類模板1. 類模板的定義與使用2. 成員函數模板3. 類模板的靜態成員與繼承 四、模板進階特性1. 非類型模板參數2. 可變參數模板&#xff08;Variadic Templates&#xff09;3. 模板元編程&#xff08;TMP&#xf…

人工智能-訓練AI模型涉及多個步驟

訓練AI模型涉及多個步驟&#xff0c;包括數據預處理、選擇合適的模型、訓練模型以及評估模型性能。下面是一個詳細的流程&#xff0c;以常見的機器學習任務——分類問題為例&#xff0c;展示如何使用Python中的scikit-learn庫來訓練一個簡單的AI模型。 步驟 1: 導入所需的庫 …

LVS+Keepalived 高可用

目錄 一、核心概念 1. LVS&#xff08;Linux Virtual Server&#xff09; 2. Keepalived 二、高可用架構設計 1. 架構拓撲圖 2. 工作流程 三、部署步驟&#xff08;以 DR 模式為例&#xff09; 1. 環境準備 2. 主 LVS 節點配置 &#xff08;1&#xff09;安裝 Keepali…

TCP 三次握手過程詳解

TCP 三次握手過程詳解 一、TCP握手基礎概念 1.1 什么是TCP握手 TCP三次握手是傳輸控制協議(Transmission Control Protocol)在建立連接時的標準過程,目的是確保通信雙方具備可靠的雙向通信能力。 關鍵結論:三次握手的本質是通過序列號同步和能力協商建立可靠的邏輯連接。 …

李宏毅NLP-7-CTC/RNN-T文本對齊

LAS LAS&#xff08;Listen, Attend and Spell &#xff09;模型&#xff0c;在語音識別中的解碼和訓練過程&#xff0c;具體內容如下&#xff1a; 解碼&#xff08;Decoding&#xff09; 公式 Y ? arg ? max ? Y log ? P ( Y ∣ X ) Y^* \arg\max_Y \log P(Y|X) Y?ar…

jQuery和CSS3卡片列表布局特效

這是一款jQuery和CSS3卡片列表布局特效。該卡片布局使用owl.carousel.js來制作輪播效果&#xff0c;使用簡單的css代碼來制作卡片布局&#xff0c;整體效果時尚大方。 預覽 下載 使用方法 在頁面最后引入jquery和owl.carousel.js相關文件。 <link rel"stylesheet&qu…

Microsoft 推出 Magentic-UI,多智能體引領網頁人機協作變革

當前&#xff0c;現代生產力與網頁操作緊密相連&#xff0c;信息檢索、表單填寫、儀表盤導航等網頁任務已成為工作流程的重要環節。然而&#xff0c;大量網頁任務仍依賴人工重復操作&#xff0c;效率低下且易出錯。與此同時&#xff0c;許多 AI 智能體雖追求自主運行&#xff0…

2023年6級第一套長篇閱讀

畫名詞概念&#xff0c;動詞概念 多處定位原詞加同義改寫 畫關鍵詞&#xff0c;多處定位直接就可以選A了 沒有定位的句子先比沒匹配到的段落&#xff0c;再匹配長的段落先易后難

登山第二十三梯:有序點云平面快速分割——35Hz幀速前進

文章目錄 一 摘要 二 資源 三 內容 一 摘要 3D 點云中的實時平面提取對于許多機器人應用至關重要。作者提出了一種新穎的算法&#xff0c;用于在從 Kinect 傳感器等設備獲得的有組織的點云中實時可靠地檢測多個平面。通過在圖像空間中將這樣的點云均勻地劃分為不重疊的點組&…

【北京盈達科技】GEO優化:引領AI時代內容霸權,重塑行業生態

盈達科技GEO優化&#xff1a;引領AI時代內容霸權&#xff0c;重塑行業生態 在人工智能飛速發展的今天&#xff0c;生成式AI已經深刻改變了人們獲取信息的方式。從ChatGPT到文心一言&#xff0c;再到各種智能問答系統&#xff0c;AI生成的內容正在成為信息傳播的新主流。然而&a…

安卓端智能耗材柜系統可行性方案(基于uniapp + Vue3)

一、系統架構設計 1. 技術棧&#xff1a; 前端框架&#xff1a;uniapp Vue3 TypeScript狀態管理&#xff1a;Pinia&#xff08;分層設計&#xff0c;模塊化Store&#xff09;硬件交互&#xff1a;Android原生插件&#xff08;Java/Kotlin封裝&#xff09;通信協議&#xff…

Java交互協議詳解:深入探索通信機制

解析Java中各類交互協議的設計原理與實戰應用&#xff0c;涵蓋TCP/UDP自定義協議、HTTP/RESTful、WebSocket、RPC等主流方案。 一、交互協議核心概念 交互協議是系統間通信的規則集合&#xff0c;包含&#xff1a; 消息格式&#xff1a;數據序列化方式&#xff08;JSON/XML/P…

k8s上運行的mysql、mariadb數據庫的備份記錄

文章目錄 前言一、獲取需要備份的數據庫的信息二、備份步驟1.準備工作2.手動備份3.定時任務自動備份 總結 前言 記錄一下在k8s運行的數據庫的備份步驟。 我的思路是新建一個數據庫的容器作為工具容器&#xff0c;通過工具容器執行mysqldump命令進行備份&#xff0c;最后通過定…

寶塔面板部署python web項目詳細教程

最近在學langchain&#xff0c;寫了一個小案例出來&#xff0c;我剛好有一臺服務器&#xff0c;就嘗試自己部署一下項目&#xff0c;結果很幸運一遍過&#xff0c;現在記錄一下。我的系統是OpenCloudOS 9 目錄 1.安裝python解釋器版本 2.上傳項目文件到寶塔面板 3.添加項目…