SpringBoot对接DeepSeek

news2025/3/20 17:57:37

文章目录

  • Spring Boot 集成 DeepSeek API 详细步骤
    • 1. 创建API Key
      • 1.访问 [DeepSeek控制台](https://platform.deepseek.com/usage) 并登录。
      • 2.点击 Create API Key 生成新密钥。
      • 3.复制并保存密钥(需在Spring Boot配置文件中使用)。
    • 2. 创建Spring Boot工程
    • 3. 配置项目依赖
    • 4. 核心代码实现
      • 4.1 定义响应实体类
      • 4.2 控制器实现(支持SSE流式响应)
    • 5. 配置文件说明
    • 6. 测试与验证
      • 6.1启动应用后,通过浏览器或工具发送请求:
      • 6.2预期输出(流式响应):

Spring Boot 集成 DeepSeek API 详细步骤

1. 创建API Key

1.访问 DeepSeek控制台 并登录。

在这里插入图片描述

2.点击 Create API Key 生成新密钥。

3.复制并保存密钥(需在Spring Boot配置文件中使用)。

2. 创建Spring Boot工程

使用 Spring Initializr 创建项目,选择以下依赖:

  • Spring Web

  • Lombok(简化实体类代码)

3. 配置项目依赖

在 pom.xml 中添加必要依赖:

<!-- OkHttp 实现HTTP请求 -->
<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>4.12.0</version>
</dependency>

<!-- FastJSON 用于JSON解析 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>2.0.48</version>
</dependency>

<!-- SSE 事件流支持 -->
<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp-sse</artifactId>
    <version>4.12.0</version>
</dependency>

4. 核心代码实现

4.1 定义响应实体类

@Data
public class AiResult {
    private Integer code;
    private String message;
    private String sid;
    private String id;
    private Long created;
    private List<AiResultChoices> choices;
    private AiResultUsage usage;
}

@Data
public class AiResultChoices {
    private AiResultDelta delta;
    private Integer index;
}

@Data
public class AiResultDelta {
    private String role;
    private String content;
}

@Data
public class AiResultUsage {
    private Integer prompt_tokens;
    private Integer completion_tokens;
    private Integer total_tokens;
}

@Data
public class ContentDto {
    private String content;
}

4.2 控制器实现(支持SSE流式响应)

/**
 * DeepSeek AI接口请求控制器
 * 功能:处理SSE(Server-Sent Events)流式请求,与DeepSeek API交互
 * 主要职责:
 * 1. 接收前端SSE请求
 * 2. 构造DeepSeek API请求
 * 3. 处理流式响应并转发给客户端
 * 4. 管理连接生命周期和异常处理
 */
@RestController
public class SeekController {
    // 日志记录器
    private static final Logger log = LoggerFactory.getLogger(SeekController.class);
    
    // 流式结束标识符(符合OpenAI标准)
    private static final String DONE_FLAG = "[DONE]";
    // HTTP请求超时时间(单位:秒)
    private static final int TIMEOUT_SECONDS = 60;
    // DeepSeek API端点
    private static final String AI_URL = "https://api.deepseek.com/chat/completions";
    // JSON媒体类型常量
    private static final MediaType JSON_MEDIA_TYPE = MediaType.parse("application/json");

    // 从配置文件注入API密钥
    @Value("${api.key}")
    private String apiKey;

    // OkHttp客户端配置(线程安全)
    private final OkHttpClient httpClient = new OkHttpClient.Builder()
            .connectTimeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)  // 连接超时
            .readTimeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)     // 读取超时
            .writeTimeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)    // 写入超时
            .build();

    /**
     * SSE请求处理入口
     * @param message 用户输入消息
     * @param response HTTP响应对象
     * @throws IOException 当响应流出现问题时抛出
     */
    @GetMapping("/stream")
    public void handleSse(@RequestParam String message, HttpServletResponse response) throws IOException {
        // 配置SSE响应头
        configureSSEResponse(response);

        PrintWriter pw = null;
        try {
            // 获取响应输出流
            pw = response.getWriter();
            // 创建同步锁(初始计数器为1)
            CountDownLatch latch = new CountDownLatch(1);

            // 执行SSE请求
            executeSSERequest(pw, buildRequest(message), latch);

            // 等待异步操作完成(最大等待TIMEOUT_SECONDS秒)
            if (!latch.await(TIMEOUT_SECONDS, TimeUnit.SECONDS)) {
                log.warn("SSE请求超时");
            }
        } catch (InterruptedException e) {
            // 正确处理线程中断
            Thread.currentThread().interrupt();
            log.error("请求被中断", e);
            response.sendError(503, "服务不可用");
        } catch (Exception e) {
            log.error("SSE请求异常", e);
            response.sendError(500, "服务器内部错误");
        } finally {
            // 确保关闭输出流
            if (pw != null) {
                try {
                    pw.close();
                } catch (Exception e) {
                    log.warn("关闭PrintWriter异常", e);
                }
            }
        }
    }

    /**
     * 配置SSE响应头
     * @param response HTTP响应对象
     */
    private void configureSSEResponse(HttpServletResponse response) {
        response.setContentType("text/event-stream");  // MIME类型
        response.setCharacterEncoding("UTF-8");        // 字符编码
        response.setHeader("Connection", "keep-alive"); // 保持长连接
        response.setHeader("Cache-Control", "no-cache"); // 禁用缓存
    }

    /**
     * 构建DeepSeek API请求
     * @param content 用户输入内容
     * @return 构造好的Request对象
     */
    private Request buildRequest(String content) {
        // 构造请求参数
        Map<String, Object> params = new HashMap<>();
        params.put("model", "deepseek-chat");    // 使用的模型
        params.put("stream", true);              // 启用流式传输
        // 构造消息列表(使用双括号初始化语法)
        params.put("messages", Collections.singletonList(
                new HashMap<String, String>() {{
                    put("role", "user");         // 消息角色
                    put("content", content);     // 消息内容
                }}
        ));

        // 构建OkHttp请求
        return new Request.Builder()
                .url(AI_URL)  // API地址
                .post(RequestBody.create(JSON.toJSONString(params), JSON_MEDIA_TYPE)) // JSON请求体
                .addHeader("Authorization", "Bearer " + apiKey) // 认证头
                .addHeader("Accept", "text/event-stream")       // 接受SSE流
                .build();
    }

    /**
     * 执行SSE请求核心方法
     * @param pw 响应输出流
     * @param request 构造好的API请求
     * @param latch 线程同步锁
     */
    private void executeSSERequest(PrintWriter pw, Request request, CountDownLatch latch) {
        // 创建事件源监听器
        RealEventSource eventSource = new RealEventSource(request, new EventSourceListener() {
            /**
             * 事件处理回调
             * @param eventSource 事件源
             * @param id 事件ID(未使用)
             * @param type 事件类型(未使用)
             * @param data 接收到的数据
             */
            @Override
            public void onEvent(EventSource eventSource, String id, String type, String data) {
                try {
                    // 处理结束标志
                    if (DONE_FLAG.equals(data)) {
                        sendData(pw, new ContentDto(DONE_FLAG));
                        return;
                    }
                    
                    // 解析并发送有效内容
                    String content = parseContent(data);
                    if (!content.isEmpty()) {
                        sendData(pw, new ContentDto(content));
                    }
                } catch (Exception e) {
                    log.error("事件处理异常", e);
                }
            }

            /**
             * 失败处理回调
             */
            @Override
            public void onFailure(EventSource eventSource, Throwable t, Response response) {
                log.error("API调用失败: {},响应码:{}", 
                         t.getMessage(), 
                         (response != null ? response.code() : "N/A"));
                latch.countDown(); // 释放同步锁
            }

            /**
             * 连接关闭回调
             */
            @Override
            public void onClosed(EventSource eventSource) {
                try {
                    sendData(pw, new ContentDto(DONE_FLAG)); // 发送结束标志
                } finally {
                    latch.countDown(); // 确保释放同步锁
                }
            }
        });

        // 发起异步连接
        eventSource.connect(httpClient);
    }

    /**
     * 线程安全的数据发送方法
     * @param pw 响应输出流
     * @param dto 要发送的数据传输对象
     */
    private synchronized void sendData(PrintWriter pw, ContentDto dto) {
        try {
            // 按照SSE格式发送数据
            pw.write("data:" + JSON.toJSONString(dto) + "\n\n");
            pw.flush(); // 立即刷新缓冲区
        } catch (Exception e) {
            log.error("数据发送失败", e);
        }
    }

    /**
     * 解析API响应内容
     * @param data 原始响应字符串
     * @return 解析出的内容文本
     */
    private String parseContent(String data) {
        try {
            // 使用FastJSON反序列化
            AiResult result = JSON.parseObject(data, AiResult.class);
            // 使用Optional处理可能为空的字段
            return Optional.ofNullable(result.getChoices())
                    .flatMap(list -> list.stream().findFirst())  // 取第一个choice
                    .map(AiResultChoices::getDelta)              // 获取delta对象
                    .map(AiResultDelta::getContent)              // 获取content字段
                    .orElse("");                                 // 默认返回空字符串
        } catch (Exception e) {
            log.error("数据解析异常,原始数据:{}", data, e);
            return "";
        }
    }
}

