SpringBoot+Vue图书推荐系统源码解析:协同过滤与前后端分离实战

最近我在帮几位同学梳理毕业设计项目,被问得最多的就是“图书推荐系统”。这类题到现在依旧热门,原因也很简单:图书天然适合做推荐,用户行为数据容易理解,和电商推荐、短视频推荐比起来业务逻辑更干净,特别适合在论文里把算法讲清楚。我手上正好有一套 SpringBoot + Vue 的个性化图书推荐系统管理平台源码,后端是 Java + MySQL,整体技术栈和课程里学的东西贴合得很紧,适合毕设、课设,也适合想完整走通一个前后端分离项目的同学拿来学习。这篇文章我不写空话,把拆解这套源码、跑通项目、以及帮同学准备答辩时遇到的关键点全部摊开讲。

1. 先聊聊这个项目:一个图书推荐平台凭什么撑起一份毕设

1.1 项目能做什么,页面大概长什么样

这套系统从使用者角度可以分成两个端:普通用户端和管理员端。普通用户端不是那种只能查书、借书的死板列表,而是有“猜你喜欢”“热门推荐”“同类图书推荐”几个推荐模块。你注册登录后可以给书打分、收藏、借阅,系统会根据这些行为慢慢调整推荐结果,用得越久推荐越准。

管理员端则负责维持整个平台的运转:图书上架、下架、分类管理、作者信息维护、用户管理、借阅记录查看、统计数据展示。这些功能组合起来就是一个完整的图书管理平台,而不是一个孤立的推荐算法Demo。我手头这份源码里,前端用的是 Vue,配合 Element 风格的后台组件;后端是 SpringBoot 提供的 REST 接口;数据库是 MySQL,存储所有业务数据。

对毕设来说,这个项目覆盖面很好看:有传统管理系统的增删改查、有权限控制、有前后端交互、有算法设计、有数据库建模,还有性能优化点可以聊。对课设来说,复杂度可以裁剪,把推荐模块简化成热门榜也不会影响完整性。拿来学习更是合适,因为所有功能都是围绕一条清晰的主线展开,不会像企业项目那样动辄几十张表、一堆中间件,初学者不会被淹没。

1.2 为什么 SpringBoot + Vue + MySQL 是黄金组合

近几年的毕设题目里,SpringBoot + Vue 几乎成了默认组合,不是没有道理。SpringBoot 把 Spring 的繁琐配置大量简化,通过 starter 依赖和自动配置就能把 Web、数据访问、校验这些能力快速集成起来,非常适合学生在有限时间内完成一个完整后端。Vue 则是渐进式框架,入门曲线不算陡,组件化和响应式机制又能让你写出像样的前端界面,比传统 JSP 页面好看得多,答辩演示时观感完全不一样。

MySQL 更不用多说,大学课程里关系数据库基本都用它,思维惯性上最接近课堂知识。而且 MySQL 的索引、事务、Explain 工具都是论文里能实实在在写出来的功夫。你不需要额外学习太多新东西,就能把整个项目跑通。

有人会问,同样的题目能不能用 Django 或者 Node.js?能,但如果你是计算机相关专业,SpringBoot 的就业属性和课程重合度通常更高,答辩时老师也更愿意从这套技术栈里挑问题考你。所以“SpringBoot + Vue + MySQL”这个组合本质上是求稳,在技术深度和完成难度之间取了平衡点。

1.3 拿到源码后的正确打开方式

源码下载下来第一件事,不是急着按启动按钮,而是先把项目里的 README 或者部署文档翻完。很多学生栽在“启动项目五分钟,报错两小时”上,就是因为跳过了数据库初始化。

我建议的学习顺序是:先看数据库脚本,了解有几张表、表之间什么关系;然后启动后端,用 Postman 或者浏览器直接访问接口文档;确认后端正常后,再启动前端,走一遍界面流程。整个过程务必按“数据库 → 后端 → 前端”的顺序来,因为前端任何一个请求背后都依赖后端接口,而后端接口又依赖数据库表数据。如果你一上来就同时启动前后端,报错时根本分不清问题出在哪一层。

另外,拿到源码后应该先做一次“代码体检”:看一眼 pom.xml 或 package.json 里的依赖版本,检查 application.yml 里的数据库账号密码是不是默认配置,再看看前端有没有代理配置。把这些环境变量确认好,基本能避开七成以上的启动问题。

