☰
Java+Springboot+Vue+微信小程序WMS仓库管理系统实战:从库存事务到扫码出入库
2026/10/7 12:23:12 网站建设 项目流程

简介:这份资源是一套基于Java、Spring Boot与Vue.js构建的WMS仓库管理系统,并集成微信小程序端,面向计算机相关专业的毕业设计学生及需要仓库管理实战项目的开发者。系统覆盖入库、出库、库存管理、订单处理等核心业务,Web端与小程序端双端协同,用户可通过微信小程序随时查看库存、下单与追踪物流,适合作为毕设选题或全栈学习参考。压缩包为zip格式,整体约12.24MB,内含后端Java源码、Spring Boot配置文件、Vue组件、数据库SQL脚本、微信小程序配置及需求分析与设计文档等,覆盖软件开发全流程。目前已有217人学习下载。项目结构完整,从需求分析、系统设计到编码测试均有体现,读者可借此理解前后端分离架构、小程序接入方式与仓库业务逻辑的实现思路,快速搭建可运行环境并对照文档梳理开发脉络。

1. 从一张 Excel 到一套 WMS:Java+Springboot+Vue+微信小程序能解决什么

很多中小仓库的日常是这样的:入库靠一张 Excel,出库靠微信群喊话,盘点靠打印纸对账。老板问“某个 SKU 现在还有多少”,仓管要翻三个表格、打两个电话才能回话。这套基于 Java+Springboot+Vue 的 WMS 仓库管理系统加微信小程序,解决的就是这个断层——后台用 Springboot 做库存事务和权限,前端用 Vue 做 PC 端管理台,微信小程序给仓管和司机做移动作业端,扫码出入库、查库存、看任务,不用回办公室开电脑。

它适合三类人:一是中小电商或制造业的 IT 负责人,想自建一套能改、能扩的库存系统,不想被 SaaS 按单量收费;二是 Java 后端开发者,想拿一个完整业务系统练手 Springboot 事务、缓存、定时任务;三是前端或小程序开发者,想理解 Vue 管理台和微信小程序如何共用一套后端接口。热词里“springboot项目”“vue项目源码怎么发给别人”“微信小程序登录获取手机号”这些搜索意图,恰好对应这套系统的三个核心模块:后端服务、前端工程、小程序授权。下面按落地顺序拆开讲。

2. 后端骨架:Springboot 库存事务与接口分层怎么搭

2.1 为什么库存扣减必须放在 Service 层加锁

WMS 最怕的不是功能少,是库存对不上。出库时先查库存再扣减,两个请求同时进来就会超卖。常见做法是在 Service 方法上加@Transactional,配合数据库行锁或 Redis 分布式锁。我一般把扣减逻辑写成“条件更新”而不是“先查后改”:

// InventoryService.java @Transactional(rollbackFor = Exception.class) public void deductStock(Long skuId, Integer qty, String orderNo) { // 条件更新:库存充足才扣,避免先查后改的并发窗口 int affected = inventoryMapper.deduct(skuId, qty); if (affected == 0) { throw new BizException("库存不足或并发冲突, skuId=" + skuId); } // 写库存流水,方便对账和追溯 stockLogMapper.insert(new StockLog(skuId, -qty, orderNo, LocalDateTime.now())); }

对应 Mapper 的 SQL 是UPDATE inventory SET qty = qty - #{qty} WHERE sku_id = #{skuId} AND qty >= #{qty}。逻辑说明:把判断和扣减合并成一条原子 SQL,数据库层面保证不会扣成负数。参数说明:skuId是商品规格唯一键,qty是本次出库数量,orderNo用于流水关联。失败时看affected是否为 0,为 0 要么库存不够,要么行锁等待超时,前者查库存表,后者查innodb_lock_wait_timeout。

2.2 接口分层与统一返回体

Springboot 项目最容易写乱的地方是 Controller 里塞业务逻辑。我习惯分四层:Controller 只做参数校验和路由,Service 做业务编排,Mapper 做数据访问,DTO/VO 做传输对象。统一返回体用泛型封装:

// Result.java public class Result<T> { private int code; // 200 成功, 500 业务异常, 401 未登录 private String msg; private T data; public static <T> Result<T> ok(T data) { ... } public static <T> Result<T> fail(String msg) { ... } }

逻辑说明:前端和小程序共用同一套返回结构,减少联调成本。参数说明:code用 HTTP 状态码语义但走 JSON body,避免网关层拦截;msg给用户看,data给程序用。注意不要用 200 包所有错误,否则小程序端wx.request的 fail 回调永远不触发,排查时容易翻车。

2.3 微信小程序登录态与后端会话打通

小程序端调用wx.login拿到 code,传给后端换 openid,后端生成 token 返回。常见做法是后端用 JWT 或 Redis 存 session。我一般用 Redis:login:token:{uuid}存 userId 和过期时间,小程序每次请求带在 header 里。

