feat(server): 完成 P2 后端能力(支付幂等、委派回收、OCR异步、审计)
这个提交包含在:
@@ -0,0 +1,103 @@
|
||||
const cron = require("node-cron");
|
||||
|
||||
async function revokeExpiredDelegations(prisma, logger, options = {}) {
|
||||
const batchSize = options.batchSize ?? 200;
|
||||
let total = 0;
|
||||
|
||||
while (true) {
|
||||
const now = new Date();
|
||||
const expired = await prisma.delegationGrant.findMany({
|
||||
where: {
|
||||
revoked_at: null,
|
||||
valid_to: {
|
||||
not: null,
|
||||
lte: now,
|
||||
},
|
||||
},
|
||||
orderBy: { valid_to: "asc" },
|
||||
take: batchSize,
|
||||
select: {
|
||||
id: true,
|
||||
principal_employee_id: true,
|
||||
delegate_employee_id: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!expired.length) {
|
||||
break;
|
||||
}
|
||||
|
||||
await prisma.$transaction(async (tx) => {
|
||||
const ids = expired.map((item) => item.id);
|
||||
await tx.delegationGrant.updateMany({
|
||||
where: {
|
||||
id: { in: ids },
|
||||
revoked_at: null,
|
||||
},
|
||||
data: {
|
||||
revoked_at: now,
|
||||
},
|
||||
});
|
||||
|
||||
await tx.auditLog.createMany({
|
||||
data: expired.map((item) => ({
|
||||
request_id: `job:delegation-revoke:${now.getTime()}:${item.id}`,
|
||||
actor_id: item.principal_employee_id,
|
||||
action: "DELEGATION_AUTO_REVOKED",
|
||||
target_type: "delegation_grant",
|
||||
target_id: item.id,
|
||||
ip_address: null,
|
||||
user_agent: "system/node-cron",
|
||||
extra: {
|
||||
principalEmployeeId: item.principal_employee_id,
|
||||
delegateEmployeeId: item.delegate_employee_id,
|
||||
},
|
||||
})),
|
||||
});
|
||||
});
|
||||
|
||||
total += expired.length;
|
||||
}
|
||||
|
||||
if (total > 0) {
|
||||
logger.info({ revoked: total }, "Expired delegations auto-revoked");
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
function startDelegationRevokeJob(fastify) {
|
||||
let running = false;
|
||||
|
||||
const task = cron.schedule(fastify.config.delegationRevokeCron, async () => {
|
||||
if (running) {
|
||||
return;
|
||||
}
|
||||
|
||||
running = true;
|
||||
try {
|
||||
await revokeExpiredDelegations(fastify.prisma, fastify.log);
|
||||
} catch (error) {
|
||||
fastify.log.error({ err: error }, "Delegation revoke job failed");
|
||||
} finally {
|
||||
running = false;
|
||||
}
|
||||
});
|
||||
|
||||
fastify.addHook("onClose", async () => {
|
||||
task.stop();
|
||||
task.destroy();
|
||||
});
|
||||
|
||||
fastify.log.info(
|
||||
{ cron: fastify.config.delegationRevokeCron },
|
||||
"Delegation revoke job scheduled",
|
||||
);
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
revokeExpiredDelegations,
|
||||
startDelegationRevokeJob,
|
||||
};
|
||||
在新工单中引用
屏蔽一个用户