Merge branch 'release/2.1.2'
This commit is contained in:
commit
c1a6c94357
|
|
@ -11,6 +11,9 @@ generator:
|
|||
# 输入配置
|
||||
input:
|
||||
# Swagger 文档源(支持多版本)
|
||||
# 注意:多个 URL 会按顺序合并,后面的文档会覆盖前面的同名模型和路径
|
||||
# 因此建议将高版本(如 V2)配置在低版本(如 V1)之后,以确保高版本的模型覆盖低版本
|
||||
# 例如:V1 在前,V2 在后,那么 V2 的模型会覆盖 V1 的同名模型
|
||||
swagger_urls: # 完整形式:可以控制每个版本的启用状态
|
||||
- url: "https://quanxue-test-api.w.23544.com:8843/swagger/v1/swagger.json"
|
||||
enabled: true
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -84,27 +84,64 @@ class GenerateCommand extends BaseCommand {
|
|||
final parser = SwaggerDataParser();
|
||||
|
||||
// 合并所有文档的 paths 和 models
|
||||
// 注意:合并策略是后面的文档会覆盖前面的同名模型和路径
|
||||
// 因此建议将高版本(如 V2)配置在低版本(如 V1)之后,以确保高版本的模型覆盖低版本
|
||||
SwaggerDocument? mergedDocument;
|
||||
|
||||
for (int i = 0; i < SwaggerConfig.swaggerJsonUrls.length; i++) {
|
||||
final url = SwaggerConfig.swaggerJsonUrls[i];
|
||||
progress(
|
||||
' [${i + 1}/${SwaggerConfig.swaggerJsonUrls.length}] 正在解析: $url');
|
||||
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 = <String>[];
|
||||
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 ? "..." : ""}');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -118,12 +155,20 @@ class GenerateCommand extends BaseCommand {
|
|||
|
||||
// 解析生成选项
|
||||
final options = _parseGenerateOptions(parsedArgs);
|
||||
final fullOutputDir = FileUtils.getProjectRootGeneratorDir();
|
||||
|
||||
progress('输出目录: $fullOutputDir');
|
||||
// 使用配置的输出目录
|
||||
final baseDir = SwaggerConfig.generatorDir;
|
||||
final apiDir = SwaggerConfig.apiDir;
|
||||
final modelsDir = SwaggerConfig.modelsDir;
|
||||
|
||||
progress('输出目录: $baseDir');
|
||||
progress('API 目录: $apiDir');
|
||||
progress('模型目录: $modelsDir');
|
||||
|
||||
// 确保输出目录存在
|
||||
await FileUtils.ensureDirectoryExists(fullOutputDir);
|
||||
await FileUtils.ensureDirectoryExists(baseDir);
|
||||
await FileUtils.ensureDirectoryExists(apiDir);
|
||||
await FileUtils.ensureDirectoryExists(modelsDir);
|
||||
|
||||
int generatedFiles = 0;
|
||||
|
||||
|
|
@ -135,20 +180,19 @@ class GenerateCommand extends BaseCommand {
|
|||
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}';
|
||||
|
||||
|
||||
// 检查是否跳过此文件
|
||||
if (ConfigLoader.shouldSkipFile(filePath)) {
|
||||
progress('跳过文件: $filePath');
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
await FileUtils.writeFile(filePath, entry.value);
|
||||
success('模型文件已保存到: $filePath');
|
||||
generatedFiles++;
|
||||
|
|
@ -159,7 +203,6 @@ class GenerateCommand extends BaseCommand {
|
|||
if (options.generateApi) {
|
||||
progress('正在按版本和tags分组生成Retrofit风格API接口...');
|
||||
|
||||
final apiDir = '$fullOutputDir/api';
|
||||
await FileUtils.ensureDirectoryExists(apiDir);
|
||||
|
||||
// 🎯 先按版本分组 paths
|
||||
|
|
@ -224,26 +267,26 @@ class GenerateCommand extends BaseCommand {
|
|||
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++;
|
||||
|
|
@ -259,7 +302,7 @@ class GenerateCommand extends BaseCommand {
|
|||
// 生成主 API 文件(ApiClient)
|
||||
final mainCode = _generateVersionedApiClient(versionedFiles);
|
||||
final mainFilePath = '$apiDir/api_client.dart';
|
||||
|
||||
|
||||
// 检查是否跳过主 API 文件
|
||||
if (!ConfigLoader.shouldSkipFile(mainFilePath)) {
|
||||
await FileUtils.writeFile(mainFilePath, mainCode);
|
||||
|
|
@ -280,20 +323,19 @@ class GenerateCommand extends BaseCommand {
|
|||
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}';
|
||||
|
||||
|
||||
// 检查是否跳过此文件
|
||||
if (ConfigLoader.shouldSkipFile(filePath)) {
|
||||
progress('跳过文件: $filePath');
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
await FileUtils.writeFile(filePath, entry.value);
|
||||
success('参数实体类文件已保存到: $filePath');
|
||||
generatedFiles++;
|
||||
|
|
@ -307,7 +349,6 @@ class GenerateCommand extends BaseCommand {
|
|||
// 重新生成 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';
|
||||
|
|
@ -321,8 +362,8 @@ class GenerateCommand extends BaseCommand {
|
|||
final generator = DocumentationGenerator(document);
|
||||
final code = generator.generate();
|
||||
|
||||
final filePath = '$fullOutputDir/api_documentation.md';
|
||||
|
||||
final filePath = '$baseDir/api_documentation.md';
|
||||
|
||||
// 检查是否跳过文档文件
|
||||
if (!ConfigLoader.shouldSkipFile(filePath)) {
|
||||
await FileUtils.writeFile(filePath, code);
|
||||
|
|
@ -334,7 +375,7 @@ class GenerateCommand extends BaseCommand {
|
|||
}
|
||||
|
||||
// 生成摘要
|
||||
_generateSummary(document, fullOutputDir);
|
||||
_generateSummary(document, baseDir);
|
||||
|
||||
success('代码生成完成!共生成 $generatedFiles 个文件');
|
||||
return 0;
|
||||
|
|
@ -428,7 +469,13 @@ class GenerateCommand extends BaseCommand {
|
|||
final fileName = path.basename(entity.path);
|
||||
// 排除 index.dart 本身和 .g.dart 文件
|
||||
if (fileName != 'index.dart' && !fileName.endsWith('.g.dart')) {
|
||||
dartFiles.add(fileName);
|
||||
// 检查文件是否在 ignored_files 配置中
|
||||
final filePath = path.join(subDir, fileName);
|
||||
if (!ConfigLoader.shouldSkipFile(filePath)) {
|
||||
dartFiles.add(fileName);
|
||||
} else {
|
||||
progress(' 跳过导出文件: $fileName (在 ignored_files 配置中)');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -473,6 +520,22 @@ class GenerateCommand extends BaseCommand {
|
|||
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\';');
|
||||
|
|
@ -513,7 +576,7 @@ class GenerateCommand extends BaseCommand {
|
|||
// 从配置文件读取版本提取模式
|
||||
final pattern = ConfigLoader.getVersionExtractionPattern();
|
||||
final defaultVersion = ConfigLoader.getDefaultVersion();
|
||||
|
||||
|
||||
try {
|
||||
final versionMatch = RegExp(pattern).firstMatch(path);
|
||||
if (versionMatch != null && versionMatch.groupCount > 0) {
|
||||
|
|
@ -527,7 +590,7 @@ class GenerateCommand extends BaseCommand {
|
|||
return 'v${versionMatch.group(1)}';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return defaultVersion;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -40,6 +40,12 @@ class SwaggerConfig {
|
|||
/// 获取模型文件目录(从配置文件读取)
|
||||
static String get modelsDir => ConfigLoader.getModelsDir();
|
||||
|
||||
/// 获取 BaseResult 导入路径(从配置文件读取)
|
||||
static String get baseResultImport => ConfigLoader.getBaseResultImport();
|
||||
|
||||
/// 获取 BasePageResult 导入路径(从配置文件读取)
|
||||
static String get basePageResultImport => ConfigLoader.getBasePageResultImport();
|
||||
|
||||
/// 默认文档文件名
|
||||
static const String defaultDocumentationFile =
|
||||
'generated_api_documentation.md';
|
||||
|
|
|
|||
|
|
@ -106,6 +106,10 @@ class ConfigLoader {
|
|||
/// 只支持 swagger_urls (列表) 配置方式
|
||||
/// 支持简写形式: ["url1", "url2"]
|
||||
/// 支持完整形式: [{url: "...", enabled: true}]
|
||||
///
|
||||
/// 注意:URL 列表的顺序很重要!
|
||||
/// 多个 Swagger 文档会按顺序合并,后面的文档会覆盖前面的同名模型和路径。
|
||||
/// 因此建议将高版本(如 V2)配置在低版本(如 V1)之后,以确保高版本的模型覆盖低版本。
|
||||
static List<String> getSwaggerUrls([Map<String, dynamic>? config]) {
|
||||
final cfg = config ?? loadConfig();
|
||||
if (cfg == null) {
|
||||
|
|
@ -430,4 +434,46 @@ class ConfigLoader {
|
|||
|
||||
return versionExtraction['default_version'] as String? ?? 'v1';
|
||||
}
|
||||
|
||||
/// 获取 BaseResult 导入路径
|
||||
static String getBaseResultImport([Map<String, dynamic>? config]) {
|
||||
final cfg = config ?? loadConfig();
|
||||
if (cfg == null) {
|
||||
return 'package:learning_officer_oa/common/models/common/base_result.dart';
|
||||
}
|
||||
|
||||
final generation = cfg['generation'] as Map<String, dynamic>?;
|
||||
if (generation == null) {
|
||||
return 'package:learning_officer_oa/common/models/common/base_result.dart';
|
||||
}
|
||||
|
||||
final api = generation['api'] as Map<String, dynamic>?;
|
||||
if (api == null) {
|
||||
return 'package:learning_officer_oa/common/models/common/base_result.dart';
|
||||
}
|
||||
|
||||
return api['base_result_import'] as String? ??
|
||||
'package:learning_officer_oa/common/models/common/base_result.dart';
|
||||
}
|
||||
|
||||
/// 获取 BasePageResult 导入路径
|
||||
static String getBasePageResultImport([Map<String, dynamic>? config]) {
|
||||
final cfg = config ?? loadConfig();
|
||||
if (cfg == null) {
|
||||
return 'package:learning_officer_oa/common/models/common/base_page_result.dart';
|
||||
}
|
||||
|
||||
final generation = cfg['generation'] as Map<String, dynamic>?;
|
||||
if (generation == null) {
|
||||
return 'package:learning_officer_oa/common/models/common/base_page_result.dart';
|
||||
}
|
||||
|
||||
final api = generation['api'] as Map<String, dynamic>?;
|
||||
if (api == null) {
|
||||
return 'package:learning_officer_oa/common/models/common/base_page_result.dart';
|
||||
}
|
||||
|
||||
return api['base_page_result_import'] as String? ??
|
||||
'package:learning_officer_oa/common/models/common/base_page_result.dart';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import '../core/models.dart';
|
||||
import '../core/config.dart';
|
||||
import '../utils/string_utils.dart';
|
||||
import 'base_generator.dart';
|
||||
|
||||
|
|
@ -430,6 +431,22 @@ class ModelCodeGenerator extends ModelGenerator {
|
|||
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) &&
|
||||
modelsByDirectory.isNotEmpty) {
|
||||
buffer.writeln('');
|
||||
}
|
||||
|
||||
// 导出所有子目录的 index.dart
|
||||
final sortedDirs = modelsByDirectory.keys.toList()..sort();
|
||||
|
||||
|
|
@ -473,6 +490,22 @@ class ModelCodeGenerator extends ModelGenerator {
|
|||
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) &&
|
||||
modelFileNames.isNotEmpty) {
|
||||
buffer.writeln('');
|
||||
}
|
||||
|
||||
// 按文件名排序并导出所有模型
|
||||
final sortedFiles = List<String>.from(modelFileNames)..sort();
|
||||
|
||||
|
|
|
|||
|
|
@ -135,17 +135,18 @@ class RetrofitApiGenerator extends BaseGenerator {
|
|||
final buffer = StringBuffer();
|
||||
|
||||
final fileName = _generateTagFileName(tagName);
|
||||
|
||||
|
||||
// 生成文件头(传入文件名)
|
||||
buffer.writeln(generateFileHeader('$tagName API 接口定义', fileName: fileName));
|
||||
buffer
|
||||
.writeln(generateFileHeader('$tagName API 接口定义', fileName: fileName));
|
||||
buffer.writeln('');
|
||||
|
||||
|
||||
// 生成导入语句
|
||||
_generateTagImports(buffer, paths);
|
||||
|
||||
|
||||
// 生成 API 接口类
|
||||
_generateTagApiInterface(buffer, tagName, paths);
|
||||
|
||||
|
||||
apiFiles[fileName] = generateTypeCheckedCode(buffer.toString());
|
||||
}
|
||||
|
||||
|
|
@ -154,12 +155,20 @@ class RetrofitApiGenerator extends BaseGenerator {
|
|||
|
||||
/// 生成导入语句
|
||||
void _generateImports(StringBuffer buffer) {
|
||||
// 添加核心依赖的导入
|
||||
// Dart 导入顺序规范:
|
||||
// 1. dart:xxx 导入
|
||||
// 2. package:xxx 导入(第三方包)
|
||||
// 3. package:project_name 导入(项目内部包)
|
||||
// 4. 相对路径导入
|
||||
// 每组之间用空行分隔
|
||||
|
||||
// dart: 标准库导入
|
||||
buffer.writeln('import \'dart:convert\';');
|
||||
buffer.writeln('import \'dart:io\';');
|
||||
buffer.writeln('import \'dart:typed_data\';');
|
||||
buffer.writeln('');
|
||||
|
||||
// Dio 和 Retrofit 相关导入
|
||||
// package: 第三方库导入(按字母顺序)
|
||||
if (useDio) {
|
||||
buffer.writeln('import \'package:dio/dio.dart\';');
|
||||
}
|
||||
|
|
@ -173,19 +182,9 @@ class RetrofitApiGenerator extends BaseGenerator {
|
|||
buffer.writeln('import \'package:crypto/crypto.dart\';');
|
||||
buffer.writeln('import \'package:path/path.dart\' as path;');
|
||||
buffer.writeln('import \'package:http_parser/http_parser.dart\';');
|
||||
|
||||
buffer.writeln('');
|
||||
|
||||
// 导入基础响应类型(从用户项目中导入)
|
||||
buffer.writeln(
|
||||
'import \'package:learning_officer_oa/common/models/common/base_result.dart\';');
|
||||
buffer.writeln(
|
||||
'import \'package:learning_officer_oa/common/models/common/base_page_result.dart\';');
|
||||
buffer.writeln('');
|
||||
|
||||
// 导入所有模型(通过 index.dart)
|
||||
// 使用统一的 index.dart 导入所有模型,简化维护
|
||||
// Dart 的 tree-shaking 会确保只打包实际使用的代码,不影响应用大小
|
||||
// 相对路径导入(api_models/index.dart 会导出 base_result 和 base_page_result)
|
||||
buffer.writeln('import \'../../api_models/index.dart\';');
|
||||
buffer.writeln('');
|
||||
|
||||
|
|
@ -1238,16 +1237,8 @@ class RetrofitApiGenerator extends BaseGenerator {
|
|||
|
||||
buffer.writeln('');
|
||||
|
||||
// 导入基础响应类型(从用户项目中导入)
|
||||
buffer.writeln(
|
||||
'import \'package:learning_officer_oa/common/models/common/base_result.dart\';');
|
||||
|
||||
// 只有在需要分页时才导入 base_page_result.dart
|
||||
if (_needsPaginationImportForDocument()) {
|
||||
buffer.writeln(
|
||||
'import \'package:learning_officer_oa/common/models/common/base_page_result.dart\';');
|
||||
}
|
||||
|
||||
// 相对路径导入(api_models/index.dart 会导出 base_result 和 base_page_result)
|
||||
buffer.writeln('import \'../../api_models/index.dart\';');
|
||||
buffer.writeln('');
|
||||
|
||||
// 导入错误处理相关类
|
||||
|
|
@ -1369,31 +1360,24 @@ class RetrofitApiGenerator extends BaseGenerator {
|
|||
|
||||
/// 生成特定 tag 的导入语句
|
||||
void _generateTagImports(StringBuffer buffer, List<ApiPath> paths) {
|
||||
// Dart 导入顺序规范:
|
||||
// 1. dart:xxx 导入
|
||||
// 2. package:xxx 导入(第三方包)
|
||||
// 3. package:project_name 导入(项目内部包)
|
||||
// 4. 相对路径导入
|
||||
// 每组之间用空行分隔
|
||||
|
||||
// package: 第三方库导入(按字母顺序)
|
||||
if (useDio) {
|
||||
buffer.writeln('import \'package:dio/dio.dart\';');
|
||||
}
|
||||
|
||||
if (useRetrofit) {
|
||||
buffer.writeln('import \'package:retrofit/retrofit.dart\';');
|
||||
}
|
||||
|
||||
buffer.writeln('');
|
||||
|
||||
// 导入基础响应类型(从用户项目中导入)
|
||||
buffer.writeln(
|
||||
'import \'package:learning_officer_oa/common/models/common/base_result.dart\';');
|
||||
|
||||
// 只有在需要分页时才导入 base_page_result.dart
|
||||
if (_needsPaginationImport(paths)) {
|
||||
buffer.writeln(
|
||||
'import \'package:learning_officer_oa/common/models/common/base_page_result.dart\';');
|
||||
}
|
||||
|
||||
buffer.writeln('');
|
||||
|
||||
// 导入所有模型(通过 index.dart)
|
||||
// 使用统一的 index.dart 导入所有模型,简化维护
|
||||
// Dart 的 tree-shaking 会确保只打包实际使用的代码,不影响应用大小
|
||||
// 相对路径导入(api_models/index.dart 会导出 base_result 和 base_page_result)
|
||||
buffer.writeln('import \'../../api_models/index.dart\';');
|
||||
buffer.writeln('');
|
||||
|
||||
|
|
@ -1663,84 +1647,85 @@ class RetrofitApiGenerator extends BaseGenerator {
|
|||
/// 生成参数实体类
|
||||
void _generateParameterEntity(
|
||||
ApiPath path, String className, List<ApiParameter> queryParams) {
|
||||
if (!_generatedParameterEntities.containsKey(className)) {
|
||||
final buffer = StringBuffer();
|
||||
// 注意:如果类名已存在,会覆盖之前的定义
|
||||
// 这样可以确保后面版本的路径覆盖前面版本的参数实体类定义
|
||||
// 例如:V2 的参数实体类会覆盖 V1 的同名参数实体类
|
||||
final buffer = StringBuffer();
|
||||
|
||||
// 生成文件头注释
|
||||
buffer.writeln(generateFileHeader(
|
||||
'参数实体类 - $className',
|
||||
fileName: '${StringUtils.toSnakeCase(className)}.dart',
|
||||
));
|
||||
buffer.writeln('// 用于 ${path.method.value.toUpperCase()} ${path.path} 的查询参数');
|
||||
buffer.writeln('');
|
||||
// 生成文件头注释
|
||||
buffer.writeln(generateFileHeader(
|
||||
'参数实体类 - $className',
|
||||
fileName: '${StringUtils.toSnakeCase(className)}.dart',
|
||||
));
|
||||
buffer
|
||||
.writeln('// 用于 ${path.method.value.toUpperCase()} ${path.path} 的查询参数');
|
||||
buffer.writeln('');
|
||||
|
||||
// 导入语句
|
||||
buffer
|
||||
.writeln('import \'package:json_annotation/json_annotation.dart\';');
|
||||
buffer.writeln('');
|
||||
buffer.writeln('part \'${StringUtils.toSnakeCase(className)}.g.dart\';');
|
||||
buffer.writeln('');
|
||||
// 导入语句
|
||||
buffer.writeln('import \'package:json_annotation/json_annotation.dart\';');
|
||||
buffer.writeln('');
|
||||
buffer.writeln('part \'${StringUtils.toSnakeCase(className)}.g.dart\';');
|
||||
buffer.writeln('');
|
||||
|
||||
// 生成参数实体类
|
||||
buffer.writeln('@JsonSerializable(checked: true, includeIfNull: false)');
|
||||
buffer.writeln('class $className {');
|
||||
// 生成参数实体类
|
||||
buffer.writeln('@JsonSerializable(checked: true, includeIfNull: false)');
|
||||
buffer.writeln('class $className {');
|
||||
|
||||
// 生成属性
|
||||
for (final param in queryParams) {
|
||||
final dartName = StringUtils.toDartPropertyName(param.name);
|
||||
final dartType = _getDartType(param.type);
|
||||
final nullable = param.required ? '' : '?';
|
||||
// 生成属性
|
||||
for (final param in queryParams) {
|
||||
final dartName = StringUtils.toDartPropertyName(param.name);
|
||||
final dartType = _getDartType(param.type);
|
||||
final nullable = param.required ? '' : '?';
|
||||
|
||||
// 处理描述中的换行符,确保注释格式正确
|
||||
final cleanDescription = param.description
|
||||
.replaceAll('\r\n', ' ')
|
||||
.replaceAll('\n', ' ')
|
||||
.replaceAll('\r', ' ')
|
||||
.trim();
|
||||
buffer.writeln(
|
||||
' /// ${cleanDescription.isNotEmpty ? cleanDescription : param.name}');
|
||||
buffer.writeln(' @JsonKey(name: \'${param.name}\')');
|
||||
buffer.writeln(' final $dartType$nullable $dartName;');
|
||||
buffer.writeln('');
|
||||
}
|
||||
|
||||
// 生成构造函数
|
||||
buffer.writeln(' const $className({');
|
||||
for (final param in queryParams) {
|
||||
final dartName = StringUtils.toDartPropertyName(param.name);
|
||||
final required = param.required ? 'required ' : '';
|
||||
buffer.writeln(' ${required}this.$dartName,');
|
||||
}
|
||||
buffer.writeln(' });');
|
||||
buffer.writeln('');
|
||||
|
||||
// 生成 fromJson 方法
|
||||
// 处理描述中的换行符,确保注释格式正确
|
||||
final cleanDescription = param.description
|
||||
.replaceAll('\r\n', ' ')
|
||||
.replaceAll('\n', ' ')
|
||||
.replaceAll('\r', ' ')
|
||||
.trim();
|
||||
buffer.writeln(
|
||||
' factory $className.fromJson(Map<String, dynamic> json) =>');
|
||||
buffer.writeln(' _\$${className}FromJson(json);');
|
||||
' /// ${cleanDescription.isNotEmpty ? cleanDescription : param.name}');
|
||||
buffer.writeln(' @JsonKey(name: \'${param.name}\')');
|
||||
buffer.writeln(' final $dartType$nullable $dartName;');
|
||||
buffer.writeln('');
|
||||
|
||||
// 生成 toJson 方法
|
||||
buffer.writeln(
|
||||
' Map<String, dynamic> toJson() => _\$${className}ToJson(this);');
|
||||
buffer.writeln('');
|
||||
|
||||
// 生成 toQueryMap 方法(用于 Dio 查询参数)
|
||||
buffer.writeln(' /// 转换为查询参数 Map');
|
||||
buffer.writeln(' Map<String, dynamic> toQueryMap() {');
|
||||
buffer.writeln(' final map = <String, dynamic>{};');
|
||||
for (final param in queryParams) {
|
||||
final dartName = StringUtils.toDartPropertyName(param.name);
|
||||
buffer.writeln(
|
||||
' if ($dartName != null) map[\'${param.name}\'] = $dartName;');
|
||||
}
|
||||
buffer.writeln(' return map;');
|
||||
buffer.writeln(' }');
|
||||
|
||||
buffer.writeln('}');
|
||||
|
||||
_generatedParameterEntities[className] = buffer.toString();
|
||||
}
|
||||
|
||||
// 生成构造函数
|
||||
buffer.writeln(' const $className({');
|
||||
for (final param in queryParams) {
|
||||
final dartName = StringUtils.toDartPropertyName(param.name);
|
||||
final required = param.required ? 'required ' : '';
|
||||
buffer.writeln(' ${required}this.$dartName,');
|
||||
}
|
||||
buffer.writeln(' });');
|
||||
buffer.writeln('');
|
||||
|
||||
// 生成 fromJson 方法
|
||||
buffer
|
||||
.writeln(' factory $className.fromJson(Map<String, dynamic> json) =>');
|
||||
buffer.writeln(' _\$${className}FromJson(json);');
|
||||
buffer.writeln('');
|
||||
|
||||
// 生成 toJson 方法
|
||||
buffer.writeln(
|
||||
' Map<String, dynamic> toJson() => _\$${className}ToJson(this);');
|
||||
buffer.writeln('');
|
||||
|
||||
// 生成 toQueryMap 方法(用于 Dio 查询参数)
|
||||
buffer.writeln(' /// 转换为查询参数 Map');
|
||||
buffer.writeln(' Map<String, dynamic> toQueryMap() {');
|
||||
buffer.writeln(' final map = <String, dynamic>{};');
|
||||
for (final param in queryParams) {
|
||||
final dartName = StringUtils.toDartPropertyName(param.name);
|
||||
buffer.writeln(
|
||||
' if ($dartName != null) map[\'${param.name}\'] = $dartName;');
|
||||
}
|
||||
buffer.writeln(' return map;');
|
||||
buffer.writeln(' }');
|
||||
|
||||
buffer.writeln('}');
|
||||
|
||||
_generatedParameterEntities[className] = buffer.toString();
|
||||
}
|
||||
|
||||
/// 存储已生成的参数实体类
|
||||
|
|
@ -1767,7 +1752,12 @@ class RetrofitApiGenerator extends BaseGenerator {
|
|||
/// 确保参数实体类已生成(在调用 generate 之前调用)
|
||||
void ensureParameterEntitiesGenerated() {
|
||||
// 遍历所有路径,确保参数实体类已生成
|
||||
for (final path in document.paths.values) {
|
||||
// 注意:按路径字符串排序,确保后面的版本(如 V2)覆盖前面的版本(如 V1)
|
||||
// 因为如果类名相同,后面的会覆盖前面的
|
||||
final sortedPaths = document.paths.values.toList()
|
||||
..sort((a, b) => a.path.compareTo(b.path));
|
||||
|
||||
for (final path in sortedPaths) {
|
||||
final queryParams = path.parameters
|
||||
.where((p) => p.location == ParameterLocation.query)
|
||||
.toList();
|
||||
|
|
|
|||
|
|
@ -1,13 +1,55 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
/// 文件工具类
|
||||
/// 提供文件操作、目录管理和代码格式化功能
|
||||
class FileUtils {
|
||||
/// 解析路径(支持相对路径和绝对路径)
|
||||
/// 如果是相对路径,相对于项目根目录(配置文件所在目录)
|
||||
static String resolvePath(String filePath) {
|
||||
// 如果是绝对路径,直接返回
|
||||
if (path.isAbsolute(filePath)) {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
// 相对路径:相对于当前工作目录
|
||||
// 查找配置文件所在的目录作为项目根目录
|
||||
final configFile = _findConfigFile();
|
||||
if (configFile != null) {
|
||||
final configDir = path.dirname(configFile);
|
||||
return path.join(configDir, filePath);
|
||||
}
|
||||
|
||||
// 如果找不到配置文件,使用当前工作目录
|
||||
return path.join(Directory.current.path, filePath);
|
||||
}
|
||||
|
||||
/// 查找配置文件
|
||||
static String? _findConfigFile() {
|
||||
var currentDir = Directory.current;
|
||||
final maxDepth = 10;
|
||||
var depth = 0;
|
||||
|
||||
while (depth < maxDepth) {
|
||||
final configFile = File(path.join(currentDir.path, 'generator_config.yaml'));
|
||||
if (configFile.existsSync()) {
|
||||
return configFile.path;
|
||||
}
|
||||
|
||||
final parent = currentDir.parent;
|
||||
if (parent.path == currentDir.path) {
|
||||
break;
|
||||
}
|
||||
currentDir = parent;
|
||||
depth++;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
/// 确保目录存在
|
||||
static Future<Directory> ensureDirectoryExists(String dirPath) async {
|
||||
final directory = Directory(dirPath);
|
||||
final resolvedPath = resolvePath(dirPath);
|
||||
final directory = Directory(resolvedPath);
|
||||
if (!await directory.exists()) {
|
||||
await directory.create(recursive: true);
|
||||
}
|
||||
|
|
@ -17,7 +59,8 @@ class FileUtils {
|
|||
/// 安全写入文件
|
||||
static Future<void> safeWriteFile(String filePath, String content) async {
|
||||
try {
|
||||
final file = File(filePath);
|
||||
final resolvedPath = resolvePath(filePath);
|
||||
final file = File(resolvedPath);
|
||||
final directory = file.parent;
|
||||
|
||||
// 确保目录存在
|
||||
|
|
|
|||
|
|
@ -386,7 +386,6 @@ class StringUtils {
|
|||
final copyright = ConfigLoader.getCopyright();
|
||||
|
||||
return '''// $description
|
||||
// 基于 Swagger API 文档: $source
|
||||
// 由 $generatorName by $author 生成
|
||||
// $copyright
|
||||
''';
|
||||
|
|
|
|||
|
|
@ -30,11 +30,11 @@ dependencies:
|
|||
dio: ^5.0.0
|
||||
retrofit: ^4.0.0
|
||||
json_annotation: ^4.8.1
|
||||
|
||||
|
||||
dev_dependencies:
|
||||
# 测试框架
|
||||
test: ^1.24.0
|
||||
|
||||
|
||||
# 代码生成工具(仅用于测试/示例)
|
||||
build_runner: ^2.4.7
|
||||
json_serializable: ^6.7.1
|
||||
|
|
|
|||
Loading…
Reference in New Issue