归档 artalk-cf 评论后端 + rss-robot 到 blog-admin(含技术选型/模块分布 README)
Deploy to Production / pre-check (push) Successful in 58s
Deploy to Production / build (push) Successful in 4m3s
Deploy to Production / deploy-edgeone (push) Successful in 3m48s
Deploy to Production / finalize (push) Successful in 26s
Deploy to Production / notify-failure (push) Skipped

This commit is contained in:
zqlit committed 2026-10-04 08:45:40 +08:00
1 parent 855a001046
commit a74b3c7127
98 files changed
+15657

No files matched your search

+109
View File
@@ -0,0 +1,109 @@
// rss-robot-cf —— 主入口
// 路由表 + Cron scheduled。迁自 EdgeOne 的 api/*.js 各端点 + check-feeds.js 抓取逻辑。
import type { Env } from './types';
import { runCron } from './lib/rss/cron';
/** 每小时抓取的源数(66 源 ÷ 22 ≈ 3 小时跑完一轮) */
const RSS_BATCH = 22;
import * as FeedsRoute from './routes/rss/feeds';
import * as LinksRoute from './routes/rss/links';
import { results, articles } from './routes/rss/results';
import { auth, health } from './routes/rss/misc';
import { favicon, update, proxy, cronRun, notifyMail, proxyHealth, deployStatusGet, deployStatusPost, deployNotify } from './routes/rss/tools';
import { linkApply, linkApplyList, linkApplyReview } from './routes/rss/links';
import * as WechatRoute from './routes/rss/wechat';
import { randomImage, artalkCommenter } from './routes/rss/media';
import * as GreetingRoute from './routes/rss/greeting';
import { respond } from './lib/rss/util';
const CORS = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
};
export const RssApi = {
// 每小时轮转抓取:每次抓 RSS_BATCH 个源,游标存在 KV(rss_cursor),
// 三四个小时覆盖全部 66 个源。免费版单次调用限 50 subrequest,
// 所以批量不能大;连续失败的源会进入退避期被跳过(见 cron.ts)。
async scheduled(_controller: ScheduledController, env: Env, _ctx: ExecutionContext): Promise<void> {
await runCron(env, { limit: RSS_BATCH, rotate: true });
},
async fetch(request: Request, env: Env, _ctx: ExecutionContext): Promise<Response> {
const url = new URL(request.url);
const path = url.pathname;
// CORS 预检(所有端点)
if (request.method === 'OPTIONS') {
return new Response(null, { status: 204, headers: CORS });
}
// 微信验证文件(MP_verify_xxx.txt)
if (path.startsWith('/MP_verify_') && path.endsWith('.txt')) {
const code = path.replace('/MP_verify_', '').replace('.txt', '');
return new Response(code, { status: 200, headers: { 'Content-Type': 'text/plain; charset=utf-8' } });
}
// 健康检查
if (path === '/api/health') return health(request, env);
// 管理页(/feed 订阅源管理)与首页登录门
if (path === '/feed') {
const resp = await env.ASSETS!.fetch(new URL('/feed.html', request.url));
if (resp.ok) return new Response(resp.body, { headers: { 'Content-Type': 'text/html; charset=utf-8' } });
return respond({ error: 'feed.html not found' }, 404);
}
// 路由分发
try {
switch (path) {
case '/api/feeds':
return FeedsRoute.onRequest(request, env);
case '/api/links':
return LinksRoute.onRequest(request, env);
case '/api/results':
return results(request, env);
case '/api/articles':
return articles(request, env);
case '/api/auth':
return auth(request, env);
case '/api/favicon':
return favicon(request, env);
case '/api/update':
return update(request, env);
case '/api/cron/run':
return cronRun(request, env);
case '/api/deploy-status':
return request.method === 'POST' ? deployStatusPost(request, env) : deployStatusGet(request, env);
case '/api/deploy-notify':
return deployNotify(request, env);
case '/api/link-apply':
return linkApply(request, env);
case '/api/link-apply/list':
return linkApplyList(request, env);
case '/api/link-apply/review':
return linkApplyReview(request, env);
case '/api/proxy':
return proxy(request, env);
case '/api/notify-mail':
return notifyMail(request, env);
case '/api/proxy-health':
return proxyHealth(request, env);
case '/api/wechat-material':
return WechatRoute.onRequest(request, env);
case '/api/random-image':
return randomImage(request, env);
case '/api/artalk/commenter':
return artalkCommenter(request, env);
case '/api/ai/greeting':
return GreetingRoute.onRequest(request, env);
default:
return respond({ error: 'not found', path }, 404);
}
} catch (err) {
return respond({ error: (err as Error).message }, 500);
}
},
};