PHP实现服务端签名阿里云OSS直传实践
Web 端通过表单直传数据到 OSS 的实现方案
一、概述
本教程介绍如何在 Web 端通过表单上传方式直接将数据上传到 OSS。传统的 Web 端上传流程是用户将文件上传到应用服务器,再由应用服务器转发至 OSS,这种方式经应用服务器中转,传输效率较低。而本文将采用服务端签名直传方式,即先在服务端生成签名,再由客户端使用该签名直接将文件上传到 OSS,不仅提高传输效率,还能避免访问密钥暴露在前端,安全性更高。
二、服务端签名直传流程
服务端签名直传的核心流程为:
客户端向服务端请求上传所需的签名及相关参数;
服务端生成上传策略、签名和回调信息,返回给客户端;
客户端使用获取到的签名和参数,通过表单直接将文件上传到 OSS;
上传完成后,OSS 根据配置的回调地址通知应用服务器,完成后续处理。
三、服务端实现:生成上传策略与签名
服务端的核心任务是生成上传策略、Base64 编码的策略字符串、签名以及回调参数,具体实现如下:
1. 获取上传策略签名(核心方法)
该方法整合了上传配置、路径生成、策略构建、签名计算和回调参数设置等功能:
php
/**
* @desc 获取上传策略签名
* @param array $param
* @return array
* @author Tinywan(ShaoBo Wan)
*/
public static function getUploadPolicy(array $param): array
{
$param['type'] = 'images';
$config = [
'accessKeyId' => 'xxxxxxxxxxxxxxx', // OSS访问密钥ID
'accessKeySecret' => 'xxxxxxxxxxxxxxxxxxxxxxxxxxx', // OSS访问密钥Secret
'callbackUrl' => 'https://oss.tinywan.com/aliyun/oss-upload-callback', // 上传回调地址
'images' => [
'bucket' => 'images', // OSS存储桶名称
'host' => 'https://images.tinywan.com', // 访问域名
'endpoint' => 'oss-cn-hangzhou.aliyuncs.com', // OSS地域节点
'ContentLengthMin' => 10, // 最小文件大小(字节)
'ContentLengthMax' => 20 * 1024 * 1024, // 最大文件大小(20MB)
]
];
$bucket = $config[$param['type']];
// 生成文件路径和随机文件名
$dir = self::PROJECT_BUCKET . DIRECTORY_SEPARATOR . env('env_name') . DIRECTORY_SEPARATOR
. self::getDirectoryPath($param['type'], $param['dirname']); // 文件夹路径(按月份分类)
$key = $dir . self::getRandomFilename($param['ext']); // 完整文件路径(含随机文件名)
// 计算过期时间(UTC格式)
$expiration = self::getExpireTime(self::EXPIRE_TIME);
// 构建上传策略
$policyParams = [
'expiration' => $expiration, // 策略过期时间
'conditions' => [
['starts-with', '$key', $dir], // 限制文件路径前缀
['content-length-range', $bucket['ContentLengthMin'], $bucket['ContentLengthMax']] // 限制文件大小范围
]
];
$policyBase64 = self::getPolicyBase64($policyParams); // 策略Base64编码
$signature = self::getSignature($policyBase64, $config['accessKeySecret']); // 计算签名
// 构建回调参数
$callbackParam = [
'callbackUrl' => $config['callbackUrl'],
'callbackBody' => 'filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}', // 回调参数
'callbackBodyType' => 'application/x-www-form-urlencoded', // 回调数据格式
];
$callbackString = json_encode($callbackParam);
$base64CallbackBody = base64_encode($callbackString); // 回调参数Base64编码
return [
'access_id' => $config['accessKeyId'],
'host' => 'https://' . $bucket['bucket'] . '.' . $bucket['endpoint'], // OSS上传地址
'policy' => $policyBase64,
'signature' => $signature,
'expire' => $expiration,
'callback' => $base64CallbackBody,
'dir' => $dir,
'key' => $key,
'url' => $bucket['host'] . DIRECTORY_SEPARATOR . $key // 上传后的文件访问地址
];
}
2. 辅助方法实现
策略 Base64 编码:将上传策略转换为 Base64 字符串,用于生成签名:
php
/**
* @desc 获取参数base64
* @param $policyParams
* @return string
* @author Tinywan(ShaoBo Wan)
*/
private static function getPolicyBase64($policyParams): string
{
return base64_encode(json_encode($policyParams));
}
生成签名:使用 OSS 访问密钥 Secret 对 Base64 编码的策略进行 HMAC-SHA1 加密,并对结果进行 Base64 编码:
php
/**
* @desc 获取签名
* @param string $policyBase64
* @param string $accessKeySecret
* @return string
* @author Tinywan(ShaoBo Wan)
*/
private static function getSignature(string $policyBase64, string $accessKeySecret): string
{
return base64_encode(hash_hmac('sha1', $policyBase64, $accessKeySecret, true));
}
计算过期时间:生成 UTC 格式的策略过期时间(当前时间 + 有效期):
php
/**
* @desc 获取过期时间
* @param int $time 有效期(秒)
* @return string
* @author Tinywan(ShaoBo Wan)
*/
private static function getExpireTime(int $time)
{
return str_replace('+00:00', '.000Z', gmdate('c', time() + $time));
}
生成文件夹路径:按文件类型和月份分类存储,便于管理:
php
/**
* @desc 获取按照月份分隔的文件夹路径
* @param string $type 文件类型
* @param string $directoryName 自定义目录名(如img/video)
* @return string
* @author Tinywan(ShaoBo Wan)
*/
private static function getDirectoryPath(string $type, string $directoryName): string
{
if ($type === 'img') {
return $directoryName . DIRECTORY_SEPARATOR . date('Y-m') . DIRECTORY_SEPARATOR;
}
return date('Y-m') . DIRECTORY_SEPARATOR;
}
生成随机文件名:使用 UUID 确保文件名唯一,避免冲突:
php
/**
* @desc 获取一个随机的文件名
* @param string $extend 文件扩展名(如jpg)
* @return string
* @author Tinywan(ShaoBo Wan)
*/
private static function getRandomFilename(string $extend): string
{
return \Ramsey\Uuid\Uuid::uuid4()->toString() . '.' . $extend;
}
四、客户端实现:微信小程序上传文件
客户端(以微信小程序为例)需先向服务端请求上传参数,再使用wx.uploadFile接口将文件直传至 OSS。
1. 请求上传参数
客户端向服务端发送文件类型、目录名和扩展名,获取签名等参数:
请求参数:
json
{
"type": "images",
"dirname": "images",
"ext": "png"
}
服务端响应:
json
{
"code": 0,
"msg": "success",
"data": {
"access_id": "xxxxxxxxxxxxxxxxx",
"host": "https://tinywan-images.oss-cn-hangzhou.aliyuncs.com",
"policy": "eyJlexxxxxxxxxxxxxxxxxxxx==",
"signature": "YrlQxxxxxxxxxxxxxxxxxxxxxxtiI=",
"expire": "2024-05-22T09:46:46.000Z",
"callback": "eyJxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx0=",
"dir": "ai/2024-05/",
"key": "ai/2024-05/14b796b1-54ef-48d



