☰
AI大模型+Spring Boot+Vue非遗数字化管理系统全栈实战
2026/10/3 9:10:50 网站建设 项目流程

这套非遗数字化管理系统我前后做了大概一个半月,真正动手敲代码的时间其实只有三周,剩下的时间全花在需求梳理和文档整理上。这也印证了我一直以来的观点——"基于AI大模型应用 + Spring Boot + Vue"这种全栈项目,真正的难点从来不是某一个技术点,而是怎么把AI能力、Java后端、前端页面和一套实际业务揉在一起。如果您正打算做一个含金量高一点的毕业设计、课程设计或者个人作品,这篇记录可以省下不少摸索时间,项目拆解、技术选型、核心代码、部署细节和设计说明书写作要点都会覆盖到。

项目的整体组合是:Spring Boot 3 提供后端服务,Vue 3 + Element Plus 搭建管理端页面,AI大模型应用通过统一封装接口对外暴露,主要承担非遗智能问答、知识科普生成和讲解词翻译三类任务。系统并不是把非遗项目登记进数据库就完事,而是把数字化采集、知识检索、内容生成串成一条完整链路。文章里凡是涉及代码的地方,我会给出能直接复制的实现;涉及设计说明书的地方,也会把章节结构和写作思路写出来。

1. 项目拆解:非遗数字化管理系统到底在管什么

1.1 业务全景与模块边界

非遗数字化管理系统,字面上是"管理非遗",但数字化才是真正的重心。早期做这类系统,很多人容易把它做成一个简单的增删改查后台——非遗项目表、传承人表、新闻表、用户表,再加几个页面就收工。这样不是说不行,但"数字化"三个字没有体现,AI更是无从谈起。我最后落地的模块是这样拆的:

  • 非遗资源库:管理非遗项目(国家级、省级、市级分类,申报批次,申报地区,传承方式)、传承人档案、非遗视频/图片/音频/文献资料。这是整个系统的数据底座,后面所有AI能力都依赖这个库。
  • 数字化采集与加工:视频转码切片、图片压缩、OCR识别、音频转文字,把线下散落的资料变成线上结构化数据。这一步决定了资料能不能被AI有效使用。
  • AI知识服务:面向公众和管理人员提供非遗智能问答、知识科普自动生成、多语言讲解。这一层是大模型应用的核心承载,也是系统的亮点。
  • 业务管理:活动申报、展览安排、文创商品管理、用户权限管理、数据统计。这部分是传统管理系统的部分,技术难度不大,但工作量不小。

模块边界一定要在开发前画清楚,否则AI服务很容易和业务模块缠在一起。我采用的原则是:AI服务单独成一个模块,只依赖资源库的数据接口,不直接操作业务表。这样即便大模型接口出了故障,基础增删改查功能还能正常跑,不会出现"AI挂了整个后台都用不了"的尴尬局面。实际开发中我还加了一层开关:通过配置项控制AI功能是否启用,演示和答辩时可以随时切换,这个细节对现场演示很有用。

1.2 AI大模型应用的真实落点

很多人一听说"基于AI大模型",第一反应是让系统能对话。对话只是表象,真正要解决的是"知识检索"和"内容生成"两个问题。非遗资料散落在PDF、Word、网页和口述录音里,用户想查一个冷门非遗项目,传统模糊匹配效果很差。我用的方案是检索增强生成(RAG):先把文档切分、向量化存入知识库,用户提问时先检索相关片段,再让大模型基于这些片段生成回答。

比如游客问"蜀绣和湘绣到底有什么区别",传统系统用SQL的LIKE去匹配,可能只命中"蜀绣"两个字,然后把几百字的介绍整页丢出来;RAG方案会先检索到两个绣种的工艺、针法、产地资料,再让大模型组织成一段200字左右的对比回答。这一步才是"AI大模型应用"在系统里真正的价值,不是让AI回答泛泛的问题,而是让它基于你自己的知识库干活。

内容生成方面,我做了三个实用功能:根据传承人档案自动生成简介、根据非遗项目信息生成讲解词、根据展览主题生成前言。这类任务不需要特别强的推理,但要模板规范、事实准确,用提示词约束就能做到不错的水平。翻译功能则是把中文非遗介绍交给模型翻译成英文和日文,供外宾参观场景使用,效率比传统人工翻译高很多。整体设计上,我把AI能力封装成三个接口:问答接口、内容生成接口、翻译接口,前端所有带"AI"字样的功能都走这三个接口,后台只负责配置模型参数和知识库文档。这套设计的最大好处是:AI模块可以独立替换,以后换模型服务商,业务代码基本不用动。

