367 lines
9.9 KiB
Markdown
367 lines
9.9 KiB
Markdown
|
|
# Task 服务独立后的任务业务回调设计方案
|
|||
|
|
|
|||
|
|
## 1. 背景
|
|||
|
|
|
|||
|
|
旧版 LMS 中,任务、业务逻辑和 `xxxTask` 都在同一个服务内,`SCH_BASE_Task.handle_class` 保存 Java 类名,`TaskService` 根据该类名获取 Spring Bean 后调用 `cancel`、`forceFinish`、`updateTaskStatus`。
|
|||
|
|
|
|||
|
|
新版本中,任务模块独立为 `nl-module-task`,LMS、WMS 会调用 Task 服务创建任务;Task 服务负责下发外部系统,后续完成、取消也先进入 Task 服务。此时如果继续让 Task 服务通过 `handle_class` 调用 LMS/WMS 的业务类,会破坏微服务边界。
|
|||
|
|
|
|||
|
|
## 2. 核心结论
|
|||
|
|
|
|||
|
|
建议改为:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Task 服务负责通用任务生命周期
|
|||
|
|
LMS/WMS 负责各自业务完成和取消
|
|||
|
|
Task 不引用 lms-server / wms-server
|
|||
|
|
旧 handle_class 改为 handler_code
|
|||
|
|
Task 通过 RPC 或 MQ 通知业务服务回调
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
也就是说:
|
|||
|
|
|
|||
|
|
- Task 服务:任务创建、状态机、调度、下发、外部反馈、日志、重试、幂等;
|
|||
|
|
- LMS 服务:LMS 入库、出库、分配、点位、库存等业务完成/取消;
|
|||
|
|
- WMS 服务:WMS 库存、库位、出入库单据等业务完成/取消;
|
|||
|
|
- 跨服务不要存 Java 类名,要存稳定业务编码。
|
|||
|
|
|
|||
|
|
推荐任务记录保存:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
owner_service = LMS / WMS
|
|||
|
|
biz_type = TWO_IN / OUTBOUND / MOVE_STORE
|
|||
|
|
biz_id = 业务侧 ID
|
|||
|
|
handler_code = LMS_TWO_IN_TASK / WMS_OUTBOUND_TASK
|
|||
|
|
external_system = ACS / WCS / AGV
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 3. 不推荐的设计
|
|||
|
|
|
|||
|
|
### 3.1 不推荐把 LMS/WMS 的 `xxxTask` 放到 Task 服务
|
|||
|
|
|
|||
|
|
如果把 `TwoInTask`、`InTask`、`OutTask` 等业务类放到 Task 服务,Task 服务就会依赖 LMS/WMS 的业务表、Service、枚举和业务规则,最终变成新的业务单体。
|
|||
|
|
|
|||
|
|
### 3.2 不推荐 Task 服务引用 LMS/WMS server
|
|||
|
|
|
|||
|
|
不要设计成:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
task-server -> lms-server -> 注入 LmsTwoInTask
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Task 服务最多依赖 `lms-api`、`wms-api` 中的 Feign/RPC 接口和 DTO,不应依赖 server 实现。
|
|||
|
|
|
|||
|
|
### 3.3 不推荐继续使用 Java 类名作为 `handle_class`
|
|||
|
|
|
|||
|
|
旧值:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
org.nl.b_lms.sch.tasks.TwoInTask
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
新架构下 Task 服务本地没有这个类,即使通过依赖引入也会破坏边界。建议改为:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
handler_code = LMS_TWO_IN_TASK
|
|||
|
|
owner_service = LMS
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 4. 推荐架构
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
LMS/WMS 创建任务
|
|||
|
|
-> Task 服务保存任务
|
|||
|
|
Task 服务下发 ACS/WCS/AGV
|
|||
|
|
外部系统反馈完成/取消
|
|||
|
|
-> Task 服务维护任务状态
|
|||
|
|
-> Task 服务通知 LMS/WMS
|
|||
|
|
LMS/WMS 根据 handler_code 找本地 handler
|
|||
|
|
-> 执行业务完成/取消
|
|||
|
|
-> Task 服务更新最终状态
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
服务职责:
|
|||
|
|
|
|||
|
|
| 服务 | 职责 |
|
|||
|
|
| --- | --- |
|
|||
|
|
| Task | 任务主数据、状态机、外部系统下发、反馈接收、重试、幂等 |
|
|||
|
|
| LMS | LMS 业务完成/取消,例如入库确认、分配回滚、点位处理 |
|
|||
|
|
| WMS | WMS 业务完成/取消,例如库存、库位、单据处理 |
|
|||
|
|
|
|||
|
|
## 5. 任务表字段建议
|
|||
|
|
|
|||
|
|
| 字段 | 示例 | 说明 |
|
|||
|
|
| --- | --- | --- |
|
|||
|
|
| `id` | `10001` | Task 任务 ID |
|
|||
|
|
| `task_no` | `T202607130001` | 任务编号 |
|
|||
|
|
| `owner_service` | `LMS` | 业务归属服务 |
|
|||
|
|
| `biz_type` | `TWO_IN` | 业务任务类型 |
|
|||
|
|
| `biz_id` | `1000001` | 业务侧 ID |
|
|||
|
|
| `handler_code` | `LMS_TWO_IN_TASK` | 业务处理器编码 |
|
|||
|
|
| `external_system` | `ACS` | 外部系统 |
|
|||
|
|
| `external_task_type` | `7` | 外部任务类型 |
|
|||
|
|
| `status` | `ISSUED` | Task 状态 |
|
|||
|
|
| `callback_status` | `PENDING/SUCCESS/FAILED` | 业务回调状态 |
|
|||
|
|
| `request_payload` | JSON | 创建扩展参数 |
|
|||
|
|
| `dispatch_payload` | JSON | 下发参数 |
|
|||
|
|
| `result_payload` | JSON | 外部反馈参数 |
|
|||
|
|
|
|||
|
|
## 6. 推荐流程
|
|||
|
|
|
|||
|
|
### 6.1 创建任务
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
LMS/WMS -> Task.createTask -> Task 保存任务 -> 返回 task_id/task_no
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
创建请求示例:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"ownerService": "LMS",
|
|||
|
|
"bizType": "TWO_IN",
|
|||
|
|
"bizId": "1000001",
|
|||
|
|
"handlerCode": "LMS_TWO_IN_TASK",
|
|||
|
|
"externalSystem": "ACS",
|
|||
|
|
"externalTaskType": "7",
|
|||
|
|
"startPointCode": "IN_001",
|
|||
|
|
"endPointCode": "A01_01_01",
|
|||
|
|
"vehicleCode": "P001",
|
|||
|
|
"priority": 1,
|
|||
|
|
"requestPayload": {}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 6.2 下发任务
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Task 定时任务/手动触发
|
|||
|
|
-> 查询可下发任务
|
|||
|
|
-> 组装外部系统 DTO
|
|||
|
|
-> 调用 ACS/WCS/AGV
|
|||
|
|
-> 成功后 status = ISSUED
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
如果下发参数依赖业务数据,优先由 LMS/WMS 创建任务时传给 Task,减少 Task 反查业务服务。
|
|||
|
|
|
|||
|
|
### 6.3 完成任务
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
外部系统反馈完成
|
|||
|
|
-> Task 幂等校验
|
|||
|
|
-> status = FINISHED_PENDING_CALLBACK
|
|||
|
|
-> 通知 LMS/WMS 执行业务完成
|
|||
|
|
-> 业务成功后 status = FINISHED
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
不要外部一反馈完成就直接设为最终 `FINISHED`,因为业务确认可能失败。
|
|||
|
|
|
|||
|
|
### 6.4 取消任务
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
用户取消
|
|||
|
|
-> Task 判断是否允许取消
|
|||
|
|
-> 如已下发,先取消外部任务
|
|||
|
|
-> status = CANCEL_PENDING_CALLBACK
|
|||
|
|
-> 通知 LMS/WMS 执行业务取消回滚
|
|||
|
|
-> 业务成功后 status = CANCELLED
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 7. 业务回调方式
|
|||
|
|
|
|||
|
|
### 7.1 第一阶段推荐:RPC 同步回调
|
|||
|
|
|
|||
|
|
Task 根据 `owner_service` 调用业务服务接口:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
POST /rpc-api/lms/task-callback/handle
|
|||
|
|
POST /rpc-api/wms/task-callback/handle
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
请求示例:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"taskId": 10001,
|
|||
|
|
"taskNo": "T202607130001",
|
|||
|
|
"eventType": "FINISH",
|
|||
|
|
"bizType": "TWO_IN",
|
|||
|
|
"bizId": "1000001",
|
|||
|
|
"handlerCode": "LMS_TWO_IN_TASK",
|
|||
|
|
"payload": {}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
优点:实现简单、链路清晰、Task 能立即知道业务成功或失败。缺点是业务服务不可用时需要重试。
|
|||
|
|
|
|||
|
|
### 7.2 第二阶段演进:MQ 事件回调
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Task 发布 TaskFinishedEvent / TaskCancelledEvent
|
|||
|
|
-> LMS/WMS 消费
|
|||
|
|
-> 执行业务逻辑
|
|||
|
|
-> 回调 Task 确认处理结果
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
优点是解耦更彻底,缺点是最终一致性和排查复杂度更高。
|
|||
|
|
|
|||
|
|
## 8. LMS/WMS 内部 handler 设计
|
|||
|
|
|
|||
|
|
业务服务内部定义统一接口:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
public interface TaskBizCallbackHandler {
|
|||
|
|
String getHandlerCode();
|
|||
|
|
void onTaskFinished(TaskCallbackRequest request);
|
|||
|
|
void onTaskCancelled(TaskCallbackRequest request);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
LMS 示例:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
@Component
|
|||
|
|
public class LmsTwoInTaskCallbackHandler implements TaskBizCallbackHandler {
|
|||
|
|
@Override
|
|||
|
|
public String getHandlerCode() {
|
|||
|
|
return "LMS_TWO_IN_TASK";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@Override
|
|||
|
|
public void onTaskFinished(TaskCallbackRequest request) {
|
|||
|
|
// 入库确认、分配明细完成、库存/点位处理
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@Override
|
|||
|
|
public void onTaskCancelled(TaskCallbackRequest request) {
|
|||
|
|
// 入库取消、分配回滚、点位释放
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
业务服务内用 `handler_code` 分发到对应 handler。核心原则是:**谁拥有业务数据,谁实现业务 handler**。
|
|||
|
|
|
|||
|
|
## 9. Task 服务内部抽象
|
|||
|
|
|
|||
|
|
旧版 `AbstractAcsTask` 职责太多,新架构建议拆分:
|
|||
|
|
|
|||
|
|
### Task 服务内:外部系统下发策略
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
public interface ExternalTaskDispatcher {
|
|||
|
|
String getExternalSystem();
|
|||
|
|
DispatchResult dispatch(TaskDO task);
|
|||
|
|
CancelExternalResult cancelExternal(TaskDO task);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
实现类例如:`AcsTaskDispatcher`、`WcsTaskDispatcher`、`AgvTaskDispatcher`。
|
|||
|
|
|
|||
|
|
### LMS/WMS 内:业务回调策略
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
public interface TaskBizCallbackHandler {
|
|||
|
|
String getHandlerCode();
|
|||
|
|
void onTaskFinished(TaskCallbackRequest request);
|
|||
|
|
void onTaskCancelled(TaskCallbackRequest request);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
这样旧版一个大 `AbstractAcsTask` 被拆成两部分:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Task 服务:负责外部任务下发
|
|||
|
|
业务服务:负责完成/取消后的业务处理
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 10. 状态机建议
|
|||
|
|
|
|||
|
|
| 状态 | 含义 |
|
|||
|
|
| --- | --- |
|
|||
|
|
| `CREATED` | 已创建 |
|
|||
|
|
| `READY` | 可下发 |
|
|||
|
|
| `ISSUING` | 下发中 |
|
|||
|
|
| `ISSUED` | 已下发 |
|
|||
|
|
| `EXECUTING` | 执行中 |
|
|||
|
|
| `FINISHED_PENDING_CALLBACK` | 外部已完成,等待业务完成 |
|
|||
|
|
| `FINISHED` | 最终完成 |
|
|||
|
|
| `CANCEL_PENDING_EXTERNAL` | 等待外部系统取消 |
|
|||
|
|
| `CANCEL_PENDING_CALLBACK` | 等待业务取消回滚 |
|
|||
|
|
| `CANCELLED` | 最终取消 |
|
|||
|
|
| `FAILED` | 失败 |
|
|||
|
|
|
|||
|
|
中间状态用于区分:外部完成不等于业务完成,外部取消不等于业务回滚完成。
|
|||
|
|
|
|||
|
|
## 11. 幂等和补偿
|
|||
|
|
|
|||
|
|
- Task 接收外部反馈时,按 `task_id + external_event_id + event_type` 幂等;
|
|||
|
|
- LMS/WMS 业务回调按 `task_id + event_type` 幂等;
|
|||
|
|
- 业务服务建议记录回调日志;
|
|||
|
|
- Task 服务增加定时补偿,扫描 `FINISHED_PENDING_CALLBACK`、`CANCEL_PENDING_CALLBACK`、`ISSUING` 超时等任务。
|
|||
|
|
|
|||
|
|
## 12. 代码结构建议
|
|||
|
|
|
|||
|
|
Task API:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
nl-module-task-api
|
|||
|
|
api/TaskApi.java
|
|||
|
|
dto/TaskCreateReqDTO.java
|
|||
|
|
dto/TaskCallbackReqDTO.java
|
|||
|
|
dto/TaskCancelReqDTO.java
|
|||
|
|
enums/TaskStatusEnum.java
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Task Server:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
nl-module-task-server
|
|||
|
|
service/TaskService.java
|
|||
|
|
service/TaskDispatchService.java
|
|||
|
|
service/TaskBizCallbackNotifyService.java
|
|||
|
|
dispatcher/ExternalTaskDispatcher.java
|
|||
|
|
dispatcher/AcsTaskDispatcher.java
|
|||
|
|
job/TaskScheduleJob.java
|
|||
|
|
job/TaskCallbackRetryJob.java
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
LMS Server:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
nl-module-lms-server
|
|||
|
|
api/TaskCallbackApiImpl.java
|
|||
|
|
taskcallback/TaskBizCallbackHandler.java
|
|||
|
|
taskcallback/TaskBizCallbackDispatcher.java
|
|||
|
|
taskcallback/handler/LmsTwoInTaskCallbackHandler.java
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 13. 对问题的直接回答
|
|||
|
|
|
|||
|
|
### 抽象类子类放 Task 还是 LMS?
|
|||
|
|
|
|||
|
|
如果子类包含 LMS 入库、出库、分配、库存、点位等业务逻辑,就放 LMS。如果只负责外部系统下发,不访问业务数据,可以放 Task。
|
|||
|
|
|
|||
|
|
更推荐拆成:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Task 服务:ExternalTaskDispatcher
|
|||
|
|
LMS/WMS:TaskBizCallbackHandler
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### handler 放 LMS,Task 是否要引用 LMS?
|
|||
|
|
|
|||
|
|
Task 不引用 `lms-server`。可以依赖 `lms-api` 做 RPC,或发布 MQ 事件让 LMS 消费。
|
|||
|
|
|
|||
|
|
### 旧 handle_class 怎么办?
|
|||
|
|
|
|||
|
|
不再存 Java 类名,改为:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
owner_service = LMS
|
|||
|
|
handler_code = LMS_TWO_IN_TASK
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 14. 总结
|
|||
|
|
|
|||
|
|
Task 服务独立后,`handle_class` 不应该再表示 Java 类,也不应该让 Task 服务持有 LMS/WMS 的业务类。正确拆法是:Task 服务负责通用任务生命周期和外部系统交互;LMS/WMS 负责各自业务完成和取消;Task 通过 `owner_service + handler_code` 定位业务回调目标;业务服务内部再通过 `handler_code` 分发到本地 handler。
|