SpringBoot集成 DeepSeek 对话补全功能
概述
在 Ruoyi-Vue 这一流行的前后端分离权限管理系统基础上,集成 DeepSeek 提供的强大 AI 对话补全功能,可以为系统添加智能对话和内容生成能力。本文将详细介绍如何在 Ruoyi-Vue 中实现这一集成。
技术栈
-
后端:Ruoyi Spring Boot
-
前端:Ruoyi Vue + Element UI
-
AI服务:DeepSeek API
1. 准备工作
首先需要获取 DeepSeek API 访问权限:
-
访问 DeepSeek 官网 注册账号
-
获取 API Key
-
了解 API 调用方式和参数
1.1在application.yml中添加配置
# application.yml
# DeepSeek配置
deepseek:api:apiKey: your_deepseek_api_key_here # 替换为您的实际API密钥apiUrl: https://api.deepseek.com/chat/completionsdefault-model: deepseek-chatdefault-temperature: 0.7default-max-tokens: 2000connect-timeout: 10000 # 连接超时时间(毫秒)read-timeout: 30000 # 读取超时时间(毫秒)# 日志配置
logging:level:com.ruoyi.project.system.service.DeepSeekService: DEBUGcom.ruoyi.project.system.controller.DeepSeekController: DEBUG
1.2DeepSeek配置属性类
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的请求数据
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;/*** 消息实体内部类*/@Datapublic 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返回的响应数据
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;/*** 选择实体内部类*/@Datapublic static class Choice {/*** 选择索引*/private int index;/*** 生成的消息*/private Message message;/*** 结束原因*/private String finish_reason;}/*** 消息实体内部类*/@Datapublic static class Message {/*** 角色*/private String role;/*** 内容*/private String content;}/*** token使用统计内部类*/@Datapublic static class Usage {/*** 提示token数量*/private int prompt_tokens;/*** 补全token数量*/private int completion_tokens;/*** 总token数量*/private int total_tokens;}/*** 错误信息内部类*/@Datapublic static class Error {/*** 错误消息*/private String message;/*** 错误类型*/private String type;/*** 参数信息*/private String param;/*** 错误代码*/private String code;}
}
2.3前端聊天请求参数实体 类用于接收前端发送的聊天请求
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配置类
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实例*/@Beanpublic RestTemplate restTemplate() {SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();// 设置连接超时时间factory.setConnectTimeout(deepSeekProperties.getConnectTimeout());// 设置读取超时时间factory.setReadTimeout(deepSeekProperties.getReadTimeout());return new RestTemplate(factory);}
}
4.DeepSeek服务类
4.1负责与DeepSeek API进行交互
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接口
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调用方法
// 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. 创建聊天组件
<!-- 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-inputtype="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;// 调用APIchatCompletion({ 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>