/** * Migration 004 — PID & Slug 字段 * * - internal_pid: 系统内部编号,如 VPS-000001(唯一,自动生成) * - provider_pid: 商家产品 ID,从购买链接自动解析(如 pid=28) * - slug: 前台友好 URL,唯一 */ const Database = require('better-sqlite3'); const path = require('path'); require('dotenv').config({ path: path.join(__dirname, '..', '.env') }); const dbPath = path.resolve(__dirname, '..', process.env.DB_PATH || 'db/monitor.sqlite'); const db = new Database(dbPath); db.pragma('journal_mode = WAL'); function ensureColumn(table, column, sql) { const cols = db.prepare(`PRAGMA table_info(${table})`).all().map(c => c.name); if (!cols.includes(column)) { db.exec(`ALTER TABLE ${table} ADD COLUMN ${sql}`); console.log(`+ ${table}.${column}`); } else { console.log(` ${table}.${column} (already exists)`); } } // 添加新字段 ensureColumn('products', 'internal_pid', 'internal_pid TEXT'); ensureColumn('products', 'provider_pid', 'provider_pid TEXT'); ensureColumn('products', 'slug', 'slug TEXT'); // 创建唯一索引(如果尚不存在) try { db.exec('CREATE UNIQUE INDEX IF NOT EXISTS idx_products_internal_pid ON products(internal_pid) WHERE internal_pid IS NOT NULL'); console.log('+ index: idx_products_internal_pid'); } catch (e) { console.log(' index idx_products_internal_pid:', e.message); } try { db.exec('CREATE UNIQUE INDEX IF NOT EXISTS idx_products_slug ON products(slug) WHERE slug IS NOT NULL'); console.log('+ index: idx_products_slug'); } catch (e) { console.log(' index idx_products_slug:', e.message); } // 为已有产品回填 internal_pid 和 slug const { generateInternalPid, generateSlug, parseProviderPid } = require('../src/utils/pidHelper'); const products = db.prepare('SELECT id, name, merchant_id, url, buy_url, internal_pid, slug, provider_pid FROM products').all(); const updateStmt = db.prepare('UPDATE products SET internal_pid = ?, slug = ?, provider_pid = ? WHERE id = ?'); const getMerchantName = db.prepare('SELECT name FROM merchants WHERE id = ?'); const existingSlugs = new Set( db.prepare("SELECT slug FROM products WHERE slug IS NOT NULL AND slug != ''").all().map(r => r.slug) ); db.transaction(() => { for (const p of products) { let ipid = p.internal_pid; let slug = p.slug; let ppid = p.provider_pid; // 生成 internal_pid if (!ipid) { ipid = generateInternalPid(db); } // 生成 slug if (!slug) { const merchant = getMerchantName.get(p.merchant_id); const merchantName = merchant ? merchant.name : ''; slug = generateSlug(p.name, merchantName, existingSlugs); existingSlugs.add(slug); } // 解析 provider_pid if (!ppid) { ppid = parseProviderPid(p.buy_url) || parseProviderPid(p.url) || null; } updateStmt.run(ipid, slug, ppid, p.id); console.log(` product #${p.id}: internal_pid=${ipid}, slug=${slug}, provider_pid=${ppid || '(none)'}`); } })(); console.log('✅ migration-004 done:', dbPath); db.close();