ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

ThinkPHP+MySQL仓库进销存系统:部署、核心表设计与API对接实践

ThinkPHP+MySQL仓库进销存系统:部署、核心表设计与API对接实践 简介基于ThinkPHP与MySQL构建的完整进销存/仓库管理系统源码包面向需要快速搭建库存业务后台的PHP开发者及中小企业信息化管理人员。系统覆盖采购管理、销售管理、库位管理、库存状况、出入库统计、财务核算等核心环节配备预警提示与语音播报并能对接扫码枪、盘点机及第三方平台内置细致全面的角色权限控制运行环境要求PHP5.6以上。压缩包共863个文件约4.16MB其中447个PHP文件承载主要业务逻辑145个JavaScript与69个HTML文件支撑前端交互与页面展示36个CSS负责样式布局另附2个SQL数据库脚本及若干配置、辅助脚本可直接导入部署目录结构清晰便于二次开发与模块化维护。该资源已有111人学习下载适合具备一定PHP基础、希望借鉴成熟后台业务设计与权限模型的开发者参考可快速理解进销存全流程与报表统计的实现思路减少从零开发的时间成本。1. 仓库进销存系统一套能跑起来的 ThinkPHP MySQL 业务后台仓库最怕的不是货多而是账实两本账对不上。这套基于 ThinkPHP MySQL 的仓库管理进销存系统把采购、销售、仓库、报表、系统管理串成了一条完整单据链源码和数据库一起打包装好 PHP 5.6 以上环境和 MySQL导入 SQL 就能直接登录跑通全流程。它没有炫技成分但模块齐全采购单、销售单、库位管理、库存状况、出入库统计、预警提示甚至带语音播报和扫码枪、盘点机设备入口。适合谁想搞懂真实进销存业务结构的新手开发者或想找一套能改能跑的底子来做二次开发的从业者。花一晚上把菜单逐个点一遍你对这类后台系统的理解会比刷十篇架构文都深。2. 部署三连PHP 版本选型、数据库初始化和 run.bat 启动2.1 环境选型逻辑为什么锚定 PHP 5.6 而不是追新摘要里写得明明白白运行环境要求 PHP5.6 以上。这里多说两句很多人在这一步就开始翻车。这套系统从代码风格看属于 ThinkPHP 3.x 体系当年的控制器和模型写法是沿用 mysql 扩展还是 PDO 取决于配置而 PHP 7.0 开始把 mysql_* 系列函数移除后老代码如果没有切到 mysqli 或 PDO页面会直接报 Fatal error看起来就像整个系统是坏的。所以本地部署我一般默认选 PHP 5.6.40——这是 5.6 分支的最后版本也是和 ThinkPHP 3.2 系列配合最稳的组合。MySQL 的版本同样有讲究。SQL 文件如果是在 MySQL 5.7 时代导出的结构上通常问题不大但 MySQL 8.0 的默认认证插件是 caching_sha2_password老版 ThinkPHP 里用的 PDO 或 mysqli 连接方式不一定认识这种认证协议连接时报错 2059 或 1251 的概率不低。我的习惯是本地统一用 MySQL 5.7把系统跑通了再去考虑 8.0 的兼容改造。Windows 上我用 PHPStudy 或 XAMPP 这类集成环境重点检查两项配置。第一php.ini 需要开启扩展extensionphp_pdo_mysql.dll extensionphp_mbstring.dll extensionphp_gd2.dll第一行负责让 PDO 能连 MySQL这行不开页面会提示找不到数据库驱动第二行处理多字节字符不开会导致 ThinkPHP 在 URL 路由解析时中文参数变乱码第三行是验证码图片的生成依赖不开的话登录页验证码直接裂图。这三项是我每次部署前必查的三板斧。第二MySQL 的 my.ini 字符集设置[mysqld] character-set-serverutf8 collation-serverutf8_general_ci [client] default-character-setutf8这里有个细节如果 SQL 文件里某张表的注释或数据包含 emoji语音播报提示文本里偶尔会出现的特殊符号utf8 是存不进去的需要 utf8mb4。建议先看 SQL 文件里建表语句用的什么字符集再决定 my.ini 怎么配别凭感觉来。2.2 数据库初始化导入 SQL 文件前的三个准备动作拿到源码包第一件事不是打开运行而是把数据库理顺。我每次重置这套环境都强制走三个动作。第一步建库并指定字符集。用命令行或者图形化工具执行CREATE DATABASE IF NOT EXISTS wms DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;注意 DEFAULT CHARACTER SET 必须写进建库语句而不是建完再改。如果库默认继承了服务器的 latin1后面导入的表都会带着 latin1 的祖传属性中文乱码会追着你跑好几个小时。这个库名可以是 wms也可以看 SQL 文件里写的是什么先 grep 一下文件开头有没有 CREATE DATABASE 语句如果有说明导入脚本自带建库逻辑那就不要重复建。第二步导入 SQL 文件。如果资源里拆成了 base.sql 和 data.sql 这类多文件结构先跑结构文件再跑数据文件顺序不能反。mysql -uroot -p wms base.sql mysql -uroot -p wms data.sql导入账号权限不够会直接中断报错大多是 INSERT 时权限不足。开发环境最省事的方式就是 grant 全部权限GRANT ALL PRIVILEGES ON wms.* TO wms_userlocalhost IDENTIFIED BY your_password; FLUSH PRIVILEGES;如果导入中途报 1273 或 1415 这类错误先停一下这不是排序规则问题就是权限问题往下翻第 5 章有专门解法。第三步核对 ThinkPHP 的数据库配置文件。3.x 老结构的位置在 Application/Common/Conf/config.php数组里写着 DB_TYPE、DB_HOST、DB_NAME、DB_USER、DB_PWD、DB_PORT逐个和实际建库信息对一遍。这一步没有技术含量但漏掉任何一个字段页面都会挂在同一句「数据库连接失败」。改完配置后强制清理 Runtime 缓存目录rm -rf Runtime/*.php这个动作是「改了配置不生效」最主要的后悔药。ThinkPHP 3.x 会把配置和编译模板缓存到 Runtime 目录你不删它改十次 config.php 都不会有反应。2.3 run.bat 与入口结构从启动脚本到首次登录项目正文里出现了 run.bat这是作者留的快速启动脚本本地调试时用 PHP 内置服务器把项目拉起来典型写法是这样echo off cd /d %~dp0 php -S 0.0.0.0:8080 -t ./public pause注意 -t 参数指定的根目录必须和实际入口位置匹配。ThinkPHP 5.x 结构入口在 public/index.php参数就要用 -t ./public如果源码是 3.x 老结构入口文件在根目录下直接叫 index.php参数就要改成 -t ./。这个参数错了页面要么 404要么直接列出目录文件列表非常容易误判成源码包有问题。启动后浏览器访问 http://localhost:8080登录页能看到系统名称。默认账号密码一般写在说明文档里常见是 admin/admin 或 admin/123456。登录之前要确认 php.ini 里 gd 扩展已开启否则验证码图片是裂开的连登录这关都过不去。第一次登录进去别急着点报表。我会按业务顺序把菜单过一遍采购 → 入库 → 销售 → 出库 → 报表。这样能最快验证数据库和代码是否同源——如果某个菜单进去直接报 SQL 错误说明 SQL 文件版本和数据表结构对不上这时候后面对接再久都是白费功夫。3. 单据流转拆解采购、销售、库位与库存的核心表设计3.1 采购管理采购单、入库单和应付账款的状态变化先把采购这条线拆开。系统里采购管理不是一张表而是由主表、明细表、入库单三张表协作完成。主表存单据头和流程状态明细表存商品维度入库单负责把「在途」变成「在库」。典型的主表字段是这样CREATE TABLE purchase_order ( id int(11) NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL COMMENT 采购单号, supplier_id int(11) NOT NULL COMMENT 供应商ID, total_amount decimal(10,2) DEFAULT 0.00 COMMENT 采购总额, status tinyint(4) DEFAULT 0 COMMENT 0待审核 1已审核 2已入库 3已作废, create_by int(11) DEFAULT NULL, create_time datetime DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8;状态字段是整条流程的骨骼0 到 3 的流转顺序不能乱。采购员新建单据时 status 是 0此时允许编辑和删除审核通过后变成 1单据锁定后续做了入库操作变成 2此时入库数量和采购数量开始核对如果审核被打回或要取消置 3 作废。权限控制上审核和作废通常只开放给主管角色采购员自己不能审自己的单。入库单的信息建议补上这批货进的是哪个仓、哪个库位否则后面库存状况报表会缺维度ALTER TABLE stock_in ADD COLUMN warehouse_id int(11) NOT NULL DEFAULT 0 COMMENT 仓库ID AFTER purchase_order_id, ADD COLUMN storage_location_id int(11) NOT NULL DEFAULT 0 COMMENT 库位ID AFTER warehouse_id;这两列分别记录货进了哪个仓、落在哪个库位是后面做库存状况报表和库位管理的数据基础。没有这两个维度报表就只能在商品维度上汇总仓库之间的调拨和分仓核算都会无从下手。商品数量上有一个容易被忽略的边界入库数量超过采购单明细数量时要给出拦截提示而不是放任多收。常见做法是入库校验时先汇总明细表的 order_qty再去比较本次入库量超了就走异常分支。这属于业务规则而非数据库约束所以一定要在控制器里写判断别指望数据库字段能帮你兜底。3.2 销售管理出库时机、退货处理和库存扣减销售这条线的核心是出库时机。销售订单审核通过之后并不立即扣库存真正扣减发生在这个时间点——订单状态流转到「已出库」时系统执行库存扣减并生成出库流水。这样做的好处是审核后、出库前这段时间内仓库还能调整实际发货物。库存扣减必须和流水写入放在同一个事务里。很多新手在这里栽跟头先 UPDATE 库存表扣数量再 INSERT 流水如果第二步抛异常库存已经少了但流水丢了月底对账时怎么都对不上。这套系统的后续 api 接口也是同样的逻辑推荐写法是把两步包进事务$pdo-beginTransaction(); try { $pdo-exec(UPDATE inventory SET quantity quantity - {$qty} WHERE goods_id {$goodsId} AND warehouse_id {$whId}); $pdo-exec(INSERT INTO stock_log (goods_id, warehouse_id, type, quantity, create_time) VALUES ({$goodsId}, {$whId}, out, {$qty}, NOW())); $pdo-commit(); } catch (Exception $e) { $pdo-rollBack(); // 记录日志并返回「出库失败请重试」 }这是 MySQL 事务处理的高频场景inventory 表没有加锁的情况下并发出库只能靠事务来保证数量一致。如果发现同一件商品同时被两个订单出库扣完库存变负数多半就是事务没包好或者 UPDATE 语句没带 warehouse_id 条件跨仓库把货扣串了。退货处理则是反向操作生成退货单 → 审核 → 库存回补 → 写一条 type 为「退货入库」的流水。注意回补的时候要重新校验库位不能直接从原库位加回去了事——如果原库位已经被其他商品占用就落到默认库位然后人工调拨。3.3 库位与库存状况库位表、库存表和出入库流水表如何协作这套系统里有「库存状况」和「出入库统计」两个报表背后是三张各司其职的表表内容读取场景inventory商品×仓库×库位的实时数量库存状况、预警提示stock_log每一次出/入动作的流水出入库统计、历史追溯storage_location库位编码与所属仓库库位管理、移动记录inventory 库存表存当前实时数量是「当下」的快照stock_log 出入库流水表存每一次增减操作是「历史」的轨迹storage_location 库位表提供空间维度。我见过不少人尝试用 SUM(stock_log.quantity) 推当前库存这种做法在数据量小的时候能跑但单据一多就会有两个问题第一流水表会越来越大每次报表查询都要全表扫描第二历史单据如果被修改或删除了推出来的库存就跟着变。所以正确的姿势是 inventory 表每发生一次出入库就即时更新流水表只做追溯和统计。库存状况报表查询时直接读 inventory 表出入库统计才去 GROUP BY 流水表。库位和库位的移动同样要走流水。货从 A 库位移到 B 库位涉及两行库存的 UPDATE 和一条调拨流水如果忘了写流水盘点时查得出来货去了哪但查不出怎么去的。这套系统的库位管理模块在这里会派上用场扫码枪扫码移动的场景本质就是把这组 UPDATE 和 INSERT 串成一次完整操作。4. 报表、预警与权限从 SQL 聚合到 RBAC 节点控制4.1 报表查询时间段参数与 SQL 聚合统计报表查询是本系统使用频率最高的模块。出入库统计的核心是按时间粒度聚合流水表最常见的写法如下SELECT DATE(create_time) AS biz_date, SUM(CASE WHEN type in THEN quantity ELSE 0 END) AS in_qty, SUM(CASE WHEN type out THEN quantity ELSE 0 END) AS out_qty FROM stock_log WHERE create_time 2025-01-01 00:00:00 AND create_time 2025-02-01 00:00:00 GROUP BY DATE(create_time) ORDER BY biz_date;两个参数细节写进注释里。第一时间条件不要用 BETWEEN 包整个月BETWEEN 2025-01-01 AND 2025-01-31 会漏掉 1 月 31 日 0 点之后的流水要用左闭右开区间「 月初 AND 下月月初」。第二create_time 字段没加索引的话这张表到几十万行时报表接口会明显变慢生产环境我在建表时就顺手加了 KEY idx_create_time。库存状况报表与出入库统计不同它读的是 inventory 实时表同时左连商品表拿出名称、条码、规格和上下限阈值SELECT g.goods_name, g.barcode, i.warehouse_id, i.quantity, g.min_stock, g.max_stock FROM inventory i LEFT JOIN goods g ON i.goods_id g.id WHERE i.quantity g.min_stock ORDER BY g.min_stock - i.quantity DESC;这个语句直接就是「预警提示」模块的数据来源。低于 min_stock 的会出现在补货待办里超过 max_stock 的会出现在积压提示里。阈值字段如果放在商品表里不够用——不同仓库对同一件商品的上下限可能不一样——那就把它挪到 inventory 表里按仓库存改起来更灵活。4.2 角色权限RBAC 节点控制和按钮级权限的落地方式系统摘要里强调「对角色和权限进行了细致全面控制」这套机制在 ThinkPHP 里通常基于 RBAC 实现。权限的载体叫节点每个节点对应一个「控制器/方法」路径比如 stock_in/add、purchase/audit。角色表绑定一组节点用户再绑定一个角色登录后系统按用户所属角色的节点集合来决定菜单显隐和数据可操作性。菜单权限和按钮权限是两回事这套系统的细致之处在于做了区分。菜单权限控制「这个功能你进不进得去」一般到模块首页级别按钮权限控制「进去了能不能点」比如采购单的「审核」按钮只有绑定 purchase/audit 节点的角色才显示。落地判断写在模板里{if conditionin_array(purchase/audit, $authNodes)} a href{:U(purchase/audit, array(id$info[id]))}审核/a {/if}$authNodes 是登录时灌进 session 的当前用户节点集合模板每次渲染时做一次 in_array 判断。要注意保存完角色节点之后该角色下所有在线用户的 session 里还有旧集合这时候直接刷新页面权限不会变。处理方式有两种要么在角色保存时强制踢掉该角色的在线 session要么写一个「重新加载权限」按钮。按我的习惯角色权限是低频操作直接提示管理员「保存成功后所有相关用户需重新登录」是最省事的方案。4.3 预警提示库存上下限阈值与轮询检查预警模块在源码里一般有两种实现方式一种是定时任务跑一种是页面加载时实时查。这套系统面向中小企业页面加载时实时查询更常见原因是部署环境大多是共享虚拟主机根本没有 crontab 可用。阈值建议分两级。一级是「补货提醒」库存低于 min_stock 时黄色标记二级是「缺货提醒」库存低于紧急值比如 min_stock 的一半时红色标记并触发语音播报。语音提示是这套系统的一个特色功能实现原理不复杂后端查出预警列表后前端用 Web Speech API 或预先合成的 mp3 文件播报。后者更可控——不用考虑浏览器对 SpeechSynthesis 接口支持度的差异。文件放在 Public/voice/ 目录下按商品名或预警类型命名查出来库存低了就播对应文件。对接广播或门店大屏时这个机制可以直接复用。5. 避坑指南五个翻车现场和对应解法5.1 现象页面白屏或直接显示目录列表现象run.bat 启动后访问 http://localhost:8080要么一片空白要么直接把 public 目录的文件列表列出来。原因白屏最常见的来源是 PHP 版本或扩展问题导致的 Fatal error被 ThinkPHP 的调试模式吞了显示目录列表则大概率是 -t 参数指错了根目录。解决先按 F12 看 Network 面板返回的状态码。500 或 200 但响应体为空去 Runtime/Logs 翻日志重点看是不是 mysql_connect 未定义是的话切回 PHP 5.6 环境。目录列表的情况直接改 run.bat 里的 -t 路径指到 index.php 所在目录即可。5.2 现象SQL 导入报错 1273 或 1415现象导入数据时报 Unknown collation: utf8mb4_unicode_ci 或者 FUNCTION 相关错误导致导入中断。原因1273 是当前 MySQL 版本不认 SQL 文件里写的排序规则1415 多半是 SQL 文件里有触发器或函数而当前账号没有相关权限。解决1273 用文本编辑器打开 SQL 文件全局替换 utf8mb4_unicode_ci 为 utf8_general_ciutf8mb4 换成 utf81415 则检查文件里有没有 DELIMITER 定义的存储过程有的话用 root 账号导入而不是业务账号。导入完成后核对几个关键表的行数是否匹配别只看「导入成功」四个字。5.3 现象语音播报一直没有声音现象预警列表里能看到红色缺货记录但语音播报完全不响换浏览器也一样。原因浏览器自动播放策略。Chrome 在用户未点击页面之前不允许音频自动播放这是前端策略不是系统 bug。另外 mp3 文件路径如果用了绝对路径 http://localhost:8080/Public/voice/xxx.mp3本机没问题换一台机器改了 IP 就 404。解决在登录页放一个「开启语音」按钮用户点击后初始化一次 AudioContext 或加载音频文件后续播放就合法了。路径改用相对地址别把域名写死。还要确认 PHP 输出页面头部没有额外 header否则音频资源被当成文本返回播放器直接报错。5.4 现象扫码枪输入乱码或丢字符现象扫码枪扫商品条码页面输入框里出现乱码或者条码首尾字符丢失。原因大部分 USB 扫码枪本质是键盘输入设备。如果电脑输入法停留在中文全角状态扫码后可能触发输入法转换英文字母被吃了或者被替换成中文符号。解决扫码前把输入法切换到英文半角。还有一个小坑某些扫码枪默认带了回车后缀焦点不在输入框时会触发键盘事件而不是自动换行。我一般建议在表单里给条码输入框 focus 事件让它自动获得焦点这样扫码枪输入就直接落在输入框里不经过中间的焦点跳跃。5.5 现象角色权限保存后不生效现象管理员给某个角色勾选了两个新节点保存成功但该角色用户重新登录后看到的菜单没变化。原因这个在 4.2 提过用户登录时把节点集合写进了 session。保存角色只是改了数据库session 里的旧集合不会自动刷新。解决最简单粗暴且有效的方案是清 Runtime 缓存并让该角色所有用户重新登录。如果系统里 session 存在 Redis 或 Memcached还需要清理对应 key 的前缀。从那以后我每次做权限相关配置都强制走一遍这套流程。6. 进阶打法把 api 接口接到第三方平台——一个库存查询接口的落地全程这套系统带了 api 模块摘要里也提到第三方平台接入。我这边的常规做法是拿它对接门店小程序或电子看板第三方定时调用接口拉取实时库存、低库存商品清单展示在看板上。落地时核心是输出纯 JSON别把模板渲染的 HTML 混进去。写一个 controller 方法public function api_stock_query() { $token I(get.token); if ($token ! C(API_TOKEN)) { $this-ajaxReturn(array(code400, msginvalid token)); } $list M(inventory) -alias(i) -join(goods ON goods.id i.goods_id) -field(goods.barcode, goods.goods_name, i.warehouse_id, i.quantity) -select(); $this-ajaxReturn(array(code200, data$list)); }接口鉴权不要用复杂签名直接一个 token 参数对设备端最友好。第三方平台接入时字段映射往往是最大的工作量——我们叫「库存」对方叫「data.quantity」对方要求时间格式是时间戳我们默认是 datetime。我一般会先写一张字段映射表再动手每次联调都不用翻代码。验证接口时用 curl 一条命令就够curl http://localhost:8080/index.php?s/Api/Stock/api_stock_querytokenyour_token返回 JSON 后再让前端做轮询展示每 30 秒拉一次即可不要上 WebSocket维护成本不值。这套系统的价值在于它可以被当作一个数据底座把内部库存数据安全地开放给外部系统而不是一个封闭的页面后台。从那以后我每次接手这套源码都会先跑一遍 api 验证再动页面代码避免翻车希望帮到你。本文还有配套的精品资源点击获取
返回列表