HaHRCS 开发进度
规则修订
| 日期 | 修订内容 |
|---|---|
| 2026-01-27 | 新增 SubTask 导航属性及相关配置 |
功能开发记录
地图锁重构为 Redis 分布式锁
状态: ✅ 已完成 完成时间: 2026-01-30 作者: zzy
功能说明
将内存中的 ConcurrentDictionary 锁机制重构为 Redis 分布式锁,支持多实例部署和集群环境下的路径冲突检测。
主要变更
1. Redis Key 设计
| 类型 | Key 格式 | 说明 |
|---|---|---|
| 节点锁 | rcs:lock:node:{MapCode}:{NodeCode} |
Hash 结构存储锁信息 |
| 边锁 | rcs:lock:edge:{MapCode}:{EdgeCode} |
Hash 结构存储锁信息 |
| 机器人节点索引 | rcs:lock:robot:{RobotId}:nodes |
Set 类型,记录机器人持有的节点锁 |
| 机器人边索引 | rcs:lock:robot:{RobotId}:edges |
Set 类型,记录机器人持有的边锁 |
2. Hash 字段定义
| 字段 | 含义 | 类型 |
|---|---|---|
rid |
RobotId | string (Guid) |
lat |
LockedAt (UTC) | long (Ticks) |
eat |
ExpiresAt (UTC) | long (Ticks) |
3. 核心机制
- Lua 脚本原子操作:使用 Lua 脚本保证锁获取/释放的原子性
- 自动过期:通过 Redis EXPIRE 设置 TTL,防止死锁
- 资源索引:使用 Redis Set 维护每个机器人持有的锁列表
- 续期机制:支持滑动过期时间更新
4. 修改文件
-
领域层
- Redis.cs - 添加锁相关 Key 前缀配置
-
基础设施层
- UnifiedTrafficControlService.cs - 完全重构,内存锁改为 Redis 锁
-
API 层
- appsettings.Development.json - 添加锁相关配置
锁获取 Lua 脚本逻辑
if 锁不存在 then
创建新锁
return 成功
elseif 当前机器人持有锁 then
续期
return 成功
elseif 锁已过期 then
抢占锁
return 成功
else
return 失败
end
任务取消时释放路径锁
状态: ✅ 已完成 完成时间: 2026-01-30 作者: zzy
功能说明
在任务取消时,除了删除 VDA 路径缓存数据外,同时释放该机器人持有的所有路径锁,防止死锁和资源泄漏。
修改文件
基础设施层
-
Vda5050ProtocolService.cs -
CancelRobotTasksAsync方法添加锁释放逻辑 -
CustomProtocolService.cs - 注入
IUnifiedTrafficControlService并添加锁释放逻辑
实现代码
// 释放该机器人的所有路径锁
// @author zzy
// @date 2026-01-30
var releasedLockCount = await _unifiedTrafficControlService.ReleaseAllRobotLocksAsync(robot.RobotId);
_logger.LogInformation("任务取消,释放机器人路径锁: RobotId={RobotId}, ReleasedCount={Count}",
robot.RobotId, releasedLockCount);
路径锁自动续期机制
状态: ✅ 已完成 完成时间: 2026-01-30 作者: zzy
功能说明
在 MQTT 状态消息处理中,当检测到机器人处于行驶状态时,自动续期该机器人的所有路径锁,解决因 30 秒 TTL 过期导致的冲突误判问题。
问题背景
TryAcquireNextSegmentLockAsync 方法中检测冲突的锁默认有效期为 30 秒,过了 30 秒后锁自动过期,其他机器人可能误判为可占用,导致路径冲突。
修改文件
基础设施层
-
MqttMessageHandler.cs - 在
HandleStateMessageAsync方法中添加锁续期逻辑
实现代码
// 机器人行驶中时,续期路径锁(防止30秒TTL过期导致冲突误判)
// @author zzy
// @date 2026-01-30
if (stateInfo.Driving)
{
var basicInfo = await _robotCacheService.GetBasicAsync(robotManufacturer, robotSerialNumber);
if (basicInfo != null && Guid.TryParse(basicInfo.RobotId, out var robotId))
{
await _unifiedTrafficControlService.RenewAllRobotLocksAsync(robotId, 30);
_logger.LogDebug("机器人行驶中,路径锁续期成功: RobotId={RobotId}", robotId);
}
}
触发条件
- 机器人状态消息中
Driving = true(正在行驶) - 成功获取机器人基础信息缓存
- 使用
RenewAllRobotLocksAsync续期所有锁(TTL 重置为 30 秒)
任务与子任务关系配置
状态: ✅ 已完成 完成时间: 2026-01-27 作者: zzy
功能说明
为 RobotTask 实体添加 SubTasks 导航属性,实现任务与子任务的双向关联。
修改文件
-
领域层
- RobotTask.cs - 添加 SubTasks 导航属性
-
应用层
- RobotSubTaskDto.cs - 新建子任务 DTO
- RobotTaskDto.cs - 添加 SubTasks 集合字段
- MappingProfile.cs - 添加 RobotSubTask 映射配置
-
基础设施层
- RobotTaskConfiguration.cs - 配置一对多关系
- RobotSubTaskConfiguration.cs - 更新为双向关系
使用方式
// 查询任务时包含子任务
var taskWithSubTasks = await context.RobotTasks
.Include(t => t.SubTasks)
.ThenInclude(s => s.Robot)
.FirstOrDefaultAsync(t => t.TaskId == taskId);
// 映射到 DTO
var taskDto = _mapper.Map<RobotTaskDto>(taskWithSubTasks);
// taskDto.SubTasks 将包含子任务列表
关系图示
RobotTask (1) ──< (N) RobotSubTask
│ │
└── SubTasks └── Task
Redis Key 前缀配置统一管理
状态: ✅ 已完成 完成时间: 2026-01-29 作者: zzy
功能说明
将项目中所有 Redis Key 前缀配置从硬编码常量迁移到 appsettings 配置文件,通过依赖注入统一管理,防止重复命名问题。
修改文件
领域层
-
Redis.cs - 扩展配置类,添加完整的
RedisKeyPrefixes记录类型
API层
-
appsettings.Development.json - 添加完整的
Redis.KeyPrefixes配置节
基础设施层 - 以下 6 个服务类已重构:
- Vda5050ProtocolService.cs - VDA5050 协议服务
- TaskExecutionBackgroundService.cs - 任务执行后台服务
- MqttMessageHandler.cs - MQTT 消息处理器
- RobotCacheService.cs - 机器人缓存服务
- MapCacheService.cs - 地图缓存服务
- DataSyncBackgroundService.cs - 数据同步后台服务
配置结构
{
"Redis": {
"Host": "localhost",
"Port": "7379",
"Password": "xxx",
"KeyPrefixes": {
"VdaPath": "rcs:vda:path:",
"Robot": "rcs:robot:",
"RobotStatusSuffix": ":status",
"RobotLocationSuffix": ":location",
"RobotBasicSuffix": ":basic",
"RobotsSet": "rcs:robots",
"RobotsOnlineSet": "rcs:robots:online",
"RobotsIdleSet": "rcs:robots:idle",
"Map": "rcs:map:",
"MapList": "rcs:maps",
"MapNodeIndexSuffix": ":nodes",
"MqttHeaderSuffix": ":revice-headers"
}
}
}
使用方式
// 在服务中注入 IOptions<AppSettings>
private readonly AppSettings _settings;
public Vda5050ProtocolService(IOptions<AppSettings> settings)
{
_settings = settings.Value;
}
// 使用配置中的 Key 前缀
var key = $"{_settings.Redis.KeyPrefixes.VdaPath}{robotId}:{taskId}";
var statusKey = $"{_settings.Redis.KeyPrefixes.Robot}{manufacturer}:{serialNumber}{_settings.Redis.KeyPrefixes.RobotStatusSuffix}";
涵盖的 Redis Key 类型
| 类型 | 前缀配置 | 说明 |
|---|---|---|
| VDA 路径缓存 | VdaPath |
VDA5050 协议分段路径缓存 |
| 机器人状态 |
Robot + RobotStatusSuffix
|
机器人状态 Hash |
| 机器人位置 |
Robot + RobotLocationSuffix
|
机器人位置 Hash |
| 机器人基础信息 |
Robot + RobotBasicSuffix
|
机器人基础信息 Hash |
| 机器人集合 | RobotsSet |
所有机器人 ID 集合 |
| 在线机器人集合 | RobotsOnlineSet |
在线机器人 ID 集合 |
| 空闲机器人集合 | RobotsIdleSet |
空闲机器人 ID 集合 |
| 地图缓存 | Map |
单个地图数据 |
| 地图列表 | MapList |
所有地图 ID 列表 |
| 地图节点索引 |
Map + MapNodeIndexSuffix
|
地图节点编码到 ID 的索引 |
| MQTT 头部 |
Robot + MqttHeaderSuffix
|
MQTT 消息头部 ID 追踪 |
扩展说明
如需添加新的 Key 前缀,只需在 RedisKeyPrefixes 记录中添加新属性,并在 appsettings.json 中配置对应值。
MQTT状态消息处理 - 下一段路径自动下发
状态: ✅ 已完成 完成时间: 2026-01-28 作者: zzy
功能说明
在处理MQTT状态消息时,当 stateInfo.NewBaseRequest=true 时(表示车辆快到当前段终点),自动判断下一段路径是否存在逆向占用冲突,如果无人占用或仅同向占用,则自动发送下一段路径指令。
核心逻辑
-
触发条件: 状态消息中
NewBaseRequest=true -
路径冲突检测:
- 检查下一段路径是否存在逆向占用(
HeadOnConflict) - 检查下一段路径是否被其他机器人占用
- 检查下一段路径是否存在逆向占用(
- 路径锁定: 使用统一交通管制服务锁定下一段路径
- 指令下发: 构建VDA5050订单并通过MQTT发送
修改文件
基础设施层
-
MqttMessageHandler.cs - 添加以下内容:
-
HandleNewBaseRequestAsync方法: 处理NewBaseRequest请求 -
TryAcquireNextSegmentLockAsync方法: 尝试锁定下一段路径 -
SendNextSegmentOrderAsync方法: 发送下一段路径指令 -
FillOrderWithSegments方法: 填充订单节点和边 -
BuildNode方法: 构建VDA5050节点信息 -
BuildEdge方法: 构建VDA5050边信息 -
NextSegmentLockResult类: 下一段路径锁定结果 -
VdaSegmentedPathCache类: VDA5050分段路径缓存 -
VdaSegmentCacheItem类: VDA5050分段缓存项
-
使用方式
该功能在 HandleStateMessageAsync 方法中自动触发,无需手动调用:
// 处理NewBaseRequest:当车辆快到当前段终点时,判断并发送下一段路径指令
if ((bool)stateInfo.NewBaseRequest)
{
await HandleNewBaseRequestAsync(robotManufacturer, robotSerialNumber);
}
处理流程
NewBaseRequest=true
↓
获取机器人基础信息和路径缓存
↓
检查下一段路径是否存在逆向冲突
↓
[存在逆向冲突?] → 是 → 暂停发送,等待下次检查
↓ 否
尝试锁定下一段路径
↓
[锁定成功?] → 否 → 记录失败原因,等待下次检查
↓ 是
构建并发送VDA5050订单
↓
更新缓存中的当前分段索引
待开发功能
暂无
已知问题
暂无
修复记录
循环依赖修复 - Vda5050ProtocolService 与 IMqttClientService
状态: ✅ 已修复 修复时间: 2026-01-29 作者: zzy
问题描述
启动时报错:
Some services are not able to be constructed (Error while validating the service descriptor
'ServiceType: Rcs.Application.Services.Protocol.IProtocolService Lifetime: Scoped
ImplementationType: Rcs.Infrastructure.Services.Protocol.Vda5050ProtocolService':
A circular dependency was detected for the service of type 'Rcs.Application.Shared.IMqttClientService'.
循环依赖链
Vda5050ProtocolService → IMqttClientService
↑ ↓
IProtocolServiceFactory ← MqttClientService
↑ ↓
MqttMessageHandler ←── IMqttMessageHandler
修复方案
在 MqttMessageHandler 中使用 IServiceProvider 延迟解析 IProtocolServiceFactory,改为属性注入方式:
/// <summary>
/// 使用延迟解析获取协议服务工厂,避免循环依赖:
/// Vda5050ProtocolService → IMqttClientService → IMqttMessageHandler → IProtocolServiceFactory
/// </summary>
private IProtocolServiceFactory ProtocolServiceFactory => _serviceProvider.GetRequiredService<IProtocolServiceFactory>();
修改文件
- MqttMessageHandler.cs - 移除构造函数注入,改为延迟属性解析