using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using TakeoutSaaS.Infrastructure.Identity.Options; using TakeoutSaaS.Shared.Abstractions.Tenancy; using DomainIdentityUser = TakeoutSaaS.Domain.Identity.Entities.IdentityUser; namespace TakeoutSaaS.Infrastructure.Identity.Persistence; /// /// 后台账号初始化种子任务 /// public sealed class IdentityDataSeeder(IServiceProvider serviceProvider, ILogger logger) : IHostedService { public async Task StartAsync(CancellationToken cancellationToken) { 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(); await context.Database.MigrateAsync(cancellationToken); if (options.Users is null or { Count: 0 }) { logger.LogInformation("AdminSeed 未配置账号,跳过后台账号初始化"); return; } foreach (var userOptions in options.Users) { using var tenantScope = EnterTenantScope(tenantContextAccessor, userOptions.TenantId); 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) { user = new DomainIdentityUser { Id = 0, Account = userOptions.Account, DisplayName = userOptions.DisplayName, TenantId = userOptions.TenantId, MerchantId = userOptions.MerchantId, Avatar = null, Roles = roles, Permissions = permissions, }; user.PasswordHash = passwordHasher.HashPassword(user, userOptions.Password); context.IdentityUsers.Add(user); logger.LogInformation("已创建后台账号 {Account}", user.Account); } else { user.DisplayName = userOptions.DisplayName; user.TenantId = userOptions.TenantId; user.MerchantId = userOptions.MerchantId; user.Roles = roles; user.Permissions = permissions; user.PasswordHash = passwordHasher.HashPassword(user, userOptions.Password); logger.LogInformation("已更新后台账号 {Account}", user.Account); } } await context.SaveChangesAsync(cancellationToken); } public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; private static string[] NormalizeValues(string[]? values) => values == null ? [] : [.. values .Where(v => !string.IsNullOrWhiteSpace(v)) .Select(v => v.Trim()) .Distinct(StringComparer.OrdinalIgnoreCase)]; private static IDisposable EnterTenantScope(ITenantContextAccessor accessor, long tenantId) { var previous = accessor.Current; accessor.Current = new TenantContext(tenantId, null, "admin-seed"); return new Scope(() => accessor.Current = previous); } private sealed class Scope(Action disposeAction) : IDisposable { private readonly Action _disposeAction = disposeAction; public void Dispose() => _disposeAction(); } }