feat:acs交互接口定义

This commit is contained in:
2026-07-30 10:15:05 +08:00
parent 0f29c81596
commit c89590f76f
10 changed files with 243 additions and 77 deletions

View File

@@ -0,0 +1,53 @@
package cn.code.nl.framework.common.pojo;
import cn.code.nl.framework.common.exception.enums.GlobalErrorCodeConstants;
import lombok.Data;
import java.util.Objects;
/**
* acs 固定返回对象
* @Author: liyongde
* @Date: 2026/7/30 9:05
*/
@Data
public class AcsCommonResult<T> {
/**
* 总条数
*/
private Long totalElements;
/**
* 业务数据体
*/
private T data;
/**
* 时间戳
*/
private String timestamp;
/**
* http状态码
*/
private Integer code;
/**
* 消息
*/
private String message;
/**
* 业务响应码 todo: 暂时不用
*/
private Integer respCode;
/**
* 业务响应信息 todo: 暂时不用
*/
private String respMsg;
public static boolean isSuccess(Integer code) {
return Objects.equals(code, GlobalErrorCodeConstants.SUCCESS.getCode()) || Objects.equals(code, 200);
}
}

View File

@@ -1,17 +1,22 @@
package cn.code.nl.framework.execute.util.http;
import cn.code.nl.framework.common.exception.ServiceException;
import cn.code.nl.framework.common.pojo.AcsBaseRespDTO;
import cn.code.nl.framework.common.exception.enums.GlobalErrorCodeConstants;
import cn.code.nl.framework.common.pojo.AcsCommonResult;
import cn.code.nl.framework.common.util.http.HttpUtils;
import cn.code.nl.framework.common.util.json.JsonUtils;
import cn.code.nl.framework.common.util.spring.SpringUtils;
import cn.code.nl.module.infra.api.config.ConfigApi;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.extern.slf4j.Slf4j;
import java.net.*;
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;
@@ -32,29 +37,32 @@ public class AcsUtil {
private static final String ACS_DISABLED = "0";
/**
* 发送 POST 请求并解析响应
* 发送 POST 请求并解析 ACS 通用响应
*
* @param serverAddress ACS 服务地址
* @param api API 路径
* @param request 请求参数
* @param responseType 响应类型
* @return 响应对象
* @param api API 路径
* @param request 请求参数
* @param responseType data 响应类型
* @return ACS 通用响应
*/
public static <T> T post(String serverAddress, String api, Object request, Class<T> responseType) {
public static <T> AcsCommonResult<T> post(String serverAddress, String api, Object request, Class<T> responseType) {
if (isAcsDisabled()) {
return buildDefaultSuccessResponse(responseType);
return buildDefaultSuccessResponse();
}
String url = buildUrl(serverAddress, api);
String response;
try {
response = HttpUtils.post(url, headers(), JsonUtils.toJsonString(request));
log.info("ACS 返回值:{}", JSON.toJSONString(response));
} catch (RuntimeException ex) {
if (isNetworkException(ex)) {
throw new ServiceException(500, "ACS服务网络不通");
log.error("ACS 服务网络不通url={}request={}", url, JsonUtils.toJsonString(request), ex);
return buildErrorResponse("ACS服务网络不通");
}
throw ex;
log.error("ACS 请求失败url={}request={}", url, JsonUtils.toJsonString(request), ex);
return buildErrorResponse(ex.getMessage());
}
return JsonUtils.parseObject(response, responseType);
return parseResponse(response, responseType);
}
/**
@@ -82,13 +90,40 @@ public class AcsUtil {
/**
* 构建默认成功响应
*/
private static <T> T buildDefaultSuccessResponse(Class<T> responseType) {
AcsBaseRespDTO response = new AcsBaseRespDTO();
response.setSuccess(true);
response.setCode("200");
response.setTraceId(IdUtil.simpleUUID());
response.setTimestamp(System.currentTimeMillis());
return JsonUtils.convertObject(response, responseType);
private static <T> AcsCommonResult<T> buildDefaultSuccessResponse() {
AcsCommonResult<T> response = new AcsCommonResult<>();
response.setCode(GlobalErrorCodeConstants.SUCCESS.getCode());
response.setMessage("ACS 已关闭,跳过请求");
response.setTimestamp(String.valueOf(System.currentTimeMillis()));
return response;
}
/**
* 构建失败响应,具体是否抛异常由业务层决定
*/
private static <T> AcsCommonResult<T> buildErrorResponse(String message) {
AcsCommonResult<T> response = new AcsCommonResult<>();
response.setCode(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR.getCode());
response.setMessage(StrUtil.blankToDefault(message, "ACS 请求失败"));
response.setTimestamp(String.valueOf(System.currentTimeMillis()));
return response;
}
/**
* 解析 ACS 响应,并将 data 转为业务指定类型
*/
private static <T> AcsCommonResult<T> parseResponse(String response, Class<T> responseType) {
AcsCommonResult<Object> rawResult = JsonUtils.parseObject(response, new TypeReference<AcsCommonResult<Object>>() {
});
AcsCommonResult<T> result = new AcsCommonResult<>();
result.setTotalElements(rawResult.getTotalElements());
result.setData(JsonUtils.convertObject(rawResult.getData(), responseType));
result.setTimestamp(rawResult.getTimestamp());
result.setCode(rawResult.getCode());
result.setMessage(rawResult.getMessage());
result.setRespCode(rawResult.getRespCode());
result.setRespMsg(rawResult.getRespMsg());
return result;
}
/**

View File

@@ -8,9 +8,9 @@ package cn.code.nl.module.task.enums;
public interface AcsApiConstants {
/** 下发任务 */
String ACS_TASK_API = "/acs-api/wms/issue-task";
String ACS_TASK_API = "/acs-api/wms-to-acs/issue-task";
/** 检测任务 */
String ACS_OPERATE_CHECK_API = "/acs-api/wms/check-enable-operate";
String ACS_OPERATE_CHECK_API = "/acs-api/wms-to-acs/check-enable-operate";
}

View File

@@ -1,15 +1,14 @@
package cn.code.nl.module.task.job.dto;
import cn.code.nl.framework.common.pojo.AcsBaseRespDTO;
import lombok.Data;
import java.util.List;
/**
* ACS 任务下发响应
* ACS 任务下发 data 响应
*/
@Data
public class AcsIssueResultRespDTO extends AcsBaseRespDTO {
public class AcsIssueResultRespDTO {
/**
* 下发失败的任务

View File

@@ -1,12 +1,13 @@
package cn.code.nl.module.task.job.dto;
import cn.code.nl.framework.common.pojo.AcsBaseReqDTO;
import lombok.Data;
/**
* ACS 任务操作校验请求
*/
@Data
public class AcsOperateCheckReqDTO {
public class AcsOperateCheckReqDTO extends AcsBaseReqDTO {
/**
* 任务标识

View File

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

View File

@@ -1,6 +1,7 @@
package cn.code.nl.module.task.manage;
import cn.code.nl.framework.common.exception.ServiceException;
import cn.code.nl.framework.common.pojo.AcsCommonResult;
import cn.code.nl.framework.common.pojo.CommonResult;
import cn.code.nl.framework.common.util.json.JsonUtils;
import cn.code.nl.framework.execute.util.http.AcsUtil;
@@ -74,7 +75,8 @@ public class TransportTaskIssueManager {
String requestJson = JsonUtils.toJsonString(acsTasks);
try {
String serverAddress = getAcsServerAddress(task.getProductArea());
AcsIssueResultRespDTO result = AcsUtil.post(serverAddress, ACS_TASK_API, acsTasks, AcsIssueResultRespDTO.class);
AcsCommonResult<AcsIssueResultRespDTO> result = AcsUtil.post(serverAddress, ACS_TASK_API, acsTasks,
AcsIssueResultRespDTO.class);
handleSingleIssueResult(task, result);
} catch (ServiceException ex) {
throw ex;
@@ -95,7 +97,8 @@ public class TransportTaskIssueManager {
String requestJson = JsonUtils.toJsonString(acsTasks);
try {
String serverAddress = getAcsServerAddress(productArea);
AcsIssueResultRespDTO result = AcsUtil.post(serverAddress, ACS_TASK_API, acsTasks, AcsIssueResultRespDTO.class);
AcsCommonResult<AcsIssueResultRespDTO> result = AcsUtil.post(serverAddress, ACS_TASK_API, acsTasks,
AcsIssueResultRespDTO.class);
handleIssueResult(tasks, result);
} catch (Exception ex) {
log.error("自动下发任务失败productArea={}tasks={}", productArea, requestJson, ex);
@@ -125,20 +128,18 @@ public class TransportTaskIssueManager {
* @param tasks 本次下发任务
* @param result ACS 响应
*/
private void handleIssueResult(List<TransportTaskDO> tasks, AcsIssueResultRespDTO result) {
private void handleIssueResult(List<TransportTaskDO> tasks, AcsCommonResult<AcsIssueResultRespDTO> result) {
if (result == null) {
tasks.forEach(task -> updateIssueFailed(task.getTaskId(), "ACS 返回为空", null));
return;
}
String resultJson = JsonUtils.toJsonString(result);
List<AcsIssueResultRespDTO.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 下发失败");
if (!AcsCommonResult.isSuccess(result.getCode())) {
String errorMessage = StrUtil.blankToDefault(result.getMessage(), "ACS 下发失败");
tasks.forEach(task -> updateIssueFailed(task.getTaskId(), errorMessage, resultJson));
return;
}
List<AcsIssueResultRespDTO.FailedTask> failedTasks = getFailedTasks(result);
Map<Long, AcsIssueResultRespDTO.FailedTask> failedTaskMap = failedTasks.stream()
.filter(failedTask -> failedTask.getTaskId() != null)
.collect(Collectors.toMap(AcsIssueResultRespDTO.FailedTask::getTaskId, Function.identity(), (first, second) -> first));
@@ -158,21 +159,34 @@ public class TransportTaskIssueManager {
* @param task 本次下发任务
* @param result ACS 响应
*/
private void handleSingleIssueResult(TransportTaskDO task, AcsIssueResultRespDTO result) {
private void handleSingleIssueResult(TransportTaskDO task, AcsCommonResult<AcsIssueResultRespDTO> result) {
if (result == null) {
throw exception(TRANSPORT_TASK_ISSUE_FAILED, "ACS 返回为空");
}
String resultJson = JsonUtils.toJsonString(result);
if (Boolean.TRUE.equals(result.getSuccess())) {
if (!AcsCommonResult.isSuccess(result.getCode())) {
throw exception(TRANSPORT_TASK_ISSUE_FAILED, StrUtil.blankToDefault(result.getMessage(), "ACS 下发失败"));
}
List<AcsIssueResultRespDTO.FailedTask> failedTasks = getFailedTasks(result);
if (CollUtil.isEmpty(failedTasks)) {
transportTaskMapper.updateIssueResult(task.getTaskId(), TransportTaskStatusEnum.ISSUED.getCode(), resultJson, null);
return;
}
String errorMessage = CollUtil.isNotEmpty(result.getFailedTasks())
? result.getFailedTasks().get(0).getErrorMessage()
: result.getMsg();
String errorMessage = failedTasks.get(0).getErrorMessage();
throw exception(TRANSPORT_TASK_ISSUE_FAILED, StrUtil.blankToDefault(errorMessage, "ACS 下发失败"));
}
/**
* 获取下发失败任务
*/
private List<AcsIssueResultRespDTO.FailedTask> getFailedTasks(AcsCommonResult<AcsIssueResultRespDTO> result) {
AcsIssueResultRespDTO data = result.getData();
if (data == null || data.getFailedTasks() == null) {
return Collections.emptyList();
}
return data.getFailedTasks();
}
/**
* 更新任务为下发失败
*

View File

@@ -1,6 +1,7 @@
package cn.code.nl.module.task.manage;
import cn.code.nl.framework.common.exception.ServiceException;
import cn.code.nl.framework.common.pojo.AcsCommonResult;
import cn.code.nl.framework.common.pojo.CommonResult;
import cn.code.nl.framework.common.util.json.JsonUtils;
import cn.code.nl.framework.execute.util.http.AcsUtil;
@@ -48,7 +49,7 @@ public class TransportTaskOperateCheckManager {
}
String serverAddress = getAcsServerAddress(task.getProductArea());
AcsOperateCheckReqDTO reqDTO = buildReqDTO(task, type);
AcsOperateCheckRespDTO result;
AcsCommonResult<AcsOperateCheckRespDTO> result;
try {
result = AcsUtil.post(serverAddress, ACS_OPERATE_CHECK_API, reqDTO, AcsOperateCheckRespDTO.class);
} catch (ServiceException ex) {
@@ -103,15 +104,23 @@ public class TransportTaskOperateCheckManager {
/**
* 处理 ACS 操作校验结果
*/
private void handleCheckResult(AcsOperateCheckReqDTO reqDTO, AcsOperateCheckRespDTO result) {
private void handleCheckResult(AcsOperateCheckReqDTO reqDTO, AcsCommonResult<AcsOperateCheckRespDTO> result) {
if (result == null) {
throw exception(TRANSPORT_TASK_ACS_OPERATE_CHECK_RESULT_EMPTY);
}
Boolean enableOperate = getEnableOperate(result);
if (!AcsCommonResult.isSuccess(result.getCode())) {
log.warn("ACS 操作校验失败reqDTO={}result={}", JsonUtils.toJsonString(reqDTO), JsonUtils.toJsonString(result));
throw exception(TRANSPORT_TASK_ACS_OPERATE_CHECK_FAILED);
}
AcsOperateCheckRespDTO data = result.getData();
if (data == null) {
throw exception(TRANSPORT_TASK_ACS_OPERATE_CHECK_RESULT_EMPTY);
}
Boolean enableOperate = data.getEnableOperate();
if (Boolean.TRUE.equals(enableOperate)) {
return;
}
String message = getMessage(result);
String message = getMessage(result, data);
log.warn("ACS 拒绝 PC 端任务操作reqDTO={}result={}",
JsonUtils.toJsonString(reqDTO), JsonUtils.toJsonString(result));
throw exception(TRANSPORT_TASK_ACS_OPERATE_NOT_ALLOW, message);
@@ -120,27 +129,11 @@ public class TransportTaskOperateCheckManager {
/**
* 获取 ACS 是否允许操作
*/
private Boolean getEnableOperate(AcsOperateCheckRespDTO result) {
if (result.getEnableOperate() != null) {
return result.getEnableOperate();
private String getMessage(AcsCommonResult<AcsOperateCheckRespDTO> result, AcsOperateCheckRespDTO data) {
if (StrUtil.isNotBlank(data.getMessage())) {
return data.getMessage();
}
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 不允许执行该操作");
return StrUtil.blankToDefault(result.getMessage(), "ACS 不允许执行该操作");
}
}

View File

@@ -199,6 +199,12 @@
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>

View File

@@ -0,0 +1,76 @@
package cn.code.nl.server.acs;
import cn.code.nl.framework.common.pojo.AcsCommonResult;
import cn.code.nl.framework.common.pojo.CommonResult;
import cn.code.nl.framework.common.util.spring.SpringUtils;
import cn.code.nl.framework.execute.util.http.AcsUtil;
import cn.code.nl.module.infra.api.config.ConfigApi;
import cn.code.nl.module.task.job.dto.AcsTaskDTO;
import lombok.Data;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import java.util.List;
import java.util.Map;
import static cn.code.nl.module.task.enums.AcsApiConstants.ACS_TASK_API;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@SpringJUnitConfig
@ContextConfiguration(classes = AcsUtilLocalSpringTest.TestConfiguration.class)
class AcsUtilLocalSpringTest {
private static final String SERVER_ADDRESS = "http://127.0.0.1:8011";
@Test
void shouldPostIssueTaskToLocalAcs() {
AcsCommonResult<AcsIssueTaskResp> result = AcsUtil.post(SERVER_ADDRESS, ACS_TASK_API,
List.of(buildAcsTask()), AcsIssueTaskResp.class);
assertNotNull(result);
}
private AcsTaskDTO buildAcsTask() {
AcsTaskDTO task = new AcsTaskDTO();
task.setTaskId(10001L);
task.setTaskCode("LOCAL-ACS-TEST-10001");
task.setStartDeviceCode("START-TEST-01");
task.setNextDeviceCode("END-TEST-01");
task.setPriority("1");
task.setVehicleCode("BOX-TEST-01");
task.setTaskType("TRANSFER");
task.setAgvSystemType("ACS");
task.setProductArea("TEST");
task.setRemark("local acs issue task test");
task.setPayload(Map.of("source", "AcsUtilLocalSpringTest"));
return task;
}
@Data
static class AcsIssueTaskResp {
private List<FailedTask> failedTasks;
}
@Data
static class FailedTask {
private Long taskId;
private String errorMessage;
}
@Configuration(proxyBeanMethods = false)
static class TestConfiguration {
@Bean
SpringUtils springUtils() {
return new SpringUtils();
}
@Bean
ConfigApi configApi() {
return key -> CommonResult.success("1");
}
}
}