feat:任务定时任务下发

This commit is contained in:
2026-07-24 10:06:35 +08:00
parent 6f566b7a69
commit 89c21e900e
14 changed files with 623 additions and 199 deletions

View File

@@ -0,0 +1,45 @@
package cn.code.nl.framework.common.util.http;
import cn.code.nl.framework.common.util.json.JsonUtils;
import cn.hutool.core.util.StrUtil;
import java.util.Collections;
import java.util.Map;
/**
* ACS 调用工具
*/
public class AcsUtil {
/**
* 发送 POST 请求并解析响应
*
* @param serverAddress ACS 服务地址
* @param api API 路径
* @param request 请求参数
* @param responseType 响应类型
* @return 响应对象
*/
public static <T> T post(String serverAddress, String api, Object request, Class<T> responseType) {
String url = buildUrl(serverAddress, api);
String response = HttpUtils.post(url, headers(), JsonUtils.toJsonString(request));
return JsonUtils.parseObject(response, responseType);
}
/**
* 拼接服务地址和 API
*/
private static String buildUrl(String serverAddress, String api) {
String baseUrl = StrUtil.removeSuffix(serverAddress, StrUtil.SLASH);
String apiPath = StrUtil.addPrefixIfNot(api, StrUtil.SLASH);
return baseUrl + apiPath;
}
/**
* 构建 JSON 请求头
*/
private static Map<String, String> headers() {
return Collections.singletonMap("Content-Type", "application/json;charset=UTF-8");
}
}

View File

@@ -1,9 +1,11 @@
package cn.code.nl.module.lms.api;
import cn.code.nl.framework.common.enums.RpcConstants;
import cn.code.nl.framework.execute.biz.api.AbstractTaskCommonApiImpl;
import cn.code.nl.framework.execute.biz.api.lms.LmsTaskCommonApi;
import org.springframework.context.annotation.Primary;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
@@ -14,5 +16,6 @@ import org.springframework.web.bind.annotation.RestController;
@RestController
@Validated
@Primary
@RequestMapping(RpcConstants.LMS_PREFIX)
public class LmsTaskExecuteApiImpl extends AbstractTaskCommonApiImpl implements LmsTaskCommonApi {
}

View File

@@ -146,7 +146,7 @@ public class TransportTaskDO extends BaseDO {
/**
* 生成方式
*
* 枚举 {@link TODO user_type 对应的类}
* 枚举
*/
private String createMode;
/**

View File

@@ -66,6 +66,17 @@ public interface TransportTaskMapper extends BaseMapperX<TransportTaskDO> {
return selectOne(TransportTaskDO::getTaskCode, taskCode);
}
/**
* 查询自动下发的待下发任务
*/
default List<TransportTaskDO> selectAutoIssueReadyList() {
return selectList(new LambdaQueryWrapperX<TransportTaskDO>()
.eq(TransportTaskDO::getIsAutoIssue, "1")
.eq(TransportTaskDO::getTaskStatus, TransportTaskStatusEnum.READY.getCode())
.orderByDesc(TransportTaskDO::getPriority)
.orderByAsc(TransportTaskDO::getTaskId));
}
/**
* 按业务归属查询未完结任务(幂等校验用)
*/
@@ -87,4 +98,16 @@ public interface TransportTaskMapper extends BaseMapperX<TransportTaskDO> {
return update(null, wrapper) > 0;
}
/**
* 更新下发结果
*/
default void updateIssueResult(Long taskId, String taskStatus, String resultParam, String remark) {
TransportTaskDO updateObj = new TransportTaskDO();
updateObj.setTaskId(taskId);
updateObj.setTaskStatus(taskStatus);
updateObj.setResultParam(resultParam);
updateObj.setRemark(remark);
updateById(updateObj);
}
}

View File

