fix:任务创建/修改状态

This commit is contained in:
2026-07-15 08:59:54 +08:00
parent 254797a154
commit 63994ef588
24 changed files with 707 additions and 50 deletions

View File

@@ -10,6 +10,7 @@
3. **测试先于实现** — 写代码前先写测试TDD 3. **测试先于实现** — 写代码前先写测试TDD
4. **验证先于完成** — 声称完成前必须运行验证命令 4. **验证先于完成** — 声称完成前必须运行验证命令
5. **代码开发规则** — 遵守 `.claude/skills/nl-java-style/SKILL.md` 技能的规范要求 5. **代码开发规则** — 遵守 `.claude/skills/nl-java-style/SKILL.md` 技能的规范要求
6. 不需要频繁提交代码,除非用户说允许测试,否则优先不做测试
## 可用 Skills ## 可用 Skills

View File

@@ -0,0 +1,2 @@
旧版本的代码可以参照D:\Code\nl\lanzhouhailiang_one\lms\nladmin-system
里面与task相关的是本次开发任务模块的旧版本

View File

@@ -0,0 +1,588 @@
# Task 服务技术开发文档
## 1. 服务定位
`Task` 服务基于 `task_transport_job` 表,负责搬运任务的通用生命周期管理和 ACS 交互。
整体链路:
```text
LMS/WMS 创建搬运任务
-> Task 保存任务
-> Task 自动或手动下发 ACS
-> ACS 反馈执行中、取货、完成、取消
-> Task 更新任务状态
-> Task 通过 MQ 通知 LMS/WMS 执行业务完成/取消
-> LMS/WMS 处理后回传结果
-> Task 更新最终完成/取消状态
```
Task 服务不处理 LMS/WMS 的具体业务数据,例如生产计划、备货区点位、库存、入库单、出库单。业务逻辑由 LMS/WMS 内部 handler 处理。
## 2. Task 服务除 CRUD 外的核心业务
Task 服务核心能力包括:
```text
1. 接收 LMS/WMS 创建任务
2. 保存任务标准数据
3. 定时扫描并自动下发任务
4. 将 task_transport_job 转换为 AcsTaskDto
5. 调用 ACS 下发任务
6. 接收 ACS 状态反馈
7. 维护任务状态机
8. 发布完成/取消 MQ 给 LMS/WMS
9. 接收 LMS/WMS 业务处理结果
10. 处理下发失败、回调失败、超时补偿
11. 幂等和并发控制
12. 任务过程日志记录
```
## 3. 表字段和核心职责对应关系
### 3.1 业务路由字段
| 字段 | 作用 |
| --- | --- |
| `owner_service` | 决定 MQ 发送给 LMS 还是 WMS |
| `biz_type` | 业务类型,用于查询、统计、防重复创建 |
| `biz_id` | 业务侧记录 IDLMS/WMS 回调时定位业务数据 |
| `handle_code` | LMS/WMS 内部 handler 编码 |
完成/取消时路由:
```text
owner_service -> MQ Tag 或目标服务
handle_code -> LMS/WMS 内部 handlerMap
```
### 3.2 ACS 下发字段
| 字段 | 对应 AcsTaskDto |
| --- | --- |
| `task_id` | `taskId` |
| `task_code` | `taskCode` |
| `acs_task_type` | `taskType` |
| `point_code1` | `startDeviceCode` |
| `point_code2` | `nextDeviceCode` |
| `point_code3` | `startDeviceCode2` |
| `point_code4` | `nextDeviceCode2` |
| `vehicle_code` | `vehicleCode` |
| `vehicle_code2` | `vehicleCode2` |
| `priority` | `priority` |
| `product_area` | `productArea` |
| `agv_system_type` | `agvSystemType` |
| `request_param` | `interactionJson` |
### 3.3 参数快照字段
| 字段 | 说明 |
| --- | --- |
| `request_param` | LMS/WMS 创建任务扩展参数,同时作为 `interactionJson` 来源 |
| `dispatch_param` | Task 下发 ACS 的 `AcsTaskDto` 报文快照 |
| `result_param` | ACS 反馈报文 |
### 3.4 回调字段
| 字段 | 说明 |
| --- | --- |
| `callback_status` | 业务回调状态PENDING/SUCCESS/FAILED |
| `callback_retry_count` | 业务回调重试次数 |
| `callback_error_msg` | 业务回调失败原因 |
## 4. 任务状态设计
推荐状态码:
| 状态码 | 状态名称 | 修改阶段 |
| --- | --- | --- |
| `10` | 生成 | 创建任务但条件不完整 |
| `40` | 待下发 | 起点终点确认,允许下发 |
| `45` | 下发中 | Task 准备调用 ACS 前 |
| `50` | 已下发 | ACS 接收任务成功 |
| `60` | 执行中 | ACS 反馈执行中 |
| `61` | 已取货搬运中 | ACS 反馈取货完成,部分任务有 |
| `67` | 外部完成待业务处理 | ACS 完成,等待 LMS/WMS 业务完成 |
| `68` | 外部取消中 | Task 正在调用 ACS 取消 |
| `69` | 外部取消待业务处理 | ACS 取消成功,等待 LMS/WMS 回滚 |
| `70` | 完成 | LMS/WMS 业务完成成功 |
| `80` | 取消 | LMS/WMS 业务取消成功 |
| `90` | 失败 | 下发失败、取消失败、不可恢复异常 |
常用查询:
```sql
-- 外部未完成任务,用于防止重复创建
SELECT *
FROM task_transport_job
WHERE deleted = b'0'
AND biz_type = ?
AND task_status < '067';
```
```sql
-- 未最终结束任务
SELECT *
FROM task_transport_job
WHERE deleted = b'0'
AND task_status < '070';
```
```sql
-- 业务回调失败任务
SELECT *
FROM task_transport_job
WHERE deleted = b'0'
AND task_status IN ('067', '069')
AND callback_status = 'FAILED';
```
## 5. 创建任务
### 5.1 接口
```text
POST /rpc-api/task/transport/create
```
### 5.2 创建请求核心字段
```java
public class TransportTaskCreateReqDTO {
private String taskName;
private String ownerService;
private String bizType;
private String bizId;
private String handleCode;
private String taskType;
private String acsTaskType;
private String agvSystemType;
private String pointCode1;
private String pointCode2;
private String pointCode3;
private String pointCode4;
private String vehicleCode;
private String vehicleCode2;
private String priority;
private String productArea;
private String isAutoIssue;
private Map<String, Object> requestParam;
}
```
### 5.3 创建逻辑
```text
1. 校验 owner_service、handle_code、acs_task_type 等必要字段
2. 生成 task_id、task_code
3. 保存业务归属字段 owner_service、biz_type、biz_id、handle_code
4. 保存 ACS 下发字段 point_code、vehicle_code、acs_task_type 等
5. 保存 request_param
6. 参数完整则 task_status = 040否则 task_status = 010
```
## 6. 自动下发任务
### 6.1 定时器
推荐定时任务:
```text
autoIssueTransportTaskJob
```
扫描条件:
```sql
SELECT *
FROM task_transport_job
WHERE deleted = b'0'
AND is_auto_issue = '1'
AND task_status = '040'
ORDER BY priority DESC, create_time ASC
LIMIT ?;
```
### 6.2 抢占任务
为避免并发重复下发,先做状态抢占:
```sql
UPDATE task_transport_job
SET task_status = '045', update_time = NOW()
WHERE task_id = ?
AND task_status = '040';
```
影响行数为 1 才继续下发。
### 6.3 下发流程
```text
1. 查询 040 待下发任务
2. 抢占为 045 下发中
3. 转换成 AcsTaskDto
4. 保存 dispatch_param
5. 调用 ACS 下发接口
6. 成功task_status = 050保存 external_task_no
7. 失败task_status = 090 或恢复 040记录错误
```
## 7. AcsTaskDto 转换
### 7.1 DTO
```java
public class AcsTaskDto {
private String taskId;
private String taskCode;
private String taskType;
private String startDeviceCode;
private String nextDeviceCode;
private String startDeviceCode2;
private String nextDeviceCode2;
private String vehicleCode;
private String vehicleCode2;
private String priority;
private String productArea;
private String agvSystemType;
private Map<String, Object> interactionJson;
}
```
### 7.2 转换器
```java
@Component
public class AcsTaskDtoConverter {
public AcsTaskDto convert(TaskTransportJobDO task) {
AcsTaskDto dto = new AcsTaskDto();
dto.setTaskId(String.valueOf(task.getTaskId()));
dto.setTaskCode(task.getTaskCode());
dto.setTaskType(task.getAcsTaskType());
dto.setStartDeviceCode(task.getPointCode1());
dto.setNextDeviceCode(task.getPointCode2());
dto.setStartDeviceCode2(task.getPointCode3());
dto.setNextDeviceCode2(task.getPointCode4());
dto.setVehicleCode(task.getVehicleCode());
dto.setVehicleCode2(task.getVehicleCode2());
dto.setPriority(task.getPriority());
dto.setProductArea(task.getProductArea());
dto.setAgvSystemType(task.getAgvSystemType());
dto.setInteractionJson(JsonUtils.parseMap(task.getRequestParam()));
return dto;
}
}
```
## 8. ACS 下发服务
### 8.1 ACS Client
```java
public interface AcsClient {
AcsIssueResult issueTask(AcsTaskDto taskDto);
AcsCancelResult cancelTask(String externalTaskNo, Long taskId);
}
```
### 8.2 下发服务职责
```text
TransportTaskIssueService.issue(taskId)
-> 查询任务
-> 抢占状态 040 -> 045
-> AcsTaskDtoConverter.convert
-> 保存 dispatch_param
-> acsClient.issueTask
-> 成功 045 -> 050
-> 失败记录错误
```
## 9. ACS 反馈接收
### 9.1 接口
```text
POST /api/task/transport/acs-feedback
```
### 9.2 反馈 DTO
```java
public class AcsFeedbackReqDTO {
private Long taskId;
private String taskCode;
private String externalTaskNo;
private String status;
private String eventId;
private Map<String, Object> payload;
}
```
### 9.3 状态处理
| ACS 反馈 | Task 更新 |
| --- | --- |
| 执行中 | `task_status = 060` |
| 已取货 | `task_status = 061` |
| 完成 | `task_status = 067`, `callback_status = PENDING` |
| 取消成功 | `task_status = 069`, `callback_status = PENDING` |
所有反馈均保存到:
```text
result_param
```
完成和取消反馈后,需要发布 MQ 给 LMS/WMS。
## 10. 完成/取消 MQ 事件
### 10.1 Topic 设计
```text
Topic = TASK_EVENT_TOPIC
Tag = owner_service
```
示例:
```text
TASK_EVENT_TOPIC:LMS
TASK_EVENT_TOPIC:WMS
```
### 10.2 消息结构
```java
public class TaskEventMessage {
private String eventId;
private String eventType; // TASK_FINISHED / TASK_CANCELLED
private Long taskId;
private String taskCode;
private String ownerService;
private String bizType;
private String bizId;
private String handleCode;
private Map<String, Object> payload;
}
```
### 10.3 发布逻辑
```text
1. 查询 task_transport_job
2. 构造 TaskEventMessage
3. topic = TASK_EVENT_TOPIC
4. tag = owner_service
5. 发送 MQ
6. 发送失败callback_status = FAILED记录 callback_error_msg
```
## 11. 接收业务回调结果
LMS/WMS 消费 MQ 后,内部通过 `handle_code` 找 handler 执行业务逻辑。处理完成后回调 Task。
接口:
```text
POST /rpc-api/task/transport/callback-result
```
DTO
```java
public class TaskCallbackResultReqDTO {
private Long taskId;
private String eventId;
private String eventType;
private String result; // SUCCESS / FAILED
private String message;
}
```
状态更新:
| 场景 | Task 更新 |
| --- | --- |
| 完成业务成功 | `task_status = 070`, `callback_status = SUCCESS` |
| 完成业务失败 | `task_status = 067`, `callback_status = FAILED`, `callback_retry_count + 1` |
| 取消业务成功 | `task_status = 080`, `callback_status = SUCCESS` |
| 取消业务失败 | `task_status = 069`, `callback_status = FAILED`, `callback_retry_count + 1` |
## 12. 人工完成和取消
### 12.1 人工完成
```text
PC 点击完成
-> Task 校验任务
-> task_status = 067
-> callback_status = PENDING
-> 发布 TASK_FINISHED MQ
-> 页面提示:完成请求已提交,业务处理中
```
### 12.2 人工取消
未下发任务:
```text
010/040 -> 069 -> 发布 TASK_CANCELLED MQ
```
已下发任务:
```text
050/060/061 -> 068 -> 调 ACS 取消 -> 069 -> 发布 TASK_CANCELLED MQ
```
## 13. 补偿和重试
### 13.1 回调失败重试
扫描:
```sql
SELECT *
FROM task_transport_job
WHERE deleted = b'0'
AND task_status IN ('067', '069')
AND callback_status = 'FAILED'
AND callback_retry_count < ?;
```
处理:
```text
重新发布 TASK_FINISHED 或 TASK_CANCELLED
callback_retry_count + 1
```
### 13.2 PENDING 超时补偿
扫描:
```text
task_status IN ('067', '069')
callback_status = PENDING
update_time 超过阈值
```
处理:
```text
重新发布 MQ 或标记 FAILED
```
### 13.3 下发中超时补偿
扫描:
```text
task_status = 045
update_time 超过阈值
```
处理:
```text
查询 ACS 是否已收到任务;无法确认则恢复 040 或标记 090
```
## 14. 幂等控制
### 14.1 创建幂等
防重复创建示例:
```sql
SELECT *
FROM task_transport_job
WHERE deleted = b'0'
AND owner_service = ?
AND biz_type = ?
AND biz_id = ?
AND task_status < '067';
```
### 14.2 下发幂等
通过 `040 -> 045` 条件更新抢占。
### 14.3 ACS 反馈幂等
建议引入反馈日志表记录 `eventId`。第一版至少根据状态判断:
```text
已是 070不重复处理完成
已是 067不重复发布完成 MQ
```
### 14.4 业务回调结果幂等
如果已是最终态:
```text
task_status = 070 或 080
```
再次收到成功回调时直接返回成功。
## 15. 推荐模块结构
```text
nl-module-task-server
controller
TransportTaskController
AcsFeedbackController
api
TransportTaskApiImpl
service
TransportTaskService
TransportTaskIssueService
TransportTaskFeedbackService
TransportTaskCallbackService
TransportTaskCancelService
TransportTaskRetryService
converter
AcsTaskDtoConverter
mq
TaskEventProducer
job
AutoIssueTransportTaskJob
CallbackRetryJob
dal
dataobject/TaskTransportJobDO
mapper/TaskTransportJobMapper
enums
TransportTaskStatusEnum
CallbackStatusEnum
TaskEventTypeEnum
```
## 16. 总结
Task 服务的核心不是简单 CRUD而是搬运任务生命周期中台。
核心职责:
```text
接收任务 -> 保存任务 -> 自动下发 -> 转换 AcsTaskDto -> 调 ACS -> 接收反馈 -> 发布业务事件 -> 接收业务结果 -> 最终完成/取消 -> 补偿重试
```
关键路由:
```text
owner_service 决定事件给 LMS/WMS
handle_code 决定业务服务内部哪个 handler 处理
```
关键状态:
```text
task_status 表示任务流转到哪一步
callback_status 表示 LMS/WMS 业务回调是否成功
```

