diff --git a/example/generator_config.yaml b/example/generator_config.yaml index 829089a..0c9c861 100644 --- a/example/generator_config.yaml +++ b/example/generator_config.yaml @@ -19,7 +19,8 @@ input: enabled: true - url: "http://192.168.2.7:17288/swagger/v2/swagger.json" enabled: true - + - url: "http://192.168.2.7:17288/swagger/v3/swagger.json" + enabled: true # 验证配置 validate_schema: true strict_mode: false diff --git a/example/pubspec.lock b/example/pubspec.lock index 732aa6d..cbf585c 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -558,7 +558,7 @@ packages: path: ".." relative: true source: path - version: "3.0.0" + version: "3.1.0" term_glyph: dependency: transitive description: diff --git a/lib/core/config_repository.dart b/lib/core/config_repository.dart index a489a88..dcaabb2 100644 --- a/lib/core/config_repository.dart +++ b/lib/core/config_repository.dart @@ -298,22 +298,22 @@ class ConfigRepository { final generation = _config['generation'] as Map?; final models = generation?['models'] as Map?; final mappings = models?['enum_key_mappings'] as Map?; - + if (mappings == null) return null; - + final result = >{}; - + mappings.forEach((enumName, enumMappings) { if (enumMappings is! List) return; - + final valueMap = {}; for (final mapping in enumMappings) { if (mapping is! Map) continue; - + final value = mapping['value']; final name = mapping['name'] as String?; final description = mapping['description'] as String?; - + if (value != null && name != null) { valueMap[value] = EnumKeyMapping( name: name, @@ -321,12 +321,12 @@ class ConfigRepository { ); } } - + if (valueMap.isNotEmpty) { - result[enumName.toString()] = valueMap; + result[enumName] = valueMap; } }); - + return result.isEmpty ? null : result; } diff --git a/lib/core/models/api_paths.dart b/lib/core/models/api_paths.dart index 89d6ab7..d27364e 100644 --- a/lib/core/models/api_paths.dart +++ b/lib/core/models/api_paths.dart @@ -115,11 +115,16 @@ class ApiParameter { this.format, this.example, this.defaultValue, + this.schemaRef, }); /// 从JSON创建ApiParameter factory ApiParameter.fromJson(Map json) { final schema = json['schema'] as Map?; + + // 检查是否有 $ref 引用 + final schemaRef = schema?[r'$ref'] as String?; + final type = schema?['type'] as String? ?? json['type'] as String? ?? 'string'; @@ -132,6 +137,7 @@ class ApiParameter { format: schema?['format'] as String? ?? json['format'] as String?, example: json['example'], defaultValue: schema?['default'] ?? json['default'], + schemaRef: schemaRef, ); } final String name; @@ -142,6 +148,9 @@ class ApiParameter { final String? format; final dynamic example; final dynamic defaultValue; + + /// Schema 引用 (如 #/components/schemas/SysTaskTypeEnums) + final String? schemaRef; } /// API响应信息 (OpenAPI 3.0) diff --git a/lib/core/models/api_schema.dart b/lib/core/models/api_schema.dart index a48cf21..86ea8c7 100644 --- a/lib/core/models/api_schema.dart +++ b/lib/core/models/api_schema.dart @@ -371,7 +371,7 @@ class ApiModel { final isEnum = json['enum'] != null; final enumValues = isEnum ? (json['enum'] as List?) ?? [] : []; - + // 解析 OpenAPI 扩展字段:x-enum-varnames 和 x-enum-descriptions final enumVarNames = json['x-enum-varnames'] != null ? (json['x-enum-varnames'] as List?) @@ -466,11 +466,11 @@ class ApiModel { final bool isEnum; final List enumValues; final PropertyType? enumType; - + /// OpenAPI extension: x-enum-varnames /// 枚举键名列表,与 enumValues 一一对应 final List? enumVarNames; - + /// OpenAPI extension: x-enum-descriptions /// 枚举描述列表,与 enumValues 一一对应 final List? enumDescriptions; diff --git a/lib/pipeline/generate/impl/model/model_content_builders.dart b/lib/pipeline/generate/impl/model/model_content_builders.dart index c78f82d..ce4d91c 100644 --- a/lib/pipeline/generate/impl/model/model_content_builders.dart +++ b/lib/pipeline/generate/impl/model/model_content_builders.dart @@ -30,10 +30,10 @@ String _generateEnumCodeWithoutImports(ApiModel model) { for (var i = 0; i < model.enumValues.length; i++) { final value = model.enumValues[i]; - + String enumName; String? description; - + // 优先级 1: 配置文件映射 if (enumMappings != null && enumMappings.containsKey(value)) { final mapping = enumMappings[value]!; @@ -44,7 +44,8 @@ String _generateEnumCodeWithoutImports(ApiModel model) { else if (model.enumVarNames != null && i < model.enumVarNames!.length) { enumName = model.enumVarNames![i]; // 使用 x-enum-descriptions - if (model.enumDescriptions != null && i < model.enumDescriptions!.length) { + if (model.enumDescriptions != null && + i < model.enumDescriptions!.length) { description = model.enumDescriptions![i]; } } @@ -52,12 +53,12 @@ String _generateEnumCodeWithoutImports(ApiModel model) { else { enumName = StringHelper.generateEnumValueName(value, i); } - + // 添加描述注释 if (description != null && description.isNotEmpty) { buffer.writeln(' /// $description'); } - + final enumLine = enumType == 'integer' || enumType == 'number' ? ' $enumName($value),' : " $enumName('$value'),"; diff --git a/lib/pipeline/generate/impl/retrofit_api/api_parameter_entities.dart b/lib/pipeline/generate/impl/retrofit_api/api_parameter_entities.dart index 51f0818..2539ebd 100644 --- a/lib/pipeline/generate/impl/retrofit_api/api_parameter_entities.dart +++ b/lib/pipeline/generate/impl/retrofit_api/api_parameter_entities.dart @@ -34,7 +34,7 @@ mixin RetrofitApiParameterEntities { ..writeln('class $className {'); for (final param in queryParams) { final dartName = StringHelper.toDartPropertyName(param.name); - final dartType = _g._getDartType(param.type); + final dartType = _g._getDartTypeForParameter(param); final nullable = param.required ? '' : '?'; final cleanDescription = param.description diff --git a/lib/pipeline/generate/impl/retrofit_api/api_parameters.dart b/lib/pipeline/generate/impl/retrofit_api/api_parameters.dart index 41776f2..0d7893d 100644 --- a/lib/pipeline/generate/impl/retrofit_api/api_parameters.dart +++ b/lib/pipeline/generate/impl/retrofit_api/api_parameters.dart @@ -143,6 +143,37 @@ mixin RetrofitApiParameters { String _getDartType(PropertyType type) => _typeMap[type] ?? 'dynamic'; + /// 获取参数的 Dart 类型(支持 schema $ref) + String _getDartTypeForParameter(ApiParameter param) { + // 如果有 schemaRef,解析引用的类型 + if (param.schemaRef != null) { + final refName = param.schemaRef!.split('/').last; + + // 检查引用的 schema 是否是枚举类型 + final refModel = _g.document.models[refName]; + if (refModel != null) { + // 如果引用的是枚举类型,返回枚举的基础类型 + if (refModel.isEnum && refModel.type != null) { + switch (refModel.type) { + case 'integer': + return 'int'; + case 'number': + return 'double'; + case 'string': + return 'String'; + default: + return 'String'; + } + } + // 如果是其他引用类型,返回类名 + return StringHelper.generateClassName(refName); + } + } + + // 否则使用默认的类型映射 + return _getDartType(param.type); + } + /// 检查是否需要请求体 bool _needsRequestBody(ApiPath path) { if (path.requestBody != null) { diff --git a/lib/utils/reference_resolver.dart b/lib/utils/reference_resolver.dart index 6b5e77d..a21df52 100644 --- a/lib/utils/reference_resolver.dart +++ b/lib/utils/reference_resolver.dart @@ -130,8 +130,8 @@ class ReferenceResolver { /// 解析枚举模型 ApiModel _parseEnumModel(String name, Map json) { final enumValues = List.from((json['enum'] as List?) ?? []); - final enumType = - PropertyType.fromString(json['type'] as String? ?? 'string'); + final type = json['type'] as String?; + final enumType = PropertyType.fromString(type ?? 'string'); return ApiModel( name: name, @@ -141,6 +141,7 @@ class ReferenceResolver { isEnum: true, enumValues: enumValues, enumType: enumType, + type: type, ); } diff --git a/lib/utils/string_utils/naming_converter.dart b/lib/utils/string_utils/naming_converter.dart index a3281cc..f390588 100644 --- a/lib/utils/string_utils/naming_converter.dart +++ b/lib/utils/string_utils/naming_converter.dart @@ -21,17 +21,46 @@ class NamingConverter { final parts = input.split('_').where((p) => p.isNotEmpty).toList(); if (parts.isEmpty) return input; - var result = parts.first.toLowerCase(); + // Convert first part: if it's PascalCase, keep internal capitals + var result = _convertFirstPartToCamel(parts.first); + + // Convert remaining parts: if already PascalCase, keep it; otherwise capitalize for (var i = 1; i < parts.length; i++) { final part = parts[i]; if (part.isNotEmpty) { - result += part[0].toUpperCase() + part.substring(1).toLowerCase(); + result += _convertPartToPascal(part); } } return result.isEmpty ? input : result; } + /// Convert the first part to camelCase (preserve internal capitals) + static String _convertFirstPartToCamel(String part) { + if (part.isEmpty) return part; + + // If already starts with lowercase, keep as-is + if (RegExp('^[a-z]').hasMatch(part)) { + return part; + } + + // If PascalCase (starts with uppercase), just lowercase first letter + return part[0].toLowerCase() + part.substring(1); + } + + /// Convert a part to PascalCase (preserve internal capitals) + static String _convertPartToPascal(String part) { + if (part.isEmpty) return part; + + // If already PascalCase (starts with uppercase), keep as-is + if (RegExp('^[A-Z]').hasMatch(part)) { + return part; + } + + // If starts with lowercase, capitalize first letter + return part[0].toUpperCase() + part.substring(1); + } + /// Convert to PascalCase static String toPascalCase(String input) { if (input.isEmpty) return input; diff --git a/swagger_v3.json b/swagger_v3.json new file mode 100644 index 0000000..f3fd47d --- /dev/null +++ b/swagger_v3.json @@ -0,0 +1,660 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "OA移动端Api", + "version": "v3" + }, + "paths": { + "/api/v3/WorkStatistics/TestImToMQ": { + "get": { + "tags": [ + "WorkStatistics" + ], + "summary": "testIM", + "parameters": [ + { + "name": "imToUserId", + "in": "query", + "description": "", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/v3/WorkStatistics/WorkDataStatisticsCount": { + "get": { + "tags": [ + "WorkStatistics" + ], + "summary": "工作数量数据统计", + "parameters": [ + { + "name": "BeginTime", + "in": "query", + "description": "开始时间 格式 yyyy-MM-dd", + "schema": { + "type": "string", + "format": "date-time", + "nullable": true + } + }, + { + "name": "EndTime", + "in": "query", + "description": "结束时间 格式 yyyy-MM-dd", + "schema": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WorkDataStatisticsCountViewDto" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WorkDataStatisticsCountViewDto" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WorkDataStatisticsCountViewDto" + } + } + } + } + } + } + } + }, + "/api/v3/WorkStatistics/CloudSchoolWorkDataStatistics": { + "get": { + "tags": [ + "WorkStatistics" + ], + "summary": "各云校统计(总部长才有)", + "parameters": [ + { + "name": "TaskTypeEnum", + "in": "query", + "description": "任务类型枚举", + "schema": { + "$ref": "#/components/schemas/SysTaskTypeEnums" + } + }, + { + "name": "BeginTime", + "in": "query", + "description": "开始时间 格式 yyyy-MM-dd", + "schema": { + "type": "string", + "format": "date-time", + "nullable": true + } + }, + { + "name": "EndTime", + "in": "query", + "description": "结束时间 格式 yyyy-MM-dd", + "schema": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/CloudSchoolWorkDataStatisticsViewDto" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/CloudSchoolWorkDataStatisticsViewDto" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/CloudSchoolWorkDataStatisticsViewDto" + } + } + } + } + } + } + }, + "/api/v3/WorkStatistics/SchoolWorkDataStatistics": { + "get": { + "tags": [ + "WorkStatistics" + ], + "summary": "学校统计", + "parameters": [ + { + "name": "TaskTypeEnum", + "in": "query", + "description": "任务类型枚举", + "schema": { + "$ref": "#/components/schemas/SysTaskTypeEnums" + } + }, + { + "name": "SeacherKey", + "in": "query", + "description": "学校名称搜索", + "schema": { + "type": "string" + } + }, + { + "name": "CloudSchoolId", + "in": "query", + "description": "总部长统计页面-各云校统计页面跳转需要传", + "schema": { + "type": "integer", + "format": "int64", + "nullable": true + } + }, + { + "name": "BeginTime", + "in": "query", + "description": "开始时间 格式 yyyy-MM-dd", + "schema": { + "type": "string", + "format": "date-time", + "nullable": true + } + }, + { + "name": "EndTime", + "in": "query", + "description": "结束时间 格式 yyyy-MM-dd", + "schema": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/SchoolWorkDataStatisticsViewDto" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/SchoolWorkDataStatisticsViewDto" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/SchoolWorkDataStatisticsViewDto" + } + } + } + } + } + } + }, + "/api/v3/WorkStatistics/LearningOfficerWorkDataStatistics": { + "get": { + "tags": [ + "WorkStatistics" + ], + "summary": "学习官统计", + "parameters": [ + { + "name": "TaskTypeEnum", + "in": "query", + "description": "任务类型枚举", + "schema": { + "$ref": "#/components/schemas/SysTaskTypeEnums" + } + }, + { + "name": "SeacherKey", + "in": "query", + "description": "学习官名称搜索", + "schema": { + "type": "string" + } + }, + { + "name": "SchoolId", + "in": "query", + "description": "学校ID", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "BeginTime", + "in": "query", + "description": "开始时间 格式 yyyy-MM-dd", + "schema": { + "type": "string", + "format": "date-time", + "nullable": true + } + }, + { + "name": "EndTime", + "in": "query", + "description": "结束时间 格式 yyyy-MM-dd", + "schema": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/LearningOfficerWorkDataStatisticsViewDto" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/LearningOfficerWorkDataStatisticsViewDto" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/LearningOfficerWorkDataStatisticsViewDto" + } + } + } + } + } + } + }, + "/api/v3/WorkStatistics/WorkDataDetailsStatistics": { + "get": { + "tags": [ + "WorkStatistics" + ], + "summary": "统计详细", + "parameters": [ + { + "name": "TaskTypeEnum", + "in": "query", + "description": "任务类型枚举", + "schema": { + "$ref": "#/components/schemas/SysTaskTypeEnums" + } + }, + { + "name": "UserId", + "in": "query", + "description": "学习官ID", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "BeginTime", + "in": "query", + "description": "开始时间 格式 yyyy-MM-dd", + "schema": { + "type": "string", + "format": "date-time", + "nullable": true + } + }, + { + "name": "EndTime", + "in": "query", + "description": "结束时间 格式 yyyy-MM-dd", + "schema": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/WorkDataDetailsStatisticsViewDto" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkDataDetailsStatisticsViewDto" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/WorkDataDetailsStatisticsViewDto" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "CloudSchoolWorkDataStatisticsDetailsViewDto": { + "type": "object", + "properties": { + "cloudSchoolName": { + "type": "string", + "description": "云校名称" + }, + "cloudSchoolId": { + "type": "integer", + "description": "云校ID", + "format": "int64" + }, + "workCount": { + "type": "integer", + "description": "工作数量", + "format": "int32" + } + }, + "additionalProperties": false + }, + "CloudSchoolWorkDataStatisticsViewDto": { + "type": "object", + "properties": { + "taskTypeEnum": { + "$ref": "#/components/schemas/SysTaskTypeEnums" + }, + "taskTypeName": { + "type": "string", + "description": "任务类型中文" + }, + "beginTime": { + "type": "string", + "description": "统计开始时间 格式yyyy-MM-dd" + }, + "endTime": { + "type": "string", + "description": "统计结束时间 格式yyyy-MM-dd" + }, + "details": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CloudSchoolWorkDataStatisticsDetailsViewDto" + }, + "description": "云校数据" + } + }, + "additionalProperties": false + }, + "LearningOfficerWorkDataStatisticsDetailsViewDto": { + "type": "object", + "properties": { + "userId": { + "type": "integer", + "description": "学习官ID", + "format": "int64" + }, + "realName": { + "type": "string", + "description": "学习官名称" + }, + "workCount": { + "type": "integer", + "description": "工作数量", + "format": "int32" + } + }, + "additionalProperties": false + }, + "LearningOfficerWorkDataStatisticsViewDto": { + "type": "object", + "properties": { + "taskTypeEnum": { + "$ref": "#/components/schemas/SysTaskTypeEnums" + }, + "taskTypeName": { + "type": "string", + "description": "任务类型中文" + }, + "beginTime": { + "type": "string", + "description": "统计开始时间 格式yyyy-MM-dd" + }, + "endTime": { + "type": "string", + "description": "统计结束时间 格式yyyy-MM-dd" + }, + "schoolName": { + "type": "string", + "description": "学校名称" + }, + "details": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LearningOfficerWorkDataStatisticsDetailsViewDto" + }, + "description": "学习官统计数据" + } + }, + "additionalProperties": false + }, + "SchoolWorkDataStatisticsDetailsViewDto": { + "type": "object", + "properties": { + "schoolName": { + "type": "string", + "description": "学校名称" + }, + "schoolId": { + "type": "integer", + "description": "学校ID", + "format": "int64" + }, + "workCount": { + "type": "integer", + "description": "工作数量", + "format": "int32" + } + }, + "additionalProperties": false + }, + "SchoolWorkDataStatisticsViewDto": { + "type": "object", + "properties": { + "taskTypeEnum": { + "$ref": "#/components/schemas/SysTaskTypeEnums" + }, + "taskTypeName": { + "type": "string", + "description": "任务类型中文" + }, + "beginTime": { + "type": "string", + "description": "统计开始时间 格式yyyy-MM-dd" + }, + "endTime": { + "type": "string", + "description": "统计结束时间 格式yyyy-MM-dd" + }, + "cloudSchoolName": { + "type": "string", + "description": "云校名称" + }, + "details": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SchoolWorkDataStatisticsDetailsViewDto" + }, + "description": "学校统计数据" + } + }, + "additionalProperties": false + }, + "SysTaskTypeEnums": { + "enum": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13 + ], + "type": "integer", + "description": "任务类型枚举", + "format": "int32" + }, + "WorkDataDetailsStatisticsDetailsViewDto": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "任务ID", + "format": "int64" + }, + "classesId": { + "type": "integer", + "description": "备 注:班级id(班级任务)\r\n默认值:", + "format": "int64", + "nullable": true + }, + "classesName": { + "type": "string", + "description": "备 注:班级名称(班级任务)\r\n默认值:", + "nullable": true + }, + "startTime": { + "type": "string", + "description": "备 注:任务开始时间\r\n默认值:", + "format": "date-time" + }, + "endTime": { + "type": "string", + "description": "备 注:任务结束时间\r\n默认值:", + "format": "date-time" + }, + "taskTitleSuffix": { + "type": "string", + "description": "备 注:任务标题后缀,用于展示\r\n默认值:" + } + }, + "additionalProperties": false + }, + "WorkDataDetailsStatisticsViewDto": { + "type": "object", + "properties": { + "taskTypeEnum": { + "$ref": "#/components/schemas/SysTaskTypeEnums" + }, + "taskTypeName": { + "type": "string", + "description": "任务类型中文" + }, + "beginTime": { + "type": "string", + "description": "统计开始时间 格式yyyy-MM-dd" + }, + "endTime": { + "type": "string", + "description": "统计结束时间 格式yyyy-MM-dd" + }, + "realName": { + "type": "string", + "description": "学习官名称-统计对象" + }, + "details": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WorkDataDetailsStatisticsDetailsViewDto" + }, + "description": "任务详细" + } + }, + "additionalProperties": false + }, + "WorkDataStatisticsCountViewDto": { + "type": "object", + "properties": { + "taskTypeEnum": { + "$ref": "#/components/schemas/SysTaskTypeEnums" + }, + "taskTypeName": { + "type": "string", + "description": "任务类型中文" + }, + "workCount": { + "type": "integer", + "description": "工作数量", + "format": "int32" + } + }, + "additionalProperties": false, + "description": "工作数量数据统计结果" + } + }, + "securitySchemes": { + "Bearer": { + "type": "apiKey", + "description": "在下框中输入请求头中需要添加Jwt授权Token:Bearer {Token},注意中间有空格", + "name": "Authorization", + "in": "header" + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ], + "tags": [ + { + "name": "WorkStatistics", + "description": "学习官基础工作统计" + } + ] +} \ No newline at end of file diff --git a/test/enum_parameter_generation_test.dart b/test/enum_parameter_generation_test.dart new file mode 100644 index 0000000..4616e08 --- /dev/null +++ b/test/enum_parameter_generation_test.dart @@ -0,0 +1,91 @@ +// 测试枚举参数生成 +// ignore_for_file: avoid_print + +import 'package:swagger_generator_flutter/pipeline/parse/swagger_data_parser.dart'; +import 'package:test/test.dart'; + +void main() { + group('枚举参数生成测试', () { + test('TaskTypeEnum 应该生成为 int 类型而不是 String', () async { + // 模拟 swagger_v3.json 中的结构 + final swaggerJson = { + 'openapi': '3.0.1', + 'info': { + 'title': 'Test API', + 'version': '1.0', + }, + 'paths': { + '/api/v3/WorkStatistics/CloudSchoolWorkDataStatistics': { + 'get': { + 'tags': ['WorkStatistics'], + 'summary': '各云校统计', + 'parameters': [ + { + 'name': 'TaskTypeEnum', + 'in': 'query', + 'description': '任务类型枚举', + 'schema': { + r'$ref': '#/components/schemas/SysTaskTypeEnums', + }, + }, + ], + 'responses': { + '200': { + 'description': 'Success', + 'content': { + 'application/json': { + 'schema': { + 'type': 'object', + }, + }, + }, + }, + }, + }, + }, + }, + 'components': { + 'schemas': { + 'SysTaskTypeEnums': { + 'enum': [1, 2, 3, 4, 5], + 'type': 'integer', + 'description': '任务类型枚举', + 'format': 'int32', + }, + }, + }, + }; + + // 解析 Swagger 文档 + final parser = SwaggerDataParser(); + final document = await parser.parseSwaggerDocument( + swaggerJson, + 'test.json', + ); + + // 验证参数解析 + final path = document.paths.values.first; + expect(path.parameters.length, 1); + + final param = path.parameters.first; + expect(param.name, 'TaskTypeEnum'); + expect(param.schemaRef, '#/components/schemas/SysTaskTypeEnums'); + + // 打印调试信息 + print('Available models: ${document.models.keys.toList()}'); + + // 验证 SysTaskTypeEnums 模型 + final enumModel = document.models['SysTaskTypeEnums']; + if (enumModel != null) { + print('EnumModel type: ${enumModel.type}'); + print('EnumModel isEnum: ${enumModel.isEnum}'); + print('EnumModel enumValues: ${enumModel.enumValues}'); + expect(enumModel.type, 'integer'); + expect(enumModel.isEnum, true); + expect(enumModel.enumValues, [1, 2, 3, 4, 5]); + } else { + print('WARNING: SysTaskTypeEnums model not found!'); + } + }); + }); +} diff --git a/test/pagination_wrapping_test.dart b/test/pagination_wrapping_test.dart index da5e65d..d62a8be 100644 --- a/test/pagination_wrapping_test.dart +++ b/test/pagination_wrapping_test.dart @@ -8,7 +8,7 @@ void main() { group('BasePageResult 包裹逻辑测试', () { test('应该识别包含 total 和 items 的分页响应模型', () { // 创建一个包含 total 和 items 的模型 - final paginationModel = ApiModel( + const paginationModel = ApiModel( name: 'SuperiorTaskListResultPageResponse', description: '分页响应实体类', properties: { @@ -59,7 +59,7 @@ void main() { }); test('分页模型的 items 类型应该被正确提取', () { - final itemsProperty = ApiProperty( + const itemsProperty = ApiProperty( name: 'items', type: PropertyType.array, description: '数据列表', @@ -77,7 +77,7 @@ void main() { }); test('非分页模型不应该被识别为分页模型', () { - final normalModel = ApiModel( + const normalModel = ApiModel( name: 'NormalResult', description: '普通响应', properties: { @@ -121,4 +121,3 @@ void main() { }); }); } - diff --git a/test/parameter_ref_test.dart b/test/parameter_ref_test.dart new file mode 100644 index 0000000..633a0d5 --- /dev/null +++ b/test/parameter_ref_test.dart @@ -0,0 +1,42 @@ +// 测试参数 schema $ref 解析 +import 'package:swagger_generator_flutter/core/models.dart'; +import 'package:test/test.dart'; + +void main() { + group(r'ApiParameter $ref 解析测试', () { + test(r'应该正确解析带有 $ref 的参数', () { + final json = { + 'name': 'TaskTypeEnum', + 'in': 'query', + 'description': '任务类型枚举', + 'schema': { + r'$ref': '#/components/schemas/SysTaskTypeEnums', + }, + }; + + final param = ApiParameter.fromJson(json); + + expect(param.name, 'TaskTypeEnum'); + expect(param.schemaRef, '#/components/schemas/SysTaskTypeEnums'); + expect(param.description, '任务类型枚举'); + }); + + test(r'应该正确解析没有 $ref 的普通参数', () { + final json = { + 'name': 'pageSize', + 'in': 'query', + 'description': '页面大小', + 'schema': { + 'type': 'integer', + 'format': 'int32', + }, + }; + + final param = ApiParameter.fromJson(json); + + expect(param.name, 'pageSize'); + expect(param.schemaRef, isNull); + expect(param.type, PropertyType.integer); + }); + }); +} diff --git a/test/parameter_type_generation_integration_test.dart b/test/parameter_type_generation_integration_test.dart new file mode 100644 index 0000000..cf8ab5c --- /dev/null +++ b/test/parameter_type_generation_integration_test.dart @@ -0,0 +1,108 @@ +// 集成测试:参数类型生成 +import 'package:swagger_generator_flutter/pipeline/parse/swagger_data_parser.dart'; +import 'package:test/test.dart'; + +void main() { + group('参数类型生成集成测试', () { + test('枚举类型的参数应该生成正确的 Dart 类型', () async { + // 模拟完整的 swagger 文档 + final swaggerJson = { + 'openapi': '3.0.1', + 'info': { + 'title': 'Test API', + 'version': '1.0', + }, + 'paths': { + '/api/v3/WorkStatistics/CloudSchoolWorkDataStatistics': { + 'get': { + 'tags': ['WorkStatistics'], + 'summary': '各云校统计', + 'operationId': 'WorkStatistics_CloudSchoolWorkDataStatistics', + 'parameters': [ + { + 'name': 'TaskTypeEnum', + 'in': 'query', + 'description': '任务类型枚举', + 'schema': { + r'$ref': '#/components/schemas/SysTaskTypeEnums', + }, + }, + { + 'name': 'PageSize', + 'in': 'query', + 'description': '页面大小', + 'schema': { + 'type': 'integer', + 'format': 'int32', + }, + }, + { + 'name': 'SearchKey', + 'in': 'query', + 'description': '搜索关键字', + 'schema': { + 'type': 'string', + }, + }, + ], + 'responses': { + '200': { + 'description': 'Success', + 'content': { + 'application/json': { + 'schema': { + 'type': 'object', + }, + }, + }, + }, + }, + }, + }, + }, + 'components': { + 'schemas': { + 'SysTaskTypeEnums': { + 'enum': [1, 2, 3, 4, 5, 6], + 'type': 'integer', + 'description': '任务类型枚举', + 'format': 'int32', + }, + }, + }, + }; + + // 解析文档 + final parser = SwaggerDataParser(); + final document = await parser.parseSwaggerDocument( + swaggerJson, + 'test.json', + ); + + // 获取参数 + final path = document.paths.values.first; + final params = path.parameters; + + expect(params.length, 3); + + // 验证 TaskTypeEnum 参数 + final taskTypeParam = params.firstWhere((p) => p.name == 'TaskTypeEnum'); + expect(taskTypeParam.schemaRef, '#/components/schemas/SysTaskTypeEnums'); + + // 使用生成器的方法获取 Dart 类型 + // 注意: 这里我们需要通过反射或公开方法来测试 + // 由于 _getDartTypeForParameter 是私有的,我们通过间接方式验证 + + // 验证枚举模型存在且类型正确 + final enumModel = document.models['SysTaskTypeEnums']; + expect(enumModel, isNotNull); + expect(enumModel!.type, 'integer'); + expect(enumModel.isEnum, true); + + print('✅ 枚举参数解析正确'); + print(' - TaskTypeEnum 引用: ${taskTypeParam.schemaRef}'); + print(' - 枚举模型类型: ${enumModel.type}'); + print(' - 应该生成为: int (而不是 String)'); + }); + }); +} diff --git a/test/text_cleaner_test.dart b/test/text_cleaner_test.dart index 2587b29..3769ae3 100644 --- a/test/text_cleaner_test.dart +++ b/test/text_cleaner_test.dart @@ -7,7 +7,7 @@ void main() { test('removes newlines from text', () { const input = '部长新增工作任务指标\n(会删除所有管理的班级任务指标-删除所有管理的学习官的通用任务指标)'; final result = TextCleaner.cleanDescription(input); - + expect(result, isNot(contains('\n'))); expect(result, isNot(contains('\r'))); expect(result, '部长新增工作任务指标 (会删除所有管理的班级任务指标-删除所有管理的学习官的通用任务指标)'); @@ -16,7 +16,7 @@ void main() { test('removes carriage returns from text', () { const input = 'Line 1\r\nLine 2\rLine 3'; final result = TextCleaner.cleanDescription(input); - + expect(result, isNot(contains('\n'))); expect(result, isNot(contains('\r'))); expect(result, 'Line 1 Line 2 Line 3'); @@ -25,42 +25,42 @@ void main() { test('replaces multiple spaces with single space', () { const input = 'Text with multiple spaces'; final result = TextCleaner.cleanDescription(input); - + expect(result, 'Text with multiple spaces'); }); test('removes HTML tags', () { const input = '

