9.9 KiB
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. 核心结论
建议改为:
Task 服务负责通用任务生命周期
LMS/WMS 负责各自业务完成和取消
Task 不引用 lms-server / wms-server
旧 handle_class 改为 handler_code
Task 通过 RPC 或 MQ 通知业务服务回调
也就是说:
- Task 服务:任务创建、状态机、调度、下发、外部反馈、日志、重试、幂等;
- LMS 服务:LMS 入库、出库、分配、点位、库存等业务完成/取消;
- WMS 服务:WMS 库存、库位、出入库单据等业务完成/取消;
- 跨服务不要存 Java 类名,要存稳定业务编码。
推荐任务记录保存:
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
不要设计成:
task-server -> lms-server -> 注入 LmsTwoInTask
Task 服务最多依赖 lms-api、wms-api 中的 Feign/RPC 接口和 DTO,不应依赖 server 实现。
3.3 不推荐继续使用 Java 类名作为 handle_class
旧值:
org.nl.b_lms.sch.tasks.TwoInTask
新架构下 Task 服务本地没有这个类,即使通过依赖引入也会破坏边界。建议改为:
handler_code = LMS_TWO_IN_TASK
owner_service = LMS
4. 推荐架构
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 创建任务
LMS/WMS -> Task.createTask -> Task 保存任务 -> 返回 task_id/task_no
创建请求示例:
{
"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 下发任务
Task 定时任务/手动触发
-> 查询可下发任务
-> 组装外部系统 DTO
-> 调用 ACS/WCS/AGV
-> 成功后 status = ISSUED
如果下发参数依赖业务数据,优先由 LMS/WMS 创建任务时传给 Task,减少 Task 反查业务服务。
6.3 完成任务
外部系统反馈完成
-> Task 幂等校验
-> status = FINISHED_PENDING_CALLBACK
-> 通知 LMS/WMS 执行业务完成
-> 业务成功后 status = FINISHED
不要外部一反馈完成就直接设为最终 FINISHED,因为业务确认可能失败。
6.4 取消任务
用户取消
-> Task 判断是否允许取消
-> 如已下发,先取消外部任务
-> status = CANCEL_PENDING_CALLBACK
-> 通知 LMS/WMS 执行业务取消回滚
-> 业务成功后 status = CANCELLED
7. 业务回调方式
7.1 第一阶段推荐:RPC 同步回调
Task 根据 owner_service 调用业务服务接口:
POST /rpc-api/lms/task-callback/handle
POST /rpc-api/wms/task-callback/handle
请求示例:
{
"taskId": 10001,
"taskNo": "T202607130001",
"eventType": "FINISH",
"bizType": "TWO_IN",
"bizId": "1000001",
"handlerCode": "LMS_TWO_IN_TASK",
"payload": {}
}
优点:实现简单、链路清晰、Task 能立即知道业务成功或失败。缺点是业务服务不可用时需要重试。
7.2 第二阶段演进:MQ 事件回调
Task 发布 TaskFinishedEvent / TaskCancelledEvent
-> LMS/WMS 消费
-> 执行业务逻辑
-> 回调 Task 确认处理结果
优点是解耦更彻底,缺点是最终一致性和排查复杂度更高。
8. LMS/WMS 内部 handler 设计
业务服务内部定义统一接口:
public interface TaskBizCallbackHandler {
String getHandlerCode();
void onTaskFinished(TaskCallbackRequest request);
void onTaskCancelled(TaskCallbackRequest request);
}
LMS 示例:
@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 服务内:外部系统下发策略
public interface ExternalTaskDispatcher {
String getExternalSystem();
DispatchResult dispatch(TaskDO task);
CancelExternalResult cancelExternal(TaskDO task);
}
实现类例如:AcsTaskDispatcher、WcsTaskDispatcher、AgvTaskDispatcher。
LMS/WMS 内:业务回调策略
public interface TaskBizCallbackHandler {
String getHandlerCode();
void onTaskFinished(TaskCallbackRequest request);
void onTaskCancelled(TaskCallbackRequest request);
}
这样旧版一个大 AbstractAcsTask 被拆成两部分:
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:
nl-module-task-api
api/TaskApi.java
dto/TaskCreateReqDTO.java
dto/TaskCallbackReqDTO.java
dto/TaskCancelReqDTO.java
enums/TaskStatusEnum.java
Task Server:
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:
nl-module-lms-server
api/TaskCallbackApiImpl.java
taskcallback/TaskBizCallbackHandler.java
taskcallback/TaskBizCallbackDispatcher.java
taskcallback/handler/LmsTwoInTaskCallbackHandler.java
13. 对问题的直接回答
抽象类子类放 Task 还是 LMS?
如果子类包含 LMS 入库、出库、分配、库存、点位等业务逻辑,就放 LMS。如果只负责外部系统下发,不访问业务数据,可以放 Task。
更推荐拆成:
Task 服务:ExternalTaskDispatcher
LMS/WMS:TaskBizCallbackHandler
handler 放 LMS,Task 是否要引用 LMS?
Task 不引用 lms-server。可以依赖 lms-api 做 RPC,或发布 MQ 事件让 LMS 消费。
旧 handle_class 怎么办?
不再存 Java 类名,改为:
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。