优化数据库自检能力

This commit is contained in:
CN-JS-HuiBai
2026-04-09 13:55:24 +08:00
parent 3963d137de
commit 9854f478c0
3 changed files with 238 additions and 1 deletions

225
server/db-schema-check.js Normal file
View File

@@ -0,0 +1,225 @@
/**
* Database schema check
* Ensures required tables and columns exist at startup.
*/
require('dotenv').config();
const db = require('./db');
const path = require('path');
const fs = require('fs');
const SCHEMA = {
users: {
createSql: `
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(255) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
salt VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
`,
columns: []
},
prometheus_sources: {
createSql: `
CREATE TABLE IF NOT EXISTS prometheus_sources (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
url VARCHAR(500) NOT NULL,
description TEXT,
is_server_source TINYINT(1) DEFAULT 1,
type VARCHAR(50) DEFAULT 'prometheus',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
`,
columns: [
{
name: 'is_server_source',
sql: "ALTER TABLE prometheus_sources ADD COLUMN is_server_source TINYINT(1) DEFAULT 1 AFTER description"
},
{
name: 'type',
sql: "ALTER TABLE prometheus_sources ADD COLUMN type VARCHAR(50) DEFAULT 'prometheus' AFTER is_server_source"
}
]
},
site_settings: {
createSql: `
CREATE TABLE IF NOT EXISTS site_settings (
id INT PRIMARY KEY DEFAULT 1,
page_name VARCHAR(255) DEFAULT 'Data Visualization Display Wall',
show_page_name TINYINT(1) DEFAULT 1,
title VARCHAR(255) DEFAULT 'Data Visualization Display Wall',
logo_url TEXT,
logo_url_dark TEXT,
favicon_url TEXT,
default_theme VARCHAR(20) DEFAULT 'dark',
show_95_bandwidth TINYINT(1) DEFAULT 0,
p95_type VARCHAR(20) DEFAULT 'tx',
require_login_for_server_details TINYINT(1) DEFAULT 1,
blackbox_source_id INT,
latency_source VARCHAR(100),
latency_dest VARCHAR(100),
latency_target VARCHAR(255),
icp_filing VARCHAR(255),
ps_filing VARCHAR(255),
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
`,
seedSql: `
INSERT IGNORE INTO site_settings (
id, page_name, show_page_name, title, default_theme, show_95_bandwidth, p95_type, require_login_for_server_details
) VALUES (
1, 'Data Visualization Display Wall', 1, 'Data Visualization Display Wall', 'dark', 0, 'tx', 1
)
`,
columns: [
{
name: 'show_page_name',
sql: "ALTER TABLE site_settings ADD COLUMN show_page_name TINYINT(1) DEFAULT 1 AFTER page_name"
},
{
name: 'logo_url_dark',
sql: "ALTER TABLE site_settings ADD COLUMN logo_url_dark TEXT AFTER logo_url"
},
{
name: 'favicon_url',
sql: "ALTER TABLE site_settings ADD COLUMN favicon_url TEXT AFTER logo_url_dark"
},
{
name: 'show_95_bandwidth',
sql: "ALTER TABLE site_settings ADD COLUMN show_95_bandwidth TINYINT(1) DEFAULT 0 AFTER default_theme"
},
{
name: 'p95_type',
sql: "ALTER TABLE site_settings ADD COLUMN p95_type VARCHAR(20) DEFAULT 'tx' AFTER show_95_bandwidth"
},
{
name: 'require_login_for_server_details',
sql: "ALTER TABLE site_settings ADD COLUMN require_login_for_server_details TINYINT(1) DEFAULT 1 AFTER p95_type"
},
{
name: 'blackbox_source_id',
sql: "ALTER TABLE site_settings ADD COLUMN blackbox_source_id INT AFTER require_login_for_server_details"
},
{
name: 'latency_source',
sql: "ALTER TABLE site_settings ADD COLUMN latency_source VARCHAR(100) AFTER blackbox_source_id"
},
{
name: 'latency_dest',
sql: "ALTER TABLE site_settings ADD COLUMN latency_dest VARCHAR(100) AFTER latency_source"
},
{
name: 'latency_target',
sql: "ALTER TABLE site_settings ADD COLUMN latency_target VARCHAR(255) AFTER latency_dest"
},
{
name: 'icp_filing',
sql: "ALTER TABLE site_settings ADD COLUMN icp_filing VARCHAR(255) AFTER latency_target"
},
{
name: 'ps_filing',
sql: "ALTER TABLE site_settings ADD COLUMN ps_filing VARCHAR(255) AFTER icp_filing"
}
]
},
traffic_stats: {
createSql: `
CREATE TABLE IF NOT EXISTS traffic_stats (
id INT AUTO_INCREMENT PRIMARY KEY,
rx_bytes BIGINT UNSIGNED DEFAULT 0,
tx_bytes BIGINT UNSIGNED DEFAULT 0,
rx_bandwidth DOUBLE DEFAULT 0,
tx_bandwidth DOUBLE DEFAULT 0,
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE INDEX (timestamp)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
`,
columns: []
},
server_locations: {
createSql: `
CREATE TABLE IF NOT EXISTS server_locations (
id INT AUTO_INCREMENT PRIMARY KEY,
ip VARCHAR(255) NOT NULL UNIQUE,
country CHAR(2),
country_name VARCHAR(100),
region VARCHAR(100),
city VARCHAR(100),
latitude DOUBLE,
longitude DOUBLE,
last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
`,
columns: []
},
latency_routes: {
createSql: `
CREATE TABLE IF NOT EXISTS latency_routes (
id INT AUTO_INCREMENT PRIMARY KEY,
source_id INT NOT NULL,
latency_source VARCHAR(100) NOT NULL,
latency_dest VARCHAR(100) NOT NULL,
latency_target VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
`,
columns: []
}
};
async function addColumnIfMissing(tableName, existingColumns, column) {
if (existingColumns.has(column.name)) {
return;
}
try {
console.log(`[Database Integrity] Missing column '${column.name}' in '${tableName}'. Adding it...`);
await db.query(column.sql);
console.log(`[Database Integrity] Column '${column.name}' added to '${tableName}'.`);
} catch (err) {
console.error(`[Database Integrity] Failed to add '${tableName}.${column.name}':`, err.message);
if (column.sql.includes(' AFTER ')) {
try {
const fallbackSql = column.sql.split(' AFTER ')[0];
await db.query(fallbackSql);
console.log(`[Database Integrity] Column '${column.name}' added to '${tableName}' via fallback.`);
} catch (fallbackErr) {
console.error(`[Database Integrity] Fallback failed for '${tableName}.${column.name}':`, fallbackErr.message);
}
}
}
}
async function ensureTable(tableName, tableSchema) {
await db.query(tableSchema.createSql);
const [columns] = await db.query(`SHOW COLUMNS FROM \`${tableName}\``);
const existingColumns = new Set(columns.map((column) => column.Field));
for (const column of tableSchema.columns || []) {
await addColumnIfMissing(tableName, existingColumns, column);
}
if (tableSchema.seedSql) {
await db.query(tableSchema.seedSql);
}
}
async function checkAndFixDatabase() {
const envPath = path.join(__dirname, '..', '.env');
if (!fs.existsSync(envPath)) return;
try {
for (const [tableName, tableSchema] of Object.entries(SCHEMA)) {
await ensureTable(tableName, tableSchema);
}
} catch (err) {
console.error('[Database Integrity] Startup schema check failed:', err.message);
}
}
module.exports = checkAndFixDatabase;

View File

@@ -7,7 +7,7 @@ const prometheusService = require('./prometheus-service');
const cache = require('./cache');
const geoService = require('./geo-service');
const latencyService = require('./latency-service');
const checkAndFixDatabase = require('./db-integrity-check');
const checkAndFixDatabase = require('./db-schema-check');
const http = require('http');
const WebSocket = require('ws');
const net = require('net');

View File

@@ -57,6 +57,18 @@ async function initDatabase() {
title VARCHAR(255) DEFAULT '数据可视化展示大屏',
logo_url TEXT,
default_theme VARCHAR(20) DEFAULT 'dark',
show_page_name TINYINT(1) DEFAULT 1,
logo_url_dark TEXT,
favicon_url TEXT,
show_95_bandwidth TINYINT(1) DEFAULT 0,
p95_type VARCHAR(20) DEFAULT 'tx',
require_login_for_server_details TINYINT(1) DEFAULT 1,
blackbox_source_id INT,
latency_source VARCHAR(100),
latency_dest VARCHAR(100),
latency_target VARCHAR(255),
icp_filing VARCHAR(255),
ps_filing VARCHAR(255),
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
`);