SpringBoot集成 DeepSeek 对话补全功能

概述

在 Ruoyi-Vue 这一流行的前后端分离权限管理系统基础上,集成 DeepSeek 提供的强大 AI 对话补全功能,可以为系统添加智能对话和内容生成能力。本文将详细介绍如何在 Ruoyi-Vue 中实现这一集成。

技术栈

  • 后端:Ruoyi Spring Boot

  • 前端:Ruoyi Vue + Element UI

  • AI服务:DeepSeek API

1. 准备工作

首先需要获取 DeepSeek API 访问权限:

  1. 访问 DeepSeek 官网 注册账号

  2. 获取 API Key

  3. 了解 API 调用方式和参数

1.1在application.yml中添加配置

bash 复制代码
# application.yml
# DeepSeek配置
deepseek:
  api:
    apiKey: your_deepseek_api_key_here  # 替换为您的实际API密钥
    apiUrl: https://api.deepseek.com/chat/completions
    default-model: deepseek-chat
    default-temperature: 0.7
    default-max-tokens: 2000
    connect-timeout: 10000  # 连接超时时间(毫秒)
    read-timeout: 30000     # 读取超时时间(毫秒)

# 日志配置
logging:
  level:
    com.ruoyi.project.system.service.DeepSeekService: DEBUG
    com.ruoyi.project.system.controller.DeepSeekController: DEBUG

1.2DeepSeek配置属性类

java 复制代码
package com.dafu.common.ai;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * DeepSeek配置属性类
 * 用于从application.yml中读取DeepSeek相关配置
 */
@Component
@ConfigurationProperties(prefix = "deepseek.api")
@Data
public class DeepSeekProperties {
    /**
     * API密钥
     */
    private String apiKey;

    /**
     * API端点URL
     */
    private String apiUrl = "https://api.deepseek.com/chat/completions";

    /**
     * 默认模型名称
     */
    private String defaultModel = "deepseek-chat";

    /**
     * 默认温度参数
     */
    private double defaultTemperature = 0.7;

    /**
     * 默认最大token数量
     */
    private int defaultMaxTokens = 2000;

    /**
     * 连接超时时间(毫秒)
     */
    private int connectTimeout = 10000;

    /**
     * 读取超时时间(毫秒)
     */
    private int readTimeout = 30000;
}

2. 添加DeepSeek请求/响应DTO

复制代码
2.1. DeepSeek API请求参数实体类用于构建发送到DeepSeek API的请求数据
java 复制代码
package com.dafu.chat.domain;

import lombok.Data;

import java.util.List;

/**
 * @author:DaFu
 * @date: 2025/8/27 16:09
 * DeepSeek API请求参数实体类
 * 用于构建发送到DeepSeek API的请求数据
 */
@Data
public class DeepSeekRequest {
    /**
     * 模型名称,例如:deepseek-chat
     */
    private String model;

    /**
     * 消息列表,包含对话历史
     */
    private List<Message> messages;

    /**
     * 生成文本的随机性控制(0-1之间)
     * 值越高,生成结果越随机;值越低,结果越确定
     */
    private double temperature = 0.7;

    /**
     * 生成的最大token数量
     */
    private int max_tokens = 2000;

    /**
     * 是否流式输出
     */
    private boolean stream = false;

    /**
     * 消息实体内部类
     */
    @Data
    public static class Message {
        /**
         * 角色:system、user、assistant
         */
        private String role;

        /**
         * 消息内容
         */
        private String content;

        /**
         * 构造函数
         * @param role 角色
         * @param content 内容
         */
        public Message(String role, String content) {
            this.role = role;
            this.content = content;
        }
    }
}

2.2 DeepSeek API响应实体类用于解析DeepSeek API返回的响应数据

java 复制代码
package com.dafu.chat.domain;

import lombok.Data;

import java.util.List;

/**
 * @author:DaFu
 * @date: 2025/8/27 16:08
 * DeepSeek API响应实体类
 * 用于解析DeepSeek API返回的响应数据
 */
@Data
public class DeepSeekResponse {
    /**
     * 请求ID
     */
    private String id;

    /**
     * 对象类型
     */
    private String object;

    /**
     * 创建时间戳
     */
    private long created;

    /**
     * 使用的模型名称
     */
    private String model;

    /**
     * 生成的选择列表
     */
    private List<Choice> choices;

    /**
     * token使用情况统计
     */
    private Usage usage;

    /**
     * 错误信息(如果有)
     */
    private Error error;

