| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # 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 å®ç°ä¹è§å¹¶åæ§å¶ã |
| | | /// å¤æå¯¹è±¡ï¼RobotCraneãCurrentTaskãæ°ç»çï¼ä»¥ 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 = "æºæ¢°æIPå°å", 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> |
| | | /// æºæ¢°æè®¾å¤åºç¡ä¿¡æ¯ï¼JSON åºååï¼ |
| | | /// </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> |
| | | /// æè¿ä¸æ¬¡åè´§å®æçä½ç½®æ°ç»ï¼JSONï¼ |
| | | /// </summary> |
| | | [SugarColumn(Length = 500, ColumnDescription = "åè´§ä½ç½®æ°ç»JSON", IsNullable = true, IsJsonKey = true)] |
| | | public string? LastPickPositionsJson { get; set; } |
| | | |
| | | /// <summary> |
| | | /// æè¿ä¸æ¬¡æ¾è´§å®æçä½ç½®æ°ç»ï¼JSONï¼ |
| | | /// </summary> |
| | | [SugarColumn(Length = 500, ColumnDescription = "æ¾è´§ä½ç½®æ°ç»JSON", IsNullable = true, IsJsonKey = true)] |
| | | public string? LastPutPositionsJson { get; set; } |
| | | |
| | | /// <summary> |
| | | /// çµæ± /è´§ä½æ¡ç å表ï¼JSONï¼ |
| | | /// </summary> |
| | | [SugarColumn(Length = 2000, ColumnDescription = "çµè¯æ¡ç å表JSON", IsNullable = true, IsJsonKey = true)] |
| | | public string? CellBarcodeJson { get; set; } |
| | | |
| | | /// <summary> |
| | | /// æºæ¢°æå½åæ£å¨æ§è¡çä»»å¡ï¼JSON åºååï¼ |
| | | /// </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> |
| | | /// å®ä¹æºæ¢°æç¶æçæ°æ®åºè®¿é®æä½ã |
| | | /// å¤æå¯¹è±¡ï¼RobotCraneãCurrentTaskãæ°ç»çï¼å¨è°ç¨æ¹ä½¿ç¨å¼ºç±»åï¼ |
| | | /// 卿¤æ¥å£å±é¢ä»¥ 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>æ¯å¦æ´æ°æåï¼false è¡¨ç¤ºçæ¬å²çªæè®°å½ä¸åå¨</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ï¼Repository å±è´è´£ä¸ `Dt_RobotState` äºè½¬ |
| | | |
| | | - [ ] **Step 1: 读åå½å RobotStateManager.cs å
¨æï¼å·²å¨ä¸ä¸æï¼** |
| | | |
| | | å½å代ç ä¸å
³é®åæ´ç¹ï¼ |
| | | |
| | | | å代ç | æ¿æ¢ä¸º | |
| | | |--------|--------| |
| | | | `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>æ¯å¦æ´æ°æåï¼false è¡¨ç¤ºçæ¬å²çªæç¶æä¸åå¨</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ï¼çæ¬å²çªï¼æ´æ°å¤±è´¥ï¼IP: {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)` è§£æï¼ |
| | | æææé 彿°åæ°å¦æé½å®ç°äº `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` ç±»åæ£ç¡®ï¼å·²å¨ä¸ä¸æ¥ç 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âæ°æ®åºè¿ç§»ï¼IRobotStateRepository æ¿æ¢ ICacheService" |
| | | ``` |