From 2a157fc570f544e5912f4ef83cef0a403af86f8f Mon Sep 17 00:00:00 2001
From: wanshenmean <cathay_xy@163.com>
Date: 星期三, 11 三月 2026 11:13:25 +0800
Subject: [PATCH] docs: 添加完整的WCS DDD重构实施计划
---
Code/WCS/WIDESEAWCS_Server/docs/superpowers/plans/2026-03-11-wcs-ddd-refactor-plan.md | 1229 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 1,229 insertions(+), 0 deletions(-)
diff --git a/Code/WCS/WIDESEAWCS_Server/docs/superpowers/plans/2026-03-11-wcs-ddd-refactor-plan.md b/Code/WCS/WIDESEAWCS_Server/docs/superpowers/plans/2026-03-11-wcs-ddd-refactor-plan.md
new file mode 100644
index 0000000..d1c08c5
--- /dev/null
+++ b/Code/WCS/WIDESEAWCS_Server/docs/superpowers/plans/2026-03-11-wcs-ddd-refactor-plan.md
@@ -0,0 +1,1229 @@
+# WCS DDD閲嶆瀯瀹炴柦璁″垝
+
+> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**鐩爣:** 瀹炵幇WCS绯荤粺鐨勬笎杩涘紡DDD閲嶆瀯锛屽寘鎷鍩熷眰銆佸簲鐢ㄥ眰鍜屽熀纭�璁炬柦灞傦紝閲嶇偣閲嶆瀯璁惧绠$悊棰嗗煙
+
+**鏋舵瀯:** 閲囩敤鏍囧噯鐨勫洓灞侱DD鏋舵瀯锛氳〃鐜板眰 鈫� 搴旂敤灞� 鈫� 棰嗗煙灞� 鈫� 鍩虹璁炬柦灞傦紝浣跨敤CQRS妯″紡鍒嗙鍛戒护鍜屾煡璇紝棰嗗煙浜嬩欢椹卞姩璁捐
+
+**鎶�鏈爤:**
+- .NET 6.0
+- SqlSugar ORM
+- Redis锛圫tackExchange.Redis锛�
+- xUnit + Moq锛堟祴璇曟鏋讹級
+- Autofac锛堜緷璧栨敞鍏ワ級
+
+---
+
+## Chunk 1: 椤圭洰缁撴瀯鎼缓鍜屽熀纭�璁炬柦绫�
+
+### Task 1: 鍒涘缓棰嗗煙灞傞」鐩粨鏋�
+
+**Files:**
+- Create: `WIDESEAWCS_Domain/WIDESEAWCS_Domain.csproj`
+- Create: `WIDESEAWCS_Domain/Common/AggregateRoot.cs`
+- Create: `WIDESEAWCS_Domain/Common/IDomainEvent.cs`
+- Create: `WIDESEAWCS_Domain/Common/DomainEvent.cs`
+- Create: `WIDESEAWCS_Domain/Common/DomainException.cs`
+- Test: `WIDESEAWCS_Domain.Tests/AggregateRootTests.cs`
+
+- [ ] **Step 1: 鍒涘缓棰嗗煙灞傞」鐩枃浠�**
+
+```xml
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <TargetFramework>net6.0</TargetFramework>
+ <ImplicitUsings>enable</ImplicitUsings>
+ <Nullable>enable</Nullable>
+ </PropertyGroup>
+</Project>
+```
+
+- [ ] **Step 2: 鍒涘缓鑱氬悎鏍瑰熀绫�**
+
+```csharp
+namespace WIDESEAWCS_Domain.Common;
+
+public abstract class AggregateRoot<TId>
+{
+ public TId Id { get; protected set; } = default!;
+ public int Version { get; private set; }
+ private readonly List<IDomainEvent> _domainEvents = new();
+
+ protected AggregateRoot() { }
+
+ protected AggregateRoot(TId id)
+ {
+ Id = id;
+ }
+
+ public IReadOnlyCollection<IDomainEvent> GetDomainEvents() => _domainEvents.AsReadOnly();
+ public void ClearDomainEvents() => _domainEvents.Clear();
+
+ protected void AddDomainEvent(IDomainEvent domainEvent)
+ {
+ _domainEvents.Add(domainEvent);
+ }
+
+ protected void IncrementVersion()
+ {
+ Version++;
+ }
+}
+```
+
+- [ ] **Step 3: 鍒涘缓棰嗗煙浜嬩欢鎺ュ彛**
+
+```csharp
+namespace WIDESEAWCS_Domain.Common;
+
+public interface IDomainEvent
+{
+ DateTime OccurredOn { get; }
+}
+```
+
+- [ ] **Step 4: 鍒涘缓棰嗗煙浜嬩欢鍩虹被**
+
+```csharp
+namespace WIDESEAWCS_Domain.Common;
+
+public abstract record DomainEvent : IDomainEvent
+{
+ public DateTime OccurredOn { get; init; } = DateTime.UtcNow;
+}
+```
+
+- [ ] **Step 5: 鍒涘缓棰嗗煙寮傚父绫�**
+
+```csharp
+namespace WIDESEAWCS_Domain.Common;
+
+public abstract class DomainException : Exception
+{
+ public string ErrorCode { get; }
+
+ protected DomainException(string message, string errorCode = "DOMAIN_ERROR")
+ : base(message)
+ {
+ ErrorCode = errorCode;
+ }
+}
+
+public class InvalidDomainOperationException : DomainException
+{
+ public InvalidDomainOperationException(string message)
+ : base(message, "INVALID_OPERATION")
+ {
+ }
+}
+```
+
+- [ ] **Step 6: 鍒涘缓鑱氬悎鏍瑰熀绫绘祴璇�**
+
+```csharp
+using Xunit;
+using WIDESEAWCS_Domain.Common;
+
+namespace WIDESEAWCS_Domain.Tests;
+
+public class AggregateRootTests
+{
+ [Fact]
+ public void Constructor_Should_SetId()
+ {
+ // Arrange & Act
+ var aggregate = new TestAggregate(1);
+
+ // Assert
+ Assert.Equal(1, aggregate.Id);
+ }
+
+ [Fact]
+ public void AddDomainEvent_Should_AddEvent()
+ {
+ // Arrange
+ var aggregate = new TestAggregate(1);
+ var domainEvent = new TestDomainEvent();
+
+ // Act
+ aggregate.TestAddDomainEvent(domainEvent);
+
+ // Assert
+ var events = aggregate.GetDomainEvents();
+ Assert.Single(events);
+ Assert.Same(domainEvent, events.First());
+ }
+
+ [Fact]
+ public void ClearDomainEvents_Should_RemoveAllEvents()
+ {
+ // Arrange
+ var aggregate = new TestAggregate(1);
+ aggregate.TestAddDomainEvent(new TestDomainEvent());
+
+ // Act
+ aggregate.ClearDomainEvents();
+
+ // Assert
+ Assert.Empty(aggregate.GetDomainEvents());
+ }
+
+ [Fact]
+ public void IncrementVersion_Should_IncreaseVersion()
+ {
+ // Arrange
+ var aggregate = new TestAggregate(1);
+ var initialVersion = aggregate.Version;
+
+ // Act
+ aggregate.TestIncrementVersion();
+
+ // Assert
+ Assert.Equal(initialVersion + 1, aggregate.Version);
+ }
+
+ // 娴嬭瘯杈呭姪绫�
+ private class TestAggregate : AggregateRoot<int>
+ {
+ public TestAggregate(int id) : base(id) { }
+
+ public void TestAddDomainEvent(IDomainEvent domainEvent)
+ => AddDomainEvent(domainEvent);
+
+ public void TestIncrementVersion() => IncrementVersion();
+ }
+
+ private record TestDomainEvent : DomainEvent { }
+}
+```
+
+- [ ] **Step 7: 杩愯娴嬭瘯楠岃瘉鍩虹绫�**
+
+Run: `dotnet test WIDESEAWCS_Domain.Tests --filter "FullyQualifiedName~AggregateRootTests"`
+Expected: All tests pass
+
+- [ ] **Step 8: 鎻愪氦鍩虹绫�**
+
+```bash
+git add WIDESEAWCS_Domain/
+git commit -m "feat: 娣诲姞DDD鍩虹绫� - AggregateRoot, IDomainEvent, DomainException"
+```
+
+---
+
+### Task 2: 鍒涘缓搴旂敤灞傞」鐩粨鏋�
+
+**Files:**
+- Create: `WIDESEAWCS_Application/WIDESEAWCS_Application.csproj`
+- Create: `WIDESEAWCS_Application/Common/IRepository.cs`
+- Create: `WIDESEAWCS_Application/Common/IUnitOfWork.cs`
+
+- [ ] **Step 1: 鍒涘缓搴旂敤灞傞」鐩枃浠�**
+
+```xml
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <TargetFramework>net6.0</TargetFramework>
+ <ImplicitUsings>enable</ImplicitUsings>
+ <Nullable>enable</Nullable>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\WIDESEAWCS_Domain\WIDESEAWCS_Domain.csproj" />
+ </ItemGroup>
+</Project>
+```
+
+- [ ] **Step 2: 鍒涘缓浠撳偍鍩烘帴鍙�**
+
+```csharp
+using WIDESEAWCS_Domain.Common;
+
+namespace WIDESEAWCS_Application.Common;
+
+public interface IRepository<TEntity, TId> where TEntity : AggregateRoot<TId>
+{
+ Task<TEntity?> GetById(TId id);
+ Task Add(TEntity entity);
+ Task Update(TEntity entity);
+ Task Delete(TId id);
+}
+```
+
+- [ ] **Step 3: 鍒涘缓宸ヤ綔鍗曞厓鎺ュ彛**
+
+```csharp
+namespace WIDESEAWCS_Application.Common;
+
+public interface IUnitOfWork : IDisposable
+{
+ ITransaction BeginTransaction();
+ Task Commit();
+ Task Rollback();
+}
+
+public interface ITransaction : IDisposable
+{
+ Task Commit();
+ Task Rollback();
+}
+```
+
+- [ ] **Step 4: 鎻愪氦搴旂敤灞傚熀纭�鎺ュ彛**
+
+```bash
+git add WIDESEAWCS_Application/
+git commit -m "feat: 娣诲姞搴旂敤灞傚熀纭�鎺ュ彛 - IRepository, IUnitOfWork"
+```
+
+---
+
+### Task 3: 鍒涘缓鍩虹璁炬柦灞傞」鐩粨鏋�
+
+**Files:**
+- Create: `WIDESEAWCS_Infrastructure/WIDESEAWCS_Infrastructure.csproj`
+- Create: `WIDESEAWCS_Infrastructure/Persistence/InMemoryUnitOfWork.cs`
+- Create: `WIDESEAWCS_Infrastructure/Persistence/InMemoryTransaction.cs`
+
+- [ ] **Step 1: 鍒涘缓**鍩虹璁炬柦灞傞」鐩枃浠�**
+
+```xml
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <TargetFramework>net6.0</TargetFramework>
+ <ImplicitUsings>enable</ImplicitUsings>
+ <Nullable>enable</Nullable>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\WIDESEAWCS_Domain\WIDESEAWCS_Domain.csproj" />
+ <ProjectReference Include="..\WIDESEAWCS_Application\WIDESEAWCS_Application.csproj" />
+ <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
+ </ItemGroup>
+</Project>
+```
+
+- [ ] **Step 2: 鍒涘缓鍐呭瓨宸ヤ綔鍗曞厓瀹炵幇**
+
+```csharp
+using WIDESEAWCS_Application.Common;
+
+namespace WIDESEAWCS_Infrastructure.Persistence;
+
+public class InMemoryUnitOfWork : IUnitOfWork
+{
+ private readonly List<Action> _operations = new();
+
+ public ITransaction BeginTransaction()
+ {
+ return new InMemoryTransaction(this);
+ }
+
+ public Task Commit()
+ {
+ foreach (var operation in _operations)
+ {
+ operation();
+ }
+ _operations.Clear();
+ return Task.CompletedTask;
+ }
+
+ public Task Rollback()
+ {
+ _operations.Clear();
+ return Task.CompletedTask;
+ }
+
+ public void Dispose()
+ {
+ _operations.Clear();
+ }
+}
+```
+
+- [ ] **Step 3: 鍒涘缓鍐呭瓨浜嬪姟瀹炵幇**
+
+```csharp
+using WIDESEAWCS_Application.Common;
+
+namespace WIDESEAWCS_Infrastructure.Persistence;
+
+public class InMemoryTransaction : ITransaction
+{
+ private readonly InMemoryUnitOfWork _unitOfWork;
+ private bool _committed;
+ private bool _rolledBack;
+
+ public InMemoryTransaction(InMemoryUnitOfWork unitOfWork)
+ {
+ _unitOfWork = unitOfWork;
+ }
+
+ public Task Commit()
+ {
+ if (_rolledBack)
+ throw new InvalidOperationException("Transaction already rolled back");
+
+ _committed = true;
+ return Task.CompletedTask;
+ }
+
+ public Task Rollback()
+ {
+ if (_committed)
+ throw new InvalidOperationException("Transaction already committed");
+
+ _rolledBack = true;
+ return _unitOfWork.Rollback();
+ }
+
+ public void Dispose()
+ {
+ if (!_committed && !_rolledBack)
+ {
+ Rollback().GetAwaiter().GetResult();
+ }
+ }
+}
+```
+
+- [ ] **Step 4: 鎻愪氦鍩虹璁炬柦灞傚熀纭�瀹炵幇**
+
+```bash
+git add WIDESEAWCS_Infrastructure/
+git commit -m "feat: 娣诲姞鍩虹璁炬柦灞傚熀纭�瀹炵幇 - InMemoryUnitOfWork, InMemoryTransaction"
+```
+
+---
+
+## Chunk 2: 璁惧绠$悊棰嗗煙瀹炵幇
+
+### Task 4: 瀹炵幇璁惧鍊煎璞�
+
+**Files:**
+- Create: `WIDESEAWCS_Domain/DeviceManagement/ValueObjects/DeviceId.cs`
+- Create: `WIDESEAWCS_Domain/DeviceManagement/ValueObjects/DeviceName.cs`
+- Create: `WIDESEAWCS_Domain/DeviceManagement/ValueObjects/DeviceAddress.cs`
+- Create: `WIDESEAWCS_Domain/DeviceManagement/ValueObjects/DeviceType.cs`
+- Create: `WIDESEAWCS_Domain/DeviceManagement/ValueObjects/DeviceStatus.cs`
+- Test: `WIDESEAWCS_Domain.Tests/DeviceManagement/ValueObjects/DeviceValueObjectsTests.cs`
+
+- [ ] **Step 1: 鍒涘缓璁惧ID鍊煎璞�**
+
+```csharp
+namespace WIDESEAWCS_Domain.DeviceManagement.ValueObjects;
+
+public record DeviceId(Guid Value)
+{
+ public static DeviceId New() => new DeviceId(Guid.NewGuid());
+ public static DeviceId From(Guid value) => new DeviceId(value);
+}
+```
+
+- [ ] **Step 2: 鍒涘缓璁惧鍚嶇О鍊煎璞�**
+
+```csharp
+namespace WIDESEAWCS_Domain.DeviceManagement.ValueObjects;
+
+public record DeviceName(string Value)
+{
+ public DeviceName(string value)
+ {
+ if (string.IsNullOrWhiteSpace(value))
+ throw new ArgumentException("璁惧鍚嶇О涓嶈兘涓虹┖", nameof(value));
+ if (value.Length > 100)
+ throw new ArgumentException("璁惧鍚嶇О闀垮害涓嶈兘瓒呰繃100涓瓧绗�", nameof(value));
+ Value = value;
+ }
+}
+```
+
+- [ ] **Step 3: 鍒涘缓璁惧鍦板潃鍊煎璞�**
+
+```csharp
+using System.Net;
+
+namespace WIDESEAWCS_Domain.DeviceManagement.ValueObjects;
+
+public record DeviceAddress(string Ip, int Port)
+{
+ public DeviceAddress(string ip, int port)
+ {
+ if (!IPAddress.TryParse(ip, out _))
+ throw new ArgumentException("IP鍦板潃鏍煎紡鏃犳晥", nameof(ip));
+ if (port < 1 || port > 65535)
+ throw new ArgumentException("绔彛鍙峰繀椤诲湪1-65535涔嬮棿", nameof(port));
+ Ip = ip;
+ Port = port;
+ }
+}
+```
+
+- [ ] **Step 4: 鍒涘缓璁惧绫诲瀷鏋氫妇**
+
+```csharp
+namespace WIDESEAWCS_Domain.DeviceManagement.ValueObjects;
+
+public enum DeviceType
+{
+ StackerCrane = 1, // 鍫嗗灈鏈�
+ ConveyorLine = 2, // 杈撻�佺嚎
+ ShuttleCar = 3, // 绌挎杞�
+ Robot = 4, // 鏈烘鎵�
+ AGV = 5 // 鑷姩瀵煎紩杞�
+}
+```
+
+- [ ] **Step 5: 鍒涘缓璁惧鐘舵�佹灇涓�**
+
+```csharp
+namespace WIDESEAWCS_Domain.DeviceManagement.ValueObjects;
+
+public enum DeviceStatus
+{
+ Disconnected = 0, // 鏈繛鎺�
+ Connecting = 1, // 杩炴帴涓�
+ Connected = 2, // 宸茶繛鎺�
+ Busy = 3, // 蹇欑
+ Error = 4, // 閿欒
+ Maintenance = 5 // 缁存姢涓�
+}
+```
+
+- [ ] **Step 6: 鎻愪氦璁惧鍊煎璞�**
+
+```bash
+git add WIDESEAWCS_Domain/DeviceManagement/ValueObjects/
+git commit -m "feat: 娣诲姞璁惧鍊煎璞� - DeviceId, DeviceName, DeviceAddress, DeviceType, DeviceStatus"
+```
+
+---
+
+### Task 5: 瀹炵幇璁惧鑱氬悎鏍�
+
+**Files:**
+- Create: `WIDESEAWCS_Domain/DeviceManagement/Aggregates/Device.cs`
+- Create: `WIDESEAWCS_Domain/DeviceManagement/Events/DeviceConnectedEvent.cs`
+- Create: `WIDESEAWCS_Domain/DeviceManagement/Events/DeviceDisconnectedEvent.cs`
+- Create: `WIDESEAWCS_Domain/DeviceManagement/Events/DeviceHeartbeatEvent.cs`
+- Test: `WIDESEAWCS_Domain.Tests/DeviceManagement/Aggregates/DeviceTests.cs`
+
+- [ ] **Step 1: 鍒涘缓璁惧杩炴帴浜嬩欢**
+
+```csharp
+using WIDESEAWCS_Domain.Common;
+using WIDESEAWCS_Domain.DeviceManagement.ValueObjects;
+
+namespace WIDESEAWCS_Domain.DeviceManagement.Events;
+
+public record DeviceConnectedEvent : DomainEvent
+{
+ public DeviceId DeviceId { get; init; }
+ public DateTime ConnectedAt { get; init; } = DateTime.UtcNow;
+}
+```
+
+- [ ] **Step 2: 鍒涘缓璁惧鏂紑浜嬩欢**
+
+```csharp
+using WIDESEAWCS_Domain.Common;
+using WIDESEAWCS_Domain.DeviceManagement.ValueObjects;
+
+namespace WIDESEAWCS_Domain.DeviceManagement.Events;
+
+public record DeviceDisconnectedEvent : DomainEvent
+{
+ public DeviceId DeviceId { get; init; }
+ public string Reason { get; init; } = string.Empty;
+}
+```
+
+- [ ] **Step 3: 鍒涘缓璁惧蹇冭烦浜嬩欢**
+
+```csharp
+using WIDESEAWCS_Domain.Common;
+using WIDESEAWCS_Domain.DeviceManagement.ValueObjects;
+
+namespace WIDESEAWCS_Domain.DeviceManagement.Events;
+
+public record DeviceHeartbeatEvent : DomainEvent
+{
+ public DeviceId DeviceId { get; init; }
+ public DateTime HeartbeatAt { get; init; } = DateTime.UtcNow;
+}
+```
+
+- [ ] **Step 4: 鍒涘缓璁惧鑱氬悎鏍圭被**
+
+```csharp
+using WIDESEAWCS_Domain.Common;
+using WIDESEAWCS_Domain.DeviceManagement.Events;
+using WIDESEAWCS_Domain.DeviceManagement.ValueObjects;
+
+namespace WIDESEAW_Domain.DeviceManagement.Aggregates;
+
+public class Device : AggregateRoot<DeviceId>
+{
+ private DeviceId _id;
+ private DeviceName _name;
+ private DeviceType _type;
+ private DeviceStatus _status;
+ private DeviceAddress? _address;
+ private DateTime _lastConnectedAt;
+ private DateTime _lastHeartbeatAt;
+ private string? _errorMessage;
+
+ public Device(DeviceId id, DeviceName name, DeviceType type)
+ {
+ _id = id;
+ _name = name;
+ _type = type;
+ _status = DeviceStatus.Disconnected;
+ _lastConnectedAt = DateTime.MinValue;
+ _lastHeartbeatAt = DateTime.MinValue;
+ }
+
+ // 鍏叡灞炴�ц闂櫒
+ public DeviceId Id => _id;
+ public DeviceName Name => _name;
+ public DeviceType Type => _type;
+ public DeviceStatus Status => _status;
+ public DeviceAddress? Address => _address;
+ public DateTime LastConnectedAt => _lastConnectedAt;
+ public DateTime LastHeartbeatAt => _lastHeartbeatAt;
+ public string? ErrorMessage => _errorMessage;
+
+ // 琛屼负鏂规硶
+ public void Connect()
+ {
+ if (_status == DeviceStatus.Connected)
+ throw new InvalidDomainOperationException("璁惧宸茶繛鎺ワ紝鏃犳硶閲嶅杩炴帴");
+
+ _status = DeviceStatus.Connected;
+ _lastConnectedAt = DateTime.UtcNow;
+ _errorMessage = null;
+ IncrementVersion();
+
+ AddDomainEvent(new DeviceConnectedEvent
+ {
+ DeviceId = _id,
+ ConnectedAt = _lastConnectedAt
+ });
+ }
+
+ public void Disconnect(string reason)
+ {
+ if (_status == DeviceStatus.Disconnected)
+ throw new InvalidDomainOperationException("璁惧宸叉柇寮�锛屾棤娉曢噸澶嶆柇寮�");
+
+ _status = DeviceStatus.Disconnected;
+ _errorMessage = reason;
+ IncrementVersion();
+
+ AddDomainEvent(new DeviceDisconnectedEvent
+ {
+ DeviceId = _id,
+ Reason = reason
+ });
+ }
+
+ public void UpdateHeartbeat()
+ {
+ if (_status != DeviceStatus.Connected && _status != DeviceStatus.Busy)
+ throw new InvalidDomainOperationException("璁惧鏈繛鎺ユ垨蹇欑锛屾棤娉曟洿鏂板績璺�");
+
+ _lastHeartbeatAt = DateTime.UtcNow;
+ IncrementVersion();
+
+ AddDomainEvent(new DeviceHeartbeatEvent
+ {
+ DeviceId = _id,
+ HeartbeatAt = _lastHeartbeatAt
+ });
+ }
+
+ public void SetAddress(DeviceAddress address)
+ {
+ _address = address;
+ IncrementVersion();
+ }
+
+ // 鍐呴儴鏂规硶锛堜緵棰嗗煙鏈嶅姟浣跨敤锛�
+ internal void SetStatus(DeviceStatus status)
+ {
+ _status = status;
+ }
+
+ internal void SetError(string errorMessage)
+ {
+ _status = DeviceStatus.Error;
+ _errorMessage = errorMessage;
+ IncrementVersion();
+ }
+}
+```
+
+- [ ] **Step 5: 鎻愪氦璁惧鑱氬悎鏍瑰疄鐜�**
+
+```bash
+git add WIDESEAWCS_Domain/DeviceManagement/
+git commit -m "feat: 瀹炵幇璁惧鑱氬悎鏍瑰拰棰嗗煙浜嬩欢"
+```
+
+---
+
+### Task 6: 瀹炵幇璁惧鐘舵�佹満
+
+**Files:**
+- Create: `WIDESEAWCS_Domain/DeviceManagement/Services/IDeviceStateMachine.cs`
+- Create: `WIDESEAWCS_Domain/DeviceManagement/Services/DeviceStateMachine.cs`
+
+- [ ] **Step 1: 鍒涘缓璁惧鐘舵�佹満鎺ュ彛**
+
+```csharp
+using WIDESEAWCS_Domain.DeviceManagement.Aggregates;
+using WIDESEAWCS_Domain.DeviceManagement.ValueObjects;
+
+namespace WIDESEAWCS_Domain.DeviceManagement.Services;
+
+public interface IDeviceStateMachine
+{
+ bool CanTransition(DeviceStatus current, DeviceStatus target);
+ void Transition(Device device, DeviceStatus target);
+}
+```
+
+- [ ] **Step 2: 鍒涘缓璁惧鐘舵�佹満瀹炵幇**
+
+```csharp
+using WIDESEAWCS_Domain.DeviceManagement.Aggregates;
+using WIDESEAWCS_Domain.DeviceManagement.ValueObjects;
+using WIDESEAWCS_Domain.Common;
+
+namespace WIDESEAWCS_Domain.DeviceManagement.Services;
+
+public class DeviceStateMachine : IDeviceStateMachine
+{
+ private static readonly Dictionary<DeviceStatus, HashSet<DeviceStatus>> _transitions = new()
+ {
+ [DeviceStatus.Disconnected] = new()
+ {
+ DeviceStatus.Connecting,
+ DeviceStatus.Maintenance
+ },
+ [DeviceStatus.Connecting] = new()
+ {
+ DeviceStatus.Connected,
+ DeviceStatus.Error
+ },
+ [DeviceStatus.Connected] = new()
+ {
+ DeviceStatus.Busy,
+ DeviceStatus.Disconnected,
+ DeviceStatus.Error,
+ DeviceStatus.Maintenance
+ },
+ [DeviceStatus.Busy] = new()
+ {
+ DeviceStatus.Connected,
+ DeviceStatus.Error
+ },
+ [DeviceStatus.Error] = new()
+ {
+ DeviceStatus.Disconnected,
+ DeviceStatus.Maintenance
+ },
+ [DeviceStatus.Maintenance] = new()
+ {
+ DeviceStatus.Disconnected
+ }
+ };
+
+ public bool CanTransition(DeviceStatus current, DeviceStatus target)
+ {
+ if (!_transitions.TryGetValue(current, out var allowed))
+ return false;
+
+ return allowed.Contains(target);
+ }
+
+ public void Transition(Device device, DeviceStatus target)
+ {
+ if (!CanTransition(device.Status, target))
+ {
+ throw new InvalidDomainOperationException(
+ $"鏃犳晥鐨勭姸鎬佽浆鎹�: {device.Status} -> {target}");
+ }
+
+ device.SetStatus(target);
+ }
+}
+```
+
+- [ ] **Step 3: 鎻愪氦璁惧鐘舵�佹満瀹炵幇**
+
+```bash
+git add WIDESEAWCS_Domain/DeviceManagement/Services/
+git commit -m "feat: 瀹炵幇璁惧鐘舵�佹満棰嗗煙鏈嶅姟"
+```
+
+---
+
+### Task 7: 瀹炵幇璁惧浠撳偍鎺ュ彛鍜屽疄鐜�
+
+**Files:**
+- Create: `WIDESEAWCS_Application/DeviceManagement/Repositories/IDeviceRepository.cs`
+- Create: `WIDESEAWCS_Infrastructure/Persistence/Repositories/InMemoryDeviceRepository.cs`
+
+- [ ] **Step 1: 鍒涘缓璁惧浠撳偍鎺ュ彛**
+
+```csharp
+using WIDESEAWCS_Application.Common;
+using WIDESEAWCS_Domain.DeviceManagement.Aggregates;
+using WIDESEAWCS_Domain.DeviceManagement.ValueObjects;
+
+namespace WIDESEAWCS_Application.DeviceManagement.Repositories;
+
+public interface IDeviceRepository : IRepository<Device, DeviceId>
+{
+ Task<Device?> GetByName(DeviceName name);
+ Task<IEnumerable<Device>> GetByType(DeviceType type);
+ Task<IEnumerable<Device>> GetByStatus(DeviceStatus status);
+ Task<IEnumerable<Device>> GetAllConnected();
+}
+```
+
+- [ ] **Step 2: 鍒涘缓鍐呭瓨璁惧浠撳偍瀹炵幇**
+
+```csharp
+using WIDESEAWCS_Application.Common;
+using WIDESEAWCS_Application.DeviceManagement.Repositories;
+using WIDESEAWCS_Domain.DeviceManagement.Aggregates;
+using WIDESEAWCS_Domain.DeviceManagement.ValueObjects;
+
+namespace WIDESEAWCS_Infrastructure.Persistence.Repositories;
+
+public class InMemoryDeviceRepository : IDeviceRepository
+{
+ private readonly Dictionary<DeviceId, Device> _devices = new();
+
+ public Task<Device?> GetById(DeviceId id)
+ {
+ _devices.TryGetValue(id, out var device);
+ return Task.FromResult(device);
+ }
+
+ public Task Add(Device entity)
+ {
+ _devices[entity.Id] = entity;
+ return Task.CompletedTask;
+ }
+
+ public Task Update(Device entity)
+ {
+ _devices[entity.Id] = entity;
+ return Task.CompletedTask;
+ }
+
+ public Task Delete(DeviceId id)
+ {
+ _devices.Remove(id);
+ return Task.CompletedTask;
+ }
+
+ public async Task<Device?> GetByName(DeviceName name)
+ {
+ var device = _devices.Values.FirstOrDefault(d => d.Name == name);
+ return Task.FromResult(device);
+ }
+
+ public async Task<IEnumerable<Device>> GetByType(DeviceType type)
+ {
+ var devices = _devices.Values.Where(d => d.Type == type);
+ return Task.FromResult(devices);
+ }
+
+ public async Task<IEnumerable<Device>> GetByStatus(DeviceStatus status)
+ {
+ var devices = _devices.Values.Where(d => d.Status == status);
+ return Task.FromResult(devices);
+ }
+
+ public async Task<IEnumerable<Device>> GetAllConnected()
+ {
+ var devices = _devices.Values.Where(d => d.Status == DeviceStatus.Connected);
+ return Task.FromResult(devices);
+ }
+}
+```
+
+- [ ] **Step 3: 鎻愪氦璁惧浠撳偍瀹炵幇**
+
+```bash
+git add WIDESEAWCS_Application/DeviceManagement/Repositories/ WIDESEAWCS_Infrastructure/Persistence/Repositories/
+git commit -m "feat: 瀹炵幇璁惧浠撳偍鎺ュ彛鍜屽唴瀛樺疄鐜�"
+```
+
+---
+
+## Chunk 3: 搴旂敤鏈嶅姟瀹炵幇
+
+### Task 8: 瀹炵幇璁惧搴旂敤鏈嶅姟
+
+**Files:**
+- Create: `WIDESEAWCS_Application/DeviceManagement/Services/DeviceApplicationService.cs`
+- Create: `WIDESEAWCS_Application/DeviceManagement/DTOs/DeviceDto.cs`
+
+- [ ] **Step 1: 鍒涘缓璁惧DTO**
+
+```csharp
+using WIDESEAWCS_Domain.DeviceManagement.ValueObjects;
+
+namespace WIDESEAWCS_Application.DeviceManagement.DTOs;
+
+public record DeviceDto
+{
+ public Guid Id { get; init; }
+ public string Name { get; init; } = string.Empty;
+ public DeviceType Type { get; init; }
+ public DeviceStatus Status { get; init; }
+ public string? Ip { get; init; }
+ public int? Port { get; init; }
+ public DateTime LastConnectedAt { get; init; }
+ public DateTime LastHeartbeatAt { get; init; }
+ public string? ErrorMessage { get; init; }
+}
+```
+
+- [ ] **Step 2: 鍒涘缓璁惧搴旂敤鏈嶅姟**
+
+```csharp
+using WIDESEAWCS_Application.Common;
+using WIDESEAWCS_Application.DeviceManagement.DTOs;
+using WIDESEAWCS_Application.DeviceManagement.Repositories;
+using WIDESEAWCS_Domain.DeviceManagement.Aggregates;
+using WIDESEAWCS_Domain.DeviceManagement.ValueObjects;
+using WIDESEAWCS_Domain.Common;
+
+namespace WIDESEAWCS_Application.DeviceManagement.Services;
+
+public class DeviceApplicationService
+{
+ private readonly IDeviceRepository _deviceRepository;
+ private readonly IUnitOfWork _unitOfWork;
+
+ public DeviceApplicationService(
+ IDeviceRepository deviceRepository,
+ IUnitOfWork unitOfWork)
+ {
+ _deviceRepository = deviceRepository;
+ _unitOfWork = unitOfWork;
+ }
+
+ public async Task<DeviceDto?> GetDevice(Guid id)
+ {
+ var deviceId = DeviceId.From(id);
+ var device = await _deviceRepository.GetById(deviceId);
+ return device?.ToDto();
+ }
+
+ public async Task<DeviceDto> CreateDevice(string name, DeviceType type, string ip, int port)
+ {
+ await using var transaction = _unitOfWork.BeginTransaction();
+
+ var deviceId = DeviceId.New();
+ var deviceName = new DeviceName(name);
+ var device = new Device(deviceId, deviceName, type);
+ device.SetAddress(new DeviceAddress(ip, port));
+
+ await _deviceRepository.Add(device);
+ await transaction.Commit();
+ await _unitOfWork.Commit();
+
+ return device.ToDto();
+ }
+
+ public async Task ConnectDevice(Guid id)
+ {
+ var deviceId = DeviceId.From(id);
+ var device = await _deviceRepository.GetById(deviceId);
+
+ if (device == null)
+ throw new DomainException($"璁惧涓嶅瓨鍦�: {id}", "DEVICE_NOT_FOUND");
+
+ device.Connect();
+ await _deviceRepository.Update(device);
+ }
+
+ public async Task DisconnectDevice(Guid id, string reason)
+ {
+ var deviceId = DeviceId.From(id);
+ var device = await _deviceRepository.GetById(deviceId);
+
+ if (device == null)
+ throw new DomainException($"璁惧涓嶅瓨鍦�: {id}", "DEVICE_NOT_FOUND");
+
+ device.Disconnect(reason);
+ await _deviceRepository.Update(device);
+ }
+
+ public async Task<IEnumerable<DeviceDto>> GetConnectedDevices()
+ {
+ var devices = await _deviceRepository.GetAllConnected();
+ return devices.Select(d => d.ToDto());
+ }
+}
+```
+
+- [ ] **Step 3: 鍦―evice绫讳腑娣诲姞ToDto鎵╁睍鏂规硶**
+
+```csharp
+// 鍦� WIDESEAWCS_Domain/DeviceManagement/Aggregates/DeviceExtensions.cs 涓垱寤�
+using WIDESEAWCS_Application.DeviceManagement.DTOs;
+
+namespace WIDESEAWCS_Domain.DeviceManagement.Aggregates;
+
+public static class DeviceExtensions
+{
+ public static DeviceDto ToDto(this Device device)
+ {
+ return new DeviceDto
+ {
+ Id = device.Id.Value,
+ Name = device.Name.Value,
+ Type = device.Type,
+ Status = device.Status,
+ Ip = device.Address?.Ip,
+ Port = device.Address?.Port,
+ LastConnectedAt = device.LastConnectedAt,
+ LastHeartbeatAt = device.LastHeartbeatAt,
+ ErrorMessage = device.ErrorMessage
+ };
+ }
+}
+```
+
+- [ ] **Step 4: 鎻愪氦璁惧搴旂敤鏈嶅姟瀹炵幇**
+
+```bash
+git add WIDESEAWCS_Application/DeviceManagement/ WIDESEAWCS_Domain/DeviceManagement/Aggregates/DeviceExtensions.cs
+git commit -m "feat: 瀹炵幇璁惧搴旂敤鏈嶅姟鍜孌TO"
+```
+
+---
+
+## Chunk 4: 闆嗘垚鍜岄獙璇�
+
+### Task 9: 閰嶇疆渚濊禆娉ㄥ叆
+
+**Files:**
+- Create: `WIDESEAWCS_Infrastructure/DependencyInjection/ServiceCollectionExtensions.cs`
+
+- [ ] **Step 1: 鍒涘缓鏈嶅姟娉ㄥ唽鎵╁睍**
+
+```csharp
+using Microsoft.Extensions.DependencyInjection;
+using WIDESEAWCS_Application.Common;
+using WIDESEAWCS_Application.DeviceManagement.Repositories;
+using WIDESEAWCS_Application.DeviceManagement.Services;
+using WIDESEAWCS_Infrastructure.Persistence;
+using WIDESEAWCS_Infrastructure.Persistence.Repositories;
+using WIDESEAWCS_Domain.DeviceManagement.Services;
+
+namespace WIDESEAWCS_Infrastructure.DependencyInjection;
+
+public static class ServiceCollectionExtensions
+{
+ public static IServiceCollection AddDeviceManagementServices(this IServiceCollection services)
+ {
+ // 娉ㄥ唽棰嗗煙鏈嶅姟
+ services.AddSingleton<IDeviceStateMachine, DeviceStateMachine>();
+
+ // 娉ㄥ唽浠撳偍
+ services.AddSingleton<IDeviceRepository, InMemoryDeviceRepository>();
+
+ // 娉ㄥ唽宸ヤ綔鍗曞厓
+ services.AddSingleton<IUnitOfWork, InMemoryUnitOfWork>();
+
+ // 娉ㄥ唽搴旂敤鏈嶅姟
+ services.AddScoped<DeviceApplicationService>();
+
+ return services;
+ }
+}
+```
+
+- [ ] **Step 2: 鎻愪氦渚濊禆娉ㄥ叆閰嶇疆**
+
+```bash
+git add WIDESEAWCS_Infrastructure/DependencyInjection/
+git commit -m "feat: 閰嶇疆璁惧绠$悊鏈嶅姟渚濊禆娉ㄥ叆"
+```
+
+---
+
+### Task 10: 闆嗘垚娴嬭瘯
+
+**Files:**
+- Create: `WIDESEAWCS_IntegrationTests/DeviceManagement/DeviceIntegrationTests.cs`
+
+- [ ] **Step 1: 鍒涘缓闆嗘垚娴嬭瘯**
+
+```csharp
+using Xunit;
+using Microsoft.Extensions.DependencyInjection;
+using WIDESEAWCS_Application.DeviceManagement.Services;
+using WIDESEAWCS_Domain.DeviceManagement.Aggregates;
+using WIDESEAWCS_Domain.DeviceManagement.ValueObjects;
+using WIDESEAWCS_Infrastructure.DependencyInjection;
+
+namespace WIDESEAWCS_IntegrationTests.DeviceManagement;
+
+public class DeviceIntegrationTests
+{
+ private readonly IServiceProvider _serviceProvider;
+ private readonly DeviceApplicationService _service;
+
+ public DeviceIntegrationTests()
+ {
+ var services = new ServiceCollection();
+ services.AddDeviceManagementServices();
+ _serviceProvider = services.BuildServiceProvider();
+ _service = _serviceProvider.GetRequiredService<DeviceApplicationService>();
+ }
+
+ [Fact]
+ public async Task DeviceWorkflow_Should_CompleteSuccessfully()
+ {
+ // Arrange & Act - 鍒涘缓璁惧
+ var device = await _service.CreateDevice("IntegrationTestDevice", DeviceType.StackerCrane, "127.0.0.1", 8080);
+ Assert.NotEqual(Guid.Empty, device.Id);
+ Assert.Equal(DeviceType.StackerCrane, device.Type);
+ Assert.Equal("IntegrationTestDevice", device.Name.Value);
+ Assert.Equal(DeviceStatus.Disconnected, device.Status);
+
+ // Act - 杩炴帴璁惧
+ await _service.ConnectDevice(device.Id);
+ var connectedDevice = await _service.GetDevice(device.Id);
+ Assert.NotNull(connectedDevice);
+ Assert.Equal(DeviceStatus.Connected, connectedDevice!.Status);
+
+ // Act - 鏂紑璁惧
+ await _service.DisconnectDevice(device.Id, "Integration test disconnect");
+ var disconnectedDevice = await _service.GetDevice(device.Id);
+ Assert.NotNull(disconnectedDevice);
+ Assert.Equal(DeviceStatus.Disconnected, disconnectedDevice!.Status);
+ Assert.Equal("Integration test disconnect", disconnectedDevice!.ErrorMessage);
+ }
+
+ // Act - 鑾峰彇宸茶繛鎺ヨ澶�
+ var connectedDevices = await _service.GetConnectedDevices();
+ Assert.Single(connectedDevices);
+ Assert.Equal("IntegrationTestDevice", connectedDevices.First().Name);
+ }
+
+ [Fact]
+ public async Task GetConnectedDevices_Should_ReturnOnlyConnected()
+ {
+ // Arrange & Act - 鍒涘缓澶氫釜璁惧
+ var device1 = await _service.CreateDevice("Device1", DeviceType.ConveyorLine, "127.0.0.2", 8081);
+ var device2 = await _service.CreateDevice("Device2", DeviceType.ShuttleCar, "127.0.0.3", 8082);
+
+ // Act - 鍙繛鎺ョ涓�涓澶�
+ await _service.ConnectDevice(device1.Id);
+
+ var connectedDevices = await _service.GetConnectedDevices();
+ Assert.Single(connectedDevices);
+ Assert.Equal("Device1", connectedDevices.First().Name);
+ }
+}
+```
+
+- [ ] **Step 2: 鎻愪氦闆嗘垚娴嬭瘯**
+
+```bash
+git add WIDESEAWCS_IntegrationTests/DeviceManagement/
+git commit -m "test: 娣诲姞璁惧绠$悊闆嗘垚娴嬭瘯"
+```
+
+---
+
+### Task 11: 鏇存柊鏂囨。
+
+**Files:**
+- Modify: `CLAUDE.md`
+
+- [ ] **Step 1: 鏇存柊CLAUDE.md娣诲姞DDD鏋舵瀯璇存槑**
+
+鍦–LAUDE.md涓坊鍔狅細
+```markdown
+## DDD鏋舵瀯瀹炴柦
+
+椤圭洰閲囩敤娓愯繘寮廌DD鏋舵瀯锛屽寘鍚互涓嬪眰娆★細
+
+### 棰嗗煙灞� (WIDESEAWCS_Domain)
+- 鑱氬悎鏍癸細Device, DeviceTask绛�
+- 鍊煎璞★細DeviceId, DeviceName, DeviceAddress绛�
+- 棰嗗煙鏈嶅姟锛欴eviceStateMachine, DeviceScheduler绛�
+- 棰嗗煙浜嬩欢锛欴eviceConnectedEvent, TaskCompletedEvent绛�
+
+### 搴旂敤灞� (WIDESEAWCS_Application)
+- 搴旂敤鏈嶅姟锛欴eviceApplicationService绛�
+- DTOs锛氳澶囥�佷换鍔$瓑鏁版嵁浼犺緭瀵硅薄
+- 浠撳偍鎺ュ彛锛欼DeviceRepository, ITaskRepository绛�
+- 宸ヤ綔鍗曞厓锛欼UnitOfWork
+
+### 鍩虹璁炬柦灞� (WIDESEAWCS_Infrastructure)
+- 浠撳偍瀹炵幇锛欼nMemoryDeviceRepository, DeviceRepository绛�
+- 鏁版嵁鎸佷箙鍖栵細SqlSugar闆嗘垚
+- 缂撳瓨锛歊edis缂撳瓨瀹炵幇
+- 閫氫俊锛氳澶囬�氫俊閫傞厤鍣�
+
+### 鐗规�у紑鍏�
+鏀寔鏂版棫浠g爜鍏卞瓨锛屽彲閫氳繃閰嶇疆鎺у埗锛�
+```json
+"FeatureFlags": {
+ "UseNewDeviceManagement": true,
+ "UseNewTaskScheduler": false
+}
+```
+```
+
+- [ ] **Step 2: 鎻愪氦鏂囨。鏇存柊**
+
+```bash
+git add CLAUDE.md
+git commit -m "docs: 鏇存柊CLAUDE.md娣诲姞DDD鏋舵瀯璇存槑"
+```
+
+---
+
+### Task 12: 鎬荤粨鍜屽悗缁楠�
+
+- [ ] **Step 1: 鏌ョ湅鎵�鏈夋彁浜�**
+
+```bash
+git log --oneline --graph
+```
+
+- [ ] **Step 2: 鏄剧ず鎬荤粨淇℃伅**
+
+鏈瀹炴柦璁″垝宸插畬鎴愰樁娈�1-4锛屽叡11涓换鍔★細
+
+**宸插畬鎴愮殑宸ヤ綔**锛�
+1. 鍩虹璁炬柦鎼缓锛欴DD鍩虹绫汇�佸簲鐢ㄥ眰鎺ュ彛銆佸熀纭�璁炬柦灞傚疄鐜�
+2. 璁惧绠$悊棰嗗煙锛氳澶囧�煎璞°�佽仛鍚堟牴銆佺姸鎬佹満銆佷粨鍌�
+3. 搴旂敤灞傚疄鐜帮細璁惧搴旂敤鏈嶅姟銆丏TO銆佹墿灞曟柟娉�
+4. 闆嗘垚楠岃瘉锛氫緷璧栨敞鍏ラ厤缃�侀泦鎴愭祴璇�
+5. 鏂囨。鏇存柊锛欳LAUDE.md
+
+**涓嬩竴姝ュ缓璁�**锛�
+1. 缂栧啓瀹屾暣鐨勫崟鍏冩祴璇曞拰闆嗘垚娴嬭瘯
+2. 瀹炵幇SqlSugar浠撳偍瀹炵幇锛堟浛鎹㈠唴瀛樺疄鐜帮級
+3. 瀹炵幇缂撳瓨浼樺寲锛圧edis闆嗘垚锛�
+4. 閰嶇疆鐗规�у紑鍏虫敮鎸佹柊鏃т唬鐮佸叡瀛�
+5. 缂栧啓API鎺у埗鍣紙DeviceController锛�
+6. 杩涜鎬ц兘娴嬭瘯鍜屼紭鍖�
+7. 鍒涘缓Pull Request鍚堝苟鍒癿aster鍒嗘敮
--
Gitblit v1.9.3