    /**
     * 选择实体内部类
     */
    @Data
    public static class Choice {
        /**
         * 选择索引
         */
        private int index;

        /**
         * 生成的消息
         */
        private Message message;

        /**
         * 结束原因
         */
        private String finish_reason;
    }

    /**
     * 消息实体内部类
     */
    @Data
    public static class Message {
        /**
         * 角色
         */
        private String role;

        /**
         * 内容
         */
        private String content;
    }

    /**
     * token使用统计内部类
     */
    @Data
    public static class Usage {
        /**
         * 提示token数量
         */
        private int prompt_tokens;

        /**
         * 补全token数量
         */
        private int completion_tokens;

        /**
         * 总token数量
         */
        private int total_tokens;
    }

    /**
     * 错误信息内部类
     */
    @Data
    public static class Error {
        /**
         * 错误消息
         */
        private String message;

        /**
         * 错误类型
         */
        private String type;

        /**
         * 参数信息
         */
        private String param;

        /**
         * 错误代码
         */
        private String code;
    }
}

2.3前端聊天请求参数实体 类用于接收前端发送的聊天请求

java 复制代码
package com.dafu.chat.domain;

import lombok.Data;

import javax.validation.constraints.NotBlank;

/**
 * @author:DaFu
 * @date: 2025/8/27 16:09
 * 前端聊天请求参数实体类
 * 用于接收前端发送的聊天请求
 */
@Data
public class ChatRequest {
    /**
     * 用户消息内容
     */
    @NotBlank(message = "消息内容不能为空")
    private String message;

    /**
     * 对话ID,用于多轮对话上下文管理
     */
    private String conversationId;

    /**
     * 模型名称,可选参数,默认使用配置的模型
     */
    private String model;
}

3.RestTemplate配置类

java 复制代码
package com.dafu.framework.config;
import com.dafu.common.ai.DeepSeekProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

/**
 * RestTemplate配置类
 * 配置HTTP请求的超时时间等参数
 */
@Configuration
public class RestTemplateConfig {
    private final DeepSeekProperties deepSeekProperties;

    /**
     * 构造函数
     * @param deepSeekProperties DeepSeek配置属性
     */
    public RestTemplateConfig(DeepSeekProperties deepSeekProperties) {
        this.deepSeekProperties = deepSeekProperties;
    }

    /**
     * 创建RestTemplate Bean
     * @return 配置好的RestTemplate实例
     */
    @Bean
    public RestTemplate restTemplate() {
        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();

        // 设置连接超时时间
        factory.setConnectTimeout(deepSeekProperties.getConnectTimeout());

        // 设置读取超时时间
        factory.setReadTimeout(deepSeekProperties.getReadTimeout());

        return new RestTemplate(factory);
    }
}

4.DeepSeek服务类

4.1负责与DeepSeek API进行交互

java 复制代码
package com.dafu.chat.service;
import com.dafu.chat.domain.DeepSeekRequest;
import com.dafu.chat.domain.DeepSeekResponse;
import com.dafu.common.ai.DeepSeekProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.Collections;
import java.util.List;

/**
 * @author:DaFu
 * @date: 2025/8/27
 * DeepSeek服务类
 * 负责与DeepSeek API进行交互
 */
@Slf4j
@Service
public class DeepSeekService {
   
    private final DeepSeekProperties properties;
    private final RestTemplate restTemplate;

    /**
     * 构造函数
     * @param properties DeepSeek配置属性
     * @param restTemplate RestTemplate实例
     */
    public DeepSeekService(DeepSeekProperties properties, RestTemplate restTemplate) {
        this.properties = properties;
        this.restTemplate = restTemplate;
    }

    /**
     * 发送聊天补全请求到DeepSeek API
     * @param messages 消息列表
     * @return DeepSeek响应对象
     */
    public DeepSeekResponse chatCompletion(List<DeepSeekRequest.Message> messages) {
        return chatCompletion(messages, properties.getDefaultModel(),
                properties.getDefaultTemperature(), properties.getDefaultMaxTokens());
    }