2. 推荐功能的核心:从协同过滤到“读懂读者”

2.1 推荐不是玄学,先搞清楚协同过滤怎么算

个性化推荐是这个项目的灵魂,也是论文里最值钱的部分。这套源码里用的核心算法通常是协同过滤,具体又可以分为基于用户(User-CF)和基于物品(Item-CF)两种。毕设里最好讲的是 User-CF,意思是“和你喜好相似的人读的书,大概率你也喜欢”。

它的运算分三步:

第一步,找到与当前用户行为最相似的若干用户; 第二步,把这些用户有过正反馈、而当前用户还没看过的图书找出来; 第三步,按照预测评分排序,取前 N 本推荐。

相似度计算一般用余弦相似度或者皮尔逊相关系数。余弦相似度的思路是把用户对所有图书的评分看成一个向量,向量夹角越小,说明两个人偏好越接近。我在源码里看到的实现,大致是这样一个骨架:

# 这里是逻辑伪代码,和项目里的Java实现思路一致 def user_similarity(user1, user2): common_books = 用户1和用户2共同评分过的图书 if len(common_books) == 0: return 0 vector1 = [评分 for 图书 in common_books 来自用户1] vector2 = [评分 for 图书 in common_books 来自用户2] numerator = sum(a * b for a, b in zip(vector1, vector2)) denominator = sqrt(sum(a*a for a in vector1)) * sqrt(sum(b*b for b in vector2)) return numerator / denominator

然后对候选图书打分:

预测分 = 所有相似用户在图书上的评分加权和 / 相似度权重之和

这里有一个容易被忽略的细节:不是所有用户都能参与计算。如果两个用户共同评分的书太少,算出来的相似度没有统计意义,应该做过滤。我帮某位同学改代码时发现,原本的计算把所有相似度大于 0 的用户都算进去了,导致偶尔出现“只看过一本书的用户”被当成最佳邻居。后来加了一个“共同评分数量不少于 3”的约束,推荐效果一下子正常多了。

2.2 评分数据从哪来:显式评分和隐式反馈一起用

纯靠用户主动打星收集评分会很慢,而且大量用户根本没有打分的习惯。所以源码里更像是一个“行为加权”的逻辑:收藏、借阅、评论、浏览时长都能变成用户对图书的偏好信号。

我在项目里见到过一张评分表,里面有 user_id、book_id、score、create_time 这些字段。但实际上给分值的来源不止一条:显式评分,是用户直接打的星星;隐式行为,比如点进图书详情、加入书架、发起借阅,都可以折算成分数。比较简单的做法是给不同行为设权重,比如:

行为折算分数
浏览详情页0.5
收藏1.0
借阅2.0
评分 3 星3.0
评分 5 星5.0

当用户没有直接评分时,系统也可以用隐式行为生成“虚拟评分”,把行为分数归一化到 1 到 5 之间。这样评分数值矩阵不会稀疏到无法计算,推荐质量也会明显提高。写论文的时候,这个“显式 + 隐式混合建模”的设计是个很不错的亮点,比只说“根据用户评分推荐”高级不少。

2.3 冷启动问题:新用户、新书没有任何行为时怎么办

协同过滤最大的软肋是冷启动。新注册用户一条行为记录都没有,拿什么算相似用户?新上架的图书没有任何用户评分,又凭什么推荐给别人?

源码里的做法我认可的是“热榜兜底 + 内容特征补充”。新用户第一次进首页时,推荐模块压根不去计算协同过滤,而是直接给一个热门图书榜,按借阅量、收藏量、平均分综合排序。同时根据注册时选填的兴趣分类做一次粗过滤,比如用户勾了“计算机”和“科幻”,就先从这两个分类里各抽一批热门书。这样新用户不会看到空白页,也不会被推一堆完全无关的内容。

至于新书,更稳妥的方式是把图书的分类、标签、作者等特征拿来做基于内容的推荐。比如刚入库一本新的 Java 编程书,系统可以找出和它同分类、同关键词的旧书,然后把“看过旧书的人”作为候选用户群,向他们推荐这本新书。这套源码不一定把所有策略都做得很全,但我们要学习的就是这种“多层推荐弹性方案”的思维。答辩时老师问“冷启动怎么解决”,你能从用户冷启动、物品冷启动两个维度分别回答,印象分立刻不一样。

3. 后端 SpringBoot 模块拆解:我做项目时最关注的几个包

