1. 毕设选题定调:为什么是SpringBoot小说站
每年到课程设计或者毕业设计的时间节点,最折磨人的不是写代码,而是选题。选得太简单,答辩时没有亮点,评委老师一句“这个工作量够吗”就能让你哑口无言;选得太复杂,技术栈铺开一大堆,最后烂尾的比比皆是。小说阅读平台这个方向,恰好卡在了一个非常巧妙的平衡点上——业务场景大众化谁都理解,功能模块可深可浅伸缩自如,技术栈用SpringBoot又是一条既有说服力又不会失控的主线。
先说清楚SpringBoot在这个项目里承担的角色。SpringBoot本身不是一个功能框架,而是一套整合框架的框架,它解决的是Spring生态里的配置地狱问题。传统SSH或者SSM项目里,光是一个spring.xml加springmvc.xml再加mybatis.xml就能折腾死人,更别提各种jar包版本冲突。SpringBoot用自动配置把这一切按了下去,让开发者能专注于业务代码而非环境搭建。小说阅读平台这种业务密集型项目,恰恰需要这种高效率的开发节奏——你不可能把大量时间耗死在配置上,你要做的是把小说管理、用户体系、阅读器、搜索等功能一个个堆起来。
从答辩角度讲,SpringBoot项目的优势也很明显。评委老师对SpringBoot的技术链路非常熟悉,自动配置原理、starter机制、约定优于配置这些知识点几乎可以稳拿分,而且GitHub上同类开源项目多,遇到坑时能查到的资料也丰富,不至于卡死在某一个奇怪的问题上。如果你做的是前后端分离版本,SpringBoot只负责提供RESTful API,前端用Vue或Thymeleaf渲染,这又是一个加分点,能体现你对现代Web开发模式的理解。
这个选题适合哪几类人?第一类是时间紧、任务重,需要在一个月甚至两三周内出完整项目的同学;第二类是Java基础有一定底子,但对SpringBoot没有系统实践的同学,正好借项目把核心原理摸一遍;第三类是打算后续找Java后端开发岗位,需要一个拿得出手的项目写进简历的同学。小说阅读平台麻雀虽小五脏俱全,它几乎覆盖了一个后端工程师日常工作的所有常见场景——CRUD、关联查询、分页搜索、权限控制、事务管理、文件上传、缓存优化,这些东西每一个都是面试官乐意深挖的话题。
我见过不少同学选“图书管理系统”这种题目,做出来的东西就是单表的增删改查,答辩时自己都讲不出花来。小说阅读平台的不同在于,它天然带有“阅读体验”这个产品层面的思考维度,意味着你必须考虑章节分页、阅读进度、书架收藏、搜索排序这些真实业务逻辑,而这些恰好在后端实现层面有足够的复杂度,能够让你的代码量和工作量经得起检验。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 从零搭建项目:核心依赖与数据表设计逻辑
定下技术方向后,第一步不是急着写代码,而是把地基打牢。我这里用的方案是SpringBoot 2.7.x + MyBatis-Plus + MySQL 8.0 + Redis(可选)+ Maven多模块或单模块按包分层。如果你不想引入太多复杂性,单模块完全够用,代码结构包名分层清晰即可。网上流传的SpringBoot版本太高的坑,核心原因是SpringBoot 3.x开始基于Jakarta EE 9+,javax迁移到jakarta包,很多老教程代码直接跑不通。课程设计项目建议锁死SpringBoot 2.7.x,这也是目前企业里使用率最高的稳定版本之一,网上遇到问题时搜到的解决方案也基本上是针对2.x的。
2.1 Maven依赖配置与版本约定
随便去GitHub拉一个开源小说项目,你会发现pom.xml里第一关就是依赖版本协调。SpringBoot的parent POM已经帮你维护了大部分常用依赖的版本,比如spring-boot-starter-web、spring-boot-starter-validation、spring-boot-starter-security等,你只需要声明starter名称而不需要写版本号。但MyBatis-Plus、MySQL驱动、Redis客户端这类第三方库,不在SpringBoot的版本管理范围内,必须显式指定。
这里是项目的基础依赖清单,我直接贴出关键部分:
xml复制<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
<relativePath/>
</parent>
<dependencies>
<!-- Web基础 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MyBatis-Plus 增强ORM -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<!-- Lombok 简化实体类 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Redis 缓存(可选项) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 安全框架 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
关于MyBatis-Plus和MyBatis的关系需要多说一句。MyBatis-Plus是MyBatis的增强工具,内置了通用Mapper和通用Service,单表CRUD不需要写SQL语句,直接调用baseMapper.selectById()或lambdaQuery()链式查询即可,能省掉大量重复劳动。课程设计项目里,你会写大量的单表操作,比如根据小说ID查章节列表、根据用户ID查书架记录,这些用MyBatis-Plus的Wrapper机制就能搞定,不需要手写XML映射文件。手写复杂SQL的场景也有——比如多表关联的排行榜查询——但你可以在Service层用LambdaQueryWrapper先过滤再组装,或者直接在Mapper接口上用@Select注解写SQL,两种方式混合使用,既灵活又有可读性。
2.2 数据库表结构设计:从用户到阅读进度的完整链路
数据库设计是整个项目的灵魂,表建歪了后面写代码就会各种别扭。我在另一个项目里见过有人试图把小说章节和小说内容存到同一张表里,结果一张表几十万条大文本数据,查询慢得离谱。这里给大家一套经受过多次检验的表结构方案,覆盖小说平台核心链路。
核心表清单
| 表名 | 用途 | 关键字段说明 |
|---|---|---|
| user | 用户表 | id, username, password(BCrypt加密), nickname, avatar, role(区分管理员/普通用户) |
| novel | 小说表 | id, title, author, category_id, intro, cover_url, status(连载/完结), word_count, click_count |
| category | 分类表 | id, name, sort_order |
| chapter | 章节表 | id, novel_id, chapter_no, title, content(长文本), word_count, create_time |
| bookshelf | 书架表 | id, user_id, novel_id, last_read_chapter_id, update_time |
| reading_progress | 阅读进度表 | id, user_id, novel_id, chapter_id, progress_percent, update_time |
| comment | 评论表 | id, novel_id, user_id, content, parent_id, like_count, create_time |
几个容易忽略但很重要的设计点:
-
chapter表的chapter_no不是自增主键,而是每个小说内部独立编号,用于排序和“上一章/下一章”跳转。查询上一章时WHERE novel_id = ? AND chapter_no < ? ORDER BY chapter_no DESC LIMIT 1即可,比拿自增ID判断靠谱得多。因为如果章节被删除过,自增ID会有空洞,用“ID-1”去查上一章就会出现查不到的情况。 -
novel表的click_count是热点字段,每次阅读都要自增。这个字段如果每次都走MySQL更新,高并发下会是性能瓶颈。优化思路有两种:一是用Redis的INCR命令先累加,定时同步回MySQL;二是在应用层直接发一条UPDATE novel SET click_count = click_count + 1 WHERE id = ?,靠MySQL的行锁保证原子性。课程设计阶段用第二种就能交差,但答辩时主动提一句“这里可以用Redis做异步化优化”,是一个天然的加分项。 -
reading_progress和bookshelf看起来功能重叠,其实有明确的职责边界。bookshelf关注的是“用户收藏了哪些小说”,reading_progress关注的是“用户读到某一本书的哪个章节、翻到了百分之几”。前端阅读器每次打开章节都要查询进度,频繁读写,所以reading_progress表的索引设计要跟上:UNIQUE KEY uk_user_novel (user_id, novel_id),既能保证同一用户对同一本书只有一条进度记录,又能加速查询。 -
分类表
category虽然只有几条数据,但单独建表而不是做成枚举字段,是为了后续运营可以自主添加分类、调整排序,不需要改代码重新部署。这个设计习惯在企业里非常重要——凡是需要运营动态配置的数据,都应该独立成表。
导入数据库脚本的时候有两点提醒。第一,用Navicat或者IDEA自带的Database工具执行SQL脚本时,注意字符集要选utf8mb4而不是utf8,否则存不了emoji和生僻字。第二,长文本字段content用text类型最大可存64KB,对一般章节绰绰有余,但如果你的小说源里有些超长章节,建议直接上mediumtext,16MB的容量,安心许多。
3. 核心功能实现拆解:别把CRUD做成“摆设”
小说阅读平台的功能模块,表面看是增删改查,但每一块里都藏着值得展开讲的门道。这一章我挑四个硬骨头——用户注册登录、小说内容管理、阅读器进度链路、搜索与排行榜——逐个说明实现思路,也会讲清楚为什么这样做,以及答辩时可以怎么延伸。
3.1 用户体系:BCrypt加密与JWT无状态认证
用户模块如果只做“用户名密码存在数据库、登录时查表比对”,太单薄了,答辩完全扛不住追问。正确的打开方式是BCrypt加盐哈希存储密码,加JWT实现无状态认证。
BCrypt是Spring Security内置的密码加密器,它对同一明文每次生成的哈希值都不同,因为内部自动混入了随机盐。这意味着就算两个用户注册时密码都设成“admin123”,数据库里存的两串密文也不一样,攻击者无法通过密文对比判断出是否有相同的弱口令。注册时调用BCryptPasswordEncoder.encode(rawPassword),登录时调用matches(rawPassword, encodedPassword)校验,代码简洁安全。
JWT的实现思路也不复杂。用户登录成功后,服务端生成一个包含用户ID、用户名、角色等信息的token返回给前端。前端每次请求在HTTP头里带上Authorization: Bearer <token>,后端通过拦截器解析token,从Claims里取出用户ID,就知道当前请求是谁发起的。这样做的好处是服务端不需要保存Session,天然支持前后端分离部署,集群环境下也不需要额外的Session同步方案。
JWT有几个坑必须提前说清楚。第一,token里不要塞敏感信息,它是Base64编码,不是加密,别把用户手机号、密码塞进去。第二,一定要设置过期时间,课程设计项目设个24小时基本够用。第三,服务端需要维护一个“token黑名单”或“用户已注销token列表”吗?这是答辩高频问题。标准答案是:在用户修改密码或管理员封号场景下,需要把旧的token作废,实现方式是在Redis里存一个用户版本的key,JWT解析时校验版本号是否一致,简单可靠。
Spring Security在这个项目里的配置方式需要平衡——默认的Spring Security配置会拦截所有请求,你必须写一个SecurityConfig类,明确哪些路径放行(比如登录、注册、小说列表、章节详情),哪些路径需要认证(比如书架、评论、阅读进度),哪些路径需要管理员角色(比如小说管理后台)。用antMatchers().permitAll()和antMatchers().hasRole("ADMIN")把权限切出来,这也是答辩时可以大讲特讲的部分。
3.2 小说管理后台:文件上传与富文本内容入库
小说管理是后台模块的核心,需要支持添加小说基本信息、上传封面图片、维护章节内容。这一块的关键技术点是文件上传,以及大文本内容的存储与读取。
封面上传我采用的是本地存储方案:文件上传接口接收MultipartFile,校验文件大小和类型(jpg/png,不超过2MB),存储到服务器的/uploads/cover目录下,文件名用UUID重命名防止冲突,然后把文件路径存到数据库。启动类里要配置一个虚拟路径映射,让外部可以通过http://你的域名/cover/uuid.jpg访问到磁盘上的文件:
java复制@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 将 /cover/** 映射到本地磁盘目录
registry.addResourceHandler("/cover/**")
.addResourceHandler("file:" + uploadDir + "/");
}
}
为什么不直接用Base64把图片存进数据库?因为Base64会比二进制体积膨胀约33%,数据库表变得臃肿,查询性能下降,也没法利用CDN加速。课程设计阶段本地存储够用,答辩时提到“生产环境会改为对象存储OSS”也是一句轻描淡写但很有层次的话。
章节内容入库时要注意一个问题:前端提交的富文本正文中可能包含HTML标签、转义字符,如果直接拼接SQL会有SQL注入风险,如果你用的是MyBatis-Plus的参数绑定机制#{},它底层走的是PreparedStatement,SQL注入基本可以免疫。真正容易踩的坑是XSS攻击——用户提交的内容里可能带有<script>标签,在后端存储时必须做HTML转义,或者在前端渲染时用textContent而不是innerHTML。如果你没有额外引入XSS过滤依赖,至少要在保存时把<和>替换成<和>,展示时再反转义。这个细节在代码评审和答辩中很受关注。
3.3 阅读器与阅读进度:分页加载与并发更新
阅读器是用户体验的核心。章节内容动辄几万字,一次性全量返回会拖慢首屏加载速度,业界通用的方案是分页加载——前端每次加载2000字左右,往下滚动时继续拉取下一段。后端提供的接口可以是GET /api/chapter/content/{chapterId}?page=1&pageSize=2000,内部通过substring截取内容段落,或者更规范一点,把章节内容提前按固定字数切分成多个片段存到chapter_segment表,按序加载。
不过,课程设计项目如果做分片存储会增加不少复杂度,我建议折中方案:章节内容一次性返回,前端用CSS控制滚动区域,文案上标注“本章共X字”,移动端和PC端阅读体验都OK。把“分页加载”作为后续优化项写进文档,并解释其实现思路,这反而比硬凹一个不成熟的分片方案更稳。
阅读进度的保存是另一个核心话题。用户每次翻页、退出阅读器,前端都要上报一次进度,接口设计为PUT /api/progress,参数包括novelId、chapterId、percent。后端更新逻辑比较直接,先查reading_progress表有没有记录,有则更新,没有则插入,但要注意用唯一索引兜底并发场景——如果用户同时在两个设备上阅读同一本书,两个请求并发到来,先select再insert就会出现唯一索引冲突。稳妥的做法是直接用MySQL的INSERT ... ON DUPLICATE KEY UPDATE语句,一条SQL搞定插入或更新,天然线程安全。
书架表和阅读进度表的关系也要理清。当用户点击“加入书架”时,插入bookshelf记录;当用户打开某本小说的阅读器时,更新的其实是reading_progress表。书架列表页面展示的“最近阅读章节”,应该联表查询bookshelf和reading_progress,拿到每一本书的最新阅读章节ID和标题。这里有一个细节——书架列表的排序,建议以reading_progress的update_time倒序,而不是书架创建时间,这样用户在书架看到的第一本永远是最近在读的书,更贴近真实产品逻辑。
3.4 评论与搜索:从简单的CRUD到倒排索引的进阶
评论模块是一个典型的关联查询场景。用户在前端发表评论,后端保存评论内容,然后小说详情页展示该小说的评论列表,支持分页加载和点赞数排序。单表CRUD之外的两个加分点:一是评论的二级回复——用户A评论,用户B回复用户A,通过parent_id字段关联,前端递归渲染。二是评论的敏感词过滤——可以先用一个简单的敏感词列表做字符串匹配,答辩时提到“完整方案会用DFA算法构建敏感词树”,这又是一个可以深挖的点。
搜索模块是小说网站的流量入口。最简单的方式是SQL的LIKE '%关键词%'模糊匹配,但表数据量大之后性能会急剧下降,因为无法走索引。稍微进阶一点的做法是,在novel表的title和author字段上建全文索引,用MySQL自带的MATCH ... AGAINST语法。如果项目做到“搜索引擎级”,就要引入Elasticsearch或者轻量级的H2全文索引了,但这对于课程设计来说可能过重。我的建议是:搜索接口用MyBatis-Plus的like条件,同时在数据库加上LOCATE或者全文索引,并在文档的“后续优化”一节里说明引入Elasticsearch的架构思路——用Logstash同步MySQL数据到ES,Java端通过RestHighLevelClient查询。论文里画一张架构图,答辩时讲得头头是道,这个项目就立起来了。
排行榜模块则是一个典型的聚合查询场景。实现指标包括点击量、收藏数、评论数。可以用COUNT(*)加GROUP BY联表统计,但如果数据量大,每次实时统计都比较吃力。课程设计阶段直接实时查询问题不大,答辩时提到“引入Redis的ZSet按日统计TOP100”是标准的优化思路,属于性价比极高的加分策略。
4. 踩坑实录:从数据库乱码到事务失效的排查链路
这一部分我想原原本本还原一遍自己在开发和调试过程中遇到的三个拦路虎。这些坑单拎出来每一个都不算难,但如果你没有排查经验,可能一下午就耗在里面了。它们也是答辩时最有“故事感”的素材。
4.1 URL传参中文乱码:字符集贯穿全链路
开发搜索功能时,前端在URL里传?keyword=斗破苍穹,后端接口收到的却是????。查了浏览器Network面板,确认请求URL里的中文是正常编码的,那问题就出在后端接收环节。
排查链路是这样的:先在Controller方法入口打日志,打印keyword参数,发现确实是问号。再看Spring的配置,检查server.servlet.encoding.enabled=true是否开启,确认启用后依然乱码。最后想到可能是Tomcat的URI编码默认不是UTF-8,于是需要在application.yml里显式配置:
yaml复制server:
tomcat:
uri-encoding: UTF-8
servlet:
encoding:
charset: UTF-8
enabled: true
force: true
配置好后重启,乱码消失。
这个问题看着小,但如果在数据库导入、HTML页面、Nginx转发的任何一层漏了,都会出现类似情况。所以从建库开始就要统一字符集:数据库连接串加characterEncoding=utf8,建表语句用DEFAULT CHARSET=utf8mb4,前端页面<meta charset="UTF-8">,全链路统一才能根治。
4.2 MyBatis-Plus分页查询失效:插件必须显式注册
用MyBatis-Plus做分页时,我写过这样的代码:
java复制Page<Novel> page = new Page<>(current, size);
novelMapper.selectPage(page, wrapper);
但返回的page.getRecords()永远是全部数据,page.getTotal()等于0。这个问题非常隐蔽,因为MyBatis-Plus的分页功能默认不生效,需要手动配置一个分页插件:
java复制@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
没有这个Bean,selectPage方法内部只是简单查了个总数,并没有在SQL尾部拼接LIMIT。这是MyBatis-Plus最经典的坑,几乎每届学生都会踩一次。排查方法很简单:打开MyBatis-Plus的SQL日志,如果发现执行的是SELECT COUNT(*)和SELECT * FROM novel而不是LIMIT ?,就说明分页插件没生效。
4.3 事务失效:同类调用与异常被吞的两种隐性陷阱
写“批量导入章节”功能时,我规定:要么全部章节都插入成功,要么全部回滚。Service层方法加了@Transactional注解,测试时手动在循环里抛一个运行时异常,结果发现前几条章节还是被写进数据库了。
这个问题的根源在于Spring AOP的事务代理机制。@Transactional生效的前提是方法被外部调用,需要通过代理对象进入。如果在同一个类内部的私有方法或平滑调用中打上注解,Spring的事务管理器根本感知不到——因为你绕过了代理直接调用了目标方法,事务自然不生效。
排查链路第一步:确认注解加在了public方法上;第二步:确认方法是Controller -> Service调用链上的入口,而不是Service内部自我调用;第三步:如果必须内部调用,用AopContext.currentProxy()获取代理对象再调用。
另一个坑是异常被吞。Spring的事务回滚策略默认只对RuntimeException和Error生效,像IOException这种受检异常,即使抛出也不会触发回滚。你在批量导入章节时如果捕获了SQL异常并且没有重新抛出,事务会错误地提交。我最后在批量导入的实现里加了@Transactional(rollbackFor = Exception.class),并且捕获异常后用throw new RuntimeException(e)直接抛出去,问题才解决。
这两个坑在基本功不扎实的同学身上经常遇到,答辩时当作“踩坑-定位-解决”的小故事讲出来,反而能给老师留下扎实的印象。
5. 把项目从“能跑”打磨成“能讲”:文档、部署与答辩加分项
写文档是最多人敷衍、也最能拉开差距的环节。课程设计或者毕业设计,最终交上去的永远是两样东西——能运行的项目和能讲清楚的文档。这里系统讲一遍我的组织方法和实战经验。
5.1 万字文档的撰写结构:从需求分析到测试用例
一份好的项目文档不是代码的流水账,而是一份“为什么这样做”的说明书。我的套路是分成六块:
- 需求分析:从用户角色出发,写管理员、普通用户分别要完成哪些操作,配上用例图和用例描述表格。用例图尽量用PlantUML画,导出成图片插入Word,干净规范。
- 系统设计:包括总体架构图(浏览器 -> Nginx -> SpringBoot -> MySQL/Redis)、功能模块划分、数据库ER图和表结构说明。这里的架构图不追求高大上,能把请求链路画清楚即可。
- 核心模块实现:挑选4-5个核心流程,比如用户登录鉴权、小说发布、阅读进度保存、搜索排序,配关键代码片段并逐段解释。
- 系统测试:设计测试用例表,覆盖正常流和异常流。例如登录测试用例:输入正确密码返回token;输入错误密码返回401;用户名不存在返回404。测试结果截图放进去,表格形式清晰直观。
- 部署说明:JDK版本、Maven打包命令、MySQL建库脚本、服务器部署步骤。这一步非常实用,因为老师如果要在本地运行你的项目,这份说明就是他唯一依靠的操作手册。
- 总结与展望:总结不要写“我学到了很多”这种空话,而是明确指出哪些模块用了什么技术、解决什么具体问题,有哪些已知不足和后续优化空间。
写完初稿后,把每个章节的字数和功能点对应起来,一份完整的万字文档就有了。注意“万字文档”的说法不要在标题或摘要里赤裸裸地强调,但答辩时“我有完整的设计文档、测试报告、部署手册”一说出口,份量立刻就不一样。
5.2 本地打包部署:IDEA导出Jar包与数据库初始化
部署这一步,我是按“一键运行”的标准来配的。项目开发完,在IDEA右侧Maven面板执行clean package,如果测试代码会影响打包时间,可以加上-DskipTests跳过。打包完成后,target目录下会生成一个novel-0.0.1-SNAPSHOT.jar。
服务器上运行只需要三样东西:JDK1.8或JDK11、MySQL数据库、这个Jar包。
bash复制# 1. 上传Jar包到服务器后,初始化数据库
mysql -u root -p < novel.sql
# 2. 修改application-prod.yml里的数据库账号密码
# 3. 启动服务
nohup java -jar novel-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod > app.log 2>&1 &
这里强调一下--spring.profiles.active=prod的作用。在开发环境里,数据库地址可能连着本地localhost;部署到服务器后,数据库地址变成远程IP,如果每次部署都去改application.yml再重新打包,效率太低。正确做法是配置多环境:application-dev.yml和application-prod.yml,分别维护不同的数据库连接、日志级别、文件上传路径。启动时通过--spring.profiles.active指定用哪套配置,非常干净。
启动成功后,在浏览器访问http://服务器IP:8080。如果用的是云服务器,记得在安全组规则里放行8080端口,否则访问超时找半天都找不到原因。数据库初始化时如果遇到Access denied for user,检查一下MySQL账号权限是不是只允许localhost访问,需要新建一个允许远程访问的账号并授权。
5.3 答辩加分项:自动装配原理、缓存与性能优化
一个项目如果想要从“完成”变成“优秀”,必须在答辩前准备好几个可以主动展开的加分点。
SpringBoot自动装配原理是必背项。SpringBoot启动时,@SpringBootApplication注解里的@EnableAutoConfiguration通过AutoConfigurationImportSelector,扫描所有jar包里的META-INF/spring.factories文件,读取里面配置的自动配置类。这些自动配置类上有@ConditionalOnClass、@ConditionalOnMissingBean等条件注解,只有当类路径下存在对应的类、容器里没有用户自定义的Bean时,才会执行自动配置逻辑。比如你引入了spring-boot-starter-data-redis后,类路径下有RedisTemplate类,Spring Boot就会自动创建一个RedisTemplate的Bean——这就是为什么你什么都不写就能直接@Autowired RedisTemplate。这段解释如果你能从注解讲到条件装配机制,评委立刻知道你读过源码,而不是只会CRUD。
Redis缓存优化也很容易展开。把首页热点小说列表、分类菜单等不常变的数据缓存到Redis,设置过期时间,比如30分钟。用户请求进来时先查Redis,命中则直接返回,不命中再查数据库并同步到Redis。这样实现的代码量不大,但性能提升是肉眼可见的——压测数据可以从每秒几十次请求提升到几百次。答辩时配上简单的redisTemplate.opsForValue().set(key, value, timeout)代码和压测截图,说服力极强。
数据库索引设计也是一个好的加分点。除了主键和唯一索引之外,要聊清楚两个常用索引的使用场景:chapter表的idx_novel_id_chapter_no (novel_id, chapter_no)覆盖了“查某本小说所有章节排序”的查询场景;reading_progress表的uk_user_novel (user_id, novel_id)覆盖了用户进度查询场景。这些索引不是越多越好——索引会拖慢写入速度、占用磁盘空间,设计时要根据实际SQL的WHERE条件来反推。能在答辩时主动聊“索引如何使用EXPLAIN验证”,而不是老师问到才慌张翻代码,项目就成功了六成。
6. 验收标准与二次开发扩展方向:项目之外还能做什么
项目做完之后,最关键的一步是拿验收标准来一遍自查。下面这份checklist是我做课程设计期间总结的,做毕业设计也能直接用。
| 检查项 | 具体要求 |
|---|---|
| 项目能本地运行 | 拿到一份全新的环境,按部署文档操作,15分钟内能跑起来 |
| 核心功能无崩溃 | 注册、登录、搜索、阅读、评论、书架,6条核心链路全部通 |
| 异常输入有提示 | 传非法ID、空密码、超长文本,后端返回明确错误信息而不是500 |
| 数据库表结构规范 | 每张表有主键,带create_time/update_time,字符集为utf8mb4 |
| 接口有权限控制 | 未登录不能访问个人书架,非管理员不能进后台管理 |
| 文档和代码一致 | 文档里的运行步骤、环境依赖、功能截图与代码实现完全对得上 |
平时用着正常的功能,在“一个全新环境”面前往往会暴露问题。所以建议你在最终提交前,拿一台干净电脑或者虚拟机按文档跑一遍部署流程,能跑通,这份作业就稳了。
项目的扩展空间其实很大,我做这个项目的时候,就已经规划好了三个可选的二次开发方向。第一个是接入WebSocket实现弹幕功能——读者在阅读时发送弹幕,后端通过WebSocket广播给其他在线读者,这比轮询接口要高效得多,也能体现你对实时通信的理解。第二个是富文本评论升级——支持表情、图片、敏感词过滤,评论的交互体验向主流社区看齐。第三个是用户阅读数据的分析统计——记录用户阅读时长、完读率、追更规律,在个人中心以图表形式展示,这就涉及ECharts数据可视化,前端又是另一片发挥空间。
所有验收项都跑通之后,再把项目打包上传。GitHub上建个仓库,写好README.md,贴上项目截图、项目介绍、运行说明。一方面是为答辩时演示做备份——万一本地环境出问题,克隆下来重新部署就行;另一方面,这也是你求职时展示给面试官的拿得出手的材料。README的写法也有讲究:第一屏放项目简介和效果图,第二屏放技术栈和功能清单,第三屏放目录结构和部署方式,冷冰冰的代码库也会变成一件体面的作品。
最后再分享一个小技巧。答辩之前,把项目的核心流程从头到尾演示两遍,第一遍按照自己熟悉的节奏,第二遍刻意打乱节奏——先演示增加评论,再演示登录,测试自己对项目每个模块的熟悉程度。一个真正掌握了项目的学生,无论老师从哪个功能点切入,都能接得住话。这比死背稿子有用得多。
