Files
huachuang/doc/task-create-and-dispatch-payload-design.md

15 KiB
Raw Blame History

Task 服务创建任务与外部系统下发参数转换设计方案

1. 问题背景

旧版 LMS 中,类似 StockAreaCallTubeTask 这样的任务子类会实现:

public List<AcsTaskDto> addTask()

旧版 addTask 的核心职责是:

查询 SCH_BASE_Task
  -> 读取任务字段、request_param、point_code、vehicle_code 等
  -> 组装 AcsTaskDto
  -> 下发 ACS

也就是说,在旧架构中:

业务任务创建、任务表保存、外部系统 DTO 组装、ACS 下发

都在 LMS 内的具体 xxxTask 子类中完成。

新架构中,Task 服务独立出来:

LMS/WMS
  -> 调用 Task 服务创建任务
Task 服务
  -> 保存任务
  -> 下发外部系统

此时会遇到一个关键问题:

LMS/WMS 创建任务时,传给 Task 服务的是业务任务信息,不一定已经是 AcsTaskDto。但 Task 服务下发 ACS 时需要 AcsTaskDto 或类似外部系统 DTO。这个转换逻辑应该放在哪里

如果让 LMS/WMS 直接传 AcsTaskDto,会有两个问题:

  1. AcsTaskDto 是外部系统下发模型,不适合作为业务创建任务模型;
  2. Task 服务还需要保存标准任务表,不能只保存一个外部 DTO。

因此需要重新设计“创建任务模型”和“下发模型”的边界。

2. 核心结论

建议将旧版 addTask 拆成两部分:

LMS/WMS负责构建业务任务创建请求 TaskCreateReqDTO
Task 服务:负责保存标准任务数据,并在下发前转换成外部系统 DTO

也就是说:

业务服务不要直接传 AcsTaskDto
Task 服务不要依赖 LMS/WMS 业务类
双方通过标准 TaskCreateReqDTO 交互
Task 服务内部通过 ExternalTaskPayloadBuilder 构建 AcsTaskDto

推荐架构:

LMS/WMS
  -> TaskCreateReqDTO 标准创建请求
Task 服务
  -> 保存 task_task 标准字段
  -> 保存 request_payload 扩展字段
  -> 根据 external_system + external_task_type + dispatch_template_code 选择构建器
  -> 转换为 AcsTaskDto / WcsTaskDto / StackerTaskDto
  -> 下发外部系统

3. 新旧职责对照

职责 旧版 新版
业务校验 xxxTask#createTask LMS/WMS 业务服务
任务保存 LMS SCH_BASE_Task Task 服务 task_task
业务回调定位 handle_class owner_service + handler_code
外部 DTO 组装 xxxTask#addTask Task 服务 ExternalTaskPayloadBuilder
外部系统下发 AbstractAcsTask#notifyAcs Task 服务 ExternalTaskDispatcher
完成/取消业务逻辑 xxxTask#updateTaskStatus LMS/WMS TaskBizCallbackHandler

4. 为什么不建议 LMS/WMS 直接传 AcsTaskDto

不建议让 LMS/WMS 创建任务时直接传 AcsTaskDto,原因如下:

4.1 业务创建模型和外部下发模型不是一回事

业务创建任务时关心的是:

我要搬什么
从哪里搬
搬到哪里
归属哪个业务
完成后回调哪个 handler
业务扩展参数是什么

外部系统下发时关心的是:

ACS task_type
start_device_code
next_device_code
vehicle_code
agv_system_type
route_plan_code
interaction_json

两者不应该强耦合。

4.2 Task 服务需要标准化保存任务

Task 服务需要保存:

task_id
task_no
owner_service
biz_type
biz_id
handler_code
external_system
status
start_point_code
end_point_code
vehicle_code
request_payload

如果只收到 AcsTaskDtoTask 服务会丢失业务归属、回调 handler、业务 ID 等关键字段。

4.3 外部系统 DTO 可能变化

未来可能不止 ACS

ACS
WCS
AGV
STACKER
PLC

