feat(task): TransportTaskService 扩展 RPC 创建方法(TDD)
- 新增 TransportTaskService.createTransportTaskByRpc() 接口方法 - 实现幂等检查(同 bizType+bizId 下未完结任务返回已有任务ID) - 参数完整时状态置为待下发(040),否则生成(010) - 添加单元测试覆盖正常创建和幂等返回两种场景 - 添加服务器模块测试依赖(JUnit 5, Mockito, AssertJ) - 将 DTO 移至正确的 Maven 子模块位置
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
package cn.code.nl.module.task.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* ACS 状态反馈请求 DTO
|
||||
*/
|
||||
@Schema(description = "ACS 状态反馈 Request DTO")
|
||||
@Data
|
||||
public class AcsFeedbackReqDTO {
|
||||
|
||||
@Schema(description = "任务ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "任务ID不能为空")
|
||||
private Long taskId;
|
||||
|
||||
@Schema(description = "ACS反馈状态:EXECUTING/PICKED/FINISHED/CANCELLED", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotEmpty(message = "反馈状态不能为空")
|
||||
private String status;
|
||||
|
||||
@Schema(description = "事件ID(幂等键)")
|
||||
private String eventId;
|
||||
|
||||
@Schema(description = "ACS反馈扩展数据")
|
||||
private Map<String, Object> payload;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package cn.code.nl.module.task.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* LMS/WMS 业务处理结果回调请求 DTO
|
||||
*/
|
||||
@Schema(description = "RPC - 业务结果回调 Request DTO")
|
||||
@Data
|
||||
public class TaskCallbackResultReqDTO {
|
||||
|
||||
@Schema(description = "任务ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "任务ID不能为空")
|
||||
private Long taskId;
|
||||
|
||||
@Schema(description = "事件ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotEmpty(message = "事件ID不能为空")
|
||||
private String eventId;
|
||||
|
||||
@Schema(description = "处理结果:SUCCESS/FAILED", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotEmpty(message = "处理结果不能为空")
|
||||
private String result;
|
||||
|
||||
@Schema(description = "结果描述")
|
||||
private String message;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package cn.code.nl.module.task.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 任务事件 MQ 消息体
|
||||
*/
|
||||
@Data
|
||||
public class TaskEventMessage {
|
||||
|
||||
/** 事件ID */
|
||||
private String eventId;
|
||||
|
||||
/** 事件类型:TASK_FINISHED / TASK_CANCELLED */
|
||||
private String eventType;
|
||||
|
||||
/** 任务ID */
|
||||
private Long taskId;
|
||||
|
||||
/** 任务编码 */
|
||||
private String taskCode;
|
||||
|
||||
/** 业务归属服务:LMS / WMS */
|
||||
private String ownerService;
|
||||
|
||||
/** 业务类型 */
|
||||
private String bizType;
|
||||
|
||||
/** 业务侧标识 */
|
||||
private String bizId;
|
||||
|
||||
/** 业务回调处理器编码 */
|
||||
private String handleCode;
|
||||
|
||||
/** 扩展数据 */
|
||||
private Map<String, Object> payload;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package cn.code.nl.module.task.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Task→LMS/WMS 同步状态回调请求 DTO
|
||||
* LMS/WMS 需依此 DTO 实现回调端点
|
||||
*/
|
||||
@Data
|
||||
public class TaskStatusCallbackReqDTO {
|
||||
|
||||
/** 任务ID */
|
||||
private Long taskId;
|
||||
|
||||
/** 任务编码 */
|
||||
private String taskCode;
|
||||
|
||||
/** 回调状态:当前为 PICKED(61) */
|
||||
private String status;
|
||||
|
||||
/** 业务归属服务 */
|
||||
private String ownerService;
|
||||
|
||||
/** 业务类型 */
|
||||
private String bizType;
|
||||
|
||||
/** 业务侧标识 */
|
||||
private String bizId;
|
||||
|
||||
/** 业务回调处理器编码(LMS/WMS 内部路由) */
|
||||
private String handleCode;
|
||||
|
||||
/** 扩展数据 */
|
||||
private Map<String, Object> payload;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package cn.code.nl.module.task.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Task→LMS/WMS 同步状态回调响应 DTO
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class TaskStatusCallbackRespDTO {
|
||||
|
||||
/** 是否成功 */
|
||||
private Boolean success;
|
||||
|
||||
/** 响应消息 */
|
||||
private String message;
|
||||
|
||||
public static TaskStatusCallbackRespDTO success() {
|
||||
return new TaskStatusCallbackRespDTO(true, "ok");
|
||||
}
|
||||
|
||||
public static TaskStatusCallbackRespDTO fail(String message) {
|
||||
return new TaskStatusCallbackRespDTO(false, message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package cn.code.nl.module.task.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* LMS/WMS 创建搬运任务请求 DTO
|
||||
*/
|
||||
@Schema(description = "RPC - 搬运任务创建 Request DTO")
|
||||
@Data
|
||||
public class TransportTaskCreateReqDTO {
|
||||
|
||||
@Schema(description = "任务名称", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotEmpty(message = "任务名称不能为空")
|
||||
private String taskName;
|
||||
|
||||
@Schema(description = "业务归属服务:LMS/WMS", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotEmpty(message = "业务归属服务不能为空")
|
||||
private String ownerService;
|
||||
|
||||
@Schema(description = "业务类型", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotEmpty(message = "业务类型不能为空")
|
||||
private String bizType;
|
||||
|
||||
@Schema(description = "业务侧标识", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotEmpty(message = "业务侧标识不能为空")
|
||||
private String bizId;
|
||||
|
||||
@Schema(description = "业务回调处理器编码", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotEmpty(message = "业务回调处理器编码不能为空")
|
||||
private String handleCode;
|
||||
|
||||
@Schema(description = "ACS任务类型", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotEmpty(message = "ACS任务类型不能为空")
|
||||
private String acsTaskType;
|
||||
|
||||
@Schema(description = "AGV系统类型", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotEmpty(message = "AGV系统类型不能为空")
|
||||
private String agvSystemType;
|
||||
|
||||
@Schema(description = "取货点1")
|
||||
private String pointCode1;
|
||||
|
||||
@Schema(description = "放货点1")
|
||||
private String pointCode2;
|
||||
|
||||
@Schema(description = "取货点2")
|
||||
private String pointCode3;
|
||||
|
||||
@Schema(description = "放货点2")
|
||||
private String pointCode4;
|
||||
|
||||
@Schema(description = "载具编码")
|
||||
private String vehicleCode;
|
||||
|
||||
@Schema(description = "载具编码2")
|
||||
private String vehicleCode2;
|
||||
|
||||
@Schema(description = "优先级")
|
||||
private String priority;
|
||||
|
||||
@Schema(description = "生产区域")
|
||||
private String productArea;
|
||||
|
||||
@Schema(description = "是否自动下发")
|
||||
private String isAutoIssue;
|
||||
|
||||
@Schema(description = "任务类型")
|
||||
private String taskType;
|
||||
|
||||
@Schema(description = "载具类型")
|
||||
private String vehicleType;
|
||||
|
||||
@Schema(description = "载具数量")
|
||||
private Long vehicleQty;
|
||||
|
||||
@Schema(description = "车号")
|
||||
private String carNo;
|
||||
|
||||
@Schema(description = "任务组标识")
|
||||
private Long taskGroupId;
|
||||
|
||||
@Schema(description = "任务组顺序号")
|
||||
private Long sortSeq;
|
||||
|
||||
@Schema(description = "扩展参数(JSON)")
|
||||
private Map<String, Object> requestParam;
|
||||
}
|
||||
@@ -115,6 +115,33 @@
|
||||
<groupId>cn.nl.cloud</groupId>
|
||||
<artifactId>nl-spring-boot-starter-monitor</artifactId>
|
||||
</dependency>
|
||||
<!-- Test 测试相关 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-inline</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -4,6 +4,7 @@ import java.util.*;
|
||||
import jakarta.validation.*;
|
||||
import cn.code.nl.module.task.controller.admin.transporttask.vo.*;
|
||||
import cn.code.nl.module.task.dal.dataobject.transporttask.TransportTaskDO;
|
||||
import cn.code.nl.module.task.dto.TransportTaskCreateReqDTO;
|
||||
import cn.code.nl.framework.common.pojo.PageResult;
|
||||
import cn.code.nl.framework.common.pojo.PageParam;
|
||||
|
||||
@@ -22,6 +23,14 @@ public interface TransportTaskService {
|
||||
*/
|
||||
Long createTransportTask(@Valid TransportTaskSaveReqVO createReqVO);
|
||||
|
||||
/**
|
||||
* 通过 RPC 创建搬运任务(LMS/WMS 调用)
|
||||
*
|
||||
* @param reqDTO 创建请求
|
||||
* @return 任务ID
|
||||
*/
|
||||
Long createTransportTaskByRpc(@Valid TransportTaskCreateReqDTO reqDTO);
|
||||
|
||||
/**
|
||||
* 更新搬运任务
|
||||
*
|
||||
|
||||
@@ -6,6 +6,10 @@ import cn.code.nl.module.task.controller.admin.transporttask.vo.TransportTaskPag
|
||||
import cn.code.nl.module.task.controller.admin.transporttask.vo.TransportTaskSaveReqVO;
|
||||
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.dto.TransportTaskCreateReqDTO;
|
||||
import cn.code.nl.module.task.enums.CallbackStatusEnum;
|
||||
import cn.code.nl.module.task.enums.TransportTaskStatusEnum;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
@@ -37,6 +41,31 @@ public class TransportTaskServiceImpl implements TransportTaskService {
|
||||
return transportTask.getTaskId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long createTransportTaskByRpc(TransportTaskCreateReqDTO reqDTO) {
|
||||
// 1. 幂等检查:同 bizType + bizId 下是否有未完结任务
|
||||
TransportTaskDO existing = transportTaskMapper.selectUnfinishedByBiz(
|
||||
reqDTO.getBizType(), reqDTO.getBizId());
|
||||
if (existing != null) {
|
||||
return existing.getTaskId();
|
||||
}
|
||||
|
||||
// 2. 构建 DO 并保存
|
||||
TransportTaskDO task = BeanUtils.toBean(reqDTO, TransportTaskDO.class);
|
||||
// 参数完整 → 待下发(040),否则 → 生成(010)
|
||||
boolean paramComplete = StrUtil.isNotBlank(reqDTO.getPointCode1())
|
||||
&& StrUtil.isNotBlank(reqDTO.getPointCode2());
|
||||
task.setTaskStatus(paramComplete
|
||||
? String.format("%03d", TransportTaskStatusEnum.READY.getCode())
|
||||
: String.format("%03d", TransportTaskStatusEnum.CREATED.getCode()));
|
||||
task.setCallbackStatus(CallbackStatusEnum.PENDING.getCode());
|
||||
task.setCallbackRetryCount(0);
|
||||
task.setCreateMode("RPC");
|
||||
|
||||
transportTaskMapper.insert(task);
|
||||
return task.getTaskId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTransportTask(TransportTaskSaveReqVO updateReqVO) {
|
||||
// 校验存在
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
package cn.code.nl.module.task.service.transporttask;
|
||||
|
||||
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.dto.TransportTaskCreateReqDTO;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class TransportTaskServiceImplTest {
|
||||
|
||||
@Mock
|
||||
private TransportTaskMapper transportTaskMapper;
|
||||
|
||||
@InjectMocks
|
||||
private TransportTaskServiceImpl transportTaskService;
|
||||
|
||||
@Test
|
||||
void testCreateTransportTaskByRpc_shouldReturnTaskId() {
|
||||
// given
|
||||
TransportTaskCreateReqDTO req = new TransportTaskCreateReqDTO();
|
||||
req.setTaskName("测试任务");
|
||||
req.setOwnerService("LMS");
|
||||
req.setBizType("INBOUND");
|
||||
req.setBizId("INB-001");
|
||||
req.setHandleCode("inboundHandler");
|
||||
req.setAcsTaskType("MOVE");
|
||||
req.setAgvSystemType("AGV_TYPE_A");
|
||||
|
||||
when(transportTaskMapper.selectUnfinishedByBiz("INBOUND", "INB-001")).thenReturn(null);
|
||||
doAnswer(inv -> {
|
||||
inv.getArgument(0, TransportTaskDO.class).setTaskId(1L);
|
||||
return 1;
|
||||
}).when(transportTaskMapper).insert((TransportTaskDO) any());
|
||||
|
||||
// when
|
||||
Long taskId = transportTaskService.createTransportTaskByRpc(req);
|
||||
|
||||
// then
|
||||
assertNotNull(taskId);
|
||||
verify(transportTaskMapper, times(1)).insert((TransportTaskDO) any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateTransportTaskByRpc_shouldReturnExistingWhenDuplicated() {
|
||||
// given
|
||||
TransportTaskCreateReqDTO req = new TransportTaskCreateReqDTO();
|
||||
req.setTaskName("测试任务");
|
||||
req.setOwnerService("LMS");
|
||||
req.setBizType("INBOUND");
|
||||
req.setBizId("INB-001");
|
||||
req.setHandleCode("inboundHandler");
|
||||
req.setAcsTaskType("MOVE");
|
||||
req.setAgvSystemType("AGV_TYPE_A");
|
||||
|
||||
TransportTaskDO existing = TransportTaskDO.builder().taskId(99L).taskStatus("040").build();
|
||||
when(transportTaskMapper.selectUnfinishedByBiz("INBOUND", "INB-001")).thenReturn(existing);
|
||||
|
||||
// when
|
||||
Long taskId = transportTaskService.createTransportTaskByRpc(req);
|
||||
|
||||
// then
|
||||
assertNotNull(taskId);
|
||||
org.assertj.core.api.Assertions.assertThat(taskId).isEqualTo(99L);
|
||||
verify(transportTaskMapper, never()).insert((TransportTaskDO) any());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user