View File

@@ -793,7 +793,7 @@ git commit -m "feat: 新增 TransportTaskStatusClient 同步 HTTP 回调客户
```java ```java
package cn.code.nl.module.task.mq; package cn.code.nl.module.task.mq;
import cn.code.nl.module.task.dto.TaskEventMessage; import cn.code.nl.module.task.mq.message.TaskEventMessage;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.core.RocketMQTemplate; import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -889,7 +889,7 @@ import cn.code.nl.module.task.client.TransportTaskStatusClient;
import cn.code.nl.module.task.dal.dataobject.transporttask.TransportTaskDO; import cn.code.nl.module.task.dal.dataobject.transporttask.TransportTaskDO;
import cn.code.nl.module.task.dal.mysql.transporttask.TransportTaskMapper; import cn.code.nl.module.task.dal.mysql.transporttask.TransportTaskMapper;
import cn.code.nl.module.task.dto.AcsFeedbackReqDTO; import cn.code.nl.module.task.dto.AcsFeedbackReqDTO;
import cn.code.nl.module.task.mq.TaskEventProducer; import cn.code.nl.module.task.mq.producer.TaskEventProducer;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
@@ -977,13 +977,13 @@ import cn.code.nl.module.task.client.TransportTaskStatusClient;
import cn.code.nl.module.task.dal.dataobject.transporttask.TransportTaskDO; import cn.code.nl.module.task.dal.dataobject.transporttask.TransportTaskDO;
import cn.code.nl.module.task.dal.mysql.transporttask.TransportTaskMapper; import cn.code.nl.module.task.dal.mysql.transporttask.TransportTaskMapper;
import cn.code.nl.module.task.dto.AcsFeedbackReqDTO; import cn.code.nl.module.task.dto.AcsFeedbackReqDTO;
import cn.code.nl.module.task.dto.TaskEventMessage; import cn.code.nl.module.task.mq.message.TaskEventMessage;
import cn.code.nl.module.task.dto.TaskStatusCallbackReqDTO; import cn.code.nl.module.task.dto.TaskStatusCallbackReqDTO;
import cn.code.nl.module.task.dto.TaskStatusCallbackRespDTO; import cn.code.nl.module.task.dto.TaskStatusCallbackRespDTO;
import cn.code.nl.module.task.enums.CallbackStatusEnum; import cn.code.nl.module.task.enums.CallbackStatusEnum;
import cn.code.nl.module.task.enums.TaskEventTypeEnum; import cn.code.nl.module.task.enums.TaskEventTypeEnum;
import cn.code.nl.module.task.enums.TransportTaskStatusEnum; import cn.code.nl.module.task.enums.TransportTaskStatusEnum;
import cn.code.nl.module.task.mq.TaskEventProducer; import cn.code.nl.module.task.mq.producer.TaskEventProducer;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;

