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配置
374 lines
12 KiB
Dart
374 lines
12 KiB
Dart
// ============================================================
|
||
// 闲言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;
|
||
}
|