204 lines
6.8 KiB
PHP
204 lines
6.8 KiB
PHP
<?php
|
|
namespace app\service;
|
|
|
|
use Throwable;
|
|
|
|
class OssUploadService
|
|
{
|
|
public function uploadBase64(string $fileName, string $base64File, array $extraFields = []): array
|
|
{
|
|
$fileContent = base64_decode($base64File, true);
|
|
if ($fileContent === false || $fileContent === '') {
|
|
return $this->createErrorResult('文件解析错误');
|
|
}
|
|
|
|
return $this->uploadBinaryContent($fileName, $fileContent, $extraFields);
|
|
}
|
|
|
|
public function uploadBinary(string $fileName, string $fileContent, array $extraFields = []): array
|
|
{
|
|
if ($fileContent === '') {
|
|
return $this->createErrorResult('Binary payload is empty');
|
|
}
|
|
|
|
return $this->uploadBinaryContent($fileName, $fileContent, $extraFields);
|
|
}
|
|
|
|
private function uploadBinaryContent(string $fileName, string $fileContent, array $extraFields = []): array
|
|
{
|
|
$uploadConfig = config('filesystem.upload_conf') ?: [];
|
|
$maxSize = (int)($uploadConfig['fileSize'] ?? 10 * 1024 * 1024);
|
|
if (strlen($fileContent) > $maxSize) {
|
|
return $this->createErrorResult('File size exceeds limit');
|
|
}
|
|
|
|
$ext = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
|
|
$allowedExts = $uploadConfig['fileExt'] ?? [];
|
|
if ($ext === '' || (!empty($allowedExts) && !in_array($ext, $allowedExts, true))) {
|
|
return $this->createErrorResult('File extension not allowed');
|
|
}
|
|
|
|
$bucket = (string)config('oss.bucket');
|
|
$folder = (string)($extraFields['Folder'] ?? ($extraFields['Bucket'] ?? ''));
|
|
$key = $extraFields['Key'] ?? basename($fileName);
|
|
$objectKey = $this->buildObjectKey($folder, (string)$key);
|
|
if ($bucket === '' || $objectKey === '') {
|
|
return $this->createErrorResult('Bucket or key missing');
|
|
}
|
|
|
|
$finfo = new \finfo(FILEINFO_MIME_TYPE);
|
|
$mimeType = (string)$finfo->buffer($fileContent);
|
|
|
|
try {
|
|
$service = new OssService(config('oss'));
|
|
$result = $service->uploadFromBinary($fileContent, $objectKey, $bucket, [
|
|
'contentType' => $this->resolveContentType($mimeType, $ext),
|
|
]);
|
|
$result['url'] = $service->buildObjectUrl($objectKey, $bucket);
|
|
$result['folder'] = $folder;
|
|
|
|
return [
|
|
'code' => 1,
|
|
'msg' => 'upload ok',
|
|
'data' => $result,
|
|
];
|
|
} catch (Throwable $e) {
|
|
$fallback = $this->saveToLocalFallback($fileContent, $bucket, $objectKey);
|
|
|
|
return $this->createErrorResult(
|
|
'OSS upload failed: ' . $e->getMessage(),
|
|
[
|
|
'bucket' => $bucket,
|
|
'key' => $objectKey,
|
|
'folder' => $folder,
|
|
'fallback_saved' => $fallback['saved'],
|
|
'fallback_path' => $fallback['path'],
|
|
'fallback_error' => $fallback['error'],
|
|
]
|
|
);
|
|
}
|
|
}
|
|
|
|
private function buildObjectKey(string $folder, string $key): string
|
|
{
|
|
$folder = trim(str_replace('\\', '/', $folder), '/');
|
|
$key = trim(str_replace('\\', '/', $key), '/');
|
|
|
|
if ($folder === '') {
|
|
return $key;
|
|
}
|
|
|
|
if ($key === '') {
|
|
return $folder;
|
|
}
|
|
|
|
if (str_starts_with($key . '/', $folder . '/')) {
|
|
return $key;
|
|
}
|
|
|
|
return $folder . '/' . $key;
|
|
}
|
|
|
|
private function saveToLocalFallback(string $fileContent, string $bucket, string $key): array
|
|
{
|
|
$baseDir = $this->resolveFallbackBaseDir();
|
|
$safeBucket = $this->sanitizePathSegment($bucket);
|
|
$safeKey = $this->sanitizeObjectKey($key);
|
|
$fullPath = $baseDir . DIRECTORY_SEPARATOR . $safeBucket . DIRECTORY_SEPARATOR . $safeKey;
|
|
$directory = dirname($fullPath);
|
|
|
|
try {
|
|
if (!is_dir($directory) && !mkdir($directory, 0777, true) && !is_dir($directory)) {
|
|
throw new \RuntimeException('Unable to create fallback directory');
|
|
}
|
|
|
|
if (file_put_contents($fullPath, $fileContent) === false) {
|
|
throw new \RuntimeException('Unable to write fallback file');
|
|
}
|
|
|
|
return [
|
|
'saved' => true,
|
|
'path' => $fullPath,
|
|
'error' => null,
|
|
];
|
|
} catch (Throwable $e) {
|
|
return [
|
|
'saved' => false,
|
|
'path' => $fullPath,
|
|
'error' => $e->getMessage(),
|
|
];
|
|
}
|
|
}
|
|
|
|
private function resolveFallbackBaseDir(): string
|
|
{
|
|
$uploadConfig = config('filesystem.upload_conf') ?: [];
|
|
$configured = (string)($uploadConfig['fallbackDir'] ?? '');
|
|
if ($configured !== '') {
|
|
return rtrim($configured, "\\/");
|
|
}
|
|
|
|
$localRoot = (string)(config('filesystem.disks.local.root') ?? '');
|
|
if ($localRoot !== '') {
|
|
return rtrim($localRoot, "\\/") . DIRECTORY_SEPARATOR . 'oss-fallback';
|
|
}
|
|
|
|
return app()->getRuntimePath() . 'storage' . DIRECTORY_SEPARATOR . 'oss-fallback';
|
|
}
|
|
|
|
private function sanitizeObjectKey(string $key): string
|
|
{
|
|
$normalized = str_replace('\\', '/', $key);
|
|
$segments = array_filter(explode('/', $normalized), static function (string $segment): bool {
|
|
return $segment !== '' && $segment !== '.' && $segment !== '..';
|
|
});
|
|
|
|
if ($segments === []) {
|
|
return 'unnamed-file';
|
|
}
|
|
|
|
$safeSegments = array_map([$this, 'sanitizePathSegment'], $segments);
|
|
return implode(DIRECTORY_SEPARATOR, $safeSegments);
|
|
}
|
|
|
|
private function sanitizePathSegment(string $segment): string
|
|
{
|
|
$cleaned = preg_replace('/[^A-Za-z0-9._-]/', '_', $segment) ?? '';
|
|
return $cleaned !== '' ? $cleaned : 'unknown';
|
|
}
|
|
|
|
private function resolveContentType(string $mimeType, string $ext): string
|
|
{
|
|
if ($mimeType !== '' && $mimeType !== 'application/octet-stream') {
|
|
return $mimeType;
|
|
}
|
|
|
|
return match ($ext) {
|
|
'jpg', 'jpeg' => 'image/jpeg',
|
|
'png' => 'image/png',
|
|
'gif' => 'image/gif',
|
|
'webp' => 'image/webp',
|
|
'bmp' => 'image/bmp',
|
|
'svg' => 'image/svg+xml',
|
|
'pdf' => 'application/pdf',
|
|
'doc' => 'application/msword',
|
|
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
default => 'application/octet-stream',
|
|
};
|
|
}
|
|
|
|
private function createErrorResult(string $message, array $data = []): array
|
|
{
|
|
$result = [
|
|
'code' => 0,
|
|
'msg' => $message,
|
|
];
|
|
|
|
if ($data !== []) {
|
|
$result['data'] = $data;
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
}
|