import 'dart:io'; import 'package:path/path.dart' as path; import 'package:swagger_generator_flutter/commands/generate_command.dart' show GenerateOptions; import 'package:swagger_generator_flutter/commands/services/service_typedefs.dart'; import 'package:swagger_generator_flutter/core/config.dart'; import 'package:swagger_generator_flutter/core/config_repository.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/utils/file_utils.dart'; import 'package:swagger_generator_flutter/utils/logger.dart'; class GenerationOutputService { const GenerationOutputService(); static ConfigRepository? _cachedConfig; ConfigRepository get _config => _cachedConfig ??= ConfigRepository.loadSync(); Future generateOutputs({ required SwaggerDocument document, required GenerateOptions options, required String baseDir, required String apiDir, required String modelsDir, required LogCallback progress, required LogCallback success, }) async { await FileUtils.ensureDirectoryExists(baseDir); await FileUtils.ensureDirectoryExists(apiDir); await FileUtils.ensureDirectoryExists(modelsDir); var generatedFiles = 0; if (options.generateModels) { generatedFiles += await _generateModels( document, modelsDir, progress, success, ); } if (options.generateApi) { generatedFiles += await _generateApis( document, apiDir, modelsDir, progress, success, ); } if (options.generateModels || options.generateApi) { await _regenerateModelsIndex(modelsDir, success); } await _generateSummary(document, baseDir); return generatedFiles; } Future _generateModels( SwaggerDocument document, String modelsDir, LogCallback progress, LogCallback success, ) async { progress('正在生成数据模型...'); final generator = ModelCodeGenerator(document); final modelFiles = generator.generateSeparateModelFiles(); var generatedFiles = 0; for (final entry in modelFiles.entries) { final filePath = '$modelsDir/${entry.key}'; if (_config.shouldSkipFile(filePath)) { progress('跳过文件: $filePath'); continue; } await FileUtils.writeFile(filePath, entry.value); success('模型文件已保存到: $filePath'); generatedFiles++; } return generatedFiles; } Future _generateApis( SwaggerDocument document, String apiDir, String modelsDir, LogCallback progress, LogCallback success, ) async { progress('正在按版本和tags分组生成Retrofit风格API接口...'); await FileUtils.ensureDirectoryExists(apiDir); final pathsByVersion = _groupPathsByVersion(document); progress( '检测到 ${pathsByVersion.keys.length} 个版本: ' '${pathsByVersion.keys.join(", ")}', ); final versionedFiles = await _buildVersionedApis( document, pathsByVersion, progress, ); var generatedFiles = 0; generatedFiles += await _writeVersionedApis( apiDir, versionedFiles, progress, success, ); generatedFiles += await _writeMainApiFile( apiDir, versionedFiles, success, ); generatedFiles += await _writeParameterEntities( document, modelsDir, success, progress, ); return generatedFiles; } Map> _groupPathsByVersion(SwaggerDocument document) { final pathsByVersion = >{}; for (final path in document.paths.values) { final version = _extractVersionFromPath(path.path); pathsByVersion.putIfAbsent(version, () => []).add(path); } return pathsByVersion; } Future>> _buildVersionedApis( SwaggerDocument document, Map> pathsByVersion, LogCallback progress, ) async { final versionedFiles = >{}; final apiClientClassName = _config.apiClientClassName; for (final versionEntry in pathsByVersion.entries) { final version = versionEntry.key; final versionPaths = versionEntry.value; progress(' 正在生成 $version 版本 API(${versionPaths.length} 个接口)...'); 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) SwaggerDocument.buildPathKey(p.path, p.method): p, }, models: document.models, controllers: versionControllers, ); final generator = RetrofitApiGenerator( className: apiClientClassName, ) ..document = versionDocument ..ensureParameterEntitiesGenerated(); 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; } } return versionedFiles; } Future _writeVersionedApis( String apiDir, Map> versionedFiles, LogCallback progress, LogCallback success, ) async { var generatedFiles = 0; for (final versionEntry in versionedFiles.entries) { final version = versionEntry.key; final files = versionEntry.value; final versionDir = '$apiDir/$version'; if (_config.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 (_config.shouldSkipFile(filePath)) { progress('跳过文件: $filePath'); continue; } await FileUtils.writeFile(filePath, code); success('API接口文件已保存到: $filePath'); generatedFiles++; } if (!_config.shouldSkipFile(versionDir)) { await _generateVersionIndexFile(versionDir, files.keys.toList()); success('$version/index.dart 已生成'); } } return generatedFiles; } Future _writeMainApiFile( String apiDir, Map> versionedFiles, LogCallback success, ) async { final apiClientFileName = _config.apiClientFileName; final mainCode = _generateVersionedApiClient(versionedFiles); final mainFilePath = '$apiDir/$apiClientFileName.dart'; if (!_config.shouldSkipFile(mainFilePath)) { await FileUtils.writeFile(mainFilePath, mainCode); success('主API接口文件已保存到: $mainFilePath'); return 1; } return 0; } Future _writeParameterEntities( SwaggerDocument document, String modelsDir, LogCallback success, LogCallback progress, ) async { final apiClientClassName = _config.apiClientClassName; final lastGenerator = RetrofitApiGenerator( className: apiClientClassName, ) ..document = document ..ensureParameterEntitiesGenerated(); final parameterEntityFiles = lastGenerator.generateParameterEntityFiles(); if (parameterEntityFiles.isEmpty) { return 0; } final parametersDir = '$modelsDir/parameters'; await FileUtils.ensureDirectoryExists(parametersDir); var generatedFiles = 0; for (final entry in parameterEntityFiles.entries) { final filePath = '$parametersDir/${entry.key}'; if (_config.shouldSkipFile(filePath)) { progress('跳过文件: $filePath'); continue; } await FileUtils.writeFile(filePath, entry.value); success('参数实体类文件已保存到: $filePath'); generatedFiles++; } await _generateSubDirectoryIndexFile(parametersDir, success); return generatedFiles; } Future _regenerateModelsIndex( String modelsDir, LogCallback success, ) async { final allFiles = await _getAllModelFiles(modelsDir); final indexContent = _generateUpdatedIndexFile(allFiles); final indexPath = '$modelsDir/index.dart'; await FileUtils.writeFile(indexPath, indexContent); success('index.dart 文件已更新'); } 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) { final dirName = path.basename(entity.path); 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); if (fileName != 'index.dart' && !fileName.endsWith('.g.dart')) { 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 []; } } Future _generateSubDirectoryIndexFile( String subDir, LogCallback success, ) async { final directory = Directory(subDir); if (!directory.existsSync()) return; final dirName = path.basename(subDir); 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); if (fileName != 'index.dart' && !fileName.endsWith('.g.dart')) { final filePath = path.join(subDir, fileName); if (!_config.shouldSkipFile(filePath)) { dartFiles.add(fileName); } } } } dartFiles.sort(); 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} 个文件'); } 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(); 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()); } String _extractVersionFromPath(String path) { final pattern = _config.versionExtractionPattern; final defaultVersion = _config.defaultVersion; 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; } String _addVersionSuffixToCode(String code, String version) { if (version == 'v1') { return code; } final versionUpper = version.toUpperCase(); var updatedCode = code; updatedCode = updatedCode.replaceAllMapped( RegExp(r'abstract class (\w+Api)\b'), (match) => 'abstract class ${match.group(1)}$versionUpper', ); updatedCode = updatedCode.replaceAllMapped( RegExp(r'factory (\w+Api)\('), (match) => 'factory ${match.group(1)}$versionUpper(', ); updatedCode = updatedCode.replaceAllMapped( RegExp(r'= _(\w+Api);'), (match) => '= _${match.group(1)}$versionUpper;', ); updatedCode = updatedCode.replaceAllMapped( RegExp(r"part '(\w+)\.g\.dart';"), (match) => "part '${match.group(1)}.g.dart';", ); updatedCode = updatedCode.replaceAllMapped( RegExp(r"import '../(\w+_api)\.dart';"), (match) => "import '../$version/${match.group(1)}.dart';", ); return updatedCode; } 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(); final apiClasses = >{}; for (final versionEntry in versionedFiles.entries) { final version = versionEntry.key; final files = versionEntry.value; apiClasses[version] = {}; for (final entry in files.entries) { final code = entry.value; final extracted = _extractApiClassNamesFromCode(code); if (extracted.isNotEmpty) { apiClasses[version]!.addAll(extracted.toSet()); continue; } final fileName = entry.key; final className = fileName .replaceAll('.dart', '') .split('_') .map( (word) => word.isEmpty ? '' : (word[0].toUpperCase() + word.substring(1)), ) .join(); apiClasses[version]!.add(className); } } final versions = apiClasses.keys.toList()..sort(); for (final version in versions) { buffer.writeln("import '$version/index.dart';"); } buffer ..writeln() ..writeln('/// 统一 API 客户端'); final apiClientClassName = _config.apiClientClassName; buffer ..writeln('/// 支持多版本 API 访问') ..writeln('class $apiClientClassName {') ..writeln(' final Dio _dio;') ..writeln(); for (final versionEntry in apiClasses.entries) { final version = versionEntry.key; final versionUpper = version == 'v1' ? '' : version.toUpperCase(); 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(); 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(); 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(); } 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 []; } } String _toLowerCamelCase(String className) { final name = className.replaceAll('Api', ''); return name[0].toLowerCase() + name.substring(1); } 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(); 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()); } }