using Jiaowu.Api.Domain.Academic; using Jiaowu.Api.Domain.Identity; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; namespace Jiaowu.Api.Infrastructure.Persistence; public sealed class DatabaseInitializer( AppDbContext db, RoleManager roleManager, UserManager userManager, DevelopmentSqliteMigrator sqliteMigrator, DatabaseOptions databaseOptions, IConfiguration configuration, IHostEnvironment environment, ILogger logger) { public async Task InitializeAsync(bool migrateOnly = false) { if (environment.IsDevelopment()) { if (migrateOnly) { throw new InvalidOperationException( "--migrate-only 仅用于非 Development 环境的 MySQL 数据库。"); } await db.Database.EnsureCreatedAsync(); await sqliteMigrator.MigrateAsync(); } else if (migrateOnly || databaseOptions.ApplyMigrationsOnStartup) { await db.Database.MigrateAsync(); } else { var pendingMigrations = (await db.Database.GetPendingMigrationsAsync()).ToArray(); if (pendingMigrations.Length > 0) { throw new InvalidOperationException( $"数据库还有 {pendingMigrations.Length} 个待执行迁移。请在发布前运行 " + "'Jiaowu.Api --migrate-only',或显式配置 " + "Database:ApplyMigrationsOnStartup=true。"); } } if (migrateOnly) { logger.LogInformation("数据库迁移已完成。"); return; } await SeedRolesAsync(); await SeedAdministratorAsync(); await SeedCourseCategoriesAsync(); } private async Task SeedRolesAsync() { var roleDefinitions = new Dictionary { [SystemRoles.SuperAdmin] = ("系统配置与全部数据管理", DataScope.All), [SystemRoles.AcademicAdmin] = ("校级教务管理", DataScope.All), [SystemRoles.CollegeAdmin] = ("院系教务管理", DataScope.College), [SystemRoles.Teacher] = ("教师教学工作台", DataScope.Self), [SystemRoles.Counselor] = ("辅导员与班级管理", DataScope.Class), [SystemRoles.Student] = ("学生自助服务", DataScope.Self), [SystemRoles.Leader] = ("校级统计查看", DataScope.All) }; foreach (var (name, definition) in roleDefinitions) { if (await roleManager.RoleExistsAsync(name)) { continue; } var result = await roleManager.CreateAsync(new ApplicationRole { Name = name, Description = definition.Description, DataScope = definition.Scope }); EnsureSucceeded(result, $"创建角色 {name}"); } } private async Task SeedAdministratorAsync() { var userName = configuration["SeedAdmin:UserName"]; var password = configuration["SeedAdmin:Password"]; if (string.IsNullOrWhiteSpace(userName) || string.IsNullOrWhiteSpace(password)) { if (!environment.IsDevelopment()) { logger.LogWarning("未配置 SeedAdmin,生产环境不会创建默认管理员。"); } return; } var user = await userManager.FindByNameAsync(userName); if (user is null) { user = new ApplicationUser { UserName = userName, DisplayName = configuration["SeedAdmin:DisplayName"] ?? "系统管理员", LockoutEnabled = true, IsEnabled = true }; EnsureSucceeded(await userManager.CreateAsync(user, password), "创建初始管理员"); } if (!user.LockoutEnabled) { user.LockoutEnabled = true; EnsureSucceeded(await userManager.UpdateAsync(user), "启用管理员登录保护"); } if (!await userManager.IsInRoleAsync(user, SystemRoles.SuperAdmin)) { EnsureSucceeded( await userManager.AddToRoleAsync(user, SystemRoles.SuperAdmin), "授予超级管理员角色"); } } private async Task SeedCourseCategoriesAsync() { var defaults = new[] { ("BASIC", "基础课程", 10), ("MORAL", "德育课程", 20), ("AESTHETIC", "美育课程", 30), ("LABOR", "劳动教育", 40), ("INNOVATION", "创新创业", 50), ("ENGLISH", "大学英语", 60), ("SPORTS", "大学体育", 70), ("MILITARY", "国防教育", 80), ("MAJOR", "专业教育", 90), ("PRACTICE", "实践教学", 100) }; var existingCodes = (await db.CourseCategories .Select(x => x.Code) .ToListAsync()) .ToHashSet(StringComparer.OrdinalIgnoreCase); foreach (var (code, name, sortOrder) in defaults) { if (existingCodes.Contains(code)) { continue; } db.CourseCategories.Add(new CourseCategory { Code = code, Name = name, SortOrder = sortOrder }); } await db.SaveChangesAsync(); var categories = await db.CourseCategories .ToDictionaryAsync(x => x.Code, StringComparer.OrdinalIgnoreCase); var uncategorizedCourses = await db.Courses .Where(x => x.CourseCategoryId == null) .ToListAsync(); foreach (var course in uncategorizedCourses) { var categoryCode = course.Nature switch { CourseNature.Practice => "PRACTICE", CourseNature.MajorRequired or CourseNature.MajorElective => "MAJOR", _ => "BASIC" }; course.CourseCategoryId = categories[categoryCode].Id; } await db.SaveChangesAsync(); } private static void EnsureSucceeded(IdentityResult result, string action) { if (result.Succeeded) { return; } throw new InvalidOperationException( $"{action}失败:{string.Join(";", result.Errors.Select(x => x.Description))}"); } }