2. 技术选型与开发环境:为什么是 Spring Boot 3 + Vue 3

2.1 后端选型与IDEA社区版的实操方式

后端选型时,不少人在Spring Boot和Python FastAPI之间纠结。如果纯做AI应用,FastAPI确实香,Python的大模型生态太成熟了。但非遗管理系统本质上是一个业务系统,有用户、权限、文件、工作流,需要大量CRUD和事务管理。Spring Boot在这块的成熟度太高了:MyBatis-Plus做持久层、Spring Security做权限、Spring Data Redis做缓存,一条龙下来几乎不用造轮子。我的做法是"业务系统用Java,AI能力通过HTTP调用模型平台",既没有引入Python技术栈的运维成本,又拿到了大模型能力。

版本上我选了Spring Boot 3.2.x,搭配Java 17。这里有个容易踩坑的点:Spring Boot 3把javax包迁移成了jakarta包,很多老教程、老依赖会直接报错,网上一搜全是Spring Boot 2.x的配置,照抄会翻车。如果用Spring Boot 3,Spring Security也必须是6.x版本,配置方式和2.x差别很大,后面权限章节我会单独讲。

如果你用的是IDEA社区版(免费版),里面没有Spring Boot项目向导。我的解决办法是打开 start.spring.io 网站,选Maven、Java 17、Spring Boot 3.2.x,勾上Web、Validation、MySQL Driver、Redis等依赖,生成项目压缩包后解压,再用IDEA打开。社区版完全能写Spring Boot,只是没有图形化创建按钮,启动就走Application类的main方法,跑起来没有任何区别。记得在IDEA的插件市场装Lombok插件,否则实体类的@Data注解不生效,编译会报找不到getter和setter。

2.2 前端栈与基础设施:Vite 5、Element Plus 与Node环境

前端选了Vue 3.4 + Vite 5 + Element Plus + Pinia + Vue Router。这套组合是当前社区最稳的方案,资料多、招聘需求也大,和Spring Boot属于"毕业设计黄金套餐",后续写设计说明书也顺手。为什么不选Vue 2?项目是全新的,没理由用老技术。为什么不选React?个人全栈项目Vue在国内的社区资料更丰富,Vue Router和Element Plus对管理端场景支持很成熟。

环境配置上,Node.js必须用18以上版本,Vite 5对Node版本有要求。装依赖前先把npm源切到国内镜像,别问我为什么强调这一点,直接跑一次npm install就知道什么叫怀疑人生。脚手架用npm create vite@latest创建,选择Vue和JavaScript——个人项目用JavaScript能更快交付,团队项目建议上TypeScript。Element Plus有两种用法:小型项目直接全局引入,app.use(ElementPlus)三行代码搞定;大项目用unplugin-auto-import和unplugin-vue-components做按需导入,打包体积更小。我建议个人项目先用全局引入,先把功能做完,性能优化放到后面再考虑。

3. 核心代码讲解:AI大模型接入与非遗知识库问答

3.1 统一大模型客户端:OkHttp流式解析SSE

AI模块的核心是一个兼容主流模型服务商接口的客户端。现在大多数模型平台都提供兼容OpenAI协议的接口,请求和响应格式统一,所以我做了个独立的AiChatClient,把HTTP调用、流式解析、错误处理都封装在里面。用OkHttp而不是RestTemplate,是因为RestTemplate在Spring Boot 3里已经进入维护期,WebClient是响应式的,对流式SSE虽然也能支持,但OkHttp用起来更直观,连接超时和逐行读取都好控制。依赖就两个:OkHttp和fastjson2。

