using Microsoft.Extensions.Logging; using Newtonsoft.Json; using WIDESEAWCS_Common; using WIDESEAWCS_Core.Caches; using WIDESEAWCS_Core.LogHelper; using WIDESEAWCS_QuartzJob; namespace WIDESEAWCS_Tasks { /// /// 机械手状态管理器 - 负责 RobotSocketState 的线程安全更新和克隆 /// /// /// 核心功能是通过缓存服务(ICacheService)管理 Redis 中的机械手状态。 /// 提供乐观并发控制,通过版本号(Version)字段防止并发更新时的数据覆盖问题。 /// public class RobotStateManager { /// /// 缓存服务实例,用于读写 Redis 中的状态数据 /// private readonly ICacheService _cache; /// /// 日志记录器 /// private readonly ILogger _logger; /// /// 构造函数 /// /// 缓存服务实例(通常为 HybridCacheService) /// 日志记录器 public RobotStateManager(ICacheService cache, ILogger logger) { _cache = cache; _logger = logger; } /// /// 安全更新 RobotSocketState 缓存,防止并发覆盖 /// /// /// 使用乐观并发模式:先读取当前版本号,执行更新时检查版本是否一致。 /// 如果版本不匹配(说明有其他线程已更新),则更新失败返回 false。 /// /// 设备 IP 地址,用于构建缓存键 /// 更新状态的委托函数,传入当前状态副本,返回修改后的新状态 /// 是否更新成功;false 表示版本冲突或状态不存在 public bool TryUpdateStateSafely(string ipAddress, Func updateAction) { // 构建 Redis 缓存键,格式:{RedisPrefix.Code}:{RedisName.SocketDevices}:{ipAddress} var cacheKey = GetCacheKey(ipAddress); // 从缓存获取当前存储的状态 var currentState = _cache.Get(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 // 指定哪个字段作为版本号 ); } /// /// 安全更新 RobotSocketState 缓存的重载版本(直接传入新状态) /// /// /// 与上一个重载的区别:此方法直接接收完整的新状态对象,而不是更新委托。 /// 如果设备状态不存在于缓存中,则直接添加新状态。 /// /// 设备 IP 地址,用于构建缓存键 /// 新状态对象(方法内部会更新其 Version 字段) /// 是否更新成功;新建设置为 true public bool TryUpdateStateSafely(string ipAddress, RobotSocketState newState) { // 构建 Redis 缓存键 var cacheKey = GetCacheKey(ipAddress); // 从缓存获取当前存储的状态 var currentState = _cache.Get(cacheKey); // 如果当前不存在该设备的状态 if (currentState == null) { // 为新状态设置版本号(时间戳) newState.Version = DateTime.UtcNow.Ticks; // 直接添加到缓存 _cache.AddObject(cacheKey, newState); _logger.LogDebug("TryUpdateStateSafely:创建新状态,IP: {IpAddress}", ipAddress); QuartzLogger.Debug($"创建新状态,IP: {ipAddress}", ipAddress); return true; } // 当前存在状态,记录期望版本号用于乐观锁检查 var expectedVersion = currentState.Version; // 更新新状态的版本号为最新时间戳 newState.Version = DateTime.UtcNow.Ticks; // 尝试安全更新,如果版本冲突则返回 false bool success = _cache.TrySafeUpdate( cacheKey, newState, expectedVersion, s => s.Version ); if (!success) { _logger.LogWarning("TryUpdateStateSafely:版本冲突,更新失败,IP: {IpAddress},期望版本: {ExpectedVersion}", ipAddress, expectedVersion); QuartzLogger.Warn($"版本冲突,更新失败,IP: {ipAddress}", ipAddress); } return success; } /// /// 克隆 RobotSocketState 对象(深拷贝) /// /// /// 使用 JSON 序列化/反序列化实现深拷贝。 /// 这样可以确保新对象与原对象完全独立,修改新对象不会影响原对象。 /// /// 源状态对象 /// 新的状态对象,是源对象的深拷贝 public RobotSocketState CloneState(RobotSocketState source) { // 将源对象序列化为 JSON 字符串 var json = JsonConvert.SerializeObject(source); // 反序列化为新的 RobotSocketState 对象 // 如果反序列化失败(返回 null),创建一个新对象并复制 IPAddress return JsonConvert.DeserializeObject(json) ?? new RobotSocketState { IPAddress = source.IPAddress }; } /// /// 获取 Redis 缓存键 /// /// /// 缓存键格式:{RedisPrefix.Code}:{RedisName.SocketDevices}:{ipAddress} /// 例如:Code:SocketDevices:192.168.1.100 /// /// 设备 IP 地址 /// 完整的 Redis 缓存键 public static string GetCacheKey(string ipAddress) { return $"{RedisPrefix.Code}:{RedisName.SocketDevices}:{ipAddress}"; } /// /// 从缓存获取机械手状态 /// /// 设备 IP 地址 /// 如果存在则返回状态对象,否则返回 null public RobotSocketState? GetState(string ipAddress) { return _cache.Get(GetCacheKey(ipAddress)); } /// /// 获取或创建机械手状态 /// /// /// 如果缓存中已存在该设备的状态,直接返回。 /// 如果不存在,则创建新的状态对象并存入缓存,然后返回。 /// /// 设备 IP 地址 /// 机器人设备信息,用于初始化新状态 /// 该设备的状态对象 public RobotSocketState GetOrCreateState(string ipAddress, RobotCraneDevice robotCrane) { // 使用缓存服务的 GetOrAdd 方法,工厂函数在缓存未命中时创建新状态 return _cache.GetOrAdd(GetCacheKey(ipAddress), _ => new RobotSocketState { IPAddress = ipAddress, // 设置 IP 地址作为标识 RobotCrane = robotCrane // 保存设备信息 }); } } }