3.1 工程结构和依赖选型

拿到源码后,先别急着看业务代码,先扫一眼包结构。正常的 SpringBoot 项目会分成 controller、service、mapper、entity、config、common 这么几个层级。控制器层只负责参数接收和结果返回,业务逻辑全在 service 里,数据访问交给 mapper。这样的分层结构本身就是答辩时的加分项,说明你理解“高内聚低耦合”。

依赖方面,常见的组合包括:

  • Spring Boot Web:提供 RESTful 接口能力
  • MyBatis-Plus:简化数据库操作,不用手写大量 XML
  • MySQL Connector:数据库驱动
  • JWT 或 Sa-Token:登录鉴权
  • Lombok:减少实体类样板代码
  • Hutool:常用工具集合,处理日期、字符串、对象拷贝都很方便

MyBatis-Plus 在毕设项目里非常常见,因为内置的 BaseMapper 让单表增删改查基本不用写 SQL。但这也带来一个误区:很多学生用多了 MyBatis-Plus,反而忘了原生 SQL 怎么写。我会建议你在学习时主动关掉一些自动能力,比如联表查询、复杂统计尽量自己写 SQL,这样面试被问到底层时才不会露怯。

3.2 登录注册与 JWT 鉴权怎么设计

图书推荐系统有用户端和管理员端,所以登录鉴权必须区分角色。源码里常见的方案是用 JWT 生成登录令牌,用户登录后拿到一个包含用户 ID、用户名、角色信息的加密 token,之后每次请求都在请求头里带上这个 token。后端通过拦截器统一校验 token 是否有效、角色是否匹配。

我评价一个毕设项目好不好,会特意看密码存储方式。如果密码是明文,或者直接 MD5 不加盐,这个项目在老师眼里基本属于“安全意识不及格”。正常做法是用 BCrypt 这类慢哈希算法,同一个密码每次加密出来的密文不一样,数据库泄露了也很难反推明文。Spring Security 或者 Shiro 里都有现成的 BCrypt 工具,源码里如果没有,建议自己加上。

登录接口返回的信息里,除了 token 和用户基本信息,还应该带上角色字段,方便前端控制路由。比如管理员能看到“图书管理”菜单,普通用户则看不到。代码可以简化成:

public class LoginResult { private String token; private Long userId; private String username; private String role; // 省略 getter/setter }

这里有个小坑:JWT 的过期时间不要设太长。有的项目为了省事设置 7 天才过期,结果用户 token 泄露后,别人可以长时间冒充身份。一般推荐 2 小时到 12 小时,配合前端重新登录的提示,体验和安全都能兼顾。

3.3 推荐接口的请求链路

推荐接口是核心入口。假设前端请求/api/recommend?userId=1,后端会怎么处理?我估算了一下,正常链路是这样的:

