SpringBoot 3.4.2 + DeepSeek API 实战:5分钟搞定AI聊天机器人(附完整代码)

SpringBoot整合LangChain4j实现高效流式对话响应 本文详细介绍了如何使用SpringBoot整合LangChain4j框架实现高效的流式对话响应。通过配置Ollama本地模型服务,结合Spring WebFlux的Flux响应式编程,构建支持SSE协议的后端API,显著降低响应延迟并提升用户体验。文章涵盖环境搭建、核心代码实现、性能优化及多模型服务切换等实战内容。 阅读详情

SpringBoot 3.4.2 实战:5分钟构建企业级DeepSeek AI对话服务

最近在重构公司的一个智能客服系统时,我面临一个挑战:如何在保持现有技术栈不变的情况下,快速集成一个性能优秀且成本可控的AI对话能力。经过多方对比,我选择了DeepSeek作为底层模型,结合SpringBoot 3.4.2的现代化特性,仅用不到一周时间就完成了从零到一的完整集成。今天我想分享这个过程中的核心实践,特别是那些在官方文档中不会提到的"坑"和解决方案。

1. 环境准备与项目初始化

在开始之前,确保你的开发环境满足以下要求:

  • JDK 17+:SpringBoot 3.x系列强制要求JDK 17及以上版本
  • Maven 3.6+ 或 Gradle 7.x+:推荐使用最新稳定版
  • IDE支持:IntelliJ IDEA 2023.3+ 或 VS Code with Java扩展

1.1 创建SpringBoot项目

使用Spring Initializr快速创建项目骨架:

curl https://start.spring.io/starter.zip \
  -d type=maven-project \
  -d language=java \
  -d bootVersion=3.4.2 \
  -d baseDir=deepseek-ai-demo \
  -d groupId=com.example \
  -d artifactId=deepseek-ai-demo \
  -d name=DeepSeekAIDemo \
  -d description="SpringBoot集成DeepSeek AI示例" \
  -d packageName=com.example.deepseek \
  -d packaging=jar \
  -d javaVersion=17 \
  -d dependencies=web \
  -o deepseek-ai-demo.zip

解压后,我们需要手动添加Spring AI相关依赖。这里有个关键点:Spring AI目前仍处于快速迭代阶段,版本选择至关重要。

1.2 依赖配置策略

在pom.xml中添加以下依赖配置:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <!-- 其他配置保持不变 -->
    
    <properties>
        <spring-ai.version>1.0.0-M6</spring-ai.version>
    </properties>
    
    <dependencies>
        <!-- Spring Boot Web Starter -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <!-- Spring AI OpenAI Starter (用于DeepSeek兼容) -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
            <version>${spring-ai.version}</version>
        </dependency>
        
        <!-- 可选:用于流式响应 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
        
        <!-- 开发工具 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        
        <!-- 测试依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    
    <!-- 添加Spring Milestone仓库 -->
    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>
</project>

注意:Spring AI 1.0.0-M6是目前相对稳定的里程碑版本,但需要注意它仍处于开发阶段。生产环境建议等待GA版本发布。

2. DeepSeek API配置与认证

DeepSeek提供了与OpenAI API兼容的接口,这使得集成变得异常简单。但魔鬼藏在细节中,正确的配置能避免很多潜在问题。

2.1 获取API密钥

首先访问DeepSeek开放平台(platform.deepseek.com)注册账号并创建API Key。建议创建时注意以下几点:

  1. 环境隔离:为开发、测试、生产环境分别创建不同的API Key
  2. 权限控制:根据实际需求设置适当的权限范围
  3. 额度监控:设置使用额度告警,避免意外费用

2.2 配置文件详解

创建application.yml配置文件,这是整个集成的核心:

server:
  port: 8080
  servlet:
    context-path: /api

spring:
  application:
    name: deepseek-ai-service
  
  ai:
    openai:
      # DeepSeek API配置
      api-key: ${DEEPSEEK_API_KEY:sk-your-api-key-here}
      base-url: https://api.deepseek.com
      
      # 聊天模型配置
      chat:
        options:
          model: deepseek-chat  # 或 deepseek-reasoner
          temperature: 0.7
          max-tokens: 2000
          top-p: 0.9
          frequency-penalty: 0.0
          presence-penalty: 0.0
          stream: false  # 是否启用流式响应
      
      # 嵌入模型配置(DeepSeek暂不支持,需禁用)
      embedding:
        enabled: false
      
      # HTTP客户端配置
      client:
        connect-timeout: 30s
        read-timeout: 60s
        
      # 重试策略
      retry:
        max-attempts: 3
        backoff:
          initial-interval: 1s
          multiplier: 2.0
          max-interval: 10s

# 自定义配置
deepseek:
  api:
    # 模型选择建议
    models:
      chat: deepseek-chat      # 通用对话场景
      reasoner: deepseek-reasoner  # 复杂推理场景
    
    # 业务相关配置
    business:
      max-retries: 3
      timeout-seconds: 30
      enable-logging: true

提示:在实际项目中,建议将敏感信息如API Key存储在环境变量或配置中心,避免硬编码在配置文件中。

2.3 环境变量配置

创建.env文件(不要提交到版本控制):

# DeepSeek API配置
DEEPSEEK_API_KEY=sk-your-actual-api-key
DEEPSEEK_BASE_URL=https://api.deepseek.com

# 应用配置
SERVER_PORT=8080
LOG_LEVEL=INFO

然后在application.yml中使用环境变量:

