sysmgr-nest/src/common/base-crud.service.ts
liuyun 13510ee216 feat(tenant): 租户 onboarding 模板 + 级联删除
- 抽取 seed 资源/字典到 tenant-template.ts,initTenant 幂等初始化租户
- 创建租户自动初始化管理员/根组织/角色/资源/字典
- 删除租户级联清理全部业务数据
- 修复 BaseCrudService 对 Tenant 模型的 tenantCode 大小写注入 bug

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-11 16:32:46 +08:00

80 lines
2.8 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.

import { PrismaService } from '../prisma/prisma.service.js';
import { getRequestContext } from './context/request-context.js';
/**
* 软删时需「改名回收」唯一键的模型映射:modelName → 唯一字段及其列长。
* 目的:软删只置 deletedAt 不释放 @@unique,重建同名会 P2002;删除时把唯一字段改名
* 成「原值(截断) + #id」释放唯一键,展示名字段(name/label)保持不变。
*/
const RECYCLE_FIELDS: Record<string, { field: string; maxLen: number }> = {
user: { field: 'username', maxLen: 64 },
role: { field: 'code', maxLen: 64 },
resource: { field: 'code', maxLen: 128 },
org: { field: 'code', maxLen: 64 },
dictType: { field: 'code', maxLen: 64 },
dictItem: { field: 'value', maxLen: 64 },
config: { field: 'key', maxLen: 128 },
};
/**
* 通用 CRUD 基类:封装分页/创建/更新/软删。
* 创建时自动注入 tenantCode(Tenant 模型除外);软删由子类按需调用 softRemove。
*/
export class BaseCrudService {
constructor(
protected readonly prisma: PrismaService,
private readonly modelName: string,
) {}
protected db(): any {
return (this.prisma.client as any)[this.modelName];
}
async paginate(
page = 1,
pageSize = 20,
where: Record<string, unknown> = {},
orderBy: Record<string, unknown> = { createdAt: 'desc' },
) {
const [list, total] = await Promise.all([
this.db().findMany({ where, skip: (page - 1) * pageSize, take: pageSize, orderBy }),
this.db().count({ where }),
]);
return { list, total, page, pageSize };
}
async list(where: Record<string, unknown> = {}, orderBy?: Record<string, unknown>) {
return this.db().findMany({ where, orderBy });
}
async findById(id: string) {
return this.db().findUnique({ where: { id } });
}
async create(data: Record<string, unknown>) {
const ctx = getRequestContext();
if (ctx && this.modelName !== 'tenant') data.tenantCode = ctx.tenantCode;
return this.db().create({ data });
}
async update(id: string, data: Record<string, unknown>) {
return this.db().update({ where: { id }, data });
}
async softRemove(id: string) {
const recycle = RECYCLE_FIELDS[this.modelName];
if (!recycle) {
return this.db().update({ where: { id }, data: { deletedAt: new Date() } });
}
// 先读原值,改名回收唯一键,再软删
const record = await this.findById(id);
const original = record?.[recycle.field];
if (original == null) {
return this.db().update({ where: { id }, data: { deletedAt: new Date() } });
}
const suffix = `#${id}`;
const recycled = `${String(original).slice(0, recycle.maxLen - suffix.length)}${suffix}`;
return this.db().update({ where: { id }, data: { [recycle.field]: recycled, deletedAt: new Date() } });
}
}