using BCrypt.Net; using Microsoft.EntityFrameworkCore; using MilliSim.Common.Entities; using MilliSim.Common.Models; using MilliSim.Common.Dtos; using System.Text.Json; namespace MilliSim.Data; public static class DbSeeder { public static async Task SeedAsync(MilliSimDbContext db) { // 先执行数据库结构补丁(为已存在的表添加新字段) await PatchDatabaseSchemaAsync(db); // 管理员 if (!db.Users.Any(u => u.Username == "admin")) { db.Users.Add(new User { Username = "admin", Password = BCryptHelper.HashPassword("admin123"), Nickname = "系统管理员", Role = UserRole.Admin, Status = UserStatus.Active, Email = "admin@millisim.com", Phone = "13800000000" }); } // 产品经理 if (!db.Users.Any(u => u.Username == "pm")) { db.Users.Add(new User { Username = "pm", Password = BCryptHelper.HashPassword("123456"), Nickname = "产品经理", Role = UserRole.ProductManager, Status = UserStatus.Active, Email = "pm@millisim.com", Phone = "13800000001" }); } // 销售 if (!db.Users.Any(u => u.Username == "sales")) { db.Users.Add(new User { Username = "sales", Password = BCryptHelper.HashPassword("123456"), Nickname = "销售员", Role = UserRole.Sales, Status = UserStatus.Active, Email = "sales@millisim.com", Phone = "13800000002" }); } // 分类 if (!db.ProjectCategories.Any()) { db.ProjectCategories.AddRange( new ProjectCategory { Name = "工业仿真", Icon = "factory", SortOrder = 1, Status = UserStatus.Active }, new ProjectCategory { Name = "医疗仿真", Icon = "medical", SortOrder = 2, Status = UserStatus.Active }, new ProjectCategory { Name = "教育仿真", Icon = "education", SortOrder = 3, Status = UserStatus.Active }, new ProjectCategory { Name = "军事仿真", Icon = "military", SortOrder = 4, Status = UserStatus.Active }, new ProjectCategory { Name = "建筑仿真", Icon = "building", SortOrder = 5, Status = UserStatus.Active } ); await db.SaveChangesAsync(); } // 平台类型 if (!db.PlatformTypes.Any()) { db.PlatformTypes.AddRange( new PlatformType { Name = "WebGL", Icon = "web", SortOrder = 1, Status = UserStatus.Active }, new PlatformType { Name = "Android", Icon = "android", SortOrder = 2, Status = UserStatus.Active }, new PlatformType { Name = "Windows", Icon = "windows", SortOrder = 3, Status = UserStatus.Active }, new PlatformType { Name = "Mac", Icon = "apple", SortOrder = 4, Status = UserStatus.Active }, new PlatformType { Name = "VR", Icon = "vr", SortOrder = 5, Status = UserStatus.Active } ); await db.SaveChangesAsync(); } // 特性标签 if (!db.FeatureTags.Any()) { db.FeatureTags.AddRange( new FeatureTag { Name = "高精度", Icon = "precision", SortOrder = 1, Status = UserStatus.Active }, new FeatureTag { Name = "实时渲染", Icon = "render", SortOrder = 2, Status = UserStatus.Active }, new FeatureTag { Name = "物理引擎", Icon = "physics", SortOrder = 3, Status = UserStatus.Active }, new FeatureTag { Name = "多人协作", Icon = "collaboration", SortOrder = 4, Status = UserStatus.Active }, new FeatureTag { Name = "VR支持", Icon = "vr", SortOrder = 5, Status = UserStatus.Active } ); await db.SaveChangesAsync(); } // 示例项目 if (!db.Projects.Any()) { var categories = db.ProjectCategories.OrderBy(c => c.SortOrder).ToList(); db.Projects.AddRange( new Project { Name = "智能工厂生产线仿真", CategoryId = categories[0].Id, CoverImage = "", Description = "基于Unity开发的智能工厂生产线三维仿真系统,支持产线布局规划与工艺流程模拟。", Content = "

智能工厂生产线仿真系统,采用物理引擎真实模拟生产流程。

", Platforms = "WebGL,Windows", Status = ProjectStatus.Published, IsFeatured = true, SortOrder = 1, ViewCount = 1280, ExperienceCount = 356, FavoriteCount = 89, PublishedAt = DateTime.Now.AddDays(-10) }, new Project { Name = "人体解剖学虚拟仿真", CategoryId = categories[1].Id, CoverImage = "", Description = "三维人体解剖学教学仿真,支持器官拆解与结构学习。", Content = "

人体解剖学虚拟仿真教学系统。

", Platforms = "WebGL,VR", Status = ProjectStatus.Published, IsFeatured = true, SortOrder = 2, ViewCount = 980, ExperienceCount = 245, FavoriteCount = 67, PublishedAt = DateTime.Now.AddDays(-5) }, new Project { Name = "化学反应实验仿真", CategoryId = categories[2].Id, CoverImage = "", Description = "虚拟化学实验室,支持多种化学反应模拟与安全培训。", Content = "

化学反应实验仿真系统。

", Platforms = "WebGL,Android", Status = ProjectStatus.Published, IsFeatured = false, SortOrder = 3, ViewCount = 560, ExperienceCount = 120, FavoriteCount = 34, PublishedAt = DateTime.Now.AddDays(-2) } ); await db.SaveChangesAsync(); } // 轮播图 if (!db.Banners.Any()) { db.Banners.AddRange( new Banner { Title = "毫秒数仿 - 专业虚拟仿真平台", Image = "", Link = "/projects", SortOrder = 1, Status = UserStatus.Active }, new Banner { Title = "海量项目资源", Image = "", Link = "/projects", SortOrder = 2, Status = UserStatus.Active }, new Banner { Title = "在线体验 WebGL", Image = "", Link = "/projects", SortOrder = 3, Status = UserStatus.Active } ); } // 系统设置(补充缺失的设置项) var allSettings = new List<(string Key, string Value, string Description)> { // ===== 基础信息 ===== ("PlatformName", "毫秒数仿", "平台名称"), ("Slogan", "专业虚拟仿真项目资产库", "平台标语"), ("CompanyName", "毫秒数仿科技有限公司", "公司名称"), ("Contact", "400-888-8888", "联系方式"), ("ICP", "沪ICP备XXXXXXXX号", "备案号"), // ===== Logo 配置 ===== ("LogoFrontend", "", "前台Logo"), ("LogoBackend", "", "后台Logo"), ("LogoFavicon", "", "浏览器图标"), ("LogoLogin", "", "登录页Logo"), ("LogoFooter", "", "页脚Logo"), // ===== 资源存储配置(默认COS私有读写) ===== ("StorageMode", "Cos", "存储模式:Local=本地存储,Cos=腾讯云COS"), ("CosSecretId", "AKIDvmQp11oNjtqa7sTr23sTzIPVlvvmR6lO", "腾讯云COS SecretId"), ("CosSecretKey", "VqHWm083mKjzIXdZN7sTtIct69z9B2gL", "腾讯云COS SecretKey"), ("CosBucket", "millisim-1300718495", "腾讯云COS 存储桶名称"), ("CosRegion", "ap-guangzhou", "腾讯云COS 地域"), ("CosSignedUrlExpireMinutes", "60", "COS临时URL有效期(分钟)"), // ===== 本地存储配置 ===== ("LocalBasePath", "wwwroot/uploads", "本地存储根目录"), ("LocalBaseUrl", "/uploads", "本地访问基础URL"), // ===== 上传大小限制(字节) ===== ("UploadLimitImage", "5242880", "图片上传限制(字节) 5MB"), ("UploadLimitVideo", "524288000", "视频上传限制(字节) 500MB"), ("UploadLimitPackage", "2147483648", "资源包上传限制(字节) 2GB"), ("UploadLimitDocument", "20971520", "文档上传限制(字节) 20MB"), // ===== 备份配置 ===== ("BackupAutoEnabled", "true", "是否启用自动备份"), ("BackupAutoTime", "02:00", "自动备份时间(HH:mm)"), ("BackupRetentionDays", "30", "备份保留天数"), // ===== 日志配置 ===== ("LogRetentionDays", "90", "日志保留天数"), // ===== 首页模块显示 ===== ("ShowPartners", "true", "是否在前台首页显示合作伙伴模块"), ("ShowAbout", "true", "是否在前台显示关于我们页面"), // ===== 关于我们页面内容(JSON) ===== ("AboutContent", JsonSerializer.Serialize(new AboutContentDto { HeroTag = "关于我们", HeroTitle = "用技术连接虚拟与现实", HeroDesc = "毫秒数仿科技有限公司,专注工业仿真与数字孪生技术创新,致力于让每一个仿真场景都触手可及。", IntroTitle = "公司简介", IntroParagraphs = new List { "毫秒数仿科技有限公司成立于 2020 年,是一家专注于工业仿真与数字孪生领域的国家高新技术企业。公司总部位于上海,在北京、深圳、成都设有研发中心,业务覆盖全国及海外市场。", "我们以\u201C让工业仿真触手可及\u201D为使命,自主研发了基于 WebGL 的高性能仿真引擎与多端运行框架,服务于制造、医疗、教育、军事、建筑等多个行业领域,累计交付项目超过 500 个,服务客户包括多家世界 500 强企业。", "公司拥有一支由资深图形学专家、仿真工程师、行业顾问组成的精英团队,核心成员来自国内外知名高校与企业,具备深厚的技术积累与丰富的行业经验。" }, Stats = new List { new() { Number = "500+", Label = "交付项目" }, new() { Number = "200+", Label = "服务客户" }, new() { Number = "50+", Label = "团队成员" }, new() { Number = "30+", Label = "行业领域" } }, MissionTitle = "使命与愿景", MissionCards = new List { new() { Title = "使命", Icon = "rocket", Desc = "让工业仿真触手可及,以技术创新驱动产业升级。" }, new() { Title = "愿景", Icon = "globe", Desc = "成为全球领先的工业仿真与数字孪生平台,连接虚拟与现实,赋能千行百业。" }, new() { Title = "价值观", Icon = "heart", Desc = "客户至上、技术驱动、开放协作、追求卓越。" } }, TimelineTitle = "发展历程", Timeline = new List { new() { Year = "2020", Title = "公司成立", Desc = "毫秒数仿在上海张江正式成立,开启工业仿真技术探索之路。" }, new() { Year = "2021", Title = "引擎发布", Desc = "自主研发的 WebGL 仿真引擎 v1.0 正式发布,支持基础渲染与交互。" }, new() { Year = "2022", Title = "多端覆盖", Desc = "完成 Web、Android、Windows、iOS 多端运行框架,业务覆盖 10+ 行业。" }, new() { Year = "2023", Title = "数字孪生", Desc = "推出数字孪生平台,融合实时数据驱动与协同交互能力,服务客户超 200 家。" }, new() { Year = "2024", Title = "生态构建", Desc = "开放平台能力,构建合作伙伴生态,累计交付项目突破 500 个。" } } }), "关于我们页面内容(JSON 格式)") }; var existingKeys = db.SystemSettings.Select(s => s.Key).ToHashSet(); var missingSettings = allSettings .Where(s => !existingKeys.Contains(s.Key)) .Select(s => new SystemSetting { Key = s.Key, Value = s.Value, Description = s.Description }) .ToList(); if (missingSettings.Count > 0) { db.SystemSettings.AddRange(missingSettings); } // 合作伙伴 if (!db.Partners.Any()) { db.Partners.AddRange( new Partner { Name = "腾讯云", Logo = "", Link = "#", SortOrder = 1, IsVisible = true }, new Partner { Name = "Unity", Logo = "", Link = "#", SortOrder = 2, IsVisible = true } ); } await db.SaveChangesAsync(); } /// /// 数据库结构补丁:为已存在的表添加新字段 /// 因为使用 EnsureCreated() 不会更新已存在数据库的表结构, /// 所以需要手动执行 ALTER TABLE 添加新字段 /// private static async Task PatchDatabaseSchemaAsync(MilliSimDbContext db) { try { // 检查 ProjectPackages 表是否需要添加新字段 var columns = await db.Database.SqlQueryRaw( "SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'ProjectPackages'" ).ToListAsync(); var columnSet = columns.ToHashSet(); // 添加 WakeProtocolPrefix 字段 if (!columnSet.Contains("WakeProtocolPrefix")) { await db.Database.ExecuteSqlRawAsync( "ALTER TABLE `ProjectPackages` ADD COLUMN `WakeProtocolPrefix` VARCHAR(50) NULL DEFAULT ''"); } // 添加 WakeProtocolParam 字段 if (!columnSet.Contains("WakeProtocolParam")) { await db.Database.ExecuteSqlRawAsync( "ALTER TABLE `ProjectPackages` ADD COLUMN `WakeProtocolParam` VARCHAR(500) NULL DEFAULT ''"); } // 添加 ExtractedPath 字段 if (!columnSet.Contains("ExtractedPath")) { await db.Database.ExecuteSqlRawAsync( "ALTER TABLE `ProjectPackages` ADD COLUMN `ExtractedPath` VARCHAR(500) NULL DEFAULT ''"); } // 添加 FileName 字段(原始文件名) if (!columnSet.Contains("FileName")) { await db.Database.ExecuteSqlRawAsync( "ALTER TABLE `ProjectPackages` ADD COLUMN `FileName` VARCHAR(255) NULL DEFAULT ''"); } // ===== 修复 Authorizations.ProjectId:客户级授权不再关联项目 ===== // 1) 移除外键约束 FK_Authorizations_Projects_ProjectId(若存在) // 2) 让 ProjectId 列可空(实体已声明为 long?) // 注意:SqlQueryRaw 要求列别名必须是 Value var fkName = await db.Database.SqlQueryRaw( "SELECT CONSTRAINT_NAME AS `Value` FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS " + "WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'Authorizations' " + "AND CONSTRAINT_TYPE = 'FOREIGN KEY' AND CONSTRAINT_NAME = 'FK_Authorizations_Projects_ProjectId'" ).FirstOrDefaultAsync(); if (fkName != null) { await db.Database.ExecuteSqlRawAsync( "ALTER TABLE `Authorizations` DROP FOREIGN KEY `FK_Authorizations_Projects_ProjectId`"); } // 让 ProjectId 列可空 var projectIdNullable = await db.Database.SqlQueryRaw( "SELECT IS_NULLABLE AS `Value` FROM INFORMATION_SCHEMA.COLUMNS " + "WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'Authorizations' AND COLUMN_NAME = 'ProjectId'" ).FirstOrDefaultAsync(); if (projectIdNullable != null && projectIdNullable != "YES") { await db.Database.ExecuteSqlRawAsync( "ALTER TABLE `Authorizations` MODIFY COLUMN `ProjectId` BIGINT NULL"); } // ===== 修复分类/平台/标签表 Description 字段:实体模型已移除 Description,但 DB 列可能仍为 NOT NULL ===== // 导致 DbSeeder 插入新记录时失败("Field 'Description' doesn't have a default value") var descTablesToPatch = new[] { "ProjectCategories", "PlatformTypes", "FeatureTags" }; foreach (var table in descTablesToPatch) { var descInfo = await db.Database.SqlQueryRaw( "SELECT IS_NULLABLE AS `Value` FROM INFORMATION_SCHEMA.COLUMNS " + "WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = '" + table + "' AND COLUMN_NAME = 'Description'" ).FirstOrDefaultAsync(); if (descInfo != null && descInfo != "YES") { await db.Database.ExecuteSqlRawAsync( "ALTER TABLE `" + table + "` MODIFY COLUMN `Description` VARCHAR(200) NULL DEFAULT ''"); } } // ===== 修复 ProjectPackages.Version 字段:实体模型已移除 Version,但 DB 列可能仍为 NOT NULL ===== var versionInfo = await db.Database.SqlQueryRaw( "SELECT IS_NULLABLE AS `Value` FROM INFORMATION_SCHEMA.COLUMNS " + "WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'ProjectPackages' AND COLUMN_NAME = 'Version'" ).FirstOrDefaultAsync(); if (versionInfo != null && versionInfo != "YES") { await db.Database.ExecuteSqlRawAsync( "ALTER TABLE `ProjectPackages` MODIFY COLUMN `Version` VARCHAR(50) NULL DEFAULT ''"); } // ===== PendingUploads 表:垃圾资源清理跟踪 ===== var pendingTableExists = await db.Database.SqlQueryRaw( "SELECT TABLE_NAME AS `Value` FROM INFORMATION_SCHEMA.TABLES " + "WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'PendingUploads'" ).FirstOrDefaultAsync(); if (pendingTableExists == null) { await db.Database.ExecuteSqlRawAsync(@" CREATE TABLE `PendingUploads` ( `Id` BIGINT NOT NULL AUTO_INCREMENT, `ObjectKey` VARCHAR(500) NOT NULL, `Folder` VARCHAR(50) NOT NULL DEFAULT '', `UploadedBy` BIGINT NULL, `UploadedAt` DATETIME NOT NULL, `ConsumedAt` DATETIME NULL, `CreatedAt` DATETIME NOT NULL, `UpdatedAt` DATETIME NOT NULL, `IsDeleted` BIT NOT NULL DEFAULT 0, PRIMARY KEY (`Id`), INDEX `IX_PendingUploads_ObjectKey` (`ObjectKey`), INDEX `IX_PendingUploads_ConsumedAt` (`ConsumedAt`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;"); } } catch { // 忽略补丁错误(如数据库不存在时 INFORMATION_SCHEMA 查询失败) } } } public static class BCryptHelper { public static string HashPassword(string password) { return BCrypt.Net.BCrypt.HashPassword(password, 12); } public static bool VerifyPassword(string password, string hash) { return BCrypt.Net.BCrypt.Verify(password, hash); } }