    /**
     * 发送聊天补全请求到DeepSeek API(带参数)
     * @param messages 消息列表
     * @param model 模型名称
     * @param temperature 温度参数
     * @param maxTokens 最大token数量
     * @return DeepSeek响应对象
     */
    public DeepSeekResponse chatCompletion(List<DeepSeekRequest.Message> messages, String model,
                                           double temperature, int maxTokens) {
        // 记录请求日志
        log.info("发送请求到DeepSeek API,模型: {}, 消息数量: {}", model, messages.size());

        // 设置请求头
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        headers.set("Authorization", "Bearer " + properties.getApiKey());
        headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));

        // 构建请求体
        DeepSeekRequest request = new DeepSeekRequest();
        request.setModel(model);
        request.setMessages(messages);
        request.setTemperature(temperature);
        request.setMax_tokens(maxTokens);

        HttpEntity<DeepSeekRequest> entity = new HttpEntity<>(request, headers);

        try {
            // 发送请求
            ResponseEntity<DeepSeekResponse> response = restTemplate.exchange(
                    properties.getApiUrl(),
                    HttpMethod.POST,
                    entity,
                    DeepSeekResponse.class
            );

            // 检查响应状态
            if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) {
                DeepSeekResponse responseBody = response.getBody();

                // 检查API返回的错误
                if (responseBody.getError() != null) {
                    log.error("DeepSeek API返回错误: {}", responseBody.getError().getMessage());
                    throw new RuntimeException("DeepSeek API错误: " + responseBody.getError().getMessage());
                }

                // 记录使用情况
                if (responseBody.getUsage() != null) {
                    log.info("API使用情况 - 提示token: {}, 补全token: {}, 总计token: {}",
                            responseBody.getUsage().getPrompt_tokens(),
                            responseBody.getUsage().getCompletion_tokens(),
                            responseBody.getUsage().getTotal_tokens());
                }

                return responseBody;
            } else {
                log.error("DeepSeek API请求失败,状态码: {}", response.getStatusCode());
                throw new RuntimeException("DeepSeek API请求失败,状态码: " + response.getStatusCode());
            }
        } catch (Exception e) {
            log.error("调用DeepSeek API失败: {}", e.getMessage(), e);
            throw new RuntimeException("调用DeepSeek API失败: " + e.getMessage(), e);
        }
    }
}

5.DeepSeek聊天控制器

5.1提供与前端交互的API接口

java 复制代码
package com.dafu.chat.controller;
import com.dafu.chat.domain.ChatRequest;
import com.dafu.chat.domain.DeepSeekRequest;
import com.dafu.chat.domain.DeepSeekResponse;
import com.dafu.chat.service.DeepSeekService;
import com.dafu.common.constant.HttpStatus;
import com.dafu.common.core.domain.AjaxResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.List;

/**
 * @author:DaFu
 * @date: 2025/8/27
 * DeepSeek聊天控制器
 * 提供与前端交互的API接口
 */
@RestController
@RequestMapping("/api/chat")
@Validated
@Slf4j
public class DeepSeekController {
 
    private final DeepSeekService deepSeekService;

    /**
     * 构造函数
     * @param deepSeekService DeepSeek服务
     */
    public DeepSeekController(DeepSeekService deepSeekService) {
        this.deepSeekService = deepSeekService;
    }

    /**
     * 处理聊天补全请求
     * @param chatRequest 聊天请求参数
     * @return 包含AI回复的响应结果
     */
    @PostMapping("/completion")
    public AjaxResult chatCompletion(@Valid @RequestBody ChatRequest chatRequest) {
        try {
            log.info("收到聊天请求,对话ID: {}, 消息长度: {}",
                    chatRequest.getConversationId(), chatRequest.getMessage().length());

            // 构建消息列表
            List<DeepSeekRequest.Message> messages = new ArrayList<>();

            // 可以根据conversationId获取对话历史(此处为简化版,实际应查询数据库)
            // 这里只添加当前消息
            messages.add(new DeepSeekRequest.Message("user", chatRequest.getMessage()));

            // 调用DeepSeek服务
            DeepSeekResponse response = deepSeekService.chatCompletion(messages);

            // 检查响应有效性
            if (response != null &&
                    response.getChoices() != null &&
                    !response.getChoices().isEmpty() &&
                    response.getChoices().get(0).getMessage() != null) {

                String reply = response.getChoices().get(0).getMessage().getContent();

                // 记录成功日志
                log.info("成功获取AI回复,回复长度: {}", reply.length());

                // 返回成功响应
                return AjaxResult.success("操作成功", reply);
            }

            // 处理无效响应
            log.warn("未获取到有效回复");
            return AjaxResult.error(HttpStatus.ERROR, "未获取到有效回复");

        } catch (Exception e) {
            // 记录错误日志
            log.error("处理聊天请求时发生错误: {}", e.getMessage(), e);

            // 返回错误响应
            return AjaxResult.error(HttpStatus.ERROR, "请求失败: " + e.getMessage());
        }
    }

