From 715cbb3d36613cb063736a1cd5bb5b37a05bd6e4 Mon Sep 17 00:00:00 2001
From: MSuMshk <2039814060@qq.com>
Date: Fri, 12 Dec 2025 10:39:51 +0800
Subject: [PATCH] docs: add xml comments and update ignore rules
---
.gitignore | 3 +
MISSING_XML_DOCS.md | 23 --
.../App/Persistence/TakeoutAppDbContext.cs | 262 ++++++++++++++++--
.../TakeoutAppDesignTimeDbContextFactory.cs | 12 +-
.../DesignTimeDbContextFactoryBase.cs | 29 ++
.../ModelBuilderCommentExtensions.cs | 10 +
.../DictionaryDesignTimeDbContextFactory.cs | 12 +-
.../Repositories/EfDictionaryRepository.cs | 83 ++++++
.../Services/DistributedDictionaryCache.cs | 26 ++
.../Persistence/EfMiniUserRepository.cs | 26 ++
.../Persistence/EfPermissionRepository.cs | 70 +++++
.../Persistence/EfRolePermissionRepository.cs | 29 ++
.../Identity/Persistence/EfRoleRepository.cs | 61 ++++
.../Persistence/EfRoleTemplateRepository.cs | 68 +++++
.../Persistence/EfUserRoleRepository.cs | 32 +++
.../Persistence/IdentityDataSeeder.cs | 41 ++-
.../IdentityDesignTimeDbContextFactory.cs | 12 +-
.../Services/RedisLoginRateLimiter.cs | 14 +
.../Services/RedisRefreshTokenStore.cs | 24 ++
.../Identity/Services/WeChatAuthService.cs | 11 +
.../PermissionAuthorizeAttribute.cs | 16 +-
.../PermissionAuthorizationHandler.cs | 23 +-
.../PermissionAuthorizationPolicyProvider.cs | 65 +++--
.../TenantContextAccessor.cs | 8 +-
24 files changed, 865 insertions(+), 95 deletions(-)
diff --git a/.gitignore b/.gitignore
index de1ce4f..8c8e5b2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,6 @@ obj/
.claude/
*.log
/src/Api/TakeoutSaaS.AdminApi/TakeoutSaaS.AdminApi.csproj.user
+
+# 保留根目录 scripts 目录提交
+!scripts/
diff --git a/MISSING_XML_DOCS.md b/MISSING_XML_DOCS.md
index 5927194..5b962d5 100644
--- a/MISSING_XML_DOCS.md
+++ b/MISSING_XML_DOCS.md
@@ -76,26 +76,3 @@
- src/Core/TakeoutSaaS.Shared.Web/Middleware/ExceptionHandlingMiddleware.cs:34
- src/Core/TakeoutSaaS.Shared.Web/Middleware/RequestLoggingMiddleware.cs:13
- src/Core/TakeoutSaaS.Shared.Web/Middleware/SecurityHeadersMiddleware.cs:10
-- src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/TakeoutAppDbContext.cs:39
-- src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/TakeoutAppDesignTimeDbContextFactory.cs:15
-- src/Infrastructure/TakeoutSaaS.Infrastructure/Common/Persistence/DesignTime/DesignTimeDbContextFactoryBase.cs:30
-- src/Infrastructure/TakeoutSaaS.Infrastructure/Common/Persistence/ModelBuilderCommentExtensions.cs:14
-- src/Infrastructure/TakeoutSaaS.Infrastructure/Dictionary/Persistence/DictionaryDesignTimeDbContextFactory.cs:15
-- src/Infrastructure/TakeoutSaaS.Infrastructure/Dictionary/Repositories/EfDictionaryRepository.cs:14
-- src/Infrastructure/TakeoutSaaS.Infrastructure/Dictionary/Services/DistributedDictionaryCache.cs:18
-- src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/EfIdentityUserRepository.cs:12
-- src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/EfMiniUserRepository.cs:12
-- src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/EfPermissionRepository.cs:12
-- src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/EfRolePermissionRepository.cs:12
-- src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/EfRoleRepository.cs:12
-- src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/EfRoleTemplateRepository.cs:12
-- src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/EfUserRoleRepository.cs:12
-- src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/IdentityDataSeeder.cs:25
-- src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/IdentityDesignTimeDbContextFactory.cs:15
-- src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Services/RedisLoginRateLimiter.cs:17
-- src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Services/RedisRefreshTokenStore.cs:19
-- src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Services/WeChatAuthService.cs:18
-- src/Modules/TakeoutSaaS.Module.Authorization/Attributes/PermissionAuthorizeAttribute.cs:12
-- src/Modules/TakeoutSaaS.Module.Authorization/Policies/PermissionAuthorizationHandler.cs:10
-- src/Modules/TakeoutSaaS.Module.Authorization/Policies/PermissionAuthorizationPolicyProvider.cs:11
-- src/Modules/TakeoutSaaS.Module.Tenancy/TenantContextAccessor.cs:31
diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/TakeoutAppDbContext.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/TakeoutAppDbContext.cs
index 19ecffe..7320052 100644
--- a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/TakeoutAppDbContext.cs
+++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/TakeoutAppDbContext.cs
@@ -36,107 +36,331 @@ public sealed class TakeoutAppDbContext(
IIdGenerator? idGenerator = null)
: TenantAwareDbContext(options, tenantProvider, currentUserAccessor, idGenerator)
{
+ ///
+ /// 租户聚合根。
+ ///
public DbSet Tenants => Set();
+ ///
+ /// 租户套餐。
+ ///
public DbSet TenantPackages => Set();
+ ///
+ /// 租户订阅。
+ ///
public DbSet TenantSubscriptions => Set();
+ ///
+ /// 租户订阅历史。
+ ///
public DbSet TenantSubscriptionHistories => Set();
+ ///
+ /// 租户配额使用记录。
+ ///
public DbSet TenantQuotaUsages => Set();
+ ///
+ /// 租户账单。
+ ///
public DbSet TenantBillingStatements => Set();
+ ///
+ /// 租户通知。
+ ///
public DbSet TenantNotifications => Set();
+ ///
+ /// 租户公告。
+ ///
public DbSet TenantAnnouncements => Set();
+ ///
+ /// 租户公告已读记录。
+ ///
public DbSet TenantAnnouncementReads => Set();
+ ///
+ /// 租户认证资料。
+ ///
public DbSet TenantVerificationProfiles => Set();
+ ///
+ /// 租户审计日志。
+ ///
public DbSet TenantAuditLogs => Set();
-
+ ///
+ /// 商户实体。
+ ///
public DbSet Merchants => Set();
+ ///
+ /// 商户资质文件。
+ ///
public DbSet MerchantDocuments => Set();
+ ///
+ /// 商户合同。
+ ///
public DbSet MerchantContracts => Set();
+ ///
+ /// 商户员工。
+ ///
public DbSet MerchantStaff => Set();
+ ///
+ /// 商户审计日志。
+ ///
public DbSet MerchantAuditLogs => Set();
+ ///
+ /// 商户分类。
+ ///
public DbSet MerchantCategories => Set();
-
+ ///
+ /// 门店实体。
+ ///
public DbSet Stores => Set();
+ ///
+ /// 门店营业时间。
+ ///
public DbSet StoreBusinessHours => Set();
+ ///
+ /// 门店节假日。
+ ///
public DbSet StoreHolidays => Set();
+ ///
+ /// 门店配送区域。
+ ///
public DbSet StoreDeliveryZones => Set();
+ ///
+ /// 门店桌台区域。
+ ///
public DbSet StoreTableAreas => Set();
+ ///
+ /// 门店桌台。
+ ///
public DbSet StoreTables => Set();
+ ///
+ /// 门店员工班次。
+ ///
public DbSet StoreEmployeeShifts => Set();
+ ///
+ /// 自提配置。
+ ///
public DbSet StorePickupSettings => Set();
+ ///
+ /// 自提时间段。
+ ///
public DbSet StorePickupSlots => Set();
-
+ ///
+ /// 商品分类。
+ ///
public DbSet ProductCategories => Set();
+ ///
+ /// 商品。
+ ///
public DbSet Products => Set();
+ ///
+ /// 商品属性组。
+ ///
public DbSet ProductAttributeGroups => Set();
+ ///
+ /// 商品属性项。
+ ///
public DbSet ProductAttributeOptions => Set();
+ ///
+ /// SKU 实体。
+ ///
public DbSet ProductSkus => Set();
+ ///
+ /// 加料分组。
+ ///
public DbSet ProductAddonGroups => Set();
+ ///
+ /// 加料选项。
+ ///
public DbSet ProductAddonOptions => Set();
+ ///
+ /// 定价规则。
+ ///
public DbSet ProductPricingRules => Set();
+ ///
+ /// 商品媒体资源。
+ ///
public DbSet ProductMediaAssets => Set();
-
+ ///
+ /// 库存项目。
+ ///
public DbSet InventoryItems => Set();
+ ///
+ /// 库存调整记录。
+ ///
public DbSet InventoryAdjustments => Set();
+ ///
+ /// 库存批次。
+ ///
public DbSet InventoryBatches => Set();
+ ///
+ /// 库存锁定记录。
+ ///
public DbSet InventoryLockRecords => Set();
-
+ ///
+ /// 购物车。
+ ///
public DbSet ShoppingCarts => Set();
+ ///
+ /// 购物车明细。
+ ///
public DbSet CartItems => Set();
+ ///
+ /// 购物车加料。
+ ///
public DbSet CartItemAddons => Set();
+ ///
+ /// 结账会话。
+ ///
public DbSet CheckoutSessions => Set();
-
+ ///
+ /// 订单聚合。
+ ///
public DbSet Orders => Set();
+ ///
+ /// 订单明细。
+ ///
public DbSet OrderItems => Set();
+ ///
+ /// 订单状态流转。
+ ///
public DbSet OrderStatusHistories => Set();
+ ///
+ /// 退款申请。
+ ///
public DbSet RefundRequests => Set();
-
+ ///
+ /// 支付记录。
+ ///
public DbSet PaymentRecords => Set();
+ ///
+ /// 支付退款记录。
+ ///
public DbSet PaymentRefundRecords => Set();
-
+ ///
+ /// 预订记录。
+ ///
public DbSet Reservations => Set();
+ ///
+ /// 排号记录。
+ ///
public DbSet QueueTickets => Set();
-
+ ///
+ /// 配送订单。
+ ///
public DbSet DeliveryOrders => Set();
+ ///
+ /// 配送事件。
+ ///
public DbSet DeliveryEvents => Set();
-
+ ///
+ /// 团购订单。
+ ///
public DbSet GroupOrders => Set();
+ ///
+ /// 团购参与者。
+ ///
public DbSet GroupParticipants => Set();
-
+ ///
+ /// 优惠券模板。
+ ///
public DbSet CouponTemplates => Set();
+ ///
+ /// 优惠券实例。
+ ///
public DbSet Coupons => Set();
+ ///
+ /// 营销活动。
+ ///
public DbSet PromotionCampaigns => Set();
-
+ ///
+ /// 会员档案。
+ ///
public DbSet MemberProfiles => Set();
+ ///
+ /// 会员等级。
+ ///
public DbSet MemberTiers => Set();
+ ///
+ /// 积分流水。
+ ///
public DbSet MemberPointLedgers => Set();
+ ///
+ /// 成长值日志。
+ ///
public DbSet MemberGrowthLogs => Set();
-
+ ///
+ /// 会话记录。
+ ///
public DbSet ChatSessions => Set();
+ ///
+ /// 会话消息。
+ ///
public DbSet ChatMessages => Set();
+ ///
+ /// 工单记录。
+ ///
public DbSet SupportTickets => Set();
+ ///
+ /// 工单评论。
+ ///
public DbSet TicketComments => Set();
-
+ ///
+ /// 分销合作伙伴。
+ ///
public DbSet AffiliatePartners => Set();
+ ///
+ /// 分销订单。
+ ///
public DbSet AffiliateOrders => Set();
+ ///
+ /// 分销结算。
+ ///
public DbSet AffiliatePayouts => Set();
-
+ ///
+ /// 打卡活动。
+ ///
public DbSet CheckInCampaigns => Set();
+ ///
+ /// 打卡记录。
+ ///
public DbSet CheckInRecords => Set();
+ ///
+ /// 社区帖子。
+ ///
public DbSet CommunityPosts => Set();
+ ///
+ /// 社区评论。
+ ///
public DbSet CommunityComments => Set();
+ ///
+ /// 社区互动。
+ ///
public DbSet CommunityReactions => Set();
-
+ ///
+ /// 地图位置。
+ ///
public DbSet MapLocations => Set();
+ ///
+ /// 导航请求。
+ ///
public DbSet NavigationRequests => Set();
-
+ ///
+ /// 指标定义。
+ ///
public DbSet MetricDefinitions => Set();
+ ///
+ /// 指标快照。
+ ///
public DbSet MetricSnapshots => Set();
+ ///
+ /// 告警规则。
+ ///
public DbSet MetricAlertRules => Set();
-
+ ///
+ /// 配置实体映射关系。
+ ///
+ /// 模型构建器。
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
+ // 1. 调用基类配置
base.OnModelCreating(modelBuilder);
-
+ // 2. 配置全部实体映射
ConfigureTenant(modelBuilder.Entity());
ConfigureMerchant(modelBuilder.Entity());
ConfigureStore(modelBuilder.Entity());
diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/TakeoutAppDesignTimeDbContextFactory.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/TakeoutAppDesignTimeDbContextFactory.cs
index e29572b..9146c16 100644
--- a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/TakeoutAppDesignTimeDbContextFactory.cs
+++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/TakeoutAppDesignTimeDbContextFactory.cs
@@ -12,11 +12,21 @@ namespace TakeoutSaaS.Infrastructure.App.Persistence;
internal sealed class TakeoutAppDesignTimeDbContextFactory
: DesignTimeDbContextFactoryBase
{
+ ///
+ /// 初始化业务库设计时上下文工厂。
+ ///
public TakeoutAppDesignTimeDbContextFactory()
: base(DatabaseConstants.AppDataSource, "TAKEOUTSAAS_APP_CONNECTION")
{
}
-
+ // 创建设计时上下文
+ ///
+ /// 创建设计时的业务库 DbContext。
+ ///
+ /// 上下文选项。
+ /// 租户提供器。
+ /// 当前用户访问器。
+ /// 业务库上下文实例。
protected override TakeoutAppDbContext CreateContext(
DbContextOptions options,
ITenantProvider tenantProvider,
diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Common/Persistence/DesignTime/DesignTimeDbContextFactoryBase.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Common/Persistence/DesignTime/DesignTimeDbContextFactoryBase.cs
index a2dcba9..177a576 100644
--- a/src/Infrastructure/TakeoutSaaS.Infrastructure/Common/Persistence/DesignTime/DesignTimeDbContextFactoryBase.cs
+++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Common/Persistence/DesignTime/DesignTimeDbContextFactoryBase.cs
@@ -16,6 +16,11 @@ internal abstract class DesignTimeDbContextFactoryBase : IDesignTimeDb
private readonly string _dataSourceName;
private readonly string? _connectionStringEnvVar;
+ ///
+ /// 初始化设计时工厂基类。
+ ///
+ /// 数据源名称。
+ /// 连接串环境变量名。
protected DesignTimeDbContextFactoryBase(string dataSourceName, string? connectionStringEnvVar = null)
{
if (string.IsNullOrWhiteSpace(dataSourceName))
@@ -27,8 +32,14 @@ internal abstract class DesignTimeDbContextFactoryBase : IDesignTimeDb
_connectionStringEnvVar = connectionStringEnvVar;
}
+ ///
+ /// 创建设计时 DbContext。
+ ///
+ /// 命令行参数。
+ /// DbContext 实例。
public TContext CreateDbContext(string[] args)
{
+ // 1. 构建 DbContextOptions
var optionsBuilder = new DbContextOptionsBuilder();
optionsBuilder.UseNpgsql(
ResolveConnectionString(),
@@ -38,12 +49,20 @@ internal abstract class DesignTimeDbContextFactoryBase : IDesignTimeDb
npgsql.EnableRetryOnFailure();
});
+ // 2. 创建上下文
return CreateContext(
optionsBuilder.Options,
new DesignTimeTenantProvider(),
new DesignTimeCurrentUserAccessor());
}
+ ///
+ /// 由子类实现的上下文工厂方法。
+ ///
+ /// 上下文选项。
+ /// 租户提供器。
+ /// 当前用户访问器。
+ /// DbContext 实例。
protected abstract TContext CreateContext(
DbContextOptions options,
ITenantProvider tenantProvider,
@@ -138,12 +157,22 @@ internal abstract class DesignTimeDbContextFactoryBase : IDesignTimeDb
private sealed class DesignTimeTenantProvider : ITenantProvider
{
+ ///
+ /// 设计时返回默认租户 ID。
+ ///
+ /// 默认租户 ID。
public long GetCurrentTenantId() => 0;
}
private sealed class DesignTimeCurrentUserAccessor : ICurrentUserAccessor
{
+ ///
+ /// 设计时用户标识。
+ ///
public long UserId => 0;
+ ///
+ /// 设计时用户鉴权标识。
+ ///
public bool IsAuthenticated => false;
}
}
diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Common/Persistence/ModelBuilderCommentExtensions.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Common/Persistence/ModelBuilderCommentExtensions.cs
index 193409c..810759e 100644
--- a/src/Infrastructure/TakeoutSaaS.Infrastructure/Common/Persistence/ModelBuilderCommentExtensions.cs
+++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Common/Persistence/ModelBuilderCommentExtensions.cs
@@ -11,6 +11,10 @@ namespace TakeoutSaaS.Infrastructure.Common.Persistence;
///
internal static class ModelBuilderCommentExtensions
{
+ ///
+ /// 将 XML 注释应用到实体与属性的 Comment。
+ ///
+ /// 模型构建器。
public static void ApplyXmlComments(this ModelBuilder modelBuilder)
{
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
@@ -51,6 +55,12 @@ internal static class ModelBuilderCommentExtensions
{
private static readonly ConcurrentDictionary> Cache = new();
+ ///
+ /// 尝试获取成员的摘要注释。
+ ///
+ /// 反射成员。
+ /// 输出的摘要文本。
+ /// 存在摘要则返回 true。
public static bool TryGetSummary(MemberInfo member, out string? summary)
{
summary = null;
diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Dictionary/Persistence/DictionaryDesignTimeDbContextFactory.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Dictionary/Persistence/DictionaryDesignTimeDbContextFactory.cs
index 45d82e2..774c649 100644
--- a/src/Infrastructure/TakeoutSaaS.Infrastructure/Dictionary/Persistence/DictionaryDesignTimeDbContextFactory.cs
+++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Dictionary/Persistence/DictionaryDesignTimeDbContextFactory.cs
@@ -12,11 +12,21 @@ namespace TakeoutSaaS.Infrastructure.Dictionary.Persistence;
internal sealed class DictionaryDesignTimeDbContextFactory
: DesignTimeDbContextFactoryBase
{
+ ///
+ /// 初始化字典库设计时上下文工厂。
+ ///
public DictionaryDesignTimeDbContextFactory()
: base(DatabaseConstants.DictionaryDataSource, "TAKEOUTSAAS_DICTIONARY_CONNECTION")
{
}
-
+ // 创建设计时上下文
+ ///
+ /// 创建设计时的 DictionaryDbContext。
+ ///
+ /// 上下文配置。
+ /// 租户提供器。
+ /// 当前用户访问器。
+ /// DictionaryDbContext 实例。
protected override DictionaryDbContext CreateContext(
DbContextOptions options,
ITenantProvider tenantProvider,
diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Dictionary/Repositories/EfDictionaryRepository.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Dictionary/Repositories/EfDictionaryRepository.cs
index 4ce7005..18e7bbe 100644
--- a/src/Infrastructure/TakeoutSaaS.Infrastructure/Dictionary/Repositories/EfDictionaryRepository.cs
+++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Dictionary/Repositories/EfDictionaryRepository.cs
@@ -11,42 +11,92 @@ namespace TakeoutSaaS.Infrastructure.Dictionary.Repositories;
///
public sealed class EfDictionaryRepository(DictionaryDbContext context) : IDictionaryRepository
{
+ ///
+ /// 根据分组 ID 查询分组。
+ ///
+ /// 分组 ID。
+ /// 取消标记。
+ /// 匹配分组或 null。
public Task FindGroupByIdAsync(long id, CancellationToken cancellationToken = default)
=> context.DictionaryGroups.FirstOrDefaultAsync(group => group.Id == id, cancellationToken);
+ ///
+ /// 根据分组编码查询分组。
+ ///
+ /// 分组编码。
+ /// 取消标记。
+ /// 匹配分组或 null。
public Task FindGroupByCodeAsync(string code, CancellationToken cancellationToken = default)
=> context.DictionaryGroups.FirstOrDefaultAsync(group => group.Code == code, cancellationToken);
+ ///
+ /// 搜索分组列表。
+ ///
+ /// 字典作用域。
+ /// 取消标记。
+ /// 分组列表。
public async Task> SearchGroupsAsync(DictionaryScope? scope, CancellationToken cancellationToken = default)
{
+ // 1. 构建分组查询
var query = context.DictionaryGroups.AsNoTracking();
if (scope.HasValue)
{
+ // 2. 按作用域过滤
query = query.Where(group => group.Scope == scope.Value);
}
+ // 3. 排序返回
return await query
.OrderBy(group => group.Code)
.ToListAsync(cancellationToken);
}
+ ///
+ /// 新增分组。
+ ///
+ /// 分组实体。
+ /// 取消标记。
+ /// 异步任务。
public Task AddGroupAsync(DictionaryGroup group, CancellationToken cancellationToken = default)
{
+ // 1. 添加分组
context.DictionaryGroups.Add(group);
+ // 2. 返回完成任务
return Task.CompletedTask;
}
+ ///
+ /// 删除分组。
+ ///
+ /// 分组实体。
+ /// 取消标记。
+ /// 异步任务。
public Task RemoveGroupAsync(DictionaryGroup group, CancellationToken cancellationToken = default)
{
+ // 1. 移除分组
context.DictionaryGroups.Remove(group);
+ // 2. 返回完成任务
return Task.CompletedTask;
}
+ ///
+ /// 根据条目 ID 查询字典项。
+ ///
+ /// 条目 ID。
+ /// 取消标记。
+ /// 匹配条目或 null。
public Task FindItemByIdAsync(long id, CancellationToken cancellationToken = default)
=> context.DictionaryItems.FirstOrDefaultAsync(item => item.Id == id, cancellationToken);
+ ///
+ /// 获取指定分组下的条目列表。
+ ///
+ /// 分组 ID。
+ /// 取消标记。
+ /// 条目列表。
public async Task> GetItemsByGroupIdAsync(long groupId, CancellationToken cancellationToken = default)
{
+ // 1. 过滤分组
return await context.DictionaryItems
.AsNoTracking()
.Where(item => item.GroupId == groupId)
@@ -54,23 +104,53 @@ public sealed class EfDictionaryRepository(DictionaryDbContext context) : IDicti
.ToListAsync(cancellationToken);
}
+ ///
+ /// 新增字典项。
+ ///
+ /// 字典项。
+ /// 取消标记。
+ /// 异步任务。
public Task AddItemAsync(DictionaryItem item, CancellationToken cancellationToken = default)
{
+ // 1. 添加条目
context.DictionaryItems.Add(item);
+ // 2. 返回完成任务
return Task.CompletedTask;
}
+ ///
+ /// 删除字典项。
+ ///
+ /// 字典项。
+ /// 取消标记。
+ /// 异步任务。
public Task RemoveItemAsync(DictionaryItem item, CancellationToken cancellationToken = default)
{
+ // 1. 移除条目
context.DictionaryItems.Remove(item);
+ // 2. 返回完成任务
return Task.CompletedTask;
}
+ ///
+ /// 持久化变更。
+ ///
+ /// 取消标记。
+ /// 保存任务。
public Task SaveChangesAsync(CancellationToken cancellationToken = default)
=> context.SaveChangesAsync(cancellationToken);
+ ///
+ /// 根据编码集合获取条目列表,可包含系统级条目。
+ ///
+ /// 分组编码集合。
+ /// 租户 ID。
+ /// 是否包含系统级。
+ /// 取消标记。
+ /// 条目列表。
public async Task> GetItemsByCodesAsync(IEnumerable codes, long tenantId, bool includeSystem, CancellationToken cancellationToken = default)
{
+ // 1. 规范化编码
var normalizedCodes = codes
.Where(code => !string.IsNullOrWhiteSpace(code))
.Select(code => code.Trim().ToLowerInvariant())
@@ -82,14 +162,17 @@ public sealed class EfDictionaryRepository(DictionaryDbContext context) : IDicti
return Array.Empty();
}
+ // 2. 构建查询并忽略 QueryFilter
var query = context.DictionaryItems
.AsNoTracking()
.IgnoreQueryFilters()
.Include(item => item.Group)
.Where(item => normalizedCodes.Contains(item.Group!.Code));
+ // 3. 按租户或系统级过滤
query = query.Where(item => item.TenantId == tenantId || (includeSystem && item.TenantId == 0));
+ // 4. 排序返回
return await query
.OrderBy(item => item.SortOrder)
.ToListAsync(cancellationToken);
diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Dictionary/Services/DistributedDictionaryCache.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Dictionary/Services/DistributedDictionaryCache.cs
index 4897f82..800af5e 100644
--- a/src/Infrastructure/TakeoutSaaS.Infrastructure/Dictionary/Services/DistributedDictionaryCache.cs
+++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Dictionary/Services/DistributedDictionaryCache.cs
@@ -15,8 +15,16 @@ public sealed class DistributedDictionaryCache(IDistributedCache cache, IOptions
private readonly DictionaryCacheOptions _options = options.Value;
private readonly JsonSerializerOptions _serializerOptions = new(JsonSerializerDefaults.Web);
+ ///
+ /// 读取指定租户与编码的字典缓存。
+ ///
+ /// 租户 ID。
+ /// 字典编码。
+ /// 取消标记。
+ /// 字典项集合或 null。
public async Task?> GetAsync(long tenantId, string code, CancellationToken cancellationToken = default)
{
+ // 1. 拼装缓存键
var cacheKey = BuildKey(tenantId, code);
var payload = await cache.GetAsync(cacheKey, cancellationToken);
if (payload == null || payload.Length == 0)
@@ -24,11 +32,21 @@ public sealed class DistributedDictionaryCache(IDistributedCache cache, IOptions
return null;
}
+ // 2. 反序列化
return JsonSerializer.Deserialize>(payload, _serializerOptions);
}
+ ///
+ /// 设置指定租户与编码的字典缓存。
+ ///
+ /// 租户 ID。
+ /// 字典编码。
+ /// 字典项集合。
+ /// 取消标记。
+ /// 异步任务。
public Task SetAsync(long tenantId, string code, IReadOnlyList items, CancellationToken cancellationToken = default)
{
+ // 1. 序列化并写入缓存
var cacheKey = BuildKey(tenantId, code);
var payload = JsonSerializer.SerializeToUtf8Bytes(items, _serializerOptions);
var options = new DistributedCacheEntryOptions
@@ -38,8 +56,16 @@ public sealed class DistributedDictionaryCache(IDistributedCache cache, IOptions
return cache.SetAsync(cacheKey, payload, options, cancellationToken);
}
+ ///
+ /// 移除指定租户与编码的缓存。
+ ///
+ /// 租户 ID。
+ /// 字典编码。
+ /// 取消标记。
+ /// 异步任务。
public Task RemoveAsync(long tenantId, string code, CancellationToken cancellationToken = default)
{
+ // 1. 删除缓存键
var cacheKey = BuildKey(tenantId, code);
return cache.RemoveAsync(cacheKey, cancellationToken);
}
diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/EfMiniUserRepository.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/EfMiniUserRepository.cs
index 78c3d52..efb45a4 100644
--- a/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/EfMiniUserRepository.cs
+++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/EfMiniUserRepository.cs
@@ -9,17 +9,41 @@ namespace TakeoutSaaS.Infrastructure.Identity.Persistence;
///
public sealed class EfMiniUserRepository(IdentityDbContext dbContext) : IMiniUserRepository
{
+ ///
+ /// 根据 OpenId 获取小程序用户。
+ ///
+ /// 微信 OpenId。
+ /// 取消标记。
+ /// 匹配的小程序用户或 null。
public Task FindByOpenIdAsync(string openId, CancellationToken cancellationToken = default)
=> dbContext.MiniUsers.AsNoTracking().FirstOrDefaultAsync(x => x.OpenId == openId, cancellationToken);
+ ///
+ /// 根据用户 ID 获取小程序用户。
+ ///
+ /// 用户 ID。
+ /// 取消标记。
+ /// 匹配的小程序用户或 null。
public Task FindByIdAsync(long id, CancellationToken cancellationToken = default)
=> dbContext.MiniUsers.AsNoTracking().FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
+ ///
+ /// 创建或更新小程序用户信息。
+ ///
+ /// 微信 OpenId。
+ /// 微信 UnionId。
+ /// 昵称。
+ /// 头像地址。
+ /// 租户 ID。
+ /// 取消标记。
+ /// 创建或更新后的小程序用户。
public async Task CreateOrUpdateAsync(string openId, string? unionId, string? nickname, string? avatar, long tenantId, CancellationToken cancellationToken = default)
{
+ // 1. 查询现有用户
var user = await dbContext.MiniUsers.FirstOrDefaultAsync(x => x.OpenId == openId, cancellationToken);
if (user == null)
{
+ // 2. 未找到则创建
user = new MiniUser
{
Id = 0,
@@ -33,11 +57,13 @@ public sealed class EfMiniUserRepository(IdentityDbContext dbContext) : IMiniUse
}
else
{
+ // 3. 已存在则更新可变字段
user.UnionId = unionId ?? user.UnionId;
user.Nickname = nickname ?? user.Nickname;
user.Avatar = avatar ?? user.Avatar;
}
+ // 4. 保存更改
await dbContext.SaveChangesAsync(cancellationToken);
return user;
}
diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/EfPermissionRepository.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/EfPermissionRepository.cs
index b8dbafc..e8b0683 100644
--- a/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/EfPermissionRepository.cs
+++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/EfPermissionRepository.cs
@@ -9,66 +9,136 @@ namespace TakeoutSaaS.Infrastructure.Identity.Persistence;
///
public sealed class EfPermissionRepository(IdentityDbContext dbContext) : IPermissionRepository
{
+ ///
+ /// 根据权限 ID 获取权限。
+ ///
+ /// 权限 ID。
+ /// 租户 ID。
+ /// 取消标记。
+ /// 权限实体或 null。
public Task FindByIdAsync(long permissionId, long tenantId, CancellationToken cancellationToken = default)
=> dbContext.Permissions.AsNoTracking().FirstOrDefaultAsync(x => x.Id == permissionId && x.TenantId == tenantId, cancellationToken);
+ ///
+ /// 根据权限编码获取权限。
+ ///
+ /// 权限编码。
+ /// 租户 ID。
+ /// 取消标记。
+ /// 权限实体或 null。
public Task FindByCodeAsync(string code, long tenantId, CancellationToken cancellationToken = default)
=> dbContext.Permissions.AsNoTracking().FirstOrDefaultAsync(x => x.Code == code && x.TenantId == tenantId, cancellationToken);
+ ///
+ /// 根据权限编码集合批量获取权限。
+ ///
+ /// 租户 ID。
+ /// 权限编码集合。
+ /// 取消标记。
+ /// 权限列表。
public Task> GetByCodesAsync(long tenantId, IEnumerable codes, CancellationToken cancellationToken = default)
{
+ // 1. 规范化编码集合
var normalizedCodes = codes
.Where(code => !string.IsNullOrWhiteSpace(code))
.Select(code => code.Trim())
.Distinct()
.ToArray();
+ // 2. 按租户筛选权限
return dbContext.Permissions.AsNoTracking()
.Where(x => x.TenantId == tenantId && normalizedCodes.Contains(x.Code))
.ToListAsync(cancellationToken)
.ContinueWith(t => (IReadOnlyList)t.Result, cancellationToken);
}
+ ///
+ /// 根据权限 ID 集合批量获取权限。
+ ///
+ /// 租户 ID。
+ /// 权限 ID 集合。
+ /// 取消标记。
+ /// 权限列表。
public Task> GetByIdsAsync(long tenantId, IEnumerable permissionIds, CancellationToken cancellationToken = default)
=> dbContext.Permissions.AsNoTracking()
.Where(x => x.TenantId == tenantId && permissionIds.Contains(x.Id))
.ToListAsync(cancellationToken)
.ContinueWith(t => (IReadOnlyList)t.Result, cancellationToken);
+ ///
+ /// 按关键字搜索权限。
+ ///
+ /// 租户 ID。
+ /// 搜索关键字。
+ /// 取消标记。
+ /// 权限列表。
public Task> SearchAsync(long tenantId, string? keyword, CancellationToken cancellationToken = default)
{
+ // 1. 构建基础查询
var query = dbContext.Permissions.AsNoTracking().Where(x => x.TenantId == tenantId);
if (!string.IsNullOrWhiteSpace(keyword))
{
+ // 2. 追加关键字过滤
var normalized = keyword.Trim();
query = query.Where(x => x.Name.Contains(normalized) || x.Code.Contains(normalized));
}
+ // 3. 返回列表
return query.ToListAsync(cancellationToken)
.ContinueWith(t => (IReadOnlyList)t.Result, cancellationToken);
}
+ ///
+ /// 新增权限。
+ ///
+ /// 权限实体。
+ /// 取消标记。
+ /// 异步任务。
public Task AddAsync(Permission permission, CancellationToken cancellationToken = default)
{
+ // 1. 添加实体
dbContext.Permissions.Add(permission);
+ // 2. 返回完成任务
return Task.CompletedTask;
}
+ ///
+ /// 更新权限。
+ ///
+ /// 权限实体。
+ /// 取消标记。
+ /// 异步任务。
public Task UpdateAsync(Permission permission, CancellationToken cancellationToken = default)
{
+ // 1. 标记实体更新
dbContext.Permissions.Update(permission);
+ // 2. 返回完成任务
return Task.CompletedTask;
}
+ ///
+ /// 删除指定权限。
+ ///
+ /// 权限 ID。
+ /// 租户 ID。
+ /// 取消标记。
+ /// 异步任务。
public async Task DeleteAsync(long permissionId, long tenantId, CancellationToken cancellationToken = default)
{
+ // 1. 查询目标权限
var entity = await dbContext.Permissions.FirstOrDefaultAsync(x => x.Id == permissionId && x.TenantId == tenantId, cancellationToken);
if (entity != null)
{
+ // 2. 删除实体
dbContext.Permissions.Remove(entity);
}
}
+ ///
+ /// 保存仓储变更。
+ ///
+ /// 取消标记。
+ /// 保存任务。
public Task SaveChangesAsync(CancellationToken cancellationToken = default)
=> dbContext.SaveChangesAsync(cancellationToken);
}
diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/EfRolePermissionRepository.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/EfRolePermissionRepository.cs
index 83c5101..ae13a45 100644
--- a/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/EfRolePermissionRepository.cs
+++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/EfRolePermissionRepository.cs
@@ -9,25 +9,49 @@ namespace TakeoutSaaS.Infrastructure.Identity.Persistence;
///
public sealed class EfRolePermissionRepository(IdentityDbContext dbContext) : IRolePermissionRepository
{
+ ///
+ /// 根据角色 ID 集合获取角色权限映射。
+ ///
+ /// 租户 ID。
+ /// 角色 ID 集合。
+ /// 取消标记。
+ /// 角色权限映射列表。
public Task> GetByRoleIdsAsync(long tenantId, IEnumerable roleIds, CancellationToken cancellationToken = default)
=> dbContext.RolePermissions.AsNoTracking()
.Where(x => x.TenantId == tenantId && roleIds.Contains(x.RoleId))
.ToListAsync(cancellationToken)
.ContinueWith(t => (IReadOnlyList)t.Result, cancellationToken);
+ ///
+ /// 批量新增角色权限。
+ ///
+ /// 角色权限集合。
+ /// 取消标记。
+ /// 异步任务。
public async Task AddRangeAsync(IEnumerable rolePermissions, CancellationToken cancellationToken = default)
{
+ // 1. 转为数组便于计数
var toAdd = rolePermissions as RolePermission[] ?? rolePermissions.ToArray();
if (toAdd.Length == 0)
{
return;
}
+ // 2. 批量插入
await dbContext.RolePermissions.AddRangeAsync(toAdd, cancellationToken);
}
+ ///
+ /// 替换指定角色的权限集合。
+ ///
+ /// 租户 ID。
+ /// 角色 ID。
+ /// 权限 ID 集合。
+ /// 取消标记。
+ /// 异步任务。
public async Task ReplaceRolePermissionsAsync(long tenantId, long roleId, IEnumerable permissionIds, CancellationToken cancellationToken = default)
{
+ // 1. 使用执行策略保证可靠性
var strategy = dbContext.Database.CreateExecutionStrategy();
await strategy.ExecuteAsync(async () =>
{
@@ -52,6 +76,11 @@ public sealed class EfRolePermissionRepository(IdentityDbContext dbContext) : IR
});
}
+ ///
+ /// 保存仓储变更。
+ ///
+ /// 取消标记。
+ /// 保存任务。
public Task SaveChangesAsync(CancellationToken cancellationToken = default)
=> dbContext.SaveChangesAsync(cancellationToken);
}
diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/EfRoleRepository.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/EfRoleRepository.cs
index 10f17d8..aa7241f 100644
--- a/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/EfRoleRepository.cs
+++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/EfRoleRepository.cs
@@ -9,53 +9,114 @@ namespace TakeoutSaaS.Infrastructure.Identity.Persistence;
///
public sealed class EfRoleRepository(IdentityDbContext dbContext) : IRoleRepository
{
+ ///
+ /// 根据角色 ID 获取角色。
+ ///
+ /// 角色 ID。
+ /// 租户 ID。
+ /// 取消标记。
+ /// 角色实体或 null。
public Task FindByIdAsync(long roleId, long tenantId, CancellationToken cancellationToken = default)
=> dbContext.Roles.AsNoTracking().FirstOrDefaultAsync(x => x.Id == roleId && x.TenantId == tenantId && x.DeletedAt == null, cancellationToken);
+ ///
+ /// 根据角色编码获取角色。
+ ///
+ /// 角色编码。
+ /// 租户 ID。
+ /// 取消标记。
+ /// 角色实体或 null。
public Task FindByCodeAsync(string code, long tenantId, CancellationToken cancellationToken = default)
=> dbContext.Roles.AsNoTracking().FirstOrDefaultAsync(x => x.Code == code && x.TenantId == tenantId && x.DeletedAt == null, cancellationToken);
+ ///
+ /// 根据角色 ID 集合获取角色列表。
+ ///
+ /// 租户 ID。
+ /// 角色 ID 集合。
+ /// 取消标记。
+ /// 角色列表。
public Task> GetByIdsAsync(long tenantId, IEnumerable roleIds, CancellationToken cancellationToken = default)
=> dbContext.Roles.AsNoTracking()
.Where(x => x.TenantId == tenantId && roleIds.Contains(x.Id) && x.DeletedAt == null)
.ToListAsync(cancellationToken)
.ContinueWith(t => (IReadOnlyList)t.Result, cancellationToken);
+ ///
+ /// 按关键字搜索角色。
+ ///
+ /// 租户 ID。
+ /// 搜索关键字。
+ /// 取消标记。
+ /// 角色列表。
public Task> SearchAsync(long tenantId, string? keyword, CancellationToken cancellationToken = default)
{
+ // 1. 构建基础查询
var query = dbContext.Roles.AsNoTracking().Where(x => x.TenantId == tenantId && x.DeletedAt == null);
if (!string.IsNullOrWhiteSpace(keyword))
{
+ // 2. 追加关键字过滤
var normalized = keyword.Trim();
query = query.Where(x => x.Name.Contains(normalized) || x.Code.Contains(normalized));
}
+ // 3. 返回列表
return query.ToListAsync(cancellationToken)
.ContinueWith(t => (IReadOnlyList)t.Result, cancellationToken);
}
+ ///
+ /// 新增角色。
+ ///
+ /// 角色实体。
+ /// 取消标记。
+ /// 异步任务。
public Task AddAsync(Role role, CancellationToken cancellationToken = default)
{
+ // 1. 添加实体
dbContext.Roles.Add(role);
+ // 2. 返回完成任务
return Task.CompletedTask;
}
+ ///
+ /// 更新角色。
+ ///
+ /// 角色实体。
+ /// 取消标记。
+ /// 异步任务。
public Task UpdateAsync(Role role, CancellationToken cancellationToken = default)
{
+ // 1. 标记更新
dbContext.Roles.Update(role);
+ // 2. 返回完成任务
return Task.CompletedTask;
}
+ ///
+ /// 软删除角色。
+ ///
+ /// 角色 ID。
+ /// 租户 ID。
+ /// 取消标记。
+ /// 异步任务。
public async Task DeleteAsync(long roleId, long tenantId, CancellationToken cancellationToken = default)
{
+ // 1. 查询目标角色
var entity = await dbContext.Roles.FirstOrDefaultAsync(x => x.Id == roleId && x.TenantId == tenantId, cancellationToken);
if (entity != null)
{
+ // 2. 标记删除时间
entity.DeletedAt = DateTime.UtcNow;
dbContext.Roles.Update(entity);
}
}
+ ///
+ /// 保存仓储变更。
+ ///
+ /// 取消标记。
+ /// 保存任务。
public Task SaveChangesAsync(CancellationToken cancellationToken = default)
=> dbContext.SaveChangesAsync(cancellationToken);
}
diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/EfRoleTemplateRepository.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/EfRoleTemplateRepository.cs
index 625c0df..e84131d 100644
--- a/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/EfRoleTemplateRepository.cs
+++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/EfRoleTemplateRepository.cs
@@ -9,83 +9,151 @@ namespace TakeoutSaaS.Infrastructure.Identity.Persistence;
///
public sealed class EfRoleTemplateRepository(IdentityDbContext dbContext) : IRoleTemplateRepository
{
+ ///
+ /// 获取全部角色模板,可选按启用状态过滤。
+ ///
+ /// 是否启用过滤。
+ /// 取消标记。
+ /// 角色模板列表。
public Task> GetAllAsync(bool? isActive, CancellationToken cancellationToken = default)
{
+ // 1. 构建基础查询
var query = dbContext.RoleTemplates.AsNoTracking();
if (isActive.HasValue)
{
+ // 2. 按启用状态过滤
query = query.Where(x => x.IsActive == isActive.Value);
}
+ // 3. 排序并返回
return query
.OrderBy(x => x.TemplateCode)
.ToListAsync(cancellationToken)
.ContinueWith(t => (IReadOnlyList)t.Result, cancellationToken);
}
+ ///
+ /// 根据模板编码获取角色模板。
+ ///
+ /// 模板编码。
+ /// 取消标记。
+ /// 角色模板或 null。
public Task FindByCodeAsync(string templateCode, CancellationToken cancellationToken = default)
{
+ // 1. 规范化编码
var normalized = templateCode.Trim();
+ // 2. 查询模板
return dbContext.RoleTemplates.AsNoTracking().FirstOrDefaultAsync(x => x.TemplateCode == normalized, cancellationToken);
}
+ ///
+ /// 获取指定模板的权限集合。
+ ///
+ /// 模板 ID。
+ /// 取消标记。
+ /// 模板权限列表。
public Task> GetPermissionsAsync(long roleTemplateId, CancellationToken cancellationToken = default)
{
+ // 1. 查询模板权限
return dbContext.RoleTemplatePermissions.AsNoTracking()
.Where(x => x.RoleTemplateId == roleTemplateId)
.ToListAsync(cancellationToken)
.ContinueWith(t => (IReadOnlyList)t.Result, cancellationToken);
}
+ ///
+ /// 获取多个模板的权限集合。
+ ///
+ /// 模板 ID 集合。
+ /// 取消标记。
+ /// 模板到权限的字典。
public async Task>> GetPermissionsAsync(IEnumerable roleTemplateIds, CancellationToken cancellationToken = default)
{
+ // 1. 去重 ID
var ids = roleTemplateIds.Distinct().ToArray();
if (ids.Length == 0)
{
return new Dictionary>();
}
+ // 2. 批量查询权限
var permissions = await dbContext.RoleTemplatePermissions.AsNoTracking()
.Where(x => ids.Contains(x.RoleTemplateId))
.ToListAsync(cancellationToken);
+ // 3. 组装字典
return permissions
.GroupBy(x => x.RoleTemplateId)
.ToDictionary(g => g.Key, g => (IReadOnlyList)g.ToList());
}
+ ///
+ /// 新增角色模板并配置权限。
+ ///
+ /// 角色模板实体。
+ /// 权限编码集合。
+ /// 取消标记。
+ /// 异步任务。
public async Task AddAsync(RoleTemplate template, IEnumerable permissionCodes, CancellationToken cancellationToken = default)
{
+ // 1. 规范化模板字段
template.TemplateCode = template.TemplateCode.Trim();
template.Name = template.Name.Trim();
+ // 2. 保存模板
await dbContext.RoleTemplates.AddAsync(template, cancellationToken);
+ // 3. 替换权限
await ReplacePermissionsInternalAsync(template, permissionCodes, cancellationToken);
}
+ ///
+ /// 更新角色模板并重置权限。
+ ///
+ /// 角色模板实体。
+ /// 权限编码集合。
+ /// 取消标记。
+ /// 异步任务。
public async Task UpdateAsync(RoleTemplate template, IEnumerable permissionCodes, CancellationToken cancellationToken = default)
{
+ // 1. 规范化模板字段
template.TemplateCode = template.TemplateCode.Trim();
template.Name = template.Name.Trim();
+ // 2. 更新模板
dbContext.RoleTemplates.Update(template);
+ // 3. 重置权限
await ReplacePermissionsInternalAsync(template, permissionCodes, cancellationToken);
}
+ ///
+ /// 删除角色模板及其权限。
+ ///
+ /// 模板 ID。
+ /// 取消标记。
+ /// 异步任务。
public async Task DeleteAsync(long roleTemplateId, CancellationToken cancellationToken = default)
{
+ // 1. 查询模板
var entity = await dbContext.RoleTemplates.FirstOrDefaultAsync(x => x.Id == roleTemplateId, cancellationToken);
if (entity != null)
{
+ // 2. 删除关联权限
var permissions = dbContext.RoleTemplatePermissions.Where(x => x.RoleTemplateId == roleTemplateId);
dbContext.RoleTemplatePermissions.RemoveRange(permissions);
+ // 3. 删除模板
dbContext.RoleTemplates.Remove(entity);
}
}
+ ///
+ /// 保存仓储变更。
+ ///
+ /// 取消标记。
+ /// 保存任务。
public Task SaveChangesAsync(CancellationToken cancellationToken = default)
=> dbContext.SaveChangesAsync(cancellationToken);
private async Task ReplacePermissionsInternalAsync(RoleTemplate template, IEnumerable permissionCodes, CancellationToken cancellationToken)
{
+ // 1. 使用执行策略保证一致性
var strategy = dbContext.Database.CreateExecutionStrategy();
await strategy.ExecuteAsync(async () =>
{
diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/EfUserRoleRepository.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/EfUserRoleRepository.cs
index 8b26acb..95c120b 100644
--- a/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/EfUserRoleRepository.cs
+++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/EfUserRoleRepository.cs
@@ -9,32 +9,58 @@ namespace TakeoutSaaS.Infrastructure.Identity.Persistence;
///
public sealed class EfUserRoleRepository(IdentityDbContext dbContext) : IUserRoleRepository
{
+ ///
+ /// 根据用户 ID 集合获取用户角色映射。
+ ///
+ /// 租户 ID。
+ /// 用户 ID 集合。
+ /// 取消标记。
+ /// 用户角色映射列表。
public Task> GetByUserIdsAsync(long tenantId, IEnumerable userIds, CancellationToken cancellationToken = default)
=> dbContext.UserRoles.AsNoTracking()
.Where(x => x.TenantId == tenantId && userIds.Contains(x.UserId))
.ToListAsync(cancellationToken)
.ContinueWith(t => (IReadOnlyList)t.Result, cancellationToken);
+ ///
+ /// 获取指定用户的角色集合。
+ ///
+ /// 租户 ID。
+ /// 用户 ID。
+ /// 取消标记。
+ /// 用户角色列表。
public Task> GetByUserIdAsync(long tenantId, long userId, CancellationToken cancellationToken = default)
=> dbContext.UserRoles.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.UserId == userId)
.ToListAsync(cancellationToken)
.ContinueWith(t => (IReadOnlyList)t.Result, cancellationToken);
+ ///
+ /// 替换指定用户的角色集合。
+ ///
+ /// 租户 ID。
+ /// 用户 ID。
+ /// 角色 ID 集合。
+ /// 取消标记。
+ /// 异步任务。
public async Task ReplaceUserRolesAsync(long tenantId, long userId, IEnumerable roleIds, CancellationToken cancellationToken = default)
{
+ // 1. 使用执行策略保障一致性
var strategy = dbContext.Database.CreateExecutionStrategy();
await strategy.ExecuteAsync(async () =>
{
await using var trx = await dbContext.Database.BeginTransactionAsync(cancellationToken);
+ // 2. 读取当前角色映射
var existing = await dbContext.UserRoles
.Where(x => x.TenantId == tenantId && x.UserId == userId)
.ToListAsync(cancellationToken);
+ // 3. 清空并保存
dbContext.UserRoles.RemoveRange(existing);
await dbContext.SaveChangesAsync(cancellationToken);
+ // 4. 构建新映射
var toAdd = roleIds.Distinct().Select(roleId => new UserRole
{
TenantId = tenantId,
@@ -42,6 +68,7 @@ public sealed class EfUserRoleRepository(IdentityDbContext dbContext) : IUserRol
RoleId = roleId
});
+ // 5. 批量新增并保存
await dbContext.UserRoles.AddRangeAsync(toAdd, cancellationToken);
await dbContext.SaveChangesAsync(cancellationToken);
@@ -49,6 +76,11 @@ public sealed class EfUserRoleRepository(IdentityDbContext dbContext) : IUserRol
});
}
+ ///
+ /// 保存仓储变更。
+ ///
+ /// 取消标记。
+ /// 保存任务。
public Task SaveChangesAsync(CancellationToken cancellationToken = default)
=> dbContext.SaveChangesAsync(cancellationToken);
}
diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/IdentityDataSeeder.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/IdentityDataSeeder.cs
index 17727ae..768e992 100644
--- a/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/IdentityDataSeeder.cs
+++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/IdentityDataSeeder.cs
@@ -22,38 +22,52 @@ namespace TakeoutSaaS.Infrastructure.Identity.Persistence;
///
public sealed class IdentityDataSeeder(IServiceProvider serviceProvider, ILogger logger) : IHostedService
{
+ ///
+ /// 执行后台账号与权限种子。
+ ///
+ /// 取消标记。
+ /// 异步任务。
public async Task StartAsync(CancellationToken cancellationToken)
{
+ // 1. 创建作用域并解析依赖
using var scope = serviceProvider.CreateScope();
var context = scope.ServiceProvider.GetRequiredService();
var options = scope.ServiceProvider.GetRequiredService>().Value;
var passwordHasher = scope.ServiceProvider.GetRequiredService>();
var tenantContextAccessor = scope.ServiceProvider.GetRequiredService();
+ // 2. 校验功能开关
if (!options.Enabled)
{
logger.LogInformation("AdminSeed 已禁用,跳过后台账号初始化");
return;
}
+ // 3. 确保数据库已迁移
await context.Database.MigrateAsync(cancellationToken);
+ // 4. 校验账号配置
if (options.Users is null or { Count: 0 })
{
logger.LogInformation("AdminSeed 未配置账号,跳过后台账号初始化");
return;
}
+ // 5. 写入角色模板
await SeedRoleTemplatesAsync(context, options.RoleTemplates, cancellationToken);
+ // 6. 逐个账号处理
foreach (var userOptions in options.Users)
{
+ // 6.1 进入租户作用域
using var tenantScope = EnterTenantScope(tenantContextAccessor, userOptions.TenantId);
+ // 6.2 查询账号并收集配置
var user = await context.IdentityUsers.FirstOrDefaultAsync(x => x.Account == userOptions.Account, cancellationToken);
var roles = NormalizeValues(userOptions.Roles);
var permissions = NormalizeValues(userOptions.Permissions);
if (user == null)
{
+ // 6.3 创建新账号
user = new DomainIdentityUser
{
Id = 0,
@@ -69,6 +83,7 @@ public sealed class IdentityDataSeeder(IServiceProvider serviceProvider, ILogger
}
else
{
+ // 6.4 更新既有账号
user.DisplayName = userOptions.DisplayName;
user.TenantId = userOptions.TenantId;
user.MerchantId = userOptions.MerchantId;
@@ -76,7 +91,7 @@ public sealed class IdentityDataSeeder(IServiceProvider serviceProvider, ILogger
logger.LogInformation("已更新后台账号 {Account}", user.Account);
}
- // 确保角色存在
+ // 6.5 确保角色存在
var existingRoles = await context.Roles
.Where(r => r.TenantId == userOptions.TenantId && roles.Contains(r.Code))
.ToListAsync(cancellationToken);
@@ -97,7 +112,7 @@ public sealed class IdentityDataSeeder(IServiceProvider serviceProvider, ILogger
});
}
- // 确保权限存在
+ // 6.6 确保权限存在
var existingPermissions = await context.Permissions
.Where(p => p.TenantId == userOptions.TenantId && permissions.Contains(p.Code))
.ToListAsync(cancellationToken);
@@ -118,9 +133,10 @@ public sealed class IdentityDataSeeder(IServiceProvider serviceProvider, ILogger
});
}
+ // 6.7 保存基础角色/权限
await context.SaveChangesAsync(cancellationToken);
- // 重新加载角色/权限以获取 Id
+ // 6.8 重新加载角色/权限以获取 Id
var roleEntities = await context.Roles
.Where(r => r.TenantId == userOptions.TenantId && roles.Contains(r.Code))
.ToListAsync(cancellationToken);
@@ -128,7 +144,7 @@ public sealed class IdentityDataSeeder(IServiceProvider serviceProvider, ILogger
.Where(p => p.TenantId == userOptions.TenantId && permissions.Contains(p.Code))
.ToListAsync(cancellationToken);
- // 重置用户角色
+ // 6.9 重置用户角色
var existingUserRoles = await context.UserRoles
.Where(ur => ur.TenantId == userOptions.TenantId && ur.UserId == user.Id)
.ToListAsync(cancellationToken);
@@ -191,6 +207,7 @@ public sealed class IdentityDataSeeder(IServiceProvider serviceProvider, ILogger
continue;
}
+ // 6.10 绑定角色与权限
await context.RolePermissions.AddAsync(new DomainRolePermission
{
TenantId = userOptions.TenantId,
@@ -209,9 +226,15 @@ public sealed class IdentityDataSeeder(IServiceProvider serviceProvider, ILogger
}
}
+ // 7. 最终保存
await context.SaveChangesAsync(cancellationToken);
}
+ ///
+ /// 停止生命周期时的清理(此处无需处理)。
+ ///
+ /// 取消标记。
+ /// 已完成任务。
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
private static async Task SeedRoleTemplatesAsync(
@@ -219,23 +242,28 @@ public sealed class IdentityDataSeeder(IServiceProvider serviceProvider, ILogger
IList templates,
CancellationToken cancellationToken)
{
+ // 1. 空集合直接返回
if (templates is null || templates.Count == 0)
{
return;
}
+ // 2. 逐个处理模板
foreach (var templateOptions in templates)
{
+ // 2.1 校验必填字段
if (string.IsNullOrWhiteSpace(templateOptions.TemplateCode) || string.IsNullOrWhiteSpace(templateOptions.Name))
{
continue;
}
+ // 2.2 查询现有模板
var code = templateOptions.TemplateCode.Trim();
var existing = await context.RoleTemplates.FirstOrDefaultAsync(x => x.TemplateCode == code, cancellationToken);
if (existing == null)
{
+ // 2.3 新增模板
existing = new DomainRoleTemplate
{
TemplateCode = code,
@@ -249,6 +277,7 @@ public sealed class IdentityDataSeeder(IServiceProvider serviceProvider, ILogger
}
else
{
+ // 2.4 更新模板
existing.Name = templateOptions.Name.Trim();
existing.Description = templateOptions.Description;
existing.IsActive = templateOptions.IsActive;
@@ -256,13 +285,15 @@ public sealed class IdentityDataSeeder(IServiceProvider serviceProvider, ILogger
await context.SaveChangesAsync(cancellationToken);
}
+ // 2.5 重置模板权限
var permissionCodes = NormalizeValues(templateOptions.Permissions);
var existingPermissions = await context.RoleTemplatePermissions
.Where(x => x.RoleTemplateId == existing.Id)
.ToListAsync(cancellationToken);
-
+ // 2.6 清空旧权限并保存
context.RoleTemplatePermissions.RemoveRange(existingPermissions);
await context.SaveChangesAsync(cancellationToken);
+ // 2.7 去重后的权限编码
var distinctPermissionCodes = permissionCodes.Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
foreach (var permissionCode in distinctPermissionCodes)
{
diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/IdentityDesignTimeDbContextFactory.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/IdentityDesignTimeDbContextFactory.cs
index c18d179..926cba6 100644
--- a/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/IdentityDesignTimeDbContextFactory.cs
+++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/IdentityDesignTimeDbContextFactory.cs
@@ -12,11 +12,21 @@ namespace TakeoutSaaS.Infrastructure.Identity.Persistence;
internal sealed class IdentityDesignTimeDbContextFactory
: DesignTimeDbContextFactoryBase
{
+ ///
+ /// 初始化 Identity 设计时上下文工厂。
+ ///
public IdentityDesignTimeDbContextFactory()
: base(DatabaseConstants.IdentityDataSource, "TAKEOUTSAAS_IDENTITY_CONNECTION")
{
}
-
+ // 创建设计时上下文实例
+ ///
+ /// 创建设计时的 IdentityDbContext。
+ ///
+ /// DbContext 配置。
+ /// 租户提供器。
+ /// 当前用户访问器。
+ /// IdentityDbContext 实例。
protected override IdentityDbContext CreateContext(
DbContextOptions options,
ITenantProvider tenantProvider,
diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Services/RedisLoginRateLimiter.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Services/RedisLoginRateLimiter.cs
index 3516290..94491e0 100644
--- a/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Services/RedisLoginRateLimiter.cs
+++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Services/RedisLoginRateLimiter.cs
@@ -14,8 +14,15 @@ public sealed class RedisLoginRateLimiter(IDistributedCache cache, IOptions
+ /// 校验指定键的登录尝试次数,超限将抛出业务异常。
+ ///
+ /// 限流键(如账号或 IP)。
+ /// 取消标记。
+ /// 异步任务。
public async Task EnsureAllowedAsync(string key, CancellationToken cancellationToken = default)
{
+ // 1. 读取当前计数
var cacheKey = BuildKey(key);
var current = await cache.GetStringAsync(cacheKey, cancellationToken);
var count = string.IsNullOrWhiteSpace(current) ? 0 : int.Parse(current);
@@ -24,6 +31,7 @@ public sealed class RedisLoginRateLimiter(IDistributedCache cache, IOptions
+ /// 重置指定键的登录计数。
+ ///
+ /// 限流键(如账号或 IP)。
+ /// 取消标记。
+ /// 异步任务。
public Task ResetAsync(string key, CancellationToken cancellationToken = default)
=> cache.RemoveAsync(BuildKey(key), cancellationToken);
diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Services/RedisRefreshTokenStore.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Services/RedisRefreshTokenStore.cs
index 6c5651e..a1970c6 100644
--- a/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Services/RedisRefreshTokenStore.cs
+++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Services/RedisRefreshTokenStore.cs
@@ -16,11 +16,20 @@ public sealed class RedisRefreshTokenStore(IDistributedCache cache, IOptions[
+ /// 签发刷新令牌并写入缓存。
+ ///
+ /// 用户 ID。
+ /// 过期时间。
+ /// 取消标记。
+ /// 刷新令牌描述。
public async Task IssueAsync(long userId, DateTime expiresAt, CancellationToken cancellationToken = default)
{
+ // 1. 生成随机令牌
var token = Convert.ToBase64String(RandomNumberGenerator.GetBytes(48));
var descriptor = new RefreshTokenDescriptor(token, userId, expiresAt, false);
+ // 2. 写入缓存
var key = BuildKey(token);
var entryOptions = new DistributedCacheEntryOptions { AbsoluteExpiration = expiresAt };
await cache.SetStringAsync(key, JsonSerializer.Serialize(descriptor, JsonOptions), entryOptions, cancellationToken);
@@ -28,22 +37,37 @@ public sealed class RedisRefreshTokenStore(IDistributedCache cache, IOptions][
+ /// 获取刷新令牌描述。
+ ///
+ /// 刷新令牌值。
+ /// 取消标记。
+ /// 刷新令牌描述或 null。
public async Task GetAsync(string refreshToken, CancellationToken cancellationToken = default)
{
+ // 1. 读取缓存
var json = await cache.GetStringAsync(BuildKey(refreshToken), cancellationToken);
return string.IsNullOrWhiteSpace(json)
? null
: JsonSerializer.Deserialize(json, JsonOptions);
}
+ ///
+ /// 吊销刷新令牌。
+ ///
+ /// 刷新令牌值。
+ /// 取消标记。
+ /// 异步任务。
public async Task RevokeAsync(string refreshToken, CancellationToken cancellationToken = default)
{
+ // 1. 读取令牌
var descriptor = await GetAsync(refreshToken, cancellationToken);
if (descriptor == null)
{
return;
}
+ // 2. 标记吊销并回写缓存
var updated = descriptor with { Revoked = true };
var entryOptions = new DistributedCacheEntryOptions { AbsoluteExpiration = updated.ExpiresAt };
await cache.SetStringAsync(BuildKey(refreshToken), JsonSerializer.Serialize(updated, JsonOptions), entryOptions, cancellationToken);
diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Services/WeChatAuthService.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Services/WeChatAuthService.cs
index bbc6328..272ffc9 100644
--- a/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Services/WeChatAuthService.cs
+++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Services/WeChatAuthService.cs
@@ -15,18 +15,27 @@ public sealed class WeChatAuthService(HttpClient httpClient, IOptions
+ /// 调用微信接口完成 code2Session。
+ ///
+ /// 临时登录凭证 code。
+ /// 取消标记。
+ /// 微信会话信息。
public async Task Code2SessionAsync(string code, CancellationToken cancellationToken = default)
{
+ // 1. 拼装请求地址
var requestUri = $"sns/jscode2session?appid={Uri.EscapeDataString(_options.AppId)}&secret={Uri.EscapeDataString(_options.Secret)}&js_code={Uri.EscapeDataString(code)}&grant_type=authorization_code";
using var response = await httpClient.GetAsync(requestUri, cancellationToken);
response.EnsureSuccessStatusCode();
+ // 2. 读取响应
var payload = await response.Content.ReadFromJsonAsync(cancellationToken: cancellationToken);
if (payload == null)
{
throw new BusinessException(ErrorCodes.Unauthorized, "微信登录失败:响应为空");
}
+ // 3. 校验错误码
if (payload.ErrorCode.HasValue && payload.ErrorCode.Value != 0)
{
var message = string.IsNullOrWhiteSpace(payload.ErrorMessage)
@@ -35,11 +44,13 @@ public sealed class WeChatAuthService(HttpClient httpClient, IOptions
-/// 权限校验特性
+/// 权限校验特性。
///
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public sealed class PermissionAuthorizeAttribute : AuthorizeAttribute
{
+ ///
+ /// 初始化权限校验特性并构建对应策略。
+ ///
+ /// 所需的权限标识集合。
public PermissionAuthorizeAttribute(params string[] permissions)
{
+ // 1. 校验权限参数不为空
ArgumentNullException.ThrowIfNull(permissions);
+
+ // 2. 规范化权限标识
var normalized = permissions
.Where(p => !string.IsNullOrWhiteSpace(p))
.Select(p => p.Trim())
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToArray();
+ // 3. 确保至少提供一个有效权限
if (normalized.Length == 0)
{
throw new ArgumentException("至少需要一个权限标识", nameof(permissions));
}
+ // 4. 绑定权限集合并生成策略名称
Permissions = normalized;
Policy = PermissionAuthorizationPolicyProvider.BuildPolicyName(normalized);
}
-
///
- /// 所需权限集合
+ /// 所需权限集合。
///
public IReadOnlyCollection Permissions { get; }
}
diff --git a/src/Modules/TakeoutSaaS.Module.Authorization/Policies/PermissionAuthorizationHandler.cs b/src/Modules/TakeoutSaaS.Module.Authorization/Policies/PermissionAuthorizationHandler.cs
index 780e58d..138d96b 100644
--- a/src/Modules/TakeoutSaaS.Module.Authorization/Policies/PermissionAuthorizationHandler.cs
+++ b/src/Modules/TakeoutSaaS.Module.Authorization/Policies/PermissionAuthorizationHandler.cs
@@ -1,38 +1,45 @@
using Microsoft.AspNetCore.Authorization;
-
namespace TakeoutSaaS.Module.Authorization.Policies;
-
///
-/// 权限校验处理器
+/// 权限校验处理器。
///
public sealed class PermissionAuthorizationHandler : AuthorizationHandler
{
+ ///
+ /// 用户声明中权限的声明类型键。
+ ///
public const string PermissionClaimType = "permission";
-
+ ///
+ /// 校验当前用户是否具备要求的权限集合。
+ ///
+ /// 授权上下文。
+ /// 权限需求描述。
+ /// 异步完成任务。
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
{
+ // 1. 校验用户已通过认证
if (context.User?.Identity?.IsAuthenticated != true)
{
return Task.CompletedTask;
}
-
+ // 2. 收集用户已授予的权限标识
var userPermissions = context.User
.FindAll(PermissionClaimType)
.Select(claim => claim.Value)
.Where(value => !string.IsNullOrWhiteSpace(value))
.Select(value => value.Trim())
.ToHashSet(StringComparer.OrdinalIgnoreCase);
-
+ // 3. 无权限直接结束
if (userPermissions.Count == 0)
{
return Task.CompletedTask;
}
-
+ // 4. 任一权限匹配即视为授权通过
if (requirement.Permissions.Any(userPermissions.Contains))
{
context.Succeed(requirement);
}
-
+ // 5. 结束处理
return Task.CompletedTask;
}
}
diff --git a/src/Modules/TakeoutSaaS.Module.Authorization/Policies/PermissionAuthorizationPolicyProvider.cs b/src/Modules/TakeoutSaaS.Module.Authorization/Policies/PermissionAuthorizationPolicyProvider.cs
index 4f2e610..4f1329c 100644
--- a/src/Modules/TakeoutSaaS.Module.Authorization/Policies/PermissionAuthorizationPolicyProvider.cs
+++ b/src/Modules/TakeoutSaaS.Module.Authorization/Policies/PermissionAuthorizationPolicyProvider.cs
@@ -1,55 +1,62 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Options;
-
namespace TakeoutSaaS.Module.Authorization.Policies;
-
///
-/// 权限策略提供者(按需动态构建策略)
+/// 权限策略提供者(按需动态构建策略)。
///
public sealed class PermissionAuthorizationPolicyProvider(IOptions options) : DefaultAuthorizationPolicyProvider(options)
{
+ ///
+ /// 权限策略名称前缀。
+ ///
public const string PolicyPrefix = "PERMISSION:";
private readonly AuthorizationOptions _options = options.Value;
-
+ ///
+ /// 获取或构建指定名称的权限策略。
+ ///
+ /// 策略名称。
+ /// 匹配的授权策略。
public override Task GetPolicyAsync(string policyName)
{
- if (policyName.StartsWith(PolicyPrefix, StringComparison.OrdinalIgnoreCase))
+ // 1. 非权限策略走基类逻辑
+ if (!policyName.StartsWith(PolicyPrefix, StringComparison.OrdinalIgnoreCase))
{
- var existingPolicy = _options.GetPolicy(policyName);
- if (existingPolicy != null)
- {
- return Task.FromResult(existingPolicy);
- }
-
- var permissions = ParsePermissions(policyName);
- if (permissions.Length == 0)
- {
- return Task.FromResult(null);
- }
-
- var policy = new AuthorizationPolicyBuilder()
- .AddRequirements(new PermissionRequirement(permissions))
- .Build();
-
- _options.AddPolicy(policyName, policy);
- return Task.FromResult(policy);
+ return base.GetPolicyAsync(policyName);
}
-
- return base.GetPolicyAsync(policyName);
+ // 2. 复用已存在的策略
+ var existingPolicy = _options.GetPolicy(policyName);
+ if (existingPolicy != null)
+ {
+ return Task.FromResult(existingPolicy);
+ }
+ // 3. 解析策略携带的权限列表
+ var permissions = ParsePermissions(policyName);
+ if (permissions.Length == 0)
+ {
+ return Task.FromResult(null);
+ }
+ // 4. 动态构建策略并缓存
+ var policy = new AuthorizationPolicyBuilder()
+ .AddRequirements(new PermissionRequirement(permissions))
+ .Build();
+ _options.AddPolicy(policyName, policy);
+ // 5. 返回构建好的策略
+ return Task.FromResult]