From 218946ab0b0a952bddcbb747c1673e1cbcecdaa6 Mon Sep 17 00:00:00 2001 From: btobab Date: Sat, 26 Jul 2025 12:05:45 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=B5=84=E6=BA=90=E9=94=81?= =?UTF-8?q?=E5=AE=9A=E8=A7=84=E5=88=99=EF=BC=8C=E6=94=B9=E4=B8=BA=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1=E4=B8=8B=E5=8F=91wcs=E6=97=B6=E9=94=81=E5=AE=9A?= =?UTF-8?q?=EF=BC=8C=E4=BB=BB=E5=8A=A1=E5=AE=8C=E6=88=90=E6=97=B6=E8=A7=A3?= =?UTF-8?q?=E9=94=81=EF=BC=8C=E6=B7=BB=E5=8A=A0=E5=87=BA=E5=BA=93=E9=94=81?= =?UTF-8?q?=E5=AE=9A=EF=BC=8C=E4=BF=AE=E6=94=B9=E5=85=A5=E5=BA=93=E9=94=81?= =?UTF-8?q?=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../wms_serve_mengyang/CLAUDE.md | 189 ++++++++++++---- .../api-tests/MyWmsController.http | 11 +- .../api-tests/TaskController.http | 8 +- .../constant/enums/wms/AppConfigKeyEnums.java | 1 + .../controller/mywms/MyWmsController.java | 5 + .../wms_main/service/api/IWcsApiService.java | 2 + .../api/serviceImpl/WcsApiServiceImpl.java | 23 ++ .../service/business/IAgvLockService.java | 61 ++++- .../serviceImpl/AgvLockServiceImpl.java | 214 +++++++++++++++++- .../controller/IMyWmsControllerService.java | 2 + .../MyWmsControllerServiceImpl.java | 22 ++ .../TaskControllerServiceImpl.java | 65 ++---- .../job_executor/WcsStackerTaskSender.java | 31 +++ CLAUDE.md | 112 +++++++++ 14 files changed, 641 insertions(+), 105 deletions(-) create mode 100644 CLAUDE.md diff --git a/202504-Wms-MengYang-tp/wms_serve_mengyang/CLAUDE.md b/202504-Wms-MengYang-tp/wms_serve_mengyang/CLAUDE.md index 0e3fe7c..b728e3b 100644 --- a/202504-Wms-MengYang-tp/wms_serve_mengyang/CLAUDE.md +++ b/202504-Wms-MengYang-tp/wms_serve_mengyang/CLAUDE.md @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## 项目概述 -这是宝应梦阳WMS(仓库管理系统)后端项目(TP托盘库版本),基于Spring Boot 3.3.5和Java 21开发,使用Maven构建。专门针对托盘库存储场景,管理仓库的入库、出库、库存和任务调度等核心业务。 +这是宝应梦阳托盘库WMS系统的Spring Boot后端服务,专门处理托盘式仓库的入库、出库、库存管理和WCS系统集成。基于Spring Boot 3.3.5和Java 21开发,采用MyBatis-Plus进行数据持久化,支持与WCS设备系统的深度集成。 ## 项目特点 @@ -65,48 +65,117 @@ mvn clean package -Dmaven.test.skip=true ### 核心业务模块 -#### 1. MyWMS模块 (`controller/mywms/`) -自定义WMS接口: -- `POST /orderIn` - 入库订单接口 -- `POST /orderOut` - 出库订单接口 -- `POST /stock` - 库存查询接口 +#### 1. MyWMS核心控制器服务 (`MyWmsControllerServiceImpl`) +**文件**: `src/main/java/com/wms_main/service/controller/serviceImpl/MyWmsControllerServiceImpl.java` -#### 2. 深度策略服务 (`service/business/DepthStrategyService`) -核心算法模块,针对托盘库优化: -- **托盘库(TP)专属存储策略** -- 大型货物的存储位置优化算法 -- 托盘堆叠和存取路径优化 +**核心功能**: +- **入库管理**: `createOrderIn()` - 处理入库订单,检查AGV锁定状态 +- **出库管理**: `createOrderOut()` - 处理出库订单,执行深度策略分析 +- **库存查询**: `queryStock()` - 按载具号/库位号查询库存 +- **入库口状态**: `queryAllowFeed()` - 查询WCS和AGV状态 +- **出库口状态**: `queryCanOut()` - 查询WCS和AGV状态 -#### 3. 任务调度系统 (`service/quartz_job/`) -基于Quartz的定时任务框架: -- `WmsTaskExecutor` - WMS任务执行器 -- `MyOutExecutor` - 出库任务执行器 -- `WcsStackerTaskSender` - WCS堆垛机任务发送器 +**关键特征**: +- 防重复推送检查机制 +- AGV互锁机制防冲突 +- 深度策略自动分析 +- 完整异常处理流程 -#### 4. 设备集成 (`service/api/`) -- `WcsApiService` - WCS系统集成(托盘库设备) -- `ExternalApiService` - 外部系统API调用 +#### 2. WCS API集成服务 (`WcsApiServiceImpl`) +**文件**: `src/main/java/com/wms_main/service/api/serviceImpl/WcsApiServiceImpl.java` -### 数据模型结构 -- `po/` - 数据库实体对象(针对托盘库schema) -- `dto/request/` - 请求数据传输对象 -- `dto/response/` - 响应数据传输对象 -- `bo/` - 业务对象(Business Object) -- `vo/` - 视图对象(View Object) +**集成接口**: +- 堆垛机任务下发: `sendStackerTask()` +- 载具释放控制: `releaseVehicle()` +- 拣选任务管理: `sendPickingTask()` +- 状态查询: `queryCanFeed()`, `queryOutFeed()` -### 常用枚举类 (`constant/enums/`) +⚠️ **重要**: 当前WCS通信为调试模式,实际HTTP调用被注释,返回模拟数据 + +#### 3. 任务控制服务 (`TaskControllerServiceImpl`) +**文件**: `src/main/java/com/wms_main/service/controller/serviceImpl/TaskControllerServiceImpl.java` + +**主要功能**: +- 入库任务请求: `taskRequire()` +- 库位分配: `choiceLocation()` +- 任务回馈: `taskFeedback()` +- AGV锁定管理: `lockAgv()`, `releaseAgv()` + +#### 4. 深度策略服务 (`IDepthStrategyService`) +**文件**: `src/main/java/com/wms_main/service/business/IDepthStrategyService.java` + +**策略类型** (WmsDepthStrategyEnums): +- `DIRECT_OUT`: 直接出库 (深度1) +- `SINGLE_TRANSFER`: 单层移库 (深度2) +- `DOUBLE_TRANSFER`: 双层移库 (深度3) +- `COMPLEX_TRANSFER`: 复杂移库 +- `NO_ACTION`: 无需处理 + +#### 5. AGV锁定服务 (`IAgvLockService`) +**文件**: `src/main/java/com/wms_main/service/business/IAgvLockService.java` + +**功能**: +- 单一入库口互锁 +- 超时锁定自动清理 +- 状态查询监控 +- 批量释放支持 + +### 数据库设计 + +#### 核心数据表 +- **t_app_order_in**: 入库订单表 +- **t_app_order_out**: 出库订单表 +- **t_app_stock**: 库存信息表 +- **t_app_task**: WMS任务表 +- **t_app_wcs_task**: WCS任务表 +- **t_app_agv_lock**: AGV锁定表 +- **t_app_location**: 库位信息表 +- **t_app_config**: 系统配置表 + +#### MyBatis-Plus配置 +- 自动分页插件 (最大1000条) +- 驼峰命名自动映射 +- 主键自动生成策略 + +### API接口设计 + +#### MyWMS外部接口 +**控制器**: `src/main/java/com/wms_main/controller/mywms/MyWmsController.java` + +```http +POST /mywms/orderIn # 创建入库订单 +POST /mywms/orderOut # 创建出库订单 +POST /mywms/stock # 查询库存信息 +GET /mywms/allowFeed # 查询投料状态 +GET /mywms/allowOut # 查询投料状态 +``` + +**测试文件**: `api-tests/MyWmsController.http` + +#### 响应格式 +```json +{ + "success": true, + "message": "操作成功", + "data": {}, + "timestamp": "2024-01-01T12:00:00" +} +``` + +### 重要枚举类 (`constant/enums/`) **WMS业务枚举** (`enums/wms/`) -- `StorageTypeEnums` - 存储类型(重点关注TP托盘库类型) -- `WmsDepthStrategyEnums` - 深度策略枚举(托盘库专用策略) -- `WmsTaskTypeEnums` - 任务类型枚举 +- `AppConfigKeyEnums` - 系统配置项枚举 +- `WmsDepthStrategyEnums` - 深度策略枚举 +- `WmsStockStatusEnums` - 库存状态枚举 - `OrderStatusEnum` - 订单状态枚举 -- `WmsLocationTypeEnums` - 货位类型枚举(托盘货位) -- `WmsVehicleStatusEnums` - 载具状态枚举(托盘载具) +- `WmsTaskTypeEnums` - 任务类型枚举 +- `WmsLocationTypeEnums` - 货位类型枚举 +- `WmsVehicleStatusEnums` - 载具状态枚举 **WCS集成枚举** (`enums/wcs/`) - `WcsApiResponseCodeEnums` - WCS API响应码 -- `WcsStackerTaskTypeEnums` - 堆垛机任务类型(托盘堆垛机) +- `WcsStackerTaskTypeEnums` - 堆垛机任务类型 - `WcsStackerTaskStatusEnums` - 堆垛机任务状态 ## 技术栈 @@ -201,16 +270,48 @@ tail -f wms_log/error/$(date +%Y-%m-%d)/$(date +%Y-%m-%d).0.log ## 开发规范 -### 代码规范 -- 使用Lombok注解减少样板代码 -- 统一的API响应格式(WmsApiResponse) -- 枚举类管理常量值 -- 接口-实现分离的服务层设计 +### 代码约定 +- Controller层只处理HTTP请求,业务逻辑在Service层 +- 使用DTO传输数据,内部业务使用BO对象 +- 统一通过MyBatis-Plus的BaseMapper操作数据库 +- 所有业务异常继承RuntimeException并返回统一错误格式 -### 测试建议 -项目配置跳过单元测试,建议: -1. 先实现功能代码 -2. 使用Postman等工具进行API测试 -3. 检查托盘库相关的数据库数据状态 -4. 验证定时任务执行情况 -5. 测试WCS设备集成功能 \ No newline at end of file +### WCS集成规范 +- WCS调用通过IWcsApiService统一管理 +- AGV操作必须使用锁定机制防冲突 +- 任务下发前检查设备状态和库位可用性 +- 调试时注意区分模拟数据和真实WCS调用 + +### 异常处理 +- 使用@ControllerAdvice全局异常处理 +- 业务异常返回统一MyWmsResponse格式 +- 记录详细错误日志便于问题定位 + +### 日志规范 +- 使用Slf4j进行日志记录 +- 关键业务操作记录INFO级别日志 +- 异常情况记录ERROR级别日志 +- 避免敏感信息出现在日志中 + +## 调试和监控 + +### 本地调试 +- 默认端口: 12315 +- H2控制台: 如果启用可通过/h2-console访问 +- 日志级别: 通过logback-spring.xml配置 + +### 数据库连接测试 +```bash +# 测试数据库连接 +mysql -h localhost -P 3306 -u root -p wms_mengyang_tp + +# 查看关键表结构 +DESCRIBE t_app_stock; +DESCRIBE t_app_order_in; +``` + +### 常见问题 +1. **WCS连接失败**: 检查WCS系统URL配置和网络连通性 +2. **AGV锁定超时**: 检查t_app_agv_lock表的锁定记录 +3. **深度策略错误**: 检查库存状态和深度计算逻辑 +4. **MySQL连接超时**: 调整数据库连接池配置 \ No newline at end of file diff --git a/202504-Wms-MengYang-tp/wms_serve_mengyang/api-tests/MyWmsController.http b/202504-Wms-MengYang-tp/wms_serve_mengyang/api-tests/MyWmsController.http index 18721bd..bc549ed 100644 --- a/202504-Wms-MengYang-tp/wms_serve_mengyang/api-tests/MyWmsController.http +++ b/202504-Wms-MengYang-tp/wms_serve_mengyang/api-tests/MyWmsController.http @@ -7,8 +7,8 @@ POST {{baseUrl}}/mywms/orderIn Content-Type: application/json { - "taskId": "testOrderId1", - "vehicleNo": "1001" + "taskId": "testOrderId2", + "vehicleNo": "1002" } ### 2. 出库订单接口 @@ -16,8 +16,8 @@ POST {{baseUrl}}/mywms/orderOut Content-Type: application/json { - "taskId": "testOrderId1", - "vehicleNo": "1001" + "taskId": "testOrderId3", + "vehicleNo": "1003" } ### 3. 库存查询接口 - 仅按载具查询 @@ -49,6 +49,9 @@ Content-Type: application/json ### 5. 检查是否允许投料 GET {{baseUrl}}/mywms/allowFeed +### 5. 检查是否允许出库 +GET {{baseUrl}}/mywms/allowOut + ### 测试数据说明 # 1. orderIn: 入库订单接口,需要taskId和vehicleNo # 2. orderOut: 出库订单接口,需要taskId和vehicleNo diff --git a/202504-Wms-MengYang-tp/wms_serve_mengyang/api-tests/TaskController.http b/202504-Wms-MengYang-tp/wms_serve_mengyang/api-tests/TaskController.http index 15dd20c..6b7237c 100644 --- a/202504-Wms-MengYang-tp/wms_serve_mengyang/api-tests/TaskController.http +++ b/202504-Wms-MengYang-tp/wms_serve_mengyang/api-tests/TaskController.http @@ -8,7 +8,7 @@ Content-Type: application/json { "origin": "R1", - "vehicleNo": "1001", + "vehicleNo": "1002", "codeMessage": "test", "remark": "载具入库测试" } @@ -18,10 +18,10 @@ POST {{baseUrl}}/wms/task/sendTaskResult Content-Type: application/json { - "taskId": "TASK202507240001", + "taskId": "1753502199802010001", "taskStatus": 100, - "vehicleNo": "1001", - "destination": "A01-01-01-02", + "vehicleNo": "1003", + "destination": "C1", "message": "任务执行成功" } diff --git a/202504-Wms-MengYang-tp/wms_serve_mengyang/src/main/java/com/wms_main/constant/enums/wms/AppConfigKeyEnums.java b/202504-Wms-MengYang-tp/wms_serve_mengyang/src/main/java/com/wms_main/constant/enums/wms/AppConfigKeyEnums.java index ee409c5..c774be6 100644 --- a/202504-Wms-MengYang-tp/wms_serve_mengyang/src/main/java/com/wms_main/constant/enums/wms/AppConfigKeyEnums.java +++ b/202504-Wms-MengYang-tp/wms_serve_mengyang/src/main/java/com/wms_main/constant/enums/wms/AppConfigKeyEnums.java @@ -13,6 +13,7 @@ public enum AppConfigKeyEnums { WCS_PICK_TASK_URL("WcsPickTaskUrl", "发送拣选任务地址"), WCS_CANCEL_PICK_TASK_URL("WcsCancelPickTaskUrl", "发送取消拣选任务地址"), WCS_CAN_FEED_URL("wcsCanFeedUrl", "查询wcs是否可上料"), + WCS_CAN_OUT_URL("wcsCanOutUrl", "wcsCanOutUrl"), WCS_RELEASE_BOX_URL("WcsReleaseBoxUrl", "发送释放站台箱子地址"), STAND_CAPACITY("StandCapacity", "站台容量"), IMAGE_IP("ImageIp", "图片存放ip"), diff --git a/202504-Wms-MengYang-tp/wms_serve_mengyang/src/main/java/com/wms_main/controller/mywms/MyWmsController.java b/202504-Wms-MengYang-tp/wms_serve_mengyang/src/main/java/com/wms_main/controller/mywms/MyWmsController.java index e3abd1d..f77ce30 100644 --- a/202504-Wms-MengYang-tp/wms_serve_mengyang/src/main/java/com/wms_main/controller/mywms/MyWmsController.java +++ b/202504-Wms-MengYang-tp/wms_serve_mengyang/src/main/java/com/wms_main/controller/mywms/MyWmsController.java @@ -41,4 +41,9 @@ public class MyWmsController { public MyWmsResponse canFeed() { return myWmsControllerService.queryCanFeed(); } + + @GetMapping("/allowOut") + public MyWmsResponse canOut() { + return myWmsControllerService.queryCanOut(); + } } diff --git a/202504-Wms-MengYang-tp/wms_serve_mengyang/src/main/java/com/wms_main/service/api/IWcsApiService.java b/202504-Wms-MengYang-tp/wms_serve_mengyang/src/main/java/com/wms_main/service/api/IWcsApiService.java index 9849085..d0e330f 100644 --- a/202504-Wms-MengYang-tp/wms_serve_mengyang/src/main/java/com/wms_main/service/api/IWcsApiService.java +++ b/202504-Wms-MengYang-tp/wms_serve_mengyang/src/main/java/com/wms_main/service/api/IWcsApiService.java @@ -48,4 +48,6 @@ public interface IWcsApiService { * @return 查询结果 */ WcsApiResponse canFeed(WcsCanFeedRequest request); + + WcsApiResponse canOut(WcsCanFeedRequest request); } diff --git a/202504-Wms-MengYang-tp/wms_serve_mengyang/src/main/java/com/wms_main/service/api/serviceImpl/WcsApiServiceImpl.java b/202504-Wms-MengYang-tp/wms_serve_mengyang/src/main/java/com/wms_main/service/api/serviceImpl/WcsApiServiceImpl.java index b54b13d..bcb211f 100644 --- a/202504-Wms-MengYang-tp/wms_serve_mengyang/src/main/java/com/wms_main/service/api/serviceImpl/WcsApiServiceImpl.java +++ b/202504-Wms-MengYang-tp/wms_serve_mengyang/src/main/java/com/wms_main/service/api/serviceImpl/WcsApiServiceImpl.java @@ -120,6 +120,29 @@ public class WcsApiServiceImpl implements IWcsApiService { // WcsApiResponse response = new WcsApiResponse<>(); // response = httpResponse.getData(response.getClass().asSubclass(WcsApiResponse.class)); // return response; +// } + // TODO error -> success +// return WcsApiResponse.error("请求未获得响应信息。", null); + WcsCanFeedResponse wcsCanFeedResponse = new WcsCanFeedResponse(); + wcsCanFeedResponse.setResponseTime(LocalDateTime.now()); + wcsCanFeedResponse.setMsg("success"); + wcsCanFeedResponse.setAllowAction(true); + return WcsApiResponse.success("成功", wcsCanFeedResponse); + } + + /** + * 查询WCS系统是否可出库 + * @param request 查询请求信息 + * @return 查询结果 + */ + @Override + public WcsApiResponse canOut(WcsCanFeedRequest request) { + HttpRequest httpRequest = HttpRequest.postInstanceOf(appCommon.getConfigByKey(AppConfigKeyEnums.WCS_CAN_OUT_URL.getKey()), request); +// HttpResponse httpResponse = httpClient.httpPost(httpRequest); +// if (httpResponse != null && httpResponse.isSuccess()) { +// WcsApiResponse response = new WcsApiResponse<>(); +// response = httpResponse.getData(response.getClass().asSubclass(WcsApiResponse.class)); +// return response; // } // TODO error -> success // return WcsApiResponse.error("请求未获得响应信息。", null); diff --git a/202504-Wms-MengYang-tp/wms_serve_mengyang/src/main/java/com/wms_main/service/business/IAgvLockService.java b/202504-Wms-MengYang-tp/wms_serve_mengyang/src/main/java/com/wms_main/service/business/IAgvLockService.java index d45dba5..44726c0 100644 --- a/202504-Wms-MengYang-tp/wms_serve_mengyang/src/main/java/com/wms_main/service/business/IAgvLockService.java +++ b/202504-Wms-MengYang-tp/wms_serve_mengyang/src/main/java/com/wms_main/service/business/IAgvLockService.java @@ -3,8 +3,8 @@ package com.wms_main.service.business; import com.wms_main.model.po.TAppAgvLock; /** - * 入库口互锁服务接口 - * 管理单一入库口的AGV上料互锁机制 + * AGV资源锁定服务接口 + * 管理入库口和出库口的AGV设备互锁机制 */ public interface IAgvLockService { @@ -49,4 +49,61 @@ public interface IAgvLockService { * @return 释放的锁定数量 */ int releaseAllInboundPortLocks(String inboundPort); + + // ========== 出库口锁定相关方法 ========== + + /** + * AGV到达出库口后,锁定出库口 + * @param agvId AGV设备ID + * @param outboundPort 出库口编号 + * @return 锁定结果:成功返回锁定记录,失败返回null + */ + TAppAgvLock lockOutboundPort(String agvId, String outboundPort); + + /** + * 检查出库口是否可以出料(MES系统调用) + * @param outboundPort 出库口编号 + * @return 是否可以出料 + */ + boolean canFeedToOutboundPort(String outboundPort); + + /** + * 获取当前出库口处理中的任务数量 + * @param outboundPort 出库口编号 + * @return 处理中的任务数量 + */ + int getProcessingOutTaskCount(String outboundPort); + + /** + * 获取出库口状态描述 + * @param outboundPort 出库口编号 + * @return 状态描述(空闲/占用) + */ + String getOutboundPortStatus(String outboundPort); + + /** + * 释放指定出库口的所有活动锁定 + * @param outboundPort 出库口编号 + * @return 释放的锁定数量 + */ + int releaseAllOutboundPortLocks(String outboundPort); + + // ========== 通用锁定方法 ========== + + /** + * 通用资源锁定方法 + * @param agvId AGV设备ID + * @param stationCode 站台编号(R1=入库口,C1=出库口) + * @param taskType 任务类型(1=入库,2=出库) + * @return 锁定结果:成功返回锁定记录,失败返回null + */ + TAppAgvLock lockStation(String agvId, String stationCode, Integer taskType); + + /** + * 通用资源解锁方法 + * @param stationCode 站台编号(R1=入库口,C1=出库口) + * @param taskType 任务类型(1=入库,2=出库) + * @return 释放的锁定数量 + */ + int releaseStationLocks(String stationCode, Integer taskType); } \ No newline at end of file diff --git a/202504-Wms-MengYang-tp/wms_serve_mengyang/src/main/java/com/wms_main/service/business/serviceImpl/AgvLockServiceImpl.java b/202504-Wms-MengYang-tp/wms_serve_mengyang/src/main/java/com/wms_main/service/business/serviceImpl/AgvLockServiceImpl.java index 51187c5..6d4af5e 100644 --- a/202504-Wms-MengYang-tp/wms_serve_mengyang/src/main/java/com/wms_main/service/business/serviceImpl/AgvLockServiceImpl.java +++ b/202504-Wms-MengYang-tp/wms_serve_mengyang/src/main/java/com/wms_main/service/business/serviceImpl/AgvLockServiceImpl.java @@ -19,10 +19,11 @@ import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; import java.util.List; +import java.util.Objects; /** - * 入库口互锁服务实现 - * 管理单一入库口的AGV上料互锁机制 + * AGV资源锁定服务实现 + * 管理入库口和出库口的AGV设备互锁机制 */ @Service @RequiredArgsConstructor @@ -33,6 +34,7 @@ public class AgvLockServiceImpl implements IAgvLockService { private final ITAppTaskService appTaskService; private static final String DEFAULT_INBOUND_PORT = "R1"; // 默认入库口 + private static final String DEFAULT_OUTBOUND_PORT = "C1"; // 默认出库口 private static final int DEFAULT_TIMEOUT_SECONDS = 600; // 默认超时时间10分钟 @Override @@ -203,6 +205,187 @@ public class AgvLockServiceImpl implements IAgvLockService { } } + // ========== 出库口锁定相关方法实现 ========== + + @Override + @Transactional + public TAppAgvLock lockOutboundPort(String agvId, String outboundPort) { + if (StringUtils.isEmpty(agvId)) { + log.warn("AGV ID为空"); + return null; + } + + // 使用默认出库口如果未指定 + if (StringUtils.isEmpty(outboundPort)) { + outboundPort = DEFAULT_OUTBOUND_PORT; + } + + // 清理超时锁定 + cleanTimeoutLocks(); + + // 检查出库口是否已被占用 + List existingLocks = agvLockService.list(new LambdaQueryWrapper().eq(TAppAgvLock::getFeedStation, outboundPort)); + if (existingLocks == null || existingLocks.isEmpty()) { + return createOutboundPortLock(agvId, outboundPort); + } else { + agvLockService.update( + new LambdaUpdateWrapper() + .eq(TAppAgvLock::getFeedStation, outboundPort) + .eq(TAppAgvLock::getLockStatus, WmsAgvLockEnums.AVAILABLE.getCode()) + .set(TAppAgvLock::getLockStatus, WmsAgvLockEnums.LOCK.getCode()) + .set(TAppAgvLock::getLockTime, LocalDateTime.now()) + .set(TAppAgvLock::getRemark, "AGV就位并取货,锁定出库口") + ); + return null; + } + } + + @Override + public boolean canFeedToOutboundPort(String outboundPort) { + // 使用默认出库口如果未指定 + if (StringUtils.isEmpty(outboundPort)) { + outboundPort = DEFAULT_OUTBOUND_PORT; + } + + // 清理超时锁定 + cleanTimeoutLocks(); + + // 检查出库口是否有活动的锁定 + List activeLocks = agvLockService.getByStation(outboundPort); + boolean hasActiveLock = activeLocks.stream() + .anyMatch(lock -> lock.getLockStatus() == 1); + + if (hasActiveLock) { + log.debug("出库口 {} 当前被占用,无法出料", outboundPort); + return false; + } + + // 检查是否有正在处理的出库任务 + int processingTasks = getProcessingOutTaskCount(outboundPort); + boolean canFeed = processingTasks == 0; + + log.debug("出库口 {} 可出料状态: {}, 处理中任务数: {}", outboundPort, canFeed, processingTasks); + return canFeed; + } + + @Override + public int getProcessingOutTaskCount(String outboundPort) { + // 使用默认出库口如果未指定 + if (StringUtils.isEmpty(outboundPort)) { + outboundPort = DEFAULT_OUTBOUND_PORT; + } + + // 统计正在处理的出库任务数量(包括等待、运行状态的任务) + List processingTasks = appTaskService.list( + new LambdaQueryWrapper() + .eq(TAppTask::getTaskType, WmsTaskTypeEnums.OUT.getCode()) + .in(TAppTask::getTaskStatus, + WmsStackerTaskStatusEnums.WAIT.getCode(), + WmsStackerTaskStatusEnums.RUN.getCode()) + .like(TAppTask::getDestination, outboundPort) // 假设任务目标包含出库口信息 + ); + + return processingTasks.size(); + } + + @Override + public String getOutboundPortStatus(String outboundPort) { + // 使用默认出库口如果未指定 + if (StringUtils.isEmpty(outboundPort)) { + outboundPort = DEFAULT_OUTBOUND_PORT; + } + + // 检查是否有活动锁定 + List activeLocks = agvLockService.getByStation(outboundPort); + boolean hasActiveLock = activeLocks.stream() + .anyMatch(lock -> lock.getLockStatus() == 1); + + if (hasActiveLock) { + return "占用"; + } + + // 检查是否有处理中的任务 + int processingTasks = getProcessingOutTaskCount(outboundPort); + if (processingTasks > 0) { + return "占用"; + } + + return "空闲"; + } + + @Override + @Transactional + public int releaseAllOutboundPortLocks(String outboundPort) { + // 使用默认出库口如果未指定 + if (StringUtils.isEmpty(outboundPort)) { + outboundPort = DEFAULT_OUTBOUND_PORT; + } + + try { + // 释放指定出库口的所有活动锁定 + agvLockService.update( + new LambdaUpdateWrapper() + .eq(TAppAgvLock::getFeedStation, outboundPort) + .eq(TAppAgvLock::getLockStatus, WmsAgvLockEnums.LOCK.getCode()) + .set(TAppAgvLock::getLockStatus, WmsAgvLockEnums.AVAILABLE.getCode()) + .set(TAppAgvLock::getUnlockTime, LocalDateTime.now()) + .set(TAppAgvLock::getRemark, "出库任务完成,批量释放锁定") + ); + + // 查询实际释放的锁定数量 + List activeLocks = agvLockService.getByStation(outboundPort); + int releasedCount = (int) activeLocks.stream() + .filter(lock -> lock.getLockStatus() == 0) + .count(); + + log.info("释放出库口 {} 的所有锁定,共释放 {} 个锁定", outboundPort, releasedCount); + return releasedCount; + } catch (Exception e) { + log.error("释放出库口 {} 的所有锁定失败", outboundPort, e); + return 0; + } + } + + // ========== 通用锁定方法实现 ========== + + @Override + @Transactional + public TAppAgvLock lockStation(String agvId, String stationCode, Integer taskType) { + if (StringUtils.isEmpty(agvId) || StringUtils.isEmpty(stationCode) || Objects.isNull(taskType)) { + log.warn("锁定参数不完整: agvId={}, stationCode={}, taskType={}", agvId, stationCode, taskType); + return null; + } + + if (WmsTaskTypeEnums.IN.getCode().equals(taskType)) { + return lockInboundPort(agvId, stationCode); + } else if (WmsTaskTypeEnums.OUT.getCode().equals(taskType)) { + return lockOutboundPort(agvId, stationCode); + } else { + log.warn("不支持的任务类型: {}", taskType); + return null; + } + } + + @Override + @Transactional + public int releaseStationLocks(String stationCode, Integer taskType) { + if (StringUtils.isEmpty(stationCode) || Objects.isNull(taskType)) { + log.warn("解锁参数不完整: stationCode={}, taskType={}", stationCode, taskType); + return 0; + } + + if (WmsTaskTypeEnums.IN.getCode().equals(taskType)) { + return releaseAllInboundPortLocks(stationCode); + } else if (WmsTaskTypeEnums.OUT.getCode().equals(taskType)) { + return releaseAllOutboundPortLocks(stationCode); + } else { + log.warn("不支持的任务类型: {}", taskType); + return 0; + } + } + + // ========== 私有方法 ========== + /** * 创建入库口锁定记录 */ @@ -229,4 +412,31 @@ public class AgvLockServiceImpl implements IAgvLockService { log.error("AGV {} 锁定入库口 {} 失败", agvId, inboundPort); return null; } + + /** + * 创建出库口锁定记录 + */ + private TAppAgvLock createOutboundPortLock(String agvId, String outboundPort) { + TAppAgvLock lock = new TAppAgvLock(); + lock.setLockId(UUIDUtils.getNewUUID()); + lock.setAgvId(agvId); + lock.setFeedStation(outboundPort); + lock.setLockStatus(1); + lock.setLockType("OUTBOUND_PORT_LOCK"); + lock.setPriority(1); + lock.setQueuePosition(1); + lock.setLockTime(LocalDateTime.now()); + lock.setTimeoutSeconds(DEFAULT_TIMEOUT_SECONDS); + lock.setCreateTime(LocalDateTime.now()); + lock.setUpdateTime(LocalDateTime.now()); + lock.setRemark("AGV就位并取货,锁定出库口"); + + if (agvLockService.save(lock)) { + log.info("AGV {} 成功锁定出库口 {}", agvId, outboundPort); + return lock; + } + + log.error("AGV {} 锁定出库口 {} 失败", agvId, outboundPort); + return null; + } } \ No newline at end of file diff --git a/202504-Wms-MengYang-tp/wms_serve_mengyang/src/main/java/com/wms_main/service/controller/IMyWmsControllerService.java b/202504-Wms-MengYang-tp/wms_serve_mengyang/src/main/java/com/wms_main/service/controller/IMyWmsControllerService.java index d7c7f09..d3cf1a8 100644 --- a/202504-Wms-MengYang-tp/wms_serve_mengyang/src/main/java/com/wms_main/service/controller/IMyWmsControllerService.java +++ b/202504-Wms-MengYang-tp/wms_serve_mengyang/src/main/java/com/wms_main/service/controller/IMyWmsControllerService.java @@ -16,4 +16,6 @@ public interface IMyWmsControllerService { MyWmsResponse> stock(StockReq request); MyWmsResponse queryCanFeed(); + + MyWmsResponse queryCanOut(); } diff --git a/202504-Wms-MengYang-tp/wms_serve_mengyang/src/main/java/com/wms_main/service/controller/serviceImpl/MyWmsControllerServiceImpl.java b/202504-Wms-MengYang-tp/wms_serve_mengyang/src/main/java/com/wms_main/service/controller/serviceImpl/MyWmsControllerServiceImpl.java index 646c3d4..7a563c2 100644 --- a/202504-Wms-MengYang-tp/wms_serve_mengyang/src/main/java/com/wms_main/service/controller/serviceImpl/MyWmsControllerServiceImpl.java +++ b/202504-Wms-MengYang-tp/wms_serve_mengyang/src/main/java/com/wms_main/service/controller/serviceImpl/MyWmsControllerServiceImpl.java @@ -196,4 +196,26 @@ public class MyWmsControllerServiceImpl implements IMyWmsControllerService { return MyWmsResponse.error("系统异常,请稍后重试", null); } } + + @Override + public MyWmsResponse queryCanOut() { + try { + WcsApiResponse wcsResponse = wcsApiService.canOut(new WcsCanFeedRequest("C1")); + + if (wcsResponse != null && wcsResponse.getData() != null) { + Boolean wcsAllow = wcsResponse.getData().isAllowAction(); + Boolean wmsAllow = agvLockService.canFeedToOutboundPort("C1"); + + if (wcsAllow && wmsAllow) { + return MyWmsResponse.success(true); + } else { + return MyWmsResponse.error("出库口锁定中", null); + } + } else { + return MyWmsResponse.error("WCS系统查询失败", null); + } + } catch (Exception e) { + return MyWmsResponse.error("系统异常,请稍后重试", null); + } + } } diff --git a/202504-Wms-MengYang-tp/wms_serve_mengyang/src/main/java/com/wms_main/service/controller/serviceImpl/TaskControllerServiceImpl.java b/202504-Wms-MengYang-tp/wms_serve_mengyang/src/main/java/com/wms_main/service/controller/serviceImpl/TaskControllerServiceImpl.java index 0a6f691..39ae640 100644 --- a/202504-Wms-MengYang-tp/wms_serve_mengyang/src/main/java/com/wms_main/service/controller/serviceImpl/TaskControllerServiceImpl.java +++ b/202504-Wms-MengYang-tp/wms_serve_mengyang/src/main/java/com/wms_main/service/controller/serviceImpl/TaskControllerServiceImpl.java @@ -2,17 +2,12 @@ package com.wms_main.service.controller.serviceImpl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; -import com.wms_main.app.AppCommon; import com.wms_main.constant.enums.wcs.WcsStackerTaskStatusEnums; -import com.wms_main.constant.enums.wcs.WcsStackerTaskTypeEnums; import com.wms_main.constant.enums.wms.*; import com.wms_main.dao.*; -import com.wms_main.model.bo.wcs.WcsStackerTask; import com.wms_main.model.dto.request.wcs.WcsTaskResultRequest; import com.wms_main.model.dto.request.wcs.WcsVehicleInRequest; import com.wms_main.model.dto.response.wcs.BaseWcsApiResponse; -import com.wms_main.model.dto.response.wcs.InTaskResp; -import com.wms_main.model.dto.response.wcs.WcsApiResponse; import com.wms_main.model.dto.response.wcs.WcsVehicleInResponse; import com.wms_main.model.po.*; import com.wms_main.repository.utils.ConvertUtils; @@ -25,7 +20,6 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.springframework.transaction.interceptor.TransactionAspectSupport; import java.time.LocalDateTime; import java.util.*; @@ -107,7 +101,6 @@ public class TaskControllerServiceImpl implements ITaskControllerService { .set(TAppOrderIn::getOrderStatus, OrderStatusEnum.RUNNING.getCode()) .set(TAppOrderIn::getUpdateTime, LocalDateTime.now()) ); - agvLockService.lockInboundPort("AGV", ""); WcsVehicleInResponse success = new WcsVehicleInResponse(); success.setCode("200"); @@ -159,19 +152,26 @@ public class TaskControllerServiceImpl implements ITaskControllerService { .eq(TAppOrderIn::getOrderId, wmsTask.getTaskGroup()) .set(TAppOrderIn::getOrderStatus, OrderStatusEnum.COMPLETE.getCode()) .set(TAppOrderIn::getCompleteTime, LocalDateTime.now())); - - // 如果是入库任务完成,释放入库口锁定 - if (wmsTask != null && WmsTaskTypeEnums.IN.getCode().equals(wmsTask.getTaskType())) { + + // 任务完成时释放相应资源锁定 + if (wmsTask != null) { try { - String inboundPort = "R1"; // 默认入库口 - - // 释放该入库口的所有活动锁定,不关心具体是哪台AGV - int releasedCount = agvLockService.releaseAllInboundPortLocks(inboundPort); - log.info("入库任务完成,释放入库口 {} 的锁定,共释放 {} 个锁定", inboundPort, releasedCount); + if (WmsTaskTypeEnums.IN.getCode().equals(wmsTask.getTaskType())) { + // 入库任务完成,释放入库口R1锁定 + int releasedCount = agvLockService.releaseStationLocks("R1", WmsTaskTypeEnums.IN.getCode()); + log.info("入库任务完成,释放入库口R1锁定,任务ID: {}, 释放锁定数: {}", + wcsTaskResultRequest.getTaskId(), releasedCount); + } else if (WmsTaskTypeEnums.OUT.getCode().equals(wmsTask.getTaskType())) { + // 出库任务完成,释放出库口C1锁定 + int releasedCount = agvLockService.releaseStationLocks("C1", WmsTaskTypeEnums.OUT.getCode()); + log.info("出库任务完成,释放出库口C1锁定,任务ID: {}, 释放锁定数: {}", + wcsTaskResultRequest.getTaskId(), releasedCount); + } } catch (Exception e) { - log.error("释放入库口锁定时发生异常", e); + log.error("任务完成时释放资源锁定失败,任务ID: {}", wcsTaskResultRequest.getTaskId(), e); } } + // 移除wcs任务,并向wcs备份表添加记录 TAppWcsTaskBak wcsTaskBak = new TAppWcsTaskBak( wcsTask.getWcsTaskId(), @@ -204,37 +204,4 @@ public class TaskControllerServiceImpl implements ITaskControllerService { } return BaseWcsApiResponse.success("处理任务状态反馈成功。"); } - - /** - * 从任务来源字符串中提取AGV ID - * 根据实际的AGV命名规则调整此方法 - */ - private String extractAgvIdFromOrigin(String origin) { - if (StringUtils.isEmpty(origin)) { - return null; - } - - // 示例逻辑:假设origin格式为 "AGV001_R1" 或包含AGV ID的格式 - if (origin.startsWith("AGV")) { - int underscoreIndex = origin.indexOf("_"); - if (underscoreIndex > 0) { - return origin.substring(0, underscoreIndex); - } - return origin; // 如果没有下划线,整个字符串就是AGV ID - } - - // 其他可能的格式 - if (origin.matches(".*AGV\\d+.*")) { - // 使用正则表达式提取AGV ID - java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("AGV\\d+"); - java.util.regex.Matcher matcher = pattern.matcher(origin); - if (matcher.find()) { - return matcher.group(); - } - } - - // 如果无法识别,返回null - log.warn("无法从origin字符串 '{}' 中提取AGV ID", origin); - return null; - } } diff --git a/202504-Wms-MengYang-tp/wms_serve_mengyang/src/main/java/com/wms_main/service/quartz_job/job_executor/WcsStackerTaskSender.java b/202504-Wms-MengYang-tp/wms_serve_mengyang/src/main/java/com/wms_main/service/quartz_job/job_executor/WcsStackerTaskSender.java index 60bb3b0..2f5dc7f 100644 --- a/202504-Wms-MengYang-tp/wms_serve_mengyang/src/main/java/com/wms_main/service/quartz_job/job_executor/WcsStackerTaskSender.java +++ b/202504-Wms-MengYang-tp/wms_serve_mengyang/src/main/java/com/wms_main/service/quartz_job/job_executor/WcsStackerTaskSender.java @@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.wms_main.constant.enums.wcs.WcsApiResponseCodeEnums; import com.wms_main.constant.enums.wcs.WcsStackerTaskStatusEnums; import com.wms_main.constant.enums.wms.WmsStackerTaskStatusEnums; +import com.wms_main.constant.enums.wms.WmsTaskTypeEnums; import com.wms_main.dao.ITAppTaskService; import com.wms_main.dao.ITAppWcsTaskService; import com.wms_main.model.bo.wcs.WcsStackerTask; @@ -14,6 +15,7 @@ import com.wms_main.model.po.TAppTask; import com.wms_main.model.po.TAppWcsTask; import com.wms_main.repository.utils.StringUtils; import com.wms_main.service.api.IWcsApiService; +import com.wms_main.service.business.IAgvLockService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.quartz.*; @@ -43,6 +45,10 @@ public class WcsStackerTaskSender implements Job { * Wcs接口服务 */ private final IWcsApiService wcsApiService; + /** + * AGV锁定服务 + */ + private final IAgvLockService agvLockService; /** * 运行定时任务 @@ -88,6 +94,31 @@ public class WcsStackerTaskSender implements Job { .set(TAppTask::getTaskStatus, WmsStackerTaskStatusEnums.SEND.getCode()) .eq(TAppTask::getWcsTaskId, wcsTask.getWcsTaskId()) ); + + // 任务下发成功后,锁定相应资源 + try { + // 获取对应的WMS任务来判断任务类型 + TAppTask wmsTask = appTaskService.getOne( + new LambdaQueryWrapper() + .eq(TAppTask::getWcsTaskId, wcsTask.getWcsTaskId()) + ); + + if (wmsTask != null) { + String agvId = StringUtils.isNotEmpty(wcsTask.getVehicleId()) ? wcsTask.getVehicleId() : "AGV"; + + if (WmsTaskTypeEnums.IN.getCode().equals(wmsTask.getTaskType())) { + // 入库任务锁定入库口R1 + agvLockService.lockStation(agvId, "R1", WmsTaskTypeEnums.IN.getCode()); + log.info("入库任务下发成功,锁定入库口R1,任务ID: {}, AGV: {}", wcsTask.getWcsTaskId(), agvId); + } else if (WmsTaskTypeEnums.OUT.getCode().equals(wmsTask.getTaskType())) { + // 出库任务锁定出库口C1 + agvLockService.lockStation(agvId, "C1", WmsTaskTypeEnums.OUT.getCode()); + log.info("出库任务下发成功,锁定出库口C1,任务ID: {}, AGV: {}", wcsTask.getWcsTaskId(), agvId); + } + } + } catch (Exception e) { + log.error("任务下发成功但锁定资源失败,任务ID: {}", wcsTask.getWcsTaskId(), e); + } TAppWcsTask doingWcsTask; do { doingWcsTask = appWcsTaskService.getOne(new LambdaQueryWrapper().eq(TAppWcsTask::getWcsTaskId, wcsTask.getWcsTaskId())); diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..f1cfe56 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,112 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## 项目概述 + +这是一个基于Spring Boot + Vue.js开发的智能化仓库管理系统(WMS),专门为宝应梦阳公司设计。包含两个版本: +- **202504-Wms-MengYang-box**: 箱式仓库版本 +- **202504-Wms-MengYang-tp**: 托盘式仓库版本 + +## 开发命令 + +### 后端开发 (Spring Boot) +```bash +# 进入后端目录 +cd 202504-Wms-MengYang-tp/wms_serve_mengyang +# 或 cd 202504-Wms-MengYang-box/wms_serve_mengyang + +# Maven构建 +mvn clean install + +# 运行后端服务 (端口12315) +mvn spring-boot:run + +# 编译打包 +mvn package +``` + +### 前端开发 (Vue.js) +```bash +# 进入前端目录 +cd 202504-Wms-MengYang-tp/wms_web_mengyang +# 或 cd 202504-Wms-MengYang-box/wms_web_mengyang + +# 安装依赖 +npm install + +# 开发服务器 +npm run serve + +# 生产构建 +npm run build +``` + +### 数据库设置 +```sql +-- 导入数据库脚本 +-- TP版本: 202504-Wms-MengYang-tp/db/wms_mengyang_tp.sql +-- Box版本: 202504-Wms-MengYang-box/db/wms_mengyang_box.sql +``` + +## 架构设计 + +### 后端分层架构 +- **controller**: REST API控制层,分为标准WMS控制器和自定义MyWMS控制器 +- **service**: 业务服务层,包含api、business、controller、quartz_job四个子模块 +- **dao/mapper**: 数据访问层,使用MyBatis Plus +- **model**: 数据模型层,包含po/dto/vo/bo四种对象类型 +- **repository**: 仓储模式实现,支持HTTP和TCP通信 +- **config**: 配置层,包含MybatisPlus、资源配置等 + +### 核心业务模块 +- **库存管理**: TAppStock(库存表)、TAppLocation(库位表) +- **订单管理**: TAppOrderIn(入库)、TAppOrderOut(出库)、TAppTask(任务) +- **货物管理**: TAppGoods(货物)、TAppProduct(产品)、TAppVehicle(载具) +- **设备集成**: WCS接口、AGV锁定、堆垛机任务控制 + +### 前端架构 +- 基于Vue 3 + Element Plus的SPA应用 +- 使用Vuex进行状态管理,Vue Router处理路由 +- 支持Excel导入导出、二维码扫描、打印功能 + +## 技术栈 + +### 后端 +- Spring Boot 3.3.5 + Java 21 +- MyBatis Plus 3.5.7 + MySQL 8.0 +- Quartz调度 + Fastjson + Hutool工具库 + +### 前端 +- Vue 3.2.13 + Element Plus 2.4.0 +- Axios HTTP客户端 + QRCode二维码支持 +- EasyExcel处理Excel文件 + +## 关键配置 + +### 应用配置 +- 后端服务端口:12315 +- 数据库连接:application.yml中配置 +- 前端代理配置:vue.config.js中的devServer设置 + +### 重要枚举 +- **AppConfigKeyEnums**: 系统配置项枚举 +- **TaskTypeEnums**: 任务类型枚举 +- **StockStatusEnums**: 库存状态枚举 + +## 开发注意事项 + +### 代码结构约定 +- Controller层只处理HTTP请求响应,业务逻辑放在Service层 +- 使用DTO在Controller层传输数据,内部业务使用BO对象 +- 数据库操作统一通过MyBatis Plus的BaseMapper进行 + +### WCS集成规范 +- WCS API调用通过IWcsApiService接口统一管理 +- AGV设备操作需要使用锁定机制防止冲突 +- 任务下发前要检查设备状态和库位可用性 + +### 数据处理规范 +- Excel导入导出使用EasyExcel库,统一在excel包下处理 +- 图片文件存储路径通过配置管理,支持本地和云存储 +- 二维码生成和识别功能封装在utils工具类中 \ No newline at end of file