2026-05-23 11:09:03 +08:00

112 lines
2.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 视频归档定时任务触发API
* 用于 Cron Job 定时调用,执行视频归档任务
*
* 使用方法:
* 1. 配置环境变量 CRON_SECRET
* 2. 在 Vercel Cron 或外部 Cron 服务中配置定时触发
* 3. 每小时执行一次:0 * * * *
*/
import { NextResponse } from "next/server";
import { runVideoArchiveJob } from "@/lib/jobs/video-archive-job";
import { getVideosToArchive } from "@/lib/jobs/video-archive-job";
/**
* 验证请求授权(防止未授权调用)
*/
function verifyAuthToken(request: Request): boolean {
const authHeader = request.headers.get("authorization");
const token = authHeader?.replace("Bearer ", "");
// 与环境变量中的密钥比对
const cronSecret = process.env.CRON_SECRET;
// 安全默认拒绝:定时任务接口被 middleware 公开放行,生产环境不能在缺少密钥时放开。
if (!cronSecret) {
console.error("[VideoArchiveAPI] CRON_SECRET 未配置,拒绝执行定时任务接口");
return false;
}
return token === cronSecret;
}
/**
* POST /api/jobs/archive-videos
* 执行视频归档任务
*/
export async function POST(request: Request) {
try {
// 1. 验证授权
if (!verifyAuthToken(request)) {
return NextResponse.json(
{ error: "Unauthorized" },
{ status: 401 }
);
}
// 2. 执行归档任务
const result = await runVideoArchiveJob();
// 3. 返回成功响应
return NextResponse.json({
success: result.success,
message: result.message,
stats: {
total: result.total,
succeeded: result.succeeded,
failed: result.failed,
},
timestamp: new Date().toISOString(),
});
} catch (error: any) {
console.error("[VideoArchiveAPI] ❌ Job execution failed:", error);
return NextResponse.json(
{
success: false,
error: error.message,
timestamp: new Date().toISOString(),
},
{ status: 500 }
);
}
}
/**
* GET /api/jobs/archive-videos
* 查询待归档视频统计
*/
export async function GET(request: Request) {
try {
// 验证授权
if (!verifyAuthToken(request)) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
// 查询待归档视频
const videos = await getVideosToArchive();
return NextResponse.json({
pendingCount: videos.length,
videos: videos.map((v) => ({
id: v.id,
createdAt: v.createdAt,
temporaryVideoUrl: v.temporaryVideoUrl,
})),
timestamp: new Date().toISOString(),
});
} catch (error: any) {
console.error("[VideoArchiveAPI] ❌ Query failed:", error);
return NextResponse.json(
{
success: false,
error: error.message,
timestamp: new Date().toISOString(),
},
{ status: 500 }
);
}
}