1. 选型定案:这套前后端分离方案到底解决了什么问题
很多人做毕设或者课设的时候,最纠结的一句话就是"我到底用什么技术栈"。我自己帮不少同学看过选题和代码,发现一个很真实的规律:选题本身不难,难点往往在于"技术选型太杂"和"边界不清"。SpringBoot加Vue加MySQL这套组合,不是因为它新或者流行才被大家反复用,而是它刚好卡在一个非常舒服的位置:后端生态成熟、前端组件丰富、数据库稳定,而且网上可参考的实践足够多。
拿在线小说阅读平台这个题目来说,它本质上是两个系统叠在一起:用户端负责读,管理端负责管。如果放在十年前,大家可能用JSP加Servlet硬拼,页面写在一起,维护成本很高。现在用前后端分离,阅读器页面、书籍列表、后台管理界面各自独立开发,后端只需要把接口做好,联调的时候各改各的,效率完全不一样。对于毕设答辩来说,面试老师问"你为什么用这个技术栈",你能讲出清晰理由,本身就非常加分。
1.1 为什么是SpringBoot而不是SSM/JSP
先聊后端。SSM在高校课程里仍然是经典教学组合,但它的问题在于配置太琐碎:Spring、SpringMVC、MyBatis三个框架要手动对接,各种XML配置文件和扫描规则,新手光是把项目跑起来可能就要折腾一周。SpringBoot把这些默认配置都做了约定,一个依赖管理器加一个启动类就能起一个Web服务,开发体验是质的飞跃。
对小说阅读平台来说,后端要做的事情本身不复杂:用户管理、书籍管理、章节管理、评论和书架。这些都是典型的CRUD加一点业务逻辑,SpringBoot加MyBatis-Plus可以很快把基础接口搭出来。MyBatis-Plus的分页插件写起来比原版MyBatis舒服很多,不用自己写重复的分页SQL,对时间紧张的课设项目非常友好。
还有一个现实原因:跑项目的时候不会因为环境问题卡死。SpringBoot内置了Tomcat,不需要单独装Tomcat再部署war包,生成一个Jar包直接启动就行。对很多在自己电脑上演示的毕设项目来说,"能快速运行"比"架构多优雅"重要得多。
1.2 Vue在这里承担了什么角色
前端选Vue,最关键的是组件化开发带来的掌控感。小说阅读平台有大量重复结构:书籍列表卡片、章节列表项、表格行、表单弹窗。用Vue组件可以把这些拆开,比如一本小说卡片只需要传一个novel对象,在组件内部渲染封面、作者、分类和状态,整个页面逻辑瞬间清晰。
配合Vue Router,页面跳转不用刷新。阅读器的体验尤其依赖这个特性:从章节页目录跳转到正文,从正文返回书籍详情,如果每次都刷新整个页面,阅读连贯性会大打折扣。Vue的单页应用模式正好解决这个问题,切换路由只更新局部组件,用户体感接近原生应用。
管理端使用Element UI这套组件库,表格、分页、弹窗、表单校验都是现成封装好的。课设项目不需要把大量时间花在前端美工上,把组件拼好、样式统一,视觉上已经达到一个比较专业的水平。这也是Vue在高校项目里流通率这么高的原因之一:它让学生的精力集中在业务逻辑上,而不是跟CSS较劲。
1.3 MySQL的定位与存储策略
数据存储用MySQL是最稳妥的选择。小说平台的数据模型非常关系化:用户、小说、章节、评论、书架之间有明确的外键关系,用关系型数据库可以很自然地表达这些关联。对比一下MongoDB这类文档数据库,确实也能存,但查询"某本小说有多少章""某用户书架里有哪些书"这类需求反而要写更复杂的聚合逻辑,没有必要。
说句实话,课设项目的数据量通常不会太大,几千本书、几万个章节已经很多了,MySQL在这个量级下轻松应付。真正要花心思的是字段类型设计:小说正文是长文本,不能用小字段存;章节排序、点击量统计这类数字要保持合适的类型和索引,查询才不会慢。数据库设计合理了,后端的代码写起来就非常流畅。
2. 模块蓝图:阅读前台与管理后台两条线的功能边界
小说平台最容易被忽略的地方,是把"用户读的页面"和"管理员用的页面"混在一起讲。实际上这两条线的需求、交互模式和权限体系都不一样。用户关心的是找到书、读起来舒服、能记住进度;管理员关心的是内容能不能方便地录入、审核、上下架。划分清楚这两条线,项目结构的骨架才算立住。
2.1 前台用户端功能清单
前台是用户看得见摸得着的部分,我建议至少覆盖这几个模块:注册登录、首页书库、分类筛选、书籍搜索、书籍详情、章节列表、正文阅读器、个人书架、阅读历史和评论。
首页不要做太复杂,一个轮播图展示推荐书籍,下面按更新时间和点击量各出一列书单就够用了。分类页做一个左侧分类树,右侧书籍卡片流,点击分类时按categoryId查询。搜索框支持按书名和作者模糊匹配,关键词传入后端走LIKE查询。书籍详情页展示封面、书名、作者、分类、简介、状态和章节目录,点击章节就进入阅读器。
阅读器是整个前台的灵魂。章节正文居中显示,章节底部有上一章和下一章的切换按钮,顶部或侧面有目录入口。用户阅读的时候记录下章节ID和阅读时间,下次打开书架或详情页时,可以直接提示"继续阅读"。这些功能点单个看都不难,拼在一起就是一个完整的阅读闭环。
2.2 后台管理端功能清单
后台管理端是内容维护的核心。小说管理负责图书信息的维护,包括新增书籍、编辑简介、上传封面、修改状态、设置分类;章节管理负责按小说维护章节,要支持自动生成章节序号;分类管理维护书库的类别树;用户管理可以查看注册用户列表,必要时进行禁用或删除操作;评论管理用于审核用户评论,支持删除违规内容。
还有一个容易被忽略的模块是统计数据。在管理端首页放一列简单的统计卡片:总用户数、总小说数、总章节数、今日新增评论数。这些数据用后端聚合函数一次查出来,虽然逻辑简单,但在答辩演示时非常有视觉冲击力,也显得系统完整。
后台和前台的账号体系要区分开。比较简单的做法是在用户表里增加一个role字段,值为admin时允许访问管理接口,值为user时只能访问前台接口。后期如果有需要,可以扩展成完整的RBAC权限表,但对课设来说,角色字段加拦截器已经足够。
2.3 权限模型:普通用户与管理员怎么区隔
权限这块建议前后端都要做控制。前端做路由守卫生效快,但只能挡普通用户;真正的安全校验必须在后端,因为接口是可以被直接请求的。后端的实现方式很直接:写一个拦截器,拦截需要登录的路径,从请求头取出Token并解析,验证通过后把用户信息塞进请求上下文。
管理端接口要多一层校验,拦截器里同时检查当前用户的role是否为admin。放行策略上,登录、注册、书籍列表、章节内容这些基础接口允许匿名访问,其他写操作、个人信息操作必须登录后才能请求。我见过不少项目把权限做成"前端跳转判断一下完事",结果后台接口裸奔,这既是安全隐患,也是答辩时老师可能追问的点。
3. 表设计:小说、章节、书架、阅读进度如何关联
数据库设计决定了项目后面能走多远。实体关系不梳理清楚,写代码的时候就会陷入"这个字段到底挂在哪张表"的纠结中。小说平台的核心实体其实就四个:用户、小说、章节、分类,其余的书架、评论、阅读历史都属于关联信息。
3.1 核心数据表的关系总览
先画一个全局的关系图:分类和小说是一对多,一个分类下有多本书;小说和章节是一对多,一部小说下挂着许多章节;用户和书架是多对多,一个用户可以收藏多本书,一本书也可以被多个用户收藏;用户和评论是一对多,每个评论属于一个用户,同时也属于某一本小说;阅读记录每次产生一行,关联用户和章节。
用数据库表来表达的话,大概是这样一张关系清单:
| 实体 | 主要字段 | 关联方式 |
|---|---|---|
| user | id, username, password, role | 被书架、评论、阅读记录引用 |
| category | id, name, sort_order | 被novel引用 |
| novel | id, title, author, category_id, cover, status | 被chapter、comment、bookshelf引用 |
| chapter | id, novel_id, chapter_no, title, content | 外键指向novel |
| bookshelf | id, user_id, novel_id, add_time | 联合唯一约束 |
| comment | id, user_id, novel_id, content, create_time | 外键指向user和novel |
| reading_history | id, user_id, chapter_id, novel_id, read_time | 用于续读 |
这张表结构基本覆盖了核心需求。用户表可以加一列avatar用于显示头像,小说表可以加上click_count、word_count用于排序和展示,这些都是加分项。
3.2 章节表和小说表为什么必须拆开
很多同学初期设计会想省事,把小说的简介和所有章节内容塞进一个字段,或者单独建一个novel_content表,这种设计是有问题的。小说的基础信息(书名、作者、封面、简介)在每个列表页都要用到,而章节正文只在阅读时才加载。如果混合存储,列表页每次都要拖出巨大的文本字段,查询效率会很难看。
把章节独立成chapter表,每行存一章,字段里带上novel_id、章节号、标题、正文和创建时间,这样按小说查询章节列表时只需要查一个轻量字段集,读取正文时才加载content字段。配合索引,性能很稳定。
正文内容的字段类型也要注意。一章小说正常在几千到几万字之间,用VARCHAR(255)肯定不行,建议用TEXT或者LONGTEXT。TEXT最多可以存65535个字节,中文按三个字节算也就两万多字,很多一章几万字的书会不够,所以直接用LONGTEXT最省心。
3.3 字段设计的一些细节习惯
建表方面我习惯把每张表都加上create_time和update_time,用数据库时间戳自动填充。逻辑删除字段deleted设为tinyint默认0,这样业务数据不会因为误操作真的消失。状态字段status用tinyint,比如小说状态0表示连载、1表示完结,而不是用字符串,查询更快,代码里用常量定义就行。
举一个实际的建表SQL片段,小说表大致长这样:
CREATE TABLE `novel` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `title` varchar(128) NOT NULL COMMENT '书名', `author` varchar(64) DEFAULT NULL COMMENT '作者', `category_id` bigint(20) DEFAULT NULL COMMENT '分类ID', `cover` varchar(255) DEFAULT NULL COMMENT '封面地址', `intro` text COMMENT '简介', `status` tinyint(4) DEFAULT 0 COMMENT '0连载 1完结', `click_count` int(11) DEFAULT 0 COMMENT '点击量', `deleted` tinyint(1) DEFAULT 0, `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_category` (`category_id`), KEY `idx_status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='小说表';字段名统一用下划线风格,Java实体里映射成驼峰,MyBatis-Plus开启驼峰映射后两边能自动对齐,不用每个字段都写@TableField注解。utf8mb4编码必须用,否则生僻字和特殊字符会出现乱码问题,这个我踩过不止一次。
4. 后端实现:登录鉴权、章节分页与推荐排序的关键逻辑
后端看着接口多,实际核心逻辑就集中在几块。把这几个关键的实现思路理清楚,其他增删改查接口都是套模板。
4.1 JWT登录流程与拦截器配置
登录接口接收用户名和密码,查库比对通过后生成Token返回给前端。推荐使用JWT,把用户ID和角色放进Token载荷,之后每次请求都由后端解析验证,服务端不需要保存会话状态,适合前后端分离和后续扩展。
生成Token之后,前端把Token存到localStorage,在请求拦截器里统一塞进Authorization请求头。后端配一个拦截器,注册到WebMvcConfigurer里,设置需要拦截的路径。放行的路径包括登录注册和获取书籍、章节内容的接口,其余需要校验。
业务代码里拿到当前用户很关键。可以直接写一个工具类,从请求头解析出用户ID,然后通过ThreadLocal或者直接传入Service层。我建议在拦截器里完成解析后,用自定义参数解析器把当前用户注入到Controller方法参数,这样接口签名可以写成public Result favorite(@CurrentUser User user, Long novelId),代码非常干净。
4.2 章节列表分页与内容查询的组织方式
章节表数据量会随书籍数量快速增长,所以章节的查询一定要走分页。以某本小说的章节列表为例,接口参数带上novelId和页码,后端用MyBatis-Plus的Page对象查询,按chapter_no排序,回传总记录数、总页数和当前页数据。
阅读正文时,前端拿到当前章节ID和上一章下一章的关系。具体做法是,章节表只需要存chapter_no,上一章就是chapter_no减一,下一章加一,查询时按照novel_id and chapter_no定位,不需要额外存前后链接字段。
还要注意接口返回体要统一。我习惯用这种结构:
public class Result<T> { private Integer code; private String message; private T data; }所有接口返回这个统一信封,前端axios封装里对code做统一判断,遇到401时跳转登录页,避免每个页面都写一遍错误处理。
4.3 推荐榜排序的实现方式
推荐榜统计的是展示在一级页面的书籍列表,核心排序依据可以是点击量、更新时间、收藏数或综合权重。最直接的做法是SQL语句里做ORDER BY字段排序,比如首页的热门榜就是:
SELECT * FROM novel WHERE deleted = 0 AND status = 0 ORDER BY click_count DESC LIMIT 10;新书榜按create_time倒序,更新榜按update_time倒序,再结合分类条件筛选。如果想做综合推荐,可以用点击量和更新的加权分数,甚至引入浏览量、评论数等多个因子,在SQL里计算一个排序值。课设阶段建议先做稳定的单维排序,功能跑通后再考虑加权。
5. 前端落地:阅读器、书架交互与后台管理页面的组织思路
前端涉及大量页面,但掌握几个核心组织思路后,剩下的基本都是复制模板加修改字段。这里重点讲阅读器、书架和后台表格页这三块。
5.1 阅读器页面的交互细节
阅读器页面建议单独成一个路由,比如/reader/:chapterId。进入页面时根据路由参数请求章节内容,展示标题和正文。底部固定上一章、下一章按钮,目录通过抽屉组件展示。
阅读进度保存是阅读器体验里比较重要的能力。当前章节获取成功后,向后端保存一条阅读记录,字段包含userId、novelId、chapterId、readTime。回到书籍详情页时,接口返回该书是否存在于书架以及最近阅读章节,前端展示一个醒目的"继续阅读"按钮,点一下直接跳转到上次章节。
正文排版也要注意基础体验。字号建议默认16px或17px,行高在1.8以上,最大宽度控制在720px左右居中,两边留白。可以设置字号和背景色的切换按钮,如果想做成亮点,这个交互加进去是不少工作量但非常容易讲出来。
5.2 书架与阅读历史的状态同步
书架的数据结构可以设计成一张独立的bookshelf表,字段包括id、userId、novelId、addTime。加入书架和取消收藏,本质就是插入和删除一行记录。需要注意的是做唯一性约束,同一个用户对同一本书不能重复收藏,插入前先查一下是否存在,或者在表上加联合唯一索引。
前端书架页展示收藏列表,每本书带封面、书名、作者和最近更新时间。点击进入详情时,后端可以在查询详情接口里顺便返回一个isInShelf的布尔值,前端根据这个字段决定按钮显示"加入书架"还是"已在书架"。这样做的好处是,详情页不需要额外请求一次书架状态,接口设计更顺畅。
阅读历史可以单独做一个tab页,按readTime倒序展示最近读过的书。点进历史记录时直接跳转到对应章节,形成完整的"从书架到阅读再回到书架"的链路。整个体验闭环了,答辩时这套交互讲起来很加分。
5.3 管理端表格页的通用模式
管理端页面的模式比较统一:一个搜索栏、一个表格、一个分页条,加上新增和编辑共用的弹窗表单。Vue加Element UI可以让这种页面开发得非常快。
拿小说管理来说,搜索条件可以放书名、分类、状态三项,搜索后重新请求列表接口。表格列展示ID、书名、作者、分类、状态、点击量、创建时间,操作列放编辑和删除按钮。新增和编辑共用一个Form组件,通过编辑传入的row对象来初始化表单数据,提交时按有无ID判断走新增还是更新接口。
所有管理接口都需要带上管理员的身份。前端管理路由配置meta字段标记需要管理员权限,路由守卫里检查当前登录用户的角色。如果角色不是admin,直接重定向到前台首页并把提示信息弹出来。
6. 从本地到答辩演示:启动配置、造数技巧与高频踩坑点
代码写完之后,真正的挑战是把项目在一台干净的电脑上跑起来,并且演示过程中不出意外。这一章讲一些实用细节,都是我实际见过和踩过的坑。
6.1 本地启动的配置顺序
拿到项目后第一步不是直接跑,而是先检查配置。后端里最关键的是application.yml,数据库连接部分要确认三个点:地址是否正确、账号密码是否正确、时区和编码是否配置。推荐写法是:
spring: datasource: url: jdbc:mysql://localhost:3306/novel_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 123456如果配置漏了serverTimezone,MySQL 8的驱动连接时很容易报时区异常。然后再确认SQL脚本有没有执行,数据库和表是否创建成功。
启动顺序上,建议先启动数据库,再启动后端,看到端口启动日志后再启动前端。前端启动前先npm install安装依赖,如果网络慢可以用国内镜像。后端用Jar包直接跑很省事,也可以从IDE里启动,演示的时候我推荐命令行启动,因为窗口清晰,出问题容易定位。
6.2 造测试数据的方法,让演示不尴尬
很多同学把项目做完之后发现页面空荡荡的,演示效果很差。原因很简单:造数据太敷衍。造测试数据必须让系统看起来像一个真实运营的小说平台。
基础数据量上,分类至少有七八个,小说至少几十本覆盖每个分类,热门小说章节至少几十章,越长的正文越有阅读感。人工造这么多数据累死人,直接写一段Java的批量初始化脚本或者SQL存储过程,循环插入即可。封面图可以用图片占位服务生成,把URL填进cover字段,前端加载起来也快。
段子和内容可以结合经典名著片段或者自己写的小段落,重点是格式正确:每章标题清晰、内容分段、字数足够。有一个小技巧,造数据时把点击量和收藏数也随机生成,随机范围拉开差距,这样排序榜才有区分度,不会所有书都一样。
6.3 演示时最容易翻车的几个地方
先说跨域。前后端分离项目,如果前端跑在8080端口、后端跑在8081端口,浏览器默认会拦截跨域请求。解决方式是在后端配置跨域过滤器,允许来自本地的请求带着Authorization头访问。如果不配置,前端页面会出现请求发出去但一直挂在pending的状态,演示时极其尴尬。
再说刷新404问题。Vue Router如果用history模式,直接访问某个子路由刷新时,静态服务器不知道如何回退到index.html,就会返回404。解决办法有两种:一是改用hash模式,地址栏有#号但刷新不挂;二是部署到Nginx时加一段try_files配置。对课设演示来说,为了求稳,我建议直接用hash模式,省掉不必要的坑。
最后是端口占用问题。演示前花十分钟检查一下8080、8081、3306这三个端口是否被占用,Windows下用netstat查,确认没有冲突再开始演示。再检查一遍数据库连接密码,很多同学换电脑后忘了改密码,导致启动报连接异常。这些都是小问题,但演示现场才暴露那就是事故了。
我在实际带项目的时候还有个习惯,会提前准备5到6个"答辩候选人必问问题"的答案,比如为什么选这个架构、数据库为什么这么设计、权限是怎么实现的、如果用户量增大怎么优化。把这些问题的答案对着代码过一遍,答辩时不仅不慌,还能主动把亮点抛出来。整体上,这个选题很适合用来掌握前后端分离项目的完整开发流程,做完之后你会对表设计、接口组织、组件通信和部署这些核心环节都有实打实的体感。