@Component public class AiChatClient { private static final Logger log = LoggerFactory.getLogger(AiChatClient.class); private final OkHttpClient httpClient = new OkHttpClient.Builder() .connectTimeout(30, TimeUnit.SECONDS) .readTimeout(60, TimeUnit.SECONDS) .build(); @Value("${ai.base-url}") private String baseUrl; @Value("${ai.api-key}") private String apiKey; @Value("${ai.model}") private String model; public void streamChat(String systemPrompt, String userMessage, Consumer<String> tokenConsumer) { JSONObject payload = new JSONObject(); payload.put("model", model); payload.put("stream", true); payload.put("temperature", 0.3); JSONArray messages = new JSONArray(); JSONObject system = new JSONObject(); system.put("role", "system"); system.put("content", systemPrompt); messages.add(system); JSONObject user = new JSONObject(); user.put("role", "user"); user.put("content", userMessage); messages.add(user); payload.put("messages", messages); Request request = new Request.Builder() .url(baseUrl + "/chat/completions") .header("Authorization", "Bearer " + apiKey) .post(RequestBody.create(payload.toJSONString(), MediaType.parse("application/json; charset=utf-8"))) .build(); try (Response response = httpClient.newCall(request).execute()) { if (!response.isSuccessful()) { throw new RuntimeException("AI接口调用失败,HTTP状态码:" + response.code()); } ResponseBody body = response.body(); if (body == null) { return; } BufferedReader reader = new BufferedReader( new InputStreamReader(body.byteStream(), StandardCharsets.UTF_8)); String line; while ((line = reader.readLine()) != null) { if (line.startsWith("data:")) { String data = line.substring(5).trim(); if ("[DONE]".equals(data)) { break; } JSONObject json = JSONObject.parseObject(data); JSONArray choices = json.getJSONArray("choices"); if (choices == null || choices.isEmpty()) { continue; } JSONObject delta = choices.getJSONObject(0).getJSONObject("delta"); String token = delta.getString("content"); if (token != null && !token.isEmpty()) { tokenConsumer.accept(token); } } } } catch (IOException e) { log.error("AI流式调用失败", e); throw new RuntimeException(e); } } }

这段代码有几个关键点。第一,stream参数必须设置为true,否则接口会等全部生成完才返回,用户端体验很差。第二,readTimeout给到60秒,长文本生成很容易超过30秒。第三,SSE的每一行都是以data:开头的,收到[DONE]表示生成结束,这两个约定是所有兼容OpenAI协议的模型平台都遵循的。我已经把超时时间、模型名称、接口地址都放到了application.yml里,切换模型服务商时不用改代码,只改配置就行。

3.2 RAG知识库:向量切分、检索与提示词拼接

RAG部分设计成两个模块:文档处理模块负责把非遗资料切分、向量化并存储;检索模块负责计算相似度、召回最相关的片段。文档切分是很容易被忽视但极其重要的环节,我的策略是优先按文档原有的章节标题切,每个片段控制在300到500字,相邻片段重叠50字,这样能防止段落上下文在切分处被截断。切完之后调用模型平台的Embedding接口,把每段文字转成向量。

这里有个工程上的取舍:为什么不直接上向量数据库?因为文档量在几千条以内时,把向量存到MySQL的一个单独表,查询时全表扫一遍算余弦相似度,耗时也就几毫秒到几十毫秒,完全够用。引入Milvus或Elasticsearch反而增加了部署和维护成本。等以后文档量到百万级再迁移也不迟,这种渐进式架构对个人项目非常友好。检索实现如下:

@Service public class KnowledgeBaseService { @Resource private KnowledgeChunkMapper chunkMapper; public List<KnowledgeChunk> searchTopK(float[] queryVector, int topK) { List<KnowledgeChunk> all = chunkMapper.selectList(null); List<KnowledgeChunk> ranked = new ArrayList<>(); for (KnowledgeChunk chunk : all) { double score = cosineSimilarity(queryVector, chunk.getVector()); chunk.setScore(score); ranked.add(chunk); } ranked.sort((a, b) -> Double.compare(b.getScore(), a.getScore())); return ranked.stream().limit(topK).collect(Collectors.toList()); } private double cosineSimilarity(float[] a, float[] b) { double dot = 0, normA = 0, normB = 0; for (int i = 0; i < a.length; i++) { dot += a[i] * b[i]; normA += a[i] * a[i]; normB += b[i] * b[i]; } return dot / (Math.sqrt(normA) * Math.sqrt(normB) + 1e-8); } }

检索到TopK片段之后,把它们拼进系统提示词,再调用大模型。提示词的设计比大多数人想象的重要,我实际用的版本是这样的:

你是一名非物质文化遗产领域的专业讲解员。 请只根据下面提供的资料片段回答用户问题。 资料中没有的内容,要明确回复"资料中暂未找到相关信息",不要编造。 【资料1】... 【资料2】...

