Files
lgp-admin-plus-api/app/Service/common/upload/AliyunStorageService.php
LQ bcf54c2727 初始化
缺陷:主题配色需要优化整体的同风格
2026-08-14 23:21:21 +08:00

152 lines
5.6 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Service\common\upload;
use App\BaseApp\BaseNotAuthService;
use App\Service\common\oss\OssStorageInterface;
use App\Service\common\UtilsService;
use Illuminate\Support\Facades\Http;
/**
* 阿里云 OSSREST PutObjectAuthorization 签名)
*/
class AliyunStorageService extends BaseNotAuthService implements OssStorageInterface
{
use ObjectKeyNormalizeTrait;
use ObjectListXmlTrait;
protected array $config = [];
public function withConfig(array $config): static
{
$this->config = $config;
return $this;
}
public function uploadImage($filePath, string $key): bool|array
{
return $this->uploadFile($filePath, $key);
}
public function uploadVideo($filePath, string $key): bool|array
{
return $this->uploadFile($filePath, $key);
}
/**
* 列举 bucket 对象GET BucketV1 签名)
*
* prefix / marker / max-keys 都不是 OSS V1 的 sub-resource不参与签名
* 所以 CanonicalizedResource 仍然只有 /bucket/,别照着 V4 的写法往里塞查询串。
*/
public function listObjects(string $prefix = '', string $marker = '', int $limit = 100): array
{
$this->assertConfig();
$bucket = (string) $this->config['bucket'];
$host = $this->host();
$date = gmdate('D, d M Y H:i:s \G\M\T');
$query = ['max-keys' => $this->boundedLimit($limit)];
$listPrefix = $this->scopedListPrefix($prefix);
if ($listPrefix !== '') {
$query['prefix'] = $listPrefix;
}
if ($marker !== '') {
$query['marker'] = $marker;
}
$response = Http::withHeaders([
'Date' => $date,
'Authorization' => 'OSS ' . $this->config['access_key'] . ':'
. $this->signature('GET', '', $date, '/' . $bucket . '/'),
])->get('https://' . $host . '/', $query);
if (!$response->successful()) {
UtilsService::getInstance()->errorThrow('阿里云列举对象失败:' . $response->body());
}
return $this->parseObjectListXml($response->body(), 'https://' . $host);
}
/**
* 删除对象OSS 对不存在的键也回 204这里把它当成功处理
*/
public function deleteObject(string $key): bool
{
$this->assertConfig();
$key = $this->normalizeObjectKey($key);
if ($key === '') {
return false;
}
$date = gmdate('D, d M Y H:i:s \G\M\T');
$resource = '/' . $this->config['bucket'] . '/' . $key;
$response = Http::withHeaders([
'Date' => $date,
'Authorization' => 'OSS ' . $this->config['access_key'] . ':'
. $this->signature('DELETE', '', $date, $resource),
])->delete('https://' . $this->host() . '/' . $key);
return $response->successful();
}
/**
* 使用 OSS V1 签名上传对象
*/
private function uploadFile($filePath, string $key): bool|array
{
$this->assertConfig();
$domain = rtrim((string) ($this->config['domain'] ?? ''), '/');
$bucket = (string) $this->config['bucket'];
$prefix = trim((string) ($this->config['path_prefix'] ?? ''), '/');
if ($prefix !== '' && !str_starts_with($key, $prefix . '/')) {
$key = $prefix . '/' . ltrim($key, '/');
}
$path = is_object($filePath) && method_exists($filePath, 'getRealPath')
? $filePath->getRealPath()
: (string) $filePath;
$content = file_get_contents($path);
$contentType = 'application/octet-stream';
$date = gmdate('D, d M Y H:i:s \G\M\T');
$signature = $this->signature('PUT', $contentType, $date, '/' . $bucket . '/' . $key);
$url = 'https://' . $this->host() . '/' . $key;
$response = Http::withHeaders([
'Date' => $date,
'Content-Type' => $contentType,
'Authorization' => 'OSS ' . $this->config['access_key'] . ':' . $signature,
])->withBody($content, $contentType)->put($url);
if (!$response->successful()) {
UtilsService::getInstance()->errorThrow('阿里云上传失败:' . $response->body());
}
$publicUrl = $domain !== '' ? ($domain . '/' . $key) : $url;
return ['key' => $key, 'url' => $publicUrl];
}
/**
* OSS V1 签名:上传、列举、删除共用同一套 StringToSign 拼法
*/
private function signature(string $method, string $contentType, string $date, string $resource): string
{
$stringToSign = "{$method}\n\n{$contentType}\n{$date}\n{$resource}";
return base64_encode(hash_hmac('sha1', $stringToSign, (string) $this->config['secret_key'], true));
}
/**
* 请求主机名;支持配置里填 oss-cn-xxx.aliyuncs.com 或已带 bucket 的域名
*/
private function host(): string
{
$bucket = (string) ($this->config['bucket'] ?? '');
$host = (string) preg_replace('#^https?://#', '', rtrim((string) ($this->config['endpoint'] ?? ''), '/'));
if (!str_starts_with($host, $bucket . '.')) {
$host = $bucket . '.' . $host;
}
return $host;
}
private function assertConfig(): void
{
if (trim((string) ($this->config['access_key'] ?? '')) === ''
|| trim((string) ($this->config['secret_key'] ?? '')) === ''
|| trim((string) ($this->config['bucket'] ?? '')) === ''
|| trim((string) ($this->config['endpoint'] ?? '')) === ''
) {
UtilsService::getInstance()->errorThrow('阿里云 OSS 配置不完整(需要 AccessKey/Secret/Bucket/Endpoint');
}
}
}