5. 配置文件说明

在 application.yml 中添加配置:

deepseek:
  api-key: your_api_key_here  # 替换为实际API Key

6. 测试与验证

6.1启动应用后,通过浏览器或工具发送请求:

GET http://localhost:8080/stream?message=你好,介绍一下你自己

6.2预期输出(流式响应):

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

大语言模型的多垂类快速评估与 A/B 测试

简介 行业领先的模型构建企业携手澳鹏&#xff08;Appen&#xff09;开展了一项极具挑战性的项目。针对 3 至 6 个大型语言模型&#xff08;LLM&#xff09;&#xff0c;在广泛的通用领域及复杂专业领域&#xff08;如医疗保健、法律、金融、编程、数学和汽车行业等&#xff0…

RAGFlow + LlamaIndex 本地知识库RAG增强架构与实现直播智能复盘

一、需求分析与架构设计 基于 RAGFlow LlamaIndex 本地知识库RAG 扩展直播话术合规与复盘系统&#xff0c;需构建 实时流处理、多模态合规引擎、智能复盘分析 三层能力。以下是完整架构图与技术方案&#xff1a; 二、核心模块技术方案 1. 直播流实时处理&#xff08;输入层→…

阿里云平台服务器操作以及发布静态项目

目录&#xff1a; 1、云服务器介绍2、云服务器界面3、发布静态项目1、启动nginx2、ngixn访问3、外网访问测试4、拷贝静态资源到nginx目录下并重启nginx 1、云服务器介绍 2、云服务器界面 实例详情&#xff1a;里面主要显示云服务的内外网地址以及一些启动/停止的操作。监控&…

