1. 项目概述与技术定位
甘肃非物质文化遗产网站系统,用大白话说,就是把一个地方的非遗项目——比如兰州刻葫芦、庆阳香包、临夏砖雕这类东西——做成一整套可在浏览器里访问和管理的数字化平台。前台给普通访客观看非遗项目、传承人故事、活动资讯;后台给管理员维护内容、管理用户、处理留言。整个项目采用前后端分离架构,后端用SpringBoot2提供接口,前端用Vue3构建页面,数据库交给MySQL8.0。
之所以把技术栈选在这套组合上,是经过实际场景权衡的结果。SpringBoot2在Java Web生态里属于“稳如老狗”的存在,起步快、约定大于配置、社区资料海量,遇到问题搜一下基本都能解决;Vue3配合Vite带来更快的开发体验,组合式API让逻辑复用变得顺手;MyBatis-Plus则把单表CRUD的工作量砍掉了一大半,日常增删改查不用再手写那些千篇一律的XML映射;MySQL8.0在性能、窗口函数、JSON支持上都比5.7强出一截。
这个项目的典型应用场景有三个。给学生做毕业设计,它覆盖面广——后端、前端、数据库、文档全都有,且技术栈主流,答辩时有得讲;给刚入行的开发者做练手项目,它结构清晰,模块边界明确,能在完整业务链路里理解前后端交互、权限控制和数据建模;给非遗保护相关的机构做原型参考,它的内容管理模型和展示维度也基本够用,往真实业务迁移只需要增加特定字段和审核流程。
我自己在拆这套源码时最大的感受是,它没有搞那些花里胡哨的“过度设计”,每一个模块都在老老实实解决问题,代码里也没有为了炫技引入一堆不必要依赖。这对于学习和二次开发来说,其实是比“高级”更重要的素质。下面我把这套系统的架构思路、数据建模、核心功能、部署实操、避坑经验一条条拆开讲。
2. 整体设计思路与关键决策拆解
2.1 前后端分离的取舍逻辑
这里有个关键决策点:为什么不沿用传统的SpringBoot+Thymeleaf服务端渲染,而是偏要拆成前后端两个工程?我实测下来的体会是,非遗类网站的内容形态天然适合前后端分离。它包含图片轮播、视频展示、分类筛选、动态列表,这些东西用服务端模板渲染不是不能做,但每次切换路由都刷新整个页面,交互体验明显差一截;而且模板引擎在前端的灵活表达上很受限。
选定Vue3是有现实考量的。一方面Vue3的Composition API让业务代码的组织方式更清晰——比如首页需要同时加载“非遗项目推荐”“最新资讯”“热门传承人”三块数据,按传统Options API写会分散在不同属性里,而组合式API可以把这三块逻辑拆成三个useXXX函数,各管各的,互不干扰。另一方面Vue3的生态已经足够成熟,周边库适配稳定,配Element Plus做管理后台的UI,效率和颜值都过得去。
后端坚持SpringBoot2而非直接上SpringBoot3,原因在于稳定性和兼容性。MyBatis-Plus、一些老的Spring Security扩展、各种第三方starter对SpringBoot3的适配虽然已经推进,但在实际生产里SpringBoot2.7仍然是兼容性最省心的版本。对于教学和毕设场景,不需要Java17的新特性,JDK8加SpringBoot2的组合,环境搭建没有任何门槛。技术选型的本质是匹配需求,不是追求最新。
2.2 单表CRUD交给MyBatis-Plus,复杂查询自己来
MyBatis-Plus在2023年之后社区热度有所下降,有人开始唱衰,但放到这个项目的体量里,它依然是最顺手的选择。非遗网站的数据表大多是“单表简单查询加少量关联”,这类场景正是MyBatis-Plus的舒适区。它提供的LambdaQueryWrapper让查询条件的构建带上类型检查,比如查“项目类别等于传统美术”时直接写eq(NonHeritage::getCategory, "传统美术"),字段名写错了编译期就能发现,而不是跑起来才报错。
在复杂查询上,这个项目没有硬扛着用MyBatis-Plus的连表能力,而是老老实实写XML里的自定义SQL。比如“非遗项目详情页需要同时返回传承人、相关视频、所属地区”这种多表联合查询,手写SQL反而更直观、更好控制。这种“简单CRUD交给封装的工具,复杂SQL自己动手”的分工,是利用率最高的组合方式。
2.3 权限模型:一间屋子两把钥匙
非遗网站系统的用户侧和管理侧对权限的要求差异很大。前台普通用户只需要浏览、搜索、留言;后台管理员需要维护内容、审核留言、管理用户。系统采用了两张用户表的设计——前台用户表和管理员表分开,登录认证也各自独立。没有引入Spring Security那种重量级的权限框架,而是用JWT加拦截器实现了一套轻量级的登录认证方案。
这个选择的逻辑很简单:系统的角色少、权限维度单一,RBAC模型在这个场景下属于杀鸡用牛刀。JWT的方案让前后端分离下的用户状态管理变得无状态化,服务端不用保存Session,前端拿到token后存进本地,每次请求带上即可。登录校验的粒度也足够——管理员端有专门的拦截器,用户端有自己的校验逻辑,两个体系互不干扰,代码好理解,答辩也好讲。
3. 非遗数据的建模思路与表结构设计
3.1 非结构化内容驱动的数据模型
非遗项目的数据和一般企业管理系统差别很大。一条“临夏砖雕”的记录,除了基础信息——名称、类别、级别、所在地区、简介——还需要关联多张图片、一段或多段视频、对应的传承人、相关资讯,甚至可能涉及文创产品的展示。如果只设计一张大宽表,字段会膨胀到没法维护,冗余也会非常严重。这套系统采用了“主表加关联子表”的拆分策略。
项目主表记录非遗项目自身的基本属性,图片表单独存放图片路径并绑定所属项目编号,视频表同理。需要强调的是,图片和视频存储的是URL路径而不是二进制文件本身,文件上传后落盘在服务器指定目录,数据库只保存访问路径。这个设计的好处在于——数据库体积可控、备份快,静态资源还能直接交给Nginx处理,不占用后端应用带宽。
3.2 核心表结构清单
搭建这套系统一共涉及核心表大概十来张,其中最有代表性的是这几张:
| 表名 | 作用 | 关键字段 |
|---|---|---|
heritage_item | 非遗项目主表 | id、name、category、level、region、introduction、cover_image |
heritage_image | 项目图片表 | id、heritage_id、image_url、sort_order |
heritage_video | 项目视频表 | id、heritage_id、video_url、title |
inheritor | 传承人表 | id、name、heritage_id、简介、portrait_url |
news_article | 资讯文章表 | id、title、content、publish_time、cover |
message_board | 留言板表 | id、user_id、content、reply、create_time |
admin_user | 管理员表 | id、username、password、nickname |
member_user | 前台用户表 | id、username、password、avatar |
这里有个容易被忽略的设计细节:heritage_image表中的sort_order字段。非遗项目往往需要多图展示,而首页展示时经常只需要“第一张图”来做缩略图或者轮播。有了排序字段,查询时按sort_order升序取第一条,就能稳定拿到主图,避免多个图片在页面上的展示顺序混乱。这种字段在初期设计时多花30秒,后期能省一大堆联调时间。
3.3 为什么不直接用JSON存图片列表
有读者可能会问:MySQL8.0支持JSON字段,直接在一个字段里存图片URL数组不行吗?这确实是新时代的另一种方案,但在这个项目里不采用,原因是JSON字段的查询和统计能力很弱。比如管理员要统计“某个非遗项目下有几张图片”,JSON的方案需要把整条记录查出来再在应用层数,而关联表直接SELECT COUNT(*)就出结果;再比如未来要给图片增加“是否封面”的标记,或者图片要单独做水印处理,关联表加字段即可完成,JSON字段要改就得解析重写整个结构。数据建模的规则里有一条很简单——需要参与查询统计的数据,就别塞进JSON里当摆设。
4. 核心功能模块与页面交互实录
4.1 前台门户的四大模块
前台页面的整体布局严格围绕“展示非遗文化”这一目标设计。顶部是导航栏,包含首页、非遗项目、传承人、资讯动态、关于我们;首页往下依次是Banner轮播区、精选推荐项目、热门传承人、最新资讯。这套结构在信息架构上遵循了文化类网站最常见的“让访客三步触达内容”的原则——先通过Banner营造氛围和品牌感,再通过推荐项目引导浏览,最后用资讯和传承人来补充深度内容。
非遗项目列表页是整个系统的浏览核心。它提供了分类筛选和关键词搜索两项能力。分类筛选依据非遗的十个大类——民间文学、传统音乐、传统舞蹈、传统戏剧、曲艺、传统体育/游艺与杂技、传统美术、传统技艺、传统医药、民俗——这正好和国家非物质文化遗产名录的标准分类对齐。搜索则按项目名称进行模糊匹配。列表采用卡片式布局,每张卡片展示封面图、名称、类别和地区标签。
项目详情页包含三个区块。第一块是基本信息,包括项目名称、级别、类别、所属地区、详细介绍;第二块是多图展示区,通过图片列表组件左右切换浏览;第三块是相关传承人模块,展示与该项目关联的传承人信息。这套三级跳转流程——“列表到详情,详情到传承人”——构成了前台最核心的浏览路径。
4.2 留言互动:用户的轻社交入口
留言板模块在前台用户浏览时承担了互动功能。用户需要登录后才能发表留言,留言展示区同时显示用户昵称、留言内容和时间。管理端后台能够对留言进行回复,回复后的内容会在前台同步显示。
这个模块在实现上有一个值得分享的细节:留言展示列表做了分页,但采用“加载更多”而不是传统的页码跳转。从产品角度看,文化展示类网站的访客大多用碎片时间浏览,翻页操作会打断阅读节奏。从技术角度看,“加载更多”本质上是记录当前已加载的页码,点击时向后端请求下一页数据并追加到列表末尾,实现上并不复杂,一次返回10条,数据量不大、接口压力也小。
4.3 后台管理系统的操作流
后台管理端面向管理员,功能模块包括项目管理、传承人管理、资讯发布、留言审核、用户管理几个部分。管理员登录后进入管理首页,左侧是功能菜单,右侧是内容区。
项目管理模块是最核心的操作区。管理员在这里可以新增非遗项目、编辑已有项目、上下架项目、删除项目。新增项目时表单包含项目名称、类别下拉选择、级别下拉选择、地区输入、简介文本域、封面图上传等字段。编辑操作会自动回填已有信息,图片上传部分支持多图上传及设置封面图。整体交互逻辑与主流的CMS后台一致,任何用过后台系统的开发者和运维人员都能快速上手。
资讯发布模块提供了富文本编辑器。考虑到非遗资讯内容可能包含大段的背景介绍、传承历史、活动报道,纯文本框显然不够用,富文本编辑器让管理员可以自己调整段落样式、插入图片,生成的文章保存为HTML格式存入数据库,前台直接渲染输出。这个功能的实现选择了一个轻量级的富文本组件,没有引入重量级的专业编辑器,因为在保证基本格式能力的基础上,保持系统整体的简化才是目标。
5. 关键代码实现与开发心得分享
5.1 后端分层:Controller-Service-Mapper
后端的包结构沿用了几乎所有SpringBoot项目的标准三层架构——Controller负责接收请求和参数校验,Service负责业务逻辑处理,Mapper负责数据库操作。下面以“获取非遗项目详情”这个接口为例,拆解一次完整的请求链路。
Controller层接收前端传来的ID参数,校验通过后调用Service层的查询方法;Service层先通过自定义SQL查询项目主表信息,再分别查询项目的图片列表、视频列表和传承人信息,最后组装成一个完整的项目详情对象返回。这里的组装逻辑是业务的核心——从三次查询合成一个完整数据结构,避免了前端为了拿全一个详情页的数据而连续请求多次接口。这就是后端接口设计里常见的“面向页面聚合数据”思想。
参数校验方面,项目采用了JSR303的@Validated注解配合@NotNull、@NotBlank之类的基础注解。比如新增项目时,项目名称字段标注@NotBlank(message = "项目名称不能为空"),Controller层通过@Validated启动校验后,参数不合格会直接抛出异常并由全局异常处理器统一转化为友好提示返回给前端。这种方式的成本极低,但对接口健壮性的提升非常明显。
5.2 MyBatis-Plus的四个高频用法
在实际开发中,MyBatis-Plus最常用的能力集中在四个地方。
第一个是分页插件。项目使用PaginationInnerInterceptor插件配合Page对象实现分页查询,代码里只需要调用page(new Page<>(current, size), queryWrapper),插件会自动拦截SQL并生成COUNT查询和LIMIT语句,比手写分页的逻辑省事得多,而且不会出现“忘了加limit”这种低级事故。
第二个是LambdaQueryWrapper的链式查询。前端传来搜索关键词时,后端这样构建条件:
LambdaQueryWrapper<HeritageItem> wrapper = new LambdaQueryWrapper<>(); wrapper.like(StringUtils.hasText(keyword), HeritageItem::getName, keyword) .eq(StringUtils.hasText(category), HeritageItem::getCategory, category) .orderByDesc(HeritageItem::getCreateTime);注意第一个参数——StringUtils.hasText(keyword)——这是条件成立时才拼接该查询条件。关键词为空就不查名称,分类没选就不按分类过滤。这个写法在搭建筛选接口时几乎每天都会用到,它让条件判断的代码量减少了一半以上。
第三个是自动填充。项目表中的create_time和update_time字段通过@TableField(fill = FieldFill.INSERT)和@TableField(fill = FieldFill.INSERT_UPDATE)声明,再配合一个实现MetaObjectHandler接口的处理器,在插入和更新时自动写入当前时间。这样业务代码里就完全不需要手动管理这两个字段了。
第四个是逻辑删除。用@TableLogic注解标注deleted字段后,调用deleteById时实际执行的是UPDATE ... SET deleted = 1而不是物理删除。这个设计对内容管理系统非常必要——管理员误删一个非遗项目后,只需要在数据库层面把deleted改回0即可恢复,这在文化数据保护场景下是很实用的兜底手段。
5.3 Vue3组合式API的页面实践
前端在项目列表页采用ref和onMounted组合,实现页面数据加载。核心思路简述如下:使用ref声明响应式数据,页面加载完成后调用后端接口获取数据,并将返回值赋给响应式变量,模板根据变量状态渲染列表内容。
分类筛选功能通过监听分类变化事件实现——当用户切换分类时,重置当前页码为1,清空列表,再按新分类重新请求接口。同时准备了加载状态变量,请求进行中模板显示加载动画,防止用户重复点击。
查看整套代码时可以发现,前端的搜索、筛选、分页三个操作最终其实是同一个请求函数——它接收keyword、category、pageNum三个参数,调用同一个接口,只是触发时机不同。这个“一个接口服务多个交互”的设计模式,对于中小型项目来说,既保证了代码不重复,又降低了前后端联调成本。
6. 部署全流程:从零到可访问的实操记录
6.1 环境版本核对清单
这套系统对部署环境的版本有一定要求,版本不匹配会带来一堆莫名其妙的问题。我自己整理了一份经过实测的版本清单:
| 依赖项 | 推荐版本 | 说明 |
|---|---|---|
| JDK | 1.8或11 | 不建议用17,某些老依赖兼容性有坑 |
| Maven | 3.6以上 | 3.8实测没问题 |
| Node.js | 16或18 | Vite3/4 在16以上运行稳定 |
| npm或yarn | npm8 / yarn1.22 | 二选一 |
| MySQL | 8.0.x | 5.7也能跑,但建议直接用8.0对齐文档 |
| Redis | 不需要 | 本系统无缓存中间件依赖,省事 |
6.2 后端启动五步走
第一步,导入数据库。将项目提供的SQL文件在MySQL8.0中执行,生成完整的表结构和初始数据。执行前确认字符集为utf8mb4,否则中文显示会出现乱码。这一步是最容易出问题的——如果是通过命令行导入,务必加上--default-character-set=utf8mb4参数。
第二步,修改配置文件。打开application.yml,修改数据源的url、username、password三项,数据库名与SQL文件名保持一致。如果MySQL部署在远程服务器,url中的IP地址也要一并修改。
第三步,配置本地文件上传路径。项目在application.yml中有一个自定义配置项,指定上传图片和视频保存的磁盘目录。我习惯在项目根目录建一个upload-dir文件夹专门存放。
第四步,启动后端服务。用IDE打开项目,等待Maven依赖下载完成后直接运行启动类,看到“Started Application in XX seconds”即代表启动成功,默认端口是8080。
第五步,验证接口。浏览器访问http://localhost:8080/api/heritage/list,能返回非遗项目列表的JSON数据即表示后端正常运行。这一步基本确认了数据库连接、MyBatis-Plus映射、接口路由都没有问题。
6.3 前端启动三步走
先确认Node环境:命令行执行node -v和npm -v,确认版本符合要求。
安装依赖:项目根目录执行npm install。这里有个经验之谈——如果安装速度极慢或卡住不动,大概率是网络原因,可以临时切换为国内镜像源,安装完成后再切回来。依赖安装完成后,执行npm run dev启动开发服务器,默认地址是localhost:5173。
最后配置代理解决跨域问题。本地开发时,前端地址是5173端口,后端接口在8080端口,两者端口不同会产生跨域问题。项目在vite.config.js中配置了代理,将前端/api路径的请求转发到后端地址:
server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } }这个代理的本质是让开发服务器充当中间人,在前端看来所有请求都是同一个源,自然就不存在跨域限制了。部署到生产环境时,更规范的做法是让Nginx同时托管前端静态文件和转发后端接口,用一层Nginx同时解决静态资源服务和反向代理两个需求。
7. 常见问题与避坑经验深度实录
7.1 数据库连接失败,报错“Access denied for user”
这个问题的排查路径基本固定。先看用户名密码是否与数据库创建的用户一致,再确认账号是否有远程访问权限。MySQL8.0默认创建的用户只允许localhost登录,如果要远程连,需要单独授权。另一个极容易踩的坑是MySQL8.0的驱动类名问题——它和5.7不同,驱动类要写成com.mysql.cj.jdbc.Driver,同时URL上要加useSSL=false和serverTimezone=Asia/Shanghai两个参数,前者关闭SSL告警,后者解决时区问题。配置对了,数据库连接这一步就不会再有奇奇怪怪的报错。
7.2 分页插件不生效,查询返回全量数据
这个问题出现的原因往往是忘记注册MyBatis-Plus的分页插件。MyBatis-Plus的分页机制不是天生就有的,必须显式添加PaginationInnerInterceptor到MyBatisPlusInterceptor配置类中,否则Page对象虽然创建了,但查询SQL不会被自动拼接LIMIT语句。排查时先检查配置类是否存在,再看@MapperScan路径是否正确覆盖了Mapper接口所在的包。这两处都没问题的话,分页大概率就正常了。
7.3 前端图片能上传但无法访问
图片上传成功后,数据库里存的是类似/upload/images/xxx.jpg的相对路径,但浏览器访问这个路径时却得到404。本质原因是后端应用没有把upload-dir映射成静态资源路径。需要在SpringBoot的配置类中加上资源映射配置,把磁盘目录和URL路径建立联系:
@Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/upload/**") .addResourceHandler("file:" + uploadPath + "/"); }这一步不做,文件相当于存进了服务器上一个浏览器无法触及的角落。
7.4 时间字段返回格式不友好
前端拿到的时间数据格式是2025-03-10T08:30:00.000+00:00,页面上直接展示很不友好。解决方式有两种。其一是在后端application.yml中统一配置JSON序列化格式:
spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8其二是在实体类的日期字段上加@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")注解。两种方式都能解决格式问题,从统一维护的角度我更推荐第一种,改一处全项目生效。
7.5 管理员登录接口被匿名访问
这种问题的典型表现是:通过浏览器直接访问管理员接口地址,没有登录也能拿到数据。原因几乎都是拦截器没有生效或者放行规则配置过宽。排查时先确认拦截器是否在配置类中注册成功;再看排除路径——通常要放行登录接口本身和静态资源,其余/admin/**路径全部拦截;最后检查前端是否正确在请求头中携带了Authorization字段。拦住这三层,权限问题基本不会漏。
8. 源码阅读顺序与二次开发建议
拿到这套源码之后,按什么顺序读效率最高?我的建议是逆着代码执行顺序走。先看数据库表结构——通过SQL文件了解数据模型;再看application.yml——了解项目的基础配置;然后从Controller层入手,顺着接口定义找Service实现,再找Mapper映射;最后结合前端页面的API调用部分,把前后端的接口对应关系理清楚。这个路径走完一遍,整个系统的骨架就在脑子里了。
如果是做二次开发,比较值得扩展的方向有三个。第一个是增加用户收藏功能——非遗项目详情页加一个“收藏”按钮,新建一张收藏表记录用户与项目的关系。第二个是加入搜索关键词联想——在搜索框输入时调用后端接口返回包含该关键词的项目名称列表。第三个是增加数据统计图表——在后台仪表盘中统计各类别项目的数量分布,这一块用ECharts写一个柱状图或饼图就能出效果。
二次开发时有一个原则性建议:尽量在原有分层结构上添加新代码,不要随意改动已有接口的返回格式。前端页面的数据解析通常是按固定字段名来的,改动返回结构会导致原本正常的页面全部报错。稳妥的做法是新增接口、新增表、新增页面,保持已有功能的稳定性。
9. 个人实操后的总结与经验补遗
把一个完整项目从源码变成能跑通的系统,中间涉及的环节其实比想象中多:环境对齐、依赖安装、配置修改、跨域处理、文件上传路径映射、静态资源放行……每一个环节都可能卡住,但每一个问题又有清晰的排查路径。我在多次部署这类系统之后有一个感受——大多数看起来玄乎的报错,最终原因都藏在环境版本不一致和配置项遗漏这两个地方。
最后分享两个实操细节。第一个是,不要跳过SQL脚本中自带的初始数据。它不仅仅是几行测试数据,更重要的是它能让系统在登录后立刻有内容可看,不需要自己手动一条条录入,验证功能时就知道期望看到的数据长什么样。第二个是,启动前端之前先确认后端接口真实可用。先访问一次接口文档或直接请求一次列表接口,确认返回JSON数据后再启动前端页面调试,能省下大量“前端看起来白屏但实际是后端没启动”的无效排查时间。
这套源码的价值不在于代码量有多少,而在于提供了一个结构完整、技术主流、业务真实的综合参考系。把它跑起来只是第一步,真正有价值的动作是顺着代码去理解每一次请求如何在前后端之间流转、每一张表为何这样设计、每一项配置在解决什么问题。按这个思路去读,收获会比单纯“把项目跑通”大得多。