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; /// /// 业务数据种子,确保默认租户与基础字典可重复执行。 /// /// /// 初始化种子服务。 /// public sealed class AppDataSeeder( IServiceProvider serviceProvider, ILogger logger, IOptions options) : IHostedService { private readonly AppSeedOptions _options = options.Value; /// public async Task StartAsync(CancellationToken cancellationToken) { if (!_options.Enabled) { logger.LogInformation("AppSeed 未启用,跳过业务数据初始化"); return; } using var scope = serviceProvider.CreateScope(); var appDbContext = scope.ServiceProvider.GetRequiredService(); var dictionaryDbContext = scope.ServiceProvider.GetRequiredService(); var defaultTenantId = await EnsureDefaultTenantAsync(appDbContext, cancellationToken); await EnsureDictionarySeedsAsync(dictionaryDbContext, defaultTenantId, cancellationToken); logger.LogInformation("AppSeed 完成业务数据初始化"); } /// public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; /// /// 确保默认租户存在。 /// private async Task 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; } /// /// 确保基础字典存在。 /// private async Task EnsureDictionarySeedsAsync(DictionaryDbContext dbContext, long? defaultTenantId, CancellationToken cancellationToken) { var dictionaryGroups = _options.DictionaryGroups ?? new List(); 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); } /// /// 确保系统参数以独立表形式可重复种子。 /// private async Task EnsureSystemParametersAsync(DictionaryDbContext dbContext, long? defaultTenantId, CancellationToken cancellationToken) { var systemParameters = _options.SystemParameters ?? new List(); 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); } } } } /// /// 合并字典项。 /// private static async Task UpsertDictionaryItemsAsync( DictionaryDbContext dbContext, DictionaryGroup group, IEnumerable 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); } } } }