ARTICLE DETAIL

资讯详情

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

基于Spring Boot与协同过滤的线上安全教育平台设计与实现

基于Spring Boot与协同过滤的线上安全教育平台设计与实现 又到一年毕设季后台私信里被问最多的就是“Java毕设做什么题目好”。说实话每年看那么多题目大部分要么太玩具、要么太陈旧。今天想认真聊一个我认为很适合用来作为Java Web方向毕业设计的项目——基于Spring Boot 协同过滤课程推荐的线上安全教育平台。这个题目的关键词是Java、Spring Boot、协同过滤算法、课程推荐和完整源码。它不是那种为了“毕业”而凑出来的CRUD而是把当下企业里最常用的后端技术栈、推荐系统里最经典的算法、以及一个真实存在的垂直业务场景安全教育结合在了一起无论你是想平稳毕业还是想在简历上多一个能打的亮点这个题目都相当合适。为什么这么说因为它在“工程量”和“技术深度”之间找到了一个比较好的平衡点。你说它难它不涉及分布式、不涉及高并发一个单体应用加一个MySQL完全能扛住你说它简单它又包含了用户体系、课程内容管理、在线考试、学习记录追踪还有一个需要你真正讲清楚原理的协同过滤推荐引擎。这样的组合在毕业设计里属于典型的“性价比高”选手。这篇内容我会把这个项目从选题逻辑到核心代码实现、再到调试部署的完整思路都拆开讲一遍。会写清楚协同过滤到底怎么落地、Spring Boot 的项目结构怎么规划、安全认证怎么做、以及那些在网上很难搜到的、只有实际动手才会踩的坑。如果你正打算做这个题目或者手里已经拿到了一份相关源码但不知道怎么讲清楚这篇文章能帮你省下不少时间。1. 项目整体设计与思路拆解1.1 安全教育平台为什么需要推荐系统先聊一个最容易被导师问住的问题一个安全教育平台为什么非要加协同过滤如果你答不好这个开题答辩那一关就有点悬。很多同学会习惯性地把“推荐系统”当成一个时髦的装饰品觉得毕设里加了算法就显得高级。但放在这个项目里推荐系统其实有非常实际的需求逻辑。一个面向企业或学校的线上安全教育平台课程数量很容易积累到几百甚至上千门涵盖消防安全、生产安全、交通安全、网络安全、急救知识等不同类别。用户不可能把每门课都学一遍每个岗位、每种角色需要的安全知识侧重点也完全不同。但问题来了平台在用户刚注册的时候手里掌握的只有用户的性别、部门、岗位这类静态信息根本没有足够的历史行为数据来做精准推荐。这其实就是业内常说的冷启动问题。所以这个项目里推荐系统不是单一算法而是一个组合策略新用户靠画像冷启动规则推荐老用户靠协同过滤做个性化推荐热门课程兜底保证用户永远有课可学。这套逻辑答出来导师会觉得你是真的理解了这个设计而不是为了用算法而用算法。1.2 技术选型背后的核心考量再来看技术栈。标题里点名了 Spring Boot这个选型本身没什么悬念它就是目前Java后端绝对的主流企业里在用网上资料多到看不完遇到任何问题都能搜到解决方案。但有几个细节值得展开说。第一JDK 版本怎么选。我见过不少同学一上来就装最新的JDK 17甚至21结果Spring Boot版本和各种依赖库之间一堆兼容性问题光调环境就花了一周。做毕设求的就是稳。主流教程和网上能搜到的源码大多基于JDK 8所以建议直接用JDK 8 Spring Boot 2.7.x 这个组合。JDK 8 依然是很多企业的生产版本面试官看着不会觉得突兀而且Lombok、MyBatis-Plus、Spring Security 这些配套组件在JDK 8下生态最成熟、问题最少。注意不是越新越好而是越稳越好。毕设期间时间宝贵别在环境兼容性上浪费时间。第二前端方案。这个项目的管理后台和用户端推荐用 Vue 做前后端分离。前后端分离已经是行业标准做法能在项目介绍里多一个亮点而且Vue的生态组件库Element UI对后端同学特别友好不用从零手写样式。如果对前端实在不熟用服务端渲染模板 Bootstrap 也能做但整体观感会差一些答辩时的冲击力不一样。第三数据库和ORM。数据库用 MySQL这个没有争议。持久层框架推荐 MyBatis-Plus 而不是纯 MyBatis。原因很简单单表CRUD占了至少60%MyBatis-Plus 的 BaseMapper 直接帮你把这些代码全部省掉你只需要关心多表联查和复杂业务SQL。省下来的时间用来啃逻辑更复杂的推荐算法模块性价比高得多。1.3 功能模块划分与数据流转设计整个平台按用户角色可以拆成三个端。管理员端负责课程分类管理、课程上下架、用户管理、统计报表。教师/安全专员端负责上传课程资料视频、文档、出题、批阅考试。普通用户端负责浏览课程、搜索课程、选课学习、完成课后考试、查看学习记录和证书。模块拆完之后核心的数据流转链路是这样的用户产生学习行为观看视频、完成考试→ 行为数据写入学习记录表 → 推荐模块定时或实时读取行为数据 → 构建“用户-课程”评分矩阵 → 协同过滤算法计算相似度 → 生成个性化推荐列表 → 存入推荐结果表 → 用户打开首页时查询推荐位展示。这套链路里每个环节都有对应的数据表和业务逻辑逻辑非常清晰写进论文里也是一条完整的主线。2. 协同过滤推荐算法的工程化落地2.1 从生活场景理解协同过滤协同过滤这个名词听起来高大上但底层的直觉其实特别朴素。想象一下你有一个口味很接近的朋友你觉得好吃的火锅店他大概率也喜欢反过来他最近新发现的一家川菜馆你盲去踩雷的概率也比随便在点评软件上找要低得多。协同过滤的思路就是把你那个“口味相近的朋友”换成了一群和你行为模式相似的陌生人然后把这群人的选择推荐给你。放在这个平台里用户A和用户B都学过《有限空间作业安全》和《动火作业规范》而且都给了高分、都完成了考试系统就认为这两个用户兴趣相似。这时候用户B又学了《危险化学品管理》而A没学过系统就把这门课推荐给A。这就是基于用户的协同过滤User-Based CF最直观的理解。还有另一种角度用户A学过《消防安全培训》并且评分不错系统发现《初期火灾扑救技巧》这门课和它经常被同一批人学习关联度很高于是也推荐给A。这是基于物品的协同过滤Item-Based CF。两种思路在这个项目里可以根据场景灵活选或者做成可切换。2.2 打分数据的获取与矩阵构建推荐系统里有句老话数据决定了效果的上限算法只是逼近这个上限。对毕设项目来说评分数据从哪来是一个必须先想清楚的问题。最直接的方案是用户显式评分即在课程页面让用户点星星或给“满意/不满意”按钮。但现实情况是绝大多数用户没有打分习惯数据会非常稀疏。所以在实现里更合适的做法是引入隐式评分把用户观看视频的时长比例、是否完成课后考试、考试成绩、是否收藏课程、是否评论等因素综合折算成一个评分值。这里分享一个我常用的评分计算公式逻辑简单但效果不错/** * 隐式评分计算 * param watchRatio 视频观看时长比例范围0.0~1.0 * param examScore 考试成绩百分制 * param hasFavorite 是否收藏 * param hasComment 是否评论 * param hasCompleted 是否完成课程1完成0未完成 */ public double calcImplicitScore(double watchRatio, double examScore, int hasFavorite, int hasComment, int hasCompleted) { // 观看比例低于20%视为无效学习不计入推荐计算 if (watchRatio 0.2) { return 0.0; } // 观看比例得分占50%的权重 double score watchRatio * 5.0; // 考试成绩加成分考得好说明课程吸收好最高加2分 score (examScore / 100.0) * 2.0; // 收藏和评论属于高意向行为各加0.5分 score hasFavorite * 0.5 hasComment * 0.5; // 完成课程说明用户对整门课认可度高加1分 score hasCompleted * 1.0; // 理论最高9分做一个归一化映射到1~5分的评分区间 return Math.max(1.0, score / 9.0 * 5.0); }就这套代码面试官要是问起来你完全能讲清楚每个打分项的权重设置理由。这就叫“可解释的代码”比直接扔一个别人写的复杂算法模型然后自己讲不明白要强得多。评分算出来之后最终要整理成一张二维矩阵行是用户ID列是课程ID交叉点的值就是评分。对Java来说最方便的实现方式是MapLong, MapLong, Double外层key是用户ID内层key是课程IDvalue是评分。如果用户之间没有共同课程矩阵里就没有对应数据这也正是真实推荐场景里最常见的稀疏问题。2.3 皮尔逊相关系数与相似度计算实战协同过滤算法里最核心的计算步骤就是算用户之间的相似度。相似度算法有好几种比如余弦相似度、杰卡德相似系数、皮尔逊相关系数。毕设里推荐用皮尔逊相关系数因为它能处理用户打分标准不一致的问题——有些人天生打分偏高有些人偏保守皮尔逊通过减去各自评分的平均值能把这种系统性偏差消掉更真实地反映用户间的相似程度。公式长这样r Σ((xᵢ - x̄)(yᵢ - ȳ)) / √(Σ(xᵢ - x̄)² · Σ(yᵢ - ȳ)²)其中xᵢ是用户A对第i门共同课程的打分x̄是用户A对所有共同课程打分的平均值yᵢ和ȳ同理。我整理了一个可以直接用的实现/** * 计算两个用户间的皮尔逊相关系数 * param user1Ratings 用户1的课程评分Map * param user2Ratings 用户2的课程评分Map * return 相关系数范围[-1, 1]越接近1表示越相似 */ public double pearsonCorrelation(MapLong, Double user1Ratings, MapLong, Double user2Ratings) { // 找出两个用户共同评过分的课程 SetLong commonCourses new HashSet(user1Ratings.keySet()); commonCourses.retainAll(user2Ratings.keySet()); // 共同课程太少时相似度没有统计意义直接返回0 if (commonCourses.size() 2) { return 0.0; } double sumX 0, sumY 0; double sumXY 0, sumX2 0, sumY2 0; for (Long courseId : commonCourses) { double x user1Ratings.get(courseId); double y user2Ratings.get(courseId); sumX x; sumY y; sumXY x * y; sumX2 x * x; sumY2 y * y; } int n commonCourses.size(); double numerator sumXY - (sumX * sumY) / n; double denominator Math.sqrt((sumX2 - sumX * sumX / n) * (sumY2 - sumY * sumY / n)); // 分母为0说明评分完全一致也是一种高相似度的表现 if (denominator 0.0) { return 1.0; } return numerator / denominator; }注意我特意加上了“共同课程数小于2时直接返回0”的判断。这个是实战中很重要的细节——如果两个用户只有一门共同课程不管评分有多接近都不能说明他们真的相似属于偶然性太强的情况。不加这个判断很容易推荐出一堆莫名其妙的结果。2.4 生成推荐列表的完整流程相似度算完接下来的推荐流程就顺理成章了。整体分三步走第一步遍历所有用户计算目标用户和其他每个用户的相似度取相似度最高的Top-N比如10个用户作为“邻居集合”。第二步把这N个用户学过的、但目标用户还没学过的课程全部提取出来作为候选课程集合。第三步对每一门候选课程用下面这个公式计算预测评分prediction userAvg Σ(sim(u, v) * (rating(v, i) - vAvg)) / Σ|sim(u, v)|其中userAvg是目标用户的历史平均评分sim(u, v)是目标用户u和邻居v的相似度rating(v, i)是邻居v对课程i的评分vAvg是邻居v自己的平均评分。加上userAvg作为基准值是为了修正不同用户打分的整体偏移问题。最后按预测分从高到低排列取前K个比如6到10个作为最终推荐结果。这一步用代码实现虽然有一百行左右但逻辑非常清晰核心代码我直接贴出来供参考/** * 基于用户的协同过滤推荐 * param targetUserId 目标用户 * param allUserRatings 所有用户的课程评分数据 * param topNNeighbors 取多少个相似用户作为邻居 * param topKRecommendations 最终推荐多少门课程 */ public ListCourseRecommendDTO recommend(Long targetUserId, MapLong, MapLong, Double allUserRatings, int topNNeighbors, int topKRecommendations) { MapLong, Double targetRatings allUserRatings.get(targetUserId); if (targetRatings null || targetRatings.isEmpty()) { return Collections.emptyList(); } double targetAvg targetRatings.values().stream() .mapToDouble(Double::doubleValue).average().orElse(0.0); // 1. 计算目标用户与其他所有用户的相似度 MapLong, Double simMap new HashMap(); for (Map.EntryLong, MapLong, Double entry : allUserRatings.entrySet()) { Long otherUserId entry.getKey(); if (otherUserId.equals(targetUserId)) { continue; } double sim pearsonCorrelation(targetRatings, entry.getValue()); if (sim 0.3) { // 相似度低于阈值的直接丢弃减少噪声 simMap.put(otherUserId, sim); } } // 2. 取Top N个相似用户作为邻居 ListMap.EntryLong, Double topNeighbors simMap.entrySet().stream() .sorted((a, b) - Double.compare(b.getValue(), a.getValue())) .limit(topNNeighbors) .collect(Collectors.toList()); // 3. 邻居评分数据按课程聚合做加权预测 MapLong, Double scoreMap new HashMap(); MapLong, Double weightMap new HashMap(); for (Map.EntryLong, Double neighbor : topNeighbors) { Long neighborId neighbor.getKey(); double sim neighbor.getValue(); MapLong, Double neighborRatings allUserRatings.get(neighborId); double neighborAvg neighborRatings.values().stream() .mapToDouble(Double::doubleValue).average().orElse(0.0); for (Map.EntryLong, Double courseRating : neighborRatings.entrySet()) { Long courseId courseRating.getKey(); // 跳过目标用户已经学习过的课程 if (targetRatings.containsKey(courseId)) { continue; } // 减去邻居自己的平均分消除打分偏差 double weightedScore sim * (courseRating.getValue() - neighborAvg); scoreMap.merge(courseId, weightedScore, Double::sum); weightMap.merge(courseId, Math.abs(sim), Double::sum); } } // 4. 最终预测分 目标用户平均分 加权偏差 / 权重和 ListCourseRecommendDTO result new ArrayList(); for (Long courseId : scoreMap.keySet()) { double predictedScore targetAvg scoreMap.get(courseId) / weightMap.get(courseId); result.add(new CourseRecommendDTO(courseId, predictedScore)); } result.sort((a, b) - Double.compare(b.getPredictedScore(), a.getPredictedScore())); return result.stream().limit(topKRecommendations).collect(Collectors.toList()); }这段代码最大的优点是每一步都能在论文里画出对应的公式和图表。答辩的时候把代码和公式、流程图对应起来讲导师对你的印象会完全不一样。心得协同过滤的代码量其实不大核心逻辑就是“相似度计算 邻居筛选 加权预测”三段式。真正花时间的反而是评分数据的整理和清洗这也是很多开源项目里看不见的隐形工作。2.5 冷启动问题的兜底方案很多做过推荐系统的同学都会告诉你冷启动问题才是真正磨人的地方。新用户没有任何行为数据协同过滤算法直接拿不到评分向量这时候如果不做兜底用户打开首页就会看到一片空白推荐位体验很差。我的做法是三重兜底方案。第一层基于用户画像的规则推荐。新用户注册时填了部门和岗位信息比如“生产部-车间操作工”系统就优先推荐“生产安全”、“机械安全”、“职业健康防护”这个类目下的课程。这个逻辑很简单但很实用。/** * 新用户冷启动推荐 * 根据用户岗位信息映射到课程分类优先取热度高的课程 */ public ListCourse recommendToNewUser(User user, int limit) { // 岗位与课程分类的映射关系初始化时加载到缓存 ListLong categoryIds positionCategoryMapping.get(user.getPosition()); if (categoryIds null || categoryIds.isEmpty()) { // 映射不到就用热门课程兜底 return courseMapper.selectHotCourses(limit); } // 按课程平均评分和选课人数排序取TopK return courseMapper.selectByCategoriesOrderByHot(categoryIds, limit); }第二层热门课程兜底。平台首页保留一个“热门课程”模块按选课人数、最近7天学习人数、评分人数综合排序。永远有一批数据展示在用户面前保证页面不空。第三层行为触发后的渐进式推荐。当新用户完成一门课程的学习或者考试之后立即根据他学习的这门课所属分类给他扩展推荐同分类下的其他课程。这个动作相当于把用户“拉入”了协同过滤的轨道之后逐步平滑切换到协同过滤为主导的推荐策略。这三层方案写进论文里正好是解决推荐系统冷启动问题的一个完整章节含金量非常高。3. 核心业务模块与Spring Boot工程实践3.1 项目目录结构与包设计拿到一份源码先别急着跑起来先看它的包结构规划得好不好。一个结构清晰的工程逻辑上就已经赢了。这是我推荐的包结构com.safety.education ├── controller # 控制层接口入口 │ ├── admin # 管理端接口 │ ├── teacher # 教师端接口 │ └── user # 用户端接口 ├── service # 业务逻辑层 │ ├── impl │ ├── course # 课程相关服务 │ ├── exam # 考试相关服务 │ ├── recommend # 推荐相关服务协同过滤核心 │ └── user # 用户相关服务 ├── mapper # MyBatis-Plus数据访问层 ├── entity # 数据库实体类 ├── dto # 数据传输对象接口入参出参 ├── config # 配置类如跨域、拦截器、异常处理 ├── common # 通用工具类、常量、统一返回结果封装 ├── security # Spring Security认证授权相关 └── recommend # 推荐算法独立模块方便写单元测试特别说明一下recommend模块为什么要独立出来。因为推荐算法是纯Java逻辑不依赖Spring容器也能运行独立出来之后你可以脱离Web环境写单元测试直接给算法喂测试数据验证结果。这对你调试和论文里的测试章节都很有帮助。3.2 Spring Security JWT 认证设计毕设项目里安全认证环节经常被忽略但这个项目面向的是安全教育平台涉及用户的学习记录、考试成绩等隐私数据认证授权是必须认真做的。现在主流方案是 Spring Security JWT 无状态认证。核心思想是用户登录成功后服务端生成一个签名的Token返回给前端前端后续每次请求都在Header里带着这个Token服务端解析验证身份完全不需要在Session里存登录状态。这个方案天然适合前后端分离架构。关键流程拆成四步登录接口接收用户名密码调用AuthenticationManager校验成功后用 JWT 工具类生成Token返回给前端。Token里可以包含用户ID、用户名、角色等基本信息。JWT工具类负责生成和解析Token。这里要重点说明一下密钥Secret的设置建议至少32位以上太短容易被暴力破解。另外要设置过期时间一般设置为2小时太长了不安全太短了用户老是要重新登录。过滤器继承OncePerRequestFilter从请求头获取Token调用JWT工具类解析如果能正常解析就说明用户已登录把用户信息放入SecurityContextHolder。权限控制在Controller方法上使用PreAuthorize(hasRole(ADMIN))注解比如管理员删除课程的接口只有ADMIN角色能调用普通用户调用会返回403。/** * 用户登录接口 */ PostMapping(/api/auth/login) public ResultString login(RequestBody LoginRequest loginRequest) { // 使用AuthenticationManager进行认证 UsernamePasswordAuthenticationToken authToken new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()); Authentication authentication authenticationManager.authenticate(authToken); // 认证成功生成JWT令牌 LoginUser loginUser (LoginUser) authentication.getPrincipal(); String token jwtUtils.generateToken(loginUser); return Result.success(token); }注意做这个模块的时候网上很多教程用的是Spring Security的旧版配置方式定义一大堆配置类去继承WebSecurityConfigurerAdapter这个类在Spring Security 5.7之后已经被标记为废弃。建议直接使用SecurityFilterChain的方式这也是新版主流写法面试聊起来还能显得你跟得上技术迭代。3.3 课程管理模块的数据模型设计课程模块是整个平台的基础表结构设计合理与否直接决定后面的开发效率。我给出的核心表设计如下课程分类表category_id主键category_name分类名parent_id父分类ID支持树形结构比如“生产安全”下面可以挂“机械安全”、“电气安全”、“危化品安全”sort_order排序字段。课程信息表course_id主键category_id分类ID外键course_title课程标题course_cover课程封面图URLdifficulty难度等级入门/初级/中级/高级duration预估学时course_desc课程简介is_publish上下架状态view_count浏览数study_count选课人数。课程章节表chapter_id主键course_id所属课程chapter_title章节标题video_url视频地址duration视频时长sort_order排序。用户学习记录表record_id主键user_id用户IDcourse_id课程IDchapter_id当前学习章节watch_duration观看视频总时长秒is_finished是否学完finish_time学完时间last_study_time最近学习时间。这张表是整个推荐系统的数据来源每一次学习行为都要实时写入。这里展示一个“课程详情返回给前端”的VO类设计技巧。课程详情接口返回的数据不只是课程表里的字段还要带上学习人数、评分、当前用户是否已选课、进度等信息。直接在Entity上扩展字段会让实体类变得很乱正确的做法是新建一个CourseDetailVO对象组合需要的字段。这个表设计里有几个容易被忽视的细节学习记录要建联合索引(user_id, course_id)因为推荐算法要频繁按用户查学习记录没有索引数据量一上来就会很慢。课程表要建is_publish索引因为首页推荐、课程列表查询都要频繁过滤已发布课程。视频地址字段建议用VARCHAR(500)因为云存储的URL通常都特别长255不够用。3.4 在线考试与成绩管理安全教育平台的课程通常配套考试学完要考核成绩要留档。这个模块看着简单但涉及几个核心业务点。题库设计上每门课程关联一套试卷试卷由若干题目组成。题目表question包含question_type单选/多选/判断、question_content题干、options_json选项JSON直接用JSON存选项最方便不用拆多张表、correct_answer正确答案。考生交卷后后端逐题比对答案计算得分例如单选题判断题答对得分多选题必须完全匹配才算对选多选少都不得分。考试成绩写入exam_record表同时作为隐式评分的一个重要参考因子。这个模块还有一个值得写入论文的小功能错题记录。把用户答错的题记录到wrong_question表用户之后可以在个人中心复习错题。这种功能开发成本不高但做出来体验很好答辩演示的时候点一下就能展示能直观地体现产品的完整体验。3.5 系统自带的模拟数据与演示账号毕设项目演示环节最尴尬的事情是什么就是打开系统之后一片空白然后你当场开始手忙脚乱地注册账号、上传数据、录入课程。为了避免这个状况项目里一定要内置一套完整的模拟数据生成器。我的做法是写一个DataInitializer类实现CommandLineRunner接口在Spring Boot启动时自动执行。它会读取配置项app.init-data-enabled默认值设为true开发环境下自动生成10个模拟用户角色覆盖管理员、教师、普通用户20门课程分布在5个分类下面每门课有3到5个章节为每个模拟用户生成30到80条不等的学习记录随机打乱分布模拟真实行为每门课配一套考试题单选10道、多选5道、判断5道模拟数据造完后协同过滤算法马上就有了可用的数据源你一启动系统就能看到推荐效果。演示的时候直接拿模拟账号登录首页就能看到推荐的课程。这个体验比现场造数据好一百倍。4. 调试部署实战与常见问题排查4.1 本地开发环境准备与版本匹配先把环境稳稳地搭起来。我推荐的最低配置是JDK 8、Maven 3.6、MySQL 5.7或8.0、Node.js 14前端部分。Spring Boot 版本建议用 2.7.x这个版本是2.x分支的最后一个稳定版本兼容性和资料丰富度都很理想。如果官网下载慢Maven 仓库可以直接用阿里云镜像配置在~/.m2/settings.xml里实测提速非常明显。mirror idaliyunmaven/id mirrorOfcentral/mirrorOf name阿里云公共仓库/name urlhttps://maven.aliyun.com/repository/public/url /mirror4.2 数据库初始化和项目启动拿到源码之后第一步不是急着启动而是先看项目里的SQL脚本。一般规范的源码都会在doc/sql/目录下放完整的建库建表语句连同初始化的分类数据和模拟数据一起执行。执行的时候要注意顺序先建库再建表最后执行数据初始化脚本。然后修改application.yml里的数据库连接配置spring: datasource: url: jdbc:mysql://localhost:3306/safety_education?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: yourpassword driver-class-name: com.mysql.cj.jdbc.Driver启动Spring Boot 主类看到 “Started SafetyEducationApplication” 日志说明后端启动成功。然后启动前端执行npm install安装依赖再执行npm run dev启动开发服务器。前后端联调的时候最常见的坑是跨域问题。你前端跑在8080端口后端跑在8081端口浏览器会因为同源策略拦截请求。解决的方案有好几种最简单的是在后端加全局跨域配置Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOrigins(http://localhost:8080) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }4.3 推荐效果不理想时的调试思路协同过滤推荐出来的结果跑偏了是很多同学会遇到的典型问题。表现有几种推荐列表跟用户完全不搭、推荐的都是自己学过的、或者干脆一个推荐位都是空的。我的排查顺序是固定的按数据链路一层层往后查。第一步查评分矩阵。打印出来看看用户-课程评分表的稀疏程度。如果90%的格子都是空的说明用户行为数据不够协同过滤没有足够的信息做相似度计算。这时候检查模拟数据生成器是不是生成的记录量太少了至少要有50条以上的有效学习记录推荐效果才会显现出来。第二步查相似度计算。看共同的课程数量是不是太少。我给相似度函数定的阈值是0.3如果相似度普遍都在这以下说明用户画像差异太大或者评分数据本身区分度不够。可以把阈值往下调到0.1试试看看推荐结果是否有改善。第三步查推荐的过滤条件。看“用户已学课程排除”这一步有没有生效。有时候就是因为没排除干净导致推荐列表里有用户已经学过的课程看起来就很蠢。第四步查SQL性能。如果推荐接口响应时间在3秒以上大概率是SQL没有走索引。用MyBatis的日志打印功能把执行的SQL打出来用EXPLAIN分析一下执行计划确认是否命中了联合索引。排查的过程也是论文里“系统测试与调优”章节的素材把整个过程记录下来比只贴一张“测试全部通过”的截图要让人信服得多。4.4 常见报错与解决速查手册整理一下这个项目从搭建到调试阶段最常见的几个报错以及对应的解决方案都是硬碰硬的经验。启动报错Consider defining a bean of type xxxMapper。原因一般是 MyBatis 的 Mapper 扫描没有生效。解决的思路是在启动类加MapperScan(com.safety.education.mapper)注解或者每个 Mapper 接口上加Mapper注解二选一即可。启动报错Failed to configure a DataSource。Spring Boot 检测到引入了数据库依赖但没配置数据源。八成是application.yml里的spring.datasource配置没写对或者数据库连不上。检查数据库是否存在、用户名密码是否正确、MySQL 服务是否启动。启动报错Access denied for user rootlocalhost。MySQL 账号密码不匹配或者权限不对。检查配置文件里用户名密码与本地数据库是否一致如果本地MySQL是刚装的用初始化脚本设置 root 密码。JWT解析失败io.jsonwebtoken.SignatureException。Token 密钥与生成时不一致或者Token在传输过程中被截断。检查 JWT 配置里的密钥是否和过滤器里用的是同一个值别写了常量配置后改了一半。前端登录后请求401。Token 没有正确传递到后端。检查前端的 axios 拦截器是否在请求头里携带了Authorization: Bearer token如果登录后刷新页面就丢失登录态多半是Token存的地方不对要存到 localStorage 里。创建唯一索引报错Duplicate entry。通常是因为模拟数据运行了多次重复插入同样的数据。解决很简单初始化数据脚本改成幂等的执行前先清空对应表或者使用INSERT IGNORE语句。Lombok相关报错。典型报错是java: You arent using a compiler supported by lombok之类的。直接检查IDEA是否安装了Lombok插件设置里是否开启了Annotation Processing以及pom里Lombok版本是否和JDK版本匹配。JDK 8 Lombok 1.18.x 是保险组合。启动端口被占用Port 8080 was already in use。改端口配置最简单直接在application.yml里加server.port: 8081或者找到后台占用8080的进程杀掉。Windows 下用netstat -ano | findstr 8080找到PID再结束进程即可。4.5 部署上线可选的轻量方案毕设如果需要部署到服务器演示推荐用 Docker 打包一条龙部署。给项目写一个Dockerfile多阶段构建的思路很实用第一阶段用 Maven 镜像编译后端打成Jar包第二阶段把Jar包放到JRE镜像里运行这样最终镜像体积会小很多。前端就简单粗暴一些npm run build生成静态文件部署到 Nginx配置一个转发把/api开头的请求反向代理到后端服务就行。这里有一个容易踩的坑Spring Boot 打成的 Jar 包默认是没有使用外置Tomcat的所以部署时直接用java -jar启动千万不要再往外部Tomcat里塞。有同学不懂把War包和Jar包搞混折腾了半天还找不到页面。5. 论文撰写与答辩准备的加分技巧5.1 论文的章节框架建议拿到源码之后论文不是简单把源代码拷贝上去。一个清晰、有干货的论文框架直接照着填内容就可以第一章 绪论研究背景与意义、国内外研究现状、主要研究内容、论文组织结构第二章 相关技术介绍Spring Boot框架、MyBatis-Plus、Vue框架、协同过滤算法、JWT认证第三章 系统需求分析可行性分析、功能需求分析用用例图、非功能需求分析第四章 系统设计系统总体架构设计、功能模块设计、数据库设计ER图表结构第五章 系统实现重点写推荐模块的实现过程和核心代码课程管理、考试管理、认证授权各分布一小节第六章 系统测试测试方案、核心功能测试用例表输入/操作/预期结果/实际结果、性能测试推荐接口响应时间分析第七章 总结与展望重点是第五章不要把这章写成代码粘贴大赏每一小节都按“功能描述 → 逻辑流程 → 核心代码 → 界面效果”的节奏来写逻辑非常清楚。5.2 答辩时如何把协同过滤讲得清楚答辩大概只有10到15分钟不要想把所有功能都讲一遍时间根本不够。我的策略是只讲两个亮点讲透它们。第一个是“为什么需要推荐系统”第二个是“协同过滤算法在系统中的具体实现”。推荐系统为什么存在从产品痛点出发课程多、用户不知道自己该学什么、平台需要提升学习转化率。讲清楚这三层导师立刻觉得你是站在产品角度思考问题。算法怎么实现的从“评分如何获取”开始到“矩阵怎么构建”再到“相似度怎么算”最后是“推荐列表怎么生成”。全程配合你项目里的公式、图表和核心代码截图一步一步往下带。只要能把这个链路讲通整个项目的技术难度就立住了。如果被问到“你这个算法有什么不足”可以诚实回答协同过滤存在冷启动问题、数据稀疏时效果会变差、没有考虑课程本身的特征信息。然后补充已经做的改进策略前面冷启动那套方案以及未来可以加内容画像方向的优化空间。这个问答流程相当完整技术深度一下就和其他同学拉开了差距。5.3 如何在项目基础上做功能扩展如果还有时间精力想在答辩中制造更多亮点可以考虑在基础功能上做以下扩展每个扩展开销都不大但吸引力足够增加学习进度可视化图表用 ECharts 画出用户学习时长趋势、分类学习分布饼图、课程热度排行榜。增加消息通知模块课程上新、考试提醒、证书即将到期通过 Spring 的事件机制实现避免写到业务代码里造成耦合。增加数据导出功能管理员可以把课程学习统计导出成Excel用 EasyExcel 库实现。增加证书生成模块学完课程并考试通过后自动生成结业证书支持下载成PDF或图片。这几个方向加进去之后项目的“完整感”会明显上档次演示的时候展示起来也更有话讲内容不会像只有课程和考试那么单薄。写在最后这个基于 Spring Boot 协同过滤的线上安全教育平台之所以值得推荐不是因为它有什么高深莫测的黑科技而是因为它在一条真实存在的业务主线上把主流的 Java 后端技术栈和经典的推荐算法完整地串了起来。做这个项目的过程中你会把 Spring Boot 的项目搭建、MyBatis-Plus 的持久层操作、Spring Security 的认证授权、Vue 的前后端交互、协同过滤算法的公式推导和代码落地全部过一遍这些能力放到实际工作环境中是完全能直接用的。我个人在实际操作中的体会是这类“算法 业务”的组合题目比单纯的“管理系统”更能在答辩中体现出思考和深度也比纯算法的题目更容易展示完整成果。你可以先用模拟数据把整个链路跑通再逐步替换成真实数据观察推荐效果的变化这个过程本身就是一次完整的推荐系统实践。如果说有什么建议就是不要想着把代码从头到尾都自己写一遍——站在已有源码结构上先把核心逻辑读懂、改透、讲明白再亲手扩展一两个功能模块这个投入产出比是最高的。最后再分享一个小技巧拿到任何源码的第一件事不要急着启动先把它的表结构看懂、把代码整体跑一遍、把关键日志打出来这个“热身”过程能让你后面调试的时候少走一大半弯路。祝大家毕设顺利答辩时都能底气十足。
返回列表