View File

@@ -36,7 +36,6 @@
<dependency> <dependency>
<groupId>org.apache.rocketmq</groupId> <groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId> <artifactId>rocketmq-spring-boot-starter</artifactId>
<optional>true</optional>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@@ -1,5 +1,6 @@
/** /**
* * 外部引用任务的task接口
* @Author: liyongde * @Author: liyongde
* @Date: 2026/7/14 18:57 * @Date: 2026/7/14 18:57
*/package cn.code.nl.module.task.api; */
package cn.code.nl.module.task.api;

View File

@@ -68,6 +68,9 @@ public class TransportTaskCreateReqDTO {
@Schema(description = "是否自动下发") @Schema(description = "是否自动下发")
private String isAutoIssue; private String isAutoIssue;
@Schema(description = "是否创建完成")
private Boolean isCreateFinish;
@Schema(description = "任务类型") @Schema(description = "任务类型")
private String taskType; private String taskType;
@@ -87,5 +90,5 @@ public class TransportTaskCreateReqDTO {
private Long sortSeq; private Long sortSeq;
@Schema(description = "扩展参数JSON") @Schema(description = "扩展参数JSON")
private Map<String, Object> requestParam; private String requestParam;
} }

View File

@@ -8,8 +8,10 @@ import cn.code.nl.framework.common.exception.ErrorCode;
* @Date: 2026/7/14 13:03 * @Date: 2026/7/14 13:03
*/ */
public interface ErrorCodeConstants { public interface ErrorCodeConstants {
ErrorCode TRANSPORT_TASK_NOT_EXISTS = new ErrorCode(1, "搬运任务不存在"); ErrorCode TRANSPORT_TASK_NOT_EXISTS = new ErrorCode(5001, "搬运任务不存在");
ErrorCode TRANSPORT_TASK_EXISTS_UNFINISHED = new ErrorCode(2, "同业务类型下存在未完结的搬运任务"); ErrorCode TRANSPORT_TASK_EXISTS_UNFINISHED = new ErrorCode(5002, "同业务类型下存在未完结的搬运任务");
ErrorCode TRANSPORT_TASK_STATUS_NOT_ALLOW = new ErrorCode(3, "当前任务状态不允许此操作"); ErrorCode TRANSPORT_TASK_STATUS_NOT_ALLOW = new ErrorCode(5003, "当前任务状态不允许此操作");
ErrorCode TRANSPORT_TASK_CALLBACK_SEND_FAILED = new ErrorCode(4, "业务回调发送失败"); ErrorCode TRANSPORT_TASK_CALLBACK_SEND_FAILED = new ErrorCode(5004, "业务回调发送失败");
ErrorCode TRANSPORT_TASK_RUNNING_ALREADY_EXIST = new ErrorCode(5005, "已存在运行中的任务");
} }

