// +--------------------------------------------------------------------- namespace cmf\lib; use huo\model\agent\AssetModel; use think\File; /** * ThinkCMF上传类,分块上传 */ class Upload { private $request; private $error = false; private $fileType; private $formName = 'file'; public function __construct() { $this->request = request(); } public function getError() { return $this->error; } public function setFileType($fileType) { $this->fileType = $fileType; } public function setFormName($name) { $this->formName = $name; } public function upload($agent_id = 0, $to_file_path = '') { $uploadSetting = cmf_get_upload_setting(); $arrFileTypes = [ 'image' => ['title' => 'Image files', 'extensions' => $uploadSetting['file_types']['image']['extensions']], 'video' => ['title' => 'Video files', 'extensions' => $uploadSetting['file_types']['video']['extensions']], 'audio' => ['title' => 'Audio files', 'extensions' => $uploadSetting['file_types']['audio']['extensions']], 'file' => ['title' => 'Custom files', 'extensions' => $uploadSetting['file_types']['file']['extensions']] ]; $arrData = $this->request->param(); if (empty($arrData["filetype"])) { $arrData["filetype"] = "image"; } $fileType = $this->fileType; if (empty($this->fileType)) { $fileType = $arrData["filetype"]; } if (array_key_exists($arrData["filetype"], $arrFileTypes)) { $extensions = $uploadSetting['file_types'][$arrData["filetype"]]['extensions']; $fileTypeUploadMaxFileSize = $uploadSetting['file_types'][$fileType]['upload_max_filesize']; } else { $this->error = '上传文件类型配置错误!'; return false; } //$strPostMaxSize = ini_get("post_max_size"); //$strUploadMaxFileSize = ini_get("upload_max_filesize"); /** * 断点续传 need */ 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"); header("Access-Control-Allow-Origin: *"); // Support CORS if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') { // other CORS headers if any... exit; // finish preflight CORS requests here } @set_time_limit(24 * 60 * 60); $cleanupTargetDir = false; // Remove old files $maxFileAge = 5 * 3600; // Temp file age in seconds /** * 断点续传 end */ $app = $this->request->param('app'); if (empty($app) || !file_exists(APP_PATH.$app)) { $app = 'default'; } $fileImage = $this->request->file($this->formName); $originalName = $fileImage->getInfo('name'); $arrAllowedExtensions = explode(',', $arrFileTypes[$fileType]['extensions']); $strFileExtension = strtolower(cmf_get_file_extension($originalName)); if (!in_array($strFileExtension, $arrAllowedExtensions) || $strFileExtension == 'php') { $this->error = "非法文件类型!"; return false; } $fileUploadMaxFileSize = $uploadSetting['upload_max_filesize'][$strFileExtension]; $fileUploadMaxFileSize = empty($fileUploadMaxFileSize) ? 2097152 : $fileUploadMaxFileSize;//默认2M $strWebPath = "";//"upload" . DS; $strId = $this->request->post("id"); $strDate = date('Ymd'); $adminId = empty($agent_id) ? $agent_id : cmf_get_current_admin_id(); $userId = cmf_get_current_user_id(); $userId = empty($adminId) ? $userId : $adminId; $targetDir = RUNTIME_PATH."upload".DS.$userId.DS; // 断点续传 need if (!file_exists($targetDir)) { mkdir($targetDir, 0777, true); } /** * 断点续传 need */ $strFilePath = md5($originalName); $chunk = $this->request->param("chunk", 0, "intval"); $chunks = $this->request->param("chunks", 1, "intval"); if (!$fileImage->isValid()) { $this->error = "非法文件!"; return false; } if ($cleanupTargetDir) { if (!is_dir($targetDir) || !$dir = opendir($targetDir)) { $this->error = "Failed to open temp directory!"; return false; } while (($file = readdir($dir)) !== false) { $tmpFilePath = $targetDir.$file; if ($tmpFilePath == "{$strFilePath}_{$chunk}.part" || $tmpFilePath == "{$strFilePath}_{$chunk}.parttmp") { continue; } if (preg_match('/\.(part|parttmp)$/', $file) && (@filemtime($tmpFilePath) < time() - $maxFileAge)) { @unlink($tmpFilePath); } } closedir($dir); } // Open temp file if (!$out = @fopen($targetDir."{$strFilePath}_{$chunk}.parttmp", "wb")) { $this->error = "上传文件临时目录不可写".$targetDir; return false; } // Read binary input stream and append it to temp file if (!$in = @fopen($fileImage->getInfo("tmp_name"), "rb")) { $this->error = "Failed to open input stream!"; return false; } while ($buff = fread($in, 4096)) { fwrite($out, $buff); } @fclose($out); @fclose($in); rename($targetDir."{$strFilePath}_{$chunk}.parttmp", $targetDir."{$strFilePath}_{$chunk}.part"); $done = true; for ($index = 0; $index < $chunks; $index++) { if (!file_exists($targetDir."{$strFilePath}_{$index}.part")) { $done = false; break; } } if (!$done) { die('');//分片没上传完 } $fileSaveName = (empty($app) ? '' : $app.'/').$strDate.'/'.md5(uniqid()).".".$strFileExtension; $strSaveFilePath = CMF_ROOT.'public/upload/'.$fileSaveName; //TODO 测试 windows 下 $strSaveFileDir = dirname($strSaveFilePath); if (!file_exists($strSaveFileDir)) { mkdir($strSaveFileDir, 0777, true); } // 合并临时文件 if (!$out = @fopen($strSaveFilePath, "wb")) { $this->error = "上传目录不可写"; return false; } if (flock($out, LOCK_EX)) { for ($index = 0; $index < $chunks; $index++) { if (!$in = @fopen($targetDir."{$strFilePath}_{$index}.part", "rb")) { break; } while ($buff = fread($in, 4096)) { fwrite($out, $buff); } fclose($in); unlink("{$targetDir}{$strFilePath}_{$index}.part"); } flock($out, LOCK_UN); } @fclose($out); $fileImage = new File($strSaveFilePath, 'r'); $arrInfo = [ "name" => $originalName, "type" => $fileImage->getMime(), "tmp_name" => $strSaveFilePath, "error" => 0, "size" => $fileImage->getSize(), ]; $fileImage->setSaveName($fileSaveName); $fileImage->setUploadInfo($arrInfo); /** * 断点续传 end */ if (!$fileImage->validate(['size' => $fileUploadMaxFileSize])->check()) { $error = $fileImage->getError(); unset($fileImage); unlink($strSaveFilePath); $this->error = $error; return false; } // $url=$first['url']; $storageSetting = cmf_get_cmf_settings('storage'); $qiniuSetting = $storageSetting['Qiniu']['setting']; //$url=preg_replace('/^https/', $qiniu_setting['protocol'], $url); //$url=preg_replace('/^http/', $qiniu_setting['protocol'], $url); $arrInfo = []; if (config('FILE_UPLOAD_TYPE') == 'Qiniu' && $qiniuSetting['enable_picture_protect']) { //todo qiniu code ... // $previewUrl = $url.$qiniuSetting['style_separator'].$qiniuSetting['styles']['thumbnail300x300']; // $url= $url.$qiniuSetting['style_separator'].$qiniuSetting['styles']['watermark']; } else { if (empty($fileImage)) { $this->error = $fileImage->getError(); return false; } else { $arrInfo["user_id"] = $userId; $arrInfo["file_size"] = $fileImage->getSize(); $arrInfo["create_time"] = time(); $arrInfo["file_md5"] = md5_file($strSaveFilePath); $arrInfo["file_sha1"] = sha1_file($strSaveFilePath); $arrInfo["file_key"] = $arrInfo["file_md5"].md5($arrInfo["file_sha1"]); $arrInfo["filename"] = $fileImage->getInfo("name"); $arrInfo["file_path"] = $strWebPath.$fileSaveName; $arrInfo["suffix"] = $fileImage->getExtension(); } } /* favicon.ico 图标上传特殊处理 名称为favicon.ico 并且应用为admin 替换 */ if ('favicon.ico' == $fileImage->getInfo('name') && 'admin' == $app) { @copy($strSaveFilePath, CMF_ROOT.'public/admin/favicon.ico'); @copy($strSaveFilePath, CMF_ROOT.'public/h5/favicon.ico'); @copy($strSaveFilePath, CMF_ROOT.'public/h5m/favicon.ico'); @copy($strSaveFilePath, CMF_ROOT.'public/agent/favicon.ico'); @copy($strSaveFilePath, CMF_ROOT.'public/mobile/favicon.ico'); @copy($strSaveFilePath, CMF_ROOT.'public/web/favicon.ico'); @rename($strSaveFilePath, './favicon.ico'); $fileImage = null; return [ 'filepath' => '/favicon.ico', "name" => 'favicon.ico', 'id' => $strId, 'preview_url' => '/favicon.ico?'.rand(1, 9999), 'url' => '/favicon.ico', ]; } /* icon-logo.png 图标上传特殊处理,SDK浮点图标 名称为icon-logo.png 并且应用为admin 替换 */ if ('icon-logo.png' == $fileImage->getInfo('name') && 'admin' == $app) { @copy($strSaveFilePath, CMF_ROOT.'public/static/h5sdk/images/icon-logo.png'); @rename($strSaveFilePath, CMF_ROOT.'public/static/sdk/images/icon-logo.png'); $fileImage = null; return [ 'filepath' => '/h5sdk/images/icon-logo.png', "name" => 'icon-logo.png', 'id' => $strId, 'preview_url' => STATICSITE.'/h5sdk/images/icon-logo.png?'.rand(1, 9999), 'url' => '/h5sdk/images/icon-logo.png', ]; } //关闭文件对象 $fileImage = null; //检查文件是否已经存在 $assetModel = new AssetModel(); $objAsset = $assetModel->where(["user_id" => $userId, "file_key" => $arrInfo["file_key"]])->find(); $storage = cmf_get_option('storage'); if (empty($storage['type'])) { $storage['type'] = 'Local'; } $needUploadToRemoteStorage = false;//是否要上传到云存储 if ($objAsset && $storage['type'] == 'Local') { $arrAsset = $objAsset->toArray(); //$arrInfo["url"] = $this->request->domain() . $arrAsset["file_path"]; $arrInfo["file_path"] = $arrAsset["file_path"]; if (file_exists(CMF_ROOT.'public/upload/'.$arrInfo["file_path"])) { @unlink($strSaveFilePath); // 删除已经上传的文件 } else { $oldFileDir = dirname(CMF_ROOT.'public/upload/'.$arrInfo["file_path"]); if (!file_exists($oldFileDir)) { mkdir($oldFileDir, 0777, true); } if (!empty($to_file_path)) { @rename($strSaveFilePath, $to_file_path); } else { @rename($strSaveFilePath, CMF_ROOT.'public/upload/'.$arrInfo["file_path"]); } } } else { $needUploadToRemoteStorage = true; $assetModel->data($arrInfo)->allowField(true)->save(); } //删除临时文件 // for ($index = 0; $index < $chunks; $index++) { // // echo $targetDir . "{$strFilePath}_{$index}.part"; // @unlink($targetDir . "{$strFilePath}_{$index}.part"); // } @rmdir($targetDir); if ($storage['type'] != 'Local') { // 增加存储驱动 $watermark = cmf_get_plugin_config($storage['type']); $storage = new Storage($storage['type'], $storage['storages'][$storage['type']]); if ($needUploadToRemoteStorage) { session_write_close(); $result = $storage->upload($arrInfo["file_path"], CMF_ROOT.'public/upload/'.$arrInfo["file_path"], $fileType); if (!empty($result)) { return array_merge( [ 'filepath' => $arrInfo["file_path"], "name" => $arrInfo["filename"], "file_md5" => $arrInfo["file_md5"], 'id' => $strId, 'preview_url' => STATICSITE.'/upload/'.$arrInfo["file_path"], 'url' => STATICSITE.'/upload/'.$arrInfo["file_path"], ], $result ); } } else { $previewUrl = $fileType == 'image' ? $storage->getPreviewUrl($arrInfo["file_path"]) : $storage->getFileDownloadUrl($arrInfo["file_path"]); $url = $fileType == 'image' ? $storage->getImageUrl( $arrInfo["file_path"], $watermark['styles_watermark'] ) : $storage->getFileDownloadUrl($arrInfo["file_path"]); //测试ing return [ 'filepath' => $arrInfo["file_path"], "name" => $arrInfo["filename"], "file_md5" => $arrInfo["file_md5"], 'id' => $strId, 'preview_url' => $previewUrl, 'url' => $url, ]; } } return [ 'filepath' => $arrInfo["file_path"], "name" => $arrInfo["filename"], "file_md5" => $arrInfo["file_md5"], 'id' => $strId, 'preview_url' => STATICSITE.'/upload/'.$arrInfo["file_path"], 'url' => STATICSITE.'/upload/'.$arrInfo["file_path"], ]; } }