ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

SpringBoot图书推荐系统毕设实战:协同过滤算法原理与落地

SpringBoot图书推荐系统毕设实战:协同过滤算法原理与落地

图书推荐系统这个毕设题目,我前前后后接触过不少回。每年毕设季都会有人拿着类似的题目来找我:"SpringBoot+推荐算法"能做出什么效果?推荐算法要怎么落地才不显得水?调试跑不起来怎么办?说实话,这个题目的价值被很多人低估了。推荐算法在图书场景下是天然适配的:用户有评分、借阅、收藏行为,图书有分类、作者、标签等结构化属性,数据模型干净,算法效果可以直观验证。它既不是"纯管理系统凑合"的平庸选题,也不是"算法堆砌缺乏工程落地"的空中楼阁。这篇内容我就从选题价值、技术选型、系统设计、算法实现到调试运行,把整个项目的开发链路完整拆一遍,给正在做或者准备做这个题目的同学切实的参考。


1. 图书推荐系统这个选题,到底值不值得做

1.1 为什么推荐算法偏偏要在图书场景落地

很多同学一听到"推荐系统"就想到电商、短视频、外卖,觉得图书这个场景太普通。但正因为普通,它才是毕设的优质土壤。电商推荐涉及价格、库存、物流这些业务噪音,短视频推荐涉及实时流的构造和内容理解,复杂度拉满。图书推荐则是一个高度浓缩的推荐场景:用户行为以评分、收藏、借阅为主,物品属性以分类、作者、出版社、简介为主,两者之间的匹配关系可以用经典的协同过滤覆盖,也能用基于内容的思路补充。数据体量不需要百万级,几千条评分记录就能让算法跑出效果,这对本科毕设非常友好。

从评审角度看,图书推荐系统的需求分析容易讲清楚,功能边界一目了然,推荐算法的接入又让系统有了"智能化"的亮点。相比单纯的管理系统,它多了一个可以展开论述的技术核心;相比那些动辄分布式、大数据的题目,它又不会让基础薄弱的同学陷进去出不来。

1.2 我要先说清楚这个项目的工作量构成

一个完整的图书推荐系统,工作量通常分四块。

第一块是基础功能,用户注册登录、图书管理、分类浏览、搜索分页这些,大概占四成工作量。第二块是业务功能,收藏、评分、借阅记录、个人中心,大概占两成。第三块是推荐模块,算法逻辑、数据预处理、结果展示,大概占三成。最后是文档和答辩准备,写论文、画图、做PPT,大概占一成,但这一成往往最花时间。

我见过的失败案例基本都是前面两块花掉了太多时间,留给推荐算法的精力严重不足。有的人做到最后,推荐模块只是简单地"按评分倒序取前十条",美其名曰"热门推荐"。这种功能在答辩现场很容易被追问到无话可说。正确的做法是先规划好推荐模块的深度,再去铺开基础功能。基础功能做到够用就行,把省下来的时间砸在算法效果和实验对比上,性价比最高。

1.3 什么样的同学适合选这个题目

如果你有一定的Java基础,SpringBoot的基本使用已经能上手,MyBatis或MyBatis-Plus会基本的增删改查,那么这个题目是你可以驾驭的。推荐算法部分需要的数学基础其实很低,余弦相似度、皮尔逊相关系数这些都是本科概率统计的内容,而且实际编码中往往直接用现成公式,不需要手推矩阵分解。如果你想挑战一下自己,可以在协同过滤基础上做混合推荐,或者引入TF-IDF做基于内容的推荐,这会让论文的"创新点"更充实。

如果你Java基础比较薄弱,但愿意花两周时间把SpringBoot的请求处理流程、MyBatis的Mapper写法、前端页面和后端接口的交互方式搞清楚,这个题目一样能做下来。关键在于不要一上来就啃源码,而是先跑通最小闭环:一个页面、一个接口、一张表,让整个链路流畅起来。


2. 技术选型:为什么是SpringBoot+协同过滤

2.1 服务端框架选型的前因后果

SpringBoot在这个题目里几乎是唯一解。原因不外乎这几点:第一,社区资料极其丰富,你遇到的问题别人基本都遇到过,搜索引擎一抓一大把答案;第二,内置Tomcat,一条命令就能启动,省去了配置外部服务器的麻烦;第三,生态完善,Spring Data JPA、Spring Security、接口文档生成工具都有成熟方案。

