import 'dart:io'; import 'package:path/path.dart' as path; import '../core/config.dart'; import '../core/config_loader.dart'; import '../core/models.dart'; import '../generators/documentation_generator.dart'; import '../generators/model_code_generator.dart'; import '../generators/retrofit_api_generator.dart'; import '../parsers/swagger_data_parser.dart'; import '../utils/file_utils.dart'; import 'base_command.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: '生成数据模型', type: OptionType.flag, ), const CommandOption( name: 'docs', shortName: 'd', description: '生成API文档', type: OptionType.flag, ), const CommandOption( name: 'api', shortName: 'r', description: '生成Retrofit风格API接口', type: OptionType.flag, ), const CommandOption( name: 'split-by-tags', shortName: 't', description: '按tags分组生成多个API文件(默认启用)', type: OptionType.flag, ), const CommandOption( name: 'all', shortName: 'a', description: '生成所有文件(默认)', type: OptionType.flag, ), const CommandOption( name: 'simple', shortName: 's', description: '使用简洁版模型生成', type: OptionType.flag, ), const CommandOption( name: 'output-dir', shortName: 'o', description: '输出目录', type: OptionType.string, defaultValue: 'generator', ), ]; @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 (int 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) { print('❌ 没有成功解析任何 Swagger 文档'); return 1; } final document = mergedDocument; success('成功合并 ${SwaggerConfig.swaggerJsonUrls.length} 个 Swagger 文档'); // 解析生成选项 final options = _parseGenerateOptions(parsedArgs); // 使用配置的输出目录 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); int generatedFiles = 0; // 生成模型代码 if (options.generateModels) { progress('正在生成数据模型...'); final generator = ModelCodeGenerator( document, useSimpleModels: options.useSimpleModels, ); 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} 个接口)...'); // 创建该版本的临时文档 final versionDocument = SwaggerDocument( title: document.title, description: document.description, version: document.version, paths: {for (var p in versionPaths) p.path: p}, models: document.models, controllers: document.controllers, ); // 创建生成器 final generator = RetrofitApiGenerator( className: 'ApiClient', useRetrofit: true, useDio: true, splitByTags: true, versionedApi: true, ); generator.document = versionDocument; generator.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 文件(ApiClient) final mainCode = _generateVersionedApiClient(versionedFiles); final mainFilePath = '$apiDir/api_client.dart'; // 检查是否跳过主 API 文件 if (!ConfigLoader.shouldSkipFile(mainFilePath)) { await FileUtils.writeFile(mainFilePath, mainCode); success('主API接口文件已保存到: $mainFilePath'); generatedFiles++; } else { progress('跳过文件: $mainFilePath'); } // 生成参数实体类文件(使用最后一个生成器) final lastGenerator = RetrofitApiGenerator( className: 'ApiClient', useRetrofit: true, useDio: true, ); lastGenerator.document = document; lastGenerator.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 文件已更新'); } // 生成文档 if (options.generateDocs) { progress('正在生成API文档...'); final generator = DocumentationGenerator(document); final code = generator.generate(); final filePath = '$baseDir/api_documentation.md'; // 检查是否跳过文档文件 if (!ConfigLoader.shouldSkipFile(filePath)) { await FileUtils.writeFile(filePath, code); success('API文档已保存到: $filePath'); generatedFiles++; } else { progress('跳过文件: $filePath'); } } // 生成摘要 _generateSummary(document, baseDir); success('代码生成完成!共生成 $generatedFiles 个文件'); return 0; } catch (e) { print('❌ 生成失败: $e'); return 1; } } /// 解析生成选项 GenerateOptions _parseGenerateOptions(ParsedArguments args) { final hasAnyFlag = args.hasOption('models') || args.hasOption('docs') || args.hasOption('api'); return GenerateOptions( generateModels: hasAnyFlag ? (args.getOption('models') ?? false) : (args.getOption('all') ?? true), generateDocs: hasAnyFlag ? (args.getOption('docs') ?? false) : (args.getOption('all') ?? true), generateApi: hasAnyFlag ? (args.getOption('api') ?? false) : (args.getOption('all') ?? true), useSimpleModels: args.getOption('simple') ?? false, splitByTags: args.getOption('split-by-tags') ?? true, // 默认启用拆分模式 ); } /// 获取所有模型文件列表 Future> _getAllModelFiles(String modelsDir) async { try { final directory = Directory(modelsDir); if (!await directory.exists()) { return []; } final files = await directory.list().toList(); 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 (await File(subIndexPath).exists()) { 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; } catch (e) { print('获取模型文件列表失败: $e'); return []; } } /// 为子目录生成 index.dart 文件 Future _generateSubDirectoryIndexFile(String subDir) async { try { final directory = Directory(subDir); if (!await directory.exists()) return; final dirName = path.basename(subDir); // 获取子目录下的所有 .dart 文件 final files = await directory.list().toList(); 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(); buffer.writeln('// 模型导出文件'); buffer.writeln('// 基于 Swagger API 文档: '); buffer.writeln('// 由 xy_swagger_generator by max 生成'); buffer.writeln('// Copyright (C) 2025 YuanXuan. All rights reserved.'); buffer.writeln(''); buffer.writeln(''); buffer.writeln('library;'); buffer.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} 个文件'); } catch (e) { print('生成 index.dart 失败: $e'); } } /// 生成更新的 index.dart 文件内容 String _generateUpdatedIndexFile(List fileNames) { final buffer = StringBuffer(); // 生成文件头 buffer.writeln('// API 模型导出文件'); buffer.writeln('// 基于 Swagger API 文档: '); buffer.writeln('// 由 xy_swagger_generator by max 生成'); buffer.writeln('// Copyright (C) 2025 YuanXuan. All rights reserved.'); buffer.writeln(''); buffer.writeln('library;'); buffer.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(); } /// 生成摘要信息 void _generateSummary(SwaggerDocument document, String outputDir) { final summary = StringBuffer(); summary.writeln('# 代码生成摘要'); summary.writeln(''); summary.writeln('**API标题**: ${document.title}'); summary.writeln('**API版本**: ${document.version}'); summary.writeln('**生成时间**: ${DateTime.now().toIso8601String()}'); summary.writeln(''); summary.writeln('## 统计信息'); summary.writeln('- 控制器数量: ${document.controllers.length}'); summary.writeln('- API路径数量: ${document.paths.length}'); summary.writeln('- 数据模型数量: ${document.models.length}'); summary.writeln(''); summary.writeln('## 控制器列表'); document.controllers.forEach((name, controller) { summary.writeln( '- **$name**: ${controller.description} (${controller.paths.length} 个路径)'); }); 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)}'; } } catch (e) { // 如果正则表达式无效,使用默认模式 final 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 // 替换 abstract class 声明 code = code.replaceAllMapped( RegExp(r'abstract class (\w+Api)\b'), (match) => 'abstract class ${match.group(1)}$versionUpper', ); // 替换 factory 构造函数 code = code.replaceAllMapped( RegExp(r'factory (\w+Api)\('), (match) => 'factory ${match.group(1)}$versionUpper(', ); // 替换实现类引用 = _XXXApi code = code.replaceAllMapped( RegExp(r'= _(\w+Api);'), (match) => '= _${match.group(1)}$versionUpper;', ); // 替换 part 文件名 code = code.replaceAllMapped( RegExp(r"part '(\w+)\.g\.dart';"), (match) => "part '${match.group(1)}.g.dart';", ); // 更新 import 路径(如果有引用其他 API) code = code.replaceAllMapped( RegExp(r"import '../(\w+_api)\.dart';"), (match) => "import '../$version/${match.group(1)}.dart';", ); return code; } /// 生成版本化的 ApiClient String _generateVersionedApiClient( Map> versionedFiles) { final buffer = StringBuffer(); // 文件头 buffer.writeln('// 统一 API 客户端'); buffer.writeln('// 支持多版本 API 管理'); buffer.writeln('// 由 xy_swagger_generator by max 生成'); buffer.writeln('// Copyright (C) 2025 YuanXuan. All rights reserved.'); buffer.writeln(); buffer.writeln('import \'package:dio/dio.dart\';'); buffer.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 fileName in files.keys) { // 从文件名提取类名: mobile_manager_api.dart → MobileManagerApi final className = fileName .replaceAll('.dart', '') .split('_') .map((word) => 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(); // 生成 ApiClient 类 buffer.writeln('/// 统一 API 客户端'); buffer.writeln('/// 支持多版本 API 访问'); buffer.writeln('class ApiClient {'); buffer.writeln(' final Dio _dio;'); buffer.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(); // 构造函数 buffer.writeln(' ApiClient(this._dio) {'); buffer.writeln(' _initApis();'); buffer.writeln(' }'); buffer.writeln(); // 初始化方法 buffer.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(' }'); buffer.writeln(); // 生成显式版本访问属性 buffer.writeln(' // ========== 版本化 API 访问 =========='); buffer.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(); } /// 转换为小驼峰命名 /// 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(); // 文件头 buffer.writeln('// API 接口导出文件'); buffer.writeln('// 由 xy_swagger_generator by max 生成'); buffer.writeln('// Copyright (C) 2025 YuanXuan. All rights reserved.'); buffer.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()); } } /// 生成选项 class GenerateOptions { final bool generateModels; final bool generateDocs; final bool generateApi; final bool useSimpleModels; final bool splitByTags; const GenerateOptions({ required this.generateModels, required this.generateDocs, required this.generateApi, required this.useSimpleModels, required this.splitByTags, }); }