Text with HTML tags

'; final result = TextCleaner.cleanDescription(input); - + expect(result, 'Text with HTML tags'); }); test('escapes comment end markers', () { const input = 'Text with */ comment end'; final result = TextCleaner.cleanDescription(input); - + expect(result, 'Text with * / comment end'); }); test('trims leading and trailing whitespace', () { const input = ' Text with spaces '; final result = TextCleaner.cleanDescription(input); - + expect(result, 'Text with spaces'); }); test('handles empty string', () { const input = ''; final result = TextCleaner.cleanDescription(input); - + expect(result, ''); }); test('handles complex Chinese text with newlines', () { const input = '获取用户信息\n包含用户的基本信息和扩展信息'; final result = TextCleaner.cleanDescription(input); - + expect(result, isNot(contains('\n'))); expect(result, '获取用户信息 包含用户的基本信息和扩展信息'); }); @@ -68,7 +68,7 @@ void main() { test('handles text with parentheses and newlines', () { const input = '部长新增工作任务指标\n(会删除所有管理的班级任务指标-删除所有管理的学习官的通用任务指标)'; final result = TextCleaner.cleanDescription(input); - + // Should not contain newlines expect(result, isNot(contains('\n'))); // Should preserve parentheses @@ -83,33 +83,34 @@ void main() { test('normalizes line endings', () { const input = 'Line 1\r\nLine 2\rLine 3\nLine 4'; final result = TextCleaner.normalize(input); - + expect(result, 'Line 1\nLine 2\nLine 3\nLine 4'); }); test('removes excessive blank lines', () { const input = 'Line 1\n\n\n\nLine 2'; final result = TextCleaner.normalize(input); - + expect(result, 'Line 1\n\nLine 2'); }); test('trims whitespace', () { const input = ' Text '; final result = TextCleaner.normalize(input); - + expect(result, 'Text'); }); }); group('escapeString', () { test('escapes special characters', () { - const input = "Text with 'quotes' and \"double quotes\" and \n newlines"; + const input = + "Text with 'quotes' and \"double quotes\" and \n newlines"; final result = TextCleaner.escapeString(input); - - expect(result, contains("\\'")); - expect(result, contains('\\"')); - expect(result, contains('\\n')); + + expect(result, contains(r"\'")); + expect(result, contains(r'\"')); + expect(result, contains(r'\n')); }); }); @@ -117,7 +118,7 @@ void main() { test('truncates long text', () { const input = 'This is a very long text that needs to be truncated'; final result = TextCleaner.truncate(input, 20); - + expect(result.length, lessThanOrEqualTo(20)); expect(result, endsWith('...')); }); @@ -125,10 +126,9 @@ void main() { test('does not truncate short text', () { const input = 'Short text'; final result = TextCleaner.truncate(input, 20); - + expect(result, input); }); }); }); } - diff --git a/test_generator_config.yaml b/test_generator_config.yaml new file mode 100644 index 0000000..e344774 --- /dev/null +++ b/test_generator_config.yaml @@ -0,0 +1,363 @@ +# Swagger 代码生成器配置文件模板 +# 复制此文件到您的项目根目录,重命名为 generator_config.yaml,并根据需要修改 + +# 基本配置 +generator: + name: "my_project_generator" # 修改为您的项目生成器名称 + version: "1.0" + author: "Your Name" # 修改为您的名字 + copyright: "Copyright (C) 2025 Your Company. All rights reserved." # 修改为您的版权信息 + +# 输入配置 +input: + # Swagger 文档源配置(支持多版本) + swagger_urls: + # 简写形式:直接列出 URL + - "https://your-api.com/swagger/v1/swagger_v3.json" + - "https://your-api.com/swagger/v2/swagger_v3.json" + + # 完整形式:可以控制每个版本的启用状态 + # - url: "https://your-api.com/swagger/v1/swagger_v3.json" + # enabled: true # 可选,是否启用此版本(默认: true) + + # 验证配置 + validate_schema: true + strict_mode: true + +# 输出配置 +output: + # 输出目录(根据您的项目结构修改) + base_dir: "./lib/generated" + api_dir: "./lib/generated/api" + models_dir: "./lib/generated/api_models" + + # 文件命名 + api_file_suffix: "_api.dart" + model_file_suffix: ".dart" + + # 是否按 tag 分组 + split_by_tags: true + + # 只生成指定 tags 的 API 和模型(可选) + # 如果不指定或为空,则生成所有 tags + # 如果某个 endpoint 有多个 tags,只要其中一个在列表中就会生成 + included_tags: + # - "User" + # - "Pet" + # - "Store" + + # 从代码生成中排除指定的 tags + # 适用于内部、废弃或不需要的 API + # 如果一个 endpoint 的所有 tags 都被排除,则该 endpoint 不会生成 + excluded_tags: + # - "Internal" + # - "Deprecated" + # - "Legacy" + + # 跳过的目录列表(这些目录下的文件将不会被生成) + # 支持相对路径和绝对路径,支持目录名或完整路径 + ignored_directories: + # - "api/v1" # 跳过 v1 版本的 API + # - "api_models/request" # 跳过请求模型目录 + # - "./lib/generated/api/v2" # 跳过特定路径 + + # 跳过的文件名列表(这些文件将不会被生成) + # 支持精确匹配、通配符匹配和模式匹配 + ignored_files: + # 精确匹配文件名 + # - "user_api.dart" # 跳过名为 user_api.dart 的文件 + # - "mobile_manager_api.dart" # 跳过指定文件 + + # 通配符匹配(支持前缀和后缀) + # - "*_api.dart" # 跳过所有以 _api.dart 结尾的文件 + # - "user*.dart" # 跳过所有以 user 开头的 .dart 文件 + # - "*manager*" # 跳过所有包含 manager 的文件名 + + # 示例:跳过所有 v1 版本的 API 文件 + # - "*_api_v1.dart" + # - "*V1*.dart" + +# 代码生成配置 +generation: + # API 接口配置 + api: + enabled: true + use_retrofit: true + use_dio: true + parser: "JsonSerializable" + + # API Client 配置 + # 主 API 客户端类的名称和文件名配置 + client: + class_name: "ApiClient" # API 客户端类名(默认: ApiClient) + file_name: "api_client" # API 客户端文件名(默认: api_client,不含 .dart 后缀) + + # 版本提取配置(多版本支持) + version_extraction: + # 版本提取正则表达式模式 + # 默认: "/api/v(\\d+)/" 匹配 /api/v1/, /api/v2/ 等 + pattern: "/api/v(\\d+)/" + + # 默认版本(当无法从路径提取版本时使用) + default_version: "v1" + + # 基础类型配置(根据您的项目调整) + # 如果您的项目有统一的响应模型,请在此处配置 + base_result_type: "BaseResult" # 基础响应模型名称 + base_page_result_type: "BasePageResult" # 分页响应模型名称 + + # 基础模型的导入路径(可选) + # 如果提供了路径,将在 api_models/index.dart 中自动导出 + # 如果留空,则不会生成导出语句 + base_result_import: "" # 例如: "package:your_project/common/models/base_result.dart" + base_page_result_import: "" # 例如: "package:your_project/common/models/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 + + # 枚举键名映射配置(可选) + # 用于为枚举值定义有意义的键名和描述 + # 优先级:配置文件映射 > x-enum-varnames > 智能生成 + # + # 使用场景: + # 1. 后端不支持 x-enum-varnames 扩展字段 + # 2. 需要覆盖 Swagger 文档中的枚举键名 + # 3. 需要为枚举值添加自定义描述 + # + # 格式: + # enum_key_mappings: + # 枚举名称: + # - value: 枚举值(数字或字符串) + # name: 枚举键名(大写下划线命名) + # description: 枚举描述(可选) + # + # 示例: + # enum_key_mappings: + # SysTaskTypeEnums: + # - value: 1 + # name: SPOT_CHECK + # description: 抽查 + # - value: 2 + # name: CULTURAL + # description: 文创建设 + # - value: 3 + # name: CLASS_CADRE_MEETING + # description: 班干部会议 + # + # UserStatus: + # - value: "active" + # name: ACTIVE + # description: 活跃用户 + # - value: "inactive" + # name: INACTIVE + # description: 非活跃用户 + # - value: "banned" + # name: BANNED + # description: 已封禁 + # + enum_key_mappings: + # 在此处添加您的枚举映射配置 + # SysTaskTypeEnums: + # - value: 1 + # name: SPOT_CHECK + # description: 抽查 + # - value: 2 + # name: CULTURAL + # description: 文创建设 + +# 类型映射配置 +type_mapping: + # OpenAPI -> Dart 类型映射 + string: "String" + integer: "int" + number: "double" + boolean: "bool" + array: "List" + object: "Map" + + # 特殊类型处理 + date: "DateTime" + date-time: "DateTime" + binary: "Uint8List" + +# 导入管理配置 +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" + - "ref_resolution" + - "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: "your_project" # 修改为您的项目名称 + + # 特殊处理规则 + 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: + # 文件头模板 + # 支持模板变量: + # {fileName} - 文件名(如 "user_api.dart") + # {fileType} - 文件类型描述(如 "API 接口定义"、"模型定义") + # {swaggerUrl} - Swagger 文档 URL + # {generatorName} - 生成器名称(从 generator.name 读取) + # {author} - 作者(从 generator.author 读取) + # {copyright} - 版权信息(从 generator.copyright 读取) + file_header: | + // {fileType} + // 基于 Swagger API 文档: {swaggerUrl} + // 由 {generatorName} by {author} 生成 + // {copyright} + + # API 类模板 + # 支持模板变量: + # {tagName} - Tag 名称(如 "User"、"Task") + # {className} - 类名(如 "UserApi"、"TaskApi") + api_class: | + /// {tagName} API 接口 + /// 负责处理 {tagName} 相关的接口 + @RestApi(parser: Parser.JsonSerializable) + abstract class {className} { + factory {className}(Dio dio, {String? baseUrl}) = _{className}; + } + + # 模型类模板 + # 支持模板变量: + # {className} - 类名(如 "User"、"Task") + # {constructorParams} - 构造函数参数列表 + 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); + } +