// AuthController.java @PostMapping("/wx/login") public Result<String> wxLogin(@RequestBody WxLoginDTO dto) { // 调用微信接口换 openid, 这里省略 HTTP 细节 String openid = wxService.code2Openid(dto.getCode()); String token = UUID.randomUUID().toString(); redisTemplate.opsForValue().set("login:token:" + token, openid, 7, TimeUnit.DAYS); return Result.ok(token); }

逻辑说明:code 只能用一次,换完立即失效,所以后端必须马上处理。参数说明:code来自wx.login,token有效期 7 天,前端存wx.setStorageSync。注意小程序获取手机号需要用户点击按钮触发,且要后端解密,不能在前端直接拿明文。

3. Vue 管理台:从路由到打包进 Springboot 的完整链路

3.1 Vue 路由与权限菜单的动态生成

WMS 管理台通常有入库、出库、库存、报表、系统设置几个大模块。Vue 路由用vue-router,权限控制常见做法是后端返回菜单树,前端动态addRoute。我一般把静态路由和动态路由分开:

// router/index.js const staticRoutes = [ { path: '/login', component: () => import('@/views/Login.vue') }, { path: '/404', component: () => import('@/views/404.vue') } ]; // 动态路由由后端 /menu/list 返回, 前端递归转成 route 对象 function buildRoutes(menus) { return menus.map(m => ({ path: m.path, name: m.name, component: () => import(`@/views/${m.component}.vue`), children: m.children ? buildRoutes(m.children) : [] })); }

逻辑说明:静态路由放登录和错误页,动态路由按角色加载,避免无权限用户看到菜单。参数说明:m.component是后端存的组件路径字符串,前端用动态 import 映射。注意import里不能用完全动态的变量,Webpack 需要能静态分析,所以路径要写成@/views/前缀加变量。

3.2 用 Axios 拦截器统一处理 token 和错误

管理台每个请求都要带 token,401 要跳登录。用 Axios 拦截器最省事:

// utils/request.js const service = axios.create({ baseURL: '/api', timeout: 10000 }); service.interceptors.request.use(config => { const token = localStorage.getItem('token'); if (token) config.headers['Authorization'] = token; return config; }); service.interceptors.response.use( res => { if (res.data.code === 401) { router.push('/login'); return Promise.reject(); } return res.data; }, err => { Message.error(err.message); return Promise.reject(err); } );

逻辑说明:请求拦截器统一加 token,响应拦截器统一处理登录过期和错误提示。参数说明:baseURL在开发环境用代理,生产环境用 Nginx 或直接同域。注意timeout不要设太短,库存导出报表可能超过 10 秒,建议单独接口放宽。

3.3 Vue 打包放进 Springboot 的静态资源目录

热词里“vue打包放进springboot中”是高频问题。做法是npm run build生成dist,把dist下文件拷到 Springboot 的src/main/resources/static,然后配置一个转发 Controller 处理前端路由:

// WebConfig.java @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/**") .addResourceLocations("classpath:/static/"); } // 前端 history 模式需要把非 api 请求转发到 index.html @Bean public WebMvcConfigurer forwardConfig() { return new WebMvcConfigurer() { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/{path:[^\\.]*}").setViewName("forward:/index.html"); } }; } }

逻辑说明:静态资源走classpath:/static/,前端路由刷新时找不到文件,转发到index.html让 Vue Router 接管。参数说明:/{path:[^\\.]*}表示不含点的路径,避免把.js、.css也转发。注意如果后端接口前缀是/api,要确保转发规则不拦截/api/**,否则接口全 404。

4. 微信小程序端:扫码出入库与手机号授权落地

4.1 扫码出入库的页面结构与 API 调用

小程序端核心页面就三个:任务列表、扫码页、库存查询。扫码用wx.scanCode,拿到条码后调后端接口:

// pages/scan/scan.js Page({ data: { result: '' }, onScan() { wx.scanCode({ onlyFromCamera: true, // 只允许相机, 防止从相册选图作弊 scanType: ['barCode', 'qrCode'], success: res => { this.setData({ result: res.result }); // 调后端出入库接口 wx.request({ url: getApp().globalData.baseUrl + '/wms/scan', method: 'POST', header: { 'Authorization': wx.getStorageSync('token') }, data: { code: res.result, type: 'IN' }, success: r => wx.showToast({ title: r.data.msg }) }); } }); } });

逻辑说明:onlyFromCamera强制扫码,避免仓管从相册选旧图。参数说明:scanType指定条码和二维码,type区分入库出库。注意小程序请求域名要在后台配置白名单,开发阶段可以勾选“不校验合法域名”,上线前必须配好。

4.2 获取手机号与后端解密

热词“微信小程序登录获取手机号”是高频需求。小程序端用button open-type="getPhoneNumber"触发,拿到encryptedData和iv传给后端:

// pages/login/login.js onGetPhone(e) { if (e.detail.errMsg !== 'getPhoneNumber:ok') return; wx.login({ success: res => { wx.request({ url: baseUrl + '/wx/phone', method: 'POST', data: { code: res.code, encryptedData: e.detail.encryptedData, iv: e.detail.iv }, success: r => { /* 存 token */ } }); } }); }

