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

632 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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。
```