ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Spring Boot进销存系统实战:从数据模型到部署落地

Spring Boot进销存系统实战:从数据模型到部署落地 简介面向毕业设计与 Java 开发者这份基于 Spring Boot 的进销存管理系统是一套可直接运行的仓库管理项目覆盖采购、库存、销售、查询统计、资料管理和系统管理六大模块支持管理员与雇员双角色登录适合作为课程设计、毕业设计或 Spring Boot 入门项目的参考方案。项目采用 Spring Boot、JPA、Maven、jQuery 技术栈Eclipse 与 IDEA 均可打开运行数据表与业务逻辑层次清晰便于二次开发。压缩包约 99.79MB包含完整源码、数据库文件、文档和 PPT源码中具体实现了商品入库、出库、移库、盘点、销售订单、采购退货等业务闭环配套文档可辅助理解设计思路与部署配置。目前已有 66 人浏览学习适合需要参考完整进销存方案、快速搭建类似管理系统的开发者同时也可作为答辩汇报与课程展示的参考素材方便在此基础上按需扩展新功能。1. 用Spring Boot建进销存系统先想清楚业务闭环再写代码进销存管理系统是最容易被低估的一类Java项目。表面上它只是商品、入库单、出库单三个页面的增删改查但真正跑起来之后库存对不上、单据回滚不干净、并发超卖这类问题全冒出来了。库存不是一个可以随手改的字段它必须由单据和流水推导出来先有入库单才能增加库存先有出库单才能扣减库存每一步变动都要留下流水记录。想明白这个闭环再动手写Spring Boot工程项目的质量会完全不一样。你手上如果是这样一个标题Spring Boot进销存管理系统加文档进销存仓库管理Java项目且Eclipse和Idea都能打开那大概率是一个标准的Maven多模块或单模块Web工程。技术栈通常是Spring Boot 2.x加MyBatis-Plus加MySQL前端是Vue或Thymeleaf。这套组合也确实是目前中小公司内部管理系统、毕业设计和仓库管理系统最常用的路线。工程本身只要是标准Maven结构Eclipse和IntelliJ Idea导入方式不同但代码完全一致不存在只能在一个IDE里跑的问题。这篇内容围绕这个标题展开先说进销存的数据模型怎么设计再讲怎么在两个IDE里把工程跑起来然后落地入库、出库、库存扣减的核心代码最后补充盘点、报表和部署阶段的细节。新手可以照着步骤做完有经验的人也能在并发处理和事务边界上找到可用的写法。2. 进销存数据底座库存模型、单据流与流水表设计2.1 核心表拆分主数据、单据、明细与流水四层进销存系统第一步不是写代码而是把表拆对。很多初学者把所有字段塞进一张商品表包括库存数量、累计入库、累计出库最后报表根本没法写。常见的做法是把数据按职责拆成四层主数据、单据头、单据明细、库存流水。主数据包括商品和仓库单据头记录一张入库单或出库单的整体信息单据明细记录这张单子里的每一行商品、数量、单价库存流水记录每一次库存变动是后面所有报表和对账的依据。表名分层核心字段说明goods主数据goods_code, goods_name, spec, unit, price商品唯一编码规格单位warehouse主数据warehouse_code, warehouse_name仓库主数据stock库存台账warehouse_id, goods_id, stock, version当前实时库存结果表inbound_order单据头order_no, warehouse_id, supplier, operate_time入库单整体信息inbound_item单据明细order_id, goods_id, qty, price一个入库单对应多行商品outbound_order单据头order_no, warehouse_id, customer, operate_time出库单整体信息outbound_item单据明细order_id, goods_id, qty出库明细stock_log流水表order_no, goods_id, warehouse_id, operate_type, qty, operate_time每次库存变动的原始凭证库存表是整个系统最有争议的一张表。严格讲库存可以从流水实时汇总出来但业务系统通常需要快速的库存查询所以专门放一张stock表做冗余。冗余的前提是所有改动必须发生在事务里并且流水表同步写入。库存表是结果流水表是事实这个优先级顺序必须清楚。2.2 建表SQLMySQL 8的InnoDB落地脚本下面这份建表SQL是进销存项目最基础的核心表去掉了审计字段和扩展字段保留直接能用的部分。数据库选MySQL 8引擎统一InnoDB字符集utf8mb4金额全部用DECIMAL数量用INT或者DECIMAL取决于业务是否需要小数单位。涉及金额的字段禁止使用DOUBLE或FLOAT否则累计对账会出现精度问题。CREATE TABLE goods ( id BIGINT PRIMARY KEY AUTO_INCREMENT, goods_code VARCHAR(32) NOT NULL COMMENT 商品编码, goods_name VARCHAR(128) NOT NULL COMMENT 商品名称, spec VARCHAR(64) DEFAULT COMMENT 规格, unit VARCHAR(16) NOT NULL COMMENT 单位, price DECIMAL(12,2) NOT NULL DEFAULT 0 COMMENT 参考单价, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_goods_code (goods_code) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT商品表; CREATE TABLE stock ( id BIGINT PRIMARY KEY AUTO_INCREMENT, warehouse_id BIGINT NOT NULL, goods_id BIGINT NOT NULL, stock INT NOT NULL DEFAULT 0 COMMENT 当前库存, version INT NOT NULL DEFAULT 0 COMMENT 乐观锁版本号, UNIQUE KEY uk_warehouse_goods (warehouse_id, goods_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT库存台账; CREATE TABLE inbound_order ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(64) NOT NULL COMMENT 入库单号, warehouse_id BIGINT NOT NULL, supplier VARCHAR(128) DEFAULT COMMENT 供应商, operate_time DATETIME NOT NULL, UNIQUE KEY uk_order_no (order_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT入库单头; CREATE TABLE inbound_item ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_id BIGINT NOT NULL, goods_id BIGINT NOT NULL, qty INT NOT NULL, price DECIMAL(12,2) NOT NULL DEFAULT 0, KEY idx_order_id (order_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT入库单明细; CREATE TABLE stock_log ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(64) NOT NULL COMMENT 关联业务单号, goods_id BIGINT NOT NULL, warehouse_id BIGINT NOT NULL, operate_type VARCHAR(16) NOT NULL COMMENT INBOUND/OUTBOUND/CHECK, qty INT NOT NULL COMMENT 正数入库负数出库, operate_time DATETIME NOT NULL, KEY idx_goods_time (goods_id, operate_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT库存流水;这里有几个字段值得单独说明。inbound_order的order_no必须唯一这个单号由程序生成不能用数据库自增主键直接当业务单号因为外部系统或甲方会拿着单号来查单自增ID在报表里难以阅读。stock表加了唯一约束uk_warehouse_goods同一商品在同一仓库只允许一行库存记录这是防止并发插入库存行的重要保障。stock_log里operate_type只存业务类型qty统一用正数入库、负数出库这样做统计时可以直接SUM不需要关心方向判断。2.3 为什么库存表要单独建并且带version字段初学者最容易犯的错误是直接把库存数量放在goods表里每次入库就UPDATE goods SET stock stock 1。这个做法在单机低并发时能跑但一旦出现两张单子同时入库就会出现丢失更新。库存表和商品表分离之后还要在库存表里增加version字段配合条件更新WHERE version ?在更新时校验版本号防止两个事务互相覆盖。MyBatis-Plus的乐观锁插件会自动处理这个字段但如果你手动写UpdateWrapper也可以自己控制。注意version只在更新时作为条件出现更新成功后要同时version version 1。如果更新影响行数为0说明这一行已经被别人改过需要重试或直接报错不能静默忽略。单据头和单据明细为什么要拆两张表因为一张入库单会有多行商品如果只存一张表每一行都要重复存单据号、供应商、操作时间数据冗余且不利于回滚。拆成一对多之后修改明细时只需要按order_id删除后重建订单头信息不用动。流水表的存在则是为了回答一个问题这个月的入库总量是多少某个商品在某个时间段的期初库存和期末库存分别是多少这些问题在库存表里找不到答案因为库存表只保留了当前值。3. 在Eclipse和Idea里把Spring Boot工程跑通版本选型与最小工程3.1 版本匹配关系JDK、Spring Boot和MySQL怎么选标题里提到“Eclipse和idea都”这一步的关键不是配置IDE而是选对Spring Boot版本。现在很多讲解进销存项目的教程和视频录制于三四年之前用的是Spring Boot 2.3或2.7对应JDK 8。如果你直接创建Spring Boot 3.x项目编译会报错因为原来的javax.servlet包在3.x里全部换成了jakarta.servlet老代码直接复制过来根本过不了编译。这个就是很多人遇到的“springboot版本太高”导致的连锁问题。Spring Boot版本最低JDK包名适用场景2.7.xJDK 8javax.*成熟稳定教程最多适合进销存这类业务系统3.2.xJDK 17jakarta.*新项目可考虑但旧代码迁移成本高3.3及以上JDK 17jakarta.*追求新特性时使用插件兼容性需要验证进销存管理系统的技术挑战不在框架新特性上而在事务、并发和数据一致性上因此我一般会选Spring Boot 2.7.18配合JDK 8这是整个2.x系列最后一个维护版本稳定且生态兼容性最好。MySQL选5.7或8.0都可以但建表SQL里的窗口函数和CHECK约束需要8.0才支持推荐直接用MySQL 8。Maven版本3.6以上即可IDEA自带MavenEclipse则用外部Maven需要在Eclipse的Preferences里指定Maven安装目录和settings.xml位置。3.2 pom.xml关键依赖与application.yml最小配置创建一个Spring Boot工程pom.xml里最核心的依赖是四个Web、MyBatis-Plus、MySQL驱动和Lombok。MyBatis-Plus的版本要和Spring Boot版本匹配Spring Boot 2.7对应的是mybatis-plus-boot-starter 3.5.x不要用太老的3.4版本否则分页插件和一些API不兼容。parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.5/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency /dependencies依赖版本锁定在Spring Boot父工程里mysql-connector-java由父工程统一管理版本2.7.x对应的驱动会自动适配MySQL 8。Lombok标记为optional后不会被打进war包减少编译期注解处理带来的运行时报错。MyBatis-Plus单独指定版本因为它不归Spring Boot父工程管理。spring: datasource: url: jdbc:mysql://localhost:3306/inventory_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8 mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-value: 1 logic-not-delete-value: 0url里必须写serverTimezone否则数据库连接会报时区错误。useSSLfalse在本地开发时可以关掉生产环境根据数据库侧配置决定。map-underscore-to-camel-case开启后数据库字段goods_name能自动映射到实体属性goodsName不用手工写resultMap这是MyBatis-Plus最实用的配置。log-impl输出SQL日志开发阶段必须开排错依赖它。3.3 启动类与MapperScan的写法启动类是Spring Boot工程唯一的入口进销存工程里它放在顶层包下比如com.example.inventory。下面这个启动类除了标准的SpringBootApplication还加了MapperScan指定MyBatis的Mapper接口在哪个包下这样就不用每个Mapper接口都写Mapper注解。package com.example.inventory; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; SpringBootApplication MapperScan(com.example.inventory.mapper) public class InventoryApplication { public static void main(String[] args) { SpringApplication.run(InventoryApplication.class, args); } }SpringBootApplication组合了ComponentScan、EnableAutoConfiguration和Configuration三个注解的作用Spring会扫描启动类所在包及其子包下的所有组件。如果没有MapperScanMapper接口不会被Spring识别成Bean启动时不会报错但运行时注入Mapper一定会抛NoSuchBeanDefinitionException。这个错误在Idea里常见在Eclipse里更常见因为两个IDE对注解处理的检查方式不同。Eclipse用户还需要确认项目开启Lombok的Annotation Processing否则实体的Data注解不会生成getter和setter编译直接失败。3.4 Idea和Eclipse导入Maven工程的差异同一个Spring Boot进销存工程两种IDE的导入方式不同。IntelliJ Idea里选择File Open直接选中项目根目录或pom.xml文件Idea会识别为Maven工程并自动下载依赖。如果依赖下载不完整右侧Maven面板点刷新按钮强制重新导入。Eclipse则用File Import Maven Existing Maven Projects选择根目录后Eclipse会生成对应的.classpath文件。两者导入后都需要确认Project SDK和Java编译器级别一致否则代码能打开但运行时报UnsupportedClassVersionError。提示Eclipse内置的Maven版本通常偏旧如果下载依赖时卡住或报错建议换成外部Maven 3.8以上版本并在maven的conf/settings.xml中配置本地仓库路径。Idea用户同样可以指定自己的Maven路径不一定要用内置的Bundled Maven。工程跑起来之后访问http://localhost:8080能看到页面控制台有Spring Boot启动日志说明基础环境就绪。接下来要做的不是先写页面而是把进销存的入库出库逻辑在后端落稳这才是这个系统的核心。4. 进销存核心业务落地入库、出库与库存扣减的Spring事务实现4.1 业务规则要写在代码前面写进销存功能之前先把业务规则定死。入库单提交时校验商品是否存在、数量是否大于0出库单提交时校验库存是否充足不足则整单失败回滚单据一旦审核通过关联的库存和流水必须同时变化不允许出现单子保存成功但库存没变的中间状态。每一条规则对应一个明确的代码实现方式而不是靠前端按钮去限制用户行为。进销存里最核心的规则是同一事务内完成单据写入、库存更新、流水记录三步。Spring的Transactional注解解决事务问题但它默认只对RuntimeException回滚遇到检查异常如IOException不会回滚所以注解上必须写rollbackFor Exception.class。这个细节在面试里常被问到实操里更是坑很多进销存项目就是栽在事务没有覆盖所有异常类型上。4.2 入库接口完整实现单据、库存与流水一次写成功入库的业务代码分成三层Controller接收参数Service处理业务逻辑Mapper操作数据库。下面是一个简化但完整的入库实现去掉了一些查询逻辑保留了核心内容。注意Service里先查库存不存在则创建库存行存在则用条件更新增加数量。Override Transactional(rollbackFor Exception.class) public void createInbound(InboundCreateForm form) { String orderNo generateOrderNo(IN); InboundOrder order new InboundOrder(); order.setOrderNo(orderNo); order.setWarehouseId(form.getWarehouseId()); order.setSupplier(form.getSupplier()); order.setOperateTime(LocalDateTime.now()); inboundOrderMapper.insert(order); for (InboundItemForm item : form.getItems()) { Stock stock stockMapper.selectOne(new LambdaQueryWrapperStock() .eq(Stock::getGoodsId, item.getGoodsId()) .eq(Stock::getWarehouseId, form.getWarehouseId())); if (stock null) { stock new Stock(); stock.setGoodsId(item.getGoodsId()); stock.setWarehouseId(form.getWarehouseId()); stock.setStock(item.getQty()); try { stockMapper.insert(stock); } catch (DuplicateKeyException e) { // 并发时两个请求同时发现库存不存在一个插入成功另一个走到更新 stockMapper.increaseStock(form.getWarehouseId(), item.getGoodsId(), item.getQty()); } } else { stockMapper.increaseStock(form.getWarehouseId(), item.getGoodsId(), item.getQty()); } StockLog log new StockLog(); log.setOrderNo(orderNo); log.setGoodsId(item.getGoodsId()); log.setWarehouseId(form.getWarehouseId()); log.setOperateType(INBOUND); log.setQty(item.getQty()); log.setOperateTime(LocalDateTime.now()); stockLogMapper.insert(log); } }这段代码有两个关键点。第一个是并发场景的处理当两个请求同时创建同一个商品的第一笔库存时可能都查询到stock为null然后都执行insert这时唯一约束uk_warehouse_goods只能让其中一个成功另一个会抛DuplicateKeyException捕获后改成更新库存既不会丢数据也不会重复建行。第二个是流水与库存必须放在同一个事务中任何一个异常都会让整个单子回滚不会出现单据存在但库存没变的脏数据。4.3 出库扣减用条件更新代替读改写出库的难点不是扣减本身而是防止超卖。最危险的做法是先查询库存判断是否充足再执行update这个“读改写”模式在高并发下会出问题两个请求同时读到库存为10都判断可以出库都执行更新最终结果变成负库存或丢失一次扣减。正确做法是把检查和扣减合成一条原子SQL。int rows stockMapper.update(null, new UpdateWrapperStock() .eq(warehouse_id, form.getWarehouseId()) .eq(goods_id, item.getGoodsId()) .ge(stock, item.getQty()) .setSql(stock stock - {0}, item.getQty())); if (rows 0) { throw new BizException(库存不足或商品未建档); }ge(stock, item.getQty())在SQL层面转化成stock ?这个条件让扣减操作只有在库存足够时才执行。数据库会对命中的行加锁第二个事务必须等第一个事务提交后才能执行update因此不会出现两个事务同时扣减同一份库存的情况。rows 0表示库存不足或者商品在该仓库没有库存行此时直接抛出业务异常整个出库单回滚。setSql里的{0}占位符会交给MyBatis参数绑定避免字符串拼接SQL注入的问题。这个模式是进销存出库扣减的标准写法既不用悲观锁SELECT FOR UPDATE也不用在Java代码里加synchronized。4.4 并发场景下的锁选择进销存系统的并发通常集中在少数热门商品上这时锁的粒度决定了系统的吞吐量。条件更新利用的是数据库行锁锁范围只覆盖一个商品在一个仓库的一行库存粒度最小性能最好。MyBatis-Plus自带的乐观锁插件适合“更新不频繁、失败重试成本低”的场景比如商品资料的修改。而SELECT FOR UPDATE悲观锁适合在更新库存前还需要读取其他表数据做判断的复杂场景但锁持有时间更长容易引发死锁。注意事务里不要执行远程调用、文件上传等耗时操作这些会让数据库连接和行锁长时间不释放并发一旦上来会拖垮整个应用。进销存系统的经验是事务方法只做数据库操作其他事情放到事务外面处理。5. 进销存账实一致盘点差异、批次过期与报表查询5.1 盘点流程与库存调整账面库存和实际库存对不上是仓库管理的常态原因可能是入库漏记、出库错发、商品损坏或者盘点计数错误。盘点的目标不是直接改库存数字而是通过一张盘点单记录差异再走调整流程。盘点单记录每个商品的账存数量、实盘数量、差异数量和调整原因审核通过后才更新库存并写入盘点流水。int diff 实际盘点数量 - 账面库存数量; if (diff 0) { stockMapper.increaseStock(warehouseId, goodsId, diff); operateType CHECK_IN; } else if (diff 0) { stockMapper.decreaseStock(warehouseId, goodsId, -diff); operateType CHECK_OUT; } stockLog.setOperateType(operateType); stockLog.setQty(Math.abs(diff)); stockLogMapper.insert(stockLog);盘盈和盘亏在流水表里用CHECK_IN和CHECK_OUT区分本质和出入库一样是库存增减。盘点调整必须和库存更新在同一个事务里防止出现盘点单生成了但库存没有调整的情况。实务中盘点一般会锁仓也就是盘点期间停止该仓库的日常出入库否则边盘点边出入库会让差异永远对不上。系统实现的常见做法是在盘点单上增加状态字段审核后自动锁仓盘点完成再解锁。5.2 批次与有效期预警涉及食品、药品或者元器件这类有保质期管理的仓库库存模型里要加商品批次表记录batch_code、production_date、expire_date、quantity和所在仓库。批次表让库存从单维度升级为多维度同一个商品同一仓库下不同批次的库存分开管理出库时优先选择过期日期最近的批次也就是先进先出策略。如果不做批次管理有效期预警就无从谈起。SELECT goods_name, batch_code, quantity, expire_date, DATEDIFF(expire_date, CURDATE()) AS remain_days FROM goods_batch WHERE expire_date BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 30 DAY) ORDER BY expire_date ASC;这段SQL查出未来30天内过期的所有批次按剩余天数升序排列业务侧拿到结果后可以发预警通知。INTERVAL 30 DAY是可配置参数不同商品可以设置不同预警周期。批量查询时注意给expire_date字段建索引否则数据量上来后这个查询会全表扫描。批次表的引入会让出库逻辑多一层扣减批次库存的同时也要扣减该商品的总库存两个操作必须放在同一事务中。5.3 报表查询用流水表不要直接查库存表进销存报表最常见的三种是商品进销存汇总表、库存流水明细表、库存台账表。库存台账直接查stock表就可以但进销存汇总表必须从stock_log流水表出发。原因是库存表只有当前一个时间点的值回答不了“本月入库多少、出库多少”这类问题。流水表记录了每一次变动的类型、数量和时间这些字段天然就是报表的数据来源。SELECT DATE_FORMAT(operate_time, %Y-%m-%d) AS biz_date, SUM(IF(operate_type IN (INBOUND, CHECK_IN), qty, 0)) AS in_qty, SUM(IF(operate_type IN (OUTBOUND, CHECK_OUT), qty, 0)) AS out_qty FROM stock_log WHERE goods_id #{goodsId} AND operate_time #{startDate} AND operate_time #{endDate} GROUP BY biz_date ORDER BY biz_date;报表查询的条件一律用和不要用BETWEEN因为BETWEEN可能包含第二天的零点数据。这段SQL返回的是每天入库和出库总量Java侧从上一个时点的期末库存开始累加逐行计算每天的期末库存公式是期末库存 期初库存 当日入库 - 当日出库。期初库存怎么取可以从stock_log里查询开始日期之前最后一笔流水发生后的结存量也可以直接查询stock表当前值再倒推具体方式取决于系统是否保留了历史快照。这个计算逻辑建议在Service层做不要把期初库存硬编码进SQL。6. 把Spring Boot进销存项目交接出去的3个细节多环境配置、索引优化与打包部署6.1 多环境配置与启动参数开发、测试、生产三个环境的数据源地址和密码都不一样不要每次部署都改application.yml。Spring Boot支持spring.profiles.active参数动态切换环境工程里按应用名加环境名拆分配置文件。application-dev.yml放本地开发数据库application-prod.yml放生产库公共配置留在application.yml里。spring: profiles: active: dev启动时通过命令行参数覆盖java -jar inventory.jar --spring.profiles.activeprod。生产环境的数据库密码不要明文写在yml文件里改成环境变量引用比如password: ${DB_PASSWORD}这样即使代码仓库泄露也不会把生产密码带出去。6.2 索引优化与数据库版本管理进销存系统数据量上去之后慢查询基本都集中在三张表stock_log按时间范围查、inbound_order和outbound_order按单号查、stock按仓库和商品查。补上下面三个索引大部分报表和单据查询都能走索引。stock的唯一索引在建表时已经有了这组索引可以再验证一次是否生效。ALTER TABLE stock_log ADD INDEX idx_log_time (operate_time); ALTER TABLE stock_log ADD INDEX idx_log_order_no (order_no); ALTER TABLE inbound_order ADD INDEX idx_order_warehouse (warehouse_id, operate_time);数据库初始化不要靠手工执行SQL脚本。本地开发可以用spring.sql.init配合schema.sql和data.sql自动建表初始化但生产环境推荐用Flyway把建表SQL纳入版本管理升级时自动执行增量脚本。进销存项目如果上了生产再改表结构没有Flyway会很痛苦少一张列多一个字段全靠人工操作迟早出问题。6.3 三种打包部署方式与安全提示打包统一用Maven命令Idea右侧Maven面板或者命令行都行。打包前先跑一遍测试没有测试的也要-DskipTests跳过避免测试类影响打包结果。mvn clean package -DskipTests java -jar target/inventory-0.0.1-SNAPSHOT.jar --spring.profiles.activeprod默认Spring Boot打成可执行jar包直接java -jar运行。如果甲方服务器要求部署到Tomcat的webapps目录需要改成war包pom.xml里把spring-boot-starter-tomcat设为provided启动类继承SpringBootServletInitializer并重写configure方法。Spring Boot 2.7对应Tomcat 9Spring Boot 3.x对应Tomcat 10部署时注意Tomcat版本兼容。容器化部署则用Dockerfile基础镜像选openjdk:8-jre-alpine把jar复制进镜像后指定启动命令。FROM openjdk:8-jre-alpine COPY target/inventory.jar /app/inventory.jar ENTRYPOINT [java, -jar, /app/inventory.jar, --spring.profiles.activeprod]最后一个容易被忽略的安全点如果工程引入了spring-boot-starter-actuator应用程序会暴露一堆监控端点包括heapdump。heapdump会把JVM堆内存里所有对象导出来进销存系统里的用户名、token、数据库连接信息都在内存中这个端点不关会直接导致敏感信息泄露。生产环境至少加一行配置把非必要端点关闭management.endpoints.web.exposure.includehealth,info不要图省事暴露所有端点。部署完成后访问/actuator/health确认服务状态再跑一遍入库、出库、盘点三条核心链路项目才算真正能交付。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进