import 'dart:io'; import 'package:path/path.dart' as path; import 'package:swagger_generator_flutter/commands/base_command.dart'; import 'package:swagger_generator_flutter/core/config.dart'; import 'package:swagger_generator_flutter/core/config_loader.dart'; import 'package:swagger_generator_flutter/core/models.dart'; import 'package:swagger_generator_flutter/generators/model_code_generator.dart'; import 'package:swagger_generator_flutter/generators/retrofit_api_generator.dart'; import 'package:swagger_generator_flutter/parsers/swagger_data_parser.dart'; import 'package:swagger_generator_flutter/utils/file_utils.dart'; import 'package:swagger_generator_flutter/utils/logger.dart'; /// Generate命令 /// 用于生成各种代码文件 class GenerateCommand extends BaseCommand { @override String get name => 'generate'; @override String get description => '生成API代码文件(模型、端点等)'; @override String get usage => 'dart swagger_cli.dart generate [options]'; @override List get options => [ const CommandOption( name: 'models', shortName: 'm', description: '生成数据模型', ), const CommandOption( name: 'api', shortName: 'r', description: '生成Retrofit风格API接口', ), const CommandOption( name: 'split-by-tags', shortName: 't', description: '按tags分组生成多个API文件(默认启用)', ), const CommandOption( name: 'all', shortName: 'a', description: '生成所有文件(默认)', ), const CommandOption( name: 'output-dir', shortName: 'o', description: '输出目录', type: OptionType.string, defaultValue: 'generator', ), const CommandOption( name: 'included-tags', shortName: 'i', description: '只生成指定tags的API和模型(逗号分隔,如:User,Pet,Store)', type: OptionType.string, ), const CommandOption( name: 'excluded-tags', shortName: 'e', description: '从生成中排除指定的tags(逗号分隔)', type: OptionType.string, ), ]; @override Future execute(List args) async { try { final parsedArgs = parseArguments(args); validateArguments(parsedArgs); await prepare(parsedArgs); // 解析所有 Swagger 文档 progress('正在解析 ${SwaggerConfig.swaggerJsonUrls.length} 个 Swagger 文档...'); final parser = SwaggerDataParser(); // 合并所有文档的 paths 和 models // 注意:合并策略是后面的文档会覆盖前面的同名模型和路径 // 因此建议将高版本(如 V2)配置在低版本(如 V1)之后,以确保高版本的模型覆盖低版本 SwaggerDocument? mergedDocument; final urls = SwaggerConfig.swaggerJsonUrls; progress('URL 处理顺序: ${urls.join(" -> ")}'); for (var i = 0; i < urls.length; i++) { final url = urls[i]; progress(' [${i + 1}/${urls.length}] 正在解析: $url'); final doc = await parser.fetchAndParseSwaggerDocument(url); progress(' 解析完成: ${doc.models.length} 个模型, ${doc.paths.length} 个路径'); if (mergedDocument == null) { mergedDocument = doc; progress(' 初始文档: ${doc.models.length} 个模型'); } else { // 合并 paths 和 models // 重要:使用 {...mergedDocument.models, ...doc.models} // 后面的 doc.models 会覆盖前面的 mergedDocument.models 中的同名 key // 这样可以确保高版本的模型定义覆盖低版本的模型定义 final beforeModelCount = mergedDocument.models.length; final currentModelCount = doc.models.length; // 找出会被覆盖的模型(同名模型) final overlappingModels = []; for (final key in doc.models.keys) { if (mergedDocument.models.containsKey(key)) { overlappingModels.add(key); } } if (overlappingModels.isNotEmpty) { progress( ' 发现 ${overlappingModels.length} 个同名模型将被覆盖: ' '${overlappingModels.take(5).join(", ")}' '${overlappingModels.length > 5 ? "..." : ""}', ); } mergedDocument = SwaggerDocument( title: mergedDocument.title, description: mergedDocument.description, version: '${mergedDocument.version} + ${doc.version}', // 注意:后面的 doc 会覆盖前面的 mergedDocument 中的同名 key // 确保 V2 的模型覆盖 V1 的同名模型 paths: {...mergedDocument.paths, ...doc.paths}, models: {...mergedDocument.models, ...doc.models}, controllers: {...mergedDocument.controllers, ...doc.controllers}, ); final afterModelCount = mergedDocument.models.length; progress( ' 合并后: $beforeModelCount + $currentModelCount ' '-> $afterModelCount 个模型', ); // 验证同名模型是否被正确覆盖 if (overlappingModels.isNotEmpty) { progress( ' 同名模型列表: ' '${overlappingModels.take(10).join(", ")}' '${overlappingModels.length > 10 ? "..." : ""}', ); } } } if (mergedDocument == null) { appLogger.severe('❌ 没有成功解析任何 Swagger 文档'); return 1; } success('成功合并 ${SwaggerConfig.swaggerJsonUrls.length} 个 Swagger 文档'); // 解析生成选项 final options = _parseGenerateOptions(parsedArgs); // 根据 includedTags 和 excludedTags 过滤文档 final document = _filterDocumentByTags( mergedDocument, options.includedTags, options.excludedTags, ); // 使用配置的输出目录 final baseDir = SwaggerConfig.generatorDir; final apiDir = SwaggerConfig.apiDir; final modelsDir = SwaggerConfig.modelsDir; progress('输出目录: $baseDir'); progress('API 目录: $apiDir'); progress('模型目录: $modelsDir'); // 确保输出目录存在 await FileUtils.ensureDirectoryExists(baseDir); await FileUtils.ensureDirectoryExists(apiDir); await FileUtils.ensureDirectoryExists(modelsDir); var generatedFiles = 0; // 生成模型代码 if (options.generateModels) { progress('正在生成数据模型...'); final generator = ModelCodeGenerator(document); await FileUtils.ensureDirectoryExists(modelsDir); final modelFiles = generator.generateSeparateModelFiles(); for (final entry in modelFiles.entries) { final filePath = '$modelsDir/${entry.key}'; // 检查是否跳过此文件 if (ConfigLoader.shouldSkipFile(filePath)) { progress('跳过文件: $filePath'); continue; } await FileUtils.writeFile(filePath, entry.value); success('模型文件已保存到: $filePath'); generatedFiles++; } } // 生成 Retrofit 风格的 API 接口(默认使用拆分模式) if (options.generateApi) { progress('正在按版本和tags分组生成Retrofit风格API接口...'); await FileUtils.ensureDirectoryExists(apiDir); // 🎯 先按版本分组 paths final pathsByVersion = >{}; for (final path in document.paths.values) { final version = _extractVersionFromPath(path.path); pathsByVersion.putIfAbsent(version, () => []).add(path); } progress( '检测到 ${pathsByVersion.keys.length} 个版本: ' '${pathsByVersion.keys.join(", ")}', ); // ✨ 按版本分别生成 API 文件 final versionedFiles = >{}; for (final versionEntry in pathsByVersion.entries) { final version = versionEntry.key; final versionPaths = versionEntry.value; progress(' 正在生成 $version 版本 API(${versionPaths.length} 个接口)...'); // 筛选出当前版本实际使用的 controllers final versionTags = versionPaths.expand((p) => p.tags).toSet(); final versionControllers = { for (final tag in versionTags) if (document.controllers.containsKey(tag)) tag: document.controllers[tag]!, }; // 创建该版本的临时文档 final versionDocument = SwaggerDocument( title: document.title, description: document.description, version: document.version, paths: {for (final p in versionPaths) p.path: p}, models: document.models, controllers: versionControllers, // 使用过滤后的 controllers ); // 创建生成器(使用配置的类名) final apiClientClassName = ConfigLoader.getApiClientClassName(); final generator = RetrofitApiGenerator( className: apiClientClassName, ) ..document = versionDocument ..ensureParameterEntitiesGenerated(); // 生成该版本的 API 文件 final tagApiFiles = generator.generateApiFilesByTags(); versionedFiles[version] = {}; for (final entry in tagApiFiles.entries) { final fileName = entry.key; var code = entry.value; // 添加版本后缀到类名 code = _addVersionSuffixToCode(code, version); versionedFiles[version]![fileName] = code; } } // 按版本写入文件 for (final versionEntry in versionedFiles.entries) { final version = versionEntry.key; final files = versionEntry.value; final versionDir = '$apiDir/$version'; // 检查是否跳过此版本目录 if (ConfigLoader.shouldSkipFile(versionDir)) { progress('跳过版本目录: $versionDir'); continue; } await FileUtils.ensureDirectoryExists(versionDir); for (final fileEntry in files.entries) { final fileName = fileEntry.key; final code = fileEntry.value; final filePath = '$versionDir/$fileName'; // 检查是否跳过此文件 if (ConfigLoader.shouldSkipFile(filePath)) { progress('跳过文件: $filePath'); continue; } await FileUtils.writeFile(filePath, code); success('API接口文件已保存到: $filePath'); generatedFiles++; } // 生成该版本目录的 index.dart(如果目录未被跳过) if (!ConfigLoader.shouldSkipFile(versionDir)) { await _generateVersionIndexFile(versionDir, files.keys.toList()); success('$version/index.dart 已生成'); } } // 生成主 API 文件(使用配置的文件名和类名) final apiClientClassName = ConfigLoader.getApiClientClassName(); final apiClientFileName = ConfigLoader.getApiClientFileName(); final mainCode = _generateVersionedApiClient(versionedFiles); final mainFilePath = '$apiDir/$apiClientFileName.dart'; // 检查是否跳过主 API 文件 if (!ConfigLoader.shouldSkipFile(mainFilePath)) { await FileUtils.writeFile(mainFilePath, mainCode); success('主API接口文件已保存到: $mainFilePath'); generatedFiles++; } else { progress('跳过文件: $mainFilePath'); } // 生成参数实体类文件(使用最后一个生成器) final lastGenerator = RetrofitApiGenerator( className: apiClientClassName, ) ..document = document ..ensureParameterEntitiesGenerated(); final parameterEntityFiles = lastGenerator.generateParameterEntityFiles(); if (parameterEntityFiles.isNotEmpty) { // Parameters 文件放在独立的 parameters 子目录中 final parametersDir = '$modelsDir/parameters'; await FileUtils.ensureDirectoryExists(parametersDir); for (final entry in parameterEntityFiles.entries) { final filePath = '$parametersDir/${entry.key}'; // 检查是否跳过此文件 if (ConfigLoader.shouldSkipFile(filePath)) { progress('跳过文件: $filePath'); continue; } await FileUtils.writeFile(filePath, entry.value); success('参数实体类文件已保存到: $filePath'); generatedFiles++; } // 生成 parameters/index.dart await _generateSubDirectoryIndexFile(parametersDir); } } // 重新生成 index.dart 文件,包含所有生成的文件 if (options.generateModels || options.generateApi) { progress('正在重新生成 index.dart 文件...'); final allFiles = await _getAllModelFiles(modelsDir); final indexContent = _generateUpdatedIndexFile(allFiles); final indexPath = '$modelsDir/index.dart'; await FileUtils.writeFile(indexPath, indexContent); success('index.dart 文件已更新'); } // 生成摘要 await _generateSummary(document, baseDir); success('代码生成完成!共生成 $generatedFiles 个文件'); return 0; } on Exception catch (e, stackTrace) { appLogger.severe('❌ 生成失败', e, stackTrace); return 1; } } /// 解析生成选项 GenerateOptions _parseGenerateOptions(ParsedArguments args) { final hasAnyFlag = args.hasOption('models') || args.hasOption('api'); // 解析 included-tags 参数 // 优先级:命令行参数 > 配置文件 List? includedTags; final includedTagsStr = args.getOption('included-tags'); if (includedTagsStr != null && includedTagsStr.isNotEmpty) { // 从命令行参数读取 includedTags = includedTagsStr .split(',') .map((tag) => tag.trim()) .where((tag) => tag.isNotEmpty) .toList(); if (includedTags.isNotEmpty) { progress('🏷️ [命令行] 只生成以下 tags: ${includedTags.join(", ")}'); } } else { // 从配置文件读取 includedTags = ConfigLoader.getIncludedTags(); if (includedTags != null && includedTags.isNotEmpty) { progress('🏷️ [配置文件] 只生成以下 tags: ${includedTags.join(", ")}'); } } // 解析 split-by-tags 参数 // 优先级:命令行参数 > 配置文件 > 默认值(true) bool splitByTags; if (args.hasOption('split-by-tags')) { // 从命令行参数读取 splitByTags = args.getOption('split-by-tags') ?? true; progress('📂 [命令行] 按 tags 分组: ${splitByTags ? "是" : "否"}'); } else { // 从配置文件读取 splitByTags = ConfigLoader.getSplitByTags(); progress('📂 [配置文件] 按 tags 分组: ${splitByTags ? "是" : "否"}'); } // 解析 excluded-tags 参数 // 优先级:命令行参数 > 配置文件 List? excludedTags; final excludedTagsStr = args.getOption('excluded-tags'); if (excludedTagsStr != null && excludedTagsStr.isNotEmpty) { // 从命令行参数读取 excludedTags = excludedTagsStr .split(',') .map((tag) => tag.trim()) .where((tag) => tag.isNotEmpty) .toList(); if (excludedTags.isNotEmpty) { progress('🚫 [命令行] 排除以下 tags: ${excludedTags.join(", ")}'); } } else { // 从配置文件读取 excludedTags = ConfigLoader.getExcludedTags(); if (excludedTags != null && excludedTags.isNotEmpty) { progress('🚫 [配置文件] 排除以下 tags: ${excludedTags.join(", ")}'); } } return GenerateOptions( generateModels: hasAnyFlag ? (args.getOption('models') ?? false) : (args.getOption('all') ?? true), generateApi: hasAnyFlag ? (args.getOption('api') ?? false) : (args.getOption('all') ?? true), splitByTags: splitByTags, includedTags: includedTags, excludedTags: excludedTags, ); } /// 获取所有模型文件列表 Future> _getAllModelFiles(String modelsDir) async { try { final directory = Directory(modelsDir); if (!directory.existsSync()) { return []; } final files = directory.listSync(); final exportPaths = []; for (final entity in files) { if (entity is Directory) { // 如果是子目录,导出子目录的 index.dart final dirName = path.basename(entity.path); // 检查子目录是否有 index.dart final subIndexPath = path.join(entity.path, 'index.dart'); if (File(subIndexPath).existsSync()) { exportPaths.add('$dirName/index.dart'); } } else if (entity is File && entity.path.endsWith('.dart')) { final fileName = path.basename(entity.path); // 排除 index.dart 本身和 .g.dart 文件 if (fileName != 'index.dart' && !fileName.endsWith('.g.dart')) { // 直接在 api_models 目录下的文件(如 Parameters 文件) exportPaths.add(fileName); } } } // 按路径排序:先子目录,后直接文件 exportPaths.sort((a, b) { final aIsDir = a.contains('/'); final bIsDir = b.contains('/'); if (aIsDir && !bIsDir) return -1; if (!aIsDir && bIsDir) return 1; return a.compareTo(b); }); return exportPaths; } on Exception catch (e, stackTrace) { appLogger.severe('获取模型文件列表失败', e, stackTrace); return []; } } /// 为子目录生成 index.dart 文件 Future _generateSubDirectoryIndexFile(String subDir) async { try { final directory = Directory(subDir); if (!directory.existsSync()) return; final dirName = path.basename(subDir); // 获取子目录下的所有 .dart 文件 final files = directory.listSync(); final dartFiles = []; for (final entity in files) { if (entity is File && entity.path.endsWith('.dart')) { final fileName = path.basename(entity.path); // 排除 index.dart 本身和 .g.dart 文件 if (fileName != 'index.dart' && !fileName.endsWith('.g.dart')) { // 检查文件是否在 ignored_files 配置中 final filePath = path.join(subDir, fileName); if (!ConfigLoader.shouldSkipFile(filePath)) { dartFiles.add(fileName); } else { progress(' 跳过导出文件: $fileName (在 ignored_files 配置中)'); } } } } // 按文件名排序 dartFiles.sort(); // 生成 index.dart 内容 final buffer = StringBuffer() ..writeln('// 模型导出文件') ..writeln('// 基于 Swagger API 文档: ') ..writeln('// 由 xy_swagger_generator by max 生成') ..writeln('// Copyright (C) 2025 YuanXuan. All rights reserved.') ..writeln() ..writeln() ..writeln('library;') ..writeln(); for (final fileName in dartFiles) { buffer.writeln("export '$fileName';"); } // 写入文件 final indexPath = path.join(subDir, 'index.dart'); await FileUtils.writeFile(indexPath, buffer.toString()); success('$dirName/index.dart 已生成,包含 ${dartFiles.length} 个文件'); } on Exception catch (e, stackTrace) { appLogger.severe('生成 index.dart 失败', e, stackTrace); } } /// 生成更新的 index.dart 文件内容 String _generateUpdatedIndexFile(List fileNames) { final buffer = StringBuffer() // 生成文件头 ..writeln('// API 模型导出文件') ..writeln('// 基于 Swagger API 文档: ') ..writeln('// 由 xy_swagger_generator by max 生成') ..writeln('// Copyright (C) 2025 YuanXuan. All rights reserved.') ..writeln() ..writeln('library;') ..writeln(); // 导出 base_result 和 base_page_result(如果配置了) final baseResultImport = SwaggerConfig.baseResultImport; final basePageResultImport = SwaggerConfig.basePageResultImport; if (baseResultImport.isNotEmpty) { buffer.writeln("export '$baseResultImport';"); } if (basePageResultImport.isNotEmpty) { buffer.writeln("export '$basePageResultImport';"); } if ((baseResultImport.isNotEmpty || basePageResultImport.isNotEmpty) && fileNames.isNotEmpty) { buffer.writeln(); } // 导出所有文件 for (final fileName in fileNames) { buffer.writeln("export '$fileName';"); } return buffer.toString(); } /// 生成摘要信息 Future _generateSummary( SwaggerDocument document, String outputDir, ) async { final summary = StringBuffer() ..writeln('# 代码生成摘要') ..writeln() ..writeln('**API标题**: ${document.title}') ..writeln('**API版本**: ${document.version}') ..writeln('**生成时间**: ${DateTime.now().toIso8601String()}') ..writeln() ..writeln('## 统计信息') ..writeln('- 控制器数量: ${document.controllers.length}') ..writeln('- API路径数量: ${document.paths.length}') ..writeln('- 数据模型数量: ${document.models.length}') ..writeln() ..writeln('## 控制器列表'); document.controllers.forEach((name, controller) { summary.writeln( '- **$name**: ${controller.description} ' '(${controller.paths.length} 个路径)', ); }); await FileUtils.writeFile('$outputDir/SUMMARY.md', summary.toString()); } /// 从 API 路径中提取版本号 /// 例如: /api/v1/User/GetData → v1 /// /api/v2/User/GetData → v2 /// /api/User/GetData → v1 (默认) /// 使用配置文件中的版本提取模式 String _extractVersionFromPath(String path) { // 从配置文件读取版本提取模式 final pattern = ConfigLoader.getVersionExtractionPattern(); final defaultVersion = ConfigLoader.getDefaultVersion(); try { final versionMatch = RegExp(pattern).firstMatch(path); if (versionMatch != null && versionMatch.groupCount > 0) { return 'v${versionMatch.group(1)}'; } } on FormatException { // 如果正则表达式无效,使用默认模式 const defaultPattern = r'/api/v(\d+)/'; final versionMatch = RegExp(defaultPattern).firstMatch(path); if (versionMatch != null) { return 'v${versionMatch.group(1)}'; } } return defaultVersion; } /// 为代码中的类名添加版本后缀 /// V1: MobileManagerApi(不加后缀) /// V2: MobileManagerApiV2(加V2后缀) String _addVersionSuffixToCode(String code, String version) { // V1 不添加后缀,直接返回 if (version == 'v1') { return code; } final versionUpper = version.toUpperCase(); // v2 → V2, v3 → V3 var updatedCode = code; // 替换 abstract class 声明 updatedCode = updatedCode.replaceAllMapped( RegExp(r'abstract class (\w+Api)\b'), (match) => 'abstract class ${match.group(1)}$versionUpper', ); // 替换 factory 构造函数 updatedCode = updatedCode.replaceAllMapped( RegExp(r'factory (\w+Api)\('), (match) => 'factory ${match.group(1)}$versionUpper(', ); // 替换实现类引用 = _XXXApi updatedCode = updatedCode.replaceAllMapped( RegExp(r'= _(\w+Api);'), (match) => '= _${match.group(1)}$versionUpper;', ); // 替换 part 文件名 updatedCode = updatedCode.replaceAllMapped( RegExp(r"part '(\w+)\.g\.dart';"), (match) => "part '${match.group(1)}.g.dart';", ); // 更新 import 路径(如果有引用其他 API) updatedCode = updatedCode.replaceAllMapped( RegExp(r"import '../(\w+_api)\.dart';"), (match) => "import '../$version/${match.group(1)}.dart';", ); return updatedCode; } /// 生成版本化的 ApiClient String _generateVersionedApiClient( Map> versionedFiles, ) { final buffer = StringBuffer() // 文件头 ..writeln('// 统一 API 客户端') ..writeln('// 支持多版本 API 管理') ..writeln('// 由 xy_swagger_generator by max 生成') ..writeln('// Copyright (C) 2025 YuanXuan. All rights reserved.') ..writeln() ..writeln("import 'package:dio/dio.dart';") ..writeln(); // 收集所有 API 类 final apiClasses = >{}; // version -> class names for (final versionEntry in versionedFiles.entries) { final version = versionEntry.key; final files = versionEntry.value; apiClasses[version] = {}; for (final entry in files.entries) { final fileName = entry.key; final code = entry.value; // 优先从代码里解析真正的类名(避免从文件名推断时丢失缩写,如 QRCode -> QCode) final extracted = _extractApiClassNamesFromCode(code); if (extracted.isNotEmpty) { apiClasses[version]!.addAll(extracted.toSet()); continue; } // 兜底:从文件名提取类名: mobile_manager_api.dart → MobileManagerApi final className = fileName .replaceAll('.dart', '') .split('_') .map( (word) => word.isEmpty ? '' : (word[0].toUpperCase() + word.substring(1)), ) .join(); apiClasses[version]!.add(className); } } // 导入所有版本的 index.dart final versions = apiClasses.keys.toList()..sort(); for (final version in versions) { buffer.writeln("import '$version/index.dart';"); } buffer ..writeln() ..writeln('/// 统一 API 客户端'); // 生成 API Client 类(使用配置的类名) final apiClientClassName = ConfigLoader.getApiClientClassName(); buffer ..writeln('/// 支持多版本 API 访问') ..writeln('class $apiClientClassName {') ..writeln(' final Dio _dio;') ..writeln(); // 生成各版本 API 实例字段 for (final versionEntry in apiClasses.entries) { final version = versionEntry.key; final versionUpper = version == 'v1' ? '' : version.toUpperCase(); // v1不加后缀 for (final className in versionEntry.value) { final suffix = version == 'v1' ? '' : versionUpper; buffer.writeln( ' late final $className$suffix ' '_${_toLowerCamelCase(className)}$suffix;', ); } } buffer ..writeln() ..writeln(' $apiClientClassName(this._dio) {') ..writeln(' _initApis();') ..writeln(' }') ..writeln() ..writeln(' void _initApis() {'); for (final versionEntry in apiClasses.entries) { final version = versionEntry.key; final versionUpper = version == 'v1' ? '' : version.toUpperCase(); // v1不加后缀 for (final className in versionEntry.value) { final fieldName = _toLowerCamelCase(className); final suffix = version == 'v1' ? '' : versionUpper; buffer.writeln(' _$fieldName$suffix = $className$suffix(_dio);'); } } buffer ..writeln(' }') ..writeln() // 生成显式版本访问属性 ..writeln(' // ========== 版本化 API 访问 ==========') ..writeln(); for (final versionEntry in apiClasses.entries) { final version = versionEntry.key; final versionUpper = version == 'v1' ? '' : version.toUpperCase(); // v1不加后缀 final versionLabel = version == 'v1' ? 'V1(默认版本)' : '${version.toUpperCase()} 版本'; buffer.writeln(' /// $versionLabel API'); for (final className in versionEntry.value) { final fieldName = _toLowerCamelCase(className); final suffix = version == 'v1' ? '' : versionUpper; buffer.writeln( ' $className$suffix get $fieldName$suffix => _$fieldName$suffix;', ); } buffer.writeln(); } buffer.writeln('}'); return buffer.toString(); } /// 从生成的 API 源码中提取 API 类名(如 QRCodeApi、MobileManagerApi 等) /// 仅匹配形如 "abstract class XxxApi {" 的声明 List _extractApiClassNamesFromCode(String code) { try { final regex = RegExp(r'abstract\s+class\s+(\w+Api)\b'); final matches = regex.allMatches(code); if (matches.isEmpty) return const []; return matches.map((m) => m.group(1)!).toList(); } on FormatException { return const []; } } /// 转换为小驼峰命名 /// MobileManagerApi → mobileManager String _toLowerCamelCase(String className) { // 移除 'Api' 后缀 final name = className.replaceAll('Api', ''); // 首字母小写 return name[0].toLowerCase() + name.substring(1); } /// 生成版本目录的 index.dart Future _generateVersionIndexFile( String versionDir, List fileNames, ) async { final buffer = StringBuffer() ..writeln('// API 接口导出文件') ..writeln('// 由 xy_swagger_generator by max 生成') ..writeln('// Copyright (C) 2025 YuanXuan. All rights reserved.') ..writeln(); // 导出所有 API 文件 final sortedFiles = fileNames.toList()..sort(); for (final fileName in sortedFiles) { buffer.writeln("export '$fileName';"); } final indexPath = '$versionDir/index.dart'; await FileUtils.writeFile(indexPath, buffer.toString()); } /// 根据 includedTags 和 excludedTags 过滤文档 SwaggerDocument _filterDocumentByTags( SwaggerDocument document, List? includedTags, List? excludedTags, ) { final hasIncludes = includedTags != null && includedTags.isNotEmpty; final hasExcludes = excludedTags != null && excludedTags.isNotEmpty; // 如果没有指定任何过滤条件,返回原文档 if (!hasIncludes && !hasExcludes) { return document; } progress('🔍 正在根据 tags 过滤文档...'); if (hasIncludes) progress(' 只保留 tags: ${includedTags.join(", ")}'); if (hasExcludes) progress(' 排除 tags: ${excludedTags.join(", ")}'); // 过滤 paths final filteredPaths = {}; final usedModelNames = {}; for (final entry in document.paths.entries) { final path = entry.value; final pathTags = path.tags; // 1. Inclusion check: 如果提供了 included_tags,则路径必须至少有一个 tag 在列表中 final included = !hasIncludes || pathTags.any((tag) => includedTags.contains(tag)); if (!included) { continue; // 不满足包含条件,跳过 } // 2. Exclusion check: 如果提供了 excluded_tags,则路径的所有 tags 不能都在排除列表中 // 换句话说,如果路径的所有 tags 都在排除列表中,则排除该路径。 final excluded = hasExcludes && pathTags.isNotEmpty && pathTags.every((tag) => excludedTags.contains(tag)); if (excluded) { continue; // 满足排除条件,跳过 } // 如果路径通过了所有检查,则保留它 filteredPaths[entry.key] = path; _collectUsedModels(path, usedModelNames); } progress(' 保留了 ${filteredPaths.length}/${document.paths.length} 个接口'); // 过滤 models:只保留被使用的 models (此逻辑与之前相同) final filteredModels = {}; final modelsToCheck = Set.from(usedModelNames); final checkedModels = {}; while (modelsToCheck.isNotEmpty) { final modelName = modelsToCheck.first; modelsToCheck.remove(modelName); if (checkedModels.contains(modelName)) { continue; } checkedModels.add(modelName); final model = document.models[modelName]; if (model != null) { filteredModels[modelName] = model; _collectModelDependencies(model, modelsToCheck, checkedModels); } } progress(' 保留了 ${filteredModels.length}/${document.models.length} 个模型'); // 过滤 controllers final filteredControllers = {}; for (final entry in document.controllers.entries) { final tagName = entry.key; var shouldKeep = true; if (hasIncludes && !includedTags.contains(tagName)) { shouldKeep = false; } if (hasExcludes && excludedTags.contains(tagName)) { shouldKeep = false; } if (shouldKeep) { filteredControllers[tagName] = entry.value; } } progress( ' 保留了 ${filteredControllers.length}/${document.controllers.length} 个控制器', ); // 返回过滤后的文档 return SwaggerDocument( title: document.title, version: document.version, description: document.description, servers: document.servers, components: document.components, paths: filteredPaths, models: filteredModels, controllers: filteredControllers, security: document.security, ); } /// 收集 ApiPath 使用的所有 model 名称 void _collectUsedModels(ApiPath path, Set usedModelNames) { // 递归地从 schema 中提取模型名称 void extractModelsFromSchema(Map schema) { if (schema.containsKey(r'$ref')) { final modelName = _extractModelNameFromRef(schema[r'$ref'] as String); if (modelName != null) { usedModelNames.add(modelName); } return; } if (schema.containsKey('type')) { final type = schema['type']; if (type == 'array' && schema.containsKey('items')) { extractModelsFromSchema(schema['items'] as Map); } else if (type == 'object' && schema.containsKey('properties')) { final properties = schema['properties'] as Map; for (final propSchema in properties.values) { extractModelsFromSchema(propSchema as Map); } } } for (final key in ['allOf', 'anyOf', 'oneOf']) { if (schema.containsKey(key)) { final subSchemas = schema[key] as List; for (final subSchema in subSchemas) { extractModelsFromSchema(subSchema as Map); } } } } // 从 requestBody 收集 if (path.requestBody != null) { for (final mediaType in path.requestBody!.content.values) { if (mediaType.schema != null) { extractModelsFromSchema(mediaType.schema!); } } } // 从 responses 收集 for (final response in path.responses.values) { for (final mediaType in response.content.values) { if (mediaType.schema != null) { extractModelsFromSchema(mediaType.schema!); } } } } /// 收集 ApiModel 依赖的其他 models void _collectModelDependencies( ApiModel model, Set modelsToCheck, Set checkedModels, ) { // 从 properties 收集 for (final property in model.properties.values) { // 使用 reference 字段 if (property.reference != null && !checkedModels.contains(property.reference)) { modelsToCheck.add(property.reference!); } // 处理数组类型 - items 是 ApiModel,有 name 字段 if (property.type == PropertyType.array && property.items != null) { final itemsName = property.items!.name; if (itemsName.isNotEmpty && !checkedModels.contains(itemsName)) { modelsToCheck.add(itemsName); } } // 处理嵌套属性中的引用 for (final nestedProp in property.nestedProperties.values) { if (nestedProp.reference != null && !checkedModels.contains(nestedProp.reference)) { modelsToCheck.add(nestedProp.reference!); } } } // 从 allOf, oneOf, anyOf 收集 - 使用 reference 字段 for (final schema in [...model.allOf, ...model.oneOf, ...model.anyOf]) { if (schema.reference != null) { final modelName = _extractModelNameFromRef(schema.reference!); if (modelName != null && !checkedModels.contains(modelName)) { modelsToCheck.add(modelName); } } } } /// 从 $ref 中提取 model 名称 /// 例如:#/components/schemas/User -> User String? _extractModelNameFromRef(String ref) { if (ref.startsWith('#/components/schemas/')) { return ref.substring('#/components/schemas/'.length); } if (ref.startsWith('#/definitions/')) { return ref.substring('#/definitions/'.length); } return null; } } /// 生成选项 class GenerateOptions { const GenerateOptions({ required this.generateModels, required this.generateApi, required this.splitByTags, this.includedTags, this.excludedTags, }); final bool generateModels; final bool generateApi; final bool splitByTags; final List? includedTags; final List? excludedTags; }