  1. Controller 接收参数,校验 userId 是否存在;
  2. Service 调用推荐策略类,判断当前用户是不是新用户;
  3. 如果是老用户,从评分表中取出该用户所有评分记录,查询候选邻居用户;
  4. 计算相似度,过滤相似度低于阈值的邻居;
  5. 取出邻居们评价较高的图书,计算预测分;
  6. 过滤掉当前用户已经借阅或已经收藏的图书;
  7. 按预测分排序,取前 10 条返回。

听起来不复杂,但实际代码涉及多层循环和集合操作,很容易写出 O(n²) 甚至 O(n³) 的糟糕性能。比如计算用户相似度时,双层循环遍历所有用户,每个用户又遍历所有评分,在演示数据很少的时候没问题,但一旦数据量上千条,接口响应就会明显变慢。

我的建议是,在 Java 实现里先把数据一次性加载到内存,用 Map 按用户分组;在计算相似度时只遍历有“共同评分项”的候选用户,不要全局遍历所有用户。源码里如果能体现这种优化,在论文复杂度分析部分会很加分。

3.4 统一返回体和全局异常处理

你打开任何一个正经点的 SpringBoot 项目,都会看到一个 Result 或者 R 这样的通用返回类。它的作用是把接口返回格式统一成:

{ "code": 200, "message": "操作成功", "data": { } }

如果没有统一返回体,前端就不得不在每个请求里单独处理成功、失败、未登录、参数错误等多种情况,代码会变得乱七八糟。而且接口文档写起来也困难。看源码的时候,我建议你重点看Result类里的静态方法,比如Result.success(data)、Result.error(code, msg),这相当于约定了一套前后端沟通的“暗号”。

全局异常处理是通过@RestControllerAdvice实现的。比如参数校验失败、业务异常、数据库异常,都可以在切面里统一捕获,转成友好的错误信息返回,而不是直接给前端抛出一堆异常堆栈。这样做既安全又专业。

我在帮同学排查问题时发现一个细节:有些人会把所有异常都吞掉,只返回一个“系统错误”,导致真正的问题被掩盖。建议至少把异常日志打印完整,线上可以隐藏细节,但本地开发时一定要能看到堆栈,否则排错效率极低。

4. 前端 Vue 管理平台:从用户书架到管理员仪表盘

4.1 页面结构和路由设计

前端部分,我手上这份源码是 Vue3 + Vite + Element Plus 的版本,如果你拿到的源码是 Vue2 + Element UI,核心思路也一样,只是 API 写法和组件引用略有区别。我建议你在学习时先梳理路由表,打开router/index.js,看每个 URL 对应哪个页面组件。

用户端常见页面有:首页推荐、图书详情、个人中心、我的借阅、我的收藏、登录注册。管理员端常见页面有:控制台仪表盘、图书列表、图书编辑、分类管理、用户管理、借阅记录、数据统计。两个端可以放在同一个前端工程里,通过路由守卫和角色字段来控制访问权限。

做毕设的同学经常忽略一点:路由懒加载。如果所有页面都一次性打包进 JS,首屏加载会非常慢,老师演示的时候网页转圈圈很尴尬。正确做法是使用component: () => import('@/views/RecommendPage.vue')这种动态导入方式,按需加载页面。源码里如果没做,你可以自己加上,这也是一个答辩时可以讲的性能优化点。

4.2 axios 请求封装与登录态处理

前端请求基本都用 axios 库,但如果不做封装,每个组件里都写axios.get、axios.post,后续维护简直是灾难。合理的做法是在request.js里创建一个 axios 实例,统一设置基础路径、超时时间、请求拦截器和响应拦截器。

请求拦截器主要干一件事:把本地存储的 token 加到请求头里。

service.interceptors.request.use((config) => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config })

响应拦截器负责统一处理后端返回的 code。如果 code 是 401,说明 token 失效,应该跳转到登录页;如果是其他错误,直接弹出提示,不用每个页面重复写错误处理。这个封装完成后,业务组件里只需要关心请求成功的逻辑,代码一下子清爽很多。

这里有个容易被忽略的细节:axios 的 baseURL 不要写死成http://localhost:8080,因为部署到服务器后域名会变。更好的做法是用环境变量VITE_API_BASE_URL,开发环境走代理,生产环境配成真实地址。源码里如果能体现环境变量配置,说明你已经具备了工程化意识。

4.3 推荐结果怎么展示:卡片流与加载状态

首页推荐页是整个项目的门面。图书推荐的结果一般以卡片列表形式展示,每张卡片包含封面、书名、作者、评分、简介摘要。为了提高浏览效率,可以用分页或者滚动加载。如果后端返回的是整套推荐列表,前端可以一次渲染;如果数据量很大,就得分页接口。

在实现时,有几个 UI 细节特别影响观感:封面图没有加载出来的占位图、评分展示的小星星、推荐理由的简短标签。我见过很多学生把封面图路径写错,导致整个页面都是空图,第一眼就能看出项目没调试好。所以图书封面上传和展示的完整链路,一定要在答辩前反复验证。

加载状态也是个常见问题。接口请求需要时间,如果不做 loading,用户点击按钮后页面毫无反应,会以为卡死了。Element Plus 里提供了v-loading指令,可以直接绑定到列表容器上,请求完成后自动隐藏。另外空状态也要处理,比如“暂无推荐图书”时显示一张空插画和提示文案,而不是白茫茫一片。

4.4 管理后台的增删改查:表格、对话框、表单校验

管理员的图书管理页面其实是经典的前端 CRUD 范式,包含四件事:展示表格、搜索筛选、编辑弹窗、删除确认。表格用el-table,列字段对应图书的标题、作者、分类、库存、状态、操作按钮。搜索区一般放在表格上方,支持按书名、分类、作者模糊搜索。

新增和编辑共用一个对话框,通过一个isEdit标记区分提交逻辑。打开对话框前,如果是从表格行点“编辑”,需要先回填当前行数据;如果是点“新增”,要清空表单并重置校验。

