# RobotState Redis → 数据库迁移设计 **日期**:2026-04-19 **状态**:已批准 ## 1. 背景与目标 将 `RobotSocketState` 的存储从 Redis 切换到 SQL Server 数据库,利用 SqlSugar 的 `RowVersion` 实现乐观并发控制。 ### 涉及范围 - `RobotJob/` 文件夹下所有使用 Redis 读写 `RobotSocketState` 的代码 - 新建 `Dt_RobotState` 数据库实体和 `IRobotStateRepository` 仓储层 ### 不涉及变更 - `RobotWorkflowOrchestrator`、`RobotTaskProcessor`、`RobotSimpleCommandHandler` 等业务逻辑不变 - 依赖 `RobotStateManager` 的调用方代码不变,只改存储后端 --- ## 2. 数据库实体设计 **表名**:`Dt_RobotState` **实体路径**:`WIDESEAWCS_Model/Models/RobotState/Dt_RobotState.cs` | 字段名 | 类型 | 说明 | |--------|------|------| | `Id` | int | 主键,自增 | | `IPAddress` | string(50) | 设备IP,唯一索引 | | `RowVersion` | byte[] | SqlSugar 行版本,并发控制 | | `IsEventSubscribed` | bool | 是否已订阅消息 | | `RobotRunMode` | int? | 运行模式(1手动 2自动) | | `RobotControlMode` | int? | 控制模式(1客户端控制 2其他) | | `RobotArmObject` | int? | 手臂抓取状态(0无物料 1有物料) | | `RobotCraneJson` | string(max) | 设备信息序列化 JSON | | `Homed` | string(50) | 回零状态(Homed/Homing) | | `CurrentAction` | string(50) | 当前动作(Picking/Putting/PickFinished 等) | | `OperStatus` | string(50) | 运行状态(Running/Pausing/Emstoping 等) | | `LastPickPositionsJson` | string(max) | 取货位置数组 JSON | | `LastPutPositionsJson` | string(max) | 放货位置数组 JSON | | `CellBarcodeJson` | string(max) | 电芯条码列表 JSON | | `CurrentTaskJson` | string(max) | 当前任务 Dt_RobotTask 序列化 JSON | | `IsSplitPallet` | bool | 是否拆盘任务 | | `IsGroupPallet` | bool | 是否组盘/换盘任务 | | `RobotTaskTotalNum` | int | 已处理任务总数 | | `IsInFakeBatteryMode` | bool | 是否假电芯补充模式 | | `CurrentBatchIndex` | int | 当前批次起始编号 | | `ChangePalletPhase` | int | 换盘任务阶段(0-5) | | `IsScanNG` | bool | 是否扫码NG | | `BatteryArrived` | bool | 电芯是否到位 | | `CreateTime` | datetime | 创建时间 | | `UpdateTime` | datetime | 最后更新时间 | **索引**:`IPAddress` 唯一索引,用于快速定位设备状态 **并发控制**:SqlSugar `RowVersion`,数据库自动递增,更新时 `WHERE RowVersion = @expected` --- ## 3. JSON 序列化字段 以下复杂对象以 JSON 字符串存储,反序列化时保持与原 `RobotSocketState` 属性完全兼容: | JSON 字段 | 对应原属性 | 反序列化类型 | |-----------|-----------|-------------| | `RobotCraneJson` | `RobotCrane` | `RobotCraneDevice` | | `CurrentTaskJson` | `CurrentTask` | `Dt_RobotTask` | | `LastPickPositionsJson` | `LastPickPositions` | `int[]` | | `LastPutPositionsJson` | `LastPutPositions` | `int[]` | | `CellBarcodeJson` | `CellBarcode` | `List` | 序列化工具:`Newtonsoft.Json`(与项目现有保持一致) --- ## 4. 架构分层 ``` 调用方(RobotJob / Workflow / Processor) ↓ 依赖 RobotStateManager ↓ 依赖 IRobotStateRepository(接口) ↓ 实现 RobotStateRepository(SqlSugar 实现) ↓ 操作 SQL Server (Dt_RobotState 表) ``` ### 4.1 IRobotStateRepository 接口 ```csharp public interface IRobotStateRepository { /// 根据 IP 获取状态,不存在返回 null Dt_RobotState? GetByIp(string ipAddress); /// 获取或创建状态(数据库无记录时创建) Dt_RobotState GetOrCreate(string ipAddress, RobotCraneDevice robotCrane); /// 安全更新(乐观锁),返回是否成功 bool TryUpdate(string ipAddress, Dt_RobotState newState, byte[] expectedRowVersion); } ``` ### 4.2 RobotStateRepository 实现要点 - 注入 `ISqlSugarClient` - `GetOrCreate`:先查,无记录则 INSERT - `TryUpdate`:执行 `UPDATE ... WHERE RowVersion = @expected`,检查影响行数 - 数组/复杂对象:在 Repository 层序列化/反序列化,对外暴露强类型属性 --- ## 5. RobotStateManager 改造 **文件**:`WIDESEAWCS_Tasks/RobotJob/RobotStateManager.cs` ### 改造内容 | 原来(Redis) | 改造后(DB) | |-------------|-------------| | `ICacheService _cache` | `IRobotStateRepository _repository` | | `GetState(ipAddress)` | `_repository.GetByIp(ipAddress)` | | `GetOrCreateState(ipAddress, robotCrane)` | `_repository.GetOrCreate(ipAddress, robotCrane)` | | `TryUpdateStateSafely(ipAddress, func)` | 内部调用 `_repository.TryUpdate`,使用 `RowVersion` 作为期望版本 | | `CloneState` | 保留(JSON 序列化深拷贝) | | `GetCacheKey(ipAddress)` | 移除(不再需要 Redis Key) | ### 构造函数变更 ```csharp // 原来 public RobotStateManager(ICacheService cache, ILogger logger) // 改造后 public RobotStateManager(IRobotStateRepository repository, ILogger logger) ``` --- ## 6. RobotJob 构造函数改造 **文件**:`WIDESEAWCS_Tasks/RobotJob/RobotJob.cs` ```csharp // 原来 _stateManager = new RobotStateManager(cache, _logger); // 改造后:需要通过 DI 注入 IRobotStateRepository _stateManager = new RobotStateManager( ResolvedInstances.FirstOrDefault(typeof(IRobotStateRepository)) as IRobotStateRepository, _logger); ``` **备选方案**:如果 DI 容器在 Job 构造时不便解析,可通过方法参数注入 `IRobotStateRepository`。 --- ## 7. 文件变更清单 | 操作 | 文件路径 | |------|---------| | 新增 | `WIDESEAWCS_Model/Models/RobotState/Dt_RobotState.cs` | | 新增 | `WIDESEAWCS_ITaskInfoRepository/IRobotStateRepository.cs` | | 新增 | `WIDESEAWCS_ITaskInfoRepository/RobotStateRepository.cs` | | 改造 | `WIDESEAWCS_Tasks/RobotJob/RobotStateManager.cs` | | 改造 | `WIDESEAWCS_Tasks/RobotJob/RobotJob.cs` | | 改造 | `WIDESEAWCS_Tasks/RobotJob/RobotSocketState.cs`(移除或保留为 DTO?) | **说明**:`RobotSocketState.cs` 建议保留作为内存中的状态对象(DTO),在 Repository 层做实体转换。业务层继续使用 `RobotSocketState`,Repository 层负责与 `Dt_RobotState` 互转。 --- ## 8. 依赖注入注册 在 `AutofacModuleRegister` 或对应 DI 配置中注册: ```csharp builder.RegisterType().As().InstancePerDependency(); ``` --- ## 9. 迁移步骤(实施计划) 1. **新建 `Dt_RobotState` 实体类** 2. **新建 `IRobotStateRepository` 接口和 `RobotStateRepository` 实现** 3. **改造 `RobotStateManager`**:依赖 Repository,替换 Redis 调用 4. **改造 `RobotJob`**:注入 Repository 到 StateManager 5. **更新 `RobotSocketState`**:作为内存 DTO 保留,或与实体合并(待定) 6. **配置 DI 注册** 7. **测试验证**:确保并发更新、状态流转逻辑与原来一致