十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

基于Java与Spring Boot的图书个性化推荐系统开发实战

基于Java与Spring Boot的图书个性化推荐系统开发实战 简介这是一套基于Java Spring Boot的图书个性化推荐系统毕业设计包面向计算机与软件工程专业学生覆盖用户注册登录、图书检索、评分收藏、个性化推荐等模块推荐算法融合协同过滤与基于内容的方法帮助用户获取精准书目。包体含代码、数据库、论文、PPT、演示录像、运行教学及配套软件共156.34MB。源码展示Spring Boot分层架构与RESTful接口设计数据库含用户信息表、图书信息表、行为记录表等论文与PPT覆盖需求分析至测试全过程演示录像呈现注册到获取推荐的交互流程配套软件与运行教学可快速搭建本地环境。目前已有82人学习浏览。使用该资源可理解推荐系统工程化实现路径复用编码规范与模块划分显著缩短毕设文档撰写与调试周期兼具学习与实用价值。 图书个性化推荐系统听起来像是课设或者毕设里最常见的“老熟人”。但把Java和Spring Boot组合起来再做一套能跑的推荐逻辑里面能挖的细节其实非常多。这篇内容我打算按自己实际做项目时的思路来拆从选题、数据库设计、算法落地到论文和答辩素材怎么整理一条线讲清楚。不管是正在纠结题目的还是已经开工想完善系统的应该都能找到点参考。1. 选题思路与系统整体架构1.1 为什么选“图书个性化推荐”当毕业设计毕设选题有个很现实的原则既要有技术含量又要能在一学期内完成还得让答辩老师一眼看出工作量。图书个性化推荐系统恰好满足这三点。它不是一个简单的CRUD项目背后有推荐算法这个“灵魂”在同时业务模型清晰用户、图书、评分、借阅记录这些实体关系很好画数据库设计不会绕晕更重要的是市面上成熟的推荐算法有大量公开资料遇到问题能查到解决方案不会卡死在某个数学公式上。我当时选这个题目还有一个私心图书推荐可以做成基于用户行为收藏、评分、借阅时长的协同过滤也可以混入基于内容的标签匹配算法选型灵活。这意味着我可以在论文里做对比实验写“改进”部分时也有话可说。如果选电商推荐数据量和业务复杂度会倍增如果选新闻推荐时效性处理又很麻烦。图书推荐的数据相对稳定冷启动问题也比较容易通过热门榜、分类榜来缓解是一个“刚刚好”的难度区间。1.2 Java Spring Boot 技术选型的真实考量用Java做毕设的好处不用多提生态成熟、资料多、面试还能顺手复习一轮基础。Spring Boot在这里面的角色是大幅降低整合成本。传统SSH项目要写一堆XML配置Spring Boot用自动配置和起步依赖把这些琐事收掉了让我能把精力放在推荐算法和业务逻辑上。尤其是做毕业设计这种周期紧、需要反复调试的场景启动快、报错清晰就是最大的效率保障。架构上我选择了前后端分离但不过度设计的方案前端用Thymeleaf模板引擎渲染后台管理页面配合Bootstrap做基础样式用户浏览和推荐展示的页面用简单的HTML AJAX调用后端接口。为什么不用Vue或React因为毕设的重点是后端和算法前端做到整洁能用即可引入Node.js构建链反而增加环境复杂度。如果导师明确要求前后端分离那换成Vue也不是不行代码里接口返回JSON适配成本很低。技术栈最后是JDK 1.8 Maven 3.6 MySQL 5.7Spring Boot 2.x我用的是2.5.x稳定且资料多MyBatis-Plus Druid连接池Thymeleaf Bootstrap jQuery这里有个实际心得Spring Boot版本不要盲目追新有时最新的版本改动很大网上很多资料对不上折腾一圈反而浪费时间。2.5.x这个版本我用下来比较省心大部分框架兼容性问题网上都有现成答案。1.3 基础功能模块划分系统的功能模块按照“前台用户”和“后台管理”两个视角来切。前台包括用户注册登录、图书列表与详情、分类浏览、搜索、评分、收藏、借阅模拟、个性化推荐猜你喜欢、热门排行榜。后台包括图书管理增删改查、用户管理、评论管理、评分数据管理、推荐算法参数配置、数据统计报表。模块划分的原则是每个模块尽量独立接口清晰。比如评分和收藏归到“用户行为”模块推荐算法单独一个service包与具体业务解耦。这样写论文的时候可以按模块展开需求分析画功能结构图也方便。另一个好处是如果时间不够某些模块可以只实现核心功能但整体框架依然是完整的答辩时能自圆其说。2. 数据库设计与核心表结构2.1 从ER图到建表哪些表必不可少数据库设计是答辩时老师很容易提问的环节也是很多同学偷懒的地方。我画ER图时整理了7张核心表用户表t_user、图书表t_book、图书分类表t_category、评分表t_rating、收藏表t_favorite、借阅记录表t_borrow、推荐结果表t_recommend。每一张表都有明确的业务含义关联关系也干净。用户表字段包括用户ID、用户名、密码加盐MD5存储、昵称、头像、角色等。图书表要涵盖书名、作者、ISBN、出版社、出版日期、封面URL、简介、分类ID、点击量、库存量。这里特别建议把ISBN设为普通索引而不是唯一索引因为有些旧版本书没有ISBN写入会报错。评分表要带用户ID、图书ID、评分值1到5分、评分时间并且对用户ID图书ID做联合唯一约束防止同一用户重复评分。建表时注意InnoDB引擎和utf8mb4字符集这是一个很容易被忽略的点。如果不指定字符集插入中文书名和简介时会出现乱码在答辩演示现场非常尴尬。我之前在实验室就遇到过这个问题后来统一在Navicat的默认字符集里设置成utf8mb4再没有出现过“问号乱码”的问题。2.2 设计推荐结果表的两种思路关于推荐结果要不要存表有两种做法。一种是动态计算用户每次请求推荐就会实时跑一遍协同过滤算法返回结果。另一种是离线计算后存到推荐结果表用户请求时直接查询。我的建议是推荐结果表一定要有但推荐结果的更新要靠定时任务。原因很实际协同过滤算法在数据量上来之后实时计算的响应时间会从毫秒级变成秒级用户体验很差。而且毕业设计答辩时要演示老师说“你点一下推荐看看”如果转半天圈圈体验分就掉了。我的方案是用户登录时触发一次计算把该用户的Top-N推荐结果写进t_recommend表每晚凌晨2点通过Spring Boot自带的Scheduled定时任务再全量计算一次刷新所有用户的推荐列表。这样既能保证推荐结果在一天内相对新鲜又不会拖慢接口响应。t_recommend表的字段为推荐ID、用户ID、图书ID、推荐分数、推荐类型基于用户还是基于物品、生成时间加一个用户ID推荐类型的联合索引。2.3 MyBatis-Plus的巧妙使用MyBatis-Plus在这里的价值被我低估了一段时间。一开始我还在手写XML的ResultMap和动态SQL后来发现用MyBatis-Plus的BaseMapper单表CRUD基本不用写SQL语句。比如图书分页查询直接用LambdaQueryWrapper构造条件代码量能减少一半以上。对于联表查询比如“查询某一用户收藏的所有图书及其分类名称”我的经验是尽量简化先查收藏表拿到图书ID列表再用ID列表到图书表查出完整信息。两次单表查询在数据量不大时性能很好而且逻辑清晰不容易出bug。不过要注意MyBatis-Plus的自动填充和逻辑删除功能要用对。比如create_time字段设置自动填充后插入数据时如果没给该字段赋值框架会自动填当前时间。但如果表里字段名和Java属性名不一致或者字段类型错了自动填充会静默失败。我出现过一次登录日志时间全是空值排查半天才发现是Java属性里用了sql.Date而表里是datetime类型。所以用框架自动功能之前先确认类型匹配。3. 个性化推荐算法的落地实现3.1 协同过滤的核心原理图书个性化推荐系统的核心在于推荐算法。我采用的是经典的协同过滤Collaborative Filtering思路。协同过滤的基本假设是对某本书感兴趣的人他们的喜好路径是可以互相引用的。基于用户的协同过滤UserCF先找到和目标用户兴趣相似的一批用户找相似用户再把这些用户喜欢的、目标用户没看过的书推荐出来。基于物品的协同过滤ItemCF则反过来先找和目标图书相似的图书再根据用户的历史行为向他推荐相似图书。相似度的计算是协同过滤的关键。我使用余弦相似度计算用户之间的兴趣相似度公式是两个用户评分的交集商品分数向量点积除以两个向量模长的乘积。代码实现时可以用一个Map存储每个用户对图书的评分然后遍历计算。虽然用双重循环但数据量小的时候性能完全足够。为了让量级可控我在SQL里做了一个过滤只计算那些与目标用户“共同评分超过2本”的用户。举个例子用户A和用户B都评过《Java核心技术》和《Spring实战》这时他们的向量点积才有意义。如果只共同评了1本书相似度的置信度不高往往是噪声。3.2 相似度计算与推荐分数生成的完整代码先看UserCF的Java实现。我用了HashMap来存储用户评分数据然后在service层完成相似度计算和Top-N推荐。public ListInteger recommendByUserCF(Integer userId, int topN) { // 1. 获取目标用户评分图书map MapInteger, Double currentUserRatings ratingMapper.selectMapByUserId(userId); // 2. 得到候选相似用户列表 ListInteger candidateUserIds ratingMapper.selectDistinctUserIds(); // 3. 计算与所有候选用户的余弦相似度 MapInteger, Double userSimilarity new HashMap(); for (Integer candidateId : candidateUserIds) { if (candidateId.equals(userId)) { continue; } MapInteger, Double candidateRatings ratingMapper.selectMapByUserId(candidateId); Double sim cosineSimilarity(currentUserRatings, candidateRatings); if (sim 0) { userSimilarity.put(candidateId, sim); } } // 4. 取TopK相似用户 ListMap.EntryInteger, Double sortedUsers userSimilarity.entrySet().stream() .sorted((e1, e2) - Double.compare(e2.getValue(), e1.getValue())) .limit(10) .collect(Collectors.toList()); // 5. 聚合推荐分数过滤已看过的 MapInteger, Double scoreMap new HashMap(); for (Map.EntryInteger, Double entry : sortedUsers) { MapInteger, Double candidateRatings ratingMapper.selectMapByUserId(entry.getKey()); for (Map.EntryInteger, Double bookEntry : candidateRatings.entrySet()) { if (!currentUserRatings.containsKey(bookEntry.getKey())) { scoreMap.merge(bookEntry.getKey(), entry.getValue() * bookEntry.getValue(), Double::sum); } } } // 6. 按分数排序返回TopN return scoreMap.entrySet().stream() .sorted((e1, e2) - Double.compare(e2.getValue(), e1.getValue())) .limit(topN) .map(Map.Entry::getKey) .collect(Collectors.toList()); }余弦相似度工具方法private double cosineSimilarity(MapInteger, Double userA, MapInteger, Double userB) { MapInteger, Double unionKeys new HashMap(); unionKeys.putAll(userA); unionKeys.putAll(userB); if (unionKeys.isEmpty()) { return 0D; } double dotProduct 0D; double normA 0D; double normB 0D; for (Integer bookId : unionKeys.keySet()) { double scoreA userA.getOrDefault(bookId, 0D); double scoreB userB.getOrDefault(bookId, 0D); dotProduct scoreA * scoreB; normA Math.pow(scoreA, 2); normB Math.pow(scoreB, 2); } if (normA 0D || normB 0D) { return 0D; } return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB)); }这段代码看起来不复杂实际运行效果也不错。但有一个细节要注意当用户数量增多时双重遍历的时间复杂度是O(N²)性能会下降。我的优化方法是在SQL层先过滤掉那些和当前用户完全没有共同评分项的用户。可以通过inner join评分表只统计co_rating_count大于等于2的用户再取前200个候选再跑内存计算。这样既能保留准确性又不会把CPU打满。3.3 ItemCF与混合推荐的参数调节在实现完UserCF后我又加了ItemCF目的是做一个简单的混合推荐论文里可以有对比数据。ItemCF的核心是计算物品之间的相似度矩阵。在实际代码里可以用一个MapInteger, MapInteger, Double来存储图书相似度矩阵。每次推荐时遍历用户历史评分图书找到这些图书最相似的TopN图书再按相似度加权评分。混合推荐的方式我用的是加权融合推荐分数 0.6 * UserCF分数 0.4 * ItemCF分数。加权系数不是拍脑袋定的我跑了一次简单的评估以测试集评分预测的MAE平均绝对误差为指标尝试了0.5/0.5、0.6/0.4、0.7/0.3三组参数最后0.6/0.4效果最好。这一段内容写到论文里会非常加分因为展示了你做实验、调参数的过程而不是把算法随便套上就算完。3.4 冷启动问题的处理策略冷启动是每个推荐系统都躲不开的问题但在毕设里可以采取一个直接有效的解法对没有行为数据的新用户推荐热门榜加分类榜。热门榜用点击量降序排列分类榜按用户选择的分类显示。当新用户产生一个评分、收藏或点击行为后系统立即触发一次推荐结果更新这时协同过滤算法开始生效。我在前台推荐模块给了一个“换一批”按钮实际上就是换个时间戳重新计算保证新用户不至于每次都看到完全相同的推荐列表。这里有一个坑如果推荐定时任务在深夜执行但用户刚刚注册并产生了行为t_recommend表里可能还没有他的推荐结果。所以我在控制器层面做了一个兜底查询推荐结果表时如果发现该用户没有推荐记录自动调用一次实时计算把结果写入后再返回。这个兜底逻辑保证了任何情况下刷新页面都能看到推荐内容演示时绝不白屏。4. 核心功能模块实现细节4.1 图书管理、搜索与分页实现图书管理是后台模块的基础。图书列表一定要分页否则几千本图书渲染到一个页面上会导致浏览器卡死。我使用MyBatis-Plus的Page对象配合LambdaQueryWrapper实现分页查询支持按书名模糊搜索、按分类精确筛选、按出版日期排序。每次前端请求传pageNum和pageSize参数后端返回包含总记录数的JSON数据。图书封面我采用本地静态资源映射方式把图片放在upload目录Spring Boot配置静态资源路径后通过链接直接访问。没配置资源映射时封面图片死活加载不出来这是新手最容易卡住的点。搜索功能我用了MySQL的LIKE模糊匹配书名和作者都加上了索引前匹配优化。数据量大了以后全文搜索其实需要Elasticsearch来做但毕设场景下MySQL足够。为了提升体验搜素框做了一个防抖处理输入停止300毫秒后再发请求载入状态下显示“加载中”。4.2 用户行为采集评分、收藏与借阅记录推荐系统的数据来源是用户行为。评分功能在图书详情页上实现用户点1到5星后通过AJAX接口提交评分数据。服务端校验用户是否已对该书评分如果已评分则更新原来的分数防止重复记录。收藏功能则通过t_favorite表存储用户可以随时取消收藏。收藏还有一个辅助作用我在推荐算法里把收藏行为等同于4分评分因为收藏的用户往往对书有较强的正向兴趣。借阅记录表是个模拟借阅流程因为毕设一般不会对接真实图书管理系统。我设计了BorrowState字段0表示借阅中1表示已归还2表示已预约。用户点击“借阅”后系统先判断库存是否大于0库存充足则生成借阅记录并扣减库存还书后恢复库存。这个流程不复杂但业务逻辑必须严谨否则会出现“库存变成负数”这种答辩事故。4.3 后台数据统计与推荐效果对比后台统计模块我用了Apache ECharts做折线图和柱状图。统计维度包括日注册用户数、每日借阅量、图书分类占比、Top10热门图书。这些数据可以通过SQL的GROUP BY日期函数和分类字段聚合获得。统计页面的作用有两个一是展示系统运行状态显得真实二是为论文中的系统测试提供数据支撑。推荐效果对比页是我比较满意的一个模块。我设计了一个简易的离线测试接口在测试用户集合上分别调用UserCF、ItemCF和混合推荐算法返回各自推荐列表然后计算推荐的准确率和召回率。虽然这个方法比较粗糙但折线图和表格一展示答辩老师就能直观感受到“混合推荐优于单一算法”比长篇文字解释更有说服力。5. Spring Boot项目搭建与常见问题排查5.1 从零搭建项目的关键步骤新建项目时我用的是Spring Initializr生成一个基础Spring Boot项目再手动引入MyBatis-Plus、MySQL驱动、Thymeleaf等依赖。核心的POM依赖片段如下dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.4.3.4/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency配置文件application.yml里我把数据源、MyBatis-Plus的日志、端口、静态资源路径都做了明确的声明。这里有一个非常容易踩的坑MySQL 8.0以上版本的驱动类名是com.mysql.cj.jdbc.DriverMySQL 5.7则是com.mysql.jdbc.Driver。如果用错驱动启动时会报“Loading class is forbidden”之类的错误。更稳妥的做法是尽量用MySQL 5.7或8.0并在配置里加上时区参数serverTimezoneAsia/Shanghai否则连接数据库时会提示时区错误。环境变量配置方面JDK和Maven的环境变量如果没配好项目连启动按钮都看不到。Windows系统里需要在系统变量里新增JAVA_HOME和MAVEN_HOMEPATH里加入对应bin目录。终端运行java -version验证一下是否成功。很多同学在cmd输入java时提示“不是内部或外部命令”基本都是环境变量没配对或者没重新打开终端导致的。5.2 启动过程中遇到的高频报错我整理了启动时最常遇到的几类问题全部踩过或者帮同学排查过报错信息原因解决办法Port 8080 was already in use端口被占用换端口或查杀占用进程Access denied for user rootlocalhost数据库账号密码不匹配或权限不足检查连接url的密码授权root可局域网登录Unknown database library数据库不存在先在MySQL客户端建库或在配置里加上createDatabaseIfNotExisttrueFailed to configure a DataSource数据源配置缺失检查spring.datasource.url和driver-class-name是否完整java.sql.SQLException: Unknown character set连接串字符集错误在url末尾加characterEncodingutf8mb4端口占用这个问题有个快速排查技巧Windows下用netstat -ano | findstr 8080命令找到占用端口的PID后在任务管理器结束对应进程。但有时8080被多个服务占用直接换一个端口更快。我在application.yml里配置了server.port默认8080如果遇到冲突会临时改成8081。5.3 中文乱码、依赖冲突与版本兼容问题中文乱码分为前端乱码和后端乱码两种。前端乱码通常是因为HTML页面没有设置 meta charsetutf-8或者模板引擎前缀配置错误。后端乱码则要考虑数据库连接串、MySQL表字符集、HTTP响应头三个层面。我的处理方案是在application.yml里设置server.tomcat.uri-encodingUTF-8在Spring Boot的WebMvcConfigurer中配置消息转换器字符集为UTF-8同时把MySQL表结构字符集统一为utf8mb4。三层都做对乱码基本绝迹。依赖冲突是另一个常见坑。Spring Boot自带了一个spring-boot-starter-logging如果重新引入logback或log4j2日志系统可能互相干扰启动时一直报SLF4J绑定失败。解决方法是排除其中一个依赖认准一套日志实现。另外Lombok版本过低可能导致编译期注解处理器报错直接用Spring Boot父POM管理的Lombok版本即可不需要手动指定。香格里拉式的经验是遇到莫名其妙的报错先用clean和package命令重新构建一般能解决一半的问题再不行就把target目录删除后重新编译。Spring Boot项目启动失败很多时候是缓存或增量编译的问题并不是代码本身有毛病。6. 论文结构、答辩准备与资料整理6.1 论文从绪论到总结的完整结构毕设论文我按“背景与意义、关键技术、需求分析、系统设计、系统实现、系统测试、总结展望”这个经典结构来写。这里分享一个实操技巧先不要从绪论开始写而是先画系统架构图、功能模块图、ER图和数据库表结构再写需求分析和系统设计。因为图表是论文的骨架写完图表后正文的文字只是“图表解释”的扩展思路会很顺畅不会卡文。绪论部分可以从“信息过载”和“如何高效获取图书资源”切入引出个性化推荐的价值。关键技术与算法原理章节要写清UserCF与ItemCF的区别、余弦相似度公式、推荐流程时序图。系统实现章节要放关键代码和截图。截图的排版用3×2的网格能有效压缩页数同时还能保持图表清晰。测试章节要包含功能测试用例表、性能测试结果和推荐算法准确率对比表有数据支撑的论文往往能拿更高的评阅分。6.2 ER图、流程图和文档规范老师评阅论文时首先看格式ER图是数据库设计部分的重要加分项。不要用截图代替ER图最好用Visio或draw.io画出来。ER图中实体和关系的命名要与数据表一致属性不要全部铺开选关键字段即可。流程图方面推荐算法的运行流程用PowerDesigner或者ProcessOn画一个简单的活动图图号、图题、字体大小都要统一。需要注意的是论文里所有图都要有编号和图题表格要有表题和编号。参考文献格式用GB/T 7714这个在Word的引用管理功能里可以自动生成。我当时把所有参考的文章标题、重点内容都放在一个表格里写论文时直接关联省下了大把回翻浏览器书签的时间。6.3 PPT制作与演示录像的细节经验答辩PPT不用花哨控制在12页左右内容覆盖题目背景、系统开发技术栈、功能模块介绍、数据库设计、系统截图展示、核心算法代码、测试结果、总结与展望。演示是关键一个稳定的系统演示胜过大段文字。PPT中的代码块和截图一定要大老师坐在教室后排也能看得清字体小于16号会显得不专业。演示录像则是我建议提前准备的工作。答辩现场网络可能不稳定数据库可能连接不上提前录一段5分钟的功能演示视频能作为稳妥预案。录像时用OBS或EV录屏分辨率1920×1080确保鼠标移动不要过于频繁。录制内容顺序为用户注册登录、浏览图书、搜索图书、评分收藏、查看个性化推荐列表、后台管理界面操作。录完后用剪映简单裁剪一下配上字幕就完成了。这一套资料放在“运行教学”文档里对学弟学妹复现项目特别有用。论文查重也是很多同学头疼的事。建议不要抄袭直接搬代码但可以多看几篇优秀论文学习它们的问题描述方式和图表表达能力用自己的话重新整理逻辑。代码部分可以用学术不端检测工具先自查一遍再针对重复度高的段落重新组织措辞。7. 运行教学与资料交付的整理心得做完整个项目之后我整理了一套完整的学习型资料包包括源代码、数据库初始化SQL、论文Word版、答辩PPT、演示录像和运行教学文档。很多同学拿到一个开源项目不知道从哪下手所以我把运行教学文档写成了一份从零开始的“保姆级”指南如何安装JDK、如何配置Maven环境变量、如何导入项目到IDEA、如何导入数据库SQL、如何修改配置、如何启动每个步骤都附带截图。这套文档在我复制给组员和后续几届学弟学妹时有效降低了大家的上手门槛。写这一节是希望大家在完成毕设时不只是写完代码就扔在一边而是把“交付物”这个思维贯穿始终。老师通常关注的不只是代码能不能跑还有项目是否可复现、逻辑是否完整。一份良好的README、清晰的数据表说明、规范注释的代码、可复现的运行环境都是评估一个毕设是否优秀的重要维度。最后还有一个很实用的小经验整个项目代码在写的时候控制台打印一下接口耗时把推荐算法的耗时数据记录下来。论文测试章节里写“平均推荐响应时间小于200毫秒”时不能只靠感觉有了真实日志输出任何答辩提问都稳得住。本文还有配套的精品资源点击获取
返回列表