wanshenmean
21 小时以前 108f83653dab9ec49fbad1f1ed688f9b89d44df7
docs: 添加 RobotState Redis→数据库迁移实施计划

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
已添加1个文件
804 ■■■■■ 文件已修改
Code/docs/superpowers/plans/2026-04-19-robot-state-redis-to-db-plan.md 804 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/docs/superpowers/plans/2026-04-19-robot-state-redis-to-db-plan.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,804 @@
# RobotState Redis â†’ æ•°æ®åº“迁移实施计划
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** å°† `RobotSocketState` çš„存储从 Redis åˆ‡æ¢åˆ° SQL Server æ•°æ®åº“,使用 SqlSugar RowVersion å®žçŽ°ä¹è§‚å¹¶å‘æŽ§åˆ¶ã€‚
**Architecture:** æ–°å¢ž `Dt_RobotState` æ•°æ®åº“实体 + `IRobotStateRepository` ä»“储接口 + `RobotStateRepository` å®žçŽ°ã€‚`RobotStateManager` æ”¹ä¸ºä¾èµ– `IRobotStateRepository`,业务层(`RobotJob` ç­‰ï¼‰é€šè¿‡ DI èŽ·å–ä»“å‚¨å®žä¾‹ã€‚
**Tech Stack:** C# / .NET 6, SqlSugar ORM, SQL Server, Newtonsoft.Json
---
## æ–‡ä»¶å˜æ›´æ€»è§ˆ
| æ“ä½œ | æ–‡ä»¶è·¯å¾„ |
|------|---------|
| æ–°å¢ž | `WIDESEAWCS_Model/Models/RobotState/Dt_RobotState.cs` |
| æ–°å¢ž | `WIDESEAWCS_ITaskInfoRepository/IRobotStateRepository.cs` |
| æ–°å¢ž | `WIDESEAWCS_TaskInfoRepository/RobotStateRepository.cs` |
| æ”¹é€  | `WIDESEAWCS_Tasks/RobotJob/RobotStateManager.cs` |
| æ”¹é€  | `WIDESEAWCS_Tasks/RobotJob/RobotJob.cs` |
| ä¿ç•™ | `WIDESEAWCS_Tasks/RobotJob/RobotSocketState.cs`(作为内存 DTO) |
---
## Task 1: æ–°å»º Dt_RobotState æ•°æ®åº“实体
**文件:**
- æ–°å¢ž: `WCS/WIDESEAWCS_Server/WIDESEAWCS_Model/Models/RobotState/Dt_RobotState.cs`
- [ ] **Step 1: åˆ›å»ºå®žä½“类文件**
```csharp
using Newtonsoft.Json;
using SqlSugar;
using WIDESEAWCS_Core.DB.Models;
namespace WIDESEAWCS_Model.Models
{
    /// <summary>
    /// æœºæ¢°æ‰‹çŠ¶æ€æ•°æ®åº“å®žä½“
    /// </summary>
    /// <remarks>
    /// å¯¹åº”数据库表 Dt_RobotState,使用 RowVersion å®žçŽ°ä¹è§‚å¹¶å‘æŽ§åˆ¶ã€‚
    /// å¤æ‚对象(RobotCrane、CurrentTask、数组等)以 JSON å­—符串存储。
    /// </remarks>
    [SugarTable(nameof(Dt_RobotState), "机械手状态表")]
    public class Dt_RobotState : BaseEntity
    {
        /// <summary>
        /// ä¸»é”® ID
        /// </summary>
        [SugarColumn(IsPrimaryKey = true, IsIdentity = true, ColumnDescription = "主键ID")]
        public int Id { get; set; }
        /// <summary>
        /// æœºæ¢°æ‰‹ IP åœ°å€ï¼Œå”¯ä¸€ç´¢å¼•
        /// </summary>
        [SugarColumn(Length = 50, ColumnDescription = "机械手IP地址", IsJsonKey = true)]
        public string IPAddress { get; set; } = string.Empty;
        /// <summary>
        /// è¡Œç‰ˆæœ¬ï¼Œç”¨äºŽä¹è§‚并发控制
        /// </summary>
        /// <remarks>
        /// SqlSugar ä¼šè‡ªåŠ¨ç®¡ç†æ­¤å­—æ®µï¼Œæ¯æ¬¡æ›´æ–°æ—¶æ•°æ®åº“è‡ªåŠ¨é€’å¢žã€‚
        /// æ›´æ–°æ—¶ WHERE RowVersion = @expectedRowVersion,检查影响行数判断是否冲突。
        /// </remarks>
        [SugarColumn(ColumnDescription = "行版本(乐观锁)", IsJsonKey = true)]
        public byte[] RowVersion { get; set; } = Array.Empty<byte>();
        /// <summary>
        /// æ˜¯å¦å·²è®¢é˜…消息事件
        /// </summary>
        [SugarColumn(ColumnDescription = "是否已订阅消息事件", IsJsonKey = true)]
        public bool IsEventSubscribed { get; set; }
        /// <summary>
        /// æœºæ¢°æ‰‹è¿è¡Œæ¨¡å¼
        /// </summary>
        /// <remarks>1: æ‰‹åŠ¨æ¨¡å¼, 2: è‡ªåŠ¨æ¨¡å¼</remarks>
        [SugarColumn(ColumnDescription = "运行模式", IsNullable = true, IsJsonKey = true)]
        public int? RobotRunMode { get; set; }
        /// <summary>
        /// æœºæ¢°æ‰‹æŽ§åˆ¶æ¨¡å¼
        /// </summary>
        /// <remarks>1: å®¢æˆ·ç«¯æŽ§åˆ¶, 2: å…¶ä»–</remarks>
        [SugarColumn(ColumnDescription = "控制模式", IsNullable = true, IsJsonKey = true)]
        public int? RobotControlMode { get; set; }
        /// <summary>
        /// æœºæ¢°æ‰‹æ‰‹è‡‚抓取对象状态
        /// </summary>
        /// <remarks>0: æ— ç‰©æ–™ï¼ˆæ‰‹è‡‚空闲), 1: æœ‰ç‰©æ–™ï¼ˆå·²æŠ“取货物)</remarks>
        [SugarColumn(ColumnDescription = "手臂抓取状态", IsNullable = true, IsJsonKey = true)]
        public int? RobotArmObject { get; set; }
        /// <summary>
        /// æœºæ¢°æ‰‹è®¾å¤‡åŸºç¡€ä¿¡æ¯ï¼ˆJSON åºåˆ—化)
        /// </summary>
        [SugarColumn(Length = 2000, ColumnDescription = "设备信息JSON", IsJsonKey = true)]
        public string RobotCraneJson { get; set; } = string.Empty;
        /// <summary>
        /// æœºæ¢°æ‰‹åˆå§‹åŒ–完成回到待机位状态
        /// </summary>
        /// <remarks>Possible values: "Homed", "Homing"</remarks>
        [SugarColumn(Length = 50, ColumnDescription = "回零状态", IsNullable = true, IsJsonKey = true)]
        public string? Homed { get; set; }
        /// <summary>
        /// æœºæ¢°æ‰‹å½“前正在执行的动作
        /// </summary>
        [SugarColumn(Length = 50, ColumnDescription = "当前动作", IsNullable = true, IsJsonKey = true)]
        public string? CurrentAction { get; set; }
        /// <summary>
        /// æœºæ¢°æ‰‹å½“前运行状态
        /// </summary>
        [SugarColumn(Length = 50, ColumnDescription = "运行状态", IsNullable = true, IsJsonKey = true)]
        public string? OperStatus { get; set; }
        /// <summary>
        /// æœ€è¿‘一次取货完成的位置数组(JSON)
        /// </summary>
        [SugarColumn(Length = 500, ColumnDescription = "取货位置数组JSON", IsNullable = true, IsJsonKey = true)]
        public string? LastPickPositionsJson { get; set; }
        /// <summary>
        /// æœ€è¿‘一次放货完成的位置数组(JSON)
        /// </summary>
        [SugarColumn(Length = 500, ColumnDescription = "放货位置数组JSON", IsNullable = true, IsJsonKey = true)]
        public string? LastPutPositionsJson { get; set; }
        /// <summary>
        /// ç”µæ± /货位条码列表(JSON)
        /// </summary>
        [SugarColumn(Length = 2000, ColumnDescription = "电芯条码列表JSON", IsNullable = true, IsJsonKey = true)]
        public string? CellBarcodeJson { get; set; }
        /// <summary>
        /// æœºæ¢°æ‰‹å½“前正在执行的任务(JSON åºåˆ—化)
        /// </summary>
        [SugarColumn(Length = 2000, ColumnDescription = "当前任务JSON", IsNullable = true, IsJsonKey = true)]
        public string? CurrentTaskJson { get; set; }
        /// <summary>
        /// æ˜¯å¦éœ€è¦æ‰§è¡Œæ‹†ç›˜ä»»åŠ¡
        /// </summary>
        [SugarColumn(ColumnDescription = "是否拆盘任务", IsJsonKey = true)]
        public bool IsSplitPallet { get; set; }
        /// <summary>
        /// æ˜¯å¦éœ€è¦æ‰§è¡Œç»„盘任务
        /// </summary>
        [SugarColumn(ColumnDescription = "是否组盘任务", IsJsonKey = true)]
        public bool IsGroupPallet { get; set; }
        /// <summary>
        /// æœºå™¨äººå·²å¤„理的任务总数
        /// </summary>
        [SugarColumn(ColumnDescription = "已处理任务总数", IsJsonKey = true)]
        public int RobotTaskTotalNum { get; set; }
        /// <summary>
        /// æ˜¯å¦å¤„于假电芯补充模式
        /// </summary>
        [SugarColumn(ColumnDescription = "是否假电芯模式", IsJsonKey = true)]
        public bool IsInFakeBatteryMode { get; set; }
        /// <summary>
        /// å½“前批次起始编号
        /// </summary>
        [SugarColumn(ColumnDescription = "当前批次编号", IsJsonKey = true)]
        public int CurrentBatchIndex { get; set; } = 1;
        /// <summary>
        /// æ¢ç›˜ä»»åŠ¡å½“å‰é˜¶æ®µ
        /// </summary>
        [SugarColumn(ColumnDescription = "换盘阶段", IsJsonKey = true)]
        public int ChangePalletPhase { get; set; }
        /// <summary>
        /// æ˜¯å¦æ‰«ç NG
        /// </summary>
        [SugarColumn(ColumnDescription = "是否扫码NG", IsJsonKey = true)]
        public bool IsScanNG { get; set; }
        /// <summary>
        /// æ˜¯å¦ç”µèŠ¯åˆ°ä½
        /// </summary>
        [SugarColumn(ColumnDescription = "电芯是否到位", IsJsonKey = true)]
        public bool BatteryArrived { get; set; }
    }
}
```
- [ ] **Step 2: éªŒè¯æ–‡ä»¶åˆ›å»ºæˆåŠŸ**
Run: `dir "D:\Git\ShanMeiXinNengYuan\Code\WCS\WIDESEAWCS_Server\WIDESEAWCS_Model\Models\RobotState\"`
Expected: `Dt_RobotState.cs` æ–‡ä»¶å­˜åœ¨
- [ ] **Step 3: æäº¤**
```bash
git add "WCS/WIDESEAWCS_Server/WIDESEAWCS_Model/Models/RobotState/Dt_RobotState.cs"
git commit -m "feat(RobotState): æ–°å¢ž Dt_RobotState æ•°æ®åº“实体,使用 RowVersion ä¹è§‚锁"
```
---
## Task 2: æ–°å»º IRobotStateRepository æŽ¥å£
**文件:**
- æ–°å¢ž: `WCS/WIDESEAWCS_Server/WIDESEAWCS_ITaskInfoRepository/IRobotStateRepository.cs`
- [ ] **Step 1: åˆ›å»ºæŽ¥å£æ–‡ä»¶**
```csharp
using WIDESEAWCS_Model.Models;
namespace WIDESEAWCS_ITaskInfoRepository
{
    /// <summary>
    /// æœºæ¢°æ‰‹çŠ¶æ€ä»“å‚¨æŽ¥å£
    /// </summary>
    /// <remarks>
    /// å®šä¹‰æœºæ¢°æ‰‹çŠ¶æ€çš„æ•°æ®åº“è®¿é—®æ“ä½œã€‚
    /// å¤æ‚对象(RobotCrane、CurrentTask、数组等)在调用方使用强类型,
    /// åœ¨æ­¤æŽ¥å£å±‚面以 Dt_RobotState å®žä½“为操作单位。
    /// </remarks>
    public interface IRobotStateRepository
    {
        /// <summary>
        /// æ ¹æ® IP åœ°å€èŽ·å–æœºæ¢°æ‰‹çŠ¶æ€
        /// </summary>
        /// <param name="ipAddress">设备 IP åœ°å€</param>
        /// <returns>状态实体,不存在则返回 null</returns>
        Dt_RobotState? GetByIp(string ipAddress);
        /// <summary>
        /// èŽ·å–æˆ–åˆ›å»ºæœºæ¢°æ‰‹çŠ¶æ€
        /// </summary>
        /// <param name="ipAddress">设备 IP åœ°å€</param>
        /// <param name="robotCrane">机器人设备信息,用于初始化新状态</param>
        /// <returns>状态实体</returns>
        Dt_RobotState GetOrCreate(string ipAddress, RobotCraneDevice robotCrane);
        /// <summary>
        /// å®‰å…¨æ›´æ–°æœºæ¢°æ‰‹çŠ¶æ€ï¼ˆä¹è§‚é”ï¼‰
        /// </summary>
        /// <param name="ipAddress">设备 IP åœ°å€</param>
        /// <param name="newState">新状态实体(RowVersion ä¼šè¢«æ›´æ–°ï¼‰</param>
        /// <param name="expectedRowVersion">期望的行版本号(更新前的版本)</param>
        /// <returns>是否更新成功;false è¡¨ç¤ºç‰ˆæœ¬å†²çªæˆ–记录不存在</returns>
        bool TryUpdate(string ipAddress, Dt_RobotState newState, byte[] expectedRowVersion);
        /// <summary>
        /// å°† Dt_RobotState å®žä½“转换为 RobotSocketState å†…存对象
        /// </summary>
        /// <param name="entity">数据库实体</param>
        /// <returns>内存状态对象</returns>
        RobotSocketState ToSocketState(Dt_RobotState entity);
        /// <summary>
        /// å°† RobotSocketState å†…存对象转换为 Dt_RobotState å®žä½“
        /// </summary>
        /// <param name="state">内存状态对象</param>
        /// <returns>数据库实体</returns>
        Dt_RobotState ToEntity(RobotSocketState state);
    }
}
```
- [ ] **Step 2: æäº¤**
```bash
git add "WCS/WIDESEAWCS_Server/WIDESEAWCS_ITaskInfoRepository/IRobotStateRepository.cs"
git commit -m "feat(RobotState): æ–°å¢ž IRobotStateRepository æŽ¥å£"
```
---
## Task 3: æ–°å»º RobotStateRepository å®žçް
**文件:**
- æ–°å¢ž: `WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoRepository/RobotStateRepository.cs`
- [ ] **Step 1: åˆ›å»º RobotStateRepository å®žçŽ°æ–‡ä»¶**
```csharp
using Newtonsoft.Json;
using SqlSugar;
using WIDESEAWCS_Core.BaseRepository;
using WIDESEAWCS_Core.UnitOfWork;
using WIDESEAWCS_ITaskInfoRepository;
using WIDESEAWCS_Model.Models;
namespace WIDESEAWCS_TaskInfoRepository
{
    /// <summary>
    /// æœºæ¢°æ‰‹çŠ¶æ€ SqlSugar ä»“储实现
    /// </summary>
    public class RobotStateRepository : IUnitOfWork, IRobotStateRepository
    {
        private readonly IUnitOfWorkManage _unitOfWork;
        private readonly SqlSugarClient _db;
        public RobotStateRepository(IUnitOfWorkManage unitOfWork)
        {
            _unitOfWork = unitOfWork;
            _db = unitOfWork.GetDbClient();
        }
        public Dt_RobotState? GetByIp(string ipAddress)
        {
            return _db.Queryable<Dt_RobotState>()
                .Where(x => x.IPAddress == ipAddress)
                .First();
        }
        public Dt_RobotState GetOrCreate(string ipAddress, RobotCraneDevice robotCrane)
        {
            var existing = GetByIp(ipAddress);
            if (existing != null)
            {
                return existing;
            }
            var newState = new Dt_RobotState
            {
                IPAddress = ipAddress,
                RobotCraneJson = JsonConvert.SerializeObject(robotCrane),
                CreateTime = DateTime.Now,
                UpdateTime = DateTime.Now
            };
            _db.Insertable(newState).ExecuteCommand();
            return newState;
        }
        public bool TryUpdate(string ipAddress, Dt_RobotState newState, byte[] expectedRowVersion)
        {
            newState.UpdateTime = DateTime.Now;
            var affectedRows = _db.Updateable<Dt_RobotState>(newState)
                .Where(x => x.IPAddress == ipAddress)
                .WhereRowVersion(x => x.RowVersion, expectedRowVersion)
                .ExecuteCommand();
            return affectedRows > 0;
        }
        public RobotSocketState ToSocketState(Dt_RobotState entity)
        {
            var state = new RobotSocketState
            {
                IPAddress = entity.IPAddress,
                Version = BitConverter.ToInt64(entity.RowVersion.Length >= 8 ? entity.RowVersion.Take(8).ToArray() : new byte[8], 0),
                IsEventSubscribed = entity.IsEventSubscribed,
                RobotRunMode = entity.RobotRunMode,
                RobotControlMode = entity.RobotControlMode,
                RobotArmObject = entity.RobotArmObject,
                Homed = entity.Homed,
                CurrentAction = entity.CurrentAction,
                OperStatus = entity.OperStatus,
                IsSplitPallet = entity.IsSplitPallet,
                IsGroupPallet = entity.IsGroupPallet,
                RobotTaskTotalNum = entity.RobotTaskTotalNum,
                IsInFakeBatteryMode = entity.IsInFakeBatteryMode,
                CurrentBatchIndex = entity.CurrentBatchIndex,
                ChangePalletPhase = entity.ChangePalletPhase,
                IsScanNG = entity.IsScanNG,
                BatteryArrived = entity.BatteryArrived
            };
            // ååºåˆ—化复杂 JSON å­—段
            if (!string.IsNullOrEmpty(entity.RobotCraneJson))
            {
                state.RobotCrane = JsonConvert.DeserializeObject<RobotCraneDevice>(entity.RobotCraneJson);
            }
            if (!string.IsNullOrEmpty(entity.CurrentTaskJson))
            {
                state.CurrentTask = JsonConvert.DeserializeObject<Dt_RobotTask>(entity.CurrentTaskJson);
            }
            if (!string.IsNullOrEmpty(entity.LastPickPositionsJson))
            {
                state.LastPickPositions = JsonConvert.DeserializeObject<int[]>(entity.LastPickPositionsJson);
            }
            if (!string.IsNullOrEmpty(entity.LastPutPositionsJson))
            {
                state.LastPutPositions = JsonConvert.DeserializeObject<int[]>(entity.LastPutPositionsJson);
            }
            if (!string.IsNullOrEmpty(entity.CellBarcodeJson))
            {
                state.CellBarcode = JsonConvert.DeserializeObject<List<string>>(entity.CellBarcodeJson) ?? new List<string>();
            }
            return state;
        }
        public Dt_RobotState ToEntity(RobotSocketState state)
        {
            var entity = new Dt_RobotState
            {
                IPAddress = state.IPAddress,
                IsEventSubscribed = state.IsEventSubscribed,
                RobotRunMode = state.RobotRunMode,
                RobotControlMode = state.RobotControlMode,
                RobotArmObject = state.RobotArmObject,
                Homed = state.Homed,
                CurrentAction = state.CurrentAction,
                OperStatus = state.OperStatus,
                IsSplitPallet = state.IsSplitPallet,
                IsGroupPallet = state.IsGroupPallet,
                RobotTaskTotalNum = state.RobotTaskTotalNum,
                IsInFakeBatteryMode = state.IsInFakeBatteryMode,
                CurrentBatchIndex = state.CurrentBatchIndex,
                ChangePalletPhase = state.ChangePalletPhase,
                IsScanNG = state.IsScanNG,
                BatteryArrived = state.BatteryArrived
            };
            // åºåˆ—化复杂对象为 JSON
            if (state.RobotCrane != null)
            {
                entity.RobotCraneJson = JsonConvert.SerializeObject(state.RobotCrane);
            }
            if (state.CurrentTask != null)
            {
                entity.CurrentTaskJson = JsonConvert.SerializeObject(state.CurrentTask);
            }
            if (state.LastPickPositions != null)
            {
                entity.LastPickPositionsJson = JsonConvert.SerializeObject(state.LastPickPositions);
            }
            if (state.LastPutPositions != null)
            {
                entity.LastPutPositionsJson = JsonConvert.SerializeObject(state.LastPutPositions);
            }
            if (state.CellBarcode != null && state.CellBarcode.Count > 0)
            {
                entity.CellBarcodeJson = JsonConvert.SerializeObject(state.CellBarcode);
            }
            return entity;
        }
        public SqlSugarClient GetDbClient() => _db;
        public void BeginTran() => _unitOfWork.BeginTran();
        public void CommitTran() => _unitOfWork.CommitTran();
        public void RollbackTran() => _unitOfWork.RollbackTran();
    }
}
```
- [ ] **Step 2: æäº¤**
```bash
git add "WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoRepository/RobotStateRepository.cs"
git commit -m "feat(RobotState): å®žçް RobotStateRepository,封装 RowVersion ä¹è§‚锁"
```
---
## Task 4: æ”¹é€  RobotStateManager
**文件:**
- æ”¹é€ : `WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotStateManager.cs`
**变更内容:**
- å°† `ICacheService _cache` æ›¿æ¢ä¸º `IRobotStateRepository _repository`
- å°†æ‰€æœ‰ Redis æ“ä½œï¼ˆ`_cache.Get`, `_cache.GetOrAdd`, `_cache.TrySafeUpdate`)替换为数据库操作
- `TryUpdateStateSafely` æ”¹ä¸ºä¸¤æ­¥ï¼šå…ˆé€šè¿‡ `_repository.GetByIp` èŽ·å–å½“å‰å®žä½“çš„ `RowVersion`,再调用 `_repository.TryUpdate`
- `CloneState` ä¿ç•™ï¼ˆç”¨äºŽå†…存中的深拷贝,不涉及数据库)
- ç§»é™¤ `GetCacheKey` æ–¹æ³•(不再需要 Redis Key)
- ä¿ç•™åŽŸæœ‰çš„ `RobotSocketState` å¯¹è±¡ä½œä¸ºå†…å­˜ DTO,Repository å±‚负责与 `Dt_RobotState` äº’转
- [ ] **Step 1: è¯»å–当前 RobotStateManager.cs å…¨æ–‡ï¼ˆå·²åœ¨ä¸Šä¸‹æ–‡ï¼‰**
当前代码中关键变更点:
| åŽŸä»£ç  | æ›¿æ¢ä¸º |
|--------|--------|
| `ICacheService _cache` | `IRobotStateRepository _repository` |
| `RobotStateManager(ICacheService cache, ILogger logger)` | `RobotStateManager(IRobotStateRepository repository, ILogger logger)` |
| `_cache.Get<RobotSocketState>(cacheKey)` | `_repository.GetByIp(ipAddress)` ç„¶åŽ `_repository.ToSocketState(entity)` |
| `_cache.GetOrAdd(cacheKey, _ => new RobotSocketState{...})` | `_repository.GetOrCreate(ipAddress, robotCrane)` ç„¶åŽ `_repository.ToSocketState(entity)` |
| `_cache.AddObject(cacheKey, newState)` | åœ¨ `TryUpdate` çš„"不存在"分支中调用 INSERT |
| `_cache.TrySafeUpdate(cacheKey, newState, expectedVersion, s => s.Version)` | `_repository.TryUpdate(ipAddress, entity, expectedRowVersion)` |
| `GetCacheKey(ipAddress)` | ç§»é™¤ |
- [ ] **Step 2: å†™å…¥æ”¹é€ åŽçš„完整文件**
改造后的完整 `RobotStateManager.cs`:
```csharp
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using WIDESEAWCS_Common;
using WIDESEAWCS_Core.LogHelper;
using WIDESEAWCS_ITaskInfoRepository;
using WIDESEAWCS_Model.Models;
namespace WIDESEAWCS_Tasks
{
    /// <summary>
    /// æœºæ¢°æ‰‹çŠ¶æ€ç®¡ç†å™¨ - è´Ÿè´£ RobotSocketState çš„线程安全更新和克隆
    /// </summary>
    /// <remarks>
    /// æ ¸å¿ƒåŠŸèƒ½æ˜¯é€šè¿‡ IRobotStateRepository ç®¡ç†æ•°æ®åº“中的机械手状态。
    /// æä¾›ä¹è§‚并发控制,通过 RowVersion é˜²æ­¢å¹¶å‘更新时的数据覆盖问题。
    /// </remarks>
    public class RobotStateManager
    {
        /// <summary>
        /// ä»“储服务实例,用于读写数据库中的状态数据
        /// </summary>
        private readonly IRobotStateRepository _repository;
        /// <summary>
        /// æ—¥å¿—记录器
        /// </summary>
        private readonly ILogger _logger;
        /// <summary>
        /// æž„造函数
        /// </summary>
        /// <param name="repository">仓储服务实例</param>
        /// <param name="logger">日志记录器</param>
        public RobotStateManager(IRobotStateRepository repository, ILogger logger)
        {
            _repository = repository;
            _logger = logger;
        }
        /// <summary>
        /// å®‰å…¨æ›´æ–° RobotSocketState ç¼“存,防止并发覆盖
        /// </summary>
        /// <remarks>
        /// ä½¿ç”¨ä¹è§‚并发模式:先读取当前 RowVersion,执行更新时检查版本是否一致。
        /// å¦‚æžœ RowVersion ä¸åŒ¹é…ï¼ˆè¯´æ˜Žæœ‰å…¶ä»–线程已更新),则更新失败返回 false。
        /// </remarks>
        /// <param name="ipAddress">设备 IP åœ°å€</param>
        /// <param name="updateAction">更新状态的委托函数,传入当前状态副本,返回修改后的新状态</param>
        /// <returns>是否更新成功;false è¡¨ç¤ºç‰ˆæœ¬å†²çªæˆ–状态不存在</returns>
        public bool TryUpdateStateSafely(string ipAddress, Func<RobotSocketState, RobotSocketState> updateAction)
        {
            // ä»Žæ•°æ®åº“获取当前存储的状态
            var currentEntity = _repository.GetByIp(ipAddress);
            if (currentEntity == null)
            {
                return false;
            }
            // è®°å½•当前存储的 RowVersion,作为更新时的期望版本
            var expectedRowVersion = currentEntity.RowVersion;
            // åˆ›å»ºçŠ¶æ€çš„æ·±æ‹·è´å‰¯æœ¬ï¼ˆä½¿ç”¨ JSON åºåˆ—化实现)
            var stateCopy = CloneState(_repository.ToSocketState(currentEntity));
            // æ‰§è¡Œè°ƒç”¨è€…提供的更新逻辑,传入副本状态,获取新的状态对象
            var newState = updateAction(stateCopy);
            // å°†æ–°çŠ¶æ€è½¬æ¢ä¸ºæ•°æ®åº“å®žä½“
            var newEntity = _repository.ToEntity(newState);
            newEntity.RowVersion = Array.Empty<byte>(); // SqlSugar ä¼šè‡ªåŠ¨ç®¡ç†
            newEntity.Id = currentEntity.Id;
            // è°ƒç”¨ä»“储的安全更新方法,传入期望 RowVersion
            // å¦‚æžœ RowVersion ä¸ä¸€è‡´ï¼ˆå·²è¢«å…¶ä»–线程更新),则更新失败
            return _repository.TryUpdate(ipAddress, newEntity, expectedRowVersion);
        }
        /// <summary>
        /// å®‰å…¨æ›´æ–° RobotSocketState çš„重载版本(直接传入新状态)
        /// </summary>
        /// <remarks>
        /// ä¸Žä¸Šä¸€ä¸ªé‡è½½çš„区别:此方法直接接收完整的新状态对象,而不是更新委托。
        /// å¦‚果数据库中不存在该设备的状态,则创建新记录。
        /// </remarks>
        /// <param name="ipAddress">设备 IP åœ°å€</param>
        /// <param name="newState">新状态对象</param>
        /// <returns>是否更新成功;新建设置为 true</returns>
        public bool TryUpdateStateSafely(string ipAddress, RobotSocketState newState)
        {
            // ä»Žæ•°æ®åº“获取当前存储的状态
            var currentEntity = _repository.GetByIp(ipAddress);
            // å¦‚果当前不存在该设备的状态,创建新记录
            if (currentEntity == null)
            {
                var entity = _repository.ToEntity(newState);
                entity.CreateTime = DateTime.Now;
                entity.UpdateTime = DateTime.Now;
                _repository.GetOrCreate(newState.IPAddress, newState.RobotCrane ?? new RobotCraneDevice());
                _logger.LogDebug("TryUpdateStateSafely:创建新状态,IP: {IpAddress}", ipAddress);
                QuartzLogger.Debug($"创建新状态,IP: {ipAddress}", ipAddress);
                return true;
            }
            // å½“前存在状态,记录期望 RowVersion ç”¨äºŽä¹è§‚锁检查
            var expectedRowVersion = currentEntity.RowVersion;
            // å°†æ–°çŠ¶æ€è½¬æ¢ä¸ºæ•°æ®åº“å®žä½“
            var newEntity = _repository.ToEntity(newState);
            newEntity.Id = currentEntity.Id;
            newEntity.RowVersion = Array.Empty<byte>();
            // å°è¯•安全更新,如果版本冲突则返回 false
            bool success = _repository.TryUpdate(ipAddress, newEntity, expectedRowVersion);
            if (!success)
            {
                _logger.LogWarning("TryUpdateStateSafely:版本冲突,更新失败,IP: {IpAddress},期望版本字节长度: {ExpectedLength}", ipAddress, expectedRowVersion.Length);
                QuartzLogger.Warn($"版本冲突,更新失败,IP: {ipAddress}", ipAddress);
            }
            return success;
        }
        /// <summary>
        /// å…‹éš† RobotSocketState å¯¹è±¡ï¼ˆæ·±æ‹·è´ï¼‰
        /// </summary>
        /// <remarks>
        /// ä½¿ç”¨ JSON åºåˆ—化/反序列化实现深拷贝。
        /// è¿™æ ·å¯ä»¥ç¡®ä¿æ–°å¯¹è±¡ä¸ŽåŽŸå¯¹è±¡å®Œå…¨ç‹¬ç«‹ï¼Œä¿®æ”¹æ–°å¯¹è±¡ä¸ä¼šå½±å“åŽŸå¯¹è±¡ã€‚
        /// </remarks>
        /// <param name="source">源状态对象</param>
        /// <returns>新的状态对象,是源对象的深拷贝</returns>
        public RobotSocketState CloneState(RobotSocketState source)
        {
            var json = JsonConvert.SerializeObject(source);
            return JsonConvert.DeserializeObject<RobotSocketState>(json) ?? new RobotSocketState { IPAddress = source.IPAddress };
        }
        /// <summary>
        /// ä»Žæ•°æ®åº“获取机械手状态
        /// </summary>
        /// <param name="ipAddress">设备 IP åœ°å€</param>
        /// <returns>如果存在则返回状态对象,否则返回 null</returns>
        public RobotSocketState? GetState(string ipAddress)
        {
            var entity = _repository.GetByIp(ipAddress);
            return entity != null ? _repository.ToSocketState(entity) : null;
        }
        /// <summary>
        /// èŽ·å–æˆ–åˆ›å»ºæœºæ¢°æ‰‹çŠ¶æ€
        /// </summary>
        /// <remarks>
        /// å¦‚果数据库中已存在该设备的状态,直接返回。
        /// å¦‚果不存在,则创建新的状态记录并返回。
        /// </remarks>
        /// <param name="ipAddress">设备 IP åœ°å€</param>
        /// <param name="robotCrane">机器人设备信息,用于初始化新状态</param>
        /// <returns>该设备的状态对象</returns>
        public RobotSocketState GetOrCreateState(string ipAddress, RobotCraneDevice robotCrane)
        {
            var entity = _repository.GetOrCreate(ipAddress, robotCrane);
            return _repository.ToSocketState(entity);
        }
    }
}
```
- [ ] **Step 3: æäº¤**
```bash
git add "WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotStateManager.cs"
git commit -m "refactor(RobotState): å°† RobotStateManager ä»Ž Redis æ”¹ä¸ºä¾èµ–数据库仓储"
```
---
## Task 5: æ”¹é€  RobotJob
**文件:**
- æ”¹é€ : `WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotJob.cs`
**变更内容:**
- æž„造函数增加 `IRobotStateRepository` å‚æ•°
- å°† `new RobotStateManager(cache, _logger)` æ”¹ä¸º `new RobotStateManager(repository, _logger)`
- ç§»é™¤æœªä½¿ç”¨çš„ `ICacheService cache` å‚数(如果原来有)
注意:根据 `JobFactory.cs`,`RobotJob` é€šè¿‡ `IServiceProvider.GetService(bundle.JobDetail.JobType)` è§£æžï¼Œ
所有构造函数参数如果都实现了 `IDependency` æŽ¥å£æˆ–被 Autofac æ‰«æåˆ°ï¼Œå°±ä¼šè‡ªåŠ¨æ³¨å…¥ã€‚
- [ ] **Step 1: æ”¹é€  RobotJob æž„造函数**
在 `RobotJob` æž„造函数中:
**原来的构造:**(参考上文已读取的代码)
```csharp
public RobotJob(
    TcpSocketServer tcpSocket,
    IRobotTaskService robotTaskService,
    ITaskService taskService,
    ICacheService cache,
    HttpClientHelper httpClientHelper,
    ILogger<RobotJob> logger,
    IFakeBatteryPositionService fakeBatteryPositionService)
{
    _stateManager = new RobotStateManager(cache, _logger);
    // ...
}
```
**改造后:**
```csharp
public RobotJob(
    TcpSocketServer tcpSocket,
    IRobotTaskService robotTaskService,
    ITaskService taskService,
    IRobotStateRepository robotStateRepository,
    HttpClientHelper httpClientHelper,
    ILogger<RobotJob> logger,
    IFakeBatteryPositionService fakeBatteryPositionService)
{
    _stateManager = new RobotStateManager(robotStateRepository, _logger);
    // ...
}
```
同时在类成员声明处,确保 `_stateManager` ç±»åž‹æ­£ç¡®ï¼ˆå·²åœ¨ä¸Šä¸€æ­¥çš„ RobotStateManager æ”¹é€ ä¸­å®Œæˆï¼‰ã€‚
- [ ] **Step 2: æäº¤**
```bash
git add "WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotJob.cs"
git commit -m "refactor(RobotJob): æž„造函数从 ICacheService æ”¹ä¸º IRobotStateRepository"
```
---
## Task 6: DI æ³¨å†ŒéªŒè¯
**文件:**
- æ— éœ€æ–°å»ºæ–‡ä»¶ï¼Œä½†éœ€è¦éªŒè¯ AutofacModuleRegister çš„自动扫描能覆盖新增的 `RobotStateRepository`
**说明:**
`AutofacModuleRegister.Load()` ä¸­æœ‰ä»¥ä¸‹è‡ªåŠ¨æ³¨å†Œé€»è¾‘ï¼š
```csharp
builder.RegisterAssemblyTypes(assemblyList.ToArray())
    .Where(x => !x.IsInterface && !x.IsAbstract && baseType.IsAssignableFrom(x))
    .AsImplementedInterfaces()
    .PropertiesAutowired()
    .InstancePerDependency()
    .EnableInterfaceInterceptors()
    .InterceptedBy(cacheType.ToArray());
```
这意味着只要 `RobotStateRepository` å®žçŽ°äº† `IRobotStateRepository` æŽ¥å£å¹¶ä¸”:
- æ‰€åœ¨ç¨‹åºé›†è¢« `assemblyList` åŒ…含(`WIDESEAWCS_TaskInfoRepository` æ˜¯é¡¹ç›®å¼•用)
- å®žçŽ°äº† `IDependency`(`IUnitOfWorkManage` ç»§æ‰¿é“¾ä¸Šæœ‰ `IDependency`)
**不需要额外注册。** å¦‚æžœ Autofac éœ€è¦æ˜¾å¼æ³¨å†Œï¼Œåœ¨ `AutofacModuleRegister.cs` ä¸­æ·»åŠ ï¼š
```csharp
builder.RegisterType<RobotStateRepository>().As<IRobotStateRepository>().InstancePerDependency();
```
- [ ] **Step 1: éªŒè¯æž„建**
Run: `dotnet build WCS/WIDESEAWCS_Server/WIDESEAWCS_Server.sln --no-restore`
Expected: ç¼–译成功,无错误
- [ ] **Step 2: å¦‚有错误,根据错误信息调整 DI æ³¨å†Œ**
---
## Task 7: æ•´ä½“验证
- [ ] **Step 1: å®Œæ•´æž„建**
Run: `dotnet build WCS/WIDESEAWCS_Server/WIDESEAWCS_Server.sln`
Expected: BUILD SUCCEEDED
- [ ] **Step 2: æ£€æŸ¥æ˜¯å¦æœ‰é—漏的 Redis å¼•用**
在 `RobotJob/` æ–‡ä»¶å¤¹å†…搜索是否还有 `_cache` æˆ– `Redis` ç›¸å…³å¼•用:
```bash
grep -r "ICacheService\|_cache\." "WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/" --include="*.cs"
```
Expected: ä»… RobotStateManager.cs ä¸­æœ‰ `_cache` ç›¸å…³å·²æ›¿æ¢ï¼Œæ— é—漏
- [ ] **Step 3: æäº¤å…¨éƒ¨å˜æ›´**
```bash
git add -A
git commit -m "feat(RobotState): å®Œæˆ Redis→数据库迁移,IRobotStateRepository æ›¿æ¢ ICacheService"
```