
在地方文化馆、非遗保护中心、项目申报单位和高校研究团队的工作场景中“非物质文化遗产系统”并不是一个简单的信息展示网站。它要解决的是项目台账分散、申报材料流程长、传承人与项目档案割裂、视频音频资料缺乏统一管理等实际问题。使用 SpringBoot 做后端服务、Vue3 做管理端界面、AI 能力做资料整理和智能咨询是目前比较典型、也适合团队熟悉的全栈实践组合。本文按一条可复现的主线来走先设计业务模型再搭 SpringBoot 后端然后搭 Vue3 前端最后接入 AI 并完成接口联调。这套系统的核心价值在于把“非遗项目信息”变成“可持续维护的数据资产”。业务人员可以在系统里维护项目名称、类别、地区、传承人、图片、视频和申报进度管理员可以控制审批状态游客或工作人员可以通过 AI 助手快速查询“某个项目属于什么类别”“某个传承人掌握了哪些技艺”。整条链路并不复杂但涉及的工程问题不少比如表结构怎么设计、文件上传后存到哪里、大模型接口返回慢怎么处理、跨域配置怎么写。下面从业务设计开始逐步讲清楚。1. 非遗数字化系统要解决哪些问题1.1 从纸质档案到共享数据资产非遗保护工作一直存在信息不对称问题。很多地方仍然靠 Excel 维护项目清单材料分散在个人电脑里。同一个非遗项目在申报书、官方网站、传承人档案、视频素材中可能出现了多个不完全一样的名称和分类。系统化管理的第一个目标就是为项目、传承人、数字资源建立统一的登记入口和唯一标识。在实际项目中最小的数据模型至少要包含三张表非遗项目表、传承人表、数字资源表。项目表负责存储项目的基础信息传承人表负责记录掌握该项目的传承关系数字资源表用来存储图片、视频、音频和文档的地址。建议用project_id做关联字段这样可以根据项目查看其下所有素材和传承人。1.2 SSM 时代和 SpringBoot Vue3 时代有何不同较早的非遗管理系统多采用 JSP 加 SpringMVC页面渲染在后端完成前端负责简单的表单交互。这种模式在小规模系统里够用但遇到移动端适配、多端展示、高并发访问时会很吃力。SpringBoot 在后端解决了配置复杂和部署困难的问题。它把依赖管理、自动配置、内嵌容器整合到一起开发人员不需要再手动配置大量的 XML。Vue3 在前端负责数据绑定、组件复用和路由切换界面交互体验比 JSP 页面更流畅。前后端通过 REST API 通信后端只暴露数据接口前端只处理页面渲染职责边界清晰。1.3 AI 放在哪个位置更合理AI 不是非物质文化遗产系统的必备模块但可以解决很多重复性工作。常见的能力有四类智能问答让用户用自然语言查询非遗项目、传承人和申报政策。文本整理把口述史访谈、专家评审意见等长文本做摘要和结构化提取。标签推荐根据项目描述自动生成分类标签、关键词和内容简介。语音转写把传承人采访录音转成文字稿便于归档和检索。建议把 AI 能力封装成独立的AiService不要散落在各个业务接口里。这样即使后续更换大模型供应商也只需要修改一个实现类。下面会给出一个最小可用的大模型接口调用示例。2. 整体架构与模块边界怎么设计2.1 前后端分离和单体后端考虑到非遗系统的用户量通常不大初期不建议引入微服务架构。推荐采用前后端分离的单体后端前端用 Vue3 和 Vite 构建后端用 SpringBoot 打包成一个 JAR 包数据库使用 MySQL。这样的结构部署简单也方便团队协作。后端按业务能力拆模块常见模块清单如下模块主要功能关键实体项目管理非遗项目的增删改查、分类筛选InheritProject传承人管理传承人信息、项目关联、技能描述Inheritor数字资源管理图片、视频、音频、文档上传与检索DigitalResource申报管理申报流程、审批状态、意见记录ApplyRecordAI 服务智能问答、文本总结、标签建议AiChatLog系统用户登录认证、角色权限SysUser2.2 后端分层设计常见分层是 Controller、Service、Mapper 三层。Controller 只做参数接收和结果返回Service 处理业务逻辑Mapper 负责数据库访问。把 AI 调用放在独立的ai包下业务代码通过接口调用避免每次都在 Controller 里写大模型请求。目录结构可以参考src/main/java/com/example/heritage/ ├── controller/ │ ├── InheritProjectController.java │ ├── InheritorController.java │ └── AiChatController.java ├── service/ │ ├── InheritProjectService.java │ └── impl/InheritProjectServiceImpl.java ├── mapper/ │ ├── InheritProjectMapper.java │ └── InheritorMapper.java ├── entity/ │ ├── InheritProject.java │ └── Inheritor.java ├── common/ │ ├── Result.java │ └── PageResult.java └── ai/ ├── AiService.java └── AiServiceImpl.java这里把实体类单独放entity包common包放统一返回结果和分页对象方便多个 Controller 复用。2.3 前端页面与状态管理前端页面不需要一开始就设计得非常复杂。建议先做三个核心页面非遗项目管理页、传承人管理页、AI 咨询页。管理页使用表格展示数据配合搜索条件和分页。AI 咨询页采用常见的对话框样式用户输入问题前端调用后端 AI 接口后渲染回答。状态管理使用 Pinia。因为项目规模不大全局状态只需要保存用户登录信息和当前项目筛选条件。组件之间优先通过 props 和事件通信不要把所有数据都放进 Pinia避免状态来源混乱。3. 后端搭建 SpringBoot 非遗服务3.1 环境准备与项目初始化开发环境建议安装 JDK 17、Maven 3.9、MySQL 8、Node.js 18 以上版本。如果你在 IDE 中创建 SpringBoot 项目时经常超时可以手动创建一个普通 Maven 项目再把依赖写入pom.xml或者把初始化服务的地址改成可用镜像。这一步不依赖 IDE 自带的模板直接创建一个空目录在里面放一个最小pom.xmlparent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.2.5/version relativePath/ /parent properties java.version17/java.version /properties dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.7/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency /dependencies注意版本选择问题SpringBoot 3.x 需要使用 MyBatis-Plus 3.5.7 或更高版本低版本会出现类冲突。如果项目仍在使用 SpringBoot 2.x建议同步降低 MyBatis-Plus 版本。3.2 数据库模型设计非遗项目管理表是系统最核心的表字段设计尽量覆盖业务查询需求CREATE TABLE inherit_project ( id BIGINT PRIMARY KEY AUTO_INCREMENT, project_name VARCHAR(200) NOT NULL COMMENT 项目名称, project_code VARCHAR(50) COMMENT 项目编号, category VARCHAR(100) COMMENT 项目类别, region VARCHAR(100) COMMENT 所在地区, level VARCHAR(50) COMMENT 非遗级别, description TEXT COMMENT 项目简介, cover_image VARCHAR(500) COMMENT 封面图片地址, status TINYINT DEFAULT 1 COMMENT 状态1启用 0禁用, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT非遗项目表;传承人表需要与项目建立多对多关系。一个项目可能有多个传承人一个传承人也可能参与多个项目。如果业务不复杂可以先在传承人表中保存project_id指向单个项目如果后续出现一对多再增加中间表。初期用外键关系会拖慢查询建议用业务字段关联并建索引。3.3 数据初始化表不存在时自动建表很多团队在本地开发时会遇到“数据库里没有表”的问题。MyBatis-Plus 本身提供ddl能力有限比较稳的做法是使用 SpringBoot 的 SQL 初始化功能。把建表语句放在src/main/resources/schema.sql在配置文件中开启初始化spring: sql: init: mode: always schema-locations: classpath:schema.sql datasource: url: jdbc:mysql://localhost:3306/heritage_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456这种方式在应用启动时会自动执行schema.sql。如果表已经存在MySQL 会报错所以建表语句中要加CREATE TABLE IF NOT EXISTS。这个配置适合学习环境和测试环境。生产环境不建议每次启动都执行建表脚本最好由 DBA 审核后统一导入。3.4 实体类与 Mapper 接口用 MyBatis-Plus 可以省略大量 XML。实体类直接使用注解映射数据库字段Data TableName(inherit_project) public class InheritProject { TableId(type IdType.AUTO) private Long id; private String projectName; private String projectCode; private String category; private String region; private String level; private String description; private String coverImage; private Integer status; private LocalDateTime createTime; private LocalDateTime updateTime; }Mapper 接口只需要继承BaseMapper基础 CRUD 方法就已经具备Mapper public interface InheritProjectMapper extends BaseMapperInheritProject { }Service 层中要实现分页查询。这里用 MyBatis-Plus 的Page对象前端传入页码和每页条数Service public class InheritProjectServiceImpl implements InheritProjectService { Resource private InheritProjectMapper inheritProjectMapper; Override public PageResultInheritProject pageQuery(int page, int size, String keyword, String category) { PageInheritProject p new Page(page, size); LambdaQueryWrapperInheritProject wrapper new LambdaQueryWrapper(); wrapper.like(StringUtils.hasText(keyword), InheritProject::getProjectName, keyword) .eq(StringUtils.hasText(category), InheritProject::getCategory, category) .orderByDesc(InheritProject::getCreateTime); inheritProjectMapper.selectPage(p, wrapper); return new PageResult(p.getRecords(), p.getTotal()); } }可以重点解释一下LambdaQueryWrapper第一个参数的写法当条件为 false 时这个查询条件不会拼入 SQL所以前端不传关键词也能正常查询。3.5 统一返回结构和业务接口接口返回体必须稳定否则前端会写出大量判断逻辑。建议统一使用Result对象Data public class ResultT { private Integer code; private String message; private T data; public static T ResultT ok(T data) { ResultT r new Result(); r.setCode(0); r.setMessage(success); r.setData(data); return r; } public static T ResultT fail(String message) { ResultT r new Result(); r.setCode(500); r.setMessage(message); return r; } }Controller 中只做参数接收和调用 ServiceRestController RequestMapping(/api/project) public class InheritProjectController { Resource private InheritProjectService inheritProjectService; GetMapping(/page) public ResultPageResultInheritProject page( RequestParam(defaultValue 1) int page, RequestParam(defaultValue 10) int size, RequestParam(required false) String keyword, RequestParam(required false) String category) { return Result.ok(inheritProjectService.pageQuery(page, size, keyword, category)); } GetMapping(/{id}) public ResultInheritProject detail(PathVariable Long id) { return Result.ok(inheritProjectService.getById(id)); } }这里统一以零作为成功码避免前端同时判断 200 和 0 两套标识。真实项目中已经有很多教训不要使用过于随意的返回结构。3.6 AI 服务模块接通大模型接口AI 模块的设计目标是把“大模型接口调用”隔离在业务外部。以调用 OpenAI 兼容接口为例后端使用 Java 内置的HttpClient完成一次 POST 请求Service public class AiServiceImpl implements AiService { Value(${ai.api-url}) private String apiUrl; Value(${ai.api-key}) private String apiKey; Override public String chat(String userMessage) { String body { model: gpt-4o-mini, messages: [ {role: system, content: 你是一个非遗保护领域的助手。}, {role: user, content: %s} ], temperature: 0.3 } .formatted(userMessage); HttpClient client HttpClient.newHttpClient(); HttpRequest request HttpRequest.newBuilder() .uri(URI.create(apiUrl)) .header(Content-Type, application/json) .header(Authorization, Bearer apiKey) .POST(HttpRequest.BodyPublishers.ofString(body)) .build(); try { HttpResponseString response client.send(request, HttpResponse.BodyHandlers.ofString()); JsonNode root new ObjectMapper().readTree(response.body()); return root.path(choices).get(0).path(message).path(content).asText(); } catch (Exception e) { log.error(AI 接口调用失败, e); return AI 服务暂时不可用请稍后重试。; } } }AI 接口是外部网络请求存在超时风险。建议给HttpClient配置连接超时和读取超时并在前端做加载状态控制。更合理的做法是把大模型返回结果写入ai_chat_log表方便后续排查用户问题和大模型回答质量。4. 前端用 Vue3 搭建管理界面4.1 Vue3 项目初始化前端使用 Vite 创建项目模板。命令如下npm create vuelatest heritage-web cd heritage-web npm install npm install axios element-plus pinia vue-router npm run dev项目结构会生成出来之后需要手动补充四块内容路由配置、接口封装、页面组件、状态管理。由于依赖版本变化很快建议以npm create vuelatest实际生成的版本为准不要在网上复制一份过期的package.json。4.2 路由与 Layout管理后台页面需要一个公共布局左侧导航右侧内容区。路由配置如下import { createRouter, createWebHistory } from vue-router const router createRouter({ history: createWebHistory(), routes: [ { path: /, component: () import(/layout/MainLayout.vue), children: [ { path: , redirect: /project }, { path: project, name: ProjectList, component: () import(/views/project/ProjectList.vue) }, { path: inheritor, name: InheritorList, component: () import(/views/inheritor/InheritorList.vue) }, { path: ai, name: AiChat, component: () import(/views/ai/AiChat.vue) } ] } ] }) export default router请注意这里使用了createWebHistory开发时 Vite 会正常代理页面。生产环境如果部署在 Nginx 子路径下需要配置try_files重写到index.html否则刷新页面会 404。4.3 axios 封装推荐把 axios 统一封装在src/utils/request.js中拦截器负责携带 token 和统一处理错误import axios from axios import { ElMessage } from element-plus const request axios.create({ baseURL: /api, timeout: 30000 }) request.interceptors.response.use( response { const res response.data if (res.code ! 0) { ElMessage.error(res.message || 请求失败) return Promise.reject(new Error(res.message)) } return res }, error { ElMessage.error(error.message || 网络异常) return Promise.reject(error) } ) export default request封装完成后业务页面不需要关心响应状态判断直接拿到data即可。4.4 非遗列表页实现列表页使用 Element Plus 的组件就能实现。核心逻辑在script setup中完成script setup import { onMounted, ref } from vue import request from /utils/request const loading ref(false) const list ref([]) const total ref(0) const query ref({ page: 1, size: 10, keyword: , category: }) async function loadData() { loading.value true try { const res await request.get(/project/page, { params: query.value }) list.value res.data.records total.value res.data.total } finally { loading.value false } } function handleSearch() { query.value.page 1 loadData() } onMounted(loadData) /script template el-card el-form :inlinetrue el-form-item label关键词 el-input v-modelquery.keyword placeholder项目名称 clearable / /el-form-item el-form-item el-button typeprimary clickhandleSearch查询/el-button /el-form-item /el-form el-table :datalist v-loadingloading border el-table-column propprojectName label项目名称 min-width180 / el-table-column propcategory label类别 width140 / el-table-column propregion label地区 width140 / el-table-column proplevel label级别 width100 / el-table-column label操作 width180 template #defaultscope el-button sizesmall clickshowDetail(scope.row)详情/el-button /template /el-table-column /el-table el-pagination v-model:current-pagequery.page v-model:page-sizequery.size :totaltotal layouttotal, prev, pager, next current-changeloadData / /el-card /template列表页完成后要自测三个分支首次加载、搜索后加载、翻页后加载。尤其是搜索后总条数变化页码要重置为 1否则会出现“搜索后还在第 10 页”的体验问题。4.5 AI 对话面板对接AI 页面使用简单的输入框和消息列表script setup import { ref } from vue import request from /utils/request const messages ref([ { role: assistant, content: 你好我可以帮你查询非遗项目和传承人信息。 } ]) const input ref() const sending ref(false) async function send() { const text input.value.trim() if (!text || sending.value) return messages.value.push({ role: user, content: text }) input.value sending.value true try { const res await request.post(/ai/chat, { message: text }) messages.value.push({ role: assistant, content: res.data }) } finally { sending.value false } } /script这里前端只负责展示消息不拼接提示词不处理大模型参数。所有提示词和模型参数都保存在后端避免前端逻辑泄露。5. 联调验证从接口到页面5.1 后端自测查询后端启动后先用 curl 验证接口是否正常curl http://localhost:8080/api/project/page?page1size10预期返回 JSON 结构{ code: 0, message: success, data: { records: [], total: 0 } }此时数据库如果没有任何数据返回空数组是正常的。业务人员录入数据后再次查询就能看到项目列表。如果返回 404要检查 Controller 的请求路径和前端请求路径是否一致如果返回 500则重点看后端日志定位是数据库连接失败、SQL 异常还是 AI 接口异常。5.2 前端联调技巧前端联调时最常见的是跨域问题。推荐在 Vite 配置中做开发代理export default defineConfig({ server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })采用代理后前端请求仍然以/api开头不需要写完整的后端地址。上生产环境后由 Nginx 统一配置反向代理。联调时可以先打开浏览器控制台观察 Network 面板的请求路径和状态码。如果状态码是 200 但页面数据为空优先检查返回 JSON 中code是否为 0可能是全局拦截器把请求拦截掉了。5.3 学习环境与生产环境部署差异学习环境的目标是快速跑通数据库密码、API Key、文件存储路径都可以写在配置文件中。生产环境不能这样做至少要做好下面几点项目学习环境生产环境端口默认 8080建议使用 80 或 443前面再挂 Nginx数据库本地 MySQL独立数据库服务配置备份策略配置明文写入 yml使用环境变量或配置中心文件存储本地目录对象存储或专用文件服务AI Key测试接口密钥由后端环境变量注入禁止出现在仓库日志控制台输出文件日志按天滚动日志保留周期明确6. 常见问题排查按现象走链路6.1 创建 SpringBoot 项目超时现象在 IDE 中新建 SpringBoot 项目时长时间卡在下载依赖或初始化模板。可能原因本地网络访问初始服务不通或 Maven 中央仓库下载缓慢。检查方式先确认是否能打开 Maven 仓库地址再检查 IDE 中 Maven settings 配置。处理建议使用已有的内部仓库地址或在 IDE 的 Maven 配置中增加阿里云镜像。也可以先手动创建普通 Maven 项目在pom.xml中加入 SpringBoot 父依赖绕过初始化模板。6.2 SpringBoot 版本太高导致启动失败现象启动时报NoSuchMethodError、ClassNotFoundException或Property sqlSessionFactory or sqlSessionTemplate not found。可能原因SpringBoot 3.x 与 MyBatis-Plus、Velocity 模板等老王依赖版本不兼容。检查方式查看启动日志第一条异常确认是哪个类加载失败再检查该依赖与 SpringBoot 的兼容版本。处理建议不要直接使用自动生成项目默认的最高版本先确认 MyBatis-Plus 是否有对应的 starter。以 MyBatis-Plus 为例SpringBoot 3.x 环境必须使用mybatis-plus-boot-starter3.5.7 以上版本。6.3 表不存在或查询报错现象应用启动成功但调用接口时报Table heritage_db.inherit_project doesnt exist。可能原因数据库中没有创建表schema.sql没有被执行或配置中关闭了 SQL 初始化。检查方式进入数据库执行SHOW TABLES;查看是否存在对应表再检查应用日志中是否看到执行建表语句。处理建议学习环境可以开启spring.sql.init.modealways并确保建表语句使用CREATE TABLE IF NOT EXISTS。生产环境提前在测试库执行一遍建表脚本确认字段类型无误后再导入正式库。6.4 Vue3 页面在 Edge 浏览器中交互异常现象页面加载正常但在浏览器窗口最小化恢复后按钮点击无反应或页面渲染异常。可能原因浏览器插件冲突、前端页面监听事件未清理、Element Plus 组件在特定分辨率下计算高度异常。检查方式按 F12 打开控制台先看是否有报错信息关闭浏览器扩展后重现一次再检查是否有未销毁的resize监听器。处理建议不要在组件卸载后继续更新状态在onUnmounted中移除全局监听器如果与浏览器渲染有关可以建议用户关闭硬件加速后重试。这类问题通常不是系统核心逻辑问题但会影响体验排查时要先区分“所有浏览器都出现”还是“单个浏览器出现”。7. 最佳实践与后续扩展7.1 非遗数据的标准化和审核非遗系统上线后数据质量决定系统价值。录入项目时建议把类别、地区、级别做成字典表由管理员维护避免同一个项目出现“传统舞蹈”和“民间舞蹈”两种写法。上传图片和视频时后端要校验文件类型、大小和命名规则。建议增加“草稿”和“发布”两种状态。业务人员录入初稿后由审核人员确认后发布。这样既保证了数据安全也符合非遗保护工作的审批习惯。7.2 AI 能力的扩展路线AI 模块完成后可以继续向两个方向扩展。一个是 RAG 检索增强把非遗项目的档案文本向量化后存入向量数据库用户提问时先从档案中检索相关内容再交给大模型回答准确率会明显提升。另一个是语音转写利用语音识别服务把传承人口述史转换成文本再接入 AI 做摘要和主题分类。如果希望完全私有化部署可以使用本地模型替代在线接口。本地模型对服务器要求较高适合数据保密要求严格的场景。普通业务可以先使用在线接口快速验证效果。7.3 给新手的练习路线自查清单阶段推荐练习通过标准第一阶段完成 SpringBoot 的 CRUD 接口能用 POST 增加项目用 GET 查询列表第二阶段完成 Vue3 表格页面和分页页面能展示后端返回的数据并支持翻页第三阶段完成文件上传和静态资源访问图片能上传并回显到页面第四阶段接入 AI 对话接口页面能发送问题并展示回答第五阶段部署上线使用 Nginx 部署前端后端 JAR 包启动刷新不 404非遗系统是一个适合练手又具备真实业务价值的项目。它在技术难度上比普通后台管理系统稍高一点因为涉及文件、流程、AI 接口和权限但又不至于复杂到让人难以上手。完成本文这条链路之后下一步最值得做的事情是结合你所在地区的真实非遗项目清单把数据和业务规则补充进去让系统真正变成一个可使用的业务工具。