library-picturebook-activity/backend/scripts/init-tenant-menu-permissions.ts
2025-12-09 11:10:36 +08:00

430 lines
13 KiB
TypeScript
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.

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck
// 加载环境变量(必须在其他导入之前)
import * as dotenv from 'dotenv';
import * as path from 'path';
import * as fs from 'fs';
// 根据 NODE_ENV 加载对应的环境配置文件
const nodeEnv = process.env.NODE_ENV || 'development';
const envFile = `.env.${nodeEnv}`;
// scripts 目录的父目录就是 backend 目录
const backendDir = path.resolve(__dirname, '..');
const envPath = path.resolve(backendDir, envFile);
// 尝试加载环境特定的配置文件
dotenv.config({ path: envPath });
// 如果环境特定文件不存在,尝试加载默认的 .env 文件
if (!process.env.DATABASE_URL) {
dotenv.config({ path: path.resolve(backendDir, '.env') });
}
// 验证必要的环境变量
if (!process.env.DATABASE_URL) {
console.error('❌ 错误: 未找到 DATABASE_URL 环境变量');
console.error(` 请确保存在以下文件之一:`);
console.error(` - ${envPath}`);
console.error(` - ${path.resolve(backendDir, '.env')}`);
console.error(` 或者设置 NODE_ENV 环境变量(当前: ${nodeEnv})`);
process.exit(1);
}
import { PrismaClient } from '@prisma/client';
import * as bcrypt from 'bcrypt';
const prisma = new PrismaClient();
// 从 JSON 文件加载权限数据
const permissionsFilePath = path.resolve(backendDir, 'data', 'permissions.json');
if (!fs.existsSync(permissionsFilePath)) {
console.error(`❌ 错误: 权限数据文件不存在: ${permissionsFilePath}`);
process.exit(1);
}
const permissions = JSON.parse(fs.readFileSync(permissionsFilePath, 'utf-8'));
async function initTenantMenuAndPermissions(tenantCode: string) {
try {
console.log(`🚀 开始为租户 "${tenantCode}" 初始化菜单和权限...\n`);
// 1. 查找或创建租户
console.log(`📋 步骤 1: 查找或创建租户 "${tenantCode}"...`);
let tenant = await prisma.tenant.findUnique({
where: { code: tenantCode },
});
if (!tenant) {
// 创建租户
tenant = await prisma.tenant.create({
data: {
name: `${tenantCode} 租户`,
code: tenantCode,
domain: tenantCode,
description: `租户 ${tenantCode}`,
isSuper: 0,
validState: 1,
},
});
console.log(`✅ 租户创建成功: ${tenant.name} (${tenant.code})\n`);
} else {
if (tenant.validState !== 1) {
console.error(`❌ 错误: 租户 "${tenantCode}" 状态无效!`);
process.exit(1);
}
console.log(`✅ 找到租户: ${tenant.name} (${tenant.code})\n`);
}
// 2. 查找或创建 admin 用户
console.log(`👤 步骤 2: 查找或创建 admin 用户...`);
let adminUser = await prisma.user.findFirst({
where: {
tenantId: tenant.id,
username: 'admin',
},
});
const password = `admin@${tenantCode}`;
const hashedPassword = await bcrypt.hash(password, 10);
if (!adminUser) {
adminUser = await prisma.user.create({
data: {
tenantId: tenant.id,
username: 'admin',
password: hashedPassword,
nickname: '管理员',
email: `admin@${tenantCode}.com`,
validState: 1,
},
});
console.log(`✅ admin 用户创建成功: ${adminUser.username}\n`);
} else {
// 更新密码(确保密码是最新的)
adminUser = await prisma.user.update({
where: { id: adminUser.id },
data: {
password: hashedPassword,
nickname: '管理员',
email: `admin@${tenantCode}.com`,
validState: 1,
},
});
console.log(`✅ admin 用户已存在: ${adminUser.username}\n`);
}
// 3. 查找或创建 admin 角色
console.log(`👤 步骤 3: 查找或创建 admin 角色...`);
let adminRole = await prisma.role.findFirst({
where: {
tenantId: tenant.id,
code: 'admin',
},
});
if (!adminRole) {
adminRole = await prisma.role.create({
data: {
tenantId: tenant.id,
name: '管理员',
code: 'admin',
description: '租户管理员角色,拥有租户的所有权限',
validState: 1,
},
});
console.log(`✅ admin 角色创建成功: ${adminRole.name} (${adminRole.code})\n`);
} else {
adminRole = await prisma.role.update({
where: { id: adminRole.id },
data: {
name: '管理员',
description: '租户管理员角色,拥有租户的所有权限',
validState: 1,
},
});
console.log(`✅ admin 角色已存在: ${adminRole.name} (${adminRole.code})\n`);
}
// 4. 为 admin 用户分配 admin 角色
console.log(`🔗 步骤 4: 为 admin 用户分配 admin 角色...`);
const existingUserRole = await prisma.userRole.findUnique({
where: {
userId_roleId: {
userId: adminUser.id,
roleId: adminRole.id,
},
},
});
if (!existingUserRole) {
await prisma.userRole.create({
data: {
userId: adminUser.id,
roleId: adminRole.id,
},
});
console.log(`✅ 角色分配成功\n`);
} else {
console.log(`✅ 用户已拥有 admin 角色\n`);
}
// 5. 初始化租户权限(如果不存在则创建)
console.log(`📝 步骤 5: 初始化租户权限...`);
const createdPermissions = [];
for (const perm of permissions) {
// 检查权限是否已存在
const existingPermission = await prisma.permission.findFirst({
where: {
tenantId: tenant.id,
code: perm.code,
},
});
if (!existingPermission) {
// 创建权限
const permission = await prisma.permission.create({
data: {
tenantId: tenant.id,
code: perm.code,
resource: perm.resource,
action: perm.action,
name: perm.name,
description: perm.description,
validState: 1,
},
});
createdPermissions.push(permission);
console.log(` ✓ 创建权限: ${perm.code} - ${perm.name}`);
} else {
// 更新现有权限(确保信息是最新的)
const permission = await prisma.permission.update({
where: { id: existingPermission.id },
data: {
name: perm.name,
resource: perm.resource,
action: perm.action,
description: perm.description,
validState: 1,
},
});
createdPermissions.push(permission);
}
}
console.log(`✅ 共确保 ${createdPermissions.length} 个权限存在\n`);
// 获取租户的所有有效权限
const tenantPermissions = await prisma.permission.findMany({
where: {
tenantId: tenant.id,
validState: 1,
},
});
// 6. 为 admin 角色分配所有权限
console.log(`🔗 步骤 6: 为 admin 角色分配所有权限...`);
const existingRolePermissions = await prisma.rolePermission.findMany({
where: { roleId: adminRole.id },
select: { permissionId: true },
});
const existingPermissionIds = new Set(
existingRolePermissions.map((rp) => rp.permissionId),
);
let addedPermissionCount = 0;
for (const permission of tenantPermissions) {
if (!existingPermissionIds.has(permission.id)) {
await prisma.rolePermission.create({
data: {
roleId: adminRole.id,
permissionId: permission.id,
},
});
addedPermissionCount++;
}
}
if (addedPermissionCount > 0) {
console.log(`✅ 为 admin 角色添加了 ${addedPermissionCount} 个权限`);
console.log(`✅ admin 角色现在拥有 ${tenantPermissions.length} 个权限\n`);
} else {
console.log(
`✅ admin 角色已拥有所有权限(${tenantPermissions.length} 个)\n`,
);
}
// 7. 为租户分配所有菜单
console.log(`📋 步骤 7: 为租户分配所有菜单...`);
// 获取所有有效菜单
const allMenus = await prisma.menu.findMany({
where: {
validState: 1,
},
orderBy: [{ sort: 'asc' }, { id: 'asc' }],
});
if (allMenus.length === 0) {
console.log('⚠️ 警告: 数据库中没有任何菜单');
console.log(' 请先运行 pnpm init:menus 初始化菜单\n');
} else {
console.log(` 找到 ${allMenus.length} 个菜单\n`);
// 获取租户已分配的菜单
const existingTenantMenus = await prisma.tenantMenu.findMany({
where: {
tenantId: tenant.id,
},
select: {
menuId: true,
},
});
const existingMenuIds = new Set(existingTenantMenus.map((tm) => tm.menuId));
// 为租户分配所有菜单
let addedMenuCount = 0;
const menuNames: string[] = [];
for (const menu of allMenus) {
if (!existingMenuIds.has(menu.id)) {
await prisma.tenantMenu.create({
data: {
tenantId: tenant.id,
menuId: menu.id,
},
});
addedMenuCount++;
menuNames.push(menu.name);
}
}
if (addedMenuCount > 0) {
console.log(`✅ 为租户添加了 ${addedMenuCount} 个菜单:`);
menuNames.forEach((name) => {
console.log(`${name}`);
});
console.log(`\n✅ 租户现在拥有 ${allMenus.length} 个菜单\n`);
} else {
console.log(`✅ 租户已拥有所有菜单(${allMenus.length} 个)\n`);
}
}
// 8. 验证结果
console.log('🔍 步骤 8: 验证结果...');
const userWithRoles = await prisma.user.findUnique({
where: { id: adminUser.id },
include: {
roles: {
include: {
role: {
include: {
permissions: {
include: {
permission: true,
},
},
},
},
},
},
},
});
const roleCodes = userWithRoles?.roles.map((ur) => ur.role.code) || [];
const permissionCodes = new Set<string>();
userWithRoles?.roles.forEach((ur) => {
ur.role.permissions.forEach((rp) => {
permissionCodes.add(rp.permission.code);
});
});
const finalMenus = await prisma.tenantMenu.findMany({
where: {
tenantId: tenant.id,
},
include: {
menu: true,
},
});
console.log(`\n📊 初始化结果:`);
console.log('========================================');
console.log('租户信息:');
console.log(` 租户名称: ${tenant.name}`);
console.log(` 租户编码: ${tenant.code}`);
console.log(` 访问链接: http://your-domain.com/?tenant=${tenant.code}`);
console.log('========================================');
console.log('管理员登录信息:');
console.log(` 用户名: ${adminUser.username}`);
console.log(` 密码: ${password}`);
console.log(` 昵称: ${adminUser.nickname}`);
console.log(` 邮箱: ${adminUser.email}`);
console.log('========================================');
console.log('角色和权限:');
console.log(` 角色: ${roleCodes.join(', ')}`);
console.log(` 权限数量: ${permissionCodes.size}`);
if (permissionCodes.size > 0 && permissionCodes.size <= 20) {
console.log(` 权限列表:`);
Array.from(permissionCodes)
.sort()
.forEach((code) => {
console.log(` - ${code}`);
});
} else if (permissionCodes.size > 20) {
console.log(` 权限列表前20个:`);
Array.from(permissionCodes)
.sort()
.slice(0, 20)
.forEach((code) => {
console.log(` - ${code}`);
});
console.log(` ... 还有 ${permissionCodes.size - 20} 个权限`);
}
console.log('========================================');
console.log('菜单分配:');
console.log(` 已分配菜单数: ${finalMenus.length}`);
if (finalMenus.length > 0) {
const topLevelMenus = finalMenus.filter((tm) => !tm.menu.parentId);
console.log(` 顶级菜单数: ${topLevelMenus.length}`);
}
console.log('========================================');
console.log(`\n✅ 租户菜单和权限初始化完成!`);
console.log(`\n💡 现在可以使用以下凭据登录:`);
console.log(` 租户编码: ${tenant.code}`);
console.log(` 用户名: ${adminUser.username}`);
console.log(` 密码: ${password}`);
} catch (error) {
console.error('❌ 初始化失败:', error);
throw error;
} finally {
await prisma.$disconnect();
}
}
// 获取命令行参数
const tenantCode = process.argv[2];
if (!tenantCode) {
console.error('❌ 错误: 请提供租户编码作为参数');
console.error(' 使用方法:');
console.error(' pnpm init:tenant-menu-permissions <租户编码>');
console.error(' 示例:');
console.error(' pnpm init:tenant-menu-permissions tenant1');
process.exit(1);
}
// 执行初始化
initTenantMenuAndPermissions(tenantCode)
.then(() => {
console.log('\n🎉 初始化脚本执行完成!');
process.exit(0);
})
.catch((error) => {
console.error('\n💥 初始化脚本执行失败:', error);
process.exit(1);
});