基于 Spring MVC + 阿里云通义千问的 AI 助手开发

在智慧养殖等垂直领域的系统开发中,集成 AI 助手能显著提升用户体验和业务效率。本文将完整拆解一个生产级 AI 助手的实现方案,涵盖前后端架构、上下文记忆、阿里云通义千问 API 集成、异常处理等核心环节,所有代码均遵循企业级开发规范,可直接落地到实际项目中。

一、项目整体架构设计

本 AI 助手采用经典的前后端分离架构(前端纯原生 JS,后端 Spring MVC),核心通信依赖 HTTP API,整体架构清晰且易于扩展:

核心技术栈

  • 前端:HTML/CSS/ 原生 JavaScript(无框架依赖,兼容所有浏览器)
  • 后端:Spring MVC + Spring Context(依赖注入)
  • AI 能力:阿里云通义千问(qwen-turbo 模型,轻量化且响应快)
  • 存储:HttpSession(对话历史临时存储,无需数据库)

二、后端核心实现(Spring MVC)

2.1 配置文件管理(安全规范)

首先在resources/aliyun.properties中配置阿里云 API 密钥(核心:密钥不硬编码,通过配置文件注入):

复制代码
# 阿里云通义千问API配置
aliyun.ai.apiKey=your-api-key-here
aliyun.ai.apiUrl=https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation
aliyun.ai.model=qwen-turbo

在 Spring 配置类中加载该配置文件:

java 复制代码
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

@Configuration
@PropertySource("classpath:aliyun.properties")
public class AiConfig {
    // 空配置类,仅用于加载自定义配置文件
}

2.2 定义 AI 服务接口(面向接口编程)

创建AiService接口,定义核心能力,便于后续切换不同 AI 服务商(如切换为 GPT、文心一言等):

java 复制代码
import java.util.List;
import java.util.Map;

/**
 * AI助手核心服务接口
 */
public interface AiService {
    /**
     * 调用AI接口获取回答
     * @param conversationHistory 对话历史(包含上下文)
     * @return AI回复内容
     * @throws Exception 调用异常时抛出
     */
    String getAiAnswer(List<Map<String, String>> conversationHistory) throws Exception;
}

2.3 实现阿里云 AI 服务(核心业务逻辑)

AliyunAiServiceImpl实现上述接口,集成阿里云通义千问 API,并处理请求参数组装、响应解析:

java 复制代码
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.*;

@Service
public class AliyunAiServiceImpl implements AiService {

    // 通过@Value注入配置文件中的参数,避免硬编码
    @Value("${aliyun.ai.apiKey}")
    private String apiKey;

    @Value("${aliyun.ai.apiUrl}")
    private String apiUrl;

    @Value("${aliyun.ai.model}")
    private String model;

    // Spring内置的HTTP请求工具
    private final RestTemplate restTemplate = new RestTemplate();

    @Override
    public String getAiAnswer(List<Map<String, String>> conversationHistory) throws Exception {
        // 1. 构建请求头(阿里云API认证)
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        headers.set("Authorization", "Bearer " + apiKey);

        // 2. 构建请求体(符合通义千问API格式)
        Map<String, Object> requestBody = new HashMap<>();
        requestBody.put("model", model);
        requestBody.put("input", buildInput(conversationHistory));
        // 设置生成参数:温度0.7(平衡随机性和准确性)
        Map<String, Object> parameters = new HashMap<>();
        parameters.put("temperature", 0.7);
        parameters.put("top_p", 0.8);
        requestBody.put("parameters", parameters);

        // 3. 发送POST请求
        HttpEntity<Map<String, Object>> request = new HttpEntity<>(requestBody, headers);
        ResponseEntity<Map> response = restTemplate.postForEntity(apiUrl, request, Map.class);

        // 4. 解析响应结果
        if (response.getStatusCode() != HttpStatus.OK) {
            throw new Exception("AI接口调用失败,状态码:" + response.getStatusCode());
        }
        Map<String, Object> resultMap = response.getBody();
        if (resultMap == null || !"Success".equals(resultMap.get("output"))) {
            throw new Exception("AI接口返回异常:" + resultMap);
        }
        // 提取AI回复内容
        List<Map<String, String>> choices = (List<Map<String, String>>) resultMap.get("choices");
        return choices.get(0).get("text").trim();
    }

