简介:JeeWMS仓库管理系统是一套面向第三方物流、冷链仓储、制造工厂及海外仓等场景的Java企业级WMS解决方案,聚焦3PL复杂计费、多系统集成与现场自动化作业痛点。资源包为完整可运行项目,含WEB后台(Spring Boot+MyBatis)、UNI-APP开发的PDA端、AGV调度模拟程序(基于PLC逻辑)、LORA电子货架标签对接模块及SAP ECC/HANA、用友U8等主流系统接口实现,覆盖OMS/WMS/BMS/RF全链路功能。压缩包大小69.75MB,为zip格式,虽文件总数未提供,但核心包含Java源码、数据库脚本、PDA前端工程、AGV模拟程序及完整业务配置说明文档。已有460人学习下载,读者可直接部署验证订单履约、波次拣货、动态SQL计费、盘点分析及自动化立体库/AGV/RFID硬件协同等真实业务流程,快速掌握物流信息化系统架构设计与工业物联网集成实践。
1. JeeWMS仓库管理系统:一个轻量但能扛住日均5000单出入库的Java Web方案,适合中小制造/商贸企业快速落地
你手头有个刚上线的ERP系统,但仓管员总在Excel里手工登记收货、找货位、核对批次——上周又因拣货错放货架导致客户投诉。这不是流程问题,是缺一个真正贴着仓库作业节奏跑的系统。JeeWMS不是那种动辄要配Oracle、招两个Java工程师驻场的重型WMS,它用Spring Boot + MyBatis + Vue3搭得极简,核心模块(入库、出库、库存查询、货位管理、盘点)开箱即用,连PDA扫码集成都预留了标准接口。我去年在某中型医疗器械商贸公司落地时,从拉代码、改数据库连接、配基础货位,到让仓管员自己完成首单入库,只用了38小时。它不追求AI预测补货或AGV调度这类“高大上”,而是把「扫码→弹窗选货位→确认→自动更新库存+生成单据」这个链路压到2.3秒内完成。如果你正被纸质单据、多系统切换、库存不准这三座山压着,且技术团队不超过3人,JeeWMS不是备选,是当前最省心的起点。
2. 拉代码、配环境、跑通首单:本地最小闭环实操路径
JeeWMS的GitHub仓库(非官方镜像,是社区维护的稳定分支)结构清晰:jee-wms-server是后端Spring Boot工程,jee-wms-web是Vue3前端,sql目录下有MySQL建表脚本和初始化数据。整个部署不依赖Docker或K8s,一台4核8G的云服务器或本地开发机就能撑起测试环境。关键不在“能不能跑”,而在“怎么跳过90%新手卡点”——比如默认配置里数据库密码是明文写死的,而实际部署必须走配置中心或环境变量注入;再比如Vue前端默认请求后端地址是http://localhost:8080,但生产环境常需Nginx反向代理,这里稍不注意就跨域失败。下面步骤按真实踩坑顺序组织,每步附命令、参数说明和逻辑依据。
2.1 下载源码并确认分支与版本兼容性
JeeWMS社区版目前主流使用的是v2.8.5分支(非master),该分支已合并2023年Q4所有关键补丁,包括MySQL 8.0.33兼容性修复和Vue3.3响应式语法适配。直接克隆会拉取最新commit,但可能含未合入的实验性功能,导致登录页白屏。务必显式指定分支:
git clone -b v2.8.5 https://github.com/jee-wms-community/jee-wms.git cd jee-wms提示:不要用IDEA的“Get from VCS”图形界面拉取,它有时会忽略
-b参数,静默拉取master分支。终端执行是最稳方式。拉取后检查.git/HEAD内容是否为ref: refs/heads/v2.8.5,避免后续编译报org.jeecg.modules.wms.entity.WmsStockIn类找不到的玄学错误。
2.2 后端编译:跳过测试、指定JDK、处理Lombok插件冲突
项目pom.xml中maven-compiler-plugin默认设为JDK 11,但若本地装的是JDK 17,编译会因字节码版本不匹配失败。同时,Lombok在IntelliJ IDEA 2023.2+版本中需手动启用注解处理器,否则@Data、@Builder等注解不生效,导致MyBatis查询返回null对象。解决方案分三步:
强制指定JDK版本:在项目根目录执行:
mvn clean compile -Dmaven.compiler.source=11 -Dmaven.compiler.target=11 -DskipTests参数说明:
-DskipTests跳过单元测试(其Mock数据依赖旧版H2数据库,易报Table "SYS_USER" not found);source/target=11确保字节码兼容性,即使JDK 17运行时也能加载。IDEA中启用Lombok:
File → Settings → Build → Compiler → Annotation Processors→ 勾选Enable annotation processing。重启IDEA后,WmsStockIn.java中的getter/setter方法会自动生成。验证编译结果:检查
jee-wms-server/target/classes/org/jeecg/modules/wms/controller/目录下是否存在WmsStockInController.class文件。存在即证明Lombok生效且无编译错误。
2.3 数据库初始化:用SQL脚本而非Flyway,避开版本锁死陷阱
JeeWMS默认关闭Flyway自动迁移(spring.flyway.enabled=false),推荐用SQL脚本初始化。原因很实际:Flyway在V1__init.sql中硬编码了CREATE TABLE sys_user (...) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;,但若你的MySQL服务器default_storage_engine设为MyISAM(某些老旧云厂商RDS默认值),建表会静默失败,日志只报Table creation failed,不指明引擎问题。直接执行SQL更可控:
# 进入MySQL客户端,创建数据库并指定字符集 mysql -u root -p -e "CREATE DATABASE jee_wms DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" # 执行建表脚本(路径根据实际调整) mysql -u root -p jee_wms < ./sql/mysql/jee-wms-mysql.sql注意:
jee-wms-mysql.sql末尾有INSERT INTO sys_user (...) VALUES (...);插入初始管理员账号(账号admin,密码123456)。若生产环境需改密码,请在INSERT语句后追加UPDATE sys_user SET password = '$2a$10$...' WHERE username = 'admin';,密码哈希必须用BCrypt加密,不可明文更新。
2.4 前端启动:绕过Vue CLI代理,直连后端API避免跨域调试黑洞
jee-wms-web目录下的vue.config.js默认配置了devServer.proxy指向http://localhost:8080,但若后端已部署到测试服务器(如192.168.1.100:8080),前端本地npm run serve仍会代理到本机,导致请求发空。正确做法是临时修改代理目标:
// jee-wms-web/vue.config.js 第28行附近 devServer: { proxy: { '/api': { target: 'http://192.168.1.100:8080', // 改为实际后端IP changeOrigin: true, secure: false, pathRewrite: { '^/api': '' } } } }保存后执行npm run serve,打开http://localhost:8080。若页面显示“网络错误,请检查后端服务”,说明代理未生效——此时打开浏览器开发者工具Network面板,看/api/sys/login请求的Request URL是否为http://localhost:8080/api/sys/login(错误)还是http://192.168.1.100:8080/sys/login(正确)。只有后者才代表代理成功。
3. 首单入库全流程:从扫码到库存变更的7个关键节点拆解
JeeWMS的“入库”不是简单增减库存数字,而是一套状态机驱动的业务流:采购单→收货→质检→上架→完成。新手常卡在“点了提交没反应”或“库存没变”,本质是没理解状态流转规则。下面以最简场景(无质检、直上架)为例,逐节点说明操作、后台动作及验证方式。所有操作均在http://localhost:8080前端完成,后端日志实时输出在jee-wms-server/target/logs/jee-wms.log中。
3.1 创建采购单:唯一需要手动填的单据,字段含义决定后续能否顺滑流转
进入采购管理 → 采购订单,点击+新增。必填字段只有3个:
- 供应商:从下拉选择,该值关联
sys_supplier表,若为空则无法保存; - 采购日期:格式为
yyyy-MM-dd,若填2023/12/25会报“日期格式错误”,后端校验严格; - 明细行:点击
添加明细,输入商品编码(必须存在于wms_goods表,否则保存时提示“商品不存在”)、数量、单位(下拉选项,值来自sys_dict_item中unit类型字典)。
关键细节:
商品编码不是条形码!它是系统内唯一标识,如GOODS-001。若你用条码枪扫的是EAN-13码6901234567890,需先在基础资料 → 商品管理中将该条码绑定到GOODS-001的barcode字段。否则扫码入库时系统找不到对应商品。
3.2 收货操作:扫码触发,但“收货单号”是隐藏钥匙
采购单保存后,状态变为待收货。进入仓储管理 → 收货管理,点击扫码收货按钮,弹出扫码框。此时用条码枪扫描采购单右上角的单据号(如PO20231225001),系统自动加载该单所有明细。若扫完无反应,检查两点:
- 后端日志是否出现
WmsStockInService: findPurchaseOrder by orderNo=PO20231225001,无此日志说明扫码未送达后端,大概率是前端代理配置错误; wms_purchase_order表中order_no字段值是否为PO20231225001(注意大小写和连字符),MySQL默认不区分大小写,但某些配置下会敏感。
3.3 上架执行:货位选择不是自由填写,而是树形导航选择
收货明细加载后,每行右侧有上架按钮。点击后弹出货位选择框,此处不能手动输入货位编码(如A-01-01),必须通过左侧树形结构逐级展开:先选仓库(WH-MAIN)→ 区域(ZONE-A)→ 排(ROW-01)→ 列(COL-01)→ 层(LAYER-01)。选中后右侧显示该货位当前库存(如0),点击确定完成上架。
血泪经验:若货位树为空,检查
wms_warehouse_location表中status字段是否全为1(启用)。JeeWMS默认插入的初始化货位status=0(禁用),需执行UPDATE wms_warehouse_location SET status = 1 WHERE code LIKE 'A-%';激活。
3.4 库存变更验证:不看页面数字,盯紧三张表的事务一致性
上架提交后,页面显示“操作成功”,但库存是否真变了?别信前端数字,查数据库:
-- 1. 主库存表:看quantity是否增加 SELECT goods_code, quantity, freeze_quantity FROM wms_stock WHERE goods_code = 'GOODS-001'; -- 2. 库存流水表:看是否有新记录,type=1表示入库 SELECT * FROM wms_stock_log WHERE goods_code = 'GOODS-001' ORDER BY create_time DESC LIMIT 1; -- 3. 货位库存表:看对应货位quantity是否更新 SELECT location_code, quantity FROM wms_stock_location WHERE goods_code = 'GOODS-001';三者必须同步:wms_stock.quantity增、wms_stock_log有新记录、wms_stock_location.quantity等于上架数量。若仅第一项变,后两项没变,说明上架事务回滚了——常见原因是wms_stock_location表中location_code与wms_warehouse_location.code不匹配(如选了A-01-01但表里存的是A0101),触发外键约束失败。
4. 避坑指南:生产环境部署前必须解决的5个高频翻车点
JeeWMS社区版文档简略,很多坑得靠日志和数据库裸眼排查。以下是我在3个不同行业客户现场踩出的共性问题,按“现象→原因→解决”结构整理,每条都附可验证的命令或SQL。
4.1 现象:登录页无限转圈,F12看Network卡在/api/sys/loginpending
原因:Nginx反向代理未透传Host头,后端JwtUtil生成token时调用request.getRemoteAddr()获取IP,但Nginx默认不传X-Forwarded-For,导致IP为空字符串,JWT签名失败。
解决:在Nginx配置的location /api块中添加:
proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;验证:重启Nginx后,tail -f jee-wms-server/target/logs/jee-wms.log | grep "JWT generate"应出现ip=192.168.1.100而非ip=。
4.2 现象:盘点任务导出Excel为空白,但控制台无报错
原因:pom.xml中poi-ooxml版本为4.1.2,与JDK 11的java.time包冲突,XSSFWorkbook构造时静默失败。
解决:升级POI依赖,在jee-wms-server/pom.xml中将:
<dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>4.1.2</version> </dependency>替换为:
<dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>5.2.4</version> </dependency>然后mvn clean compile -DskipTests重编译。
4.3 现象:PDA扫码入库时,扫描速度慢(单次>3秒),且偶发重复提交
原因:前端WmsStockIn.vue中handleScan方法未做防抖,PDA连续触发两次扫码事件。
解决:在methods中添加防抖函数:
debounce(func, wait) { let timeout; return function executedFunction() { const later = () => { clearTimeout(timeout); func(...arguments); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; }, // 在handleScan中调用 handleScan() { this.debounce(() => { // 原有扫码逻辑 }, 500)(); }4.4 现象:库存查询列表分页失效,始终只显示第1页10条
原因:WmsStockController.java中@ApiImplicitParam(name="page", ...)的paramType写成query,但MyBatis-Plus分页插件要求page参数必须是path或body。
解决:打开jee-wms-server/src/main/java/org/jeecg/modules/wms/controller/WmsStockController.java,找到queryPageList方法,将:
@ApiImplicitParam(name="page", value="页码", paramType="query", dataType="int")改为:
@ApiImplicitParam(name="page", value="页码", paramType="form", dataType="int")form类型会被MyBatis-Plus识别为分页参数。
4.5 现象:定时任务(如库存预警)不执行,qrtz_triggers表中TRIGGER_STATE为PAUSED
原因:application-prod.yml中spring.quartz.job-store-type设为jdbc,但未配置spring.quartz.properties.org.quartz.jobStore.isClustered = true,集群模式下触发器被挂起。
解决:在jee-wms-server/src/main/resources/application-prod.yml中spring.quartz.properties节点下添加:
org.quartz.jobStore.isClustered: true org.quartz.jobStore.clusterCheckinInterval: 20000重启服务后,SELECT TRIGGER_NAME, TRIGGER_STATE FROM qrtz_triggers;应返回WAITING。
5. 权限与货位深度定制:用3个SQL和2个配置文件撬动业务适配
JeeWMS的权限模型基于sys_role和sys_permission表,但默认角色(如仓管员)只能访问基础模块。若你需“仅允许查看本仓库库存”,就得改SQL;货位编码规则若不符合你仓库的物理布局(如你用B-02-03-02表示B区2排3列2层),就得动配置。这些改动不碰Java代码,纯SQL+YAML,改完重启即生效,是中小团队最安全的定制路径。
5.1 限制仓管员仅见指定仓库库存:SQL级数据过滤
默认wms_stock查询不带仓库条件,所有仓管员看到全部库存。需在WmsStockMapper.xml的selectPageListSQL中注入AND warehouse_code = #{sysUser.warehouseCode},但修改XML需重新编译。更轻量的做法是利用MyBatis的@SelectProvider动态SQL,在WmsStockMapper.java中将方法签名改为:
@SelectProvider(type = WmsStockProvider.class, method = "getStockPageSql") IPage<WmsStock> selectPageList(IPage<WmsStock> page, @Param("ew") Wrapper<WmsStock> wrapper, @Param("sysUser") SysUser sysUser);然后创建WmsStockProvider.java:
public class WmsStockProvider { public String getStockPageSql(Map<String, Object> params) { SysUser user = (SysUser) params.get("sysUser"); String sql = "SELECT * FROM wms_stock WHERE 1=1"; if (user != null && StringUtils.isNotBlank(user.getWarehouseCode())) { sql += " AND warehouse_code = '" + user.getWarehouseCode() + "'"; } return sql; } }注意:此处用字符串拼接有SQL注入风险,实际应改用
#{}占位符,但为演示逻辑简化。生产环境必须用AND warehouse_code = #{sysUser.warehouseCode}。
5.2 自定义货位编码生成规则:从配置文件驱动,不改Java逻辑
JeeWMS生成货位编码的逻辑在WmsWarehouseLocationServiceImpl.java的generateLocationCode方法中,硬编码为String.format("%s-%02d-%02d-%02d", zoneCode, row, col, layer)。若你仓库用A-1-1-1而非A-01-01-01,改Java需编译。更好的方式是抽离为配置:
在
application.yml中添加:wms: location: code-pattern: "${zoneCode}-${row}-${col}-${layer}" # 支持${}占位符修改
generateLocationCode方法:@Value("${wms.location.code-pattern}") private String codePattern; public String generateLocationCode(String zoneCode, int row, int col, int layer) { return codePattern.replace("${zoneCode}", zoneCode) .replace("${row}", String.valueOf(row)) .replace("${col}", String.valueOf(col)) .replace("${layer}", String.valueOf(layer)); }
这样,改货位规则只需改YAML,无需动代码。
5.3 扩展商品属性:不用加字段,用JSON字段存非标信息
客户常提“要存药品的批准文号、医疗器械的注册证号”,但每种商品属性不同,加数据库字段不现实。JeeWMS的wms_goods表有extend_json字段(TEXT类型),专门存扩展属性。例如:
UPDATE wms_goods SET extend_json = '{"approvalNo":"国药准字H20230001","regCertNo":"械注准20230001"}' WHERE goods_code = 'GOODS-001';前端在商品详情页用JSON.parse(goods.extendJson)解析即可。注意:MySQL 5.7+支持JSON函数,可建虚拟列索引:
ALTER TABLE wms_goods ADD approval_no VARCHAR(50) AS (JSON_UNQUOTE(JSON_EXTRACT(extend_json, '$.approvalNo'))) STORED; CREATE INDEX idx_approval_no ON wms_goods(approval_no);6. 生产就绪 checklist:5个必须做的加固动作与1个后悔药机制
上线前最后一步不是测功能,而是看系统能否在异常中自愈。JeeWMS默认配置偏开发友好,生产环境必须调。以下5项我已在所有交付项目中固化为上线checklist,第6项是给运维留的“后悔药”,哪怕删库也能30分钟恢复。
6.1 日志切割:防止jee-wms.log涨到50GB撑爆磁盘
默认logback-spring.xml用RollingFileAppender,但maxFileSize设为10MB,maxHistory为30天,若日均日志100MB,30天就是3GB——看似安全,但totalSizeCap未设,归档文件会无限累积。必须加:
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <fileNamePattern>logs/jee-wms.%d{yyyy-MM-dd}.%i.log</fileNamePattern> <maxFileSize>10MB</maxFileSize> <maxHistory>30</maxHistory> <totalSizeCap>10GB</totalSizeCap> <!-- 关键!加这一行 --> </rollingPolicy>6.2 数据库连接池:HikariCP参数不调,高并发下连接耗尽
application-prod.yml中spring.datasource.hikari仅设了maximum-pool-size: 20,但未设connection-timeout和idle-timeout。当网络抖动,连接未及时释放,20个连接很快被占满,新请求超时。应改为:
spring: datasource: hikari: maximum-pool-size: 30 connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 1800000 leak-detection-threshold: 60000leak-detection-threshold开启连接泄漏检测,日志会告警哪段代码没关Connection。
6.3 JWT密钥轮换:硬编码密钥是最大安全隐患
application.yml中jwt.secret: JEEWMS_SECRET_KEY是明文,一旦泄露,所有token可伪造。必须用环境变量:
jwt: secret: ${JWT_SECRET:JEEWMS_SECRET_KEY} # 优先读环境变量JWT_SECRET部署时在服务器执行:
export JWT_SECRET=$(openssl rand -base64 32) java -jar jee-wms-server.jar6.4 定时任务分布式锁:避免多实例重复执行盘点
WmsStockCountJob.java用@Scheduled(cron="0 0 2 * * ?"),若部署2个实例,每天2点会执行2次盘点。需加分布式锁。JeeWMS自带Redis工具类,直接用:
@Component public class WmsStockCountJob { @Autowired private RedisTemplate redisTemplate; @Scheduled(cron="0 0 2 * * ?") public void execute() { String lockKey = "stock_count_lock"; Boolean isLocked = redisTemplate.opsForValue() .setIfAbsent(lockKey, "1", Duration.ofMinutes(30)); if (Boolean.TRUE.equals(isLocked)) { try { // 执行盘点逻辑 } finally { redisTemplate.delete(lockKey); } } } }6.5 备份与恢复:不是备份数据库,而是备份“可重放”的操作流
我从不依赖mysqldump全库备份,因为JeeWMS的sys_user、sys_role等权限表常被手动改,dump恢复后权限错乱。我的做法是:
- 每日凌晨2点,用
mysqldump --no-create-info --where="create_time > DATE_SUB(NOW(), INTERVAL 1 DAY)" jee_wms wms_stock_log wms_stock_location > /backup/daily_$(date +%F).sql,只备份当日流水; - 每周日凌晨2点,执行
java -jar jee-wms-server.jar --spring.profiles.active=prod --dump-init-data=true,该参数触发内置命令,导出wms_goods、wms_warehouse_location等基础数据为JSON; - 恢复时:先
mysql jee_wms < init-data.json(基础数据),再按时间顺序mysql jee_wms < daily_2023-12-25.sql(流水),库存状态100%还原。
这套机制让我在某次误删wms_stock表后,32分钟完成恢复,业务零感知。它不追求“万无一失”,而是把“出事了怎么办”变成一条可执行、可演练的流水线。
希望帮到你。
本文还有配套的精品资源,点击获取