全部產品
Search
文件中心

Object Storage Service:表單上傳(PHP SDK V2)

更新時間:Aug 05, 2025

OSS表單上傳允許網頁應用通過標準HTML表單直接將檔案上傳至OSS。本文介紹如何使用PHP SDK V2產生Post簽名和Post Policy等資訊,並調用HTTP Post方法上傳檔案到OSS。

注意事項

  • 本文範例程式碼以華東1(杭州)的地區IDcn-hangzhou為例,預設使用外網Endpoint,如果您希望通過與OSS同地區的其他阿里雲產品訪問OSS,請使用內網Endpoint。關於OSS支援的Region與Endpoint的對應關係,請參見OSS地區和訪問網域名稱

  • 通過表單上傳的方式上傳的Object大小不能超過5 GB。

  • 本文以從環境變數讀取存取憑證為例。更多配置訪問憑證的樣本,請參見PHP配置訪問憑證

範例程式碼

以下程式碼範例實現了表單上傳的完整過程,主要步驟如下:

  1. 建立Post Policy:定義上傳請求的有效時間和條件,包括儲存桶名稱、簽名版本、憑證資訊、請求日期和請求體長度範圍。

  2. 序列化並編碼Policy:將Policy序列化為JSON字串,並進行Base64編碼。

  3. 產生簽名密鑰:使用HMAC-SHA256演算法產生簽名密鑰,包括日期、地區、產品和請求類型。

  4. 計算簽名:使用產生的金鑰組Base64編碼後的Policy字串進行簽名,並將簽名結果轉換為十六進位字串。

  5. 構建請求體:建立一個multipart表單寫入器,添加對象鍵、策略、簽名版本、憑證資訊、請求日期和簽名到表單中,並將要上傳的資料寫入表單。

  6. 建立並執行請求:建立一個HTTP POST請求,佈建要求頭,並發送請求,檢查響應狀態代碼確保請求成功。

<?php

// 引入自動負載檔案,確保依賴庫能夠正確載入
require_once __DIR__ . '/../vendor/autoload.php';

use AlibabaCloud\Oss\V2 as Oss;

// 定義命令列參數的描述資訊
$optsdesc = [
    "region" => ['help' => 'The region in which the bucket is located.', 'required' => True], // Bucket所在的地區(必填)
    "bucket" => ['help' => 'The name of the bucket', 'required' => True], // Bucket名稱(必填)
    "key" => ['help' => 'The name of the object', 'required' => True], // 對象名稱(必填)
];

// 將參數描述轉換為getopt所需的長選項格式
// 每個參數後面加上":"表示該參數需要值
$longopts = \array_map(function ($key) {
    return "$key:";
}, array_keys($optsdesc));

// 解析命令列參數
$options = getopt("", $longopts);

// 驗證必填參數是否存在
foreach ($optsdesc as $key => $value) {
    if ($value['required'] === True && empty($options[$key])) {
        $help = $value['help']; // 擷取參數的協助資訊
        echo "Error: the following arguments are required: --$key, $help" . PHP_EOL;
        exit(1); // 如果必填參數缺失,則退出程式
    }
}

// 從解析的參數中提取值
$region = $options["region"]; // Bucket所在的地區
$bucket = $options["bucket"]; // Bucket名稱
$key = $options["key"];       // 對象名稱
$product = 'oss';             // 固定為OSS服務

// 載入環境變數中的憑證資訊
// 使用EnvironmentVariableCredentialsProvider從環境變數中讀取Access Key ID和Access Key Secret
$credentialsProvider = new Oss\Credentials\EnvironmentVariableCredentialsProvider();
$cred = $credentialsProvider->getCredentials();

// 要上傳的資料內容
$data = 'hi oss'; // 樣本資料,實際使用時可以替換為其他內容

// 擷取當前UTC時間並格式化
$utcTime = new DateTime('now', new DateTimeZone('UTC'));
$date = $utcTime->format('Ymd'); // 當前日期,用於簽名計算
$expiration = clone $utcTime;
$expiration->add(new DateInterval('PT1H')); // 設定到期時間為1小時後

// 構建Policy文檔
$policyMap = [
    "expiration" => $expiration->format('Y-m-d\TH:i:s.000\Z'), // Policy的到期時間
    "conditions" => [
        ["bucket" => $bucket], // 指定Bucket名稱
        ["x-oss-signature-version" => "OSS4-HMAC-SHA256"], // 簽名版本
        ["x-oss-credential" => sprintf("%s/%s/%s/%s/aliyun_v4_request",
            $cred->getAccessKeyId(), $date, $region, $product)], // 憑證資訊
        ["x-oss-date" => $utcTime->format('Ymd\THis\Z')], // 目前時間戳
        // 其他條件
        ["content-length-range", 1, 1024], // 檔案大小範圍限制
        // ["eq", "$success_action_status", "201"], // 可選:指定成功狀態代碼
        // ["starts-with", "$key", "user/eric/"], // 可選:指定對象Key的首碼
        // ["in", "$content-type", ["image/jpg", "image/png"]], // 可選:指定允許的內容類型
        // ["not-in", "$cache-control", ["no-cache"]], // 可選:排除某些緩衝控制頭
    ],
];

