Spring集成DeepSeek方法1:自定义Client集成
本文介绍如何通过自定义HTTP客户端的方式,在Spring Boot应用中集成DeepSeek API。这种方法提供了最大的灵活性和控制力,适合需要深度定制或特殊需求的场景。
·
Spring集成DeepSeek方法1:自定义Client集成
概述
本文介绍如何通过自定义HTTP客户端的方式,在Spring Boot应用中集成DeepSeek API。这种方法提供了最大的灵活性和控制力,适合需要深度定制或特殊需求的场景。
前置条件
- Java 17+
- Spring Boot 3.x
- DeepSeek API密钥(从 https://platform.deepseek.com 获取)
- Maven或Gradle构建工具
项目依赖
Maven (pom.xml)
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot WebFlux (推荐用于异步HTTP调用) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- Jackson用于JSON处理 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- Lombok (可选,简化代码) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Spring Boot Configuration Processor -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
Gradle (build.gradle)
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation 'com.fasterxml.jackson.core:jackson-databind'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
}
配置类
application.yml
deepseek:
api:
base-url: https://api.deepseek.com
api-key: ${DEEPSEEK_API_KEY:your-api-key-here}
model: deepseek-chat
timeout: 60000
max-retries: 3
DeepSeekProperties.java
package com.example.deepseek.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
@ConfigurationProperties(prefix = "deepseek.api")
public class DeepSeekProperties {
private String baseUrl = "https://api.deepseek.com";
private String apiKey;
private String model = "deepseek-chat";
private Long timeout = 60000L;
private Integer maxRetries = 3;
}
DeepSeekConfig.java
package com.example.deepseek.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
@Configuration
public class DeepSeekConfig {
@Bean
public WebClient deepSeekWebClient(DeepSeekProperties properties) {
// 配置内存缓冲区大小,用于处理大响应
ExchangeStrategies strategies = ExchangeStrategies.builder()
.codecs(configurer -> configurer
.defaultCodecs()
.maxInMemorySize(16 * 1024 * 1024)) // 16MB
.build();
return WebClient.builder()
.baseUrl(properties.getBaseUrl())
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer " + properties.getApiKey())
.exchangeStrategies(strategies)
.build();
}
}
数据模型
ChatMessage.java
package com.example.deepseek.model;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ChatMessage {
public enum Role {
SYSTEM, USER, ASSISTANT
}
private Role role;
private String content;
}
ChatRequest.java
package com.example.deepseek.model;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ChatRequest {
private String model;
private List<ChatMessage> messages;
private Double temperature;
private Integer maxTokens;
private Double topP;
private Boolean stream;
public static ChatRequest of(String model, List<ChatMessage> messages) {
return ChatRequest.builder()
.model(model)
.messages(messages)
.build();
}
}
ChatResponse.java
package com.example.deepseek.model;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ChatResponse {
private String id;
private String object;
private Long created;
private String model;
private List<Choice> choices;
private Usage usage;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class Choice {
private Integer index;
private ChatMessage message;
private String finishReason;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class Usage {
private Integer promptTokens;
private Integer completionTokens;
private Integer totalTokens;
}
}
ErrorResponse.java
package com.example.deepseek.model;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ErrorResponse {
private Error error;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class Error {
private String message;
private String type;
private String code;
}
}
服务层实现
DeepSeekService.java
package com.example.deepseek.service;
import com.example.deepseek.config.DeepSeekProperties;
import com.example.deepseek.model.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClientResponseException;
import reactor.core.publisher.Mono;
import reactor.util.retry.Retry;
import java.time.Duration;
import java.util.List;
@Slf4j
@Service
@RequiredArgsConstructor
public class DeepSeekService {
private final WebClient deepSeekWebClient;
private final DeepSeekProperties properties;
/**
* 发送聊天请求
*/
public Mono<String> chat(String userMessage) {
return chat(userMessage, null);
}
/**
* 发送带系统提示的聊天请求
*/
public Mono<String> chat(String userMessage, String systemPrompt) {
List<ChatMessage> messages = buildMessages(userMessage, systemPrompt);
ChatRequest request = ChatRequest.builder()
.model(properties.getModel())
.messages(messages)
.temperature(0.7)
.maxTokens(4096)
.build();
return chat(request);
}
/**
* 发送自定义聊天请求
*/
public Mono<String> chat(ChatRequest request) {
return deepSeekWebClient.post()
.uri("/v1/chat/completions")
.bodyValue(request)
.retrieve()
.bodyToMono(ChatResponse.class)
.retryWhen(Retry.backoff(properties.getMaxRetries(), Duration.ofSeconds(1))
.filter(this::isRetryableException))
.map(response -> {
if (response.getChoices() == null || response.getChoices().isEmpty()) {
throw new RuntimeException("No response choices returned");
}
return response.getChoices().get(0).getMessage().getContent();
})
.doOnError(error -> log.error("DeepSeek API call failed", error));
}
/**
* 流式聊天
*/
public Mono<String> chatStream(String userMessage) {
return chatStream(userMessage, null);
}
/**
* 流式聊天(带系统提示)
*/
public Mono<String> chatStream(String userMessage, String systemPrompt) {
List<ChatMessage> messages = buildMessages(userMessage, systemPrompt);
ChatRequest request = ChatRequest.builder()
.model(properties.getModel())
.messages(messages)
.temperature(0.7)
.stream(true)
.build();
return deepSeekWebClient.post()
.uri("/v1/chat/completions")
.bodyValue(request)
.retrieve()
.bodyToFlux(String.class)
.collectList()
.map(chunks -> String.join("", chunks));
}
private List<ChatMessage> buildMessages(String userMessage, String systemPrompt) {
List<ChatMessage> messages = new java.util.ArrayList<>();
if (systemPrompt != null && !systemPrompt.isBlank()) {
messages.add(ChatMessage.builder()
.role(ChatMessage.Role.SYSTEM)
.content(systemPrompt)
.build());
}
messages.add(ChatMessage.builder()
.role(ChatMessage.Role.USER)
.content(userMessage)
.build());
return messages;
}
private boolean isRetryableException(Throwable throwable) {
if (throwable instanceof WebClientResponseException) {
WebClientResponseException ex = (WebClientResponseException) throwable;
int statusCode = ex.getStatusCode().value();
// 重试 429 (Too Many Requests), 500, 502, 503, 504
return statusCode == 429 || statusCode >= 500;
}
return false;
}
}
控制器层
DeepSeekController.java
package com.example.deepseek.controller;
import com.example.deepseek.model.ChatRequest;
import com.example.deepseek.service.DeepSeekService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import reactor.core.publisher.Mono;
import java.io.IOException;
@RestController
@RequestMapping("/api/deepseek")
@RequiredArgsConstructor
public class DeepSeekController {
private final DeepSeekService deepSeekService;
/**
* 简单聊天接口
*/
@PostMapping("/chat")
public Mono<String> chat(@RequestBody ChatRequest request) {
return deepSeekService.chat(request);
}
/**
* 快速聊天接口
*/
@GetMapping("/chat")
public Mono<String> quickChat(@RequestParam String message,
@RequestParam(required = false) String systemPrompt) {
return deepSeekService.chat(message, systemPrompt);
}
/**
* 流式聊天接口 (SSE)
*/
@GetMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter chatStream(@RequestParam String message,
@RequestParam(required = false) String systemPrompt) {
SseEmitter emitter = new SseEmitter(60000L);
deepSeekService.chatStream(message, systemPrompt)
.subscribe(
response -> {
try {
emitter.send(SseEmitter.event().data(response));
emitter.complete();
} catch (IOException e) {
emitter.completeWithError(e);
}
},
error -> emitter.completeWithError(error)
);
return emitter;
}
}
异常处理
DeepSeekExceptionHandler.java
package com.example.deepseek.exception;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.reactive.function.client.WebClientResponseException;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@RestControllerAdvice
public class DeepSeekExceptionHandler {
@ExceptionHandler(WebClientResponseException.class)
public ResponseEntity<Map<String, Object>> handleWebClientResponseException(WebClientResponseException ex) {
log.error("DeepSeek API error: {} - {}", ex.getStatusCode(), ex.getResponseBodyAsString());
Map<String, Object> error = new HashMap<>();
error.put("timestamp", LocalDateTime.now());
error.put("status", ex.getStatusCode().value());
error.put("error", ex.getStatusText());
error.put("message", ex.getMessage());
return ResponseEntity.status(ex.getStatusCode()).body(error);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<Map<String, Object>> handleGenericException(Exception ex) {
log.error("Unexpected error", ex);
Map<String, Object> error = new HashMap<>();
error.put("timestamp", LocalDateTime.now());
error.put("status", HttpStatus.INTERNAL_SERVER_ERROR.value());
error.put("error", "Internal Server Error");
error.put("message", ex.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
}
}
完整示例:Spring Boot主类
DeepSeekApplication.java
package com.example.deepseek;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DeepSeekApplication {
public static void main(String[] args) {
SpringApplication.run(DeepSeekApplication.class, args);
}
}
使用示例
测试类
package com.example.deepseek;
import com.example.deepseek.service.DeepSeekService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import reactor.test.StepVerifier;
@SpringBootTest
class DeepSeekServiceTest {
@Autowired
private DeepSeekService deepSeekService;
@Test
void testChat() {
StepVerifier.create(deepSeekService.chat("你好,请介绍一下你自己"))
.expectNextMatches(response -> response != null && !response.isEmpty())
.verifyComplete();
}
@Test
void testChatWithSystemPrompt() {
StepVerifier.create(deepSeekService.chat(
"解释什么是量子计算",
"你是一位物理学教授,请用通俗易懂的语言解释复杂概念"
))
.expectNextMatches(response -> response != null && response.length() > 50)
.verifyComplete();
}
}
高级特性
1. 请求拦截器
package com.example.deepseek.interceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
import reactor.core.publisher.Mono;
@Slf4j
@Component
public class DeepSeekLoggingFilter {
public ExchangeFilterFunction logRequest() {
return ExchangeFilterFunction.ofRequestProcessor(request -> {
log.info("DeepSeek API Request: {} {}", request.method(), request.url());
log.debug("Request Headers: {}", request.headers());
return Mono.just(request);
});
}
public ExchangeFilterFunction logResponse() {
return ExchangeFilterFunction.ofResponseProcessor(response -> {
log.info("DeepSeek API Response Status: {}", response.statusCode());
return Mono.just(response);
});
}
}
2. 缓存装饰器
package com.example.deepseek.cache;
import com.example.deepseek.model.ChatRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
@Slf4j
@Component
public class DeepSeekCacheService {
private final DeepSeekService deepSeekService;
public DeepSeekCacheService(DeepSeekService deepSeekService) {
this.deepSeekService = deepSeekService;
}
@Cacheable(value = "deepseekResponses", key = "#request.hashCode()")
public Mono<String> chatWithCache(ChatRequest request) {
log.debug("Cache miss, calling DeepSeek API");
return deepSeekService.chat(request);
}
}
3. 限流器
package com.example.deepseek.ratelimit;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
@Component
public class DeepSeekRateLimiter {
private final AtomicInteger requestCount = new AtomicInteger(0);
private final int maxRequestsPerMinute = 60;
private volatile long resetTime = System.currentTimeMillis() + 60000;
public Mono<Boolean> tryAcquire() {
long currentTime = System.currentTimeMillis();
if (currentTime > resetTime) {
synchronized (this) {
if (currentTime > resetTime) {
requestCount.set(0);
resetTime = currentTime + 60000;
}
}
}
int current = requestCount.incrementAndGet();
if (current > maxRequestsPerMinute) {
log.warn("Rate limit exceeded: {} requests in current minute", current);
return Mono.just(false);
}
return Mono.just(true);
}
}
优点与缺点
优点
- 完全控制:可以完全控制请求和响应的处理逻辑
- 灵活性高:易于实现自定义功能如缓存、限流、重试等
- 无额外依赖:只需Spring Boot基础依赖
- 易于调试:代码透明,便于问题排查
- 性能优化:可以使用WebFlux实现非阻塞IO
缺点
- 开发成本高:需要编写更多代码
- 维护负担:需要自行维护API兼容性
- 功能有限:需要自行实现流式输出、函数调用等高级功能
- 测试复杂:需要编写更多测试用例
适用场景
- 需要深度定制API调用逻辑的场景
- 对性能有极致要求的场景
- 需要实现特殊功能(如复杂的重试策略、缓存策略)的场景
- 学习和理解DeepSeek API的工作原理
更多推荐



所有评论(0)