如何用Forest方便快捷地在SpringBoot项目中对接DeepSeek
一. 环境要求
JDK 8 / 17
SpringBoot 2.x / 3.x
Forest 1.6.4+
Fastjson2
依赖配置
除了 SpringBoot 和 Lombok 等基础框架之外,再加上 Forest 和 Fastjson2 的依赖
<!-- Forest框架 -->
<dependency>
<groupId>com.dtflys.forest</groupId>
<artifactId>forest-spring-boot-starter</artifactId>
<version>1.6.4</version>
</dependency> <!-- Fastjson2 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.53</version>
</dependency>

二. 申请 DeepSeek 的 API Key
打开 DeepSeek 官网,进入到 API Key 的管理页面(DeepSeek),就能找到您的 API Key。
如果还没有 KEY,可以点击页面下方的创建API Key按钮

创建完之后,会弹出一个对话框告诉您新生成的 API Key 字符串,然后要及时把它复制下来保存到一个安全的地方。
三. 配置项目
进入 SpringBoot 的配置文件application.yml,加入以下代码:
# Forest 框架配置
forest:
connect-timeout: 10000 # 请求连接超时时间
read-timeout: 3600000 # 请求数据读取超时时间,越长越好
variables:
apiKey: YOUR_API_KEY # 替换为您申请到的 API Key
model: deepseek-reasoner # DeepSeek 支持的模型,R1 模型

四. 创建声名式接口
Forest 支持以声名式的方式发送 HTTP 请求,以下代码就是将 DeepSeek API 请求以声名式接口的方式进行定义
public interface DeepSeek {
@Post(
url = "https://api.deepseek.com/chat/completions",
contentType = "application/json",
headers = "Authorization: Bearer {apiKey}",
data = "{\"messages\":[{\"content\":\"{content}\",\"role\":\"user\"}],\"model\":\"{model}\",\"stream\":true}")
ForestSSE completions(@Var("content") String content);
}

以上的代码意思也很明显,调用该接口方法就会发送一个POST请求,URL 地址为 https://api.deepseek.com/chat/completions
其中 {apiKey} 和 {model} 的意思为读取配置文件中的 apiKey 字段,{content} 则是读取 @Var("content") 注解修饰的参数。 并且请求体数据为官网文档提供的 JSON 字符串,然后通过{变量名}这种字符串模板占位符的形式拼接出您想要的参数。
接口方法的返回类型为ForestSSE,这是 Forest 框架提供的内置类型,主要用于接受和处理 SSE 事件流消息。
五. 调用接口
在声名式接口创建完之后,可以通过 Spring 的@Resouce注解将此接口实例注入到启动类中,Forest框架会利用动态代理模式自动生成相应的接口代理类实例,并将其自动注入到您所需要调用的类中。
@Resource
private DeepSeek deepSeek;

然后就可以调用接口进行发送请求的操作了,并设置Lambda表达式来接收和处理返回的 SSE 流式事件消息
@SpringBootApplication
public class DeepSeekExampleApplication implements CommandLineRunner { // DeepSeek 声名式接口
@Resource
private DeepSeek deepSeek; @Override
public void run(String... args) {
// 调用声明式接口方法
deepSeek.completions("你好,你是谁?")
.setOnMessage(event -> {
// 接受和处理 SSE 事件
try {
// 获取消息数据,并反序列化为 DeepSeekResult 类
DeepSeekResult result = event.value(DeepSeekResult.class);
// 打印 DeepSeekResult 对象中的消息内容
System.out.print(result.content());
} catch (Exception e) {
}
})
.listen(SSELinesMode.SINGLE_LINE); // 监听 SSE,并设置为单行消息模式
} public static void main(String[] args) {
try {
SpringApplication.run(DeepSeekExampleApplication.class, args);
} catch (Throwable th) {
th.printStackTrace();
}
}
}