【大模型实战篇】使用GPTQ量化QwQ-32B微调后的推理模型

1. 量化背景 之所以做量化&#xff0c;就是希望在现有的硬件条件下&#xff0c;提升性能。量化能将模型权重从高精度&#xff08;如FP32&#xff09;转换为低精度&#xff08;如INT8/FP16&#xff09;&#xff0c;内存占用可减少50%~75%。低精度运算&#xff08;如INT8&#xf…

基于springboot医疗平台系统(源码+lw+部署文档+讲解),源码可白嫖!

摘要 信息化时代&#xff0c;各行各业都以网络为基础飞速发展&#xff0c;而医疗服务行业的发展却进展缓慢&#xff0c;传统的医疗服务行业已经逐渐不满足民众的需求&#xff0c;有些还在以线下预约挂号的方式接待病人&#xff0c;为此设计一个医疗平台系统很有必要。此类系统…

Stable Diffusion lora训练(一)

一、不同维度的LoRA训练步数建议 2D风格训练 数据规模&#xff1a;建议20-50张高质量图片&#xff08;分辨率≥10241024&#xff09;&#xff0c;覆盖多角度、多表情的平面风格。步数范围&#xff1a;总步数控制在1000-2000步&#xff0c;公式为 总步数 Repeat Image Epoch …

网络空间安全(37)获取webshell方法总结

一、直接上传获取Webshell 这是最常见且直接的方法&#xff0c;利用网站对上传文件的过滤不严或存在漏洞&#xff0c;直接上传Webshell文件。 常见场景&#xff1a; 许多PHP和JSP程序存在此类漏洞。例如&#xff0c;一些论坛系统允许用户上传头像或心情图标&#xff0c;攻击者可…

第十三次CCF-CSP认证(含C++源码)

第十三次CCF-CSP认证 跳一跳满分题解 碰撞的小球满分题解遇到的问题 棋局评估满分题解 跳一跳 题目链接 满分题解 没什么好说的 基本思路就是如何用代码翻译题目所给的一些限制&#xff0c;以及变量应该如何更新&#xff0c;没像往常一样给一个n&#xff0c;怎么读入数据&…

swagger ui 界面清除登录信息的办法

我们在开发过程中&#xff0c;用swagger ui 测试接口的时候&#xff0c;可能会要修改当前登录的用户。 但是如果我们在谷歌中对调试的本地swagger ui 登录地址存储过账户密码&#xff0c;每次启动项目调试之后&#xff0c;都会自动登录swagger ui &#xff0c;登录界面一闪就…

TensorFlow 的基本概念和使用场景

