概述
在 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中添加配置
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.响应显示

