寻求PHP环境下轻量AWS S3 API替代方案
轻量实现S3兼容服务的备份操作(PHP)
轻量库推荐
- 单文件S3客户端:社区有不少仅包含V4签名和核心操作的单文件PHP脚本,体积通常在50KB以内,无需Composer依赖,直接引入就能实现上传、删除、生成预签名下载链接的需求,完全适配小型项目的轻量化要求。
- League Flysystem S3 Adapter:如果可以接受少量依赖,这个选项更友好。Flysystem核心约1MB,加上S3适配器总大小不到5MB,提供统一的文件系统接口,后续切换存储服务成本更低。
原生PHP实现方案
如果不想引入任何第三方库,可以自己实现S3 V4签名逻辑,封装核心功能。以下是完整的可复用代码:
核心客户端类
class SimpleS3Client { private $accessKey; private $secretKey; private $endpoint; private $region; private $bucket; public function __construct($accessKey, $secretKey, $endpoint, $region, $bucket) { $this->accessKey = $accessKey; $this->secretKey = $secretKey; $this->endpoint = rtrim($endpoint, '/'); $this->region = $region; $this->bucket = $bucket; } // 生成AWS V4签名 private function signRequest($method, $uri, $headers = [], $payload = '') { $timestamp = gmdate('Ymd\THis\Z'); $date = substr($timestamp, 0, 8); // 构建规范请求 $canonicalHeaders = array_change_key_case($headers, CASE_LOWER); array_walk($canonicalHeaders, function(&$val) { $val = trim($val); }); ksort($canonicalHeaders); $canonicalHeaderStr = ''; $signedHeaders = []; foreach ($canonicalHeaders as $key => $value) { $canonicalHeaderStr .= "$key:$value\n"; $signedHeaders[] = $key; } $signedHeadersStr = implode(';', $signedHeaders); $hashedPayload = hash('sha256', $payload); $canonicalRequest = "$method\n$uri\n\n$canonicalHeaderStr\n$signedHeadersStr\n$hashedPayload"; // 构建待签字符串 $algorithm = 'AWS4-HMAC-SHA256'; $credentialScope = "$date/{$this->region}/s3/aws4_request"; $hashedCanonicalRequest = hash('sha256', $canonicalRequest); $stringToSign = "$algorithm\n$timestamp\n$credentialScope\n$hashedCanonicalRequest"; // 生成签名密钥链 $kSecret = "AWS4{$this->secretKey}"; $kDate = hash_hmac('sha256', $date, $kSecret, true); $kRegion = hash_hmac('sha256', $this->region, $kDate, true); $kService = hash_hmac('sha256', 's3', $kRegion, true); $kSigning = hash_hmac('sha256', 'aws4_request', $kService, true); $signature = hash_hmac('sha256', $stringToSign, $kSigning); // 组装Authorization头 $headers['Authorization'] = "$algorithm Credential={$this->accessKey}/$credentialScope, SignedHeaders=$signedHeadersStr, Signature=$signature"; $headers['X-Amz-Date'] = $timestamp; return $headers; } // 上传本地文件到S3 public function uploadFile($localFilePath, $s3ObjectKey) { $fileContent = file_get_contents($localFilePath); if ($fileContent === false) { throw new RuntimeException('无法读取本地文件'); } $uri = "/{$this->bucket}/" . rawurlencode($s3ObjectKey); $headers = [ 'Host' => parse_url($this->endpoint, PHP_URL_HOST), 'Content-Length' => strlen($fileContent), 'Content-Type' => mime_content_type($localFilePath) ]; $signedHeaders = $this->signRequest('PUT', $uri, $headers, $fileContent); $ch = curl_init($this->endpoint . $uri); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT'); curl_setopt($ch, CURLOPT_POSTFIELDS, $fileContent); curl_setopt($ch, CURLOPT_HTTPHEADER, array_map(fn($k, $v) => "$k: $v", array_keys($signedHeaders), $signedHeaders)); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 生产环境建议根据实际情况调整 curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); return $httpCode >= 200 && $httpCode < 300; } // 删除S3上的文件 public function deleteFile($s3ObjectKey) { $uri = "/{$this->bucket}/" . rawurlencode($s3ObjectKey); $headers = [ 'Host' => parse_url($this->endpoint, PHP_URL_HOST) ]; $signedHeaders = $this->signRequest('DELETE', $uri, $headers); $ch = curl_init($this->endpoint . $uri); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE'); curl_setopt($ch, CURLOPT_HTTPHEADER, array_map(fn($k, $v) => "$k: $v", array_keys($signedHeaders), $signedHeaders)); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); return $httpCode >= 200 && $httpCode < 300; } // 生成预签名下载链接(默认有效期1小时) public function getPresignedDownloadUrl($s3ObjectKey, $expires = 3600) { $expiresAt = time() + $expires; $timestamp = gmdate('Ymd\THis\Z'); $date = substr($timestamp, 0, 8); $uri = "/{$this->bucket}/" . rawurlencode($s3ObjectKey); $queryParams = [ 'X-Amz-Algorithm' => 'AWS4-HMAC-SHA256', 'X-Amz-Credential' => "{$this->accessKey}/$date/{$this->region}/s3/aws4_request", 'X-Amz-Date' => $timestamp, 'X-Amz-Expires' => $expires, 'X-Amz-SignedHeaders' => 'host' ]; ksort($queryParams); $queryString = http_build_query($queryParams); $canonicalRequest = "GET\n$uri\n$queryString\nhost:" . parse_url($this->endpoint, PHP_URL_HOST) . "\n\nhost\nUNSIGNED-PAYLOAD"; $algorithm = 'AWS4-HMAC-SHA256'; $credentialScope = "$date/{$this->region}/s3/aws4_request"; $hashedCanonicalRequest = hash('sha256', $canonicalRequest); $stringToSign = "$algorithm\n$timestamp\n$credentialScope\n$hashedCanonicalRequest"; // 生成签名密钥链 $kSecret = "AWS4{$this->secretKey}"; $kDate = hash_hmac('sha256', $date, $kSecret, true); $kRegion = hash_hmac('sha256', $this->region, $kDate, true); $kService = hash_hmac('sha256', 's3', $kRegion, true); $kSigning = hash_hmac('sha256', 'aws4_request', $kService, true); $signature = hash_hmac('sha256', $stringToSign, $kSigning); $queryParams['X-Amz-Signature'] = $signature; return $this->endpoint . $uri . '?' . http_build_query($queryParams); } }
使用示例
// 初始化客户端 $s3Client = new SimpleS3Client( '你的AccessKey', '你的SecretKey', 'https://你的S3兼容服务端点', '你的区域(如us-east-1)', '目标存储桶名称' ); // 上传备份文件 try { $uploadSuccess = $s3Client->uploadFile('/path/to/backup.zip', 'backups/20240520_backup.zip'); echo $uploadSuccess ? '文件上传成功' : '上传失败'; } catch (RuntimeException $e) { echo '上传出错:' . $e->getMessage(); } // 删除指定备份文件 $deleteSuccess = $s3Client->deleteFile('backups/20240520_backup.zip'); echo $deleteSuccess ? '文件删除成功' : '删除失败'; // 获取有效1小时的下载链接 $downloadUrl = $s3Client->getPresignedDownloadUrl('backups/20240520_backup.zip'); echo '备份文件下载链接:' . $downloadUrl;
关键注意事项
- SSL验证:示例中关闭了
CURLOPT_SSL_VERIFYPEER,生产环境如果使用合法证书,建议设置为true以保证安全性。 - 签名兼容性:大部分主流S3兼容服务(MinIO、Ceph RGW、阿里云OSS等)都支持V4签名,若使用仅支持V2签名的服务,需要调整签名逻辑。
- 错误处理:示例代码的错误处理较为基础,实际项目中可根据HTTP状态码和服务返回的错误信息做更精准的异常捕获和处理。
内容的提问来源于stack exchange,提问作者Zortext
相关产品推荐
相关产品推荐

