96 lines
3.9 KiB
C#
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();
|
|
}
|
|
}
|