This commit is contained in:
wushumin
2026-05-11 15:28:27 +08:00
commit 9aac78b8da
289 changed files with 67193 additions and 0 deletions

View File

@@ -0,0 +1,363 @@
<?php
namespace app\support;
use OSS\OssClient;
use Qiniu\Auth as QiniuAuth;
use Qiniu\Config as QiniuConfig;
use Qiniu\Storage\BucketManager as QiniuBucketManager;
use Qiniu\Storage\UploadManager as QiniuUploadManager;
use support\Request;
use function dirname;
use function file_exists;
use function file_put_contents;
use function is_dir;
use function is_file;
use function ltrim;
use function mkdir;
use function sys_get_temp_dir;
use function tempnam;
use function unlink;
class FileStorageService
{
private static bool $caBundleInitialized = false;
private ?OssClient $ossClient = null;
private ?QiniuAuth $qiniuAuth = null;
public function publicUrl(Request $request, string $value): string
{
return $this->assetUrlService()->buildUrl($request, $this->storagePath($value));
}
public function normalizeUrl(string $value, Request $request): string
{
return $this->assetUrlService()->normalizeUrl($value, $request);
}
public function storagePath(string $value): string
{
return ltrim($this->assetUrlService()->storagePath($value), '/');
}
public function putUploadedFile(mixed $file, string $relativePath): void
{
$relativePath = $this->storagePath($relativePath);
if ($this->configService()->isOss()) {
$this->configService()->assertReady();
$realPath = $file->getRealPath();
if (!is_string($realPath) || $realPath === '' || !is_file($realPath)) {
throw new \RuntimeException('上传文件无效');
}
$this->ossClient()->uploadFile(
$this->configService()->bucket(),
$this->configService()->objectKey($relativePath),
$realPath
);
return;
}
if ($this->configService()->isQiniu()) {
$this->configService()->assertReady();
$realPath = $file->getRealPath();
if (!is_string($realPath) || $realPath === '' || !is_file($realPath)) {
throw new \RuntimeException('上传文件无效');
}
$key = $this->configService()->objectKey($relativePath);
$this->qiniuUploadFile($realPath, $key);
return;
}
$target = public_path() . '/' . $relativePath;
if (!is_dir(dirname($target))) {
mkdir(dirname($target), 0775, true);
}
$file->move($target);
}
public function putContents(string $relativePath, string $content): void
{
$relativePath = $this->storagePath($relativePath);
if ($this->configService()->isOss()) {
$this->configService()->assertReady();
$tmpFile = tempnam(sys_get_temp_dir(), 'anxinyan_oss_');
if ($tmpFile === false) {
throw new \RuntimeException('无法创建临时文件');
}
file_put_contents($tmpFile, $content);
try {
$this->ossClient()->uploadFile(
$this->configService()->bucket(),
$this->configService()->objectKey($relativePath),
$tmpFile
);
} finally {
if (file_exists($tmpFile)) {
@unlink($tmpFile);
}
}
return;
}
if ($this->configService()->isQiniu()) {
$this->configService()->assertReady();
$tmpFile = tempnam(sys_get_temp_dir(), 'anxinyan_qiniu_');
if ($tmpFile === false) {
throw new \RuntimeException('无法创建临时文件');
}
file_put_contents($tmpFile, $content);
try {
$key = $this->configService()->objectKey($relativePath);
$this->qiniuUploadFile($tmpFile, $key);
} finally {
if (file_exists($tmpFile)) {
@unlink($tmpFile);
}
}
return;
}
$target = public_path() . '/' . $relativePath;
if (!is_dir(dirname($target))) {
mkdir(dirname($target), 0775, true);
}
file_put_contents($target, $content);
}
public function exists(string $value): bool
{
$relativePath = $this->storagePath($value);
if ($relativePath === '') {
return false;
}
if ($this->configService()->isOss()) {
$this->configService()->assertReady();
return $this->ossClient()->doesObjectExist(
$this->configService()->bucket(),
$this->configService()->objectKey($relativePath)
);
}
if ($this->configService()->isQiniu()) {
$this->configService()->assertReady();
[$ret, $err] = $this->qiniuBucketManager()->stat(
$this->configService()->qiniuBucket(),
$this->configService()->objectKey($relativePath)
);
if ($err !== null && $this->shouldRetryQiniuWithoutHttps($err)) {
[$ret, $err] = $this->qiniuBucketManager(false)->stat(
$this->configService()->qiniuBucket(),
$this->configService()->objectKey($relativePath)
);
}
if ($err === null) {
return true;
}
if ((int)$err->code() === 612) {
return false;
}
throw new \RuntimeException('七牛云文件检查失败:' . $err->message());
}
return is_file(public_path() . '/' . $relativePath);
}
public function delete(string $value): void
{
$relativePath = $this->storagePath($value);
if ($relativePath === '') {
return;
}
if ($this->configService()->isOss()) {
$this->configService()->assertReady();
$this->ossClient()->deleteObject(
$this->configService()->bucket(),
$this->configService()->objectKey($relativePath)
);
return;
}
if ($this->configService()->isQiniu()) {
$this->configService()->assertReady();
[$ret, $err] = $this->qiniuBucketManager()->delete(
$this->configService()->qiniuBucket(),
$this->configService()->objectKey($relativePath)
);
if ($err !== null && $this->shouldRetryQiniuWithoutHttps($err)) {
[$ret, $err] = $this->qiniuBucketManager(false)->delete(
$this->configService()->qiniuBucket(),
$this->configService()->objectKey($relativePath)
);
}
if ($err !== null && (int)$err->code() !== 612) {
throw new \RuntimeException('七牛云删除失败:' . $err->message());
}
return;
}
$fullPath = public_path() . '/' . $relativePath;
if (file_exists($fullPath) && is_file($fullPath)) {
@unlink($fullPath);
}
}
private function ossClient(): OssClient
{
if ($this->ossClient instanceof OssClient) {
return $this->ossClient;
}
$this->configService()->assertReady();
$this->ensureCaBundleConfigured();
return $this->ossClient = new OssClient(
$this->configService()->accessKeyId(),
$this->configService()->accessKeySecret(),
$this->configService()->endpoint()
);
}
private function qiniuAuth(): QiniuAuth
{
if ($this->qiniuAuth instanceof QiniuAuth) {
return $this->qiniuAuth;
}
$this->configService()->assertReady();
$this->ensureCaBundleConfigured();
return $this->qiniuAuth = new QiniuAuth(
$this->configService()->qiniuAccessKey(),
$this->configService()->qiniuSecretKey()
);
}
private function qiniuConfig(bool $useHttps = true): QiniuConfig
{
$config = new QiniuConfig();
$config->useHTTPS = $useHttps;
$config->useCdnDomains = false;
return $config;
}
private function qiniuUploadManager(bool $useHttps = true): QiniuUploadManager
{
return new QiniuUploadManager($this->qiniuConfig($useHttps));
}
private function qiniuBucketManager(bool $useHttps = true): QiniuBucketManager
{
return new QiniuBucketManager(
$this->qiniuAuth(),
$this->qiniuConfig($useHttps)
);
}
private function qiniuUploadFile(string $filePath, string $key): void
{
$token = $this->qiniuAuth()->uploadToken($this->configService()->qiniuBucket(), $key);
try {
[$ret, $err] = $this->qiniuUploadManager()->putFile($token, $key, $filePath);
} catch (\Throwable $e) {
$err = $e;
}
if ($err !== null && $this->shouldRetryQiniuWithoutHttps($err)) {
try {
[$ret, $err] = $this->qiniuUploadManager(false)->putFile($token, $key, $filePath);
} catch (\Throwable $e) {
$err = $e;
}
}
if ($err !== null) {
throw new \RuntimeException('七牛云上传失败:' . $this->qiniuErrorMessage($err));
}
}
private function shouldRetryQiniuWithoutHttps(mixed $err): bool
{
return stripos($this->qiniuErrorMessage($err), 'SSL certificate problem') !== false;
}
private function qiniuErrorMessage(mixed $err): string
{
if ($err instanceof \Throwable) {
return $err->getMessage();
}
if (is_object($err) && method_exists($err, 'message')) {
return (string)$err->message();
}
if (is_string($err) && $err !== '') {
return $err;
}
return '未知错误';
}
private function assetUrlService(): PublicAssetUrlService
{
return new PublicAssetUrlService();
}
private function configService(): FileStorageConfigService
{
return new FileStorageConfigService();
}
private function ensureCaBundleConfigured(): void
{
if (self::$caBundleInitialized) {
return;
}
$currentCurlCa = (string)ini_get('curl.cainfo');
$currentOpensslCa = (string)ini_get('openssl.cafile');
if (($currentCurlCa !== '' && is_file($currentCurlCa)) || ($currentOpensslCa !== '' && is_file($currentOpensslCa))) {
self::$caBundleInitialized = true;
return;
}
$candidates = [
'/etc/ssl/cert.pem',
'/private/etc/ssl/cert.pem',
'/etc/ssl/certs/ca-certificates.crt',
'/etc/pki/tls/certs/ca-bundle.crt',
'/opt/homebrew/etc/openssl@3/cert.pem',
'/usr/local/etc/openssl@3/cert.pem',
];
foreach ($candidates as $path) {
if (!is_file($path)) {
continue;
}
@ini_set('curl.cainfo', $path);
@ini_set('openssl.cafile', $path);
self::$caBundleInitialized = true;
return;
}
}
}