介绍:在项目开发的过程中有时候会用到上传大文件或者视频的方法,下面介绍两种大文件上传的方法。
1.Thinkphp5框架中采用前后端不分离的同源模式,前端使用 webuploader 分片上传的方法,后端使用分片上传。
public function maxFileUpload()
{
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
json(['code' => -1, 'msg' => '使用get或post方法上传']) -> send();
exit;
}
if (!empty($_REQUEST['debug'])) {
$random = rand(0, intval($_REQUEST['debug']));
if ($random === 0) {
json(['code' => -1, 'msg' => '上传失败']) -> send();
exit;
}
}
@set_time_limit(3600);
$path = './uploads/max_file';
$targetDir = $path . '\\' . 'file_tmp';
$uploadDir = $path;
$cleanupTargetDir = true;
$maxFileAge = 20 * 3600;
if (!is_dir($targetDir)) {
mkdir($targetDir, 0777, true);
}
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0777, true);
}
if (isset($_REQUEST['name'])) {
$fileName = $_REQUEST['name'];
} else if (!empty($_FILES)) {
$fileName = $_FILES['file']['name'];
} else {
$fileName = uniqid('file_');
}
$oldName = $fileName;
$change_file_info = pathinfo($fileName);
$extension = $change_file_info['extension'];
$fileName = md5(basename($fileName, '.' . $extension));
$filePath = $targetDir . DIRECTORY_SEPARATOR . $fileName . '.' . $extension;
$chunk = isset($_REQUEST['chunk']) ? intval($_REQUEST['chunk']) : 0;
$chunks = isset($_REQUEST['chunks']) ? intval($_REQUEST['chunks']) : 1;
if ($cleanupTargetDir) {
if (!is_dir($targetDir) || !$dir = opendir($targetDir)) {
json(['code' => -1, 'msg' => '上传失败']) -> send();
exit;
}
while (($file = readdir($dir)) !== false) {
$tmpfilePath = $targetDir . DIRECTORY_SEPARATOR . $file;
if ($tmpfilePath == "{$filePath}_{$chunk}.part" || $tmpfilePath == "{$filePath}_{$chunk}.parttmp") {
continue;
}
if (preg_match('/\.(part|parttmp)$/', $file) && (@filemtime($tmpfilePath) < time() - $maxFileAge)) {
@unlink($tmpfilePath);
}
}
closedir($dir);
}
if (!$out = @fopen("{$filePath}_{$chunk}.parttmp", "wb")) {
json(['code' => -1, 'msg' => '上传失败']) -> send();
exit;
}
if (!empty($_FILES)) {
if ($_FILES["file"]["error"] || !is_uploaded_file($_FILES["file"]["tmp_name"])) {
json(['code' => -1, 'msg' => '上传失败']) -> send();
exit;
}
if (!$in = @fopen($_FILES["file"]["tmp_name"], "rb")) {
json(['code' => -1, 'msg' => '上传失败']) -> send();
exit;
}
} else {
if (!$in = @fopen("php://input", "rb")) {
json(['code' => -1, 'msg' => '上传失败']) -> send();
exit;
}
}
while ($buff = fread($in, 4096)) {
fwrite($out, $buff);
}
@fclose($out);
@fclose($in);
rename("{$filePath}_{$chunk}.parttmp", "{$filePath}_{$chunk}.part");
$done = true;
for ($index = 0; $index < $chunks; $index++) {
if (!file_exists("{$filePath}_{$index}.part")) {
$done = false;
break;
}
}
if ($done) {
$uploadPath = $uploadDir . DIRECTORY_SEPARATOR . $fileName . "." . $extension;
if (!$out = @fopen($uploadPath, "wb")) {
json(['code' => -1, 'msg' => '上传失败']) -> send();
exit;
}
if (flock($out, LOCK_EX)) {
for ($index = 0; $index < $chunks; $index++) {
if (!$in = @fopen("{$filePath}_{$index}.part", "rb")) {
break;
}
while ($buff = fread($in, 4096)) {
fwrite($out, $buff);
}
@fclose($in);
@unlink("{$filePath}_{$index}.part");
}
flock($out, LOCK_UN);
}
@fclose($out);
$response = [
'success' => true,
'oldName' => $oldName,
"newName" => $fileName . "." . $extension,
'fileSuffixes' => $extension,
'path' => $path
];
@rmdir($targetDir);
return json($response);
} else {
json(['code' => 1, 'msg' => '分片上传成功']) -> send();
}
}
2.前后端分离的方式,前端采用 react,后端采用 Thinkphp5。
public function maxFileUpload()
{
@set_time_limit(3600);
header_public();
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
exit;
}
$param = input('param.');
$chunk = $param['chunk'] ?? 0;
$chunk_count = $param['chunk_count'] ?? 1;
$file_name = $param['file_name'] ?? '';
$file_temp_name = $param['file_temp_name'] ?? '';
$path = ROOT_PATH . 'public' . DS . 'uploads' . DS . 'max_file';
$date_dir = date('Ymd');
$targetDir = $path . DS . 'patrol_tmp' . DS;
$uploadDir = $path . DS . 'patrol' . DS . $date_dir;
$this->mkdir($targetDir);
$this->mkdir($uploadDir);
if ($chunk !== $chunk_count) {
$file = request()->file('file');
$file->move($targetDir, "{$file_temp_name}.{$chunk}");
return json([
'code' => 1,
'done' => false,
'msg' => "切片{$chunk}上传成功",
]);
}
$fileName_arr = pathinfo($file_name);
$finalFileName = $fileName_arr['filename'] . date("YmdHis") . "." . $fileName_arr['extension'];
$uploadPath = $uploadDir . DIRECTORY_SEPARATOR . $finalFileName;
$out = @fopen($uploadPath, "wb");
if (flock($out, LOCK_EX)) {
for ($index = 0; $index < $chunk_count; $index++) {
$tmp_file = "{$targetDir}{$file_temp_name}.{$index}";
if (!$in = @fopen($tmp_file, "rb")) {
return json([
'code' => -1,
'done' => false,
'msg' => "切片{$index}号,没有上传",
]);
}
while ($buff = fread($in, 4096)) {
fwrite($out, $buff);
}
@fclose($in);
@unlink($tmp_file);
}
flock($out, LOCK_UN);
}
@fclose($out);
$this->deleteOldFile($targetDir);
$pathInfo = pathinfo($finalFileName);
$new_oldName = $pathInfo["filename"];
$filepath = str_replace(ROOT_PATH . 'public', '', $uploadPath);
$response = [
'code' => 1,
'done' => true,
'oldName' => $new_oldName,
'filePath' => $filepath,
'src' => $filepath,
'fileSuffixes' => $pathInfo['extension'],
'data'=>$data,
];
return json($response);
}
3.附上封装的公用的 header 头跨域方法。
if (!function_exists('header_public')) {
function header_public($headers = 'X-Requested-With,key,token,content-type')
{
ob_clean();
header('Access-Control-Allow-Origin:*');
header('Access-Control-Allow-Methods:OPTIONS, GET, POST');
header("Access-Control-Allow-Headers:{$headers}");
}
}