// src/lib/monitor/settings-manager.ts import { getDb } from '@/lib/db' import crypto from 'crypto' import type { MonitorConfig } from './types' const ALGORITHM = 'aes-256-cbc' function getEncryptionKey(): string { const db = getDb() const row = db.prepare("SELECT value FROM settings WHERE key = 'monitor.encryption_key'").get() as { value: string } | undefined if (row) return row.value // 首次启动:生成密钥 const newKey = crypto.randomBytes(32).toString('hex') db.prepare("INSERT OR IGNORE INTO settings (key, value, category) VALUES ('monitor.encryption_key', ?, 'security')").run(newKey) return newKey } export function encryptValue(plaintext: string): string { if (!plaintext) return '' const key = Buffer.from(getEncryptionKey(), 'hex') const iv = crypto.randomBytes(16) const cipher = crypto.createCipheriv(ALGORITHM, key, iv) let encrypted = cipher.update(plaintext, 'utf8', 'hex') encrypted += cipher.final('hex') return iv.toString('hex') + ':' + encrypted } export function decryptValue(ciphertext: string): string { if (!ciphertext) return '' try { const key = Buffer.from(getEncryptionKey(), 'hex') const [ivHex, encrypted] = ciphertext.split(':') const iv = Buffer.from(ivHex, 'hex') const decipher = crypto.createDecipheriv(ALGORITHM, key, iv) let decrypted = decipher.update(encrypted, 'hex', 'utf8') decrypted += decipher.final('utf8') return decrypted } catch { console.error('[Settings] 密码解密失败,可能密钥不匹配') return '' } } export function getSetting(key: string): string | null { const db = getDb() const row = db.prepare('SELECT value FROM settings WHERE key = ?').get(key) as { value: string } | undefined return row?.value ?? null } export function setSetting(key: string, value: string, category = 'general'): void { const db = getDb() db.prepare( "INSERT INTO settings (key, value, category, updated_at) VALUES (?, ?, ?, datetime('now', '+8 hours')) ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at" ).run(key, value, category) } const DEFAULT_CONFIG: MonitorConfig = { monitor: { enabled: false, interval_seconds: 60, push_delay_ms: 2000, silent_start_hour: 0, silent_end_hour: 7 }, mail: { address: 'gxp@qx002575.com', imap_server: 'imaphz.qiye.163.com', imap_port: 993, smtp_server: 'smtphz.qiye.163.com', smtp_port: 465, password: '' }, filter: { subject_keywords: ['服务器故障单'], sender_email: 'tencent_dcops@tencent.com', oem_repair_keywords: { base_keyword: '服务器故障单', type_keyword: 'OEM维修', exclude_keyword: 'OEM诊断' }, oem_diag_keywords: { base_keyword: '服务器故障单', type_keyword: 'OEM诊断', exclude_keyword: 'OEM维修' }, }, wechat: { webhook_url: '' }, } export function getMonitorConfig(): MonitorConfig { const config = JSON.parse(JSON.stringify(DEFAULT_CONFIG)) const mappings: Record void> = { 'monitor.enabled': (v) => { config.monitor.enabled = v === 'true' }, 'monitor.interval_seconds': (v) => { config.monitor.interval_seconds = parseInt(v) || 60 }, 'monitor.push_delay_ms': (v) => { config.monitor.push_delay_ms = parseInt(v) || 2000 }, 'monitor.silent_start_hour': (v) => { config.monitor.silent_start_hour = parseInt(v) || 0 }, 'monitor.silent_end_hour': (v) => { config.monitor.silent_end_hour = parseInt(v) || 7 }, 'mail.address': (v) => { config.mail.address = v }, 'mail.imap_server': (v) => { config.mail.imap_server = v }, 'mail.imap_port': (v) => { config.mail.imap_port = parseInt(v) || 993 }, 'mail.smtp_server': (v) => { config.mail.smtp_server = v }, 'mail.smtp_port': (v) => { config.mail.smtp_port = parseInt(v) || 465 }, 'mail.password': (v) => { config.mail.password = v ? decryptValue(v) : '' }, 'filter.subject_keywords': (v) => { try { config.filter.subject_keywords = JSON.parse(v) } catch {} }, 'filter.sender_email': (v) => { config.filter.sender_email = v }, 'filter.oem_repair_keywords': (v) => { try { config.filter.oem_repair_keywords = JSON.parse(v) } catch {} }, 'filter.oem_diag_keywords': (v) => { try { config.filter.oem_diag_keywords = JSON.parse(v) } catch {} }, 'wechat.webhook_url': (v) => { config.wechat.webhook_url = v }, } const db = getDb() const rows = db.prepare("SELECT key, value FROM settings WHERE key LIKE 'monitor.%' OR key LIKE 'mail.%' OR key LIKE 'filter.%' OR key LIKE 'wechat.%'").all() as { key: string; value: string }[] for (const row of rows) { mappings[row.key]?.(row.value) } return config } export function updateMonitorConfig(updates: Record): void { const encryptedFields = new Set(['mail.password']) for (const [key, value] of Object.entries(updates)) { if (key === 'monitor.encryption_key') continue // 不允许修改密钥 let stringValue: string if (typeof value === 'boolean') stringValue = String(value) else if (typeof value === 'number') stringValue = String(value) else if (typeof value === 'object') stringValue = JSON.stringify(value) else stringValue = String(value ?? '') if (encryptedFields.has(key) && stringValue && !stringValue.match(/^[0-9a-f]{32}:/)) { stringValue = encryptValue(stringValue) } const category = key.split('.')[0] setSetting(key, stringValue, category) } } export function getMaskedConfig(): MonitorConfig { const config = getMonitorConfig() if (config.mail.password) config.mail.password = '••••••••' if (config.wechat.webhook_url) { const keyMatch = config.wechat.webhook_url.match(/key=([0-9a-f-]+)/) if (keyMatch) config.wechat.webhook_url = config.wechat.webhook_url.replace(keyMatch[1], '••••••••') } return config }