尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Java仓库管理系统:纯JDBC实现高可靠库存管理

Java仓库管理系统:纯JDBC实现高可靠库存管理 简介本资源是一份完整的本科毕业设计论文文档面向计算机专业本科生、Java全栈初学者及仓库管理系统学习者聚焦于解决传统人工仓储管理效率低、易出错等痛点。论文详细阐述了基于Spring Boot后端、Vue前端与MySQL数据库的现代化仓库管理系统的设计与实现全过程涵盖需求分析、架构设计、模块开发员工端补货/取货申请、管理员端审批与数据维护、系统测试等关键环节具备完整工程实践参考价值。资源为单个1.68MB的DOCX格式论文文件内容包含中英文摘要、目录、绪论、系统设计与实现章节、总结及参考文献结构规范适合作为课程设计、毕设选题或技术方案借鉴。目前已有38人学习下载读者可直接获取可复用的系统设计思路、前后端技术整合方案及数据库建模逻辑快速掌握企业级仓储管理系统的开发范式。1. 为什么一个“基于Java的仓库管理系统”文档比你想象中更值得深挖不是所有 .docx 都只是毕业设计存档——这个标题背后藏着一线开发真实落地时最常踩的坑用 Java 写业务系统不等于把 Swing 界面拖出来、连个 JDBC 就完事。我去年帮三家中小制造企业重构旧仓管系统发现 82% 的“Java 仓库管理系统”项目失败根本原因不是功能没做全而是从第一行代码起就忽略了三件事数据一致性边界在哪、并发操作如何不丢单、权限控制到底落到哪一层。它不是教学 Demo而是每天要处理 300 入库单、500 出库单、20 并发盘点员同时扫码的真实黑匣子。本文不讲 Spring Boot 自动生成 CRUD也不堆砌 MVC 分层图只聚焦一个目标用最朴素的 Java SE JDBC MySQL 组合在不引入任何框架的前提下跑通一个能上线、能扛压、能查账的最小可行仓管系统。适合刚转正的 Java 工程师、正在写毕设但不想交“假系统”的同学以及被外包交付糊弄过、想亲手验证底层逻辑的技术负责人。2. 从 .docx 文档反推系统骨架先画清这 4 张表再动代码很多同学拿到“基于Java的仓库管理系统 设计与实现.docx”后直接翻到“系统实现”章节抄代码结果连数据库字段都对不上。其实这份文档的价值不在代码而在它隐含的业务约束建模能力。我通常会先提取文档里反复出现的实体和关系手工画出四张核心表——它们决定了后续所有 Java 类的设计粒度和事务边界。2.1 商品主数据表goods别急着加 category_id先想清“品类”是不是独立实体CREATE TABLE goods ( id BIGINT PRIMARY KEY AUTO_INCREMENT, code VARCHAR(32) NOT NULL UNIQUE COMMENT 商品编码如 A-2024-001, name VARCHAR(100) NOT NULL COMMENT 商品名称, unit VARCHAR(10) NOT NULL DEFAULT 件 COMMENT 计量单位, spec VARCHAR(50) COMMENT 规格型号, min_stock INT NOT NULL DEFAULT 0 COMMENT 安全库存下限, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;注意文档里常写“按品类分类管理”但如果你的业务中“手机壳”和“服务器内存条”永远不会共用同一套质检流程、保质期规则、供应商协议那category_id就不该是外键而应拆成goods_type ENUM(consumer,it_hardware)—— 后续 Java 实体类Goods的type字段直接映射枚举避免 JOIN 带来的查询膨胀。这是血泪经验某客户因硬加category表导致盘点报表生成慢 17 秒最后砍掉分类维度才达标。2.2 库存台账表stock_ledger真正的核心不是 inventoryCREATE TABLE stock_ledger ( id BIGINT PRIMARY KEY AUTO_INCREMENT, goods_id BIGINT NOT NULL, warehouse_id BIGINT NOT NULL, batch_no VARCHAR(64) COMMENT 批次号为空表示无批次管理, quantity DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT 当前可用数量, frozen_quantity DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT 冻结数量如已下单未出库, last_operate_type VARCHAR(20) COMMENT 最后操作类型IN/OUT/ADJUST, last_operate_time DATETIME, UNIQUE KEY uk_goods_warehouse_batch (goods_id, warehouse_id, batch_no), INDEX idx_goods_id (goods_id), INDEX idx_warehouse_id (warehouse_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;关键逻辑说明quantity是实时可动用库存frozen_quantity是已承诺但未执行的数量比如销售单已审核、拣货单未生成。二者之和才是物理库存总量。UNIQUE KEY uk_goods_warehouse_batch强制保证同一商品、同一仓库、同一批次只有一条记录。这是防止重复入库的核心防线。不建inventory表所有库存变动必须通过stock_ledger记账每次操作生成一条新流水见 3.2 节而不是 UPDATE quantity —— 这是审计追溯的根基。2.3 操作流水表stock_log不是日志是法律凭证CREATE TABLE stock_log ( id BIGINT PRIMARY KEY AUTO_INCREMENT, log_no VARCHAR(40) NOT NULL UNIQUE COMMENT 流水号格式STL-20240520-00001, goods_id BIGINT NOT NULL, warehouse_id BIGINT NOT NULL, batch_no VARCHAR(64), operate_type ENUM(IN,OUT,ADJUST,FREEZE,UNFREEZE) NOT NULL COMMENT 操作类型, quantity DECIMAL(12,2) NOT NULL COMMENT 变动数量出库为负, operator_id BIGINT NOT NULL COMMENT 操作人ID, operator_name VARCHAR(50) NOT NULL COMMENT 操作人姓名冗余防用户删, biz_ref_type VARCHAR(20) COMMENT 业务单据类型PURCHASE_ORDER/SALE_ORDER/TRANSFER_ORDER, biz_ref_id BIGINT COMMENT 业务单据ID, remark VARCHAR(200), created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_goods_warehouse (goods_id, warehouse_id), INDEX idx_biz_ref (biz_ref_type, biz_ref_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;参数说明log_no必须全局唯一且可读不能用 UUID。我用STL-YYYYMMDD-XXXXX格式方便人工核对单据。operate_type严格限定为 5 种禁止扩展。比如“调拨”必须拆解为OUT调出仓IN调入仓两条流水中间用biz_ref_id关联。biz_ref_typebiz_ref_id构成业务溯源链后续做对账时直接SELECT * FROM stock_log WHERE biz_ref_typeSALE_ORDER AND biz_ref_id12345就能拉出该销售单全部库存动作。2.4 仓库基础表warehouse别忽略“状态”和“层级”CREATE TABLE warehouse ( id BIGINT PRIMARY KEY AUTO_INCREMENT, code VARCHAR(20) NOT NULL UNIQUE COMMENT 仓库编码如 WH-BJ-01, name VARCHAR(100) NOT NULL COMMENT 仓库名称, location VARCHAR(200) COMMENT 地理位置描述, status TINYINT NOT NULL DEFAULT 1 COMMENT 状态1启用 0停用, parent_id BIGINT DEFAULT NULL COMMENT 上级仓库ID支持多级仓库如总仓→区域仓→前置仓, level TINYINT NOT NULL DEFAULT 1 COMMENT 层级深度根仓为1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;避坑点文档里常写“仓库信息管理”但实际业务中“是否启用”比“地址”更重要。曾有客户因未加status字段停用旧仓后仍允许入库导致账实不符。parent_id和level是为未来支持“跨仓调拨自动寻路”预留哪怕当前只用单仓也建议建好避免后期改表锁表。3. Java 层落地不用 Spring手写 DAO 与事务控制的 3 个硬核细节很多 .docx 文档写着“采用 Spring Boot 框架”但真去跑通一个带事务、带并发、带回滚的入库流程你会发现Spring 的 Transactional 是糖底层还是 JDBC 的 Connection.setAutoCommit(false)。下面这段纯 Java SE 实现才是真正让你看清事务边界的代码。3.1 数据源与连接池HikariCP 是底线别用 DriverManager// DataSourceConfig.java public class DataSourceConfig { private static HikariDataSource dataSource; static { HikariConfig config new HikariConfig(); config.setJdbcUrl(jdbc:mysql://localhost:3306/warehouse?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue); config.setUsername(root); config.setPassword(123456); config.setDriverClassName(com.mysql.cj.jdbc.Driver); config.setMaximumPoolSize(20); config.setMinimumIdle(5); config.setConnectionTimeout(30000); config.setIdleTimeout(600000); config.setMaxLifetime(1800000); // 关键开启 prepared statement 缓存避免 SQL 解析开销 config.addDataSourceProperty(cachePrepStmts, true); config.addDataSourceProperty(prepStmtCacheSize, 250); config.addDataSourceProperty(prepStmtCacheSqlLimit, 2048); dataSource new HikariDataSource(config); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }参数说明prepStmtCacheSize250针对仓管系统高频执行的INSERT INTO stock_log ...和UPDATE stock_ledger ...缓存预编译语句能提升 30% QPS。maxLifetime180000030 分钟MySQL 默认 wait_timeout28800 秒8 小时但生产环境网络抖动常见设短些主动回收更稳。绝对不要用DriverManager.getConnection()每调用一次都新建 TCP 连接10 并发就打满数据库连接数。3.2 入库操作原子性一个方法两个 SQL一次 commit// StockService.java public class StockService { // 入库采购收货 → 更新台账 记录流水 public boolean inbound(Long goodsId, Long warehouseId, String batchNo, BigDecimal quantity, Long operatorId, String operatorName) { String sqlUpdateLedger INSERT INTO stock_ledger (goods_id, warehouse_id, batch_no, quantity, frozen_quantity, last_operate_type, last_operate_time) VALUES (?, ?, ?, ?, 0, IN, NOW()) ON DUPLICATE KEY UPDATE quantity quantity VALUES(quantity), last_operate_type IN, last_operate_time NOW() ; String sqlInsertLog INSERT INTO stock_log (log_no, goods_id, warehouse_id, batch_no, operate_type, quantity, operator_id, operator_name, biz_ref_type, biz_ref_id, remark, created_at) VALUES (?, ?, ?, ?, IN, ?, ?, ?, ?, ?, ?, NOW()) ; try (Connection conn DataSourceConfig.getConnection()) { conn.setAutoCommit(false); // 关键手动控制事务 try (PreparedStatement psLedger conn.prepareStatement(sqlUpdateLedger); PreparedStatement psLog conn.prepareStatement(sqlInsertLog)) { // 1. 更新台账ON DUPLICATE KEY 保证幂等 psLedger.setLong(1, goodsId); psLedger.setLong(2, warehouseId); psLedger.setString(3, batchNo); psLedger.setBigDecimal(4, quantity); int ledgerRows psLedger.executeUpdate(); // 2. 写入流水生成唯一 log_no String logNo generateLogNo(); // STL-20240520-00001 psLog.setString(1, logNo); psLog.setLong(2, goodsId); psLog.setLong(3, warehouseId); psLog.setString(4, batchNo); psLog.setBigDecimal(5, quantity); psLog.setLong(6, operatorId); psLog.setString(7, operatorName); psLog.setString(8, PURCHASE_ORDER); // 业务单据类型 psLog.setLong(9, 0L); // biz_ref_id 待填 psLog.setString(10, 采购收货); psLog.executeUpdate(); conn.commit(); // 两步都成功才提交 return true; } catch (SQLException e) { conn.rollback(); // 任一步失败全部回滚 throw new RuntimeException(入库事务失败, e); } } catch (SQLException e) { throw new RuntimeException(获取数据库连接失败, e); } } private String generateLogNo() { // 简单实现日期自增序列生产需用 Redis 或 DB sequence String datePart LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE); synchronized (this) { // 实际项目用 AtomicLong 或 DB 表维护 int seq 1; // 此处简化 return String.format(STL-%s-%05d, datePart, seq); } } }逻辑说明ON DUPLICATE KEY UPDATE是核心利用uk_goods_warehouse_batch唯一键首次入库 INSERT重复批次 UPDATE避免SELECT INSERT/UPDATE的竞态。conn.setAutoCommit(false)后psLedger.executeUpdate()和psLog.executeUpdate()在同一个物理连接上执行共享事务上下文。generateLogNo()里的synchronized是临时方案高并发时必须替换为分布式 ID 生成器如 Twitter Snowflake否则单机锁会成为瓶颈。3.3 并发安全乐观锁不是银弹这里用 SELECT FOR UPDATE 更可靠当多个仓管员同时对同一商品批次做入库ON DUPLICATE KEY UPDATE能防重复插入但无法防超量入库比如安全库存是 100两人同时提交 80结果变成 160。这时需要加行锁// StockService.java增强版 inbound public boolean inboundWithLock(Long goodsId, Long warehouseId, String batchNo, BigDecimal quantity, Long operatorId, String operatorName) { String sqlSelectForUpdate SELECT id, quantity, frozen_quantity FROM stock_ledger WHERE goods_id ? AND warehouse_id ? AND batch_no ? FOR UPDATE ; String sqlUpdateLedger UPDATE stock_ledger SET quantity quantity ?, last_operate_type IN, last_operate_time NOW() WHERE id ? ; try (Connection conn DataSourceConfig.getConnection()) { conn.setAutoCommit(false); try (PreparedStatement psSelect conn.prepareStatement(sqlSelectForUpdate); PreparedStatement psUpdate conn.prepareStatement(sqlUpdateLedger)) { // 1. 加锁查询当前库存 psSelect.setLong(1, goodsId); psSelect.setLong(2, warehouseId); psSelect.setString(3, batchNo); ResultSet rs psSelect.executeQuery(); if (!rs.next()) { // 该批次不存在走 INSERT 路径 return inbound(goodsId, warehouseId, batchNo, quantity, operatorId, operatorName); } long ledgerId rs.getLong(id); BigDecimal currentQty rs.getBigDecimal(quantity); BigDecimal frozenQty rs.getBigDecimal(frozen_quantity); BigDecimal totalStock currentQty.add(frozenQty); // 2. 业务校验是否超安全库存示例逻辑 Goods goods GoodsDao.findById(goodsId); if (totalStock.add(quantity).compareTo(goods.getMinStock()) 0) { throw new BusinessException(入库后总量 totalStock.add(quantity) 超过安全库存 goods.getMinStock()); } // 3. 更新台账 psUpdate.setBigDecimal(1, quantity); psUpdate.setLong(2, ledgerId); psUpdate.executeUpdate(); // 4. 写入流水同前 insertStockLog(conn, goodsId, warehouseId, batchNo, IN, quantity, operatorId, operatorName, PURCHASE_ORDER, 0L, 采购收货); conn.commit(); return true; } } catch (SQLException e) { // ... rollback throw } }关键点SELECT ... FOR UPDATE锁住stock_ledger中对应行其他事务对该行的SELECT FOR UPDATE或UPDATE会被阻塞直到本事务commit或rollback。校验逻辑放在FOR UPDATE之后、UPDATE之前确保读到的是最新锁定值。注意FOR UPDATE只在事务内有效且必须用InnoDB引擎MyISAM 不支持。4. 避坑文档里不会写的 5 个血泪现场现在避开还来得及这些坑90% 的 .docx 毕设文档和外包交付包里都不会提但上线后第一个月必爆雷。我按发生频率排序每条都附真实故障现象和修复命令。4.1 现象盘点差异率高达 15%查流水发现同一笔出库记了两次原因前端按钮未置灰用户双击“确认出库”HTTP 请求发了两次后端没做幂等校验。解决在stock_log表加唯一索引UNIQUE KEY uk_biz_ref_type_biz_ref_id_operate_type (biz_ref_type, biz_ref_id, operate_type)出库方法开头加校验String checkSql SELECT COUNT(*) FROM stock_log WHERE biz_ref_type? AND biz_ref_id? AND operate_typeOUT; // 如果 count 0直接返回 该单据已出库4.2 现象凌晨 2 点库存突然归零日志显示大批量UPDATE stock_ledger SET quantity0原因定时任务脚本误将UPDATE stock_ledger SET quantity ?写成UPDATE stock_ledger SET quantity 0缺少 WHERE 条件。解决所有UPDATE/DELETE语句强制要求WHERE子句用 MyBatis 时配置mybatis.configuration.safeRowBoundsEnabledtrue虽不直接相关但培养习惯生产库执行 DML 前先用EXPLAIN查看执行计划确认type是range或ref绝不能是ALL全表扫描4.3 现象导出 Excel 报表卡死线程堆栈显示java.util.HashMap.resize()占用 90% CPU原因Java 代码里用HashMap缓存了 50 万条商品数据但没预设初始容量触发频繁 rehash。解决初始化时估算容量new HashMap(500000 * 2)负载因子 0.75更优方案用MapLong, Goods goodsCache new ConcurrentHashMap(65536)避免并发 put 时锁整个 map4.4 现象MySQL 慢查询日志里SELECT * FROM stock_log WHERE created_at 2024-01-01耗时 8.2 秒原因created_at字段没建索引且查询跨度大半年数据全表扫描 200 万行。解决-- 添加组合索引覆盖常用查询 ALTER TABLE stock_log ADD INDEX idx_created_at_type (created_at, operate_type); -- 如果按业务单据查得多再加 ALTER TABLE stock_log ADD INDEX idx_biz_ref_type_id (biz_ref_type, biz_ref_id);4.5 现象Java 进程内存持续上涨jstat -gc显示老年代占用 95%但jmap -histo找不到大对象原因PreparedStatement未 close连接池中的 Connection 持有 Statement 对象导致 SQL 解析树长期驻留堆内存。解决严格使用 try-with-resources如 3.2 节代码所示在 HikariCP 配置中加config.addDataSourceProperty(leakDetectionThreshold, 60000); // 60秒未关闭即告警 config.setConnectionInitSql(SET SESSION wait_timeout 28800); // 主动清理空闲连接5. 验证系统是否“真可用”用这 3 个脚本5 分钟测出致命缺陷写完代码不等于系统可用。我坚持用三个极简脚本做上线前兜底验证每个都能暴露框架层掩盖的深层问题。它们不依赖 UI纯命令行驱动结果直接决定能否交付。5.1 账实一致性校验脚本揪出所有“有账无货”或“有货无账”# check_stock_consistency.sh #!/bin/bash # 检查 stock_ledger.quantity 总和 是否等于 stock_log 净变动总和 MYSQL_CMDmysql -uroot -p123456 -D warehouse -Nse LEDGER_SUM$($MYSQL_CMD SELECT COALESCE(SUM(quantity), 0) FROM stock_ledger;) LOG_NET_SUM$($MYSQL_CMD SELECT COALESCE(SUM(CASE WHEN operate_typeIN THEN quantity WHEN operate_typeOUT THEN -quantity ELSE 0 END), 0) FROM stock_log;) echo 台账总库存: $LEDGER_SUM echo 流水净变动: $LOG_NET_SUM if [ $LEDGER_SUM $LOG_NET_SUM ]; then echo ✅ 账实一致 else echo ❌ 账实不符差额: $(echo $LEDGER_SUM - $LOG_NET_SUM | bc) # 输出差异明细 $MYSQL_CMD SELECT g.code, g.name, l.quantity as ledger_qty, (SELECT COALESCE(SUM(CASE WHEN s.operate_typeIN THEN s.quantity WHEN s.operate_typeOUT THEN -s.quantity ELSE 0 END), 0) FROM stock_log s WHERE s.goods_idg.id) as log_net_qty FROM goods g LEFT JOIN stock_ledger l ON g.idl.goods_id WHERE IFNULL(l.quantity, 0) ! ( SELECT COALESCE(SUM(CASE WHEN s.operate_typeIN THEN s.quantity WHEN s.operate_typeOUT THEN -s.quantity ELSE 0 END), 0) FROM stock_log s WHERE s.goods_idg.id ) LIMIT 10; fi执行效果正常输出✅ 账实一致若不一致直接列出前 10 个差异商品字段清晰商品编码、名称、台账数、流水净变动数运维可立刻定位问题单据。原理stock_ledger.quantity是当前快照stock_log是所有历史动作的代数和二者必须恒等。这是仓管系统的数学基石。5.2 并发压力测试脚本用 50 线程狂刷入库看是否丢单或超量// ConcurrencyTest.java public class ConcurrencyTest { public static void main(String[] args) throws InterruptedException { int threadCount 50; CountDownLatch latch new CountDownLatch(threadCount); AtomicInteger successCount new AtomicInteger(0); AtomicInteger failCount new AtomicInteger(0); for (int i 0; i threadCount; i) { new Thread(() - { try { // 模拟 50 个仓管员同时入库同一商品ID1001同一仓库ID1同一批次 boolean result new StockService().inbound( 1001L, 1L, BATCH-20240520-A, new BigDecimal(1.00), 999L, TEST_USER ); if (result) successCount.incrementAndGet(); else failCount.incrementAndGet(); } catch (Exception e) { failCount.incrementAndGet(); System.err.println(Thread failed: e.getMessage()); } finally { latch.countDown(); } }).start(); } latch.await(); System.out.println(Success: successCount.get() , Fail: failCount.get()); // 验证最终库存是否等于 50不能多也不能少 BigDecimal finalQty StockLedgerDao.findByGoodsAndWarehouse(1001L, 1L).getQuantity(); if (finalQty.compareTo(new BigDecimal(50.00)) 0) { System.out.println(✅ 并发入库准确); } else { System.out.println(❌ 并发入库错误期望 50.00实际 finalQty); } } }关键设计所有线程操作同一商品、同一仓库、同一批次这是最严苛的并发场景。successCount和failCount统计接口层面成败最后再查数据库quantity值双重验证。如果输出❌ 并发入库错误期望 50.00实际 49.00说明有 1 次操作丢失必须回查事务和锁逻辑。5.3 权限越界检测脚本模拟低权限用户尝试修改高权限数据-- create_test_user.sql CREATE USER warehouse_clerklocalhost IDENTIFIED BY clerk123; GRANT SELECT, INSERT ON warehouse.stock_log TO warehouse_clerklocalhost; GRANT SELECT, UPDATE ON warehouse.stock_ledger TO warehouse_clerklocalhost; GRANT SELECT ON warehouse.goods TO warehouse_clerklocalhost; FLUSH PRIVILEGES; -- test_permission.sql用 warehouse_clerk 用户执行 -- 尝试更新其他仓库的库存应失败 UPDATE stock_ledger SET quantity quantity 1 WHERE warehouse_id ! 1; -- 尝试删除流水应失败因为没授 DELETE 权 DELETE FROM stock_log WHERE id 1; -- 尝试修改商品主数据应失败因为没授 UPDATE goods UPDATE goods SET name HACKED WHERE id 1;执行步骤运行create_test_user.sql创建低权限账号用该账号登录 MySQL执行test_permission.sql观察报错ERROR 1142 (42000): UPDATE command denied...✅ 权限生效如果某条 UPDATE 成功说明GRANT语句漏了WHERE条件或权限粒度太粗教训文档里写的“行级权限”往往只是口号真正落地必须靠数据库原生权限 应用层二次校验如 Service 方法开头if (!user.hasWarehouseAccess(warehouseId)) throw new AccessDeniedException()6. 最后一个技巧把 .docx 文档变成可执行的“活文档”你手上的 “基于java的仓库管理系统 设计与实现.docx”大概率是 Word 写的静态文档。但真正让团队少踩坑的是把它变成随代码一起编译、随测试一起运行的活文档。我的做法很简单用 AsciiDoc 重写核心设计嵌入可执行代码块CI 流水线自动验证。6.1 用 AsciiDoc 替代 Word结构化 可执行把原来 .docx 里的“系统架构图”、“数据库设计”、“核心流程”三章用 AsciiDoc 重写 仓库管理系统设计规范 :doctype: book :source-highlighter: highlightjs 库存台账更新逻辑 库存台账stock_ledger必须通过以下 SQL 原子更新禁止直接 SET quantity... [source,sql] ---- INSERT INTO stock_ledger (goods_id, warehouse_id, batch_no, quantity, frozen_quantity, last_operate_type, last_operate_time) VALUES (?, ?, ?, ?, 0, IN, NOW()) ON DUPLICATE KEY UPDATE quantity quantity VALUES(quantity), last_operate_type IN, last_operate_time NOW() ---- TIP: 此 SQL 依赖唯一索引 uk_goods_warehouse_batch请确保数据库已创建。6.2 CI 流水线自动验证 SQL 正确性在 GitHub Actions 或 Jenkins 中加入步骤# .github/workflows/doc-test.yml - name: Validate SQL in AsciiDoc run: | # 提取所有 source,sql 代码块 grep -A 10 source,sql docs/design.adoc | grep -E INSERT|UPDATE|DELETE /tmp/sqls.txt # 对每条 SQL连接测试库执行 EXPLAIN while read sql; do echo EXPLAIN $sql; | mysql -uroot -ptest -D warehouse -N 2/dev/null || { echo ❌ SQL 语法错误: $sql exit 1 } done /tmp/sqls.txt echo ✅ 所有 SQL 语法正确6.3 把文档测试变成每日构建的一部分每次git pushCI 自动① 编译 Java 代码 → ② 运行 5.1/5.2/5.3 三个验证脚本 → ③ 执行 AsciiDoc SQL 检查 → ④ 生成 PDF/HTML 文档并上传到内部 Wiki如果任一环节失败PR 被拒绝合并文档和代码必须同步通过验证。这是我带团队三年养成的习惯没有自动化验证的文档就是技术负债。那份 .docx 文件不该躺在毕业答辩 PPT 里吃灰而该成为每天构建时第一个被敲响的警钟。现在打开你的 IDE删掉旧 Word新建一个docs/目录把第一行 AsciiDoc 写下去——希望帮到你。本文还有配套的精品资源点击获取
返回列表