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:
parent
498c2f3d7e
commit
69aad6bda1
|
|
@ -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
|
||||
24
CHANGELOG.md
24
CHANGELOG.md
|
|
@ -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
|
||||
|
||||
### 🎉 新特性
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
369
CONTRIBUTING.md
369
CONTRIBUTING.md
|
|
@ -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) 下授权。
|
||||
|
||||
## 🙏 致谢
|
||||
|
||||
感谢所有贡献者的努力!您的贡献让这个项目变得更好。
|
||||
|
||||
### 贡献者列表
|
||||
|
||||
<!-- 这里会自动生成贡献者列表 -->
|
||||
|
||||
---
|
||||
|
||||
再次感谢您的贡献!🎉
|
||||
|
|
@ -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] Makefile(12+ 命令)
|
||||
- [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
|
||||
**状态**: ✅ 完成
|
||||
|
||||
|
|
@ -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` 完全自定义生成的文件头格式了!
|
||||
|
||||
|
|
@ -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 的代码量
|
||||
|
||||
|
|
@ -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 个 endpoints,10 个 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
257
README.md
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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` 文件!
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 "✅ 代码质量检查完成"
|
||||
|
||||
|
|
@ -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"
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -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 类名(如 QRCodeApi、MobileManagerApi 等)
|
||||
/// 仅匹配形如 "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,
|
||||
|
|
|
|||
|
|
@ -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 =
|
||||
|
|
|
|||
|
|
@ -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 [];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
// 这里应该从文件或数据库读取缓存数据
|
||||
// 实际实现会更复杂
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
'');
|
||||
buffer.writeln('');
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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'^_'), '');
|
||||
}
|
||||
}
|
||||
|
|
@ -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('');
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
// 工具类
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
130
run_swagger.sh
130
run_swagger.sh
|
|
@ -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 "$@"
|
||||
1143
swagger.json
1143
swagger.json
File diff suppressed because it is too large
Load Diff
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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}');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -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%的任务成功
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import 'lib/utils/string_utils.dart';
|
||||
import '../lib/utils/string_utils.dart';
|
||||
|
||||
void main() {
|
||||
print('Testing function name generation:');
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import 'lib/utils/string_utils.dart';
|
||||
import '../lib/utils/string_utils.dart';
|
||||
|
||||
void main() {
|
||||
print('Testing property name conversion:');
|
||||
280
validate.sh
280
validate.sh
|
|
@ -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 "$@"
|
||||
|
|
@ -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]}');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue