632 lines
15 KiB
Markdown
632 lines
15 KiB
Markdown
# 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。
|
||
```
|