chore: release 3.0.0 – remove OptimizedRetrofitGenerator; consolidate on RetrofitApiGenerator; update docs and examples; fix QRCodeApi reference; improve ApiClient class-name extraction; bump version to 3.0.0; update CHANGELOG

This commit is contained in:
Max 2025-11-21 18:10:37 +08:00
parent 498c2f3d7e
commit 69aad6bda1
43 changed files with 17334 additions and 8569 deletions

View File

@ -1,458 +0,0 @@
# Augment 代码生成规范
## 基于 OpenAPI 3.0 标准的 Flutter API 代码生成规范
### 📋 **核心原则**
#### 1. **OpenAPI 3.0 标准优先**
- **严格遵循 OpenAPI 3.0 规范**
- **swagger.json 是唯一真实来源**
- **不进行主观推断或猜测**
- **有问题与服务器端沟通完善文档**
#### 2. **类型安全第一**
- **所有类型必须从 schema 定义中提取**
- **禁止硬编码类型映射**
- **使用强类型,避免 dynamic**
- **严格的可空性控制**
#### 3. **一致性保证**
- **统一的命名规范**
- **统一的文件结构**
- **统一的代码风格**
- **统一的注释格式**
---
## 🏗️ **项目结构规范**
### **目录结构**
```
generator/
├── api/ # API 接口文件
│ ├── api_client.dart # 主 API 客户端
│ ├── login_api.dart # 按 tag 分组的 API
│ └── ...
├── api_models/ # 数据模型
│ ├── index.dart # 统一导出文件
│ ├── user_result.dart # 具体模型类
│ └── ...
├── api_paths.dart # API 路径常量
├── build.yaml # 构建配置
└── pubspec.yaml # 依赖配置
```
### **文件命名规范**
- **API 文件**: `{tag_name}_api.dart` (snake_case)
- **模型文件**: `{schema_name}.dart` (snake_case)
- **参数文件**: `{operation_id}_parameters.dart`
- **类名**: `PascalCase`
- **方法名**: `camelCase`
- **常量**: `UPPER_SNAKE_CASE`
---
## 🔧 **代码生成规范**
### **1. API 接口生成**
#### **基本结构**
```dart
// {Tag} API 接口定义
// 基于 Swagger API 文档: {swagger_url}
// 由 xy_swagger_generator by max 生成
// Copyright (C) 2025 YuanXuan. All rights reserved.
import 'package:dio/dio.dart';
import 'package:retrofit/retrofit.dart';
import 'package:learning_officer_oa/common/models/common/base_result.dart';
// 按需导入分页类型
import 'package:learning_officer_oa/common/models/common/base_page_result.dart';
part '{tag_name}_api.g.dart';
/// {Tag} API 接口
/// 负责处理 {Tag} 相关的接口
@RestApi(parser: Parser.JsonSerializable)
abstract class {Tag}Api {
factory {Tag}Api(Dio dio, {String? baseUrl}) = _{Tag}Api;
}
```
#### **方法生成规则**
```dart
/// {summary}
@{HTTP_METHOD}('{path}')
Future<{ReturnType}> {methodName}(
// 路径参数
@Path('{param}') {Type} param,
// 查询参数
@Query('{param}') {Type}? param,
// 请求体(仅当 swagger 中明确定义时)
@Body() {Type} request
);
```
### **2. 返回类型规范**
#### **类型提取优先级**
1. **从 responses.200.content.application/json.schema 提取**
2. **从 responses.200.content.text/plain.schema 提取**
3. **特殊处理**: 健康检查接口返回 `BaseResult<void>`
4. **默认**: `BaseResult<Map<String, dynamic>>`
#### **分页类型判断**
```dart
// 当返回类型包含分页结构时使用 BasePageResult
BasePageResult<{ItemType}>
// 判断依据:
// 1. schema 中包含 pageIndex, pageSize, totalCount 等字段
// 2. 查询参数中包含分页参数 (page, size, limit 等)
```
### **3. 请求体生成规范**
#### **添加 @Body() 的条件**
```dart
// 仅在以下情况添加 @Body() 参数:
// 1. requestBody 在 swagger 中明确定义
// 2. parameters 中存在 in: "body" 的参数
// 3. 其他情况一律不添加
```
#### **请求体类型提取**
```dart
// 优先级:
// 1. requestBody.content.application/json.schema
// 2. requestBody.content.text/plain.schema
// 3. 默认: Map<String, dynamic>
```
---
## 📝 **数据模型生成规范**
### **1. 模型类结构**
#### **基本模板**
```dart
// {schemaName} 模型定义
// 基于 Swagger API 文档: {swagger_url}
// 由 xy_swagger_generator by max 生成
// Copyright (C) 2025 YuanXuan. All rights reserved.
import 'package:json_annotation/json_annotation.dart';
import 'index.dart';
part '{schema_name}.g.dart';
@JsonSerializable(checked: true, includeIfNull: false)
class {SchemaName} {
// 属性定义
const {SchemaName}({
// 构造函数参数
});
factory {SchemaName}.fromJson(Map<String, dynamic> json) =>
_${SchemaName}FromJson(json);
Map<String, dynamic> toJson() => _${SchemaName}ToJson(this);
}
```
### **2. 属性生成规则**
#### **可空性判断**
```dart
// 严格按照 OpenAPI 规范:
// 1. 有 "nullable": true -> 可空类型 (Type?)
// 2. 没有 "nullable": true -> 非空类型 (Type)
// 3. 忽略 required 字段,只看 nullable
final String name; // 非空
final String? nickname; // 可空 (nullable: true)
```
#### **类型映射**
```dart
// OpenAPI -> Dart 类型映射
"string" -> String
"integer" -> int
"number" -> double
"boolean" -> bool
"array" -> List<T>
"object" -> Map<String, dynamic> 或具体类型
"$ref" -> 引用的具体类型
```
#### **构造函数规则**
```dart
const {ClassName}({
required this.nonNullableField, // 非空字段必须 required
this.nullableField, // 可空字段不需要 required
});
```
### **3. 导入管理**
#### **按需导入原则**
```dart
// 只导入实际使用的类型
// 分页相关
import 'package:learning_officer_oa/common/models/common/base_page_result.dart';
// 基础响应
import 'package:learning_officer_oa/common/models/common/base_result.dart';
// 模型类型
import '../../api_models/{model_name}.dart';
```
---
## 🚫 **禁止事项**
### **1. 硬编码推断**
```dart
// ❌ 禁止基于路径关键词推断类型
if (path.contains('login')) return 'UserLoginResult';
// ❌ 禁止基于 tag 推断类型
if (tag.contains('task')) return 'TaskInfoResult';
// ❌ 禁止基于操作推断请求体
if (method == 'POST') addBody();
```
### **2. 不存在的类型**
```dart
// ❌ 禁止生成 swagger 中不存在的类型
Future<BaseResult<TaskInfoResult>> // TaskInfoResult 不存在
// ✅ 使用通用类型或实际存在的类型
Future<BaseResult<Map<String, dynamic>>>
Future<BaseResult<ActualExistingType>>
```
### **3. 主观判断**
```dart
// ❌ 禁止主观添加参数
@Body() Map<String, dynamic> request // swagger 中没有定义
// ❌ 禁止主观修改类型
List<dynamic> -> List<SpecificType> // 没有明确的 items schema
```
---
## ✅ **质量保证**
### **1. 生成前检查**
- **验证 swagger.json 格式正确性**
- **检查所有 $ref 引用完整性**
- **确认 components/schemas 定义完整**
### **2. 生成后验证**
- **所有生成的类型在 swagger 中都有定义**
- **没有硬编码的类型映射**
- **导入语句按需生成**
- **代码通过 dart analyze 检查**
### **3. 错误处理**
```dart
// 当 swagger 定义不完整时的处理策略:
// 1. 记录警告日志
// 2. 使用安全的默认类型
// 3. 提示完善 swagger 文档
// 4. 不进行主观推断
```
---
## 📞 **沟通机制**
### **文档问题反馈**
1. **发现 swagger 定义缺失** -> 联系后端完善
2. **类型定义不明确** -> 要求明确 schema
3. **响应结构不一致** -> 统一响应格式
4. **参数定义缺失** -> 补充参数说明
### **版本管理**
- **swagger.json 版本控制**
- **生成代码版本标记**
- **变更日志记录**
- **向后兼容性检查**
---
## 🛠️ **工具配置规范**
### **1. 生成器配置**
```yaml
# pubspec.yaml
dependencies:
dio: ^5.0.0
retrofit: ^4.0.0
json_annotation: ^4.8.0
dev_dependencies:
build_runner: ^2.3.0
retrofit_generator: ^8.0.0
json_serializable: ^6.6.0
```
### **2. 构建配置**
```yaml
# build.yaml
targets:
$default:
builders:
json_serializable:
options:
checked: true
include_if_null: false
explicit_to_json: true
```
### **3. 分析配置**
```yaml
# analysis_options.yaml
analyzer:
strong-mode:
implicit-casts: false
implicit-dynamic: false
linter:
rules:
- prefer_const_constructors
- prefer_final_fields
- avoid_dynamic_calls
```
---
## 📚 **最佳实践示例**
### **1. 标准 API 接口**
```dart
/// 用户登录
@POST('/api/v1/Login/userLogin')
Future<BaseResult<UserLoginResult>> userLogin(
@Body() LoginRequest request
);
/// 获取用户列表(分页)
@GET('/api/v1/User/GetUserList')
Future<BasePageResult<UserResult>> getUserList(
@Query('page') int page,
@Query('size') int size,
@Query('keyword') String? keyword
);
/// 健康检查
@GET('/health')
Future<BaseResult<void>> healthCheck();
/// 无明确 schema 的接口
@POST('/api/v1/Action/DoSomething')
Future<BaseResult<Map<String, dynamic>>> doSomething();
```
### **2. 标准数据模型**
```dart
@JsonSerializable(checked: true, includeIfNull: false)
class UserResult {
/// 用户ID
final int id;
/// 用户名
final String username;
/// 昵称(可空)
final String? nickname;
/// 头像URL可空
final String? avatarUrl;
/// 是否激活
final bool isActive;
const UserResult({
required this.id,
required this.username,
this.nickname,
this.avatarUrl,
required this.isActive,
});
factory UserResult.fromJson(Map<String, dynamic> json) =>
_$UserResultFromJson(json);
Map<String, dynamic> toJson() => _$UserResultToJson(this);
}
```
### **3. 参数类生成**
```dart
@JsonSerializable(checked: true, includeIfNull: false)
class GetUserListParameters {
final int page;
final int size;
final String? keyword;
const GetUserListParameters({
required this.page,
required this.size,
this.keyword,
});
factory GetUserListParameters.fromJson(Map<String, dynamic> json) =>
_$GetUserListParametersFromJson(json);
Map<String, dynamic> toJson() => _$GetUserListParametersToJson(this);
}
```
---
## 🔍 **代码审查清单**
### **生成代码检查项**
- [ ] 所有类型都在 swagger.json 中有定义
- [ ] 没有硬编码的类型推断
- [ ] 可空性严格按照 nullable 字段
- [ ] 导入语句按需生成
- [ ] 方法参数与 swagger 定义一致
- [ ] 返回类型正确提取
- [ ] 注释信息完整
- [ ] 代码格式规范
### **swagger.json 检查项**
- [ ] 所有接口都有明确的 responses 定义
- [ ] 所有 schema 都有完整的属性定义
- [ ] 所有 $ref 引用都存在
- [ ] 参数定义完整name, in, schema
- [ ] requestBody 定义明确
- [ ] 版本信息正确
---
## 📖 **参考资源**
### **官方文档**
- [OpenAPI 3.0 规范](https://swagger.io/specification/)
- [Retrofit for Dart](https://pub.dev/packages/retrofit)
- [JSON Serializable](https://pub.dev/packages/json_serializable)
### **项目相关**
- [项目 README](./README.md)
- [API 参考文档](./docs/API_REFERENCE.md)
- [贡献指南](./CONTRIBUTING.md)
---
**最后更新**: 2025-01-24
**版本**: v2.0
**维护者**: Augment Team

View File

@ -2,6 +2,30 @@
All notable changes to this project will be documented in this file.
## [3.0.0] - 2025-11-21
### Breaking changes
- Removed OptimizedRetrofitGenerator and its public export. RetrofitApiGenerator is now the sole supported generator.
### Migration
- Replace imports/usages of OptimizedRetrofitGenerator with RetrofitApiGenerator. Example:
- Before:
import 'package:swagger_generator_flutter/generators/optimized_retrofit_generator.dart';
final generator = OptimizedRetrofitGenerator(className: 'ApiService');
- After:
import 'package:swagger_generator_flutter/generators/retrofit_api_generator.dart';
final generator = RetrofitApiGenerator(
className: 'ApiService',
useRetrofit: true,
useDio: true,
splitByTags: true,
versionedApi: true,
);
### Docs
- Updated README and docs to reference RetrofitApiGenerator only.
## [2.1.1] - 2025-11-05
### 🎉 新特性

View File

@ -1,242 +0,0 @@
# 代码审核报告 - 版本变动审核
**审核日期**: 2025-11-05
**审核范围**: 本次版本的所有变更
**审核重点**: 文件头配置功能、文件跳过功能
---
## 📋 本次版本主要变更
### 1. 文件头配置功能 ✅
- **新增**: `ConfigLoader.getFileHeaderTemplate()` - 读取文件头模板
- **新增**: `ConfigLoader.getGeneratorName()` - 读取生成器名称
- **新增**: `ConfigLoader.getAuthor()` - 读取作者信息
- **新增**: `ConfigLoader.getCopyright()` - 读取版权信息
- **更新**: `StringUtils.generateFileHeader()` - 支持配置模板和变量替换
- **更新**: `BaseGenerator.generateFileHeader()` - 传递文件名参数
### 2. 文件跳过功能 ✅
- **新增**: `ConfigLoader.getIgnoredDirectories()` - 读取跳过的目录列表
- **新增**: `ConfigLoader.getIgnoredFiles()` - 读取跳过的文件名列表
- **新增**: `ConfigLoader.shouldSkipFile()` - 检查文件是否应该跳过
- **更新**: `GenerateCommand.execute()` - 在所有文件生成点添加跳过检查
### 3. 配置文件更新 ✅
- **更新**: `generator_config.yaml` - 添加模板配置示例
- **更新**: `generator_config.template.yaml` - 添加模板配置部分
- **更新**: `example/as_dev_dependency/generator_config.yaml` - 添加完整模板配置
---
## ✅ 代码质量检查
### 1. 代码逻辑检查
#### ✅ 文件头配置逻辑
- **状态**: ✅ 正确
- **说明**:
- 模板变量替换逻辑正确
- 支持默认值回退机制
- 当配置不存在时使用默认模板
#### ✅ 文件跳过逻辑
- **状态**: ✅ 正确
- **说明**:
- 目录级别跳过:路径标准化处理正确
- 文件名级别跳过:支持精确匹配和通配符匹配
- 边界情况处理:空目录、空文件名都有处理
#### ✅ Swagger URL 处理
- **状态**: ✅ 正确
- **说明**:
- 支持多个 Swagger URL
- 文件头使用第一个 URL合理
- URL 合并逻辑正确
### 2. 代码完整性检查
#### ✅ 所有文件生成点都已添加跳过检查
- 模型文件生成 ✅
- API 文件生成 ✅
- 版本目录生成 ✅
- 主 API 文件生成 ✅
- 参数实体类生成 ✅
- 文档文件生成 ✅
#### ✅ 配置文件完整性
- 模板配置项完整 ✅
- 注释说明完整 ✅
- 示例配置正确 ✅
---
## ⚠️ 发现的问题
### 1. 未使用的方法
**位置**: `lib/generators/retrofit_api_generator.dart:1037`
**问题**: `_getRequiredModelImports()` 方法未被引用
**影响**: 低(不影响功能)
**建议**:
```dart
// 可以考虑删除或标记为 @deprecated
// 或者保留以备将来使用
```
**处理**: 暂不处理,保留以备将来使用
---
## 🔍 潜在问题分析
### 1. 文件头模板变量替换
**潜在问题**: 当模板包含多个相同的变量时,`replaceAll` 会替换所有出现
**影响**: 低(通常模板中每个变量只出现一次)
**验证**: ✅ 代码逻辑正确,`replaceAll` 是正确的选择
### 2. 文件跳过路径匹配
**潜在问题**: 路径匹配可能在某些边界情况下不够精确
**影响**: 低(已处理主要边界情况)
**验证**: ✅ 代码包含边界检查:
- 空目录名检查
- 路径标准化(统一使用 `/`
- 目录边界检查
### 3. 多版本文件头 URL
**潜在问题**: 当有多个 Swagger URL 时,文件头只显示第一个 URL
**影响**: 低(这是合理的设计选择)
**说明**: 这是有意为之的设计,因为:
- 文件头通常只需要显示一个来源
- 多个 URL 可能导致文件头过长
- 如果需要,可以通过配置模板自定义
---
## 📊 代码质量指标
### 代码覆盖率
- ✅ 所有新增功能都有对应的配置选项
- ✅ 所有配置都有默认值处理
- ✅ 所有边界情况都有处理
### 错误处理
- ✅ 配置文件不存在时使用默认值
- ✅ 配置解析失败时显示警告
- ✅ 文件跳过检查失败时继续执行(不影响主流程)
### 代码一致性
- ✅ 命名规范一致
- ✅ 代码风格一致
- ✅ 注释风格一致
---
## 🎯 功能验证
### 1. 文件头配置功能 ✅
**测试场景**:
- ✅ 配置存在时使用配置的模板
- ✅ 配置不存在时使用默认模板
- ✅ 模板变量正确替换
- ✅ 生成器信息从配置读取
**验证结果**: ✅ 所有场景通过
### 2. 文件跳过功能 ✅
**测试场景**:
- ✅ 目录级别跳过
- ✅ 文件名级别跳过(精确匹配)
- ✅ 文件名级别跳过(通配符匹配)
- ✅ 组合跳过(目录 + 文件名)
**验证结果**: ✅ 所有场景通过
---
## 📝 建议改进
### 1. 文档完善 ✅
- ✅ 已添加 `FILE_HEADER_CONFIGURATION.md`
- ✅ 配置文件包含详细注释
- ✅ 示例配置完整
### 2. 代码优化建议
#### 建议 1: 考虑添加单元测试
- **优先级**: 中
- **说明**: 为新功能添加单元测试可以提高代码质量
#### 建议 2: 优化文件跳过性能
- **优先级**: 低
- **说明**: 当前实现已经足够高效,但如果文件数量很大,可以考虑缓存配置
### 3. 功能增强建议
#### 建议 1: 支持更多通配符模式
- **优先级**: 低
- **说明**: 当前支持 `*prefix`, `suffix*`, `*pattern*`,已满足大部分需求
#### 建议 2: 支持正则表达式匹配
- **优先级**: 低
- **说明**: 如果需要更复杂的匹配模式,可以考虑支持正则表达式
---
## ✅ 总结
### 代码质量
- ✅ **优秀**: 代码结构清晰,逻辑正确
- ✅ **完整**: 所有功能都有完整的实现
- ✅ **健壮**: 错误处理和边界情况处理完善
### 功能完整性
- ✅ **文件头配置**: 完全实现,功能完整
- ✅ **文件跳过**: 完全实现,功能完整
- ✅ **配置支持**: 配置项完整,注释详细
### 潜在风险
- ⚠️ **低风险**: 只有一个未使用的方法,不影响功能
- ✅ **无高风险问题**: 未发现高风险问题
### 建议
1. ✅ **可以发布**: 代码质量良好,可以发布
2. ✅ **文档完善**: 文档已完善,用户可以理解如何使用
3. ✅ **向后兼容**: 新功能不影响现有功能,向后兼容
---
## 📋 审核结论
**审核状态**: ✅ **通过**
**总体评价**:
- 代码质量优秀
- 功能实现完整
- 错误处理完善
- 文档齐全
**建议**: 可以发布此版本
**备注**:
- 发现一个未使用的方法(`_getRequiredModelImports`),但不影响功能,可以保留以备将来使用
- 所有核心功能都已正确实现并通过验证
---
**审核人**: AI Assistant
**审核日期**: 2025-11-05

View File

@ -1,369 +0,0 @@
# 贡献指南
感谢您对 Swagger Generator Flutter 项目的关注!我们欢迎各种形式的贡献。
## 🤝 如何贡献
### 报告问题
如果您发现了 bug 或有功能建议,请:
1. 检查 [现有 Issues](https://github.com/your-repo/swagger_generator_flutter/issues) 是否已有相关报告
2. 如果没有,请创建新的 Issue包含
- 清晰的标题和描述
- 重现步骤(如果是 bug
- 期望的行为
- 实际的行为
- 环境信息Dart/Flutter 版本等)
- 相关的代码片段或错误日志
### 提交代码
1. **Fork 项目**
```bash
git clone https://github.com/your-username/swagger_generator_flutter.git
cd swagger_generator_flutter
```
2. **创建分支**
```bash
git checkout -b feature/your-feature-name
# 或
git checkout -b fix/your-bug-fix
```
3. **安装依赖**
```bash
flutter pub get
```
4. **进行更改**
- 遵循项目的代码风格
- 添加必要的测试
- 更新相关文档
5. **运行测试**
```bash
dart test
```
6. **提交更改**
```bash
git add .
git commit -m "feat: add new feature" # 遵循 Conventional Commits
```
7. **推送分支**
```bash
git push origin feature/your-feature-name
```
8. **创建 Pull Request**
- 提供清晰的 PR 描述
- 链接相关的 Issues
- 确保所有检查通过
## 📝 代码风格
### Dart 代码风格
我们遵循 [Dart 官方代码风格指南](https://dart.dev/guides/language/effective-dart/style)
```dart
// ✅ 好的示例
class ApiGenerator {
final String className;
final bool generateModels;
ApiGenerator({
required this.className,
this.generateModels = true,
});
String generateCode() {
// 实现逻辑
return 'generated code';
}
}
// ❌ 不好的示例
class api_generator {
String class_name;
bool generate_models;
api_generator(this.class_name, this.generate_models);
String generate_code() {
return "generated code";
}
}
```
### 命名约定
- **类名**: PascalCase (`ApiGenerator`)
- **方法名**: camelCase (`generateCode`)
- **变量名**: camelCase (`className`)
- **常量**: SCREAMING_SNAKE_CASE (`DEFAULT_TIMEOUT`)
- **文件名**: snake_case (`api_generator.dart`)
### 注释规范
```dart
/// 生成 Retrofit API 代码的生成器
///
/// 支持多种配置选项,包括:
/// - 模块化 API 生成
/// - 基础响应类型
/// - 分页支持
///
/// 示例用法:
/// ```dart
/// final generator = RetrofitApiGenerator(
/// className: 'ApiService',
/// splitByTags: true,
/// );
/// ```
class RetrofitApiGenerator {
/// API 服务类名
final String className;
/// 是否按标签分割 API
final bool splitByTags;
/// 创建 Retrofit API 生成器
///
/// [className] 生成的 API 服务类名
/// [splitByTags] 是否按标签分割成多个 API 类
RetrofitApiGenerator({
this.className = 'ApiService',
this.splitByTags = false,
});
}
```
## 🧪 测试指南
### 测试结构
```
tests/
├── unit/ # 单元测试
│ ├── generators/ # 生成器测试
│ ├── parsers/ # 解析器测试
│ └── validators/ # 验证器测试
├── integration/ # 集成测试
└── fixtures/ # 测试数据
```
### 编写测试
```dart
import 'package:test/test.dart';
import 'package:swagger_generator_flutter/swagger_generator_flutter.dart';
void main() {
group('RetrofitApiGenerator', () {
late RetrofitApiGenerator generator;
setUp(() {
generator = RetrofitApiGenerator(
className: 'TestApi',
splitByTags: false,
);
});
test('should generate basic API structure', () {
// Arrange
final document = createTestDocument();
// Act
final result = generator.generateFromDocument(document);
// Assert
expect(result, contains('abstract class TestApi'));
expect(result, contains('@RestApi()'));
});
test('should handle empty document', () {
// Arrange
final emptyDocument = createEmptyDocument();
// Act & Assert
expect(() => generator.generateFromDocument(emptyDocument),
returnsNormally);
});
});
}
SwaggerDocument createTestDocument() {
return SwaggerDocument(
title: 'Test API',
version: '1.0.0',
description: 'Test',
servers: [],
components: ApiComponents(schemas: {}, securitySchemes: {}),
paths: {},
models: {},
controllers: {},
security: [],
);
}
```
### 测试覆盖率
我们目标是保持 90%+ 的测试覆盖率:
```bash
# 运行测试并生成覆盖率报告
dart test --coverage=coverage
genhtml coverage/lcov.info -o coverage/html
```
## 📚 文档贡献
### 文档类型
1. **API 文档**: 代码中的 dartdoc 注释
2. **用户指南**: README.md 和 docs/ 目录
3. **示例代码**: example/ 目录
4. **迁移指南**: MIGRATION_GUIDE.md
### 文档风格
- 使用清晰、简洁的语言
- 提供实际的代码示例
- 包含常见用例和最佳实践
- 保持文档与代码同步
## 🔄 发布流程
### 版本号规范
我们遵循 [语义化版本](https://semver.org/lang/zh-CN/)
- **主版本号**: 不兼容的 API 修改
- **次版本号**: 向下兼容的功能性新增
- **修订号**: 向下兼容的问题修正
### 提交信息规范
我们使用 [Conventional Commits](https://www.conventionalcommits.org/zh-hans/)
```
<类型>[可选的作用域]: <描述>
[可选的正文]
[可选的脚注]
```
**类型:**
- `feat`: 新功能
- `fix`: 修复 bug
- `docs`: 文档更新
- `style`: 代码格式调整
- `refactor`: 重构
- `test`: 测试相关
- `chore`: 构建过程或辅助工具的变动
**示例:**
```
feat(generator): add support for file upload
- Add MultipartFile support in OptimizedRetrofitGenerator
- Generate proper @MultiPart annotations
- Update tests and documentation
Closes #123
```
## 🏗️ 开发环境设置
### 必需工具
- Dart SDK 3.0+
- Flutter SDK 3.0+
- Git
### 推荐工具
- VS Code 或 IntelliJ IDEA
- Dart 和 Flutter 插件
- Git hooks (pre-commit)
### 环境配置
1. **克隆项目**
```bash
git clone https://github.com/your-repo/swagger_generator_flutter.git
cd swagger_generator_flutter
```
2. **安装依赖**
```bash
flutter pub get
```
3. **运行测试**
```bash
dart test
```
4. **运行示例**
```bash
dart run example/basic_usage.dart
```
### 开发工作流
1. 创建功能分支
2. 编写代码和测试
3. 运行所有测试
4. 更新文档
5. 提交代码
6. 创建 Pull Request
## 🎯 贡献领域
我们特别欢迎以下领域的贡献:
### 高优先级
- 🐛 Bug 修复
- 📚 文档改进
- 🧪 测试覆盖率提升
- 🚀 性能优化
### 中优先级
- ✨ 新功能开发
- 🔧 工具改进
- 📝 示例代码
- 🌐 国际化支持
### 低优先级
- 🎨 UI/UX 改进
- 📦 依赖更新
- 🔍 代码质量提升
## 📞 联系我们
- **GitHub Issues**: 报告 bug 和功能请求
- **GitHub Discussions**: 一般讨论和问题
- **Email**: maintainer@example.com
## 📄 许可证
通过贡献代码,您同意您的贡献将在与项目相同的 [MIT 许可证](LICENSE) 下授权。
## 🙏 致谢
感谢所有贡献者的努力!您的贡献让这个项目变得更好。
### 贡献者列表
<!-- 这里会自动生成贡献者列表 -->
---
再次感谢您的贡献!🎉

View File

@ -1,293 +0,0 @@
# 📦 Dev Dependency 功能完成总结
本文档总结了将 `swagger_generator_flutter` 配置为 dev_dependencies 的所有改动。
## ✅ 完成的工作
### 1. 核心配置文件修改
#### `pubspec.yaml`
- ✅ 添加 `executables` 配置
- ✅ 映射命令:`swagger_generator: main`
- ✅ 更新描述信息
- ✅ 版本升级到 2.1.1
```yaml
executables:
swagger_generator: main
```
这使得其他项目可以通过以下命令使用:
```bash
dart run swagger_generator_flutter generate --all
```
### 2. 新增文档文件
#### `USAGE_AS_DEV_DEPENDENCY.md`
完整的使用指南,包含:
- 📦 安装步骤
- 📝 配置文件说明
- 🚀 使用方法3种方式
- 📋 命令选项说明
- 📂 生成的文件结构
- 🔧 必需的项目依赖
- 🔄 完整工作流程
- 🎯 CI/CD 集成示例
- 🐛 常见问题排除
- 💡 最佳实践
#### `generator_config.template.yaml`
配置文件模板,用户可以:
- 复制到自己的项目
- 根据项目需求修改
- 包含详细的配置说明和注释
### 3. 示例项目(`example/as_dev_dependency/`
创建了完整的示例应用,包含以下文件:
#### 项目配置文件
- ✅ `pubspec.yaml` - 依赖配置(使用本地路径引用)
- ✅ `generator_config.yaml` - 生成器配置(使用 Petstore API
- ✅ `analysis_options.yaml` - 代码分析配置
- ✅ `.gitignore` - Git 忽略配置
#### 基础代码文件
- ✅ `lib/common/api_response.dart` - 通用 API 响应包装类
- ✅ `lib/common/paged_response.dart` - 分页响应包装类
- ✅ `lib/main.dart` - Flutter 应用入口(带使用说明)
#### 自动化脚本
- ✅ `generate_api.sh` - macOS/Linux 生成脚本
- ✅ `generate_api.bat` - Windows 生成脚本
- ✅ `Makefile` - Make 命令配置12+ 命令)
#### 文档
- ✅ `README.md` - 完整的项目说明
- ✅ `QUICK_START.md` - 5分钟快速开始指南
### 4. 主项目文档更新
#### `README.md`
- ✅ 新增 "📦 作为 dev_dependencies 使用" 章节
- ✅ 提供快速开始步骤
- ✅ 添加文档链接
#### `CHANGELOG.md`
- ✅ 新增 [2.1.1] 版本说明
- ✅ 详细记录所有新特性
- ✅ 说明文档更新
## 📂 文件结构总览
```
swagger_generator_flutter/
├── pubspec.yaml ← 更新:添加 executables
├── CHANGELOG.md ← 更新:新增 2.1.1 版本
├── README.md ← 更新:新增使用章节
├── USAGE_AS_DEV_DEPENDENCY.md ← 新增:完整使用指南
├── generator_config.template.yaml ← 新增:配置模板
├── DEV_DEPENDENCY_SETUP_SUMMARY.md ← 新增:本文件
└── example/
└── as_dev_dependency/ ← 新增:完整示例项目
├── pubspec.yaml
├── generator_config.yaml
├── analysis_options.yaml
├── .gitignore
├── Makefile
├── generate_api.sh ← 可执行
├── generate_api.bat
├── README.md
├── QUICK_START.md
└── lib/
├── common/
│ ├── api_response.dart
│ └── paged_response.dart
└── main.dart
```
## 🎯 使用方式总结
### 在其他项目中使用
#### 1. 添加依赖
```yaml
dev_dependencies:
swagger_generator_flutter:
git:
url: https://github.com/your-org/swagger_generator_flutter.git
ref: develop
```
#### 2. 创建配置
```bash
cp node_modules/swagger_generator_flutter/generator_config.template.yaml \
generator_config.yaml
```
#### 3. 生成代码
```bash
dart run swagger_generator_flutter generate --all
dart run build_runner build --delete-conflicting-outputs
```
### 测试示例项目
```bash
cd example/as_dev_dependency
flutter pub get
./generate_api.sh # 或 make build
flutter run
```
## 📋 功能清单
### ✅ 已实现功能
- [x] 作为 dev_dependencies 使用
- [x] 通过 `dart run` 命令执行
- [x] 从项目根目录读取配置文件
- [x] 完整的使用文档
- [x] 配置文件模板
- [x] 完整的示例项目
- [x] 自动化生成脚本Shell/Batch
- [x] Makefile 命令支持
- [x] CI/CD 集成示例
- [x] 故障排除指南
- [x] 最佳实践建议
### 🎓 文档完整性
- [x] 快速开始指南QUICK_START.md
- [x] 完整使用指南USAGE_AS_DEV_DEPENDENCY.md
- [x] 示例项目文档example/as_dev_dependency/README.md
- [x] 配置文件模板generator_config.template.yaml
- [x] 主 README 更新
- [x] CHANGELOG 更新
### 🛠️ 辅助工具
- [x] Shell 脚本macOS/Linux
- [x] Batch 脚本Windows
- [x] Makefile12+ 命令)
- [x] .gitignore 配置
- [x] analysis_options.yaml
## 🚀 下一步建议
### 发布前检查
1. **测试示例项目**
```bash
cd example/as_dev_dependency
make build
flutter run
```
2. **验证配置文件**
- 确保 `generator_config.template.yaml` 包含所有必要配置
- 验证路径和导入是否正确
3. **文档审查**
- 检查所有链接是否有效
- 确保示例代码可以运行
- 验证命令是否正确
4. **版本发布**
- 更新版本号到 2.1.1
- 创建 Git tag
- 发布到仓库
### 可选增强功能
- [ ] 添加更多示例(不同的 Swagger API
- [ ] 创建视频教程
- [ ] 添加单元测试
- [ ] 创建 GitHub Actions 工作流模板
- [ ] 添加性能基准测试
- [ ] 创建 VSCode 插件/扩展
## 💡 使用技巧
### 1. 本地开发时使用相对路径
```yaml
dev_dependencies:
swagger_generator_flutter:
path: ../swagger_generator_flutter
```
### 2. 生产环境使用 Git 引用
```yaml
dev_dependencies:
swagger_generator_flutter:
git:
url: https://github.com/your-org/swagger_generator_flutter.git
ref: v2.1.1 # 使用具体版本标签
```
### 3. 使用 Makefile 简化命令
```bash
make build # 代替长命令
make watch # 监听模式
make clean # 清理
```
### 4. CI/CD 集成
```yaml
# .github/workflows/build.yml
- name: Generate API
run: dart run swagger_generator_flutter generate --all
```
## 📊 对比:改动前后
### 改动前
- ❌ 只能作为独立项目使用
- ❌ 需要复制整个项目到工作区
- ❌ 配置不灵活
- ❌ 缺少使用文档
### 改动后
- ✅ 可以作为 dev_dependencies 集成
- ✅ 通过包管理器安装
- ✅ 灵活的配置系统
- ✅ 完整的文档和示例
- ✅ 自动化工具支持
- ✅ CI/CD 友好
## 🎉 总结
现在 `swagger_generator_flutter` 已经完全支持作为 dev_dependencies 使用!
**主要优势:**
1. 🚀 **易于集成** - 一行依赖配置即可使用
2. 📝 **配置灵活** - 通过 YAML 文件自定义所有选项
3. 🔄 **工作流友好** - 提供脚本和 Makefile 支持
4. 📚 **文档完善** - 从快速开始到高级用法都有详细说明
5. 🎯 **实战示例** - 完整的示例项目可以直接运行
**用户体验:**
- 从添加依赖到生成代码只需 3 步
- 5 分钟即可开始使用
- 完善的错误处理和故障排除指南
- 支持多种使用方式命令行、脚本、Makefile
## 📞 支持
如有问题,请参考:
1. [快速开始指南](example/as_dev_dependency/QUICK_START.md)
2. [完整使用指南](USAGE_AS_DEV_DEPENDENCY.md)
3. [示例项目](example/as_dev_dependency/)
4. [主文档](README.md)
---
**版本**: 2.1.1
**更新日期**: 2025-11-05
**状态**: ✅ 完成

View File

@ -1,330 +0,0 @@
# ✅ 文件头注释配置功能
## 📋 功能说明
已添加文件头注释的配置支持,可以通过 `generator_config.yaml` 自定义生成的文件头格式。
**实现日期**: 2025-11-05
**状态**: ✅ 已完成
---
## 🎯 功能特性
### 1. 配置文件头模板
`generator_config.yaml` 中的 `templates.file_header` 配置项可以自定义文件头格式:
```yaml
# 模板配置
templates:
# 文件头模板
file_header: |
// {fileType}
// 基于 Swagger API 文档: {swaggerUrl}
// 由 {generatorName} by {author} 生成
// {copyright}
```
### 2. 支持的模板变量
| 变量 | 说明 | 示例 |
|------|------|------|
| `{fileName}` | 文件名(完整文件名) | `user_api.dart` |
| `{fileType}` | 文件类型描述 | `API 接口定义`、`模型定义` |
| `{swaggerUrl}` | Swagger 文档 URL | `https://api.example.com/swagger.json` |
| `{generatorName}` | 生成器名称 | `xy_swagger_generator` |
| `{author}` | 作者信息 | `max` |
| `{copyright}` | 版权信息 | `Copyright (C) 2025 YuanXuan. All rights reserved.` |
### 3. 生成器信息配置
生成器信息可以从 `generator` 配置项中读取:
```yaml
generator:
name: "my_custom_generator" # 生成器名称
version: "1.0" # 版本号
author: "Your Name" # 作者
copyright: "Copyright (C) 2025 Your Company. All rights reserved." # 版权信息
```
---
## 📝 配置示例
### 示例 1: 默认模板(不配置时使用)
如果不配置 `templates.file_header`,会使用默认模板:
```dart
// HealthCheck API 接口定义
// 基于 Swagger API 文档: https://api.example.com/swagger.json
// 由 xy_swagger_generator by max 生成
// Copyright (C) 2025 YuanXuan. All rights reserved.
```
### 示例 2: 自定义模板(包含文件名)
```yaml
templates:
file_header: |
// {fileName} - {fileType}
// 基于 Swagger API 文档: {swaggerUrl}
// 由 {generatorName} by {author} 生成
// {copyright}
```
**生成结果**:
```dart
// health_check_api.dart - HealthCheck API 接口定义
// 基于 Swagger API 文档: https://api.example.com/swagger.json
// 由 xy_swagger_generator by max 生成
// Copyright (C) 2025 YuanXuan. All rights reserved.
```
### 示例 3: 简洁模板
```yaml
templates:
file_header: |
// {fileType}
// Generated by {generatorName}
// {copyright}
```
**生成结果**:
```dart
// HealthCheck API 接口定义
// Generated by xy_swagger_generator
// Copyright (C) 2025 YuanXuan. All rights reserved.
```
### 示例 4: 多行模板(添加空行)
```yaml
templates:
file_header: |
// {fileType}
//
// 基于 Swagger API 文档: {swaggerUrl}
// 由 {generatorName} by {author} 生成
// {copyright}
```
**生成结果**:
```dart
// HealthCheck API 接口定义
//
// 基于 Swagger API 文档: https://api.example.com/swagger.json
// 由 xy_swagger_generator by max 生成
// Copyright (C) 2025 YuanXuan. All rights reserved.
```
### 示例 5: 公司规范模板
```yaml
generator:
name: "company_api_generator"
author: "Dev Team"
copyright: "Copyright (C) 2025 Company Inc. All rights reserved."
templates:
file_header: |
/**
* {fileType}
*
* @file {fileName}
* @generated {generatorName} by {author}
* @source {swaggerUrl}
* @copyright {copyright}
*/
```
**生成结果**:
```dart
/**
* HealthCheck API 接口定义
*
* @file health_check_api.dart
* @generated company_api_generator by Dev Team
* @source https://api.example.com/swagger.json
* @copyright Copyright (C) 2025 Company Inc. All rights reserved.
*/
```
---
## 🔍 实现细节
### 1. 配置读取流程
1. **读取模板**: 从 `templates.file_header` 读取模板字符串
2. **读取生成器信息**: 从 `generator` 配置项读取 `name`、`author`、`copyright`
3. **变量替换**: 使用实际值替换模板中的变量
4. **生成文件头**: 将替换后的模板作为文件头
### 2. 默认值处理
如果配置项不存在,使用默认值:
- `generator.name`: `'xy_swagger_generator'`
- `generator.author`: `'max'`
- `generator.copyright`: `'Copyright (C) 2025 YuanXuan. All rights reserved.'`
- `templates.file_header`: 使用默认模板格式
### 3. 文件类型说明
不同文件类型会生成不同的描述:
- **API 接口文件**: `{tagName} API 接口定义`
- **模型文件**: `{modelName} 模型定义`
- **参数实体类**: `参数实体类 - {className}`
- **索引文件**: `API 模型导出文件`
---
## ✅ 测试验证
### 测试场景
1. ✅ **默认模板** - 不配置时使用默认模板
2. ✅ **自定义模板** - 配置模板后使用自定义格式
3. ✅ **变量替换** - 所有模板变量正确替换
4. ✅ **生成器信息** - 从配置读取生成器信息
5. ✅ **多种文件类型** - API、模型、参数实体类都使用配置的模板
### 测试命令
```bash
cd example/as_dev_dependency
# 1. 配置自定义模板
# 在 generator_config.yaml 中添加:
# templates:
# file_header: |
# // {fileType}
# // Generated by {generatorName}
# 2. 运行生成
dart run swagger_generator_flutter generate --all
# 3. 检查生成的文件头
head -5 generator/api/v2/follow_manager_api.dart
```
---
## 📊 输出示例
### 使用默认模板
```dart
// HealthCheck API 接口定义
// 基于 Swagger API 文档: https://quanxue-test-api.w.23544.com:8843/swagger/v1/swagger.json
// 由 xy_swagger_generator by max 生成
// Copyright (C) 2025 YuanXuan. All rights reserved.
```
### 使用自定义模板
```dart
// HealthCheck API 接口定义
// 基于 Swagger API 文档: https://quanxue-test-api.w.23544.com:8843/swagger/v1/swagger.json
// 由 example_app_generator by Example Team 生成
// Copyright (C) 2025 Example Company. All rights reserved.
```
---
## ⚠️ 注意事项
### 1. 模板格式
- 模板使用 YAML 的多行字符串格式 (`|`)
- 支持多行注释
- 可以使用 `//``/* */` 格式
### 2. 变量替换
- 变量名必须使用大括号 `{variableName}`
- 变量名区分大小写
- 未定义的变量会被替换为空字符串
### 3. 兼容性
- 如果不配置 `templates.file_header`,会使用默认模板
- 如果配置的模板格式不正确,会尝试添加默认格式
### 4. 文件类型
- `{fileType}` 会根据文件类型自动设置
- `{fileName}` 需要显式传入(在生成时自动传入)
---
## 💡 最佳实践
### 1. 统一格式
```yaml
# ✅ 推荐:在项目根目录的 generator_config.yaml 中统一配置
templates:
file_header: |
// {fileType}
// Generated by {generatorName}
// {copyright}
```
### 2. 包含必要信息
```yaml
# ✅ 推荐:包含文件类型、来源、生成器信息
file_header: |
// {fileType}
// Source: {swaggerUrl}
// Generated by {generatorName} by {author}
```
### 3. 符合公司规范
```yaml
# ✅ 推荐:符合公司代码规范
generator:
copyright: "Copyright (C) 2025 Your Company. All rights reserved."
templates:
file_header: |
// {fileType}
// {copyright}
```
### 4. 简洁明了
```yaml
# ✅ 推荐:简洁但包含关键信息
file_header: |
// {fileType} - Generated by {generatorName}
```
---
## ✨ 总结
**已完成**:
- ✅ 添加 `templates.file_header` 配置项支持
- ✅ 实现模板变量替换(`{fileName}`, `{fileType}`, `{swaggerUrl}`, `{generatorName}`, `{author}`, `{copyright}`
- ✅ 从配置读取生成器信息(`generator.name`, `generator.author`, `generator.copyright`
- ✅ 支持默认模板(当配置不存在时)
- ✅ 更新所有生成器使用配置的文件头
**功能**:
- ✅ 完全可配置的文件头格式
- ✅ 模板变量支持
- ✅ 自动从配置读取生成器信息
- ✅ 向后兼容(默认模板)
**状态**: ✅ **功能完成,可以使用**
现在可以通过 `generator_config.yaml` 完全自定义生成的文件头格式了!

View File

@ -1,208 +0,0 @@
# included_tags 功能实现文档
## 📋 功能概述
`included_tags` 是一个新增的配置选项,允许用户只生成指定 tags 的 API 和模型代码,而不是生成整个 Swagger 文档的所有内容。
## ✨ 功能特性
### 1. 智能过滤
- **Endpoint 过滤**:只保留包含指定 tags 的 API endpoints
- **Model 过滤**:只生成被选中 endpoints 引用的 models
- **依赖追踪**:自动递归收集 model 依赖,确保生成的代码完整可用
### 2. 灵活配置
- **CLI 参数**`--included-tags=User,Pet,Store`
- **配置文件**:在 `generator_config.yaml` 中配置
- **可选功能**:如果不指定,则生成所有 tags保持向后兼容
### 3. 多 Tag 支持
- 如果某个 endpoint 有多个 tags只要其中一个在 `included_tags` 列表中,就会生成该 endpoint
- 支持逗号分隔的多个 tags
## 🚀 使用方法
### 方式一CLI 命令行
```bash
# 只生成 User 和 Pet 相关的代码
dart run swagger_generator_flutter generate --all --included-tags=User,Pet
# 使用短选项
dart run swagger_generator_flutter generate --all -i User,Pet,Store
# 只生成 API不生成模型
dart run swagger_generator_flutter generate --api --included-tags=User
# 只生成模型
dart run swagger_generator_flutter generate --models --included-tags=Pet,Store
```
### 方式二:配置文件
`generator_config.yaml` 中添加:
```yaml
output:
split_by_tags: true
# 只生成指定 tags
included_tags:
- "User"
- "Pet"
- "Store"
```
## 📊 工作原理
### 1. 过滤流程
```
原始 Swagger 文档
解析所有 paths 和 models
根据 included_tags 过滤 paths
收集被使用的 model 名称
递归收集 model 依赖
生成过滤后的代码
```
### 2. Model 依赖追踪
系统会自动追踪以下依赖关系:
- **RequestBody 引用**:从请求体中提取 model 引用
- **Response 引用**:从响应中提取 model 引用
- **Property 引用**:从 model 属性中提取引用
- **数组类型**:处理 `List<Model>` 类型的依赖
- **嵌套属性**:处理嵌套对象的引用
- **组合模式**:处理 `allOf`, `oneOf`, `anyOf` 的引用
### 3. 示例
假设 Swagger 文档有以下结构:
```yaml
paths:
/users:
get:
tags: [User]
responses:
200:
schema:
$ref: '#/components/schemas/UserList'
/pets:
get:
tags: [Pet]
responses:
200:
schema:
$ref: '#/components/schemas/PetList'
/stores:
get:
tags: [Store]
responses:
200:
schema:
$ref: '#/components/schemas/StoreList'
components:
schemas:
UserList:
properties:
items:
type: array
items:
$ref: '#/components/schemas/User'
User:
properties:
id: { type: integer }
name: { type: string }
PetList: ...
Pet: ...
StoreList: ...
Store: ...
```
**使用 `--included-tags=User`**
- ✅ 生成 `/users` endpoint
- ✅ 生成 `UserList` model
- ✅ 生成 `User` model依赖
- ❌ 跳过 `/pets` endpoint
- ❌ 跳过 `PetList``Pet` models
- ❌ 跳过 `/stores` endpoint
- ❌ 跳过 `StoreList``Store` models
## 📁 修改的文件
### 1. `lib/commands/generate_command.dart`
- 添加 `--included-tags` CLI 参数
- 在 `GenerateOptions` 类中添加 `includedTags` 字段
- 实现 `_filterDocumentByTags()` 方法
- 实现 `_collectUsedModels()` 方法
- 实现 `_collectModelDependencies()` 方法
- 实现 `_extractModelNameFromRef()` 方法
### 2. `generator_config.template.yaml`
- 添加 `included_tags` 配置项文档和示例
### 3. `README.md`
- 添加 CLI 命令选项说明
- 添加使用示例
- 添加行为说明
## 🧪 测试
运行测试脚本:
```bash
./test_included_tags.sh
```
或手动测试:
```bash
# 测试 1: 不指定 tags生成所有
dart run bin/main.dart generate --all
# 测试 2: 指定单个 tag
dart run bin/main.dart generate --all --included-tags=MobileManager
# 测试 3: 指定多个 tags
dart run bin/main.dart generate --all --included-tags=User,Pet,Store
```
## ✅ 兼容性
- ✅ 与现有 `split-by-tags` 选项完全兼容
- ✅ 向后兼容:不指定 `included_tags` 时行为不变
- ✅ 支持所有生成模式(--all, --api, --models, --docs
- ✅ 支持配置文件和 CLI 参数两种方式
## 🎯 使用场景
1. **大型项目**:只生成当前模块需要的 API减少生成时间和代码量
2. **模块化开发**:不同团队只生成自己负责的 API 模块
3. **增量开发**:先实现部分功能,逐步添加更多 tags
4. **测试和调试**:快速生成特定模块的代码进行测试
## 📝 注意事项
1. **Tag 名称大小写敏感**:确保 tag 名称与 Swagger 文档中的完全一致
2. **依赖完整性**:系统会自动追踪所有依赖,无需手动指定
3. **多 Tag Endpoint**:如果 endpoint 有多个 tags只要匹配一个就会生成
4. **空列表行为**:如果 `included_tags` 为空或未指定,生成所有 tags
## 🔧 未来改进
可能的增强功能:
- [ ] 支持 tag 通配符(如 `User*` 匹配所有以 User 开头的 tags
- [ ] 支持排除 tags`excluded_tags`
- [ ] 生成 tag 依赖关系图
- [ ] 统计每个 tag 的代码量

View File

@ -1,270 +0,0 @@
# included_tags 功能实现总结
## ✅ 实现完成
`included_tags` 功能已完整实现并测试通过!
## 📊 实现概览
### 修改的文件
| 文件 | 修改内容 | 行数 |
|------|----------|------|
| `lib/commands/generate_command.dart` | 添加 CLI 参数、过滤逻辑、依赖追踪 | +200 |
| `generator_config.template.yaml` | 添加配置文档和示例 | +8 |
| `README.md` | 添加使用说明和示例 | +35 |
### 新增的文件
| 文件 | 说明 |
|------|------|
| `INCLUDED_TAGS_FEATURE.md` | 完整的功能文档 |
| `INCLUDED_TAGS_IMPLEMENTATION_SUMMARY.md` | 实现总结(本文件)|
| `examples/included_tags_example.md` | 使用示例 |
| `test_included_tags.sh` | 测试脚本 |
## 🎯 核心功能
### 1. CLI 参数支持
```bash
# 短选项
dart run swagger_generator_flutter generate --all -i MobileManager
# 长选项
dart run swagger_generator_flutter generate --all --included-tags=MobileManager,TaskSummarize
```
### 2. 配置文件支持
```yaml
output:
included_tags:
- "MobileManager"
- "TaskSummarize"
```
### 3. 智能过滤
- **Endpoint 过滤**:只保留包含指定 tags 的 API endpoints
- **Model 过滤**:只生成被使用的 models
- **依赖追踪**:自动递归收集 model 依赖
## 🧪 测试结果
### 测试环境
- Swagger 文档36 个 endpoints10 个 tags
- 测试项目XY Swagger Generator
### 测试用例 1: 不指定 tags生成所有
```bash
dart run bin/main.dart generate --api
```
**结果:**
- ✅ 生成 10 个 API 文件
- ✅ 生成所有 models
- ✅ 36 个 endpoints
### 测试用例 2: 指定单个 tag
```bash
dart run bin/main.dart generate --all --included-tags=MobileManager
```
**结果:**
- ✅ 只生成 1 个 API 文件mobile_manager_api.dart
- ✅ 只生成 10 个 models被 MobileManager 引用的)
- ✅ 10 个 endpoints
- ✅ 执行时间1.19 秒
- ✅ 生成 17 个文件
**对比:**
- 📉 API 文件减少 90%10 → 1
- 📉 Endpoints 减少 72%36 → 10
- ⚡ 生成速度提升
### 测试用例 3: 指定多个 tags
```bash
dart run bin/main.dart generate --all --included-tags=MobileManager,TaskSummarize
```
**预期结果:**
- ✅ 生成 2 个 API 文件
- ✅ 生成相关的 models
- ✅ 只包含这两个 tags 的 endpoints
## 🔍 代码实现细节
### 1. 过滤流程
```dart
// 在 generate_command.dart 中
if (options.includedTags != null && options.includedTags!.isNotEmpty) {
print('🔍 过滤 tags: ${options.includedTags!.join(", ")}');
document = _filterDocumentByTags(document, options.includedTags!);
}
```
### 2. Model 依赖追踪
系统会自动追踪以下依赖:
```dart
// 从 RequestBody 收集
if (mediaType.schema != null) {
final ref = mediaType.schema!['\$ref'] as String?;
if (ref != null) {
final modelName = _extractModelNameFromRef(ref);
usedModelNames.add(modelName);
}
}
// 从 Response 收集
// 从 Properties 收集
// 从 allOf/oneOf/anyOf 收集
```
### 3. 引用解析
```dart
String? _extractModelNameFromRef(String ref) {
// #/components/schemas/User -> User
// #/definitions/Pet -> Pet
if (ref.contains('/')) {
return ref.split('/').last;
}
return ref;
}
```
## 📈 性能对比
基于实际测试数据:
| 场景 | API 文件 | Endpoints | Models | 生成时间 |
|------|----------|-----------|--------|----------|
| 全部生成 | 10 | 36 | ~50 | ~2.5s |
| 只生成 MobileManager | 1 | 10 | ~10 | ~1.2s |
| 生成 2 个 tags | 2 | ~20 | ~20 | ~1.5s |
**性能提升:**
- ⚡ 生成速度提升 50%+
- 📦 代码量减少 70%+
- 🎯 更专注于当前模块
## ✨ 功能特性
### ✅ 已实现
- [x] CLI 参数支持(`--included-tags` / `-i`
- [x] 配置文件支持(`generator_config.yaml`
- [x] Endpoint 过滤
- [x] Model 依赖追踪
- [x] 递归依赖收集
- [x] 多 tag 支持
- [x] 与 `split-by-tags` 兼容
- [x] 向后兼容(不指定时生成所有)
- [x] 完整文档
- [x] 使用示例
- [x] 测试脚本
### 🎯 未来增强
- [ ] Tag 通配符支持(`User*`
- [ ] 排除 tags 支持(`excluded_tags`
- [ ] Tag 依赖关系图
- [ ] 统计每个 tag 的代码量
- [ ] 交互式 tag 选择
## 📚 文档
### 用户文档
1. **README.md** - 快速开始和基本用法
2. **INCLUDED_TAGS_FEATURE.md** - 完整功能文档
3. **examples/included_tags_example.md** - 详细使用示例
4. **generator_config.template.yaml** - 配置文件模板
### 开发文档
1. **本文件** - 实现总结
2. **代码注释** - 详细的实现说明
## 🎓 使用建议
### 适用场景
1. **大型项目**:减少生成时间和代码量
2. **模块化开发**:不同团队生成不同模块
3. **增量开发**:逐步添加功能
4. **测试调试**:快速生成特定模块
### 最佳实践
1. **明确 tag 名称**:确保与 Swagger 文档一致
2. **合理分组**:按业务模块划分 tags
3. **渐进式生成**:先生成核心模块,再扩展
4. **配置文件优先**:团队协作时使用配置文件
## 🔧 故障排除
### 问题 1: Tag 名称不匹配
**症状:** 指定了 tag 但没有生成任何文件
**解决:**
```bash
# 查看可用的 tags
cat swagger.json | jq -r '.paths | to_entries[] | .value | to_entries[] | .value.tags[]?' | sort -u
# 确保 tag 名称完全匹配(大小写敏感)
```
### 问题 2: 缺少依赖 models
**症状:** 生成的代码编译错误,提示找不到某个 model
**解决:** 这不应该发生,因为系统会自动追踪依赖。如果发生,请报告 bug。
### 问题 3: 生成了不需要的 models
**症状:** 生成的 models 比预期多
**解决:** 这是正常的,系统会包含所有依赖的 models确保代码完整可用。
## ✅ 验收标准
- [x] CLI 参数正常工作
- [x] 配置文件正常工作
- [x] 过滤逻辑正确
- [x] 依赖追踪完整
- [x] 向后兼容
- [x] 文档完整
- [x] 示例清晰
- [x] 测试通过
## 🎉 总结
`included_tags` 功能已完整实现并测试通过!
**主要成就:**
- ✅ 完整的功能实现
- ✅ 智能的依赖追踪
- ✅ 完善的文档和示例
- ✅ 实际测试验证
- ✅ 性能显著提升
**用户价值:**
- 🚀 生成速度提升 50%+
- 📦 代码量减少 70%+
- 🎯 更专注于当前模块
- 🔧 更容易维护和调试
**下一步:**
- 发布到 pub.dev
- 收集用户反馈
- 考虑实现高级功能(通配符、排除等)

257
README.md
View File

@ -27,13 +27,22 @@
- **性能监控**:内置性能统计和优化
- **增量生成**:支持增量更新和变更检测
## 🔍 当前状态要点
- 版本 **3.0.0**,命令入口统一为 `dart run swagger_generator_flutter generate`
- 支持从多个 `swagger_urls` 依次解析并按顺序合并,后者覆盖前者
- 生成的 API 会按版本落在 `api/<version>/`,类名自动添加 `V2`/`V3` 后缀v1 保持不变)
- `included_tags` / `excluded_tags``ignored_directories` / `ignored_files` 可通过配置或 CLI 控制生成范围
- 模型按用途分类生成request/result/enums/parameters分页响应优先用统一的 `BasePageResult<T>`
- `generator_config.yaml` 放在哪里就按该目录解析相对路径,便于在宿主项目中以 dev dependency 使用
## 📚 **文档和规范**
### **核心文档**
- [**代码生成规范**](./AUGMENT_CODE_GENERATION_STANDARDS.md) - 完整的生成规范和最佳实践
- [**快速参考指南**](./QUICK_REFERENCE.md) - 常见问题和解决方案
- [**代码审查清单**](./CODE_REVIEW_CHECKLIST.md) - 质量保证检查清单
- [**生成器配置**](./generator_config.yaml) - 详细的配置选项
- [**项目概览**](./docs/PROJECT_OVERVIEW.md) - 架构与功能总览
- [**使用指南**](./docs/USAGE_GUIDE.md) - 生成流程与最佳实践
- [**API 参考**](./docs/API_REFERENCE.md) - 核心类型与生成器说明
- [**快速参考**](./QUICK_REFERENCE.md) - 常见问题与命令速查
- [**配置模板**](./generator_config.template.yaml) - 复制为 `generator_config.yaml` 后按需调整
### **设计原则**
1. **OpenAPI 3.0 标准优先** - 严格遵循规范,不进行主观推断
@ -45,106 +54,77 @@
### 📦 作为 dev_dependencies 使用(推荐)
本工具可以作为 `dev_dependencies` 集成到您的 Flutter/Dart 项目中:
#### 1. 添加依赖
在您的项目 `pubspec.yaml` 中添加:
1) 在宿主项目 `pubspec.yaml` 添加依赖
```yaml
dev_dependencies:
swagger_generator_flutter:
git:
url: https://github.com/your-org/swagger_generator_flutter.git
ref: develop
# 或使用本地路径(开发时)
ref: main
# 或在开发阶段使用本地路径
# swagger_generator_flutter:
# path: ../swagger_generator_flutter
```
#### 2. 创建配置文件
复制 [`generator_config.template.yaml`](./generator_config.template.yaml) 到您的项目根目录,重命名为 `generator_config.yaml` 并修改配置。
#### 3. 执行代码生成
2) 在宿主项目根目录准备 `generator_config.yaml`(复制 `generator_config.template.yaml`
重点字段:
- `input.swagger_urls`:可配置多个 URL后面的会覆盖前面的同名模型/路径(适合 v1 → v2 迭代)
- `output.base_dir/api_dir/models_dir`:输出目录,支持相对路径(基于配置文件所在目录)
- `output.included_tags / excluded_tags``ignored_directories / ignored_files`:控制生成范围和跳过文件
- `generation.api.version_extraction`:自定义版本提取正则与默认版本
- `generation.api.client`:设置 ApiClient 类名/文件名
- `generation.api.base_result_import / base_page_result_import`:接入自定义响应包装类型
3) 生成代码
```bash
# 安装依赖
flutter pub get
# 生成所有文件
dart run swagger_generator_flutter generate --all
dart run build_runner build --delete-conflicting-outputs
```
CLI 里的 `--included-tags/--excluded-tags/--split-by-tags` 优先级高于配置文件。生成结果会按照版本落在 `api/<version>/` 下,模型分类在 `api_models/`
# 生成 .g.dart 文件
4) 参考示例
`example/` 目录包含可直接运行的示例与 Makefile/generate_api.* 脚本,演示 dev dependency 场景。
### 💻 独立项目使用
1) 安装依赖
```bash
flutter pub get
```
2) 直接运行 CLI可继续使用仓库内的 `generator_config.yaml` 配置)
```bash
dart run swagger_generator_flutter generate --all
# 或指定本地文件:在 swagger_urls 中写入 file:///absolute/path/to/swagger.json
```
3) 生成序列化文件
```bash
dart run build_runner build --delete-conflicting-outputs
```
📖 **详细使用说明**:查看 [作为 dev_dependencies 使用指南](./USAGE_AS_DEV_DEPENDENCY.md)
---
### 💻 独立项目使用
如果您想直接在本项目中开发和测试:
#### 1. 安装依赖
```bash
flutter pub get
```
#### 2. 基础用法(命令行)
```bash
# 生成模型和API推荐
sh run_swagger.sh all
# 分别生成
sh run_swagger.sh api # 只生成 API
sh run_swagger.sh models # 只生成模型
# 或直接使用 dart 命令
dart run bin/main.dart generate --models --api
# 只生成指定 tags 的 API 和模型
dart run bin/main.dart generate --all --included-tags=User,Pet,Store
```
### 3. 编程式用法(推荐)
### 🧩 编程式用法
```dart
import 'package:swagger_generator_flutter/swagger_generator_flutter.dart';
import 'dart:io';
import 'package:swagger_generator_flutter/parsers/swagger_data_parser.dart';
import 'package:swagger_generator_flutter/generators/retrofit_api_generator.dart';
void main() async {
// 使用高性能解析器
final parser = PerformanceParser(
config: ParseConfig(
enablePerformanceStats: true,
enableParallelParsing: true,
),
Future<void> main() async {
// 解析本地或远程的 Swagger 文档(支持 file:// 与 http(s)://
final parser = SwaggerDataParser();
final document = await parser.fetchAndParseSwaggerDocument(
'file:///absolute/path/to/swagger.json',
);
// 解析 OpenAPI 文档
final jsonString = await File('swagger.json').readAsString();
final document = await parser.parseDocument(jsonString);
// 使用优化生成器
final generator = OptimizedRetrofitGenerator(
// 生成 Dio + Retrofit 风格的 API
final generator = RetrofitApiGenerator(
className: 'ApiService',
generateModularApis: true,
generateBaseResult: true,
generatePagination: true,
generateFileUpload: true,
useRetrofit: true,
useDio: true,
splitByTags: true,
versionedApi: true,
);
// 生成代码
final generatedCode = generator.generateFromDocument(document);
// 保存文件
await File('lib/api/api_service.dart').writeAsString(generatedCode);
// 查看性能统计
final stats = parser.lastStats;
print('解析时间: ${stats?.totalTime.inMilliseconds}ms');
print('生成的路径数: ${stats?.pathCount}');
final code = generator.generateFromDocument(document);
await File('lib/api/api_service.dart').writeAsString(code);
}
```
@ -252,28 +232,7 @@ final generator = RetrofitApiGenerator(
);
```
#### 2. OptimizedRetrofitGenerator推荐
```dart
final generator = OptimizedRetrofitGenerator(
className: 'ApiService',
generateModularApis: true, // 模块化 API
generateBaseResult: true, // 基础响应类型
generatePagination: true, // 分页支持
generateFileUpload: true, // 文件上传
baseResultType: 'BaseResult', // 自定义基础类型
pageResultType: 'PageResult', // 自定义分页类型
);
```
#### 3. PerformanceGenerator高性能
```dart
final generator = PerformanceGenerator(
maxConcurrency: 4, // 最大并发数
enableCaching: true, // 启用缓存
enableIncremental: true, // 增量生成
enableParallel: true, // 并行生成
);
```
### 解析器配置
```dart
@ -307,17 +266,25 @@ print(errorReport);
## 📊 性能优化
### 缓存策略
当前版本不再内置 SmartCache。建议在业务层按需实现缓存如内存 Map + 过期时间)或使用第三方库。示例:
```dart
// 配置智能缓存
final cache = SmartCache<String>(
maxSize: 1000, // 最大缓存大小
strategy: CacheStrategy.smart, // 缓存策略
defaultTtl: Duration(hours: 1), // 默认过期时间
);
class SimpleCache {
final _store = <String, (Object value, DateTime expireAt)>{};
// 获取缓存统计
final stats = cache.getStats();
print('缓存命中率: ${(stats.hitRate * 100).toStringAsFixed(1)}%');
T? get<T>(String key) {
final entry = _store[key];
if (entry == null) return null;
if (DateTime.now().isAfter(entry.$2)) {
_store.remove(key);
return null;
}
return entry.$1 as T;
}
void put(String key, Object value, {Duration ttl = const Duration(minutes: 30)}) {
_store[key] = (value, DateTime.now().add(ttl));
}
}
```
### 性能监控
@ -333,15 +300,19 @@ print(' 吞吐量: ${parseStats?.bytesPerSecond.toStringAsFixed(2)} bytes/s');
## 📁 目录结构
```
swagger_generator_flutter/
bin/ # 命令行入口
example/ # 使用示例
generator/ # 生成的 API、模型、文档
lib/ # 生成器核心代码
core/ # 核心模型和解析器
generators/ # 代码生成器
validators/ # 文档验证器
tests/ # 单元测试和集成测试
swagger.json # OpenAPI 源文件
bin/ # CLI 入口main & swagger_generator_flutter
docs/ # 项目文档概览、使用指南、API 参考)
example/ # dev dependency 场景示例(含 make / 脚本)
generator/ # 默认输出目录示例(生成的文档)
lib/ # 核心代码
commands/ # CLI 命令GenerateCommand 等)
core/ # 配置、模型、异常
generators/ # 模型/API/文档生成器
parsers/ # SwaggerDataParser
utils/ # FileUtils、StringUtils 等
validators/ # Schema/Enhanced 校验器
tests/ # 基础测试示例
generator_config.template.yaml
```
## 运行测试
@ -384,51 +355,21 @@ if (path.contains('login')) return 'UserLoginResult';
```
## 贡献指南
- **严格遵循 [代码生成规范](./AUGMENT_CODE_GENERATION_STANDARDS.md)**
- **使用 [代码审查清单](./CODE_REVIEW_CHECKLIST.md) 进行质量检查**
- 代码需包含中英文注释
- 新增功能请补充对应测试用例
- 生成规则/命名风格如有特殊需求请在 issue 说明
- 在提交前请先跑通 `dart run swagger_generator_flutter generate --all` 与必要的 `build_runner`
- 参考 [docs/USAGE_GUIDE.md](./docs/USAGE_GUIDE.md) 和 [QUICK_REFERENCE.md](./QUICK_REFERENCE.md) 保持生成规则一致
- 新增能力需补充最小可复现示例或测试
- 生成规则/命名风格如有特殊需求请在 issue 说明并同步更新文档
## 常见问题
- **生成的类型不存在?** 检查 swagger.json 中是否定义了对应的 schema
- **接口缺少参数?** 确认 swagger.json 中是否有完整的参数定义
- **可空性不正确?** 检查 swagger.json 中的 nullable 字段设置
- 更多问题请参考 [快速参考指南](./QUICK_REFERENCE.md)
### 脚本命令说明
#### Linux/macOS (run_swagger.sh)
```bash
# 显示帮助
./run_swagger.sh help
# 只生成数据模型
./run_swagger.sh models
# 只生成API文档
./run_swagger.sh docs
# 只生成Retrofit API
./run_swagger.sh api
```
#### Windows (run_swagger.bat)
```cmd
# 显示帮助
run_swagger.bat help
# 只生成数据模型
run_swagger.bat models
# 只生成API文档
run_swagger.bat docs
# 只生成Retrofit API
run_swagger.bat api
```
### 命令行提示
- 查看所有选项:`dart run swagger_generator_flutter generate --help`
- 旧版 `run_swagger.sh/.bat` 已移除,统一使用 `dart run` 入口
---
更新日期2025-07-13
作者Max
更新日期2025-11-09
作者Max

View File

@ -1,293 +0,0 @@
# ✅ ApiPaths 生成功能移除总结
## 📋 移除目标
移除 `ApiPaths` 文件(`api_paths.dart`)的生成功能,简化代码生成器。
**移除日期**: 2025-11-05
**状态**: ✅ 已完成
---
## ✅ 已移除的内容
### 1. 命令选项
**移除前**:
```dart
const CommandOption(
name: 'endpoints',
shortName: 'e',
description: '生成API端点常量',
type: OptionType.flag,
),
```
**移除后**: ✅ 已删除
### 2. 生成逻辑
**移除前**:
```dart
// 生成端点代码
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++;
}
```
**移除后**: ✅ 已删除
### 3. 生成选项类
**移除前**:
```dart
class GenerateOptions {
final bool generateEndpoints;
final bool generateModels;
// ...
}
```
**移除后**:
```dart
class GenerateOptions {
final bool generateModels;
final bool generateDocs;
final bool generateApi;
// ...
}
```
### 4. 导入语句
**移除前**:
```dart
import '../generators/endpoint_code_generator.dart';
```
**移除后**: ✅ 已删除
### 5. 配置相关
**移除前**:
```dart
static const String defaultEndpointsFile = 'generated_api_paths.dart';
static const Map<String, dynamic> defaultGenerateOptions = {
'generateEndpoints': true,
// ...
};
```
**移除后**: ✅ 已删除
### 6. 类型验证器
**移除前**:
```dart
enum CodeType { model, endpoints, documentation }
case CodeType.endpoints:
// 验证逻辑
break;
```
**移除后**:
```dart
enum CodeType { model, documentation }
```
### 7. 未使用的方法
**移除**: `_detectApiVersion()` 方法(未使用)
---
## 📁 文件变更清单
### 修改的文件
1. ✅ `lib/commands/generate_command.dart`
- 移除 `--endpoints` 选项
- 移除端点生成逻辑
- 移除 `EndpointCodeGenerator` 导入
- 更新 `GenerateOptions`
- 移除 `_detectApiVersion()` 方法
2. ✅ `lib/core/config.dart`
- 移除 `defaultEndpointsFile` 常量
- 移除 `generateEndpoints` 配置项
3. ✅ `lib/utils/type_validator.dart`
- 移除 `CodeType.endpoints` 枚举值
- 移除相关验证逻辑
4. ✅ `USAGE_AS_DEV_DEPENDENCY.md`
- 移除 `--endpoints` 选项说明
- 移除 `api_paths.dart` 文件结构说明
### 保留的文件(未修改)
- `lib/generators/endpoint_code_generator.dart` - 生成器类文件保留(未使用)
- `lib/utils/string_utils.dart` - `generateEndpointName()` 方法保留(可能被其他功能使用)
---
## 📊 移除效果
### 代码简化
| 指标 | 移除前 | 移除后 | 改善 |
|------|--------|--------|------|
| 命令选项 | 8 个 | 7 个 | ✅ 减少 1 个 |
| 生成选项字段 | 6 个 | 5 个 | ✅ 减少 1 个 |
| 生成逻辑块 | 4 个 | 3 个 | ✅ 减少 1 个 |
| 配置项 | 6 个 | 5 个 | ✅ 减少 1 个 |
### 生成的文件
**移除前**:
```
generator/
├── api_paths.dart ❌ 不再生成
├── api/
├── api_models/
└── api_documentation.md
```
**移除后**:
```
generator/
├── api/
├── api_models/
└── api_documentation.md
```
---
## ✅ 保留的功能
### 完全保留
1. ✅ **API 接口生成** (`--api`)
- Retrofit 风格 API 接口
- 版本化 API 支持
2. ✅ **数据模型生成** (`--models`)
- 请求模型
- 响应模型
- 参数模型
- 枚举类型
3. ✅ **API 文档生成** (`--docs`)
- Markdown 格式文档
- 完整的 API 说明
---
## 📋 命令选项更新
### 移除前
```bash
dart run swagger_generator_flutter generate --endpoints # ❌ 已移除
dart run swagger_generator_flutter generate --models
dart run swagger_generator_flutter generate --api
dart run swagger_generator_flutter generate --docs
```
### 移除后
```bash
dart run swagger_generator_flutter generate --models
dart run swagger_generator_flutter generate --api
dart run swagger_generator_flutter generate --docs
dart run swagger_generator_flutter generate --all # 生成所有(不包含 endpoints
```
---
## 🎯 使用建议
### 替代方案
如果需要 API 路径常量,可以考虑:
1. **使用生成的 API 接口**
- 生成的 Retrofit API 接口已经包含了路径信息
- 路径在 `@GET/@POST/@PUT/@DELETE` 注解中
2. **手动定义常量**
```dart
class ApiPaths {
static const String getUser = '/api/v1/users';
static const String createUser = '/api/v1/users';
}
```
3. **从生成的 API 接口提取**
- 使用代码分析工具从生成的 API 文件中提取路径
---
## ✅ 测试验证
### 测试场景
1. ✅ **生成所有文件** - 正常工作,不生成 `api_paths.dart`
2. ✅ **生成 API** - 正常工作
3. ✅ **生成模型** - 正常工作
4. ✅ **生成文档** - 正常工作
5. ✅ **命令帮助** - 不显示 `--endpoints` 选项
### 测试命令
```bash
cd example/as_dev_dependency
dart run swagger_generator_flutter generate --help
dart run swagger_generator_flutter generate --all
```
**结果**: ✅ 正常工作,不再生成 `api_paths.dart`
---
## 📚 相关文件
### 已修改
1. ✅ `lib/commands/generate_command.dart`
2. ✅ `lib/core/config.dart`
3. ✅ `lib/utils/type_validator.dart`
4. ✅ `USAGE_AS_DEV_DEPENDENCY.md`
### 未修改(保留)
- `lib/generators/endpoint_code_generator.dart` - 生成器类(未使用,可后续删除)
- `lib/utils/string_utils.dart` - `generateEndpointName()` 方法(可能被其他功能使用)
---
## ✨ 总结
**移除完成**:
- ✅ 移除了 `--endpoints` 命令选项
- ✅ 移除了端点生成逻辑
- ✅ 移除了相关配置项
- ✅ 更新了文档
**保留功能**:
- ✅ API 接口生成
- ✅ 数据模型生成
- ✅ API 文档生成
**状态**: ✅ **移除完成,功能正常**
现在代码生成器更加精简,不再生成 `ApiPaths` 文件!

View File

@ -59,22 +59,20 @@ class ParseConfig {
### 🏭 生成器 (Generators)
#### OptimizedRetrofitGenerator
#### RetrofitApiGenerator
优化的 Retrofit API 代码生成器,专为企业级项目设计
基于 Retrofit 注解的 API 代码生成器,支持按 tag 拆分与多版本输出
```dart
import 'package:swagger_generator_flutter/swagger_generator_flutter.dart';
// 创建生成器
final generator = OptimizedRetrofitGenerator(
final generator = RetrofitApiGenerator(
className: 'ApiService', // API 服务类名
generateModularApis: true, // 生成模块化 API
generateBaseResult: true, // 生成基础响应类型
generatePagination: true, // 生成分页支持
generateFileUpload: true, // 生成文件上传支持
baseResultType: 'BaseResult', // 基础响应类型名
pageResultType: 'BasePageResult', // 分页响应类型名
useRetrofit: true, // 使用 Retrofit 注解
useDio: true, // 使用 Dio
splitByTags: true, // 按 tag 拆分
versionedApi: true, // 多版本输出
);
// 生成代码
@ -96,38 +94,28 @@ await File('lib/api/api_service.dart').writeAsString(generatedCode);
**配置选项:**
```dart
class OptimizedRetrofitGenerator {
final String className; // 生成的类名
final bool generateModularApis; // 是否生成模块化 API
final bool generateBaseResult; // 是否生成基础响应类型
final bool generatePagination; // 是否生成分页支持
final bool generateFileUpload; // 是否生成文件上传支持
final String baseResultType; // 基础响应类型名
final String pageResultType; // 分页响应类型名
final List<String> excludeTags; // 排除的标签
final Map<String, String> typeMapping; // 类型映射
class RetrofitApiGenerator {
final String className; // 生成的类名
final bool useRetrofit; // 是否使用 Retrofit 注解
final bool useDio; // 是否使用 Dio
final bool splitByTags; // 是否按 tag 拆分
final bool versionedApi; // 是否按版本输出
}
```
#### PerformanceGenerator
#### 性能生成器(已移除)
高性能代码生成器,支持并发生成和增量更新
该项目已简化,移除了 PerformanceGenerator。请使用 RetrofitApiGenerator 作为主要生成器
```dart
final generator = PerformanceGenerator(
maxConcurrency: 4, // 最大并发数
enableCaching: true, // 启用缓存
enableIncremental: true, // 启用增量生成
enableParallel: true, // 启用并行生成
cacheStrategy: CacheStrategy.smart, // 缓存策略
final generator = RetrofitApiGenerator(
className: 'ApiService',
useRetrofit: true,
useDio: true,
splitByTags: true,
);
// 并行生成多个文件
final results = await generator.generateParallel(document, [
GenerationTask.apis,
GenerationTask.models,
GenerationTask.utils,
]);
final code = generator.generateFromDocument(document);
```
---
@ -189,42 +177,7 @@ enum ErrorSeverity {
### 🗄️ 缓存管理 (Cache)
#### SmartCache
智能缓存管理器,支持多级缓存和自动清理。
```dart
import 'package:swagger_generator_flutter/swagger_generator_flutter.dart';
// 创建缓存
final cache = SmartCache<String>(
maxSize: 1000, // 最大缓存大小
strategy: CacheStrategy.smart, // 缓存策略
defaultTtl: Duration(hours: 1), // 默认过期时间
enablePersistence: true, // 启用持久化
);
// 使用缓存
cache.put('key', 'value', ttl: Duration(minutes: 30));
final value = cache.get('key');
// 获取统计信息
final stats = cache.getStats();
print('缓存命中率: ${(stats.hitRate * 100).toStringAsFixed(1)}%');
print('内存使用: ${stats.memoryUsage}');
```
**缓存策略:**
```dart
enum CacheStrategy {
lru, // LRU (最近最少使用)
lfu, // LFU (最少使用频率)
fifo, // FIFO (先进先出)
smart, // 智能策略 (结合多种算法)
}
```
当前版本不再内置 SmartCache请在业务侧按需实现缓存策略如内存缓存/磁盘缓存),或使用成熟的第三方库。
---
### 🔧 工具类 (Utils)
@ -334,10 +287,9 @@ Future<void> generateApiCode() async {
);
// 3. 创建生成器
final generator = OptimizedRetrofitGenerator(
final generator = RetrofitApiGenerator(
className: 'ApiService',
generateModularApis: true,
generateBaseResult: true,
splitByTags: true,
);
try {
@ -401,33 +353,18 @@ Future<void> generateEnterpriseApiCode() async {
],
);
// 3. 配置性能生成器
final generator = PerformanceGenerator(
maxConcurrency: 4,
enableCaching: true,
enableIncremental: true,
enableParallel: true,
);
// 4. 配置智能缓存
final cache = SmartCache<SwaggerDocument>(
maxSize: 100,
strategy: CacheStrategy.smart,
defaultTtl: Duration(hours: 2),
// 3. 配置生成器(推荐)
final generator = RetrofitApiGenerator(
className: 'ApiService',
splitByTags: true,
);
try {
// 5. 解析和缓存文档
final cacheKey = 'swagger_document_v1';
var document = cache.get(cacheKey);
if (document == null) {
final jsonString = await File('swagger.json').readAsString();
document = await parser.parseDocument(jsonString);
cache.put(cacheKey, document);
}
// 4. 解析文档
final jsonString = await File('swagger.json').readAsString();
final document = await parser.parseDocument(jsonString);
// 6. 验证文档
// 5. 验证文档
final isValid = validator.validateDocument(document);
if (!isValid) {
final errors = validator.errorReporter
@ -437,24 +374,11 @@ Future<void> generateEnterpriseApiCode() async {
}
}
// 7. 并行生成多个文件
final results = await generator.generateParallel(document, [
GenerationTask.apis,
GenerationTask.models,
GenerationTask.utils,
GenerationTask.documentation,
]);
// 6. 生成代码(单文件或模块化)
final code = generator.generateFromDocument(document);
// 8. 保存生成的文件
for (final entry in results.entries) {
final filePath = 'lib/api/generated/${entry.key}.dart';
await FileUtils.writeStringToFile(
filePath,
entry.value,
createDirs: true,
backup: true,
);
}
// 7. 保存生成的文件
await File('lib/api/api_service.dart').writeAsString(code);
print('✅ 企业级代码生成完成!');
@ -539,6 +463,6 @@ print('缓存占用: ${usage.cacheMemory}MB');
---
**文档版本**: v2.0
**最后更新**: 2025-01-24
**文档版本**: v3.0
**最后更新**: 2025-11-21
**维护者**: Max

View File

@ -16,90 +16,67 @@ XY Swagger Generator 是一个专为 Flutter 开发优化的 OpenAPI 3.0 代码
### 架构层次
```
┌─────────────────────────────────────────────────┐
│ 用户接口层 │
├─────────────────────────────────────────────────┤
│ 命令行工具 (CLI) │
├─────────────────────────────────────────────────┤
│ 生成器层 │
│ ┌─────────────┬─────────────┬─────────────┐ │
│ │ 基础 │ 优化 │ 性能 │ │
│ │ 生成器 │ 生成器 │ 生成器 │ │
│ └─────────────┴─────────────┴─────────────┘ │
├─────────────────────────────────────────────────┤
│ 验证层 │
│ ┌─────────────┬─────────────────────────────┐ │
│ │ Schema │ Enhanced │ │
│ │ Validator │ Validator │ │
│ └─────────────┴─────────────────────────────┘ │
├─────────────────────────────────────────────────┤
│ 解析层 │
│ ┌─────────────┬─────────────────────────────┐ │
│ │ Swagger │ Performance │ │
│ │ Parser │ Parser │ │
│ └─────────────┴─────────────────────────────┘ │
├─────────────────────────────────────────────────┤
│ 核心层 │
│ ┌─────────────┬─────────────┬─────────────┐ │
│ │ Models │ Cache │ Utils │ │
│ └─────────────┴─────────────┴─────────────┘ │
└─────────────────────────────────────────────────┘
命令行输入
SwaggerCLI / GenerateCommand合并多 Swagger、处理版本与 Tag 过滤)
SwaggerDataParser下载/解析+缓存+性能监测)
生成器层ModelCodeGenerator / RetrofitApiGenerator
校验与工具Enhanced/Schema Validator、ConfigLoader、FileUtils、StringUtils
落盘输出(按版本与模型类别组织)
```
### 核心组件
#### 1. 解析器 (Parsers)
- **SwaggerDataParser**: 基础 OpenAPI 文档解析
- **PerformanceParser**: 高性能解析器,支持并行处理和流式解析
#### 1. 命令与配置
- **SwaggerCLI / GenerateCommand**: 注册命令、展示帮助、执行生成,支持多 Swagger 合并、版本化输出、Tag 过滤和忽略列表
- **ConfigLoader / SwaggerConfig**: 解析 `generator_config.yaml`,提供 swagger_urls 顺序合并、输出目录、版本提取正则、ApiClient 命名、BaseResult 导入等配置
#### 2. 验证器 (Validators)
- **SchemaValidator**: 基础 Schema 验证
- **EnhancedValidator**: 增强验证器,提供详细的错误报告
#### 2. 解析器 (Parsers)
- **SwaggerDataParser**: 支持 http(s) 与 file:// 源的 OpenAPI 解析,内置缓存与性能监测,解析 controllers/tags/schema 依赖
#### 3. 生成器 (Generators)
- **RetrofitApiGenerator**: 基础 Retrofit API 生成器
- **OptimizedRetrofitGenerator**: 优化版生成器,支持模块化和企业级特性
- **PerformanceGenerator**: 高性能生成器,支持并发和缓存
#### 3. 验证器 (Validators)
- **SchemaValidator / EnhancedValidator**: 基础与增强校验器,用于在生成前验证文档一致性
#### 4. 工具类 (Utils)
- **SmartCache**: 智能缓存管理
- **FileUtils**: 文件操作工具
- **StringUtils**: 字符串处理工具
- **TypeValidator**: 类型验证工具
#### 4. 生成器 (Generators)
- **ModelCodeGenerator**: 按 request/result/enums/parameters 分类生成模型,分页模型自动替换为 `BasePageResult<T>`,响应模型补全字符串/列表默认值
- **RetrofitApiGenerator**: 支持按 tag 拆分、多版本目录与统一 ApiClient自动生成查询参数实体并处理版本化类名
- **DocumentationGenerator**: 输出 Markdown API 文档与统计摘要
#### 5. 工具类 (Utils)
- **CacheManager / PerformanceMonitor**: 缓存解析结果并记录耗时
- **FileUtils / StringUtils**: 路径解析(基于配置文件目录)、命名转换、文件写入等
## 🔧 技术特性
### 性能优化
- **并行解析**: 支持多线程解析大型 API 文档
- **智能缓存**: 基于 LRU 算法的多级缓存机制
- **增量生成**: 只更新变更的部分,避免全量重新生成
- **内存优化**: 流式处理,降低内存占用
### 生成行为
- 支持按顺序合并多个 `swagger_urls`,后者覆盖前者(适合 v1→v2 升级)
- 版本化输出路径按版本分目录v2+ 类名自动追加 `V2`/`V3` 后缀,统一 ApiClient 聚合各版本
- Tag 过滤与模型裁剪:`included_tags`/`excluded_tags` 与依赖分析确保只生成实际使用的模型
- 分页识别:检测 `total/items` 模式并替换为 `BasePageResult<T>`,包装在 `BaseResult`
- 查询参数实体GET 查询参数超过 4 个时自动生成 parameters 类,集中导出
- 忽略列表与相对路径:`ignored_directories/files` 控制落盘,路径基于配置文件所在目录解析
- BaseResult/BasePageResult 导入可配置,模型导出自动补全统一的 index.dart
### 代码质量
- **严格类型检查**: 基于 OpenAPI Schema 的强类型生成
- **代码规范**: 统一的命名规范和代码风格
- **错误处理**: 详细的错误诊断和修复建议
- **测试覆盖**: 完整的单元测试和集成测试
### 质量保障
- 模型使用 `@JsonSerializable(checked: true, includeIfNull: false)`,字符串/非空列表默认值自动补齐(响应模型)
- Request/Response/Enums/Parameters 分类生成,分页模型避免重复定义
- 生成器提供基本语法校验与类型推断,保持 Dart 命名规范(类名 PascalCase字段 camelCase
- 文档生成器输出接口统计、控制器分组与示例,辅助对齐后端文档
### 企业级特性
- **配置管理**: 灵活的配置选项和预设模板
- **版本控制**: 支持 API 版本管理和向后兼容性
- **监控统计**: 详细的性能统计和生成报告
- **扩展性**: 插件化架构,支持自定义扩展
### 性能与观测
- CacheManager 缓存解析结果,避免重复请求 Swagger 源
- PerformanceMonitor 记录获取/解析耗时,生成流程有摘要 (SUMMARY.md)
- 文件写入前统一检查跳过策略,目录按需创建,减少无效 IO
## 📊 性能指标
### 解析性能
- **大型文档**: 支持 10MB+ 的 OpenAPI 文档
- **解析速度**: 平均 1000+ paths/second
- **内存效率**: 流式处理,内存占用 < 100MB
- **并发支持**: 最大 8 个并发解析任务
### 生成性能
- **代码生成**: 平均 500+ endpoints/second
- **文件操作**: 支持批量文件生成和原子操作
- **缓存命中率**: 智能缓存命中率 > 80%
- **增量更新**: 变更检测准确率 > 95%
## 📊 性能与可观测性
- 解析层通过 PerformanceMonitor 记录获取/解析耗时,并复用 CacheManager 结果避免重复网络请求
- 多文档合并时会输出模型/路径统计与覆盖提示,便于确认版本覆盖关系
- 生成结束会输出 SUMMARY.md 与控制台摘要,包含控制器、路径、模型数量
- 文件写入前的跳过策略减少无意义的 IO提升重复生成时的稳定性
## 🎯 应用场景
@ -133,44 +110,33 @@ XY Swagger Generator 是一个专为 Flutter 开发优化的 OpenAPI 3.0 代码
## 📈 发展路线
### 当前版本 (v2.0.x)
- ✅ 完整的 OpenAPI 3.0 支持
- ✅ 高性能解析和生成
- ✅ 企业级验证和错误处理
- ✅ Dio + Retrofit 完美集成
### 当前版本 (v2.1.x)
- ✅ dev dependency 场景的 CLI 入口与可执行别名
- ✅ 多 Swagger 顺序合并与版本化 API 输出
- ✅ Tag 过滤、忽略策略、BaseResult/BasePageResult 导入配置
- ✅ 示例项目与基础测试脚本tests/
### 下一版本 (v2.1.x)
- 🔄 GraphQL 支持
- 🔄 更多代码生成模板
- 🔄 可视化配置界面
- 🔄 CI/CD 集成工具
### 未来规划 (v3.0.x)
- 📋 多语言支持 (Kotlin, Swift)
- 📋 云端代码生成服务
- 📋 AI 辅助优化建议
- 📋 实时 API 监控
### 后续计划
- 🔄 提升自动化测试覆盖与生成结果校验
- 🔄 完善配置校验与错误提示体验
- 🔄 持续同步 README/示例与最新生成逻辑
## 🤝 社区与支持
### 文档资源
- [快速开始指南](../QUICK_REFERENCE.md)
- [项目主文档](../README.md)
- [使用指南](./USAGE_GUIDE.md)
- [API 参考文档](./API_REFERENCE.md)
- [最佳实践指南](./BEST_PRACTICES.md)
- [故障排除指南](./TROUBLESHOOTING.md)
- [快速参考](../QUICK_REFERENCE.md)
- [配置模板](../generator_config.template.yaml)
### 贡献方式
- [贡献指南](../CONTRIBUTING.md)
- [代码审查清单](../CODE_REVIEW_CHECKLIST.md)
- [开发环境搭建](./DEVELOPMENT_SETUP.md)
- [测试指南](./TESTING_GUIDE.md)
- 提交功能前运行 `dart run swagger_generator_flutter generate --all` 以及必要的 `build_runner`
- 在 Issue/PR 中附上配置片段与最小示例(可参考 example/
- 变更生成规则时同步更新 README 与 docs/
---
**项目维护者**: Max
**最后更新**: 2025-01-24
**文档版本**: v2.0
**最后更新**: 2025-11-09
**文档版本**: v2.1

View File

@ -63,10 +63,9 @@ void main() async {
final document = await parser.parseDocument(jsonString);
// 3. 创建生成器
final generator = OptimizedRetrofitGenerator(
final generator = RetrofitApiGenerator(
className: 'ApiService',
generateModularApis: true,
generateBaseResult: true,
splitByTags: true,
);
// 4. 生成并保存代码
@ -252,19 +251,16 @@ final generator = RetrofitApiGenerator(
splitByTags: true,
);
// 中型项目 - 优化生成器
final generator = OptimizedRetrofitGenerator(
// 中型项目 - 基础生成器(按标签拆分)
final generator = RetrofitApiGenerator(
className: 'ApiService',
generateModularApis: true,
generateBaseResult: true,
generatePagination: true,
splitByTags: true,
);
// 大型项目 - 性能生成器
final generator = PerformanceGenerator(
maxConcurrency: 8,
enableCaching: true,
enableIncremental: true,
// 大型项目 - 使用 RetrofitApiGenerator按标签拆分并在 CI 中并行处理多个模块
final generator = RetrofitApiGenerator(
className: 'ApiService',
splitByTags: true,
);
```
@ -517,13 +513,8 @@ class {{className}} {
使用自定义模板:
```dart
final generator = OptimizedRetrofitGenerator(
templatePath: 'templates/custom_api.mustache',
customVariables: {
'author': 'Your Name',
'generatedAt': DateTime.now().toIso8601String(),
},
);
// 使用自定义生成器时,请继承 BaseGenerator 并实现 generate()
// 或基于 RetrofitApiGenerator 的输出进行二次处理。
```
---
@ -616,12 +607,10 @@ final parser = PerformanceParser(
**解决方案**:
```dart
// 启用并行生成和缓存
final generator = PerformanceGenerator(
maxConcurrency: 4,
enableCaching: true,
enableIncremental: true,
cacheStrategy: CacheStrategy.smart,
// 在 CI 中按模块并行执行多个 RetrofitApiGenerator 任务
final generator = RetrofitApiGenerator(
className: 'ApiService',
splitByTags: true,
);
```
@ -778,6 +767,6 @@ Future<void> main() async {
---
**文档版本**: v2.0
**最后更新**: 2025-01-24
**文档版本**: v3.0
**最后更新**: 2025-11-21
**维护者**: Max

View File

@ -1,70 +0,0 @@
.PHONY: help install generate build clean run test
# 默认目标
help:
@echo "可用命令:"
@echo " make install - 安装依赖"
@echo " make generate - 生成 API 代码"
@echo " make build - 运行 build_runner"
@echo " make clean - 清理生成的文件"
@echo " make run - 运行应用"
@echo " make test - 运行测试"
# 安装依赖
install:
@echo "📦 安装依赖..."
@flutter pub get
@echo "✅ 依赖安装完成"
# 生成 API 代码
generate:
@echo "🚀 生成 API 代码..."
@dart run swagger_generator_flutter generate --all
@echo "✅ API 代码生成完成"
# 运行 build_runner
build: generate
@echo "🔧 运行 build_runner..."
@dart run build_runner build --delete-conflicting-outputs
@dart format lib/generated
@echo "✅ 构建完成"
# 监听模式
watch:
@echo "👀 启动监听模式..."
@dart run build_runner watch --delete-conflicting-outputs
# 清理生成的文件
clean:
@echo "🧹 清理生成的文件..."
@rm -rf lib/generated
@flutter clean
@echo "✅ 清理完成"
# 重新生成
regenerate: clean build
# 运行应用
run:
@echo "🚀 运行应用..."
@flutter run
# 运行测试
test:
@echo "🧪 运行测试..."
@flutter test
# 分析代码
analyze:
@echo "🔍 分析代码..."
@dart analyze
# 格式化代码
format:
@echo "📐 格式化代码..."
@dart format lib/
# 检查代码质量
check: analyze test
@echo "✅ 代码质量检查完成"

View File

@ -39,23 +39,27 @@ output:
split_by_tags: true
excluded_tags:
# 通用
- "Login"
- "MyInfo"
# K8S
- "HealthCheck"
- "FeedBackInfo"
# H5 积分
- "Points"
# H5意见反馈
- "FeedBackInfo"
# 跳过的目录列表(这些目录下的文件将不会被生成)
# 支持相对路径和绝对路径,支持目录名或完整路径
ignored_directories:
- "class_type_enum.dart"
- "sys_role_enum.dart"
- "sys_task_type_enums.dart"
- "teaching_level_enum.dart"
- "base_task_add_result.dart"
- "school_tree.dart"
- "sys_parameter.dart"
- "task_checklist_cloud_school_result.dart"
# - "class_type_enum.dart"
# - "sys_role_enum.dart"
# - "sys_task_type_enums.dart"
# - "teaching_level_enum.dart"
# - "base_task_add_result.dart"
# - "school_tree.dart"
# - "sys_parameter.dart"
# - "task_checklist_cloud_school_result.dart"
# - "api/v1" # 跳过 v1 版本的 API
# - "api_models/request" # 跳过请求模型目录
# - "./lib/generated/api/v2" # 跳过特定路径
@ -93,8 +97,8 @@ generation:
default_version: "v1"
# 基础类型配置
base_result_type: "ApiResponse"
base_page_result_type: "PagedResponse"
base_result_type: "BaseResult"
base_page_result_type: "BasePageResult"
base_result_import: "package:example_app/common/base_result.dart"
base_page_result_import: "package:example_app/common/base_page_result.dart"

16921
example/swagger.json Normal file

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,6 @@ 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';
@ -19,7 +18,7 @@ class GenerateCommand extends BaseCommand {
String get name => 'generate';
@override
String get description => '生成API代码文件模型、端点、文档等)';
String get description => '生成API代码文件模型、端点等)';
@override
String get usage => 'dart swagger_cli.dart generate [options]';
@ -32,12 +31,6 @@ class GenerateCommand extends BaseCommand {
description: '生成数据模型',
type: OptionType.flag,
),
const CommandOption(
name: 'docs',
shortName: 'd',
description: '生成API文档',
type: OptionType.flag,
),
const CommandOption(
name: 'api',
shortName: 'r',
@ -385,24 +378,6 @@ class GenerateCommand extends BaseCommand {
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);
@ -416,9 +391,7 @@ class GenerateCommand extends BaseCommand {
///
GenerateOptions _parseGenerateOptions(ParsedArguments args) {
final hasAnyFlag = args.hasOption('models') ||
args.hasOption('docs') ||
args.hasOption('api');
final hasAnyFlag = args.hasOption('models') || args.hasOption('api');
// included-tags
// >
@ -483,9 +456,6 @@ class GenerateCommand extends BaseCommand {
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),
@ -751,14 +721,24 @@ class GenerateCommand extends BaseCommand {
apiClasses[version] = {};
for (final fileName in files.keys) {
// : mobile_manager_api.dart MobileManagerApi
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[0].toUpperCase() + word.substring(1))
.map((word) =>
word.isEmpty ? '' : (word[0].toUpperCase() + word.substring(1)))
.join('');
apiClasses[version]!.add(className);
}
}
@ -842,6 +822,19 @@ class GenerateCommand extends BaseCommand {
return buffer.toString();
}
/// API API QRCodeApiMobileManagerApi
/// "abstract class XxxApi {"
List<String> _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();
} catch (_) {
return const [];
}
}
///
/// MobileManagerApi mobileManager
String _toLowerCamelCase(String className) {
@ -1090,7 +1083,6 @@ class GenerateCommand extends BaseCommand {
///
class GenerateOptions {
final bool generateModels;
final bool generateDocs;
final bool generateApi;
final bool useSimpleModels;
final bool splitByTags;
@ -1099,7 +1091,6 @@ class GenerateOptions {
const GenerateOptions({
required this.generateModels,
required this.generateDocs,
required this.generateApi,
required this.useSimpleModels,
required this.splitByTags,

View File

@ -44,7 +44,8 @@ class SwaggerConfig {
static String get baseResultImport => ConfigLoader.getBaseResultImport();
/// BasePageResult
static String get basePageResultImport => ConfigLoader.getBasePageResultImport();
static String get basePageResultImport =>
ConfigLoader.getBasePageResultImport();
///
static const String defaultDocumentationFile =

View File

@ -573,4 +573,25 @@ class ConfigLoader {
return output['split_by_tags'] as bool? ?? true;
}
///
/// imports.package_imports
static List<String> getPackageImports([Map<String, dynamic>? config]) {
final cfg = config ?? loadConfig();
if (cfg == null) {
return [];
}
final imports = cfg['imports'] as Map<String, dynamic>?;
if (imports == null) {
return [];
}
final packageImports = imports['package_imports'];
if (packageImports is List) {
return packageImports.map((e) => e.toString()).toList();
}
return [];
}
}

View File

@ -1,444 +0,0 @@
///
///
library;
import 'dart:async';
///
class CacheEntry<T> {
final String key;
final T value;
final DateTime createdAt;
final DateTime lastAccessedAt;
final Duration ttl;
final String? etag;
final int? version;
final Map<String, dynamic> metadata;
CacheEntry({
required this.key,
required this.value,
required this.createdAt,
DateTime? lastAccessedAt,
this.ttl = const Duration(hours: 1),
this.etag,
this.version,
this.metadata = const {},
}) : lastAccessedAt = lastAccessedAt ?? createdAt;
///
bool get isExpired => DateTime.now().difference(createdAt) > ttl;
///
bool get needsRefresh =>
DateTime.now().difference(lastAccessedAt) >
Duration(minutes: ttl.inMinutes ~/ 2);
///
CacheEntry<T> withAccess() {
return CacheEntry<T>(
key: key,
value: value,
createdAt: createdAt,
lastAccessedAt: DateTime.now(),
ttl: ttl,
etag: etag,
version: version,
metadata: metadata,
);
}
///
CacheEntry<T> withValue(T newValue, {String? newEtag, int? newVersion}) {
return CacheEntry<T>(
key: key,
value: newValue,
createdAt: DateTime.now(),
lastAccessedAt: DateTime.now(),
ttl: ttl,
etag: newEtag ?? etag,
version: newVersion ?? ((version ?? 0) + 1),
metadata: metadata,
);
}
}
///
enum CacheStrategy {
/// 使
lru,
/// 使
lfu,
///
fifo,
///
ttl,
///
smart,
}
///
class CacheStats {
final int totalRequests;
final int hits;
final int misses;
final int evictions;
final int size;
final int maxSize;
final Duration averageAccessTime;
final Map<String, int> keyAccessCounts;
const CacheStats({
required this.totalRequests,
required this.hits,
required this.misses,
required this.evictions,
required this.size,
required this.maxSize,
required this.averageAccessTime,
required this.keyAccessCounts,
});
double get hitRate => totalRequests > 0 ? hits / totalRequests : 0.0;
double get missRate => totalRequests > 0 ? misses / totalRequests : 0.0;
double get fillRate => maxSize > 0 ? size / maxSize : 0.0;
@override
String toString() {
return '''
Cache Statistics:
Total Requests: $totalRequests
Hits: $hits (${(hitRate * 100).toStringAsFixed(1)}%)
Misses: $misses (${(missRate * 100).toStringAsFixed(1)}%)
Evictions: $evictions
Size: $size / $maxSize (${(fillRate * 100).toStringAsFixed(1)}%)
Average Access Time: ${averageAccessTime.inMicroseconds}μs
Most Accessed Keys: ${_getTopKeys()}
''';
}
String _getTopKeys() {
final sorted = keyAccessCounts.entries.toList()
..sort((a, b) => b.value.compareTo(a.value));
return sorted.take(5).map((e) => '${e.key}(${e.value})').join(', ');
}
}
///
class SmartCache<T> {
final int _maxSize;
final CacheStrategy _strategy;
final Duration _defaultTtl;
final bool _enablePersistence;
final Map<String, CacheEntry<T>> _cache = {};
final Map<String, int> _accessCounts = {};
final Map<String, DateTime> _lastAccess = {};
final List<String> _accessOrder = [];
int _totalRequests = 0;
int _hits = 0;
int _misses = 0;
int _evictions = 0;
final List<Duration> _accessTimes = [];
SmartCache({
int maxSize = 1000,
CacheStrategy strategy = CacheStrategy.smart,
Duration defaultTtl = const Duration(hours: 1),
bool enablePersistence = false,
}) : _maxSize = maxSize,
_strategy = strategy,
_defaultTtl = defaultTtl,
_enablePersistence = enablePersistence;
///
T? get(String key) {
final stopwatch = Stopwatch()..start();
_totalRequests++;
final entry = _cache[key];
if (entry == null) {
_misses++;
stopwatch.stop();
_accessTimes.add(stopwatch.elapsed);
return null;
}
//
if (entry.isExpired) {
_cache.remove(key);
_accessCounts.remove(key);
_lastAccess.remove(key);
_accessOrder.remove(key);
_misses++;
stopwatch.stop();
_accessTimes.add(stopwatch.elapsed);
return null;
}
// 访
_hits++;
_updateAccessStats(key);
_cache[key] = entry.withAccess();
stopwatch.stop();
_accessTimes.add(stopwatch.elapsed);
return entry.value;
}
///
void put(String key, T value,
{Duration? ttl, String? etag, Map<String, dynamic>? metadata}) {
final entry = CacheEntry<T>(
key: key,
value: value,
createdAt: DateTime.now(),
ttl: ttl ?? _defaultTtl,
etag: etag,
metadata: metadata ?? {},
);
//
if (_cache.length >= _maxSize && !_cache.containsKey(key)) {
_evict();
}
_cache[key] = entry;
_updateAccessStats(key);
}
///
bool containsKey(String key) {
final entry = _cache[key];
if (entry == null) return false;
if (entry.isExpired) {
_cache.remove(key);
_accessCounts.remove(key);
_lastAccess.remove(key);
_accessOrder.remove(key);
return false;
}
return true;
}
///
T? remove(String key) {
final entry = _cache.remove(key);
_accessCounts.remove(key);
_lastAccess.remove(key);
_accessOrder.remove(key);
return entry?.value;
}
///
void clear() {
_cache.clear();
_accessCounts.clear();
_lastAccess.clear();
_accessOrder.clear();
}
///
CacheStats getStats() {
final avgAccessTime = _accessTimes.isNotEmpty
? Duration(
microseconds: _accessTimes
.map((d) => d.inMicroseconds)
.reduce((a, b) => a + b) ~/
_accessTimes.length)
: Duration.zero;
return CacheStats(
totalRequests: _totalRequests,
hits: _hits,
misses: _misses,
evictions: _evictions,
size: _cache.length,
maxSize: _maxSize,
averageAccessTime: avgAccessTime,
keyAccessCounts: Map.from(_accessCounts),
);
}
///
List<String> getKeysNeedingRefresh() {
return _cache.entries
.where((entry) => entry.value.needsRefresh)
.map((entry) => entry.key)
.toList();
}
///
Future<void> refreshKeys(
List<String> keys, Future<T> Function(String key) refreshFunction) async {
final futures = keys.map((key) async {
try {
final newValue = await refreshFunction(key);
final oldEntry = _cache[key];
if (oldEntry != null) {
_cache[key] = oldEntry.withValue(newValue);
}
} catch (e) {
//
}
});
await Future.wait(futures);
}
///
Future<void> warmUp(Map<String, Future<T> Function()> warmUpFunctions) async {
final futures = warmUpFunctions.entries.map((entry) async {
try {
final value = await entry.value();
put(entry.key, value);
} catch (e) {
//
}
});
await Future.wait(futures);
}
/// 访
void _updateAccessStats(String key) {
_accessCounts[key] = (_accessCounts[key] ?? 0) + 1;
_lastAccess[key] = DateTime.now();
// 访
_accessOrder.remove(key);
_accessOrder.add(key);
}
///
void _evict() {
if (_cache.isEmpty) return;
String? keyToEvict;
switch (_strategy) {
case CacheStrategy.lru:
keyToEvict = _evictLRU();
break;
case CacheStrategy.lfu:
keyToEvict = _evictLFU();
break;
case CacheStrategy.fifo:
keyToEvict = _evictFIFO();
break;
case CacheStrategy.ttl:
keyToEvict = _evictTTL();
break;
case CacheStrategy.smart:
keyToEvict = _evictSmart();
break;
}
if (keyToEvict != null) {
remove(keyToEvict);
_evictions++;
}
}
/// LRU
String? _evictLRU() {
if (_accessOrder.isEmpty) return null;
return _accessOrder.first;
}
/// LFU
String? _evictLFU() {
if (_accessCounts.isEmpty) return null;
final sorted = _accessCounts.entries.toList()
..sort((a, b) => a.value.compareTo(b.value));
return sorted.first.key;
}
/// FIFO
String? _evictFIFO() {
if (_cache.isEmpty) return null;
final sorted = _cache.entries.toList()
..sort((a, b) => a.value.createdAt.compareTo(b.value.createdAt));
return sorted.first.key;
}
/// TTL
String? _evictTTL() {
//
for (final entry in _cache.entries) {
if (entry.value.isExpired) {
return entry.key;
}
}
//
return _evictFIFO();
}
///
String? _evictSmart() {
if (_cache.isEmpty) return null;
//
final scores = <String, double>{};
final now = DateTime.now();
for (final entry in _cache.entries) {
final key = entry.key;
final cacheEntry = entry.value;
//
final ageFactor = now.difference(cacheEntry.createdAt).inMinutes / 60.0;
// 访访
final accessCount = _accessCounts[key] ?? 1;
final frequencyFactor = 1.0 / accessCount;
// 访访
final lastAccess = _lastAccess[key] ?? cacheEntry.createdAt;
final recencyFactor = now.difference(lastAccess).inMinutes / 60.0;
//
final expireFactor = cacheEntry.isExpired ? 10.0 : 0.0;
//
scores[key] = ageFactor * 0.3 +
frequencyFactor * 0.3 +
recencyFactor * 0.3 +
expireFactor;
}
//
final sorted = scores.entries.toList()
..sort((a, b) => b.value.compareTo(a.value));
return sorted.first.key;
}
///
Future<void> persist() async {
if (!_enablePersistence) return;
//
//
}
///
Future<void> load() async {
if (!_enablePersistence) return;
//
//
}
}

View File

@ -18,8 +18,8 @@ abstract class BaseGenerator {
String generateFileHeader(String description, {String? fileName}) {
return StringUtils.generateFileHeader(
description,
SwaggerConfig.swaggerJsonUrls.isNotEmpty
? SwaggerConfig.swaggerJsonUrls.first
SwaggerConfig.swaggerJsonUrls.isNotEmpty
? SwaggerConfig.swaggerJsonUrls.first
: '',
fileName: fileName,
fileType: description,

View File

@ -1,711 +0,0 @@
import '../core/models.dart';
import '../utils/string_utils.dart';
import 'base_generator.dart';
///
/// API文档
class DocumentationGenerator extends BaseGenerator {
final SwaggerDocument document;
final bool includeExamples;
final bool includeSchemas;
final bool includeResponses;
final String? customTitle;
DocumentationGenerator(
this.document, {
this.includeExamples = true,
this.includeSchemas = true,
this.includeResponses = true,
this.customTitle,
});
@override
String get generatorType => 'DocumentationGenerator';
@override
String generate() {
final buffer = StringBuffer();
//
_generateHeader(buffer);
//
_generateTableOfContents(buffer);
// API概述
_generateApiOverview(buffer);
//
_generateAuthenticationInfo(buffer);
// API端点文档
_generateEndpointsDocumentation(buffer);
//
if (includeSchemas) {
_generateSchemasDocumentation(buffer);
}
//
_generateErrorCodesDocumentation(buffer);
//
if (includeExamples) {
_generateExamplesDocumentation(buffer);
}
//
_generateChangeLog(buffer);
return generateTypeCheckedCode(buffer.toString());
}
///
void _generateHeader(StringBuffer buffer) {
final title = customTitle ?? document.title;
buffer.writeln('# $title');
buffer.writeln('');
if (document.description.isNotEmpty) {
buffer.writeln('${document.description}');
buffer.writeln('');
}
buffer.writeln('**版本**: ${document.version}');
buffer.writeln('**基础URL**: ${_getBaseUrl()}');
buffer.writeln('**生成时间**: ${DateTime.now().toIso8601String()}');
buffer.writeln('');
//
buffer.writeln(
'![API版本](https://img.shields.io/badge/API-${document.version}-blue.svg)');
buffer.writeln('![状态](https://img.shields.io/badge/状态-活跃-green.svg)');
buffer.writeln('');
}
///
void _generateTableOfContents(StringBuffer buffer) {
buffer.writeln('## 📋 目录');
buffer.writeln('');
buffer.writeln('- [API概述](#api概述)');
buffer.writeln('- [认证](#认证)');
buffer.writeln('- [API端点](#api端点)');
//
final controllerGroups = _groupPathsByController();
for (final controllerName in controllerGroups.keys) {
final anchor = controllerName.toLowerCase().replaceAll(' ', '-');
buffer.writeln(' - [$controllerName](#$anchor)');
}
if (includeSchemas) {
buffer.writeln('- [数据模型](#数据模型)');
}
buffer.writeln('- [错误代码](#错误代码)');
if (includeExamples) {
buffer.writeln('- [示例代码](#示例代码)');
}
buffer.writeln('- [更新日志](#更新日志)');
buffer.writeln('');
}
/// API概述
void _generateApiOverview(StringBuffer buffer) {
buffer.writeln('## 🚀 API概述');
buffer.writeln('');
//
final stats = _generateStats();
buffer.writeln('### 📊 统计信息');
buffer.writeln('');
buffer.writeln('- **总端点数**: ${stats['totalEndpoints']}');
buffer.writeln('- **控制器数**: ${stats['controllersCount']}');
buffer.writeln('- **数据模型数**: ${stats['modelsCount']}');
buffer.writeln('');
// HTTP方法统计
final methodStats = stats['methodStats'] as Map<String, int>;
buffer.writeln('### 🔗 HTTP方法分布');
buffer.writeln('');
for (final entry in methodStats.entries) {
final method = entry.key;
final count = entry.value;
final percentage =
((count / stats['totalEndpoints']) * 100).toStringAsFixed(1);
buffer.writeln('- **$method**: $count个 ($percentage%)');
}
buffer.writeln('');
//
buffer.writeln('### 🌐 服务器配置');
buffer.writeln('');
if (document.servers.isNotEmpty) {
for (final server in document.servers) {
buffer.writeln('**服务器**: `${server.url}`');
if (server.description.isNotEmpty) {
buffer.writeln('- ${server.description}');
}
if (server.variables.isNotEmpty) {
buffer.writeln('- 变量:');
server.variables.forEach((name, variable) {
buffer.writeln(
' - `$name`: ${variable.description} (默认: ${variable.defaultValue})');
});
}
buffer.writeln('');
}
} else {
buffer.writeln('**服务器**: 相对路径 `/`');
buffer.writeln('');
}
}
///
void _generateAuthenticationInfo(StringBuffer buffer) {
buffer.writeln('## 🔐 认证');
buffer.writeln('');
buffer.writeln('本API使用以下认证方式');
buffer.writeln('');
buffer.writeln('### Bearer Token');
buffer.writeln('');
buffer.writeln('在请求头中包含Authorization字段');
buffer.writeln('');
buffer.writeln('```');
buffer.writeln('Authorization: Bearer YOUR_TOKEN_HERE');
buffer.writeln('```');
buffer.writeln('');
buffer.writeln('### 获取Token');
buffer.writeln('');
buffer.writeln('请使用登录接口获取访问令牌。');
buffer.writeln('');
}
///
void _generateEndpointsDocumentation(StringBuffer buffer) {
buffer.writeln('## 📡 API端点');
buffer.writeln('');
final controllerGroups = _groupPathsByController();
for (final entry in controllerGroups.entries) {
final controllerName = entry.key;
final paths = entry.value;
buffer.writeln('### $controllerName');
buffer.writeln('');
// HTTP方法和路径排序
paths.sort((a, b) {
final methodOrder = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'];
final aIndex = methodOrder.indexOf(a.method.value);
final bIndex = methodOrder.indexOf(b.method.value);
if (aIndex != bIndex) {
return aIndex.compareTo(bIndex);
}
return a.path.compareTo(b.path);
});
for (final path in paths) {
_generateEndpointDocumentation(buffer, path);
}
buffer.writeln('');
}
}
///
void _generateEndpointDocumentation(StringBuffer buffer, ApiPath path) {
//
final title = path.summary.isNotEmpty ? path.summary : path.operationId;
buffer.writeln('#### ${path.method.value} ${path.path}');
buffer.writeln('');
if (title.isNotEmpty) {
buffer.writeln('**$title**');
buffer.writeln('');
}
//
if (path.description.isNotEmpty) {
buffer.writeln(path.description);
buffer.writeln('');
}
//
if (path.tags.isNotEmpty) {
buffer.writeln('**标签**: ${path.tags.join(', ')}');
buffer.writeln('');
}
//
if (path.parameters.isNotEmpty) {
buffer.writeln('**参数**:');
buffer.writeln('');
//
final paramGroups = <ParameterLocation, List<ApiParameter>>{};
for (final param in path.parameters) {
paramGroups.putIfAbsent(param.location, () => []).add(param);
}
for (final entry in paramGroups.entries) {
final location = entry.key;
final params = entry.value;
buffer.writeln('*${_getLocationName(location)}参数*:');
buffer.writeln('');
buffer.writeln('| 参数名 | 类型 | 必填 | 描述 | 示例 |');
buffer.writeln('|--------|------|------|------|------|');
for (final param in params) {
final required = param.required ? '' : '';
final example = param.example?.toString() ?? '-';
final description =
param.description.isNotEmpty ? param.description : '-';
buffer.writeln(
'| ${param.name} | ${param.type.value} | $required | $description | $example |');
}
buffer.writeln('');
}
}
//
if (includeResponses && path.responses.isNotEmpty) {
buffer.writeln('**响应**:');
buffer.writeln('');
for (final entry in path.responses.entries) {
final code = entry.key;
final response = entry.value;
buffer.writeln('*HTTP $code*:');
if (response.description.isNotEmpty) {
buffer.writeln('- ${response.description}');
}
buffer.writeln('');
}
}
//
if (includeExamples) {
_generateEndpointExample(buffer, path);
}
buffer.writeln('---');
buffer.writeln('');
}
///
void _generateEndpointExample(StringBuffer buffer, ApiPath path) {
buffer.writeln('**示例**:');
buffer.writeln('');
// cURL示例
buffer.writeln('```bash');
buffer.write('curl -X ${path.method.value} ');
buffer.write('${_getBaseUrl()}${path.path}');
if (path.parameters.any((p) => p.location == ParameterLocation.header)) {
buffer.write(' \\');
buffer.writeln('');
buffer.write(' -H "Authorization: Bearer YOUR_TOKEN"');
}
if (path.method == HttpMethod.post || path.method == HttpMethod.put) {
buffer.write(' \\');
buffer.writeln('');
buffer.write(' -H "Content-Type: application/json"');
buffer.write(' \\');
buffer.writeln('');
buffer.write(' -d \'{"key": "value"}\'');
}
buffer.writeln('');
buffer.writeln('```');
buffer.writeln('');
// Dart示例
buffer.writeln('```dart');
buffer.writeln('import \'dart:convert\';');
buffer.writeln('import \'package:http/http.dart\' as http;');
buffer.writeln('');
buffer.writeln('class ApiClient {');
buffer.writeln(' static const String baseUrl = \'${_getBaseUrl()}\';');
buffer.writeln(' String? _token;');
buffer.writeln('');
buffer.writeln(' void setToken(String token) {');
buffer.writeln(' _token = token;');
buffer.writeln(' }');
buffer.writeln('');
buffer.writeln(' Map<String, String> get _headers => {');
buffer.writeln(' \'Content-Type\': \'application/json\',');
buffer.writeln(
' if (_token != null) \'Authorization\': \'Bearer \$_token\',');
buffer.writeln(' };');
buffer.writeln('');
buffer
.writeln(' Future<Map<String, dynamic>> get(String endpoint) async {');
buffer.writeln(' final response = await http.get(');
buffer.writeln(' Uri.parse(\'\$baseUrl\$endpoint\'),');
buffer.writeln(' headers: _headers,');
buffer.writeln(' );');
buffer.writeln('');
buffer.writeln(' if (response.statusCode == 200) {');
buffer.writeln(' return jsonDecode(response.body);');
buffer.writeln(' } else {');
buffer.writeln(
' throw Exception(\'Failed to load data: \${response.statusCode}\');');
buffer.writeln(' }');
buffer.writeln(' }');
buffer.writeln('');
buffer.writeln(
' Future<Map<String, dynamic>> post(String endpoint, Map<String, dynamic> data) async {');
buffer.writeln(' final response = await http.post(');
buffer.writeln(' Uri.parse(\'\$baseUrl\$endpoint\'),');
buffer.writeln(' headers: _headers,');
buffer.writeln(' body: jsonEncode(data),');
buffer.writeln(' );');
buffer.writeln('');
buffer.writeln(
' if (response.statusCode == 200 || response.statusCode == 201) {');
buffer.writeln(' return jsonDecode(response.body);');
buffer.writeln(' } else {');
buffer.writeln(
' throw Exception(\'Failed to post data: \${response.statusCode}\');');
buffer.writeln(' }');
buffer.writeln(' }');
buffer.writeln('}');
buffer.writeln('```');
buffer.writeln('');
}
///
void _generateSchemasDocumentation(StringBuffer buffer) {
if (document.models.isEmpty) return;
buffer.writeln('## 📋 数据模型');
buffer.writeln('');
final sortedModels = document.models.values.toList()
..sort((a, b) => a.name.compareTo(b.name));
for (final model in sortedModels) {
_generateModelDocumentation(buffer, model);
}
}
///
void _generateModelDocumentation(StringBuffer buffer, ApiModel model) {
buffer.writeln('### ${model.name}');
buffer.writeln('');
if (model.description.isNotEmpty) {
buffer.writeln(model.description);
buffer.writeln('');
}
if (model.isEnum) {
buffer.writeln('**枚举值**:');
buffer.writeln('');
for (final value in model.enumValues) {
buffer.writeln('- `$value`');
}
buffer.writeln('');
} else {
buffer.writeln('**属性**:');
buffer.writeln('');
if (model.properties.isNotEmpty) {
buffer.writeln('| 属性名 | 类型 | 必填 | 描述 |');
buffer.writeln('|--------|------|------|------|');
for (final entry in model.properties.entries) {
final propName = entry.key;
final prop = entry.value;
final required = model.required.contains(propName) ? '' : '';
final type = _getPropertyTypeDescription(prop);
final description =
prop.description.isNotEmpty ? prop.description : '-';
buffer.writeln('| $propName | $type | $required | $description |');
}
}
buffer.writeln('');
}
// JSON示例
if (includeExamples) {
buffer.writeln('**JSON示例**:');
buffer.writeln('');
buffer.writeln('```json');
buffer.writeln(_generateModelExample(model));
buffer.writeln('```');
buffer.writeln('');
}
buffer.writeln('---');
buffer.writeln('');
}
///
void _generateErrorCodesDocumentation(StringBuffer buffer) {
buffer.writeln('## ❌ 错误代码');
buffer.writeln('');
// HTTP状态码
final errorCodes = {
'400': '请求参数错误',
'401': '未授权访问',
'403': '禁止访问',
'404': '资源不存在',
'405': '方法不允许',
'422': '参数验证失败',
'500': '服务器内部错误',
'502': '网关错误',
'503': '服务不可用',
};
buffer.writeln('| 状态码 | 描述 |');
buffer.writeln('|--------|------|');
for (final entry in errorCodes.entries) {
buffer.writeln('| ${entry.key} | ${entry.value} |');
}
buffer.writeln('');
//
buffer.writeln('### 错误响应格式');
buffer.writeln('');
buffer.writeln('```json');
buffer.writeln('{');
buffer.writeln(' "error": {');
buffer.writeln(' "code": "ERROR_CODE",');
buffer.writeln(' "message": "错误描述",');
buffer.writeln(' "details": "详细信息"');
buffer.writeln(' }');
buffer.writeln('}');
buffer.writeln('```');
buffer.writeln('');
}
///
void _generateExamplesDocumentation(StringBuffer buffer) {
buffer.writeln('## 💡 示例代码');
buffer.writeln('');
// Dart HTTP客户端示例
buffer.writeln('### Dart HTTP客户端');
buffer.writeln('');
buffer.writeln('```dart');
buffer.writeln('import \'dart:convert\';');
buffer.writeln('import \'package:http/http.dart\' as http;');
buffer.writeln('');
buffer.writeln('class ApiClient {');
buffer.writeln(' static const String baseUrl = \'${_getBaseUrl()}\';');
buffer.writeln(' String? _token;');
buffer.writeln('');
buffer.writeln(' void setToken(String token) {');
buffer.writeln(' _token = token;');
buffer.writeln(' }');
buffer.writeln('');
buffer.writeln(' Map<String, String> get _headers => {');
buffer.writeln(' \'Content-Type\': \'application/json\',');
buffer.writeln(
' if (_token != null) \'Authorization\': \'Bearer \$_token\',');
buffer.writeln(' };');
buffer.writeln('');
buffer
.writeln(' Future<Map<String, dynamic>> get(String endpoint) async {');
buffer.writeln(' final response = await http.get(');
buffer.writeln(' Uri.parse(\'\$baseUrl\$endpoint\'),');
buffer.writeln(' headers: _headers,');
buffer.writeln(' );');
buffer.writeln('');
buffer.writeln(' if (response.statusCode == 200) {');
buffer.writeln(' return jsonDecode(response.body);');
buffer.writeln(' } else {');
buffer.writeln(
' throw Exception(\'Failed to load data: \${response.statusCode}\');');
buffer.writeln(' }');
buffer.writeln(' }');
buffer.writeln('');
buffer.writeln(
' Future<Map<String, dynamic>> post(String endpoint, Map<String, dynamic> data) async {');
buffer.writeln(' final response = await http.post(');
buffer.writeln(' Uri.parse(\'\$baseUrl\$endpoint\'),');
buffer.writeln(' headers: _headers,');
buffer.writeln(' body: jsonEncode(data),');
buffer.writeln(' );');
buffer.writeln('');
buffer.writeln(
' if (response.statusCode == 200 || response.statusCode == 201) {');
buffer.writeln(' return jsonDecode(response.body);');
buffer.writeln(' } else {');
buffer.writeln(
' throw Exception(\'Failed to post data: \${response.statusCode}\');');
buffer.writeln(' }');
buffer.writeln(' }');
buffer.writeln('}');
buffer.writeln('```');
buffer.writeln('');
}
///
void _generateChangeLog(StringBuffer buffer) {
buffer.writeln('## 📝 更新日志');
buffer.writeln('');
buffer.writeln(
'### ${document.version} - ${DateTime.now().toIso8601String().split('T')[0]}');
buffer.writeln('');
buffer.writeln('- 🎉 初始版本发布');
buffer.writeln('- 📡 ${document.paths.length} 个API端点');
buffer.writeln('- 📋 ${document.models.length} 个数据模型');
buffer.writeln('- 🔧 完整的API文档');
buffer.writeln('');
buffer.writeln('---');
buffer.writeln('');
buffer.writeln('*文档由 Swagger CLI By Max 自动生成*');
buffer.writeln('');
}
///
Map<String, List<ApiPath>> _groupPathsByController() {
final groups = <String, List<ApiPath>>{};
for (final path in document.paths.values) {
final controllerName = StringUtils.extractControllerName(path);
groups.putIfAbsent(controllerName, () => []).add(path);
}
return groups;
}
// StringUtils.extractControllerName
/// URL ( OpenAPI 3.0 servers )
String _getBaseUrl() {
if (document.servers.isNotEmpty) {
return document.servers.first.url;
}
return '/'; //
}
///
String _getLocationName(ParameterLocation location) {
switch (location) {
case ParameterLocation.query:
return '查询';
case ParameterLocation.path:
return '路径';
case ParameterLocation.header:
return '请求头';
case ParameterLocation.body:
return '请求体';
case ParameterLocation.form:
return '表单';
case ParameterLocation.cookie:
return 'Cookie';
}
}
///
String _getPropertyTypeDescription(ApiProperty prop) {
String baseType = prop.type.value;
if (prop.format != null) {
baseType += ' (${prop.format})';
}
if (prop.nullable) {
baseType += '?';
}
return baseType;
}
///
String _generateModelExample(ApiModel model) {
if (model.isEnum) {
return '"${model.enumValues.first}"';
}
final buffer = StringBuffer();
buffer.writeln('{');
final properties = model.properties.entries.toList();
for (int i = 0; i < properties.length; i++) {
final entry = properties[i];
final propName = entry.key;
final prop = entry.value;
final exampleValue = _generatePropertyExample(prop);
buffer.write(' "$propName": $exampleValue');
if (i < properties.length - 1) {
buffer.write(',');
}
buffer.writeln();
}
buffer.write('}');
return buffer.toString();
}
///
String _generatePropertyExample(ApiProperty prop) {
switch (prop.type) {
case PropertyType.string:
return '"string"';
case PropertyType.integer:
return '0';
case PropertyType.number:
return '0.0';
case PropertyType.boolean:
return 'true';
case PropertyType.array:
return '[]';
case PropertyType.object:
return '{}';
case PropertyType.reference:
return '{}';
default:
return 'null';
}
}
///
Map<String, dynamic> _generateStats() {
final stats = <String, dynamic>{};
stats['totalEndpoints'] = document.paths.length;
stats['controllersCount'] = _groupPathsByController().length;
stats['modelsCount'] = document.models.length;
// HTTP方法统计
final methodStats = <String, int>{};
for (final path in document.paths.values) {
final method = path.method.value;
methodStats[method] = (methodStats[method] ?? 0) + 1;
}
stats['methodStats'] = methodStats;
return stats;
}
}

View File

@ -1,255 +0,0 @@
import '../core/models.dart';
import '../utils/string_utils.dart';
import 'base_generator.dart';
///
/// API端点常量代码
class EndpointCodeGenerator extends BaseGenerator {
final SwaggerDocument document;
final bool includeBaseUrl;
final String? customBaseUrl;
EndpointCodeGenerator(
this.document, {
this.includeBaseUrl = true,
this.customBaseUrl,
});
@override
String get generatorType => 'EndpointCodeGenerator';
@override
String generate() {
final buffer = StringBuffer();
//
buffer.writeln(generateFileHeader('API 端点常量定义'));
buffer.writeln('');
//
buffer.writeln('/// API路径常量定义');
buffer.writeln('/// 统一管理所有API端点路径便于维护和修改');
buffer.writeln('class ApiPaths {');
buffer.writeln(' ApiPaths._(); // 私有构造函数,防止实例化');
buffer.writeln('');
// URL常量 ( OpenAPI 3.0 servers )
if (includeBaseUrl) {
final baseUrl = customBaseUrl ??
(document.servers.isNotEmpty ? document.servers.first.url : '/');
buffer.writeln(' /// 基础URL');
buffer.writeln(' static const String baseUrl = \'$baseUrl\';');
buffer.writeln('');
}
//
final controllerGroups = _groupPathsByController();
for (final entry in controllerGroups.entries) {
final controllerName = entry.key;
final paths = entry.value;
buffer.writeln(' // ${controllerName}相关端点');
for (final path in paths) {
final constantName = _generateConstantName(path);
final cleanPath = StringUtils.cleanPath(path.path);
//
if (path.summary.isNotEmpty) {
buffer.writeln(' ${StringUtils.generateComment(path.summary)}');
}
if (path.description.isNotEmpty && path.description != path.summary) {
buffer.writeln(' ${StringUtils.generateComment(path.description)}');
}
buffer.writeln(' static const String $constantName = \'$cleanPath\';');
buffer.writeln('');
}
buffer.writeln('');
}
//
buffer.writeln(' /// 所有端点列表');
buffer.writeln(' static const List<String> allEndpoints = [');
for (final entry in controllerGroups.entries) {
final paths = entry.value;
for (final path in paths) {
final constantName = _generateConstantName(path);
buffer.writeln(' $constantName,');
}
}
buffer.writeln(' ];');
buffer.writeln('');
// HTTP方法常量
buffer.writeln(' /// HTTP方法常量');
buffer.writeln(' static const Map<String, String> httpMethods = {');
for (final entry in controllerGroups.entries) {
final paths = entry.value;
for (final path in paths) {
final constantName = _generateConstantName(path);
buffer.writeln(' \'$constantName\': \'${path.method.value}\',');
}
}
buffer.writeln(' };');
buffer.writeln('');
// URL构建方法
if (includeBaseUrl) {
buffer.writeln(' /// 构建完整URL');
buffer.writeln(
' static String buildUrl(String endpoint, {Map<String, dynamic>? params}) {');
buffer.writeln(' String url = baseUrl + endpoint;');
buffer.writeln(' ');
buffer.writeln(' if (params != null && params.isNotEmpty) {');
buffer.writeln(' final queryParams = <String>[];');
buffer.writeln(' params.forEach((key, value) {');
buffer.writeln(' if (value != null) {');
buffer.writeln(
' queryParams.add(\'\\\${Uri.encodeComponent(key)}=\\\${Uri.encodeComponent(value.toString())}\');');
buffer.writeln(' }');
buffer.writeln(' });');
buffer.writeln(' ');
buffer.writeln(' if (queryParams.isNotEmpty) {');
buffer.writeln(' url += \'?\' + queryParams.join(\'&\');');
buffer.writeln(' }');
buffer.writeln(' }');
buffer.writeln(' ');
buffer.writeln(' return url;');
buffer.writeln(' }');
buffer.writeln('');
//
buffer.writeln(' /// 替换路径参数');
buffer.writeln(
' static String replacePathParams(String endpoint, Map<String, dynamic> params) {');
buffer.writeln(' String result = endpoint;');
buffer.writeln(' params.forEach((key, value) {');
buffer.writeln(
' result = result.replaceAll(\'{\\\$key}\', value.toString());');
buffer.writeln(' });');
buffer.writeln(' return result;');
buffer.writeln(' }');
buffer.writeln('');
}
//
buffer.writeln(' /// 验证端点是否存在');
buffer.writeln(' static bool isValidEndpoint(String endpoint) {');
buffer.writeln(' return allEndpoints.contains(endpoint);');
buffer.writeln(' }');
buffer.writeln('');
// HTTP方法的方法
buffer.writeln(' /// 获取端点的HTTP方法');
buffer.writeln(' static String? getHttpMethod(String endpoint) {');
buffer.writeln(' return httpMethods[endpoint];');
buffer.writeln(' }');
buffer.writeln('');
buffer.writeln('}');
//
buffer.writeln('');
buffer.writeln('/// API端点枚举');
buffer.writeln('/// 提供类型安全的端点访问');
buffer.writeln('enum ApiEndpoint {');
for (final entry in controllerGroups.entries) {
final paths = entry.value;
for (final path in paths) {
final enumName = _generateEnumName(path);
final constantName = _generateConstantName(path);
if (path.summary.isNotEmpty) {
buffer.writeln(' ${StringUtils.generateComment(path.summary)}');
}
buffer.writeln(
' $enumName(ApiPaths.$constantName, \'${path.method.value}\'),');
}
}
buffer.writeln(';');
buffer.writeln('');
//
buffer.writeln(' const ApiEndpoint(this.path, this.method);');
buffer.writeln('');
buffer.writeln(' /// 端点路径');
buffer.writeln(' final String path;');
buffer.writeln('');
buffer.writeln(' /// HTTP方法');
buffer.writeln(' final String method;');
buffer.writeln('');
if (includeBaseUrl) {
buffer.writeln(' /// 获取完整URL');
buffer.writeln(' String get fullUrl => ApiPaths.baseUrl + path;');
buffer.writeln('');
}
buffer.writeln(' /// 根据路径查找端点');
buffer.writeln(' static ApiEndpoint? findByPath(String path) {');
buffer.writeln(' for (final endpoint in values) {');
buffer.writeln(' if (endpoint.path == path) {');
buffer.writeln(' return endpoint;');
buffer.writeln(' }');
buffer.writeln(' }');
buffer.writeln(' return null;');
buffer.writeln(' }');
buffer.writeln('');
buffer.writeln(' /// 根据HTTP方法过滤端点');
buffer
.writeln(' static List<ApiEndpoint> filterByMethod(String method) {');
buffer.writeln(
' return values.where((endpoint) => endpoint.method == method).toList();');
buffer.writeln(' }');
buffer.writeln('');
buffer.writeln('}');
return generateTypeCheckedCode(buffer.toString());
}
///
Map<String, List<ApiPath>> _groupPathsByController() {
final groups = <String, List<ApiPath>>{};
for (final path in document.paths.values) {
final controllerName = StringUtils.extractControllerName(path);
groups.putIfAbsent(controllerName, () => []).add(path);
}
return groups;
}
// StringUtils.extractControllerName
///
String _generateConstantName(ApiPath path) {
final baseName =
StringUtils.generateEndpointName(path.path, path.operationId);
final methodPrefix = path.method.value.toLowerCase();
return StringUtils.toCamelCase('${methodPrefix}_$baseName');
}
///
String _generateEnumName(ApiPath path) {
final baseName =
StringUtils.generateEndpointName(path.path, path.operationId);
final methodPrefix = path.method.value.toLowerCase();
return StringUtils.toCamelCase('${methodPrefix}_$baseName');
}
// StringUtils.cleanPath
}

View File

@ -1,5 +1,5 @@
import '../core/models.dart';
import '../core/config.dart';
import '../core/models.dart';
import '../utils/string_utils.dart';
import 'base_generator.dart';
@ -53,6 +53,49 @@ class ModelCodeGenerator extends ModelGenerator {
return generateAnnotatedModelCode(model);
}
@override
String getDartPropertyType(ApiProperty property) {
//
if (property.type == PropertyType.reference && property.reference != null) {
final refModel = document.models[property.reference];
// 使 BasePageResult<T>
if (refModel != null && _isPaginationResponseModel(refModel)) {
final itemsProp = refModel.properties['items'];
if (itemsProp != null && itemsProp.items != null) {
final itemType = _getPaginationItemType(itemsProp.items!);
return 'BasePageResult<$itemType>';
}
}
}
return super.getDartPropertyType(property);
}
///
String _getPaginationItemType(ApiModel items) {
//
if (items.name != 'string' &&
items.name != 'integer' &&
items.name != 'number' &&
items.name != 'boolean') {
return StringUtils.generateClassName(items.name);
}
// Dart类型
switch (items.name) {
case 'string':
return 'String';
case 'integer':
return 'int';
case 'number':
return 'double';
case 'boolean':
return 'bool';
default:
return 'dynamic';
}
}
///
String generateAnnotatedModelCode(ApiModel model) {
final className = StringUtils.generateClassName(model.name);

View File

@ -1,547 +0,0 @@
/// Retrofit API
/// Dio + Retrofit
library;
import '../core/models.dart';
import 'base_generator.dart';
/// Retrofit API
/// Dio + Retrofit
class OptimizedRetrofitGenerator extends BaseGenerator {
final String className;
final bool generateModularApis;
final bool generateBaseResult;
final bool generatePagination;
final bool generateFileUpload;
final String baseResultType;
final String pageResultType;
OptimizedRetrofitGenerator({
this.className = 'ApiService',
this.generateModularApis = true,
this.generateBaseResult = true,
this.generatePagination = true,
this.generateFileUpload = true,
this.baseResultType = 'BaseResult',
this.pageResultType = 'BasePageResult',
});
@override
String get generatorType => 'OptimizedRetrofitGenerator';
@override
String generate() {
throw UnimplementedError('Use generateFromDocument instead');
}
/// API
String generateFromDocument(SwaggerDocument document) {
final buffer = StringBuffer();
//
_generateFileHeader(buffer);
//
_generateImports(buffer);
//
if (generateBaseResult) {
_generateBaseResultTypes(buffer);
}
//
if (generatePagination) {
_generatePaginationTypes(buffer);
}
//
if (generateFileUpload) {
_generateFileUploadTypes(buffer);
}
// API API
if (generateModularApis) {
_generateModularApis(buffer, document);
} else {
_generateSingleApi(buffer, document);
}
//
_generateUtilityClasses(buffer);
return buffer.toString();
}
///
void _generateFileHeader(StringBuffer buffer) {
buffer.writeln('/// 自动生成的 API 接口文件');
buffer.writeln('/// 基于 Dio + Retrofit 架构优化');
buffer.writeln('/// 支持模块化、分页、文件上传等功能');
buffer.writeln('/// 请勿手动修改此文件');
buffer.writeln('/// 生成时间: ${DateTime.now().toIso8601String()}');
buffer.writeln();
}
///
void _generateImports(StringBuffer buffer) {
buffer.writeln('// Dart 核心库');
buffer.writeln('import \'dart:convert\';');
buffer.writeln('import \'dart:io\';');
buffer.writeln('import \'dart:typed_data\';');
buffer.writeln();
buffer.writeln('// 网络请求相关');
buffer.writeln('import \'package:dio/dio.dart\';');
buffer.writeln('import \'package:retrofit/retrofit.dart\';');
buffer.writeln('import \'package:json_annotation/json_annotation.dart\';');
buffer.writeln();
buffer.writeln('// 文件处理');
buffer.writeln('import \'package:path/path.dart\' as path;');
buffer.writeln('import \'package:http_parser/http_parser.dart\';');
buffer.writeln();
buffer.writeln('// 生成的代码');
buffer.writeln('part \'${_getGeneratedFileName()}.g.dart\';');
buffer.writeln();
}
///
void _generateBaseResultTypes(StringBuffer buffer) {
buffer.writeln('/// 基础响应结果');
buffer.writeln('@JsonSerializable(genericArgumentFactories: true)');
buffer.writeln('class $baseResultType<T> {');
buffer.writeln(' /// 响应码');
buffer.writeln(' final int code;');
buffer.writeln();
buffer.writeln(' /// 响应消息');
buffer.writeln(' final String message;');
buffer.writeln();
buffer.writeln(' /// 响应数据');
buffer.writeln(' final T? data;');
buffer.writeln();
buffer.writeln(' /// 是否成功');
buffer.writeln(' bool get isSuccess => code == 200;');
buffer.writeln();
buffer.writeln(' const $baseResultType({');
buffer.writeln(' required this.code,');
buffer.writeln(' required this.message,');
buffer.writeln(' this.data,');
buffer.writeln(' });');
buffer.writeln();
buffer.writeln(' factory $baseResultType.fromJson(');
buffer.writeln(' Map<String, dynamic> json,');
buffer.writeln(' T Function(Object? json) fromJsonT,');
buffer.writeln(' ) => _\$${baseResultType}FromJson(json, fromJsonT);');
buffer.writeln();
buffer.writeln(
' Map<String, dynamic> toJson(Object Function(T value) toJsonT) =>');
buffer.writeln(' _\$${baseResultType}ToJson(this, toJsonT);');
buffer.writeln('}');
buffer.writeln();
}
///
void _generatePaginationTypes(StringBuffer buffer) {
buffer.writeln('/// 分页参数');
buffer.writeln('@JsonSerializable()');
buffer.writeln('class BasePageParameter {');
buffer.writeln(' /// 页码从1开始');
buffer.writeln(' final int page;');
buffer.writeln();
buffer.writeln(' /// 每页大小');
buffer.writeln(' final int size;');
buffer.writeln();
buffer.writeln(' const BasePageParameter({');
buffer.writeln(' this.page = 1,');
buffer.writeln(' this.size = 20,');
buffer.writeln(' });');
buffer.writeln();
buffer.writeln(
' factory BasePageParameter.fromJson(Map<String, dynamic> json) =>');
buffer.writeln(' _\$BasePageParameterFromJson(json);');
buffer.writeln();
buffer.writeln(
' Map<String, dynamic> toJson() => _\$BasePageParameterToJson(this);');
buffer.writeln('}');
buffer.writeln();
buffer.writeln('/// 分页响应结果');
buffer.writeln('@JsonSerializable(genericArgumentFactories: true)');
buffer.writeln('class $pageResultType<T> {');
buffer.writeln(' /// 数据列表');
buffer.writeln(' final List<T> list;');
buffer.writeln();
buffer.writeln(' /// 总数量');
buffer.writeln(' final int total;');
buffer.writeln();
buffer.writeln(' /// 当前页码');
buffer.writeln(' final int page;');
buffer.writeln();
buffer.writeln(' /// 每页大小');
buffer.writeln(' final int size;');
buffer.writeln();
buffer.writeln(' /// 总页数');
buffer.writeln(' int get totalPages => (total / size).ceil();');
buffer.writeln();
buffer.writeln(' /// 是否有下一页');
buffer.writeln(' bool get hasNext => page < totalPages;');
buffer.writeln();
buffer.writeln(' /// 是否有上一页');
buffer.writeln(' bool get hasPrevious => page > 1;');
buffer.writeln();
buffer.writeln(' const $pageResultType({');
buffer.writeln(' required this.list,');
buffer.writeln(' required this.total,');
buffer.writeln(' required this.page,');
buffer.writeln(' required this.size,');
buffer.writeln(' });');
buffer.writeln();
buffer.writeln(' factory $pageResultType.fromJson(');
buffer.writeln(' Map<String, dynamic> json,');
buffer.writeln(' T Function(Object? json) fromJsonT,');
buffer.writeln(' ) => _\$${pageResultType}FromJson(json, fromJsonT);');
buffer.writeln();
buffer.writeln(
' Map<String, dynamic> toJson(Object Function(T value) toJsonT) =>');
buffer.writeln(' _\$${pageResultType}ToJson(this, toJsonT);');
buffer.writeln('}');
buffer.writeln();
}
///
void _generateFileUploadTypes(StringBuffer buffer) {
buffer.writeln('/// 文件上传请求');
buffer.writeln('@JsonSerializable()');
buffer.writeln('class FileUploadRequest {');
buffer.writeln(' /// 文件');
buffer.writeln(' @JsonKey(includeFromJson: false, includeToJson: false)');
buffer.writeln(' final MultipartFile file;');
buffer.writeln();
buffer.writeln(' /// 文件名');
buffer.writeln(' final String? filename;');
buffer.writeln();
buffer.writeln(' /// 文件类型');
buffer.writeln(' final String? contentType;');
buffer.writeln();
buffer.writeln(' const FileUploadRequest({');
buffer.writeln(' required this.file,');
buffer.writeln(' this.filename,');
buffer.writeln(' this.contentType,');
buffer.writeln(' });');
buffer.writeln();
buffer.writeln(
' factory FileUploadRequest.fromJson(Map<String, dynamic> json) =>');
buffer.writeln(' _\$FileUploadRequestFromJson(json);');
buffer.writeln();
buffer.writeln(
' Map<String, dynamic> toJson() => _\$FileUploadRequestToJson(this);');
buffer.writeln('}');
buffer.writeln();
buffer.writeln('/// 文件上传响应');
buffer.writeln('@JsonSerializable()');
buffer.writeln('class FileUploadResult {');
buffer.writeln(' /// 文件 URL');
buffer.writeln(' final String url;');
buffer.writeln();
buffer.writeln(' /// 文件名');
buffer.writeln(' final String filename;');
buffer.writeln();
buffer.writeln(' /// 文件大小');
buffer.writeln(' final int size;');
buffer.writeln();
buffer.writeln(' /// 文件类型');
buffer.writeln(' final String? contentType;');
buffer.writeln();
buffer.writeln(' const FileUploadResult({');
buffer.writeln(' required this.url,');
buffer.writeln(' required this.filename,');
buffer.writeln(' required this.size,');
buffer.writeln(' this.contentType,');
buffer.writeln(' });');
buffer.writeln();
buffer.writeln(
' factory FileUploadResult.fromJson(Map<String, dynamic> json) =>');
buffer.writeln(' _\$FileUploadResultFromJson(json);');
buffer.writeln();
buffer.writeln(
' Map<String, dynamic> toJson() => _\$FileUploadResultToJson(this);');
buffer.writeln('}');
buffer.writeln();
}
/// API
void _generateModularApis(StringBuffer buffer, SwaggerDocument document) {
// API
final modules = _groupApisByModule(document);
for (final entry in modules.entries) {
final moduleName = entry.key;
final paths = entry.value;
_generateModuleApi(buffer, moduleName, paths);
}
// API
_generateMainApiClass(buffer, modules.keys.toList());
}
/// API
void _generateSingleApi(StringBuffer buffer, SwaggerDocument document) {
buffer.writeln('/// $className API 接口');
buffer.writeln('@RestApi()');
buffer.writeln('abstract class $className {');
buffer.writeln(
' factory $className(Dio dio, {String? baseUrl}) = _$className;');
buffer.writeln();
// API
document.paths.forEach((path, apiPath) {
_generateApiMethod(buffer, path, apiPath);
});
buffer.writeln('}');
buffer.writeln();
}
/// API
Map<String, Map<String, ApiPath>> _groupApisByModule(
SwaggerDocument document) {
final modules = <String, Map<String, ApiPath>>{};
document.paths.forEach((path, apiPath) {
final moduleName = _extractModuleName(path);
modules.putIfAbsent(moduleName, () => {});
modules[moduleName]![path] = apiPath;
});
return modules;
}
///
String _extractModuleName(String path) {
final parts = path.split('/').where((part) => part.isNotEmpty).toList();
if (parts.length >= 3) {
// /api/v1/ModuleName/... -> ModuleName
return _toPascalCase(parts[2]);
}
return 'Common';
}
/// API
void _generateModuleApi(
StringBuffer buffer, String moduleName, Map<String, ApiPath> paths) {
final className = '${moduleName}Api';
buffer.writeln('/// $moduleName 模块 API');
buffer.writeln('@RestApi()');
buffer.writeln('abstract class $className {');
buffer.writeln(
' factory $className(Dio dio, {String? baseUrl}) = _$className;');
buffer.writeln();
paths.forEach((path, apiPath) {
_generateApiMethod(buffer, path, apiPath);
});
buffer.writeln('}');
buffer.writeln();
}
/// API
void _generateMainApiClass(StringBuffer buffer, List<String> modules) {
buffer.writeln('/// 主 API 服务类');
buffer.writeln('/// 包含所有模块的 API 接口');
buffer.writeln('class $className {');
buffer.writeln(' final Dio _dio;');
buffer.writeln();
// API
for (final module in modules) {
final propertyName = _toCamelCase(module);
buffer.writeln(' late final ${module}Api $propertyName;');
}
buffer.writeln();
buffer.writeln(' $className(this._dio, {String? baseUrl}) {');
// API
for (final module in modules) {
final propertyName = _toCamelCase(module);
buffer
.writeln(' $propertyName = ${module}Api(_dio, baseUrl: baseUrl);');
}
buffer.writeln(' }');
buffer.writeln('}');
buffer.writeln();
}
/// API
void _generateApiMethod(StringBuffer buffer, String path, ApiPath apiPath) {
final methodName = _generateMethodName(path, apiPath.method);
final returnType = _generateReturnType(apiPath);
final parameters = _generateParameters(apiPath);
buffer.writeln(
' /// ${apiPath.summary.isNotEmpty ? apiPath.summary : apiPath.description}');
if (apiPath.description.isNotEmpty &&
apiPath.description != apiPath.summary) {
buffer.writeln(' /// ${apiPath.description}');
}
buffer.writeln(' @${apiPath.method.value.toUpperCase()}(\'$path\')');
//
if (_isMultipartRequest(apiPath)) {
buffer.writeln(' @MultiPart()');
}
buffer.writeln(' Future<$returnType> $methodName($parameters);');
buffer.writeln();
}
///
String _generateMethodName(String path, HttpMethod method) {
final pathParts = path
.split('/')
.where((part) => part.isNotEmpty && !part.startsWith('{'))
.toList();
final methodPrefix = method.value.toLowerCase();
if (pathParts.length >= 3) {
// api/v1
pathParts.removeRange(0, 2);
}
final nameParts = pathParts.map((part) => _toPascalCase(part)).join('');
return '$methodPrefix$nameParts';
}
///
String _generateReturnType(ApiPath apiPath) {
//
final successResponse =
apiPath.responses['200'] ?? apiPath.responses['201'];
if (successResponse != null && successResponse.content.isNotEmpty) {
final jsonContent = successResponse.content['application/json'];
if (jsonContent?.schema != null) {
// schema
return '$baseResultType<dynamic>';
}
}
return '$baseResultType<void>';
}
///
String _generateParameters(ApiPath apiPath) {
final params = <String>[];
//
for (final param in apiPath.parameters
.where((p) => p.location == ParameterLocation.path)) {
params.add(
'@Path(\'${param.name}\') ${_getDartType(param.type)} ${param.name}');
}
//
for (final param in apiPath.parameters
.where((p) => p.location == ParameterLocation.query)) {
final required = param.required ? 'required ' : '';
params.add(
'@Query(\'${param.name}\') ${required}${_getDartType(param.type)}${param.required ? '' : '?'} ${param.name}');
}
//
if (apiPath.requestBody != null) {
if (_isMultipartRequest(apiPath)) {
//
params.add('@Part() MultipartFile file');
} else {
// JSON
params.add('@Body() Map<String, dynamic> body');
}
}
return params.join(', ');
}
/// multipart
bool _isMultipartRequest(ApiPath apiPath) {
if (apiPath.requestBody == null) return false;
return apiPath.requestBody!.content.keys
.any((type) => type.contains('multipart'));
}
/// Dart
String _getDartType(PropertyType type) {
switch (type) {
case PropertyType.string:
return 'String';
case PropertyType.integer:
return 'int';
case PropertyType.number:
return 'double';
case PropertyType.boolean:
return 'bool';
case PropertyType.array:
return 'List<dynamic>';
case PropertyType.object:
return 'Map<String, dynamic>';
default:
return 'dynamic';
}
}
///
void _generateUtilityClasses(StringBuffer buffer) {
buffer.writeln('/// API 工具类');
buffer.writeln('class ApiUtils {');
buffer.writeln(' /// 创建文件上传对象');
buffer.writeln(
' static Future<MultipartFile> createFileUpload(String filePath) async {');
buffer.writeln(' return MultipartFile.fromFile(');
buffer.writeln(' filePath,');
buffer.writeln(' filename: path.basename(filePath),');
buffer.writeln(' );');
buffer.writeln(' }');
buffer.writeln();
buffer.writeln(' /// 创建分页参数');
buffer.writeln(
' static BasePageParameter createPageParam({int page = 1, int size = 20}) {');
buffer.writeln(' return BasePageParameter(page: page, size: size);');
buffer.writeln(' }');
buffer.writeln('}');
}
///
String _getGeneratedFileName() {
return '${_toSnakeCase(className)}_api';
}
/// PascalCase
String _toPascalCase(String input) {
return input
.split('_')
.map((word) => word.isEmpty
? ''
: word[0].toUpperCase() + word.substring(1).toLowerCase())
.join('');
}
/// camelCase
String _toCamelCase(String input) {
final pascalCase = _toPascalCase(input);
return pascalCase.isEmpty
? ''
: pascalCase[0].toLowerCase() + pascalCase.substring(1);
}
/// snake_case
String _toSnakeCase(String input) {
return input
.replaceAllMapped(
RegExp(r'[A-Z]'), (match) => '_${match.group(0)!.toLowerCase()}')
.replaceAll(RegExp(r'^_'), '');
}
}

View File

@ -1,591 +0,0 @@
///
///
library;
import 'dart:async';
import '../core/models.dart';
import '../core/smart_cache.dart';
import 'base_generator.dart';
///
class GenerationTask {
final String id;
final String type;
final Map<String, dynamic> data;
final DateTime createdAt;
GenerationTask({
required this.id,
required this.type,
required this.data,
DateTime? createdAt,
}) : createdAt = createdAt ?? DateTime.now();
}
///
class GenerationResult {
final String taskId;
final String content;
final Duration generationTime;
final Map<String, dynamic> metadata;
const GenerationResult({
required this.taskId,
required this.content,
required this.generationTime,
this.metadata = const {},
});
}
///
class GenerationStats {
final int totalTasks;
final int completedTasks;
final int failedTasks;
final Duration totalTime;
final Duration averageTaskTime;
final int linesGenerated;
final int bytesGenerated;
final double parallelEfficiency;
const GenerationStats({
required this.totalTasks,
required this.completedTasks,
required this.failedTasks,
required this.totalTime,
required this.averageTaskTime,
required this.linesGenerated,
required this.bytesGenerated,
required this.parallelEfficiency,
});
double get successRate => totalTasks > 0 ? completedTasks / totalTasks : 0.0;
double get linesPerSecond => totalTime.inMilliseconds > 0
? linesGenerated / (totalTime.inMilliseconds / 1000)
: 0.0;
double get bytesPerSecond => totalTime.inMilliseconds > 0
? bytesGenerated / (totalTime.inMilliseconds / 1000)
: 0.0;
@override
String toString() {
return '''
Generation Performance Statistics:
Total Tasks: $totalTasks
Completed: $completedTasks (${(successRate * 100).toStringAsFixed(1)}%)
Failed: $failedTasks
Total Time: ${totalTime.inMilliseconds}ms
Average Task Time: ${averageTaskTime.inMilliseconds}ms
Lines Generated: $linesGenerated (${linesPerSecond.toStringAsFixed(1)}/s)
Bytes Generated: ${(bytesGenerated / 1024).toStringAsFixed(2)}KB (${(bytesPerSecond / 1024).toStringAsFixed(2)}KB/s)
Parallel Efficiency: ${(parallelEfficiency * 100).toStringAsFixed(1)}%
''';
}
}
///
class PerformanceGenerator extends BaseGenerator {
final int _maxConcurrency;
final bool _enableCaching;
final bool _enableIncremental;
final bool _enableParallel;
final SmartCache<String> _cache;
final Map<String, String> _previousGeneration = {};
final List<GenerationResult> _results = [];
int _totalTasks = 0;
int _completedTasks = 0;
int _failedTasks = 0;
final List<Duration> _taskTimes = [];
PerformanceGenerator({
int maxConcurrency = 4,
bool enableCaching = true,
bool enableIncremental = true,
bool enableParallel = true,
}) : _maxConcurrency = maxConcurrency,
_enableCaching = enableCaching,
_enableIncremental = enableIncremental,
_enableParallel = enableParallel,
_cache = SmartCache<String>(
maxSize: 1000,
strategy: CacheStrategy.smart,
defaultTtl: Duration(hours: 1),
);
@override
String get generatorType => 'PerformanceGenerator';
@override
String generate() {
throw UnimplementedError('Use generateFromDocument instead');
}
///
Future<String> generateFromDocument(SwaggerDocument document) async {
final stopwatch = Stopwatch()..start();
try {
//
final changes = _enableIncremental ? _analyzeChanges(document) : null;
//
final tasks = _createGenerationTasks(document, changes);
_totalTasks = tasks.length;
//
final results = _enableParallel && tasks.length > 1
? await _generateParallel(tasks)
: await _generateSequential(tasks);
//
final finalResult = _mergeResults(results);
//
if (_enableIncremental) {
_updateGenerationHistory(document, finalResult);
}
stopwatch.stop();
return finalResult;
} catch (e) {
stopwatch.stop();
rethrow;
}
}
///
Map<String, dynamic>? _analyzeChanges(SwaggerDocument document) {
final currentHash = _calculateDocumentHash(document);
final previousHash = _previousGeneration['hash'];
if (previousHash == null || currentHash != previousHash) {
return {
'hasChanges': true,
'currentHash': currentHash,
'previousHash': previousHash,
'changedSections': _detectChangedSections(document),
};
}
return {
'hasChanges': false,
'currentHash': currentHash,
};
}
///
List<GenerationTask> _createGenerationTasks(
SwaggerDocument document, Map<String, dynamic>? changes) {
final tasks = <GenerationTask>[];
//
if (_enableIncremental && changes != null && !changes['hasChanges']) {
return tasks;
}
//
tasks.add(GenerationTask(
id: 'header',
type: 'header',
data: {
'title': document.title,
'version': document.version,
'description': document.description,
},
));
//
tasks.add(GenerationTask(
id: 'imports',
type: 'imports',
data: {},
));
//
document.models.forEach((name, model) {
tasks.add(GenerationTask(
id: 'model_$name',
type: 'model',
data: {
'name': name,
'model': model,
},
));
});
// API
final pathGroups = _groupPathsByModule(document.paths);
pathGroups.forEach((module, paths) {
tasks.add(GenerationTask(
id: 'api_$module',
type: 'api',
data: {
'module': module,
'paths': paths,
},
));
});
return tasks;
}
///
Future<List<GenerationResult>> _generateParallel(
List<GenerationTask> tasks) async {
final chunks = _chunkTasks(tasks, _maxConcurrency);
final results = <GenerationResult>[];
for (final chunk in chunks) {
final chunkResults = await Future.wait(
chunk.map((task) => _executeTask(task)),
);
results.addAll(chunkResults);
}
return results;
}
///
Future<List<GenerationResult>> _generateSequential(
List<GenerationTask> tasks) async {
final results = <GenerationResult>[];
for (final task in tasks) {
final result = await _executeTask(task);
results.add(result);
}
return results;
}
///
Future<GenerationResult> _executeTask(GenerationTask task) async {
final stopwatch = Stopwatch()..start();
try {
//
if (_enableCaching) {
final cacheKey = _generateCacheKey(task);
final cached = _cache.get(cacheKey);
if (cached != null) {
stopwatch.stop();
_completedTasks++;
_taskTimes.add(stopwatch.elapsed);
return GenerationResult(
taskId: task.id,
content: cached,
generationTime: stopwatch.elapsed,
metadata: {'fromCache': true},
);
}
}
//
final content = await _generateTaskContent(task);
//
if (_enableCaching) {
final cacheKey = _generateCacheKey(task);
_cache.put(cacheKey, content);
}
stopwatch.stop();
_completedTasks++;
_taskTimes.add(stopwatch.elapsed);
return GenerationResult(
taskId: task.id,
content: content,
generationTime: stopwatch.elapsed,
metadata: {'fromCache': false},
);
} catch (e) {
stopwatch.stop();
_failedTasks++;
_taskTimes.add(stopwatch.elapsed);
return GenerationResult(
taskId: task.id,
content: '// Error generating ${task.type}: $e',
generationTime: stopwatch.elapsed,
metadata: {'error': e.toString()},
);
}
}
///
Future<String> _generateTaskContent(GenerationTask task) async {
switch (task.type) {
case 'header':
return _generateHeader(task.data);
case 'imports':
return _generateImports(task.data);
case 'model':
return _generateModel(task.data);
case 'api':
return _generateApi(task.data);
default:
throw UnsupportedError('Unknown task type: ${task.type}');
}
}
///
String _generateHeader(Map<String, dynamic> data) {
final buffer = StringBuffer();
buffer.writeln('/// Generated API for ${data['title']}');
buffer.writeln('/// Version: ${data['version']}');
buffer.writeln('/// ${data['description']}');
buffer.writeln('/// Generated at: ${DateTime.now().toIso8601String()}');
buffer.writeln();
return buffer.toString();
}
///
String _generateImports(Map<String, dynamic> data) {
final buffer = StringBuffer();
buffer.writeln('import \'dart:convert\';');
buffer.writeln('import \'package:dio/dio.dart\';');
buffer.writeln('import \'package:retrofit/retrofit.dart\';');
buffer.writeln('import \'package:json_annotation/json_annotation.dart\';');
buffer.writeln();
buffer.writeln('part \'generated_api.g.dart\';');
buffer.writeln();
return buffer.toString();
}
///
String _generateModel(Map<String, dynamic> data) {
final name = data['name'] as String;
final model = data['model'] as ApiModel;
final buffer = StringBuffer();
buffer.writeln('@JsonSerializable()');
buffer.writeln('class $name {');
//
model.properties.forEach((propName, property) {
buffer.writeln(' final ${_getDartType(property.type)} $propName;');
});
buffer.writeln();
buffer.writeln(' const $name({');
model.properties.forEach((propName, property) {
final required = property.required ? 'required ' : '';
buffer.writeln(' ${required}this.$propName,');
});
buffer.writeln(' });');
buffer.writeln();
buffer.writeln(' factory $name.fromJson(Map<String, dynamic> json) =>');
buffer.writeln(' _\$${name}FromJson(json);');
buffer.writeln();
buffer
.writeln(' Map<String, dynamic> toJson() => _\$${name}ToJson(this);');
buffer.writeln('}');
buffer.writeln();
return buffer.toString();
}
/// API
String _generateApi(Map<String, dynamic> data) {
final module = data['module'] as String;
final paths = data['paths'] as Map<String, ApiPath>;
final buffer = StringBuffer();
buffer.writeln('@RestApi()');
buffer.writeln('abstract class ${module}Api {');
buffer.writeln(
' factory ${module}Api(Dio dio, {String? baseUrl}) = _${module}Api;');
buffer.writeln();
paths.forEach((path, apiPath) {
buffer.writeln(' @${apiPath.method.value.toUpperCase()}(\'$path\')');
buffer.writeln(
' Future<dynamic> ${_generateMethodName(path, apiPath.method)}();');
buffer.writeln();
});
buffer.writeln('}');
buffer.writeln();
return buffer.toString();
}
///
String _mergeResults(List<GenerationResult> results) {
final buffer = StringBuffer();
//
final sortedResults = List<GenerationResult>.from(results);
sortedResults.sort((a, b) {
final order = ['header', 'imports', 'model', 'api'];
final aType = a.taskId.split('_')[0];
final bType = b.taskId.split('_')[0];
final aIndex = order.indexOf(aType);
final bIndex = order.indexOf(bType);
return aIndex.compareTo(bIndex);
});
for (final result in sortedResults) {
buffer.write(result.content);
}
return buffer.toString();
}
///
List<List<GenerationTask>> _chunkTasks(
List<GenerationTask> tasks, int chunkSize) {
final chunks = <List<GenerationTask>>[];
for (int i = 0; i < tasks.length; i += chunkSize) {
final end = (i + chunkSize).clamp(0, tasks.length);
chunks.add(tasks.sublist(i, end));
}
return chunks;
}
///
Map<String, Map<String, ApiPath>> _groupPathsByModule(
Map<String, ApiPath> paths) {
final groups = <String, Map<String, ApiPath>>{};
paths.forEach((path, apiPath) {
final module = _extractModuleName(path);
groups.putIfAbsent(module, () => {});
groups[module]![path] = apiPath;
});
return groups;
}
///
String _extractModuleName(String path) {
final parts = path.split('/').where((part) => part.isNotEmpty).toList();
if (parts.length >= 3) {
return _toPascalCase(parts[2]);
}
return 'Common';
}
///
String _generateCacheKey(GenerationTask task) {
final dataHash = task.data.toString().hashCode;
return '${task.type}_${dataHash}';
}
///
String _calculateDocumentHash(SwaggerDocument document) {
final content =
'${document.title}_${document.version}_${document.paths.length}_${document.models.length}';
return content.hashCode.toString();
}
///
List<String> _detectChangedSections(SwaggerDocument document) {
//
return ['paths', 'models', 'components'];
}
///
void _updateGenerationHistory(SwaggerDocument document, String result) {
_previousGeneration['hash'] = _calculateDocumentHash(document);
_previousGeneration['result'] = result;
_previousGeneration['timestamp'] = DateTime.now().toIso8601String();
}
///
GenerationStats getStats() {
final totalTime = _taskTimes.isNotEmpty
? _taskTimes.reduce((a, b) => a + b)
: Duration.zero;
final averageTime = _taskTimes.isNotEmpty
? Duration(
microseconds: _taskTimes
.map((d) => d.inMicroseconds)
.reduce((a, b) => a + b) ~/
_taskTimes.length)
: Duration.zero;
//
int linesGenerated = 0;
int bytesGenerated = 0;
for (final result in _results) {
linesGenerated += result.content.split('\n').length;
bytesGenerated += result.content.length;
}
//
final parallelEfficiency = _enableParallel && _totalTasks > 1 ? 0.8 : 1.0;
return GenerationStats(
totalTasks: _totalTasks,
completedTasks: _completedTasks,
failedTasks: _failedTasks,
totalTime: totalTime,
averageTaskTime: averageTime,
linesGenerated: linesGenerated,
bytesGenerated: bytesGenerated,
parallelEfficiency: parallelEfficiency,
);
}
///
CacheStats getCacheStats() => _cache.getStats();
///
void clearCache() => _cache.clear();
/// Dart
String _getDartType(PropertyType type) {
switch (type) {
case PropertyType.string:
return 'String';
case PropertyType.integer:
return 'int';
case PropertyType.number:
return 'double';
case PropertyType.boolean:
return 'bool';
case PropertyType.array:
return 'List<dynamic>';
case PropertyType.object:
return 'Map<String, dynamic>';
default:
return 'dynamic';
}
}
///
String _generateMethodName(String path, HttpMethod method) {
final pathParts = path
.split('/')
.where((part) => part.isNotEmpty && !part.startsWith('{'))
.toList();
final methodPrefix = method.value.toLowerCase();
if (pathParts.length >= 3) {
pathParts.removeRange(0, 2);
}
final nameParts = pathParts.map((part) => _toPascalCase(part)).join('');
return '$methodPrefix$nameParts';
}
/// PascalCase
String _toPascalCase(String input) {
return input
.split('_')
.map((word) => word.isEmpty
? ''
: word[0].toUpperCase() + word.substring(1).toLowerCase())
.join('');
}
}

View File

@ -1,3 +1,4 @@
import '../core/config_loader.dart';
import '../core/models.dart';
import '../utils/string_utils.dart';
import 'base_generator.dart';
@ -163,26 +164,13 @@ class RetrofitApiGenerator extends BaseGenerator {
//
// dart:
buffer.writeln('import \'dart:convert\';');
buffer.writeln('import \'dart:io\';');
buffer.writeln('import \'dart:typed_data\';');
buffer.writeln('');
// package:
if (useRetrofit) {
buffer.writeln('import \'package:retrofit/retrofit.dart\';');
buffer
.writeln('import \'package:json_annotation/json_annotation.dart\';');
} else if (useDio) {
buffer.writeln('import \'package:dio/dio.dart\';');
//
final packageImports = ConfigLoader.getPackageImports();
for (final import in packageImports) {
buffer.writeln('import \'$import\';');
}
//
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('');
// api_models/index.dart base_result base_page_result
buffer.writeln('import \'../../api_models/index.dart\';');
buffer.writeln('');
@ -275,7 +263,7 @@ class RetrofitApiGenerator extends BaseGenerator {
final commentParts = <String>[];
if (param.description.isNotEmpty) {
commentParts.add(param.description);
commentParts.add(StringUtils.cleanDescription(param.description));
}
if (param.defaultValue != null) {
@ -1365,14 +1353,43 @@ class RetrofitApiGenerator extends BaseGenerator {
// 3. package:project_name
// 4.
//
print(
'DEBUG: _generateTagImports called. useRetrofit: $useRetrofit, useDio: $useDio');
// dart:
// dart:
buffer.writeln('import \'dart:convert\';');
buffer.writeln('import \'dart:io\';');
buffer.writeln('import \'dart:typed_data\';');
buffer.writeln('');
// package:
if (useRetrofit) {
buffer.writeln('import \'package:retrofit/retrofit.dart\';');
buffer
.writeln('import \'package:json_annotation/json_annotation.dart\';');
// Retrofit Dio
buffer.writeln('import \'package:dio/dio.dart\';');
} else if (useDio) {
buffer.writeln('import \'package:dio/dio.dart\';');
}
//
final packageImports = ConfigLoader.getPackageImports();
for (final import in packageImports) {
//
if (!import.contains('dio/dio.dart') &&
!import.contains('retrofit/retrofit.dart') &&
!import.contains('json_annotation/json_annotation.dart')) {
buffer.writeln('import \'$import\';');
}
}
//
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('');
// api_models/index.dart base_result base_page_result

View File

@ -7,9 +7,6 @@ export 'core/error_reporter.dart';
//
export 'core/models.dart';
export 'core/performance_parser.dart';
export 'core/smart_cache.dart';
export 'generators/optimized_retrofit_generator.dart';
export 'generators/performance_generator.dart';
//
export 'generators/retrofit_api_generator.dart';
//

View File

@ -11,7 +11,7 @@ class FileUtils {
if (path.isAbsolute(filePath)) {
return filePath;
}
//
//
final configFile = _findConfigFile();
@ -19,23 +19,24 @@ class FileUtils {
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'));
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;
@ -43,9 +44,10 @@ class FileUtils {
currentDir = parent;
depth++;
}
return null;
}
///
static Future<Directory> ensureDirectoryExists(String dirPath) async {
final resolvedPath = resolvePath(dirPath);

View File

@ -191,12 +191,6 @@ class StringUtils {
final controller = parts[0];
final action = parts[1];
//
if (controller.toLowerCase() == 'login' &&
action.toLowerCase() == 'userlogin') {
return 'login';
}
// camelCase
return toCamelCase('${controller}_$action');
}

View File

@ -1,7 +1,7 @@
name: swagger_generator_flutter
description: A powerful Swagger/OpenAPI code generator for Flutter projects with Dio + Retrofit support
version: 2.1.1
version: 3.0.0
environment:
sdk: '>=3.0.0 <4.0.0'

View File

@ -1,67 +0,0 @@
@echo off
REM 简化版 Swagger CLI 运行脚本
REM 提供便捷的命令行界面
REM 颜色定义 (Windows不支持ANSI颜色使用echo)
REM GREEN='\033[0;32m'
REM YELLOW='\033[1;33m'
REM CYAN='\033[0;36m'
REM NC='\033[0m' # No Color
REM 脚本路径
set SCRIPT_DIR=%~dp0
set CLI_DART_FILE=%SCRIPT_DIR%bin\main.dart
REM 显示帮助
:show_help
if "%1"=="help" goto help_content
if "%1"=="--help" goto help_content
if "%1"=="" goto help_content
goto :eof
:help_content
echo.
echo 🚀 Swagger CLI 工具
echo.
echo 用法: %0 [命令] [选项]
echo.
echo 快速命令:
echo %0 all # 生成所有文件
echo %0 models # 生成数据模型
echo %0 docs # 生成API文档
echo %0 api # 生成Retrofit API
echo.
echo 直接使用:
echo dart run bin\main.dart generate --help
echo.
goto :eof
REM 主函数
:main
if "%1"=="" goto show_help
if "%1"=="help" goto show_help
if "%1"=="--help" goto show_help
if "%1"=="all" (
dart run "%CLI_DART_FILE%" generate --models --api --split-by-tags
goto :eof
)
if "%1"=="models" (
dart run "%CLI_DART_FILE%" generate --models
goto :eof
)
if "%1"=="docs" (
dart run "%CLI_DART_FILE%" generate --docs
goto :eof
)
if "%1"=="api" (
dart run "%CLI_DART_FILE%" generate --api
goto :eof
)
echo 未知命令: %1
goto show_help

View File

@ -1,130 +0,0 @@
#!/bin/bash
# 简化版 Swagger CLI 运行脚本
# 提供便捷的命令行界面
# 颜色定义
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
# 脚本路径
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CLI_DART_FILE="$SCRIPT_DIR/bin/main.dart"
# 显示帮助
show_help() {
echo -e "${CYAN}🚀 Swagger CLI 工具${NC}"
echo ""
echo -e "${YELLOW}用法: $0 [命令] [选项]${NC}"
echo ""
echo -e "${GREEN}快速命令:${NC}"
echo -e " $0 all # 生成所有文件(推荐)"
echo -e " $0 models # 生成数据模型"
echo -e " $0 docs # 生成API文档"
echo -e " $0 api # 生成Retrofit API"
echo ""
echo -e "${GREEN}工具命令:${NC}"
echo -e " $0 clean # 清理生成的文件"
echo -e " $0 validate # 验证生成的代码"
echo -e " $0 format # 格式化代码"
echo ""
echo -e "${GREEN}直接使用:${NC}"
echo -e " dart run bin/main.dart generate --help"
echo ""
}
# 检查必要的工具
check_prerequisites() {
if ! command -v dart &> /dev/null; then
echo -e "${YELLOW}❌ Dart SDK 未安装或不在 PATH 中${NC}"
exit 1
fi
if [ ! -f "$CLI_DART_FILE" ]; then
echo -e "${YELLOW}❌ CLI 文件不存在: $CLI_DART_FILE${NC}"
exit 1
fi
}
# 执行生成并格式化
generate_and_format() {
local cmd="$1"
echo -e "${CYAN}🚀 执行: $cmd${NC}"
if eval "$cmd"; then
echo -e "${CYAN}🔧 修复和排序 imports...${NC}"
dart fix --apply
echo -e "${CYAN}🎨 格式化代码...${NC}"
dart format .
echo -e "${GREEN}✅ 生成完成!${NC}"
else
echo -e "${YELLOW}❌ 生成失败${NC}"
exit 1
fi
}
# 主函数
main() {
if [ $# -eq 0 ] || [ "$1" = "help" ] || [ "$1" = "--help" ]; then
show_help
exit 0
fi
# 检查必要工具(除了 clean 命令)
if [ "$1" != "clean" ]; then
check_prerequisites
fi
case "$1" in
all)
dart run "$CLI_DART_FILE" generate --models --api
dart fix --apply # 先修复和排序 imports
dart format . # 再格式化代码
;;
models)
dart run "$CLI_DART_FILE" generate --models
dart fix --apply # 先修复和排序 imports
dart format . # 再格式化代码
;;
docs)
dart run "$CLI_DART_FILE" generate --docs
dart fix --apply # 先修复和排序 imports
dart format . # 再格式化代码
;;
api)
dart run "$CLI_DART_FILE" generate --api
dart fix --apply # 先修复和排序 imports
dart format . # 再格式化代码
;;
clean)
echo -e "${CYAN}🧹 清理生成的文件...${NC}"
rm -rf generator/
echo -e "${GREEN}✅ 清理完成${NC}"
;;
validate)
echo -e "${CYAN}🔍 验证生成的代码...${NC}"
if [ -f "validate.sh" ]; then
./validate.sh
else
echo -e "${YELLOW}⚠️ 验证脚本不存在,请先运行: chmod +x validate.sh${NC}"
fi
;;
format)
echo -e "${CYAN}🎨 格式化代码...${NC}"
dart fix --apply # 先修复和排序 imports
dart format . # 再格式化代码
echo -e "${GREEN}✅ 格式化完成${NC}"
;;
*)
echo -e "${YELLOW}未知命令: $1${NC}"
show_help
exit 1
;;
esac
}
main "$@"

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,4 @@
import 'package:swagger_generator_flutter/core/models.dart';
import 'package:swagger_generator_flutter/generators/optimized_retrofit_generator.dart';
import 'package:swagger_generator_flutter/generators/performance_generator.dart';
import 'package:swagger_generator_flutter/generators/retrofit_api_generator.dart';
import 'package:test/test.dart';
@ -363,109 +361,6 @@ void main() {
});
});
group('OptimizedRetrofitGenerator', () {
test('generates optimized code with base types', () {
final generator = OptimizedRetrofitGenerator(
className: 'OptimizedApiService',
generateModularApis: true,
generateBaseResult: true,
generatePagination: true,
generateFileUpload: true,
);
final result = generator.generateFromDocument(testDocument);
expect(result, isNotEmpty);
expect(result, contains('class BaseResult<T>'));
expect(result, contains('class BasePageResult<T>'));
expect(result, contains('class FileUploadRequest'));
expect(result, contains('class FileUploadResult'));
expect(result, contains('class ApiUtils'));
});
test('generates modular APIs', () {
final generator = OptimizedRetrofitGenerator(
generateModularApis: true,
);
final result = generator.generateFromDocument(testDocument);
expect(result, contains('class UsersApi'));
expect(result, contains('class FilesApi'));
expect(result, contains('class ApiService'));
});
test('generates single API when modular is disabled', () {
final generator = OptimizedRetrofitGenerator(
generateModularApis: false,
className: 'SingleApiService',
);
final result = generator.generateFromDocument(testDocument);
expect(result, contains('abstract class SingleApiService'));
expect(result, isNot(contains('class UsersApi')));
expect(result, isNot(contains('class FilesApi')));
});
test('includes utility methods', () {
final generator = OptimizedRetrofitGenerator(
generateFileUpload: true,
generatePagination: true,
);
final result = generator.generateFromDocument(testDocument);
expect(result, contains('class ApiUtils'));
expect(result, contains('createFileUpload'));
expect(result, contains('createPageParam'));
});
});
group('PerformanceGenerator', () {
test('generates code with performance tracking', () async {
final generator = PerformanceGenerator(
maxConcurrency: 2,
enableCaching: true,
enableParallel: true,
);
final result = await generator.generateFromDocument(testDocument);
expect(result, isNotEmpty);
expect(result, contains('Generated API for Test API'));
expect(result, contains('class User'));
expect(result, contains('CommonApi'));
final stats = generator.getStats();
expect(stats.totalTasks, greaterThan(0));
expect(stats.completedTasks, greaterThan(0));
});
test('caching improves performance', () async {
final generator = PerformanceGenerator(
enableCaching: true,
);
// First generation
final stopwatch1 = Stopwatch()..start();
await generator.generateFromDocument(testDocument);
stopwatch1.stop();
// Second generation (should use cache)
final stopwatch2 = Stopwatch()..start();
await generator.generateFromDocument(testDocument);
stopwatch2.stop();
// Second should be faster due to caching
expect(stopwatch2.elapsedMicroseconds,
lessThan(stopwatch1.elapsedMicroseconds));
final cacheStats = generator.getCacheStats();
expect(cacheStats.hits, greaterThan(0));
});
});
group('Code Quality', () {
test('generated code is valid Dart syntax', () {
final generator = RetrofitApiGenerator();
@ -526,19 +421,6 @@ void main() {
expect(result, contains('class'));
});
test('generates proper JSON annotations', () {
final generator = OptimizedRetrofitGenerator(
generateBaseResult: true,
);
final result = generator.generateFromDocument(testDocument);
expect(result, contains('@JsonSerializable()'));
expect(result, contains('fromJson'));
expect(result, contains('toJson'));
expect(result, contains('_\$'));
});
test('handles nullable and required fields correctly', () {
final generator = RetrofitApiGenerator();
final result = generator.generateFromDocument(testDocument);

View File

@ -3,7 +3,6 @@ import 'dart:io';
import 'package:swagger_generator_flutter/core/error_reporter.dart';
import 'package:swagger_generator_flutter/core/performance_parser.dart';
import 'package:swagger_generator_flutter/generators/optimized_retrofit_generator.dart';
import 'package:swagger_generator_flutter/generators/retrofit_api_generator.dart';
import 'package:swagger_generator_flutter/validators/enhanced_validator.dart';
import 'package:test/test.dart';
@ -312,27 +311,7 @@ void main() {
expect(retrofitCode, contains('@Query(\'page\')'));
expect(retrofitCode, contains('@MultiPart()'));
// 5. API
final optimizedGenerator = OptimizedRetrofitGenerator(
className: 'OptimizedIntegrationApi',
generateModularApis: true,
generateBaseResult: true,
generatePagination: true,
generateFileUpload: true,
);
final optimizedCode = optimizedGenerator.generateFromDocument(document);
//
expect(optimizedCode, isNotEmpty);
expect(optimizedCode, contains('class BaseResult'));
expect(optimizedCode, contains('class BasePageResult'));
expect(optimizedCode, contains('class FileUploadRequest'));
expect(optimizedCode, contains('class ApiUtils'));
expect(optimizedCode, contains('UsersApi'));
expect(optimizedCode, contains('FilesApi'));
// 6.
// 5.
print('Integration Test Performance Summary:');
print(' Parse Time: ${parseStats.totalTime.inMilliseconds}ms');
print(
@ -341,15 +320,11 @@ void main() {
print(' Schemas Parsed: ${parseStats.schemaCount}');
print(
' Retrofit Code Size: ${(retrofitCode.length / 1024).toStringAsFixed(2)}KB');
print(
' Optimized Code Size: ${(optimizedCode.length / 1024).toStringAsFixed(2)}KB');
//
expect(
parseStats.totalTime.inMilliseconds, lessThan(2000)); // 2
expect(retrofitCode.length, greaterThan(1000)); //
expect(optimizedCode.length,
greaterThan(retrofitCode.length)); //
});
test('handles real project swagger.json', () async {
@ -400,13 +375,10 @@ void main() {
print(' Errors: ${errors.length}');
print(' Critical: ${criticalErrors.length}');
//
final generator = OptimizedRetrofitGenerator(
// 使 RetrofitApiGenerator
final generator = RetrofitApiGenerator(
className: 'OAMobileApiService',
generateModularApis: true,
generateBaseResult: true,
generatePagination: true,
generateFileUpload: true,
splitByTags: true,
);
final genStopwatch = Stopwatch()..start();
@ -425,7 +397,7 @@ void main() {
//
expect(parseStopwatch.elapsedMilliseconds, lessThan(15000)); // 15
expect(genStopwatch.elapsedMilliseconds, lessThan(10000)); // 10
expect(generatedCode.length, greaterThan(5000)); // 5KB代码
expect(generatedCode.length, greaterThan(1000)); // 1KB代码
});
});
@ -564,9 +536,9 @@ void main() {
expect(document.models.length, greaterThan(100));
expect(parseStopwatch.elapsedMilliseconds, lessThan(10000)); // 10
//
final generator = OptimizedRetrofitGenerator(
generateModularApis: true,
// 使 RetrofitApiGenerator
final generator = RetrofitApiGenerator(
splitByTags: true,
);
final genStopwatch = Stopwatch()..start();

View File

@ -1,392 +0,0 @@
import 'dart:convert';
import 'dart:io';
import 'package:swagger_generator_flutter/core/models.dart';
import 'package:swagger_generator_flutter/generators/optimized_retrofit_generator.dart';
import 'package:test/test.dart';
void main() {
group('OptimizedRetrofitGenerator', () {
late OptimizedRetrofitGenerator generator;
late SwaggerDocument testDocument;
setUp(() {
generator = OptimizedRetrofitGenerator(
className: 'TestApiService',
generateModularApis: true,
generateBaseResult: true,
generatePagination: true,
generateFileUpload: true,
);
//
testDocument = const SwaggerDocument(
title: 'Test API',
version: '1.0.0',
description: 'Test API for generator',
servers: [
ApiServer(
url: 'https://api.example.com',
description: 'Test server',
),
],
components: ApiComponents(
schemas: {},
securitySchemes: {},
),
paths: {
'/api/v1/users/{id}': const ApiPath(
path: '/api/v1/users/{id}',
method: HttpMethod.get,
summary: 'Get user by ID',
description: 'Retrieve user information by ID',
operationId: 'getUser',
tags: ['users'],
parameters: [
ApiParameter(
name: 'id',
location: ParameterLocation.path,
required: true,
type: PropertyType.integer,
description: 'User ID',
),
],
responses: {
'200': const ApiResponse(
code: '200',
description: 'Success',
content: {
'application/json': const ApiMediaType(
schema: {'type': 'object'},
),
},
),
},
),
'/api/v1/users': const ApiPath(
path: '/api/v1/users',
method: HttpMethod.post,
summary: 'Create user',
description: 'Create a new user',
operationId: 'createUser',
tags: ['users'],
parameters: [],
requestBody: ApiRequestBody(
description: 'User data',
required: true,
content: {
'application/json': const ApiMediaType(
schema: {'type': 'object'},
),
},
),
responses: {
'201': const ApiResponse(
code: '201',
description: 'Created',
content: {
'application/json': const ApiMediaType(
schema: {'type': 'object'},
),
},
),
},
),
'/api/v1/files/upload': const ApiPath(
path: '/api/v1/files/upload',
method: HttpMethod.post,
summary: 'Upload file',
description: 'Upload a file',
operationId: 'uploadFile',
tags: ['files'],
parameters: [],
requestBody: ApiRequestBody(
description: 'File to upload',
required: true,
content: {
'multipart/form-data': const ApiMediaType(
schema: {'type': 'object'},
),
},
),
responses: {
'200': const ApiResponse(
code: '200',
description: 'Success',
content: {
'application/json': const ApiMediaType(
schema: {'type': 'object'},
),
},
),
},
),
},
models: {},
controllers: {},
security: [],
);
});
test('generates optimized code successfully', () {
expect(
() => generator.generateFromDocument(testDocument), returnsNormally);
final generatedCode = generator.generateFromDocument(testDocument);
expect(generatedCode, isNotEmpty);
print('Generated code length: ${generatedCode.length} characters');
});
test('includes required imports', () {
final generatedCode = generator.generateFromDocument(testDocument);
expect(generatedCode, contains('import \'package:dio/dio.dart\';'));
expect(generatedCode,
contains('import \'package:retrofit/retrofit.dart\';'));
expect(generatedCode,
contains('import \'package:json_annotation/json_annotation.dart\';'));
expect(generatedCode, contains('part \'test_api_service_api.g.dart\';'));
});
test('generates BaseResult type', () {
final generatedCode = generator.generateFromDocument(testDocument);
expect(generatedCode, contains('class BaseResult<T>'));
expect(generatedCode, contains('final int code;'));
expect(generatedCode, contains('final String message;'));
expect(generatedCode, contains('final T? data;'));
expect(generatedCode, contains('bool get isSuccess => code == 200;'));
});
test('generates pagination types', () {
final generatedCode = generator.generateFromDocument(testDocument);
expect(generatedCode, contains('class BasePageParameter'));
expect(generatedCode, contains('class BasePageResult<T>'));
expect(generatedCode, contains('final int page;'));
expect(generatedCode, contains('final int size;'));
expect(generatedCode, contains('final int total;'));
expect(generatedCode, contains('int get totalPages'));
expect(generatedCode, contains('bool get hasNext'));
expect(generatedCode, contains('bool get hasPrevious'));
});
test('generates file upload types', () {
final generatedCode = generator.generateFromDocument(testDocument);
expect(generatedCode, contains('class FileUploadRequest'));
expect(generatedCode, contains('class FileUploadResult'));
expect(generatedCode, contains('final MultipartFile file;'));
expect(generatedCode, contains('final String url;'));
expect(generatedCode, contains('final String filename;'));
expect(generatedCode, contains('final int size;'));
});
test('generates modular APIs', () {
final generatedCode = generator.generateFromDocument(testDocument);
expect(generatedCode, contains('class UsersApi'));
expect(generatedCode, contains('class FilesApi'));
expect(generatedCode, contains('class TestApiService'));
expect(generatedCode, contains('late final UsersApi users;'));
expect(generatedCode, contains('late final FilesApi files;'));
});
test('generates API methods with correct annotations', () {
final generatedCode = generator.generateFromDocument(testDocument);
// GET
expect(generatedCode, contains('@GET(\'/api/v1/users/{id}\')'));
expect(generatedCode, contains('@Path(\'id\') int id'));
// POST
expect(generatedCode, contains('@POST(\'/api/v1/users\')'));
expect(generatedCode, contains('@Body() Map<String, dynamic> body'));
//
expect(generatedCode, contains('@POST(\'/api/v1/files/upload\')'));
expect(generatedCode, contains('@MultiPart()'));
expect(generatedCode, contains('@Part() MultipartFile file'));
});
test('generates utility classes', () {
final generatedCode = generator.generateFromDocument(testDocument);
expect(generatedCode, contains('class ApiUtils'));
expect(generatedCode,
contains('static Future<MultipartFile> createFileUpload'));
expect(
generatedCode, contains('static BasePageParameter createPageParam'));
});
test('handles method name generation correctly', () {
final generatedCode = generator.generateFromDocument(testDocument);
expect(generatedCode, contains('Future<BaseResult<dynamic>> getUsers('));
expect(generatedCode, contains('Future<BaseResult<dynamic>> postUsers('));
expect(generatedCode,
contains('Future<BaseResult<dynamic>> postFilesUpload('));
});
test('generates single API when modular is disabled', () {
final singleApiGenerator = OptimizedRetrofitGenerator(
className: 'SingleApiService',
generateModularApis: false,
);
final generatedCode =
singleApiGenerator.generateFromDocument(testDocument);
expect(generatedCode, contains('abstract class SingleApiService'));
expect(generatedCode, contains('factory SingleApiService(Dio dio'));
expect(generatedCode, isNot(contains('class UsersApi')));
expect(generatedCode, isNot(contains('class FilesApi')));
});
test('handles custom base result type', () {
final customGenerator = OptimizedRetrofitGenerator(
baseResultType: 'CustomResult',
pageResultType: 'CustomPageResult',
);
final generatedCode = customGenerator.generateFromDocument(testDocument);
expect(generatedCode, contains('class CustomResult<T>'));
expect(generatedCode, contains('class CustomPageResult<T>'));
expect(generatedCode, contains('Future<CustomResult<dynamic>>'));
});
test('extracts module names correctly', () {
final generator = OptimizedRetrofitGenerator();
// 使
//
final generatedCode = generator.generateFromDocument(testDocument);
expect(generatedCode, contains('UsersApi')); // /api/v1/users -> Users
expect(generatedCode, contains('FilesApi')); // /api/v1/files -> Files
});
test('handles different parameter types', () {
//
const complexDocument = SwaggerDocument(
title: 'Complex API',
version: '1.0.0',
description: 'API with complex parameters',
servers: [ApiServer(url: 'https://api.example.com')],
components: ApiComponents(schemas: {}, securitySchemes: {}),
paths: {
'/api/v1/search': const ApiPath(
path: '/api/v1/search',
method: HttpMethod.get,
summary: 'Search',
description: 'Search with various parameters',
operationId: 'search',
tags: ['search'],
parameters: [
ApiParameter(
name: 'query',
location: ParameterLocation.query,
required: true,
type: PropertyType.string,
description: 'Search query',
),
ApiParameter(
name: 'page',
location: ParameterLocation.query,
required: false,
type: PropertyType.integer,
description: 'Page number',
),
ApiParameter(
name: 'active',
location: ParameterLocation.query,
required: false,
type: PropertyType.boolean,
description: 'Filter active items',
),
],
responses: {
'200': const ApiResponse(
code: '200',
description: 'Success',
content: {
'application/json':
const ApiMediaType(schema: {'type': 'object'}),
},
),
},
),
},
models: {},
controllers: {},
security: [],
);
final generatedCode = generator.generateFromDocument(complexDocument);
expect(
generatedCode, contains('@Query(\'query\') required String query'));
expect(generatedCode, contains('@Query(\'page\') int? page'));
expect(generatedCode, contains('@Query(\'active\') bool? active'));
});
test('performance test with complex document', () {
final stopwatch = Stopwatch()..start();
final generatedCode = generator.generateFromDocument(testDocument);
final elapsedMs = stopwatch.elapsedMilliseconds;
stopwatch.stop();
print('Generation time: ${elapsedMs}ms');
print('Generated code size: ${generatedCode.length} characters');
expect(elapsedMs, lessThan(1000)); // 1
expect(generatedCode.length, greaterThan(1000)); //
});
});
group('Real Project Integration', () {
test('generates code for real swagger.json', () async {
// swagger.json
final file = File('swagger.json');
if (!file.existsSync()) {
print('swagger.json not found, skipping real project test');
return;
}
final jsonString = await file.readAsString();
final swaggerJson = jsonDecode(jsonString) as Map<String, dynamic>;
final document = SwaggerDocument.fromJson(swaggerJson);
final generator = OptimizedRetrofitGenerator(
className: 'OAMobileApiService',
generateModularApis: true,
generateBaseResult: true,
generatePagination: true,
generateFileUpload: true,
);
final stopwatch = Stopwatch()..start();
final generatedCode = generator.generateFromDocument(document);
final elapsedMs = stopwatch.elapsedMilliseconds;
print('Real project generation:');
print(' Time: ${elapsedMs}ms');
print(' Code size: ${generatedCode.length} characters');
print(' Lines: ${generatedCode.split('\n').length}');
expect(generatedCode, isNotEmpty);
expect(generatedCode, contains('class OAMobileApiService'));
expect(generatedCode, contains('FollowManagerApi'));
expect(generatedCode, contains('LoginApi'));
expect(generatedCode, contains('IndexApi'));
//
// final outputFile = File('generated_oa_mobile_api.dart');
// await outputFile.writeAsString(generatedCode);
// print('Generated code written to: ${outputFile.path}');
});
});
}

View File

@ -1,488 +0,0 @@
import 'dart:convert';
import 'dart:io';
import 'package:swagger_generator_flutter/core/models.dart';
import 'package:swagger_generator_flutter/core/performance_parser.dart';
import 'package:swagger_generator_flutter/core/smart_cache.dart';
import 'package:swagger_generator_flutter/generators/performance_generator.dart';
import 'package:test/test.dart';
void main() {
group('Performance Tests', () {
group('PerformanceParser', () {
late PerformanceParser parser;
setUp(() {
parser = PerformanceParser(
config: ParseConfig(
enableParallelParsing: true,
enableStreamParsing: false,
enablePerformanceStats: true,
maxConcurrency: 4,
),
);
});
test('parses document with performance tracking', () async {
final jsonString = jsonEncode({
'openapi': '3.0.3',
'info': {
'title': 'Test API',
'version': '1.0.0',
},
'paths': {
'/users': {
'get': {
'summary': 'Get users',
'responses': {
'200': {
'description': 'Success',
},
},
},
},
},
});
final document = await parser.parseDocument(jsonString);
expect(document.title, equals('Test API'));
expect(document.version, equals('1.0.0'));
expect(document.paths, hasLength(1));
final stats = parser.lastStats;
expect(stats, isNotNull);
expect(stats!.totalTime.inMicroseconds, greaterThan(0));
expect(stats.documentSize, equals(jsonString.length));
expect(stats.pathCount, equals(1));
});
test('handles large documents efficiently', () async {
//
final paths = <String, dynamic>{};
for (int i = 0; i < 100; i++) {
paths['/api/v1/resource$i'] = {
'get': {
'summary': 'Get resource $i',
'responses': {
'200': {'description': 'Success'},
},
},
'post': {
'summary': 'Create resource $i',
'responses': {
'201': {'description': 'Created'},
},
},
};
}
final largeDoc = {
'openapi': '3.0.3',
'info': {
'title': 'Large API',
'version': '1.0.0',
},
'paths': paths,
};
final jsonString = jsonEncode(largeDoc);
final stopwatch = Stopwatch()..start();
final document = await parser.parseDocument(jsonString);
stopwatch.stop();
expect(document.paths.length, greaterThan(50)); //
expect(stopwatch.elapsedMilliseconds, lessThan(5000)); // 5
final stats = parser.lastStats;
expect(stats, isNotNull);
expect(stats!.pathsPerSecond, greaterThan(10)); // 10
});
test('parallel parsing improves performance', () async {
final sequentialParser = PerformanceParser(
config: ParseConfig(
enableParallelParsing: false,
enablePerformanceStats: true,
),
);
final parallelParser = PerformanceParser(
config: ParseConfig(
enableParallelParsing: true,
enablePerformanceStats: true,
maxConcurrency: 4,
),
);
//
final paths = <String, dynamic>{};
for (int i = 0; i < 50; i++) {
paths['/api/v1/resource$i'] = {
'get': {
'summary': 'Get resource $i',
'responses': {
'200': {'description': 'Success'}
},
},
};
}
final doc = {
'openapi': '3.0.3',
'info': {'title': 'Test API', 'version': '1.0.0'},
'paths': paths,
};
final jsonString = jsonEncode(doc);
//
await sequentialParser.parseDocument(jsonString);
final sequentialTime = sequentialParser.lastStats!.totalTime;
//
await parallelParser.parseDocument(jsonString);
final parallelTime = parallelParser.lastStats!.totalTime;
//
print('Sequential: ${sequentialTime.inMilliseconds}ms');
print('Parallel: ${parallelTime.inMilliseconds}ms');
//
expect(parallelTime.inMilliseconds,
lessThan(sequentialTime.inMilliseconds * 2));
});
});
group('SmartCache', () {
late SmartCache<String> cache;
setUp(() {
cache = SmartCache<String>(
maxSize: 10,
strategy: CacheStrategy.smart,
defaultTtl: Duration(seconds: 1),
);
});
test('basic cache operations work correctly', () {
cache.put('key1', 'value1');
expect(cache.get('key1'), equals('value1'));
expect(cache.containsKey('key1'), isTrue);
cache.remove('key1');
expect(cache.get('key1'), isNull);
expect(cache.containsKey('key1'), isFalse);
});
test('cache eviction works when full', () {
//
for (int i = 0; i < 10; i++) {
cache.put('key$i', 'value$i');
}
expect(cache.getStats().size, equals(10));
//
cache.put('key10', 'value10');
expect(cache.getStats().size, equals(10));
expect(cache.getStats().evictions, greaterThan(0));
});
test('TTL expiration works', () async {
cache.put('key1', 'value1', ttl: Duration(milliseconds: 100));
expect(cache.get('key1'), equals('value1'));
await Future.delayed(Duration(milliseconds: 150));
expect(cache.get('key1'), isNull);
});
test('cache statistics are accurate', () {
cache.put('key1', 'value1');
cache.get('key1'); // hit
cache.get('key2'); // miss
final stats = cache.getStats();
expect(stats.totalRequests, equals(2));
expect(stats.hits, equals(1));
expect(stats.misses, equals(1));
expect(stats.hitRate, equals(0.5));
});
test('smart eviction strategy works', () {
// 访
cache.put('frequent', 'value1');
cache.put('recent', 'value2');
cache.put('old', 'value3');
// 访
for (int i = 0; i < 5; i++) {
cache.get('frequent');
}
// 访
cache.get('recent');
// 访
//
for (int i = 0; i < 8; i++) {
cache.put('filler$i', 'value$i');
}
// 访
expect(cache.get('frequent'), equals('value1'));
//
// expect(cache.get('old'), isNull);
});
});
group('PerformanceGenerator', () {
late PerformanceGenerator generator;
late SwaggerDocument testDocument;
setUp(() {
generator = PerformanceGenerator(
maxConcurrency: 4,
enableCaching: true,
enableIncremental: true,
enableParallel: true,
);
testDocument = SwaggerDocument(
title: 'Performance Test API',
version: '1.0.0',
description: 'API for performance testing',
servers: [ApiServer(url: 'https://api.example.com')],
components: ApiComponents(schemas: {}, securitySchemes: {}),
paths: {
'/users': ApiPath(
path: '/users',
method: HttpMethod.get,
summary: 'Get users',
description: 'Get all users',
operationId: 'getUsers',
tags: ['users'],
parameters: [],
responses: {
'200': ApiResponse(
code: '200',
description: 'Success',
),
},
),
'/posts': ApiPath(
path: '/posts',
method: HttpMethod.get,
summary: 'Get posts',
description: 'Get all posts',
operationId: 'getPosts',
tags: ['posts'],
parameters: [],
responses: {
'200': ApiResponse(
code: '200',
description: 'Success',
),
},
),
},
models: {
'User': ApiModel(
name: 'User',
description: 'User model',
properties: {
'id': ApiProperty(
name: 'id',
type: PropertyType.integer,
description: 'User ID',
required: true,
),
'name': ApiProperty(
name: 'name',
type: PropertyType.string,
description: 'User name',
required: true,
),
},
required: ['id', 'name'],
),
},
controllers: {},
security: [],
);
});
test('generates code with performance tracking', () async {
final result = await generator.generateFromDocument(testDocument);
expect(result, isNotEmpty);
expect(result, contains('Performance Test API'));
expect(result, contains('class User'));
expect(result, contains('UsersApi'));
expect(result, contains('PostsApi'));
final stats = generator.getStats();
expect(stats.totalTasks, greaterThan(0));
expect(stats.completedTasks, greaterThan(0));
expect(stats.successRate, equals(1.0));
});
test('caching improves performance on repeated generation', () async {
//
final stopwatch1 = Stopwatch()..start();
await generator.generateFromDocument(testDocument);
stopwatch1.stop();
final firstTime = stopwatch1.elapsedMicroseconds;
// 使
final stopwatch2 = Stopwatch()..start();
await generator.generateFromDocument(testDocument);
stopwatch2.stop();
final secondTime = stopwatch2.elapsedMicroseconds;
print('First generation: ${firstTime}μs');
print('Second generation: ${secondTime}μs');
//
expect(secondTime, lessThan(firstTime));
final cacheStats = generator.getCacheStats();
expect(cacheStats.hits, greaterThan(0));
});
test('parallel generation works with multiple modules', () async {
//
final largePaths = <String, ApiPath>{};
final modules = ['users', 'posts', 'comments', 'tags', 'categories'];
for (final module in modules) {
for (int i = 0; i < 5; i++) {
final path = '/api/v1/$module/$i';
largePaths[path] = ApiPath(
path: path,
method: HttpMethod.get,
summary: 'Get $module $i',
description: 'Get $module item $i',
operationId: 'get${module.toUpperCase()}$i',
tags: [module],
parameters: [],
responses: {
'200': ApiResponse(code: '200', description: 'Success'),
},
);
}
}
final largeDocument = SwaggerDocument(
title: 'Large API',
version: '1.0.0',
description: 'Large API for testing',
servers: [ApiServer(url: 'https://api.example.com')],
components: ApiComponents(schemas: {}, securitySchemes: {}),
paths: largePaths,
models: {},
controllers: {},
security: [],
);
final result = await generator.generateFromDocument(largeDocument);
expect(result, isNotEmpty);
expect(result, contains('Large API'));
// API
for (final module in modules) {
final className =
'${module[0].toUpperCase()}${module.substring(1)}Api';
expect(result, contains(className));
}
final stats = generator.getStats();
expect(stats.totalTasks, greaterThan(modules.length));
expect(stats.parallelEfficiency, greaterThan(0.5));
});
test('incremental generation detects no changes', () async {
//
await generator.generateFromDocument(testDocument);
final firstStats = generator.getStats();
//
await generator.generateFromDocument(testDocument);
final secondStats = generator.getStats();
//
expect(
secondStats.totalTasks, lessThanOrEqualTo(firstStats.totalTasks));
});
});
group('Real Project Performance', () {
test('handles real swagger.json efficiently', () async {
final file = File('swagger.json');
if (!file.existsSync()) {
print(
'swagger.json not found, skipping real project performance test');
return;
}
final jsonString = await file.readAsString();
//
final parser = PerformanceParser(
config: ParseConfig(
enableParallelParsing: true,
enablePerformanceStats: true,
maxConcurrency: 4,
),
);
final parseStopwatch = Stopwatch()..start();
final document = await parser.parseDocument(jsonString);
parseStopwatch.stop();
print('Parse Performance:');
print(' Time: ${parseStopwatch.elapsedMilliseconds}ms');
print(
' Document size: ${(jsonString.length / 1024).toStringAsFixed(2)}KB');
print(' Paths: ${document.paths.length}');
print(' Models: ${document.models.length}');
final parseStats = parser.lastStats!;
print(parseStats.toString());
expect(
parseStopwatch.elapsedMilliseconds, lessThan(10000)); // 10
expect(parseStats.pathsPerSecond, greaterThan(1)); // 1
//
final generator = PerformanceGenerator(
maxConcurrency: 4,
enableCaching: true,
enableParallel: true,
);
final genStopwatch = Stopwatch()..start();
final result = await generator.generateFromDocument(document);
genStopwatch.stop();
print('\nGeneration Performance:');
print(' Time: ${genStopwatch.elapsedMilliseconds}ms');
print(
' Generated size: ${(result.length / 1024).toStringAsFixed(2)}KB');
print(' Lines: ${result.split('\n').length}');
final genStats = generator.getStats();
print(genStats.toString());
expect(genStopwatch.elapsedMilliseconds, lessThan(15000)); // 15
expect(result.length, greaterThan(1000)); //
expect(genStats.successRate, greaterThan(0.8)); // 80%
});
});
});
}

View File

@ -1,5 +1,4 @@
import 'package:swagger_generator_flutter/core/models.dart';
import 'package:swagger_generator_flutter/generators/optimized_retrofit_generator.dart';
import 'package:swagger_generator_flutter/generators/retrofit_api_generator.dart';
import 'package:test/test.dart';
@ -113,7 +112,7 @@ void main() {
});
test('generates imports correctly', () {
final generator = RetrofitApiGenerator();
final generator = RetrofitApiGenerator(splitByTags: false);
final result = generator.generateFromDocument(simpleDocument);
@ -153,97 +152,6 @@ void main() {
});
});
group('OptimizedRetrofitGenerator Tests', () {
test('generates optimized code structure', () {
final generator = OptimizedRetrofitGenerator(
className: 'OptimizedApi',
generateModularApis: false,
generateBaseResult: true,
);
final result = generator.generateFromDocument(simpleDocument);
expect(result, isNotEmpty);
expect(result, contains('Simple Test API'));
expect(result, contains('BaseResult'));
});
test('generates base result types', () {
final generator = OptimizedRetrofitGenerator(
generateBaseResult: true,
);
final result = generator.generateFromDocument(simpleDocument);
expect(result, contains('class BaseResult'));
expect(result, contains('final int code'));
expect(result, contains('final String message'));
expect(result, contains('bool get isSuccess'));
});
test('generates pagination types', () {
final generator = OptimizedRetrofitGenerator(
generatePagination: true,
);
final result = generator.generateFromDocument(simpleDocument);
expect(result, contains('BasePageParameter'));
expect(result, contains('BasePageResult'));
expect(result, contains('final int page'));
expect(result, contains('final int size'));
});
test('generates file upload types', () {
final generator = OptimizedRetrofitGenerator(
generateFileUpload: true,
);
final result = generator.generateFromDocument(simpleDocument);
expect(result, contains('FileUploadRequest'));
expect(result, contains('FileUploadResult'));
expect(result, contains('MultipartFile'));
});
test('generates utility classes', () {
final generator = OptimizedRetrofitGenerator(
generateFileUpload: true,
generatePagination: true,
);
final result = generator.generateFromDocument(simpleDocument);
expect(result, contains('class ApiUtils'));
expect(result, contains('createFileUpload'));
expect(result, contains('createPageParam'));
});
test('generates modular APIs when enabled', () {
final generator = OptimizedRetrofitGenerator(
generateModularApis: true,
);
final result = generator.generateFromDocument(simpleDocument);
expect(result, contains('UsersApi'));
expect(result, contains('class ApiService'));
expect(result, contains('late final UsersApi'));
});
test('generates single API when modular disabled', () {
final generator = OptimizedRetrofitGenerator(
generateModularApis: false,
className: 'SingleApi',
);
final result = generator.generateFromDocument(simpleDocument);
expect(result, contains('abstract class SingleApi'));
expect(result, isNot(contains('UsersApi')));
});
});
group('Code Quality Tests', () {
test('generated code has proper structure', () {
final generator = RetrofitApiGenerator();
@ -313,18 +221,6 @@ void main() {
returnsNormally);
});
test('generates valid JSON annotations', () {
final generator = OptimizedRetrofitGenerator(
generateBaseResult: true,
);
final result = generator.generateFromDocument(simpleDocument);
expect(result, contains('@JsonSerializable'));
expect(result, contains('fromJson'));
expect(result, contains('toJson'));
});
test('handles nullable parameters correctly', () {
final documentWithOptionalParams = SwaggerDocument(
title: 'Test API',

View File

@ -1,4 +1,4 @@
import 'lib/utils/string_utils.dart';
import '../lib/utils/string_utils.dart';
void main() {
print('Testing function name generation:');

View File

@ -1,4 +1,4 @@
import 'lib/utils/string_utils.dart';
import '../lib/utils/string_utils.dart';
void main() {
print('Testing property name conversion:');

View File

@ -1,280 +0,0 @@
#!/bin/bash
# Augment 代码生成规范验证脚本
# 用于验证生成的代码是否符合规范要求
set -e
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 打印带颜色的消息
print_info() {
echo -e "${BLUE} $1${NC}"
}
print_success() {
echo -e "${GREEN}$1${NC}"
}
print_warning() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
print_error() {
echo -e "${RED}$1${NC}"
}
print_header() {
echo -e "${BLUE}🔍 Augment 代码生成规范验证${NC}"
echo "=================================================="
}
# 检查必要的工具
check_prerequisites() {
print_info "检查必要工具..."
if ! command -v dart &> /dev/null; then
print_error "Dart SDK 未安装或不在 PATH 中"
exit 1
fi
print_success "Dart SDK 已安装"
}
# 验证 swagger.json
validate_swagger() {
print_info "验证 swagger.json..."
if [ ! -f "swagger.json" ]; then
print_error "swagger.json 文件不存在"
return 1
fi
# 检查 JSON 格式
if ! jq empty swagger.json 2>/dev/null; then
if ! python3 -m json.tool swagger.json > /dev/null 2>&1; then
print_error "swagger.json 格式错误"
return 1
fi
fi
print_success "swagger.json 格式正确"
}
# 验证生成的文件结构
validate_file_structure() {
print_info "验证文件结构..."
if [ ! -d "generator" ]; then
print_error "generator 目录不存在"
return 1
fi
if [ ! -d "generator/api" ]; then
print_error "generator/api 目录不存在"
return 1
fi
if [ ! -d "generator/api_models" ]; then
print_error "generator/api_models 目录不存在"
return 1
fi
print_success "文件结构正确"
}
# 验证代码质量
validate_code_quality() {
print_info "验证代码质量..."
# 检查 generator 目录是否存在
if [ ! -d "generator" ]; then
print_warning "generator 目录不存在,跳过代码质量检查"
return 0
fi
# 运行 dart analyze
if dart analyze generator/ 2>/dev/null; then
print_success "静态分析通过"
else
print_warning "静态分析发现问题,请检查生成的代码"
fi
}
# 验证命名规范
validate_naming_conventions() {
print_info "验证命名规范..."
local errors=0
# 检查 API 文件命名
if [ -d "generator/api" ]; then
for file in generator/api/*.dart; do
if [ -f "$file" ]; then
filename=$(basename "$file")
if [[ ! "$filename" =~ ^[a-z][a-z0-9_]*_api\.dart$ ]] && [[ "$filename" != "api_client.dart" ]]; then
print_error "API 文件命名不规范: $filename"
errors=$((errors + 1))
fi
fi
done
fi
# 检查模型文件命名
if [ -d "generator/api_models" ]; then
for file in generator/api_models/*.dart; do
if [ -f "$file" ]; then
filename=$(basename "$file")
if [[ ! "$filename" =~ ^[a-z][a-z0-9_]*\.dart$ ]] && [[ "$filename" != "index.dart" ]]; then
print_error "模型文件命名不规范: $filename"
errors=$((errors + 1))
fi
fi
done
fi
if [ $errors -eq 0 ]; then
print_success "命名规范检查通过"
else
print_error "发现 $errors 个命名规范问题"
return 1
fi
}
# 验证类型一致性
validate_type_consistency() {
print_info "验证类型一致性..."
local generator_file="lib/generators/retrofit_api_generator.dart"
if [ ! -f "$generator_file" ]; then
print_warning "生成器文件不存在,跳过类型一致性检查"
return 0
fi
# 检查是否有硬编码的类型推断
local hardcoded_patterns=(
"TaskInfoResult"
"if.*contains.*login.*return"
"if.*contains.*task.*return"
"if.*tag.*contains.*return"
)
local errors=0
for pattern in "${hardcoded_patterns[@]}"; do
if grep -qiE "$pattern" "$generator_file"; then
print_error "发现硬编码类型推断: $pattern"
errors=$((errors + 1))
fi
done
if [ $errors -eq 0 ]; then
print_success "类型一致性检查通过"
else
print_error "发现 $errors 个类型一致性问题"
return 1
fi
}
# 运行完整验证
run_full_validation() {
print_header
local total_errors=0
check_prerequisites || total_errors=$((total_errors + 1))
validate_swagger || total_errors=$((total_errors + 1))
validate_file_structure || total_errors=$((total_errors + 1))
validate_code_quality || total_errors=$((total_errors + 1))
validate_naming_conventions || total_errors=$((total_errors + 1))
validate_type_consistency || total_errors=$((total_errors + 1))
echo ""
echo "=================================================="
if [ $total_errors -eq 0 ]; then
print_success "所有检查通过!代码符合 Augment 规范。"
exit 0
else
print_error "验证失败,发现 $total_errors 个问题。"
echo ""
print_info "请参考以下文档进行修复:"
echo " - 代码生成规范: AUGMENT_CODE_GENERATION_STANDARDS.md"
echo " - 快速参考指南: QUICK_REFERENCE.md"
echo " - 代码审查清单: CODE_REVIEW_CHECKLIST.md"
exit 1
fi
}
# 显示帮助信息
show_help() {
echo "Augment 代码生成规范验证脚本"
echo ""
echo "用法:"
echo " $0 [选项]"
echo ""
echo "选项:"
echo " all, -a, --all 运行所有验证检查(默认)"
echo " swagger, -s, --swagger 只验证 swagger.json"
echo " files, -f, --files 只验证文件结构"
echo " quality, -q, --quality 只验证代码质量"
echo " naming, -n, --naming 只验证命名规范"
echo " types, -t, --types 只验证类型一致性"
echo " help, -h, --help 显示此帮助信息"
echo ""
echo "示例:"
echo " $0 # 运行所有检查"
echo " $0 swagger # 只验证 swagger.json"
echo " $0 --quality # 只验证代码质量"
}
# 主函数
main() {
case "${1:-all}" in
all|-a|--all)
run_full_validation
;;
swagger|-s|--swagger)
print_header
check_prerequisites
validate_swagger
print_success "Swagger 验证完成"
;;
files|-f|--files)
print_header
validate_file_structure
print_success "文件结构验证完成"
;;
quality|-q|--quality)
print_header
validate_code_quality
print_success "代码质量验证完成"
;;
naming|-n|--naming)
print_header
validate_naming_conventions
print_success "命名规范验证完成"
;;
types|-t|--types)
print_header
validate_type_consistency
print_success "类型一致性验证完成"
;;
help|-h|--help)
show_help
;;
*)
print_error "未知选项: $1"
show_help
exit 1
;;
esac
}
# 执行主函数
main "$@"

View File

@ -1,239 +0,0 @@
#!/usr/bin/env dart
/// Augment
///
import 'dart:convert';
import 'dart:io';
void main(List<String> args) async {
print('🔍 Augment 代码生成规范验证');
print('=' * 50);
final validator = StandardsValidator();
try {
await validator.validateAll();
print('\n✅ 所有检查通过!代码符合 Augment 规范。');
} catch (e) {
print('\n❌ 验证失败:$e');
exit(1);
}
}
class StandardsValidator {
final List<String> errors = [];
final List<String> warnings = [];
///
Future<void> validateAll() async {
print('📋 开始验证...\n');
await _validateSwaggerJson();
await _validateGeneratedFiles();
await _validateCodeQuality();
await _validateNamingConventions();
await _validateTypeConsistency();
_printResults();
if (errors.isNotEmpty) {
throw Exception('发现 ${errors.length} 个错误');
}
}
/// swagger.json
Future<void> _validateSwaggerJson() async {
print('🔍 验证 swagger.json...');
final swaggerFile = File('swagger.json');
if (!swaggerFile.existsSync()) {
errors.add('swagger.json 文件不存在');
return;
}
try {
final content = await swaggerFile.readAsString();
final swagger = jsonDecode(content) as Map<String, dynamic>;
// OpenAPI
final openapi = swagger['openapi'] as String?;
if (openapi == null || !openapi.startsWith('3.0')) {
errors.add('OpenAPI 版本必须是 3.0.x当前$openapi');
}
//
if (!swagger.containsKey('paths')) {
errors.add('swagger.json 缺少 paths 定义');
}
if (!swagger.containsKey('components')) {
warnings.add('swagger.json 缺少 components 定义');
}
// schemas
final components = swagger['components'] as Map<String, dynamic>?;
if (components != null) {
final schemas = components['schemas'] as Map<String, dynamic>?;
if (schemas == null || schemas.isEmpty) {
warnings.add('components/schemas 为空,可能影响类型生成');
}
}
print(' ✅ swagger.json 格式正确');
} catch (e) {
errors.add('swagger.json 格式错误:$e');
}
}
///
Future<void> _validateGeneratedFiles() async {
print('🔍 验证生成的文件...');
final generatorDir = Directory('generator');
if (!generatorDir.existsSync()) {
errors.add('generator 目录不存在');
return;
}
//
final apiDir = Directory('generator/api');
final modelsDir = Directory('generator/api_models');
if (!apiDir.existsSync()) {
errors.add('generator/api 目录不存在');
}
if (!modelsDir.existsSync()) {
errors.add('generator/api_models 目录不存在');
}
// index.dart
final indexFile = File('generator/api_models/index.dart');
if (!indexFile.existsSync()) {
warnings.add('generator/api_models/index.dart 文件不存在');
}
print(' ✅ 文件结构正确');
}
///
Future<void> _validateCodeQuality() async {
print('🔍 验证代码质量...');
// dart analyze
final analyzeResult = await Process.run('dart', ['analyze', 'generator/']);
if (analyzeResult.exitCode != 0) {
final output = analyzeResult.stdout.toString();
final errorOutput = analyzeResult.stderr.toString();
if (output.contains('error') || errorOutput.contains('error')) {
errors.add('dart analyze 发现错误:\n$output\n$errorOutput');
} else if (output.contains('warning') ||
errorOutput.contains('warning')) {
warnings.add('dart analyze 发现警告:\n$output\n$errorOutput');
}
} else {
print(' ✅ 静态分析通过');
}
}
///
Future<void> _validateNamingConventions() async {
print('🔍 验证命名规范...');
final apiDir = Directory('generator/api');
if (apiDir.existsSync()) {
await for (final file in apiDir.list()) {
if (file is File && file.path.endsWith('.dart')) {
final fileName = file.path.split('/').last;
// API _api.dart
if (!fileName.endsWith('_api.dart')) {
errors.add('API 文件命名不规范:$fileName(应该以 _api.dart 结尾)');
}
// snake_case
final baseName = fileName.replaceAll('_api.dart', '');
if (!_isSnakeCase(baseName)) {
errors.add('API 文件名不是 snake_case$fileName');
}
}
}
}
final modelsDir = Directory('generator/api_models');
if (modelsDir.existsSync()) {
await for (final file in modelsDir.list()) {
if (file is File &&
file.path.endsWith('.dart') &&
!file.path.endsWith('index.dart')) {
final fileName = file.path.split('/').last;
final baseName = fileName.replaceAll('.dart', '');
// snake_case
if (!_isSnakeCase(baseName)) {
errors.add('模型文件名不是 snake_case$fileName');
}
}
}
}
print(' ✅ 命名规范检查完成');
}
///
Future<void> _validateTypeConsistency() async {
print('🔍 验证类型一致性...');
//
final generatorFile = File('lib/generators/retrofit_api_generator.dart');
if (generatorFile.existsSync()) {
final content = await generatorFile.readAsString();
//
final hardcodedPatterns = [
'TaskInfoResult',
'if.*contains.*login.*return',
'if.*contains.*task.*return',
'if.*tag.*contains.*return',
];
for (final pattern in hardcodedPatterns) {
final regex = RegExp(pattern, caseSensitive: false);
if (regex.hasMatch(content)) {
errors.add('发现硬编码类型推断:$pattern');
}
}
}
print(' ✅ 类型一致性检查完成');
}
/// snake_case
bool _isSnakeCase(String name) {
return RegExp(r'^[a-z][a-z0-9_]*$').hasMatch(name);
}
///
void _printResults() {
print('\n📊 验证结果:');
print(' 错误:${errors.length}');
print(' 警告:${warnings.length}');
if (errors.isNotEmpty) {
print('\n❌ 错误列表:');
for (int i = 0; i < errors.length; i++) {
print(' ${i + 1}. ${errors[i]}');
}
}
if (warnings.isNotEmpty) {
print('\n⚠️ 警告列表:');
for (int i = 0; i < warnings.length; i++) {
print(' ${i + 1}. ${warnings[i]}');
}
}
}
}