Как объединить фрагментированные загрузки файлов из Dropzone.js с помощью PHP?

Я использую Dropzone.js, чтобы брать файлы разных типов (в том числе изображения и не изображения, такие как PDF), и загружать их по 1 МБ чанками на наш сервер. Затем я пытаюсь объединить файлы с помощью PHP, а затем загрузить их в базу данных FileMaker нашей компании.

До сих пор я был в состоянии получить файлы для загрузки в чанках, как они должны. Я храню их все во временной папке «uploads» с тем же «кодовым именем», с «-INDEX #», добавляемым в конце каждого имени (INDEX # - загружаемый чанк #, передаваемый Dropzone).

Я выделил сбой той части PHP, которая перебирает уже загруженные фрагменты для захвата контента. В частности, когда я собираюсь извлечь содержимое, я пытаюсь сохранить путь к файлу чанка в переменной, используя PHP-путь "realpath", чтобы получить абсолютный путь, а также служить проверкой истинности / ложности существования файла. В обязательном порядке PHP не может «увидеть» файл.

Я не эксперт в области разрешений для папок, поэтому есть большая вероятность, что это может быть связано с этим, и я просто не знаю, как с этим справиться. Вы увидите, что я попытался выполнить chmod в верхней части PHP в каталоге uploads /.

Я включил только код PHP ниже, и ни один из javascript. Я не уверен, что JS имеет значение, так как он, очевидно, отлично работает с реальными загрузками чанков.

Файл PHP, приведенный ниже, находится в том же каталоге, что и папка uploads /, где должны находиться фрагменты.

<?php header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0"); header("Cache-Control: post-check=0, pre-check=0", false); header("Pragma: no-cache"); /* ======================================== VARIABLES ======================================== */ // chunk variables $fileId = $_POST['dzuuid']; $chunkIndex = $_POST['dzchunkindex'] + 1; $chunkTotal = $_POST['dztotalchunkcount']; // file path variables $ds = DIRECTORY_SEPARATOR; $targetPath = dirname( __FILE__ ) . "{$ds}uploads{$ds}"; $fileType = strtolower(pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION)); $fileSize = $_FILES["file"]["size"]; $filename = "{$fileId}-{$chunkIndex}.{$fileType}"; $targetFile = $targetPath . $filename; // change directory permissions chmod(realpath($targetPath), 0777); /* ======================================== DEPENDENCY FUNCTIONS ======================================== */ $returnResponse = function ($info = null, $filelink = null, $status = "error") { die (json_encode( array( "status" => $status, "info" => $info, "file_link" => $filelink ))); }; /* ======================================== VALIDATION CHECKS ======================================== */ // I removed all the validation code here. They just prevent upload, so assume the upload is going through. /* ======================================== CHUNK UPLOAD ======================================== */ move_uploaded_file($_FILES['file']['tmp_name'], $targetFile); // Be sure that the file has been uploaded if ( !file_exists($targetFile) ) $returnResponse("An error occurred and we couldn't upload the requested file."); /* ======================================== FINAL UPLOAD CONDITIONAL ======================================== */ if ( $chunkIndex == $chunkTotal ) { // ===== concatenate uploaded files ===== // set emtpy string for file content concatonation $file_content = ""; // loop through temp files and grab the content for ($i = 1; $i <= $chunkTotal; $i++) { // target temp file $temp_file_path = realpath("{$targetPath}{$fileId}-{$i}.{$fileType}") or $returnResponse("Your chunk was lost mid-upload."); // ^^^^^^^ this is where the failure is occurring, $i = 1, so first iteration // copy chunk...you'll see a bunch of methods included below that I've tried, but the simplest one is method 3, so I've tested mostly there // method 1 /*$temp_file = fopen($temp_file_path, "rb") or $returnResponse("The server cannot open your chunks"); $chunk = base64_encode(fread($temp_file, $fileSize)); fclose($temp_file); // method 2 $chunk = base64_encode(stream_get_contents($temp_file_path, $fileSize));*/ // method 3 $chunk = base64_encode(file_get_contents($temp_file_path)); // check chunk content if ( empty($chunk) ) $returnResponse("Chunks are uploading as empty strings."); // add chunk to main file $file_content .= $chunk; // delete chunk unlink($temp_file_path); if ( file_exists($temp_file_path) ) $returnResponse("Your temp files could not be deleted."); continue; } // create and write concatonated chunk to the main file file_put_contents("{$targetPath}{$fileId}.{$fileType}", base64_decode($file_content)); // other method of adding contents to new file below, but the one above seemed simpler //$final = fopen("{$target_file}.{$fileType}", 'ab'); //fwrite($final, base64_decode($file_content)); //fclose($final); // create new FileMaker code removed here, irrelevant // run FileMaker script to populate container field with concatenated file code removed here, irrelevant // somewhere in the code above, if everything succeeds, I unlink the concatenated file so that it's not cluttering my "uploads" folder, but I never get this far } else { $returnResponse(null, null, "success"); } 

Всего 1 ответ


