簡體   English   中英

flutter web如何上傳大文件?

[英]How to upload large files in flutter web?

已經有一個關於如何挑選文件的精彩話題: 文章

我最終得到List<PlatformFile>? files List<PlatformFile>? files ,如果我使用 file_picker。 我將withReadStream: true傳遞給了 pickFiles 方法,所以我得到了 stream。 到目前為止,這是我的代碼:

List<PlatformFile>? files = fileUploadView.result?.files;
for (PlatformFile file in files!) {


//-----add selected file with request
request.files.add(http.MultipartFile(
    "Your parameter name on server side",  file.readStream!,
    file.size,
    filename: file.name));
}

//-------Send request
var resp = await request.send();

但是,如果我運行它,每隔幾秒鍾就會出現一個錯誤:

RangeError: Array buffer allocation failed
    at new ArrayBuffer (<anonymous>)
    at new Uint8Array (<anonymous>)
    at Function._create1 (http://localhost:54372/dart_sdk.js:32192:14)
    at Function.new (http://localhost:54372/dart_sdk.js:32155:49)

幾周后,我可以發布答案: go 的方法是使用塊上傳器。 這意味着手動發送文件的小部分。 例如,我每個請求發送 99MB。 網上已經有了這個的基本實現: https://pub.dev/packages/chunked_uploader

你必須得到一個 stream,這可以通過 file_picker 或 drop_zone 庫來實現。 我使用了 drop_zone 庫,因為它提供了文件選擇器和拖放區功能。 在我的代碼中, dynamic file對象來自 drop_zone 庫。

也許您必須根據您的后端調整塊上傳器功能。 我使用 django 后端,我在其中編寫了一個簡單的視圖來保存文件。 在小文件的情況下,它可以接收包含多個文件的多部分請求,在大文件的情況下,它可以接收塊並在收到前一個塊的情況下繼續寫入文件。 這是我的代碼的一些部分:

Python 后端:

@api_view(["POST"])
def upload(request):
    basePath = config.get("BasePath")
    
    targetFolder = os.path.join(basePath, request.data["taskId"], "input")
    if not os.path.exists(targetFolder):
        os.makedirs(targetFolder)

    for count, file in enumerate(request.FILES.getlist("Your parameter name on server side")):
        path = os.path.join(targetFolder, file.name)
        print(path)
        with open(path, 'ab') as destination:
            for chunk in file.chunks():
                destination.write(chunk)

    return HttpResponse("File(s) uploaded!")

我的版本中的 flutter 塊上傳器:

import 'dart:async';
import 'dart:html';
import 'dart:math';
import 'package:dio/dio.dart';
import 'package:flutter_dropzone/flutter_dropzone.dart';
import 'package:http/http.dart' as http;

class UploadRequest {
  final Dio dio;
  final String url;
  final String method;
  final String fileKey;
  final Map<String, String>? bodyData;
  final Map<String, String>? headers;
  final CancelToken? cancelToken;
  final dynamic file;
  final Function(double)? onUploadProgress;
  late final int _maxChunkSize;
  int fileSize;
  String fileName;
  late DropzoneViewController controller;

  UploadRequest(
    this.dio, {
    required this.url,
    this.method = "POST",
    this.fileKey = "file",
    this.bodyData = const {},
    this.cancelToken,
    required this.file,
    this.onUploadProgress,
    int maxChunkSize = 1024 * 1024 * 99,
    required this.controller,
    required this.fileSize,
    required this.fileName,
    this.headers
  }) {
    _maxChunkSize = min(fileSize, maxChunkSize);
  }

  Future<Response?> upload() async {
    Response? finalResponse;
    for (int i = 0; i < _chunksCount; i++) {
      final start = _getChunkStart(i);
      print("start is $start");
      final end = _getChunkEnd(i);
      final chunkStream = _getChunkStream(start, end);
      
      
      var request = http.MultipartRequest(
        "POST",
        Uri.parse(url),
      );

      //request.headers.addAll(_getHeaders(start, end));
      request.headers.addAll(headers!);

      //-----add other fields if needed
      request.fields.addAll(bodyData!);

      request.files.add(http.MultipartFile(
        "Your parameter name on server side",
        chunkStream,
        fileSize,
        filename: fileName// + i.toString(),
        )
      );


      //-------Send request
      var resp = await request.send();

      //------Read response
      String result = await resp.stream.bytesToString();

      //-------Your response
      print(result);

      
    }
    return finalResponse;
  }

  Stream<List<int>> _getChunkStream(int start, int end) async* {
    print("reading from $start to $end");
    final reader = FileReader();
    final blob = file.slice(start, end);
    reader.readAsArrayBuffer(blob);
    await reader.onLoad.first;
    yield reader.result as List<int>;
  }


  // Updating total upload progress
  _updateProgress(int chunkIndex, int chunkCurrent, int chunkTotal) {
    int totalUploadedSize = (chunkIndex * _maxChunkSize) + chunkCurrent;
    double totalUploadProgress = totalUploadedSize / fileSize;
    this.onUploadProgress?.call(totalUploadProgress);
  }

  // Returning start byte offset of current chunk
  int _getChunkStart(int chunkIndex) => chunkIndex * _maxChunkSize;

  // Returning end byte offset of current chunk
  int _getChunkEnd(int chunkIndex) =>
      min((chunkIndex + 1) * _maxChunkSize, fileSize);

  // Returning a header map object containing Content-Range
  // https://tools.ietf.org/html/rfc7233#section-2
  Map<String, String> _getHeaders(int start, int end) {
    var header = {'Content-Range': 'bytes $start-${end - 1}/$fileSize'};
    if (headers != null) {
      header.addAll(headers!);
    }
    return header;
  }

  // Returning chunks count based on file size and maximum chunk size
  int get _chunksCount {
    var result = (fileSize / _maxChunkSize).ceil();
    return result;
  }
}

    

上傳代碼決定是在一個請求中上傳多個文件還是一個文件分成多個請求:

//upload the large files

Map<String, String> headers = {
  'Authorization': requester.loginToken!
};

fileUploadView.droppedFiles.sort((a, b) => b.size - a.size);

//calculate the sum of teh files:

double sumInMb = 0;
int divideBy = 1000000;

for (UploadableFile file in fileUploadView.droppedFiles) {
    sumInMb += file.size / divideBy;
}

var dio = Dio();

int uploadedAlready = 0;
for (UploadableFile file in fileUploadView.droppedFiles) {

  if (sumInMb < 99) {
    break;
  }

  var uploadRequest = UploadRequest(
    dio,
    url: requester.backendApi+ "/upload",
    file: file.file,
    controller: fileUploadView.controller!,
    fileSize: file.size,
    fileName: file.name,
    headers: headers,
    bodyData: {
      "taskId": taskId.toString(),
      "user": requester.username!,
    },
  );

  await uploadRequest.upload();

  uploadedAlready++;
  sumInMb -= file.size / divideBy;
}

if (uploadedAlready > 0) {
  fileUploadView.droppedFiles.removeRange(0, uploadedAlready);
}

print("large files uploaded");

// upload the small files

//---Create http package multipart request object
var request = http.MultipartRequest(
  "POST",
  Uri.parse(requester.backendApi+ "/upload"),
);


request.headers.addAll(headers);

//-----add other fields if needed
request.fields["taskId"] = taskId.toString();

print("adding files selected with drop zone");
for (UploadableFile file in fileUploadView.droppedFiles) {

  Stream<List<int>>? stream = fileUploadView.controller?.getFileStream(file.file);

  print("sending " + file.name);

  request.files.add(http.MultipartFile(
      "Your parameter name on server side",
      stream!,
      file.size,
      filename: file.name));
}


//-------Send request
var resp = await request.send();

//------Read response
String result = await resp.stream.bytesToString();

//-------Your response
print(result);

希望這能給您一個很好的概述,我是如何解決這個問題的。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM