Files
xianyan/Script/ui_enhance_api_test.dart
Developer 00ff5f152a feat: 添加清除结果功能到检查提供者
refactor: 更新URL哈希处理逻辑

feat: 添加聊天消息存储支持

docs: 更新API控制器基类文档

chore: 删除无用脚本文件

fix: 修复分类模型返回类型问题

feat: 添加回执登录功能

build: 更新依赖项配置

style: 统一HTML模板中的哈希ID引用格式

ci: 添加部署和检查脚本
2026-04-30 10:19:56 +08:00

600 lines
19 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 — UI增强相关API接口验证脚本
// 创建时间: 2026-04-30
// 更新时间: 2026-04-30
// 作用: 验证成就/打卡/查重/金币/公开主页/文章/仪表盘/统计等接口
// 上次更新: 初始创建覆盖10个页面所需的全部API
// ============================================================
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:crypto/crypto.dart';
const String baseUrl = 'https://tools.wktyl.com';
const String receiptSecret = 'Xy7kP9mL2qR4wS8v';
const String testAccount = 'apitest_user';
const String testPassword = '123456';
String? authToken;
int passCount = 0;
int failCount = 0;
final List<String> results = [];
final dio = Dio(
BaseOptions(
baseUrl: baseUrl,
connectTimeout: const Duration(seconds: 30),
receiveTimeout: const Duration(seconds: 30),
headers: {'Accept': 'application/json'},
),
);
void setToken(String? t) {
authToken = t;
if (t != null && t.isNotEmpty) {
dio.options.headers['token'] = t;
} else {
dio.options.headers.remove('token');
}
}
Map<String, String> generateReceipt(String action, String payloadStr) {
final payloadDigest = sha256
.convert(utf8.encode(payloadStr))
.toString()
.substring(0, 16);
final data = {
'action': action,
'payload': payloadDigest,
'ts': DateTime.now().millisecondsSinceEpoch ~/ 1000,
'nonce': List.generate(8, (_) => DateTime.now().microsecondsSinceEpoch.toRadixString(16)).join().substring(0, 8),
};
final receipt = base64Encode(utf8.encode(jsonEncode(data)));
final sig = Hmac(sha256, utf8.encode(receiptSecret))
.convert(utf8.encode(receipt))
.toString();
return {'receipt': receipt, 'sig': sig};
}
Future<Map<String, dynamic>> post(String path, {Map<String, dynamic>? data}) async {
final resp = await dio.post<Map<String, dynamic>>(path, data: data);
return resp.data ?? {};
}
Future<Map<String, dynamic>> get(String path, {Map<String, dynamic>? params}) async {
final resp = await dio.get<Map<String, dynamic>>(path, queryParameters: params);
return resp.data ?? {};
}
void log(String name, bool passed, [String detail = '']) {
final icon = passed ? '' : '';
final msg = '$icon $name${detail.isNotEmpty ? '$detail' : ''}';
results.add(msg);
print(msg);
if (passed) passCount++; else failCount++;
}
// ============================================================
// 登录获取Token
// ============================================================
Future<void> testLogin() async {
print('\n🔑 登录获取Token');
try {
final resp = await post('/api/user_security/login', data: {
'account': testAccount,
'password': testPassword,
});
final code = resp['code'] as int? ?? 0;
if (code == 1) {
final data = resp['data'] as Map<String, dynamic>?;
final userinfo = data?['userinfo'] as Map<String, dynamic>?;
final token = userinfo?['token'] as String? ?? data?['token'] as String? ?? '';
setToken(token);
log('登录', token.isNotEmpty, 'token=${token.substring(0, 20)}...');
} else {
log('登录', false, 'code=$code, msg=${resp['msg']}');
}
} catch (e) {
log('登录', false, e.toString());
}
}
// ============================================================
// 模块6: 成就与激励体系
// ============================================================
Future<void> testAchievementAPIs() async {
print('\n🏆 成就与激励体系');
// 成就列表
try {
final resp = await get('/api/achievement/list', params: {'type': 'signin'});
final code = resp['code'] as int? ?? 0;
final data = resp['data'] as List?;
log('成就列表(signin)', code == 1, 'count=${data?.length ?? 0}');
} catch (e) {
log('成就列表', false, e.toString());
}
// 我的成就
try {
final resp = await get('/api/achievement/my');
final code = resp['code'] as int? ?? 0;
final data = resp['data'] as Map<String, dynamic>?;
log('我的成就', code == 1, 'achieved=${data?['achieved']?.length}, unachieved=${data?['unachieved']?.length}');
} catch (e) {
log('我的成就', false, e.toString());
}
// 学习打卡
try {
final resp = await post('/api/achievement/checkin', data: {'type': 'poetry'});
final code = resp['code'] as int? ?? 0;
log('学习打卡(poetry)', code == 1 || (resp['msg'] as String?)?.contains('已打卡') == true,
'code=$code, msg=${resp['msg']}');
} catch (e) {
log('学习打卡', false, e.toString());
}
// 每日签到
try {
final resp = await post('/api/user_center/signin');
final code = resp['code'] as int? ?? 0;
log('每日签到', code == 1 || (resp['msg'] as String?)?.contains('已签到') == true,
'code=$code, msg=${resp['msg']}');
} catch (e) {
log('每日签到', false, e.toString());
}
// 签到日历
try {
final resp = await get('/api/user_center/signin_calendar', params: {'month': '2026-04'});
final code = resp['code'] as int? ?? 0;
final data = resp['data'] as Map<String, dynamic>?;
log('签到日历', code == 1, 'keys=${data?.keys.toList()}');
} catch (e) {
log('签到日历', false, e.toString());
}
// 补签
try {
final r = generateReceipt('signin_makeup', '2026-04-28');
final resp = await post('/api/user_center/signin_makeup', data: {
'date': '2026-04-28',
'receipt': r['receipt'],
'sig': r['sig'],
});
final code = resp['code'] as int? ?? 0;
log('补签', code == 1 || (resp['msg'] as String?)?.contains('已签到') == true,
'code=$code, msg=${resp['msg']}');
} catch (e) {
log('补签', false, e.toString());
}
}
// ============================================================
// 模块13: 内容查重
// ============================================================
Future<void> testCheckAPIs() async {
print('\n🔍 内容查重');
// 数据源列表
try {
final resp = await get('/api/check/sources');
final code = resp['code'] as int? ?? 0;
final data = resp['data'] as List?;
log('数据源列表', code == 1, 'count=${data?.length ?? 0}');
} catch (e) {
log('数据源列表', false, e.toString());
}
// 精确查重
try {
final resp = await post('/api/check/exact', data: {
'text': '君不见黄河之水天上来',
'type': 'poetry',
});
final code = resp['code'] as int? ?? 0;
log('精确查重', code == 1, 'matches=${(resp['data'] as Map?)?['matches']}');
} catch (e) {
log('精确查重', false, e.toString());
}
// 模糊查重
try {
final resp = await post('/api/check/fuzzy', data: {
'text': '黄河之水天上来奔流到海不复回',
});
final code = resp['code'] as int? ?? 0;
log('模糊查重', code == 1, 'code=$code');
} catch (e) {
log('模糊查重', false, e.toString());
}
// 相似度查重
try {
final resp = await post('/api/check/similar', data: {
'text': '床前明月光疑是地上霜',
'limit': 5,
});
final code = resp['code'] as int? ?? 0;
log('相似度查重', code == 1, 'code=$code');
} catch (e) {
log('相似度查重', false, e.toString());
}
// 综合报告
try {
final resp = await post('/api/check/report', data: {
'text': '白日依山尽黄河入海流',
});
final code = resp['code'] as int? ?? 0;
final data = resp['data'] as Map<String, dynamic>?;
log('综合报告', code == 1,
'risk=${data?['risk_level']}, score=${data?['risk_score']}, max_sim=${data?['max_similarity']}');
} catch (e) {
log('综合报告', false, e.toString());
}
}
// ============================================================
// 金币记录
// ============================================================
Future<void> testCoinAPIs() async {
print('\n💰 金币记录');
try {
final resp = await get('/api/user_center/coin', params: {'page': 1, 'limit': 10});
final code = resp['code'] as int? ?? 0;
final data = resp['data'] as Map<String, dynamic>?;
final list = data?['list'] as List?;
log('金币记录', code == 1, 'count=${list?.length ?? 0}');
} catch (e) {
log('金币记录', false, e.toString());
}
}
// ============================================================
// 公开主页
// ============================================================
Future<void> testPublicProfileAPIs() async {
print('\n👤 公开主页');
try {
final resp = await get('/api/user_center/public_profile', params: {'uid': 39});
final code = resp['code'] as int? ?? 0;
final data = resp['data'] as Map<String, dynamic>?;
log('公开主页', code == 1,
'name=${data?['display_name']}, title=${data?['title']}, score=${data?['score']}');
} catch (e) {
log('公开主页', false, e.toString());
}
}
// ============================================================
// 文章模块
// ============================================================
Future<void> testArticleAPIs() async {
print('\n📝 文章模块');
// 文章列表
try {
final resp = await get('/api/article/list', params: {'page': 1, 'sort': 'newst'});
final code = resp['code'] as int? ?? 0;
final data = resp['data'] as Map<String, dynamic>?;
final list = data?['list'] as List?;
log('文章列表', code == 1, 'count=${list?.length ?? 0}');
} catch (e) {
log('文章列表', false, e.toString());
}
// 文章详情
try {
final resp = await get('/api/article/detail', params: {'id': 1});
final code = resp['code'] as int? ?? 0;
final data = resp['data'] as Map<String, dynamic>?;
log('文章详情', code == 1, 'title=${data?['title']}');
} catch (e) {
log('文章详情', false, e.toString());
}
// 我的文章
try {
final resp = await get('/api/article/mine', params: {'page': 1});
final code = resp['code'] as int? ?? 0;
final data = resp['data'] as Map<String, dynamic>?;
final list = data?['list'] as List?;
log('我的文章', code == 1, 'count=${list?.length ?? 0}');
} catch (e) {
log('我的文章', false, e.toString());
}
}
// ============================================================
// 仪表盘 + 学习中心
// ============================================================
Future<void> testDashboardAPIs() async {
print('\n📚 学习中心');
// 仪表盘
try {
final resp = await get('/api/user_center/dashboard');
final code = resp['code'] as int? ?? 0;
final data = resp['data'] as Map<String, dynamic>?;
log('仪表盘', code == 1,
'score=${data?['score']}, signin=${data?['signin_days']}, fav=${data?['favorite_count']}, note=${data?['note_count']}');
} catch (e) {
log('仪表盘', false, e.toString());
}
// 学习统计 - overview
try {
final resp = await get('/api/user_center/stats', params: {'type': 'overview'});
final code = resp['code'] as int? ?? 0;
log('学习统计(overview)', code == 1, 'code=$code');
} catch (e) {
log('学习统计(overview)', false, e.toString());
}
// 学习统计 - category
try {
final resp = await get('/api/user_center/stats', params: {'type': 'category'});
final code = resp['code'] as int? ?? 0;
log('学习统计(category)', code == 1, 'code=$code');
} catch (e) {
log('学习统计(category)', false, e.toString());
}
// 学习统计 - trend
try {
final resp = await get('/api/user_center/stats', params: {'type': 'trend'});
final code = resp['code'] as int? ?? 0;
log('学习统计(trend)', code == 1, 'code=$code');
} catch (e) {
log('学习统计(trend)', false, e.toString());
}
// 热力图
try {
final resp = await get('/api/user_center/heatmap', params: {'year': 2026});
final code = resp['code'] as int? ?? 0;
final data = resp['data'] as Map<String, dynamic>?;
log('热力图', code == 1, 'keys=${data?.keys.take(5).toList()}');
} catch (e) {
log('热力图', false, e.toString());
}
// 每日推荐
try {
final resp = await get('/api/daily/recommend');
final code = resp['code'] as int? ?? 0;
final data = resp['data'] as Map<String, dynamic>?;
log('每日推荐', code == 1,
'poetry=${(data?['poetry'] as Map?)?['title']}, wisdom=${(data?['wisdom'] as Map?)?['author']}');
} catch (e) {
log('每日推荐', false, e.toString());
}
}
// ============================================================
// 统计模块
// ============================================================
Future<void> testStatisticsAPIs() async {
print('\n📊 数据统计');
// 站点总览
try {
final resp = await get('/api/webapi/stats_overview');
final code = resp['code'] as int? ?? 0;
final data = resp['data'] as Map<String, dynamic>?;
log('站点总览', code == 1,
'users=${data?['total_users']}, articles=${data?['total_articles']}');
} catch (e) {
log('站点总览', false, e.toString());
}
// 热门工具
try {
final resp = await get('/api/webapi/stats_hot_tools', params: {'limit': 10});
final code = resp['code'] as int? ?? 0;
final data = resp['data'] as List?;
log('热门工具', code == 1, 'count=${data?.length ?? 0}');
} catch (e) {
log('热门工具', false, e.toString());
}
// 热门文章
try {
final resp = await get('/api/webapi/stats_hot_articles', params: {'limit': 10});
final code = resp['code'] as int? ?? 0;
final data = resp['data'] as List?;
log('热门文章', code == 1, 'count=${data?.length ?? 0}');
} catch (e) {
log('热门文章', false, e.toString());
}
// 内容统计
try {
final resp = await get('/api/webapi/stats_content');
final code = resp['code'] as int? ?? 0;
log('内容统计', code == 1, 'code=$code');
} catch (e) {
log('内容统计', false, e.toString());
}
// 用户活跃
try {
final resp = await get('/api/webapi/stats_user_activity');
final code = resp['code'] as int? ?? 0;
log('用户活跃', code == 1, 'code=$code');
} catch (e) {
log('用户活跃', false, e.toString());
}
// 趋势数据7天
try {
final resp = await get('/api/webapi/stats_trend', params: {'days': 7});
final code = resp['code'] as int? ?? 0;
log('趋势数据(7天)', code == 1, 'code=$code');
} catch (e) {
log('趋势数据(7天)', false, e.toString());
}
// 趋势数据30天
try {
final resp = await get('/api/webapi/stats_trend', params: {'days': 30});
final code = resp['code'] as int? ?? 0;
log('趋势数据(30天)', code == 1, 'code=$code');
} catch (e) {
log('趋势数据(30天)', false, e.toString());
}
// API调用统计
try {
final resp = await get('/api/webapi/stats_api_usage', params: {'days': 7});
final code = resp['code'] as int? ?? 0;
log('API调用统计', code == 1, 'code=$code');
} catch (e) {
log('API调用统计', false, e.toString());
}
// 站点统计 - overview
try {
final resp = await get('/api/statistics/overview');
final code = resp['code'] as int? ?? 0;
log('站点统计(overview)', code == 1, 'code=$code');
} catch (e) {
log('站点统计(overview)', false, e.toString());
}
// 签到统计
try {
final resp = await get('/api/statistics/signins');
final code = resp['code'] as int? ?? 0;
log('签到统计', code == 1, 'code=$code');
} catch (e) {
log('签到统计', false, e.toString());
}
// 金币统计
try {
final resp = await get('/api/statistics/coins');
final code = resp['code'] as int? ?? 0;
log('金币统计', code == 1, 'code=$code');
} catch (e) {
log('金币统计', false, e.toString());
}
// 笔记统计
try {
final resp = await get('/api/statistics/notes');
final code = resp['code'] as int? ?? 0;
log('笔记统计', code == 1, 'code=$code');
} catch (e) {
log('笔记统计', false, e.toString());
}
}
// ============================================================
// Feed互动
// ============================================================
Future<void> testFeedInteractionAPIs() async {
print('\n💬 Feed互动');
// 收藏列表
try {
final resp = await get('/api/feed/favorites', params: {'limit': 5});
final code = resp['code'] as int? ?? 0;
log('收藏列表', code == 1, 'code=$code');
} catch (e) {
log('收藏列表', false, e.toString());
}
// 点赞列表
try {
final resp = await get('/api/feed/likes', params: {'limit': 5});
final code = resp['code'] as int? ?? 0;
log('点赞列表', code == 1, 'code=$code');
} catch (e) {
log('点赞列表', false, e.toString());
}
// 浏览历史
try {
final resp = await get('/api/feed/history', params: {'limit': 5});
final code = resp['code'] as int? ?? 0;
log('浏览历史', code == 1, 'code=$code');
} catch (e) {
log('浏览历史', false, e.toString());
}
// 稍后读
try {
final resp = await get('/api/feed/readlater', params: {'limit': 5});
final code = resp['code'] as int? ?? 0;
log('稍后读列表', code == 1, 'code=$code');
} catch (e) {
log('稍后读列表', false, e.toString());
}
// 互动操作 - like
try {
final resp = await post('/api/feed/action', data: {
'action': 'like',
'feed_type': 'poetry',
'feed_id': 1,
});
final code = resp['code'] as int? ?? 0;
log('互动-点赞', code == 1, 'code=$code, msg=${resp['msg']}');
} catch (e) {
log('互动-点赞', false, e.toString());
}
// 互动操作 - counts
try {
final resp = await post('/api/feed/action', data: {
'action': 'counts',
'feed_type': 'poetry',
'feed_id': 1,
});
final code = resp['code'] as int? ?? 0;
log('互动-统计', code == 1, 'code=$code');
} catch (e) {
log('互动-统计', false, e.toString());
}
}
// ============================================================
// 主函数
// ============================================================
Future<void> main() async {
print('╔══════════════════════════════════════════╗');
print('║ 闲言APP — UI增强API接口验证 ║');
print('║ 覆盖: 成就/打卡/查重/金币/主页/文章/ ║');
print('║ 仪表盘/统计/Feed互动 ║');
print('╚══════════════════════════════════════════╝');
await testLogin();
if (authToken == null || authToken!.isEmpty) {
print('\n❌ 登录失败,终止测试');
return;
}
await testAchievementAPIs();
await testCheckAPIs();
await testCoinAPIs();
await testPublicProfileAPIs();
await testArticleAPIs();
await testDashboardAPIs();
await testStatisticsAPIs();
await testFeedInteractionAPIs();
print('\n╔══════════════════════════════════════════╗');
print('║ 测试结果汇总 ║');
print('╚══════════════════════════════════════════╝');
print('✅ 通过: $passCount');
print('❌ 失败: $failCount');
print('📊 总计: ${passCount + failCount}');
print('📈 通过率: ${((passCount / (passCount + failCount)) * 100).toStringAsFixed(1)}%');
}