cangku/backend/src/config/reportReplicaDatabase.js

135 lines
3.8 KiB
JavaScript

'use strict';
const sql = require('mssql');
const database = require('./database');
const CONFIG_NAME = 'shujuku2_report_replica';
const CONFIG_CACHE_TTL_MS = 30000;
let pool = null;
let poolPromise = null;
let poolSignature = '';
let cachedConfig = null;
let configPromise = null;
let configExpiresAt = 0;
async function loadConfig() {
if (cachedConfig && Date.now() < configExpiresAt) return cachedConfig;
if (configPromise) return configPromise;
configPromise = database.local.query(
`SELECT host, port, database_name, username, password, \`ssl\`, status
FROM database_configs
WHERE name = ?
LIMIT 1`,
[CONFIG_NAME]
).then((rows) => {
const stored = rows[0];
if (stored) {
cachedConfig = stored.status === 'inactive'
? { disabled: true }
: {
server: stored.host,
port: Number(stored.port || 1433),
database: stored.database_name,
user: stored.username,
password: stored.password,
encrypt: stored.ssl === 1 || stored.ssl === true,
};
} else {
cachedConfig = {
server: process.env.CLOUD_DB_REPORT_HOST || process.env.CLOUD_DB_PUBLIC_HOST,
port: Number(process.env.CLOUD_DB_REPORT_PORT || process.env.CLOUD_DB_PUBLIC_PORT || 1433),
database: process.env.CLOUD_DB_REPORT_DATABASE || process.env.CLOUD_DB_PUBLIC_NAME || 'JSReportReplica',
user: process.env.CLOUD_DB_REPORT_USER,
password: process.env.CLOUD_DB_REPORT_PASSWORD,
encrypt: String(process.env.CLOUD_DB_REPORT_SSL || process.env.CLOUD_DB_PUBLIC_SSL || '').toLowerCase() === 'true',
};
}
configExpiresAt = Date.now() + CONFIG_CACHE_TTL_MS;
return cachedConfig;
}).finally(() => {
configPromise = null;
});
return configPromise;
}
function createPoolConfig(config) {
return {
server: config.server,
port: config.port,
database: config.database,
user: config.user,
password: config.password,
options: {
encrypt: config.encrypt,
trustServerCertificate: true,
},
requestTimeout: 120000,
connectionTimeout: 120000,
pool: {
max: 8,
min: 0,
idleTimeoutMillis: 120000,
},
};
}
async function getPool() {
const config = await loadConfig();
if (config.disabled) throw new Error('报表副本数据库配置已停用');
if (!config.server || !config.database || !config.user || !config.password) {
throw new Error('报表副本数据库配置不完整');
}
const signature = [config.server, config.port, config.database, config.user, config.password, config.encrypt].join('|');
if (pool?.connected && signature === poolSignature) return pool;
if (poolPromise && signature === poolSignature) return poolPromise;
if (pool) await pool.close().catch(() => {});
poolSignature = signature;
poolPromise = new sql.ConnectionPool(createPoolConfig(config)).connect()
.then((connectedPool) => {
pool = connectedPool;
return connectedPool;
})
.catch((error) => {
pool = null;
poolSignature = '';
throw error;
})
.finally(() => {
poolPromise = null;
});
return poolPromise;
}
async function query(sqlText, params = {}, options = {}) {
const activePool = await getPool();
const request = activePool.request();
if (options.requestTimeout) request.timeout = Number(options.requestTimeout);
Object.entries(params).forEach(([key, value]) => request.input(key, value));
const result = await request.query(sqlText);
return result.recordset || [];
}
async function close() {
if (poolPromise) await poolPromise.catch(() => {});
if (pool) await pool.close().catch(() => {});
pool = null;
poolPromise = null;
poolSignature = '';
cachedConfig = null;
configPromise = null;
configExpiresAt = 0;
}
module.exports = {
CONFIG_NAME,
query,
close,
};