    /**
     * 构建通义千问要求的输入格式(包含系统人设+对话历史)
     */
    private Map<String, Object> buildInput(List<Map<String, String>> conversationHistory) {
        List<Map<String, String>> messages = new ArrayList<>();
        // 1. 添加系统人设(首次对话生效,后续复用上下文)
        Map<String, String> systemMsg = new HashMap<>();
        systemMsg.put("role", "system");
        systemMsg.put("content", "你是一名智慧养殖专家,请用专业、简练的语言回答问题。");
        messages.add(systemMsg);

        // 2. 添加对话历史(用户+AI的交互记录)
        messages.addAll(conversationHistory);

        Map<String, Object> input = new HashMap<>();
        input.put("messages", messages);
        return input;
    }
}

2.4 Controller 层(处理前端请求)

AiController负责接收前端请求,管理会话历史,调用 AI 服务,并返回结果:

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpSession;
import java.util.*;

@RestController
@RequestMapping("/ai")
public class AiController {

    @Autowired
    private AiService aiService;

    // 对话历史的Session Key
    private static final String CONV_HISTORY_KEY = "AI_CONVERSATION_HISTORY";
    // 最大对话记录数(10条,一问一答为2条,即最多5轮对话)
    private static final int MAX_HISTORY_SIZE = 10;

    /**
     * 处理用户提问
     */
    @PostMapping("/ask")
    public Map<String, Object> askQuestion(
            @RequestParam("question") String question,
            HttpSession session) {
        Map<String, Object> result = new HashMap<>();
        try {
            // 1. 校验输入
            if (question == null || question.trim().isEmpty()) {
                result.put("success", false);
                result.put("message", "问题不能为空");
                return result;
            }

            // 2. 获取/初始化会话历史
            List<Map<String, String>> history = getConversationHistory(session);

            // 3. 添加当前用户问题到历史
            Map<String, String> userMsg = new HashMap<>();
            userMsg.put("role", "user");
            userMsg.put("content", question.trim());
            history.add(userMsg);

            // 4. 调用AI服务获取回答
            String aiAnswer = aiService.getAiAnswer(history);

            // 5. 添加AI回答到历史
            Map<String, String> aiMsg = new HashMap<>();
            aiMsg.put("role", "assistant");
            aiMsg.put("content", aiAnswer);
            history.add(aiMsg);

            // 6. 智能清理历史(超过10条时删除最早的一问一答)
            cleanHistoryIfNecessary(history);

            // 7. 更新Session中的历史记录
            session.setAttribute(CONV_HISTORY_KEY, history);

            // 8. 组装返回结果
            result.put("success", true);
            result.put("answer", aiAnswer);
            result.put("history", history);
        } catch (Exception e) {
            // 异常处理:返回友好提示
            result.put("success", false);
            result.put("message", "AI回答生成失败:" + e.getMessage());
        }
        return result;
    }

    /**
     * 清空对话历史
     */
    @PostMapping("/clear")
    public Map<String, Object> clearHistory(HttpSession session) {
        Map<String, Object> result = new HashMap<>();
        session.removeAttribute(CONV_HISTORY_KEY);
        result.put("success", true);
        result.put("message", "对话历史已清空");
        return result;
    }

    /**
     * 获取会话中的对话历史,不存在则初始化
     */
    private List<Map<String, String>> getConversationHistory(HttpSession session) {
        List<Map<String, String>> history = (List<Map<String, String>>) session.getAttribute(CONV_HISTORY_KEY);
        if (history == null) {
            history = new ArrayList<>();
        }
        return history;
    }

