crawl.js 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
  1. 'use strict';
  2. // 门店采集路由:店铺清单、抓取任务控制、任务/系统日志
  3. const fs = require('fs');
  4. const { sendJson, readBody } = require('../util.js');
  5. function handleShopsManual(ctx, body, res) {
  6. const lines = String((body && body.text) || '').split(/\r?\n/);
  7. const parsed = [];
  8. for (const line of lines) {
  9. const t = line.trim();
  10. if (!t) continue;
  11. const parts = t.split(/[\s,,]+/).filter(Boolean);
  12. const idPart = parts.find((p) => /^\d+$/.test(p));
  13. if (!idPart) continue;
  14. const shopId = Number(idPart);
  15. const shopName = parts.filter((p) => p !== idPart).join(' ') || String(shopId);
  16. parsed.push({ shopId, shopName, source: 'manual' });
  17. }
  18. const previousShops = ctx.shops.getList();
  19. const mergedShops = ctx.shops.mergeShops(parsed);
  20. try { ctx.shops.saveManualShops(mergedShops); ctx.shops.setList(mergedShops); }
  21. catch (e) { ctx.shops.setList(previousShops); throw e; }
  22. sendJson(res, 200, { shops: ctx.shops.getList(), addedCount: parsed.length });
  23. }
  24. function handleShopsList(ctx, root, res) {
  25. if (root && !ctx.workspace.isWorkspaceRoot(root)) return sendJson(res, 403, { error: '仅允许访问当前输出根目录' });
  26. const completed = root && fs.existsSync(root) ? ctx.crawl.readCompletionState(root) : {};
  27. sendJson(res, 200, {
  28. shops: ctx.shops.getList().map((shop) => ({ ...shop, completed: completed[String(shop.shopId)] || null })),
  29. });
  30. }
  31. function register(app, ctx) {
  32. app.get('/api/shops', async ({ res, query }) => {
  33. // 前端把 root 做 base64url 编码传入,避免中文路径在 URL 里出问题
  34. const encodedRoot = query.root;
  35. const root = encodedRoot ? Buffer.from(encodedRoot, 'base64url').toString('utf8') : '';
  36. return handleShopsList(ctx, root, res);
  37. });
  38. app.post('/api/shops/manual', async ({ req, res }) => {
  39. const body = await readBody(req);
  40. return handleShopsManual(ctx, body, res);
  41. });
  42. app.post('/api/crawl/start', async ({ req, res }) => {
  43. const body = await readBody(req);
  44. return ctx.crawl.startCrawl(body, res);
  45. });
  46. app.post('/api/crawl/stop', async ({ res }) => {
  47. return ctx.crawl.stopCrawl(res);
  48. });
  49. app.get('/api/crawl/status', async ({ res }) => {
  50. sendJson(res, 200, ctx.crawl.getState());
  51. });
  52. app.get('/api/logs', async ({ res }) => {
  53. sendJson(res, 200, { logs: ctx.logs.mergeLogs(ctx.crawl.getLog()) });
  54. });
  55. app.post('/api/logs/clear', async ({ res }) => {
  56. ctx.logs.clearAll(ctx.crawl.getLog());
  57. ctx.logs.pushSystemLog('日志已清空', 'warn');
  58. return sendJson(res, 200, { ok: true });
  59. });
  60. }
  61. module.exports = { register };