338 lines
11 KiB
PHP
338 lines
11 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
namespace Builder;
|
|
use Hyperf\Di\Annotation\Inject;
|
|
use Hyperf\Filesystem\FilesystemFactory;
|
|
use Hyperf\HttpMessage\Upload\UploadedFile;
|
|
use League\Flysystem\Filesystem;
|
|
use Psr\EventDispatcher\EventDispatcherInterface;
|
|
use Psr\Container\ContainerInterface;
|
|
|
|
use App\System\Service\SettingConfigService;
|
|
use Builder\Exception\NormalStatusException;
|
|
use Builder\Helper\Str;
|
|
class Upload
|
|
{
|
|
/**
|
|
* @var FilesystemFactory
|
|
*/
|
|
#[Inject]
|
|
protected FilesystemFactory $factory;
|
|
|
|
/**
|
|
* @var Filesystem
|
|
*/
|
|
protected Filesystem $filesystem;
|
|
|
|
/**
|
|
* @var EventDispatcherInterface
|
|
*/
|
|
#[Inject]
|
|
protected EventDispatcherInterface $evDispatcher;
|
|
|
|
/**
|
|
* @var ContainerInterface
|
|
*/
|
|
protected ContainerInterface $container;
|
|
|
|
/**
|
|
* Upload constructor.
|
|
* @param ContainerInterface $container
|
|
* @throws \Psr\Container\ContainerExceptionInterface
|
|
* @throws \Psr\Container\NotFoundExceptionInterface
|
|
*/
|
|
public function __construct(ContainerInterface $container)
|
|
{
|
|
$this->container = $container;
|
|
$this->filesystem = $this->factory->get($this->getMappingMode());
|
|
}
|
|
|
|
/**
|
|
* 获取文件操作处理系统
|
|
* @return Filesystem
|
|
*/
|
|
public function getFileSystem(): Filesystem
|
|
{
|
|
return $this->filesystem;
|
|
}
|
|
|
|
/**
|
|
* 上传文件
|
|
* @param UploadedFile $uploadedFile
|
|
* @param array $config
|
|
* @return array
|
|
* @throws \League\Flysystem\FileExistsException
|
|
* @throws \Psr\Container\ContainerExceptionInterface
|
|
* @throws \Psr\Container\NotFoundExceptionInterface
|
|
*/
|
|
public function upload(UploadedFile $uploadedFile, array $config = []): array
|
|
{
|
|
return $this->handleUpload($uploadedFile, $config);
|
|
}
|
|
|
|
/**
|
|
* 处理上传
|
|
* @param UploadedFile $uploadedFile
|
|
* @param array $config
|
|
* @return array
|
|
* @throws \League\Flysystem\FileExistsException
|
|
* @throws \Psr\Container\ContainerExceptionInterface
|
|
* @throws \Psr\Container\NotFoundExceptionInterface
|
|
* @throws \Exception
|
|
*/
|
|
protected function handleUpload(UploadedFile $uploadedFile, array $config): array
|
|
{
|
|
$tmpFile = $uploadedFile->getPath() . '/' . $uploadedFile->getFilename();
|
|
$path = $this->getPath($config['path'] ?? null, $this->getStorageMode() != 1);
|
|
$filename = $this->getNewName() . '.' . Str::lower($uploadedFile->getExtension());
|
|
|
|
if (!$this->filesystem->writeStream($path . '/' . $filename, $uploadedFile->getStream()->detach())) {
|
|
throw new NormalStatusException((string) $uploadedFile->getError(), 500);
|
|
}
|
|
|
|
$fileInfo = [
|
|
'storage_mode' => $this->getStorageMode(),
|
|
'origin_name' => $uploadedFile->getClientFilename(),
|
|
'object_name' => $filename,
|
|
'mime_type' => $uploadedFile->getClientMediaType(),
|
|
'storage_path' => $path,
|
|
'hash' => md5_file($tmpFile),
|
|
'suffix' => Str::lower($uploadedFile->getExtension()),
|
|
'size_byte' => $uploadedFile->getSize(),
|
|
'size_info' => format_size($uploadedFile->getSize() * 1024),
|
|
'url' => $this->assembleUrl($config['path'] ?? null, $filename),
|
|
];
|
|
|
|
$this->evDispatcher->dispatch(new \Builder\Event\UploadAfter($fileInfo));
|
|
|
|
return $fileInfo;
|
|
}
|
|
|
|
/**
|
|
* 处理分块上传
|
|
* @param array $data
|
|
* @return array
|
|
*/
|
|
public function handleChunkUpload(array $data): array
|
|
{
|
|
$uploadFile = $data['package'];
|
|
/* @var UploadedFile $uploadFile */
|
|
$path = BASE_PATH . '/runtime/chunk/';
|
|
$chunkName = "{$path}{$data['hash']}_{$data['total']}_{$data['index']}.chunk";
|
|
$fs = container()->get(\Hyperf\Utils\Filesystem\Filesystem::class);
|
|
$fs->isDirectory($path) || $fs->makeDirectory($path);
|
|
$uploadFile->moveTo($chunkName);
|
|
if ($data['index'] === $data['total']) {
|
|
$content = '';
|
|
for($i = 1; $i <= $data['total']; $i++) {
|
|
$chunkFile = "{$path}{$data['hash']}_{$data['total']}_{$i}.chunk";
|
|
if (! $fs->isFile($chunkFile)) {
|
|
return ['chunk' => $data['index'], 'code' => 500, 'status' => 'fail'];
|
|
}
|
|
$content .= $fs->get($chunkFile);
|
|
$fs->delete($chunkFile);
|
|
}
|
|
$fileName = $this->getNewName().'.'.Str::lower($data['ext']);
|
|
$storagePath = $this->getPath(null, $this->getStorageMode() != 1);
|
|
if (! $this->filesystem->write($storagePath.'/'.$fileName, $content)) {
|
|
throw new NormalStatusException('分块上传失败', 500);
|
|
}
|
|
$fileInfo = [
|
|
'storage_mode' => $this->getStorageMode(),
|
|
'origin_name' => $data['name'],
|
|
'object_name' => $fileName,
|
|
'mime_type' => $data['type'],
|
|
'storage_path' => $storagePath,
|
|
'hash' => $data['hash'],
|
|
'suffix' => $data['ext'],
|
|
'size_byte' => $data['size'],
|
|
'size_info' => format_size(((int) $data['size'] * 1024)),
|
|
'url' => $this->assembleUrl(null, $fileName),
|
|
];
|
|
|
|
$this->evDispatcher->dispatch(new \Builder\Event\UploadAfter($fileInfo));
|
|
|
|
return $fileInfo;
|
|
}
|
|
return ['chunk' => $data['index'], 'code' => 201, 'status' => 'success'];
|
|
}
|
|
|
|
/**
|
|
* 保存网络图片
|
|
* @param array $data
|
|
* @return array
|
|
* @throws \Psr\Container\ContainerExceptionInterface
|
|
* @throws \Psr\Container\NotFoundExceptionInterface
|
|
* @throws \Exception
|
|
*/
|
|
public function handleSaveNetworkImage(array $data): array
|
|
{
|
|
$path = $this->getPath($data['path'] ?? null, $this->getStorageMode() != 1);
|
|
$filename = $this->getNewName() . '.jpg';
|
|
|
|
try {
|
|
$content = file_get_contents($data['url']);
|
|
|
|
$handle = fopen($data['url'], 'rb');
|
|
$meta = stream_get_meta_data($handle);
|
|
fclose($handle);
|
|
|
|
$dataInfo = $meta['wrapper_data']['headers'] ?? $meta['wrapper_data'];
|
|
$size = 0;
|
|
|
|
foreach ($dataInfo as $va) {
|
|
if ( preg_match('/length/iU', $va) ) {
|
|
$ts = explode(':', $va);
|
|
$size = intval(trim(array_pop($ts)));
|
|
break;
|
|
}
|
|
}
|
|
|
|
$realPath = BASE_PATH . '/runtime/' . $filename;
|
|
$fs = container()->get(\Hyperf\Utils\Filesystem\Filesystem::class);
|
|
$fs->put($realPath, $content);
|
|
|
|
$hash = md5_file($realPath);
|
|
$fs->delete($realPath);
|
|
|
|
if (! $hash) {
|
|
throw new \Exception(t('network_image_save_fail'));
|
|
}
|
|
|
|
if ($model = (new \App\System\Mapper\SystemUploadFileMapper)->getFileInfoByHash($hash)) {
|
|
return $model->toArray();
|
|
}
|
|
|
|
if (!$this->filesystem->write($path . '/' . $filename, $content)) {
|
|
throw new \Exception(t('network_image_save_fail'));
|
|
}
|
|
|
|
} catch (\Throwable $e) {
|
|
throw new NormalStatusException($e->getMessage(), 500);
|
|
}
|
|
|
|
$fileInfo = [
|
|
'storage_mode' => $this->getStorageMode(),
|
|
'origin_name' => md5((string) time()).'.jpg',
|
|
'object_name' => $filename,
|
|
'mime_type' => 'image/jpg',
|
|
'storage_path' => $path,
|
|
'suffix' => 'jpg',
|
|
'hash' => $hash,
|
|
'size_byte' => $size,
|
|
'size_info' => format_size($size * 1024),
|
|
'url' => $this->assembleUrl($data['path'] ?? null, $filename),
|
|
];
|
|
|
|
$this->evDispatcher->dispatch(new \Builder\Event\UploadAfter($fileInfo));
|
|
|
|
return $fileInfo;
|
|
}
|
|
|
|
/**
|
|
* @param string $config
|
|
* @param false $isContainRoot
|
|
* @return string
|
|
*/
|
|
protected function getPath(?string $path = null, bool $isContainRoot = false): string
|
|
{
|
|
$uploadfile = $isContainRoot ? '/'.env('UPLOAD_PATH', 'uploadfile').'/' : '';
|
|
return empty($path) ? $uploadfile . date('Ymd') : $uploadfile . $path;
|
|
}
|
|
|
|
/**
|
|
* 创建目录
|
|
* @param string $name
|
|
* @return bool
|
|
*/
|
|
public function createUploadDir(string $name): bool
|
|
{
|
|
return $this->filesystem->createDir($name);
|
|
}
|
|
|
|
/**
|
|
* 获取目录内容
|
|
* @param string $path
|
|
* @return array
|
|
*/
|
|
public function listContents(string $path = ''): array
|
|
{
|
|
return $this->filesystem->listContents($path);
|
|
}
|
|
|
|
/**
|
|
* 获取目录
|
|
* @param string $path
|
|
* @param bool $isChildren
|
|
* @return array
|
|
*/
|
|
public function getDirectory(string $path, bool $isChildren): array
|
|
{
|
|
$contents = $this->filesystem->listContents($path, $isChildren);
|
|
$dirs = [];
|
|
foreach ($contents as $content) {
|
|
if ($content['type'] == 'dir') {
|
|
$dirs[] = $content;
|
|
}
|
|
}
|
|
return $dirs;
|
|
}
|
|
|
|
/**
|
|
* 组装url
|
|
* @param string|null $path
|
|
* @param string $filename
|
|
* @return string
|
|
*/
|
|
public function assembleUrl(?string $path, string $filename): string
|
|
{
|
|
return $this->getPath($path, true) . '/' . $filename;
|
|
}
|
|
|
|
/**
|
|
* 获取存储方式
|
|
* @return int|string
|
|
* @throws \Psr\Container\ContainerExceptionInterface
|
|
* @throws \Psr\Container\NotFoundExceptionInterface
|
|
*/
|
|
public function getStorageMode(): int|string
|
|
{
|
|
return $this->container->get(SettingConfigService::class)->getConfigByKey('upload_mode')['value'] ?? 1;
|
|
}
|
|
|
|
/**
|
|
* 获取编码后的文件名
|
|
* @return string
|
|
* @throws \Psr\Container\ContainerExceptionInterface
|
|
* @throws \Psr\Container\NotFoundExceptionInterface
|
|
*/
|
|
public function getNewName(): string
|
|
{
|
|
return (string) container()->get(\Hyperf\Snowflake\IdGeneratorInterface::class)->generate();
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
* @throws \Psr\Container\ContainerExceptionInterface
|
|
* @throws \Psr\Container\NotFoundExceptionInterface
|
|
*/
|
|
protected function getMappingMode(): string
|
|
{
|
|
return match ( $this->getStorageMode() ) {
|
|
'1' => 'local',
|
|
'2' => 'oss',
|
|
'3' => 'qiniu',
|
|
'4' => 'cos',
|
|
default => 'local',
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @throws \Psr\Container\ContainerExceptionInterface
|
|
* @throws \Psr\Container\NotFoundExceptionInterface
|
|
*/
|
|
protected function getProtocol(): string
|
|
{
|
|
return $this->container->get(BaseRequest::class)->getScheme();
|
|
}
|
|
} |