本次提交包含大量代码优化、功能新增与服务端配置更新: 1. 修复分析报告统计数据,调整CMake策略设置 2. 优化APP权限配置、编辑器与聊天界面组件 3. 更新依赖库版本与pubspec配置 4. 新增文件传输服务端、信令服务器相关配置与脚本 5. 完善用户注销功能与数据库迁移脚本 6. 优化多处动画效果、代码风格与日志输出 7. 新增多种调试与部署脚本,修复已知BUG
320 lines
13 KiB
Dart
320 lines
13 KiB
Dart
// ============================================================
|
||
// 闲言APP — LocalSend传输接口验证脚本
|
||
// 创建时间: 2026-05-11
|
||
// 更新时间: 2026-05-11
|
||
// 作用: 验证LocalSend HTTP服务器端点+HTTPS→HTTP降级+信令服务连接
|
||
// 上次更新: 初始创建
|
||
// ============================================================
|
||
|
||
import 'dart:async';
|
||
import 'dart:convert';
|
||
import 'dart:io';
|
||
|
||
import 'package:dio/dio.dart';
|
||
import 'package:dio/io.dart';
|
||
|
||
void main(List<String> args) async {
|
||
final targetIp = args.isNotEmpty ? args[0] : '10.0.0.7';
|
||
final targetPort = args.length > 1 ? int.parse(args[1]) : 53317;
|
||
|
||
print('╔══════════════════════════════════════════════════╗');
|
||
print('║ LocalSend 传输接口验证脚本 v1.0 ║');
|
||
print('╚══════════════════════════════════════════════════╝');
|
||
print('');
|
||
print('目标设备: $targetIp:$targetPort');
|
||
print('');
|
||
|
||
final dio = Dio(
|
||
BaseOptions(
|
||
connectTimeout: const Duration(seconds: 5),
|
||
sendTimeout: const Duration(seconds: 5),
|
||
receiveTimeout: const Duration(seconds: 5),
|
||
validateStatus: (status) => status != null && status < 600,
|
||
),
|
||
);
|
||
|
||
(dio.httpClientAdapter as IOHttpClientAdapter).createHttpClient = () {
|
||
final client = HttpClient();
|
||
client.badCertificateCallback = (cert, host, port) => true;
|
||
return client;
|
||
};
|
||
|
||
final results = <String, bool>{};
|
||
|
||
// ─── 1. 测试 HTTPS /api/localsend/v2/info ────────────────
|
||
print('━━━ 1. HTTPS v2/info ─━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||
try {
|
||
final resp = await dio.get(
|
||
'https://$targetIp:$targetPort/api/localsend/v2/info',
|
||
);
|
||
print(' ✅ HTTPS v2/info: ${resp.statusCode}');
|
||
print(' 📦 ${jsonEncode(resp.data)}');
|
||
results['https_v2_info'] = resp.statusCode == 200;
|
||
} on DioException catch (e) {
|
||
print(' ❌ HTTPS v2/info 失败: ${e.type} ${e.message}');
|
||
results['https_v2_info'] = false;
|
||
}
|
||
|
||
// ─── 2. 测试 HTTP /api/localsend/v2/info ────────────────
|
||
print('');
|
||
print('━━━ 2. HTTP v2/info ─━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||
try {
|
||
final resp = await dio.get(
|
||
'http://$targetIp:$targetPort/api/localsend/v2/info',
|
||
);
|
||
print(' ✅ HTTP v2/info: ${resp.statusCode}');
|
||
print(' 📦 ${jsonEncode(resp.data)}');
|
||
results['http_v2_info'] = resp.statusCode == 200;
|
||
} on DioException catch (e) {
|
||
print(' ❌ HTTP v2/info 失败: ${e.type} ${e.message}');
|
||
results['http_v2_info'] = false;
|
||
}
|
||
|
||
// ─── 3. 测试 HTTPS /api/localsend/v1/info ────────────────
|
||
print('');
|
||
print('━━━ 3. HTTPS v1/info ─━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||
try {
|
||
final resp = await dio.get(
|
||
'https://$targetIp:$targetPort/api/localsend/v1/info',
|
||
);
|
||
print(' ✅ HTTPS v1/info: ${resp.statusCode}');
|
||
print(' 📦 ${jsonEncode(resp.data)}');
|
||
results['https_v1_info'] = resp.statusCode == 200;
|
||
} on DioException catch (e) {
|
||
print(' ❌ HTTPS v1/info 失败: ${e.type} ${e.message}');
|
||
results['https_v1_info'] = false;
|
||
}
|
||
|
||
// ─── 4. 测试 HTTP /api/localsend/v1/info ────────────────
|
||
print('');
|
||
print('━━━ 4. HTTP v1/info ─━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||
try {
|
||
final resp = await dio.get(
|
||
'http://$targetIp:$targetPort/api/localsend/v1/info',
|
||
);
|
||
print(' ✅ HTTP v1/info: ${resp.statusCode}');
|
||
print(' 📦 ${jsonEncode(resp.data)}');
|
||
results['http_v1_info'] = resp.statusCode == 200;
|
||
} on DioException catch (e) {
|
||
print(' ❌ HTTP v1/info 失败: ${e.type} ${e.message}');
|
||
results['http_v1_info'] = false;
|
||
}
|
||
|
||
// ─── 5. 测试 HTTPS 文本消息 ──────────────────────────────
|
||
print('');
|
||
print('━━━ 5. HTTPS v2/message ─━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||
try {
|
||
final resp = await dio.post(
|
||
'https://$targetIp:$targetPort/api/localsend/v2/message',
|
||
data: {
|
||
'senderAlias': '验证脚本',
|
||
'senderFingerprint': 'test-script',
|
||
'text': '🔍 接口验证测试消息',
|
||
'sessionId': 'verify-${DateTime.now().millisecondsSinceEpoch}',
|
||
},
|
||
options: Options(contentType: Headers.jsonContentType),
|
||
);
|
||
print(' ✅ HTTPS v2/message: ${resp.statusCode}');
|
||
print(' 📦 ${jsonEncode(resp.data)}');
|
||
results['https_v2_message'] = resp.statusCode == 200;
|
||
} on DioException catch (e) {
|
||
print(' ❌ HTTPS v2/message 失败: ${e.type} ${e.message}');
|
||
results['https_v2_message'] = false;
|
||
}
|
||
|
||
// ─── 6. 测试 HTTP 文本消息 ──────────────────────────────
|
||
print('');
|
||
print('━━━ 6. HTTP v2/message ─━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||
try {
|
||
final resp = await dio.post(
|
||
'http://$targetIp:$targetPort/api/localsend/v2/message',
|
||
data: {
|
||
'senderAlias': '验证脚本',
|
||
'senderFingerprint': 'test-script',
|
||
'text': '🔍 HTTP接口验证测试消息',
|
||
'sessionId': 'verify-http-${DateTime.now().millisecondsSinceEpoch}',
|
||
},
|
||
options: Options(contentType: Headers.jsonContentType),
|
||
);
|
||
print(' ✅ HTTP v2/message: ${resp.statusCode}');
|
||
print(' 📦 ${jsonEncode(resp.data)}');
|
||
results['http_v2_message'] = resp.statusCode == 200;
|
||
} on DioException catch (e) {
|
||
print(' ❌ HTTP v2/message 失败: ${e.type} ${e.message}');
|
||
results['http_v2_message'] = false;
|
||
}
|
||
|
||
// ─── 7. 测试 HTTPS prepare-upload ────────────────────────
|
||
print('');
|
||
print('━━━ 7. HTTPS v2/prepare-upload ─━━━━━━━━━━━━━━━━━━━');
|
||
try {
|
||
final resp = await dio.post(
|
||
'https://$targetIp:$targetPort/api/localsend/v2/prepare-upload',
|
||
data: {
|
||
'senderInfo': {
|
||
'alias': '验证脚本',
|
||
'fingerprint': 'test-script',
|
||
'deviceModel': 'Script',
|
||
'deviceType': 'desktop',
|
||
},
|
||
'files': {
|
||
'file-0': {
|
||
'id': 'file-0',
|
||
'fileName': 'test.txt',
|
||
'size': 13,
|
||
'sha256':
|
||
'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855',
|
||
},
|
||
},
|
||
},
|
||
options: Options(contentType: Headers.jsonContentType),
|
||
);
|
||
print(' ✅ HTTPS v2/prepare-upload: ${resp.statusCode}');
|
||
print(' 📦 ${jsonEncode(resp.data)}');
|
||
results['https_v2_prepare'] = resp.statusCode == 200;
|
||
} on DioException catch (e) {
|
||
print(' ❌ HTTPS v2/prepare-upload 失败: ${e.type} ${e.message}');
|
||
results['https_v2_prepare'] = false;
|
||
}
|
||
|
||
// ─── 8. 测试 HTTP prepare-upload ────────────────────────
|
||
print('');
|
||
print('━━━ 8. HTTP v2/prepare-upload ─━━━━━━━━━━━━━━━━━━━━');
|
||
try {
|
||
final resp = await dio.post(
|
||
'http://$targetIp:$targetPort/api/localsend/v2/prepare-upload',
|
||
data: {
|
||
'senderInfo': {
|
||
'alias': '验证脚本',
|
||
'fingerprint': 'test-script',
|
||
'deviceModel': 'Script',
|
||
'deviceType': 'desktop',
|
||
},
|
||
'files': {
|
||
'file-0': {
|
||
'id': 'file-0',
|
||
'fileName': 'test.txt',
|
||
'size': 13,
|
||
'sha256':
|
||
'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855',
|
||
},
|
||
},
|
||
},
|
||
options: Options(contentType: Headers.jsonContentType),
|
||
);
|
||
print(' ✅ HTTP v2/prepare-upload: ${resp.statusCode}');
|
||
print(' 📦 ${jsonEncode(resp.data)}');
|
||
results['http_v2_prepare'] = resp.statusCode == 200;
|
||
} on DioException catch (e) {
|
||
print(' ❌ HTTP v2/prepare-upload 失败: ${e.type} ${e.message}');
|
||
results['http_v2_prepare'] = false;
|
||
}
|
||
|
||
// ─── 9. 测试信令服务器连接 ──────────────────────────────
|
||
print('');
|
||
print('━━━ 9. 信令服务器连接 ─━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||
const signalingUrls = [
|
||
'wss://tools.wktyl.com:9443',
|
||
'ws://tools.wktyl.com:9443',
|
||
'wss://tools.wktyl.com/ws',
|
||
];
|
||
for (final url in signalingUrls) {
|
||
final uri = Uri.parse(url);
|
||
try {
|
||
final socket = await WebSocket.connect(
|
||
url,
|
||
).timeout(const Duration(seconds: 5));
|
||
print(' ✅ $url 连接成功');
|
||
socket.close();
|
||
results['signaling_${uri.scheme}'] = true;
|
||
} catch (e) {
|
||
print(' ❌ $url 连接失败: $e');
|
||
results['signaling_${uri.scheme}'] = false;
|
||
}
|
||
}
|
||
|
||
// ─── 10. 测试服务器API信令信息 ──────────────────────────
|
||
print('');
|
||
print('━━━ 10. 服务器API信令信息 ─━━━━━━━━━━━━━━━━━━━━━━━━');
|
||
try {
|
||
final resp = await dio.get(
|
||
'https://tools.wktyl.com/api/file_transfer/signaling_info',
|
||
);
|
||
print(' ✅ signaling_info: ${resp.statusCode}');
|
||
print(' 📦 ${jsonEncode(resp.data)}');
|
||
results['api_signaling_info'] = true;
|
||
} on DioException catch (e) {
|
||
print(' ❌ signaling_info 失败: ${e.type} ${e.message}');
|
||
results['api_signaling_info'] = false;
|
||
}
|
||
|
||
// ─── 11. 测试本机HTTP服务器 ──────────────────────────────
|
||
print('');
|
||
print('━━━ 11. 本机HTTP服务器 ─━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||
final localIps = await _getLocalIps();
|
||
print(' 📋 本机IP: ${localIps.join(", ")}');
|
||
for (final ip in localIps) {
|
||
try {
|
||
final resp = await dio.get(
|
||
'http://$ip:$targetPort/api/localsend/v2/info',
|
||
);
|
||
print(' ✅ 本机 $ip:$targetPort: ${resp.statusCode}');
|
||
results['local_http_$ip'] = true;
|
||
} catch (e) {
|
||
print(' ❌ 本机 $ip:$targetPort: $e');
|
||
results['local_http_$ip'] = false;
|
||
}
|
||
}
|
||
|
||
// ─── 汇总 ────────────────────────────────────────────────
|
||
print('');
|
||
print('╔══════════════════════════════════════════════════╗');
|
||
print('║ 验证结果汇总 ║');
|
||
print('╚══════════════════════════════════════════════════╝');
|
||
int pass = 0, fail = 0;
|
||
results.forEach((name, ok) {
|
||
final icon = ok ? '✅' : '❌';
|
||
print(' $icon $name');
|
||
if (ok)
|
||
pass++;
|
||
else
|
||
fail++;
|
||
});
|
||
print('');
|
||
print(' 通过: $pass / ${results.length}');
|
||
|
||
if (fail > 0) {
|
||
print('');
|
||
print(' ⚠️ 失败项分析:');
|
||
if (results['https_v2_info'] == false && results['http_v2_info'] == true) {
|
||
print(' → HTTPS不可用但HTTP可用: 服务器未启用HTTPS,客户端必须降级HTTP');
|
||
}
|
||
if (results['https_v2_info'] == false && results['http_v2_info'] == false) {
|
||
print(' → HTTPS和HTTP都不可用: 目标设备未运行传输服务');
|
||
}
|
||
if (results['signaling_wss'] == false) {
|
||
print(' → 信令服务器不可达: 端口9443可能被防火墙阻止或服务未运行');
|
||
}
|
||
if (results['http_v2_message'] == false &&
|
||
results['http_v2_info'] == true) {
|
||
print(' → info可用但message不可用: message端点可能未注册或实现有误');
|
||
}
|
||
}
|
||
|
||
exit(fail > 0 ? 1 : 0);
|
||
}
|
||
|
||
Future<List<String>> _getLocalIps() async {
|
||
final ips = <String>[];
|
||
try {
|
||
final result = await Process.run('ipconfig', []);
|
||
final output = result.stdout.toString();
|
||
final ipv4Regex = RegExp(r'IPv4 Address[.\s]*:\s*(\d+\.\d+\.\d+\.\d+)');
|
||
for (final match in ipv4Regex.allMatches(output)) {
|
||
ips.add(match.group(1)!);
|
||
}
|
||
} catch (_) {}
|
||
if (ips.isEmpty) ips.add('127.0.0.1');
|
||
return ips;
|
||
}
|