Skip to content

苍穹外卖项目复盘引导(对话记录)

日期:2026-03-28
项目路径:D:\project\backend\sky-take-out

目标:产出一份能讲清楚“我做了什么、怎么做的、为什么这么做、遇到什么坑、下次怎么改”的项目总结(可用于面试讲项目,也可作为复盘文档)。

1) 项目一句话 + 边界

  1. 这个“苍穹外卖”你实现的范围是哪些端:管理端、用户端、小程序/APP、还是仅后端API?
  2. 你覆盖的核心业务链路有哪些:下单-支付-接单-配送-完成,还是到“模拟支付/未接第三方”为止?
  3. 哪些你明确没做,但你知道该怎么做(写出来也很加分)?

2) 功能清单(按业务域讲)

按模块写“你做了哪些功能点”,不要按 Controller 罗列。先列出你做过的模块,例如:

  • 用户与认证:登录方式、JWT/Session、权限(RBAC?)
  • 商品与分类:SKU/规格、上架下架、口味
  • 购物车与下单:幂等、库存处理(有没有做)
  • 订单:状态机流转、催单/取消/退款(是否有)
  • 支付:对接与否、回调验签、重复回调处理(是否有)
  • 配送/地址:地址簿、距离/配送费(是否有)
  • 运营:营业状态、店铺信息、统计报表(是否有)
  • 消息与通知:WebSocket/短信/邮件(是否有)
  • 文件:图片上传(本地/OSS)

3) 技术架构(把“为什么这样选”讲出来)

按“选型理由 + 你用到的点”来回答:

  1. 技术栈:Spring Boot 版本、MyBatis/MyBatis-Plus、Redis、MQ(有无)、WebSocket(有无)、对象存储(有无)。
  2. 分层与结构:Controller/Service/Mapper 是否清晰?DTO/VO/PO 如何划分?
  3. 关键中间件你具体怎么用的:
    • Redis:缓存哪些数据?怎么设计 key?怎么处理过期/一致性?
    • 登录鉴权:JWT 里放什么?如何续期/踢人(有无)?
    • 事务:哪些场景必须事务?有没有遇到事务失效/传播行为问题?
  4. 异常与统一返回:有没有全局异常处理、统一响应体、参数校验?

4) 数据库设计(体现工程能力的重点)

  1. 你最满意的 3 张表(或 1 个业务域)是什么?为什么(范式/可扩展/索引)?
  2. 你加过哪些索引?解决了什么慢查询?(哪怕是“我会怎么加”也行)
  3. 订单状态是怎么建模的(枚举/状态表/状态流转校验)?

5) 难点与坑(面试最爱听)

请列 3-5 个“真实遇到的问题”,按模板回答:

  • 现象:发生了什么
  • 定位:怎么排查(日志/断点/SQL/Redis 监控)
  • 根因:真正原因
  • 解决:怎么修
  • 预防:下次如何避免(规范/测试/监控/重构)

6) 质量与交付(像“工程师”而不是“只写功能的人”)

  1. 测试:单测/集成测试有没有?没有的话,会给哪个模块补,怎么补?
  2. 日志:用了什么规范(traceId/订单号)?是否做过关键链路日志?
  3. 文档:Swagger/OpenAPI、数据库文档、部署文档是否有?
  4. 部署:怎么跑起来的(Jar、Docker、Nginx、Windows/Linux)?配置如何管理(profile、Nacos/本地)?

7) 下一步升级(把练手变成“可展示作品”)

先选 2-3 个最想补强的方向(后续再展开实现):

  • 安全:权限模型、接口防刷、脱敏、验签
  • 性能:缓存策略、热点 key、分页优化、批量查询、异步化
  • 稳定性:幂等、重试、限流、熔断、降级
  • 可观测性:链路追踪、指标、告警
  • 架构:模块化、DDD/领域划分、事件驱动(MQ)

下一步(按对话约定):先回答“第 1 部分(一句话 + 边界)”的 3 个问题,然后把做过的模块(第 2 部分)列出来,再继续深挖关键细节并整理成可放简历的成稿。

对话记录:你的回答(第 1 部分)

  1. 实现范围:管理端、用户端(小程序)以及后端 API;你主要负责后端 API 的开发。
  2. 核心业务链路:下单、支付、接单、配送、完成;实现了订单状态机流转。支付由于无商家资质,采用“模拟微信支付预支付响应 + 手动回调”的方式完成闭环验证。
  3. 明确没做但有思路:基于 AOP/注解的状态校验与权限控制。
    • 设想:在需要状态检验的类/方法上加注解,注解携带操作类型(接单/派送/完成等)。
    • 切面拦截:从注解拿到操作类型,从方法参数拿到订单 ID,查询数据库获取订单状态。
    • 按操作类型判断状态是否合法:不合法则抛异常拦截,合法则放行。

对话记录:你的回答(并发/幂等/模块)

并发下的状态流转

  • 未做“带旧状态条件的更新”(如 update ... where id=? and status=?),目前是复用一个“按订单实体对象更新”的接口,仅校验了订单 ID,因此不能保证并发下不串状态。
  • 如果需要增强并发安全性,你认为可以引入 Redis 分布式锁并使用 UUID 作为锁值,但这部分尚未系统学习与落地。

支付回调幂等

  • 幂等策略:更新前查询订单状态是否为“已支付”(避免重复更新)。
  • 风险认知:大并发下仍可能存在竞态,但你认为正常用户短时间重复支付的概率较低。

已实现的业务模块(后端 API)

  • 登录鉴权:小程序端、管理端。
  • 店铺营业状态。
  • 文件上传。
  • 管理端:员工管理(未做权限划分)、分类管理、菜品管理、套餐管理、订单管理、数据统计、工作台、来单提醒。
  • 小程序端:下单、购物车管理、地址管理、催单等。

对话记录:我后续追问(待你补充回答)

为把项目总结写成“工程化口径”,需要补齐以下关键信息点:

  1. 登录鉴权:
    • 你用的是 JWT 还是 Session?
    • JWT 里放了哪些字段(例如用户 id/员工 id/角色)?
    • 有没有做 token 续期/黑名单/踢人?
  2. 来单提醒与催单:
    • 用的是什么实现(WebSocket/轮询/长轮询)?
    • 后端触发推送的时机是什么(如下单成功/支付成功/接单等)?
  3. 文件上传:
    • 存本地磁盘还是对象存储(OSS/MinIO)?
    • 访问 URL 怎么生成?
    • 路径/文件名如何防冲突(UUID/按日期分桶等)?

你的回答(鉴权 / WebSocket / OSS)

  1. 登录鉴权:
    • 使用 JWT。
    • JWT 中放员工/用户 id;解析后放入线程上下文(ThreadLocal)以便在同一次请求中获取当前登录人信息;通过会话(token)实现隔离。
    • 未做 token 续期;过期后需重新登录;未做黑名单机制。
  2. 来单提醒、催单:
    • 使用 WebSocket。
    • 支付成功后,将订单信息推送给商家(管理端)。
  3. 文件上传:
    • 使用阿里云 OSS。
    • 访问 URL 通过拼接生成。
    • 存储路径/文件名格式:yyyy/MM/UUID.原后缀,通过 UUID 防止文件名冲突。
    • 你也了解分布式可用雪花算法生成唯一 ID(在服务器时钟不回拨的前提下),ID 由符号位/时间戳/机器 ID/序列号组成。

下一轮补充问题(用于写“技术亮点/难点”段落)

  1. 订单状态机:
    • 订单状态有哪些(枚举值列表)?每个状态允许哪些操作(接单/派送/完成/取消/拒单等)?
    • 目前状态校验是怎么做的(写在 Service 里 if-else 还是集中校验方法)?
    • 如果让你升级并发安全:你更倾向“条件更新 where status=旧值”还是“version 乐观锁”?你会改哪几处接口?
  2. 模拟支付与回调:
    • 你模拟“预支付响应”返回了哪些字段(如 prepayId/二维码链接/支付单号等)?
    • 手动回调接口长什么样(请求体字段、签名有没有模拟)?
    • 如果升级幂等:你会选“条件更新 + 回调记录表唯一约束”还是“redis 去重 key”,为什么?
  3. 数据库设计与性能:
    • 你最满意的 3 张表是哪 3 张?各自的关键字段/索引是什么?
    • 有没有遇到过慢 SQL 或 N+1 查询?如果有,怎么优化;如果没有,你会优先检查哪些接口的 SQL?
  4. 工程质量:
    • 全局异常处理与统一返回是否有?校验(JSR-303)是否有?
    • 日志有没有记录关键链路字段(如 userId、orderId、traceId)?
    • 有没有写过单测/集成测试?如果没有,你计划补哪 2 个用例(最关键的)?

你的回答(订单状态机 / 并发升级 / 模拟支付回调)

  1. 订单状态枚举:待支付、待接单、待派送、派送中、待完成、已取消。
  2. 不同端允许操作不同(示例:接单、派送、完成、取消、拒单):
    • 管理端:
      • 待支付:不可操作;有定时任务,未支付超过 15 分钟自动取消。
      • 待接单:可接单、可拒单。
      • 待派送:可派送;也可取消。
      • 派送中:可取消。
      • 待完成:可完成;也可取消。
      • 已取消:不可操作。
    • 用户端:
      • 待支付:可支付;支付完成后可取消、可催单。
      • 已接单:如需取消,需要联系商家(用户端不直接发起取消)。
  3. 状态校验实现:当前是在 Service 层用 if-else 做状态判断。
  4. 并发安全升级思路:条件更新(where status=旧值)与乐观锁(version)结合使用;优先改造支付、退款等资金相关接口,其次再逐步改造状态机流转。
  5. 模拟预支付响应:返回 prepayId、签名等(你认为后端返回内容本身不关键);小程序端不论是否返回预支付数据,都会视为“支付成功”。
  6. 回调与内网穿透:通过 frp 实现内网穿透完成回调链路验证。
  7. 手动回调请求体:订单号、微信端订单号、交易状态(成功/失败);本地接收请求后会解密。
  8. 幂等升级偏好:选择“条件更新 + 回调记录表唯一约束”;认为 Redis 去重 key 性能高但维护成本大、易出问题。

