很多计算机专业的同学做毕设,都会遇到一个尴尬的循环:选题太大做不完,选题太小没亮点,做完系统却讲不清技术含量。今天要拆解的这套“基于 Spring Boot 与内容推荐算法的手工编织非遗平台”,恰好踩中了当前毕设评审最看重的几个点:前后端分离架构、推荐算法落地、完整业务闭环。它不是那种只堆 CRUD 的“管理系统”,而是把非遗展示、用户互动、个性化推荐串成了一个可运行、可讲解、可扩展的真实项目。
这篇文章会从选题价值、系统设计、核心代码、推荐算法实现、部署排错几个角度完整展开。如果你正在准备毕设,或者想把这类项目改造成自己的课设/求职项目,这篇内容值得你认真看完。
1. 这个项目真正值得做的原因
手工编织非遗平台的难点不在于“增删改查”,而在于“内容怎么触达用户”。一个普通的管理系统,用户只能靠分类目录一页页翻找作品。但非遗平台的内容分类往往比较主观——用户可能喜欢的是“粗棒针织的复古风毛衣”,而不是“分类编号 03 下的围巾”。
内容推荐算法解决的就是这个问题:根据用户的历史浏览、收藏、点赞行为,推测他可能喜欢哪一类编织作品,然后把对应的内容推到他面前。这在毕设答辩里是一个非常好的技术亮点,因为评审老师能看到“你的系统不只是数据库的壳子,而是真的有算法逻辑在里面”。
同时,从工程角度看,这个项目使用了 Spring Boot + Vue 的前后端分离模式。这类架构的好处是:
- 前端独立开发、独立部署,可以使用 Node 生态的构建工具;
- 后端只提供 RESTful API,便于后期接小程序、App 或第三方系统;
- 分工清晰,便于写进简历和答辩文档。
从源码资料的完整性看,这类项目通常附带数据库脚本、接口文档、前端页面源码和部署说明,对于需要快速跑通并二次开发的同学来说,是非常友好的起点。
2. 核心概念与前置知识
在做这个项目之前,有几个概念必须先理清。否则即使代码跑通了,答辩或面试时也很容易被追问到答不上来。
2.1 Spring Boot 在项目中扮演什么角色
Spring Boot 是这套系统的后端基础框架。它简化了 Spring 项目的配置,内置 Tomcat,支持自动装配。在这个项目中,Spring Boot 负责:
- 提供 RESTful API 接口;
- 管理业务逻辑层的依赖注入;
- 整合 MyBatis/JPA 操作数据库;
- 处理全局异常与统一返回结构。
2.2 前后端分离到底“分”在哪
传统单体项目里,前端页面模板和后端代码在同一个工程内。前后端分离后,前端是独立的 Vue 工程,通过 HTTP 请求调用后端接口。
两者的约定通常在接口文档中体现。后端返回统一的 JSON 格式,前端拿到数据后渲染页面,状态由前端管理。
一个典型的分离架构如下:
Vue Frontend (端口: 8080) | | axios / fetch (RESTful API) v Spring Boot Backend (端口: 8081) | | MyBatis / JPA v MySQL Database (端口: 3306)2.3 内容推荐算法在非遗平台中的应用方式
内容推荐算法广义上可分为三类:
- 基于内容的推荐(Content-based):分析用户曾经喜欢的物品的特征,推荐具有相似特征的物品;
- 协同过滤推荐(Collaborative Filtering):利用“相似用户”或“相似物品”的历史行为做推荐;
- 混合推荐:融合多种策略,提升推荐质量和覆盖率。
在手工编织平台中,基于内容的推荐更容易落地。原因在于,编织作品可以被结构化打标:技法类型(钩针、棒针、梭编)、材质(羊毛、棉线、混纺)、适用场景(家居、服饰、配饰)、难度等级。有了标签体系,就可以构建“用户兴趣画像”,再通过画像进行推荐。
3. 系统功能模块与数据库设计
3.1 功能模块拆解
一个完整的非遗平台,从角色角度看主要分为管理员和普通用户。
用户端:
- 注册登录;
- 浏览编织作品列表与详情;
- 按分类筛选、关键词搜索;
- 收藏作品、点赞作品、评论作品;
- 查看个人收藏列表与浏览历史;
- 接收首页推荐内容。
管理员端:
- 作品管理:新增、编辑、下架编织作品;
- 分类管理:维护技法、风格、材质等分类标签;
- 用户管理:封禁 / 启用用户账号;
- 推荐管理:配置算法开关或查看推荐数据统计;
- 数据看板:展示访问量、点赞量、收藏量等基础统计。
从“增删改查”的角度看,作品管理、分类管理、用户管理是核心。从“算法落地”的角度看,用户行为记录表(浏览、收藏、点赞、评论)是关键。
3.2 核心数据表设计
编写数据库脚本时,建议至少包含以下表。表结构设计的好坏会直接影响推荐算法的实现难度。
-- 用户表 CREATE TABLE `t_user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL COMMENT '用户名', `password` varchar(255) NOT NULL COMMENT '加密密码', `nickname` varchar(50) DEFAULT NULL, `avatar` varchar(255) DEFAULT NULL, `role` tinyint(4) DEFAULT '0' COMMENT '0-用户,1-管理员', `status` tinyint(4) DEFAULT '1' COMMENT '1-正常,0-禁用', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 作品分类表 CREATE TABLE `t_category` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(100) NOT NULL COMMENT '分类名称', `code` varchar(50) DEFAULT NULL COMMENT '分类编码', `parent_id` bigint(20) DEFAULT '0', `sort` int(11) DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 编织作品表 CREATE TABLE `t_craft` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `title` varchar(200) NOT NULL COMMENT '作品标题', `cover_image` varchar(255) DEFAULT NULL COMMENT '封面图', `images` text COMMENT '多图,逗号分隔', `category_id` bigint(20) DEFAULT NULL, `technique` varchar(50) DEFAULT NULL COMMENT '技法:钩针、棒针等', `material` varchar(50) DEFAULT NULL COMMENT '材质:羊毛、棉线等', `difficulty` tinyint(4) DEFAULT '1' COMMENT '难度:1-简单,2-中等,3-困难', `description` text COMMENT '作品描述', `view_count` int(11) DEFAULT '0', `like_count` int(11) DEFAULT '0', `favorite_count` int(11) DEFAULT '0', `status` tinyint(4) DEFAULT '1' COMMENT '1-上架,0-下架', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 收藏表 CREATE TABLE `t_favorite` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `user_id` bigint(20) NOT NULL, `craft_id` bigint(20) NOT NULL, `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_user_id` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 浏览历史表 CREATE TABLE `t_view_history` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `user_id` bigint(20) NOT NULL, `craft_id` bigint(20) NOT NULL, `view_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_user_craft` (`user_id`, `craft_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;这套表结构将“用户-作品-行为”三张核心关系拆开,后续推荐模块需要查询某个用户的喜好标签时,可以直接从收藏表和浏览历史表关联作品表聚合得出。
4. 环境准备与项目初始化
4.1 环境要求
| 依赖项 | 说明 |
|---|---|
| JDK | 推荐 JDK 1.8 或 JDK 11,具体以项目 pom.xml 为准 |
| Maven | 3.6+ 即可 |
| Node.js | 前端构建需要,推荐 14+ 或 16+ |
| MySQL | 5.7 或 8.0 |
| IDE | IDEA 或 Eclipse,后端推荐 IDEA |
| 前端工具 | VS Code |
版本请以你手中源码资料里的实际要求为准。本文重点演示通用思路,不绑定死版本。
4.2 后端工程初始目录结构
一个清晰的 Spring Boot 工程应当按职责分包,推荐结构如下:
src/main/java/com/example/craft/ ├── CraftApplication.java # 启动类 ├── common/ │ ├── Result.java # 统一返回结构 │ ├── ResultCode.java │ └── GlobalExceptionHandler.java ├── config/ │ ├── CorsConfig.java # 跨域配置 │ ├── WebMvcConfig.java │ └── MybatisPlusConfig.java # 分页插件配置 ├── controller/ │ ├── UserController.java │ ├── CraftController.java │ ├── CategoryController.java │ ├── FavoriteController.java │ ├── CommentController.java │ └── RecommendController.java ├── service/ │ ├── UserService.java │ ├── CraftService.java │ ├── FavoriteService.java │ ├── ViewHistoryService.java │ └── RecommendService.java ├── mapper/ │ ├── UserMapper.java │ ├── CraftMapper.java │ ├── FavoriteMapper.java │ └── ViewHistoryMapper.java ├── entity/ │ ├── User.java │ ├── Craft.java │ ├── Category.java │ ├── Favorite.java │ └── ViewHistory.java └── dto/ ├── CraftQueryDTO.java └── RecommendResultDTO.java4.3 后端核心配置文件
application.yml是 Spring Boot 项目的核心配置文件。你需要根据本地数据库信息调整用户名密码。
server: port: 8081 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/craft_platform?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: your_password jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8 mybatis-plus: mapper-locations: classpath:mapper/*.xml configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl map-underscore-to-camel-case: true global-config: db-config: id-type: auto这里的关键字段是数据库连接串中的serverTimezone=Asia/Shanghai。如果缺少这个配置,在部分 MySQL 版本下会出现 8 小时时差或连接报错。
5. 后端核心代码实现
5.1 统一返回结果封装
前后端分离项目中,统一返回结构能让前端处理数据更简单。后端所有接口都返回这个 Result 对象。
// 文件路径:src/main/java/com/example/craft/common/Result.java package com.example.craft.common; public class Result<T> { private Integer code; private String message; private T data; public Result(Integer code, String message, T data) { this.code = code; this.message = message; this.data = data; } public static <T> Result<T> success(T data) { return new Result<>(200, "success", data); } public static <T> Result<T> success() { return new Result<>(200, "success", null); } public static <T> Result<T> error(Integer code, String message) { return new Result<>(code, message, null); } public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public T getData() { return data; } public void setData(T data) { this.data = data; } }前端只需要判断code是否为 200,就可以决定是渲染数据还是弹出错误提示。
5.2 作品分页查询接口
作品列表是平台最核心的接口,也是“增删改查”中最常被考察的部分。这里使用 MyBatis Plus 的分页插件实现。
// 文件路径:src/main/java/com/example/craft/controller/CraftController.java package com.example.craft.controller; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.example.craft.common.Result; import com.example.craft.entity.Craft; import com.example.craft.service.CraftService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/craft") public class CraftController { @Autowired private CraftService craftService; /** * 分页查询编织作品列表 * @param pageNum 页码,从1开始 * @param pageSize 每页条数 * @param title 标题模糊搜索(可选) * @param categoryId 分类筛选(可选) */ @GetMapping("/list") public Result<Page<Craft>> list( @RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize, @RequestParam(required = false) String title, @RequestParam(required = false) Long categoryId) { Page<Craft> page = new Page<>(pageNum, pageSize); QueryWrapper<Craft> wrapper = new QueryWrapper<>(); wrapper.eq("status", 1); if (title != null && !title.isEmpty()) { wrapper.like("title", title); } if (categoryId != null) { wrapper.eq("category_id", categoryId); } wrapper.orderByDesc("create_time"); Page<Craft> result = craftService.page(page, wrapper); return Result.success(result); } /** * 查询作品详情 */ @GetMapping("/detail/{id}") public Result<Craft> detail(@PathVariable Long id) { Craft craft = craftService.getById(id); if (craft == null) { return Result.error(404, "作品不存在"); } // 浏览量加1 craftService.increaseViewCount(id); return Result.success(craft); } }这个接口同时支持分页、搜索、分类筛选和状态过滤。在设计时需要注意:查询列表时不要返回 description 等大字段,否则数据量大时响应会变慢。可以用 DTO 或 QueryWrapper 的 select 方法做字段裁剪。
5.3 收藏与浏览记录接口
推荐算法依赖用户行为数据。收藏和浏览记录是两条最重要的数据来源。
// 文件路径:src/main/java/com/example/craft/controller/FavoriteController.java package com.example.craft.controller; import com.example.craft.common.Result; import com.example.craft.entity.Favorite; import com.example.craft.service.FavoriteService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/favorite") public class FavoriteController { @Autowired private FavoriteService favoriteService; /** * 收藏作品 */ @PostMapping("/add") public Result<Void> add(@RequestParam Long userId, @RequestParam Long craftId) { boolean success = favoriteService.addFavorite(userId, craftId); return success ? Result.success() : Result.error(500, "收藏失败"); } /** * 取消收藏 */ @DeleteMapping("/cancel") public Result<Void> cancel(@RequestParam Long userId, @RequestParam Long craftId) { boolean success = favoriteService.cancelFavorite(userId, craftId); return success ? Result.success() : Result.error(500, "取消收藏失败"); } /** * 当前用户是否已收藏该作品 */ @GetMapping("/check") public Result<Boolean> check(@RequestParam Long userId, @RequestParam Long craftId) { return Result.success(favoriteService.isFavorite(userId, craftId)); } }这里有一个非常容易忽略的细节:收藏接口一定需要做“重复收藏”校验。如果用户连续点击两次收藏按钮,数据库里出现重复记录,推荐算法在统计行为权重时就会出错。
5.4 推荐算法核心实现
5.4.1 基于标签聚合的用户兴趣画像
对于非遗手工艺平台,比较实用的推荐策略是先构建“用户-标签”矩阵,通过用户历史行为累计标签权重。
标签可以是技法(钩针/棒针/绳编)、材质(羊毛/棉线/麻线)、风格(复古/简约/民族风)。一个作品如果有多个标签,可以在标签表中以多条记录存储,也可以用 string 类型字段存储后按分隔符拆解。
以下是一个简化版推荐服务实现思路:
// 文件路径:src/main/java/com/example/craft/service/impl/RecommendServiceImpl.java package com.example.craft.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.example.craft.entity.Craft; import com.example.craft.entity.Favorite; import com.example.craft.entity.ViewHistory; import com.example.craft.mapper.CraftMapper; import com.example.craft.mapper.FavoriteMapper; import com.example.craft.mapper.ViewHistoryMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.*; import java.util.stream.Collectors; @Service public class RecommendServiceImpl { @Autowired private FavoriteMapper favoriteMapper; @Autowired private ViewHistoryMapper viewHistoryMapper; @Autowired private CraftMapper craftMapper; /** * 基于用户行为的推荐 * 简化策略:根据用户收藏作品的分类标签统计偏好,推荐同标签下其他作品 */ public List<Craft> recommendForUser(Long userId, int limit) { // 1. 查询用户收藏的作品 QueryWrapper<Favorite> favWrapper = new QueryWrapper<>(); favWrapper.eq("user_id", userId); List<Favorite> favorites = favoriteMapper.selectList(favWrapper); // 2. 查询用户浏览历史 QueryWrapper<ViewHistory> historyWrapper = new QueryWrapper<>(); historyWrapper.eq("user_id", userId); List<ViewHistory> histories = viewHistoryMapper.selectList(historyWrapper); // 3. 汇总用户感兴趣的作品ID集合(收藏权重 > 浏览权重,这里简单做去重合并) Set<Long> interactedCraftIds = new HashSet<>(); favorites.forEach(f -> interactedCraftIds.add(f.getCraftId())); histories.forEach(h -> interactedCraftIds.add(h.getCraftId())); if (interactedCraftIds.isEmpty()) { // 新用户无行为数据,返回最新上架作品 QueryWrapper<Craft> newWrapper = new QueryWrapper<>(); newWrapper.eq("status", 1).orderByDesc("create_time").last("limit " + limit); return craftMapper.selectList(newWrapper); } // 4. 统计用户最感兴趣的分类 Map<Long, Integer> categoryScore = new HashMap<>(); for (Long craftId : interactedCraftIds) { Craft craft = craftMapper.selectById(craftId); if (craft != null && craft.getCategoryId() != null) { categoryScore.merge(craft.getCategoryId(), 1, Integer::sum); } } List<Map.Entry<Long, Integer>> sortedCategories = categoryScore.entrySet().stream() .sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue())) .collect(Collectors.toList()); if (sortedCategories.isEmpty()) { return new ArrayList<>(); } // 5. 从偏好分类中取热度较高的作品返回,排除已交互的 List<Craft> recommendList = new ArrayList<>(); for (Map.Entry<Long, Integer> entry : sortedCategories) { if (recommendList.size() >= limit) break; QueryWrapper<Craft> craftWrapper = new QueryWrapper<>(); craftWrapper.eq("status", 1) .eq("category_id", entry.getKey()) .notIn("id", interactedCraftIds) .orderByDesc("view_count") .last("limit " + (limit - recommendList.size())); recommendList.addAll(craftMapper.selectList(craftWrapper)); } return recommendList; } }这段代码的逻辑可以总结为三步:
- 收集用户行为数据;
- 统计用户偏好的分类;
- 在偏好分类下筛选热度高且用户未交互过的作品。
这种实现比简单的“随机推荐”或“最新推荐”更有说服力。而且它不依赖复杂的外部组件,直接在项目里就能跑通。如果想要更强的推荐效果,可以引入 Redis 做缓存、用定时任务计算离线推荐结果,但毕设阶段用这个方案已经能清楚展示推荐思路。
5.4.2 协同过滤思路扩展
如果项目要求“内容推荐算法”更深入,可以在这一版基础上增加基于用户的协同过滤:
- 找到与当前用户兴趣最相似的其他用户;
- 把那些用户收藏过、但当前用户没看过的高热度作品推荐出来。
相似度计算可以使用余弦相似度。用“用户-作品收藏矩阵”的每一行代表用户的兴趣向量,然后计算余弦相似度。
// 简化版余弦相似度计算 public double cosineSimilarity(Map<Long, Integer> userA, Map<Long, Integer> userB) { Set<Long> commonKeys = new HashSet<>(userA.keySet()); commonKeys.retainAll(userB.keySet()); double dotProduct = 0; double normA = 0; double normB = 0; for (Long key : commonKeys) { dotProduct += userA.get(key) * userB.get(key); } for (Integer value : userA.values()) { normA += Math.pow(value, 2); } for (Integer value : userB.values()) { normB += Math.pow(value, 2); } if (normA == 0 || normB == 0) { return 0; } return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB)); }需要说明的是,当前用户数量少时,协同过滤可能找不到“相似用户”,所以实践中通常会把基于内容的推荐作为主推荐策略,协同过滤作为补充。这种组合策略在项目文档里也更容易写出层次感。
6. 前端功能与接口联调
6.1 前端页面结构
前端工程建议基于 Vue 2 或 Vue 3 + Element UI。核心页面包括:
- 首页:Banner 轮播、热门作品、个性化推荐列表;
- 作品列表页:分类筛选、搜索、分页;
- 作品详情页:多图展示、标签信息、收藏 / 点赞 / 评论;
- 个人中心:收藏列表、浏览历史、修改资料。
6.2 前端调用接口示例
以作品列表页为例,使用 axios 调用后端接口:
// 文件路径:src/api/craft.js import axios from 'axios' const request = axios.create({ baseURL: 'http://localhost:8081/api', timeout: 10000, headers: { 'Content-Type': 'application/json' } }) // 分页获取作品列表 export function getCraftList(params) { return request.get('/craft/list', { params }) } // 获取作品详情 export function getCraftDetail(id) { return request.get(`/craft/detail/${id}`) } // 收藏作品 export function addFavorite(userId, craftId) { return request.post('/favorite/add', null, { params: { userId, craftId } }) } // 获取推荐列表 export function getRecommendList(userId, limit) { return request.get('/recommend/list', { params: { userId, limit } }) }前端调用时需要注意接口的返回结构,统一从res.data.code判断请求是否成功。
// 页面中调用 import { getCraftList } from '@/api/craft' export default { data() { return { craftList: [], total: 0, pageNum: 1, pageSize: 12 } }, methods: { async fetchList() { const res = await getCraftList({ pageNum: this.pageNum, pageSize: this.pageSize, title: this.keyword, categoryId: this.currentCategoryId }) if (res.data.code === 200) { this.craftList = res.data.data.records this.total = res.data.data.total } else { this.$message.error(res.data.message) } } } }6.3 跨域问题处理
前后端分离开发中,跨域是第一个会遇到的问题。后端可以通过 CorsConfig 统一解决。
// 文件路径:src/main/java/com/example/craft/config/CorsConfig.java package com.example.craft.config; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); } }注意,如果开启了allowCredentials(true)并设置了具体的前端地址,建议把allowedOriginPatterns配置为你的前端实际地址,而不是*。这样更安全,也符合生产环境规范。
7. 运行验证与效果检查
7.1 后端启动
在 IDEA 中打开后端工程,等待 Maven 依赖下载完成后,运行CraftApplication.java。启动成功后,控制台会显示 Spring Boot 的启动日志和 Tomcat 端口。
验证接口是否可用,可以直接在浏览器访问:
http://localhost:8081/api/craft/list?pageNum=1&pageSize=5如果返回 JSON 数据,说明接口正常。
7.2 前端启动
在 VS Code 中打开前端工程,执行:
npm install npm run serve启动后访问http://localhost:8080,即可看到前端首页。
建议先测试以下用户路径:
- 注册一个普通用户并登录;
- 浏览几个作品,点击详情并收藏;
- 查看个人中心的收藏列表是否更新;
- 回到首页刷新“推荐内容”区域,确认推荐列表基于刚才的浏览行为发生了变化。
7.3 推荐效果判断方法
手工编织平台上,如果用户收藏了一个“钩针编织包”的作品,推荐区域应优先出现同分类或同标签(钩针、编织包)的其他作品,而不是随机出现棒针毛衣。这是验证推荐算法是否生效的最直观方法。
8. 常见问题与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 后端启动报数据库连接失败 | MySQL 未启动或库不存在 | 查看控制台报错信息;检查 application.yml 配置 | 先启动 MySQL,创建对应数据库并导入 SQL 脚本 |
| 前端页面白屏 | Vue 工程启动失败,或后端地址配置错误 | 打开浏览器控制台,查看 Network 请求状态 | 确认前端 api 文件中的 baseURL 与后端端口一致 |
| 跨域请求被浏览器拦截 | 后端未配置 CORS | 浏览器控制台查看 CORS 报错 | 配置 CorsConfig,允许前端地址跨域访问 |
| 登录后获取用户信息报 401 | Token 失效或未携带请求头 | 查看请求 Headers 是否包含 Authorization | 在 axios 拦截器中统一添加 Token |
| 收藏重复插入报错 | 缺少联合唯一索引 | 查看数据库表结构 | 给 t_favorite 表的 user_id + craft_id 添加联合唯一索引,并在 Service 中做前置校验 |
| 推荐结果为空 | 新用户无行为数据,或同分类下没有其他作品 | 查看数据库是否有分类为空的记录 | 推荐算法增加默认兜底策略:返回热门作品或最新上架作品 |
每一条问题都有明确的修复路径,实际操作中优先看日志。Spring Boot 项目的日志会直接打印 SQL 语句,排查数据库问题时非常有用。
9. 最佳实践与工程建议
9.1 代码层面
- 统一异常处理:建议在全局异常处理器中捕获业务异常、参数校验异常和兜底异常,避免接口偶发异常时向前端透出堆栈信息。
- 接口加注释:每个 Controller 接口都加 Swagger 注解或 JavaDoc 注释。答辩时可以直接展示 API 文档,也能提升代码评审的通过率。
- 密码加密存储:用户密码不要明文存储,使用 BCrypt 或其他哈希算法。这是安全底线,不是可选优化。
9.2 数据库层面
- 所有业务表建议加
create_time和update_time字段,审计问题时会非常方便。 - 收藏表、浏览历史表的数据量增长快,建议加索引。查询推荐数据时会用到
user_id和category_id两个维度的聚合,索引能显著提升响应速度。 - 手工艺作品的图片不要直接存 base64 到数据库,建议存图片 URL,图片文件上传到本地目录或对象存储。
9.3 毕设文档与答辩建议
毕设如果需要写论文或设计文档,可以按下面结构组织:
- 绪论:非遗文化数字化保护背景、意义;
- 相关技术综述:Spring Boot、Vue、推荐算法;
- 需求分析:包括功能需求和非功能需求;
- 系统设计:架构图、功能模块图、数据库表设计;
- 系统实现:展示核心代码和页面截图;
- 系统测试:功能测试、推荐效果分析;
- 总结与展望。
答辩时有一个技巧:不要从头到尾念 PPT。重点展示“遇到的问题”和“如何解决”。比如跨域问题、重复收藏问题、推荐兜底逻辑,这些细节比“我实现了增删改查”更能让老师记住你的项目深度。
10. 总结与后续学习方向
这个项目真正的价值在于,它用一套常见的 Spring Boot + Vue 技术栈,把“非遗平台”这个业务场景和“内容推荐算法”这个技术亮点结合了起来。对毕设而言,既能满足系统功能完整度,又能体现算法设计能力,是一个性价比很高的选题方向。
如果你已经拿到了源码,建议先别急着改功能,按顺序做这几件事:
- 把项目完整跑通一遍,记录每一步的启动过程和端口;
- 对照数据库表结构,梳理“用户-作品-行为”三条核心链路;
- 阅读推荐模块代码,理解算法流程;
- 找一个可以扩展的点进行二次开发,比如增加评论热度排序、加入相似作品推荐、增加热门榜单等。
从就业角度看,前后端分离项目的经验可以直接迁移到日常业务开发中。内容推荐算法的设计思路也可以延伸到商品推荐、资讯推荐、短视频推荐等场景。后续如果想深入,可以学习 Elasticsearch 做内容检索、Redis 做热门榜单缓存、RabbitMQ 做行为异步上报,这些都是从“毕设作品”走向“工业级项目”的必经之路。