    /**
     * 智能清理历史记录:超过10条时删除最早的2条(一问一答)
     */
    private void cleanHistoryIfNecessary(List<Map<String, String>> history) {
        while (history.size() > MAX_HISTORY_SIZE) {
            // 删除最早的两条记录(用户提问+AI回答)
            history.remove(0);
            history.remove(0);
        }
    }
}

三、前端核心实现(浮动聊天窗口)

3.1 样式文件(ai-chat.css)

实现可拖拽的浮动按钮和聊天窗口样式:

css 复制代码
/* 浮动AI助手按钮 */
.ai-chat-btn {
    position: fixed;
    bottom: 30px;
    right: 30px;
    width: 60px;
    height: 60px;
    border-radius: 50%;
    background-color: #409EFF;
    color: white;
    text-align: center;
    line-height: 60px;
    font-size: 24px;
    cursor: move;
    box-shadow: 0 2px 12px rgba(0,0,0,0.1);
    z-index: 9999;
}

/* 聊天窗口容器 */
.ai-chat-container {
    position: fixed;
    bottom: 100px;
    right: 30px;
    width: 400px;
    height: 500px;
    border: 1px solid #e6e6e6;
    border-radius: 8px;
    background-color: white;
    box-shadow: 0 2px 12px rgba(0,0,0,0.1);
    z-index: 9998;
    display: none;
    flex-direction: column;
}

/* 聊天内容区域 */
.ai-chat-content {
    flex: 1;
    padding: 10px;
    overflow-y: auto;
    border-bottom: 1px solid #e6e6e6;
}

/* 输入区域 */
.ai-chat-input-area {
    padding: 10px;
    display: flex;
    gap: 10px;
}

.ai-chat-input {
    flex: 1;
    padding: 8px;
    border: 1px solid #e6e6e6;
    border-radius: 4px;
    outline: none;
}

.ai-chat-send, .ai-chat-clear {
    padding: 8px 16px;
    border: none;
    border-radius: 4px;
    cursor: pointer;
}

.ai-chat-send {
    background-color: #409EFF;
    color: white;
}

.ai-chat-clear {
    background-color: #F56C6C;
    color: white;
}

/* 消息样式 */
.msg-item {
    margin: 8px 0;
}
.user-msg {
    text-align: right;
}
.ai-msg {
    text-align: left;
}
.msg-content {
    display: inline-block;
    padding: 8px 12px;
    border-radius: 8px;
    max-width: 80%;
}
.user-msg .msg-content {
    background-color: #409EFF;
    color: white;
}
.ai-msg .msg-content {
    background-color: #f5f5f5;
    color: #333;
}

3.2 交互逻辑(ai-chat.js)

实现拖拽、消息发送、异步请求、历史记录展示等核心功能:

