You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

寻求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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 14:07:58