开发进度.md 12.8 KB

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. 修改文件

锁获取 Lua 脚本逻辑

if 锁不存在 then
    创建新锁
    return 成功
elseif 当前机器人持有锁 then
    续期
    return 成功
elseif 锁已过期 then
    抢占锁
    return 成功
else
    return 失败
end

任务取消时释放路径锁

状态: ✅ 已完成 完成时间: 2026-01-30 作者: zzy

功能说明

在任务取消时,除了删除 VDA 路径缓存数据外,同时释放该机器人持有的所有路径锁,防止死锁和资源泄漏。

修改文件

基础设施层

实现代码

// 释放该机器人的所有路径锁
// @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 秒后锁自动过期,其他机器人可能误判为可占用,导致路径冲突。

修改文件

基础设施层

实现代码

// 机器人行驶中时,续期路径锁(防止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 导航属性,实现任务与子任务的双向关联。

修改文件

  1. 领域层

  2. 应用层

  3. 基础设施层

使用方式

// 查询任务时包含子任务
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层

基础设施层 - 以下 6 个服务类已重构:

配置结构

{
  "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 时(表示车辆快到当前段终点),自动判断下一段路径是否存在逆向占用冲突,如果无人占用或仅同向占用,则自动发送下一段路径指令。

核心逻辑

  1. 触发条件: 状态消息中 NewBaseRequest=true
  2. 路径冲突检测:
    • 检查下一段路径是否存在逆向占用(HeadOnConflict
    • 检查下一段路径是否被其他机器人占用
  3. 路径锁定: 使用统一交通管制服务锁定下一段路径
  4. 指令下发: 构建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>();

修改文件