- error-codes 单一注册表 + BusinessException + 过滤器识别 - 迁移登录/锁定/待改密/用户名冲突/租户/会话等关键错误 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
100 lines
4.1 KiB
TypeScript
100 lines
4.1 KiB
TypeScript
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<string, unknown> = {};
|
|
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;
|
|
}
|
|
}
|