不过SpringBoot版本的选择值得留意。我见过太多同学因为用了过高或过低的版本,导致依赖冲突、配置不兼容,白白浪费一两天时间。个人建议是选择当前较稳定的2.x版本,比如2.7.x系列,搭配JDK 8或JDK 11。3.x系列不是不能用,但部分第三方工具(比如某些代码生成器、旧版连接池)可能出现兼容问题,对毕设来说没必要冒这个险。

2.2 推荐算法选型:协同过滤为什么是首选

推荐算法家族很大,大致分成三类。

基于内容的推荐,思路是分析用户历史上喜欢图书的特征,再推荐具有相似特征的图书。它的优点是解释性强,新书没有评分也能推。缺点是特征提取要做不少工作,而且容易陷入"都是同一类书"的信息茧房。

协同过滤推荐,又分成基于用户(UserCF)和基于物品(ItemCF)两类。核心思想是"相似的人喜欢相似的东西"或者"喜欢这个物品的人也喜欢那个物品"。它不需要理解图书内容,只需要评分和购买行为数据,实现直接,效果肉眼可见。

基于模型的推荐,典型代表是矩阵分解、隐语义模型、深度学习。效果好,但模型训练、参数调优、结果评估的复杂度对毕设来说偏高,而且没有足够数据时效果反而不如协同过滤。

对于图书推荐系统这个题目,用户不会很多,图书数量在几百到几千的规模,评分数据天然稀疏但结构清晰,协同过滤不仅是首选,也是最稳的选择。我通常建议在系统里同时实现UserCF和ItemCF,做一个简单的策略融合或者对比展示,这样论文和答辩时的"技术深度"就有了支撑点。

2.3 数据库与持久层:细节决定开发效率

数据库用MySQL没什么悬念,成本低、环境好搭。需要重点考虑的是表结构设计和数据量。毕设项目里的用户表、图书表、评分表、收藏表加起来不超过十张,用MySQL完全够用,而且方便展示SQL语句和索引设计。

持久层我推荐MyBatis-Plus,而不是原生MyBatis。省去大量XML配置,单表CRUD直接继承BaseMapper就能用,分页插件、条件构造器也都是开箱即用。如果项目里需要写复杂的多表联查,MyBatis-Plus也支持自定义SQL,不会限制发挥。

有一点要提醒:不要在数据库设计阶段就抠"完美范式"。图书搜索、分类推荐这些场景经常需要冗余字段,比如图书表里直接冗余一个"评分平均值"字段,每次评选更新,这样推荐模块读取时就少一次联表查询。毕设系统性能要求不高,只要字段没有明显冗余混乱,适度反范式是可以接受的。


3. 系统功能模块与数据库设计:先把架构立起来

3.1 用户端和管理端的功能划分

图书推荐系统整体分用户端和管理端。

用户端面向普通读者。核心功能包括:用户注册登录、图书检索(按书名、作者、分类)、图书详情查看、评分、收藏、借阅记录、个人信息管理,以及两个核心推荐入口——"猜你喜欢"和"相似图书推荐"。

管理端面向系统管理员。核心功能包括:图书信息的上架、编辑、下架,分类管理,用户管理,评分和借阅数据查看,以及推荐算法参数的配置(比如相似度阈值、推荐条数)。很多人觉得管理端不重要,其实管理端恰恰是数据来源的保障。没有数据,推荐算法就是无米之炊。

3.2 核心表结构设计要点

一张好的表结构是项目后续开发顺畅的前提。以这个系统为例,最少需要这几张表。

用户表(t_user)存用户基本信息,注册时间、用户名、密码等,密码记得加密存储。

图书表(t_book)是核心主表,字段包括书名、作者、出版社、ISBN、分类ID、封面图URL、简介、出版日期、总点击量等。评分平均值可以冗余到这里,或者单独存储。

评分表(t_rating)是推荐算法的数据来源。字段包括用户ID、图书ID、评分值、评分时间。每次评分行为都要落库,推荐模块会从这里取全部评分数据构建用户-物品矩阵。这张表务必加上联合索引(user_id, book_id),否则数据量上来后查询会明显变慢。

收藏表(t_favorite)存用户的收藏行为,可以用来补充"隐式反馈"数据——没有评分但有收藏,说明用户是感兴趣的。

