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

资讯详情

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

Spring Boot + Redis 在线小说阅读平台:从数据建模到缓存实战

Spring Boot + Redis 在线小说阅读平台:从数据建模到缓存实战 简介在Java后端开发中CRUD只是基本功真正拉开差距的是对缓存、索引、并发等工程问题的理解。在线小说阅读平台作为典型的业务系统涵盖了用户认证、书籍管理、章节阅读、书架同步等完整链路是练手和面试准备的优质题材。从技术原理上看Spring Boot 2.7搭配MyBatis Plus能快速实现业务层而Redis则承担查询缓存、排行榜、分布式锁等多重角色解决高并发场景下的性能瓶颈。本文从数据模型设计出发拆解书籍与章节的强父子关系、阅读进度的高频写入、书架的一致性等核心难点并针对缓存穿透、击穿、雪崩给出可落地的兜底方案最终通过Docker Compose与Nginx完成部署。无论你是中级开发者还是面试备战者理解这套架构演进与实战细节都比堆砌多个Demo更有价值。 想拿Spring Boot练手很多人第一反应是做商城、博客或后台管理但今天我想聊聊一个被严重低估的方向在线小说阅读平台。这个题材非常适合中级Java开发者拿来深入学习——业务链路完整从用户登录、书城浏览、书籍详情、章节阅读到书架管理每一步都有真实的技术难点。用Spring Boot MyBatis Plus Redis这套组合来实现前后端分离既能覆盖常见的CRUD又能把缓存、分页、搜索、权限这些服务端重点技术全部过一遍。如果你正在准备Java后端面试想找一个能写进简历的项目把一套在线小说阅读平台的源码完整吃透比堆砌十个Demo都有用。1. 小说阅读平台的业务全貌从用户打开首页到追更链路里藏着哪些核心模块1.1 一条完整的用户主链路做项目之前我习惯先把用户走完一遍全流程画出一条主链路再顺着这条链路找功能点。用户打开在线小说阅读平台后第一件事是登录。登录之后进入书城首页看到的是分类推荐、热门榜单、新书上架。用户点击一本书进入书籍详情页看到书籍简介、作者、字数、状态、章节目录。点击章节进入阅读器翻页、调整字号、做书签、调目录。阅读完几章后用户可能把这本书加入书架方便下次继续阅读。下次打开App或网页从书架进入直接跳到上次读到的章节。读的过程中可以评论、打分、收藏。如果平台有付费模式还有VIP章节、充值、订阅这套逻辑。这就是在线小说阅读平台的主链路。跟着这条链路走一遍你会发现它天然被切成了五大模块用户模块、书城模块、书籍模块、阅读模块、互动模块。1.2 小说平台区别于普通CMS的特殊业务规则我们平时写管理系统本质是CRUD加上权限控制。但小说阅读平台有几个特殊的业务特征决定了它不能简单当作CMS来做这些特征也正是面试官喜欢深挖的点。第一个特征是书籍与章节的强父子关系。一本书对应数百甚至上千章节书籍基本信息是低频更新章节内容则是高频写入。如果设计成一张大表把所有信息塞在一起查询性能一定会恶化。所以书籍主信息、章节目录、章节正文这三层必须拆开设计。第二个特征是阅读进度跟踪。用户读了第120章关掉页面下次进来必须直接跳回第120章。这个最后一读位置看似简单但它对写入频率的要求很高——用户在阅读器每翻一章都要记录一次。这种高频率小写入如果把压力全部打在MySQL上数据库会很难受必须用缓存层做削峰。第三个特征是书籍的双状态管理。小说有连载和完结两种状态连载中的书会有章节更新完结的书则固定不变。这个状态影响列表筛选、推荐策略、WebSocket推送等很多场景。连载书在更新章节时缓存怎么失效、搜索索引怎么同步都是有讲究的。第四个特征是书架的数据一致性。用户书架里的书排序、最后阅读时间、未读章节数这些数据要在多端保持一致又不能每次请求都查数据库。这四个特征决定了在线小说阅读平台的技术方案和普通CRUD项目有明显差异。你把这个差异讲清楚面试的时候就能把项目讲得有深度。2. 技术选型与工程结构Spring Boot版本、ORM、缓存组件怎么定下来2.1 技术栈对比沿用Spring Boot 2.7还是升级3.x很多源码项目一上来就用最新版本但我个人建议在线小说阅读平台这类业务系统优先选择Spring Boot 2.7.x而不是3.x原因有两点。第一是生态兼容性。Spring Boot 3.x强制要求JDK 17及以上而很多国内公司的生产环境还在用JDK 8或者刚迁到JDK 11。如果你写的源码要给别人拿去参考或者想要部署到便宜的云服务器上2.7.x兼容JDK 8/11适配性广得多。第二是第三方库兼容问题。MyBatis Plus、一些老的加密组件、文件处理库在Spring Boot 3.x下都需要升级版本甚至改变用法。比如javax.servlet要改成jakarta.servlet这个改动虽然不大但对新手来说就是一道坎。如果你不是特别在意Java 17的新特性老老实实选Spring Boot 2.7.14这是2.x系列的最后一个稳定版本社区沉淀最充分。2.2 ORM选型为什么是MyBatis Plus而不是JPA持久层框架我选了MyBatis Plus3.5.3版本。理由很简单国内Java项目的通用语言。MyBatis Plus在MyBatis基础上提供了单表CRUD的封装内置分页插件、乐观锁插件、逻辑删除开箱即用。对于小说平台这种表结构相对清晰的项目90%的SQL都可以靠BaseMapper解决剩下的复杂查询写XML。有人会纠结JPA的自动建表能力。我的经验是业务系统建表用脚本管理更可控让框架自动建表在开发期省事上线后反而容易出幺蛾子。MyBatis Plus在配置里把auto-renames、驼峰映射处理好用起来非常顺手。2.3 缓存组件Redis承载的不只是缓存还有排行榜和分布式锁Redis在在线小说阅读平台里不只是缓存这么简单。它承担了三类职责。第一类是数据查询缓存。书籍详情、章节目录、章节正文这些高频读的数据缓存到Redis减少数据库压力。第二类是排行榜。热门榜、新书榜用Redis的ZSET实现每日定时任务统计点击量、收藏量写入ZSET接口查询直接取Top N。第三类是分布式锁。缓存重建、定时任务执行等场景需要加锁Redis的SETNX是个简单可靠的方案。这几类用法在简历上都能写也都能在面试时候展开。比我见过很多项目只在Redis里放了个验证码要有说服力得多。2.4 工程目录结构按业务域分包而不是按技术层分包项目结构我推荐按业务域划分而不是传统的controller/service/mapper三层平铺。小说平台的核心业务域有五个用户、书城、书籍、阅读、书架。com.novel.platform ├── common // 通用工具、常量、异常处理 ├── config // 配置类包括Redis、MyBatisPlus、Interceptor ├── security // JWT认证、登录拦截、权限注解 ├── module │ ├── user // 用户注册登录 │ ├── book // 书籍、章节、分类 │ ├── shelf // 书架 │ ├── search // 搜索 │ └── recommend // 榜单、推荐 └── job // 定时任务模块每个业务域内部再分controller、service、mapper、entity、dto。这种结构的最大好处是当你要找书架的某一处逻辑时直接进shelf包不大需要在整个项目里翻来翻去自己维护起来也舒服。3. 核心模块实现拆解阅读器、书架、搜索的落地代码逻辑3.1 阅读器模块章节内容接口的设计阅读器是整个平台最核心的页面。它需要的数据有三个章节正文、上一章/下一章ID、当前章节的目录位置。这三个数据如果分开查接口每次翻章都会发起三次请求很浪费。我的做法是合并成一个接口返回。GetMapping(/chapter/{chapterId}/content) public ResultChapterContentVO getChapterContent(PathVariable Long chapterId) { // 1. 查章节信息 BookChapter chapter bookChapterService.getById(chapterId); if (chapter null) { return Result.fail(章节不存在); } // 2. 查章节正文带缓存 String content chapterContentService.getContent(chapterId); // 3. 查上下章ID Long prevId bookChapterService.getPrevChapterId(chapter.getBookId(), chapter.getSort()); Long nextId bookChapterService.getNextChapterId(chapter.getBookId(), chapter.getSort()); // 4. 组装返回 ChapterContentVO vo new ChapterContentVO(); vo.setChapterId(chapterId); vo.setContent(content); vo.setPrevChapterId(prevId); vo.setNextChapterId(nextId); return Result.success(vo); }上下章ID这里有一个细节不要用chapterId - 1和chapterId 1。因为章节有可能被删除ID不一定是连续的。正确做法是查询这本书里比当前章节排序值小的最大值、比当前排序值大的最小值。排序字段sort才是业务上可靠的顺序依据。阅读进度上报接口我设计成一个独立的异步接口。用户每次进入章节或滑动翻页时调用后端把userId bookId chapterId写入Redis的Hash结构再通过定时任务每5分钟批量刷入MySQL。PostMapping(/progress/report) public ResultVoid reportProgress(RequestBody ProgressReportDTO dto) { // key: user:progress:{userId}, field: {bookId}, value: {chapterId} stringRedisTemplate.opsForHash().put( user:progress: dto.getUserId(), String.valueOf(dto.getBookId()), String.valueOf(dto.getChapterId()) ); return Result.success(); }为什么不用Redis的String类型直接存因为用户可能有几百本书的阅读进度用Hash结构一个key就能覆盖一个用户所有书的进度管理起来方便也方便定时任务批量扫描。3.2 书架模块最近阅读与收藏夹的同步策略书架功能要展示三样信息书的基本信息、最后阅读章节、最后阅读时间。这三样数据分别存在MySQL的user_bookshelf表、Redis的阅读进度Hash、书籍基本信息表中。书架列表接口的逻辑GetMapping(/shelf/list) public ResultListShelfItemVO getShelfList(RequestParam Long userId) { // 1. 从MySQL查书架记录按更新时间倒序 ListUserBookshelf shelfList userBookshelfMapper.selectList( new LambdaQueryWrapperUserBookshelf() .eq(UserBookshelf::getUserId, userId) .orderByDesc(UserBookshelf::getUpdateTime) ); // 2. 批量查书籍基本信息 ListLong bookIds shelfList.stream().map(UserBookshelf::getBookId).collect(Collectors.toList()); MapLong, BookInfo bookMap bookInfoService.listByIds(bookIds) .stream().collect(Collectors.toMap(BookInfo::getId, Function.identity())); // 3. 从Redis批量取阅读进度 ListObject progressList stringRedisTemplate.opsForHash() .multiGet(user:progress: userId, bookIds.stream().map(String::valueOf).collect(Collectors.toList())); // 4. 组装返回 ListShelfItemVO result new ArrayList(); for (int i 0; i shelfList.size(); i) { UserBookshelf shelf shelfList.get(i); ShelfItemVO vo new ShelfItemVO(); vo.setBookInfo(bookMap.get(shelf.getBookId())); vo.setLastReadChapterId(progressList.get(i) null ? null : Long.valueOf(progressList.get(i).toString())); result.add(vo); } return Result.success(result); }这里有个实际经验书架数量一般不会特别大一个重度用户可能有几十本书所以直接查MySQL再批量组装是可行的。不要为了显得高级给它加Redis缓存书架数据更新频率高缓存反而容易出脏数据。书架加入、移除、排序这三个操作都很直接注意加上userId条件防止横向越权。我见过不少源码项目在删除书架接口里只写了deleteById这要是上线了用户A可以把用户B书架里的书删掉妥妥的事故。3.3 搜索模块先别上ESMySQL也能顶住早期流量搜索功能很多教程上来就建议上Elasticsearch。我的观点是数据量没到百万级别别折腾ES。在线小说阅读平台早期书籍量可能就几千本MySQL的全文索引加上LIKE模糊查询完全够用。SELECT id, book_name, author, book_desc FROM book_info WHERE (book_name LIKE CONCAT(%, #{keyword}, %) OR author LIKE CONCAT(%, #{keyword}, %)) AND book_status 1 ORDER BY click_count DESC LIMIT 20;这个SQL在小数据量下表现很好。要注意的是LIKE %keyword%这种写法无法使用索引会走全表扫描所以务必加上LIMIT限制查询量并在book_status、click_count上建索引。当数据量真正增大到搜索延迟不可接受时再迁移到ES迁移时用Logstash同步MySQL数据应用层把搜素实现换成ES客户端这样演进路径是平滑的。3.4 用户模块JWT登录与拦截器配置用户模块是平台的入口。认证方案我选了基于JWT的无状态方案配合Spring Boot拦截器实现接口保护。Component public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (StringUtils.isBlank(token)) { response.setStatus(401); return false; } try { Claims claims JwtUtil.parseToken(token); UserContext.setUserId(Long.valueOf(claims.get(userId).toString())); return true; } catch (Exception e) { response.setStatus(401); return false; } } }JWT的好处是服务端不保存会话天然适合分布式部署。但要注意几个坑token过期时间不要设置太长建议7天同时前端配合拦截401刷新tokenJWT里不要放敏感信息只放userId即可登录拦截器要配置排除路径比如登录接口、注册接口、书籍详情、章节内容这些公开接口不需要登录访问。有一个实用小技巧拦截器放行公开接口通过注册WebMvcConfigurer时设置addPathPatterns(/**).excludePathPatterns(...)把公开接口写清楚后续新增接口时默认都是需要登录的这个思路比只给需要登录的接口加注解要安全得多。4. 数据建模与缓存策略哪些表要拆哪些数据必须进Redis4.1 六张核心表的设计与索引规划在线小说阅读平台的核心表我总结下来是六张用户表、书籍信息表、章节表、书架表、评论表、分类表。书籍信息表和章节表是重点。CREATE TABLE book_info ( id bigint(20) NOT NULL AUTO_INCREMENT, book_name varchar(255) NOT NULL COMMENT 书名, author varchar(100) NOT NULL COMMENT 作者, category_id bigint(20) DEFAULT NULL COMMENT 分类ID, book_desc text COMMENT 简介, cover_url varchar(500) DEFAULT NULL COMMENT 封面URL, book_status tinyint(4) NOT NULL DEFAULT 0 COMMENT 0连载 1完结, word_count int(11) DEFAULT 0 COMMENT 总字数, click_count int(11) DEFAULT 0 COMMENT 点击量, score decimal(2,1) DEFAULT 0.0 COMMENT 评分, create_time datetime DEFAULT NULL, update_time datetime DEFAULT NULL, PRIMARY KEY (id), KEY idx_category_status (category_id, book_status), KEY idx_click_count (click_count), KEY idx_create_time (create_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT书籍基本信息表;CREATE TABLE book_chapter ( id bigint(20) NOT NULL AUTO_INCREMENT, book_id bigint(20) NOT NULL COMMENT 书籍ID, chapter_no int(11) NOT NULL COMMENT 章节序号从1开始, chapter_title varchar(255) NOT NULL COMMENT 章节标题, word_count int(11) DEFAULT 0 COMMENT 章节字数, content longtext COMMENT 章节正文, create_time datetime DEFAULT NULL, update_time datetime DEFAULT NULL, PRIMARY KEY (id), KEY idx_book_no (book_id, chapter_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT章节表;这两个表的设计有几个关键决策。第一book_info和book_chapter分离。书籍基本信息是高频查询、低更新章节正文是超大字段。如果把正文放在书籍主表里列表查询会带上大量无用的大字段IO开销显著增加。分离后列表页只查book_info阅读器才查chapter表互不干扰。第二章节表用(book_id, chapter_no)联合索引。业务上查询一个章节目录、上下章跳转都依赖这个组合条件联合索引能覆盖这些查询。第三正文用longtext类型。有些源码把正文拆成多行存储在我看来没必要小说的单章内容一般不超过10KBmysql可以很好地处理。真要优化可以在应用层做内容压缩或静态化。4.2 三级缓存定位书籍详情、章节目录、章节正文的分层策略缓存不是一把梭什么数据都往Redis放。我把在线小说阅读平台的热点数据分为三级。缓存级别数据对象Redis Key过期时间策略一级书籍详情book:detail:{id}30分钟缓存穿透二级章节目录book:chapters:{bookId}1小时缓存穿透三级章节正文book:content:{chapterId}永久 主动失效逻辑过期书籍详情和章节目录的更新频率极低书籍上线后基本不变设置30分钟到1小时的过期时间即便出现短暂的不一致也没关系。章节正文的更新场景只有两种章节发布和章节修改。这两个操作都来自后台作者频率很低。所以章节正文缓存不设置过期时间而是采用主动失效策略——后台更新章节时同步删除Redis中对应的key。这种策略的优点是热点章节永远不会因为缓存过期而回源数据库能有效避免缓存击穿。4.3 缓存穿透、击穿、雪崩的兜底设计缓存穿透、击穿、雪崩是面试必问的高频题在线小说阅读平台里恰好都有真实场景。缓存穿透指的是查询一个不存在的书籍ID比如恶意用户拿着bookId999999频繁请求详情接口缓存里没有数据库也没有请求直接打穿。我的方案是双重保险一是接口层做参数校验bookId必须大于0二是用布隆过滤器把平台上所有有效的bookId初始放入布隆过滤器查询前先判断不在布隆过滤器中直接返回不存在。// 项目启动时把有效bookId加载到布隆过滤器 PostConstruct public void init() { ListLong allBookIds bookInfoMapper.selectAllIds(); bloomFilter BloomFilter.create(Funnels.longFunnel(), allBookIds.size(), 0.01); allBookIds.forEach(bloomFilter::put); } // 查缓存前先判断 if (!bloomFilter.mightContain(bookId)) { return Result.fail(书籍不存在); }缓存击穿发生在缓存失效瞬间大量请求同时打到数据库。对应到小说场景就是某本热门书籍的详情缓存刚好过期瞬间有上万请求进来。解决办法是加互斥锁重建缓存Redis的SETNX实现锁拿到锁的线程查数据库并重建缓存其他线程等待后直接读缓存。public BookInfo getBookDetail(Long bookId) { // 读缓存 String cacheKey book:detail: bookId; BookInfo book (BookInfo) redisTemplate.opsForValue().get(cacheKey); if (book ! null) { return book; } // 加锁重建 String lockKey lock:book:detail: bookId; boolean locked redisTemplate.opsForValue() .setIfAbsent(lockKey, 1, 10, TimeUnit.SECONDS); if (locked) { try { book bookInfoMapper.selectById(bookId); redisTemplate.opsForValue().set(cacheKey, book, 30, TimeUnit.MINUTES); } finally { redisTemplate.delete(lockKey); } return book; } // 没拿到锁短暂休眠后重试 Thread.sleep(100); return getBookDetail(bookId); }缓存雪崩是大量key在同一时间失效。常见解法是过期时间加随机值。我给不同的书设置不同的过期时间比如基础30分钟加随机几分钟避免集中失效。5. 一次慢查询与一次缓存雪崩两个真实问题的完整排查链路5.1 案例一详情页从50ms变成2s的慢查询根因追踪项目联调阶段我发现一个奇怪的现象本地开发环境详情页响应时间稳定在50ms部署到测试环境后响应时间涨到了2秒多。数据库数据量几乎一样代码也一样问题出在哪里我第一步看了慢查询日志。MySQL的慢查询日志默认关闭测试环境我提前开启了配置如下slow_query_logON slow_query_log_file/var/log/mysql/mysql-slow.log long_query_time1日志里果然抓到了问题SQL是一条查询书籍列表的语句执行时间1.8秒SELECT * FROM book_info ORDER BY update_time DESC LIMIT 10;update_time字段没有索引导致MySQL做了全表排序。本地数据量几百条全表排序无所谓测试环境数据量到了几万条排序开销就出来了。修复方案是在update_time字段加索引ALTER TABLE book_info ADD INDEX idx_update_time (update_time);加上之后这条SQL的执行时间降到了30ms以内。经过这次排查我养成了一个习惯建表时把所有排序字段、筛选字段的索引一次性设计好。在线小说阅读平台里常见的排序场景有按更新时间、按点击量、按创建时间、按评分这些字段都值得加索引。5.2 案例二热门书籍缓存雪崩后的降级处理第二个问题出现在压力测试阶段。我模拟了500个并发用户同时访问首页结果发现首页接口响应时间从80ms飙升到了10秒部分请求直接超时。定位问题之前我先检查了首页接口的缓存策略。首页返回的是热门书籍列表缓存key是index:hot:list缓存逻辑是启动时加载一次每天凌晨定时刷新。问题就出在这里定时刷新任务在凌晨12点整删除旧缓存重新查询数据库并写入新缓存。在删除和写入之间的空档期所有请求直接打到数据库赶上并发高峰数据库连接池被打满整个接口就雪崩了。这是一个非常典型的缓存雪崩场景。修复方案是双缓存加逻辑过期。具体做法是缓存数据结构中增加一个过期标记字段比如DBType值为hotexpireTime为5分钟。查询接口读到数据后判断expireTime是否过期如果没过期直接返回如果过期了不等于查数据库而是先返回旧数据同时异步触发一次缓存刷新。这样用户在刷新期间的请求不需要打到数据库体验也不受影响。public ListBookInfo getHotBooks() { // 1. 尝试获取互斥锁 boolean locked tryLock(lock:index:hot); // 2. 读取缓存 String cacheKey index:hot:list; IndexCache cache (IndexCache) redisTemplate.opsForValue().get(cacheKey); // 3. 缓存为空加载数据库 if (cache null) { if (locked) { ListBookInfo books bookInfoMapper.selectHotBooks(10); // 逻辑过期时间 IndexCache newCache new IndexCache(books, System.currentTimeMillis() 300000); redisTemplate.opsForValue().set(cacheKey, newCache); return books; } else { Thread.sleep(50); return getHotBooks(); } } // 4. 逻辑过期返回旧数据并异步刷新 if (cache.getExpireTime() System.currentTimeMillis()) { asyncRefreshHotBooks(); } return cache.getBooks(); }这套方案把缓存雪崩的风险降到了最低。后端这个经验后来也延伸到了书籍详情的缓存处理上效果一直很稳。6. 从开发机到服务器Docker部署、Nginx配置与日常维护6.1 Docker Compose编排MySQL、Redis与应用服务在线小说阅读平台的部署我推荐用Docker Compose一把梭。一台2核4G的云服务器跑MySQL、Redis和Spring Boot应用绰绰有余。先写DockerfileFROM openjdk:8-jre-alpine WORKDIR /app COPY target/novel-platform.jar app.jar EXPOSE 8080 ENTRYPOINT [java, -jar, app.jar, --spring.profiles.activeprod]再用docker-compose.yml编排三个服务version: 3 services: mysql: image: mysql:5.7 container_name: novel-mysql environment: - MYSQL_ROOT_PASSWORDroot123 - MYSQL_DATABASEnovel_platform volumes: - ./mysql-data:/var/lib/mysql - ./init.sql:/docker-entrypoint-initdb.d/init.sql ports: - 3306:3306 restart: always redis: image: redis:6.2 container_name: novel-redis ports: - 6379:6379 restart: always app: build: . container_name: novel-app ports: - 8080:8080 depends_on: - mysql - redis restart: always有个细节要注意depends_on只保证容器启动顺序不保证MySQL和Redis已经就绪。Spring Boot应用刚启动时可能会遇到连不上数据库的问题。解决方法是在应用配置中加上重试机制或者用Spring Boot的spring.datasource.hikari.initialization-fail-timeout参数调大一点。6.2 Nginx反向代理与静态资源分离部署到服务器后Nginx承担两个职责反向代理Java接口和托管前端静态文件。server { listen 80; server_name novel.example.com; location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_read_timeout 60s; } location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; } }接口统一放在/api前缀下前端静态文件用Nginx托管这样动静分离Java应用只处理接口请求。前端的JS、CSS、图片这些静态资源由Nginx负责性能更好。try_files配置是为了支持前端路由的history模式刷新页面时把请求转发到index.html。6.3 Actuator监控与备份策略上线之后的日常维护我开的是Spring Boot Actuator的少量端点配合简单的脚本检查服务健康状态。在application.yml中配置management: endpoints: web: exposure: include: health,info,metrics endpoint: health: show-details: always然后写一个简单的健康检查脚本每分钟检查一次#!/bin/bash status$(curl -s -o /dev/null -w %{http_code} http://127.0.0.1:8080/actuator/health) if [ $status -ne 200 ]; then docker restart novel-app echo $(date) - app restart /var/log/novel-monitor.log fi数据库备份用mysqldump配合crontab每天凌晨备份一次0 3 * * * mysqldump -uroot -proot123 novel_platform /backup/novel_$(date %Y%m%d).sql备份文件保留7天超过的自动清理。这个习惯看起来简单但真遇到数据损坏的时候能救命。小说平台的书籍数据是核心资产丢了就真没了。我自己的体会是部署环节花的时间不超过写代码的十分之一但它在面试里能拉开差距。能完整讲清楚从开发到部署全链路的人和只会写接口的人在面试官眼里是两个层级。这个在线小说阅读平台项目跑起来之后后续如果想继续扩展还可以往阅读时长统计、作者端、敏感词审核、充值支付这些方向加每一个都是一块独立的业务。先把核心链路吃透后面的一切都好说。本文还有配套的精品资源点击获取
返回列表