feat: 首次推送到Gitea - 完整项目代码 + 安全加固 + 知识库
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
// Task runner with retry, timeout, and structured logging
|
||||
import { createLogger } from "./logger.mjs";
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import { PrismaMariaDb } from "@prisma/adapter-mariadb";
|
||||
|
||||
const logger = createLogger("task-runner");
|
||||
|
||||
function createPrisma() {
|
||||
const base = process.env.DATABASE_URL.replace("mysql://", "mariadb://");
|
||||
const sep = base.includes("?") ? "&" : "?";
|
||||
const connectionString = `${base}${sep}connection_limit=3&pool_timeout=30`;
|
||||
const adapter = new PrismaMariaDb(connectionString);
|
||||
return new PrismaClient({ adapter });
|
||||
}
|
||||
|
||||
async function runTask(taskKey, taskName, fn, { timeoutMs = 30 * 60 * 1000, maxRetries = 2 } = {}) {
|
||||
const prisma = createPrisma();
|
||||
const startedAt = new Date();
|
||||
let attempt = 0;
|
||||
let lastError;
|
||||
|
||||
logger.info(`Starting task: ${taskName}`, { taskKey });
|
||||
|
||||
while (attempt <= maxRetries) {
|
||||
if (attempt > 0) {
|
||||
const delay = Math.min(5000 * Math.pow(2, attempt - 1), 30000);
|
||||
logger.warn(`Retry ${attempt}/${maxRetries} for ${taskName} after ${delay}ms`, { taskKey });
|
||||
await new Promise((r) => setTimeout(r, delay));
|
||||
}
|
||||
|
||||
try {
|
||||
// Timeout wrapper
|
||||
const timeoutPromise = new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error(`Task timed out after ${timeoutMs / 60000} minutes`)), timeoutMs)
|
||||
);
|
||||
|
||||
const result = await Promise.race([fn(prisma), timeoutPromise]);
|
||||
|
||||
// Log success
|
||||
await prisma.taskLog.create({
|
||||
data: {
|
||||
taskKey,
|
||||
taskName,
|
||||
status: "success",
|
||||
startedAt,
|
||||
finishedAt: new Date(),
|
||||
duration: Date.now() - startedAt.getTime(),
|
||||
triggerBy: "cron",
|
||||
result: result ? JSON.parse(JSON.stringify(result).slice(0, 2000)) : undefined,
|
||||
},
|
||||
}).catch(() => {});
|
||||
|
||||
logger.info(`Task completed: ${taskName}`, {
|
||||
taskKey,
|
||||
durationMs: Date.now() - startedAt.getTime(),
|
||||
attempt,
|
||||
});
|
||||
|
||||
await prisma.$disconnect();
|
||||
return result;
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
attempt++;
|
||||
logger.error(`Task attempt failed: ${taskName}`, {
|
||||
taskKey,
|
||||
attempt,
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// All retries exhausted
|
||||
const errorMsg = (lastError?.message || String(lastError)).slice(0, 1000);
|
||||
await prisma.taskLog.create({
|
||||
data: {
|
||||
taskKey,
|
||||
taskName,
|
||||
status: "failed",
|
||||
startedAt,
|
||||
finishedAt: new Date(),
|
||||
duration: Date.now() - startedAt.getTime(),
|
||||
triggerBy: "cron",
|
||||
error: errorMsg,
|
||||
},
|
||||
}).catch(() => {});
|
||||
|
||||
logger.error(`Task failed after ${maxRetries + 1} attempts: ${taskName}`, {
|
||||
taskKey,
|
||||
error: errorMsg,
|
||||
});
|
||||
|
||||
await prisma.$disconnect();
|
||||
throw lastError;
|
||||
}
|
||||
|
||||
export { runTask, createLogger };
|
||||
Reference in New Issue
Block a user