Files
XuJiacheng f61a63d8c1 feat: 外置数据库初始化与分区管理功能
- 删除主服务中的数据库初始化与分区管理逻辑,降低复杂度。
- 新增 SQL 脚本用于数据库初始化和分区管理,集中在 SQL_Script 目录。
- 移除环境变量 ENABLE_DATABASE_INITIALIZATION,简化配置。
- 更新 package.json,新增数据库初始化和分区管理的 npm 脚本。
- 删除不再使用的初始化和分区管理相关文件。
- 提供统一的命令行接口,支持外部调用数据库初始化和分区创建。
2026-03-04 14:54:27 +08:00

160 lines
4.9 KiB
JavaScript

import fs from 'fs';
import path from 'path';
import pg from 'pg';
import { fileURLToPath } from 'url';
const { Client } = pg;
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const scriptDir = __dirname;
const parseNumber = (value, defaultValue) => {
const parsed = Number(value);
return Number.isFinite(parsed) ? parsed : defaultValue;
};
const dbConfig = {
host: process.env.DB_HOST || process.env.POSTGRES_HOST || 'localhost',
port: parseNumber(process.env.DB_PORT || process.env.POSTGRES_PORT, 5432),
user: process.env.DB_USER || process.env.POSTGRES_USER || 'postgres',
password: process.env.DB_PASSWORD || process.env.POSTGRES_PASSWORD || '',
database: process.env.DB_DATABASE || process.env.POSTGRES_DATABASE || 'bls_rcu_action',
ssl: process.env.DB_SSL === 'true' ? { rejectUnauthorized: false } : undefined
};
const withClient = async (runner) => {
const client = new Client(dbConfig);
await client.connect();
try {
await runner(client);
} finally {
await client.end();
}
};
const executeSqlFile = async (client, fileName) => {
const filePath = path.join(scriptDir, fileName);
const sql = fs.readFileSync(filePath, 'utf8');
await client.query(sql);
};
export const ensureDatabase = async () => {
const adminClient = new Client({
...dbConfig,
database: process.env.DB_ADMIN_DATABASE || 'postgres'
});
await adminClient.connect();
try {
const targetDb = dbConfig.database;
const check = await adminClient.query('SELECT 1 FROM pg_database WHERE datname = $1', [targetDb]);
if (check.rowCount === 0) {
await adminClient.query(`CREATE DATABASE "${targetDb}"`);
console.log(`[SQL_Script] created database: ${targetDb}`);
} else {
console.log(`[SQL_Script] database exists: ${targetDb}`);
}
} finally {
await adminClient.end();
}
};
const toPartitionSuffix = (date) => {
const yyyy = date.getFullYear();
const mm = String(date.getMonth() + 1).padStart(2, '0');
const dd = String(date.getDate()).padStart(2, '0');
return `${yyyy}${mm}${dd}`;
};
const getDayRange = (date) => {
const start = new Date(date);
start.setHours(0, 0, 0, 0);
const end = new Date(start);
end.setDate(end.getDate() + 1);
return { startMs: start.getTime(), endMs: end.getTime() };
};
export const ensureRcuPartitions = async (daysAhead = 30) => {
const tpl = fs.readFileSync(path.join(scriptDir, 'partition_rcu_action.sql'), 'utf8');
await withClient(async (client) => {
const now = new Date();
for (let i = 0; i < daysAhead; i++) {
const d = new Date(now);
d.setDate(now.getDate() + i);
const suffix = toPartitionSuffix(d);
const partitionName = `rcu_action.rcu_action_events_${suffix}`;
const { startMs, endMs } = getDayRange(d);
const sql = tpl
.replaceAll('{partition_name}', partitionName)
.replaceAll('{start_ms}', String(startMs))
.replaceAll('{end_ms}', String(endMs));
await client.query(sql);
}
});
console.log(`[SQL_Script] ensured rcu_action partitions for ${daysAhead} days`);
};
export const ensureRoomStatusPartition = async (hotelId) => {
if (!Number.isFinite(Number(hotelId))) {
throw new Error('hotelId is required and must be a number');
}
const tpl = fs.readFileSync(path.join(scriptDir, 'partition_room_status.sql'), 'utf8');
const sql = tpl.replaceAll('{hotel_id}', String(hotelId));
await withClient(async (client) => {
await client.query(sql);
});
console.log(`[SQL_Script] ensured room_status partition for hotel_id=${hotelId}`);
};
export const initAll = async () => {
await ensureDatabase();
await withClient(async (client) => {
await executeSqlFile(client, 'init_rcu_action.sql');
await executeSqlFile(client, 'init_room_status.sql');
});
console.log('[SQL_Script] initialized schemas and tables');
};
const run = async () => {
const cmd = process.argv[2];
if (!cmd) {
throw new Error('missing command: init-all | init-rcu | init-room-status | partition-rcu [days] | partition-room-status <hotelId>');
}
switch (cmd) {
case 'init-all':
await initAll();
break;
case 'init-rcu':
await withClient((client) => executeSqlFile(client, 'init_rcu_action.sql'));
console.log('[SQL_Script] initialized rcu_action schema/table');
break;
case 'init-room-status':
await withClient((client) => executeSqlFile(client, 'init_room_status.sql'));
console.log('[SQL_Script] initialized room_status schema/table');
break;
case 'partition-rcu': {
const days = parseNumber(process.argv[3], 30);
await ensureRcuPartitions(days);
break;
}
case 'partition-room-status': {
const hotelId = process.argv[3];
await ensureRoomStatusPartition(hotelId);
break;
}
default:
throw new Error(`unsupported command: ${cmd}`);
}
};
run().catch((err) => {
console.error('[SQL_Script] failed:', err?.message || err);
process.exit(1);
});