Spaces:
Sleeping
Sleeping
| import path from 'path'; | |
| import { fileURLToPath } from 'url'; | |
| import bcrypt from 'bcryptjs'; | |
| const __filename = fileURLToPath(import.meta.url); | |
| const __dirname = path.dirname(__filename); | |
| // 数据库文件存储在 /app/data 或项目根目录 | |
| const dataDir = process.env.DATA_DIR || path.resolve(process.cwd(), 'data'); | |
| const dbPath = path.resolve(dataDir, 'platform.db'); | |
| let db: any = null; | |
| /** | |
| * 延迟加载数据库,防止 native 模块加载失败导致进程直接退出 | |
| */ | |
| const getDB = async () => { | |
| if (db) return db; | |
| try { | |
| console.log(`[Database] 正在连接 SQLite: ${dbPath}`); | |
| // 使用动态导入,以便捕获加载原生模块时的错误 | |
| const { default: Database } = await import('better-sqlite3'); | |
| db = new Database(dbPath); | |
| return db; | |
| } catch (err: any) { | |
| console.error('[Database] SQLite 加载失败:', err.message); | |
| if (err.message.includes('bindings')) { | |
| console.error('[Database] 提示: 可能是 better-sqlite3 的原生模块未正确编译。'); | |
| } | |
| throw err; | |
| } | |
| }; | |
| // 初始化表结构 | |
| const initDB = async () => { | |
| const database = await getDB(); | |
| // 1. 用户表 (增加配额与等级) | |
| database.exec(` | |
| CREATE TABLE IF NOT EXISTS users ( | |
| id TEXT PRIMARY KEY, | |
| email TEXT UNIQUE NOT NULL, | |
| password TEXT NOT NULL, | |
| name TEXT, | |
| avatar TEXT, -- 用户头像 URL | |
| role TEXT DEFAULT 'user', -- 'user', 'admin' | |
| plan TEXT DEFAULT 'free', -- 'free', 'pro', 'enterprise' | |
| quota_remaining INTEGER DEFAULT 500, -- 每月剩余 AI 消息额度 | |
| two_factor_enabled BOOLEAN DEFAULT 0, -- 是否开启双重认证 | |
| login_alert_enabled BOOLEAN DEFAULT 1, -- 是否开启登录提醒 | |
| created_at DATETIME DEFAULT CURRENT_TIMESTAMP | |
| ) | |
| `); | |
| // 检查并增加新列 (针对已存在的数据库) | |
| try { | |
| const columns = database.prepare("PRAGMA table_info(users)").all(); | |
| const hasAvatar = columns.some((c: any) => c.name === 'avatar'); | |
| const has2FA = columns.some((c: any) => c.name === 'two_factor_enabled'); | |
| const hasAlert = columns.some((c: any) => c.name === 'login_alert_enabled'); | |
| if (!hasAvatar) { | |
| database.exec("ALTER TABLE users ADD COLUMN avatar TEXT"); | |
| console.log('[Database] users 表已增加 avatar 列'); | |
| } | |
| if (!has2FA) { | |
| database.exec("ALTER TABLE users ADD COLUMN two_factor_enabled BOOLEAN DEFAULT 0"); | |
| console.log('[Database] users 表已增加 two_factor_enabled 列'); | |
| } | |
| if (!hasAlert) { | |
| database.exec("ALTER TABLE users ADD COLUMN login_alert_enabled BOOLEAN DEFAULT 1"); | |
| console.log('[Database] users 表已增加 login_alert_enabled 列'); | |
| } | |
| } catch (err) { | |
| console.error('[Database] 更新 users 表结构失败:', err); | |
| } | |
| // 2. 产品/计划表 (增加库存管理) | |
| database.exec(` | |
| CREATE TABLE IF NOT EXISTS products ( | |
| id TEXT PRIMARY KEY, | |
| name TEXT NOT NULL, | |
| price INTEGER NOT NULL, -- 以分为单位 | |
| type TEXT NOT NULL, -- 'subscription', 'one-time' | |
| stock INTEGER DEFAULT -1, -- -1 表示无限, 0+ 表示限购数量 | |
| description TEXT, | |
| created_at DATETIME DEFAULT CURRENT_TIMESTAMP | |
| ) | |
| `); | |
| // 3. 订单表 (支持支付闭环) | |
| database.exec(` | |
| CREATE TABLE IF NOT EXISTS orders ( | |
| id TEXT PRIMARY KEY, | |
| user_id TEXT NOT NULL, | |
| product_id TEXT NOT NULL, | |
| amount INTEGER NOT NULL, | |
| status TEXT DEFAULT 'pending', -- 'pending', 'paid', 'cancelled', 'refunded' | |
| payment_method TEXT, -- 'alipay', 'stripe', 'wechat' | |
| payment_id TEXT, -- 外部支付流水号 | |
| idempotency_key TEXT UNIQUE, -- 幂等键 | |
| created_at DATETIME DEFAULT CURRENT_TIMESTAMP, | |
| updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, | |
| FOREIGN KEY (user_id) REFERENCES users (id), | |
| FOREIGN KEY (product_id) REFERENCES products (id) | |
| ) | |
| `); | |
| // 4. 聊天会话表 (增加知识库关联) | |
| database.exec(` | |
| CREATE TABLE IF NOT EXISTS chat_sessions ( | |
| id TEXT PRIMARY KEY, | |
| user_id TEXT NOT NULL, | |
| title TEXT DEFAULT '新对话', | |
| knowledge_base_id TEXT, -- 关联的知识库 ID | |
| created_at DATETIME DEFAULT CURRENT_TIMESTAMP, | |
| FOREIGN KEY (user_id) REFERENCES users (id), | |
| FOREIGN KEY (knowledge_base_id) REFERENCES knowledge_bases (id) | |
| ) | |
| `); | |
| // 检查并增加 knowledge_base_id 列 (针对已存在的数据库) | |
| try { | |
| const columns = database.prepare("PRAGMA table_info(chat_sessions)").all(); | |
| const hasKbId = columns.some((c: any) => c.name === 'knowledge_base_id'); | |
| if (!hasKbId) { | |
| database.exec("ALTER TABLE chat_sessions ADD COLUMN knowledge_base_id TEXT"); | |
| console.log('[Database] chat_sessions 表已增加 knowledge_base_id 列'); | |
| } | |
| } catch (err) { | |
| console.error('[Database] 更新 chat_sessions 表结构失败:', err); | |
| } | |
| // 3. 聊天消息表 (增加向量存储) | |
| database.exec(` | |
| CREATE TABLE IF NOT EXISTS chat_messages ( | |
| id TEXT PRIMARY KEY, | |
| session_id TEXT NOT NULL, | |
| role TEXT NOT NULL, -- 'user' or 'assistant' | |
| content TEXT NOT NULL, | |
| embedding TEXT, -- 存储向量 JSON 字符串 | |
| created_at DATETIME DEFAULT CURRENT_TIMESTAMP, | |
| FOREIGN KEY (session_id) REFERENCES chat_sessions (id) | |
| ) | |
| `); | |
| // 5. 审计日志表 (System Engineering: Observability) | |
| database.exec(` | |
| CREATE TABLE IF NOT EXISTS audit_logs ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| user_id TEXT, | |
| action TEXT NOT NULL, -- 'LOGIN', 'CHAT', 'ORDER_CREATE', 'PAYMENT_COMPLETE' | |
| status TEXT, -- 'SUCCESS', 'FAILED', 'CIRCUIT_OPEN' | |
| payload TEXT, -- JSON data | |
| ip TEXT, | |
| created_at DATETIME DEFAULT CURRENT_TIMESTAMP | |
| ) | |
| `); | |
| // 6. 系统通知表 (Business: Message) | |
| database.exec(` | |
| CREATE TABLE IF NOT EXISTS notifications ( | |
| id TEXT PRIMARY KEY, | |
| user_id TEXT NOT NULL, | |
| title TEXT NOT NULL, | |
| content TEXT NOT NULL, | |
| type TEXT DEFAULT 'info', -- 'info', 'success', 'warning', 'error' | |
| is_read BOOLEAN DEFAULT 0, | |
| created_at DATETIME DEFAULT CURRENT_TIMESTAMP, | |
| FOREIGN KEY (user_id) REFERENCES users (id) | |
| ) | |
| `); | |
| // 7. 工作流表 | |
| database.exec(` | |
| CREATE TABLE IF NOT EXISTS workflows ( | |
| id TEXT PRIMARY KEY, | |
| user_id TEXT NOT NULL, | |
| name TEXT NOT NULL, | |
| data TEXT NOT NULL, -- JSON string of nodes and edges | |
| created_at DATETIME DEFAULT CURRENT_TIMESTAMP, | |
| updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, | |
| FOREIGN KEY (user_id) REFERENCES users (id) | |
| ) | |
| `); | |
| // 8. 知识库表 (System: RAG) | |
| database.exec(` | |
| CREATE TABLE IF NOT EXISTS knowledge_bases ( | |
| id TEXT PRIMARY KEY, | |
| user_id TEXT NOT NULL, | |
| name TEXT NOT NULL, | |
| description TEXT, | |
| status TEXT DEFAULT 'processing', -- 'processing', 'ready' | |
| created_at DATETIME DEFAULT CURRENT_TIMESTAMP, | |
| FOREIGN KEY (user_id) REFERENCES users (id) | |
| ) | |
| `); | |
| // 9. 知识切片表 (System: Vector Store) | |
| database.exec(` | |
| CREATE TABLE IF NOT EXISTS knowledge_chunks ( | |
| id TEXT PRIMARY KEY, | |
| kb_id TEXT NOT NULL, | |
| content TEXT NOT NULL, | |
| embedding TEXT, -- JSON string of vector | |
| metadata TEXT, -- JSON string of extra info | |
| created_at DATETIME DEFAULT CURRENT_TIMESTAMP, | |
| FOREIGN KEY (kb_id) REFERENCES knowledge_bases (id) | |
| ) | |
| `); | |
| // 10. API Keys 表 (System: OpenAPI) | |
| database.exec(` | |
| CREATE TABLE IF NOT EXISTS api_keys ( | |
| id TEXT PRIMARY KEY, | |
| user_id TEXT NOT NULL, | |
| key_name TEXT NOT NULL, | |
| key_secret TEXT UNIQUE NOT NULL, | |
| status TEXT DEFAULT 'active', -- 'active', 'inactive' | |
| scopes TEXT DEFAULT '["all"]', -- JSON array of scopes | |
| last_used DATETIME, | |
| created_at DATETIME DEFAULT CURRENT_TIMESTAMP, | |
| FOREIGN KEY (user_id) REFERENCES users (id) | |
| ) | |
| `); | |
| // 检查并增加 status 和 scopes 列 (针对已存在的数据库) | |
| try { | |
| const columns = database.prepare("PRAGMA table_info(api_keys)").all(); | |
| const hasStatus = columns.some((c: any) => c.name === 'status'); | |
| const hasScopes = columns.some((c: any) => c.name === 'scopes'); | |
| if (!hasStatus) { | |
| database.exec("ALTER TABLE api_keys ADD COLUMN status TEXT DEFAULT 'active'"); | |
| console.log('[Database] api_keys 表已增加 status 列'); | |
| } | |
| if (!hasScopes) { | |
| database.exec("ALTER TABLE api_keys ADD COLUMN scopes TEXT DEFAULT '[\"all\"]'"); | |
| console.log('[Database] api_keys 表已增加 scopes 列'); | |
| } | |
| } catch (err) { | |
| console.error('[Database] 更新 api_keys 表结构失败:', err); | |
| } | |
| // 11. Hugging Face Projects Cache | |
| database.exec(` | |
| CREATE TABLE IF NOT EXISTS hf_projects ( | |
| id TEXT PRIMARY KEY, | |
| full_name TEXT NOT NULL, | |
| name TEXT NOT NULL, | |
| title TEXT, | |
| description TEXT, | |
| url TEXT, | |
| iframe_url TEXT, | |
| type TEXT, | |
| created_at_hf DATETIME, | |
| likes INTEGER, | |
| sdk TEXT, | |
| synced_at DATETIME DEFAULT CURRENT_TIMESTAMP | |
| ) | |
| `); | |
| console.log('[Database] SQLite 已就绪:', dbPath); | |
| // 初始化基础产品数据 | |
| const products = [ | |
| { id: 'plan_pro', name: '专业版', price: 9900, type: 'subscription', stock: -1, description: '无限 AI 消息 + 高级工作流' }, | |
| { id: 'plan_ent', name: '企业版', price: 29900, type: 'subscription', stock: -1, description: '全方位技术支持 + 专属域名' }, | |
| { id: 'limited_offer', name: '限时秒杀 (Pro 体验卡)', price: 100, type: 'one-time', stock: 10, description: '限量 10 份,仅需 1 元' } | |
| ]; | |
| const insertProduct = database.prepare(` | |
| INSERT OR IGNORE INTO products (id, name, price, type, stock, description) | |
| VALUES (?, ?, ?, ?, ?, ?) | |
| `); | |
| products.forEach(p => { | |
| insertProduct.run(p.id, p.name, p.price, p.type, p.stock, p.description); | |
| }); | |
| // 初始化默认管理员 (如果不存在) | |
| const adminEmail = 'admin@codex.ai'; | |
| const existingAdmin = database.prepare('SELECT id FROM users WHERE email = ?').get(adminEmail); | |
| if (!existingAdmin) { | |
| const hashedPassword = bcrypt.hashSync('admin123', 10); | |
| database.prepare('INSERT INTO users (id, email, password, name, role, plan) VALUES (?, ?, ?, ?, ?, ?)') | |
| .run('ADMIN_ROOT', adminEmail, hashedPassword, 'System Admin', 'admin', 'enterprise'); | |
| console.log('[Database] 默认管理员已创建: admin@codex.ai / admin123'); | |
| } | |
| }; | |
| /** | |
| * 数据库代理对象,保持与现有代码的兼容性 | |
| * 在调用 await initDB() 之后,可以直接使用 import db from './lib/db.js' | |
| */ | |
| const dbProxy = new Proxy({} as any, { | |
| get(target, prop) { | |
| if (!db) { | |
| throw new Error('[Database] 数据库未初始化。请确保在应用启动时先调用了 await initDB()'); | |
| } | |
| const value = db[prop]; | |
| if (typeof value === 'function') { | |
| return value.bind(db); | |
| } | |
| return value; | |
| } | |
| }); | |
| export { getDB, initDB }; | |
| export default dbProxy; | |