表单校验是很容易漏掉的部分。有些同学只写了必填项,忽略了 ISBN 格式、价格区间、库存不能为负数这些约束。虽然后端也会做校验,但前端校验能提前拦截无效输入,减少无意义的请求。Element Plus 的rules属性配合formRef.validate()就能实现。把这块写完整,项目给人的专业感会强很多。

我自己的习惯是,每个管理页面都测试三遍:第一遍新增空表单,看看校验提示是否清晰;第二遍编辑一条数据后再刷新,确认数据真的改进了数据库;第三遍删除后再分页翻页,检查页码是否越界。这三遍下来,大部分低级 bug 都能被提前消灭。

5. 数据库设计精讲:MySQL 里那些表和索引

5.1 核心表结构及字段说明

数据库是整套系统的基础。我粗略梳理了一下这套源码里的核心表,至少会有这么几张:

  • user:用户表,存放账号、密码、昵称、角色、注册时间;
  • book:图书表,存放书名、作者、ISBN、分类 ID、封面、库存、简介、出版时间;
  • category:图书分类表;
  • rating:评分/行为表,记录哪个用户对哪本书评了多少分;
  • borrow_record:借阅记录表,包含借出时间、应还时间、归还状态;
  • collection:收藏表,记录用户收藏的图书;
  • recommend_log:推荐日志表,记录系统给哪个用户推荐了哪些书。

以book表为例,关键字段大概是这样的:

