494 lines
19 KiB
JavaScript
494 lines
19 KiB
JavaScript
// config/database.js
|
|
const Database = require('better-sqlite3');
|
|
|
|
class AppDatabase {
|
|
constructor(dbPath) {
|
|
if (!dbPath) throw new Error("Database path must be provided.");
|
|
try {
|
|
this.db = new Database(dbPath);
|
|
console.log(`成功连接到数据库: ${dbPath}`);
|
|
this.initTables();
|
|
} catch (err) {
|
|
console.error('数据库连接错误:', err.message);
|
|
}
|
|
}
|
|
|
|
run(sql, params = []) { return this.db.prepare(sql).run(params); }
|
|
get(sql, params = []) { return this.db.prepare(sql).get(params); }
|
|
all(sql, params = []) { return this.db.prepare(sql).all(params); }
|
|
|
|
initTables() {
|
|
// 日志表
|
|
this.run(`CREATE TABLE IF NOT EXISTS logs (id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp TEXT NOT NULL, action TEXT NOT NULL, category TEXT)`);
|
|
// 基础配置表
|
|
this.run(`CREATE TABLE IF NOT EXISTS app_config (key TEXT PRIMARY KEY, value TEXT)`);
|
|
// 流量统计表
|
|
this.run(`CREATE TABLE IF NOT EXISTS traffic_log (log_date TEXT PRIMARY KEY, bytes_used INTEGER NOT NULL)`);
|
|
|
|
// 远程配置存储表
|
|
this.run(`CREATE TABLE IF NOT EXISTS remote_configs (id TEXT PRIMARY KEY, data TEXT, updated_at TEXT)`);
|
|
|
|
// [重构] 接口健康度校验表
|
|
this.run(`CREATE TABLE IF NOT EXISTS api_health (
|
|
id TEXT PRIMARY KEY,
|
|
api_id TEXT NOT NULL,
|
|
status TEXT NOT NULL,
|
|
response_time INTEGER,
|
|
error_message TEXT,
|
|
checked_at TEXT NOT NULL,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
)`);
|
|
|
|
// [重构] 接口预留位置表
|
|
this.run(`CREATE TABLE IF NOT EXISTS api_reservations (
|
|
id TEXT PRIMARY KEY,
|
|
api_id TEXT NOT NULL UNIQUE,
|
|
category TEXT,
|
|
description TEXT,
|
|
priority INTEGER DEFAULT 0,
|
|
enabled INTEGER DEFAULT 1,
|
|
config TEXT,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
)`);
|
|
|
|
// [重构] 接口调用记录表
|
|
this.run(`CREATE TABLE IF NOT EXISTS api_call_logs (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
api_id TEXT NOT NULL,
|
|
method TEXT,
|
|
url TEXT,
|
|
status_code INTEGER,
|
|
response_time INTEGER,
|
|
success INTEGER DEFAULT 0,
|
|
error_message TEXT,
|
|
called_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
)`);
|
|
|
|
// [重构] 工具健康检查结果表(5天强制检查)
|
|
this.run(`CREATE TABLE IF NOT EXISTS tool_health_check_results (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
tool_id TEXT NOT NULL,
|
|
check_date TEXT NOT NULL,
|
|
status TEXT NOT NULL,
|
|
response_time INTEGER,
|
|
error_message TEXT,
|
|
http_status INTEGER,
|
|
attempt INTEGER,
|
|
reason TEXT,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
)`);
|
|
|
|
// [重构] 工具健康检查汇总表(用于5天强制检查判断)
|
|
this.run(`CREATE TABLE IF NOT EXISTS tool_health_check_summary (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
check_date TEXT NOT NULL UNIQUE,
|
|
total_tools INTEGER NOT NULL,
|
|
success_count INTEGER NOT NULL,
|
|
failed_count INTEGER NOT NULL,
|
|
check_duration INTEGER,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
)`);
|
|
|
|
// 创建索引以提高查询性能
|
|
this.run(`CREATE INDEX IF NOT EXISTS idx_api_health_api_id ON api_health(api_id)`);
|
|
this.run(`CREATE INDEX IF NOT EXISTS idx_api_health_checked_at ON api_health(checked_at)`);
|
|
this.run(`CREATE INDEX IF NOT EXISTS idx_api_call_logs_api_id ON api_call_logs(api_id)`);
|
|
this.run(`CREATE INDEX IF NOT EXISTS idx_api_call_logs_called_at ON api_call_logs(called_at)`);
|
|
this.run(`CREATE INDEX IF NOT EXISTS idx_tool_health_check_results_tool_id ON tool_health_check_results(tool_id)`);
|
|
this.run(`CREATE INDEX IF NOT EXISTS idx_tool_health_check_results_check_date ON tool_health_check_results(check_date)`);
|
|
this.run(`CREATE INDEX IF NOT EXISTS idx_tool_health_check_summary_check_date ON tool_health_check_summary(check_date)`);
|
|
|
|
// 初始化默认配置
|
|
const initialConfigs = {
|
|
'theme': 'dark', 'global_volume': '0.5', 'total_downloaded_bytes': '0',
|
|
'background_image': '', 'background_opacity': '1.0', 'card_opacity': '0.7',
|
|
'window_width': '1200', 'window_height': '800', 'window_x': 'null', 'window_y': 'null',
|
|
'secret_code_attempts': '0', 'secret_code_lockout_until': '0',
|
|
'app_version': '0.0.0',
|
|
// [新增] 用户协议状态 (默认为 false)
|
|
'user_agreement_accepted': 'false'
|
|
};
|
|
|
|
const stmt = this.db.prepare(`INSERT OR IGNORE INTO app_config (key, value) VALUES (?, ?)`);
|
|
this.db.transaction(() => {
|
|
for (const [key, value] of Object.entries(initialConfigs)) {
|
|
stmt.run(key, value);
|
|
}
|
|
})();
|
|
}
|
|
|
|
// --- 远程配置操作 ---
|
|
saveRemoteConfig(id, data) {
|
|
const jsonStr = JSON.stringify(data);
|
|
const time = new Date().toISOString();
|
|
const sql = `INSERT OR REPLACE INTO remote_configs (id, data, updated_at) VALUES (?, ?, ?)`;
|
|
return this.run(sql, [id, jsonStr, time]);
|
|
}
|
|
|
|
getRemoteConfig(id) {
|
|
const result = this.get(`SELECT data FROM remote_configs WHERE id = ?`, [id]);
|
|
return result ? JSON.parse(result.data) : null;
|
|
}
|
|
|
|
// --- 版本升级检查 ---
|
|
checkAndRecordVersion(currentVersion) {
|
|
const oldVersion = this.getConfig('app_version') || '0.0.0';
|
|
|
|
if (oldVersion !== currentVersion) {
|
|
this.setConfig('app_version', currentVersion);
|
|
return { isUpgrade: true, oldVersion: oldVersion };
|
|
}
|
|
return { isUpgrade: false, oldVersion: oldVersion };
|
|
}
|
|
|
|
logAction(logData) {
|
|
const { timestamp, action, category = 'general' } = logData;
|
|
this.run(`INSERT INTO logs (timestamp, action, category) VALUES (?, ?, ?)`, [timestamp, action, category]);
|
|
}
|
|
|
|
getLogs(filterDate = null, limit = 500) {
|
|
if (filterDate) return this.all(`SELECT * FROM logs WHERE date(timestamp) = ? ORDER BY timestamp DESC`, [filterDate]);
|
|
return this.all(`SELECT * FROM logs ORDER BY timestamp DESC LIMIT ?`, [limit]);
|
|
}
|
|
|
|
clearLogs() { return this.run(`DELETE FROM logs`); }
|
|
|
|
getConfig(key) {
|
|
const result = this.get(`SELECT value FROM app_config WHERE key = ?`, [key]);
|
|
return result ? result.value : null;
|
|
}
|
|
|
|
setConfig(key, value) {
|
|
return this.run(`INSERT OR REPLACE INTO app_config (key, value) VALUES (?, ?)`, [key, value]);
|
|
}
|
|
|
|
getTrafficStats() {
|
|
const result = this.get(`SELECT value FROM app_config WHERE key = 'total_downloaded_bytes'`);
|
|
return result ? parseInt(result.value, 10) : 0;
|
|
}
|
|
|
|
getTrafficHistory() {
|
|
return this.all(`SELECT log_date, bytes_used FROM traffic_log ORDER BY log_date ASC`);
|
|
}
|
|
|
|
addTraffic(bytes) {
|
|
if (typeof bytes !== 'number' || bytes <= 0) return;
|
|
const currentTotalBytes = this.getTrafficStats();
|
|
this.setConfig('total_downloaded_bytes', (currentTotalBytes + bytes).toString());
|
|
const today = new Date().toISOString().split('T')[0];
|
|
this.run(`INSERT INTO traffic_log (log_date, bytes_used) VALUES (?, ?) ON CONFLICT(log_date) DO UPDATE SET bytes_used = bytes_used + excluded.bytes_used;`, [today, bytes]);
|
|
}
|
|
|
|
// --- [重构] 接口健康度校验操作 ---
|
|
|
|
/**
|
|
* 记录接口健康度
|
|
* @param {Object} healthData - 健康度数据
|
|
*/
|
|
recordApiHealth(healthData) {
|
|
const { apiId, status, responseTime, errorMessage } = healthData;
|
|
const checkedAt = new Date().toISOString();
|
|
|
|
// 删除旧记录(只保留最近100条)
|
|
this.run(`DELETE FROM api_health WHERE api_id = ? AND id NOT IN (
|
|
SELECT id FROM api_health WHERE api_id = ? ORDER BY checked_at DESC LIMIT 99
|
|
)`, [apiId, apiId]);
|
|
|
|
// 插入新记录
|
|
const id = `${apiId}_${Date.now()}`;
|
|
this.run(`INSERT INTO api_health (id, api_id, status, response_time, error_message, checked_at)
|
|
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
[id, apiId, status, responseTime || null, errorMessage || null, checkedAt]);
|
|
}
|
|
|
|
/**
|
|
* 获取接口健康度历史
|
|
* @param {string} apiId - 接口ID
|
|
* @param {number} limit - 限制数量
|
|
* @returns {Array} 健康度历史记录
|
|
*/
|
|
getApiHealthHistory(apiId, limit = 100) {
|
|
return this.all(`SELECT * FROM api_health WHERE api_id = ? ORDER BY checked_at DESC LIMIT ?`, [apiId, limit]);
|
|
}
|
|
|
|
/**
|
|
* 获取接口最新健康度
|
|
* @param {string} apiId - 接口ID
|
|
* @returns {Object|null} 最新健康度记录
|
|
*/
|
|
getLatestApiHealth(apiId) {
|
|
return this.get(`SELECT * FROM api_health WHERE api_id = ? ORDER BY checked_at DESC LIMIT 1`, [apiId]);
|
|
}
|
|
|
|
// --- [重构] 接口预留位置操作 ---
|
|
|
|
/**
|
|
* 注册接口预留位置
|
|
* @param {Object} reservation - 预留位置数据
|
|
*/
|
|
registerApiReservation(reservation) {
|
|
const { apiId, category, description, priority, enabled, config } = reservation;
|
|
const updatedAt = new Date().toISOString();
|
|
|
|
this.run(`INSERT OR REPLACE INTO api_reservations
|
|
(id, api_id, category, description, priority, enabled, config, updated_at)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
[apiId, apiId, category || null, description || null, priority || 0, enabled ? 1 : 0,
|
|
config ? JSON.stringify(config) : null, updatedAt]);
|
|
}
|
|
|
|
/**
|
|
* 获取接口预留位置
|
|
* @param {string} apiId - 接口ID
|
|
* @returns {Object|null} 预留位置数据
|
|
*/
|
|
getApiReservation(apiId) {
|
|
const result = this.get(`SELECT * FROM api_reservations WHERE api_id = ?`, [apiId]);
|
|
if (result && result.config) {
|
|
try {
|
|
result.config = JSON.parse(result.config);
|
|
} catch (e) {
|
|
result.config = null;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* 获取所有接口预留位置
|
|
* @param {string} category - 分类过滤(可选)
|
|
* @returns {Array} 预留位置列表
|
|
*/
|
|
getAllApiReservations(category = null) {
|
|
if (category) {
|
|
return this.all(`SELECT * FROM api_reservations WHERE category = ? ORDER BY priority DESC, created_at ASC`, [category]);
|
|
}
|
|
return this.all(`SELECT * FROM api_reservations ORDER BY priority DESC, created_at ASC`);
|
|
}
|
|
|
|
/**
|
|
* 更新接口预留位置
|
|
* @param {string} apiId - 接口ID
|
|
* @param {Object} updates - 更新数据
|
|
*/
|
|
updateApiReservation(apiId, updates) {
|
|
const fields = [];
|
|
const values = [];
|
|
|
|
if (updates.category !== undefined) {
|
|
fields.push('category = ?');
|
|
values.push(updates.category);
|
|
}
|
|
if (updates.description !== undefined) {
|
|
fields.push('description = ?');
|
|
values.push(updates.description);
|
|
}
|
|
if (updates.priority !== undefined) {
|
|
fields.push('priority = ?');
|
|
values.push(updates.priority);
|
|
}
|
|
if (updates.enabled !== undefined) {
|
|
fields.push('enabled = ?');
|
|
values.push(updates.enabled ? 1 : 0);
|
|
}
|
|
if (updates.config !== undefined) {
|
|
fields.push('config = ?');
|
|
values.push(updates.config ? JSON.stringify(updates.config) : null);
|
|
}
|
|
|
|
if (fields.length === 0) return;
|
|
|
|
fields.push('updated_at = ?');
|
|
values.push(new Date().toISOString());
|
|
values.push(apiId);
|
|
|
|
this.run(`UPDATE api_reservations SET ${fields.join(', ')} WHERE api_id = ?`, values);
|
|
}
|
|
|
|
/**
|
|
* 删除接口预留位置
|
|
* @param {string} apiId - 接口ID
|
|
*/
|
|
deleteApiReservation(apiId) {
|
|
this.run(`DELETE FROM api_reservations WHERE api_id = ?`, [apiId]);
|
|
}
|
|
|
|
// --- [重构] 接口调用记录操作 ---
|
|
|
|
/**
|
|
* 记录接口调用
|
|
* @param {Object} callData - 调用数据
|
|
*/
|
|
recordApiCall(callData) {
|
|
const { apiId, method, url, statusCode, responseTime, success, errorMessage } = callData;
|
|
|
|
this.run(`INSERT INTO api_call_logs
|
|
(api_id, method, url, status_code, response_time, success, error_message)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
[apiId, method || null, url || null, statusCode || null, responseTime || null,
|
|
success ? 1 : 0, errorMessage || null]);
|
|
|
|
// 自动清理旧记录(只保留最近10000条)
|
|
this.run(`DELETE FROM api_call_logs WHERE id NOT IN (
|
|
SELECT id FROM api_call_logs ORDER BY called_at DESC LIMIT 10000
|
|
)`);
|
|
}
|
|
|
|
/**
|
|
* 获取接口调用统计
|
|
* @param {string} apiId - 接口ID
|
|
* @param {string} startDate - 开始日期(可选)
|
|
* @param {string} endDate - 结束日期(可选)
|
|
* @returns {Object} 调用统计
|
|
*/
|
|
getApiCallStats(apiId, startDate = null, endDate = null) {
|
|
let query = `SELECT
|
|
COUNT(*) as total_calls,
|
|
SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) as success_calls,
|
|
SUM(CASE WHEN success = 0 THEN 1 ELSE 0 END) as failed_calls,
|
|
AVG(response_time) as avg_response_time,
|
|
MIN(response_time) as min_response_time,
|
|
MAX(response_time) as max_response_time
|
|
FROM api_call_logs WHERE api_id = ?`;
|
|
|
|
const params = [apiId];
|
|
|
|
if (startDate) {
|
|
query += ` AND called_at >= ?`;
|
|
params.push(startDate);
|
|
}
|
|
if (endDate) {
|
|
query += ` AND called_at <= ?`;
|
|
params.push(endDate);
|
|
}
|
|
|
|
return this.get(query, params);
|
|
}
|
|
|
|
/**
|
|
* 获取接口调用历史
|
|
* @param {string} apiId - 接口ID
|
|
* @param {number} limit - 限制数量
|
|
* @returns {Array} 调用历史记录
|
|
*/
|
|
getApiCallHistory(apiId, limit = 100) {
|
|
return this.all(`SELECT * FROM api_call_logs WHERE api_id = ? ORDER BY called_at DESC LIMIT ?`, [apiId, limit]);
|
|
}
|
|
|
|
// --- [重构] 工具健康检查结果操作 ---
|
|
|
|
/**
|
|
* 记录工具健康检查结果
|
|
* @param {Object} resultData - 检查结果数据
|
|
*/
|
|
recordToolHealthCheckResult(resultData) {
|
|
const { toolId, checkDate, status, responseTime, errorMessage, httpStatus, attempt, reason } = resultData;
|
|
const createdAt = new Date().toISOString();
|
|
|
|
this.run(`INSERT INTO tool_health_check_results
|
|
(tool_id, check_date, status, response_time, error_message, http_status, attempt, reason, created_at)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
[toolId, checkDate, status, responseTime || null, errorMessage || null, httpStatus || null, attempt || 1, reason || null, createdAt]);
|
|
}
|
|
|
|
/**
|
|
* 获取工具的最新健康检查结果
|
|
* @param {string} toolId - 工具ID
|
|
* @returns {Object|null} 最新检查结果
|
|
*/
|
|
getLatestToolHealthCheckResult(toolId) {
|
|
return this.get(`SELECT * FROM tool_health_check_results WHERE tool_id = ? ORDER BY check_date DESC, created_at DESC LIMIT 1`, [toolId]);
|
|
}
|
|
|
|
/**
|
|
* 获取工具的健康检查历史
|
|
* @param {string} toolId - 工具ID
|
|
* @param {number} limit - 限制数量
|
|
* @returns {Array} 检查历史记录
|
|
*/
|
|
getToolHealthCheckHistory(toolId, limit = 100) {
|
|
return this.all(`SELECT * FROM tool_health_check_results WHERE tool_id = ? ORDER BY check_date DESC, created_at DESC LIMIT ?`, [toolId, limit]);
|
|
}
|
|
|
|
/**
|
|
* 获取指定日期的所有工具健康检查结果
|
|
* @param {string} checkDate - 检查日期 (YYYY-MM-DD)
|
|
* @returns {Array} 检查结果列表
|
|
*/
|
|
getToolHealthCheckResultsByDate(checkDate) {
|
|
return this.all(`SELECT * FROM tool_health_check_results WHERE check_date = ? ORDER BY tool_id`, [checkDate]);
|
|
}
|
|
|
|
/**
|
|
* 获取最后一次健康检查的日期
|
|
* @returns {string|null} 最后一次检查的日期
|
|
*/
|
|
getLastHealthCheckDate() {
|
|
const result = this.get(`SELECT check_date FROM tool_health_check_summary ORDER BY check_date DESC LIMIT 1`);
|
|
return result ? result.check_date : null;
|
|
}
|
|
|
|
/**
|
|
* 检查距离上次检查是否超过指定天数
|
|
* @param {number} days - 天数(默认5天)
|
|
* @returns {boolean} true 表示超过指定天数
|
|
*/
|
|
shouldForceHealthCheck(days = 5) {
|
|
const lastDate = this.getLastHealthCheckDate();
|
|
if (!lastDate) {
|
|
return true; // 从未检查过
|
|
}
|
|
|
|
const lastCheck = new Date(lastDate);
|
|
const now = new Date();
|
|
const daysDiff = Math.floor((now - lastCheck) / (1000 * 60 * 60 * 24));
|
|
|
|
return daysDiff >= days;
|
|
}
|
|
|
|
/**
|
|
* 保存工具健康检查汇总
|
|
* @param {Object} summaryData - 汇总数据
|
|
*/
|
|
saveToolHealthCheckSummary(summaryData) {
|
|
const { checkDate, totalTools, successCount, failedCount, checkDuration } = summaryData;
|
|
|
|
// 删除同一天的旧记录
|
|
this.run(`DELETE FROM tool_health_check_summary WHERE check_date = ?`, [checkDate]);
|
|
|
|
// 插入新记录
|
|
this.run(`INSERT INTO tool_health_check_summary
|
|
(check_date, total_tools, success_count, failed_count, check_duration, created_at)
|
|
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
[checkDate, totalTools, successCount, failedCount, checkDuration || null, new Date().toISOString()]);
|
|
}
|
|
|
|
/**
|
|
* 获取工具健康检查汇总
|
|
* @param {string} checkDate - 检查日期(可选,不提供则返回最新的)
|
|
* @returns {Object|null} 汇总数据
|
|
*/
|
|
getToolHealthCheckSummary(checkDate = null) {
|
|
if (checkDate) {
|
|
return this.get(`SELECT * FROM tool_health_check_summary WHERE check_date = ?`, [checkDate]);
|
|
} else {
|
|
return this.get(`SELECT * FROM tool_health_check_summary ORDER BY check_date DESC LIMIT 1`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取所有健康检查汇总(按日期排序)
|
|
* @param {number} limit - 限制数量
|
|
* @returns {Array} 汇总列表
|
|
*/
|
|
getAllToolHealthCheckSummaries(limit = 100) {
|
|
return this.all(`SELECT * FROM tool_health_check_summary ORDER BY check_date DESC LIMIT ?`, [limit]);
|
|
}
|
|
|
|
close() {
|
|
if (this.db) { this.db.close(); console.log('数据库连接已关闭。'); }
|
|
}
|
|
}
|
|
|
|
module.exports = AppDatabase; |