diff --git a/Backend/Directory.Packages.props b/Backend/Directory.Packages.props
index 0d3de24..44a6402 100644
--- a/Backend/Directory.Packages.props
+++ b/Backend/Directory.Packages.props
@@ -2,7 +2,6 @@
true
-
3.2.1
@@ -13,14 +12,12 @@
1.0.5
1.1.2
-
-
@@ -39,7 +36,6 @@
-
@@ -50,14 +46,12 @@
-
-
-
+
@@ -70,7 +64,6 @@
-
@@ -98,7 +91,6 @@
-
@@ -126,7 +118,6 @@
-
@@ -145,8 +136,7 @@
-
-
+
\ No newline at end of file
diff --git a/Backend/dotnet-tools.json b/Backend/dotnet-tools.json
new file mode 100644
index 0000000..bffb60c
--- /dev/null
+++ b/Backend/dotnet-tools.json
@@ -0,0 +1,13 @@
+{
+ "version": 1,
+ "isRoot": true,
+ "tools": {
+ "dotnet-ef": {
+ "version": "10.0.3",
+ "commands": [
+ "dotnet-ef"
+ ],
+ "rollForward": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/Backend/src/Fengling.Backend.Domain/AggregatesModel/AdminAggregate/Admin.cs b/Backend/src/Fengling.Backend.Domain/AggregatesModel/AdminAggregate/Admin.cs
new file mode 100644
index 0000000..dca47e3
--- /dev/null
+++ b/Backend/src/Fengling.Backend.Domain/AggregatesModel/AdminAggregate/Admin.cs
@@ -0,0 +1,117 @@
+using Fengling.Backend.Domain.DomainEvents;
+
+namespace Fengling.Backend.Domain.AggregatesModel.AdminAggregate;
+
+///
+/// 管理员ID
+///
+public partial record AdminId : IGuidStronglyTypedId;
+
+///
+/// 管理员聚合根
+///
+public class Admin : Entity, IAggregateRoot
+{
+ protected Admin() { }
+
+ private Admin(string username, string passwordHash)
+ {
+ Username = username;
+ PasswordHash = passwordHash;
+ Status = AdminStatus.Active;
+ CreatedAt = DateTime.UtcNow;
+
+ this.AddDomainEvent(new AdminCreatedDomainEvent(this));
+ }
+
+ ///
+ /// 用户名
+ ///
+ public string Username { get; private set; } = string.Empty;
+
+ ///
+ /// 密码哈希
+ ///
+ public string PasswordHash { get; private set; } = string.Empty;
+
+ ///
+ /// 管理员状态
+ ///
+ public AdminStatus Status { get; private set; }
+
+ ///
+ /// 最后登录时间
+ ///
+ public DateTime? LastLoginAt { get; private set; }
+
+ ///
+ /// 创建时间
+ ///
+ public DateTime CreatedAt { get; private set; }
+
+ ///
+ /// 软删除标记
+ ///
+ public Deleted Deleted { get; private set; } = new();
+
+ ///
+ /// 行版本
+ ///
+ public RowVersion RowVersion { get; private set; } = new(0);
+
+ ///
+ /// 创建管理员
+ ///
+ public static Admin Create(string username, string password)
+ {
+ if (string.IsNullOrWhiteSpace(username))
+ throw new KnownException("用户名不能为空");
+
+ if (string.IsNullOrWhiteSpace(password))
+ throw new KnownException("密码不能为空");
+
+ var passwordHash = PasswordHelper.HashPassword(password);
+ return new Admin(username, passwordHash);
+ }
+
+ ///
+ /// 验证密码
+ ///
+ public bool VerifyPassword(string password)
+ {
+ return PasswordHelper.VerifyPassword(password, PasswordHash);
+ }
+
+ ///
+ /// 记录登录
+ ///
+ public void RecordLogin()
+ {
+ LastLoginAt = DateTime.UtcNow;
+ this.AddDomainEvent(new AdminLoggedInDomainEvent(this));
+ }
+
+ ///
+ /// 禁用管理员
+ ///
+ public void Disable()
+ {
+ if (Status == AdminStatus.Disabled)
+ return;
+
+ Status = AdminStatus.Disabled;
+ this.AddDomainEvent(new AdminDisabledDomainEvent(this));
+ }
+
+ ///
+ /// 启用管理员
+ ///
+ public void Enable()
+ {
+ if (Status == AdminStatus.Active)
+ return;
+
+ Status = AdminStatus.Active;
+ this.AddDomainEvent(new AdminEnabledDomainEvent(this));
+ }
+}
diff --git a/Backend/src/Fengling.Backend.Domain/AggregatesModel/AdminAggregate/AdminStatus.cs b/Backend/src/Fengling.Backend.Domain/AggregatesModel/AdminAggregate/AdminStatus.cs
new file mode 100644
index 0000000..f48fa2f
--- /dev/null
+++ b/Backend/src/Fengling.Backend.Domain/AggregatesModel/AdminAggregate/AdminStatus.cs
@@ -0,0 +1,17 @@
+namespace Fengling.Backend.Domain.AggregatesModel.AdminAggregate;
+
+///
+/// 管理员状态
+///
+public enum AdminStatus
+{
+ ///
+ /// 正常
+ ///
+ Active = 1,
+
+ ///
+ /// 禁用
+ ///
+ Disabled = 2
+}
diff --git a/Backend/src/Fengling.Backend.Domain/AggregatesModel/AdminAggregate/PasswordHelper.cs b/Backend/src/Fengling.Backend.Domain/AggregatesModel/AdminAggregate/PasswordHelper.cs
new file mode 100644
index 0000000..4cdb9b2
--- /dev/null
+++ b/Backend/src/Fengling.Backend.Domain/AggregatesModel/AdminAggregate/PasswordHelper.cs
@@ -0,0 +1,36 @@
+namespace Fengling.Backend.Domain.AggregatesModel.AdminAggregate;
+
+///
+/// 密码加密工具类
+///
+public static class PasswordHelper
+{
+ ///
+ /// 加密密码
+ ///
+ public static string HashPassword(string password)
+ {
+ if (string.IsNullOrWhiteSpace(password))
+ throw new ArgumentException("密码不能为空", nameof(password));
+
+ return BCrypt.Net.BCrypt.HashPassword(password);
+ }
+
+ ///
+ /// 验证密码
+ ///
+ public static bool VerifyPassword(string password, string hash)
+ {
+ if (string.IsNullOrWhiteSpace(password) || string.IsNullOrWhiteSpace(hash))
+ return false;
+
+ try
+ {
+ return BCrypt.Net.BCrypt.Verify(password, hash);
+ }
+ catch
+ {
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Backend/src/Fengling.Backend.Domain/AggregatesModel/CategoryAggregate/Category.cs b/Backend/src/Fengling.Backend.Domain/AggregatesModel/CategoryAggregate/Category.cs
new file mode 100644
index 0000000..9040387
--- /dev/null
+++ b/Backend/src/Fengling.Backend.Domain/AggregatesModel/CategoryAggregate/Category.cs
@@ -0,0 +1,111 @@
+namespace Fengling.Backend.Domain.AggregatesModel.CategoryAggregate;
+
+///
+/// 品类ID
+///
+public partial record CategoryId : IGuidStronglyTypedId;
+
+///
+/// 品类聚合根
+///
+public class Category : Entity, IAggregateRoot
+{
+ protected Category() { }
+
+ public Category(
+ string name,
+ string code,
+ string? description = null,
+ int sortOrder = 0)
+ {
+ Name = name;
+ Code = code;
+ Description = description ?? string.Empty;
+ SortOrder = sortOrder;
+ IsActive = true;
+ CreatedAt = DateTime.UtcNow;
+ UpdatedAt = DateTime.UtcNow;
+ }
+
+ ///
+ /// 品类名称
+ ///
+ public string Name { get; private set; } = string.Empty;
+
+ ///
+ /// 品类编码(唯一)
+ ///
+ public string Code { get; private set; } = string.Empty;
+
+ ///
+ /// 描述
+ ///
+ public string Description { get; private set; } = string.Empty;
+
+ ///
+ /// 排序
+ ///
+ public int SortOrder { get; private set; }
+
+ ///
+ /// 是否激活
+ ///
+ public bool IsActive { get; private set; }
+
+ ///
+ /// 创建时间
+ ///
+ public DateTime CreatedAt { get; private set; }
+
+ ///
+ /// 更新时间
+ ///
+ public DateTime UpdatedAt { get; private set; }
+
+ public Deleted Deleted { get; private set; } = new();
+ public RowVersion RowVersion { get; private set; } = new(0);
+
+ ///
+ /// 更新品类信息
+ ///
+ public void UpdateInfo(
+ string? name = null,
+ string? description = null,
+ int? sortOrder = null)
+ {
+ if (!string.IsNullOrWhiteSpace(name))
+ Name = name;
+
+ if (description != null)
+ Description = description;
+
+ if (sortOrder.HasValue)
+ SortOrder = sortOrder.Value;
+
+ UpdatedAt = DateTime.UtcNow;
+ }
+
+ ///
+ /// 激活
+ ///
+ public void Activate()
+ {
+ if (!IsActive)
+ {
+ IsActive = true;
+ UpdatedAt = DateTime.UtcNow;
+ }
+ }
+
+ ///
+ /// 停用
+ ///
+ public void Deactivate()
+ {
+ if (IsActive)
+ {
+ IsActive = false;
+ UpdatedAt = DateTime.UtcNow;
+ }
+ }
+}
diff --git a/Backend/src/Fengling.Backend.Domain/AggregatesModel/ProductAggregate/Product.cs b/Backend/src/Fengling.Backend.Domain/AggregatesModel/ProductAggregate/Product.cs
new file mode 100644
index 0000000..d6b8bea
--- /dev/null
+++ b/Backend/src/Fengling.Backend.Domain/AggregatesModel/ProductAggregate/Product.cs
@@ -0,0 +1,116 @@
+namespace Fengling.Backend.Domain.AggregatesModel.ProductAggregate;
+
+///
+/// 产品ID
+///
+public partial record ProductId : IGuidStronglyTypedId;
+
+///
+/// 产品聚合根
+///
+public class Product : Entity, IAggregateRoot
+{
+ protected Product() { }
+
+ public Product(
+ string name,
+ Guid categoryId,
+ string categoryName,
+ string? description = null)
+ {
+ Name = name;
+ CategoryId = categoryId;
+ CategoryName = categoryName;
+ Description = description ?? string.Empty;
+ IsActive = true;
+ CreatedAt = DateTime.UtcNow;
+ UpdatedAt = DateTime.UtcNow;
+ }
+
+ ///
+ /// 产品名称
+ ///
+ public string Name { get; private set; } = string.Empty;
+
+ ///
+ /// 品类ID
+ ///
+ public Guid CategoryId { get; private set; }
+
+ ///
+ /// 品类名称
+ ///
+ public string CategoryName { get; private set; } = string.Empty;
+
+ ///
+ /// 描述
+ ///
+ public string Description { get; private set; } = string.Empty;
+
+ ///
+ /// 是否激活
+ ///
+ public bool IsActive { get; private set; }
+
+ ///
+ /// 创建时间
+ ///
+ public DateTime CreatedAt { get; private set; }
+
+ ///
+ /// 更新时间
+ ///
+ public DateTime UpdatedAt { get; private set; }
+
+ public Deleted Deleted { get; private set; } = new();
+ public RowVersion RowVersion { get; private set; } = new(0);
+
+ ///
+ /// 更新产品信息
+ ///
+ public void UpdateInfo(
+ string? name = null,
+ Guid? categoryId = null,
+ string? categoryName = null,
+ string? description = null)
+ {
+ if (!string.IsNullOrWhiteSpace(name))
+ Name = name;
+
+ if (categoryId.HasValue && categoryId.Value != Guid.Empty)
+ {
+ CategoryId = categoryId.Value;
+ if (!string.IsNullOrWhiteSpace(categoryName))
+ CategoryName = categoryName;
+ }
+
+ if (description != null)
+ Description = description;
+
+ UpdatedAt = DateTime.UtcNow;
+ }
+
+ ///
+ /// 激活
+ ///
+ public void Activate()
+ {
+ if (!IsActive)
+ {
+ IsActive = true;
+ UpdatedAt = DateTime.UtcNow;
+ }
+ }
+
+ ///
+ /// 停用
+ ///
+ public void Deactivate()
+ {
+ if (IsActive)
+ {
+ IsActive = false;
+ UpdatedAt = DateTime.UtcNow;
+ }
+ }
+}
diff --git a/Backend/src/Fengling.Backend.Domain/DomainEvents/AdminDomainEvents.cs b/Backend/src/Fengling.Backend.Domain/DomainEvents/AdminDomainEvents.cs
new file mode 100644
index 0000000..24efbfb
--- /dev/null
+++ b/Backend/src/Fengling.Backend.Domain/DomainEvents/AdminDomainEvents.cs
@@ -0,0 +1,23 @@
+using Fengling.Backend.Domain.AggregatesModel.AdminAggregate;
+
+namespace Fengling.Backend.Domain.DomainEvents;
+
+///
+/// 管理员创建领域事件
+///
+public record AdminCreatedDomainEvent(Admin Admin) : IDomainEvent;
+
+///
+/// 管理员登录领域事件
+///
+public record AdminLoggedInDomainEvent(Admin Admin) : IDomainEvent;
+
+///
+/// 管理员禁用领域事件
+///
+public record AdminDisabledDomainEvent(Admin Admin) : IDomainEvent;
+
+///
+/// 管理员启用领域事件
+///
+public record AdminEnabledDomainEvent(Admin Admin) : IDomainEvent;
diff --git a/Backend/src/Fengling.Backend.Domain/Fengling.Backend.Domain.csproj b/Backend/src/Fengling.Backend.Domain/Fengling.Backend.Domain.csproj
index 1ab15ae..b04e537 100644
--- a/Backend/src/Fengling.Backend.Domain/Fengling.Backend.Domain.csproj
+++ b/Backend/src/Fengling.Backend.Domain/Fengling.Backend.Domain.csproj
@@ -7,6 +7,7 @@
true
+
diff --git a/Backend/src/Fengling.Backend.Infrastructure/ApplicationDbContext.cs b/Backend/src/Fengling.Backend.Infrastructure/ApplicationDbContext.cs
index ebc34a4..85ebb21 100644
--- a/Backend/src/Fengling.Backend.Infrastructure/ApplicationDbContext.cs
+++ b/Backend/src/Fengling.Backend.Infrastructure/ApplicationDbContext.cs
@@ -1,12 +1,15 @@
using MediatR;
using Microsoft.EntityFrameworkCore;
using NetCorePal.Extensions.DistributedTransactions.CAP.Persistence;
+using Fengling.Backend.Domain.AggregatesModel.AdminAggregate;
using Fengling.Backend.Domain.AggregatesModel.MemberAggregate;
using Fengling.Backend.Domain.AggregatesModel.MarketingCodeAggregate;
using Fengling.Backend.Domain.AggregatesModel.PointsRuleAggregate;
using Fengling.Backend.Domain.AggregatesModel.PointsTransactionAggregate;
using Fengling.Backend.Domain.AggregatesModel.GiftAggregate;
using Fengling.Backend.Domain.AggregatesModel.RedemptionOrderAggregate;
+using Fengling.Backend.Domain.AggregatesModel.CategoryAggregate;
+using Fengling.Backend.Domain.AggregatesModel.ProductAggregate;
namespace Fengling.Backend.Infrastructure;
@@ -14,6 +17,9 @@ public partial class ApplicationDbContext(DbContextOptions
: AppDbContextBase(options, mediator)
, ISqliteCapDataStorage
{
+ // 管理员聚合
+ public DbSet Admins => Set();
+
// 会员聚合
public DbSet Members => Set();
public DbSet PointsTransactions => Set();
@@ -30,6 +36,12 @@ public partial class ApplicationDbContext(DbContextOptions
// 兑换订单聚合
public DbSet RedemptionOrders => Set();
+ // 品类聚合
+ public DbSet Categories => Set();
+
+ // 产品聚合
+ public DbSet Products => Set();
+
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
if (modelBuilder is null)
diff --git a/Backend/src/Fengling.Backend.Infrastructure/EntityConfigurations/AdminEntityTypeConfiguration.cs b/Backend/src/Fengling.Backend.Infrastructure/EntityConfigurations/AdminEntityTypeConfiguration.cs
new file mode 100644
index 0000000..f729be5
--- /dev/null
+++ b/Backend/src/Fengling.Backend.Infrastructure/EntityConfigurations/AdminEntityTypeConfiguration.cs
@@ -0,0 +1,49 @@
+using Fengling.Backend.Domain.AggregatesModel.AdminAggregate;
+
+namespace Fengling.Backend.Infrastructure.EntityConfigurations;
+
+///
+/// 管理员实体配置
+///
+public class AdminEntityTypeConfiguration : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder builder)
+ {
+ builder.ToTable("Admins");
+
+ builder.HasKey(x => x.Id);
+
+ builder.Property(x => x.Id)
+ .UseGuidVersion7ValueGenerator()
+ .HasComment("管理员ID");
+
+ builder.Property(x => x.Username)
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasComment("用户名");
+
+ builder.Property(x => x.PasswordHash)
+ .IsRequired()
+ .HasMaxLength(255)
+ .HasComment("密码哈希");
+
+ builder.Property(x => x.Status)
+ .IsRequired()
+ .HasComment("管理员状态(1=Active,2=Disabled)");
+
+ builder.Property(x => x.LastLoginAt)
+ .HasComment("最后登录时间");
+
+ builder.Property(x => x.CreatedAt)
+ .IsRequired()
+ .HasComment("创建时间");
+
+ // 索引
+ builder.HasIndex(x => x.Username)
+ .IsUnique()
+ .HasDatabaseName("IX_Admins_Username");
+
+ builder.HasIndex(x => x.Status)
+ .HasDatabaseName("IX_Admins_Status");
+ }
+}
diff --git a/Backend/src/Fengling.Backend.Infrastructure/EntityConfigurations/CategoryEntityTypeConfiguration.cs b/Backend/src/Fengling.Backend.Infrastructure/EntityConfigurations/CategoryEntityTypeConfiguration.cs
new file mode 100644
index 0000000..7fd2dbd
--- /dev/null
+++ b/Backend/src/Fengling.Backend.Infrastructure/EntityConfigurations/CategoryEntityTypeConfiguration.cs
@@ -0,0 +1,51 @@
+using Fengling.Backend.Domain.AggregatesModel.CategoryAggregate;
+
+namespace Fengling.Backend.Infrastructure.EntityConfigurations;
+
+public class CategoryEntityTypeConfiguration : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder builder)
+ {
+ builder.ToTable("Categories");
+ builder.HasKey(x => x.Id);
+
+ builder.Property(x => x.Id)
+ .UseGuidVersion7ValueGenerator()
+ .HasComment("品类ID");
+
+ builder.Property(x => x.Name)
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasComment("品类名称");
+
+ builder.Property(x => x.Code)
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasComment("品类编码");
+
+ builder.Property(x => x.Description)
+ .IsRequired()
+ .HasMaxLength(500)
+ .HasComment("描述");
+
+ builder.Property(x => x.SortOrder)
+ .IsRequired()
+ .HasComment("排序");
+
+ builder.Property(x => x.IsActive)
+ .IsRequired()
+ .HasComment("是否激活");
+
+ builder.Property(x => x.CreatedAt)
+ .IsRequired()
+ .HasComment("创建时间");
+
+ builder.Property(x => x.UpdatedAt)
+ .IsRequired()
+ .HasComment("更新时间");
+
+ builder.HasIndex(x => x.Code).IsUnique().HasDatabaseName("IX_Categories_Code");
+ builder.HasIndex(x => x.SortOrder).HasDatabaseName("IX_Categories_SortOrder");
+ builder.HasIndex(x => x.IsActive).HasDatabaseName("IX_Categories_IsActive");
+ }
+}
diff --git a/Backend/src/Fengling.Backend.Infrastructure/EntityConfigurations/PointsTransactionEntityTypeConfiguration.cs b/Backend/src/Fengling.Backend.Infrastructure/EntityConfigurations/PointsTransactionEntityTypeConfiguration.cs
index 2a94a8b..daebedb 100644
--- a/Backend/src/Fengling.Backend.Infrastructure/EntityConfigurations/PointsTransactionEntityTypeConfiguration.cs
+++ b/Backend/src/Fengling.Backend.Infrastructure/EntityConfigurations/PointsTransactionEntityTypeConfiguration.cs
@@ -48,7 +48,7 @@ public class PointsTransactionEntityTypeConfiguration : IEntityTypeConfiguration
// 索引
builder.HasIndex(x => x.MemberId).HasDatabaseName("IX_PointsTransactions_MemberId");
- builder.HasIndex(x => x.RelatedId).IsUnique().HasDatabaseName("IX_PointsTransactions_RelatedId");
+ builder.HasIndex(x => x.RelatedId).HasDatabaseName("IX_PointsTransactions_RelatedId");
builder.HasIndex(x => x.Type).HasDatabaseName("IX_PointsTransactions_Type");
builder.HasIndex(x => x.CreatedAt).HasDatabaseName("IX_PointsTransactions_CreatedAt");
}
diff --git a/Backend/src/Fengling.Backend.Infrastructure/EntityConfigurations/ProductEntityTypeConfiguration.cs b/Backend/src/Fengling.Backend.Infrastructure/EntityConfigurations/ProductEntityTypeConfiguration.cs
new file mode 100644
index 0000000..94521b2
--- /dev/null
+++ b/Backend/src/Fengling.Backend.Infrastructure/EntityConfigurations/ProductEntityTypeConfiguration.cs
@@ -0,0 +1,50 @@
+using Fengling.Backend.Domain.AggregatesModel.ProductAggregate;
+
+namespace Fengling.Backend.Infrastructure.EntityConfigurations;
+
+public class ProductEntityTypeConfiguration : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder builder)
+ {
+ builder.ToTable("Products");
+ builder.HasKey(x => x.Id);
+
+ builder.Property(x => x.Id)
+ .UseGuidVersion7ValueGenerator()
+ .HasComment("产品ID");
+
+ builder.Property(x => x.Name)
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasComment("产品名称");
+
+ builder.Property(x => x.CategoryId)
+ .IsRequired()
+ .HasComment("品类ID");
+
+ builder.Property(x => x.CategoryName)
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasComment("品类名称");
+
+ builder.Property(x => x.Description)
+ .IsRequired()
+ .HasMaxLength(500)
+ .HasComment("描述");
+
+ builder.Property(x => x.IsActive)
+ .IsRequired()
+ .HasComment("是否激活");
+
+ builder.Property(x => x.CreatedAt)
+ .IsRequired()
+ .HasComment("创建时间");
+
+ builder.Property(x => x.UpdatedAt)
+ .IsRequired()
+ .HasComment("更新时间");
+
+ builder.HasIndex(x => x.CategoryId).HasDatabaseName("IX_Products_CategoryId");
+ builder.HasIndex(x => x.IsActive).HasDatabaseName("IX_Products_IsActive");
+ }
+}
diff --git a/Backend/src/Fengling.Backend.Infrastructure/Migrations/20260211044819_Init.Designer.cs b/Backend/src/Fengling.Backend.Infrastructure/Migrations/20260211061437_Init.Designer.cs
similarity index 85%
rename from Backend/src/Fengling.Backend.Infrastructure/Migrations/20260211044819_Init.Designer.cs
rename to Backend/src/Fengling.Backend.Infrastructure/Migrations/20260211061437_Init.Designer.cs
index 3754614..f7fad15 100644
--- a/Backend/src/Fengling.Backend.Infrastructure/Migrations/20260211044819_Init.Designer.cs
+++ b/Backend/src/Fengling.Backend.Infrastructure/Migrations/20260211061437_Init.Designer.cs
@@ -11,7 +11,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Fengling.Backend.Infrastructure.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
- [Migration("20260211044819_Init")]
+ [Migration("20260211061437_Init")]
partial class Init
{
///
@@ -355,7 +355,6 @@ namespace Fengling.Backend.Infrastructure.Migrations
.HasDatabaseName("IX_PointsTransactions_MemberId");
b.HasIndex("RelatedId")
- .IsUnique()
.HasDatabaseName("IX_PointsTransactions_RelatedId");
b.HasIndex("Type")
@@ -449,6 +448,112 @@ namespace Fengling.Backend.Infrastructure.Migrations
b.ToTable("RedemptionOrders", (string)null);
});
+ modelBuilder.Entity("NetCorePal.Extensions.DistributedTransactions.CAP.Persistence.CapLock", b =>
+ {
+ b.Property("Key")
+ .HasMaxLength(128)
+ .HasColumnType("TEXT");
+
+ b.Property("Instance")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("LastLockTime")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Key");
+
+ b.ToTable("CAPLock", (string)null);
+ });
+
+ modelBuilder.Entity("NetCorePal.Extensions.DistributedTransactions.CAP.Persistence.PublishedMessage", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Added")
+ .HasColumnType("TEXT");
+
+ b.Property("Content")
+ .HasColumnType("TEXT");
+
+ b.Property("ExpiresAt")
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("TEXT");
+
+ b.Property("Retries")
+ .HasColumnType("INTEGER");
+
+ b.Property("StatusName")
+ .IsRequired()
+ .HasMaxLength(40)
+ .HasColumnType("TEXT");
+
+ b.Property("Version")
+ .HasMaxLength(20)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex(new[] { "ExpiresAt", "StatusName" }, "IX_ExpiresAt_StatusName");
+
+ b.HasIndex(new[] { "Version", "ExpiresAt", "StatusName" }, "IX_Version_ExpiresAt_StatusName");
+
+ b.ToTable("CAPPublishedMessage", (string)null);
+ });
+
+ modelBuilder.Entity("NetCorePal.Extensions.DistributedTransactions.CAP.Persistence.ReceivedMessage", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Added")
+ .HasColumnType("TEXT");
+
+ b.Property("Content")
+ .HasColumnType("TEXT");
+
+ b.Property("ExpiresAt")
+ .HasColumnType("TEXT");
+
+ b.Property("Group")
+ .HasMaxLength(200)
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(400)
+ .HasColumnType("TEXT");
+
+ b.Property("Retries")
+ .HasColumnType("INTEGER");
+
+ b.Property("StatusName")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("TEXT");
+
+ b.Property("Version")
+ .HasMaxLength(20)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex(new[] { "ExpiresAt", "StatusName" }, "IX_ExpiresAt_StatusName")
+ .HasDatabaseName("IX_ExpiresAt_StatusName1");
+
+ b.HasIndex(new[] { "Version", "ExpiresAt", "StatusName" }, "IX_Version_ExpiresAt_StatusName")
+ .HasDatabaseName("IX_Version_ExpiresAt_StatusName1");
+
+ b.ToTable("CAPReceivedMessage", (string)null);
+ });
+
modelBuilder.Entity("Fengling.Backend.Domain.AggregatesModel.MarketingCodeAggregate.MarketingCode", b =>
{
b.OwnsOne("Fengling.Backend.Domain.AggregatesModel.MarketingCodeAggregate.ProductInfo", "ProductInfo", b1 =>
diff --git a/Backend/src/Fengling.Backend.Infrastructure/Migrations/20260211044819_Init.cs b/Backend/src/Fengling.Backend.Infrastructure/Migrations/20260211061437_Init.cs
similarity index 80%
rename from Backend/src/Fengling.Backend.Infrastructure/Migrations/20260211044819_Init.cs
rename to Backend/src/Fengling.Backend.Infrastructure/Migrations/20260211061437_Init.cs
index f2ddc9d..19d2a1d 100644
--- a/Backend/src/Fengling.Backend.Infrastructure/Migrations/20260211044819_Init.cs
+++ b/Backend/src/Fengling.Backend.Infrastructure/Migrations/20260211061437_Init.cs
@@ -11,6 +11,58 @@ namespace Fengling.Backend.Infrastructure.Migrations
///
protected override void Up(MigrationBuilder migrationBuilder)
{
+ migrationBuilder.CreateTable(
+ name: "CAPLock",
+ columns: table => new
+ {
+ Key = table.Column(type: "TEXT", maxLength: 128, nullable: false),
+ Instance = table.Column(type: "TEXT", maxLength: 256, nullable: true),
+ LastLockTime = table.Column(type: "TEXT", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_CAPLock", x => x.Key);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "CAPPublishedMessage",
+ columns: table => new
+ {
+ Id = table.Column(type: "INTEGER", nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ Version = table.Column(type: "TEXT", maxLength: 20, nullable: true),
+ Name = table.Column(type: "TEXT", maxLength: 200, nullable: false),
+ Content = table.Column(type: "TEXT", nullable: true),
+ Retries = table.Column(type: "INTEGER", nullable: true),
+ Added = table.Column(type: "TEXT", nullable: false),
+ ExpiresAt = table.Column(type: "TEXT", nullable: true),
+ StatusName = table.Column(type: "TEXT", maxLength: 40, nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_CAPPublishedMessage", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "CAPReceivedMessage",
+ columns: table => new
+ {
+ Id = table.Column(type: "INTEGER", nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ Version = table.Column(type: "TEXT", maxLength: 20, nullable: true),
+ Name = table.Column(type: "TEXT", maxLength: 400, nullable: false),
+ Group = table.Column(type: "TEXT", maxLength: 200, nullable: true),
+ Content = table.Column(type: "TEXT", nullable: true),
+ Retries = table.Column(type: "INTEGER", nullable: true),
+ Added = table.Column(type: "TEXT", nullable: false),
+ ExpiresAt = table.Column(type: "TEXT", nullable: true),
+ StatusName = table.Column(type: "TEXT", maxLength: 50, nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_CAPReceivedMessage", x => x.Id);
+ });
+
migrationBuilder.CreateTable(
name: "Gifts",
columns: table => new
@@ -160,6 +212,26 @@ namespace Fengling.Backend.Infrastructure.Migrations
table.PrimaryKey("PK_RedemptionOrders", x => x.Id);
});
+ migrationBuilder.CreateIndex(
+ name: "IX_ExpiresAt_StatusName",
+ table: "CAPPublishedMessage",
+ columns: new[] { "ExpiresAt", "StatusName" });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_Version_ExpiresAt_StatusName",
+ table: "CAPPublishedMessage",
+ columns: new[] { "Version", "ExpiresAt", "StatusName" });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_ExpiresAt_StatusName1",
+ table: "CAPReceivedMessage",
+ columns: new[] { "ExpiresAt", "StatusName" });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_Version_ExpiresAt_StatusName1",
+ table: "CAPReceivedMessage",
+ columns: new[] { "Version", "ExpiresAt", "StatusName" });
+
migrationBuilder.CreateIndex(
name: "IX_Gifts_IsOnShelf",
table: "Gifts",
@@ -235,8 +307,7 @@ namespace Fengling.Backend.Infrastructure.Migrations
migrationBuilder.CreateIndex(
name: "IX_PointsTransactions_RelatedId",
table: "PointsTransactions",
- column: "RelatedId",
- unique: true);
+ column: "RelatedId");
migrationBuilder.CreateIndex(
name: "IX_PointsTransactions_Type",
@@ -268,6 +339,15 @@ namespace Fengling.Backend.Infrastructure.Migrations
///
protected override void Down(MigrationBuilder migrationBuilder)
{
+ migrationBuilder.DropTable(
+ name: "CAPLock");
+
+ migrationBuilder.DropTable(
+ name: "CAPPublishedMessage");
+
+ migrationBuilder.DropTable(
+ name: "CAPReceivedMessage");
+
migrationBuilder.DropTable(
name: "Gifts");
diff --git a/Backend/src/Fengling.Backend.Infrastructure/Migrations/20260211074433_AddAdminAggregate.Designer.cs b/Backend/src/Fengling.Backend.Infrastructure/Migrations/20260211074433_AddAdminAggregate.Designer.cs
new file mode 100644
index 0000000..f19be80
--- /dev/null
+++ b/Backend/src/Fengling.Backend.Infrastructure/Migrations/20260211074433_AddAdminAggregate.Designer.cs
@@ -0,0 +1,753 @@
+//
+using System;
+using Fengling.Backend.Infrastructure;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace Fengling.Backend.Infrastructure.Migrations
+{
+ [DbContext(typeof(ApplicationDbContext))]
+ [Migration("20260211074433_AddAdminAggregate")]
+ partial class AddAdminAggregate
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "9.0.0");
+
+ modelBuilder.Entity("Fengling.Backend.Domain.AggregatesModel.AdminAggregate.Admin", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("TEXT")
+ .HasComment("管理员ID");
+
+ b.Property("CreatedAt")
+ .HasColumnType("TEXT")
+ .HasComment("创建时间");
+
+ b.Property("Deleted")
+ .HasColumnType("INTEGER");
+
+ b.Property("LastLoginAt")
+ .HasColumnType("TEXT")
+ .HasComment("最后登录时间");
+
+ b.Property("PasswordHash")
+ .IsRequired()
+ .HasMaxLength(255)
+ .HasColumnType("TEXT")
+ .HasComment("密码哈希");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .HasColumnType("INTEGER");
+
+ b.Property("Status")
+ .HasColumnType("INTEGER")
+ .HasComment("管理员状态(1=Active,2=Disabled)");
+
+ b.Property("Username")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("TEXT")
+ .HasComment("用户名");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Status")
+ .HasDatabaseName("IX_Admins_Status");
+
+ b.HasIndex("Username")
+ .IsUnique()
+ .HasDatabaseName("IX_Admins_Username");
+
+ b.ToTable("Admins", (string)null);
+ });
+
+ modelBuilder.Entity("Fengling.Backend.Domain.AggregatesModel.GiftAggregate.Gift", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("TEXT")
+ .HasComment("礼品ID");
+
+ b.Property("AvailableStock")
+ .HasColumnType("INTEGER")
+ .HasComment("可用库存");
+
+ b.Property("CreatedAt")
+ .HasColumnType("TEXT")
+ .HasComment("创建时间");
+
+ b.Property("Deleted")
+ .HasColumnType("INTEGER");
+
+ b.Property("Description")
+ .IsRequired()
+ .HasMaxLength(500)
+ .HasColumnType("TEXT")
+ .HasComment("描述");
+
+ b.Property("ImageUrl")
+ .IsRequired()
+ .HasMaxLength(500)
+ .HasColumnType("TEXT")
+ .HasComment("图片URL");
+
+ b.Property("IsOnShelf")
+ .HasColumnType("INTEGER")
+ .HasComment("是否上架");
+
+ b.Property("LimitPerMember")
+ .HasColumnType("INTEGER")
+ .HasComment("每人限兑数量");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("TEXT")
+ .HasComment("礼品名称");
+
+ b.Property("RequiredPoints")
+ .HasColumnType("INTEGER")
+ .HasComment("所需积分");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .HasColumnType("INTEGER");
+
+ b.Property("SortOrder")
+ .HasColumnType("INTEGER")
+ .HasComment("排序");
+
+ b.Property("TotalStock")
+ .HasColumnType("INTEGER")
+ .HasComment("总库存");
+
+ b.Property("Type")
+ .HasColumnType("INTEGER")
+ .HasComment("礼品类型(1:实物,2:虚拟,3:自有产品)");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("TEXT")
+ .HasComment("更新时间");
+
+ b.HasKey("Id");
+
+ b.HasIndex("IsOnShelf")
+ .HasDatabaseName("IX_Gifts_IsOnShelf");
+
+ b.HasIndex("SortOrder")
+ .HasDatabaseName("IX_Gifts_SortOrder");
+
+ b.HasIndex("Type")
+ .HasDatabaseName("IX_Gifts_Type");
+
+ b.ToTable("Gifts", (string)null);
+ });
+
+ modelBuilder.Entity("Fengling.Backend.Domain.AggregatesModel.MarketingCodeAggregate.MarketingCode", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("TEXT")
+ .HasComment("营销码ID");
+
+ b.Property("BatchNo")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("TEXT")
+ .HasComment("批次号");
+
+ b.Property("Code")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("TEXT")
+ .HasComment("营销码");
+
+ b.Property("CreatedAt")
+ .HasColumnType("TEXT")
+ .HasComment("创建时间");
+
+ b.Property("Deleted")
+ .HasColumnType("INTEGER");
+
+ b.Property("ExpiryDate")
+ .HasColumnType("TEXT")
+ .HasComment("过期时间");
+
+ b.Property("IsUsed")
+ .HasColumnType("INTEGER")
+ .HasComment("是否已使用");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .HasColumnType("INTEGER");
+
+ b.Property("UsedAt")
+ .HasColumnType("TEXT")
+ .HasComment("使用时间");
+
+ b.Property("UsedByMemberId")
+ .HasColumnType("TEXT")
+ .HasComment("使用者会员ID");
+
+ b.HasKey("Id");
+
+ b.HasIndex("BatchNo")
+ .HasDatabaseName("IX_MarketingCodes_BatchNo");
+
+ b.HasIndex("Code")
+ .IsUnique()
+ .HasDatabaseName("IX_MarketingCodes_Code");
+
+ b.HasIndex("IsUsed")
+ .HasDatabaseName("IX_MarketingCodes_IsUsed");
+
+ b.ToTable("MarketingCodes", (string)null);
+ });
+
+ modelBuilder.Entity("Fengling.Backend.Domain.AggregatesModel.MemberAggregate.Member", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("TEXT")
+ .HasComment("会员ID");
+
+ b.Property("AvailablePoints")
+ .HasColumnType("INTEGER")
+ .HasComment("可用积分");
+
+ b.Property("Deleted")
+ .HasColumnType("INTEGER");
+
+ b.Property("Nickname")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("TEXT")
+ .HasComment("昵称");
+
+ b.Property("Password")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("TEXT")
+ .HasComment("密码(已加密)");
+
+ b.Property("Phone")
+ .IsRequired()
+ .HasMaxLength(20)
+ .HasColumnType("TEXT")
+ .HasComment("手机号");
+
+ b.Property("RegisteredAt")
+ .HasColumnType("TEXT")
+ .HasComment("注册时间");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .HasColumnType("INTEGER");
+
+ b.Property("Status")
+ .HasColumnType("INTEGER")
+ .HasComment("状态(1:正常,2:禁用)");
+
+ b.Property("TotalPoints")
+ .HasColumnType("INTEGER")
+ .HasComment("累计总积分");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Phone")
+ .IsUnique()
+ .HasDatabaseName("IX_Members_Phone");
+
+ b.HasIndex("Status")
+ .HasDatabaseName("IX_Members_Status");
+
+ b.ToTable("Members", (string)null);
+ });
+
+ modelBuilder.Entity("Fengling.Backend.Domain.AggregatesModel.PointsRuleAggregate.PointsRule", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("TEXT")
+ .HasComment("积分规则ID");
+
+ b.Property("BonusMultiplier")
+ .HasColumnType("TEXT")
+ .HasComment("奖励倍数");
+
+ b.Property("CategoryId")
+ .HasColumnType("TEXT")
+ .HasComment("品类ID");
+
+ b.Property("CreatedAt")
+ .HasColumnType("TEXT")
+ .HasComment("创建时间");
+
+ b.Property("Deleted")
+ .HasColumnType("INTEGER");
+
+ b.Property("EndDate")
+ .HasColumnType("TEXT")
+ .HasComment("生效结束时间");
+
+ b.Property("IsActive")
+ .HasColumnType("INTEGER")
+ .HasComment("是否激活");
+
+ b.Property("MemberLevelCode")
+ .HasMaxLength(20)
+ .HasColumnType("TEXT")
+ .HasComment("会员等级编码");
+
+ b.Property("PointsValue")
+ .HasColumnType("INTEGER")
+ .HasComment("积分值");
+
+ b.Property("ProductId")
+ .HasColumnType("TEXT")
+ .HasComment("产品ID");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .HasColumnType("INTEGER");
+
+ b.Property("RuleName")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("TEXT")
+ .HasComment("规则名称");
+
+ b.Property("RuleType")
+ .HasColumnType("INTEGER")
+ .HasComment("规则类型(1:产品,2:时间,3:会员等级)");
+
+ b.Property("StartDate")
+ .HasColumnType("TEXT")
+ .HasComment("生效开始时间");
+
+ b.HasKey("Id");
+
+ b.HasIndex("IsActive")
+ .HasDatabaseName("IX_PointsRules_IsActive");
+
+ b.HasIndex("MemberLevelCode")
+ .HasDatabaseName("IX_PointsRules_MemberLevelCode");
+
+ b.HasIndex("ProductId")
+ .HasDatabaseName("IX_PointsRules_ProductId");
+
+ b.HasIndex("StartDate", "EndDate")
+ .HasDatabaseName("IX_PointsRules_DateRange");
+
+ b.ToTable("PointsRules", (string)null);
+ });
+
+ modelBuilder.Entity("Fengling.Backend.Domain.AggregatesModel.PointsTransactionAggregate.PointsTransaction", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("TEXT")
+ .HasComment("积分流水ID");
+
+ b.Property("Amount")
+ .HasColumnType("INTEGER")
+ .HasComment("积分数量");
+
+ b.Property("CreatedAt")
+ .HasColumnType("TEXT")
+ .HasComment("创建时间");
+
+ b.Property("Deleted")
+ .HasColumnType("INTEGER");
+
+ b.Property("ExpiryDate")
+ .HasColumnType("TEXT")
+ .HasComment("过期时间");
+
+ b.Property("MemberId")
+ .HasColumnType("TEXT")
+ .HasComment("会员ID");
+
+ b.Property("Reason")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("TEXT")
+ .HasComment("原因描述");
+
+ b.Property("RelatedId")
+ .HasColumnType("TEXT")
+ .HasComment("关联ID");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .HasColumnType("INTEGER");
+
+ b.Property("Source")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("TEXT")
+ .HasComment("来源");
+
+ b.Property("Type")
+ .HasColumnType("INTEGER")
+ .HasComment("交易类型(1:获得,2:消费,3:过期,4:退还)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CreatedAt")
+ .HasDatabaseName("IX_PointsTransactions_CreatedAt");
+
+ b.HasIndex("MemberId")
+ .HasDatabaseName("IX_PointsTransactions_MemberId");
+
+ b.HasIndex("RelatedId")
+ .HasDatabaseName("IX_PointsTransactions_RelatedId");
+
+ b.HasIndex("Type")
+ .HasDatabaseName("IX_PointsTransactions_Type");
+
+ b.ToTable("PointsTransactions", (string)null);
+ });
+
+ modelBuilder.Entity("Fengling.Backend.Domain.AggregatesModel.RedemptionOrderAggregate.RedemptionOrder", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("TEXT")
+ .HasComment("兑换订单ID");
+
+ b.Property("CancelReason")
+ .HasMaxLength(500)
+ .HasColumnType("TEXT")
+ .HasComment("取消原因");
+
+ b.Property("ConsumedPoints")
+ .HasColumnType("INTEGER")
+ .HasComment("消耗积分");
+
+ b.Property("CreatedAt")
+ .HasColumnType("TEXT")
+ .HasComment("创建时间");
+
+ b.Property("Deleted")
+ .HasColumnType("INTEGER");
+
+ b.Property("GiftId")
+ .HasColumnType("TEXT")
+ .HasComment("礼品ID");
+
+ b.Property("GiftName")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("TEXT")
+ .HasComment("礼品名称");
+
+ b.Property("GiftType")
+ .HasColumnType("INTEGER")
+ .HasComment("礼品类型");
+
+ b.Property("MemberId")
+ .HasColumnType("TEXT")
+ .HasComment("会员ID");
+
+ b.Property("OrderNo")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("TEXT")
+ .HasComment("订单号");
+
+ b.Property("Quantity")
+ .HasColumnType("INTEGER")
+ .HasComment("数量");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .HasColumnType("INTEGER");
+
+ b.Property("Status")
+ .HasColumnType("INTEGER")
+ .HasComment("订单状态(1:待处理,2:已发货,3:已送达,4:已完成,5:已取消)");
+
+ b.Property("TrackingNo")
+ .HasMaxLength(100)
+ .HasColumnType("TEXT")
+ .HasComment("物流单号");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("TEXT")
+ .HasComment("更新时间");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CreatedAt")
+ .HasDatabaseName("IX_RedemptionOrders_CreatedAt");
+
+ b.HasIndex("MemberId")
+ .HasDatabaseName("IX_RedemptionOrders_MemberId");
+
+ b.HasIndex("OrderNo")
+ .IsUnique()
+ .HasDatabaseName("IX_RedemptionOrders_OrderNo");
+
+ b.HasIndex("Status")
+ .HasDatabaseName("IX_RedemptionOrders_Status");
+
+ b.ToTable("RedemptionOrders", (string)null);
+ });
+
+ modelBuilder.Entity("NetCorePal.Extensions.DistributedTransactions.CAP.Persistence.CapLock", b =>
+ {
+ b.Property("Key")
+ .HasMaxLength(128)
+ .HasColumnType("TEXT");
+
+ b.Property("Instance")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("LastLockTime")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Key");
+
+ b.ToTable("CAPLock", (string)null);
+ });
+
+ modelBuilder.Entity("NetCorePal.Extensions.DistributedTransactions.CAP.Persistence.PublishedMessage", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Added")
+ .HasColumnType("TEXT");
+
+ b.Property("Content")
+ .HasColumnType("TEXT");
+
+ b.Property("ExpiresAt")
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("TEXT");
+
+ b.Property("Retries")
+ .HasColumnType("INTEGER");
+
+ b.Property("StatusName")
+ .IsRequired()
+ .HasMaxLength(40)
+ .HasColumnType("TEXT");
+
+ b.Property("Version")
+ .HasMaxLength(20)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex(new[] { "ExpiresAt", "StatusName" }, "IX_ExpiresAt_StatusName");
+
+ b.HasIndex(new[] { "Version", "ExpiresAt", "StatusName" }, "IX_Version_ExpiresAt_StatusName");
+
+ b.ToTable("CAPPublishedMessage", (string)null);
+ });
+
+ modelBuilder.Entity("NetCorePal.Extensions.DistributedTransactions.CAP.Persistence.ReceivedMessage", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Added")
+ .HasColumnType("TEXT");
+
+ b.Property("Content")
+ .HasColumnType("TEXT");
+
+ b.Property("ExpiresAt")
+ .HasColumnType("TEXT");
+
+ b.Property("Group")
+ .HasMaxLength(200)
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(400)
+ .HasColumnType("TEXT");
+
+ b.Property("Retries")
+ .HasColumnType("INTEGER");
+
+ b.Property("StatusName")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("TEXT");
+
+ b.Property("Version")
+ .HasMaxLength(20)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex(new[] { "ExpiresAt", "StatusName" }, "IX_ExpiresAt_StatusName")
+ .HasDatabaseName("IX_ExpiresAt_StatusName1");
+
+ b.HasIndex(new[] { "Version", "ExpiresAt", "StatusName" }, "IX_Version_ExpiresAt_StatusName")
+ .HasDatabaseName("IX_Version_ExpiresAt_StatusName1");
+
+ b.ToTable("CAPReceivedMessage", (string)null);
+ });
+
+ modelBuilder.Entity("Fengling.Backend.Domain.AggregatesModel.MarketingCodeAggregate.MarketingCode", b =>
+ {
+ b.OwnsOne("Fengling.Backend.Domain.AggregatesModel.MarketingCodeAggregate.ProductInfo", "ProductInfo", b1 =>
+ {
+ b1.Property("MarketingCodeId")
+ .HasColumnType("TEXT");
+
+ b1.Property("CategoryId")
+ .HasColumnType("TEXT")
+ .HasColumnName("CategoryId")
+ .HasComment("品类ID");
+
+ b1.Property("CategoryName")
+ .HasMaxLength(100)
+ .HasColumnType("TEXT")
+ .HasColumnName("CategoryName")
+ .HasComment("品类名称");
+
+ b1.Property("ProductId")
+ .HasColumnType("TEXT")
+ .HasColumnName("ProductId")
+ .HasComment("产品ID");
+
+ b1.Property("ProductName")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("TEXT")
+ .HasColumnName("ProductName")
+ .HasComment("产品名称");
+
+ b1.HasKey("MarketingCodeId");
+
+ b1.ToTable("MarketingCodes");
+
+ b1.WithOwner()
+ .HasForeignKey("MarketingCodeId");
+ });
+
+ b.Navigation("ProductInfo")
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Fengling.Backend.Domain.AggregatesModel.MemberAggregate.Member", b =>
+ {
+ b.OwnsOne("Fengling.Backend.Domain.AggregatesModel.MemberAggregate.MemberLevel", "Level", b1 =>
+ {
+ b1.Property("MemberId")
+ .HasColumnType("TEXT");
+
+ b1.Property("BonusRate")
+ .HasColumnType("TEXT")
+ .HasColumnName("BonusRate")
+ .HasComment("积分奖励倍率");
+
+ b1.Property("LevelCode")
+ .IsRequired()
+ .HasMaxLength(20)
+ .HasColumnType("TEXT")
+ .HasColumnName("LevelCode")
+ .HasComment("等级编码");
+
+ b1.Property("LevelName")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("TEXT")
+ .HasColumnName("LevelName")
+ .HasComment("等级名称");
+
+ b1.Property("RequiredPoints")
+ .HasColumnType("INTEGER")
+ .HasColumnName("RequiredPoints")
+ .HasComment("所需积分");
+
+ b1.HasKey("MemberId");
+
+ b1.ToTable("Members");
+
+ b1.WithOwner()
+ .HasForeignKey("MemberId");
+ });
+
+ b.Navigation("Level")
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Fengling.Backend.Domain.AggregatesModel.RedemptionOrderAggregate.RedemptionOrder", b =>
+ {
+ b.OwnsOne("Fengling.Backend.Domain.AggregatesModel.RedemptionOrderAggregate.Address", "ShippingAddress", b1 =>
+ {
+ b1.Property("RedemptionOrderId")
+ .HasColumnType("TEXT");
+
+ b1.Property("City")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("TEXT")
+ .HasColumnName("City")
+ .HasComment("市");
+
+ b1.Property("DetailAddress")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("TEXT")
+ .HasColumnName("DetailAddress")
+ .HasComment("详细地址");
+
+ b1.Property("District")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("TEXT")
+ .HasColumnName("District")
+ .HasComment("区/县");
+
+ b1.Property("Phone")
+ .IsRequired()
+ .HasMaxLength(20)
+ .HasColumnType("TEXT")
+ .HasColumnName("ReceiverPhone")
+ .HasComment("联系电话");
+
+ b1.Property("Province")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("TEXT")
+ .HasColumnName("Province")
+ .HasComment("省");
+
+ b1.Property("ReceiverName")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("TEXT")
+ .HasColumnName("ReceiverName")
+ .HasComment("收货人姓名");
+
+ b1.HasKey("RedemptionOrderId");
+
+ b1.ToTable("RedemptionOrders");
+
+ b1.WithOwner()
+ .HasForeignKey("RedemptionOrderId");
+ });
+
+ b.Navigation("ShippingAddress");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/Backend/src/Fengling.Backend.Infrastructure/Migrations/20260211074433_AddAdminAggregate.cs b/Backend/src/Fengling.Backend.Infrastructure/Migrations/20260211074433_AddAdminAggregate.cs
new file mode 100644
index 0000000..39d8f9d
--- /dev/null
+++ b/Backend/src/Fengling.Backend.Infrastructure/Migrations/20260211074433_AddAdminAggregate.cs
@@ -0,0 +1,51 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Fengling.Backend.Infrastructure.Migrations
+{
+ ///
+ public partial class AddAdminAggregate : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "Admins",
+ columns: table => new
+ {
+ Id = table.Column(type: "TEXT", nullable: false, comment: "管理员ID"),
+ Username = table.Column(type: "TEXT", maxLength: 50, nullable: false, comment: "用户名"),
+ PasswordHash = table.Column(type: "TEXT", maxLength: 255, nullable: false, comment: "密码哈希"),
+ Status = table.Column(type: "INTEGER", nullable: false, comment: "管理员状态(1=Active,2=Disabled)"),
+ LastLoginAt = table.Column(type: "TEXT", nullable: true, comment: "最后登录时间"),
+ CreatedAt = table.Column(type: "TEXT", nullable: false, comment: "创建时间"),
+ Deleted = table.Column(type: "INTEGER", nullable: false),
+ RowVersion = table.Column(type: "INTEGER", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Admins", x => x.Id);
+ });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_Admins_Status",
+ table: "Admins",
+ column: "Status");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_Admins_Username",
+ table: "Admins",
+ column: "Username",
+ unique: true);
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "Admins");
+ }
+ }
+}
diff --git a/Backend/src/Fengling.Backend.Infrastructure/Migrations/20260211084650_AddProductAndCategoryAggregates.Designer.cs b/Backend/src/Fengling.Backend.Infrastructure/Migrations/20260211084650_AddProductAndCategoryAggregates.Designer.cs
new file mode 100644
index 0000000..94ae82a
--- /dev/null
+++ b/Backend/src/Fengling.Backend.Infrastructure/Migrations/20260211084650_AddProductAndCategoryAggregates.Designer.cs
@@ -0,0 +1,873 @@
+//
+using System;
+using Fengling.Backend.Infrastructure;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace Fengling.Backend.Infrastructure.Migrations
+{
+ [DbContext(typeof(ApplicationDbContext))]
+ [Migration("20260211084650_AddProductAndCategoryAggregates")]
+ partial class AddProductAndCategoryAggregates
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "9.0.0");
+
+ modelBuilder.Entity("Fengling.Backend.Domain.AggregatesModel.AdminAggregate.Admin", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("TEXT")
+ .HasComment("管理员ID");
+
+ b.Property("CreatedAt")
+ .HasColumnType("TEXT")
+ .HasComment("创建时间");
+
+ b.Property("Deleted")
+ .HasColumnType("INTEGER");
+
+ b.Property("LastLoginAt")
+ .HasColumnType("TEXT")
+ .HasComment("最后登录时间");
+
+ b.Property("PasswordHash")
+ .IsRequired()
+ .HasMaxLength(255)
+ .HasColumnType("TEXT")
+ .HasComment("密码哈希");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .HasColumnType("INTEGER");
+
+ b.Property("Status")
+ .HasColumnType("INTEGER")
+ .HasComment("管理员状态(1=Active,2=Disabled)");
+
+ b.Property("Username")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("TEXT")
+ .HasComment("用户名");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Status")
+ .HasDatabaseName("IX_Admins_Status");
+
+ b.HasIndex("Username")
+ .IsUnique()
+ .HasDatabaseName("IX_Admins_Username");
+
+ b.ToTable("Admins", (string)null);
+ });
+
+ modelBuilder.Entity("Fengling.Backend.Domain.AggregatesModel.CategoryAggregate.Category", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("TEXT")
+ .HasComment("品类ID");
+
+ b.Property("Code")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("TEXT")
+ .HasComment("品类编码");
+
+ b.Property("CreatedAt")
+ .HasColumnType("TEXT")
+ .HasComment("创建时间");
+
+ b.Property("Deleted")
+ .HasColumnType("INTEGER");
+
+ b.Property("Description")
+ .IsRequired()
+ .HasMaxLength(500)
+ .HasColumnType("TEXT")
+ .HasComment("描述");
+
+ b.Property("IsActive")
+ .HasColumnType("INTEGER")
+ .HasComment("是否激活");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("TEXT")
+ .HasComment("品类名称");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .HasColumnType("INTEGER");
+
+ b.Property("SortOrder")
+ .HasColumnType("INTEGER")
+ .HasComment("排序");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("TEXT")
+ .HasComment("更新时间");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Code")
+ .IsUnique()
+ .HasDatabaseName("IX_Categories_Code");
+
+ b.HasIndex("IsActive")
+ .HasDatabaseName("IX_Categories_IsActive");
+
+ b.HasIndex("SortOrder")
+ .HasDatabaseName("IX_Categories_SortOrder");
+
+ b.ToTable("Categories", (string)null);
+ });
+
+ modelBuilder.Entity("Fengling.Backend.Domain.AggregatesModel.GiftAggregate.Gift", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("TEXT")
+ .HasComment("礼品ID");
+
+ b.Property("AvailableStock")
+ .HasColumnType("INTEGER")
+ .HasComment("可用库存");
+
+ b.Property("CreatedAt")
+ .HasColumnType("TEXT")
+ .HasComment("创建时间");
+
+ b.Property("Deleted")
+ .HasColumnType("INTEGER");
+
+ b.Property("Description")
+ .IsRequired()
+ .HasMaxLength(500)
+ .HasColumnType("TEXT")
+ .HasComment("描述");
+
+ b.Property("ImageUrl")
+ .IsRequired()
+ .HasMaxLength(500)
+ .HasColumnType("TEXT")
+ .HasComment("图片URL");
+
+ b.Property("IsOnShelf")
+ .HasColumnType("INTEGER")
+ .HasComment("是否上架");
+
+ b.Property("LimitPerMember")
+ .HasColumnType("INTEGER")
+ .HasComment("每人限兑数量");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("TEXT")
+ .HasComment("礼品名称");
+
+ b.Property("RequiredPoints")
+ .HasColumnType("INTEGER")
+ .HasComment("所需积分");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .HasColumnType("INTEGER");
+
+ b.Property("SortOrder")
+ .HasColumnType("INTEGER")
+ .HasComment("排序");
+
+ b.Property("TotalStock")
+ .HasColumnType("INTEGER")
+ .HasComment("总库存");
+
+ b.Property("Type")
+ .HasColumnType("INTEGER")
+ .HasComment("礼品类型(1:实物,2:虚拟,3:自有产品)");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("TEXT")
+ .HasComment("更新时间");
+
+ b.HasKey("Id");
+
+ b.HasIndex("IsOnShelf")
+ .HasDatabaseName("IX_Gifts_IsOnShelf");
+
+ b.HasIndex("SortOrder")
+ .HasDatabaseName("IX_Gifts_SortOrder");
+
+ b.HasIndex("Type")
+ .HasDatabaseName("IX_Gifts_Type");
+
+ b.ToTable("Gifts", (string)null);
+ });
+
+ modelBuilder.Entity("Fengling.Backend.Domain.AggregatesModel.MarketingCodeAggregate.MarketingCode", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("TEXT")
+ .HasComment("营销码ID");
+
+ b.Property("BatchNo")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("TEXT")
+ .HasComment("批次号");
+
+ b.Property("Code")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("TEXT")
+ .HasComment("营销码");
+
+ b.Property("CreatedAt")
+ .HasColumnType("TEXT")
+ .HasComment("创建时间");
+
+ b.Property("Deleted")
+ .HasColumnType("INTEGER");
+
+ b.Property("ExpiryDate")
+ .HasColumnType("TEXT")
+ .HasComment("过期时间");
+
+ b.Property("IsUsed")
+ .HasColumnType("INTEGER")
+ .HasComment("是否已使用");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .HasColumnType("INTEGER");
+
+ b.Property("UsedAt")
+ .HasColumnType("TEXT")
+ .HasComment("使用时间");
+
+ b.Property("UsedByMemberId")
+ .HasColumnType("TEXT")
+ .HasComment("使用者会员ID");
+
+ b.HasKey("Id");
+
+ b.HasIndex("BatchNo")
+ .HasDatabaseName("IX_MarketingCodes_BatchNo");
+
+ b.HasIndex("Code")
+ .IsUnique()
+ .HasDatabaseName("IX_MarketingCodes_Code");
+
+ b.HasIndex("IsUsed")
+ .HasDatabaseName("IX_MarketingCodes_IsUsed");
+
+ b.ToTable("MarketingCodes", (string)null);
+ });
+
+ modelBuilder.Entity("Fengling.Backend.Domain.AggregatesModel.MemberAggregate.Member", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("TEXT")
+ .HasComment("会员ID");
+
+ b.Property("AvailablePoints")
+ .HasColumnType("INTEGER")
+ .HasComment("可用积分");
+
+ b.Property("Deleted")
+ .HasColumnType("INTEGER");
+
+ b.Property("Nickname")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("TEXT")
+ .HasComment("昵称");
+
+ b.Property("Password")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("TEXT")
+ .HasComment("密码(已加密)");
+
+ b.Property("Phone")
+ .IsRequired()
+ .HasMaxLength(20)
+ .HasColumnType("TEXT")
+ .HasComment("手机号");
+
+ b.Property("RegisteredAt")
+ .HasColumnType("TEXT")
+ .HasComment("注册时间");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .HasColumnType("INTEGER");
+
+ b.Property("Status")
+ .HasColumnType("INTEGER")
+ .HasComment("状态(1:正常,2:禁用)");
+
+ b.Property("TotalPoints")
+ .HasColumnType("INTEGER")
+ .HasComment("累计总积分");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Phone")
+ .IsUnique()
+ .HasDatabaseName("IX_Members_Phone");
+
+ b.HasIndex("Status")
+ .HasDatabaseName("IX_Members_Status");
+
+ b.ToTable("Members", (string)null);
+ });
+
+ modelBuilder.Entity("Fengling.Backend.Domain.AggregatesModel.PointsRuleAggregate.PointsRule", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("TEXT")
+ .HasComment("积分规则ID");
+
+ b.Property("BonusMultiplier")
+ .HasColumnType("TEXT")
+ .HasComment("奖励倍数");
+
+ b.Property("CategoryId")
+ .HasColumnType("TEXT")
+ .HasComment("品类ID");
+
+ b.Property("CreatedAt")
+ .HasColumnType("TEXT")
+ .HasComment("创建时间");
+
+ b.Property("Deleted")
+ .HasColumnType("INTEGER");
+
+ b.Property("EndDate")
+ .HasColumnType("TEXT")
+ .HasComment("生效结束时间");
+
+ b.Property("IsActive")
+ .HasColumnType("INTEGER")
+ .HasComment("是否激活");
+
+ b.Property("MemberLevelCode")
+ .HasMaxLength(20)
+ .HasColumnType("TEXT")
+ .HasComment("会员等级编码");
+
+ b.Property("PointsValue")
+ .HasColumnType("INTEGER")
+ .HasComment("积分值");
+
+ b.Property