diff --git a/nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/job/dto/AcsBaseReqDTO.java b/nl-framework/nl-common/src/main/java/cn/code/nl/framework/common/pojo/AcsBaseReqDTO.java similarity index 86% rename from nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/job/dto/AcsBaseReqDTO.java rename to nl-framework/nl-common/src/main/java/cn/code/nl/framework/common/pojo/AcsBaseReqDTO.java index a920d0c8..23fff4ab 100644 --- a/nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/job/dto/AcsBaseReqDTO.java +++ b/nl-framework/nl-common/src/main/java/cn/code/nl/framework/common/pojo/AcsBaseReqDTO.java @@ -1,4 +1,4 @@ -package cn.code.nl.module.task.job.dto; +package cn.code.nl.framework.common.pojo; import lombok.Data; diff --git a/nl-framework/nl-common/src/main/java/cn/code/nl/framework/common/pojo/AcsBaseRespDTO.java b/nl-framework/nl-common/src/main/java/cn/code/nl/framework/common/pojo/AcsBaseRespDTO.java new file mode 100644 index 00000000..91c763ea --- /dev/null +++ b/nl-framework/nl-common/src/main/java/cn/code/nl/framework/common/pojo/AcsBaseRespDTO.java @@ -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; +} diff --git a/nl-framework/nl-common/src/main/java/cn/code/nl/framework/common/util/http/AcsUtil.java b/nl-framework/nl-common/src/main/java/cn/code/nl/framework/common/util/http/AcsUtil.java index 9bf9806c..2125eba0 100644 --- a/nl-framework/nl-common/src/main/java/cn/code/nl/framework/common/util/http/AcsUtil.java +++ b/nl-framework/nl-common/src/main/java/cn/code/nl/framework/common/util/http/AcsUtil.java @@ -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 post(String serverAddress, String api, Object request, Class 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; + } + } diff --git a/nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/job/dto/AcsIssueResultDTO.java b/nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/job/dto/AcsIssueResultRespDTO.java similarity index 67% rename from nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/job/dto/AcsIssueResultDTO.java rename to nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/job/dto/AcsIssueResultRespDTO.java index b8bd4422..388bce41 100644 --- a/nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/job/dto/AcsIssueResultDTO.java +++ b/nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/job/dto/AcsIssueResultRespDTO.java @@ -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 { /** * 下发失败的任务 diff --git a/nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/job/dto/AcsOperateCheckReqDTO.java b/nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/job/dto/AcsOperateCheckReqDTO.java new file mode 100644 index 00000000..876c5865 --- /dev/null +++ b/nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/job/dto/AcsOperateCheckReqDTO.java @@ -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; + +} diff --git a/nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/job/dto/AcsOperateCheckRespDTO.java b/nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/job/dto/AcsOperateCheckRespDTO.java new file mode 100644 index 00000000..ab4278bb --- /dev/null +++ b/nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/job/dto/AcsOperateCheckRespDTO.java @@ -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; + +} diff --git a/nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/job/dto/AcsTaskDTO.java b/nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/job/dto/AcsTaskDTO.java index 0c3a2153..7aa6bf79 100644 --- a/nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/job/dto/AcsTaskDTO.java +++ b/nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/job/dto/AcsTaskDTO.java @@ -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 { /** * 任务标识 */ diff --git a/nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/manage/TransportTaskIssueManager.java b/nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/manage/TransportTaskIssueManager.java index 4224dd45..e26ece3c 100644 --- a/nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/manage/TransportTaskIssueManager.java +++ b/nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/manage/TransportTaskIssueManager.java @@ -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 tasks, AcsIssueResultDTO result) { + private void handleIssueResult(List tasks, AcsIssueResultRespDTO result) { if (result == null) { tasks.forEach(task -> updateIssueFailed(task.getTaskId(), "ACS 返回为空", null)); return; } String resultJson = JsonUtils.toJsonString(result); - List failedTasks = result.getFailedTasks() == null + List 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 failedTaskMap = failedTasks.stream() + Map 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 failedTaskIds = failedTaskMap.keySet(); for (TransportTaskDO task : tasks) { if (failedTaskIds.contains(task.getTaskId())) { diff --git a/nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/manage/TransportTaskOperateCheckManager.java b/nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/manage/TransportTaskOperateCheckManager.java new file mode 100644 index 00000000..2acafce2 --- /dev/null +++ b/nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/manage/TransportTaskOperateCheckManager.java @@ -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 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 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 不允许执行该操作"); + } + +} diff --git a/nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/manage/TransportTaskOperationManager.java b/nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/manage/TransportTaskOperationManager.java index 9e9ee168..0a09e89b 100644 --- a/nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/manage/TransportTaskOperationManager.java +++ b/nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/manage/TransportTaskOperationManager.java @@ -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(); diff --git a/nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/service/transporttask/TransportTaskServiceImpl.java b/nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/service/transporttask/TransportTaskServiceImpl.java index b923ee1a..3b687f7d 100644 --- a/nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/service/transporttask/TransportTaskServiceImpl.java +++ b/nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/service/transporttask/TransportTaskServiceImpl.java @@ -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) {