后端用微信提供的解密工具类,结合 session_key 解密。逻辑说明:code换 session_key,encryptedData和iv解密出手机号。参数说明:session_key 不能下发给前端,必须后端存 Redis。注意解密失败常见原因是 session_key 过期,重新wx.login即可。

4.3 小程序顶部导航栏高度适配

热词“微信小程序顶部导航栏高度”是自定义导航栏必踩的坑。用wx.getSystemInfoSync拿状态栏高度,再算导航栏高度:

const sys = wx.getSystemInfoSync(); const statusBarHeight = sys.statusBarHeight; // 状态栏, 单位 px const navBarHeight = 44; // 安卓/iOS 常见导航栏高度 const totalHeight = statusBarHeight + navBarHeight;

逻辑说明:自定义导航栏时,页面内容要留出totalHeight的 padding-top。参数说明:statusBarHeight各机型不同,navBarHeight在胶囊按钮对齐时可能需要微调。注意不要写死 44,部分安卓机型胶囊位置不同,建议用wx.getMenuButtonBoundingClientRect动态计算。

5. 避坑与排查:库存对不上、打包白屏、授权失败

5.1 库存扣成负数

现象:并发出库后库存表出现负值。原因:先查后改,两个请求都查到足够库存。解决:改成条件更新UPDATE ... WHERE qty >= #{qty},或加 Redis 分布式锁,锁 key 用lock:stock:{skuId}。

5.2 Vue 打包后刷新 404

现象:管理台首页能打开,刷新子路由变 404。原因:history 模式下服务器找不到对应文件。解决:Springboot 加转发 Controller,把非静态资源请求转发到index.html,Nginx 则配try_files $uri $uri/ /index.html。

5.3 小程序请求被拦截

现象:开发者工具正常,真机请求失败。原因:域名未加入小程序后台白名单,或用了 HTTP 而非 HTTPS。解决:后台配置 request 合法域名,确保是 HTTPS,开发阶段可临时关闭校验但上线前必须开。

5.4 手机号解密报错

现象:encryptedData解密失败。原因:session_key 过期或 code 被重复使用。解决:每次解密前重新wx.login换新 code,后端确保 session_key 与解密请求对应。

5.5 Springboot 版本太高导致依赖冲突

现象:启动报NoSuchMethodError或自动配置不生效。原因:Springboot 3.x 要求 Java 17,且部分旧依赖不兼容。解决:新项目用 2.7.x 加 Java 8/11 更稳,或统一升级到 3.x 并检查javax到jakarta的包名替换。

6. 进阶技巧:用定时任务做库存预警与报表预计算

库存预警和日报表如果每次打开页面都实时算,数据量大时页面会卡。我一般用 Springboot 的@Scheduled做预计算,把结果写进汇总表,前端直接查汇总表。

// StockWarnTask.java @Component public class StockWarnTask { @Autowired private InventoryMapper inventoryMapper; @Autowired private WarnMapper warnMapper; // 每天凌晨 2 点跑一次, 生成低库存预警 @Scheduled(cron = "0 0 2 * * ?") public void genWarn() { List<Inventory> lowList = inventoryMapper.selectLowStock(10); // 阈值 10 warnMapper.deleteAll(); lowList.forEach(i -> warnMapper.insert(new Warn(i.getSkuId(), i.getQty()))); } }

逻辑说明:定时任务把低库存 SKU 提前算好,小程序端查预警时直接读warn表,响应从秒级降到毫秒级。参数说明:cron表达式0 0 2 * * ?表示每天 2 点,阈值 10 可按品类调整。注意多实例部署时定时任务会重复执行,加 Redis 锁或改用分布式调度。

验证方法:手动把某个 SKU 库存改成 5,等任务跑完或手动调genWarn(),查warn表是否有记录。报表预计算同理,按天汇总出入库流水,前端查汇总表而不是明细表。

我自己的习惯是:任何实时算超过 1 秒的查询,先想能不能预计算。库存预警、日报、月报这三类最适合。踩过的坑是定时任务没加锁,两个实例同时跑,预警表被清空两次,数据反而丢了。后来加了个 Redis 锁setIfAbsent("task:warn", "1", 5, TimeUnit.MINUTES)才稳。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询