Так что это решение. Прежде всего, код html:
<script src="/js/lib/jquery.min.js" type="text/javascript" ></script><!-- jquery 3.4.1 -->
<form id="fupForm" enctype="multipart/form-data">
<p id="dbi-upload-progress">Choose a file then click Upload.</p>
<input id="dbi-file-upload" type="file" name="dbi_import_file" /><br><br>
<input id="dbi-upload-submit" type="submit" value="Upload" />
</form>
Итак, начнем с раздела javascript. Вот переменные, используемые глобально:
var max_size = 5$uploadedFileId='fileUpload';
$name = $_FILES[$uploadedFileId]['name'];
$tmp_name = $_FILES[$uploadedFileId]['tmp_name'];
$type = $_FILES[$uploadedFileId]['type'];
if($_POST['init'] && $_POST['init']==1) {
// Init part, let's clean session and prepare for the upload
if(isset($_SESSION[session_id()]['UPLOADED_FILE'])) {
array_splice($_SESSION[session_id()]['UPLOADED_FILE'], 1);
unset($_SESSION[session_id()]['UPLOADED_FILE']);
}
$_SESSION[session_id()]['UPLOADED_FILE'][0]='';
die(new JsonSuccess("Init session ok")); // Print a simple json string and exit
} else if(substr($name, -5) === '.part') {
// chunk upload part, this can be executed on different web servers in different threads
$part=$_POST['part'];
if(isset($_FILES[$uploadedFileId]['tmp_name']) && is_uploaded_file($_FILES[$uploadedFileId]['tmp_name'])) {
if($fp = fopen($_FILES[$uploadedFileId]['tmp_name'],'rb')) {
$contents = '';
while (!feof($fp)) {
$contents .= fread($fp, 8192);
}
$_SESSION[session_id()]['UPLOADED_FILE'][$part] = $contents;
die(new JsonSuccess("Chunk ".$name.": loaded ".strlen($contents)." bytes"); // Print a simple json string and exit
}
}
} else {
// Commit part: let's assemble the final file in /tmp folder
$myfile = fopen("/tmp/".$name, "w")
or die("Write error: /tmp/".$name);
for ($row = 0; isset($_SESSION[session_id()]['UPLOADED_FILE'][$row]) ; $row++) {
fwrite($myfile, $_SESSION[session_id()]['UPLOADED_FILE'][$row]);
}
fclose($myfile);
// Clean the session
array_splice($_SESSION[session_id()]['UPLOADED_FILE'], 1);
unset($_SESSION[session_id()]['UPLOADED_FILE']);
die(new JsonSuccess("Commit success!")); // print a simple json string and exit
}
1024; // Max upload size: 5MB
var slice_size = 100*1024; // Max size of each fragment: 100 KB
var concurrency = 4; // Max thread number
var start_time_total=$.now(); // Time var, just to check upload performance
var file = {}; // Pointer to file to upload
var abort = false; // Flag used to abort operation
var goals = 0; // Counter of parallel threads completed
var percent = 0; // Percent of progress
Затем нам нужна функция инициализации jquery, чтобы предотвратить отправку стандартной формы и начать транзакцию:
$(document).ready(function(e){
$("#fupForm").on('submit', function(e){
e.preventDefault(); // prevent standard submit
// Point to the file
file = document.querySelector( '#dbi-file-upload' ).files[0];
// Checking file size and extension
if(file.size > max_size) {
alert("Warning! \nFiles bigger than 5MB not supported");
return;
} else if(file.name.endsWith('.part')) {
alert("Warning! \nFiles with .part extension not supported");
return;
}
init_upload_file(); // Beginning the transaction
});
});
Далее нам нужно вызвать один раз наш php скрипт для инициализации сеанса на стороне сервера, чтобы избежать ошибок параллелизма и совместного использования сеанса.
function init_upload_file() {
// Global browser-side variables init, form disable and some console logging
abort = false; goals = 0; percent = 0; start_time_total=$.now();
$('#dbi-upload-submit').attr("disabled","disabled");
$('#dbi-upload-progress').html('Upload init...');
console.log("Start global transaction @"+ new Date(start_time_total));
// First call to server with 'init' parameter
var formData = new FormData();
formData.append("init", 1);
var start_time=$.now();
console.log("start init call @"+new Date(start_time));
$.ajax( {
url: "/myPhpFileUrl", type: 'POST', dataType: 'json', contentType: false,
cache: false, processData:false, data: formData,
error: function( data ) {
$('#dbi-upload-progress').html( 'Init call failed' ); // You can use an alert
console.log( "Init call ko after "+($.now()-start_time)+" msecs; error: "+data );
console.log( "End global transaction after "+ ($.now()-start_time_total)+" msecs");
$('#dbi-upload-submit').removeAttr("disabled"); // Re-enable the form submit
},
success: function( data ) {
$( '#dbi-upload-progress' ).html( 'Sending file...' );
// Calling 'core' function to send multiple fragments
for(var i=0;i<concurrency;i++)
upload_file( (i * (slice_size + 1)) );
}
});
}
Итак, здесь вы находитесь в «основной» функции, созданной для вызова (js - фальшивый) многопоточный режим:
function upload_file( start ) { // Start argument is the offset used to read a fragment
if(abort) return; // Check if some thread is in error (abort the transaction)
// Initialize file reader and put in blob var the fragment to send
var reader = new FileReader();
var next_slice = start + slice_size + 1;
var blob = file.slice( start, next_slice );
reader.onloadend = function( event ) {
if ( event.target.readyState !== FileReader.DONE ) { return; }
// preparing the form data to send and calling the server
var slice_id=Math.floor(start / slice_size);
var formData = new FormData();
formData.append("fileUpload", blob, "." + slice_id + ".part");
formData.append("part", slice_id);
var start_time=$.now();
console.log("Start sending slice "+slice_id+" @"+new Date(start_time));
$.ajax( {
url: "/myPhpFileUrl", type: 'POST', dataType: 'json', contentType: false,
cache: false, processData:false, data: formData,
error: function( data ) {
console.log("slice "+slice_id+" send error after "+($.now()-start_time)+" msecs");
console.log("error: "+data );
console.log("End global transaction after "+ ($.now()-start_time_total)+" msecs");
if(abort) return; // Maybe some other thread was in error...
// If it's the first error we inform the user and re-enable the form submit
$('#dbi-upload-progress').html('Upload error'); // You can use an alert
$('#dbi-upload-submit').removeAttr("disabled");
abort=true;
},
success: function( data ) {
if(abort) return; // Some other thread is in error, so we exit
console.log("slice "+slice_id+" ok after "+($.now()-start_time)+" msecs");
// Calculate progress and next offset
var size_done = start + slice_size;
var percent_done = Math.floor( ( size_done / file.size ) * 100 );
var real_next_slice=start + ((slice_size + 1)* concurrency);
if ( real_next_slice < file.size ) {
// Let's inform the user with the best upload % progress
if(percent < percent_done) percent=percent_done;
$( '#dbi-upload-progress' ).html( 'Sending file (' + percent + '%)' );
upload_file( real_next_slice ); // More to upload, call function recursively
} else {
// No more fragments to read
// if all fragments have been sent we commit the transaction
goals++;
if(goals==concurrency) {
$('#dbi-upload-progress').html( 'Completing upload...' );
commit_uploaded_file();
}
}
}
});
};
reader.readAsDataURL( blob );
}
Теперь последняя часть нашей jquery головоломки: вызов commit. В двух словах мы в последний раз призываем наш скрипт php собрать файл.
function commit_uploaded_file() {
var formData = new FormData();
formData.append("fileUpload", new Blob() , file.name);
formData.append("fileType", file.type);
var start_time=$.now();
console.log("start final commit @"+new Date(start_time));
$.ajax( {
url: "/myPhpFileUrl", type: 'POST', dataType: 'json', contentType: false,
cache: false, processData:false, data: formData,
error: function( data ) {
$( '#dbi-upload-progress' ).html( 'Upload failed' );
console.log( "Commit transaction ko after "+($.now()-start_time)+" msecs");
console.log( "Errore: "+data );
console.log( "End global transaction after "+ ($.now()-start_time_total)+" msecs");
$('#dbi-upload-submit').removeAttr("disabled");
},
success: function( data ) {
$( '#dbi-upload-progress' ).html( 'Upload completed' );
console.log( "Commit transaction ok after "+($.now()-start_time)+" msecs");
console.log( "End global transaction after "+ ($.now()-start_time_total)+" msecs");
$('#dbi-upload-submit').removeAttr("disabled");
}
});
}
Вот и все, ребята!
... ок, просто шучу; -)
Нам нужен файл myPhpFileUrl php: ветвь if / else для управления фазой инициализации, фазой загрузки фрагмента и фазой фиксации.
*1024*