需求背景
给所有model添加 审计字段 ,deleted,createdAt,createdBy,updatedAt,updatedBy,deletedAt,deletedBy
衍生需求需求
- create的时候 自动添加
createdAt,createdBy的值 - update的时候 自动添加
updatedAt,updatedBy的值 - delete的时候 自动添加
deleted,deletedAt,deletedBy的值 - 查询的时候 添加
deleted过滤
那么就开始吧,主要使用的prisma的$extend函数
参考链接 www.prisma.io/docs/orm/pr…
1.添加一个ExtendedClient函数
private createExtendedClient(): PrismaClient {
const base = new PrismaClient();
return base.$extends({
query: {
$allModels: {
},
},
}) as PrismaClient;
}
2.判断模型是不是存在审计字段
interface AuditInfo {
hasDeleted: boolean;
hasCreatedAt: boolean;
hasCreatedBy: boolean;
hasUpdatedAt: boolean;
hasUpdatedBy: boolean;
hasDeletedAt: boolean;
hasDeletedBy: boolean;
}
private getOrBuildAuditMap(): Record<string, AuditInfo> {
// 如果已有缓存,直接返回
if (PrismaService.auditMapCache) {
return PrismaService.auditMapCache;
}
const auditMap: Record<string, AuditInfo> = {};
const schemaPath = path.join(process.cwd(), "prisma", "schema.prisma");
const modelsDir = path.join(process.cwd(), "prisma", "models");
try {
let raw = this.collectPrismaFiles(modelsDir);
// 若 models 目录为空,则回退到主 schema.prisma
if (!raw.trim().length) {
try {
raw = fs.readFileSync(schemaPath, "utf8");
} catch (error) {
this.logger.warn(
`无法读取 schema.prisma: ${error instanceof Error ? error.message : String(error)}`
);
raw = "";
}
}
// 解析所有 model
const modelRegex = /model\s+(\w+)\s*\{([^}]*)\}/gms;
for (const match of raw.matchAll(modelRegex)) {
const name = match[1];
const body = match[2];
const hasField = (f: string) =>
new RegExp(`^\\s*${f}\\s+`, "m").test(body);
auditMap[name] = {
hasDeleted: /\bdeleted\s+Boolean\b/.test(body),
hasCreatedAt: hasField("createdAt"),
hasCreatedBy: hasField("createdBy"),
hasUpdatedAt: hasField("updatedAt"),
hasUpdatedBy: hasField("updatedBy"),
hasDeletedAt: hasField("deletedAt"),
hasDeletedBy: hasField("deletedBy"),
};
}
// 缓存结果
PrismaService.auditMapCache = auditMap;
this.logger.log(
`成功解析 ${Object.keys(auditMap).length} 个 Prisma models`
);
} catch (error) {
this.logger.error(
`解析 Prisma schema 时出错: ${error instanceof Error ? error.message : String(error)}`
);
}
return auditMap;
}
3.添加fillAuditFileds方法
function fillAuditFields(
data: any,
info: AuditInfo,
type: "create" | "update" | "delete",
actor: string
): any {
const result = { ...data };
const now = new Date();
if (type === "create") {
if (info.hasCreatedAt && result.createdAt === undefined) {
result.createdAt = now;
}
if (info.hasCreatedBy && result.createdBy === undefined) {
result.createdBy = actor;
}
if (info.hasDeleted && result.deleted === undefined) {
result.deleted = false;
}
}
if (type === "update" || type === "delete") {
if (info.hasUpdatedAt && result.updatedAt === undefined) {
result.updatedAt = now;
}
if (info.hasUpdatedBy && result.updatedBy === undefined) {
result.updatedBy = actor;
}
}
if (type === "delete") {
if (info.hasDeleted) result.deleted = true;
if (info.hasDeletedAt) result.deletedAt = now;
if (info.hasDeletedBy) result.deletedBy = actor;
}
return result;
}
4.create,createMany
{
$allModels: {
create: ({ model, args, query }: any) => {
const info = auditMap[model];
if (info) {
args.data = fillAuditFields(
args.data ?? {},
info,
"create",
this.getActor()
);
}
return query(args);
},
createMany: ({ model, args, query }: any) => {
const info = auditMap[model];
if (info && Array.isArray(args.data)) {
const actor = this.getActor();
args.data = args.data.map((d: any) =>
fillAuditFields(d, info, "create", actor)
);
}
return query(args);
},
}
}
5.update,updateMany
{
$allModels: {
...,
update: ({ model, args, query }: any) => {
const info = auditMap[model];
if (info) {
args.data = fillAuditFields(
args.data ?? {},
info,
"update",
this.getActor()
);
}
return query(args);
},
updateMany: ({ model, args, query }: any) => {
const info = auditMap[model];
if (info) {
args.data = fillAuditFields(
args.data ?? {},
info,
"update",
this.getActor()
);
}
return query(args);
},
}
}
6.delete,deleteMany
delete: ({ model, args, query }: any) => {
const info = auditMap[model];
if (!info?.hasDeleted) {
return query(args);
}
const data = fillAuditFields({}, info, "delete", this.getActor());
return (base as any)[model].update({
where: args.where,
data,
});
},
deleteMany: ({ model, args, query }: any) => {
const info = auditMap[model];
if (!info?.hasDeleted) {
return query(args);
}
const data = fillAuditFields({}, info, "delete", this.getActor());
return (base as any)[model].updateMany({
where: args.where,
data,
});
},
7.查询相关
查询相关稍微复杂一点,2个需求
1.添加deleted查询过滤条件
2.查询的deleted,deletedAt,deletedBy三个审计字段不查询
那么就又衍生出一个方法
private applySoftDeleteFilter(
info: AuditInfo | undefined,
args: any,
isUniqueQuery = false
): any {
if (!info?.hasDeleted) return args;
if (args?.includeDeleted) {
delete args.includeDeleted;
return args;
}
if (!args.where || args.where.deleted === undefined) {
// findUnique/findUniqueOrThrow 不能使用 AND,直接在 where 上添加 deleted 字段
if (isUniqueQuery) {
args.where = { ...args.where, deleted: false };
} else {
args.where = { AND: [args.where ?? {}, { deleted: false }] };
}
}
return args;
}
方法方法如下
findMany: ({ model, args, query }: any) => {
const info = auditMap[model];
const shouldOmit = !args?.includeDeleted;
const a = this.applySoftDeleteFilter(info, args);
return query(a).then((records: any[]) => {
if (!records || !Array.isArray(records)) return records;
return shouldOmit && info?.hasDeleted
? records.map((record) => omitDeletedFields(record))
: records;
});
},
findFirst: ({ model, args, query }: any) => {
const info = auditMap[model];
const shouldOmit = !args?.includeDeleted;
const a = this.applySoftDeleteFilter(info, args);
return query(a).then((record: any) =>
record && shouldOmit && info?.hasDeleted
? omitDeletedFields(record)
: record
);
},
findUnique: ({ model, args, query }: any) => {
const info = auditMap[model];
const shouldOmit = !args?.includeDeleted;
const a = this.applySoftDeleteFilter(info, args, true);
return query(a).then((record: any) =>
record && shouldOmit && info?.hasDeleted
? omitDeletedFields(record)
: record
);
},
findUniqueOrThrow: ({ model, args, query }: any) => {
const info = auditMap[model];
const shouldOmit = !args?.includeDeleted;
const a = this.applySoftDeleteFilter(info, args, true);
return query(a).then((record: any) =>
record && shouldOmit && info?.hasDeleted
? omitDeletedFields(record)
: record
);
},
count: ({ model, args, query }: any) => {
const info = auditMap[model];
return query(this.applySoftDeleteFilter(info, args));
},
aggregate: ({ model, args, query }: any) => {
const info = auditMap[model];
return query(this.applySoftDeleteFilter(info, args));
},
groupBy: ({ model, args, query }: any) => {
const info = auditMap[model];
return query(this.applySoftDeleteFilter(info, args));
},
好了 ,完结,撒花~
谢谢大家