先看效果:
前端调用:
deepseek - Trim
apifox调用接口:
deepseek-HD
前言:
本篇文章主要介绍个人或企业java项目使用SpringBoot引入deepseek满血版,实现在个人项目中与deepseek对话。主要使用技术栈:SpringBoot、WebFlux
2025年最火的AI编程组合非SpringBoot+DeepSeek莫属!相比OpenAI,DeepSeek三大优势:
-
✅ 网络友好:国内直接调用API,无需复杂代理 1
-
✅ 成本低廉:相同性能下价格仅为GPT-4的1/3
-
✅ 中文优化:对中文代码注释和理解能力超群
一、注册接口:
首先进入官网设置注册api并获取api-key,api-key仅在创建时可见,需在创建时复制并妥善保管
DeepSeek地址

二、构建代码(实体类):
首先在SpringBoot项目pom文件中引入WebFlux依赖:

请求实体类:
package com.cinc.ai.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* com.cinc.ai.ChatRequestDto
* DeepSeek API 请求DTO
* 用于构造发送给DeepSeek API的请求参数
* on 2025/4/2-下午5:14
*
* @author weijx魏佳旋
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ChatRequestDto {
/**
* 使用的模型名称
* 例如:
* "deepseek-chat" 表示使用DeepSeek的V3模型
* "deepseek-reasoner" 表示使用DeepSeek的R1模型(带有推理过程)
*/
private String model;
/**
* 是否启用流式响应
* true: 启用流式传输,数据会分块返回
* false: 一次性返回完整响应
*/
private boolean stream = true;
/**
* 对话消息列表
* 包含系统消息和用户消息的对话历史
* 通常以系统消息开始,后跟用户消息
*/
private List<ChatRequestMessageDto> messages;
/**
* 用于标识当前对话会话,在首次请求时,可以不设置,由DeepSeek自动生成
* 后续请求时,必须设置,否则会报错
*/
private String conversationId;
}
package com.cinc.ai.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* com.cinc.ai.dto.ChatRequestMessageDto
* 对话消息DTO
* 表示对话中的一个消息条目
* on 2025/4/3-下午3:38
*
* @author weijx魏佳旋
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ChatRequestMessageDto {
/**
* 消息角色
* 可选值:
* - "system": 系统消息,用于设定助手行为
* - "user": 用户发送的消息
* - "assistant": 助手的回复消息
*/
private String role;
/**
* 消息内容
* 包含角色对应的实际文本内容
*/
private String content;
}
通过指定请求实体类中的stream来设置是否为流式响应,请求时stream为true则流式响应,反之false为非流式响应
模型目前分为v3和r1,官网描述如下:
构建请求时通过指定请求实体类中的model来设置模型,deepseek-chat为V3,deepseek-reasoner为R1区别如下:
V3输出更快,效率更高,但不进行推理,输出的内容较为干练。
R1在回答之前会进行推理,并将在回答之前的思考过程进行输出,但输出更慢,且价格与V3不同,会更昂贵一些。

