如何用Forest方便快捷地在SpringBoot项目中对接DeepSeek

news/2025/3/9 9:10:04/文章来源:https://www.cnblogs.com/dtflyx/p/18756951

 一. 环境要求

  • 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 Keys 页面

 

创建完之后,会弹出一个对话框告诉您新生成的 API Key 字符串,然后要及时把它复制下来保存到一个安全的地方。

三. 配置项目

进入 SpringBoot 的配置文件application.yml,加入以下代码:

# Forest 框架配置
forest:connect-timeout: 10000      # 请求连接超时时间read-timeout: 3600000       # 请求数据读取超时时间,越长越好
  variables:apiKey: YOUR_API_KEY      # 替换为您申请到的 API Keymodel: 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 声名式接口
    @Resourceprivate DeepSeek deepSeek; @Overridepublic 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.contentpublic 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 声名式接口
    @Resourceprivate DeepSeek deepSeek; @Overridepublic 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 {// 接受到请求响应时会自动调用该方法
    @Overridepublic 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: 声明式HTTP客户端API框架,让Java发送HTTP/HTTPS请求不再难。它比OkHttp和HttpClient更高层,是封装调用第三方restful api client接口的好帮手,是retrofit和feign之外另一个选择。通过在接口上声明注解的方式配置HTTP请求接口 - Gitee.com

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/894941.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

5. MySQL 存储引擎(详解说明)

5. MySQL 存储引擎(详解说明) @目录5. MySQL 存储引擎(详解说明)1. 查看存储引擎2. 设置系统默认的存储引擎3. 设置表的存储引擎3.1 创建表时指定存储引擎3.2 修改表的存储引擎4. 引擎介绍4.1 InnoDB 引擎:具备外键支持功能的事务存储引擎4.2 MyISAM 引擎:主要的非事务处…

130道基础OJ编程题之: 68~77

130道基础OJ编程题之: 68~77 @目录130道基础OJ编程题之: 68~7768:BC72 平均身高69:BC74 HTTP状态码70:BC75 数字三角形71:BC76 公务员面试72:BC77 有序序列插入一个数73:BC78 筛选法求素数74: BC79 图像相似度75: BC80 登录验证76: BC85 包含数字9的数77:BC86 奇偶统计最后:68…

如何选择既能支持稳定传输 又适配信创环境的文件传输系统?

在日常工作开展中,财政局作为政府单位中负责财政收支、预算管理和财务监督的重要部门,在文件传输方面存在多种场景及需求。财政局会存在与其他政府部门间协作,向上级财政部门传输文件以及财政局各部门间传输预算报告、财务报告等场景,需要实现快速、准确的流转。财政局一般…

本地新建js公用库组件并打包发布到npm仓库详细说明

有时候,我们想在本地开发一个公用js函数组件库,并上传到npm仓库供开发者使用,本文就详细介绍了从新建本地项目到发布至npm仓库的整过过程,供大家学习!1、注册账号 首先我们去npm官网注册一个账号,注册成功后请牢记账号和密码。 需要注意的是,现在npm登录好像启用了双因素…

51CTO:《DeepSeek入门宝典(全4册)》 - 官方完整版 - PDF免费下载

由51CTO智能研究院、51CTO传媒、51CTO学堂联合倾力打造了这份《DeepSeek入门宝典》,这份DeepSeek宝典共分为四册:《技术解析篇》、《开发实战篇》、《个人使用篇》、《行业应用篇》,长达80余页。它涵盖了技术解析、开发实战、个人使用以及行业应用等多个维度,是帮助每一位通…

Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!

在众多开源项目中,高颜值、功能强大且部署简单的项目往往更能俘获开发者的心。然而,实际部署 Web 应用时,面对数据库、缓存、消息队列等复杂的依赖关系,常常令人头疼。Docker 的开源为我们普及了容器化技术,能够快速打包和部署 Web 应用,让一切变得轻松简单。但当你从开发…

到底是谁还没搞清楚 OMS 和 WMS的区别?

聊到 OMS(订单管理系统) 和 WMS(仓库管理系统),很多人第一反应是:“不就是订单和仓库嘛?谁还分不清?” 但等到真正操作的时候,很多企业就开始搞混了:“WMS 不是也能管库存吗?为什么还要 OMS?” “OMS 负责订单,那 WMS 发货的时候为啥还要管订单?” “库存到底是 …

VMware ESXi 8.0U2d macOS Unlocker OEM BIOS 标准版和厂商定制版

VMware ESXi 8.0U2d macOS Unlocker & OEM BIOS 标准版和厂商定制版VMware ESXi 8.0U2d macOS Unlocker & OEM BIOS 标准版和厂商定制版 ESXi 8.0U2 标准版,Dell (戴尔)、HPE (慧与)、Lenovo (联想)、Inspur (浪潮)、Cisco (思科)、Hitachi (日立)、Fujitsu (富士通)、…

当AI学会“读心”,浙大DeepSeek第三期掌握AI分布式学习,多场景下的「超级外挂」!

当AI学会“读心”,浙大DeepSeek第三期掌握AI分布式学习,多场景下的「超级外挂」!"当AI不仅能写诗画画,还能参与社会治理,我们的世界会变成什么样?"最近浙大的DeepSeek公开课第三期,直接把这个问题抛给了大众。这场线上讲座一边拆解大模型的技术内核、展示如何…

SecureCRT报错--文件名目录名或卷标语法不正确

SecureCRT版本:Version 7.0.0 (build 326)绿色版 1.错误展示2.解决办法 删除C:\Users\Administrator\AppData\Roaming\SecureCRT.dmp后重新解压

读DAMA数据管理知识体系指南12数据设计

数据建模工具、血缘、分析、元数据库等行业工具介绍,命名、数据库设计最佳实践,开发标准,评审质量,管理版本与集成,以及模型度量指标等关键方面。1. 工具 1.1. 数据建模工具1.1.1. 自动实现数据建模功能的软件1.1.2. 入门级数据建模工具提供基本的绘图功能,以便用户可以轻…

hive安装--远程模式

系统版本:CentOS Linux release 7.9.2009 (Core)ps: 最小化安装一、安装MySQL 1.下载 1.1安装包 官网:https://downloads.mysql.com/archives/community/1.2驱动 官网:https://downloads.mysql.com/archives/c-j/ps mysql-connector-java-5.1.47.jar,要这个2.安装 2.1安装依…