// 將Policy文檔編碼為JSON字串
$jsonOptions = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE; // 防止轉義斜杠和Unicode字元
$policy = json_encode($policyMap, $jsonOptions);
if (json_last_error() !== JSON_ERROR_NONE) {
    error_log("json_encode fail, err: " . json_last_error_msg()); // 檢查JSON編碼是否失敗
    exit(1);
}

// 計算簽名所需的資訊
$stringToSign = base64_encode($policy); // 對Policy進行Base64編碼
$signingKey = "aliyun_v4" . $cred->getAccessKeySecret(); // 建構簽章密鑰
$h1Key = hmacSign($signingKey, $date); // 第一步:對日期簽名
$h2Key = hmacSign($h1Key, $region);   // 第二步:對地區簽名
$h3Key = hmacSign($h2Key, $product);  // 第三步:對產品簽名
$h4Key = hmacSign($h3Key, "aliyun_v4_request"); // 第四步:對請求籤名

// 計算最終簽名
$signature = hash_hmac('sha256', $stringToSign, $h4Key);

// 構建POST請求的表單資料
$bodyWriter = new CURLFileUpload(); // 建立表單構建器執行個體

// 添加欄位到表單
$bodyWriter->addField('key', $key); // 對象名稱
$bodyWriter->addField('policy', $stringToSign); // Base64編碼後的Policy
$bodyWriter->addField('x-oss-signature-version', 'OSS4-HMAC-SHA256'); // 簽名版本
$bodyWriter->addField('x-oss-credential', sprintf("%s/%s/%s/%s/aliyun_v4_request",
    $cred->getAccessKeyId(), $date, $region, $product)); // 憑證資訊
$bodyWriter->addField('x-oss-date', $utcTime->format('Ymd\THis\Z')); // 時間戳記
$bodyWriter->addField('x-oss-signature', $signature); // 最終簽名

// 添加檔案內容到表單
$bodyWriter->addFileFromString('file', $data); // 上傳的檔案內容
$postData = $bodyWriter->getFormData(); // 擷取完整的表單資料

// 發送POST請求
$client = new \GuzzleHttp\Client(); // 建立HTTP用戶端
$response = $client->post(
    sprintf("http://%s.oss-%s.aliyuncs.com/", $bucket, $region), // OSS的上傳地址
    [
        'headers' => [
            'content-type' => $bodyWriter->getContentType(), // 設定Content-Type
        ],
        'body' => $postData // 佈建要求體
    ]
);

// 檢查響應狀態代碼
if ($response->getStatusCode() < 200 || $response->getStatusCode() >= 300) {
    echo "Post Object Fail, status code:" . $response->getStatusCode() . ", reason: " . $response->getReasonPhrase() . PHP_EOL;
    exit(1); // 如果狀態代碼不在2xx範圍內,則退出程式
}

// 列印上傳結果
echo "post object done, status code:" . $response->getStatusCode() . ", request id:" . $response->getHeaderLine('x-oss-request-id') . PHP_EOL;

/**
 * HMAC簽名函數
 * @param string $key 簽名密鑰
 * @param string $data 待簽名資料
 * @return string 返回簽名結果
 */
function hmacSign($key, $data)
{
    return hash_hmac('sha256', $data, $key, true); // 使用SHA256演算法產生HMAC簽名
}

/**
 * 表單構建器類,用於產生multipart/form-data格式的請求體
 */
class CURLFileUpload
{
    private $fields = []; // 儲存普通欄位
    private $files = [];  // 隱藏檔欄位
    private $boundary;    // 分隔字元

    public function __construct()
    {
        $this->boundary = uniqid(); // 產生唯一的分隔字元
    }

    /**
     * 添加普通欄位
     * @param string $name 欄位名稱
     * @param string $value 欄位值
     */
    public function addField($name, $value)
    {
        $this->fields[$name] = $value;
    }

    /**
     * 添加檔案欄位
     * @param string $name 欄位名稱
     * @param string $content 檔案內容
     */
    public function addFileFromString($name, $content)
    {
        $this->files[$name] = [
            'content' => $content,
            'filename' => $name,
            'type' => 'application/octet-stream' // 預設MIME類型
        ];
    }

    /**
     * 擷取完整的表單資料
     * @return string 返回multipart/form-data格式的請求體
     */
    public function getFormData()
    {
        $data = '';
        foreach ($this->fields as $name => $value) {
            $data .= "--{$this->boundary}\r\n";
            $data .= "Content-Disposition: form-data; name=\"$name\"\r\n\r\n";
            $data .= $value . "\r\n";
        }
        foreach ($this->files as $name => $file) {
            $data .= "--{$this->boundary}\r\n";
            $data .= "Content-Disposition: form-data; name=\"$name\"; filename=\"{$file['filename']}\"\r\n";
            $data .= "Content-Type: {$file['type']}\r\n\r\n";
            $data .= $file['content'] . "\r\n";
        }
        $data .= "--{$this->boundary}--\r\n";
        return $data;
    }

    /**
     * 擷取Content-Type頭部
     * @return string 返回Content-Type值
     */
    public function getContentType()
    {
        return "multipart/form-data; boundary={$this->boundary}";
    }
}

相關文檔

  • 關於表單上傳的完整樣本,請參見Github樣本