/** * 文件: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 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; print('${blue}JSON 结构分析:${reset}'); print(' - code: ${jsonData['code']}'); print(' - message: ${jsonData['message']}'); if (jsonData['data'] != null) { final dataMap = jsonData['data'] as Map; print(' - data keys: ${dataMap.keys.toList()}'); // 检查热门排行数据结构 if (dataMap.containsKey('total')) { final total = dataMap['total'] as Map; 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; 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 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; if (jsonData['code'] == 200) { print( ' ${green}✓ 状态码 200${reset} - ${stopwatch.elapsedMilliseconds}ms', ); final dataMap = jsonData['data'] as Map?; if (dataMap != null) { // 检查不同可能的数据结构 int recipeCount = 0; if (dataMap.containsKey(period) && dataMap[period] is Map) { final periodData = dataMap[period] as Map; 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; 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 performanceBenchmark() async { printSection('测试 3:性能基准测试'); final iterations = 5; final results = []; 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'); }