檔案上傳
後端上傳
TIP
檔案上傳由 MineAdmin 接入 mineadmin/upload 元件建設而成
MineAdmin 提供了一個預設的服務端檔案上傳邏輯。介面 /admin/attachment/upload
並且在上傳成功後接入了資源管理器。
#[Post(
path: '/admin/attachment/upload',
operationId: 'UploadAttachment',
summary: '上傳附件',
security: [['Bearer' => [], 'ApiKey' => []]],
tags: ['資料中心'],
)]
#[Permission(code: 'attachment:upload')]
#[ResultResponse(instance: new Result())]
public function upload(UploadRequest $request): Result
{
$uploadFile = $request->file('file');
$newTmpPath = sys_get_temp_dir() . '/' . uniqid() . '.' . $uploadFile->getExtension();
$uploadFile->moveTo($newTmpPath);
$splFileInfo = new SplFileInfo($newTmpPath, '', '');
return $this->success(
$this->service->upload($splFileInfo, $uploadFile, $this->currentUser->id())
);
}
namespace App\Service;
use App\Model\Attachment;
use App\Repository\AttachmentRepository;
use Hyperf\HttpMessage\Upload\UploadedFile;
use Mine\Upload\UploadInterface;
use Symfony\Component\Finder\SplFileInfo;
/**
* @extends IService<AttachmentRepository>
*/
final class AttachmentService extends IService
{
public function __construct(
protected readonly AttachmentRepository $repository,
protected readonly UploadInterface $upload
) {}
public function upload(SplFileInfo $fileInfo, UploadedFile $uploadedFile, int $userId): Attachment
{
$fileHash = md5_file($fileInfo->getRealPath());
if ($attachment = $this->repository->findByHash($fileHash)) {
return $attachment;
}
$upload = $this->upload->upload(
$fileInfo,
);
return $this->repository->create([
'created_by' => $userId,
'origin_name' => $uploadedFile->getClientFilename(),
'storage_mode' => $upload->getStorageMode(),
'object_name' => $upload->getObjectName(),
'mime_type' => $upload->getMimeType(),
'storage_path' => $upload->getStoragePath(),
'hash' => $fileHash,
'suffix' => $upload->getSuffix(),
'size_byte' => $upload->getSizeByte(),
'size_info' => $upload->getSizeInfo(),
'url' => $upload->getUrl(),
]);
}
替換本地儲存為 Oss 儲存
在日常的業務場景中,一般檔案儲存在 OSS 上。那麼此時就需要替換預設的檔案上傳處理了。以阿里雲舉例 首先我們要配置 config/autoload/file.php
檔案。新增阿里雲通道。 然後新建一個 AliyunUploadSubscribe
替換 config/autoload/listeners.php
中預設的 UploadSubscribe
指定為阿里雲通道
<?php
// app/Http/Common/Subscribe/AliyunUploadSubscribe.php
declare(strict_types=1);
/**
* This file is part of MineAdmin.
*
* @link https://www.mineadmin.com
* @document https://doc.mineadmin.com
* @contact root@imoi.cn
* @license https://github.com/mineadmin/MineAdmin/blob/master/LICENSE
*/
namespace App\Http\Common\Subscriber\Upload;
use Mine\Upload\Listener\UploadListener as AbstractUploadListener;
final class AliyunUploadSubscribe extends AbstractUploadListener
{
public const ADAPTER_NAME = 'oss';
}
<?php
// config/autoload/file.php
declare(strict_types=1);
/**
* This file is part of MineAdmin.
*
* @link https://www.mineadmin.com
* @document https://doc.mineadmin.com
* @contact root@imoi.cn
* @license https://github.com/mineadmin/MineAdmin/blob/master/LICENSE
*/
use Hyperf\Filesystem\Adapter\AliyunOssAdapterFactory;
use Hyperf\Filesystem\Adapter\CosAdapterFactory;
use Hyperf\Filesystem\Adapter\FtpAdapterFactory;
use Hyperf\Filesystem\Adapter\LocalAdapterFactory;
use Hyperf\Filesystem\Adapter\MemoryAdapterFactory;
use Hyperf\Filesystem\Adapter\QiniuAdapterFactory;
use Hyperf\Filesystem\Adapter\S3AdapterFactory;
return [
'default' => 'local',
'storage' => [
'local' => [
'driver' => LocalAdapterFactory::class,
'root' => BASE_PATH . '/storage/uploads',
'public_url' => env('APP_URL') . '/uploads',
],
'oss' => [
'driver' => AliyunOssAdapterFactory::class,
'accessId' => '',
'accessSecret' => '',
'bucket' => '',
'endpoint' => '',
'domain' => '',
'schema' => 'http://',
'isCName' => false,
// 'timeout' => 3600,
// 'connectTimeout' => 10,
// 'token' => '',
],
],
];
// config/autoload/listeners.php
<?php
declare(strict_types=1);
/**
* This file is part of MineAdmin.
*
* @link https://www.mineadmin.com
* @document https://doc.mineadmin.com
* @contact root@imoi.cn
* @license https://github.com/mineadmin/MineAdmin/blob/master/LICENSE
*/
use Hyperf\ExceptionHandler\Listener\ErrorExceptionHandler;
use Mine\Core\Subscriber\BootApplicationSubscriber;
use Mine\Core\Subscriber\DbQueryExecutedSubscriber;
use Mine\Core\Subscriber\FailToHandleSubscriber;
use Mine\Core\Subscriber\QueueHandleSubscriber;
use Mine\Core\Subscriber\ResumeExitCoordinatorSubscriber;
use Mine\Core\Subscriber\Upload\UploadSubscriber;
use App\Http\Common\Subscriber\AliyunUploadSubscribe;
use Mine\Support\Listener\RegisterBlueprintListener;
return [
ErrorExceptionHandler::class,
// 預設檔案上傳
// UploadSubscriber::class,
// 阿里雲 oss 上傳
AliyunUploadSubscribe::class,
// 處理程式啟動
BootApplicationSubscriber::class,
// 處理 sql 執行
DbQueryExecutedSubscriber::class,
// 處理命令異常
FailToHandleSubscriber::class,
// 處理 worker 退出
ResumeExitCoordinatorSubscriber::class,
// 處理佇列
QueueHandleSubscriber::class,
// 註冊新的 Blueprint 宏
RegisterBlueprintListener::class,
];
修改預設上傳檔案命名和目錄命名
檔案的命名以及檔案的目錄命名預設是由 Mine\Upload\Listener\UploadListener->generatorPath()
和 Mine\Upload\Listener\UploadListener->generatorId()
實現的,而所有的上傳處理類都是繼承與這個類。 那麼以上一張替換 OSS 儲存為例。只需要在你的上傳處理類中替換這兩個方法即可
<?php
declare(strict_types=1);
/**
* This file is part of MineAdmin.
*
* @link https://www.mineadmin.com
* @document https://doc.mineadmin.com
* @contact root@imoi.cn
* @license https://github.com/mineadmin/MineAdmin/blob/master/LICENSE
*/
namespace Mine\Core\Subscriber\Upload;
use Hyperf\Stringable\Str;
use Mine\Upload\Listener\UploadListener as AbstractUploadListener;
final class UploadSubscriber extends AbstractUploadListener
{
public const ADAPTER_NAME = 'local';
protected function generatorId(): string
{
// 生成檔名,隨機字串長度為 10
return Str::random(10);
}
protected function generatorPath(): string
{
// 目錄格式:年/月/日
return date('Y/m/d');
}
}
處理流程
前端
呼叫介面 /admin/attachment/upload
,傳入引數 file
、引數型別為檔案。 服務端檔案控制器呼叫 檔案服務對前端傳入的 file
進行處理、 檔案服務
判斷上傳檔案hash值是否已經上傳過 如果有。則查詢資料庫返回上次上傳的資訊 如果沒有。則呼叫 UploadInterface 例項分發一個 UploadEvent 事件
分發後。判斷 UploadEvent->isUploaded() 是否上傳成功
如果上傳成功則返回 Upload
上傳例項 如果沒有上傳成功則丟擲異常上傳失敗
流程圖
時序圖
前端直傳 OSS
TODO