先说结论非遗文化传承网站这种项目想既撑得住答辩、又能真的有人愿意点开看技术选型比“花哨功能”重要得多。这次我直接定了 Java Vue 这套组合前端用 Vue 做页面交互后端用 Java 写接口配 MySQL 存数据最后把源码、数据库脚本和文档一起整整齐齐交付。整体做下来从数据库设计到前后端联调踩了不少坑这篇就把完整的实现思路、核心表结构、接口设计、页面落法和排查经验一次说清楚给正在做类似网站系统或课程设计的同学一份能直接抄作业的参考。这个系统的核心瞄准的是“非遗文化传承”这个相对垂直的场景既要展示非遗项目比如传统技艺、民俗、曲艺又要方便管理员维护内容还得让访客有基本的搜索、分类浏览和详情查看体验。适合谁来参考一是 Java Vue 方向的学生做课设或毕设二是刚入门前后端分离开发、想找个完整项目练手的人。下面从整体设计开始拆。1. 项目定位与整体技术方案1.1 为什么是 Java Vue而不是其他组合很多人一上来就纠结技术栈其实非遗文化传承网站这种管理系统型项目核心诉求是“开发效率高”“结构清晰”“资料好找”Java Vue 正好全占。后端选 Java 生态里的 Spring Boot最大的理由是这套东西在业界的资料密度太高了。不管是用户登录、权限控制还是文件上传随便一搜就是一堆成熟方案。而且 Spring Boot 的自动配置能把原来 Spring MVC 那套繁琐的 XML 配置大幅简化一个工程起来之后按 Controller→Service→Mapper 分层写下去结构非常干净。对于后续要维护、要扩展比如加一个非遗专题申报模块的人来说这种分层比“所有逻辑堆在一个 Servlet 里”要舒服太多。前端选 Vue 而不是 JSP 或传统 jQuery 页面核心原因是前后端分离之后接口和页面可以并行开发不用互相等。Vue 的组件化特性特别适合“非遗文化传承网站”这种内容展示型项目首页、列表页、详情页、后台管理页可以拆成一个个独立组件哪个页面要加个视频播放器或者图片轮播不会牵扯到其他页面。再加上 Vue 对新手很友好模板语法直白响应式数据模型理解起来也比 React 的 Hooks 心智负担低。提示如果团队里有人只会 Java前端经验几乎为零Vue 的学习曲线也是前端三框架里最平滑的。我见过不少项目用 Vue Element UI 做后台两天就能上手。1.2 功能模块划分与应用场景这个系统不是那种“功能堆得越多越好”的项目我把它的功能收敛成三个端访客端也就是门户展示首页非遗项目推荐、分类入口、最新资讯。非遗项目列表按分类筛选、按关键词搜索、分页展示。项目详情文字介绍、图片、视频以及传承人、所在地等信息。资讯公告展示非遗相关的新闻、活动通知。管理员端后台管理登录认证管理员账号登录非登录状态不能访问管理接口。项目管理对非遗项目的增删改查支持上传图片和视频。分类管理维护非遗分类传统技艺、传统舞蹈、传统美术等。资讯管理发布和编辑资讯内容。首页推荐位管理控制哪些项目展示在首页。公共能力统一返回格式、全局异常处理、跨域配置等。从应用场景来看这个系统可以服务于地方文化馆、非遗保护中心或者学校社团的线上展示。访客不需要注册就能浏览内容降低了访问门槛管理员通过后台维护内容不用改代码就能更新页面信息。这套设计也方便后续扩展成多角色系统例如给传承人增加投稿入口。2. 数据库设计与非遗数据建模2.1 核心表结构拆解数据库是这类网站系统的地基表设计得好不好直接决定后面写接口和页面是“抄近道”还是“绕远路”。我把核心表分成四张管理员表、非遗分类表、非遗项目表、资讯表另外加一张项目图片表存多图。非遗项目表是整个系统里字段最多、也最需要花心思的CREATE TABLE heritage_item ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键, name varchar(200) NOT NULL COMMENT 非遗项目名称, category_id bigint(20) DEFAULT NULL COMMENT 所属分类ID, level varchar(50) DEFAULT NULL COMMENT 非遗级别国家级/省级/市级/县级, area varchar(100) DEFAULT NULL COMMENT 所属地区, inheritor varchar(100) DEFAULT NULL COMMENT 代表性传承人, cover_image varchar(500) DEFAULT NULL COMMENT 封面图URL, video_url varchar(500) DEFAULT NULL COMMENT 视频URL, content text COMMENT 项目详细介绍, is_recommend tinyint(1) DEFAULT 0 COMMENT 是否首页推荐, view_count int(11) DEFAULT 0 COMMENT 浏览量, create_time datetime DEFAULT NULL COMMENT 创建时间, update_time datetime DEFAULT NULL COMMENT 更新时间, PRIMARY KEY (id) ) ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETutf8mb4;这里有几个字段需要专门说明。video_url是我建议一开始就要预留的。非遗项目很多都带有纪录片、教学视频如果没有这个字段后面想在前端展示视频就要改表、改接口、改页面非常被动。同理cover_image单独拎出来而不是混在富文本内容里是为了列表页和首页推荐位能快速拉取缩略图不用从长文本里解析图片地址。is_recommend这个字段看着简单实际上解决了一个很现实的运营问题管理员想把某个非遗项目放到首页推荐位如果每次都要改代码那就不是“内容管理系统”了。有了这个布尔字段首页接口里加一句WHERE is_recommend 1就行。分类表设计成父子结构还是平铺结构我建议平铺就行因为非遗分类本身比较固定传统技艺、传统美术、传统舞蹈、传统戏剧、民俗等。CREATE TABLE category ( id bigint(20) NOT NULL AUTO_INCREMENT, name varchar(100) NOT NULL COMMENT 分类名称, sort int(11) DEFAULT 0 COMMENT 排序号, create_time datetime DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;资讯表和管理员表相对简单但管理员表有两点要注意一是密码不要存明文二是要有status字段控制账号是否可用。密码加密我用的 BCrypt这是 Spring Security 里默认支持的加密方式每次加密结果不一样但比对方法可以正确校验安全性比 MD5 强一个量级。2.2 非遗数据特有的字段处理非遗项目和普通新闻资讯不同它有几个“信息维度”是必须体现的级别、地区、传承人、分类。这些字段如果只是零散地塞进一个“简介”文本框里后期做筛选和统计会很痛苦。我的处理方式是level字段用字符串存固定枚举值国家级/省级/市级/县级方便下拉选择也方便按级别筛选。area字段单独存储支持按地区维度展示。inheritor字段单独存储方便用户直接看到传承人信息也为以后做“传承人专题”预留数据基础。content字段我选择用text类型而不是longtext因为非遗项目的介绍文案一般几千字以内text足够。如果以后要支持长篇论文或申报书再改longtext也不迟。另一个容易忽略的点是字符集。建表的时候我统一用了utf8mb4而不是utf8。区别在于utf8mb4能存 emoji 和生僻字非遗项目名称里经常会碰到一些生僻汉字或特殊符号用utf8在某些版本 MySQL 下会报“Incorrect string value”错误。这个坑我踩过当时录入某个非遗项目名称带了生僻字接口直接 500查了半天才发现是字符集问题。注意如果你用的 MySQL 是 5.5 以下默认字符集可能不支持utf8mb4。建议装 5.7 及以上版本建库时指定DEFAULT CHARACTER SET utf8mb4。3. 后端实现与接口设计3.1 技术栈与工程结构后端我是按标准的 Spring Boot 三层架构来的Controller接收请求→ Service业务逻辑→ Mapper数据库操作。工程结构大致如下src/main/java/com/example/heritage/ ├── controller/ # 接口层 ├── service/ # 业务逻辑层 ├── mapper/ # MyBatis 数据访问层 ├── entity/ # 实体类 ├── common/ # 通用返回结果、异常处理 └── config/ # 跨域配置、拦截器配置视图层我用的是 Vue但在交付的源码里后端并没有整合任何模板引擎因为前后端分离之后后端只需要返回 JSON 数据就行前端拿到 JSON 再渲染页面。依赖层面核心引入这几个spring-boot-starter-web提供 Web 能力。mybatis-spring-boot-starter数据持久层框架。mysql-connector-javaMySQL 驱动。lombok简化实体类代码。spring-boot-starter-validation参数校验。spring-boot-starter-security或jwt相关依赖用于管理员登录鉴权。关于登录鉴权我用的是 JWT 方案。流程是管理员调用登录接口成功后后端返回一个 token前端把 token 存在 localStorage 里之后每次请求在请求头里带Authorization: Bearer token。后端写一个拦截器只拦截/api/admin/**路径对登录接口和访客端接口放行。为什么用 JWT 而不是 Session因为前后端分离场景下前端可能部署在另一个端口比如 8080 部署后端5173 跑前端Session 的 Cookie 跨域携带比较麻烦而 JWT 是纯字符串存在前端请求头里就行跨域配置相对简单。虽然 JWT 有 token 无法主动失效的问题但对课设和中小型系统来说完全够用。3.2 核心接口与统一返回格式接口设计我遵循一个原则按资源命名接口动词交给 HTTP 方法。核心接口清单如下模块方法路径说明访客端GET/api/heritage/list分页获取非遗项目列表访客端GET/api/heritage/detail/{id}获取项目详情访客端GET/api/heritage/search按关键词搜索访客端GET/api/category/list获取分类列表管理员POST/api/admin/login管理员登录管理员POST/api/admin/heritage/save新增/修改非遗项目管理员DELETE/api/admin/heritage/{id}删除非遗项目管理员GET/api/admin/heritage/page后台分页查询所有接口统一返回一个 Result 对象Data public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(success); result.setData(data); return result; } public static T ResultT error(String message) { ResultT result new Result(); result.setCode(500); result.setMessage(message); return result; } }统一返回格式最大的好处是前端处理逻辑变得非常简单不管哪个接口先判断code200 就走正常渲染非 200 就弹错误提示。如果不做统一封装有的接口返回{data: []}有的返回{rows: []}前端到处写兼容逻辑联调效率会非常低。列表接口我重点说一下。非遗项目列表页需要展示封面图、名称、分类、等级、地区、浏览量所以我用 MyBatis 写了一条关联查询select idselectHeritagePage resultTypecom.example.heritage.entity.HeritageItem SELECT h.id, h.name, h.level, h.area, h.inheritor, h.cover_image, h.view_count, c.name AS categoryName FROM heritage_item h LEFT JOIN category c ON h.category_id c.id where if testkeyword ! null and keyword ! AND h.name LIKE CONCAT(%, #{keyword}, %) /if if testcategoryId ! null AND h.category_id #{categoryId} /if /where ORDER BY h.create_time DESC LIMIT #{offset}, #{pageSize} /select分页我没有引入 PageHelper 插件直接手动算offset因为项目查询量不大手动分页足够还能少一个依赖减少别人导入源码时的报错概率。这个查询看起来简单但有几个细节值得说LEFT JOIN而不是INNER JOIN是为了防止分类被误删后项目查询不出来LIKE CONCAT(%, #{keyword}, %)而不是直接在 Java 里拼%是为了防止 SQL 注入分页字段用#{offset}, #{pageSize}传参同样避免拼接 SQL。4. 前端页面与交互实现4.1 页面骨架与路由组织前端我用 Vue 3 Vite 搭建配上 Vue Router 做页面路由、Pinia 或简单的 localStorage 做登录状态管理。整体路由设计const routes [ { path: /, component: Home, meta: { title: 首页 } }, { path: /heritage, component: HeritageList, meta: { title: 非遗项目 } }, { path: /heritage/:id, component: HeritageDetail, meta: { title: 项目详情 } }, { path: /news, component: NewsList, meta: { title: 资讯动态 } }, { path: /admin/login, component: AdminLogin }, { path: /admin/heritage, component: AdminHeritage }, { path: /admin/category, component: AdminCategory }, { path: /admin/news, component: AdminNews } ]组件划分上我尽量把公共部分抽出来。顶部导航栏单独做一个NavBar.vue底部做一个FooterBar.vue项目列表的每一行抽成HeritageCard.vue。这样首页和列表页可以复用同一套卡片组件后台的表格页也有单独的AdminLayout.vue统一管理侧边栏和顶栏。Vue 的组件化在这里体现得非常明显非遗项目列表页的卡片首页推荐位也可以直接用只是传入的 props 不同。开发阶段我一天时间就把用户端四个页面搭完了很大程度归功于组件复用。4.2 列表、搜索和详情页的落地细节列表页我做了三件事分类筛选、关键词搜索、分页加载。由于是前后端分离搜索和筛选都是通过改变 URL query 参数触发的async function loadList() { const params { pageNum: currentPage.value, pageSize: 12, keyword: keyword.value || , categoryId: activeCategory.value || } const res await axios.get(/api/heritage/list, { params }) list.value res.data.data.records total.value res.data.data.total }这里有个小坑如果用户搜索完“剪纸”然后切到“传统美术”分类页面应该带着搜索词重新查询。我通过watch监听route.query的变化来触发loadList()这样不管是点分页、点分类还是输入搜索词都会走同一个方法逻辑统一不会出现“搜索完点分类但搜索词被清空”的诡异问题。详情页是内容型页面的重点布局我做成上下结构标题和基础信息区名称、级别、地区、传承人、浏览量。媒体展示区封面大图 视频播放器。正文内容区富文本渲染。视频播放这里如果视频文件是 mp4 且编码兼容直接用 HTML 的video标签就能播。如果遇到一些浏览器不支持的视频格式可以借助 vue-video-player 这类工具库。当时查资料时看到很多人问“Vue 播放 m3u8”那是直播或 HLS 流媒体场景这种非遗展示网站用不到普通 mp4 就够了。富文本内容我用v-html渲染。这里必须加一句提醒前后端分离项目里用v-html渲染富文本存在 XSS 风险。因为内容只有管理员能编辑风险可控但如果系统要做用户投稿功能建议后端在存储时做 HTML 白名单过滤或者前端用专门的富文本渲染组件。搜索功能我前端只做了输入框和搜索按钮真正干活的是后端LIKE查询。有人可能会想用 Elasticsearch但非遗项目的数据量撑死几千条上搜索中间件反而增加部署负担。一个LIKE查询加个索引响应时间在毫秒级完全够用。技术选型要匹配业务规模这是我一直强调的原则。5. 联调、部署与常见问题5.1 开发环境联调技巧前后端分离开发最容易出问题的就是跨域也就是“后端接口通了但前端页面访问不了”。我用 Vite 开发服务器的时候通过server.proxy把/api路径转发到后端的 8080 端口这样前端页面请求/api/heritage/list时实际由 Vite 帮你转发到http://localhost:8080/api/heritage/list浏览器看到的请求地址是同源的不触发跨域。同时后端也加了一层全局跨域配置双保险。开发阶段用 Vite 代理、后端允许跨域生产部署时前端打包成静态文件、后端单独跑两者互不干扰这套配合下来最省心。Configuration public class CorsConfig { Bean public WebMvcConfigurer corsConfigurer() { return new WebMvcConfigurer() { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }; } }注意allowedOriginPatterns(*)配合allowCredentials(true)是现在 Spring Boot 里比较推荐的写法。如果只用allowedOrigins(*)加allowCredentials(true)低版本 Spring Boot 会直接报错高版本也建议用 Patterns 写法。联调时我习惯先在浏览器地址栏直接访问一个 GET 接口确认后端通再用前端页面发起请求看 Network 面板里请求和响应是否符合预期。如果接口报 401先看请求头有没有带 token如果报 404先看路径拼写和 Controller 的RequestMapping是否一致。这些基础排查做熟练了联调效率会非常高。5.2 高频报错与排查表我整理了一份这个项目里最常遇到的问题排查表给第一次做这类系统的同学参考现象可能原因处理方法启动报错Failed to configure a DataSource数据库没建或连接配置不对检查application.yml里的 url、用户名、密码确认数据库已建好接口返回中文乱码数据库字符集不是 utf8mb4重建库或修改表字符集连接串加characterEncodingutf8前端请求/api接口报 404Vite 代理没生效检查vite.config.js里的server.proxy配置重启前端请求带 token 还是 401拦截器路径配置不对确认拦截器只拦截/api/admin/**放行登录和访客接口图片上传成功但页面不显示静态资源映射没配后端配置/upload/**映射到本地磁盘目录列表分页总是少一条分页参数从 0 开始还是从 1 开始不一致统一约定前端传 pageNum 从 1 开始后端计算 offset部署后刷新页面 404前端路由是 history 模式Nginx 没有 try_filesNginx 配置location / { try_files $uri $uri/ /index.html; }图片上传这块要单独说很多新手卡在这里。我的方案是后端提供一个/api/upload接口接收 MultipartFile 文件保存到本地磁盘指定目录然后把访问 URL 返回给前端。配置上需要加一个虚拟路径映射Configuration public class WebConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceLocations(file: uploadPath /); } }上传路径不要写死在代码里放到application.yml配置里方便迁移部署。图片名我用UUID 原始后缀重新生成避免重名覆盖也避免中文文件名在 URL 里出现编码问题。部署环境我这次没上 Docker直接打包成 jar 跑前端 build 后放在 Nginx 里。如果你的服务器内存有限Spring Boot 启动参数可以加-Xms256m -Xmx512m避免内存不足被系统杀掉。数据库脚本我单独放在sql目录里新建数据库后直接导入即可。6. 避坑心得与扩展思路最后按老规矩分享几个我实际开发中总结的小细节不一定写在代码注释里但对项目质量影响很大。第一非遗数据录入时要规范化。我接手这个项目时数据库里有一个字段同时存了“序号01”这样的内容导致所有按序号排序的功能都跟着异常。视频、图片、正文里的“非遗项目名称”保持绝对一致不要出现“剪纸民间剪纸”和“民间剪纸”两种写法否则分类聚合、搜索匹配都会出问题。做后台管理的同学一定要在录入阶段就定好规范。第二接口不要只写增删改查至少要加一个“浏览量 1”的逻辑。非遗文化传承网站的核心是“让更多人看到”没有浏览量统计运营者根本不知道哪些项目受欢迎。这个功能实现很简单详情接口里UPDATE heritage_item SET view_count view_count 1 WHERE id #{id}但带来的数据价值很大首页推荐位可以按浏览量排序就能把优质内容顶上去。第三这个系统后续可以扩展的方向不少。比如增加一个“非遗活动报名”功能用户在详情页看到线下体验活动后提交报名信息或者增加“传承人风采”模块把传承人的故事单独做成一个栏目再往上走可以加一个简单的数据统计页面用图表展示不同类别、不同地区的非遗项目分布。这些方向说明初始架构留好了扩展位加新表、新接口、新页面不会动到原有核心逻辑。我个人在实际操作中的体会是非遗文化传承网站这种项目技术上并没有太难的坎真正的功夫在于把“内容”和“结构”理顺。数据模型能把非遗项目的级别、分类、地区、传承人、多媒体资源组织清楚这个系统就已经成功了一半。剩下的一半是前后端接口约定统一、异常处理完善、部署流程顺畅这些细节堆起来才是一个能拿得出手、能真正交付使用的网站系统。希望这篇拆分能帮你少走点弯路也欢迎大家把自己做这类项目时踩到的坑和经验丢到评论区里来聊。