SpringBoot+Vue+MySQL学习资源智能推荐平台开发全流程
发布时间:2026/9/9 4:51:08来源:尧图网络
每年到毕业设计选题季后台私信里问得最多的就是学长毕设做什么题目比较稳。我的答案一直很固定做一个能讲清楚完整业务链路、又带点算法亮点的管理系统。SpringBootVue这套组合配合MySQL作为数据底座做线上学习资源智能推荐平台是目前公认兼容上手难度、答辩通过率、技术含金量三个维度的最优解之一没有太大争议。这篇文章不是那种项目介绍式的空话而是把我自己带学生做这类项目时沉淀下来的完整思路、核心代码、数据库设计、联调细节和答辩避坑经验全部整理出来。无论你是选这个题目做毕设还是打算作为课程设计或者只是想用全栈项目练手这篇内容都能让你少走弯路。我会重点讲清楚三个东西推荐算法如何在SpringBoot里低成本落地、八张核心表怎么设计、以及前后端联调时那些不写进文档的坑。1. 为什么选这个题目毕设项目的定位与功能边界毕设选题最怕两件事一是技术栈过于冷门答辩老师看不懂你的亮点二是业务范围无限膨胀做到最后发现工作量完全失控。学习资源推荐平台这个方向恰好避开了这两个坑。1.1 学习资源管理是公认的刚需场景线上学习资源管理本身是一个覆盖用户、资源、分类、行为、统计的管理类业务。它的业务链路非常完整用户登录后浏览资源列表查看资源详情收藏或开始学习管理员在后台上传资源、管理分类、查看统计数据。这样一条链路天然覆盖了增删改查权限管理数据展示这些管理系统必备的基础能力对应到毕设评分表的各项指标都能找到落脚点。智能推荐是这个选题的加分项。正如一个纯粹的管理系统比如图书管理系统、仓库管理系统虽然功能完整但缺乏技术亮点答辩时很难拉开差距。而加入推荐算法之后项目的技术含量和故事性完全上了一个档次。你可以理直气壮地告诉答辩老师我做的不只是增删改查而是一个有数据驱动的智能化系统。1.2 功能边界如何控制别把自己埋进去我见过不少学生选题选得很好但做着做着就失控了。比如本来只想做一个学习资源管理系统结果硬要加在线支付、实时代答、视频通话。我每次带项目都会强调一个原则毕设功能做减法技术深度做加法。这个项目的推荐功能建议做以下内容不要再多基于用户行为的个性化推荐核心亮点热门资源榜单冷启动兜底策略同类资源推荐资源详情页的扩展阅读至于社区发帖、实时聊天、在线支付这些和学习资源推荐的业务主线关系不大不建议加。如果答辩老师问为什么没有做XX功能你完全可以回答为了保证核心功能的质量和深度在需求分析阶段对非核心功能做了取舍这也是工程实践中常见的范围管理思路。这样反而显得你专业。1.3 这个题目适合什么样的人做如果你是以下三类人中的任何一类这个题目基本不会出问题有一定Java基础想在毕设里用一套主流技术栈完整走一遍前后端开发流程的学生需要课程设计项目希望代码结构清晰、能快速跑通演示的在校生自学Java全栈想通过一个完整项目把所有技术点串起来的学习者如果还在犹豫可以看一个简单的自查你会不会SpringBoot的基本流程Controller、Service、Mapper三层调用你会不会MySQL的基本增删改查如果这两个答案是肯定的这个项目对你来说就具备了可行性。剩下的推荐算法其实没有想象中那么高不可攀后面我会用一段能跑通的Java代码说明白。2. 技术选型的底层逻辑SpringBootVueMySQL为什么最稳很多人在技术选型时纠结得不行SSM、SpringBoot、SpringCloud前端又有Vue2、Vue3、React。我直接给结论这个场景下SpringBoot 2.x Vue 2 Element UI MySQL 8.0是最稳的组合没有之一。2.1 后端为什么坚决不碰SpringCloudSpringBoot的核心价值在于约定优于配置。它把繁琐的XML配置简化成了自动配置和Starter依赖让开发者从配置地狱中解放出来把精力放在业务逻辑本身。相比之下SSM要手动维护大量XML映射和配置SSH则更是老旧。对于毕设来说SpringBoot的自动配置机制还能作为答辩时的加分知识点——老师问SpringBoot为什么简单你能说出自动配置、starter、嵌入式容器这三板斧就已经稳了。至于SpringCloud那是分布式微服务的范畴需要引入注册中心、网关、配置中心、熔断器一堆组件。一个毕设项目业务体量用单机应用完全足够强行引入微服务只会让自己陷入部署和调试的泥潭。答辩时老师也容易追问你们这个业务场景分布式的必要性体现在哪里答不上来反而减分。做毕设不丢人别为了炫技给自己挖坑。2.2 前端选Vue 2还是Vue 3Element UI还是Element Plus负责任地告诉你如果你不是为了简历上写熟练掌握Vue3这个目标选Vue 2 Element UI反而更稳。原因很现实Element UI的生态成熟网上随便一搜就是大量现成的管理后台模板和问题解决方案而Vue 3 Element Plus虽然新但在一些组件用法和生态兼容性上仍有坑。毕设的核心目标是稳妥交付而不是给前端团队试点新框架。Vue在这个项目里的核心职责有两个一是通过Vue Router实现前端路由管理由路由守卫拦截未登录用户二是通过Vuex或简单的全局状态管理保存登录状态、用户信息、侧边栏折叠状态。Element UI提供el-table、el-form、el-pagination这些组件让后台管理页面的开发速度提升数倍。前端没有必须用多复杂的技巧能用组件解决的就不要手写。2.3 持久层框架MyBatis-Plus是毕设的作弊器数据访问层我的建议很直接不要用原生MyBatis写一堆XML直接用MyBatis-Plus。它提供了开箱即用的BaseMapper单表增删改查完全不用手写SQL分页用内置的Page对象就能搞定代码量至少减少40%。尤其推荐MyBatis-Plus的LambdaQueryWrapper配合Lambda表达式构建查询条件代码可读性非常高。比如要查询某分类下的资源列表传统MyBatis要写SQL和接口用MyBatis-Plus一行LambdaQueryWrapper就能表达清楚。这确实有点作弊的感觉但对毕设来说效率高、代码清晰、答辩好讲这就够了。2.4 版本匹配这里有一份直接抄的依赖清单SpringBoot、MyBatis-Plus、MySQL驱动这三者的版本最容易踩坑。MyBatis-Plus版本过老可能不兼容新版本SpringBootMySQL 8.0的驱动类名和5.x也不一样。以下组合是我经过多次测试稳定运行,可参考组件推荐版本说明JDK1.8毕设最稳不要盲目用11或17SpringBoot2.7.x2.x的稳定版本资料最多MyBatis-Plus3.5.x支持SpringBoot 2.xMySQL8.0驱动类名改为com.mysql.cj.jdbc.DriverVue2.6.x配Element UI 2.15.x统一用这些版本能规避90%以上的版本兼容问题。不需要在此步骤过分执着于最新核心是跑得起来、跑得稳定。值得一提的是工程实践中稳定优先的理念在任何技术场景都适用。3. 核心亮点把智能推荐做成真正能跑通的功能推荐系统是本项目的技术亮点也是答辩时最容易被追问的部分。很多学生把推荐模块做成了假推荐接口写死返回几条记录这完全浪费了选题的优势。下面从行为数据、算法逻辑、冷启动三个维度讲清楚。3.1 用户行为如何转化为隐式评分经典推荐算法依赖用户对物品的评分数据但学习资源平台不像电商那样有明确的星级打分。所以我们使用隐式反馈——通过用户行为来推算偏好强度。比如浏览资源记2分收藏资源记3分学习资源记5分评论资源记1分。这样每个用户对每个资源的兴趣程度就被量化了相当于构建了一个完整的用户-资源评分矩阵。这个小小的设计决策非常重要因为它让推荐算法的输入数据有了实际来源。从用户点击资源详情页的那一刻开始前端就会调用一个行为上报接口把谁、在什么时间、对哪个资源、做了什么操作写入行为记录表。这个行为采集链路是整个推荐系统的数据根基。3.2 基于物品的协同过滤ItemCF核心代码推荐算法有很多种对这个项目来说基于物品的协同过滤(Item-Based Collaborative Filtering)是最合适的选择。它的核心思想是如果一个用户喜欢资源A而资源B和资源A被同一批用户喜欢过那么这个用户大概率也喜欢资源B。用生活类比就是你看过《三体》系统发现看过《三体》的人也喜欢《球状闪电》于是把《球状闪电》推荐给你。算法实现分四步每一步都有对应代码Service public class RecommendServiceImpl implements RecommendService { Autowired private UserBehaviorMapper behaviorMapper; Autowired private ResourceMapper resourceMapper; /** * 为用户生成TopN推荐列表 */ Override public ListResourceVO recommendForUser(Integer userId, int topN) { // 1. 获取用户历史正反馈行为收藏、学习等评分0 ListUserBehavior userBehaviors behaviorMapper .selectList(new LambdaQueryWrapperUserBehavior() .eq(UserBehavior::getUserId, userId) .gt(UserBehavior::getScore, 0)); // 如果用户没有行为数据走冷启动策略返回热门资源 if (userBehaviors.isEmpty()) { return resourceMapper.selectHotResources(topN); } // 收集用户喜欢的资源ID列表 SetInteger userItemIds userBehaviors.stream() .map(UserBehavior::getResourceId) .collect(Collectors.toSet()); // 2. 构建物品相似度矩阵实际项目中可放入缓存避免每次重复计算 MapInteger, MapInteger, Double similarityMatrix buildItemSimilarityMatrix(); // 3. 基于用户历史物品的相似物品累加推荐分数 MapInteger, Double scoreMap new HashMap(); for (Integer itemId : userItemIds) { MapInteger, Double simMap similarityMatrix.getOrDefault(itemId, Collections.emptyMap()); for (Map.EntryInteger, Double entry : simMap.entrySet()) { Integer candidateId entry.getKey(); // 排除用户已经有过行为的资源 if (userItemIds.contains(candidateId)) { continue; } scoreMap.put(candidateId, scoreMap.getOrDefault(candidateId, 0.0) entry.getValue()); } } // 4. 按分数排序截取TopN作为推荐结果 return scoreMap.entrySet().stream() .sorted((e1, e2) - Double.compare(e2.getValue(), e1.getValue())) .limit(topN) .map(entry - resourceMapper.selectVOById(entry.getKey())) .collect(Collectors.toList()); } /** * 构建物品-物品相似度矩阵 * 核心公式sim(i,j) |N(i) ∩ N(j)| / sqrt(|N(i)| * |N(j)|) * N(i)表示喜欢物品i的用户集合|N(i)|表示喜欢物品i的用户数量 */ private MapInteger, MapInteger, Double buildItemSimilarityMatrix() { // 查询所有正反馈行为记录 ListUserBehavior allBehaviors behaviorMapper .selectList(new LambdaQueryWrapperUserBehavior() .gt(UserBehavior::getScore, 0)); // 构建物品 - 用户集合的倒排索引 MapInteger, SetInteger itemUsers new HashMap(); for (UserBehavior behavior : allBehaviors) { itemUsers.computeIfAbsent(behavior.getResourceId(), k - new HashSet()) .add(behavior.getUserId()); } // 统计同时喜欢两个物品的用户数 C[i][j] MapInteger, MapInteger, Integer coMatrix new HashMap(); for (SetInteger users : itemUsers.values()) { ListInteger itemList new ArrayList(users); // 这里简化处理真实场景应遍历用户喜欢的物品两两组合 } // 简化版本只统计物品被喜欢次数以及共现矩阵 for (Map.EntryInteger, SetInteger entry : itemUsers.entrySet()) { Integer itemI entry.getKey(); SetInteger usersI entry.getValue(); for (Map.EntryInteger, SetInteger other : itemUsers.entrySet()) { Integer itemJ other.getKey(); if (itemI.equals(itemJ)) { continue; } SetInteger usersJ other.getValue(); // 计算交集大小 long intersection usersI.stream().filter(usersJ::contains).count(); if (intersection 0) { // 余弦相似度公式 double similarity intersection / Math.sqrt(usersI.size() * (double) usersJ.size()); coMatrix.computeIfAbsent(itemI, k - new HashMap()) .put(itemJ, similarity); } } } return coMatrix; } }上面这段代码把ItemCF的完整链路呈现出来了。在实际生产级项目中相似度矩阵必然要缓存到Redis里避免每次请求都全量计算。但对毕设来说用HashMap在内存中计算就够用了但答辩时如果能说出用Redis缓存相似度矩阵这个优化点会非常加分。3.3 冷启动问题的处理策略推荐系统有个经典问题叫冷启动新用户没有任何行为数据算法根本不知道要推荐什么。我用的策略是当用户没有行为记录时推荐热门资源榜单来兜底也就是按学习资源的总学习次数和收藏次数综合排序取前N条返回。这个兜底策略在代码里已经体现。这个设计细节能在答辩时展开因为冷启动是推荐系统领域公认的难题你能自己发现并提出解决方案说明你是真的理解了系统面临的问题而非只会调用现成接口。3.4 推荐接口设计与猜你喜欢展示推荐接口设计为RESTful风格返回JSON数据RestController RequestMapping(/api/recommend) public class RecommendController { Autowired private RecommendService recommendService; /** * 首页猜你喜欢推荐 */ GetMapping(/personal) public ResultListResourceVO personalRecommend( RequestParam(defaultValue 10) Integer limit, RequestAttribute(userId) Integer userId) { ListResourceVO resources recommendService.recommendForUser(userId, limit); return Result.success(resources); } }前端首页的猜你喜欢区域只需要在Vue组件的mounted生命周期里调用这个接口然后把返回的资源列表渲染成卡片即可。需要强调一点如果推荐接口返回为空前端要设置一个兜底展示比如显示暂无推荐去热门榜单逛逛的占位提示。细节上对用户友好也避免前端报错。4. 数据库设计八张表如何支撑整个平台数据库设计是答辩时老师必问的环节。表结构设计得合理与否直接反映出你对业务的理解深度。我建议核心表控制在八张左右不要贪多。4.1 用户与权限相关表用户表和系统中最基础的角色控制相关至少要包含id、用户名、密码、昵称、角色、头像、创建时间这些字段。角色用tinyint区分就行1表示普通用户2表示管理员。密码必须加密存储推荐使用BCrypt加密SpringSecurity的加密工具类可以直接使用比MD5安全得多答辩时还能解释一下为什么不用MD5。4.2 资源与分类表资源表是整个系统的核心业务表建议包含这些字段id资源IDtitle资源标题cover封面图URLsummary简介category_id所属分类content_type资源类型视频、文档、图文等file_url资源内容地址creator_id上传人study_count学习次数create_time发布时间分类表很简单id和分类名称即可最多再加一个排序字段。资源表和分类表通过category_id关联一个分类下有多个资源是典型的一对多关系。答辩时老师如果问为什么分类表和资源表要分开建标准答案是避免资源字段冗余同时也方便扩展子分类。4.3 用户行为表推荐系统的数据底座用户行为表的字段设计直接影响推荐算法的实现难度。我建议用一张统一行为表来记录所有操作而不是拆分成浏览表、收藏表、学习表三张。统一的优点在于推荐算法一次性就能取出全部行为数据不需要跨多张表做合并操作。行为类型用type标识区分就好。CREATE TABLE tb_user_behavior ( id bigint NOT NULL AUTO_INCREMENT COMMENT 行为ID, user_id int NOT NULL COMMENT 用户ID, resource_id int NOT NULL COMMENT 资源ID, behavior_type tinyint NOT NULL COMMENT 行为类型1-浏览 2-收藏 3-学习 4-评论, score double NOT NULL DEFAULT 0 COMMENT 行为对应权重分, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 行为时间, PRIMARY KEY (id), KEY idx_user_resource (user_id, resource_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户行为记录表;这个score字段非常关键。它对应了前面说的行为权重浏览记2分、收藏记3分、学习记5分。每一条行为记录同时携带用户ID和资源ID以及对应的权重分数。推荐算法的buildItemSimilarityMatrix直接从这个表取数。行为表的索引设计也要提一下idx_user_resource联合索引可以覆盖查询某个用户的全部行为这个高频操作这属于数据库优化的基本素养写进文档里是一个小亮点。4.4 一张完整建表SQL示例为了让你对表结构有整体感我把两张最核心的表的建表SQL贴出来剩余的表结构可以直接参考这个风格-- 用户表 CREATE TABLE tb_user ( id int NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL COMMENT 登录账号, password varchar(100) NOT NULL COMMENT BCrypt加密密码, nickname varchar(50) DEFAULT NULL COMMENT 昵称, role tinyint NOT NULL DEFAULT 1 COMMENT 角色1-普通用户 2-管理员, avatar varchar(255) DEFAULT NULL COMMENT 头像URL, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; -- 资源表 CREATE TABLE tb_resource ( id int NOT NULL AUTO_INCREMENT, title varchar(100) NOT NULL COMMENT 资源标题, cover varchar(255) DEFAULT NULL COMMENT 封面图, summary varchar(500) DEFAULT NULL COMMENT 资源简介, category_id int NOT NULL COMMENT 分类ID, content_type tinyint DEFAULT 1 COMMENT 资源类型1-视频 2-文档 3-图文, file_url varchar(255) DEFAULT NULL COMMENT 内容地址, creator_id int DEFAULT NULL COMMENT 上传用户ID, study_count int DEFAULT 0 COMMENT 学习次数, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_category (category_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT学习资源表;这套表结构设计回答了一个核心问题推荐算法依赖的数据从哪里来 答案就是行为表。所以说数据库设计的顺序应该是先想清楚算法要什么数据再设计表结构而不是反过来。5. 前后端联调认证、接口与页面联动前后端分离架构下联调环节最多出现的问题集中在认证、跨域、请求封装这三个方面。把这三点理清楚联调流程基本就顺畅了。5.1 JWT认证与前端路由守卫推荐模块要识别用户身份普通的管理系统会话方案是Session但前后端分离场景更适合用JWT也就是无状态令牌。用户在登录接口输入账号密码后端验证成功后签发一个带有效期的令牌返回给前端。前端把令牌存到localStorage之后每次请求都自动携带在HTTP Header里。后端再用一个拦截器统一从Header取令牌并解析出用户ID塞到请求属性里。这样业务接口只要声明RequestAttribute(userId)就能拿到当前登录用户非常方便。需要注意拦截器要放行登录接口、资源列表接口只拦截需要认证的接口。JWT三部分的结构Header、Payload、Signature也是一个很好的答辩知识点。前端对应的核心逻辑是Vue Router的全局前置守卫// 路由守卫未登录跳转登录页 router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.meta.requiresAuth !token) { next({ path: /login }) } else { next() } })这里要注意如果后端返回401状态码前端应该清除本地token并跳转到登录页。这个逻辑属于前后端鉴权的闭环缺一不可。5.2 axios封装与跨域处理axios封装是前端工程化的基础配置。建议单独建一个request.js文件创建axios实例并设置基础URL、超时时间然后用拦截器统一附加tokenimport axios from axios const request axios.create({ baseURL: process.env.VUE_APP_BASE_API || /api, timeout: 10000 }) // 请求拦截器携带token request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] Bearer token } return config }) // 响应拦截器统一处理错误 request.interceptors.response.use( response response.data, error { if (error.response error.response.status 401) { localStorage.removeItem(token) window.location.href /login } return Promise.reject(error) } ) export default request跨域问题在开发环境用Vue CLI的代理解决在vue.config.js里配置devServer.proxy把/api开头的请求代理到后端8080端口。生产环境如果前后端分开部署则在后端配置CORS策略允许前端域名跨域访问。开发环境和生产环境两个不同阶段要用不同手段解决同样的问题这个细节面试时也经常被问到。5.3 管理后台的资源管理与数据统计管理后台是管理端的核心界面我建议使用嵌套路由实现布局侧边栏是菜单右侧是内容区。菜单项包括资源管理、分类管理、用户管理、行为统计。资源管理页面用列表展示资源信息支持新增、编辑、删除。新增资源用表单弹窗配合文件上传组件完成。数据统计页面可以用ECharts做两个图一个是分类资源分布饼图另一个是近7天用户行为趋势折线图。这两个图表虽然代码量不大但让管理后台看起来具备数据分析能力是答辩时的视觉加分项。注意ECharts要在Vue组件的mounted钩子里初始化并且在数据返回后用setOption填充数据。5.4 前端打包与刷新404问题前端开发完成后需要打包部署但这里有一个非常经典的坑。Vue Router使用history模式时直接访问某个子路由地址比如/admin/resourcesNginx找不到对应的物理文件会返回404。解决方案是在Nginx配置中增加try_files回退到index.htmllocation / { try_files $uri $uri/ /index.html; }这个配置会让Nginx在前端路由刷新时统一返回入口页面由Vue Router接管后续的路由解析。如果不用history模式改成hash模式URL带#号也可以规避这个问题但URL不太美观。我通常都用history模式加Nginx配置来解决。这个刷新后404的坑很多学生在演示时才发现手忙脚乱提前配好是必须的。6. 开发和答辩中容易踩的坑最后这部分是我认为最有实操价值的内容。每个坑都是我或我带的学生实际遇到过的问题提前知道能帮你省下大量排查时间。6.1 MySQL 8.0连接配置与时区陷阱很多学生第一次用MySQL 8.0连接字符串还在用5.x时代的配置结果启动就报错。MySQL 8.0的驱动类名是com.mysql.cj.jdbc.Driver连接URL建议带上时区参数spring: datasource: url: jdbc:mysql://localhost:3306/study_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driver其中serverTimezoneAsia/Shanghai和allowPublicKeyRetrievaltrue是关键。前者解决时区导致的时间差8小时问题后者解决MySQL 8.0客户端连接时的公钥检索权限问题。这两个参数缺一个都可能在特定环境下报错。6.2 文件上传大小限制与资源路径映射资源管理涉及图片和文件上传SpringBoot默认的单文件上传上限是1MB这显然不够用需要手动调整spring: servlet: multipart: max-file-size: 50MB max-request-size: 50MB还有一个容易忽略的问题上传的文件保存在服务器/本地磁盘后前端访问文件URL会404。原因在于没有配置静态资源映射把磁盘路径映射为URL路径。代码如下Configuration public class WebMvcConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceLocations(file: System.getProperty(user.dir) /upload/); } }这样前端在上传接口拿到返回的相对路径/upload/xxx.jpg后直接拼接接口域名就能访问到图片。这个步骤如果不配置上传功能就会看似成功实则全挂是高频踩坑点。6.3 推荐列表为空时前端如何兜底推荐算法依赖行为数据如果测试账号没有任何行为记录推荐接口就会返回空列表。这会导致前端猜你喜欢区域一片空白看起来像功能挂了。我在推荐接口里已经把无行为记录的用户导向了热门资源。但前端仍然要做好空数据处理v-if判断列表长度为空时展示一个友好的引导文案比如完成学习任务后推荐会更懂你。这种细节既能提升用户体感也体现出你做事的完整性。6.4 答辩演示前必须检查的三件事答辩演示环节很多项目不是被老师问倒的而是被自己环境问题坑倒的。根据经验建议按这个清单逐一确认演示账号是否能正常登录数据库服务是否已启动。SQL脚本先在别的机器上执行一遍避免编码或依赖问题。推荐功能演示前确保测试账号已经录入了至少10条不同的用户行为记录这样猜你喜欢板块才有内容可以展示。提前关闭电脑的自动息屏和系统更新弹窗。万一演示途中系统弹窗覆盖全屏体验极差。如果答辩环境没有网络前端CDN资源必须改成本地依赖。这个坑我见过不止一次平时开发用的是npm本地打包没问题但有些模板直接引用CDN现场没网就彻底白屏。建议在打包前检查一下是否引入了CDN外部资源。最后再分享一个个人心得做这类全栈项目最忌讳的是拿到手就敲代码。先把表结构画出来把接口清单列出来把推荐算法流程走通再动手写效率反而最高。推荐系统别怕算法代码量不够把行为采集、相似度计算、冷启动处理这些细节都讲清楚本身就是一篇有血有肉的毕设论文了。只要这八张表和四步推荐链路你都能讲明白这个项目的价值就彻底发挥出来了。
网站建设高端定制企业官网