其中,DeepSeekResult 是根据返回的消息格式定义的数据类,具体代码如下
@Data
public class DeepSeekResult { private String id; private String object; private Integer created; private String model; @JSONField(name = "system_fingerprint")
private String systemFingerprint; private List<JSONObject> choices; // 获取消息中的 choices[0].delta.content
public String content() {
List<JSONObject> choices = getChoices();
if (CollectionUtil.isNotEmpty(choices)) {
JSONObject chooseJson = choices.get(0);
DeepSeekResultChoice choice = chooseJson.toJavaObject(DeepSeekResultChoice.class);
return choice.getDelta().getContent();
}
return "";
}
}

其他的数据类包括 DeepSeekResultChoice 类也都类似。如果要看具体代码,在文章末尾会提供代码仓库地址。
六. 应答测试
调用方法写完之后,我们就可以跑一下代码看看了,点击 Run 之后可以看到控制台日志会打印以下内容

日志上半部分POST https://api.deepseek.com/chat/completions HTTPS [SSE]这类信息为 Forest 的请求日志,会告诉您发出去的 HTTP 请求信息中有些什么数据和参数。
而下半部分 “您好!我是由中国的深度求索(DeepSeek)公司开发的智能助手DeepSeek-R1...” 自然就是 DeepSeek 的回答了。
七. 思维链
以上的代码案例,只会返回 DeepSeek 的回答内容,不包含他的思考过程,拿怕模型是DeepSeek-R1也一样。如果要打印出思维链,就要修改一下代码
首先要修改 DeepSeekResult 类中的 content() 方法
@Data
public class DeepSeekResult { private String id; private String object; private Integer created; private String model; @JSONField(name = "system_fingerprint")
private String systemFingerprint; private List<JSONObject> choices; // 获取消息中的 choices[0].delta.reasoning_content
// 或 choices[0].delta.content
// 是否为思维内容,通过 DeepSeekContent.isReasoning 来标识
public DeepSeekContent content() {
List<JSONObject> choices = getChoices();
if (CollectionUtil.isNotEmpty(choices)) {
JSONObject chooseJson = choices.get(0);
DeepSeekResultChoice choice = chooseJson.toJavaObject(DeepSeekResultChoice.class);
String reasoningContent = choice.getDelta().getReasoningContent();
// 判断是否存在 reasoningContent,存在就是思维链内容,否则就是存粹的回答内容
if (StringUtils.isNotEmpty(reasoningContent)) {
return new DeepSeekContent(true, reasoningContent);
}
return new DeepSeekContent(false, choice.getDelta().getContent());
}
return new DeepSeekContent();
}
}

添加 DeepSeekContent 类
@Data
public class DeepSeekContent {
// 是否为思考过程内容
private boolean reasoning = false;
// DeepSeek 回答的具体内容
private String content = ""; public DeepSeekContent() {
} public DeepSeekContent(boolean reasoning, String content) {
this.reasoning = reasoning;
this.content = content;
}
}

最后,修改接口的调用部分
@SpringBootApplication
public class DeepSeekExampleApplication implements CommandLineRunner { // DeepSeek 声名式接口
@Resource
private DeepSeek deepSeek; @Override
public void run(String... args) {
// 标志位:是否为第一次接收到到思维链内容
AtomicBoolean isFirstReasoning = new AtomicBoolean(false);
// 调用声明式接口方法
deepSeek.completions("1+1等于几?")
.setOnMessage(event -> {
try {
DeepSeekResult result = event.value(DeepSeekResult.class);
DeepSeekContent content = result.content();
// 通过 CAS 判断是否第一次接收到到思维链内容
// 如果是,则打印出<思维链>标签
if (content.isReasoning() && isFirstReasoning.compareAndSet(false, true)) {
System.out.println("<思维链>");
System.out.print(content.getContent());
} else if (!content.isReasoning() && isFirstReasoning.compareAndSet(true, false)) {
// 当 isFirstReasoning 由 true 转为 false
// 则表明消息从思维链内容转向正式回答内容
System.out.print(content.getContent());
System.out.println("\n</思维链>\n");
} else {
// 打印正常的思维链或正式回答内容
System.out.print(Opt.ofBlankAble(content.getContent()).orElse(""));
}
} catch (Exception e) {
}
})
.listen(SSELinesMode.SINGLE_LINE);
} public static void main(String[] args) {
try {
SpringApplication.run(DeepSeekExampleApplication.class, args);
} catch (Throwable th) {
th.printStackTrace();
}
}
}

