feat: modularize external providers

This commit is contained in:
2026-07-27 17:47:21 +08:00
parent db4c7b4496
commit 70b99e6063
47 changed files with 1219 additions and 667 deletions

View File

@@ -2,6 +2,8 @@ using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using Tiku.Application.Catalog;
using Tiku.Application.Content;
using Tiku.Application.Notifications;
using Tiku.Application.Tenancy;
using Tiku.Application.TenantAdmin;
using Tiku.Domain.Catalog;
using Tiku.Domain.Common;
@@ -13,7 +15,10 @@ using Tiku.Infrastructure.Persistence;
namespace Tiku.Infrastructure.TenantAdmin;
public sealed class TenantAdminDirectService(TikuDbContext dbContext) : ITenantAdminDirectService
public sealed class TenantAdminDirectService(
TikuDbContext dbContext,
ITenantExternalProviderConfigService providerConfigService,
INotificationProvider notificationProvider) : ITenantAdminDirectService
{
private static readonly TenantAdminPermissionCatalogItem[] PermissionCatalog =
[
@@ -1099,42 +1104,39 @@ public sealed class TenantAdminDirectService(TikuDbContext dbContext) : ITenantA
return new ContentManagementResult<TenantDomainItem>(ToDomainItem(item));
}
public async Task<CatalogList<TenantAuthProviderItem>> GetAuthProvidersAsync(
public async Task<CatalogList<TenantIdentityProviderItem>> GetAuthProvidersAsync(
TenantAdminActor actor,
CancellationToken cancellationToken = default)
{
var items = await dbContext.TenantAuthProviders.AsNoTracking()
.Where(item => item.TenantId == actor.TenantId)
.OrderBy(item => item.CreatedAt)
.Select(item => ToAuthProviderItem(item))
.ToArrayAsync(cancellationToken);
return new CatalogList<TenantAuthProviderItem>(items);
var items = await providerConfigService.GetProvidersAsync(
actor.TenantId,
TenantExternalProviderCapability.Identity,
cancellationToken: cancellationToken);
return new CatalogList<TenantIdentityProviderItem>(items.Select(ToAuthProviderItem).ToArray());
}
public async Task<ContentManagementResult<TenantAuthProviderItem>> UpsertAuthProviderAsync(
public async Task<ContentManagementResult<TenantIdentityProviderItem>> UpsertAuthProviderAsync(
TenantAdminActor actor,
UpsertTenantAuthProviderCommand command,
UpsertTenantIdentityProviderCommand command,
CancellationToken cancellationToken = default)
{
ArgumentException.ThrowIfNullOrWhiteSpace(command.Provider);
AssertNoSecrets(command.ConfigPublic, "config_public");
var provider = command.Provider.Trim().ToLowerInvariant();
var item = await dbContext.TenantAuthProviders.FirstOrDefaultAsync(
authProvider => authProvider.TenantId == actor.TenantId && authProvider.Provider == provider,
var item = await providerConfigService.UpsertProviderAsync(
actor.TenantId,
new UpsertTenantExternalProviderCommand(
TenantExternalProviderCapability.Identity,
command.Provider,
ParseEnum(command.Status, TenantExternalProviderStatus.Disabled, "invalid_auth_provider_status"),
command.DisplayName,
command.SecretRef,
command.Priority,
command.ConfigPublic,
JsonObjectOrDefault(default)),
cancellationToken);
var isNew = item is null;
item ??= new TenantAuthProvider { TenantId = actor.TenantId, Provider = provider };
item.Status = ParseEnum(command.Status, TenantAuthProviderStatus.Disabled, "invalid_auth_provider_status");
item.DisplayName = Normalize(command.DisplayName);
item.ConfigPublic = JsonObjectOrDefault(command.ConfigPublic);
if (isNew)
{
dbContext.TenantAuthProviders.Add(item);
}
await AddAuditAsync(actor, "tenant.auth_provider.upserted", "tenant_auth_providers", item.Id, cancellationToken);
await AddAuditAsync(actor, "tenant.auth_provider.upserted", "tenant_external_providers", item.Id, cancellationToken);
await dbContext.SaveChangesAsync(cancellationToken);
return new ContentManagementResult<TenantAuthProviderItem>(ToAuthProviderItem(item));
return new ContentManagementResult<TenantIdentityProviderItem>(ToAuthProviderItem(item));
}
public async Task<CatalogList<TenantAdminBadgeItem>> GetBadgesAsync(
@@ -1273,33 +1275,22 @@ public sealed class TenantAdminDirectService(TikuDbContext dbContext) : ITenantA
}
var dedupeKey = $"badge:{grant.Id:N}";
var notification = await dbContext.UserNotifications.FirstOrDefaultAsync(
item =>
item.TenantId == actor.TenantId &&
item.UserId == command.UserId &&
item.NotificationType == "badge_granted" &&
item.DedupeKey == dedupeKey,
await notificationProvider.UpsertInAppAsync(
new InAppNotificationRequest(
actor.TenantId,
command.UserId,
"badge_granted",
NotificationSeverity.Success,
$"获得勋章:{badge.Name}",
Normalize(command.Note) ?? "管理员为你发放了一枚新的学习勋章。",
actor.UserId,
"查看勋章",
"/profile?tab=badges",
"user_badges",
grant.Id,
dedupeKey,
JsonSerializer.SerializeToElement(new { badgeId = badge.Id, badgeName = badge.Name })),
cancellationToken);
notification ??= new UserNotification
{
TenantId = actor.TenantId,
UserId = command.UserId,
NotificationType = "badge_granted",
DedupeKey = dedupeKey,
CreatedBy = actor.UserId
};
notification.Severity = NotificationSeverity.Success;
notification.Title = $"获得勋章:{badge.Name}";
notification.Message = Normalize(command.Note) ?? "管理员为你发放了一枚新的学习勋章。";
notification.ActionLabel = "查看勋章";
notification.ActionPath = "/profile?tab=badges";
notification.SourceType = "user_badges";
notification.SourceId = grant.Id;
notification.Metadata = JsonSerializer.SerializeToElement(new { badgeId = badge.Id, badgeName = badge.Name });
if (dbContext.Entry(notification).State == EntityState.Detached)
{
dbContext.UserNotifications.Add(notification);
}
await AddAuditAsync(actor, "tenant.badge.granted", "user_badges", grant.Id, cancellationToken);
await dbContext.SaveChangesAsync(cancellationToken);
@@ -1346,35 +1337,22 @@ public sealed class TenantAdminDirectService(TikuDbContext dbContext) : ITenantA
ArgumentException.ThrowIfNullOrWhiteSpace(command.Message);
await AssertTenantMemberAsync(actor.TenantId, command.UserId, cancellationToken);
var item = !string.IsNullOrWhiteSpace(command.DedupeKey)
? await dbContext.UserNotifications.FirstOrDefaultAsync(notification =>
notification.TenantId == actor.TenantId &&
notification.UserId == command.UserId &&
notification.NotificationType == command.NotificationType &&
notification.DedupeKey == command.DedupeKey,
cancellationToken)
: null;
var isNew = item is null;
item ??= new UserNotification
{
TenantId = actor.TenantId,
UserId = command.UserId,
CreatedBy = actor.UserId
};
item.NotificationType = command.NotificationType.Trim();
item.Severity = ParseEnum(command.Severity, NotificationSeverity.Info, "invalid_notification_severity");
item.Title = command.Title.Trim();
item.Message = command.Message.Trim();
item.ActionLabel = Normalize(command.ActionLabel);
item.ActionPath = Normalize(command.ActionPath);
item.SourceType = Normalize(command.SourceType);
item.SourceId = command.SourceId;
item.DedupeKey = Normalize(command.DedupeKey);
item.Metadata = JsonObjectOrDefault(command.Metadata);
if (isNew)
{
dbContext.UserNotifications.Add(item);
}
var item = await notificationProvider.UpsertInAppAsync(
new InAppNotificationRequest(
actor.TenantId,
command.UserId,
command.NotificationType,
ParseEnum(command.Severity, NotificationSeverity.Info, "invalid_notification_severity"),
command.Title,
command.Message,
actor.UserId,
command.ActionLabel,
command.ActionPath,
command.SourceType,
command.SourceId,
command.DedupeKey,
JsonObjectOrDefault(command.Metadata)),
cancellationToken);
await AddAuditAsync(actor, "tenant.notification.upserted", "user_notifications", item.Id, cancellationToken);
await dbContext.SaveChangesAsync(cancellationToken);
@@ -1941,13 +1919,15 @@ public sealed class TenantAdminDirectService(TikuDbContext dbContext) : ITenantA
item.UpdatedAt);
}
private static TenantAuthProviderItem ToAuthProviderItem(TenantAuthProvider item)
private static TenantIdentityProviderItem ToAuthProviderItem(TenantExternalProviderItem item)
{
return new TenantAuthProviderItem(
return new TenantIdentityProviderItem(
item.Id,
item.Provider,
item.Status,
item.DisplayName,
item.SecretRef,
item.Priority,
item.ConfigPublic,
item.CreatedAt,
item.UpdatedAt);