ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

SpringBoot3+Vue3非遗交流平台毕设:协同过滤推荐全栈实战

SpringBoot3+Vue3非遗交流平台毕设:协同过滤推荐全栈实战 非遗文化交流平台这种题目在Java毕设里算是一股清流——它没有走烂大街的“商城”或“博客”套路而是把地方非遗展示、用户交流、个性化推荐三个点揉到了一起技术上有SpringBoot3Vue3的全栈实践算法上有协同过滤推荐可写论文LW里也有“算法设计系统实现”的完整链条。很多同学拿到这类题目后第一反应是“推荐算法是不是要自己手写”、“Vue3跟Vue2差别到底多大”、“协同过滤出来的结果怎么兜底”。这篇文章我就基于这个项目的完整实现过程把从选题拆解、表结构设计、算法落地到前后端联调、文档撰写的整套链路讲清楚尤其是那些代码之外、但决定项目能不能通过的细节都会一并提到。如果你正打算用这个题目做毕业设计或者已经下载了一份完整前后端代码但不知道怎么讲清楚、怎么应付老师提问这篇文章应该能给你一个从“知道怎么运行”到“知道为什么这么做”的进阶指引。下面我会按一个真实开发者的思路逐步拆解这个项目。1. 项目整体设计与思路拆解1.1 为什么选“非遗文化交流平台”这个业务场景非遗非物质文化遗产这个话题天然适合做信息展示与社区交流。从毕设角度讲它有三个优势一是文化素材丰富各地传统技艺、民俗、戏曲、美食等都有现成的图文和视频资料填充数据不愁二是用户角色清晰普通游客可以浏览、搜索、收藏注册用户可以参与交流、评论、发布动态管理员负责内容审核和用户管理角色权限很有层次三是能够自然引出推荐需求用户在浏览大量非遗项目时会有一个“猜你喜欢”的推荐模块来提升浏览深度这就给协同过滤算法提供了落地场景而不是生硬地为了算法而算法。这个平台我建议拆成两个端口用户端和管理端。用户端重点放在“内容浏览 搜索筛选 收藏评分 社区交流 个性化推荐”这五块管理端则负责非遗项目管理、用户管理、评论管理、数据统计。这样的功能划分既覆盖了普通管理系统该有的增删改查又多了一个算法落地点答辩时可以说“我的系统不是一个纯CRUD而是有核心算法支撑的推荐平台”档次立刻不一样。1.2 协同过滤算法为什么是它而不是其他推荐方式推荐算法有很多种常见的是基于内容的推荐、协同过滤推荐、混合推荐。基于内容推荐是提取物品本身的特征标签比如非遗的类别、地区、关键词然后找跟用户过去喜欢的物品特征相似的物品。这个做法实现简单但有一个明显问题内容特征的提取和相似度计算容易流于表面比如两个非遗项目都是“传统舞蹈”但风格差异很大特征标签没法完全表达。协同过滤不一样它完全依赖“人的行为”来计算相似性不需要对物品做复杂的特征建模。核心思想是“物以类聚、人以群分”——如果你和某个人都喜欢相同的非遗项目那这个人喜欢的其他项目大概率你也喜欢。这个思路在论文里能讲得很清楚也符合直觉。相比基于内容协同过滤在系统刚上线、行为数据稀少时也会遇到冷启动问题但毕设场景下我们可以通过初始化模拟数据来规避后面我会讲到具体做法。所以选择协同过滤作为核心算法不是因为它是新技术而是因为它在“数据收集—相似度计算—推荐列表生成”这条链路上最直观最适合用Java 数据库来实现也最容易在论文中画出清晰的流程图和公式推导。1.3 技术选型SpringBoot3 Vue3这步棋怎么走很多同学还在用SpringBoot2.7 Vue2的老组合这个项目直接用SpringBoot3 Vue3是很有前瞻性的。SpringBoot3有几个关键变化它基于JDK17使用了Jakarta EE命名空间包名从javax.变成了jakarta.所以如果你的代码里还有import javax.servlet那一定是旧版本的写法同时SpringBoot3内置了对GraalVM原生镜像的支持性能更好。不过要注意这些升级也带来一些兼容性问题比如旧版的MyBatis、某些第三方依赖可能不兼容后面我会给出具体的依赖版本搭配。Vue3则引入了Composition API组合式API相比Vue2的Options API逻辑复用更灵活配合script setup语法写代码的时候内心是舒畅的。项目通常还会搭配Vite构建工具、Pinia状态管理库、Vue Router路由这些组合起来前端项目的启动速度比Webpack时代的Vue2快了一个量级。前后端分离模式下SpringBoot只负责提供RESTful APIVue3通过Axios异步调用接口数据以JSON格式交互这已经是现代Web开发的标准姿势了。1.4 功能模块怎么划分才能让算法有得玩我把功能模块划分为几个层面。基础数据层用户表、非遗项目表、非遗分类表、地区表行为数据层用户收藏表、用户评分表、用户浏览记录表、评论表社区交流层动态发布表、点赞表推荐服务层基于用户行为计算协同过滤推荐结果并缓存管理后台层内容管理、用户管理、数据查看。这里要特别强调评分表的重要性。协同过滤算法需要用户对物品的“偏好分数”常见的来源包括显式评分用户直接打分和隐式反馈点赞、收藏、浏览时长。在毕设里我建议两种都用用户端提供“收藏”和“评分1-5星”两种操作算法只需基于评分数据进行计算收藏和浏览记录可以作为辅助权重。为了算法有数据可算初始化数据库时可以插入一批模拟用户和模拟评分记录比如20个用户、50个非遗项目、几百条评分这样推荐结果立刻就能出来。这个初始化数据脚本既是开发阶段的测试工具也是论文里做实验的数据集一举两得。2. 核心细节解析与实操要点2.1 数据库表设计别让关系乱成一锅粥既然是文化交流平台核心表至少要有这些user用户表id, username, password, nickname, avatar, role, create_timeheritage非遗项目表id, name, category_id, region, description, image, video_url, create_timecategory非遗分类表id, name, descriptionrating用户评分表id, user_id, heritage_id, score, create_timefavorite收藏表id, user_id, heritage_id, create_timecomment评论表id, user_id, heritage_id, content, create_timepost社区动态表id, user_id, content, images, create_timeadmin或直接在user表里用role字段区分管理员这里有一个很容易踩的坑如果在heritage表里直接放一个category字段删除分类时会造成数据不一致。正确做法是使用外键category_id关联分类表。但毕设项目为了图省事可能不做物理外键只保留逻辑关联这也不是不行但你要在代码里保证插入数据时分类存在否则会查出空数据。另外用户评分表一定要加唯一约束(user_id, heritage_id)保证同一个用户对一个项目只能有一条评分记录否则协同过滤计算时会出现重复值排序直接乱掉。我在第一次设计时漏了这个约束结果推荐列表里同一个项目出现了两次排查了半天才发现是数据重复导致相似度计算偏差。2.2 协同过滤算法落地从公式到Java代码的思维转换协同过滤分两种UserCF基于用户的协同过滤和ItemCF基于物品的协同过滤。UserCF的核心第一步是计算用户之间的相似度第二步是找到与当前用户最相似的K个用户第三步是聚合这些相似用户评分过的物品排除当前用户已评分的按加权分数排序输出推荐列表。ItemCF则先计算物品之间的相似度然后根据用户历史评分物品去找相似物品。推荐用哪个我建议用ItemCF理由是非遗项目的数量几十到几百通常远小于用户数量几千到几万物品相似度矩阵可以先计算好实时推荐时只需要查表而且非遗项目的兴趣往往是稳定的用户今天喜欢传统戏曲明天大概率还会喜欢同类别项目物品相似度的解释性也更强。而UserCF在用户数多时用户相似度矩阵的计算和维护成本很高用户兴趣漂移也会导致结果不稳定。在Java实现时不需要引入复杂的ML库手写就行。整个流程分三步从rating表中查出所有评分记录构建物品评分向量对每个非遗项目记录所有对它评过分的用户及分数计算物品之间的相似度最常用的是余弦相似度两个物品的评分向量夹角的余弦值。公式是cos (a·b) / (|a|*|b|)其中a和b分别是两个物品在“共同评过分的用户”上的评分向量。这个流程建议用Java 8的Stream流来写代码很清晰。后面我会贴出核心代码逻辑。2.3 SpringBoot3 的依赖迷局版本配错启动就哭SpringBoot3要求JDK17以上所以你的开发环境必须装JDK17。同时一些老牌的第三方库必须使用支持Jakarta EE的版本。我给大家一个亲测可用的依赖清单SpringBoot3.2.xMyBatis-Plus3.5.x注意要用适配SpringBoot3的starter比如mybatis-plus-spring-boot3-starterMySQL驱动com.mysql:mysql-connector-j旧版com.mysql:mysql-connector-java在新驱动中已经改名Lombok1.18.30以上Hutool5.8.x工具库用来处理日期等pom.xml里最容易出错的是MyBatis-Plus的starter。现在网上很多教程还在用mybatis-plus-boot-starter这玩意儿在SpringBoot3下会报错必须用mybatis-plus-spring-boot3-starter。另外一个坑是数据库连接池SpringBoot3默认使用HikariCP但配置的时候需要注意时区连接串里建议加上serverTimezoneAsia/Shanghai。如果你在项目里还要配置日志需要注意SpringBoot3默认使用Logback但很多同学想在项目里用Log4j2则需要排除spring-boot-starter-logging并引入spring-boot-starter-log4j2。这里有个容易踩的坑如果两个日志框架的依赖同时存在启动时会疯狂打印适配错误。建议直接使用默认Logback简单省事不需要额外配置log4j2。热词里有人搜“springboot3 log4j2”和“logback-spring.xml”说明这块混淆的人不少。2.4 Vue3 前端架构组合式API Vite Pinia一步到位前端项目的核心依赖我建议这样搭Vue 3.4 Vite 5.x构建工具Vue Router 4路由Pinia状态管理Element PlusUI组件库AxiosHTTP请求库Vite创建项目命令npm create vitelatest heritage-web -- --template vue然后进入目录执行npm install再依次安装依赖npm install vue-router4 pinia axios element-plus。项目结构上我会把代码放在src/views页面、src/components组件、src/api接口封装、src/store状态这样几个目录。有一点要提醒Element Plus的国际化配置和按需引入。如果直接全量引入虽然简单但打包体积大如果按需引入需要安装unplugin-auto-import和unplugin-vue-components。毕设项目一般建议全量引入减少配置出错概率老师也不会因为你多打包几百KB扣分。Vue3中很重要的一个点是script setup语法编写组件时变量和方法在模板里直接用不需要像Vue2那样写在data和methods里代码量会缩水很多。Axios封装时要注意拦截器的使用。统一在请求拦截器中把token加到header里响应拦截器中统一处理后端返回的状态码比如401跳转登录页。前端展示推荐结果只需要在页面onMounted里调用后端接口然后把返回的推荐项目列表渲染成卡片网格就行。3. 实操过程与核心环节实现3.1 开发环境搭建少走弯路的版本清单这一步不能偷懒我按实际项目开发顺序列出完整的环境清单JDK安装JDK 17推荐用Temurin或Alibaba Dragonwell配置JAVA_HOME环境变量。IDEA2023.x版本安装Lombok插件新版IDEA已内置。Node.js18.x以上npm会同步安装。MySQL8.0.x创建数据库heritage_db字符集选utf8mb4。Redis可选如果你的项目要把推荐结果缓存起来建议用RedisSpringBoot3中直接用spring-boot-starter-data-redis。如果不搞缓存项目也可以跑但每次推荐都实时计算数据量大一点就会卡。后端项目创建步骤使用Spring Initializr生成SpringBoot3项目依赖选Web、MySQL Driver、Validation然后手动添加MyBatis-Plus依赖。前端项目使用Vite创建按2.4节步骤操作。前后端开发时我习惯在sprintboot的application.yml中配置端口为8080前端Vite开发服务器端口为5173通过Axios代理解决跨域。3.2 后端核心代码示例协同过滤推荐服务下面这个是简化且能跑的核心代码。我理解你可能会想要完整代码但这里我更希望讲清楚算法逻辑以便你能在自己的代码里改出特色。物品相似度计算ItemCFpublic class ItemCF { // 计算物品相似度矩阵 public MapLong, MapLong, Double calcItemSimilarities(ListRating ratings) { // itemVector: itemId - (userId - score) MapLong, MapLong, Double itemVector new HashMap(); for (Rating r : ratings) { itemVector .computeIfAbsent(r.getHeritageId(), k - new HashMap()) .put(r.getUserId(), r.getScore()); } ListLong itemIds new ArrayList(itemVector.keySet()); MapLong, MapLong, Double similarityMatrix new HashMap(); for (int i 0; i itemIds.size(); i) { Long itemA itemIds.get(i); for (int j i 1; j itemIds.size(); j) { Long itemB itemIds.get(j); double similarity cosine(itemVector.get(itemA), itemVector.get(itemB)); if (similarity 0) { similarityMatrix .computeIfAbsent(itemA, k - new HashMap()) .put(itemB, similarity); similarityMatrix .computeIfAbsent(itemB, k - new HashMap()) .put(itemA, similarity); } } } return similarityMatrix; } private double cosine(MapLong, Double vecA, MapLong, Double vecB) { SetLong common new HashSet(vecA.keySet()); common.retainAll(vecB.keySet()); if (common.isEmpty()) { return 0.0; } double dot 0, normA 0, normB 0; for (Map.EntryLong, Double e : vecA.entrySet()) { normA e.getValue() * e.getValue(); if (common.contains(e.getKey())) { dot e.getValue() * vecB.get(e.getKey()); } } for (Double val : vecB.values()) { normB val * val; } if (normA 0 || normB 0) { return 0.0; } return dot / (Math.sqrt(normA) * Math.sqrt(normB)); } }生成推荐列表public ListHeritage recommendForUser(Long userId, int topN) { ListRating myRatings ratingMapper.selectByUserId(userId); SetLong ratedIds myRatings.stream() .map(Rating::getHeritageId) .collect(Collectors.toSet()); // 获取与用户历史评分物品最相似的物品按相似度加权 MapLong, Double scoreMap new HashMap(); for (Rating r : myRatings) { Long itemId r.getHeritageId(); double userScore r.getScore(); MapLong, Double simMap similarityMatrix.get(itemId); if (simMap null) { continue; } for (Map.EntryLong, Double e : simMap.entrySet()) { Long candId e.getKey(); if (ratedIds.contains(candId)) { continue; } double weight e.getValue() * userScore; scoreMap.merge(candId, weight, Double::sum); } } return scoreMap.entrySet().stream() .sorted(Map.Entry.Long, DoublecomparingByValue().reversed()) .limit(topN) .map(e - heritageMapper.selectById(e.getKey())) .collect(Collectors.toList()); }注意这里把用户评分作为权重的思路在论文里可以解释为“对用户喜欢程度越高的物品它相似物品的推荐参考价值越大”。如果你想严格套公式推荐分数可以表达为pred(u,i) sum( s(i,j) * r(u,j) ) / sum( s(i,j) )这样可以抵消不同用户评分习惯的影响但代码稍微复杂一点。毕设论文里写这个公式后代码里实现它就会很加分。3.3 前端核心代码示例推荐页面的展示逻辑在Vue3中推荐页面大致是这样一个流程登录后页面加载时请求/api/recommend?userIdxx拿到推荐项目列表渲染卡片。一个简单的script setup写法如下template div classrecommend-list el-row :gutter20 el-col :span6 v-foritem in recommendList :keyitem.id el-card :body-style{ padding: 0px } classitem-card img :srcitem.image classimage / div classinfo span{{ item.name }}/span div classcategory{{ item.categoryName }}/div /div /el-card /el-col /el-row /div /template script setup import { ref, onMounted } from vue import request from /api/request const recommendList ref([]) const userId localStorage.getItem(userId) const loadRecommend async () { const res await request.get(/recommend?userId${userId}topN8) if (res.data.code 200) { recommendList.value res.data.data } } onMounted(loadRecommend) /scriptAxios封装时记得设置baseURL为/api然后在Vite配置里加代理// vite.config.js export default defineConfig({ server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true, rewrite: (path) path.replace(/^\/api/, ) } } } })这个代理配置能解决跨域问题。如果不使用代理前端页面请求http://localhost:8080后端必须在WebConfig里配置CORS跨域允许否则浏览器会因为同源策略拦截响应。代理方式更简单推荐。3.4 调试与测试把推荐结果调到像样项目跑起来之后第一件事不是写论文而是测推荐结果是否合理。我建议准备一组测试用例假设用户A只给“京剧”和“昆曲”打了五星给“越剧”打了三星那么推荐列表里大概率要出现“黄梅戏”这种同类别的传统戏曲而不会出现“京剧”本身因为已经评分过。如果推荐结果出现“美食”或“杂技”类项目说明相似度计算有问题多半是评分数据中共同评分的用户太少或者物品向量构建错了。调推荐结果时有几个常用参数可以调topN推荐数量、相似用户或相似物品的截断数K。K值太大推荐结果会比较泛K值太小推荐结果会局限。毕设场景中K取10~20比较合适。另外如果用户只有一两条评分记录推荐结果可能比较差这时可以做一个兜底策略当用户行为数据太少或相似度计算出的候选物品少于N个时直接推荐当前用户未浏览过的、点击量最高的项目。这个策略在论文里写清楚老师会觉得你考虑全面。4. 常见问题与排查技巧实录4.1 前后端分离的CORS跨域问题前端启动后控制台报错“Access to XMLHttpRequest at ... from origin ... has been blocked by CORS policy”。这个问题的原因很直接浏览器安全策略不允许不同源协议、域名、端口任一不同的页面随意请求资源。前后端分别是localhost:5173和localhost:8080端口不同属于跨域。解决方案有三种一是后端加CrossOrigin注解懒人做法但不推荐写在Controller上二是写一个WebMvcConfigurer配置全局CORS映射三是前端使用Vite代理也就是我前面提到的方式。最推荐代理方案因为它把跨域处理放在开发服务器层面生产部署时Nginx也可以做同样的反向代理思路一致。4.2 协同过滤中评分矩阵稀疏怎么办真实系统里用户数量大、物品数量大但每个用户只对很少的物品评分导致矩阵中绝大多数位置是空值这叫稀疏问题。稀疏的直接后果是物品之间共同评分的用户太少余弦相似度无法计算算出来也不可靠。毕设项目的数据量小但如果你只用默认的几条数据也会有这个问题。一个简单有效的做法是“填充默认分”。对于那些在一组物品中都只被极少数用户评过分的可以放弃计算只保留至少被两个以上用户共同评分过的物品对。另外可以引入“隐式反馈”来增加信号——用户收藏过未评分的项目那隐式给一个4分比如收藏视为4分浏览视为2分。这样矩阵的密度就会提高。冷启动问题也可以这样处理新注册用户没有任何行为记录协同过滤无法给Ta推荐那就走热门推荐按评分数量或平均分倒序返回排行榜。新上架的非遗项目没有评分无法参与协同过滤那就按创建时间和浏览量做推荐。把这些规则写到推荐服务里做成一个多策略推荐器前端不用改代码。4.3 为什么我的SpringBoot3项目离不开Tomcat却报错还有一种常见情况SpringBoot3项目运行后发现访问任何接口都没有响应查看日志才发现Tomcat没有启动成功。这通常是端口被占用。用命令行netstat -ano | findstr 8080查看PID然后杀掉进程或者修改application.yml里的server.port。不过要注意前端代理里的target端口也要同步修改两边不一致就会一直404。4.4 项目交付前一定要做的几件事很多同学代码写完运行正常但交付时被老师挑毛病。根据我辅导过的经验最容易忽略的是这些点数据库初始化脚本要单独放一份SQL文件不能让老师自己去数据库里手动建表。同时要附上模拟数据让老师导入后就能看到效果。接口文档要写好。最简单的方式是用SwaggerSpringDocSpringBoot3对应的是springdoc-openapi-starter-webmvc-ui配置后访问/swagger-ui.html就能看到所有接口。这样老师测试时不用一个个问你要接口名。说明文档LW里必须包含项目运行步骤从安装JDK到启动前端、后端每一步写清楚。老师最怕那种连环境都起不来的毕设。另外论文里要有一张系统架构图和一张算法流程图可以从项目文档中引用过来。答辩前自己演示一遍推荐功能登录一个模拟用户账号查看推荐列表点击推荐项目再回到推荐页看推荐是否变化。这个过程能提前发现“推荐不变”的bug。通常是因为后端把推荐结果缓存死了没有在用户行为变化后更新。4.5 论文/LW写作算法章节怎么写出深度标题里的“LW”一般指论文或设计文档。这一块很多同学容易写成“项目介绍”流水账这是不对的。要拿高分算法章节至少要有这几部分问题描述用数学语言定义“输入—输出”——输入是所有用户的评分数据输出是当前用户的TOP-N推荐列表。算法流程画出ItemCF的步骤图一步步说明数据在哪里读取、相似度矩阵怎么构建、推荐分数怎么计算。案例演示手工计算两三个项目的相似度展示余弦相似度的数值过程。这个案例建议直接从数据库里选两个真实项目用它们的评分数据算一遍结果截图放进论文。评估实验既然有推荐算法最好能做一个简单离线实验——把评分数据按8:2分成训练集和测试集用训练集生成推荐用测试集计算PrecisionN或Coverage等指标。不需要做到100%严谨但思路要有这会让论文的“含金量”立刻提升。我曾经帮一个学弟做过上述评估他选了30个用户和50个项目只用了不到100行代码就完成了数据划分和TopN命中统计。在论文里列出一张“不同K值下的推荐准确率”表格老师看了就说“这个项目有实际研究内容”。写在最后这个项目还能怎么扩展如果你做完基础功能还有时间我建议做三个方向的升级任何一个拿出来都能让答辩眼前一亮。第一是加入“基于用户的协同过滤”做双通道组合推荐前端展示两个推荐列表一个是“可能是因为你喜欢的非遗类型”另一个是“和你兴趣相似的人也在关注”这样算法从一套变两套论文对比实验更好写。第二是在非遗详情页加入“相关项目推荐”模块利用ItemCF计算当前项目最相似的几个项目展示出来实现成本极低但效果很明显。第三是加一个简单的数据统计大屏展示非遗项目总数、用户行为热度榜、分类占比饼图这能撑起系统的视觉和完成度。我个人在实际操作中的体会是这类全栈毕设项目最大的难点不是代码本身而是“把代码讲成故事”。你下载来的完整代码也好自己写的也好都要能在半小时内让别人导师、答辩老师听明白你做了什么、用了什么技术、解决了什么问题。协同过滤算法的推荐结果也要能说得清楚不要只说“系统推荐了这些”而是要说“因为用户A喜欢京剧和昆曲系统通过协同过滤发现喜欢黄梅戏的用户与A有相似的评分模式所以给他推荐了黄梅戏”。能讲到这个颗粒度你的项目就已经成功了。最后分享一个调试小技巧在你写完协同过滤后别急着接前端先用一个简单的控制台测试类跑一遍推荐逻辑把相似度矩阵和推荐分数打印出来肉眼检查一遍再推进。很多同学把推荐逻辑写进Service里结果前端调不起来都不知道是接口问题还是算法bug。分开测试能帮你少走至少半天弯路。
返回列表