Я понял! Проблема в том, что я пытался вызвать цикл конкатенации, когда $ chunkIndex == $ chunkTotal. Если вы посмотрите на монитор сети браузера, вы увидите, что порции часто загружаются в неправильном порядке, что может привести к сбою конкатенации на шаге realpath, поскольку файл на самом деле еще не существует (он просто выглядел так, как когда Я посетил фактическую папку несколько секунд спустя). Чтобы доказать это, просто попробуйте sleep (5), чтобы дать оставшееся время для загрузки и убедиться, что все получилось (конечно, это плохое решение, но быстрый тестер).

Решение состоит в том, чтобы отделить сценарий загрузки от сценария объединения. Если вы используете Dropzone.js, вы можете запустить сценарий конкатенации из "chunksUploaded", как описано по этой ссылке:

Ниже вы можете увидеть, как выглядели финальные сценарии:

script.js

var myDropzone = new Dropzone(target, {
  url: ($(target).attr("action")) ? $(target).attr("action") : "../../chunk-upload.php", // Check that our form has an action attr and if not, set one here
  maxFilesize: 25, // megabytes
  chunking: true,
  parallelUploads: 1,
  parallelChunkUploads: true,
  retryChunks: true,
  retryChunksLimit: 3,
  forceChunking: true,
  chunkSize: 1000000,
  acceptedFiles: "image/*,application/pdf,.doc,.docx,.xls,.xlsx,.csv,.tsv,.ppt,.pptx,.pages,.odt,.rtf,.heif,.hevc",
  previewTemplate: previewTemplate,
  previewsContainer: "#previews",
  clickable: true,
  autoProcessQueue: false,
  chunksUploaded: function(file, done) {
    // All chunks have been uploaded. Perform any other actions
    let currentFile = file;

    // This calls server-side code to merge all chunks for the currentFile
    $.ajax({
        url: "chunk-concat.php?dzuuid=" + currentFile.upload.uuid + "&dztotalchunkcount=" + currentFile.upload.totalChunkCount + "&fileName=" + currentFile.name.substr( (currentFile.name.lastIndexOf('.') +1) ),
        success: function (data) {
            done();
        },
        error: function (msg) {
            currentFile.accepted = false;
            myDropzone._errorProcessing([currentFile], msg.responseText);
        }
     });
  },

});

Кусок-upload.php

 <?php /* ======================================== VARIABLES ======================================== */ // chunk variables $fileId = $_POST['dzuuid']; $chunkIndex = $_POST['dzchunkindex'] + 1; $chunkTotal = $_POST['dztotalchunkcount']; // file path variables $ds = DIRECTORY_SEPARATOR; $targetPath = dirname( __FILE__ ) . "{$ds}uploads{$ds}"; $fileType = strtolower(pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION)); $fileSize = $_FILES["file"]["size"]; $filename = "{$fileId}-{$chunkIndex}.{$fileType}"; $targetFile = $targetPath . $filename; // change directory permissions chmod(realpath($targetPath), 0777) or die("Could not modify directory permissions."); /* ======================================== DEPENDENCY FUNCTIONS ======================================== */ $returnResponse = function ($info = null, $filelink = null, $status = "error") { die (json_encode( array( "status" => $status, "info" => $info, "file_link" => $filelink ))); }; /* ======================================== VALIDATION CHECKS ======================================== */ // blah, blah, blah validation stuff goes here /* ======================================== CHUNK UPLOAD ======================================== */ move_uploaded_file($_FILES['file']['tmp_name'], $targetFile); // Be sure that the file has been uploaded if ( !file_exists($targetFile) ) $returnResponse("An error occurred and we couldn't upload the requested file."); chmod($targetFile, 0777) or $returnResponse("Could not reset permissions on uploaded chunk."); $returnResponse(null, null, "success"); 

Кусок-concat.php

 <?php // get variables $fileId = $_GET['dzuuid']; $chunkTotal = $_GET['dztotalchunkcount']; // file path variables $ds = DIRECTORY_SEPARATOR; $targetPath = dirname( __FILE__ ) . "{$ds}uploads{$ds}"; $fileType = $_GET['fileName']; /* ======================================== DEPENDENCY FUNCTIONS ======================================== */ $returnResponse = function ($info = null, $filelink = null, $status = "error") { die (json_encode( array( "status" => $status, "info" => $info, "file_link" => $filelink ))); }; /* ======================================== CONCATENATE UPLOADED FILES ======================================== */ // loop through temp files and grab the content for ($i = 1; $i <= $chunkTotal; $i++) { // target temp file $temp_file_path = realpath("{$targetPath}{$fileId}-{$i}.{$fileType}") or $returnResponse("Your chunk was lost mid-upload."); // copy chunk $chunk = file_get_contents($temp_file_path); if ( empty($chunk) ) $returnResponse("Chunks are uploading as empty strings."); // add chunk to main file file_put_contents("{$targetPath}{$fileId}.{$fileType}", $chunk, FILE_APPEND | LOCK_EX); // delete chunk unlink($temp_file_path); if ( file_exists($temp_file_path) ) $returnResponse("Your temp files could not be deleted."); } /* ========== a bunch of steps I removed below here because they're irrelevant, but I described them anyway ========== */ // create FileMaker record // run FileMaker script to populate container field with newly-created file // unlink newly created file // return success 

Есть идеи?

10000