在智慧养殖等垂直领域的系统开发中,集成 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, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"');
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中的getConversationHistory和cleanHistoryIfNecessary方法。
4.2 智能历史管理
- 限制规则:最多保留 10 条对话记录(5 轮一问一答),超过后自动删除最早的两条;
- 设计初衷:平衡上下文连贯性和 API 调用成本(通义千问按 token 计费,历史越多 token 消耗越大);
- 实现方式 :通过
while循环检查列表长度,超出时删除头部元素。
4.3 安全与异常处理
- 前端安全 :输入内容转义(
appendMessage中的safeContent),防止 XSS 攻击; - 后端安全:API 密钥通过配置文件注入,不在代码中硬编码;
- 异常处理 :
- 前端:网络请求失败时展示友好提示,而非直接抛出控制台错误;
- 后端:Controller 层捕获所有异常,返回
success: false和错误信息,避免页面 500 错误。
4.4 可扩展性设计
- 接口化 :
AiService接口隔离 AI 服务商实现,切换为其他 AI 仅需新增实现类; - 配置化:所有可变参数(API 地址、模型名称、最大历史数)均通过配置 / 常量定义,便于运维调整;
- 前端解耦:样式和逻辑分离,支持后续替换为 Vue/React 等框架。
五、特殊页面说明
项目中额外提供了staff_ai.html页面,用于 AI 行为识别分析功能,当前为占位页面,可基于现有架构快速扩展:
- 复用现有 AI 服务接口,仅需修改
system人设为 "行为识别专家"; - 前端新增图片上传功能,支持上传养殖场景图片;
- 后端扩展
AiService,集成阿里云视觉识别 API,实现行为分析。
六、部署与使用说明
6.1 前置条件
- 注册阿里云账号,开通通义千问 API,获取
apiKey; - 替换
aliyun.properties中的apiKey为实际密钥; - 确保后端项目依赖完整(Spring MVC、RestTemplate 等)。
6.2 运行步骤
- 启动 Spring MVC 项目(Tomcat 容器);
- 访问
ai-chat.html页面; - 点击浮动 AI 按钮,输入问题即可与 AI 助手对话。
总结
- 核心架构:基于 Spring MVC + 阿里云通义千问的前后端分离架构,通过 Session 管理对话历史,实现上下文记忆;
- 关键特性:智能历史记录清理(限制 10 条)、完善的异常处理、安全的配置管理(API 密钥不硬编码);
- 扩展价值:前端实现可拖拽的浮动聊天窗口,后端通过接口化设计支持切换 AI 服务商,可快速扩展到其他垂直领域。
该实现方案兼顾了用户体验、安全性和可扩展性,完全符合企业级项目开发规范,可直接作为 AI 助手功能的落地模板。