修改资源锁定规则,改为任务下发wcs时锁定,任务完成时解锁,添加出库锁定,修改入库锁定

This commit is contained in:
李宇奇 2025-07-26 12:05:45 +08:00
parent ad9ee93500
commit 218946ab0b
14 changed files with 641 additions and 105 deletions

View File

@ -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设备集成功能
### 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连接超时**: 调整数据库连接池配置

View File

@ -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

View File

@ -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": "任务执行成功"
}

View File

@ -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"),

View File

@ -41,4 +41,9 @@ public class MyWmsController {
public MyWmsResponse<Boolean> canFeed() {
return myWmsControllerService.queryCanFeed();
}
@GetMapping("/allowOut")
public MyWmsResponse<Boolean> canOut() {
return myWmsControllerService.queryCanOut();
}
}

View File

@ -48,4 +48,6 @@ public interface IWcsApiService {
* @return 查询结果
*/
WcsApiResponse<WcsCanFeedResponse> canFeed(WcsCanFeedRequest request);
WcsApiResponse<WcsCanFeedResponse> canOut(WcsCanFeedRequest request);
}

View File

@ -120,6 +120,29 @@ public class WcsApiServiceImpl implements IWcsApiService {
// WcsApiResponse<WcsCanFeedResponse> 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<WcsCanFeedResponse> 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<WcsCanFeedResponse> response = new WcsApiResponse<>();
// response = httpResponse.getData(response.getClass().asSubclass(WcsApiResponse.class));
// return response;
// }
// TODO error -> success
// return WcsApiResponse.error("请求未获得响应信息。", null);

View File

@ -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);
}

View File

@ -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<TAppAgvLock> existingLocks = agvLockService.list(new LambdaQueryWrapper<TAppAgvLock>().eq(TAppAgvLock::getFeedStation, outboundPort));
if (existingLocks == null || existingLocks.isEmpty()) {
return createOutboundPortLock(agvId, outboundPort);
} else {
agvLockService.update(
new LambdaUpdateWrapper<TAppAgvLock>()
.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<TAppAgvLock> 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<TAppTask> processingTasks = appTaskService.list(
new LambdaQueryWrapper<TAppTask>()
.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<TAppAgvLock> 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<TAppAgvLock>()
.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<TAppAgvLock> 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;
}
}

View File

@ -16,4 +16,6 @@ public interface IMyWmsControllerService {
MyWmsResponse<List<StockRespGoodsDetail>> stock(StockReq request);
MyWmsResponse<Boolean> queryCanFeed();
MyWmsResponse<Boolean> queryCanOut();
}

View File

@ -196,4 +196,26 @@ public class MyWmsControllerServiceImpl implements IMyWmsControllerService {
return MyWmsResponse.error("系统异常,请稍后重试", null);
}
}
@Override
public MyWmsResponse<Boolean> queryCanOut() {
try {
WcsApiResponse<WcsCanFeedResponse> 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);
}
}
}

View File

@ -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;
}
}

View File

@ -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<TAppTask>()
.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<TAppWcsTask>().eq(TAppWcsTask::getWcsTaskId, wcsTask.getWcsTaskId()));

112
CLAUDE.md Normal file
View File

@ -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工具类中