CREATE TABLE `book` ( `id` bigint NOT NULL AUTO_INCREMENT, `title` varchar(100) NOT NULL COMMENT '书名', `author` varchar(50) DEFAULT NULL COMMENT '作者', `isbn` varchar(20) DEFAULT NULL COMMENT 'ISBN', `category_id` bigint DEFAULT NULL COMMENT '分类ID', `cover` varchar(255) DEFAULT NULL COMMENT '封面图URL', `stock` int DEFAULT '0' COMMENT '库存', `publish_date` date DEFAULT NULL COMMENT '出版日期', `description` text COMMENT '简介', `create_time` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='图书表';

主键用自增 bigint 即可,不用 UUID,因为推荐算法里经常要按 ID 排序和关联,数字主键性能更好。字符集必须选 utf8mb4,因为要兼容生僻字和 emoji,如果只用了 utf8,中文都能存,但一些特殊符号可能报错。

5.2 为什么推荐结果要单独建表

实习和工程经验越多的人,越能理解缓存的概念。推荐接口如果每次请求都现算,数据库压力会很大,而且响应时间不可控。这套源码里如果只有一张用户行为表,那推荐结果就是实时计算的。但很多成熟项目会专门建一张推荐结果表,把“给某个用户算好的推荐列表”存起来,下次请求直接查表,这叫“预计算”。

预计算表的核心字段包括:user_id、book_id、推荐分值、排序位置、生成时间。因为用户行为在变化,这张表的内容不能永远不变,要有定时任务或者触发机制去刷新。比如每天晚上跑一遍全量推荐任务,白天用户请求时直接读预计算结果,响应时间可能从几百毫秒降到几十毫秒。

对毕设而言,我不建议一开始就设计得过于复杂。如果你们的系统数据量只有几百条,实时计算完全没问题。但论文里可以把这个“实时计算改为预计算”的演进方案写进去,作为性能优化章节的亮点。这也是回答“你这个推荐系统如果用户量大了怎么办”这类问题的一个标准答案。

5.3 查询性能优化和常见慢 SQL

即使是小项目,也要培养 SQL 优化意识。最常见的慢 SQL 场景是联表查询和模糊搜索。比如统计某本书的借阅次数,需要联查book和borrow_record,如果不在外键列上建立索引,数据量大了以后查询会全表扫描。

我的建议是,至少在这几个字段上建索引:

  • rating表的user_id和book_id联合索引,因为推荐算法要频繁按用户查评分、按图书查用户;
  • borrow_record表的user_id和book_id索引,用于用户借阅历史和管理员统计;
  • book表的category_id索引,用于分类筛选;
  • 书名模糊搜索建议用全文索引或者改成前缀匹配,不要大范围LIKE '%关键字%'。

建索引用 SQL 就是在关键表上追加:

ALTER TABLE rating ADD INDEX idx_user_book (user_id, book_id);

在写论文时,可以用EXPLAIN命令截几张执行计划图放在附录里,展示优化前后的type从ALL变成index或ref,需要的行数大幅减少。这是老师非常认可的实干证据。

5.4 演示数据的准备技巧

很多同学下载源码后,数据库里只有几条数据,推荐模块几乎看不出效果。为了让答辩演示足够漂亮,一定要自己造一批“看起来真实”的数据。

我的做法是准备一个演示数据 SQL 脚本,手动构造几十个用户、上百本图书、上千条评分和借阅记录。造数据时要注意分布不能太均匀,得有热门书和冷门书之分,必须有一部分用户评分很多、一部分用户几乎不活跃,否则推荐结果全是随机排序,看起来没有说服力。

比如我想演示 User-CF 的效果,会刻意构造三个用户:A 和 B 都读过《深入理解计算机系统》《数据密集型应用系统设计》,并且给了高分;A 还没读过《数据库系统概念》,B 读过并打了高分。这样系统给 A 推荐《数据库系统概念》时,推荐理由非常直观——“因为与你相似的 B 喜欢这本书”。这种可控的演示数据比随机数据更有说服力,也更能体现你对算法的理解。

6. 从零跑通源码:环境配置与部署过程中的真实踩坑

6.1 环境版本匹配问题

跑这种前后端分离项目,环境版本不对是最大的坑。SpringBoot 2.x 通常配 JDK8 或 JDK11,MyBatis-Plus 太老的版本跟 SpringBoot 3.x 会冲突,因为 SpringBoot 3 把javax包换成了jakarta,很多老依赖直接报类找不到。前端同理,Vite 创建的项目要求 Node 14.18+ 或更高,但注意 Node 版本太高也可能遇到 OpenSSL 相关报错,比如error:0308010C:digital envelope routines::unsupported,这个问题的经典解法是在 package.json 里设置NODE_OPTIONS=--openssl-legacy-provider,或者把 Node 降到 16.20 左右。

我建议在写论文前,把环境版本截图记录清楚。比如说“本项目的开发环境为 JDK 8、Maven 3.6.3、Node 16.20、MySQL 8.0”,这样的描述放到论文里非常加分。而不是等答辩老师问了一句“你用的什么版本”,你支支吾吾答不出来。

6.2 数据库初始化最容易卡住的点

数据库连接是新手重灾区。第一个问题是数据库账号密码不对,application.yml里面写的是root/123456,而你的 MySQL 密码是admin123,启动日志里就会一直报Access denied for user。第二个问题是时区,SpringBoot 连接 MySQL 8 时,如果连接串没加serverTimezone=Asia/Shanghai,可能报时区错误。第三个问题是Public Key Retrieval is not allowed,需要在连接参数上加上allowPublicKeyRetrieval=true。

一个保险的做法是把连接串写成这样:

spring: datasource: url: jdbc:mysql://localhost:3306/book_recommend?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driver

如果你启动时看不到报错,但接口一查询就乱码,那大概率是数据库字符集问题。建库的时候建议用:

CREATE DATABASE book_recommend DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

表结构里的中文注释能不能正常显示,也取决于字符集。这个小细节,很多项目资料里不会写,但实测中特别容易踩。

6.3 跨域问题:前后端分离最常见的拦路虎

前端跑在 5173 或 8087,后端跑在 8080,端口不一致就必然触发跨域。解决跨域有两条主流路线:后端配置 CORS,或者前端通过代理转发。

后端配置 CORS 很简单,写一个配置类实现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); } }

但我更推荐前端代理方案。在vite.config.js里配置:

server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } }

这样前端代码里请求的是/api/xxx,由开发服务器转发到后端,浏览器看到的是同源请求,完全绕开了跨域。而且以后部署到 Nginx 上,也可以沿用同样的代理逻辑,不需要改后端代码。我见过不少项目在前端里把http://localhost:8080写死发请求,开发时开了代理就没问题,一打包部署就跨域,就是因为这个原因。

6.4 前后端打包与部署

毕设现场演示一般跑在本地,但有些学校会要求线上答辩或提交部署记录,所以打包部署能力也算基本要求。后端打包很简单,在项目根目录执行:

mvn clean package -DskipTests

打包后 target 目录下会生成一个 jar 包,通过java -jar 项目名.jar就能启动。注意打包前检查配置文件里的数据库地址是不是换成了线上地址。

前端打包更简单:

npm run build