如果业务服务直接构建外部 DTO会导致业务服务感知所有外部系统协议不利于统一下发和协议升级。

5. 推荐数据模型

5.1 TaskCreateReqDTO

LMS/WMS 调用 Task 服务创建任务时,传标准创建请求。

@Data
public class TaskCreateReqDTO {
    private String ownerService;
    private String bizType;
    private String bizId;
    private String handlerCode;

    private String externalSystem;
    private String externalTaskType;
    private String dispatchTemplateCode;

    private String startPointCode;
    private String endPointCode;
    private String startPointCode2;
    private String endPointCode2;

    private String vehicleCode;
    private String vehicleCode2;
    private Integer priority;
    private String productArea;

    private Map<String, Object> requestPayload;
}

字段说明:

字段 说明
ownerService LMS/WMS决定完成取消回调发给谁
bizType 业务类型,例如 STOCK_AREA_CALL_TUBE
bizId 业务侧 ID
handlerCode 业务回调 handler 编码
externalSystem 外部系统,例如 ACS/WCS/STACKER
externalTaskType 外部系统任务类型
dispatchTemplateCode 下发参数构建模板编码
startPointCode 起点
endPointCode 终点
vehicleCode 载具、架子、托盘等
requestPayload 扩展参数

5.2 TaskDO 保存字段

Task 服务保存标准字段和扩展 JSON

id
task_no
owner_service
biz_type
biz_id
handler_code
external_system
external_task_type
dispatch_template_code
start_point_code
end_point_code
start_point_code2
end_point_code2
vehicle_code
vehicle_code2
priority
product_area
status
request_payload
dispatch_payload
result_payload

6. 下发参数转换设计

6.1 定义统一构建器接口

Task 服务内部定义:

public interface ExternalTaskPayloadBuilder<T> {

    /**
     * 外部系统,例如 ACS/WCS/STACKER
     */
    String getExternalSystem();

    /**
     * 下发模板编码,例如 STOCK_AREA_CALL_TUBE
     */
    String getDispatchTemplateCode();

    /**
     * 根据 TaskDO 构建外部系统 DTO
     */
    T build(TaskDO task);
}

6.2 ACS 纸管搬运构建器

旧版 StockAreaCallTubeTask#addTask 逻辑迁移到 Task 服务的构建器中。

@Component
public class AcsStockAreaCallTubePayloadBuilder implements ExternalTaskPayloadBuilder<AcsTaskDto> {

    @Override
    public String getExternalSystem() {
        return "ACS";
    }

    @Override
    public String getDispatchTemplateCode() {
        return "STOCK_AREA_CALL_TUBE";
    }

    @Override
    public AcsTaskDto build(TaskDO task) {
        Map<String, Object> payload = task.getRequestPayloadMap();

        return AcsTaskDto.builder()
                .extTaskId(String.valueOf(task.getId()))
                .taskCode(task.getTaskNo())
                .taskType(task.getExternalTaskType())
                .startDeviceCode(task.getStartPointCode())
                .nextDeviceCode(task.getEndPointCode())
                .startDeviceCode2(task.getStartPointCode2())
                .nextDeviceCode2(task.getEndPointCode2())
                .vehicleCode(task.getVehicleCode())
                .interactionJson(payload)
                .agvSystemType("2")
                .priority(String.valueOf(task.getPriority()))
                .productArea(task.getProductArea())
                .build();
    }
}

对应旧版逻辑:

StockAreaCallTubeTask#addTask
  -> 查询任务
  -> 构建 AcsTaskDto

新版变为:

AcsStockAreaCallTubePayloadBuilder#build
  -> 根据 TaskDO 构建 AcsTaskDto

6.3 通用 ACS 构建器

如果很多 ACS 任务字段差不多,也可以提供一个通用构建器:

@Component
public class AcsDefaultPayloadBuilder implements ExternalTaskPayloadBuilder<AcsTaskDto> {

    @Override
    public String getExternalSystem() {
        return "ACS";
    }

