parent
67e3ebe4fb
commit
ea83eb9eeb
17 changed files with 455 additions and 87 deletions
@ -0,0 +1,23 @@ |
||||
package org.springblade.job.processor.logistics; |
||||
|
||||
import lombok.Data; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.springframework.stereotype.Component; |
||||
import tech.powerjob.worker.core.processor.ProcessResult; |
||||
import tech.powerjob.worker.core.processor.TaskContext; |
||||
import tech.powerjob.worker.core.processor.sdk.BasicProcessor; |
||||
|
||||
/** |
||||
* 物流订单绑定模块 |
||||
*/ |
||||
@Component |
||||
@Data |
||||
@Slf4j |
||||
public class OrderBoxRefinement implements BasicProcessor { |
||||
@Override |
||||
public ProcessResult process(TaskContext taskContext) throws Exception { |
||||
System.out.println("hello world"); |
||||
log.info("hello world"); |
||||
return new ProcessResult(true); |
||||
} |
||||
} |
||||
@ -1,11 +1,11 @@ |
||||
package org.springblade.desk.logistics.pojo.entity; |
||||
package org.springblade.desk.logistics.pojo.dto; |
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema; |
||||
import lombok.Data; |
||||
|
||||
@Data |
||||
@Schema(description = "箱绑定接收") |
||||
public class AGVCallBack { |
||||
public class AGVCallBackDto { |
||||
/** |
||||
* 请求编号,每个请求都要一个唯一 |
||||
* 编号, 同一个请求重复提交, 使 |
||||
@ -1,14 +1,13 @@ |
||||
package org.springblade.desk.logistics.pojo.entity; |
||||
package org.springblade.desk.logistics.pojo.dto; |
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema; |
||||
import lombok.Data; |
||||
import lombok.EqualsAndHashCode; |
||||
|
||||
import java.util.ArrayList; |
||||
|
||||
@Data |
||||
@Schema(description = "箱绑定接收表") |
||||
public class BoxBinding { |
||||
public class BoxBindingDto { |
||||
private String boxBarcode; |
||||
private ArrayList<Long> orderIdList; |
||||
private Long wcId; |
||||
@ -0,0 +1,57 @@ |
||||
package org.springblade.desk.logistics.pojo.dto; |
||||
import com.baomidou.mybatisplus.annotation.TableName; |
||||
import io.swagger.v3.oas.annotations.media.Schema; |
||||
import lombok.Data; |
||||
import lombok.EqualsAndHashCode; |
||||
import org.springblade.core.mp.base.BaseEntity; |
||||
|
||||
import java.io.Serial; |
||||
import java.math.BigDecimal; |
||||
import java.util.Date; |
||||
|
||||
/** |
||||
* 物流任务实体类 |
||||
* |
||||
* @author: liweidong |
||||
* @create: 2026-03-03 |
||||
*/ |
||||
@Data |
||||
@Schema(description = "物流任务对象") |
||||
|
||||
public class TaskDto extends BaseEntity { |
||||
|
||||
/** |
||||
* 箱条码 |
||||
*/ |
||||
@Schema(description = "箱条码") |
||||
private String boxBarcode; |
||||
|
||||
/** |
||||
* 站点ID |
||||
*/ |
||||
@Schema(description = "站点ID") |
||||
private Long stationId; |
||||
|
||||
/** |
||||
* 作业中心ID |
||||
*/ |
||||
@Schema(description = "作业中心ID") |
||||
private Long wcId; |
||||
/** |
||||
* 当前状态 0:退回(超重) 1:站点 2:库位 3:等待 4:回库 5:结束 |
||||
*/ |
||||
@Schema(description = "当前状态 0:退回(超重) 1:站点 2:库位 3:等待 4:回库 5:结束") |
||||
private Integer taskStatus; |
||||
/** |
||||
* 开始时间 |
||||
*/ |
||||
@Schema(description = "开始时间") |
||||
private Date startTime; |
||||
/** |
||||
* 结束时间 |
||||
*/ |
||||
@Schema(description = "结束时间") |
||||
private Date endTime; |
||||
|
||||
|
||||
} |
||||
@ -0,0 +1,25 @@ |
||||
package org.springblade.desk.logistics.pojo.vo; |
||||
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize; |
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; |
||||
import lombok.Data; |
||||
import lombok.EqualsAndHashCode; |
||||
import org.springblade.desk.device.pojo.entity.EquipmentEntity; |
||||
import org.springblade.desk.logistics.pojo.entity.Task; |
||||
|
||||
import java.io.Serial; |
||||
@Data |
||||
@EqualsAndHashCode(callSuper = true) |
||||
public class TaskVO extends Task { |
||||
@Serial |
||||
private static final long serialVersionUID = 1L; |
||||
/** |
||||
* 主键ID |
||||
*/ |
||||
@JsonSerialize(using = ToStringSerializer.class) |
||||
private Long id; |
||||
/** |
||||
* 状态名称 |
||||
*/ |
||||
// String statusName;
|
||||
} |
||||
@ -1,10 +1,26 @@ |
||||
package org.springblade.desk.logistics.mapper; |
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
||||
import com.baomidou.mybatisplus.core.metadata.IPage; |
||||
import org.apache.ibatis.annotations.Param; |
||||
import org.springblade.desk.device.pojo.entity.EquipmentEntity; |
||||
import org.springblade.desk.device.pojo.vo.EquipmentVO; |
||||
import org.springblade.desk.logistics.pojo.dto.TaskDto; |
||||
import org.springblade.desk.logistics.pojo.entity.Task; |
||||
import org.springblade.desk.logistics.pojo.entity.WeighData; |
||||
import org.springblade.desk.logistics.pojo.vo.TaskVO; |
||||
|
||||
import java.util.List; |
||||
|
||||
public interface TaskMapper extends BaseMapper<Task> { |
||||
|
||||
Task selectByBoxBarcode(@Param("boxBarcode") String boxBarcode); |
||||
/** |
||||
* 自定义分页 |
||||
* |
||||
* @param page 分页参数 |
||||
* @param taskDto 查询参数 |
||||
* @return List<EquipmentVO> |
||||
*/ |
||||
List<TaskVO> selectEquipmentPage(IPage page, TaskDto taskDto); |
||||
} |
||||
|
||||
@ -1,50 +1,185 @@ |
||||
package org.springblade.desk.logistics.service.impl; |
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.springblade.desk.logistics.pojo.entity.Location; |
||||
import org.springblade.desk.logistics.pojo.entity.Station; |
||||
import org.springblade.desk.logistics.pojo.entity.Task; |
||||
import org.springblade.desk.logistics.service.ILocationService; |
||||
import org.springblade.desk.logistics.service.IStationService; |
||||
import org.springblade.desk.logistics.service.IStorageMonitoringService; |
||||
import org.springblade.desk.logistics.service.ITaskService; |
||||
import org.springframework.scheduling.annotation.Scheduled; |
||||
import org.springframework.stereotype.Service; |
||||
|
||||
import java.util.HashSet; |
||||
import java.util.Set; |
||||
import java.util.List; |
||||
|
||||
@Service |
||||
/** |
||||
* 仓储监控服务实现类 |
||||
* <p> |
||||
* 核心业务: |
||||
* 1. 定时(每5秒)扫描系统中所有空闲状态的站点 |
||||
* 2. 为每个空闲站点匹配最早创建的待库位任务 |
||||
* 3. 更新任务关联库位为空闲状态,并绑定站点与任务关系 |
||||
* 4. 触发AGV小车移动指令(待实现) |
||||
* <p> |
||||
* 设计思路: |
||||
* - 采用定时任务自动巡检,减少人工干预 |
||||
* - 按任务创建时间升序匹配,保证任务执行的公平性 |
||||
* - 增加多层空值防护和状态校验,避免无效数据库操作 |
||||
* |
||||
* @author (可补充作者信息) |
||||
* @date 2026-03-05 |
||||
* @version 1.0 |
||||
*/ |
||||
@Service // Spring业务层注解,将类注册为Spring Bean
|
||||
@Slf4j // Lombok日志注解,自动生成log对象,无需手动创建Logger
|
||||
public class StorageMonitoringServiceImpl implements IStorageMonitoringService { |
||||
private static final Set<Integer> RUNNING_STATUSES = new HashSet<>(); |
||||
|
||||
static { |
||||
// 初始化运行中任务状态
|
||||
RUNNING_STATUSES.add(Task.STATUS_START); // 任务启动
|
||||
RUNNING_STATUSES.add(Task.STATUS_CONVEYOR_START); // 输送机启动
|
||||
RUNNING_STATUSES.add(Task.STATUS_CONVEYOR_END); // 输送机结束
|
||||
RUNNING_STATUSES.add(Task.STATUS_STATION); // 站点状态
|
||||
RUNNING_STATUSES.add(Task.STATUS_LOCATION); // 库位状态
|
||||
RUNNING_STATUSES.add(Task.STATUS_WAITING); // 等待状态
|
||||
RUNNING_STATUSES.add(Task.STATUS_STATION_RECEIVE);// 站点接收
|
||||
RUNNING_STATUSES.add(Task.STATUS_BACK_TO_STORAGE);// 返库状态
|
||||
} |
||||
|
||||
/** |
||||
* 任务服务:处理箱绑定任务的创建、状态更新、删除等 |
||||
* 任务业务服务:提供任务的CRUD、状态查询、条件筛选等操作 |
||||
*/ |
||||
private final ITaskService iTaskService; |
||||
private final ITaskService taskService; |
||||
|
||||
/** |
||||
* 站点服务:处理站点状态(占用/空闲)管理 |
||||
* 站点业务服务:提供站点状态管理、站点信息查询等操作 |
||||
*/ |
||||
private final IStationService iStationService; |
||||
private final IStationService stationService; |
||||
|
||||
/** |
||||
* 库位服务:处理库位状态(占用/空闲)管理 |
||||
* 库位业务服务:提供库位状态更新、库位信息查询等操作 |
||||
*/ |
||||
private final ILocationService iLocationService; |
||||
public StorageMonitoringServiceImpl(ITaskService iTaskService, IStationService iStationService, ILocationService iLocationService) { |
||||
this.iTaskService = iTaskService; |
||||
this.iStationService = iStationService; |
||||
this.iLocationService = iLocationService; |
||||
private final ILocationService locationService; |
||||
|
||||
/** |
||||
* 构造器注入依赖(Spring官方推荐方式) |
||||
* <p> |
||||
* 优势: |
||||
* 1. 强制依赖注入,避免NPE(空指针异常) |
||||
* 2. 便于单元测试时模拟依赖 |
||||
* 3. 符合依赖倒置原则,降低耦合 |
||||
* |
||||
* @param taskService 任务服务Bean(Spring自动注入) |
||||
* @param stationService 站点服务Bean(Spring自动注入) |
||||
* @param locationService 库位服务Bean(Spring自动注入) |
||||
*/ |
||||
public StorageMonitoringServiceImpl(ITaskService taskService, IStationService stationService, ILocationService locationService) { |
||||
this.taskService = taskService; |
||||
this.stationService = stationService; |
||||
this.locationService = locationService; |
||||
} |
||||
|
||||
@Override |
||||
/** |
||||
* 仓储空闲站点监控核心方法(定时执行) |
||||
* <p> |
||||
* 执行频率:每5秒执行一次 |
||||
* 核心流程: |
||||
* 1. 拉取全量空闲状态的站点列表 |
||||
* 2. 遍历每个空闲站点,匹配对应工位的待库位任务 |
||||
* 3. 对首个任务关联的库位执行「置为空闲」操作 |
||||
* 4. 绑定任务与站点关系,更新站点预占用状态 |
||||
* 5. 触发AGV小车移动指令(待实现) |
||||
* <p> |
||||
* 异常防护: |
||||
* - 空集合直接返回,避免循环空指针 |
||||
* - 库位ID/任务/库位信息空值校验,跳过异常数据 |
||||
* - 状态未变化时跳过更新,减少数据库IO |
||||
*/ |
||||
@Scheduled(cron = "*/5 * * * * ?") // Spring定时任务注解,cron表达式控制执行频率
|
||||
@Override // 实现IStorageMonitoringService接口的抽象方法
|
||||
public void monitoringStation() { |
||||
// ========== 步骤1:查询所有空闲状态的站点 ==========
|
||||
List<Station> stationList = stationService.list( |
||||
new LambdaQueryWrapper<Station>() |
||||
.eq(Station::getStationStatus, Station.STATUS_FREE) // 筛选条件:站点状态为空闲
|
||||
); |
||||
// 日志打印空闲站点数量,便于监控和问题排查
|
||||
log.info("【仓储监控】定时任务执行 - 查询到空闲站点数量:{}", stationList == null ? 0 : stationList.size()); |
||||
|
||||
// 空值防护:无空闲站点时直接返回,避免后续无效操作
|
||||
if (stationList == null || stationList.isEmpty()) { |
||||
log.warn("【仓储监控】定时任务执行 - 未查询到空闲站点,任务提前结束"); |
||||
return; |
||||
} |
||||
|
||||
// ========== 步骤2:遍历空闲站点处理关联任务 ==========
|
||||
int processedCount = 0; // 统计成功处理的站点数量
|
||||
for (Station station : stationList) { |
||||
// 打印当前处理的站点信息,便于定位单站点异常
|
||||
log.info("【仓储监控】开始处理站点 - 站点ID:{},工位ID:{}", station.getId(), station.getWcId()); |
||||
|
||||
try { |
||||
// ========== 步骤2.1:查询对应工位的待库位任务(按创建时间升序) ==========
|
||||
List<Task> taskList = taskService.list( |
||||
new LambdaQueryWrapper<Task>() |
||||
.eq(Task::getTaskStatus, Task.STATUS_LOCATION) // 任务状态:待库位
|
||||
.eq(Task::getWcId, station.getWcId()) // 匹配当前站点的工位ID
|
||||
.orderByAsc(Task::getCreateTime) // 按创建时间升序,优先处理最早创建的任务
|
||||
); |
||||
|
||||
// 无待处理任务时跳过当前站点
|
||||
if (taskList == null || taskList.isEmpty()) { |
||||
log.info("【仓储监控】站点ID:{} - 未查询到待库位任务,跳过处理", station.getId()); |
||||
continue; |
||||
} |
||||
|
||||
// ========== 步骤2.2:获取首个任务并校验关联库位ID ==========
|
||||
Task firstTask = taskList.get(0); // 取最早创建的待库位任务
|
||||
Long locationId = firstTask.getLocationId(); // 获取任务关联的库位ID
|
||||
log.info("【仓储监控】站点ID:{} - 匹配到首个待库位任务,任务ID:{},关联库位ID:{}", |
||||
station.getId(), firstTask.getId(), locationId); |
||||
|
||||
// 空值防护:库位ID为空时跳过,避免后续查询异常
|
||||
if (locationId == null) { |
||||
log.error("【仓储监控】任务ID:{} - 关联库位ID为空,跳过当前站点处理", firstTask.getId()); |
||||
continue; |
||||
} |
||||
|
||||
// ========== 步骤2.3:查询库位信息并更新状态为空闲 ==========
|
||||
Location location = locationService.getById(locationId); // 根据库位ID查询库位信息
|
||||
// 库位不存在时跳过
|
||||
if (location == null) { |
||||
log.error("【仓储监控】库位ID:{} - 库位信息不存在,跳过当前站点处理", locationId); |
||||
continue; |
||||
} |
||||
|
||||
// 状态校验:库位已为空闲时跳过更新,减少数据库操作
|
||||
if (Location.STATUS_FREE.equals(location.getLocationStatus())) { |
||||
log.info("【仓储监控】库位ID:{} - 已为空闲状态,无需更新", locationId); |
||||
} else { |
||||
// 更新库位状态为空闲
|
||||
location.setLocationStatus(Location.STATUS_FREE); |
||||
boolean updateResult = locationService.updateById(location); |
||||
if (updateResult) { |
||||
log.info("【仓储监控】库位ID:{} - 状态更新为空闲成功", locationId); |
||||
} else { |
||||
log.error("【仓储监控】库位ID:{} - 状态更新为空闲失败,跳过后续操作", locationId); |
||||
continue; // 更新失败时不触发AGV指令
|
||||
} |
||||
} |
||||
|
||||
// ========== 步骤2.4:绑定任务与站点关系,更新站点预占用状态 ==========
|
||||
firstTask.setStationId(station.getId()); // 任务绑定当前站点ID
|
||||
taskService.updateById(firstTask); // 保存任务关联关系
|
||||
station.setStationStatus(Station.PRE_STATUS_OCCUPIED); // 站点置为预占用状态
|
||||
stationService.updateById(station); // 保存站点状态
|
||||
|
||||
// ========== 步骤2.5:触发AGV小车移动指令(待实现) ==========
|
||||
log.info("【仓储监控】站点ID:{} - 准备调用AGV接口,任务ID:{},库位ID:{}", |
||||
station.getId(), firstTask.getId(), locationId); |
||||
// todo: 调用AGV接口传递任务ID和站点ID,触发小车移动
|
||||
// agvService.dispatchAgv(firstTask.getId(), station.getId());
|
||||
|
||||
processedCount++; // 成功处理计数+1
|
||||
|
||||
} catch (Exception e) { |
||||
// 单个站点处理异常不影响其他站点,记录异常日志便于排查
|
||||
log.error("【仓储监控】站点ID:{} - 处理过程中发生异常,跳过当前站点", station.getId(), e); |
||||
continue; |
||||
} |
||||
} |
||||
|
||||
// ========== 任务结束:打印处理结果 ==========
|
||||
log.info("【仓储监控】定时任务执行完成 - 总计扫描空闲站点:{},成功处理:{}", stationList.size(), processedCount); |
||||
} |
||||
} |
||||
} |
||||
Loading…
Reference in new issue