文件
hyb-oa/server/src/jobs/delegationRevokeJob.js
T
wangsli 6ecebf8ad6 feat(server): /metrics 可观测与日志上下文(F22)
- 新增 Prometheus 指标端点 /metrics(HTTP + DB + 作业统计)
- request.log 绑定 requestId/actorId,避免日志难追踪
- cron/pg-boss 作业增加 metrics 计数(runs/processed)
- 更新 env/runbook/README 与 v3 CSV 状态推进
2026-02-09 20:30:41 +08:00

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,
};