TensorFlow 是一个由 Google 开发的开源机器学习框架&#xff0c;主要用于构建和训练深度学习模型。下面是一些 TensorFlow 的基本概念和使用场景&#xff1a; 基本概念&#xff1a; 张量&#xff08;Tensor&#xff09;&#xff1a;在 TensorFlow 中&#xff0c;数据以张量的…

基于x11vnc的ubuntu远程桌面

1、安装VNC服务 sudo apt install x11vnc -y2、创建连接密码 sudo x11vnc -storepasswd3、安装lightdm服务 x11vnc 在 默认的 GDM3 中不起作用&#xff0c;因此需要使用 lightdm 桌面管理环境 sudo apt install lightdm -y切换至lightdm&#xff0c;上一步已经切换则跳过该…

Cursor解锁Claude Max,助力AI编程新突破!

Cursor 最新推出的 Claude Max 模型&#xff0c;以其卓越的性能和创新的能力&#xff0c;正在重新定义我们对 AI 辅助编程的认知。这款搭载 Claude3.7 大脑的超级模型&#xff0c;不仅具备超强智能&#xff0c;还凭借一系列技术突破&#xff0c;向传统 AI 编程工具发起了挑战。…

ESP8266 与 ARM7 接口-LPC2148 创建 Web 服务器以控制 LED

ESP8266 与 ARM7 接口-LPC2148 创建 Web 服务器以控制 LED ESP8266 Wi-Fi 收发器提供了一种将微控制器连接到网络的方法。它被广泛用于物联网项目,因为它便宜、体积小且易于使用。 在本教程中,我们将 ESP8266 Wi-Fi 模块与 ARM7-LPC2148 微控制器连接,并创建一个 Web 服务…

通过C#脚本更改材质球的参数

// 设置贴图Texture mTexture Resources.Load("myTexture", typeof(Texture )) as Texture;material.SetTexture("_MainTex", mTexture );// 设置整数material.SetInt("_Int", 1);// 设置浮点material.SetFloat("_Float", 0.1f);// 设…

FPGA管脚约束

目录 前言 一、IO约束 二、延迟约束 前言 IO约束包括管脚约束和延迟约束。 一、IO约束 对管脚进行约束&#xff0c;对应的约束语句&#xff1a; set_property -dict {PACKAGE_PIN AJ16 IOSTANDARD LVCMOS18} [get_ports "led[0]" ] 上面是单端的管脚&…

实现前端.ttf字体包的压缩

前言 平常字体包都有1M的大小&#xff0c;所以网络请求耗时会比较长&#xff0c;所以对字体包的压缩也是前端优化的一个点。但是前端如果想要特点字符打包成字体包&#xff0c;网上查阅资料后&#xff0c;都是把前端代码里面的字符获取&#xff0c;但是对于动态的内容&#xf…

uni-app集成保利威直播、点播SDK经验FQ(二)|小程序直播/APP直播开发适用

通过uniapp集成保利威直播、点播SDK来开发小程序/APP的视频直播能力&#xff0c;在实际开发中可能会遇到的疑问和解决方案&#xff0c;下篇。更多疑问请咨询19924784795。 1.ios不能后台挂起uniapp插件 ios端使用后台音频播放和画中画功能&#xff0c;没有在 manifest.json 进…

Sensodrive机器人力控关节模组SensoJoint在海洋垃圾清理机器人中的拓展应用

海洋污染已成为全球性的环境挑战&#xff0c;其中海底垃圾的清理尤为困难。据研究&#xff0c;海洋中约有2600万至6600万吨垃圾&#xff0c;超过90%沉积在海底。传统上&#xff0c;潜水员收集海底垃圾不仅成本高昂&#xff0c;而且充满风险。为解决这一问题&#xff0c;欧盟资助…

Git的基本指令

一、回滚 1.git init 在项目文件夹中打开bash生成一个.git的子目录&#xff0c;产生一个仓库 2.git status 查看当前目录下的所有文件的状态 3.git add . 将该目录下的所有文件提交到暂存区 4.git add 文件名 将该目录下的指定文件提交到暂存区 5.git commit -m 备注信…

Vitis 2024.1 无法正常编译custom ip的bug(因为Makefile里的wildcard)

现象&#xff1a;如果在vivado中&#xff0c;添加了自己的custom IP&#xff0c;比如AXI4 IP&#xff0c;那么在Vitis&#xff08;2024.1&#xff09;编译导出的原本的.xsa的时候&#xff0c;会构建build失败。报错代码是&#xff1a; "Compiling blank_test_ip..."…