您现在的位置是:亿华云 > 系统运维

PHP实现服务端签名阿里云OSS直传实践

亿华云2025-10-03 02:20:24【系统运维】2人已围观

简介概述本教程介绍如何在Web端通过表单上传方式直接上传数据到OSS。Web端常见的上传方法是用户在浏览器或App端上传文件到应用服务器,应用服务器再把文件上传到OSS。这种方式需通过应用服务器中转,传输

概述

本教程介绍如何在Web端通过表单上传方式直接上传数据到OSS。实实践Web端常见的现服上传方法是用户在浏览器或App端上传文件到应用服务器,应用服务器再把文件上传到OSS。签名这种方式需通过应用服务器中转,阿里传输效率明显低于数据直传至OSS的直传方式。

这里使用在服务端完成签名,实实践然后通过表单直传数据到OSS

服务端签名直传

服务端签名直传是现服指在服务端生成签名,将签名返回给客户端,签名然后客户端使用签名上传文件到OSS。阿里由于服务端签名直传无需将访问密钥暴露在前端页面,直传相比客户端签名直传具有更高的实实践安全性。本文介绍如何进行服务端签名直传。网站模板现服

请求流程

服务端签名直传流程如下图所示

图片来源:阿里云

获取上传策略签名 复制/** * @desc 获取上传策略签名 * @param array $param * @return array * @author Tinywan(ShaoBo Wan) */ public static function getUploadPolicy(array $param): array { $param[type] = images; $config = [ accessKeyId => xxxxxxxxxxxxxxx,签名 accessKeySecret => xxxxxxxxxxxxxxxxxxxxxxxxxxx, callbackUrl => https://oss.tinywan.com/aliyun/oss-upload-callback, images => [ bucket => images, host => https://images.tinywan.com, endpoint => oss-cn-hangzhou.aliyuncs.com, ContentLengthMin => 10, ContentLengthMax => 20 * 1024 * 1024, ] ]; $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]); // 过期时间 $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); $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); return [ access_id => $config[accessKeyId], host => https:// . $bucket[bucket] . . . $bucket[endpoint], // $host的格式为 bucketname.endpoint policy => $policyBase64, signature => $signature, expire => $expiration, callback => $base64CallbackBody, dir => $dir, key => $key, url => $bucket[host] . DIRECTORY_SEPARATOR . $key ]; }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.53.54.55.56.57.58.59. 获取参数base64 复制/** * @desc: 获取参数base64 * @param $policyParams * @return string * @author Tinywan(ShaoBo Wan) */ private static function getPolicyBase64($policyParams): string { return base64_encode(json_encode($policyParams)); }1.2.3.4.5.6.7.8.9.10. 获取签名 复制/** * @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)); }1.2.3.4.5.6.7.8.9.10.11. 获取过期时间 复制/** * @desc: 获取过期时间 * @param int $time * @return array|false|string|string[] * @author Tinywan(ShaoBo Wan) */ private static function getExpireTime(int $time) { return str_replace(+00:00, .000Z, gmdate(c, time() + $time)); }1.2.3.4.5.6.7.8.9.10. 获取按照月份分隔的文件夹路径 复制/** * @desc: 获取按照月份分隔的文件夹路径 * @param string $type * @param string $directoryName eg: 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; }1.2.3.4.5.6.7.8.9.10.11.12.13.14. 获取一个随机的文件名 复制/** * @desc: 获取一个随机的文件名 * @param string $extend eg: jpg * @return string * @author Tinywan(ShaoBo Wan) */ private static function getRandomFilename(string $extend): string { return \Ramsey\Uuid\Uuid::uuid4()->toString() . . . $extend; }1.2.3.4.5.6.7.8.9.10.

微信小程序客户端

请求上传参数 复制{ "type": "images", "dirname": "images", "ext": "png" }1.2.3.4.5. 请求上传响应 复制{ "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-48d9-83a3-cde7cbc298e7.png", "url": "https://images.tinywan.com/ai/2024-05/14b796b1-54ef-48d9-83a3-cde7cbc298e7.png" } }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15. 示例实现

客户端拿到文件名后缀后,传给服务端,阿里获取签名和文件名等必要的直传上传参数,让更多的工作在服务端完成

复制/** 获取上传文件扩展名 */ function getFilePathExtention(filePath) { return filePath.split(.).slice(-1)[0]; } /** 上传到阿里云oss */ function uploadFileAsync(config, filePath) { console.log(config); return new Promise((resolve, reject) => { wx.uploadFile({ url: config.host, // 开发者服务器的URL。 filePath: filePath, name: file, // 必须填file。 formData: { key: config.key, policy: config.policy, OSSAccessKeyId: config.accessKeyId, signature: config.signature }, success: (res) => { console.log(res); if (res.statusCode === 204) { resolve(); } else { reject(上传失败); } }, fail: (err) => { console.log(err); }, }); }); } /** 上传文件 */ export async function uploadFile(filePath, dirname = image) { console.log(filePath); let ext = getFilePathExtention(filePath); // 改方法通过接口获取服务端生成的上传签名 const resParams = await Http.AliOssGetUploadParams({ ext, dirname, }); await uploadFileAsync(resParams.data, filePath); return resParams; }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.

上传回调

大多数情况下,用户上传文件后,源码库应用服务器需要知道用户上传了哪些文件以及文件名;如果上传了图片,还需要知道图片的大小等,为此OSS提供了上传回调方案。

流程介绍

当用户要上传一个文件到OSS,而且希望将上传的结果返回给应用服务器时,需要设置一个回调函数,将请求告知应用服务器。用户上传完文件后,不会直接得到返回结果,而是先通知应用服务器,再把结果转达给用户。

图片

参考代码 复制/** * @desc: OSS上传回调 * @throws ForbiddenHttpException * @return Response * @author Tinywan(ShaoBo Wan) */ public function ossUploadCallback(): Response { // 1.请求头参数 $header = $this->request->header(); $authorizationBase64 = $header[authorization] ?? ; $pubKeyUrlBase64 = $header[x-oss-pub-key-url] ?? ; if ($authorizationBase64 == || $pubKeyUrlBase64 == ) { throw new \tinywan\exception\ForbiddenHttpException(); } // 2.获取OSS的签名 $authorization = base64_decode($authorizationBase64); // 3.获取公钥 $pubKeyUrl = base64_decode($pubKeyUrlBase64); $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $pubKeyUrl); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); $publicKey = curl_exec($ch); if ($publicKey == ) { throw new \tinywan\exception\ForbiddenHttpException(); } // 4.获取回调body $body = $this->request->getInput(); // 5.拼接待签名字符串 $path = $_SERVER[REQUEST_URI]; $pos = strpos($path, ?); if ($pos === false) { $authStr = urldecode($path) . "\n" . $body; } else { $authStr = urldecode(substr($path, 0, $pos)) . substr($path, $pos, strlen($path) - $pos) . "\n" . $body; } // 6.验证签名 $verifyRes = openssl_verify($authStr, $authorization, $publicKey, OPENSSL_ALGO_MD5); if ($verifyRes === 1) { return response_json(success, 200, $this->request->post()); } throw new \tinywan\exception\ForbiddenHttpException(); }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.高防服务器

很赞哦!(7)