资讯动态

Spring Cloud微服务架构下的在线阅读打卡系统全解析

发布时间:2026/10/22 7:51:20 来源:尧图企业网站定制
先交代一下我为什么想写这个项目。前阵子一个做毕设的同学拿了个需求过来说想做一个“在线阅读读书打卡”的完整系统要求是微服务架构前端要覆盖Web和小程序两个端。标题一听就很典型SpringBoot Vue Spring Cloud 小程序再加一个“书洞”的意象和打卡闭环。这类系统其实市面上特别多但真正做完整的、能演示的、代码不跑崩的反而少。这篇文章我就以这个“书洞图书在线阅读打卡系统”为载体把微服务分布式架构从设计到落地、从用户端到管理端、从数据库到部署完整讲一遍把我实际开发中踩过的坑和验证过的方案都放进来给准备做类似项目的同学做一个可以直接抄作业的参考。说明本文所有业务功能和模块均基于一个虚构的在线阅读项目场景展开不涉及任何真实平台、机构或商业产品技术方案属于常规工程实践可以直接复现。我按真实的开发推进顺序来讲先讲整体设计怎么拆再讲后端微服务每一块怎么实现然后讲Vue管理端和小程序端的核心逻辑最后是数据库、部署和排障。内容比较长建议先收藏再慢慢看。1. 系统整体设计与技术选型思路做任何一个“看起来名字很全”的系统第一步不是写代码而是把架构想明白。尤其是带微服务、分布式、小程序、Vue这么多关键词的项目如果一上来就建工程、写接口后面一定会被服务之间的调用关系搞得头皮发麻。1.1 为什么选择微服务而不是单体先说一个很多人会忽略的点并不是项目叫“微服务”就一定要拆得非常碎。这个系统从业务体量看用户量可能是几千到几万的量级图书资源也是可控范围理论上单体完全跑得动。但这里选择Spring Cloud微服务不是纯粹为了“炫技”而是有三个实际目的。第一是业务边界的清晰化。把用户、图书、打卡、订单如果有会员/兑换码拆成独立服务每个服务表结构清晰、接口清晰多人协作时一个人负责一个模块不会整天合并代码打架。第二是局部独立扩展。比如打卡模块如果做活动、做任务体系压力会非常大独立拆出来后可以单独给这个服务加实例而不影响图书服务和用户服务。第三是技术栈学习的完整性对于教学、毕设、培训项目来说微服务本身就是考核点Spring Cloud全家桶的运用是评估核心架构能力的重要体现。但这不等同于说拆得越细越好。实际工程里有一个很关键的原则如果不是明确的独立业务域不要单独拆服务。比如日志、文件上传这类可以做成公共服务没必要每块都搞一个独立的微服务。我这个系统的最终服务划分是认证/用户服务、图书/阅读服务、打卡服务、管理后台网关聚合以及一个公共服务文件存储/验证码/通用配置。1.2 技术栈分工与版本选型这个项目整体用的版本组合我建议锁定一套兼容性好的不要东一个版本西一个版本。我实际用的是后端基础框架Spring Boot 2.7.x微服务治理Spring Cloud 2021.x Spring Cloud Alibaba 2021.x注册中心与配置中心Nacos 2.x网关Spring Cloud Gateway远程调用OpenFeign服务熔断Sentinel前端管理端Vue 2 Element UI如果团队熟悉Vue 3也可以但Element Plus的坑略多一点小程序端原生微信小程序数据库MySQL 8.x Redis部署Docker Docker Compose选这套组合的考虑是Nacos在国内项目中使用率极高相比Eureka的优势在于它同时提供注册中心和配置中心不需要额外搭Config ServerGateway相比Zuul性能更好且基于WebFlux响应式模型和Spring Boot 2.x配合很成熟Sentinel在流量控制和熔断降级上有现成的控制台演示效果也直观。Vue管理端和小程序端之间的数据来源完全一致都走后端API。这里要提一个容易踩坑的架构决策点小程序端调用的API域名和管理端的API域名必须统一走网关不要在服务里把接口散着暴露。所有请求经由Gateway统一鉴权、统一限流、统一路由这是微服务架构的底线。1.3 整体调用链路设计画一下这个系统的核心请求链路你在答辩和文档里都能直接用小程序/管理端 Web ↓ HTTPS Nginx静态资源 反向代理 ↓ Spring Cloud Gateway统一入口/鉴权/路由 ↓ Nacos 注册中心服务发现 ↓ 用户服务 / 图书服务 / 打卡服务 / 公共服务 ↓ MySQL业务数据 Redis缓存/Token/分布式锁管理端的Vue项目构建后是静态文件部署在Nginx上小程序端直接通过Nginx的HTTPS代理转发到网关网关再根据路径前缀路由到对应服务。用户服务统一做JWT的生成与校验其他服务通过网关传递的用户信息确定身份不再各自解析Token——这个“统一鉴权”一定要在设计说明里写清楚它避免了每个微服务重复写一遍认证逻辑的巨大冗余。2. 后端微服务核心模块实现这一节是全文的重点我会直接拆到模块级别来讲。每个微服务的代码量都不大但职责一定要单一否则后面扩展的时候难受死。2.1 认证与用户服务用户服务承担的事情比名字看起来要多注册、登录、小程序openid绑定、JWT签发、用户信息维护。这里的关键设计是小程序登录与Web管理端登录走两条通道。小程序端用户登录前端调用wx.login拿到code传给后端后端拿着code去微信的接口换openid。这个过程后端封装一个WxAuthService因为微信接口调用有网络IO必须设置超时并且要处理code重复使用的情况否则会报invalid code。换取到openid之后查用户表不存在就自动注册一个账号存在就直接生成JWT返回。管理端登录就是标准的用户名密码 验证码登录密码存储用BCrypt加密。这里要特别注意密码绝对不能明文存哪怕是教学项目答辩评委看到明文密码表印象分直接掉一截。JWT的设计我采用AccessToken RefreshToken双Token机制。AccessToken有效期2小时RefreshToken 7天。小程序端每次请求接口如果发现401就拿着RefreshToken去刷新。这个机制虽然增加了一点前端复杂度但比单一Token过期就让用户重新登录要体面得多。核心表大概长这样用户表设计如下字段类型说明idbigint主键openidvarchar(64)微信用户唯一标识可空usernamevarchar(32)用户名管理端passwordvarchar(100)BCrypt密文nicknamevarchar(50)昵称avatarvarchar(255)头像地址roletinyint0-普通用户 1-管理员statustinyint0-禁用 1-正常create_timedatetime创建时间在用户服务里有一个非常容易被忽略但特别重要的点不要在服务内自己解析JWT。网关已经把Token校验过了并把用户ID放在Header里向下游传递用户服务直接从Header取用户ID就行。这样用户服务自身是无状态的水平扩展时没有会话压力分布式这块能说得很有底气。2.2 图书与阅读服务图书服务是整个系统内容侧的基石。图书的数据结构不能只建一张表因为在线阅读要支持目录章节还要支持阅读位置记录。我拆了三张表图书表book、章节表book_chapter、阅读进度表read_progress。图书表字段包括书名、作者、出版社、ISBN、封面图、分类、简介、总章节数、点击量、状态。这里有个实用技巧点击量不要每次都update数据库。只要用户点击了图书详情就先在Redis里对应key自增然后定时任务每隔一段时间把增量刷入MySQL。你想想如果几百人同时点一本书每次都写库数据库会被搞得很被动。用Redis做缓冲不仅抗压而且演示的时候还能顺便展示一下分布式缓存的设计思想。章节表比较简单book_id、chapter_no、title、content_text。在线阅读的章节内容有两种存储方式。一种是直接存MySQL的TEXT字段这种方式实现最简单适合章节数不大、内容以纯文本为主的项目另一种是存对象存储或者本地文件MySQL里只存路径。我建议用第一种因为对于演示项目来说数据都存在库里更方便管理也避免了文件读写产生的IO瓶颈问题。阅读进度的实现是这类系统体验好不好的关键。用户打开一本书系统要记录他读到哪个章节、在章节内的滚动位置。前端跳出阅读页的时候把bookId、chapterId、scrollPercent传给后端后端更新read_progress表。下次用户打开这本书接口把进度返回前端直接定位到上次的位置。这个表还有个妙用——它可以派生“最近阅读”列表。小程序首页的“继续阅读”区域就是查这个表按更新时间倒序取前10条根本不需要额外的业务表去维护“我的书架”阅读进度本身就蕴含了书架关系。一个设计解决两个需求这种细节越往后越能体现出架构能力。2.3 打卡服务的业务规则与实现打卡是这个项目的灵魂功能也是最能体现业务逻辑设计能力的地方。先明确打卡的规则用户每天可以打卡一次打卡动作是“记录今日阅读时长”超过一定分钟数才能打卡成功连续打卡天数要能统计缺卡则中断连续记录可以通过补签卡补签补签卡由连续打卡奖励获得打卡后生成一张可分享的打卡卡片小程序海报打卡服务的核心难点是防重复打卡和连续天数计算。先说防重复用户一天只能打卡一次最简单的方式是在打卡记录表加唯一索引字段为user_id和date数据库层面保证唯一。但前端快速点两次按钮两个并发请求同时进来如果只靠数据库唯一索引兜底用户会看到一次失败一次成功的情况。更好的做法是打卡前先尝试Redis分布式锁锁的key设计为checkin:lock:{userId}:{date}加锁成功才允许继续执行打卡逻辑执行完释放锁。我实际开发中用的是Redis的SETNX命令来实现这个分布式锁设置过期时间3秒防止服务异常时锁一直不释放。实现起来大概是这样String lockKey checkin:lock: userId : today; Boolean locked redisTemplate.opsForValue().setIfAbsent(lockKey, 1, 3, TimeUnit.SECONDS); if (locked null || !locked) { throw new BusinessException(打卡处理中请勿重复操作); } try { // 执行打卡逻辑 } finally { redisTemplate.delete(lockKey); }连续打卡天数怎么算我设计了打卡记录表checkin_record每次打卡成功插入一条记录。查询连续天数时从今天开始往前倒推检查每一天是否有打卡记录。这个逻辑如果每次都用SQL循环查数据量大了效率不行。优化方案是在用户表冗余一个字段continuous_days每天第一次打卡的时候检查昨天有没有打卡如果有就1没有就重置为1。这个方案有一个好处不用在查询时做大量计算接口响应非常快。代价是如果打卡记录被手工删除冗余字段会不准——但业务上我们本来就不允许用户删除打卡记录所以这个隐患在业务规则层面就规避了。补签卡的设计也依赖这个冗余字段用户连续打卡7天发放1张补签卡后端在打卡成功的事务里检查continuous_days是否达到7的倍数是则插入一张补签卡记录。阅读时长的统计稍微复杂一点。小程序端阅读的时候我在前端每30秒上报一次累计阅读时长后端用Redis记录当日累计时长key为read:duration:{userId}:{date}。打卡的时候读取这个key判断是否满足最低时长要求满足就允许打卡。这里要注意前端上报的时长不能直接信任这是分布式系统设计里的经典问题。可以做个简单校验统计用户当天后端下发的章节内容刷新次数结合合理的阅读速度区间做一个上下限的合理性判断。太复杂的风控在这个项目里没必要但你要在文档里提到这个思路答辩时就是一个亮点。2.4 公共服务与网关设计公共服务主要提供文件上传、验证码等通用能力。图书封面、用户头像这类图片我统一用本地存储 Nginx静态映射的方式实现。Nginx配置一个/uploads/路径映射到服务器的某个目录上传接口把文件保存到这个目录后返回访问URL。用对象存储也可以但本地存储对演示环境更友好不依赖第三方服务。网关模块做的事情比看起来要多。每一条经过网关的请求都要过一遍全局过滤器Component public class AuthGlobalFilter implements GlobalFilter, Ordered { Override public MonoVoid filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 放行登录、注册等白名单路径 // 其余路径校验请求头中的 Authorization // 校验通过后解析用户ID放入请求头传递给下游服务 // 校验失败返回401 } Override public int getOrder() { return -100; } }网关的白名单至少包含用户登录接口、注册接口、验证码接口、微信小程序code换token接口。除白名单外的所有接口都必须校验Token校验通过后把用户ID写入Header下游服务统一从Header取用户ID不再自己解析JWT。网关的另一个重要职责是跨域处理。Vue管理端在开发环境通过8080端口访问后端在8000端口一定会有跨域问题。我在网关里统一配置了CORS避免每个微服务各自配置跨域能少掉很多麻烦。这个统一配置的思路一定不要忘记否则你在前后端联调时会被Origin相关的报错折磨到怀疑人生。2.5 服务调用与配置管理图书服务需要知道用户的基本信息来展示评论者的头像昵称吗需要但我们不会直接在图书服务里查用户表而是通过OpenFeign调用用户服务提供的接口。这个就是分布式系统里典型的服务间通信场景。Feign接口的定义放在单独的模块里避免服务之间循环依赖。我一般在common模块里定义Feign接口和DTO每个服务实现对应的Controller。比如用户服务提供一个GET /user/info/{userId}内部接口图书服务通过Feign客户端调用它。你需要注意Feign调用的超时时间一定要单独设置因为服务间调用经过网关会多一跳默认的1秒超时实在不够用。我实际配置feign: client: config: default: connectTimeout: 5000 readTimeout: 10000配置管理统一放Nacos每个服务都在Nacos上维护自己的配置。数据库连接、Redis地址、JWT密钥这些不要写死在本地配置文件里。实际使用中你会体会到不用改代码、不用重新打包直接在Nacos控制台改配置就能热更新的体验对调试和演示帮助极大。3. 小程序端与Vue管理端实现细节前端这块是整个系统“看起来完整”的最直接因素。小程序端面向普通用户管理端面向运营人员两端的交互完全不同实现思路也分开讲。3.1 小程序端的页面结构与阅读体验小程序的页面我分成五块首页、书架、打卡、我的、阅读页。首页是图书推荐流上面是轮播图推荐位下面是“热门图书”和“最近阅读”两个Tab。热门图书需要后端接口返回点击量最高的前10本最近阅读返回当前用户最近读过的书。轮播图管理在管理端配置小程序端拉取接口展示。书架页本质上就是阅读进度列表把read_progress里有记录的书按最近阅读时间排序列出来每本书显示书名、封面、阅读进度百分比和最后阅读时间。这一页不涉及复杂的逻辑纯展示。打卡页的交互是整个项目最有特色的地方。顶部显示两张卡片今日打卡状态和连续打卡天数。如果今天已经打卡显示“已打卡”并展示打卡天数如果没有显示“去打卡”按钮点击前先请求后端接口检查当日阅读时长是否达到门槛。达到门槛则打卡成功弹出分享海报未达到则提示还需阅读多长时间。打卡海报是很多同学觉得麻烦的功能。实际上实现不复杂后端返回一个Canvas绘制的数据结构包含书名、阅读天数、用户昵称、二维码内容等字段小程序端利用Canvas绘制并保存到相册。这里有一个我在实际开发中调试了很久的坑Canvas绘制的图片一定要在onLoad完成后调用不能在onReady之前绘制否则在某些基础库版本下会得到一张空白图。你可以在绘制前加一个wx.nextTick包裹通常能解决问题。阅读页是小程序端开发工作量最大的页面。要支持字号调整、背景色切换、翻页方式这里用滚动翻页就够、目录弹层、进度展示。章节内容的加载实现了一个简单的预加载机制当前章节渲染完成后后台静默请求下一章内容缓存到前端变量里用户滑动到底部时可以秒开下一章几乎感觉不到加载过程。这个优化在网速慢的时候效果显著建议一定要做。3.2 小程序用户登录态管理小程序端做登录态管理时要注意用户拒绝授权或者openid获取失败的情况。整体流程是小程序启动检查本地是否有token有token先去后端校验有效性可以调一个轻量的接口如“获取用户信息”有效则直接进入首页没有token或者已过期调用wx.login获取code后端换openid并返回新的token。还要处理一个容易被忽视的场景用户在小程序里点击了“重新登录”或者清除了缓存。这时本地没token但是后台数据库已经存在这个openid对应的账号。后端换openid的逻辑要注意如果用户存在则直接返回新token而不是报错如果不存在才自动注册。这样才能做到“静默登录”用户全程无感知。请求封装上一定要做一个统一的request工具函数所有接口都走这个工具。它要做三件事请求头自动加上token、响应401时自动刷新token并重放请求、统一处理业务错误码和弹出提示。3.3 Vue管理端的页面与角色权限管理端用的Vue Element UI页面按模块划分登录页、仪表盘、图书管理、用户管理、打卡数据、轮播图配置、系统设置。仪表盘的统计接口需要聚合多个服务的数据这里有两个方案。方案一分别调用各服务的统计接口前端并行请求再组合展示方案二后端做一个聚合接口通过Feign调用各服务统计由网关暴露成一个统一接口。我用的是方案二原因是前端只需要请求一次接口逻辑也更集中比较符合“管理层需要整体视图”的场景。图书管理页是最常见的CRUD表格要支持分页查询、关键字搜索、封面图上传。编辑图书时章节内容可以在一个弹窗里以“章节列表 编辑器”的形式维护保存时一次性提交整本书的章节信息。新增图书时可以做一个“快速导入”功能通过ISBN调用一个外部书库API填充基本信息前提是演示环境能访问外部服务如果不能就在本地写一个JSON文件作为模拟数据源。用户管理的核心功能是用户列表和状态管理。列表展示用户昵称、头像、注册时间、连续打卡天数、累计阅读时长。管理员可以将违规用户禁用禁用后该用户的下一次请求会因用户状态校验失败而得到提示。这里要求用户服务在网关校验Token之后再查一次用户状态。这个额外的查询是必要的因为Token的有效期是2小时用户在这段时间内被禁用必须能立即失效不能等到Token过期。权限控制用最经典的路由守卫方案登录后后端返回角色普通用户或管理员前端存到Vuex里路由表定义哪些页面需要管理员权限在路由守卫中判断角色没有权限则跳转到403页面。4. 数据库表设计详解与缓存策略数据库是整个系统稳定运行的基本盘。微服务拆分后每个服务单独持有自己的数据库实例或Schema服务之间不直接访问对方的表只能通过接口调用。这个设计在文档里一定要写清楚它是微服务和单体在数据库层面最核心的区别。4.1 四个核心库的表结构我把数据库按服务拆成四个逻辑库用户库、图书库、打卡库、运营库。以下是每个库的核心表设计都是按实际能跑通的标准来的。用户库核心表表名核心字段说明userid、openid、username、password、nickname、avatar、role、status用户主表user_read_durationid、user_id、date、duration_minutes每日阅读时长累计user_continuous_daysuser_id、latest_date、continuous_days连续打卡天数冗余表图书库核心表表名核心字段说明bookid、title、author、press、isbn、cover、category、intro、status图书主表book_chapterid、book_id、chapter_no、title、content_text章节内容表read_progressid、user_id、book_id、chapter_id、scroll_percent、update_time阅读进度表打卡库核心表表名核心字段说明checkin_recordid、user_id、checkin_date、continuous_days打卡记录表makeup_cardid、user_id、total_count、used_count补签卡表checkin_rewardid、user_id、reward_type、reward_date打卡奖励发放记录运营库核心表表名核心字段说明bannerid、image_url、book_id、sort、status首页轮播位admin_userid、username、password、role管理端账号可并入user表用role区分补充一下为什么user_read_duration和checkin_record不在同一张表阅读时长是高频写入的数据每次阅读上报都要更新打卡记录是低频写入的数据每天最多一条。混在一张表读操作会被频繁的写操作拖慢。拆开后两类操作的负载被隔离这在分布式系统里是常见的“读写分离”思想。4.2 Redis缓存Key设计与一致性Redis在这个系统里承担四类任务Token黑名单、分布式锁、阅读时长计数、热门图书缓存。Token黑名单用户退出登录时把当前Token的jti加入黑名单过期时间设为Token的剩余有效期。网关校验Token时先查黑名单在黑名单则拒绝。分布式锁就是打卡那节提到的checkin:lock:{userId}:{date}。阅读时长计数read:duration:{userId}:{date}每次阅读上报做INCR操作打卡时读取该值判断是否达到门槛。热门图书缓存hot:books缓存时间10分钟后台定时任务每10分钟重新计算并刷新一次。缓存一致性是微服务面试必问的问题这里也要在文档里说清楚。热门图书允许10分钟内的短暂延迟所以用定时刷新缓存就可以了。这个叫“最终一致性”对于非关键数据是完全够用的。4.3 MySQL索引建议索引设计不用多但几个核心的必须加上。打卡记录表的user_id checkin_date建联合唯一索引图书章节表的book_id chapter_no建唯一索引阅读进度表的user_id book_id建唯一索引。这三组索引直接支撑了系统最高频的查询没有它们数据量到几十万后接口会明显变慢。加索引不是随便加。联合唯一索引既有唯一约束的作用比如防止重复打卡又加速了按用户和日期查询的效率一个索引干了两个活这种设计要优先考虑。5. 部署上云与演示环境搭建项目开发完成之后部署是本地的、跑在IDE里的还是在服务器上的观感完全不一样。没有部署的微服务项目在答辩或者展示时说服力会弱很多。我建议至少用一台4核8G的云服务器通过Docker Compose把整套环境跑起来。5.1 Docker Compose编排整套系统的容器包含MySQL、Redis、Nacos、网关、四个微服务、管理端Nginx。Docker Compose的编排要点是服务依赖关系Nacos一定要等MySQL准备好之后再启动微服务要等Nacos可用后再启动否则会出现注册失败的问题。Nacos启动时要注意用单机模式设置MODEstandalone。MySQL容器设置初始密码和数据库创建脚本Redis不用额外配置默认参数即可。微服务的Dockerfile我统一用多阶段构建先Maven打包再使用JRE基础镜像运行jar包FROM maven:3.8-openjdk-8 AS builder WORKDIR /app COPY pom.xml . RUN mvn dependency:go-offline COPY src ./src RUN mvn package -DskipTests FROM openjdk:8-jre-alpine WORKDIR /app COPY --frombuilder /app/target/*.jar app.jar EXPOSE 8080 ENTRYPOINT [java, -jar, /app/app.jar]这种多阶段构建的好处是最终镜像只包含运行需要的JRE和jar包体积比直接用一个带Maven的镜像小很多部署和拉取都快。5.2 Nginx配置要点Nginx承担两个角色管理端静态页面托管和API反向代理。静态页面托管就是把Vue构建后的dist目录映射到/路径API反向代理是把所有/api/开头的请求转发到网关的地址。部署后有个非常经典的坑Vue打包之后刷新页面404。原因在于前端路由用了history模式刷新时Nginx找不到对应的物理文件。解决办法是在Nginx配置里加一个try_files回退location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; }这个问题你在开发环境用Vue dev server时不会遇到因为dev server内部做了history回退但部署到Nginx后如果没有这段配置每次刷新都会白屏。我当初在这个问题上卡了半小时写在这里你们可以跳过。5.3 小程序端请求域名配置微信小程序的网络请求限制非常严格只能请求HTTPS域名且域名必须在小程序后台配置为白名单。开发时可以勾选“不校验合法域名”但线上发布前必须在后台配置域名白名单。注意一个问题如果部署环境没有备案域名小程序可能无法正常请求。演示时的替代方案是使用“开发版 不校验合法域名”模式这在课堂演示和毕业答辩场景是完全可以接受的。6. 常见问题与避坑实录这个项目我从零到一跑通踩过的坑不少。下面按分类整理每条都是实际问题排查思路也写出来直接对着查。6.1 服务注册与调用问题问题微服务启动后Nacos看不到服务实例排查思路先看服务的日志中是否打印了Nacos registration success。如果没有检查三个配置Nacos地址是否正确、服务名是否设置、spring.cloud.nacos.discovery.enabled是否为true。还有一个最容易忽略的服务启动时Nacos还没完全就绪导致注册失败。解决方法是加启动等待机制或者手动把Nacos先启动稳定后再启动微服务。问题Feign调用报连接超时或拒绝连接排查思路Nacos上服务显示在线但Feign调用失败十有八九是服务之间配置了不同的网络模式比如一个在Docker里一个在本机。Docker Compose环境内服务名就是主机名Feign客户端要配置服务名而不是localhost。另外检查网关是否在两个服务之间做了额外跳转如果图书服务直接调用用户服务不走网关那关注点就是服务间网络不要把时间花在网关上。6.2 前端联调问题问题管理端请求接口报CORS跨域错误排查思路如果后端走网关只需要在网关配置CORS微服务里不要重复配置否则可能出现重复的响应头反而导致浏览器报错。检查请求是否先被Nginx拦截并代理到网关如果是要确认Nginx代理时没有丢弃请求头中的Origin信息。问题小程序登录报code无效排查思路wx.login的code只能用一次而且有效期很短大约5分钟。如果后端报invalid code一般是前端重复请求了登录接口或者后端拿到code后没有及时调用微信接口。排查前端是否有登录请求并发发出在request工具里加一个登录请求的互斥锁。6.3 数据与并发问题问题打卡数据重复排查思路虽然加了Redis分布式锁但极端情况仍然可能重复插入。最终的兜底在数据库的唯一索引如果重复插入抛了异常程序要用业务兜底逻辑处理而不是直接报500。建索引的方式见4.3节这个唯一索引一定要有。问题阅读时长不清零用户越来越少排查思路经排查发现定时任务没跑read:duration:{userId}:{date}这个key永不过期日期切换后还在累计。解决方案是Redis的key加上业务日期过期时间设置为两天这样昨天的数据会自然消失新的一天计数自动从0开始。做类似功能时所有这种日维度的key都要设置过期时间。6.4 小程序审核与发布问题问题小程序审核不通过这个项目涉及“阅读打卡”业务审核时要注意两点。第一是用户协议和隐私政策必须写清楚尤其是收集用户微信信息、阅读记录的部分否则会被打回。第二是打卡分享的海报如果有用户头像和昵称需要处理用户授权问题没有授权不能使用。我在小程序后台把“用户隐私保护指引”提前配置好这个流程可能需要提前三天操作因为审核有周期性一定要排在进行答辩或者演示之前。7. 微服务扩展到生产环境的思路如果只是做演示项目前面内容已经完全够用。但如果你希望这个系统具备更强的说服力或者导师问“你这个架构能扩展到什么程度”可以再补充三个生产化的方向不需要全部实现讲思路即可。第一个方向是服务分库分表与读写分离阅读进度表这种高频写表可以按user_id做水平拆分用户读多写少的表可以做读写分离。现在的配置管理里其实已经预留了多数据源切换的配置项真到数据量大的时候可以不改代码只改配置。第二个方向是消息队列解耦目前阅读时长的上报是直接调后端接口如果用户量增大这种同步请求会让用户服务非常被动。引入消息队列之后前端把阅读时长上报事件发到队列打卡服务消费事件更新时长考勤服务消费事件判定是否满足打卡门槛服务之间彻底解耦。第三个方向是容器编排目前用的是单机Docker Compose多机部署可以升级为Kubernetes。服务已经是无状态设计加个实例就能水平扩容这点在前面做技术选型时已经铺垫好了。到时候只需要把镜像推到仓库写Deployment和Service的编排文件即可。这些扩展方向不需要在当前阶段实现但能在答辩中展示你有全局眼光知道系统现在在哪里将来可以向哪里走这个思路的价值有时候比实现本身更被看重。8. 个人经验与最终建议这个系统看似模块很多但真正的主体就是“一套微服务骨架 一个阅读核心业务 一个打卡闭环”。骨架都是通用能力真正体现你工程能力的地方在于阅读和打卡这两个核心业务的建模是否合理。我个人实际开发中最深的体会是重复打卡的防护和阅读进度记录这两个设计最能体现一个开发者对业务细节的把握程度。前者考验并发处理意识后者考验数据建模思路。如果你只做一个简单的“打卡按钮 阅读页面”整个项目会显得很单薄把这两块的边界想清楚系统立刻就有“完整产品”的味道了。最后再分享一个小技巧在开发和调试过程中网关层的接口列表建议做一个简易的在线文档页把每个接口的请求参数、响应示例、当前状态写清楚。虽然只是多了个页面但联调效率和答辩演示的流畅度提升非常明显。把这个习惯保持住你会感谢自己的。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