接到“非遗藤条茶展示平台”这种项目时,我心里其实很清楚,难点从来不在Spring Boot本身,而在于怎么把一个偏文化的、散落在纸质资料和老师傅口中的内容,变成一套逻辑清楚的数据结构。藤条茶不是普通茶叶,它牵扯到茶树养护方式、采摘手法、制作流程、传承人脉络,还有产区特色。这些内容如果只是堆几张图片在网页上,那叫相册,不叫平台。
这个项目我定位成“文化内容管理 + 前台展示 + 后台维护”三件事。功能上,游客能看到轮播图、茶叶档案、制作技艺、新闻动态;管理员能维护这些内容;权限上做最简单的角色区分。技术栈用Spring Boot做后端,MySQL存数据,前端用服务端渲染加Bootstrap风格页面。整套做完之后,源码结构清晰、论文好写、答辩时也拿得出东西。下面就把整个思路拆开讲,包括为什么这么设计、核心代码怎么落、论文和PPT怎么组织,以及我踩过的那些坑。
1. 展示平台的需求拆解与整体设计
1.1 非遗藤条茶到底要展示什么
我接到这个选题时,第一件事不是建工程,而是去整理“藤条茶有什么可展示的”。我跟做过这类非遗项目的朋友聊过,也翻了不少资料,最后把所有内容归成了四类:
- 基础档案类:茶叶名称、核心产区、历史渊源、生长环境、形态特征、采摘时间。这些属于确定性较高的数据,适合做成结构化的表。
- 制作技艺类:茶树养护方式、采摘标准、晾晒炒制流程。这类内容最好用分步骤图文来呈现,用户一步一步往下看。
- 文化延伸类:非遗传承人故事、民俗背景、节气活动、新闻动态。这类内容更新频率高,需要后台能够随时录入。
- 对外服务类:留言反馈、展厅预约入口、联系方式。这部分体现平台的“服务”属性,而不是单纯展示。
这样一分,数据结构就清楚了。基础档案对应一张茶叶表,制作技艺对应一张步骤表,传承人和文章各一张表,留言再一张表。后面做查询、搜索、分类汇总都方便。如果一开始不分,全部塞进一个富文本字段里,后台录入倒是省事,但前台想按产区筛选、按年份检索就完全没办法做,论文里的功能设计也没东西可写。
1.2 为什么选Spring Boot而不是别的方案
做一个展示平台,可选方案其实很多。静态HTML加JS也能做,WordPress也能做,但作为“设计与开发”类项目,Spring Boot几乎是性价比最高的选择。
原因是这样的:
第一,Spring Boot能快速把骨架搭起来。内嵌Tomcat,不用单独部署容器,一个可执行Jar包就能跑,这对答辩演示特别重要。现场最怕环境配半天起不来,Spring Boot基本可以做到打包复制到哪都能跑。
第二,生态足够成熟。登录鉴权有Spring Security,数据库操作有MyBatis-Plus,文件上传有现成方案,分页查询写起来也不复杂。这个项目里需要的无非就是增删改查、上传图片、权限控制,这些在Spring Boot里都有标准解法。
第三,体现在论文和答辩里有说服力。非遗展示平台本身不是一个高并发、大流量的系统,但它需要完整地展示“需求分析—系统设计—数据库设计—功能实现—系统测试”这套工程化过程。Spring Boot作为目前最主流的企业级开发框架之一,放在论文里天然有分量。
我当时也犹豫过要不要做成前后端分离,用Vue3加Element-UI。后来考虑到“展示平台”的核心是内容呈现,交互复杂度并不高,而且项目还需要交付源码、论文和PPT,用传统的服务端渲染反而更容易解释清楚。前台页面可以直接通过Thymeleaf模板从后端拿数据,后台管理页面复用同一套登录逻辑,整个项目结构更紧凑,导出的文档也更聚焦。
2. 技术选型与前期准备
2.1 后端技术栈怎么搭配
这个项目的后端依赖我列一下,都是实际用到的:
- Spring Boot 2.7.x:用2.x而不是3.x,是因为当时团队对JDK 8更熟悉,而且2.7还在维护周期内,网上资料也多,遇到问题容易搜到答案。
- MyBatis-Plus:负责数据库交互。自带分页插件、条件构造器,写Dao层的时候能省掉大量重复SQL。
- MySQL 8.0:存储业务数据。字符集设置成utf8mb4,因为非遗介绍里经常出现特殊符号。
- Lombok:简化实体类代码,减少Getter/Setter的样板代码。
- Hutool:工具类集合,生成验证码、文件读写、日期处理都用它,少写很多工具方法。
- Spring Security + JWT:后台管理员登录认证用JWT无状态令牌,前台用户登录也可以复用同一套逻辑。
依赖管理直接写在pom.xml里。核心依赖片段大概是这样的:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.18</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> </dependencies>数据库连接配置放在application.yml里:
server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/tea_platform?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: 你的密码 servlet: multipart: max-file-size: 20MB max-request-size: 50MB mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: id-type: auto注意一点:serverTimezone=Asia/Shanghai这个参数必须要加,不然Docker部署或者服务器时区不是北京时间时,时间查询会出现8小时偏移,测出来数据对不上还以为是代码有Bug。
2.2 前端方案与页面布局
前台页面我用的是Thymeleaf加简单原生CSS,再加一点Bootstrap的样式。对这类非遗展示平台来说,花哨的动画意义不大,重要的是页面结构清楚、图片大而清晰、文字排版舒服。
页面结构大概如下:
- 首页:顶部导航栏、大图轮播、推荐茶品区块、最新动态区块。
- 茶叶档案列表页:按产区、年份、类别筛选,卡片式展示。
- 茶叶详情页:基本信息区、制作步骤时间线、传承人介绍、相关文章推荐。
- 制作技艺页:用分步图文展示藤条茶从养护到成茶的过程。
- 新闻动态列表页及详情页:展示非遗相关活动和新闻。
- 关于/留言页:用户留言表单和后端展示。
管理端单独用一套页面,路径放在/admin下,登录后才能访问。管理员能管理轮播图、茶品档案、新闻、留言回复,还能调整推荐的展示顺序。
我建议前端尽量不要依赖太重的框架,因为这个平台本身是内容展示为主,HTML模板加少量JavaScript就够了。把精力放在后端结构设计上,论文里“系统功能结构图”“系统架构图”更好画。
2.3 数据库表结构设计
数据库设计是整个系统的灵魂。我建的表不算多,但每张表都对应一个明确的功能点:
| 表名 | 用途 | 关键字段 |
|---|---|---|
| t_admin | 管理员账号 | id, username, password, nickname, role |
| t_user | 前台注册用户 | id, username, password, phone, avatar, create_time |
| t_tea_archives | 茶叶档案 | id, name, region, category, history, feature, image, video_url, year |
| t_technique_step | 制作技艺步骤 | id, tea_id, step_no, title, content, image |
| t_inheritor | 传承人信息 | id, name, portrait, intro, honors, tea_id |
| t_article | 新闻动态 | id, title, summary, content, cover, publish_time |
| t_banner | 轮播图配置 | id, image, link_url, sort, status |
| t_message | 用户留言 | id, user_id, content, reply, create_time |
t_tea_archives是核心表,里面放茶叶的基础信息。值得一提的是region和category这两个字段,我特意把它们单独拿出来而不是写死在内容里,这样前台筛选就可以直接按字段分组查询,论文里也能写“系统支持多维检索”。
t_technique_step用tea_id关联茶品,用step_no来控制显示顺序。平台展示制作技艺时,前端按step_no升序排列,输出一组步骤卡片,用户上下滑动就能看完整个流程。
所有日期字段统一用datetime类型,代码里对应LocalDateTime。字符集全部用utf8mb4,排序规则用utf8mb4_general_ci,避免中文乱码。
3. 核心功能模块实现与关键代码
3.1 首页聚合数据的加载逻辑
首页不能直接把数据库所有数据都拿出来,那样页面会非常臃肿。我当时设计了三个聚合块:轮播图、推荐茶品、最新文章。每个聚合块对应一个Service方法,最终在Controller里组合成Model数据返回给Thymeleaf模板。
核心代码大致如下:
@Controller public class IndexController { private final BannerService bannerService; private final TeaArchivesService teaArchivesService; private final ArticleService articleService; public IndexController(BannerService bannerService, TeaArchivesService teaArchivesService, ArticleService articleService) { this.bannerService = bannerService; this.teaArchivesService = teaArchivesService; this.articleService = articleService; } @GetMapping("/") public String index(Model model) { model.addAttribute("banners", bannerService.listActiveBanners()); model.addAttribute("recommendedTeas", teaArchivesService.listRecommended(6)); model.addAttribute("latestArticles", articleService.listLatest(4)); return "index"; } }这里利用Spring的构造器注入,比@Autowired字段注入更推荐,写在论文里也说得通:构造器注入保证依赖不可变,方便单元测试时替换Mock对象。
3.2 茶叶档案的列表展示与条件筛选
茶叶档案列表页需要支持按产区和年份筛选。MyBatis-Plus的条件构造器写起来很简洁:
public PageResult<TeaArchivesVO> queryArchives(String region, Integer year, int page, int size) { Page<TeaArchives> pageParam = new Page<>(page, size); LambdaQueryWrapper<TeaArchives> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(StringUtils.hasText(region), TeaArchives::getRegion, region) .eq(year != null, TeaArchives::getYear, year) .orderByDesc(TeaArchives::getCreateTime); Page<TeaArchives> result = teaArchivesMapper.selectPage(pageParam, wrapper); // 转VO并返回 }LambdaQueryWrapper用方法引用来指向实体字段,比写字符串字段名要安全。修改字段名后如果忘记同步SQL,这里会直接报编译错误,不至于等到运行时才暴露。
页面上的搜索结果返回一个分页结果对象,包含当前页数据、总条数、总页数,前端拿到后在列表底部渲染分页组件。分页这个点在答辩时经常会被问,所以我在论文里专门写了分页插件底层原理:MyBatis-Plus分页插件通过拦截器自动在SQL末尾拼接LIMIT语句,不用手写分页SQL。
3.3 图片上传与静态资源映射
非遗平台必然要传很多茶叶照片、人物照片、步骤图。这些图片不能存到数据库里,数据库只存路径字符串,文件本身要落盘。我在项目里加了一个上传接口,支持单张和多张上传:
@PostMapping("/api/upload") @ResponseBody public Result<String> upload(@RequestParam("file") MultipartFile file) { if (file.isEmpty()) { return Result.error("文件不能为空"); } String originalFilename = file.getOriginalFilename(); String ext = originalFilename.substring(originalFilename.lastIndexOf(".")); String filename = IdUtil.simpleUUID() + ext; String datePath = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")); File dir = new File(uploadPath, datePath); if (!dir.exists()) { dir.mkdirs(); } File dest = new File(dir, filename); file.transferTo(dest); String url = "/upload/" + datePath + "/" + filename; return Result.ok(url); }上传后的文件路径要单独做静态资源映射,否则浏览器访问不到。
@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/upload/**") .addResourceHandler("file:" + uploadPath + "/"); } }文件名的生成我用了UUID,避免中文名乱码和同名覆盖问题。日期目录的拆分让图片文件不至于全部堆在一个文件夹里,方便以后迁移归档。这里有个细节:file.transferTo如果目标文件已经存在会抛异常,所以一定要保证文件名唯一,UUID是最省事的方案。
3.4 后台管理与权限控制
后台管理是这个项目的重头戏,也是论文里的核心功能之一。我用Spring Security加上JWT做认证。管理员登录成功后得到一个Token,后续访问管理接口都在请求头带上Authorization: Bearer token,后端通过拦截器校验。
关键配置类是SecurityConfig:
@Configuration @EnableWebSecurity public class SecurityConfig { private final JwtAuthenticationFilter jwtAuthenticationFilter; public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter) { this.jwtAuthenticationFilter = jwtAuthenticationFilter; } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.csrf().disable() .cors().and() .authorizeHttpRequests(auth -> auth .requestMatchers("/api/login", "/api/register", "/upload/**", "/", "/archives/**", "/article/**").permitAll() .requestMatchers("/admin/**").hasRole("ADMIN") .anyRequest().authenticated() ) .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } }这里使用/admin/**作为管理端入口,只有ADMIN角色才能访问。前台游客访问展示页面不需要登录,但留言需要登录,这样设计既降低了使用门槛,又保留了用户交互记录,论文里也方便写“系统分为前台游客和后台管理员两种角色”。
后台管理的功能我给每个模块都做了独立的Controller:茶叶管理、步骤管理、传承人管理、文章管理、轮播图管理、留言管理。每个Controller里的方法就是标准的增删改查,配合统一的返回结果类Result<T>,前端拿到的数据格式一致,页面渲染代码写起来也不累。
4. 论文文档与答辩PPT的组织要点
4.1 精品论文的结构怎么排
这个项目配套的论文,我当时是按工程标准来组织的。一篇完整的毕业论文大概分七个章节,每个章节的侧重点不一样:
- 第一章 绪论:写非遗数字化背景、藤条茶文化价值、国内外研究现状、项目目标与意义。这里要引出“为什么需要这样一个平台”,不需要写代码,但要体现出你对行业的理解。
- 第二章 相关技术介绍:写Spring Boot、MyBatis-Plus、MySQL、Thymeleaf、Spring Security、JWT。每项技术写清楚它是什么、能解决什么问题、为什么适合本项目。
- 第三章 需求分析:画系统用例图,列功能需求和非功能需求。不要抄网上的模板,要结合非遗展示平台的实际场景。
- 第四章 系统设计:画系统架构图、功能模块图、数据库ER图。数据库表的字段说明尽量详细,每张表加一段设计说明。
- 第五章 系统实现:按模块写核心代码和运行效果截图。每个模块最好配一张页面截图和一段关键代码,代码不要贴太多,论文不是代码仓库,关键是讲清楚逻辑。
- 第六章 系统测试:写测试环境、测试用例表、测试结果。至少覆盖登录、茶品查询、分类筛选、上传图片、留言这些核心功能。
- 第七章 总结与展望:写项目成果、遇到的问题、未来可以扩展的方向。
字数上正文保持在1.5万字到2万字之间比较合适,技术类论文太短说明内容不够,太长又会注水。
4.2 答辩PPT应该突出什么
答辩时间一般只有五到十分钟,PPT千万别超过十二页。我当时做完多项式后删了很久,只保留这几块核心内容:
- 项目背景页:用一两句话说清楚藤条茶非遗数字化的意义,这里可以放一张茶叶产区的现场照片。
- 系统架构图页:画一张简洁的后端分层图,Controller、Service、Mapper三层,标明用了哪些技术。
- 功能模块图页:把前台和后台的功能用树形图列出来。
- 数据库设计页:放核心表结构截图,重点讲茶叶档案表和步骤表的关系。
- 核心功能演示页:分三个截图,一个是首页,一个是茶品列表筛选,一个是后台管理页。
- 测试结论页:放一条测试用例表,证明系统是经过测试的。
- 总结页:讲项目亮点和不足,亮点写“多维检索”“多媒体展示”“前后台权限分离”,不足写“高并发能力有待提升”“内容录入与人工运营相关”这类话,体现你有真实的工程认识。
5. 实操中的避坑经验与问题排查
5.1 上传了图片页面却显示不出来
这个坑我遇到过不止一次。文件上传成功了,数据库里也存了路径,但页面就是加载不出图片。原因几乎都是静态资源映射没生效。addResourceHandler("/upload/**")配置了以后,还要确认访问路径以/upload/开头,并且上传目录是有读写权限的。服务器上部署的时候,上传目录最好放在项目外部,比如/data/tea-platform/upload,这样更新版本时不会把图片文件一起清掉。
5.2 时间差8小时
如果数据库查询出来的时间比实际时间晚了8个小时,基本就是连接串缺少serverTimezone参数,或者MySQL驱动版本和时区配置不匹配。我通常在application.yml写死Asia/Shanghai,并且实体类字段用LocalDateTime,这样无论服务器在哪个时区,处理逻辑都是统一的。
5.3 演示现场没有演示数据
答辩时最尴尬的一刻是打开系统发现数据库是空的,只能现场到处点也点不出内容来。所以我在项目里写了一个初始化数据脚本,放了两条完整的茶叶档案、三套制作步骤图、四篇新闻、一套轮播图。演示时候起码不会冷场。而且这些数据都是围绕藤条茶真实流程整理的,不是随便Lorem Ipsum,看起来更专业。
5.4 外键和关联查询不要滥用
刚开始做的时候,我给茶叶表、步骤表、传承人表之间都建了物理外键。后来发现管理端删除茶叶信息时,因为有关联步骤数据,删除失败,还要先手动清子表数据才能删父表,非常麻烦。后来我干脆去掉物理外键,表之间的关联关系只在业务代码里维护,删除操作时先查子表有没有数据再决定是否允许删除。这个设计其实更灵活,论文里还可以在“系统设计”一节说明这么做的理由。
5.5 MyBatis-Plus条件构造器的坑
条件构造器里eq(condition, column, value)的第一个参数是动态条件,很多人习惯直接写eq(column, value),这没问题,但如果某个查询参数为null,而你又没加动态判断,SQL就会拼接出where xx = null,查不到任何数据。我习惯把所有可空参数都包一层StringUtils.hasText或Objects.nonNull(),虽然代码多几行,但逻辑严谨很多,这种细节在代码评审和答辩时都是加分项。
6. 从一次非遗展示项目里总结的体会
这个项目做完后,我个人最大的体会是:所谓数字化平台,说到底不是“给文化套一个网页外壳”,而是要真的去理解文化内容本身的结构。藤条茶和普通茶叶的展示方式完全不同,它更强调“养护”和“技艺”的长期性,所以平台里最能打动人的模块不是华丽的首页,而是那张分步骤的制作流程图,以及传承人的口述文字。
很多做毕设或项目竞赛的同学,容易把精力花在给页面加特效、给代码套复杂设计模式上。而这些技术在“展示平台”里价值其实不大。反而是把内容分类、把筛选条件设计清楚、把后台录入体验做到顺手,这些不起眼的活儿决定了系统好不好用。
如果以后想把这个项目继续扩展,可以考虑加移动端适配、VR展厅、视频点播模块,甚至可以接地图接口展示产区分布,让用户真正“云游”一下茶山。技术栈也能替换成Spring Cloud Alibaba那套微服务体系,但说实话,对于这类平台,单体应用加足够的代码质量,已经能覆盖绝大多数真实需求了。
最后分享一个小经验:无论你做的是源码、论文还是PPT,一定要把“这个项目解决了什么问题”放在最高优先级。只要这句话在答辩时能讲清楚,整个项目的完成度就会显得特别扎实。