/** * 模块权限服务 * * 管理用户可访问的模块,支持多租户模块权限合并 */ import { prisma } from '../../config/database.js'; import { logger } from '../logging/index.js'; /** * 模块信息 */ export interface ModuleInfo { code: string; name: string; description?: string | null; icon?: string | null; } /** * 用户模块访问结果 */ export interface UserModulesResult { modules: string[]; // 可访问的模块代码列表 moduleDetails: ModuleInfo[]; // 模块详细信息 tenantModules: { // 按租户分组的模块 tenantId: string; tenantName: string; isPrimary: boolean; modules: string[]; }[]; } class ModuleService { /** * 获取所有可用模块 */ async getAllModules(): Promise { const modules = await prisma.modules.findMany({ where: { is_active: true }, orderBy: { sort_order: 'asc' }, select: { code: true, name: true, description: true, icon: true, }, }); return modules; } /** * 获取用户可访问的所有模块 * 合并用户所属的所有租户的模块权限 */ async getUserModules(userId: string): Promise { try { // 1. 获取用户的主租户 const user = await prisma.user.findUnique({ where: { id: userId }, select: { tenant_id: true, tenants: { select: { id: true, name: true, }, }, }, }); if (!user) { logger.warn('[ModuleService] 用户不存在', { userId }); return { modules: [], moduleDetails: [], tenantModules: [] }; } // 2. 获取用户加入的其他租户 const memberships = await prisma.tenant_members.findMany({ where: { user_id: userId }, select: { tenant_id: true, tenants: { select: { id: true, name: true, }, }, }, }); // 3. 构建租户列表(主租户 + 额外加入的租户) const tenantMap = new Map(); // 主租户 if (user.tenant_id && user.tenants) { tenantMap.set(user.tenant_id, { name: user.tenants.name, isPrimary: true, }); } // 额外加入的租户 for (const m of memberships) { if (!tenantMap.has(m.tenant_id)) { tenantMap.set(m.tenant_id, { name: m.tenants.name, isPrimary: false, }); } } const tenantIds = Array.from(tenantMap.keys()); if (tenantIds.length === 0) { logger.warn('[ModuleService] 用户没有关联任何租户', { userId }); return { modules: [], moduleDetails: [], tenantModules: [] }; } // 4. 查询所有租户的已开通模块 const tenantModulesData = await prisma.tenant_modules.findMany({ where: { tenant_id: { in: tenantIds }, is_enabled: true, OR: [ { expires_at: null }, { expires_at: { gt: new Date() } }, ], }, select: { tenant_id: true, module_code: true, }, }); // 5. 按租户分组模块 const tenantModulesGrouped: UserModulesResult['tenantModules'] = []; const modulesByTenant = new Map(); for (const tm of tenantModulesData) { if (!modulesByTenant.has(tm.tenant_id)) { modulesByTenant.set(tm.tenant_id, []); } modulesByTenant.get(tm.tenant_id)!.push(tm.module_code); } Array.from(tenantMap.entries()).forEach(([tenantId, tenantInfo]) => { tenantModulesGrouped.push({ tenantId, tenantName: tenantInfo.name, isPrimary: tenantInfo.isPrimary, modules: modulesByTenant.get(tenantId) || [], }); }); // 5.5 查询用户级别的模块权限(精细化控制) const userModulesData = await prisma.user_modules.findMany({ where: { user_id: userId, tenant_id: { in: tenantIds }, }, select: { tenant_id: true, module_code: true, is_enabled: true, }, }); // 按租户分组 user_modules const userModulesByTenant = new Map>(); for (const um of userModulesData) { if (!userModulesByTenant.has(um.tenant_id)) { userModulesByTenant.set(um.tenant_id, new Map()); } userModulesByTenant.get(um.tenant_id)!.set(um.module_code, um.is_enabled); } // 6. 合并所有模块(去重),尊重 user_modules 精细化配置 const moduleSet = new Set(); for (const tm of tenantModulesData) { const userModulesForTenant = userModulesByTenant.get(tm.tenant_id); if (userModulesForTenant && userModulesForTenant.size > 0) { const isEnabled = userModulesForTenant.get(tm.module_code); if (isEnabled) { moduleSet.add(tm.module_code); } } else { moduleSet.add(tm.module_code); } } // 6.5 补充用户级独立配置的模块(如 AIA_PROTOCOL,租户未订阅但用户单独开通) for (const [, userModuleMap] of userModulesByTenant) { for (const [moduleCode, isEnabled] of userModuleMap) { if (isEnabled) { moduleSet.add(moduleCode); } } } const allModuleCodes = Array.from(moduleSet); // 7. 获取模块详细信息 const moduleDetails = await prisma.modules.findMany({ where: { code: { in: allModuleCodes }, is_active: true, }, orderBy: { sort_order: 'asc' }, select: { code: true, name: true, description: true, icon: true, }, }); logger.debug('[ModuleService] 获取用户模块成功', { userId, tenantCount: tenantIds.length, moduleCount: allModuleCodes.length, }); return { modules: allModuleCodes, moduleDetails, tenantModules: tenantModulesGrouped, }; } catch (error) { logger.error('[ModuleService] 获取用户模块失败', { userId, error }); return { modules: [], moduleDetails: [], tenantModules: [] }; } } /** * 检查用户是否有权访问指定模块 */ async canAccessModule(userId: string, moduleCode: string): Promise { try { const { modules } = await this.getUserModules(userId); return modules.includes(moduleCode); } catch (error) { logger.error('[ModuleService] 检查模块权限失败', { userId, moduleCode, error }); return false; } } /** * 获取租户的已开通模块 */ async getTenantModules(tenantId: string): Promise { const modules = await prisma.tenant_modules.findMany({ where: { tenant_id: tenantId, is_enabled: true, OR: [ { expires_at: null }, { expires_at: { gt: new Date() } }, ], }, select: { module_code: true }, }); return modules.map(m => m.module_code); } /** * 设置租户的模块配置 */ async setTenantModules( tenantId: string, moduleConfigs: { code: string; enabled: boolean; expiresAt?: Date | null }[] ): Promise { for (const config of moduleConfigs) { // 先查询是否存在 const existing = await prisma.tenant_modules.findUnique({ where: { tenant_id_module_code: { tenant_id: tenantId, module_code: config.code, }, }, }); if (existing) { // 更新 await prisma.tenant_modules.update({ where: { id: existing.id }, data: { is_enabled: config.enabled, expires_at: config.expiresAt, }, }); } else { // 创建(使用 crypto.randomUUID 生成 id) const { randomUUID } = await import('crypto'); await prisma.tenant_modules.create({ data: { id: randomUUID(), tenant_id: tenantId, module_code: config.code, is_enabled: config.enabled, expires_at: config.expiresAt, }, }); } } logger.info('[ModuleService] 更新租户模块配置', { tenantId, moduleCount: moduleConfigs.length, }); } } // 导出单例 export const moduleService = new ModuleService();