255 lines
5.7 KiB
PHP
255 lines
5.7 KiB
PHP
<?php
|
|
|
|
namespace Dcat\Admin\Support;
|
|
|
|
use Illuminate\Http\Request;
|
|
use Symfony\Component\Finder\Finder;
|
|
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
|
|
|
/**
|
|
* WebUploader文件上传处理.
|
|
*
|
|
* @property string $_id
|
|
* @property int $chunk
|
|
* @property int $chunks
|
|
* @property string $upload_column
|
|
* @property UploadedFile $file
|
|
*/
|
|
class WebUploader
|
|
{
|
|
const FILE_NAME = '_file_';
|
|
|
|
public $temporaryDirectory = 'tmp';
|
|
|
|
protected $temporaryFilePath;
|
|
|
|
protected $completeFile;
|
|
|
|
public function __construct(Request $request = null)
|
|
{
|
|
$request = $this->prepareRequest($request ?: request());
|
|
|
|
$this->_id = $request->get('_id');
|
|
$this->chunk = $request->get('chunk');
|
|
$this->chunks = $request->get('chunks');
|
|
$this->upload_column = $request->get('upload_column');
|
|
$this->file = $request->file(static::FILE_NAME);
|
|
}
|
|
|
|
protected function prepareRequest($request)
|
|
{
|
|
$relation = $request->get('_relation');
|
|
if (! $relation || ! is_string($relation)) {
|
|
return $request;
|
|
}
|
|
|
|
return $request->merge([
|
|
'_relation' => trim($relation, ','),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* 判断是否是分块上传.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function hasChunkFile()
|
|
{
|
|
return $this->chunks > 1;
|
|
}
|
|
|
|
/**
|
|
* 判断是否是文件上传请求.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function isUploading()
|
|
{
|
|
$file = $this->file;
|
|
|
|
if (
|
|
! $file
|
|
|| ! $this->upload_column
|
|
|| ! $file instanceof UploadedFile
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* 获取完整的上传文件.
|
|
*
|
|
* @return UploadedFile|void
|
|
*/
|
|
public function getUploadedFile()
|
|
{
|
|
$file = $this->file;
|
|
|
|
if (! $file || ! $file instanceof UploadedFile) {
|
|
return;
|
|
}
|
|
|
|
if (! $this->hasChunkFile()) {
|
|
return $file;
|
|
}
|
|
|
|
if ($this->completeFile !== null) {
|
|
return $this->completeFile;
|
|
}
|
|
|
|
return $this->completeFile = $this->mergeChunks($file);
|
|
}
|
|
|
|
/**
|
|
* 移除临时文件以及文件夹.
|
|
*/
|
|
public function deleteTemporaryFile()
|
|
{
|
|
if (! $this->temporaryFilePath) {
|
|
return;
|
|
}
|
|
@unlink($this->temporaryFilePath);
|
|
|
|
if (
|
|
! Finder::create()
|
|
->in($dir = dirname($this->temporaryFilePath))
|
|
->files()
|
|
->count()
|
|
) {
|
|
@rmdir($dir);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 合并分块文件.
|
|
*
|
|
* @param UploadedFile $file
|
|
* @return UploadedFile|false
|
|
*/
|
|
protected function mergeChunks(UploadedFile $file)
|
|
{
|
|
$tmpDir = $this->getTemporaryPath($this->_id);
|
|
$newFilename = $this->generateChunkFileName($file);
|
|
|
|
// 移动当前分块到临时目录.
|
|
$this->moveChunk($file, $tmpDir, $newFilename);
|
|
|
|
// 判断所有分块是否上传完毕.
|
|
if (! $this->isComplete($tmpDir, $newFilename)) {
|
|
return false;
|
|
}
|
|
|
|
$this->temporaryFilePath = $tmpDir.'/'.$newFilename.'.tmp';
|
|
|
|
$this->putTempFileContent($this->temporaryFilePath, $tmpDir, $newFilename);
|
|
|
|
return new UploadedFile(
|
|
$this->temporaryFilePath,
|
|
$file->getClientOriginalName(),
|
|
null,
|
|
null,
|
|
true
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 判断所有分块是否上传完毕.
|
|
*
|
|
* @param string $tmpDir
|
|
* @param string $newFilename
|
|
* @return bool
|
|
*/
|
|
protected function isComplete($tmpDir, $newFilename)
|
|
{
|
|
for ($index = 0; $index < $this->chunks; $index++) {
|
|
if (! is_file("{$tmpDir}/{$newFilename}.{$index}.part")) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* 移动分块文件到临时目录.
|
|
*
|
|
* @param UploadedFile $file
|
|
* @param string $tmpDir
|
|
* @param string $newFilename
|
|
*/
|
|
protected function moveChunk(UploadedFile $file, $tmpDir, $newFilename)
|
|
{
|
|
$file->move($tmpDir, "{$newFilename}.{$this->chunk}.part");
|
|
}
|
|
|
|
/**
|
|
* @param string $path
|
|
* @param string $tmpDir
|
|
* @param string $newFilename
|
|
*/
|
|
protected function putTempFileContent($path, $tmpDir, $newFileame)
|
|
{
|
|
$out = fopen($path, 'wb');
|
|
|
|
if (flock($out, LOCK_EX)) {
|
|
for ($index = 0; $index < $this->chunks; $index++) {
|
|
$partPath = "{$tmpDir}/{$newFileame}.{$index}.part";
|
|
if (! $in = @fopen($partPath, 'rb')) {
|
|
break;
|
|
}
|
|
|
|
while ($buff = fread($in, 4096)) {
|
|
fwrite($out, $buff);
|
|
}
|
|
|
|
@fclose($in);
|
|
@unlink($partPath);
|
|
}
|
|
|
|
flock($out, LOCK_UN);
|
|
}
|
|
|
|
fclose($out);
|
|
}
|
|
|
|
/**
|
|
* 生成分块文件名称.
|
|
*
|
|
* @param UploadedFile $file
|
|
* @return string
|
|
*/
|
|
protected function generateChunkFileName(UploadedFile $file)
|
|
{
|
|
return md5($file->getClientOriginalName());
|
|
}
|
|
|
|
/**
|
|
* 获取临时文件路径.
|
|
*
|
|
* @param mixed $path
|
|
* @return string
|
|
*/
|
|
public function getTemporaryPath($path)
|
|
{
|
|
return $this->getTemporaryDirectory().'/'.$path;
|
|
}
|
|
|
|
/**
|
|
* 获取临时文件目录.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getTemporaryDirectory()
|
|
{
|
|
$dir = storage_path($this->temporaryDirectory);
|
|
|
|
if (! is_dir($dir)) {
|
|
app('files')->makeDirectory($dir, 0755, true);
|
|
}
|
|
|
|
return rtrim($dir, '/');
|
|
}
|
|
}
|