'use strict'; // 门店采集路由:店铺清单、抓取任务控制、任务/系统日志 const fs = require('fs'); const { sendJson, readBody } = require('../util.js'); function handleShopsManual(ctx, body, res) { const lines = String((body && body.text) || '').split(/\r?\n/); const parsed = []; for (const line of lines) { const t = line.trim(); if (!t) continue; const parts = t.split(/[\s,,]+/).filter(Boolean); const idPart = parts.find((p) => /^\d+$/.test(p)); if (!idPart) continue; const shopId = Number(idPart); const shopName = parts.filter((p) => p !== idPart).join(' ') || String(shopId); parsed.push({ shopId, shopName, source: 'manual' }); } const previousShops = ctx.shops.getList(); const mergedShops = ctx.shops.mergeShops(parsed); try { ctx.shops.saveManualShops(mergedShops); ctx.shops.setList(mergedShops); } catch (e) { ctx.shops.setList(previousShops); throw e; } sendJson(res, 200, { shops: ctx.shops.getList(), addedCount: parsed.length }); } function handleShopsList(ctx, root, res) { if (root && !ctx.workspace.isWorkspaceRoot(root)) return sendJson(res, 403, { error: '仅允许访问当前输出根目录' }); const completed = root && fs.existsSync(root) ? ctx.crawl.readCompletionState(root) : {}; sendJson(res, 200, { shops: ctx.shops.getList().map((shop) => ({ ...shop, completed: completed[String(shop.shopId)] || null })), }); } function register(app, ctx) { app.get('/api/shops', async ({ res, query }) => { // 前端把 root 做 base64url 编码传入,避免中文路径在 URL 里出问题 const encodedRoot = query.root; const root = encodedRoot ? Buffer.from(encodedRoot, 'base64url').toString('utf8') : ''; return handleShopsList(ctx, root, res); }); app.post('/api/shops/manual', async ({ req, res }) => { const body = await readBody(req); return handleShopsManual(ctx, body, res); }); app.post('/api/crawl/start', async ({ req, res }) => { const body = await readBody(req); return ctx.crawl.startCrawl(body, res); }); app.post('/api/crawl/stop', async ({ res }) => { return ctx.crawl.stopCrawl(res); }); app.get('/api/crawl/status', async ({ res }) => { sendJson(res, 200, ctx.crawl.getState()); }); // 采集通道探测:客户端调试端口是否就绪 + Cookie 是否已配置(采集页提示用) app.get('/api/crawl/channel', async ({ res }) => { const kit = require('../../napos/kit.js'); const p = await kit.cdp.probe().catch(() => ({ ok: false })); sendJson(res, 200, { channel: ctx.config.crawlChannel || 'auto', clientUsable: !!p.ok, clientBrowser: (p.ok && p.version && p.version.Browser) || null, cookieConfigured: !!ctx.session.cookie && !!ctx.session.getKsid(), }); }); app.get('/api/logs', async ({ res }) => { sendJson(res, 200, { logs: ctx.logs.mergeLogs(ctx.crawl.getLog()) }); }); app.post('/api/logs/clear', async ({ res }) => { ctx.logs.clearAll(ctx.crawl.getLog()); ctx.logs.pushSystemLog('日志已清空', 'warn'); return sendJson(res, 200, { ok: true }); }); } module.exports = { register };