八. 思维链消息测试
接下来就可以运行程序测试了,看看日志中是否包含了思维链的过程

从日志中可以看出,程序正常运行了,其中被包裹在<思维链>和</思维链>标签中间的部分就是 DeepSeek 告诉我们的思维过程。 而在</思维链>结束标签之后的文字就是他的正式回答内容。
九. 错误处理
本文案例调用的是 DeepSeek 官方的 API。由于众所周知的原因,调用接口时极有可能发生401等网络错误。
遇到这种请求,加一个拦截器就完事了
// Forest 的 SSE 请求拦截器
public class DeepSeekInterceptor implements SSEInterceptor { // 接受到请求响应时会自动调用该方法
@Override
public ResponseResult onResponse(ForestRequest request, ForestResponse response) {
// 判断请求是否发生错误,如 401、404 等等
if (response.isError()) {
// 如有错,就打印“服务端繁忙,请稍后再试”
System.out.println("服务端繁忙,请稍后再试");
return success();
}
return proceed();
}
}

然后,将拦截器绑定到接口上
// 为整个接口绑定拦截器
@BaseRequest(interceptor = DeepSeekInterceptor.class)
public interface DeepSeek { @Post(
url = "https://api.deepseek.com/chat/completions",
contentType = "application/json",
headers = "Authorization: Bearer {apiKey}",
data = "{\"messages\":[{\"content\":\"{content}\",\"role\":\"user\"}],\"model\":\"{model}\",\"stream\":true}")
ForestSSE completions(@Var("content") String content);
}