@@ -1,28 +1,38 @@
package cn.code.nl.module.task.job;
import cn.code.nl.framework.tenant.core.job.TenantJob;
import cn.code.nl.module.task.service.transporttask.TransportTaskService;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 任务相关的定时任务
*
* @Author: liyongde
* @Date: 2026/7/13 14:01
*/
@Slf4j
@Component
public class TaskScheduleJob {
@Resource
private TransportTaskService transportTaskService;
@XxlJob("autoTaskAssignmentJob")
@TenantJob
public void autoTaskAssignmentJob() {
log.info("自动下发任务开始.....");
XxlJobHelper.log("自动下发任务开始");
// todo: 具体业务
transportTaskService.autoIssueTransportTasks();
String msg = "自动下发任务成功";
String msg = "自动下发任务结束";
XxlJobHelper.log(msg);
XxlJobHelper.handleSuccess(msg);
log.info(msg);
}
}

View File

@@ -0,0 +1,22 @@
package cn.code.nl.module.task.job.dto;
import lombok.Data;
/**
* 基础DTO对象
* @Author: liyongde
* @Date: 2026/7/24 8:49
*/
@Data
public class AcsBaseReqDTO {
/**
* 请求号: traceId
*/
private String traceId;
/**
*
* 时间
*/
private Long timestamp;
}

View File

@@ -0,0 +1,51 @@
package cn.code.nl.module.task.job.dto;
import lombok.Data;
import java.util.List;
/**
* ACS 任务下发响应
*/
@Data
public class AcsIssueResultDTO {
/**
* 是否整体成功
*/
private Boolean success;
/**
* 响应编码
*/
private String code;
/**
* 响应消息
*/
private String msg;
/**
* 下发失败的任务
*/
private List<FailedTask> failedTasks;
/**
* ACS 失败任务明细
*/
@Data
public static class FailedTask {
/**
* 任务标识
*/
private Long taskId;
/**
* 错误信息
*/
private String errorMessage;
}
}

View File

@@ -0,0 +1,71 @@
package cn.code.nl.module.task.job.dto;
import lombok.Data;
import java.util.Map;
/**
* 下发给ACS的实体
* @Author: liyongde
* @Date: 2026/7/23 17:10
*/
@Data
public class AcsTaskDTO extends AcsBaseReqDTO{
/**
* 任务标识
*/
private Long taskId;
/**
* 任务编码
*/
private String taskCode;
/**
* 取货点1
*/
private String startDeviceCode;
/**
* 放货点1
*/
private String nextDeviceCode;
/**
* 取货点2
*/
private String startDeviceCode2;
/**
* 放货点2
*/
private String nextDeviceCode2;
/**
* 优先级
*/
private String priority;
/**
* 载具号
*/
private String vehicleCode;
/**
* 载具号2
*/
private String vehicleCode2;
/**
* 任务类型
*/
private String taskType;
/**
* Agv系统类型
*/
private String agvSystemType;
/**
* 备注
*/
private String remark;
/**
* 扩展参数
*/
private Map<String, Object> payload;
/**
* 生产区域
*/
private String productArea;
}

View File

