import { HttpStatus, Injectable } from '@nestjs/common'; import { randomUUID } from 'node:crypto'; import type { PrismaClient } from '@prisma/client'; import { BaseCrudService } from '../common/base-crud.service.js'; import { PrismaService } from '../prisma/prisma.service.js'; import { RedisService } from '../common/redis/redis.service.js'; import { getRequestContext } from '../common/context/request-context.js'; import { TENANT_ADMIN_ROLE_CODE } from '../common/constants.js'; import { BusinessException } from '../common/business-exception.js'; import { ErrorCodes } from '../common/error-codes.js'; import { initTenant } from '../common/tenant-template.js'; import { CreateTenantDto, ListTenantQuery, UpdateTenantDto } from './dto.js'; @Injectable() export class TenantsService extends BaseCrudService { constructor( prisma: PrismaService, private readonly redis: RedisService, ) { super(prisma, 'tenant'); } /** 创建租户并初始化(事务化);未提供管理员密码时生成随机密码返回。 */ async createTenant(dto: CreateTenantDto) { const ctx = getRequestContext(); const code = dto.code; const adminPassword = dto.adminPassword ?? randomUUID().slice(0, 12); await this.prisma.raw.$transaction(async (tx) => { await tx.tenant.create({ data: { code, name: dto.name, status: dto.status ?? 'enabled', remark: dto.remark, brandName: dto.brandName, logoUrl: dto.logoUrl, themeColor: dto.themeColor, copyright: dto.copyright, createdBy: ctx?.userId ?? null, }, }); await initTenant({ prisma: tx as unknown as PrismaClient, tenantCode: code, tenantName: dto.name, adminRoleCode: TENANT_ADMIN_ROLE_CODE, adminRoleName: '租户管理员', adminUsername: dto.adminUsername ?? 'admin', adminPassword, includePlatformPerms: false, forcePasswordChange: true, }); }); const tenant = await this.db().findUnique({ where: { code } }); return dto.adminPassword ? tenant : { ...tenant, adminPassword }; } listTenants(q: ListTenantQuery) { const where: Record = {}; if (q.name) where.name = { contains: q.name }; if (q.code) where.code = { contains: q.code }; if (q.status) where.status = q.status; return this.paginate(q.page, q.pageSize, where); } async updateTenant(id: string, dto: UpdateTenantDto) { const existing = await this.findById(id); if (!existing) throw new BusinessException(ErrorCodes.TENANT_NOT_FOUND, '租户不存在', HttpStatus.NOT_FOUND); return this.update(id, { ...dto }); } /** 删除租户并级联清理全部业务数据与 Redis 缓存。 */ async removeTenant(id: string) { const existing = await this.findById(id); if (!existing) throw new BusinessException(ErrorCodes.TENANT_NOT_FOUND, '租户不存在', HttpStatus.NOT_FOUND); const code = existing.code; const db = this.prisma.raw; await db.$transaction([ db.userRole.deleteMany({ where: { tenantCode: code } }), db.roleResource.deleteMany({ where: { tenantCode: code } }), db.roleOrg.deleteMany({ where: { tenantCode: code } }), db.refreshToken.deleteMany({ where: { tenantCode: code } }), db.loginLog.deleteMany({ where: { tenantCode: code } }), db.auditLog.deleteMany({ where: { tenantCode: code } }), db.config.deleteMany({ where: { tenantCode: code } }), db.dictItem.deleteMany({ where: { tenantCode: code } }), db.dictType.deleteMany({ where: { tenantCode: code } }), db.org.deleteMany({ where: { tenantCode: code } }), db.resource.deleteMany({ where: { tenantCode: code } }), db.role.deleteMany({ where: { tenantCode: code } }), db.user.deleteMany({ where: { tenantCode: code } }), db.tenant.delete({ where: { id } }), ]); // 清该租户的权限缓存与用户状态缓存(旧 token 立即失效) await this.redis.delByPrefix(`perm:${code}:`); await this.redis.delByPrefix(`user:${code}:`); return null; } }