简介:包含源码、原型与数据库的水果库存管理系统资源包,面向需要完成课程设计、毕业设计、系统开发练习或学习库存管理流程的读者,可作为从零搭建同类小型系统的起步素材。资源共4个文件,压缩包仅1.08兆字节,内含数据库脚本、数据库备份文件、源码压缩包及说明文档:脚本与备份文件可直接恢复数据表结构,源码压缩包内为系统实现代码,说明文档用于了解原型界面与使用逻辑,整体轻量且层次清晰。内容围绕水果库存的入库、出库、查询与盘点等常见业务,读者可对照源码理解模块划分,借助说明文档把握界面交互,通过数据库脚本快速还原初始数据,缩短二次开发与调试时间。目前已有1518人学习下载,适合作为课程设计、期末项目或答辩展示的参考蓝本,尤其对初学者厘清数据库、后端逻辑与前端原型之间的协作关系很有帮助。整套资料按源码、原型、数据库分层组织,便于按需查阅和二次扩展。
1. 水果库存管理系统,为什么要“源码+原型+数据库”三件套
做课程设计或给生鲜门店做内部管理工具时,“源码+原型+数据库 水果库存管理系统”的交付清单很常见。可代码能跑、答辩时依然被要求重做的情况太多了:原型里没标库存规则,数据库没有批次字段,一并发就负库存。这套组合的价值,是在编码前把业务漏洞暴露出来,而不是上线后靠手工改数据救火。
水果库存比日用品库存麻烦在两点:损耗和批次。进货按箱、出货按斤要换算;同一款苹果不同日期进的货,保质期和成本都不一样。一个能交付的水果库存管理系统,必须把这两点体现在原型、数据库字段和源码逻辑里。适合在准备课程设计、毕业设计的学生,以及要做生鲜轻量库存工具的开发者。
顺序是关键。先画原型、再建表、最后写代码;跳过原型直接建表,写代码时大概率发现缺字段,回头改表。
2. 从水果损耗倒推数据库设计:这张库存表该怎么建
2.1 业务规则才是表结构的来源
水果库存系统里最容易被写错的,是“库存数量”这个字段。老师在评审时经常会追问:账面库存、可用库存、实际库存分别是什么?如果系统只给一个字段,等于把这三个概念混在一起。账面库存是累计进货减去累计出货,可用库存还要扣除锁定和过期批次的数量,实际库存靠盘点校准——因为搬运损耗、腐烂损耗都会让账面和实物对不上。
我习惯从这样的业务规则反推表,最少要7张:用户表、水果分类表、水果信息表、入库单表、入库明细表、出库单表、盘点记录表。再附带一张库存快照表用于承载并发扣减。这个规模对于课程设计和门店小系统来说不多不少,每张表都有明确归属,文档也好写。
这几张表的关系很直观:分类表一对多水果信息,水果信息一对多入库明细和出库明细,盘点表按水果加批次记录。
2.2 MySQL建表的具体写法与参数说明
MySQL 5.7+InnoDB就够用。核心表结构要避免两个典型错误:数量用浮点数、批号不单独建字段。我给出直接可用的建表SQL,字段注释已经写进去,方便写文档时原样引用。
CREATE TABLE `fruit_category` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `name` VARCHAR(64) NOT NULL COMMENT '分类名, 如热带水果', `sort_order` INT NOT NULL DEFAULT 0 COMMENT '展示排序', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='水果分类表';CREATE TABLE `fruit_info` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `category_id` INT UNSIGNED NOT NULL COMMENT '所属分类ID', `name` VARCHAR(64) NOT NULL COMMENT '水果名称', `shelf_life_days` INT NOT NULL DEFAULT 7 COMMENT '保质期天数', `stock_unit` VARCHAR(16) NOT NULL DEFAULT '斤' COMMENT '零售单位', `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_category` (`category_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='水果信息表';这里把shelf_life_days放在fruit_info而不是批次表,是默认同一水果保质期一致;如果同一水果不同产地保质期不同,就把它挪到批次表。对课程设计来说,放fruit_info更简单。
CREATE TABLE `in_stock_detail` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `in_stock_id` INT UNSIGNED NOT NULL COMMENT '入库单ID', `fruit_id` INT UNSIGNED NOT NULL, `batch_no` VARCHAR(32) NOT NULL COMMENT '批次号, 如B20250101A', `quantity` DECIMAL(10,2) NOT NULL COMMENT '入库数量(斤)', `purchase_price` DECIMAL(10,2) NOT NULL COMMENT '进货单价', `produced_at` DATE DEFAULT NULL COMMENT '采摘日期', PRIMARY KEY (`id`), KEY `idx_batch` (`batch_no`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='入库明细表';quantity用DECIMAL(10,2)而不是FLOAT,是为了避免浮点数累加导致的账面偏差。batch_no单独成一个字段,这是后来做保质期预警和先进先出的基础,不能省。
CREATE TABLE `stock_snapshot` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `fruit_id` INT UNSIGNED NOT NULL, `batch_no` VARCHAR(32) NOT NULL, `available_qty` DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT '可用库存', `locked_qty` DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT '锁定库存', `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_fruit_batch` (`fruit_id`, `batch_no`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='库存快照表';快照表是并发扣减的关键。UNIQUE KEY(fruit_id, batch_no)让数据天然按水果+批次唯一,后续使用INSERT...ON DUPLICATE KEY UPDATE或者UPDATE ... WHERE都方便锁定目标行。locked_qty字段虽然课程设计里用得少,但保留它会让表结构更接近真实进销存。
2.3 盘点表不能省:把盘盈盘亏变成可审计的调整单
很多课程设计实现会在界面上放一个“手动修改库存”的按钮,直接在快照表改数量。这种做法在答辩时很难解释清楚:你改的依据是什么?差异怎么留下记录?正确做法是建盘点表,让每一次盘盈盘亏都成为一张单据。
CREATE TABLE `stocktaking_record` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `fruit_id` INT UNSIGNED NOT NULL, `batch_no` VARCHAR(32) NOT NULL, `book_qty` DECIMAL(10,2) NOT NULL COMMENT '盘点前账面数', `actual_qty` DECIMAL(10,2) NOT NULL COMMENT '实际盘点数', `diff_qty` DECIMAL(10,2) NOT NULL COMMENT '差异=实际-账面', `reason` VARCHAR(255) NOT NULL DEFAULT '正常损耗' COMMENT '损耗原因', `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_fruit_batch` (`fruit_id`, `batch_no`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='盘点记录表';盘点流程是:先锁定一行快照读账面数,操作员录入实际数,系统计算diff_qty并写一条盘点记录,再在同一事务中把快照的available_qty加上diff_qty。
2.4 统一字符集与字段类型的三个约定
第一,所有表统一utf8mb4。第二,金额与数量统一DECIMAL(10,2),入库明细和快照表不能混用FLOAT。第三,时间字段用DATETIME。这三个约定在写代码前就锁死,能规避后面一大类乱码和金额偏差问题。索引方面,入库明细和出库明细都要保留idx_in_stock_id、idx_fruit_id,如果查询经常带时间范围,再加复合索引(fruit_id, created_at)。
3. 用Axure把页面和交互规则画清楚:原型做到哪一步算合格
3.1 水果库存原型最少要有3类页面
Axure原型的核心不是画得漂亮,而是让评审的人看到系统怎么操作。我建议页面按三类划分:列表页(水果列表、入库单列表、出库单列表)、表单页(新建入库、新建出库、盘点录入)、状态反馈页(库存预警、操作成功/失败提示)。这三类基本覆盖库存管理系统的主要交互流程。
水果列表页的表格列建议直接对齐数据库字段:名称、分类、批次号、可用库存、锁定库存、剩余保质期天数。不要在原型里只放一个“库存数”字段——答辩时对方一定会问同一款水果不同批次怎么区分。用Axure中继器(Repeater)模拟这个表格,并为每个批次行加上“查看流水”的按钮,列表页的可信度就能上来。
表单页里,新建出库单是重中之重。页面要包含:选择水果(下拉联动)、选择批次(联动显示剩余量与单价)、输入出库数量、提交。这里需要把联动关系用交互事件做出来,中继器模拟真实数据后再决定提交按钮是否可用。
3.2 把“可用库存”和“锁定库存”写进交互规则
原型里最容易偷懒的做法,是在输入框边上加一行注释:“此处要校验库存”。交互规则应该在原型里直接体现,而不是留给开发去猜。
具体做法:在Axure中给“出库数量”输入框设置文本改变交互。从当前行中继器中读取available_qty和locked_qty,计算可用数量为两者差值。当输入数量大于可用数量时,弹出一个红色提示框,并把“提交”按钮置灰。这个逻辑在Axure里用条件判断和局部变量就能实现,不要求写JavaScript,但必须让评审人员直观看到校验存在。
给每个批次行做一个“锁定确认”按钮也值得推荐。在某些场景下,用户先锁定一批货防止被别人买走,这个操作会让locked_qty增加。原型中把这个按钮放出来,会让后续写源码时的状态字段说明更有说服力。
这里要顺带说清原型和demo的区别。demo是给外行看效果的半成品,原型是给开发看规则的设计交付物。哪怕用AI辅助生成Axure原型,最终也要在这一步把校验规则补齐,否则AI生成得再漂亮,开发也拿不到约束条件。
3.3 角色权限在原型里怎么做
店长能看到进货成本、毛利报表;收银员只能看到销售操作和库存数。这个差异在原型里可以用两个母版(Master)来实现。Axure Master的好处是,切换角色时只需要替换导航栏和表格中可见列,不用复制整套页面。
实现方式是在每个页面引入一个“当前用户类型”变量,在列表页表格列的中继器过滤条件中写上:如果用户类型是收银员,就隐藏“进货单价”列。这套操作虽然会花一点时间,但拿着它去评审时,能直接说明你对权限模型有思考。
记得把权限模型写进数据库设计,也就是用户表加一个role字段。
3.4 原型评审时容易被人盯住的5个交互细节
以下5个细节在演示时被点名的概率最高,建议在原型评审前逐项与页面核对:
首先是单位换算。入库按箱、出库按斤,页面必须提供“箱到斤”的换算控件,在表单页明确标注换算率,否则系统上线后一线操作员会困惑。
第二是空态页面。比如没有批次数据时,列表页不要显示空表格,要显示“请先入库”的引导按钮。
第三是负数限制。出库数量输入框要限制只能输入数字,并且当可用数量不足时明确提示剩余量。
第四是回执反馈。提交出库单成功后,页面要显示“扣减成功,当前剩余XX斤”,而不是笼统的“操作成功”。
第五是未完成录入的保护。用户在新建入库单时,如果点击离开页面,要弹出“当前单据未保存”的提示。
把这5个点画进原型后,评审通过率会高很多,后续编码阶段也不需要反复回来改交互。
4. 用PHP+MySQL写最小可跑源码:三个关键接口的落地过程
4.1 项目结构、MySQL的数据库连接池与PDO参数
项目不大,我建议直接用PHP 7.4加原生PDO,不引入框架。目录结构保持清晰:api/放接口文件,includes/放数据库连接和公共函数,pages/放简单的后台页面,sql/放建表脚本。这样课程设计文档里画模块图、写功能说明都很方便。
数据库连接时,要注意连接池和连接复用的区别。真实场景里会用专门的连接池组件管理连接,但在这个量级下,打开PDO的持久连接就能满足需求,效果接近轻量连接池。
<?php // includes/db.php $host = '127.0.0.1'; $port = '3306'; $dbname = 'fruit_inventory'; $user = 'fruit_app'; $pass = 'Fruit_2025'; $dsn = "mysql:host=$host;port=$port;dbname=$dbname;charset=utf8mb4"; $options = [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false, PDO::ATTR_PERSISTENT => true, ]; try { $pdo = new PDO($dsn, $user, $pass, $options); } catch (PDOException $e) { http_response_code(500); exit(json_encode(['code' => 500, 'msg' => '数据库连接失败'])); }参数说明:charset=utf8mb4写进DSN是解决中文乱码的第一道防线。ATTR_EMULATE_PREPARES设为false,让PDO使用MySQL原生预处理,既能防SQL注入,也能让整数和字符串参数被MySQL正确区分。ATTR_PERSISTENT为true让PHP-FPM进程复用PDO连接,避免每次请求重复握手建立新连接。这里要留意持久连接与事务的配合:使用持久连接时,事务结束务必提交或回滚,否则连接归还池子时会残留未关闭的事务状态。
4.2 进货接口:入单写明细、快照累加
进货接口接收一张入库单(头表)和若干明细行(items数组),在同一个事务里完成所有写入。核心是这个逻辑:如果快照表已有同水果同批次的行,就累加数量;如果没有,就新插入一行。
public function doInStock(array $order, array $items): array { $pdo->beginTransaction(); try { // 1. 插入入库单头表 $sql = "INSERT INTO in_stock_order (order_no, supplier, total_amount, operator_id) VALUES (:order_no, :supplier, :total_amount, :operator_id)"; $stmt = $pdo->prepare($sql); $stmt->execute($order); $inStockId = $pdo->lastInsertId(); $totalAmount = 0; // 2. 逐条处理明细 foreach ($items as $item) { // 防止写入不存在的水果ID $fruit = $pdo->prepare("SELECT id FROM fruit_info WHERE id = ?"); $fruit->execute([$item['fruit_id']]); if (!$fruit->fetch()) { throw new RuntimeException("水果ID {$item['fruit_id']} 不存在"); } $batchNo = $this->generateBatchNo($item['fruit_id'], $item['produced_at']); $lineAmount = $item['quantity'] * $item['purchase_price']; $totalAmount += $lineAmount; // 3. 写入库明细 $sqlDetail = "INSERT INTO in_stock_detail (in_stock_id, fruit_id, batch_no, quantity, purchase_price, produced_at) VALUES (?, ?, ?, ?, ?, ?)"; $pdo->prepare($sqlDetail)->execute([ $inStockId, $item['fruit_id'], $batchNo, $item['quantity'], $item['purchase_price'], $item['produced_at'] ]); // 4. 累加快照库存 $sqlUpsert = "INSERT INTO stock_snapshot (fruit_id, batch_no, available_qty, locked_qty) VALUES (?, ?, ?, 0) ON DUPLICATE KEY UPDATE available_qty = available_qty + VALUES(available_qty)"; $pdo->prepare($sqlUpsert)->execute([$item['fruit_id'], $batchNo, $item['quantity']]); } // 5. 回填头表总金额 $pdo->prepare("UPDATE in_stock_order SET total_amount = ? WHERE id = ?") ->execute([$totalAmount, $inStockId]); $pdo->commit(); return ['code' => 0, 'order_id' => $inStockId]; } catch (Throwable $e) { $pdo->rollBack(); return ['code' => 1, 'msg' => $e->getMessage()]; } }逻辑说明:这个接口用beginTransaction包住全部写操作,任何一步抛出异常都会回滚,保证不会出现“头表写了但明细没写”的脏数据。第4步的ON DUPLICATE KEY UPDATE是快照更新的核心,它靠stock_snapshot唯一索引(fruit_id, batch_no)判断是插入还是累加。总金额不在插入时写死,而是在明细全部计算完后回填,避免前端传一个错误的总金额污染数据。
参数说明:execute的数组参数必须与SQL中的占位符数量一一对应。batch_no由服务端生成,格式建议B+日期+序号,比如B20250101A001,这种格式在排障时一眼就能看出是哪天的货。
4.3 出库接口:条件更新防超卖
出库比入库多一个需要处理的点:并发时不能把库存卖成负数。如果先SELECT再UPDATE,两个请求都可能看到剩余5斤,然后同时扣减,最终变成负数。解决方案是把“库存充足”条件放进UPDATE语句本身。
UPDATE stock_snapshot SET available_qty = available_qty - :delta WHERE fruit_id = :fruit_id AND batch_no = :batch_no AND available_qty - locked_qty >= :delta执行这条UPDATE后,如果受影响行数为0,说明该批次可用库存不足以扣减,回滚整个出库事务并返回“库存不足”。这是数据库层面的原子操作,不需要额外加锁,也避免了死锁问题。
出库接口的完整事务里,除了更新快照,还要写out_stock_order和out_stock_detail两张表。建议把出库单号也按日期生成,方便和入库单对账。加上前面列表查询,这三个写接口加一个读接口就把库存系统的数据库增删改查闭环覆盖了。
4.4 盘点接口:差异更新而不是覆盖更新
盘点接口的逻辑是三段式:锁行读账面、计算差异、写记录并更新快照。这里有一个面试和答辩都爱问的点:为什么不是直接把actual_qty覆盖到快照表?因为如果直接覆盖,就无法区分这次差异到底是销售造成的还是损耗造成的,审计信息就丢了。
// 盘点更新的核心片段 foreach ($items as $item) { $stmt = $pdo->prepare("SELECT available_qty FROM stock_snapshot WHERE fruit_id = ? AND batch_no = ? FOR UPDATE"); $stmt->execute([$item['fruit_id'], $item['batch_no']]); $bookQty = $stmt->fetchColumn(); $diff = $item['actual_qty'] - $bookQty; $stmt = $pdo->prepare("INSERT INTO stocktaking_record (fruit_id, batch_no, book_qty, actual_qty, diff_qty, reason) VALUES (?, ?, ?, ?, ?, ?)"); $stmt->execute([ $item['fruit_id'], $item['batch_no'], $bookQty, $item['actual_qty'], $diff, $item['reason'] ]); $stmt = $pdo->prepare("UPDATE stock_snapshot SET available_qty = available_qty + ? WHERE fruit_id = ? AND batch_no = ?"); $stmt->execute([$diff, $item['fruit_id'], $item['batch_no']]); }逻辑说明:FOR UPDATE会在事务期间锁住这一行,防止并发盘点或出库同时修改。在事务里写盘点记录后再更新快照,这两步是一体的,如果中途抛异常,整个事务回滚,快照不会变成不一致状态。
注意,这个代码在盘点批次很多时锁行时间会长,适合小规模门店。如果需要大批量盘点,后续可以改成每批独立事务。
4.5 一个简单的库存列表查询SQL
最后给出一个高频查询——带批次的水果库存列表。这里要区分“表里的快照”和“页面要显示的剩余保质期”,后者通常实时计算。
SELECT f.name AS fruit_name, c.name AS category_name, s.batch_no, s.available_qty, s.locked_qty, DATEDIFF(DATE_ADD(fi.produced_at, INTERVAL f.shelf_life_days DAY), CURDATE()) AS left_days FROM stock_snapshot s LEFT JOIN fruit_info f ON f.id = s.fruit_id LEFT JOIN fruit_category c ON c.id = f.category_id LEFT JOIN in_stock_detail fi ON fi.batch_no = s.batch_no WHERE s.available_qty + s.locked_qty > 0 ORDER BY left_days ASC;为什么这里要LEFT JOIN in_stock_detail取produced_at,而不是在快照表里直接存采摘日期?因为快照表只负责“当前数量”,生产日期属于业务属性,两者分离后,如果同一批次多次入库,日期维护不会冲突。
5. 避坑与排查:连接池、并发扣减、中文字段这些坑逐个踩
5.1 页面中文全部变成问号:先查连接串,再查表字符集
现象:在MySQL命令行下看数据完全正常,但打开PHP页面后中文全部变成“?”。
原因大多是PHP连接MySQL时字符集不一致。MySQL终端默认连接字符集可能和代码里不一致,PDO没有把charset=utf8mb4写进DSN时,连接使用默认字符集,导致中文在传输过程中被破坏。
解决:在DSN里加charset=utf8mb4,并在创建PDO后执行一次SET NAMES utf8mb4。同时确认表和字段的字符集是utf8mb4。排查顺序是:先用SQL检查表字符集,再检查DSN,最后检查HTML的meta charset。如果改完连接串还有问题,确认一下PHP文件本身保存为UTF-8无BOM格式。
5.2 库存被扣成负数:先查事务隔离级别和条件更新
现象:收银高峰期同一个批次的水果,卖出的数量超过了入库数量,库存快照出现负数。
原因在于代码写成“先查后改”:先SELECT可用库存,判断大于0再UPDATE。两个请求同时读到5斤,分别扣了2斤和4斤,第二次更新前没有重新校验,最后变成-1斤。
解决:把条件写进UPDATE语句,使用UPDATE ... SET available_qty = available_qty - ? WHERE available_qty - locked_qty >= ?来判断。如果影响行数为0就回滚。另外确认表引擎是InnoDB,MyISAM不支持行锁,条件更新也保护不了并发。排查时可以用SHOW ENGINE INNODB STATUS查看最近的事务等待记录,确认没有死锁。
5.3 金额对账差出一分钱:FLOAT类型背锅
现象:系统跑了一段时间后,汇总的进货金额和明细表对不上,总是差几分钱。
原因:quantity和purchase_price用了FLOAT或DOUBLE,浮点数在二进制中无法精确表示0.1,累加多了偏差就暴露出来。
解决:把表和接口里的所有金额、数量字段改为DECIMAL(10,2)。历史数据通过ALTER TABLE转换前,先备份原表,并检查有没有视图依赖。金额和数量相关计算禁止使用FLOAT,这是一条可以写进团队规范的红线。
5.4 拿原型验收不了系统:开发和原型对不上
现象:原型评审时甲方确认了交互,结果开发出来的系统页面上找不到对应交互,或者逻辑与原型的文字描述不符。
原因:原型里用注释说明的规则太多,真正的交互没做出来。比如“出库数量不能大于可用库存”只写了一句提示,没有做置灰和提示弹窗,开发就按普通输入框实现了。
解决:评审前用本文前面列出的5个交互细节逐项检查原型,把规则全部做成可操作的交互,不要留注释。代码完成后,把验收清单表格打印出来,逐条对照:弹窗文案、按钮置灰条件、空态引导、回执内容,四项都一致才算通过。
5.5 电脑重启后数据丢失:没有备份和binlog
现象:磁盘故障或误删库后,发现最近的入库出库记录全部丢失,只剩手动导出的那次备份。
原因:数据库没有开启binlog,也没有定时备份,恢复只能靠之前的零星SQL导出。
解决:在MySQL配置中开启binlog,设置保留时间。同时写一个cron每天凌晨导出全量备份。命令大致是mysqldump加--single-transaction和--routines,把导出的SQL放到带日期的目录里。恢复时用mysql < backup.sql回放,如果binlog也存在,就可以恢复到出问题前几分钟的状态。
6. 从能跑到能用:验收清单、压测方法和下一版怎么改
在收尾之前,先用一张验收清单把所有关键功能过一遍,这张表会直接变成你的测试脚本:
| 编号 | 验收项 | 操作方式 | 期望结果 |
|---|---|---|---|
| 1 | 入库单提交 | 新建苹果入库10箱,每箱10斤 | 快照增加100斤,批次号可查 |
| 2 | 出库扣减 | 同一批次出货30斤 | 剩余70斤,出库单可追溯 |
| 3 | 超卖拦截 | 并发出货超过剩余量 | 提示库存不足,库存不为负 |
| 4 | 盘点调整 | 账面100斤实盘95斤 | diff=-5,快照变为95斤 |
| 5 | 中文与特殊字符 | 输入“火龙果”“枣” | 页面显示正常 |
| 6 | 并发压测 | 20个并发出货请求 | 库存总数一致,无超卖 |
第6项压测是最有价值的。先用SQL把某个批次剩余数重置为100斤,然后用ab工具并发发20个出货3斤的请求。预期结果是剩余40斤,如果出现负数或剩余不等于40,说明条件更新没生效或事务没有回滚,需要回到第4章的出库接口排查。
# 准备按照表单格式的请求体文件,用ab模拟并发出货 ab -n 20 -c 10 -T 'application/x-www-form-urlencoded' \ -p outbound.txt http://127.0.0.1:8080/api/out_stock.php参数说明:-n 20是总请求数,-c 10是并发数,-T指定Content-Type为表单格式,-p指定请求体文件。这里的outbound.txt内容是一段fruit_id=1&batch_no=B20250101&quantity=2这样的表单数据。跑完再查stock_snapshot表,核对剩余数量。
压测过后可以顺手做的事,是检查MySQL慢查询日志。把这20个请求过程中耗时的SQL捞出来,看是不是缺失索引导致逐行扫描。
下一版扩展,我会优先加保质期预警。实现不难,在fruit_info和in_stock_detail之间做一次日期计算,把剩余保质期小于3天的批次在列表页标成红色,并生成一张预警表。
我的习惯是每完成一个功能就回到Axure原型里走一遍对应的页面,确认原型描述的交互和代码实现一致。这个习惯救过我很多次,希望帮到你。
本文还有配套的精品资源,点击获取