opt:任务完成取消与ACS确认是否可行

This commit is contained in:
2026-07-24 13:14:43 +08:00
parent 89c21e900e
commit 4805e6482f
11 changed files with 283 additions and 25 deletions

View File

@@ -1,4 +1,4 @@
package cn.code.nl.module.task.job.dto;
package cn.code.nl.framework.common.pojo;
import lombok.Data;

View File

@@ -0,0 +1,26 @@
package cn.code.nl.framework.common.pojo;
import lombok.Data;
/**
* acs返回基础对象
* @Author: liyongde
* @Date: 2026/7/24 10:27
*/
@Data
public class AcsBaseRespDTO {
/**
* 是否整体成功
*/
private Boolean success;
/**
* 响应编码
*/
private String code;
/**
* 响应消息
*/
private String msg;
}

View File

@@ -1,8 +1,15 @@
package cn.code.nl.framework.common.util.http;
import cn.code.nl.framework.common.exception.ServerException;
import cn.code.nl.framework.common.exception.ServiceException;
import cn.code.nl.framework.common.util.json.JsonUtils;
import cn.hutool.core.util.StrUtil;
import java.net.ConnectException;
import java.net.NoRouteToHostException;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.Collections;
import java.util.Map;
@@ -22,7 +29,15 @@ public class AcsUtil {
*/
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));
String response;
try {
response = HttpUtils.post(url, headers(), JsonUtils.toJsonString(request));
} catch (RuntimeException ex) {
if (isNetworkException(ex)) {
throw new ServerException(500, "ACS服务网络不通");
}
throw ex;
}
return JsonUtils.parseObject(response, responseType);
}
@@ -42,4 +57,22 @@ public class AcsUtil {
return Collections.singletonMap("Content-Type", "application/json;charset=UTF-8");
}
/**
* 判断是否为 ACS 网络连接异常
*/
private static boolean isNetworkException(Throwable ex) {
Throwable cause = ex;
while (cause != null) {
if (cause instanceof ConnectException
|| cause instanceof SocketTimeoutException
|| cause instanceof UnknownHostException
|| cause instanceof NoRouteToHostException
|| cause instanceof SocketException) {
return true;
}
cause = cause.getCause();
}
return false;
}
}

View File

