Files
TakeoutSaaS.AdminApi/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/IdentityDataSeeder.cs

96 lines
3.9 KiB
C#

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;
/// <summary>
/// 后台账号初始化种子任务
/// </summary>
public sealed class IdentityDataSeeder(IServiceProvider serviceProvider, ILogger<IdentityDataSeeder> logger) : IHostedService
{
public async Task StartAsync(CancellationToken cancellationToken)
{
using var scope = serviceProvider.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<IdentityDbContext>();
var options = scope.ServiceProvider.GetRequiredService<IOptions<AdminSeedOptions>>().Value;
var passwordHasher = scope.ServiceProvider.GetRequiredService<IPasswordHasher<DomainIdentityUser>>();
var tenantContextAccessor = scope.ServiceProvider.GetRequiredService<ITenantContextAccessor>();
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();
}
}