这段提示词里"不要编造"四个字非常关键。大模型训练数据里可能包含非遗相关的信息,但很可能和你系统里的资料不一致。如果不明确限定"只根据资料回答",模型就会自由发挥,把错误信息当作事实说出来。我在实际测试中发现,加了RAG和这条限定之后,回答的准确性从大约六成提升到九成以上,效果非常明显。

3.3 Vue端流式接收:fetch + ReadableStream

前端接收流式响应时,要注意axios底层是基于XHR的,虽然能拿到响应内容,但流式事件的处理很不直观。我用的是fetch + ReadableStream,既能处理GET请求,也能轻松应对POST和自定义Header。大模型返回的内容是标准SSE格式,每行以data:开头,逐帧解析并追加到页面即可。

async function streamChat(question) { const response = await fetch('/api/ai/chat?question=' + encodeURIComponent(question), { headers: { 'Accept': 'text/event-stream' } }) const reader = response.body.getReader() const decoder = new TextDecoder('utf-8') let buffer = '' while (true) { const { value, done } = await reader.read() if (done) break buffer += decoder.decode(value, { stream: true }) const lines = buffer.split('\n') buffer = lines.pop() for (const line of lines) { if (line.startsWith('data:')) { const data = line.slice(5).trim() if (data === '[DONE]') continue answerText.value += data answerHtml.value = marked.parse(answerText.value) } } } }

buffer的处理是这段代码的隐藏重点。网络传输过程中,一个SSE事件可能被拆成多个数据帧到达,如果每次都直接把data:行拿来解析,会遇到内容被截断的情况。我先把所有收到的数据追加到buffer里,再按换行符切分,最后把最后一行留回buffer继续拼接,这样能保证每个事件被完整处理。渲染方面,大模型返回的是Markdown格式,我用marked库转成HTML展示,同时用DOMPurify做了一次清洗,防止模型输出内容里夹带不安全的HTML标签。风格上还做了一个打字机效果,配合流式刷新,用户能直观看到文字逐字出现,互动感强很多。

4. 数字化资源管理:视频、文件与监控的工程化方案

4.1 非遗视频的HLS切片与Vue播放器

非遗资源里视频是重头戏:老艺人技艺展示、非遗项目实地拍摄、传承人口述访谈,动辄几百MB甚至几GB。管理端上传后,如果直接让前端播放mp4文件,大文件会在拖动进度条时卡顿,而且各浏览器兼容性参差不齐。我的方案是统一用FFmpeg把视频转码成HLS格式,生成m3u8索引文件和ts切片,前端用hls.js播放。

FFmpeg转码命令看起来很简单,参数却有不少讲究:

ffmpeg -i input.mp4 -c:v libx264 -c:a aac -hls_time 10 \ -hls_list_size 0 -hls_segment_filename "output_%03d.ts" output.m3u8

-hls_time 10表示每10秒切一个切片文件,-hls_list_size 0表示保留所有切片,不循环覆盖。如果要做多码率适配,可以再加-filter_complex做视频缩放,分别生成720p和1080p两个版本的m3u8,再用主m3u8文件引用子m3u8。不过个人项目里我建议先生成单码率,跑通整个链路后再优化,否则FFmpeg的参数调试会占掉大量时间。

前端播放器组件用hls.js,兼容性处理是重点。Safari浏览器原生支持HLS,直接设置video元素的src就能播放;Chrome和Firefox不行,必须引入hls.js来转码播放。组件里先判断videoElement.canPlayType('application/vnd.apple.mpegurl'),能播放就直接用原生,否则走hls.js分支。播放器我用的video.js + hls.js的组合,UI控件、字幕、清晰度切换都现成,比自己封装省事。

4.2 文件存储与MinIO:配置与异步化

视频、图片这类大文件,直接用Java接收MultipartFile保存到本地磁盘是最简单的方案,开发环境足够用。但文件量一多,分散在磁盘各处,备份和迁移会很痛苦。我采用的方式是:开发环境用本地磁盘,通过Nginx或Spring Boot的静态资源映射暴露访问路径;生产环境切换到MinIO对象存储,两者对外接口统一,后端服务感知不到差异。

MinIO的配置不复杂,依赖和连接Client封装好之后,上传就是一个方法的事:

