mirror of
https://gitee.com/technical-laohu/mpay_v2_webman.git
synced 2026-04-26 20:14:26 +08:00
重构初始化
This commit is contained in:
113
app/service/file/storage/AbstractStorageDriver.php
Normal file
113
app/service/file/storage/AbstractStorageDriver.php
Normal file
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
namespace app\service\file\storage;
|
||||
|
||||
use app\common\constant\FileConstant;
|
||||
use app\service\file\StorageConfigService;
|
||||
use support\Response;
|
||||
|
||||
/**
|
||||
* 文件存储驱动抽象基类。
|
||||
*/
|
||||
abstract class AbstractStorageDriver implements StorageDriverInterface
|
||||
{
|
||||
public function __construct(
|
||||
protected StorageConfigService $storageConfigService
|
||||
) {
|
||||
}
|
||||
|
||||
protected function assetValue(array $asset, string $key, mixed $default = null): mixed
|
||||
{
|
||||
return $asset[$key] ?? $default;
|
||||
}
|
||||
|
||||
protected function resolveLocalAbsolutePath(array $asset): string
|
||||
{
|
||||
$objectKey = trim((string) $this->assetValue($asset, 'object_key', ''));
|
||||
$visibility = (int) $this->assetValue($asset, 'visibility', FileConstant::VISIBILITY_PRIVATE);
|
||||
$candidate = '';
|
||||
|
||||
if ($objectKey !== '') {
|
||||
$candidate = $this->storageConfigService->buildLocalAbsolutePath($visibility, $objectKey);
|
||||
if ($candidate !== '' && is_file($candidate)) {
|
||||
return $candidate;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (['url', 'public_url'] as $field) {
|
||||
$url = trim((string) $this->assetValue($asset, $field, ''));
|
||||
if ($url === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$parsedPath = (string) parse_url($url, PHP_URL_PATH);
|
||||
if ($parsedPath === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$candidate = public_path() . DIRECTORY_SEPARATOR . ltrim($parsedPath, '/');
|
||||
if (is_file($candidate)) {
|
||||
return $candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return $candidate;
|
||||
}
|
||||
|
||||
protected function bodyResponse(string $body, string $mimeType = 'application/octet-stream', int $status = 200, array $headers = []): Response
|
||||
{
|
||||
$responseHeaders = array_merge([
|
||||
'Content-Type' => $mimeType !== '' ? $mimeType : 'application/octet-stream',
|
||||
], $headers);
|
||||
|
||||
return response($body, $status, $responseHeaders);
|
||||
}
|
||||
|
||||
protected function downloadBodyResponse(string $body, string $downloadName, string $mimeType = 'application/octet-stream'): Response
|
||||
{
|
||||
$response = $this->bodyResponse($body, $mimeType, 200, [
|
||||
'Content-Disposition' => 'attachment; filename="' . str_replace(['"', "\r", "\n", "\0"], '', $downloadName) . '"',
|
||||
]);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
protected function responseFromPath(string $path, string $downloadName = '', bool $attachment = false): Response
|
||||
{
|
||||
if ($attachment) {
|
||||
return response()->download($path, $downloadName);
|
||||
}
|
||||
|
||||
return response()->file($path);
|
||||
}
|
||||
|
||||
protected function localPreviewResponse(array $asset): Response
|
||||
{
|
||||
$path = $this->resolveLocalAbsolutePath($asset);
|
||||
if ($path === '' || !is_file($path)) {
|
||||
return response('文件不存在', 404);
|
||||
}
|
||||
|
||||
return $this->responseFromPath($path);
|
||||
}
|
||||
|
||||
protected function localDownloadResponse(array $asset): Response
|
||||
{
|
||||
$path = $this->resolveLocalAbsolutePath($asset);
|
||||
if ($path === '' || !is_file($path)) {
|
||||
return response('文件不存在', 404);
|
||||
}
|
||||
|
||||
return $this->responseFromPath($path, (string) $this->assetValue($asset, 'original_name', basename($path)), true);
|
||||
}
|
||||
|
||||
protected function scenePrefix(int $scene): string
|
||||
{
|
||||
return match ($scene) {
|
||||
FileConstant::SCENE_IMAGE => 'image',
|
||||
FileConstant::SCENE_CERTIFICATE => 'certificate',
|
||||
FileConstant::SCENE_TEXT => 'text',
|
||||
default => 'other',
|
||||
};
|
||||
}
|
||||
}
|
||||
188
app/service/file/storage/CosStorageDriver.php
Normal file
188
app/service/file/storage/CosStorageDriver.php
Normal file
@@ -0,0 +1,188 @@
|
||||
<?php
|
||||
|
||||
namespace app\service\file\storage;
|
||||
|
||||
use app\common\constant\FileConstant;
|
||||
use app\exception\BusinessStateException;
|
||||
use Qcloud\Cos\Client as CosClient;
|
||||
use support\Response;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* 腾讯云 COS 文件存储驱动。
|
||||
*/
|
||||
class CosStorageDriver extends AbstractStorageDriver
|
||||
{
|
||||
public function engine(): int
|
||||
{
|
||||
return FileConstant::STORAGE_TENCENT_COS;
|
||||
}
|
||||
|
||||
public function storeFromPath(string $sourcePath, array $context): array
|
||||
{
|
||||
if (!is_file($sourcePath)) {
|
||||
throw new BusinessStateException('待上传文件不存在');
|
||||
}
|
||||
|
||||
$config = $this->storageConfigService->cosConfig();
|
||||
foreach (['region', 'bucket', 'secret_id', 'secret_key'] as $key) {
|
||||
if (trim((string) ($config[$key] ?? '')) === '') {
|
||||
throw new BusinessStateException('腾讯云 COS 存储配置未完整');
|
||||
}
|
||||
}
|
||||
|
||||
$client = $this->client($config);
|
||||
$objectKey = (string) ($context['object_key'] ?? '');
|
||||
$client->putObject([
|
||||
'Bucket' => (string) $config['bucket'],
|
||||
'Key' => $objectKey,
|
||||
'Body' => fopen($sourcePath, 'rb'),
|
||||
]);
|
||||
|
||||
$publicUrl = $this->publicUrl([
|
||||
'object_key' => $objectKey,
|
||||
]);
|
||||
$visibility = (int) ($context['visibility'] ?? FileConstant::VISIBILITY_PRIVATE);
|
||||
|
||||
return [
|
||||
'storage_engine' => $this->engine(),
|
||||
'object_key' => $objectKey,
|
||||
'url' => $visibility === FileConstant::VISIBILITY_PUBLIC ? $publicUrl : '',
|
||||
'public_url' => $publicUrl,
|
||||
];
|
||||
}
|
||||
|
||||
public function delete(array $asset): bool
|
||||
{
|
||||
$config = $this->storageConfigService->cosConfig();
|
||||
if (trim((string) ($config['bucket'] ?? '')) === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$objectKey = (string) ($asset['object_key'] ?? '');
|
||||
if ($objectKey === '') {
|
||||
return true;
|
||||
}
|
||||
|
||||
$client = $this->client($config);
|
||||
$client->deleteObject([
|
||||
'Bucket' => (string) $config['bucket'],
|
||||
'Key' => $objectKey,
|
||||
]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function previewResponse(array $asset): Response
|
||||
{
|
||||
$url = $this->publicUrl($asset);
|
||||
if ($url !== '') {
|
||||
return redirect($url);
|
||||
}
|
||||
|
||||
return $this->responseFromObject($asset, false);
|
||||
}
|
||||
|
||||
public function downloadResponse(array $asset): Response
|
||||
{
|
||||
return $this->responseFromObject($asset, true);
|
||||
}
|
||||
|
||||
public function publicUrl(array $asset): string
|
||||
{
|
||||
$publicUrl = trim((string) ($asset['url'] ?? $asset['public_url'] ?? ''));
|
||||
if ($publicUrl !== '') {
|
||||
return $publicUrl;
|
||||
}
|
||||
|
||||
$config = $this->storageConfigService->cosConfig();
|
||||
$objectKey = (string) ($asset['object_key'] ?? '');
|
||||
if ($objectKey === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$customDomain = trim((string) ($config['public_domain'] ?? ''));
|
||||
if ($customDomain !== '') {
|
||||
return rtrim($customDomain, '/') . '/' . ltrim($objectKey, '/');
|
||||
}
|
||||
|
||||
$region = trim((string) ($config['region'] ?? ''));
|
||||
$bucket = trim((string) ($config['bucket'] ?? ''));
|
||||
if ($region === '' || $bucket === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return 'https://' . $bucket . '.cos.' . $region . '.myqcloud.com/' . ltrim($objectKey, '/');
|
||||
}
|
||||
|
||||
public function temporaryUrl(array $asset): string
|
||||
{
|
||||
$config = $this->storageConfigService->cosConfig();
|
||||
if (trim((string) ($config['bucket'] ?? '')) === '' || trim((string) ($config['region'] ?? '')) === '') {
|
||||
return $this->publicUrl($asset);
|
||||
}
|
||||
|
||||
try {
|
||||
$client = $this->client($config);
|
||||
$objectKey = (string) ($asset['object_key'] ?? '');
|
||||
if ($objectKey === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $client->getObjectUrl(
|
||||
(string) $config['bucket'],
|
||||
$objectKey
|
||||
);
|
||||
} catch (Throwable) {
|
||||
return $this->publicUrl($asset);
|
||||
}
|
||||
}
|
||||
|
||||
private function client(array $config): CosClient
|
||||
{
|
||||
return new CosClient([
|
||||
'region' => (string) $config['region'],
|
||||
'credentials' => [
|
||||
'secretId' => (string) $config['secret_id'],
|
||||
'secretKey' => (string) $config['secret_key'],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
private function responseFromObject(array $asset, bool $attachment): Response
|
||||
{
|
||||
$config = $this->storageConfigService->cosConfig();
|
||||
$bucket = trim((string) ($config['bucket'] ?? ''));
|
||||
$objectKey = (string) ($asset['object_key'] ?? '');
|
||||
if ($bucket === '' || $objectKey === '') {
|
||||
return response('文件不存在', 404);
|
||||
}
|
||||
|
||||
try {
|
||||
$client = $this->client($config);
|
||||
$result = $client->getObject([
|
||||
'Bucket' => $bucket,
|
||||
'Key' => $objectKey,
|
||||
]);
|
||||
|
||||
$body = '';
|
||||
if (is_string($result)) {
|
||||
$body = $result;
|
||||
} elseif (is_object($result) && method_exists($result, '__toString')) {
|
||||
$body = (string) $result;
|
||||
} elseif (is_array($result)) {
|
||||
$body = (string) ($result['Body'] ?? $result['body'] ?? '');
|
||||
}
|
||||
|
||||
$mimeType = (string) ($asset['mime_type'] ?? 'application/octet-stream');
|
||||
|
||||
if ($attachment) {
|
||||
return $this->downloadBodyResponse($body, (string) ($asset['original_name'] ?? basename($objectKey)), $mimeType);
|
||||
}
|
||||
|
||||
return $this->bodyResponse($body, $mimeType);
|
||||
} catch (Throwable) {
|
||||
return response('文件不存在', 404);
|
||||
}
|
||||
}
|
||||
}
|
||||
121
app/service/file/storage/LocalStorageDriver.php
Normal file
121
app/service/file/storage/LocalStorageDriver.php
Normal file
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
namespace app\service\file\storage;
|
||||
|
||||
use app\common\constant\FileConstant;
|
||||
use app\exception\BusinessStateException;
|
||||
use support\Response;
|
||||
|
||||
/**
|
||||
* 本地文件存储驱动。
|
||||
*/
|
||||
class LocalStorageDriver extends AbstractStorageDriver
|
||||
{
|
||||
public function engine(): int
|
||||
{
|
||||
return FileConstant::STORAGE_LOCAL;
|
||||
}
|
||||
|
||||
public function storeFromPath(string $sourcePath, array $context): array
|
||||
{
|
||||
if (!is_file($sourcePath)) {
|
||||
throw new BusinessStateException('待上传文件不存在');
|
||||
}
|
||||
|
||||
$visibility = (int) ($context['visibility'] ?? FileConstant::VISIBILITY_PRIVATE);
|
||||
$objectKey = (string) ($context['object_key'] ?? '');
|
||||
$absolutePath = $this->storageConfigService->buildLocalAbsolutePath($visibility, $objectKey);
|
||||
$publicUrl = (string) ($context['public_url'] ?? '');
|
||||
|
||||
if ($objectKey === '' || $absolutePath === '') {
|
||||
throw new BusinessStateException('文件存储路径无效');
|
||||
}
|
||||
|
||||
$this->ensureDirectory(dirname($absolutePath));
|
||||
|
||||
if (@rename($sourcePath, $absolutePath) === false) {
|
||||
if (!@copy($sourcePath, $absolutePath)) {
|
||||
throw new BusinessStateException('本地文件保存失败');
|
||||
}
|
||||
|
||||
@unlink($sourcePath);
|
||||
}
|
||||
|
||||
@chmod($absolutePath, 0666 & ~umask());
|
||||
|
||||
return [
|
||||
'storage_engine' => $this->engine(),
|
||||
'object_key' => $objectKey,
|
||||
'url' => $visibility === FileConstant::VISIBILITY_PUBLIC ? $publicUrl : '',
|
||||
'public_url' => $publicUrl,
|
||||
];
|
||||
}
|
||||
|
||||
public function delete(array $asset): bool
|
||||
{
|
||||
$path = $this->resolveLocalAbsolutePath($asset);
|
||||
if ($path === '' || !is_file($path)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return @unlink($path);
|
||||
}
|
||||
|
||||
public function previewResponse(array $asset): Response
|
||||
{
|
||||
return $this->localPreviewResponse($asset);
|
||||
}
|
||||
|
||||
public function downloadResponse(array $asset): Response
|
||||
{
|
||||
return $this->localDownloadResponse($asset);
|
||||
}
|
||||
|
||||
public function publicUrl(array $asset): string
|
||||
{
|
||||
$url = trim((string) ($asset['url'] ?? $asset['public_url'] ?? ''));
|
||||
if ($url !== '') {
|
||||
return $url;
|
||||
}
|
||||
|
||||
$visibility = (int) ($asset['visibility'] ?? FileConstant::VISIBILITY_PRIVATE);
|
||||
if ($visibility !== FileConstant::VISIBILITY_PUBLIC) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$objectKey = trim((string) ($asset['object_key'] ?? ''));
|
||||
if ($objectKey === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $this->storageConfigService->buildLocalPublicUrl($objectKey);
|
||||
}
|
||||
|
||||
public function temporaryUrl(array $asset): string
|
||||
{
|
||||
$url = trim((string) ($asset['url'] ?? $asset['public_url'] ?? ''));
|
||||
if ($url !== '') {
|
||||
return $url;
|
||||
}
|
||||
|
||||
$visibility = (int) ($asset['visibility'] ?? FileConstant::VISIBILITY_PRIVATE);
|
||||
if ($visibility === FileConstant::VISIBILITY_PUBLIC) {
|
||||
return $this->publicUrl($asset);
|
||||
}
|
||||
|
||||
$id = (int) ($asset['id'] ?? 0);
|
||||
|
||||
return $id > 0 ? '/adminapi/file-asset/' . $id . '/preview' : '';
|
||||
}
|
||||
|
||||
private function ensureDirectory(string $directory): void
|
||||
{
|
||||
if (is_dir($directory)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!@mkdir($directory, 0777, true) && !is_dir($directory)) {
|
||||
throw new BusinessStateException('文件目录创建失败');
|
||||
}
|
||||
}
|
||||
}
|
||||
196
app/service/file/storage/OssStorageDriver.php
Normal file
196
app/service/file/storage/OssStorageDriver.php
Normal file
@@ -0,0 +1,196 @@
|
||||
<?php
|
||||
|
||||
namespace app\service\file\storage;
|
||||
|
||||
use app\common\constant\FileConstant;
|
||||
use app\exception\BusinessStateException;
|
||||
use AlibabaCloud\Oss\V2 as Oss;
|
||||
use support\Response;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* 阿里云 OSS 文件存储驱动。
|
||||
*/
|
||||
class OssStorageDriver extends AbstractStorageDriver
|
||||
{
|
||||
public function engine(): int
|
||||
{
|
||||
return FileConstant::STORAGE_ALIYUN_OSS;
|
||||
}
|
||||
|
||||
public function storeFromPath(string $sourcePath, array $context): array
|
||||
{
|
||||
if (!is_file($sourcePath)) {
|
||||
throw new BusinessStateException('待上传文件不存在');
|
||||
}
|
||||
|
||||
$config = $this->storageConfigService->ossConfig();
|
||||
foreach (['region', 'bucket', 'access_key_id', 'access_key_secret'] as $key) {
|
||||
if (trim((string) ($config[$key] ?? '')) === '') {
|
||||
throw new BusinessStateException('阿里云 OSS 存储配置未完整');
|
||||
}
|
||||
}
|
||||
|
||||
$client = $this->client($config);
|
||||
$objectKey = (string) ($context['object_key'] ?? '');
|
||||
$request = new Oss\Models\PutObjectRequest(
|
||||
bucket: (string) $config['bucket'],
|
||||
key: $objectKey
|
||||
);
|
||||
$request->body = Oss\Utils::streamFor(fopen($sourcePath, 'rb'));
|
||||
|
||||
$client->putObject($request);
|
||||
|
||||
$publicUrl = $this->publicUrl([
|
||||
'object_key' => $objectKey,
|
||||
]);
|
||||
$visibility = (int) ($context['visibility'] ?? FileConstant::VISIBILITY_PRIVATE);
|
||||
|
||||
return [
|
||||
'storage_engine' => $this->engine(),
|
||||
'object_key' => $objectKey,
|
||||
'url' => $visibility === FileConstant::VISIBILITY_PUBLIC ? $publicUrl : '',
|
||||
'public_url' => $publicUrl,
|
||||
];
|
||||
}
|
||||
|
||||
public function delete(array $asset): bool
|
||||
{
|
||||
$config = $this->storageConfigService->ossConfig();
|
||||
if (trim((string) ($config['bucket'] ?? '')) === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$objectKey = (string) ($asset['object_key'] ?? '');
|
||||
if ($objectKey === '') {
|
||||
return true;
|
||||
}
|
||||
|
||||
$client = $this->client($config);
|
||||
$request = new Oss\Models\DeleteObjectRequest(
|
||||
bucket: (string) $config['bucket'],
|
||||
key: $objectKey
|
||||
);
|
||||
$client->deleteObject($request);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function previewResponse(array $asset): Response
|
||||
{
|
||||
$url = $this->publicUrl($asset);
|
||||
if ($url !== '') {
|
||||
return redirect($url);
|
||||
}
|
||||
|
||||
return $this->responseFromObject($asset, false);
|
||||
}
|
||||
|
||||
public function downloadResponse(array $asset): Response
|
||||
{
|
||||
return $this->responseFromObject($asset, true);
|
||||
}
|
||||
|
||||
public function publicUrl(array $asset): string
|
||||
{
|
||||
$publicUrl = trim((string) ($asset['url'] ?? $asset['public_url'] ?? ''));
|
||||
if ($publicUrl !== '') {
|
||||
return $publicUrl;
|
||||
}
|
||||
|
||||
$config = $this->storageConfigService->ossConfig();
|
||||
$objectKey = (string) ($asset['object_key'] ?? '');
|
||||
if ($objectKey === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$customDomain = trim((string) ($config['public_domain'] ?? ''));
|
||||
if ($customDomain !== '') {
|
||||
return rtrim($customDomain, '/') . '/' . ltrim($objectKey, '/');
|
||||
}
|
||||
|
||||
$endpoint = trim((string) ($config['endpoint'] ?? ''));
|
||||
$bucket = trim((string) ($config['bucket'] ?? ''));
|
||||
if ($endpoint === '' || $bucket === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$endpoint = preg_replace('#^https?://#i', '', $endpoint) ?: $endpoint;
|
||||
|
||||
return 'https://' . $bucket . '.' . ltrim($endpoint, '/') . '/' . ltrim($objectKey, '/');
|
||||
}
|
||||
|
||||
public function temporaryUrl(array $asset): string
|
||||
{
|
||||
$config = $this->storageConfigService->ossConfig();
|
||||
if (trim((string) ($config['bucket'] ?? '')) === '' || trim((string) ($config['region'] ?? '')) === '') {
|
||||
return $this->publicUrl($asset);
|
||||
}
|
||||
|
||||
try {
|
||||
$client = $this->client($config);
|
||||
$objectKey = (string) ($asset['object_key'] ?? '');
|
||||
if ($objectKey === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$request = new Oss\Models\GetObjectRequest(
|
||||
bucket: (string) $config['bucket'],
|
||||
key: $objectKey
|
||||
);
|
||||
$result = $client->presign($request);
|
||||
|
||||
return (string) ($result->url ?? '');
|
||||
} catch (Throwable) {
|
||||
return $this->publicUrl($asset);
|
||||
}
|
||||
}
|
||||
|
||||
private function client(array $config): Oss\Client
|
||||
{
|
||||
$provider = new Oss\Credentials\StaticCredentialsProvider(
|
||||
accessKeyId: (string) $config['access_key_id'],
|
||||
accessKeySecret: (string) $config['access_key_secret']
|
||||
);
|
||||
|
||||
$cfg = Oss\Config::loadDefault();
|
||||
$cfg->setCredentialsProvider(credentialsProvider: $provider);
|
||||
$cfg->setRegion(region: (string) $config['region']);
|
||||
|
||||
$endpoint = trim((string) ($config['endpoint'] ?? ''));
|
||||
if ($endpoint !== '') {
|
||||
$cfg->setEndpoint(endpoint: $endpoint);
|
||||
}
|
||||
|
||||
return new Oss\Client($cfg);
|
||||
}
|
||||
|
||||
private function responseFromObject(array $asset, bool $attachment): Response
|
||||
{
|
||||
$config = $this->storageConfigService->ossConfig();
|
||||
$bucket = trim((string) ($config['bucket'] ?? ''));
|
||||
$objectKey = (string) ($asset['object_key'] ?? '');
|
||||
if ($bucket === '' || $objectKey === '') {
|
||||
return response('文件不存在', 404);
|
||||
}
|
||||
|
||||
try {
|
||||
$client = $this->client($config);
|
||||
$request = new Oss\Models\GetObjectRequest(
|
||||
bucket: $bucket,
|
||||
key: $objectKey
|
||||
);
|
||||
$result = $client->getObject($request);
|
||||
$body = (string) $result->body->getContents();
|
||||
$mimeType = (string) ($asset['mime_type'] ?? 'application/octet-stream');
|
||||
|
||||
if ($attachment) {
|
||||
return $this->downloadBodyResponse($body, (string) ($asset['original_name'] ?? basename($objectKey)), $mimeType);
|
||||
}
|
||||
|
||||
return $this->bodyResponse($body, $mimeType);
|
||||
} catch (Throwable) {
|
||||
return response('文件不存在', 404);
|
||||
}
|
||||
}
|
||||
}
|
||||
53
app/service/file/storage/RemoteUrlStorageDriver.php
Normal file
53
app/service/file/storage/RemoteUrlStorageDriver.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace app\service\file\storage;
|
||||
|
||||
use app\common\constant\FileConstant;
|
||||
use app\exception\BusinessStateException;
|
||||
use support\Response;
|
||||
|
||||
/**
|
||||
* 远程引用驱动。
|
||||
*/
|
||||
class RemoteUrlStorageDriver extends AbstractStorageDriver
|
||||
{
|
||||
public function engine(): int
|
||||
{
|
||||
return FileConstant::STORAGE_REMOTE_URL;
|
||||
}
|
||||
|
||||
public function storeFromPath(string $sourcePath, array $context): array
|
||||
{
|
||||
throw new BusinessStateException('远程引用模式不支持直接上传,请先下载后再入库');
|
||||
}
|
||||
|
||||
public function delete(array $asset): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function previewResponse(array $asset): Response
|
||||
{
|
||||
$url = (string) ($asset['source_url'] ?? $asset['url'] ?? '');
|
||||
if ($url === '') {
|
||||
return response('文件不存在', 404);
|
||||
}
|
||||
|
||||
return redirect($url);
|
||||
}
|
||||
|
||||
public function downloadResponse(array $asset): Response
|
||||
{
|
||||
return $this->previewResponse($asset);
|
||||
}
|
||||
|
||||
public function publicUrl(array $asset): string
|
||||
{
|
||||
return (string) ($asset['source_url'] ?? $asset['url'] ?? '');
|
||||
}
|
||||
|
||||
public function temporaryUrl(array $asset): string
|
||||
{
|
||||
return (string) ($asset['source_url'] ?? $asset['url'] ?? '');
|
||||
}
|
||||
}
|
||||
25
app/service/file/storage/StorageDriverInterface.php
Normal file
25
app/service/file/storage/StorageDriverInterface.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace app\service\file\storage;
|
||||
|
||||
use support\Response;
|
||||
|
||||
/**
|
||||
* 文件存储驱动接口。
|
||||
*/
|
||||
interface StorageDriverInterface
|
||||
{
|
||||
public function engine(): int;
|
||||
|
||||
public function storeFromPath(string $sourcePath, array $context): array;
|
||||
|
||||
public function delete(array $asset): bool;
|
||||
|
||||
public function previewResponse(array $asset): Response;
|
||||
|
||||
public function downloadResponse(array $asset): Response;
|
||||
|
||||
public function publicUrl(array $asset): string;
|
||||
|
||||
public function temporaryUrl(array $asset): string;
|
||||
}
|
||||
158
app/service/file/storage/StorageManager.php
Normal file
158
app/service/file/storage/StorageManager.php
Normal file
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
namespace app\service\file\storage;
|
||||
|
||||
use app\common\constant\FileConstant;
|
||||
use app\service\file\StorageConfigService;
|
||||
use support\Response;
|
||||
|
||||
/**
|
||||
* 文件存储驱动管理器。
|
||||
*/
|
||||
class StorageManager
|
||||
{
|
||||
public function __construct(
|
||||
protected StorageConfigService $storageConfigService,
|
||||
protected LocalStorageDriver $localStorageDriver,
|
||||
protected OssStorageDriver $ossStorageDriver,
|
||||
protected CosStorageDriver $cosStorageDriver,
|
||||
protected RemoteUrlStorageDriver $remoteUrlStorageDriver
|
||||
) {
|
||||
}
|
||||
|
||||
public function buildContext(
|
||||
string $sourcePath,
|
||||
string $originalName,
|
||||
?int $scene = null,
|
||||
?int $visibility = null,
|
||||
?int $engine = null,
|
||||
?string $sourceUrl = null,
|
||||
string $sourceType = 'upload'
|
||||
): array {
|
||||
$mimeType = $this->guessMimeType($sourcePath, $originalName);
|
||||
$scene = $this->storageConfigService->normalizeScene($scene, $originalName, $mimeType);
|
||||
$visibility = $this->storageConfigService->normalizeVisibility($visibility, $scene);
|
||||
$engine = $this->storageConfigService->normalizeEngine($engine ?? $this->storageConfigService->defaultEngine());
|
||||
$ext = strtolower(trim(pathinfo($originalName, PATHINFO_EXTENSION)));
|
||||
if ($ext === '') {
|
||||
$ext = strtolower((string) pathinfo($sourcePath, PATHINFO_EXTENSION));
|
||||
}
|
||||
|
||||
$objectKey = $this->storageConfigService->buildObjectKey($scene, $visibility, $ext);
|
||||
$publicUrl = $this->buildPublicUrlByEngine($engine, $visibility, $objectKey);
|
||||
|
||||
return [
|
||||
'scene' => $scene,
|
||||
'visibility' => $visibility,
|
||||
'storage_engine' => $engine,
|
||||
'source_type' => $sourceType === 'remote_url' ? FileConstant::SOURCE_REMOTE_URL : FileConstant::SOURCE_UPLOAD,
|
||||
'source_url' => (string) ($sourceUrl ?? ''),
|
||||
'original_name' => $originalName,
|
||||
'file_name' => basename($objectKey),
|
||||
'file_ext' => $ext,
|
||||
'mime_type' => $mimeType,
|
||||
'size' => is_file($sourcePath) ? (int) filesize($sourcePath) : 0,
|
||||
'md5' => is_file($sourcePath) ? (string) md5_file($sourcePath) : '',
|
||||
'object_key' => $objectKey,
|
||||
'public_url' => $publicUrl,
|
||||
];
|
||||
}
|
||||
|
||||
public function storeFromPath(
|
||||
string $sourcePath,
|
||||
string $originalName,
|
||||
?int $scene = null,
|
||||
?int $visibility = null,
|
||||
?int $engine = null,
|
||||
?string $sourceUrl = null,
|
||||
string $sourceType = 'upload'
|
||||
): array {
|
||||
$context = $this->buildContext($sourcePath, $originalName, $scene, $visibility, $engine, $sourceUrl, $sourceType);
|
||||
$driver = $this->resolveDriver((int) $context['storage_engine']);
|
||||
|
||||
return array_merge($context, $driver->storeFromPath($sourcePath, $context));
|
||||
}
|
||||
|
||||
public function delete(array $asset): bool
|
||||
{
|
||||
return $this->resolveDriver((int) ($asset['storage_engine'] ?? FileConstant::STORAGE_LOCAL))
|
||||
->delete($asset);
|
||||
}
|
||||
|
||||
public function previewResponse(array $asset): Response
|
||||
{
|
||||
return $this->resolveDriver((int) ($asset['storage_engine'] ?? FileConstant::STORAGE_LOCAL))
|
||||
->previewResponse($asset);
|
||||
}
|
||||
|
||||
public function downloadResponse(array $asset): Response
|
||||
{
|
||||
return $this->resolveDriver((int) ($asset['storage_engine'] ?? FileConstant::STORAGE_LOCAL))
|
||||
->downloadResponse($asset);
|
||||
}
|
||||
|
||||
public function publicUrl(array $asset): string
|
||||
{
|
||||
return $this->resolveDriver((int) ($asset['storage_engine'] ?? FileConstant::STORAGE_LOCAL))
|
||||
->publicUrl($asset);
|
||||
}
|
||||
|
||||
public function temporaryUrl(array $asset): string
|
||||
{
|
||||
return $this->resolveDriver((int) ($asset['storage_engine'] ?? FileConstant::STORAGE_LOCAL))
|
||||
->temporaryUrl($asset);
|
||||
}
|
||||
|
||||
public function resolveDriver(int $engine): StorageDriverInterface
|
||||
{
|
||||
return match ($engine) {
|
||||
FileConstant::STORAGE_LOCAL => $this->localStorageDriver,
|
||||
FileConstant::STORAGE_ALIYUN_OSS => $this->ossStorageDriver,
|
||||
FileConstant::STORAGE_TENCENT_COS => $this->cosStorageDriver,
|
||||
FileConstant::STORAGE_REMOTE_URL => $this->remoteUrlStorageDriver,
|
||||
default => $this->localStorageDriver,
|
||||
};
|
||||
}
|
||||
|
||||
private function buildPublicUrlByEngine(int $engine, int $visibility, string $objectKey): string
|
||||
{
|
||||
if ($engine === FileConstant::STORAGE_LOCAL && $visibility === FileConstant::VISIBILITY_PUBLIC) {
|
||||
return $this->storageConfigService->buildLocalPublicUrl($objectKey);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
private function guessMimeType(string $sourcePath, string $originalName): string
|
||||
{
|
||||
$mimeType = '';
|
||||
if (is_file($sourcePath) && function_exists('mime_content_type')) {
|
||||
$detected = @mime_content_type($sourcePath);
|
||||
if (is_string($detected)) {
|
||||
$mimeType = trim($detected);
|
||||
}
|
||||
}
|
||||
|
||||
if ($mimeType !== '') {
|
||||
return $mimeType;
|
||||
}
|
||||
|
||||
$ext = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
|
||||
return match ($ext) {
|
||||
'jpg', 'jpeg' => 'image/jpeg',
|
||||
'png' => 'image/png',
|
||||
'gif' => 'image/gif',
|
||||
'webp' => 'image/webp',
|
||||
'svg' => 'image/svg+xml',
|
||||
'bmp' => 'image/bmp',
|
||||
'txt', 'log', 'md', 'ini', 'conf', 'yml', 'yaml' => 'text/plain',
|
||||
'json' => 'application/json',
|
||||
'xml' => 'application/xml',
|
||||
'csv' => 'text/csv',
|
||||
'pem' => 'application/x-pem-file',
|
||||
'crt', 'cer' => 'application/x-x509-ca-cert',
|
||||
'key' => 'application/octet-stream',
|
||||
default => 'application/octet-stream',
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user