Commit 6b2a3eb2 by Administrator

Merge branch 'feature-2511-opt-update-test' of http://120.24.213.56/ethan/appfra…

…me into feature-2511-opt-update-test
1 parent e54b285c
{"guid":"dc4b70c03e8043e50e38f2068887b1d4","name":"Pods","path":"/Users/ethanlam/works/gitlab/flutter_pros/appframe.git/ios/Pods/Pods.xcodeproj/project.xcworkspace","projects":["PROJECT@v11_mod=14ee682db2378d556925c69eae52a50e_hash=bfdfe7dc352907fc980b868725387e98plugins=1OJSG6M1FOV3XYQCBH7Z29RZ0FPR9XDE1"]}
\ No newline at end of file \ No newline at end of file
import 'package:dio/dio.dart';
class ParallelUploader {
final Dio dio = Dio();
final int maxConcurrentUploads;
ParallelUploader({this.maxConcurrentUploads = 3});
// 并行上传多个分片
Future<List<UploadResult>> uploadChunksParallel(
File file,
List<ChunkInfo> chunks,
String uploadUrl,
) async {
final results = <UploadResult>[];
final completed = <bool>[];
// 使用队列控制并发数量
for (int i = 0; i < chunks.length; i += maxConcurrentUploads) {
final currentBatch = chunks.sublist(
i,
i + maxConcurrentUploads > chunks.length ? chunks.length : i + maxConcurrentUploads
);
// 并行上传当前批次的分片
final batchFutures = currentBatch.map((chunk) {
return compute(_uploadSingleChunk, UploadTask(
filePath: file.path,
chunk: chunk,
uploadUrl: uploadUrl,
));
}).toList();
final batchResults = await Future.wait(batchFutures);
results.addAll(batchResults);
// 更新进度
_updateProgress(results.length, chunks.length);
}
return results;
}
void _updateProgress(int completed, int total) {
final progress = (completed / total * 100).toInt();
print('上传进度: $progress%');
}
}
// 上传任务数据类(必须是可序列化的)
class UploadTask {
final String filePath;
final ChunkInfo chunk;
final String uploadUrl;
UploadTask({
required this.filePath,
required this.chunk,
required this.uploadUrl,
});
}
// 上传结果类
class UploadResult {
final int chunkIndex;
final bool success;
final String? error;
final String? chunkId;
UploadResult({
required this.chunkIndex,
required this.success,
this.error,
this.chunkId,
});
}
// 在后台线程上传单个分片
Future<UploadResult> _uploadSingleChunk(UploadTask task) async {
try {
final file = File(task.filePath);
final chunk = task.chunk;
// 读取分片数据
final raf = file.openSync();
raf.setPositionSync(chunk.start);
final chunkData = raf.readSync(chunk.end - chunk.start);
raf.closeSync();
// 上传分片
final dio = Dio();
final formData = FormData.fromMap({
'file': MultipartFile.fromBytes(
chunkData,
filename: 'chunk-${chunk.index}'
),
'chunkIndex': chunk.index,
'totalChunks': chunk.totalChunks,
'chunkSize': chunk.end - chunk.start,
});
final response = await dio.post(
task.uploadUrl,
data: formData,
options: Options(
sendTimeout: Duration(seconds: 30),
receiveTimeout: Duration(seconds: 30),
),
);
return UploadResult(
chunkIndex: chunk.index,
success: response.statusCode == 200,
chunkId: response.data['chunkId'],
);
} catch (e) {
return UploadResult(
chunkIndex: task.chunk.index,
success: false,
error: e.toString(),
);
}
}
\ No newline at end of file \ No newline at end of file
class MultiThreadedFileUploader {
static const int CHUNK_SIZE = 1024 * 1024; // 1MB
static const int MAX_CONCURRENT_UPLOADS = 3;
final ParallelUploader _uploader = ParallelUploader(
maxConcurrentUploads: MAX_CONCURRENT_UPLOADS
);
// 主上传方法
Future<UploadSummary> uploadFile(
File file,
String uploadUrl,
String mergeUrl,
) async {
final stopwatch = Stopwatch()..start();
try {
print('开始准备分片...');
// 在后台线程计算分片
final chunks = await FileUploader.prepareChunks(file, CHUNK_SIZE);
print('文件分片完成,共 ${chunks.length} 个分片');
// 并行上传所有分片
print('开始并行上传分片...');
final results = await _uploader.uploadChunksParallel(
file, chunks, uploadUrl
);
// 检查上传结果
final failedChunks = results.where((r) => !r.success).toList();
if (failedChunks.isNotEmpty) {
throw Exception('部分分片上传失败: ${failedChunks.length}');
}
// 通知服务端合并文件
print('所有分片上传完成,开始合并...');
await _notifyMerge(file, chunks, mergeUrl);
stopwatch.stop();
return UploadSummary(
success: true,
totalChunks: chunks.length,
fileSize: await file.length(),
duration: stopwatch.elapsed,
);
} catch (e) {
stopwatch.stop();
return UploadSummary(
success: false,
error: e.toString(),
duration: stopwatch.elapsed,
);
}
}
Future<void> _notifyMerge(
File file,
List<ChunkInfo> chunks,
String mergeUrl
) async {
final dio = Dio();
await dio.post(mergeUrl, data: {
'fileName': file.path.split('/').last,
'totalChunks': chunks.length,
'fileSize': await file.length(),
});
}
}
class UploadSummary {
final bool success;
final int? totalChunks;
final int? fileSize;
final Duration duration;
final String? error;
UploadSummary({
required this.success,
this.totalChunks,
this.fileSize,
required this.duration,
this.error,
});
}
\ No newline at end of file \ No newline at end of file
class FileUploadScreen extends StatefulWidget {
@override
_FileUploadScreenState createState() => _FileUploadScreenState();
}
class _FileUploadScreenState extends State<FileUploadScreen> {
final _uploader = MultiThreadedFileUploader();
double _progress = 0.0;
bool _isUploading = false;
String _status = '准备就绪';
Future<void> _uploadFile() async {
setState(() {
_isUploading = true;
_progress = 0.0;
_status = '开始上传...';
});
// 选择文件
final file = await _pickFile();
if (file == null) return;
try {
final summary = await _uploader.uploadFile(
file,
'https://api.example.com/upload-chunk',
'https://api.example.com/merge-file',
);
setState(() {
_status = summary.success
? '上传成功! 耗时: ${summary.duration.inSeconds}秒'
: '上传失败: ${summary.error}';
_progress = 1.0;
});
} catch (e) {
setState(() {
_status = '上传异常: $e';
});
} finally {
setState(() {
_isUploading = false;
});
}
}
Future<File?> _pickFile() async {
// 实现文件选择逻辑
return null;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('多线程文件上传')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
LinearProgressIndicator(value: _progress),
SizedBox(height: 20),
Text(_status),
SizedBox(height: 20),
ElevatedButton(
onPressed: _isUploading ? null : _uploadFile,
child: Text(_isUploading ? '上传中...' : '选择文件并上传'),
),
],
),
),
);
}
}
\ No newline at end of file \ No newline at end of file
import 'package:flutter/foundation.dart';
import 'dart:io';
// 分片信息类
class ChunkInfo {
final int index;
final int start;
final int end;
final int totalChunks;
ChunkInfo(this.index, this.start, this.end, this.totalChunks);
}
// 在后台线程计算文件分片信息
List<ChunkInfo> calculateChunks(String filePath, int chunkSize) {
final file = File(filePath);
final fileSize = file.lengthSync();
final totalChunks = (fileSize / chunkSize).ceil();
final chunks = <ChunkInfo>[];
for (int i = 0; i < totalChunks; i++) {
final start = i * chunkSize;
final end = (i + 1) * chunkSize > fileSize ? fileSize : (i + 1) * chunkSize;
chunks.add(ChunkInfo(i, start, end, totalChunks));
}
return chunks;
}
class FileUploader {
static Future<List<ChunkInfo>> prepareChunks(File file, int chunkSize) async {
// 在后台线程计算分片信息,避免阻塞UI
return await compute(calculateChunks, file.path, chunkSize);
}
}
\ No newline at end of file \ No newline at end of file
...@@ -7,7 +7,7 @@ import Foundation ...@@ -7,7 +7,7 @@ import Foundation
import connectivity_plus import connectivity_plus
import device_info_plus import device_info_plus
import ffmpeg_kit_flutter_new_audio import ffmpeg_kit_flutter_new
import file_picker import file_picker
import flutter_image_compress_macos import flutter_image_compress_macos
import flutter_localization import flutter_localization
...@@ -19,7 +19,7 @@ import package_info_plus ...@@ -19,7 +19,7 @@ import package_info_plus
import path_provider_foundation import path_provider_foundation
import photo_manager import photo_manager
import shared_preferences_foundation import shared_preferences_foundation
import sqflite_darwin import tencent_cloud_chat_sdk
import url_launcher_macos import url_launcher_macos
import video_compress import video_compress
import video_player_avfoundation import video_player_avfoundation
...@@ -40,7 +40,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { ...@@ -40,7 +40,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
PhotoManagerPlugin.register(with: registry.registrar(forPlugin: "PhotoManagerPlugin")) PhotoManagerPlugin.register(with: registry.registrar(forPlugin: "PhotoManagerPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) TencentCloudChatSdkPlugin.register(with: registry.registrar(forPlugin: "TencentCloudChatSdkPlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
VideoCompressPlugin.register(with: registry.registrar(forPlugin: "VideoCompressPlugin")) VideoCompressPlugin.register(with: registry.registrar(forPlugin: "VideoCompressPlugin"))
FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin"))
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include <flutter_localization/flutter_localization_plugin_c_api.h> #include <flutter_localization/flutter_localization_plugin_c_api.h>
#include <geolocator_windows/geolocator_windows.h> #include <geolocator_windows/geolocator_windows.h>
#include <permission_handler_windows/permission_handler_windows_plugin.h> #include <permission_handler_windows/permission_handler_windows_plugin.h>
#include <tencent_cloud_chat_sdk/tencent_cloud_chat_sdk_plugin_c_api.h>
#include <url_launcher_windows/url_launcher_windows.h> #include <url_launcher_windows/url_launcher_windows.h>
void RegisterPlugins(flutter::PluginRegistry* registry) { void RegisterPlugins(flutter::PluginRegistry* registry) {
...@@ -21,6 +22,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { ...@@ -21,6 +22,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("GeolocatorWindows")); registry->GetRegistrarForPlugin("GeolocatorWindows"));
PermissionHandlerWindowsPluginRegisterWithRegistrar( PermissionHandlerWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
TencentCloudChatSdkPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("TencentCloudChatSdkPluginCApi"));
UrlLauncherWindowsRegisterWithRegistrar( UrlLauncherWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("UrlLauncherWindows")); registry->GetRegistrarForPlugin("UrlLauncherWindows"));
} }
...@@ -7,6 +7,7 @@ list(APPEND FLUTTER_PLUGIN_LIST ...@@ -7,6 +7,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
flutter_localization flutter_localization
geolocator_windows geolocator_windows
permission_handler_windows permission_handler_windows
tencent_cloud_chat_sdk
url_launcher_windows url_launcher_windows
) )
......
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!