十. 总结
可以看到,通过 Forest 这种声名式的形式来对接 DeepSeek API,相比于 OkHttp 和 HttpClient 有很多明显的好处。除了代码简洁,容易实现之外,更重要的是声名式代码天然更容易解耦。文本代码很自然的就实现了在参数配置、HTTP请求参数、以及接口调用的业务逻辑之间实现了代码解耦。如果要修改 API Key 或者模型,直接该配置文件就行。如果要修改 HTTP 的 URL 或参数,可以直接改声名式接口,而不会影响到调用接口的业务代码。而且可以很自然地将 DeepSeek API 的 HTTP 代码统一放到一个接口类中,方便管理,而且请求中的 URL、请求头、请求体参数都都一目了然。
如何用Forest方便快捷地在SpringBoot项目中对接DeepSeek的更多相关文章
- SpringBoot12 QueryDSL01之QueryDSL介绍、springBoot项目中集成QueryDSL
1 QueryDSL介绍 1.1 背景 QueryDSL的诞生解决了HQL查询类型安全方面的缺陷:HQL查询的扩展需要用字符串拼接的方式进行,这往往会导致代码的阅读困难:通过字符串对域类型和属性的不安 ...
- 在SpringBoot项目中添加logback的MDC
在SpringBoot项目中添加logback的MDC 先看下MDC是什么 Mapped Diagnostic Context,用于打LOG时跟踪一个“会话“.一个”事务“.举例,有一个web ...
- 自身使用的springboot项目中比较全的pom.xml
在学习的时候常建新的项目,mark下商用的jar <dependency> <groupId>org.mybatis</groupId> <artifactI ...
- springboot 项目中获取默认注入的序列化对象 ObjectMapper
在 springboot 项目中使用 @SpringBootApplication 会自动标记 @EnableAutoConfiguration 在接口中经常需要使用时间类型,Date ,如果想要格式 ...
- springboot项目中js、css静态文件路径访问
springboot静态文件访问的问题,相信大家也有遇到这个问题,如下图项目结构. 项目结构如上所示,静态页面引入js.css如下所示. 大家肯定都是这样写的,但是运行的话就是出不来效果,图片也不显示 ...
- 解决springboot项目中@Value注解参数值为null的问题
1.错误场景: springboot项目中在.properties文件(.yml)文件中配置了属性值,在Bean中使用@Value注解引入该属性,Bean的构造器中使用该属性进行初始化,此时有可能会出 ...
- springboot项目中引用其他springboot项目jar
1. 剔除要引入的springboot项目中不需要的文件:如Application和ApplicationTests等 2.打包 不能使用springboot项目自带的打包插件进行打包: 3.打包 4 ...
- 在前后端分离的SpringBoot项目中集成Shiro权限框架
参考[1].在前后端分离的SpringBoot项目中集成Shiro权限框架 参考[2]. Springboot + Vue + shiro 实现前后端分离.权限控制 以及跨域的问题也有涉及
- 五分钟后,你将学会在SpringBoot项目中如何集成CAT调用链
买买买结算系统 一年一度的双十一购物狂欢节就要到了,又到剁手党们开始表演的时刻了.当我们把种草很久的商品放入购物车以后,点击"结算"按钮时,就来到了买买买必不可少的结算页面了.让我 ...
- SpringBoot项目中遇到的BUG
1.启动项目的时候报错 1.Error starting ApplicationContext. To display the auto-configuration report re-run you ...
随机推荐
- 【数据库】MySQL概念性基础知识期末复习
选择题 第一章 3 二维表结构--数据模型--关系数据模型 5 描述全部数据整体逻辑结构--模式 6 逻辑数据独立性--模式变,外模式和应用程序不变 7 物理数据独立性--内模式变,外模式和应用程序不 ...
- Pytorch的主要组成模块
Pytorch的主要组成模块 一.基本配置 对于一个PyTorch项目,我们需要导入一些Python常用的包来帮助我们快速实现功能.常见的包有os.numpy等,此外还需要调用PyTorch自身一些模 ...
- Pytorch Layer层总结
卷积层 nn.Conv1d 对由多个输入平面组成的输入信号应用一维卷积. nn.Conv2d 在由多个输入平面组成的输入信号上应用 2D 卷积. nn.Conv3d 对由多个输入平面组成的输入信号应用 ...
- Github + Jekyll 搭建项目wiki
网站托管 创建新仓库 创建以自己名字为前缀, .github.io为后缀的仓库 在仓库的Settings中的Pages里设置Build and deployment为Github Actio ...
- Python多分类Logistic回归详解与实践
在机器学习中,Logistic回归是一种基本但非常有效的分类算法.它不仅可以用于二分类问题,还可以扩展应用于多分类问题.本文将详细介绍如何使用Python实现一个多分类的Logistic回归模型,并给 ...
- IT系统架构的演化-copy
前言 一个成熟的大型网站(如淘宝.天猫.腾讯等)的系统架构并不是一开始设计时就具备完整的高性能.高可用.高伸缩等特性的,它是随着用户量的增加,业务功能的扩展逐渐演变完善的,在这个过程中,开发模式.技术 ...
- Mina源码-整体解读
阅读笔记(一)-整体解读 Apache MINA is a network application framework which helps users develop high performan ...
- C语言这种单细胞编程语言和指针的一些理解
转行做嵌入式也有一段时间了,原来做c#以及一些其它的上层语言, 本想的是也就是仅仅是语法上有点不一样.但是实际使用的切身体会真的是只有自己才知道.很多方面刷新了我对c语言以及计算机结构体系的认知 ,绝 ...
- 使用 Dify + LLM 构建精确任务处理应用
在构建基于大语言模型(LLM)的应用时,如何确保返回结果的准确性和可重复性是一个常见的挑战.本文将结合 Dify + LLM 的使用经验,介绍如何设计一个精确的 LLM 任务处理流程,避免传统 LLM ...
- 用SignalR和Layui搭建自己的web聊天网站
1.开发背景 之前是做项目一直有一个困扰,就是如何进行及时通讯,本人.Net开发,不太想用别人的接口,然后偶然的机会知道了SignalR,那么什么是SignalR呢? 2.SignalR简介 ASP. ...