毕设所有选题:
https://blog.csdn.net/2303_76227485/article/details/131104075
基于Springboot+Vue3+Ai对话的非遗传承管理系统(源代码+数据库+2万字论文)
项目编号:260
一、系统介绍
本项目前后端分离,分为用户、管理员2种角色。
1、用户:
- 登录、注册、热门作品查询、作品搜索、最新作品查询、活动报名、非遗课程学习、传承人查询
- 非遗手办商城、订单提交、支付、取消、确认收货
- 修改密码、个人信息修改、收货地址维护、默认地址设置、非遗作品发布
- AI智能助手模块:创建会话、会话列表查询、会话消息历史查询、流式对话、非流式对话、更新会话标题、删除会话
2、管理员:
- 首页大屏数据图表统计:用户数、订单数、销售额、非遗作品数、订单趋势曲线图、销售额柱状图、订单状态分布环状图、非遗类别饼状图
- 用户管理
- 非遗作品管理:非遗作品增删改查、作品发布、作品下架
- 课程管理:课程管理、章节管理
- 活动管理:报名审核、活动签到、报名列表查询
- 商城管理:商品增删改查、商品上架、商品下架、商品库存更新、商品分类管理
- 订单管理(查看、发货)
- 传承人管理:传承人增删改查、传承人与作品关联管理
4、亮点:
- 实现ai对话优化用户体验
- 后台首页大屏使用echarts图表统计,更直观的看出系统的运行数据
二、所用技术
后端技术栈:
- Springboot3
- mybatisPlus
- Jwt
- Spring Security
- Mysql
- Maven
前端技术栈:
- Vue3
- Vue-router
- axios
- elementPlus
- echarts
三、环境介绍
基础环境 :IDEA/eclipse, JDK17或以上, Mysql5.7及以上, Maven3.6, node14, navicat, 通义千问apikey
所有项目以及源代码本人均调试运行无问题 可支持远程调试运行
四、页面截图
文档截图:


1、用户:






























2、管理员:





















