OSS表單上傳允許網頁應用通過標準HTML表單直接將檔案上傳至OSS。本文介紹如何使用PHP SDK V2產生Post簽名和Post Policy等資訊,並調用HTTP Post方法上傳檔案到OSS。
注意事項
本文範例程式碼以華東1(杭州)的地區ID
cn-hangzhou為例,預設使用外網Endpoint,如果您希望通過與OSS同地區的其他阿里雲產品訪問OSS,請使用內網Endpoint。關於OSS支援的Region與Endpoint的對應關係,請參見OSS地區和訪問網域名稱。通過表單上傳的方式上傳的Object大小不能超過5 GB。
本文以從環境變數讀取存取憑證為例。更多配置訪問憑證的樣本,請參見PHP配置訪問憑證。
範例程式碼
以下程式碼範例實現了表單上傳的完整過程,主要步驟如下:
建立Post Policy:定義上傳請求的有效時間和條件,包括儲存桶名稱、簽名版本、憑證資訊、請求日期和請求體長度範圍。
序列化並編碼Policy:將Policy序列化為JSON字串,並進行Base64編碼。
產生簽名密鑰:使用HMAC-SHA256演算法產生簽名密鑰,包括日期、地區、產品和請求類型。
計算簽名:使用產生的金鑰組Base64編碼後的Policy字串進行簽名,並將簽名結果轉換為十六進位字串。
構建請求體:建立一個multipart表單寫入器,添加對象鍵、策略、簽名版本、憑證資訊、請求日期和簽名到表單中,並將要上傳的資料寫入表單。
建立並執行請求:建立一個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樣本。