Spring Boot课程学习平台实战:从用户认证到订单支付的全链路设计
1. 这个项目到底解决了什么问题我先说结论课程学习平台这类系统表面上是把课程放到网上卖但真正难的不是课程管理而是把用户学习路径、课程权限、支付订单、内容分发这几条线串起来。我见过太多团队一上来就堆功能最后后台管理页面比前台还复杂学员连个课程都找不到。我做的这个基于Spring Boot的课程学习平台核心目标就三个一是让学员能按自己的节奏学习随时暂停、随时续看二是让管理员能轻松上架课程、查看学习数据三是让整个购买到学习的链路尽量顺畅不要在支付环节卡住用户。适合谁来参考两类人最合适。一类是刚接触Spring Boot项目开发的同学想看看一个完整业务系统是怎么组织模块的另一类是公司内部要做培训平台、知识付费小站的技术负责人不想从零踩坑想直接抄一份可靠的设计方案。先交代一下我当时的技术选型背景。Spring Boot 2.7.x作为基础框架配合MyBatis-Plus做持久层MySQL存业务数据Redis扛缓存和登录态OSS存课程视频文件前端用Vue做管理后台和学员端。这套组合在中小型项目里非常成熟招人好招出问题好排查运维成本也低。我没有刻意追求微服务、容器化这些时髦概念因为对一个初期用户量在几千到几万的课程平台来说单体应用完全够用反而更容易维护。整个项目我从零开始搭建覆盖了用户注册登录、课程分类与上下架、视频播放权限校验、订单支付、学习记录追踪、后台数据统计这几大块。下面我会按模块拆开讲重点讲每个模块在设计时是怎么想的以及实际编码中哪些地方容易出问题。2. 用户体系注册登录不是简单的存个密码2.1 登录方案选型用户模块是几乎所有业务系统的基础但也是最容易被低估的。课程平台的用户体系有几个特殊点用户不只是在电脑前学习可能在手机、平板上切换用户购买课程后登录态失效会直接影响学习体验另外平台有管理后台需要区分普通学员和管理员角色。我当时做了两个方案对比。一个是传统的Session Cookie简单直接但跨端体验差分布式部署时还要处理Session共享另一个是JWT Redis的方案JWT做身份凭证Redis存用户信息和权限标识。我最后选了后者原因很实际课程平台的用户会频繁在多个设备间切换JWT这种无状态凭证在移动端、PC端都好用配合Redis可以做主动失效控制比如后台封号时可以立刻踢人下线。JWT这里有个常见的坑就是token过期时间设太长。很多开发者图省事直接设7天甚至30天结果用户改密码后旧token还能用很危险。我当时的做法是JWT本身设2小时过期同时Redis里存一个续期标识用户活跃时自动续期超过7天没活跃就强制重新登录。2.2 注册流程与密码安全注册流程没做太复杂手机号加验证码是主流做法。但这里要注意验证码的发送频率必须限制否则容易被刷。我当时在Redis里做了两层控制同一个手机号60秒内只能发送一次同一个IP一天最多发送10次。这个看起来简单的限制能挡住绝大部分恶意刷短信的行为。密码存储是另一个重点。课程平台虽然不像金融系统那么敏感但用户密码一旦泄露牵连的是用户在其他平台的账号。我用的BCrypt加密这是Spring Security自带支持的算法每次加密会随机生成盐同一个密码加密两次结果都不同即使数据库泄露彩虹表也基本失效。2.3 角色权限的简洁设计很多教程会推荐用Spring Security OAuth2那套复杂体系但对课程平台来说用户角色就两种学员和管理员。我采用了更简洁的方式用户表加一个role字段然后在拦截器里做路由级别的权限控制。具体来说前端路由根据用户角色渲染不同的菜单后端接口在网关层或拦截器里校验角色的访问权限。比如管理后台的所有接口统一以/admin/**开头拦截器里校验role字段是否为管理员同时校验当前用户是否登录、登录态是否有效。这里要强调一个原则前端的权限控制只是提升用户体验的真正的权限校验必须在后端。我见过不少项目只在前端隐藏了管理入口接口完全没有防护别人直接请求接口地址就能拿到管理数据这是非常严重的安全漏洞。3. 课程模块从分类到视频存储的完整链路3.1 课程分类与检索的设计课程平台的课程内容五花八门有免费的引流课有付费的体系课还有会员专享课。我设计了课程分类表支持二级分类技术类、设计类、职场类这种粗粒度分类就够了。每个课程有一个课程详情表包含标题、封面图、简介、讲师信息、价格、是否上架等字段。这里有个细节就是价格字段一定要用整数类型存分而不是用浮点数存元。浮点数在计算订单金额时会出现0.1加0.2不等于0.3的经典问题用整数存分可以彻底避开这个坑。比如课程标价99.9元数据库存的是9990分前端展示时再转成两位小数。检索功能初期没上Elasticsearch直接用的MySQL的like模糊查询。课程量在几千条以内时性能没问题等数据量真的起来了再考虑搜索引擎也不迟。我加了课程名称、讲师名、课程简介三个字段的模糊匹配排序规则是上架时间倒序加销量倒序。3.2 课程的上下架与状态流转课程的状态是我特意设计的不是简单一个status字段就完了。我用了四个状态草稿、待审核、已上架、已下架。草稿是讲师或管理员在编辑中待审核是提交后等待审核已上架是正式对外展示并可购买已下架是暂时下架但数据保留。为什么要有草稿和待审核的区分因为课程平台如果允许内容直接上架很容易出现课程质量参差不齐的问题。尤其是多人协作运营时一个编辑可能误操作就把没做完的课发出去了。有状态的流转配合后台的审核操作记录出问题能追溯到人。课程上下架还有一个连带操作下架时不能影响已购买用户的学习。我的处理方式是已购用户不受影响可以继续观看新用户则无法购买已下架的课程。这个逻辑在课程详情查询和购买接口里都要判断。3.3 视频存储与播放权限视频存储用的云存储服务上传时通过服务端签名直传不走应用服务器中转这样可以节省大量带宽。视频播放这里有个安全考量不能让用户直接拿到视频文件的原始URL否则很快就会被搬运下载。我的方案是播放地址加时效签名默认有效期30分钟过期后重新向后端请求新的播放地址。后端在生成播放地址前会校验当前用户是否已购买该课程。这个校验逻辑是课程平台的核心一点都不能马虎。这里我踩过一个坑最开始视频文件用的是普通HTTP直链结果有用户买了课程后把链接发到群里所有人都能免费看。后来改成时效签名加用户校验才算把这个问题堵住。当然视频加密、防盗链这些更高级的手段也可以做但如果课程平台本身的定位是中小型知识付费时效签名加权限校验已经足够成本也低得多。4. 订单与支付买课链路中的坑与解法4.1 订单状态机设计订单模块是整个平台里最容易出隐性问题的地方。我设计的订单状态有这些待支付、已支付、已取消、已退款。状态流转规则很明确待支付可以取消可以支付已支付可以申请退款变已退款已取消和已退款是终态不能再往下走。为什么状态机这么重要因为如果代码里随意改变订单状态就会出现用户付了钱但课程没开通、退款了但权限还在这种事故。我见过最离谱的一个案例是开发者直接在订单表里UPDATE状态没有任何校验结果客服手动改数据把订单状态改乱了。我用MyBatis-Plus的乐观锁来处理订单更新加一个version字段更新时校验version是否匹配。这样即使并发请求同时来也只有一个能成功更新订单状态不会出现重复开通的问题。4.2 支付回调的幂等处理支付回调是订单模块最容易踩坑的地方。微信支付或支付宝在支付成功后会给后端发送异步通知这个通知可能会重试多次如果后端没有做幂等处理就会出现用户支付一次、课程开通两次或者订单状态更新多次的问题。我在支付回调接口里做的处理是先根据订单号查订单状态如果已经是已支付直接返回成功不再重复处理如果是待支付状态则更新状态并开通课程权限。整个操作放在一个事务里保证订单状态更新和课程权限开通要么同时成功要么同时失败。还有一个细节是支付回调一定要验签。我见过有人图方便直接信任了回调参数结果被伪造回调免费开通了课程。正确的做法是用支付平台提供的SDK来做签名验证验证通过后才处理业务逻辑。4.3 超时未支付的自动关闭用户下单后迟迟不付款会占用订单号的资源所以需要有一个超时关闭机制。我最初想的方案是定时任务每分钟扫描一次待支付订单超过15分钟就关闭。后来发现这个方案在高并发下会有问题用户刚好在超时边缘付款定时任务先把订单关了用户的支付请求后到就尴尬了。最终改成了延迟消息的方案用户在创建订单时同时发一条延迟消息15分钟后触发检查。检查时如果订单已经支付什么都不做如果还是待支付就关闭订单。这个方案在并发下表现更稳定也不会误伤正在支付的用户。5. 学习记录与进度追踪提升用户粘性的隐藏功臣5.1 为什么要记录学习进度课程平台如果没有学习进度记录用户关掉页面再打开就得自己找上次看到哪体验非常差。有了进度记录用户每次进入课程都能直接续看平台也能根据学习数据分析课程的完课率这是后台数据统计的重要数据来源。我设计的进度记录表包含用户ID、课程ID、小节ID、视频播放位置秒、最近一次学习时间。每次播放器上报进度时更新这个表前端在进入课程时拉取上次播放位置直接定位续播。5.2 播放进度的上报策略播放进度上报不能太频繁否则会给服务器造成不必要的压力。我当时采用的策略是每15秒上报一次如果用户暂停了就不再上报。同时前端在页面卸载时主动上报一次保证最后的学习位置能保存下来。这里有个细节就是播放器上报的信息不要只传秒数最好把视频总时长也带上。后端可以做一次简单的校验比如上报位置大于视频总时长就自动修正为总时长减1秒避免数据异常。5.3 学习时长统计与完课率学习时长的统计我放在了另一个维度来做不去修改进度表的数据而是专门有一张学习记录流水表每次播放器的心跳上报都插一条记录包含用户ID、小节ID、学习时长秒。后台统计完课率时用已学完的小节数 / 课程总小节数来算。这个统计有个需要注意的点不能简单地看用户是否点击过某个小节就算学完而是要看播放进度是否达到了90%以上。这个阈值我设置在代码里统一管理避免了各端统计口径不一致的问题。学习数据的价值在运营侧体现得很明显。后台可以看到哪些课程完课率高、哪些课程用户学着学着就流失了从而指导课程内容的优化方向。很多团队忽略了这部分其实学习数据是课程平台做内容运营最可靠的依据之一。6. 消息通知课程上架、购买成功与学习提醒6.1 通知类型与触发时机消息通知看似简单其实牵扯到很多触发点。我梳理下来课程平台的业务场景里有四类通知是很有价值的。购买成功通知用户支付完成后系统发一条站内信和短信告知课程已开通附上开始学习的入口链接。这部分对用户感知很有帮助尤其是在支付成功后到跳转学习页那几秒一条通知能让用户觉得平台很可靠。课程上架通知面向关注了某一类课程的用户课程上架后通知他们。比如用户关注了后端开发分类新的Java课程上架时就推送一条。这个能显著提升新课程的曝光和转化率。学习提醒通知用户连续几天没有登录时发一条提醒消息引导用户回来继续学习。注意频率要克制否则容易被当成骚扰用户反而会取关。活动通知营销活动、限时折扣这类消息主要由运营后台手动发起。6.2 站内信的实现方案站内信的实现方案网上有很多种我采用的是相对简单的收件箱模式。发送消息时往消息表插一条记录标记接收人ID用户查询自己的消息时按接收人ID查询并按时间倒序排列。已读未读状态用单独的字段标记。这个方案的优点是实现简单、查询效率可控初期完全够用。缺点是如果系统要做全站群发往消息表插大量记录会有性能压力。我的做法是群发消息只存一条用户查询时判断这条消息的发送时间是否晚于用户注册时间满足条件就展示这样就能在不插N条记录的情况下实现全站可见的效果。6.3 短信通知的坑短信通知我接的是市场上的短信平台服务使用中发现最大的坑是签名和模板审核。新注册的短信账号签名和模板都需要审核审核不过就无法发送。我当时因为课程名称里带了培训两个字被平台判定为需要特殊资质折腾了好几天。另外短信发送是有成本的发送频率必须控制。我在代码里做了节流同一个用户一天最多接收5条短信超过后只发站内信。这个策略上线后短信费用直接降了一半用户也没有抱怨收不到消息说明之前的短信确实发得太多了。7. 后台管理的核心模块设计7.1 数据看板运营最关心的几个数字后台管理不是一个可有可无的附庸而是运营每天都要用的工具。数据看板我重点做了几个卡片今日新增用户、今日订单数、今日销售额、总课程数、总用户数、总订单数。同时下面放几个趋势图展示最近7天和30天的订单量走势。这部分技术上没有太多难点就是要注意查询效率。如果直接用订单表做聚合统计数据量大了会拖慢数据库。我的方案是每天晚上跑定时任务把当天的统计数据汇总到一张统计表中后台看板的数据都从统计表读取。事实表和汇总表的分离在这个场景里很实用。7.2 课程的审核与编辑流程后台的课程审核页面要展示课程的基本信息、视频列表、封面图等审核通过后课程状态变为已上架。编辑功能要注意一个问题正在被学员购买的课程编辑后不能影响已购买用户的学习体验。我的做法是课程内容分为草稿区和发布区。编辑时修改的是草稿区确认没问题后点击发布发布区才更新。已购买用户学习的始终是发布区的版本这样既允许讲师优化课程内容又不会让正在上架中的课程出现内容断裂。7.3 用户管理封禁与解封用户管理除了查看用户列表还有一个重要的功能是封禁。当用户出现恶意刷课、盗版传播等行为时需要能快速封禁账号。封禁操作在我这里不只是改一个status字段还需要连带处理清除该用户的登录态删Redis中的token并把该用户已购买的课程权限临时冻结。这里有个容易忽略的点封禁后用户如果再次注册怎么办我用的是手机号黑名单机制被封禁的手机号无法重新注册。这样能防止恶意用户换个马甲继续搞事。8. 部署、性能与安全的几点实战总结8.1 服务器部署与架构演进我当时的部署方案前期是单体应用部署在一台云服务器上MySQL、Redis、应用部署在同一台没什么问题。用户量上来后做了读写分离MySQL加了一台从库应用的读操作走从库写操作走主库。这样数据库压力缓解了不少。应用层的负载均衡我用的Nginx反向代理指向多台应用服务器。既然是多个应用实例登录态就不能只用JWT那样用户在不同实例间切换会掉线其实JWT本身无状态有问题的反而是Redis里的用户存储。所以我把Redis独立出来多台应用共享同一个Redis这样登录态和用户信息就能在多实例间共享。8.2 接口安全防护课程平台虽然不像电商那么敏感但接口安全还是要重视。我重点做了这几层防护接口限流、参数校验、SQL注入防护、XSS过滤。接口限流用的网关层拦截器加Redis计数器实现。比如登录接口限流为每分钟5次支付回调接口为每分钟100次后台接口为每分钟60次。参数校验用Spring的Validation注解统一处理避免脏数据进入数据库。SQL注入用MyBatis-Plus的预编译机制只要不自己拼SQL基本能防住。XSS过滤我用的开源的过滤组件对用户输入的富文本内容做清洗防止脚本注入到页面里执行。这个在课程简介、评论这些用户可编辑的字段上尤其重要。8.3 定时任务与缓存策略定时任务用的Spring自带的Scheduled注解项目里主要跑三个任务每日统计汇总、超时未支付订单检查延迟消息方案为主、课程上架状态轮询。定时任务的代码建议单独拆一个包不要和业务逻辑混在一起否则排查问题会很吃力。缓存策略上课程详情、轮播图这类热点数据我放在Redis里缓存有效期30分钟。用户学习进度这种数据读多写少也放Redis过期时间1天。订单、支付回调这类强一致性的数据不缓存直接走数据库。8.4 项目上线后踩到的两个真实问题第一个问题出现在上线后第二周有一个课程的封面图突然裂了。查了半天发现是OSS的Bucket权限设置问题前端可以上传但读取时部分路径没有公开权限。因为课程图片是所有人都要能看的我把图片Bucket设为公共读同时配合时效签名来做防盗链问题解决。第二个问题更隐蔽。有一次运营反馈说后台看板的今日订单数比实际少了几十单。查了很久发现我统计用的事订单创建时间而不是支付完成时间。有些用户创建订单后第二天才支付导致当天的统计遗漏了这部分。后来我改成按支付完成时间统计看板数据才准确。这两个问题都不算技术难度大但恰恰说明了课程平台这类系统里细节往往比技术栈本身更影响用户体验和运营判断。9. 最后分享一点我做这个项目的体会整个课程学习平台做下来我最大的感受是Spring Boot技术栈本身并不复杂真正花时间的是把业务流程理清楚把各种状态边界条件想周全。如果你打算自己从零做一个类似的平台我的建议是先画清楚数据模型和状态流转图再动手写代码。用户、课程、订单、学习记录几张表之间的关系想明白了开发效率会高很多。反过来如果急着写代码后面改数据结构的成本会非常高。另外不要迷信复杂的技术方案。初期用户量不大单体应用加MySQL加Redis完全够用。等到了真正需要消息队列、微服务、容器化的时候说明业务已经壮大了那时候再演进也来得及。项目能跑得稳比技术栈显得高级重要得多。