在项目中快速整合实现通义千问AI智能客服(所有大模型都一样)
·
前置条件
- 已部署若依微服务版(RuoYi-Cloud)
- 拥有阿里云账号并开通通义千问API权限
后端集成步骤
在ruoyi-新建模块中添加通义千问SDK依赖:
这是Maven 项目的 pom.xml 配置文件(常见于 Java 项目,如 Spring Boot),核心内容是引入 ** 阿里云 DashScope SDK(通义千问)** 的依赖,并处理日志框架冲突。
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dashscope-sdk-java</artifactId>
<version>${dashscope.version}</version>
<exclusions>
<!-- 排除slf4j-simple,解决日志框架冲突 -->
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</exclusion>
<!-- 可选:排除重复的slf4j-api,避免版本不一致 -->
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>
封装调用通义千问大模型的请求参数
@Data
public class QianWenQuestionDTO {
/** 提问内容(必填) */
private String question;
/** 模型版本(可选,默认qwen-plus) */
private String model = "qwen-plus";
/** 回答随机性(可选,默认0.7) */
private Float temperature = 0.7F;
}
调用接口控制器
@Slf4j
@RestController
@RequestMapping("/qianwen")
public class QianWenCarController extends BaseController {
@Autowired
private IQianWenService qianWenService;
/**
* POST调用接口(核心适配你的基准逻辑)
* 访问示例:POST http://localhost:9203/car/qianwen/ask
* 请求体:{"question":"你是大模型多少版本?","model":"qwen-plus"}
*/
@PostMapping("/ask")
public AjaxResult askPost(@RequestBody QianWenQuestionDTO questionDTO) {
// 参数校验
if (questionDTO.getQuestion() == null || questionDTO.getQuestion().trim().isEmpty()) {
return AjaxResult.error("提问内容不能为空!");
}
// 默认模型版本(和你的基准代码一致)
if (questionDTO.getModel() == null || questionDTO.getModel().trim().isEmpty()) {
questionDTO.setModel("qwen-turbo");
}
try {
// 调用Service(核心是你的callWithMessage逻辑)
String answer = qianWenService.callQianWen(questionDTO);
return AjaxResult.success("查询成功", answer);
} catch (NoApiKeyException e) {
// 复用你的异常提示逻辑
log.error("错误信息:{}", e.getMessage());
return AjaxResult.error("错误信息:" + e.getMessage() + ",请参考文档:https://help.aliyun.com/zh/model-studio/developer-reference/error-code");
} catch (InputRequiredException | ApiException e) {
log.error("错误信息:{}", e.getMessage());
return AjaxResult.error("错误信息:" + e.getMessage() + ",请参考文档:https://help.aliyun.com/zh/model-studio/developer-reference/error-code");
} catch (Exception e) {
log.error("系统异常:{}", e.getMessage(), e);
return AjaxResult.error("系统异常,请稍后重试!");
}
}
/**
* 模拟你的main方法测试接口(GET方式,快速验证)
* 访问示例:http://localhost:9203/car/qianwen/ask?question=你是大模型多少版本?
*/
@GetMapping("/ask")
public AjaxResult askGet(@RequestParam("question") String question) {
QianWenQuestionDTO dto = new QianWenQuestionDTO();
dto.setQuestion(question);
dto.setModel("qwen-plus");
return askPost(dto);
}
}
实现业务层逻辑:
public interface IQianWenService {
/**
* 多轮循环调用千问大模型
* @param questionDTO 请求参数(含当前问题+历史上下文)
* @return 大模型回答内容
* @throws ApiException API调用异常
* @throws NoApiKeyException 缺少API Key异常
* @throws InputRequiredException 输入参数缺失异常
*/
String callQianWen(QianWenQuestionDTO questionDTO) throws ApiException, NoApiKeyException, InputRequiredException;
}
@Slf4j
@Service
public class QianWenServiceImpl implements IQianWenService {
/** 系统默认提示词 */
private static final String SYSTEM_DEFAULT_PROMPT = "You are a helpful assistant.";
/**
* 核心多轮循环调用方法
* 每次调用自动累积「用户提问+AI回答」到历史上下文,实现循环对话
*/
@Override
public String callQianWen(QianWenQuestionDTO questionDTO) throws ApiException, NoApiKeyException, InputRequiredException {
// 1. 参数校验
if (questionDTO == null || questionDTO.getQuestion() == null || questionDTO.getQuestion().trim().isEmpty()) {
throw new InputRequiredException("用户提问内容不能为空");
}
if (questionDTO.getModel() == null || questionDTO.getModel().trim().isEmpty()) {
log.warn("未指定模型版本,默认使用qwen-plus");
questionDTO.setModel("qwen-plus");
}
// 2. 初始化消息列表(系统提示词 + 历史消息 + 当前提问)
List<Message> messageList = buildMessageList(questionDTO);
// 3. 构建调用参数
GenerationParam param = GenerationParam.builder()
.apiKey(System.getenv("DASHSCOPE_API_KEY")) // 从环境变量读取API Key
.model(questionDTO.getModel())
.messages(messageList)
.resultFormat(GenerationParam.ResultFormat.MESSAGE) // 消息格式返回
.temperature(0.7f) // 随机性(可根据需求调整)
.topP(0.8) // 采样策略(可根据需求调整)
.build();
// 4. 调用千问大模型
Generation generation = new Generation();
GenerationResult result = generation.call(param);
// 5. 解析AI回答
Message aiReplyMsg = result.getOutput().getChoices().get(0).getMessage();
String aiReplyContent = aiReplyMsg.getContent();
log.info("千问大模型回答:{}", aiReplyContent);
// 6. 更新历史消息(追加「当前用户提问+AI回答」,用于下一轮循环)
updateHistoryMessages(questionDTO, aiReplyMsg);
// 7. 返回AI回答内容
return aiReplyContent;
}
/**
* 构建完整的消息列表(系统提示词 + 历史上下文 + 当前提问)
*/
private List<Message> buildMessageList(QianWenQuestionDTO questionDTO) {
List<Message> messageList = new ArrayList<>();
// 第一步:添加系统提示词(仅首次循环时添加,避免重复)
if (questionDTO.getHistoryMessages() == null || questionDTO.getHistoryMessages().isEmpty()) {
Message systemMsg = Message.builder()
.role(Role.SYSTEM.getValue())
.content(SYSTEM_DEFAULT_PROMPT)
.build();
messageList.add(systemMsg);
}
// 第二步:添加历史对话消息(循环上下文)
if (questionDTO.getHistoryMessages() != null && !questionDTO.getHistoryMessages().isEmpty()) {
messageList.addAll(questionDTO.getHistoryMessages());
}
// 第三步:添加当前用户提问
Message currentUserMsg = Message.builder()
.role(Role.USER.getValue())
.content(questionDTO.getQuestion().trim())
.build();
messageList.add(currentUserMsg);
return messageList;
}
/**
* 更新历史消息列表(追加本轮「用户提问+AI回答」,供下一轮循环使用)
*/
private void updateHistoryMessages(QianWenQuestionDTO questionDTO, Message aiReplyMsg) {
// 初始化历史消息列表(避免空指针)
if (questionDTO.getHistoryMessages() == null) {
questionDTO.setHistoryMessages(new ArrayList<>());
}
// 追加当前用户提问
questionDTO.getHistoryMessages().add(Message.builder()
.role(Role.USER.getValue())
.content(questionDTO.getQuestion().trim())
.build());
// 追加AI回答
questionDTO.getHistoryMessages().add(aiReplyMsg);
}
}
添加配置参数到application.yml:
qianfan:
access-key: your-access-key
access-secret: your-secret-key
endpoint: https://qianfan.aliyun.com/api/v1
前端集成步骤
在ruoyi-ui/src/views/QianWen/index.vue中创建AI客服组件:
<template>
<div class="qianwen-container">
<div class="dialog-box">
<div class="dialog-header">
<h3>智能助手</h3>
<el-button
type="text"
icon="el-icon-close"
@click="closeDialog"
></el-button>
</div>
<div class="dialog-content">
<div
v-for="(message, index) in messages"
:key="index"
:class="['message-item', message.type]"
>
<div class="message-avatar">
<el-avatar
v-if="message.type === 'user'"
:src="userAvatar"
size="small"
></el-avatar>
<el-avatar v-else :src="aiAvatar" size="small"></el-avatar>
</div>
<div class="message-content">
<div class="message-text">{{ message.content }}</div>
<div class="message-time">{{ formatTime(message.timestamp) }}</div>
</div>
</div>
</div>
<div class="dialog-footer">
<el-input
v-model="inputText"
type="textarea"
:rows="3"
placeholder="请输入您的问题..."
@keyup.enter.native="sendMessage"
resize="none"
></el-input>
<div class="button-group">
<el-button
type="primary"
@click="sendMessage"
:disabled="!inputText.trim() || loading"
:loading="loading"
>发送</el-button
>
<el-button @click="clearMessages">清空</el-button>
</div>
</div>
</div>
</div>
</template>
<script>
import { askQianWen } from "@/api/car/car";
export default {
name: "QianWenDialog",
data() {
return {
messages: [
{
type: "ai",
content: "您好!我是您的AI对话助手,有什么可以帮助您的吗?",
timestamp: new Date(),
},
],
inputText: "",
loading: false,
userAvatar:
"https://bwie-wqy.oss-cn-beijing.aliyuncs.com/OIP-C%20%283%29.jpg",
aiAvatar: "https://bwie-wqy.oss-cn-beijing.aliyuncs.com/login-zuche.jpg",
};
},
methods: {
sendMessage() {
if (!this.inputText.trim()) return;
// 添加用户消息
const userMessage = {
type: "user",
content: this.inputText.trim(),
timestamp: new Date(),
};
this.messages.push(userMessage);
this.inputText = "";
this.loading = true;
// 转换历史消息格式
const historyMessages = this.messages
.slice(0, -1) // 排除刚添加的用户消息
.map((msg) => ({
role: msg.type === "user" ? "user" : "assistant",
content: msg.content,
}));
// 调用真实API
askQianWen({
question: userMessage.content,
model: "qwen-plus", // 使用指定的模型
historyMessages: historyMessages,
})
.then((response) => {
const aiMessage = {
type: "ai",
content: response || "很抱歉,我无法回答这个问题。",
timestamp: new Date(),
};
this.messages.push(aiMessage);
})
.catch((error) => {
console.error("API调用失败:", error);
const aiMessage = {
type: "ai",
content: "很抱歉,服务暂时不可用,请稍后再试。",
timestamp: new Date(),
};
this.messages.push(aiMessage);
})
.finally(() => {
this.loading = false;
this.scrollToBottom();
});
},
clearMessages() {
this.messages = [
{
type: "ai",
content: "您好!我是您的AI对话助手,有什么可以帮助您的吗?",
timestamp: new Date(),
},
];
},
closeDialog() {
this.$router.go(-1);
},
formatTime(date) {
const hours = date.getHours().toString().padStart(2, "0");
const minutes = date.getMinutes().toString().padStart(2, "0");
return `${hours}:${minutes}`;
},
scrollToBottom() {
this.$nextTick(() => {
const dialogContent = document.querySelector(".dialog-content");
if (dialogContent) {
dialogContent.scrollTop = dialogContent.scrollHeight;
}
});
},
},
watch: {
messages() {
this.scrollToBottom();
},
},
mounted() {
this.scrollToBottom();
},
};
</script>
<style scoped>
.qianwen-container {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #f5f7fa;
}
.dialog-box {
width: 1000px;
height: 800px;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
}
.dialog-header {
padding: 15px 20px;
border-bottom: 1px solid #ebeef5;
display: flex;
justify-content: space-between;
align-items: center;
background-color: #ecf5ff;
border-top-left-radius: 8px;
border-top-right-radius: 8px;
}
.dialog-header h3 {
margin: 0;
color: #409eff;
font-size: 18px;
font-weight: 500;
}
.dialog-content {
flex: 1;
padding: 20px;
overflow-y: auto;
background-color: #fafafa;
}
.message-item {
display: flex;
margin-bottom: 20px;
animation: fadeIn 0.3s ease;
}
.message-item.user {
justify-content: flex-end;
}
.message-item.ai {
justify-content: flex-start;
}
.message-avatar {
margin: 0 10px;
}
.message-content {
max-width: 70%;
background-color: #fff;
padding: 12px 15px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
position: relative;
}
.message-item.user .message-content {
background-color: #409eff;
color: #fff;
}
.message-text {
line-height: 1.6;
margin-bottom: 5px;
word-wrap: break-word;
}
.message-time {
font-size: 12px;
opacity: 0.7;
text-align: right;
}
.dialog-footer {
padding: 20px;
border-top: 1px solid #ebeef5;
background-color: #fff;
border-bottom-left-radius: 8px;
border-bottom-right-radius: 8px;
}
.button-group {
display: flex;
justify-content: flex-end;
margin-top: 10px;
gap: 10px;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 滚动条样式 */
.dialog-content::-webkit-scrollbar {
width: 8px;
}
.dialog-content::-webkit-scrollbar-track {
background-color: #f1f1f1;
border-radius: 4px;
}
.dialog-content::-webkit-scrollbar-thumb {
background-color: #c1c1c1;
border-radius: 4px;
}
.dialog-content::-webkit-scrollbar-thumb:hover {
background-color: #a8a8a8;
}
</style>
添加路由配置:
// src/router/index.js
{
path: '/QianWen',
component: Layout,
redirect: '/QianWen/index',
children: [
{
path: 'index',
component: () => import('@/views/QianWen/index'),
name: 'QianWen',
meta: { title: 'AI 对话', icon: 'question', affix: false }
}
]
}
安全增强措施
在ruoyi-gateway添加鉴权过滤(可以不要):
- 统一鉴权:在网关层对 AI 接口做集中的 JWT 验证,无需在每个微服务中重复实现鉴权逻辑,符合 “单一职责” 设计原则。
- 接口保护:AI 接口(如通义千问调用)通常涉及资源消耗或付费服务,通过鉴权防止未授权访问,降低安全风险。
- 灵活扩展:可在过滤器中添加更多逻辑,如接口限流(限制 AI 接口的调用频率)、日志记录(记录 AI 接口的调用情况)、参数校验(验证请求参数合法性)等。
// src/main/java/com/ruoyi/gateway/filter/AiAuthFilter.java
public class AiAuthFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
if (exchange.getRequest().getPath().toString().contains("/ai/")) {
// 验证JWT令牌逻辑
}
return chain.filter(exchange);
}
}
性能优化建议
实现结果缓存机制:
// 在QianFanServiceImpl中添加
@Cacheable(value = "aiResponses", key = "#prompt")
public String getAIResponse(String prompt) {
// 原有逻辑
}
配置限流策略(可以忽略):
# application.yml
spring:
cloud:
gateway:
routes:
- id: ai-route
uri: lb://ruoyi-ai
predicates:
- Path=/ai/**
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20
测试验证方法
- 启动所有微服务模块
- 访问
http://localhost:8080/ai-chat - 输入测试问题如"如何重置密码"
- 验证返回的回答是否符合预期
异常处理方案
全局异常处理器增强:
// src/main/java/com/ruoyi/ai/exception/AiExceptionHandler.java
@RestControllerAdvice
public class AiExceptionHandler {
@ExceptionHandler(QianfanException.class)
public R handleAiException(QianfanException e) {
log.error("AI服务异常: {}", e.getMessage());
return R.fail("AI服务暂时不可用");
}
}
部署注意事项
-
阿里云密钥需通过Vault或KMS加密存储
-
建议为通义千问服务创建独立命名空间
-
生产环境需配置HTTPS加密通信
-
监控接口需添加Prometheus指标采集
效果展示:

更多推荐
所有评论(0)