# 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 { /// /// 机械手状态数据库实体 /// /// /// 对应数据库表 Dt_RobotState,使用 RowVersion 实现乐观并发控制。 /// 复杂对象(RobotCrane、CurrentTask、数组等)以 JSON 字符串存储。 /// [SugarTable(nameof(Dt_RobotState), "机械手状态表")] public class Dt_RobotState : BaseEntity { /// /// 主键 ID /// [SugarColumn(IsPrimaryKey = true, IsIdentity = true, ColumnDescription = "主键ID")] public int Id { get; set; } /// /// 机械手 IP 地址,唯一索引 /// [SugarColumn(Length = 50, ColumnDescription = "机械手IP地址", IsJsonKey = true)] public string IPAddress { get; set; } = string.Empty; /// /// 行版本,用于乐观并发控制 /// /// /// SqlSugar 会自动管理此字段,每次更新时数据库自动递增。 /// 更新时 WHERE RowVersion = @expectedRowVersion,检查影响行数判断是否冲突。 /// [SugarColumn(ColumnDescription = "行版本(乐观锁)", IsJsonKey = true)] public byte[] RowVersion { get; set; } = Array.Empty(); /// /// 是否已订阅消息事件 /// [SugarColumn(ColumnDescription = "是否已订阅消息事件", IsJsonKey = true)] public bool IsEventSubscribed { get; set; } /// /// 机械手运行模式 /// /// 1: 手动模式, 2: 自动模式 [SugarColumn(ColumnDescription = "运行模式", IsNullable = true, IsJsonKey = true)] public int? RobotRunMode { get; set; } /// /// 机械手控制模式 /// /// 1: 客户端控制, 2: 其他 [SugarColumn(ColumnDescription = "控制模式", IsNullable = true, IsJsonKey = true)] public int? RobotControlMode { get; set; } /// /// 机械手手臂抓取对象状态 /// /// 0: 无物料(手臂空闲), 1: 有物料(已抓取货物) [SugarColumn(ColumnDescription = "手臂抓取状态", IsNullable = true, IsJsonKey = true)] public int? RobotArmObject { get; set; } /// /// 机械手设备基础信息(JSON 序列化) /// [SugarColumn(Length = 2000, ColumnDescription = "设备信息JSON", IsJsonKey = true)] public string RobotCraneJson { get; set; } = string.Empty; /// /// 机械手初始化完成回到待机位状态 /// /// Possible values: "Homed", "Homing" [SugarColumn(Length = 50, ColumnDescription = "回零状态", IsNullable = true, IsJsonKey = true)] public string? Homed { get; set; } /// /// 机械手当前正在执行的动作 /// [SugarColumn(Length = 50, ColumnDescription = "当前动作", IsNullable = true, IsJsonKey = true)] public string? CurrentAction { get; set; } /// /// 机械手当前运行状态 /// [SugarColumn(Length = 50, ColumnDescription = "运行状态", IsNullable = true, IsJsonKey = true)] public string? OperStatus { get; set; } /// /// 最近一次取货完成的位置数组(JSON) /// [SugarColumn(Length = 500, ColumnDescription = "取货位置数组JSON", IsNullable = true, IsJsonKey = true)] public string? LastPickPositionsJson { get; set; } /// /// 最近一次放货完成的位置数组(JSON) /// [SugarColumn(Length = 500, ColumnDescription = "放货位置数组JSON", IsNullable = true, IsJsonKey = true)] public string? LastPutPositionsJson { get; set; } /// /// 电池/货位条码列表(JSON) /// [SugarColumn(Length = 2000, ColumnDescription = "电芯条码列表JSON", IsNullable = true, IsJsonKey = true)] public string? CellBarcodeJson { get; set; } /// /// 机械手当前正在执行的任务(JSON 序列化) /// [SugarColumn(Length = 2000, ColumnDescription = "当前任务JSON", IsNullable = true, IsJsonKey = true)] public string? CurrentTaskJson { get; set; } /// /// 是否需要执行拆盘任务 /// [SugarColumn(ColumnDescription = "是否拆盘任务", IsJsonKey = true)] public bool IsSplitPallet { get; set; } /// /// 是否需要执行组盘任务 /// [SugarColumn(ColumnDescription = "是否组盘任务", IsJsonKey = true)] public bool IsGroupPallet { get; set; } /// /// 机器人已处理的任务总数 /// [SugarColumn(ColumnDescription = "已处理任务总数", IsJsonKey = true)] public int RobotTaskTotalNum { get; set; } /// /// 是否处于假电芯补充模式 /// [SugarColumn(ColumnDescription = "是否假电芯模式", IsJsonKey = true)] public bool IsInFakeBatteryMode { get; set; } /// /// 当前批次起始编号 /// [SugarColumn(ColumnDescription = "当前批次编号", IsJsonKey = true)] public int CurrentBatchIndex { get; set; } = 1; /// /// 换盘任务当前阶段 /// [SugarColumn(ColumnDescription = "换盘阶段", IsJsonKey = true)] public int ChangePalletPhase { get; set; } /// /// 是否扫码NG /// [SugarColumn(ColumnDescription = "是否扫码NG", IsJsonKey = true)] public bool IsScanNG { get; set; } /// /// 是否电芯到位 /// [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 { /// /// 机械手状态仓储接口 /// /// /// 定义机械手状态的数据库访问操作。 /// 复杂对象(RobotCrane、CurrentTask、数组等)在调用方使用强类型, /// 在此接口层面以 Dt_RobotState 实体为操作单位。 /// public interface IRobotStateRepository { /// /// 根据 IP 地址获取机械手状态 /// /// 设备 IP 地址 /// 状态实体,不存在则返回 null Dt_RobotState? GetByIp(string ipAddress); /// /// 获取或创建机械手状态 /// /// 设备 IP 地址 /// 机器人设备信息,用于初始化新状态 /// 状态实体 Dt_RobotState GetOrCreate(string ipAddress, RobotCraneDevice robotCrane); /// /// 安全更新机械手状态(乐观锁) /// /// 设备 IP 地址 /// 新状态实体(RowVersion 会被更新) /// 期望的行版本号(更新前的版本) /// 是否更新成功;false 表示版本冲突或记录不存在 bool TryUpdate(string ipAddress, Dt_RobotState newState, byte[] expectedRowVersion); /// /// 将 Dt_RobotState 实体转换为 RobotSocketState 内存对象 /// /// 数据库实体 /// 内存状态对象 RobotSocketState ToSocketState(Dt_RobotState entity); /// /// 将 RobotSocketState 内存对象转换为 Dt_RobotState 实体 /// /// 内存状态对象 /// 数据库实体 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 { /// /// 机械手状态 SqlSugar 仓储实现 /// 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() .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(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(entity.RobotCraneJson); } if (!string.IsNullOrEmpty(entity.CurrentTaskJson)) { state.CurrentTask = JsonConvert.DeserializeObject(entity.CurrentTaskJson); } if (!string.IsNullOrEmpty(entity.LastPickPositionsJson)) { state.LastPickPositions = JsonConvert.DeserializeObject(entity.LastPickPositionsJson); } if (!string.IsNullOrEmpty(entity.LastPutPositionsJson)) { state.LastPutPositions = JsonConvert.DeserializeObject(entity.LastPutPositionsJson); } if (!string.IsNullOrEmpty(entity.CellBarcodeJson)) { state.CellBarcode = JsonConvert.DeserializeObject>(entity.CellBarcodeJson) ?? new List(); } 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(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 { /// /// 机械手状态管理器 - 负责 RobotSocketState 的线程安全更新和克隆 /// /// /// 核心功能是通过 IRobotStateRepository 管理数据库中的机械手状态。 /// 提供乐观并发控制,通过 RowVersion 防止并发更新时的数据覆盖问题。 /// public class RobotStateManager { /// /// 仓储服务实例,用于读写数据库中的状态数据 /// private readonly IRobotStateRepository _repository; /// /// 日志记录器 /// private readonly ILogger _logger; /// /// 构造函数 /// /// 仓储服务实例 /// 日志记录器 public RobotStateManager(IRobotStateRepository repository, ILogger logger) { _repository = repository; _logger = logger; } /// /// 安全更新 RobotSocketState 缓存,防止并发覆盖 /// /// /// 使用乐观并发模式:先读取当前 RowVersion,执行更新时检查版本是否一致。 /// 如果 RowVersion 不匹配(说明有其他线程已更新),则更新失败返回 false。 /// /// 设备 IP 地址 /// 更新状态的委托函数,传入当前状态副本,返回修改后的新状态 /// 是否更新成功;false 表示版本冲突或状态不存在 public bool TryUpdateStateSafely(string ipAddress, Func 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(); // SqlSugar 会自动管理 newEntity.Id = currentEntity.Id; // 调用仓储的安全更新方法,传入期望 RowVersion // 如果 RowVersion 不一致(已被其他线程更新),则更新失败 return _repository.TryUpdate(ipAddress, newEntity, expectedRowVersion); } /// /// 安全更新 RobotSocketState 的重载版本(直接传入新状态) /// /// /// 与上一个重载的区别:此方法直接接收完整的新状态对象,而不是更新委托。 /// 如果数据库中不存在该设备的状态,则创建新记录。 /// /// 设备 IP 地址 /// 新状态对象 /// 是否更新成功;新建设置为 true 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(); // 尝试安全更新,如果版本冲突则返回 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; } /// /// 克隆 RobotSocketState 对象(深拷贝) /// /// /// 使用 JSON 序列化/反序列化实现深拷贝。 /// 这样可以确保新对象与原对象完全独立,修改新对象不会影响原对象。 /// /// 源状态对象 /// 新的状态对象,是源对象的深拷贝 public RobotSocketState CloneState(RobotSocketState source) { var json = JsonConvert.SerializeObject(source); return JsonConvert.DeserializeObject(json) ?? new RobotSocketState { IPAddress = source.IPAddress }; } /// /// 从数据库获取机械手状态 /// /// 设备 IP 地址 /// 如果存在则返回状态对象,否则返回 null public RobotSocketState? GetState(string ipAddress) { var entity = _repository.GetByIp(ipAddress); return entity != null ? _repository.ToSocketState(entity) : null; } /// /// 获取或创建机械手状态 /// /// /// 如果数据库中已存在该设备的状态,直接返回。 /// 如果不存在,则创建新的状态记录并返回。 /// /// 设备 IP 地址 /// 机器人设备信息,用于初始化新状态 /// 该设备的状态对象 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 logger, IFakeBatteryPositionService fakeBatteryPositionService) { _stateManager = new RobotStateManager(cache, _logger); // ... } ``` **改造后:** ```csharp public RobotJob( TcpSocketServer tcpSocket, IRobotTaskService robotTaskService, ITaskService taskService, IRobotStateRepository robotStateRepository, HttpClientHelper httpClientHelper, ILogger 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().As().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" ```