299 lines
9.6 KiB
Dart
299 lines
9.6 KiB
Dart
/**
|
||
* 文件:verify_nutrition_api.dart
|
||
* 名称:营养中心接口验证脚本
|
||
* 作用:验证营养中心相关接口的连通性和性能
|
||
* 使用:dart scripts/verify_nutrition_api.dart
|
||
* 更新:2026-04-10 创建,用于测试 API 响应和数据格式
|
||
*/
|
||
|
||
import 'dart:async';
|
||
import 'dart:convert';
|
||
import 'dart:io';
|
||
|
||
// 配置
|
||
const String baseUrl = 'http://eat.wktyl.com/api';
|
||
const String statsFullEndpoint = '/stats_full.php';
|
||
const int timeoutSeconds = 12;
|
||
|
||
// 颜色常量
|
||
const String reset = '\x1B[0m';
|
||
const String green = '\x1B[32m';
|
||
const String red = '\x1B[31m';
|
||
const String yellow = '\x1B[33m';
|
||
const String blue = '\x1B[34m';
|
||
const String cyan = '\x1B[36m';
|
||
|
||
void main() async {
|
||
printHeader();
|
||
|
||
// 测试 1:验证营养报告接口(总览)
|
||
await testNutritionOverview();
|
||
|
||
// 测试 2:验证热门排行接口
|
||
await testHotRanking();
|
||
|
||
// 测试 3:性能基准测试
|
||
await performanceBenchmark();
|
||
|
||
printFooter();
|
||
}
|
||
|
||
void printHeader() {
|
||
print(
|
||
'\n${cyan}╔════════════════════════════════════════════════════════╗${reset}',
|
||
);
|
||
print(
|
||
'${cyan}║${reset} ${green}🔬 营养中心接口验证脚本${reset} ${cyan}║${reset}',
|
||
);
|
||
print(
|
||
'${cyan}║${reset} ${yellow}验证 API 连通性、数据格式、响应时间${reset} ${cyan}║${reset}',
|
||
);
|
||
print(
|
||
'${cyan}╚════════════════════════════════════════════════════════╝${reset}\n',
|
||
);
|
||
}
|
||
|
||
void printFooter() {
|
||
print(
|
||
'\n${cyan}═══════════════════════════════════════════════════════${reset}',
|
||
);
|
||
print('${green}✅ 验证完成${reset}');
|
||
print(
|
||
'${cyan}═══════════════════════════════════════════════════════${reset}\n',
|
||
);
|
||
}
|
||
|
||
Future<void> testNutritionOverview() async {
|
||
printSection('测试 1:营养报告接口 (stats_full.php)');
|
||
|
||
final url = Uri.parse(
|
||
'$baseUrl$statsFullEndpoint?act=hot&period=total&limit=10',
|
||
);
|
||
|
||
print('${blue}请求 URL:${reset} $url');
|
||
|
||
try {
|
||
final stopwatch = Stopwatch()..start();
|
||
|
||
final response = await HttpClient()
|
||
.getUrl(url)
|
||
.timeout(Duration(seconds: timeoutSeconds));
|
||
|
||
final data = await response.close();
|
||
final body = await utf8.decoder.bind(data).join();
|
||
|
||
stopwatch.stop();
|
||
|
||
print('${green}✓ 响应状态码:${reset} ${data.statusCode}');
|
||
print('${green}✓ 响应时间:${reset} ${stopwatch.elapsedMilliseconds}ms');
|
||
|
||
// 解析 JSON
|
||
final jsonData = jsonDecode(body) as Map<String, dynamic>;
|
||
|
||
print('${blue}JSON 结构分析:${reset}');
|
||
print(' - code: ${jsonData['code']}');
|
||
print(' - message: ${jsonData['message']}');
|
||
|
||
if (jsonData['data'] != null) {
|
||
final dataMap = jsonData['data'] as Map<String, dynamic>;
|
||
print(' - data keys: ${dataMap.keys.toList()}');
|
||
|
||
// 检查热门排行数据结构
|
||
if (dataMap.containsKey('total')) {
|
||
final total = dataMap['total'] as Map<String, dynamic>;
|
||
print(' - total 字段存在 ✓');
|
||
|
||
if (total.containsKey('recipe_view')) {
|
||
final recipes = total['recipe_view'] as List;
|
||
print(' - recipe_view: ${recipes.length} 条记录');
|
||
|
||
if (recipes.isNotEmpty) {
|
||
final first = recipes.first as Map<String, dynamic>;
|
||
print(' 示例数据:');
|
||
print(' id: ${first['id']}');
|
||
print(' name: ${first['name']}');
|
||
print(' value: ${first['value']}');
|
||
}
|
||
}
|
||
|
||
if (total.containsKey('recipe_like')) {
|
||
final likes = total['recipe_like'] as List;
|
||
print(' - recipe_like: ${likes.length} 条记录');
|
||
}
|
||
|
||
if (total.containsKey('ingredient_view')) {
|
||
final ingredients = total['ingredient_view'] as List;
|
||
print(' - ingredient_view: ${ingredients.length} 条记录');
|
||
}
|
||
} else {
|
||
print('${yellow}⚠ 警告:total 字段不存在${reset}');
|
||
}
|
||
} else {
|
||
print('${red}✗ data 字段为空${reset}');
|
||
}
|
||
|
||
print('${green}✓ 接口连通性测试通过${reset}\n');
|
||
} on TimeoutException catch (e) {
|
||
print('${red}✗ 请求超时:${e.message}${reset}');
|
||
print('${yellow}建议:检查网络连接或 API 服务器状态${reset}\n');
|
||
} catch (e) {
|
||
print('${red}✗ 请求失败:$e${reset}');
|
||
print('${yellow}建议:检查 URL 是否正确${reset}\n');
|
||
}
|
||
}
|
||
|
||
Future<void> testHotRanking() async {
|
||
printSection('测试 2:热门排行数据验证');
|
||
|
||
final testCases = [
|
||
{'period': 'total', 'name': '总排行'},
|
||
{'period': 'month', 'name': '月排行'},
|
||
{'period': 'today', 'name': '今日排行'},
|
||
];
|
||
|
||
for (final testCase in testCases) {
|
||
final period = testCase['period']!;
|
||
final name = testCase['name']!;
|
||
|
||
print('${blue}测试 $name ($period):${reset}');
|
||
|
||
final url = Uri.parse(
|
||
'$baseUrl$statsFullEndpoint?act=hot&period=$period&limit=5',
|
||
);
|
||
|
||
try {
|
||
final stopwatch = Stopwatch()..start();
|
||
|
||
final response = await HttpClient()
|
||
.getUrl(url)
|
||
.timeout(Duration(seconds: timeoutSeconds));
|
||
|
||
final data = await response.close();
|
||
final body = await utf8.decoder.bind(data).join();
|
||
|
||
stopwatch.stop();
|
||
|
||
final jsonData = jsonDecode(body) as Map<String, dynamic>;
|
||
|
||
if (jsonData['code'] == 200) {
|
||
print(
|
||
' ${green}✓ 状态码 200${reset} - ${stopwatch.elapsedMilliseconds}ms',
|
||
);
|
||
|
||
final dataMap = jsonData['data'] as Map<String, dynamic>?;
|
||
if (dataMap != null) {
|
||
// 检查不同可能的数据结构
|
||
int recipeCount = 0;
|
||
|
||
if (dataMap.containsKey(period) && dataMap[period] is Map) {
|
||
final periodData = dataMap[period] as Map<String, dynamic>;
|
||
if (periodData.containsKey('recipe_view')) {
|
||
recipeCount = (periodData['recipe_view'] as List).length;
|
||
}
|
||
} else if (dataMap.containsKey('recipe_view')) {
|
||
recipeCount = (dataMap['recipe_view'] as List).length;
|
||
} else {
|
||
// 检查嵌套结构
|
||
for (final key in ['total', 'month', 'today']) {
|
||
if (dataMap.containsKey(key) && dataMap[key] is Map) {
|
||
final sub = dataMap[key] as Map<String, dynamic>;
|
||
if (sub.containsKey('recipe_view')) {
|
||
recipeCount = (sub['recipe_view'] as List).length;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (recipeCount > 0) {
|
||
print(' ${green}✓ 获取到 $recipeCount 条菜谱数据${reset}');
|
||
} else {
|
||
print(' ${yellow}⚠ 未获取到有效数据${reset}');
|
||
}
|
||
}
|
||
} else {
|
||
print(
|
||
' ${red}✗ 状态码 ${jsonData['code']}: ${jsonData['message']}${reset}',
|
||
);
|
||
}
|
||
} catch (e) {
|
||
print(' ${red}✗ 请求失败:$e${reset}');
|
||
}
|
||
}
|
||
|
||
print('');
|
||
}
|
||
|
||
Future<void> performanceBenchmark() async {
|
||
printSection('测试 3:性能基准测试');
|
||
|
||
final iterations = 5;
|
||
final results = <int>[];
|
||
|
||
print('${blue}执行 $iterations 次连续请求测试...${reset}\n');
|
||
|
||
for (int i = 0; i < iterations; i++) {
|
||
final url = Uri.parse(
|
||
'$baseUrl$statsFullEndpoint?act=hot&period=total&limit=5',
|
||
);
|
||
|
||
try {
|
||
final stopwatch = Stopwatch()..start();
|
||
|
||
final response = await HttpClient()
|
||
.getUrl(url)
|
||
.timeout(Duration(seconds: timeoutSeconds));
|
||
|
||
await response.close();
|
||
|
||
stopwatch.stop();
|
||
results.add(stopwatch.elapsedMilliseconds);
|
||
|
||
print(' 请求 #${i + 1}: ${stopwatch.elapsedMilliseconds}ms');
|
||
} catch (e) {
|
||
print(' 请求 #${i + 1}: ${red}失败 ($e)${reset}');
|
||
results.add(-1);
|
||
}
|
||
}
|
||
|
||
// 计算统计信息
|
||
final validResults = results.where((r) => r > 0).toList();
|
||
|
||
if (validResults.isNotEmpty) {
|
||
final avg = validResults.reduce((a, b) => a + b) / validResults.length;
|
||
final min = validResults.reduce((a, b) => a < b ? a : b);
|
||
final max = validResults.reduce((a, b) => a > b ? a : b);
|
||
|
||
print('\n${blue}性能统计:${reset}');
|
||
print(' - 平均响应时间:${avg.toStringAsFixed(0)}ms');
|
||
print(' - 最快响应时间:${min}ms');
|
||
print(' - 最慢响应时间:${max}ms');
|
||
print(
|
||
' - 成功率:${validResults.length}/${iterations} (${(validResults.length / iterations * 100).toStringAsFixed(0)}%)',
|
||
);
|
||
|
||
// 性能评级
|
||
String rating;
|
||
if (avg < 500) {
|
||
rating = '${green}优秀 🌟${reset}';
|
||
} else if (avg < 1000) {
|
||
rating = '${green}良好 ✓${reset}';
|
||
} else if (avg < 2000) {
|
||
rating = '${yellow}一般 ⚠${reset}';
|
||
} else {
|
||
rating = '${red}较差 ✗${reset}';
|
||
}
|
||
print(' - 性能评级:$rating');
|
||
} else {
|
||
print('\n${red}所有请求均失败,无法计算性能统计${reset}');
|
||
}
|
||
|
||
print('');
|
||
}
|
||
|
||
void printSection(String title) {
|
||
print('\n${cyan}───────────────────────────────────────────────────${reset}');
|
||
print('${green}$title${reset}');
|
||
print('${cyan}───────────────────────────────────────────────────${reset}\n');
|
||
}
|