/** * Cloudflare Worker — 定时刷新 ds2api 代理健康状态 * * 部署步骤: * 1. cd cf-worker && npm install * 2. 设置 secrets: * wrangler secret put DS2API_BASE_URL * wrangler secret put ADMIN_KEY * wrangler secret put VERCEL_BYPASS_TOKEN (如果开了 Vercel Deployment Protection) * 3. wrangler deploy * * Cron 每 5 小时触发一次 (与 wrangler.toml 中的 crons 对应)。 * 也可以手动 GET/POST https:///check 触发。 */ const LOGIN_PATH = "/admin/login" const CHECK_ALL_PATH = "/admin/proxies/check-all" function vercelHeaders(env) { const headers = {} const bypass = env.VERCEL_BYPASS_TOKEN if (bypass) { headers["x-vercel-protection-bypass"] = bypass } return headers } async function login(baseURL, adminKey, extraHeaders) { const res = await fetch(`${baseURL}${LOGIN_PATH}`, { method: "POST", headers: { "Content-Type": "application/json", ...extraHeaders }, body: JSON.stringify({ admin_key: adminKey, expire_hours: 1 }), }) if (!res.ok) { const text = await res.text() throw new Error(`login failed (${res.status}): ${text}`) } const data = await res.json() return data.token } async function checkAll(baseURL, token, extraHeaders) { const res = await fetch(`${baseURL}${CHECK_ALL_PATH}`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, ...extraHeaders, }, }) if (!res.ok) { const text = await res.text() throw new Error(`check-all failed (${res.status}): ${text}`) } return res.json() } async function runCheck(env) { const baseURL = (env.DS2API_BASE_URL || "").replace(/\/+$/, "") const adminKey = env.ADMIN_KEY || "" if (!baseURL || !adminKey) { throw new Error("DS2API_BASE_URL and ADMIN_KEY secrets must be set") } const extraHeaders = vercelHeaders(env) const token = await login(baseURL, adminKey, extraHeaders) const result = await checkAll(baseURL, token, extraHeaders) return result } export default { async scheduled(event, env, ctx) { ctx.waitUntil( runCheck(env) .then((result) => { const items = result.items || [] const healthy = items.filter((i) => i.healthy && !i.disabled).length const banned = items.filter((i) => i.disabled).length console.log( `proxy health check done: ${healthy}/${items.length} healthy, ${banned} banned` ) }) .catch((err) => { console.error(`proxy health check failed: ${err.message}`) }) ) }, async fetch(request, env) { const url = new URL(request.url) if (url.pathname !== "/check") { return new Response("not found", { status: 404 }) } try { const result = await runCheck(env) return Response.json(result) } catch (err) { return Response.json({ error: err.message }, { status: 500 }) } }, }