library-picturebook-activity/backend/scripts/init-roles-permissions.ts
2026-01-12 20:04:11 +08:00

562 lines
26 KiB
TypeScript

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck
import * as dotenv from 'dotenv';
import * as path from 'path';
const nodeEnv = process.env.NODE_ENV || 'development';
const envFile = `.env.${nodeEnv}`;
const backendDir = path.resolve(__dirname, '..');
const envPath = path.resolve(backendDir, envFile);
dotenv.config({ path: envPath });
if (!process.env.DATABASE_URL) {
dotenv.config({ path: path.resolve(backendDir, '.env') });
}
if (!process.env.DATABASE_URL) {
console.error('DATABASE_URL not found');
process.exit(1);
}
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
// ============================================
// 权限定义
// ============================================
// 基础权限(所有角色共享的权限池)
const allPermissions = [
// 工作台
{ code: 'workbench:read', resource: 'workbench', action: 'read', name: '查看工作台', description: '允许查看工作台' },
// 用户管理
{ code: 'user:create', resource: 'user', action: 'create', name: '创建用户', description: '允许创建新用户' },
{ code: 'user:read', resource: 'user', action: 'read', name: '查看用户', description: '允许查看用户列表和详情' },
{ code: 'user:update', resource: 'user', action: 'update', name: '更新用户', description: '允许更新用户信息' },
{ code: 'user:delete', resource: 'user', action: 'delete', name: '删除用户', description: '允许删除用户' },
// 角色管理
{ code: 'role:create', resource: 'role', action: 'create', name: '创建角色', description: '允许创建新角色' },
{ code: 'role:read', resource: 'role', action: 'read', name: '查看角色', description: '允许查看角色列表和详情' },
{ code: 'role:update', resource: 'role', action: 'update', name: '更新角色', description: '允许更新角色信息' },
{ code: 'role:delete', resource: 'role', action: 'delete', name: '删除角色', description: '允许删除角色' },
{ code: 'role:assign', resource: 'role', action: 'assign', name: '分配角色', description: '允许给用户分配角色' },
// 权限管理
{ code: 'permission:read', resource: 'permission', action: 'read', name: '查看权限', description: '允许查看权限列表' },
// 菜单管理
{ code: 'menu:read', resource: 'menu', action: 'read', name: '查看菜单', description: '允许查看菜单列表' },
// 租户管理(超级租户专属)
{ code: 'tenant:create', resource: 'tenant', action: 'create', name: '创建租户', description: '允许创建租户' },
{ code: 'tenant:read', resource: 'tenant', action: 'read', name: '查看租户', description: '允许查看租户列表' },
{ code: 'tenant:update', resource: 'tenant', action: 'update', name: '更新租户', description: '允许更新租户信息' },
{ code: 'tenant:delete', resource: 'tenant', action: 'delete', name: '删除租户', description: '允许删除租户' },
// 学校管理
{ code: 'school:create', resource: 'school', action: 'create', name: '创建学校', description: '允许创建学校信息' },
{ code: 'school:read', resource: 'school', action: 'read', name: '查看学校', description: '允许查看学校信息' },
{ code: 'school:update', resource: 'school', action: 'update', name: '更新学校', description: '允许更新学校信息' },
{ code: 'school:delete', resource: 'school', action: 'delete', name: '删除学校', description: '允许删除学校信息' },
// 部门管理
{ code: 'department:create', resource: 'department', action: 'create', name: '创建部门', description: '允许创建部门' },
{ code: 'department:read', resource: 'department', action: 'read', name: '查看部门', description: '允许查看部门列表' },
{ code: 'department:update', resource: 'department', action: 'update', name: '更新部门', description: '允许更新部门信息' },
{ code: 'department:delete', resource: 'department', action: 'delete', name: '删除部门', description: '允许删除部门' },
// 年级管理
{ code: 'grade:create', resource: 'grade', action: 'create', name: '创建年级', description: '允许创建年级' },
{ code: 'grade:read', resource: 'grade', action: 'read', name: '查看年级', description: '允许查看年级列表' },
{ code: 'grade:update', resource: 'grade', action: 'update', name: '更新年级', description: '允许更新年级信息' },
{ code: 'grade:delete', resource: 'grade', action: 'delete', name: '删除年级', description: '允许删除年级' },
// 班级管理
{ code: 'class:create', resource: 'class', action: 'create', name: '创建班级', description: '允许创建班级' },
{ code: 'class:read', resource: 'class', action: 'read', name: '查看班级', description: '允许查看班级列表' },
{ code: 'class:update', resource: 'class', action: 'update', name: '更新班级', description: '允许更新班级信息' },
{ code: 'class:delete', resource: 'class', action: 'delete', name: '删除班级', description: '允许删除班级' },
// 教师管理
{ code: 'teacher:create', resource: 'teacher', action: 'create', name: '创建教师', description: '允许创建教师' },
{ code: 'teacher:read', resource: 'teacher', action: 'read', name: '查看教师', description: '允许查看教师列表' },
{ code: 'teacher:update', resource: 'teacher', action: 'update', name: '更新教师', description: '允许更新教师信息' },
{ code: 'teacher:delete', resource: 'teacher', action: 'delete', name: '删除教师', description: '允许删除教师' },
// 学生管理
{ code: 'student:create', resource: 'student', action: 'create', name: '创建学生', description: '允许创建学生' },
{ code: 'student:read', resource: 'student', action: 'read', name: '查看学生', description: '允许查看学生列表' },
{ code: 'student:update', resource: 'student', action: 'update', name: '更新学生', description: '允许更新学生信息' },
{ code: 'student:delete', resource: 'student', action: 'delete', name: '删除学生', description: '允许删除学生' },
// 赛事管理(超级租户)
{ code: 'contest:create', resource: 'contest', action: 'create', name: '创建赛事', description: '允许创建赛事' },
{ code: 'contest:read', resource: 'contest', action: 'read', name: '查看赛事', description: '允许查看赛事列表' },
{ code: 'contest:update', resource: 'contest', action: 'update', name: '更新赛事', description: '允许更新赛事信息' },
{ code: 'contest:delete', resource: 'contest', action: 'delete', name: '删除赛事', description: '允许删除赛事' },
{ code: 'contest:publish', resource: 'contest', action: 'publish', name: '发布赛事', description: '允许发布/取消发布赛事' },
{ code: 'contest:finish', resource: 'contest', action: 'finish', name: '结束赛事', description: '允许结束赛事' },
// 评审规则管理
{ code: 'review-rule:create', resource: 'review-rule', action: 'create', name: '创建评审规则', description: '允许创建评审规则' },
{ code: 'review-rule:read', resource: 'review-rule', action: 'read', name: '查看评审规则', description: '允许查看评审规则' },
{ code: 'review-rule:update', resource: 'review-rule', action: 'update', name: '更新评审规则', description: '允许更新评审规则' },
{ code: 'review-rule:delete', resource: 'review-rule', action: 'delete', name: '删除评审规则', description: '允许删除评审规则' },
// 评委管理
{ code: 'judge:create', resource: 'judge', action: 'create', name: '添加评委', description: '允许添加评委' },
{ code: 'judge:read', resource: 'judge', action: 'read', name: '查看评委', description: '允许查看评委列表' },
{ code: 'judge:update', resource: 'judge', action: 'update', name: '更新评委', description: '允许更新评委信息' },
{ code: 'judge:delete', resource: 'judge', action: 'delete', name: '删除评委', description: '允许删除评委' },
{ code: 'judge:assign', resource: 'judge', action: 'assign', name: '分配评委', description: '允许为赛事分配评委' },
// 赛事报名(学校端)
{ code: 'registration:create', resource: 'registration', action: 'create', name: '创建报名', description: '允许报名赛事' },
{ code: 'registration:read', resource: 'registration', action: 'read', name: '查看报名', description: '允许查看报名记录' },
{ code: 'registration:update', resource: 'registration', action: 'update', name: '更新报名', description: '允许更新报名信息' },
{ code: 'registration:delete', resource: 'registration', action: 'delete', name: '取消报名', description: '允许取消报名' },
{ code: 'registration:approve', resource: 'registration', action: 'approve', name: '审核报名', description: '允许审核报名' },
// 参赛作品
{ code: 'work:create', resource: 'work', action: 'create', name: '上传作品', description: '允许上传参赛作品' },
{ code: 'work:read', resource: 'work', action: 'read', name: '查看作品', description: '允许查看参赛作品' },
{ code: 'work:update', resource: 'work', action: 'update', name: '更新作品', description: '允许更新作品信息' },
{ code: 'work:delete', resource: 'work', action: 'delete', name: '删除作品', description: '允许删除作品' },
{ code: 'work:submit', resource: 'work', action: 'submit', name: '提交作品', description: '允许提交作品' },
// 作品评审(评委端)
{ code: 'review:read', resource: 'review', action: 'read', name: '查看评审任务', description: '允许查看待评审作品' },
{ code: 'review:score', resource: 'review', action: 'score', name: '评审打分', description: '允许对作品打分' },
// 赛事公告
{ code: 'notice:create', resource: 'notice', action: 'create', name: '创建公告', description: '允许创建赛事公告' },
{ code: 'notice:read', resource: 'notice', action: 'read', name: '查看公告', description: '允许查看赛事公告' },
{ code: 'notice:update', resource: 'notice', action: 'update', name: '更新公告', description: '允许更新公告信息' },
{ code: 'notice:delete', resource: 'notice', action: 'delete', name: '删除公告', description: '允许删除公告' },
// 作业管理
{ code: 'homework:create', resource: 'homework', action: 'create', name: '创建作业', description: '允许创建作业' },
{ code: 'homework:read', resource: 'homework', action: 'read', name: '查看作业', description: '允许查看作业列表' },
{ code: 'homework:update', resource: 'homework', action: 'update', name: '更新作业', description: '允许更新作业信息' },
{ code: 'homework:delete', resource: 'homework', action: 'delete', name: '删除作业', description: '允许删除作业' },
{ code: 'homework:publish', resource: 'homework', action: 'publish', name: '发布作业', description: '允许发布作业' },
// 作业提交
{ code: 'homework-submission:create', resource: 'homework-submission', action: 'create', name: '提交作业', description: '允许提交作业' },
{ code: 'homework-submission:read', resource: 'homework-submission', action: 'read', name: '查看作业提交', description: '允许查看作业提交记录' },
{ code: 'homework-submission:update', resource: 'homework-submission', action: 'update', name: '更新作业提交', description: '允许更新提交的作业' },
// 作业评审规则
{ code: 'homework-review-rule:create', resource: 'homework-review-rule', action: 'create', name: '创建作业评审规则', description: '允许创建作业评审规则' },
{ code: 'homework-review-rule:read', resource: 'homework-review-rule', action: 'read', name: '查看作业评审规则', description: '允许查看作业评审规则' },
{ code: 'homework-review-rule:update', resource: 'homework-review-rule', action: 'update', name: '更新作业评审规则', description: '允许更新作业评审规则' },
{ code: 'homework-review-rule:delete', resource: 'homework-review-rule', action: 'delete', name: '删除作业评审规则', description: '允许删除作业评审规则' },
// 作业评分
{ code: 'homework-score:create', resource: 'homework-score', action: 'create', name: '作业评分', description: '允许对作业评分' },
{ code: 'homework-score:read', resource: 'homework-score', action: 'read', name: '查看作业评分', description: '允许查看作业评分' },
// 字典管理
{ code: 'dict:create', resource: 'dict', action: 'create', name: '创建字典', description: '允许创建新字典' },
{ code: 'dict:read', resource: 'dict', action: 'read', name: '查看字典', description: '允许查看字典列表和详情' },
{ code: 'dict:update', resource: 'dict', action: 'update', name: '更新字典', description: '允许更新字典信息' },
{ code: 'dict:delete', resource: 'dict', action: 'delete', name: '删除字典', description: '允许删除字典' },
// 系统配置
{ code: 'config:create', resource: 'config', action: 'create', name: '创建配置', description: '允许创建新配置' },
{ code: 'config:read', resource: 'config', action: 'read', name: '查看配置', description: '允许查看配置列表和详情' },
{ code: 'config:update', resource: 'config', action: 'update', name: '更新配置', description: '允许更新配置信息' },
{ code: 'config:delete', resource: 'config', action: 'delete', name: '删除配置', description: '允许删除配置' },
// 日志管理
{ code: 'log:read', resource: 'log', action: 'read', name: '查看日志', description: '允许查看系统日志' },
{ code: 'log:delete', resource: 'log', action: 'delete', name: '删除日志', description: '允许删除系统日志' },
// 赛事活动(学校端)
{ code: 'activity:read', resource: 'activity', action: 'read', name: '查看赛事活动', description: '允许查看已发布的赛事活动' },
{ code: 'activity:guidance', resource: 'activity', action: 'guidance', name: '指导学生', description: '允许指导学生参赛' },
];
// ============================================
// 角色定义和权限映射
// ============================================
// 超级租户角色
const superTenantRoles = [
{
code: 'super_admin',
name: '超级管理员',
description: '系统超级管理员,管理赛事和系统配置',
permissions: [
// 工作台
'workbench:read',
// 系统管理
'user:create', 'user:read', 'user:update', 'user:delete',
'role:create', 'role:read', 'role:update', 'role:delete', 'role:assign',
'permission:read',
'menu:read',
'tenant:create', 'tenant:read', 'tenant:update', 'tenant:delete',
'dict:create', 'dict:read', 'dict:update', 'dict:delete',
'config:create', 'config:read', 'config:update', 'config:delete',
'log:read', 'log:delete',
// 赛事管理
'contest:create', 'contest:read', 'contest:update', 'contest:delete', 'contest:publish', 'contest:finish',
'review-rule:create', 'review-rule:read', 'review-rule:update', 'review-rule:delete',
'judge:create', 'judge:read', 'judge:update', 'judge:delete', 'judge:assign',
'registration:read', 'registration:approve',
'work:read',
'notice:create', 'notice:read', 'notice:update', 'notice:delete',
],
},
{
code: 'judge',
name: '评委',
description: '赛事评委,可以评审作品',
permissions: [
'workbench:read',
'activity:read', // 查看赛事活动
'work:read', // 查看待评审作品
'review:read', // 查看评审任务
'review:score', // 评审打分
'notice:read', // 查看公告
],
},
];
// 普通租户(学校)角色
const normalTenantRoles = [
{
code: 'school_admin',
name: '学校管理员',
description: '学校管理员,管理学校信息、教师、学生等',
permissions: [
'workbench:read',
'user:create', 'user:read', 'user:update', 'user:delete',
'role:create', 'role:read', 'role:update', 'role:delete', 'role:assign',
'permission:read',
'menu:read',
// 学校管理
'school:create', 'school:read', 'school:update', 'school:delete',
'department:create', 'department:read', 'department:update', 'department:delete',
'grade:create', 'grade:read', 'grade:update', 'grade:delete',
'class:create', 'class:read', 'class:update', 'class:delete',
'teacher:create', 'teacher:read', 'teacher:update', 'teacher:delete',
'student:create', 'student:read', 'student:update', 'student:delete',
// 赛事活动
'activity:read',
'notice:read',
// 可以查看报名和作品
'registration:read',
'work:read',
],
},
{
code: 'teacher',
name: '教师',
description: '教师角色,可以报名赛事、指导学生、管理作业',
permissions: [
'workbench:read',
// 查看基础信息
'grade:read',
'class:read',
'student:read',
// 赛事活动
'activity:read', // 查看赛事活动列表
'activity:guidance', // 指导学生参赛
'notice:read', // 查看赛事公告
'registration:create', 'registration:read', 'registration:update', 'registration:delete', // 报名管理
'work:create', 'work:read', 'work:update', 'work:submit', // 指导学生上传作品
// 作业管理
'homework:create', 'homework:read', 'homework:update', 'homework:delete', 'homework:publish',
'homework-submission:read',
'homework-review-rule:create', 'homework-review-rule:read', 'homework-review-rule:update', 'homework-review-rule:delete',
'homework-score:create', 'homework-score:read',
],
},
{
code: 'student',
name: '学生',
description: '学生角色,可以查看赛事、上传作品、提交作业',
permissions: [
'workbench:read',
// 赛事活动
'activity:read', // 查看赛事活动列表
'notice:read', // 查看赛事公告
'registration:read', // 查看自己的报名记录
'work:create', 'work:read', 'work:update', 'work:submit', // 上传/管理自己的作品
// 作业
'homework:read', // 查看作业
'homework-submission:create', 'homework-submission:read', 'homework-submission:update', // 提交作业
'homework-score:read', // 查看自己的作业评分
],
},
];
// ============================================
// 初始化函数
// ============================================
/**
* 为租户创建权限
*/
async function createPermissions(tenantId: number, permissionCodes: string[]) {
const createdPermissions: { [code: string]: number } = {};
for (const code of permissionCodes) {
const permDef = allPermissions.find(p => p.code === code);
if (!permDef) {
console.log(` ⚠️ 权限定义不存在: ${code}`);
continue;
}
// 检查是否已存在
let permission = await prisma.permission.findFirst({
where: { tenantId, code },
});
if (!permission) {
permission = await prisma.permission.create({
data: {
tenantId,
code: permDef.code,
resource: permDef.resource,
action: permDef.action,
name: permDef.name,
description: permDef.description,
validState: 1,
},
});
console.log(` ✓ 创建权限: ${code}`);
}
createdPermissions[code] = permission.id;
}
return createdPermissions;
}
/**
* 为租户创建角色并分配权限
*/
async function createRoleWithPermissions(
tenantId: number,
roleConfig: { code: string; name: string; description: string; permissions: string[] },
permissionMap: { [code: string]: number }
) {
// 创建或获取角色
let role = await prisma.role.findFirst({
where: { tenantId, code: roleConfig.code },
});
if (!role) {
role = await prisma.role.create({
data: {
tenantId,
code: roleConfig.code,
name: roleConfig.name,
description: roleConfig.description,
validState: 1,
},
});
console.log(` ✓ 创建角色: ${roleConfig.name} (${roleConfig.code})`);
} else {
// 更新角色信息
role = await prisma.role.update({
where: { id: role.id },
data: {
name: roleConfig.name,
description: roleConfig.description,
},
});
console.log(` ✓ 更新角色: ${roleConfig.name} (${roleConfig.code})`);
}
// 分配权限
const existingRolePermissions = await prisma.rolePermission.findMany({
where: { roleId: role.id },
select: { permissionId: true },
});
const existingPermissionIds = new Set(existingRolePermissions.map(rp => rp.permissionId));
let addedCount = 0;
for (const permCode of roleConfig.permissions) {
const permissionId = permissionMap[permCode];
if (!permissionId) {
console.log(` ⚠️ 权限不存在: ${permCode}`);
continue;
}
if (!existingPermissionIds.has(permissionId)) {
await prisma.rolePermission.create({
data: {
roleId: role.id,
permissionId,
},
});
addedCount++;
}
}
if (addedCount > 0) {
console.log(` 添加了 ${addedCount} 个权限`);
}
return role;
}
/**
* 初始化超级租户的角色和权限
*/
async function initSuperTenantRoles() {
console.log('\n🚀 开始初始化超级租户角色和权限...\n');
// 查找超级租户
const superTenant = await prisma.tenant.findFirst({
where: { isSuper: 1, validState: 1 },
});
if (!superTenant) {
console.error('❌ 超级租户不存在!请先运行 init:super-tenant');
return;
}
console.log(`找到超级租户: ${superTenant.name} (${superTenant.code})\n`);
// 收集所有需要的权限码
const allPermissionCodes = new Set<string>();
superTenantRoles.forEach(role => {
role.permissions.forEach(code => allPermissionCodes.add(code));
});
// 创建权限
console.log('📝 创建权限...');
const permissionMap = await createPermissions(superTenant.id, Array.from(allPermissionCodes));
console.log(`✅ 共 ${Object.keys(permissionMap).length} 个权限\n`);
// 创建角色
console.log('👥 创建角色...');
for (const roleConfig of superTenantRoles) {
await createRoleWithPermissions(superTenant.id, roleConfig, permissionMap);
}
console.log('\n✅ 超级租户角色和权限初始化完成!');
}
/**
* 初始化普通租户的角色和权限
*/
async function initNormalTenantRoles(tenantCode: string) {
console.log(`\n🚀 开始初始化租户 "${tenantCode}" 的角色和权限...\n`);
// 查找租户
const tenant = await prisma.tenant.findFirst({
where: { code: tenantCode, validState: 1 },
});
if (!tenant) {
console.error(`❌ 租户 "${tenantCode}" 不存在!`);
return;
}
if (tenant.isSuper === 1) {
console.log('⚠️ 这是超级租户,请使用 --super 选项');
return;
}
console.log(`找到租户: ${tenant.name} (${tenant.code})\n`);
// 收集所有需要的权限码
const allPermissionCodes = new Set<string>();
normalTenantRoles.forEach(role => {
role.permissions.forEach(code => allPermissionCodes.add(code));
});
// 创建权限
console.log('📝 创建权限...');
const permissionMap = await createPermissions(tenant.id, Array.from(allPermissionCodes));
console.log(`✅ 共 ${Object.keys(permissionMap).length} 个权限\n`);
// 创建角色
console.log('👥 创建角色...');
for (const roleConfig of normalTenantRoles) {
await createRoleWithPermissions(tenant.id, roleConfig, permissionMap);
}
// 输出角色信息
console.log('\n📊 角色权限概览:');
for (const roleConfig of normalTenantRoles) {
console.log(` ${roleConfig.name} (${roleConfig.code}): ${roleConfig.permissions.length} 个权限`);
}
console.log(`\n✅ 租户 "${tenantCode}" 角色和权限初始化完成!`);
}
/**
* 初始化所有普通租户的角色和权限
*/
async function initAllNormalTenantRoles() {
console.log('\n🚀 开始初始化所有普通租户的角色和权限...\n');
// 查找所有普通租户
const normalTenants = await prisma.tenant.findMany({
where: { isSuper: { not: 1 }, validState: 1 },
});
if (normalTenants.length === 0) {
console.log('⚠️ 没有找到普通租户');
return;
}
console.log(`找到 ${normalTenants.length} 个普通租户\n`);
for (const tenant of normalTenants) {
await initNormalTenantRoles(tenant.code);
console.log('');
}
console.log('\n✅ 所有普通租户角色和权限初始化完成!');
}
// ============================================
// 主函数
// ============================================
async function main() {
const args = process.argv.slice(2);
const isSuper = args.includes('--super');
const isAll = args.includes('--all');
const tenantCode = args.find(arg => !arg.startsWith('--'));
try {
if (isSuper) {
await initSuperTenantRoles();
} else if (isAll) {
await initAllNormalTenantRoles();
} else if (tenantCode) {
await initNormalTenantRoles(tenantCode);
} else {
console.log('使用方法:');
console.log(' 初始化超级租户角色: ts-node scripts/init-roles-permissions.ts --super');
console.log(' 初始化指定租户角色: ts-node scripts/init-roles-permissions.ts <租户编码>');
console.log(' 初始化所有普通租户: ts-node scripts/init-roles-permissions.ts --all');
process.exit(1);
}
} finally {
await prisma.$disconnect();
}
}
main()
.then(() => {
console.log('\n🎉 脚本执行完成!');
process.exit(0);
})
.catch((error) => {
console.error('\n💥 脚本执行失败:', error);
process.exit(1);
});