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

367 lines
9.9 KiB
Markdown
Raw 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 中,任务、业务逻辑和 `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。