Files
xianyan/scripts/localsend_interface_verify.dart
Developer 283950ea07 chore: 批量代码优化与功能迭代更新
本次提交包含大量代码优化、功能新增与服务端配置更新:
1. 修复分析报告统计数据,调整CMake策略设置
2. 优化APP权限配置、编辑器与聊天界面组件
3. 更新依赖库版本与pubspec配置
4. 新增文件传输服务端、信令服务器相关配置与脚本
5. 完善用户注销功能与数据库迁移脚本
6. 优化多处动画效果、代码风格与日志输出
7. 新增多种调试与部署脚本,修复已知BUG
2026-05-12 06:28:04 +08:00

320 lines
13 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 — 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;
}