diff --git a/flutter_app/lib/camera/camera_screen.dart b/flutter_app/lib/camera/camera_screen.dart index 0a9e383..df8aad8 100644 --- a/flutter_app/lib/camera/camera_screen.dart +++ b/flutter_app/lib/camera/camera_screen.dart @@ -147,7 +147,7 @@ class _CameraScreenState extends State { try { await ModelManager.instance .refresh() - .timeout(const Duration(seconds: 15)); + .timeout(const Duration(seconds: 30)); } catch (_) {} await _reloadWorker(); await _startCamera(); diff --git a/flutter_app/lib/models/model_manager.dart b/flutter_app/lib/models/model_manager.dart index 314ab1d..072494e 100644 --- a/flutter_app/lib/models/model_manager.dart +++ b/flutter_app/lib/models/model_manager.dart @@ -164,7 +164,7 @@ class ModelManager extends ChangeNotifier { await _loadActive(); final res = await _client .get(Uri.parse('$baseUrl/api/v1/app/update')) - .timeout(const Duration(seconds: 8)); + .timeout(const Duration(seconds: 30)); // 服务器 Content-Type 无 charset,http 包默认按 latin1 解码会乱码 → 显式 utf8 final body = jsonDecode(utf8.decode(res.bodyBytes)) as Map; @@ -297,15 +297,18 @@ class ModelManager extends ChangeNotifier { final sink = part.openWrite(); var received = 0; try { + // 下载无总时长上限(大模型慢网可能数分钟);连接/响应头与数据流 + // 分别做 30s 停滞判定,避免断流黑洞永久卡死 final res = await _client .send(http.Request('GET', Uri.parse('$baseUrl${item.downloadUrl}'))) - .timeout(const Duration(minutes: 3)); + .timeout(const Duration(seconds: 30)); if (res.statusCode != 200) { await sink.close(); return false; } final total = res.contentLength ?? item.sizeBytes; - await for (final chunk in res.stream) { + await for (final chunk + in res.stream.timeout(const Duration(seconds: 30))) { if (_cancelRequested.contains(item.datasetId)) break; // 用户取消 sink.add(chunk); received += chunk.length; diff --git a/flutter_app/lib/payment/order_api.dart b/flutter_app/lib/payment/order_api.dart index 567f4fc..77ff0ec 100644 --- a/flutter_app/lib/payment/order_api.dart +++ b/flutter_app/lib/payment/order_api.dart @@ -97,7 +97,7 @@ class OrderApi { res = await _client.get( Uri.parse('$baseUrl/api/v1/plans'), headers: {'Accept': 'application/json', ...await _authHeaders()}, - ).timeout(const Duration(seconds: 15)); + ).timeout(const Duration(seconds: 30)); } catch (e) { if (e is OrderApiException) rethrow; throw OrderApiException('网络请求失败: $e'); @@ -115,7 +115,7 @@ class OrderApi { res = await _client.get( Uri.parse('$baseUrl/api/v1/license'), headers: {'Accept': 'application/json', ...await _authHeaders()}, - ).timeout(const Duration(seconds: 15)); + ).timeout(const Duration(seconds: 30)); } catch (e) { if (e is OrderApiException) rethrow; throw OrderApiException('网络请求失败: $e'); @@ -136,7 +136,7 @@ class OrderApi { }, body: body, ) - .timeout(const Duration(seconds: 15)); + .timeout(const Duration(seconds: 30)); } catch (e) { if (e is OrderApiException) rethrow; throw OrderApiException('网络请求失败: $e'); diff --git a/flutter_app/lib/update/update_checker.dart b/flutter_app/lib/update/update_checker.dart index 817aecc..05ab23f 100644 --- a/flutter_app/lib/update/update_checker.dart +++ b/flutter_app/lib/update/update_checker.dart @@ -38,7 +38,7 @@ class UpdateChecker { try { final res = await _client .get(Uri.parse('$baseUrl/api/v1/app/update')) - .timeout(const Duration(seconds: 8)); + .timeout(const Duration(seconds: 30)); // 服务器 Content-Type 无 charset,http 包默认按 latin1 解码会乱码 → 显式 utf8 final body = jsonDecode(utf8.decode(res.bodyBytes)) as Map; diff --git a/flutter_app/lib/update/update_screen.dart b/flutter_app/lib/update/update_screen.dart index 282a502..81c5cec 100644 --- a/flutter_app/lib/update/update_screen.dart +++ b/flutter_app/lib/update/update_screen.dart @@ -98,14 +98,19 @@ class _UpdateScreenState extends State { }); try { if (_apkPart.existsSync()) _apkPart.deleteSync(); - final res = await _client.send(http.Request('GET', Uri.parse(widget.url))); + // 下载无总时长上限(APK 几十 MB 慢网可能数分钟);连接/响应头与 + // 数据流分别做 30s 停滞判定,避免断流黑洞永久卡死 + final res = await _client + .send(http.Request('GET', Uri.parse(widget.url))) + .timeout(const Duration(seconds: 30)); if (res.statusCode != 200) { throw HttpException('HTTP ${res.statusCode}'); } final total = res.contentLength ?? -1; final sink = _apkPart.openWrite(); var received = 0; - await for (final chunk in res.stream) { + await for (final chunk + in res.stream.timeout(const Duration(seconds: 30))) { sink.add(chunk); received += chunk.length; if (mounted && total > 0) {