docs(zulip): 完善Zulip业务模块功能文档

范围: src/business/zulip/README.md
- 补充对外提供的接口章节(14个公共方法)
- 添加使用的项目内部依赖说明(7个依赖)
- 完善核心特性描述(5个特性)
- 添加潜在风险评估(4个风险及缓解措施)
- 优化文档结构和内容完整性
This commit is contained in:
moyin
2026-01-15 10:53:04 +08:00
parent 30a4a2813d
commit ed04b8c92d
32 changed files with 622 additions and 8886 deletions

View File

@@ -0,0 +1,603 @@
/**
* 统一配置管理控制器
*
* 功能描述:
* - 提供统一配置管理的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
);
}
}
}