HTML定义文件上传组件和上传按钮,使用了 Bootstrap ,不满意可以自己美化。
<form id="form1">
<div class="form-group">
<div class="custom-file"><input id="fileUpload" class="custom-file-input" type="file" />
<label class="custom-file-label" for="customFile">选择文件</label></div>
</div>
<div class="form-group"><label for="title">视频标题:</label>
<input id="title" class="form-control" maxlength="32" name="title" type="text" /></div>
<div class="form-group"><label for="description">视频描述:</label>
<input id="description" class="form-control" maxlength="32" name="description" type="text" /></div>
<div class="form-group"><label for="status">上传状态</label>
<input id="status" class="form-control" name="status" readonly="readonly" type="text" />
<div class="progress m-t-sm">
<div id="auth-progress" class="progress-bar progress-bar-striped progress-bar-animated" style="width: 0;">0%</div>
</div>
</div>
<button id="authUpload" class="btn btn-primary" disabled="disabled" type="button"><i class="fa fa-fw fa-upload"></i> Start Upload</button>
</form>
Javascript 脚本内容,这里使用了 jQuery 库,也完全可以使用原生代码或其他框架,没多大区别,自己高兴就好。
使用了 SparkMD5 库,目的是获取文件分片的md5值
<script type="text/javascript">
// 一个上传视频文件的例子
let allow_type = ["mp4","avi","mkv"]; // 定义允许上传的文件类型
var chunk_size = 1024 * 100; // 上传文件分片大小 100K
var slice_count = 0; // 文件分片块数
var slice_data = Array(); // 文件分片数据
var file_size = 0; // 文件大小
var file_name = "";
$('#fileUpload').on('change', function (e) {
var file = e.target.files[0];
if (!file) {
alert("请先选择需要上传的文件!");
return;
}
$("#title").val(file.name);
$('#authUpload').prop("disabled", false);
});
$('#authUpload').on('click', function () {
if ($("#title").val().length < 2) {
toastr.warning("视频标题填写太短!");
return false;
}
const file = $("#fileUpload")[0].files[0];
file_size = file.size;
file_name = file.name;
if (allow_type.indexOf(file_name.substr(file_name.lastIndexOf(".")+1).toLowerCase()) == -1) {
toastr.warning("只允许上传视频文件格式: mp4 avi mkv");
return;
}
var timestamp_start = new Date().getTime();
$('#authUpload').prop("disabled", true);
$("#status").val("开始处理本地文件 ...");
slice_count = Math.ceil(file.size / chunk_size);
const fileReader = new FileReader();
var index = 0;
const loadFile = () => {
var start = index === 0 ? 0 : index * chunk_size;
var end = start + chunk_size;
if (end > file_size) end = file_size;
//console.log(start + " / " + end);
const slice = file.slice(start, end);
fileReader.readAsArrayBuffer(slice);
}
loadFile();
fileReader.onload = e => {
//console.log(index + " / " + slice_count);
//console.log(e.target.result);
// 上传分片数据
const spark = new SparkMD5.ArrayBuffer();
spark.append(e.target.result)
upload_slice_data(index, e.target.result, spark.end(), function () {
index ++;
if (index >= slice_count) {
// 分片处理结束
$("#auth-progress").html("100%").css("width", "100%");
var timestamp_end = new Date().getTime();
$("#status").val("上传完成,消耗时间:" + ((timestamp_end - timestamp_start) / 1000).toFixed(3) + "秒");
} else {
loadFile();
}
});
};
});
// 开始分片上传,单线程上传,
// 如果需要多线程上传会麻烦一些,前后台都要有此小改动,需要的可以私聊交流。
function upload_slice_data(index, binrary, chunk_md5, callback) {
if (index > file_size) index = file_size
let file = new File([binrary], file_name);
file.type = "application/octet-stream";
let formData = new FormData();
formData.append("file", file);
formData.append("name", file_name);
formData.append("index", index);
formData.append("chunk_md5", chunk_md5);
formData.append("total_size", file_size);
formData.append("slice_count", slice_count);
formData.append("title", $("#title").val());
formData.append("description", $("#description").val());
$.ajax({
url: "resources_upload_chunk.php",
type: "POST",
timeout: 25000,
processData:false,
contentType: false,
data: formData,
error: function (xhr, textStatus) {
alert(textStatus);
},
success: function (data, textStatus, jqXHR) {
if (data.status === "error") {
alert(data.msg);
} else {
let current = (index + 1) * chunk_size;
if (current > file_size) current = file_size;
let progress = (current / file_size * 100).toFixed(2);
$("#status").val(current.toLocaleString() + " / " + file_size.toLocaleString());
$("#auth-progress").html(progress + "%").css("width", progress + "%");
callback();
}
}
});
}
</script>
后台 resources_upload_chunk.php 文件
<?php
$file_name = clear_string($_POST["name"] ?? "");
$index = intval($_POST["index"] ?? -1);
$slice_count = intval($_POST["slice_count"] ?? -1);
$total_size = intval($_POST["total_size"] ?? -1);
$chunk_md5 = clear_string($_POST["chunk_md5"] ?? "");
$upload_file = $_FILES["file"];
$md5 = md5_file($upload_file["tmp_name"]);
if ($md5 != $chunk_md5) ajax_result(AJAX_RETURN_TYPE::ERROR, "上传文件验证失败!");
$upload_tmp_path = dirname(__FILE__). "/tmp/upload/". md5(session_id(). $file_name);
// 上传第一块,清理临时文件
if ($index == 0 && file_exists($upload_tmp_path)) clear_upload_tmp();
if (!file_exists($upload_tmp_path)) mkdir($upload_tmp_path, 0755, true);
// 将上传的分片文件放到上传临时目录
move_uploaded_file($upload_file["tmp_name"], $upload_tmp_path . "/{$index}.part");
// 上传完所有分片后,组合文件。
// 因前端是单线程上传,index 是按顺序上传过来的
if ($index == $slice_count - 1) {
$ext = strrchr($file_name,'.');
$fp_target = fopen($upload_tmp_path. "/merge{$ext}", "wb");
for ($i=0; $i<$slice_count; $i++) {
$part_file = $upload_tmp_path. "/{$i}.part";
$fp_source = fopen($part_file, "rb");
$source = fread($fp_source, filesize($part_file));
fclose($fp_source);
fwrite($fp_target, $source);
unlink($part_file);
}
fclose($fp_target);
if (filesize($upload_tmp_path. "/merge{$ext}") == $total_size) {
ajax_result(AJAX_RETURN_TYPE::SUCCESS, "上传完成");
} else {
ajax_result(AJAX_RETURN_TYPE::ERROR, "上传文件验证失败");
}
} else {
// 未上传完成,通知客户端继续
ajax_result(AJAX_RETURN_TYPE::SUCCESS, "continue");
}
// 清除上传临时目录,这里自己去搞定,就是删除临时目录下所有文件
private function clear_upload_tmp() {
global $upload_tmp_path;
$this->file = __base_service::getInstance()->file();
$this->file->delete($upload_tmp_path);
}
/**
* 输出ajax提示
* @param string $status 返回状态,分为 error 和 successful 两种,或在前台javascript中自行判断
* @param string $msg 直接输出的json字符串
*/
function ajax_result($status="successful", $msg=""){
// 禁用缓存
header("Content-type: text/html; charset=utf-8");
header("Expires: Mon, 26 Jul 1970 05:00:00 GMT");
header("Last-Modified:" . gmdate("D, d M Y H:i:s") . "GMT");
header("Cache-Control:no-cache, must-revalidate");
header("Pragma:no-cache");
echo json_encode(array('status'=>$status, 'msg'=>$msg), JSON_UNESCAPED_UNICODE);
exit;
}
文章评论