异步获取Google Secret Manager凭据后如何初始化pg-promise
解决方案
方案1:使用顶级await(适配Node.js 14.18.0+ ES模块)
该方案改动最小,无需修改原有控制器的导入和调用逻辑,利用Node.js 14.3+支持的ES模块顶级await特性,等待密钥拉取完成后再初始化并导出pg-promise实例。
注意:使用该方案需要你的项目为ES模块规范,即package.json中配置"type": "module"
pgpProvider/index.ts 代码改造如下:
import { SecretManagerServiceClient } from '@google-cloud/secret-manager'; import pgpromise, { IMain, IDatabase } from 'pg-promise'; // 保留原有pg-promise初始化配置 const initOptions = { /* 你的原有初始化参数 */ }; const pgp: IMain = pgpromise(initOptions); // 从Google Secret Manager拉取数据库配置 async function fetchDbConfig() { const client = new SecretManagerServiceClient(); // 替换为你的密钥资源路径 const secretResource = 'projects/你的项目ID/secrets/你的数据库配置密钥名/versions/latest'; const [version] = await client.accessSecretVersion({ name: secretResource }); const secretData = version.payload?.data?.toString(); if (!secretData) throw new Error('拉取数据库配置密钥失败'); // 若密钥为JSON格式直接解析,若拆分存储多个密钥可在此处并行拉取后组装 return JSON.parse(secretData) as { database: string; host: string; port: number; user: string; password: string; }; } // 顶级await等待配置拉取完成后初始化数据库实例 const dbConfig = await fetchDbConfig(); const db: IDatabase<{}> = pgp(dbConfig); export default db;
方案2:代理包装实例(兼容所有模块规范,无侵入改造)
如果你的项目使用CommonJS规范不支持顶级await,可以通过Proxy代理封装数据库实例,所有方法调用会自动等待初始化完成后执行,原有控制器代码完全无需修改。
pgpProvider/index.ts 代码改造如下:
import { SecretManagerServiceClient } from '@google-cloud/secret-manager'; import pgpromise, { IMain, IDatabase } from 'pg-promise'; const initOptions = { /* 你的原有初始化参数 */ }; const pgp: IMain = pgpromise(initOptions); let dbInstance: IDatabase<{}> | null = null; // 初始化promise全局只执行一次 const dbInitPromise = (async () => { const client = new SecretManagerServiceClient(); const [version] = await client.accessSecretVersion({ name: 'projects/你的项目ID/secrets/你的数据库配置密钥名/versions/latest' }); const dbConfig = JSON.parse(version.payload!.data!.toString()!); dbInstance = pgp(dbConfig); })(); // 导出代理对象,拦截所有方法调用 const db = new Proxy({} as IDatabase<{}>, { get(target, propKey) { return async (...args: unknown[]) => { // 等待数据库初始化完成 await dbInitPromise; // 调用真实实例的对应方法 return (dbInstance as any)[propKey](...args); }; } }); export default db;
配套优化建议
- 若你将数据库用户名、密码等配置拆分存储为多个Secret Manager条目,可使用
Promise.all并行拉取所有密钥,减少初始化耗时 - 可在
accessSecretVersion调用逻辑中增加3-5次的失败重试机制,避免临时网络波动导致服务启动失败 - 服务启动时增加初始化校验,等数据库实例准备完成后再监听端口,避免启动初期请求报错,Express启动代码示例如下:
import express from 'express'; import db from './pgpProvider'; const app = express(); // 原有中间件、路由注册逻辑 // ... // 等待数据库就绪后再监听端口 (async () => { // 简单测试数据库连接 await db.any('SELECT 1'); app.listen(3000, () => { console.log('服务启动成功,监听端口3000'); }); })();
内容的提问来源于stack exchange,提问作者arano879n
相关产品推荐
相关产品推荐