借阅表(t_borrow)存借阅记录,包含借出时间和归还时间,既是业务功能,也是推荐数据的重要补充。

3.3 后端接口设计的大致轮廓

接口设计要围绕前端页面需要的"动作"来规划,而不是围绕数据库操作。

用户模块接口:注册、登录、退出、获取用户信息、更新用户信息。

图书模块接口:图书列表(分页)、图书详情、按分类查询、搜索、热门图书、最新图书。

交互模块接口:添加评分、取消评分、添加收藏、取消收藏、借阅图书、归还图书。

推荐模块接口:获取"猜你喜欢"列表、获取"相似图书"列表、获取近期热门推荐。

推荐模块的接口值得多说一句。很多项目喜欢在用户请求推荐时才实时跑算法,这样体验很不好,而且用户等待时间可能长达数秒。更合理的做法是:系统定时离线计算,把每个用户的推荐结果存到缓存或推荐结果表里,接口只需要读取已经算好的结果返回即可。这一点后面算法落地部分会详细展开。


4. 推荐算法落地:协同过滤的原理与代码实现

4.1 从用户-物品评分矩阵说起

协同过滤的起点是一张评分矩阵。矩阵的行是用户,列是图书,单元格是用户对图书的评分。如果用户没有对某本书评分,单元格就是空的。

假设现在有3个用户和4本书,矩阵可能是这样:

书A书B书C书D
用户1534
用户242
用户3253

推荐算法要回答的问题就是:用户3没有对书A评过分,但系统猜测他会喜欢书A吗?如果会,就把书A推荐给用户3。

这个矩阵在代码里就是一个二维数组,或者用Map嵌套结构来模拟。获取数据的方式很简单,从评分表里一次查出来,然后按用户分组构建成矩阵。数据量在毕设范围内,内存完全装得下。

4.2 基于用户的协同过滤(UserCF)三步走

基于用户的协同过滤的思路看起来像"人以群分":找到和当前用户口味最相似的一群用户,看看这群用户喜欢了什么当前用户没接触过的书,然后推荐给当前用户。

第一步,计算用户之间的相似度。常用的方式是皮尔逊相关系数或余弦相似度。这里给一个适合初学者的余弦相似度实现思路:

