accessKeyId = getenv('OSS_ACCESSID') ?: ''; $this->accessKeySecret = getenv('OSS_ACCESSSECRET') ?: ''; $this->bucket = getenv('OSS_BUCKET') ?: ''; $this->endpoint = getenv('OSS_ENDPOINT') ?: ''; $this->staticUrl = getenv('OSS_STATIC_URL') ?: ''; $this->mainDir = rtrim(getenv('OSS_MAIN_DIR') ?: 'uploads', '/'); if (empty($this->accessKeyId) || empty($this->accessKeySecret) || empty($this->bucket) || empty($this->endpoint)) { throw new \Exception('OSS 配置不完整,请检查 .env 文件'); } } finally { error_reporting($oldLevel); } } /** * 获取 OSS Client 实例(延迟加载 + 错误抑制) */ private function getClient() { if ($this->ossClient === null) { $oldLevel = error_reporting(E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED); try { $this->ossClient = new \OSS\OssClient($this->accessKeyId, $this->accessKeySecret, $this->endpoint); } catch (\Throwable $e) { throw new \Exception('OSS 初始化失败: ' . $e->getMessage()); } finally { error_reporting($oldLevel); } } return $this->ossClient; } /** * 上传文件到 OSS * * @param string $filePath 本地文件路径 * @param string $objectKey OSS上存储的对象名称 * @return string 文件的公共访问 URL * @throws \Exception */ public function uploadFile(string $filePath, string $objectKey): string { $oldLevel = error_reporting(E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED); try { $options = [ \OSS\OssClient::OSS_HEADERS => [ 'Content-Type' => $this->getMimeType($filePath), ], ]; $this->getClient()->uploadFile($this->bucket, $objectKey, $filePath, $options); return $this->getPublicUrl($objectKey); } catch (\OSS\Core\OssException $e) { throw new \Exception('OSS 上传失败: ' . $e->getMessage()); } catch (\Throwable $e) { throw new \Exception('OSS 上传失败: ' . $e->getMessage()); } finally { error_reporting($oldLevel); } } /** * 删除 OSS 上的文件 * * @param string $objectKey OSS上存储的对象名称 * @return bool * @throws \Exception */ public function deleteFile(string $objectKey): bool { $oldLevel = error_reporting(E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED); try { $this->getClient()->deleteObject($this->bucket, $objectKey); return true; } catch (\OSS\Core\OssException $e) { throw new \Exception('OSS 删除失败: ' . $e->getMessage()); } catch (\Throwable $e) { throw new \Exception('OSS 删除失败: ' . $e->getMessage()); } finally { error_reporting($oldLevel); } } /** * 批量删除文件 * * @param array $objectKeys 对象名称数组 * @return bool * @throws \Exception */ public function deleteFiles(array $objectKeys): bool { $oldLevel = error_reporting(E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED); try { $this->getClient()->deleteObjects($this->bucket, $objectKeys); return true; } catch (\OSS\Core\OssException $e) { throw new \Exception('OSS 批量删除失败: ' . $e->getMessage()); } catch (\Throwable $e) { throw new \Exception('OSS 批量删除失败: ' . $e->getMessage()); } finally { error_reporting($oldLevel); } } /** * 获取文件的公共访问 URL * * @param string $objectKey * @return string */ public function getPublicUrl(string $objectKey): string { if (!empty($this->staticUrl)) { return rtrim($this->staticUrl, '/') . '/' . $objectKey; } return 'https://' . $this->bucket . '.' . str_replace('https://', '', $this->endpoint) . '/' . $objectKey; } /** * 生成带签名的访问 URL(用于私有bucket) * * @param string $objectKey * @param int $timeout 有效期(秒) * @return string */ public function getSignedUrl(string $objectKey, int $timeout = 3600): string { $oldLevel = error_reporting(E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED); try { return $this->getClient()->signUrl($this->bucket, $objectKey, $timeout); } catch (\Throwable $e) { return $this->getPublicUrl($objectKey); } finally { error_reporting($oldLevel); } } /** * 获取完整的对象键(包含主目录) * * @param string $path 相对路径 * @return string */ public function getObjectKey(string $path): string { $path = ltrim($path, '/'); return $this->mainDir ? ($this->mainDir . '/' . $path) : $path; } /** * 检查 OSS 是否已配置 * * @return bool */ public static function isConfigured(): bool { return !empty(getenv('OSS_ACCESSID')) && !empty(getenv('OSS_ACCESSSECRET')) && !empty(getenv('OSS_BUCKET')) && !empty(getenv('OSS_ENDPOINT')); } /** * 获取文件的 MIME 类型 * * @param string $filePath * @return string */ private function getMimeType(string $filePath): string { $extension = strtolower(pathinfo($filePath, PATHINFO_EXTENSION)); $mimeMap = [ 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'png' => 'image/png', 'gif' => 'image/gif', 'webp' => 'image/webp', 'bmp' => 'image/bmp', 'pdf' => 'application/pdf', 'doc' => 'application/msword', 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'xls' => 'application/vnd.ms-excel', 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'txt' => 'text/plain', 'zip' => 'application/zip', ]; return $mimeMap[$extension] ?? 'application/octet-stream'; } /** * 获取 OSS 主目录 * * @return string */ public function getMainDir(): string { return $this->mainDir; } }