UNPKG

@ghini/kit

Version:

js practical tools to assist efficient development

179 lines (178 loc) 6.04 kB
import pg from "pg"; import { insert } from "./insert.js"; import { truncate } from "./del.js"; const { Pool } = pg; const instanceCache = new Map(); const defaultConfig = { user: process.env.PGUSER || "postgres", password: process.env.PGPASSWORD || "postgres", host: process.env.PGHOST || "localhost", port: process.env.PGPORT || 5432, database: process.env.PGDATABASE || "postgres", max: 20, idleTimeoutMillis: 30000, connectionTimeoutMillis: 2000, }; function createCacheKey(config) { const normalized = { ...defaultConfig, ...config }; const keys = Object.keys(normalized).sort(); return keys.map((key) => `${key}:${normalized[key]}`).join("|"); } const gracefulShutdown = async (signal) => { console.log(`收到${signal}信号,开始优雅关闭...`); await xpg.closeAll(); console.log("所有连接池已关闭"); process.exit(0); }; ["SIGINT", "SIGTERM"].forEach((signal) => process.on(signal, () => gracefulShutdown(signal))); class PGClient { constructor(config = {}) { const finalConfig = { ...defaultConfig, ...config }; this.config = finalConfig; this.pool = new Pool(finalConfig); this.pool.on("error", (err) => console.error("PG Pool Error:", { message: err.message, code: err.code, database: finalConfig.database, host: finalConfig.host, })); if (process.env.NODE_ENV !== "production") { this.pool.on("connect", (client) => { console.log(`数据库连接已建立 (PID: ${client.processID})`); }); } this.insert = (table, data, options = {}) => insert(this, table, data, options); this.truncate = (table) => truncate(this, table); } async query(text, params = []) { const startTime = Date.now(); try { const result = await this.pool.query(text, params); const duration = Date.now() - startTime; if (duration > 1000) { console.warn("慢查询检测:", { duration: `${duration}ms`, query: text.length > 200 ? text.substring(0, 200) + "..." : text, rowCount: result.rowCount, }); } return [null, result]; } catch (err) { const duration = Date.now() - startTime; return [err, null]; } } async getClient() { try { return [null, await this.pool.connect()]; } catch (err) { console.error("获取数据库客户端失败:", { message: err.message, code: err.code, poolStatus: this.getPoolStatus(), }); return [err, null]; } } async transaction(callback, options = {}) { const [err, client] = await this.getClient(); if (err) return [err, null]; try { await client.query("BEGIN"); if (options.isolationLevel) { await client.query(`SET TRANSACTION ISOLATION LEVEL ${options.isolationLevel}`); } const result = await callback(client); await client.query("COMMIT"); return [null, result]; } catch (error) { try { await client.query("ROLLBACK"); console.log("事务已回滚"); } catch (rollbackErr) { console.error("事务回滚失败:", rollbackErr); } return [error, null]; } finally { client.release(); } } async mquery(mquery) { if (!Array.isArray(mquery) || mquery.length === 0) { return [null, []]; } return this.transaction(async (client) => { const results = []; for (const query of mquery) { const result = await client.query(query.text, query.params || []); results.push(result); } return results; }); } getPoolStatus() { return { totalCount: this.pool.totalCount, idleCount: this.pool.idleCount, waitingCount: this.pool.waitingCount, config: { max: this.config.max, database: this.config.database, host: this.config.host, port: this.config.port, }, }; } async testConnection() { const [err, result] = await this.query("SELECT 1 as test"); if (err) return [err, null]; return [null, result.rows[0]?.test === 1]; } async close() { if (this.pool.ended) { console.log("连接池已经关闭"); return; } try { await this.pool.end(); console.log("连接池已关闭"); } catch (err) { console.error("关闭连接池失败:", err); throw err; } } } function xpg(config = {}) { const cacheKey = createCacheKey(config); if (!instanceCache.has(cacheKey)) { const client = new PGClient(config); instanceCache.set(cacheKey, client); console.log(`创建新的PG客户端实例: ${client.config.database}@${client.config.host}`); } return instanceCache.get(cacheKey); } xpg.transaction = async (callback, config = {}) => { const instance = xpg(config); return instance.transaction(callback); }; xpg.getAllInstancesStatus = () => { return Array.from(instanceCache.entries()).map(([key, client]) => ({ cacheKey: key, status: client.getPoolStatus(), })); }; xpg.closeAll = async () => { const closePromises = Array.from(instanceCache.values()).map((client) => client.close()); await Promise.allSettled(closePromises); instanceCache.clear(); console.log("所有PG实例已关闭并清空缓存"); }; export { xpg };