Files
wechat-radar/app/api/rescan/route.ts
T
2026-05-24 13:42:56 +08:00

90 lines
2.4 KiB
TypeScript

import { NextRequest } from 'next/server';
import { wxSessions } from '@/lib/wx';
import { syncFullHistory } from '@/lib/stats-aggregator';
import { normalizeDate, normalizeRangeKey, rangeToWindow, type RangeKey } from '@/lib/range';
import { readConfig } from '@/lib/config';
import { cache, CK } from '@/lib/cache';
export const dynamic = 'force-dynamic';
export const maxDuration = 1800; // 30 min
interface RescanBody {
range?: RangeKey;
anchorDate?: string;
since?: string;
until?: string;
full?: boolean; // 一键全量:1 年
}
export async function POST(req: NextRequest) {
const body = (await req.json().catch(() => ({}))) as RescanBody;
let since: string;
let until: string;
let scope: string;
if (body.full) {
const w = rangeToWindow('year');
since = w.since;
until = w.until;
scope = 'full(365d)';
} else if (body.since && body.until) {
since = body.since;
until = body.until;
scope = `custom(${since}~${until})`;
} else {
const range = normalizeRangeKey(body.range, 'month');
const w = rangeToWindow(range, normalizeDate(body.anchorDate));
since = w.since;
until = w.until;
scope = range;
}
const sessions = await wxSessions(500);
const targets = sessions
.filter((s) => s.is_group)
.map((s) => ({ chatroomId: s.username, display: s.chat }));
const cfg = readConfig();
const concurrency = cfg.rescanConcurrency ?? 6;
const stream = new ReadableStream({
async start(controller) {
const enc = new TextEncoder();
const send = (obj: unknown) =>
controller.enqueue(enc.encode(`data: ${JSON.stringify(obj)}\n\n`));
send({ type: 'start', scope, since, until, groups: targets.length });
try {
const result = await syncFullHistory({
targets,
since,
until,
concurrency,
onProgress: (p) => send(p),
});
cache.del(CK.sessions());
send({
type: 'finished',
ok: result.ok,
failed: result.failed,
messages: result.messages,
});
} catch (e) {
send({ type: 'error', error: e instanceof Error ? e.message : 'unknown' });
} finally {
controller.close();
}
},
});
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache, no-transform',
Connection: 'keep-alive',
},
});
}