ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

SpringBoot影视评论系统毕设全解析:从架构到推荐算法落地

SpringBoot影视评论系统毕设全解析:从架构到推荐算法落地 1. 破题这个毕设到底在做什么先说结论这个题目不是让你做一个简单的发评论网站它背后其实藏着三条完整的能力线Java Web 全栈功底、SpringBoot 工程化能力、推荐系统落地思维。很多同学一看影视评论系统就以为是个低难度项目真动手才发现光是把影评互动做流畅、再把推荐模块接进去涉及的坑能写满一本笔记。我当时做这个项目的时候已经过了单纯能跑就行的阶段所以整篇博文我不会只讲怎么抄代码而是把我踩过的坑、拆过的结构、答辩时候被问的问题一并写清楚。如果你正在准备计算机毕业设计或者想用 SpringBoot 做一个作品级的个人项目这篇文章值得你花十分钟认真看。这个系统适合谁来参考我大致分三类第一类是没做过完整 Web 项目的在校生可以用它打通前端请求→Controller→Service→Mapper→数据库这条主线顺便把 Redis、JWT、拦截器这些企业级组件都串起来第二类是想搞推荐系统但不敢碰 Python的同学用纯 Java 也能实现一套基于协同过滤的推荐流程面试和答辩都拿得出手第三类是时间紧、需要快速出活的我会在后面的常见问题里给出最小可用方案让你省掉一半功夫还不掉档次。2. 整体架构与技术选型背后的考量2.1 为什么选 SpringBoot 而不是 SSH 或 SSM现在的用人市场和教学环境都偏向 SpringBoot原因很直接它把配置的复杂度降到最低让开发者把精力放在业务逻辑上。我记得早期用 SpringMVC MyBatis 搭一个项目光 XML 配置文件就要写好几页数据源、事务管理器、视图解析器、扫描路径每一项都得手工配而且配错了报错信息还特别隐晦新手很容易卡在环境阶段。SpringBoot 通过自动配置解决了这个问题。比如你想连 MySQL只要引入spring-boot-starter-data-jpa或mybatis-spring-boot-starter再在application.yml里填上数据源地址、用户名、密码就能直接注入DataSource使用。它背后用到的EnableAutoConfiguration会读取META-INF/spring.factories中的配置类按条件装配需要的 Bean这就是为什么你只写了几行代码Tomcat、Jackson、日志框架全都自动就位了。这个项目的技术栈组合我给出一套“主流 稳妥”的方案不炫技但覆盖了实际开发中最高频的组件后端SpringBoot 2.7.x MyBatis-Plus 3.5.x Spring Security或 JWT 拦截器手写鉴权缓存Redis 5.x/6.x做热点影视数据缓存、验证码存储、点赞防刷数据库MySQL 8.x核心业务表加一些常用索引前端Vue 3 Element Plus Axios部署时可把构建产物放到 SpringBoot 的static目录实现单包部署搜索与推荐初期用 MySQL LIKE 兜底后期可以接 Elasticsearch但毕设阶段不必上太重2.2 三级表设计影视、用户、影评的核心关系我见过很多同学一上来就建了二三十张表结果字段之间全是冗余连自己也理不清。做这种有口碑和推荐场景的项目表设计要围绕一条主线用户看过什么、用户说了什么、影视本身有哪些标签。我实际用的是下面这几张核心表先列结构再解释为什么这样设计-- 用户表 CREATE TABLE user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, nickname VARCHAR(50), avatar VARCHAR(255), role TINYINT DEFAULT 1 COMMENT 1-普通用户 2-管理员, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 影视信息表 CREATE TABLE movie ( id BIGINT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(200) NOT NULL, director VARCHAR(100), actors VARCHAR(500), genre VARCHAR(100) COMMENT 类型如 剧情/科幻/动作, release_date DATE, rating DECIMAL(3,1) DEFAULT 0.0 COMMENT 综合评分由影评聚合计算, rating_count INT DEFAULT 0, cover_url VARCHAR(500), summary TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 影评表 CREATE TABLE review ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, movie_id BIGINT NOT NULL, content TEXT NOT NULL, rating DECIMAL(2,1) NOT NULL COMMENT 用户评分 0.0-10.0, like_count INT DEFAULT 0, is_deleted TINYINT DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_user (user_id), KEY idx_movie (movie_id) ); -- 影视标签表 CREATE TABLE tag ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL UNIQUE ); -- 影视-标签关联表 CREATE TABLE movie_tag ( id BIGINT PRIMARY KEY AUTO_INCREMENT, movie_id BIGINT NOT NULL, tag_id BIGINT NOT NULL ); -- 用户观影状态表 CREATE TABLE watch_status ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, movie_id BIGINT NOT NULL, status TINYINT DEFAULT 0 COMMENT 0-想看 1-已看 2-在看, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_user_movie (user_id, movie_id) ); -- 收藏/点赞表 CREATE TABLE favorite ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, target_type TINYINT COMMENT 1-影视 2-影评, target_id BIGINT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_user_target (user_id, target_type, target_id) );用review表里的rating字段而不是在movie表里手填评分好处是评分来源可追溯后面做推荐和口碑聚合时能直接基于用户行为算平均分。movie_tag是典型的多对多关联表推荐系统里做内容匹配、标签召回全靠它。watch_status这张表很多人会漏掉但它是构建“用户偏好画像”的关键后面讲推荐的时候你会看到它的价值有多大。2.3 推荐系统模块选型基于物品的协同过滤推荐系统是这个项目的亮点也是最容易被导师追问的部分。我选的是基于物品的协同过滤ItemCF原因有几个第一影视数量相对用户来说更稳定计算物品间相似度矩阵比计算用户间相似度矩阵更方便缓存和离线更新第二对毕设而言只需要展示“和你喜欢的电影相似的作品”就足够说明你理解推荐原理第三不需要像深度学习这样的大规模算力一台笔记本就能跑完整流程。算法思路并不复杂核心是两步先根据用户历史行为已看、收藏、高评分构建物品相似度再根据用户最近正反馈物品生成候选集并过滤排序。相似度计算我用的是余弦相似度公式是cos(θ) (A·B) / (|A|×|B|)在实际代码里把每个电影表示成一个用户行为向量如[看过的人数, 收藏人数, 平均评分]然后计算两个电影之间的余弦相似度。这个公式不需要引入第三方算法库纯手写也就几十行答辩时还能现场讲清楚每一步在做什么。为什么不选基于用户的协同过滤UserCF因为用户兴趣变化快、用户数增长后矩阵计算量陡增而且“和你相似的人喜欢什么”在单人调试时很难看出效果演示起来不直观。ItemCF 有一个天然优势给用户推荐的是“物品”的相似物对小白观众的理解门槛低解释成本也低。3. 核心模块的实操细节从用户登录到影评互动3.1 注册登录与 JWT 鉴权别再用 Session 了很多教学项目还在用 Session 保存登录状态但我建议你直接用 JWT因为这套写法在前后端分离场景下更接近企业真实开发答辩时也能体现你对状态管理的理解。登录流程是这样的用户提交用户名和密码后端校验通过后用jjwt库生成一个 token把它返回给前端前端存在本地存储中后续每次请求在Authorization头里带上Bearer token。后端写一个拦截器或过滤器解析 token 并放入ThreadLocal或请求属性中Controller 就能拿到当前用户信息。这里有几个特别容易踩的坑。第一JWT 密钥不要硬编码在代码里应该放到配置文件中并做好长度要求至少 256 位否则运行时会报错第二token 过期时间不要太长我设定登录态 24 小时并额外提供 refresh token 机制第三Redis 里存一份 token 黑名单用户修改密码或退出登录时把该 token 加入黑名单防止“退出无效”的尴尬问题。验证码环节其实也有讲究很多人直接存在内存 Map 里但毕设如果用了 Redis最好把验证码也放 Redis设置过期时间5分钟并限制每个手机号或邮箱每天的发送次数。这样在演示“验证码过期”“请求频繁限制”时能很自然地把缓存应用体现出来这也是一道加分项。3.2 影评发布、点赞与收藏的消息流设计影评发布不只是 insert 一条数据那么简单。我拆成了四个动作插入影评记录、更新影视评分和评分人数、给用户增加活跃积分可选、记录该影评到 “待推荐召回池”。更新评分的逻辑要注意并发问题。如果用户 A 和用户 B 同时给同一部电影打分直接UPDATE movie SET rating (rating new_rating) / 2会出问题因为两次打分没有原子性。稳妥的做法是评分人数加一评分总和累加然后rating total_score / rating_count。我实际用的是UPDATE movie SET rating (rating * rating_count new_rating) / (rating_count 1)这种带条件的更新语句再加一层数据库行锁或乐观锁控制保证并发场景下不丢数据。不过毕设环节如果你的演示是单机单用户简单处理也说得过去但代码里至少要体现你在思考这个问题。点赞功能最怕的就是用户连续点导致数据虚高。我在 Redis 里用SET key user_id结构存储已点赞用户集合每次点赞先判断是否已存在如果不存在才写库并同步缓存。为了避免每次都穿透到 MySQL我采用“先写 Redis再异步刷库”的策略后台用 Spring 的Scheduled定时任务把点赞数据批量落库。3.3 评论区的敏感词过滤与分页查询影视评论属于公开内容一定要有内容安全思路。我在评论发布接口里加入了敏感词过滤用基于字典树Trie的关键词匹配来实现。相比正则表达式循环匹配Trie 的复杂度是 O(n*m) 且一次遍历能替换掉所有命中词效率高很多。实现时可以把敏感词表放在 Redis 或内存加载启动时构建一次 Trie更新词库时重新加载。分页查询这块MyBatis-Plus 的Page分页插件用起来很顺手但我要提醒一句查询影评列表时不要只查 review 表还要联查用户昵称和头像、影视标题否则前端要额外发很多次请求。我通常会写一个自定义 SQL 或使用Select注解联表查询把user表、movie表和review表一次查出来。另外别忘记给created_at字段加索引否则数据量到十万级时分页深度增加会引起明显的性能下降后面我会详细讲索引优化。4. 影视推荐系统的落地实现4.1 用户画像构建用标签和行为向量描述一个人推荐系统不能“千人一面”第一步就是给每个用户建画像。我的画像分两层显性画像注册时选择的感兴趣类型和隐性画像从用户行为中挖掘出来的偏好。显性画像存在user表或单独的用户偏好表里用户注册时可以勾选“喜欢科幻、动作、悬疑”等标签。隐性画像则由行为事件推导比如用户给某部电影打了 8 分以上就认为用户喜欢这部电影的标签用户加入“想看”列表也视为一个轻量正反馈。我设计了三种行为权重用于构建用户的标签兴趣向量行为类型行为权重说明高分影评评分≥82.0强正反馈说明很喜欢收藏影视1.8明确的感兴趣信号标记想看1.0有兴趣但还没看用户对某个标签的兴趣分数 该标签下所有行为的加权累加。比如用户写了一条科幻片的高分影评又收藏了两部科幻片那“科幻”标签的得分就是 2.0 1.8 1.8 5.6。计算完成后取分数最高的前 N 个标签作为用户标签向量存入 Redis 缓存每天定时更新一次。4.2 ItemCF 相似度计算纯 Java 手写余弦相似度聊到算法我直接上代码片段。首先把每个电影表示成行为向量维度是所有用户对该电影的行为组合。我这里实现一个简化版用电影的平均评分向量用户维度做相似度计算。public class ItemSimilarity { // 电影A和电影B的向量向量维度按用户ID对齐 public static double cosineSimilarity(double[] vectorA, double[] vectorB) { double dotProduct 0.0; double normA 0.0; double normB 0.0; for (int i 0; i vectorA.length; i) { dotProduct vectorA[i] * vectorB[i]; normA Math.pow(vectorA[i], 2); normB Math.pow(vectorB[i], 2); } if (normA 0.0 || normB 0.0) { return 0.0; } return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB)); } }这个方法的优点是直观易懂面试和答辩时你可以边说边画解释两个向量的夹角越小余弦值越接近 1表示电影越相似。缺点是没有考虑用户评分尺度差异比如有的用户手松、有的用户手紧这会导致向量偏斜。改进方案是做平均中心化把每个用户对电影的评分减去该用户所有评分的平均值再用中心化后的向量计算相似度。我记得测试时中心化后的结果明显更贴近直觉比如“肖申克的救赎”能正确推荐出“阿甘正传”“教父”这类影史经典而不是凭热度堆出来的大片。4.3 召回、过滤、排序推荐接口的完整流程一个可演示的推荐接口我的流水线是 召回 → 过滤 → 排序 → 输出。召回根据用户画像里权重最高的标签从movie_tag里找到对应电影集合再根据用户最近有过正反馈的电影从相似度矩阵中取出 Top 20 相似电影。过滤用户已经看过的电影、已经标记不喜欢的电影、完全没人评分的新片都要过滤掉。排序综合得分用一个线性加权公式score 0.6 * 相似度得分 0.3 * 标签热度得分 0.1 * 时间衰减因子然后把结果按 score 降序播放给前端。输出补全电影的评分、封面、导演、简介等展示字段组装成前端友好的 VO 对象。时间衰减因子我用的公式是1 / log(当前时间 - 上映时间 1)这样影片越新略微加分但不会完全压过相似度得分。如果你不想在这个细节上扣分也可以用简单规则上映时间在一年内的影片加 0.1 分。关键是让推荐列表“看起来合理”而不是随机堆砌用户一看感觉“这推荐的确实是我喜欢的类型”你就成功了。4.4 冷启动问题没有历史行为数据怎么办推荐系统最尴尬的场景是新用户和新电影。你不可能要求答辩老师先注册、打分、收藏再来看推荐效果所以必须准备冷启动方案。我的做法是在首页推荐栏设置多个策略位策略一热门影视榜按评分人数和评分综合排序比如hot_score 0.7 * rating 0.3 * log(rating_count 1)。策略二编辑精选后台管理员可以手动配置推荐位。策略三基于标签的试探推荐随机从用户最开始选择的兴趣标签里召回电影并提示“根据你关注的类型推荐”。这些策略位每个都对应一个独立的推荐接口或缓存 Key前端轮播位置可以自由组合。新用户一旦产生点击、收藏、评分等行为后台就为该用户生成画像下个请求周期就会主动切到个性化推荐体感很自然。5. 实操过程中的完整流程记录5.1 从零搭建 SpringBoot 工程项目我建议用 IDEA 的 Spring Initializr 创建项目选择 Java 8 或 11SpringBoot 版本选 2.7.x。为什么不选 3.x因为 3.x 基于 Jakarta EE部分老教程和插件不完全兼容学生阶段没必要给自己挖坑。依赖这块我勾选的是Spring Web、MySQL Driver、Spring Data Redis然后在pom.xml里手动补充mybatis-plus-boot-starter、jjwt、lombok、hutool这几个工具包。工程目录我习惯按功能分包而不是按技术分层。也就是说不搞controller/service/mapper/entity这种四层大文件夹而是按模块划分例如com.example.movie ├── common // 通用返回体、异常处理、常量 ├── config // Redis、MyBatis-Plus、Cors 配置 ├── security // JWT 拦截器、注解 ├── module │ ├── user // 用户相关的 Controller/Service/Mapper/Entity │ ├── movie │ ├── review │ ├── recommend │ └── dashboard这种分包方式在微服务概念盛行的今天更受认可也方便后续把推荐模块单独抽出去。5.2 配置文件的编写经验application.yml是整个项目最容易埋雷的地方。我的通用配置模板如下server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/movie_review?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: yourpassword redis: host: localhost port: 6379 database: 0 timeout: 3000ms mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: isDeleted logic-delete-value: 1 logic-not-delete-value: 0 jwt: secret: your-256-bit-secret-key-here-change-me expire-hours: 24注意serverTimezoneAsia/Shanghai一定要加否则连接 MySQL 8 会报时区错乱。map-underscore-to-camel-case: true可以让你数据库字段user_id自动映射到 Java 属性userId少写很多TableField配置。逻辑删除配置也强烈建议开启这样影评和用户下架都是软删除演示时还能保留数据真正做到可追溯。5.3 前端页面的接地气实现前端我推荐直接用 Vue 3 Element Plus不需要自己从零画页面。Element Plus 里现成的el-rate组件做星级评分el-carousel做首页推荐轮播el-tabs做“热门/最新/推荐”分类切换基本能覆盖 80% 页面需求。接口联调时注意跨域问题最简单的方案是在后端加一个CorsFilter配置允许本地开发地址访问。其实更好的是利用 SpringBoot 自带的反向代理机制把前端打包产物丢到src/main/resources/static里这样部署时只是启动一个 jar 包省去 nginx 配置演示也更稳。不过如果你要用npm run dev联调跨域配置还是少不了的我建议配置一个WebMvcConfig放行所有来源同时指定允许的请求头和方法。5.4 数据准备让数据库不那么“空”很多人的系统做完后一打开全是空数据评委一看到暂无数据四个字就可能觉得项目不完整。我建议写一个DataInitializer类在应用启动时判断数据表是否为空为空则自动插入 20 部热门电影、10 个预设用户、一批影评和标签。这些数据可以手工准备也可以用爬虫抓取豆瓣公开数据后整理成 SQL 脚本。注意版权和合规问题我建议只用标题、简介、类型这类公开基础信息不要抓取用户隐私数据或大段完整影评。实操时数据准备脚本和业务代码要分开data/import.sql单独存放避免启动时重复执行。选片时尽量选那些有代表性、类型差异大的电影比如动作片、科幻片、文艺片各有几部这样推荐系统的效果才能明显体现出来。6. 常见问题与避坑指南6.1 MyBatis-Plus 分页不生效、自动填充失效我踩过的第一个坑是分页插件没有正确注册。MyBatis-Plus 从 3.4 之后需要用MybatisPlusInterceptor配置分页插件直接new PaginationInnerInterceptor()是没有效果的。正确姿势是把它注册成 BeanConfiguration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }自动填充同理如果created_at字段想自动赋值要实现MetaObjectHandler接口并重写insertFill和updateFill方法别忘了在实体字段上加TableField(fill FieldFill.INSERT)。6.2 Redis 连接失败、对象序列化报错这个问题几乎人人都会遇到。如果启动时报Unable to connect to Redis先检查 Redis 服务是否启动、密码是否正确、端口是否被占用。Windows 同学尤其注意Redis 默认不启用远程连接且绑定127.0.0.1如果本地部署没问题但服务器部署失败要检查 redis.conf 里的bind和protected-mode。再说序列化问题Redis 默认使用 JDK 序列化Java 对象必须实现Serializable接口否则存入缓存时会报SerializationException。更优雅的方案是配置 Jackson 序列化器。但要注意Jackson 序列化LocalDateTime会报错需要额外添加JavaTimeModule并设置时间格式。网上有很多教程只贴了部分代码我建议直接用GenericJackson2JsonRedisSerializer它把类型信息也存进 value反序列化时不会丢失对象类型。如果还是报错大概率是你没有在 POJO 里添加默认空构造器。6.3 数据库连接乱码与时间丢失MySQL 连接串里没加characterEncodingutf8中文就会变成问号。这个好解决但我见过一个更隐蔽的坑连接串正确了但数据库本身的字符集是latin1这时候表字段还是乱码。解决办法是建库时显式指定DEFAULT CHARSETutf8mb4同时对已有表执行ALTER TABLE xxx CONVERT TO CHARACTER SET utf8mb4。时间丢失大多是serverTimezone没设对。中国时区建议写Asia/Shanghai如果你用了GMT8部分高版本 MySQL 驱动反而会在夏令时问题上出毛病。另外 Java 实体字段最好用LocalDateTime而不是Date配合 MyBatis-Plus 的自动转换前后端用时间戳或 ISO 字符串传输能避免不少格式化问题。6.4 推荐结果“感觉不对”怎么调试推荐效果不合预期的原因有很多我在实践中总结出的排查顺序是这样的先看行为数据是否完整比如用户是否真的产生了足够多的正反馈再看画像是否生成去 Redis 里检查user:profile:{id}这个 Key 是否存在、标签分数是否正确然后看相似度矩阵是否冷启动不充分如果新电影没有任何用户行为它和所有电影的相似度都是 0自然会被过滤掉这时需要兜底策略补位。最后看排序公式权重设置不合理会让相似度高的影片排在被刷出来的热门片后面建议把日志打开在控制台打印每条候选电影的得分拆解问题一目了然。6.5 一套“最小可用方案”供时间紧张的同学参考如果你离答辩只剩一周我建议按以下顺序砍功能保留用户注册登录、影视浏览、影评发布、热门榜单、基于标签的推荐把收藏、点赞、关注、后台管理、Elasticsearch 这些全部砍掉。核心思路是先把主干链路打通保证演示流畅。之后再补一个亮点比如用 Redis 做热门缓存、用定时任务更新推荐列表就已经超过大多数毕设了。7. 答辩准备与项目扩展方向答辩时老师最常问的问题集中在为什么选这个课题、系统架构怎么设计的、数据库为什么这样设计、推荐算法原理是什么、遇到了什么难点怎么解决的。我建议你准备一页“系统架构图”手画即可上面标注出用户端、后端、缓存、数据库、定时任务之间的关系讲的时候按链路走条理清晰且能展示工程全局观。关于推荐算法回答重点放在“召回、过滤、排序”三步流程上顺便点出冷启动策略。老师如果继续问“怎么评估推荐效果”你可以说用离线实验的准确率和召回率评估比如取用户最近看的电影做测试集把推荐结果 Top 10 和测试集做交集算准确率。这个方法在毕设层面已经足够了不需要真的搭一套在线 A/B 测试平台。项目后续扩展方向也想清楚这部分能体现你对技术发展的认知。一是引入 Elasticsearch 做全文搜索和更精准的标签召回二是用 Redis 的 ZSet 做实时热门榜单三是引入 RabbitMQ 或 Kafka 做用户行为事件的异步采集解耦影评写入与推荐更新四是把推荐从 ItemCF 升级为矩阵分解或图神经网络模型但中间件和模型都要考虑部署成本毕设阶段点到为止即可。8. 我的几点真实体会做到最后你会发现这类系统真正难的不是某个算法有多高深而是怎么把一堆组件协调起来让整个链路稳定跑通。我中间有一次因为 Redis 序列化配置没配好整个用户登录后的推荐接口全挂了排查了一下午才发现是 value 序列化器的问题。所以我想提醒所有做类似项目的同学环境问题、配置问题永远比业务逻辑问题更隐蔽、更浪费时间从一开始就要养成随手看日志、写单元测试、做接口自测的习惯。还有一点是关于演示的。答辩时宁可少做功能也要把主线流程做到“毫无破绽”。我强烈建议在答辩前一天把系统从零启动一遍走一遍“注册→登录→搜索电影→发影评→看推荐列表”的完整路径把所有可能报错的环节修掉。因为老师不一定会看你的代码细节但一定会点开你的系统点两下。最后送大家一句我在做完这个项目后写在笔记第一页的话毕设不是学习过程的终点而是把零散知识拼装成完整认知的起点。做完 SpringBoot 影视评论系统之后你再看 Java 生态里的其他框架、中间件、设计模式很多地方都会有一种“原来如此”的贯通感。希望这篇长文能帮你在做项目的路上少踩几个坑多拿一点分。
返回列表