reset(); $this->urlBase = ''; if (!empty($maxSize) && is_numeric($maxSize)) { $this->maxSize = $maxSize; } if ($allowExt) { if (is_array($allowExt)) { $this->allowExt = array_map('strtolower', $allowExt); } else { $this->allowExt = explode(',', strtolower($allowExt)); } } if ($allowMime) { if (is_array($allowMime)) { $this->allowMime = $allowMime; } else { $this->allowMime = explode(',', $allowMime); } } if ($savePath != '') { $this->savePath = $savePath; } else { $this->savePath = ''; } if ($saveRootPath) { $this->saveRootPath = rtrim($saveRootPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; } if ($renameRule) { $this->renameRule = $renameRule; } $imageServersConf = config('image_servers'); $isEncode = $imageServersConf['servers'][$imageServersConf['upload_server_no']]['is_encode'] ?? false; $this->isEncode = $isEncode; $this->setServerNo($imageServersConf['upload_server_no']); } public function setServerNo($number) { $this->serverNo = str_pad((string)$number, 3, '0', STR_PAD_LEFT); } public function getServerNo() { return $this->serverNo; } public function setIsEncode($isEncode) { $this->isEncode = $isEncode; } public function getIsEncode() { return $this->isEncode; } /** * 重设参数 */ public function reset() { $this->urlBase = ''; $this->maxSize = -1; $this->allowExt = array(); $this->savePath = ''; $this->thumb = false; $this->thumbMaxWidth = array(); $this->thumbMaxHeight = array(); $this->thumbAppointed = array(); $this->thumbWater = array(); $this->thumbPath = array(); $this->thumbFileName = array(); $this->thumbPrefix = 't_'; $this->thumbSuffix = ''; $this->waterType = 'picture'; $this->waterPicture = ''; $this->waterText = ''; $this->waterFont = ''; $this->waterColor = ''; $this->waterPosition = ''; $this->subDir = ''; $this->savePath = ''; $this->dirDateFormat = 'Ym'; $this->renameRule = ''; $this->saveRootPath = ''; $this->saveRemote = false; $this->remoteServer = array(); $this->error = ''; $this->unlinkOriginPic = true; } /** * 保存 * @param UploadedFile $file 文件对象 * @param string $save_dir 保存路径 * @return array|bool */ private function save($file, $save_dir) { $arr_return = array(); $file_ext = strtolower($file->getClientOriginalExtension()); $file_mime = $file->getMimeType(); $file_origin_name = $file->getClientOriginalName(); $file_base_name = $file->getBasename('.' . $file_ext); $arr_file_names = $this->getSaveName($file); $file_name = $arr_file_names['save_name']; $file_origin_tmp_name = $arr_file_names['save_origin_name']; $file_old_size = $file->getSize(); $is_pic = false; if (in_array($file_ext, array('gif', 'jpg', 'jpeg', 'png', 'swf')) && false === getimagesize($file->getPathname())) { $this->error = '非法图像文件'; return false; } $file->move($save_dir, $file_origin_tmp_name); $file_native_path = $save_dir . $file_origin_tmp_name; if (in_array($file_ext, $this->picExt)) { // 图片类 $is_pic = true; if ($this->thumb) { $native_image = getimagesize($file_native_path); if (false !== $native_image) { $water_limit_width = 300; $water_limit_height = 300; $water_pic_width = $water_pic_height = 0; if ($this->waterType == 'picture' && !empty($this->waterPicture)) { $arr_water_pic_position = array( '1' => 'top-left', '2' => 'top', '3' => 'top-right', '4' => 'left', '5' => 'center', '6' => 'right', '7' => 'bottom-left', '8' => 'bottom', '9' => 'bottom-right' ); if ($this->waterPosition >= 1 && $this->waterPosition <= 9) { $water_pic_position = $arr_water_pic_position[$this->waterPosition]; } else { $water_rand_key = mt_rand(1, 9); $water_pic_position = $arr_water_pic_position[$water_rand_key]; } $water_image = getimagesize($this->waterPicture); if ($water_image !== false) { $water_pic_width = $water_image[0]; $water_pic_height = $water_image[1]; if ($water_pic_width) { $water_limit_width = $water_pic_width * 2; } if ($water_pic_height) { $water_limit_height = $water_pic_height * 2; } } } $count_thumb = count($this->thumbMaxWidth); for ($i = 0, $len = $count_thumb; $i < $len; $i++) { $img = null; $arr_img_path = $arr_img_relative_path = array(); $name_prefix = $count_thumb > 1 ? $this->thumbPrefix : ''; $name_suffix = $count_thumb > 1 ? $this->thumbSuffix : ''; $name_suffix_new = $count_thumb > 1 ? $name_suffix . '_' . ($i + 1) : $name_suffix; $fileThumbBaseNameNoExt = $name_prefix . substr($file_name, 0, strrpos($file_name, '.')) . $name_suffix_new; $file_thumb_bame = $fileThumbBaseNameNoExt . '.' . $file_ext; $arr_img_path[$i] = $save_dir . $file_thumb_bame; $arr_img_relative_path[$i] = $this->saveRelativePath . $file_thumb_bame; $tmp_file_path = $save_dir . 'tmp_' . $i . '_' . $file_name; File::copy($file_native_path, $tmp_file_path); if ($this->thumbMaxWidth[$i] > 0 && $this->thumbMaxHeight[$i] > 0) { if ($this->thumbAppointed[$i]) { $img = Image::make($tmp_file_path)->resize( $this->thumbMaxWidth[$i], $this->thumbMaxHeight[$i], function ($constraint) { $constraint->upsize(); } ); } else { $img = Image::make($tmp_file_path)->resize( $this->thumbMaxWidth[$i], $this->thumbMaxHeight[$i], function ($constraint) { $constraint->aspectRatio(); } ); } } else { $img = Image::make($tmp_file_path); } if ($this->thumbWater[$i] && $this->waterType == 'picture' && !empty($this->waterPicture)) { $native_thumb_image = getimagesize($tmp_file_path); if ($native_thumb_image[0] > $water_limit_width && $native_thumb_image[1] > $water_limit_height) { $img->insert($this->waterPicture, 0, 0, $water_pic_position); } } $img->save($arr_img_path[$i]); $file_thumb_size = File::size($arr_img_path[$i]); $thumb_native_image = getimagesize($arr_img_path[$i]); $thumb_native_width = $thumb_native_image[0]; $thumb_native_height = $thumb_native_image[1]; File::delete($tmp_file_path); if ($this->isEncode) { $fileEncodePath = $fileThumbBaseNameNoExt . '.ceb'; $encodeSavePath = $save_dir . $fileEncodePath; $this->getImageRepo()->encode(file_get_contents($arr_img_path[$i]), $encodeSavePath); $arr_img_relative_path[$i] = $this->saveRelativePath . $fileEncodePath; File::delete($arr_img_path[$i]); // 删除未加密的图 } $arr_temp = array( 'file_origin_name' => $file_origin_name, 'file_name' => $file_thumb_bame, 'file_size' => $file_thumb_size, 'file_path' => $arr_img_relative_path[$i], 'file_ext' => $file_ext, 'file_mime' => $file_mime, 'width' => $thumb_native_width, 'height' => $thumb_native_height, 'link_url' => $this->urlBase . $arr_img_relative_path[$i], 'is_pic' => 1, 'origin_pic_relative_path' => $this->saveRelativePath . $file_origin_tmp_name, 'origin_pic_native_path' => $file_native_path, //返回本地原图 'origin_pic_size' => $file_old_size, 'origin_pic_name' => $file_origin_tmp_name, 'origin_pic_width' => $native_image[0], 'origin_pic_height' => $native_image[1], 'file_native_path' => $save_dir . $file_thumb_bame, ); $arr_return[] = $arr_temp; $img->destroy(); } if ($this->unlinkOriginPic == true) { File::delete($file_native_path); //删除本地原图 } } } else { // 不裁剪或者水印 $fileNameNoExt = substr($file_name, 0, strrpos($file_name, '.')); $file_relative_path = $this->saveRelativePath . $file_name; $saveNewPath = $save_dir. $file_name; @rename($file_native_path, $saveNewPath); if ($this->isEncode) { $fileEncodePath = $fileNameNoExt . '.ceb'; $encodeSavePath = $save_dir . $fileEncodePath; $this->getImageRepo()->encode(file_get_contents($saveNewPath), $encodeSavePath); $file_relative_path = $this->saveRelativePath . $fileEncodePath; File::delete($saveNewPath); // 删除未加密的图 } $arr_temp = array( 'file_origin_name' => $file_origin_name, 'file_name' => $file_name, 'file_size' => $file_old_size, 'file_path' => $file_relative_path, 'file_ext' => $file_ext, 'file_mime' => $file_mime, 'width' => 0, 'height' => 0, 'link_url' => $this->urlBase . $file_relative_path, 'is_pic' => 1, 'origin_pic_relative_path' => '', 'origin_pic_native_path' => '', //返回本地原图 'origin_pic_size' => '', 'origin_pic_name' => '', 'origin_pic_width' => 0, 'origin_pic_height' => 0, 'file_native_path' => $save_dir . $file_name, ); $arr_return[] = $arr_temp; } } else { // 非图片类. $file_relative_path = $this->saveRelativePath . $file_name; @rename($save_dir . $file_origin_tmp_name, $save_dir . $file_name); $arr_temp = array( 'file_origin_name' => $file_origin_name, 'file_name' => $file_name, 'file_size' => $file_old_size, 'file_path' => $file_relative_path, 'file_ext' => $file_ext, 'file_mime' => $file_mime, 'width' => 0, 'height' => 0, 'link_url' => $this->urlBase . $file_relative_path, 'is_pic' => 0, 'origin_pic_relative_path' => '', 'origin_pic_native_path' => '', //返回本地原图 'origin_pic_size' => '', 'origin_pic_name' => '', 'origin_pic_width' => 0, 'origin_pic_height' => 0, 'file_native_path' => $save_dir . $file_name, ); $arr_return[] = $arr_temp; } return $arr_return; } /** * 获得保存的目录 * @return string */ private function getSaveDir() { $dir = ''; if (!empty($this->savePath) && $this->savePath != '/') { $dir = trim($this->savePath, '/') . '/'; } $dir .= $this->getServerNo() . '/'; if (!empty($this->subDir) && $this->subDir != '/') { $dir .= trim($this->subDir, '/') . '/'; } $dateEncodeName = caesar_encode(Carbon::now()->format($this->dirDateFormat), '', $this->caesarOffset); $dir_date = $dateEncodeName . '/'; $dir .= $dir_date; $this->saveRelativePath = $dir; $dir = $this->saveRootPath . $dir; if (!File::isDirectory($dir)) { File::makeDirectory($dir, 0777, true, true); } return $dir; } /** * 开始上传 * @param string $file_field 文件域 * @return bool|array */ public function upload($file_field) { $request = request(); $save_dir = $this->getSaveDir(); $file_info = array(); if ($request->exists('chunk') && $request->exists('chunks')) { $result = self::chunkInputSave($file_field); if ($result != false) { $file_info[] = $result; } else { return false; } } else { if ($request->hasFile($file_field)) { $files = $this->dealFiles($file_field); foreach ($files as $key => $file) { /** * @var UploadedFile $file */ if (!$this->check($file)) { return false; } if ($file->getClientOriginalName()) { //$_FILES['xxx']['name'] if ($request->exists('name') && $request->exists('chunk') && $request->exists('chunks')) { $result = self::chunkInputSave($file_field); } else { $result = $this->save($file, $save_dir); } if ($result) { $file_info[] = $result; } else { return false; } } } } else { $this->error = '非法请求'; return false; } } if ($file_info) { $this->uploadFileInfo = $file_info; return true; } else { $this->error = '没有选择上传文件'; return false; } } public function getResultFileInfo() { return $this->uploadFileInfo ? $this->uploadFileInfo : array(); } public function getResultError() { return $this->error ? $this->error : ''; } private function chunkInputSave($file_field) { $request = request(); if ($request->getMethod() != 'POST') { $this->error = '请求无效'; return false; } if (!($request->exists('chunk') && $request->exists('chunks'))) { $this->error = '请求无效'; return false; } if ($request->exists('name')) { $file_origin_name = $file_name = trim($request->get('name', '')); } elseif ($request->hasFile($file_field)) { $file_origin_name = $file_name = $_FILES[$file_field]['name']; } else { $file_origin_name = $file_name = uniqid('tmp_ck_'); } $file_name = md5($file_name); $chunk = intval($request->get('chunk', 0)); $chunks = intval($request->get('chunks', 0)); $arr_return = array(); $arr_file_info = array(); $is_pic = 0; $width = 0; $height = 0; $file_size = 0; $file_ext = file_ext($file_origin_name); //扩展名 if (!self::checkMime($file_ext)) { $this->error = '上传文件MIME类型不允许!'; return false; } if (!self::checkExtension($file_ext)) { $this->error = '上传文件类型不允许'; return false; } $arr_mimes = include(storage_path('app/data') . DIRECTORY_SEPARATOR . 'mime.php'); $file_mime = isset($arr_mimes[$file_ext]) ? $arr_mimes[$file_ext] : 'text/plain'; //文件MIME $tmp_save_path = storage_path('data') . DIRECTORY_SEPARATOR . $this->tmpChunkDir; //临时保存目录路径 //临时文件不存在则强制创建 if (!File::exists($tmp_save_path)) { File::makeDirectory($tmp_save_path, 0777, true, true); } $tmp_file_name = $file_name . '.part'; //临时文件名 $tmp_file_path = $tmp_save_path . DIRECTORY_SEPARATOR . $tmp_file_name; //完整临时文件路径 $file_tmp_full_name = 'ck_' . $file_origin_name; $file_tmp_path = $tmp_save_path . DIRECTORY_SEPARATOR . $tmp_file_name; //清理过期分块文件 self::chunkCleanupDir($file_tmp_path); $err_msg = ''; if ($out = @fopen($file_tmp_path, $chunk ? 'ab' : 'wb')) { if ($request->hasFile($file_field)) { $in = @fopen($_FILES[$file_field]["tmp_name"], "rb"); } else { $in = @fopen("php://input", 'rb'); } if ($in) { while ($buff = fread($in, 4096)) { fwrite($out, $buff); } } else { $err_msg = '无法读取文件'; } @fclose($in); } else { $err_msg = '无法打开文件'; } @fclose($out); if ($err_msg) { $this->error = $err_msg; return false; } if (empty($chunks) || $chunk == $chunks - 1) { $save_dir = $this->getSaveDir(); //保存目录路径 $rd_name = self::makeFileTimeName(); //产生一个随机文件名 $file_full_name = 'ck_' . $rd_name . '.' . $file_ext; $file_path = $save_dir . DIRECTORY_SEPARATOR . $file_full_name; $file_relative_path = $this->saveRelativePath . $file_full_name; if (!File::move($file_tmp_path, $file_path)) { $this->error = '无法移动文件'; return false; } $file_size = File::size($file_path); //获取文件大小 if (in_array($file_ext, array('jpg', 'jpeg', 'gif', 'png', 'psd'))) { $is_pic = 1; $img = getimagesize($file_path); if ($img !== false) { $width = $img[0]; $height = $img[1]; } } $arr_file_info = array( 'file_origin_name' => $file_origin_name, 'file_name' => $file_full_name, 'file_size' => $file_size, 'file_path' => $file_relative_path, 'file_ext' => $file_ext, 'file_mime' => $file_mime, 'width' => $width, 'height' => $height, 'link_url' => $this->urlBase . $file_relative_path, 'is_pic' => $is_pic, 'origin_pic_relative_path' => $file_relative_path, 'origin_pic_native_path' => $file_path, //返回本地原图 ); } $arr_return[] = $arr_file_info; return $arr_return; } private function chunkCleanupDir($file_path) { $max_file_age = 5 * 60 * 60; //5小时 $tmp_save_path = storage_path('app/data') . DIRECTORY_SEPARATOR . $this->tmpChunkDir; $dir = opendir($tmp_save_path); if ($dir) { $now_time = Carbon::now()->timestamp; while (($file = readdir($dir)) !== false) { $tmp_file_path = $tmp_save_path . DIRECTORY_SEPARATOR . $file; if ($tmp_file_path == $file_path) { continue; } if (preg_match('/\.part$/', $file) && filemtime($tmp_file_path) < $now_time - $max_file_age) { File::delete($tmp_file_path); } } } closedir($dir); } /** * 分配多文件上传 * @param string $file_field 上传域 * @return array */ private function dealFiles($file_field) { $request = request(); $arr_files = array(); if (is_array($request->file($file_field))) { foreach ($request->file($file_field) as $file) { $arr_files[] = $file; } } else { $arr_files[] = $request->file($file_field); } return $arr_files; } /** * 获取保存后的文件名,不包含扩展名 * @param UploadedFile $file 文件对象 * @return array */ private function getSaveName($file) { $rd_name = self::makeFileTimeName(); $r = array( 'save_name' => '', 'save_origin_name' => '', ); if (empty($this->renameRule)) { $save_name = $rd_name . '.' . $file->getClientOriginalExtension(); } else { if (method_exists($this, $this->renameRule)) { $rule_name = $this->renameRule; $rd_name = $rule_name(); } else { $save_name = self::makeFileTimeName() . '.' . $file->getClientOriginalExtension(); } } return array( 'save_name' => $rd_name . '.' . $file->getClientOriginalExtension(), 'save_origin_name' => 'original_' . $rd_name . '.' . $file->getClientOriginalExtension(), ); } /** * 产生一个时间格式的文件名 * @return string */ private function makeFileTimeName() { $dateDir = caesar_encode(Carbon::now()->format('Ymd'), '', $this->caesarOffset); return $this->getServerNo() . rand_str(9); } /** * 常规检查 * @param UploadedFile $file 文件对象 * @return bool */ private function check($file) { if ($file->getError()) { $this->error = $file->getError(); return false; } if (!self::checkMime($file->getMimeType())) { $this->error = '上传文件MIME类型不允许!'; return false; } if (!self::checkExtension($file->getClientOriginalExtension())) { $this->error = '上传文件类型不允许'; return false; } if (!self::checkSize($file->getSize())) { $this->error = '上传文件大小超出限制!'; return false; } return true; } /** * 检查MIME * @param string $mime Mime * @return bool */ private function checkMime($mime) { if ($this->allowMime) { return in_array($mime, $this->allowMime); } else { return true; } } /** * 检查扩展名 * @param string $ext 扩展名 * @return bool */ private function checkExtension($ext) { if ($this->allowExt) { return in_array($ext, $this->allowExt); } else { return true; } } /** * 检查尺寸 * @param int $size 尺寸大小 * @return bool */ private function checkSize($size) { return !($size > $this->maxSize) || (-1 == $this->maxSize); } /** * 获取图像仓库. * @return ImageCryptRepository */ private function getImageRepo() { if (!$this->imageRepo instanceof ImageCryptRepository) { $this->imageRepo = new ImageCryptRepository(); } return $this->imageRepo; } }