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。建议创建时注意以下几点:
- 环境隔离:为开发、测试、生产环境分别创建不同的API Key
- 权限控制:根据实际需求设置适当的权限范围
- 额度监控:设置使用额度告警,避免意外费用
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
895



被折叠的 条评论
为什么被折叠?



