前置条件

  • 已部署若依微服务版(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添加鉴权过滤(可以不要):

  1. 统一鉴权:在网关层对 AI 接口做集中的 JWT 验证,无需在每个微服务中重复实现鉴权逻辑,符合 “单一职责” 设计原则。
  2. 接口保护:AI 接口(如通义千问调用)通常涉及资源消耗或付费服务,通过鉴权防止未授权访问,降低安全风险。
  3. 灵活扩展:可在过滤器中添加更多逻辑,如接口限流(限制 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

测试验证方法

  1. 启动所有微服务模块
  2. 访问http://localhost:8080/ai-chat
  3. 输入测试问题如"如何重置密码"
  4. 验证返回的回答是否符合预期

异常处理方案

全局异常处理器增强:

// 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服务暂时不可用");
    }
}

部署注意事项

  1. 阿里云密钥需通过Vault或KMS加密存储
  2. 建议为通义千问服务创建独立命名空间
  3. 生产环境需配置HTTPS加密通信
  4. 监控接口需添加Prometheus指标采集

效果展示:

更多推荐