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。
示例代码
以下代码示例实现了表单上传的完整过程,主要步骤如下:
创建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示例。