422 lines
20 KiB
C#
Raw Permalink 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 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 = "<p>智能工厂生产线仿真系统,采用物理引擎真实模拟生产流程。</p>",
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 = "<p>人体解剖学虚拟仿真教学系统。</p>",
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 = "<p>化学反应实验仿真系统。</p>",
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<string>
{
"毫秒数仿科技有限公司成立于 2020 年,是一家专注于工业仿真与数字孪生领域的国家高新技术企业。公司总部位于上海,在北京、深圳、成都设有研发中心,业务覆盖全国及海外市场。",
"我们以\u201C让工业仿真触手可及\u201D为使命自主研发了基于 WebGL 的高性能仿真引擎与多端运行框架,服务于制造、医疗、教育、军事、建筑等多个行业领域,累计交付项目超过 500 个,服务客户包括多家世界 500 强企业。",
"公司拥有一支由资深图形学专家、仿真工程师、行业顾问组成的精英团队,核心成员来自国内外知名高校与企业,具备深厚的技术积累与丰富的行业经验。"
},
Stats = new List<AboutStatDto>
{
new() { Number = "500+", Label = "交付项目" },
new() { Number = "200+", Label = "服务客户" },
new() { Number = "50+", Label = "团队成员" },
new() { Number = "30+", Label = "行业领域" }
},
MissionTitle = "使命与愿景",
MissionCards = new List<AboutMissionCardDto>
{
new() { Title = "使命", Icon = "rocket", Desc = "让工业仿真触手可及,以技术创新驱动产业升级。" },
new() { Title = "愿景", Icon = "globe", Desc = "成为全球领先的工业仿真与数字孪生平台,连接虚拟与现实,赋能千行百业。" },
new() { Title = "价值观", Icon = "heart", Desc = "客户至上、技术驱动、开放协作、追求卓越。" }
},
TimelineTitle = "发展历程",
Timeline = new List<AboutTimelineItemDto>
{
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();
}
/// <summary>
/// 数据库结构补丁:为已存在的表添加新字段
/// 因为使用 EnsureCreated() 不会更新已存在数据库的表结构,
/// 所以需要手动执行 ALTER TABLE 添加新字段
/// </summary>
private static async Task PatchDatabaseSchemaAsync(MilliSimDbContext db)
{
try
{
// 检查 ProjectPackages 表是否需要添加新字段
var columns = await db.Database.SqlQueryRaw<string>(
"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<string> 要求列别名必须是 Value
var fkName = await db.Database.SqlQueryRaw<string>(
"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<string>(
"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<string>(
"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<string>(
"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<string>(
"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);
}
}