swagger_generator_flutter/lib/commands/generate_command.dart

725 lines
24 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:io';
import 'package:path/path.dart' as path;
import '../core/config.dart';
import '../core/models.dart';
import '../generators/documentation_generator.dart';
import '../generators/endpoint_code_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<CommandOption> get options => [
const CommandOption(
name: 'endpoints',
shortName: 'e',
description: '生成API端点常量',
type: OptionType.flag,
),
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<int> execute(List<String> args) async {
try {
final parsedArgs = parseArguments(args);
validateArguments(parsedArgs);
await prepare(parsedArgs);
// 解析所有 Swagger 文档
progress('正在解析 ${SwaggerConfig.swaggerJsonUrls.length} 个 Swagger 文档...');
final parser = SwaggerDataParser();
// 合并所有文档的 paths 和 models
SwaggerDocument? mergedDocument;
for (int i = 0; i < SwaggerConfig.swaggerJsonUrls.length; i++) {
final url = SwaggerConfig.swaggerJsonUrls[i];
progress(
' [${i + 1}/${SwaggerConfig.swaggerJsonUrls.length}] 正在解析: $url');
final doc = await parser.fetchAndParseSwaggerDocument(url);
if (mergedDocument == null) {
mergedDocument = doc;
} else {
// 合并 paths 和 models
mergedDocument = SwaggerDocument(
title: mergedDocument.title,
description: mergedDocument.description,
version: '${mergedDocument.version} + ${doc.version}',
paths: {...mergedDocument.paths, ...doc.paths},
models: {...mergedDocument.models, ...doc.models},
controllers: {...mergedDocument.controllers, ...doc.controllers},
);
}
}
if (mergedDocument == null) {
print('❌ 没有成功解析任何 Swagger 文档');
return 1;
}
final document = mergedDocument;
success('成功合并 ${SwaggerConfig.swaggerJsonUrls.length} 个 Swagger 文档');
// 解析生成选项
final options = _parseGenerateOptions(parsedArgs);
final fullOutputDir = FileUtils.getProjectRootGeneratorDir();
progress('输出目录: $fullOutputDir');
// 确保输出目录存在
await FileUtils.ensureDirectoryExists(fullOutputDir);
int generatedFiles = 0;
// 生成端点代码
if (options.generateEndpoints) {
progress('正在生成API端点常量...');
final generator = EndpointCodeGenerator(document);
final code = generator.generate();
final filePath = '$fullOutputDir/api_paths.dart';
await FileUtils.writeFile(filePath, code);
success('API端点常量已保存到: $filePath');
generatedFiles++;
}
// 生成模型代码
if (options.generateModels) {
progress('正在生成数据模型...');
final generator = ModelCodeGenerator(
document,
useSimpleModels: options.useSimpleModels,
);
final modelsDir = '$fullOutputDir/api_models';
await FileUtils.ensureDirectoryExists(modelsDir);
final modelFiles = generator.generateSeparateModelFiles();
for (final entry in modelFiles.entries) {
final filePath = '$modelsDir/${entry.key}';
await FileUtils.writeFile(filePath, entry.value);
success('模型文件已保存到: $filePath');
generatedFiles++;
}
}
// 生成 Retrofit 风格的 API 接口(默认使用拆分模式)
if (options.generateApi) {
progress('正在按版本和tags分组生成Retrofit风格API接口...');
final apiDir = '$fullOutputDir/api';
await FileUtils.ensureDirectoryExists(apiDir);
// 🎯 先按版本分组 paths
final pathsByVersion = <String, List<ApiPath>>{};
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 = <String, Map<String, String>>{};
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';
await FileUtils.ensureDirectoryExists(versionDir);
for (final fileEntry in files.entries) {
final fileName = fileEntry.key;
final code = fileEntry.value;
final filePath = '$versionDir/$fileName';
await FileUtils.writeFile(filePath, code);
success('API接口文件已保存到: $filePath');
generatedFiles++;
}
// 生成该版本目录的 index.dart
await _generateVersionIndexFile(versionDir, files.keys.toList());
success('$version/index.dart 已生成');
}
// 生成主 API 文件ApiClient
final mainCode = _generateVersionedApiClient(versionedFiles);
final mainFilePath = '$apiDir/api_client.dart';
await FileUtils.writeFile(mainFilePath, mainCode);
success('主API接口文件已保存到: $mainFilePath');
generatedFiles++;
// 生成参数实体类文件(使用最后一个生成器)
final lastGenerator = RetrofitApiGenerator(
className: 'ApiClient',
useRetrofit: true,
useDio: true,
);
lastGenerator.document = document;
lastGenerator.ensureParameterEntitiesGenerated();
final parameterEntityFiles =
lastGenerator.generateParameterEntityFiles();
if (parameterEntityFiles.isNotEmpty) {
final modelsDir = '$fullOutputDir/api_models';
// Parameters 文件放在独立的 parameters 子目录中
final parametersDir = '$modelsDir/parameters';
await FileUtils.ensureDirectoryExists(parametersDir);
for (final entry in parameterEntityFiles.entries) {
final filePath = '$parametersDir/${entry.key}';
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 modelsDir = '$fullOutputDir/api_models';
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 = '$fullOutputDir/api_documentation.md';
await FileUtils.writeFile(filePath, code);
success('API文档已保存到: $filePath');
generatedFiles++;
}
// 生成摘要
_generateSummary(document, fullOutputDir);
success('代码生成完成!共生成 $generatedFiles 个文件');
return 0;
} catch (e) {
print('❌ 生成失败: $e');
return 1;
}
}
/// 解析生成选项
GenerateOptions _parseGenerateOptions(ParsedArguments args) {
final hasAnyFlag = args.hasOption('endpoints') ||
args.hasOption('models') ||
args.hasOption('docs') ||
args.hasOption('api');
return GenerateOptions(
generateEndpoints: hasAnyFlag
? (args.getOption<bool>('endpoints') ?? false)
: (args.getOption<bool>('all') ?? true),
generateModels: hasAnyFlag
? (args.getOption<bool>('models') ?? false)
: (args.getOption<bool>('all') ?? true),
generateDocs: hasAnyFlag
? (args.getOption<bool>('docs') ?? false)
: (args.getOption<bool>('all') ?? true),
generateApi: hasAnyFlag
? (args.getOption<bool>('api') ?? false)
: (args.getOption<bool>('all') ?? true),
useSimpleModels: args.getOption<bool>('simple') ?? false,
splitByTags: args.getOption<bool>('split-by-tags') ?? true, // 默认启用拆分模式
);
}
/// 获取所有模型文件列表
Future<List<String>> _getAllModelFiles(String modelsDir) async {
try {
final directory = Directory(modelsDir);
if (!await directory.exists()) {
return [];
}
final files = await directory.list().toList();
final exportPaths = <String>[];
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<void> _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 = <String>[];
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')) {
dartFiles.add(fileName);
}
}
}
// 按文件名排序
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<String> 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('');
// 导出所有文件
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 versionMatch = RegExp(r'/api/v(\d+)/').firstMatch(path);
if (versionMatch != null) {
return 'v${versionMatch.group(1)}';
}
return 'v1'; // 默认版本
}
/// 从 API 代码中检测版本
/// 通过检查代码中的 API 路径来确定版本
String _detectApiVersion(String code) {
// 查找第一个 @GET/@POST/@PUT/@DELETE 注解中的路径
final pathMatch =
RegExp(r"@(?:GET|POST|PUT|DELETE|PATCH)\('(/api/v\d+/[^']+)'")
.firstMatch(code);
if (pathMatch != null) {
final path = pathMatch.group(1)!;
final versionMatch = RegExp(r'/api/v(\d+)/').firstMatch(path);
if (versionMatch != null) {
return 'v${versionMatch.group(1)}';
}
}
return 'v1'; // 默认版本
}
/// 为代码中的类名添加版本后缀
/// 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<String, Map<String, String>> 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 = <String, Set<String>>{}; // 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<void> _generateVersionIndexFile(
String versionDir, List<String> 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 generateEndpoints;
final bool generateModels;
final bool generateDocs;
final bool generateApi;
final bool useSimpleModels;
final bool splitByTags;
const GenerateOptions({
required this.generateEndpoints,
required this.generateModels,
required this.generateDocs,
required this.generateApi,
required this.useSimpleModels,
required this.splitByTags,
});
}