db-export.js 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
  1. #!/usr/bin/env node
  2. 'use strict';
  3. // 导出店铺运营数据为 JSON(用于 SQLite ↔ PostgreSQL 搬迁)
  4. // 用法:
  5. // node scripts/db-export.js # 导出到 ops-export.json(不含原件)
  6. // node scripts/db-export.js out.json --with-files # 连原件一起导出(base64)
  7. //
  8. // 说明:不带 --with-files 时只导出元数据与数据行;导入端要求每个上传都有原件,
  9. // 因此完整搬迁务必加 --with-files。
  10. const fs = require('fs');
  11. const path = require('path');
  12. const { createContext } = require('../server/context.js');
  13. const { createOpsStore } = require('../ops-store.js');
  14. (async () => {
  15. const args = process.argv.slice(2);
  16. const withFiles = args.includes('--with-files');
  17. const outPath = path.resolve(args.find((a) => !a.startsWith('--')) || 'ops-export.json');
  18. const ctx = createContext();
  19. const store = createOpsStore(ctx.config);
  20. const health = await store.health();
  21. if (!health.ok) {
  22. console.error(`读取失败:${health.reason || 'storage-unreachable'} ${health.detail || ''}`);
  23. process.exit(1);
  24. }
  25. const uploads = await store.listUploads();
  26. if (withFiles) {
  27. for (const upload of uploads) {
  28. const record = await store.getContent(upload.id);
  29. upload.contentBase64 = record ? record.content.toString('base64') : '';
  30. }
  31. }
  32. const rows = await store.queryRows({});
  33. const platformRows = await store.queryPlatformRows({});
  34. const summary = await store.summary();
  35. // 经营报告历史(完整快照),迁移后仍可回看/下载
  36. const reports = [];
  37. for (const item of await store.listReports()) {
  38. const full = await store.getReport(item.id);
  39. if (full) reports.push({ ...item, report: full });
  40. }
  41. const payload = {
  42. version: 1,
  43. exportedAt: new Date().toISOString(),
  44. driver: store.kind,
  45. withFiles,
  46. uploads,
  47. shops: summary.shops,
  48. platformRows,
  49. reports,
  50. rows: rows.map((r) => ({
  51. date: r.date,
  52. shopKey: r.shopKey,
  53. uploadId: r.uploadId,
  54. shopId: r.shopId,
  55. shopName: r.shopName,
  56. school: r.school,
  57. status: r.status,
  58. order_cnt: r.order_cnt,
  59. revenue: r.revenue,
  60. exposure: r.exposure,
  61. click_rate: r.click_rate,
  62. raw: r.raw,
  63. quality: r.quality,
  64. })),
  65. };
  66. fs.writeFileSync(outPath, JSON.stringify(payload, null, 2));
  67. console.log(`已导出 ${uploads.length} 次上传、${rows.length} 行人工数据、${platformRows.length} 行平台明细、${reports.length} 份报告 → ${outPath}${withFiles ? '(含原件)' : '(不含原件)'}`);
  68. await store.close();
  69. })();