修改资源锁定规则,改为任务下发wcs时锁定,任务完成时解锁,添加出库锁定,修改入库锁定
This commit is contained in:
parent
ad9ee93500
commit
218946ab0b
|
|
@ -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连接超时**: 调整数据库连接池配置
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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": "任务执行成功"
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -41,4 +41,9 @@ public class MyWmsController {
|
|||
public MyWmsResponse<Boolean> canFeed() {
|
||||
return myWmsControllerService.queryCanFeed();
|
||||
}
|
||||
|
||||
@GetMapping("/allowOut")
|
||||
public MyWmsResponse<Boolean> canOut() {
|
||||
return myWmsControllerService.queryCanOut();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,4 +48,6 @@ public interface IWcsApiService {
|
|||
* @return 查询结果
|
||||
*/
|
||||
WcsApiResponse<WcsCanFeedResponse> canFeed(WcsCanFeedRequest request);
|
||||
|
||||
WcsApiResponse<WcsCanFeedResponse> canOut(WcsCanFeedRequest request);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -16,4 +16,6 @@ public interface IMyWmsControllerService {
|
|||
MyWmsResponse<List<StockRespGoodsDetail>> stock(StockReq request);
|
||||
|
||||
MyWmsResponse<Boolean> queryCanFeed();
|
||||
|
||||
MyWmsResponse<Boolean> queryCanOut();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
112
CLAUDE.md
Normal 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工具类中
|
||||
Loading…
Reference in New Issue
Block a user