wanshenmean
2026-03-11 2a157fc570f544e5912f4ef83cef0a403af86f8f
docs: 添加完整的WCS DDD重构实施计划
已添加1个文件
1229 ■■■■■ 文件已修改
Code/WCS/WIDESEAWCS_Server/docs/superpowers/plans/2026-03-11-wcs-ddd-refactor-plan.md 1229 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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重构,包括领域层、应用层和基础设施层,重点重构设备管理领域
**架构:** é‡‡ç”¨æ ‡å‡†çš„四层DDD架构:表现层 â†’ åº”用层 â†’ é¢†åŸŸå±‚ â†’ åŸºç¡€è®¾æ–½å±‚,使用CQRS模式分离命令和查询,领域事件驱动设计
**技术栈:**
- .NET 6.0
- SqlSugar ORM
- Redis(StackExchange.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: åœ¨Device类中添加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: å®žçŽ°è®¾å¤‡åº”ç”¨æœåŠ¡å’ŒDTO"
```
---
## 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架构说明**
在CLAUDE.md中添加:
```markdown
## DDD架构实施
项目采用渐进式DDD架构,包含以下层次:
### é¢†åŸŸå±‚ (WIDESEAWCS_Domain)
- èšåˆæ ¹ï¼šDevice, DeviceTask等
- å€¼å¯¹è±¡ï¼šDeviceId, DeviceName, DeviceAddress等
- é¢†åŸŸæœåŠ¡ï¼šDeviceStateMachine, DeviceScheduler等
- é¢†åŸŸäº‹ä»¶ï¼šDeviceConnectedEvent, TaskCompletedEvent等
### åº”用层 (WIDESEAWCS_Application)
- åº”用服务:DeviceApplicationService等
- DTOs:设备、任务等数据传输对象
- ä»“储接口:IDeviceRepository, ITaskRepository等
- å·¥ä½œå•元:IUnitOfWork
### åŸºç¡€è®¾æ–½å±‚ (WIDESEAWCS_Infrastructure)
- ä»“储实现:InMemoryDeviceRepository, DeviceRepository等
- æ•°æ®æŒä¹…化:SqlSugar集成
- ç¼“存:Redis缓存实现
- é€šä¿¡ï¼šè®¾å¤‡é€šä¿¡é€‚配器
### ç‰¹æ€§å¼€å…³
支持新旧代码共存,可通过配置控制:
```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. åŸºç¡€è®¾æ–½æ­å»ºï¼šDDD基础类、应用层接口、基础设施层实现
2. è®¾å¤‡ç®¡ç†é¢†åŸŸï¼šè®¾å¤‡å€¼å¯¹è±¡ã€èšåˆæ ¹ã€çŠ¶æ€æœºã€ä»“å‚¨
3. åº”用层实现:设备应用服务、DTO、扩展方法
4. é›†æˆéªŒè¯ï¼šä¾èµ–注入配置、集成测试
5. æ–‡æ¡£æ›´æ–°ï¼šCLAUDE.md
**下一步建议**:
1. ç¼–写完整的单元测试和集成测试
2. å®žçްSqlSugar仓储实现(替换内存实现)
3. å®žçŽ°ç¼“å­˜ä¼˜åŒ–ï¼ˆRedis集成)
4. é…ç½®ç‰¹æ€§å¼€å…³æ”¯æŒæ–°æ—§ä»£ç å…±å­˜
5. ç¼–写API控制器(DeviceController)
6. è¿›è¡Œæ€§èƒ½æµ‹è¯•和优化
7. åˆ›å»ºPull Request合并到master分支