@@ -0,0 +1,138 @@
package cn.code.nl.module.task.manage;
import cn.code.nl.framework.common.exception.ServiceException;
import cn.code.nl.framework.common.pojo.CommonResult;
import cn.code.nl.framework.common.util.http.AcsUtil;
import cn.code.nl.framework.common.util.json.JsonUtils;
import cn.code.nl.module.infra.api.config.ConfigApi;
import cn.code.nl.module.task.dal.dataobject.transporttask.TransportTaskDO;
import cn.code.nl.module.task.dal.mysql.transporttask.TransportTaskMapper;
import cn.code.nl.module.task.enums.TransportTaskStatusEnum;
import cn.code.nl.module.task.job.dto.AcsIssueResultDTO;
import cn.code.nl.module.task.job.dto.AcsTaskDTO;
import cn.code.nl.module.task.utils.AcsTaskUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 搬运任务下发管理器
*/
@Slf4j
@Component
public class TransportTaskIssueManager {
private static final String ACS_TASK_API = "/acs-api/wms/task";
private static final String ACS_SERVER_ADDRESS_CONFIG_SUFFIX = "-acs-server-address";
@Resource
private TransportTaskMapper transportTaskMapper;
@Resource
private ConfigApi configApi;
/**
* 按生产区域分组下发任务
*
* @param tasks 待下发任务
*/
public void issueTasks(List<TransportTaskDO> tasks) {
tasks.stream()
.filter(task -> StrUtil.isBlank(task.getProductArea()))
.forEach(task ->
updateIssueFailed(task.getTaskId(), "生产区域为空,无法获取 ACS 服务地址", null));
Map<String, List<TransportTaskDO>> taskMap = tasks.stream()
.filter(task -> StrUtil.isNotBlank(task.getProductArea()))
.collect(Collectors.groupingBy(TransportTaskDO::getProductArea));
taskMap.forEach(this::issueProductAreaTasks);
}
/**
* 下发单个生产区域的任务
*
* @param productArea 生产区域
* @param tasks 待下发任务
*/
private void issueProductAreaTasks(String productArea, List<TransportTaskDO> tasks) {
List<AcsTaskDTO> acsTasks = tasks.stream().map(AcsTaskUtil::buildAcsTaskDTO).toList();
String requestJson = JsonUtils.toJsonString(acsTasks);
try {
String serverAddress = getAcsServerAddress(productArea);
AcsIssueResultDTO result = AcsUtil.post(serverAddress, ACS_TASK_API, acsTasks, AcsIssueResultDTO.class);
handleIssueResult(tasks, result);
} catch (Exception ex) {
log.error("自动下发任务失败productArea={}tasks={}", productArea, requestJson, ex);
tasks.forEach(task -> updateIssueFailed(task.getTaskId(), ex.getMessage(), null));
}
}
/**
* 获取 ACS 服务地址
*
* @param productArea 生产区域
* @return ACS 服务地址
*/
private String getAcsServerAddress(String productArea) {
String configKey = productArea + ACS_SERVER_ADDRESS_CONFIG_SUFFIX;
CommonResult<String> result = configApi.getConfigValueByKey(configKey);
String serverAddress = result.getCheckedData();
if (StrUtil.isBlank(serverAddress)) {
throw new ServiceException(500, "未配置 ACS 服务地址:" + configKey);
}
return serverAddress;
}
/**
* 处理 ACS 下发响应
*
* @param tasks 本次下发任务
* @param result ACS 响应
*/
private void handleIssueResult(List<TransportTaskDO> tasks, AcsIssueResultDTO result) {
if (result == null) {
tasks.forEach(task -> updateIssueFailed(task.getTaskId(), "ACS 返回为空", null));
return;
}
String resultJson = JsonUtils.toJsonString(result);
List<AcsIssueResultDTO.FailedTask> failedTasks = result.getFailedTasks() == null
? Collections.emptyList()
: result.getFailedTasks();
if (Boolean.FALSE.equals(result.getSuccess()) && CollUtil.isEmpty(failedTasks)) {
String errorMessage = StrUtil.blankToDefault(result.getMsg(), "ACS 下发失败");
tasks.forEach(task -> updateIssueFailed(task.getTaskId(), errorMessage, resultJson));
return;
}
Map<Long, AcsIssueResultDTO.FailedTask> failedTaskMap = failedTasks.stream()
.filter(failedTask -> failedTask.getTaskId() != null)
.collect(Collectors.toMap(AcsIssueResultDTO.FailedTask::getTaskId, Function.identity(), (first, second) -> first));
Set<Long> failedTaskIds = failedTaskMap.keySet();
for (TransportTaskDO task : tasks) {
if (failedTaskIds.contains(task.getTaskId())) {
updateIssueFailed(task.getTaskId(), failedTaskMap.get(task.getTaskId()).getErrorMessage(), resultJson);
} else {
transportTaskMapper.updateIssueResult(task.getTaskId(), TransportTaskStatusEnum.ISSUED.getCode(), resultJson, null);
}
}
}
/**
* 更新任务为下发失败
*
* @param taskId 任务标识
* @param errorMessage 错误信息
* @param resultJson ACS 响应 JSON
*/
private void updateIssueFailed(Long taskId, String errorMessage, String resultJson) {
transportTaskMapper.updateIssueResult(taskId, TransportTaskStatusEnum.FAILED.getCode(), resultJson, errorMessage);
}
}

View File

@@ -92,4 +92,9 @@ public interface TransportTaskService {
*/
TaskInfoDTO getTaskInfoByCode(String taskCode);
/**
* 自动下发待下发任务到 ACS
*/
void autoIssueTransportTasks();
}

View File

