config.ts•13.1 kB
import { config as loadDotEnv } from 'dotenv';
import { hideBin } from 'yargs/helpers';
import yargs from 'yargs';
import { Logger, LogLevel } from './logger.js';
/**
* 配置来源枚举
*/
export enum ConfigSource {
DEFAULT = 'default',
ENV = 'env',
CLI = 'cli',
FILE = 'file'
}
/**
* 服务器配置接口
*/
export interface ServerConfig {
port: number;
}
/**
* 飞书配置接口
*/
export interface FeishuConfig {
appId: string;
appSecret: string;
baseUrl: string;
authType: 'tenant' | 'user';
tokenEndpoint: string;
}
/**
* 日志配置接口
*/
export interface LogConfig {
level: LogLevel;
showTimestamp: boolean;
showLevel: boolean;
timestampFormat: string;
}
/**
* 缓存配置接口
*/
export interface CacheConfig {
enabled: boolean;
ttl: number; // 单位:秒
maxSize: number; // 最大缓存条目数
}
/**
* 应用配置管理类
* 统一管理所有配置,支持环境变量、命令行参数和默认值
*/
export class Config {
private static instance: Config;
public readonly server: ServerConfig;
public readonly feishu: FeishuConfig;
public readonly log: LogConfig;
public readonly cache: CacheConfig;
public readonly configSources: {
[key: string]: ConfigSource;
};
/**
* 私有构造函数,用于单例模式
*/
private constructor() {
// 确保在任何配置读取前加载.env文件
loadDotEnv();
// 解析命令行参数
const argv = this.parseCommandLineArgs();
// 初始化配置来源记录
this.configSources = {};
// 配置服务器
this.server = this.initServerConfig(argv);
// 配置飞书
this.feishu = this.initFeishuConfig(argv);
// 配置日志
this.log = this.initLogConfig(argv);
// 配置缓存
this.cache = this.initCacheConfig(argv);
}
/**
* 获取配置单例
* @returns 配置实例
*/
public static getInstance(): Config {
if (!Config.instance) {
Config.instance = new Config();
}
return Config.instance;
}
/**
* 解析命令行参数
* @returns 解析后的参数对象
*/
private parseCommandLineArgs(): any {
return yargs(hideBin(process.argv))
.options({
port: {
type: 'number',
description: '服务器监听端口'
},
'log-level': {
type: 'string',
description: '日志级别 (debug, info, log, warn, error, none)'
},
'feishu-app-id': {
type: 'string',
description: '飞书应用ID'
},
'feishu-app-secret': {
type: 'string',
description: '飞书应用密钥'
},
'feishu-base-url': {
type: 'string',
description: '飞书API基础URL'
},
'cache-enabled': {
type: 'boolean',
description: '是否启用缓存'
},
'cache-ttl': {
type: 'number',
description: '缓存生存时间(秒)'
},
'feishu-auth-type': {
type: 'string',
description: '飞书认证类型 (tenant 或 user)'
},
'feishu-token-endpoint': {
type: 'string',
description: '获取token的接口地址,默认 http://localhost:3333/getToken'
}
})
.help()
.parseSync();
}
/**
* 初始化服务器配置
* @param argv 命令行参数
* @returns 服务器配置
*/
private initServerConfig(argv: any): ServerConfig {
const serverConfig: ServerConfig = {
port: 3333,
};
// 处理PORT
if (argv.port) {
serverConfig.port = argv.port;
this.configSources['server.port'] = ConfigSource.CLI;
} else if (process.env.PORT) {
serverConfig.port = parseInt(process.env.PORT, 10);
this.configSources['server.port'] = ConfigSource.ENV;
} else {
this.configSources['server.port'] = ConfigSource.DEFAULT;
}
return serverConfig;
}
/**
* 初始化飞书配置
* @param argv 命令行参数
* @returns 飞书配置
*/
private initFeishuConfig(argv: any): FeishuConfig {
// 先初始化serverConfig以获取端口
const serverConfig = this.server || this.initServerConfig(argv);
const feishuConfig: FeishuConfig = {
appId: '',
appSecret: '',
baseUrl: 'https://open.feishu.cn/open-apis',
authType: 'tenant', // 默认
tokenEndpoint: `http://127.0.0.1:${serverConfig.port}/getToken`, // 默认动态端口
};
// 处理App ID
if (argv['feishu-app-id']) {
feishuConfig.appId = argv['feishu-app-id'];
this.configSources['feishu.appId'] = ConfigSource.CLI;
} else if (process.env.FEISHU_APP_ID) {
feishuConfig.appId = process.env.FEISHU_APP_ID;
this.configSources['feishu.appId'] = ConfigSource.ENV;
}
// 处理App Secret
if (argv['feishu-app-secret']) {
feishuConfig.appSecret = argv['feishu-app-secret'];
this.configSources['feishu.appSecret'] = ConfigSource.CLI;
} else if (process.env.FEISHU_APP_SECRET) {
feishuConfig.appSecret = process.env.FEISHU_APP_SECRET;
this.configSources['feishu.appSecret'] = ConfigSource.ENV;
}
// 处理Base URL
if (argv['feishu-base-url']) {
feishuConfig.baseUrl = argv['feishu-base-url'];
this.configSources['feishu.baseUrl'] = ConfigSource.CLI;
} else if (process.env.FEISHU_BASE_URL) {
feishuConfig.baseUrl = process.env.FEISHU_BASE_URL;
this.configSources['feishu.baseUrl'] = ConfigSource.ENV;
} else {
this.configSources['feishu.baseUrl'] = ConfigSource.DEFAULT;
}
// 处理authType
if (argv['feishu-auth-type']) {
feishuConfig.authType = argv['feishu-auth-type'] === 'user' ? 'user' : 'tenant';
this.configSources['feishu.authType'] = ConfigSource.CLI;
} else if (process.env.FEISHU_AUTH_TYPE) {
feishuConfig.authType = process.env.FEISHU_AUTH_TYPE === 'user' ? 'user' : 'tenant';
this.configSources['feishu.authType'] = ConfigSource.ENV;
} else {
this.configSources['feishu.authType'] = ConfigSource.DEFAULT;
}
// 处理tokenEndpoint
if (argv['feishu-token-endpoint']) {
feishuConfig.tokenEndpoint = argv['feishu-token-endpoint'];
this.configSources['feishu.tokenEndpoint'] = ConfigSource.CLI;
} else if (process.env.FEISHU_TOKEN_ENDPOINT) {
feishuConfig.tokenEndpoint = process.env.FEISHU_TOKEN_ENDPOINT;
this.configSources['feishu.tokenEndpoint'] = ConfigSource.ENV;
} else {
this.configSources['feishu.tokenEndpoint'] = ConfigSource.DEFAULT;
}
return feishuConfig;
}
/**
* 初始化日志配置
* @param argv 命令行参数
* @returns 日志配置
*/
private initLogConfig(argv: any): LogConfig {
const logConfig: LogConfig = {
level: LogLevel.INFO,
showTimestamp: true,
showLevel: true,
timestampFormat: 'yyyy-MM-dd HH:mm:ss.SSS'
};
// 处理日志级别
if (argv['log-level']) {
logConfig.level = this.getLogLevelFromString(argv['log-level']);
this.configSources['log.level'] = ConfigSource.CLI;
} else if (process.env.LOG_LEVEL) {
logConfig.level = this.getLogLevelFromString(process.env.LOG_LEVEL);
this.configSources['log.level'] = ConfigSource.ENV;
} else {
this.configSources['log.level'] = ConfigSource.DEFAULT;
}
// 处理时间戳显示
if (process.env.LOG_SHOW_TIMESTAMP) {
logConfig.showTimestamp = process.env.LOG_SHOW_TIMESTAMP.toLowerCase() === 'true';
this.configSources['log.showTimestamp'] = ConfigSource.ENV;
} else {
this.configSources['log.showTimestamp'] = ConfigSource.DEFAULT;
}
// 处理级别显示
if (process.env.LOG_SHOW_LEVEL) {
logConfig.showLevel = process.env.LOG_SHOW_LEVEL.toLowerCase() === 'true';
this.configSources['log.showLevel'] = ConfigSource.ENV;
} else {
this.configSources['log.showLevel'] = ConfigSource.DEFAULT;
}
// 处理时间戳格式
if (process.env.LOG_TIMESTAMP_FORMAT) {
logConfig.timestampFormat = process.env.LOG_TIMESTAMP_FORMAT;
this.configSources['log.timestampFormat'] = ConfigSource.ENV;
} else {
this.configSources['log.timestampFormat'] = ConfigSource.DEFAULT;
}
return logConfig;
}
/**
* 初始化缓存配置
* @param argv 命令行参数
* @returns 缓存配置
*/
private initCacheConfig(argv: any): CacheConfig {
const cacheConfig: CacheConfig = {
enabled: true,
ttl: 300, // 5分钟,单位:秒
maxSize: 100
};
// 处理缓存启用
if (argv['cache-enabled'] !== undefined) {
cacheConfig.enabled = argv['cache-enabled'];
this.configSources['cache.enabled'] = ConfigSource.CLI;
} else if (process.env.CACHE_ENABLED) {
cacheConfig.enabled = process.env.CACHE_ENABLED.toLowerCase() === 'true';
this.configSources['cache.enabled'] = ConfigSource.ENV;
} else {
this.configSources['cache.enabled'] = ConfigSource.DEFAULT;
}
// 处理TTL
if (argv['cache-ttl']) {
cacheConfig.ttl = argv['cache-ttl'];
this.configSources['cache.ttl'] = ConfigSource.CLI;
} else if (process.env.CACHE_TTL) {
cacheConfig.ttl = parseInt(process.env.CACHE_TTL, 10);
this.configSources['cache.ttl'] = ConfigSource.ENV;
} else {
this.configSources['cache.ttl'] = ConfigSource.DEFAULT;
}
// 处理最大缓存大小
if (process.env.CACHE_MAX_SIZE) {
cacheConfig.maxSize = parseInt(process.env.CACHE_MAX_SIZE, 10);
this.configSources['cache.maxSize'] = ConfigSource.ENV;
} else {
this.configSources['cache.maxSize'] = ConfigSource.DEFAULT;
}
return cacheConfig;
}
/**
* 从字符串获取日志级别
* @param levelStr 日志级别字符串
* @returns 日志级别枚举值
*/
private getLogLevelFromString(levelStr: string): LogLevel {
switch (levelStr.toLowerCase()) {
case 'debug': return LogLevel.DEBUG;
case 'info': return LogLevel.INFO;
case 'log': return LogLevel.LOG;
case 'warn': return LogLevel.WARN;
case 'error': return LogLevel.ERROR;
case 'none': return LogLevel.NONE;
default: return LogLevel.INFO;
}
}
/**
* 打印当前配置信息
* @param isStdioMode 是否在stdio模式下
*/
public printConfig(isStdioMode: boolean = false): void {
if (isStdioMode) return;
Logger.info('当前配置:');
Logger.info('服务器配置:');
Logger.info(`- 端口: ${this.server.port} (来源: ${this.configSources['server.port']})`);
Logger.info('飞书配置:');
if (this.feishu.appId) {
Logger.info(`- App ID: ${this.maskApiKey(this.feishu.appId)} (来源: ${this.configSources['feishu.appId']})`);
}
if (this.feishu.appSecret) {
Logger.info(`- App Secret: ${this.maskApiKey(this.feishu.appSecret)} (来源: ${this.configSources['feishu.appSecret']})`);
}
Logger.info(`- API URL: ${this.feishu.baseUrl} (来源: ${this.configSources['feishu.baseUrl']})`);
Logger.info(`- 认证类型: ${this.feishu.authType} (来源: ${this.configSources['feishu.authType']})`);
Logger.info('日志配置:');
Logger.info(`- 日志级别: ${LogLevel[this.log.level]} (来源: ${this.configSources['log.level']})`);
Logger.info(`- 显示时间戳: ${this.log.showTimestamp} (来源: ${this.configSources['log.showTimestamp']})`);
Logger.info(`- 显示日志级别: ${this.log.showLevel} (来源: ${this.configSources['log.showLevel']})`);
Logger.info('缓存配置:');
Logger.info(`- 启用缓存: ${this.cache.enabled} (来源: ${this.configSources['cache.enabled']})`);
Logger.info(`- 缓存TTL: ${this.cache.ttl}秒 (来源: ${this.configSources['cache.ttl']})`);
Logger.info(`- 最大缓存条目: ${this.cache.maxSize} (来源: ${this.configSources['cache.maxSize']})`);
}
/**
* 掩盖API密钥
* @param key API密钥
* @returns 掩盖后的密钥字符串
*/
private maskApiKey(key: string): string {
if (!key || key.length <= 4) return '****';
return `${key.substring(0, 2)}****${key.substring(key.length - 2)}`;
}
/**
* 验证配置是否完整有效
* @returns 是否验证成功
*/
public validate(): boolean {
// 验证服务器配置
if (!this.server.port || this.server.port <= 0) {
Logger.error('无效的服务器端口配置');
return false;
}
// 验证飞书配置
if (!this.feishu.appId) {
Logger.error('缺少飞书应用ID,请通过环境变量FEISHU_APP_ID或命令行参数--feishu-app-id提供');
return false;
}
if (!this.feishu.appSecret) {
Logger.error('缺少飞书应用Secret,请通过环境变量FEISHU_APP_SECRET或命令行参数--feishu-app-secret提供');
return false;
}
return true;
}
}