更新统一使用 PHPDoc + PSR-19 标准注释

This commit is contained in:
技术老胡
2026-04-21 08:38:59 +08:00
parent dcd58e24ce
commit 9a16a88640
252 changed files with 9218 additions and 659 deletions

View File

@@ -4,6 +4,7 @@ namespace app\service\file;
use app\common\base\BaseService;
use app\exception\BusinessStateException;
use app\exception\ResourceNotFoundException;
use app\exception\ValidationException;
use app\repository\file\FileRecordRepository;
use app\service\file\storage\StorageManager;
@@ -12,9 +13,25 @@ use Webman\Http\UploadFile;
/**
* 文件命令服务。
*
* 负责上传、远程导入和删除文件,并负责把文件内容同步到存储驱动和数据库。
*
* @property FileRecordRepository $fileRecordRepository 文件记录仓库
* @property FileRecordQueryService $fileRecordQueryService 文件记录查询服务
* @property StorageManager $storageManager 存储管理器
* @property StorageConfigService $storageConfigService 存储配置服务
*/
class FileRecordCommandService extends BaseService
{
/**
* 构造方法。
*
* @param FileRecordRepository $fileRecordRepository 文件记录仓库
* @param FileRecordQueryService $fileRecordQueryService 文件记录查询服务
* @param StorageManager $storageManager 存储管理器
* @param StorageConfigService $storageConfigService 存储配置服务
* @return void
*/
public function __construct(
protected FileRecordRepository $fileRecordRepository,
protected FileRecordQueryService $fileRecordQueryService,
@@ -23,6 +40,15 @@ class FileRecordCommandService extends BaseService
) {
}
/**
* 上传文件并创建记录。
*
* @param UploadFile $file 上传文件
* @param array $data 文件参数
* @param int $createdBy 创建人ID
* @param string $createdByName 创建人名称
* @return array 文件记录
*/
public function upload(UploadFile $file, array $data, int $createdBy = 0, string $createdByName = ''): array
{
$this->assertFileUpload($file);
@@ -31,7 +57,7 @@ class FileRecordCommandService extends BaseService
try {
$scene = $this->storageConfigService->normalizeScene($data['scene'] ?? null, (string) $file->getUploadName(), (string) $file->getUploadMimeType());
$visibility = $this->storageConfigService->normalizeVisibility($data['visibility'] ?? null, $scene);
$engine = $this->storageConfigService->defaultEngine();
$engine = $this->resolveStorageEngine($data);
$result = $this->storageManager->storeFromPath(
$sourcePath,
@@ -62,7 +88,10 @@ class FileRecordCommandService extends BaseService
'created_by_name' => $createdByName,
]);
} catch (\Throwable $e) {
$this->storageManager->delete($result);
try {
$this->storageManager->delete($result);
} catch (\Throwable) {
}
throw $e;
}
@@ -74,18 +103,28 @@ class FileRecordCommandService extends BaseService
}
}
/**
* 导入远程文件并创建记录。
*
* @param string $remoteUrl 远程地址
* @param array $data 文件参数
* @param int $createdBy 创建人ID
* @param string $createdByName 创建人名称
* @return array 文件记录
* @throws ValidationException
*/
public function importRemote(string $remoteUrl, array $data, int $createdBy = 0, string $createdByName = ''): array
{
$remoteUrl = trim($remoteUrl);
if ($remoteUrl === '') {
throw new ValidationException('远程图片地址不能为空');
throw new ValidationException('远程文件地址不能为空');
}
$download = $this->downloadRemoteFile($remoteUrl, (int) ($data['scene'] ?? 0));
try {
$scene = $this->storageConfigService->normalizeScene($data['scene'] ?? null, $download['name'], $download['mime_type']);
$visibility = $this->storageConfigService->normalizeVisibility($data['visibility'] ?? null, $scene);
$engine = $this->storageConfigService->defaultEngine();
$engine = $this->resolveStorageEngine($data);
$result = $this->storageManager->storeFromPath(
$download['path'],
@@ -116,7 +155,10 @@ class FileRecordCommandService extends BaseService
'created_by_name' => $createdByName,
]);
} catch (\Throwable $e) {
$this->storageManager->delete($result);
try {
$this->storageManager->delete($result);
} catch (\Throwable) {
}
throw $e;
}
@@ -128,18 +170,38 @@ class FileRecordCommandService extends BaseService
}
}
/**
* 删除文件记录。
*
* @param int $id 文件记录命令ID
* @return bool 是否删除成功
* @throws ResourceNotFoundException
* @throws BusinessStateException
*/
public function delete(int $id): bool
{
$asset = $this->fileRecordRepository->findById($id);
if (!$asset) {
return false;
throw new ResourceNotFoundException('文件不存在', ['id' => $id]);
}
$this->storageManager->delete($this->fileRecordQueryService->formatModel($asset));
return $this->fileRecordRepository->deleteById($id);
if (!$this->fileRecordRepository->deleteById($id)) {
throw new BusinessStateException('文件删除失败');
}
return true;
}
/**
* 校验上传文件是否合法。
*
* @param UploadFile $file 文件
* @return void
* @throws ValidationException
* @throws BusinessStateException
*/
private function assertFileUpload(UploadFile $file): void
{
if (!$file->isValid()) {
@@ -162,10 +224,19 @@ class FileRecordCommandService extends BaseService
}
}
/**
* 下载远程文件到临时文件。
*
* @param string $remoteUrl 远程地址
* @param int $scene 场景
* @return array 下载结果
* @throws ValidationException
* @throws BusinessStateException
*/
private function downloadRemoteFile(string $remoteUrl, int $scene = 0): array
{
if (!filter_var($remoteUrl, FILTER_VALIDATE_URL)) {
throw new ValidationException('远程图片地址格式不正确');
throw new ValidationException('远程文件地址格式不正确');
}
$scheme = strtolower((string) parse_url($remoteUrl, PHP_URL_SCHEME));
@@ -175,7 +246,7 @@ class FileRecordCommandService extends BaseService
$host = (string) parse_url($remoteUrl, PHP_URL_HOST);
if ($host === '') {
throw new ValidationException('远程图片地址格式不正确');
throw new ValidationException('远程文件地址格式不正确');
}
if (filter_var($host, FILTER_VALIDATE_IP) && Request::isIntranetIp($host)) {
@@ -278,4 +349,22 @@ class FileRecordCommandService extends BaseService
'scene' => $scene,
];
}
/**
* 解析存储Engine
*
* @param array $data 数据
* @return int 整数结果
*/
private function resolveStorageEngine(array $data): int
{
if (!array_key_exists('storage_engine', $data) || $data['storage_engine'] === null || $data['storage_engine'] === '') {
return $this->storageConfigService->defaultEngine();
}
return $this->storageConfigService->normalizeEngine($data['storage_engine']);
}
}

