perf: optimize authorization scoreline and workers

This commit is contained in:
2026-07-30 09:12:27 +08:00
parent 4bea745b79
commit bedc77bffd
52 changed files with 21911 additions and 347 deletions

View File

@@ -6,7 +6,9 @@ using Tiku.Infrastructure.Persistence;
namespace Tiku.Infrastructure.Security;
internal sealed class FeatureAccessService(TikuDbContext dbContext) : IFeatureAccessService
internal sealed class FeatureAccessService(
TikuDbContext dbContext,
ITenantFeatureSnapshotProvider snapshotProvider) : IFeatureAccessService
{
public async Task<FeatureAccessDecision> EvaluateAsync(
Guid tenantId,
@@ -14,97 +16,8 @@ internal sealed class FeatureAccessService(TikuDbContext dbContext) : IFeatureAc
FeatureAccessOperation operation,
CancellationToken cancellationToken = default)
{
var normalized = Normalize(featureCode);
var tenantStatus = await dbContext.Tenants.AsNoTracking()
.Where(value => value.Id == tenantId)
.Select(value => (TenantStatus?)value.Status)
.SingleOrDefaultAsync(cancellationToken);
if (tenantStatus != TenantStatus.Active)
{
return Denied(normalized, operation, "tenant_inactive");
}
var feature = await dbContext.SaasFeatures.AsNoTracking()
.Where(value => value.Code == normalized)
.Select(value => new { value.Status, value.IsCore })
.SingleOrDefaultAsync(cancellationToken);
if (feature is null || feature.Status != SaasFeatureStatus.Active)
{
return Denied(normalized, operation, "feature_unavailable");
}
if (feature.IsCore)
{
return Allowed(normalized, operation);
}
var now = DateTimeOffset.UtcNow;
var overrideMode = await dbContext.TenantFeatureOverrides.AsNoTracking()
.Where(value => value.TenantId == tenantId && value.FeatureCode == normalized &&
(value.ExpiresAt == null || value.ExpiresAt > now))
.Select(value => (TenantFeatureOverrideMode?)value.Mode)
.SingleOrDefaultAsync(cancellationToken);
if (overrideMode == TenantFeatureOverrideMode.Disabled)
{
return Denied(normalized, operation, "feature_disabled");
}
var subscription = await dbContext.TenantSaasSubscriptions.AsNoTracking()
.Where(value => value.TenantId == tenantId)
.OrderByDescending(value => value.UpdatedAt)
.Select(value => new
{
value.Id,
value.BaseOfferingVersionId,
value.Status,
value.StartsAt,
value.CurrentPeriodEnd
})
.FirstOrDefaultAsync(cancellationToken);
if (subscription is null)
{
return overrideMode == TenantFeatureOverrideMode.Enabled
? Allowed(normalized, operation)
: Denied(normalized, operation, "subscription_missing");
}
if (operation == FeatureAccessOperation.Write &&
(subscription.Status is not (TenantSaasSubscriptionStatus.Trial or TenantSaasSubscriptionStatus.Active) ||
subscription.StartsAt > now || subscription.CurrentPeriodEnd <= now))
{
return Denied(normalized, operation, "subscription_read_only");
}
if (subscription.Status == TenantSaasSubscriptionStatus.Suspended)
{
return Denied(normalized, operation, "subscription_suspended");
}
if (overrideMode == TenantFeatureOverrideMode.Enabled)
{
return Allowed(normalized, operation);
}
var versionIds = await dbContext.TenantSaasSubscriptionItems.AsNoTracking()
.Where(value => value.TenantId == tenantId && value.SubscriptionId == subscription.Id &&
(operation == FeatureAccessOperation.Read
? value.Status != TenantSaasSubscriptionItemStatus.Pending &&
value.Status != TenantSaasSubscriptionItemStatus.Scheduled &&
value.StartsAt <= now
: value.Status == TenantSaasSubscriptionItemStatus.Active &&
value.StartsAt <= now && value.EndsAt > now))
.Select(value => value.OfferingVersionId)
.ToArrayAsync(cancellationToken);
if (!versionIds.Contains(subscription.BaseOfferingVersionId))
{
versionIds = [.. versionIds, subscription.BaseOfferingVersionId];
}
var entitled = await dbContext.SaasOfferingVersionFeatures.AsNoTracking()
.AnyAsync(value => versionIds.Contains(value.OfferingVersionId) && value.FeatureCode == normalized, cancellationToken);
return entitled
? Allowed(normalized, operation)
: Denied(normalized, operation, "feature_not_purchased");
return (await snapshotProvider.GetAsync(tenantId, operation, cancellationToken))
.Evaluate(featureCode, operation);
}
public async Task<IReadOnlySet<string>> GetEnabledFeaturesAsync(
@@ -112,20 +25,11 @@ internal sealed class FeatureAccessService(TikuDbContext dbContext) : IFeatureAc
FeatureAccessOperation operation = FeatureAccessOperation.Read,
CancellationToken cancellationToken = default)
{
var codes = await dbContext.SaasFeatures.AsNoTracking()
.Where(value => value.Status == SaasFeatureStatus.Active)
.Select(value => value.Code)
.ToArrayAsync(cancellationToken);
var enabled = new HashSet<string>(StringComparer.Ordinal);
foreach (var code in codes)
{
if ((await EvaluateAsync(tenantId, code, operation, cancellationToken)).Allowed)
{
enabled.Add(code);
}
}
return enabled;
var snapshot = await snapshotProvider.GetAsync(tenantId, operation, cancellationToken);
return snapshot.Features
.Where(feature => snapshot.Evaluate(feature.Code, operation).Allowed)
.Select(feature => feature.Code)
.ToHashSet(StringComparer.Ordinal);
}
public async Task<IReadOnlySet<string>> FilterPermissionCodesAsync(
@@ -142,11 +46,12 @@ internal sealed class FeatureAccessService(TikuDbContext dbContext) : IFeatureAc
where requested.Contains(permission.Code)
select new { permission.Code, module.RequiredFeatureCode })
.ToArrayAsync(cancellationToken);
var snapshot = await snapshotProvider.GetAsync(tenantId, operation, cancellationToken);
var allowed = new HashSet<string>(StringComparer.Ordinal);
foreach (var permission in permissions)
{
if (permission.RequiredFeatureCode is null ||
(await EvaluateAsync(tenantId, permission.RequiredFeatureCode, operation, cancellationToken)).Allowed)
snapshot.Evaluate(permission.RequiredFeatureCode, operation).Allowed)
{
allowed.Add(permission.Code);
}