swagger_generator_flutter/lib/parsers/swagger_data_parser.dart

589 lines
18 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:convert';
import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:logging/logging.dart';
import 'package:swagger_generator_flutter/core/config.dart';
import 'package:swagger_generator_flutter/core/exceptions.dart';
import 'package:swagger_generator_flutter/core/models.dart';
import 'package:swagger_generator_flutter/utils/cache_manager.dart';
import 'package:swagger_generator_flutter/utils/performance_monitor.dart';
import 'package:swagger_generator_flutter/utils/reference_resolver.dart';
import 'package:swagger_generator_flutter/utils/string_utils.dart';
/// Swagger数据解析器
/// 负责解析Swagger JSON文档并提取相关信息
class SwaggerDataParser {
SwaggerDataParser()
: _cacheManager = CacheManager(),
_performanceMonitor = PerformanceMonitor();
final CacheManager _cacheManager;
final PerformanceMonitor _performanceMonitor;
static final Logger _logger = Logger('SwaggerDataParser');
// 缓存解析结果
final Map<String, SwaggerDocument> _cachedDocuments = {};
/// 获取并解析Swagger JSON文档
/// [url] 可选参数,如果不传则使用配置中的第一个 URL
Future<SwaggerDocument> fetchAndParseSwaggerDocument([String? url]) async {
final swaggerUrl = url ?? SwaggerConfig.swaggerJsonUrls.first;
// 如果有缓存,直接返回缓存结果
if (_cachedDocuments.containsKey(swaggerUrl)) {
_logger.info('📦 使用缓存的文档: $swaggerUrl');
return _cachedDocuments[swaggerUrl]!;
}
return _performanceMonitor.measure(
'fetchAndParseSwaggerDocument',
() async {
try {
print('🔄 正在获取Swagger JSON文档: $swaggerUrl');
Map<String, dynamic> jsonData;
if (swaggerUrl.startsWith('file://')) {
// 处理本地文件
final filePath = swaggerUrl.replaceFirst('file://', '');
final file = File(filePath);
if (file.existsSync()) {
final content = file.readAsStringSync();
jsonData = json.decode(content) as Map<String, dynamic>;
} else {
throw SwaggerParseException(
'本地文件不存在',
url: swaggerUrl,
details: '文件路径: $filePath',
);
}
} else {
// 处理远程URL
final response = await http.get(
Uri.parse(swaggerUrl),
headers: SwaggerConfig.httpHeaders,
);
if (response.statusCode == 200) {
jsonData = json.decode(response.body) as Map<String, dynamic>;
} else {
throw SwaggerParseException(
'HTTP请求失败',
url: swaggerUrl,
statusCode: response.statusCode,
details: 'HTTP响应状态码: ${response.statusCode}',
);
}
}
final document = await parseSwaggerDocument(jsonData);
_cachedDocuments[swaggerUrl] = document;
_logger.info('✅ Swagger文档解析完成');
return document;
} on Object catch (e) {
if (e is SwaggerParseException) {
rethrow;
}
throw SwaggerParseException(
'获取Swagger文档失败',
url: swaggerUrl,
details: e.toString(),
);
}
},
);
}
/// 解析Swagger JSON文档
Future<SwaggerDocument> parseSwaggerDocument(
Map<String, dynamic> jsonData,
) async {
return _performanceMonitor.measure('parseSwaggerDocument', () async {
// 尝试从缓存获取
final cacheKey = 'swagger_doc_${jsonData.hashCode}';
final cachedResult = _cacheManager.get<SwaggerDocument>(cacheKey);
if (cachedResult != null) {
// 将缓存结果存储到 map 中(使用第一个 URL 作为 key
_cachedDocuments[SwaggerConfig.swaggerJsonUrls.first] = cachedResult;
return cachedResult;
}
// 解析文档基本信息
final info = jsonData['info'] as Map<String, dynamic>? ?? {};
final title = info['title'] as String? ?? 'API Documentation';
final version = info['version'] as String? ?? '1.0.0';
final description = info['description'] as String? ?? '';
// ✨ 分析 schema 使用情况(在解析 components 之前)
final schemaUsage = _analyzeSchemaUsage(jsonData);
// 解析 servers (OpenAPI 3.0)
final servers = _parseServers(jsonData);
// 解析 components (OpenAPI 3.0),传入使用情况分析结果
final components = _parseComponents(jsonData, schemaUsage);
// 解析tags信息 (用于获取控制器描述)
final tagsInfo = _parseTagsInfo(jsonData);
// 解析API路径
final paths = _parseApiPaths(jsonData);
// 解析API模型 (从 components 中提取)
final models = components.schemas;
// 解析API控制器 (传入tags信息)
final controllers = _parseApiControllers(paths, tagsInfo);
final document = SwaggerDocument(
title: title,
version: version,
description: description,
servers: servers,
components: components,
paths: paths,
models: models,
controllers: controllers,
);
// 缓存结果
_cacheManager.put(cacheKey, document);
_cachedDocuments[SwaggerConfig.swaggerJsonUrls.first] = document;
return document;
});
}
/// 解析 servers 配置 (OpenAPI 3.0)
List<ApiServer> _parseServers(Map<String, dynamic> jsonData) {
final servers = <ApiServer>[];
try {
final serversJson = jsonData['servers'] as List<dynamic>?;
if (serversJson != null) {
for (final serverJson in serversJson) {
if (serverJson is Map<String, dynamic>) {
final server = ApiServer.fromJson(serverJson);
servers.add(server);
}
}
}
// 如果没有 servers 配置,提供默认值
if (servers.isEmpty) {
servers.add(const ApiServer(url: '/'));
}
} on Object catch (e) {
_logger.warning('⚠️ 解析servers配置时发生错误: $e');
// 提供默认服务器配置
servers.add(const ApiServer(url: '/'));
}
return servers;
}
/// 解析 components 配置 (OpenAPI 3.0)
ApiComponents _parseComponents(
Map<String, dynamic> jsonData,
Map<String, Set<ModelUsageType>> schemaUsage,
) {
try {
final componentsJson = jsonData['components'] as Map<String, dynamic>?;
if (componentsJson != null) {
// 使用引用解析器处理复杂嵌套和循环引用
final resolver = ReferenceResolver();
final resolvedSchemas = resolver.resolveModels(componentsJson);
// ✨ 根据使用情况更新模型的 usageType
final schemasWithUsageType = <String, ApiModel>{};
resolvedSchemas.forEach((name, model) {
final usages = schemaUsage[name] ?? {};
final ModelUsageType usageType;
if (usages.isEmpty) {
usageType = ModelUsageType.unknown;
} else if (usages.length == 1) {
usageType = usages.first;
} else {
// 既用于请求又用于响应
usageType = ModelUsageType.common;
}
schemasWithUsageType[name] = model.copyWithUsageType(usageType);
});
// 创建 ApiComponents但使用解析后的 schemas
final components = ApiComponents.fromJson(componentsJson);
return ApiComponents(
schemas: schemasWithUsageType,
responses: components.responses,
parameters: components.parameters,
examples: components.examples,
requestBodies: components.requestBodies,
headers: components.headers,
securitySchemes: components.securitySchemes,
links: components.links,
callbacks: components.callbacks,
);
}
} on Object catch (e) {
_logger.warning('⚠️ 解析components配置时发生错误: $e');
}
return const ApiComponents();
}
/// 解析tags信息
Map<String, String> _parseTagsInfo(Map<String, dynamic> jsonData) {
final tagsInfo = <String, String>{};
try {
final tags = jsonData['tags'] as List<dynamic>?;
if (tags != null) {
for (final tag in tags) {
if (tag is Map<String, dynamic>) {
final name = tag['name'] as String?;
final description = tag['description'] as String?;
if (name != null && description != null) {
tagsInfo[name] = description;
}
}
}
}
} on Object catch (e) {
_logger.warning('⚠️ 解析tags信息时发生错误: $e');
}
return tagsInfo;
}
/// 解析API路径
Map<String, ApiPath> _parseApiPaths(Map<String, dynamic> jsonData) {
final paths = <String, ApiPath>{};
final pathsData = jsonData['paths'] as Map<String, dynamic>?;
if (pathsData == null) {
throw SwaggerParseException('未发现API路径定义');
}
try {
pathsData.forEach((pathKey, pathValue) {
if (pathValue is Map<String, dynamic>) {
pathValue.forEach((methodKey, methodValue) {
if (methodValue is Map<String, dynamic>) {
final method = HttpMethod.fromString(methodKey);
final apiPath = ApiPath.fromJson(
pathKey,
method,
methodValue,
);
final key = '${method.value.toUpperCase()}_'
'${pathKey.replaceAll('/', '_')}';
paths[key] = apiPath;
}
});
}
});
} catch (e) {
throw SwaggerParseException('解析API路径失败', details: e.toString());
}
return paths;
}
/// 解析API控制器
Map<String, ApiController> _parseApiControllers(
Map<String, ApiPath> paths,
Map<String, String> tagsInfo,
) {
final controllers = <String, List<ApiPath>>{};
try {
// 根据tags分组API路径
for (final apiPath in paths.values) {
for (final tag in apiPath.tags) {
controllers.putIfAbsent(tag, () => []).add(apiPath);
}
}
// 创建控制器对象
final result = <String, ApiController>{};
controllers.forEach((name, pathList) {
// 从tags信息中获取描述
final swaggerDescription = tagsInfo[name];
// 使用通用的描述获取方法
final description = SwaggerConfig.getControllerDescription(
name,
swaggerDescription: swaggerDescription,
);
result[name] = ApiController(
name: name,
description: description,
paths: pathList,
);
});
return result;
} catch (e) {
throw SwaggerParseException('解析API控制器失败', details: e.toString());
}
}
/// 解析属性类型
String parsePropertyType(Map<String, dynamic> propData) {
try {
// 直接类型
if (propData['type'] != null) {
return propData['type'] as String;
}
// 引用类型 ($ref)
if (propData[r'$ref'] != null) {
final ref = propData[r'$ref'] as String;
// 从 #/components/schemas/ModelName 或 #/definitions/ModelName 中提取类型名
final parts = ref.split('/');
if (parts.isNotEmpty) {
return parts.last;
}
}
// 数组类型
if (propData['items'] != null) {
final items = propData['items'] as Map<String, dynamic>;
final itemType = parsePropertyType(items);
return 'array<$itemType>';
}
// 默认类型
return 'string';
} catch (e) {
throw SwaggerParseException('解析属性类型失败', details: e.toString());
}
}
/// 获取Dart类型映射
String getDartType(String swaggerType, String? format) {
switch (swaggerType.toLowerCase()) {
case 'string':
switch (format?.toLowerCase()) {
case 'date':
case 'date-time':
return 'DateTime';
case 'byte':
case 'binary':
return 'String';
default:
return 'String';
}
case 'integer':
switch (format?.toLowerCase()) {
case 'int64':
return 'int';
case 'int32':
default:
return 'int';
}
case 'number':
switch (format?.toLowerCase()) {
case 'float':
case 'double':
return 'double';
default:
return 'double';
}
case 'boolean':
return 'bool';
case 'array':
return 'List<dynamic>';
case 'object':
return 'Map<String, dynamic>';
case 'file':
return 'String';
default:
// 检查是否为数组类型
if (swaggerType.startsWith('array<') && swaggerType.endsWith('>')) {
final itemType = swaggerType.substring(6, swaggerType.length - 1);
final dartItemType = getDartType(itemType, null);
return 'List<$dartItemType>';
}
// 默认为自定义类型
return StringUtils.generateClassName(swaggerType);
}
}
/// 清除缓存
void clearCache() {
_cachedDocuments.clear();
_cacheManager.clear();
}
/// 获取文档统计信息
Map<String, dynamic> getDocumentStats() {
if (_cachedDocuments.isEmpty) {
return {};
}
// 使用第一个缓存的文档
final doc = _cachedDocuments.values.first;
// 统计HTTP方法
final methodStats = <String, int>{};
for (final path in doc.paths.values) {
final method = path.method.value;
methodStats[method] = (methodStats[method] ?? 0) + 1;
}
// 统计模型类型
final modelStats = <String, int>{};
for (final model in doc.models.values) {
if (model.isEnum) {
modelStats['enum'] = (modelStats['enum'] ?? 0) + 1;
} else {
modelStats['class'] = (modelStats['class'] ?? 0) + 1;
}
}
return {
'title': doc.title,
'version': doc.version,
'paths': doc.paths.length,
'models': doc.models.length,
'controllers': doc.controllers.length,
'methods': methodStats,
'modelTypes': modelStats,
};
}
/// 分析 schemas 在 API 中的使用情况
/// 返回每个 schema 的用途类型集合
Map<String, Set<ModelUsageType>> _analyzeSchemaUsage(
Map<String, dynamic> jsonData,
) {
final schemaUsage = <String, Set<ModelUsageType>>{};
final pathsData = jsonData['paths'] as Map<String, dynamic>?;
if (pathsData == null) return schemaUsage;
pathsData.forEach((pathKey, pathValue) {
if (pathValue is! Map<String, dynamic>) return;
pathValue.forEach((methodKey, methodValue) {
if (methodValue is! Map<String, dynamic>) return;
// 分析 requestBody 中使用的 schemas
final requestBody = methodValue['requestBody'] as Map<String, dynamic>?;
if (requestBody != null) {
_extractSchemasFromContent(
requestBody['content'] as Map<String, dynamic>?,
schemaUsage,
ModelUsageType.request,
);
}
// 分析 parameters 中使用的 schemas
final parameters = methodValue['parameters'] as List<dynamic>?;
if (parameters != null) {
for (final param in parameters) {
if (param is Map<String, dynamic>) {
final schema = param['schema'] as Map<String, dynamic>?;
if (schema != null) {
_extractSchemaRef(schema, schemaUsage, ModelUsageType.request);
}
}
}
}
// 分析 responses 中使用的 schemas
final responses = methodValue['responses'] as Map<String, dynamic>?;
if (responses != null) {
responses.forEach((statusCode, responseValue) {
if (responseValue is Map<String, dynamic>) {
_extractSchemasFromContent(
responseValue['content'] as Map<String, dynamic>?,
schemaUsage,
ModelUsageType.response,
);
}
});
}
});
});
return schemaUsage;
}
/// 从 content 中提取 schema 引用
void _extractSchemasFromContent(
Map<String, dynamic>? content,
Map<String, Set<ModelUsageType>> schemaUsage,
ModelUsageType usageType,
) {
if (content == null) return;
content.forEach((mediaType, mediaTypeValue) {
if (mediaTypeValue is Map<String, dynamic>) {
final schema = mediaTypeValue['schema'] as Map<String, dynamic>?;
if (schema != null) {
_extractSchemaRef(schema, schemaUsage, usageType);
}
}
});
}
/// 递归提取 schema $ref 并记录用途
void _extractSchemaRef(
Map<String, dynamic> schema,
Map<String, Set<ModelUsageType>> schemaUsage,
ModelUsageType usageType,
) {
// 处理 $ref
final ref = schema[r'$ref'] as String?;
if (ref != null) {
// 从 #/components/schemas/ModelName 提取 ModelName
final schemaName = ref.split('/').last;
schemaUsage.putIfAbsent(schemaName, () => {}).add(usageType);
return; // $ref 存在时,不再处理其他字段
}
// 处理数组类型
if (schema['type'] == 'array') {
final items = schema['items'] as Map<String, dynamic>?;
if (items != null) {
_extractSchemaRef(items, schemaUsage, usageType);
}
}
// 处理 allOf/oneOf/anyOf
for (final key in ['allOf', 'oneOf', 'anyOf']) {
final schemas = schema[key] as List<dynamic>?;
if (schemas != null) {
for (final s in schemas) {
if (s is Map<String, dynamic>) {
_extractSchemaRef(s, schemaUsage, usageType);
}
}
}
}
// 处理对象属性
final properties = schema['properties'] as Map<String, dynamic>?;
if (properties != null) {
properties.forEach((propName, propSchema) {
if (propSchema is Map<String, dynamic>) {
_extractSchemaRef(propSchema, schemaUsage, usageType);
}
});
}
// 处理 additionalProperties
final additionalProperties = schema['additionalProperties'];
if (additionalProperties is Map<String, dynamic>) {
_extractSchemaRef(additionalProperties, schemaUsage, usageType);
}
}
}