78 lines
2.5 KiB
PHP
78 lines
2.5 KiB
PHP
<?php
|
|
|
|
namespace App\Utilities;
|
|
|
|
use DateTimeInterface;
|
|
use RuntimeException;
|
|
|
|
class QiniuPrivateUrlSigner
|
|
{
|
|
public function __construct(private ?array $configuration = null)
|
|
{
|
|
}
|
|
|
|
public function supports(string $disk): bool
|
|
{
|
|
$configuration = $this->configuration ?? config('business_storage', []);
|
|
|
|
return ($configuration['driver'] ?? null) === 'qiniu'
|
|
&& $disk === config('mediable.default_disk');
|
|
}
|
|
|
|
public function forPath(string $path, ?DateTimeInterface $expiration = null): string
|
|
{
|
|
$configuration = ($this->configuration ?? config('business_storage', []))['qiniu'] ?? [];
|
|
$accessKey = (string) ($configuration['access_key'] ?? '');
|
|
$secretKey = (string) ($configuration['secret_key'] ?? '');
|
|
|
|
if ($accessKey === '' || $secretKey === '') {
|
|
throw new RuntimeException('业务文件存储凭据不完整。');
|
|
}
|
|
|
|
$deadline = $expiration?->getTimestamp()
|
|
?? time() + max(60, (int) ($configuration['signed_url_ttl'] ?? 300));
|
|
|
|
$urlToSign = $this->unsignedUrlForPath($path) . '?e=' . $deadline;
|
|
$signature = strtr(
|
|
base64_encode(hash_hmac('sha1', $urlToSign, $secretKey, true)),
|
|
'+/',
|
|
'-_'
|
|
);
|
|
|
|
return $urlToSign . '&token=' . $accessKey . ':' . $signature;
|
|
}
|
|
|
|
public function unsignedUrlForPath(string $path): string
|
|
{
|
|
$configuration = ($this->configuration ?? config('business_storage', []))['qiniu'] ?? [];
|
|
$domain = rtrim((string) ($configuration['private_domain'] ?? ''), '/');
|
|
$root = trim((string) ($configuration['root'] ?? ''), '/');
|
|
$objectPath = $this->encodePath(implode('/', array_filter([
|
|
$root,
|
|
trim(str_replace('\\', '/', $path), '/'),
|
|
], static fn ($part) => $part !== '')));
|
|
|
|
if ($domain === '' || $objectPath === '') {
|
|
throw new RuntimeException('业务文件路径无效。');
|
|
}
|
|
|
|
return $domain . '/' . $objectPath;
|
|
}
|
|
|
|
private function encodePath(string $path): string
|
|
{
|
|
$segments = array_filter(
|
|
explode('/', $path),
|
|
static fn ($segment) => $segment !== ''
|
|
);
|
|
|
|
foreach ($segments as $segment) {
|
|
if ($segment === '.' || $segment === '..' || str_contains($segment, "\0")) {
|
|
throw new RuntimeException('业务文件路径无效。');
|
|
}
|
|
}
|
|
|
|
return implode('/', array_map('rawurlencode', $segments));
|
|
}
|
|
}
|