wanshenmean
20 小时以前 165d9665316da78056a85a12da8ae56e7fe51b0a
docs: 添加 RobotState Redis→数据库迁移设计文档

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
已添加1个文件
195 ■■■■■ 文件已修改
Code/docs/superpowers/specs/2026-04-19-robot-state-redis-to-db-design.md 195 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/docs/superpowers/specs/2026-04-19-robot-state-redis-to-db-design.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,195 @@
# 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<string>` |
序列化工具:`Newtonsoft.Json`(与项目现有保持一致)
---
## 4. æž¶æž„分层
```
调用方(RobotJob / Workflow / Processor)
        â†“ ä¾èµ–
RobotStateManager
        â†“ ä¾èµ–
IRobotStateRepository(接口)
        â†“ å®žçް
RobotStateRepository(SqlSugar å®žçŽ°ï¼‰
        â†“ æ“ä½œ
SQL Server (Dt_RobotState è¡¨)
```
### 4.1 IRobotStateRepository æŽ¥å£
```csharp
public interface IRobotStateRepository
{
    /// <summary>根据 IP èŽ·å–çŠ¶æ€ï¼Œä¸å­˜åœ¨è¿”å›ž null</summary>
    Dt_RobotState? GetByIp(string ipAddress);
    /// <summary>获取或创建状态(数据库无记录时创建)</summary>
    Dt_RobotState GetOrCreate(string ipAddress, RobotCraneDevice robotCrane);
    /// <summary>安全更新(乐观锁),返回是否成功</summary>
    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<RobotStateRepository>().As<IRobotStateRepository>().InstancePerDependency();
```
---
## 9. è¿ç§»æ­¥éª¤ï¼ˆå®žæ–½è®¡åˆ’)
1. **新建 `Dt_RobotState` å®žä½“ç±»**
2. **新建 `IRobotStateRepository` æŽ¥å£å’Œ `RobotStateRepository` å®žçް**
3. **改造 `RobotStateManager`**:依赖 Repository,替换 Redis è°ƒç”¨
4. **改造 `RobotJob`**:注入 Repository åˆ° StateManager
5. **更新 `RobotSocketState`**:作为内存 DTO ä¿ç•™ï¼Œæˆ–与实体合并(待定)
6. **配置 DI æ³¨å†Œ**
7. **测试验证**:确保并发更新、状态流转逻辑与原来一致