SpringBoot如何引入deepseek

部署运行你感兴趣的模型镜像

先看效果:

前端调用:

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引入个人项目或公司项目中了,可以拿去做毕设或者在公司项目中接入新功能。

如果在学习过程中有遇到各种问题可以评论或者私信,看到就及时回复。(个人原创,如有转载请提前确认)

(目前也可以实现多轮对话也就是上下文对话,如果需要的人多的话可以再出一期)

您可能感兴趣的与本文相关的镜像

FLUX.1-dev

FLUX.1-dev

图片生成
FLUX

FLUX.1-dev 是一个由 Black Forest Labs 创立的开源 AI 图像生成模型版本,它以其高质量和类似照片的真实感而闻名,并且比其他模型更有效率

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

云宝丶丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值