本次更新涵盖多个功能模块的优化与新增: 1. 新增Wi-Fi直连配对方式与协作画布模块 2. 完成设备管理重命名API与前端适配 3. 优化日签卡片空数据保护与UI细节 4. 新增剪贴板工具与每日运势会话 5. 修复应用锁恢复、语音消息录制等已知问题 6. 完善文件传输统计与配对逻辑 7. 更新安卓权限配置与iOS隐私描述 8. 新增自动化测试脚本与文档 9. 清理旧版审计报告与测试文件
357 lines
11 KiB
Dart
357 lines
11 KiB
Dart
// ============================================================
|
||
// 闲言APP — 签到接口测试脚本
|
||
// 创建时间: 2026-05-14
|
||
// 更新时间: 2026-05-14
|
||
// 作用: 测试签到系统HTTP接口
|
||
// - 签到日历数据获取
|
||
// - 签到状态验证
|
||
// - 补签接口测试
|
||
// - API基础URL: https://tools.wktyl.com
|
||
// 上次更新: 初始版本
|
||
// 运行: dart run Scripts/signin_api_test.dart
|
||
// ============================================================
|
||
|
||
import 'dart:convert';
|
||
import 'dart:io';
|
||
|
||
const String kApiBase = 'https://tools.wktyl.com';
|
||
const String kSigninBase = '/api/user_center';
|
||
const Duration kTimeout = Duration(seconds: 15);
|
||
|
||
int _passCount = 0;
|
||
int _failCount = 0;
|
||
|
||
void _result(String name, bool pass, {String? detail}) {
|
||
final icon = pass ? '✅' : '❌';
|
||
final status = pass ? 'PASS' : 'FAIL';
|
||
_passCount += pass ? 1 : 0;
|
||
_failCount += pass ? 0 : 1;
|
||
print('$icon [$status] $name${detail != null ? ' — $detail' : ''}');
|
||
}
|
||
|
||
Future<Map<String, dynamic>?> _httpGet(
|
||
String path, {
|
||
Map<String, dynamic>? queryParameters,
|
||
String? token,
|
||
}) async {
|
||
try {
|
||
final uri = Uri.parse('$kApiBase$path').replace(
|
||
queryParameters: queryParameters?.map(
|
||
(k, v) => MapEntry(k, v.toString()),
|
||
),
|
||
);
|
||
final client = HttpClient();
|
||
client.connectionTimeout = kTimeout;
|
||
final request = await client.getUrl(uri);
|
||
request.headers.set('Accept', 'application/json');
|
||
request.headers.set('Content-Type', 'application/x-www-form-urlencoded');
|
||
if (token != null && token.isNotEmpty) {
|
||
request.headers.set('Authorization', 'Bearer $token');
|
||
}
|
||
final response = await request.close();
|
||
final body = await response.transform(utf8.decoder).join();
|
||
client.close();
|
||
return jsonDecode(body) as Map<String, dynamic>;
|
||
} catch (e) {
|
||
print(' ⚠️ HTTP GET $path 失败: $e');
|
||
return null;
|
||
}
|
||
}
|
||
|
||
Future<Map<String, dynamic>?> _httpPost(
|
||
String path, {
|
||
Map<String, dynamic>? data,
|
||
String? token,
|
||
}) async {
|
||
try {
|
||
final uri = Uri.parse('$kApiBase$path');
|
||
final client = HttpClient();
|
||
client.connectionTimeout = kTimeout;
|
||
final request = await client.postUrl(uri);
|
||
request.headers.set('Accept', 'application/json');
|
||
request.headers.set('Content-Type', 'application/x-www-form-urlencoded');
|
||
if (token != null && token.isNotEmpty) {
|
||
request.headers.set('Authorization', 'Bearer $token');
|
||
}
|
||
if (data != null && data.isNotEmpty) {
|
||
final formData = data.entries
|
||
.map((e) =>
|
||
'${Uri.encodeComponent(e.key)}=${Uri.encodeComponent(e.value.toString())}')
|
||
.join('&');
|
||
request.write(formData);
|
||
}
|
||
final response = await request.close();
|
||
final body = await response.transform(utf8.decoder).join();
|
||
client.close();
|
||
return jsonDecode(body) as Map<String, dynamic>;
|
||
} catch (e) {
|
||
print(' ⚠️ HTTP POST $path 失败: $e');
|
||
return null;
|
||
}
|
||
}
|
||
|
||
String? _testToken;
|
||
|
||
Future<void> main(List<String> args) async {
|
||
print('╔══════════════════════════════════════════════════════════╗');
|
||
print('║ 闲言APP — 签到接口测试 ║');
|
||
print('║ API Base: $kApiBase ║');
|
||
print('╚══════════════════════════════════════════════════════════╝\n');
|
||
|
||
if (args.isNotEmpty) {
|
||
_testToken = args[0];
|
||
print('🔑 使用提供的Token: ${_testToken!.substring(0, 10)}...\n');
|
||
} else {
|
||
print('⚠️ 未提供Token, 部分需要登录的接口可能返回401');
|
||
print(' 用法: dart run Scripts/signin_api_test.dart <token>\n');
|
||
}
|
||
|
||
await testSigninCalendar();
|
||
await testSigninStatus();
|
||
await testSigninAction();
|
||
await testSigninMakeup();
|
||
|
||
print('\n╔══════════════════════════════════════════════════════════╗');
|
||
print('║ 测试结果汇总 ║');
|
||
print('╠══════════════════════════════════════════════════════════╣');
|
||
print('║ ✅ 通过: $_passCount ║');
|
||
print('║ ❌ 失败: $_failCount ║');
|
||
print('║ 📊 总计: ${_passCount + _failCount} ║');
|
||
print('╚══════════════════════════════════════════════════════════╝');
|
||
|
||
exit(_failCount > 0 ? 1 : 0);
|
||
}
|
||
|
||
Future<void> testSigninCalendar() async {
|
||
print('\n━━━ 1. 签到日历数据获取 ━━━');
|
||
|
||
final result = await _httpGet(
|
||
'$kSigninBase/signin_calendar',
|
||
token: _testToken,
|
||
);
|
||
|
||
_result(
|
||
'签到日历接口响应',
|
||
result != null,
|
||
detail: result != null ? 'code=${result['code']}' : '请求失败',
|
||
);
|
||
|
||
if (result != null) {
|
||
final code = result['code'] as int? ?? 0;
|
||
_result(
|
||
'接口返回成功(code=1)',
|
||
code == 1,
|
||
detail: 'code=$code, msg=${result['msg']}',
|
||
);
|
||
|
||
if (code == 1) {
|
||
final data = result['data'] as Map<String, dynamic>? ?? {};
|
||
_result(
|
||
'日历数据非空',
|
||
data.isNotEmpty,
|
||
detail: 'keys=${data.keys.join(', ')}',
|
||
);
|
||
|
||
final calendar = data['calendar'];
|
||
_result(
|
||
'包含calendar字段',
|
||
calendar != null,
|
||
detail: calendar != null
|
||
? '类型=${calendar.runtimeType}, 数据摘要=${_truncate(calendar.toString(), 120)}'
|
||
: '缺失calendar字段',
|
||
);
|
||
|
||
final continuous = data['current_continuous'] as int?;
|
||
_result(
|
||
'包含连续签到天数',
|
||
continuous != null,
|
||
detail: 'current_continuous=$continuous',
|
||
);
|
||
|
||
final totalSignins = data['total_signins'] as int?;
|
||
_result(
|
||
'包含累计签到天数',
|
||
totalSignins != null,
|
||
detail: 'total_signins=$totalSignins',
|
||
);
|
||
}
|
||
}
|
||
|
||
final now = DateTime.now();
|
||
final monthStr =
|
||
'${now.year}-${now.month.toString().padLeft(2, '0')}';
|
||
final resultWithMonth = await _httpGet(
|
||
'$kSigninBase/signin_calendar',
|
||
queryParameters: {'month': monthStr},
|
||
token: _testToken,
|
||
);
|
||
|
||
_result(
|
||
'指定月份查询日历',
|
||
resultWithMonth != null,
|
||
detail: resultWithMonth != null
|
||
? 'month=$monthStr, code=${resultWithMonth['code']}'
|
||
: '请求失败',
|
||
);
|
||
}
|
||
|
||
Future<void> testSigninStatus() async {
|
||
print('\n━━━ 2. 签到状态验证 ━━━');
|
||
|
||
final calendarResult = await _httpGet(
|
||
'$kSigninBase/signin_calendar',
|
||
token: _testToken,
|
||
);
|
||
|
||
if (calendarResult == null) {
|
||
_result('签到状态验证(依赖日历接口)', false, detail: '日历接口请求失败');
|
||
return;
|
||
}
|
||
|
||
final code = calendarResult['code'] as int? ?? 0;
|
||
if (code != 1) {
|
||
_result(
|
||
'签到状态验证',
|
||
false,
|
||
detail: '日历接口返回code=$code, msg=${calendarResult['msg']}',
|
||
);
|
||
return;
|
||
}
|
||
|
||
final data = calendarResult['data'] as Map<String, dynamic>? ?? {};
|
||
final calendar = data['calendar'];
|
||
final now = DateTime.now();
|
||
final todayStr =
|
||
'${now.year}-${now.month.toString().padLeft(2, '0')}-${now.day.toString().padLeft(2, '0')}';
|
||
|
||
bool todaySigned = false;
|
||
|
||
if (calendar is Map<String, dynamic>) {
|
||
todaySigned = calendar[todayStr] == true ||
|
||
calendar[todayStr] == 1;
|
||
} else if (calendar is List) {
|
||
for (final item in calendar) {
|
||
if (item is Map<String, dynamic>) {
|
||
final date = item['date']?.toString() ?? '';
|
||
final signed = item['signed'] == true || item['signed'] == 1;
|
||
if (date == todayStr && signed) {
|
||
todaySigned = true;
|
||
break;
|
||
}
|
||
} else if (item?.toString() == todayStr) {
|
||
todaySigned = true;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
_result(
|
||
'今日签到状态可解析',
|
||
true,
|
||
detail: 'today=$todayStr, todaySigned=$todaySigned',
|
||
);
|
||
|
||
final continuous = data['current_continuous'] as int? ?? 0;
|
||
_result(
|
||
'连续签到天数有效',
|
||
continuous >= 0,
|
||
detail: 'current_continuous=$continuous',
|
||
);
|
||
}
|
||
|
||
Future<void> testSigninAction() async {
|
||
print('\n━━━ 3. 签到接口测试 ━━━');
|
||
|
||
if (_testToken == null) {
|
||
_result('签到接口(需登录)', false, detail: '未提供Token, 跳过');
|
||
return;
|
||
}
|
||
|
||
final result = await _httpPost(
|
||
'$kSigninBase/signin',
|
||
token: _testToken,
|
||
);
|
||
|
||
_result(
|
||
'签到接口响应',
|
||
result != null,
|
||
detail: result != null ? 'code=${result['code']}' : '请求失败',
|
||
);
|
||
|
||
if (result != null) {
|
||
final code = result['code'] as int? ?? 0;
|
||
final msg = result['msg'] as String? ?? '';
|
||
|
||
if (code == 1) {
|
||
final data = result['data'] as Map<String, dynamic>? ?? {};
|
||
final continuous = data['continuous'] as int? ?? 0;
|
||
final coinReward = data['coin_reward'] as int? ?? 0;
|
||
final todaySigned = data['today_signed'] as bool? ?? true;
|
||
|
||
_result('签到成功', true, detail: '连续$continuous天, +$coinReward积分');
|
||
_result(
|
||
'返回today_signed',
|
||
data.containsKey('today_signed'),
|
||
detail: 'today_signed=$todaySigned',
|
||
);
|
||
_result(
|
||
'返回coin_reward',
|
||
data.containsKey('coin_reward'),
|
||
detail: 'coin_reward=$coinReward',
|
||
);
|
||
} else if (msg.contains('已签到')) {
|
||
_result('今日已签到', true, detail: 'msg=$msg');
|
||
} else {
|
||
_result('签到接口返回', false, detail: 'code=$code, msg=$msg');
|
||
}
|
||
}
|
||
}
|
||
|
||
Future<void> testSigninMakeup() async {
|
||
print('\n━━━ 4. 补签接口测试 ━━━');
|
||
|
||
if (_testToken == null) {
|
||
_result('补签接口(需登录)', false, detail: '未提供Token, 跳过');
|
||
return;
|
||
}
|
||
|
||
final now = DateTime.now();
|
||
final yesterday = now.subtract(const Duration(days: 1));
|
||
final yesterdayStr =
|
||
'${yesterday.year}-${yesterday.month.toString().padLeft(2, '0')}-${yesterday.day.toString().padLeft(2, '0')}';
|
||
|
||
print(' ℹ️ 尝试补签日期: $yesterdayStr (注意: 如已签到或积分不足会失败)');
|
||
|
||
final result = await _httpPost(
|
||
'$kSigninBase/signin_makeup',
|
||
data: {'date': yesterdayStr},
|
||
token: _testToken,
|
||
);
|
||
|
||
_result(
|
||
'补签接口响应',
|
||
result != null,
|
||
detail: result != null ? 'code=${result['code']}' : '请求失败',
|
||
);
|
||
|
||
if (result != null) {
|
||
final code = result['code'] as int? ?? 0;
|
||
final msg = result['msg'] as String? ?? '';
|
||
|
||
if (code == 1) {
|
||
_result('补签成功', true, detail: 'date=$yesterdayStr');
|
||
} else {
|
||
_result(
|
||
'补签接口返回',
|
||
false,
|
||
detail: 'code=$code, msg=$msg (可能已签到/积分不足/每日限1次)',
|
||
);
|
||
}
|
||
}
|
||
}
|
||
|
||
String _truncate(String s, int maxLen) {
|
||
if (s.length <= maxLen) return s;
|
||
return '${s.substring(0, maxLen)}...';
|
||
}
|