- 新增 Prometheus 指标端点 /metrics(HTTP + DB + 作业统计) - request.log 绑定 requestId/actorId,避免日志难追踪 - cron/pg-boss 作业增加 metrics 计数(runs/processed) - 更新 env/runbook/README 与 v3 CSV 状态推进
140 行
3.4 KiB
JavaScript
140 行
3.4 KiB
JavaScript
const cron = require("node-cron");
|
|
|
|
async function claimAndRevokeExpiredDelegations(tx, batchSize) {
|
|
return tx.$queryRaw`
|
|
WITH picked AS (
|
|
SELECT id
|
|
FROM "delegation_grants"
|
|
WHERE revoked_at IS NULL
|
|
AND valid_to IS NOT NULL
|
|
AND valid_to <= NOW()
|
|
ORDER BY valid_to ASC
|
|
LIMIT ${batchSize}
|
|
FOR UPDATE SKIP LOCKED
|
|
)
|
|
UPDATE "delegation_grants" AS dg
|
|
SET revoked_at = NOW()
|
|
FROM picked
|
|
WHERE dg.id = picked.id
|
|
AND dg.revoked_at IS NULL
|
|
RETURNING dg.id, dg.principal_employee_id, dg.delegate_employee_id, dg.revoked_at
|
|
`;
|
|
}
|
|
|
|
async function revokeExpiredDelegations(prisma, logger, options = {}) {
|
|
const batchSize = Math.max(1, Number(options.batchSize ?? 200));
|
|
let total = 0;
|
|
let round = 0;
|
|
|
|
while (true) {
|
|
round += 1;
|
|
|
|
const revokedRows = await prisma.$transaction(async (tx) => {
|
|
const claimed = await claimAndRevokeExpiredDelegations(tx, batchSize);
|
|
if (!claimed.length) {
|
|
return [];
|
|
}
|
|
|
|
const requestTime = Date.now();
|
|
await tx.auditLog.createMany({
|
|
data: claimed.map((item, index) => {
|
|
const revokedAt = item.revoked_at ? new Date(item.revoked_at).getTime() : requestTime;
|
|
return {
|
|
request_id: `job:delegation-revoke:${revokedAt}:${item.id}:${index}`,
|
|
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,
|
|
},
|
|
};
|
|
}),
|
|
});
|
|
|
|
return claimed;
|
|
});
|
|
|
|
if (!revokedRows.length) {
|
|
break;
|
|
}
|
|
|
|
total += revokedRows.length;
|
|
|
|
logger.info(
|
|
{
|
|
round,
|
|
claimed: revokedRows.length,
|
|
updated: revokedRows.length,
|
|
audited: revokedRows.length,
|
|
},
|
|
"Expired delegations batch processed",
|
|
);
|
|
}
|
|
|
|
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 {
|
|
const revoked = await revokeExpiredDelegations(fastify.prisma, fastify.log);
|
|
fastify.metrics?.jobRunsTotal?.inc?.({
|
|
job: "delegation.revoke-expired",
|
|
driver: "cron",
|
|
outcome: "success",
|
|
});
|
|
if (revoked > 0) {
|
|
fastify.metrics?.jobProcessedTotal?.inc?.(
|
|
{
|
|
job: "delegation.revoke-expired",
|
|
driver: "cron",
|
|
kind: "revoked",
|
|
},
|
|
revoked,
|
|
);
|
|
}
|
|
} catch (error) {
|
|
fastify.log.error({ err: error }, "Delegation revoke job failed");
|
|
fastify.metrics?.jobRunsTotal?.inc?.({
|
|
job: "delegation.revoke-expired",
|
|
driver: "cron",
|
|
outcome: "failure",
|
|
});
|
|
} 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,
|
|
};
|