From d9e46be9ddaa3c731c25cb526849542f88393f63 Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 14 Jul 2025 18:46:43 +0800 Subject: [PATCH] feat: base page support --- lib/generators/model_code_generator.dart | 29 ++- lib/generators/retrofit_api_generator.dart | 209 ++++++++++++++++++++- 2 files changed, 227 insertions(+), 11 deletions(-) diff --git a/lib/generators/model_code_generator.dart b/lib/generators/model_code_generator.dart index 634ed81..67e8d87 100644 --- a/lib/generators/model_code_generator.dart +++ b/lib/generators/model_code_generator.dart @@ -287,8 +287,13 @@ class ModelCodeGenerator extends ModelGenerator { Map generateSeparateModelFiles() { final files = {}; - // 生成所有模型文件 + // 生成所有模型文件,但过滤掉分页响应文件 for (final model in document.models.values) { + // 检查是否是分页响应模型(包含 total 和 items 字段) + if (_isPaginationResponseModel(model)) { + continue; // 跳过分页响应模型,使用统一的 BasePageResult + } + final fileName = StringUtils.generateFileName(model.name); final content = generateSingleModelFile(model); files[fileName] = content; @@ -296,6 +301,7 @@ class ModelCodeGenerator extends ModelGenerator { // 生成 index.dart 文件 final modelFileNames = document.models.keys + .where((name) => !_isPaginationResponseModel(document.models[name]!)) .map((name) => StringUtils.generateFileName(name)) .toList(); final indexContent = generateIndexFile(modelFileNames); @@ -679,4 +685,25 @@ class ModelCodeGenerator extends ModelGenerator { return annotations.join(', '); } + + /// 检查是否是分页响应模型(包含 total 和 items 字段) + bool _isPaginationResponseModel(ApiModel model) { + // 检查是否包含 total 和 items 字段 + if (!model.properties.containsKey('total') || + !model.properties.containsKey('items')) { + return false; + } + + final totalProp = model.properties['total']!; + final itemsProp = model.properties['items']!; + + // 检查 total 字段是否为数字类型 + final isTotalNumeric = totalProp.type == PropertyType.integer || + totalProp.type == PropertyType.number; + + // 检查 items 字段是否为数组类型 + final isItemsArray = itemsProp.type == PropertyType.array; + + return isTotalNumeric && isItemsArray; + } } diff --git a/lib/generators/retrofit_api_generator.dart b/lib/generators/retrofit_api_generator.dart index 8953a64..a998f31 100644 --- a/lib/generators/retrofit_api_generator.dart +++ b/lib/generators/retrofit_api_generator.dart @@ -118,7 +118,7 @@ class RetrofitApiGenerator extends BaseGenerator { final modelImports = _getRequiredModelImports(); for (final modelImport in modelImports) { buffer.writeln( - 'import \'../api_models/${StringUtils.generateFileName(modelImport)}\';'); + 'import \'../../api_models/${StringUtils.generateFileName(modelImport)}\';'); } if (modelImports.isNotEmpty) { @@ -281,18 +281,115 @@ class RetrofitApiGenerator extends BaseGenerator { /// 包装返回类型为BaseResult或BasePageResult String _wrapWithBaseResult(String originalType, ApiPath? path) { - // 检查是否是列表类型且可能需要分页 - if (originalType.startsWith('List<') && - _isPageableType(originalType, path)) { - // 提取List中的类型 - final innerType = originalType.substring(5, originalType.length - 1); - return 'BaseResult>'; + // 特殊处理 void 类型(如健康检查接口) + if (originalType == 'void') { + return 'BaseResult'; + } + + // 检查是否是列表类型 + if (originalType.startsWith('List<')) { + // 如果是从 schema 中识别出的分页响应(包含 total 和 items),使用 BasePageResult + if (_isPaginationResponseFromSchema(originalType, path)) { + final innerType = originalType.substring(5, originalType.length - 1); + return 'BaseResult>'; + } + + // 如果响应 schema 直接返回数组(没有 total 和 items),使用 List + if (path != null && _isDirectArrayResponse(path)) { + return 'BaseResult<$originalType>'; + } + + // 其他列表类型,检查是否需要分页 + if (_isPageableType(originalType, path)) { + final innerType = originalType.substring(5, originalType.length - 1); + return 'BaseResult>'; + } } // 对于其他类型,使用BaseResult包装 return 'BaseResult<$originalType>'; } + /// 检查是否是从 schema 中识别出的分页响应 + bool _isPaginationResponseFromSchema(String type, ApiPath? path) { + if (path == null) return false; + + // 检查响应 schema 是否包含 total 和 items 字段 + final successResponses = ['200', '201', '202']; + + for (final statusCode in successResponses) { + final response = path.responses[statusCode]; + if (response != null) { + // 检查 content.application/json.schema (Swagger 3.0) + if (response.content != null) { + final applicationJson = + response.content!['application/json'] as Map?; + if (applicationJson != null) { + final schema = applicationJson['schema'] as Map?; + if (schema != null && _hasPaginationSchema(schema)) { + return true; + } + } + } + + // 检查 schema 字段 (Swagger 2.0) + if (response.schema != null && _hasPaginationSchema(response.schema!)) { + return true; + } + } + } + + return false; + } + + /// 检查是否是分页响应模型(包含 total 和 items 字段) + bool _isPaginationResponseModel(ApiModel model) { + // 检查是否包含 total 和 items 字段 + if (!model.properties.containsKey('total') || + !model.properties.containsKey('items')) { + return false; + } + + final totalProp = model.properties['total']!; + final itemsProp = model.properties['items']!; + + // 检查 total 字段是否为数字类型 + final isTotalNumeric = totalProp.type == PropertyType.integer || + totalProp.type == PropertyType.number; + + // 检查 items 字段是否为数组类型 + final isItemsArray = itemsProp.type == PropertyType.array; + + return isTotalNumeric && isItemsArray; + } + + /// 检查 schema 是否包含分页结构(total 和 items 字段) + bool _hasPaginationSchema(Map schema) { + if (schema['type'] != 'object') return false; + + final properties = schema['properties'] as Map?; + if (properties == null) return false; + + // 检查是否包含 total 和 items 字段 + if (!properties.containsKey('total') || !properties.containsKey('items')) { + return false; + } + + final totalProp = properties['total'] as Map?; + final itemsProp = properties['items'] as Map?; + + // 检查 total 字段是否为数字类型 + final isTotalNumeric = totalProp != null && + (totalProp['type'] == 'integer' || totalProp['type'] == 'number'); + + // 检查 items 字段是否为数组类型 + final isItemsArray = itemsProp != null && + itemsProp['type'] == 'array' && + itemsProp['items'] != null; + + return isTotalNumeric && isItemsArray; + } + /// 智能判断是否是可分页的类型 bool _isPageableType(String type, ApiPath? path) { if (path == null) { @@ -326,8 +423,8 @@ class RetrofitApiGenerator extends BaseGenerator { score += 0.5; } - // 需要达到阈值才认为是分页 - return score >= 4; + // 降低阈值,让更多列表类型被识别为分页 + return score >= 2; } /// 检查是否包含分页相关的关键词 @@ -466,6 +563,40 @@ class RetrofitApiGenerator extends BaseGenerator { return paginationPathPatterns.any((pattern) => pattern.hasMatch(pathLower)); } + /// 检查响应是否直接返回数组(没有 total 和 items 字段) + bool _isDirectArrayResponse(ApiPath path) { + final successResponses = ['200', '201', '202']; + + for (final statusCode in successResponses) { + final response = path.responses[statusCode]; + if (response != null) { + // 检查 content.application/json.schema (Swagger 3.0) + if (response.content != null) { + final applicationJson = + response.content!['application/json'] as Map?; + if (applicationJson != null) { + final schema = applicationJson['schema'] as Map?; + if (schema != null && _isArraySchema(schema)) { + return true; + } + } + } + + // 检查 schema 字段 (Swagger 2.0) + if (response.schema != null && _isArraySchema(response.schema!)) { + return true; + } + } + } + + return false; + } + + /// 检查 schema 是否为数组类型 + bool _isArraySchema(Map schema) { + return schema['type'] == 'array'; + } + /// 从响应中提取返回类型 String? _extractResponseType(ApiResponse response) { // 优先检查 content.application/json.schema (Swagger 3.0) @@ -502,6 +633,27 @@ class RetrofitApiGenerator extends BaseGenerator { final refName = parts.last; // 检查是否是已知的模型类型 if (document.models.containsKey(refName)) { + final model = document.models[refName]!; + // 检查是否是分页响应模型 + if (_isPaginationResponseModel(model)) { + // 提取 items 中的类型 + final itemsProp = model.properties['items']; + if (itemsProp != null && itemsProp.type == PropertyType.array) { + String itemType = 'dynamic'; + if (itemsProp.reference != null) { + itemType = StringUtils.generateClassName(itemsProp.reference!); + } else if (itemsProp.items != null) { + // itemsProp.items 可能是 ApiModel,尝试用 name + itemType = StringUtils.generateClassName(itemsProp.items!.name); + } else if (itemsProp.name.isNotEmpty) { + itemType = StringUtils.generateClassName(itemsProp.name); + } else if (itemsProp.type != PropertyType.array && + itemsProp.type != PropertyType.reference) { + itemType = itemsProp.type.value; + } + return 'List<$itemType>'; + } + } return StringUtils.generateClassName(refName); } // 尝试生成类名 @@ -522,6 +674,34 @@ class RetrofitApiGenerator extends BaseGenerator { if (schema['type'] == 'object') { // 检查是否有 properties 定义 if (schema['properties'] != null) { + final properties = schema['properties'] as Map; + + // 检查是否是分页响应格式(包含 total 和 items 字段) + if (properties.containsKey('total') && + properties.containsKey('items')) { + final totalProp = properties['total'] as Map?; + final itemsProp = properties['items'] as Map?; + + // 检查 total 字段是否为数字类型 + final isTotalNumeric = totalProp != null && + (totalProp['type'] == 'integer' || totalProp['type'] == 'number'); + + // 检查 items 字段是否为数组类型 + final isItemsArray = itemsProp != null && + itemsProp['type'] == 'array' && + itemsProp['items'] != null; + + if (isTotalNumeric && isItemsArray) { + // 这是一个分页响应,提取 items 中的类型 + final itemsSchema = itemsProp['items'] as Map; + final itemType = _extractTypeFromSchema(itemsSchema); + if (itemType != null) { + // 返回 List 格式,让 _wrapWithBaseResult 处理为 BasePageResult + return 'List<$itemType>'; + } + } + } + // 这是一个复杂对象,返回 Map return 'Map'; } @@ -1134,7 +1314,7 @@ class RetrofitApiGenerator extends BaseGenerator { final modelImports = _getRequiredModelImportsForPaths(paths); for (final modelImport in modelImports) { buffer.writeln( - 'import \'../api_models/${StringUtils.generateFileName(modelImport)}\';'); + 'import \'../../api_models/${StringUtils.generateFileName(modelImport)}\';'); } if (modelImports.isNotEmpty) { @@ -1259,6 +1439,11 @@ class RetrofitApiGenerator extends BaseGenerator { return; } + // 检查是否是分页响应类型(以 PageResponse 结尾) + if (cleanType.endsWith('PageResponse')) { + return; // 跳过分页响应类型,因为我们已经使用 BasePageResult 替代 + } + // 添加模型类型到导入列表(使用转换后的类名) imports.add(StringUtils.generateClassName(cleanType)); } @@ -1482,6 +1667,10 @@ class RetrofitApiGenerator extends BaseGenerator { String? _inferTypeFromPathKeywords( String pathLower, String summaryLower, List tags) { // 基于路径关键词的智能推断 + if (pathLower.contains('healthcheck') || pathLower.contains('health')) { + return 'void'; + } + if (pathLower.contains('/login/')) { return 'UserLoginResult'; }