wanshenmean
2026-03-26 8e42d0c1b7ae36cff2e7c69999117911a4b6f300
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotStateManager.cs
@@ -6,74 +6,110 @@
namespace WIDESEAWCS_Tasks
{
    /// <summary>
    /// 机械手状态管理器 - 负责RobotSocketState的安全更新和克隆
    /// 机械手状态管理器 - 负责 RobotSocketState 的线程安全更新和克隆
    /// </summary>
    /// <remarks>
    /// 核心功能是通过缓存服务(ICacheService)管理 Redis 中的机械手状态。
    /// 提供乐观并发控制,通过版本号(Version)字段防止并发更新时的数据覆盖问题。
    /// </remarks>
    public class RobotStateManager
    {
        /// <summary>
        /// 缓存服务实例,用于读写 Redis 中的状态数据
        /// </summary>
        private readonly ICacheService _cache;
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="cache">缓存服务实例(通常为 HybridCacheService)</param>
        public RobotStateManager(ICacheService cache)
        {
            _cache = cache;
        }
        /// <summary>
        /// 安全更新RobotSocketState缓存,防止并发覆盖
        /// 安全更新 RobotSocketState 缓存,防止并发覆盖
        /// </summary>
        /// <param name="ipAddress">设备IP地址</param>
        /// <param name="updateAction">更新状态的委托(传入当前状态,返回修改后的新状态)</param>
        /// <returns>是否更新成功</returns>
        /// <remarks>
        /// 使用乐观并发模式:先读取当前版本号,执行更新时检查版本是否一致。
        /// 如果版本不匹配(说明有其他线程已更新),则更新失败返回 false。
        /// </remarks>
        /// <param name="ipAddress">设备 IP 地址,用于构建缓存键</param>
        /// <param name="updateAction">更新状态的委托函数,传入当前状态副本,返回修改后的新状态</param>
        /// <returns>是否更新成功;false 表示版本冲突或状态不存在</returns>
        public bool TryUpdateStateSafely(string ipAddress, Func<RobotSocketState, RobotSocketState> updateAction)
        {
            // 构建 Redis 缓存键,格式:{RedisPrefix.Code}:{RedisName.SocketDevices}:{ipAddress}
            var cacheKey = GetCacheKey(ipAddress);
            // 从缓存获取当前存储的状态
            var currentState = _cache.Get<RobotSocketState>(cacheKey);
            // 如果缓存中不存在该设备的状态,直接返回 false(应由 GetOrCreateState 先创建)
            if (currentState == null)
            {
                return false;
            }
            // 使用当前存储的版本号作为期望版本
            // 记录当前存储的版本号,作为更新时的期望版本
            var expectedVersion = currentState.Version;
            // 创建状态副本进行修改(避免修改原对象引用)
            // 创建状态的深拷贝副本,避免直接修改原对象引用
            // 这样可以确保在多线程环境下,每个线程操作的是独立的状态副本
            var stateCopy = CloneState(currentState);
            // 执行调用者提供的更新逻辑,传入副本状态,获取新的状态对象
            var newState = updateAction(stateCopy);
            // 将新状态的版本号更新为最新的时间戳,表示数据已更新
            newState.Version = DateTime.UtcNow.Ticks;
            // 调用缓存服务的安全更新方法,传入期望版本和版本提取器
            // 如果当前版本与期望版本不一致(已被其他线程更新),则更新失败
            return _cache.TrySafeUpdate(
                cacheKey,
                newState,
                expectedVersion,
                s => s.Version
                s => s.Version  // 指定哪个字段作为版本号
            );
        }
        /// <summary>
        /// 安全更新RobotSocketState缓存(简单版本)
        /// 安全更新 RobotSocketState 缓存的重载版本(直接传入新状态)
        /// </summary>
        /// <param name="ipAddress">设备IP地址</param>
        /// <param name="newState">新状态(会被更新Version字段)</param>
        /// <returns>是否更新成功</returns>
        /// <remarks>
        /// 与上一个重载的区别:此方法直接接收完整的新状态对象,而不是更新委托。
        /// 如果设备状态不存在于缓存中,则直接添加新状态。
        /// </remarks>
        /// <param name="ipAddress">设备 IP 地址,用于构建缓存键</param>
        /// <param name="newState">新状态对象(方法内部会更新其 Version 字段)</param>
        /// <returns>是否更新成功;新建设置为 true</returns>
        public bool TryUpdateStateSafely(string ipAddress, RobotSocketState newState)
        {
            // 构建 Redis 缓存键
            var cacheKey = GetCacheKey(ipAddress);
            // 从缓存获取当前存储的状态
            var currentState = _cache.Get<RobotSocketState>(cacheKey);
            // 如果当前不存在该设备的状态
            if (currentState == null)
            {
                // 当前不存在,直接添加
                // 为新状态设置版本号(时间戳)
                newState.Version = DateTime.UtcNow.Ticks;
                // 直接添加到缓存
                _cache.AddObject(cacheKey, newState);
                return true;
            }
            // 使用当前存储的版本号作为期望版本
            // 当前存在状态,记录期望版本号用于乐观锁检查
            var expectedVersion = currentState.Version;
            // 更新新状态的版本号为最新时间戳
            newState.Version = DateTime.UtcNow.Ticks;
            // 尝试安全更新,如果版本冲突则返回 false
            return _cache.TrySafeUpdate(
                cacheKey,
                newState,
@@ -83,40 +119,64 @@
        }
        /// <summary>
        /// 克隆RobotSocketState对象(创建深拷贝)
        /// 克隆 RobotSocketState 对象(深拷贝)
        /// </summary>
        /// <remarks>
        /// 使用 JSON 序列化/反序列化实现深拷贝。
        /// 这样可以确保新对象与原对象完全独立,修改新对象不会影响原对象。
        /// </remarks>
        /// <param name="source">源状态对象</param>
        /// <returns>新的状态对象,是源对象的深拷贝</returns>
        public RobotSocketState CloneState(RobotSocketState source)
        {
            // 使用序列化/反序列化进行深拷贝
            // 将源对象序列化为 JSON 字符串
            var json = JsonConvert.SerializeObject(source);
            // 反序列化为新的 RobotSocketState 对象
            // 如果反序列化失败(返回 null),创建一个新对象并复制 IPAddress
            return JsonConvert.DeserializeObject<RobotSocketState>(json) ?? new RobotSocketState { IPAddress = source.IPAddress };
        }
        /// <summary>
        /// 获取Redis缓存键
        /// 获取 Redis 缓存键
        /// </summary>
        /// <remarks>
        /// 缓存键格式:{RedisPrefix.Code}:{RedisName.SocketDevices}:{ipAddress}
        /// 例如:Code:SocketDevices:192.168.1.100
        /// </remarks>
        /// <param name="ipAddress">设备 IP 地址</param>
        /// <returns>完整的 Redis 缓存键</returns>
        public static string GetCacheKey(string ipAddress)
        {
            return $"{RedisPrefix.Code}:{RedisName.SocketDevices}:{ipAddress}";
        }
        /// <summary>
        /// 从缓存获取状态
        /// 从缓存获取机械手状态
        /// </summary>
        /// <param name="ipAddress">设备 IP 地址</param>
        /// <returns>如果存在则返回状态对象,否则返回 null</returns>
        public RobotSocketState? GetState(string ipAddress)
        {
            return _cache.Get<RobotSocketState>(GetCacheKey(ipAddress));
        }
        /// <summary>
        /// 获取或创建状态
        /// 获取或创建机械手状态
        /// </summary>
        /// <remarks>
        /// 如果缓存中已存在该设备的状态,直接返回。
        /// 如果不存在,则创建新的状态对象并存入缓存,然后返回。
        /// </remarks>
        /// <param name="ipAddress">设备 IP 地址</param>
        /// <param name="robotCrane">机器人设备信息,用于初始化新状态</param>
        /// <returns>该设备的状态对象</returns>
        public RobotSocketState GetOrCreateState(string ipAddress, RobotCraneDevice robotCrane)
        {
            // 使用缓存服务的 GetOrAdd 方法,工厂函数在缓存未命中时创建新状态
            return _cache.GetOrAdd(GetCacheKey(ipAddress), _ => new RobotSocketState
            {
                IPAddress = ipAddress,
                RobotCrane = robotCrane
                IPAddress = ipAddress,  // 设置 IP 地址作为标识
                RobotCrane = robotCrane  // 保存设备信息
            });
        }
    }