本次更新涵盖多个功能模块的优化与新增: 1. 新增Wi-Fi直连配对方式与协作画布模块 2. 完成设备管理重命名API与前端适配 3. 优化日签卡片空数据保护与UI细节 4. 新增剪贴板工具与每日运势会话 5. 修复应用锁恢复、语音消息录制等已知问题 6. 完善文件传输统计与配对逻辑 7. 更新安卓权限配置与iOS隐私描述 8. 新增自动化测试脚本与文档 9. 清理旧版审计报告与测试文件
543 lines
17 KiB
Dart
543 lines
17 KiB
Dart
// ============================================================
|
|
// 闲言APP — 收藏同步接口测试脚本
|
|
// 创建时间: 2026-05-14
|
|
// 更新时间: 2026-05-14
|
|
// 作用: 测试收藏系统HTTP接口
|
|
// - 收藏列表获取 (Feed API + UserCenter API)
|
|
// - 收藏添加/删除
|
|
// - Feed API收藏操作
|
|
// - 同步逻辑验证
|
|
// - API基础URL: https://tools.wktyl.com
|
|
// 上次更新: 初始版本
|
|
// 运行: dart run Scripts/favorite_sync_test.dart [token]
|
|
// ============================================================
|
|
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
const String kApiBase = 'https://tools.wktyl.com';
|
|
const String kUserCenterBase = '/api/user_center';
|
|
const String kFeedBase = '/api/feed';
|
|
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/favorite_sync_test.dart <token>\n');
|
|
}
|
|
|
|
await testFeedFavoriteList();
|
|
await testUserCenterFavoriteList();
|
|
await testFavoriteAddAndRemove();
|
|
await testFeedActionFavorite();
|
|
await testFavoriteSyncLogic();
|
|
|
|
print('\n╔══════════════════════════════════════════════════════════╗');
|
|
print('║ 测试结果汇总 ║');
|
|
print('╠══════════════════════════════════════════════════════════╣');
|
|
print('║ ✅ 通过: $_passCount ║');
|
|
print('║ ❌ 失败: $_failCount ║');
|
|
print('║ 📊 总计: ${_passCount + _failCount} ║');
|
|
print('╚══════════════════════════════════════════════════════════╝');
|
|
|
|
exit(_failCount > 0 ? 1 : 0);
|
|
}
|
|
|
|
Future<void> testFeedFavoriteList() async {
|
|
print('\n━━━ 1. Feed API 收藏列表获取 ━━━');
|
|
|
|
final result = await _httpGet(
|
|
'$kFeedBase/favorites',
|
|
queryParameters: {'page': '1', 'limit': '10'},
|
|
token: _testToken,
|
|
);
|
|
|
|
_result(
|
|
'Feed收藏列表接口响应',
|
|
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>? ?? {};
|
|
final list = data['list'] as List<dynamic>? ?? [];
|
|
final total = data['total'] as int? ?? 0;
|
|
final page = data['page'] as int? ?? 1;
|
|
|
|
_result(
|
|
'收藏列表非空',
|
|
list.isNotEmpty || total == 0,
|
|
detail: 'total=$total, page=$page, list.length=${list.length}',
|
|
);
|
|
|
|
if (list.isNotEmpty) {
|
|
final firstItem = list.first as Map<String, dynamic>;
|
|
final hasRequiredFields = firstItem.containsKey('id') &&
|
|
firstItem.containsKey('feed_type') &&
|
|
firstItem.containsKey('content');
|
|
_result(
|
|
'收藏项包含必要字段(id/feed_type/content)',
|
|
hasRequiredFields,
|
|
detail: 'keys=${firstItem.keys.join(', ')}',
|
|
);
|
|
|
|
final isFavorited = firstItem['is_favorited'] as bool? ?? false;
|
|
_result(
|
|
'收藏项标记为已收藏',
|
|
isFavorited,
|
|
detail: 'is_favorited=$isFavorited',
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<void> testUserCenterFavoriteList() async {
|
|
print('\n━━━ 2. UserCenter API 收藏列表获取 ━━━');
|
|
|
|
final result = await _httpPost(
|
|
'$kUserCenterBase/favorite',
|
|
data: {
|
|
'action': 'list',
|
|
'target_type': 'article',
|
|
'page': '1',
|
|
'limit': '10',
|
|
},
|
|
token: _testToken,
|
|
);
|
|
|
|
_result(
|
|
'UserCenter收藏列表接口响应',
|
|
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>? ?? {};
|
|
final list = data['list'] as List<dynamic>? ?? [];
|
|
final total = data['total'] as int? ?? 0;
|
|
|
|
_result(
|
|
'UserCenter收藏列表数据',
|
|
true,
|
|
detail: 'total=$total, list.length=${list.length}',
|
|
);
|
|
|
|
if (list.isNotEmpty) {
|
|
final firstItem = list.first as Map<String, dynamic>;
|
|
final hasRequiredFields = firstItem.containsKey('id') &&
|
|
firstItem.containsKey('target_type') &&
|
|
firstItem.containsKey('target_id');
|
|
_result(
|
|
'收藏项包含必要字段(id/target_type/target_id)',
|
|
hasRequiredFields,
|
|
detail: 'keys=${firstItem.keys.join(', ')}',
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<void> testFavoriteAddAndRemove() async {
|
|
print('\n━━━ 3. 收藏添加/删除测试 ━━━');
|
|
|
|
if (_testToken == null) {
|
|
_result('收藏添加/删除(需登录)', false, detail: '未提供Token, 跳过');
|
|
return;
|
|
}
|
|
|
|
final feedListResult = await _httpGet(
|
|
'$kFeedBase/list',
|
|
queryParameters: {'channel': 'all', 'limit': '5'},
|
|
);
|
|
|
|
int? testFeedId;
|
|
String? testFeedType;
|
|
|
|
if (feedListResult != null && feedListResult['code'] == 1) {
|
|
final data = feedListResult['data'] as Map<String, dynamic>? ?? {};
|
|
final list = data['list'] as List<dynamic>? ?? [];
|
|
if (list.isNotEmpty) {
|
|
final firstItem = list.first as Map<String, dynamic>;
|
|
testFeedId = firstItem['id'] as int?;
|
|
testFeedType = firstItem['feed_type'] as String? ?? 'feed';
|
|
}
|
|
}
|
|
|
|
if (testFeedId == null) {
|
|
_result('获取测试用Feed项', false, detail: '无法获取Feed列表中的项目');
|
|
return;
|
|
}
|
|
|
|
_result('获取测试用Feed项', true, detail: 'id=$testFeedId, type=$testFeedType');
|
|
|
|
final checkResult = await _httpPost(
|
|
'$kUserCenterBase/favorite',
|
|
data: {
|
|
'action': 'check',
|
|
'target_type': testFeedType,
|
|
'target_id': '$testFeedId',
|
|
},
|
|
token: _testToken,
|
|
);
|
|
|
|
bool wasFavorited = false;
|
|
if (checkResult != null && checkResult['code'] == 1) {
|
|
final data = checkResult['data'] as Map<String, dynamic>? ?? {};
|
|
wasFavorited = data['is_favorited'] as bool? ?? false;
|
|
_result('收藏状态检查', true, detail: 'is_favorited=$wasFavorited');
|
|
} else {
|
|
_result(
|
|
'收藏状态检查',
|
|
false,
|
|
detail: '接口返回code=${checkResult?['code']}',
|
|
);
|
|
}
|
|
|
|
final addAction = wasFavorited ? 'remove' : 'add';
|
|
final addResult = await _httpPost(
|
|
'$kUserCenterBase/favorite',
|
|
data: {
|
|
'action': addAction,
|
|
'target_type': testFeedType,
|
|
'target_id': '$testFeedId',
|
|
},
|
|
token: _testToken,
|
|
);
|
|
|
|
_result(
|
|
'收藏${addAction == 'add' ? '添加' : '移除'}操作',
|
|
addResult != null && addResult['code'] == 1,
|
|
detail: addResult != null
|
|
? 'code=${addResult['code']}, msg=${addResult['msg']}'
|
|
: '请求失败',
|
|
);
|
|
|
|
final verifyResult = await _httpPost(
|
|
'$kUserCenterBase/favorite',
|
|
data: {
|
|
'action': 'check',
|
|
'target_type': testFeedType,
|
|
'target_id': '$testFeedId',
|
|
},
|
|
token: _testToken,
|
|
);
|
|
|
|
if (verifyResult != null && verifyResult['code'] == 1) {
|
|
final data = verifyResult['data'] as Map<String, dynamic>? ?? {};
|
|
final nowFavorited = data['is_favorited'] as bool? ?? false;
|
|
final expectedFavorited = !wasFavorited;
|
|
_result(
|
|
'收藏状态已变更',
|
|
nowFavorited == expectedFavorited,
|
|
detail: '操作前=$wasFavorited, 操作后=$nowFavorited, 期望=$expectedFavorited',
|
|
);
|
|
}
|
|
|
|
final restoreAction = wasFavorited ? 'add' : 'remove';
|
|
final restoreResult = await _httpPost(
|
|
'$kUserCenterBase/favorite',
|
|
data: {
|
|
'action': restoreAction,
|
|
'target_type': testFeedType,
|
|
'target_id': '$testFeedId',
|
|
},
|
|
token: _testToken,
|
|
);
|
|
|
|
_result(
|
|
'恢复原始收藏状态',
|
|
restoreResult != null && restoreResult['code'] == 1,
|
|
detail: '已恢复为${wasFavorited ? "已收藏" : "未收藏"}',
|
|
);
|
|
}
|
|
|
|
Future<void> testFeedActionFavorite() async {
|
|
print('\n━━━ 4. Feed API 收藏操作测试 ━━━');
|
|
|
|
if (_testToken == null) {
|
|
_result('Feed API收藏操作(需登录)', false, detail: '未提供Token, 跳过');
|
|
return;
|
|
}
|
|
|
|
final feedListResult = await _httpGet(
|
|
'$kFeedBase/list',
|
|
queryParameters: {'channel': 'all', 'limit': '3'},
|
|
);
|
|
|
|
int? testFeedId;
|
|
String? testFeedType;
|
|
|
|
if (feedListResult != null && feedListResult['code'] == 1) {
|
|
final data = feedListResult['data'] as Map<String, dynamic>? ?? {};
|
|
final list = data['list'] as List<dynamic>? ?? [];
|
|
if (list.isNotEmpty) {
|
|
final item = list.last as Map<String, dynamic>;
|
|
testFeedId = item['id'] as int?;
|
|
testFeedType = item['feed_type'] as String? ?? 'feed';
|
|
}
|
|
}
|
|
|
|
if (testFeedId == null) {
|
|
_result('获取测试用Feed项', false, detail: '无法获取Feed列表中的项目');
|
|
return;
|
|
}
|
|
|
|
_result('获取测试用Feed项', true, detail: 'id=$testFeedId, type=$testFeedType');
|
|
|
|
final favoriteResult = await _httpPost(
|
|
'$kFeedBase/action',
|
|
data: {
|
|
'action': 'favorite',
|
|
'feed_type': testFeedType,
|
|
'feed_id': '$testFeedId',
|
|
},
|
|
token: _testToken,
|
|
);
|
|
|
|
_result(
|
|
'Feed API收藏操作',
|
|
favoriteResult != null && favoriteResult['code'] == 1,
|
|
detail: favoriteResult != null
|
|
? 'code=${favoriteResult['code']}, msg=${favoriteResult['msg']}'
|
|
: '请求失败',
|
|
);
|
|
|
|
await Future<void>.delayed(const Duration(seconds: 1));
|
|
|
|
final unfavoriteResult = await _httpPost(
|
|
'$kFeedBase/action',
|
|
data: {
|
|
'action': 'unfavorite',
|
|
'feed_type': testFeedType,
|
|
'feed_id': '$testFeedId',
|
|
},
|
|
token: _testToken,
|
|
);
|
|
|
|
_result(
|
|
'Feed API取消收藏操作',
|
|
unfavoriteResult != null && unfavoriteResult['code'] == 1,
|
|
detail: unfavoriteResult != null
|
|
? 'code=${unfavoriteResult['code']}, msg=${unfavoriteResult['msg']}'
|
|
: '请求失败',
|
|
);
|
|
}
|
|
|
|
Future<void> testFavoriteSyncLogic() async {
|
|
print('\n━━━ 5. 同步逻辑验证 ━━━');
|
|
|
|
if (_testToken == null) {
|
|
_result('同步逻辑验证(需登录)', false, detail: '未提供Token, 跳过');
|
|
return;
|
|
}
|
|
|
|
final feedFavResult = await _httpGet(
|
|
'$kFeedBase/favorites',
|
|
queryParameters: {'page': '1', 'limit': '20'},
|
|
token: _testToken,
|
|
);
|
|
|
|
int feedFavCount = 0;
|
|
List<int> feedFavIds = [];
|
|
|
|
if (feedFavResult != null && feedFavResult['code'] == 1) {
|
|
final data = feedFavResult['data'] as Map<String, dynamic>? ?? {};
|
|
final list = data['list'] as List<dynamic>? ?? [];
|
|
feedFavCount = data['total'] as int? ?? list.length;
|
|
feedFavIds = list
|
|
.map<int>((e) => (e as Map<String, dynamic>)['id'] as int? ?? 0)
|
|
.where((id) => id > 0)
|
|
.toList();
|
|
}
|
|
|
|
_result(
|
|
'Feed API收藏列表可获取',
|
|
feedFavResult != null,
|
|
detail: 'total=$feedFavCount, 本页${feedFavIds.length}条',
|
|
);
|
|
|
|
final ucFavResult = await _httpPost(
|
|
'$kUserCenterBase/favorite',
|
|
data: {'action': 'list', 'target_type': 'article', 'page': '1', 'limit': '20'},
|
|
token: _testToken,
|
|
);
|
|
|
|
int ucFavCount = 0;
|
|
List<int> ucFavIds = [];
|
|
|
|
if (ucFavResult != null && ucFavResult['code'] == 1) {
|
|
final data = ucFavResult['data'] as Map<String, dynamic>? ?? {};
|
|
final list = data['list'] as List<dynamic>? ?? [];
|
|
ucFavCount = data['total'] as int? ?? list.length;
|
|
ucFavIds = list
|
|
.map<int>(
|
|
(e) => (e as Map<String, dynamic>)['target_id'] as int? ?? 0)
|
|
.where((id) => id > 0)
|
|
.toList();
|
|
}
|
|
|
|
_result(
|
|
'UserCenter API收藏列表可获取',
|
|
ucFavResult != null,
|
|
detail: 'total=$ucFavCount, 本页${ucFavIds.length}条',
|
|
);
|
|
|
|
final countResult = await _httpPost(
|
|
'$kUserCenterBase/favorite',
|
|
data: {'action': 'count'},
|
|
token: _testToken,
|
|
);
|
|
|
|
if (countResult != null && countResult['code'] == 1) {
|
|
final data = countResult['data'] as Map<String, dynamic>? ?? {};
|
|
final counts = data['counts'] as Map<String, dynamic>? ?? {};
|
|
_result(
|
|
'收藏统计接口可用',
|
|
true,
|
|
detail: 'counts=$counts',
|
|
);
|
|
} else {
|
|
_result(
|
|
'收藏统计接口',
|
|
false,
|
|
detail: 'code=${countResult?['code']}, msg=${countResult?['msg']}',
|
|
);
|
|
}
|
|
|
|
final groupsResult = await _httpPost(
|
|
'$kUserCenterBase/favorite',
|
|
data: {'action': 'groups'},
|
|
token: _testToken,
|
|
);
|
|
|
|
if (groupsResult != null && groupsResult['code'] == 1) {
|
|
final data = groupsResult['data'] as Map<String, dynamic>? ?? {};
|
|
final groups = data['groups'] as List<dynamic>? ?? [];
|
|
_result(
|
|
'收藏分组接口可用',
|
|
true,
|
|
detail: 'groups=${groups.join(', ')}',
|
|
);
|
|
} else {
|
|
_result(
|
|
'收藏分组接口',
|
|
false,
|
|
detail: 'code=${groupsResult?['code']}, msg=${groupsResult?['msg']}',
|
|
);
|
|
}
|
|
|
|
if (feedFavIds.isNotEmpty && ucFavIds.isNotEmpty) {
|
|
final overlapIds = feedFavIds.toSet().intersection(ucFavIds.toSet());
|
|
_result(
|
|
'两个API收藏数据有交集(同步一致性)',
|
|
overlapIds.isNotEmpty,
|
|
detail: 'Feed API ${feedFavIds.length}条, UserCenter API ${ucFavIds.length}条, 交集${overlapIds.length}条',
|
|
);
|
|
} else {
|
|
_result(
|
|
'同步一致性验证',
|
|
true,
|
|
detail: '至少一个API收藏列表为空, 无法比较交集',
|
|
);
|
|
}
|
|
}
|