chore: 汇总批量提交的功能优化与bug修复
本次提交包含多项迭代优化和问题修复: 1. 新增缩略图图片组件、数字格式化工具类,补充多语言翻译类型与本地化支持 2. 优化底部导航栏主题色统一使用动态accent色值 3. 修复多处图表动画、路由跳转、API请求相关问题 4. 简化服务器公告文案,调整默认分屏状态为关闭 5. 新增安卓/iOS桌面快捷方式配置 6. 重构多处状态管理类使用SafeNotifierInit统一异常保护 7. 替换硬编码蓝色为主题色,更新版本号获取方式为动态读取 8. 优化缓存预加载逻辑,移除无用代码 9. 调整默认设置项,优化用户体验细节
This commit is contained in:
116
scripts/auto_git_stash.ps1
Normal file
116
scripts/auto_git_stash.ps1
Normal file
@@ -0,0 +1,116 @@
|
||||
# -------------------------------------------------------
|
||||
# 文件: auto_git_stash.ps1
|
||||
# 创建: 2026-05-31
|
||||
# 更新: 2026-05-31
|
||||
# 名称: Git 自动暂存脚本
|
||||
# 作用: 每5分钟自动检测并暂存 Git 仓库的未提交更改
|
||||
# 上次更新内容: 初始创建
|
||||
# -------------------------------------------------------
|
||||
|
||||
param(
|
||||
[int]$IntervalMinutes = 5,
|
||||
[string]$RepoPath = "e:\project\flutter\f\xianyan",
|
||||
[switch]$DryRun
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Continue"
|
||||
$stamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host " Git Auto Stash - 自动暂存守护脚本" -ForegroundColor Cyan
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host " 仓库路径 : $RepoPath" -ForegroundColor Gray
|
||||
Write-Host " 检查间隔 : $IntervalMinutes 分钟" -ForegroundColor Gray
|
||||
Write-Host " 模拟运行 : $DryRun" -ForegroundColor Gray
|
||||
Write-Host " 启动时间 : $stamp" -ForegroundColor Gray
|
||||
Write-Host " 按 Ctrl+C 停止" -ForegroundColor Yellow
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
if (-not (Test-Path $RepoPath)) {
|
||||
Write-Host "[错误] 仓库路径不存在: $RepoPath" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
$logFile = Join-Path $RepoPath "auto_stash.log"
|
||||
|
||||
function Write-Log {
|
||||
param([string]$Message)
|
||||
$ts = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
||||
$line = "[$ts] $Message"
|
||||
Add-Content -Path $logFile -Value $line -Encoding UTF8
|
||||
Write-Host $line
|
||||
}
|
||||
|
||||
function Test-HasChanges {
|
||||
param([string]$Path)
|
||||
$status = git -C $Path status --porcelain 2>&1
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Log "[错误] git status 失败: $status"
|
||||
return $false
|
||||
}
|
||||
return ($status -and $status.ToString().Trim().Length -gt 0)
|
||||
}
|
||||
|
||||
function Get-StashCount {
|
||||
param([string]$Path)
|
||||
$list = git -C $Path stash list 2>&1
|
||||
if ($LASTEXITCODE -ne 0 -or -not $list) { return 0 }
|
||||
return ($list -split "`n" | Where-Object { $_.Trim().Length -gt 0 }).Count
|
||||
}
|
||||
|
||||
function Invoke-AutoStash {
|
||||
$now = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
||||
$branch = git -C $RepoPath rev-parse --abbrev-ref HEAD 2>&1
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Log "[错误] 无法获取当前分支"
|
||||
return
|
||||
}
|
||||
|
||||
$hasChanges = Test-HasChanges -Path $RepoPath
|
||||
|
||||
if (-not $hasChanges) {
|
||||
Write-Log "[$branch] 无未提交更改,跳过"
|
||||
return
|
||||
}
|
||||
|
||||
$stashLabel = "auto-stash-$((Get-Date).ToString('yyyyMMdd-HHmmss'))"
|
||||
|
||||
if ($DryRun) {
|
||||
Write-Log "[$branch] [模拟] 检测到更改,将执行: git stash push -m `"$stashLabel`""
|
||||
return
|
||||
}
|
||||
|
||||
$beforeCount = Get-StashCount -Path $RepoPath
|
||||
|
||||
git -C $RepoPath stash push -m $stashLabel 2>&1 | ForEach-Object {
|
||||
Write-Log "[$branch] $_"
|
||||
}
|
||||
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
$afterCount = Get-StashCount -Path $RepoPath
|
||||
if ($afterCount -gt $beforeCount) {
|
||||
Write-Log "[$branch] ✓ 已暂存: $stashLabel (stash@{$afterCount - 1})"
|
||||
} else {
|
||||
Write-Log "[$branch] stash 执行但未创建新条目(可能无实际更改)"
|
||||
}
|
||||
} else {
|
||||
Write-Log "[$branch] ✗ stash 失败"
|
||||
}
|
||||
}
|
||||
|
||||
Write-Log "===== 自动暂存守护启动 ====="
|
||||
|
||||
while ($true) {
|
||||
try {
|
||||
Invoke-AutoStash
|
||||
} catch {
|
||||
Write-Log "[异常] $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
$nextTime = (Get-Date).AddMinutes($IntervalMinutes).ToString("HH:mm:ss")
|
||||
Write-Host " 下次检查: $nextTime (间隔 ${IntervalMinutes}min)" -ForegroundColor DarkGray
|
||||
|
||||
Start-Sleep -Seconds ($IntervalMinutes * 60)
|
||||
}
|
||||
591
scripts/test_tool_api.py
Normal file
591
scripts/test_tool_api.py
Normal file
@@ -0,0 +1,591 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
闲言APP — 工具中心API验证脚本
|
||||
创建时间: 2026-05-30
|
||||
更新时间: 2026-05-30
|
||||
作用: 验证工具中心所有API接口,测试hanzi_search和searchall两种搜索类型,以及外部API和RSS源
|
||||
上次更新: 新增汇率换算、音乐识别、RSS源可达性测试,确认fortune接口仍可用
|
||||
"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
import sys
|
||||
import time
|
||||
|
||||
BASE_URL = 'https://tools.wktyl.com'
|
||||
|
||||
HANZI_SEARCH_TYPES = {
|
||||
'poetry': {'keyword': '春', 'name': '古诗词'},
|
||||
'brainteaser': {'keyword': '什么', 'name': '脑筋急转弯'},
|
||||
'couplet': {'keyword': '春', 'name': '对联大全'},
|
||||
'wisdom': {'keyword': '人生', 'name': '名人名言'},
|
||||
'story': {'keyword': '故事', 'name': '故事大全'},
|
||||
'saying': {'keyword': '早', 'name': '谚语大全'},
|
||||
'riddle': {'keyword': '月亮', 'name': '谜语'},
|
||||
'xiehouyu': {'keyword': '猪', 'name': '歇后语'},
|
||||
'zuowen': {'keyword': '春天', 'name': '作文大全'},
|
||||
'why': {'keyword': '为什么', 'name': '十万个为什么'},
|
||||
'drug': {'keyword': '感冒', 'name': '药品查询'},
|
||||
'food': {'keyword': '鸡蛋', 'name': '食物相克'},
|
||||
'herbal': {'keyword': '人参', 'name': '中药材'},
|
||||
'pianfang': {'keyword': '咳嗽', 'name': '民间偏方'},
|
||||
'tisana': {'keyword': '菊花', 'name': '药茶大全'},
|
||||
'changshi': {'keyword': '生活', 'name': '生活常识'},
|
||||
'lyric': {'keyword': '月亮', 'name': '歌词大全'},
|
||||
}
|
||||
|
||||
SEARCHALL_TYPES = {
|
||||
'zgjm': {'keyword': '水', 'name': '周公解梦'},
|
||||
'joke': {'keyword': '小明', 'name': '笑话大全'},
|
||||
'illness': {'keyword': '头痛', 'name': '疾病自查'},
|
||||
'surname': {'keyword': '王', 'name': '姓氏起源'},
|
||||
'jieqi': {'keyword': '立春', 'name': '节气查询'},
|
||||
'nation': {'keyword': '中国', 'name': '国家查询'},
|
||||
}
|
||||
|
||||
OTHER_APIS = {
|
||||
'hanzi_zi': {
|
||||
'method': 'POST',
|
||||
'url': '/api/hanzi/zi',
|
||||
'data': {'zi': '中'},
|
||||
'name': '查字',
|
||||
},
|
||||
'hanzi_zuci': {
|
||||
'method': 'POST',
|
||||
'url': '/api/hanzi/zuci',
|
||||
'data': {'zi': '中'},
|
||||
'name': '组词查询',
|
||||
},
|
||||
'hanzi_cidian': {
|
||||
'method': 'POST',
|
||||
'url': '/api/hanzi/cidian',
|
||||
'data': {'zi': '快乐'},
|
||||
'name': '词典查询',
|
||||
},
|
||||
'hanzi_chengyu': {
|
||||
'method': 'POST',
|
||||
'url': '/api/hanzi/chengyu',
|
||||
'data': {'zi': '一心一意'},
|
||||
'name': '成语查询',
|
||||
},
|
||||
'hanzi_jinyici': {
|
||||
'method': 'POST',
|
||||
'url': '/api/hanzi/jinyici',
|
||||
'data': {'zi': '快乐'},
|
||||
'name': '近义词查询',
|
||||
},
|
||||
'hanzi_juzi': {
|
||||
'method': 'POST',
|
||||
'url': '/api/hanzi/juzi',
|
||||
'data': {'zi': '春天'},
|
||||
'name': '句子查询',
|
||||
},
|
||||
'hanzi_danci': {
|
||||
'method': 'POST',
|
||||
'url': '/api/hanzi/danci',
|
||||
'data': {'zi': 'hello'},
|
||||
'name': '英语单词',
|
||||
},
|
||||
'hanzi_suoxie': {
|
||||
'method': 'POST',
|
||||
'url': '/api/hanzi/suoxie',
|
||||
'data': {'zi': 'AI'},
|
||||
'name': '英文缩写',
|
||||
},
|
||||
'hitokoto_random': {
|
||||
'method': 'GET',
|
||||
'url': '/api/hitokoto/random',
|
||||
'params': {'num': 1},
|
||||
'name': '一言随机',
|
||||
},
|
||||
'hitokoto_categories': {
|
||||
'method': 'GET',
|
||||
'url': '/api/hitokoto/categories',
|
||||
'params': None,
|
||||
'name': '一言分类',
|
||||
},
|
||||
'jiufang_search': {
|
||||
'method': 'GET',
|
||||
'url': '/api/webapi/jiufang_search',
|
||||
'params': {'keyword': '人参'},
|
||||
'name': '酒方搜索',
|
||||
},
|
||||
'daily_recommend': {
|
||||
'method': 'GET',
|
||||
'url': '/api/daily/recommend',
|
||||
'params': None,
|
||||
'name': '每日推荐',
|
||||
},
|
||||
'china_colors': {
|
||||
'method': 'GET',
|
||||
'url': '/api/hanzi/china_colors',
|
||||
'params': None,
|
||||
'name': '中国传统色',
|
||||
},
|
||||
'nick_gen': {
|
||||
'method': 'POST',
|
||||
'url': '/api/hanzi/nick',
|
||||
'data': {'zi': '风'},
|
||||
'name': '网名生成',
|
||||
},
|
||||
'ip_query': {
|
||||
'method': 'POST',
|
||||
'url': '/api/webapi/ip',
|
||||
'data': {'ip': '8.8.8.8'},
|
||||
'name': 'IP查询',
|
||||
},
|
||||
'ip_check': {
|
||||
'method': 'POST',
|
||||
'url': '/api/ipcheck/check',
|
||||
'data': {'ip': '8.8.8.8', 'port': 443},
|
||||
'name': 'IP连通性检测',
|
||||
},
|
||||
'stats_overview': {
|
||||
'method': 'GET',
|
||||
'url': '/api/webapi/stats_overview',
|
||||
'params': None,
|
||||
'name': '站点统计概览',
|
||||
},
|
||||
'stats_hot_tools': {
|
||||
'method': 'GET',
|
||||
'url': '/api/webapi/stats_hot_tools',
|
||||
'params': {'limit': 5},
|
||||
'name': '热门工具排行',
|
||||
},
|
||||
'stats_content': {
|
||||
'method': 'GET',
|
||||
'url': '/api/webapi/stats_content',
|
||||
'params': None,
|
||||
'name': '内容数据统计',
|
||||
},
|
||||
'check_sources': {
|
||||
'method': 'GET',
|
||||
'url': '/api/check/sources',
|
||||
'params': None,
|
||||
'name': '查重数据源列表',
|
||||
},
|
||||
'check_exact': {
|
||||
'method': 'POST',
|
||||
'url': '/api/check/exact',
|
||||
'data': {'text': '春眠不觉晓'},
|
||||
'name': '精确查重',
|
||||
},
|
||||
'fortune_daily': {
|
||||
'method': 'GET',
|
||||
'url': '/api/fortune/daily',
|
||||
'params': {'uid': 'test_user_api'},
|
||||
'name': '每日运势',
|
||||
},
|
||||
'fortune_themes': {
|
||||
'method': 'GET',
|
||||
'url': '/api/fortune/themes',
|
||||
'params': None,
|
||||
'name': '运势卡片风格',
|
||||
},
|
||||
'fortune_60s': {
|
||||
'method': 'GET',
|
||||
'url': '/api/fortune/60s',
|
||||
'params': None,
|
||||
'name': '60秒新闻',
|
||||
},
|
||||
'fortune_huangli': {
|
||||
'method': 'GET',
|
||||
'url': '/api/fortune/huangli',
|
||||
'params': None,
|
||||
'name': '黄历数据',
|
||||
},
|
||||
'fortune_horoscope': {
|
||||
'method': 'GET',
|
||||
'url': '/api/fortune/horoscope',
|
||||
'params': {'sign': '白羊', 'time': 'today'},
|
||||
'name': '星座运势',
|
||||
},
|
||||
'feed_stats': {
|
||||
'method': 'GET',
|
||||
'url': '/api/feed/stats',
|
||||
'params': None,
|
||||
'name': 'Feed信息流统计',
|
||||
},
|
||||
'searchall_suggest': {
|
||||
'method': 'GET',
|
||||
'url': '/api/searchall/suggest',
|
||||
'params': {'keyword': '春'},
|
||||
'name': '搜索建议',
|
||||
},
|
||||
'unit_temp': {
|
||||
'method': 'POST',
|
||||
'url': '/api/hanzi/temp',
|
||||
'data': {'value': '100', 'type': 'c2f'},
|
||||
'name': '温度换算',
|
||||
},
|
||||
'unit_speed': {
|
||||
'method': 'POST',
|
||||
'url': '/api/hanzi/speed',
|
||||
'data': {'value': '100', 'type': 'kmh2ms'},
|
||||
'name': '速度换算',
|
||||
},
|
||||
'unit_weight': {
|
||||
'method': 'POST',
|
||||
'url': '/api/hanzi/weight',
|
||||
'data': {'value': '1', 'type': 'kg2lb'},
|
||||
'name': '重量换算',
|
||||
},
|
||||
}
|
||||
|
||||
EXTERNAL_APIS = {
|
||||
'exchange_rate_cny': {
|
||||
'method': 'GET',
|
||||
'url': 'https://open.er-api.com/v6/latest/CNY',
|
||||
'name': '汇率换算(CNY基准)',
|
||||
'validate': 'rates_field',
|
||||
'required_currencies': ['USD', 'EUR', 'JPY'],
|
||||
},
|
||||
'audd_lyrics_search': {
|
||||
'method': 'GET',
|
||||
'url': 'https://api.audd.io/findLyrics/',
|
||||
'params': {'api_token': 'test', 'q': 'hello'},
|
||||
'name': '音乐识别-歌词搜索',
|
||||
'validate': 'reachable',
|
||||
},
|
||||
'audd_recognize': {
|
||||
'method': 'POST',
|
||||
'url': 'https://api.audd.io/recognizeWithOffset',
|
||||
'data': {'api_token': 'test'},
|
||||
'name': '音乐识别-API可达性',
|
||||
'validate': 'reachable',
|
||||
},
|
||||
}
|
||||
|
||||
RSS_FEEDS = {
|
||||
'36kr': {
|
||||
'url': 'https://36kr.com/feed',
|
||||
'name': '36氪',
|
||||
},
|
||||
'ifanr': {
|
||||
'url': 'https://www.ifanr.com/feed',
|
||||
'name': '爱范儿',
|
||||
},
|
||||
'sspai': {
|
||||
'url': 'https://sspai.com/feed',
|
||||
'name': '少数派',
|
||||
},
|
||||
'v2ex': {
|
||||
'url': 'https://www.v2ex.com/index.xml',
|
||||
'name': 'V2EX',
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def test_hanzi_search():
|
||||
print('\n' + '=' * 60)
|
||||
print(' 一、hanzi_search 接口测试 (17种类型)')
|
||||
print('=' * 60)
|
||||
|
||||
results = {'pass': 0, 'fail': 0, 'errors': []}
|
||||
|
||||
for type_key, info in HANZI_SEARCH_TYPES.items():
|
||||
try:
|
||||
resp = requests.post(
|
||||
f'{BASE_URL}/api/hanzi/search',
|
||||
data={'zi': info['keyword'], 'type': type_key, 'page': 1, 'limit': 5},
|
||||
timeout=10,
|
||||
)
|
||||
data = resp.json()
|
||||
|
||||
if data.get('code') == 1:
|
||||
total = data.get('data', {}).get('total', 0)
|
||||
list_count = len(data.get('data', {}).get('list', []))
|
||||
print(f' ✅ {info["name"]:8s} (type={type_key:14s}) — 总{total}条, 返回{list_count}条')
|
||||
results['pass'] += 1
|
||||
else:
|
||||
msg = data.get('msg', '未知错误')
|
||||
print(f' ❌ {info["name"]:8s} (type={type_key:14s}) — {msg}')
|
||||
results['fail'] += 1
|
||||
results['errors'].append(f'{info["name"]}: {msg}')
|
||||
except Exception as e:
|
||||
print(f' 💥 {info["name"]:8s} (type={type_key:14s}) — 请求异常: {e}')
|
||||
results['fail'] += 1
|
||||
results['errors'].append(f'{info["name"]}: 请求异常 {e}')
|
||||
|
||||
time.sleep(0.3)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def test_searchall():
|
||||
print('\n' + '=' * 60)
|
||||
print(' 二、searchall 接口测试 (6种类型)')
|
||||
print('=' * 60)
|
||||
|
||||
results = {'pass': 0, 'fail': 0, 'errors': []}
|
||||
|
||||
for type_key, info in SEARCHALL_TYPES.items():
|
||||
try:
|
||||
resp = requests.get(
|
||||
f'{BASE_URL}/api/searchall/search',
|
||||
params={'keyword': info['keyword'], 'type': type_key, 'page': 1, 'limit': 5},
|
||||
timeout=10,
|
||||
)
|
||||
data = resp.json()
|
||||
|
||||
if data.get('code') == 1:
|
||||
total = data.get('data', {}).get('total', 0)
|
||||
list_count = len(data.get('data', {}).get('list', []))
|
||||
print(f' ✅ {info["name"]:8s} (type={type_key:14s}) — 总{total}条, 返回{list_count}条')
|
||||
results['pass'] += 1
|
||||
else:
|
||||
msg = data.get('msg', '未知错误')
|
||||
print(f' ❌ {info["name"]:8s} (type={type_key:14s}) — {msg}')
|
||||
results['fail'] += 1
|
||||
results['errors'].append(f'{info["name"]}: {msg}')
|
||||
except Exception as e:
|
||||
print(f' 💥 {info["name"]:8s} (type={type_key:14s}) — 请求异常: {e}')
|
||||
results['fail'] += 1
|
||||
results['errors'].append(f'{info["name"]}: 请求异常 {e}')
|
||||
|
||||
time.sleep(0.3)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def test_other_apis():
|
||||
print('\n' + '=' * 60)
|
||||
print(' 三、其他工具接口测试')
|
||||
print('=' * 60)
|
||||
|
||||
results = {'pass': 0, 'fail': 0, 'errors': []}
|
||||
|
||||
for api_key, info in OTHER_APIS.items():
|
||||
try:
|
||||
if info['method'] == 'POST':
|
||||
resp = requests.post(
|
||||
f'{BASE_URL}{info["url"]}',
|
||||
data=info.get('data'),
|
||||
timeout=10,
|
||||
)
|
||||
else:
|
||||
resp = requests.get(
|
||||
f'{BASE_URL}{info["url"]}',
|
||||
params=info.get('params'),
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
data = resp.json()
|
||||
|
||||
if data.get('code') == 1:
|
||||
data_preview = ''
|
||||
if isinstance(data.get('data'), dict):
|
||||
keys = list(data['data'].keys())[:3]
|
||||
data_preview = f' — 字段: {", ".join(keys)}...'
|
||||
elif isinstance(data.get('data'), list):
|
||||
data_preview = f' — {len(data["data"])}条'
|
||||
print(f' ✅ {info["name"]:12s} ({api_key:20s}){data_preview}')
|
||||
results['pass'] += 1
|
||||
else:
|
||||
msg = data.get('msg', '未知错误')
|
||||
print(f' ❌ {info["name"]:12s} ({api_key:20s}) — {msg}')
|
||||
results['fail'] += 1
|
||||
results['errors'].append(f'{info["name"]}: {msg}')
|
||||
except requests.exceptions.JSONDecodeError:
|
||||
print(f' ⚠️ {info["name"]:12s} ({api_key:20s}) — 返回非JSON(可能已下线)')
|
||||
results['fail'] += 1
|
||||
results['errors'].append(f'{info["name"]}: 返回非JSON')
|
||||
except Exception as e:
|
||||
print(f' 💥 {info["name"]:12s} ({api_key:20s}) — 请求异常: {e}')
|
||||
results['fail'] += 1
|
||||
results['errors'].append(f'{info["name"]}: 请求异常 {e}')
|
||||
|
||||
time.sleep(0.3)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def test_error_type():
|
||||
print('\n' + '=' * 60)
|
||||
print(' 四、错误类型验证(确认不支持的类型返回正确错误)')
|
||||
print('=' * 60)
|
||||
|
||||
invalid_types = ['invalid_type', 'zgjm', 'joke', 'illness']
|
||||
|
||||
for t in invalid_types:
|
||||
try:
|
||||
resp = requests.post(
|
||||
f'{BASE_URL}/api/hanzi/search',
|
||||
data={'zi': '测试', 'type': t, 'page': 1, 'limit': 5},
|
||||
timeout=10,
|
||||
)
|
||||
data = resp.json()
|
||||
if data.get('code') == 0 and '不支持的搜索类型' in data.get('msg', ''):
|
||||
print(f' ✅ type={t:14s} — 正确返回错误: {data["msg"][:40]}...')
|
||||
else:
|
||||
print(f' ⚠️ type={t:14s} — 意外响应: code={data.get("code")}, msg={data.get("msg")}')
|
||||
except Exception as e:
|
||||
print(f' 💥 type={t:14s} — 请求异常: {e}')
|
||||
|
||||
time.sleep(0.3)
|
||||
|
||||
|
||||
def test_external_apis():
|
||||
print('\n' + '=' * 60)
|
||||
print(' 五、外部API接口测试(汇率/音乐识别)')
|
||||
print('=' * 60)
|
||||
|
||||
results = {'pass': 0, 'fail': 0, 'errors': []}
|
||||
|
||||
for api_key, info in EXTERNAL_APIS.items():
|
||||
try:
|
||||
if info['method'] == 'POST':
|
||||
resp = requests.post(info['url'], data=info.get('data'), timeout=15)
|
||||
else:
|
||||
resp = requests.get(info['url'], params=info.get('params'), timeout=15)
|
||||
|
||||
validate_type = info.get('validate', 'reachable')
|
||||
|
||||
if validate_type == 'rates_field':
|
||||
data = resp.json()
|
||||
rates = data.get('rates', {})
|
||||
if not rates:
|
||||
print(f' ❌ {info["name"]:20s} ({api_key}) — rates字段为空')
|
||||
results['fail'] += 1
|
||||
results['errors'].append(f'{info["name"]}: rates字段为空')
|
||||
continue
|
||||
missing = [c for c in info.get('required_currencies', []) if c not in rates]
|
||||
if missing:
|
||||
print(f' ❌ {info["name"]:20s} ({api_key}) — 缺少货币: {", ".join(missing)}')
|
||||
results['fail'] += 1
|
||||
results['errors'].append(f'{info["name"]}: 缺少货币 {", ".join(missing)}')
|
||||
else:
|
||||
base = data.get('base_code', '?')
|
||||
currency_count = len(rates)
|
||||
sample = {c: rates[c] for c in info.get('required_currencies', [])[:3]}
|
||||
print(f' ✅ {info["name"]:20s} ({api_key}) — 基准:{base}, {currency_count}种货币, 示例:{sample}')
|
||||
results['pass'] += 1
|
||||
|
||||
elif validate_type == 'reachable':
|
||||
if resp.status_code < 500:
|
||||
status = resp.status_code
|
||||
try:
|
||||
body = resp.json()
|
||||
result_status = body.get('status', '')
|
||||
print(f' ✅ {info["name"]:20s} ({api_key}) — HTTP {status}, status={result_status}')
|
||||
except Exception:
|
||||
print(f' ✅ {info["name"]:20s} ({api_key}) — HTTP {status}, API可达')
|
||||
results['pass'] += 1
|
||||
else:
|
||||
print(f' ❌ {info["name"]:20s} ({api_key}) — HTTP {resp.status_code}, 服务不可达')
|
||||
results['fail'] += 1
|
||||
results['errors'].append(f'{info["name"]}: HTTP {resp.status_code}')
|
||||
|
||||
except requests.exceptions.Timeout:
|
||||
print(f' ⏱️ {info["name"]:20s} ({api_key}) — 请求超时(15s)')
|
||||
results['fail'] += 1
|
||||
results['errors'].append(f'{info["name"]}: 请求超时')
|
||||
except Exception as e:
|
||||
print(f' 💥 {info["name"]:20s} ({api_key}) — 请求异常: {e}')
|
||||
results['fail'] += 1
|
||||
results['errors'].append(f'{info["name"]}: 请求异常 {e}')
|
||||
|
||||
time.sleep(0.5)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def test_rss_feeds():
|
||||
print('\n' + '=' * 60)
|
||||
print(' 六、RSS源可达性测试')
|
||||
print('=' * 60)
|
||||
|
||||
results = {'pass': 0, 'fail': 0, 'errors': []}
|
||||
|
||||
for feed_key, info in RSS_FEEDS.items():
|
||||
try:
|
||||
resp = requests.get(info['url'], timeout=15, headers={
|
||||
'User-Agent': 'Mozilla/5.0 (compatible; XianYanApp/1.0; RSS Reader)'
|
||||
})
|
||||
|
||||
if resp.status_code != 200:
|
||||
print(f' ❌ {info["name"]:8s} ({feed_key:8s}) — HTTP {resp.status_code}')
|
||||
results['fail'] += 1
|
||||
results['errors'].append(f'{info["name"]}: HTTP {resp.status_code}')
|
||||
continue
|
||||
|
||||
content = resp.text
|
||||
has_xml = '<rss' in content or '<feed' in content or '<xml' in content or '<?xml' in content
|
||||
|
||||
if has_xml:
|
||||
tag = '<rss' if '<rss' in content else ('<feed' if '<feed' in content else '<?xml')
|
||||
item_count = content.count('<item') + content.count('<entry')
|
||||
print(f' ✅ {info["name"]:8s} ({feed_key:8s}) — HTTP 200, 含{tag}, 约{item_count}条内容')
|
||||
results['pass'] += 1
|
||||
else:
|
||||
print(f' ⚠️ {info["name"]:8s} ({feed_key:8s}) — HTTP 200, 但未检测到XML标签')
|
||||
results['fail'] += 1
|
||||
results['errors'].append(f'{info["name"]}: 无XML标签')
|
||||
|
||||
except requests.exceptions.Timeout:
|
||||
print(f' ⏱️ {info["name"]:8s} ({feed_key:8s}) — 请求超时(15s)')
|
||||
results['fail'] += 1
|
||||
results['errors'].append(f'{info["name"]}: 请求超时')
|
||||
except Exception as e:
|
||||
print(f' 💥 {info["name"]:8s} ({feed_key:8s}) — 请求异常: {e}')
|
||||
results['fail'] += 1
|
||||
results['errors'].append(f'{info["name"]}: 请求异常 {e}')
|
||||
|
||||
time.sleep(0.5)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def main():
|
||||
print('╔══════════════════════════════════════════════════════════╗')
|
||||
print('║ 闲言APP — 工具中心API全面验证脚本 ║')
|
||||
print('║ 基础URL: https://tools.wktyl.com ║')
|
||||
print('╚══════════════════════════════════════════════════════════╝')
|
||||
|
||||
all_results = []
|
||||
|
||||
r1 = test_hanzi_search()
|
||||
all_results.append(('hanzi_search', r1))
|
||||
|
||||
r2 = test_searchall()
|
||||
all_results.append(('searchall', r2))
|
||||
|
||||
r3 = test_other_apis()
|
||||
all_results.append(('other_apis', r3))
|
||||
|
||||
test_error_type()
|
||||
|
||||
r5 = test_external_apis()
|
||||
all_results.append(('external_apis', r5))
|
||||
|
||||
r6 = test_rss_feeds()
|
||||
all_results.append(('rss_feeds', r6))
|
||||
|
||||
print('\n' + '=' * 60)
|
||||
print(' 汇总报告')
|
||||
print('=' * 60)
|
||||
|
||||
total_pass = sum(r['pass'] for _, r in all_results)
|
||||
total_fail = sum(r['fail'] for _, r in all_results)
|
||||
total = total_pass + total_fail
|
||||
|
||||
for name, r in all_results:
|
||||
status = '✅ 全部通过' if r['fail'] == 0 else f'❌ {r["fail"]}个失败'
|
||||
print(f' {name:16s}: {r["pass"]}通过 / {r["fail"]}失败 — {status}')
|
||||
|
||||
print(f'\n 总计: {total_pass}/{total} 通过 ({total_pass * 100 // max(total, 1)}%)')
|
||||
|
||||
if total_fail > 0:
|
||||
print('\n ❌ 失败详情:')
|
||||
for name, r in all_results:
|
||||
for err in r['errors']:
|
||||
print(f' - [{name}] {err}')
|
||||
|
||||
print('\n' + '=' * 60)
|
||||
|
||||
return 0 if total_fail == 0 else 1
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user