javascript 复制代码
// AI聊天窗口核心逻辑
document.addEventListener('DOMContentLoaded', function() {
    // 1. 获取DOM元素
    const chatBtn = document.getElementById('ai-chat-btn');
    const chatContainer = document.getElementById('ai-chat-container');
    const chatContent = document.getElementById('ai-chat-content');
    const msgInput = document.getElementById('ai-msg-input');
    const sendBtn = document.getElementById('ai-send-btn');
    const clearBtn = document.getElementById('ai-clear-btn');

    // 2. 浮动按钮拖拽功能
    let isDragging = false;
    let startX, startY;
    chatBtn.addEventListener('mousedown', function(e) {
        isDragging = true;
        startX = e.clientX - chatBtn.offsetLeft;
        startY = e.clientY - chatBtn.offsetTop;
        chatBtn.style.cursor = 'move';
    });
    document.addEventListener('mousemove', function(e) {
        if (!isDragging) return;
        const x = e.clientX - startX;
        const y = e.clientY - startY;
        // 限制拖拽范围在可视区域内
        if (x > 0 && x < window.innerWidth - chatBtn.offsetWidth &&
            y > 0 && y < window.innerHeight - chatBtn.offsetHeight) {
            chatBtn.style.left = x + 'px';
            chatBtn.style.top = y + 'px';
        }
    });
    document.addEventListener('mouseup', function() {
        isDragging = false;
        chatBtn.style.cursor = 'pointer';
    });

    // 3. 打开/关闭聊天窗口
    chatBtn.addEventListener('click', function() {
        chatContainer.style.display = chatContainer.style.display === 'none' ? 'flex' : 'none';
    });

    // 4. 发送消息
    sendBtn.addEventListener('click', sendMessage);
    msgInput.addEventListener('keypress', function(e) {
        if (e.key === 'Enter') sendMessage();
    });

    // 5. 清空对话
    clearBtn.addEventListener('click', clearConversation);

    /**
     * 发送消息到后端
     */
    function sendMessage() {
        const question = msgInput.value.trim();
        if (!question) {
            alert('请输入问题!');
            return;
        }

        // 1. 展示用户消息
        appendMessage('user', question);
        msgInput.value = '';

        // 2. 发送异步请求
        fetch('/ai/ask', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded',
            },
            body: 'question=' + encodeURIComponent(question)
        })
        .then(response => response.json())
        .then(data => {
            if (data.success) {
                // 展示AI回答
                appendMessage('ai', data.answer);
            } else {
                // 错误提示
                appendMessage('ai', '❌ ' + data.message);
            }
        })
        .catch(error => {
            // 网络异常处理
            appendMessage('ai', '❌ 网络请求失败,请稍后重试');
            console.error('请求失败:', error);
        });
    }

    /**
     * 清空对话历史
     */
    function clearConversation() {
        fetch('/ai/clear', {
            method: 'POST'
        })
        .then(response => response.json())
        .then(data => {
            if (data.success) {
                chatContent.innerHTML = '';
                alert('对话历史已清空!');
            } else {
                alert('清空失败:' + data.message);
            }
        })
        .catch(error => {
            alert('清空失败:网络异常');
            console.error('清空失败:', error);
        });
    }

    /**
     * 追加消息到聊天窗口
     * @param type 消息类型:user/ai
     * @param content 消息内容
     */
    function appendMessage(type, content) {
        // 转义HTML特殊字符,防止XSS攻击
        const safeContent = content.replace(/&/g, '&amp;')
                                   .replace(/</g, '&lt;')
                                   .replace(/>/g, '&gt;')
                                   .replace(/"/g, '&quot;');
        
        const msgItem = document.createElement('div');
        msgItem.className = 'msg-item ' + (type === 'user' ? 'user-msg' : 'ai-msg');
        msgItem.innerHTML = `<div class="msg-content">${safeContent}</div>`;
        chatContent.appendChild(msgItem);
        // 滚动到底部
        chatContent.scrollTop = chatContent.scrollHeight;
    }
});

3.3 前端页面(ai-chat.html)

整合样式和脚本,实现完整的聊天界面:

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>智慧养殖AI助手</title>
    <link rel="stylesheet" href="ai-chat.css">
</head>
<body>
    <!-- 浮动AI按钮 -->
    <div id="ai-chat-btn" class="ai-chat-btn">🤖</div>

    <!-- 聊天窗口 -->
    <div id="ai-chat-container" class="ai-chat-container">
        <!-- 聊天内容区域 -->
        <div id="ai-chat-content" class="ai-chat-content"></div>
        <!-- 输入区域 -->
        <div class="ai-chat-input-area">
            <input type="text" id="ai-msg-input" class="ai-chat-input" placeholder="请输入您的问题...">
            <button id="ai-send-btn" class="ai-chat-send">发送</button>
            <button id="ai-clear-btn" class="ai-chat-clear">清空</button>
        </div>
    </div>

    <script src="ai-chat.js"></script>
</body>
</html>

四、核心特性解析

4.1 上下文记忆机制

  • 实现原理 :通过HttpSession存储对话历史列表,每条记录包含role(user/assistant)和content(消息内容);
  • 核心价值:AI 能基于历史对话理解上下文,例如用户先问 "如何预防鸡瘟?",再问 "用什么药?",AI 能识别 "什么药" 指的是鸡瘟相关药物;
  • 关键代码AiController中的getConversationHistorycleanHistoryIfNecessary方法。

