开头先说点实际的。上个月帮某小型物流公司梳理他们的仓储流程发现还在用 Excel 记账、纸质单据流转出入库靠人工记忆月底盘点要翻一整天的聊天记录和本子。这种模式下库存不准是必然的货物丢了都不知道是哪一环出的问题。当时我就意识到这类中小仓储最缺的不是多智能的算法而是一套能让现场人员用着顺手、管理者能实时看到数据的小系统。于是就有了这套基于 SpringBoot 和微信小程序的物流仓储管理系统项目代号就叫它 w41h6a03。这套系统的价值一句话能说清楚仓库现场的扫码、入库、出库、盘点、查询全部搬到微信里后端用 SpringBoot 把库存逻辑、权限控制和数据记录收得明明白白。管理员在手机上就能看库存、查操作记录仓管员扫码就能完成货物收发老板不用再到仓库里翻账本。对于正在做毕业设计、或者想给中小仓储做信息化的开发者来说这篇文章不打算讲花哨架构只讲我从需求拆解、表结构设计、接口实现到真机部署这一路走下来哪些地方值得抄作业哪些坑值得绕开。1. 为什么小程序 SpringBoot这套组合最适合这类项目需求视角的取舍1.1 仓库管理的真实痛点是什么很多人一上来就把系统设计得很复杂觉得仓储系统要有 WMS 级别的波次策略、库存周转分析、智能补货。但中小物流公司的实际情况根本不是这样。他们的痛点集中在三个地方入库时货物和单据对不上出库时找不到货或者拿错货库存数据永远滞后于实物。以这家公司为例仓管员老张每天要做的事情是货车到了对照纸质送货单清点货物把数量写在入库单上客户来提货凭单子进库房找货出来再签字确认。听上去有流程但一旦货量上来单子多了就容易漏记、错记。月末盘点差异率长期在 3% 到 5% 徘徊。所以我在梳理这块的时候确定的第一原则是系统要解决的是录清楚和查得到不是算最优。核心功能必须围绕货物的进出与库存现状展开先把数据弄准再谈分析和优化。这也是整个项目后面所有设计决策的出发点。1.2 小程序端与后端的技术选型逻辑为什么前端选微信小程序而不是 App 或者 Web 后台我当时主要考虑了现场使用环境。仓库里信号不稳定仓管员手上的终端千奇百怪如果让每个仓管员都装一个 App光适配和更新就是灾难。小程序扫码不用跳转 App微信里直接扫对一线操作工人几乎零学习成本这是最关键的。后端用 SpringBoot理由更简单生态成熟、上手快、招人容易。这套系统需要一个 Web 管理端给管理员在电脑上操作后端如果选 Node 或 Python 也能做但 SpringBoot 加上 MyBatis Plus 之后单表 CRUD 的代码量少得惊人而且事务控制、连接池管理这些现成方案稳定。配合 MySQL 存业务数据Redis 存登录态和分布式锁这套组合足够撑起日均几千单的业务量。1.3 功能清单要克制哪些必须做哪些第一版可以砍掉项目一开始很容易膨胀我通过和仓库主管反复访谈把第一版功能收敛到下面这张表模块必须有的功能第一版先不做登录微信静默登录、账号绑定、角色区分多租户、单点登录入库扫码录入、手动录入、入库单生成、明细校验质检流程、上架策略推荐出库扫码出库、库存校验、出库单生成拣货路径优化、波次拣货库存实时查询、库存预警、操作日志库存周转率分析、ABC 分类基础数据货物档案、仓库/库区维护多仓协同、调拨流程砍掉这些功能不是能力问题而是业务根本没有这个需求。第一版只要做到每一件货从哪里来、到哪里去、系统说得清就已经超过了 90% 的同行。2. 从表结构开始把业务钉死角色权限与核心数据模型2.1 三类角色与权限边界在动手写代码之前我做的第一件事不是建 SpringBoot 工程而是用一张图把角色理清楚。这个系统最终只有三类人系统管理员、仓管员、普通用户可能是客户或司机。管理员维护基础数据看全局库存和操作日志不发货不入库仓管员执行出入库操作扫码、核对、确认普通用户只查自己的货。权限如果不分开会产生很多笑话式的 Bug比如客户把别人的库存单删了。在 SpringBoot 里我用 Sa-Token 做登录鉴权配合注解SaCheckRole(admin)实现接口级角色控制。小程序端拿到登录凭证后后端签发 token后续请求带上这个 token拦截器统一校验。这里有个细节不要把小程序的 openid 直接当用户 ID 用业务表里应该用自增user_idopenid 只存放在 user 表的关联字段里否则将来做账号合并、多端登录会非常痛苦。2.2 核心表的设计与关联关系说一个我一开始没想透、后来返工过的点货物表和库存表一定要分开。货物表product存的是商品的基础信息比如名称、规格、条码库存表stock存的是某个货物在某个库区的存量比如 A 货在 1 号库区有 200 件。如果你把库存数量直接挂在货物表上那么同一个货放在两个库区这个场景就无法表达查询也只能一把抓。核心表大概是这几张sys_user用户表字段包括账号、昵称、openid、角色标识admin/keeper/userproduct货物档案表字段包括名称、规格、单位、条码唯一索引、图片 URLwarehouse仓库表这里可以做简化只保留仓库名称和地址stock库存表联合唯一索引(product_id, warehouse_id)字段包含quantity和locked_quantitystock_in_order/stock_out_order出入库单主表字段包含单号、操作人、仓库 ID、状态、备注stock_in_item/stock_out_item出入库明细表字段包含单号关联、货物 ID、数量、库区这里有个很多人会忽略的设计为什么要有 locked_quantity锁定库存这个字段因为在出库流程中订单先生成、仓库后拣货中间有一段已申请但未实际出库的时间。如果没有锁定库存两个客户同时下大单系统会都提示有货实际拣货时货不够就会产生超卖。哪怕第一版流程简单我也建议保留这个字段后面扩展拣货流程会非常顺手。2.3 库存字段类型和更新策略的两个关键决策第一个决策是库存数量用什么类型答案是INT不是DECIMAL。仓库里的货物只有整件整箱的概念不存在 3.5 件用 INT 省去一堆精度问题。如果业务有按重量计算的货物那种场景建议单独建字段不要和数量混在一个字段里。第二个决策是库存更新不能用读出来改回去的朴素写法比如先SELECT quantity FROM stock WHERE product_id ?然后在 Java 里减掉 10再UPDATE回去。这种写法在高并发下一定出问题。后面第 4 节我会专门讲我最终采用的方案这里先提个醒。3. 小程序端那些绕不开的实现细节登录、扫码与出入库动线3.1 微信登录怎么和自家用户体系打通小程序端的登录流程是这类项目里最容易让新手卡住的地方。微信的wx.login()只能拿到一个临时 code这个 code 要发给后端由后端去微信的接口换 openid 和 session_key。注意openid 换取的请求必须放在后端完成小程序端直接请求微信接口会暴露密钥这是安全红线。我的做法是小程序端调用wx.login()拿到 code通过/api/auth/login传给后端后端拿到 code 后调用微信接口换取 openid然后查sys_user表如果这个 openid 还没绑定就自动创建一个新用户默认角色是普通用户如果已绑定就正常签发 token 返回。前端把 token 存到 storage 里每次请求带上Authorization请求头。这里要补充一个体验细节自动注册的用户没有真实姓名和手机号所以在小程序端个人中心加了一个完善资料的功能用户至少绑一次手机号。仓管员角色由管理员在后台手动把某个用户设置为keeper这比小程序端自己选角色要安全得多。3.2 扫码出入库的完整动线扫码是这套系统里小程序端最核心的动作它用到的 API 是wx.scanCode。这个 API 可以识别一维码EAN-13、Code128 等和二维码扫描结果在result字段里带回。我踩过的一个典型问题是货物条码贴着反光膜扫不出来。这个属于硬件环境问题代码里能做的优化是加上scanType: [barCode, qrCode]参数并提示用户调整光线角度。入库动线我设计成四步每一步都有独立界面仓管员点扫码入库调起wx.scanCode扫到条码后后端根据条码查product表返回货物名称和规格仓管员填写本次入库数量和目标库区提交入库单后端扣减锁定库存如果是采购入库则没有锁定概念、增加实际库存、写操作日志。出库动线则是反过来扫货物条码、选关联出库单、填出库数量、提交。为了减少仓管员的操作次数每个页面都支持连续扫码模式扫一个货填数量自动进入下一个商品添加状态不用再点一次添加商品按钮。这一个小细节让仓管员每天扫码的手速快了很多。3.3 表单校验与空状态处理小程序端最容易被吐槽的就是点了没反应。我在这套系统里做了一个统一请求封装所有接口返回统一结构{ code: 0, msg: success, data: ... }。前端封装里做了三件事code 不为 0 时弹出 msg 提示token 过期时跳转登录页网络异常时显示重试按钮而不是白屏。空状态也做了统一组件暂无数据配上操作按钮比干巴巴一行文字好用很多。另外一个细节是表单校验。例如出库数量不能大于库存数量这个校验不能只靠后端前端也要提前判断因为后端校验返回错误再弹窗用户感知到的延迟非常明显特别是仓库里网络不好时。前端先判断一次后端再兜底校验一次体验和安全都能兼顾。4. SpringBoot 后端要扛住的关键逻辑接口设计、并发扣减与事务4.1 接口分层与统一返回结构后端工程我按 controller - service - mapper 三层来组织。controller只负责接收参数和调用 service不写业务逻辑service层承载所有业务规则比如出入库数量校验、库存扣减、日志记录mapper层用 MyBatis Plus 的BaseMapper解决单表 CRUD。统一返回结构我选了比较经典的 Result 类public class ResultT { private Integer code; private String msg; private T data; }这里的 code 我定义成0 表示成功非 0 表示业务失败。还有一类是 401表示未登录或 token 过期。这个结构看起来简单但它统一了前后端的契约前端封装的request.js里只需要判断res.code 0即可不需要对每个接口写不同的错误处理逻辑。4.2 并发出入库的库存扣减方案这是我全项目最重视的地方。前面提到过先查再改的方式会出问题那换成什么我最终是这么做的用乐观锁 条件更新。每次更新库存时不先去查询而是直接在 SQL 里带条件执行更新操作返回受影响行数来判断是否成功。比如出库扣减 10 件UPDATE stock SET quantity quantity - 10, version version 1 WHERE product_id ? AND warehouse_id ? AND quantity 10这行 SQL 的关键在于quantity 10这个条件。MySQL 在执行 UPDATE 时会锁住匹配的行所以quantity - 10是原子操作不会出现两个请求同时读到 100 再各自扣成 90 的问题。如果第二个请求进来时库存已经被扣到 5不满足quantity 10受影响行数就是 0程序检测到后返回库存不足。同时在stock表加了一个version字段做乐观锁扣减成功的同时版本号自增这样如果将来并发量更大可以在这套逻辑之上再做版本冲突重试。第一版其实不强制要 version但提前埋好这个点后续演进不用改表结构。4.3 事务与日志怎么配合出入库一旦涉及多张表就必须引入事务。入库操作要同时更新两张表stock表的库存数和stock_in_item表的明细分录中间任何一步失败前面成功的更新也要回滚。我的实现是在 service 方法上标注Transactional同时把事务边界控制在 service 层不要在 controller 层开事务。日志方面我单独建了一张operation_log表记录操作人、操作类型IN/OUT/UPDATE、货物 ID、数量、操作时间。注意日志表的写入必须在业务事务之外或者采用独立事务否则很可能因为日志写入失败导致整个出库操作回滚业务反而做不成了。实践上我用了一个Transactional(propagation Propagation.REQUIRES_NEW)的方法来写日志保证日志和主业务互不拖累。要补充的是这类系统不需要引入消息队列。出入库单量如果到不了每秒几十上百的级别Redis 锁和数据库事务足够。引入 MQ 只会增加运维成本第一版千万别过度设计。5. 真机联调与部署阶段最容易踩的坑5.1 小程序域名与 HTTPS 要求微信小程序正式版要求所有接口必须是 HTTPS而且域名要备案。很多人把后端开发完在小程序开发者工具里勾了不校验合法域名真机预览一切正常一提交审核就被打回说请求的域名不在合法列表中。正确做法是后端上线后在微信公众平台的后台配置服务器域名 request 合法域名然后在小程序开发工具里关闭不校验合法域名重新测试。域名必须是公网可访问的不能用 IP 地址本地调试除外。备案周期通常要 1~2 周这个时间要提前预留别等系统开发完才开始申请。5.2 扫码识别与条码库的坑真机扫码时我发现用wx.scanCode扫某些 EAN-13 条码识别结果偶尔会多一个数字或者少一个数字。最初以为是 API 问题排查后发现是货品条码本身印刷不规范比如条码下面没有印数字、条码纸卷边。这种情况下代码要做的是扫到条码后先去product表精确查查不到时提示货物档案不存在同时把条码原值展示给仓管员让他在界面上手动修改后再查一次。还有一类问题是条码前缀。有些供应商会在条码后面追加批次号扫出来是6901234567890A01这种长字符串。我在product表里建了一个match_rule字段存的是长度截取规则比如取前 13 位。这个需求很小但如果第一版不处理现场扫码的成功率会直接影响仓管员对这个系统的信任感。5.3 数据库时区与连接池配置部署阶段最容易出的隐性 Bug 是时间差。服务器默认时区是 UTCMySQL 连接串没指定serverTimezoneAsia/Shanghai结果小程序端显示的时间和数据库存的时间差了 8 小时。这个查起来非常隐蔽因为接口返回的数据看起来正常只是对不上。我的连接串配置是url: jdbc:mysql://localhost:3306/wms?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrueuseSSLfalse连接池我用的是 HikariCPSpringBoot 默认生产环境设置maximum-pool-size: 20就足够这套系统的量级。不要开太多连接连接池过大反而会因为空闲连接被 MySQL 提前断开而报错。5.4 小程序端图片上传的路径问题货物档案一般会上传照片小程序端用wx.uploadFile上传图片到后端。这里有个坑wx.uploadFile不支持自定义请求头虽然新版开始支持部分但兼容性仍不稳定所以 token 没法通过 header 传递。我最后的方案是先用普通请求向后端申请一个上传凭证接口返回带时效的临时上传 URL再调用wx.uploadFile直接上传到这个 URL。或者更简单一点上传接口在框架层面加一个IgnoreAuth注解允许匿名访问靠 URL 里带一个随机 token 参数来做校验。这种方式够用但要注意上传接口必须做文件类型和大小校验防止垃圾文件占满磁盘。6. 交付后我做的三件加分改造6.1 操作日志回放功能第一版上线跑了一周后仓库主管提了一个需求有一票货数量对不上想知道是哪一天、哪个人、在哪个环节出了问题。虽然日志表里已经存了全量操作记录但管理后台只能单调地列表展示。我把日志改造成了回放视图选择一个时间段系统按时间线把每一天的入库、出库、调整操作列出来每条记录都带操作人、货物、数量和备注。这样一来仓库主管查账的效率提升非常多这种基于现有数据的二次加工成本很低但价值感极强。6.2 库存上下限预警当时某个常用耗材因为没人注意库存见底导致临时补货耽误了两天。我给product表加了low_stock_threshold和high_stock_threshold字段。每次出入库提交后后端顺手检查库存数量是否低于下限或高于上限把预警信息写入一张预警表。小程序端库存页面直接显示库存不足黄色标签管理后台也有待处理预警列表。预警本身不需要很复杂但它是让系统从记录工具变成管理工具的关键一步。6.3 批量导入与初始化工具这套系统上线时最痛苦的是初始库存数据录入。仓库里现有 300 多种货每一种都要填名称、规格、条码、期初库存如果靠手工录入仓管员会当场崩溃。我做一个 Excel 导入工具用固定的模板表格后端用 Easy Excel 解析批量插入product和stock表。这里有个教训导入模板一定要先给数据示例而不是给空表头。第一次我给的是空表头模板结果对方把货物名称那列填成了货物名称四个字本身的示例导致导入后数据里混入脏数据又花了一个小时清洗。批量导入的一个实现要点是逐行解析后先用事务包住整体或者先做全量校验再做全量插入。我的做法是先读 Excel 到内存 List逐行校验条码是否重复、库存是否为负数全部校验通过后再开启事务批量插入。这样避免了导入到一半发现第 150 行有错前面 149 行已经写进数据库的尴尬局面。上线稳定之后我又陆续加了些小功能比如出库单打印、库存导出 Excel、首页仓库面积利用率看板。但核心的思路一直没变先把每一次出入库记录清楚把库存数值变准确所有花哨的功能都是在这个地基上长出来的。如果你现在正准备动手做类似的系统我建议你先抛开那些炫技的设想去真实的仓库里待一天看看仓管员是怎么干活、在哪一步最容易出错的。把这一步理顺了系统的价值就已经成功了一大半。回头再看这套 SpringBoot 加微信小程序的组合技术层面确实没什么高深的地方但它解决了真实业务里最要命的问题——数据匹配和流程可控这就够了。