    /**
     * 健康检查接口
     * @return 健康状态
     */
    @GetMapping("/health")
    public AjaxResult healthCheck() {
        return AjaxResult.success("DeepSeek服务正常运行");
    }
}

6.前端实现 (Vue)

6.1. 创建API调用方法
javascript 复制代码
// src/api/chat/deepseek.js
import request from '@/utils/request'

export function chatCompletion(data) {
  return request({
    url: '/api/chat/completion',
    method: 'post',
    data: data
  })
}
6.2. 创建聊天组件
rust 复制代码
<!-- src/views/chat/DeepSeekChat.vue -->
<template>
  <div class="app-container">
    <el-card class="box-card">
      <div slot="header" class="clearfix">
        <span>DeepSeek 智能对话</span>
      </div>
      
      <div class="chat-container">
        <!-- 消息显示区域 -->
        <div class="messages" ref="messagesContainer">
          <div v-for="(message, index) in messages" :key="index" 
               :class="['message', message.role]">
            <div class="avatar">
              <i :class="message.role === 'user' ? 'el-icon-user' : 'el-icon-robot'"></i>
            </div>
            <div class="content">
              <div class="text">{{ message.content }}</div>
              <div class="time">{{ message.time }}</div>
            </div>
          </div>
        </div>
        
        <!-- 输入区域 -->
        <div class="input-area">
          <el-input
            type="textarea"
            :rows="3"
            placeholder="请输入您的问题..."
            v-model="inputMessage"
            @keydown.enter.native="handleSend"
            :disabled="loading"
          ></el-input>
          <div class="actions">
            <el-button 
              type="primary" 
              @click="handleSend" 
              :loading="loading"
              :disabled="!inputMessage.trim()">
              发送
            </el-button>
            <el-button @click="clearChat">清空对话</el-button>
          </div>
        </div>
      </div>
    </el-card>
  </div>
</template>

<script>
import { chatCompletion } from "@/api/chat/deepseek";

export default {
  name: "DeepSeekChat",
  data() {
    return {
      messages: [],
      inputMessage: "",
      loading: false,
      conversationId: null
    };
  },
  methods: {
    handleSend() {
      if (!this.inputMessage.trim() || this.loading) return;
      
      const userMessage = {
        role: "user",
        content: this.inputMessage,
        time: new Date().toLocaleTimeString()
      };
      
      this.messages.push(userMessage);
      this.scrollToBottom();
      
      const messageToSend = this.inputMessage;
      this.inputMessage = "";
      this.loading = true;
      
      // 调用API
      chatCompletion({ 
        message: messageToSend, 
        conversationId: this.conversationId 
      })
        .then(response => {
          const assistantMessage = {
            role: "assistant",
            content: response.data,
            time: new Date().toLocaleTimeString()
          };
          
          this.messages.push(assistantMessage);
          this.scrollToBottom();
        })
        .catch(error => {
          this.$message.error("请求失败: " + (error.message || "未知错误"));
        })
        .finally(() => {
          this.loading = false;
        });
    },
    
    scrollToBottom() {
      this.$nextTick(() => {
        const container = this.$refs.messagesContainer;
        if (container) {
          container.scrollTop = container.scrollHeight;
        }
      });
    },
    
    clearChat() {
      this.messages = [];
      this.conversationId = null;
    }
  }
};
</script>

<style scoped>
.chat-container {
  display: flex;
  flex-direction: column;
  height: 600px;
}

.messages {
  flex: 1;
  overflow-y: auto;
  padding: 10px;
  border: 1px solid #ebeef5;
  border-radius: 4px;
  margin-bottom: 15px;
  background-color: #fafafa;
}

.message {
  display: flex;
  margin-bottom: 15px;
}

.message.user {
  flex-direction: row-reverse;
}

.message .avatar {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background-color: #409EFF;
  color: white;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0 10px;
}

.message.user .avatar {
  background-color: #67C23A;
}

.message .content {
  max-width: 70%;
}

.message.user .content {
  text-align: right;
}

.message .text {
  padding: 10px 15px;
  border-radius: 4px;
  background-color: white;
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}

.message.user .text {
  background-color: #ecf5ff;
}

.message .time {
  font-size: 12px;
  color: #909399;
  margin-top: 5px;
}

.input-area {
  border-top: 1px solid #ebeef5;
  padding-top: 15px;
}

.actions {
  margin-top: 10px;
  text-align: right;
}
</style>

7.响应显示