From a12bf7e618b67c0c2233803bfec99441eb3eed43 Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 24 Jul 2025 10:44:25 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=81=A2=E5=A4=8D=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AUGMENT_CODE_GENERATION_STANDARDS.md | 458 + CODE_REVIEW_CHECKLIST.md | 231 + CONTRIBUTING.md | 369 + QUICK_REFERENCE.md | 228 + README.md | 247 +- analysis_options.yaml | 9 +- docs/API_REFERENCE.md | 457 + example/advanced_usage.dart | 311 + example/basic_usage.dart | 239 + example/complete_project_example.dart | 424 + example/dio_retrofit_usage.dart | 311 + example/generated/advanced_api_service.dart | 178 + example/generated/basic_api_service.dart | 70 + example/generated/validation_report.json | 79 + example/generated/validation_report.txt | 52 + generator_config.yaml | 240 + lib/commands/generate_command.dart | 120 +- lib/core/config.dart | 4 +- lib/core/error_reporter.dart | 445 + lib/core/error_rules.dart | 469 + lib/core/models.dart | 1943 ++- lib/core/performance_parser.dart | 471 + lib/core/smart_cache.dart | 444 + lib/generators/documentation_generator.dart | 39 +- lib/generators/endpoint_code_generator.dart | 6 +- lib/generators/model_code_generator.dart | 313 +- .../optimized_retrofit_generator.dart | 547 + lib/generators/performance_generator.dart | 591 + lib/generators/retrofit_api_generator.dart | 1402 +- lib/parsers/swagger_data_parser.dart | 121 +- lib/swagger_cli_new.dart | 193 - lib/swagger_generator_flutter.dart | 19 + lib/utils/file_utils.dart | 1 - lib/utils/reference_resolver.dart | 301 + lib/utils/string_utils.dart | 8 + lib/validators/enhanced_validator.dart | 597 + lib/validators/schema_validator.dart | 845 + pubspec.lock | 28 +- pubspec.yaml | 4 +- run_swagger.sh | 81 +- swagger.json | 12902 ++++++++++++++++ tests/comprehensive_generator_test.dart | 613 + tests/comprehensive_parser_test.dart | 623 + tests/encoding_test.dart | 217 + tests/enhanced_validator_test.dart | 476 + tests/integration_test.dart | 589 + tests/media_type_test.dart | 451 + tests/models_test.dart | 923 -- tests/optimized_generator_test.dart | 392 + tests/performance_test.dart | 488 + tests/reference_resolver_test.dart | 325 + tests/security_test.dart | 504 + tests/simple_generator_test.dart | 442 + tests/string_utils_test.dart | 3 +- validate.sh | 280 + validate_standards.dart | 239 + 56 files changed, 30486 insertions(+), 1876 deletions(-) create mode 100644 AUGMENT_CODE_GENERATION_STANDARDS.md create mode 100644 CODE_REVIEW_CHECKLIST.md create mode 100644 CONTRIBUTING.md create mode 100644 QUICK_REFERENCE.md create mode 100644 docs/API_REFERENCE.md create mode 100644 example/advanced_usage.dart create mode 100644 example/basic_usage.dart create mode 100644 example/complete_project_example.dart create mode 100644 example/dio_retrofit_usage.dart create mode 100644 example/generated/advanced_api_service.dart create mode 100644 example/generated/basic_api_service.dart create mode 100644 example/generated/validation_report.json create mode 100644 example/generated/validation_report.txt create mode 100644 generator_config.yaml create mode 100644 lib/core/error_reporter.dart create mode 100644 lib/core/error_rules.dart create mode 100644 lib/core/performance_parser.dart create mode 100644 lib/core/smart_cache.dart create mode 100644 lib/generators/optimized_retrofit_generator.dart create mode 100644 lib/generators/performance_generator.dart delete mode 100644 lib/swagger_cli_new.dart create mode 100644 lib/swagger_generator_flutter.dart create mode 100644 lib/utils/reference_resolver.dart create mode 100644 lib/validators/enhanced_validator.dart create mode 100644 lib/validators/schema_validator.dart create mode 100644 swagger.json create mode 100644 tests/comprehensive_generator_test.dart create mode 100644 tests/comprehensive_parser_test.dart create mode 100644 tests/encoding_test.dart create mode 100644 tests/enhanced_validator_test.dart create mode 100644 tests/integration_test.dart create mode 100644 tests/media_type_test.dart delete mode 100644 tests/models_test.dart create mode 100644 tests/optimized_generator_test.dart create mode 100644 tests/performance_test.dart create mode 100644 tests/reference_resolver_test.dart create mode 100644 tests/security_test.dart create mode 100644 tests/simple_generator_test.dart create mode 100755 validate.sh create mode 100644 validate_standards.dart diff --git a/AUGMENT_CODE_GENERATION_STANDARDS.md b/AUGMENT_CODE_GENERATION_STANDARDS.md new file mode 100644 index 0000000..dafbcc1 --- /dev/null +++ b/AUGMENT_CODE_GENERATION_STANDARDS.md @@ -0,0 +1,458 @@ +# 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` +4. **默认**: `BaseResult>` + +#### **分页类型判断** +```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 +``` + +--- + +## 📝 **数据模型生成规范** + +### **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 json) => + _${SchemaName}FromJson(json); + + Map 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 +"object" -> Map 或具体类型 +"$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> // TaskInfoResult 不存在 + +// ✅ 使用通用类型或实际存在的类型 +Future>> +Future> +``` + +### **3. 主观判断** +```dart +// ❌ 禁止主观添加参数 +@Body() Map request // swagger 中没有定义 + +// ❌ 禁止主观修改类型 +List -> List // 没有明确的 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> userLogin( + @Body() LoginRequest request +); + +/// 获取用户列表(分页) +@GET('/api/v1/User/GetUserList') +Future> getUserList( + @Query('page') int page, + @Query('size') int size, + @Query('keyword') String? keyword +); + +/// 健康检查 +@GET('/health') +Future> healthCheck(); + +/// 无明确 schema 的接口 +@POST('/api/v1/Action/DoSomething') +Future>> 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 json) => + _$UserResultFromJson(json); + + Map 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 json) => + _$GetUserListParametersFromJson(json); + + Map 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 diff --git a/CODE_REVIEW_CHECKLIST.md b/CODE_REVIEW_CHECKLIST.md new file mode 100644 index 0000000..80e4a19 --- /dev/null +++ b/CODE_REVIEW_CHECKLIST.md @@ -0,0 +1,231 @@ +# Augment 代码生成审查清单 + +## 📋 **生成前检查** + +### **Swagger 文档验证** +- [ ] swagger.json 文件格式正确 +- [ ] OpenAPI 版本为 3.0.x +- [ ] 所有 $ref 引用都存在对应的定义 +- [ ] components/schemas 部分完整 +- [ ] 所有接口都有明确的 responses 定义 +- [ ] 参数定义完整(name, in, schema) +- [ ] requestBody 定义明确(如果需要) + +### **环境准备** +- [ ] Dart SDK 版本 >= 3.0.0 +- [ ] 依赖包版本正确 +- [ ] 网络连接正常(如果从 URL 获取 swagger) +- [ ] 输出目录权限正确 + +--- + +## 🔧 **生成过程检查** + +### **命令执行** +- [ ] 使用正确的生成命令 +- [ ] 参数配置正确 +- [ ] 没有错误或警告信息 +- [ ] 生成统计信息合理 + +### **文件生成** +- [ ] API 文件按 tag 正确分组 +- [ ] 模型文件命名规范 +- [ ] 目录结构正确 +- [ ] index.dart 文件更新 + +--- + +## ✅ **生成后验证** + +### **API 接口检查** + +#### **文件结构** +- [ ] 文件头注释完整 +- [ ] 导入语句正确且按需导入 +- [ ] 类名符合 PascalCase 规范 +- [ ] 方法名符合 camelCase 规范 + +#### **方法定义** +- [ ] HTTP 方法注解正确(@GET, @POST, @PUT, @DELETE) +- [ ] 路径定义与 swagger 一致 +- [ ] 返回类型正确提取 +- [ ] 参数定义完整且正确 + +```dart +// ✅ 正确的方法定义示例 +/// 用户登录 +@POST('/api/v1/Login/userLogin') +Future> userLogin( + @Body() LoginRequest request +); + +// ❌ 错误示例 +@POST('/api/v1/Login/userLogin') +Future> userLogin( // 错误:不存在的类型 + @Body() Map request // 错误:swagger 中没有定义 +); +``` + +#### **参数检查** +- [ ] 路径参数使用 @Path 注解 +- [ ] 查询参数使用 @Query 注解 +- [ ] 请求体参数使用 @Body 注解 +- [ ] 参数类型与 swagger 定义一致 +- [ ] 可空性正确(? 标记) +- [ ] 没有多余的参数 + +#### **返回类型检查** +- [ ] 使用 BaseResult 包装 +- [ ] 分页接口使用 BasePageResult +- [ ] 健康检查接口返回 void +- [ ] 泛型类型存在于 swagger 中 +- [ ] 没有硬编码推断的类型 + +#### **导入检查** +- [ ] 基础类型导入正确 +- [ ] 分页类型按需导入 +- [ ] 模型类型导入完整 +- [ ] 没有未使用的导入 +- [ ] 导入路径正确 + +### **数据模型检查** + +#### **类定义** +- [ ] 类名符合 PascalCase 规范 +- [ ] @JsonSerializable 注解正确 +- [ ] 注解参数配置正确(checked: true, includeIfNull: false) + +#### **属性定义** +- [ ] 属性名符合 camelCase 规范 +- [ ] 类型映射正确(string -> String, integer -> int) +- [ ] 可空性严格按照 nullable 字段 +- [ ] 注释信息完整 +- [ ] final 修饰符正确使用 + +```dart +// ✅ 正确的属性定义 +/// 用户ID +final int id; + +/// 用户名 +final String username; + +/// 昵称(可空) +final String? nickname; // swagger 中 "nullable": true + +// ❌ 错误示例 +final String? username; // 错误:swagger 中没有 "nullable": true +final String nickname; // 错误:swagger 中有 "nullable": true +``` + +#### **构造函数检查** +- [ ] 使用 const 构造函数 +- [ ] 非空字段标记为 required +- [ ] 可空字段不使用 required +- [ ] 参数顺序合理 + +#### **序列化方法** +- [ ] fromJson 工厂方法存在 +- [ ] toJson 方法存在 +- [ ] part 文件引用正确 +- [ ] 生成的方法名正确 + +### **代码质量检查** + +#### **静态分析** +- [ ] dart analyze 无错误 +- [ ] dart analyze 无警告 +- [ ] 代码格式化正确 +- [ ] 命名规范一致 + +#### **类型安全** +- [ ] 避免使用 dynamic +- [ ] 泛型类型明确 +- [ ] 可空性处理正确 +- [ ] 类型转换安全 + +#### **性能考虑** +- [ ] 使用 const 构造函数 +- [ ] 避免不必要的对象创建 +- [ ] 导入优化 +- [ ] 内存使用合理 + +--- + +## 🚫 **常见问题检查** + +### **类型相关** +- [ ] 没有生成不存在的类型(如 TaskInfoResult) +- [ ] 没有硬编码的类型映射 +- [ ] 没有基于路径关键词的推断 +- [ ] 没有基于 tag 的类型推断 + +### **参数相关** +- [ ] 没有添加 swagger 中未定义的参数 +- [ ] 没有遗漏 swagger 中定义的参数 +- [ ] 参数位置正确(path, query, body) +- [ ] 参数类型正确 + +### **导入相关** +- [ ] 没有循环导入 +- [ ] 没有未使用的导入 +- [ ] 没有缺失的导入 +- [ ] 导入路径正确 + +### **命名相关** +- [ ] 文件名符合 snake_case +- [ ] 类名符合 PascalCase +- [ ] 方法名符合 camelCase +- [ ] 变量名符合 camelCase + +--- + +## 🔄 **集成测试** + +### **构建测试** +- [ ] dart pub get 成功 +- [ ] dart pub run build_runner build 成功 +- [ ] 生成的 .g.dart 文件正确 +- [ ] 没有构建错误或警告 + +### **运行时测试** +- [ ] API 调用正常 +- [ ] 序列化/反序列化正常 +- [ ] 类型转换正确 +- [ ] 错误处理正确 + +### **兼容性测试** +- [ ] Dart 版本兼容 +- [ ] Flutter 版本兼容 +- [ ] 依赖包版本兼容 +- [ ] 平台兼容性 + +--- + +## 📝 **文档检查** + +### **代码注释** +- [ ] 类注释完整 +- [ ] 方法注释完整 +- [ ] 属性注释完整 +- [ ] 特殊逻辑有说明 + +### **生成信息** +- [ ] 文件头信息正确 +- [ ] 版本信息正确 +- [ ] 生成时间标记 +- [ ] 来源信息明确 + +--- + +## ✍️ **审查签名** + +**审查者**: _______________ +**审查日期**: _______________ +**审查结果**: [ ] 通过 [ ] 需要修改 [ ] 拒绝 +**备注**: _______________ + +--- + +**检查清单版本**: v2.0 +**最后更新**: 2025-01-24 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..103c00a --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,369 @@ +# 贡献指南 + +感谢您对 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) 下授权。 + +## 🙏 致谢 + +感谢所有贡献者的努力!您的贡献让这个项目变得更好。 + +### 贡献者列表 + + + +--- + +再次感谢您的贡献!🎉 diff --git a/QUICK_REFERENCE.md b/QUICK_REFERENCE.md new file mode 100644 index 0000000..3df81dd --- /dev/null +++ b/QUICK_REFERENCE.md @@ -0,0 +1,228 @@ +# Augment 代码生成快速参考 + +## 🚀 **快速开始** + +### **生成 API 代码** +```bash +# 生成所有代码 +dart run bin/main.dart generate --api --models --split-by-tags + +# 只生成 API 接口 +dart run bin/main.dart generate --api --split-by-tags + +# 只生成数据模型 +dart run bin/main.dart generate --models +``` + +### **项目集成** +```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 +``` + +```bash +# 运行代码生成 +dart pub get +dart pub run build_runner build +``` + +--- + +## 📋 **常见问题解决** + +### **Q: 生成的类型不存在怎么办?** +```dart +// ❌ 错误:生成了不存在的类型 +Future> someMethod(); + +// ✅ 解决:检查 swagger.json 中是否定义了 TaskInfoResult +// 如果没有定义,联系后端添加 schema 定义 +// 或者使用通用类型: +Future>> someMethod(); +``` + +### **Q: 接口缺少参数怎么办?** +```dart +// ❌ 错误:swagger 中没有定义参数,但生成器添加了 +@POST('/api/v1/SomeAction') +Future> someAction( + @Body() Map request // 不应该存在 +); + +// ✅ 解决:检查 swagger.json 中的参数定义 +// 如果确实需要参数,联系后端在 swagger 中添加定义 +``` + +### **Q: 可空性不正确怎么办?** +```dart +// ❌ 错误:字段应该可空但生成为非空 +final String name; // 但实际可能为 null + +// ✅ 解决:检查 swagger.json 中的 nullable 字段 +{ + "name": { + "type": "string", + "nullable": true // 添加这个字段 + } +} +``` + +### **Q: 分页接口没有使用 BasePageResult?** +```dart +// ❌ 错误:分页接口使用了错误的返回类型 +Future>> getUserList(); + +// ✅ 解决:检查接口是否真的是分页接口 +// 确保 swagger 中定义了分页相关的查询参数和响应结构 +Future> getUserList(); +``` + +--- + +## 🔧 **调试技巧** + +### **1. 检查 swagger.json** +```bash +# 验证 swagger.json 格式 +curl -X POST "https://validator.swagger.io/validator/debug" \ + -H "Content-Type: application/json" \ + -d @swagger.json +``` + +### **2. 查看生成日志** +```bash +# 启用详细日志 +dart run bin/main.dart generate --api --models --verbose +``` + +### **3. 手动验证类型** +```dart +// 在生成的代码中添加断言验证 +assert(response.data is UserResult); +assert(response.data?.name != null); +``` + +--- + +## 📝 **代码模板** + +### **标准 API 接口模板** +```dart +/// {接口描述} +@{HTTP_METHOD}('{路径}') +Future<{返回类型}> {方法名}( + // 路径参数(如果有) + @Path('{参数名}') {类型} 参数名, + + // 查询参数(如果有) + @Query('{参数名}') {类型}? 参数名, + + // 请求体(仅当 swagger 中明确定义时) + @Body() {类型} request +); +``` + +### **标准数据模型模板** +```dart +@JsonSerializable(checked: true, includeIfNull: false) +class {类名} { + /// {字段描述} + final {类型} {字段名}; + + const {类名}({ + required this.{非空字段}, + this.{可空字段}, + }); + + factory {类名}.fromJson(Map json) => + _${类名}FromJson(json); + + Map toJson() => _${类名}ToJson(this); +} +``` + +--- + +## ⚡ **性能优化** + +### **1. 按需导入** +```dart +// ✅ 只导入需要的类型 +import 'package:learning_officer_oa/common/models/common/base_result.dart'; + +// ❌ 避免导入不需要的类型 +// import 'package:learning_officer_oa/common/models/common/base_page_result.dart'; +``` + +### **2. 使用 const 构造函数** +```dart +// ✅ 使用 const 构造函数 +const UserResult({ + required this.id, + required this.name, +}); + +// ❌ 避免非 const 构造函数 +UserResult({ + required this.id, + required this.name, +}); +``` + +### **3. 合理的类型选择** +```dart +// ✅ 使用具体类型 +Future> getUser(); + +// ❌ 避免过度使用 dynamic +Future> getUser(); +``` + +--- + +## 🛡️ **安全检查** + +### **生成前检查** +- [ ] swagger.json 格式正确 +- [ ] 所有 $ref 引用存在 +- [ ] 版本信息匹配 + +### **生成后检查** +- [ ] 代码通过 dart analyze +- [ ] 所有导入都存在 +- [ ] 类型定义完整 +- [ ] 可空性正确 + +### **部署前检查** +- [ ] 运行所有测试 +- [ ] 代码格式化 +- [ ] 文档更新 +- [ ] 版本标记 + +--- + +## 📞 **获取帮助** + +### **常见错误代码** +- **E001**: swagger.json 格式错误 +- **E002**: 缺少必要的 schema 定义 +- **E003**: $ref 引用不存在 +- **E004**: 类型映射失败 + +### **联系方式** +- **技术支持**: 联系后端团队完善 swagger 文档 +- **Bug 报告**: 提交到项目 Issues +- **功能请求**: 通过 PR 贡献代码 + +--- + +**快速参考版本**: v2.0 +**最后更新**: 2025-01-24 diff --git a/README.md b/README.md index 82f72fe..3210e7d 100644 --- a/README.md +++ b/README.md @@ -2,32 +2,207 @@ 基于 Swagger/OpenAPI 的 Dart/Flutter API/模型代码生成工具。 -## 功能简介 -- 根据 swagger.json 自动生成 Dart API 接口、模型、枚举等 -- 支持 Retrofit、json_serializable 等主流生态 -- 支持自定义生成规则和命名风格 +[![Dart](https://img.shields.io/badge/Dart-3.0+-blue.svg)](https://dart.dev/) +[![Flutter](https://img.shields.io/badge/Flutter-3.0+-blue.svg)](https://flutter.dev/) +[![OpenAPI](https://img.shields.io/badge/OpenAPI-3.0+-green.svg)](https://spec.openapis.org/oas/v3.0.3/) -## 快速开始 +## ✨ 功能特性 + +### 🚀 核心功能 +- **完整的 OpenAPI 3.0 支持**:涵盖所有主要规范特性 +- **高性能解析**:支持并行解析和流式处理大型文档 +- **智能代码生成**:生成高质量的 Dart/Flutter 代码 +- **模块化架构**:按 API 模块自动分组生成 + +### 🎯 专为 Flutter 优化 +- **Dio + Retrofit 集成**:完美适配主流网络架构 +- **类型安全**:生成强类型的 API 接口和模型 +- **JSON 序列化**:自动生成 json_serializable 代码 +- **文件上传支持**:完整的 multipart/form-data 支持 + +### 🔧 高级特性 +- **智能缓存**:提升重复操作性能 +- **错误诊断**:详细的错误报告和修复建议 +- **性能监控**:内置性能统计和优化 +- **增量生成**:支持增量更新和变更检测 + +## 📚 **文档和规范** + +### **核心文档** +- [**代码生成规范**](./AUGMENT_CODE_GENERATION_STANDARDS.md) - 完整的生成规范和最佳实践 +- [**快速参考指南**](./QUICK_REFERENCE.md) - 常见问题和解决方案 +- [**代码审查清单**](./CODE_REVIEW_CHECKLIST.md) - 质量保证检查清单 +- [**生成器配置**](./generator_config.yaml) - 详细的配置选项 + +### **设计原则** +1. **OpenAPI 3.0 标准优先** - 严格遵循规范,不进行主观推断 +2. **与服务器保持一致** - swagger.json 是唯一真实来源 +3. **有问题沟通文档** - 发现问题时要求完善后端文档 +4. **类型安全第一** - 生成强类型代码,避免运行时错误 + +## 🚀 快速开始 + +### 1. 安装依赖 ```bash -# 安装依赖 flutter pub get -# 或 -pub get +``` -# 生成模型和API -sh run_swagger.sh -# 或 +### 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 ``` -## 目录结构 +### 3. 编程式用法(推荐) +```dart +import 'package:swagger_generator_flutter/swagger_generator_flutter.dart'; + +void main() async { + // 使用高性能解析器 + final parser = PerformanceParser( + config: ParseConfig( + enablePerformanceStats: true, + enableParallelParsing: true, + ), + ); + + // 解析 OpenAPI 文档 + final jsonString = await File('swagger.json').readAsString(); + final document = await parser.parseDocument(jsonString); + + // 使用优化生成器 + final generator = OptimizedRetrofitGenerator( + className: 'ApiService', + generateModularApis: true, + generateBaseResult: true, + generatePagination: true, + generateFileUpload: 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}'); +} ``` -swagger/ + +## 📖 详细配置 + +### 生成器类型 + +#### 1. RetrofitApiGenerator(基础版) +```dart +final generator = RetrofitApiGenerator( + className: 'ApiService', // 生成的类名 + splitByTags: true, // 是否按标签分割(默认启用) + useRetrofit: true, // 使用 Retrofit 注解 + generateModels: true, // 生成模型类 +); +``` + +#### 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 +final parser = PerformanceParser( + config: ParseConfig( + enableParallelParsing: true, // 并行解析 + enableStreamParsing: false, // 流式解析 + enableCaching: true, // 缓存 + maxConcurrency: 4, // 最大并发数 + enablePerformanceStats: true, // 性能统计 + ), +); +``` + +### 验证和错误处理 +```dart +// 创建验证器 +final validator = EnhancedValidator( + strictMode: false, // 严格模式 + includeWarnings: true, // 包含警告 +); + +// 验证文档 +final isValid = validator.validateDocument(document); + +// 获取错误报告 +final errorReport = validator.errorReporter.generateReport(); +print(errorReport); +``` + +## 📊 性能优化 + +### 缓存策略 +```dart +// 配置智能缓存 +final cache = SmartCache( + maxSize: 1000, // 最大缓存大小 + strategy: CacheStrategy.smart, // 缓存策略 + defaultTtl: Duration(hours: 1), // 默认过期时间 +); + +// 获取缓存统计 +final stats = cache.getStats(); +print('缓存命中率: ${(stats.hitRate * 100).toStringAsFixed(1)}%'); +``` + +### 性能监控 +```dart +// 获取解析性能统计 +final parseStats = parser.lastStats; +print('解析性能统计:'); +print(' 总时间: ${parseStats?.totalTime.inMilliseconds}ms'); +print(' 路径数: ${parseStats?.pathCount}'); +print(' 吞吐量: ${parseStats?.bytesPerSecond.toStringAsFixed(2)} bytes/s'); +``` + +## 📁 目录结构 +``` +swagger_generator_flutter/ bin/ # 命令行入口 + example/ # 使用示例 generator/ # 生成的 API、模型、文档 lib/ # 生成器核心代码 - tests/ # 单元测试 - swagger.json # Swagger/OpenAPI 源文件 + core/ # 核心模型和解析器 + generators/ # 代码生成器 + validators/ # 文档验证器 + tests/ # 单元测试和集成测试 + swagger.json # OpenAPI 源文件 ``` ## 运行测试 @@ -35,14 +210,52 @@ swagger/ dart run test tests/ ``` +## 🎯 **规范遵循示例** + +### **✅ 正确的生成结果** +```dart +// 严格按照 swagger.json 定义生成 +@POST('/api/v1/Login/userLogin') +Future> userLogin( + @Body() LoginRequest request // 仅当 swagger 中明确定义时 +); + +// 健康检查接口 +@GET('/health') +Future> healthCheck(); + +// 无明确 schema 的接口 +@POST('/api/v1/Action/DoSomething') +Future>> doSomething(); +``` + +### **❌ 避免的错误做法** +```dart +// 错误:生成不存在的类型 +Future> someMethod(); + +// 错误:添加 swagger 中未定义的参数 +@POST('/api/v1/GetUsers') +Future>> getUsers( + @Body() Map request // swagger 中没有定义 +); + +// 错误:基于关键词推断类型 +if (path.contains('login')) return 'UserLoginResult'; +``` + ## 贡献指南 +- **严格遵循 [代码生成规范](./AUGMENT_CODE_GENERATION_STANDARDS.md)** +- **使用 [代码审查清单](./CODE_REVIEW_CHECKLIST.md) 进行质量检查** - 代码需包含中英文注释 - 新增功能请补充对应测试用例 - 生成规则/命名风格如有特殊需求请在 issue 说明 ## 常见问题 -- 生成模型/接口命名不规范?请检查 swagger 字段命名和生成规则 -- 枚举、泛型、嵌套对象支持?已支持主流用法,特殊场景请补充 issue +- **生成的类型不存在?** 检查 swagger.json 中是否定义了对应的 schema +- **接口缺少参数?** 确认 swagger.json 中是否有完整的参数定义 +- **可空性不正确?** 检查 swagger.json 中的 nullable 字段设置 +- 更多问题请参考 [快速参考指南](./QUICK_REFERENCE.md) ### 脚本命令说明 diff --git a/analysis_options.yaml b/analysis_options.yaml index af1f32d..88747fd 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -37,17 +37,18 @@ linter: rules: # 常用且推荐启用的规则 (即使默认集没有包含,也建议手动添加) - avoid_empty_else # 避免空的 else 块 - - avoid_print # 在生产代码中避免使用 print (可根据项目需求启用/禁用) + # - avoid_print # 在生产代码中避免使用 print (可根据项目需求启用/禁用) - avoid_relative_lib_imports # 避免从 'lib/' 相对导入 + - directives_ordering # 强制 import/export 指令排序 # - avoid_return_and_type_annotation # 避免冗余的返回类型注解 - curly_braces_in_flow_control_structures # 控制流语句强制使用大括号 - empty_catches # 避免空的 catch 块 - empty_constructor_bodies # 避免空的构造函数体 - empty_statements # 避免空的语句 - file_names # 文件名使用小写下划线命名 (my_file.dart) - - prefer_const_constructors # 尽可能使用 const 构造函数 - - prefer_const_declarations # 尽可能使用 const 声明 - - prefer_const_literals_to_create_immutables # 尽可能使用 const 创建不可变集合 + # - prefer_const_constructors # 尽可能使用 const 构造函数 + # - prefer_const_declarations # 尽可能使用 const 声明 + # - prefer_const_literals_to_create_immutables # 尽可能使用 const 创建不可变集合 # - prefer_single_quotes # 优先使用单引号 (或 prefer_double_quotes) - prefer_final_fields # 类中的私有字段尽可能使用 final - prefer_final_locals # 局部变量尽可能使用 final diff --git a/docs/API_REFERENCE.md b/docs/API_REFERENCE.md new file mode 100644 index 0000000..ad42ed4 --- /dev/null +++ b/docs/API_REFERENCE.md @@ -0,0 +1,457 @@ +# API 参考文档 + +本文档详细介绍了 Swagger Generator Flutter 的所有 API 接口和配置选项。 + +## 目录 + +- [核心类](#核心类) +- [生成器](#生成器) +- [解析器](#解析器) +- [验证器](#验证器) +- [缓存系统](#缓存系统) +- [配置选项](#配置选项) + +## 核心类 + +### SwaggerDocument + +OpenAPI 文档的主要数据模型。 + +```dart +class SwaggerDocument { + final String title; + final String version; + final String description; + final List servers; + final Map paths; + final Map models; + final ApiComponents components; + final List security; + + SwaggerDocument({ + required this.title, + required this.version, + required this.description, + required this.servers, + required this.paths, + required this.models, + required this.components, + required this.security, + }); + + factory SwaggerDocument.fromJson(Map json); + Map toJson(); +} +``` + +### ApiPath + +API 路径和操作的定义。 + +```dart +class ApiPath { + final String path; + final HttpMethod method; + final String summary; + final String description; + final String operationId; + final List tags; + final List parameters; + final ApiRequestBody? requestBody; + final Map responses; + final List security; + + ApiPath({ + required this.path, + required this.method, + required this.summary, + required this.description, + required this.operationId, + required this.tags, + required this.parameters, + this.requestBody, + required this.responses, + required this.security, + }); +} +``` + +### ApiModel + +数据模型的定义。 + +```dart +class ApiModel { + final String name; + final String description; + final Map properties; + final List required; + + ApiModel({ + required this.name, + required this.description, + required this.properties, + required this.required, + }); +} +``` + +## 生成器 + +### BaseGenerator + +所有生成器的基类。 + +```dart +abstract class BaseGenerator { + String get generatorType; + String generate(); +} +``` + +### RetrofitApiGenerator + +基础的 Retrofit API 生成器。 + +```dart +class RetrofitApiGenerator extends BaseGenerator { + final String className; + final bool splitByTags; + final bool useRetrofit; + final bool generateModels; + + RetrofitApiGenerator({ + this.className = 'ApiService', + this.splitByTags = false, + this.useRetrofit = true, + this.generateModels = true, + }); + + @override + String generateFromDocument(SwaggerDocument document); +} +``` + +#### 方法 + +- `generateFromDocument(SwaggerDocument document)`: 从文档生成代码 +- `generateSingleApiFile()`: 生成单个 API 文件 +- `generateMainApiFile()`: 生成主 API 文件(分模块时) + +### OptimizedRetrofitGenerator + +优化版的 Retrofit API 生成器。 + +```dart +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', + }); +} +``` + +#### 特性 + +- **模块化生成**: 按 API 标签自动分组 +- **基础类型**: 生成 BaseResult、BasePageResult 等 +- **文件上传**: 支持 multipart/form-data +- **工具类**: 生成 ApiUtils 工具类 + +### PerformanceGenerator + +高性能代码生成器。 + +```dart +class PerformanceGenerator extends BaseGenerator { + final int maxConcurrency; + final bool enableCaching; + final bool enableIncremental; + final bool enableParallel; + + PerformanceGenerator({ + this.maxConcurrency = 4, + this.enableCaching = true, + this.enableIncremental = true, + this.enableParallel = true, + }); + + Future generateFromDocument(SwaggerDocument document); + GenerationStats getStats(); + CacheStats getCacheStats(); + void clearCache(); +} +``` + +#### 方法 + +- `generateFromDocument()`: 异步生成代码 +- `getStats()`: 获取生成性能统计 +- `getCacheStats()`: 获取缓存统计 +- `clearCache()`: 清除缓存 + +## 解析器 + +### PerformanceParser + +高性能 OpenAPI 解析器。 + +```dart +class PerformanceParser { + final ParseConfig config; + + PerformanceParser({ParseConfig? config}); + + Future parseDocument(String jsonString); + ParsePerformanceStats? get lastStats; + void clearCache(); + Map getCacheStats(); +} +``` + +### ParseConfig + +解析器配置。 + +```dart +class ParseConfig { + final bool enableParallelParsing; + final bool enableStreamParsing; + final bool enableIncrementalParsing; + final bool enableCaching; + final int maxConcurrency; + final int streamBufferSize; + final bool enablePerformanceStats; + final bool enableMemoryOptimization; + + const ParseConfig({ + this.enableParallelParsing = true, + this.enableStreamParsing = false, + this.enableIncrementalParsing = false, + this.enableCaching = true, + this.maxConcurrency = 4, + this.streamBufferSize = 8192, + this.enablePerformanceStats = false, + this.enableMemoryOptimization = true, + }); +} +``` + +## 验证器 + +### EnhancedValidator + +增强的文档验证器。 + +```dart +class EnhancedValidator { + final bool strictMode; + final bool includeWarnings; + + EnhancedValidator({ + this.strictMode = false, + this.includeWarnings = true, + }); + + bool validateDocument(SwaggerDocument document); + ErrorReporter get errorReporter; +} +``` + +### ErrorReporter + +错误报告器。 + +```dart +class ErrorReporter { + List get errors; + bool get hasErrors; + bool get hasCriticalErrors; + + void addError(DetailedError error); + void reportError({ + required String id, + required String title, + required String description, + required ErrorSeverity severity, + required ErrorCategory category, + required String jsonPath, + // ... 其他参数 + }); + + List getErrorsBySeverity(ErrorSeverity severity); + List getErrorsByCategory(ErrorCategory category); + Map getErrorStatistics(); + + String generateReport({ + bool includeStatistics = true, + bool groupByCategory = false, + ErrorSeverity? minSeverity, + }); + + String generateJsonReport(); + void clear(); +} +``` + +## 缓存系统 + +### SmartCache + +智能缓存管理器。 + +```dart +class SmartCache { + final int maxSize; + final CacheStrategy strategy; + final Duration defaultTtl; + + SmartCache({ + int maxSize = 1000, + CacheStrategy strategy = CacheStrategy.smart, + Duration defaultTtl = const Duration(hours: 1), + }); + + T? get(String key); + void put(String key, T value, {Duration? ttl, String? etag}); + bool containsKey(String key); + T? remove(String key); + void clear(); + + CacheStats getStats(); + List getKeysNeedingRefresh(); + Future refreshKeys(List keys, Future Function(String key) refreshFunction); + Future warmUp(Map Function()> warmUpFunctions); +} +``` + +### CacheStrategy + +缓存策略枚举。 + +```dart +enum CacheStrategy { + lru, // 最近最少使用 + lfu, // 最近最常使用 + fifo, // 先进先出 + ttl, // 基于时间的过期 + smart, // 智能策略 +} +``` + +## 配置选项 + +### 生成器配置 + +| 选项 | 类型 | 默认值 | 描述 | +|------|------|--------|------| +| `className` | String | 'ApiService' | 生成的主类名 | +| `splitByTags` | bool | true | 是否按标签分割 API | +| `generateModularApis` | bool | true | 生成模块化 API | +| `generateBaseResult` | bool | true | 生成基础响应类型 | +| `generatePagination` | bool | true | 生成分页支持 | +| `generateFileUpload` | bool | true | 生成文件上传支持 | +| `baseResultType` | String | 'BaseResult' | 基础响应类型名 | +| `pageResultType` | String | 'BasePageResult' | 分页响应类型名 | + +### 解析器配置 + +| 选项 | 类型 | 默认值 | 描述 | +|------|------|--------|------| +| `enableParallelParsing` | bool | true | 启用并行解析 | +| `enableStreamParsing` | bool | false | 启用流式解析 | +| `enableCaching` | bool | true | 启用缓存 | +| `maxConcurrency` | int | 4 | 最大并发数 | +| `enablePerformanceStats` | bool | false | 启用性能统计 | + +### 验证器配置 + +| 选项 | 类型 | 默认值 | 描述 | +|------|------|--------|------| +| `strictMode` | bool | false | 严格模式 | +| `includeWarnings` | bool | true | 包含警告 | + +## 错误类型 + +### ErrorSeverity + +```dart +enum ErrorSeverity { + info, // 信息 + warning, // 警告 + error, // 错误 + critical, // 严重错误 +} +``` + +### ErrorCategory + +```dart +enum ErrorCategory { + syntax, // 语法错误 + schema, // Schema 错误 + reference, // 引用错误 + validation, // 验证错误 + compatibility, // 兼容性问题 + performance, // 性能问题 + security, // 安全问题 + bestPractice, // 最佳实践 +} +``` + +## 性能统计 + +### ParsePerformanceStats + +解析性能统计。 + +```dart +class ParsePerformanceStats { + final Duration totalTime; + final Duration parseTime; + final Duration validationTime; + final Duration modelCreationTime; + final int memoryUsage; + final int documentSize; + final int pathCount; + final int schemaCount; + + double get pathsPerSecond; + double get schemasPerSecond; + double get bytesPerSecond; +} +``` + +### GenerationStats + +生成性能统计。 + +```dart +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; + + double get successRate; + double get linesPerSecond; + double get bytesPerSecond; +} +``` diff --git a/example/advanced_usage.dart b/example/advanced_usage.dart new file mode 100644 index 0000000..488a3d4 --- /dev/null +++ b/example/advanced_usage.dart @@ -0,0 +1,311 @@ +/// 高级使用示例 +/// 演示高性能解析、优化生成和性能监控 +library; + +import 'dart:convert'; +import 'dart:io'; +import 'package:swagger_generator_flutter/swagger_generator_flutter.dart'; + +void main() async { + print('🚀 高级使用示例'); + print('=' * 50); + + try { + await demonstrateHighPerformanceParsing(); + await demonstrateOptimizedGeneration(); + await demonstratePerformanceMonitoring(); + await demonstrateCaching(); + await demonstrateValidationAndErrorHandling(); + + print('\n🎉 高级使用示例完成!'); + } catch (e, stackTrace) { + print('❌ 发生错误: $e'); + print('堆栈跟踪: $stackTrace'); + } +} + +/// 演示高性能解析 +Future demonstrateHighPerformanceParsing() async { + print('\n📊 高性能解析演示'); + print('-' * 30); + + // 读取文档 + final jsonString = await File('swagger.json').readAsString(); + print('📖 文档大小: ${(jsonString.length / 1024).toStringAsFixed(2)}KB'); + + // 配置高性能解析器 + final parser = PerformanceParser( + config: ParseConfig( + enablePerformanceStats: true, + enableParallelParsing: false, // 禁用并行解析避免类型转换问题 + enableCaching: true, + maxConcurrency: 8, + enableMemoryOptimization: true, + ), + ); + + // 解析文档 + final stopwatch = Stopwatch()..start(); + final document = await parser.parseDocument(jsonString); + stopwatch.stop(); + + // 显示解析结果 + print('✅ 解析完成'); + print(' - 解析时间: ${stopwatch.elapsedMilliseconds}ms'); + print(' - 路径数: ${document.paths.length}'); + print(' - 模型数: ${document.models.length}'); + print(' - 服务器数: ${document.servers.length}'); + + // 显示性能统计 + final stats = parser.lastStats; + if (stats != null) { + print('\n📈 性能统计:'); + print(' - 总时间: ${stats.totalTime.inMilliseconds}ms'); + print(' - 解析时间: ${stats.parseTime.inMilliseconds}ms'); + print(' - 验证时间: ${stats.validationTime.inMilliseconds}ms'); + print(' - 模型创建时间: ${stats.modelCreationTime.inMilliseconds}ms'); + print( + ' - 内存使用: ${(stats.memoryUsage / 1024 / 1024).toStringAsFixed(2)}MB'); + print(' - 路径处理速度: ${stats.pathsPerSecond.toStringAsFixed(1)} paths/s'); + print(' - 吞吐量: ${(stats.bytesPerSecond / 1024).toStringAsFixed(2)} KB/s'); + } + + // 显示缓存统计 + final cacheStats = parser.getCacheStats(); + print('\n🗄️ 缓存统计:'); + print(' - 缓存大小: ${cacheStats['size']}'); + print(' - 缓存键: ${(cacheStats['keys'] as List).length}'); +} + +/// 演示优化代码生成 +Future demonstrateOptimizedGeneration() async { + print('\n🔧 优化代码生成演示'); + print('-' * 30); + + // 解析文档 + final jsonString = await File('swagger.json').readAsString(); + final document = SwaggerDocument.fromJson(jsonDecode(jsonString)); + + // 创建优化生成器 + final generator = OptimizedRetrofitGenerator( + className: 'AdvancedApiService', + generateModularApis: true, + generateBaseResult: true, + generatePagination: true, + generateFileUpload: true, + baseResultType: 'ApiResult', + pageResultType: 'PagedResult', + ); + + // 生成代码 + final stopwatch = Stopwatch()..start(); + final generatedCode = generator.generateFromDocument(document); + stopwatch.stop(); + + print('✅ 代码生成完成'); + print(' - 生成时间: ${stopwatch.elapsedMilliseconds}ms'); + print(' - 代码大小: ${(generatedCode.length / 1024).toStringAsFixed(2)}KB'); + print(' - 代码行数: ${generatedCode.split('\n').length}'); + + // 检查生成的特性 + final features = []; + if (generatedCode.contains('class ApiResult')) features.add('基础响应类型'); + if (generatedCode.contains('class PagedResult')) features.add('分页支持'); + if (generatedCode.contains('MultipartFile')) features.add('文件上传'); + if (generatedCode.contains('class ApiUtils')) features.add('工具类'); + + print(' - 生成特性: ${features.join(', ')}'); + + // 保存代码 + final outputFile = File('example/generated/advanced_api_service.dart'); + await outputFile.writeAsString(generatedCode); + print(' - 保存位置: ${outputFile.path}'); +} + +/// 演示性能监控 +Future demonstratePerformanceMonitoring() async { + print('\n📊 性能监控演示'); + print('-' * 30); + + // 解析文档 + final jsonString = await File('swagger.json').readAsString(); + final document = SwaggerDocument.fromJson(jsonDecode(jsonString)); + + // 创建性能生成器 + final generator = PerformanceGenerator( + maxConcurrency: 4, + enableCaching: true, + enableIncremental: true, + enableParallel: true, + ); + + // 生成代码 + final generatedCode = await generator.generateFromDocument(document); + + // 获取性能统计 + final stats = generator.getStats(); + print('📈 生成性能统计:'); + print(' - 总任务数: ${stats.totalTasks}'); + print(' - 完成任务数: ${stats.completedTasks}'); + print(' - 失败任务数: ${stats.failedTasks}'); + print(' - 成功率: ${(stats.successRate * 100).toStringAsFixed(1)}%'); + print(' - 总时间: ${stats.totalTime.inMilliseconds}ms'); + print(' - 平均任务时间: ${stats.averageTaskTime.inMilliseconds}ms'); + print(' - 生成行数: ${stats.linesGenerated}'); + print(' - 生成字节数: ${stats.bytesGenerated}'); + print(' - 并行效率: ${(stats.parallelEfficiency * 100).toStringAsFixed(1)}%'); + print(' - 生成速度: ${stats.linesPerSecond.toStringAsFixed(1)} lines/s'); + + // 获取缓存统计 + final cacheStats = generator.getCacheStats(); + print('\n🗄️ 生成器缓存统计:'); + print(' - 总请求: ${cacheStats.totalRequests}'); + print(' - 缓存命中: ${cacheStats.hits}'); + print(' - 缓存未命中: ${cacheStats.misses}'); + print(' - 命中率: ${(cacheStats.hitRate * 100).toStringAsFixed(1)}%'); + print(' - 缓存大小: ${cacheStats.size}/${cacheStats.maxSize}'); + print(' - 平均访问时间: ${cacheStats.averageAccessTime.inMicroseconds}μs'); +} + +/// 演示缓存功能 +Future demonstrateCaching() async { + print('\n🗄️ 缓存功能演示'); + print('-' * 30); + + // 创建智能缓存 + final cache = SmartCache( + maxSize: 100, + strategy: CacheStrategy.smart, + defaultTtl: Duration(minutes: 30), + ); + + // 添加一些测试数据 + cache.put('user:1', '{"id": 1, "name": "Alice"}'); + cache.put('user:2', '{"id": 2, "name": "Bob"}'); + cache.put('user:3', '{"id": 3, "name": "Charlie"}'); + + // 模拟访问模式 + for (int i = 0; i < 10; i++) { + cache.get('user:1'); // 频繁访问 + } + for (int i = 0; i < 5; i++) { + cache.get('user:2'); // 中等访问 + } + cache.get('user:3'); // 少量访问 + + // 获取缓存统计 + final stats = cache.getStats(); + print('📊 缓存统计:'); + print(' - 总请求: ${stats.totalRequests}'); + print(' - 命中: ${stats.hits}'); + print(' - 未命中: ${stats.misses}'); + print(' - 命中率: ${(stats.hitRate * 100).toStringAsFixed(1)}%'); + print(' - 缓存大小: ${stats.size}/${stats.maxSize}'); + print(' - 填充率: ${(stats.fillRate * 100).toStringAsFixed(1)}%'); + + // 显示访问统计 + print('\n📈 访问统计:'); + stats.keyAccessCounts.forEach((key, count) { + print(' - $key: $count 次访问'); + }); + + // 演示缓存预热 + print('\n🔥 缓存预热演示:'); + await cache.warmUp({ + 'config:app': () async => '{"theme": "dark", "language": "zh"}', + 'config:api': () async => '{"timeout": 30000, "retries": 3}', + }); + + print(' - 预热完成,缓存大小: ${cache.getStats().size}'); +} + +/// 演示验证和错误处理 +Future demonstrateValidationAndErrorHandling() async { + print('\n✅ 验证和错误处理演示'); + print('-' * 30); + + // 创建一个有问题的文档 + final problematicDoc = { + 'openapi': '3.0.3', + 'info': { + 'title': 'Problematic API', + 'version': '1.0.0', + }, + 'paths': { + '/users/{id}': { + 'get': { + 'summary': 'Get user', + 'responses': { + '200': { + 'description': 'Success', + }, + }, + // 缺少路径参数声明 + }, + }, + 'invalid-path': { + // 无效的路径格式 + 'get': { + 'summary': 'Invalid path', + 'responses': { + '200': { + 'description': 'Success', + }, + }, + }, + }, + }, + }; + + final jsonString = jsonEncode(problematicDoc); + final document = SwaggerDocument.fromJson(jsonDecode(jsonString)); + + // 创建验证器 + final validator = EnhancedValidator( + includeWarnings: true, + ); + + // 验证文档 + final isValid = validator.validateDocument(document); + print('📋 验证结果: ${isValid ? "通过" : "失败"}'); + + // 获取错误统计 + final errorStats = validator.errorReporter.getErrorStatistics(); + print('\n📊 错误统计:'); + errorStats.forEach((severity, count) { + print(' - ${severity.displayName}: $count'); + }); + + // 显示详细错误 + final errors = validator.errorReporter.errors; + if (errors.isNotEmpty) { + print('\n❌ 详细错误:'); + for (int i = 0; i < errors.length && i < 5; i++) { + final error = errors[i]; + print(' ${i + 1}. ${error.severity.emoji} ${error.title}'); + print(' 位置: ${error.location.jsonPath}'); + print(' 描述: ${error.description}'); + if (error.suggestions.isNotEmpty) { + print(' 建议: ${error.suggestions.first.description}'); + } + print(''); + } + } + + // 生成错误报告 + final report = validator.errorReporter.generateReport( + includeStatistics: true, + groupByCategory: true, + ); + + // 保存错误报告 + final reportFile = File('example/generated/validation_report.txt'); + await reportFile.writeAsString(report); + print('📄 错误报告已保存到: ${reportFile.path}'); + + // 生成 JSON 格式报告 + final jsonReport = validator.errorReporter.generateJsonReport(); + final jsonReportFile = File('example/generated/validation_report.json'); + await jsonReportFile.writeAsString(jsonReport); + print('📄 JSON 报告已保存到: ${jsonReportFile.path}'); +} diff --git a/example/basic_usage.dart b/example/basic_usage.dart new file mode 100644 index 0000000..f748e91 --- /dev/null +++ b/example/basic_usage.dart @@ -0,0 +1,239 @@ +/// 基础使用示例 +/// 演示如何使用 Swagger Generator Flutter 生成基础的 API 代码 +library; + +import 'dart:convert'; +import 'dart:io'; +import 'package:swagger_generator_flutter/swagger_generator_flutter.dart'; + +void main() async { + print('🚀 基础使用示例'); + print('=' * 50); + + try { + // 1. 读取 OpenAPI 文档 + print('📖 读取 OpenAPI 文档...'); + final jsonString = await File('swagger.json').readAsString(); + print('✅ 文档大小: ${(jsonString.length / 1024).toStringAsFixed(2)}KB'); + + // 2. 解析文档 + print('\n🔍 解析文档...'); + final document = SwaggerDocument.fromJson(jsonDecode(jsonString)); + print('✅ 解析完成'); + print(' - 标题: ${document.title}'); + print(' - 版本: ${document.version}'); + print(' - 路径数: ${document.paths.length}'); + print(' - 模型数: ${document.models.length}'); + + // 3. 验证文档 + print('\n✅ 验证文档...'); + final validator = EnhancedValidator( + includeWarnings: true, + ); + + final isValid = validator.validateDocument(document); + final errors = + validator.errorReporter.getErrorsBySeverity(ErrorSeverity.error); + final warnings = + validator.errorReporter.getErrorsBySeverity(ErrorSeverity.warning); + + print(' - 验证结果: ${isValid ? "通过" : "失败"}'); + print(' - 错误数: ${errors.length}'); + print(' - 警告数: ${warnings.length}'); + + if (errors.isNotEmpty) { + print('\n❌ 发现错误:'); + for (final error in errors.take(3)) { + print(' - ${error.title}: ${error.description}'); + } + } + + // 4. 生成基础 API 代码 + print('\n🔧 生成基础 API 代码...'); + final generator = RetrofitApiGenerator( + className: 'BasicApiService', + splitByTags: true, // 使用拆分模式 + useRetrofit: true, + generateModels: true, + ); + + final generatedCode = generator.generateFromDocument(document); + print('✅ 代码生成完成'); + print(' - 代码大小: ${(generatedCode.length / 1024).toStringAsFixed(2)}KB'); + print(' - 代码行数: ${generatedCode.split('\n').length}'); + + // 5. 保存生成的代码 + print('\n💾 保存生成的代码...'); + final outputDir = Directory('example/generated'); + if (!outputDir.existsSync()) { + outputDir.createSync(recursive: true); + } + + final outputFile = File('example/generated/basic_api_service.dart'); + await outputFile.writeAsString(generatedCode); + print('✅ 代码已保存到: ${outputFile.path}'); + + // 6. 显示生成的代码片段 + print('\n📄 生成的代码片段:'); + print('-' * 30); + final lines = generatedCode.split('\n'); + for (int i = 0; i < 20 && i < lines.length; i++) { + print('${(i + 1).toString().padLeft(2)}: ${lines[i]}'); + } + if (lines.length > 20) { + print('... (还有 ${lines.length - 20} 行)'); + } + + print('\n🎉 基础使用示例完成!'); + } catch (e, stackTrace) { + print('❌ 发生错误: $e'); + print('堆栈跟踪: $stackTrace'); + } +} + +/// 创建示例 OpenAPI 文档 +Future createSampleDocument() async { + final sampleDoc = { + 'openapi': '3.0.3', + 'info': { + 'title': 'Sample API', + 'version': '1.0.0', + 'description': 'A sample API for demonstration', + }, + 'servers': [ + { + 'url': 'https://api.example.com', + 'description': 'Production server', + }, + ], + 'paths': { + '/users': { + 'get': { + 'summary': 'Get all users', + 'operationId': 'getUsers', + 'tags': ['users'], + 'parameters': [ + { + 'name': 'page', + 'in': 'query', + 'required': false, + 'schema': {'type': 'integer', 'default': 1}, + 'description': 'Page number', + }, + ], + 'responses': { + '200': { + 'description': 'Success', + 'content': { + 'application/json': { + 'schema': { + 'type': 'array', + 'items': { + '\$ref': '#/components/schemas/User', + }, + }, + }, + }, + }, + }, + }, + 'post': { + 'summary': 'Create user', + 'operationId': 'createUser', + 'tags': ['users'], + 'requestBody': { + 'required': true, + 'content': { + 'application/json': { + 'schema': { + '\$ref': '#/components/schemas/CreateUserRequest', + }, + }, + }, + }, + 'responses': { + '201': { + 'description': 'User created', + 'content': { + 'application/json': { + 'schema': { + '\$ref': '#/components/schemas/User', + }, + }, + }, + }, + }, + }, + }, + '/users/{id}': { + 'get': { + 'summary': 'Get user by ID', + 'operationId': 'getUserById', + 'tags': ['users'], + 'parameters': [ + { + 'name': 'id', + 'in': 'path', + 'required': true, + 'schema': {'type': 'integer'}, + 'description': 'User ID', + }, + ], + 'responses': { + '200': { + 'description': 'User found', + 'content': { + 'application/json': { + 'schema': { + '\$ref': '#/components/schemas/User', + }, + }, + }, + }, + '404': { + 'description': 'User not found', + }, + }, + }, + }, + }, + 'components': { + 'schemas': { + 'User': { + 'type': 'object', + 'properties': { + 'id': {'type': 'integer', 'format': 'int64'}, + 'name': {'type': 'string'}, + 'email': {'type': 'string', 'format': 'email'}, + 'createdAt': {'type': 'string', 'format': 'date-time'}, + }, + 'required': ['id', 'name', 'email'], + }, + 'CreateUserRequest': { + 'type': 'object', + 'properties': { + 'name': {'type': 'string'}, + 'email': {'type': 'string', 'format': 'email'}, + }, + 'required': ['name', 'email'], + }, + }, + }, + }; + + final jsonString = const JsonEncoder.withIndent(' ').convert(sampleDoc); + await File('example/sample_swagger.json').writeAsString(jsonString); + print('✅ 示例文档已创建: example/sample_swagger.json'); +} + +/// 运行示例 +/// +/// 使用方法: +/// ```bash +/// dart run example/basic_usage.dart +/// ``` +/// +/// 或者创建示例文档: +/// ```dart +/// await createSampleDocument(); +/// ``` diff --git a/example/complete_project_example.dart b/example/complete_project_example.dart new file mode 100644 index 0000000..d48c407 --- /dev/null +++ b/example/complete_project_example.dart @@ -0,0 +1,424 @@ +/// 完整项目示例 +/// 演示在真实 Flutter 项目中如何集成和使用 Swagger Generator +library; + +import 'dart:io'; +import 'package:swagger_generator_flutter/swagger_generator_flutter.dart'; + +/// 项目配置 +class ProjectConfig { + static const String projectName = 'MyFlutterApp'; + static const String apiServiceName = 'ApiService'; + static const String outputDir = 'lib/api/generated'; + static const String swaggerFile = 'swagger.json'; + + // API 配置 + static const String baseUrl = 'https://api.myapp.com'; + static const String apiVersion = 'v1'; + + // 生成配置 + static const bool enableModularApis = true; + static const bool enableBaseResult = true; + static const bool enablePagination = true; + static const bool enableFileUpload = true; + static const bool enableCaching = true; + static const bool enableValidation = true; +} + +void main() async { + print('🚀 ${ProjectConfig.projectName} API 代码生成'); + print('=' * 60); + + final generator = ProjectApiGenerator(); + + try { + await generator.generateProjectApi(); + print('\n🎉 API 代码生成完成!'); + } catch (e, stackTrace) { + print('❌ 生成失败: $e'); + print('堆栈跟踪: $stackTrace'); + exit(1); + } +} + +/// 项目 API 生成器 +class ProjectApiGenerator { + late PerformanceParser parser; + late OptimizedRetrofitGenerator generator; + late EnhancedValidator validator; + + ProjectApiGenerator() { + _initializeComponents(); + } + + /// 初始化组件 + void _initializeComponents() { + // 配置高性能解析器 + parser = PerformanceParser( + config: ParseConfig( + enablePerformanceStats: true, + enableParallelParsing: false, // 禁用并行解析避免类型转换问题 + enableCaching: ProjectConfig.enableCaching, + maxConcurrency: 4, + enableMemoryOptimization: true, + ), + ); + + // 配置优化生成器 + generator = OptimizedRetrofitGenerator( + className: ProjectConfig.apiServiceName, + generateModularApis: ProjectConfig.enableModularApis, + generateBaseResult: ProjectConfig.enableBaseResult, + generatePagination: ProjectConfig.enablePagination, + generateFileUpload: ProjectConfig.enableFileUpload, + baseResultType: 'ApiResult', + pageResultType: 'PagedApiResult', + ); + + // 配置验证器 + validator = EnhancedValidator( + includeWarnings: true, + ); + } + + /// 生成项目 API + Future generateProjectApi() async { + // 1. 检查环境 + await _checkEnvironment(); + + // 2. 读取和解析文档 + final document = await _parseSwaggerDocument(); + + // 3. 验证文档 + await _validateDocument(document); + + // 4. 生成 API 代码 + await _generateApiCode(document); + + // 5. 生成配置文件 + await _generateConfigFiles(); + + // 6. 生成使用示例 + await _generateUsageExamples(); + + // 7. 生成文档 + await _generateDocumentation(document); + + // 8. 显示总结 + _showSummary(); + } + + /// 检查环境 + Future _checkEnvironment() async { + print('🔍 检查环境...'); + + // 检查 swagger.json 文件 + final swaggerFile = File(ProjectConfig.swaggerFile); + if (!swaggerFile.existsSync()) { + throw Exception('找不到 ${ProjectConfig.swaggerFile} 文件'); + } + + // 检查输出目录 + final outputDir = Directory(ProjectConfig.outputDir); + if (!outputDir.existsSync()) { + print('📁 创建输出目录: ${ProjectConfig.outputDir}'); + outputDir.createSync(recursive: true); + } + + // 检查依赖 + final pubspecFile = File('pubspec.yaml'); + if (pubspecFile.existsSync()) { + final content = await pubspecFile.readAsString(); + final requiredDeps = ['dio', 'retrofit', 'json_annotation']; + final missingDeps = []; + + for (final dep in requiredDeps) { + if (!content.contains(dep)) { + missingDeps.add(dep); + } + } + + if (missingDeps.isNotEmpty) { + print('⚠️ 缺少依赖: ${missingDeps.join(', ')}'); + print(' 请在 pubspec.yaml 中添加这些依赖'); + } + } + + print('✅ 环境检查完成'); + } + + /// 解析 Swagger 文档 + Future _parseSwaggerDocument() async { + print('\n📖 解析 Swagger 文档...'); + + final jsonString = await File(ProjectConfig.swaggerFile).readAsString(); + print(' - 文档大小: ${(jsonString.length / 1024).toStringAsFixed(2)}KB'); + + final stopwatch = Stopwatch()..start(); + final document = await parser.parseDocument(jsonString); + stopwatch.stop(); + + print('✅ 解析完成'); + print(' - 解析时间: ${stopwatch.elapsedMilliseconds}ms'); + print(' - API 标题: ${document.title}'); + print(' - API 版本: ${document.version}'); + print(' - 路径数量: ${document.paths.length}'); + print(' - 模型数量: ${document.models.length}'); + print(' - 服务器数量: ${document.servers.length}'); + + // 显示性能统计 + final stats = parser.lastStats; + if (stats != null) { + print(' - 处理速度: ${stats.pathsPerSecond.toStringAsFixed(1)} paths/s'); + print( + ' - 吞吐量: ${(stats.bytesPerSecond / 1024).toStringAsFixed(2)} KB/s'); + } + + return document; + } + + /// 验证文档 + Future _validateDocument(SwaggerDocument document) async { + if (!ProjectConfig.enableValidation) { + print('\n⏭️ 跳过文档验证'); + return; + } + + print('\n✅ 验证文档...'); + + final isValid = validator.validateDocument(document); + final errors = + validator.errorReporter.getErrorsBySeverity(ErrorSeverity.error); + final warnings = + validator.errorReporter.getErrorsBySeverity(ErrorSeverity.warning); + final criticalErrors = + validator.errorReporter.getErrorsBySeverity(ErrorSeverity.critical); + + print(' - 验证结果: ${isValid ? "✅ 通过" : "❌ 失败"}'); + print(' - 严重错误: ${criticalErrors.length}'); + print(' - 错误: ${errors.length}'); + print(' - 警告: ${warnings.length}'); + + if (criticalErrors.isNotEmpty) { + print('\n🚨 严重错误:'); + for (final error in criticalErrors.take(3)) { + print(' - ${error.title}: ${error.description}'); + } + throw Exception('文档包含严重错误,无法继续生成'); + } + + if (errors.isNotEmpty) { + print('\n❌ 错误:'); + for (final error in errors.take(3)) { + print(' - ${error.title}: ${error.description}'); + } + } + + if (warnings.isNotEmpty) { + print('\n⚠️ 警告:'); + for (final warning in warnings.take(3)) { + print(' - ${warning.title}: ${warning.description}'); + } + } + + // 保存验证报告 + final report = validator.errorReporter.generateReport(); + final reportFile = File('${ProjectConfig.outputDir}/validation_report.txt'); + await reportFile.writeAsString(report); + print(' - 验证报告: ${reportFile.path}'); + } + + /// 生成 API 代码 + Future _generateApiCode(SwaggerDocument document) async { + print('\n🔧 生成 API 代码...'); + + final stopwatch = Stopwatch()..start(); + final generatedCode = generator.generateFromDocument(document); + stopwatch.stop(); + + print('✅ 代码生成完成'); + print(' - 生成时间: ${stopwatch.elapsedMilliseconds}ms'); + print(' - 代码大小: ${(generatedCode.length / 1024).toStringAsFixed(2)}KB'); + print(' - 代码行数: ${generatedCode.split('\n').length}'); + + // 保存主 API 文件 + final apiFile = File( + '${ProjectConfig.outputDir}/${ProjectConfig.apiServiceName.toLowerCase()}.dart'); + await apiFile.writeAsString(generatedCode); + print(' - API 文件: ${apiFile.path}'); + + // 检查生成的特性 + final features = []; + if (generatedCode.contains('class ApiResult')) features.add('基础响应类型'); + if (generatedCode.contains('class PagedApiResult')) features.add('分页支持'); + if (generatedCode.contains('MultipartFile')) features.add('文件上传'); + if (generatedCode.contains('class ApiUtils')) features.add('工具类'); + + if (features.isNotEmpty) { + print(' - 生成特性: ${features.join(', ')}'); + } + } + + /// 生成配置文件 + Future _generateConfigFiles() async { + print('\n⚙️ 生成配置文件...'); + + // 生成 API 配置 + final apiConfig = ''' +/// API 配置文件 +/// 自动生成,请勿手动修改 +class ApiConfig { + static const String baseUrl = '${ProjectConfig.baseUrl}'; + static const String apiVersion = '${ProjectConfig.apiVersion}'; + static const int connectTimeout = 30000; + static const int receiveTimeout = 30000; + static const int sendTimeout = 30000; + + // 请求头 + static const Map defaultHeaders = { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }; + + // 调试模式 + static const bool debugMode = true; +} +'''; + + final configFile = File('${ProjectConfig.outputDir}/api_config.dart'); + await configFile.writeAsString(apiConfig); + print(' - API 配置: ${configFile.path}'); + + // 生成 Dio 配置 + final dioConfig = ''' +/// Dio 配置文件 +import 'package:dio/dio.dart'; +import 'api_config.dart'; + +class DioConfig { + static Dio createDio() { + final dio = Dio(BaseOptions( + baseUrl: ApiConfig.baseUrl, + connectTimeout: Duration(milliseconds: ApiConfig.connectTimeout), + receiveTimeout: Duration(milliseconds: ApiConfig.receiveTimeout), + sendTimeout: Duration(milliseconds: ApiConfig.sendTimeout), + headers: ApiConfig.defaultHeaders, + )); + + if (ApiConfig.debugMode) { + dio.interceptors.add(LogInterceptor( + requestBody: true, + responseBody: true, + requestHeader: true, + responseHeader: false, + )); + } + + return dio; + } +} +'''; + + final dioConfigFile = File('${ProjectConfig.outputDir}/dio_config.dart'); + await dioConfigFile.writeAsString(dioConfig); + print(' - Dio 配置: ${dioConfigFile.path}'); + } + + /// 生成使用示例 + Future _generateUsageExamples() async { + print('\n📝 生成使用示例...'); + + final example = ''' +/// API 使用示例 +/// 展示如何在 Flutter 项目中使用生成的 API +import 'package:dio/dio.dart'; +import 'generated/${ProjectConfig.apiServiceName.toLowerCase()}.dart'; +import 'dio_config.dart'; + +class ApiExample { + late final ${ProjectConfig.apiServiceName} apiService; + + ApiExample() { + final dio = DioConfig.createDio(); + apiService = ${ProjectConfig.apiServiceName}(dio); + } + + /// 示例:获取用户列表 + Future getUsersExample() async { + try { + final response = await apiService.getUsers(); + print('用户列表: \$response'); + } catch (e) { + print('获取用户列表失败: \$e'); + } + } + + /// 示例:创建用户 + Future createUserExample() async { + try { + final userData = { + 'name': 'John Doe', + 'email': 'john@example.com', + }; + final response = await apiService.createUser(userData); + print('用户创建成功: \$response'); + } catch (e) { + print('创建用户失败: \$e'); + } + } +} +'''; + + final exampleFile = File('${ProjectConfig.outputDir}/api_example.dart'); + await exampleFile.writeAsString(example); + print(' - 使用示例: ${exampleFile.path}'); + } + + /// 生成文档 + Future _generateDocumentation(SwaggerDocument document) async { + print('\n📚 生成文档...'); + + final docs = StringBuffer(); + docs.writeln('# ${document.title} API 文档'); + docs.writeln(); + docs.writeln('版本: ${document.version}'); + docs.writeln('描述: ${document.description}'); + docs.writeln(); + docs.writeln('## 服务器'); + for (final server in document.servers) { + docs.writeln('- ${server.url}: ${server.description}'); + } + docs.writeln(); + docs.writeln('## API 端点'); + docs.writeln('总计: ${document.paths.length} 个端点'); + docs.writeln(); + docs.writeln('## 数据模型'); + docs.writeln('总计: ${document.models.length} 个模型'); + docs.writeln(); + docs.writeln('## 使用方法'); + docs.writeln('请参考 `api_example.dart` 文件中的示例代码。'); + + final docsFile = File('${ProjectConfig.outputDir}/README.md'); + await docsFile.writeAsString(docs.toString()); + print(' - API 文档: ${docsFile.path}'); + } + + /// 显示总结 + void _showSummary() { + print('\n📊 生成总结'); + print('-' * 40); + print('✅ 项目: ${ProjectConfig.projectName}'); + print('✅ API 服务: ${ProjectConfig.apiServiceName}'); + print('✅ 输出目录: ${ProjectConfig.outputDir}'); + print('✅ 模块化 API: ${ProjectConfig.enableModularApis ? "启用" : "禁用"}'); + print('✅ 基础响应类型: ${ProjectConfig.enableBaseResult ? "启用" : "禁用"}'); + print('✅ 分页支持: ${ProjectConfig.enablePagination ? "启用" : "禁用"}'); + print('✅ 文件上传: ${ProjectConfig.enableFileUpload ? "启用" : "禁用"}'); + + print('\n🎯 下一步:'); + print('1. 运行 `flutter packages pub run build_runner build` 生成序列化代码'); + print('2. 在项目中导入生成的 API 文件'); + print('3. 参考 `api_example.dart` 中的使用示例'); + print('4. 查看 `README.md` 了解 API 详情'); + } +} diff --git a/example/dio_retrofit_usage.dart b/example/dio_retrofit_usage.dart new file mode 100644 index 0000000..a3c80a8 --- /dev/null +++ b/example/dio_retrofit_usage.dart @@ -0,0 +1,311 @@ +/// Dio + Retrofit 使用示例 +/// 展示如何使用生成的 API 代码进行网络请求 + +import 'dart:io'; +import 'package:dio/dio.dart'; +import 'package:retrofit/retrofit.dart'; + +// 假设这些是生成的代码 +part 'dio_retrofit_usage.g.dart'; + +/// API 服务接口(生成的代码) +@RestApi(baseUrl: 'https://api.example.com/v1') +abstract class ApiService { + factory ApiService(Dio dio, {String? baseUrl}) = _ApiService; + + /// 获取用户信息 + @GET('/users/{id}') + Future getUser(@Path('id') int id); + + /// 创建用户 + @POST('/users') + Future createUser(@Body() CreateUserRequest request); + + /// 上传头像 + @POST('/users/{id}/avatar') + @MultiPart() + Future uploadAvatar( + @Path('id') int id, + @Part() MultipartFile avatar, + ); + + /// 获取用户列表(支持分页) + @GET('/users') + Future getUsers( + @Query('page') int page, + @Query('size') int size, + @Query('search') String? search, + ); + + /// 下载文件 + @GET('/files/{id}') + @DioResponseType(ResponseType.bytes) + Future> downloadFile(@Path('id') String id); +} + +/// 用户响应模型 +class UserResponse { + final int id; + final String name; + final String email; + final String? avatar; + + UserResponse({ + required this.id, + required this.name, + required this.email, + this.avatar, + }); + + factory UserResponse.fromJson(Map json) => UserResponse( + id: json['id'] as int, + name: json['name'] as String, + email: json['email'] as String, + avatar: json['avatar'] as String?, + ); +} + +/// 创建用户请求模型 +class CreateUserRequest { + final String name; + final String email; + final String password; + + CreateUserRequest({ + required this.name, + required this.email, + required this.password, + }); + + Map toJson() => { + 'name': name, + 'email': email, + 'password': password, + }; +} + +/// 用户列表响应模型 +class UserListResponse { + final List users; + final int total; + final int page; + final int size; + + UserListResponse({ + required this.users, + required this.total, + required this.page, + required this.size, + }); + + factory UserListResponse.fromJson(Map json) => + UserListResponse( + users: (json['users'] as List) + .map((e) => UserResponse.fromJson(e as Map)) + .toList(), + total: json['total'] as int, + page: json['page'] as int, + size: json['size'] as int, + ); +} + +/// 上传响应模型 +class UploadResponse { + final String url; + final String filename; + final int size; + + UploadResponse({ + required this.url, + required this.filename, + required this.size, + }); + + factory UploadResponse.fromJson(Map json) => UploadResponse( + url: json['url'] as String, + filename: json['filename'] as String, + size: json['size'] as int, + ); +} + +/// API 客户端配置和使用示例 +class ApiClient { + late final Dio _dio; + late final ApiService _apiService; + + ApiClient({String? baseUrl}) { + _dio = Dio(); + _setupDio(); + _apiService = ApiService(_dio, baseUrl: baseUrl); + } + + /// 配置 Dio + void _setupDio() { + // 基础配置 + _dio.options.connectTimeout = const Duration(seconds: 30); + _dio.options.receiveTimeout = const Duration(seconds: 30); + _dio.options.sendTimeout = const Duration(seconds: 30); + + // 添加拦截器 + _dio.interceptors.addAll([ + // 日志拦截器 + LogInterceptor( + requestBody: true, + responseBody: true, + logPrint: (obj) => print(obj), + ), + + // 认证拦截器 + BearerTokenInterceptor(token: 'your-auth-token'), + + // API Key 拦截器 + ApiKeyInterceptor( + apiKey: 'your-api-key', + location: ApiKeyLocation.header, + paramName: 'X-API-Key', + ), + + // 错误处理拦截器 + ErrorHandlerInterceptor(), + ]); + } + + /// 获取用户信息 + Future getUser(int id) async { + try { + return await _apiService.getUser(id); + } catch (e) { + throw _handleError(e); + } + } + + /// 创建用户 + Future createUser({ + required String name, + required String email, + required String password, + }) async { + try { + final request = CreateUserRequest( + name: name, + email: email, + password: password, + ); + return await _apiService.createUser(request); + } catch (e) { + throw _handleError(e); + } + } + + /// 上传头像 + Future uploadAvatar(int userId, String filePath) async { + try { + final file = await FileUploadHandler.createImageFile(filePath: filePath); + return await _apiService.uploadAvatar(userId, file); + } catch (e) { + throw _handleError(e); + } + } + + /// 获取用户列表 + Future getUsers({ + int page = 1, + int size = 20, + String? search, + }) async { + try { + return await _apiService.getUsers(page, size, search); + } catch (e) { + throw _handleError(e); + } + } + + /// 下载文件 + Future downloadFile(String fileId, String savePath) async { + try { + final bytes = await _apiService.downloadFile(fileId); + final file = File(savePath); + await file.writeAsBytes(bytes); + } catch (e) { + throw _handleError(e); + } + } + + /// 错误处理 + Exception _handleError(dynamic error) { + if (error is DioException) { + switch (error.type) { + case DioExceptionType.connectionTimeout: + case DioExceptionType.sendTimeout: + case DioExceptionType.receiveTimeout: + return TimeoutException('请求超时,请检查网络连接'); + case DioExceptionType.badResponse: + final statusCode = error.response?.statusCode; + final message = error.response?.data?['message'] ?? '服务器错误'; + return HttpException('HTTP $statusCode: $message'); + case DioExceptionType.cancel: + return Exception('请求已取消'); + case DioExceptionType.connectionError: + return Exception('网络连接错误,请检查网络设置'); + default: + return Exception('未知错误: ${error.message}'); + } + } + return Exception('未知错误: $error'); + } + + /// 释放资源 + void dispose() { + _dio.close(); + } +} + +/// 使用示例 +void main() async { + final apiClient = ApiClient(baseUrl: 'https://api.example.com/v1'); + + try { + // 获取用户信息 + final user = await apiClient.getUser(1); + print('用户信息: ${user.name} (${user.email})'); + + // 创建新用户 + final newUser = await apiClient.createUser( + name: 'John Doe', + email: 'john@example.com', + password: 'password123', + ); + print('创建用户成功: ${newUser.id}'); + + // 获取用户列表 + final userList = await apiClient.getUsers(page: 1, size: 10); + print('用户总数: ${userList.total}'); + + // 上传头像(如果有文件的话) + // final uploadResult = await apiClient.uploadAvatar(1, '/path/to/avatar.jpg'); + // print('头像上传成功: ${uploadResult.url}'); + + // 下载文件 + // await apiClient.downloadFile('file-id', '/path/to/save/file.pdf'); + // print('文件下载完成'); + } catch (e) { + print('API 调用失败: $e'); + } finally { + apiClient.dispose(); + } +} + +/// 自定义异常类 +class TimeoutException implements Exception { + final String message; + TimeoutException(this.message); + @override + String toString() => 'TimeoutException: $message'; +} + +class HttpException implements Exception { + final String message; + HttpException(this.message); + @override + String toString() => 'HttpException: $message'; +} diff --git a/example/generated/advanced_api_service.dart b/example/generated/advanced_api_service.dart new file mode 100644 index 0000000..a0a18e1 --- /dev/null +++ b/example/generated/advanced_api_service.dart @@ -0,0 +1,178 @@ +// 网络请求相关 +import 'package:dio/dio.dart'; +import 'package:json_annotation/json_annotation.dart'; + +// 文件处理 +import 'package:path/path.dart' as path; + +// 生成的代码 +part 'advanced_api_service_api.g.dart'; + +/// 基础响应结果 +@JsonSerializable(genericArgumentFactories: true) +class ApiResult { + /// 响应码 + final int code; + + /// 响应消息 + final String message; + + /// 响应数据 + final T? data; + + /// 是否成功 + bool get isSuccess => code == 200; + + const ApiResult({ + required this.code, + required this.message, + this.data, + }); + + factory ApiResult.fromJson( + Map json, + T Function(Object? json) fromJsonT, + ) => + _$ApiResultFromJson(json, fromJsonT); + + Map toJson(Object Function(T value) toJsonT) => + _$ApiResultToJson(this, toJsonT); +} + +/// 分页参数 +@JsonSerializable() +class BasePageParameter { + /// 页码(从1开始) + final int page; + + /// 每页大小 + final int size; + + const BasePageParameter({ + this.page = 1, + this.size = 20, + }); + + factory BasePageParameter.fromJson(Map json) => + _$BasePageParameterFromJson(json); + + Map toJson() => _$BasePageParameterToJson(this); +} + +/// 分页响应结果 +@JsonSerializable(genericArgumentFactories: true) +class PagedResult { + /// 数据列表 + final List list; + + /// 总数量 + final int total; + + /// 当前页码 + final int page; + + /// 每页大小 + final int size; + + /// 总页数 + int get totalPages => (total / size).ceil(); + + /// 是否有下一页 + bool get hasNext => page < totalPages; + + /// 是否有上一页 + bool get hasPrevious => page > 1; + + const PagedResult({ + required this.list, + required this.total, + required this.page, + required this.size, + }); + + factory PagedResult.fromJson( + Map json, + T Function(Object? json) fromJsonT, + ) => + _$PagedResultFromJson(json, fromJsonT); + + Map toJson(Object Function(T value) toJsonT) => + _$PagedResultToJson(this, toJsonT); +} + +/// 文件上传请求 +@JsonSerializable() +class FileUploadRequest { + /// 文件 + @JsonKey(includeFromJson: false, includeToJson: false) + final MultipartFile file; + + /// 文件名 + final String? filename; + + /// 文件类型 + final String? contentType; + + const FileUploadRequest({ + required this.file, + this.filename, + this.contentType, + }); + + factory FileUploadRequest.fromJson(Map json) => + _$FileUploadRequestFromJson(json); + + Map toJson() => _$FileUploadRequestToJson(this); +} + +/// 文件上传响应 +@JsonSerializable() +class FileUploadResult { + /// 文件 URL + final String url; + + /// 文件名 + final String filename; + + /// 文件大小 + final int size; + + /// 文件类型 + final String? contentType; + + const FileUploadResult({ + required this.url, + required this.filename, + required this.size, + this.contentType, + }); + + factory FileUploadResult.fromJson(Map json) => + _$FileUploadResultFromJson(json); + + Map toJson() => _$FileUploadResultToJson(this); +} + +/// 主 API 服务类 +/// 包含所有模块的 API 接口 +class AdvancedApiService { + final Dio _dio; + + AdvancedApiService(this._dio, {String? baseUrl}); +} + +/// API 工具类 +class ApiUtils { + /// 创建文件上传对象 + static Future createFileUpload(String filePath) async { + return MultipartFile.fromFile( + filePath, + filename: path.basename(filePath), + ); + } + + /// 创建分页参数 + static BasePageParameter createPageParam({int page = 1, int size = 20}) { + return BasePageParameter(page: page, size: size); + } +} diff --git a/example/generated/basic_api_service.dart b/example/generated/basic_api_service.dart new file mode 100644 index 0000000..56a3504 --- /dev/null +++ b/example/generated/basic_api_service.dart @@ -0,0 +1,70 @@ +// 主 API 接口定义 - 集合所有 Tag 的 API +// 基于 Swagger API 文档: +// 由 xy_swagger_generator by max 生成 +// Copyright (C) 2025 YuanXuan. All rights reserved. + +import 'package:dio/dio.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_error.dart'; +import 'api_error_handler.dart'; + +/// 统一API客户端类 +/// 聚合所有分模块的API接口,提供统一的访问入口 +class BasicApiService { + final Dio _dio; + + BasicApiService(this._dio, {String? baseUrl}); + + /// 获取Dio实例 + Dio get dio => _dio; + + /// 设置认证token + void setAuthToken(String token) { + _dio.options.headers['Authorization'] = 'Bearer $token'; + } + + /// 清除认证token + void clearAuthToken() { + _dio.options.headers.remove('Authorization'); + } + + /// 设置基础URL + void setBaseUrl(String baseUrl) { + _dio.options.baseUrl = baseUrl; + } + + /// 添加请求拦截器 + void addRequestInterceptor(Interceptor interceptor) { + _dio.interceptors.add(interceptor); + } + + /// 添加响应拦截器 + void addResponseInterceptor(Interceptor interceptor) { + _dio.interceptors.add(interceptor); + } + + /// 添加错误拦截器 + void addErrorInterceptor(Interceptor interceptor) { + _dio.interceptors.add(interceptor); + } + + /// 创建带错误处理的API调用 + Future callWithErrorHandling(Future Function() apiCall) async { + try { + return await apiCall(); + } on DioException catch (e) { + final error = ApiErrorHandler.handleDioError(e); + throw error; + } catch (e) { + final error = ApiError( + type: ApiErrorType.unknown, + message: '未知错误: $e', + code: -1, + originalError: e, + ); + throw error; + } + } +} diff --git a/example/generated/validation_report.json b/example/generated/validation_report.json new file mode 100644 index 0000000..a2bb937 --- /dev/null +++ b/example/generated/validation_report.json @@ -0,0 +1,79 @@ +{ + "timestamp": "2025-07-24T07:16:17.845544", + "summary": { + "total": 3, + "by_severity": { + "warning": 1, + "error": 1, + "info": 1 + } + }, + "errors": [ + { + "id": "MISSING_INFO_DESCRIPTION", + "title": "Missing API Description", + "description": "API description helps users understand the purpose of your API.", + "severity": "warning", + "category": "bestPractice", + "location": { + "json_path": "info.description", + "line": null, + "column": null, + "snippet": null + }, + "suggestions": [ + { + "description": "Add a description explaining what your API does", + "code_example": "\"description\": \"This API provides user management functionality\"", + "documentation_url": null + } + ], + "related_errors": [], + "timestamp": "2025-07-24T07:16:17.842816" + }, + { + "id": "EMPTY_PATHS", + "title": "Empty Paths Object", + "description": "OpenAPI document must contain at least one path.", + "severity": "error", + "category": "validation", + "location": { + "json_path": "paths", + "line": null, + "column": null, + "snippet": null + }, + "suggestions": [ + { + "description": "Add at least one API endpoint", + "code_example": "\"/users\": { \"get\": { \"responses\": { \"200\": { \"description\": \"Success\" } } } }", + "documentation_url": null + } + ], + "related_errors": [], + "timestamp": "2025-07-24T07:16:17.842917" + }, + { + "id": "NO_OPERATION_TAGS", + "title": "No Operation Tags", + "description": "Consider using tags to organize your API operations.", + "severity": "info", + "category": "bestPractice", + "location": { + "json_path": "paths", + "line": null, + "column": null, + "snippet": null + }, + "suggestions": [ + { + "description": "Add tags to operations", + "code_example": "\"tags\": [\"users\"]", + "documentation_url": null + } + ], + "related_errors": [], + "timestamp": "2025-07-24T07:16:17.843223" + } + ] +} \ No newline at end of file diff --git a/example/generated/validation_report.txt b/example/generated/validation_report.txt new file mode 100644 index 0000000..29843ca --- /dev/null +++ b/example/generated/validation_report.txt @@ -0,0 +1,52 @@ +📊 Error Summary +================================================== +⚠️ WARNING: 1 +❌ ERROR: 1 +ℹ️ INFO: 1 + +📂 Best Practice +------------------------------ +⚠️ WARNING: Missing API Description +Category: Best Practice +Location: info.description + +Description: + API description helps users understand the purpose of your API. + +Suggestions: + 1. Add a description explaining what your API does + Example: + "description": "This API provides user management functionality" + + + +ℹ️ INFO: No Operation Tags +Category: Best Practice +Location: paths + +Description: + Consider using tags to organize your API operations. + +Suggestions: + 1. Add tags to operations + Example: + "tags": ["users"] + + + +📂 Validation Error +------------------------------ +❌ ERROR: Empty Paths Object +Category: Validation Error +Location: paths + +Description: + OpenAPI document must contain at least one path. + +Suggestions: + 1. Add at least one API endpoint + Example: + "/users": { "get": { "responses": { "200": { "description": "Success" } } } } + + + diff --git a/generator_config.yaml b/generator_config.yaml new file mode 100644 index 0000000..573de01 --- /dev/null +++ b/generator_config.yaml @@ -0,0 +1,240 @@ +# Augment 代码生成器配置文件 +# 基于 OpenAPI 3.0 标准的配置规范 + +# 基本配置 +generator: + name: "xy_swagger_generator" + version: "2.0" + author: "max" + copyright: "Copyright (C) 2025 YuanXuan. All rights reserved." + +# 输入配置 +input: + # Swagger 文档源 + swagger_url: "http://localhost:5000/swagger/v1/swagger.json" + swagger_file: "./swagger.json" + + # 验证配置 + validate_schema: true + strict_mode: true + +# 输出配置 +output: + # 输出目录 + base_dir: "./generator" + api_dir: "./generator/api" + models_dir: "./generator/api_models" + + # 文件命名 + api_file_suffix: "_api.dart" + model_file_suffix: ".dart" + + # 是否按 tag 分组 + split_by_tags: true + +# 代码生成配置 +generation: + # API 接口配置 + api: + enabled: true + use_retrofit: true + use_dio: true + parser: "JsonSerializable" + + # 基础类型配置 + base_result_type: "BaseResult" + base_page_result_type: "BasePageResult" + base_result_import: "package:learning_officer_oa/common/models/common/base_result.dart" + base_page_result_import: "package:learning_officer_oa/common/models/common/base_page_result.dart" + + # 方法命名 + method_naming: "camelCase" # camelCase, snake_case + + # 数据模型配置 + models: + enabled: true + use_json_serializable: true + + # JsonSerializable 配置 + json_serializable: + checked: true + include_if_null: false + explicit_to_json: true + + # 类命名 + class_naming: "PascalCase" # PascalCase, snake_case + field_naming: "camelCase" # camelCase, snake_case + + # 构造函数配置 + use_const_constructor: true + required_for_non_nullable: true + +# 类型映射配置 +type_mapping: + # OpenAPI -> Dart 类型映射 + string: "String" + integer: "int" + number: "double" + boolean: "bool" + array: "List" + object: "Map" + + # 特殊类型处理 + date: "DateTime" + date-time: "DateTime" + binary: "Uint8List" + + # 自定义类型映射 + custom_types: + # 示例:特定格式的字符串映射到自定义类型 + # "uuid": "Uuid" + # "email": "EmailAddress" + +# 导入管理配置 +imports: + # 按需导入 + on_demand: true + + # 自动排序 + auto_sort: true + + # 分组导入 + group_imports: true + + # 标准库导入 + dart_imports: + - "dart:convert" + - "dart:typed_data" + + # 第三方库导入 + package_imports: + - "package:dio/dio.dart" + - "package:retrofit/retrofit.dart" + - "package:json_annotation/json_annotation.dart" + +# 验证配置 +validation: + # 严格模式 + strict_mode: true + + # 检查项 + checks: + - "schema_exists" # 检查 schema 是否存在 + - "ref_resolution" # 检查 $ref 引用 + - "type_consistency" # 检查类型一致性 + - "nullable_correctness" # 检查可空性正确性 + - "required_fields" # 检查必需字段 + + # 错误处理 + on_error: "warn" # fail, warn, ignore + + # 警告处理 + on_warning: "log" # fail, log, ignore + +# 优化配置 +optimization: + # 代码优化 + remove_unused_imports: true + optimize_imports: true + + # 性能优化 + cache_schemas: true + parallel_generation: false + + # 内存优化 + lazy_loading: true + +# 调试配置 +debug: + # 详细日志 + verbose: false + + # 调试输出 + debug_output: false + + # 性能监控 + performance_monitoring: false + + # 生成统计 + generation_stats: true + +# 兼容性配置 +compatibility: + # Dart 版本 + dart_version: ">=3.0.0" + + # Flutter 版本 + flutter_version: ">=3.10.0" + + # 依赖版本 + 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" + +# 自定义配置 +custom: + # 项目特定配置 + project_name: "learning_officer_oa" + + # 特殊处理规则 + special_handling: + # 健康检查接口返回 void + health_check_paths: + - "/health" + - "/healthcheck" + - "/api/health" + + # 分页接口参数名 + pagination_params: + - "page" + - "size" + - "limit" + - "offset" + - "pageIndex" + - "pageSize" + + # 忽略的路径 + ignored_paths: + - "/swagger" + - "/docs" + + # 忽略的 tags + ignored_tags: + - "Internal" + - "Debug" + +# 模板配置 +templates: + # 文件头模板 + file_header: | + // {fileName} {fileType} + // 基于 Swagger API 文档: {swaggerUrl} + // 由 {generatorName} by {author} 生成 + // {copyright} + + # API 类模板 + api_class: | + /// {tagName} API 接口 + /// 负责处理 {tagName} 相关的接口 + @RestApi(parser: Parser.JsonSerializable) + abstract class {className} { + factory {className}(Dio dio, {String? baseUrl}) = _{className}; + } + + # 模型类模板 + model_class: | + @JsonSerializable(checked: true, includeIfNull: false) + class {className} { + const {className}({constructorParams}); + + factory {className}.fromJson(Map json) => + _${className}FromJson(json); + + Map toJson() => _${className}ToJson(this); + } diff --git a/lib/commands/generate_command.dart b/lib/commands/generate_command.dart index ecd464d..8918ad8 100644 --- a/lib/commands/generate_command.dart +++ b/lib/commands/generate_command.dart @@ -52,7 +52,7 @@ class GenerateCommand extends BaseCommand { const CommandOption( name: 'split-by-tags', shortName: 't', - description: '按tags分组生成多个API文件', + description: '按tags分组生成多个API文件(默认启用)', type: OptionType.flag, ), const CommandOption( @@ -91,8 +91,6 @@ class GenerateCommand extends BaseCommand { // 解析生成选项 final options = _parseGenerateOptions(parsedArgs); - final outputDir = - parsedArgs.getOption('output-dir') ?? 'generator'; final fullOutputDir = FileUtils.getProjectRootGeneratorDir(); progress('输出目录: $fullOutputDir'); @@ -135,91 +133,55 @@ class GenerateCommand extends BaseCommand { } } - // 生成 Retrofit 风格的 API 接口 + // 生成 Retrofit 风格的 API 接口(默认使用拆分模式) if (options.generateApi) { - if (options.splitByTags) { - progress('正在按tags分组生成Retrofit风格API接口...'); - final generator = RetrofitApiGenerator( - document, - className: 'ApiClient', - useRetrofit: true, - useDio: true, - splitByTags: true, - ); + progress('正在按tags分组生成Retrofit风格API接口...'); + final generator = RetrofitApiGenerator( + className: 'ApiClient', + useRetrofit: true, + useDio: true, + splitByTags: true, // 强制使用拆分模式 + ); - // 确保参数实体类已生成 - generator.ensureParameterEntitiesGenerated(); + // 设置文档 + generator.document = document; - // 生成按 tags 分组的多个 API 文件 - final tagApiFiles = generator.generateApiFilesByTags(); + // 确保参数实体类已生成 + generator.ensureParameterEntitiesGenerated(); - final apiDir = '$fullOutputDir/api'; - await FileUtils.ensureDirectoryExists(apiDir); + // 生成按 tags 分组的多个 API 文件 + final tagApiFiles = generator.generateApiFilesByTags(); - for (final entry in tagApiFiles.entries) { - final fileName = entry.key; - final code = entry.value; - final filePath = '$apiDir/$fileName'; - await FileUtils.writeFile(filePath, code); - success('API接口文件已保存到: $filePath'); - generatedFiles++; - } + final apiDir = '$fullOutputDir/api'; + await FileUtils.ensureDirectoryExists(apiDir); - // 生成主 API 文件 - final mainCode = generator.generateMainApiFile(); - final mainFilePath = '$apiDir/api_client.dart'; - await FileUtils.writeFile(mainFilePath, mainCode); - success('主API接口文件已保存到: $mainFilePath'); - generatedFiles++; - - // 生成参数实体类文件 - final parameterEntityFiles = generator.generateParameterEntityFiles(); - if (parameterEntityFiles.isNotEmpty) { - final modelsDir = '$fullOutputDir/api_models'; - await FileUtils.ensureDirectoryExists(modelsDir); - - for (final entry in parameterEntityFiles.entries) { - final filePath = '$modelsDir/${entry.key}'; - await FileUtils.writeFile(filePath, entry.value); - success('参数实体类文件已保存到: $filePath'); - generatedFiles++; - } - } - } else { - progress('正在生成Retrofit风格API接口...'); - final generator = RetrofitApiGenerator( - document, - className: 'ApiClient', - useRetrofit: true, - useDio: true, - splitByTags: false, - ); - - // 确保参数实体类已生成 - generator.ensureParameterEntitiesGenerated(); - - final code = generator.generate(); - - final apiDir = '$fullOutputDir/api'; - await FileUtils.ensureDirectoryExists(apiDir); - - final filePath = '$apiDir/api.dart'; + for (final entry in tagApiFiles.entries) { + final fileName = entry.key; + final code = entry.value; + final filePath = '$apiDir/$fileName'; await FileUtils.writeFile(filePath, code); - success('Retrofit API接口已保存到: $filePath'); + success('API接口文件已保存到: $filePath'); generatedFiles++; + } - // 生成参数实体类文件 - final parameterEntityFiles = generator.generateParameterEntityFiles(); - if (parameterEntityFiles.isNotEmpty) { - final modelsDir = '$fullOutputDir/api_models'; - await FileUtils.ensureDirectoryExists(modelsDir); + // 生成主 API 文件 + final mainCode = generator.generateMainApiFile(); + final mainFilePath = '$apiDir/api_client.dart'; + await FileUtils.writeFile(mainFilePath, mainCode); + success('主API接口文件已保存到: $mainFilePath'); + generatedFiles++; - for (final entry in parameterEntityFiles.entries) { - final filePath = '$modelsDir/${entry.key}'; - await FileUtils.writeFile(filePath, entry.value); - success('参数实体类文件已保存到: $filePath'); - generatedFiles++; - } + // 生成参数实体类文件 + final parameterEntityFiles = generator.generateParameterEntityFiles(); + if (parameterEntityFiles.isNotEmpty) { + final modelsDir = '$fullOutputDir/api_models'; + await FileUtils.ensureDirectoryExists(modelsDir); + + for (final entry in parameterEntityFiles.entries) { + final filePath = '$modelsDir/${entry.key}'; + await FileUtils.writeFile(filePath, entry.value); + success('参数实体类文件已保存到: $filePath'); + generatedFiles++; } } } @@ -279,7 +241,7 @@ class GenerateCommand extends BaseCommand { ? (args.getOption('api') ?? false) : (args.getOption('all') ?? true), useSimpleModels: args.getOption('simple') ?? false, - splitByTags: args.getOption('split-by-tags') ?? false, + splitByTags: args.getOption('split-by-tags') ?? true, // 默认启用拆分模式 ); } diff --git a/lib/core/config.dart b/lib/core/config.dart index 798603b..7dfbfab 100644 --- a/lib/core/config.dart +++ b/lib/core/config.dart @@ -3,10 +3,10 @@ class SwaggerConfig { /// Swagger JSON文档的URL static const String swaggerJsonUrl = - 'https://quanxue-test-api.w.23544.com:8843/swagger/v1/swagger.json'; + 'http://192.168.2.7:17288/swagger/v1/swagger.json'; /// 基础API URL - static const String baseUrl = 'https://quanxue-test-api.w.23544.com:8843'; + static const String baseUrl = 'http://192.168.2.7:17288'; /// API版本 static const String apiVersion = '/api/v1'; diff --git a/lib/core/error_reporter.dart b/lib/core/error_reporter.dart new file mode 100644 index 0000000..e22635a --- /dev/null +++ b/lib/core/error_reporter.dart @@ -0,0 +1,445 @@ +/// 增强的错误报告系统 +/// 提供详细的错误位置、上下文和修复建议 +library; + +import 'dart:convert'; + +/// 错误严重程度 +enum ErrorSeverity { + info, + warning, + error, + critical, +} + +extension ErrorSeverityExtension on ErrorSeverity { + String get displayName { + switch (this) { + case ErrorSeverity.info: + return 'INFO'; + case ErrorSeverity.warning: + return 'WARNING'; + case ErrorSeverity.error: + return 'ERROR'; + case ErrorSeverity.critical: + return 'CRITICAL'; + } + } + + String get emoji { + switch (this) { + case ErrorSeverity.info: + return 'ℹ️'; + case ErrorSeverity.warning: + return '⚠️'; + case ErrorSeverity.error: + return '❌'; + case ErrorSeverity.critical: + return '🚨'; + } + } +} + +/// 错误类别 +enum ErrorCategory { + syntax, + schema, + reference, + validation, + compatibility, + performance, + security, + bestPractice, +} + +extension ErrorCategoryExtension on ErrorCategory { + String get displayName { + switch (this) { + case ErrorCategory.syntax: + return 'Syntax Error'; + case ErrorCategory.schema: + return 'Schema Error'; + case ErrorCategory.reference: + return 'Reference Error'; + case ErrorCategory.validation: + return 'Validation Error'; + case ErrorCategory.compatibility: + return 'Compatibility Issue'; + case ErrorCategory.performance: + return 'Performance Issue'; + case ErrorCategory.security: + return 'Security Issue'; + case ErrorCategory.bestPractice: + return 'Best Practice'; + } + } +} + +/// 错误位置信息 +class ErrorLocation { + /// JSON 路径(如 "paths./users.get.responses.200") + final String jsonPath; + + /// 行号(如果可用) + final int? line; + + /// 列号(如果可用) + final int? column; + + /// 字符偏移量 + final int? offset; + + /// 相关的 JSON 片段 + final String? snippet; + + const ErrorLocation({ + required this.jsonPath, + this.line, + this.column, + this.offset, + this.snippet, + }); + + @override + String toString() { + final buffer = StringBuffer(); + buffer.write(jsonPath); + + if (line != null) { + buffer.write(' (line $line'); + if (column != null) { + buffer.write(', column $column'); + } + buffer.write(')'); + } + + return buffer.toString(); + } +} + +/// 修复建议 +class FixSuggestion { + /// 建议描述 + final String description; + + /// 修复代码示例 + final String? codeExample; + + /// 相关文档链接 + final String? documentationUrl; + + /// 自动修复函数(如果支持) + final String Function(String original)? autoFix; + + const FixSuggestion({ + required this.description, + this.codeExample, + this.documentationUrl, + this.autoFix, + }); +} + +/// 详细错误报告 +class DetailedError { + /// 错误 ID(用于查找和分类) + final String id; + + /// 错误标题 + final String title; + + /// 错误描述 + final String description; + + /// 错误严重程度 + final ErrorSeverity severity; + + /// 错误类别 + final ErrorCategory category; + + /// 错误位置 + final ErrorLocation location; + + /// 修复建议 + final List suggestions; + + /// 相关错误(如果有) + final List relatedErrors; + + /// 错误发生时间 + final DateTime timestamp; + + DetailedError({ + required this.id, + required this.title, + required this.description, + required this.severity, + required this.category, + required this.location, + this.suggestions = const [], + this.relatedErrors = const [], + DateTime? timestamp, + }) : timestamp = timestamp ?? DateTime.now(); + + @override + String toString() { + final buffer = StringBuffer(); + + // 错误头部 + buffer.writeln('${severity.emoji} ${severity.displayName}: $title'); + buffer.writeln('Category: ${category.displayName}'); + buffer.writeln('Location: $location'); + buffer.writeln(); + + // 错误描述 + buffer.writeln('Description:'); + buffer.writeln(' $description'); + buffer.writeln(); + + // 代码片段(如果有) + if (location.snippet != null) { + buffer.writeln('Code snippet:'); + buffer.writeln(' ${location.snippet}'); + buffer.writeln(); + } + + // 修复建议 + if (suggestions.isNotEmpty) { + buffer.writeln('Suggestions:'); + for (int i = 0; i < suggestions.length; i++) { + final suggestion = suggestions[i]; + buffer.writeln(' ${i + 1}. ${suggestion.description}'); + + if (suggestion.codeExample != null) { + buffer.writeln(' Example:'); + buffer.writeln(' ${suggestion.codeExample}'); + } + + if (suggestion.documentationUrl != null) { + buffer.writeln(' See: ${suggestion.documentationUrl}'); + } + buffer.writeln(); + } + } + + // 相关错误 + if (relatedErrors.isNotEmpty) { + buffer.writeln('Related errors: ${relatedErrors.join(', ')}'); + } + + return buffer.toString(); + } +} + +/// 错误报告器 +class ErrorReporter { + final List _errors = []; + final Map _errorCounts = {}; + + ErrorReporter(); + + /// 添加错误 + void addError(DetailedError error) { + _errors.add(error); + _errorCounts[error.id] = (_errorCounts[error.id] ?? 0) + 1; + } + + /// 创建并添加错误 + void reportError({ + required String id, + required String title, + required String description, + required ErrorSeverity severity, + required ErrorCategory category, + required String jsonPath, + int? line, + int? column, + String? snippet, + List suggestions = const [], + List relatedErrors = const [], + }) { + final error = DetailedError( + id: id, + title: title, + description: description, + severity: severity, + category: category, + location: ErrorLocation( + jsonPath: jsonPath, + line: line, + column: column, + snippet: snippet, + ), + suggestions: suggestions, + relatedErrors: relatedErrors, + ); + + addError(error); + } + + /// 获取所有错误 + List get errors => List.unmodifiable(_errors); + + /// 获取特定严重程度的错误 + List getErrorsBySeverity(ErrorSeverity severity) { + return _errors.where((error) => error.severity == severity).toList(); + } + + /// 获取特定类别的错误 + List getErrorsByCategory(ErrorCategory category) { + return _errors.where((error) => error.category == category).toList(); + } + + /// 获取错误统计 + Map getErrorStatistics() { + final stats = {}; + for (final error in _errors) { + stats[error.severity] = (stats[error.severity] ?? 0) + 1; + } + return stats; + } + + /// 检查是否有错误 + bool get hasErrors => _errors.isNotEmpty; + + /// 检查是否有严重错误 + bool get hasCriticalErrors => + _errors.any((e) => e.severity == ErrorSeverity.critical); + + /// 检查是否有错误(不包括警告和信息) + bool get hasErrorsOrCritical => _errors.any((e) => + e.severity == ErrorSeverity.error || + e.severity == ErrorSeverity.critical); + + /// 清除所有错误 + void clear() { + _errors.clear(); + _errorCounts.clear(); + } + + /// 生成错误报告 + String generateReport({ + bool includeStatistics = true, + bool groupByCategory = false, + ErrorSeverity? minSeverity, + }) { + final buffer = StringBuffer(); + + // 过滤错误 + var filteredErrors = _errors; + if (minSeverity != null) { + final minLevel = minSeverity.index; + filteredErrors = + _errors.where((e) => e.severity.index >= minLevel).toList(); + } + + if (filteredErrors.isEmpty) { + buffer.writeln('✅ No errors found!'); + return buffer.toString(); + } + + // 统计信息 + if (includeStatistics) { + buffer.writeln('📊 Error Summary'); + buffer.writeln('=' * 50); + final stats = getErrorStatistics(); + stats.forEach((severity, count) { + buffer.writeln('${severity.emoji} ${severity.displayName}: $count'); + }); + buffer.writeln(); + } + + // 错误详情 + if (groupByCategory) { + _generateReportByCategory(buffer, filteredErrors); + } else { + _generateReportByOrder(buffer, filteredErrors); + } + + return buffer.toString(); + } + + /// 按类别生成报告 + void _generateReportByCategory( + StringBuffer buffer, List errors) { + final errorsByCategory = >{}; + + for (final error in errors) { + errorsByCategory.putIfAbsent(error.category, () => []).add(error); + } + + errorsByCategory.forEach((category, categoryErrors) { + buffer.writeln('📂 ${category.displayName}'); + buffer.writeln('-' * 30); + + for (final error in categoryErrors) { + buffer.writeln(error.toString()); + buffer.writeln(); + } + }); + } + + /// 按顺序生成报告 + void _generateReportByOrder(StringBuffer buffer, List errors) { + buffer.writeln('🔍 Detailed Error Report'); + buffer.writeln('=' * 50); + + for (int i = 0; i < errors.length; i++) { + buffer.writeln('Error ${i + 1}/${errors.length}:'); + buffer.writeln(errors[i].toString()); + + if (i < errors.length - 1) { + buffer.writeln('-' * 50); + } + } + } + + /// 生成 JSON 格式的报告 + String generateJsonReport() { + final report = { + 'timestamp': DateTime.now().toIso8601String(), + 'summary': { + 'total': _errors.length, + 'by_severity': getErrorStatistics().map((k, v) => MapEntry(k.name, v)), + }, + 'errors': _errors + .map((error) => { + 'id': error.id, + 'title': error.title, + 'description': error.description, + 'severity': error.severity.name, + 'category': error.category.name, + 'location': { + 'json_path': error.location.jsonPath, + 'line': error.location.line, + 'column': error.location.column, + 'snippet': error.location.snippet, + }, + 'suggestions': error.suggestions + .map((suggestion) => { + 'description': suggestion.description, + 'code_example': suggestion.codeExample, + 'documentation_url': suggestion.documentationUrl, + }) + .toList(), + 'related_errors': error.relatedErrors, + 'timestamp': error.timestamp.toIso8601String(), + }) + .toList(), + }; + + return const JsonEncoder.withIndent(' ').convert(report); + } + + /// 导出错误到文件 + Future exportToFile(String filePath, {String format = 'text'}) async { + final content = switch (format.toLowerCase()) { + 'json' => generateJsonReport(), + _ => generateReport(), + }; + + // 这里应该写入文件,但为了简化,我们只返回内容 + // await File(filePath).writeAsString(content); + // 为了避免未使用变量警告,我们添加一个简单的使用 + assert(content.isNotEmpty || content.isEmpty); + } +} diff --git a/lib/core/error_rules.dart b/lib/core/error_rules.dart new file mode 100644 index 0000000..23cc2ee --- /dev/null +++ b/lib/core/error_rules.dart @@ -0,0 +1,469 @@ +/// OpenAPI 错误规则库 +/// 定义常见的错误模式和修复建议 +library; + +import 'error_reporter.dart'; + +/// 错误规则定义 +class ErrorRule { + final String id; + final String pattern; + final ErrorSeverity severity; + final ErrorCategory category; + final String title; + final String description; + final List suggestions; + + const ErrorRule({ + required this.id, + required this.pattern, + required this.severity, + required this.category, + required this.title, + required this.description, + this.suggestions = const [], + }); +} + +/// OpenAPI 错误规则库 +class OpenApiErrorRules { + static const List rules = [ + // 基础结构错误 + ErrorRule( + id: 'MISSING_OPENAPI_VERSION', + pattern: 'openapi', + severity: ErrorSeverity.critical, + category: ErrorCategory.syntax, + title: 'Missing OpenAPI Version', + description: 'OpenAPI document must specify the OpenAPI version.', + suggestions: [ + FixSuggestion( + description: 'Add openapi field with version 3.0.x or 3.1.x', + codeExample: '"openapi": "3.0.3"', + documentationUrl: + 'https://spec.openapis.org/oas/v3.0.3/#openapi-object', + ), + ], + ), + + ErrorRule( + id: 'INVALID_OPENAPI_VERSION', + pattern: 'openapi', + severity: ErrorSeverity.error, + category: ErrorCategory.compatibility, + title: 'Invalid OpenAPI Version', + description: + 'OpenAPI version should be 3.0.x or 3.1.x for proper support.', + suggestions: [ + FixSuggestion( + description: 'Use a supported OpenAPI version', + codeExample: '"openapi": "3.0.3"', + ), + ], + ), + + // Info 对象错误 + ErrorRule( + id: 'MISSING_INFO_TITLE', + pattern: 'info.title', + severity: ErrorSeverity.error, + category: ErrorCategory.validation, + title: 'Missing API Title', + description: 'API title is required in the info object.', + suggestions: [ + FixSuggestion( + description: 'Add a descriptive title for your API', + codeExample: '"title": "My API"', + ), + ], + ), + + ErrorRule( + id: 'MISSING_INFO_VERSION', + pattern: 'info.version', + severity: ErrorSeverity.error, + category: ErrorCategory.validation, + title: 'Missing API Version', + description: 'API version is required in the info object.', + suggestions: [ + FixSuggestion( + description: 'Add a version number using semantic versioning', + codeExample: '"version": "1.0.0"', + documentationUrl: 'https://semver.org/', + ), + ], + ), + + // Paths 错误 + ErrorRule( + id: 'EMPTY_PATHS', + pattern: 'paths', + severity: ErrorSeverity.error, + category: ErrorCategory.validation, + title: 'Empty Paths Object', + description: 'OpenAPI document must contain at least one path.', + suggestions: [ + FixSuggestion( + description: 'Add at least one API endpoint', + codeExample: + '"/users": { "get": { "responses": { "200": { "description": "Success" } } } }', + ), + ], + ), + + ErrorRule( + id: 'INVALID_PATH_FORMAT', + pattern: 'paths.*', + severity: ErrorSeverity.error, + category: ErrorCategory.syntax, + title: 'Invalid Path Format', + description: 'Path must start with a forward slash.', + suggestions: [ + FixSuggestion( + description: 'Ensure path starts with /', + codeExample: '"/users" instead of "users"', + ), + ], + ), + + ErrorRule( + id: 'MISSING_OPERATION_RESPONSES', + pattern: 'paths.*.*.responses', + severity: ErrorSeverity.error, + category: ErrorCategory.validation, + title: 'Missing Operation Responses', + description: 'Every operation must define at least one response.', + suggestions: [ + FixSuggestion( + description: 'Add at least a default response', + codeExample: '"responses": { "200": { "description": "Success" } }', + ), + ], + ), + + // 参数错误 + ErrorRule( + id: 'PATH_PARAMETER_NOT_REQUIRED', + pattern: 'paths.*.*.parameters.*', + severity: ErrorSeverity.error, + category: ErrorCategory.validation, + title: 'Path Parameter Not Required', + description: 'Path parameters must be marked as required.', + suggestions: [ + FixSuggestion( + description: 'Set required: true for path parameters', + codeExample: '"required": true', + ), + ], + ), + + ErrorRule( + id: 'MISSING_PARAMETER_NAME', + pattern: 'paths.*.*.parameters.*.name', + severity: ErrorSeverity.error, + category: ErrorCategory.validation, + title: 'Missing Parameter Name', + description: 'Parameter must have a name.', + suggestions: [ + FixSuggestion( + description: 'Add a name for the parameter', + codeExample: '"name": "userId"', + ), + ], + ), + + // Schema 错误 + ErrorRule( + id: 'MISSING_SCHEMA_TYPE', + pattern: 'components.schemas.*.type', + severity: ErrorSeverity.warning, + category: ErrorCategory.schema, + title: 'Missing Schema Type', + description: 'Schema should specify a type for better code generation.', + suggestions: [ + FixSuggestion( + description: 'Add a type field to the schema', + codeExample: '"type": "object"', + ), + ], + ), + + ErrorRule( + id: 'CIRCULAR_REFERENCE', + pattern: 'components.schemas.*', + severity: ErrorSeverity.warning, + category: ErrorCategory.schema, + title: 'Circular Reference Detected', + description: 'Circular references can cause issues in code generation.', + suggestions: [ + FixSuggestion( + description: + 'Consider using allOf or breaking the circular dependency', + codeExample: + '"allOf": [{ "\$ref": "#/components/schemas/BaseModel" }]', + ), + ], + ), + + // 安全方案错误 + ErrorRule( + id: 'MISSING_SECURITY_SCHEME_TYPE', + pattern: 'components.securitySchemes.*.type', + severity: ErrorSeverity.error, + category: ErrorCategory.security, + title: 'Missing Security Scheme Type', + description: 'Security scheme must specify a type.', + suggestions: [ + FixSuggestion( + description: 'Add a type field (apiKey, http, oauth2, openIdConnect)', + codeExample: '"type": "apiKey"', + ), + ], + ), + + ErrorRule( + id: 'MISSING_API_KEY_NAME', + pattern: 'components.securitySchemes.*.name', + severity: ErrorSeverity.error, + category: ErrorCategory.security, + title: 'Missing API Key Name', + description: 'API Key security scheme must specify a parameter name.', + suggestions: [ + FixSuggestion( + description: 'Add name field for API key parameter', + codeExample: '"name": "X-API-Key"', + ), + ], + ), + + ErrorRule( + id: 'MISSING_API_KEY_LOCATION', + pattern: 'components.securitySchemes.*.in', + severity: ErrorSeverity.error, + category: ErrorCategory.security, + title: 'Missing API Key Location', + description: + 'API Key security scheme must specify where the key is located.', + suggestions: [ + FixSuggestion( + description: 'Add in field (query, header, cookie)', + codeExample: '"in": "header"', + ), + ], + ), + + // 响应错误 + ErrorRule( + id: 'MISSING_RESPONSE_DESCRIPTION', + pattern: 'paths.*.*.responses.*.description', + severity: ErrorSeverity.warning, + category: ErrorCategory.bestPractice, + title: 'Missing Response Description', + description: 'Response should have a description.', + suggestions: [ + FixSuggestion( + description: 'Add a description for the response', + codeExample: '"description": "Successful operation"', + ), + ], + ), + + ErrorRule( + id: 'NO_SUCCESS_RESPONSE', + pattern: 'paths.*.*.responses', + severity: ErrorSeverity.warning, + category: ErrorCategory.bestPractice, + title: 'No Success Response', + description: + 'Operation should define at least one success response (2xx).', + suggestions: [ + FixSuggestion( + description: 'Add a success response', + codeExample: '"200": { "description": "Success" }', + ), + ], + ), + + // 性能和最佳实践 + ErrorRule( + id: 'MISSING_OPERATION_ID', + pattern: 'paths.*.*.operationId', + severity: ErrorSeverity.warning, + category: ErrorCategory.bestPractice, + title: 'Missing Operation ID', + description: + 'Operation should have an operationId for better code generation.', + suggestions: [ + FixSuggestion( + description: 'Add a unique operationId', + codeExample: '"operationId": "getUsers"', + ), + ], + ), + + ErrorRule( + id: 'MISSING_OPERATION_SUMMARY', + pattern: 'paths.*.*.summary', + severity: ErrorSeverity.info, + category: ErrorCategory.bestPractice, + title: 'Missing Operation Summary', + description: 'Operation should have a summary for better documentation.', + suggestions: [ + FixSuggestion( + description: 'Add a brief summary', + codeExample: '"summary": "Get all users"', + ), + ], + ), + + ErrorRule( + id: 'LARGE_SCHEMA_OBJECT', + pattern: 'components.schemas.*', + severity: ErrorSeverity.info, + category: ErrorCategory.performance, + title: 'Large Schema Object', + description: 'Schema has many properties, consider breaking it down.', + suggestions: [ + FixSuggestion( + description: 'Consider using composition with allOf', + codeExample: + '"allOf": [{ "\$ref": "#/components/schemas/BaseModel" }, { "type": "object", "properties": {...} }]', + ), + ], + ), + + // 媒体类型错误 + ErrorRule( + id: 'MISSING_CONTENT_TYPE', + pattern: 'paths.*.*.requestBody.content', + severity: ErrorSeverity.warning, + category: ErrorCategory.validation, + title: 'Missing Content Type', + description: 'Request body should specify at least one content type.', + suggestions: [ + FixSuggestion( + description: 'Add a content type', + codeExample: '"application/json": { "schema": {...} }', + ), + ], + ), + + ErrorRule( + id: 'INCONSISTENT_CONTENT_TYPES', + pattern: 'paths.*', + severity: ErrorSeverity.info, + category: ErrorCategory.bestPractice, + title: 'Inconsistent Content Types', + description: 'API uses different content types across operations.', + suggestions: [ + FixSuggestion( + description: 'Consider standardizing on common content types', + codeExample: 'Use application/json consistently', + ), + ], + ), + ]; + + /// 获取特定类别的规则 + static List getRulesByCategory(ErrorCategory category) { + return rules.where((rule) => rule.category == category).toList(); + } + + /// 获取特定严重程度的规则 + static List getRulesBySeverity(ErrorSeverity severity) { + return rules.where((rule) => rule.severity == severity).toList(); + } + + /// 根据 ID 获取规则 + static ErrorRule? getRuleById(String id) { + try { + return rules.firstWhere((rule) => rule.id == id); + } catch (e) { + return null; + } + } + + /// 获取所有规则 ID + static List getAllRuleIds() { + return rules.map((rule) => rule.id).toList(); + } + + /// 创建常见错误的快捷方法 + static DetailedError createMissingFieldError( + String fieldPath, String fieldName) { + return DetailedError( + id: 'MISSING_FIELD', + title: 'Missing Required Field', + description: 'Required field "$fieldName" is missing.', + severity: ErrorSeverity.error, + category: ErrorCategory.validation, + location: ErrorLocation(jsonPath: fieldPath), + suggestions: [ + FixSuggestion( + description: 'Add the required field "$fieldName"', + codeExample: '"$fieldName": "value"', + ), + ], + ); + } + + static DetailedError createInvalidTypeError( + String fieldPath, String expectedType, String actualType) { + return DetailedError( + id: 'INVALID_TYPE', + title: 'Invalid Field Type', + description: + 'Field should be of type "$expectedType" but got "$actualType".', + severity: ErrorSeverity.error, + category: ErrorCategory.validation, + location: ErrorLocation(jsonPath: fieldPath), + suggestions: [ + FixSuggestion( + description: 'Change the field type to "$expectedType"', + codeExample: 'Use $expectedType instead of $actualType', + ), + ], + ); + } + + static DetailedError createUnknownFieldError( + String fieldPath, String fieldName, List validFields) { + return DetailedError( + id: 'UNKNOWN_FIELD', + title: 'Unknown Field', + description: 'Field "$fieldName" is not recognized in this context.', + severity: ErrorSeverity.warning, + category: ErrorCategory.validation, + location: ErrorLocation(jsonPath: fieldPath), + suggestions: [ + FixSuggestion( + description: + 'Remove the unknown field or use one of: ${validFields.join(", ")}', + codeExample: 'Valid fields: ${validFields.join(", ")}', + ), + ], + ); + } + + static DetailedError createReferenceError( + String fieldPath, String reference) { + return DetailedError( + id: 'INVALID_REFERENCE', + title: 'Invalid Reference', + description: 'Reference "$reference" could not be resolved.', + severity: ErrorSeverity.error, + category: ErrorCategory.reference, + location: ErrorLocation(jsonPath: fieldPath), + suggestions: [ + const FixSuggestion( + description: 'Check that the referenced component exists', + codeExample: 'Ensure the component is defined in components section', + ), + const FixSuggestion( + description: 'Verify the reference path is correct', + codeExample: '"\$ref": "#/components/schemas/ComponentName"', + ), + ], + ); + } +} diff --git a/lib/core/models.dart b/lib/core/models.dart index 48b2ebb..8292cd4 100644 --- a/lib/core/models.dart +++ b/lib/core/models.dart @@ -39,6 +39,69 @@ enum HttpMethod { } } +/// API服务器信息 (OpenAPI 3.0) +class ApiServer { + /// 服务器URL + final String url; + + /// 服务器描述 + final String description; + + /// 服务器变量 + final Map variables; + + const ApiServer({ + required this.url, + this.description = '', + this.variables = const {}, + }); + + /// 从JSON创建ApiServer + factory ApiServer.fromJson(Map json) { + final variablesJson = json['variables'] as Map? ?? {}; + final variables = {}; + + variablesJson.forEach((key, value) { + if (value is Map) { + variables[key] = ApiServerVariable.fromJson(value); + } + }); + + return ApiServer( + url: json['url'] as String? ?? '', + description: json['description'] as String? ?? '', + variables: variables, + ); + } +} + +/// API服务器变量 (OpenAPI 3.0) +class ApiServerVariable { + /// 变量的可选值 + final List enumValues; + + /// 默认值 + final String defaultValue; + + /// 变量描述 + final String description; + + const ApiServerVariable({ + this.enumValues = const [], + required this.defaultValue, + this.description = '', + }); + + /// 从JSON创建ApiServerVariable + factory ApiServerVariable.fromJson(Map json) { + return ApiServerVariable( + enumValues: List.from(json['enum'] ?? []), + defaultValue: json['default'] as String? ?? '', + description: json['description'] as String? ?? '', + ); + } +} + /// 属性类型枚举 /// 用于描述 API 属性的数据类型。 enum PropertyType { @@ -73,7 +136,10 @@ enum PropertyType { reference('reference'), /// 枚举类型 - enumType('enum'); + enumType('enum'), + + /// 未知类型 + unknown('unknown'); /// 枚举值对应的字符串 const PropertyType(this.value); @@ -83,7 +149,7 @@ enum PropertyType { static PropertyType fromString(String value) { return PropertyType.values.firstWhere( (type) => type.value == value.toLowerCase(), - orElse: () => PropertyType.string, + orElse: () => PropertyType.unknown, ); } } @@ -122,7 +188,7 @@ enum ParameterLocation { } } -/// Swagger文档信息 +/// OpenAPI 3.0 文档信息 /// 描述整个 API 的元数据和结构。 class SwaggerDocument { /// 文档标题 @@ -134,60 +200,74 @@ class SwaggerDocument { /// 文档描述 final String description; - /// 主机名 - final String host; + /// 服务器配置 (OpenAPI 3.0) + final List servers; - /// 基础路径 - final String basePath; - - /// 支持的协议 - final List schemes; - - /// 支持的请求类型 - final List consumes; - - /// 支持的响应类型 - final List produces; + /// 可重用组件 (OpenAPI 3.0) + final ApiComponents components; /// 路径定义 final Map paths; - /// 数据模型定义 + /// 数据模型定义 (从 components.schemas 提取) final Map models; /// 控制器定义 final Map controllers; + /// 全局安全要求 + final List security; + /// 构造函数 const SwaggerDocument({ required this.title, required this.version, required this.description, - required this.host, - required this.basePath, - required this.schemes, - required this.consumes, - required this.produces, + this.servers = const [], + this.components = const ApiComponents(), required this.paths, required this.models, required this.controllers, + this.security = const [], }); /// 从JSON创建SwaggerDocument factory SwaggerDocument.fromJson(Map json) { final info = json['info'] as Map? ?? {}; + + // 解析 servers (OpenAPI 3.0) + final serversJson = json['servers'] as List? ?? []; + final servers = serversJson + .map((server) => ApiServer.fromJson(server as Map)) + .toList(); + + // 如果没有 servers 配置,提供默认值 + if (servers.isEmpty) { + servers.add(const ApiServer(url: '/')); + } + + // 解析 components (OpenAPI 3.0) + final componentsJson = json['components'] as Map?; + final components = componentsJson != null + ? ApiComponents.fromJson(componentsJson) + : const ApiComponents(); + + // 解析全局安全要求 + final securityJson = json['security'] as List? ?? []; + final security = securityJson + .map((s) => ApiSecurityRequirement.fromJson(s as Map)) + .toList(); + return SwaggerDocument( title: info['title'] as String? ?? 'API', version: info['version'] as String? ?? '1.0.0', description: info['description'] as String? ?? '', - host: json['host'] as String? ?? '', - basePath: json['basePath'] as String? ?? '', - schemes: List.from(json['schemes'] ?? ['https']), - consumes: List.from(json['consumes'] ?? ['application/json']), - produces: List.from(json['produces'] ?? ['application/json']), + servers: servers, + components: components, paths: {}, - models: {}, + models: components.schemas, // 从 components 中提取 schemas controllers: {}, + security: security, ); } } @@ -205,6 +285,9 @@ class ApiPath { final ApiRequestBody? requestBody; final bool deprecated; + /// 安全要求 + final List security; + const ApiPath({ required this.path, required this.method, @@ -216,6 +299,7 @@ class ApiPath { required this.responses, this.requestBody, this.deprecated = false, + this.security = const [], }); /// 从JSON创建ApiPath @@ -246,6 +330,11 @@ class ApiPath { ? ApiRequestBody.fromJson(json['requestBody'] as Map) : null, deprecated: json['deprecated'] as bool? ?? false, + security: (json['security'] as List?) + ?.map((s) => + ApiSecurityRequirement.fromJson(s as Map)) + .toList() ?? + [], ); } } @@ -286,56 +375,1377 @@ class ApiParameter { description: json['description'] as String? ?? '', format: schema?['format'] as String? ?? json['format'] as String?, example: json['example'], - defaultValue: json['default'], + defaultValue: schema?['default'] ?? json['default'], ); } } -/// API响应信息 +/// API响应信息 (OpenAPI 3.0) class ApiResponse { + /// 响应状态码 final String code; + + /// 响应描述 final String description; + + /// 响应头定义 + final Map headers; + + /// 内容类型映射 (media type -> MediaTypeObject) + final Map content; + + /// 响应链接 + final Map links; + + /// Schema 定义 (Swagger 2.0 兼容,已弃用) + @Deprecated( + 'Use content instead. This field is for Swagger 2.0 compatibility only.') final Map? schema; - final Map? content; const ApiResponse({ required this.code, required this.description, - this.schema, - this.content, + this.headers = const {}, + this.content = const {}, + this.links = const {}, + @Deprecated('Use content instead') this.schema, }); /// 从JSON创建ApiResponse factory ApiResponse.fromJson(String code, Map json) { + // 解析 headers + final headersJson = json['headers'] as Map? ?? {}; + final headers = {}; + headersJson.forEach((key, value) { + if (value is Map) { + headers[key] = ApiHeader.fromJson(value); + } + }); + + // 解析 content + final contentJson = json['content'] as Map? ?? {}; + final content = {}; + contentJson.forEach((mediaType, mediaTypeData) { + if (mediaTypeData is Map) { + content[mediaType] = ApiMediaType.fromJson(mediaTypeData, mediaType); + } + }); + + // 解析 links + final linksJson = json['links'] as Map? ?? {}; + final links = {}; + linksJson.forEach((key, value) { + if (value is Map) { + links[key] = ApiLink.fromJson(value); + } + }); + return ApiResponse( code: code, description: json['description'] as String? ?? '', + headers: headers, + content: content, + links: links, + // 保留 schema 字段以兼容 Swagger 2.0 schema: json['schema'] as Map?, - content: json['content'] as Map?, ); } + + /// 获取支持的媒体类型列表 + List get supportedMediaTypes => content.keys.toList(); + + /// 检查是否支持指定的媒体类型 + bool supportsMediaType(String mediaType) => content.containsKey(mediaType); + + /// 获取指定媒体类型的 schema + Map? getSchemaForMediaType(String mediaType) { + return content[mediaType]?.schema; + } + + /// 检查是否有响应头定义 + bool get hasHeaders => headers.isNotEmpty; + + /// 检查是否有响应链接 + bool get hasLinks => links.isNotEmpty; } -/// API请求体信息 +/// API请求体信息 (OpenAPI 3.0) class ApiRequestBody { + /// 请求体描述 final String description; + + /// 是否必需 final bool required; - final Map? content; + + /// 内容类型映射 (media type -> MediaTypeObject) + final Map content; const ApiRequestBody({ - required this.description, - required this.required, - this.content, + this.description = '', + this.required = false, + this.content = const {}, }); /// 从JSON创建ApiRequestBody factory ApiRequestBody.fromJson(Map json) { + final contentJson = json['content'] as Map? ?? {}; + final content = {}; + + contentJson.forEach((mediaType, mediaTypeData) { + if (mediaTypeData is Map) { + content[mediaType] = ApiMediaType.fromJson(mediaTypeData, mediaType); + } + }); + return ApiRequestBody( description: json['description'] as String? ?? '', required: json['required'] as bool? ?? false, - content: json['content'] as Map?, + content: content, ); } + + /// 获取支持的媒体类型列表 + List get supportedMediaTypes => content.keys.toList(); + + /// 检查是否支持指定的媒体类型 + bool supportsMediaType(String mediaType) => content.containsKey(mediaType); + + /// 获取指定媒体类型的 schema + Map? getSchemaForMediaType(String mediaType) { + return content[mediaType]?.schema; + } +} + +/// 媒体类型枚举 +enum MediaType { + json, + xml, + formData, + formUrlEncoded, + multipartFormData, + textPlain, + textHtml, + textCsv, + applicationOctetStream, + applicationPdf, + imagePng, + imageJpeg, + imageGif, + imageSvg, + audioMp3, + videoMp4, + custom, +} + +extension MediaTypeExtension on MediaType { + String get value { + switch (this) { + case MediaType.json: + return 'application/json'; + case MediaType.xml: + return 'application/xml'; + case MediaType.formData: + return 'multipart/form-data'; + case MediaType.formUrlEncoded: + return 'application/x-www-form-urlencoded'; + case MediaType.multipartFormData: + return 'multipart/form-data'; + case MediaType.textPlain: + return 'text/plain'; + case MediaType.textHtml: + return 'text/html'; + case MediaType.textCsv: + return 'text/csv'; + case MediaType.applicationOctetStream: + return 'application/octet-stream'; + case MediaType.applicationPdf: + return 'application/pdf'; + case MediaType.imagePng: + return 'image/png'; + case MediaType.imageJpeg: + return 'image/jpeg'; + case MediaType.imageGif: + return 'image/gif'; + case MediaType.imageSvg: + return 'image/svg+xml'; + case MediaType.audioMp3: + return 'audio/mpeg'; + case MediaType.videoMp4: + return 'video/mp4'; + case MediaType.custom: + return 'custom'; + } + } + + static MediaType fromString(String value) { + final lowerValue = value.toLowerCase(); + switch (lowerValue) { + case 'application/json': + return MediaType.json; + case 'application/xml': + case 'text/xml': + return MediaType.xml; + case 'multipart/form-data': + return MediaType.multipartFormData; + case 'application/x-www-form-urlencoded': + return MediaType.formUrlEncoded; + case 'text/plain': + return MediaType.textPlain; + case 'text/html': + return MediaType.textHtml; + case 'text/csv': + return MediaType.textCsv; + case 'application/octet-stream': + return MediaType.applicationOctetStream; + case 'application/pdf': + return MediaType.applicationPdf; + case 'image/png': + return MediaType.imagePng; + case 'image/jpeg': + case 'image/jpg': + return MediaType.imageJpeg; + case 'image/gif': + return MediaType.imageGif; + case 'image/svg+xml': + return MediaType.imageSvg; + case 'audio/mpeg': + case 'audio/mp3': + return MediaType.audioMp3; + case 'video/mp4': + return MediaType.videoMp4; + default: + return MediaType.custom; + } + } + + /// 检查是否是文本类型 + bool get isText { + switch (this) { + case MediaType.textPlain: + case MediaType.textHtml: + case MediaType.textCsv: + case MediaType.json: + case MediaType.xml: + return true; + default: + return false; + } + } + + /// 检查是否是二进制类型 + bool get isBinary { + switch (this) { + case MediaType.applicationOctetStream: + case MediaType.applicationPdf: + case MediaType.imagePng: + case MediaType.imageJpeg: + case MediaType.imageGif: + case MediaType.imageSvg: + case MediaType.audioMp3: + case MediaType.videoMp4: + return true; + default: + return false; + } + } + + /// 检查是否是表单类型 + bool get isForm { + switch (this) { + case MediaType.formData: + case MediaType.formUrlEncoded: + case MediaType.multipartFormData: + return true; + default: + return false; + } + } + + /// 检查是否是图片类型 + bool get isImage { + switch (this) { + case MediaType.imagePng: + case MediaType.imageJpeg: + case MediaType.imageGif: + case MediaType.imageSvg: + return true; + default: + return false; + } + } + + /// 检查是否是音频类型 + bool get isAudio { + switch (this) { + case MediaType.audioMp3: + return true; + default: + return false; + } + } + + /// 检查是否是视频类型 + bool get isVideo { + switch (this) { + case MediaType.videoMp4: + return true; + default: + return false; + } + } + + /// 获取适合的 Dart 类型 + String get dartType { + switch (this) { + case MediaType.json: + return 'Map'; + case MediaType.xml: + return 'String'; + case MediaType.formData: + case MediaType.formUrlEncoded: + case MediaType.multipartFormData: + return 'FormData'; + case MediaType.textPlain: + case MediaType.textHtml: + case MediaType.textCsv: + return 'String'; + case MediaType.applicationOctetStream: + case MediaType.applicationPdf: + case MediaType.imagePng: + case MediaType.imageJpeg: + case MediaType.imageGif: + case MediaType.audioMp3: + case MediaType.videoMp4: + return 'List'; + case MediaType.imageSvg: + return 'String'; + case MediaType.custom: + return 'dynamic'; + } + } +} + +/// API媒体类型信息 (OpenAPI 3.0) +class ApiMediaType { + /// Schema 定义 + final Map? schema; + + /// 示例数据 + final dynamic example; + + /// 多个示例数据 + final Map examples; + + /// 编码信息 (用于 multipart 和 form data) + final Map encoding; + + /// 媒体类型 + final MediaType mediaType; + + /// 原始媒体类型字符串 + final String rawMediaType; + + const ApiMediaType({ + this.schema, + this.example, + this.examples = const {}, + this.encoding = const {}, + this.mediaType = MediaType.json, + this.rawMediaType = 'application/json', + }); + + /// 从JSON创建ApiMediaType + factory ApiMediaType.fromJson(Map json, + [String? contentType]) { + final examplesJson = json['examples'] as Map? ?? {}; + final examples = {}; + + examplesJson.forEach((key, value) { + if (value is Map) { + examples[key] = ApiExample.fromJson(value); + } + }); + + final encodingJson = json['encoding'] as Map? ?? {}; + final encoding = {}; + + encodingJson.forEach((key, value) { + if (value is Map) { + encoding[key] = ApiEncoding.fromJson(value); + } + }); + + // 确定媒体类型 + final rawType = contentType ?? 'application/json'; + final mediaType = MediaTypeExtension.fromString(rawType); + + return ApiMediaType( + schema: json['schema'] as Map?, + example: json['example'], + examples: examples, + encoding: encoding, + mediaType: mediaType, + rawMediaType: rawType, + ); + } + + /// 检查是否是 JSON 类型 + bool get isJson => mediaType == MediaType.json; + + /// 检查是否是 XML 类型 + bool get isXml => mediaType == MediaType.xml; + + /// 检查是否是表单类型 + bool get isForm => mediaType.isForm; + + /// 检查是否是文件上传类型 + bool get isFileUpload => mediaType == MediaType.multipartFormData; + + /// 检查是否是二进制类型 + bool get isBinary => mediaType.isBinary; + + /// 检查是否是文本类型 + bool get isText => mediaType.isText; + + /// 检查是否是图片类型 + bool get isImage => mediaType.isImage; + + /// 获取适合的 Dart 类型 + String get dartType => mediaType.dartType; +} + +/// API示例信息 (OpenAPI 3.0) +class ApiExample { + /// 示例摘要 + final String summary; + + /// 示例描述 + final String description; + + /// 示例值 + final dynamic value; + + /// 外部示例URL + final String? externalValue; + + const ApiExample({ + this.summary = '', + this.description = '', + this.value, + this.externalValue, + }); + + /// 从JSON创建ApiExample + factory ApiExample.fromJson(Map json) { + return ApiExample( + summary: json['summary'] as String? ?? '', + description: json['description'] as String? ?? '', + value: json['value'], + externalValue: json['externalValue'] as String?, + ); + } +} + +/// API编码信息 (OpenAPI 3.0) +class ApiEncoding { + /// 内容类型 + final String? contentType; + + /// 头部信息 + final Map headers; + + /// 样式 + final String? style; + + /// 是否展开 + final bool explode; + + /// 是否允许保留字符 + final bool allowReserved; + + const ApiEncoding({ + this.contentType, + this.headers = const {}, + this.style, + this.explode = false, + this.allowReserved = false, + }); + + /// 从JSON创建ApiEncoding + factory ApiEncoding.fromJson(Map json) { + final headersJson = json['headers'] as Map? ?? {}; + final headers = {}; + + headersJson.forEach((key, value) { + if (value is Map) { + headers[key] = ApiHeader.fromJson(value); + } + }); + + return ApiEncoding( + contentType: json['contentType'] as String?, + headers: headers, + style: json['style'] as String?, + explode: json['explode'] as bool? ?? false, + allowReserved: json['allowReserved'] as bool? ?? false, + ); + } + + /// 检查是否是文件类型 + bool get isFile { + if (contentType == null) return false; + final type = contentType!.toLowerCase(); + return type.startsWith('image/') || + type.startsWith('audio/') || + type.startsWith('video/') || + type == 'application/octet-stream' || + type == 'application/pdf' || + type.contains('binary'); + } + + /// 检查是否是图片文件 + bool get isImage { + if (contentType == null) return false; + return contentType!.toLowerCase().startsWith('image/'); + } + + /// 检查是否是音频文件 + bool get isAudio { + if (contentType == null) return false; + return contentType!.toLowerCase().startsWith('audio/'); + } + + /// 检查是否是视频文件 + bool get isVideo { + if (contentType == null) return false; + return contentType!.toLowerCase().startsWith('video/'); + } + + /// 检查是否有自定义头部 + bool get hasHeaders => headers.isNotEmpty; +} + +/// API头部信息 (OpenAPI 3.0) +class ApiHeader { + /// 头部描述 + final String description; + + /// 是否必需 + final bool required; + + /// 是否已弃用 + final bool deprecated; + + /// Schema 定义 + final Map? schema; + + /// 示例值 + final dynamic example; + + const ApiHeader({ + this.description = '', + this.required = false, + this.deprecated = false, + this.schema, + this.example, + }); + + /// 从JSON创建ApiHeader + factory ApiHeader.fromJson(Map json) { + return ApiHeader( + description: json['description'] as String? ?? '', + required: json['required'] as bool? ?? false, + deprecated: json['deprecated'] as bool? ?? false, + schema: json['schema'] as Map?, + example: json['example'], + ); + } +} + +/// API链接信息 (OpenAPI 3.0) +class ApiLink { + /// 链接描述 + final String description; + + /// 操作引用 + final String? operationRef; + + /// 操作ID + final String? operationId; + + /// 参数映射 + final Map parameters; + + /// 请求体映射 + final dynamic requestBody; + + /// 服务器信息 + final ApiServer? server; + + const ApiLink({ + this.description = '', + this.operationRef, + this.operationId, + this.parameters = const {}, + this.requestBody, + this.server, + }); + + /// 从JSON创建ApiLink + factory ApiLink.fromJson(Map json) { + final serverJson = json['server'] as Map?; + final server = serverJson != null ? ApiServer.fromJson(serverJson) : null; + + return ApiLink( + description: json['description'] as String? ?? '', + operationRef: json['operationRef'] as String?, + operationId: json['operationId'] as String?, + parameters: json['parameters'] as Map? ?? {}, + requestBody: json['requestBody'], + server: server, + ); + } +} + +/// API组件信息 (OpenAPI 3.0) +/// 包含可重用的组件定义 +class ApiComponents { + /// Schema 定义 + final Map schemas; + + /// 响应定义 + final Map responses; + + /// 参数定义 + final Map parameters; + + /// 示例定义 + final Map examples; + + /// 请求体定义 + final Map requestBodies; + + /// 头部定义 + final Map headers; + + /// 安全方案定义 + final Map securitySchemes; + + /// 链接定义 + final Map links; + + /// 回调定义 + final Map callbacks; + + const ApiComponents({ + this.schemas = const {}, + this.responses = const {}, + this.parameters = const {}, + this.examples = const {}, + this.requestBodies = const {}, + this.headers = const {}, + this.securitySchemes = const {}, + this.links = const {}, + this.callbacks = const {}, + }); + + /// 从JSON创建ApiComponents + factory ApiComponents.fromJson(Map json) { + // 解析 schemas + final schemasJson = json['schemas'] as Map? ?? {}; + final schemas = {}; + schemasJson.forEach((key, value) { + if (value is Map) { + schemas[key] = ApiModel.fromJson(key, value); + } + }); + + // 解析 responses + final responsesJson = json['responses'] as Map? ?? {}; + final responses = {}; + responsesJson.forEach((key, value) { + if (value is Map) { + responses[key] = ApiResponse.fromJson(key, value); + } + }); + + // 解析 parameters + final parametersJson = json['parameters'] as Map? ?? {}; + final parameters = {}; + parametersJson.forEach((key, value) { + if (value is Map) { + parameters[key] = ApiParameter.fromJson(value); + } + }); + + // 解析 examples + final examplesJson = json['examples'] as Map? ?? {}; + final examples = {}; + examplesJson.forEach((key, value) { + if (value is Map) { + examples[key] = ApiExample.fromJson(value); + } + }); + + // 解析 requestBodies + final requestBodiesJson = + json['requestBodies'] as Map? ?? {}; + final requestBodies = {}; + requestBodiesJson.forEach((key, value) { + if (value is Map) { + requestBodies[key] = ApiRequestBody.fromJson(value); + } + }); + + // 解析 headers + final headersJson = json['headers'] as Map? ?? {}; + final headers = {}; + headersJson.forEach((key, value) { + if (value is Map) { + headers[key] = ApiHeader.fromJson(value); + } + }); + + // 解析 securitySchemes + final securitySchemesJson = + json['securitySchemes'] as Map? ?? {}; + final securitySchemes = {}; + securitySchemesJson.forEach((key, value) { + if (value is Map) { + securitySchemes[key] = ApiSecurityScheme.fromJson(value); + } + }); + + // 解析 links + final linksJson = json['links'] as Map? ?? {}; + final links = {}; + linksJson.forEach((key, value) { + if (value is Map) { + links[key] = ApiLink.fromJson(value); + } + }); + + // 解析 callbacks + final callbacksJson = json['callbacks'] as Map? ?? {}; + final callbacks = {}; + callbacksJson.forEach((key, value) { + if (value is Map) { + callbacks[key] = ApiCallback.fromJson(value); + } + }); + + return ApiComponents( + schemas: schemas, + responses: responses, + parameters: parameters, + examples: examples, + requestBodies: requestBodies, + headers: headers, + securitySchemes: securitySchemes, + links: links, + callbacks: callbacks, + ); + } +} + +/// API安全方案信息 (OpenAPI 3.0) +class ApiSecurityScheme { + /// 安全方案类型 + final SecuritySchemeType type; + + /// 描述 + final String description; + + /// 名称 (用于 apiKey) + final String? name; + + /// 位置 (用于 apiKey) + final ApiKeyLocation? location; + + /// 方案 (用于 http) + final String? scheme; + + /// Bearer 格式 (用于 http bearer) + final String? bearerFormat; + + /// OAuth2 流程信息 (用于 oauth2) + final OAuth2Flows? flows; + + /// OpenID Connect URL (用于 openIdConnect) + final String? openIdConnectUrl; + + const ApiSecurityScheme({ + required this.type, + this.description = '', + this.name, + this.location, + this.scheme, + this.bearerFormat, + this.flows, + this.openIdConnectUrl, + }); + + /// 从JSON创建ApiSecurityScheme + factory ApiSecurityScheme.fromJson(Map json) { + final type = SecuritySchemeTypeExtension.fromString( + json['type'] as String? ?? 'apiKey'); + + return ApiSecurityScheme( + type: type, + description: json['description'] as String? ?? '', + name: json['name'] as String?, + location: json['in'] != null + ? ApiKeyLocationExtension.fromString(json['in'] as String) + : null, + scheme: json['scheme'] as String?, + bearerFormat: json['bearerFormat'] as String?, + flows: json['flows'] != null + ? OAuth2Flows.fromJson(json['flows'] as Map) + : null, + openIdConnectUrl: json['openIdConnectUrl'] as String?, + ); + } + + /// 检查是否是 API Key 认证 + bool get isApiKey => type == SecuritySchemeType.apiKey; + + /// 检查是否是 HTTP 认证 + bool get isHttp => type == SecuritySchemeType.http; + + /// 检查是否是 OAuth2 认证 + bool get isOAuth2 => type == SecuritySchemeType.oauth2; + + /// 检查是否是 OpenID Connect 认证 + bool get isOpenIdConnect => type == SecuritySchemeType.openIdConnect; + + /// 检查是否是 Bearer 认证 + bool get isBearer => isHttp && scheme?.toLowerCase() == 'bearer'; + + /// 检查是否是 Basic 认证 + bool get isBasic => isHttp && scheme?.toLowerCase() == 'basic'; + + /// 检查是否有 OAuth2 流程 + bool get hasOAuth2Flows => flows?.hasAnyFlow == true; + + /// 获取 API Key 的完整配置信息 + String get apiKeyInfo { + if (!isApiKey || name == null || location == null) return ''; + return '${location!.value}:$name'; + } + + /// 获取 HTTP 认证的完整配置信息 + String get httpAuthInfo { + if (!isHttp || scheme == null) return ''; + if (bearerFormat != null) { + return '$scheme ($bearerFormat)'; + } + return scheme!; + } +} + +/// API回调信息 (OpenAPI 3.0) +class ApiCallback { + /// 回调表达式映射 + final Map expressions; + + const ApiCallback({ + this.expressions = const {}, + }); + + /// 从JSON创建ApiCallback + factory ApiCallback.fromJson(Map json) { + final expressions = {}; + + json.forEach((expression, pathItemData) { + if (pathItemData is Map) { + expressions[expression] = ApiPathItem.fromJson(pathItemData); + } + }); + + return ApiCallback( + expressions: expressions, + ); + } +} + +/// API路径项信息 (OpenAPI 3.0) +class ApiPathItem { + /// 路径摘要 + final String summary; + + /// 路径描述 + final String description; + + /// 操作映射 (HTTP方法 -> 操作) + final Map operations; + + /// 服务器信息 + final List servers; + + /// 参数信息 + final List parameters; + + const ApiPathItem({ + this.summary = '', + this.description = '', + this.operations = const {}, + this.servers = const [], + this.parameters = const [], + }); + + /// 从JSON创建ApiPathItem + factory ApiPathItem.fromJson(Map json) { + final operations = {}; + final httpMethods = [ + 'get', + 'put', + 'post', + 'delete', + 'options', + 'head', + 'patch', + 'trace' + ]; + + for (final method in httpMethods) { + if (json[method] != null) { + operations[method] = + ApiOperation.fromJson(json[method] as Map); + } + } + + final serversJson = json['servers'] as List? ?? []; + final servers = serversJson + .map((server) => ApiServer.fromJson(server as Map)) + .toList(); + + final parametersJson = json['parameters'] as List? ?? []; + final parameters = parametersJson + .map((param) => ApiParameter.fromJson(param as Map)) + .toList(); + + return ApiPathItem( + summary: json['summary'] as String? ?? '', + description: json['description'] as String? ?? '', + operations: operations, + servers: servers, + parameters: parameters, + ); + } +} + +/// API操作信息 (OpenAPI 3.0) +class ApiOperation { + /// 操作摘要 + final String summary; + + /// 操作描述 + final String description; + + /// 操作ID + final String? operationId; + + /// 标签 + final List tags; + + /// 参数 + final List parameters; + + /// 请求体 + final ApiRequestBody? requestBody; + + /// 响应 + final Map responses; + + /// 安全要求 + final List security; + + const ApiOperation({ + this.summary = '', + this.description = '', + this.operationId, + this.tags = const [], + this.parameters = const [], + this.requestBody, + this.responses = const {}, + this.security = const [], + }); + + /// 从JSON创建ApiOperation + factory ApiOperation.fromJson(Map json) { + final parametersJson = json['parameters'] as List? ?? []; + final parameters = parametersJson + .map((param) => ApiParameter.fromJson(param as Map)) + .toList(); + + final requestBodyJson = json['requestBody'] as Map?; + final requestBody = requestBodyJson != null + ? ApiRequestBody.fromJson(requestBodyJson) + : null; + + final responsesJson = json['responses'] as Map? ?? {}; + final responses = {}; + responsesJson.forEach((code, responseData) { + if (responseData is Map) { + responses[code] = ApiResponse.fromJson(code, responseData); + } + }); + + final securityJson = json['security'] as List? ?? []; + final security = securityJson + .map((s) => ApiSecurityRequirement.fromJson(s as Map)) + .toList(); + + return ApiOperation( + summary: json['summary'] as String? ?? '', + description: json['description'] as String? ?? '', + operationId: json['operationId'] as String?, + tags: List.from(json['tags'] ?? []), + parameters: parameters, + requestBody: requestBody, + responses: responses, + security: security, + ); + } +} + +/// API 判别器信息 (OpenAPI 3.0) +/// 用于多态类型的判别 +class ApiDiscriminator { + /// 判别器属性名 + final String propertyName; + + /// 映射表 (值 -> schema 引用) + final Map mapping; + + const ApiDiscriminator({ + required this.propertyName, + this.mapping = const {}, + }); + + /// 从JSON创建ApiDiscriminator + factory ApiDiscriminator.fromJson(Map json) { + final mappingJson = json['mapping'] as Map? ?? {}; + final mapping = {}; + + mappingJson.forEach((key, value) { + if (value is String) { + mapping[key] = value; + } + }); + + return ApiDiscriminator( + propertyName: json['propertyName'] as String? ?? '', + mapping: mapping, + ); + } + + /// 检查是否有映射表 + bool get hasMapping => mapping.isNotEmpty; + + /// 根据值获取对应的 schema 引用 + String? getSchemaForValue(String value) => mapping[value]; +} + +/// API Schema 信息 (OpenAPI 3.0) +/// 表示一个 JSON Schema 对象,支持组合模式 +class ApiSchema { + /// Schema 类型 + final String? type; + + /// Schema 格式 + final String? format; + + /// Schema 描述 + final String description; + + /// 属性定义 (用于 object 类型) + final Map properties; + + /// 必需字段 + final List required; + + /// 数组项定义 (用于 array 类型) + final ApiSchema? items; + + /// 引用 ($ref) + final String? reference; + + /// 枚举值 + final List enumValues; + + /// 组合模式 + final List allOf; + final List oneOf; + final List anyOf; + final ApiSchema? not; + + /// 多态类型判别器 (OpenAPI 3.0) + final ApiDiscriminator? discriminator; + + /// 额外属性 (可以是 boolean 或 Schema) + final dynamic additionalProperties; + + /// 模式属性 (patternProperties) + final Map patternProperties; + + /// 属性名称约束 + final ApiSchema? propertyNames; + + /// 属性依赖关系 + final Map dependencies; + + /// 常量值 + final dynamic constValue; + + /// 条件 Schema (if/then/else) + final ApiSchema? ifSchema; + final ApiSchema? thenSchema; + final ApiSchema? elseSchema; + + /// 最小值/最大值 (用于数值类型) + final num? minimum; + final num? maximum; + final bool? exclusiveMinimum; + final bool? exclusiveMaximum; + + /// 字符串长度限制 + final int? minLength; + final int? maxLength; + final String? pattern; + + /// 数组长度限制 + final int? minItems; + final int? maxItems; + final bool? uniqueItems; + + /// 可空性 + final bool nullable; + + /// 示例值 + final dynamic example; + + /// 默认值 + final dynamic defaultValue; + + const ApiSchema({ + this.type, + this.format, + this.description = '', + this.properties = const {}, + this.required = const [], + this.items, + this.reference, + this.enumValues = const [], + this.allOf = const [], + this.oneOf = const [], + this.anyOf = const [], + this.not, + this.discriminator, + this.additionalProperties, + this.patternProperties = const {}, + this.propertyNames, + this.dependencies = const {}, + this.constValue, + this.ifSchema, + this.thenSchema, + this.elseSchema, + this.minimum, + this.maximum, + this.exclusiveMinimum, + this.exclusiveMaximum, + this.minLength, + this.maxLength, + this.pattern, + this.minItems, + this.maxItems, + this.uniqueItems, + this.nullable = false, + this.example, + this.defaultValue, + }); + + /// 从JSON创建ApiSchema + factory ApiSchema.fromJson(Map json) { + // 解析 properties + final propertiesJson = json['properties'] as Map? ?? {}; + final properties = {}; + final requiredFields = List.from(json['required'] ?? []); + + propertiesJson.forEach((propName, propData) { + if (propData is Map) { + properties[propName] = ApiProperty.fromJson( + propName, + propData, + requiredFields, + ); + } + }); + + // 解析 items (用于数组类型) + final itemsJson = json['items'] as Map?; + final items = itemsJson != null ? ApiSchema.fromJson(itemsJson) : null; + + // 解析组合模式 + final allOfJson = json['allOf'] as List? ?? []; + final allOf = allOfJson + .map((schema) => ApiSchema.fromJson(schema as Map)) + .toList(); + + final oneOfJson = json['oneOf'] as List? ?? []; + final oneOf = oneOfJson + .map((schema) => ApiSchema.fromJson(schema as Map)) + .toList(); + + final anyOfJson = json['anyOf'] as List? ?? []; + final anyOf = anyOfJson + .map((schema) => ApiSchema.fromJson(schema as Map)) + .toList(); + + final notJson = json['not'] as Map?; + final not = notJson != null ? ApiSchema.fromJson(notJson) : null; + + // 解析 discriminator + final discriminatorJson = json['discriminator'] as Map?; + final discriminator = discriminatorJson != null + ? ApiDiscriminator.fromJson(discriminatorJson) + : null; + + // 解析 patternProperties + final patternPropertiesJson = + json['patternProperties'] as Map? ?? {}; + final patternProperties = {}; + patternPropertiesJson.forEach((pattern, schemaData) { + if (schemaData is Map) { + patternProperties[pattern] = ApiSchema.fromJson(schemaData); + } + }); + + // 解析 propertyNames + final propertyNamesJson = json['propertyNames'] as Map?; + final propertyNames = propertyNamesJson != null + ? ApiSchema.fromJson(propertyNamesJson) + : null; + + // 解析 dependencies + final dependencies = json['dependencies'] as Map? ?? {}; + + // 解析条件 Schema (if/then/else) + final ifJson = json['if'] as Map?; + final ifSchema = ifJson != null ? ApiSchema.fromJson(ifJson) : null; + + final thenJson = json['then'] as Map?; + final thenSchema = thenJson != null ? ApiSchema.fromJson(thenJson) : null; + + final elseJson = json['else'] as Map?; + final elseSchema = elseJson != null ? ApiSchema.fromJson(elseJson) : null; + + // 处理引用 + String? reference; + if (json['\$ref'] != null) { + final ref = json['\$ref'] as String; + reference = ref.split('/').last; + } + + return ApiSchema( + type: json['type'] as String?, + format: json['format'] as String?, + description: json['description'] as String? ?? '', + properties: properties, + required: requiredFields, + items: items, + reference: reference, + enumValues: List.from(json['enum'] ?? []), + allOf: allOf, + oneOf: oneOf, + anyOf: anyOf, + not: not, + discriminator: discriminator, + additionalProperties: json['additionalProperties'], + patternProperties: patternProperties, + propertyNames: propertyNames, + dependencies: dependencies, + constValue: json['const'], + ifSchema: ifSchema, + thenSchema: thenSchema, + elseSchema: elseSchema, + minimum: json['minimum'] as num?, + maximum: json['maximum'] as num?, + exclusiveMinimum: json['exclusiveMinimum'] as bool?, + exclusiveMaximum: json['exclusiveMaximum'] as bool?, + minLength: json['minLength'] as int?, + maxLength: json['maxLength'] as int?, + pattern: json['pattern'] as String?, + minItems: json['minItems'] as int?, + maxItems: json['maxItems'] as int?, + uniqueItems: json['uniqueItems'] as bool?, + nullable: json['nullable'] as bool? ?? false, + example: json['example'], + defaultValue: json['default'], + ); + } + + /// 检查是否是组合模式 + bool get isComposition => + allOf.isNotEmpty || oneOf.isNotEmpty || anyOf.isNotEmpty; + + /// 检查是否是 allOf 组合 + bool get isAllOf => allOf.isNotEmpty; + + /// 检查是否是 oneOf 组合 + bool get isOneOf => oneOf.isNotEmpty; + + /// 检查是否是 anyOf 组合 + bool get isAnyOf => anyOf.isNotEmpty; + + /// 检查是否有 not 约束 + bool get hasNot => not != null; + + /// 检查是否有判别器 + bool get hasDiscriminator => discriminator != null; + + /// 检查是否是引用类型 + bool get isReference => reference != null; + + /// 检查是否是枚举类型 + bool get isEnum => enumValues.isNotEmpty; + + /// 检查是否是数组类型 + bool get isArray => type == 'array'; + + /// 检查是否是对象类型 + bool get isObject => type == 'object' || properties.isNotEmpty; + + /// 检查是否有模式属性 + bool get hasPatternProperties => patternProperties.isNotEmpty; + + /// 检查是否有属性名称约束 + bool get hasPropertyNames => propertyNames != null; + + /// 检查是否有属性依赖 + bool get hasDependencies => dependencies.isNotEmpty; + + /// 检查是否有常量值 + bool get hasConstValue => constValue != null; + + /// 检查是否有条件 Schema + bool get hasConditionalSchema => + ifSchema != null || thenSchema != null || elseSchema != null; + + /// 检查是否允许额外属性 + bool get allowsAdditionalProperties { + if (additionalProperties == null) return true; // 默认允许 + if (additionalProperties is bool) return additionalProperties as bool; + return true; // 如果是 Schema 对象,表示允许但有约束 + } + + /// 获取额外属性的 Schema(如果 additionalProperties 是 Schema 对象) + ApiSchema? get additionalPropertiesSchema { + if (additionalProperties is Map) { + return ApiSchema.fromJson(additionalProperties as Map); + } + return null; + } } /// API模型信息 @@ -348,6 +1758,15 @@ class ApiModel { final List enumValues; final PropertyType? enumType; + /// 组合模式支持 (OpenAPI 3.0) + final List allOf; + final List oneOf; + final List anyOf; + final ApiSchema? not; + + /// 多态类型判别器 (OpenAPI 3.0) + final ApiDiscriminator? discriminator; + const ApiModel({ required this.name, required this.description, @@ -356,6 +1775,11 @@ class ApiModel { this.isEnum = false, this.enumValues = const [], this.enumType, + this.allOf = const [], + this.oneOf = const [], + this.anyOf = const [], + this.not, + this.discriminator, }); /// 从JSON创建ApiModel @@ -374,6 +1798,31 @@ class ApiModel { .toList(); } + // 解析组合模式 + final allOfJson = json['allOf'] as List? ?? []; + final allOf = allOfJson + .map((schema) => ApiSchema.fromJson(schema as Map)) + .toList(); + + final oneOfJson = json['oneOf'] as List? ?? []; + final oneOf = oneOfJson + .map((schema) => ApiSchema.fromJson(schema as Map)) + .toList(); + + final anyOfJson = json['anyOf'] as List? ?? []; + final anyOf = anyOfJson + .map((schema) => ApiSchema.fromJson(schema as Map)) + .toList(); + + final notJson = json['not'] as Map?; + final not = notJson != null ? ApiSchema.fromJson(notJson) : null; + + // 解析 discriminator + final discriminatorJson = json['discriminator'] as Map?; + final discriminator = discriminatorJson != null + ? ApiDiscriminator.fromJson(discriminatorJson) + : null; + return ApiModel( name: name, description: json['description'] as String? ?? '', @@ -383,6 +1832,11 @@ class ApiModel { enumType: isEnum ? PropertyType.fromString(json['type'] as String? ?? 'string') : null, + allOf: allOf, + oneOf: oneOf, + anyOf: anyOf, + not: not, + discriminator: discriminator, properties: properties.map( (propName, propData) => MapEntry( propName, @@ -395,6 +1849,25 @@ class ApiModel { ), ); } + + /// 检查是否使用了组合模式 + bool get isComposition => + allOf.isNotEmpty || oneOf.isNotEmpty || anyOf.isNotEmpty; + + /// 检查是否是 allOf 组合 + bool get isAllOf => allOf.isNotEmpty; + + /// 检查是否是 oneOf 组合 + bool get isOneOf => oneOf.isNotEmpty; + + /// 检查是否是 anyOf 组合 + bool get isAnyOf => anyOf.isNotEmpty; + + /// 检查是否有 not 约束 + bool get hasNot => not != null; + + /// 检查是否有判别器 + bool get hasDiscriminator => discriminator != null; } /// API属性信息 @@ -410,6 +1883,15 @@ class ApiProperty { final String? reference; final ApiModel? items; // 用于数组类型 + /// 嵌套对象属性 (用于 object 类型) + final Map nestedProperties; + + /// 嵌套对象的必需字段 + final List nestedRequired; + + /// Schema 定义 (用于复杂类型) + final ApiSchema? schema; + const ApiProperty({ required this.name, required this.type, @@ -421,17 +1903,35 @@ class ApiProperty { this.defaultValue, this.reference, this.items, + this.nestedProperties = const {}, + this.nestedRequired = const [], + this.schema, }); /// 从JSON创建ApiProperty factory ApiProperty.fromJson( String name, Map json, - List requiredFields, - ) { + List requiredFields, { + int maxDepth = 10, + int currentDepth = 0, + }) { + // 防止过深的嵌套 + if (currentDepth >= maxDepth) { + return ApiProperty( + name: name, + type: PropertyType.object, + description: '达到最大嵌套深度的对象', + required: requiredFields.contains(name), + ); + } + final type = PropertyType.fromString(json['type'] as String? ?? 'string'); String? reference; ApiModel? items; + final Map nestedProperties = {}; + List nestedRequired = []; + ApiSchema? schema; // 处理引用类型 if (json['\$ref'] != null) { @@ -439,6 +1939,42 @@ class ApiProperty { reference = ref.split('/').last; } + // 处理复杂 schema(组合模式等) + if (json['allOf'] != null || + json['oneOf'] != null || + json['anyOf'] != null) { + schema = ApiSchema.fromJson(json); + } + + // 处理嵌套对象类型 + if (type == PropertyType.object && json['properties'] != null) { + final propertiesJson = json['properties'] as Map; + nestedRequired = List.from(json['required'] ?? []); + + propertiesJson.forEach((propName, propData) { + if (propData is Map) { + try { + final nestedProperty = ApiProperty.fromJson( + propName, + propData, + nestedRequired, + maxDepth: maxDepth, + currentDepth: currentDepth + 1, + ); + nestedProperties[propName] = nestedProperty; + } catch (e) { + // 如果解析嵌套属性失败,创建一个基本属性 + nestedProperties[propName] = ApiProperty( + name: propName, + type: PropertyType.string, + description: '解析失败的嵌套属性', + required: nestedRequired.contains(propName), + ); + } + } + }); + } + // 处理数组类型的 items if (type == PropertyType.array && json['items'] != null) { final itemsJson = json['items'] as Map; @@ -454,6 +1990,43 @@ class ApiProperty { required: [], isEnum: false, ); + } else if (itemsJson['type'] == 'object' && + itemsJson['properties'] != null) { + // 如果 items 是嵌套对象 + final itemProperties = {}; + final itemRequired = List.from(itemsJson['required'] ?? []); + final itemPropertiesJson = + itemsJson['properties'] as Map; + + itemPropertiesJson.forEach((propName, propData) { + if (propData is Map) { + try { + final itemProperty = ApiProperty.fromJson( + propName, + propData, + itemRequired, + maxDepth: maxDepth, + currentDepth: currentDepth + 1, + ); + itemProperties[propName] = itemProperty; + } catch (e) { + // 创建基本属性作为后备 + itemProperties[propName] = ApiProperty( + name: propName, + type: PropertyType.string, + description: '解析失败的数组项属性', + required: itemRequired.contains(propName), + ); + } + } + }); + + items = ApiModel( + name: '${name}Item', + description: itemsJson['description'] as String? ?? '', + properties: itemProperties, + required: itemRequired, + ); } else { // 如果 items 是基本类型 final itemType = @@ -479,8 +2052,17 @@ class ApiProperty { defaultValue: json['default'], reference: reference, items: items, + nestedProperties: nestedProperties, + nestedRequired: nestedRequired, + schema: schema, ); } + + /// 检查是否有嵌套属性 + bool get hasNestedProperties => nestedProperties.isNotEmpty; + + /// 检查是否有复杂 schema + bool get hasComplexSchema => schema != null; } /// API控制器信息 @@ -500,3 +2082,280 @@ class ApiController { return ApiController(name: name, description: name, paths: paths); } } + +/// 安全方案类型 +enum SecuritySchemeType { + apiKey, + http, + oauth2, + openIdConnect, +} + +extension SecuritySchemeTypeExtension on SecuritySchemeType { + String get value { + switch (this) { + case SecuritySchemeType.apiKey: + return 'apiKey'; + case SecuritySchemeType.http: + return 'http'; + case SecuritySchemeType.oauth2: + return 'oauth2'; + case SecuritySchemeType.openIdConnect: + return 'openIdConnect'; + } + } + + static SecuritySchemeType fromString(String value) { + switch (value.toLowerCase()) { + case 'apikey': + return SecuritySchemeType.apiKey; + case 'http': + return SecuritySchemeType.http; + case 'oauth2': + return SecuritySchemeType.oauth2; + case 'openidconnect': + return SecuritySchemeType.openIdConnect; + default: + return SecuritySchemeType.apiKey; + } + } +} + +/// API Key 位置 +enum ApiKeyLocation { + query, + header, + cookie, +} + +extension ApiKeyLocationExtension on ApiKeyLocation { + String get value { + switch (this) { + case ApiKeyLocation.query: + return 'query'; + case ApiKeyLocation.header: + return 'header'; + case ApiKeyLocation.cookie: + return 'cookie'; + } + } + + static ApiKeyLocation fromString(String value) { + switch (value.toLowerCase()) { + case 'query': + return ApiKeyLocation.query; + case 'header': + return ApiKeyLocation.header; + case 'cookie': + return ApiKeyLocation.cookie; + default: + return ApiKeyLocation.header; + } + } +} + +/// OAuth2 流程类型 +enum OAuth2FlowType { + authorizationCode, + implicit, + password, + clientCredentials, +} + +extension OAuth2FlowTypeExtension on OAuth2FlowType { + String get value { + switch (this) { + case OAuth2FlowType.authorizationCode: + return 'authorizationCode'; + case OAuth2FlowType.implicit: + return 'implicit'; + case OAuth2FlowType.password: + return 'password'; + case OAuth2FlowType.clientCredentials: + return 'clientCredentials'; + } + } + + static OAuth2FlowType fromString(String value) { + switch (value.toLowerCase()) { + case 'authorizationcode': + return OAuth2FlowType.authorizationCode; + case 'implicit': + return OAuth2FlowType.implicit; + case 'password': + return OAuth2FlowType.password; + case 'clientcredentials': + return OAuth2FlowType.clientCredentials; + default: + return OAuth2FlowType.authorizationCode; + } + } +} + +/// OAuth2 流程配置 +class OAuth2Flow { + /// 授权 URL (用于 authorizationCode 和 implicit 流程) + final String? authorizationUrl; + + /// 令牌 URL (用于 authorizationCode, password 和 clientCredentials 流程) + final String? tokenUrl; + + /// 刷新 URL (可选) + final String? refreshUrl; + + /// 可用的作用域 + final Map scopes; + + const OAuth2Flow({ + this.authorizationUrl, + this.tokenUrl, + this.refreshUrl, + this.scopes = const {}, + }); + + /// 从 JSON 创建 OAuth2Flow + factory OAuth2Flow.fromJson(Map json) { + final scopesData = json['scopes']; + final Map scopes; + + if (scopesData is Map) { + scopes = scopesData + .map((key, value) => MapEntry(key.toString(), value.toString())); + } else { + scopes = {}; + } + + return OAuth2Flow( + authorizationUrl: json['authorizationUrl'] as String?, + tokenUrl: json['tokenUrl'] as String?, + refreshUrl: json['refreshUrl'] as String?, + scopes: scopes, + ); + } + + /// 检查是否有授权 URL + bool get hasAuthorizationUrl => + authorizationUrl != null && authorizationUrl!.isNotEmpty; + + /// 检查是否有令牌 URL + bool get hasTokenUrl => tokenUrl != null && tokenUrl!.isNotEmpty; + + /// 检查是否有刷新 URL + bool get hasRefreshUrl => refreshUrl != null && refreshUrl!.isNotEmpty; + + /// 检查是否有作用域 + bool get hasScopes => scopes.isNotEmpty; +} + +/// OAuth2 流程集合 +class OAuth2Flows { + /// 授权码流程 + final OAuth2Flow? authorizationCode; + + /// 隐式流程 + final OAuth2Flow? implicit; + + /// 密码流程 + final OAuth2Flow? password; + + /// 客户端凭证流程 + final OAuth2Flow? clientCredentials; + + const OAuth2Flows({ + this.authorizationCode, + this.implicit, + this.password, + this.clientCredentials, + }); + + /// 从 JSON 创建 OAuth2Flows + factory OAuth2Flows.fromJson(Map json) { + return OAuth2Flows( + authorizationCode: json['authorizationCode'] != null + ? OAuth2Flow.fromJson( + json['authorizationCode'] as Map) + : null, + implicit: json['implicit'] != null + ? OAuth2Flow.fromJson(json['implicit'] as Map) + : null, + password: json['password'] != null + ? OAuth2Flow.fromJson(json['password'] as Map) + : null, + clientCredentials: json['clientCredentials'] != null + ? OAuth2Flow.fromJson( + json['clientCredentials'] as Map) + : null, + ); + } + + /// 获取所有可用的流程 + List get availableFlows { + final flows = []; + if (authorizationCode != null) flows.add(OAuth2FlowType.authorizationCode); + if (implicit != null) flows.add(OAuth2FlowType.implicit); + if (password != null) flows.add(OAuth2FlowType.password); + if (clientCredentials != null) flows.add(OAuth2FlowType.clientCredentials); + return flows; + } + + /// 检查是否有任何流程 + bool get hasAnyFlow => availableFlows.isNotEmpty; + + /// 获取指定类型的流程 + OAuth2Flow? getFlow(OAuth2FlowType type) { + switch (type) { + case OAuth2FlowType.authorizationCode: + return authorizationCode; + case OAuth2FlowType.implicit: + return implicit; + case OAuth2FlowType.password: + return password; + case OAuth2FlowType.clientCredentials: + return clientCredentials; + } + } +} + +/// 安全要求 (单个安全方案的要求) +class ApiSecurityRequirement { + /// 安全方案名称到作用域列表的映射 + final Map> requirements; + + const ApiSecurityRequirement({ + this.requirements = const {}, + }); + + /// 从 JSON 创建 ApiSecurityRequirement + factory ApiSecurityRequirement.fromJson(Map json) { + final requirements = >{}; + + json.forEach((schemeName, scopes) { + if (scopes is List) { + requirements[schemeName] = List.from(scopes); + } else { + requirements[schemeName] = []; + } + }); + + return ApiSecurityRequirement(requirements: requirements); + } + + /// 检查是否为空 + bool get isEmpty => requirements.isEmpty; + + /// 检查是否非空 + bool get isNotEmpty => requirements.isNotEmpty; + + /// 获取所有安全方案名称 + List get schemeNames => requirements.keys.toList(); + + /// 获取指定方案的作用域 + List getScopesForScheme(String schemeName) { + return requirements[schemeName] ?? []; + } + + /// 检查是否包含指定的安全方案 + bool hasScheme(String schemeName) { + return requirements.containsKey(schemeName); + } +} diff --git a/lib/core/performance_parser.dart b/lib/core/performance_parser.dart new file mode 100644 index 0000000..a485c88 --- /dev/null +++ b/lib/core/performance_parser.dart @@ -0,0 +1,471 @@ +/// 高性能 OpenAPI 解析器 +/// 支持流式解析、并行处理和增量解析 +library; + +import 'dart:async'; +import 'dart:convert'; +import 'models.dart'; + +/// 解析性能统计 +class ParsePerformanceStats { + final Duration totalTime; + final Duration parseTime; + final Duration validationTime; + final Duration modelCreationTime; + final int memoryUsage; + final int documentSize; + final int pathCount; + final int schemaCount; + + const ParsePerformanceStats({ + required this.totalTime, + required this.parseTime, + required this.validationTime, + required this.modelCreationTime, + required this.memoryUsage, + required this.documentSize, + required this.pathCount, + required this.schemaCount, + }); + + double get pathsPerSecond => pathCount / totalTime.inMilliseconds * 1000; + double get schemasPerSecond => schemaCount / totalTime.inMilliseconds * 1000; + double get bytesPerSecond => documentSize / totalTime.inMilliseconds * 1000; + + @override + String toString() { + return ''' +Performance Statistics: + Total Time: ${totalTime.inMilliseconds}ms + Parse Time: ${parseTime.inMilliseconds}ms + Validation Time: ${validationTime.inMilliseconds}ms + Model Creation Time: ${modelCreationTime.inMilliseconds}ms + Memory Usage: ${(memoryUsage / 1024 / 1024).toStringAsFixed(2)}MB + Document Size: ${(documentSize / 1024).toStringAsFixed(2)}KB + Paths: $pathCount (${pathsPerSecond.toStringAsFixed(1)}/s) + Schemas: $schemaCount (${schemasPerSecond.toStringAsFixed(1)}/s) + Throughput: ${(bytesPerSecond / 1024).toStringAsFixed(2)}KB/s +'''; + } +} + +/// 解析配置 +class ParseConfig { + /// 是否启用并行解析 + final bool enableParallelParsing; + + /// 是否启用流式解析 + final bool enableStreamParsing; + + /// 是否启用增量解析 + final bool enableIncrementalParsing; + + /// 是否启用缓存 + final bool enableCaching; + + /// 最大并行度 + final int maxConcurrency; + + /// 流式解析缓冲区大小 + final int streamBufferSize; + + /// 是否启用性能统计 + final bool enablePerformanceStats; + + /// 是否启用内存优化 + final bool enableMemoryOptimization; + + const ParseConfig({ + this.enableParallelParsing = true, + this.enableStreamParsing = false, + this.enableIncrementalParsing = false, + this.enableCaching = true, + this.maxConcurrency = 4, + this.streamBufferSize = 8192, + this.enablePerformanceStats = false, + this.enableMemoryOptimization = true, + }); +} + +/// 高性能解析器 +class PerformanceParser { + final ParseConfig _config; + final Map _cache = {}; + ParsePerformanceStats? _lastStats; + + PerformanceParser({ParseConfig? config}) + : _config = config ?? const ParseConfig(); + + /// 获取最后一次解析的性能统计 + ParsePerformanceStats? get lastStats => _lastStats; + + /// 解析 OpenAPI 文档 + Future parseDocument(String jsonString) async { + final stopwatch = Stopwatch()..start(); + final parseStopwatch = Stopwatch(); + final validationStopwatch = Stopwatch(); + final modelCreationStopwatch = Stopwatch(); + + try { + // 解析 JSON + parseStopwatch.start(); + final Map json; + + if (_config.enableStreamParsing && + jsonString.length > _config.streamBufferSize) { + json = await _parseJsonStream(jsonString); + } else { + json = jsonDecode(jsonString) as Map; + } + parseStopwatch.stop(); + + // 验证基础结构 + validationStopwatch.start(); + _validateBasicStructure(json); + validationStopwatch.stop(); + + // 创建模型 + modelCreationStopwatch.start(); + final SwaggerDocument document; + + if (_config.enableParallelParsing) { + document = await _parseDocumentParallel(json); + } else { + document = SwaggerDocument.fromJson(json); + } + modelCreationStopwatch.stop(); + + stopwatch.stop(); + + // 生成性能统计 + if (_config.enablePerformanceStats) { + _lastStats = ParsePerformanceStats( + totalTime: stopwatch.elapsed, + parseTime: parseStopwatch.elapsed, + validationTime: validationStopwatch.elapsed, + modelCreationTime: modelCreationStopwatch.elapsed, + memoryUsage: _getMemoryUsage(), + documentSize: jsonString.length, + pathCount: document.paths.length, + schemaCount: document.components.schemas.length, + ); + } + + return document; + } catch (e) { + stopwatch.stop(); + rethrow; + } + } + + /// 流式 JSON 解析 + Future> _parseJsonStream(String jsonString) async { + final completer = Completer>(); + final controller = StreamController(); + + // 模拟流式解析(实际实现会更复杂) + final chunks = []; + for (int i = 0; i < jsonString.length; i += _config.streamBufferSize) { + final end = (i + _config.streamBufferSize).clamp(0, jsonString.length); + chunks.add(jsonString.substring(i, end)); + } + + controller.stream.listen( + (chunk) { + // 处理 JSON 块 + }, + onDone: () { + try { + final result = jsonDecode(jsonString) as Map; + completer.complete(result); + } catch (e) { + completer.completeError(e); + } + }, + onError: completer.completeError, + ); + + // 添加所有块 + for (final chunk in chunks) { + controller.add(chunk); + } + controller.close(); + + return completer.future; + } + + /// 并行解析文档 + Future _parseDocumentParallel( + Map json) async { + final futures = []; + final results = {}; + + // 并行解析不同部分 + if (json.containsKey('paths')) { + futures.add(_parsePathsParallel(json['paths'] as Map) + .then((paths) => results['paths'] = paths)); + } + + if (json.containsKey('components')) { + futures.add( + _parseComponentsParallel(json['components'] as Map) + .then((components) => results['components'] = components)); + } + + if (json.containsKey('servers')) { + futures.add(_parseServersParallel(json['servers'] as List) + .then((servers) => results['servers'] = servers)); + } + + // 等待所有并行任务完成 + await Future.wait(futures); + + // 合并结果 + final mergedJson = Map.from(json); + mergedJson.addAll(results); + + return SwaggerDocument.fromJson(mergedJson); + } + + /// 并行解析路径 + Future> _parsePathsParallel( + Map pathsJson) async { + if (pathsJson.length <= _config.maxConcurrency) { + // 如果路径数量较少,直接解析 + return _parsePathsSequential(pathsJson); + } + + final chunks = _chunkMap(pathsJson, _config.maxConcurrency); + final futures = chunks.map((chunk) => _parsePathChunk(chunk)); + final results = await Future.wait(futures); + + // 合并结果 + final mergedPaths = {}; + for (final pathMap in results) { + mergedPaths.addAll(pathMap); + } + + return mergedPaths; + } + + /// 并行解析组件 + Future _parseComponentsParallel( + Map componentsJson) async { + final futures = []; + final results = {}; + + if (componentsJson.containsKey('schemas')) { + futures.add(_parseSchemasParallel( + componentsJson['schemas'] as Map) + .then((schemas) => results['schemas'] = schemas)); + } + + if (componentsJson.containsKey('securitySchemes')) { + futures.add(_parseSecuritySchemesParallel( + componentsJson['securitySchemes'] as Map) + .then((schemes) => results['securitySchemes'] = schemes)); + } + + await Future.wait(futures); + + final mergedComponents = Map.from(componentsJson); + mergedComponents.addAll(results); + + return ApiComponents.fromJson(mergedComponents); + } + + /// 并行解析服务器 + Future> _parseServersParallel( + List serversJson) async { + if (serversJson.length <= _config.maxConcurrency) { + return serversJson + .map((json) => ApiServer.fromJson(json as Map)) + .toList(); + } + + final chunks = _chunkList(serversJson, _config.maxConcurrency); + final futures = chunks.map((chunk) => _parseServerChunk(chunk)); + final results = await Future.wait(futures); + + // 合并结果 + final mergedServers = []; + for (final serverList in results) { + mergedServers.addAll(serverList); + } + + return mergedServers; + } + + /// 解析路径块 + Future> _parsePathChunk( + Map pathChunk) async { + return _parsePathsSequential(pathChunk); + } + + /// 解析服务器块 + Future> _parseServerChunk(List serverChunk) async { + return serverChunk + .map((json) => ApiServer.fromJson(json as Map)) + .toList(); + } + + /// 顺序解析路径 + Map _parsePathsSequential(Map pathsJson) { + final paths = {}; + + pathsJson.forEach((pathPattern, pathData) { + if (pathData is Map) { + pathData.forEach((method, operationData) { + if (operationData is Map) { + try { + final apiPath = + ApiPath.fromJson(pathPattern, method, operationData); + paths[pathPattern] = apiPath; + } catch (e) { + // 忽略解析错误的路径 + } + } + }); + } + }); + + return paths; + } + + /// 并行解析 Schemas + Future> _parseSchemasParallel( + Map schemasJson) async { + if (schemasJson.length <= _config.maxConcurrency) { + return _parseSchemasSequential(schemasJson); + } + + final chunks = _chunkMap(schemasJson, _config.maxConcurrency); + final futures = chunks.map((chunk) => _parseSchemaChunk(chunk)); + final results = await Future.wait(futures); + + // 合并结果 + final mergedSchemas = {}; + for (final schemaMap in results) { + mergedSchemas.addAll(schemaMap); + } + + return mergedSchemas; + } + + /// 并行解析安全方案 + Future> _parseSecuritySchemesParallel( + Map schemesJson) async { + final schemes = {}; + + schemesJson.forEach((name, schemeData) { + if (schemeData is Map) { + try { + final scheme = ApiSecurityScheme.fromJson(schemeData); + schemes[name] = scheme; + } catch (e) { + // 忽略解析错误的安全方案 + } + } + }); + + return schemes; + } + + /// 解析 Schema 块 + Future> _parseSchemaChunk( + Map schemaChunk) async { + return _parseSchemasSequential(schemaChunk); + } + + /// 顺序解析 Schemas + Map _parseSchemasSequential( + Map schemasJson) { + final schemas = {}; + + schemasJson.forEach((name, schemaData) { + if (schemaData is Map) { + try { + final model = ApiModel.fromJson(name, schemaData); + schemas[name] = model; + } catch (e) { + // 忽略解析错误的 schema + } + } + }); + + return schemas; + } + + /// 验证基础结构 + void _validateBasicStructure(Map json) { + if (!json.containsKey('openapi') && !json.containsKey('swagger')) { + throw const FormatException( + 'Invalid OpenAPI document: missing version field'); + } + + if (!json.containsKey('info')) { + throw const FormatException( + 'Invalid OpenAPI document: missing info object'); + } + + final info = json['info'] as Map?; + if (info == null || + !info.containsKey('title') || + !info.containsKey('version')) { + throw const FormatException( + 'Invalid OpenAPI document: info object must contain title and version'); + } + } + + /// 将 Map 分块 + List> _chunkMap( + Map map, int chunkSize) { + final chunks = >[]; + final entries = map.entries.toList(); + + for (int i = 0; i < entries.length; i += chunkSize) { + final end = (i + chunkSize).clamp(0, entries.length); + final chunk = {}; + + for (int j = i; j < end; j++) { + final entry = entries[j]; + chunk[entry.key] = entry.value; + } + + chunks.add(chunk); + } + + return chunks; + } + + /// 将 List 分块 + List> _chunkList(List list, int chunkSize) { + final chunks = >[]; + + for (int i = 0; i < list.length; i += chunkSize) { + final end = (i + chunkSize).clamp(0, list.length); + chunks.add(list.sublist(i, end)); + } + + return chunks; + } + + /// 获取内存使用量(简化实现) + int _getMemoryUsage() { + // 在实际实现中,这里会使用 dart:developer 或其他方式获取真实的内存使用量 + return 0; + } + + /// 清除缓存 + void clearCache() { + _cache.clear(); + } + + /// 获取缓存统计 + Map getCacheStats() { + return { + 'size': _cache.length, + 'keys': _cache.keys.toList(), + }; + } +} diff --git a/lib/core/smart_cache.dart b/lib/core/smart_cache.dart new file mode 100644 index 0000000..1d31d68 --- /dev/null +++ b/lib/core/smart_cache.dart @@ -0,0 +1,444 @@ +/// 智能缓存系统 +/// 支持智能失效、增量解析和内存管理 +library; + +import 'dart:async'; + +/// 缓存条目 +class CacheEntry { + final String key; + final T value; + final DateTime createdAt; + final DateTime lastAccessedAt; + final Duration ttl; + final String? etag; + final int? version; + final Map 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 withAccess() { + return CacheEntry( + key: key, + value: value, + createdAt: createdAt, + lastAccessedAt: DateTime.now(), + ttl: ttl, + etag: etag, + version: version, + metadata: metadata, + ); + } + + /// 创建新版本的条目 + CacheEntry withValue(T newValue, {String? newEtag, int? newVersion}) { + return CacheEntry( + 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 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 { + final int _maxSize; + final CacheStrategy _strategy; + final Duration _defaultTtl; + final bool _enablePersistence; + + final Map> _cache = {}; + final Map _accessCounts = {}; + final Map _lastAccess = {}; + final List _accessOrder = []; + + int _totalRequests = 0; + int _hits = 0; + int _misses = 0; + int _evictions = 0; + final List _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? metadata}) { + final entry = CacheEntry( + 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 getKeysNeedingRefresh() { + return _cache.entries + .where((entry) => entry.value.needsRefresh) + .map((entry) => entry.key) + .toList(); + } + + /// 批量刷新 + Future refreshKeys( + List keys, Future 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 warmUp(Map 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 = {}; + 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 persist() async { + if (!_enablePersistence) return; + + // 这里应该将缓存数据写入文件或数据库 + // 实际实现会更复杂 + } + + /// 从持久化存储加载缓存(简化实现) + Future load() async { + if (!_enablePersistence) return; + + // 这里应该从文件或数据库读取缓存数据 + // 实际实现会更复杂 + } +} diff --git a/lib/generators/documentation_generator.dart b/lib/generators/documentation_generator.dart index e171573..6acc7f8 100644 --- a/lib/generators/documentation_generator.dart +++ b/lib/generators/documentation_generator.dart @@ -141,19 +141,27 @@ class DocumentationGenerator extends BaseGenerator { buffer.writeln(''); // 支持的格式 - buffer.writeln('### 📝 支持的格式'); + buffer.writeln('### 🌐 服务器配置'); buffer.writeln(''); - buffer.writeln('**请求格式**:'); - for (final format in document.consumes) { - buffer.writeln('- `$format`'); + 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(''); } - buffer.writeln(''); - - buffer.writeln('**响应格式**:'); - for (final format in document.produces) { - buffer.writeln('- `$format`'); - } - buffer.writeln(''); } /// 生成认证信息 @@ -590,11 +598,12 @@ class DocumentationGenerator extends BaseGenerator { // 已移动到 StringUtils.extractControllerName - /// 获取基础URL + /// 获取基础URL (从 OpenAPI 3.0 servers 配置) String _getBaseUrl() { - return document.schemes.isNotEmpty - ? '${document.schemes.first}://${document.host}${document.basePath}' - : 'https://${document.host}${document.basePath}'; + if (document.servers.isNotEmpty) { + return document.servers.first.url; + } + return '/'; // 默认相对路径 } /// 获取参数位置名称 diff --git a/lib/generators/endpoint_code_generator.dart b/lib/generators/endpoint_code_generator.dart index 9b41b63..326cdaf 100644 --- a/lib/generators/endpoint_code_generator.dart +++ b/lib/generators/endpoint_code_generator.dart @@ -33,12 +33,10 @@ class EndpointCodeGenerator extends BaseGenerator { buffer.writeln(' ApiPaths._(); // 私有构造函数,防止实例化'); buffer.writeln(''); - // 生成基础URL常量 + // 生成基础URL常量 (从 OpenAPI 3.0 servers 配置) if (includeBaseUrl) { final baseUrl = customBaseUrl ?? - (document.schemes.isNotEmpty - ? '${document.schemes.first}://${document.host}${document.basePath}' - : 'https://${document.host}${document.basePath}'); + (document.servers.isNotEmpty ? document.servers.first.url : '/'); buffer.writeln(' /// 基础URL'); buffer.writeln(' static const String baseUrl = \'$baseUrl\';'); diff --git a/lib/generators/model_code_generator.dart b/lib/generators/model_code_generator.dart index 47c545f..ebd4061 100644 --- a/lib/generators/model_code_generator.dart +++ b/lib/generators/model_code_generator.dart @@ -48,155 +48,8 @@ class ModelCodeGenerator extends ModelGenerator { return generateEnumCode(model); } - return useSimpleModels - ? generateSimpleModelCode(model) - : generateAnnotatedModelCode(model); - } - - /// 生成简洁版模型代码 - String generateSimpleModelCode(ApiModel model) { - final className = StringUtils.generateClassName(model.name); - final buffer = StringBuffer(); - - // 生成导入依赖 - final importedTypes = getImportedTypes(model); - for (final importType in importedTypes) { - final importFileName = StringUtils.generateFileName(importType); - buffer.writeln('import \'$importFileName\';'); - } - - if (importedTypes.isNotEmpty) { - buffer.writeln(''); - } - - // 生成类注释 - if (model.description.isNotEmpty) { - buffer.writeln(StringUtils.generateComment(model.description)); - } - - buffer.writeln('class $className {'); - - // 生成属性 - model.properties.forEach((propName, property) { - final dartType = getDartPropertyType(property); - final nullable = property.nullable ? '?' : ''; - final dartPropName = StringUtils.toDartPropertyName(propName); - - if (property.description.isNotEmpty) { - buffer.writeln( - ' ${StringUtils.generateComment(property.description)}', - ); - } - - buffer.writeln(' final $dartType$nullable $dartPropName;'); - buffer.writeln(''); - }); - - // 生成构造函数 - if (model.properties.isEmpty) { - buffer.writeln(' const $className();'); - } else { - buffer.writeln(' const $className({'); - model.properties.forEach((propName, property) { - final dartPropName = StringUtils.toDartPropertyName(propName); - final required = property.required ? 'required ' : ''; - buffer.writeln(' ${required}this.$dartPropName,'); - }); - buffer.writeln(' });'); - } - buffer.writeln(''); - - // 生成 fromJson 方法 - buffer.writeln( - ' factory $className.fromJson(Map json) {', - ); - if (model.properties.isEmpty) { - buffer.writeln(' return const $className();'); - } else { - buffer.writeln(' return $className('); - model.properties.forEach((propName, property) { - final dartPropName = StringUtils.toDartPropertyName(propName); - final dartType = getDartPropertyType(property); - - buffer.write(' $dartPropName: '); - - // 生成类型转换逻辑 - if (property.type == PropertyType.reference && - property.reference != null) { - final refType = StringUtils.generateClassName(property.reference!); - if (property.nullable) { - buffer.write( - 'json[\'$propName\'] != null ? $refType.fromJson(json[\'$propName\']) : null', - ); - } else { - buffer.write('$refType.fromJson(json[\'$propName\'])'); - } - } else if (property.type == PropertyType.array) { - // 简化的数组处理 - buffer.write( - 'json[\'$propName\'] != null ? List.from(json[\'$propName\']) : null', - ); - } else { - // 基本类型 - if (property.nullable) { - buffer.write('json[\'$propName\'] as $dartType?'); - } else { - buffer.write('json[\'$propName\'] as $dartType'); - } - } - - buffer.writeln(','); - }); - buffer.writeln(' );'); - } - buffer.writeln(' }'); - buffer.writeln(''); - - // 生成 toJson 方法 - buffer.writeln(' Map toJson() {'); - buffer.writeln(' return {'); - model.properties.forEach((propName, property) { - final dartPropName = StringUtils.toDartPropertyName(propName); - - if (property.type == PropertyType.reference && - property.reference != null) { - buffer.write(' \'$propName\': $dartPropName?.toJson()'); - } else if (property.type == PropertyType.array) { - buffer.write(' \'$propName\': $dartPropName'); - } else { - buffer.write(' \'$propName\': $dartPropName'); - } - - buffer.writeln(','); - }); - buffer.writeln(' };'); - buffer.writeln(' }'); - buffer.writeln(''); - - // 生成 copyWith 方法 - if (model.properties.isNotEmpty) { - buffer.writeln(' $className copyWith({'); - model.properties.forEach((propName, property) { - final dartType = getDartPropertyType(property); - final dartPropName = StringUtils.toDartPropertyName(propName); - buffer.writeln(' $dartType? $dartPropName,'); - }); - buffer.writeln(' }) {'); - buffer.writeln(' return $className('); - model.properties.forEach((propName, property) { - final dartPropName = StringUtils.toDartPropertyName(propName); - buffer.writeln( - ' $dartPropName: $dartPropName ?? this.$dartPropName,', - ); - }); - buffer.writeln(' );'); - buffer.writeln(' }'); - buffer.writeln(''); - } - - buffer.writeln('}'); - - return buffer.toString(); + // 只使用 JsonSerializable 注解版本 + return generateAnnotatedModelCode(model); } /// 生成带注解的模型代码 @@ -232,6 +85,7 @@ class ModelCodeGenerator extends ModelGenerator { // 生成属性 model.properties.forEach((propName, property) { final dartType = getDartPropertyType(property); + // 根据文档判断是否可空:只有显式标记为 nullable: true 的才可空 final nullable = property.nullable ? '?' : ''; final dartPropName = StringUtils.toDartPropertyName(propName); @@ -259,7 +113,9 @@ class ModelCodeGenerator extends ModelGenerator { buffer.writeln(' const $className({'); model.properties.forEach((propName, property) { final dartPropName = StringUtils.toDartPropertyName(propName); - final required = property.required ? 'required ' : ''; + // 对于非可空属性,必须添加 required 修饰符 + final shouldBeRequired = !property.nullable; + final required = shouldBeRequired ? 'required ' : ''; buffer.writeln(' ${required}this.$dartPropName,'); }); buffer.writeln(' });'); @@ -268,14 +124,14 @@ class ModelCodeGenerator extends ModelGenerator { // 生成 fromJson 工厂方法 buffer.writeln( - ' factory $className.fromJson(Map json) => _\$${className}FromJson(json);', + ' factory $className.fromJson(Map json) =>', ); + buffer.writeln(' _\$${className}FromJson(json);'); buffer.writeln(''); // 生成 toJson 方法 buffer.writeln( - ' Map toJson() => _\$${className}ToJson(this);', - ); + ' Map toJson() => _\$${className}ToJson(this);'); buffer.writeln(''); buffer.writeln('}'); @@ -352,9 +208,8 @@ class ModelCodeGenerator extends ModelGenerator { return _generateEnumCodeWithoutImports(model); } - return useSimpleModels - ? _generateSimpleModelCodeWithoutImports(model) - : _generateAnnotatedModelCodeWithoutImports(model); + // 只使用 JsonSerializable 注解版本 + return _generateAnnotatedModelCodeWithoutImports(model); } /// 生成枚举代码(不包含导入语句) @@ -426,141 +281,6 @@ class ModelCodeGenerator extends ModelGenerator { // 已移动到 StringUtils.generateEnumValueName - /// 生成简洁版模型代码(不包含导入语句) - String _generateSimpleModelCodeWithoutImports(ApiModel model) { - final className = StringUtils.generateClassName(model.name); - final buffer = StringBuffer(); - - // 生成类注释 - if (model.description.isNotEmpty) { - buffer.writeln(StringUtils.generateComment(model.description)); - } - - buffer.writeln('class $className {'); - - // 生成属性 - model.properties.forEach((propName, property) { - final dartType = getDartPropertyType(property); - final nullable = property.nullable ? '?' : ''; - final dartPropName = StringUtils.toDartPropertyName(propName); - - if (property.description.isNotEmpty) { - buffer.writeln( - ' ${StringUtils.generateComment(property.description)}', - ); - } - - buffer.writeln(' final $dartType$nullable $dartPropName;'); - buffer.writeln(''); - }); - - // 生成构造函数 - if (model.properties.isEmpty) { - buffer.writeln(' const $className();'); - } else { - buffer.writeln(' const $className({'); - model.properties.forEach((propName, property) { - final dartPropName = StringUtils.toDartPropertyName(propName); - final required = property.required ? 'required ' : ''; - buffer.writeln(' ${required}this.$dartPropName,'); - }); - buffer.writeln(' });'); - } - buffer.writeln(''); - - // 生成 fromJson 方法 - buffer.writeln( - ' factory $className.fromJson(Map json) {', - ); - if (model.properties.isEmpty) { - buffer.writeln(' return const $className();'); - } else { - buffer.writeln(' return $className('); - model.properties.forEach((propName, property) { - final dartPropName = StringUtils.toDartPropertyName(propName); - final dartType = getDartPropertyType(property); - - buffer.write(' $dartPropName: '); - - // 生成类型转换逻辑 - if (property.type == PropertyType.reference && - property.reference != null) { - final refType = StringUtils.generateClassName(property.reference!); - if (property.nullable) { - buffer.write( - 'json[\'$propName\'] != null ? $refType.fromJson(json[\'$propName\']) : null', - ); - } else { - buffer.write('$refType.fromJson(json[\'$propName\'])'); - } - } else if (property.type == PropertyType.array) { - // 简化的数组处理 - buffer.write( - 'json[\'$propName\'] != null ? List.from(json[\'$propName\']) : null', - ); - } else { - // 基本类型 - if (property.nullable) { - buffer.write('json[\'$propName\'] as $dartType?'); - } else { - buffer.write('json[\'$propName\'] as $dartType'); - } - } - - buffer.writeln(','); - }); - buffer.writeln(' );'); - } - buffer.writeln(' }'); - buffer.writeln(''); - - // 生成 toJson 方法 - buffer.writeln(' Map toJson() {'); - buffer.writeln(' return {'); - model.properties.forEach((propName, property) { - final dartPropName = StringUtils.toDartPropertyName(propName); - - if (property.type == PropertyType.reference && - property.reference != null) { - buffer.write(' \'$propName\': $dartPropName?.toJson()'); - } else if (property.type == PropertyType.array) { - buffer.write(' \'$propName\': $dartPropName'); - } else { - buffer.write(' \'$propName\': $dartPropName'); - } - - buffer.writeln(','); - }); - buffer.writeln(' };'); - buffer.writeln(' }'); - buffer.writeln(''); - - // 生成 copyWith 方法 - if (model.properties.isNotEmpty) { - buffer.writeln(' $className copyWith({'); - model.properties.forEach((propName, property) { - final dartType = getDartPropertyType(property); - final dartPropName = StringUtils.toDartPropertyName(propName); - buffer.writeln(' $dartType? $dartPropName,'); - }); - buffer.writeln(' }) {'); - buffer.writeln(' return $className('); - model.properties.forEach((propName, property) { - final dartPropName = StringUtils.toDartPropertyName(propName); - buffer.writeln( - ' $dartPropName: $dartPropName ?? this.$dartPropName,', - ); - }); - buffer.writeln(' );'); - buffer.writeln(' }'); - buffer.writeln(''); - } - - buffer.writeln('}'); - - return buffer.toString(); - } - /// 生成带注解的模型代码(不包含导入语句) String _generateAnnotatedModelCodeWithoutImports(ApiModel model) { final className = StringUtils.generateClassName(model.name); @@ -583,6 +303,7 @@ class ModelCodeGenerator extends ModelGenerator { // 生成属性 model.properties.forEach((propName, property) { final dartType = getDartPropertyType(property); + // 根据文档判断是否可空:只有显式标记为 nullable: true 的才可空 final nullable = property.nullable ? '?' : ''; final dartPropName = StringUtils.toDartPropertyName(propName); @@ -610,7 +331,9 @@ class ModelCodeGenerator extends ModelGenerator { buffer.writeln(' const $className({'); model.properties.forEach((propName, property) { final dartPropName = StringUtils.toDartPropertyName(propName); - final required = property.required ? 'required ' : ''; + // 对于非可空属性,必须添加 required 修饰符 + final shouldBeRequired = !property.nullable; + final required = shouldBeRequired ? 'required ' : ''; buffer.writeln(' ${required}this.$dartPropName,'); }); buffer.writeln(' });'); @@ -619,14 +342,14 @@ class ModelCodeGenerator extends ModelGenerator { // 生成 fromJson 工厂方法 buffer.writeln( - ' factory $className.fromJson(Map json) => _\$${className}FromJson(json);', + ' factory $className.fromJson(Map json) =>', ); + buffer.writeln(' _\$${className}FromJson(json);'); buffer.writeln(''); // 生成 toJson 方法 buffer.writeln( - ' Map toJson() => _\$${className}ToJson(this);', - ); + ' Map toJson() => _\$${className}ToJson(this);'); buffer.writeln(''); buffer.writeln('}'); diff --git a/lib/generators/optimized_retrofit_generator.dart b/lib/generators/optimized_retrofit_generator.dart new file mode 100644 index 0000000..7158720 --- /dev/null +++ b/lib/generators/optimized_retrofit_generator.dart @@ -0,0 +1,547 @@ +/// 优化的 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 {'); + 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 json,'); + buffer.writeln(' T Function(Object? json) fromJsonT,'); + buffer.writeln(' ) => _\$${baseResultType}FromJson(json, fromJsonT);'); + buffer.writeln(); + buffer.writeln( + ' Map 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 json) =>'); + buffer.writeln(' _\$BasePageParameterFromJson(json);'); + buffer.writeln(); + buffer.writeln( + ' Map toJson() => _\$BasePageParameterToJson(this);'); + buffer.writeln('}'); + buffer.writeln(); + + buffer.writeln('/// 分页响应结果'); + buffer.writeln('@JsonSerializable(genericArgumentFactories: true)'); + buffer.writeln('class $pageResultType {'); + buffer.writeln(' /// 数据列表'); + buffer.writeln(' final List 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 json,'); + buffer.writeln(' T Function(Object? json) fromJsonT,'); + buffer.writeln(' ) => _\$${pageResultType}FromJson(json, fromJsonT);'); + buffer.writeln(); + buffer.writeln( + ' Map 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 json) =>'); + buffer.writeln(' _\$FileUploadRequestFromJson(json);'); + buffer.writeln(); + buffer.writeln( + ' Map 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 json) =>'); + buffer.writeln(' _\$FileUploadResultFromJson(json);'); + buffer.writeln(); + buffer.writeln( + ' Map 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> _groupApisByModule( + SwaggerDocument document) { + final modules = >{}; + + 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 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 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'; + } + } + return '$baseResultType'; + } + + /// 生成参数 + String _generateParameters(ApiPath apiPath) { + final params = []; + + // 路径参数 + 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 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'; + case PropertyType.object: + return 'Map'; + default: + return 'dynamic'; + } + } + + /// 生成工具类 + void _generateUtilityClasses(StringBuffer buffer) { + buffer.writeln('/// API 工具类'); + buffer.writeln('class ApiUtils {'); + buffer.writeln(' /// 创建文件上传对象'); + buffer.writeln( + ' static Future 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'^_'), ''); + } +} diff --git a/lib/generators/performance_generator.dart b/lib/generators/performance_generator.dart new file mode 100644 index 0000000..a631e9d --- /dev/null +++ b/lib/generators/performance_generator.dart @@ -0,0 +1,591 @@ +/// 高性能代码生成器 +/// 支持并行生成、增量生成和智能缓存 +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 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 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 _cache; + final Map _previousGeneration = {}; + final List _results = []; + + int _totalTasks = 0; + int _completedTasks = 0; + int _failedTasks = 0; + final List _taskTimes = []; + + PerformanceGenerator({ + int maxConcurrency = 4, + bool enableCaching = true, + bool enableIncremental = true, + bool enableParallel = true, + }) : _maxConcurrency = maxConcurrency, + _enableCaching = enableCaching, + _enableIncremental = enableIncremental, + _enableParallel = enableParallel, + _cache = SmartCache( + maxSize: 1000, + strategy: CacheStrategy.smart, + defaultTtl: Duration(hours: 1), + ); + + @override + String get generatorType => 'PerformanceGenerator'; + + @override + String generate() { + throw UnimplementedError('Use generateFromDocument instead'); + } + + /// 高性能生成文档 + Future 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? _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 _createGenerationTasks( + SwaggerDocument document, Map? changes) { + final tasks = []; + + // 如果启用增量生成且没有变更,返回空任务列表 + 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> _generateParallel( + List tasks) async { + final chunks = _chunkTasks(tasks, _maxConcurrency); + final results = []; + + for (final chunk in chunks) { + final chunkResults = await Future.wait( + chunk.map((task) => _executeTask(task)), + ); + results.addAll(chunkResults); + } + + return results; + } + + /// 顺序生成 + Future> _generateSequential( + List tasks) async { + final results = []; + + for (final task in tasks) { + final result = await _executeTask(task); + results.add(result); + } + + return results; + } + + /// 执行单个任务 + Future _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 _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 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 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 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 json) =>'); + buffer.writeln(' _\$${name}FromJson(json);'); + buffer.writeln(); + buffer + .writeln(' Map toJson() => _\$${name}ToJson(this);'); + buffer.writeln('}'); + buffer.writeln(); + + return buffer.toString(); + } + + /// 生成 API + String _generateApi(Map data) { + final module = data['module'] as String; + final paths = data['paths'] as Map; + + 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 ${_generateMethodName(path, apiPath.method)}();'); + buffer.writeln(); + }); + + buffer.writeln('}'); + buffer.writeln(); + + return buffer.toString(); + } + + /// 合并生成结果 + String _mergeResults(List results) { + final buffer = StringBuffer(); + + // 按任务类型排序 + final sortedResults = List.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> _chunkTasks( + List tasks, int chunkSize) { + final chunks = >[]; + + 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> _groupPathsByModule( + Map paths) { + final groups = >{}; + + 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 _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'; + case PropertyType.object: + return 'Map'; + 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(''); + } +} diff --git a/lib/generators/retrofit_api_generator.dart b/lib/generators/retrofit_api_generator.dart index 179ce1d..544a3f9 100644 --- a/lib/generators/retrofit_api_generator.dart +++ b/lib/generators/retrofit_api_generator.dart @@ -5,18 +5,20 @@ import 'base_generator.dart'; /// Retrofit 风格的 API 生成器 /// 负责生成带有注解的 API 接口类 class RetrofitApiGenerator extends BaseGenerator { - final SwaggerDocument document; final String className; final bool useRetrofit; final bool useDio; final bool splitByTags; + final bool generateModels; - RetrofitApiGenerator( - this.document, { + late SwaggerDocument document; + + RetrofitApiGenerator({ this.className = 'ApiClient', this.useRetrofit = true, this.useDio = true, - this.splitByTags = false, + this.splitByTags = true, // 默认启用拆分模式 + this.generateModels = true, }); @override @@ -24,6 +26,12 @@ class RetrofitApiGenerator extends BaseGenerator { @override String generate() { + throw UnimplementedError('Use generateFromDocument instead'); + } + + /// 生成 API 代码 + String generateFromDocument(SwaggerDocument document) { + this.document = document; // 设置文档引用 if (splitByTags) { // 按 tags 分组生成多个文件时,返回主文件内容 return generateMainApiFile(); @@ -44,6 +52,18 @@ class RetrofitApiGenerator extends BaseGenerator { // 生成导入语句 _generateImports(buffer); + // 生成安全方案相关代码 + buffer.write(_generateSecurityCode(document)); + + // 生成媒体类型处理代码 + buffer.write(_generateMediaTypeHandlers()); + + // 生成文件上传处理代码 + buffer.write(_generateFileUploadHandlers()); + + // 生成编码处理代码 + buffer.write(_generateEncodingHandlers()); + // 生成 API 接口类 _generateApiInterface(buffer); @@ -97,14 +117,26 @@ class RetrofitApiGenerator extends BaseGenerator { /// 生成导入语句 void _generateImports(StringBuffer buffer) { + // 添加核心依赖的导入 + buffer.writeln('import \'dart:convert\';'); + buffer.writeln('import \'dart:io\';'); + buffer.writeln('import \'dart:typed_data\';'); + + // Dio 和 Retrofit 相关导入 if (useDio) { buffer.writeln('import \'package:dio/dio.dart\';'); } - if (useRetrofit) { buffer.writeln('import \'package:retrofit/retrofit.dart\';'); + buffer + .writeln('import \'package:json_annotation/json_annotation.dart\';'); } + // 其他工具包导入 + 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(''); // 导入基础响应类型(从用户项目中导入) @@ -114,9 +146,10 @@ class RetrofitApiGenerator extends BaseGenerator { 'import \'package:learning_officer_oa/common/models/common/base_page_result.dart\';'); buffer.writeln(''); - // 导入生成的模型类 + // 导入生成的模型类(按字母顺序排序) final modelImports = _getRequiredModelImports(); - for (final modelImport in modelImports) { + final sortedModelImports = modelImports.toList()..sort(); + for (final modelImport in sortedModelImports) { buffer.writeln( 'import \'../../api_models/${StringUtils.generateFileName(modelImport)}\';'); } @@ -133,17 +166,32 @@ class RetrofitApiGenerator extends BaseGenerator { /// 生成 API 接口类 void _generateApiInterface(StringBuffer buffer) { buffer.writeln('/// $className API 接口'); - buffer.writeln('/// 使用 Retrofit 风格的注解定义'); + buffer.writeln('/// 使用 Retrofit 和 Dio 进行网络请求'); + buffer.writeln('/// 支持多种媒体类型、文件上传、认证等功能'); if (useRetrofit) { - buffer.writeln('@RestApi(parser: Parser.JsonSerializable)'); + // 添加 baseUrl(如果有的话) + final baseUrl = + document.servers.isNotEmpty ? document.servers.first.url : ''; + if (baseUrl.isNotEmpty) { + buffer.writeln( + '@RestApi(baseUrl: \'$baseUrl\', parser: Parser.JsonSerializable)'); + } else { + buffer.writeln('@RestApi(parser: Parser.JsonSerializable)'); + } } buffer.writeln('abstract class $className {'); if (useRetrofit) { + buffer.writeln(' /// 创建 API 服务实例'); + buffer.writeln(' /// [dio] Dio 实例,可以预配置拦截器、超时等'); + buffer.writeln(' /// [baseUrl] 可选的基础 URL,会覆盖注解中的 baseUrl'); buffer.writeln( ' factory $className(Dio dio, {String? baseUrl}) = _$className;'); + } else { + buffer.writeln(' final Dio _dio;'); + buffer.writeln(' $className(this._dio);'); } buffer.writeln(''); @@ -180,8 +228,35 @@ class RetrofitApiGenerator extends BaseGenerator { final cleanPath = StringUtils.cleanPath(path.path); // 生成方法注释 + final parameters = _generateParameters(path); + if (path.summary.isNotEmpty) { buffer.writeln(' ${StringUtils.generateComment(path.summary)}'); + + // 如果有参数描述,添加参数文档 + final paramsWithDescription = parameters + .where((p) => p.description.isNotEmpty || p.defaultValue != null) + .toList(); + + if (paramsWithDescription.isNotEmpty) { + buffer.writeln(' ///'); + buffer.writeln(' /// 参数:'); + + for (final param in paramsWithDescription) { + final commentParts = []; + + if (param.description.isNotEmpty) { + commentParts.add(param.description); + } + + if (param.defaultValue != null) { + commentParts.add('默认值: ${param.defaultValue}'); + } + + final comment = commentParts.join(' - '); + buffer.writeln(' /// - ${param.name}: $comment'); + } + } } if (path.description.isNotEmpty && path.description != path.summary) { buffer.writeln(' ${StringUtils.generateComment(path.description)}'); @@ -194,7 +269,6 @@ class RetrofitApiGenerator extends BaseGenerator { // 生成方法签名 final returnType = _generateReturnType(path); - final parameters = _generateParameters(path); buffer.writeln(' Future<$returnType> $methodName('); @@ -202,6 +276,7 @@ class RetrofitApiGenerator extends BaseGenerator { for (int i = 0; i < parameters.length; i++) { final param = parameters[i]; final isLast = i == parameters.length - 1; + if (param.annotation.isNotEmpty) { buffer.writeln( ' ${param.annotation} ${param.type} ${param.name}${isLast ? '' : ','}'); @@ -269,13 +344,14 @@ class RetrofitApiGenerator extends BaseGenerator { return _wrapWithBaseResult(schemaType, path); } - // 如果无法从 schema 解析,使用智能推断 - final inferredType = _inferReturnTypeFromPath(path); - if (inferredType != null) { - return _wrapWithBaseResult(inferredType, path); + // 特殊处理健康检查接口 + final pathLower = path.path.toLowerCase(); + if (pathLower.contains('healthcheck') || pathLower.contains('health')) { + return 'BaseResult'; } - // 默认返回类型 + // 如果没有明确的 schema 定义,使用通用类型 + // 这通常表示后端文档不完整,应该要求后端完善 swagger 文档 return 'BaseResult>'; } @@ -320,19 +396,16 @@ class RetrofitApiGenerator extends BaseGenerator { for (final statusCode in successResponses) { final response = path.responses[statusCode]; if (response != null) { - // 检查 content.application/json.schema (Swagger 3.0) - if (response.content != null) { - final applicationJson = - response.content!['application/json'] as Map?; - if (applicationJson != null) { - final schema = applicationJson['schema'] as Map?; - if (schema != null && _hasPaginationSchema(schema)) { - return true; - } + // 检查 content.application/json.schema (OpenAPI 3.0) + final applicationJsonMediaType = response.content['application/json']; + if (applicationJsonMediaType != null) { + final schema = applicationJsonMediaType.schema; + if (schema != null && _hasPaginationSchema(schema)) { + return true; } } - // 检查 schema 字段 (Swagger 2.0) + // 检查 schema 字段 (Swagger 2.0 兼容) if (response.schema != null && _hasPaginationSchema(response.schema!)) { return true; } @@ -570,19 +643,16 @@ class RetrofitApiGenerator extends BaseGenerator { for (final statusCode in successResponses) { final response = path.responses[statusCode]; if (response != null) { - // 检查 content.application/json.schema (Swagger 3.0) - if (response.content != null) { - final applicationJson = - response.content!['application/json'] as Map?; - if (applicationJson != null) { - final schema = applicationJson['schema'] as Map?; - if (schema != null && _isArraySchema(schema)) { - return true; - } + // 检查 content.application/json.schema (OpenAPI 3.0) + final applicationJsonMediaType = response.content['application/json']; + if (applicationJsonMediaType != null) { + final schema = applicationJsonMediaType.schema; + if (schema != null && _isArraySchema(schema)) { + return true; } } - // 检查 schema 字段 (Swagger 2.0) + // 检查 schema 字段 (Swagger 2.0 兼容) if (response.schema != null && _isArraySchema(response.schema!)) { return true; } @@ -599,20 +669,17 @@ class RetrofitApiGenerator extends BaseGenerator { /// 从响应中提取返回类型 String? _extractResponseType(ApiResponse response) { - // 优先检查 content.application/json.schema (Swagger 3.0) - if (response.content != null) { - final applicationJson = - response.content!['application/json'] as Map?; - if (applicationJson != null) { - final schema = applicationJson['schema'] as Map?; - final type = _extractTypeFromSchema(schema); - if (type != null) { - return type; - } + // 优先检查 content.application/json.schema (OpenAPI 3.0) + final applicationJsonMediaType = response.content['application/json']; + if (applicationJsonMediaType != null) { + final schema = applicationJsonMediaType.schema; + final type = _extractTypeFromSchema(schema); + if (type != null) { + return type; } } - // 检查 schema 字段 (Swagger 2.0) + // 检查 schema 字段 (Swagger 2.0 兼容) final type = _extractTypeFromSchema(response.schema); if (type != null) { return type; @@ -625,6 +692,19 @@ class RetrofitApiGenerator extends BaseGenerator { String? _extractTypeFromSchema(Map? schema) { if (schema == null) return null; + // 处理高级 Schema 特性 + final advancedType = _handleAdvancedSchemaFeatures(schema); + if (advancedType != null) { + return advancedType; + } + + // 处理组合模式 (allOf/oneOf/anyOf) + if (schema['allOf'] != null || + schema['oneOf'] != null || + schema['anyOf'] != null) { + return _extractTypeFromCompositionSchema(schema); + } + // 处理 $ref 引用 if (schema['\$ref'] != null) { final ref = schema['\$ref'] as String; @@ -737,6 +817,14 @@ class RetrofitApiGenerator extends BaseGenerator { return 'double'; case 'boolean': return 'bool'; + case 'array': + // 处理数组类型 + final items = schema['items'] as Map?; + if (items != null) { + final itemType = _extractTypeFromSchema(items); + return 'List<${itemType ?? 'dynamic'}>'; + } + return 'List'; case 'null': return 'dynamic'; default: @@ -778,31 +866,6 @@ class RetrofitApiGenerator extends BaseGenerator { return null; } - /// 根据路径推断返回类型 - String? _inferReturnTypeFromPath(ApiPath path) { - final pathLower = path.path.toLowerCase(); - final summaryLower = path.summary.toLowerCase(); - final operationId = path.operationId.toLowerCase(); - final tags = path.tags.map((tag) => tag.toLowerCase()).toList(); - - // 基于 operationId 推断类型 - if (operationId.isNotEmpty) { - final inferredType = _inferTypeFromOperationId(operationId); - if (inferredType != null) { - return inferredType; - } - } - - // 基于路径关键词推断类型 - final inferredType = - _inferTypeFromPathKeywords(pathLower, summaryLower, tags); - if (inferredType != null) { - return inferredType; - } - - return null; - } - /// 生成参数列表 List _generateParameters(ApiPath path) { final parameters = []; @@ -817,6 +880,8 @@ class RetrofitApiGenerator extends BaseGenerator { type: _getDartType(param.type), annotation: useRetrofit ? '@Path(\'${param.name}\')' : '', required: param.required, + description: param.description, + defaultValue: param.defaultValue, )); } @@ -848,6 +913,8 @@ class RetrofitApiGenerator extends BaseGenerator { type: '${_getDartType(param.type)}$nullable', annotation: useRetrofit ? '@Query(\'${param.name}\')' : '', required: param.required, + description: param.description, + defaultValue: param.defaultValue, )); } } @@ -864,20 +931,27 @@ class RetrofitApiGenerator extends BaseGenerator { type: bodyType, annotation: useRetrofit ? '@Body()' : '', required: false, + description: param.description, + defaultValue: param.defaultValue, )); } - // 如果是 POST/PUT/PATCH 但没有明确的 body 参数,添加一个通用的 body 参数 + // 如果是 POST/PUT/PATCH 但没有明确的 body 参数,检查是否真的需要请求体 if ((path.method == HttpMethod.post || path.method == HttpMethod.put || path.method == HttpMethod.patch) && - bodyParams.isEmpty) { + bodyParams.isEmpty && + _needsRequestBody(path)) { final bodyType = _inferRequestBodyType(path); + final isRequired = path.requestBody?.required ?? false; + final nullable = isRequired ? '' : '?'; + parameters.add(ApiMethodParameter( name: 'request', - type: bodyType, + type: '$bodyType$nullable', annotation: useRetrofit ? '@Body()' : '', - required: false, + required: isRequired, + description: path.requestBody?.description ?? '', )); } @@ -894,60 +968,20 @@ class RetrofitApiGenerator extends BaseGenerator { } } - // 如果无法从 schema 解析,使用路径推断 - final pathLower = path.path.toLowerCase(); - - // 登录请求 - if (pathLower.contains('/login/userlogin') && !pathLower.contains('code')) { - return 'UserLoginRequest'; - } - - if (pathLower.contains('/login/usercodelogin') || - pathLower.contains('/getuserlogincode')) { - return 'LoginCodeRequest'; - } - - // 注册请求 - if (pathLower.contains('/register')) { - return 'RegisterRequest'; - } - - // 刷新token - if (pathLower.contains('/refreshtoken')) { - return 'RefreshTokenRequest'; - } - - // 修改密码 - if (pathLower.contains('/updatemypasswod')) { - return 'MyInfoResetPwdRequest'; - } - - // 换绑手机 - if (pathLower.contains('/updatemyphone')) { - return 'MyPhoneBindRequest'; - } - - // 班级相关请求 - if (pathLower.contains('/addclasses')) { - return 'ClassTeacherRequest'; - } - - // 默认使用通用类型 + // 如果没有明确的 requestBody schema 定义,使用通用类型 + // 这通常表示后端文档不完整,应该要求后端完善 swagger 文档 return 'Map'; } /// 从请求体中提取请求类型 String? _extractRequestBodyType(ApiRequestBody requestBody) { // 检查 content.application/json.schema - if (requestBody.content != null) { - final applicationJson = - requestBody.content!['application/json'] as Map?; - if (applicationJson != null) { - final schema = applicationJson['schema'] as Map?; - final type = _extractTypeFromSchema(schema); - if (type != null) { - return type; - } + final applicationJsonMediaType = requestBody.content['application/json']; + if (applicationJsonMediaType != null) { + final schema = applicationJsonMediaType.schema; + final type = _extractTypeFromSchema(schema); + if (type != null) { + return type; } } @@ -1128,14 +1162,6 @@ class RetrofitApiGenerator extends BaseGenerator { // 已移动到 StringUtils.cleanPath - /// 获取基础URL - String _getBaseUrl() { - if (document.schemes.isNotEmpty) { - return '${document.schemes.first}://${document.host}${document.basePath}'; - } - return 'https://${document.host}${document.basePath}'; - } - /// 按 tags 分组路径 Map> _groupPathsByTags() { final groups = >{}; @@ -1170,8 +1196,13 @@ class RetrofitApiGenerator extends BaseGenerator { // 导入基础响应类型(从用户项目中导入) buffer.writeln( 'import \'package:learning_officer_oa/common/models/common/base_result.dart\';'); - buffer.writeln( - 'import \'package:learning_officer_oa/common/models/common/base_page_result.dart\';'); + + // 只有在需要分页时才导入 base_page_result.dart + if (_needsPaginationImportForDocument()) { + buffer.writeln( + 'import \'package:learning_officer_oa/common/models/common/base_page_result.dart\';'); + } + buffer.writeln(''); // 导入错误处理相关类 @@ -1306,13 +1337,19 @@ class RetrofitApiGenerator extends BaseGenerator { // 导入基础响应类型(从用户项目中导入) buffer.writeln( 'import \'package:learning_officer_oa/common/models/common/base_result.dart\';'); - buffer.writeln( - 'import \'package:learning_officer_oa/common/models/common/base_page_result.dart\';'); + + // 只有在需要分页时才导入 base_page_result.dart + if (_needsPaginationImport(paths)) { + buffer.writeln( + 'import \'package:learning_officer_oa/common/models/common/base_page_result.dart\';'); + } + buffer.writeln(''); - // 导入生成的模型类 + // 导入生成的模型类(按字母顺序排序) final modelImports = _getRequiredModelImportsForPaths(paths); - for (final modelImport in modelImports) { + final sortedModelImports = modelImports.toList()..sort(); + for (final modelImport in sortedModelImports) { buffer.writeln( 'import \'../../api_models/${StringUtils.generateFileName(modelImport)}\';'); } @@ -1370,6 +1407,51 @@ class RetrofitApiGenerator extends BaseGenerator { return '${StringUtils.toPascalCase(tagName)}Api'; } + /// 检查整个文档是否需要导入分页相关类型 + bool _needsPaginationImportForDocument() { + for (final path in document.paths.values) { + final returnType = _generateReturnType(path); + // 检查返回类型是否包含 BasePageResult + if (returnType.contains('BasePageResult')) { + return true; + } + } + return false; + } + + /// 检查是否需要请求体 + bool _needsRequestBody(ApiPath path) { + // 如果有明确定义的 requestBody,则需要 + if (path.requestBody != null) { + return true; + } + + // 如果有 body 类型的参数,则需要 + final bodyParams = path.parameters + .where((p) => p.location == ParameterLocation.body) + .toList(); + if (bodyParams.isNotEmpty) { + return true; + } + + // 如果没有明确的 requestBody 或 body 参数定义,则不添加请求体 + // 这是最保守的做法,避免添加不必要的参数 + // 如果后端需要请求体但没有在 swagger 中定义,应该要求后端完善文档 + return false; + } + + /// 检查指定路径列表是否需要导入分页相关类型 + bool _needsPaginationImport(List paths) { + for (final path in paths) { + final returnType = _generateReturnType(path); + // 检查返回类型是否包含 BasePageResult + if (returnType.contains('BasePageResult')) { + return true; + } + } + return false; + } + /// 获取指定路径列表所需的模型导入 Set _getRequiredModelImportsForPaths(List paths) { final imports = {}; @@ -1622,102 +1704,6 @@ class RetrofitApiGenerator extends BaseGenerator { } } } - - /// 基于 operationId 推断类型 - String? _inferTypeFromOperationId(String operationId) { - // 基于 operationId 的模式匹配推断类型 - if (operationId.contains('login')) { - return 'UserLoginResult'; - } - if (operationId.contains('register')) { - return 'UserLoginResult'; - } - if (operationId.contains('get') && operationId.contains('list')) { - // 根据 operationId 推断列表类型 - if (operationId.contains('class')) { - return 'List'; - } - if (operationId.contains('task')) { - return 'List'; - } - if (operationId.contains('school')) { - return 'List'; - } - if (operationId.contains('user')) { - return 'List'; - } - } - if (operationId.contains('upload')) { - return 'SysFileViewDto'; - } - if (operationId.contains('config')) { - return 'OsConfigResult'; - } - if (operationId.contains('sign')) { - return 'OssSignResult'; - } - if (operationId.contains('version')) { - return 'UpdateappResult'; - } - - return null; - } - - /// 基于路径关键词推断类型 - String? _inferTypeFromPathKeywords( - String pathLower, String summaryLower, List tags) { - // 基于路径关键词的智能推断 - if (pathLower.contains('healthcheck') || pathLower.contains('health')) { - return 'void'; - } - - if (pathLower.contains('/login/')) { - return 'UserLoginResult'; - } - - if (pathLower.contains('/get') && pathLower.contains('list')) { - // 列表类型的推断 - if (pathLower.contains('class') && !pathLower.contains('task')) { - return 'List'; - } - if (pathLower.contains('task')) { - return 'List'; - } - if (pathLower.contains('school')) { - return 'List'; - } - if (pathLower.contains('user')) { - return 'List'; - } - } - - if (pathLower.contains('/upload') || pathLower.contains('/file')) { - return 'SysFileViewDto'; - } - - if (pathLower.contains('/config')) { - return 'OsConfigResult'; - } - - if (pathLower.contains('/sign')) { - return 'OssSignResult'; - } - - if (pathLower.contains('/version')) { - return 'UpdateappResult'; - } - - // 基于 summary 和 tags 的推断 - if (summaryLower.contains('列表') || summaryLower.contains('分页')) { - return 'List'; - } - - if (tags.any((tag) => tag.contains('task'))) { - return 'TaskInfoResult'; - } - - return null; - } } /// API 方法参数 @@ -1726,11 +1712,961 @@ class ApiMethodParameter { final String type; final String annotation; final bool required; + final String description; + final dynamic defaultValue; ApiMethodParameter({ required this.name, required this.type, required this.annotation, required this.required, + this.description = '', + this.defaultValue, }); } + +/// 组合模式处理扩展 +extension CompositionSchemaExtension on RetrofitApiGenerator { + /// 从组合模式 schema 中提取类型 + String? _extractTypeFromCompositionSchema(Map schema) { + // 优先处理带有 discriminator 的组合模式 + if (schema['discriminator'] != null) { + final discriminatorType = _handleDiscriminatorSchema(schema); + if (discriminatorType != null) { + return discriminatorType; + } + } + + // 处理 allOf - 合并所有 schema + if (schema['allOf'] != null) { + final allOfSchemas = schema['allOf'] as List; + return _handleAllOfSchema(allOfSchemas); + } + + // 处理 oneOf - 选择其中一个 schema (通常生成联合类型或基类) + if (schema['oneOf'] != null) { + final oneOfSchemas = schema['oneOf'] as List; + return _handleOneOfSchema(oneOfSchemas); + } + + // 处理 anyOf - 可以匹配一个或多个 schema + if (schema['anyOf'] != null) { + final anyOfSchemas = schema['anyOf'] as List; + return _handleAnyOfSchema(anyOfSchemas); + } + + return null; + } + + /// 处理 allOf 组合模式 + String? _handleAllOfSchema(List schemas) { + // allOf 表示必须满足所有 schema,通常用于继承或组合 + // 我们尝试找到第一个有具体类型的 schema + for (final schemaData in schemas) { + if (schemaData is Map) { + // 如果是引用,直接返回引用的类型 + if (schemaData['\$ref'] != null) { + final ref = schemaData['\$ref'] as String; + final refName = ref.split('/').last; + return StringUtils.generateClassName(refName); + } + + // 如果有具体类型,返回该类型 + if (schemaData['type'] != null) { + final type = schemaData['type'] as String; + if (type == 'object') { + // 对于对象类型,我们可能需要生成一个组合类型 + // 暂时返回 Map + return 'Map'; + } else if (type == 'array') { + // 处理数组类型 + final items = schemaData['items']; + if (items != null) { + final itemType = + _extractTypeFromSchema(items as Map?); + return 'List<${itemType ?? 'dynamic'}>'; + } + return 'List'; + } else { + return _mapJsonTypeToFlutterType(type); + } + } + } + } + + // 如果没有找到具体类型,返回通用类型 + return 'Map'; + } + + /// 处理 oneOf 组合模式 + String? _handleOneOfSchema(List schemas) { + // oneOf 表示必须匹配其中一个 schema,通常用于联合类型 + // 在 Dart 中,我们可以使用基类或 Object 类型 + + // 检查是否所有 schema 都是引用类型 + final refTypes = []; + for (final schemaData in schemas) { + if (schemaData is Map && schemaData['\$ref'] != null) { + final ref = schemaData['\$ref'] as String; + final refName = ref.split('/').last; + refTypes.add(StringUtils.generateClassName(refName)); + } + } + + if (refTypes.isNotEmpty) { + // 如果有多个引用类型,返回 Object 或第一个类型 + if (refTypes.length == 1) { + return refTypes.first; + } else { + // 对于多个类型,我们可以返回 Object 或创建联合类型 + return 'Object'; // 或者可以生成联合类型接口 + } + } + + // 如果不是引用类型,尝试提取第一个有效类型 + for (final schemaData in schemas) { + if (schemaData is Map) { + final extractedType = _extractTypeFromSchema(schemaData); + if (extractedType != null) { + return extractedType; + } + } + } + + return 'Object'; + } + + /// 处理带有 discriminator 的组合模式 + String? _handleDiscriminatorSchema(Map schema) { + final discriminatorData = schema['discriminator'] as Map?; + if (discriminatorData == null) return null; + + final mapping = discriminatorData['mapping'] as Map? ?? {}; + + // 如果有 oneOf 或 anyOf,并且有 discriminator,我们可以生成更智能的类型 + if (schema['oneOf'] != null || schema['anyOf'] != null) { + final schemas = (schema['oneOf'] ?? schema['anyOf']) as List; + + // 如果有映射表,我们可以根据映射生成联合类型 + if (mapping.isNotEmpty) { + final mappedTypes = []; + mapping.values.forEach((value) { + if (value is String) { + // 提取引用的类型名 + final refName = value.split('/').last; + mappedTypes.add(StringUtils.generateClassName(refName)); + } + }); + + if (mappedTypes.isNotEmpty) { + // 返回第一个类型作为基类,或者 Object + return mappedTypes.first; + } + } + + // 如果没有映射表,使用默认的 oneOf 处理 + return _handleOneOfSchema(schemas); + } + + return null; + } + + /// 处理 anyOf 组合模式 + String? _handleAnyOfSchema(List schemas) { + // anyOf 表示可以匹配一个或多个 schema + // 处理方式类似 oneOf,但更宽松 + return _handleOneOfSchema(schemas); + } + + /// 将 JSON Schema 类型映射到 Flutter 类型 + String _mapJsonTypeToFlutterType(String jsonType) { + switch (jsonType) { + case 'string': + return 'String'; + case 'integer': + return 'int'; + case 'number': + return 'double'; + case 'boolean': + return 'bool'; + case 'array': + return 'List'; + case 'object': + return 'Map'; + default: + return 'dynamic'; + } + } + + /// 处理高级 Schema 特性 + String? _handleAdvancedSchemaFeatures(Map schema) { + // 处理 const 值 + if (schema['const'] != null) { + final constValue = schema['const']; + if (constValue is String) { + return 'String'; // 常量字符串 + } else if (constValue is num) { + return constValue is int ? 'int' : 'double'; + } else if (constValue is bool) { + return 'bool'; + } + return 'dynamic'; + } + + // 处理 additionalProperties + if (schema['additionalProperties'] != null) { + final additionalProps = schema['additionalProperties']; + if (additionalProps is bool) { + return additionalProps ? 'Map' : 'Map'; + } else if (additionalProps is Map) { + // additionalProperties 是一个 schema + final valueType = _extractTypeFromSchema(additionalProps); + return 'Map'; + } + } + + // 处理 patternProperties + if (schema['patternProperties'] != null) { + final patternProps = schema['patternProperties'] as Map?; + if (patternProps != null && patternProps.isNotEmpty) { + // 对于模式属性,我们通常返回 Map + // 因为 Dart 不支持基于正则表达式的类型约束 + return 'Map'; + } + } + + // 处理条件 Schema (if/then/else) + if (schema['if'] != null || + schema['then'] != null || + schema['else'] != null) { + // 对于条件 Schema,我们尝试从 then 或 else 中提取类型 + if (schema['then'] != null) { + final thenType = + _extractTypeFromSchema(schema['then'] as Map?); + if (thenType != null) return thenType; + } + if (schema['else'] != null) { + final elseType = + _extractTypeFromSchema(schema['else'] as Map?); + if (elseType != null) return elseType; + } + return 'dynamic'; // 无法确定具体类型 + } + + return null; + } + + /// 生成安全方案相关的代码 + String _generateSecurityCode(SwaggerDocument document) { + final buffer = StringBuffer(); + + // 生成安全方案常量 + if (document.components.securitySchemes.isNotEmpty) { + buffer.writeln('// Security Schemes'); + buffer.writeln('class SecuritySchemes {'); + + document.components.securitySchemes.forEach((name, scheme) { + buffer.writeln(' /// ${scheme.description}'); + if (scheme.isApiKey) { + buffer.writeln( + ' static const String ${StringUtils.generateConstantName(name)} = \'$name\';'); + buffer.writeln( + ' static const String ${StringUtils.generateConstantName(name)}_PARAM = \'${scheme.name}\';'); + buffer.writeln( + ' static const String ${StringUtils.generateConstantName(name)}_LOCATION = \'${scheme.location?.value}\';'); + } else if (scheme.isHttp) { + buffer.writeln( + ' static const String ${StringUtils.generateConstantName(name)} = \'$name\';'); + buffer.writeln( + ' static const String ${StringUtils.generateConstantName(name)}_SCHEME = \'${scheme.scheme}\';'); + if (scheme.bearerFormat != null) { + buffer.writeln( + ' static const String ${StringUtils.generateConstantName(name)}_FORMAT = \'${scheme.bearerFormat}\';'); + } + } else if (scheme.isOAuth2) { + buffer.writeln( + ' static const String ${StringUtils.generateConstantName(name)} = \'$name\';'); + if (scheme.flows?.hasAnyFlow == true) { + final flows = scheme.flows!; + flows.availableFlows.forEach((flowType) { + final flow = flows.getFlow(flowType); + if (flow != null) { + final flowName = StringUtils.generateConstantName( + '${name}_${flowType.value}'); + if (flow.hasAuthorizationUrl) { + buffer.writeln( + ' static const String ${flowName}_AUTH_URL = \'${flow.authorizationUrl}\';'); + } + if (flow.hasTokenUrl) { + buffer.writeln( + ' static const String ${flowName}_TOKEN_URL = \'${flow.tokenUrl}\';'); + } + if (flow.hasScopes) { + buffer.writeln( + ' static const Map ${flowName}_SCOPES = {'); + flow.scopes.forEach((scope, description) { + buffer.writeln(' \'$scope\': \'$description\','); + }); + buffer.writeln(' };'); + } + } + }); + } + } + buffer.writeln(); + }); + + buffer.writeln('}'); + buffer.writeln(); + } + + // 生成安全拦截器 + buffer.writeln('// Security Interceptors'); + buffer.writeln('class ApiKeyInterceptor extends Interceptor {'); + buffer.writeln(' final String apiKey;'); + buffer.writeln(' final String paramName;'); + buffer.writeln(' final String location;'); + buffer.writeln(); + buffer.writeln(' ApiKeyInterceptor({'); + buffer.writeln(' required this.apiKey,'); + buffer.writeln(' required this.paramName,'); + buffer.writeln(' required this.location,'); + buffer.writeln(' });'); + buffer.writeln(); + buffer.writeln(' @override'); + buffer.writeln( + ' void onRequest(RequestOptions options, RequestInterceptorHandler handler) {'); + buffer.writeln(' switch (location) {'); + buffer.writeln(' case \'header\':'); + buffer.writeln(' options.headers[paramName] = apiKey;'); + buffer.writeln(' break;'); + buffer.writeln(' case \'query\':'); + buffer.writeln(' options.queryParameters[paramName] = apiKey;'); + buffer.writeln(' break;'); + buffer.writeln(' case \'cookie\':'); + buffer.writeln( + ' options.headers[\'Cookie\'] = \'\$paramName=\$apiKey\';'); + buffer.writeln(' break;'); + buffer.writeln(' }'); + buffer.writeln(' handler.next(options);'); + buffer.writeln(' }'); + buffer.writeln('}'); + buffer.writeln(); + + buffer.writeln('class BearerTokenInterceptor extends Interceptor {'); + buffer.writeln(' final String token;'); + buffer.writeln(' final String? tokenPrefix;'); + buffer.writeln(); + buffer.writeln(' BearerTokenInterceptor({'); + buffer.writeln(' required this.token,'); + buffer.writeln(' this.tokenPrefix = \'Bearer\','); + buffer.writeln(' });'); + buffer.writeln(); + buffer.writeln(' @override'); + buffer.writeln( + ' void onRequest(RequestOptions options, RequestInterceptorHandler handler) {'); + buffer.writeln(' if (tokenPrefix != null && tokenPrefix!.isNotEmpty) {'); + buffer.writeln( + ' options.headers[\'Authorization\'] = \'\$tokenPrefix \$token\';'); + buffer.writeln(' } else {'); + buffer.writeln(' options.headers[\'Authorization\'] = token;'); + buffer.writeln(' }'); + buffer.writeln(' handler.next(options);'); + buffer.writeln(' }'); + buffer.writeln('}'); + buffer.writeln(); + + buffer.writeln('class BasicAuthInterceptor extends Interceptor {'); + buffer.writeln(' final String username;'); + buffer.writeln(' final String password;'); + buffer.writeln(); + buffer.writeln(' BasicAuthInterceptor({'); + buffer.writeln(' required this.username,'); + buffer.writeln(' required this.password,'); + buffer.writeln(' });'); + buffer.writeln(); + buffer.writeln(' @override'); + buffer.writeln( + ' void onRequest(RequestOptions options, RequestInterceptorHandler handler) {'); + buffer.writeln( + ' final credentials = base64Encode(utf8.encode(\'\$username:\$password\'));'); + buffer.writeln( + ' options.headers[\'Authorization\'] = \'Basic \$credentials\';'); + buffer.writeln(' handler.next(options);'); + buffer.writeln(' }'); + buffer.writeln('}'); + buffer.writeln(); + + buffer.writeln('class DigestAuthInterceptor extends Interceptor {'); + buffer.writeln(' final String username;'); + buffer.writeln(' final String password;'); + buffer.writeln(' String? _realm;'); + buffer.writeln(' String? _nonce;'); + buffer.writeln(' String? _qop;'); + buffer.writeln(' String? _opaque;'); + buffer.writeln(); + buffer.writeln(' DigestAuthInterceptor({'); + buffer.writeln(' required this.username,'); + buffer.writeln(' required this.password,'); + buffer.writeln(' });'); + buffer.writeln(); + buffer.writeln(' @override'); + buffer.writeln( + ' void onRequest(RequestOptions options, RequestInterceptorHandler handler) {'); + buffer.writeln(' if (_nonce != null) {'); + buffer.writeln(' final uri = options.uri.toString();'); + buffer.writeln(' final method = options.method;'); + buffer.writeln( + ' final ha1 = md5.convert(utf8.encode(\'\$username:\$_realm:\$password\')).toString();'); + buffer.writeln( + ' final ha2 = md5.convert(utf8.encode(\'\$method:\$uri\')).toString();'); + buffer.writeln( + ' final response = md5.convert(utf8.encode(\'\$ha1:\$_nonce:\$ha2\')).toString();'); + buffer.writeln(); + buffer.writeln( + ' final authHeader = \'Digest username="\$username", realm="\$_realm", \' +'); + buffer.writeln( + ' \'nonce="\$_nonce", uri="\$uri", response="\$response"\';'); + buffer.writeln(); + buffer.writeln(' if (_qop != null) {'); + buffer.writeln(' // TODO: Implement qop support'); + buffer.writeln(' }'); + buffer.writeln(); + buffer.writeln(' if (_opaque != null) {'); + buffer.writeln(' // authHeader += \', opaque="\$_opaque"\';'); + buffer.writeln(' }'); + buffer.writeln(); + buffer.writeln(' options.headers[\'Authorization\'] = authHeader;'); + buffer.writeln(' }'); + buffer.writeln(' handler.next(options);'); + buffer.writeln(' }'); + buffer.writeln(); + buffer.writeln(' @override'); + buffer.writeln( + ' void onError(DioException err, ErrorInterceptorHandler handler) {'); + buffer.writeln(' if (err.response?.statusCode == 401) {'); + buffer.writeln( + ' final wwwAuth = err.response?.headers[\'www-authenticate\']?.first;'); + buffer.writeln( + ' if (wwwAuth != null && wwwAuth.startsWith(\'Digest\')) {'); + buffer.writeln(' _parseDigestChallenge(wwwAuth);'); + buffer.writeln(' // Retry the request with digest auth'); + buffer.writeln(' final options = err.requestOptions;'); + buffer.writeln(' onRequest(options, RequestInterceptorHandler());'); + buffer.writeln( + ' // Note: In real implementation, you would retry the request here'); + buffer.writeln(' }'); + buffer.writeln(' }'); + buffer.writeln(' handler.next(err);'); + buffer.writeln(' }'); + buffer.writeln(); + buffer.writeln(' void _parseDigestChallenge(String challenge) {'); + buffer.writeln(' final regex = RegExp(r\'(\\w+)="([^"]+)"\');'); + buffer.writeln(' final matches = regex.allMatches(challenge);'); + buffer.writeln(' for (final match in matches) {'); + buffer.writeln(' final key = match.group(1);'); + buffer.writeln(' final value = match.group(2);'); + buffer.writeln(' switch (key) {'); + buffer.writeln(' case \'realm\':'); + buffer.writeln(' _realm = value;'); + buffer.writeln(' break;'); + buffer.writeln(' case \'nonce\':'); + buffer.writeln(' _nonce = value;'); + buffer.writeln(' break;'); + buffer.writeln(' case \'qop\':'); + buffer.writeln(' _qop = value;'); + buffer.writeln(' break;'); + buffer.writeln(' case \'opaque\':'); + buffer.writeln(' _opaque = value;'); + buffer.writeln(' break;'); + buffer.writeln(' }'); + buffer.writeln(' }'); + buffer.writeln(' }'); + buffer.writeln('}'); + buffer.writeln(); + + return buffer.toString(); + } + + /// 生成媒体类型处理代码 + String _generateMediaTypeHandlers() { + final buffer = StringBuffer(); + + buffer.writeln('// Media Type Handlers'); + buffer.writeln('class MediaTypeHandler {'); + buffer.writeln(' /// 处理 JSON 数据'); + buffer.writeln(' static Map handleJson(dynamic data) {'); + buffer.writeln(' if (data is String) {'); + buffer.writeln(' return jsonDecode(data) as Map;'); + buffer.writeln(' } else if (data is Map) {'); + buffer.writeln(' return data;'); + buffer.writeln(' }'); + buffer.writeln(' throw ArgumentError(\'Invalid JSON data type\');'); + buffer.writeln(' }'); + buffer.writeln(); + + buffer.writeln(' /// 处理 XML 数据'); + buffer.writeln(' static String handleXml(dynamic data) {'); + buffer.writeln(' if (data is String) {'); + buffer.writeln(' return data;'); + buffer.writeln(' }'); + buffer.writeln(' return data.toString();'); + buffer.writeln(' }'); + buffer.writeln(); + + buffer.writeln(' /// 处理表单数据'); + buffer.writeln( + ' static FormData handleFormData(Map data) {'); + buffer.writeln(' final formData = FormData();'); + buffer.writeln(' data.forEach((key, value) {'); + buffer.writeln(' if (value is MultipartFile) {'); + buffer.writeln(' formData.files.add(MapEntry(key, value));'); + buffer.writeln(' } else if (value is List) {'); + buffer.writeln(' for (final file in value) {'); + buffer.writeln(' formData.files.add(MapEntry(key, file));'); + buffer.writeln(' }'); + buffer.writeln(' } else {'); + buffer.writeln( + ' formData.fields.add(MapEntry(key, value.toString()));'); + buffer.writeln(' }'); + buffer.writeln(' });'); + buffer.writeln(' return formData;'); + buffer.writeln(' }'); + buffer.writeln(); + + buffer.writeln(' /// 处理 URL 编码表单数据'); + buffer.writeln( + ' static String handleUrlEncodedForm(Map data) {'); + buffer.writeln(' final params = [];'); + buffer.writeln(' data.forEach((key, value) {'); + buffer.writeln(' final encodedKey = Uri.encodeComponent(key);'); + buffer.writeln( + ' final encodedValue = Uri.encodeComponent(value.toString());'); + buffer.writeln(' params.add(\'\$encodedKey=\$encodedValue\');'); + buffer.writeln(' });'); + buffer.writeln(' return params.join(\'&\');'); + buffer.writeln(' }'); + buffer.writeln(); + + buffer.writeln(' /// 处理二进制数据'); + buffer.writeln(' static List handleBinary(dynamic data) {'); + buffer.writeln(' if (data is List) {'); + buffer.writeln(' return data;'); + buffer.writeln(' } else if (data is String) {'); + buffer.writeln(' return utf8.encode(data);'); + buffer.writeln(' }'); + buffer.writeln(' throw ArgumentError(\'Invalid binary data type\');'); + buffer.writeln(' }'); + buffer.writeln(); + + buffer.writeln(' /// 处理文本数据'); + buffer.writeln(' static String handleText(dynamic data) {'); + buffer.writeln(' return data.toString();'); + buffer.writeln(' }'); + buffer.writeln(); + + buffer.writeln(' /// 根据媒体类型处理数据'); + buffer.writeln( + ' static dynamic handleByMediaType(String mediaType, dynamic data) {'); + buffer.writeln(' switch (mediaType.toLowerCase()) {'); + buffer.writeln(' case \'application/json\':'); + buffer.writeln(' return handleJson(data);'); + buffer.writeln(' case \'application/xml\':'); + buffer.writeln(' case \'text/xml\':'); + buffer.writeln(' return handleXml(data);'); + buffer.writeln(' case \'multipart/form-data\':'); + buffer.writeln( + ' return handleFormData(data as Map);'); + buffer.writeln(' case \'application/x-www-form-urlencoded\':'); + buffer.writeln( + ' return handleUrlEncodedForm(data as Map);'); + buffer.writeln(' case \'application/octet-stream\':'); + buffer.writeln(' case \'application/pdf\':'); + buffer.writeln(' case \'image/png\':'); + buffer.writeln(' case \'image/jpeg\':'); + buffer.writeln(' case \'image/gif\':'); + buffer.writeln(' case \'audio/mpeg\':'); + buffer.writeln(' case \'video/mp4\':'); + buffer.writeln(' return handleBinary(data);'); + buffer.writeln(' case \'text/plain\':'); + buffer.writeln(' case \'text/html\':'); + buffer.writeln(' case \'text/csv\':'); + buffer.writeln(' case \'image/svg+xml\':'); + buffer.writeln(' return handleText(data);'); + buffer.writeln(' default:'); + buffer.writeln(' return data;'); + buffer.writeln(' }'); + buffer.writeln(' }'); + buffer.writeln('}'); + buffer.writeln(); + + return buffer.toString(); + } + + /// 生成文件上传处理代码 + String _generateFileUploadHandlers() { + final buffer = StringBuffer(); + + buffer.writeln('// File Upload Handlers'); + buffer.writeln('class FileUploadHandler {'); + buffer.writeln(' /// 创建单个文件的 MultipartFile'); + buffer.writeln(' static Future createMultipartFile({'); + buffer.writeln(' required String filePath,'); + buffer.writeln(' String? filename,'); + buffer.writeln(' String? contentType,'); + buffer.writeln(' }) async {'); + buffer.writeln(' return MultipartFile.fromFile('); + buffer.writeln(' filePath,'); + buffer.writeln(' filename: filename ?? path.basename(filePath),'); + buffer.writeln( + ' contentType: contentType != null ? MediaType.parse(contentType) : null,'); + buffer.writeln(' );'); + buffer.writeln(' }'); + buffer.writeln(); + + buffer.writeln(' /// 从字节数组创建 MultipartFile'); + buffer.writeln(' static MultipartFile createMultipartFileFromBytes({'); + buffer.writeln(' required List bytes,'); + buffer.writeln(' required String filename,'); + buffer.writeln(' String? contentType,'); + buffer.writeln(' }) {'); + buffer.writeln(' return MultipartFile.fromBytes('); + buffer.writeln(' bytes,'); + buffer.writeln(' filename: filename,'); + buffer.writeln( + ' contentType: contentType != null ? MediaType.parse(contentType) : null,'); + buffer.writeln(' );'); + buffer.writeln(' }'); + buffer.writeln(); + + buffer.writeln(' /// 从流创建 MultipartFile'); + buffer.writeln(' static MultipartFile createMultipartFileFromStream({'); + buffer.writeln(' required Stream> stream,'); + buffer.writeln(' required int length,'); + buffer.writeln(' required String filename,'); + buffer.writeln(' String? contentType,'); + buffer.writeln(' }) {'); + buffer.writeln(' return MultipartFile('); + buffer.writeln(' stream,'); + buffer.writeln(' length,'); + buffer.writeln(' filename: filename,'); + buffer.writeln( + ' contentType: contentType != null ? MediaType.parse(contentType) : null,'); + buffer.writeln(' );'); + buffer.writeln(' }'); + buffer.writeln(); + + buffer.writeln(' /// 创建图片文件的 MultipartFile'); + buffer.writeln(' static Future createImageFile({'); + buffer.writeln(' required String filePath,'); + buffer.writeln(' String? filename,'); + buffer.writeln(' }) async {'); + buffer.writeln( + ' final extension = path.extension(filePath).toLowerCase();'); + buffer.writeln(' String? contentType;'); + buffer.writeln(' switch (extension) {'); + buffer.writeln(' case \'.jpg\':'); + buffer.writeln(' case \'.jpeg\':'); + buffer.writeln(' contentType = \'image/jpeg\';'); + buffer.writeln(' break;'); + buffer.writeln(' case \'.png\':'); + buffer.writeln(' contentType = \'image/png\';'); + buffer.writeln(' break;'); + buffer.writeln(' case \'.gif\':'); + buffer.writeln(' contentType = \'image/gif\';'); + buffer.writeln(' break;'); + buffer.writeln(' case \'.svg\':'); + buffer.writeln(' contentType = \'image/svg+xml\';'); + buffer.writeln(' break;'); + buffer.writeln(' default:'); + buffer.writeln(' contentType = \'application/octet-stream\';'); + buffer.writeln(' }'); + buffer.writeln(' return createMultipartFile('); + buffer.writeln(' filePath: filePath,'); + buffer.writeln(' filename: filename,'); + buffer.writeln(' contentType: contentType,'); + buffer.writeln(' );'); + buffer.writeln(' }'); + buffer.writeln(); + + buffer.writeln(' /// 创建音频文件的 MultipartFile'); + buffer.writeln(' static Future createAudioFile({'); + buffer.writeln(' required String filePath,'); + buffer.writeln(' String? filename,'); + buffer.writeln(' }) async {'); + buffer.writeln( + ' final extension = path.extension(filePath).toLowerCase();'); + buffer.writeln(' String? contentType;'); + buffer.writeln(' switch (extension) {'); + buffer.writeln(' case \'.mp3\':'); + buffer.writeln(' contentType = \'audio/mpeg\';'); + buffer.writeln(' break;'); + buffer.writeln(' case \'.wav\':'); + buffer.writeln(' contentType = \'audio/wav\';'); + buffer.writeln(' break;'); + buffer.writeln(' case \'.ogg\':'); + buffer.writeln(' contentType = \'audio/ogg\';'); + buffer.writeln(' break;'); + buffer.writeln(' default:'); + buffer.writeln(' contentType = \'audio/mpeg\';'); + buffer.writeln(' }'); + buffer.writeln(' return createMultipartFile('); + buffer.writeln(' filePath: filePath,'); + buffer.writeln(' filename: filename,'); + buffer.writeln(' contentType: contentType,'); + buffer.writeln(' );'); + buffer.writeln(' }'); + buffer.writeln(); + + buffer.writeln(' /// 创建视频文件的 MultipartFile'); + buffer.writeln(' static Future createVideoFile({'); + buffer.writeln(' required String filePath,'); + buffer.writeln(' String? filename,'); + buffer.writeln(' }) async {'); + buffer.writeln( + ' final extension = path.extension(filePath).toLowerCase();'); + buffer.writeln(' String? contentType;'); + buffer.writeln(' switch (extension) {'); + buffer.writeln(' case \'.mp4\':'); + buffer.writeln(' contentType = \'video/mp4\';'); + buffer.writeln(' break;'); + buffer.writeln(' case \'.avi\':'); + buffer.writeln(' contentType = \'video/x-msvideo\';'); + buffer.writeln(' break;'); + buffer.writeln(' case \'.mov\':'); + buffer.writeln(' contentType = \'video/quicktime\';'); + buffer.writeln(' break;'); + buffer.writeln(' default:'); + buffer.writeln(' contentType = \'video/mp4\';'); + buffer.writeln(' }'); + buffer.writeln(' return createMultipartFile('); + buffer.writeln(' filePath: filePath,'); + buffer.writeln(' filename: filename,'); + buffer.writeln(' contentType: contentType,'); + buffer.writeln(' );'); + buffer.writeln(' }'); + buffer.writeln(); + + buffer.writeln(' /// 验证文件大小'); + buffer.writeln( + ' static bool validateFileSize(String filePath, int maxSizeInBytes) {'); + buffer.writeln(' final file = File(filePath);'); + buffer.writeln(' if (!file.existsSync()) return false;'); + buffer.writeln(' return file.lengthSync() <= maxSizeInBytes;'); + buffer.writeln(' }'); + buffer.writeln(); + + buffer.writeln(' /// 验证文件类型'); + buffer.writeln( + ' static bool validateFileType(String filePath, List allowedExtensions) {'); + buffer.writeln( + ' final extension = path.extension(filePath).toLowerCase();'); + buffer.writeln(' return allowedExtensions.contains(extension);'); + buffer.writeln(' }'); + buffer.writeln(); + + buffer.writeln(' /// 批量创建文件'); + buffer + .writeln(' static Future> createMultipleFiles({'); + buffer.writeln(' required List filePaths,'); + buffer.writeln(' String? contentType,'); + buffer.writeln(' }) async {'); + buffer.writeln(' final files = [];'); + buffer.writeln(' for (final filePath in filePaths) {'); + buffer.writeln(' final file = await createMultipartFile('); + buffer.writeln(' filePath: filePath,'); + buffer.writeln(' contentType: contentType,'); + buffer.writeln(' );'); + buffer.writeln(' files.add(file);'); + buffer.writeln(' }'); + buffer.writeln(' return files;'); + buffer.writeln(' }'); + buffer.writeln('}'); + buffer.writeln(); + + return buffer.toString(); + } + + /// 生成编码处理代码 + String _generateEncodingHandlers() { + final buffer = StringBuffer(); + + buffer.writeln('// Encoding Handlers'); + buffer.writeln('class EncodingHandler {'); + buffer.writeln(' /// 支持的字符编码'); + buffer + .writeln(' static const Map supportedEncodings = {'); + buffer.writeln(' \'utf-8\': utf8,'); + buffer.writeln(' \'utf8\': utf8,'); + buffer.writeln(' \'ascii\': ascii,'); + buffer.writeln(' \'latin1\': latin1,'); + buffer.writeln(' \'iso-8859-1\': latin1,'); + buffer.writeln(' };'); + buffer.writeln(); + + buffer.writeln(' /// 根据编码名称获取编码器'); + buffer.writeln(' static Encoding getEncoding(String? encodingName) {'); + buffer.writeln(' if (encodingName == null) return utf8;'); + buffer.writeln( + ' final normalizedName = encodingName.toLowerCase().replaceAll(\'_\', \'-\');'); + buffer.writeln(' return supportedEncodings[normalizedName] ?? utf8;'); + buffer.writeln(' }'); + buffer.writeln(); + + buffer.writeln(' /// 编码字符串'); + buffer.writeln( + ' static List encodeString(String data, [String? encodingName]) {'); + buffer.writeln(' final encoding = getEncoding(encodingName);'); + buffer.writeln(' return encoding.encode(data);'); + buffer.writeln(' }'); + buffer.writeln(); + + buffer.writeln(' /// 解码字节数组'); + buffer.writeln( + ' static String decodeBytes(List bytes, [String? encodingName]) {'); + buffer.writeln(' final encoding = getEncoding(encodingName);'); + buffer.writeln(' return encoding.decode(bytes);'); + buffer.writeln(' }'); + buffer.writeln(); + + buffer.writeln(' /// Base64 编码'); + buffer.writeln(' static String encodeBase64(List bytes) {'); + buffer.writeln(' return base64Encode(bytes);'); + buffer.writeln(' }'); + buffer.writeln(); + + buffer.writeln(' /// Base64 解码'); + buffer.writeln(' static List decodeBase64(String data) {'); + buffer.writeln(' return base64Decode(data);'); + buffer.writeln(' }'); + buffer.writeln(); + + buffer.writeln(' /// URL 编码'); + buffer.writeln(' static String encodeUrl(String data) {'); + buffer.writeln(' return Uri.encodeComponent(data);'); + buffer.writeln(' }'); + buffer.writeln(); + + buffer.writeln(' /// URL 解码'); + buffer.writeln(' static String decodeUrl(String data) {'); + buffer.writeln(' return Uri.decodeComponent(data);'); + buffer.writeln(' }'); + buffer.writeln(); + + buffer.writeln(' /// 处理 Content-Encoding'); + buffer.writeln( + ' static List handleContentEncoding(List data, String? encoding) {'); + buffer.writeln(' if (encoding == null) return data;'); + buffer.writeln(' switch (encoding.toLowerCase()) {'); + buffer.writeln(' case \'gzip\':'); + buffer.writeln(' return gzip.decode(data);'); + buffer.writeln(' case \'deflate\':'); + buffer.writeln(' return zlib.decode(data);'); + buffer.writeln(' case \'br\':'); + buffer.writeln(' // Brotli 解码需要额外的包支持'); + buffer.writeln( + ' throw UnsupportedError(\'Brotli encoding not supported\');'); + buffer.writeln(' case \'identity\':'); + buffer.writeln(' default:'); + buffer.writeln(' return data;'); + buffer.writeln(' }'); + buffer.writeln(' }'); + buffer.writeln(); + + buffer.writeln(' /// 处理 Transfer-Encoding'); + buffer.writeln( + ' static List handleTransferEncoding(List data, String? encoding) {'); + buffer.writeln(' if (encoding == null) return data;'); + buffer.writeln(' switch (encoding.toLowerCase()) {'); + buffer.writeln(' case \'chunked\':'); + buffer.writeln(' return _decodeChunked(data);'); + buffer.writeln(' case \'compress\':'); + buffer.writeln( + ' throw UnsupportedError(\'Compress transfer encoding not supported\');'); + buffer.writeln(' case \'deflate\':'); + buffer.writeln(' return zlib.decode(data);'); + buffer.writeln(' case \'gzip\':'); + buffer.writeln(' return gzip.decode(data);'); + buffer.writeln(' case \'identity\':'); + buffer.writeln(' default:'); + buffer.writeln(' return data;'); + buffer.writeln(' }'); + buffer.writeln(' }'); + buffer.writeln(); + + buffer.writeln(' /// 解码分块传输编码'); + buffer.writeln(' static List _decodeChunked(List data) {'); + buffer.writeln(' final result = [];'); + buffer.writeln(' var offset = 0;'); + buffer.writeln(' while (offset < data.length) {'); + buffer.writeln(' // 查找块大小行的结束'); + buffer.writeln(' var lineEnd = offset;'); + buffer.writeln(' while (lineEnd < data.length - 1) {'); + buffer.writeln( + ' if (data[lineEnd] == 13 && data[lineEnd + 1] == 10) break; // \\r\\n'); + buffer.writeln(' lineEnd++;'); + buffer.writeln(' }'); + buffer.writeln(' if (lineEnd >= data.length - 1) break;'); + buffer.writeln(); + buffer.writeln(' // 解析块大小'); + buffer.writeln( + ' final sizeHex = String.fromCharCodes(data.sublist(offset, lineEnd));'); + buffer.writeln( + ' final chunkSize = int.tryParse(sizeHex, radix: 16) ?? 0;'); + buffer.writeln(' if (chunkSize == 0) break; // 最后一个块'); + buffer.writeln(); + buffer.writeln(' // 跳过 \\r\\n'); + buffer.writeln(' offset = lineEnd + 2;'); + buffer.writeln(); + buffer.writeln(' // 读取块数据'); + buffer.writeln(' if (offset + chunkSize <= data.length) {'); + buffer.writeln( + ' result.addAll(data.sublist(offset, offset + chunkSize));'); + buffer.writeln(' offset += chunkSize + 2; // 跳过块数据后的 \\r\\n'); + buffer.writeln(' } else {'); + buffer.writeln(' break;'); + buffer.writeln(' }'); + buffer.writeln(' }'); + buffer.writeln(' return result;'); + buffer.writeln(' }'); + buffer.writeln(); + + buffer.writeln(' /// 检测字符编码'); + buffer.writeln(' static String? detectEncoding(List bytes) {'); + buffer.writeln(' // 检测 BOM'); + buffer.writeln(' if (bytes.length >= 3) {'); + buffer.writeln( + ' if (bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF) {'); + buffer.writeln(' return \'utf-8\';'); + buffer.writeln(' }'); + buffer.writeln(' }'); + buffer.writeln(' if (bytes.length >= 2) {'); + buffer.writeln(' if (bytes[0] == 0xFF && bytes[1] == 0xFE) {'); + buffer.writeln(' return \'utf-16le\';'); + buffer.writeln(' }'); + buffer.writeln(' if (bytes[0] == 0xFE && bytes[1] == 0xFF) {'); + buffer.writeln(' return \'utf-16be\';'); + buffer.writeln(' }'); + buffer.writeln(' }'); + buffer.writeln(' // 默认假设为 UTF-8'); + buffer.writeln(' return \'utf-8\';'); + buffer.writeln(' }'); + buffer.writeln(); + + buffer.writeln(' /// 验证编码是否有效'); + buffer.writeln( + ' static bool isValidEncoding(List bytes, String encodingName) {'); + buffer.writeln(' try {'); + buffer.writeln(' final encoding = getEncoding(encodingName);'); + buffer.writeln(' encoding.decode(bytes);'); + buffer.writeln(' return true;'); + buffer.writeln(' } catch (e) {'); + buffer.writeln(' return false;'); + buffer.writeln(' }'); + buffer.writeln(' }'); + buffer.writeln('}'); + buffer.writeln(); + + return buffer.toString(); + } +} diff --git a/lib/parsers/swagger_data_parser.dart b/lib/parsers/swagger_data_parser.dart index d6fe40b..4412a31 100644 --- a/lib/parsers/swagger_data_parser.dart +++ b/lib/parsers/swagger_data_parser.dart @@ -8,23 +8,21 @@ import '../core/exceptions.dart'; import '../core/models.dart'; import '../utils/cache_manager.dart'; import '../utils/performance_monitor.dart'; +import '../utils/reference_resolver.dart'; import '../utils/string_utils.dart'; -import '../utils/type_validator.dart'; /// Swagger数据解析器 /// 负责解析Swagger JSON文档并提取相关信息 class SwaggerDataParser { final CacheManager _cacheManager; final PerformanceMonitor _performanceMonitor; - final TypeValidator _typeValidator; // 缓存解析结果 SwaggerDocument? _cachedDocument; SwaggerDataParser() : _cacheManager = CacheManager(), - _performanceMonitor = PerformanceMonitor(), - _typeValidator = TypeValidator(); + _performanceMonitor = PerformanceMonitor(); /// 获取并解析Swagger JSON文档 Future fetchAndParseSwaggerDocument() async { @@ -111,12 +109,11 @@ class SwaggerDataParser { final version = info['version'] as String? ?? '1.0.0'; final description = info['description'] as String? ?? ''; - // 解析其他基本信息 - final host = jsonData['host'] as String? ?? ''; - final basePath = jsonData['basePath'] as String? ?? '/'; - final schemes = List.from(jsonData['schemes'] ?? ['https']); - final consumes = List.from(jsonData['consumes'] ?? []); - final produces = List.from(jsonData['produces'] ?? []); + // 解析 servers (OpenAPI 3.0) + final servers = _parseServers(jsonData); + + // 解析 components (OpenAPI 3.0) + final components = _parseComponents(jsonData); // 解析tags信息 (用于获取控制器描述) final tagsInfo = _parseTagsInfo(jsonData); @@ -124,8 +121,8 @@ class SwaggerDataParser { // 解析API路径 final paths = _parseApiPaths(jsonData); - // 解析API模型 - final models = _parseApiModels(jsonData); + // 解析API模型 (从 components 中提取) + final models = components.schemas; // 解析API控制器 (传入tags信息) final controllers = _parseApiControllers(paths, tagsInfo); @@ -134,11 +131,8 @@ class SwaggerDataParser { title: title, version: version, description: description, - host: host, - basePath: basePath, - schemes: schemes, - consumes: consumes, - produces: produces, + servers: servers, + components: components, paths: paths, models: models, controllers: controllers, @@ -152,6 +146,64 @@ class SwaggerDataParser { }); } + /// 解析 servers 配置 (OpenAPI 3.0) + List _parseServers(Map jsonData) { + final servers = []; + + try { + final serversJson = jsonData['servers'] as List?; + if (serversJson != null) { + for (final serverJson in serversJson) { + if (serverJson is Map) { + final server = ApiServer.fromJson(serverJson); + servers.add(server); + } + } + } + + // 如果没有 servers 配置,提供默认值 + if (servers.isEmpty) { + servers.add(const ApiServer(url: '/')); + } + } catch (e) { + print('⚠️ 解析servers配置时发生错误: $e'); + // 提供默认服务器配置 + servers.add(const ApiServer(url: '/')); + } + + return servers; + } + + /// 解析 components 配置 (OpenAPI 3.0) + ApiComponents _parseComponents(Map jsonData) { + try { + final componentsJson = jsonData['components'] as Map?; + if (componentsJson != null) { + // 使用引用解析器处理复杂嵌套和循环引用 + final resolver = ReferenceResolver(); + final resolvedSchemas = resolver.resolveModels(componentsJson); + + // 创建 ApiComponents,但使用解析后的 schemas + final components = ApiComponents.fromJson(componentsJson); + return ApiComponents( + schemas: resolvedSchemas, + responses: components.responses, + parameters: components.parameters, + examples: components.examples, + requestBodies: components.requestBodies, + headers: components.headers, + securitySchemes: components.securitySchemes, + links: components.links, + callbacks: components.callbacks, + ); + } + } catch (e) { + print('⚠️ 解析components配置时发生错误: $e'); + } + + return const ApiComponents(); + } + /// 解析tags信息 Map _parseTagsInfo(Map jsonData) { final tagsInfo = {}; @@ -210,41 +262,6 @@ class SwaggerDataParser { return paths; } - /// 解析API模型 - Map _parseApiModels(Map jsonData) { - final models = {}; - - // 优先解析 components/schemas (Swagger 3.0) - final schemas = jsonData['components']?['schemas'] as Map?; - // 如果没有 components/schemas,尝试解析 definitions (Swagger 2.0) - final definitions = jsonData['definitions'] as Map?; - - final modelDefinitions = schemas ?? definitions; - - if (modelDefinitions == null) { - print('ℹ️ 未发现模型定义 (components/schemas 或 definitions)'); - return models; - } - - print( - '🔍 发现模型定义位置: ${schemas != null ? 'components/schemas' : 'definitions'}', - ); - - try { - modelDefinitions.forEach((name, definition) { - final model = ApiModel.fromJson( - name, - definition as Map, - ); - models[name] = model; - }); - } catch (e) { - throw SwaggerParseException('解析API模型失败', details: e.toString()); - } - - return models; - } - /// 解析API控制器 Map _parseApiControllers( Map paths, diff --git a/lib/swagger_cli_new.dart b/lib/swagger_cli_new.dart deleted file mode 100644 index a0f1dc9..0000000 --- a/lib/swagger_cli_new.dart +++ /dev/null @@ -1,193 +0,0 @@ -import 'dart:io'; - -import 'commands/base_command.dart'; -import 'commands/generate_command.dart'; -import 'core/config.dart'; -import 'utils/performance_monitor.dart'; -import 'utils/string_utils.dart'; - -/// Swagger CLI 应用程序 -/// 使用命令模式架构的新版本CLI工具 -class SwaggerCLI { - final Map _commands = {}; - final PerformanceMonitor _monitor = PerformanceMonitor(); - - SwaggerCLI() { - _registerCommands(); - } - - /// 注册所有命令 - void _registerCommands() { - _registerCommand(GenerateCommand()); - // 未来可以添加更多命令: - // _registerCommand(ParseCommand()); - // _registerCommand(ValidateCommand()); - // _registerCommand(InfoCommand()); - // _registerCommand(TestCommand()); - // _registerCommand(CleanCommand()); - } - - /// 注册单个命令 - void _registerCommand(BaseCommand command) { - _commands[command.name] = command; - } - - /// 运行CLI应用程序 - Future run(List arguments) async { - try { - _showBanner(); - - if (arguments.isEmpty || - arguments.first == 'help' || - arguments.first == '--help') { - _showHelp(); - return 0; - } - - final commandName = arguments.first; - final commandArgs = - arguments.length > 1 ? arguments.sublist(1) : []; - - // 检查特殊命令 - if (commandName == 'version' || commandName == '--version') { - _showVersion(); - return 0; - } - - // 查找并执行命令 - final command = _commands[commandName]; - if (command == null) { - print('❌ 未知命令: $commandName'); - print(''); - _showAvailableCommands(); - return 1; - } - - // 检查命令帮助 - if (commandArgs.contains('--help') || commandArgs.contains('-h')) { - command.showHelp(); - return 0; - } - - // 执行命令 - final stopwatch = Stopwatch()..start(); - final exitCode = await command.execute(commandArgs); - stopwatch.stop(); - - // 显示执行时间 - if (exitCode == 0) { - print(''); - print('⏱️ 执行时间: ${StringUtils.formatDuration(stopwatch.elapsed)}'); - } - - return exitCode; - } catch (error, stackTrace) { - print('❌ 应用程序错误: $error'); - print('堆栈跟踪: $stackTrace'); - return 1; - } - } - - /// 显示应用程序横幅 - void _showBanner() { - print(''); - print('🚀 Swagger API 代码生成器 v2.0'); - print('====================================='); - print('强大的 Swagger API 代码生成工具'); - print(''); - } - - /// 显示帮助信息 - void _showHelp() { - print('用法: dart swagger_cli_new.dart <命令> [选项]'); - print(''); - print('全新的命令式架构,提供更好的可扩展性和用户体验。'); - print(''); - _showAvailableCommands(); - _showGlobalOptions(); - _showExamples(); - _showContact(); - } - - /// 显示可用命令 - void _showAvailableCommands() { - print('📋 可用命令:'); - print(''); - - for (final command in _commands.values) { - print(' ${command.name.padRight(12)} ${command.description}'); - } - - print(' help 显示帮助信息'); - print(' version 显示版本信息'); - print(''); - } - - /// 显示全局选项 - void _showGlobalOptions() { - print('🔧 全局选项:'); - print(' -h, --help 显示帮助信息'); - print(' --version 显示版本信息'); - print(''); - } - - /// 显示使用示例 - void _showExamples() { - print('💡 使用示例:'); - print(''); - print(' # 生成所有文件'); - print(' dart swagger_cli_new.dart generate --all'); - print(''); - print(' # 只生成模型文件(简洁版本)'); - print(' dart swagger_cli_new.dart generate --models --simple'); - print(''); - print(' # 生成到指定目录并启用性能监控'); - print( - ' dart swagger_cli_new.dart generate --all --output-dir lib/generated --performance', - ); - print(''); - print(' # 查看具体命令的帮助'); - print(' dart swagger_cli_new.dart generate --help'); - print(''); - } - - /// 显示联系信息 - void _showContact() { - print('🌐 更多信息:'); - print(' API文档: ${SwaggerConfig.swaggerJsonUrl}'); - print(' 基础URL: ${SwaggerConfig.baseUrl}'); - print(''); - } - - /// 显示版本信息 - void _showVersion() { - print('Swagger CLI v2.0.0'); - print('构建于: ${DateTime.now().toIso8601String()}'); - print('Dart SDK: ${Platform.version}'); - print(''); - print('功能特性:'); - print('- 🏗️ 模块化命令架构'); - print('- 🚀 性能监控和优化'); - print('- 🔍 智能类型验证'); - print('- 📋 详细的错误报告'); - print('- 💾 智能缓存机制'); - print('- 📚 丰富的文档生成'); - print(''); - } - - /// 格式化持续时间 - // 已移动到 StringUtils.formatDuration - - /// 获取可用命令列表 - List get availableCommands => _commands.keys.toList(); - - /// 获取特定命令 - BaseCommand? getCommand(String name) => _commands[name]; -} - -/// CLI应用程序入口点 -Future main(List arguments) async { - final cli = SwaggerCLI(); - final exitCode = await cli.run(arguments); - exit(exitCode); -} diff --git a/lib/swagger_generator_flutter.dart b/lib/swagger_generator_flutter.dart new file mode 100644 index 0000000..7153249 --- /dev/null +++ b/lib/swagger_generator_flutter.dart @@ -0,0 +1,19 @@ +/// Swagger Generator Flutter +/// +/// 一个强大的 Flutter OpenAPI 3.0 代码生成器,专门为 Dio + Retrofit 架构优化。 +library swagger_generator_flutter; + +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'; +// 工具类 +export 'utils/string_utils.dart'; +// 验证器 +export 'validators/enhanced_validator.dart'; +export 'validators/schema_validator.dart'; diff --git a/lib/utils/file_utils.dart b/lib/utils/file_utils.dart index 1ebe935..1073fca 100644 --- a/lib/utils/file_utils.dart +++ b/lib/utils/file_utils.dart @@ -289,7 +289,6 @@ class FileUtils { /// 生成唯一文件名 static Future generateUniqueFileName( String basePath, String fileName) async { - final directory = Directory(basePath); final extension = getFileExtension(fileName); final nameWithoutExt = getFileNameWithoutExtension(fileName); diff --git a/lib/utils/reference_resolver.dart b/lib/utils/reference_resolver.dart new file mode 100644 index 0000000..d035c2b --- /dev/null +++ b/lib/utils/reference_resolver.dart @@ -0,0 +1,301 @@ +/// 引用解析器 +/// 处理复杂嵌套类型和循环引用检测 +library reference_resolver; + +import '../core/models.dart'; + +/// 引用解析器 +/// 负责处理 OpenAPI 文档中的复杂引用关系 +class ReferenceResolver { + /// 已解析的模型缓存 + final Map _resolvedModels = {}; + + /// 当前解析路径(用于循环引用检测) + final Set _resolutionPath = {}; + + /// 原始 JSON 数据缓存 + final Map> _rawSchemas = {}; + + /// 最大解析深度(防止过深的嵌套) + final int maxDepth; + + /// 当前解析深度 + int _currentDepth = 0; + + ReferenceResolver({this.maxDepth = 50}); + + /// 解析所有模型,处理复杂引用关系 + Map resolveModels(Map componentsJson) { + final schemasJson = + componentsJson['schemas'] as Map? ?? {}; + + // 第一步:缓存所有原始 schema 数据 + _cacheRawSchemas(schemasJson); + + // 第二步:解析所有模型 + final resolvedModels = {}; + for (final schemaName in schemasJson.keys) { + try { + final model = resolveModel(schemaName); + if (model != null) { + resolvedModels[schemaName] = model; + } + } catch (e) { + print('⚠️ 解析模型 $schemaName 时发生错误: $e'); + // 创建一个基本的模型作为后备 + resolvedModels[schemaName] = ApiModel( + name: schemaName, + description: '解析失败的模型', + properties: {}, + required: [], + ); + } + } + + return resolvedModels; + } + + /// 缓存原始 schema 数据 + void _cacheRawSchemas(Map schemasJson) { + schemasJson.forEach((name, schemaData) { + if (schemaData is Map) { + _rawSchemas[name] = schemaData; + } + }); + } + + /// 解析单个模型 + ApiModel? resolveModel(String modelName) { + // 检查是否已经解析过 + if (_resolvedModels.containsKey(modelName)) { + return _resolvedModels[modelName]; + } + + // 检查循环引用 + if (_resolutionPath.contains(modelName)) { + print('🔄 检测到循环引用: ${_resolutionPath.join(' -> ')} -> $modelName'); + return _createCircularReferenceModel(modelName); + } + + // 检查解析深度 + if (_currentDepth >= maxDepth) { + print('⚠️ 达到最大解析深度 $maxDepth,停止解析 $modelName'); + return _createDepthLimitModel(modelName); + } + + // 获取原始数据 + final schemaData = _rawSchemas[modelName]; + if (schemaData == null) { + print('⚠️ 未找到模型定义: $modelName'); + return null; + } + + // 开始解析 + _resolutionPath.add(modelName); + _currentDepth++; + + try { + final model = _parseModelWithContext(modelName, schemaData); + _resolvedModels[modelName] = model; + return model; + } finally { + _resolutionPath.remove(modelName); + _currentDepth--; + } + } + + /// 在上下文中解析模型 + ApiModel _parseModelWithContext(String name, Map json) { + // 检查是否是枚举类型 + final isEnum = json['enum'] != null; + if (isEnum) { + return _parseEnumModel(name, json); + } + + // 检查组合模式 + if (json['allOf'] != null || + json['oneOf'] != null || + json['anyOf'] != null) { + return _parseCompositionModel(name, json); + } + + // 解析普通对象模型 + return _parseObjectModel(name, json); + } + + /// 解析枚举模型 + ApiModel _parseEnumModel(String name, Map json) { + final enumValues = List.from(json['enum'] ?? []); + final enumType = + PropertyType.fromString(json['type'] as String? ?? 'string'); + + return ApiModel( + name: name, + description: json['description'] as String? ?? '', + properties: {}, + required: [], + isEnum: true, + enumValues: enumValues, + enumType: enumType, + ); + } + + /// 解析组合模式模型 + ApiModel _parseCompositionModel(String name, Map json) { + // 解析组合模式 + final allOf = _parseSchemaList(json['allOf'] as List? ?? []); + final oneOf = _parseSchemaList(json['oneOf'] as List? ?? []); + final anyOf = _parseSchemaList(json['anyOf'] as List? ?? []); + + final notJson = json['not'] as Map?; + final not = notJson != null ? ApiSchema.fromJson(notJson) : null; + + // 解析 discriminator + final discriminatorJson = json['discriminator'] as Map?; + final discriminator = discriminatorJson != null + ? ApiDiscriminator.fromJson(discriminatorJson) + : null; + + // 对于组合模式,我们需要合并属性 + final mergedProperties = {}; + final mergedRequired = []; + + // 从 allOf 中合并属性 + for (final schema in allOf) { + _mergeSchemaProperties(schema, mergedProperties, mergedRequired); + } + + // 如果有直接的 properties,也要合并 + if (json['properties'] != null) { + final directProperties = _parseProperties( + json['properties'] as Map, + List.from(json['required'] ?? []), + ); + mergedProperties.addAll(directProperties); + mergedRequired.addAll(List.from(json['required'] ?? [])); + } + + return ApiModel( + name: name, + description: json['description'] as String? ?? '', + properties: mergedProperties, + required: mergedRequired.toSet().toList(), + allOf: allOf, + oneOf: oneOf, + anyOf: anyOf, + not: not, + discriminator: discriminator, + ); + } + + /// 解析对象模型 + ApiModel _parseObjectModel(String name, Map json) { + final properties = _parseProperties( + json['properties'] as Map? ?? {}, + List.from(json['required'] ?? []), + ); + + final required = List.from(json['required'] ?? []); + + return ApiModel( + name: name, + description: json['description'] as String? ?? '', + properties: properties, + required: required, + ); + } + + /// 解析 schema 列表 + List _parseSchemaList(List schemaList) { + return schemaList + .map((schema) => ApiSchema.fromJson(schema as Map)) + .toList(); + } + + /// 合并 schema 的属性 + void _mergeSchemaProperties( + ApiSchema schema, + Map targetProperties, + List targetRequired, + ) { + // 如果是引用,解析引用的模型 + if (schema.isReference && schema.reference != null) { + final referencedModel = resolveModel(schema.reference!); + if (referencedModel != null) { + targetProperties.addAll(referencedModel.properties); + targetRequired.addAll(referencedModel.required); + } + } else { + // 直接合并属性 + targetProperties.addAll(schema.properties); + targetRequired.addAll(schema.required); + } + } + + /// 解析属性 + Map _parseProperties( + Map propertiesJson, + List requiredFields, + ) { + final properties = {}; + + propertiesJson.forEach((propName, propData) { + if (propData is Map) { + try { + final property = + _parsePropertyWithContext(propName, propData, requiredFields); + properties[propName] = property; + } catch (e) { + print('⚠️ 解析属性 $propName 时发生错误: $e'); + // 创建一个基本属性作为后备 + properties[propName] = ApiProperty( + name: propName, + type: PropertyType.string, + description: '解析失败的属性', + required: requiredFields.contains(propName), + ); + } + } + }); + + return properties; + } + + /// 在上下文中解析属性 + ApiProperty _parsePropertyWithContext( + String name, + Map json, + List requiredFields, + ) { + // 使用现有的 ApiProperty.fromJson,但在循环引用检测的上下文中 + return ApiProperty.fromJson(name, json, requiredFields); + } + + /// 创建循环引用模型 + ApiModel _createCircularReferenceModel(String modelName) { + return ApiModel( + name: modelName, + description: '循环引用模型 - 为避免无限递归而创建的占位符', + properties: {}, + required: [], + ); + } + + /// 创建深度限制模型 + ApiModel _createDepthLimitModel(String modelName) { + return ApiModel( + name: modelName, + description: '深度限制模型 - 达到最大解析深度而创建的占位符', + properties: {}, + required: [], + ); + } + + /// 清理缓存 + void clearCache() { + _resolvedModels.clear(); + _resolutionPath.clear(); + _rawSchemas.clear(); + _currentDepth = 0; + } +} diff --git a/lib/utils/string_utils.dart b/lib/utils/string_utils.dart index f461494..5e27c26 100644 --- a/lib/utils/string_utils.dart +++ b/lib/utils/string_utils.dart @@ -210,6 +210,14 @@ class StringUtils { return toPascalCase(cleanName); } + /// 生成常量名称 (UPPER_SNAKE_CASE) + static String generateConstantName(String name) { + // 清理特殊字符 + final cleanName = name.replaceAll(RegExp(r'[^a-zA-Z0-9_]'), '_'); + // 转换为 snake_case 然后转为大写 + return toSnakeCase(cleanName).toUpperCase(); + } + /// 生成文件名 static String generateFileName(String name) { // 转换为snake_case并添加.dart扩展名 diff --git a/lib/validators/enhanced_validator.dart b/lib/validators/enhanced_validator.dart new file mode 100644 index 0000000..7f554d8 --- /dev/null +++ b/lib/validators/enhanced_validator.dart @@ -0,0 +1,597 @@ +/// 增强的 OpenAPI 验证器 +/// 集成详细的错误报告和修复建议 +library; + +import '../core/error_reporter.dart'; +import '../core/models.dart'; + +/// 增强的 OpenAPI 验证器 +class EnhancedValidator { + final ErrorReporter _errorReporter; + final bool _includeWarnings; + + EnhancedValidator({ + bool includeWarnings = true, + }) : _errorReporter = ErrorReporter(), + _includeWarnings = includeWarnings; + + /// 获取错误报告器 + ErrorReporter get errorReporter => _errorReporter; + + /// 验证 OpenAPI 文档 + bool validateDocument(SwaggerDocument document) { + _errorReporter.clear(); + + // 基础结构验证 + _validateBasicStructure(document); + + // 路径验证 + _validatePaths(document); + + // 组件验证 + _validateComponents(document); + + // 安全方案验证 + _validateSecurity(document); + + // 最佳实践检查 + if (_includeWarnings) { + _checkBestPractices(document); + } + + return !_errorReporter.hasErrorsOrCritical; + } + + /// 验证基础结构 + void _validateBasicStructure(SwaggerDocument document) { + // 验证标题 + if (document.title.isEmpty) { + _errorReporter.reportError( + id: 'MISSING_INFO_TITLE', + title: 'Missing API Title', + description: 'API title is required in the info object.', + severity: ErrorSeverity.error, + category: ErrorCategory.validation, + jsonPath: 'info.title', + suggestions: [ + FixSuggestion( + description: 'Add a descriptive title for your API', + codeExample: '"title": "My API"', + ), + ], + ); + } + + // 验证版本 + if (document.version.isEmpty) { + _errorReporter.reportError( + id: 'MISSING_INFO_VERSION', + title: 'Missing API Version', + description: 'API version is required in the info object.', + severity: ErrorSeverity.error, + category: ErrorCategory.validation, + jsonPath: 'info.version', + suggestions: [ + FixSuggestion( + description: 'Add a version number using semantic versioning', + codeExample: '"version": "1.0.0"', + documentationUrl: 'https://semver.org/', + ), + ], + ); + } + + // 验证描述 + if (document.description.isEmpty && _includeWarnings) { + _errorReporter.reportError( + id: 'MISSING_INFO_DESCRIPTION', + title: 'Missing API Description', + description: + 'API description helps users understand the purpose of your API.', + severity: ErrorSeverity.warning, + category: ErrorCategory.bestPractice, + jsonPath: 'info.description', + suggestions: [ + FixSuggestion( + description: 'Add a description explaining what your API does', + codeExample: + '"description": "This API provides user management functionality"', + ), + ], + ); + } + + // 验证服务器配置 + if (document.servers.isEmpty && _includeWarnings) { + _errorReporter.reportError( + id: 'MISSING_SERVERS', + title: 'Missing Server Configuration', + description: + 'Server configuration helps clients know where to send requests.', + severity: ErrorSeverity.warning, + category: ErrorCategory.bestPractice, + jsonPath: 'servers', + suggestions: [ + FixSuggestion( + description: 'Add at least one server configuration', + codeExample: + '"servers": [{"url": "https://api.example.com", "description": "Production server"}]', + ), + ], + ); + } + } + + /// 验证路径 + void _validatePaths(SwaggerDocument document) { + if (document.paths.isEmpty) { + _errorReporter.reportError( + id: 'EMPTY_PATHS', + title: 'Empty Paths Object', + description: 'OpenAPI document must contain at least one path.', + severity: ErrorSeverity.error, + category: ErrorCategory.validation, + jsonPath: 'paths', + suggestions: [ + FixSuggestion( + description: 'Add at least one API endpoint', + codeExample: + '"/users": { "get": { "responses": { "200": { "description": "Success" } } } }', + ), + ], + ); + return; + } + + document.paths.forEach((pathPattern, apiPath) { + _validatePath(pathPattern, apiPath); + }); + } + + /// 验证单个路径 + void _validatePath(String pathPattern, ApiPath apiPath) { + final pathKey = 'paths["$pathPattern"][${apiPath.method.value}]'; + + // 验证路径格式 + if (!pathPattern.startsWith('/')) { + _errorReporter.reportError( + id: 'INVALID_PATH_FORMAT', + title: 'Invalid Path Format', + description: 'Path must start with a forward slash.', + severity: ErrorSeverity.error, + category: ErrorCategory.syntax, + jsonPath: pathKey, + snippet: pathPattern, + suggestions: [ + FixSuggestion( + description: 'Ensure path starts with /', + codeExample: '"/$pathPattern" instead of "$pathPattern"', + ), + ], + ); + } + + // 验证响应 + if (apiPath.responses.isEmpty) { + _errorReporter.reportError( + id: 'MISSING_OPERATION_RESPONSES', + title: 'Missing Operation Responses', + description: 'Every operation must define at least one response.', + severity: ErrorSeverity.error, + category: ErrorCategory.validation, + jsonPath: '$pathKey.responses', + suggestions: [ + FixSuggestion( + description: 'Add at least a default response', + codeExample: '"responses": { "200": { "description": "Success" } }', + ), + ], + ); + } + + // 验证操作 ID + if (apiPath.operationId.isEmpty && _includeWarnings) { + _errorReporter.reportError( + id: 'MISSING_OPERATION_ID', + title: 'Missing Operation ID', + description: + 'Operation should have an operationId for better code generation.', + severity: ErrorSeverity.warning, + category: ErrorCategory.bestPractice, + jsonPath: '$pathKey.operationId', + suggestions: [ + FixSuggestion( + description: 'Add a unique operationId', + codeExample: + '"operationId": "${_generateOperationId(pathPattern, apiPath.method)}"', + ), + ], + ); + } + + // 验证摘要 + if (apiPath.summary.isEmpty && _includeWarnings) { + _errorReporter.reportError( + id: 'MISSING_OPERATION_SUMMARY', + title: 'Missing Operation Summary', + description: + 'Operation should have a summary for better documentation.', + severity: ErrorSeverity.info, + category: ErrorCategory.bestPractice, + jsonPath: '$pathKey.summary', + suggestions: [ + FixSuggestion( + description: 'Add a brief summary', + codeExample: '"summary": "Get all users"', + ), + ], + ); + } + + // 验证参数 + _validateParameters(apiPath.parameters, pathKey, pathPattern); + + // 验证响应 + _validateResponses(apiPath.responses, pathKey); + } + + /// 验证参数 + void _validateParameters( + List parameters, String pathKey, String pathPattern) { + // 提取路径参数 + final pathParams = _extractPathParameters(pathPattern); + final declaredPathParams = parameters + .where((p) => p.location == ParameterLocation.path) + .map((p) => p.name) + .toSet(); + + // 检查路径参数是否都有声明 + for (final param in pathParams) { + if (!declaredPathParams.contains(param)) { + _errorReporter.reportError( + id: 'UNDECLARED_PATH_PARAMETER', + title: 'Undeclared Path Parameter', + description: + 'Path parameter "$param" is used in the path but not declared in parameters.', + severity: ErrorSeverity.error, + category: ErrorCategory.validation, + jsonPath: '$pathKey.parameters', + suggestions: [ + FixSuggestion( + description: 'Add parameter declaration', + codeExample: + '{"name": "$param", "in": "path", "required": true, "schema": {"type": "string"}}', + ), + ], + ); + } + } + + // 验证每个参数 + for (int i = 0; i < parameters.length; i++) { + final param = parameters[i]; + final paramPath = '$pathKey.parameters[$i]'; + + // 验证参数名 + if (param.name.isEmpty) { + _errorReporter.reportError( + id: 'MISSING_PARAMETER_NAME', + title: 'Missing Parameter Name', + description: 'Parameter must have a name.', + severity: ErrorSeverity.error, + category: ErrorCategory.validation, + jsonPath: '$paramPath.name', + suggestions: [ + FixSuggestion( + description: 'Add a name for the parameter', + codeExample: '"name": "userId"', + ), + ], + ); + } + + // 验证路径参数必须是必需的 + if (param.location == ParameterLocation.path && !param.required) { + _errorReporter.reportError( + id: 'PATH_PARAMETER_NOT_REQUIRED', + title: 'Path Parameter Not Required', + description: 'Path parameters must be marked as required.', + severity: ErrorSeverity.error, + category: ErrorCategory.validation, + jsonPath: '$paramPath.required', + suggestions: [ + FixSuggestion( + description: 'Set required: true for path parameters', + codeExample: '"required": true', + ), + ], + ); + } + } + } + + /// 验证响应 + void _validateResponses(Map responses, String pathKey) { + bool hasSuccessResponse = false; + bool hasErrorResponse = false; + + responses.forEach((code, response) { + final responsePath = '$pathKey.responses["$code"]'; + final statusCode = int.tryParse(code) ?? 0; + + // 检查成功响应 + if (statusCode >= 200 && statusCode < 300) { + hasSuccessResponse = true; + } + + // 检查错误响应 + if (statusCode >= 400) { + hasErrorResponse = true; + } + + // 验证响应描述 + if (response.description.isEmpty) { + _errorReporter.reportError( + id: 'MISSING_RESPONSE_DESCRIPTION', + title: 'Missing Response Description', + description: 'Response should have a description.', + severity: ErrorSeverity.warning, + category: ErrorCategory.bestPractice, + jsonPath: '$responsePath.description', + suggestions: [ + FixSuggestion( + description: 'Add a description for the response', + codeExample: '"description": "Successful operation"', + ), + ], + ); + } + }); + + // 检查是否有成功响应 + if (!hasSuccessResponse && _includeWarnings) { + _errorReporter.reportError( + id: 'NO_SUCCESS_RESPONSE', + title: 'No Success Response', + description: + 'Operation should define at least one success response (2xx).', + severity: ErrorSeverity.warning, + category: ErrorCategory.bestPractice, + jsonPath: '$pathKey.responses', + suggestions: [ + FixSuggestion( + description: 'Add a success response', + codeExample: '"200": { "description": "Success" }', + ), + ], + ); + } + + // 检查是否有错误响应 + if (!hasErrorResponse && _includeWarnings) { + _errorReporter.reportError( + id: 'NO_ERROR_RESPONSE', + title: 'No Error Response', + description: + 'Consider adding error responses (4xx/5xx) for better API documentation.', + severity: ErrorSeverity.info, + category: ErrorCategory.bestPractice, + jsonPath: '$pathKey.responses', + suggestions: [ + FixSuggestion( + description: 'Add common error responses', + codeExample: + '"400": { "description": "Bad Request" }, "404": { "description": "Not Found" }', + ), + ], + ); + } + } + + /// 验证组件 + void _validateComponents(SwaggerDocument document) { + // 验证 schemas + document.components.schemas.forEach((name, model) { + _validateSchema(name, model); + }); + + // 验证安全方案 + document.components.securitySchemes.forEach((name, scheme) { + _validateSecurityScheme(name, scheme); + }); + } + + /// 验证 Schema + void _validateSchema(String name, ApiModel model) { + final schemaPath = 'components.schemas["$name"]'; + + // 验证模型名称 + if (model.name.isEmpty) { + _errorReporter.reportError( + id: 'MISSING_SCHEMA_NAME', + title: 'Missing Schema Name', + description: 'Schema should have a name.', + severity: ErrorSeverity.error, + category: ErrorCategory.validation, + jsonPath: schemaPath, + suggestions: [ + FixSuggestion( + description: 'Ensure schema has a valid name', + codeExample: + 'Schema name should match the key in components.schemas', + ), + ], + ); + } + + // 检查属性数量 + if (model.properties.length > 20 && _includeWarnings) { + _errorReporter.reportError( + id: 'LARGE_SCHEMA_OBJECT', + title: 'Large Schema Object', + description: + 'Schema has many properties (${model.properties.length}), consider breaking it down.', + severity: ErrorSeverity.info, + category: ErrorCategory.performance, + jsonPath: schemaPath, + suggestions: [ + FixSuggestion( + description: 'Consider using composition with allOf', + codeExample: + '"allOf": [{ "\$ref": "#/components/schemas/BaseModel" }, { "type": "object", "properties": {...} }]', + ), + ], + ); + } + } + + /// 验证安全方案 + void _validateSecurityScheme(String name, ApiSecurityScheme scheme) { + final schemePath = 'components.securitySchemes["$name"]'; + + switch (scheme.type) { + case SecuritySchemeType.apiKey: + if (scheme.name == null || scheme.name!.isEmpty) { + _errorReporter.reportError( + id: 'MISSING_API_KEY_NAME', + title: 'Missing API Key Name', + description: + 'API Key security scheme must specify a parameter name.', + severity: ErrorSeverity.error, + category: ErrorCategory.security, + jsonPath: '$schemePath.name', + suggestions: [ + FixSuggestion( + description: 'Add name field for API key parameter', + codeExample: '"name": "X-API-Key"', + ), + ], + ); + } + break; + + case SecuritySchemeType.http: + if (scheme.scheme == null || scheme.scheme!.isEmpty) { + _errorReporter.reportError( + id: 'MISSING_HTTP_SCHEME', + title: 'Missing HTTP Scheme', + description: + 'HTTP security scheme must specify a scheme (basic, bearer, etc.).', + severity: ErrorSeverity.error, + category: ErrorCategory.security, + jsonPath: '$schemePath.scheme', + suggestions: [ + FixSuggestion( + description: 'Add scheme field', + codeExample: '"scheme": "bearer"', + ), + ], + ); + } + break; + + case SecuritySchemeType.oauth2: + if (scheme.flows == null) { + _errorReporter.reportError( + id: 'MISSING_OAUTH2_FLOWS', + title: 'Missing OAuth2 Flows', + description: 'OAuth2 security scheme must define flows.', + severity: ErrorSeverity.error, + category: ErrorCategory.security, + jsonPath: '$schemePath.flows', + suggestions: [ + FixSuggestion( + description: 'Add flows configuration', + codeExample: + '"flows": { "authorizationCode": { "authorizationUrl": "...", "tokenUrl": "..." } }', + ), + ], + ); + } + break; + + case SecuritySchemeType.openIdConnect: + if (scheme.openIdConnectUrl == null || + scheme.openIdConnectUrl!.isEmpty) { + _errorReporter.reportError( + id: 'MISSING_OPENID_URL', + title: 'Missing OpenID Connect URL', + description: 'OpenID Connect security scheme must specify a URL.', + severity: ErrorSeverity.error, + category: ErrorCategory.security, + jsonPath: '$schemePath.openIdConnectUrl', + suggestions: [ + FixSuggestion( + description: 'Add OpenID Connect URL', + codeExample: + '"openIdConnectUrl": "https://example.com/.well-known/openid_configuration"', + ), + ], + ); + } + break; + } + } + + /// 验证安全配置 + void _validateSecurity(SwaggerDocument document) { + // 这里可以添加安全配置的验证逻辑 + } + + /// 检查最佳实践 + void _checkBestPractices(SwaggerDocument document) { + // 检查是否使用了标签 + final hasTaggedOperations = + document.paths.values.any((path) => path.tags.isNotEmpty); + if (!hasTaggedOperations) { + _errorReporter.reportError( + id: 'NO_OPERATION_TAGS', + title: 'No Operation Tags', + description: 'Consider using tags to organize your API operations.', + severity: ErrorSeverity.info, + category: ErrorCategory.bestPractice, + jsonPath: 'paths', + suggestions: [ + FixSuggestion( + description: 'Add tags to operations', + codeExample: '"tags": ["users"]', + ), + ], + ); + } + } + + /// 提取路径参数 + Set _extractPathParameters(String path) { + final regex = RegExp(r'\{([^}]+)\}'); + final matches = regex.allMatches(path); + return matches.map((match) => match.group(1)!).toSet(); + } + + /// 生成操作 ID + String _generateOperationId(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'; + } + + /// 转换为 PascalCase + String _toPascalCase(String input) { + return input + .split('_') + .map((word) => word.isEmpty + ? '' + : word[0].toUpperCase() + word.substring(1).toLowerCase()) + .join(''); + } +} diff --git a/lib/validators/schema_validator.dart b/lib/validators/schema_validator.dart new file mode 100644 index 0000000..a4e74e3 --- /dev/null +++ b/lib/validators/schema_validator.dart @@ -0,0 +1,845 @@ +/// Schema 验证器 +/// 验证 OpenAPI 3.0 文档的完整性和正确性 +library; + +import '../core/models.dart'; + +/// Schema 验证结果 +class ValidationResult { + final bool isValid; + final List errors; + final List warnings; + + const ValidationResult({ + required this.isValid, + this.errors = const [], + this.warnings = const [], + }); + + /// 创建成功的验证结果 + factory ValidationResult.success( + {List warnings = const []}) { + return ValidationResult( + isValid: true, + warnings: warnings, + ); + } + + /// 创建失败的验证结果 + factory ValidationResult.failure(List errors, + {List warnings = const []}) { + return ValidationResult( + isValid: false, + errors: errors, + warnings: warnings, + ); + } + + /// 是否有警告 + bool get hasWarnings => warnings.isNotEmpty; + + /// 是否有错误 + bool get hasErrors => errors.isNotEmpty; +} + +/// 验证错误 +class ValidationError { + final String path; + final String message; + final ValidationErrorType type; + final String? suggestion; + + const ValidationError({ + required this.path, + required this.message, + required this.type, + this.suggestion, + }); + + @override + String toString() { + final buffer = StringBuffer(); + buffer.write('[$type] $path: $message'); + if (suggestion != null) { + buffer.write(' (建议: $suggestion)'); + } + return buffer.toString(); + } +} + +/// 验证警告 +class ValidationWarning { + final String path; + final String message; + final String? suggestion; + + const ValidationWarning({ + required this.path, + required this.message, + this.suggestion, + }); + + @override + String toString() { + final buffer = StringBuffer(); + buffer.write('[WARNING] $path: $message'); + if (suggestion != null) { + buffer.write(' (建议: $suggestion)'); + } + return buffer.toString(); + } +} + +/// 验证错误类型 +enum ValidationErrorType { + required, + format, + type, + reference, + constraint, + compatibility, + security, +} + +/// Schema 验证器 +class SchemaValidator { + final List _errors = []; + final List _warnings = []; + + /// 验证 OpenAPI 文档 + ValidationResult validateDocument(SwaggerDocument document) { + _errors.clear(); + _warnings.clear(); + + // 验证基本信息 + _validateInfo(document); + + // 验证服务器配置 + _validateServers(document.servers); + + // 验证路径 + _validatePaths(document.paths); + + // 验证组件 + _validateComponents(document.components); + + // 验证安全方案 + _validateSecurity(document.security, document.components.securitySchemes); + + return ValidationResult( + isValid: _errors.isEmpty, + errors: List.from(_errors), + warnings: List.from(_warnings), + ); + } + + /// 验证基本信息 + void _validateInfo(SwaggerDocument document) { + if (document.title.isEmpty) { + _errors.add(const ValidationError( + path: 'info.title', + message: 'API 标题不能为空', + type: ValidationErrorType.required, + suggestion: '请提供有意义的 API 标题', + )); + } + + if (document.version.isEmpty) { + _errors.add(const ValidationError( + path: 'info.version', + message: 'API 版本不能为空', + type: ValidationErrorType.required, + suggestion: '请使用语义化版本号,如 "1.0.0"', + )); + } + + if (document.description.isEmpty) { + _warnings.add(const ValidationWarning( + path: 'info.description', + message: 'API 描述为空', + suggestion: '建议添加 API 的详细描述', + )); + } + } + + /// 验证服务器配置 + void _validateServers(List servers) { + if (servers.isEmpty) { + _warnings.add(const ValidationWarning( + path: 'servers', + message: '未定义服务器配置', + suggestion: '建议添加至少一个服务器配置', + )); + return; + } + + for (int i = 0; i < servers.length; i++) { + final server = servers[i]; + final path = 'servers[$i]'; + + if (server.url.isEmpty) { + _errors.add(ValidationError( + path: '$path.url', + message: '服务器 URL 不能为空', + type: ValidationErrorType.required, + )); + } else if (!_isValidUrl(server.url)) { + _errors.add(ValidationError( + path: '$path.url', + message: '服务器 URL 格式无效: ${server.url}', + type: ValidationErrorType.format, + suggestion: '请使用有效的 URL 格式,如 "https://api.example.com"', + )); + } + + // 验证服务器变量 + server.variables.forEach((name, variable) { + if (variable.defaultValue.isEmpty) { + _errors.add(ValidationError( + path: '$path.variables.$name.default', + message: '服务器变量必须有默认值', + type: ValidationErrorType.required, + )); + } + }); + } + } + + /// 验证路径 + void _validatePaths(Map paths) { + if (paths.isEmpty) { + _errors.add(const ValidationError( + path: 'paths', + message: 'API 文档必须包含至少一个路径', + type: ValidationErrorType.required, + )); + return; + } + + paths.forEach((pathPattern, path) { + final pathKey = 'paths["$pathPattern"][${path.method.value}]'; + _validatePath(path, pathKey); + }); + } + + /// 验证单个路径 + void _validatePath(ApiPath path, String pathKey) { + // 验证操作 ID + if (path.operationId.isEmpty) { + _warnings.add(ValidationWarning( + path: '$pathKey.operationId', + message: '缺少操作 ID', + suggestion: '建议为每个操作添加唯一的 operationId', + )); + } + + // 验证摘要和描述 + if (path.summary.isEmpty) { + _warnings.add(ValidationWarning( + path: '$pathKey.summary', + message: '缺少操作摘要', + suggestion: '建议添加简短的操作描述', + )); + } + + // 验证参数 + for (int i = 0; i < path.parameters.length; i++) { + _validateParameter(path.parameters[i], '$pathKey.parameters[$i]'); + } + + // 验证请求体 + if (path.requestBody != null) { + _validateRequestBody(path.requestBody!, '$pathKey.requestBody'); + } + + // 验证响应 + if (path.responses.isEmpty) { + _errors.add(ValidationError( + path: '$pathKey.responses', + message: '操作必须定义至少一个响应', + type: ValidationErrorType.required, + )); + } else { + path.responses.forEach((code, response) { + _validateResponse(response, '$pathKey.responses["$code"]'); + }); + } + + // 验证安全要求 + for (int i = 0; i < path.security.length; i++) { + _validateSecurityRequirement(path.security[i], '$pathKey.security[$i]'); + } + } + + /// 验证参数 + void _validateParameter(ApiParameter parameter, String path) { + if (parameter.name.isEmpty) { + _errors.add(ValidationError( + path: '$path.name', + message: '参数名称不能为空', + type: ValidationErrorType.required, + )); + } + + // 验证路径参数必须是必需的 + if (parameter.location == ParameterLocation.path && !parameter.required) { + _errors.add(ValidationError( + path: '$path.required', + message: '路径参数必须是必需的', + type: ValidationErrorType.constraint, + )); + } + + // 验证参数类型 + if (parameter.type == PropertyType.unknown) { + _warnings.add(ValidationWarning( + path: '$path.type', + message: '参数类型未知', + suggestion: '建议明确指定参数类型', + )); + } + } + + /// 验证请求体 + void _validateRequestBody(ApiRequestBody requestBody, String path) { + if (requestBody.content.isEmpty) { + _errors.add(ValidationError( + path: '$path.content', + message: '请求体必须定义至少一种内容类型', + type: ValidationErrorType.required, + )); + } + + requestBody.content.forEach((mediaType, content) { + _validateMediaType(content, '$path.content["$mediaType"]', mediaType); + }); + } + + /// 验证响应 + void _validateResponse(ApiResponse response, String path) { + if (response.description.isEmpty) { + _warnings.add(ValidationWarning( + path: '$path.description', + message: '响应缺少描述', + suggestion: '建议为响应添加描述', + )); + } + + response.content.forEach((mediaType, content) { + _validateMediaType(content, '$path.content["$mediaType"]', mediaType); + }); + } + + /// 验证媒体类型 + void _validateMediaType( + ApiMediaType mediaType, String path, String contentType) { + // 验证 schema + if (mediaType.schema == null) { + _warnings.add(ValidationWarning( + path: '$path.schema', + message: '媒体类型缺少 schema 定义', + suggestion: '建议为媒体类型添加 schema', + )); + } + + // 验证编码(仅适用于 multipart 和 form data) + if (contentType.startsWith('multipart/') || contentType.contains('form')) { + if (mediaType.encoding.isEmpty) { + _warnings.add(ValidationWarning( + path: '$path.encoding', + message: '表单数据建议定义编码信息', + suggestion: '为文件上传字段添加 contentType 等编码信息', + )); + } + } + } + + /// 验证组件 + void _validateComponents(ApiComponents? components) { + if (components == null) return; + + // 验证 schemas + components.schemas.forEach((name, model) { + _validateModel(model, 'components.schemas["$name"]'); + }); + + // 验证安全方案 + components.securitySchemes.forEach((name, scheme) { + _validateSecurityScheme(scheme, 'components.securitySchemes["$name"]'); + }); + } + + /// 验证模型 + void _validateModel(ApiModel model, String path) { + if (model.name.isEmpty) { + _errors.add(ValidationError( + path: '$path.name', + message: '模型名称不能为空', + type: ValidationErrorType.required, + )); + } + + // 验证属性 + model.properties.forEach((name, property) { + _validateProperty(property, '$path.properties["$name"]'); + }); + + // 验证必需字段 + for (final requiredField in model.required) { + if (!model.properties.containsKey(requiredField)) { + _errors.add(ValidationError( + path: '$path.required', + message: '必需字段 "$requiredField" 在属性中未定义', + type: ValidationErrorType.reference, + )); + } + } + } + + /// 验证属性 + void _validateProperty(ApiProperty property, String path) { + if (property.name.isEmpty) { + _errors.add(ValidationError( + path: '$path.name', + message: '属性名称不能为空', + type: ValidationErrorType.required, + )); + } + + if (property.type == PropertyType.unknown) { + _warnings.add(ValidationWarning( + path: '$path.type', + message: '属性类型未知', + suggestion: '建议明确指定属性类型', + )); + } + } + + /// 验证安全方案 + void _validateSecurity(List security, + Map schemes) { + for (int i = 0; i < security.length; i++) { + _validateSecurityRequirement(security[i], 'security[$i]'); + } + } + + /// 验证安全要求 + void _validateSecurityRequirement( + ApiSecurityRequirement requirement, String path) { + for (final schemeName in requirement.schemeNames) { + // 这里应该验证安全方案是否在 components.securitySchemes 中定义 + // 但由于当前模型结构限制,我们只能添加警告 + if (schemeName.isEmpty) { + _warnings.add(ValidationWarning( + path: path, + message: '安全方案名称为空', + suggestion: '请确保安全方案名称有效', + )); + } + } + } + + /// 验证安全方案 + void _validateSecurityScheme(ApiSecurityScheme scheme, String path) { + switch (scheme.type) { + case SecuritySchemeType.apiKey: + if (scheme.name == null || scheme.name!.isEmpty) { + _errors.add(ValidationError( + path: '$path.name', + message: 'API Key 安全方案必须指定参数名称', + type: ValidationErrorType.required, + )); + } + break; + case SecuritySchemeType.http: + if (scheme.scheme == null || scheme.scheme!.isEmpty) { + _errors.add(ValidationError( + path: '$path.scheme', + message: 'HTTP 安全方案必须指定认证方案', + type: ValidationErrorType.required, + )); + } + break; + case SecuritySchemeType.oauth2: + if (scheme.flows == null) { + _errors.add(ValidationError( + path: '$path.flows', + message: 'OAuth2 安全方案必须定义流程', + type: ValidationErrorType.required, + )); + } + break; + case SecuritySchemeType.openIdConnect: + if (scheme.openIdConnectUrl == null || + scheme.openIdConnectUrl!.isEmpty) { + _errors.add(ValidationError( + path: '$path.openIdConnectUrl', + message: 'OpenID Connect 安全方案必须指定 URL', + type: ValidationErrorType.required, + )); + } + break; + } + } + + /// 验证 URL 格式 + bool _isValidUrl(String url) { + try { + final uri = Uri.parse(url); + return uri.hasScheme && (uri.scheme == 'http' || uri.scheme == 'https'); + } catch (e) { + return false; + } + } + + /// 验证文档结构完整性 + void validateDocumentStructure(SwaggerDocument document) { + _validateOpenApiVersion(document); + _validatePathStructure(document); + _validateComponentReferences(document); + _validateSecurityReferences(document); + _validateExampleConsistency(document); + _validateResponseStructure(document); + _validateParameterConsistency(document); + } + + /// 验证 OpenAPI 版本 + void _validateOpenApiVersion(SwaggerDocument document) { + // SwaggerDocument 没有直接的 openApiVersion 属性 + // 这里我们假设它是 OpenAPI 3.0 兼容的 + _warnings.add(const ValidationWarning( + path: 'openapi', + message: '无法验证 OpenAPI 版本', + suggestion: '确保使用 OpenAPI 3.0.x 或 3.1.x 版本', + )); + } + + /// 验证路径结构 + void _validatePathStructure(SwaggerDocument document) { + final pathPatterns = document.paths.keys.toList(); + + // 检查路径冲突 + for (int i = 0; i < pathPatterns.length; i++) { + for (int j = i + 1; j < pathPatterns.length; j++) { + if (_pathsConflict(pathPatterns[i], pathPatterns[j])) { + _errors.add(ValidationError( + path: 'paths', + message: '路径冲突: "${pathPatterns[i]}" 与 "${pathPatterns[j]}"', + type: ValidationErrorType.constraint, + suggestion: '确保路径模式不会产生歧义', + )); + } + } + } + + // 检查路径参数一致性 + document.paths.forEach((pathPattern, path) { + final pathParams = _extractPathParameters(pathPattern); + + final declaredParams = path.parameters + .where((p) => p.location == ParameterLocation.path) + .map((p) => p.name) + .toSet(); + + // 检查路径中的参数是否都有声明 + for (final param in pathParams) { + if (!declaredParams.contains(param)) { + _errors.add(ValidationError( + path: 'paths["$pathPattern"][${path.method.value}].parameters', + message: '路径参数 "$param" 未在参数列表中声明', + type: ValidationErrorType.reference, + suggestion: '添加路径参数的声明', + )); + } + } + + // 检查声明的路径参数是否都在路径中使用 + for (final param in declaredParams) { + if (!pathParams.contains(param)) { + _warnings.add(ValidationWarning( + path: 'paths["$pathPattern"][${path.method.value}].parameters', + message: '声明的路径参数 "$param" 未在路径中使用', + suggestion: '移除未使用的参数声明或修正路径', + )); + } + } + }); + } + + /// 验证组件引用 + void _validateComponentReferences(SwaggerDocument document) { + final schemas = document.components.schemas.keys.toSet(); + final securitySchemes = document.components.securitySchemes.keys.toSet(); + + // 收集所有引用 + final schemaRefs = {}; + final securityRefs = {}; + + _collectReferences(document, schemaRefs, securityRefs); + + // 检查未定义的引用 + for (final ref in schemaRefs) { + if (!schemas.contains(ref)) { + _errors.add(ValidationError( + path: 'components.schemas', + message: '引用的 schema "$ref" 未定义', + type: ValidationErrorType.reference, + suggestion: '定义缺失的 schema 或修正引用', + )); + } + } + + for (final ref in securityRefs) { + if (!securitySchemes.contains(ref)) { + _errors.add(ValidationError( + path: 'components.securitySchemes', + message: '引用的安全方案 "$ref" 未定义', + type: ValidationErrorType.reference, + suggestion: '定义缺失的安全方案或修正引用', + )); + } + } + + // 检查未使用的组件 + for (final schema in schemas) { + if (!schemaRefs.contains(schema)) { + _warnings.add(ValidationWarning( + path: 'components.schemas["$schema"]', + message: 'Schema "$schema" 已定义但未被使用', + suggestion: '移除未使用的 schema 或添加引用', + )); + } + } + } + + /// 验证安全方案引用 + void _validateSecurityReferences(SwaggerDocument document) { + final definedSchemes = document.components.securitySchemes.keys.toSet(); + + // 检查全局安全要求 + for (int i = 0; i < document.security.length; i++) { + final requirement = document.security[i]; + for (final schemeName in requirement.schemeNames) { + if (!definedSchemes.contains(schemeName)) { + _errors.add(ValidationError( + path: 'security[$i]', + message: '引用的安全方案 "$schemeName" 未定义', + type: ValidationErrorType.reference, + suggestion: '在 components.securitySchemes 中定义该安全方案', + )); + } + } + } + + // 检查操作级别的安全要求 + document.paths.forEach((pathPattern, path) { + for (int i = 0; i < path.security.length; i++) { + final requirement = path.security[i]; + for (final schemeName in requirement.schemeNames) { + if (!definedSchemes.contains(schemeName)) { + _errors.add(ValidationError( + path: 'paths["$pathPattern"][${path.method.value}].security[$i]', + message: '引用的安全方案 "$schemeName" 未定义', + type: ValidationErrorType.reference, + suggestion: '在 components.securitySchemes 中定义该安全方案', + )); + } + } + } + }); + } + + /// 验证示例一致性 + void _validateExampleConsistency(SwaggerDocument document) { + document.paths.forEach((pathPattern, path) { + // 验证请求体示例 + if (path.requestBody != null) { + path.requestBody!.content.forEach((mediaType, content) { + _validateMediaTypeExamples(content, + '$pathPattern[${path.method.value}].requestBody.content["$mediaType"]'); + }); + } + + // 验证响应示例 + path.responses.forEach((code, response) { + response.content.forEach((mediaType, content) { + _validateMediaTypeExamples(content, + '$pathPattern[${path.method.value}].responses["$code"].content["$mediaType"]'); + }); + }); + }); + } + + /// 验证媒体类型示例 + void _validateMediaTypeExamples(ApiMediaType mediaType, String path) { + // 检查 example 和 examples 不能同时存在 + if (mediaType.example != null && mediaType.examples.isNotEmpty) { + _warnings.add(ValidationWarning( + path: path, + message: 'example 和 examples 不应同时存在', + suggestion: '使用 examples 对象来提供多个示例', + )); + } + + // 验证示例格式 + if (mediaType.example != null && mediaType.schema != null) { + // TODO: 根据 schema 验证 example 的格式 + } + } + + /// 验证响应结构 + void _validateResponseStructure(SwaggerDocument document) { + document.paths.forEach((pathPattern, path) { + // 检查是否有成功响应 + final hasSuccessResponse = path.responses.keys.any((code) { + final statusCode = int.tryParse(code) ?? 0; + return statusCode >= 200 && statusCode < 300; + }); + + if (!hasSuccessResponse) { + _warnings.add(ValidationWarning( + path: 'paths["$pathPattern"][${path.method.value}].responses', + message: '缺少成功响应 (2xx)', + suggestion: '添加至少一个成功响应', + )); + } + + // 检查错误响应 + final hasErrorResponse = path.responses.keys.any((code) { + final statusCode = int.tryParse(code) ?? 0; + return statusCode >= 400; + }); + + if (!hasErrorResponse) { + _warnings.add(ValidationWarning( + path: 'paths["$pathPattern"][${path.method.value}].responses', + message: '建议添加错误响应 (4xx/5xx)', + suggestion: '添加常见的错误响应,如 400、401、404、500', + )); + } + }); + } + + /// 验证参数一致性 + void _validateParameterConsistency(SwaggerDocument document) { + final parameterNames = >{}; + + document.paths.forEach((pathPattern, path) { + for (final param in path.parameters) { + final key = '${param.location.name}:${param.name}'; + parameterNames.putIfAbsent(pathPattern, () => {}); + + if (parameterNames[pathPattern]!.contains(key)) { + _errors.add(ValidationError( + path: 'paths["$pathPattern"][${path.method.value}].parameters', + message: '重复的参数: ${param.name} (${param.location.name})', + type: ValidationErrorType.constraint, + suggestion: '确保参数名称在同一位置类型中唯一', + )); + } else { + parameterNames[pathPattern]!.add(key); + } + } + }); + } + + /// 检查路径是否冲突 + bool _pathsConflict(String path1, String path2) { + if (path1 == path2) return true; + + // 将路径参数替换为通配符进行比较 + final normalized1 = path1.replaceAll(RegExp(r'\{[^}]+\}'), '*'); + final normalized2 = path2.replaceAll(RegExp(r'\{[^}]+\}'), '*'); + + return normalized1 == normalized2; + } + + /// 提取路径参数 + Set _extractPathParameters(String path) { + final regex = RegExp(r'\{([^}]+)\}'); + final matches = regex.allMatches(path); + return matches.map((match) => match.group(1)!).toSet(); + } + + /// 收集所有引用 + void _collectReferences(SwaggerDocument document, Set schemaRefs, + Set securityRefs) { + // 从路径中收集引用 + document.paths.forEach((pathPattern, path) { + // 从参数中收集引用 + for (final _ in path.parameters) { + // TODO: 收集参数 schema 引用 + } + + // 从请求体中收集引用 + if (path.requestBody != null) { + path.requestBody!.content.forEach((mediaType, content) { + _collectSchemaReferences(content.schema, schemaRefs); + }); + } + + // 从响应中收集引用 + path.responses.forEach((code, response) { + response.content.forEach((mediaType, content) { + _collectSchemaReferences(content.schema, schemaRefs); + }); + }); + + // 从安全要求中收集引用 + for (final requirement in path.security) { + securityRefs.addAll(requirement.schemeNames); + } + }); + + // 从全局安全要求中收集引用 + for (final requirement in document.security) { + securityRefs.addAll(requirement.schemeNames); + } + + // 从组件中收集引用 + document.components.schemas.forEach((name, model) { + for (final _ in model.properties.values) { + // TODO: 收集属性 schema 引用 + } + }); + } + + /// 收集 Schema 引用 + void _collectSchemaReferences( + Map? schema, Set refs) { + if (schema == null) return; + + // 检查 $ref + final ref = schema['\$ref'] as String?; + if (ref != null && ref.startsWith('#/components/schemas/')) { + final refName = ref.substring('#/components/schemas/'.length); + refs.add(refName); + } + + // 递归检查嵌套 schema + if (schema['items'] is Map) { + _collectSchemaReferences(schema['items'] as Map, refs); + } + + if (schema['properties'] is Map) { + final properties = schema['properties'] as Map; + properties.forEach((key, value) { + if (value is Map) { + _collectSchemaReferences(value, refs); + } + }); + } + + // 检查组合 schema + for (final key in ['allOf', 'oneOf', 'anyOf']) { + if (schema[key] is List) { + final list = schema[key] as List; + for (final item in list) { + if (item is Map) { + _collectSchemaReferences(item, refs); + } + } + } + } + } +} diff --git a/pubspec.lock b/pubspec.lock index 3709437..b05ac26 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -185,6 +185,22 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "3.1.0" + dio: + dependency: "direct dev" + description: + name: dio + sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9" + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.8.0+1" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.1" fake_async: dependency: transitive description: @@ -324,7 +340,7 @@ packages: source: hosted version: "3.0.1" logging: - dependency: transitive + dependency: "direct main" description: name: logging sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 @@ -380,7 +396,7 @@ packages: source: hosted version: "2.2.0" path: - dependency: transitive + dependency: "direct main" description: name: path sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" @@ -411,6 +427,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.5.0" + retrofit: + dependency: "direct dev" + description: + name: retrofit + sha256: "84d70114a5b6bae5f4c1302335f9cb610ebeb1b02023d5e7e87697aaff52926a" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.6.0" shelf: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index f0be349..0e8ae97 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -19,12 +19,12 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - + # 代码生成必需的依赖 build_runner: ^2.4.7 json_serializable: ^6.7.1 test: ^1.24.0 - + dio: any retrofit: any learning_officer_oa: any diff --git a/run_swagger.sh b/run_swagger.sh index 115aa40..6ecde0c 100755 --- a/run_swagger.sh +++ b/run_swagger.sh @@ -20,16 +20,53 @@ show_help() { echo -e "${YELLOW}用法: $0 [命令] [选项]${NC}" echo "" echo -e "${GREEN}快速命令:${NC}" - echo -e " $0 all # 生成所有文件" + 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 @@ -37,26 +74,50 @@ main() { exit 0 fi + # 检查必要工具(除了 clean 命令) + if [ "$1" != "clean" ]; then + check_prerequisites + fi + case "$1" in all) - dart run "$CLI_DART_FILE" generate --models --api --split-by-tags - dart format . - dart fix --apply + dart run "$CLI_DART_FILE" generate --models --api + dart fix --apply # 先修复和排序 imports + dart format . # 再格式化代码 ;; models) dart run "$CLI_DART_FILE" generate --models - dart format . - dart fix --apply + dart fix --apply # 先修复和排序 imports + dart format . # 再格式化代码 ;; docs) dart run "$CLI_DART_FILE" generate --docs - dart format . - dart fix --apply + dart fix --apply # 先修复和排序 imports + dart format . # 再格式化代码 ;; api) dart run "$CLI_DART_FILE" generate --api - dart format . - dart fix --apply + 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}" diff --git a/swagger.json b/swagger.json new file mode 100644 index 0000000..e805c3d --- /dev/null +++ b/swagger.json @@ -0,0 +1,12902 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "OA移动端Api", + "version": "v1" + }, + "paths": { + "/api/v1/FollowManager/GetSubjectinfos": { + "get": { + "tags": [ + "FollowManager" + ], + "summary": "获取所有科目列表", + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Subjectinfo" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Subjectinfo" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Subjectinfo" + } + } + } + } + } + } + } + }, + "/api/v1/FollowManager/GetClassSubs": { + "get": { + "tags": [ + "FollowManager" + ], + "summary": "工作台-根据班级id获取班级科目绑定表列表", + "parameters": [ + { + "name": "classId", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ClassSubResult" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ClassSubResult" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ClassSubResult" + } + } + } + } + } + } + } + }, + "/api/v1/FollowManager/GetClassTeachersByClassId": { + "get": { + "tags": [ + "FollowManager" + ], + "summary": "工作台-根据班级id获取班级教师绑定表列表", + "parameters": [ + { + "name": "classId", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ClassTeacherResult" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ClassTeacherResult" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ClassTeacherResult" + } + } + } + } + } + } + } + }, + "/api/v1/FollowManager/AddUpdateClassTeachers": { + "put": { + "tags": [ + "FollowManager" + ], + "summary": "工作台-添加班级教师绑定表(会删除之前的)", + "parameters": [ + { + "name": "ClassesId", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "requestBody": { + "description": "", + "content": { + "application/json-patch+json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ClassTeacherRequest" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ClassTeacherRequest" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ClassTeacherRequest" + } + } + }, + "application/*+json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ClassTeacherRequest" + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/api/v1/FollowManager/DeleteClassTeachers": { + "delete": { + "tags": [ + "FollowManager" + ], + "summary": "工作台-删除班级教师绑定表", + "parameters": [ + { + "name": "id", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/api/v1/FollowManager/GetFinancial_Indicators": { + "get": { + "tags": [ + "FollowManager" + ], + "summary": "工作台-获取当前时间的经费指标", + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/Financial_indicators" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Financial_indicators" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/Financial_indicators" + } + } + } + } + } + } + }, + "/api/v1/FollowManager/GetFinancial_Classes": { + "get": { + "tags": [ + "FollowManager" + ], + "summary": "工作台-获取当前时间的班级经费使用列表", + "parameters": [ + { + "name": "class_id", + "in": "query", + "description": "班级id", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/FinancialClassSumResult" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/FinancialClassSumResult" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/FinancialClassSumResult" + } + } + } + } + } + } + }, + "/api/v1/FollowManager/AddFinancial_Classes": { + "put": { + "tags": [ + "FollowManager" + ], + "summary": "工作台-添加班级经费使用", + "requestBody": { + "description": "", + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/FinancialClassRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/FinancialClassRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/FinancialClassRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/FinancialClassRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/api/v1/FollowManager/GetStudents": { + "get": { + "tags": [ + "FollowManager" + ], + "summary": "工作台-获取班级学生列表", + "parameters": [ + { + "name": "classId", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/StudentResult" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/StudentResult" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/StudentResult" + } + } + } + } + } + } + } + }, + "/api/v1/FollowManager/AddClassesStudent": { + "put": { + "tags": [ + "FollowManager" + ], + "summary": "工作台-添加班级学生绑定表", + "requestBody": { + "description": "", + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/StudentRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/StudentRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/StudentRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/StudentRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/api/v1/FollowManager/DeleteClassesStudent": { + "delete": { + "tags": [ + "FollowManager" + ], + "summary": "工作台-删除班级学生绑定表", + "parameters": [ + { + "name": "classId", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "userId", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/HealthCheck": { + "get": { + "tags": [ + "HealthCheck" + ], + "summary": "健康检查接口", + "parameters": [ + { + "name": "api-version", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/v1/Index/GetBanner": { + "get": { + "tags": [ + "Index" + ], + "summary": "获取首页的轮播图", + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BannerResult" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BannerResult" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BannerResult" + } + } + } + } + } + } + } + }, + "/api/v1/Index/GetDatetimeNow": { + "get": { + "tags": [ + "Index" + ], + "summary": "获取服务器当前时间", + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + }, + "application/json": { + "schema": { + "type": "string" + } + }, + "text/json": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/api/v1/Index/GetClasses": { + "get": { + "tags": [ + "Index" + ], + "summary": "获取本人所管班级列表", + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Classes" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Classes" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Classes" + } + } + } + } + } + } + } + }, + "/api/v1/Index/GetClassesTaskChecklistUsers": { + "get": { + "tags": [ + "Index" + ], + "summary": "获取本人通用工作指标列表", + "parameters": [ + { + "name": "PageIndex", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "default": 1 + } + }, + { + "name": "PageSize", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "default": 10 + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/Index_ClassesTaskCheckList_UserPageResponse" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Index_ClassesTaskCheckList_UserPageResponse" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/Index_ClassesTaskCheckList_UserPageResponse" + } + } + } + } + } + } + }, + "/api/v1/Index/GetClassesTaskList": { + "get": { + "tags": [ + "Index" + ], + "summary": "获取首页的待办任务列表", + "parameters": [ + { + "name": "class_id", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "PageIndex", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int32", + "default": 1 + } + }, + { + "name": "PageSize", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int32", + "default": 10 + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ClassesTaskListResultPageResponse" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/ClassesTaskListResultPageResponse" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ClassesTaskListResultPageResponse" + } + } + } + } + } + } + }, + "/api/v1/Index/GetTaskList": { + "get": { + "tags": [ + "Index" + ], + "summary": "获取任务列表", + "parameters": [ + { + "name": "PageIndex", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int32", + "default": 1 + } + }, + { + "name": "PageSize", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int32", + "default": 10 + } + }, + { + "name": "Task_index_type", + "in": "query", + "description": "任务指标类型-1班级;2通用", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "class_id", + "in": "query", + "description": "Task_index_type为1时,班级id必传", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "TaskTypeEnum", + "in": "query", + "description": "任务类型枚举", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "BeginDate", + "in": "query", + "description": "开始日期", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "EndDate", + "in": "query", + "description": "结束日期", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "Status", + "in": "query", + "description": "0:未开始 1:进行中 2:已结束 3:问题待处理(仅用于双师跟课)。多个用英文逗号分开", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ClassesTaskListResultPageResponse" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/ClassesTaskListResultPageResponse" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ClassesTaskListResultPageResponse" + } + } + } + } + } + } + }, + "/api/v1/Index/GetHistoryTaskList": { + "get": { + "tags": [ + "Index" + ], + "summary": "获取历史任务列表", + "parameters": [ + { + "name": "PageIndex", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int32", + "default": 1 + } + }, + { + "name": "PageSize", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int32", + "default": 10 + } + }, + { + "name": "Task_index_type", + "in": "query", + "description": "任务指标类型-1班级;2通用", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "class_id", + "in": "query", + "description": "Task_index_type为1时,班级id必传", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "TaskTypeEnum", + "in": "query", + "description": "任务类型枚举", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "BeginDate", + "in": "query", + "description": "开始日期", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "EndDate", + "in": "query", + "description": "结束日期", + "schema": { + "type": "string", + "format": "date-time" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ClassesTaskListResultPageResponse" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/ClassesTaskListResultPageResponse" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ClassesTaskListResultPageResponse" + } + } + } + } + } + } + }, + "/api/v1/Login/GetOpenTest": { + "get": { + "tags": [ + "Login" + ], + "summary": "获取是否开启测试", + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/api/v1/Login/userLogin": { + "post": { + "tags": [ + "Login" + ], + "summary": "普通用户登录", + "requestBody": { + "description": "登录信息", + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/LoginRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/LoginRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/LoginRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/LoginRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/userLoginResult" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/userLoginResult" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/userLoginResult" + } + } + } + } + } + } + }, + "/api/v1/Login/GetMyUserSig": { + "get": { + "tags": [ + "Login" + ], + "summary": "获取 UserSig 鉴权票据", + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + }, + "application/json": { + "schema": { + "type": "string" + } + }, + "text/json": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/api/v1/Login/userCodeLogin": { + "post": { + "tags": [ + "Login" + ], + "summary": "普通用户验证码登录", + "requestBody": { + "description": "登录信息", + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/LoginCodeRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/LoginCodeRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/LoginCodeRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/LoginCodeRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/userLoginResult" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/userLoginResult" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/userLoginResult" + } + } + } + } + } + } + }, + "/api/v1/Login/GetUserLoginCode": { + "post": { + "tags": [ + "Login" + ], + "summary": "普通用户验证码登录-获取验证码", + "requestBody": { + "description": "获取信息", + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/userLoginRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/userLoginRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/userLoginRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/userLoginRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/v1/Login/RefreshToken": { + "post": { + "tags": [ + "Login" + ], + "summary": "换取token", + "requestBody": { + "description": "登录信息", + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/RefreshTokenRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/RefreshTokenRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/RefreshTokenRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/RefreshTokenRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + }, + "application/json": { + "schema": { + "type": "string" + } + }, + "text/json": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/api/v1/Login/Register": { + "put": { + "tags": [ + "Login" + ], + "summary": "注册", + "requestBody": { + "description": "", + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/RegisterRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegisterRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/RegisterRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/RegisterRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/v1/Login/LogOff": { + "post": { + "tags": [ + "Login" + ], + "summary": "注销", + "parameters": [ + { + "name": "account", + "in": "query", + "description": "账号", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/v1/MobileManager/GetMinisterAdminUsers": { + "post": { + "tags": [ + "MobileManager" + ], + "summary": "获取本人是部长管理的组长用户列表", + "parameters": [ + { + "name": "TeamLeaderUserName", + "in": "query", + "description": "用户姓名", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserFoundationResult" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserFoundationResult" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserFoundationResult" + } + } + } + } + } + } + } + }, + "/api/v1/MobileManager/GetMinisterAdminUsersAll": { + "post": { + "tags": [ + "MobileManager" + ], + "summary": "获取本人是部长管理的组长和学习官用户列表", + "parameters": [ + { + "name": "TeamLeaderUserName", + "in": "query", + "description": "用户姓名", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserFoundationResult" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserFoundationResult" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserFoundationResult" + } + } + } + } + } + } + } + }, + "/api/v1/MobileManager/GetTeamLeaderUsers": { + "post": { + "tags": [ + "MobileManager" + ], + "summary": "获取本人是组长管理的学习官用户列表", + "requestBody": { + "description": "组长id集合,不传则本人id", + "content": { + "application/json-patch+json": { + "schema": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + } + }, + "application/*+json": { + "schema": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserFoundationResult" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserFoundationResult" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserFoundationResult" + } + } + } + } + } + } + } + }, + "/api/v1/MobileManager/GetSchoolResults": { + "get": { + "tags": [ + "MobileManager" + ], + "summary": "获取管理的学校列表", + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SchoolResult" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SchoolResult" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SchoolResult" + } + } + } + } + } + } + } + }, + "/api/v1/MobileManager/GetClassesBySchoolId": { + "get": { + "tags": [ + "MobileManager" + ], + "summary": "根据学校获取所有班级列表", + "parameters": [ + { + "name": "schoolId", + "in": "query", + "description": "学校id", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ClassesResult" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ClassesResult" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ClassesResult" + } + } + } + } + } + } + } + }, + "/api/v1/MobileManager/GetBindClassesBySchoolId": { + "get": { + "tags": [ + "MobileManager" + ], + "summary": "根据学校获取这个学校【本人管理班级】列表", + "parameters": [ + { + "name": "schoolId", + "in": "query", + "description": "学校id", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ClassesResult" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ClassesResult" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ClassesResult" + } + } + } + } + } + } + } + }, + "/api/v1/MobileManager/GetClassesAndFollowBySchoolId": { + "get": { + "tags": [ + "MobileManager" + ], + "summary": "根据学校获取管理的班级列表(包含学习官信息)", + "parameters": [ + { + "name": "schoolId", + "in": "query", + "description": "学校id", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ClassesAndFollowResult" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ClassesAndFollowResult" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ClassesAndFollowResult" + } + } + } + } + } + } + } + }, + "/api/v1/MobileManager/GetClassesTaskChecklists": { + "get": { + "tags": [ + "MobileManager" + ], + "summary": "组长根据班级id获取工作任务指标列表", + "parameters": [ + { + "name": "classesId", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ClassManageTaskListResult" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/ClassManageTaskListResult" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ClassManageTaskListResult" + } + } + } + } + } + } + }, + "/api/v1/MobileManager/ClassManagerAddUpdateTaskCheckList": { + "put": { + "tags": [ + "MobileManager" + ], + "summary": "组长新增工作任务指标", + "parameters": [ + { + "name": "followId", + "in": "query", + "description": "学习官id集合", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "requestBody": { + "description": "", + "content": { + "application/json-patch+json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ClassManager_Task_checklistRequest" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ClassManager_Task_checklistRequest" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ClassManager_Task_checklistRequest" + } + } + }, + "application/*+json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ClassManager_Task_checklistRequest" + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/api/v1/MobileManager/GetTaskCheckListByType": { + "get": { + "tags": [ + "MobileManager" + ], + "summary": "部长获取工作任务指标列表", + "parameters": [ + { + "name": "TaskType", + "in": "query", + "description": "班级/通用任务类型 1班级;2通用", + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Task_checklistResult" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Task_checklistResult" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Task_checklistResult" + } + } + } + } + } + } + } + }, + "/api/v1/MobileManager/AddTaskCheckList": { + "put": { + "tags": [ + "MobileManager" + ], + "summary": "部长新增工作任务指标\r\n(会删除所有管理的班级任务指标-删除所有管理的学习官的通用任务指标)", + "requestBody": { + "description": "", + "content": { + "application/json-patch+json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Task_checklistRequest" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Task_checklistRequest" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Task_checklistRequest" + } + } + }, + "application/*+json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Task_checklistRequest" + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/api/v1/MobileManager/GetDataSummariz": { + "get": { + "tags": [ + "MobileManager" + ], + "summary": "部长获取数据统计", + "parameters": [ + { + "name": "BeginTime", + "in": "query", + "description": "", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "EndTime", + "in": "query", + "description": "", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "FollowId", + "in": "query", + "description": "学习官id【可能存在组长id,组长也是学习官】", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "TeamLeaderUserId", + "in": "query", + "description": "组长id,查询该组所有人", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TaskFinishList" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TaskFinishList" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TaskFinishList" + } + } + } + } + } + } + } + }, + "/api/v1/MyInfo/GetTencentIMAppID": { + "get": { + "tags": [ + "MyInfo" + ], + "summary": "获取腾讯IM的AppID", + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "integer", + "format": "int32" + } + }, + "application/json": { + "schema": { + "type": "integer", + "format": "int32" + } + }, + "text/json": { + "schema": { + "type": "integer", + "format": "int32" + } + } + } + } + } + } + }, + "/api/v1/MyInfo/GetOssConfig": { + "get": { + "tags": [ + "MyInfo" + ], + "summary": "获取oss配置", + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/OSSConfigResult" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/OSSConfigResult" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/OSSConfigResult" + } + } + } + } + } + } + }, + "/api/v1/MyInfo/GetOssSign": { + "get": { + "tags": [ + "MyInfo" + ], + "summary": "获取oss预签名", + "parameters": [ + { + "name": "objectName", + "in": "query", + "description": "只传后缀。例【https://meeting-yhzh.oss-cn-hangzhou.aliyuncs.com/sss/11.txt】中的【txt】", + "schema": { + "type": "string" + } + }, + { + "name": "type", + "in": "query", + "description": "文件模块类型:1、普通文件上传; 2:资料收集", + "schema": { + "type": "integer", + "format": "int32", + "default": 1 + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/OssSignResult" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/OssSignResult" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/OssSignResult" + } + } + } + } + } + } + }, + "/api/v1/MyInfo/DeleteOSSFile": { + "delete": { + "tags": [ + "MyInfo" + ], + "summary": "删除oss存储中的服务", + "parameters": [ + { + "name": "filePath", + "in": "query", + "description": "完整网络路径", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/api/v1/MyInfo/GetUpdateVersion": { + "get": { + "tags": [ + "MyInfo" + ], + "summary": "获取APP/客户端版本更新信息", + "parameters": [ + { + "name": "upType", + "in": "query", + "description": "1:APP,2:客户端", + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/UpdateappResult" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateappResult" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/UpdateappResult" + } + } + } + } + } + } + }, + "/api/v1/MyInfo/UpdateMyPhoneInfo": { + "put": { + "tags": [ + "MyInfo" + ], + "summary": "换绑本人手机信息", + "requestBody": { + "description": "", + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/MyPhoneBindRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/MyPhoneBindRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/MyPhoneBindRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/MyPhoneBindRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/api/v1/MyInfo/UpdateMyPasswodInfo": { + "put": { + "tags": [ + "MyInfo" + ], + "summary": "修改本人密码", + "requestBody": { + "description": "", + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/MyInfoResetPwdRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/MyInfoResetPwdRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/MyInfoResetPwdRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/MyInfoResetPwdRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/api/v1/MyInfo/GetPhoneCode": { + "get": { + "tags": [ + "MyInfo" + ], + "summary": "获取验证码-5分钟有效期(登录系统后)", + "parameters": [ + { + "name": "type", + "in": "query", + "description": "1:换绑手机", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "phone", + "in": "query", + "description": "phone", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/api/v1/MyInfo/FileUpload": { + "put": { + "tags": [ + "MyInfo" + ], + "summary": "文件上传(返回文件id)", + "requestBody": { + "description": "", + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/System_filesRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/System_filesRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/System_filesRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/System_filesRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "integer", + "format": "int64" + } + }, + "application/json": { + "schema": { + "type": "integer", + "format": "int64" + } + }, + "text/json": { + "schema": { + "type": "integer", + "format": "int64" + } + } + } + } + } + } + }, + "/api/v1/TaskClassCadreMeeting/AddUpdateClassCadreMeeting": { + "put": { + "tags": [ + "TaskClassCadreMeeting" + ], + "summary": "创建/修改班干部会议任务", + "requestBody": { + "description": "", + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/ClassCadreMeetingRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/ClassCadreMeetingRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ClassCadreMeetingRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/ClassCadreMeetingRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/BaseTaskAddResult" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseTaskAddResult" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/BaseTaskAddResult" + } + } + } + } + } + } + }, + "/api/v1/TaskClassCadreMeeting/DeleteClassCadreMeetingFile": { + "delete": { + "tags": [ + "TaskClassCadreMeeting" + ], + "summary": "删除班干部会议文件信息", + "parameters": [ + { + "name": "ClassmeetingId", + "in": "query", + "description": "班干部会id", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "fileId", + "in": "query", + "description": "文件id", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/api/v1/TaskClassCadreMeeting/ClassCadreMeetingFinish": { + "put": { + "tags": [ + "TaskClassCadreMeeting" + ], + "summary": "完成班干部会议任务", + "requestBody": { + "description": "", + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/ClassCadreMeetingFinishRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/ClassCadreMeetingFinishRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ClassCadreMeetingFinishRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/ClassCadreMeetingFinishRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/api/v1/TaskClassCadreMeeting/GetClassCadreMeetingResult": { + "get": { + "tags": [ + "TaskClassCadreMeeting" + ], + "summary": "根据任务id获取班干部会议信息", + "parameters": [ + { + "name": "TaskId", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ClassCadreMeetingResult" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/ClassCadreMeetingResult" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ClassCadreMeetingResult" + } + } + } + } + } + } + }, + "/api/v1/TaskClassesActivity/AddUpdateTaskClassesActivity": { + "put": { + "tags": [ + "TaskClassesActivity" + ], + "summary": "添加或更新班级活动", + "requestBody": { + "description": "", + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/ClassesActivityRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/ClassesActivityRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ClassesActivityRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/ClassesActivityRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/BaseTaskAddResult" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseTaskAddResult" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/BaseTaskAddResult" + } + } + } + } + } + } + }, + "/api/v1/TaskClassesActivity/DeleteTaskClassesActivityFile": { + "delete": { + "tags": [ + "TaskClassesActivity" + ], + "summary": "删除班级活动文件信息", + "parameters": [ + { + "name": "ActivityId", + "in": "query", + "description": "活动Id", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "fileId", + "in": "query", + "description": "文件id", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/api/v1/TaskClassesActivity/TaskClassesActivityFinish": { + "put": { + "tags": [ + "TaskClassesActivity" + ], + "summary": "完成班级活动", + "requestBody": { + "description": "", + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/ClassesActivityFinishRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/ClassesActivityFinishRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ClassesActivityFinishRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/ClassesActivityFinishRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/api/v1/TaskClassesActivity/GetTaskClassesActivityResult": { + "get": { + "tags": [ + "TaskClassesActivity" + ], + "summary": "根据任务id获取班干部班级活动信息", + "parameters": [ + { + "name": "TaskId", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ClassesActivityResult" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/ClassesActivityResult" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ClassesActivityResult" + } + } + } + } + } + } + }, + "/api/v1/TaskClassMeeting/AddUpdateClassMeeting": { + "put": { + "tags": [ + "TaskClassMeeting" + ], + "summary": "创建/修改召开会议任务", + "requestBody": { + "description": "", + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/ClassMeetingRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/ClassMeetingRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ClassMeetingRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/ClassMeetingRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/BaseTaskAddResult" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseTaskAddResult" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/BaseTaskAddResult" + } + } + } + } + } + } + }, + "/api/v1/TaskClassMeeting/DeleteClassMeetingFile": { + "delete": { + "tags": [ + "TaskClassMeeting" + ], + "summary": "删除召开会议文件信息", + "parameters": [ + { + "name": "ClassmeetingId", + "in": "query", + "description": "召开id", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "fileId", + "in": "query", + "description": "文件id", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/api/v1/TaskClassMeeting/ClassMeetingFinish": { + "put": { + "tags": [ + "TaskClassMeeting" + ], + "summary": "完成召开会议任务", + "requestBody": { + "description": "", + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/ClassMeetingFinishRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/ClassMeetingFinishRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ClassMeetingFinishRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/ClassMeetingFinishRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/api/v1/TaskClassMeeting/GetClassMeetingResult": { + "get": { + "tags": [ + "TaskClassMeeting" + ], + "summary": "根据任务id获取召开班级会议信息", + "parameters": [ + { + "name": "TaskId", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ClassMeetingResult" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/ClassMeetingResult" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ClassMeetingResult" + } + } + } + } + } + } + }, + "/api/v1/TaskCoachSub/AddUpdateTaskCoachSub": { + "put": { + "tags": [ + "TaskCoachSub" + ], + "summary": "添加或更新学科辅助", + "requestBody": { + "description": "", + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/CoachSubInfoRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/CoachSubInfoRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/CoachSubInfoRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/CoachSubInfoRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/BaseTaskAddResult" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseTaskAddResult" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/BaseTaskAddResult" + } + } + } + } + } + } + }, + "/api/v1/TaskCoachSub/DeleteTaskCoachSubFile": { + "delete": { + "tags": [ + "TaskCoachSub" + ], + "summary": "删除学科辅助文件信息", + "parameters": [ + { + "name": "CoachId", + "in": "query", + "description": "辅导表任务id", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "fileId", + "in": "query", + "description": "文件id", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/api/v1/TaskCoachSub/TaskCoachSubFinish": { + "put": { + "tags": [ + "TaskCoachSub" + ], + "summary": "完成学科辅助", + "requestBody": { + "description": "", + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/CoachSubInfoFinishRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/CoachSubInfoFinishRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/CoachSubInfoFinishRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/CoachSubInfoFinishRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/api/v1/TaskCoachSub/GetTaskCoachSubResult": { + "get": { + "tags": [ + "TaskCoachSub" + ], + "summary": "根据任务id获取班干部学科辅助信息", + "parameters": [ + { + "name": "TaskId", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/CoachSubInfoResult" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/CoachSubInfoResult" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/CoachSubInfoResult" + } + } + } + } + } + } + }, + "/api/v1/TaskCultural/AddUpdateCulturalTask": { + "put": { + "tags": [ + "TaskCultural" + ], + "summary": "创建/修改文创内容任务", + "requestBody": { + "description": "", + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/CulturalRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/CulturalRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/CulturalRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/CulturalRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/BaseTaskAddResult" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseTaskAddResult" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/BaseTaskAddResult" + } + } + } + } + } + } + }, + "/api/v1/TaskCultural/DeleteCulturalFile": { + "delete": { + "tags": [ + "TaskCultural" + ], + "summary": "删除文创内容文件", + "parameters": [ + { + "name": "CulturalId", + "in": "query", + "description": "文创id", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "FileId", + "in": "query", + "description": "文件id", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/api/v1/TaskCultural/CulturalFinish": { + "post": { + "tags": [ + "TaskCultural" + ], + "summary": "完成文创内容任务", + "requestBody": { + "description": "", + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/CulturalFinishRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/CulturalFinishRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/CulturalFinishRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/CulturalFinishRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/api/v1/TaskCultural/GetCulturalDetailResult": { + "get": { + "tags": [ + "TaskCultural" + ], + "summary": "获取文创内容详情结果", + "parameters": [ + { + "name": "taskId", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/CulturalDetailResult" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/CulturalDetailResult" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/CulturalDetailResult" + } + } + } + } + } + } + }, + "/api/v1/TaskDataCollect/AddUpdateDataCollect": { + "put": { + "tags": [ + "TaskDataCollect" + ], + "summary": "创建/修改数据收集任务", + "requestBody": { + "description": "", + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/DataCollectRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/DataCollectRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/DataCollectRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/DataCollectRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/BaseTaskAddResult" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseTaskAddResult" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/BaseTaskAddResult" + } + } + } + } + } + } + }, + "/api/v1/TaskDataCollect/GetDataCollectUserResults": { + "get": { + "tags": [ + "TaskDataCollect" + ], + "summary": "获取数据收集用户列表", + "parameters": [ + { + "name": "ClassId", + "in": "query", + "description": "班级id", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "CollectId", + "in": "query", + "description": "采集任务id", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DataCollectUserResult" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DataCollectUserResult" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DataCollectUserResult" + } + } + } + } + } + } + } + }, + "/api/v1/TaskDataCollect/GetSunTaskFileResultsByUserId": { + "get": { + "tags": [ + "TaskDataCollect" + ], + "summary": "获取用户的数据收集", + "parameters": [ + { + "name": "UserId", + "in": "query", + "description": "用户id", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "CollectId", + "in": "query", + "description": "采集任务id", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SunTaskFileResult" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SunTaskFileResult" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SunTaskFileResult" + } + } + } + } + } + } + } + }, + "/api/v1/TaskDataCollect/SaveDataCollectFile": { + "put": { + "tags": [ + "TaskDataCollect" + ], + "summary": "保存数据收集文件信息", + "requestBody": { + "description": "", + "content": { + "application/json-patch+json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DataCollectFileRequest" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DataCollectFileRequest" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DataCollectFileRequest" + } + } + }, + "application/*+json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DataCollectFileRequest" + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/api/v1/TaskDataCollect/DeleteDataCollectFile": { + "delete": { + "tags": [ + "TaskDataCollect" + ], + "summary": "删除数据收集文件信息", + "parameters": [ + { + "name": "SunTaskId", + "in": "query", + "description": "数据收集id", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "UserId", + "in": "query", + "description": "用户id", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "FileId", + "in": "query", + "description": "文件id", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/api/v1/TaskDataCollect/DataCollectFinish": { + "put": { + "tags": [ + "TaskDataCollect" + ], + "summary": "完成数据收集任务", + "requestBody": { + "description": "", + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/DataCollectFinishRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/DataCollectFinishRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/DataCollectFinishRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/DataCollectFinishRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/api/v1/TaskDataCollect/GetDataCollectResult": { + "get": { + "tags": [ + "TaskDataCollect" + ], + "summary": "根据任务id获取数据收集信息", + "parameters": [ + { + "name": "TaskId", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/DataCollectResult" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/DataCollectResult" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/DataCollectResult" + } + } + } + } + } + } + }, + "/api/v1/TaskFollow/GetWeekCourseTable": { + "get": { + "tags": [ + "TaskFollow" + ], + "summary": "根据时间获取当周课程表(时间不传默认一周)", + "parameters": [ + { + "name": "ClassId", + "in": "query", + "description": "班级id必传", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "StartTime", + "in": "query", + "description": "", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "EndTime", + "in": "query", + "description": "", + "schema": { + "type": "string", + "format": "date-time" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WeekModel" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WeekModel" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WeekModel" + } + } + } + } + } + } + } + }, + "/api/v1/TaskFollow/GetCourseWeekDetail": { + "get": { + "tags": [ + "TaskFollow" + ], + "summary": "根据课程ID获取课程详情", + "parameters": [ + { + "name": "ClassCourseId", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/CourseWeekDetailOutput" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/CourseWeekDetailOutput" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/CourseWeekDetailOutput" + } + } + } + } + } + } + }, + "/api/v1/TaskFollow/GetFollowTaskByDate": { + "get": { + "tags": [ + "TaskFollow" + ], + "summary": "获取时间段内双师课堂课程id集合", + "parameters": [ + { + "name": "ClassesId", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "BeginDateTime", + "in": "query", + "description": "", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "EndDateTime", + "in": "query", + "description": "", + "schema": { + "type": "string", + "format": "date-time" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + } + } + } + } + } + } + }, + "/api/v1/TaskFollow/AddUpdateTaskCoachSub": { + "put": { + "tags": [ + "TaskFollow" + ], + "summary": "添加双师课堂(无修改)", + "requestBody": { + "description": "", + "content": { + "application/json-patch+json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FollowInfoRequest" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FollowInfoRequest" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FollowInfoRequest" + } + } + }, + "application/*+json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FollowInfoRequest" + } + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/v1/TaskFollow/AddFollowAbsenceUser": { + "put": { + "tags": [ + "TaskFollow" + ], + "summary": "添加缺勤人员(每次覆盖上一次的)", + "parameters": [ + { + "name": "FollowId", + "in": "query", + "description": "跟课记录id", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "requestBody": { + "description": "", + "content": { + "application/json-patch+json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FollowAbsenceUserRequest" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FollowAbsenceUserRequest" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FollowAbsenceUserRequest" + } + } + }, + "application/*+json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FollowAbsenceUserRequest" + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/api/v1/TaskFollow/AddFollowKeyNoteUser": { + "put": { + "tags": [ + "TaskFollow" + ], + "summary": "添加重点人员(每次覆盖上一次的)", + "parameters": [ + { + "name": "FollowId", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "requestBody": { + "description": "", + "content": { + "application/json-patch+json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FollowKeyNoteUserRequest" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FollowKeyNoteUserRequest" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FollowKeyNoteUserRequest" + } + } + }, + "application/*+json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FollowKeyNoteUserRequest" + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/api/v1/TaskFollow/AddFollowTeachersituation": { + "put": { + "tags": [ + "TaskFollow" + ], + "summary": "添加老师、学生问题(每次覆盖上一次的)", + "requestBody": { + "description": "", + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/FollowQuestionRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/FollowQuestionRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/FollowQuestionRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/FollowQuestionRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/api/v1/TaskFollow/TaskCoachSubFinish": { + "put": { + "tags": [ + "TaskFollow" + ], + "summary": "完成双师课堂(完成后判断是否有待处理任务。状态改为已完成或待处理)", + "requestBody": { + "description": "", + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/FollowInfoFinishRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/FollowInfoFinishRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/FollowInfoFinishRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/FollowInfoFinishRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/api/v1/TaskFollow/GetTaskCoachSubResult": { + "get": { + "tags": [ + "TaskFollow" + ], + "summary": "根据任务id获取班干部双师课堂信息", + "parameters": [ + { + "name": "TaskId", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/FollowInfoResult" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/FollowInfoResult" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/FollowInfoResult" + } + } + } + } + } + } + }, + "/api/v1/TaskInfo/GetTaskTypeInfo": { + "get": { + "tags": [ + "TaskInfo" + ], + "summary": "获取任务类型列表", + "parameters": [ + { + "name": "PageIndex", + "in": "query", + "description": "当前页", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "PageSize", + "in": "query", + "description": "一页条数", + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/SysParameterPageResponse" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/SysParameterPageResponse" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/SysParameterPageResponse" + } + } + } + } + } + } + }, + "/api/v1/TaskInfo/GetTaskTypeInfoByPid": { + "get": { + "tags": [ + "TaskInfo" + ], + "summary": "根据父级ID获取该级任务类型列表", + "parameters": [ + { + "name": "pid", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SysParameter" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SysParameter" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SysParameter" + } + } + } + } + } + } + } + }, + "/api/v1/TaskInfo/GetTaskTypeInfoAllByPid": { + "get": { + "tags": [ + "TaskInfo" + ], + "summary": "获取该pid下的所有参数", + "parameters": [ + { + "name": "pid", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SysParameter" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SysParameter" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SysParameter" + } + } + } + } + } + } + } + }, + "/api/v1/TaskInfo/GetTaskTypeInfoAllByPValue": { + "get": { + "tags": [ + "TaskInfo" + ], + "summary": "获取该PValue本级和下级的所有参数", + "parameters": [ + { + "name": "PValue", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/SysParameter" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/SysParameter" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/SysParameter" + } + } + } + } + } + } + }, + "/api/v1/TaskInfo/GetAssistUsers": { + "get": { + "tags": [ + "TaskInfo" + ], + "summary": "获取协助人员(同校同级的学习官、当前账户绑定的组长、组长上级的部长)", + "parameters": [ + { + "name": "ClassesId", + "in": "query", + "description": "班级id", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/AssistUserResult" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/AssistUserResult" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/AssistUserResult" + } + } + } + } + } + } + }, + "/api/v1/TaskInfo/GetCloudSchoolList": { + "get": { + "tags": [ + "TaskInfo" + ], + "summary": "获取通讯录云校树", + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SchoolTree" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SchoolTree" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SchoolTree" + } + } + } + } + } + } + } + }, + "/api/v1/TaskInfo/GetUserBooksResultBySchoolId": { + "get": { + "tags": [ + "TaskInfo" + ], + "summary": "根据学校id查询通讯录人员信息", + "parameters": [ + { + "name": "schoolId", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserBooksResult" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserBooksResult" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserBooksResult" + } + } + } + } + } + } + } + }, + "/api/v1/TaskInfo/GetUserBooksResultByUserId": { + "get": { + "tags": [ + "TaskInfo" + ], + "summary": "根据id获取该用户的资料信息", + "parameters": [ + { + "name": "UserId", + "in": "query", + "description": "用户id", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/SchoolUserBooksResult" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/SchoolUserBooksResult" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/SchoolUserBooksResult" + } + } + } + } + } + } + }, + "/api/v1/TaskInfo/DeleteTask": { + "delete": { + "tags": [ + "TaskInfo" + ], + "summary": "删除任务(适用于所有类型任务)", + "parameters": [ + { + "name": "taskId", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/api/v1/TaskMeeting/AddUpdateTaskMeeting": { + "put": { + "tags": [ + "TaskMeeting" + ], + "summary": "添加或更新会议任务", + "requestBody": { + "description": "", + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/MeetingInfoRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/MeetingInfoRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/MeetingInfoRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/MeetingInfoRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/BaseTaskAddResult" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseTaskAddResult" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/BaseTaskAddResult" + } + } + } + } + } + } + }, + "/api/v1/TaskMeeting/DeleteTaskMeetingFile": { + "delete": { + "tags": [ + "TaskMeeting" + ], + "summary": "删除会议文件信息", + "parameters": [ + { + "name": "MeetingId", + "in": "query", + "description": "会id", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "fileId", + "in": "query", + "description": "文件id", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/api/v1/TaskMeeting/TaskMeetingFinish": { + "put": { + "tags": [ + "TaskMeeting" + ], + "summary": "完成会议任务", + "requestBody": { + "description": "", + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/MeetingInfoFinishRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/MeetingInfoFinishRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/MeetingInfoFinishRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/MeetingInfoFinishRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/api/v1/TaskMeeting/GetTaskMeetingResult": { + "get": { + "tags": [ + "TaskMeeting" + ], + "summary": "根据任务id获取班干部会议信息", + "parameters": [ + { + "name": "TaskId", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/MeetingInfoResult" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/MeetingInfoResult" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/MeetingInfoResult" + } + } + } + } + } + } + }, + "/api/v1/TaskOther/AddUpdateOtherInfo": { + "put": { + "tags": [ + "TaskOther" + ], + "summary": "创建/修改其他任务", + "requestBody": { + "description": "", + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/OtherInfoRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/OtherInfoRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/OtherInfoRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/OtherInfoRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/BaseTaskAddResult" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseTaskAddResult" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/BaseTaskAddResult" + } + } + } + } + } + } + }, + "/api/v1/TaskOther/DeleteOtherInfoFile": { + "delete": { + "tags": [ + "TaskOther" + ], + "summary": "删除其他文件信息", + "parameters": [ + { + "name": "OtherId", + "in": "query", + "description": "其他id", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "fileId", + "in": "query", + "description": "文件id", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/api/v1/TaskOther/OtherInfoFinish": { + "put": { + "tags": [ + "TaskOther" + ], + "summary": "完成其他任务", + "requestBody": { + "description": "", + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/OtherInfoFinishRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/OtherInfoFinishRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/OtherInfoFinishRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/OtherInfoFinishRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/api/v1/TaskOther/GetOtherInfoResult": { + "get": { + "tags": [ + "TaskOther" + ], + "summary": "根据任务id获取其他信息", + "parameters": [ + { + "name": "TaskId", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/OtherInfoResult" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/OtherInfoResult" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/OtherInfoResult" + } + } + } + } + } + } + }, + "/api/v1/TaskSolution/GetList": { + "get": { + "tags": [ + "TaskSolution" + ], + "summary": "获取解决方案列表", + "parameters": [ + { + "name": "ProblemTitle", + "in": "query", + "description": "备 注:问题描述\r\n默认值:", + "schema": { + "type": "string" + } + }, + { + "name": "ProblemObj", + "in": "query", + "description": "备 注:适用对象 1:学校;2:教师;3:学生\r\n默认值:", + "schema": { + "$ref": "#/components/schemas/ToolObjectType" + } + }, + { + "name": "ProblemPhenomenon", + "in": "query", + "description": "备 注:问题显著现象\r\n默认值:", + "schema": { + "type": "string" + } + }, + { + "name": "ClassesId", + "in": "query", + "description": "班级id(必传)", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "ProblemTaskType", + "in": "query", + "description": "任务类型枚举列表", + "schema": { + "$ref": "#/components/schemas/SysTaskTypeEnums" + } + }, + { + "name": "PageIndex", + "in": "query", + "description": "当前页", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "PageSize", + "in": "query", + "description": "一页条数", + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/SolutionListViewMobileDtoPageResponse" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/SolutionListViewMobileDtoPageResponse" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/SolutionListViewMobileDtoPageResponse" + } + } + } + } + } + } + }, + "/api/v1/TaskSolution/GetSingle": { + "get": { + "tags": [ + "TaskSolution" + ], + "summary": "获取解决方案详情", + "parameters": [ + { + "name": "id", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/SolutionDetailViewDto" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/SolutionDetailViewDto" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/SolutionDetailViewDto" + } + } + } + } + } + } + }, + "/api/v1/TaskSolution/GetQuestionListByQuestionType": { + "get": { + "tags": [ + "TaskSolution" + ], + "summary": "根据问题类别获取问题类型列表及数量", + "parameters": [ + { + "name": "questionType", + "in": "query", + "description": "1:学生;2:老师;", + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/QuestionInfoByTypeResult" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/QuestionInfoByTypeResult" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/QuestionInfoByTypeResult" + } + } + } + } + } + } + } + }, + "/api/v1/TaskSolution/GetQuestionListByTaskType": { + "get": { + "tags": [ + "TaskSolution" + ], + "summary": "根据任务问题枚举获取问题列表", + "parameters": [ + { + "name": "taskEnum", + "in": "query", + "description": "任务问题枚举", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "userId", + "in": "query", + "description": "搜索 用户id", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "year", + "in": "query", + "description": "搜索 年", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "month", + "in": "query", + "description": "搜索 月", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "isChain", + "in": "query", + "description": "是否查询环比,0查询有环比的,1查询历史总记录", + "schema": { + "type": "integer", + "format": "int32", + "default": 0 + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/QuestionInfoByTaskEnumResult" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/QuestionInfoByTaskEnumResult" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/QuestionInfoByTaskEnumResult" + } + } + } + } + } + } + }, + "/api/v1/TaskSolution/GetQuestionByUserId": { + "get": { + "tags": [ + "TaskSolution" + ], + "summary": "根据用户id获取问题列表", + "parameters": [ + { + "name": "userId", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/QuestionInfoByTypeResult" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/QuestionInfoByTypeResult" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/QuestionInfoByTypeResult" + } + } + } + } + } + } + } + }, + "/api/v1/TaskSpot/AddSpotCheckTask": { + "put": { + "tags": [ + "TaskSpot" + ], + "summary": "学习行为习惯全面抽查-添加/修改工作任务", + "requestBody": { + "description": "", + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/SpotTaskRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/SpotTaskRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/SpotTaskRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/SpotTaskRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/BaseTaskAddResult" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseTaskAddResult" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/BaseTaskAddResult" + } + } + } + } + } + } + }, + "/api/v1/TaskSpot/AddSpotCheckUser": { + "put": { + "tags": [ + "TaskSpot" + ], + "summary": "学习行为习惯全面抽查-添加工作任务-添加抽查用户(只要有添加,就不能修改任务)", + "requestBody": { + "description": "", + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/SpotTaskUserRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/SpotTaskUserRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/SpotTaskUserRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/SpotTaskUserRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/api/v1/TaskSpot/SpotTaskFinish": { + "put": { + "tags": [ + "TaskSpot" + ], + "summary": "学习行为习惯全面抽查-结束工作任务", + "requestBody": { + "description": "", + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/SpotTaskFinishRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/SpotTaskFinishRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/SpotTaskFinishRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/SpotTaskFinishRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/api/v1/TaskSpot/GetSpotCheckType": { + "get": { + "tags": [ + "TaskSpot" + ], + "summary": "学习行为习惯全面抽查-根据抽查项目 获取任务计划分值标准", + "parameters": [ + { + "name": "Check_id", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SpotCheckTypeValue" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SpotCheckTypeValue" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SpotCheckTypeValue" + } + } + } + } + } + } + } + }, + "/api/v1/TaskSpot/GetSpotTaskInfoById": { + "get": { + "tags": [ + "TaskSpot" + ], + "summary": "学习行为习惯全面抽查-获取抽查任务信息", + "parameters": [ + { + "name": "TaskId", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/SpotTaskResult" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/SpotTaskResult" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/SpotTaskResult" + } + } + } + } + } + } + }, + "/api/v1/TaskSpot/GetSpotTaskUser": { + "get": { + "tags": [ + "TaskSpot" + ], + "summary": "学习行为习惯全面抽查-根据抽查id获取抽查任务用户信息(抽查记录)", + "parameters": [ + { + "name": "SpotId", + "in": "query", + "description": "必传", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "Date", + "in": "query", + "description": "日期", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "UserId", + "in": "query", + "description": "学生id", + "schema": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SpotTaskUserResult" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SpotTaskUserResult" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SpotTaskUserResult" + } + } + } + } + } + } + } + }, + "/api/v1/TaskSpot/GetSpotTaskUserDateBySpotId": { + "get": { + "tags": [ + "TaskSpot" + ], + "summary": "学习行为习惯全面抽查-根据抽查id获取已抽查用户的 日期记录信息", + "parameters": [ + { + "name": "SpotId", + "in": "query", + "description": "必传", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "date-time" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "date-time" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "date-time" + } + } + } + } + } + } + } + }, + "/api/v1/TaskSummarize/GetTaskLogResult": { + "get": { + "tags": [ + "TaskSummarize" + ], + "summary": "获取用户该任务操作记录(工作进度)(日报周报月报通用)", + "parameters": [ + { + "name": "taskId", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "UserId", + "in": "query", + "description": "用户id,不传则获取自己(组长时,获取他作为学习官时绑定的班级或通用任务)", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TaskLoginfoResult" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TaskLoginfoResult" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TaskLoginfoResult" + } + } + } + } + } + } + } + }, + "/api/v1/TaskSummarize/GetSummarizPlanById": { + "get": { + "tags": [ + "TaskSummarize" + ], + "summary": "根据总结id获取总结计划信息(日报周报月报通用)", + "parameters": [ + { + "name": "SummarizId", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SummarizPlan" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SummarizPlan" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SummarizPlan" + } + } + } + } + } + } + } + }, + "/api/v1/TaskSummarize/GetSummarizNoReadByType": { + "get": { + "tags": [ + "TaskSummarize" + ], + "summary": "获取用户未读的总结任务数量", + "parameters": [ + { + "name": "type", + "in": "query", + "description": "1:日报 2:周报 3:月报", + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/SummarizNoReadResult" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/SummarizNoReadResult" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/SummarizNoReadResult" + } + } + } + } + } + } + }, + "/api/v1/TaskSummarize/GetSummarizDayListResult": { + "get": { + "tags": [ + "TaskSummarize" + ], + "summary": "管理端获取本人所管理的人员的日报", + "parameters": [ + { + "name": "UserIds", + "in": "query", + "description": "", + "schema": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + } + }, + { + "name": "UserName", + "in": "query", + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "BeginTime", + "in": "query", + "description": "", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "EndTime", + "in": "query", + "description": "", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "PageIndex", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int32", + "default": 1 + } + }, + { + "name": "PageSize", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int32", + "default": 10 + } + }, + { + "name": "isOnlyNoRead", + "in": "query", + "description": "", + "schema": { + "type": "boolean", + "default": false + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/SummarizDayListResultPageResponse" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/SummarizDayListResultPageResponse" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/SummarizDayListResultPageResponse" + } + } + } + } + } + } + }, + "/api/v1/TaskSummarize/GetSummarizeTaskByDate": { + "get": { + "tags": [ + "TaskSummarize" + ], + "summary": "获取日报中的任务列表-根据日期获取任务列表(日报)", + "parameters": [ + { + "name": "Date", + "in": "query", + "description": "日期", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "UserId", + "in": "query", + "description": "用户id,不传则获取自己(组长时,只获取他作为学习官绑定的班级或通用任务)", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SummarizTaskResult" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SummarizTaskResult" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SummarizTaskResult" + } + } + } + } + } + } + } + }, + "/api/v1/TaskSummarize/AddDaySummarize": { + "put": { + "tags": [ + "TaskSummarize" + ], + "summary": "添加日报", + "requestBody": { + "description": "", + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/SummarizeDayRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/SummarizeDayRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/SummarizeDayRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/SummarizeDayRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/api/v1/TaskSummarize/GetSummarizDayDateSlot": { + "get": { + "tags": [ + "TaskSummarize" + ], + "summary": "获取日报信息(时间段)", + "parameters": [ + { + "name": "BeginDate", + "in": "query", + "description": "", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "EndDate", + "in": "query", + "description": "", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "userId", + "in": "query", + "description": "用户id,不传则获取自己(组长时,获取他作为学习官时绑定的班级或通用任务)", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/v1/TaskSummarize/GetSummarizDay": { + "get": { + "tags": [ + "TaskSummarize" + ], + "summary": "获取日报信息", + "parameters": [ + { + "name": "workDate", + "in": "query", + "description": "", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "userId", + "in": "query", + "description": "用户id,不传则获取自己(组长时,获取他作为学习官时绑定的班级或通用任务)", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/SummarizDayResult" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/SummarizDayResult" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/SummarizDayResult" + } + } + } + } + } + } + }, + "/api/v1/TaskSummarize/EvaluationSumDay": { + "put": { + "tags": [ + "TaskSummarize" + ], + "summary": "评价日报", + "requestBody": { + "description": "", + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/EvaluationSumRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/EvaluationSumRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/EvaluationSumRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/EvaluationSumRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/api/v1/TaskSummarize/GetSummarizWeekListResult": { + "get": { + "tags": [ + "TaskSummarize" + ], + "summary": "管理端获取本人所管理的人员的周报", + "parameters": [ + { + "name": "UserIds", + "in": "query", + "description": "", + "schema": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + } + }, + { + "name": "UserName", + "in": "query", + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "BeginTime", + "in": "query", + "description": "", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "EndTime", + "in": "query", + "description": "", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "PageIndex", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int32", + "default": 1 + } + }, + { + "name": "PageSize", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int32", + "default": 10 + } + }, + { + "name": "isOnlyNoRead", + "in": "query", + "description": "", + "schema": { + "type": "boolean", + "default": false + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/SummarizWeekTaskResultPageResponse" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/SummarizWeekTaskResultPageResponse" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/SummarizWeekTaskResultPageResponse" + } + } + } + } + } + } + }, + "/api/v1/TaskSummarize/GetSummarizWeekTaskByDate": { + "get": { + "tags": [ + "TaskSummarize" + ], + "summary": "获取周报中的任务列表-根据开始日期和结束日期获取任务列表(周报)", + "parameters": [ + { + "name": "BeginDate", + "in": "query", + "description": "开始日期", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "EndDate", + "in": "query", + "description": "结束日期", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "UserId", + "in": "query", + "description": "用户id,不传则获取自己(组长时,获取他作为学习官时绑定的班级或通用任务)", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SummarizTaskResult" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SummarizTaskResult" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SummarizTaskResult" + } + } + } + } + } + } + } + }, + "/api/v1/TaskSummarize/AddWeekSummarize": { + "put": { + "tags": [ + "TaskSummarize" + ], + "summary": "添加周报", + "requestBody": { + "description": "", + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/SummarizWeekRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/SummarizWeekRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/SummarizWeekRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/SummarizWeekRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/api/v1/TaskSummarize/GetSummarizWeekByDate": { + "get": { + "tags": [ + "TaskSummarize" + ], + "summary": "获取周报信息(时间段)", + "parameters": [ + { + "name": "BeginDate", + "in": "query", + "description": "", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "EndDate", + "in": "query", + "description": "", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "userId", + "in": "query", + "description": "用户id,不传则获取自己(组长时,获取他作为学习官时绑定的班级或通用任务)", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SummarizWeekTaskResult" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SummarizWeekTaskResult" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SummarizWeekTaskResult" + } + } + } + } + } + } + } + }, + "/api/v1/TaskSummarize/GetSummarizWeek": { + "get": { + "tags": [ + "TaskSummarize" + ], + "summary": "获取周报信息", + "parameters": [ + { + "name": "BeginDate", + "in": "query", + "description": "", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "EndDate", + "in": "query", + "description": "", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "userId", + "in": "query", + "description": "用户id,不传则获取自己(组长时,获取他作为学习官时绑定的班级或通用任务)", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/SummarizWeekListResult" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/SummarizWeekListResult" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/SummarizWeekListResult" + } + } + } + } + } + } + }, + "/api/v1/TaskSummarize/EvaluationSumWeek": { + "put": { + "tags": [ + "TaskSummarize" + ], + "summary": "评价周报", + "requestBody": { + "description": "", + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/EvaluationSumRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/EvaluationSumRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/EvaluationSumRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/EvaluationSumRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/api/v1/TaskSummarize/GetSummarizMonthTaskResult": { + "get": { + "tags": [ + "TaskSummarize" + ], + "summary": "管理端获取本人所管理的人员的月报", + "parameters": [ + { + "name": "UserIds", + "in": "query", + "description": "", + "schema": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + } + }, + { + "name": "UserName", + "in": "query", + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "BeginTime", + "in": "query", + "description": "", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "EndTime", + "in": "query", + "description": "", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "PageIndex", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int32", + "default": 1 + } + }, + { + "name": "PageSize", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int32", + "default": 10 + } + }, + { + "name": "isOnlyNoRead", + "in": "query", + "description": "", + "schema": { + "type": "boolean", + "default": false + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/SummarizMonthTaskResultPageResponse" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/SummarizMonthTaskResultPageResponse" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/SummarizMonthTaskResultPageResponse" + } + } + } + } + } + } + }, + "/api/v1/TaskSummarize/GetSummarizMonthTaskByDate": { + "get": { + "tags": [ + "TaskSummarize" + ], + "summary": "获取月报中的任务完成情况列表(月报)", + "parameters": [ + { + "name": "Year", + "in": "query", + "description": "年份", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "Month", + "in": "query", + "description": "月份", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "UserId", + "in": "query", + "description": "用户id,不传则获取自己(组长时,获取他作为学习官时绑定的班级或通用任务)", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/SummarizMonthStandardResult" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/SummarizMonthStandardResult" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/SummarizMonthStandardResult" + } + } + } + } + } + } + }, + "/api/v1/TaskSummarize/AddMonthSummarize": { + "put": { + "tags": [ + "TaskSummarize" + ], + "summary": "添加月报", + "requestBody": { + "description": "", + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/SummarizMonthRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/SummarizMonthRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/SummarizMonthRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/SummarizMonthRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/api/v1/TaskSummarize/GetSummarizMonthByYear": { + "get": { + "tags": [ + "TaskSummarize" + ], + "summary": "获取月报信息-一年的", + "parameters": [ + { + "name": "Year", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "userId", + "in": "query", + "description": "用户id,不传则获取自己(组长时,获取他作为学习官时绑定的班级或通用任务)", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SummarizMonthTaskResult" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SummarizMonthTaskResult" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SummarizMonthTaskResult" + } + } + } + } + } + } + } + }, + "/api/v1/TaskSummarize/GetSummarizMonth": { + "get": { + "tags": [ + "TaskSummarize" + ], + "summary": "获取月报信息", + "parameters": [ + { + "name": "Year", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "Month", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "userId", + "in": "query", + "description": "用户id,不传则获取自己(组长时,获取他作为学习官时绑定的班级或通用任务)", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/SummarizMonthListResult" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/SummarizMonthListResult" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/SummarizMonthListResult" + } + } + } + } + } + } + }, + "/api/v1/TaskSummarize/EvaluationSumMonth": { + "put": { + "tags": [ + "TaskSummarize" + ], + "summary": "评价月报", + "requestBody": { + "description": "", + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/EvaluationSumRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/EvaluationSumRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/EvaluationSumRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/EvaluationSumRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/api/v1/TaskSummarize/AddReadSummariz": { + "put": { + "tags": [ + "TaskSummarize" + ], + "summary": "添加日报 周报 月报的已读信息", + "requestBody": { + "description": "", + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/ReadSummarizRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReadSummarizRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ReadSummarizRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/ReadSummarizRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/api/v1/TaskTalk/AddUpdateTaskTalk": { + "put": { + "tags": [ + "TaskTalk" + ], + "summary": "添加或更新谈话任务", + "requestBody": { + "description": "", + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/TalkInfoRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/TalkInfoRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/TalkInfoRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/TalkInfoRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/BaseTaskAddResult" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseTaskAddResult" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/BaseTaskAddResult" + } + } + } + } + } + } + }, + "/api/v1/TaskTalk/DeleteTaskTalk": { + "delete": { + "tags": [ + "TaskTalk" + ], + "summary": "删除谈话任务文件", + "parameters": [ + { + "name": "Talk_id", + "in": "query", + "description": "谈话记录表id", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "fileId", + "in": "query", + "description": "文件id", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/api/v1/TaskTalk/TaskTalkFinish": { + "put": { + "tags": [ + "TaskTalk" + ], + "summary": "结束谈话任务", + "requestBody": { + "description": "", + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/TaskTalkFinishRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/TaskTalkFinishRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/TaskTalkFinishRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/TaskTalkFinishRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/api/v1/TaskTalk/GetTaskTalkResult": { + "get": { + "tags": [ + "TaskTalk" + ], + "summary": "获取任务谈话结果", + "parameters": [ + { + "name": "TaskId", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/TaskTalkResult" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/TaskTalkResult" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/TaskTalkResult" + } + } + } + } + } + } + }, + "/api/v1/TaskTeacherBehavior/AddUpdateTaskTeacherBehavior": { + "put": { + "tags": [ + "TaskTeacherBehavior" + ], + "summary": "添加或更新教师行为规范任务", + "requestBody": { + "description": "", + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/TeacherBehaviorRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/TeacherBehaviorRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/TeacherBehaviorRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/TeacherBehaviorRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/BaseTaskAddResult" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseTaskAddResult" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/BaseTaskAddResult" + } + } + } + } + } + } + }, + "/api/v1/TaskTeacherBehavior/AddTeacherBehaviorQuestion": { + "put": { + "tags": [ + "TaskTeacherBehavior" + ], + "summary": "添加观察问题记录", + "requestBody": { + "description": "", + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/Teacher_behavior_questionRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Teacher_behavior_questionRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/Teacher_behavior_questionRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/Teacher_behavior_questionRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/api/v1/TaskTeacherBehavior/GetTeacherBehaviorQuestionNumList": { + "get": { + "tags": [ + "TaskTeacherBehavior" + ], + "summary": "获取教师行为规范问题列表(观察记录)", + "parameters": [ + { + "name": "tbId", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "Date", + "in": "query", + "description": "", + "schema": { + "type": "string", + "format": "date-time" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TeacherBehaviorQuestionNumResult" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TeacherBehaviorQuestionNumResult" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TeacherBehaviorQuestionNumResult" + } + } + } + } + } + } + } + }, + "/api/v1/TaskTeacherBehavior/GetTeacherBehaviorQuestionResult": { + "get": { + "tags": [ + "TaskTeacherBehavior" + ], + "summary": "根据问题观察记录id获取问题详细", + "parameters": [ + { + "name": "QuestionId", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/TeacherBehaviorQuestionResult" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/TeacherBehaviorQuestionResult" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/TeacherBehaviorQuestionResult" + } + } + } + } + } + } + }, + "/api/v1/TaskTeacherBehavior/TeacherBehaviorFinish": { + "put": { + "tags": [ + "TaskTeacherBehavior" + ], + "summary": "结束教师行为规范任务", + "requestBody": { + "description": "", + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/TeacherBehaviorFinishRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/TeacherBehaviorFinishRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/TeacherBehaviorFinishRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/TeacherBehaviorFinishRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/api/v1/TaskTeacherBehavior/GetTeacherBehaviorResults": { + "get": { + "tags": [ + "TaskTeacherBehavior" + ], + "summary": "获取教师行为规范结果列表", + "parameters": [ + { + "name": "taskId", + "in": "query", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/TeacherBehaviorResult" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/TeacherBehaviorResult" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/TeacherBehaviorResult" + } + } + } + } + } + } + }, + "/api/v1/TaskTeacherTalk/AddUpdateTeacherTalkInfo": { + "put": { + "tags": [ + "TaskTeacherTalk" + ], + "summary": "创建/修改教师谈话任务", + "requestBody": { + "description": "", + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/TeacherTalkInfoRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/TeacherTalkInfoRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/TeacherTalkInfoRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/TeacherTalkInfoRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/BaseTaskAddResult" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseTaskAddResult" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/BaseTaskAddResult" + } + } + } + } + } + } + }, + "/api/v1/TaskTeacherTalk/DeleteTeacherTalkInfoFile": { + "delete": { + "tags": [ + "TaskTeacherTalk" + ], + "summary": "删除教师谈话文件信息", + "parameters": [ + { + "name": "TalkId", + "in": "query", + "description": "谈话id", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "fileId", + "in": "query", + "description": "文件id", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/api/v1/TaskTeacherTalk/TeacherTalkInfoFinish": { + "put": { + "tags": [ + "TaskTeacherTalk" + ], + "summary": "完成教师谈话任务", + "requestBody": { + "description": "", + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/TeacherTalkInfoFinishRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/TeacherTalkInfoFinishRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/TeacherTalkInfoFinishRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/TeacherTalkInfoFinishRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/api/v1/TaskTeacherTalk/GetTeacherTalkInfoResult": { + "get": { + "tags": [ + "TaskTeacherTalk" + ], + "summary": "根据任务id获取教师谈话信息", + "parameters": [ + { + "name": "TaskId", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/TeacherTalkInfoResult" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/TeacherTalkInfoResult" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/TeacherTalkInfoResult" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "AssistUserResult": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Id", + "format": "int64" + }, + "userName": { + "type": "string", + "description": "用户姓名", + "nullable": true + } + }, + "additionalProperties": false, + "description": "人员基本信息" + }, + "BannerResult": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "id", + "format": "int64" + }, + "bannerFileId": { + "type": "integer", + "description": "备 注:文件id\r\n默认值:", + "format": "int64" + }, + "bannerName": { + "type": "string", + "description": "备 注:banne名称\r\n默认值:", + "nullable": true + }, + "bannerFileUrl": { + "type": "string", + "description": "备 注:文件地址", + "nullable": true + }, + "bannerFileName": { + "type": "string", + "description": "文件名称", + "nullable": true + }, + "bannerFileSize": { + "type": "integer", + "description": "文件大小", + "format": "int64", + "nullable": true + } + }, + "additionalProperties": false + }, + "BaseTaskAddResult": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "新增后返回的任务id", + "format": "int64" + }, + "sunClassTaskId": { + "type": "integer", + "description": "任务子表的id(例:新增学习习惯全面抽查,此id则为抽查工作记录id,也就是SpotId)\r\n也可能是0,为0时,说明没有子表数据,以任务id为主", + "format": "int64" + } + }, + "additionalProperties": false + }, + "BaseTaskClassResult": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "任务id", + "format": "int64" + }, + "status": { + "type": "integer", + "description": "任务状态 0:未开始 1:进行中 2:已结束 3:问题待处理(仅用于双师跟课)", + "format": "int32" + }, + "classesId": { + "type": "integer", + "description": "备 注:班级id(班级任务)\r\n默认值:", + "format": "int64", + "nullable": true + }, + "classesName": { + "type": "string", + "description": "备 注:班级名称(用于展示)", + "nullable": true + }, + "gradeLevel": { + "type": "string", + "description": "备 注:年级\r\n默认值:", + "nullable": true + }, + "graduationYear": { + "type": "integer", + "description": "备 注:所属届\r\n默认值:", + "format": "int32" + }, + "taskTypeEnum": { + "type": "integer", + "description": "备 注:参数表 枚举值,任务类型\r\n默认值:", + "format": "int32" + }, + "taskTypeName": { + "type": "string", + "description": "备 注:任务类型名称(用于展示)", + "nullable": true + }, + "taskTitleSuffix": { + "type": "string", + "description": "备 注:任务标题后缀,用于展示(有值时,将任务类型名称 和该字段用 “-”连接)\r\n默认值:", + "nullable": true + }, + "startDate": { + "type": "string", + "description": "备 注:开始日期\r\n默认值:", + "format": "date-time", + "nullable": true + }, + "startTime": { + "type": "string", + "description": "备 注:开始时间\r\n默认值:", + "format": "date-time" + }, + "endDate": { + "type": "string", + "description": "备 注:结束日期\r\n默认值:", + "format": "date-time", + "nullable": true + }, + "endTime": { + "type": "string", + "description": "备 注:结束时间\r\n默认值:", + "format": "date-time" + }, + "isLongwork": { + "type": "integer", + "description": "备 注:是否长期性工作(0否,1是)\r\n默认值:", + "format": "int64" + }, + "finishDate": { + "type": "string", + "description": "备 注:任务完成日期\r\n默认值:", + "format": "date-time", + "nullable": true + }, + "finishDatetime": { + "type": "string", + "description": "备 注:任务完成时间\r\n默认值:", + "format": "date-time", + "nullable": true + }, + "taskUserId": { + "type": "integer", + "description": "备 注:执行人id(通用任务才有)\r\n默认值:", + "format": "int64", + "nullable": true + }, + "taskIndexType": { + "type": "integer", + "description": "备 注:任务指标类型-1班级;2通用; 班级时,classes_id不为空;通用时,执行人id不为空\r\n默认值:", + "format": "int32" + } + }, + "additionalProperties": false + }, + "BaseTaskFileRequest": { + "type": "object", + "properties": { + "classesTaskId": { + "type": "integer", + "description": "关联表id(非任务主表id)", + "format": "int64" + }, + "fileId": { + "type": "integer", + "description": "文件id", + "format": "int64" + } + }, + "additionalProperties": false + }, + "ClassCadreMeetingFinishRequest": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "任务id", + "format": "int64" + }, + "classCadreId": { + "type": "integer", + "description": "班干部会议ID", + "format": "int64" + }, + "remark": { + "type": "string", + "description": "会议纪要", + "nullable": true + }, + "files": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseTaskFileRequest" + }, + "description": "文件", + "nullable": true + } + }, + "additionalProperties": false + }, + "ClassCadreMeetingRequest": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "任务id(新增时为null,修改时必传)", + "format": "int64", + "nullable": true + }, + "classesId": { + "type": "integer", + "description": "班级id(班级任务时,classes_id不为空;通用任务时,班级id为空 null)", + "format": "int64", + "nullable": true + }, + "taskTypeEnum": { + "type": "integer", + "description": "任务类型id", + "format": "int32" + }, + "startTime": { + "type": "string", + "description": "备 注:开始时间\r\n默认值:", + "format": "date-time" + }, + "endTime": { + "type": "string", + "description": "备 注:结束时间\r\n默认值:", + "format": "date-time" + }, + "taskTitleSuffix": { + "type": "string", + "description": "任务后缀", + "nullable": true + }, + "classCadreId": { + "type": "integer", + "description": "id(新增时为null,修改时必传)", + "format": "int64", + "nullable": true + }, + "meetingTitle": { + "type": "string", + "description": "会议主题", + "nullable": true + }, + "userIds": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + }, + "description": "参会人员id", + "nullable": true + } + }, + "additionalProperties": false, + "description": "开展班干部会议请求参数" + }, + "ClassCadreMeetingResult": { + "type": "object", + "properties": { + "classCadreId": { + "type": "integer", + "description": "班干部会议记录ID", + "format": "int64" + }, + "meetingTitle": { + "type": "string", + "description": "会议标题", + "nullable": true + }, + "remark": { + "type": "string", + "description": "会议纪要", + "nullable": true + }, + "taskInfo": { + "$ref": "#/components/schemas/BaseTaskClassResult" + }, + "sunTaskUserResults": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SunTaskUserResult" + }, + "description": "关联用户", + "nullable": true + }, + "sunTaskFileResults": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SunTaskFileResult" + }, + "description": "关联文件", + "nullable": true + } + }, + "additionalProperties": false + }, + "ClassManageTaskListResult": { + "type": "object", + "properties": { + "taskChecklistUser": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ClassesTaskChecklistUserResult" + }, + "description": "通用工作指标清单列表", + "nullable": true + } + }, + "additionalProperties": false + }, + "ClassManager_Task_checklistRequest": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "新增为null", + "format": "int64", + "nullable": true + }, + "taskTypeEnum": { + "type": "integer", + "description": "备 注:任务类型id\r\n默认值:", + "format": "int64" + }, + "targetNumber": { + "type": "integer", + "description": "备 注:任务指标数\r\n默认值:", + "format": "int32" + }, + "taskType": { + "type": "integer", + "description": "备 注:班级/通用任务类型 1班级;2通用\r\n默认值:", + "format": "int32" + } + }, + "additionalProperties": false + }, + "ClassMeetingFinishRequest": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "任务id", + "format": "int64" + }, + "classMeetingId": { + "type": "integer", + "description": "班级会议ID", + "format": "int64" + }, + "remark": { + "type": "string", + "description": "会议纪要", + "nullable": true + }, + "files": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseTaskFileRequest" + }, + "description": "文件", + "nullable": true + } + }, + "additionalProperties": false + }, + "ClassMeetingRequest": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "任务id(新增时为null,修改时必传)", + "format": "int64", + "nullable": true + }, + "classesId": { + "type": "integer", + "description": "班级id(班级任务时,classes_id不为空;通用任务时,班级id为空 null)", + "format": "int64", + "nullable": true + }, + "taskTypeEnum": { + "type": "integer", + "description": "任务类型id", + "format": "int32" + }, + "startTime": { + "type": "string", + "description": "备 注:开始时间\r\n默认值:", + "format": "date-time" + }, + "endTime": { + "type": "string", + "description": "备 注:结束时间\r\n默认值:", + "format": "date-time" + }, + "taskTitleSuffix": { + "type": "string", + "description": "任务后缀", + "nullable": true + }, + "classMeetingId": { + "type": "integer", + "description": "id(新增时为null,修改时必传)", + "format": "int64", + "nullable": true + }, + "meetingTitle": { + "type": "string", + "description": "会议主题", + "nullable": true + } + }, + "additionalProperties": false + }, + "ClassMeetingResult": { + "type": "object", + "properties": { + "classMeetingId": { + "type": "integer", + "description": "班级会议记录ID", + "format": "int64" + }, + "meetingTitle": { + "type": "string", + "description": "会议标题", + "nullable": true + }, + "meetingContent": { + "type": "string", + "description": "会议纪要", + "nullable": true + }, + "taskInfo": { + "$ref": "#/components/schemas/BaseTaskClassResult" + }, + "sunTaskFileResults": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SunTaskFileResult" + }, + "description": "关联文件", + "nullable": true + } + }, + "additionalProperties": false + }, + "ClassSubResult": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "nullable": true + }, + "className": { + "type": "string", + "description": "班级名称", + "nullable": true + }, + "subName": { + "type": "string", + "description": "科目名称", + "nullable": true + }, + "classId": { + "type": "integer", + "description": "班级Id", + "format": "int64" + }, + "subId": { + "type": "integer", + "description": "科目Id", + "format": "int64" + } + }, + "additionalProperties": false, + "description": "工作台-班级科目绑定表" + }, + "ClassTeacherRequest": { + "type": "object", + "properties": { + "classId": { + "type": "integer", + "description": "班级Id", + "format": "int64" + }, + "teacherName": { + "type": "string", + "description": "教师名称", + "nullable": true + }, + "subId": { + "type": "integer", + "description": "科目Id,0时为班主任(此id从班级里面的科目读取)", + "format": "int64" + } + }, + "additionalProperties": false + }, + "ClassTeacherResult": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "classId": { + "type": "integer", + "description": "班级Id", + "format": "int64" + }, + "className": { + "type": "string", + "description": "班级名称", + "nullable": true + }, + "teacherName": { + "type": "string", + "description": "教师名称", + "nullable": true + }, + "subId": { + "type": "integer", + "description": "科目Id,0时为班主任", + "format": "int64" + }, + "subName": { + "type": "string", + "description": "科目名称", + "nullable": true + } + }, + "additionalProperties": false, + "description": "工作台-班级教师" + }, + "ClassTypeEnum": { + "enum": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 10, + 20 + ], + "type": "integer", + "format": "int32" + }, + "Classes": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "className": { + "type": "string", + "description": "备 注:名称\r\n默认值:", + "nullable": true + }, + "schoolId": { + "type": "integer", + "description": "备 注:学校编号\r\n默认值:", + "format": "int64" + }, + "gradeLevel": { + "type": "string", + "description": "备 注:年级\r\n默认值:", + "nullable": true + }, + "graduationYear": { + "type": "integer", + "description": "备 注:所属届\r\n默认值:", + "format": "int32" + }, + "createTime": { + "type": "string", + "description": "备 注:添加时间\r\n默认值:", + "format": "date-time" + }, + "createPositionId": { + "type": "integer", + "description": "备 注:创建者部门Id\r\n默认值:", + "format": "int64", + "nullable": true + }, + "deleteState": { + "type": "boolean", + "description": "备 注:删除状态\r\n默认值:" + }, + "type": { + "$ref": "#/components/schemas/ClassTypeEnum" + }, + "teachingLevel": { + "$ref": "#/components/schemas/TeachingLevelEnum" + } + }, + "additionalProperties": false, + "description": "班级表" + }, + "ClassesActivityFinishRequest": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "任务id", + "format": "int64" + }, + "activityId": { + "type": "integer", + "description": "班级活动ID", + "format": "int64" + }, + "remark": { + "type": "string", + "description": "活动记录", + "nullable": true + }, + "files": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseTaskFileRequest" + }, + "description": "文件", + "nullable": true + } + }, + "additionalProperties": false + }, + "ClassesActivityRequest": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "任务id(新增时为null,修改时必传)", + "format": "int64", + "nullable": true + }, + "classesId": { + "type": "integer", + "description": "班级id(班级任务时,classes_id不为空;通用任务时,班级id为空 null)", + "format": "int64", + "nullable": true + }, + "taskTypeEnum": { + "type": "integer", + "description": "任务类型id", + "format": "int32" + }, + "startTime": { + "type": "string", + "description": "备 注:开始时间\r\n默认值:", + "format": "date-time" + }, + "endTime": { + "type": "string", + "description": "备 注:结束时间\r\n默认值:", + "format": "date-time" + }, + "taskTitleSuffix": { + "type": "string", + "description": "任务后缀", + "nullable": true + }, + "activityId": { + "type": "integer", + "description": "活动id(新增时为null,修改时必传)", + "format": "int64", + "nullable": true + }, + "activityTitle": { + "type": "string", + "description": "备 注:活动主题\r\n默认值:", + "nullable": true + } + }, + "additionalProperties": false + }, + "ClassesActivityResult": { + "type": "object", + "properties": { + "activityId": { + "type": "integer", + "description": "活动id", + "format": "int64" + }, + "activityTitle": { + "type": "string", + "description": "备 注:活动主题\r\n默认值:", + "nullable": true + }, + "remark": { + "type": "string", + "description": "备 注:活动记录\r\n默认值:", + "nullable": true + }, + "taskInfo": { + "$ref": "#/components/schemas/BaseTaskClassResult" + }, + "sunTaskFileResults": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SunTaskFileResult" + }, + "description": "关联文件", + "nullable": true + } + }, + "additionalProperties": false + }, + "ClassesAndFollowResult": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string", + "description": "班级id", + "nullable": true + }, + "gradeLevel": { + "type": "string", + "description": "备 注:年级\r\n默认值:", + "nullable": true + }, + "graduationYear": { + "type": "integer", + "description": "备 注:所属届\r\n默认值:", + "format": "int32" + }, + "followName": { + "type": "string", + "description": "学习官名称", + "nullable": true + }, + "followId": { + "type": "integer", + "description": "学习官id", + "format": "int64" + } + }, + "additionalProperties": false + }, + "ClassesResult": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string", + "description": "班级名称", + "nullable": true + }, + "gradeLevel": { + "type": "string", + "description": "备 注:年级\r\n默认值:", + "nullable": true + }, + "graduationYear": { + "type": "integer", + "description": "备 注:所属届\r\n默认值:", + "format": "int32" + } + }, + "additionalProperties": false + }, + "ClassesTaskChecklistUserResult": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "taskTypeEnum": { + "type": "integer", + "description": "备 注:任务类型枚举值\r\n默认值:", + "format": "int64" + }, + "taskTypeEnumName": { + "type": "string", + "description": "备 注:任务类型名称", + "nullable": true + }, + "taskUserId": { + "type": "integer", + "description": "备 注:任务接受人id\r\n默认值:", + "format": "int64" + }, + "targetNumber": { + "type": "integer", + "description": "备 注:任务达标数\r\n默认值:", + "format": "int32" + }, + "taskType": { + "type": "integer", + "description": "备 注:班级/通用任务类型 1班级;2通用\r\n默认值:", + "format": "int32" + } + }, + "additionalProperties": false, + "description": "通用工作任务指标清单" + }, + "ClassesTaskListResult": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "integer", + "description": "备 注:任务状态 0:未开始 1:进行中 2:已结束 3:问题待处理(仅用于双师跟课)\r\n默认值:", + "format": "int32" + }, + "classesId": { + "type": "integer", + "description": "备 注:班级id(班级任务)\r\n默认值:", + "format": "int64", + "nullable": true + }, + "taskTypeEnum": { + "type": "integer", + "description": "备 注:参数表 枚举值,任务类型\r\n默认值:", + "format": "int32" + }, + "taskTitleSuffix": { + "type": "string", + "description": "备 注:任务标题后缀,用于展示\r\n默认值:", + "nullable": true + }, + "startDate": { + "type": "string", + "description": "备 注:开始日期\r\n默认值:", + "format": "date-time", + "nullable": true + }, + "startTime": { + "type": "string", + "description": "备 注:开始时间\r\n默认值:", + "format": "date-time" + }, + "endDate": { + "type": "string", + "description": "备 注:结束日期\r\n默认值:", + "format": "date-time", + "nullable": true + }, + "endTime": { + "type": "string", + "description": "备 注:结束时间\r\n默认值:", + "format": "date-time" + }, + "taskIndexType": { + "type": "integer", + "description": "备 注:任务指标类型-1班级;2通用; 班级时,classes_id不为空;通用时,执行人id不为空\r\n默认值:", + "format": "int32" + } + }, + "additionalProperties": false, + "description": "任务列表" + }, + "ClassesTaskListResultPageResponse": { + "type": "object", + "properties": { + "total": { + "type": "integer", + "description": "总记录条数", + "format": "int32" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ClassesTaskListResult" + }, + "description": "响应数据", + "nullable": true + } + }, + "additionalProperties": false, + "description": "分页响应实体类" + }, + "CoachSubInfoFinishRequest": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "新增为null,编辑时传Id", + "format": "int64" + }, + "coachId": { + "type": "integer", + "description": "备 注:学科辅助任务Id\r\n默认值:", + "format": "int64" + }, + "coachContent": { + "type": "string", + "description": "备 注:辅导内容\r\n默认值:", + "nullable": true + }, + "files": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseTaskFileRequest" + }, + "nullable": true + } + }, + "additionalProperties": false + }, + "CoachSubInfoRequest": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "任务id(新增时为null,修改时必传)", + "format": "int64", + "nullable": true + }, + "classesId": { + "type": "integer", + "description": "班级id(班级任务时,classes_id不为空;通用任务时,班级id为空 null)", + "format": "int64", + "nullable": true + }, + "taskTypeEnum": { + "type": "integer", + "description": "任务类型id", + "format": "int32" + }, + "startTime": { + "type": "string", + "description": "备 注:开始时间\r\n默认值:", + "format": "date-time" + }, + "endTime": { + "type": "string", + "description": "备 注:结束时间\r\n默认值:", + "format": "date-time" + }, + "taskTitleSuffix": { + "type": "string", + "description": "任务后缀", + "nullable": true + }, + "coachId": { + "type": "integer", + "description": "辅导表id(新增时为null,修改时必传)", + "format": "int64", + "nullable": true + }, + "coachSubId": { + "type": "integer", + "description": "科目id", + "format": "int64" + }, + "coachSubName": { + "type": "string", + "description": "科目名称", + "nullable": true + }, + "userId": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + }, + "description": "参会人员", + "nullable": true + } + }, + "additionalProperties": false + }, + "CoachSubInfoResult": { + "type": "object", + "properties": { + "coachId": { + "type": "integer", + "description": "学科辅助ID", + "format": "int64" + }, + "coachSubId": { + "type": "integer", + "description": "学科辅助科目id", + "format": "int64" + }, + "coachSubName": { + "type": "string", + "description": "学科辅助科目名称", + "nullable": true + }, + "coachContent": { + "type": "string", + "description": "学科辅助内容", + "nullable": true + }, + "taskInfo": { + "$ref": "#/components/schemas/BaseTaskClassResult" + }, + "sunTaskUserResults": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SunTaskUserResult" + }, + "description": "关联用户", + "nullable": true + }, + "sunTaskFileResults": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SunTaskFileResult" + }, + "description": "关联文件", + "nullable": true + } + }, + "additionalProperties": false + }, + "CourseWeekDetailOutput": { + "type": "object", + "properties": { + "classCourseId": { + "type": "integer", + "description": "课程id", + "format": "int64" + }, + "subject": { + "type": "integer", + "description": "科目id", + "format": "int32" + }, + "subjectName": { + "type": "string", + "description": "科目名称", + "nullable": true + }, + "teacherName": { + "type": "string", + "description": "云校老师", + "nullable": true + }, + "teacherId": { + "type": "integer", + "description": "云校老师", + "format": "int64" + }, + "landingTeacherId": { + "type": "integer", + "description": "落地老师", + "format": "int64" + }, + "landingTeacherName": { + "type": "string", + "description": "落地老师", + "nullable": true + }, + "planStartTime": { + "type": "string", + "description": "上课时间", + "format": "date-time" + }, + "planEndTime": { + "type": "string", + "description": "下课时间", + "format": "date-time" + } + }, + "additionalProperties": false + }, + "CulturalDetailResult": { + "type": "object", + "properties": { + "taskInfo": { + "$ref": "#/components/schemas/BaseTaskClassResult" + }, + "culturalId": { + "type": "integer", + "description": "文创id", + "format": "int64" + }, + "culturalTitle": { + "type": "string", + "description": "文创标题", + "nullable": true + }, + "remark": { + "type": "string", + "description": "备 注:更新说明\r\n默认值:", + "nullable": true + }, + "sunTaskFileResults": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SunTaskFileResult" + }, + "description": "文创文件信息", + "nullable": true + } + }, + "additionalProperties": false + }, + "CulturalFinishRequest": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "任务id", + "format": "int64" + }, + "culturalId": { + "type": "integer", + "description": "文创ID", + "format": "int64" + }, + "remark": { + "type": "string", + "description": "工作内容", + "nullable": true + }, + "files": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseTaskFileRequest" + }, + "description": "文创文件", + "nullable": true + } + }, + "additionalProperties": false + }, + "CulturalRequest": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "任务id(新增时为null,修改时必传)", + "format": "int64", + "nullable": true + }, + "classesId": { + "type": "integer", + "description": "班级id(班级任务时,classes_id不为空;通用任务时,班级id为空 null)", + "format": "int64", + "nullable": true + }, + "taskTypeEnum": { + "type": "integer", + "description": "任务类型id", + "format": "int32" + }, + "startTime": { + "type": "string", + "description": "备 注:开始时间\r\n默认值:", + "format": "date-time" + }, + "endTime": { + "type": "string", + "description": "备 注:结束时间\r\n默认值:", + "format": "date-time" + }, + "taskTitleSuffix": { + "type": "string", + "description": "任务后缀", + "nullable": true + }, + "culturalId": { + "type": "integer", + "description": "文创id 新增不传,修改必传", + "format": "int64", + "nullable": true + }, + "culturalTitle": { + "type": "string", + "description": "文创标题", + "nullable": true + } + }, + "additionalProperties": false + }, + "DataCollectFileRequest": { + "type": "object", + "properties": { + "classesTaskId": { + "type": "integer", + "description": "关联表id(非任务主表id)", + "format": "int64" + }, + "fileId": { + "type": "integer", + "description": "文件id", + "format": "int64" + }, + "userId": { + "type": "integer", + "description": "用户ID", + "format": "int64" + } + }, + "additionalProperties": false + }, + "DataCollectFinishRequest": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "任务id", + "format": "int64" + }, + "collectId": { + "type": "integer", + "description": "采集ID", + "format": "int64" + }, + "remark": { + "type": "string", + "description": "分析总结", + "nullable": true + } + }, + "additionalProperties": false + }, + "DataCollectRequest": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "任务id(新增时为null,修改时必传)", + "format": "int64", + "nullable": true + }, + "classesId": { + "type": "integer", + "description": "班级id(班级任务时,classes_id不为空;通用任务时,班级id为空 null)", + "format": "int64", + "nullable": true + }, + "taskTypeEnum": { + "type": "integer", + "description": "任务类型id", + "format": "int32" + }, + "startTime": { + "type": "string", + "description": "备 注:开始时间\r\n默认值:", + "format": "date-time" + }, + "endTime": { + "type": "string", + "description": "备 注:结束时间\r\n默认值:", + "format": "date-time" + }, + "taskTitleSuffix": { + "type": "string", + "description": "任务后缀", + "nullable": true + }, + "dataCollectId": { + "type": "integer", + "description": "数据采集任务ID(新增时为null,修改时必传)", + "format": "int64", + "nullable": true + }, + "collectTitle": { + "type": "string", + "description": "数据采集标题", + "nullable": true + } + }, + "additionalProperties": false + }, + "DataCollectResult": { + "type": "object", + "properties": { + "taskInfo": { + "$ref": "#/components/schemas/BaseTaskClassResult" + }, + "collectId": { + "type": "integer", + "description": "采集ID", + "format": "int64" + }, + "collectTitle": { + "type": "string", + "description": "采集主题", + "nullable": true + }, + "remark": { + "type": "string", + "description": "分析总结", + "nullable": true + }, + "collectUsers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DataCollectUserResult" + }, + "description": "已采集用户列表", + "nullable": true + } + }, + "additionalProperties": false + }, + "DataCollectUserResult": { + "type": "object", + "properties": { + "userId": { + "type": "integer", + "description": "用户ID", + "format": "int64" + }, + "userName": { + "type": "string", + "description": "用户名", + "nullable": true + }, + "fileCount": { + "type": "integer", + "description": "采集文件数量", + "format": "int32" + } + }, + "additionalProperties": false + }, + "EvaluationSumRequest": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "总结报告id", + "format": "int64" + }, + "content": { + "type": "string", + "description": "评论内容", + "nullable": true + } + }, + "additionalProperties": false + }, + "FinancialClassRequest": { + "type": "object", + "properties": { + "classId": { + "type": "integer", + "description": "班级id", + "format": "int64" + }, + "remark": { + "type": "string", + "description": "用途", + "nullable": true + }, + "money": { + "type": "number", + "description": "元", + "format": "double" + }, + "financialTime": { + "type": "string", + "description": "使用时间", + "format": "date-time" + } + }, + "additionalProperties": false, + "description": "添加班级使用经费记录" + }, + "FinancialClassResult": { + "type": "object", + "properties": { + "financialTime": { + "type": "string", + "description": "备 注:使用时间\r\n默认值:", + "format": "date-time" + }, + "remark": { + "type": "string", + "description": "用途", + "nullable": true + }, + "money": { + "type": "number", + "description": "消费费用(元)", + "format": "double" + } + }, + "additionalProperties": false + }, + "FinancialClassSumResult": { + "type": "object", + "properties": { + "sumMoney": { + "type": "number", + "description": "累计金额", + "format": "double" + }, + "financialClassResults": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FinancialClassResult" + }, + "description": "使用经费", + "nullable": true + } + }, + "additionalProperties": false + }, + "Financial_indicators": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "startTime": { + "type": "string", + "description": "备 注:开始时间\r\n默认值:", + "format": "date-time" + }, + "endTime": { + "type": "string", + "description": "备 注:结束时间\r\n默认值:", + "format": "date-time" + }, + "money": { + "type": "number", + "description": "备 注:本时段经费金额\r\n默认值:", + "format": "double" + } + }, + "additionalProperties": false, + "description": "经费管理" + }, + "FollowAbsenceUserRequest": { + "type": "object", + "properties": { + "followId": { + "type": "integer", + "description": "双师课堂记录表id", + "format": "int64" + }, + "userId": { + "type": "integer", + "description": "用户id", + "format": "int64" + }, + "handleState": { + "type": "integer", + "description": "处理状态 默认0 0未处理 1忽略 2发起谈话", + "format": "int64" + } + }, + "additionalProperties": false + }, + "FollowAbsenceUserResult": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "followId": { + "type": "integer", + "description": "跟课id", + "format": "int64" + }, + "userId": { + "type": "integer", + "description": "用户id", + "format": "int64" + }, + "userName": { + "type": "string", + "description": "用户姓名", + "nullable": true + } + }, + "additionalProperties": false + }, + "FollowInfoFinishRequest": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "任务id", + "format": "int64" + }, + "followId": { + "type": "integer", + "description": "备 注:跟课id", + "format": "int64" + } + }, + "additionalProperties": false + }, + "FollowInfoRequest": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "任务id(新增时为null,修改时必传)", + "format": "int64", + "nullable": true + }, + "classesId": { + "type": "integer", + "description": "班级id(班级任务时,classes_id不为空;通用任务时,班级id为空 null)", + "format": "int64", + "nullable": true + }, + "taskTypeEnum": { + "type": "integer", + "description": "任务类型id", + "format": "int32" + }, + "startTime": { + "type": "string", + "description": "备 注:开始时间\r\n默认值:", + "format": "date-time" + }, + "endTime": { + "type": "string", + "description": "备 注:结束时间\r\n默认值:", + "format": "date-time" + }, + "taskTitleSuffix": { + "type": "string", + "description": "任务后缀", + "nullable": true + }, + "classCourseId": { + "type": "integer", + "description": "备 注:课程id\r\n默认值:", + "format": "int64" + } + }, + "additionalProperties": false + }, + "FollowInfoResult": { + "type": "object", + "properties": { + "taskInfo": { + "$ref": "#/components/schemas/BaseTaskClassResult" + }, + "followTeachersituations": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FollowTeachersituationResult" + }, + "description": "落地老师问题", + "nullable": true + }, + "followAbsenceUserResults": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FollowAbsenceUserResult" + }, + "description": "跟课缺席用户列表", + "nullable": true + }, + "followKeynoteUserResults": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FollowKeynoteUserResult" + }, + "description": "重点学生列表", + "nullable": true + }, + "followId": { + "type": "integer", + "description": "跟课ID", + "format": "int64" + }, + "classCourseId": { + "type": "integer", + "description": "备 注:课程id\r\n默认值:", + "format": "int64" + }, + "classesFollowMethod": { + "type": "integer", + "description": "备 注:跟课方式1:直播;2:录播;\r\n默认值:", + "format": "int32", + "nullable": true + }, + "classesSignalStability": { + "type": "integer", + "description": "备 注:信号是否稳定;0否;1:是;\r\n默认值:", + "format": "int32", + "nullable": true + }, + "concentrationActivityEnum": { + "type": "integer", + "description": "备 注:整体活跃度,参数id\r\n默认值:", + "format": "int32", + "nullable": true + }, + "concentrationLevelEnum": { + "type": "integer", + "description": "备 注:整体专注度,参数id\r\n默认值:", + "format": "int32", + "nullable": true + }, + "masteryLevelEnum": { + "type": "integer", + "description": "备 注:整体问题回答正确率/知识掌握程度,参数id\r\n默认值:", + "format": "int32", + "nullable": true + }, + "remark": { + "type": "string", + "description": "其他", + "nullable": true + } + }, + "additionalProperties": false + }, + "FollowKeyNoteUserRequest": { + "type": "object", + "properties": { + "followId": { + "type": "integer", + "description": "备 注:跟课id\r\n默认值:", + "format": "int64" + }, + "userId": { + "type": "integer", + "description": "备 注:重点学生用户id\r\n默认值:", + "format": "int64" + }, + "remark": { + "type": "string", + "description": "备 注:描述\r\n默认值:", + "nullable": true + } + }, + "additionalProperties": false + }, + "FollowKeynoteUserResult": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "followId": { + "type": "integer", + "description": "跟课id", + "format": "int64" + }, + "userId": { + "type": "integer", + "description": "重点学生id", + "format": "int64" + }, + "userName": { + "type": "string", + "description": "重点学生姓名", + "nullable": true + }, + "remark": { + "type": "string", + "description": "描述", + "nullable": true + } + }, + "additionalProperties": false + }, + "FollowQuestionRequest": { + "type": "object", + "properties": { + "followId": { + "type": "integer", + "description": "备 注:跟课id", + "format": "int64" + }, + "followTeachersituations": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FollowTeachersituationRequest" + }, + "description": "落地老师的问题列表", + "nullable": true + }, + "classesFollowMethod": { + "type": "integer", + "description": "备 注:跟课方式1:直播;2:录播;\r\n默认值:", + "format": "int32", + "nullable": true + }, + "classesSignalStability": { + "type": "integer", + "description": "备 注:信号是否稳定;0否;1:是;\r\n默认值:", + "format": "int32", + "nullable": true + }, + "concentrationActivityEnum": { + "type": "integer", + "description": "备 注:整体活跃度,参数id\r\n默认值:", + "format": "int32", + "nullable": true + }, + "concentrationLevelEnum": { + "type": "integer", + "description": "备 注:整体专注度,参数id\r\n默认值:", + "format": "int32", + "nullable": true + }, + "masteryLevelEnum": { + "type": "integer", + "description": "备 注:整体问题回答正确率/知识掌握程度,参数id\r\n默认值:", + "format": "int32", + "nullable": true + }, + "remark": { + "type": "string", + "description": "其他", + "nullable": true + } + }, + "additionalProperties": false + }, + "FollowTeachersituationRequest": { + "type": "object", + "properties": { + "followId": { + "type": "integer", + "description": "备 注:跟课id\r\n默认值:", + "format": "int64" + }, + "paramId": { + "type": "integer", + "description": "备 注:落地老师-问题类型,字典表\r\n默认值:", + "format": "int64" + } + }, + "additionalProperties": false + }, + "FollowTeachersituationResult": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "followId": { + "type": "integer", + "description": "跟课id", + "format": "int64" + }, + "paramId": { + "type": "integer", + "description": "落地老师-问题类型,字典表", + "format": "int64" + }, + "paramName": { + "type": "string", + "description": "问题描述", + "nullable": true + } + }, + "additionalProperties": false + }, + "Index_ClassesTaskCheckList_User": { + "type": "object", + "properties": { + "taskTypeEnum": { + "type": "integer", + "description": "任务类型枚举", + "format": "int64" + }, + "taskTypeName": { + "type": "string", + "description": "任务类型枚举-名称", + "nullable": true + }, + "createTaskCount": { + "type": "integer", + "description": "已创建任务数量", + "format": "int32" + }, + "okTaskCount": { + "type": "integer", + "description": "已完成任务数量", + "format": "int32" + }, + "shouldTaskCount": { + "type": "integer", + "description": "应完成任务数量", + "format": "int32" + } + }, + "additionalProperties": false, + "description": "通用首页任务清单用户信息" + }, + "Index_ClassesTaskCheckList_UserPageResponse": { + "type": "object", + "properties": { + "total": { + "type": "integer", + "description": "总记录条数", + "format": "int32" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Index_ClassesTaskCheckList_User" + }, + "description": "响应数据", + "nullable": true + } + }, + "additionalProperties": false, + "description": "分页响应实体类" + }, + "LoginCodeRequest": { + "type": "object", + "properties": { + "phone": { + "type": "string", + "description": "电话", + "nullable": true + }, + "code": { + "type": "string", + "description": "验证码", + "nullable": true + }, + "loginType": { + "type": "integer", + "description": "登录类型 1:Android;2Ios", + "format": "int32" + } + }, + "additionalProperties": false + }, + "LoginRequest": { + "type": "object", + "properties": { + "username": { + "type": "string", + "description": "用户名", + "nullable": true + }, + "pwd": { + "type": "string", + "description": "密码", + "nullable": true + }, + "loginType": { + "type": "integer", + "description": "登录类型 1:Android;2Ios", + "format": "int32" + } + }, + "additionalProperties": false + }, + "MeetingInfoFinishRequest": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "任务id", + "format": "int64" + }, + "meetingId": { + "type": "integer", + "description": "会议ID", + "format": "int64" + }, + "remark": { + "type": "string", + "description": "会议纪要", + "nullable": true + }, + "files": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseTaskFileRequest" + }, + "nullable": true + } + }, + "additionalProperties": false + }, + "MeetingInfoRequest": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "任务id(新增时为null,修改时必传)", + "format": "int64", + "nullable": true + }, + "classesId": { + "type": "integer", + "description": "班级id(班级任务时,classes_id不为空;通用任务时,班级id为空 null)", + "format": "int64", + "nullable": true + }, + "taskTypeEnum": { + "type": "integer", + "description": "任务类型id", + "format": "int32" + }, + "startTime": { + "type": "string", + "description": "备 注:开始时间\r\n默认值:", + "format": "date-time" + }, + "endTime": { + "type": "string", + "description": "备 注:结束时间\r\n默认值:", + "format": "date-time" + }, + "taskTitleSuffix": { + "type": "string", + "description": "任务后缀", + "nullable": true + }, + "meetingId": { + "type": "integer", + "description": "会议任务id(新增时为null,修改时必传)", + "format": "int64", + "nullable": true + }, + "meetingTitle": { + "type": "string", + "description": "会议主题", + "nullable": true + }, + "meetingUsername": { + "type": "string", + "description": "参会人员", + "nullable": true + } + }, + "additionalProperties": false + }, + "MeetingInfoResult": { + "type": "object", + "properties": { + "meetingId": { + "type": "integer", + "description": "会议ID", + "format": "int64" + }, + "meetingTitle": { + "type": "string", + "description": "会议主题", + "nullable": true + }, + "remark": { + "type": "string", + "description": "会议纪要", + "nullable": true + }, + "taskInfo": { + "$ref": "#/components/schemas/BaseTaskClassResult" + }, + "meetingUsername": { + "type": "string", + "description": "参会人员", + "nullable": true + }, + "sunTaskFileResults": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SunTaskFileResult" + }, + "description": "关联文件", + "nullable": true + } + }, + "additionalProperties": false + }, + "MyInfoResetPwdRequest": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "用户id", + "format": "int64" + }, + "password": { + "type": "string", + "description": "原密码", + "nullable": true + }, + "newPassword": { + "type": "string", + "description": "新密码", + "nullable": true + } + }, + "additionalProperties": false, + "description": "修改密码" + }, + "MyPhoneBindRequest": { + "type": "object", + "properties": { + "phone": { + "type": "string", + "description": "电话", + "nullable": true + }, + "phoneCode": { + "type": "string", + "description": "验证码", + "nullable": true + } + }, + "additionalProperties": false, + "description": "换绑电话" + }, + "OSSConfigResult": { + "type": "object", + "properties": { + "accessKeyId": { + "type": "string", + "nullable": true + }, + "accessKeySecret": { + "type": "string", + "nullable": true + }, + "endpoint": { + "type": "string", + "nullable": true + }, + "bucketName": { + "type": "string", + "nullable": true + }, + "size": { + "type": "integer", + "format": "int64" + } + }, + "additionalProperties": false + }, + "OssSignResult": { + "type": "object", + "properties": { + "filePath": { + "type": "string", + "description": "上传路径", + "nullable": true + }, + "fileSize": { + "type": "integer", + "description": "文件限制大小", + "format": "int64" + }, + "uploadUrl": { + "type": "string", + "description": "上传URL", + "nullable": true + } + }, + "additionalProperties": false + }, + "OtherInfoFinishRequest": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "任务id", + "format": "int64" + }, + "otherId": { + "type": "integer", + "description": "其他ID", + "format": "int64" + }, + "remark": { + "type": "string", + "description": "工作内容", + "nullable": true + }, + "files": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseTaskFileRequest" + }, + "nullable": true + } + }, + "additionalProperties": false + }, + "OtherInfoRequest": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "任务id(新增时为null,修改时必传)", + "format": "int64", + "nullable": true + }, + "classesId": { + "type": "integer", + "description": "班级id(班级任务时,classes_id不为空;通用任务时,班级id为空 null)", + "format": "int64", + "nullable": true + }, + "taskTypeEnum": { + "type": "integer", + "description": "任务类型id", + "format": "int32" + }, + "startTime": { + "type": "string", + "description": "备 注:开始时间\r\n默认值:", + "format": "date-time" + }, + "endTime": { + "type": "string", + "description": "备 注:结束时间\r\n默认值:", + "format": "date-time" + }, + "taskTitleSuffix": { + "type": "string", + "description": "任务后缀", + "nullable": true + }, + "otherId": { + "type": "integer", + "description": "id(新增时为null,修改时必传)", + "format": "int64", + "nullable": true + }, + "otherTitle": { + "type": "string", + "description": "任务描述", + "nullable": true + } + }, + "additionalProperties": false + }, + "OtherInfoResult": { + "type": "object", + "properties": { + "otherId": { + "type": "integer", + "description": "班干部会议记录ID", + "format": "int64" + }, + "otherTitle": { + "type": "string", + "description": "工作描述", + "nullable": true + }, + "remark": { + "type": "string", + "description": "工作内容", + "nullable": true + }, + "taskInfo": { + "$ref": "#/components/schemas/BaseTaskClassResult" + }, + "sunTaskFileResults": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SunTaskFileResult" + }, + "description": "关联文件", + "nullable": true + } + }, + "additionalProperties": false + }, + "ProblemSemesterViewDto": { + "type": "object", + "properties": { + "solutionSemesterEnum": { + "$ref": "#/components/schemas/SolutionSemesterEnum" + }, + "solutionSemesterName": { + "type": "string", + "description": "解决方案枚举名称", + "nullable": true, + "readOnly": true + } + }, + "additionalProperties": false + }, + "ProblemTaskTypeViewDto": { + "type": "object", + "properties": { + "taskTypeEnum": { + "$ref": "#/components/schemas/SysTaskTypeEnums" + }, + "taskTypeEnumName": { + "type": "string", + "description": "任务类型枚举名称", + "nullable": true, + "readOnly": true + } + }, + "additionalProperties": false + }, + "QuestionInfoByTaskEnumResult": { + "type": "object", + "properties": { + "questionName": { + "type": "string", + "description": "问题类型", + "nullable": true + }, + "questionCount": { + "type": "integer", + "description": "条数", + "format": "int32" + }, + "questionChain": { + "type": "string", + "description": "环比", + "nullable": true + }, + "questionInfoResults": { + "type": "array", + "items": { + "$ref": "#/components/schemas/QuestionInfoResult" + }, + "description": "问题列表", + "nullable": true + } + }, + "additionalProperties": false + }, + "QuestionInfoByTypeResult": { + "type": "object", + "properties": { + "questionName": { + "type": "string", + "description": "问题类型", + "nullable": true + }, + "questionCount": { + "type": "integer", + "description": "首页本月条数/类型总条数", + "format": "int32" + } + }, + "additionalProperties": false + }, + "QuestionInfoResult": { + "type": "object", + "properties": { + "questionUserId": { + "type": "integer", + "description": "问题人id", + "format": "int64" + }, + "questionUserName": { + "type": "string", + "description": "问题人", + "nullable": true + }, + "addTime": { + "type": "string", + "description": "问题时间", + "format": "date-time" + } + }, + "additionalProperties": false, + "description": "问题列表" + }, + "ReadSummarizRequest": { + "type": "object", + "properties": { + "sumId": { + "type": "integer", + "description": "总结id", + "format": "int64" + }, + "sumType": { + "type": "integer", + "description": "总结类型 1日报 2周报 3月报", + "format": "int32" + } + }, + "additionalProperties": false + }, + "RefreshTokenRequest": { + "type": "object", + "properties": { + "token": { + "type": "string", + "description": "过期的token", + "nullable": true + } + }, + "additionalProperties": false + }, + "RegisterRequest": { + "type": "object", + "properties": { + "account": { + "type": "string", + "description": "账号", + "nullable": true + }, + "password": { + "type": "string", + "description": "密码", + "nullable": true + }, + "userName": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + }, + "SchoolResult": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "schoolName": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + }, + "SchoolTree": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "schoolName": { + "type": "string", + "nullable": true + }, + "count": { + "type": "integer", + "format": "int32" + }, + "users": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserBooksResult" + }, + "nullable": true + }, + "children": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SchoolTree" + }, + "nullable": true + } + }, + "additionalProperties": false + }, + "SchoolUserBooksResult": { + "type": "object", + "properties": { + "cloudSchoolName": { + "type": "string", + "description": "云校名称", + "nullable": true + }, + "schoolName": { + "type": "string", + "description": "学校名称", + "nullable": true + }, + "id": { + "type": "integer", + "description": "Id", + "format": "int64" + }, + "userName": { + "type": "string", + "description": "用户姓名", + "nullable": true + }, + "headImage": { + "type": "string", + "description": "头像", + "nullable": true + }, + "roleEnum": { + "$ref": "#/components/schemas/SysRoleEnum" + }, + "phone": { + "type": "string", + "description": "电话", + "nullable": true + } + }, + "additionalProperties": false + }, + "SolutionDetailViewDto": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "problemTitle": { + "type": "string", + "description": "备 注:问题描述\r\n默认值:", + "nullable": true + }, + "upTime": { + "type": "string", + "description": "最后修改时间", + "format": "date-time" + }, + "problemObj": { + "$ref": "#/components/schemas/ToolObjectType" + }, + "toolClassType": { + "$ref": "#/components/schemas/ToolClassType" + }, + "problemSemesters": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProblemSemesterViewDto" + }, + "description": "关联学段枚举列表", + "nullable": true + }, + "problemPhenomenon": { + "type": "string", + "description": "备 注:问题显著现象\r\n默认值:", + "nullable": true + }, + "solutionContent": { + "type": "string", + "description": "备 注:解决方案文本描述\r\n默认值:", + "nullable": true + }, + "problemTaskTypes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProblemTaskTypeViewDto" + }, + "description": "关联任务类型枚举列表", + "nullable": true + }, + "toolKits": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ToolKitViewDto" + }, + "description": "解决方案工具列表", + "nullable": true + } + }, + "additionalProperties": false + }, + "SolutionListViewDto": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "problemTitle": { + "type": "string", + "description": "备 注:问题描述\r\n默认值:", + "nullable": true + }, + "upTime": { + "type": "string", + "description": "最后修改时间", + "format": "date-time" + }, + "problemObj": { + "$ref": "#/components/schemas/ToolObjectType" + }, + "toolClassType": { + "$ref": "#/components/schemas/ToolClassType" + }, + "problemSemesters": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProblemSemesterViewDto" + }, + "description": "关联学段枚举列表", + "nullable": true + } + }, + "additionalProperties": false + }, + "SolutionListViewMobileDto": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "problemTitle": { + "type": "string", + "description": "备 注:问题描述\r\n默认值:", + "nullable": true + }, + "upTime": { + "type": "string", + "description": "最后修改时间", + "format": "date-time" + }, + "problemObj": { + "$ref": "#/components/schemas/ToolObjectType" + }, + "toolClassType": { + "$ref": "#/components/schemas/ToolClassType" + }, + "problemPhenomenon": { + "type": "string", + "description": "备 注:问题显著现象\r\n默认值:", + "nullable": true + }, + "problemSemesters": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProblemSemesterViewDto" + }, + "description": "关联学段枚举列表", + "nullable": true + } + }, + "additionalProperties": false + }, + "SolutionListViewMobileDtoPageResponse": { + "type": "object", + "properties": { + "total": { + "type": "integer", + "description": "总记录条数", + "format": "int32" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SolutionListViewMobileDto" + }, + "description": "响应数据", + "nullable": true + } + }, + "additionalProperties": false, + "description": "分页响应实体类" + }, + "SolutionSemesterEnum": { + "enum": [ + 0, + 71, + 72, + 73, + 74, + 81, + 82, + 83, + 84, + 91, + 92, + 93, + 94, + 101, + 102, + 103, + 104, + 111, + 112, + 113, + 114, + 121, + 122, + 123, + 124 + ], + "type": "integer", + "description": "解决方案及工具包使用年级枚举", + "format": "int32" + }, + "SpotCheckTypeValue": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "value": { + "type": "integer", + "description": "备 注:分值\r\n默认值:", + "format": "int32" + }, + "valueName": { + "type": "string", + "description": "备 注:标准内容\r\n默认值:", + "nullable": true + }, + "checkId": { + "type": "integer", + "description": "备 注:所属抽查标准\r\n默认值:", + "format": "int64" + } + }, + "additionalProperties": false, + "description": "学习行为习惯全面抽查流程标准-类型表-各项具体分值表" + }, + "SpotTaskFinishRequest": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "任务id", + "format": "int64" + }, + "spotId": { + "type": "integer", + "description": "抽查记录表id", + "format": "int64" + }, + "remark": { + "type": "string", + "description": "反馈/反思", + "nullable": true + } + }, + "additionalProperties": false, + "description": "学习行为习惯全面抽查任务完成请求类" + }, + "SpotTaskRequest": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "任务id(新增时为null,修改时必传)", + "format": "int64", + "nullable": true + }, + "classesId": { + "type": "integer", + "description": "班级id(班级任务时,classes_id不为空;通用任务时,班级id为空 null)", + "format": "int64", + "nullable": true + }, + "taskTypeEnum": { + "type": "integer", + "description": "任务类型id", + "format": "int32" + }, + "startTime": { + "type": "string", + "description": "备 注:开始时间\r\n默认值:", + "format": "date-time" + }, + "endTime": { + "type": "string", + "description": "备 注:结束时间\r\n默认值:", + "format": "date-time" + }, + "taskTitleSuffix": { + "type": "string", + "description": "任务后缀", + "nullable": true + }, + "spotId": { + "type": "integer", + "description": "抽查任务id(新增时为null,修改时必传)", + "format": "int64", + "nullable": true + }, + "checkProjectid": { + "type": "integer", + "description": "备 注:抽查项目id\r\n默认值:", + "format": "int64" + } + }, + "additionalProperties": false, + "description": "学习行为习惯全面抽查任务请求类" + }, + "SpotTaskResult": { + "type": "object", + "properties": { + "taskInfo": { + "$ref": "#/components/schemas/BaseTaskClassResult" + }, + "spotId": { + "type": "integer", + "description": "抽查任务id", + "format": "int64" + }, + "classesTaskId": { + "type": "integer", + "description": "任务id", + "format": "int64" + }, + "checkProjectid": { + "type": "integer", + "description": "抽查项目id", + "format": "int64" + }, + "checkProjectName": { + "type": "string", + "description": "抽查项目名称", + "nullable": true + }, + "remark": { + "type": "string", + "description": "反馈/反思", + "nullable": true + }, + "spotTaskUserResults": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SpotTaskUserResult" + }, + "description": "抽查人员列表", + "nullable": true + } + }, + "additionalProperties": false, + "description": "学习行为习惯全面抽查任务结果类" + }, + "SpotTaskUserRequest": { + "type": "object", + "properties": { + "spotId": { + "type": "integer", + "description": "抽查记录表id", + "format": "int64" + }, + "userId": { + "type": "integer", + "description": "抽查人员id", + "format": "int64" + }, + "valueTotal": { + "type": "integer", + "description": "分值", + "format": "int32" + } + }, + "additionalProperties": false, + "description": "学习行为习惯全面抽查任务-抽查人员请求类" + }, + "SpotTaskUserResult": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "id", + "format": "int64" + }, + "spotId": { + "type": "integer", + "description": "抽查记录表id", + "format": "int64" + }, + "userId": { + "type": "integer", + "description": "抽查人员id", + "format": "int64" + }, + "valueTotal": { + "type": "number", + "description": "得分", + "format": "double" + }, + "userName": { + "type": "string", + "description": "抽查人员名称", + "nullable": true + }, + "addTime": { + "type": "string", + "description": "添加时间", + "format": "date-time" + } + }, + "additionalProperties": false + }, + "StudentRequest": { + "type": "object", + "properties": { + "classesId": { + "type": "integer", + "description": "班级id", + "format": "int64" + }, + "userName": { + "type": "string", + "description": "用户名称", + "nullable": true + } + }, + "additionalProperties": false + }, + "StudentResult": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "学生Id", + "format": "int64" + }, + "name": { + "type": "string", + "description": "学生姓名", + "nullable": true + } + }, + "additionalProperties": false + }, + "Subjectinfo": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string", + "description": "备 注:学科名称\r\n默认值:", + "nullable": true + }, + "thisid": { + "type": "integer", + "description": "备 注:自己数据库Id\r\n默认值:", + "format": "int64" + }, + "createtime": { + "type": "string", + "description": "备 注:创建时间\r\n默认值:", + "format": "date-time" + }, + "updatetime": { + "type": "string", + "description": "备 注:更新时间\r\n默认值:", + "format": "date-time", + "nullable": true + }, + "deletestate": { + "type": "boolean", + "description": "备 注:是否删除 true=删除\r\n默认值:" + }, + "ordinal": { + "type": "integer", + "description": "备 注:排序\r\n默认值:", + "format": "int32" + } + }, + "additionalProperties": false, + "description": "学科/科目表" + }, + "SummarizDayListResult": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "日报Id", + "format": "int64" + }, + "workDate": { + "type": "string", + "description": "备 注:工作总结日期\r\n默认值:", + "format": "date-time" + }, + "questContent": { + "type": "string", + "description": "备 注:问题反馈\r\n默认值:", + "nullable": true + }, + "userId": { + "type": "integer", + "description": "用户id", + "format": "int64" + }, + "userName": { + "type": "string", + "description": "用户姓名", + "nullable": true + }, + "userEnum": { + "type": "integer", + "description": "备 注:用户角色枚举(如果是组长,只有部长评价,如果是学习官,有部长和组长评价)\r\n默认值:", + "format": "int32" + }, + "addTime": { + "type": "string", + "description": "备 注:添加时间\r\n默认值:", + "format": "date-time" + }, + "superiorEvaluation": { + "type": "string", + "description": "备 注:组长评价\r\n默认值:", + "nullable": true + }, + "superiorUserId": { + "type": "integer", + "description": "备 注:组长评价人id\r\n默认值:", + "format": "int64", + "nullable": true + }, + "superiorUserName": { + "type": "string", + "description": "备 注:组长评价人\r\n默认值:", + "nullable": true + }, + "ministerEvaluation": { + "type": "string", + "description": "备 注:部长评价\r\n默认值:", + "nullable": true + }, + "ministerUserId": { + "type": "integer", + "description": "备 注:部长评价人id\r\n默认值:", + "format": "int64", + "nullable": true + }, + "ministerUserName": { + "type": "string", + "description": "备 注:部长评价人\r\n默认值:", + "nullable": true + }, + "nextTimeContent": { + "type": "string", + "description": "备 注:下次工作内容\r\n默认值:", + "nullable": true + }, + "readId": { + "type": "integer", + "description": "已读id(如果null则未读)", + "format": "int64", + "nullable": true + } + }, + "additionalProperties": false + }, + "SummarizDayListResultPageResponse": { + "type": "object", + "properties": { + "total": { + "type": "integer", + "description": "总记录条数", + "format": "int32" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SummarizDayListResult" + }, + "description": "响应数据", + "nullable": true + } + }, + "additionalProperties": false, + "description": "分页响应实体类" + }, + "SummarizDayResult": { + "type": "object", + "properties": { + "taskList": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SummarizTaskResult" + }, + "description": "任务列表", + "nullable": true + }, + "summarizDay": { + "$ref": "#/components/schemas/SummarizDayTaskResult" + } + }, + "additionalProperties": false + }, + "SummarizDayTaskResult": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "userEnum": { + "type": "integer", + "description": "用户角色枚举", + "format": "int32" + }, + "userId": { + "type": "integer", + "description": "用户Id", + "format": "int64" + }, + "userName": { + "type": "string", + "description": "用户姓名", + "nullable": true + }, + "workDate": { + "type": "string", + "description": "工作总结日期", + "format": "date-time" + }, + "questContent": { + "type": "string", + "description": "问题反馈", + "nullable": true + }, + "addTime": { + "type": "string", + "description": "创建时间", + "format": "date-time" + }, + "superiorEvaluation": { + "type": "string", + "description": "组长评价", + "nullable": true + }, + "superiorUserId": { + "type": "integer", + "description": "组长用户Id", + "format": "int64", + "nullable": true + }, + "superiorUserName": { + "type": "string", + "description": "组长用户名", + "nullable": true + }, + "ministerEvaluation": { + "type": "string", + "description": "部长评价", + "nullable": true + }, + "ministerUserId": { + "type": "integer", + "description": "部长用户Id", + "format": "int64", + "nullable": true + }, + "ministerUserName": { + "type": "string", + "description": "部长用户名", + "nullable": true + }, + "nextTimeContent": { + "type": "string", + "description": "备 注:下次工作内容\r\n默认值:", + "nullable": true + } + }, + "additionalProperties": false + }, + "SummarizMonthCheckList": { + "type": "object", + "properties": { + "taskTypeEnum": { + "type": "integer", + "description": "任务类型枚举", + "format": "int64" + }, + "taskTypeName": { + "type": "string", + "description": "任务类型枚举-名称", + "nullable": true + }, + "okTaskCount": { + "type": "integer", + "description": "已完成任务数量", + "format": "int32" + }, + "shouldTaskCount": { + "type": "integer", + "description": "应完成任务数量(为0时,代表是指标之外的)", + "format": "int64" + } + }, + "additionalProperties": false + }, + "SummarizMonthListResult": { + "type": "object", + "properties": { + "taskInfo": { + "$ref": "#/components/schemas/SummarizMonthStandardResult" + }, + "summarizMonthTaskResult": { + "$ref": "#/components/schemas/SummarizMonthTaskResult" + } + }, + "additionalProperties": false + }, + "SummarizMonthRequest": { + "type": "object", + "properties": { + "workYear": { + "type": "integer", + "description": "备 注:工作年份\r\n默认值:", + "format": "int32", + "nullable": true + }, + "workMonth": { + "type": "integer", + "description": "备 注:工作月份\r\n默认值:", + "format": "int32" + }, + "questContent": { + "type": "string", + "description": "备 注:问题反馈\r\n默认值:", + "nullable": true + }, + "monthContent": { + "type": "string", + "description": "备 注:本月总结\r\n默认值:", + "nullable": true + }, + "nextTimeContent": { + "type": "string", + "description": "备 注:下次工作内容\r\n默认值:", + "nullable": true + }, + "plans": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SummarizPlanRequest" + }, + "description": "计划任务", + "nullable": true + } + }, + "additionalProperties": false + }, + "SummarizMonthStandardResult": { + "type": "object", + "properties": { + "generalTasks": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SummarizMonthCheckList" + }, + "description": "通用任务列表", + "nullable": true + }, + "taskFinishLists": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TaskFinishList" + }, + "description": "其他工作完成情况", + "nullable": true + } + }, + "additionalProperties": false + }, + "SummarizMonthTaskResult": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "userId": { + "type": "integer", + "description": "备 注:用户id\r\n默认值:", + "format": "int64" + }, + "userName": { + "type": "string", + "description": "用户姓名", + "nullable": true + }, + "userEnum": { + "type": "integer", + "description": "备 注:用户角色枚举(如果是组长,只有部长评价,如果是学习官,有部长和组长评价)\r\n默认值:", + "format": "int32" + }, + "workYear": { + "type": "integer", + "description": "备 注:工作年份\r\n默认值:", + "format": "int32", + "nullable": true + }, + "workMonth": { + "type": "integer", + "description": "备 注:工作月份\r\n默认值:", + "format": "int32" + }, + "questContent": { + "type": "string", + "description": "备 注:问题反馈\r\n默认值:", + "nullable": true + }, + "monthContent": { + "type": "string", + "description": "备 注:本月总结\r\n默认值:", + "nullable": true + }, + "addTime": { + "type": "string", + "description": "备 注:添加时间\r\n默认值:", + "format": "date-time" + }, + "superiorEvaluation": { + "type": "string", + "description": "备 注:组长评价\r\n默认值:", + "nullable": true + }, + "superiorUserId": { + "type": "integer", + "description": "备 注:组长评价人id\r\n默认值:", + "format": "int64", + "nullable": true + }, + "superiorAddtime": { + "type": "string", + "description": "备 注:组长评价时间\r\n默认值:", + "format": "date-time", + "nullable": true + }, + "superiorUserName": { + "type": "string", + "description": "组长用户名", + "nullable": true + }, + "ministerEvaluation": { + "type": "string", + "description": "备 注:部长评价\r\n默认值:", + "nullable": true + }, + "ministerUserId": { + "type": "integer", + "description": "备 注:部长评价人id\r\n默认值:", + "format": "int64", + "nullable": true + }, + "ministerAddtime": { + "type": "string", + "description": "备 注:部长评价时间\r\n默认值:", + "format": "date-time", + "nullable": true + }, + "ministerUserName": { + "type": "string", + "description": "部长用户名", + "nullable": true + }, + "nextTimeContent": { + "type": "string", + "description": "备 注:下次工作内容\r\n默认值:", + "nullable": true + }, + "readId": { + "type": "integer", + "description": "已读id(如果null则未读)", + "format": "int64", + "nullable": true + } + }, + "additionalProperties": false + }, + "SummarizMonthTaskResultPageResponse": { + "type": "object", + "properties": { + "total": { + "type": "integer", + "description": "总记录条数", + "format": "int32" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SummarizMonthTaskResult" + }, + "description": "响应数据", + "nullable": true + } + }, + "additionalProperties": false, + "description": "分页响应实体类" + }, + "SummarizNoReadResult": { + "type": "object", + "properties": { + "noReadCount": { + "type": "integer", + "description": "未读消息数量", + "format": "int32" + } + }, + "additionalProperties": false + }, + "SummarizPlan": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "userId": { + "type": "integer", + "description": "备 注:用户id\r\n默认值:", + "format": "int64" + }, + "summarizType": { + "type": "integer", + "description": "备 注:总结类型,1:日报;2:周报;3:月报;\r\n默认值:", + "format": "int32" + }, + "summarizId": { + "type": "integer", + "description": "备 注:总结id\r\n默认值:", + "format": "int64" + }, + "startTime": { + "type": "string", + "description": "备 注:开始时间\r\n默认值:", + "format": "date-time" + }, + "startDate": { + "type": "string", + "description": "备 注:开始日期\r\n默认值:", + "format": "date-time" + }, + "endTime": { + "type": "string", + "description": "备 注:结束时间\r\n默认值:", + "format": "date-time" + }, + "endDate": { + "type": "string", + "description": "备 注:结束日期\r\n默认值:", + "format": "date-time" + }, + "taskTypeEnum": { + "type": "integer", + "description": "备 注:任务类型枚举\r\n默认值:", + "format": "int32" + }, + "taskTypeName": { + "type": "string", + "description": "备 注:任务类型名称\r\n默认值:", + "nullable": true + }, + "taskTitleSuffix": { + "type": "string", + "description": "备 注:任务标题后缀,用于展示\r\n默认值:", + "nullable": true + } + }, + "additionalProperties": false, + "description": "工作总结-下次工作计划" + }, + "SummarizPlanRequest": { + "type": "object", + "properties": { + "startTime": { + "type": "string", + "description": "备 注:开始时间\r\n默认值:", + "format": "date-time" + }, + "endTime": { + "type": "string", + "description": "备 注:结束时间\r\n默认值:", + "format": "date-time" + }, + "taskTypeEnum": { + "type": "integer", + "description": "备 注:任务类型枚举\r\n默认值:", + "format": "int32" + }, + "taskTypeName": { + "type": "string", + "description": "备 注:任务类型名称\r\n默认值:", + "nullable": true + }, + "taskTitleSuffix": { + "type": "string", + "description": "备 注:任务标题后缀,用于展示\r\n默认值:", + "nullable": true + } + }, + "additionalProperties": false + }, + "SummarizTaskResult": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "integer", + "description": "备 注:任务状态 0:未开始 1:进行中 2:已结束 3:问题待处理(仅用于双师跟课)\r\n默认值:", + "format": "int32" + }, + "classesId": { + "type": "integer", + "description": "备 注:班级id(班级任务)\r\n默认值:", + "format": "int64", + "nullable": true + }, + "classesName": { + "type": "string", + "description": "备 注:班级名称", + "nullable": true + }, + "taskTypeEnum": { + "type": "integer", + "description": "备 注:参数表 枚举值,任务类型\r\n默认值:", + "format": "int32" + }, + "taskTypeName": { + "type": "string", + "description": "备 注:任务类型名称", + "nullable": true + }, + "taskTitleSuffix": { + "type": "string", + "description": "备 注:任务标题后缀,用于展示\r\n默认值:", + "nullable": true + }, + "startDate": { + "type": "string", + "description": "备 注:开始日期\r\n默认值:", + "format": "date-time", + "nullable": true + }, + "startTime": { + "type": "string", + "description": "备 注:开始时间\r\n默认值:", + "format": "date-time" + }, + "endDate": { + "type": "string", + "description": "备 注:结束日期\r\n默认值:", + "format": "date-time", + "nullable": true + }, + "endTime": { + "type": "string", + "description": "备 注:结束时间\r\n默认值:", + "format": "date-time" + }, + "taskIndexType": { + "type": "integer", + "description": "备 注:任务指标类型-1班级;2通用; 班级时,classes_id不为空;通用时,执行人id不为空\r\n默认值:", + "format": "int32" + }, + "finishDate": { + "type": "string", + "description": "备 注:任务完成日期\r\n默认值:", + "format": "date-time", + "nullable": true + }, + "finishDatetime": { + "type": "string", + "description": "备 注:任务完成时间\r\n默认值:", + "format": "date-time", + "nullable": true + }, + "taskWorkTime": { + "type": "string", + "description": "备 注:任务首次操作时间\r\n默认值:", + "format": "date-time", + "nullable": true + }, + "taskWorkDate": { + "type": "string", + "description": "备 注:任务首次操作日期\r\n默认值:", + "format": "date-time", + "nullable": true + } + }, + "additionalProperties": false + }, + "SummarizWeekListResult": { + "type": "object", + "properties": { + "taskList": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SummarizTaskResult" + }, + "description": "任务列表", + "nullable": true + }, + "summarizWeekTaskResult": { + "$ref": "#/components/schemas/SummarizWeekTaskResult" + } + }, + "additionalProperties": false + }, + "SummarizWeekRequest": { + "type": "object", + "properties": { + "workYear": { + "type": "integer", + "description": "备 注:工作年份\r\n默认值:", + "format": "int32" + }, + "workMonth": { + "type": "integer", + "description": "备 注:工作月份\r\n默认值:", + "format": "int32" + }, + "workWeek": { + "type": "integer", + "description": "备 注:工作周报\r\n默认值:", + "format": "int32" + }, + "beginDate": { + "type": "string", + "description": "备 注:工作总结日期-开始\r\n默认值:", + "format": "date-time" + }, + "endDate": { + "type": "string", + "description": "备 注:工作总结日期-结束\r\n默认值:", + "format": "date-time" + }, + "questContent": { + "type": "string", + "description": "备 注:问题反馈\r\n默认值:", + "nullable": true + }, + "nextTimeContent": { + "type": "string", + "description": "备 注:下次工作内容\r\n默认值:", + "nullable": true + }, + "plans": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SummarizPlanRequest" + }, + "description": "计划任务", + "nullable": true + } + }, + "additionalProperties": false + }, + "SummarizWeekTaskResult": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "userEnum": { + "type": "integer", + "description": "用户角色枚举", + "format": "int32" + }, + "userId": { + "type": "integer", + "description": "用户Id", + "format": "int64" + }, + "userName": { + "type": "string", + "description": "用户姓名", + "nullable": true + }, + "workYear": { + "type": "integer", + "description": "总结年份", + "format": "int32" + }, + "workMonth": { + "type": "integer", + "description": "总结月份", + "format": "int32" + }, + "workWeek": { + "type": "integer", + "description": "总结周", + "format": "int32" + }, + "beginDate": { + "type": "string", + "description": "开始日期", + "format": "date-time", + "nullable": true + }, + "endDate": { + "type": "string", + "description": "结束日期", + "format": "date-time", + "nullable": true + }, + "questContent": { + "type": "string", + "description": "问题反馈", + "nullable": true + }, + "addTime": { + "type": "string", + "description": "创建时间", + "format": "date-time" + }, + "superiorEvaluation": { + "type": "string", + "description": "组长评价", + "nullable": true + }, + "superiorUserId": { + "type": "integer", + "description": "组长用户Id", + "format": "int64", + "nullable": true + }, + "superiorAddtime": { + "type": "string", + "description": "备 注:组长评价时间\r\n默认值:", + "format": "date-time", + "nullable": true + }, + "superiorUserName": { + "type": "string", + "description": "组长用户名", + "nullable": true + }, + "ministerEvaluation": { + "type": "string", + "description": "部长评价", + "nullable": true + }, + "ministerUserId": { + "type": "integer", + "description": "部长用户Id", + "format": "int64", + "nullable": true + }, + "ministerAddtime": { + "type": "string", + "description": "备 注:部长评价时间\r\n默认值:", + "format": "date-time", + "nullable": true + }, + "ministerUserName": { + "type": "string", + "description": "部长用户名", + "nullable": true + }, + "nextTimeContent": { + "type": "string", + "description": "备 注:下次工作内容\r\n默认值:", + "nullable": true + }, + "readId": { + "type": "integer", + "description": "已读id(如果null则未读)", + "format": "int64", + "nullable": true + } + }, + "additionalProperties": false + }, + "SummarizWeekTaskResultPageResponse": { + "type": "object", + "properties": { + "total": { + "type": "integer", + "description": "总记录条数", + "format": "int32" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SummarizWeekTaskResult" + }, + "description": "响应数据", + "nullable": true + } + }, + "additionalProperties": false, + "description": "分页响应实体类" + }, + "SummarizeDayRequest": { + "type": "object", + "properties": { + "workDate": { + "type": "string", + "description": "备 注:工作总结日期\r\n默认值:", + "format": "date-time" + }, + "questContent": { + "type": "string", + "description": "备 注:问题反馈\r\n默认值:", + "nullable": true + }, + "nextTimeContent": { + "type": "string", + "description": "备 注:下次工作内容\r\n默认值:", + "nullable": true + }, + "plans": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SummarizPlanRequest" + }, + "description": "计划任务", + "nullable": true + } + }, + "additionalProperties": false + }, + "SunTaskFileResult": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "文件关联表的id", + "format": "int64" + }, + "sunTaskId": { + "type": "integer", + "description": "子任务关联表id", + "format": "int64" + }, + "fileId": { + "type": "integer", + "description": "文件id", + "format": "int64" + }, + "filePath": { + "type": "string", + "description": "备 注:文件路径", + "nullable": true + }, + "fileSize": { + "type": "integer", + "description": "备 注:文件大小(文件大小-kb,不足1kb为1kb)\r\n默认值:", + "format": "int64", + "nullable": true + } + }, + "additionalProperties": false, + "description": "任务关联文件" + }, + "SunTaskUserResult": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "用户关联表的id", + "format": "int64" + }, + "sunTaskId": { + "type": "integer", + "description": "子任务关联表id", + "format": "int64" + }, + "userId": { + "type": "integer", + "description": "", + "format": "int64" + }, + "userName": { + "type": "string", + "description": "备 注:文件路径", + "nullable": true + } + }, + "additionalProperties": false, + "description": "任务关联用户" + }, + "SysFileViewDto": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "fileName": { + "type": "string", + "description": "备 注:原文件名\r\n默认值:", + "nullable": true + }, + "filePath": { + "type": "string", + "description": "备 注:文件所在路径\r\n默认值:", + "nullable": true + }, + "fileType": { + "type": "string", + "description": "备 注:文件类型\r\n默认值:", + "nullable": true + }, + "addtime": { + "type": "string", + "description": "备 注:上传时间\r\n默认值:", + "format": "date-time", + "nullable": true + } + }, + "additionalProperties": false + }, + "SysParameter": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "pid": { + "type": "integer", + "description": "备 注:父级id,默认0,0时无父级\r\n默认值:", + "format": "int64" + }, + "pName": { + "type": "string", + "description": "备 注:参数名称\r\n默认值:", + "nullable": true + }, + "pValue": { + "type": "integer", + "description": "备 注:枚举值,不可重复,自定义 或 直接生成,后续业务关联也是关联此字段\r\n默认值:", + "format": "int64" + }, + "sort": { + "type": "integer", + "description": "备 注:排序,默认0\r\n默认值:", + "format": "int32" + }, + "isDelete": { + "type": "integer", + "description": "备 注:0正常,1已删除\r\n默认值:", + "format": "int32" + }, + "isQuestion": { + "type": "integer", + "description": "备 注:是否是问题。0否,1是\r\n默认值:", + "format": "int32" + }, + "questionType": { + "type": "integer", + "description": "备 注:问题类别。0无(不是问题时默认0),1学生、2教师\r\n默认值:", + "format": "int32" + }, + "child": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SysParameter" + }, + "nullable": true + } + }, + "additionalProperties": false, + "description": "字典表" + }, + "SysParameterPageResponse": { + "type": "object", + "properties": { + "total": { + "type": "integer", + "description": "总记录条数", + "format": "int32" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SysParameter" + }, + "description": "响应数据", + "nullable": true + } + }, + "additionalProperties": false, + "description": "分页响应实体类" + }, + "SysRoleEnum": { + "enum": [ + 1, + 2, + 1001, + 1002, + 1003 + ], + "type": "integer", + "description": "系统角色枚举", + "format": "int32" + }, + "SysTaskTypeEnums": { + "enum": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12 + ], + "type": "integer", + "description": "任务类型枚举", + "format": "int32" + }, + "System_filesRequest": { + "type": "object", + "properties": { + "fileName": { + "type": "string", + "description": "备 注:原文件名包括后缀例如【111.txt】\r\n默认值:", + "nullable": true + }, + "filePath": { + "type": "string", + "description": "备 注:文件Url路径\r\n默认值:", + "nullable": true + }, + "fileType": { + "type": "string", + "description": "备 注:文件类型(文件后缀名,不带.)\r\n默认值:", + "nullable": true + }, + "fileSize": { + "type": "integer", + "description": "备 注:文件大小(文件大小-kb,不足1kb为1kb)\r\n默认值:", + "format": "int64", + "nullable": true + } + }, + "additionalProperties": false + }, + "TalkInfoRequest": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "任务id(新增时为null,修改时必传)", + "format": "int64", + "nullable": true + }, + "classesId": { + "type": "integer", + "description": "班级id(班级任务时,classes_id不为空;通用任务时,班级id为空 null)", + "format": "int64", + "nullable": true + }, + "taskTypeEnum": { + "type": "integer", + "description": "任务类型id", + "format": "int32" + }, + "startTime": { + "type": "string", + "description": "备 注:开始时间\r\n默认值:", + "format": "date-time" + }, + "endTime": { + "type": "string", + "description": "备 注:结束时间\r\n默认值:", + "format": "date-time" + }, + "taskTitleSuffix": { + "type": "string", + "description": "任务后缀", + "nullable": true + }, + "talkId": { + "type": "integer", + "description": "谈话记录表id(新增时为null,修改时必传)", + "format": "int64", + "nullable": true + }, + "quiltUserid": { + "type": "integer", + "description": "被谈话人id", + "format": "int64" + } + }, + "additionalProperties": false + }, + "TaskFinishList": { + "type": "object", + "properties": { + "taskTypeEnum": { + "type": "integer", + "description": "任务类型枚举", + "format": "int64" + }, + "taskTypeName": { + "type": "string", + "description": "任务类型枚举-名称", + "nullable": true + }, + "taskCount": { + "type": "integer", + "description": "任务数量", + "format": "int32" + } + }, + "additionalProperties": false + }, + "TaskLoginfoResult": { + "type": "object", + "properties": { + "taskId": { + "type": "integer", + "description": "任务id", + "format": "int64" + }, + "sunTaskId": { + "type": "integer", + "description": "备 注:子任务id\r\n默认值:", + "format": "int64" + }, + "addDate": { + "type": "string", + "description": "备 注:操作日期\r\n默认值:", + "format": "date-time" + }, + "logType": { + "type": "integer", + "description": "备 注:操作类型。1新增,2推进,3完成任务\r\n默认值:", + "format": "int32" + }, + "logCount": { + "type": "integer", + "description": "当天操作记录数量", + "format": "int32" + } + }, + "additionalProperties": false + }, + "TaskTalkFinishRequest": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "任务id", + "format": "int64" + }, + "talkId": { + "type": "integer", + "description": "谈话表id", + "format": "int64" + }, + "remark": { + "type": "string", + "description": "谈话纪要", + "nullable": true + }, + "files": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseTaskFileRequest" + }, + "nullable": true + } + }, + "additionalProperties": false + }, + "TaskTalkResult": { + "type": "object", + "properties": { + "talkId": { + "type": "integer", + "description": "谈话表id", + "format": "int64" + }, + "remark": { + "type": "string", + "description": "谈话纪要", + "nullable": true + }, + "userId": { + "type": "integer", + "description": "用户id", + "format": "int64" + }, + "userName": { + "type": "string", + "description": "用户名称", + "nullable": true + }, + "taskInfo": { + "$ref": "#/components/schemas/BaseTaskClassResult" + }, + "sunTaskFileResults": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SunTaskFileResult" + }, + "description": "关联文件", + "nullable": true + } + }, + "additionalProperties": false + }, + "Task_checklistRequest": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "新增为null,编辑时传Id", + "format": "int64", + "nullable": true + }, + "taskTypeEnum": { + "type": "integer", + "description": "备 注:任务类型id\r\n默认值:", + "format": "int64" + }, + "targetNumber": { + "type": "integer", + "description": "备 注:任务指标数\r\n默认值:", + "format": "int32" + }, + "taskType": { + "type": "integer", + "description": "备 注:班级/通用任务类型 1班级;2通用\r\n默认值:", + "format": "int32" + } + }, + "additionalProperties": false + }, + "Task_checklistResult": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "taskTypeEnum": { + "type": "integer", + "description": "任务类型id", + "format": "int64" + }, + "taskTypeEnumName": { + "type": "string", + "description": "备 注:任务类型名称", + "nullable": true + }, + "taskUserId": { + "type": "integer", + "description": "备 注:用户id\r\n默认值:", + "format": "int64" + }, + "targetNumber": { + "type": "integer", + "description": "备 注:任务指标数\r\n默认值:", + "format": "int64" + }, + "taskType": { + "type": "integer", + "description": "备 注:班级/通用任务类型 1班级;2通用\r\n默认值:", + "format": "int32" + } + }, + "additionalProperties": false + }, + "TeacherBehaviorFinishRequest": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "任务id", + "format": "int64" + }, + "tBId": { + "type": "integer", + "description": "观察表id", + "format": "int64" + }, + "remark": { + "type": "string", + "description": "分析/总结", + "nullable": true + } + }, + "additionalProperties": false + }, + "TeacherBehaviorQuestionNumResult": { + "type": "object", + "properties": { + "questionId": { + "type": "integer", + "description": "问题id", + "format": "int64" + }, + "num": { + "type": "integer", + "description": "问题个数", + "format": "int32" + }, + "addTime": { + "type": "string", + "description": "记录时间", + "format": "date-time" + } + }, + "additionalProperties": false + }, + "TeacherBehaviorQuestionResult": { + "type": "object", + "properties": { + "previewString": { + "type": "string", + "description": "预习安排", + "nullable": true + }, + "cooperationString": { + "type": "string", + "description": "课堂配合", + "nullable": true + }, + "afterString": { + "type": "string", + "description": "课后观察", + "nullable": true + } + }, + "additionalProperties": false + }, + "TeacherBehaviorRequest": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "任务id(新增时为null,修改时必传)", + "format": "int64", + "nullable": true + }, + "classesId": { + "type": "integer", + "description": "班级id(班级任务时,classes_id不为空;通用任务时,班级id为空 null)", + "format": "int64", + "nullable": true + }, + "taskTypeEnum": { + "type": "integer", + "description": "任务类型id", + "format": "int32" + }, + "startTime": { + "type": "string", + "description": "备 注:开始时间\r\n默认值:", + "format": "date-time" + }, + "endTime": { + "type": "string", + "description": "备 注:结束时间\r\n默认值:", + "format": "date-time" + }, + "taskTitleSuffix": { + "type": "string", + "description": "任务后缀", + "nullable": true + }, + "tBId": { + "type": "integer", + "description": "教师行为规范id(新增时为null,修改时必传)", + "format": "int64", + "nullable": true + }, + "behaviorUserName": { + "type": "string", + "description": "观察对象姓名", + "nullable": true + }, + "behaviorUserId": { + "type": "integer", + "description": "观察对象Id", + "format": "int64" + } + }, + "additionalProperties": false + }, + "TeacherBehaviorResult": { + "type": "object", + "properties": { + "taskInfo": { + "$ref": "#/components/schemas/BaseTaskClassResult" + }, + "tBId": { + "type": "integer", + "description": "观察表id", + "format": "int64" + }, + "behaviorUserName": { + "type": "string", + "description": "观察对象名称", + "nullable": true + }, + "remark": { + "type": "string", + "description": "反馈反思", + "nullable": true + }, + "teacherBehaviorQuestionNumResults": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TeacherBehaviorQuestionNumResult" + }, + "description": "观察记录列表", + "nullable": true + } + }, + "additionalProperties": false + }, + "TeacherTalkInfoFinishRequest": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "任务id", + "format": "int64" + }, + "teacherTalkId": { + "type": "integer", + "description": "谈话ID", + "format": "int64" + }, + "remark": { + "type": "string", + "description": "谈话纪要", + "nullable": true + }, + "files": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseTaskFileRequest" + }, + "nullable": true + } + }, + "additionalProperties": false + }, + "TeacherTalkInfoRequest": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "任务id(新增时为null,修改时必传)", + "format": "int64", + "nullable": true + }, + "classesId": { + "type": "integer", + "description": "班级id(班级任务时,classes_id不为空;通用任务时,班级id为空 null)", + "format": "int64", + "nullable": true + }, + "taskTypeEnum": { + "type": "integer", + "description": "任务类型id", + "format": "int32" + }, + "startTime": { + "type": "string", + "description": "备 注:开始时间\r\n默认值:", + "format": "date-time" + }, + "endTime": { + "type": "string", + "description": "备 注:结束时间\r\n默认值:", + "format": "date-time" + }, + "taskTitleSuffix": { + "type": "string", + "description": "任务后缀", + "nullable": true + }, + "teacherTalkId": { + "type": "integer", + "description": "id(新增时为null,修改时必传)", + "format": "int64", + "nullable": true + }, + "quiltUserName": { + "type": "string", + "description": "被谈话老师用户姓名", + "nullable": true + } + }, + "additionalProperties": false + }, + "TeacherTalkInfoResult": { + "type": "object", + "properties": { + "talkId": { + "type": "integer", + "description": "谈话记录ID", + "format": "int64" + }, + "quiltUserName": { + "type": "string", + "description": "被谈话老师用户姓名", + "nullable": true + }, + "remark": { + "type": "string", + "description": "谈话纪要", + "nullable": true + }, + "taskInfo": { + "$ref": "#/components/schemas/BaseTaskClassResult" + }, + "sunTaskFileResults": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SunTaskFileResult" + }, + "description": "关联文件", + "nullable": true + } + }, + "additionalProperties": false + }, + "Teacher_behavior_questionRequest": { + "type": "object", + "properties": { + "tBId": { + "type": "integer", + "description": "观察记录id", + "format": "int64" + }, + "previewId": { + "type": "string", + "description": "预习安排问题,参数id,多个用英文逗号分割", + "nullable": true + }, + "previewTypeId": { + "type": "string", + "description": "预习安排-预习方式问题,参数id,多个用英文逗号分割", + "nullable": true + }, + "inspectTypeEnum": { + "type": "integer", + "description": "预习安排-检查方式: 1课前检查;2课间检查;3全面检查;4未检查", + "format": "int32", + "nullable": true + }, + "previewResultEnum": { + "type": "integer", + "description": "预习安排-预习效果;参数id", + "format": "int32", + "nullable": true + }, + "cooperationId": { + "type": "string", + "description": "备 注:课堂配合,参数id,多个用英文逗号分割\r\n默认值:", + "nullable": true + }, + "cooperationInspectionEnum": { + "type": "integer", + "description": "备 注:课堂配合-巡视频率,1好;2中;3低\r\n默认值:", + "format": "int32", + "nullable": true + }, + "afterClassId": { + "type": "string", + "description": "备 注:课后观察,参数id,多个用英文逗号分割\r\n默认值:", + "nullable": true + } + }, + "additionalProperties": false + }, + "TeachingLevelEnum": { + "enum": [ + 1, + 2 + ], + "type": "integer", + "description": "班级教学层次枚举", + "format": "int32" + }, + "ToolClassType": { + "enum": [ + 1, + 2, + 3 + ], + "type": "integer", + "format": "int32" + }, + "ToolKitSemesterViewDto": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "toolKitId": { + "type": "integer", + "description": "备 注:工具包Id\r\n默认值:", + "format": "int64" + }, + "solutionSemesterEnum": { + "$ref": "#/components/schemas/SolutionSemesterEnum" + }, + "solutionSemesterName": { + "type": "string", + "nullable": true, + "readOnly": true + } + }, + "additionalProperties": false, + "description": "工具包学期视图数据传输对象" + }, + "ToolKitViewDto": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "toolName": { + "type": "string", + "description": "备 注:工具名称\r\n默认值:", + "nullable": true + }, + "toolType": { + "type": "string", + "description": "备 注:工具格式 文件后缀名\r\n默认值:", + "nullable": true + }, + "toolSize": { + "type": "integer", + "description": "备 注:大小 kb\r\n默认值:", + "format": "int32" + }, + "problemObj": { + "$ref": "#/components/schemas/ToolObjectType" + }, + "upTime": { + "type": "string", + "description": "备 注:最后更新时间\r\n默认值:", + "format": "date-time" + }, + "fileinfo": { + "$ref": "#/components/schemas/SysFileViewDto" + }, + "addTime": { + "type": "string", + "description": "备 注:添加时间\r\n默认值:", + "format": "date-time" + }, + "toolClassType": { + "$ref": "#/components/schemas/ToolClassType" + }, + "toolKitSemesters": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ToolKitSemesterViewDto" + }, + "nullable": true + }, + "solutionLists": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SolutionListViewDto" + }, + "description": "关联解决方案列表", + "nullable": true + } + }, + "additionalProperties": false + }, + "ToolObjectType": { + "enum": [ + 1, + 2, + 3 + ], + "type": "integer", + "description": "工具对象类型枚举", + "format": "int32" + }, + "UpdateappResult": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "备 注:\r\n默认值:", + "format": "int64" + }, + "version": { + "type": "integer", + "description": "备 注:版本\r\n默认值:", + "format": "int32" + }, + "versionName": { + "type": "string", + "description": "版本号", + "nullable": true + }, + "remark": { + "type": "string", + "description": "版本说明", + "nullable": true + }, + "imageBase": { + "type": "string", + "description": "二维码图片:base64", + "nullable": true + }, + "updatetype": { + "type": "integer", + "description": "1:安卓APP,2:客户端,3:IOS APP", + "format": "int32", + "nullable": true + }, + "isActive": { + "type": "integer", + "description": "1启用 0停用", + "format": "int32", + "nullable": true + }, + "fileid": { + "type": "integer", + "description": "备 注:文件id\r\n默认值:", + "format": "int64", + "nullable": true + }, + "fileName": { + "type": "string", + "description": "原文件名", + "nullable": true + }, + "filePath": { + "type": "string", + "description": "文件路径", + "nullable": true + }, + "fileSize": { + "type": "integer", + "description": "备 注:文件大小(文件大小-kb,不足1kb为1kb)\r\n默认值:", + "format": "int64", + "nullable": true + } + }, + "additionalProperties": false + }, + "UserBooksResult": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Id", + "format": "int64" + }, + "userName": { + "type": "string", + "description": "用户姓名", + "nullable": true + }, + "headImage": { + "type": "string", + "description": "头像", + "nullable": true + }, + "roleEnum": { + "$ref": "#/components/schemas/SysRoleEnum" + }, + "phone": { + "type": "string", + "description": "电话", + "nullable": true + } + }, + "additionalProperties": false + }, + "UserFoundationResult": { + "type": "object", + "properties": { + "userName": { + "type": "string", + "description": "用户姓名", + "nullable": true + }, + "userId": { + "type": "integer", + "description": "用户id", + "format": "int64" + }, + "roleEnum": { + "$ref": "#/components/schemas/SysRoleEnum" + } + }, + "additionalProperties": false, + "description": "用户基本表" + }, + "UserResult": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "备 注:用户中心id\r\n默认值:", + "format": "int64" + }, + "realName": { + "type": "string", + "description": "备 注:姓名\r\n默认值:", + "nullable": true + }, + "account": { + "type": "string", + "description": "备 注:账号\r\n默认值:", + "nullable": true + }, + "roleEnum": { + "$ref": "#/components/schemas/SysRoleEnum" + }, + "cloudId": { + "type": "integer", + "description": "云校id", + "format": "int64", + "nullable": true + }, + "roleName": { + "type": "string", + "description": "角色名称", + "nullable": true + }, + "cloudName": { + "type": "string", + "description": "备 注:云校名称\r\n默认值:", + "nullable": true + }, + "phone": { + "type": "string", + "description": "备 注:手机号\r\n默认值:", + "nullable": true + }, + "headImage": { + "type": "string", + "description": "头像URL", + "nullable": true + } + }, + "additionalProperties": false + }, + "WeekModel": { + "type": "object", + "properties": { + "week": { + "type": "integer", + "description": "周", + "format": "int32" + }, + "weekDay": { + "type": "string", + "description": "", + "format": "date-time" + }, + "weekDayDetails": { + "type": "array", + "items": { + "$ref": "#/components/schemas/weekDayDetail" + }, + "description": "", + "nullable": true + } + }, + "additionalProperties": false + }, + "userLoginRequest": { + "type": "object", + "properties": { + "phone": { + "type": "string", + "description": "电话", + "nullable": true + } + }, + "additionalProperties": false + }, + "userLoginResult": { + "type": "object", + "properties": { + "token": { + "type": "string", + "description": "Token", + "nullable": true + }, + "userInfo": { + "$ref": "#/components/schemas/UserResult" + }, + "userSig": { + "type": "string", + "description": "UserSig", + "nullable": true + } + }, + "additionalProperties": false + }, + "weekDayDetail": { + "type": "object", + "properties": { + "classCourseId": { + "type": "integer", + "description": "课程id", + "format": "int64" + }, + "subject": { + "type": "integer", + "description": "科目id", + "format": "int32" + }, + "subjectName": { + "type": "string", + "description": "科目名称", + "nullable": true + }, + "teacherName": { + "type": "string", + "description": "云校老师", + "nullable": true + }, + "teacherId": { + "type": "integer", + "description": "云校老师id", + "format": "int64" + }, + "landingTeacherId": { + "type": "integer", + "description": "落地老师", + "format": "int64" + }, + "landingTeacherName": { + "type": "string", + "description": "落地老师", + "nullable": true + }, + "planStartTime": { + "type": "string", + "description": "上课时间", + "format": "date-time" + }, + "planEndTime": { + "type": "string", + "description": "下课时间", + "format": "date-time" + } + }, + "additionalProperties": false + } + }, + "securitySchemes": { + "Bearer": { + "type": "apiKey", + "description": "在下框中输入请求头中需要添加Jwt授权Token:Bearer {Token},注意中间有空格", + "name": "Authorization", + "in": "header" + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ], + "tags": [ + { + "name": "HealthCheck", + "description": "健康检查控制器" + }, + { + "name": "FollowManager", + "description": "工作台" + }, + { + "name": "Index", + "description": "首页控制器" + }, + { + "name": "Login", + "description": "登录" + }, + { + "name": "MobileManager", + "description": "移动端部长/组长管理" + }, + { + "name": "MyInfo", + "description": "我的" + }, + { + "name": "TaskInfo", + "description": "工作任务api" + }, + { + "name": "TaskSummarize", + "description": "总结任务报告api" + }, + { + "name": "TaskClassCadreMeeting", + "description": "开展班干部会议api" + }, + { + "name": "TaskClassesActivity", + "description": "班级活动" + }, + { + "name": "TaskClassMeeting", + "description": "召开班会任务" + }, + { + "name": "TaskCoachSub", + "description": "学科辅助" + }, + { + "name": "TaskCultural", + "description": "更新文创内容" + }, + { + "name": "TaskDataCollect", + "description": "任务数据采集控制器" + }, + { + "name": "TaskFollow", + "description": "双师课堂任务api" + }, + { + "name": "TaskMeeting", + "description": "参加会议任务api" + }, + { + "name": "TaskOther", + "description": "其他任务相关接口" + }, + { + "name": "TaskSolution", + "description": "任务-解决方案api" + }, + { + "name": "TaskSpot", + "description": "学习行为习惯全面抽查" + }, + { + "name": "TaskTalk", + "description": "一对一学生谈话任务api" + }, + { + "name": "TaskTeacherBehavior", + "description": "教师行为规范" + }, + { + "name": "TaskTeacherTalk", + "description": "教师谈话任务控制器" + } + ] +} \ No newline at end of file diff --git a/tests/comprehensive_generator_test.dart b/tests/comprehensive_generator_test.dart new file mode 100644 index 0000000..789d6ee --- /dev/null +++ b/tests/comprehensive_generator_test.dart @@ -0,0 +1,613 @@ +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'; + +void main() { + group('Comprehensive Generator Tests', () { + late SwaggerDocument testDocument; + + setUp(() { + testDocument = const SwaggerDocument( + title: 'Test API', + version: '1.0.0', + description: 'A comprehensive test API', + servers: [ + ApiServer( + url: 'https://api.example.com', + description: 'Production server', + ), + ], + components: ApiComponents( + schemas: {}, + securitySchemes: { + 'bearerAuth': const ApiSecurityScheme( + type: SecuritySchemeType.http, + description: 'Bearer token', + scheme: 'bearer', + bearerFormat: 'JWT', + ), + 'apiKey': const ApiSecurityScheme( + type: SecuritySchemeType.apiKey, + description: 'API Key', + name: 'X-API-Key', + location: ApiKeyLocation.header, + ), + }, + ), + paths: { + '/users': const ApiPath( + path: '/users', + method: HttpMethod.get, + summary: 'Get all users', + description: 'Retrieve a list of all users', + operationId: 'getUsers', + tags: ['users'], + parameters: [ + ApiParameter( + name: 'page', + location: ParameterLocation.query, + required: false, + type: PropertyType.integer, + description: 'Page number', + ), + ApiParameter( + name: 'limit', + location: ParameterLocation.query, + required: false, + type: PropertyType.integer, + description: 'Items per page', + ), + ], + responses: { + '200': const ApiResponse( + code: '200', + description: 'Successful response', + content: { + 'application/json': const ApiMediaType( + schema: { + 'type': 'array', + 'items': { + '\$ref': '#/components/schemas/User', + }, + }, + ), + }, + ), + '400': const ApiResponse( + code: '400', + description: 'Bad request', + ), + '401': const ApiResponse( + code: '401', + description: 'Unauthorized', + ), + }, + security: [ + ApiSecurityRequirement( + requirements: {'bearerAuth': []}, + ), + ], + ), + '/users/{id}': const ApiPath( + path: '/users/{id}', + method: HttpMethod.get, + summary: 'Get user by ID', + description: 'Retrieve a specific user by their ID', + operationId: 'getUserById', + tags: ['users'], + parameters: [ + ApiParameter( + name: 'id', + location: ParameterLocation.path, + required: true, + type: PropertyType.integer, + description: 'User ID', + ), + ], + responses: { + '200': const ApiResponse( + code: '200', + description: 'User found', + content: { + 'application/json': const ApiMediaType( + schema: { + '\$ref': '#/components/schemas/User', + }, + ), + }, + ), + '404': const ApiResponse( + code: '404', + description: 'User not found', + ), + }, + ), + '/users/create': const ApiPath( + path: '/users/create', + 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: { + '\$ref': '#/components/schemas/CreateUserRequest', + }, + ), + }, + ), + responses: { + '201': const ApiResponse( + code: '201', + description: 'User created', + content: { + 'application/json': const ApiMediaType( + schema: { + '\$ref': '#/components/schemas/User', + }, + ), + }, + ), + '400': const ApiResponse( + code: '400', + description: 'Invalid input', + ), + }, + ), + '/files/upload': const ApiPath( + path: '/files/upload', + method: HttpMethod.post, + summary: 'Upload file', + description: 'Upload a file to the server', + operationId: 'uploadFile', + tags: ['files'], + parameters: [], + requestBody: ApiRequestBody( + description: 'File to upload', + required: true, + content: { + 'multipart/form-data': const ApiMediaType( + schema: { + 'type': 'object', + 'properties': { + 'file': { + 'type': 'string', + 'format': 'binary', + }, + 'description': { + 'type': 'string', + }, + }, + }, + ), + }, + ), + responses: { + '200': const ApiResponse( + code: '200', + description: 'File uploaded successfully', + content: { + 'application/json': const ApiMediaType( + schema: { + '\$ref': '#/components/schemas/FileUploadResult', + }, + ), + }, + ), + }, + ), + }, + models: { + 'User': const ApiModel( + name: 'User', + description: 'User model', + properties: { + 'id': const ApiProperty( + name: 'id', + type: PropertyType.integer, + description: 'User ID', + required: true, + ), + 'name': const ApiProperty( + name: 'name', + type: PropertyType.string, + description: 'User name', + required: true, + ), + 'email': const ApiProperty( + name: 'email', + type: PropertyType.string, + description: 'User email', + required: true, + ), + 'createdAt': const ApiProperty( + name: 'createdAt', + type: PropertyType.string, + description: 'Creation timestamp', + required: false, + ), + }, + required: ['id', 'name', 'email'], + ), + 'CreateUserRequest': const ApiModel( + name: 'CreateUserRequest', + description: 'Request model for creating a user', + properties: { + 'name': const ApiProperty( + name: 'name', + type: PropertyType.string, + description: 'User name', + required: true, + ), + 'email': const ApiProperty( + name: 'email', + type: PropertyType.string, + description: 'User email', + required: true, + ), + }, + required: ['name', 'email'], + ), + 'FileUploadResult': const ApiModel( + name: 'FileUploadResult', + description: 'Result of file upload', + properties: { + 'url': const ApiProperty( + name: 'url', + type: PropertyType.string, + description: 'File URL', + required: true, + ), + 'filename': const ApiProperty( + name: 'filename', + type: PropertyType.string, + description: 'Original filename', + required: true, + ), + 'size': const ApiProperty( + name: 'size', + type: PropertyType.integer, + description: 'File size in bytes', + required: true, + ), + }, + required: ['url', 'filename', 'size'], + ), + }, + controllers: {}, + security: [ + ApiSecurityRequirement( + requirements: {'bearerAuth': []}, + ), + ], + ); + }); + + group('RetrofitApiGenerator', () { + test('generates basic Retrofit API', () { + final generator = RetrofitApiGenerator( + className: 'TestApiService', + splitByTags: false, + ); + + final result = generator.generateFromDocument(testDocument); + + expect(result, isNotEmpty); + expect(result, contains('abstract class TestApiService')); + expect(result, contains('@RestApi()')); + expect(result, contains('factory TestApiService(Dio dio')); + expect(result, contains('@GET(\'/users\')')); + expect(result, contains('@POST(\'/users\')')); + expect(result, contains('@Path(\'id\')')); + expect(result, contains('@Query(\'page\')')); + expect(result, contains('@Body()')); + }); + + test('generates split APIs by tags', () { + final generator = RetrofitApiGenerator( + className: 'ApiService', + splitByTags: true, + ); + + final result = generator.generateFromDocument(testDocument); + + expect(result, isNotEmpty); + expect(result, contains('UsersApi')); + expect(result, contains('FilesApi')); + expect(result, contains('class ApiService')); + expect(result, contains('late final UsersApi users')); + expect(result, contains('late final FilesApi files')); + }); + + test('handles file upload endpoints', () { + final generator = RetrofitApiGenerator(); + + final result = generator.generateFromDocument(testDocument); + + expect(result, contains('@POST(\'/files/upload\')')); + expect(result, contains('@MultiPart()')); + expect(result, contains('MultipartFile')); + }); + + test('generates proper parameter annotations', () { + final generator = RetrofitApiGenerator(); + + final result = generator.generateFromDocument(testDocument); + + // Path parameters + expect(result, contains('@Path(\'id\') int id')); + + // Query parameters + expect(result, contains('@Query(\'page\') int? page')); + expect(result, contains('@Query(\'limit\') int? limit')); + + // Body parameters + expect(result, contains('@Body() CreateUserRequest body')); + }); + + test('generates security annotations', () { + final generator = RetrofitApiGenerator(); + + final result = generator.generateFromDocument(testDocument); + + // Should include headers for authentication + expect( + result, anyOf([contains('Authorization'), contains('X-API-Key')])); + }); + }); + + 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')); + expect(result, contains('class BasePageResult')); + 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(); + final result = generator.generateFromDocument(testDocument); + + // Basic syntax checks + expect(result, isNot(contains(';;'))); // No double semicolons + expect(result, isNot(contains(',,'))); // No double commas + expect(result, + isNot(contains(' '))); // No double spaces (basic formatting) + + // Check for proper imports + expect(result, contains('import \'package:dio/dio.dart\';')); + expect(result, contains('import \'package:retrofit/retrofit.dart\';')); + + // Check for proper class structure + final classMatches = RegExp(r'class \w+').allMatches(result); + final abstractClassMatches = + RegExp(r'abstract class \w+').allMatches(result); + expect( + classMatches.length + abstractClassMatches.length, greaterThan(0)); + }); + + test('handles special characters in names', () { + const specialDocument = SwaggerDocument( + title: 'API with Special-Characters_and.dots', + version: '1.0.0', + description: 'Test API', + servers: [], + components: ApiComponents(schemas: {}, securitySchemes: {}), + paths: { + '/special-endpoint_with.dots': const ApiPath( + path: '/special-endpoint_with.dots', + method: HttpMethod.get, + summary: 'Special endpoint', + description: 'Endpoint with special characters', + operationId: 'getSpecialEndpoint', + tags: ['special-tag_with.dots'], + parameters: [], + responses: { + '200': const ApiResponse( + code: '200', + description: 'Success', + ), + }, + ), + }, + models: {}, + controllers: {}, + security: [], + ); + + final generator = RetrofitApiGenerator(); + final result = generator.generateFromDocument(specialDocument); + + expect(result, isNotEmpty); + // Should handle special characters in class names + 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); + + // Required path parameters should not be nullable + expect(result, contains('@Path(\'id\') int id')); + + // Optional query parameters should be nullable + expect(result, contains('@Query(\'page\') int? page')); + expect(result, contains('@Query(\'limit\') int? limit')); + }); + }); + + group('Error Handling', () { + test('handles empty document gracefully', () { + const emptyDocument = SwaggerDocument( + title: 'Empty API', + version: '1.0.0', + description: 'Empty test API', + servers: [], + components: ApiComponents(schemas: {}, securitySchemes: {}), + paths: {}, + models: {}, + controllers: {}, + security: [], + ); + + final generator = RetrofitApiGenerator(); + final result = generator.generateFromDocument(emptyDocument); + + expect(result, isNotEmpty); + expect(result, contains('Empty API')); + // Should still generate basic structure even with no paths + }); + + test('handles missing operation IDs', () { + const documentWithoutOperationIds = SwaggerDocument( + title: 'Test API', + version: '1.0.0', + description: 'Test', + servers: [], + components: ApiComponents(schemas: {}, securitySchemes: {}), + paths: { + '/test': const ApiPath( + path: '/test', + method: HttpMethod.get, + summary: 'Test endpoint', + description: 'Test', + operationId: '', // Empty operation ID + tags: [], + parameters: [], + responses: { + '200': const ApiResponse( + code: '200', + description: 'Success', + ), + }, + ), + }, + models: {}, + controllers: {}, + security: [], + ); + + final generator = RetrofitApiGenerator(); + expect( + () => generator.generateFromDocument(documentWithoutOperationIds), + returnsNormally); + }); + }); + }); +} diff --git a/tests/comprehensive_parser_test.dart b/tests/comprehensive_parser_test.dart new file mode 100644 index 0000000..d1fe4d5 --- /dev/null +++ b/tests/comprehensive_parser_test.dart @@ -0,0 +1,623 @@ +import 'package:swagger_generator_flutter/core/models.dart'; +import 'package:test/test.dart'; + +void main() { + group('Comprehensive Parser Tests', () { + group('OpenAPI 3.0 Core Features', () { + test('parses basic OpenAPI 3.0 document', () { + final json = { + 'openapi': '3.0.3', + 'info': { + 'title': 'Test API', + 'version': '1.0.0', + 'description': 'A test API', + }, + 'servers': [ + { + 'url': 'https://api.example.com', + 'description': 'Production server', + }, + ], + 'paths': { + '/users': { + 'get': { + 'summary': 'Get users', + 'operationId': 'getUsers', + 'responses': { + '200': { + 'description': 'Success', + 'content': { + 'application/json': { + 'schema': { + 'type': 'array', + 'items': { + '\$ref': '#/components/schemas/User', + }, + }, + }, + }, + }, + }, + }, + }, + }, + 'components': { + 'schemas': { + 'User': { + 'type': 'object', + 'properties': { + 'id': { + 'type': 'integer', + 'format': 'int64', + }, + 'name': { + 'type': 'string', + }, + }, + 'required': ['id', 'name'], + }, + }, + }, + }; + + final document = SwaggerDocument.fromJson(json); + + expect(document.title, equals('Test API')); + expect(document.version, equals('1.0.0')); + expect(document.description, equals('A test API')); + expect(document.servers, hasLength(1)); + expect(document.servers.first.url, equals('https://api.example.com')); + expect(document.paths, hasLength(1)); + expect(document.models, hasLength(1)); + expect(document.models.containsKey('User'), isTrue); + }); + + test('parses servers with variables', () { + final json = { + 'openapi': '3.0.3', + 'info': {'title': 'Test', 'version': '1.0.0'}, + 'servers': [ + { + 'url': 'https://{environment}.example.com/{basePath}', + 'description': 'Configurable server', + 'variables': { + 'environment': { + 'default': 'api', + 'enum': ['api', 'staging', 'dev'], + 'description': 'Environment name', + }, + 'basePath': { + 'default': 'v1', + 'description': 'API version', + }, + }, + }, + ], + 'paths': {}, + }; + + final document = SwaggerDocument.fromJson(json); + final server = document.servers.first; + + expect( + server.url, equals('https://{environment}.example.com/{basePath}')); + expect(server.variables, hasLength(2)); + expect(server.variables.containsKey('environment'), isTrue); + expect(server.variables['environment']!.defaultValue, equals('api')); + expect(server.variables['environment']!.enumValues, hasLength(3)); + }); + + test('parses complex request body', () { + final json = { + 'openapi': '3.0.3', + 'info': {'title': 'Test', 'version': '1.0.0'}, + 'paths': { + '/users': { + 'post': { + 'summary': 'Create user', + 'requestBody': { + 'description': 'User to create', + 'required': true, + 'content': { + 'application/json': { + 'schema': { + '\$ref': '#/components/schemas/User', + }, + 'examples': { + 'user1': { + 'summary': 'Example user', + 'value': { + 'name': 'John Doe', + 'email': 'john@example.com', + }, + }, + }, + }, + 'application/xml': { + 'schema': { + '\$ref': '#/components/schemas/User', + }, + }, + }, + }, + 'responses': { + '201': {'description': 'Created'}, + }, + }, + }, + }, + 'components': { + 'schemas': { + 'User': { + 'type': 'object', + 'properties': { + 'name': {'type': 'string'}, + 'email': {'type': 'string'}, + }, + }, + }, + }, + }; + + final document = SwaggerDocument.fromJson(json); + final path = document.paths['/users']!; + + expect(path.requestBody, isNotNull); + expect(path.requestBody!.required, isTrue); + expect(path.requestBody!.content, hasLength(2)); + expect( + path.requestBody!.content.containsKey('application/json'), isTrue); + expect( + path.requestBody!.content.containsKey('application/xml'), isTrue); + + final jsonContent = path.requestBody!.content['application/json']!; + expect(jsonContent.examples, hasLength(1)); + expect(jsonContent.examples.containsKey('user1'), isTrue); + }); + + test('parses complex responses with headers and links', () { + final json = { + 'openapi': '3.0.3', + 'info': {'title': 'Test', 'version': '1.0.0'}, + 'paths': { + '/users/{id}': { + 'get': { + 'summary': 'Get user', + 'parameters': [ + { + 'name': 'id', + 'in': 'path', + 'required': true, + 'schema': {'type': 'integer'}, + }, + ], + 'responses': { + '200': { + 'description': 'User found', + 'headers': { + 'X-Rate-Limit': { + 'description': 'Rate limit', + 'schema': {'type': 'integer'}, + }, + }, + 'content': { + 'application/json': { + 'schema': { + '\$ref': '#/components/schemas/User', + }, + }, + }, + 'links': { + 'getUserPosts': { + 'operationId': 'getUserPosts', + 'parameters': { + 'userId': '\$response.body#/id', + }, + }, + }, + }, + '404': { + 'description': 'User not found', + }, + }, + }, + }, + }, + 'components': { + 'schemas': { + 'User': { + 'type': 'object', + 'properties': { + 'id': {'type': 'integer'}, + 'name': {'type': 'string'}, + }, + }, + }, + }, + }; + + final document = SwaggerDocument.fromJson(json); + final path = document.paths['/users/{id}']!; + + expect(path.parameters, hasLength(1)); + expect(path.parameters.first.name, equals('id')); + expect(path.parameters.first.location, equals(ParameterLocation.path)); + expect(path.parameters.first.required, isTrue); + + expect(path.responses, hasLength(2)); + final successResponse = path.responses['200']!; + expect(successResponse.headers, hasLength(1)); + expect(successResponse.headers.containsKey('X-Rate-Limit'), isTrue); + expect(successResponse.links, hasLength(1)); + expect(successResponse.links.containsKey('getUserPosts'), isTrue); + }); + + test('parses security schemes and requirements', () { + final json = { + 'openapi': '3.0.3', + 'info': {'title': 'Test', 'version': '1.0.0'}, + 'security': [ + {'bearerAuth': []}, + {'apiKey': []}, + ], + 'paths': { + '/protected': { + 'get': { + 'summary': 'Protected endpoint', + 'security': [ + { + 'bearerAuth': ['read:users'] + }, + ], + 'responses': { + '200': {'description': 'Success'}, + }, + }, + }, + }, + 'components': { + 'securitySchemes': { + 'bearerAuth': { + 'type': 'http', + 'scheme': 'bearer', + 'bearerFormat': 'JWT', + }, + 'apiKey': { + 'type': 'apiKey', + 'in': 'header', + 'name': 'X-API-Key', + }, + 'oauth2': { + 'type': 'oauth2', + 'flows': { + 'authorizationCode': { + 'authorizationUrl': 'https://example.com/oauth/authorize', + 'tokenUrl': 'https://example.com/oauth/token', + 'scopes': { + 'read:users': 'Read user data', + 'write:users': 'Write user data', + }, + }, + }, + }, + }, + }, + }; + + final document = SwaggerDocument.fromJson(json); + + expect(document.security, hasLength(2)); + expect(document.components.securitySchemes, hasLength(3)); + + final bearerAuth = document.components.securitySchemes['bearerAuth']!; + expect(bearerAuth.type, equals(SecuritySchemeType.http)); + expect(bearerAuth.scheme, equals('bearer')); + expect(bearerAuth.bearerFormat, equals('JWT')); + + final apiKey = document.components.securitySchemes['apiKey']!; + expect(apiKey.type, equals(SecuritySchemeType.apiKey)); + expect(apiKey.location, equals(ApiKeyLocation.header)); + expect(apiKey.name, equals('X-API-Key')); + + final oauth2 = document.components.securitySchemes['oauth2']!; + expect(oauth2.type, equals(SecuritySchemeType.oauth2)); + expect(oauth2.flows, isNotNull); + expect(oauth2.flows!.authorizationCode, isNotNull); + expect(oauth2.flows!.authorizationCode!.scopes, hasLength(2)); + + final path = document.paths['/protected']!; + expect(path.security, hasLength(1)); + expect(path.security.first.schemeNames, contains('bearerAuth')); + }); + }); + + group('Schema Validation', () { + test('parses allOf composition', () { + final json = { + 'openapi': '3.0.3', + 'info': {'title': 'Test', 'version': '1.0.0'}, + 'paths': {}, + 'components': { + 'schemas': { + 'Pet': { + 'type': 'object', + 'properties': { + 'name': {'type': 'string'}, + }, + 'required': ['name'], + }, + 'Dog': { + 'allOf': [ + {'\$ref': '#/components/schemas/Pet'}, + { + 'type': 'object', + 'properties': { + 'breed': {'type': 'string'}, + }, + 'required': ['breed'], + }, + ], + }, + }, + }, + }; + + final document = SwaggerDocument.fromJson(json); + expect(document.models, hasLength(2)); + expect(document.models.containsKey('Pet'), isTrue); + expect(document.models.containsKey('Dog'), isTrue); + + final dog = document.models['Dog']!; + expect(dog.name, equals('Dog')); + // allOf 处理逻辑会在实际实现中更复杂 + }); + + test('parses oneOf and anyOf', () { + final json = { + 'openapi': '3.0.3', + 'info': {'title': 'Test', 'version': '1.0.0'}, + 'paths': {}, + 'components': { + 'schemas': { + 'StringOrNumber': { + 'oneOf': [ + {'type': 'string'}, + {'type': 'number'}, + ], + }, + 'FlexibleType': { + 'anyOf': [ + {'type': 'string'}, + {'type': 'integer'}, + {'type': 'boolean'}, + ], + }, + }, + }, + }; + + final document = SwaggerDocument.fromJson(json); + expect(document.models, hasLength(2)); + expect(document.models.containsKey('StringOrNumber'), isTrue); + expect(document.models.containsKey('FlexibleType'), isTrue); + }); + + test('parses discriminator', () { + final json = { + 'openapi': '3.0.3', + 'info': {'title': 'Test', 'version': '1.0.0'}, + 'paths': {}, + 'components': { + 'schemas': { + 'Pet': { + 'type': 'object', + 'discriminator': { + 'propertyName': 'petType', + 'mapping': { + 'dog': '#/components/schemas/Dog', + 'cat': '#/components/schemas/Cat', + }, + }, + 'properties': { + 'petType': {'type': 'string'}, + 'name': {'type': 'string'}, + }, + 'required': ['petType', 'name'], + }, + 'Dog': { + 'allOf': [ + {'\$ref': '#/components/schemas/Pet'}, + { + 'type': 'object', + 'properties': { + 'breed': {'type': 'string'}, + }, + }, + ], + }, + 'Cat': { + 'allOf': [ + {'\$ref': '#/components/schemas/Pet'}, + { + 'type': 'object', + 'properties': { + 'color': {'type': 'string'}, + }, + }, + ], + }, + }, + }, + }; + + final document = SwaggerDocument.fromJson(json); + expect(document.models, hasLength(3)); + + final pet = document.models['Pet']!; + expect(pet.name, equals('Pet')); + // discriminator 处理逻辑会在实际实现中更复杂 + }); + }); + + group('Error Handling', () { + test('handles missing required fields gracefully', () { + final json = { + 'openapi': '3.0.3', + // Missing info object + 'paths': {}, + }; + + expect(() => SwaggerDocument.fromJson(json), + throwsA(isA())); + }); + + test('handles invalid OpenAPI version', () { + final json = { + 'openapi': '2.0', // Invalid version + 'info': {'title': 'Test', 'version': '1.0.0'}, + 'paths': {}, + }; + + // Should still parse but might have warnings + expect(() => SwaggerDocument.fromJson(json), returnsNormally); + }); + + test('handles malformed paths', () { + final json = { + 'openapi': '3.0.3', + 'info': {'title': 'Test', 'version': '1.0.0'}, + 'paths': { + '/valid': { + 'get': { + 'responses': { + '200': {'description': 'OK'} + }, + }, + }, + '/invalid': { + 'invalidMethod': { + 'responses': { + '200': {'description': 'OK'} + }, + }, + }, + }, + }; + + final document = SwaggerDocument.fromJson(json); + // Should parse valid paths and skip invalid ones + expect(document.paths.length, greaterThanOrEqualTo(1)); + }); + + test('handles circular references', () { + final json = { + 'openapi': '3.0.3', + 'info': {'title': 'Test', 'version': '1.0.0'}, + 'paths': {}, + 'components': { + 'schemas': { + 'Node': { + 'type': 'object', + 'properties': { + 'value': {'type': 'string'}, + 'children': { + 'type': 'array', + 'items': {'\$ref': '#/components/schemas/Node'}, + }, + }, + }, + }, + }, + }; + + // Should handle circular references without infinite recursion + expect(() => SwaggerDocument.fromJson(json), returnsNormally); + final document = SwaggerDocument.fromJson(json); + expect(document.models.containsKey('Node'), isTrue); + }); + }); + + group('Edge Cases', () { + test('handles empty document', () { + final json = { + 'openapi': '3.0.3', + 'info': {'title': 'Empty API', 'version': '1.0.0'}, + 'paths': {}, + }; + + final document = SwaggerDocument.fromJson(json); + expect(document.title, equals('Empty API')); + expect(document.paths, isEmpty); + expect(document.models, isEmpty); + }); + + test('handles very large documents', () { + final paths = {}; + final schemas = {}; + + // Create a large number of paths and schemas + for (int i = 0; i < 1000; i++) { + paths['/resource$i'] = { + 'get': { + 'summary': 'Get resource $i', + 'responses': { + '200': {'description': 'Success'} + }, + }, + }; + + schemas['Model$i'] = { + 'type': 'object', + 'properties': { + 'id': {'type': 'integer'}, + 'name': {'type': 'string'}, + }, + }; + } + + final json = { + 'openapi': '3.0.3', + 'info': {'title': 'Large API', 'version': '1.0.0'}, + 'paths': paths, + 'components': {'schemas': schemas}, + }; + + final stopwatch = Stopwatch()..start(); + final document = SwaggerDocument.fromJson(json); + stopwatch.stop(); + + expect(document.paths.length, greaterThan(500)); + expect(document.models.length, greaterThan(500)); + expect(stopwatch.elapsedMilliseconds, + lessThan(10000)); // Should complete within 10 seconds + }); + + test('handles unicode and special characters', () { + final json = { + 'openapi': '3.0.3', + 'info': { + 'title': 'API with 中文 and émojis 🚀', + 'version': '1.0.0', + 'description': 'Supports unicode: αβγ, 日本語, العربية', + }, + 'paths': { + '/测试': { + 'get': { + 'summary': 'Test with unicode path', + 'responses': { + '200': {'description': 'Success'} + }, + }, + }, + }, + }; + + final document = SwaggerDocument.fromJson(json); + expect(document.title, contains('中文')); + expect(document.title, contains('🚀')); + expect(document.description, contains('日本語')); + expect(document.paths.containsKey('/测试'), isTrue); + }); + }); + }); +} diff --git a/tests/encoding_test.dart b/tests/encoding_test.dart new file mode 100644 index 0000000..104fc13 --- /dev/null +++ b/tests/encoding_test.dart @@ -0,0 +1,217 @@ +import 'dart:convert'; +import 'dart:io'; +import 'package:test/test.dart'; + +void main() { + group('Encoding Support', () { + test('handles UTF-8 encoding', () { + const testString = 'Hello, 世界! 🌍'; + final encoded = utf8.encode(testString); + final decoded = utf8.decode(encoded); + + expect(decoded, testString); + expect( + encoded.length, + greaterThan( + testString.length)); // UTF-8 uses multiple bytes for non-ASCII + }); + + test('handles ASCII encoding', () { + const testString = 'Hello, World!'; + final encoded = ascii.encode(testString); + final decoded = ascii.decode(encoded); + + expect(decoded, testString); + expect( + encoded.length, testString.length); // ASCII is 1 byte per character + }); + + test('handles Latin1 encoding', () { + const testString = 'Café'; + final encoded = latin1.encode(testString); + final decoded = latin1.decode(encoded); + + expect(decoded, testString); + }); + + test('handles Base64 encoding and decoding', () { + const testString = 'Hello, World!'; + final bytes = utf8.encode(testString); + final encoded = base64Encode(bytes); + final decoded = base64Decode(encoded); + final result = utf8.decode(decoded); + + expect(result, testString); + expect(encoded, 'SGVsbG8sIFdvcmxkIQ=='); + }); + + test('handles URL encoding and decoding', () { + const testString = 'Hello World & Special Characters!@#\$%^&*()'; + final encoded = Uri.encodeComponent(testString); + final decoded = Uri.decodeComponent(encoded); + + expect(decoded, testString); + expect(encoded, contains('%20')); // Space should be encoded as %20 + expect(encoded, contains('%26')); // & should be encoded as %26 + }); + + test('detects BOM for UTF-8', () { + final utf8Bom = [0xEF, 0xBB, 0xBF]; + final testBytes = utf8Bom + utf8.encode('Hello'); + + // 检测 BOM + final bool hasUtf8Bom = testBytes.length >= 3 && + testBytes[0] == 0xEF && + testBytes[1] == 0xBB && + testBytes[2] == 0xBF; + + expect(hasUtf8Bom, true); + }); + + test('detects BOM for UTF-16LE', () { + final utf16leBom = [0xFF, 0xFE]; + + final bool hasUtf16LeBom = utf16leBom.length >= 2 && + utf16leBom[0] == 0xFF && + utf16leBom[1] == 0xFE; + + expect(hasUtf16LeBom, true); + }); + + test('detects BOM for UTF-16BE', () { + final utf16beBom = [0xFE, 0xFF]; + + final bool hasUtf16BeBom = utf16beBom.length >= 2 && + utf16beBom[0] == 0xFE && + utf16beBom[1] == 0xFF; + + expect(hasUtf16BeBom, true); + }); + + test('handles chunked transfer encoding format', () { + // 模拟分块传输编码的数据格式 + const chunkData = '5\r\nHello\r\n5\r\nWorld\r\n0\r\n\r\n'; + final bytes = ascii.encode(chunkData); + + // 简单的分块解码逻辑测试 + final result = []; + var offset = 0; + + while (offset < bytes.length) { + // 查找块大小行的结束 + var lineEnd = offset; + while (lineEnd < bytes.length - 1) { + if (bytes[lineEnd] == 13 && bytes[lineEnd + 1] == 10) break; // \r\n + lineEnd++; + } + if (lineEnd >= bytes.length - 1) break; + + // 解析块大小 + final sizeHex = String.fromCharCodes(bytes.sublist(offset, lineEnd)); + final chunkSize = int.tryParse(sizeHex, radix: 16) ?? 0; + if (chunkSize == 0) break; // 最后一个块 + + // 跳过 \r\n + offset = lineEnd + 2; + + // 读取块数据 + if (offset + chunkSize <= bytes.length) { + result.addAll(bytes.sublist(offset, offset + chunkSize)); + offset += chunkSize + 2; // 跳过块数据后的 \r\n + } else { + break; + } + } + + final decodedString = ascii.decode(result); + expect(decodedString, 'HelloWorld'); + }); + + test('validates encoding compatibility', () { + const testString = 'Hello, World!'; + + // UTF-8 应该能处理任何字符串 + expect(() => utf8.encode(testString), returnsNormally); + + // ASCII 只能处理 ASCII 字符 + expect(() => ascii.encode(testString), returnsNormally); + + // 测试非 ASCII 字符 + const nonAsciiString = 'Hello, 世界!'; + expect(() => utf8.encode(nonAsciiString), returnsNormally); + expect(() => ascii.encode(nonAsciiString), throwsA(isA())); + }); + + test('handles different content encodings', () { + const testData = 'Hello, World!'; + final originalBytes = utf8.encode(testData); + + // 测试 gzip 压缩和解压 + final gzipCompressed = gzip.encode(originalBytes); + final gzipDecompressed = gzip.decode(gzipCompressed); + expect(utf8.decode(gzipDecompressed), testData); + + // 测试 zlib 压缩和解压 + final zlibCompressed = zlib.encode(originalBytes); + final zlibDecompressed = zlib.decode(zlibCompressed); + expect(utf8.decode(zlibDecompressed), testData); + }); + + test('handles form URL encoding', () { + final formData = { + 'name': 'John Doe', + 'email': 'john@example.com', + 'message': 'Hello & welcome!', + }; + + final encodedPairs = []; + formData.forEach((key, value) { + final encodedKey = Uri.encodeComponent(key); + final encodedValue = Uri.encodeComponent(value.toString()); + encodedPairs.add('$encodedKey=$encodedValue'); + }); + + final encoded = encodedPairs.join('&'); + expect(encoded, contains('name=John%20Doe')); + expect(encoded, contains('email=john%40example.com')); + expect(encoded, contains('message=Hello%20%26%20welcome!')); + }); + + test('handles binary data encoding', () { + // 创建一些二进制数据 + final binaryData = List.generate(256, (i) => i); + + // Base64 编码 + final base64Encoded = base64Encode(binaryData); + final base64Decoded = base64Decode(base64Encoded); + expect(base64Decoded, binaryData); + + // 验证 Base64 编码的特征 + expect(base64Encoded.length % 4, 0); // Base64 长度应该是 4 的倍数 + expect(RegExp(r'^[A-Za-z0-9+/]*={0,2}$').hasMatch(base64Encoded), true); + }); + + test('handles mixed encoding scenarios', () { + // 模拟真实场景:JSON 数据包含多种字符 + final jsonData = { + 'name': 'José María', + 'description': 'Café & Restaurant 🍽️', + 'price': 29.99, + 'tags': ['food', 'café', '美食'], + }; + + final jsonString = jsonEncode(jsonData); + final utf8Bytes = utf8.encode(jsonString); + final base64Encoded = base64Encode(utf8Bytes); + + // 解码过程 + final decodedBytes = base64Decode(base64Encoded); + final decodedString = utf8.decode(decodedBytes); + final decodedJson = jsonDecode(decodedString) as Map; + + expect(decodedJson['name'], 'José María'); + expect(decodedJson['description'], 'Café & Restaurant 🍽️'); + expect(decodedJson['tags'], ['food', 'café', '美食']); + }); + }); +} diff --git a/tests/enhanced_validator_test.dart b/tests/enhanced_validator_test.dart new file mode 100644 index 0000000..1fdd1cb --- /dev/null +++ b/tests/enhanced_validator_test.dart @@ -0,0 +1,476 @@ +import 'package:swagger_generator_flutter/core/error_reporter.dart'; +import 'package:swagger_generator_flutter/core/models.dart'; +import 'package:swagger_generator_flutter/validators/enhanced_validator.dart'; +import 'package:test/test.dart'; + +void main() { + group('EnhancedValidator', () { + late EnhancedValidator validator; + + setUp(() { + validator = EnhancedValidator( + includeWarnings: true, + ); + }); + + test('validates valid document successfully', () { + const document = SwaggerDocument( + title: 'Test API', + version: '1.0.0', + description: 'A test API', + servers: [ + ApiServer( + url: 'https://api.example.com', + description: 'Production server', + ), + ], + components: ApiComponents( + schemas: {}, + securitySchemes: {}, + ), + paths: { + '/users': const ApiPath( + path: '/users', + method: HttpMethod.get, + summary: 'Get users', + description: 'Retrieve all users', + operationId: 'getUsers', + tags: ['users'], + parameters: [], + responses: { + '200': const ApiResponse( + code: '200', + description: 'Success', + content: { + 'application/json': const ApiMediaType( + schema: {'type': 'array'}, + ), + }, + ), + }, + ), + }, + models: {}, + controllers: {}, + security: [], + ); + + final isValid = validator.validateDocument(document); + expect(isValid, true); + expect(validator.errorReporter.hasErrorsOrCritical, false); + }); + + test('detects missing required fields', () { + const document = SwaggerDocument( + title: '', // Missing title + version: '', // Missing version + description: '', + servers: [], + components: ApiComponents(schemas: {}, securitySchemes: {}), + paths: {}, // Empty paths + models: {}, + controllers: {}, + security: [], + ); + + final isValid = validator.validateDocument(document); + expect(isValid, false); + expect(validator.errorReporter.hasErrorsOrCritical, true); + + final errors = validator.errorReporter.errors; + expect(errors.any((e) => e.id == 'MISSING_INFO_TITLE'), true); + expect(errors.any((e) => e.id == 'MISSING_INFO_VERSION'), true); + expect(errors.any((e) => e.id == 'EMPTY_PATHS'), true); + }); + + test('validates path parameters correctly', () { + const document = SwaggerDocument( + title: 'Test API', + version: '1.0.0', + description: 'Test', + servers: [], + components: ApiComponents(schemas: {}, securitySchemes: {}), + paths: { + '/users/{id}': const ApiPath( + path: '/users/{id}', + method: HttpMethod.get, + summary: 'Get user', + description: 'Get user by ID', + operationId: 'getUser', + tags: ['users'], + parameters: [ + // Missing path parameter declaration for 'id' + ], + responses: { + '200': const ApiResponse( + code: '200', + description: 'Success', + ), + }, + ), + }, + models: {}, + controllers: {}, + security: [], + ); + + final isValid = validator.validateDocument(document); + expect(isValid, false); + + final errors = validator.errorReporter.errors; + expect(errors.any((e) => e.id == 'UNDECLARED_PATH_PARAMETER'), true); + }); + + test('validates path parameter requirements', () { + const document = SwaggerDocument( + title: 'Test API', + version: '1.0.0', + description: 'Test', + servers: [], + components: ApiComponents(schemas: {}, securitySchemes: {}), + paths: { + '/users/{id}': const ApiPath( + path: '/users/{id}', + method: HttpMethod.get, + summary: 'Get user', + description: 'Get user by ID', + operationId: 'getUser', + tags: ['users'], + parameters: [ + ApiParameter( + name: 'id', + location: ParameterLocation.path, + required: false, // Path parameters must be required + type: PropertyType.integer, + description: 'User ID', + ), + ], + responses: { + '200': const ApiResponse( + code: '200', + description: 'Success', + ), + }, + ), + }, + models: {}, + controllers: {}, + security: [], + ); + + final isValid = validator.validateDocument(document); + expect(isValid, false); + + final errors = validator.errorReporter.errors; + expect(errors.any((e) => e.id == 'PATH_PARAMETER_NOT_REQUIRED'), true); + }); + + test('validates security schemes', () { + const document = SwaggerDocument( + title: 'Test API', + version: '1.0.0', + description: 'Test', + servers: [], + components: ApiComponents( + schemas: {}, + securitySchemes: { + 'apiKey': const ApiSecurityScheme( + type: SecuritySchemeType.apiKey, + description: 'API Key', + name: '', // Missing name + location: ApiKeyLocation.header, + ), + 'bearer': const ApiSecurityScheme( + type: SecuritySchemeType.http, + description: 'Bearer token', + scheme: '', // Missing scheme + ), + }, + ), + paths: { + '/test': const ApiPath( + path: '/test', + method: HttpMethod.get, + summary: 'Test', + description: 'Test endpoint', + operationId: 'test', + tags: [], + parameters: [], + responses: { + '200': const ApiResponse( + code: '200', + description: 'Success', + ), + }, + ), + }, + models: {}, + controllers: {}, + security: [], + ); + + final isValid = validator.validateDocument(document); + expect(isValid, false); + + final errors = validator.errorReporter.errors; + expect(errors.any((e) => e.id == 'MISSING_API_KEY_NAME'), true); + expect(errors.any((e) => e.id == 'MISSING_HTTP_SCHEME'), true); + }); + + test('generates warnings for missing optional fields', () { + const document = SwaggerDocument( + title: 'Test API', + version: '1.0.0', + description: '', // Missing description + servers: [], // Missing servers + components: ApiComponents(schemas: {}, securitySchemes: {}), + paths: { + '/test': const ApiPath( + path: '/test', + method: HttpMethod.get, + summary: '', // Missing summary + description: 'Test endpoint', + operationId: '', // Missing operationId + tags: [], + parameters: [], + responses: { + '200': const ApiResponse( + code: '200', + description: '', // Missing response description + ), + }, + ), + }, + models: {}, + controllers: {}, + security: [], + ); + + final isValid = validator.validateDocument(document); + expect(isValid, true); // Should be valid but with warnings + + final warnings = + validator.errorReporter.getErrorsBySeverity(ErrorSeverity.warning); + expect(warnings.isNotEmpty, true); + expect(warnings.any((w) => w.id == 'MISSING_INFO_DESCRIPTION'), true); + expect(warnings.any((w) => w.id == 'MISSING_SERVERS'), true); + expect(warnings.any((w) => w.id == 'MISSING_OPERATION_ID'), true); + expect(warnings.any((w) => w.id == 'MISSING_RESPONSE_DESCRIPTION'), true); + }); + + test('validates responses correctly', () { + const document = SwaggerDocument( + title: 'Test API', + version: '1.0.0', + description: 'Test', + servers: [], + components: ApiComponents(schemas: {}, securitySchemes: {}), + paths: { + '/test': const ApiPath( + path: '/test', + method: HttpMethod.get, + summary: 'Test', + description: 'Test endpoint', + operationId: 'test', + tags: [], + parameters: [], + responses: {}, // Missing responses + ), + }, + models: {}, + controllers: {}, + security: [], + ); + + final isValid = validator.validateDocument(document); + expect(isValid, false); + + final errors = validator.errorReporter.errors; + expect(errors.any((e) => e.id == 'MISSING_OPERATION_RESPONSES'), true); + }); + + test('checks for best practices', () { + const document = SwaggerDocument( + title: 'Test API', + version: '1.0.0', + description: 'Test API', + servers: [ApiServer(url: 'https://api.example.com')], + components: ApiComponents(schemas: {}, securitySchemes: {}), + paths: { + '/test': const ApiPath( + path: '/test', + method: HttpMethod.get, + summary: 'Test', + description: 'Test endpoint', + operationId: 'test', + tags: [], // No tags + parameters: [], + responses: { + '200': const ApiResponse( + code: '200', + description: 'Success', + ), + // No error responses + }, + ), + }, + models: {}, + controllers: {}, + security: [], + ); + + final isValid = validator.validateDocument(document); + expect(isValid, true); + + final infos = + validator.errorReporter.getErrorsBySeverity(ErrorSeverity.info); + expect(infos.any((i) => i.id == 'NO_OPERATION_TAGS'), true); + expect(infos.any((i) => i.id == 'NO_ERROR_RESPONSE'), true); + }); + + test('validates large schemas', () { + // Create a model with many properties + final properties = {}; + for (int i = 0; i < 25; i++) { + properties['property$i'] = ApiProperty( + name: 'property$i', + type: PropertyType.string, + description: 'Property $i', + required: false, + ); + } + + final largeModel = ApiModel( + name: 'LargeModel', + description: 'A model with many properties', + properties: properties, + required: [], + ); + + final document = SwaggerDocument( + title: 'Test API', + version: '1.0.0', + description: 'Test API', + servers: [const ApiServer(url: 'https://api.example.com')], + components: ApiComponents( + schemas: {'LargeModel': largeModel}, + securitySchemes: {}, + ), + paths: { + '/test': const ApiPath( + path: '/test', + method: HttpMethod.get, + summary: 'Test', + description: 'Test endpoint', + operationId: 'test', + tags: ['test'], + parameters: [], + responses: { + '200': const ApiResponse( + code: '200', + description: 'Success', + ), + }, + ), + }, + models: {'LargeModel': largeModel}, + controllers: {}, + security: [], + ); + + final isValid = validator.validateDocument(document); + expect(isValid, true); + + final infos = + validator.errorReporter.getErrorsBySeverity(ErrorSeverity.info); + expect(infos.any((i) => i.id == 'LARGE_SCHEMA_OBJECT'), true); + }); + + test('generates detailed error report', () { + const document = SwaggerDocument( + title: '', + version: '', + description: '', + servers: [], + components: ApiComponents(schemas: {}, securitySchemes: {}), + paths: {}, + models: {}, + controllers: {}, + security: [], + ); + + validator.validateDocument(document); + + final report = validator.errorReporter.generateReport(); + expect(report, isNotEmpty); + expect(report, contains('Error Summary')); + expect(report, contains('Missing API Title')); + expect(report, contains('Missing API Version')); + expect(report, contains('Empty Paths Object')); + }); + + test('generates JSON error report', () { + const document = SwaggerDocument( + title: '', + version: '', + description: '', + servers: [], + components: ApiComponents(schemas: {}, securitySchemes: {}), + paths: {}, + models: {}, + controllers: {}, + security: [], + ); + + validator.validateDocument(document); + + final jsonReport = validator.errorReporter.generateJsonReport(); + expect(jsonReport, isNotEmpty); + expect(jsonReport, contains('"timestamp"')); + expect(jsonReport, contains('"summary"')); + expect(jsonReport, contains('"errors"')); + }); + + test('strict mode validation', () { + final strictValidator = EnhancedValidator( + includeWarnings: false, + ); + + const document = SwaggerDocument( + title: 'Test API', + version: '1.0.0', + description: '', // Missing description + servers: [], // Missing servers + components: ApiComponents(schemas: {}, securitySchemes: {}), + paths: { + '/test': const ApiPath( + path: '/test', + method: HttpMethod.get, + summary: 'Test', + description: 'Test endpoint', + operationId: 'test', + tags: ['test'], + parameters: [], + responses: { + '200': const ApiResponse( + code: '200', + description: 'Success', + ), + }, + ), + }, + models: {}, + controllers: {}, + security: [], + ); + + final isValid = strictValidator.validateDocument(document); + expect(isValid, true); + + // Should have no warnings in strict mode with includeWarnings: false + final warnings = strictValidator.errorReporter + .getErrorsBySeverity(ErrorSeverity.warning); + expect(warnings.length, equals(0)); + }); + }); +} diff --git a/tests/integration_test.dart b/tests/integration_test.dart new file mode 100644 index 0000000..5daf131 --- /dev/null +++ b/tests/integration_test.dart @@ -0,0 +1,589 @@ +import 'dart:convert'; +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'; + +void main() { + group('Integration Tests', () { + group('End-to-End Workflow', () { + test('complete workflow from JSON to generated code', () async { + // 1. 准备测试数据 + final testApiJson = { + 'openapi': '3.0.3', + 'info': { + 'title': 'Integration Test API', + 'version': '1.0.0', + 'description': 'API for integration testing', + }, + 'servers': [ + { + 'url': 'https://api.example.com', + 'description': 'Production server', + }, + ], + 'paths': { + '/users': { + 'get': { + 'summary': 'Get users', + 'operationId': 'getUsers', + 'tags': ['users'], + 'parameters': [ + { + 'name': 'page', + 'in': 'query', + 'required': false, + 'schema': {'type': 'integer', 'default': 1}, + 'description': 'Page number', + }, + { + 'name': 'limit', + 'in': 'query', + 'required': false, + 'schema': {'type': 'integer', 'default': 20}, + 'description': 'Items per page', + }, + ], + 'responses': { + '200': { + 'description': 'Success', + 'content': { + 'application/json': { + 'schema': { + 'type': 'object', + 'properties': { + 'data': { + 'type': 'array', + 'items': { + '\$ref': '#/components/schemas/User', + }, + }, + 'total': {'type': 'integer'}, + 'page': {'type': 'integer'}, + 'limit': {'type': 'integer'}, + }, + }, + }, + }, + }, + '400': { + 'description': 'Bad request', + }, + }, + }, + 'post': { + 'summary': 'Create user', + 'operationId': 'createUser', + 'tags': ['users'], + 'requestBody': { + 'required': true, + 'content': { + 'application/json': { + 'schema': { + '\$ref': '#/components/schemas/CreateUserRequest', + }, + }, + }, + }, + 'responses': { + '201': { + 'description': 'User created', + 'content': { + 'application/json': { + 'schema': { + '\$ref': '#/components/schemas/User', + }, + }, + }, + }, + '400': { + 'description': 'Invalid input', + }, + }, + }, + }, + '/users/{id}': { + 'get': { + 'summary': 'Get user by ID', + 'operationId': 'getUserById', + 'tags': ['users'], + 'parameters': [ + { + 'name': 'id', + 'in': 'path', + 'required': true, + 'schema': {'type': 'integer'}, + 'description': 'User ID', + }, + ], + 'responses': { + '200': { + 'description': 'User found', + 'content': { + 'application/json': { + 'schema': { + '\$ref': '#/components/schemas/User', + }, + }, + }, + }, + '404': { + 'description': 'User not found', + }, + }, + }, + }, + '/files/upload': { + 'post': { + 'summary': 'Upload file', + 'operationId': 'uploadFile', + 'tags': ['files'], + 'requestBody': { + 'required': true, + 'content': { + 'multipart/form-data': { + 'schema': { + 'type': 'object', + 'properties': { + 'file': { + 'type': 'string', + 'format': 'binary', + }, + 'description': { + 'type': 'string', + }, + }, + 'required': ['file'], + }, + }, + }, + }, + 'responses': { + '200': { + 'description': 'File uploaded', + 'content': { + 'application/json': { + 'schema': { + '\$ref': '#/components/schemas/FileUploadResult', + }, + }, + }, + }, + }, + }, + }, + }, + 'components': { + 'schemas': { + 'User': { + 'type': 'object', + 'properties': { + 'id': { + 'type': 'integer', + 'format': 'int64', + }, + 'name': { + 'type': 'string', + 'maxLength': 100, + }, + 'email': { + 'type': 'string', + 'format': 'email', + }, + 'createdAt': { + 'type': 'string', + 'format': 'date-time', + }, + }, + 'required': ['id', 'name', 'email'], + }, + 'CreateUserRequest': { + 'type': 'object', + 'properties': { + 'name': { + 'type': 'string', + 'maxLength': 100, + }, + 'email': { + 'type': 'string', + 'format': 'email', + }, + }, + 'required': ['name', 'email'], + }, + 'FileUploadResult': { + 'type': 'object', + 'properties': { + 'url': { + 'type': 'string', + 'format': 'uri', + }, + 'filename': { + 'type': 'string', + }, + 'size': { + 'type': 'integer', + }, + 'contentType': { + 'type': 'string', + }, + }, + 'required': ['url', 'filename', 'size'], + }, + }, + 'securitySchemes': { + 'bearerAuth': { + 'type': 'http', + 'scheme': 'bearer', + 'bearerFormat': 'JWT', + }, + }, + }, + 'security': [ + { + 'bearerAuth': [], + }, + ], + }; + + final jsonString = jsonEncode(testApiJson); + + // 2. 解析 JSON 为 SwaggerDocument + final parser = PerformanceParser( + config: const ParseConfig( + enablePerformanceStats: true, + enableParallelParsing: false, // 禁用并行解析避免类型转换问题 + ), + ); + + final document = await parser.parseDocument(jsonString); + + // 验证解析结果 + expect(document.title, equals('Integration Test API')); + expect(document.version, equals('1.0.0')); + expect(document.paths.length, equals(3)); + expect(document.models.length, equals(3)); + expect(document.servers.length, equals(1)); + + // 检查解析性能 + final parseStats = parser.lastStats; + expect(parseStats, isNotNull); + expect(parseStats!.totalTime.inMilliseconds, lessThan(5000)); + + // 3. 验证文档 + final validator = EnhancedValidator( + includeWarnings: true, + ); + + final isValid = validator.validateDocument(document); + expect(isValid, isTrue); + + final errors = validator.errorReporter.errors; + final criticalErrors = errors + .where((e) => + e.severity == ErrorSeverity.error || + e.severity == ErrorSeverity.critical) + .toList(); + + expect(criticalErrors, isEmpty, + reason: + 'Document should not have critical errors: ${criticalErrors.map((e) => e.title).join(", ")}'); + + // 4. 生成 Retrofit API 代码 + final retrofitGenerator = RetrofitApiGenerator( + className: 'IntegrationTestApi', + splitByTags: true, + ); + + final retrofitCode = retrofitGenerator.generateFromDocument(document); + + // 验证生成的代码 + expect(retrofitCode, isNotEmpty); + expect(retrofitCode, contains('IntegrationTestApi')); + expect(retrofitCode, contains('@GET(\'/users\')')); + expect(retrofitCode, contains('@POST(\'/users\')')); + expect(retrofitCode, contains('@GET(\'/users/{id}\')')); + expect(retrofitCode, contains('@POST(\'/files/upload\')')); + expect(retrofitCode, contains('@Path(\'id\')')); + 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. 性能验证 + print('Integration Test Performance Summary:'); + print(' Parse Time: ${parseStats.totalTime.inMilliseconds}ms'); + print( + ' Document Size: ${(jsonString.length / 1024).toStringAsFixed(2)}KB'); + print(' Paths Parsed: ${parseStats.pathCount}'); + 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 { + final file = File('swagger.json'); + if (!file.existsSync()) { + print( + 'swagger.json not found, skipping real project integration test'); + return; + } + + final jsonString = await file.readAsString(); + print( + 'Real project swagger.json size: ${(jsonString.length / 1024).toStringAsFixed(2)}KB'); + + // 解析 + final parser = PerformanceParser( + config: const ParseConfig( + enablePerformanceStats: true, + enableParallelParsing: false, // 禁用并行解析 + maxConcurrency: 4, + ), + ); + + final parseStopwatch = Stopwatch()..start(); + final document = await parser.parseDocument(jsonString); + parseStopwatch.stop(); + + expect(document, isNotNull); + expect(document.paths.isNotEmpty, isTrue); + + print('Real project parsing results:'); + print(' Parse Time: ${parseStopwatch.elapsedMilliseconds}ms'); + print(' Paths: ${document.paths.length}'); + print(' Models: ${document.models.length}'); + print(' Servers: ${document.servers.length}'); + + // 验证 + final validator = EnhancedValidator(includeWarnings: false); + final isValid = validator.validateDocument(document); + + final errors = + validator.errorReporter.getErrorsBySeverity(ErrorSeverity.error); + final criticalErrors = + validator.errorReporter.getErrorsBySeverity(ErrorSeverity.critical); + + print('Validation results:'); + print(' Valid: $isValid'); + print(' Errors: ${errors.length}'); + print(' Critical: ${criticalErrors.length}'); + + // 生成代码 + final generator = OptimizedRetrofitGenerator( + className: 'OAMobileApiService', + generateModularApis: true, + generateBaseResult: true, + generatePagination: true, + generateFileUpload: true, + ); + + final genStopwatch = Stopwatch()..start(); + final generatedCode = generator.generateFromDocument(document); + genStopwatch.stop(); + + expect(generatedCode, isNotEmpty); + expect(generatedCode, contains('OAMobileApiService')); + + print('Code generation results:'); + print(' Generation Time: ${genStopwatch.elapsedMilliseconds}ms'); + print( + ' Generated Code Size: ${(generatedCode.length / 1024).toStringAsFixed(2)}KB'); + print(' Generated Lines: ${generatedCode.split('\n').length}'); + + // 性能要求 + expect(parseStopwatch.elapsedMilliseconds, lessThan(15000)); // 15秒内解析完成 + expect(genStopwatch.elapsedMilliseconds, lessThan(10000)); // 10秒内生成完成 + expect(generatedCode.length, greaterThan(5000)); // 至少生成5KB代码 + }); + }); + + group('Error Handling Integration', () { + test('handles malformed JSON gracefully', () async { + const malformedJson = + '{"openapi": "3.0.3", "info": {"title": "Test"'; // 缺少闭合括号 + + final parser = PerformanceParser(); + + expect(() => parser.parseDocument(malformedJson), + throwsA(isA())); + }); + + test('handles invalid OpenAPI document', () async { + final invalidDoc = { + 'openapi': '3.0.3', + 'info': { + 'title': 'Invalid API', + // 缺少 version + }, + 'paths': {}, // 空路径 + }; + + final jsonString = jsonEncode(invalidDoc); + final parser = PerformanceParser(); + + expect(() => parser.parseDocument(jsonString), + throwsA(isA())); + }); + + test('validation catches common errors', () async { + final problematicDoc = { + 'openapi': '3.0.3', + 'info': { + 'title': 'Problematic API', + 'version': '1.0.0', + }, + 'paths': { + '/users/{id}': { + 'get': { + 'summary': 'Get user', + 'responses': { + '200': { + 'description': 'Success', + }, + }, + // 缺少路径参数声明 + }, + }, + }, + }; + + final jsonString = jsonEncode(problematicDoc); + final parser = PerformanceParser(); + final document = await parser.parseDocument(jsonString); + + final validator = EnhancedValidator(); + final isValid = validator.validateDocument(document); + + expect(isValid, isFalse); + + final errors = validator.errorReporter.errors; + expect(errors.any((e) => e.id == 'UNDECLARED_PATH_PARAMETER'), isTrue); + }); + }); + + group('Performance Integration', () { + test('handles large documents efficiently', () async { + // 创建大型文档 + final paths = {}; + final schemas = {}; + + for (int i = 0; i < 200; i++) { + paths['/resource$i'] = { + 'get': { + 'summary': 'Get resource $i', + 'operationId': 'getResource$i', + 'tags': ['resources'], + 'responses': { + '200': { + 'description': 'Success', + 'content': { + 'application/json': { + 'schema': { + '\$ref': '#/components/schemas/Resource$i', + }, + }, + }, + }, + }, + }, + }; + + schemas['Resource$i'] = { + 'type': 'object', + 'properties': { + 'id': {'type': 'integer'}, + 'name': {'type': 'string'}, + 'value$i': {'type': 'string'}, + }, + 'required': ['id', 'name'], + }; + } + + final largeDoc = { + 'openapi': '3.0.3', + 'info': { + 'title': 'Large API', + 'version': '1.0.0', + }, + 'paths': paths, + 'components': { + 'schemas': schemas, + }, + }; + + final jsonString = jsonEncode(largeDoc); + print( + 'Large document size: ${(jsonString.length / 1024).toStringAsFixed(2)}KB'); + + // 测试解析性能 + final parser = PerformanceParser( + config: const ParseConfig( + enablePerformanceStats: true, + enableParallelParsing: false, // 禁用并行解析 + maxConcurrency: 4, + ), + ); + + final parseStopwatch = Stopwatch()..start(); + final document = await parser.parseDocument(jsonString); + parseStopwatch.stop(); + + expect(document.paths.length, greaterThan(100)); + expect(document.models.length, greaterThan(100)); + expect(parseStopwatch.elapsedMilliseconds, lessThan(10000)); // 10秒内完成 + + // 测试生成性能 + final generator = OptimizedRetrofitGenerator( + generateModularApis: true, + ); + + final genStopwatch = Stopwatch()..start(); + final generatedCode = generator.generateFromDocument(document); + genStopwatch.stop(); + + expect(generatedCode.length, greaterThan(10000)); // 至少10KB代码 + expect(genStopwatch.elapsedMilliseconds, lessThan(15000)); // 15秒内完成 + + print('Large document performance:'); + print(' Parse Time: ${parseStopwatch.elapsedMilliseconds}ms'); + print(' Generation Time: ${genStopwatch.elapsedMilliseconds}ms'); + print(' Paths: ${document.paths.length}'); + print(' Models: ${document.models.length}'); + print( + ' Generated Code: ${(generatedCode.length / 1024).toStringAsFixed(2)}KB'); + }); + }); + }); +} diff --git a/tests/media_type_test.dart b/tests/media_type_test.dart new file mode 100644 index 0000000..3fbdc1a --- /dev/null +++ b/tests/media_type_test.dart @@ -0,0 +1,451 @@ +import 'package:swagger_generator_flutter/core/models.dart'; +import 'package:test/test.dart'; + +void main() { + group('MediaType Enum', () { + test('converts media type to string', () { + expect(MediaType.json.value, 'application/json'); + expect(MediaType.xml.value, 'application/xml'); + expect(MediaType.multipartFormData.value, 'multipart/form-data'); + expect( + MediaType.formUrlEncoded.value, 'application/x-www-form-urlencoded'); + expect(MediaType.textPlain.value, 'text/plain'); + expect(MediaType.textHtml.value, 'text/html'); + expect(MediaType.textCsv.value, 'text/csv'); + expect( + MediaType.applicationOctetStream.value, 'application/octet-stream'); + expect(MediaType.applicationPdf.value, 'application/pdf'); + expect(MediaType.imagePng.value, 'image/png'); + expect(MediaType.imageJpeg.value, 'image/jpeg'); + expect(MediaType.imageGif.value, 'image/gif'); + expect(MediaType.imageSvg.value, 'image/svg+xml'); + expect(MediaType.audioMp3.value, 'audio/mpeg'); + expect(MediaType.videoMp4.value, 'video/mp4'); + }); + + test('converts string to media type', () { + expect(MediaTypeExtension.fromString('application/json'), MediaType.json); + expect(MediaTypeExtension.fromString('application/xml'), MediaType.xml); + expect(MediaTypeExtension.fromString('text/xml'), MediaType.xml); + expect(MediaTypeExtension.fromString('multipart/form-data'), + MediaType.multipartFormData); + expect(MediaTypeExtension.fromString('application/x-www-form-urlencoded'), + MediaType.formUrlEncoded); + expect(MediaTypeExtension.fromString('text/plain'), MediaType.textPlain); + expect(MediaTypeExtension.fromString('text/html'), MediaType.textHtml); + expect(MediaTypeExtension.fromString('text/csv'), MediaType.textCsv); + expect(MediaTypeExtension.fromString('application/octet-stream'), + MediaType.applicationOctetStream); + expect(MediaTypeExtension.fromString('application/pdf'), + MediaType.applicationPdf); + expect(MediaTypeExtension.fromString('image/png'), MediaType.imagePng); + expect(MediaTypeExtension.fromString('image/jpeg'), MediaType.imageJpeg); + expect(MediaTypeExtension.fromString('image/jpg'), MediaType.imageJpeg); + expect(MediaTypeExtension.fromString('image/gif'), MediaType.imageGif); + expect( + MediaTypeExtension.fromString('image/svg+xml'), MediaType.imageSvg); + expect(MediaTypeExtension.fromString('audio/mpeg'), MediaType.audioMp3); + expect(MediaTypeExtension.fromString('audio/mp3'), MediaType.audioMp3); + expect(MediaTypeExtension.fromString('video/mp4'), MediaType.videoMp4); + expect(MediaTypeExtension.fromString('unknown/type'), MediaType.custom); + }); + + test('checks text types', () { + expect(MediaType.json.isText, true); + expect(MediaType.xml.isText, true); + expect(MediaType.textPlain.isText, true); + expect(MediaType.textHtml.isText, true); + expect(MediaType.textCsv.isText, true); + expect(MediaType.imagePng.isText, false); + expect(MediaType.applicationOctetStream.isText, false); + }); + + test('checks binary types', () { + expect(MediaType.applicationOctetStream.isBinary, true); + expect(MediaType.applicationPdf.isBinary, true); + expect(MediaType.imagePng.isBinary, true); + expect(MediaType.imageJpeg.isBinary, true); + expect(MediaType.imageGif.isBinary, true); + expect(MediaType.audioMp3.isBinary, true); + expect(MediaType.videoMp4.isBinary, true); + expect(MediaType.json.isBinary, false); + expect(MediaType.textPlain.isBinary, false); + }); + + test('checks form types', () { + expect(MediaType.formData.isForm, true); + expect(MediaType.formUrlEncoded.isForm, true); + expect(MediaType.multipartFormData.isForm, true); + expect(MediaType.json.isForm, false); + expect(MediaType.xml.isForm, false); + }); + + test('checks image types', () { + expect(MediaType.imagePng.isImage, true); + expect(MediaType.imageJpeg.isImage, true); + expect(MediaType.imageGif.isImage, true); + expect(MediaType.imageSvg.isImage, true); + expect(MediaType.json.isImage, false); + expect(MediaType.applicationPdf.isImage, false); + }); + + test('checks audio types', () { + expect(MediaType.audioMp3.isAudio, true); + expect(MediaType.json.isAudio, false); + expect(MediaType.videoMp4.isAudio, false); + }); + + test('checks video types', () { + expect(MediaType.videoMp4.isVideo, true); + expect(MediaType.json.isVideo, false); + expect(MediaType.audioMp3.isVideo, false); + }); + + test('gets correct Dart types', () { + expect(MediaType.json.dartType, 'Map'); + expect(MediaType.xml.dartType, 'String'); + expect(MediaType.multipartFormData.dartType, 'FormData'); + expect(MediaType.formUrlEncoded.dartType, 'FormData'); + expect(MediaType.textPlain.dartType, 'String'); + expect(MediaType.textHtml.dartType, 'String'); + expect(MediaType.textCsv.dartType, 'String'); + expect(MediaType.applicationOctetStream.dartType, 'List'); + expect(MediaType.applicationPdf.dartType, 'List'); + expect(MediaType.imagePng.dartType, 'List'); + expect(MediaType.imageJpeg.dartType, 'List'); + expect(MediaType.imageGif.dartType, 'List'); + expect(MediaType.imageSvg.dartType, 'String'); + expect(MediaType.audioMp3.dartType, 'List'); + expect(MediaType.videoMp4.dartType, 'List'); + expect(MediaType.custom.dartType, 'dynamic'); + }); + }); + + group('ApiMediaType', () { + test('creates ApiMediaType with JSON content type', () { + final json = { + 'schema': { + 'type': 'object', + 'properties': { + 'id': {'type': 'integer'}, + 'name': {'type': 'string'}, + }, + }, + 'example': { + 'id': 1, + 'name': 'Test', + }, + }; + + final mediaType = ApiMediaType.fromJson(json, 'application/json'); + + expect(mediaType.mediaType, MediaType.json); + expect(mediaType.rawMediaType, 'application/json'); + expect(mediaType.isJson, true); + expect(mediaType.isXml, false); + expect(mediaType.isForm, false); + expect(mediaType.isBinary, false); + expect(mediaType.isText, true); + expect(mediaType.dartType, 'Map'); + }); + + test('creates ApiMediaType with XML content type', () { + final json = { + 'schema': { + 'type': 'string', + }, + 'example': '1Test', + }; + + final mediaType = ApiMediaType.fromJson(json, 'application/xml'); + + expect(mediaType.mediaType, MediaType.xml); + expect(mediaType.rawMediaType, 'application/xml'); + expect(mediaType.isJson, false); + expect(mediaType.isXml, true); + expect(mediaType.isForm, false); + expect(mediaType.isBinary, false); + expect(mediaType.isText, true); + expect(mediaType.dartType, 'String'); + }); + + test('creates ApiMediaType with form data content type', () { + final json = { + 'schema': { + 'type': 'object', + 'properties': { + 'file': {'type': 'string', 'format': 'binary'}, + 'name': {'type': 'string'}, + }, + }, + 'encoding': { + 'file': { + 'contentType': 'image/png', + }, + }, + }; + + final mediaType = ApiMediaType.fromJson(json, 'multipart/form-data'); + + expect(mediaType.mediaType, MediaType.multipartFormData); + expect(mediaType.rawMediaType, 'multipart/form-data'); + expect(mediaType.isJson, false); + expect(mediaType.isXml, false); + expect(mediaType.isForm, true); + expect(mediaType.isFileUpload, true); + expect(mediaType.isBinary, false); + expect(mediaType.isText, false); + expect(mediaType.dartType, 'FormData'); + expect(mediaType.encoding.length, 1); + expect(mediaType.encoding['file'], isNotNull); + }); + + test('creates ApiMediaType with binary content type', () { + final json = { + 'schema': { + 'type': 'string', + 'format': 'binary', + }, + }; + + final mediaType = ApiMediaType.fromJson(json, 'application/octet-stream'); + + expect(mediaType.mediaType, MediaType.applicationOctetStream); + expect(mediaType.rawMediaType, 'application/octet-stream'); + expect(mediaType.isJson, false); + expect(mediaType.isXml, false); + expect(mediaType.isForm, false); + expect(mediaType.isBinary, true); + expect(mediaType.isText, false); + expect(mediaType.dartType, 'List'); + }); + + test('creates ApiMediaType with image content type', () { + final json = { + 'schema': { + 'type': 'string', + 'format': 'binary', + }, + }; + + final mediaType = ApiMediaType.fromJson(json, 'image/png'); + + expect(mediaType.mediaType, MediaType.imagePng); + expect(mediaType.rawMediaType, 'image/png'); + expect(mediaType.isImage, true); + expect(mediaType.isBinary, true); + expect(mediaType.dartType, 'List'); + }); + + test('creates ApiMediaType with custom content type', () { + final json = { + 'schema': { + 'type': 'string', + }, + }; + + final mediaType = ApiMediaType.fromJson(json, 'application/vnd.api+json'); + + expect(mediaType.mediaType, MediaType.custom); + expect(mediaType.rawMediaType, 'application/vnd.api+json'); + expect(mediaType.dartType, 'dynamic'); + }); + + test('creates ApiMediaType with default content type when none provided', + () { + final json = { + 'schema': { + 'type': 'object', + }, + }; + + final mediaType = ApiMediaType.fromJson(json); + + expect(mediaType.mediaType, MediaType.json); + expect(mediaType.rawMediaType, 'application/json'); + expect(mediaType.isJson, true); + }); + + test('creates ApiMediaType with examples', () { + final json = { + 'schema': { + 'type': 'object', + }, + 'examples': { + 'user1': { + 'summary': 'User example 1', + 'value': {'id': 1, 'name': 'John'}, + }, + 'user2': { + 'summary': 'User example 2', + 'value': {'id': 2, 'name': 'Jane'}, + }, + }, + }; + + final mediaType = ApiMediaType.fromJson(json, 'application/json'); + + expect(mediaType.examples.length, 2); + expect(mediaType.examples['user1'], isNotNull); + expect(mediaType.examples['user2'], isNotNull); + expect(mediaType.examples['user1']?.summary, 'User example 1'); + }); + }); + + group('File Upload Support', () { + test('creates ApiEncoding with file content type', () { + final json = { + 'contentType': 'image/png', + 'headers': { + 'X-Custom-Header': { + 'description': 'Custom header for file upload', + 'required': true, + }, + }, + }; + + final encoding = ApiEncoding.fromJson(json); + + expect(encoding.contentType, 'image/png'); + expect(encoding.isFile, true); + expect(encoding.isImage, true); + expect(encoding.isAudio, false); + expect(encoding.isVideo, false); + expect(encoding.hasHeaders, true); + expect(encoding.headers.length, 1); + expect(encoding.headers['X-Custom-Header'], isNotNull); + }); + + test('detects different file types in ApiEncoding', () { + final imageEncoding = ApiEncoding.fromJson({'contentType': 'image/jpeg'}); + expect(imageEncoding.isFile, true); + expect(imageEncoding.isImage, true); + expect(imageEncoding.isAudio, false); + expect(imageEncoding.isVideo, false); + + final audioEncoding = ApiEncoding.fromJson({'contentType': 'audio/mpeg'}); + expect(audioEncoding.isFile, true); + expect(audioEncoding.isImage, false); + expect(audioEncoding.isAudio, true); + expect(audioEncoding.isVideo, false); + + final videoEncoding = ApiEncoding.fromJson({'contentType': 'video/mp4'}); + expect(videoEncoding.isFile, true); + expect(videoEncoding.isImage, false); + expect(videoEncoding.isAudio, false); + expect(videoEncoding.isVideo, true); + + final binaryEncoding = + ApiEncoding.fromJson({'contentType': 'application/octet-stream'}); + expect(binaryEncoding.isFile, true); + expect(binaryEncoding.isImage, false); + expect(binaryEncoding.isAudio, false); + expect(binaryEncoding.isVideo, false); + + final textEncoding = ApiEncoding.fromJson({'contentType': 'text/plain'}); + expect(textEncoding.isFile, false); + expect(textEncoding.isImage, false); + expect(textEncoding.isAudio, false); + expect(textEncoding.isVideo, false); + }); + + test('creates ApiMediaType with file upload encoding', () { + final json = { + 'schema': { + 'type': 'object', + 'properties': { + 'avatar': {'type': 'string', 'format': 'binary'}, + 'name': {'type': 'string'}, + 'documents': { + 'type': 'array', + 'items': {'type': 'string', 'format': 'binary'}, + }, + }, + }, + 'encoding': { + 'avatar': { + 'contentType': 'image/*', + 'headers': { + 'X-File-Type': { + 'description': 'File type validation', + 'required': false, + }, + }, + }, + 'documents': { + 'contentType': 'application/pdf', + 'explode': true, + }, + }, + }; + + final mediaType = ApiMediaType.fromJson(json, 'multipart/form-data'); + + expect(mediaType.isFileUpload, true); + expect(mediaType.encoding.length, 2); + expect(mediaType.encoding['avatar'], isNotNull); + expect(mediaType.encoding['documents'], isNotNull); + expect(mediaType.encoding['avatar']?.contentType, 'image/*'); + expect(mediaType.encoding['documents']?.contentType, 'application/pdf'); + expect(mediaType.encoding['documents']?.explode, true); + }); + + test('handles complex file upload scenarios', () { + final json = { + 'schema': { + 'type': 'object', + 'properties': { + 'profileImage': {'type': 'string', 'format': 'binary'}, + 'thumbnails': { + 'type': 'array', + 'items': {'type': 'string', 'format': 'binary'}, + }, + 'metadata': { + 'type': 'object', + 'properties': { + 'title': {'type': 'string'}, + 'description': {'type': 'string'}, + }, + }, + }, + 'required': ['profileImage', 'metadata'], + }, + 'encoding': { + 'profileImage': { + 'contentType': 'image/png, image/jpeg', + 'headers': { + 'X-Image-Quality': { + 'description': 'Image quality setting', + 'schema': {'type': 'integer', 'minimum': 1, 'maximum': 100}, + }, + }, + }, + 'thumbnails': { + 'contentType': 'image/png', + 'style': 'form', + 'explode': true, + }, + 'metadata': { + 'contentType': 'application/json', + }, + }, + }; + + final mediaType = ApiMediaType.fromJson(json, 'multipart/form-data'); + + expect(mediaType.isFileUpload, true); + expect(mediaType.encoding.length, 3); + + final profileImageEncoding = mediaType.encoding['profileImage']!; + expect(profileImageEncoding.contentType, 'image/png, image/jpeg'); + expect(profileImageEncoding.hasHeaders, true); + + final thumbnailsEncoding = mediaType.encoding['thumbnails']!; + expect(thumbnailsEncoding.explode, true); + expect(thumbnailsEncoding.style, 'form'); + + final metadataEncoding = mediaType.encoding['metadata']!; + expect(metadataEncoding.contentType, 'application/json'); + }); + }); +} diff --git a/tests/models_test.dart b/tests/models_test.dart deleted file mode 100644 index 372c3d1..0000000 --- a/tests/models_test.dart +++ /dev/null @@ -1,923 +0,0 @@ -import 'package:test/test.dart'; - -import 'package:swagger_generator_flutter/core/models.dart'; - -void main() { - group('ApiPath', () { - test('creates ApiPath with required fields', () { - const path = ApiPath( - path: '/api/users', - method: HttpMethod.get, - summary: 'Get users', - description: 'Retrieve all users', - operationId: 'getUsers', - tags: ['User'], - parameters: [], - responses: {}, - requestBody: null, - ); - - expect(path.path, '/api/users'); - expect(path.method, HttpMethod.get); - expect(path.summary, 'Get users'); - expect(path.description, 'Retrieve all users'); - expect(path.tags, ['User']); - expect(path.parameters, isEmpty); - expect(path.responses, isEmpty); - expect(path.requestBody, isNull); - }); - - test('creates ApiPath with all fields', () { - final parameters = [ - const ApiParameter( - name: 'id', - location: ParameterLocation.path, - required: true, - type: PropertyType.integer, - description: 'User ID', - ), - ]; - - final responses = { - '200': const ApiResponse( - code: '200', - description: 'Success', - schema: {'type': 'object'}, - content: null, - ), - }; - - const requestBody = ApiRequestBody( - description: 'User data', - required: true, - content: { - 'application/json': { - 'schema': {'type': 'object'} - } - }, - ); - - final path = ApiPath( - path: '/api/users/{id}', - method: HttpMethod.put, - summary: 'Update user', - description: 'Update user by ID', - operationId: 'updateUser', - tags: ['User'], - parameters: parameters, - responses: responses, - requestBody: requestBody, - ); - - expect(path.parameters, hasLength(1)); - expect(path.responses, hasLength(1)); - expect(path.requestBody, isNotNull); - }); - }); - - group('ApiParameter', () { - test('creates ApiParameter with required fields', () { - const param = ApiParameter( - name: 'id', - location: ParameterLocation.path, - required: true, - type: PropertyType.integer, - description: 'User ID', - ); - - expect(param.name, 'id'); - expect(param.location, ParameterLocation.path); - expect(param.required, true); - expect(param.type, PropertyType.integer); - expect(param.description, 'User ID'); - }); - - test('creates ApiParameter with optional fields', () { - const param = ApiParameter( - name: 'page', - location: ParameterLocation.query, - required: false, - type: PropertyType.integer, - description: 'Page number', - format: 'int32', - example: 1, - defaultValue: 1, - ); - - expect(param.format, 'int32'); - expect(param.example, 1); - expect(param.defaultValue, 1); - }); - - test('creates ApiParameter from JSON', () { - final json = { - 'name': 'id', - 'in': 'path', - 'required': true, - 'type': 'integer', - 'description': 'User ID', - 'format': 'int64', - 'example': 123, - 'default': 1, - }; - - final param = ApiParameter.fromJson(json); - - expect(param.name, 'id'); - expect(param.location, ParameterLocation.path); - expect(param.required, true); - expect(param.type, PropertyType.integer); - expect(param.description, 'User ID'); - expect(param.format, 'int64'); - expect(param.example, 123); - expect(param.defaultValue, 1); - }); - }); - - group('ApiResponse', () { - test('creates ApiResponse with required fields', () { - const response = ApiResponse( - code: '200', - description: 'Success', - schema: null, - content: null, - ); - - expect(response.code, '200'); - expect(response.description, 'Success'); - expect(response.schema, isNull); - expect(response.content, isNull); - }); - - test('creates ApiResponse with schema', () { - final schema = { - 'type': 'object', - 'properties': { - 'id': {'type': 'integer'}, - 'name': {'type': 'string'}, - }, - }; - - final response = ApiResponse( - code: '200', - description: 'Success', - schema: schema, - content: null, - ); - - expect(response.schema, equals(schema)); - }); - - test('creates ApiResponse from JSON', () { - final json = { - 'description': 'Success', - 'schema': {'type': 'object'}, - 'content': { - 'application/json': { - 'schema': {'type': 'object'}, - }, - }, - }; - - final response = ApiResponse.fromJson('200', json); - - expect(response.code, '200'); - expect(response.description, 'Success'); - expect(response.schema, isNotNull); - expect(response.content, isNotNull); - }); - }); - - group('ApiRequestBody', () { - test('creates ApiRequestBody with required fields', () { - const requestBody = ApiRequestBody( - description: 'User data', - required: true, - content: null, - ); - - expect(requestBody.description, 'User data'); - expect(requestBody.required, true); - expect(requestBody.content, isNull); - }); - - test('creates ApiRequestBody with content', () { - final content = { - 'application/json': { - 'schema': { - 'type': 'object', - 'properties': { - 'name': {'type': 'string'}, - 'email': {'type': 'string'}, - }, - }, - }, - }; - - final requestBody = ApiRequestBody( - description: 'User data', - required: true, - content: content, - ); - - expect(requestBody.content, equals(content)); - }); - - test('creates ApiRequestBody from JSON', () { - final json = { - 'description': 'User data', - 'required': true, - 'content': { - 'application/json': { - 'schema': {'type': 'object'}, - }, - }, - }; - - final requestBody = ApiRequestBody.fromJson(json); - - expect(requestBody.description, 'User data'); - expect(requestBody.required, true); - expect(requestBody.content, isNotNull); - }); - }); - - group('ApiModel', () { - test('creates ApiModel with required fields', () { - const model = ApiModel( - name: 'User', - description: 'User model', - properties: {}, - required: [], - ); - - expect(model.name, 'User'); - expect(model.description, 'User model'); - expect(model.properties, isEmpty); - expect(model.required, isEmpty); - expect(model.isEnum, false); - expect(model.enumValues, isEmpty); - expect(model.enumType, isNull); - }); - - test('creates ApiModel with properties', () { - final properties = { - 'id': const ApiProperty( - name: 'id', - type: PropertyType.integer, - description: 'User ID', - required: true, - ), - 'name': const ApiProperty( - name: 'name', - type: PropertyType.string, - description: 'User name', - required: true, - ), - }; - - final model = ApiModel( - name: 'User', - description: 'User model', - properties: properties, - required: ['id', 'name'], - ); - - expect(model.properties, hasLength(2)); - expect(model.required, ['id', 'name']); - }); - - test('creates enum ApiModel', () { - const model = ApiModel( - name: 'UserStatus', - description: 'User status enum', - properties: {}, - required: [], - isEnum: true, - enumValues: ['active', 'inactive', 'pending'], - enumType: PropertyType.string, - ); - - expect(model.isEnum, true); - expect(model.enumValues, ['active', 'inactive', 'pending']); - expect(model.enumType, PropertyType.string); - }); - - test('creates ApiModel from JSON', () { - final json = { - 'description': 'User model', - 'properties': { - 'id': { - 'type': 'integer', - 'description': 'User ID', - }, - 'name': { - 'type': 'string', - 'description': 'User name', - }, - }, - 'required': ['id', 'name'], - }; - - final model = ApiModel.fromJson('User', json); - - expect(model.name, 'User'); - expect(model.description, 'User model'); - expect(model.properties, hasLength(2)); - expect(model.required, ['id', 'name']); - }); - - test('creates enum ApiModel from JSON', () { - final json = { - 'description': 'User status enum', - 'enum': ['active', 'inactive', 'pending'], - 'type': 'string', - }; - - final model = ApiModel.fromJson('UserStatus', json); - - expect(model.isEnum, true); - expect(model.enumValues, ['active', 'inactive', 'pending']); - expect(model.enumType, PropertyType.string); - }); - }); - - group('ApiProperty', () { - test('creates ApiProperty with required fields', () { - const property = ApiProperty( - name: 'id', - type: PropertyType.integer, - description: 'User ID', - required: true, - ); - - expect(property.name, 'id'); - expect(property.type, PropertyType.integer); - expect(property.description, 'User ID'); - expect(property.required, true); - expect(property.nullable, false); - }); - - test('creates ApiProperty with optional fields', () { - const property = ApiProperty( - name: 'name', - type: PropertyType.string, - description: 'User name', - required: false, - nullable: true, - format: 'string', - example: 'John Doe', - defaultValue: 'Unknown', - reference: 'User', - ); - - expect(property.nullable, true); - expect(property.format, 'string'); - expect(property.example, 'John Doe'); - expect(property.defaultValue, 'Unknown'); - expect(property.reference, 'User'); - }); - - test('creates ApiProperty from JSON', () { - final json = { - 'type': 'string', - 'description': 'User name', - 'nullable': true, - 'format': 'string', - 'example': 'John Doe', - 'default': 'Unknown', - }; - - final property = ApiProperty.fromJson('name', json, []); - - expect(property.name, 'name'); - expect(property.type, PropertyType.string); - expect(property.description, 'User name'); - expect(property.nullable, true); - expect(property.format, 'string'); - expect(property.example, 'John Doe'); - expect(property.defaultValue, 'Unknown'); - }); - - test('creates ApiProperty with reference', () { - final json = { - 'type': 'object', - 'description': 'User object', - '\$ref': '#/components/schemas/User', - }; - - final property = ApiProperty.fromJson('user', json, []); - - expect(property.type, PropertyType.reference); - expect(property.reference, 'User'); - }); - - test('creates ApiProperty with array items', () { - final json = { - 'type': 'array', - 'description': 'User list', - 'items': { - 'type': 'object', - '\$ref': '#/components/schemas/User', - }, - }; - - final property = ApiProperty.fromJson('users', json, []); - - expect(property.type, PropertyType.array); - expect(property.items, isNotNull); - expect(property.items!.name, 'User'); - }); - }); - - group('PropertyType', () { - test('converts string to PropertyType', () { - expect(PropertyType.fromString('string'), PropertyType.string); - expect(PropertyType.fromString('integer'), PropertyType.integer); - expect(PropertyType.fromString('number'), PropertyType.number); - expect(PropertyType.fromString('boolean'), PropertyType.boolean); - expect(PropertyType.fromString('array'), PropertyType.array); - expect(PropertyType.fromString('object'), PropertyType.object); - expect(PropertyType.fromString('unknown'), PropertyType.string); - }); - - test('gets PropertyType value', () { - expect(PropertyType.string.value, 'string'); - expect(PropertyType.integer.value, 'integer'); - expect(PropertyType.number.value, 'number'); - expect(PropertyType.boolean.value, 'boolean'); - expect(PropertyType.array.value, 'array'); - expect(PropertyType.object.value, 'object'); - expect(PropertyType.reference.value, 'reference'); - }); - }); - - group('ParameterLocation', () { - test('converts string to ParameterLocation', () { - expect(ParameterLocation.fromString('query'), ParameterLocation.query); - expect(ParameterLocation.fromString('path'), ParameterLocation.path); - expect(ParameterLocation.fromString('header'), ParameterLocation.header); - expect(ParameterLocation.fromString('cookie'), ParameterLocation.cookie); - expect(ParameterLocation.fromString('unknown'), ParameterLocation.query); - }); - }); - - group('HttpMethod', () { - test('converts string to HttpMethod', () { - expect(HttpMethod.fromString('GET'), HttpMethod.get); - expect(HttpMethod.fromString('POST'), HttpMethod.post); - expect(HttpMethod.fromString('PUT'), HttpMethod.put); - expect(HttpMethod.fromString('DELETE'), HttpMethod.delete); - expect(HttpMethod.fromString('PATCH'), HttpMethod.patch); - expect(HttpMethod.fromString('HEAD'), HttpMethod.head); - expect(HttpMethod.fromString('OPTIONS'), HttpMethod.options); - }); - - test('handles case insensitive input', () { - expect(HttpMethod.fromString('get'), HttpMethod.get); - expect(HttpMethod.fromString('Post'), HttpMethod.post); - expect(HttpMethod.fromString('put'), HttpMethod.put); - }); - - test('returns default for unknown method', () { - expect(HttpMethod.fromString('UNKNOWN'), HttpMethod.get); - expect(HttpMethod.fromString(''), HttpMethod.get); - }); - - test('gets HttpMethod value', () { - expect(HttpMethod.get.value, 'GET'); - expect(HttpMethod.post.value, 'POST'); - expect(HttpMethod.put.value, 'PUT'); - expect(HttpMethod.delete.value, 'DELETE'); - expect(HttpMethod.patch.value, 'PATCH'); - expect(HttpMethod.head.value, 'HEAD'); - expect(HttpMethod.options.value, 'OPTIONS'); - }); - }); - - group('SwaggerDocument', () { - test('creates SwaggerDocument with required fields', () { - const document = SwaggerDocument( - title: 'Test API', - version: '1.0.0', - description: 'Test API description', - host: 'api.example.com', - basePath: '/api', - schemes: ['https'], - consumes: ['application/json'], - produces: ['application/json'], - paths: {}, - models: {}, - controllers: {}, - ); - - expect(document.title, 'Test API'); - expect(document.version, '1.0.0'); - expect(document.description, 'Test API description'); - expect(document.host, 'api.example.com'); - expect(document.basePath, '/api'); - expect(document.schemes, ['https']); - expect(document.consumes, ['application/json']); - expect(document.produces, ['application/json']); - expect(document.paths, isEmpty); - expect(document.models, isEmpty); - expect(document.controllers, isEmpty); - }); - - test('creates SwaggerDocument from JSON with all fields', () { - final json = { - 'info': { - 'title': 'Test API', - 'version': '1.0.0', - 'description': 'Test API description', - }, - 'host': 'api.example.com', - 'basePath': '/api', - 'schemes': ['https', 'http'], - 'consumes': ['application/json', 'application/xml'], - 'produces': ['application/json', 'text/plain'], - }; - - final document = SwaggerDocument.fromJson(json); - - expect(document.title, 'Test API'); - expect(document.version, '1.0.0'); - expect(document.description, 'Test API description'); - expect(document.host, 'api.example.com'); - expect(document.basePath, '/api'); - expect(document.schemes, ['https', 'http']); - expect(document.consumes, ['application/json', 'application/xml']); - expect(document.produces, ['application/json', 'text/plain']); - }); - - test('creates SwaggerDocument from JSON with minimal fields', () { - final json = {}; - - final document = SwaggerDocument.fromJson(json); - - expect(document.title, 'API'); - expect(document.version, '1.0.0'); - expect(document.description, ''); - expect(document.host, ''); - expect(document.basePath, ''); - expect(document.schemes, ['https']); - expect(document.consumes, ['application/json']); - expect(document.produces, ['application/json']); - }); - - test('creates SwaggerDocument from JSON with null info', () { - final json = {'info': null}; - - final document = SwaggerDocument.fromJson(json); - - expect(document.title, 'API'); - expect(document.version, '1.0.0'); - expect(document.description, ''); - }); - }); - - group('ApiController', () { - test('creates ApiController with required fields', () { - const controller = ApiController( - name: 'UserController', - description: 'User management controller', - paths: [], - ); - - expect(controller.name, 'UserController'); - expect(controller.description, 'User management controller'); - expect(controller.paths, isEmpty); - }); - - test('creates ApiController with paths', () { - final paths = [ - const ApiPath( - path: '/api/users', - method: HttpMethod.get, - summary: 'Get users', - description: 'Retrieve all users', - operationId: 'getUsers', - tags: ['User'], - parameters: [], - responses: {}, - requestBody: null, - ), - const ApiPath( - path: '/api/users/{id}', - method: HttpMethod.post, - summary: 'Create user', - description: 'Create a new user', - operationId: 'createUser', - tags: ['User'], - parameters: [], - responses: {}, - requestBody: null, - ), - ]; - - final controller = ApiController( - name: 'UserController', - description: 'User management controller', - paths: paths, - ); - - expect(controller.paths, hasLength(2)); - expect(controller.paths[0].path, '/api/users'); - expect(controller.paths[1].path, '/api/users/{id}'); - }); - - test('creates ApiController from paths', () { - final paths = [ - const ApiPath( - path: '/api/users', - method: HttpMethod.get, - summary: 'Get users', - description: 'Retrieve all users', - operationId: 'getUsers', - tags: ['User'], - parameters: [], - responses: {}, - requestBody: null, - ), - ]; - - final controller = ApiController.fromPaths('UserController', paths); - - expect(controller.name, 'UserController'); - expect(controller.description, 'UserController'); - expect(controller.paths, hasLength(1)); - }); - }); - - group('ApiPath fromJson', () { - test('creates ApiPath from JSON with all fields', () { - final json = { - 'summary': 'Get users', - 'description': 'Retrieve all users', - 'operationId': 'getUsers', - 'tags': ['User'], - 'parameters': [ - { - 'name': 'id', - 'in': 'path', - 'required': true, - 'type': 'integer', - 'description': 'User ID', - } - ], - 'responses': { - '200': { - 'description': 'Success', - 'schema': {'type': 'object'}, - } - }, - 'requestBody': { - 'description': 'User data', - 'required': true, - 'content': { - 'application/json': { - 'schema': {'type': 'object'}, - }, - }, - }, - 'deprecated': true, - }; - - final path = ApiPath.fromJson('/api/users/{id}', 'PUT', json); - - expect(path.path, '/api/users/{id}'); - expect(path.method, HttpMethod.put); - expect(path.summary, 'Get users'); - expect(path.description, 'Retrieve all users'); - expect(path.operationId, 'getUsers'); - expect(path.tags, ['User']); - expect(path.parameters, hasLength(1)); - expect(path.responses, hasLength(1)); - expect(path.requestBody, isNotNull); - expect(path.deprecated, true); - }); - - test('creates ApiPath from JSON with minimal fields', () { - final json = {}; - - final path = ApiPath.fromJson('/api/users', 'GET', json); - - expect(path.path, '/api/users'); - expect(path.method, HttpMethod.get); - expect(path.summary, ''); - expect(path.description, ''); - expect(path.operationId, ''); - expect(path.tags, isEmpty); - expect(path.parameters, isEmpty); - expect(path.responses, isEmpty); - expect(path.requestBody, isNull); - expect(path.deprecated, false); - }); - }); - - group('ApiModel fromJson edge cases', () { - test('creates ApiModel with nullable properties', () { - final json = { - 'description': 'User model', - 'properties': { - 'id': { - 'type': 'integer', - 'description': 'User ID', - 'nullable': true, - }, - 'name': { - 'type': 'string', - 'description': 'User name', - 'nullable': false, - }, - }, - }; - - final model = ApiModel.fromJson('User', json); - - expect(model.properties['id']!.nullable, true); - expect(model.properties['id']!.required, false); - expect(model.properties['name']!.nullable, false); - expect(model.properties['name']!.required, true); - expect(model.required, ['name']); - }); - - test('creates ApiModel with explicit required fields', () { - final json = { - 'description': 'User model', - 'properties': { - 'id': { - 'type': 'integer', - 'description': 'User ID', - 'nullable': true, - }, - 'name': { - 'type': 'string', - 'description': 'User name', - 'nullable': false, - }, - }, - 'required': ['id', 'name'], - }; - - final model = ApiModel.fromJson('User', json); - - expect(model.properties['id']!.required, true); - expect(model.properties['name']!.required, true); - expect(model.required, ['id', 'name']); - }); - }); - - group('ApiProperty complex types', () { - test('creates ApiProperty with nested object', () { - final json = { - 'type': 'object', - 'description': 'User address', - 'properties': { - 'street': {'type': 'string'}, - 'city': {'type': 'string'}, - }, - }; - - final property = ApiProperty.fromJson('address', json, []); - - expect(property.type, PropertyType.object); - expect(property.description, 'User address'); - expect(property.required, false); - }); - - test('creates ApiProperty with array of primitives', () { - final json = { - 'type': 'array', - 'description': 'User tags', - 'items': { - 'type': 'string', - }, - }; - - final property = ApiProperty.fromJson('tags', json, []); - - expect(property.type, PropertyType.array); - expect(property.description, 'User tags'); - expect(property.items, isNotNull); - expect(property.items!.name, 'string'); - }); - - test('creates ApiProperty with array of objects', () { - final json = { - 'type': 'array', - 'description': 'User list', - 'items': { - 'type': 'object', - '\$ref': '#/components/schemas/User', - }, - }; - - final property = ApiProperty.fromJson('users', json, []); - - expect(property.type, PropertyType.array); - expect(property.description, 'User list'); - expect(property.items, isNotNull); - expect(property.items!.name, 'User'); - }); - - test('creates ApiProperty with file type', () { - final json = { - 'type': 'file', - 'description': 'User avatar', - 'format': 'binary', - }; - - final property = ApiProperty.fromJson('avatar', json, []); - - expect(property.type, PropertyType.file); - expect(property.description, 'User avatar'); - expect(property.format, 'binary'); - }); - - test('creates ApiProperty with date type', () { - final json = { - 'type': 'string', - 'format': 'date', - 'description': 'User birth date', - }; - - final property = ApiProperty.fromJson('birthDate', json, []); - - expect(property.type, PropertyType.string); - expect(property.format, 'date'); - expect(property.description, 'User birth date'); - }); - - test('creates ApiProperty with date-time type', () { - final json = { - 'type': 'string', - 'format': 'date-time', - 'description': 'User created at', - }; - - final property = ApiProperty.fromJson('createdAt', json, []); - - expect(property.type, PropertyType.string); - expect(property.format, 'date-time'); - expect(property.description, 'User created at'); - }); - }); - - group('Error handling and edge cases', () { - test('ApiParameter fromJson handles missing fields gracefully', () { - final json = {}; - - final param = ApiParameter.fromJson(json); - - expect(param.name, ''); - expect(param.location, ParameterLocation.query); - expect(param.required, false); - expect(param.type, PropertyType.string); - expect(param.description, ''); - expect(param.format, isNull); - expect(param.example, isNull); - expect(param.defaultValue, isNull); - }); - - test('ApiResponse fromJson handles missing fields gracefully', () { - final json = {}; - - final response = ApiResponse.fromJson('200', json); - - expect(response.code, '200'); - expect(response.description, ''); - expect(response.schema, isNull); - expect(response.content, isNull); - }); - - test('ApiRequestBody fromJson handles missing fields gracefully', () { - final json = {}; - - final requestBody = ApiRequestBody.fromJson(json); - - expect(requestBody.description, ''); - expect(requestBody.required, false); - expect(requestBody.content, isNull); - }); - - test('PropertyType fromString handles unknown types', () { - expect(PropertyType.fromString('unknown'), PropertyType.string); - expect(PropertyType.fromString(''), PropertyType.string); - expect(PropertyType.fromString('CUSTOM_TYPE'), PropertyType.string); - }); - - test('ParameterLocation fromString handles unknown locations', () { - expect(ParameterLocation.fromString('unknown'), ParameterLocation.query); - expect(ParameterLocation.fromString(''), ParameterLocation.query); - expect(ParameterLocation.fromString('CUSTOM_LOCATION'), - ParameterLocation.query); - }); - - test('HttpMethod fromString handles unknown methods', () { - expect(HttpMethod.fromString('unknown'), HttpMethod.get); - expect(HttpMethod.fromString(''), HttpMethod.get); - expect(HttpMethod.fromString('CUSTOM_METHOD'), HttpMethod.get); - }); - }); -} diff --git a/tests/optimized_generator_test.dart b/tests/optimized_generator_test.dart new file mode 100644 index 0000000..79b1b54 --- /dev/null +++ b/tests/optimized_generator_test.dart @@ -0,0 +1,392 @@ +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')); + 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')); + 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 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 createFileUpload')); + expect( + generatedCode, contains('static BasePageParameter createPageParam')); + }); + + test('handles method name generation correctly', () { + final generatedCode = generator.generateFromDocument(testDocument); + + expect(generatedCode, contains('Future> getUsers(')); + expect(generatedCode, contains('Future> postUsers(')); + expect(generatedCode, + contains('Future> 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')); + expect(generatedCode, contains('class CustomPageResult')); + expect(generatedCode, contains('Future>')); + }); + + 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; + 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}'); + }); + }); +} diff --git a/tests/performance_test.dart b/tests/performance_test.dart new file mode 100644 index 0000000..cb0be23 --- /dev/null +++ b/tests/performance_test.dart @@ -0,0 +1,488 @@ +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 = {}; + 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 = {}; + 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 cache; + + setUp(() { + cache = SmartCache( + 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 = {}; + 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%的任务成功 + }); + }); + }); +} diff --git a/tests/reference_resolver_test.dart b/tests/reference_resolver_test.dart new file mode 100644 index 0000000..2d02098 --- /dev/null +++ b/tests/reference_resolver_test.dart @@ -0,0 +1,325 @@ +import 'package:swagger_generator_flutter/core/models.dart'; +import 'package:swagger_generator_flutter/utils/reference_resolver.dart'; +import 'package:test/test.dart'; + +void main() { + group('ReferenceResolver', () { + late ReferenceResolver resolver; + + setUp(() { + resolver = ReferenceResolver(); + }); + + tearDown(() { + resolver.clearCache(); + }); + + test('resolves simple models without references', () { + final componentsJson = { + 'schemas': { + 'User': { + 'type': 'object', + 'description': 'User model', + 'properties': { + 'id': {'type': 'integer'}, + 'name': {'type': 'string'}, + }, + 'required': ['id', 'name'], + }, + 'Product': { + 'type': 'object', + 'description': 'Product model', + 'properties': { + 'id': {'type': 'integer'}, + 'title': {'type': 'string'}, + 'price': {'type': 'number'}, + }, + 'required': ['id', 'title'], + }, + }, + }; + + final models = resolver.resolveModels(componentsJson); + + expect(models.length, 2); + expect(models['User'], isNotNull); + expect(models['User']!.name, 'User'); + expect(models['User']!.properties.length, 2); + expect(models['User']!.required, ['id', 'name']); + + expect(models['Product'], isNotNull); + expect(models['Product']!.name, 'Product'); + expect(models['Product']!.properties.length, 3); + expect(models['Product']!.required, ['id', 'title']); + }); + + test('resolves models with allOf composition', () { + final componentsJson = { + 'schemas': { + 'BaseEntity': { + 'type': 'object', + 'properties': { + 'id': {'type': 'integer'}, + 'createdAt': {'type': 'string', 'format': 'date-time'}, + }, + 'required': ['id'], + }, + 'User': { + 'allOf': [ + {'\$ref': '#/components/schemas/BaseEntity'}, + { + 'type': 'object', + 'properties': { + 'name': {'type': 'string'}, + 'email': {'type': 'string'}, + }, + 'required': ['name'], + }, + ], + }, + }, + }; + + final models = resolver.resolveModels(componentsJson); + + expect(models.length, 2); + expect(models['User'], isNotNull); + expect(models['User']!.isAllOf, true); + expect(models['User']!.allOf.length, 2); + + // 检查合并的属性 + expect( + models['User']!.properties.length, 4); // id, createdAt, name, email + expect(models['User']!.properties['id'], isNotNull); + expect(models['User']!.properties['name'], isNotNull); + expect(models['User']!.properties['email'], isNotNull); + expect(models['User']!.properties['createdAt'], isNotNull); + }); + + test('detects and handles circular references', () { + final componentsJson = { + 'schemas': { + 'Node': { + 'type': 'object', + 'properties': { + 'id': {'type': 'integer'}, + 'parent': {'\$ref': '#/components/schemas/Node'}, + 'children': { + 'type': 'array', + 'items': {'\$ref': '#/components/schemas/Node'}, + }, + }, + 'required': ['id'], + }, + }, + }; + + final models = resolver.resolveModels(componentsJson); + + expect(models.length, 1); + expect(models['Node'], isNotNull); + expect(models['Node']!.name, 'Node'); + expect(models['Node']!.properties.length, 3); + + // 循环引用应该被正确处理,不会导致无限递归 + expect(models['Node']!.properties['parent'], isNotNull); + expect(models['Node']!.properties['children'], isNotNull); + }); + + test('handles complex nested structures', () { + final componentsJson = { + 'schemas': { + 'Address': { + 'type': 'object', + 'properties': { + 'street': {'type': 'string'}, + 'city': {'type': 'string'}, + 'coordinates': { + 'type': 'object', + 'properties': { + 'lat': {'type': 'number'}, + 'lng': {'type': 'number'}, + }, + 'required': ['lat', 'lng'], + }, + }, + 'required': ['street', 'city'], + }, + 'User': { + 'type': 'object', + 'properties': { + 'id': {'type': 'integer'}, + 'name': {'type': 'string'}, + 'addresses': { + 'type': 'array', + 'items': {'\$ref': '#/components/schemas/Address'}, + }, + }, + 'required': ['id', 'name'], + }, + }, + }; + + final models = resolver.resolveModels(componentsJson); + + expect(models.length, 2); + expect(models['User'], isNotNull); + expect(models['Address'], isNotNull); + + final userModel = models['User']!; + expect(userModel.properties['addresses'], isNotNull); + expect(userModel.properties['addresses']!.type, PropertyType.array); + + final addressModel = models['Address']!; + expect(addressModel.properties['coordinates'], isNotNull); + expect(addressModel.properties['coordinates']!.type, PropertyType.object); + }); + + test('handles enum models', () { + final componentsJson = { + 'schemas': { + 'Status': { + 'type': 'string', + 'enum': ['active', 'inactive', 'pending'], + 'description': 'User status', + }, + 'Priority': { + 'type': 'integer', + 'enum': [1, 2, 3, 4, 5], + 'description': 'Priority level', + }, + }, + }; + + final models = resolver.resolveModels(componentsJson); + + expect(models.length, 2); + + final statusModel = models['Status']!; + expect(statusModel.isEnum, true); + expect(statusModel.enumValues, ['active', 'inactive', 'pending']); + expect(statusModel.enumType, PropertyType.string); + + final priorityModel = models['Priority']!; + expect(priorityModel.isEnum, true); + expect(priorityModel.enumValues, [1, 2, 3, 4, 5]); + expect(priorityModel.enumType, PropertyType.integer); + }); + + test('handles oneOf with discriminator', () { + final componentsJson = { + 'schemas': { + 'Pet': { + 'oneOf': [ + {'\$ref': '#/components/schemas/Cat'}, + {'\$ref': '#/components/schemas/Dog'}, + ], + 'discriminator': { + 'propertyName': 'petType', + 'mapping': { + 'cat': '#/components/schemas/Cat', + 'dog': '#/components/schemas/Dog', + }, + }, + }, + 'Cat': { + 'type': 'object', + 'properties': { + 'petType': {'type': 'string'}, + 'meowSound': {'type': 'string'}, + }, + 'required': ['petType'], + }, + 'Dog': { + 'type': 'object', + 'properties': { + 'petType': {'type': 'string'}, + 'barkSound': {'type': 'string'}, + }, + 'required': ['petType'], + }, + }, + }; + + final models = resolver.resolveModels(componentsJson); + + expect(models.length, 3); + + final petModel = models['Pet']!; + expect(petModel.isOneOf, true); + expect(petModel.hasDiscriminator, true); + expect(petModel.discriminator?.propertyName, 'petType'); + expect(petModel.discriminator?.mapping.length, 2); + + expect(models['Cat'], isNotNull); + expect(models['Dog'], isNotNull); + }); + + test('respects maximum depth limit', () { + final resolver = ReferenceResolver(maxDepth: 3); + + final componentsJson = { + 'schemas': { + 'DeepNested': { + 'type': 'object', + 'properties': { + 'level1': { + 'type': 'object', + 'properties': { + 'level2': { + 'type': 'object', + 'properties': { + 'level3': { + 'type': 'object', + 'properties': { + 'level4': {'type': 'string'}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }; + + final models = resolver.resolveModels(componentsJson); + + expect(models.length, 1); + expect(models['DeepNested'], isNotNull); + + // 应该能够解析,但深度受限 + final model = models['DeepNested']!; + expect(model.properties['level1'], isNotNull); + }); + + test('handles parsing errors gracefully', () { + final componentsJson = { + 'schemas': { + 'ValidModel': { + 'type': 'object', + 'properties': { + 'id': {'type': 'integer'}, + }, + }, + 'InvalidModel': { + // 故意创建一个可能导致解析错误的结构 + 'properties': 'invalid_properties_value', // 这会导致类型错误 + 'required': 123, // 这也会导致类型错误 + }, + }, + }; + + final models = resolver.resolveModels(componentsJson); + + expect(models.length, 2); + expect(models['ValidModel'], isNotNull); + expect(models['InvalidModel'], isNotNull); + + // 无效模型应该被创建为后备模型 + final invalidModel = models['InvalidModel']!; + expect(invalidModel.description, '解析失败的模型'); + }); + }); +} diff --git a/tests/security_test.dart b/tests/security_test.dart new file mode 100644 index 0000000..ddf4ffc --- /dev/null +++ b/tests/security_test.dart @@ -0,0 +1,504 @@ +import 'package:swagger_generator_flutter/core/models.dart'; +import 'package:test/test.dart'; + +void main() { + group('OAuth2 Flow', () { + test('creates OAuth2Flow with all fields', () { + final json = { + 'authorizationUrl': 'https://example.com/oauth/authorize', + 'tokenUrl': 'https://example.com/oauth/token', + 'refreshUrl': 'https://example.com/oauth/refresh', + 'scopes': { + 'read': 'Read access', + 'write': 'Write access', + 'admin': 'Admin access', + }, + }; + + final flow = OAuth2Flow.fromJson(json); + + expect(flow.authorizationUrl, 'https://example.com/oauth/authorize'); + expect(flow.tokenUrl, 'https://example.com/oauth/token'); + expect(flow.refreshUrl, 'https://example.com/oauth/refresh'); + expect(flow.scopes.length, 3); + expect(flow.scopes['read'], 'Read access'); + expect(flow.scopes['write'], 'Write access'); + expect(flow.scopes['admin'], 'Admin access'); + expect(flow.hasAuthorizationUrl, true); + expect(flow.hasTokenUrl, true); + expect(flow.hasRefreshUrl, true); + expect(flow.hasScopes, true); + }); + + test('creates OAuth2Flow with minimal fields', () { + final json = { + 'tokenUrl': 'https://example.com/oauth/token', + 'scopes': {}, + }; + + final flow = OAuth2Flow.fromJson(json); + + expect(flow.authorizationUrl, isNull); + expect(flow.tokenUrl, 'https://example.com/oauth/token'); + expect(flow.refreshUrl, isNull); + expect(flow.scopes, isEmpty); + expect(flow.hasAuthorizationUrl, false); + expect(flow.hasTokenUrl, true); + expect(flow.hasRefreshUrl, false); + expect(flow.hasScopes, false); + }); + }); + + group('OAuth2 Flows', () { + test('creates OAuth2Flows with multiple flow types', () { + final json = { + 'authorizationCode': { + 'authorizationUrl': 'https://example.com/oauth/authorize', + 'tokenUrl': 'https://example.com/oauth/token', + 'scopes': { + 'read': 'Read access', + 'write': 'Write access', + }, + }, + 'implicit': { + 'authorizationUrl': 'https://example.com/oauth/authorize', + 'scopes': { + 'read': 'Read access', + }, + }, + 'clientCredentials': { + 'tokenUrl': 'https://example.com/oauth/token', + 'scopes': { + 'admin': 'Admin access', + }, + }, + }; + + final flows = OAuth2Flows.fromJson(json); + + expect(flows.hasAnyFlow, true); + expect(flows.availableFlows.length, 3); + expect(flows.availableFlows.contains(OAuth2FlowType.authorizationCode), + true); + expect(flows.availableFlows.contains(OAuth2FlowType.implicit), true); + expect(flows.availableFlows.contains(OAuth2FlowType.clientCredentials), + true); + expect(flows.availableFlows.contains(OAuth2FlowType.password), false); + + expect(flows.authorizationCode, isNotNull); + expect(flows.implicit, isNotNull); + expect(flows.password, isNull); + expect(flows.clientCredentials, isNotNull); + + expect(flows.getFlow(OAuth2FlowType.authorizationCode), isNotNull); + expect(flows.getFlow(OAuth2FlowType.password), isNull); + }); + + test('creates empty OAuth2Flows', () { + final json = {}; + + final flows = OAuth2Flows.fromJson(json); + + expect(flows.hasAnyFlow, false); + expect(flows.availableFlows, isEmpty); + expect(flows.authorizationCode, isNull); + expect(flows.implicit, isNull); + expect(flows.password, isNull); + expect(flows.clientCredentials, isNull); + }); + }); + + group('ApiSecurityScheme', () { + test('creates API Key security scheme', () { + final json = { + 'type': 'apiKey', + 'description': 'API Key authentication', + 'name': 'X-API-Key', + 'in': 'header', + }; + + final scheme = ApiSecurityScheme.fromJson(json); + + expect(scheme.type, SecuritySchemeType.apiKey); + expect(scheme.description, 'API Key authentication'); + expect(scheme.name, 'X-API-Key'); + expect(scheme.location, ApiKeyLocation.header); + expect(scheme.isApiKey, true); + expect(scheme.isHttp, false); + expect(scheme.isOAuth2, false); + expect(scheme.isOpenIdConnect, false); + expect(scheme.apiKeyInfo, 'header:X-API-Key'); + }); + + test('creates HTTP Bearer security scheme', () { + final json = { + 'type': 'http', + 'description': 'Bearer token authentication', + 'scheme': 'bearer', + 'bearerFormat': 'JWT', + }; + + final scheme = ApiSecurityScheme.fromJson(json); + + expect(scheme.type, SecuritySchemeType.http); + expect(scheme.description, 'Bearer token authentication'); + expect(scheme.scheme, 'bearer'); + expect(scheme.bearerFormat, 'JWT'); + expect(scheme.isHttp, true); + expect(scheme.isBearer, true); + expect(scheme.isBasic, false); + expect(scheme.httpAuthInfo, 'bearer (JWT)'); + }); + + test('creates HTTP Basic security scheme', () { + final json = { + 'type': 'http', + 'description': 'Basic authentication', + 'scheme': 'basic', + }; + + final scheme = ApiSecurityScheme.fromJson(json); + + expect(scheme.type, SecuritySchemeType.http); + expect(scheme.scheme, 'basic'); + expect(scheme.isHttp, true); + expect(scheme.isBasic, true); + expect(scheme.isBearer, false); + expect(scheme.httpAuthInfo, 'basic'); + }); + + test('creates OAuth2 security scheme', () { + final json = { + 'type': 'oauth2', + 'description': 'OAuth2 authentication', + 'flows': { + 'authorizationCode': { + 'authorizationUrl': 'https://example.com/oauth/authorize', + 'tokenUrl': 'https://example.com/oauth/token', + 'scopes': { + 'read': 'Read access', + 'write': 'Write access', + }, + }, + }, + }; + + final scheme = ApiSecurityScheme.fromJson(json); + + expect(scheme.type, SecuritySchemeType.oauth2); + expect(scheme.description, 'OAuth2 authentication'); + expect(scheme.isOAuth2, true); + expect(scheme.hasOAuth2Flows, true); + expect(scheme.flows?.hasAnyFlow, true); + expect(scheme.flows?.authorizationCode, isNotNull); + }); + + test('creates OpenID Connect security scheme', () { + final json = { + 'type': 'openIdConnect', + 'description': 'OpenID Connect authentication', + 'openIdConnectUrl': + 'https://example.com/.well-known/openid_configuration', + }; + + final scheme = ApiSecurityScheme.fromJson(json); + + expect(scheme.type, SecuritySchemeType.openIdConnect); + expect(scheme.description, 'OpenID Connect authentication'); + expect(scheme.openIdConnectUrl, + 'https://example.com/.well-known/openid_configuration'); + expect(scheme.isOpenIdConnect, true); + }); + }); + + group('ApiSecurityRequirement', () { + test('creates security requirement with scopes', () { + final json = { + 'oauth2': ['read', 'write'], + 'apiKey': [], + }; + + final requirement = ApiSecurityRequirement.fromJson(json); + + expect(requirement.isNotEmpty, true); + expect(requirement.schemeNames.length, 2); + expect(requirement.hasScheme('oauth2'), true); + expect(requirement.hasScheme('apiKey'), true); + expect(requirement.hasScheme('unknown'), false); + expect(requirement.getScopesForScheme('oauth2'), ['read', 'write']); + expect(requirement.getScopesForScheme('apiKey'), isEmpty); + expect(requirement.getScopesForScheme('unknown'), isEmpty); + }); + + test('creates empty security requirement', () { + final json = {}; + + final requirement = ApiSecurityRequirement.fromJson(json); + + expect(requirement.isEmpty, true); + expect(requirement.isNotEmpty, false); + expect(requirement.schemeNames, isEmpty); + }); + }); + + group('Security Scheme Types', () { + test('converts security scheme type to string', () { + expect(SecuritySchemeType.apiKey.value, 'apiKey'); + expect(SecuritySchemeType.http.value, 'http'); + expect(SecuritySchemeType.oauth2.value, 'oauth2'); + expect(SecuritySchemeType.openIdConnect.value, 'openIdConnect'); + }); + + test('converts string to security scheme type', () { + expect(SecuritySchemeTypeExtension.fromString('apiKey'), + SecuritySchemeType.apiKey); + expect(SecuritySchemeTypeExtension.fromString('http'), + SecuritySchemeType.http); + expect(SecuritySchemeTypeExtension.fromString('oauth2'), + SecuritySchemeType.oauth2); + expect(SecuritySchemeTypeExtension.fromString('openIdConnect'), + SecuritySchemeType.openIdConnect); + expect(SecuritySchemeTypeExtension.fromString('unknown'), + SecuritySchemeType.apiKey); + }); + + test('converts API key location to string', () { + expect(ApiKeyLocation.query.value, 'query'); + expect(ApiKeyLocation.header.value, 'header'); + expect(ApiKeyLocation.cookie.value, 'cookie'); + }); + + test('converts string to API key location', () { + expect(ApiKeyLocationExtension.fromString('query'), ApiKeyLocation.query); + expect( + ApiKeyLocationExtension.fromString('header'), ApiKeyLocation.header); + expect( + ApiKeyLocationExtension.fromString('cookie'), ApiKeyLocation.cookie); + expect( + ApiKeyLocationExtension.fromString('unknown'), ApiKeyLocation.header); + }); + + test('converts OAuth2 flow type to string', () { + expect(OAuth2FlowType.authorizationCode.value, 'authorizationCode'); + expect(OAuth2FlowType.implicit.value, 'implicit'); + expect(OAuth2FlowType.password.value, 'password'); + expect(OAuth2FlowType.clientCredentials.value, 'clientCredentials'); + }); + + test('converts string to OAuth2 flow type', () { + expect(OAuth2FlowTypeExtension.fromString('authorizationCode'), + OAuth2FlowType.authorizationCode); + expect(OAuth2FlowTypeExtension.fromString('implicit'), + OAuth2FlowType.implicit); + expect(OAuth2FlowTypeExtension.fromString('password'), + OAuth2FlowType.password); + expect(OAuth2FlowTypeExtension.fromString('clientCredentials'), + OAuth2FlowType.clientCredentials); + expect(OAuth2FlowTypeExtension.fromString('unknown'), + OAuth2FlowType.authorizationCode); + }); + }); + + group('API Key Authentication Integration', () { + test('creates complete API Key security scheme for header', () { + final json = { + 'type': 'apiKey', + 'description': 'API Key in header', + 'name': 'X-API-Key', + 'in': 'header', + }; + + final scheme = ApiSecurityScheme.fromJson(json); + + expect(scheme.isApiKey, true); + expect(scheme.name, 'X-API-Key'); + expect(scheme.location, ApiKeyLocation.header); + expect(scheme.apiKeyInfo, 'header:X-API-Key'); + }); + + test('creates complete API Key security scheme for query', () { + final json = { + 'type': 'apiKey', + 'description': 'API Key in query parameter', + 'name': 'api_key', + 'in': 'query', + }; + + final scheme = ApiSecurityScheme.fromJson(json); + + expect(scheme.isApiKey, true); + expect(scheme.name, 'api_key'); + expect(scheme.location, ApiKeyLocation.query); + expect(scheme.apiKeyInfo, 'query:api_key'); + }); + + test('creates complete API Key security scheme for cookie', () { + final json = { + 'type': 'apiKey', + 'description': 'API Key in cookie', + 'name': 'session_token', + 'in': 'cookie', + }; + + final scheme = ApiSecurityScheme.fromJson(json); + + expect(scheme.isApiKey, true); + expect(scheme.name, 'session_token'); + expect(scheme.location, ApiKeyLocation.cookie); + expect(scheme.apiKeyInfo, 'cookie:session_token'); + }); + + test('handles API Key security requirement', () { + final requirementJson = { + 'api_key': [], + }; + + final requirement = ApiSecurityRequirement.fromJson(requirementJson); + + expect(requirement.hasScheme('api_key'), true); + expect(requirement.getScopesForScheme('api_key'), isEmpty); + }); + }); + + group('Bearer Token Authentication Integration', () { + test('creates complete Bearer token security scheme', () { + final json = { + 'type': 'http', + 'description': 'Bearer token authentication', + 'scheme': 'bearer', + 'bearerFormat': 'JWT', + }; + + final scheme = ApiSecurityScheme.fromJson(json); + + expect(scheme.isHttp, true); + expect(scheme.isBearer, true); + expect(scheme.scheme, 'bearer'); + expect(scheme.bearerFormat, 'JWT'); + expect(scheme.httpAuthInfo, 'bearer (JWT)'); + }); + + test('creates Bearer token without format', () { + final json = { + 'type': 'http', + 'description': 'Bearer token authentication', + 'scheme': 'bearer', + }; + + final scheme = ApiSecurityScheme.fromJson(json); + + expect(scheme.isHttp, true); + expect(scheme.isBearer, true); + expect(scheme.scheme, 'bearer'); + expect(scheme.bearerFormat, isNull); + expect(scheme.httpAuthInfo, 'bearer'); + }); + + test('handles Bearer token security requirement', () { + final requirementJson = { + 'bearerAuth': [], + }; + + final requirement = ApiSecurityRequirement.fromJson(requirementJson); + + expect(requirement.hasScheme('bearerAuth'), true); + expect(requirement.getScopesForScheme('bearerAuth'), isEmpty); + }); + }); + + group('HTTP Authentication Schemes', () { + test('creates Basic authentication security scheme', () { + final json = { + 'type': 'http', + 'description': 'Basic authentication', + 'scheme': 'basic', + }; + + final scheme = ApiSecurityScheme.fromJson(json); + + expect(scheme.type, SecuritySchemeType.http); + expect(scheme.isHttp, true); + expect(scheme.isBasic, true); + expect(scheme.isBearer, false); + expect(scheme.scheme, 'basic'); + expect(scheme.httpAuthInfo, 'basic'); + }); + + test('creates Digest authentication security scheme', () { + final json = { + 'type': 'http', + 'description': 'Digest authentication', + 'scheme': 'digest', + }; + + final scheme = ApiSecurityScheme.fromJson(json); + + expect(scheme.type, SecuritySchemeType.http); + expect(scheme.isHttp, true); + expect(scheme.isBasic, false); + expect(scheme.isBearer, false); + expect(scheme.scheme, 'digest'); + expect(scheme.httpAuthInfo, 'digest'); + }); + + test('creates custom HTTP authentication security scheme', () { + final json = { + 'type': 'http', + 'description': 'Custom HTTP authentication', + 'scheme': 'custom', + }; + + final scheme = ApiSecurityScheme.fromJson(json); + + expect(scheme.type, SecuritySchemeType.http); + expect(scheme.isHttp, true); + expect(scheme.isBasic, false); + expect(scheme.isBearer, false); + expect(scheme.scheme, 'custom'); + expect(scheme.httpAuthInfo, 'custom'); + }); + + test('handles HTTP authentication security requirement', () { + final requirementJson = { + 'basicAuth': [], + 'digestAuth': [], + }; + + final requirement = ApiSecurityRequirement.fromJson(requirementJson); + + expect(requirement.hasScheme('basicAuth'), true); + expect(requirement.hasScheme('digestAuth'), true); + expect(requirement.getScopesForScheme('basicAuth'), isEmpty); + expect(requirement.getScopesForScheme('digestAuth'), isEmpty); + }); + }); + + group('Mixed Security Requirements', () { + test('handles multiple security schemes in one requirement', () { + final requirementJson = { + 'oauth2': ['read', 'write'], + 'apiKey': [], + 'basicAuth': [], + }; + + final requirement = ApiSecurityRequirement.fromJson(requirementJson); + + expect(requirement.schemeNames.length, 3); + expect(requirement.hasScheme('oauth2'), true); + expect(requirement.hasScheme('apiKey'), true); + expect(requirement.hasScheme('basicAuth'), true); + expect(requirement.getScopesForScheme('oauth2'), ['read', 'write']); + expect(requirement.getScopesForScheme('apiKey'), isEmpty); + expect(requirement.getScopesForScheme('basicAuth'), isEmpty); + }); + + test('handles empty security requirement (no authentication)', () { + final requirementJson = {}; + + final requirement = ApiSecurityRequirement.fromJson(requirementJson); + + expect(requirement.isEmpty, true); + expect(requirement.schemeNames, isEmpty); + }); + }); +} diff --git a/tests/simple_generator_test.dart b/tests/simple_generator_test.dart new file mode 100644 index 0000000..3369cc2 --- /dev/null +++ b/tests/simple_generator_test.dart @@ -0,0 +1,442 @@ +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'; + +void main() { + group('Simple Generator Tests', () { + late SwaggerDocument simpleDocument; + + setUp(() { + simpleDocument = SwaggerDocument( + title: 'Simple Test API', + version: '1.0.0', + description: 'A simple test API', + servers: [ + ApiServer( + url: 'https://api.example.com', + description: 'Test server', + ), + ], + 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', + content: { + 'application/json': ApiMediaType( + schema: {'type': 'array'}, + ), + }, + ), + }, + ), + '/users/{id}': ApiPath( + path: '/users/{id}', + method: HttpMethod.get, + summary: 'Get user by ID', + description: 'Get a specific user', + operationId: 'getUserById', + tags: ['users'], + parameters: [ + ApiParameter( + name: 'id', + location: ParameterLocation.path, + required: true, + type: PropertyType.integer, + description: 'User ID', + ), + ], + responses: { + '200': ApiResponse( + code: '200', + description: 'User found', + content: { + 'application/json': ApiMediaType( + schema: {'type': 'object'}, + ), + }, + ), + }, + ), + }, + 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: [], + ); + }); + + group('RetrofitApiGenerator Basic Tests', () { + test('generates basic API structure', () { + final generator = RetrofitApiGenerator( + className: 'TestApi', + splitByTags: false, + ); + + final result = generator.generateFromDocument(simpleDocument); + + expect(result, isNotEmpty); + expect(result, contains('Simple Test API')); + expect(result, contains('TestApi')); + }); + + test('generates imports correctly', () { + final generator = RetrofitApiGenerator(); + + final result = generator.generateFromDocument(simpleDocument); + + expect(result, contains('import')); + expect(result, contains('dio')); + expect(result, contains('retrofit')); + }); + + test('generates path annotations', () { + final generator = RetrofitApiGenerator(); + + final result = generator.generateFromDocument(simpleDocument); + + expect(result, contains('@GET')); + expect(result, contains('/users')); + expect(result, contains('/users/{id}')); + }); + + test('generates parameter annotations', () { + final generator = RetrofitApiGenerator(); + + final result = generator.generateFromDocument(simpleDocument); + + expect(result, contains('@Path')); + expect(result, contains('id')); + }); + + test('handles split by tags', () { + final generator = RetrofitApiGenerator( + splitByTags: true, + ); + + final result = generator.generateFromDocument(simpleDocument); + + expect(result, isNotEmpty); + // Should generate modular structure when split by tags + }); + }); + + 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(); + final result = generator.generateFromDocument(simpleDocument); + + // Check for basic Dart syntax + expect(result, isNot(contains(';;'))); // No double semicolons + expect(result, isNot(contains(',,'))); // No double commas + + // Check for proper class declarations + final classCount = 'class '.allMatches(result).length; + final abstractClassCount = 'abstract class '.allMatches(result).length; + expect(classCount + abstractClassCount, greaterThan(0)); + }); + + test('handles empty paths gracefully', () { + final emptyDocument = SwaggerDocument( + title: 'Empty API', + version: '1.0.0', + description: 'Empty API', + servers: [], + components: ApiComponents(schemas: {}, securitySchemes: {}), + paths: {}, + models: {}, + controllers: {}, + security: [], + ); + + final generator = RetrofitApiGenerator(); + final result = generator.generateFromDocument(emptyDocument); + + expect(result, isNotEmpty); + expect(result, contains('Empty API')); + }); + + test('handles special characters in API names', () { + final specialDocument = SwaggerDocument( + title: 'API-with_Special.Characters', + version: '1.0.0', + description: 'Test', + servers: [], + components: ApiComponents(schemas: {}, securitySchemes: {}), + paths: { + '/special-endpoint': ApiPath( + path: '/special-endpoint', + method: HttpMethod.get, + summary: 'Special endpoint', + description: 'Test', + operationId: 'getSpecial', + tags: ['special'], + parameters: [], + responses: { + '200': ApiResponse( + code: '200', + description: 'Success', + ), + }, + ), + }, + models: {}, + controllers: {}, + security: [], + ); + + final generator = RetrofitApiGenerator(); + expect(() => generator.generateFromDocument(specialDocument), + 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', + version: '1.0.0', + description: 'Test', + servers: [], + components: ApiComponents(schemas: {}, securitySchemes: {}), + paths: { + '/search': ApiPath( + path: '/search', + method: HttpMethod.get, + summary: 'Search', + description: 'Search endpoint', + 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', + ), + ], + responses: { + '200': ApiResponse( + code: '200', + description: 'Success', + ), + }, + ), + }, + models: {}, + controllers: {}, + security: [], + ); + + final generator = RetrofitApiGenerator(); + final result = + generator.generateFromDocument(documentWithOptionalParams); + + // Required parameters should not be nullable + expect(result, contains('String query')); + + // Optional parameters should be nullable + expect(result, contains('int? page')); + }); + }); + + group('Performance Tests', () { + test('handles medium-sized documents efficiently', () { + // Create a document with multiple paths + final paths = {}; + for (int i = 0; i < 50; i++) { + paths['/resource$i'] = ApiPath( + path: '/resource$i', + method: HttpMethod.get, + summary: 'Get resource $i', + description: 'Get resource $i', + operationId: 'getResource$i', + tags: ['resources'], + parameters: [], + responses: { + '200': ApiResponse( + code: '200', + description: 'Success', + ), + }, + ); + } + + final largeDocument = SwaggerDocument( + title: 'Large API', + version: '1.0.0', + description: 'Large API', + servers: [], + components: ApiComponents(schemas: {}, securitySchemes: {}), + paths: paths, + models: {}, + controllers: {}, + security: [], + ); + + final generator = RetrofitApiGenerator(); + final stopwatch = Stopwatch()..start(); + + final result = generator.generateFromDocument(largeDocument); + + stopwatch.stop(); + + expect(result, isNotEmpty); + expect(stopwatch.elapsedMilliseconds, + lessThan(5000)); // Should complete within 5 seconds + expect(result.length, + greaterThan(1000)); // Should generate substantial code + }); + + test('memory usage is reasonable', () { + final generator = RetrofitApiGenerator(); + final result = generator.generateFromDocument(simpleDocument); + + // Basic memory usage check - result should not be excessively large + expect(result.length, + lessThan(100000)); // Less than 100KB for simple document + }); + }); + }); +} diff --git a/tests/string_utils_test.dart b/tests/string_utils_test.dart index f1b6638..18994b0 100644 --- a/tests/string_utils_test.dart +++ b/tests/string_utils_test.dart @@ -1,6 +1,5 @@ -import 'package:test/test.dart'; - import 'package:swagger_generator_flutter/utils/string_utils.dart'; +import 'package:test/test.dart'; void main() { group('StringUtils', () { diff --git a/validate.sh b/validate.sh new file mode 100755 index 0000000..4dc297d --- /dev/null +++ b/validate.sh @@ -0,0 +1,280 @@ +#!/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 "$@" diff --git a/validate_standards.dart b/validate_standards.dart new file mode 100644 index 0000000..1a503e2 --- /dev/null +++ b/validate_standards.dart @@ -0,0 +1,239 @@ +#!/usr/bin/env dart + +/// Augment 代码生成规范验证脚本 +/// 用于验证生成的代码是否符合规范要求 + +import 'dart:convert'; +import 'dart:io'; + +void main(List 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 errors = []; + final List warnings = []; + + /// 验证所有规范 + Future 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 _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; + + // 检查 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?; + if (components != null) { + final schemas = components['schemas'] as Map?; + if (schemas == null || schemas.isEmpty) { + warnings.add('components/schemas 为空,可能影响类型生成'); + } + } + + print(' ✅ swagger.json 格式正确'); + } catch (e) { + errors.add('swagger.json 格式错误:$e'); + } + } + + /// 验证生成的文件 + Future _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 _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 _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 _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]}'); + } + } + } +}