你的回答(数据库设计 / 性能 / 工程质量)

  1. 你认为设计较好的 3 张表:
    • 订单明细表:
      • 原因:一个订单包含 n 个菜品/套餐;订单表还要承载其他重要数据,因此抽离订单明细表便于管理与扩展。
      • 关键字段:逻辑外键 order_iddish_id / setmeal_id(二选一,指向菜品或套餐)。
    • 菜品口味表:
      • 原因:口味属于菜品的可扩展属性,单独建表更易维护。
      • 关键字段:逻辑外键 dish_id
    • 分类表:
      • 原因:通过 type 字段同时管理菜品与套餐的分类归属。
      • 关键字段:你暂时不确定(可能是业务状态/启用禁用等字段)。
  2. 性能与查询:
    • 遇到过 N+1 查询:主要出现在“返回复杂对象(成员含 list)”的接口。
    • 处理方式:用两条 SQL 分步查询(先查主表/ids,再查明细)并在内存组装。
    • 慢查询:目前缺少系统化定位与优化经验。
  3. 工程质量:
    • 有全局异常处理:@RestControllerAdvice
    • 统一返回体:Result(code, msg, data)
    • 参数校验(JSR-303):目前不熟悉/未系统使用。
    • 日志:目前主要做了入参记录。
    • 测试:未写;计划优先补“订单支付、退款”等资金相关接口测试。

对话记录:补充追问(ORM / 分页 / 定时任务)

我的问题(待补充的信息点)

  1. 你用的 ORM 是 MyBatis 还是 MyBatis-Plus?分页怎么做的?
  2. 定时任务用的是 Spring @Scheduled 还是 XXL-Job/Quartz?订单超时取消的实现细节是“查出超时订单批量更新”还是“逐单更新”?

你的回答

  1. ORM:MyBatis;分页:PageHelper。
  2. 定时任务:SpringTask 的 @Scheduled;订单超时取消:先查出所有超时订单,再批量取消。

对话记录:评判与改进建议(基于你的回答)

目标:把“能跑通的练手项目”升级成“工程化可信、抗追问”的项目复盘口径。

你做得好的点(建议保留并强化)

  1. 核心链路完整:下单到完成,包含状态机、定时取消、WebSocket 来单提醒,叙事主线清晰。
  2. 支付闭环意识强:无商家资质仍用“模拟预支付 + 回调”跑通流程,具备系统思维(需讲清模拟范围与真实差异)。
  3. 表设计抽离合理:订单明细、菜品口味独立建表,符合一对多与可扩展属性建模。

需要改进的关键问题(面试高频追问点)

  1. 并发与幂等不能用“概率很小/用户不可能”来解释:
    • 真实环境存在重试、重复回调、网络抖动、网关重放等情况,幂等必须依赖数据层约束。
  2. 状态机“更新接口只校验 ID”属于结构性缺陷:
    • 定时取消、商家接单、支付回调并发时可能导致状态覆盖/乱跳,需要用条件更新/乐观锁兜底。
  3. 小程序端“无论是否返回预支付都算成功”的表述风险大:
    • 更工程化的口径:支付成功以回调或支付结果查询为准;若为 demo 权衡需明确指出并给出上线改造方式。
  4. JWT + ThreadLocal 的表述需要更严谨:
    • ThreadLocal 是“请求线程上下文”,不是“会话隔离”;会话隔离来自 token 与鉴权逻辑。
    • 管理端未做权限划分意味着“鉴权有身份、缺授权”,应作为待完善项写清楚。
  5. JSR-303、日志、测试要给出明确补齐路径:
    • 目前“不了解/只入参日志/未写测试”偏弱,需要补“怎么做、优先补哪里、为什么”。

推荐的工程化升级方案(可写入复盘的改进计划)

  1. 状态机并发安全(优先级最高):
    • 关键写操作改为条件更新(示例:接单 update ... set status=待派送 where id=? and status=待接单)。
    • 支付/退款等资金相关接口在条件更新基础上加 version 乐观锁;定时取消也必须带 status=待支付 条件。
    • Redis 分布式锁不作为首选解法,可作为补充(例如跨资源一致性场景),优先用数据库约束。
  2. 支付回调强幂等:
    • 引入“支付回调记录表”,以外部支付单号或(订单号 + 回调类型)做唯一约束。
    • 订单状态推进使用条件更新,确保只成功一次;避免“先查再改”的竞态窗口。
  3. WebSocket 推送一致性:
    • 推送应在订单状态变更成功后触发;更稳妥做法是事务提交后再推送,避免推送不存在/回滚的状态。
  4. N+1 查询治理:
    • 你当前“两段查询 + 组装”方向正确;可进一步强调“批量查询 + Map 聚合组装”,控制 SQL 次数与返回量。
  5. 参数校验与统一错误码:
    • 引入 JSR-303(@Valid/@Validated + @NotNull 等),结合 @RestControllerAdvice 统一输出业务错误码与提示。
  6. 测试优先级:
    • 优先补:支付回调幂等、状态条件更新失败分支、超时取消与支付并发竞态等集成测试用例。