// ============================================================ // 闲言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 _section(String title) async { print('\n── $title ──'); } Future _testPost( String path, Map 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; 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 _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; 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; }