最近刚把这套苍穹外卖项目完整跑通了一遍,前后花了几周时间,从需求分析、表结构设计到接口开发、前后端联调再到部署上云,整个链路都过了一遍。收获确实比想象中大,不光是多会了几个中间件,而是真正理解了“一个线上项目到底是怎么从零长出来的”。这篇就当复盘笔记写,把我觉得有价值的东西沉淀下来,也给正在做同类外卖/商城类实战项目的朋友一个参考。
这个项目解决的是很典型的问题:一个完整的C端外卖业务,涉及用户点餐、商家接单、平台管理、第三方支付、订单状态流转、高并发读支撑等一堆真实场景。跟平时练习写的单表CRUD完全不是一个量级,非常适合用来打通 Spring Boot + Redis + MySQL + WebSocket + 定时任务 这一整套后端技术栈,同时对前端项目也做了不少工作,前后端分离、接口联调、跨域处理这些都能碰到。
先说清内容适合哪些人:正在准备校招或初入职场的后端开发,想从“增删改查选手”往“有点工程思维”方向走的人,靠自己看视频学Spring Boot但缺少完整业务串联的读者。如果你已经工作多年,这篇可能更多是查漏补缺,但里面有几个场景的踩坑思路应该还是有点参考价值的。
1. 项目整体设计与链路拆解
1.1 外卖业务的完整闭环
拿到项目需求的时候别急着写代码,先把业务流转图画一遍。苍穹外卖这个业务模型其实非常典型,包含四个角色:用户、商家、骑手、平台管理。用户在小程序端浏览菜品、加购物车、下单支付;商家在管理端接单、出餐;骑手接配送任务;平台侧则需要对订单、菜品、用户数据做监管和统计。四个角色分别对应不同的入口,但又共享同一套核心订单数据。
我最开始以为外卖系统不过是“用户下单-商家接单”两步,真上手才发现中间还有支付回调、订单状态机、超时取消、退款流程、菜品库存校验、购物车聚合、用户地址管理、配送记录等一大堆关联动作。任何一个环节没设计好,都会造成数据不一致或者流程死锁。做这套项目最大的收获之一,就是把这些“隐性业务”全部暴露了出来。
具体到数据流转,核心链路大概是这样的:用户提交订单请求,后端先创建待支付订单并扣减相关库存,然后引导用户去第三方支付平台付款,支付平台异步回调通知后端,后端校验并更新订单状态为“待接单”,商家端通过WebSocket收到新订单提醒,接单后状态变为“配送中”,骑手完成配送后订单进入“已完成”,整个链路才算走完。任何一个状态跳变如果缺少前提校验,订单就会在多个终端显示错乱。
这个业务闭环在课程练习里很难碰到,因为大多数练习题都在做“单点功能”。但做一次完整闭环之后,你对“为什么订单表要有状态字段”“为什么支付要用异步回调而不是前端跳转后直接改状态”这类问题的理解会完全不同。
1.2 表结构与模块边界设计
这个项目给我的另一个硬核训练是数据库表设计。外卖业务的表大概包括:用户表、用户地址表、购物车表、订单表、订单明细表、菜品表、分类表、套餐表、员工表(管理后台登录用),以及一些辅助记录的表。订单主表和订单明细表拆开设计是必然的,因为一个订单会包含多个菜品明细,如果把明细直接塞进订单表,要么存JSON导致统计查询困难,要么重复冗余导致维护困难。
拆分表之后就要面对另一个经典问题:查询时的N+1问题。比如展示订单列表时,每查一条订单都要再查它的菜品明细,数据量一上去就特别慢。我当时就是在订单模块查询里踩了这个坑,后来用一次性关联查询或者动态拼接SQL的方式解决掉。实际操作中,你要想清楚哪些查询是高频低频字段各放到哪张表,哪些字段是冗余字段但可以少一次连表查询。
模块边界上,这个项目按照“用户端”“管理端”“公共模块(登录、文件上传、缓存等)”来划分,包结构清晰,业务逻辑和控制器分离。我学到的分层方式是:Controller只做参数接收和结果封装,Service层承载业务规则,Mapper层只做数据操作。这种分层一开始觉得啰嗦,但项目模块一多,好处立刻凸显:排查问题时不需要在Controller里翻业务逻辑,改SQL也不会误伤接口逻辑。
1.3 技术选型的取舍逻辑
苍穹外卖整体是单体Spring Boot架构,但内部模块划分粒度是照着企业级规范来的。我在做之前一直纠结“为什么不直接上Spring Cloud微服务”,做完之后感受是:对于这种体量的业务,单体完全够用,还能避免微服务带来的分布式事务、服务治理、链路追踪等额外复杂度。如果一开始就上微服务,大概率所有时间都花在环境搭建和问题排查上,核心业务反而没吃透。
技术栈选型背后其实都对应具体业务痛点,这点比技术本身更值得复盘。为什么用Redis?因为菜品列表、分类信息在高峰期会被大量用户并发读取,属于典型的高频读场景,每次查MySQL压力太大,加一层缓存能明显改善。为什么用JWT而不用Session?因为小程序后端接口是前后端分离的,天然无状态,JWT可以把用户身份信息直接放在令牌里,不需要服务端保存会话状态,水平扩展时也不依赖会话同步。为什么定时任务接管超时订单?因为用户下单后可能一直不支付,如果一直占着库存或菜品名额,系统会被无效订单拖垮。
把这些“为什么”都弄清楚,面试和实际项目里才不会露怯。很多同学做完项目只会说“用了Redis缓存”,但一被问“缓存和数据库一致性怎么解决”就答不上来。所以我强烈建议,每用一个中间件或技术方案,都要在文档里写下当时的选型理由和遇到的坑。
2. 核心功能模块的重难点实战
2.1 登录鉴权:JWT + ThreadLocal 双端隔离
登录鉴权是每个业务系统的第一道门槛。苍穹外卖项目里有用户端和管理端两套身份体系,我一开始偷懒想共用同一个拦截器,结果发现用户登录态和员工登录态的安全要求、过期时间、接口范围都不同,强行共用会埋下权限漏洞。
实际做法是:用户登录后返回一个JWT令牌,之后的请求在请求头中带上Token;后端通过拦截器统一解析Token,把解析出的用户ID存入ThreadLocal;业务层随时可以从ThreadLocal中拿到当前操作人ID,不需要每个接口都显式传递用户信息。这里有一个特别容易踩的坑:ThreadLocal使用完必须调用remove方法清理,否则高并发下线程池复用的线程会读到上一个请求留下的用户数据,造成“串号”和数据越权。我当时就是在一个业务接口里忘了清理,测试时发现用户A的购物车偶尔会出现用户B的菜品,排查半天才意识到是ThreadLocal没清。
JWT本身的结构也要理解清楚,它由三部分组成:Header、Payload、Signature。头部声明签名算法和令牌类型,负载部分存放用户信息、过期时间等业务数据,签名部分用密钥对前两段进行哈希签名,防止内容被篡改。这里要重点记住:JWT的Payload只是Base64编码,不是加密,千万别把密码等敏感信息放进去。过期时间也不能设太长,一般来说管理端可以比用户端稍长一点,但也要根据实际使用频率设计。
如果有兴趣继续深挖,可以在项目里加上“双Token”机制:一个短期有效的AccessToken用来正常请求,一个长期有效的RefreshToken用来无感续期。这个不是必须,但做过的同学在简历上会多一个亮点。
2.2 缓存设计:Redis缓存高并发读,一致性是核心
外卖系统中高并发读的场景很集中,比如首页菜品分类、菜品列表、用户购物车内容,这些数据读频率远高于写频率。用Redis做缓存是最直接有效的优化手段。苍穹外卖里的典型做法是先查缓存,命中直接返回,未命中再查数据库,并将结果写入缓存同时设置过期时间。
但缓存方案里真正的难点不是“怎么用”,而是“一致性怎么保证”。我当时踩过一个大坑:更新菜品信息时,我选择直接更新Redis缓存而不是删除缓存,结果两个并发请求同时更新同一份菜品数据,后写入数据库的先写入缓存,导致数据库和缓存里的最终数据不一致。后来改成“更新数据库之后主动删除缓存”,让下一次读请求重新加载数据库内容,才算解决了这种写覆盖问题。
缓存实战中还要考虑三种经典场景:穿透、击穿、雪崩。穿透是指查询一个不存在的ID,缓存和数据库里都没有,恶意请求会打到数据库,处理办法是对不存在的key也做空值缓存,或者用布隆过滤器;击穿是某一个热点key在过期瞬间被大量请求同时打到数据库,解决思路是用互斥锁/逻辑过期控制并发回源;雪崩是大量key在同一时间段集中过期,导致请求流量全部压向数据库,简单做法是给过期时间加随机偏移量。这些方案在这个项目里不一定都用到,但理解清楚后,遇到高并发场景不会心里没底。
另外要注意Redis的key命名规范,项目中基本采用“模块名:业务ID”的格式,比如dish:1、category:list。命名清晰最大的价值不是好看,是排查问题时能快速定位、批量清理。我在实习时见过有人把key命名为a1、b2,结果缓存事故一出现,想精准删除都找不到目标。
2.3 订单状态机、支付回调和幂等处理
订单模块是整个外卖系统里最难啃的骨头,也是我最推荐花时间研究的部分。订单状态不是简单一个字段存起来就完事,业务上要求状态只能按照某种合法路径流转:待支付 -> 已支付/待接单 -> 配送中 -> 已完成,或者待支付 -> 已取消,已支付 -> 申请退款 -> 已退款。如果接口没有做状态校验,用户连续点击提交订单,就可能生成多条重复订单;商家重复接单,也可能跳转出非法状态。
实际开发里要用“状态机”思维来控制:在Service层的状态变更逻辑里,先查旧状态,校验旧状态是否符合期望的前置状态,再更新新状态。比如接单操作,只有订单处于“待接单”状态才能执行,其他状态一律返回业务异常提示。这样看似多写了几行判断,但能挡住大量并发重放和恶意调用。
支付回调是另一块硬骨头。用户在前端完成支付后,支付平台会异步发起回调通知到后端,后端处理这个回调通知并更新订单状态。这个过程中最怕两种情况:一是回调通知重复发送,二是本地网络抖动导致回调延迟。重复回调会直接导致订单状态被重复更新,所以回调处理逻辑必须具备幂等性。我的做法是:先用订单号和支付流水号查一次订单当前状态,如果已经处于“已支付”状态并且流水号一致,直接返回成功,不再重复更新。
有一个大家容易忽略的点:不能只在前端判断支付成功后跳转页面,就认为订单状态已经是“已支付”。真正可信的支付结果必须以后端收到回调并更新数据库为准。我在测试时就遇到过Android前端支付成功但后端回调迟迟没到的情况,用户端页面显示支付成功,订单却一直是“待支付”,最后靠定时任务补偿扫描才恢复。支付相关逻辑一定要设计超时补偿机制,不能完全依赖回调。
2.4 购物车并发与防超卖
购物车的增删改查表面上是普通CRUD,但有些细节很考验基本功。比如用户往购物车里加同一个菜品,是新增一条记录还是累加数量?正常业务应该是在同一购物车记录里增加数量。那如果两个并发请求同时执行“菜品不存在则插入,存在则数量加一”的逻辑,就可能出现重复插入,需要在唯一索引或条件更新上做约束。
真正值得展开的是防超卖问题。外卖场景虽然不像秒杀系统压力那么大,但特价菜、限量套餐同样会出现库存不足的情况。如果库存直接在数据库里做“先查库存、判断足够、再扣减”三步,并发请求下就可能在扣减时出现负数。解决思路有两种:一种是乐观锁,在更新语句里加上条件判断,比如UPDATE dish SET stock = stock - 1 WHERE id = ? AND stock >= 1,如果影响行数为0说明扣减失败,再返回库存不足提示;另一种是Redis预扣库存,先扣减Redis中的可用库存数,成功后再异步同步数据库,但操作复杂度更高,涉及到Redis和数据库双写一致性问题。
对于外卖项目,我建议用乐观锁就够了,原因很简单:并发量级没有那么夸张,而且代码逻辑清晰、容易维护。不需要上来就上消息队列削峰、Redis Lua脚本扣减这种重方案。做项目不是追求技术炫技,而是选择合适的方案满足业务要求,这个判断力也是“项目收获”的一部分。
3. 前后端协作与接口工程化
3.1 统一返回体与全局异常体系
做联调的时候,最深的一个体会是:接口返回格式如果不统一,前端会写到想骂人。你想啊,有的接口成功返回{status: 1, data: {...}},另一个返回{code: 200, result: [...]},前端接一个接口就要换一套解析逻辑,项目大了纯粹是灾难。苍穹外卖这套项目里使用的是通用返回结果:code表示状态码,msg表示提示信息,data放具体数据。成功时code为1,失败时code为0,异常时code为500这种维度自由设计。
跟统一返回体搭配的还有全局异常处理器。业务异常、参数校验异常、未知系统异常分别对应不同的返回码和提示信息。这样Controller层就不用写大量try-catch包裹代码,业务代码只关心正常逻辑,异常统一抛给全局处理器。这个习惯非常推荐,我之前写的代码全部都是try-catch嵌套,一个接口几十行都在处理异常,后来发现既难看又容易漏掉异常分支。
全局异常体系设计时有个小技巧:自定义业务异常类,携带错误码和错误信息,在Service层直接抛出。Controller本身几乎看不到异常处理逻辑,既干净又不容易出错。我在项目里写了一套基础返回类和分页结果封装,所有模块复用,后续写接口基本只需要关注入参和返回值。
3.2 文件上传与对象存储方案
菜品图片上传是外卖系统一定会有的功能。刚开始我想的是把图片放到本地服务器某个目录,在数据库里存本地路径,然后通过静态资源映射访问。本地方案在练习环境没问题,但一旦部署到云服务器或容器里,问题就来了:图片存本地,代码重新部署时可能丢失,图片所占用磁盘空间会无限增长,多实例部署时静态资源率先不同步。
正式项目更推荐做法是使用对象存储服务,上传接口把文件交给存储服务,拿到文件URL后存入数据库字段。这里有几个细节:文件名不能直接用用户上传的原始文件名,因为可能重名、可能包含特殊字符,一般用UUID或时间戳生成新文件名,再拼接原文件后缀;访问权限要根据图片类型设置,用户头像、菜品图片一般公开只读即可;对象存储的客户端要封装成统一的FileStorageService接口,方便后续切换不同云厂商。
上传功能看起来就是个“接收MultipartFile,调OSS接口”,但把它做利落也需要考虑大小限制、类型白名单、URL有效期、防盗链等问题。而且我强烈建议不要在前端直接暴露存储服务的密钥配置,一切上传操作都走后端接口,前端只负责选文件和展示结果。
3.3 联调、Mock 与接口文档
前后端分离项目里,联调是个大工程。前端开发的时候后端接口可能还没写好,这时就需要Mock数据。项目里做了一部分Mock能力,让我体会到了接口“契约先行”的价值:前后端先约定请求路径、参数名、返回结构,然后用Mock数据并行开发,最后联调时再切换到真实后端。这样能减少大量等待时间,也让后端知道自己的接口设计是否合理。
接口文档也是协作的关键一环。用Swagger/Knife4j自动生成接口文档,比写Word文档要省时得多,还能直接在页面上调试。实际联调中最常出的问题反而很简单:参数名不一致,比如前端传userId,后端接收userId但加上@RequestParam后名字写错;字段类型不匹配,时间传的是字符串,后端却要Date对象。这些问题通过文档在线调试基本一次就能发现。
另外做支付模块测试时,并没有真实第三方支付环境可用,我自己写了一个Mock回调工具,模拟支付平台向后端发起异步通知,用来验证订单状态流转、幂等逻辑和异常处理。这个经验后来在真实项目里也很有用:凡是依赖外部系统的模块,都可以用本地Mock先保证自身逻辑正确,再考虑跟真实系统对接。
4. 高并发与性能优化实战
4.1 SQL 慢查询分析与索引优化
外卖系统的订单表会随着时间膨胀得非常快,索引设计不合理会导致接口响应越来越慢。我在项目里因为订单查询加了很多条件,比如按状态筛选、按时间范围筛选、按订单号精确查询,一开始没建索引,测试数据量稍微大了一点,查询就明显卡顿。后来通过慢查询日志和EXPLAIN分析,发现全表扫描,于是对status、create_time、order_number等查询字段创建了联合索引,效率提升非常明显。
有一个比较隐蔽的问题:分页查询深翻页性能很差。比如用户管理端查询第10000条之后的数据,用传统的LIMIT 100000, 10方式,MySQL仍然需要先扫描并丢弃前100000条记录。优化思路是改成基于索引的延迟关联或者记录上次查询最大值来做游标分页。外卖项目里分页数据量没那么夸张,但掌握这个优化思路之后,遇到大数据量表心里有底。
优化SQL不只是“加索引”三个字,还要养成先看执行计划的习惯。我在调试慢SQL时发现,查询条件里对索引字段做了函数处理,比如DATE(create_time) = '2024-01-01',索引就失效了。改成范围查询create_time >= ? AND create_time < ?之后,同样的数据量查询快了几十倍。这些都是适合写进复盘笔记的真实经验。
4.2 WebSocket 实时推送的实现与坑
外卖商家端需要实时接收新订单提醒,用户端也需要实时看到订单状态变化。如果用前端轮询接口,每隔几秒去查一次订单状态,体验差而且还会有大量无效请求打到后端。项目里用的是WebSocket实现服务端推送,商家在商家端建立长连接,后端订单状态变化时主动向商家端发送消息。
WebSocket的代码实现其实不难,难点在于连接管理。系统同时存在用户端和商家端两类连接,必须根据连接携带的身份信息来区分推送目标。我是用一个ConnectionPool统一管理连接,连接建立时从Token中解析用户/商家标识,存入一个ConcurrentHashMap,发送消息时按标识找到对应的WebSocket Session再推送。如果不做区分,所有连接都订阅所有消息,那消息就会串线。
我在联调中还真遇到过一次消息丢失的问题:用户支付成功后,订单状态更新为“待接单”,但商家端页面没收到新订单提醒。排查后发现,问题出在WebSocket连接建立时机和后端推送时机上——商家端连接还没建立完成,后端就已经开始推送消息了。解决办法是先确认连接就绪再允许消息推送,并在连接断开时提供重连机制。WebSocket虽然不像REST接口那样人人熟练,但外卖、物流、聊天类业务几乎离不开它,值得认真研究。
4.3 定时任务的正确打开方式
外卖系统里有两个典型的定时任务场景:一是超过一定时间未支付的订单自动取消,二是每日产生平台统计数据报表。这些场景用Spring Task或者XXL-Job都能实现。苍穹外卖里用Spring Task比较多,直接在启动类开启定时任务,然后在方法上配置cron表达式。开始我只觉得“定时任务嘛,就是定时跑个方法罢了”,后来才发现里面容易踩的坑一点不比接口少。
定时任务最容易出现的坑是“重复执行”:如果你的应用部署了多台实例,每台机器上的定时任务都会触发,同一批超时订单可能被重复扫描和处理。单机环境下没事,一旦集群部署,必须考虑使用分布式锁保证同一时刻只有一台机器在执行任务。还有一个坑是任务执行时间过长,如果上一次任务还没执行完,下一次任务又触发了,会导致数据混乱。解决方式是加任务执行状态标识,或者直接把执行时间设计得比任务耗时长得多。
处理超时订单时,还有个细节是扫描条件必须严谨。我当时写定时任务时条件只判断“支付状态为待支付”和“创建时间超过30分钟”,结果把用户刚创建但还没来及支付的订单也扫进去取消掉了。后来加上了“订单状态、支付状态、创建时间、是否已取消标记”的组合条件才稳定。定时任务上游的“边界条件”要比普通接口严格得多,因为它是无人值守运行的,错了也没人立刻发现。
5. 工程协作中的思维升级
5.1 从需求到落地的拆解能力
写代码之前,先学会拆解需求。苍穹外卖这种规模的项目,需求文档一展开可能几十个页面、上百个接口。如果直接埋头开写,很容易写着写着就迷路,或者是做着做着发现某个模块跟另一个模块对不上。项目里比较有效的做法是先做功能拆分:把整个系统拆成用户端、管理端、公共模块三大块,再往下拆具体功能模块,比如用户端可以拆出登录注册、分类浏览、菜品搜索、购物车、下单支付、订单历史、地址管理。
每个功能模块内部还要继续拆:涉及哪些表、需要哪些接口、接口的入参出参是什么、访问权限是什么。拆得越细,后续写起来越不容易返工。这种需求拆解能力不是天生的,多拆几个需求自然就有感觉了。我刚开做时跳过了需求分析直接写代码,结果后面改了三版表结构,时间成本很高。
这个能力在职场上价值极大。面试官问项目经验时,最在意的不是你会不会写个接口,而是给你一个模糊需求,你能不能通过提问、拆解、设计把它落地成可执行的技术方案。做苍穹外卖这类完整项目,正好可以练这一层。
5.2 Git 协作与 Code Review
之前自己写小项目都是单分支开发,写完直接提交master,但苍穹外卖这种多人协作让我第一次系统接触Git分支管理。切分支是很基本但很重要的协作习惯:开发新功能时从主分支拉出功能分支,功能开发完再合并回去。我在合并时遇到的问题主要是冲突,因为两个人都改了同一个文件,最典型的是同时修改了接口文档相关代码,后面用“重新拉取最新代码并解决冲突再提交”的方式解决。最怕的不是冲突本身,而是冲突时由于不了解对方改动内容而乱取舍,把别人的功能改坏了。
代码评审是比Git分支更容易被忽视的学习机会。项目做完后,我跟A同学彼此做了一轮代码Review,发现了很多自己看不到的问题:比如有些方法命名不清,变量名用了拼音;比如有些代码逻辑存在重复,两个Service都写了同样的菜品缓存逻辑;还有一段逻辑处理存在潜在空指针风险,业务对象从缓存取出来没有判空就直接调用。这些东西,自己闭门造车很难发现,一旦养成了Review习惯,写出的代码质量会明显提升。
5.3 日志与排查问题的思路
排查项目Bug是每天都做的事,但“怎么排查”是有方法论可循的。第一次遇到订单状态异常问题时,我几乎是盲猜式排查,一会儿怀疑缓存,一会儿怀疑数据库,最后才发现是回调幂等处理没写好。后来我总结出一条相对固定的排查路径:查入口日志确认请求参数 -> 查业务日志确认逻辑分支 -> 查SQL日志确认数据库操作 -> 查缓存状态确认读写是否正常。
日志打印本身也有讲究,不能随便打印,也不能完全不打印。我通常要求自己在有判断逻辑的关键节点打印必要信息,比如登录校验结果、订单状态流转前后值、支付回调接收结果、远程接口耗时。最好带上订单号或用户标识,这样出了问题能按标识直接检索日志。项目中还加了简单的切面日志,记录每个接口的访问耗时,这样可以找出哪些接口响应慢,再针对性优化。
Debug能力是区分“会写代码”和“能上生产”的重要标准之一。真正排查线上问题时,不能依赖单步调试,必须在日志和监控层面下功夫。做苍穹外卖项目,虽然规模还没到一线大厂那种级别,但能完整地走一遍“从现象到定位到修复”的流程,这个思维迁移到任何项目都适用。
6. 常见问题与避坑速查表
| 问题现象 | 可能原因 | 处理方向 |
|---|---|---|
| 用户A看到用户B的购物车数据 | ThreadLocal未清理,线程池复用 | 使用完调用remove,最好配合Filter统一清理 |
| 缓存中的菜品数据与数据库不一致 | 更新数据库后直接更新缓存 | 改为更新后删除缓存,必要时用延迟双删 |
| 用户支付成功但订单仍显示待支付 | 回调延迟或回调处理失败 | 增加定时任务补偿扫描,幂等处理回调 |
| 商家端收不到新订单提醒 | WebSocket未连接成功或连接串线 | 按身份区分连接通道,动态重连,消息发送前确认连接状态 |
| 库存扣成负数 | 并发请求下先查后扣 | 使用乐乐观锁条件更新,影响行数为0时提示失败 |
| 定时任务在集群环境下重复执行 | 每台实例都触发任务 | 引入分布式锁,或使用任务调度框架 |
| 分页查询越到后面越慢 | 深翻页导致大量无效扫描 | 基于索引延迟关联或游标分页 |
| 查询带函数导致索引失效 | 对索引字段做了函数处理 | 改成范围查询,避免对字段做计算 |
| 文件图片部署后丢失 | 图片存在本地磁盘 | 改用对象存储服务保存文件URL |
| 前端拿到的接口返回格式不统一 | 没有统一返回体,异常也没有全局处理 | 自定义通用返回结果和全局异常处理器 |
这张表格里的问题大多是从实际开发和联调中遇到的,有一部分是项目里复现过的,有一部分是我在真实项目中见过但提前在这个项目里做了预防。建议在开发过程中也建一份属于自己的“避坑清单”,不要把所有经验都留在脑袋里,写下来才是真正沉淀。
做完苍穹外卖项目,我个人最大的体会是:技术栈再多,如果没串成一条业务线,学完也很容易忘;反过来,一个完整业务哪怕用的都是基础组件,也能逼你理解分布式、缓存、并发、前后端协作这些关键问题。之前写单模块小项目时,我的关注点永远在“这个语法怎么写”“这个接口怎么调”,做完这个项目之后,我开始下意识地去想“这个数据为什么这样设计”“这个接口拆得合不合理”“线上出了问题怎么排查”。这种思维转变,可能就是整个项目最值钱的部分。
最后再分享一个小技巧。如果想在简历上把这类项目写得更出彩,可以设计几个实际的性能对比实验,比如在菜品列表接口压测时,对比接入Redis前后的QPS和响应时间;在订单列表查询对比索引优化前后的耗时。把实验数据写进项目总结,比单纯罗列技术栈要有说服力得多。这套苍穹外卖项目后续还可以继续扩展的方向也不少,比如引入Spring Cloud Alibaba微服务化、用容器化部署、加上JMeter压测报告,每一步都能让项目从“课程Demo”往“工业级系统”再靠一步。