@Configuration public class MinioConfig { @Value("${minio.endpoint}") private String endpoint; @Value("${minio.access-key}") private String accessKey; @Value("${minio.secret-key}") private String secretKey; @Bean public MinioClient minioClient() { return MinioClient.builder() .endpoint(endpoint) .credentials(accessKey, secretKey) .build(); } }

上传接口里要特别注意文件名的处理。直接用用户上传的原始文件名有两个风险:一是可能包含路径和恶意字符,二是多个用户可能上传同名文件造成覆盖。我的做法是使用UUID.randomUUID()生成新的对象名,再拼接原始文件名的后缀,既能避免冲突,又能保留扩展名供浏览器识别类型。上传大视频时接口很可能超时,所以要异步化:上传成功后立即返回"已上传,正在处理"的状态,后台线程池执行转码、截取封面、更新数据库等耗时操作,前端通过轮询或WebSocket获取进度。如果不想引入消息队列,用Spring的CompletableFuture.runAsync加状态字段就能撑住个人项目的并发量。

4.3 Spring Boot Admin监控与WebSocket进度推送

系统上线后有几个指标必须盯:内存占用、接口响应时间、异步任务积压数量。Spring Boot Actuator已经提供了基础的监控端点,但页面上一个个查JSON太难受了,我直接引入Spring Boot Admin。方式很取巧:新建一个独立的管理端模块,启动类上加@EnableAdminServer注解,这个模块跑在一个独立端口上;业务模块引入spring-boot-admin-starter-client,在yml里配置spring.boot.admin.client.url指向管理端地址,业务服务的信息就会自动上报上去。管理端界面能看到服务列表、实时内存曲线、HTTP接口调用统计,还能动态调整日志级别,排查生产问题非常有用。

这里有个很多人容易犯的错:Spring Boot集成WebSocket时,到处找yml配置项。实际上原生WebSocket(@ServerEndpoint方式)不需要在application.yml里写任何专用配置,只需要注册一个ServerEndpointExporter的Bean,再写一个@ServerEndpoint注解的配置类就行。Spring的STOMP方案也是在配置类里addEndpoint注册路径,yml里默认没有全局开关。网上一堆教程往yml里塞自定义的websocket配置,那些基本都是瞎写。我实际做视频转码进度推送时,用WebSocket把任务状态推给管理端页面,整个WebSocket模块的配置加起来不到20行代码。

5. 前后端联调、权限部署与关键配置

5.1 开发环境联调:Vite proxy与CORS取舍

开发阶段前后端分离,前端跑在5173端口,后端跑在8080端口,必然面临跨域问题。解决跨域有两条路:一条是后端配置CORS,另一条是前端配置Vite代理。我强烈推荐Vite代理,因为开发环境下代理方案不需要后端做任何额外配置,浏览器请求的是5173端口,Vite在本地把请求转发到8080,浏览器根本感知不到跨域的存在。

// vite.config.js server: { port: 5173, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } }

如果选择后端CORS方案,需要注意allowCredentials(true)和allowedOrigins不能使用通配符*,必须明确写出允许的域名,否则带Cookie的请求会失败。个人项目建议直接引入spring-boot-starter-validation做参数校验,Controller的@Valid注解加DTO校验字段,比手写一堆if判断清爽太多。接口返回结构我统一封装成Result<T>,包含code、message、data三个字段,前端axios拦截器统一处理错误码,这样接口写多了之后维护成本并不会线性增长。

5.2 权限路由:Vue动态路由与Spring Security配合

管理端有超级管理员、内容编辑员、审核员三种角色,各自的菜单和操作权限不同。前端的做法是动态路由:登录成功后,后端返回当前用户的角色和菜单权限,前端根据这个列表动态生成路由,调用router.addRoute注册到路由实例。

const dynamicRoutes = generateRoutes(menus) dynamicRoutes.forEach(route => router.addRoute(route))

这里有个细节:未登录用户第一次进入页面时,默认只能访问登录页,其他路由都不存在。一旦登录成功,动态添加的路由才会生效。为了防止刷新页面后路由丢失,我在Vuex/Pinia里持久化了菜单信息,刷新时重新从后端拉取。路由守卫里还做了另一层判断:访问的后端接口如果没有权限,后端会返回403,前端弹出无权限提示。前端的动态路由更多是为了交互体验,真正的安全防线必须放在后端,不能只靠隐藏按钮。

