Files
huachuang/doc/task-service-business-callback-design.md

367 lines
9.9 KiB
Markdown
Raw Permalink Normal View History

2026-07-14 14:09:48 +08:00
# 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/WMSTaskBizCallbackHandler
```
### handler 放 LMSTask 是否要引用 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。