589 lines
18 KiB
Dart
589 lines
18 KiB
Dart
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);
|
||
}
|
||
}
|
||
}
|