简介:这是一套面向计算机、通信、自动化等相关专业学生与开发者的WMS仓库管理系统完整项目源码,采用Java+SpringBoot后端与Vue前端架构,并配套微信小程序端,可作为毕业设计、课程大作业或进阶练手项目。压缩包共1923个文件,约14.51MB,涵盖302个Java源文件、222个Vue组件、406个JavaScript脚本、95个XML配置、37个WXML与37个WXSS小程序页面文件,以及数据库脚本、说明文档、PDF资料与构建脚本等,前后端与小程序三端结构完整。项目经调试测试可正常运行,答辩评审分达98分,已有1163人学习下载。读者可从中获取完整的三端源码、数据库设计与文档说明,理解仓库入库、出库、库存管理等模块的实现思路,并在此基础上修改调整,实现个性化功能,适合小白学习与进阶参考。
1. 从一张入库单说起:Java+Springboot+Vue 的 WMS 到底在管什么
很多做企业信息化的朋友第一次接触 WMS 仓库管理系统,都是从一张入库单开始的。货到了仓库,收货员扫码、填数量、选库位,系统自动生成库存流水,同时把这条记录同步到微信小程序端,让采购和销售在手机上就能看到实时库存。这套动作背后,就是 Java 做后端服务、Springboot 做快速集成、Vue 做管理后台、微信小程序做移动作业的典型组合。它解决的核心问题只有一个:让仓库里的每一次货物移动都有据可查、有账可对,而不是靠 Excel 和微信群吼来吼去。
这套技术栈适合谁?一是中小型仓储团队,预算有限但需要一套能自己维护的系统;二是接私活或做毕设的开发者,需要一套结构完整、能跑通、能讲清楚的项目;三是想从 CRUD 进阶到业务系统设计的 Java 开发者,WMS 的库存扣减、批次管理、库位分配是很好的练手场景。下面我会按「先跑起来、再拆模块、最后避坑」的顺序,把 Java+Springboot+Vue 的 WMS 加微信小程序这条链路讲透,包括数据库怎么设计、接口怎么联调、小程序怎么接,以及我踩过的那些血泪坑。
2. 环境搭建与项目骨架:把 Springboot 后端和 Vue 前端先跑通
2.1 后端骨架:Springboot 项目初始化与数据库连接
拿到一套 WMS 源码,第一步不是急着看业务代码,而是先把后端跑起来。我一般会先确认 JDK 版本和 Maven 依赖是否完整。常见做法是 JDK 1.8 或 11,Springboot 2.7.x 居多,因为很多 WMS 源码用的 MyBatis-Plus 和 Druid 在这个版本下最稳。数据库一般是 MySQL 5.7 或 8.0,建库语句通常在sql目录下。
# 1. 创建数据库并导入初始化脚本 mysql -u root -p -e "CREATE DATABASE wms DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;" mysql -u root -p wms < sql/wms_init.sql # 2. 修改 application.yml 中的数据库连接 # spring.datasource.url: jdbc:mysql://localhost:3306/wms?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai # spring.datasource.username: root # spring.datasource.password: 你的密码 # 3. 编译并启动 mvn clean package -DskipTests java -jar target/wms-backend-1.0.0.jar上面三步里,建库和导脚本是最容易翻车的地方。很多源码的 SQL 文件里用了utf8mb4,但 MySQL 配置文件没开innodb_large_prefix,导入时会报「Specified key was too long」。解决办法是在 my.cnf 里加上innodb_large_prefix=ON和innodb_file_format=Barracuda,或者直接把索引长度改小。启动成功后,访问http://localhost:8080/doc.html看 Swagger 接口文档是否正常,这是判断后端是否跑通的最快方式。
参数方面,server.port默认 8080,如果被占用就改成 8081;spring.datasource.druid.initial-size设 5 就够,WMS 并发不会太高;mybatis-plus.mapper-locations要指向classpath*:mapper/**/*.xml,否则 XML 里的 SQL 加载不到。这些配置在application.yml里都有注释,照着改就行。
2.2 前端骨架:Vue 项目安装依赖与代理配置
Vue 前端一般是 Vue2 + Element UI 或 Vue3 + Element Plus。拿到源码后,先看package.json里的依赖版本,然后执行安装。这里有个高频坑:node-sass和sass-loader版本不匹配会导致npm install直接失败。我一般会先删掉node_modules和package-lock.json,再用npm install --legacy-peer-deps绕过 peer 依赖检查。
# 进入前端目录 cd wms-web # 清理旧依赖 rm -rf node_modules package-lock.json # 安装依赖,指定国内镜像加速 npm install --registry=https://registry.npmmirror.com --legacy-peer-deps # 启动开发服务器 npm run serve启动后默认访问http://localhost:8081。前端要能调通后端,关键在vue.config.js里的代理配置。很多源码写的是target: 'http://localhost:8080',但后端如果改了端口,这里必须同步改。代理路径一般是/api,所以后端接口也要统一加/api前缀,否则会出现 404 但浏览器控制台看不到跨域报错,因为请求根本没发出去。
// vue.config.js 关键配置 module.exports = { devServer: { port: 8081, proxy: { '/api': { target: 'http://localhost:8080', // 后端地址 changeOrigin: true, pathRewrite: { '^/api': '' } // 如果后端没有 /api 前缀,这里要去掉 } } } }changeOrigin: true是为了让后端看到的请求来源是它自己,避免跨域拦截。pathRewrite要看后端 Controller 的@RequestMapping有没有/api,有就去掉这行,没有就保留。这两个参数搞反了,前端就会一直报 404,但后端日志里连请求记录都没有。
2.3 微信小程序端:项目导入与接口域名配置
微信小程序端一般是原生开发或 uni-app。原生小程序用微信开发者工具导入wms-miniprogram目录即可。导入后第一件事是改app.js里的globalData.baseUrl,指向你的后端地址。开发阶段可以在开发者工具里勾选「不校验合法域名」,这样http://localhost:8080也能请求。
// app.js App({ globalData: { baseUrl: 'http://localhost:8080/api', // 后端接口地址 userInfo: null }, onLaunch() { // 检查登录态 const token = wx.getStorageSync('token'); if (!token) { wx.redirectTo({ url: '/pages/login/login' }); } } });小程序请求封装一般放在utils/request.js里,统一加 token 和错误处理。这里要注意:微信小程序的wx.request默认超时是 60 秒,但 WMS 的库存查询接口如果 SQL 没优化,可能超过 3 秒,用户就会觉得卡。我一般会把常用查询加 Redis 缓存,或者在小程序端做分页加载,每页 20 条,避免一次性拉全量库存。
3. 核心模块拆解:库存、库位、批次与小程序联调
3.1 库存扣减:乐观锁与悲观锁怎么选
WMS 最核心的逻辑就是库存扣减。出库时,系统要保证不会超卖。常见做法有两种:悲观锁和乐观锁。悲观锁是SELECT ... FOR UPDATE,在事务里锁住库存行,扣完再提交。优点是简单,缺点是并发高时锁等待严重。乐观锁是加version字段,更新时检查版本号,失败就重试。
-- 悲观锁写法 START TRANSACTION; SELECT quantity FROM wms_stock WHERE sku_id = 1001 AND location_id = 5 FOR UPDATE; -- 业务判断库存是否充足 UPDATE wms_stock SET quantity = quantity - 10 WHERE sku_id = 1001 AND location_id = 5; INSERT INTO wms_stock_log (sku_id, location_id, change_qty, type) VALUES (1001, 5, -10, 'OUT'); COMMIT;// 乐观锁写法(MyBatis-Plus) @Version private Integer version; // 更新时自动带 version 条件 UpdateWrapper<Stock> wrapper = new UpdateWrapper<>(); wrapper.eq("sku_id", skuId).eq("location_id", locationId).ge("quantity", qty); Stock stock = new Stock(); stock.setQuantity(stock.getQuantity() - qty); int rows = stockMapper.update(stock, wrapper); if (rows == 0) { throw new BusinessException("库存不足或并发冲突,请重试"); }我一般会选乐观锁,因为 WMS 的并发不会像秒杀那么高,乐观锁重试成本低,而且不会长时间占着数据库连接。但要注意:乐观锁的重试次数要限制,比如 3 次,超过就返回「系统繁忙」,避免无限重试拖垮服务。另外,库存扣减和流水记录必须在同一个事务里,否则会出现库存扣了但流水没记,对账时就是一笔糊涂账。
3.2 库位分配:上架策略与小程序扫码联动
库位分配是 WMS 区别于普通进销存的关键。货到了仓库,不能随便放,要按策略推荐库位。常见策略有:就近上架、按品类分区、按周转率分区。实现上,一般是在wms_location表里加zone、row、col、max_capacity字段,然后写一个推荐算法。
// 库位推荐:优先找同品类且容量足够的库位 public Location recommendLocation(String skuId, int qty) { // 1. 查该 SKU 的品类 String category = skuMapper.selectById(skuId).getCategory(); // 2. 找同品类库位,按剩余容量降序 List<Location> locations = locationMapper.selectList( new QueryWrapper<Location>() .eq("category", category) .apply("max_capacity - used_capacity >= {0}", qty) .orderByDesc("max_capacity - used_capacity") ); if (locations.isEmpty()) { // 3. 没有同品类库位,找任意有空位的库位 locations = locationMapper.selectList( new QueryWrapper<Location>() .apply("max_capacity - used_capacity >= {0}", qty) .orderByAsc("used_capacity") ); } return locations.isEmpty() ? null : locations.get(0); }小程序端扫码上架时,调用这个接口拿到推荐库位,然后让操作员确认。这里有个细节:小程序扫码用的是wx.scanCode,返回的条码可能是 SKU 编码,也可能是库位编码。我一般会在条码前加前缀区分,比如SKU-1001和LOC-A01-01,解析时按前缀路由到不同逻辑。如果不加前缀,扫到库位码当成 SKU 查,就会报「商品不存在」,操作员会一脸懵。
3.3 批次与效期管理:先进先出怎么落地
食品、医药类 WMS 必须管批次和效期。出库时要按先进先出(FIFO)或先过期先出(FEFO)扣减。实现上,wms_stock表要加batch_no和expire_date字段,出库时按expire_date升序查库存,逐批扣减。
-- 按效期升序查可用库存 SELECT * FROM wms_stock WHERE sku_id = 1001 AND quantity > 0 ORDER BY expire_date ASC; -- 逐批扣减(伪代码) for (Stock stock : stockList) { if (qty <= 0) break; int deduct = Math.min(stock.getQuantity(), qty); stock.setQuantity(stock.getQuantity() - deduct); stockMapper.updateById(stock); qty -= deduct; // 记录流水,关联批次 stockLogService.record(stock.getBatchNo(), -deduct); }这里最容易翻车的是:批次库存和总库存不一致。比如总库存显示 100,但按批次查只有 80,剩下 20 是历史脏数据。我一般会在系统里加一个「库存对账」定时任务,每天凌晨跑一次,把总库存和批次库存汇总比对,不一致就告警。另外,小程序端查库存时,默认只展示总库存,点进去才看批次明细,避免操作员被一堆批次号搞晕。
4. 避坑与排查:WMS 联调中最容易翻车的 5 个地方
4.1 跨域问题:前端 404 但后端没日志
现象:Vue 前端调接口报 404,但后端控制台没有任何请求记录。原因:vue.config.js的pathRewrite配置和后端@RequestMapping不匹配,请求被代理转发到了错误路径。解决:先确认后端接口完整路径,比如http://localhost:8080/stock/list,如果前端代理配了pathRewrite: { '^/api': '' },那前端请求/api/stock/list会被转发到/stock/list,这是对的。如果后端本身就有/api前缀,就要去掉pathRewrite。
4.2 小程序登录态失效:token 过期没刷新
现象:小程序用着用着突然所有接口返回 401,但重新进入小程序又好了。原因:token 存在wx.setStorageSync里,过期后没有自动刷新机制,用户只能杀掉小程序重进。解决:在request.js里拦截 401,调用刷新 token 接口,拿到新 token 后重试原请求。如果刷新也失败,再跳登录页。注意刷新接口本身不能走拦截器,否则会死循环。
4.3 库存扣减为负数:并发下乐观锁重试没生效
现象:压测时库存扣成了负数。原因:乐观锁的version字段在更新时没有正确带上,或者重试逻辑写在了事务外面,导致重试时读到的还是旧数据。解决:确保@Version注解生效,MyBatis-Plus 的乐观锁插件要注册;重试逻辑要包在事务里,或者用@Retryable注解,但要注意事务传播行为。
4.4 数据库时区问题:入库时间差 8 小时
现象:小程序端显示的入库时间比实际时间少 8 小时。原因:MySQL 的serverTimezone配成了 UTC,而业务用的是东八区。解决:JDBC URL 里加serverTimezone=Asia/Shanghai,同时 MySQL 全局时区也设成+08:00。如果已经存了脏数据,要写脚本批量修正。
4.5 微信小程序扫码无响应:相机权限没配
现象:点击扫码按钮没反应,也不报错。原因:app.json里没有声明scope.camera权限,或者用户之前拒绝过。解决:在app.json的permission字段里加"scope.camera": { "desc": "用于扫码出入库" },然后在扫码前用wx.authorize检查权限,被拒后引导用户去设置页开启。
5. 进阶技巧:用 Redis 缓存库存与小程序端性能优化
5.1 Redis 缓存库存:把查询 QPS 从 200 提到 2000
WMS 的库存查询是高频操作,每次查 MySQL 都要走索引,QPS 上不去。我一般会把库存数据缓存到 Redis,用Hash结构存sku_id + location_id对应的数量,查询时先走 Redis,扣减时先更新 Redis 再异步落库。这样查询 QPS 能从 200 提到 2000 以上。
// Redis 缓存库存查询 public Integer getStockFromCache(Long skuId, Long locationId) { String key = "stock:" + skuId + ":" + locationId; Object val = redisTemplate.opsForHash().get(key, "quantity"); if (val != null) { return Integer.parseInt(val.toString()); } // 缓存未命中,查数据库并回写 Integer qty = stockMapper.selectQuantity(skuId, locationId); redisTemplate.opsForHash().put(key, "quantity", qty.toString()); redisTemplate.expire(key, 30, TimeUnit.MINUTES); return qty; }注意:缓存和数据库的一致性要靠「先更新数据库,再删除缓存」来保证,不要用「先删缓存再更新数据库」,那样并发下容易读到旧数据。另外,缓存过期时间设 30 分钟就够,WMS 的库存变化不会特别频繁,太短了缓存没意义,太长了脏数据风险高。
5.2 小程序端性能:分页加载与图片懒加载
小程序端最怕一次拉太多数据。库存列表我一般做分页,每页 20 条,上拉加载更多。商品图片用lazy-load属性,避免一次性加载几十张图卡死。另外,小程序的setData是性能瓶颈,不要频繁调用,能合并就合并。
// 分页加载库存列表 Page({ data: { list: [], page: 1, hasMore: true }, onReachBottom() { if (!this.data.hasMore) return; this.setData({ page: this.data.page + 1 }); this.loadList(); }, loadList() { wx.request({ url: `${app.globalData.baseUrl}/stock/page`, data: { page: this.data.page, size: 20 }, success: (res) => { this.setData({ list: this.data.list.concat(res.data.records), hasMore: res.data.records.length === 20 }); } }); } });最后说一个我自己的习惯:每次改完库存逻辑,我一定会用 JMeter 跑一轮 100 并发的出库测试,看库存会不会扣成负数、流水有没有漏记。这个习惯帮我省了至少三次线上事故。WMS 这种系统,功能跑通只是及格,并发和数据一致性才是真正的门槛。希望帮到你。
本文还有配套的精品资源,点击获取