    @Override
    public String getDispatchTemplateCode() {
        return "DEFAULT";
    }

    @Override
    public AcsTaskDto build(TaskDO task) {
        return AcsTaskDto.builder()
                .extTaskId(String.valueOf(task.getId()))
                .taskCode(task.getTaskNo())
                .taskType(task.getExternalTaskType())
                .startDeviceCode(task.getStartPointCode())
                .nextDeviceCode(task.getEndPointCode())
                .vehicleCode(task.getVehicleCode())
                .priority(String.valueOf(task.getPriority()))
                .productArea(task.getProductArea())
                .interactionJson(task.getRequestPayloadMap())
                .build();
    }
}

特殊任务再单独实现专用 Builder。

7. Builder 注册表

Task 服务启动时收集所有 Builder。

@Component
public class ExternalTaskPayloadBuilderRegistry {

    private final Map<String, ExternalTaskPayloadBuilder<?>> builderMap = new HashMap<>();

    public ExternalTaskPayloadBuilderRegistry(List<ExternalTaskPayloadBuilder<?>> builders) {
        for (ExternalTaskPayloadBuilder<?> builder : builders) {
            String key = buildKey(builder.getExternalSystem(), builder.getDispatchTemplateCode());
            if (builderMap.containsKey(key)) {
                throw new IllegalStateException("重复的下发参数构建器:" + key);
            }
            builderMap.put(key, builder);
        }
    }

    public ExternalTaskPayloadBuilder<?> getRequiredBuilder(String externalSystem, String dispatchTemplateCode) {
        String key = buildKey(externalSystem, dispatchTemplateCode);
        ExternalTaskPayloadBuilder<?> builder = builderMap.get(key);
        if (builder == null) {
            throw new IllegalArgumentException("未找到下发参数构建器:" + key);
        }
        return builder;
    }

    private String buildKey(String externalSystem, String dispatchTemplateCode) {
        return externalSystem + ":" + dispatchTemplateCode;
    }
}

例如:

ACS:DEFAULT -> AcsDefaultPayloadBuilder
ACS:STOCK_AREA_CALL_TUBE -> AcsStockAreaCallTubePayloadBuilder
STACKER:INBOUND -> StackerInboundPayloadBuilder

8. 外部系统下发器设计

Builder 只负责构建 DTO下发器负责调用外部系统。

public interface ExternalTaskDispatcher {
    String getExternalSystem();
    DispatchResult dispatch(TaskDO task, Object payload);
    CancelExternalResult cancelExternal(TaskDO task);
}

ACS 下发器示例:

@Component
public class AcsTaskDispatcher implements ExternalTaskDispatcher {

    @Override
    public String getExternalSystem() {
        return "ACS";
    }

    @Override
    public DispatchResult dispatch(TaskDO task, Object payload) {
        AcsTaskDto acsTask = (AcsTaskDto) payload;
        // 调用 ACS HTTP 接口
        // acsClient.createTask(Collections.singletonList(acsTask));
        return DispatchResult.success();
    }

    @Override
    public CancelExternalResult cancelExternal(TaskDO task) {
        // 调用 ACS 取消接口
        return CancelExternalResult.success();
    }
}

9. Task 下发流程

Task 服务下发任务时:

1. 查询 READY 状态任务
2. 根据 external_system + dispatch_template_code 找 Builder
3. Builder 根据 TaskDO 构建 AcsTaskDto/WcsTaskDto
4. 保存 dispatch_payload
5. 根据 external_system 找 Dispatcher
6. Dispatcher 调用外部系统
7. 成功后状态改为 ISSUED

伪代码:

public void dispatchTask(Long taskId) {
    TaskDO task = taskMapper.selectById(taskId);

    ExternalTaskPayloadBuilder<?> builder = builderRegistry.getRequiredBuilder(
            task.getExternalSystem(),
            task.getDispatchTemplateCode()
    );

    Object externalPayload = builder.build(task);
    task.setDispatchPayload(JsonUtils.toJsonString(externalPayload));
    task.setStatus(TaskStatus.ISSUING);
    taskMapper.updateById(task);

    ExternalTaskDispatcher dispatcher = dispatcherRegistry.getRequiredDispatcher(task.getExternalSystem());
    DispatchResult result = dispatcher.dispatch(task, externalPayload);

    if (result.isSuccess()) {
        task.setStatus(TaskStatus.ISSUED);
    } else {
        task.setStatus(TaskStatus.FAILED);
        task.setErrorMsg(result.getMessage());
    }
    taskMapper.updateById(task);
}

10. StockAreaCallTubeTask 新版完整链路

10.1 LMS 创建任务

LMS 不传 AcsTaskDto,而是传标准 TaskCreateReqDTO

{
  "ownerService": "LMS",
  "bizType": "STOCK_AREA_CALL_TUBE",
  "bizId": "LMS-BIZ-10001",
  "handlerCode": "LMS_STOCK_AREA_CALL_TUBE",
  "externalSystem": "ACS",
  "externalTaskType": "3",
  "dispatchTemplateCode": "STOCK_AREA_CALL_TUBE",
  "startPointCode": "STOCK_A01",
  "endPointCode": "ROBOT_STOCK_A01",
  "startPointCode2": "",
  "endPointCode2": "",
  "vehicleCode": "TUBE_CAR_001",
  "priority": 1,
  "productArea": "BLK",
  "requestPayload": {
    "to_material": "M001,M002"
  }
}

10.2 Task 保存任务

Task 保存为标准任务记录:

owner_service = LMS
handler_code = LMS_STOCK_AREA_CALL_TUBE
external_system = ACS
dispatch_template_code = STOCK_AREA_CALL_TUBE
request_payload = {to_material: ...}

10.3 Task 下发 ACS

Task 根据:

external_system = ACS
dispatch_template_code = STOCK_AREA_CALL_TUBE

找到:

AcsStockAreaCallTubePayloadBuilder

构建:

AcsTaskDto

再由:

AcsTaskDispatcher

下发 ACS。

10.4 ACS 完成后业务回调

ACS 完成后:

Task 接收反馈
  -> 发布 TASK_FINISHED
  -> owner_service = LMS
  -> handler_code = LMS_STOCK_AREA_CALL_TUBE
  -> LMS 消费
  -> StockAreaCallTubeTaskHandler#onTaskFinished

11. 设计重点

11.1 TaskCreateReqDTO 不是 AcsTaskDto

TaskCreateReqDTO 是任务服务标准创建模型。

AcsTaskDto 是外部系统 ACS 的下发模型。

二者不要混用。

11.2 addTask 职责迁移到 Builder

旧版:

xxxTask#addTask -> AcsTaskDto

新版:

ExternalTaskPayloadBuilder#build -> AcsTaskDto

11.3 特殊字段放 request_payload

如果某个任务需要特殊参数,例如:

interaction_json
route_plan_code
agv_system_type
paper_array
truss_type

可以通过两种方式处理:

  1. 常用字段提升为 TaskDO 标准字段;
  2. 特殊字段放入 request_payload,由专用 Builder 读取。

11.4 dispatch_template_code 决定怎么转 DTO

同样是 ACS不同任务可能 DTO 组装方式不同,所以需要:

external_system + dispatch_template_code

联合定位 Builder。

12. 最终建议

不要让 LMS/WMS 直接封装 AcsTaskDto

推荐方式是:

LMS/WMS
  -> TaskCreateReqDTO
Task 服务
  -> 保存标准任务字段 + request_payload
  -> ExternalTaskPayloadBuilder 转换外部 DTO
  -> ExternalTaskDispatcher 下发外部系统

这样既能让 Task 服务保存标准任务数据,又能保留旧版 addTask 对不同任务差异化组装外部 DTO 的能力。

一句话总结:

旧 addTask 不放 LMS/WMS也不让业务服务直接传 AcsTaskDto
旧 addTask 的 DTO 转换职责迁移到 Task 服务中的 ExternalTaskPayloadBuilder。