Files
xianyan/scripts/tool_api_verify.dart
Developer 41a60b0288 feat: 新增多项核心服务与功能增强
refactor(theme): 扩展AppTheme支持卡片样式和圆角风格动态配置
feat(services): 新增HapticService触觉反馈服务
feat(services): 实现ScreenWakeService屏幕常亮管理
feat(services): 添加SoundService音效播放服务
feat(services): 集成AppLockService应用锁功能
feat(services): 实现BatteryOptimizationService电池优化
feat(services): 新增NetworkProxyService网络代理
feat(services): 完善DataExportService数据导出
feat(services): 增强PermissionService权限管理
feat(tools): 工具中心新增拼音转换等多项功能
fix(localization): 修复时区初始化错误
docs: 更新工具中心开发清单和设置重构文档
chore: 更新依赖版本和CI配置
2026-05-07 09:05:35 +08:00

374 lines
12 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.
// ============================================================
// 闲言APP — 工具中心API验证脚本
// 创建时间: 2026-05-07
// 更新时间: 2026-05-07
// 作用: 验证工具中心所有API接口的能力、性能、内容质量
// 上次更新: 初始创建覆盖所有工具API
// ============================================================
// 使用方法: dart run scripts/tool_api_verify.dart
import 'dart:convert';
import 'dart:io';
const _baseUrl = 'https://tools.wktyl.com';
final _client = HttpClient();
final _results = <_ApiResult>[];
void main() async {
print('═══════════════════════════════════════════════════════');
print(' 闲言APP — 工具中心API全面验证');
print(' ${DateTime.now().toIso8601String()}');
print('═══════════════════════════════════════════════════════\n');
// 1. 汉语工具
await _section('汉语工具');
await _testPost('/api/hanzi/zi', {'zi': ''}, '查字');
await _testPost('/api/hanzi/bishun', {'zi': ''}, '笔顺');
await _testPost('/api/hanzi/zuci', {'zi': ''}, '组词');
await _testPost('/api/hanzi/cidian', {'zi': '中国'}, '词典');
await _testPost('/api/hanzi/chengyu', {'zi': '一心一意'}, '成语');
await _testPost('/api/hanzi/jinyici', {'zi': '快乐'}, '近义词');
await _testPost('/api/hanzi/fanyici', {'zi': '快乐'}, '反义词');
await _testPost('/api/hanzi/juzi', {'zi': '春天'}, '句子');
await _testPost('/api/hanzi/nick', {'zi': ''}, '网名');
await _testPost('/api/hanzi/barcode', {'zi': '6901234567890'}, '条码');
await _testPost('/api/hanzi/danci', {'zi': 'hello'}, '英语单词');
await _testPost('/api/hanzi/suoxie', {'zi': 'AI'}, '英文缩写');
await _testPost('/api/hanzi/pinyin', {'hanzi': '中国'}, '拼音(默认)');
await _testPost('/api/hanzi/pinyin', {
'hanzi': '中国',
'option': 'shengdiao',
}, '拼音(声调)');
// 2. 单位换算 - 关键验证:参数格式
await _section('单位换算 (关键验证: value+type vs zi)');
await _testPost('/api/hanzi/speed', {
'value': '100',
'type': 'km',
}, '速度(value+type)');
await _testPost('/api/hanzi/weight', {
'value': '1',
'type': 'kilogram',
}, '重量(value+type)');
await _testPost('/api/hanzi/volume', {
'value': '1',
'type': 'dm3',
}, '体积(value+type)');
await _testPost('/api/hanzi/area', {
'value': '100',
'type': 'm2',
}, '面积(value+type)');
await _testPost('/api/hanzi/temp', {
'value': '36.5',
'type': 'c',
}, '温度(value+type)');
await _testPost('/api/hanzi/power', {
'value': '1500',
'type': 'w',
}, '功率(value+type)');
await _testPost('/api/hanzi/pressure', {
'value': '101325',
'type': 'pa',
}, '压力(value+type)');
// 3. 搜索型工具
await _section('搜索型工具 (hanzi/search)');
final searchTypes = [
'poetry',
'brainteaser',
'couplet',
'wisdom',
'story',
'saying',
'riddle',
'xiehouyu',
'zuowen',
'why',
'drug',
'food',
'herbal',
'pianfang',
'tisana',
'changshi',
'lyric',
];
for (final type in searchTypes) {
await _testPost('/api/hanzi/search', {
'zi': '',
'type': type,
'limit': '3',
}, '搜索($type)');
}
// 4. searchall接口
await _section('searchall接口');
await _testGet(
'/api/searchall/search?keyword=春天&type=illness&limit=3',
'疾病自查',
);
await _testGet(
'/api/searchall/search?keyword=李&type=surname&limit=3',
'姓氏起源',
);
await _testGet('/api/searchall/search?keyword=立春&type=jieqi&limit=3', '节气查询');
await _testGet(
'/api/searchall/search?keyword=中国&type=nation&limit=3',
'国家查询',
);
await _testGet('/api/searchall/search?keyword=水&type=zgjm&limit=3', '周公解梦');
await _testGet('/api/searchall/search?keyword=搞笑&type=joke&limit=3', '笑话大全');
await _testGet('/api/searchall/search?keyword=春天&limit=5', '全量搜索');
await _testGet('/api/searchall/suggest?keyword=春', '搜索建议');
await _testGet('/api/searchall/hot', '热门搜索');
await _testGet('/api/searchall/sources', '数据源列表');
// 5. 一言接口
await _section('一言接口');
await _testGet('/api/hitokoto/random?num=3', '一言随机');
await _testGet('/api/hitokoto/search?kw=人生&limit=3', '一言搜索');
await _testGet('/api/hitokoto/categories', '一言分类');
await _testGet('/api/hitokoto/hot?num=3', '一言热门');
// 6. 热搜接口
await _section('热搜接口');
await _testGet('/api/hot/read', '热搜聚合');
await _testGet('/api/hot/read?type=baidu', '百度热搜');
// 7. 每日推荐
await _section('每日推荐');
await _testGet('/api/daily/recommend', '每日推荐');
// 8. 中国传统色
await _section('中国传统色');
await _testGet('/api/hanzi/china_colors', '传统色列表');
// 9. 酒方接口
await _section('酒方接口');
await _testGet('/api/webapi/jiufang_search?page=1&pageSize=3', '酒方列表');
await _testGet(
'/api/webapi/jiufang_search?keyword=人参&page=1&pageSize=3',
'酒方搜索',
);
// 10. 统计接口
await _section('统计接口');
await _testGet('/api/webapi/stats_overview', '站点概览');
await _testGet('/api/webapi/hot_tools?limit=5', '热门工具');
_printReport();
_client.close();
}
// ── 测试方法 ──
Future<void> _section(String title) async {
print('\n── $title ──');
}
Future<void> _testPost(
String path,
Map<String, String> data,
String label,
) async {
final sw = Stopwatch()..start();
try {
final uri = Uri.parse('$_baseUrl$path');
final request = await _client.postUrl(uri);
request.headers.set('Content-Type', 'application/x-www-form-urlencoded');
request.write(
data.entries
.map((e) => '${e.key}=${Uri.encodeComponent(e.value)}')
.join('&'),
);
final response = await request.close();
final body = await response.transform(utf8.decoder).join();
sw.stop();
final json = jsonDecode(body) as Map<String, dynamic>;
final code = json['code'] as int? ?? 0;
final msg = json['msg'] as String? ?? '';
final dataResult = json['data'];
final hasData = dataResult != null && dataResult.toString().isNotEmpty;
String contentPreview = '';
if (hasData) {
if (dataResult is List) {
contentPreview = '${dataResult.length}条记录';
} else if (dataResult is Map) {
contentPreview = '${dataResult.keys.take(5).join(', ')}';
}
}
final status = code == 1 && hasData ? '' : (code == 1 ? '⚠️空' : '');
print(
' $status $label: ${sw.elapsedMilliseconds}ms code=$code ${hasData ? '有数据' : '无数据'} $contentPreview',
);
_results.add(
_ApiResult(
label: label,
path: path,
method: 'POST',
params: data.toString(),
success: code == 1 && hasData,
hasData: hasData,
ms: sw.elapsedMilliseconds,
code: code,
msg: msg,
contentPreview: contentPreview,
),
);
} catch (e) {
sw.stop();
print('$label: ${sw.elapsedMilliseconds}ms ERROR: $e');
_results.add(
_ApiResult(
label: label,
path: path,
method: 'POST',
params: data.toString(),
success: false,
hasData: false,
ms: sw.elapsedMilliseconds,
code: -1,
msg: e.toString(),
contentPreview: '',
),
);
}
}
Future<void> _testGet(String path, String label) async {
final sw = Stopwatch()..start();
try {
final uri = Uri.parse('$_baseUrl$path');
final request = await _client.getUrl(uri);
final response = await request.close();
final body = await response.transform(utf8.decoder).join();
sw.stop();
final json = jsonDecode(body) as Map<String, dynamic>;
final code = json['code'] as int? ?? 0;
final msg = json['msg'] as String? ?? '';
final dataResult = json['data'];
final hasData = dataResult != null && dataResult.toString().isNotEmpty;
String contentPreview = '';
if (hasData) {
if (dataResult is List) {
contentPreview = '${dataResult.length}条记录';
} else if (dataResult is Map) {
contentPreview = '${dataResult.keys.take(5).join(', ')}';
}
}
final status = code == 1 && hasData ? '' : (code == 1 ? '⚠️空' : '');
print(
' $status $label: ${sw.elapsedMilliseconds}ms code=$code ${hasData ? '有数据' : '无数据'} $contentPreview',
);
_results.add(
_ApiResult(
label: label,
path: path,
method: 'GET',
params: '',
success: code == 1 && hasData,
hasData: hasData,
ms: sw.elapsedMilliseconds,
code: code,
msg: msg,
contentPreview: contentPreview,
),
);
} catch (e) {
sw.stop();
print('$label: ${sw.elapsedMilliseconds}ms ERROR: $e');
_results.add(
_ApiResult(
label: label,
path: path,
method: 'GET',
params: '',
success: false,
hasData: false,
ms: sw.elapsedMilliseconds,
code: -1,
msg: e.toString(),
contentPreview: '',
),
);
}
}
void _printReport() {
print('\n═══════════════════════════════════════════════════════');
print(' 验证报告汇总');
print('═══════════════════════════════════════════════════════\n');
final total = _results.length;
final passed = _results.where((r) => r.success).length;
final failed = _results.where((r) => !r.success).length;
final avgMs = _results.isEmpty
? 0
: _results.map((r) => r.ms).reduce((a, b) => a + b) ~/ _results.length;
print(' 总计: $total | 通过: $passed | 失败: $failed | 平均耗时: ${avgMs}ms\n');
if (failed > 0) {
print(' ❌ 失败接口:');
for (final r in _results.where((r) => !r.success)) {
print(
' - ${r.label} [${r.method} ${r.path}]: code=${r.code} ${r.msg}',
);
}
print('');
}
final slowApis = _results.where((r) => r.ms > 2000).toList();
if (slowApis.isNotEmpty) {
print(' 🐌 慢接口 (>2s):');
for (final r in slowApis) {
print(' - ${r.label}: ${r.ms}ms');
}
print('');
}
print(' 📊 性能分布:');
final fast = _results.where((r) => r.ms < 500).length;
final medium = _results.where((r) => r.ms >= 500 && r.ms < 2000).length;
final slow = _results.where((r) => r.ms >= 2000).length;
print(' 快速(<500ms): $fast | 中等(500-2000ms): $medium | 慢速(>2s): $slow\n');
print('═══════════════════════════════════════════════════════');
}
class _ApiResult {
const _ApiResult({
required this.label,
required this.path,
required this.method,
required this.params,
required this.success,
required this.hasData,
required this.ms,
required this.code,
required this.msg,
required this.contentPreview,
});
final String label;
final String path;
final String method;
final String params;
final bool success;
final bool hasData;
final int ms;
final int code;
final String msg;
final String contentPreview;
}