/** * 数据库初始化 — 建表 + 种子数据 */ 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'); db.pragma('foreign_keys = ON'); db.exec(` -- ========== 商家 ========== CREATE TABLE IF NOT EXISTS merchants ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, website TEXT, notes TEXT, created_at TEXT DEFAULT (datetime('now')), updated_at TEXT DEFAULT (datetime('now')) ); -- ========== 产品 ========== CREATE TABLE IF NOT EXISTS products ( id INTEGER PRIMARY KEY AUTOINCREMENT, merchant_id INTEGER NOT NULL REFERENCES merchants(id) ON DELETE CASCADE, name TEXT NOT NULL, url TEXT, sku TEXT, price TEXT, in_stock INTEGER DEFAULT 0, -- 0=未知 1=有货 2=缺货 last_checked TEXT, notes TEXT, created_at TEXT DEFAULT (datetime('now')), updated_at TEXT DEFAULT (datetime('now')) ); -- ========== Aff 链接 ========== CREATE TABLE IF NOT EXISTS aff_links ( id INTEGER PRIMARY KEY AUTOINCREMENT, product_id INTEGER NOT NULL REFERENCES products(id) ON DELETE CASCADE, platform TEXT NOT NULL DEFAULT 'default', -- e.g. default / telegram / twitter url TEXT NOT NULL, notes TEXT, created_at TEXT DEFAULT (datetime('now')) ); -- ========== Telegram 频道配置 ========== CREATE TABLE IF NOT EXISTS tg_channels ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, chat_id TEXT NOT NULL, enabled INTEGER DEFAULT 1, notes TEXT, created_at TEXT DEFAULT (datetime('now')) ); -- ========== 监控任务 ========== CREATE TABLE IF NOT EXISTS monitor_tasks ( id INTEGER PRIMARY KEY AUTOINCREMENT, product_id INTEGER NOT NULL REFERENCES products(id) ON DELETE CASCADE, tg_channel_id INTEGER REFERENCES tg_channels(id) ON DELETE SET NULL, cron_expr TEXT DEFAULT '*/5 * * * *', enabled INTEGER DEFAULT 1, last_run TEXT, created_at TEXT DEFAULT (datetime('now')) ); -- ========== 检测记录 ========== CREATE TABLE IF NOT EXISTS check_logs ( id INTEGER PRIMARY KEY AUTOINCREMENT, task_id INTEGER NOT NULL REFERENCES monitor_tasks(id) ON DELETE CASCADE, product_id INTEGER NOT NULL, status TEXT NOT NULL, -- in_stock / out_of_stock / error message TEXT, notified INTEGER DEFAULT 0, created_at TEXT DEFAULT (datetime('now')) ); CREATE INDEX IF NOT EXISTS idx_check_logs_task ON check_logs(task_id); CREATE INDEX IF NOT EXISTS idx_check_logs_date ON check_logs(created_at); CREATE INDEX IF NOT EXISTS idx_products_merchant ON products(merchant_id); `); console.log('✅ 数据库初始化完成:', dbPath); // 种子数据(仅当 merchants 为空时插入) const count = db.prepare('SELECT count(*) AS c FROM merchants').get().c; if (count === 0) { const insertMerchant = db.prepare('INSERT INTO merchants (name, website, notes) VALUES (?, ?, ?)'); const insertProduct = db.prepare('INSERT INTO products (merchant_id, name, url, price) VALUES (?, ?, ?, ?)'); const insertChannel = db.prepare('INSERT INTO tg_channels (name, chat_id) VALUES (?, ?)'); db.transaction(() => { insertMerchant.run('示例商家 A', 'https://example-a.com', '这是一个演示商家'); insertMerchant.run('示例商家 B', 'https://example-b.com', null); insertProduct.run(1, 'VPS 套餐 Basic', 'https://example-a.com/vps-basic', '$4.99/mo'); insertProduct.run(1, 'VPS 套餐 Pro', 'https://example-a.com/vps-pro', '$9.99/mo'); insertProduct.run(2, '独服 E3', 'https://example-b.com/dedi-e3', '€29/mo'); insertChannel.run('测试频道', '-1001234567890'); })(); console.log('🌱 种子数据已插入'); } db.close();