swagger_generator_flutter/lib/generators/documentation_generator.dart

712 lines
21 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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