import bcrypt from 'bcryptjs'; import type { PrismaClient } from '@prisma/client'; import { Permissions } from './permissions.js'; /** 菜单资源(导航)。 */ export const MENUS = [ { code: 'dashboard', name: '工作台', parent: null, path: '/', component: 'dashboard/index', icon: 'mdi:view-dashboard', sort: 1 }, { code: 'permission', name: '权限管理', parent: null, path: '/permission', component: 'Layout', icon: 'mdi:shield-account', sort: 2 }, { code: 'user', name: '用户管理', parent: 'permission', path: '/system/user', component: 'system/user/index', icon: 'mdi:account-group', sort: 1 }, { code: 'role', name: '角色管理', parent: 'permission', path: '/system/role', component: 'system/role/index', icon: 'mdi:account-key', sort: 2 }, { code: 'resource', name: '资源管理', parent: 'permission', path: '/system/resource', component: 'system/resource/index', icon: 'mdi:view-list', sort: 3 }, { code: 'org', name: '组织管理', parent: 'permission', path: '/system/org', component: 'system/org/index', icon: 'mdi:sitemap', sort: 4 }, { code: 'system', name: '系统管理', parent: null, path: '/system', component: 'Layout', icon: 'mdi:cog', sort: 3 }, { code: 'tenant', name: '租户管理', parent: 'system', path: '/system/tenant', component: 'system/tenant/index', icon: 'mdi:domain', sort: 1 }, { code: 'dict', name: '字典管理', parent: 'system', path: '/system/dict', component: 'system/dict/index', icon: 'mdi:book-open-variant', sort: 2 }, { code: 'config', name: '参数配置', parent: 'system', path: '/system/config', component: 'system/config/index', icon: 'mdi:tune', sort: 3 }, { code: 'notice', name: '通知管理', parent: 'system', path: '/system/notice', component: 'system/notice/index', icon: 'mdi:bell-ring', sort: 4 }, { code: 'monitor', name: '系统监控', parent: null, path: '/monitor', component: 'Layout', icon: 'mdi:monitor-dashboard', sort: 4 }, { code: 'audit', name: '操作日志', parent: 'monitor', path: '/monitor/audit', component: 'monitor/audit/index', icon: 'mdi:clipboard-text', sort: 1 }, { code: 'login-log', name: '登录日志', parent: 'monitor', path: '/monitor/login-log', component: 'monitor/login-log/index', icon: 'mdi:login', sort: 2 }, { code: 'session', name: '在线会话', parent: 'monitor', path: '/monitor/session', component: 'monitor/session/index', icon: 'mdi:account-multiple-check', sort: 3 }, ]; /** 接口资源(每个受控接口一个,code = 权限码)。 */ export const APIS: { code: string; name: string; method: string; path: string; parent: string }[] = [ { code: Permissions.tenant.list, name: '租户列表', method: 'GET', path: '/api/v1/tenants', parent: 'tenant' }, { code: Permissions.tenant.create, name: '创建租户', method: 'POST', path: '/api/v1/tenants', parent: 'tenant' }, { code: Permissions.tenant.update, name: '更新租户', method: 'PATCH', path: '/api/v1/tenants/:id', parent: 'tenant' }, { code: Permissions.tenant.delete, name: '删除租户', method: 'DELETE', path: '/api/v1/tenants/:id', parent: 'tenant' }, { code: Permissions.user.list, name: '用户列表', method: 'GET', path: '/api/v1/users', parent: 'user' }, { code: Permissions.user.create, name: '创建用户', method: 'POST', path: '/api/v1/users', parent: 'user' }, { code: Permissions.user.update, name: '更新用户', method: 'PATCH', path: '/api/v1/users/:id', parent: 'user' }, { code: Permissions.user.delete, name: '删除用户', method: 'DELETE', path: '/api/v1/users/:id', parent: 'user' }, { code: Permissions.user.resetPwd, name: '重置密码', method: 'PATCH', path: '/api/v1/users/:id/password', parent: 'user' }, { code: Permissions.user.assignRole, name: '分配角色', method: 'PUT', path: '/api/v1/users/:id/roles', parent: 'user' }, { code: Permissions.role.list, name: '角色列表', method: 'GET', path: '/api/v1/roles', parent: 'role' }, { code: Permissions.role.create, name: '创建角色', method: 'POST', path: '/api/v1/roles', parent: 'role' }, { code: Permissions.role.update, name: '更新角色', method: 'PATCH', path: '/api/v1/roles/:id', parent: 'role' }, { code: Permissions.role.delete, name: '删除角色', method: 'DELETE', path: '/api/v1/roles/:id', parent: 'role' }, { code: Permissions.role.assignResource, name: '分配资源', method: 'PUT', path: '/api/v1/roles/:id/resources', parent: 'role' }, { code: Permissions.role.assignOrg, name: '分配数据权限部门', method: 'PUT', path: '/api/v1/roles/:id/orgs', parent: 'role' }, { code: Permissions.resource.list, name: '资源列表', method: 'GET', path: '/api/v1/resources', parent: 'resource' }, { code: Permissions.resource.create, name: '创建资源', method: 'POST', path: '/api/v1/resources', parent: 'resource' }, { code: Permissions.resource.update, name: '更新资源', method: 'PATCH', path: '/api/v1/resources/:id', parent: 'resource' }, { code: Permissions.resource.delete, name: '删除资源', method: 'DELETE', path: '/api/v1/resources/:id', parent: 'resource' }, { code: Permissions.org.list, name: '组织列表', method: 'GET', path: '/api/v1/orgs', parent: 'org' }, { code: Permissions.org.create, name: '创建组织', method: 'POST', path: '/api/v1/orgs', parent: 'org' }, { code: Permissions.org.update, name: '更新组织', method: 'PATCH', path: '/api/v1/orgs/:id', parent: 'org' }, { code: Permissions.org.delete, name: '删除组织', method: 'DELETE', path: '/api/v1/orgs/:id', parent: 'org' }, { code: Permissions.dict.list, name: '字典列表', method: 'GET', path: '/api/v1/dict-types', parent: 'dict' }, { code: Permissions.dict.create, name: '创建字典', method: 'POST', path: '/api/v1/dict-types', parent: 'dict' }, { code: Permissions.dict.update, name: '更新字典', method: 'PATCH', path: '/api/v1/dict-types/:id', parent: 'dict' }, { code: Permissions.dict.delete, name: '删除字典', method: 'DELETE', path: '/api/v1/dict-types/:id', parent: 'dict' }, { code: Permissions.config.list, name: '配置列表', method: 'GET', path: '/api/v1/configs', parent: 'config' }, { code: Permissions.config.create, name: '创建配置', method: 'POST', path: '/api/v1/configs', parent: 'config' }, { code: Permissions.config.update, name: '更新配置', method: 'PATCH', path: '/api/v1/configs/:id', parent: 'config' }, { code: Permissions.config.delete, name: '删除配置', method: 'DELETE', path: '/api/v1/configs/:id', parent: 'config' }, { code: Permissions.audit.list, name: '操作日志列表', method: 'GET', path: '/api/v1/audit-logs', parent: 'audit' }, { code: Permissions.loginLog.list, name: '登录日志列表', method: 'GET', path: '/api/v1/login-logs', parent: 'login-log' }, { code: Permissions.session.list, name: '会话列表', method: 'GET', path: '/api/v1/sessions', parent: 'session' }, { code: Permissions.session.kick, name: '强退会话', method: 'POST', path: '/api/v1/sessions/:sid/kick', parent: 'session' }, { code: Permissions.user.kickSessions, name: '踢全部会话', method: 'POST', path: '/api/v1/users/:id/kick-sessions', parent: 'user' }, { code: Permissions.user.lock, name: '锁定用户', method: 'POST', path: '/api/v1/users/:id/lock', parent: 'user' }, { code: Permissions.user.unlock, name: '解锁用户', method: 'POST', path: '/api/v1/users/:id/unlock', parent: 'user' }, { code: Permissions.notice.list, name: '通知列表', method: 'GET', path: '/api/v1/notices', parent: 'notice' }, { code: Permissions.notice.create, name: '创建通知', method: 'POST', path: '/api/v1/notices', parent: 'notice' }, { code: Permissions.notice.update, name: '更新通知', method: 'PATCH', path: '/api/v1/notices/:id', parent: 'notice' }, { code: Permissions.notice.delete, name: '删除通知', method: 'DELETE', path: '/api/v1/notices/:id', parent: 'notice' }, ]; /** 按钮资源(纯前端按钮,无独立受控接口)。 */ export const BUTTONS = [ { code: 'user:export', name: '用户导出', parent: 'user' }, { code: 'role:export', name: '角色导出', parent: 'role' }, { code: 'audit:export', name: '操作日志导出', parent: 'audit' }, { code: 'login-log:export', name: '登录日志导出', parent: 'login-log' }, ]; /** 内置枚举字典(值对齐 enums.ts,仅展示层 label/颜色/排序可改)。 */ export const DICTS: { code: string; name: string; items: { label: string; value: string; colorType: string; sort: number }[] }[] = [ { code: 'common_status', name: '通用状态', items: [ { label: '启用', value: 'enabled', colorType: 'success', sort: 1 }, { label: '停用', value: 'disabled', colorType: 'info', sort: 2 }, ] }, { code: 'user_status', name: '用户状态', items: [ { label: '启用', value: 'enabled', colorType: 'success', sort: 1 }, { label: '停用', value: 'disabled', colorType: 'info', sort: 2 }, { label: '锁定', value: 'locked', colorType: 'danger', sort: 3 }, ] }, { code: 'resource_type', name: '资源类型', items: [ { label: '菜单', value: 'menu', colorType: 'primary', sort: 1 }, { label: '按钮', value: 'button', colorType: 'warning', sort: 2 }, { label: '接口', value: 'api', colorType: 'info', sort: 3 }, ] }, { code: 'data_scope', name: '数据权限', items: [ { label: '全部', value: 'all', colorType: 'success', sort: 1 }, { label: '本部门', value: 'dept', colorType: 'info', sort: 2 }, { label: '本部门及以下', value: 'dept_and_child', colorType: 'warning', sort: 3 }, { label: '本人', value: 'self', colorType: 'danger', sort: 4 }, { label: '自定义', value: 'custom', colorType: 'primary', sort: 5 }, ] }, { code: 'login_status', name: '登录状态', items: [ { label: '成功', value: 'success', colorType: 'success', sort: 1 }, { label: '失败', value: 'fail', colorType: 'danger', sort: 2 }, ] }, ]; export interface InitTenantOptions { prisma: PrismaClient; tenantCode: string; tenantName: string; /** 顶层管理员角色码:平台=super_admin,业务租户=tenant_super_admin。 */ adminRoleCode: string; adminRoleName: string; adminUsername: string; adminPassword: string; /** 是否包含平台级权限(tenant:*)——仅平台租户为 true。 */ includePlatformPerms: boolean; /** 管理员是否需首登强制改密(随机密码场景为 true)。 */ forcePasswordChange?: boolean; } export interface SyncTenantResourcesOptions { prisma: PrismaClient; tenantCode: string; adminRoleCode: string; includePlatformPerms: boolean; } /** 同步租户资源(菜单/接口/按钮)+ 内置字典 + 顶层角色全量授权(幂等;不触碰用户/密码)。 */ export async function syncTenantResources(opts: SyncTenantResourcesOptions): Promise { const { prisma, tenantCode, adminRoleCode, includePlatformPerms } = opts; // 资源(菜单 + 接口 + 按钮;业务租户剥离 tenant:* 平台级接口) const apis = includePlatformPerms ? APIS : APIS.filter((a) => !a.code.startsWith('tenant:')); const allResourceCodes: string[] = []; for (const m of MENUS) { allResourceCodes.push(m.code); await prisma.resource.upsert({ where: { tenantCode_code: { tenantCode, code: m.code } }, update: { name: m.name, parentCode: m.parent, path: m.path, component: m.component, icon: m.icon, sort: m.sort, type: 'menu' }, create: { tenantCode, code: m.code, name: m.name, parentCode: m.parent, type: 'menu', path: m.path, component: m.component, icon: m.icon, sort: m.sort, status: 'enabled' }, }); } for (const a of apis) { allResourceCodes.push(a.code); await prisma.resource.upsert({ where: { tenantCode_code: { tenantCode, code: a.code } }, update: { name: a.name, parentCode: a.parent, method: a.method, apiPath: a.path, type: 'api' }, create: { tenantCode, code: a.code, name: a.name, parentCode: a.parent, type: 'api', method: a.method, apiPath: a.path, status: 'enabled' }, }); } for (const b of BUTTONS) { allResourceCodes.push(b.code); await prisma.resource.upsert({ where: { tenantCode_code: { tenantCode, code: b.code } }, update: { name: b.name, parentCode: b.parent, type: 'button' }, create: { tenantCode, code: b.code, name: b.name, parentCode: b.parent, type: 'button', status: 'enabled' }, }); } // 内置字典 for (const d of DICTS) { await prisma.dictType.upsert({ where: { tenantCode_code: { tenantCode, code: d.code } }, update: { name: d.name, status: 'enabled', builtIn: true }, create: { tenantCode, code: d.code, name: d.name, status: 'enabled', builtIn: true }, }); for (const it of d.items) { await prisma.dictItem.upsert({ where: { tenantCode_dictTypeCode_value: { tenantCode, dictTypeCode: d.code, value: it.value } }, update: { label: it.label, colorType: it.colorType, sort: it.sort, status: 'enabled', builtIn: true }, create: { tenantCode, dictTypeCode: d.code, label: it.label, value: it.value, colorType: it.colorType, sort: it.sort, status: 'enabled', builtIn: true }, }); } } // 顶层角色全量授权 for (const code of allResourceCodes) { await prisma.roleResource.upsert({ where: { tenantCode_roleCode_resourceCode: { tenantCode, roleCode: adminRoleCode, resourceCode: code } }, update: {}, create: { tenantCode, roleCode: adminRoleCode, resourceCode: code }, }); } } /** 初始化一个租户:根组织、顶层角色、管理员、全量资源、内置字典、授权(幂等)。 */ export async function initTenant(opts: InitTenantOptions): Promise { const { prisma, tenantCode, tenantName, adminRoleCode, adminRoleName, adminUsername, adminPassword, includePlatformPerms, forcePasswordChange = false } = opts; // 1. 根组织 await prisma.org.upsert({ where: { tenantCode_code: { tenantCode, code: tenantCode } }, update: {}, create: { tenantCode, code: tenantCode, name: `${tenantName}总部`, parentCode: null, sort: 0, status: 'enabled' }, }); // 2. 顶层角色 await prisma.role.upsert({ where: { tenantCode_code: { tenantCode, code: adminRoleCode } }, update: {}, create: { tenantCode, code: adminRoleCode, name: adminRoleName, dataScope: 'all', status: 'enabled' }, }); // 3. 管理员账号(update 不重置密码/状态) const passwordHash = await bcrypt.hash(adminPassword, 10); const admin = await prisma.user.upsert({ where: { tenantCode_username: { tenantCode, username: adminUsername } }, update: {}, create: { tenantCode, username: adminUsername, passwordHash, nickname: '管理员', status: 'enabled', orgCode: tenantCode, mustChangePassword: forcePasswordChange }, }); // 4. 绑定管理员到顶层角色 await prisma.userRole.upsert({ where: { tenantCode_userId_roleCode: { tenantCode, userId: admin.id, roleCode: adminRoleCode } }, update: {}, create: { tenantCode, userId: admin.id, roleCode: adminRoleCode }, }); // 5. 资源 + 内置字典 + 全量授权 await syncTenantResources({ prisma, tenantCode, adminRoleCode, includePlatformPerms }); }