生成的是dist目录,里面是纯静态文件,扔到 Nginx 的 html 目录下就能访问。Nginx 还需要配一个反向代理,把/api开头的请求转发到后端:

server { listen 80; server_name localhost; root /usr/share/nginx/html; index index.html; location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location / { try_files $uri $uri/ /index.html; } }

最后一行很重要,因为 Vue 是单页应用,路由刷新时如果不做try_files,页面会 404。这个坑几乎每个人都会遇到,提前写进 Nginx 配置里能省不少事。

7. 给毕设答辩和学习提升的建议

7.1 推荐算法怎么讲才不露怯

答辩时最怕的不是老师问“你的算法是什么”,而是只答一句“我用了协同过滤”。老师立刻会追问:协同过滤分几种?你用的是哪种?相似度公式是什么?为什么不用另一种?

我的建议是准备一条“算法讲解链路”:

  1. 一句话概述:本系统采用基于用户的协同过滤算法,核心思想是聚合相似用户的偏好来产生推荐;
  2. 画出步骤图:数据获取 → 评分矩阵 → 相似度计算 → 邻居选择 → 生成推荐;
  3. 在黑板或者 PPT 上写一遍余弦相似度公式,并解释分母和分子的含义;
  4. 结合项目说数据来源:评分表、借阅记录、收藏记录如何转换成分数;
  5. 承认缺点:协同过滤存在冷启动和数据稀疏问题,本项目通过热门推荐和内容特征进行缓解。

如果你还能把“隐式反馈加权”和“预计算表”两个设计加进去,整个答辩会显得既有理论又有工程实践。老师想听到的恰恰是你能指出算法的局限,并且给出合理的工程化方案。

7.2 可以从哪些方向加分拓展

一套源码跑通只是起点,想拿高分,必须有自己的扩展工作。我推荐几个性价比比较高的方向:

  • 引入 Redis 做推荐结果缓存:把预计算表从 MySQL 搬到 Redis,访问更快,论文里能多写一整个性能对比章节;
  • 做成混合推荐:协同过滤 + 基于内容的知识推荐,冷启动效果更好,算法立体感更强;
  • 增加推荐效果评估:简单统计推荐列表里被用户点击的次数、借阅的次数,计算覆盖率、准确率,推导出“推荐是否真的有用”;
  • 加一个 Elasticsearch 搜索引擎:替换现有的 MySQL 模糊搜索,做一个独立的图书搜索页面;
  • 加前后端部署自动化:用 Docker 把 MySQL、后端、前端都用容器管理,提交部署文档。

选一个方向深挖三周,项目深度立刻拉开差距。毕设评分里“有创新点”和“没有创新点”通常是一个明显的分界,哪怕创新很小,也比纯抄源码强得多。

7.3 用这个项目学习框架的路线

最后聊一点学习层面的事情。如果你不是拿它应付答辩,而是真想通过这个项目学会 SpringBoot 和 Vue,那我的建议是分三步走。

第一步,什么都不要改,把这套系统完整跑通,每个页面点一遍,理解“请求从哪发起、到哪结束”。这一步的目标是把环境问题全部解决,建立全局观。

第二步,找一个自己最有兴趣的模块重写一遍。比如你觉得推荐算法太简单,就自己写一个基于物品的协同过滤;你觉得管理后台表格太丑,就自己加一个多条件筛选和导出 Excel 功能。改的过程中你会遇到十几个报错,那才是真正的学习。

第三步,尝试把一个完整的前后端流程从头实现一次。不看源码,只根据自己画的接口文档,从建表开始,写用户登录、图书列表、推荐展示三个功能。完成这步,你已经具备了独立开发一个前后端分离项目的能力,去实习或者面校招时,这个东西比任何证书都更有说服力。

对我个人来说,这种“图书 + 推荐 + 管理”组合的项目,最珍贵的地方不是代码本身,而是它把算法和工程完整地串在了一起。你写了一个公式,然后真的看得到那个公式在界面上给你推荐了一本书——这种正反馈,是在刷题和背概念时完全体会不到的。所以如果你手上正好有这样一套 SpringBoot + Vue 的图书推荐系统源码,别只把它当毕设作业,当成练手项目认真折腾一遍,收获会超出你的预期。

相关新闻

技能不是标签而是动作:用刻意练习、反馈与复盘构建底层能力

技能不是标签而是动作:用刻意练习、反馈与复盘构建底层能力

