Files
TakeoutSaaS.AdminApi/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/AppDataSeeder.cs

391 lines
14 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using TakeoutSaaS.Domain.Dictionary.Entities;
using TakeoutSaaS.Domain.Dictionary.Enums;
using TakeoutSaaS.Domain.SystemParameters.Entities;
using TakeoutSaaS.Domain.Tenants.Entities;
using TakeoutSaaS.Domain.Tenants.Enums;
using TakeoutSaaS.Infrastructure.App.Options;
using TakeoutSaaS.Infrastructure.Dictionary.Persistence;
namespace TakeoutSaaS.Infrastructure.App.Persistence;
/// <summary>
/// 业务数据种子,确保默认租户与基础字典可重复执行。
/// </summary>
/// <remarks>
/// 初始化种子服务。
/// </remarks>
public sealed class AppDataSeeder(
IServiceProvider serviceProvider,
ILogger<AppDataSeeder> logger,
IOptions<AppSeedOptions> options) : IHostedService
{
private readonly AppSeedOptions _options = options.Value;
/// <inheritdoc />
public async Task StartAsync(CancellationToken cancellationToken)
{
if (!_options.Enabled)
{
logger.LogInformation("AppSeed 未启用,跳过业务数据初始化");
return;
}
using var scope = serviceProvider.CreateScope();
var appDbContext = scope.ServiceProvider.GetRequiredService<TakeoutAppDbContext>();
var dictionaryDbContext = scope.ServiceProvider.GetRequiredService<DictionaryDbContext>();
var defaultTenantId = await EnsureDefaultTenantAsync(appDbContext, cancellationToken);
await EnsureDictionarySeedsAsync(dictionaryDbContext, defaultTenantId, cancellationToken);
logger.LogInformation("AppSeed 完成业务数据初始化");
}
/// <inheritdoc />
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
/// <summary>
/// 确保默认租户存在。
/// </summary>
private async Task<long?> EnsureDefaultTenantAsync(TakeoutAppDbContext dbContext, CancellationToken cancellationToken)
{
var tenantOptions = _options.DefaultTenant;
if (tenantOptions == null || string.IsNullOrWhiteSpace(tenantOptions.Code) || string.IsNullOrWhiteSpace(tenantOptions.Name))
{
logger.LogInformation("AppSeed 未配置默认租户,跳过租户种子");
return null;
}
var code = tenantOptions.Code.Trim();
var existingTenant = await dbContext.Tenants
.IgnoreQueryFilters()
.FirstOrDefaultAsync(x => x.Code == code, cancellationToken);
if (existingTenant == null)
{
var tenant = new Tenant
{
Id = tenantOptions.TenantId,
Code = code,
Name = tenantOptions.Name.Trim(),
ShortName = tenantOptions.ShortName?.Trim(),
ContactName = tenantOptions.ContactName?.Trim(),
ContactPhone = tenantOptions.ContactPhone?.Trim(),
Status = TenantStatus.Active
};
await dbContext.Tenants.AddAsync(tenant, cancellationToken);
await dbContext.SaveChangesAsync(cancellationToken);
logger.LogInformation("AppSeed 已创建默认租户 {TenantCode}", code);
return tenant.Id;
}
var updated = false;
if (!string.Equals(existingTenant.Name, tenantOptions.Name, StringComparison.Ordinal))
{
existingTenant.Name = tenantOptions.Name.Trim();
updated = true;
}
if (!string.Equals(existingTenant.ShortName, tenantOptions.ShortName, StringComparison.Ordinal))
{
existingTenant.ShortName = tenantOptions.ShortName?.Trim();
updated = true;
}
if (!string.Equals(existingTenant.ContactName, tenantOptions.ContactName, StringComparison.Ordinal))
{
existingTenant.ContactName = tenantOptions.ContactName?.Trim();
updated = true;
}
if (!string.Equals(existingTenant.ContactPhone, tenantOptions.ContactPhone, StringComparison.Ordinal))
{
existingTenant.ContactPhone = tenantOptions.ContactPhone?.Trim();
updated = true;
}
if (existingTenant.Status != TenantStatus.Active)
{
existingTenant.Status = TenantStatus.Active;
updated = true;
}
if (updated)
{
dbContext.Tenants.Update(existingTenant);
await dbContext.SaveChangesAsync(cancellationToken);
logger.LogInformation("AppSeed 已更新默认租户 {TenantCode}", code);
}
else
{
logger.LogInformation("AppSeed 默认租户 {TenantCode} 已存在且无需更新", code);
}
return existingTenant.Id;
}
/// <summary>
/// 确保基础字典存在。
/// </summary>
private async Task EnsureDictionarySeedsAsync(DictionaryDbContext dbContext, long? defaultTenantId, CancellationToken cancellationToken)
{
var dictionaryGroups = _options.DictionaryGroups ?? new List<DictionarySeedGroupOptions>();
var hasDictionaryGroups = dictionaryGroups.Count > 0;
if (!hasDictionaryGroups)
{
logger.LogInformation("AppSeed 未配置基础字典,跳过字典种子");
}
if (hasDictionaryGroups)
{
foreach (var groupOptions in dictionaryGroups)
{
if (string.IsNullOrWhiteSpace(groupOptions.Code) || string.IsNullOrWhiteSpace(groupOptions.Name))
{
logger.LogWarning("AppSeed 跳过字典分组Code 或 Name 为空");
continue;
}
var tenantId = groupOptions.TenantId ?? defaultTenantId ?? 0;
var code = groupOptions.Code.Trim();
var group = await dbContext.DictionaryGroups
.IgnoreQueryFilters()
.FirstOrDefaultAsync(x => x.TenantId == tenantId && x.Code == code, cancellationToken);
if (group == null)
{
group = new DictionaryGroup
{
Id = 0,
TenantId = tenantId,
Code = code,
Name = groupOptions.Name.Trim(),
Scope = groupOptions.Scope,
Description = groupOptions.Description?.Trim(),
IsEnabled = groupOptions.IsEnabled
};
await dbContext.DictionaryGroups.AddAsync(group, cancellationToken);
logger.LogInformation("AppSeed 创建字典分组 {GroupCode} (Tenant: {TenantId})", code, tenantId);
}
else
{
var groupUpdated = false;
if (!string.Equals(group.Name, groupOptions.Name, StringComparison.Ordinal))
{
group.Name = groupOptions.Name.Trim();
groupUpdated = true;
}
if (!string.Equals(group.Description, groupOptions.Description, StringComparison.Ordinal))
{
group.Description = groupOptions.Description?.Trim();
groupUpdated = true;
}
if (group.Scope != groupOptions.Scope)
{
group.Scope = groupOptions.Scope;
groupUpdated = true;
}
if (group.IsEnabled != groupOptions.IsEnabled)
{
group.IsEnabled = groupOptions.IsEnabled;
groupUpdated = true;
}
if (groupUpdated)
{
dbContext.DictionaryGroups.Update(group);
}
}
await UpsertDictionaryItemsAsync(dbContext, group, groupOptions.Items, tenantId, cancellationToken);
}
}
await EnsureSystemParametersAsync(dbContext, defaultTenantId, cancellationToken);
await dbContext.SaveChangesAsync(cancellationToken);
}
/// <summary>
/// 确保系统参数以独立表形式可重复种子。
/// </summary>
private async Task EnsureSystemParametersAsync(DictionaryDbContext dbContext, long? defaultTenantId, CancellationToken cancellationToken)
{
var systemParameters = _options.SystemParameters ?? new List<SystemParameterSeedOptions>();
if (systemParameters.Count == 0)
{
logger.LogInformation("AppSeed 未配置系统参数,跳过系统参数种子");
return;
}
var grouped = systemParameters
.Where(x => !string.IsNullOrWhiteSpace(x.Key) && !string.IsNullOrWhiteSpace(x.Value))
.GroupBy(x => x.TenantId ?? defaultTenantId ?? 0);
if (!grouped.Any())
{
logger.LogInformation("AppSeed 系统参数配置为空,跳过系统参数种子");
return;
}
foreach (var group in grouped)
{
var tenantId = group.Key;
var existingParameters = await dbContext.SystemParameters
.IgnoreQueryFilters()
.Where(x => x.TenantId == tenantId)
.ToListAsync(cancellationToken);
foreach (var seed in group)
{
var key = seed.Key.Trim();
var existing = existingParameters.FirstOrDefault(x => x.Key.Equals(key, StringComparison.OrdinalIgnoreCase));
if (existing == null)
{
var parameter = new SystemParameter
{
Id = 0,
TenantId = tenantId,
Key = key,
Value = seed.Value.Trim(),
Description = seed.Description?.Trim(),
SortOrder = seed.SortOrder,
IsEnabled = seed.IsEnabled
};
await dbContext.SystemParameters.AddAsync(parameter, cancellationToken);
continue;
}
var updated = false;
if (!string.Equals(existing.Value, seed.Value, StringComparison.Ordinal))
{
existing.Value = seed.Value.Trim();
updated = true;
}
if (!string.Equals(existing.Description, seed.Description, StringComparison.Ordinal))
{
existing.Description = seed.Description?.Trim();
updated = true;
}
if (existing.SortOrder != seed.SortOrder)
{
existing.SortOrder = seed.SortOrder;
updated = true;
}
if (existing.IsEnabled != seed.IsEnabled)
{
existing.IsEnabled = seed.IsEnabled;
updated = true;
}
if (updated)
{
dbContext.SystemParameters.Update(existing);
}
}
}
}
/// <summary>
/// 合并字典项。
/// </summary>
private static async Task UpsertDictionaryItemsAsync(
DictionaryDbContext dbContext,
DictionaryGroup group,
IEnumerable<DictionarySeedItemOptions> seedItems,
long tenantId,
CancellationToken cancellationToken)
{
var materializedItems = seedItems
.Where(item => !string.IsNullOrWhiteSpace(item.Key) && !string.IsNullOrWhiteSpace(item.Value))
.ToList();
if (materializedItems.Count == 0)
{
return;
}
var existingItems = await dbContext.DictionaryItems
.IgnoreQueryFilters()
.Where(x => x.GroupId == group.Id)
.ToListAsync(cancellationToken);
foreach (var seed in materializedItems)
{
var key = seed.Key.Trim();
var existing = existingItems.FirstOrDefault(x => x.Key.Equals(key, StringComparison.OrdinalIgnoreCase));
if (existing == null)
{
var newItem = new DictionaryItem
{
Id = 0,
TenantId = tenantId,
GroupId = group.Id,
Key = key,
Value = seed.Value.Trim(),
Description = seed.Description?.Trim(),
SortOrder = seed.SortOrder,
IsEnabled = seed.IsEnabled
};
await dbContext.DictionaryItems.AddAsync(newItem, cancellationToken);
continue;
}
var updated = false;
if (!string.Equals(existing.Value, seed.Value, StringComparison.Ordinal))
{
existing.Value = seed.Value.Trim();
updated = true;
}
if (!string.Equals(existing.Description, seed.Description, StringComparison.Ordinal))
{
existing.Description = seed.Description?.Trim();
updated = true;
}
if (existing.SortOrder != seed.SortOrder)
{
existing.SortOrder = seed.SortOrder;
updated = true;
}
if (existing.IsEnabled != seed.IsEnabled)
{
existing.IsEnabled = seed.IsEnabled;
updated = true;
}
if (updated)
{
dbContext.DictionaryItems.Update(existing);
}
}
}
}