优化数据库自检
This commit is contained in:
@@ -18,7 +18,11 @@ const SCHEMA = {
|
|||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||||
`,
|
`,
|
||||||
columns: []
|
columns: [
|
||||||
|
{ name: 'username', sql: "ALTER TABLE users ADD COLUMN username VARCHAR(255) NOT NULL UNIQUE AFTER id" },
|
||||||
|
{ name: 'password', sql: "ALTER TABLE users ADD COLUMN password VARCHAR(255) NOT NULL AFTER username" },
|
||||||
|
{ name: 'salt', sql: "ALTER TABLE users ADD COLUMN salt VARCHAR(255) NOT NULL AFTER password" }
|
||||||
|
]
|
||||||
},
|
},
|
||||||
prometheus_sources: {
|
prometheus_sources: {
|
||||||
createSql: `
|
createSql: `
|
||||||
@@ -34,23 +38,20 @@ const SCHEMA = {
|
|||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||||
`,
|
`,
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{ name: 'name', sql: "ALTER TABLE prometheus_sources ADD COLUMN name VARCHAR(255) NOT NULL AFTER id" },
|
||||||
name: 'is_server_source',
|
{ name: 'url', sql: "ALTER TABLE prometheus_sources ADD COLUMN url VARCHAR(500) NOT NULL AFTER name" },
|
||||||
sql: "ALTER TABLE prometheus_sources ADD COLUMN is_server_source TINYINT(1) DEFAULT 1 AFTER description"
|
{ name: 'description', sql: "ALTER TABLE prometheus_sources ADD COLUMN description TEXT AFTER url" },
|
||||||
},
|
{ 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" }
|
||||||
name: 'type',
|
|
||||||
sql: "ALTER TABLE prometheus_sources ADD COLUMN type VARCHAR(50) DEFAULT 'prometheus' AFTER is_server_source"
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
site_settings: {
|
site_settings: {
|
||||||
createSql: `
|
createSql: `
|
||||||
CREATE TABLE IF NOT EXISTS site_settings (
|
CREATE TABLE IF NOT EXISTS site_settings (
|
||||||
id INT PRIMARY KEY DEFAULT 1,
|
id INT PRIMARY KEY DEFAULT 1,
|
||||||
page_name VARCHAR(255) DEFAULT 'Data Visualization Display Wall',
|
page_name VARCHAR(255) DEFAULT '数据可视化展示大屏',
|
||||||
show_page_name TINYINT(1) DEFAULT 1,
|
show_page_name TINYINT(1) DEFAULT 1,
|
||||||
title VARCHAR(255) DEFAULT 'Data Visualization Display Wall',
|
title VARCHAR(255) DEFAULT '数据可视化展示大屏',
|
||||||
logo_url TEXT,
|
logo_url TEXT,
|
||||||
logo_url_dark TEXT,
|
logo_url_dark TEXT,
|
||||||
favicon_url TEXT,
|
favicon_url TEXT,
|
||||||
@@ -76,78 +77,31 @@ const SCHEMA = {
|
|||||||
INSERT IGNORE INTO site_settings (
|
INSERT IGNORE INTO site_settings (
|
||||||
id, page_name, show_page_name, title, default_theme, show_95_bandwidth, p95_type, require_login_for_server_details
|
id, page_name, show_page_name, title, default_theme, show_95_bandwidth, p95_type, require_login_for_server_details
|
||||||
) VALUES (
|
) VALUES (
|
||||||
1, 'Data Visualization Display Wall', 1, 'Data Visualization Display Wall', 'dark', 0, 'tx', 1
|
1, '数据可视化展示大屏', 1, '数据可视化展示大屏', 'dark', 0, 'tx', 1
|
||||||
)
|
)
|
||||||
`,
|
`,
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{ name: 'page_name', sql: "ALTER TABLE site_settings ADD COLUMN page_name VARCHAR(255) DEFAULT '数据可视化展示大屏' AFTER id" },
|
||||||
name: 'show_page_name',
|
{ name: 'show_page_name', sql: "ALTER TABLE site_settings ADD COLUMN show_page_name TINYINT(1) DEFAULT 1 AFTER page_name" },
|
||||||
sql: "ALTER TABLE site_settings ADD COLUMN show_page_name TINYINT(1) DEFAULT 1 AFTER page_name"
|
{ name: 'title', sql: "ALTER TABLE site_settings ADD COLUMN title VARCHAR(255) DEFAULT '数据可视化展示大屏' AFTER show_page_name" },
|
||||||
},
|
{ name: 'logo_url', sql: "ALTER TABLE site_settings ADD COLUMN logo_url TEXT AFTER title" },
|
||||||
{
|
{ name: 'logo_url_dark', sql: "ALTER TABLE site_settings ADD COLUMN logo_url_dark TEXT AFTER logo_url" },
|
||||||
name: 'logo_url_dark',
|
{ name: 'favicon_url', sql: "ALTER TABLE site_settings ADD COLUMN favicon_url TEXT AFTER logo_url_dark" },
|
||||||
sql: "ALTER TABLE site_settings ADD COLUMN logo_url_dark TEXT AFTER logo_url"
|
{ name: 'default_theme', sql: "ALTER TABLE site_settings ADD COLUMN default_theme VARCHAR(20) DEFAULT 'dark' AFTER favicon_url" },
|
||||||
},
|
{ 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: 'favicon_url',
|
{ 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" },
|
||||||
sql: "ALTER TABLE site_settings ADD COLUMN favicon_url TEXT AFTER logo_url_dark"
|
{ 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: 'show_95_bandwidth',
|
{ name: 'latency_target', sql: "ALTER TABLE site_settings ADD COLUMN latency_target VARCHAR(255) AFTER latency_dest" },
|
||||||
sql: "ALTER TABLE site_settings ADD COLUMN show_95_bandwidth TINYINT(1) DEFAULT 0 AFTER default_theme"
|
{ 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" },
|
||||||
{
|
{ name: 'network_data_sources', sql: "ALTER TABLE site_settings ADD COLUMN network_data_sources TEXT AFTER ps_filing" },
|
||||||
name: 'p95_type',
|
{ name: 'show_server_ip', sql: "ALTER TABLE site_settings ADD COLUMN show_server_ip TINYINT(1) DEFAULT 0 AFTER network_data_sources" },
|
||||||
sql: "ALTER TABLE site_settings ADD COLUMN p95_type VARCHAR(20) DEFAULT 'tx' AFTER show_95_bandwidth"
|
{ name: 'ip_metric_name', sql: "ALTER TABLE site_settings ADD COLUMN ip_metric_name VARCHAR(100) DEFAULT NULL AFTER show_server_ip" },
|
||||||
},
|
{ name: 'ip_label_name', sql: "ALTER TABLE site_settings ADD COLUMN ip_label_name VARCHAR(100) DEFAULT 'address' AFTER ip_metric_name" },
|
||||||
{
|
{ name: 'custom_metrics', sql: "ALTER TABLE site_settings ADD COLUMN custom_metrics JSON DEFAULT NULL AFTER ip_label_name" }
|
||||||
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"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'network_data_sources',
|
|
||||||
sql: "ALTER TABLE site_settings ADD COLUMN network_data_sources TEXT AFTER ps_filing"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'show_server_ip',
|
|
||||||
sql: "ALTER TABLE site_settings ADD COLUMN show_server_ip TINYINT(1) DEFAULT 0 AFTER network_data_sources"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'ip_metric_name',
|
|
||||||
sql: "ALTER TABLE site_settings ADD COLUMN ip_metric_name VARCHAR(100) DEFAULT NULL AFTER show_server_ip"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'ip_label_name',
|
|
||||||
sql: "ALTER TABLE site_settings ADD COLUMN ip_label_name VARCHAR(100) DEFAULT 'address' AFTER ip_metric_name"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'custom_metrics',
|
|
||||||
sql: "ALTER TABLE site_settings ADD COLUMN custom_metrics JSON DEFAULT NULL AFTER ip_label_name"
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
traffic_stats: {
|
traffic_stats: {
|
||||||
@@ -162,7 +116,12 @@ const SCHEMA = {
|
|||||||
UNIQUE INDEX (timestamp)
|
UNIQUE INDEX (timestamp)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||||
`,
|
`,
|
||||||
columns: []
|
columns: [
|
||||||
|
{ name: 'rx_bytes', sql: "ALTER TABLE traffic_stats ADD COLUMN rx_bytes BIGINT UNSIGNED DEFAULT 0 AFTER id" },
|
||||||
|
{ name: 'tx_bytes', sql: "ALTER TABLE traffic_stats ADD COLUMN tx_bytes BIGINT UNSIGNED DEFAULT 0 AFTER rx_bytes" },
|
||||||
|
{ name: 'rx_bandwidth', sql: "ALTER TABLE traffic_stats ADD COLUMN rx_bandwidth DOUBLE DEFAULT 0 AFTER tx_bytes" },
|
||||||
|
{ name: 'tx_bandwidth', sql: "ALTER TABLE traffic_stats ADD COLUMN tx_bandwidth DOUBLE DEFAULT 0 AFTER rx_bandwidth" }
|
||||||
|
]
|
||||||
},
|
},
|
||||||
server_locations: {
|
server_locations: {
|
||||||
createSql: `
|
createSql: `
|
||||||
@@ -178,7 +137,15 @@ const SCHEMA = {
|
|||||||
last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||||
`,
|
`,
|
||||||
columns: []
|
columns: [
|
||||||
|
{ name: 'ip', sql: "ALTER TABLE server_locations ADD COLUMN ip VARCHAR(255) NOT NULL UNIQUE AFTER id" },
|
||||||
|
{ name: 'country', sql: "ALTER TABLE server_locations ADD COLUMN country CHAR(2) AFTER ip" },
|
||||||
|
{ name: 'country_name', sql: "ALTER TABLE server_locations ADD COLUMN country_name VARCHAR(100) AFTER country" },
|
||||||
|
{ name: 'region', sql: "ALTER TABLE server_locations ADD COLUMN region VARCHAR(100) AFTER country_name" },
|
||||||
|
{ name: 'city', sql: "ALTER TABLE server_locations ADD COLUMN city VARCHAR(100) AFTER region" },
|
||||||
|
{ name: 'latitude', sql: "ALTER TABLE server_locations ADD COLUMN latitude DOUBLE AFTER city" },
|
||||||
|
{ name: 'longitude', sql: "ALTER TABLE server_locations ADD COLUMN longitude DOUBLE AFTER latitude" }
|
||||||
|
]
|
||||||
},
|
},
|
||||||
latency_routes: {
|
latency_routes: {
|
||||||
createSql: `
|
createSql: `
|
||||||
@@ -191,64 +158,73 @@ const SCHEMA = {
|
|||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||||
`,
|
`,
|
||||||
columns: []
|
columns: [
|
||||||
|
{ name: 'source_id', sql: "ALTER TABLE latency_routes ADD COLUMN source_id INT NOT NULL AFTER id" },
|
||||||
|
{ name: 'latency_source', sql: "ALTER TABLE latency_routes ADD COLUMN latency_source VARCHAR(100) NOT NULL AFTER source_id" },
|
||||||
|
{ name: 'latency_dest', sql: "ALTER TABLE latency_routes ADD COLUMN latency_dest VARCHAR(100) NOT NULL AFTER latency_source" },
|
||||||
|
{ name: 'latency_target', sql: "ALTER TABLE latency_routes ADD COLUMN latency_target VARCHAR(255) NOT NULL AFTER latency_dest" }
|
||||||
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
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) {
|
async function ensureTable(tableName, tableSchema) {
|
||||||
|
try {
|
||||||
|
// 1. Ensure table exists
|
||||||
await db.query(tableSchema.createSql);
|
await db.query(tableSchema.createSql);
|
||||||
|
|
||||||
|
// 2. Check columns
|
||||||
const [columns] = await db.query(`SHOW COLUMNS FROM \`${tableName}\``);
|
const [columns] = await db.query(`SHOW COLUMNS FROM \`${tableName}\``);
|
||||||
const existingColumns = new Set(columns.map((column) => column.Field));
|
const existingColumns = new Set(columns.map((column) => column.Field));
|
||||||
|
|
||||||
|
console.log(`[Database Integrity] Table '${tableName}' verified (${columns.length} columns)`);
|
||||||
|
|
||||||
for (const column of tableSchema.columns || []) {
|
for (const column of tableSchema.columns || []) {
|
||||||
await addColumnIfMissing(tableName, existingColumns, column);
|
if (!existingColumns.has(column.name)) {
|
||||||
|
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}'.`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3. Seed data
|
||||||
if (tableSchema.seedSql) {
|
if (tableSchema.seedSql) {
|
||||||
|
const [rows] = await db.query(`SELECT count(*) as count FROM \`${tableName}\``);
|
||||||
|
if (rows[0].count === 0) {
|
||||||
|
console.log(`[Database Integrity] Table '${tableName}' is empty. Seeding initial data...`);
|
||||||
await db.query(tableSchema.seedSql);
|
await db.query(tableSchema.seedSql);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`[Database Integrity] Error ensuring table '${tableName}':`, err.message);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function checkAndFixDatabase() {
|
async function checkAndFixDatabase() {
|
||||||
// Check for DB host to see if we have configuration (could be in .env or environment)
|
console.log('[Database Integrity] Starting comprehensive database audit...');
|
||||||
if (!process.env.MYSQL_HOST && !fs.existsSync(path.join(__dirname, '..', '.env'))) {
|
|
||||||
console.log('[Database Integrity] No configuration found, skipping check.');
|
// Try to check if we can even connect
|
||||||
return false;
|
try {
|
||||||
|
const health = await db.checkHealth();
|
||||||
|
if (health.status !== 'up') {
|
||||||
|
console.warn(`[Database Integrity] initial health check failed: ${health.error}`);
|
||||||
|
// If we can't connect, maybe the DB itself doesn't exist?
|
||||||
|
// For now, we rely on the pool to handle connection retries/errors.
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Ignore health check errors, let ensureTable handle the primary queries
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
let tablesChecked = 0;
|
||||||
for (const [tableName, tableSchema] of Object.entries(SCHEMA)) {
|
for (const [tableName, tableSchema] of Object.entries(SCHEMA)) {
|
||||||
await ensureTable(tableName, tableSchema);
|
await ensureTable(tableName, tableSchema);
|
||||||
|
tablesChecked++;
|
||||||
}
|
}
|
||||||
|
console.log(`[Database Integrity] Audit complete. ${tablesChecked} tables verified and healthy.`);
|
||||||
return true;
|
return true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[Database Integrity] Startup schema check failed:', err.message);
|
console.error('[Database Integrity] ❌ Audit failed:', err.message);
|
||||||
// Rethrow to allow caller to decide if this is fatal
|
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -305,15 +305,26 @@ function getCookie(req, name) {
|
|||||||
return matches ? decodeURIComponent(matches[1]) : undefined;
|
return matches ? decodeURIComponent(matches[1]) : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Database Initialization Check
|
||||||
|
*/
|
||||||
|
let isDbInitialized = false;
|
||||||
async function checkDb() {
|
async function checkDb() {
|
||||||
try {
|
try {
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
if (!fs.existsSync(path.join(__dirname, '..', '.env'))) {
|
// In Docker or high-level environments, .env might not exist but process.env is set
|
||||||
|
const hasConfig = process.env.MYSQL_HOST || fs.existsSync(path.join(__dirname, '..', '.env'));
|
||||||
|
|
||||||
|
if (!hasConfig) {
|
||||||
isDbInitialized = false;
|
isDbInitialized = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for essential tables
|
||||||
const [rows] = await db.query("SHOW TABLES LIKE 'prometheus_sources'");
|
const [rows] = await db.query("SHOW TABLES LIKE 'prometheus_sources'");
|
||||||
isDbInitialized = rows.length > 0;
|
const [rows2] = await db.query("SHOW TABLES LIKE 'site_settings'");
|
||||||
|
|
||||||
|
isDbInitialized = rows.length > 0 && rows2.length > 0;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
isDbInitialized = false;
|
isDbInitialized = false;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user