毕设所有选题:
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、管理员:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

五、浏览地址

前台地址:http://localhost:8800

  • 用户账号密码:test/123456

  • 管理员账户密码:admin/123456

六、部署教程

  1. 使用Navicat或者其它工具,在mysql中创建对应名称的数据库,并执行项目的sql文件

  2. 使用IDEA/Eclipse导入springboot项目,若为maven项目请选择maven,等待依赖下载完成

  3. 修改application.yml里面的数据库配置和数据库配置,还有通义千问的apikey配置,
    src/main/java/org/example/springboot/SpringbootApplication.java启动后端项目

  4. vscode或idea打开vue3项目

  5. 在编译器中打开terminal,执行npm install 依赖下载完成后执行 npm run serve,执行成功后会显示访问地址

七、ai对话部分代码

    /**
 * 流式聊天接口(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);
        }
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