尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Spring Boot实战:在线小说阅读平台开发与部署全解析

Spring Boot实战:在线小说阅读平台开发与部署全解析 简介这是一套基于SpringBoot与Vue的全栈在线小说阅读平台源码面向Java Web初学者及毕业设计学生解决小说类Web系统从需求分析、前后端分离开发到数据库设计的完整实践问题。资源包为ZIP格式大小18.83MB包含后端Java工程含SpringBoot主模块、MyBatis-Plus数据层、RESTful接口、Vue前端项目含用户中心、小说分类、章节阅读、搜索功能等页面、MySQL 5.7建表SQL及系统设计文档含摘要、目录、绪论、技术选型、数据库设计等章节覆盖用户信息管理、素材资源组织等核心模块。目前已有61人学习下载读者可直接导入IDEA/Eclipse与Vue CLI运行调试快速掌握SpringBootVue协同开发流程、MySQL关系建模方法及Web阅读类系统的典型业务逻辑实现路径。 做在线小说阅读平台这个项目对我来说算是一次比较完整的Spring Boot实战。整个项目从需求梳理、表结构设计到核心业务实现再到部署上线踩了不少坑也积累了一些值得记录的经验。这篇文章就围绕“基于Spring Boot的在线小说阅读平台”这个主题把项目的设计思路、核心功能实现、实操过程以及常见问题排查做个系统梳理希望能给正在做类似项目或者想拿Spring Boot练手的同学一些参考。先说清楚这个项目到底是干什么的。它的定位是一个Web端的小说在线阅读平台用户可以在上面浏览书籍、按分类筛选、搜索小说、阅读章节、记录阅读进度、加入书架管理员则可以通过后台对书籍和章节进行管理。技术栈以Java Spring Boot为核心配合MySQL存储数据、Redis做缓存前端用Thymeleaf模板引擎渲染页面。整个项目非常适合用来理解Spring Boot的项目结构、分层思想、数据库设计和常用中间件整合。1. 项目整体设计思路与方案选型1.1 为什么选择Spring Boot作为基础框架说到Java Web开发Spring Boot基本已经是事实上的标准了。它解决掉了我以前用SSHStruts Spring Hibernate或者SSMSpring SpringMVC MyBatis搭建项目时最头疼的配置问题。以前搭一个项目要先配置web.xml、spring-mvc.xml、applicationContext.xml一堆XML文件哪个标签写错了整个项目都起不来。Spring Boot通过自动配置和约定大于配置的方式把大量繁琐的配置工作省掉了我只需要引入相关场景的starter依赖再在application.yml里写少量配置项目就能跑起来。那Spring Boot版本怎么选这其实是个很实际的问题。Spring Boot 3.x相比2.x是一次大的版本跃迁它基于JDK 17采用了Jakarta EE命名空间对Servlet、JPA等依赖包名做了调整。如果项目是全新的、团队也愿意升级JDK那直接用3.x没有问题。但如果考虑到当前网络上大量的教程资料还停留在2.x或者项目需要兼容一些老的依赖库Spring Boot 2.7.x仍然是一个非常稳妥的选择。我这个项目用的是Spring Boot 2.7.6搭配JDK 8理由很简单部署环境是CentOS 7.9上面已经装好了JDK 8而且MySQL连接、MyBatis-Plus、Thymeleaf这些依赖在2.x下兼容性最好不需要额外折腾。提示如果你刚开始学Spring Boot不建议一上来就用最新版本。版本太高很多老教程里的依赖坐标和配置方式都不一样排查问题会非常痛苦。选一个成熟稳定、社区资料多的版本学习成本会低很多。1.2 系统核心模块与技术栈选择在线小说阅读平台的功能模块划分差不多决定了整个项目的骨架。前端用户端是最主要的包括用户注册登录、首页书籍展示、分类浏览、关键词搜索、小说详情页、章节列表、在线阅读器、书架管理、阅读历史记录等功能。管理后台则负责书籍信息管理、章节内容录入、分类维护、用户管理等。我最终选定的技术栈如下技术组件选型方案选型理由后端框架Spring Boot 2.7.6成熟稳定社区资料全自动配置省心Java版本JDK 8部署环境兼容性好大部分服务器都是8ORM框架MyBatis-Plus单表操作不用写SQL分页插件好用数据库MySQL 8.0主流、免费、稳定缓存Redis 5.x缓存书籍详情和章节列表减轻数据库压力模板引擎Thymeleaf后端渲染页面适合中小型项目安全认证Spring Security JWT无状态认证适合前后端分离的接口设计这里说一下为什么用MyBatis-Plus而不是原生MyBatis。MyBatis-Plus是在MyBatis基础上的增强工具它提供了BaseMapper这个通用接口像单表的增删改查、分页查询这些最常见的操作我不用写SQL就能完成。实际开发中大概80%的数据库操作都是单表操作MyBatis-Plus可以直接搞定剩下20%的多表联查才需要手写XML。对于小说阅读平台这种业务逻辑不复杂、表关系相对清晰的系统来说用MyBatis-Plus能明显提升开发效率。1.3 数据库设计与表结构规划数据库设计是整个项目的地基这个环节没做好后面写业务代码会越写越别扭。在线小说阅读平台的核心数据模型其实并不复杂就是用户、书籍、章节、分类、书架、阅读记录这六张核心表。用户表user主要记录用户的基本信息包括用户名、加密后的密码、昵称、头像地址、状态等。这里有个关键点密码绝对不能明文存储。我用的是Spring Security自带的BCryptPasswordEncoder它对同一个密码生成的哈希结果每次都不同内置了盐值处理安全性比MD5加盐还要好。书籍表book是系统的主数据表字段包括书名、作者、封面图URL、简介、分类ID、字数、状态、点击量等。我在设计这本书表的时候特意加了一个status字段用来标记书籍是连载中还是已完结这样在分类页和详情页可以很方便地进行筛选和展示。章节表book_chapter记录每本书的章节信息字段主要包含书籍ID、章节序号、章节标题、章节内容、字数、创建时间等。这里要注意给book_id和chapter_no加上索引因为阅读器在跳转上一章下一章的时候需要频繁按这两个字段查询。书架表bookshelf和阅读记录表reading_record的核心逻辑是类似的都是用户和书籍的多对多关系只是关注点不同。书架表记录用户收藏了哪些书阅读记录表除了记录用户看过哪本书还会记录最新的章节ID和阅读进度百分比。这类关联表的索引策略是重点必须给user_id建索引因为查询书架和阅读历史时用户的ID就是查询条件。2. 核心功能模块实现细节2.1 用户注册登录与安全认证用户模块是整个平台最基础的部分我一开始用的是Session方案基于Spring Security的表单登录。但后来发现前后端在交互的时候Session的维护还是有点麻烦比如跨域请求带上Cookie、Session的超时管理等。于是我把认证方式改成了JWTJSON Web Token方案。JWT的核心思想是服务器不保存用户的登录状态而是把用户ID、过期时间等信息经过签名后生成一个token字符串返回给前端前端在后续请求中通过Authorization请求头携带这个token后端通过解析token来判断用户身份。JWT的优势在于无状态方便水平扩展服务端不需要存储Session负载均衡的时候也不需要处理Session共享问题。但JWT也有一个坑就是token一旦签发在过期之前是没办法主动失效的。所以我在实际项目中给token设置了有效期一般24小时过期同时提供了刷新token的机制尽量降低token泄露带来的风险。Spring Security JWT的组合实现起来也不复杂。核心是编写一个JwtAuthenticationFilter继承OncePerRequestFilter在每个请求进来的时候从请求头中解析token如果token有效就把用户信息放入SecurityContext中。配置上主要是在Spring Security的过滤链中注册这个过滤器并放行注册登录接口、书籍浏览接口等无需登录就能访问的路径。2.2 小说书籍管理模块书籍管理模块是内容运营的核心。在前台用户端最常遇到的需求就是分类浏览和书籍列表分页展示。我用MyBatis-Plus的分页插件来实现分页查询这个插件用起来非常方便只需要在配置类里注册一个MybatisPlusInterceptor然后向其中添加PaginationInnerInterceptor之后调用分页查询方法时传入当前页码和每页大小参数即可。书籍列表展示还有一个重要需求是排序。我在book表中设计了多个排序字段比如点击量、收藏量、更新时间前台可以通过参数灵活指定排序方式。这里要提醒一下不同排序方式要对应到不同索引否则数据量大的时候查询会比较慢。我为了偷懒前期只在点击量字段上建了索引后面发现按更新时间排序的时候查询延迟明显增加又补了一个联合索引才解决。书籍详情页展示的是单本书的完整信息包括封面、作者、分类、简介、字数、状态等同时还要展示最新的章节信息和本书的收藏数量、点击数量。这些数据如果每次请求都去数据库查一旦并发量上来数据库就会成为瓶颈。我是用Redis做了一层缓存把书籍的详情信息以JSON格式缓存起来Key的设计是book:detail:{bookId}。查询的时候先去Redis取取不到再去数据库查查完回填Redis。这里有个缓存一致性要考虑运营后台修改了书籍信息之后如果不清理缓存前台用户看到的还是旧数据。我的做法是在后台修改书籍信息的service方法里主动删除对应的Redis缓存这样下一次请求就会重新从数据库加载并回填缓存。2.3 在线阅读器与阅读进度跟踪在线阅读器是小说平台的核心功能体验好不好直接决定用户愿不愿意留下来。我做的阅读器主要提供这样几个能力按章节加载正文内容、上一章下一章跳转、字号调整、背景颜色换肤以及阅读进度的自动记录。章节内容的加载最笨也最简单的方式就是用户每翻一章就请求一次后台后台从数据库查出本章内容返回。但这样做有两个问题一是每次页面刷新都需要等待网络请求阅读体验不好二是高并发下数据库压力很大。我的方案是在前端做章节预加载读者正在阅读第N章的时候前端悄悄把第N1章的接口请求过来存到浏览器的变量里点击下一章的时候直接渲染体验会顺畅很多。阅读进度的记录我是这样设计的用户阅读某个章节超过5秒后前端向后端发送一条请求带上书籍ID、章节ID和阅读百分比。后端把这条记录写入阅读历史表同时更新阅读进度。这里有个小细节用户在目录页或者详情页点击“继续阅读”按钮时前端可以直接使用这个记录跳转到对应章节并且把滚动条定位到上次阅读的位置。定位的实现思路是前端记录上次阅读位置距离页面顶部的像素值保存在localStorage里重新进入时通过window.scrollTo方法恢复位置。2.4 搜索与分类筛选搜素功能是另一个高频使用的入口。我的第一版实现用的就是MySQL的LIKE %keyword%查询因为数据量不大的情况下这个方案完全够用而且实现最简单。SELECT * FROM book WHERE book_name LIKE CONCAT(%, #{keyword}, %)这样用户输入关键词就能模糊匹配书名。但这种方案有两个比较明显的短板一是%keyword%这种两边都用百分号的模糊查询无法使用索引全表扫描的性能随着数据量增长会快速下降二是做不到分词用户搜索“斗破王者”这种连续十几个字的关键词如果书名是“斗破苍穹”、“王者荣耀”就一条都匹配不出来。所以在项目做到后期的时候我把全文搜索这部分单独拆了出来引入了Elasticsearch。ES通过分词器把文本切分成词条并建立倒排索引搜索的时候按词条匹配既能提高查询性能也能实现更智能的匹配。不过引入ES也意味着架构复杂度上升服务部署多了一个节点数据同步也需要处理。我的建议是如果你的项目数据量不大、并发量不高先用MySQL的LIKE查询就够了等数据量真的涨上来了再引入ES不要一开始就上重武器。3. 实操过程从零搭建并跑通项目3.1 项目骨架搭建与依赖配置创建项目我用的工具是IntelliJ IDEA在Spring Initializr页面选择好项目类型是Maven、语言是Java、Spring Boot版本是2.7.6然后勾选需要的依赖Spring Web、Thymeleaf、Spring Security、MyBatis-Plus就不要在初始化器里选了后面手动加到pom.xml里。项目创建好之后我先做了两件很重要的事情。第一件事是确认pom.xml中依赖的版本。mybatis-plus-spring-boot3-starter和mybatis-plus-boot-starter这两个依赖的区分要特别注意如果是Spring Boot 3.x必须引入mybatis-plus-spring-boot3-starter如果是Spring Boot 2.x引入mybatis-plus-boot-starter。第二件事是配置application.yml把数据源信息、Redis信息、Mybatis-Plus的配置都写在里面。下面是我使用的关键依赖配置可以直接参考parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.6/version relativePath/ /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-thymeleaf/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-security/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.31/version /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-api/artifactId version0.11.5/version /dependency /dependencies注意JWT相关的jjwt依赖如果只用jjwt-api运行时会报ClassNotFoundException因为jjwt-api只是接口定义实现逻辑在jjwt-impl里JSON编解码需要jjwt-jackson。这三个依赖需要同时引入我当时就是因为只加了jjwt-api启动后第一次调用登录接口就报错了。3.2 关键业务代码实现示例以小说章节列表查询为例展示一下Controller、Service、Mapper三层是怎么协作的。Controller层负责接收前端请求参数并返回视图或JSON数据Controller RequestMapping(/book) public class BookController { Autowired private BookService bookService; GetMapping(/{bookId}/chapters) public String chapterList(PathVariable Long bookId, Model model) { // 查询书籍基本信息 Book book bookService.getBookDetail(bookId); // 查询章节列表按章节序号升序 ListBookChapter chapterList bookService.listChapters(bookId); model.addAttribute(book, book); model.addAttribute(chapterList, chapterList); return chapter_list; } }Service层负责业务逻辑处理比如缓存的处理、数据组装Service public class BookServiceImpl extends ServiceImplBookMapper, Book implements BookService { Autowired private StringRedisTemplate redisTemplate; private static final String CACHE_KEY_PREFIX book:detail:; Override public Book getBookDetail(Long bookId) { // 先从Redis缓存中查询 String cacheKey CACHE_KEY_PREFIX bookId; String cachedJson redisTemplate.opsForValue().get(cacheKey); if (cachedJson ! null) { return JSON.parseObject(cachedJson, Book.class); } // 缓存不存在查询数据库 Book book this.getById(bookId); if (book ! null) { redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(book), 30, TimeUnit.MINUTES); } return book; } }Mapper层直接用MyBatis-Plus提供的BaseMapper接口就可以了不需要额外写方法。像selectById、selectList这些方法都是封装好的分页查询稍微特殊一点需要传入一个Page对象Mapper public interface BookMapper extends BaseMapperBook { // 如果不需要多表联查这里可以什么都不用写 }3.3 数据初始化与本地调试数据库建好之后接下来要做的事情就是初始化数据。我写了一个data.sql文件里面包含了一段建表语句和测试数据用MySQL的source命令可以直接执行。测试数据不用太多够调试功能用就行我准备了大概20本不同类型的书籍比如玄幻、都市、历史、科技等分类各放了几本。每本书又手动加了5到10个章节保证阅读器功能有足够的数据可以测试。本地调试过程中我建议把Spring Boot的日志级别调整一下。默认的INFO日志看不到SQL语句排查问题时会有很多不便。在application.yml里加上这样一段配置logging: level: com.example.novel.mapper: debug这样MyBatis在执行SQL时会把完整的SQL语句和参数值打印到控制台哪些字段没查到、哪条SQL写错了一眼就能看出来。这个配置是我做全项目开发时期必开的一个调试手段。还有一个调试时很实用的技巧在启动类写一个CommandLineRunner每次启动时自动往Redis里写入一些预热数据。这样我调试阅读器功能时不需要手动去触发缓存回填的流程省了不少时间。等正式上线的时候把这段代码去掉或者加上开关控制就行。4. 常见问题与排查技巧实录4.1 数据库连接与驱动问题数据库相关的报错是最早遇到的也是新手最容易卡住的地方。最典型的一个错误是Access denied for user rootlocalhost (using password: YES)。这个错误的意思很明确就是用户名或者密码不对。但很多情况下我在MySQL的命令行里用同样的用户名密码是可以登录的为什么项目里连不上后来发现问题出在MySQL 8.0默认的认证插件是caching_sha2_password而某些版本的连接驱动不支持这种认证方式。解决办法是把连接URL加上参数spring: datasource: url: jdbc:mysql://localhost:3306/novel_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrueallowPublicKeyRetrievaltrue这个参数很关键MySQL 8.0的caching_sha2_password插件在首次认证时需要通过RSA公钥加密密码如果连接URL里不加这个参数会报Public Key Retrieval is not allowed的错误。serverTimezoneAsia/Shanghai也是必须要加的不加的话如果数据库服务器和Java应用的时区不一致查询带日期时间类型的数据时会发现时间差了好几个小时。这个问题的根源在于MySQL的驱动默认取的是JVM的时区而数据库用的是操作系统时区两边没对齐就容易乱。4.2 Redis缓存引发的坑Redis缓存这块我踩过最深的坑就是缓存穿透和缓存雪崩。先说缓存穿透。用户请求一个不存在的书籍ID比如book:detail:999999Redis里没有数据库里也没有请求就直接打到数据库上了。如果是恶意攻击专门挑不存在的ID大量请求数据库分分钟就被打挂了。解决办法很简单就是空值缓存当数据库没有查到数据时也在Redis里放一个空值并设置较短的过期时间比如30秒。这样后续同样的请求就会命中空值缓存不会打到数据库。缓存雪崩指的是大量缓存同时过期导致大量请求全部落到数据库。我的做法是给缓存设置过期时间的时候加上一个随机值比如基础过期时间30分钟再加上0到5分钟的随机数这样就避免了一大片缓存同一时刻集体失效的情况。缓存击穿是指某个热点的缓存过期瞬间大量并发请求同时打到数据库。我在项目里用了Redis的setIfAbsent方法实现分布式锁只有一个线程能去数据库查询其他线程短暂等待后从缓存里取数据。这里要注意锁的过期时间一定要设置得比实际查询时间长否则锁提前释放了后面等着拿锁的线程又会重复查询数据库。我平时习惯把锁超时时间设为5秒如果查询超过5秒说明SQL本身就有问题需要先优化SQL。4.3 部署环境中的实际问题项目部署到Linux服务器上还有一批和本地开发环境不太一样的问题。最常见的是端口占用。Spring Boot默认端口是8080如果服务器上已经跑了其他Java应用新手经常会遇到Port 8080 was already in use的报错。这个问题的解决很简单要么换一个端口要么把占用端口的进程杀掉。但是这里我要说一个更稳妥的做法部署线上服务的时候在启动命令里显式指定端口和JVM参数避免环境不一致nohup java -Xms512m -Xmx1024m \ -Dserver.port8080 \ -Dspring.profiles.activeprod \ -Dfile.encodingUTF-8 \ -jar novel-platform.jar \ logs/novel.log 21 -Xms和-Xmx是JVM堆内存的初始值和最大值。Xms设为512MBXmx设为1024MB是因为我的服务器内存只有2GB要给操作系统和MySQL留出余量。如果这台服务器上同时跑着MySQL和RedisJVM堆内存设得太大操作系统内存吃紧反而会导致性能下降甚至OOM。说到OOM我再提一个我真实遇到过的问题。有一次我写了一个批量导入章节内容的方法一次性把一本书几千个章节全部加载到了List集合里然后又做了很多字符串拼接和JSON转换操作结果直接抛出了java.lang.OutOfMemoryError: Java heap space。后来我把批量导入改成流式处理每次只处理100章同时用StringBuilder替代大量字符串拼接才把这个问题解决。在开发时我也会给IDEA的JVM加大内存但更重要的是在代码里避免一次性装载过多数据。4.4 常见问题速查表我把这个项目从开发到上线过程中遇到的高频问题整理成了一个表格方便后续排查时快速对照。问题现象可能原因解决办法Access denied for user数据库密码错误或MySQL 8.0认证插件问题核对密码连接URL加allowPublicKeyRetrievaltruePublic Key Retrieval is not allowed缺少allowPublicKeyRetrieval参数连接URL中补充该参数日期时间差8小时时区配置不一致连接URL加serverTimezoneAsia/Shanghai中文乱码字符集配置不一致数据库连接URL加useUnicodetruecharacterEncodingutf8端口被占用服务器端口冲突或上次启动未关闭更换server.port或者查杀占用进程Redis连接超时Redis未启动、密码错误、防火墙拦截确认Redis进程和配置放行6379端口缓存穿透导致数据库压力大频繁查不存在的Key添加空值缓存设置短过期时间大批量数据导入OOM一次加载数据过多改为分批处理控制单批数据量Spring Security登录后接口返回403未放行对应接口路径在SecurityConfig中放行公开接口4.5 内容安全与版权合规做在线小说阅读平台还有一件非常重要但容易被忽略的事情内容安全和版权合规。我在开发时同步做了一套内容审核链路新章节发布时先调用文本审核接口识别敏感词、违规关键词和广告内容审核通过后状态才变为正式发布。另外章节正文中如果包含外链或者明显的引流信息系统会自动拦截并提示运营人员人工复核。书籍的版权信息也会记录在后台每本书上线前都要填写授权类型和版权方信息。这个设计更多是从实际业务合规的角度考虑的很多做小说聚合类网站的同学前期往往不重视这个后面内容做大了反而会被版权问题拖累。5. 在线小说阅读平台的扩展方向做到这一步项目的基本功能已经完整可以作为一个可运行的毕业设计或者个人作品集项目使用。但如果想把项目做得更有深度还可以在几个方向上进行扩展。第一个是接口安全层面。当前项目里JWT已经解决了一部分认证问题但接口层面还缺少防刷和限流的机制。可以引入Redis的分布式限流组件对单个用户在单位时间内的请求次数做限制防止爬虫和恶意攻击。同时在网关入口统一做参数校验和幂等校验避免重复提交。第二个是前端工程化层面。当前是Thymeleaf服务端渲染页面是传统的多页面跳转模式体验还行但谈不上现代。如果想让前端体验上一个台阶可以改为前后端分离架构前端用Vue 3 Element Plus后端只提供JSON接口。Spring Boot对RESTful API的支持非常友好Controller层返回统一封装的Result对象前端通过Axios调用。这个改动虽然工作量不小但能体现更完整的前后端分离思想在面试或者展示时是很好的加分项。第三个是阅读器的交互体验优化。当前阅读器支持基础的章节切换、字号调整和进度记录如果要做到类似全本小说阅读器的体验还需要加入字体切换、段落间距调整、夜间模式、自动翻页、离线缓存等功能。这部分需要前后端协同可以单独作为一个模块来迭代。第四个是监控与运维层面。项目在本地跑通是一回事部署到云服务器上稳定运行又是另一回事。可以引入Spring Boot Actuator暴露应用的健康检查端点配合Prometheus Grafana做应用监控和指标可视化。内存、CPU、接口耗时、请求量这些指标都能直观地在监控面板上看到出现问题也能第一时间定位。最后分享一点我自己的实操感受这个项目做下来最深的体会是明确边界非常重要。在线小说阅读平台看起来功能很多但核心就是“用户 - 书籍 - 章节”这条业务主线所有模块都是围绕这条主线延伸出来的。从简单的用户登录、书籍列表到复杂的阅读器、搜索、缓存优化每一步我都在做之前先问自己“为什么需要这个功能”“有没有更简单的实现方式”。这种思考习惯帮我少走了很多弯路。另外一个很实的建议是开发过程中一定要养成随时验证的习惯。写完一个接口就用Postman或者Swagger调用一次确认返回结果是否符合预期页面渲染是否正确。不要攒到最后一口气全部验证那样出了问题都不知道是哪个环节引入的。毕竟项目最终能不能跑起来跑得好不好都是靠代码一行一行堆出来的。本文还有配套的精品资源点击获取
返回列表