/**
* 配置管理器
*/
import * as fs from 'fs';
import { resolve, dirname, join, isAbsolute } from 'path';
import { parse, stringify } from 'yaml';
import { logger } from './logger';
import { IConfigManager, ConfigSchema, ConfigError } from '@/types';
/**
* 配置管理器实现
*/
export class ConfigManager implements IConfigManager {
private config: Partial<ConfigSchema> = {};
private watchers: Map<string, fs.FSWatcher> = new Map();
private configPath: string = '';
constructor() {
// TODO: 初始化配置管理器
}
/**
* 加载配置文件
*/
async load<T>(filePath: string): Promise<T> {
try {
const fullPath = resolve(filePath);
const content = await fs.promises.readFile(fullPath, 'utf8');
let config: T;
if (filePath.endsWith('.yaml') || filePath.endsWith('.yml')) {
config = parse(content) as T;
} else if (filePath.endsWith('.json')) {
config = JSON.parse(content) as T;
} else {
throw new ConfigError(`Unsupported config file format: ${filePath}`, filePath);
}
logger.debug(`Configuration loaded from ${filePath}`, { config });
return config;
} catch (error) {
logger.error(`Failed to load configuration from ${filePath}`, error as Error);
const errorMessage = error instanceof Error ? error.message : String(error);
throw new ConfigError(`Failed to load configuration: ${errorMessage}`, filePath, error);
}
}
/**
* 保存配置文件
*/
async save<T>(filePath: string, data: T): Promise<void> {
try {
const fullPath = resolve(filePath);
const dir = dirname(fullPath);
// 确保目录存在
await fs.promises.mkdir(dir, { recursive: true });
let content: string;
if (filePath.endsWith('.yaml') || filePath.endsWith('.yml')) {
content = stringify(data);
} else if (filePath.endsWith('.json')) {
content = JSON.stringify(data, null, 2);
} else {
throw new ConfigError(`Unsupported config file format: ${filePath}`, filePath);
}
await fs.promises.writeFile(fullPath, content, 'utf8');
logger.debug(`Configuration saved to ${filePath}`, { data });
} catch (error) {
logger.error(`Failed to save configuration to ${filePath}`, error as Error);
const errorMessage = error instanceof Error ? error.message : String(error);
throw new ConfigError(`Failed to save configuration: ${errorMessage}`, filePath, error);
}
}
/**
* 监听配置文件变化
*/
watch(filePath: string, callback: (data: any) => void): void {
try {
const fullPath = resolve(filePath);
// 如果已经在监听,先停止
if (this.watchers.has(fullPath)) {
this.watchers.get(fullPath)?.close();
}
const watcher = fs.watch(fullPath, async (eventType) => {
if (eventType === 'change') {
logger.info(`Configuration file ${filePath} changed, reloading...`);
try {
const data = await this.load(filePath);
callback(data);
} catch (error) {
logger.error(`Failed to reload configuration from ${filePath}`, error as Error);
}
}
});
this.watchers.set(fullPath, watcher);
logger.debug(`Watching configuration file ${filePath}`);
} catch (error) {
logger.error(`Failed to watch configuration file ${filePath}`, error as Error);
const errorMessage = error instanceof Error ? error.message : String(error);
throw new ConfigError(`Failed to watch configuration: ${errorMessage}`, filePath, error);
}
}
/**
* 获取配置值
*/
get<T>(key: string): T {
// TODO: 实现嵌套键访问,如 'server.port'
const keys = key.split('.');
let value: any = this.config;
for (const k of keys) {
if (value && typeof value === 'object' && k in value) {
value = value[k];
} else {
throw new ConfigError(`Configuration key not found: ${key}`);
}
}
return value as T;
}
/**
* 设置配置值
*/
set(key: string, value: any): void {
// TODO: 实现嵌套键设置
const keys = key.split('.');
let target: any = this.config;
for (let i = 0; i < keys.length - 1; i++) {
const k = keys[i];
if (!target[k] || typeof target[k] !== 'object') {
target[k] = {};
}
target = target[k];
}
target[keys[keys.length - 1]] = value;
logger.debug(`Configuration updated: ${key}`, { value });
}
/**
* 加载应用配置
*/
async loadConfig(configPath?: string): Promise<void> {
try {
let mainConfigPath: string;
if (configPath) {
// 如果提供了自定义配置路径
if (isAbsolute(configPath)) {
// 确保Windows路径正确处理
mainConfigPath = resolve(configPath);
} else {
// 相对路径基于项目根目录
const projectRoot = join(__dirname, '..', '..');
mainConfigPath = join(projectRoot, configPath);
}
logger.info(`Loading configuration from custom path: ${mainConfigPath}`);
} else {
// 默认配置路径
const projectRoot = join(__dirname, '..', '..');
mainConfigPath = join(projectRoot, 'config', 'default.yaml');
logger.info(`Loading configuration from default path: ${mainConfigPath}`);
}
// 验证配置文件是否存在
try {
await fs.promises.access(mainConfigPath);
} catch (error) {
throw new ConfigError(`Configuration file not found: ${mainConfigPath}`);
}
this.config = await this.load<ConfigSchema>(mainConfigPath);
this.configPath = mainConfigPath;
// 监听配置文件变化
this.watch(mainConfigPath, (newConfig) => {
this.config = newConfig;
logger.info('Configuration reloaded');
});
// 加载环境特定配置(仅在未指定自定义配置路径时)
if (!configPath) {
const env = process.env.NODE_ENV || 'development';
const envConfigPath = join(__dirname, '..', '..', 'config', `${env}.yaml`);
try {
const envConfig = await this.load<Partial<ConfigSchema>>(envConfigPath);
if (envConfig && typeof envConfig === 'object') {
this.config = { ...this.config, ...envConfig };
logger.info(`Environment-specific configuration loaded: ${env}`);
}
} catch (error) {
logger.warn(`Environment-specific configuration not found: ${envConfigPath}`);
}
}
logger.info('Application configuration loaded successfully');
} catch (error) {
logger.error('Failed to load application configuration', error as Error);
throw error;
}
}
/**
* 获取完整配置
*/
getConfig(): Partial<ConfigSchema> {
return this.config;
}
/**
* 停止监听
*/
dispose(): void {
for (const [path, watcher] of this.watchers) {
watcher.close();
logger.debug(`Stopped watching configuration file: ${path}`);
}
this.watchers.clear();
}
}