// ============================================================ // config.ts — 中心化配置模块(YAML 驱动) // ============================================================ // 职责: // 1. 从项目根目录 env.yaml 加载配置 // 2. 使用 validateConfig() 校验并类型收窄 // 3. 导出按职责分组的强类型配置对象(pgsql / redis / logging) // // 使用方式: // import { pgsql, redis, logging } from "./config"; // const ds = new DataSource({ ...pgsql }); // const redisClient = new Redis(redis.url); // // 配置文件位置:data/env.yaml(软链接 → 项目根目录 env.yaml) // TypeScript / Python 模块共享同一份配置。 // ============================================================ import { readFileSync } from "node:fs"; import { resolve, dirname } from "node:path"; import { fileURLToPath } from "node:url"; import { parse as parseYaml } from "yaml"; import { validateConfig, type EnvConfig } from "./validators"; // ============================================================ // 1. 定位并读取 env.yaml // ============================================================ /** * 计算项目根目录的绝对路径。 * data/config.ts → data/ → / * * 兼容 ESM(无 __dirname)和 Bun 运行时。 */ function getProjectRoot(): string { const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // config/index.ts → config/ → data/ → return resolve(__dirname, "../.."); } /** * 读取 data/env.yaml(软链接指向项目根目录 env.yaml)并解析为原始对象。 * 文件不存在时抛出明确错误,不做静默降级。 */ function loadYamlConfig(): unknown { const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // config/index.ts → config/ → data/env.yaml const yamlPath = resolve(__dirname, "../env.yaml"); let content: string; try { content = readFileSync(yamlPath, "utf-8"); } catch { throw new Error( `[config] 无法读取配置文件: ${yamlPath}\n` + `请确保 data/env.yaml 软链接指向项目根目录的 env.yaml。`, ); } const parsed = parseYaml(content); if (parsed === null || parsed === undefined) { throw new Error(`[config] env.yaml 解析结果为空: ${yamlPath}`); } return parsed; } // ============================================================ // 2. 加载 & 校验 // ============================================================ /** 经校验后的 env.yaml 配置(强类型) */ const rawConfig: EnvConfig = (() => { const raw = loadYamlConfig(); return validateConfig(raw); })(); // ============================================================ // 3. 按职责分组的导出配置对象 // ============================================================ /** * PostgreSQL / TimescaleDB 连接配置 * * 连接池参数(max / idleTimeoutMillis / connectionTimeoutMillis) * 为硬编码常量,不在 env.yaml 中暴露,避免误调导致连接耗尽。 */ export const pgsql = { host: rawConfig.db.host, port: rawConfig.db.port, database: rawConfig.db.name, user: rawConfig.db.user, password: rawConfig.db.password, /** pg.Pool 连接上限,避免连接数暴涨 */ max: 20, /** 连接空闲超时(毫秒) */ idleTimeoutMillis: 30000, /** 连接获取超时(毫秒) */ connectionTimeoutMillis: 5000, } as const; /** * Redis 连接与发布配置 * * channelPrefix / retryDelayBaseMs / maxRetries 为硬编码常量, * 跨模块保持一致,不需要通过配置文件修改。 */ export const redis = { /** Redis 连接 URL(ioredis 可直接使用) */ url: rawConfig.redis.url, /** 是否启用 Pub/Sub 发布行情数据(开发环境可关闭以节省资源) */ publishEnabled: rawConfig.redis.publish_enabled, /** 频道前缀,避免多环境 key 冲突 */ channelPrefix: "trade", /** 重连策略:指数退避基数(毫秒) */ retryDelayBaseMs: 1000, /** 最大重试次数 */ maxRetries: 10, } as const; /** * 日志配置 * * pretty 由 NODE_ENV 自动推导,不在 env.yaml 中独立配置。 */ export const logging = { /** 日志级别:trace / debug / info / warn / error / fatal */ level: rawConfig.logging.level, /** 运行环境(production 时 pino 输出 JSON 便于日志采集) */ nodeEnv: rawConfig.logging.node_env, /** 是否启用 pino-pretty(开发环境友好输出) */ pretty: rawConfig.logging.node_env === "development", } as const; /** * 交易所 API 密钥配置 * * 按交易所 ID 索引,目前仅 binance。 * 新增交易所时在 env.yaml 的 exchange 段添加对应子配置即可。 */ export const exchange = { binance: { apiKey: rawConfig.exchange.binance.api_key, apiSecret: rawConfig.exchange.binance.api_secret, }, /** USDT-M 永续合约 API Key */ binanceFutures: { apiKey: rawConfig.exchange.binance_futures.api_key, apiSecret: rawConfig.exchange.binance_futures.api_secret, }, } as const; // ============================================================ // 4. 工具:运行时打印配置概要(不含敏感信息) // ============================================================ /** 打印脱敏后的配置概要,便于启动排查 */ export function printConfigSummary(): void { const summary = { projectRoot: getProjectRoot(), pgsql: { host: pgsql.host, port: pgsql.port, database: pgsql.database, user: pgsql.user, password: "***", }, redis: { url: redis.url.replace(/\/\/.*@/, "//***@"), // 隐藏密码 publishEnabled: redis.publishEnabled, }, exchange: { binance: { apiKey: exchange.binance.apiKey.slice(0, 6) + "***", apiSecret: "***", }, binanceFutures: { apiKey: exchange.binanceFutures.apiKey.slice(0, 6) + "***", apiSecret: "***", }, }, logging: { level: logging.level, nodeEnv: logging.nodeEnv, pretty: logging.pretty, }, }; console.log("[config] 配置概要:", JSON.stringify(summary, null, 2)); }