#!/usr/bin/env node 'use strict'; // 导出店铺运营数据为 JSON(用于 SQLite ↔ PostgreSQL 搬迁) // 用法: // node scripts/db-export.js # 导出到 ops-export.json(不含原件) // node scripts/db-export.js out.json --with-files # 连原件一起导出(base64) // // 说明:不带 --with-files 时只导出元数据与数据行;导入端要求每个上传都有原件, // 因此完整搬迁务必加 --with-files。 const fs = require('fs'); const path = require('path'); const { createContext } = require('../server/context.js'); const { createOpsStore } = require('../ops-store.js'); (async () => { const args = process.argv.slice(2); const withFiles = args.includes('--with-files'); const outPath = path.resolve(args.find((a) => !a.startsWith('--')) || 'ops-export.json'); const ctx = createContext(); const store = createOpsStore(ctx.config); const health = await store.health(); if (!health.ok) { console.error(`读取失败:${health.reason || 'storage-unreachable'} ${health.detail || ''}`); process.exit(1); } const uploads = await store.listUploads(); if (withFiles) { for (const upload of uploads) { const record = await store.getContent(upload.id); upload.contentBase64 = record ? record.content.toString('base64') : ''; } } const rows = await store.queryRows({}); const summary = await store.summary(); const payload = { version: 1, exportedAt: new Date().toISOString(), driver: store.kind, withFiles, uploads, shops: summary.shops, rows: rows.map((r) => ({ date: r.date, shopKey: r.shopKey, uploadId: r.uploadId, shopId: r.shopId, shopName: r.shopName, school: r.school, status: r.status, order_cnt: r.order_cnt, revenue: r.revenue, exposure: r.exposure, click_rate: r.click_rate, raw: r.raw, quality: r.quality, })), }; fs.writeFileSync(outPath, JSON.stringify(payload, null, 2)); console.log(`已导出 ${uploads.length} 次上传、${rows.length} 行 → ${outPath}${withFiles ? '(含原件)' : '(不含原件)'}`); await store.close(); })();