先说个结论放在最前面:这套基于 Spring Boot 的新闻推荐系统,是一个非常适合用来打通“后端开发 + 推荐算法入门 + 毕设论文写作”三条线的项目。它没有把算法做得很高深,而是把真实系统里最常用、最容易落地的那套推荐思路完整地实现了出来——用户行为收集、内容画像、兴趣相似度计算、结果更新,全都有。无论你是想拿来应付毕业设计,还是想通过一个完整项目来反推 Spring Boot 的各种实战写法,这个源码包里的工程结构和论文内容都值得认真拆一拆。
我实际把源码整个过了一遍。从工程代码,到数据库设计,再到论文的每章布局,整体属于“结构清楚、没有为了炫技而盲目堆框架”的那类项目。下面我把这套系统的核心设计、推荐算法的代码级实现思路、Spring Boot 项目的落地细节,以及我在跑通和排查过程中踩到过的坑全部整理出来。内容会比较细,建议先收藏再对照源码慢慢看。
1. 项目整体设计与需求拆解
1.1 为什么选“新闻推荐”作为选题
做技术类毕业设计或者个人项目,选题是第一步,也直接决定后面的工作量。新闻推荐系统这个选题妙就妙在它“可深可浅、处处有事做”。
从技术栈覆盖来看,Spring Boot 项目通常绕不开用户管理、内容管理、数据存储、接口设计这几个基本盘,这些在新闻系统里全都有天然的业务载体。新闻网站的场景又是纯内容、无交易、无支付,逻辑链条简单,非常适合用来梳理 MVC 架构和后端工程的完整流程。
而从推荐算法角度切入更加平滑:新闻属于短文本内容,天然适合用 TF-IDF、分词、余弦相似度这类经典文本匹配方法来做。用户对新闻的浏览行为(点击、收藏、点赞)也是显性反馈里最容易收集的数据。这就意味着,推荐引擎可以做到“有算法含量,但不用上深度学习”,工作量可控,论文也写得出东西来。
单从工作量来评估的话,这套系统属于中等偏上的水平,既不会让自己天天对着空数据库发呆,也不至于陷入大型分布式项目那种“写不完整、讲不清楚”的泥潭。
1.2 功能模块拆解:基础功能 + 推荐功能
整套系统的功能可以分成两大块:一块是新闻网站本身该有的基本功能,另一块是围绕推荐引擎展开的数据收集和推荐展示功能。
基础功能部分,主要包括:
- 用户注册与登录,支持用户与管理员两种角色的权限区分
- 新闻频道管理,也就是常说的新闻分类,比如科技、体育、财经、娱乐
- 新闻内容管理,后台能发布、编辑、上下架新闻
- 新闻浏览列表,按频道展示新闻摘要列表
- 新闻详情页,包含正文内容、发布时间、来源、阅读次数
- 用户行为记录,浏览、收藏、点赞等关键动作会落到数据库里
推荐功能部分是整个系统的核心亮点,也是论文中最重要的篇章。它做的事情是:根据用户过去的行为记录,分析出用户对哪些内容主题感兴趣,然后从全量新闻库里筛选出用户最可能感兴趣的新闻推荐给用户。落到页面上的表现就是推荐列表页,或者带“猜你喜欢”标签的新闻流。
系统还做了一层很关键的博弈处理:如果没有用户行为数据,也就是一个全新的注册用户,就按热度来推荐,也就是按阅读量、发布时间计算一个热度分。这个冷启动策略在工程里是必须有的,因为任何推荐系统刚上线时都存在冷启动问题,先把新闻推出去,用户才开始有点击记录,算法才有数据可算。
管理端的功能则集中在后台,新闻发布、频道管理、用户列表查看、行为数据统计,基本覆盖了一个内容型网站的后台管理需求。
1.3 数据库表设计:推荐的“证据链”都在表里
数据库设计这部分我建议认真看,因为推荐算法要算得准,前提是行为数据被完整、规范地记录下来了。这套系统的核心表设计大体是这么几条线:
用户线
用户表(user)字段比较常规:用户ID、用户名、密码、昵称、角色、头像、注册时间。这条线主要负责权限判断和作为行为数据的“主体引用”。
内容线
频道表(category)存频道ID、频道名称、排序权重。新闻表(news)的字段就多一些了:标题、摘要、正文内容、所属频道、封面图、发布人、发布时间、阅读量。正文内容字段建议用TEXT类型,摘要用VARCHAR,这样既保证数据存储在合理区间,也不影响列表页的查询速度。
行为线
行为数据是推荐系统的命根子。浏览记录表、收藏表、点赞表的逻辑类似:用户ID + 新闻ID + 行为时间。有的表还会冗余一个分类ID进去,这样做推荐算法的时候可以少关联一次新闻表,直接用行为表里的分类字段做统计聚合。
从设计的角度说,“行为表里冗余内容分类”这个细节非常实际。算用户兴趣标签时,最常用的逻辑就是“用户看过哪些频道最多”,如果行为表直接带分类字段,一条 SQL 就能搞定统计,不需要反复 join 新闻表,效率高很多。这个细节在论文的数据表设计章节里也是个不错的加分点。
推荐结果线
部分版本的系统里还会配置一张推荐日志表,用来记录“某一天给某用户推荐了哪些新闻,用户是否点击了”。这张表单独存在是为了后续评估算法效果,比如算一算推荐点击率,论文里加一段这样的内容,实验数据就有了来源,说服力和完整性都会明显提高。
整体看下来,这套系统的数据库表数量在 8 到 12 张之间,属于比较典型的中型毕设项目规模。表设计不算复杂,但字段完整度足够,业务闭环是通顺的。
2. 推荐算法落地:从理论到代码
2.1 算法选型背后的思考路径
刚开始接触推荐系统的人,容易一上来就想着用神经网络,或者堆一套基于深度学习的向量召回。实际上在毕设项目和个人项目里,这种选择往往两头不讨好:一是需要大量的训练数据才能出效果,二是论文的数学推导自己未必讲得清楚,答辩时被追问几句就很容易露馅。
这套系统的推荐模块走的是“基于内容的推荐为主 + 热度补位”的方案,这个选型是很务实的。原理上讲,它做的事情非常直接:分析用户浏览过的历史新闻,把新闻拆成主题关键词,按主题统计出用户的兴趣权重,再拿着这个具体的用户兴趣画像去全量新闻里做相似度排序,把最相近的几条返回给前端。
这种方案的优点在于:
- 项目不需要构造复杂的用户-物品交互矩阵,做基于物品的协同过滤时,稀疏矩阵的处理常常会消耗大量精力,业务价值还不明显
- 对论文而言,核心算法的数学公式清晰可控,余弦相似度、TF-IDF 权重计算都能写出完整的推导过程
- 在实际运行效果上,对刚注册的新用户和中等活跃用户都能有不错的推荐体验
2.2 基于内容的推荐核心实现
基于内容的推荐大体分三步:内容表示、兴趣画像构建、相似度计算。下面分别展开说明。
第一步:内容表示,关键字段的权重分配
新闻是短文本,内容表示相对简单。最粗暴的做法是把整篇正文扔进分词器里算 TF-IDF,然后拼出一个关键词权重向量。不过做得更精细一点的话,标题和摘要的重要性应该被额外放大,因为这些位置的词语信息密度远高于正文。
在实际处理中,我建议这样组织:
- 把标题、摘要、正文拼接成一个文本串
- 如果使用了 HanLP 分词器,可以对标题按自定义词典做精确模式分词
- 对分词后的词条计算 TF 权重,再配合 IDF 得到每个词的 TF-IDF 值
- 存
JSON或冗余字段时,取出 Top 20 到 Top 50 的关键词及其权重作为这篇新闻的内容特征向量
标题拼进去和直接只算正文效果差别明显。标题里往往会直接出现“苹果”“新能源车”“美联储”这类强主题词,如果不单独加权,这些词的 TF 值会被正文长文本稀释掉。一个相对简单的做法是:把标题、摘要、正文分别按 3 : 2 : 1 的权重参与 TF 计算,实测下来推荐结果的精度会明显好于平铺处理。
第二步:兴趣画像构建,核心是“统计”
用户的兴趣画像本质上是一组词权重,表示这个用户对哪些主题词更感兴趣。最浅层的计算方式是直接统计用户的浏览历史,算出每个频道出现的次数,然后按频次排序。如果要做得细一点,就把浏览过的每篇新闻的 Top 关键词累加起来,再按权重排序。
伪代码风格的核心逻辑如下:
用户兴趣画像 = 空映射表 for 每条浏览/收藏记录: 新闻 = 查询新闻表获取内容特征 for word, weight in 新闻的关键词列表: 用户兴趣画像[word] += weight * (1 + 行为类型加成系数)行为类型加成系数可以这样给:收藏行为给 1.5 倍加权,点赞行为给 1.2 倍,纯浏览行为给 1 倍。这么做在逻辑上更合理,因为主动收藏比顺手点开的一篇新闻更能反映真实兴趣。如果数据库里用一张行为明细表存了不同类型的行为记录,推荐模块里加这个加权逻辑非常容易。
第三步:相似度计算与 TopK 推荐
拿到用户兴趣画像后,要推荐给用户的新闻就是找出画像和新闻的相似度最高的那一批。这里选用余弦相似度即可,不需要引入太复杂的数学工具。两个向量越相似,夹角越小,余弦值越接近 1。
单看公式不直观,举个例子:用户画像里的“苹果”权重是 3.2、“手机”权重是 2.8;待推荐的新闻 A 的“苹果”权重是 1.5、“手机”权重是 0。两个向量点积结果是 3.2 × 1.5 = 4.8,再除以两个向量的模长,算出来的余弦相似度是一个 0 到 1 之间的值。所有候选新闻都算一遍,按从高到低排序取 Top 10,推荐列表就出来了。
这套逻辑用 Java 实现,几十行核心代码就能完成。为了性能,接口里可以考虑对全量新闻预计算好特征向量并缓存到内存中,避免每次推荐都查一遍数据库然后现场分词,实测下来在几千篇新闻的数据规模下性能没有任何问题。
2.3 冷启动与热度兜底策略
冷启动问题必须单独考虑,因为新注册用户的行为记录是空的,没有数据可以分析兴趣。常见做法当然不是硬推算法,而是先用一个“热门新闻”列表顶上去。
这套系统的做法倾向于:按阅读量降序,或者按阅读量、发布时间、评论数做一个简单的热度分公式。热度分公式不需要太复杂,字段权重可以这样设计:
热度分 = 阅读量 * 1.0 + 发布时间衰减系数 * 10发布时间衰减系数可以用一个递减函数来表示,比如新闻发布超过 24 小时后,衰减系数从 1 开始逐步下降,这样既能保证新新闻有较高的初始热度,又不至于让“几天前的爆款”永远霸榜。
用的具体公式形式可以自己选,但要注意在论文里把公式写出来,并把每个参数的含义解释清楚。这部分内容是答辩中用户提问频率最高的地方,可以提前准备好。
3. Spring Boot 工程实现与核心代码细节
3.1 工程结构与依赖选型
这套系统的工程是标准的多模块 Monolith 布局,用 Maven 构建,目录结构大体如下:
src ├── main │ ├── java │ │ └── com.xx.news │ │ ├── controller │ │ ├── service │ │ ├── mapper │ │ ├── entity │ │ └── config │ └── resources │ ├── mapper │ ├── static │ └── application.yml └── test分层方式就是经典的六边形结构:Controller 层负责接收请求和参数校验;Service 层承载核心业务逻辑,包括推荐算法的编排;Mapper 层用 MyBatis 操作数据库;实体类直接对表结构。项目如果用的是 Spring Boot 3.x 或 Spring Boot 2.7 版本,依赖版本会有差异,这个章节后面单独讲避坑问题。
核心依赖大体如下:
- Spring Boot 基础 Web 依赖,用于对外提供 REST 接口
- MyBatis Plus 或 MyBatis,做数据访问,如果用了 Plus,推荐单表 CRUD 速度会明显提升
- MySQL 驱动
- JWT 或 Spring Security 做登录鉴权,简单的项目直接用一个 JWT 工具类就够了,不必引入完整的 Security 全家桶,否则配置量会明显增加
- HanLP 或其他分词库,用于中文分词和关键词提取
- Lombok,减少实体类的样板代码
选型上没有过度堆砌框架,我觉得是合适的。Spring Security 对于新闻系统来说功能过大,如果只是想实现一个前后端分离的登录态校验,手写 JWT 拦截器是性价比最高的方案。
3.2 登录鉴权:JWT 还是 Session
这套系统的登录方案建议直接用 JWT。在前后端分离的项目里,JWT 最大的优势是服务端无状态,用户请求过来只要验签通过就放行,不需要在服务端维护 SessionMap,水平扩展时少了一层会话同步的麻烦。
具体实现上,登录成功后服务端生成一个 Token,把用户ID和角色信息写进 Token 里,设置合理的过期时间,返回给前端。前端每次请求带上请求头,后端加一个拦截器统一校验 Token 是否有效。
JWT 的密钥要注意不能写在代码里写死。至少放到配置文件里,用环境变量注入。虽然毕设项目里对外暴露风险不大,但论文里面把 “配置外部化” 作为安全性设计来写,算是一个容易加分的小地方。
3.3 新闻发布、频道分类与检索功能
管理端发布新闻的核心表操作只有几步:接收页面传来的标题、摘要、正文、频道等信息,组装成实体,交给 Mapper 层完成INSERT。发布过程中需要做好的两个细节是防 XSS 和排版问题。
富文本编辑器中如果允许填写任意 HTML 内容,需要做一层白名单过滤,否则会带来脚本注入问题。论文里的安全模块可以拿这一点来写:使用 Jsoup 的clean()方法,只保留安全的标签和属性。
如果要加全文检索,这里有一个 I/O 上的选择:如果数据量只有几千篇,直接用LIKE '%关键词%'查询就够了,方便且无需引入额外的搜索引擎。如果项目里想展示更完整的方案,可以在论文里探讨接入 Elasticsearch 的可行性。基于毕设项目的硬件环境,本地安装 ES 是可行的,但如果时间紧张,直接依赖 MySQL 的模糊搜索在数据量小的前提下完全撑得住。
3.4 推荐接口与定时任务刷新
推荐接口的逻辑不提倡每次请求都现场计算,因为先查行为、再查新闻、再做分词和相似度计算,整个过程耗时比较高,用户等待时间会明显变长。更合理的方式是用缓存把推荐结果保存一段时间。
实现路径有两种:
一是简单粗暴,用户第一次访问推荐接口时算好结果放进 Redis 或本地缓存,设置缓存过期时间(比如 30 分钟),过期后重新计算。这种方案的优点是代码简单,适合毕设。
二是用 Spring Boot 自带的定时任务能力,每隔固定时间批量计算活跃用户的前 N 条推荐结果,存到推荐结果表里。用户请求推荐页时直接查询推荐表。这种方案的推荐结果更新更稳定,论文的实验数据也更完整,但实现量稍大一点。
两种方案选哪一种都行,看自己的精力。我建议选第二种更稳妥一些——毕设答辩时,讲“推荐结果是持久化存储的,通过定时任务周期刷新”比“用户请求时现算”要更有说服力,而且顺带把 Quartz 或 Spring Scheduler 的使用写进技术选型章节,丰富了技术栈的展现。
关于定时任务,有一个细节值得注意:定时任务的方法一定要加好日志。比如每次刷新任务运行结束时,打印一行日志,记录本轮刷新了多少用户、耗时多少毫秒。这在对账推荐接口返回数据、排查刷新未生效时作用很大,效率会高不少。
4. 实战中的坑与排查经验
4.1 Spring Boot 版本太高引发的配置问题
在实践过程中,我发现很多开发者或同学拿到的源码里有 Spring Boot 版本太高的情况。如果本地装的是 JDK 8,却去跑一个 Spring Boot 3.x 的项目,项目启动时大概率会直接报错,或者运行到一半因为依赖注入方式不兼容而出各种诡异问题。
Spring Boot 3.x 从底层要求的是 Java 17 及以上。因此,拿到源码后第一件事,就是确认本地的 JDK 版本,再决定当前源码是否可以直接运行。如果本地只有 JDK 8 而源码是 Boot 3.x,有两个方向可以选择:
- 换本地的 JDK 版本到 17
- 把 pom.xml 里的 Spring Boot 版本降到 2.7.x,同时把
javax依赖调整为jakarta命名空间
很多网上现成的源码并没有把这个问题写在部署文档里,文档里往往默认“我本地能跑你也能跑”。这其实是碰到源码类项目时最常见也最坑的地方,很多同学在一开始就会被这个问题直接劝退。
解决办法其实不复杂:启动项目后如果遇到UnsupportedClassVersionError,那就是 JDK 版本不对。确认版本匹配后再去做别的排错,能省下很多无头苍蝇式的时间。
4.2 MySQL 时区与中文乱码问题
数据库连接串里如果没有配置时区参数,比如serverTimezone=Asia/Shanghai,新版本的 MySQL 驱动在连接时会直接抛异常。即使正好绕过了,插入中文数据后读出乱码,基本也是连接串里缺少characterEncoding=utf8导致的。
连接串的正确写法建议保持这样的完整格式:
jdbc:mysql://localhost:3306/news_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false另外还可以通过建库语句指定字符集,双保险。演示环境碰到乱码问题,排查优先级最高的就是:数据库字符集、连接串参数、前端页面字符集。这三层全部统一为 UTF-8,问题一般不会出现。
4.3 MyBatis 里的循环依赖与事务失效问题
单模块项目里最容易出现的事务坑是@Transactional自调用失效。比如在 Service 的一个事务方法里,通过this.xxx()调用同一个类的另一个带事务注解的方法,Spring AOP 默认代理方式下,事务注解不会生效。解决办法是拆成两个不同的 Service,或者直接注入自身代理。这类问题在论文的“系统测试”章节里可以作为经典 Bug 来描述,体现排除问题的能力。
另外,如果在 Mapper 接口方法中直接写多表 join 查询,字段名映射要对齐。最好在 SQL 查询时,给每个查询列写上别名。MyBatis 返回Map时,如果列名带下划线而实体字段是驼峰命名,要确保mapUnderscoreToCamelCase配置是开启的,否则查出来的对象属性全是空。
4.4 行为重复导致推荐权重被刷
行为记录表的统计逻辑没有做去重时,会带来一个隐患:用户反复刷新同一个详情页,浏览记录表就会插入多条相同记录,用户画像里“某篇新闻的关键词”就会被反复累加放大,导致推荐结果全被这篇新闻的同主题内容霸占,多样性大幅下降。
合理做法是:插入浏览记录前先判断同一用户、同一新闻在短时间内是否已有记录。简单去重逻辑可以这样设计:如果 10 分钟内有重复浏览记录,则不再新增;或者把行为表加一个唯一索引(user_id + news_id + 行为日期)。做好这层防护后,用户画像的稳定性会明显提升,推荐列表也正常得多。
5. 从源码到论文:怎么把项目价值讲透
5.1 论文的整体结构与写作思路
从这套源码自带的论文来看,结构是按标准毕业论文模板来的,大致是:绪论、相关技术介绍、系统分析与设计、系统实现、系统测试、总结与展望。真正拉开分差的核心在于每个章节里“为什么选这个技术”要写清楚。
不建议把相关技术介绍写成教科书式的名词解释,而是在介绍每一项技术时都加上一句“本项目选择它的原因”。比如介绍 HanLP 时,可以明确说选择它是因为新闻文本分词是中文场景,英文分词器处理不了中文分词问题。这样的写法在答辩时非常加分,因为它直接展示了思考过程。
系统设计一章,重点写清楚产品需求如何转化为功能模块,再落到表结构设计上,最后用用例图、流程图把流程表达清楚。这里要注意的是,图的逻辑必须和代码实现一致,图上画的流程如果代码里没实现,答辩时被追问很容易演砸。
系统实现这一章是论文篇幅最多的部分,建议按功能模块分小节来写,每一个小节按“实现思路 + 关键代码 + 实现效果截图”的结构来展开。每段代码贴出来后,要重点解说这段代码解决了什么问题,而不是代码本身是什么。
5.2 推荐算法的实验设计与对比分析
论文要做出真实效果,实验数据环节建议这样设计:从数据库中导出行为记录和新闻数据,用留存数据跑一遍推荐算法,计算推荐列表与实际点击行为的重合度,或者统计推荐结果中用户点击占比。有数字做支撑,系统测试章节就有真实的内容可以写了。
如果拿不到线上数据,也可以手动模拟一批数据:准备 10 个用户,每个用户都人工分配几条不同频道的浏览记录,然后运行推荐接口,检查返回结果是符合预期。这个过程建议写成表格形式,记录每个用户的历史兴趣频道和推荐结果的对应情况。这样做出来的实验数据完全可信,比空口说一句“推荐效果良好”强无数倍。
5.3 答辩演示准备的一些经验
代码写完了论文写好了,离顺利答辩还差临门一脚——现场演示。演示顺序建议这样安排:
先打开前台页面,直接展示新闻列表页和新闻详情页,让评委直观理解系统是做什么的。切到新注册一个用户,此时推荐页显示了热门新闻,顺便解释冷启动策略。然后用这个新用户去浏览几篇科技类新闻,刷新推荐页,观察推荐结果变化,这时候基于内容的推荐就现场可感了。最后切到后台管理端,发布一篇新新闻,再回到前台确认新内容出现,证明管理闭环是完整的。
演示过程中的核心原则是“提前固定数据”。不要在演示现场临时造数据,尤其是用户行为数据,现场做容易拖慢节奏。所有数据提前用脚本整理好,演示时只要按顺序展示即可。把这一步准备充分了,答辩效果会稳妥很多。
一点实战收尾心得
这套基于 Spring Boot 的新闻推荐系统,前前后后我梳理了两遍,最大的感触是:它的架构和代码并不复杂,但业务闭环完整度相当高,从用户注册、新闻管理、行为记录到推荐计算,链条是通的,非常适合拿来作为学习和改造的底子。
如果后续你想在这个基础上继续扩展,比较推荐的两个方向:一是把用户行为上报改造成埋点接口,前端页面定时上报浏览时长,后端用消息队列异步处理,推荐画像会精细得多;二是引入协同过滤,在现有基于内容的推荐结果基础上,加一个“看过这篇新闻的人还看过什么”的关联推荐通道,推荐结果的多样性会有明显提升。但如果你现在的目标是把毕设顺利做完、把论文写扎实,那当前这套系统本身的完成度已经足够撑起一个合格的答辩了。
最后再分享一个我在实际使用时觉得最有效的小习惯:任何改动上线前,先把推荐接口的入参、返回值和数据库里的行为数据对一遍。90% 的“推荐结果不对”问题,最后发现都不是算法算错了,而是行为数据没记全、或者缓存没刷新。把数据链路理清楚了,这个项目的每个环节你都会觉得非常顺手。