| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # 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忝 |