后端权限用的是Spring Security 6 + JWT。Spring Boot 3下要开启方法级权限,用的是@EnableMethodSecurity注解,不再是Spring Boot 2时代的@EnableGlobalMethodSecurity,这一点很多人会踩坑。接口上用@PreAuthorize("hasRole('ADMIN')")控制访问权限,比如删除非遗项目这个操作,只有管理员能调,编辑员只能编辑内容,不能删除。

5.3 生产部署:Vue打包进Spring Boot的两种方式

部署方案我踩过不少坑,最后稳定下来的是两根路线。路线一是前后端分离部署:前端打包后放到Nginx静态目录,后端独立跑在8080,通过Nginx反向代理把/api路径转发到后端。路线二是单包部署:执行npm run build后,把dist目录复制到Spring Boot的src/main/resources/static,打成一个jar包,端口和静态资源全由Spring Boot管理。对毕设和个人项目来说,路线二更省事,一个jar包直接java -jar就完事,演示环境部署非常快。

但路线二有个经典坑:Vue Router用history模式时,前端路由是虚拟路径,比如/admin/items,刷新页面时Spring Boot会去静态目录找admin/items这个文件,找不到就返回404。解决方法是加一个Spa转发Controller,把所有不带扩展名的路径都转发到index.html:

@Controller public class SpaForwardController { @GetMapping(value = {"/", "/{path:[^\\.]*}", "/{path:[^\\.]*}/{rest:[^\\.]*}"}) public String forward() { return "forward:/index.html"; } }

注意这里必须用forward而不是redirect,redirect会让浏览器地址栏变成index.html,视觉上很怪;forward是服务器内部转发,URL不变。正则里的[^\.]*是为了排除带点号的静态资源,比如/assets/app.js这种请求会走正常静态资源映射,不会被拦截。

6. 设计说明书编写:从代码到文档的转化要点

6.1 设计说明书的标准结构与每章写作重点

项目能跑起来只是第一步,文档写不好,答辩和验收会非常吃亏。非遗数字化管理系统这种"业务系统 + AI应用"的组合,说明书结构一般按八章来写:绪论、相关技术介绍、需求分析、总体设计、详细设计、系统实现、系统测试、总结与展望。

绪论部分最容易被忽略的是"研究现状"。非遗数字化是全球性的议题,欧盟有文化遗产数字化项目,中国各地方也在推进非遗数据库建设,这些内容写进"国内外研究现状"可以瞬间提升论文的文献支撑感。需求分析章节除了功能需求,还要写非功能需求,比如系统响应时间、并发用户数、数据安全等级。总的架构建议画一张大图:前端Vue展示层、后端Spring Boot服务层、数据存储层,再加一个独立的AI服务模块放旁边,说明清楚数据流方向。这张图在毕业设计答辩里往往是评委第一个盯的地方。

6.2 数据库设计与图表工具

数据库设计要跟着功能走,我把核心表整理成了一张表,文档里直接照着这个结构写:

表名说明关键字段
sys_user用户表id, username, password, real_name, role_id
sys_role角色表id, role_code, role_name
non_heritage_item非遗项目表id, name, level, batch, category, region, status, content
inheritor传承人表id, name, item_id, level, phone, photo, story
video_asset视频资源表id, item_id, title, original_name, oss_url, hls_url, cover_url, status
knowledge_chunk知识库切片表id, source_id, content, vector_json, embedding_model

表之间的关系不用画得太复杂,E-R图里把主外键关系画清楚就行。工具方面,draw.io画架构图和E-R图完全够用,ProcessOn在线协作也不错;数据库表结构用数据库客户端导出SQL,然后在文档里贴核心建表语句就够,不需要把所有表结构都贴进去。要注意一点:文档中的表字段必须和实际数据库一致,很多人写完代码再凭记忆写文档,字段名对不上,答辩时被问数据库设计就傻眼。

6.3 含AI模块的设计说明书怎么写

既然标题里写了"基于AI大模型应用",说明书里AI相关的内容就不能只是简单提一句。我建议专门用一个章节写AI模块设计,至少包含五块内容:模型选型对比、Prompt设计、RAG流程、知识库构建、评测结果。