4.2 智能历史管理

  • 限制规则:最多保留 10 条对话记录(5 轮一问一答),超过后自动删除最早的两条;
  • 设计初衷:平衡上下文连贯性和 API 调用成本(通义千问按 token 计费,历史越多 token 消耗越大);
  • 实现方式 :通过while循环检查列表长度,超出时删除头部元素。

4.3 安全与异常处理

  1. 前端安全 :输入内容转义(appendMessage中的safeContent),防止 XSS 攻击;
  2. 后端安全:API 密钥通过配置文件注入,不在代码中硬编码;
  3. 异常处理
    • 前端:网络请求失败时展示友好提示,而非直接抛出控制台错误;
    • 后端:Controller 层捕获所有异常,返回success: false和错误信息,避免页面 500 错误。

4.4 可扩展性设计

  • 接口化AiService接口隔离 AI 服务商实现,切换为其他 AI 仅需新增实现类;
  • 配置化:所有可变参数(API 地址、模型名称、最大历史数)均通过配置 / 常量定义,便于运维调整;
  • 前端解耦:样式和逻辑分离,支持后续替换为 Vue/React 等框架。

五、特殊页面说明

项目中额外提供了staff_ai.html页面,用于 AI 行为识别分析功能,当前为占位页面,可基于现有架构快速扩展:

  1. 复用现有 AI 服务接口,仅需修改system人设为 "行为识别专家";
  2. 前端新增图片上传功能,支持上传养殖场景图片;
  3. 后端扩展AiService,集成阿里云视觉识别 API,实现行为分析。

六、部署与使用说明

6.1 前置条件

  1. 注册阿里云账号,开通通义千问 API,获取apiKey
  2. 替换aliyun.properties中的apiKey为实际密钥;
  3. 确保后端项目依赖完整(Spring MVC、RestTemplate 等)。

6.2 运行步骤

  1. 启动 Spring MVC 项目(Tomcat 容器);
  2. 访问ai-chat.html页面;
  3. 点击浮动 AI 按钮,输入问题即可与 AI 助手对话。

总结

  1. 核心架构:基于 Spring MVC + 阿里云通义千问的前后端分离架构,通过 Session 管理对话历史,实现上下文记忆;
  2. 关键特性:智能历史记录清理(限制 10 条)、完善的异常处理、安全的配置管理(API 密钥不硬编码);
  3. 扩展价值:前端实现可拖拽的浮动聊天窗口,后端通过接口化设计支持切换 AI 服务商,可快速扩展到其他垂直领域。

该实现方案兼顾了用户体验、安全性和可扩展性,完全符合企业级项目开发规范,可直接作为 AI 助手功能的落地模板。

相关推荐
翼龙云_cloud2 小时前
阿里云渠道商:弹性伸缩爬虫实战 智能应对流量高峰的 3 步方案
爬虫·阿里云·云计算
计算机学姐2 小时前
基于SpringBoot的社区互助系统
java·spring boot·后端·mysql·spring·信息可视化·推荐算法
码农幻想梦3 小时前
实验七 springMVC环境搭建及入门项目
spring
Java程序员威哥3 小时前
Spring AI快速上手:Java集成ChatGPT/文心一言,30分钟实现智能问答接口
java·人工智能·spring boot·后端·python·spring·云原生
BD_Marathon3 小时前
MyBatis的一级缓存
spring·缓存·mybatis
Remember_9933 小时前
【数据结构】深入理解排序算法:从基础原理到高级应用
java·开发语言·数据结构·算法·spring·leetcode·排序算法
qq_12498707534 小时前
基于Spring Boot的心理咨询预约微信小程序(源码+论文+部署+安装)
java·spring boot·后端·spring·微信小程序·小程序·毕业设计
Zxxxxxy_4 小时前
Spring MVC
开发语言·spring·maven
爬山算法15 小时前
Hibernate(51)Hibernate的查询缓存如何使用?
spring·缓存·hibernate