Files
f9-web-ai-test/backend/F9WebAiTest.Infrastructure/Data/DbSeeder.cs
T
yanpeng200212 fe1ab34242 车间班次菜单调整到「生产-前缫」下
- 菜单归属由「基础数据」改为「生产-前缫」,路径 /base/chejianbanci → /prod/qianxian/chejianbanci
- 权限标识 base:chejianbanci:list → prod:chejianbanci:list
- 清理历史遗留的旧菜单与旧权限单元
- 页面组件路径保持 base/chejianbanci/index 不变,前端无需改动
2026-10-12 16:37:07 +08:00

902 lines
47 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.Data;
using FreeSql;
using F9WebAiTest.Application.Interfaces.SnowflakeID;
using F9WebAiTest.Domain.Common;
using F9WebAiTest.Domain.Entities;
using F9WebAiTest.Domain.Entities.Base;
using F9WebAiTest.Domain.Entities.Notice;
using F9WebAiTest.Domain.Enums.SYS.RBAC;
using SysMenu = F9WebAiTest.Domain.Entities.SYS.RBAC.Menu;
using SysRole = F9WebAiTest.Domain.Entities.SYS.RBAC.Role;
using SysPermission = F9WebAiTest.Domain.Entities.SYS.RBAC.Permission;
using SysRolePermission = F9WebAiTest.Domain.Entities.SYS.RBAC.RolePermission;
namespace F9WebAiTest.Infrastructure.Data;
public static class DbSeeder
{
public static void Seed(IFreeSql fsql, ISnowflakeIdGenerator snowflake)
{
// 1. 建表(CodeFirst SyncStructure,增量同步,兼容旧库)
SyncStructure(fsql);
// 2. 默认角色
if (!fsql.Select<Role>().Any())
{
fsql.Insert(new Role { Name = "超级管理员", Code = "admin", Description = "拥有所有权限", Sort = 1 }).ExecuteAffrows();
fsql.Insert(new Role { Name = "普通用户", Code = "user", Description = "普通员工", Sort = 2 }).ExecuteAffrows();
}
// 3. 默认菜单(系统管理)
if (!fsql.Select<Menu>().Any())
{
var systemId = InsertAndGetId(fsql, new Menu { Name = "系统管理", Type = "directory", Path = "/system", Icon = "Setting", Sort = 1 });
var deptId = InsertAndGetId(fsql, new Menu { Name = "部门管理", Type = "menu", Path = "/system/department", Component = "system/department/index", Icon = "OfficeBuilding", Permission = "system:department:list", Sort = 1, ParentId = systemId });
var empId = InsertAndGetId(fsql, new Menu { Name = "职工管理", Type = "menu", Path = "/system/employee", Component = "system/employee/index", Icon = "User", Permission = "system:employee:list", Sort = 2, ParentId = systemId });
var roleId = InsertAndGetId(fsql, new Menu { Name = "角色管理", Type = "menu", Path = "/system/role", Component = "system/role/index", Icon = "UserFilled", Permission = "system:role:list", Sort = 3, ParentId = systemId });
var menuId = InsertAndGetId(fsql, new Menu { Name = "菜单管理", Type = "menu", Path = "/system/menu", Component = "system/menu/index", Icon = "Menu", Permission = "system:menu:list", Sort = 4, ParentId = systemId });
var logId = InsertAndGetId(fsql, new Menu { Name = "操作日志", Type = "menu", Path = "/system/log", Component = "system/log/index", Icon = "Document", Permission = "system:log:list", Sort = 5, ParentId = systemId });
fsql.Insert(new Menu[]
{
new Menu { Name = "部门操作", Type = "button", Permission = "system:department:btn", Sort = 1, ParentId = deptId },
new Menu { Name = "职工操作", Type = "button", Permission = "system:employee:btn", Sort = 1, ParentId = empId },
new Menu { Name = "角色操作", Type = "button", Permission = "system:role:btn", Sort = 1, ParentId = roleId },
new Menu { Name = "菜单操作", Type = "button", Permission = "system:menu:btn", Sort = 1, ParentId = menuId },
}).ExecuteAffrows();
}
// 4. 考勤菜单(增量)
SeedAttendanceMenus(fsql);
// 5. 业务基础数据(部门树 + 机组车台 + 庄口 + 规格 + 定级指标 + 菜单)
BusinessSeedData.Seed(fsql);
// 6. 通知:消息类型种子 + 菜单
SeedNotificationData(fsql, snowflake);
// 6.5 数据迁移:旧 RBAC 表 → 新 DDD RBAC 表(先迁移,确保 admin 角色/用户已在新表)
MigrateRbacData(fsql);
// 6.6 F9 风格 RBAC 菜单(用户/角色/菜单/部门管理,写入新 sys_rbac_menus 表)
SeedSysRbacMenus(fsql, snowflake);
// 6.65 基础数据(工种 + 注册表配置 + 原料庄口 + 物资 + 往来单位 + 职工,F9 导出数据幂等导入)
WorkTypeSeedData.Seed(fsql, snowflake);
RegistrySeedData.Seed(fsql);
YuanliaoZhuangkouSeedData.Seed(fsql);
WuziSeedData.Seed(fsql);
GongyingshangSeedData.Seed(fsql);
EmployeeSeedData.Seed(fsql);
// 6.66 基础数据菜单(工种管理、账期管理、注册表、原料庄口、物资目录、往来单位)
SeedWorkTypeMenus(fsql, snowflake);
SeedZhangqiMenus(fsql, snowflake);
SeedRegistryMenus(fsql, snowflake);
SeedYuanliaoZhuangkouMenus(fsql, snowflake);
SeedWuziMenus(fsql, snowflake);
SeedGongyingshangMenus(fsql, snowflake);
// 6.67 职工管理(一级菜单,与"基础数据"平级)
SeedEmployeeTopMenu(fsql, snowflake);
// 6.68 生产模块(按工序拆分:生产-前缫 / 生产-后缫)
SeedProdMenus(fsql, snowflake);
// 6.69 修正前缫部门的职责类型(车间班次菜单随"生产-前缫"一起处理)
FixQianxianDutyType(fsql);
// 6.7 为菜单创建权限单元(Permission),并修正旧菜单权限关联(统一权限模型)
FixMenuPermissions(fsql, snowflake);
// 7. 管理员账号(幂等)
var admin = fsql.Select<Employee>().Where(e => e.Username == "admin").First();
if (admin == null)
{
var dept = fsql.Select<Department>().Where(d => d.Name == "主干运维").First()
?? fsql.Select<Department>().First();
var adminId = InsertAndGetId(fsql, new Employee
{
Username = "admin",
Password = BCrypt.Net.BCrypt.HashPassword("123456"),
RealName = "系统管理员",
Nickname = "管理员",
DepartmentId = dept.Id,
Email = "admin@company.com",
Phone = "13800000000",
Status = 1
});
admin = fsql.Select<Employee>().Where(e => e.Id == adminId).First();
}
// 8. admin 关联 admin 角色
var adminRole = fsql.Select<Role>().Where(r => r.Code == "admin").First();
if (!fsql.Select<EmployeeRole>().Any(er => er.EmployeeId == admin.Id && er.RoleId == adminRole.Id))
{
fsql.Insert(new EmployeeRole { EmployeeId = admin.Id, RoleId = adminRole.Id }).ExecuteAffrows();
}
// 9. admin 角色分配所有菜单(幂等补充)
var allMenus = fsql.Select<Menu>().ToList();
foreach (var m in allMenus)
{
if (!fsql.Select<RoleMenu>().Any(rm => rm.RoleId == adminRole.Id && rm.MenuId == m.Id))
{
fsql.Insert(new RoleMenu { RoleId = adminRole.Id, MenuId = m.Id }).ExecuteAffrows();
}
}
}
/// <summary>CodeFirst 建表(新表建表,旧表同步失败则跳过)+ 旧库加列兜底</summary>
private static void SyncStructure(IFreeSql fsql)
{
// 扫描 BaseEntity(旧)与 Entity(新 DDD 基类)两类实体的具体子类建表
var entityTypes = typeof(BaseEntity).Assembly.GetTypes()
.Where(t => t.IsClass && !t.IsAbstract &&
(t.IsSubclassOf(typeof(BaseEntity)) || t.IsSubclassOf(typeof(Entity))));
foreach (var t in entityTypes)
{
try
{
fsql.CodeFirst.SyncStructure(t);
}
catch (Exception ex)
{
// 旧表同步失败(如唯一索引与历史数据冲突),跳过,保持旧库兼容
Console.WriteLine($"[DbSeeder] 表 {t.Name} 结构同步跳过:{ex.Message}");
}
}
// 旧库加列兜底(SyncStructure 不会为已有表加列)
EnsureColumn(fsql, "LeaveRequests", "CurrentOrderNo", "INTEGER NOT NULL DEFAULT 1");
EnsureColumn(fsql, "NotificationTypes", "Category", "INTEGER NOT NULL DEFAULT 0");
EnsureColumn(fsql, "NotificationTypes", "Icon", "TEXT NOT NULL DEFAULT ''");
EnsureColumn(fsql, "NotificationTypes", "RouteTemplate", "TEXT NOT NULL DEFAULT ''");
EnsureColumn(fsql, "NotificationMessages", "Category", "INTEGER NOT NULL DEFAULT 0");
EnsureColumn(fsql, "NotificationMessages", "RoutePath", "TEXT NOT NULL DEFAULT ''");
}
/// <summary>为已有表补加列(列已存在则忽略)</summary>
private static void EnsureColumn(IFreeSql fsql, string table, string column, string definition)
{
try
{
fsql.Ado.ExecuteNonQuery($"ALTER TABLE \"{table}\" ADD COLUMN \"{column}\" {definition};");
}
catch
{
// 列已存在,忽略
}
}
/// <summary>插入并返回自增主键 ID(SQLite 用 last_insert_rowid 可靠获取)</summary>
public static long InsertAndGetId<T>(IFreeSql fsql, T entity) where T : BaseEntity
{
fsql.Insert(entity).ExecuteAffrows();
return Convert.ToInt64(fsql.Ado.ExecuteScalar("SELECT last_insert_rowid();"));
}
/// <summary>通知种子:消息类型 + 消息中心菜单</summary>
private static void SeedNotificationData(IFreeSql fsql, ISnowflakeIdGenerator snowflake)
{
// 消息类型:发现特野
if (!fsql.Select<NotificationType>().Any(t => t.Code == "TeyeFound"))
{
fsql.Insert(new NotificationType
{
Code = "TeyeFound", Name = "发现特野", Description = "纤度检验发现特野时自动推送提醒",
RequiredPermission = "", IsActive = true, CreatedBy = 0, Sort = 1,
Category = NotificationCategory.Notification, Icon = "Warning", RouteTemplate = "/prod/houxian/xdjy"
}).ExecuteAffrows();
}
// 消息类型:发现特大糙
if (!fsql.Select<NotificationType>().Any(t => t.Code == "TdcFound"))
{
fsql.Insert(new NotificationType
{
Code = "TdcFound", Name = "发现特大糙", Description = "黑板检验发现特大糙时自动推送提醒",
RequiredPermission = "", IsActive = true, CreatedBy = 0, Sort = 2,
Category = NotificationCategory.Notification, Icon = "Warning", RouteTemplate = "/prod/houxian/hbjy"
}).ExecuteAffrows();
}
// 消息类型:请假审批(订阅权限用独立码,避免与业务按钮权限冲突)
if (!fsql.Select<NotificationType>().Any(t => t.Code == "LeaveApproval"))
{
fsql.Insert(new NotificationType
{
Code = "LeaveApproval", Name = "请假审批", Description = "员工提交请假申请后,推送待办给审批人",
RequiredPermission = "notification:subscribe:leaveapproval", IsActive = true, CreatedBy = 0, Sort = 3,
Category = NotificationCategory.Todo, Icon = "Checked", RouteTemplate = "/attendance/approve?leaveId={businessId}"
}).ExecuteAffrows();
}
// 消息类型:请假结果通知
if (!fsql.Select<NotificationType>().Any(t => t.Code == "LeaveResult"))
{
fsql.Insert(new NotificationType
{
Code = "LeaveResult", Name = "请假结果通知", Description = "请假审批完成后通知申请人结果",
RequiredPermission = "", IsActive = true, CreatedBy = 0, Sort = 4,
Category = NotificationCategory.Notification, Icon = "Message", RouteTemplate = "/attendance/my"
}).ExecuteAffrows();
}
// 消息类型:广播通知(有权限的用户主动发送的广播,推送给所有订阅者)
if (!fsql.Select<NotificationType>().Any(t => t.Code == "Broadcast"))
{
fsql.Insert(new NotificationType
{
Code = "Broadcast", Name = "广播通知", Description = "用户发送的广播通知,推送给所有订阅该类型的用户",
RequiredPermission = "", IsActive = true, CreatedBy = 0, Sort = 5,
Category = NotificationCategory.Announcement, Icon = "Bell", RouteTemplate = ""
}).ExecuteAffrows();
}
// 广播发送权限单元(ResourceType=操作,Code=notification:broadcast,admin 自动拥有,普通用户可在角色管理分配)
var broadcastPerm = fsql.Select<SysPermission>()
.Where(p => p.Code == "notification:broadcast").First();
if (broadcastPerm == null)
{
var perm = new SysPermission(snowflake.CreateId(), Permission_Type_Enum.Enable, Resource_Type_Enum.操作, "broadcast", 0);
perm.SetCode("notification:broadcast");
fsql.Insert(perm).ExecuteAffrows();
}
// 菜单:消息中心
var notiDirId = fsql.Select<Menu>().Where(m => m.Path == "/notification").First()?.Id ?? 0;
if (notiDirId == 0)
{
notiDirId = InsertAndGetId(fsql, new Menu { Name = "消息中心", Type = "directory", Path = "/notification", Icon = "Bell", Sort = 5 });
}
AddNotificationMenu(fsql, notiDirId, "我的消息", "/notification/messages", "notification/messages/index", "Message", "notification:messages:list", 1);
AddNotificationMenu(fsql, notiDirId, "消息订阅", "/notification/subscribe", "notification/subscribe/index", "BellFilled", "notification:subscribe:list", 2);
AddNotificationMenu(fsql, notiDirId, "消息类型管理", "/notification/type", "notification/type/index", "SetUp", "notification:type:list", 3);
// 清理历史遗留:发现特野/发现特大糙 改为「所有人可订阅」
foreach (var (code, oldPerm) in new[] { ("TeyeFound", "notification:subscribe:foundteye"), ("TdcFound", "notification:subscribe:foundtedacao") })
{
var t = fsql.Select<NotificationType>().Where(x => x.Code == code).First();
if (t == null) continue;
// 1. RequiredPermission 改为空(所有人可订阅)
if (!string.IsNullOrEmpty(t.RequiredPermission))
{
t.RequiredPermission = "";
fsql.Update<NotificationType>().SetSource(t).ExecuteAffrows();
}
// 2. 删除订阅权限单元及其角色关联(新 Permission 表)
var perm = fsql.Select<SysPermission>()
.Where(p => p.ResourceType == Resource_Type_Enum.消息订阅 && p.ResourceId == t.Id).First();
if (perm != null)
{
fsql.Delete<SysRolePermission>().Where(rp => rp.PermissionId == perm.Id).ExecuteAffrows();
fsql.Delete<SysPermission>().Where(p => p.Id == perm.Id).ExecuteAffrows();
}
// 3. 删除历史遗留的旧"订阅按钮"菜单及其角色关联(新表 sys_rbac_menus + 旧表 Menus)
fsql.Ado.ExecuteNonQuery($@"
DELETE FROM sys_rbac_role_permissions WHERE permission_id IN
(SELECT id FROM sys_rbac_menus WHERE permission = '{oldPerm}');
DELETE FROM sys_rbac_menus WHERE permission = '{oldPerm}';
DELETE FROM RoleMenus WHERE MenuId IN (SELECT Id FROM Menus WHERE Permission = '{oldPerm}');
DELETE FROM Menus WHERE Permission = '{oldPerm}';");
}
// 清理所有旧的"订阅按钮"菜单(已被 Permission 表的消息订阅权限替代,避免与消息订阅权限重复冗余)
fsql.Ado.ExecuteNonQuery(@"
DELETE FROM sys_rbac_role_permissions WHERE permission_id IN
(SELECT id FROM sys_rbac_menus WHERE name LIKE '订阅:%' AND menu_type = 0);
DELETE FROM sys_rbac_menus WHERE name LIKE '订阅:%' AND menu_type = 0;
DELETE FROM RoleMenus WHERE MenuId IN (SELECT Id FROM Menus WHERE Name LIKE '订阅:%' AND Type = 'button');
DELETE FROM Menus WHERE Name LIKE '订阅:%' AND Type = 'button';");
// 清理 RequiredPermission 为空的消息类型的订阅权限(历史遗留,所有人可订阅无需权限单元)
foreach (var t in fsql.Select<NotificationType>().Where(x => x.RequiredPermission == "").ToList())
{
var perm = fsql.Select<SysPermission>()
.Where(p => p.ResourceType == Resource_Type_Enum.消息订阅 && p.ResourceId == t.Id).First();
if (perm != null)
{
fsql.Delete<SysRolePermission>().Where(rp => rp.PermissionId == perm.Id).ExecuteAffrows();
fsql.Delete<SysPermission>().Where(p => p.Id == perm.Id).ExecuteAffrows();
}
}
// 同步订阅权限到 RBAC 权限单元(ResourceType=消息订阅,Code=RequiredPermission)
foreach (var t in fsql.Select<NotificationType>().ToList())
{
if (string.IsNullOrEmpty(t.RequiredPermission)) continue;
var existPerm = fsql.Select<SysPermission>()
.Where(p => p.ResourceType == Resource_Type_Enum.消息订阅 && p.ResourceId == t.Id).First();
if (existPerm == null)
{
var perm = new SysPermission(snowflake.CreateId(), Permission_Type_Enum.Visble, Resource_Type_Enum.消息订阅, t.Code, t.Id);
perm.SetCode(t.RequiredPermission);
fsql.Insert(perm).ExecuteAffrows();
}
else if (existPerm.Code != t.RequiredPermission)
{
// 修正 Code 为业务权限码(覆盖旧的自动生成码)
existPerm.SetCode(t.RequiredPermission);
fsql.Update<SysPermission>().SetSource(existPerm).ExecuteAffrows();
}
}
// admin 角色分配消息中心菜单权限
var adminRole = fsql.Select<Role>().Where(r => r.Code == "admin").First();
if (adminRole != null)
{
var notiMenus = fsql.Select<Menu>().Where(m => m.Path.StartsWith("/notification")).ToList();
foreach (var m in notiMenus)
{
if (!fsql.Select<RoleMenu>().Any(rm => rm.RoleId == adminRole.Id && rm.MenuId == m.Id))
fsql.Insert(new RoleMenu { RoleId = adminRole.Id, MenuId = m.Id }).ExecuteAffrows();
}
}
}
private static void AddNotificationMenu(IFreeSql fsql, long parentId, string name, string path, string component, string icon, string permission, int sort)
{
if (!fsql.Select<Menu>().Any(m => m.Path == path))
{
fsql.Insert(new Menu { ParentId = parentId, Name = name, Type = "menu", Path = path, Component = component, Icon = icon, Permission = permission, Sort = sort }).ExecuteAffrows();
}
}
private static void SeedAttendanceMenus(IFreeSql fsql)
{
var attDirId = fsql.Select<Menu>().Where(m => m.Path == "/attendance").First()?.Id ?? 0;
if (attDirId == 0)
{
attDirId = InsertAndGetId(fsql, new Menu { Name = "考勤管理", Type = "directory", Path = "/attendance", Icon = "Clock", Sort = 2 });
}
void AddMenu(string name, string path, string component, string icon, string permission, int sort)
{
if (!fsql.Select<Menu>().Any(m => m.Path == path))
{
fsql.Insert(new Menu { Name = name, Type = "menu", Path = path, Component = component, Icon = icon, Permission = permission, Sort = sort, ParentId = attDirId }).ExecuteAffrows();
}
}
AddMenu("考勤打卡", "/attendance/punch", "attendance/punch/index", "AlarmClock", "attendance:punch:list", 1);
AddMenu("我的考勤", "/attendance/my", "attendance/my/index", "Calendar", "attendance:my:list", 2);
AddMenu("请假申请", "/attendance/leave", "attendance/leave/index", "Suitcase", "attendance:leave:list", 3);
AddMenu("考勤记录", "/attendance/records", "attendance/records/index", "List", "attendance:records:list", 4);
AddMenu("请假审批", "/attendance/approve", "attendance/approve/index", "Checked", "attendance:approve:list", 5);
void AddButton(string menuPath, string name, string permission)
{
var menu = fsql.Select<Menu>().Where(m => m.Path == menuPath).First();
if (menu != null && !fsql.Select<Menu>().Any(m => m.Permission == permission))
{
fsql.Insert(new Menu { Name = name, Type = "button", Permission = permission, Sort = 1, ParentId = menu.Id }).ExecuteAffrows();
}
}
AddButton("/attendance/punch", "打卡操作", "attendance:punch:btn");
AddButton("/attendance/leave", "请假操作", "attendance:leave:btn");
AddButton("/attendance/approve", "审批操作", "attendance:approve:btn");
var adminRole = fsql.Select<Role>().Where(r => r.Code == "admin").First();
if (adminRole != null)
{
var attMenus = fsql.Select<Menu>().Where(m => m.Path.StartsWith("/attendance") || m.Permission.StartsWith("attendance:")).ToList();
foreach (var m in attMenus)
{
if (!fsql.Select<RoleMenu>().Any(rm => rm.RoleId == adminRole.Id && rm.MenuId == m.Id))
fsql.Insert(new RoleMenu { RoleId = adminRole.Id, MenuId = m.Id }).ExecuteAffrows();
}
}
}
/// <summary>数据迁移:旧 RBAC 表 → 新 DDD RBAC 表(幂等,仅迁移尚不存在的记录)</summary>
private static void MigrateRbacData(IFreeSql fsql)
{
try
{
// 1. 部门:Departments → sys_departments
fsql.Ado.ExecuteNonQuery(@"
INSERT INTO sys_departments (id, code, name, parent_id, manage_type, duty_type, description, is_active, sort_order, created_by, created_at, update_by, updated_at, is_deleted)
SELECT Id, COALESCE(Code, ''), Name, COALESCE(ParentId, 0), 10, 5, '', Status, Sort, '', COALESCE(CreatedAt, datetime('now')), '', UpdatedAt, IsDeleted
FROM Departments WHERE Id NOT IN (SELECT id FROM sys_departments);");
// 2. 角色:Roles → sys_rbac_roles
fsql.Ado.ExecuteNonQuery(@"
INSERT INTO sys_rbac_roles (id, name, code, sort_order, description, is_active, created_by, created_at, update_by, updated_at, is_deleted)
SELECT Id, Name, Code, Sort, COALESCE(Description, ''), Status, '', COALESCE(CreatedAt, datetime('now')), '', UpdatedAt, IsDeleted
FROM Roles WHERE Id NOT IN (SELECT id FROM sys_rbac_roles);");
// 3. 菜单:Menus → sys_rbac_menus
fsql.Ado.ExecuteNonQuery(@"
INSERT INTO sys_rbac_menus (id, icon_svg, code, name, menu_type, assembly_name, type_full_name, parent_id, level, is_active, sort_order, path, component, permission, created_by, created_at, update_by, updated_at, is_deleted)
SELECT Id, COALESCE(Icon, ''), '', Name, CASE WHEN Type = 'directory' THEN 1 WHEN Type = 'menu' THEN 2 ELSE 0 END, '', '', COALESCE(ParentId, 0), 1, Status, Sort, COALESCE(Path, ''), COALESCE(Component, ''), COALESCE(Permission, ''), '', COALESCE(CreatedAt, datetime('now')), '', UpdatedAt, IsDeleted
FROM Menus WHERE Id NOT IN (SELECT id FROM sys_rbac_menus);");
// 4. 用户:Employees → sys_rbac_users
fsql.Ado.ExecuteNonQuery(@"
INSERT INTO sys_rbac_users (id, department_id, user_name, phone_number, login_name, password_hash, is_active, sort_order, lastlogin_at, nickname, avatar, email, gender, created_by, created_at, update_by, updated_at, is_deleted)
SELECT Id, DepartmentId, RealName, COALESCE(Phone, ''), Username, Password, Status, 0, NULL, COALESCE(Nickname, ''), COALESCE(Avatar, ''), COALESCE(Email, ''), COALESCE(Gender, '男'), '', COALESCE(CreatedAt, datetime('now')), '', UpdatedAt, IsDeleted
FROM Employees WHERE Id NOT IN (SELECT id FROM sys_rbac_users);");
// 5. 用户角色:EmployeeRoles → sys_rbac_user_roles
fsql.Ado.ExecuteNonQuery(@"
INSERT INTO sys_rbac_user_roles (id, user_id, role_id, created_by, created_at, update_by, updated_at, is_deleted)
SELECT Id, EmployeeId, RoleId, '', COALESCE(CreatedAt, datetime('now')), '', UpdatedAt, IsDeleted
FROM EmployeeRoles WHERE Id NOT IN (SELECT id FROM sys_rbac_user_roles);");
// 6. 角色权限:RoleMenus → sys_rbac_role_permissions
fsql.Ado.ExecuteNonQuery(@"
INSERT INTO sys_rbac_role_permissions (id, role_id, permission_id, created_by, created_at, update_by, updated_at, is_deleted)
SELECT Id, RoleId, MenuId, '', COALESCE(CreatedAt, datetime('now')), '', UpdatedAt, IsDeleted
FROM RoleMenus WHERE Id NOT IN (SELECT id FROM sys_rbac_role_permissions);");
Console.WriteLine("[DbSeeder] RBAC 数据迁移完成");
}
catch (Exception ex)
{
Console.WriteLine($"[DbSeeder] RBAC 数据迁移跳过:{ex.Message}");
}
}
/// <summary>F9 风格 RBAC 前端菜单种子(写入新 sys_rbac_menus 表,驱动前端导航)</summary>
private static void SeedSysRbacMenus(IFreeSql fsql, ISnowflakeIdGenerator snowflake)
{
try
{
// 删除旧"系统管理"菜单(/system/*,对接已下线的旧接口),仅保留新的 /sys 管理菜单
fsql.Ado.ExecuteNonQuery("DELETE FROM sys_rbac_role_permissions WHERE permission_id IN (SELECT id FROM sys_rbac_menus WHERE path LIKE '/system%')");
fsql.Ado.ExecuteNonQuery("DELETE FROM sys_rbac_menus WHERE path LIKE '/system%'");
// sys 目录
long sysDirId;
var sysDir = fsql.Ado.ExecuteDataTable("SELECT id FROM sys_rbac_menus WHERE path = '/sys'");
if (sysDir.Rows.Count == 0)
{
sysDirId = snowflake.CreateId();
fsql.Ado.ExecuteNonQuery($"INSERT INTO sys_rbac_menus (id, icon_svg, name, menu_type, parent_id, level, is_active, sort_order, path, component, permission, created_at, is_deleted) VALUES ({sysDirId}, 'UserFilled', '系统管理(RBAC)', 1, 0, 1, 0, 6, '/sys', '', '', datetime('now'), 0)");
}
else
{
sysDirId = Convert.ToInt64(sysDir.Rows[0]["id"]);
}
void AddMenu(string name, string path, string component, string icon, string permission, int sort)
{
var exist = fsql.Ado.ExecuteDataTable($"SELECT id FROM sys_rbac_menus WHERE path = '{path}'");
if (exist.Rows.Count > 0) return;
var id = snowflake.CreateId();
fsql.Ado.ExecuteNonQuery($"INSERT INTO sys_rbac_menus (id, icon_svg, name, menu_type, parent_id, level, is_active, sort_order, path, component, permission, created_at, is_deleted) VALUES ({id}, '{icon}', '{name}', 2, {sysDirId}, 2, 0, {sort}, '{path}', '{component}', '{permission}', datetime('now'), 0)");
}
AddMenu("用户管理", "/sys/user", "sys/user/index", "User", "sys:user:list", 1);
AddMenu("角色管理", "/sys/role", "sys/role/index", "UserFilled", "sys:role:list", 2);
AddMenu("菜单管理", "/sys/menu", "sys/menu/index", "Menu", "sys:menu:list", 3);
AddMenu("部门管理", "/sys/department", "sys/department/index", "OfficeBuilding", "sys:department:list", 4);
// admin 角色分配 sys 菜单权限
var adminRole = fsql.Ado.ExecuteDataTable("SELECT id FROM sys_rbac_roles WHERE code = 'admin'");
if (adminRole.Rows.Count > 0)
{
var roleId = Convert.ToInt64(adminRole.Rows[0]["id"]);
var menus = fsql.Ado.ExecuteDataTable("SELECT id FROM sys_rbac_menus WHERE path LIKE '/sys%'");
foreach (DataRow row in menus.Rows)
{
var menuId = Convert.ToInt64(row["id"]);
var exist = fsql.Ado.ExecuteDataTable($"SELECT id FROM sys_rbac_role_permissions WHERE role_id = {roleId} AND permission_id = {menuId}");
if (exist.Rows.Count == 0)
{
fsql.Ado.ExecuteNonQuery($"INSERT INTO sys_rbac_role_permissions (id, role_id, permission_id, created_at, is_deleted) VALUES ({snowflake.CreateId()}, {roleId}, {menuId})");
}
}
}
}
catch (Exception ex)
{
Console.WriteLine($"[DbSeeder] RBAC 菜单种子跳过:{ex}");
}
}
/// <summary>工种管理菜单(挂在"基础数据"目录下)</summary>
private static void SeedWorkTypeMenus(IFreeSql fsql, ISnowflakeIdGenerator snowflake)
{
try
{
EnsureBaseDataMenu(fsql, snowflake, "/base/worktype", "工种管理", "base/worktype/index", "base:worktype:list", "Postcard", 5);
Console.WriteLine("[DbSeeder] 工种管理菜单已就绪");
}
catch (Exception ex)
{
Console.WriteLine($"[DbSeeder] 工种菜单种子跳过:{ex}");
}
}
/// <summary>账期管理菜单(挂在"基础数据"目录下)</summary>
private static void SeedZhangqiMenus(IFreeSql fsql, ISnowflakeIdGenerator snowflake)
{
try
{
EnsureBaseDataMenu(fsql, snowflake, "/base/zhangqi", "账期管理", "base/zhangqi/index", "base:zhangqi:list", "Tickets", 6);
Console.WriteLine("[DbSeeder] 账期管理菜单已就绪");
}
catch (Exception ex)
{
Console.WriteLine($"[DbSeeder] 账期菜单种子跳过:{ex}");
}
}
/// <summary>注册表菜单(挂在"基础数据"目录下)</summary>
private static void SeedRegistryMenus(IFreeSql fsql, ISnowflakeIdGenerator snowflake)
{
try
{
EnsureBaseDataMenu(fsql, snowflake, "/base/registry", "注册表", "base/registry/index", "base:registry:list", "Setting", 7);
Console.WriteLine("[DbSeeder] 注册表菜单已就绪");
}
catch (Exception ex)
{
Console.WriteLine($"[DbSeeder] 注册表菜单种子跳过:{ex}");
}
}
/// <summary>原料庄口菜单(挂在"基础数据"目录下)</summary>
private static void SeedYuanliaoZhuangkouMenus(IFreeSql fsql, ISnowflakeIdGenerator snowflake)
{
try
{
EnsureBaseDataMenu(fsql, snowflake, "/base/yuanliaozhuangkou", "原料庄口", "base/yuanliaozhuangkou/index", "base:yuanliaozhuangkou:list", "Box", 8);
Console.WriteLine("[DbSeeder] 原料庄口菜单已就绪");
}
catch (Exception ex)
{
Console.WriteLine($"[DbSeeder] 原料庄口菜单种子跳过:{ex}");
}
}
/// <summary>物资目录菜单(挂在"基础数据"目录下)</summary>
private static void SeedWuziMenus(IFreeSql fsql, ISnowflakeIdGenerator snowflake)
{
try
{
EnsureBaseDataMenu(fsql, snowflake, "/base/wuzimulu", "物资目录", "base/wuzimulu/index", "base:wuzimulu:list", "Goods", 9);
Console.WriteLine("[DbSeeder] 物资目录菜单已就绪");
}
catch (Exception ex)
{
Console.WriteLine($"[DbSeeder] 物资目录菜单种子跳过:{ex}");
}
}
/// <summary>往来单位菜单(挂在"基础数据"目录下)</summary>
private static void SeedGongyingshangMenus(IFreeSql fsql, ISnowflakeIdGenerator snowflake)
{
try
{
EnsureBaseDataMenu(fsql, snowflake, "/base/gongyingshang", "往来单位", "base/gongyingshang/index", "base:gongyingshang:list", "Connection", 10);
Console.WriteLine("[DbSeeder] 往来单位菜单已就绪");
}
catch (Exception ex)
{
Console.WriteLine($"[DbSeeder] 往来单位菜单种子跳过:{ex}");
}
}
/// <summary>
/// 职工管理菜单:独立的一级菜单(原作为"基础数据"的子菜单)。
/// 会清理历史遗留的 /base/employee 子菜单,并规范一级菜单排序,避免同一页面出现两个入口。
/// </summary>
private static void SeedEmployeeTopMenu(IFreeSql fsql, ISnowflakeIdGenerator snowflake)
{
try
{
const string menuPath = "/employee";
const string menuIcon = "Avatar";
// 1) 清理历史:曾是"基础数据"下的子菜单
var legacy = fsql.Select<SysMenu>().Where(m => m.Path == "/base/employee").First();
if (legacy != null)
{
fsql.Ado.ExecuteNonQuery($"DELETE FROM sys_rbac_role_permissions WHERE permission_id = {legacy.Id}");
fsql.Ado.ExecuteNonQuery($"DELETE FROM sys_rbac_menus WHERE id = {legacy.Id}");
Console.WriteLine("[DbSeeder] 已清理历史子菜单 /base/employee");
}
// 2) 创建/校正一级菜单
var menu = fsql.Select<SysMenu>().Where(m => m.Path == menuPath).First();
if (menu == null)
{
menu = new SysMenu(snowflake.CreateId(), "职工管理", menuIcon, MenuType_Enum.页面, null, null, 0, 4, 0);
menu.SetWebInfo(menuPath, "base/employee/index", "base:employee:list");
fsql.Insert(menu).ExecuteAffrows();
}
else if (!menu.IsActive || menu.IconSvg != menuIcon || menu.ParentId != 0)
{
fsql.Update<SysMenu>()
.Set(x => x.IconSvg, menuIcon)
.Set(x => x.IsActive, true)
.Set(x => x.ParentId, 0)
.Set(x => x.Level, 1)
.Where(x => x.Id == menu.Id)
.ExecuteAffrows();
}
// 3) 规范一级菜单顺序:考勤管理 / 基础数据 / 职工管理 / 生产管理 / 消息中心 / 系统管理
foreach (var (path, sort) in new (string Path, int Sort)[]
{
("/attendance", 2), ("/base", 3), ("/employee", 4),
("/prod", 5), ("/notification", 6), ("/sys", 7)
})
{
fsql.Update<SysMenu>()
.Set(x => x.SortOrder, sort)
.Where(x => x.ParentId == 0 && x.Path == path && x.SortOrder != sort)
.ExecuteAffrows();
}
// 4) admin 角色授权(后续 FixMenuPermissions 会把 permission_id 修正为权限单元 Id)
var adminRole = fsql.Select<SysRole>().Where(r => r.Code == "admin").First();
if (adminRole != null)
{
var any = fsql.Select<SysRolePermission>()
.Where(x => x.RoleId == adminRole.Id && x.PermissionId == menu.Id)
.First();
if (any == null)
{
fsql.Insert(new SysRolePermission(snowflake.CreateId(), adminRole.Id, menu.Id)).ExecuteAffrows();
}
}
Console.WriteLine("[DbSeeder] 职工管理一级菜单已就绪");
}
catch (Exception ex)
{
Console.WriteLine($"[DbSeeder] 职工管理菜单种子跳过:{ex}");
}
}
/// <summary>
/// 生产模块菜单:按缫丝工序拆分为「生产-前缫」(落丝记录)与「生产-后缫」(纤度检验、黑板检验)两个一级菜单。
/// 会清理旧的「生产管理」(/prod) 目录及其子菜单;页面组件与权限标识保持不变。
/// </summary>
private static void SeedProdMenus(IFreeSql fsql, ISnowflakeIdGenerator snowflake)
{
try
{
// 1) 清理旧「生产管理」结构(精确匹配历史路径)
foreach (var legacyPath in new[] { "/prod", "/prod/lsjl", "/prod/xdjy", "/prod/hbjy" })
{
var legacy = fsql.Select<SysMenu>().Where(m => m.Path == legacyPath).First();
if (legacy == null) continue;
fsql.Ado.ExecuteNonQuery($"DELETE FROM sys_rbac_role_permissions WHERE permission_id = {legacy.Id}");
fsql.Ado.ExecuteNonQuery($"DELETE FROM sys_rbac_menus WHERE id = {legacy.Id}");
Console.WriteLine($"[DbSeeder] 已清理旧生产菜单 {legacyPath}");
}
// 2) 两个一级目录 + 各自页面菜单
// 车间班次原先挂在"基础数据"目录下,现归入"生产-前缫"
var legacyChejianbanci = fsql.Select<SysMenu>().Where(m => m.Path == "/base/chejianbanci").First();
if (legacyChejianbanci != null)
{
fsql.Ado.ExecuteNonQuery($"DELETE FROM sys_rbac_role_permissions WHERE permission_id = {legacyChejianbanci.Id}");
fsql.Ado.ExecuteNonQuery($"DELETE FROM sys_rbac_menus WHERE id = {legacyChejianbanci.Id}");
Console.WriteLine("[DbSeeder] 已清理旧菜单 /base/chejianbanci(车间班次移入生产-前缫)");
}
// 一并清理旧菜单遗留的权限单元(权限标识已从 base:chejianbanci:list 改为 prod:chejianbanci:list)
var legacyPerms = fsql.Ado.ExecuteDataTable("SELECT id FROM sys_rbac_permissions WHERE code = 'base:chejianbanci:list'");
foreach (DataRow permRow in legacyPerms.Rows)
{
var permId = Convert.ToInt64(permRow["id"]);
fsql.Ado.ExecuteNonQuery($"DELETE FROM sys_rbac_role_permissions WHERE permission_id = {permId}");
fsql.Ado.ExecuteNonQuery($"DELETE FROM sys_rbac_permissions WHERE id = {permId}");
Console.WriteLine("[DbSeeder] 已清理旧权限单元 base:chejianbanci:list");
}
EnsureTopMenuWithChildren(fsql, snowflake, "/prod/qianxian", "生产-前缫", "Cpu", 5,
new (string Name, string Path, string Component, string Permission, string Icon, int Sort)[]
{
("落丝记录", "/prod/qianxian/lsjl", "prod/lsjl/index", "prod:lsjl:list", "Document", 1),
("车间班次", "/prod/qianxian/chejianbanci", "base/chejianbanci/index", "prod:chejianbanci:list", "Timer", 2)
});
EnsureTopMenuWithChildren(fsql, snowflake, "/prod/houxian", "生产-后缫", "DataLine", 6,
new (string Name, string Path, string Component, string Permission, string Icon, int Sort)[]
{
("纤度检验", "/prod/houxian/xdjy", "prod/xdjy/index", "prod:xdjy:list", "EditPen", 1),
("黑板检验", "/prod/houxian/hbjy", "prod/hbjy/index", "prod:hbjy:list", "Notebook", 2)
});
// 3) 规范一级菜单顺序
foreach (var (path, sort) in new (string Path, int Sort)[]
{
("/attendance", 2), ("/base", 3), ("/employee", 4),
("/prod/qianxian", 5), ("/prod/houxian", 6), ("/notification", 7), ("/sys", 8)
})
{
fsql.Update<SysMenu>()
.Set(x => x.SortOrder, sort)
.Where(x => x.ParentId == 0 && x.Path == path && x.SortOrder != sort)
.ExecuteAffrows();
}
// 4) 消息跳转路径同步到新路由
fsql.Ado.ExecuteNonQuery("UPDATE NotificationTypes SET RouteTemplate = '/prod/houxian/xdjy' WHERE Code = 'TeyeFound'");
fsql.Ado.ExecuteNonQuery("UPDATE NotificationTypes SET RouteTemplate = '/prod/houxian/hbjy' WHERE Code = 'TdcFound'");
Console.WriteLine("[DbSeeder] 生产模块菜单已就绪(生产-前缫 / 生产-后缫)");
}
catch (Exception ex)
{
Console.WriteLine($"[DbSeeder] 生产菜单种子跳过:{ex}");
}
}
/// <summary>
/// 确保一个一级目录菜单及其下属页面菜单存在(含父级、图标、启用状态校正),并给 admin 角色授权。
/// 注意:图标名必须是 Element Plus 图标组件名。
/// </summary>
private static void EnsureTopMenuWithChildren(IFreeSql fsql, ISnowflakeIdGenerator snowflake,
string dirPath, string dirName, string dirIcon, int dirSort,
(string Name, string Path, string Component, string Permission, string Icon, int Sort)[] children)
{
var dir = fsql.Select<SysMenu>().Where(m => m.Path == dirPath).First();
if (dir == null)
{
dir = new SysMenu(snowflake.CreateId(), dirName, dirIcon, MenuType_Enum.分组, null, null, 0, dirSort, 0);
dir.SetWebInfo(dirPath, "", "");
fsql.Insert(dir).ExecuteAffrows();
}
else if (!dir.IsActive || dir.IconSvg != dirIcon || dir.Name != dirName || dir.ParentId != 0)
{
fsql.Update<SysMenu>()
.Set(x => x.Name, dirName)
.Set(x => x.IconSvg, dirIcon)
.Set(x => x.IsActive, true)
.Set(x => x.ParentId, 0)
.Set(x => x.Level, 1)
.Where(x => x.Id == dir.Id)
.ExecuteAffrows();
}
var adminRoleId = fsql.Select<SysRole>().Where(r => r.Code == "admin").First()?.Id ?? 0;
EnsureMenuPermission(fsql, snowflake, adminRoleId, dir.Id);
foreach (var (name, path, component, permission, icon, sort) in children)
{
var child = fsql.Select<SysMenu>().Where(m => m.Path == path).First();
if (child == null)
{
child = new SysMenu(snowflake.CreateId(), name, icon, MenuType_Enum.页面, null, null, dir.Id, sort, 1);
child.SetWebInfo(path, component, permission);
fsql.Insert(child).ExecuteAffrows();
}
else if (!child.IsActive || child.IconSvg != icon || child.ParentId != dir.Id)
{
fsql.Update<SysMenu>()
.Set(x => x.IconSvg, icon)
.Set(x => x.IsActive, true)
.Set(x => x.ParentId, dir.Id)
.Set(x => x.Level, 2)
.Where(x => x.Id == child.Id)
.ExecuteAffrows();
}
EnsureMenuPermission(fsql, snowflake, adminRoleId, child.Id);
}
}
/// <summary>
/// 修正前缫部门的职责类型:历史数据迁移时 duty_type 统一写成了"其他"(5),
/// 而车间班次等模块需要按职责类型(前缫=25)筛选部门。
/// </summary>
private static void FixQianxianDutyType(IFreeSql fsql)
{
try
{
var rows = fsql.Ado.ExecuteNonQuery(
"UPDATE sys_departments SET duty_type = 25 WHERE is_deleted = 0 AND name LIKE '前缫%' AND duty_type <> 25");
if (rows > 0) Console.WriteLine($"[DbSeeder] 已修正 {rows} 个前缫部门的职责类型");
}
catch (Exception ex)
{
Console.WriteLine($"[DbSeeder] 部门职责类型修正跳过:{ex.Message}");
}
}
/// <summary>确保指定菜单已授权给角色(后续 FixMenuPermissions 会把 permission_id 修正为权限单元 Id)</summary>
private static void EnsureMenuPermission(IFreeSql fsql, ISnowflakeIdGenerator snowflake, long roleId, long menuId)
{
if (roleId <= 0 || menuId <= 0) return;
var any = fsql.Select<SysRolePermission>()
.Where(x => x.RoleId == roleId && x.PermissionId == menuId)
.First();
if (any == null)
{
fsql.Insert(new SysRolePermission(snowflake.CreateId(), roleId, menuId)).ExecuteAffrows();
}
}
/// <summary>
/// 确保"基础数据"目录下的菜单存在(含启用状态与图标校正),并给 admin 角色授权。
/// 注意:图标名必须是 Element Plus 图标组件名,前端按图标名渲染菜单图标。
/// </summary>
private static void EnsureBaseDataMenu(IFreeSql fsql, ISnowflakeIdGenerator snowflake,
string path, string name, string component, string permission, string icon, int sort)
{
// 基础数据目录:不存在则创建,已存在则复用
var baseDir = fsql.Select<SysMenu>().Where(m => m.Path == "/base").First();
if (baseDir == null)
{
baseDir = new SysMenu(snowflake.CreateId(), "基础数据", "Files", MenuType_Enum.分组, null, null, 0, 3, 0);
baseDir.SetWebInfo("/base", "", "");
fsql.Insert(baseDir).ExecuteAffrows();
}
var menu = fsql.Select<SysMenu>().Where(m => m.Path == path).First();
if (menu == null)
{
menu = new SysMenu(snowflake.CreateId(), name, icon, MenuType_Enum.页面, null, null, baseDir.Id, sort, 1);
menu.SetWebInfo(path, component, permission);
fsql.Insert(menu).ExecuteAffrows();
}
else if (!menu.IsActive || menu.IconSvg != icon)
{
// 兼容历史数据:补齐启用状态与图标
fsql.Update<SysMenu>()
.Set(x => x.IconSvg, icon)
.Set(x => x.IsActive, true)
.Where(x => x.Id == menu.Id)
.ExecuteAffrows();
}
// admin 角色授权(后续 FixMenuPermissions 会把 permission_id 修正为权限单元 Id)
var adminRole = fsql.Select<SysRole>().Where(r => r.Code == "admin").First();
if (adminRole != null)
{
var any = fsql.Select<SysRolePermission>()
.Where(x => x.RoleId == adminRole.Id && x.PermissionId == menu.Id)
.First();
if (any == null)
{
fsql.Insert(new SysRolePermission(snowflake.CreateId(), adminRole.Id, menu.Id)).ExecuteAffrows();
}
}
}
/// <summary>为菜单创建权限单元(Permission),并修正旧菜单权限关联(RolePermission.PermissionId 从 Menu.Id 改为 Permission.Id)</summary>
private static void FixMenuPermissions(IFreeSql fsql, ISnowflakeIdGenerator snowflake)
{
try
{
foreach (var menu in fsql.Select<SysMenu>().ToList())
{
// 1. 查或创建菜单权限单元(ResourceType=菜单,ResourceId=Menu.Id)
var perm = fsql.Select<SysPermission>()
.Where(p => p.ResourceType == Resource_Type_Enum.菜单 && p.ResourceId == menu.Id).First();
if (perm == null)
{
var resourceIdentity = string.IsNullOrEmpty(menu.Code) ? menu.Id.ToString() : menu.Code;
perm = new SysPermission(snowflake.CreateId(), Permission_Type_Enum.Visble, Resource_Type_Enum.菜单, resourceIdentity, menu.Id);
if (!string.IsNullOrEmpty(menu.Permission))
perm.SetCode(menu.Permission);
fsql.Insert(perm).ExecuteAffrows();
}
else if (!string.IsNullOrEmpty(menu.Permission) && perm.Code != menu.Permission)
{
perm.SetCode(menu.Permission);
fsql.Update<SysPermission>().SetSource(perm).ExecuteAffrows();
}
// 2. 修正旧菜单权限关联(permission_id 旧值 = Menu.Id,改为 Permission.Id)
fsql.Ado.ExecuteNonQuery($"UPDATE sys_rbac_role_permissions SET permission_id = {perm.Id} WHERE permission_id = {menu.Id};");
}
Console.WriteLine("[DbSeeder] 菜单权限单元同步完成");
}
catch (Exception ex)
{
Console.WriteLine($"[DbSeeder] 菜单权限单元同步跳过:{ex.Message}");
}
}
}