五、浏览地址
-
用户账号密码:test/123456
-
管理员账户密码:admin/123456
六、部署教程
-
使用Navicat或者其它工具,在mysql中创建对应名称的数据库,并执行项目的sql文件
-
使用IDEA/Eclipse导入springboot项目,若为maven项目请选择maven,等待依赖下载完成
-
修改application.yml里面的数据库配置和数据库配置,还有通义千问的apikey配置,
src/main/java/org/example/springboot/SpringbootApplication.java启动后端项目
-
vscode或idea打开vue3项目
-
在编译器中打开terminal,执行npm install 依赖下载完成后执行 npm run serve,执行成功后会显示访问地址
七、ai对话部分代码
java
/**
* 流式聊天接口(SSE)
*/
@PostMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> chatStream(@Valid @RequestBody ChatRequest request,
HttpServletResponse response) {
response.setHeader("X-Accel-Buffering", "no"); // 禁用Nginx缓冲
response.setHeader("Cache-Control", "no-cache, no-store"); // 禁用浏览器缓冲
response.setHeader("Pragma", "no-cache");
response.setCharacterEncoding("UTF-8");
Long userId = JwtTokenUtils.getCurrentUserId();
// 验证权限
if (!sessionService.isSessionOwnedByUser(request.getSessionId(), userId)) {
return Flux.just("data: 无权访问此会话\n\n");
}
log.info("开始流式对话,sessionId: {}, userId: {}", request.getSessionId(), userId);
return tongyiQianwenService.chatStream(request.getSessionId(), request.getUserMessage())
.map(chatResponse -> "data: " + JSON.toJSONString(chatResponse) + "\n\n");
}
/**
* 流式聊天(SSE实时返回)
*/
public Flux<ChatResponse> chatStream(String sessionId, String prompt) {
log.info("开始AI对话,sessionId: {}, userMessage: {}", sessionId, prompt);
// 保存用户消息
sessionService.saveMessage(sessionId, "user", prompt);
// 1. 获取会话上下文
SessionContext context = sessionManager.getSessionContext(sessionId);
if (context == null) {
context = new SessionContext(sessionId); // 初始化空上下文
sessionManager.saveSessionContext(context);
}
String finalSessionId = context.getSessionId();
// 2. 构建请求体
Map<String, Object> requestBody = buildRequestBody(context, prompt, true);
// 定义原子变量累加完整响应文本
AtomicReference<String> fullAnswer = new AtomicReference<>("");
// 3. 流式调用API
return WebClient.create()
.post()
.uri(apiUrl)
.headers(headers -> headers.addAll(buildHeaders()))
.bodyValue(requestBody)
.retrieve()
// 禁用WebClient的响应缓冲(关键!)
.bodyToFlux(String.class)
// 逐个处理通义千问返回的流式片段
.doOnNext(line -> log.info("通义千问原生片段:{}", line)) // 打印原生片段,验证是否逐行返回
// 步骤1:先过滤空行(提前剔除无效数据)
.filter(line -> line != null && !line.trim().isEmpty())
.scan(new Accumulator(null, ""), (acc, line) -> {
// 空行处理:保留原有累加文本,line设为null
if (line == null || line.isEmpty()) {
return new Accumulator(null, acc.getFullAnswer());
}
// 解析SSE数据前缀
String data = line.startsWith("data: ") ? line.substring(6) : line;
// 解析流式片段
TongyiQianwenResponse fragment = JSON.parseObject(data, TongyiQianwenResponse.class);
// 1. 流结束标识:返回完整响应
if ("stop".equals(fragment.getOutput().getFinish_reason())) {
return new Accumulator(data, acc.getFullAnswer());
}
// 解析失败:保留原有累加文本,line设为null
if (fragment == null || fragment.getOutput() == null) {
return new Accumulator(null, acc.getFullAnswer());
}
// 正常片段:累加文本
String newFullAnswer = acc.getFullAnswer() + fragment.getOutput().getText();
// 返回新的累加器(当前行 + 最新累加文本)
return new Accumulator(line, newFullAnswer);
})
// 步骤3:过滤累加器中line为null的无效数据(替代map里返回null)
.filter(acc -> acc.getLine() != null)
.map(acc -> {
String line = acc.getLine();
String currentFullAnswer = acc.getFullAnswer();
ChatResponse response = new ChatResponse();
// 空行/解析失败的累加器直接返回null(后续filter过滤)
if (line == null) {
return null;
}
// 解析SSE数据前缀
String data = line.startsWith("data: ") ? line.substring(6) : line;
// 2. 解析流式片段
TongyiQianwenResponse fragment = JSON.parseObject(data, TongyiQianwenResponse.class);
// 1. 流结束标识:返回完整响应
if ("stop".equals(fragment.getOutput().getFinish_reason())) {
response.setSuccess(true);
response.setSessionId(finalSessionId);
response.setDone(true);
response.setAnswer(currentFullAnswer); // 完整回答
// 保存完整回答到会话
sessionService.saveMessage(finalSessionId, "assistant", currentFullAnswer);
log.info("AI对话完成,sessionId: {}, 响应长度: {}", sessionId, currentFullAnswer.length());
return response;
}
if (fragment == null || fragment.getOutput() == null) {
return null;
}
// 3. 异常处理(非stop结束)
if (fragment.getOutput().getFinish_reason() != null && !"null".equals(fragment.getOutput().getFinish_reason())
&& !"stop".equals(fragment.getOutput().getFinish_reason())) {
response.setSuccess(false);
response.setMessage(fragment.getOutput().getFinish_reason());
response.setRequestId(fragment.getRequest_id());
return response;
}
// 4. 正常片段:返回单条流式响应
response.setSuccess(true);
response.setSessionId(finalSessionId);
response.setAnswer(fragment.getOutput().getText()); // 单片段文本
response.setDone(false);
response.setRequestId(fragment.getRequest_id());
// 设置token用量
response.setUsage(Map.of(
"inputTokens", fragment.getUsage().getInput_tokens(),
"outputTokens", fragment.getUsage().getOutput_tokens(),
"totalTokens", fragment.getUsage().getTotal_tokens()
));
return response;
})
// 过滤掉null响应(空行/解析失败的情况)
.filter(res -> res != null);
}