Files
kitchen/scripts/verify_nutrition_api.dart
2026-04-11 02:02:23 +08:00

299 lines
9.6 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.
/**
* 文件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');
}