很多人都把“技能”当成一个名词来理解,觉得自己会写代码、会做PPT、会剪辑视频,就算拥有一项技能。但真正带过人、自己从零啃过几门硬技能的人会知道,技能其实是动词——它是你每天都要做的一个动作,而不是简历上既能唬人又能自我…

2026/10/20 12:46:33 阅读更多 →
把涂鸦设备搬回局域网:Tuya-Local 本地控制与 Home Assistant 配置指南

把涂鸦设备搬回局域网:Tuya-Local 本地控制与 Home Assistant 配置指南

把涂鸦设备搬回局域网:Tuya-Local 本地控制与 Home Assistant 配置指南 【免费下载链接】tuya-local Local support for Tuya devices in Home Assistant 项目地址: https://gitcode.com/GitHub_Trending/tu/tuya-local Tuya-Local 是 Home Assistant 的一个…

2026/10/20 12:46:33 阅读更多 →
MCP协议驱动智能体底座架构设计:从插件思维到接口思维

MCP协议驱动智能体底座架构设计:从插件思维到接口思维

1. 从“插件思维”到“接口思维”的认知跃迁大多数人第一次接触MCP(Model Context Protocol,模型上下文协议)时,脑子里蹦出来的第一个词就是“插件系统”。这也不奇怪,毕竟它的外在表现确实像插件:你写一个…

2026/10/20 12:45:32 阅读更多 →

最新新闻

点微同城源码34.7部署全流程:从环境配置到小程序联调避坑指南

点微同城源码34.7部署全流程:从环境配置到小程序联调避坑指南

简介:点微同城源码34.7是一套面向本地生活服务领域的开源建站方案,适合具备PHP与小程序开发基础、希望快速搭建类似美团或大众点评平台的技术人员与创业者。资源整合了H5网页端与微信小程序前后端代码,并附带全套功能插件,可覆盖信…

2026/10/20 13:29:25 阅读更多 →
SSM+Vue竞赛管理APP毕设实战:从需求拆解到答辩通关指南

SSM+Vue竞赛管理APP毕设实战:从需求拆解到答辩通关指南

毕设选题年年难,年年难选。如果你正在为2026年的毕业设计发愁,手里又恰好攥着一个“SSMVue竞赛管理APP”的题目,那这篇内容就是给你准备的。我见过太多同学卡在同一个地方——代码跑起来了,但不懂业务逻辑为什么这么设计&#xff…

2026/10/20 13:29:25 阅读更多 →
JSP+Servlet+MySQL相册系统实战:轻量级图片归档方案

JSP+Servlet+MySQL相册系统实战:轻量级图片归档方案

简介:这是一套基于JSP技术实现的轻量级相册管理系统源码,面向Java Web初学者与课程设计实践者,解决个人或小团队照片的上传、分类、查看与评论等核心管理需求。资源包共115个文件,含20个JSP页面(负责前端交互与展示&am…

2026/10/20 13:29:25 阅读更多 →
context-mode:上下文感知的模式切换架构设计与工程实践

context-mode:上下文感知的模式切换架构设计与工程实践

1. 从“上下文模式”这个命名说起:它到底在解决什么问题第一次看到“context-mode”这个词,很多人会下意识地把它当成某个框架里的配置项,或者某个编辑器插件的开关。但如果你真的在工程一线待过几年,就会发现这个词背后藏着一个非…

2026/10/20 13:29:25 阅读更多 →
Modbus TCP设备数据采集与防错系统实战:从通讯选型到落地

Modbus TCP设备数据采集与防错系统实战:从通讯选型到落地

前阵子帮一家制造车间做了一套设备数据采集与防错系统,从需求梳理到落地上线,前后折腾了两个多月。起因其实特别朴素:车间主任拿着两份产量报表来找我,一份是系统统计的,一份是人工点数出来的,对不上&#…

2026/10/20 13:29:25 阅读更多 →
PyTorch深度学习框架核心机制与工程实践全解析

PyTorch深度学习框架核心机制与工程实践全解析

1. 为什么“pytorch”这个标题值得单独拎出来聊如果你在技术社区里闲逛,看到有人只甩出“pytorch”这五个字母当标题,大概率会以为这是一篇入门安装教程。但真正在一线做过深度学习项目的人都知道,这个词背后能牵扯出的东西太多了——从张量计…

2026/10/20 13:28:24 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/19 17:32:41 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 10:02:08 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:45 阅读更多 →