# 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"
```