View File

@@ -1,8 +1,44 @@
package cn.code.nl.module.task.enums; package cn.code.nl.module.task.enums;
import lombok.Getter;
/** /**
* * 任务状态的枚举
* @Author: liyongde * @Author: liyongde
* @Date: 2026/7/14 16:14 * @Date: 2026/7/14 16:14
*/public enum TransportTaskStatusEnum { */
@Getter
public enum TransportTaskStatusEnum {
CREATED(10, "生成"),
READY(40, "待下发-起点终点确认"),
ISSUING(45, "下发中"),
ISSUED(50, "已下发"),
EXECUTING(60, "执行中"),
PICKED(61, "已取货搬运中"),
FINISHED_CALLBACK_PENDING(67, "外部完成待业务处理"),
CANCEL_EXTERNAL_PENDING(68, "外部取消中"),
CANCEL_CALLBACK_PENDING(69, "外部取消待业务处理"),
FINISHED(70, "完成"),
CANCELLED(80, "取消"),
FAILED(90, "失败");
private final Integer code;
private final String name;
TransportTaskStatusEnum(Integer code, String name) {
this.code = code;
this.name = name;
}
} }

View File

@@ -1,4 +1,4 @@
package cn.code.nl.module.task.controller; package cn.code.nl.module.task.controller.admin.transporttask;
import cn.code.nl.framework.common.pojo.CommonResult; import cn.code.nl.framework.common.pojo.CommonResult;
import cn.code.nl.module.task.dto.AcsFeedbackReqDTO; import cn.code.nl.module.task.dto.AcsFeedbackReqDTO;
@@ -22,7 +22,7 @@ import static cn.code.nl.framework.common.pojo.CommonResult.success;
*/ */
@Tag(name = "ACS 反馈") @Tag(name = "ACS 反馈")
@RestController @RestController
@RequestMapping("/api/task/transport") @RequestMapping("/task/transport")
@Validated @Validated
public class AcsFeedbackController { public class AcsFeedbackController {

View File

@@ -1,4 +1,4 @@
package cn.code.nl.module.task.controller; package cn.code.nl.module.task.controller.admin.transporttask;
import cn.code.nl.framework.common.pojo.CommonResult; import cn.code.nl.framework.common.pojo.CommonResult;
import cn.code.nl.module.task.dto.TaskCallbackResultReqDTO; import cn.code.nl.module.task.dto.TaskCallbackResultReqDTO;

View File

@@ -4,6 +4,7 @@ import java.util.*;
import cn.code.nl.framework.common.pojo.PageResult; import cn.code.nl.framework.common.pojo.PageResult;
import cn.code.nl.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.code.nl.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.code.nl.module.task.enums.TransportTaskStatusEnum;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import cn.code.nl.framework.mybatis.core.mapper.BaseMapperX; import cn.code.nl.framework.mybatis.core.mapper.BaseMapperX;
import cn.code.nl.module.task.dal.dataobject.transporttask.TransportTaskDO; import cn.code.nl.module.task.dal.dataobject.transporttask.TransportTaskDO;
@@ -65,7 +66,7 @@ public interface TransportTaskMapper extends BaseMapperX<TransportTaskDO> {
return selectOne(new LambdaQueryWrapperX<TransportTaskDO>() return selectOne(new LambdaQueryWrapperX<TransportTaskDO>()
.eq(TransportTaskDO::getBizType, bizType) .eq(TransportTaskDO::getBizType, bizType)
.eq(TransportTaskDO::getBizId, bizId) .eq(TransportTaskDO::getBizId, bizId)
.lt(TransportTaskDO::getTaskStatus, "067")); .lt(TransportTaskDO::getTaskStatus, TransportTaskStatusEnum.FINISHED_CALLBACK_PENDING.getCode()));
} }
/** /**

View File

@@ -1,15 +1,12 @@
package cn.code.nl.module.task.framework.rpc.config; package cn.code.nl.module.task.framework.rpc.config;
import cn.code.nl.module.task.api.TransportTaskApi;
import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
/** /**
* Task 服务 RPC 配置 * Task 服务 RPC 配置
* <p>
* 注册 Feign 客户端,使 LMS/WMS 等调用方可通过 @Resource 注入 TransportTaskApi
*/ */
@Configuration @Configuration(value = "taskRpcConfiguration", proxyBeanMethods = false)
@EnableFeignClients(clients = {TransportTaskApi.class}) @EnableFeignClients()
public class RpcConfiguration { public class RpcConfiguration {
} }

View File

@@ -1,14 +1,14 @@
package cn.code.nl.module.task.framework.security.config; package cn.code.nl.module.task.framework.security.config;
import cn.code.nl.framework.security.config.AuthorizeRequestsCustomizer; import cn.code.nl.framework.security.config.AuthorizeRequestsCustomizer;
import cn.code.nl.module.infra.enums.ApiConstants; import cn.code.nl.module.task.enums.ApiConstants;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer; import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer;
/** /**
* Infra 模块的 Security 配置 * Task 模块的 Security 配置
*/ */
@Configuration(proxyBeanMethods = false, value = "taskSecurityConfiguration") @Configuration(proxyBeanMethods = false, value = "taskSecurityConfiguration")
public class SecurityConfiguration { public class SecurityConfiguration {

View File

@@ -1,5 +1,6 @@
/** /**
* * 消费者
* @Author: liyongde * @Author: liyongde
* @Date: 2026/7/14 18:47 * @Date: 2026/7/14 18:47
*/package cn.code.nl.module.task.mq.consumer; */
package cn.code.nl.module.task.mq.consumer;

View File

@@ -1,4 +1,4 @@
package cn.code.nl.module.task.dto; package cn.code.nl.module.task.mq.message;
import lombok.Data; import lombok.Data;

View File

@@ -1,5 +1,6 @@
/** /**
* * 信息体
* @Author: liyongde * @Author: liyongde
* @Date: 2026/7/14 18:47 * @Date: 2026/7/14 18:47
*/package cn.code.nl.module.task.mq.message; */
package cn.code.nl.module.task.mq.message;

View File

@@ -1,4 +1,4 @@
/** /**
* 消息队列的消息 * 消息队列的消息
*/ */
package cn.code.nl.module.infra.mq.message; package cn.code.nl.module.task.mq;

View File

@@ -1,6 +1,6 @@
package cn.code.nl.module.task.mq; package cn.code.nl.module.task.mq.producer;
import cn.code.nl.module.task.dto.TaskEventMessage; import cn.code.nl.module.task.mq.message.TaskEventMessage;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.core.RocketMQTemplate; import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;

View File

@@ -4,13 +4,13 @@ import cn.code.nl.module.task.client.TransportTaskStatusClient;
import cn.code.nl.module.task.dal.dataobject.transporttask.TransportTaskDO; import cn.code.nl.module.task.dal.dataobject.transporttask.TransportTaskDO;
import cn.code.nl.module.task.dal.mysql.transporttask.TransportTaskMapper; import cn.code.nl.module.task.dal.mysql.transporttask.TransportTaskMapper;
import cn.code.nl.module.task.dto.AcsFeedbackReqDTO; import cn.code.nl.module.task.dto.AcsFeedbackReqDTO;
import cn.code.nl.module.task.dto.TaskEventMessage; import cn.code.nl.module.task.mq.message.TaskEventMessage;
import cn.code.nl.module.task.dto.TaskStatusCallbackReqDTO; import cn.code.nl.module.task.dto.TaskStatusCallbackReqDTO;
import cn.code.nl.module.task.dto.TaskStatusCallbackRespDTO; import cn.code.nl.module.task.dto.TaskStatusCallbackRespDTO;
import cn.code.nl.module.task.enums.CallbackStatusEnum; import cn.code.nl.module.task.enums.CallbackStatusEnum;
import cn.code.nl.module.task.enums.TaskEventTypeEnum; import cn.code.nl.module.task.enums.TaskEventTypeEnum;
import cn.code.nl.module.task.enums.TransportTaskStatusEnum; import cn.code.nl.module.task.enums.TransportTaskStatusEnum;
import cn.code.nl.module.task.mq.TaskEventProducer; import cn.code.nl.module.task.mq.producer.TaskEventProducer;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;

View File

@@ -1,5 +1,6 @@
package cn.code.nl.module.task.service.transporttask; package cn.code.nl.module.task.service.transporttask;
import cn.code.nl.framework.common.exception.ServiceException;
import cn.code.nl.framework.common.pojo.PageResult; import cn.code.nl.framework.common.pojo.PageResult;
import cn.code.nl.framework.common.util.object.BeanUtils; import cn.code.nl.framework.common.util.object.BeanUtils;
import cn.code.nl.module.task.controller.admin.transporttask.vo.TransportTaskPageReqVO; import cn.code.nl.module.task.controller.admin.transporttask.vo.TransportTaskPageReqVO;
@@ -43,21 +44,12 @@ public class TransportTaskServiceImpl implements TransportTaskService {
@Override @Override
public Long createTransportTaskByRpc(TransportTaskCreateReqDTO reqDTO) { public Long createTransportTaskByRpc(TransportTaskCreateReqDTO reqDTO) {
// 1. 幂等检查:同 bizType + bizId 下是否有未完结任务 // 1. 构建 DO 并保存
TransportTaskDO existing = transportTaskMapper.selectUnfinishedByBiz(
reqDTO.getBizType(), reqDTO.getBizId());
if (existing != null) {
return existing.getTaskId();
}
// 2. 构建 DO 并保存
TransportTaskDO task = BeanUtils.toBean(reqDTO, TransportTaskDO.class); TransportTaskDO task = BeanUtils.toBean(reqDTO, TransportTaskDO.class);
// 参数完整 → 待下发(040),否则 → 生成(010) // 参数完整 → 待下发(040),否则 → 生成(010)
boolean paramComplete = StrUtil.isNotBlank(reqDTO.getPointCode1()) task.setTaskStatus(reqDTO.getIsCreateFinish()
&& StrUtil.isNotBlank(reqDTO.getPointCode2()); ? String.valueOf(TransportTaskStatusEnum.READY.getCode())
task.setTaskStatus(paramComplete : String.valueOf(TransportTaskStatusEnum.CREATED.getCode()));
? String.format("%03d", TransportTaskStatusEnum.READY.getCode())
: String.format("%03d", TransportTaskStatusEnum.CREATED.getCode()));
task.setCallbackStatus(CallbackStatusEnum.PENDING.getCode()); task.setCallbackStatus(CallbackStatusEnum.PENDING.getCode());
task.setCallbackRetryCount(0); task.setCallbackRetryCount(0);
task.setCreateMode("RPC"); task.setCreateMode("RPC");

View File

@@ -79,6 +79,8 @@ spring:
# rocketmq 配置项,对应 RocketMQProperties 配置类 # rocketmq 配置项,对应 RocketMQProperties 配置类
rocketmq: rocketmq:
name-server: 127.0.0.1:9876 # RocketMQ Namesrv name-server: 127.0.0.1:9876 # RocketMQ Namesrv
producer:
group: ${spring.application.name}_TASK_DEV_PRODUCER # 生产者分组
spring: spring:
# RabbitMQ 配置项,对应 RabbitProperties 配置类 # RabbitMQ 配置项,对应 RabbitProperties 配置类

View File

@@ -64,10 +64,10 @@ mybatis-plus:
map-underscore-to-camel-case: true # 虽然默认为 true ,但是还是显示去指定下。 map-underscore-to-camel-case: true # 虽然默认为 true ,但是还是显示去指定下。
global-config: global-config:
db-config: db-config:
id-type: NONE # “智能”模式,基于 IdTypeEnvironmentPostProcessor + 数据源的类型,自动适配成 AUTO、INPUT 模式。 id-type: ASSIGN_ID # 分配 ID默认使用雪花算法。注意Oracle、PostgreSQL、Kingbase、DB2、H2 数据库时,需要去除实体类上的 @KeySequence 注解
# id-type: NONE # “智能”模式,基于 IdTypeEnvironmentPostProcessor + 数据源的类型,自动适配成 AUTO、INPUT 模式。
# id-type: AUTO # 自增 ID适合 MySQL 等直接自增的数据库 # id-type: AUTO # 自增 ID适合 MySQL 等直接自增的数据库
# id-type: INPUT # 用户输入 ID适合 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库 # id-type: INPUT # 用户输入 ID适合 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库
# id-type: ASSIGN_ID # 分配 ID默认使用雪花算法。注意Oracle、PostgreSQL、Kingbase、DB2、H2 数据库时,需要去除实体类上的 @KeySequence 注解
logic-delete-value: 1 # 逻辑已删除值(默认为 1) logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0) logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
banner: false # 关闭控制台的 Banner 打印 banner: false # 关闭控制台的 Banner 打印

View File

@@ -1,8 +1,11 @@
package cn.code.nl.module.task.service.transporttask; package cn.code.nl.module.task.service.transporttask;
import cn.code.nl.framework.common.pojo.CommonResult;
import cn.code.nl.module.task.api.TransportTaskApi;
import cn.code.nl.module.task.dal.dataobject.transporttask.TransportTaskDO; import cn.code.nl.module.task.dal.dataobject.transporttask.TransportTaskDO;
import cn.code.nl.module.task.dal.mysql.transporttask.TransportTaskMapper; import cn.code.nl.module.task.dal.mysql.transporttask.TransportTaskMapper;
import cn.code.nl.module.task.dto.TransportTaskCreateReqDTO; import cn.code.nl.module.task.dto.TransportTaskCreateReqDTO;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
@@ -22,6 +25,34 @@ class TransportTaskServiceImplTest {
@InjectMocks @InjectMocks
private TransportTaskServiceImpl transportTaskService; private TransportTaskServiceImpl transportTaskService;
/**
* Feign 客户端 —— 真实发起 HTTP 调用,直连本机 Controller
*/
@Mock
private TransportTaskApi transportTaskApi;
/**
* 端到端Feign → Controller → Service → Mapper
*/
@Test
void testApiCreateTask() {
// given
TransportTaskCreateReqDTO req = new TransportTaskCreateReqDTO();
req.setTaskName("测试任务");
req.setOwnerService("LMS");
req.setBizType("INBOUND");
req.setBizId("INB-001");
req.setHandleCode("inboundHandler");
req.setAcsTaskType("MOVE");
req.setAgvSystemType("AGV_TYPE_A");
req.setPointCode1("1");
req.setPointCode2("2");
req.setPointCode3("3");
req.setPointCode4("4");
transportTaskApi.createTransportTask(req);
}
@Test @Test
void testCreateTransportTaskByRpc_shouldReturnTaskId() { void testCreateTransportTaskByRpc_shouldReturnTaskId() {
// given // given