/** * 统一配置管理控制器 * * 功能描述: * - 提供统一配置管理的REST API接口 * - 支持配置查询、同步、状态检查 * - 提供备份管理功能 * * 架构定位: * - 层级:Gateway层(网关层) * - 职责:HTTP协议处理、API接口暴露 * - 依赖:调用Business层的ZulipModule服务 * * 职责分离: * - API接口:提供RESTful风格的配置管理接口 * - 协议处理:处理HTTP请求和响应 * - 参数验证:验证请求参数格式 * - 错误转换:将业务异常转换为HTTP响应 * * 最近修改: * - 2026-01-14: 架构优化 - 从Business层迁移到Gateway层,符合四层架构规范 (修改者: moyin) * - 2026-01-14: 代码规范优化 - 完善文件头注释和职责分离描述 (修改者: moyin) * - 2026-01-12: 功能新增 - 初始创建统一配置管理控制器 (修改者: moyin) * * @author moyin * @version 3.0.0 * @since 2026-01-12 * @lastModified 2026-01-14 */ import { Controller, Get, Post, Query, HttpStatus, HttpException, Logger, } from '@nestjs/common'; import { ApiTags, ApiOperation, ApiResponse, ApiQuery, } from '@nestjs/swagger'; import { DynamicConfigManagerService } from '../../core/zulip_core/services/dynamic_config_manager.service'; @ApiTags('unified-config') @Controller('api/zulip/config') export class DynamicConfigController { private readonly logger = new Logger(DynamicConfigController.name); constructor( private readonly configManager: DynamicConfigManagerService, ) {} /** * 获取当前配置 */ @Get() @ApiOperation({ summary: '获取当前配置', description: '获取当前的统一配置(自动从本地加载,如需最新数据请先调用同步接口)' }) @ApiResponse({ status: 200, description: '配置获取成功', schema: { type: 'object', properties: { success: { type: 'boolean' }, data: { type: 'object' }, source: { type: 'string', enum: ['remote', 'local', 'default'] }, timestamp: { type: 'string' } } } }) async getCurrentConfig() { try { this.logger.log('获取当前配置'); const config = await this.configManager.getConfig(); const status = this.configManager.getConfigStatus(); return { success: true, data: config, source: config.source || 'unknown', lastSyncTime: status.lastSyncTime, mapCount: status.mapCount, objectCount: status.objectCount, timestamp: new Date().toISOString() }; } catch (error) { this.logger.error('获取配置失败', { error: (error as Error).message, }); throw new HttpException( { success: false, error: (error as Error).message, timestamp: new Date().toISOString() }, HttpStatus.INTERNAL_SERVER_ERROR ); } } /** * 获取配置状态 */ @Get('status') @ApiOperation({ summary: '获取配置状态', description: '获取统一配置管理器的状态信息' }) @ApiResponse({ status: 200, description: '状态获取成功' }) async getConfigStatus() { try { const status = this.configManager.getConfigStatus(); return { success: true, data: { ...status, lastSyncTimeAgo: status.lastSyncTime ? Math.round((Date.now() - status.lastSyncTime.getTime()) / 60000) : null, }, timestamp: new Date().toISOString() }; } catch (error) { this.logger.error('获取配置状态失败', { error: (error as Error).message, }); throw new HttpException( { success: false, error: (error as Error).message, timestamp: new Date().toISOString() }, HttpStatus.INTERNAL_SERVER_ERROR ); } } /** * 测试Zulip连接 */ @Get('test-connection') @ApiOperation({ summary: '测试Zulip连接', description: '测试与Zulip服务器的连接状态' }) @ApiResponse({ status: 200, description: '连接测试完成' }) async testConnection() { try { this.logger.log('测试Zulip连接'); const connected = await this.configManager.testZulipConnection(); return { success: true, data: { connected, message: connected ? 'Zulip连接正常' : 'Zulip连接失败' }, timestamp: new Date().toISOString() }; } catch (error) { this.logger.error('连接测试失败', { error: (error as Error).message, }); return { success: false, data: { connected: false, message: '连接测试异常' }, error: (error as Error).message, timestamp: new Date().toISOString() }; } } /** * 同步远程配置 */ @Post('sync') @ApiOperation({ summary: '同步远程配置', description: '手动触发从Zulip服务器同步配置到本地文件' }) @ApiResponse({ status: 200, description: '配置同步完成' }) async syncConfig() { try { this.logger.log('手动同步配置'); const result = await this.configManager.syncConfig(); return { success: result.success, data: { source: result.source, mapCount: result.mapCount, objectCount: result.objectCount, lastUpdated: result.lastUpdated, backupCreated: result.backupCreated, message: result.success ? '配置同步成功' : '配置同步失败' }, error: result.error, timestamp: new Date().toISOString() }; } catch (error) { this.logger.error('同步配置失败', { error: (error as Error).message, }); throw new HttpException( { success: false, error: (error as Error).message, timestamp: new Date().toISOString() }, HttpStatus.INTERNAL_SERVER_ERROR ); } } /** * 获取Stream列表 */ @Get('streams') @ApiOperation({ summary: '获取Zulip Stream列表', description: '直接从Zulip服务器获取Stream列表' }) @ApiResponse({ status: 200, description: 'Stream列表获取成功' }) async getStreams() { try { this.logger.log('获取Zulip Stream列表'); const streams = await this.configManager.getZulipStreams(); return { success: true, data: { streams: streams.map(stream => ({ id: stream.stream_id, name: stream.name, description: stream.description, isPublic: !stream.invite_only, isWebPublic: stream.is_web_public })), count: streams.length }, timestamp: new Date().toISOString() }; } catch (error) { this.logger.error('获取Stream列表失败', { error: (error as Error).message, }); throw new HttpException( { success: false, error: (error as Error).message, timestamp: new Date().toISOString() }, HttpStatus.INTERNAL_SERVER_ERROR ); } } /** * 获取指定Stream的Topic列表 */ @Get('topics') @ApiOperation({ summary: '获取Stream的Topic列表', description: '获取指定Stream的所有Topic' }) @ApiQuery({ name: 'streamId', description: 'Stream ID', required: true, type: 'number' }) @ApiResponse({ status: 200, description: 'Topic列表获取成功' }) async getTopics(@Query('streamId') streamId: string) { try { const streamIdNum = parseInt(streamId, 10); if (isNaN(streamIdNum)) { throw new Error('无效的Stream ID'); } this.logger.log('获取Stream Topic列表', { streamId: streamIdNum }); const topics = await this.configManager.getZulipTopics(streamIdNum); return { success: true, data: { streamId: streamIdNum, topics: topics.map(topic => ({ name: topic.name, lastMessageId: topic.max_id })), count: topics.length }, timestamp: new Date().toISOString() }; } catch (error) { this.logger.error('获取Topic列表失败', { streamId, error: (error as Error).message, }); throw new HttpException( { success: false, error: (error as Error).message, timestamp: new Date().toISOString() }, HttpStatus.INTERNAL_SERVER_ERROR ); } } /** * 查询地图配置 */ @Get('maps') @ApiOperation({ summary: '获取地图配置列表', description: '获取所有地图的配置信息' }) @ApiResponse({ status: 200, description: '地图配置获取成功' }) async getMaps() { try { this.logger.log('获取地图配置列表'); const maps = await this.configManager.getAllMapConfigs(); return { success: true, data: { maps: maps.map(map => ({ mapId: map.mapId, mapName: map.mapName, zulipStream: map.zulipStream, zulipStreamId: map.zulipStreamId, description: map.description, isPublic: map.isPublic, objectCount: map.interactionObjects?.length || 0 })), count: maps.length }, timestamp: new Date().toISOString() }; } catch (error) { this.logger.error('获取地图配置失败', { error: (error as Error).message, }); throw new HttpException( { success: false, error: (error as Error).message, timestamp: new Date().toISOString() }, HttpStatus.INTERNAL_SERVER_ERROR ); } } /** * 根据地图ID获取Stream */ @Get('map-to-stream') @ApiOperation({ summary: '地图ID转Stream名称', description: '根据地图ID获取对应的Zulip Stream名称' }) @ApiQuery({ name: 'mapId', description: '地图ID', required: true, type: 'string' }) @ApiResponse({ status: 200, description: '转换成功' }) async mapToStream(@Query('mapId') mapId: string) { try { this.logger.log('地图ID转Stream', { mapId }); const streamName = await this.configManager.getStreamByMap(mapId); return { success: true, data: { mapId, streamName, found: !!streamName }, timestamp: new Date().toISOString() }; } catch (error) { this.logger.error('地图ID转Stream失败', { mapId, error: (error as Error).message, }); throw new HttpException( { success: false, error: (error as Error).message, timestamp: new Date().toISOString() }, HttpStatus.INTERNAL_SERVER_ERROR ); } } /** * 根据Stream名称获取地图ID */ @Get('stream-to-map') @ApiOperation({ summary: 'Stream名称转地图ID', description: '根据Zulip Stream名称获取对应的地图ID' }) @ApiQuery({ name: 'streamName', description: 'Stream名称', required: true, type: 'string' }) @ApiResponse({ status: 200, description: '转换成功' }) async streamToMap(@Query('streamName') streamName: string) { try { this.logger.log('Stream转地图ID', { streamName }); const mapId = await this.configManager.getMapIdByStream(streamName); return { success: true, data: { streamName, mapId, found: !!mapId }, timestamp: new Date().toISOString() }; } catch (error) { this.logger.error('Stream转地图ID失败', { streamName, error: (error as Error).message, }); throw new HttpException( { success: false, error: (error as Error).message, timestamp: new Date().toISOString() }, HttpStatus.INTERNAL_SERVER_ERROR ); } } /** * 获取备份文件列表 */ @Get('backups') @ApiOperation({ summary: '获取备份文件列表', description: '获取所有配置备份文件的列表' }) @ApiResponse({ status: 200, description: '备份列表获取成功' }) async getBackups() { try { this.logger.log('获取备份文件列表'); const backups = this.configManager.getBackupFiles(); return { success: true, data: { backups: backups.map(backup => ({ name: backup.name, size: backup.size, created: backup.created, sizeKB: Math.round(backup.size / 1024) })), count: backups.length }, timestamp: new Date().toISOString() }; } catch (error) { this.logger.error('获取备份列表失败', { error: (error as Error).message, }); throw new HttpException( { success: false, error: (error as Error).message, timestamp: new Date().toISOString() }, HttpStatus.INTERNAL_SERVER_ERROR ); } } /** * 从备份恢复配置 */ @Post('restore') @ApiOperation({ summary: '从备份恢复配置', description: '从指定的备份文件恢复配置' }) @ApiQuery({ name: 'backupFile', description: '备份文件名', required: true, type: 'string' }) @ApiResponse({ status: 200, description: '配置恢复完成' }) async restoreFromBackup(@Query('backupFile') backupFile: string) { try { this.logger.log('从备份恢复配置', { backupFile }); const success = await this.configManager.restoreFromBackup(backupFile); return { success, data: { backupFile, message: success ? '配置恢复成功' : '配置恢复失败' }, timestamp: new Date().toISOString() }; } catch (error) { this.logger.error('配置恢复失败', { backupFile, error: (error as Error).message, }); throw new HttpException( { success: false, error: (error as Error).message, timestamp: new Date().toISOString() }, HttpStatus.INTERNAL_SERVER_ERROR ); } } }