@@ -11,8 +11,13 @@ import cn.code.nl.module.task.dal.mysql.transporttask.TransportTaskMapper;
import cn.code.nl.module.task.dto.AcsFeedbackReqDTO;
import cn.code.nl.module.task.dto.TaskInfoDTO;
import cn.code.nl.module.task.dto.TransportTaskCreateReqDTO;
import cn.code.nl.module.task.enums.*;
import cn.code.nl.module.task.enums.CallbackStatusEnum;
import cn.code.nl.module.task.enums.FinishedTypeEnum;
import cn.code.nl.module.task.enums.TaskOperationTypeEnum;
import cn.code.nl.module.task.enums.TransportTaskStatusEnum;
import cn.code.nl.module.task.manage.TransportTaskIssueManager;
import cn.code.nl.module.task.manage.TransportTaskOperationManager;
import cn.hutool.core.collection.CollUtil;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@@ -42,21 +47,19 @@ public class TransportTaskServiceImpl implements TransportTaskService {
@Resource
private TransportTaskOperationManager transportTaskOperationManager;
@Resource
private TransportTaskIssueManager transportTaskIssueManager;
@Override
public Long createTransportTask(TransportTaskSaveReqVO createReqVO) {
// 插入
TransportTaskDO transportTask = BeanUtils.toBean(createReqVO, TransportTaskDO.class);
transportTaskMapper.insert(transportTask);
// 返回
return transportTask.getTaskId();
}
@Override
public Long createTransportTaskByRpc(TransportTaskCreateReqDTO reqDTO) {
// 1. 构建 DO 并保存
TransportTaskDO task = BeanUtils.toBean(reqDTO, TransportTaskDO.class);
// 参数完整 → 待下发(040),否则 → 生成(010)
task.setTaskStatus(reqDTO.getIsCreateFinish()
? TransportTaskStatusEnum.READY.getCode()
: TransportTaskStatusEnum.CREATED.getCode());
@@ -70,28 +73,27 @@ public class TransportTaskServiceImpl implements TransportTaskService {
@Override
public void updateTransportTask(TransportTaskSaveReqVO updateReqVO) {
// 校验存在
validateTransportTaskExists(updateReqVO.getTaskId());
// 更新
TransportTaskDO updateObj = BeanUtils.toBean(updateReqVO, TransportTaskDO.class);
transportTaskMapper.updateById(updateObj);
}
@Override
public void deleteTransportTask(Long id) {
// 校验存在
validateTransportTaskExists(id);
// 删除
transportTaskMapper.deleteById(id);
}
@Override
public void deleteTransportTaskListByIds(List<Long> ids) {
// 删除
public void deleteTransportTaskListByIds(List<Long> ids) {
transportTaskMapper.deleteByIds(ids);
}
}
/**
* 校验搬运任务是否存在
*
* @param id 任务标识
*/
private void validateTransportTaskExists(Long id) {
if (transportTaskMapper.selectById(id) == null) {
throw exception(TRANSPORT_TASK_NOT_EXISTS);
@@ -109,55 +111,59 @@ public class TransportTaskServiceImpl implements TransportTaskService {
}
/**
* PC 端操作搬运任务(完成/取消/强制完成):先校验再走统一路由表分发
* PC 端操作搬运任务,统一走状态操作分发
*
* @param reqVO 操作请求
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void operateTransportTask(TransportTaskOperateReqVO reqVO) {
// 1. 校验任务存在
TransportTaskDO task = transportTaskMapper.selectById(reqVO.getTaskId());
if (task == null) {
throw exception(TRANSPORT_TASK_NOT_EXISTS);
}
// 2. 校验操作类型允许 PC 端触发
TaskOperationTypeEnum type = TaskOperationTypeEnum.getByCode(reqVO.getOperationType());
if (type == null || !type.isPcAllowed()) {
throw exception(TRANSPORT_TASK_OPERATION_NOT_SUPPORTED);
}
// 3. 终态校验:已完成/已取消不允许再操作
if (TransportTaskStatusEnum.FINISHED.getCode().equals(task.getTaskStatus())
|| TransportTaskStatusEnum.CANCELLED.getCode().equals(task.getTaskStatus())) {
throw exception(TRANSPORT_TASK_ALREADY_FINAL);
}
// 4. PC 端与 ACS 的差异点:记录完成类型
if (type == TaskOperationTypeEnum.FINISHED) {
task.setFinishedType(FinishedTypeEnum.MANUAL.getCode());
} else if (type == TaskOperationTypeEnum.FORCE_FINISH) {
task.setFinishedType(FinishedTypeEnum.MANUAL_FORCE.getCode());
}
// 5. 构造精简反馈对象,走统一路由表分发
AcsFeedbackReqDTO reqDTO = new AcsFeedbackReqDTO();
reqDTO.setTaskId(task.getTaskId());
reqDTO.setStatus(type.getCode());
transportTaskOperationManager.dispatchOperation(task, type, reqDTO);
}
/**
* 根据 taskId 查询任务全量信息:查不到返回 null由调用方判断
*/
@Override
public TaskInfoDTO getTaskInfoById(Long taskId) {
TransportTaskDO task = transportTaskMapper.selectById(taskId);
return TransportTaskConvert.INSTANCE.convert(task);
}
/**
* 根据 taskCode 查询任务全量信息:查不到返回 null由调用方判断
*/
@Override
public TaskInfoDTO getTaskInfoByCode(String taskCode) {
TransportTaskDO task = transportTaskMapper.selectByTaskCode(taskCode);
return TransportTaskConvert.INSTANCE.convert(task);
}
/**
* 查询待下发任务,并委托下发管理器按生产区域下发到 ACS
*/
@Override
public void autoIssueTransportTasks() {
List<TransportTaskDO> tasks = transportTaskMapper.selectAutoIssueReadyList();
if (CollUtil.isEmpty(tasks)) {
log.info("自动下发任务结束:没有待下发任务");
return;
}
transportTaskIssueManager.issueTasks(tasks);
}
}

View File

@@ -0,0 +1,41 @@
package cn.code.nl.module.task.utils;
import cn.code.nl.framework.common.util.json.JsonUtils;
import cn.code.nl.module.task.dal.dataobject.transporttask.TransportTaskDO;
import cn.code.nl.module.task.job.dto.AcsTaskDTO;
import java.util.UUID;
/**
* ACS 任务工具类
*/
public class AcsTaskUtil {
/**
* 构建 ACS 下发任务
*
* @param task 搬运任务
* @return ACS 下发任务
*/
public static AcsTaskDTO buildAcsTaskDTO(TransportTaskDO task) {
AcsTaskDTO dto = new AcsTaskDTO();
dto.setTraceId(UUID.randomUUID().toString());
dto.setTimestamp(System.currentTimeMillis());
dto.setTaskId(task.getTaskId());
dto.setTaskCode(task.getTaskCode());
dto.setStartDeviceCode(task.getPointCode1());
dto.setNextDeviceCode(task.getPointCode2());
dto.setStartDeviceCode2(task.getPointCode3());
dto.setNextDeviceCode2(task.getPointCode4());
dto.setPriority(task.getPriority());
dto.setVehicleCode(task.getVehicleCode());
dto.setVehicleCode2(task.getVehicleCode2());
dto.setTaskType(task.getAcsTaskType());
dto.setAgvSystemType(task.getAgvSystemType());
dto.setRemark(task.getRemark());
dto.setPayload(JsonUtils.parseMap(task.getDispatchParam()));
dto.setProductArea(task.getProductArea());
return dto;
}
}

View File

@@ -0,0 +1,6 @@
/**
* 本模块业务员这中使用工具类
* @Author: liyongde
* @Date: 2026/7/23 17:02
*/
package cn.code.nl.module.task.utils;

View File

@@ -1,9 +1,11 @@
package cn.code.nl.module.wms.api;
import cn.code.nl.framework.common.enums.RpcConstants;
import cn.code.nl.framework.execute.biz.api.AbstractTaskCommonApiImpl;
import cn.code.nl.framework.execute.biz.api.wms.WmsTaskCommonApi;
import org.springframework.context.annotation.Primary;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
@@ -14,5 +16,6 @@ import org.springframework.web.bind.annotation.RestController;
@RestController
@Validated
@Primary
@RequestMapping(RpcConstants.WMS_PREFIX)
public class WmsTaskExecuteApiImpl extends AbstractTaskCommonApiImpl implements WmsTaskCommonApi {
}