@@ -1,5 +1,6 @@
package cn.code.nl.module.task.job.dto;
import cn.code.nl.framework.common.pojo.AcsBaseRespDTO;
import lombok.Data;
import java.util.List;
@@ -8,22 +9,7 @@ import java.util.List;
* ACS 任务下发响应
*/
@Data
public class AcsIssueResultDTO {
/**
* 是否整体成功
*/
private Boolean success;
/**
* 响应编码
*/
private String code;
/**
* 响应消息
*/
private String msg;
public class AcsIssueResultRespDTO extends AcsBaseRespDTO {
/**
* 下发失败的任务

View File

@@ -0,0 +1,31 @@
package cn.code.nl.module.task.job.dto;
import lombok.Data;
/**
* ACS 任务操作校验请求
*/
@Data
public class AcsOperateCheckReqDTO {
/**
* 任务标识
*/
private Long taskId;
/**
* 任务编码
*/
private String taskCode;
/**
* 操作类型
*/
private String operationType;
/**
* 生产区域
*/
private String productArea;
}

View File

@@ -0,0 +1,32 @@
package cn.code.nl.module.task.job.dto;
import cn.code.nl.framework.common.pojo.AcsBaseRespDTO;
import lombok.Data;
/**
* ACS 任务操作校验响应
*/
@Data
public class AcsOperateCheckRespDTO extends AcsBaseRespDTO {
/**
* 是否允许操作
*/
private Boolean enableOperate;
/**
* 是否允许操作,兼容 ACS 字段
*/
private Boolean canOperate;
/**
* 是否允许操作,兼容通用 data 字段
*/
private Boolean data;
/**
* 不允许操作的原因
*/
private String message;
}

View File

@@ -2,15 +2,18 @@ package cn.code.nl.module.task.job.dto;
import lombok.Data;
import cn.code.nl.framework.common.pojo.AcsBaseReqDTO;
import java.util.Map;
/**
* 下发给ACS的实体
*
* @Author: liyongde
* @Date: 2026/7/23 17:10
*/
@Data
public class AcsTaskDTO extends AcsBaseReqDTO{
public class AcsTaskDTO extends AcsBaseReqDTO {
/**
* 任务标识
*/

View File

@@ -8,7 +8,7 @@ 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.AcsIssueResultRespDTO;
import cn.code.nl.module.task.job.dto.AcsTaskDTO;
import cn.code.nl.module.task.utils.AcsTaskUtil;
import cn.hutool.core.collection.CollUtil;
@@ -67,7 +67,7 @@ public class TransportTaskIssueManager {
String requestJson = JsonUtils.toJsonString(acsTasks);
try {
String serverAddress = getAcsServerAddress(productArea);
AcsIssueResultDTO result = AcsUtil.post(serverAddress, ACS_TASK_API, acsTasks, AcsIssueResultDTO.class);
AcsIssueResultRespDTO result = AcsUtil.post(serverAddress, ACS_TASK_API, acsTasks, AcsIssueResultRespDTO.class);
handleIssueResult(tasks, result);
} catch (Exception ex) {
log.error("自动下发任务失败productArea={}tasks={}", productArea, requestJson, ex);
@@ -97,13 +97,13 @@ public class TransportTaskIssueManager {
* @param tasks 本次下发任务
* @param result ACS 响应
*/
private void handleIssueResult(List<TransportTaskDO> tasks, AcsIssueResultDTO result) {
private void handleIssueResult(List<TransportTaskDO> tasks, AcsIssueResultRespDTO 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
List<AcsIssueResultRespDTO.FailedTask> failedTasks = result.getFailedTasks() == null
? Collections.emptyList()
: result.getFailedTasks();
if (Boolean.FALSE.equals(result.getSuccess()) && CollUtil.isEmpty(failedTasks)) {
@@ -111,9 +111,9 @@ public class TransportTaskIssueManager {
tasks.forEach(task -> updateIssueFailed(task.getTaskId(), errorMessage, resultJson));
return;
}
Map<Long, AcsIssueResultDTO.FailedTask> failedTaskMap = failedTasks.stream()
Map<Long, AcsIssueResultRespDTO.FailedTask> failedTaskMap = failedTasks.stream()
.filter(failedTask -> failedTask.getTaskId() != null)
.collect(Collectors.toMap(AcsIssueResultDTO.FailedTask::getTaskId, Function.identity(), (first, second) -> first));
.collect(Collectors.toMap(AcsIssueResultRespDTO.FailedTask::getTaskId, Function.identity(), (first, second) -> first));
Set<Long> failedTaskIds = failedTaskMap.keySet();
for (TransportTaskDO task : tasks) {
if (failedTaskIds.contains(task.getTaskId())) {

View File

@@ -0,0 +1,139 @@
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.enums.TaskOperationTypeEnum;
import cn.code.nl.module.task.job.dto.AcsOperateCheckRespDTO;
import cn.code.nl.module.task.job.dto.AcsOperateCheckReqDTO;
import cn.hutool.core.util.StrUtil;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* PC 端任务操作 ACS 校验管理器
*/
@Slf4j
@Component
public class TransportTaskOperateCheckManager {
private static final String TASK_OPERATE_ENABLE_CONFIG_KEY = "task-operate-enable";
private static final String TASK_OPERATE_ENABLE_VALUE = "1";
private static final String ACS_OPERATE_CHECK_API = "/acs-api/wms/check-enable-operate";
private static final String ACS_SERVER_ADDRESS_CONFIG_SUFFIX = "-acs-server-address";
@Resource
private ConfigApi configApi;
/**
* 按配置校验 PC 端是否允许完成或取消任务
*
* @param task 任务信息
* @param type 操作类型
*/
public void checkEnableOperate(TransportTaskDO task, TaskOperationTypeEnum type) {
if (!needCheckAcs()) {
return;
}
String serverAddress = getAcsServerAddress(task.getProductArea());
AcsOperateCheckReqDTO reqDTO = buildReqDTO(task, type);
AcsOperateCheckRespDTO result;
try {
result = AcsUtil.post(serverAddress, ACS_OPERATE_CHECK_API, reqDTO, AcsOperateCheckRespDTO.class);
} catch (ServiceException ex) {
log.error("ACS 操作校验请求失败reqDTO={}", JsonUtils.toJsonString(reqDTO), ex);
throw ex;
} catch (Exception ex) {
log.error("ACS 操作校验请求失败reqDTO={}", JsonUtils.toJsonString(reqDTO), ex);
throw new ServiceException(500, "ACS操作校验失败");
}
handleCheckResult(reqDTO, result);
}
/**
* 判断是否需要请求 ACS 校验
*/
private boolean needCheckAcs() {
CommonResult<String> result = configApi.getConfigValueByKey(TASK_OPERATE_ENABLE_CONFIG_KEY);
return TASK_OPERATE_ENABLE_VALUE.equals(result.getCheckedData());
}
/**
* 获取 ACS 服务地址
*
* @param productArea 生产区域
* @return ACS 服务地址
*/
private String getAcsServerAddress(String productArea) {
if (StrUtil.isBlank(productArea)) {
throw new ServiceException(500, "生产区域为空,无法获取 ACS 服务地址");
}
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 操作校验请求
*/
private AcsOperateCheckReqDTO buildReqDTO(TransportTaskDO task, TaskOperationTypeEnum type) {
AcsOperateCheckReqDTO reqDTO = new AcsOperateCheckReqDTO();
reqDTO.setTaskId(task.getTaskId());
reqDTO.setTaskCode(task.getTaskCode());
reqDTO.setOperationType(type.getCode());
reqDTO.setProductArea(task.getProductArea());
return reqDTO;
}
/**
* 处理 ACS 操作校验结果
*/
private void handleCheckResult(AcsOperateCheckReqDTO reqDTO, AcsOperateCheckRespDTO result) {
if (result == null) {
throw new ServiceException(500, "ACS 操作校验返回为空");
}
Boolean enableOperate = getEnableOperate(result);
if (Boolean.TRUE.equals(enableOperate)) {
return;
}
String message = getMessage(result);
log.warn("ACS 拒绝 PC 端任务操作reqDTO={}result={}",
JsonUtils.toJsonString(reqDTO), JsonUtils.toJsonString(result));
throw new ServiceException(500, message);
}
/**
* 获取 ACS 是否允许操作
*/
private Boolean getEnableOperate(AcsOperateCheckRespDTO result) {
if (result.getEnableOperate() != null) {
return result.getEnableOperate();
}
if (result.getCanOperate() != null) {
return result.getCanOperate();
}
if (result.getData() != null) {
return result.getData();
}
return result.getSuccess();
}
/**
* 获取 ACS 拒绝原因
*/
private String getMessage(AcsOperateCheckRespDTO result) {
if (StrUtil.isNotBlank(result.getMessage())) {
return result.getMessage();
}
return StrUtil.blankToDefault(result.getMsg(), "ACS 不允许执行该操作");
}
}

View File

@@ -73,6 +73,7 @@ public class TransportTaskOperationManager {
handler.accept(task, reqDTO);
} catch (Exception ex) {
log.error("[messageResend][执行异常][lockKey={}]", task.getTaskId(), ex);
throw new ServiceException(500, ex.getMessage());
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();

View File

@@ -16,6 +16,7 @@ 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.TransportTaskOperateCheckManager;
import cn.code.nl.module.task.manage.TransportTaskOperationManager;
import cn.hutool.core.collection.CollUtil;
import jakarta.annotation.Resource;
@@ -50,6 +51,9 @@ public class TransportTaskServiceImpl implements TransportTaskService {
@Resource
private TransportTaskIssueManager transportTaskIssueManager;
@Resource
private TransportTaskOperateCheckManager transportTaskOperateCheckManager;
@Override
public Long createTransportTask(TransportTaskSaveReqVO createReqVO) {
TransportTaskDO transportTask = BeanUtils.toBean(createReqVO, TransportTaskDO.class);
@@ -130,6 +134,9 @@ public class TransportTaskServiceImpl implements TransportTaskService {
|| TransportTaskStatusEnum.CANCELLED.getCode().equals(task.getTaskStatus())) {
throw exception(TRANSPORT_TASK_ALREADY_FINAL);
}
if (type == TaskOperationTypeEnum.FINISHED || type == TaskOperationTypeEnum.CANCELLED) {
transportTaskOperateCheckManager.checkEnableOperate(task, type);
}
if (type == TaskOperationTypeEnum.FINISHED) {
task.setFinishedType(FinishedTypeEnum.MANUAL.getCode());
} else if (type == TaskOperationTypeEnum.FORCE_FINISH) {