View File

@@ -10,15 +10,35 @@ use app\service\file\storage\StorageManager;
/**
* 文件查询服务。
*
* 负责文件记录的分页、详情、选项和展示数据格式化。
*
* @property FileRecordRepository $fileRecordRepository 文件记录仓库
* @property StorageManager $storageManager 存储管理器
*/
class FileRecordQueryService extends BaseService
{
/**
* 构造方法。
*
* @param FileRecordRepository $fileRecordRepository 文件记录仓库
* @param StorageManager $storageManager 存储管理器
* @return void
*/
public function __construct(
protected FileRecordRepository $fileRecordRepository,
protected StorageManager $storageManager
) {
}
/**
* 分页查询文件记录。
*
* @param array $filters 筛选条件
* @param int $page 页码
* @param int $pageSize 每页条数
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator 分页结果
*/
public function paginate(array $filters = [], int $page = 1, int $pageSize = 10)
{
$query = $this->fileRecordRepository->query()->from('ma_file_asset as f');
@@ -50,6 +70,13 @@ class FileRecordQueryService extends BaseService
return $paginator;
}
/**
* 查询文件记录详情。
*
* @param int $id 文件记录查询ID
* @return array 文件详情
* @throws ResourceNotFoundException
*/
public function detail(int $id): array
{
$asset = $this->fileRecordRepository->findById($id);
@@ -60,7 +87,13 @@ class FileRecordQueryService extends BaseService
return $this->formatModel($asset);
}
public function formatModel(mixed $asset): array
/**
* 将文件记录格式化为前端展示结构。
*
* @param array|object|null $asset 文件记录
* @return array<string, mixed> 展示数据
*/
public function formatModel(array|object|null $asset): array
{
$id = (int) $this->field($asset, 'id', 0);
$scene = (int) $this->field($asset, 'scene', FileConstant::SCENE_OTHER);
@@ -68,10 +101,19 @@ class FileRecordQueryService extends BaseService
$storageEngine = (int) $this->field($asset, 'storage_engine', FileConstant::STORAGE_LOCAL);
$sourceType = (int) $this->field($asset, 'source_type', FileConstant::SOURCE_UPLOAD);
$size = (int) $this->field($asset, 'size', 0);
$publicUrl = (string) $this->field($asset, 'url', '');
$previewUrl = $publicUrl !== '' ? $publicUrl : $this->storageManager->temporaryUrl($this->normalizeAsset($asset));
if ($previewUrl === '' && $id > 0) {
$previewUrl = '/adminapi/file-asset/' . $id . '/preview';
$mimeType = strtolower((string) $this->field($asset, 'mime_type', ''));
$fileExt = strtolower((string) $this->field($asset, 'file_ext', ''));
$normalizedAsset = $this->normalizeAsset($asset);
$publicUrl = $visibility === FileConstant::VISIBILITY_PUBLIC
? $this->storageManager->publicUrl($normalizedAsset)
: '';
$previewable = $this->isPreviewable($scene, $mimeType, $fileExt);
$previewUrl = '';
if ($previewable) {
$previewUrl = $publicUrl !== '' ? $publicUrl : $this->storageManager->temporaryUrl($normalizedAsset);
if ($previewUrl === '' && $id > 0) {
$previewUrl = '/adminapi/file-asset/' . $id . '/preview';
}
}
return [
@@ -93,10 +135,11 @@ class FileRecordQueryService extends BaseService
'md5' => (string) $this->field($asset, 'md5', ''),
'object_key' => (string) $this->field($asset, 'object_key', ''),
'source_url' => (string) $this->field($asset, 'source_url', ''),
'url' => $previewUrl,
'url' => $publicUrl,
'public_url' => $publicUrl,
'preview_url' => $previewUrl,
'download_url' => $id > 0 ? '/adminapi/file-asset/' . $id . '/download' : '',
'previewable' => $previewable,
'created_by' => (int) $this->field($asset, 'created_by', 0),
'created_by_name' => (string) $this->field($asset, 'created_by_name', ''),
'remark' => (string) $this->field($asset, 'remark', ''),
@@ -106,6 +149,11 @@ class FileRecordQueryService extends BaseService
];
}
/**
* 获取文件记录选项。
*
* @return array<string, array<int, array{label: string, value: int}>> 选项数据
*/
public function options(): array
{
return [
@@ -117,6 +165,12 @@ class FileRecordQueryService extends BaseService
];
}
/**
* 格式化文件大小。
*
* @param int $size 文件大小(字节)
* @return string 格式化后的大小
*/
private function formatSize(int $size): string
{
if ($size <= 0) {
@@ -134,6 +188,12 @@ class FileRecordQueryService extends BaseService
return $index === 0 ? (string) (int) $value . ' ' . $units[$index] : number_format($value, 2) . ' ' . $units[$index];
}
/**
* 将映射表转换为前端选项。
*
* @param array $map 映射表
* @return array 选项列表
*/
private function toOptions(array $map): array
{
$options = [];
@@ -147,7 +207,15 @@ class FileRecordQueryService extends BaseService
return $options;
}
private function field(mixed $asset, string $key, mixed $default = null): mixed
/**
* 从数组或对象中读取字段值。
*
* @param array|object|null $asset 文件记录数据
* @param string $key 字段名
* @param mixed $default 默认值
* @return mixed 文件字段值
*/
private function field(array|object|null $asset, string $key, mixed $default = null): mixed
{
if (is_array($asset)) {
return $asset[$key] ?? $default;
@@ -160,7 +228,13 @@ class FileRecordQueryService extends BaseService
return $default;
}
private function normalizeAsset(mixed $asset): array
/**
* 归一化文件记录。
*
* @param array|object|null $asset 原始记录
* @return array<string, mixed> 标准化记录
*/
private function normalizeAsset(array|object|null $asset): array
{
return $this->field($asset, 'id', null) === null ? [] : [
'id' => (int) $this->field($asset, 'id', 0),
@@ -173,4 +247,25 @@ class FileRecordQueryService extends BaseService
'mime_type' => (string) $this->field($asset, 'mime_type', ''),
];
}
/**
* 判断文件是否支持预览。
*
* @param int $scene 场景
* @param string $mimeType MIME 类型
* @param string $fileExt 文件扩展名
* @return bool 是否支持预览
*/
private function isPreviewable(int $scene, string $mimeType, string $fileExt): bool
{
if ($scene === FileConstant::SCENE_IMAGE || str_starts_with($mimeType, 'image/')) {
return true;
}
if ($scene === FileConstant::SCENE_TEXT || str_starts_with($mimeType, 'text/')) {
return true;
}
return in_array($fileExt, ['pem', 'crt', 'cer', 'key'], true);
}
}

View File

@@ -7,10 +7,22 @@ use app\service\file\storage\StorageManager;
use Webman\Http\UploadFile;
/**
* 文件门面服务。
* 文件记录服务。
*
* @property FileRecordQueryService $queryService 查询服务
* @property FileRecordCommandService $commandService 命令服务
* @property StorageManager $storageManager 存储管理器
*/
class FileRecordService extends BaseService
{
/**
* 构造方法。
*
* @param FileRecordQueryService $queryService 查询服务
* @param FileRecordCommandService $commandService 命令服务
* @param StorageManager $storageManager 存储管理器
* @return void
*/
public function __construct(
protected FileRecordQueryService $queryService,
protected FileRecordCommandService $commandService,
@@ -18,36 +30,85 @@ class FileRecordService extends BaseService
) {
}
/**
* 分页查询文件记录。
*
* @param array $filters 筛选条件
* @param int $page 页码
* @param int $pageSize 每页条数
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator 分页结果
*/
public function paginate(array $filters = [], int $page = 1, int $pageSize = 10)
{
return $this->queryService->paginate($filters, $page, $pageSize);
}
/**
* 查询文件记录详情。
*
* @param int $id 文件记录ID
* @return array 文件详情
*/
public function detail(int $id): array
{
return $this->queryService->detail($id);
}
/**
* 获取文件记录选项。
*
* @return array 选项数据
*/
public function options(): array
{
return $this->queryService->options();
}
/**
* 上传文件并创建记录。
*
* @param UploadFile $file 上传文件
* @param array $data 文件参数
* @param int $createdBy 创建人ID
* @param string $createdByName 创建人名称
* @return array 文件记录
*/
public function upload(UploadFile $file, array $data, int $createdBy = 0, string $createdByName = ''): array
{
return $this->commandService->upload($file, $data, $createdBy, $createdByName);
}
/**
* 导入远程文件并创建记录。
*
* @param string $remoteUrl 远程地址
* @param array $data 文件参数
* @param int $createdBy 创建人ID
* @param string $createdByName 创建人名称
* @return array 文件记录
*/
public function importRemote(string $remoteUrl, array $data, int $createdBy = 0, string $createdByName = ''): array
{
return $this->commandService->importRemote($remoteUrl, $data, $createdBy, $createdByName);
}
/**
* 删除文件记录。
*
* @param int $id 文件记录ID
* @return bool 是否删除成功
*/
public function delete(int $id): bool
{
return $this->commandService->delete($id);
}
/**
* 获取文件预览响应。
*
* @param int $id 文件记录ID
* @return \support\Response 响应对象
*/
public function previewResponse(int $id)
{
$asset = $this->queryService->detail($id);
@@ -55,6 +116,12 @@ class FileRecordService extends BaseService
return $this->storageManager->previewResponse($asset);
}
/**
* 获取文件下载响应。
*
* @param int $id 文件记录ID
* @return \support\Response 响应对象
*/
public function downloadResponse(int $id)
{
$asset = $this->queryService->detail($id);

View File

@@ -4,32 +4,51 @@ namespace app\service\file;
use app\common\base\BaseService;
use app\common\constant\FileConstant;
use app\exception\BusinessStateException;
/**
* 文件存储配置服务。
*
* 负责读取系统配置并统一整理文件场景、可见性、扩展名和存储引擎相关规则。
*/
class StorageConfigService extends BaseService
{
/**
* 获取默认存储引擎。
*
* @return int 存储引擎
*/
public function defaultEngine(): int
{
return $this->normalizeSelectableEngine((int) sys_config(FileConstant::CONFIG_DEFAULT_ENGINE, FileConstant::STORAGE_LOCAL));
}
/**
* 获取本地公开访问基址。
*
* @return string 基础地址
* @throws BusinessStateException
*/
public function localPublicBaseUrl(): string
{
$baseUrl = trim((string) sys_config(FileConstant::CONFIG_LOCAL_PUBLIC_BASE_URL, ''));
if ($baseUrl !== '') {
return rtrim($baseUrl, '/');
}
$siteUrl = trim((string) sys_config('site_url', ''));
if ($siteUrl !== '') {
$baseUrl = trim((string) sys_config(FileConstant::CONFIG_LOCAL_PUBLIC_BASE_URL, ''));
if ($baseUrl !== '') {
return rtrim($baseUrl, '/');
}
return rtrim($siteUrl, '/');
}
return '';
throw new BusinessStateException('请先在系统配置中设置站点 URL');
}
/**
* 获取本地公开目录。
*
* @return string 目录
*/
public function localPublicDir(): string
{
$dir = trim((string) sys_config(FileConstant::CONFIG_LOCAL_PUBLIC_DIR, 'storage/uploads'), "/ \t\n\r\0\x0B");
@@ -37,6 +56,11 @@ class StorageConfigService extends BaseService
return $dir !== '' ? $dir : 'storage/uploads';
}
/**
* 获取本地私有目录。
*
* @return string 目录
*/
public function localPrivateDir(): string
{
$dir = trim((string) sys_config(FileConstant::CONFIG_LOCAL_PRIVATE_DIR, 'storage/private'), "/ \t\n\r\0\x0B");
@@ -44,6 +68,11 @@ class StorageConfigService extends BaseService
return $dir !== '' ? $dir : 'storage/private';
}
/**
* 获取上传大小上限。
*
* @return int 字节数
*/
public function uploadMaxSizeBytes(): int
{
$mb = max(1, (int) sys_config(FileConstant::CONFIG_UPLOAD_MAX_SIZE_MB, 20));
@@ -51,6 +80,11 @@ class StorageConfigService extends BaseService
return $mb * 1024 * 1024;
}
/**
* 获取远程下载大小上限。
*
* @return int 字节数
*/
public function remoteDownloadLimitBytes(): int
{
$mb = max(1, (int) sys_config(FileConstant::CONFIG_REMOTE_DOWNLOAD_LIMIT_MB, 10));
@@ -58,6 +92,11 @@ class StorageConfigService extends BaseService
return $mb * 1024 * 1024;
}
/**
* 获取允许上传的扩展名列表。
*
* @return array 允许的扩展名列表
*/
public function allowedExtensions(): array
{
$raw = trim((string) sys_config(FileConstant::CONFIG_ALLOWED_EXTENSIONS, implode(',', FileConstant::defaultAllowedExtensions())));
@@ -70,6 +109,11 @@ class StorageConfigService extends BaseService
return array_values(array_unique($extensions));
}
/**
* 获取阿里云 OSS 配置。
*
* @return array OSS 配置
*/
public function ossConfig(): array
{
return [
@@ -82,6 +126,11 @@ class StorageConfigService extends BaseService
];
}
/**
* 获取腾讯云 COS 配置。
*
* @return array COS 配置
*/
public function cosConfig(): array
{
return [
@@ -93,6 +142,14 @@ class StorageConfigService extends BaseService
];
}
/**
* 归一化文件场景。
*
* @param int|string|null $scene 场景
* @param string $originalName 原始文件名
* @param string $mimeType MIME 类型
* @return int 场景值
*/
public function normalizeScene(int|string|null $scene = null, string $originalName = '', string $mimeType = ''): int
{
$scene = (int) $scene;
@@ -122,6 +179,13 @@ class StorageConfigService extends BaseService
return FileConstant::SCENE_OTHER;
}
/**
* 归一化文件可见性。
*
* @param int|string|null $visibility 可见性
* @param int $scene 场景
* @return int 可见性值
*/
public function normalizeVisibility(int|string|null $visibility = null, int $scene = FileConstant::SCENE_OTHER): int
{
$visibility = (int) $visibility;
@@ -134,6 +198,12 @@ class StorageConfigService extends BaseService
: FileConstant::VISIBILITY_PRIVATE;
}
/**
* 归一化存储引擎。
*
* @param int|string|null $engine 存储引擎
* @return int 存储引擎值
*/
public function normalizeEngine(int|string|null $engine = null): int
{
$engine = (int) $engine;
@@ -141,6 +211,12 @@ class StorageConfigService extends BaseService
return $this->normalizeSelectableEngine($engine);
}
/**
* 获取场景对应的目录名。
*
* @param int $scene 场景
* @return string 目录名
*/
public function sceneFolder(int $scene): string
{
return match ($scene) {
@@ -151,6 +227,14 @@ class StorageConfigService extends BaseService
};
}
/**
* 构建对象键。
*
* @param int $scene 场景
* @param int $visibility 可见性
* @param string $extension 扩展名
* @return string 对象键
*/
public function buildObjectKey(int $scene, int $visibility, string $extension): string
{
$extension = strtolower(trim($extension, ". \t\n\r\0\x0B"));
@@ -168,6 +252,13 @@ class StorageConfigService extends BaseService
return trim($rootDir . '/' . $this->sceneFolder($scene) . '/' . $timestampPath . '/' . $name, '/');
}
/**
* 构建本地绝对路径。
*
* @param int $visibility 可见性
* @param string $objectKey 对象键
* @return string 绝对路径
*/
public function buildLocalAbsolutePath(int $visibility, string $objectKey): string
{
$root = $visibility === FileConstant::VISIBILITY_PUBLIC
@@ -178,6 +269,12 @@ class StorageConfigService extends BaseService
return rtrim($root, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $relativePath);
}
/**
* 构建本地公开访问 URL。
*
* @param string $objectKey 对象键
* @return string 访问 URL
*/
public function buildLocalPublicUrl(string $objectKey): string
{
$path = trim(str_replace('\\', '/', $objectKey), '/');
@@ -190,6 +287,12 @@ class StorageConfigService extends BaseService
return '/' . $path;
}
/**
* 归一化可选存储引擎。
*
* @param int $engine 存储引擎
* @return int 存储引擎值
*/
private function normalizeSelectableEngine(int $engine): int
{
return match ($engine) {
@@ -200,3 +303,7 @@ class StorageConfigService extends BaseService
};
}
}

View File

@@ -3,57 +3,69 @@
namespace app\service\file\storage;
use app\common\constant\FileConstant;
use app\exception\ResourceNotFoundException;
use app\service\file\StorageConfigService;
use support\Response;
/**
* 文件存储驱动抽象基类。
*
* 提供文件存储驱动公共能力。
*
* @property-read StorageConfigService $storageConfigService 存储配置服务
*/
abstract class AbstractStorageDriver implements StorageDriverInterface
{
/**
* 注入存储配置服务。
*
* @param StorageConfigService $storageConfigService 存储配置服务
*/
public function __construct(
protected StorageConfigService $storageConfigService
) {
}
/**
* 从资产数组中读取指定字段。
*
* @param array<string, mixed> $asset 文件资产数据
* @param string $key 字段名
* @param mixed $default 默认值
* @return mixed 资产字段值
*/
protected function assetValue(array $asset, string $key, mixed $default = null): mixed
{
return $asset[$key] ?? $default;
}
/**
* 解析本地存储文件的绝对路径。
*
* @param array $asset 文件资产数据
* @return string 绝对路径
*/
protected function resolveLocalAbsolutePath(array $asset): string
{
$objectKey = trim((string) $this->assetValue($asset, 'object_key', ''));
if ($objectKey === '') {
return '';
}
$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;
return $this->storageConfigService->buildLocalAbsolutePath($visibility, $objectKey);
}
/**
* 构造字符串响应。
*
* @param string $body 响应内容
* @param string $mimeType MIME 类型
* @param int $status HTTP 状态码
* @param array $headers 额外响应头
* @return Response 响应对象
*/
protected function bodyResponse(string $body, string $mimeType = 'application/octet-stream', int $status = 200, array $headers = []): Response
{
$responseHeaders = array_merge([
@@ -63,6 +75,14 @@ abstract class AbstractStorageDriver implements StorageDriverInterface
return response($body, $status, $responseHeaders);
}
/**
* 构造文件下载响应。
*
* @param string $body 响应内容
* @param string $downloadName 下载文件名
* @param string $mimeType MIME 类型
* @return Response 响应对象
*/
protected function downloadBodyResponse(string $body, string $downloadName, string $mimeType = 'application/octet-stream'): Response
{
$response = $this->bodyResponse($body, $mimeType, 200, [
@@ -72,6 +92,14 @@ abstract class AbstractStorageDriver implements StorageDriverInterface
return $response;
}
/**
* 根据本地路径构造预览或下载响应。
*
* @param string $path 本地文件路径
* @param string $downloadName 下载文件名
* @param bool $attachment 是否下载附件
* @return Response 响应对象
*/
protected function responseFromPath(string $path, string $downloadName = '', bool $attachment = false): Response
{
if ($attachment) {
@@ -81,26 +109,46 @@ abstract class AbstractStorageDriver implements StorageDriverInterface
return response()->file($path);
}
/**
* 构造本地文件预览响应。
*
* @param array $asset 文件资产数据
* @return Response 响应对象
* @throws ResourceNotFoundException
*/
protected function localPreviewResponse(array $asset): Response
{
$path = $this->resolveLocalAbsolutePath($asset);
if ($path === '' || !is_file($path)) {
return response('文件不存在', 404);
throw new ResourceNotFoundException('文件不存在');
}
return $this->responseFromPath($path);
}
/**
* 构造本地文件下载响应。
*
* @param array $asset 文件资产数据
* @return Response 响应对象
* @throws ResourceNotFoundException
*/
protected function localDownloadResponse(array $asset): Response
{
$path = $this->resolveLocalAbsolutePath($asset);
if ($path === '' || !is_file($path)) {
return response('文件不存在', 404);
throw new ResourceNotFoundException('文件不存在');
}
return $this->responseFromPath($path, (string) $this->assetValue($asset, 'original_name', basename($path)), true);
}
/**
* 根据文件场景返回目录前缀。
*
* @param int $scene 文件场景
* @return string 目录前缀
*/
protected function scenePrefix(int $scene): string
{
return match ($scene) {

View File

@@ -4,20 +4,36 @@ namespace app\service\file\storage;
use app\common\constant\FileConstant;
use app\exception\BusinessStateException;
use app\exception\ResourceNotFoundException;
use Qcloud\Cos\Client as CosClient;
use support\Response;
use Throwable;
/**
* 腾讯云 COS 文件存储驱动。
*
* 负责对象上传、删除、公开地址生成和对象内容响应。
*/
class CosStorageDriver extends AbstractStorageDriver
{
/**
* 获取 COS 存储引擎标识。
*
* @return int 存储引擎常量
*/
public function engine(): int
{
return FileConstant::STORAGE_TENCENT_COS;
}
/**
* 将本地临时文件上传到 COS。
*
* @param string $sourcePath 待上传文件路径
* @param array $context 上传上下文,包含 object_key、visibility 等信息
* @return array 上传后的资产数据
* @throws BusinessStateException
*/
public function storeFromPath(string $sourcePath, array $context): array
{
if (!is_file($sourcePath)) {
@@ -33,6 +49,7 @@ class CosStorageDriver extends AbstractStorageDriver
$client = $this->client($config);
$objectKey = (string) ($context['object_key'] ?? '');
$visibility = (int) ($context['visibility'] ?? FileConstant::VISIBILITY_PRIVATE);
$client->putObject([
'Bucket' => (string) $config['bucket'],
'Key' => $objectKey,
@@ -40,23 +57,30 @@ class CosStorageDriver extends AbstractStorageDriver
]);
$publicUrl = $this->publicUrl([
'visibility' => $visibility,
'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_url' => $visibility === FileConstant::VISIBILITY_PUBLIC ? $publicUrl : '',
];
}
/**
* 删除 COS 对象。
*
* @param array $asset 文件资产数据
* @return bool 是否删除成功
* @throws BusinessStateException
*/
public function delete(array $asset): bool
{
$config = $this->storageConfigService->cosConfig();
if (trim((string) ($config['bucket'] ?? '')) === '') {
return false;
throw new BusinessStateException('腾讯云 COS 存储配置未完整');
}
$objectKey = (string) ($asset['object_key'] ?? '');
@@ -73,23 +97,41 @@ class CosStorageDriver extends AbstractStorageDriver
return true;
}
/**
* 构造 COS 文件预览响应。
*
* @param array $asset 文件资产数据
* @return Response 响应对象
*/
public function previewResponse(array $asset): Response
{
$url = $this->publicUrl($asset);
if ($url !== '') {
return redirect($url);
}
return $this->responseFromObject($asset, false);
}
/**
* 构造 COS 文件下载响应。
*
* @param array $asset 文件资产数据
* @return Response 响应对象
*/
public function downloadResponse(array $asset): Response
{
return $this->responseFromObject($asset, true);
}
/**
* 获取 COS 公开访问地址。
*
* @param array $asset 文件资产数据
* @return string 公共 URL
*/
public function publicUrl(array $asset): string
{
$visibility = (int) ($asset['visibility'] ?? FileConstant::VISIBILITY_PRIVATE);
if ($visibility !== FileConstant::VISIBILITY_PUBLIC) {
return '';
}
$publicUrl = trim((string) ($asset['url'] ?? $asset['public_url'] ?? ''));
if ($publicUrl !== '') {
return $publicUrl;
@@ -115,11 +157,17 @@ class CosStorageDriver extends AbstractStorageDriver
return 'https://' . $bucket . '.cos.' . $region . '.myqcloud.com/' . ltrim($objectKey, '/');
}
/**
* 获取 COS 临时访问地址。
*
* @param array $asset 文件资产数据
* @return string 临时 URL
*/
public function temporaryUrl(array $asset): string
{
$config = $this->storageConfigService->cosConfig();
if (trim((string) ($config['bucket'] ?? '')) === '' || trim((string) ($config['region'] ?? '')) === '') {
return $this->publicUrl($asset);
return '';
}
try {
@@ -134,10 +182,16 @@ class CosStorageDriver extends AbstractStorageDriver
$objectKey
);
} catch (Throwable) {
return $this->publicUrl($asset);
return '';
}
}
/**
* 创建 COS 客户端。
*
* @param array $config 存储配置
* @return CosClient COS 客户端
*/
private function client(array $config): CosClient
{
return new CosClient([
@@ -149,13 +203,21 @@ class CosStorageDriver extends AbstractStorageDriver
]);
}
/**
* 根据 COS 对象内容构造预览或下载响应。
*
* @param array $asset 文件资产数据
* @param bool $attachment 是否下载附件
* @return Response 响应对象
* @throws ResourceNotFoundException
*/
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);
throw new ResourceNotFoundException('文件不存在');
}
try {
@@ -171,7 +233,7 @@ class CosStorageDriver extends AbstractStorageDriver
} elseif (is_object($result) && method_exists($result, '__toString')) {
$body = (string) $result;
} elseif (is_array($result)) {
$body = (string) ($result['Body'] ?? $result['body'] ?? '');
$body = (string) ($result['Body'] ?? $result['body'] ?? '');
}
$mimeType = (string) ($asset['mime_type'] ?? 'application/octet-stream');
@@ -182,7 +244,7 @@ class CosStorageDriver extends AbstractStorageDriver
return $this->bodyResponse($body, $mimeType);
} catch (Throwable) {
return response('文件不存在', 404);
throw new ResourceNotFoundException('文件不存在');
}
}
}

View File

@@ -8,14 +8,29 @@ use support\Response;
/**
* 本地文件存储驱动。
*
* 负责本地文件存储和响应构造。
*/
class LocalStorageDriver extends AbstractStorageDriver
{
/**
* 获取本地存储引擎标识。
*
* @return int 存储引擎常量
*/
public function engine(): int
{
return FileConstant::STORAGE_LOCAL;
}
/**
* 将临时文件写入本地存储目录。
*
* @param string $sourcePath 待上传文件路径
* @param array $context 上传上下文,包含 object_key、visibility、public_url 等信息
* @return array 上传后的资产数据
* @throws BusinessStateException
*/
public function storeFromPath(string $sourcePath, array $context): array
{
if (!is_file($sourcePath)) {
@@ -51,6 +66,13 @@ class LocalStorageDriver extends AbstractStorageDriver
];
}
/**
* 删除本地文件。
*
* @param array $asset 文件资产数据
* @return bool 是否删除成功
* @throws BusinessStateException
*/
public function delete(array $asset): bool
{
$path = $this->resolveLocalAbsolutePath($asset);
@@ -58,26 +80,43 @@ class LocalStorageDriver extends AbstractStorageDriver
return true;
}
return @unlink($path);
if (@unlink($path)) {
return true;
}
throw new BusinessStateException('本地文件删除失败');
}
/**
* 构造本地文件预览响应。
*
* @param array $asset 文件资产数据
* @return Response 响应对象
*/
public function previewResponse(array $asset): Response
{
return $this->localPreviewResponse($asset);
}
/**
* 构造本地文件下载响应。
*
* @param array $asset 文件资产数据
* @return Response 响应对象
*/
public function downloadResponse(array $asset): Response
{
return $this->localDownloadResponse($asset);
}
/**
* 获取本地公开访问地址。
*
* @param array $asset 文件资产数据
* @return string 公共 URL
*/
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 '';
@@ -91,13 +130,14 @@ class LocalStorageDriver extends AbstractStorageDriver
return $this->storageConfigService->buildLocalPublicUrl($objectKey);
}
/**
* 获取本地临时访问地址。
*
* @param array $asset 文件资产数据
* @return string 临时 URL
*/
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);
@@ -108,6 +148,12 @@ class LocalStorageDriver extends AbstractStorageDriver
return $id > 0 ? '/adminapi/file-asset/' . $id . '/preview' : '';
}
/**
* 确保目标目录存在。
*
* @param string $directory 目录路径
* @throws BusinessStateException
*/
private function ensureDirectory(string $directory): void
{
if (is_dir($directory)) {

View File

@@ -4,20 +4,36 @@ namespace app\service\file\storage;
use app\common\constant\FileConstant;
use app\exception\BusinessStateException;
use app\exception\ResourceNotFoundException;
use AlibabaCloud\Oss\V2 as Oss;
use support\Response;
use Throwable;
/**
* 阿里云 OSS 文件存储驱动。
*
* 负责对象上传、删除、公开地址生成和预签名访问。
*/
class OssStorageDriver extends AbstractStorageDriver
{
/**
* 获取 OSS 存储引擎标识。
*
* @return int 存储引擎常量
*/
public function engine(): int
{
return FileConstant::STORAGE_ALIYUN_OSS;
}
/**
* 将本地临时文件上传到 OSS。
*
* @param string $sourcePath 待上传文件路径
* @param array $context 上传上下文,包含 object_key、visibility 等信息
* @return array 上传后的资产数据
* @throws BusinessStateException
*/
public function storeFromPath(string $sourcePath, array $context): array
{
if (!is_file($sourcePath)) {
@@ -33,6 +49,9 @@ class OssStorageDriver extends AbstractStorageDriver
$client = $this->client($config);
$objectKey = (string) ($context['object_key'] ?? '');
$visibility = (int) ($context['visibility'] ?? FileConstant::VISIBILITY_PRIVATE);
/** @var Oss\Models\PutObjectRequest $request */
$request = new Oss\Models\PutObjectRequest(
bucket: (string) $config['bucket'],
key: $objectKey
@@ -42,23 +61,30 @@ class OssStorageDriver extends AbstractStorageDriver
$client->putObject($request);
$publicUrl = $this->publicUrl([
'visibility' => $visibility,
'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_url' => $visibility === FileConstant::VISIBILITY_PUBLIC ? $publicUrl : '',
];
}
/**
* 删除 OSS 对象。
*
* @param array $asset 文件资产数据
* @return bool 是否删除成功
* @throws BusinessStateException
*/
public function delete(array $asset): bool
{
$config = $this->storageConfigService->ossConfig();
if (trim((string) ($config['bucket'] ?? '')) === '') {
return false;
throw new BusinessStateException('阿里云 OSS 存储配置未完整');
}
$objectKey = (string) ($asset['object_key'] ?? '');
@@ -67,6 +93,8 @@ class OssStorageDriver extends AbstractStorageDriver
}
$client = $this->client($config);
/** @var Oss\Models\DeleteObjectRequest $request */
$request = new Oss\Models\DeleteObjectRequest(
bucket: (string) $config['bucket'],
key: $objectKey
@@ -76,23 +104,41 @@ class OssStorageDriver extends AbstractStorageDriver
return true;
}
/**
* 构造 OSS 文件预览响应。
*
* @param array $asset 文件资产数据
* @return Response 响应对象
*/
public function previewResponse(array $asset): Response
{
$url = $this->publicUrl($asset);
if ($url !== '') {
return redirect($url);
}
return $this->responseFromObject($asset, false);
}
/**
* 构造 OSS 文件下载响应。
*
* @param array $asset 文件资产数据
* @return Response 响应对象
*/
public function downloadResponse(array $asset): Response
{
return $this->responseFromObject($asset, true);
}
/**
* 获取 OSS 公开访问地址。
*
* @param array $asset 文件资产数据
* @return string 公共 URL
*/
public function publicUrl(array $asset): string
{
$visibility = (int) ($asset['visibility'] ?? FileConstant::VISIBILITY_PRIVATE);
if ($visibility !== FileConstant::VISIBILITY_PUBLIC) {
return '';
}
$publicUrl = trim((string) ($asset['url'] ?? $asset['public_url'] ?? ''));
if ($publicUrl !== '') {
return $publicUrl;
@@ -120,11 +166,17 @@ class OssStorageDriver extends AbstractStorageDriver
return 'https://' . $bucket . '.' . ltrim($endpoint, '/') . '/' . ltrim($objectKey, '/');
}
/**
* 获取 OSS 预签名访问地址。
*
* @param array $asset 文件资产数据
* @return string 临时 URL
*/
public function temporaryUrl(array $asset): string
{
$config = $this->storageConfigService->ossConfig();
if (trim((string) ($config['bucket'] ?? '')) === '' || trim((string) ($config['region'] ?? '')) === '') {
return $this->publicUrl($asset);
return '';
}
try {
@@ -134,6 +186,7 @@ class OssStorageDriver extends AbstractStorageDriver
return '';
}
/** @var Oss\Models\GetObjectRequest $request */
$request = new Oss\Models\GetObjectRequest(
bucket: (string) $config['bucket'],
key: $objectKey
@@ -142,12 +195,19 @@ class OssStorageDriver extends AbstractStorageDriver
return (string) ($result->url ?? '');
} catch (Throwable) {
return $this->publicUrl($asset);
return '';
}
}
/**
* 创建 OSS 客户端。
*
* @param array $config 存储配置
* @return Oss\Client OSS 客户端
*/
private function client(array $config): Oss\Client
{
/** @var Oss\Credentials\StaticCredentialsProvider $provider */
$provider = new Oss\Credentials\StaticCredentialsProvider(
accessKeyId: (string) $config['access_key_id'],
accessKeySecret: (string) $config['access_key_secret']
@@ -165,17 +225,26 @@ class OssStorageDriver extends AbstractStorageDriver
return new Oss\Client($cfg);
}
/**
* 根据 OSS 对象内容构造预览或下载响应。
*
* @param array $asset 文件资产数据
* @param bool $attachment 是否下载附件
* @return Response 响应对象
* @throws ResourceNotFoundException
*/
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);
throw new ResourceNotFoundException('文件不存在');
}
try {
$client = $this->client($config);
/** @var Oss\Models\GetObjectRequest $request */
$request = new Oss\Models\GetObjectRequest(
bucket: $bucket,
key: $objectKey
@@ -190,7 +259,7 @@ class OssStorageDriver extends AbstractStorageDriver
return $this->bodyResponse($body, $mimeType);
} catch (Throwable) {
return response('文件不存在', 404);
throw new ResourceNotFoundException('文件不存在');
}
}
}

View File

@@ -4,48 +4,95 @@ namespace app\service\file\storage;
use app\common\constant\FileConstant;
use app\exception\BusinessStateException;
use app\exception\ResourceNotFoundException;
use support\Response;
/**
* 远程引用驱动。
* 远程引用文件存储驱动。
*
* 仅保存原始远程 URL不做本地落盘或对象存储复制。
*/
class RemoteUrlStorageDriver extends AbstractStorageDriver
{
/**
* 获取远程引用引擎标识。
*
* @return int 存储引擎常量
*/
public function engine(): int
{
return FileConstant::STORAGE_REMOTE_URL;
}
/**
* 远程引用模式不支持直接上传。
*
* @param string $sourcePath 待上传文件路径
* @param array $context 上传上下文
* @return array 上传后的资产数据
* @throws BusinessStateException
*/
public function storeFromPath(string $sourcePath, array $context): array
{
throw new BusinessStateException('远程引用模式不支持直接上传,请先下载后再入库');
}
/**
* 远程引用模式不需要真正删除对象。
*
* @param array $asset 文件资产数据
* @return bool 是否删除成功
*/
public function delete(array $asset): bool
{
return true;
}
/**
* 直接跳转到源站地址进行预览。
*
* @param array $asset 文件资产数据
* @return Response 响应对象
* @throws ResourceNotFoundException
*/
public function previewResponse(array $asset): Response
{
$url = (string) ($asset['source_url'] ?? $asset['url'] ?? '');
if ($url === '') {
return response('文件不存在', 404);
throw new ResourceNotFoundException('文件不存在');
}
return redirect($url);
}
/**
* 远程引用文件的下载行为与预览保持一致。
*
* @param array $asset 文件资产数据
* @return Response 响应对象
*/
public function downloadResponse(array $asset): Response
{
return $this->previewResponse($asset);
}
/**
* 获取原始远程地址。
*
* @param array $asset 文件资产数据
* @return string 远程 URL
*/
public function publicUrl(array $asset): string
{
return (string) ($asset['source_url'] ?? $asset['url'] ?? '');
}
/**
* 获取原始远程地址。
*
* @param array $asset 文件资产数据
* @return string 远程 URL
*/
public function temporaryUrl(array $asset): string
{
return (string) ($asset['source_url'] ?? $asset['url'] ?? '');

View File

@@ -6,20 +6,64 @@ use support\Response;
/**
* 文件存储驱动接口。
*
* 统一定义文件存储驱动能力。
*/
interface StorageDriverInterface
{
/**
* 获取存储引擎标识。
*
* @return int 存储引擎常量
*/
public function engine(): int;
/**
* 将本地临时文件写入存储后端。
*
* @param string $sourcePath 待上传的本地临时文件路径
* @param array $context 上传上下文,通常包含 object_key、visibility 等信息
* @return array 上传后的资产数据
*/
public function storeFromPath(string $sourcePath, array $context): array;
/**
* 删除指定文件资产。
*
* @param array $asset 文件资产数据
* @return bool 是否删除成功
*/
public function delete(array $asset): bool;
/**
* 构造文件预览响应。
*
* @param array $asset 文件资产数据
* @return Response 响应对象
*/
public function previewResponse(array $asset): Response;
/**
* 构造文件下载响应。
*
* @param array $asset 文件资产数据
* @return Response 响应对象
*/
public function downloadResponse(array $asset): Response;
/**
* 获取公开访问地址。
*
* @param array $asset 文件资产数据
* @return string 公开 URL
*/
public function publicUrl(array $asset): string;
/**
* 获取临时访问地址。
*
* @param array $asset 文件资产数据
* @return string 临时 URL
*/
public function temporaryUrl(array $asset): string;
}

View File

@@ -8,9 +8,27 @@ use support\Response;
/**
* 文件存储驱动管理器。
*
* 负责根据存储引擎分发文件操作。
*
* @property StorageConfigService $storageConfigService 存储配置服务
* @property LocalStorageDriver $localStorageDriver 本地存储驱动
* @property OssStorageDriver $ossStorageDriver oss存储驱动
* @property CosStorageDriver $cosStorageDriver cos存储驱动
* @property RemoteUrlStorageDriver $remoteUrlStorageDriver remoteUrl存储驱动
*/
class StorageManager
{
/**
* 构造方法。
*
* @param StorageConfigService $storageConfigService 存储配置服务
* @param LocalStorageDriver $localStorageDriver 本地存储驱动
* @param OssStorageDriver $ossStorageDriver oss存储驱动
* @param CosStorageDriver $cosStorageDriver cos存储驱动
* @param RemoteUrlStorageDriver $remoteUrlStorageDriver remoteUrl存储驱动
* @return void
*/
public function __construct(
protected StorageConfigService $storageConfigService,
protected LocalStorageDriver $localStorageDriver,
@@ -20,6 +38,18 @@ class StorageManager
) {
}
/**
* 构建存储上下文。
*
* @param string $sourcePath 源文件路径
* @param string $originalName 原始文件名
* @param int|null $scene 场景
* @param int|null $visibility 可见性
* @param int|null $engine 存储引擎
* @param string|null $sourceUrl 源地址
* @param string $sourceType 来源类型
* @return array 上下文数据
*/
public function buildContext(
string $sourcePath,
string $originalName,
@@ -58,6 +88,18 @@ class StorageManager
];
}
/**
* 从文件路径保存文件。
*
* @param string $sourcePath 源文件路径
* @param string $originalName 原始文件名
* @param int|null $scene 场景
* @param int|null $visibility 可见性
* @param int|null $engine 存储引擎
* @param string|null $sourceUrl 源地址
* @param string $sourceType 来源类型
* @return array 保存结果
*/
public function storeFromPath(
string $sourcePath,
string $originalName,
@@ -73,36 +115,72 @@ class StorageManager
return array_merge($context, $driver->storeFromPath($sourcePath, $context));
}
/**
* 删除存储对象。
*
* @param array $asset 文件记录
* @return bool 是否删除成功
*/
public function delete(array $asset): bool
{
return $this->resolveDriver((int) ($asset['storage_engine'] ?? FileConstant::STORAGE_LOCAL))
->delete($asset);
}
/**
* 获取预览响应。
*
* @param array $asset 文件记录
* @return Response 响应对象
*/
public function previewResponse(array $asset): Response
{
return $this->resolveDriver((int) ($asset['storage_engine'] ?? FileConstant::STORAGE_LOCAL))
->previewResponse($asset);
}
/**
* 获取下载响应。
*
* @param array $asset 文件记录
* @return Response 响应对象
*/
public function downloadResponse(array $asset): Response
{
return $this->resolveDriver((int) ($asset['storage_engine'] ?? FileConstant::STORAGE_LOCAL))
->downloadResponse($asset);
}
/**
* 获取公开访问 URL。
*
* @param array $asset 文件记录
* @return string 访问 URL
*/
public function publicUrl(array $asset): string
{
return $this->resolveDriver((int) ($asset['storage_engine'] ?? FileConstant::STORAGE_LOCAL))
->publicUrl($asset);
}
/**
* 获取临时访问 URL。
*
* @param array $asset 文件记录
* @return string 访问 URL
*/
public function temporaryUrl(array $asset): string
{
return $this->resolveDriver((int) ($asset['storage_engine'] ?? FileConstant::STORAGE_LOCAL))
->temporaryUrl($asset);
}
/**
* 解析对应的存储驱动。
*
* @param int $engine 存储引擎
* @return StorageDriverInterface 存储驱动
*/
public function resolveDriver(int $engine): StorageDriverInterface
{
return match ($engine) {
@@ -114,6 +192,14 @@ class StorageManager
};
}
/**
* 按存储引擎构建公开访问 URL。
*
* @param int $engine 存储引擎
* @param int $visibility 可见性
* @param string $objectKey 对象键
* @return string 访问 URL
*/
private function buildPublicUrlByEngine(int $engine, int $visibility, string $objectKey): string
{
if ($engine === FileConstant::STORAGE_LOCAL && $visibility === FileConstant::VISIBILITY_PUBLIC) {
@@ -123,6 +209,13 @@ class StorageManager
return '';
}
/**
* 估算 MIME 类型。
*
* @param string $sourcePath 源文件路径
* @param string $originalName 原始文件名
* @return string MIME 类型
*/
private function guessMimeType(string $sourcePath, string $originalName): string
{
$mimeType = '';
@@ -156,3 +249,5 @@ class StorageManager
};
}
}