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

632 lines
15 KiB
Markdown
Raw Permalink Normal View History

2026-07-14 14:09:48 +08:00
# Task 服务创建任务与外部系统下发参数转换设计方案
## 1. 问题背景
旧版 LMS 中,类似 `StockAreaCallTubeTask` 这样的任务子类会实现:
```java
public List<AcsTaskDto> 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<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
```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<T> {
/**
* 外部系统,例如 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<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();
}
}
```
对应旧版逻辑:
```text
StockAreaCallTubeTask#addTask
-> 查询任务
-> 构建 AcsTaskDto
```
新版变为:
```text
AcsStockAreaCallTubePayloadBuilder#build
-> 根据 TaskDO 构建 AcsTaskDto
```
### 6.3 通用 ACS 构建器
如果很多 ACS 任务字段差不多,也可以提供一个通用构建器:
```java
@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。
```java
@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;
}
}
```
例如:
```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。
```