
前阵子帮一个做社区零售的朋友搭建后台聊着聊着发现很多刚接触 SpringBoot 的毕业生或者说想转后台开发的同学对商品管理后台系统这个题目的理解还停留在CRUD 四张表的层面。实际上一个能真正跑在零售业务里的后台运营平台远比想象中复杂库存怎么扣才不出负数、订单状态怎么流转才不混乱、商品上下架和 SKU 怎么关联这些才是毕业设计答辩时真正能拿得出手的亮点。这篇就基于我实际开发 SpringBoot 零售商品后台运营平台的经验把商品、库存、订单中心这三条主线的设计与实现完整拆开聊一聊适合正在做 SpringBoot 毕设、或者想系统理解电商后台核心模块的读者参考。1. 项目背景与需求分析零售后台到底要解决什么问题1.1 零售后台的业务痛点拆解很多同学拿到商品管理后台系统这个题目时第一反应是做个页面增删改查就完事了。但如果你真的去问一个开超市或者做电商运营的人他们最头疼的往往是这几件事商品信息散落在 Excel 里谁改了什么没人知道库存数量月底对不上账既不知道什么时候该进货也不知道哪些商品卖不动该下架订单全靠人工记发货状态、退款状态全靠脑子硬记一忙就乱。所以这个系统的价值本质上是把这三件事从人肉管理变成系统化运营。我在设计时把核心需求拆成了三条主线商品生命周期管理从录入、上架到下架、库存实时变动追踪入库、出库、预警、订单状态的闭环流转下单、支付确认、发货、完成。这三个模块看似独立实际上是一条完整的数据链路商品产生库存库存被订单消耗订单反过来又驱动库存变化。1.2 功能模块划分与优先级排序做后台系统最容易犯的错误是一上来就想把所有功能都做全结果连基础的主流程都没跑通。我当时的做法是先用 80% 的精力保证主链路的完整可用剩下 20% 再去做锦上添花的功能。优先级功能模块核心内容说明P0商品管理商品分类、SPU/SKU、上下架所有业务的基础必须先做扎实P0库存管理入库、出库、库存查询、预警与商品强关联是订单的支撑P0订单管理订单创建、状态流转、列表筛选前台交易的最终落点P1用户与权限管理员登录、角色权限区分后台系统的安全底线P1数据统计商品销量排行、库存周转提醒给运营决策提供依据P2日志审计操作留痕、登录日志有更好没有也能交代过去这个优先级排序背后有个很实际的理由毕设答辩或者项目演示时考官最关心的是你的系统能不能支撑一个完整的业务流程。如果订单创建完库存没减或者商品下架了订单还能继续买那功能再多也白搭。先把主链路打通再补权限和统计整个项目的完成度和说服力会高很多。2. 技术选型与架构设计为什么是 SpringBoot 这套组合2.1 技术栈的选型依据SpringBoot 能成为 Java 后端开发的事实标准不是没有原因的。对于这种后台管理类系统它最舒服的点在于约定优于配置不需要像传统 SSM 那样写一大堆 XML 配置一个SpringBootApplication注解跑起来内嵌 Tomcat一个 jar 包就能部署。我当时选用这套组合具体是这么搭配的。核心框架SpringBoot 2.x Spring MVC负责 RESTful API 接口和请求分发持久层MyBatis-Plus配合它的BaseMapper可以省掉大量单表 CRUD 的重复代码数据库MySQL 8.x存储业务数据InnoDB 引擎保证事务前端Vue Element UI前后端分离方便演示时快速调整页面认证方案JWT 做无状态登录接口签名校验避免越权访问为什么不用 MyBatis 原生版不是说它不好而是对于商品、库存、订单这种大量单表操作、少量复杂联表查询的场景MyBatis-Plus 的LambdaQueryWrapper写起来实在太顺手了。比如筛选状态为上架且库存小于预警值的商品一行条件构造器就搞定不用在 XML 里手写动态 SQL。2.2 后端工程结构与分层思路项目的包结构我习惯按业务域划分而不是按技术层划分。这样做的最大好处是当你新增一个商品批量导入功能时你能很清楚地知道该改哪几个类而不是把 controller/service/mapper 各层的同名包翻个底朝天。com.retail.platform ├── controller // 接收请求、参数校验、返回统一响应 │ ├── GoodsController │ ├── StockController │ └── OrderController ├── service // 业务逻辑层事务边界在这里 │ ├── GoodsService │ ├── StockService │ └── OrderService ├── mapper // 数据访问层继承 BaseMapper ├── entity // 数据库实体映射 ├── dto // 入参出参对象避免实体直接暴露 ├── common // 统一返回结构、异常处理、工具类 └── config // 配置类如拦截器、跨域、Knife4j这里想特别说一下dto这层。很多初学者喜欢把Goods实体直接当作接口参数和返回值传来传去图省事。但实际开发中你会发现前端传来的参数往往比实体字段少比如只需要商品名、价格、库存返回给前端的数据又往往比实体字段多比如要带商品分类名称、库存状态描述。如果始终用实体传最后每个接口的参数校验都写得极其别扭还容易暴露数据库表的敏感字段。这也是我踩过坑之后才体会到的事。3. 数据库建模库存与订单的数据闭环设计3.1 核心数据表结构设计数据库设计是整个零售后台系统的地基地基歪了后面写多少代码都别扭。我按照业务链路拆出了这几张核心表。首先是goods商品表这张表承载商品的基础信息CREATE TABLE goods ( id BIGINT PRIMARY KEY AUTO_INCREMENT, goods_name VARCHAR(128) NOT NULL COMMENT 商品名称, category_id BIGINT NOT NULL COMMENT 分类ID, goods_sn VARCHAR(64) NOT NULL COMMENT 商品编码唯一, price DECIMAL(10,2) NOT NULL COMMENT 销售单价, status TINYINT NOT NULL DEFAULT 0 COMMENT 0-下架 1-上架, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_goods_sn (goods_sn) ) COMMENT商品基础信息表;然后是stock库存表这里我选择了把库存单独拆出来而不是塞进商品表。原因是商品信息是相对静态的改个名字、换个价格库存是高频变动的每次订单产生、每次入库都要动两者混在一起会导致同一行数据频繁更新锁竞争变严重查询商品列表时也会因为无关的库存更新拖慢速度。CREATE TABLE stock ( id BIGINT PRIMARY KEY AUTO_INCREMENT, goods_id BIGINT NOT NULL COMMENT 商品ID, quantity INT NOT NULL DEFAULT 0 COMMENT 当前库存数量, locked_quantity INT NOT NULL DEFAULT 0 COMMENT 锁定库存数量, warning_line INT NOT NULL DEFAULT 10 COMMENT 库存预警线, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_goods_id (goods_id) ) COMMENT库存表;至于订单表orders我在命名和设计上特别注意了订单不是买了就完它需要记录完整的流转状态。我在表中加了一个order_status字段用整型枚举来标识待支付、已支付待发货、已发货、已完成、已取消、售后中。每个状态背后都有明确的业务含义也有对应的操作触发条件。3.2 库存流水表追踪每一件商品的来龙去脉只设计一张库存表其实是不够的因为你只知道现在库存是5但说不清这5是怎么来的。所以我还加了一张stock_flow库存流水表。CREATE TABLE stock_flow ( id BIGINT PRIMARY KEY AUTO_INCREMENT, goods_id BIGINT NOT NULL COMMENT 商品ID, change_type TINYINT NOT NULL COMMENT 1-入库 2-销售出库 3-订单取消退回 4-盘点调整, change_quantity INT NOT NULL COMMENT 变动数量(正负), before_quantity INT NOT NULL COMMENT 变动前库存, after_quantity INT NOT NULL COMMENT 变动后库存, order_no VARCHAR(64) DEFAULT NULL COMMENT 关联订单号, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_goods_id (goods_id) ) COMMENT库存流水表;这张表的价值在系统上线后会体现得淋漓尽致。运营问为什么这个商品库存对不上的时候不再需要一个个查日志直接按商品ID查流水每笔变动的时间、类型、关联订单全部清清楚楚。这也是毕设答辩时可以重点讲的设计亮点——大部分学生的系统里根本没有流水表。我还在库存表上设计了一个锁定库存的思路用户下单但还没支付时先把库存锁定支付成功后才真正扣减支付超时则释放锁定。这个逻辑直接避免了超卖问题后面在并发部分会详细说。4. 核心功能实现商品、库存、订单三模块的落地细节4.1 商品管理上下架与分类的联动处理商品管理听起来是纯 CRUD但实际做的时候有细节门槛。第一个细节是商品编码goods_sn的唯一性校验。这个编码是给运营人员识别的编号一旦重复后面的库存单据、订单报表全都会串数据。我在接口层做了一件事插入时先查一次是否存在更新时排除自身判断。// 更新商品时校验编码唯一性 LambdaQueryWrapperGoods wrapper new LambdaQueryWrapper(); wrapper.eq(Goods::getGoodsSn, goods.getGoodsSn()) .ne(Goods::getId, goods.getId()); if (goodsMapper.selectCount(wrapper) 0) { throw new ServiceException(商品编码已存在请检查后重试); }第二个细节是上下架联动库存判断。直接给一个上架按钮就行了吗不行库存为零的商品上架没有任何意义只会带来无效订单。所以我在GoodsService的上架方法里做了这样的判断查询库存数量如果当前库存加锁定库存小于等于0直接拒绝上架并明确提示运营人员先补货。第三个细节是分类的层级处理。零售商品分类经常是两级甚至三级的比如食品下面有休闲零食休闲零食下面还有饼干。如果直接存一个category_id那点进某个叶子分类时想展示它上级分类的信息就得反复查表。我的处理方式是冗余一个category_path字段格式类似1,5,23查询时直接IN查询该路径前缀的分类一次搞定整条分类链。这种用空间换时间的思路在后台列表页会显著降低查询复杂度。4.2 库存管理MySQL 事务与分布式锁的取舍库存这块是整个系统里最容易翻车的地方我强烈建议毕设里把并发扣减这个场景做出来绝对是加分项。最原始的做法是先查库存再扣减伪代码如下Integer quantity stockMapper.getQuantity(goodsId); if (quantity 订单数量) { stockMapper.deduct(goodsId, 订单数量); }这个代码在单人操作时没问题一旦有两个用户同时下单都读到了库存10然后各自扣减5最终库存变成5而不是0这就产生了超卖。解决思路有两种。乐观锁方案在库存表里加一个version字段扣减时带上版本号条件悲观锁方案查询时使用SELECT ... FOR UPDATE显式加行锁我实际用的是悲观锁加事务的方式因为在这个场景里库存行锁冲突的几率本身不高FOR UPDATE 简单直接、可读性强。Transactional(rollbackFor Exception.class) public void deductStock(Long goodsId, Integer count) { Stock stock stockMapper.selectForUpdate(goodsId); if (stock null) { throw new ServiceException(商品库存记录不存在); } if (stock.getQuantity() - count 0) { throw new ServiceException(库存不足当前库存为 stock.getQuantity()); } stock.setQuantity(stock.getQuantity() - count); stockMapper.updateById(stock); // 同时写入库存流水 stockFlowService.record(goodsId, 2, -count, 原库存, 新库存, 订单号); }这里我特意强调Transactional必须指定rollbackFor Exception.class因为 Spring 默认只对 RuntimeException 回滚如果业务代码里 throws 了一个自定义的受检异常事务是不会自动回滚的这点不知道坑了多少人。另外要说的是在毕设这种单机单体架构下悲观锁FOR UPDATE已经足够应对模拟并发扣库存的演示场景了。如果想去讲分布式锁Redis 分布式锁或者 Zookeeper 锁那属于进阶内容需要换分布式环境去讲作为毕设主线反而容易讲得太散。把事务 行锁这套逻辑理清楚再把库存流水补完整已经是一个很扎实的设计。4.3 库存预警与定时补货逻辑库存预警是运营平台里特别能体现系统智能感的功能。实现思路并不复杂在库存表里加一个warning_line预警线然后定时任务去扫描所有库存量低于预警线的商品。我实现时用了 Spring Boot 自带的Scheduled注解配置了一个 cron 表达式每天早上九点跑一次Component public class StockWarningTask { Scheduled(cron 0 0 9 * * ?) public void checkStockWarning() { ListGoodsStockVO warningGoods stockMapper.selectWarningList(); if (CollectionUtils.isEmpty(warningGoods)) { return; } // 组装预警消息发送到管理员的待办通知 warningGoods.forEach(item - { System.out.println(商品[ item.getGoodsName() ]库存不足当前剩余 item.getQuantity()); // 实际项目中这里可以短信/站内信提醒 }); } }这段代码看起来简单但我要补一句非常重要的细节定时任务是跑在应用进程里的如果你的后端是多实例部署比如 Docker 起了两个容器同一个任务会被触发两次。毕设环境单实例没问题但如果以后做企业项目就得考虑使用 XXL-Job 这类分布式任务调度或者借助数据库锁表让同一时间只有一个实例真正执行。这个点可以作为拓展思路在答辩时提一句显得你有全局视角。4.4 订单管理状态流转的闭环控制订单模块是整条业务链路的消费端也是状态最复杂的模块。我设计订单状态机的核心思想是状态只能按预先定义的路径流转不能随意跳转。具体状态枚举定义如下状态值含义触发操作0待支付用户提交订单1已支付待发货用户支付成功回调2已发货商家后台点击发货3已完成用户确认收货或超时自动完成4已取消用户取消或超时未支付自动关闭5售后中用户发起售后申请代码层面我在OrderService里写了一个transition方法专门校验状态迁移的合法性private static final MapInteger, SetInteger STATUS_FLOW new HashMap(); static { STATUS_FLOW.put(0, Set.of(1, 4)); // 待支付 - 已支付或取消 STATUS_FLOW.put(1, Set.of(2, 4)); // 已支付 - 发货或退款关闭 STATUS_FLOW.put(2, Set.of(3, 5)); // 已发货 - 完成或进入售后 }这个方法接收当前状态和目标状态如果目标状态不在允许的集合里直接抛异常。它最大的作用不是防用户而是防开发自己脑子混乱——将来接手这个项目的人改代码时看到这张状态流转表一眼就明白每个状态和边界条件是怎么约定的不再需要靠猜。订单创建时还有一件事不能漏必须关联写入库存流水。也就是前面代码里stockFlowService.record(...)那一行。没有这一步库存数据就是一笔糊涂账没法对账。我在这个模块里特意把订单号作为流水表的关联字段存了下来以后想查某个订单影响过哪些库存就变得非常容易。5. 前后端联调与部署让系统真正能跑起来5.1 前端路由与权限控制前端我选择了 Vue Element UI主要看中它背靠成熟的组件生态表格、表单、弹窗这些后台管理系统的高频组件都能快速搭建。页面结构上我按左侧菜单 顶部面包屑 右侧内容区做了经典的后台布局菜单项与后端接口的权限做了绑定。权限这块如果只做登录后才能看是不够的。我设计了sys_user、sys_role、sys_menu三张表管理员账号可以给不同角色分配不同菜单权限。操作员只能看商品和订单财务角色还能看营收统计这是典型的 RBAC 模型。前端在路由守卫里读取用户拥有的菜单权限没有权限的菜单直接不渲染后端接口也用拦截器做了二次校验。// 前端路由守卫示例 router.beforeEach((to, from, next) { const token localStorage.getItem(token); if (!token) { next(/login); } else { // 校验路由meta上声明的权限是否在当前用户权限列表中 const perms store.state.user.permissions; if (to.meta.permission !perms.includes(to.meta.permission)) { next(/403); } else { next(); } } });这里要特别强调一个原则前端的权限控制只是用户体验层面的优化真正的安全校验永远要放在后端。前端隐藏了某个按钮不代表用户不能通过 Postman 直接调接口所以后端每个涉及数据变动的接口都必须校验当前登录用户的角色权限。我在拦截器里统一处理了 JWT 的解析和权限判断而不是在每个 Controller 里复制粘贴校验代码。5.2 容易忽略的全局跨域与统一响应前后端分离后第一个遇到的大坑就是跨域。前端跑在 8080 端口后端跑在 8081 端口浏览器默认禁止跨源请求。我直接在 SpringBoot 配置类里写了一个WebMvcConfigurer配置允许的来源、请求头和请求方法。Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }注意我用的是allowedOriginPatterns(*)而不是allowedOrigins(*)。原因在于allowCredentials(true)开启后Spring 不允许使用通配符配置allowedOrigins但允许使用allowedOriginPatterns做模式匹配。这个细节在低版本 SpringBoot 2.4 以下还不存在升级版本之后就会在这里卡一下属于典型的版本升级踩坑。统一响应结构也是我在项目一开始就定好的。所有接口都返回{ code, message, data }三段式 JSON业务异常和系统异常分别返回不同的 code。前端接手时非常省心拿到 code 不是 200 就直接弹 message再不会出现后端返回了一坨奇怪格式的报错前端没法统一解析的情况。5.3 Docker 部署实践毕设演示时最怕的就是在我电脑上能跑换台电脑就跑不起来。为了解决环境一致性问题我最终把系统用 Docker 容器化部署了。我的部署规划是三个容器MySQL 数据容器、后端应用容器、前端 Nginx 容器。用了 Docker Compose 编排一条docker-compose up -d就能启动全部服务。version: 3 services: mysql: image: mysql:8.0 container_name: retail-mysql environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: retail_platform ports: - 3306:3306 volumes: - ./mysql-data:/var/lib/mysql restart: always backend: build: ./backend container_name: retail-backend depends_on: - mysql ports: - 8081:8081 restart: always frontend: build: ./frontend container_name: retail-frontend depends_on: - backend ports: - 80:80 restart: always在制作后端 Dockerfile 时我给项目里的application.yml增加了多环境配置本地用dev配置连接本机 MySQL生产用prod配置连接容器内部的 MySQL。需要注意的地方是容器间通信不能用localhost而要用 Compose 里的服务名比如后端连接数据库的地址写的是jdbc:mysql://mysql:3306/retail_platform这里的mysql就是 Compose 里的服务名。Docker 部署这件事放到毕设里绝对是一个亮点。答辩的时候老师问系统是怎么交付的你直接说打包成镜像目标服务器只需装一个 Docker 就能跑起来体感上完全不一样。6. 实操中的性能优化与并发避坑6.1 分页查询的性能细节商品列表页和订单列表页的分页查询是整个系统里调用频率最高的接口我注意到很多基础写法在这种接口上容易忽略性能细节。比如一次性select *查出所有字段再丢弃不用或者列表页关联查询出每件商品的库存却又在循环里去单查数据库。我这里做了两个优化。第一个是列表页的字段裁剪只查询列表需要的字段不查那些大文本字段比如商品详情 description等用户点开详情再读完整数据能显著减少 MySQL 的 I/O 开销。第二个是避免 N1 查询商品列表需要展示商品分类名称和库存数量时我先批量查出当前页商品ID列表再一次性按这些 ID 查询分类和库存然后在内存里组装数据。用一次联表查询替代了对每件商品各查一次数据库的做法。// 批量查询库存避免逐条查询的 N1 问题 ListLong goodsIds goodsList.stream().map(Goods::getId).toList(); ListStock stockList stockMapper.selectBatchIds(goodsIds); MapLong, Integer stockMap stockList.stream() .collect(Collectors.toMap(Stock::getGoodsId, Stock::getQuantity)); // 遍历 goodsList 时直接 stockMap.get(goodsId) 拿库存这种批量查询 内存组装的思路是后端开发里非常高频且基础的优化手段。你把这个点写进代码注释里面试官问你做过什么优化时就可以直接举它。另一个关于分页的坑是 MySQL 深分页问题偏移量越大limit 100000, 10这样的查询会越来越慢。后台系统里常见做法是给列表页提供筛选条件鼓励用户通过筛选缩小数据集范围。所以在订单列表页我特意做了按时间区间、商品编码、订单状态三个筛选维度运营人员想找一条几个月前的订单完全不需要翻到第几万条记录直接按条件筛选即可。6.2 并发场景值得关注的几个边界刚才讲了库存扣减用 FOR UPDATE 解决并发超卖但系统里不止一个并发场景至少还有这几个边界值得考虑。先说说订单超时未支付。如果用户下单后一直不付款锁定库存就一直占着影响其他用户购买。我的实现是用延迟任务定期扫描超过30分钟未支付的订单自动改为已取消同时释放锁定库存并回补可售库存。这里的核心逻辑是状态变化的同时库存也必须有对应变动否则就产生订单取消了库存却没回来的严重问题。再一个边界场景是退款用户支付后申请退款后台同意前我要检查订单状态必须是已支付待发货或者已发货但未完成。如果已经发货了一般走的是退货退款流程状态要先进入售后中不能直接从已支付跳到退款完成。这个判断在OrderService.transition的状态机里已经做了限制所以我只需要保证调用顺序正确即可。最后是超卖问题在秒杀这种极端场景下的处理方式。FOR UPDATE 能解决数字准确性问题但场景再极端一些比如只剩最后一件商品几百人同时抢行锁会引发大量线程排队。进阶的做法是先用 Redis 的原子递减预扣库存真正落单时再校验数据库库存。这个属于高并发系统的玩法了毕设里不做也行但聊起来能体现你对问题边界的认识。6.3 日志与链路追踪的重要性业务系统上线后最痛苦的问题就是用户说他的订单出问题了但开发不知道当时发生了什么。我在这套系统里加了两个层级的日志保障。第一层是接口访问日志用拦截器统一打印每个请求的路径、方法、参数、耗时。这一层解决用户到底调了哪个接口的疑问。第二层是核心业务操作日志比如商品上下架、订单状态变更、库存变动都会以结构化格式写入日志文件。我在库存流水表、订单日志表里都做了落库保存。第一层解决当时发生了什么第二层解决业务数据为什么变成了现在这样。PostConstruct public void init() { log.info(订单状态变更日志: orderNo{}, fromStatus{}, toStatus{}, operator{}, orderNo, oldStatus, newStatus, operatorName); }对于单体毕设项目我建议用 SLF4J Logback 打印日志就足够不推荐一上来就上 ELK 或 SkyWalking 全家桶。先把日志打到位、把流水记录好将来真需要追查线上问题时你才有底气说这问题我能定位。7. 从题目到答辩给毕设同学的核心建议7.1 答辩演示的演示链路设计很多同学代码写得很好但答辩现场演示时却东点一下西点一下考官看不出系统的完整逻辑。我的建议是设计一条业务演示故事线把系统串起来讲。我的演示流程是先以管理员身份登录展示权限控制的效果——普通操作员看不到库存预警菜单。然后录入一件新商品设置价格和库存预警线演示商品上下架操作。接着模拟用户下单在订单列表里看到订单状态变为待支付此时故意不支付等待超时任务自动关单再演示一次正常支付然后发货、确认收货让订单走完完整状态流。最后回到库存列表展示这件商品的库存扣减流水并演示触发预警线时的提示效果。这条故事线的核心逻辑是让每一张表和每一个接口都被串起来讲一遍考官顺着这条线路能直观地看到系统各个模块是协同运转的而不是一个个孤立页面的拼凑。7.2 可以继续深化的方向如果你的毕设做完基础功能后还有余力我建议往这几个方向选一个做深度扩展。数据可视化引入 ECharts在首页展示近七日销售额折线图、商品分类占比饼图、库存预警排行柱状图商品批量导入用 EasyExcel 支持运营人员从 Excel 批量导入商品数据处理格式校验和错误反馈验证码与行为校验登录接口加入图形验证码后台接口增加防重复提交机制接口文档集成引入 Knife4j 自动生成接口文档答辩时直接展示 RESTful 接口的说明页面我特别推荐第一个数据可视化方向因为零售后台的运营场景天然适合图表展示而且能让你用上 SQL 聚合查询按月份GROUP BY统计销售额内容完成度会明显提升。7.3 回顾全文这套系统的核心价值整体讲完你会发现这个项目表面上是一个 SpringBoot 后台系统实际上它练的是你对业务链路的理解能力。商品模块考的是基础 CRUD 和字段设计库存模块考的是并发控制、事务边界和流水追踪订单模块考的是状态机的严谨性和异常场景处理。这三个能力恰好对应了后端开发最核心的数据建模、并发安全、业务闭环三件事。如果非要给一个最小可行的落地顺序我会建议你先把商品和库存的 CRUD 跑通再补上流水表和并发扣减接着做订单状态机最后用 Docker 部署。这个顺序每完成一步系统都是完整可用的不会有任何一步是需要全部做完才能看到效果的尴尬状态。这样无论时间多紧保证主链路始终是能演示的状态整个项目就不会失败。