Spring Boot农批仓储管理系统:批次管理与保质期预警实战解析 在农批市场里摸爬滚打过的人都知道仓储这环节有多磨人。我早年给几位做农牧产品批发的朋友做过小工具后来才意识到通用进销存软件在农批场景下真不够用——土豆入库按麻袋算、出库按斤称中间还要扣掉装卸破损一车冻肉分三批入库保质期各差半个月稍不留意就压货过期。这篇文章要讲的就是我基于 Spring Boot 搭建的一套面向农牧产品批发商的仓储管理系统从需求拆解、表结构设计到入库出库、批次追踪、保质期预警这些核心环节的实际落地过程以及我踩过的一些坑。项目本身是给一个做禽蛋和冻品的二级批发商做的代码量不算大但业务细节很密适合正在做仓储类毕业设计、或者准备给中小批发商做信息化改造的开发者参考。1. 项目来由与核心需求梳理1.1 农批仓储为什么不能用通用进销存市面上的通用仓储软件按 SKU 建档、按数量出入库这套逻辑在标准工业品上没问题但放在农牧产品上第一关就卡住了。农牧产品有几个天然特性决定了它的仓储管理必须“特事特办”。第一个是计量单位混乱。同一种商品进货可能按“件”或“箱”批发按“斤”或“公斤”走卖给商超又可能按“袋”。这不是简单地做个单位折算就行因为折算率会浮动——一箱苹果在不同季节、不同水分含量下实际净重差别很大。通用软件里的固定换算率在这种场景下只能靠人工反复修正。第二个是批次与保质期必须精细到“天”。一整托盘冻鸡腿拆成三个批号进库保质期可能是今天/下月/三个月后如果不做批号级管理出库时很容易先把新货发出去了老货压在库里变成临期品。农批利润本来就薄一批货压到报废几天白干。第三个是损耗和秤差是经营常态不是异常。装卸掉地碎了几箱、运输途中脱水缩水、过磅时和供应商的秤差半吨……这些都要作为正式业务记录在系统里而不是塞进“其他出库”备注。没有损耗台账月底对账就全是糊涂账。所以这个系统的核心需求不是把账记清楚而是把“活着、会变质、单位会变、重量会损耗”的商品的流转过程管起来。明确了这一点后续所有设计都有了方向。1.2 角色划分与业务流程梳理农批仓库涉及的干系人比一般仓库多。我这个项目里最终确认了五类角色仓管员负责收货、上架、盘点、拣货是系统最频繁的使用者界面一定要快、操作步骤要少。采购员创建采购订单但实际入库要以“实收数量”为准不能直接按订单数量入库——这个差异是农批行业的常识。销售/开单员创建销售出库单只锁定批次不直接改库存真正扣减在仓管员发货确认时才发生。老板/经理看库存总览、临期预警、毛利估算、供应商和客户对账报表。管理员维护商品档案、供应商、客户、账号权限。业务流程走下来是这样的采购订单 → 到货验收记录实收、扣损、秤差→ 批次入库分配库位→ 在库养护保质期预警、移库、盘点→ 销售订单 → 批次拣货 → 出库复核 → 过账扣库存。中间任何一步都允许作废和红冲但要留痕。这套流程画出来以后我发现一个关键点所有的核心业务动作都围绕“批次”展开而不是围绕“商品”展开。所以后续设计数据表时批次表成了当之无愧的核心表。1.3 功能模块的最终划分结合上面流程我把系统功能划分成六大模块基础资料商品档案、计量单位体系、库位、供应商、客户。采购入库采购订单、收货质检、批次入库、采购退货。销售出库销售订单、批次拣货、出库复核、销售退货。库存管理实时库存、批次台账、库存移动移库/调拨、盘点、损耗登记。预警中心保质期预警、库存上下限预警、临期商品报表。报表统计进出存汇总、供应商对账、客户对账、毛利估算。每个模块控制在两张主表以内避免过度设计。农批老板不关心花哨功能他关心的是今天库里还剩什么、哪些快过期、这个月到底赚了多少、几个大客户各自欠了多少钱。系统能快速回答这四个问题就已经值回全部开发成本。2. 技术选型与整体架构设计2.1 为什么选 Spring Boot 而不是其他方案这个项目我最终选了 Spring Boot 3.x MyBatis-Plus MySQL 的组合理由很实际。第一生态成熟、招人好招。虽然 Spring Boot 不算最轻量但对于一个需要长期维护、可能要接 ERP 对账、也可能要接电子秤接口的行业系统稳定性和后续找人改代码的容易程度比技术噱头重要得多。第二Spring Boot 的约定优于配置能把大量精力省在业务上。农批业务逻辑本身就够绕了我不希望开发时间浪费在配置 MVC 和事务管理器上。第三团队协作方便。Controller-Service-Mapper 这套分层刚毕业的学生也看得懂带人维护成本低。你要是单机小作坊十来个人用用 Python Flask 也能做甚至 Excel 加手工也能跑。但我这个项目后续有明确的移动端扫码需求后端要出 REST APISpring Boot 天然合适。技术选型这件事我的建议永远是先确认业务复杂度和团队能力再选框架。而不是反过来先选个时髦框架再逼业务去适配它。2.2 单体架构还是微服务明确说单体。农批仓储系统用户量就是仓库里那几十个账号并发也就是早高峰大家同时录单据TPS 撑死几十。这种情况下上微服务纯属给自己找麻烦。用的是一套 Spring Boot 单体工程但内部按模块分包将来万一真要拆分按包边界切就行。工程结构大致是这样的warehouse-management/ ├── src/main/java/com/pfms/ │ ├── common/ # 通用返回体、异常、工具类 │ ├── config/ # 配置类MyBatis、WebMVC、定时任务 │ ├── controller/ # REST 控制器 │ ├── service/ # 业务层 │ ├── mapper/ # MyBatis-Plus Mapper │ ├── model/ │ │ ├── entity/ # 数据库实体 │ │ ├── dto/ # 入参出参 │ │ └── vo/ # 视图对象 │ └── task/ # 定时任务保质期预警等 └── src/main/resources/ ├── mapper/ # XML 文件 └── application.yml分包的逻辑是不要把业务写在 Controller 里也不要把 SQL 堆在 Service 里。每个模块的入库、出库、冲销逻辑集中在对应的 Service 类里事务边界也划在这里。2.3 后端技术栈明细组件选型说明开发框架Spring Boot 3.2.x稳定版本JDK 17持久层MyBatis-Plus 3.5.x单表 CRUD 免写 SQL复杂查询用 XML数据库MySQL 8.0事务支持好运维普遍熟悉缓存本地 Caffeine商品档案、单位换算率等基础数据缓存定时任务Spring Schedule保质期预警、库存预警无需额外引入认证JWT Sa-Token轻量、无状态方便以后做小程序端缓存这里多说一句。农批系统里商品档案就几百条单位换算率也是低频变动数据用 Redis 有点杀鸡用牛刀。本地 Caffeine 缓存 修改即失效完全够用还能少维护一个中间件。3. 数据模型设计的硬核细节3.1 核心表结构总览这套系统一共 16 张表不算多但两张核心表的设计值得重点讲product_batch商品批次表和stock_ledger库存流水表。其余表基本都是这两张的附属。product商品档案category分类冻品、禽蛋、蔬菜、粮油等unit/unit_conversion计量单位及换算关系supplier/customer供应商 / 客户warehouse/location仓库 / 库位purchase_order/purchase_order_item采购订单及明细inbound_order/inbound_order_item入库单及明细sales_order/sales_order_item销售订单及明细outbound_order/outbound_order_item出库单及明细product_batch批次表核心stock_ledger库存流水表核心inventory实时库存汇总表stock_take_task/stock_take_item盘点loss_report损耗登记alert_config/alert_record预警配置与记录sys_user系统用户这么设计的好处是业务单据和库存变动分开单据管业务流水管事实。任何库存异常都能通过流水反查是哪张单据造成的对农批行业常见的“货对不上账”问题尤其有效。3.2 批次表的设计批次表是整个系统的命脉。我按实际业务情况把核心字段这么设计CREATE TABLE product_batch ( id BIGINT PRIMARY KEY AUTO_INCREMENT, batch_no VARCHAR(64) NOT NULL COMMENT 批次号, product_id BIGINT NOT NULL COMMENT 商品ID, supplier_id BIGINT COMMENT 供应商ID, inbound_date DATE NOT NULL COMMENT 入库日期, production_date DATE COMMENT 生产日期, expiry_date DATE NOT NULL COMMENT 保质期截止日, origin_place VARCHAR(128) COMMENT 产地, quantity_remaining DECIMAL(18,3) NOT NULL COMMENT 剩余数量, unit_id BIGINT NOT NULL COMMENT 批次计量单位, eu_weight DECIMAL(18,3) COMMENT 入库毛重kg, net_weight DECIMAL(18,3) COMMENT 入库净重kg, location_id BIGINT COMMENT 所在库位, quality_status TINYINT COMMENT 质量状态1-正常 2-次品 3-冻结, status TINYINT NOT NULL DEFAULT 1 COMMENT 状态0-已清空 1-可用 2-冻结, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT商品批次表;这个表的几个细节quantity_remaining是当前剩余数量而不是累计入库数量。剩余数量小于等于 0 时状态自动置为已清空就不再参与后续出库拣选。这样做能避免“批次还挂在清单里但实际没货”的尴尬。quality_status字段很有用。农批市场经常遇到到货里面有部分次品一箱苹果有半箱磕碰伤。如果全部按正常品入库后面拣货容易把次品发给客户搞坏关系如果全部拒收又太浪费。我这里的做法是次品单独建一个批次质量状态标为“次品”价格单独维护可以做降价处理。批次表里存了eu_weight和net_weight很多人问为什么不直接合成一个字段。因为农批收货时毛重在仓库过秤就有了净重往往要等供应商提供或者抽样测算。两个字段分开存后续才能算真实损耗率。3.3 单位与换算率设计固定加浮动前面提到过农批商品的换算率会浮动。我在设计单位表时做了两套并存固定换算1 箱 10 公斤、1 件 12 袋这类在unit_conversion表里存固定值适用于绝大多数标准化商品。浮动换算登记在当前批次上。A 供应商这一车土豆每袋实际 48 斤B 供应商那车可能每袋 52 斤。系统里批次的单位是袋但出库时按斤称这时候换算率取的是批次上的“本批实际单重”而不是商品档案里的固定值。具体实现是CREATE TABLE unit_conversion ( id BIGINT PRIMARY KEY AUTO_INCREMENT, product_id BIGINT NOT NULL, from_unit_id BIGINT NOT NULL, to_unit_id BIGINT NOT NULL, fixed_rate DECIMAL(18,4) COMMENT 固定换算率, is_float TINYINT DEFAULT 0 COMMENT 是否浮动换算, UNIQUE KEY uk_prod_units (product_id, from_unit_id, to_unit_id) ) COMMENT计量单位换算表;入库单生成批次时如果该商品启用了浮动换算仓管员就要填“本批件重”或“本批密度”。这样后面所有按重量出库的换算都拿这个值算。3.4 库存流水表所有库存变动的底账库存流水表的设计原则是只增不改只能红冲不能更新。CREATE TABLE stock_ledger ( id BIGINT PRIMARY KEY AUTO_INCREMENT, batch_id BIGINT NOT NULL, product_id BIGINT NOT NULL, change_type TINYINT NOT NULL COMMENT 变动类型1-入库 2-出库 3-移库出 4-移库入 5-盘盈 6-盘亏 7-损耗 8-红冲, quantity DECIMAL(18,3) NOT NULL COMMENT 变动数量正负, from_location_id BIGINT, to_location_id BIGINT, source_order_no VARCHAR(64) COMMENT 来源单号, source_order_type TINYINT COMMENT 来源单据类型, remark VARCHAR(255), create_time DATETIME DEFAULT CURRENT_TIMESTAMP, create_by BIGINT COMMENT 操作人ID, KEY idx_batch (batch_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT库存流水表;source_order_no这个字段很关键。任何时候发现库存数量不对只要能定位到流水就能顺藤摸瓜找到来源单据追查是录入错误、拣货漏发还是盘亏。我在实际使用中很多库存异常是收银端和仓管端数据不同步造成的靠这个字段几分钟就能定位问题。3.5 库存汇总表与性能取舍有人会问既然有流水表为什么不直接每次查询都汇总流水能但没必要。农批仓库一天流水几百条汇总几张表问题不大但到了月底对账、老板要实时看库存时每次都全表 GROUP BY 会拖慢页面。我的做法是维护一张inventory汇总表按product_id batch_id location_id唯一记录当前剩余数量。每次出入库过账时在同一事务里更新流水表和汇总表保证两者一致。汇总表就是给前台查询用的流水表就是给对账追查用的各司其职。后面做盘点时也方便盘点单生成后比对汇总表数量和实盘数量自动生成盘盈盘亏流水再更新汇总表一步到位。4. 核心业务环节的实现细节4.1 入库流程验收、称重、建批次入库是整个系统里最容易出乱子的环节因为农批收货的实际情况远比软件操作复杂。供应商送货来了仓管员先看货、抽查质量然后过磅记录毛重。这时候可能出现三种情况实收重量和采购订单差很多、有破损需要扣重扣损、有次品需要单独隔离。所以入库单设计成“主子结构 批次自动生成”的模式。仓管员在入库单上选择订单系统带出商品和预期数量实际入库时直接修改实收数量差异系统自动感知。超过约定损耗率的差额要么记入损耗要么走索赔流程。批次号生成规则建议这样日期 供应商编号 流水号比如20241201 - S0012 - 001。打印成条码贴在每个托盘或每批货上后面出库扫码直接定位批次。关键点入库时必须支持同一种商品拆分成多个批次入库。一车冻货分三层每一层生产日期不同如果我设计成只允许一个批次仓管员就得做多个入库单麻烦不说还容易漏。批次生成的接口签名大概是这样的Transactional(rollbackFor Exception.class) public InboundResult createInbound(InboundCreateDTO dto) { // 1. 校验采购订单状态 // 2. 遍历明细逐个生成或更新批次 // 3. 写入库存流水入库类型 // 4. 更新 inventory 汇总表 // 5. 更新采购订单的入库累计量 // 6. 生成批次条码 }事务要开到方法级别确保这六步要么全成要么全失败。有一次我图省事把事务开在了 Controller 层结果一个入库单拆成好几次请求提交批次建了一半库存没更新查了好几天才查出原因。4.2 出库流程先进先出加批次锁定出库是另一个高频场景。农批的出库开单和发货经常是两个人所以流程上要分成两步销售开单先锁定仓管发货再确认。销售开单时系统自动按“先进先出”规则推荐批次。具体排序是状态为可用且未过期按expiry_date升序先过期先出同一保质期的按inbound_date升序先入库先出自动推荐出来的批次允许开单员手动调整。比如大客户指名要某个产地的货或者某批次质量更好手动改一下即可。发货确认时系统才真正扣减库存。这时候要重新校验批次剩余数量是否够防止 A 开单员锁了 100 件B 开单员也锁了同一批的 100 件结果发货时发现只剩 150 件——第二个确认的人会被拦截。这个“重新校验”逻辑特别重要我一开始图省事没做结果出现负库存对账对到头大。后来加上后基本再没出过系统内的负数。出库扣库存的 SQL 上我做了乐观锁控制UPDATE product_batch SET quantity_remaining quantity_remaining - #{qty} WHERE id #{batchId} AND quantity_remaining #{qty} AND status 1受影响行数为 0 时说明超卖直接抛业务异常。这比先查再更的写法更稳并发下也不会扣成负数。4.3 称重溢短差异怎么入账农批按重量结算时典型的场景是这样的采购订单定了 2000 公斤土豆实际到货过磅 2050 公斤多了 50 公斤。这时候不能直接改订单数量因为单价、应付金额、供应商余额都会变。我的处理方式是采购订单仍然记 2000 公斤但入库单允许“溢短入库”超出部分生成一个“溢耗批次”单独记录不参与订单结算只参与库存实物管理。月底和供应商对账时把这 50 公斤单独拿出来讨论是补钱还是退回去系统留痕不至于扯皮。出库也一样。客户订货 100 斤实际装车 103 斤多给了防止途损。系统出库单记 103但按 100 斤单价结算另外 3 斤记录成“让耗”或者“途损”方便业务员月底核算毛利时知道到底让了多少利。这些操作在代码里不复杂但设计时不做后面改起来伤筋动骨。我在第一个版本就踩过当时没有“溢耗”概念全都按实收记结果财务对账时采购订单和入库单永远对不上。4.4 移库、调拨和库位管理农批仓库里移库非常频繁。早上一批货在收货区中午被移到 A 区冷库下午又被挑出一部分放到拣货区。如果系统不做库位级库存管理这些动作没法追踪盘点时也找不到货。移库操作走的是源库位出流水类型 3移库出目标库位入流水类型 4移库入在同一事务里完成。相当于把货从一个库位搬到另一个不改变批次数量。冷库的库位管理我做得比较粗放按区 排 位编号比如 A-03-02。扫码枪扫批次条码再扫库位条码就能完成移库。仓管员培训十分钟就会用比我预想的顺利。库位管理的好处不只是找货方便。同一种商品放在不同温区冷藏/冷冻/常温保质期表现完全不同库位加上后临期预警可以按库位分级提醒常温库的提前 7 天预警冷库的提前 15 天预警。效果比单纯看日期好太多。4.5 盘点先锁后盘差异自动过账农批仓库盘点有自己的规律大账每月盘一次重点商品每周抽盘不需要所有库存同一天盘完。抽盘实现上我按“盘点任务”组织。管理员创建盘点任务选定一批库位或商品系统生成盘点明细格子是当前库存数。仓管员扫描实盘数填入提交后系统自动比对差异自动生成盘盈盘亏流水并更新汇总库存。核心注意点盘点期间被盘点的库位或批次要加锁不允许出库。否则一边盘一边出库差异永远对不上。冷库盘点还有个实际问题太冷了仓管员不想在里面多待一秒钟。所以我把盘点界面做成了大按钮 扫码枪优先减少打字操作盘完一个商品按一下确认尽量缩短冻库停留时间。5. 保质期预警与库存预警模块5.1 生命周期状态机农牧产品最大的风险是过期所以系统里给每个批次设计了一个状态机编辑中 → 在库正常 → 临期已预警 → 已过期 ↓ ↓ 次品/冻结 强制下架当批次剩余数量大于 0 且未过期状态为“在库”。保质期临近阈值时系统自动置为“临期”出库拣选时排在最后而且界面红色高亮。一旦过了保质期状态变更为“已过期”不允许正常出库只能走“报损出库”或“粉碎处置”流程。这个状态机不是仅靠数据库字段硬编码而是在 Service 层抽象成了一个方法public BatchLifecycle getLifecycle(ProductBatch batch) { if (batch.getExpiryDate().isBefore(LocalDate.now())) { return BatchLifecycle.EXPIRED; } if (daysUntilExpiry(batch) alertThreshold(batch.getProductId())) { return BatchLifecycle.EXPIRING; } return BatchLifecycle.NORMAL; }后续加新的预警规则比如“到货后一周未出完货的鲜活商品也预警”只需要在这个方法里加分支不破坏原有逻辑。5.2 定时任务怎么扫每天凌晨三点系统跑一个定时任务扫描所有在库批次凡是 15 天内过期的自动生成预警记录并推送通知给仓管员和老板。通知渠道目前只有站内信和微信服务号模板消息通过第三方推送服务没做短信因为短信要花钱且重试逻辑麻烦。定时任务的实现用的是 Spring Schedule 分布式锁。单体项目没有分布式锁问题但如果将来部署多节点要小心任务重复执行。我用的是一个简单的 Redis 锁key 是任务名过期时间设到任务预估耗时的三倍防止任务卡死导致锁不释放。定时任务触发时间选凌晨是因为这时候系统几乎没人用不会出现库存汇总和跑批打架的情况。如果你仓库半夜也在出库有些批发市场凌晨灯火通明那把任务挪到中午午休时段另外记得跑批前做个库存异常快照跑完对比一下。5.3 预警阈值怎么配保质期预警阈值不是写死的我加了一张alert_config表按商品分类配置商品分类预警提前天数提醒方式冻品-18℃30 天站内信 服务号冷藏品0-4℃15 天站内信 服务号常温干货30 天站内信鲜活水产当场确认实时推送 电话提醒为什么冻品要提前 30 天因为冻品从临期到真正过期还有“化冻”、品质下降的缓冲期提前预警是为了让老板有时间做促销甩货。鲜活水产这种没法提前——今天拿货明天卖不掉就得处理所以实时性要求最高。库存上下限预警也加了低于下限提醒补货高于上限提醒防止积压。农批商品波动大上下限配置允许按季节调整——夏天的蔬菜上限调低容易烂冬天的冻品上限可以调高耐存。6. 常见问题与排查技巧实录6.1 库存变成负数了怎么办我最常被问的是库存怎么变成负数了绝大多数原因是出库扣减没有做“先校验后扣减”或者事务边界不对。我在系统里做了四重防线出库前页面锁定批次时校验一遍剩余量发货确认时 UPDATE SQL 里带上quantity_remaining qty条件受影响行数为 0 直接报错库存流水记录正向和负向变动任何时候都能算出一个批次的逻辑库存每天晚上定时任务跑一遍“虚拟库存重算”把各批次流水 SUM 出来和汇总表比对不一致就告警。即便如此偶尔还是会有损耗没及时登记导致账实不符但至少系统内不会再出现负数。排查步骤记住一个口诀先看流水再看单据最后看用户操作时间轴。6.2 同一批货拆期入库怎么防止出库漏批次有一部分商品到货时保质期不均匀——比如三层货架分别产自三个月前、一个月前、一周前。如果仓管员偷懒录成一个批次保质期填最早的那后到的那部分货就会在系统里变成“临期品”被提前低价抛售损失肉眼可见。我解决的办法是入库界面做强制校验商品保质期小于等于 45 天的常温干货或冷藏品入库时必须填生产日期系统自动算保质期并提示“本商品可能存在多生产日期请按实拆分批次”。同时入库单上加了“新增子行”按钮一行变成多行每行对应一个生产日期和数量这样同一种商品可以一次录入多个批次。这个看似很小的功能实际用起来非常顺手仓管员几乎没有额外工作量但系统数据的准确度提升了一大截。6.3 并发锁冲突导致操作超时早高峰时段几个仓管员同时过账偶尔会遇到 “ Deadlock found when trying to get lock” 的报错。原因是我在扣批次数量时用行锁而多个事务对同一批次的更新顺序不一致。解决方式有两步把扣减批次的 UPDATE 语句统一放在事务的最前面执行尽可能早获取行锁所有按批次扣减的操作统一先按 batchId 排序再更新避免交叉加锁。改成“先排序再加锁”之后死锁基本消失。这个经验也是我在一次线上事故里换来的——那天早高峰客户催发货系统响了好几次超时从那以后我再也没敢轻视锁顺序。6.4 单位换算精度问题农批里 1 吨 1000 公斤、1 公斤 2 斤换算率看起来很规整但一旦涉及袋、箱、件这种包装单位精度问题就来了。比如 1 箱 12.5 公斤库存 3 箱换算成公斤时3 × 12.5 37.5没问题但如果库存数量保留 3 位小数出库斤数又是整数就会出现“出完斤数箱数剩 0.001”的零头盘点时很刺眼。我的处理策略数据库数量统一 DECIMAL(18,3)存“基准单位”的数量界面显示时按用户习惯切换单位但内部运算都用基准单位单据审核后如果发现某批次剩余数量绝对值小于 0.005直接归零避免幽灵零头零头处理规则在系统参数里可配四舍五入 / 向上取整 / 向下取整三种策略老板自己选。这个经验看着小但实际影响很大。我第一次交付时没处理零头客户盘点发现每个批次都有个奇怪的尾数对账对到崩溃后来加了“尾数归零”策略才彻底解决。6.5 扫码枪输入法冲突仓库用的扫码枪本质上是模拟键盘输入扫码后直接回车。我系统里做了搜索框自动聚焦理论上扫完码自动触发搜索。但实际部署时发现某些扫码枪配置了“扫码后追加回车”而我的搜索框在某些浏览器上回车不触发跳转导致仓管员扫一个码要点两次屏幕效率低一半。排查了很久最后是在扫码枪自带的配置手册里找到设置项把“扫码后发送 Tab”改成“发送 Enter”并在前端统一用 keydown 事件监听 Enter 触发搜索问题解决。这个坑几乎每个做扫码场景的人都会踩建议在系统里默认兼容 Enter 和 Tab 两种扫码枪模式。7. 部署与环境配置要点7.1 运行环境开发和生产环境的配置差异我是用 Spring Profile 区分的。本机跑devIDEA 里直接起服务器跑prod用 Docker Compose 编排 MySQL 应用服务。# application-prod.yml spring: datasource: url: jdbc:mysql://mysql:3306/warehouse_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: warehouse_user password: ${DB_PASSWORD} redis: host: redis port: 6379 server: port: 8080生产环境用 Docker 的好处是换服务器时直接迁移镜像MySQL 数据卷单独挂载不容易丢数据。小团队没有运维精力Docker Compose 已经足够了别整 K8s。7.2 初始化数据脚本我写了一个>