# Task 服务创建任务与外部系统下发参数转换设计方案 ## 1. 问题背景 旧版 LMS 中,类似 `StockAreaCallTubeTask` 这样的任务子类会实现: ```java public List addTask() ``` 旧版 `addTask` 的核心职责是: ```text 查询 SCH_BASE_Task -> 读取任务字段、request_param、point_code、vehicle_code 等 -> 组装 AcsTaskDto -> 下发 ACS ``` 也就是说,在旧架构中: ```text 业务任务创建、任务表保存、外部系统 DTO 组装、ACS 下发 ``` 都在 LMS 内的具体 `xxxTask` 子类中完成。 新架构中,`Task` 服务独立出来: ```text LMS/WMS -> 调用 Task 服务创建任务 Task 服务 -> 保存任务 -> 下发外部系统 ``` 此时会遇到一个关键问题: > LMS/WMS 创建任务时,传给 Task 服务的是业务任务信息,不一定已经是 `AcsTaskDto`。但 Task 服务下发 ACS 时需要 `AcsTaskDto` 或类似外部系统 DTO。这个转换逻辑应该放在哪里? 如果让 LMS/WMS 直接传 `AcsTaskDto`,会有两个问题: 1. `AcsTaskDto` 是外部系统下发模型,不适合作为业务创建任务模型; 2. Task 服务还需要保存标准任务表,不能只保存一个外部 DTO。 因此需要重新设计“创建任务模型”和“下发模型”的边界。 ## 2. 核心结论 建议将旧版 `addTask` 拆成两部分: ```text LMS/WMS:负责构建业务任务创建请求 TaskCreateReqDTO Task 服务:负责保存标准任务数据,并在下发前转换成外部系统 DTO ``` 也就是说: ```text 业务服务不要直接传 AcsTaskDto Task 服务不要依赖 LMS/WMS 业务类 双方通过标准 TaskCreateReqDTO 交互 Task 服务内部通过 ExternalTaskPayloadBuilder 构建 AcsTaskDto ``` 推荐架构: ```text 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 业务创建模型和外部下发模型不是一回事 业务创建任务时关心的是: ```text 我要搬什么 从哪里搬 搬到哪里 归属哪个业务 完成后回调哪个 handler 业务扩展参数是什么 ``` 外部系统下发时关心的是: ```text ACS task_type start_device_code next_device_code vehicle_code agv_system_type route_plan_code interaction_json ``` 两者不应该强耦合。 ### 4.2 Task 服务需要标准化保存任务 Task 服务需要保存: ```text 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 ``` 如果只收到 `AcsTaskDto`,Task 服务会丢失业务归属、回调 handler、业务 ID 等关键字段。 ### 4.3 外部系统 DTO 可能变化 未来可能不止 ACS: ```text ACS WCS AGV STACKER PLC ``` 如果业务服务直接构建外部 DTO,会导致业务服务感知所有外部系统协议,不利于统一下发和协议升级。 ## 5. 推荐数据模型 ### 5.1 TaskCreateReqDTO LMS/WMS 调用 Task 服务创建任务时,传标准创建请求。 ```java @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 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: ```text 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 服务内部定义: ```java public interface ExternalTaskPayloadBuilder { /** * 外部系统,例如 ACS/WCS/STACKER */ String getExternalSystem(); /** * 下发模板编码,例如 STOCK_AREA_CALL_TUBE */ String getDispatchTemplateCode(); /** * 根据 TaskDO 构建外部系统 DTO */ T build(TaskDO task); } ``` ### 6.2 ACS 纸管搬运构建器 旧版 `StockAreaCallTubeTask#addTask` 逻辑迁移到 Task 服务的构建器中。 ```java @Component public class AcsStockAreaCallTubePayloadBuilder implements ExternalTaskPayloadBuilder { @Override public String getExternalSystem() { return "ACS"; } @Override public String getDispatchTemplateCode() { return "STOCK_AREA_CALL_TUBE"; } @Override public AcsTaskDto build(TaskDO task) { Map 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(); } } ``` 对应旧版逻辑: ```text StockAreaCallTubeTask#addTask -> 查询任务 -> 构建 AcsTaskDto ``` 新版变为: ```text AcsStockAreaCallTubePayloadBuilder#build -> 根据 TaskDO 构建 AcsTaskDto ``` ### 6.3 通用 ACS 构建器 如果很多 ACS 任务字段差不多,也可以提供一个通用构建器: ```java @Component public class AcsDefaultPayloadBuilder implements ExternalTaskPayloadBuilder { @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。 ```java @Component public class ExternalTaskPayloadBuilderRegistry { private final Map> builderMap = new HashMap<>(); public ExternalTaskPayloadBuilderRegistry(List> 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; } } ``` 例如: ```text ACS:DEFAULT -> AcsDefaultPayloadBuilder ACS:STOCK_AREA_CALL_TUBE -> AcsStockAreaCallTubePayloadBuilder STACKER:INBOUND -> StackerInboundPayloadBuilder ``` ## 8. 外部系统下发器设计 Builder 只负责构建 DTO,下发器负责调用外部系统。 ```java public interface ExternalTaskDispatcher { String getExternalSystem(); DispatchResult dispatch(TaskDO task, Object payload); CancelExternalResult cancelExternal(TaskDO task); } ``` ACS 下发器示例: ```java @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 服务下发任务时: ```text 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 ``` 伪代码: ```java 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`: ```json { "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 保存为标准任务记录: ```text 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 根据: ```text external_system = ACS dispatch_template_code = STOCK_AREA_CALL_TUBE ``` 找到: ```text AcsStockAreaCallTubePayloadBuilder ``` 构建: ```text AcsTaskDto ``` 再由: ```text AcsTaskDispatcher ``` 下发 ACS。 ### 10.4 ACS 完成后业务回调 ACS 完成后: ```text 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 旧版: ```text xxxTask#addTask -> AcsTaskDto ``` 新版: ```text ExternalTaskPayloadBuilder#build -> AcsTaskDto ``` ### 11.3 特殊字段放 request_payload 如果某个任务需要特殊参数,例如: ```text 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 组装方式不同,所以需要: ```text external_system + dispatch_template_code ``` 联合定位 Builder。 ## 12. 最终建议 不要让 LMS/WMS 直接封装 `AcsTaskDto`。 推荐方式是: ```text LMS/WMS -> TaskCreateReqDTO Task 服务 -> 保存标准任务字段 + request_payload -> ExternalTaskPayloadBuilder 转换外部 DTO -> ExternalTaskDispatcher 下发外部系统 ``` 这样既能让 Task 服务保存标准任务数据,又能保留旧版 `addTask` 对不同任务差异化组装外部 DTO 的能力。 一句话总结: ```text 旧 addTask 不放 LMS/WMS,也不让业务服务直接传 AcsTaskDto; 旧 addTask 的 DTO 转换职责迁移到 Task 服务中的 ExternalTaskPayloadBuilder。 ```