public double cosineSimilarity(Map<Long, Double> user1Ratings, Map<Long, Double> user2Ratings) { // 找出两个用户共同评过分的图书 Set<Long> commonBooks = new HashSet<>(user1Ratings.keySet()); commonBooks.retainAll(user2Ratings.keySet()); if (commonBooks.isEmpty()) { return 0.0; } double dotProduct = 0.0; double norm1 = 0.0; double norm2 = 0.0; for (Long bookId : commonBooks) { dotProduct += user1Ratings.get(bookId) * user2Ratings.get(bookId); } for (Double rating : user1Ratings.values()) { norm1 += rating * rating; } for (Double rating : user2Ratings.values()) { norm2 += rating * rating; } if (norm1 == 0.0 || norm2 == 0.0) { return 0.0; } return dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2)); }

第二步,找到K个最相似的用户。对当前用户,遍历其他所有用户,计算相似度,按相似度降序排序,取前K个。毕设数据量小,直接全量遍历没有压力。

第三步,生成推荐列表。对相似用户喜欢的图书进行加权汇总,权重就是用户之间的相似度,最终根据加权得分排序,取Top-N作为推荐结果。

public List<Book> recommendBooksForUser(Long userId, int topN) { Map<Long, Map<Long, Double>> ratingMatrix = buildRatingMatrix(); Map<Long, Double> userRatings = ratingMatrix.get(userId); // 计算用户相似度并排序 Map<Long, Double> similarityMap = new HashMap<>(); for (Long otherUserId : ratingMatrix.keySet()) { if (otherUserId.equals(userId)) { continue; } double similarity = cosineSimilarity(userRatings, ratingMatrix.get(otherUserId)); if (similarity > 0.1) { similarityMap.put(otherUserId, similarity); } } // 加权汇总相似用户喜欢的图书 Map<Long, Double> candidateScores = new HashMap<>(); for (Map.Entry<Long, Double> entry : similarityMap.entrySet()) { Long otherUserId = entry.getKey(); double similarity = entry.getValue(); for (Map.Entry<Long, Double> ratingEntry : ratingMatrix.get(otherUserId).entrySet()) { Long bookId = ratingEntry.getKey(); if (userRatings.containsKey(bookId)) { continue; // 排除当前用户已经评过分的 } candidateScores.merge(bookId, similarity * ratingEntry.getValue(), Double::sum); } } // 按分数排序,返回Top-N图书 return candidateScores.entrySet().stream() .sorted(Map.Entry.<Long, Double>comparingByValue().reversed()) .limit(topN) .map(entry -> bookService.getById(entry.getKey())) .collect(Collectors.toList()); }

这段代码能直接跑通,但还有优化空间。比如相似度阈值0.1要控制好,太高会导致推荐结果过少,太低会引入噪声。实际调参时我会看推荐列表的多样性,反复调整阈值和K值。

4.3 基于物品的协同过滤(ItemCF):实现相似图书推荐

基于物品的协同过滤思路反过来,核心是"物以类聚":计算图书之间的相似度,当用户看了某本书时,把和这本书最相似的其他书推荐给用户。

图书相似度的计算方式和用户相似度几乎一样,只不过把评分矩阵转置了一下——原来是按用户取行为,现在是按图书取被评分的情况。

// 转换矩阵:bookId -> Map<userId, rating> Map<Long, Map<Long, Double>> itemMatrix = new HashMap<>(); for (Map.Entry<Long, Map<Long, Double>> userEntry : ratingMatrix.entrySet()) { Long userId = userEntry.getKey(); for (Map.Entry<Long, Double> ratingEntry : userEntry.getValue().entrySet()) { Long bookId = ratingEntry.getKey(); itemMatrix.computeIfAbsent(bookId, k -> new HashMap<>()) .put(userId, ratingEntry.getValue()); } }

然后对每一对图书计算余弦相似度,预先存储起来。当用户查看某本书详情时,从已经算好的相似度表里取出Top-N,就是"相似图书推荐"。

ItemCF有一个非常实用的细节:计算图书相似度时,最好做"共同评分人数惩罚"或者"活跃用户惩罚"。比如一本冷门但评价极高的书,和一本所有人都打高分的畅销书,直接算余弦相似度会得到一个虚高的值,这在推荐领域叫做"热门物品偏差"。简化做法是引入一个系数,共同评分人数越少,相似度可信度越低,乘以一个惩罚因子把它降下来。毕设里不用做太复杂,加一个简单的权重即可。

4.4 推荐结果如何存、如何取

前面说过,推荐结果不要每次请求都现算。实践里我习惯建一张推荐结果表(t_recommend),字段包括用户ID、图书ID、推荐分数、推荐类型和生成时间。系统启动后,或者每天晚上定时任务触发时,跑一遍推荐算法,把结果写入这张表。前端调用推荐接口时,直接根据用户ID从表里查出推荐结果。

@Component public class RecommendScheduler { @Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点执行 public void generateDailyRecommendations() { recommendService.generateRecommendationsForAllUsers(); } }

用Spring自带的任务调度就能搞定,不需要引入额外的MQ或分布式任务框架。定时离线计算的好处是:接口响应快,用户体验好;算法参数可以随时调整并重新生成结果,不用改动上线代码。这在实际答辩演示中是个加分项,你可以现场把K值从10改成20,重新生成推荐结果,对比两拨结果的不同,比单纯展示静态页面有说服力得多。


5. 开发中最容易踩的坑与我的解决办法

5.1 冷启动问题:新用户和新书没有行为数据

冷启动是推荐系统最经典的坑,毕设评委也总喜欢问。新用户没有任何评分,UserCF就找不到相似用户,推荐结果为空;新书没有任何评分,ItemCF就永远推不出去。

我的方案是兜底策略:当推荐结果为空或者用户行为不足时,按热度推荐,比如按评分人数、平均评分、点击量加权排序取Top-N。给新用户推荐热门书,至少体验不会太差。

if (recommendList.isEmpty()) { // 兜底逻辑:按综合热度推荐 return bookService.getHotBooks(10); }

另一个办法是用默认评分初始化。注册时勾选几个喜欢的图书分类,系统给这些分类的图书一个基准分,就能让算法在冷启动阶段有数据可用。这个做法不复杂,但能够显著提升推荐效果,而且能写进论文里作为"冷启动解决方案",很有论述价值。

5.2 评分矩阵太稀疏,相似度算不准

图书系统里用户大多只看几本书、评几分,矩阵稀疏是常态。两个用户如果只在极少几本书上有共同评分,算出来的相似度波动很大,参考意义有限。

两个处理思路。第一,限制相似用户的共同评分数量,少于某个阈值(比如2本)直接放弃计算。第二,使用皮尔逊相关系数代替余弦相似度,因为它会先对用户评分做均值中心化,更能反映评分模式的相似而不是绝对分值的接近。

// 皮尔逊相关系数的计算要点 // 1. 求出用户a和用户b在共同评分上的均值 // 2. 每本书的评分减去对应用户的均值 // 3. 计算标准余弦相似度

很多同学觉得皮尔逊公式记起来麻烦,实际上它就是把两个用户的评分向量分别减去各自的均值再做余弦相似度。写的时候可以先封装一个均值计算函数,再在相似度函数里调用,结构清晰而且不容易出bug。

5.3 图书数据少导致的"推荐结果太窄"

一个常见的现象是:推荐来推荐去就是那么几本书,新鲜感不足。这往往是因为用户兴趣集中在一个分类,ItemCF计算出的相似图书也都是同一分类的,导致推荐列表缺乏多样性。

解决思路是在结果融合阶段做分类打散。把推荐结果按分类分组,先保证每个分类下至少取一到两本,再用补充策略填满Top-N。这看起来是策略层的改动,但我认为它是推荐效果从"能跑"到"好用"的关键一步。你在论文里也可以把这个策略命名为"基于分类加权的多样性优化",实在又漂亮。

5.4 推荐结果没有解释,答辩被问住

答辩时最常见的追问就是:"你为什么给这个用户推荐这本书?"如果你只会说"系统算出来的",那很难服众。我的建议是在推荐接口里带上推荐理由字段。比如UserCF的结果可以写"与你口味相似的用户喜欢这本书";ItemCF的结果可以写"因为你喜欢《XX》,所以推荐这本相似的书"。

实现也不复杂。加入推荐理由前,你需要记录这条推荐是由哪个相似用户产生的,或者由哪本种子图书产生的。推荐结果表外加一个说明字段即可。这个细节让系统的推荐结果有了"可解释性",是推荐系统领域一个真正的研究方向,写进论文很加分。


6. 拿到源码后怎么运行:从零到一调试整个项目

6.1 基础环境四项准备

这个项目要跑起来,基础环境需要四样东西:

  • JDK 8或JDK 11(推荐JDK 8,兼容性最好)
  • Maven 3.6以上(项目依赖管理)
  • MySQL 5.7或8.0(数据库)
  • IDEA(开发运行环境,也可以用Eclipse)

环境变量记得配好。IDEA里导入项目选择Maven项目,等待依赖下载完成。如果依赖下载慢,换阿里云镜像能省很多时间。

6.2 数据库初始化流程

拿到项目之后,数据库初始化是第一个关卡。通常会有一个初始化SQL脚本,里面包含建库语句、建表语句和测试数据。重点看一下分类表、图书表、用户表、评分表这四张核心表是否有初始数据。

推荐算法要跑得起来,评分表的初始数据非常重要。如果脚本里只有几张表没有数据,那推荐接口大概率返回空列表或者报空指针。我一般建议测试数据至少在几百条以上,而且多模拟几个不同兴趣的用户,这样既方便算法演示,也方便测试各种边界情况。

6.3 常见启动失败场景与排查方法

启动失败多数集中在三个点。

数据库连接失败。报错信息通常是Cannot connect to MySQL或者Access denied for user。排查思路很简单:先确认MySQL服务有没有启动,再拿配置文件里的账号密码手动在命令行里连一下,如果命令行能连上而应用连不上,那就是配置文件里的地址写错了。

端口被占用。SpringBoot默认端口是8080,如果本地有其他服务占用了,要么找到占用进程,要么改配置文件里的server.port。注意:改了端口后,前端所有请求的BaseURL也要跟着改。

依赖下载不完整。这种问题最隐蔽,有时报的是类找不到,有时报的是Bean创建失败。排查方法是清空本地的Maven仓库中该项目的相关依赖,重新reimport,或者用mvn clean package看看编译阶段的报错信息。

6.4 调试运行的核心验证路径

项目启动后,我建议按这个顺序验证系统状态。

第一步是验证登录注册。注册一个新用户,再登录,确认Session或Token机制正常。

第二步是验证图书列表。测试分页是否生效,搜索能否按书名或作者模糊查询。

第三步是验证评分功能。对几本不同的书打不同分数,在数据库里确认评分表正确写入。

第四步是验证推荐结果。这是最关键的一步。随便找一个有行为记录的用户,调用推荐接口,观察返回结果是否合理。比如一个只喜欢计算机类图书的用户,推荐列表里应该能出现其他计算机类书,而不应该出现一堆言情小说。

如果推荐结果明显不合理,不要急着怀疑算法写错了,先检查评分数据够不够。另一个可能的原因是推荐结果表已经很久没有刷新,导致推荐接口读的还是老数据。在Scheduler还没触发的时候,先手动调用一次推荐生成接口,强制刷新一下数据再测试,这是个非常常见的问题。


7. 项目定制方向与后续进阶思路

7.1 算法层面的三个升级方向

如果觉得现有项目不够出彩,推荐算法部分可以从三个方向升级。

第一个方向是混合推荐。把协同过滤和基于内容的推荐做一个加权融合。基于内容的推荐需要从图书简介、书名、分类中提取特征,最简单的做法是用分类标签做向量化。这个升级的代码量不大,但论文里的技术方案立刻丰满了很多。

第二个方向是引入隐语义模型(LFM)。利用SVD或ALS把评分矩阵分解成用户特征矩阵和物品特征矩阵,用降维后的向量做推荐。矩阵分解在数据稀疏时表现更稳定,而且能学习出用户和物品的潜在特征,是答辩时非常能打的加分项。缺点是代码量稍大,需要引入数值计算库,对数学基础有一定要求。

第三个方向是实时推荐。现有方案是离线定时计算,如果能用Redis做缓存,在用户完成评分后立即重新生成该用户的推荐列表,体验上会好很多。这个方向更多是工程上的优化,适合论文里写"基于Redis的实时推荐优化"。

7.2 功能层面的扩展思路

除了算法,功能上的扩展也有很多空间。

图书评论功能。评分只是数字,评论能承载更丰富的信息。评论数据未来也能用于情感分析,可以讲的故事很多。

阅读记录与阅读时长统计。用户在图书详情页停留的时间,或者在线阅读功能里的阅读时长,都是很有价值的隐式反馈数据。这类数据叠加到推荐算法里,就能绕开"评分稀疏"的困境。

个性化报告。给用户生成每月阅读行为统计和兴趣变化的可视化报告,让用户直观看到推荐系统如何理解自己。这个功能视觉效果好,答辩的时候展示出来非常讨喜。

7.3 工程化与部署层面的建议

毕设项目如果时间充裕,部署上线会是一个完整的加分项。用一台云服务器,装好JDK和MySQL,把项目打成jar包直接运行,前端页面可以打包后由SpringBoot统一提供静态资源。这样不用额外配置Nginx就能对外访问,减少部署复杂度。

再进一步可以做容器化部署,写一个简单的Dockerfile和docker-compose.yml,把MySQL和应用都容器化。这个写在论文的"系统部署"章节会显得非常专业。但我得提醒一句:云服务器需要实名购买,注意自行评估成本和合规问题。如果只是为了答辩演示,本地运行完全够用,不必强行部署上线。

数据库方面也可以做些优化。推荐结果表按用户ID做索引,评分表按用户和图书做联合索引,定期清理过期的推荐缓存,这些看似琐碎的细节往往决定了整个系统的稳定性和演示流畅度。


最后分享一点个人经验。这个项目我已经不是第一次接触了,每次拿到不同版本的源码,最先看的不是算法代码,而是数据初始化脚本和推荐结果表的生成逻辑。很多同学在项目演示的时候推荐结果为空,十有八九不是算法写错,而是数据没喂够、定时任务没触发、或者缓存没刷新。先把这条数据链路跑通,再去优化算法,这个顺序不能乱。

如果你正在做这个题目,记住一个原则:系统功能是骨架,推荐算法是灵魂,文档答辩是门面。骨架要稳,灵魂要亮,门面要光。把这三块配好,图书推荐系统绝对是一个拿得出手的毕设选题。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进