响应实体类:
package com.cinc.ai.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* com.cinc.ai.dto.ChatResponseDto
* DeepSeek API 响应DTO
* 包含DeepSeek API返回的完整响应信息
* on 2025/4/2-下午5:16
*
* @author weijx魏佳旋
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ChatResponseDto {
/**
* 本次对话的唯一ID
* 用于标识和追踪特定对话
*/
private String id;
/**
* 对象类型
* 例如:"chat.completion" 表示聊天完成对象
*/
private String object;
/**
* 响应创建时间戳(Unix时间)
* 表示响应生成的时间,单位为秒
*/
private Long created;
/**
* 使用的模型名称
* 与请求中的model参数对应
*/
private String model;
/**
* 生成的回复选项列表
* 通常包含一个或多个可能的回复
*/
private List<ChatChoiceDto> choices;
}
package com.cinc.ai.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* com.cinc.ai.dto.ChatChoiceDto
* 回复选项DTO
* 包含API生成的一个可能的回复及其相关信息
* on 2025/4/3-下午4:20
*
* @author weijx魏佳旋
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ChatChoiceDto {
/**
* 选项索引
* 当API生成多个回复选项时,用于区分不同选项
* 通常从0开始
*/
private Integer index;
/**
* 增量回复内容
* 在流式响应中,包含当前块的回复内容
*/
private Delta delta;
/**
* 完成原因
* 表示生成停止的原因,可能值:
* - "stop": API输出自然结束
* - "length": 达到最大token限制
* - "content_filter": 内容被过滤
* - null: 生成尚未完成(流式传输中)
*/
@JsonProperty("finish_reason")
private String finishReason;
/**
* 增量回复内容DTO
* 用于流式传输中的部分回复内容
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class Delta {
/**
* 当前消息块的角色
* 通常在流式传输开始时返回"assistant"
*/
private String role;
/**
* 当前消息块的内容
* 包含回复的部分文本内容
* 在流式传输中会分多次返回
*/
private String content;
/**
* 理解内容
* 当调用R1模型时,会提供思考过程
* 在流式传输中会分多次返回
*/
@JsonProperty("reasoning_content")
private String reasoningContent;
}
}
其中如果调用R1模型,思考过程为reasoningContent属性,如果调用V3模型,该属性为空。content则是正式回答
三、构建代码(服务类):
服务类:
package com.cinc.ai.service;
import com.cinc.ai.dto.ChatRequestDto;
import com.cinc.ai.dto.ChatResponseDto;
import reactor.core.publisher.Flux;
/**
* com.cinc.ai.service.ChatDataAiFindService
* <p>
* on 2025/5/8-上午9:07
*
* @author weijx魏佳旋
*/
public interface ChatDataAiFindService {
/**
* 流式对话调用
*
* @param requestDto 请求内容
* @return Flux<DeepSeekResponseDto>
*/
Flux<ChatResponseDto> handleChatRequest(ChatRequestDto requestDto);
/**
* 智能数据分析
*
* @param requestDto 请求内容
* @return Flux<DeepSeekResponseDto>
*/
Flux<ChatResponseDto> chatStreamAnalysis(ChatRequestDto requestDto);
/**
* 转为图表调用
*
* @param requestDto 请求内容
* @return Flux<DeepSeekResponseDto>
*/
Flux<ChatResponseDto> chatStreamTransChart(ChatRequestDto requestDto);
}
我这里由于有多种场景,所以有多个方法, 实际上只需要handleChatRequest就够,另外两个方法就不做展示了。
服务实现类:
首先构建WebClient
// 创建 DeepSeek API-official 的 WebClient
@Bean("webClientOfficial")
public WebClient webClientOfficial() {
return WebClient.builder()
.baseUrl(dkOfficialApiUrl)
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer " + dkOfficialApiKey)
.build();
}
其中apiurl为:https://api.deepseek.com/v1/chat/completions
apikey则是第一步中获取到的key
然后实现handleChatRequest方法:
/**
* 实现方法:处理聊天请求的主方法
*
* @param requestDto 请求内容
*/
@Override
public Flux<ChatResponseDto> handleChatRequest(ChatRequestDto requestDto) {
if (requestDto == null || requestDto.getMessages() == null || requestDto.getMessages().isEmpty()) {
return Flux.error(new IllegalArgumentException("请求参数不能为空"));
}
String conversationId = requestDto.getConversationId();
if (StringUtils.isEmpty(conversationId) || chatDataFindService.isConversationValid(conversationId)) {
conversationId = createNewConversation(requestDto, 1);
requestDto.setConversationId(conversationId);
}
String messageId = SequenceGenUtil.getKey();
// 获取用户输入
String userInput;
if (requestDto.getMessages() == null || requestDto.getMessages().isEmpty()) {
userInput = "";
} else {
userInput =
requestDto.getMessages().stream().filter(msg -> "user".equals(msg.getRole())).reduce((first, second) -> second).map(ChatRequestMessageDto::getContent).orElse("");
}
return Flux.defer(() ->
// 将保存操作包装为响应式流
Flux.fromIterable(requestDto.getMessages()).
then(Mono.defer(() -> Mono.just(streamChat(requestDto, finalConversationId, messageId)))).flatMapMany(flux -> flux)).
subscribeOn(Schedulers.fromExecutor(blockingTaskExecutor));
}
/**
* 处理流式聊天请求不需要查询数据库
*
* @param requestDto 包含聊天请求内容的数据传输对象
* @return Flux<ChatResponseDto> 返回响应流
*/
private Flux<ChatResponseDto> streamChat(ChatRequestDto requestDto, String conversationId, String messageId) {
StringBuilder aiResponseContent = new StringBuilder();
StringBuilder aiReasoningContent = new StringBuilder();
ChatParseJsonUtils.ThinkProcessingState.reset();
return webClientZmj.post()
.body(BodyInserters.fromObject(requestDto))
.accept(MediaType.TEXT_EVENT_STREAM)
.retrieve()
.bodyToFlux(String.class)
.filter(data -> !"[DONE]".equals(data))
.map(json -> parseJsonSafe(json, requestDto.getThink()))
.doOnNext(response -> processResponseChunk(response, aiResponseContent, aiReasoningContent, conversationId))
.onBackpressureBuffer(500)
.doFinally(signalType -> handleFinalProcessingAsync(conversationId, messageId,
aiResponseContent.toString(),
"y".equalsIgnoreCase(requestDto.getThink()) ? aiReasoningContent.toString() : null)
.exceptionally(ex -> {
log.error("异步处理失败", ex);
return null;
}))
.timeout(Duration.ofMinutes(2));
}
我这里由于业务比较复杂,代码已经精简过很多了,但对于简单实现流式响应对话来说还是有点冗余,其中有很多方法和属性是为了实现上下文对话和让deepseek查询数据库数据所用的, 如果在过程中发现某些方法或属性是没有的话,那其实就是不需要的,可以直接删除
四、构建代码(控制类):
通过以上代码构建,已经基本可以实现调用deepseek接口并获取返回值了,接下来需要控制类来编写接口。
package com.cinc.ai.controller;
import com.cinc.ai.dto.ChatRequestDto;
import com.cinc.ai.dto.ChatRequestMessageDto;
import com.cinc.ai.dto.ChatResponseDto;
import com.cinc.ai.enums.BackResultEnum;
import com.cinc.ai.enums.TransChartEnum;
import com.cinc.ai.exception.BasException;
import com.cinc.ai.service.ChatDataAiFindService;
import com.cinc.ai.utils.BackResultUtils;
import com.cinc.ai.utils.ChatMessageSystemUtils;
import com.cinc.ai.utils.EmptyUtils;
import com.cinc.ai.utils.SequenceGenUtil;
import com.cinc.ai.vo.BackResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import reactor.core.publisher.MonoProcessor;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* com.cinc.ai.controller.AiChatController
* AiChat控制层
* on 2025/4/2-下午5:18
*
* @author weijx魏佳旋
*/
@Api(tags = "ai接口调用")
@RestController
@Slf4j
public class AiChatController {
@Autowired
private ChatDataAiFindService chatDataAiFindService;
// 添加一个用于存储中断信号的Map
private final Map<String, MonoProcessor<Void>> cancelSignals = new ConcurrentHashMap<>();
@ApiOperation("流式对话调用")
@PostMapping(path = "/chatStream",
produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ChatResponseDto> chatStream(@RequestBody ChatRequestDto requestDto) {
// 中断会话用
String conversationId = getConversationId(requestDto.getConversationId());
MonoProcessor<Void> cancelSignal = MonoProcessor.create();
cancelSignals.put(conversationId, cancelSignal);
requestDto.setConversationId(conversationId);
return chatDataAiFindService.handleChatRequest(requestDto)
.takeUntilOther(cancelSignal)
.doOnSubscribe(sub -> logStreamRequest(requestDto))
.doOnNext(response -> log.debug("收到对话响应: {}", response))
.doOnError(e -> {
log.error("对话请求错误", e);
cancelSignals.remove(requestDto.getConversationId());
})
.doOnCancel(() -> {
log.info("对话已取消");
cancelSignals.remove(requestDto.getConversationId());
})
.doOnComplete(() -> {
log.info("对话请求完成");
cancelSignals.remove(requestDto.getConversationId());
});
}
@ApiOperation("中断对话调用")
@PostMapping("/cancelChatStream")
public BackResult cancelChatStream(@RequestBody ChatRequestDto requestDto) {
String conversationId = requestDto.getConversationId();
if (conversationId != null && cancelSignals.containsKey(conversationId)) {
MonoProcessor<Void> signal = cancelSignals.get(conversationId);
signal.onComplete();
cancelSignals.remove(conversationId);
log.info("成功中断对话: {}", conversationId);
return BackResultUtils.success("中断对话成功");
}
log.info("未找到可中断的对话: {}", conversationId);
return BackResultUtils.success("该对话已被中断");
}
@ApiOperation("新建会话时返回ID")
@PostMapping("/returnConversationId")
public BackResult returnConversationId() {
return BackResultUtils.success(getConversationId(""));
}
// 打印流式对话请求
private void logStreamRequest(ChatRequestDto requestDto) {
List<ChatRequestMessageDto> messages = requestDto.getMessages();
if (messages != null && !messages.isEmpty()) {
log.info("系统接受到对话请求,开始请求。用户问题:{}", messages.get(messages.size() - 1).getContent());
} else {
log.info("系统接受到对话请求,但消息列表为空");
}
}
// 获取会话ID
private String getConversationId(String conversationId) {
if (EmptyUtils.isStringEmpty(conversationId)) {
return SequenceGenUtil.getKey();
}
return conversationId;
}
}
到这里之后查看代码如果没有硬编译错误,就可以尝试调用接口去实现和deepseek对话了。
效果已经在开头展示,请求体根据实际情况来写即可,无需其他请求头。



前端代码就不在这里展示了,可以将代码发送给deepseek或者其他ai服务,让其帮你生成适配的前端代码即可。这样就可以将deepseek引入个人项目或公司项目中了,可以拿去做毕设或者在公司项目中接入新功能。
如果在学习过程中有遇到各种问题可以评论或者私信,看到就及时回复。(个人原创,如有转载请提前确认)
(目前也可以实现多轮对话也就是上下文对话,如果需要的人多的话可以再出一期)
391

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