模型选型对比可以列一个表格,对比不同模型在非遗问答场景下的优劣势、调用成本、部署方式。Prompt设计要把实际使用的系统提示词贴出来,并说明为什么这么写。RAG流程画一个从左到右的流程图:文档导入、文本清洗、切片、向量化、存储;查询时问题编码、向量检索、拼接上下文、模型生成。知识库构建部分写清楚切片大小、重叠策略、向量存储方案。最关键的是评测结果,我实际做了30个非遗相关问题的测试集,分别测试"直接问大模型"和"RAG + 大模型"两种模式,把回答准确率对比数据做成了表格放在说明书里。这个评测数据在答辩现场的效果非常好,评委看到量化结果后基本不会再纠结"AI功能是不是摆设"。

7. 常见问题与避坑记录

7.1 开发期的五个高频坑

围绕这套系统开发时遇到的实际问题,我整理成了一份问题速查表,按解决成本从低到高排列:

问题现象原因与解决办法
IDEA社区版没有Spring Boot向导新建项目找不到Spring Initializr选项去start.spring.io生成项目包再导入,或用Maven手动创建pom.xml
Spring Boot 3项目编译报javax不存在引入的某个依赖还在用javax包把依赖升级到兼容jakarta的版本,代码里javax.*全部改成jakarta.*
npm install卡住不动依赖下载极慢或报网络错误先执行npm config set registry https://registry.npmmirror.com,再重新安装
前端打包后放Spring Boot刷新404刷新深层路由白屏加SPA转发Controller,把无扩展名路径forward到index.html
Spring Security 6的注解不生效加了@PreAuthorize但没拦截确认启动类上用了@EnableMethodSecurity,而不是老注解

这些问题看起来简单,但每一个都花了我不少时间搜索。印象最深的是javax和jakarta的问题:系统里引入了一个老版本的工具包,编译时疯狂报错,一开始以为是自己代码写错了,后来才发现是依赖兼容性。所以用Spring Boot 3做项目,依赖版本一定要尽量用最新的,别迷信老教程里的版本号。

7.2 运行期的四个排查思路

系统跑起来之后会有一类更难查的问题,接接口没反应、页面一直转圈、视频播不出来,这类问题的排查思路我总结了四个方向:

现象排查方向关键检查点
SSE接口一直不出内容后端的响应是否被缓冲Controller返回类型是否为text/event-stream,SseEmitter是否正常send并flush
file输入大视频上传超时Spring和Nginx两层限制spring.servlet.multipart.max-file-size和max-request-size,Nginx的client_max_body_size
m3u8视频播放白屏跨域和MIME类型MinIO或Nginx是否正确配置Access-Control-Allow-Origin,Content-Type是否为application/vnd.apple.mpegurl
AI回答的内容明显编造知识库检索是否真的命中查看检索日志,看召回片段是否相关;如果没召回,检查Embedding向量模型是否一致

最后一条是我最想强调的。AI回答内容编造,十次里有八次不是模型问题,而是知识库没检索到相关内容,模型只能"自由发挥"。上线前我把非遗知识库里的文本做了一轮清洗,把PDF转换带来的乱码、页眉页脚都处理掉,检索命中率才显著提升。数据质量是AI系统的生命线,这个理念在非遗数字化系统里体现得淋漓尽致。

7.3 个人对这类项目的一点体会

整个项目做完,我最深的体会是:这类系统的技术框架其实不复杂,真正决定成败的是"数据能不能喂好"。Spring Boot写CRUD、Vue写页面,网上教程一抓一大把,但能说清楚"非遗资料如何清洗、切分、向量化,如何设计提示词才能让模型给出可靠回答"的内容非常稀缺。如果你也准备做类似的项目,我建议先花三天时间把业务数据梳理清楚,哪怕先找一个具体的非遗项目做试点,把它的全流程资料走通,再扩展成整个系统。这样开发节奏会稳很多。

还有一个小建议:视频转码和文件存储这类基础设施,不要一开始就追求高大全的视频平台方案。先实现本地上传+FFmpeg转码+浏览器播放,跑通链路之后再考虑对象存储、CDN、多码率这些优化,循序渐进比一步到位可靠。这个系统后续如果要扩展,可以增加小程序端,后端接口和知识库完全可以复用,工作量主要集中在登录适配和视频播放器选型上。代码里多留一点扩展的余地,后面会感谢自己。

最后分享一个调SSE时的小技巧:后端开发时直接打开浏览器访问接口地址,如果能看到一行行data:内容滚动输出,说明流式链路是通的;如果等到最后才一次性输出,就该检查是不是哪里做了缓冲。这个简单的判断方法帮我快速定位过不少问题,比反复看前端截图高效得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询