spring:
  ai:
    openai:
      api-key: ${DEEPSEEK_API_KEY}
      base-url: ${DEEPSEEK_BASE_URL:https://api.deepseek.com}

3. 核心服务层设计与实现

3.1 请求响应模型设计

首先定义清晰的数据模型,这是保证代码可维护性的基础:

package com.example.deepseek.model;

import lombok.Data;
import lombok.Builder;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import com.fasterxml.jackson.annotation.JsonInclude;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ChatRequest {
    
    /**
     * 会话ID,用于保持对话上下文
     */
    private String sessionId;
    
    /**
     * 用户输入的消息内容
     */
    private String message;
    
    /**
     * 系统角色设定
     */
    private String systemPrompt;
    
    /**
     * 对话历史(用于多轮对话)
     */
    private List<Message> history;
    
    /**
     * 模型参数覆盖
     */
    private ModelOptions options;
    
    /**
     * 业务扩展字段
     */
    private Map<String, Object> extras;
    
    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public static class Message {
        private String role;  // user, assistant, system
        private String content;
        private LocalDateTime timestamp;
    }
    
    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public static class ModelOptions {
        private String model;
        private Double temperature;
        private Integer maxTokens;
        private Double topP;
        private Boolean stream;
    }
}

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ChatResponse {
    
    /**
     * 响应状态码
     */
    private Integer code;
    
    /**
     * 响应消息
     */
    private String message;
    
    /**
     * 实际响应数据
     */
    private ResponseData data;
    
    /**
     * 请求元数据
     */
    private Metadata metadata;
    
    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public static class ResponseData {
        private String content;
        private String reasoningContent;  // DeepSeek-R1的思维链
        private String model;
        private Integer totalTokens;
        private Integer promptTokens;
        private Integer completionTokens;
        private LocalDateTime generatedAt;
    }
    
    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public static class Metadata {
        private String requestId;
        private String sessionId;
        private Long processingTimeMs;
        private Boolean cached;
    }
    
    // 成功响应工厂方法
    public static ChatResponse success(ResponseData data) {
        return ChatResponse.builder()
                .code(200)
                .message("success")
                .data(data)
                .build();
    }
    
    // 错误响应工厂方法
    public static ChatResponse error(Integer code, String message) {
        return ChatResponse.builder()
                .code(code)
                .message(message)
                .build();
    }
}

3.2 服务层实现

服务层是业务逻辑的核心,这里我设计了一个具有完整错误处理和日志记录的服务:

package com.example.deepseek.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.SystemPromptTemplate;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import com.example.deepseek.model.ChatRequest;
import com.example.deepseek.model.ChatResponse as ApiResponse;

import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
@Service
public class DeepSeekService {
    
    private final ChatClient chatClient;
    private final Map<String, List<Message>> sessionStore;
    
    @Value("${deepseek.api.business.max-retries:3}")
    private int maxRetries;
    
    @Value("${deepseek.api.business.timeout-seconds:30}")
    private int timeoutSeconds;
    
    @Autowired
    public DeepSeekService(ChatClient.Builder chatClientBuilder) {
        this.chatClient = chatClientBuilder.build();
        this.sessionStore = new ConcurrentHashMap<>();
        
        log.info("DeepSeekService initialized with timeout: {}s, maxRetries: {}", 
                timeoutSeconds, maxRetries);
    }
    
    /**
     * 单次对话(无上下文)
     */
    public ApiResponse singleChat(ChatRequest request) {
        Instant start = Instant.now();
        String requestId = UUID.randomUUID().toString();
        
        try {
            log.info("Processing single chat request: {}", requestId);
            
            // 参数校验
            validateRequest(request);
            
            // 构建提示
            Prompt prompt = buildPrompt(request, null);
            
            // 调用AI服务
            ChatResponse aiResponse = executeWithRetry(prompt, requestId);
            
            // 构建响应
            ApiResponse.ResponseData data = buildResponseData(aiResponse, request);
            ApiResponse.Metadata metadata = buildMetadata(requestId, request.getSessionId(), start);
            
            log.info("Chat request completed in {}ms: {}", 
                    Duration.between(start, Instant.now()).toMillis(), requestId);
            
            return ApiResponse.success(data)
                    .metadata(metadata)
                    .build();
                    
        } catch (IllegalArgumentException e) {
            log.warn("Invalid request: {}", e.getMessage());
            return ApiResponse.error(400, e.getMessage());
        } catch (Exception e) {
            log.error("Chat request failed: {}", requestId, e);
            return ApiResponse.error(500, "服务内部错误: " + e.getMessage());
        }
    }
    
    /**
     * 多轮对话(保持上下文)
     */
    public ApiResponse contextualChat(ChatRequest request) {
        Instant start = Instant.now();
        String requestId = UUID.randomUUID().toString();
        
        try {
            log.info("Processing contextual chat request: {}, session: {}", 
                    requestId, request.getSessionId());
            
            // 参数校验
            validateRequest(request);
            
            // 获取或创建会话历史
            List<Message> history = getOrCreateSessionHistory(request.getSessionId());
            
            // 添加用户消息到历史
            history.add(new UserMessage(request.getMessage()));
            
            // 构建提示(包含历史)
            Prompt prompt = buildPrompt(request, history);
            
            // 调用AI服务
            ChatResponse aiResponse = executeWithRetry(prompt, requestId);
            
            // 添加AI响应到历史
            Message assistantMessage = aiResponse.getResult().getOutput();
            history.add(assistantMessage);
            
            // 限制历史记录长度(防止token超限)
            trimHistory(history);
            
            // 构建响应
            ApiResponse.ResponseData data = buildResponseData(aiResponse, request);
            ApiResponse.Metadata metadata = buildMetadata(requestId, request.getSessionId(), start);
            
            log.info("Contextual chat completed in {}ms: {}, session: {}", 
                    Duration.between(start, Instant.now()).toMillis(), 
                    requestId, request.getSessionId());
            
            return ApiResponse.success(data)
                    .metadata(metadata)
                    .build();
                    
        } catch (Exception e) {
            log.error("Contextual chat failed: {}", requestId, e);
            return ApiResponse.error(500, "多轮对话处理失败: " + e.getMessage());
        }
    }
    
    /**
     * 流式对话响应
     */
    public Flux<String> streamChat(ChatRequest request) {
        return Flux.create(sink -> {
            try {
                validateRequest(request);
                
                Prompt prompt = buildPrompt(request, null);
                
                chatClient.prompt(prompt)
                        .stream()
                        .content()
                        .doOnNext(chunk -> {
                            sink.next(chunk);
                            log.debug("Stream chunk received: {}", chunk);
                        })
                        .doOnComplete(() -> {
                            sink.complete();
                            log.info("Stream completed successfully");
                        })
                        .doOnError(error -> {
                            sink.error(error);
                            log.error("Stream error occurred", error);
                        })
                        .subscribe();
                        
            } catch (Exception e) {
                sink.error(e);
                log.error("Stream setup failed", e);
            }
        });
    }
    
    /**
     * 清理会话历史
     */
    public void clearSession(String sessionId) {
        if (StringUtils.hasText(sessionId)) {
            sessionStore.remove(sessionId);
            log.info("Session cleared: {}", sessionId);
        }
    }
    
    // ========== 私有辅助方法 ==========
    
    private void validateRequest(ChatRequest request) {
        if (request == null) {
            throw new IllegalArgumentException("请求不能为空");
        }
        
        if (!StringUtils.hasText(request.getMessage())) {
            throw new IllegalArgumentException("消息内容不能为空");
        }
        
        // 消息长度限制
        if (request.getMessage().length() > 4000) {
            throw new IllegalArgumentException("消息长度不能超过4000字符");
        }
    }
    
    private Prompt buildPrompt(ChatRequest request, List<Message> history) {
        List<Message> messages = new ArrayList<>();
        
        // 添加系统提示
        if (StringUtils.hasText(request.getSystemPrompt())) {
            messages.add(new SystemPromptTemplate(request.getSystemPrompt())
                    .createMessage());
        } else {
            // 默认系统提示
            messages.add(new SystemPromptTemplate("""
                    你是一个专业的AI助手,回答要准确、简洁、有帮助。
                    如果不知道答案,请诚实说明,不要编造信息。
                    """).createMessage());
        }
        
        // 添加历史消息
        if (history != null && !history.isEmpty()) {
            messages.addAll(history);
        }
        
        // 添加当前用户消息
        messages.add(new UserMessage(request.getMessage()));
        
        // 构建Prompt
        Prompt prompt = new Prompt(messages);
        
        // 应用自定义选项
        if (request.getOptions() != null) {
            ChatRequest.ModelOptions options = request.getOptions();
            Map<String, Object> promptOptions = new HashMap<>();
            
            if (options.getModel() != null) {
                promptOptions.put("model", options.getModel());
            }
            if (options.getTemperature() != null) {
                promptOptions.put("temperature", options.getTemperature());
            }
            if (options.getMaxTokens() != null) {
                promptOptions.put("maxTokens", options.getMaxTokens());
            }
            
            prompt = new Prompt(messages, promptOptions);
        }
        
        return prompt;
    }
    
    private ChatResponse executeWithRetry(Prompt prompt, String requestId) {
        int attempt = 0;
        Exception lastException = null;
        
        while (attempt < maxRetries) {
            try {
                attempt++;
                log.debug("Attempt {} for request: {}", attempt, requestId);
                
                return chatClient.prompt(prompt)
                        .call()
                        .chatResponse();
                        
            } catch (Exception e) {
                lastException = e;
                log.warn("Attempt {} failed for request {}: {}", 
                        attempt, requestId, e.getMessage());
                
                if (attempt < maxRetries) {
                    try {
                        Thread.sleep(1000L * attempt); // 指数退避
                    } catch (InterruptedException ie) {
                        Thread.currentThread().interrupt();
                        throw new RuntimeException("重试被中断", ie);
                    }
                }
            }
        }
        
        throw new RuntimeException("所有重试尝试均失败", lastException);
    }
    
    private List<Message> getOrCreateSessionHistory(String sessionId) {
        if (!StringUtils.hasText(sessionId)) {
            return new ArrayList<>();
        }
        
        return sessionStore.computeIfAbsent(sessionId, k -> {
            log.debug("Created new session: {}", sessionId);
            return new ArrayList<>();
        });
    }
    
    private void trimHistory(List<Message> history) {
        // 简单策略:保留最近10轮对话
        if (history.size() > 20) { // 每轮2条消息(用户+助手)
            List<Message> trimmed = history.subList(history.size() - 20, history.size());
            history.clear();
            history.addAll(trimmed);
            log.debug("Trimmed conversation history to last 10 rounds");
        }
    }
    
    private ApiResponse.ResponseData buildResponseData(
            ChatResponse aiResponse, ChatRequest request) {
        
        String content = aiResponse.getResult().getOutput().getContent();
        
        // 提取思维链(如果使用DeepSeek-R1)
        String reasoningContent = extractReasoningContent(aiResponse);
        
        return ApiResponse.ResponseData.builder()
                .content(content)
                .reasoningContent(reasoningContent)
                .model(request.getOptions() != null ? 
                        request.getOptions().getModel() : "deepseek-chat")
                .totalTokens(aiResponse.getMetadata().getUsage().getTotalTokens())
                .promptTokens(aiResponse.getMetadata().getUsage().getPromptTokens())
                .completionTokens(aiResponse.getMetadata().getUsage().getCompletionTokens())
                .generatedAt(java.time.LocalDateTime.now())
                .build();
    }
    
    private String extractReasoningContent(ChatResponse response) {
        // DeepSeek-R1会在响应中包含思维链
        // 这里需要根据实际响应格式解析
        // 简化实现,实际项目中需要更复杂的解析逻辑
        try {
            Object metadata = response.getMetadata().get("reasoning");
            return metadata != null ? metadata.toString() : null;
        } catch (Exception e) {
            log.debug("Failed to extract reasoning content", e);
            return null;
        }
    }
    
    private ApiResponse.Metadata buildMetadata(
            String requestId, String sessionId, Instant startTime) {
        
        return ApiResponse.Metadata.builder()
                .requestId(requestId)
                .sessionId(sessionId)
                .processingTimeMs(Duration.between(startTime, Instant.now()).toMillis())
                .cached(false)
                .build();
    }
}

3.3 控制器层设计

控制器层负责处理HTTP请求和响应,这里我设计了一个RESTful风格的API:

package com.example.deepseek.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ServerWebExchange;

import com.example.deepseek.model.ChatRequest;
import com.example.deepseek.model.ChatResponse;
import com.example.deepseek.service.DeepSeekService;

import reactor.core.publisher.Flux;

import javax.validation.Valid;
import java.util.Map;

@Slf4j
@RestController
@RequestMapping("/v1/chat")
public class ChatController {
    
    private final DeepSeekService deepSeekService;
    
    @Autowired
    public ChatController(DeepSeekService deepSeekService) {
        this.deepSeekService = deepSeekService;
    }
    
    /**
     * 健康检查端点
     */
    @GetMapping("/health")
    public ResponseEntity<Map<String, Object>> health() {
        return ResponseEntity.ok(Map.of(
            "status", "UP",
            "service", "deepseek-ai-service",
            "timestamp", System.currentTimeMillis()
        ));
    }
    
    /**
     * 单次对话API
     */
    @PostMapping("/single")
    public ResponseEntity<ChatResponse> singleChat(
            @Valid @RequestBody ChatRequest request,
            ServerWebExchange exchange) {
        
        log.info("Received single chat request from {}", 
                exchange.getRequest().getRemoteAddress());
        
        ChatResponse response = deepSeekService.singleChat(request);
        
        // 根据响应状态码设置HTTP状态
        HttpStatus status = response.getCode() == 200 ? 
                HttpStatus.OK : HttpStatus.valueOf(response.getCode());
        
        return ResponseEntity.status(status)
                .header("X-Request-ID", response.getMetadata().getRequestId())
                .body(response);
    }
    
    /**
     * 多轮对话API
     */
    @PostMapping("/context")
    public ResponseEntity<ChatResponse> contextualChat(
            @Valid @RequestBody ChatRequest request,
            ServerWebExchange exchange) {
        
        log.info("Received contextual chat request from {}, session: {}", 
                exchange.getRequest().getRemoteAddress(), 
                request.getSessionId());
        
        ChatResponse response = deepSeekService.contextualChat(request);
        
        HttpStatus status = response.getCode() == 200 ? 
                HttpStatus.OK : HttpStatus.valueOf(response.getCode());
        
        return ResponseEntity.status(status)
                .header("X-Request-ID", response.getMetadata().getRequestId())
                .header("X-Session-ID", response.getMetadata().getSessionId())
                .body(response);
    }
    
    /**
     * 流式对话API
     */
    @PostMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> streamChat(
            @Valid @RequestBody ChatRequest request,
            ServerWebExchange exchange) {
        
        log.info("Received stream chat request from {}", 
                exchange.getRequest().getRemoteAddress());
        
        return deepSeekService.streamChat(request)
                .doOnSubscribe(subscription -> 
                        log.debug("Stream subscription started"))
                .doOnTerminate(() -> 
                        log.debug("Stream terminated"))
                .onErrorResume(e -> {
                    log.error("Stream error", e);
                    return Flux.just("data: {\"error\": \"" + e.getMessage() + "\"}\n\n");
                });
    }
    
    /**
     * 清理会话
     */
    @DeleteMapping("/session/{sessionId}")
    public ResponseEntity<Map<String, Object>> clearSession(
            @PathVariable String sessionId) {
        
        log.info("Clearing session: {}", sessionId);
        
        deepSeekService.clearSession(sessionId);
        
        return ResponseEntity.ok(Map.of(
            "message", "Session cleared successfully",
            "sessionId", sessionId,
            "timestamp", System.currentTimeMillis()
        ));
    }
    
    /**
     * 全局异常处理
     */
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ChatResponse> handleException(Exception e) {
        log.error("Controller exception occurred", e);
        
        ChatResponse errorResponse = ChatResponse.error(
                500, 
                "服务器内部错误: " + e.getMessage()
        );
        
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(errorResponse);
    }
    
    /**
     * 参数验证异常处理
     */
    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<ChatResponse> handleValidationException(
            IllegalArgumentException e) {
        
        log.warn("Validation error: {}", e.getMessage());
        
        ChatResponse errorResponse = ChatResponse.error(
                400, 
                "请求参数错误: " + e.getMessage()
        );
        
        return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                .body(errorResponse);
    }
}

4. 高级特性与优化实践

4.1 性能监控与指标收集

在实际生产环境中,监控AI服务的性能至关重要。这里我实现了一个简单的监控切面:

package com.example.deepseek.aspect;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

@Slf4j
@Aspect
@Component
public class PerformanceMonitorAspect {
    
    private final ConcurrentHashMap<String, AtomicLong> requestCounters = 
            new ConcurrentHashMap<>();
    private final ConcurrentHashMap<String, AtomicLong> totalLatency = 
            new ConcurrentHashMap<>();
    
    @Pointcut("execution(* com.example.deepseek.service.DeepSeekService.*(..))")
    public void serviceMethods() {}
    
    @Around("serviceMethods()")
    public Object monitorPerformance(ProceedingJoinPoint joinPoint) throws Throwable {
        String methodName = joinPoint.getSignature().getName();
        StopWatch stopWatch = new StopWatch();
        
        try {
            stopWatch.start();
            Object result = joinPoint.proceed();
            stopWatch.stop();
            
            long executionTime = stopWatch.getTotalTimeMillis();
            
            // 更新统计信息
            updateStatistics(methodName, executionTime);
            
            // 记录慢查询
            if (executionTime > 5000) { // 5秒阈值
                log.warn("Slow method execution detected: {} took {}ms", 
                        methodName, executionTime);
            }
            
            log.debug("Method {} executed in {}ms", methodName, executionTime);
            return result;
            
        } catch (Exception e) {
            stopWatch.stop();
            log.error("Method {} failed after {}ms", 
                    methodName, stopWatch.getTotalTimeMillis(), e);
            throw e;
        }
    }
    
    private void updateStatistics(String methodName, long latency) {
        requestCounters.computeIfAbsent(methodName, k -> new AtomicLong(0))
                .incrementAndGet();
        
        totalLatency.computeIfAbsent(methodName, k -> new AtomicLong(0))
                .addAndGet(latency);
    }
    
    /**
     * 获取性能统计信息
     */
    public ConcurrentHashMap<String, Map<String, Object>> getStatistics() {
        ConcurrentHashMap<String, Map<String, Object>> stats = 
                new ConcurrentHashMap<>();
        
        requestCounters.forEach((method, count) -> {
            AtomicLong total = totalLatency.get(method);
            long avgLatency = total != null ? total.get() / count.get() : 0;
            
            stats.put(method, Map.of(
                "requestCount", count.get(),
                "averageLatencyMs", avgLatency,
                "totalLatencyMs", total != null ? total.get() : 0
            ));
        });
        
        return stats;
    }
}

4.2 配置管理类

为了提供更灵活的配置管理,我创建了一个专门的配置类:

package com.example.deepseek.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.annotation.Validated;

import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;

@Data
@Validated
@Configuration
@ConfigurationProperties(prefix = "deepseek")
public class DeepSeekConfig {
    
    @Data
    public static class ApiConfig {
        @NotBlank(message = "API密钥不能为空")
        private String apiKey;
        
        @NotBlank(message = "基础URL不能为空")
        private String baseUrl = "https://api.deepseek.com";
        
        @NotNull
        private ModelConfig model = new ModelConfig();
        
        @NotNull
        private HttpClientConfig httpClient = new HttpClientConfig();
        
        @NotNull
        private RetryConfig retry = new RetryConfig();
    }
    
    @Data
    public static class ModelConfig {
        private String defaultModel = "deepseek-chat";
        
        @Min(0)
        @Max(2)
        private Double defaultTemperature = 0.7;
        
        @Min(1)
        @Max(4000)
        private Integer defaultMaxTokens = 2000;
        
        @Min(0)
        @Max(1)
        private Double defaultTopP = 0.9;
        
        private Boolean enableStreaming = false;
    }
    
    @Data
    public static class HttpClientConfig {
        @Min(1000)
        private Integer connectTimeoutMs = 30000;
        
        @Min(1000)
        private Integer readTimeoutMs = 60000;
        
        @Min(1)
        private Integer maxConnections = 100;
        
        @Min(1)
        private Integer maxConnectionsPerRoute = 50;
    }
    
    @Data
    public static class RetryConfig {
        @Min(0)
        @Max(10)
        private Integer maxAttempts = 3;
        
        @Min(100)
        private Long initialIntervalMs = 1000L;
        
        @Min(1)
        @Max(5)
        private Double multiplier = 2.0;
        
        @Min(1000)
        private Long maxIntervalMs = 10000L;
    }
    
    @Data
    public static class BusinessConfig {
        @Min(1)
        private Integer maxMessageLength = 4000;
        
        @Min(1)
        private Integer maxHistoryRounds = 10;
        
        private Boolean enableCache = false;
        
        @Min(60)
        private Integer cacheTtlSeconds = 300;
        
        private Boolean enableRateLimit = true;
        
        @Min(1)
        private Integer rateLimitPerMinute = 60;
    }
    
    @NotNull
    private ApiConfig api = new ApiConfig();
    
    @NotNull
    private BusinessConfig business = new BusinessConfig();
    
    // 验证配置的完整性
    public void validate() {
        if (api.getApiKey() == null || api.getApiKey().trim().isEmpty()) {
            throw new IllegalStateException("DeepSeek API密钥未配置");
        }
        
        if (api.getBaseUrl() == null || api.getBaseUrl().trim().isEmpty()) {
            throw new IllegalStateException("DeepSeek基础URL未配置");
        }
        
        log.info("DeepSeek配置验证通过,使用模型: {}", 
                api.getModel().getDefaultModel());
    }
}

4.3 自定义ChatClient配置

为了获得更好的控制权和性能优化,我们可以自定义ChatClient的配置:

package com.example.deepseek.config;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.memory.InMemoryChatMemory;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestClient;

@Configuration
public class ChatClientConfig {
    
    @Autowired
    private DeepSeekConfig deepSeekConfig;
    
    @Bean
    public ChatClient chatClient() {
        // 创建自定义的RestClient
        RestClient.Builder restClientBuilder = RestClient.builder()
                .baseUrl(deepSeekConfig.getApi().getBaseUrl())
                .defaultHeader("Authorization", 
                        "Bearer " + deepSeekConfig.getApi().getApiKey())
                .defaultHeader("Content-Type", "application/json");
        
        // 配置HTTP客户端参数
        if (deepSeekConfig.getApi().getHttpClient() != null) {
            // 这里可以添加更详细的HTTP客户端配置
            // 如连接池、超时设置等
        }
        
        // 构建ChatOptions
        OpenAiChatOptions options = OpenAiChatOptions.builder()
                .model(deepSeekConfig.getApi().getModel().getDefaultModel())
                .temperature(deepSeekConfig.getApi().getModel().getDefaultTemperature())
                .maxTokens(deepSeekConfig.getApi().getModel().getDefaultMaxTokens())
                .topP(deepSeekConfig.getApi().getModel().getDefaultTopP())
                .stream(deepSeekConfig.getApi().getModel().getEnableStreaming())
                .build();
        
        // 创建ChatClient
        return ChatClient.builder()
                .defaultSystem("""
                    你是一个专业的AI助手,请遵循以下原则:
                    1. 回答要准确、客观、有帮助
                    2. 如果不知道答案,请诚实说明
                    3. 保持友好和专业的态度
                    4. 对于代码问题,提供可运行的示例
                    5. 对于复杂问题,分步骤解释
                    """)
                .defaultOptions(options)
                .defaultAdvisors(
                        new MessageChatMemoryAdvisor(new InMemoryChatMemory())
                )
                .build();
    }
    
    /**
     * 创建用于流式响应的ChatClient
     */
    @Bean
    public ChatClient streamingChatClient() {
        OpenAiChatOptions streamingOptions = OpenAiChatOptions.builder()
                .model(deepSeekConfig.getApi().getModel().getDefaultModel())
                .temperature(deepSeekConfig.getApi().getModel().getDefaultTemperature())
                .maxTokens(deepSeekConfig.getApi().getModel().getDefaultMaxTokens())
                .topP(deepSeekConfig.getApi().getModel().getDefaultTopP())
                .stream(true)  // 启用流式
                .build();
        
        return ChatClient.builder()
                .defaultSystem("你是一个流式响应的AI助手")
                .defaultOptions(streamingOptions)
                .build();
    }
}

5. 测试与验证

5.1 单元测试

编写全面的单元测试确保代码质量:

package com.example.deepseek.service;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.model.Generation;
import org.springframework.ai.chat.prompt.Prompt;

import com.example.deepseek.model.ChatRequest;

import java.util.Map;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
class DeepSeekServiceTest {
    
    @Mock
    private ChatClient chatClient;
    
    @Mock
    private ChatClient.Builder chatClientBuilder;
    
    private DeepSeekService deepSeekService;
    
    @BeforeEach
    void setUp() {
        when(chatClientBuilder.build()).thenReturn(chatClient);
        deepSeekService = new DeepSeekService(chatClientBuilder);
    }
    
    @Test
    void testSingleChat_Success() {
        // 准备测试数据
        ChatRequest request = ChatRequest.builder()
                .message("你好,介绍一下Spring Boot")
                .build();
        
        // 模拟AI响应
        ChatResponse mockResponse = mock(ChatResponse.class);
        Generation mockGeneration = mock(Generation.class);
        org.springframework.ai.chat.messages.AssistantMessage mockMessage = 
                mock(org.springframework.ai.chat.messages.AssistantMessage.class);
        
        when(mockMessage.getContent()).thenReturn("Spring Boot是一个...");
        when(mockGeneration.getOutput()).thenReturn(mockMessage);
        when(mockResponse.getResult()).thenReturn(mockGeneration);
        
        when(chatClient.prompt(any(Prompt.class)))
                .thenReturn(new ChatClient.ChatClientRequestSpec() {
                    @Override
                    public ChatClient.ChatClientRequestSpec options(Map<String, Object> options) {
                        return this;
                    }
                    
                    @Override
                    public ChatClient.ChatClientRequestSpec options(
                            org.springframework.ai.chat.model.ChatOptions options) {
                        return this;
                    }
                    
                    @Override
                    public ChatClient.ChatClientCallResponseSpec call() {
                        return new ChatClient.ChatClientCallResponseSpec() {
                            @Override
                            public ChatResponse chatResponse() {
                                return mockResponse;
                            }
                            
                            @Override
                            public String content() {
                                return "Spring Boot是一个...";
                            }
                        };
                    }
                });
        
        // 执行测试
        com.example.deepseek.model.ChatResponse response = 
                deepSeekService.singleChat(request);
        
        // 验证结果
        assertNotNull(response);
        assertEquals(200, response.getCode());
        assertEquals("success", response.getMessage());
        assertNotNull(response.getData());
        assertNotNull(response.getData().getContent());
        
        // 验证方法调用
        verify(chatClient, times(1)).prompt(any(Prompt.class));
    }
    
    @Test
    void testSingleChat_InvalidRequest() {
        // 测试空消息
        ChatRequest request = ChatRequest.builder()
                .message("")
                .build();
        
        com.example.deepseek.model.ChatResponse response = 
                deepSeekService.singleChat(request);
        
        assertEquals(400, response.getCode());
        assertTrue(response.getMessage().contains("消息内容不能为空"));
    }
    
    @Test
    void testSingleChat_MessageTooLong() {
        // 创建超长消息
        String longMessage = "a".repeat(5000);
        ChatRequest request = ChatRequest.builder()
                .message(longMessage)
                .build();
        
        com.example.deepseek.model.ChatResponse response = 
                deepSeekService.singleChat(request);
        
        assertEquals(400, response.getCode());
        assertTrue(response.getMessage().contains("消息长度不能超过"));
    }
    
    @Test
    void testContextualChat_WithSession() {
        // 测试带会话的多轮对话
        String sessionId = "test-session-123";
        
        ChatRequest request1 = ChatRequest.builder()
                .sessionId(sessionId)
                .message("第一轮对话")
                .build();
        
        ChatRequest request2 = ChatRequest.builder()
                .sessionId(sessionId)
                .message("第二轮对话,基于上一轮上下文")
                .build();
        
        // 模拟第一次响应
        when(chatClient.prompt(any(Prompt.class)))
                .thenReturn(createMockRequestSpec("第一次响应"));
        
        com.example.deepseek.model.ChatResponse response1 = 
                deepSeekService.contextualChat(request1);
        
        assertEquals(200, response1.getCode());
        assertEquals(sessionId, response1.getMetadata().getSessionId());
        
        // 模拟第二次响应(应该包含历史)
        when(chatClient.prompt(any(Prompt.class)))
                .thenReturn(createMockRequestSpec("第二次响应,包含上下文"));
        
        com.example.deepseek.model.ChatResponse response2 = 
                deepSeekService.contextualChat(request2);
        
        assertEquals(200, response2.getCode());
        assertEquals(sessionId, response2.getMetadata().getSessionId());
    }
    
    @Test
    void testClearSession() {
        // 先创建一个会话
        String sessionId = "test-clear-session";
        ChatRequest request = ChatRequest.builder()
                .sessionId(sessionId)
                .message("测试消息")
                .build();
        
        when(chatClient.prompt(any(Prompt.class)))
                .thenReturn(createMockRequestSpec("响应"));
        
        deepSeekService.contextualChat(request);
        
        // 清理会话
        deepSeekService.clearSession(sessionId);
        
        // 再次使用同一会话,应该重新开始
        com.example.deepseek.model.ChatResponse response = 
                deepSeekService.contextualChat(request);
        
        assertEquals(200, response.getCode());
    }
    
    private ChatClient.ChatClientRequestSpec createMockRequestSpec(String responseContent) {
        return new ChatClient.ChatClientRequestSpec() {
            @Override
            public ChatClient.ChatClientRequestSpec options(Map<String, Object> options) {
                return this;
            }
            
            @Override
            public ChatClient.ChatClientRequestSpec options(
                    org.springframework.ai.chat.model.ChatOptions options) {
                return this;
            }
            
            @Override
            public ChatClient.ChatClientCallResponseSpec call() {
                return new ChatClient.ChatClientCallResponseSpec() {
                    @Override
                    public ChatResponse chatResponse() {
                        ChatResponse mockResponse = mock(ChatResponse.class);
                        Generation mockGeneration = mock(Generation.class);
                        org.springframework.ai.chat.messages.AssistantMessage mockMessage = 
                                mock(org.springframework.ai.chat.messages.AssistantMessage.class);
                        
                        when(mockMessage.getContent()).thenReturn(responseContent);
                        when(mockGeneration.getOutput()).thenReturn(mockMessage);
                        when(mockResponse.getResult()).thenReturn(mockGeneration);
                        
                        return mockResponse;
                    }
                    
                    @Override
                    public String content() {
                        return responseContent;
                    }
                };
            }
        };
    }
}

5.2 集成测试

创建集成测试验证完整的API流程:

package com.example.deepseek.integration;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;

import com.fasterxml.jackson.databind.ObjectMapper;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@SpringBootTest
@AutoConfigureMockMvc
class ChatControllerIntegrationTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @Autowired
    private ObjectMapper objectMapper;
    
    @Test
    void testHealthEndpoint() throws Exception {
        mockMvc.perform(get("/v1/chat/health"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.status").value("UP"))
                .andExpect(jsonPath("$.service").value("deepseek-ai-service"));
    }
    
    @Test
    void testSingleChatEndpoint() throws Exception {
        String requestJson = """
            {
                "message": "你好,请用Java写一个Hello World程序",
                "options": {
                    "model": "deepseek-chat",
                    "temperature": 0.7,
                    "maxTokens": 500
                }
            }
            """;
        
        mockMvc.perform(post("/v1/chat/single")
                .contentType(MediaType.APPLICATION_JSON)
                .content(requestJson))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(200))
                .andExpect(jsonPath("$.message").value("success"))
                .andExpect(jsonPath("$.data.content").exists())
                .andExpect(jsonPath("$.data.model").value("deepseek-chat"))
                .andExpect(jsonPath("$.metadata.requestId").exists());
    }
    
    @Test
    void testContextualChatEndpoint() throws Exception {
        String sessionId = "test-session-" + System.currentTimeMillis();
        
        // 第一轮对话
        String firstRequest = String.format("""
            {
                "sessionId": "%s",
                "message": "Java中的String类有哪些常用方法?",
                "systemPrompt": "你是一个Java专家,请详细回答技术问题"
            }
            """, sessionId);
        
        mockMvc.perform(post("/v1/chat/context")
                .contentType(MediaType.APPLICATION_JSON)
                .content(firstRequest))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.metadata.sessionId").value(sessionId));
        
        // 第二轮对话(基于上下文)
        String secondRequest = String.format("""
            {
                "sessionId": "%s",
                "message": "能举例说明length()方法的用法吗?"
            }
            """, sessionId);
        
        mockMvc.perform(post("/v1/chat/context")
                .contentType(MediaType.APPLICATION_JSON)
                .content(secondRequest))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.metadata.sessionId").value(sessionId));
    }
    
    @Test
    void testStreamChatEndpoint() throws Exception {
        String requestJson = """
            {
                "message": "请简要介绍Spring Boot",
                "options": {
                    "stream": true
                }
            }
            """;
        
        mockMvc.perform(post("/v1/chat/stream")
                .contentType(MediaType.APPLICATION_JSON)
                .content(requestJson))
                .andExpect(status().isOk())
                .andExpect(content().contentTypeCompatibleWith(
                        MediaType.TEXT_EVENT_STREAM_VALUE));
    }
    
    @Test
    void testClearSessionEndpoint() throws Exception {
        String sessionId = "clear-test-" + System.currentTimeMillis();
        
        mockMvc.perform(delete("/v1/chat/session/{sessionId}", sessionId))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.message").value("Session cleared successfully"))
                .andExpect(jsonPath("$.sessionId").value(sessionId));
    }
    
    @Test
    void testInvalidRequest() throws Exception {
        // 测试空消息
        String invalidRequest = """
            {
                "message": ""
            }
            """;
        
        mockMvc.perform(post("/v1/chat/single")
                .contentType(MediaType.APPLICATION_JSON)
                .content(invalidRequest))
                .andExpect(status().isBadRequest())
                .andExpect(jsonPath("$.code").value(400))
                .andExpect(jsonPath("$.message").exists());
    }
}

5.3 性能测试

使用JMeter或编写简单的性能测试脚本:

package com.example.deepseek.performance;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.util.StopWatch;

import com.example.deepseek.model.ChatRequest;
import com.example.deepseek.service.DeepSeekService;

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

@SpringBootTest
class PerformanceTest {
    
    @Autowired
    private DeepSeekService deepSeekService;
    
    @Test
    void testConcurrentRequests() throws InterruptedException {
        int threadCount = 10;
        int requestsPerThread = 20;
        ExecutorService executor = Executors.newFixedThreadPool(threadCount);
        CountDownLatch latch = new CountDownLatch(threadCount);
        AtomicInteger successCount = new AtomicInteger(0);
        AtomicInteger errorCount = new AtomicInteger(0);
        
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        
        for (int i = 0; i < threadCount; i++) {
            executor.submit(() -> {
                try {
                    for (int j = 0; j < requestsPerThread; j++) {
                        ChatRequest request = ChatRequest.builder()
                                .message("测试消息 " + j)
                                .build();
                        
                        try {
                            var response = deepSeekService.singleChat(request);
                            if (response.getCode() == 200) {
                                successCount.incrementAndGet();
                            } else {
                                errorCount.incrementAndGet();
                            }
                        } catch (Exception e) {
                            errorCount.incrementAndGet();
                        }
                        
                        // 稍微延迟,避免请求过于密集
                        Thread.sleep(50);
                    }
                } catch (Exception e) {
                    errorCount.incrementAndGet();
                } finally {
                    latch.countDown();
                }
            });
        }
        
        latch.await(5, TimeUnit.MINUTES);
        stopWatch.stop();
        
        executor.shutdown();
        
        int totalRequests = threadCount * requestsPerThread;
        System.out.println("性能测试结果:");
        System.out.println("总请求数: " + totalRequests);
        System.out.println("成功数: " + successCount.get());
        System.out.println("失败数: " + errorCount.get());
        System.out.println("总耗时: " + stopWatch.getTotalTimeMillis() + "ms");
        System.out.println("平均响应时间: " + 
                stopWatch.getTotalTimeMillis() / totalRequests + "ms/请求");
        System.out.println("QPS: " + 
                (totalRequests * 1000.0 / stopWatch.getTotalTimeMillis()));
    }
}

6. 部署与运维

6.1 Docker容器化

创建Dockerfile进行容器化部署:

# 使用多阶段构建
FROM eclipse-temurin:17-jdk-alpine as builder

WORKDIR /app

# 复制Maven包装器
COPY mvnw .
COPY .mvn .mvn

# 复制POM文件
COPY pom.xml .

# 下载依赖(利用Docker缓存)
RUN ./mvnw dependency:go-offline -B

# 复制源代码
COPY src src

# 构建应用
RUN ./mvnw clean package -DskipTests

# 运行时镜像
FROM eclipse-temurin:17-jre-alpine

WORKDIR /app

# 安装必要的工具
RUN apk add --no-cache curl bash

# 创建非root用户
RUN addgroup -S spring && adduser -S spring -G spring
USER spring:spring

# 从构建阶段复制JAR文件
COPY --from=builder /app/target/*.jar app.jar

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:8080/api/v1/chat/health || exit 1

# 暴露端口
EXPOSE 8080

# 启动应用
ENTRYPOINT ["java", "-jar", "app.jar"]

6.2 Docker Compose配置

创建docker-compose.yml用于本地开发环境:

version: '3.8'

services:
  deepseek-ai:
    build: .
    container_name: deepseek-ai-service
    ports:
      - "8080:8080"
    environment:
      - DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY}
      - SPRING_PROFILES_ACTIVE=docker
      - JAVA_OPTS=-Xmx512m -Xms256m
    volumes:
      - ./logs:/app/logs
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/api/v1/chat/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
    networks:
      - ai-network
    restart: unless-stopped

  # 可选:添加Prometheus监控
  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus-data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--web.console.libraries=/etc/prometheus/console_libraries'
      - '--web.console.templates=/etc/prometheus/consoles'
      - '--storage.tsdb.retention.time=200h'
      - '--web.enable-lifecycle'
    networks:
      - ai-network
    restart: unless-stopped

  # 可选:添加Grafana仪表板
  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
    volumes:
      - grafana-data:/var/lib/grafana
      - ./grafana/provisioning:/etc/grafana/provisioning
    networks:
      - ai-network
    restart: unless-stopped

networks:
  ai-network:
    driver: bridge

volumes:
  prometheus-data:
  grafana-data:

6.3 生产环境配置

创建生产环境配置文件application-prod.yml:

spring:
  application:
    name: deepseek-ai-service
  
  # 生产环境数据库配置(如果需要)
  datasource:
    url: ${DB_URL}
    username: ${DB_USERNAME}
    password: ${DB_PASSWORD}
    hikari:
      maximum-pool-size: 20
      minimum-idle: 5
      connection-timeout: 30000
      idle-timeout: 600000
      max-lifetime: 1800000
  
  # JPA配置
  jpa:
    hibernate:
      ddl-auto: validate
    show-sql: false
    properties:
      hibernate:
        dialect: org.hibernate.dialect.PostgreSQLDialect
        format_sql: false
  
  # 缓存配置
  cache:
    type: redis
    redis:
      time-to-live: 300000
      cache-null-values: false
  
  # Redis配置
  redis:
    host: ${REDIS_HOST:localhost}
    port: ${REDIS_PORT:6379}
    password: ${REDIS_PASSWORD:}
    timeout: 5000
    lettuce:
      pool:
        max-active: 20
        max-idle: 10
        min-idle: 5
        max-wait: 5000
  
  # 日志配置
  logging:
    level:
      com.example.deepseek: INFO
      org.springframework.ai: WARN
      org.springframework.web: INFO
    file:
      name: /app/logs/application.log
    logback:
      rollingpolicy:
        max-file-size: 10MB
        max-history: 30
  
  # Actuator配置
  management:
    endpoints:
      web:
        exposure:
          include: health,info,metrics,prometheus
    endpoint:
      health:
        show-details: always
    metrics:
      export:
        prometheus:
          enabled: true
    tracing:
      sampling:
        probability: 0.1

# DeepSeek API配置
deepseek:
  api:
    api-key: ${DEEPSEEK_API_KEY}
    base-url: https://api.deepseek.com
    model:
      default-model: deepseek-chat
      default-temperature: 0.7
      default-max-tokens: 2000
      default-top-p: 0.9
      enable-streaming: false
    http-client:
      connect-timeout-ms: 30000
      read-timeout-ms: 60000
      max-connections: 100
      max-connections-per-route: 50
    retry:
      max-attempts: 3
      initial-interval-ms: 1000
      multiplier: 2.0
      max-interval-ms: 10000
  business:
    max-message-length: 4000
    max-history-rounds: 10
    enable-cache: true
    cache-ttl-seconds: 300
    enable-rate-limit: true
    rate-limit-per-minute: 60

# 服务器配置
server:
  port: 8080
  servlet:
    context-path: /api
  compression:
    enabled: true
    mime-types: text/html,text/xml,text/plain,text/css,text/javascript,application/json,application/javascript
    min-response-size: 1024
  tomcat:
    max-connections: 10000
    max-threads: 200
    min-spare-threads: 20
    connection-timeout: 30000
    accept-count: 100

# 监控配置
management:
  server:
    port: 8081
  metrics:
    tags:
      application: ${spring.application.name}
    export:
      prometheus:
        step: 1m
        descriptions: true
  health:
    defaults:
      enabled: true
    diskspace:
      enabled: true
    db:
      enabled: true
    redis:
      enabled: true

# 安全配置(根据实际需求调整)
security:
  basic:
    enabled: false

6.4 监控与告警配置

创建Prometheus配置文件prometheus.yml:

global:
  scrape_interval: 15s
  evaluation_interval: 15s

alerting:
  alertmanagers:
    - static_configs:
        - targets: []

rule_files:
  - "alert_rules.yml"

scrape_configs:
  - job_name: 'deepseek-ai-service'
    metrics_path: '/api/actuator/prometheus'
    static_configs:
      - targets: ['deepseek-ai:8080']
        labels:
          application: 'deepseek-ai-service'
          environment: 'production'
    
  - job_name: 'prometheus'
    static_configs:
      - targets: ['localhost:9090']

创建告警规则alert_rules.yml:

groups:
  - name: deepseek-ai-alerts
    rules:
      - alert: HighErrorRate
        expr: rate(http_server_requests_seconds_count{status=~"5..", uri!~".*actuator.*"}[5m]) > 0.1
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "高错误率检测"
          description: "服务 {{ $labels.instance }} 的错误率超过10%,当前值: {{ $value }}"
      
      - alert: HighResponseTime
        expr: histogram_quantile(0.95, rate(http_server_requests_seconds_bucket[5m])) > 5
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "高响应时间检测"
          description: "服务 {{ $labels.instance }} 的95%分位响应时间超过5秒,当前值: {{ $value }}s"
      
      - alert: ServiceDown
        expr: up{job="deepseek-ai-service"} == 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "服务不可用"
          description: "服务 {{ $labels.instance }} 已下线超过1分钟"
      
      - alert: HighMemoryUsage
        expr: (jvm_memory_used_bytes{area="heap"} / jvm_memory_max_bytes{area="heap"}) > 0.8
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "高内存使用率"
          description: "服务 {{ $labels.instance }} 堆内存使用率超过80%,当前值: {{ $value }}"

7. 最佳实践与优化建议

在实际项目中集成DeepSeek时,我总结了一些最佳实践:

7.1 性能优化

连接池管理:Spring AI底层使用RestTemplate或WebClient,合理配置连接池能显著提升性能:

spring:
  ai:
    openai:
      client:
        max-connections: 100
        max-connections-per-route: 50
        connect-timeout: 30s
        read-timeout: 60s
        connection-request-timeout: 30s

响应缓存:对于相同或相似的查询,实现响应缓存:

@Component
public class ResponseCacheService {
    
    private final Cache<String, CachedResponse> cache;
    
    public ResponseCacheService() {
        this.cache = Caffeine.newBuilder()
                .maximumSize(1000)
                .expireAfterWrite(5, TimeUnit.MINUTES)
                .recordStats()
                .build();
    }
    
    public Optional<String> get(String key) {
        CachedResponse cached = cache.getIfPresent(key);
        if (cached != null && !cached.isExpired()) {
            return Optional.of(cached.getContent());
        }
        return Optional.empty();
    }
    
    public void put(String key, String content, Duration ttl) {
        cache.put(key, new CachedResponse(content, ttl));
    }
    
    @Data
    @AllArgsConstructor
    private static class CachedResponse {
        private String content;
        private Instant expiryTime;
        
        public CachedResponse(String content, Duration ttl) {
            this.content = content;
            this.expiryTime = Instant.now().plus(ttl);
        }
        
        public boolean isExpired() {
            return Instant.now().isAfter(expiryTime);
        }
    }
}

7.2 错误处理与降级

优雅降级策略:当DeepSeek服务不可用时,提供备选方案:

@Service
public class FallbackAIService {
    
    private final Map<String, String> faqResponses = new ConcurrentHashMap<>();
    
    public FallbackAIService() {
        // 初始化常见问题回复
        faqResponses.put("你好", "您好!我是AI助手,目前服务暂时不可用,请稍后再试。");
        faqResponses.put("帮助", "您可以通过以下方式获取帮助:\n1. 查看用户手册\n2. 联系客服\n3. 访问帮助中心");
        // 更多预定义回复...
    }
    
    public String getFallbackResponse(String query) {
        // 简单关键词匹配
        return faqResponses.entrySet().stream()
                .filter(entry -> query.contains(entry.getKey()))
                .findFirst()
                .map(Map.Entry::getValue)
                .orElse("抱歉,我暂时无法回答这个问题。请稍后再试或联系人工客服。");
    }
}

7.3 安全考虑

API密钥管理:永远不要将API密钥硬编码在代码中:

@Configuration
public class SecurityConfig {
    
    @Bean
Hermes Agent实战避坑指南:从Cron调度到Desktop开发全链路 AI Agent并非即插即用的黑盒,而是需系统性工程化治理的智能工作流引擎。其核心在于任务调度的可靠性、技能组合的可编排性,以及本地开发与生产环境的一致性。Hermes Agent通过Cron驱动的可审计调度机制、基于契约的技能协作模型,以及Desktop版的可视化内存与调度管理,显著降低AI工作流落地门槛。尤其在应对超时熔断、配置链断裂、内存溢出等高频故障时,需深入理解JVM资源约束、Redis流式记忆、分布式锁等底层机制。本文聚焦Hermes在电商客服、工业预测性维护、物流状态同步等真实场景中的稳定性实 阅读详情

相关推荐

用LangChain Agents构建可审计的数据分析副驾驶

数据分析代理(Data Analyst Agent)是一种将自然语言查询转化为确定性SQL执行的智能调度系统,其核心在于解耦‘意图理解’与‘结果计算’:大模型仅负责决策路径规划,而数据库、API等工具承担100%确定性执行。这种架构有效规避了纯大模型在指标口径、时间语义、区域划分等业务场景中的幻觉风险,显著提升复购率、GMV、留存率等关键指标查询的准确率与可审计性。它适用于已掌握SQL和Python基础、熟悉企业数据治理规则的在职数据分析师,尤其适合嵌入BI取数、周报生成、临时分析等高频重复工作流,实现从执

Faith, Simpleness, Transparency, Innovation 895

SpringBoot 3.4.2 + DeepSeek 实战:5分钟搞定AI聊天机器人(附完整代码)

本文详细介绍了如何使用SpringBoot 3.4.2和SpringAI框架快速集成DeepSeek API,构建一个功能完整的AI聊天机器人。从环境准备、项目初始化到核心代码实现,包括简单聊天接口、流式响应和带上下文的对话功能,提供了完整的代码示例和配置指南。此外,还涵盖了异常处理、性能监控和部署优化等高级功能,帮助Java开发者快速将大模型能力集成到现有系统中。

vodka的博客 355

基于Vue 3与Spring Boot 3的智慧医院系统:集成DeepSeek大模型实现AI诊疗辅助

在医疗信息化领域,前后端分离架构已成为构建复杂业务系统的标准范式。其核心原理在于将用户界面与业务逻辑解耦,通过RESTful API进行数据交互,从而实现前后端独立开发与部署。这一技术架构的价值在于提升了开发效率、系统可维护性和团队协作能力。在智慧医疗等具体应用场景中,如何将前沿的AI能力,特别是大语言模型,无缝融入核心业务流程,成为提升系统智能水平的关键。本文聚焦于一个结合了Vue 3前端框架与Spring Boot 3后端框架的智慧医院就诊系统项目,并深入探讨了如何通过创新的Prompt工程,将Deep

weixin_35516624的博客 298

Spring AI 1.x 系列【6】集成 DeepSeek + 智谱 GLM,实现多模型一键切换的 AI 聊天助手

从零搭建一套 AI 聊天助手,基于 Spring AI 同时集成 DeepSeek 和 智普 GLM 两大主流模型,实现前端一键切换模型、流式对话等完整功能。

记录知识、锤炼自我 1825

基于Vue+SpringBoot+DeepSeek的智慧医院就诊系统毕业设计全解析

大语言模型(LLM)作为人工智能领域的前沿技术,通过其强大的自然语言理解和生成能力,正在深刻改变传统行业的交互方式与工作流程。其核心原理是基于海量数据训练的Transformer架构,能够理解和生成类人文本。在工程实践中,将LLM API集成到现有系统,能够为特定业务场景提供智能化解决方案,创造显著的技术价值。在医疗信息化领域,这一技术可应用于智能问诊、文档自动生成等场景,有效提升服务效率与用户体验。本文聚焦于一个具体的毕业设计项目——智慧医院就诊系统,详细阐述了如何将DeepSeek大语言模型与Vue前端

weixin_35756892的博客 315

SpringBoot+Vue+Mysql社区医院信息平台(源码+文档)

SpringBoot+Vue+Mysql学生宿舍信息管理系统 WEB端软件项目(源码+文档) 项目包含:①完整源码(后端+管理后台+用户前台);②数据库脚本文件;③万字论文文档;④开题报告;⑤答辩PPT;⑥项目运行视频教程。 项目架构: B/S 架构、MVC 分层设计 技术栈: Java、MySQL、SpringBoot、MyBatis-Plus、Token 权限校验、管理后台 Vue+ElementUI+ECharts、用户前台 Layui+jQuery、Maven 构建。 一、角色设计 学生用户: 注册登录、浏览宿舍信息、查询宿舍安排、在线报修、缴费查询、失物招领、桶装水预订、评论交流、收藏等。 商家用户: 注册登录、桶装水品牌与商品信息维护、预订订单查看与处理。 维修人员: 注册登录、查看待处理报修、接单维修、填写报修处理结果与进度反馈。 管理员: 学生管理、班级管理、宿舍信息管理、宿舍安排管理、报修处理管理、缴费信息管理、卫生检查与文明宿舍管理、失物招领管理、桶装水与商家管理、维修人员管理、公告资讯与轮播图管理等。 二、前台功能模块(学生端) 1. 首页展示:轮播图、宿舍公告、最新动态推荐、常用服务快速入口 2. 宿舍信息模块:宿舍列表、楼栋与房间条件筛选、宿舍详情查看、我的宿舍安排查询 3. 在线报修模块:在线提交宿舍设施报修申请、上传故障描述、查看报修进度与维修处理结果 4. 缴费模块:水电等缴费记录查询、缴费状态查看 5. 卫生检查与文明宿舍:宿舍卫生检查结果公示、文明宿舍展示 6. 失物招领模块:失物信息发布、招领信息发布、详情查看、评论互动 7. 桶装水预订:桶装水品牌与商品浏览、在线预订、我的预订记录查询 8. 假期留宿:假期留校申请提交、申请记录查询 9. 评论与收藏:信息评论、查看他人评论、评论回复互动、收藏感兴趣的内容 10. 个人中心...

SpringBoot+Vue项目实战:5分钟给你的仿小红书App嵌入DeepSeek AI聊天机器人

本文详细介绍了如何在SpringBoot+Vue开发的仿小红书App中快速集成DeepSeek AI聊天机器人。通过Dify平台配置AI应用,解决UniApp中的script标签限制问题,并实现样式定制和后端API安全集成,提升用户体验。文章还提供了高级功能扩展建议,帮助开发者轻松实现AI聊天功能。

weixin_42522964的博客 361

YOLOv8作物病害检测实战源码

这是一套基于YOLOv8n的农田病虫害目标检测系统,对叶片照片做目标检测,识别健康叶片、褐斑病、白粉病、锈病四类,并给出对应防治建议。全流程纯CPU可跑,配套交付完整Python源码、9章实战教程(md/html/pdf三种格式)、100张自造样本及YOLO标签、训练曲线与混淆矩阵截图、Tkinter界面和答辩PPT。 系统围绕五项功能展开:同一套推理逻辑覆盖图像、视频、摄像头三种来源;图形界面选文件即检测,画面叠彩色框标注病名与置信度,右侧面板同步给出症状与防治措施;检测记录自动写入CSV,统计页一键生成四类病害占比饼图;训练结束自动产出损失与精度曲线;内置防治建议文案库,检测命中即回填面板。 选型用YOLOv8n nano档,参数量约320万、权重约6.2MB,CPU单帧几十毫秒,小样本训练5到8分钟完成。数据由data_gen.py程序化绘制100张叶片图,固定随机种子,重跑分布一致,不依赖公开数据集。依赖全部锁版本,教程第7章整理了19条真实报错及排查方法,第8章给出换模型档、换PlantVillage数据集的迁移路径。 适合做计算机视觉方向课程设计或毕业设计的学生,也适合智慧农业方向想了解检测模型如何落进业务面板的学习者。不需要GPU,不需要系统学过目标检测,教程第3章会按需补齐原理。 使用时先pip install -r requirements.txt装好依赖,download_data.py下载预训练权重,data_gen.py生成样本后train.py训练,完成后用main.py infer批量检测或main.py gui启动界面。

五款主流AI工具实战对比:教师、法务、小企业主如何选对不踩坑

AI工具的本质不是通用智能,而是面向具体工作流的效率增强器。从语音转写、长文本解析、代码生成到垂直场景嵌入,不同模型在多模态输入、上下文理解、结构化输出等核心能力上存在显著差异。技术价值不在于参数大小,而在于能否精准匹配用户角色(如教师备课、小微企业财税、学术文献处理)的真实约束——包括政策时效性、文件格式兼容性、生态集成度与安全策略边界。本文基于27个API实测与147组提示词调优,聚焦豆包的语音降噪、Kimi的128K逻辑拆解、DeepSeek的AST代码建模、纳米AI的行业知识图谱及腾讯元宝的微信协议

weixin_30945039的博客 536

数据挖掘实战:决策树 + 聚类 + 关联规则,零 Key 离线可跑(Python 源码)

数据挖掘实战包。用 Python 标准库实现 CART 风格决策树、K-Means 聚类与 Apriori 关联规则,在虚构「星河便利店」会员表和购物篮上跑通分类、分群与购物篮分析,并给出准确率、轮廓系数、支持度/置信度/提升度。零第三方依赖、零 API Key 即可离线跑通。含 4 个样例、离线冒烟、中文 README(含「使用方法」)。均为作者原创可运行源码。MIT。作者 blog.csdn.net/mtl1994

科技中介服务机构在技术供需对接中如何实现服务流程的自动化与标准化?.docx

科技中介服务机构在技术供需对接中如何实现服务流程的自动化与标准化?

Java大模型 ~ 智能医疗

LangChain4j 的目标是简化将大语言模型(LLM - Large Language Model)集成到 Java 应用程序中的过程。阿里云百炼是 2023 年 10 月推出的。它集成了阿里的通义系列大模型和第三方大模型,涵盖文本、图像、音视频等不同模态。功能优势:集成超百款大模型 API,模型选择丰富;5-10 分钟就能低代码快速构建智能体,应用构建高效;提供全链路模型训练、评估工具及全套应用开发工具,模型服务多元;在线部署可按需扩缩容,新用户有千万 token 免费送,业务落地成本低。

zouyang920的博客 1000

产业园区如何高效对接企业技术需求与高校科研成果?.docx

科易网基于AI模型+行业大数据,在技术转移、成果转化、技术经纪、知识产权、产业创新、科技招商等垂直领域的多样化应用场景,推出科技创新领域的AI+工具与数智化解决方案,推动科技创新与产业创新智能化发展。

JB3-9-SpringAI(一)

基础对话,通义千问,SpringAI,SpringAIAlibaba,AI对话,文生图,文生音频,文生视频,AI记忆,RAG,ETL,向量库,ToolCalling,MCP,Graph 等。

周航宇的博客 3924

国央企创新负责人如何快速掌握本地科技资源分布与匹配情况?.docx

科易网基于AI模型+行业大数据,在技术转移、成果转化、技术经纪、知识产权、产业创新、科技招商等垂直领域的多样化应用场景,推出科技创新领域的AI+工具与数智化解决方案,推动科技创新与产业创新智能化发展。

国央企创新负责人如何实现科技成果的精准识别与高效筛选?.docx

科易网基于AI模型+行业大数据,在技术转移、成果转化、技术经纪、知识产权、产业创新、科技招商等垂直领域的多样化应用场景,推出科技创新领域的AI+工具与数智化解决方案,推动科技创新与产业创新智能化发展。

技术文章系列整理(持续更新)

本博客有很多关于性能及测试开发的文章。我试着对你可能感兴趣的一些重要文章进行分类。

7DGroup,让技术更有价值! 1万+

高校技术转移办公室人员如何系统化管理科技成果信息?.docx

科易网基于AI模型+行业大数据,在技术转移、成果转化、技术经纪、知识产权、产业创新、科技招商等垂直领域的多样化应用场景,推出科技创新领域的AI+工具与数智化解决方案,推动科技创新与产业创新智能化发展。

上一篇: ADS2023实战:FR4基板微带天线设计全流程(附阻抗匹配技巧)
下一篇: 微信小程序动态组件实战:从零搭建一个可复用的按钮组件(附完整代码)
花椒哥拜托了
博客等级 码龄10年 43粉丝 1578原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值