From 108f83653dab9ec49fbad1f1ed688f9b89d44df7 Mon Sep 17 00:00:00 2001
From: wanshenmean <cathay_xy@163.com>
Date: 星期日, 19 四月 2026 19:21:08 +0800
Subject: [PATCH] docs: 添加 RobotState Redis→数据库迁移实施计划
---
Code/docs/superpowers/plans/2026-04-19-robot-state-redis-to-db-plan.md | 804 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 804 insertions(+), 0 deletions(-)
diff --git a/Code/docs/superpowers/plans/2026-04-19-robot-state-redis-to-db-plan.md b/Code/docs/superpowers/plans/2026-04-19-robot-state-redis-to-db-plan.md
new file mode 100644
index 0000000..373c00d
--- /dev/null
+++ b/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 瀹炵幇涔愯骞跺彂鎺у埗銆�
+ /// 澶嶆潅瀵硅薄锛圧obotCrane銆丆urrentTask銆佹暟缁勭瓑锛変互 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 = "鏈烘鎵婭P鍦板潃", 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>
+ /// 鏈烘鎵嬭澶囧熀纭�淇℃伅锛圝SON 搴忓垪鍖栵級
+ /// </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>
+ /// 鏈�杩戜竴娆″彇璐у畬鎴愮殑浣嶇疆鏁扮粍锛圝SON锛�
+ /// </summary>
+ [SugarColumn(Length = 500, ColumnDescription = "鍙栬揣浣嶇疆鏁扮粍JSON", IsNullable = true, IsJsonKey = true)]
+ public string? LastPickPositionsJson { get; set; }
+
+ /// <summary>
+ /// 鏈�杩戜竴娆℃斁璐у畬鎴愮殑浣嶇疆鏁扮粍锛圝SON锛�
+ /// </summary>
+ [SugarColumn(Length = 500, ColumnDescription = "鏀捐揣浣嶇疆鏁扮粍JSON", IsNullable = true, IsJsonKey = true)]
+ public string? LastPutPositionsJson { get; set; }
+
+ /// <summary>
+ /// 鐢垫睜/璐т綅鏉$爜鍒楄〃锛圝SON锛�
+ /// </summary>
+ [SugarColumn(Length = 2000, ColumnDescription = "鐢佃姱鏉$爜鍒楄〃JSON", IsNullable = true, IsJsonKey = true)]
+ public string? CellBarcodeJson { get; set; }
+
+ /// <summary>
+ /// 鏈烘鎵嬪綋鍓嶆鍦ㄦ墽琛岀殑浠诲姟锛圝SON 搴忓垪鍖栵級
+ /// </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>
+ /// 瀹氫箟鏈烘鎵嬬姸鎬佺殑鏁版嵁搴撹闂搷浣溿��
+ /// 澶嶆潅瀵硅薄锛圧obotCrane銆丆urrentTask銆佹暟缁勭瓑锛夊湪璋冪敤鏂逛娇鐢ㄥ己绫诲瀷锛�
+ /// 鍦ㄦ鎺ュ彛灞傞潰浠� 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>鏄惁鏇存柊鎴愬姛锛沠alse 琛ㄧず鐗堟湰鍐茬獊鎴栬褰曚笉瀛樺湪</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锛孯epository 灞傝礋璐d笌 `Dt_RobotState` 浜掕浆
+
+- [ ] **Step 1: 璇诲彇褰撳墠 RobotStateManager.cs 鍏ㄦ枃锛堝凡鍦ㄤ笂涓嬫枃锛�**
+
+褰撳墠浠g爜涓叧閿彉鏇寸偣锛�
+
+| 鍘熶唬鐮� | 鏇挎崲涓� |
+|--------|--------|
+| `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>鏄惁鏇存柊鎴愬姛锛沠alse 琛ㄧず鐗堟湰鍐茬獊鎴栫姸鎬佷笉瀛樺湪</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锛氱増鏈啿绐侊紝鏇存柊澶辫触锛孖P: {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)` 瑙f瀽锛�
+鎵�鏈夋瀯閫犲嚱鏁板弬鏁板鏋滈兘瀹炵幇浜� `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` 绫诲瀷姝g‘锛堝凡鍦ㄤ笂涓�姝ョ殑 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鈫掓暟鎹簱杩佺Щ锛孖RobotStateRepository 鏇挎崲 ICacheService"
+```
--
Gitblit v1.9.3