const Database = require('better-sqlite3'); const path = require('path'); const logger = require('./utils/logger'); const db = new Database(path.join(__dirname, '..', 'data.db')); // 启用 WAL 模式和外键约束 db.pragma('journal_mode = WAL'); db.pragma('foreign_keys = ON'); /** * 创建表结构 */ function createTables() { db.exec(` CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, password TEXT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS records ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, type TEXT NOT NULL CHECK(type IN ('income', 'expense')), category TEXT NOT NULL, amount REAL NOT NULL, quantity INTEGER, unit_price REAL, boss TEXT, partner TEXT, source TEXT, destination TEXT, note TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ); CREATE INDEX IF NOT EXISTS idx_records_user_id ON records(user_id); CREATE INDEX IF NOT EXISTS idx_records_category ON records(category); CREATE INDEX IF NOT EXISTS idx_records_created_at ON records(created_at); CREATE INDEX IF NOT EXISTS idx_records_type ON records(type); CREATE INDEX IF NOT EXISTS idx_records_boss ON records(boss); CREATE INDEX IF NOT EXISTS idx_records_partner ON records(partner); CREATE INDEX IF NOT EXISTS idx_records_source ON records(source); CREATE INDEX IF NOT EXISTS idx_records_user_date ON records(user_id, created_at); CREATE INDEX IF NOT EXISTS idx_records_user_category ON records(user_id, category, created_at); CREATE INDEX IF NOT EXISTS idx_records_destination ON records(destination); `); logger.debug('表结构创建完成'); } /** * 迁移:去掉 category 的 CHECK 约束 */ function migrateRemoveCategoryCheck() { try { const tableInfo = db.prepare("SELECT sql FROM sqlite_master WHERE type='table' AND name='records'").get(); if (tableInfo && tableInfo.sql.includes("CHECK(category IN")) { logger.info('执行迁移:移除 category CHECK 约束'); db.exec(` CREATE TABLE records_new ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, type TEXT NOT NULL CHECK(type IN ('income', 'expense')), category TEXT NOT NULL, amount REAL NOT NULL, quantity INTEGER, unit_price REAL, boss TEXT, partner TEXT, source TEXT, destination TEXT, note TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ); INSERT INTO records_new SELECT * FROM records; DROP TABLE records; ALTER TABLE records_new RENAME TO records; `); logger.info('迁移完成:category CHECK 约束已移除'); } } catch (e) { logger.warn('迁移跳过 (category):', e.message); } } /** * 迁移:添加 rebate 列 */ function migrateAddRebateColumn() { try { const cols = db.prepare("PRAGMA table_info(records)").all(); if (!cols.find(c => c.name === 'rebate')) { logger.info('执行迁移:添加 rebate 列'); db.exec("ALTER TABLE records ADD COLUMN rebate REAL"); logger.info('迁移完成:rebate 列已添加'); } } catch (e) { logger.warn('迁移跳过 (rebate):', e.message); } } /** * 初始化默认用户 */ function initDefaultUser() { if (process.env.NODE_ENV === 'production') { logger.info('生产环境跳过默认用户初始化'); return; } const existingUser = db.prepare('SELECT id FROM users WHERE username = ?').get('lizhilun'); if (!existingUser) { const bcrypt = require('bcryptjs'); const hash = bcrypt.hashSync('lizhilun', 10); db.prepare('INSERT INTO users (username, password) VALUES (?, ?)').run('lizhilun', hash); logger.info('开发默认用户 lizhilun 已创建'); } } // 执行初始化 createTables(); migrateRemoveCategoryCheck(); migrateAddRebateColumn(); initDefaultUser(); module.exports = db;