{"id":10020875,"date":"2025-07-03T22:15:38","date_gmt":"2025-07-03T14:15:38","guid":{"rendered":"https:\/\/itmax.cn\/?p=10020875"},"modified":"2025-07-03T22:15:38","modified_gmt":"2025-07-03T14:15:38","slug":"laravel-%e9%98%bf%e9%87%8c%e4%ba%91-oss-%e8%a7%86%e9%a2%91%e4%b8%8a%e4%bc%a0%e5%ae%8c%e6%95%b4%e6%96%b9%e6%a1%88","status":"publish","type":"post","link":"https:\/\/itmax.cn\/?p=10020875","title":{"rendered":"Laravel \u963f\u91cc\u4e91 OSS \u89c6\u9891\u4e0a\u4f20\u5b8c\u6574\u65b9\u6848"},"content":{"rendered":"<h2>\u4e00\u3001\u73af\u5883\u51c6\u5907<\/h2>\n<h3>1.1 \u5b89\u88c5 OSS SDK<\/h3>\n<pre><code class=\"bash\">composer require aliyuncs\/oss-sdk-php<\/code><\/pre>\n<h3>1.2 \u73af\u5883\u914d\u7f6e<\/h3>\n<p>\u5728 <code>.env<\/code> \u6587\u4ef6\u4e2d\u6dfb\u52a0\uff1a<\/p>\n<pre><code class=\"env\">OSS_ACCESS_KEY_ID=\u4f60\u7684AccessKeyId\nOSS_ACCESS_KEY_SECRET=\u4f60\u7684AccessKeySecret\nOSS_ENDPOINT=oss-cn-hangzhou.aliyuncs.com\nOSS_BUCKET=\u4f60\u7684Bucket\u540d\u79f0\nOSS_IS_PRIVATE=false<\/code><\/pre>\n<h3>1.3 \u521b\u5efa\u914d\u7f6e\u6587\u4ef6<\/h3>\n<p>\u521b\u5efa <code>config\/oss.php<\/code>\uff1a<\/p>\n<pre><code class=\"php\">&lt;?php\nreturn [\n    'access_key_id' =&gt; env('OSS_ACCESS_KEY_ID'),\n    'access_key_secret' =&gt; env('OSS_ACCESS_KEY_SECRET'),\n    'endpoint' =&gt; env('OSS_ENDPOINT'),\n    'bucket' =&gt; env('OSS_BUCKET'),\n    'is_private' =&gt; env('OSS_IS_PRIVATE', false),\n];<\/code><\/pre>\n<h2>\u4e8c\u3001\u5206\u7247\u4e0a\u4f20\u5927\u89c6\u9891<\/h2>\n<h3>2.1 \u57fa\u7840\u5206\u7247\u4e0a\u4f20\u4ee3\u7801<\/h3>\n<pre><code class=\"php\">&lt;?php\nuse OSS\/OssClient;\nuse OSS\/Core\/OssException;\n\nclass VideoUploader\n{\n    private $ossClient;\n    private $bucket;\n\n    public function __construct()\n    {\n        $this-&gt;ossClient = new OssClient(\n            config('oss.access_key_id'),\n            config('oss.access_key_secret'),\n            config('oss.endpoint')\n        );\n        $this-&gt;bucket = config('oss.bucket');\n    }\n\n    \/**\n     * \u5206\u7247\u4e0a\u4f20\u89c6\u9891\u6587\u4ef6\n     *\/\n    public function multipartUpload($filePath, $objectName)\n    {\n        try {\n            \/\/ \u521d\u59cb\u5316\u5206\u7247\u4e0a\u4f20\n            $uploadId = $this-&gt;ossClient-&gt;initiateMultipartUpload($this-&gt;bucket, $objectName);\n\n            $partSize = 5 * 1024 * 1024; \/\/ \u6bcf\u72475MB\n            $uploadFileSize = filesize($filePath);\n            $pieces = $this-&gt;ossClient-&gt;generateMultiuploadParts($uploadFileSize, $partSize);\n            $uploadParts = [];\n\n            \/\/ \u9010\u4e2a\u4e0a\u4f20\u5206\u7247\n            foreach ($pieces as $i =&gt; $piece) {\n                $fromPos = $piece[$this-&gt;ossClient::OSS_SEEK_TO];\n                $toPos = $piece[$this-&gt;ossClient::OSS_LENGTH];\n                \n                $uploadPart = fopen($filePath, 'r');\n                fseek($uploadPart, $fromPos);\n                \n                $eTag = $this-&gt;ossClient-&gt;uploadPart($this-&gt;bucket, $objectName, $uploadId, [\n                    'partNumber' =&gt; ($i + 1),\n                    'uploadPart' =&gt; $uploadPart,\n                    'length' =&gt; $toPos,\n                ]);\n                \n                fclose($uploadPart);\n                \n                $uploadParts[] = [\n                    'PartNumber' =&gt; ($i + 1),\n                    'ETag' =&gt; $eTag,\n                ];\n            }\n\n            \/\/ \u5b8c\u6210\u5206\u7247\u4e0a\u4f20\n            $result = $this-&gt;ossClient-&gt;completeMultipartUpload(\n                $this-&gt;bucket, \n                $objectName, \n                $uploadId, \n                $uploadParts\n            );\n\n            return [\n                'success' =&gt; true,\n                'url' =&gt; $this-&gt;getPublicUrl($objectName),\n                'etag' =&gt; $result\n            ];\n\n        } catch (OssException $e) {\n            return [\n                'success' =&gt; false,\n                'error' =&gt; $e-&gt;getMessage()\n            ];\n        }\n    }\n\n    private function getPublicUrl($objectName)\n    {\n        return &quot;https:\/\/{$this-&gt;bucket}.&quot; . config('oss.endpoint') . &quot;\/{$objectName}&quot;;\n    }\n}<\/code><\/pre>\n<h3>2.2 \u4f18\u5316\u7248\u672c\uff08\u5e26\u8fdb\u5ea6\u56de\u8c03\uff09<\/h3>\n<pre><code class=\"php\">public function multipartUploadWithProgress($filePath, $objectName, $progressCallback = null)\n{\n    try {\n        $uploadId = $this-&gt;ossClient-&gt;initiateMultipartUpload($this-&gt;bucket, $objectName);\n        $partSize = 5 * 1024 * 1024;\n        $uploadFileSize = filesize($filePath);\n        $pieces = $this-&gt;ossClient-&gt;generateMultiuploadParts($uploadFileSize, $partSize);\n        $uploadParts = [];\n\n        foreach ($pieces as $i =&gt; $piece) {\n            $fromPos = $piece[$this-&gt;ossClient::OSS_SEEK_TO];\n            $toPos = $piece[$this-&gt;ossClient::OSS_LENGTH];\n            \n            $uploadPart = fopen($filePath, 'r');\n            fseek($uploadPart, $fromPos);\n            \n            $eTag = $this-&gt;ossClient-&gt;uploadPart($this-&gt;bucket, $objectName, $uploadId, [\n                'partNumber' =&gt; ($i + 1),\n                'uploadPart' =&gt; $uploadPart,\n                'length' =&gt; $toPos,\n            ]);\n            \n            fclose($uploadPart);\n            \n            $uploadParts[] = [\n                'PartNumber' =&gt; ($i + 1),\n                'ETag' =&gt; $eTag,\n            ];\n\n            \/\/ \u8fdb\u5ea6\u56de\u8c03\n            if ($progressCallback) {\n                $progress = round(($i + 1) \/ count($pieces) * 100, 2);\n                call_user_func($progressCallback, $progress, $i + 1, count($pieces));\n            }\n        }\n\n        $result = $this-&gt;ossClient-&gt;completeMultipartUpload(\n            $this-&gt;bucket, \n            $objectName, \n            $uploadId, \n            $uploadParts\n        );\n\n        return ['success' =&gt; true, 'url' =&gt; $this-&gt;getPublicUrl($objectName), 'etag' =&gt; $result];\n\n    } catch (OssException $e) {\n        return ['success' =&gt; false, 'error' =&gt; $e-&gt;getMessage()];\n    }\n}<\/code><\/pre>\n<h2>\u4e09\u3001\u89c6\u9891\u622a\u56fe\u529f\u80fd<\/h2>\n<h3>3.1 \u751f\u6210\u7b2c\u4e00\u5e27\u622a\u56fe<\/h3>\n<pre><code class=\"php\">\/**\n * \u83b7\u53d6\u89c6\u9891\u7b2c\u4e00\u5e27\u622a\u56fe\n *\/\npublic function getVideoSnapshot($objectName, $width = 800, $height = 600, $time = 0)\n{\n    $process = &quot;video\/snapshot,t_{$time},f_jpg,w_{$width},h_{$height},m_fast&quot;;\n    \n    \/\/ \u516c\u5171\u8bfb\u53d6\u7684 Bucket\n    if (!config('oss.is_private')) {\n        $endpoint = config('oss.endpoint');\n        return &quot;https:\/\/{$this-&gt;bucket}.{$endpoint}\/{$objectName}?x-oss-process={$process}&quot;;\n    }\n    \n    \/\/ \u79c1\u6709 Bucket\uff0c\u9700\u8981\u7b7e\u540d\n    return $this-&gt;ossClient-&gt;signUrl($this-&gt;bucket, $objectName, 3600, 'GET', [\n        'x-oss-process' =&gt; $process\n    ]);\n}<\/code><\/pre>\n<h3>3.2 \u591a\u79cd\u622a\u56fe\u53c2\u6570<\/h3>\n<pre><code class=\"php\">\/**\n * \u751f\u6210\u591a\u79cd\u89c4\u683c\u7684\u622a\u56fe\n *\/\npublic function generateMultipleSnapshots($objectName, $configs = [])\n{\n    $defaultConfigs = [\n        'thumbnail' =&gt; ['width' =&gt; 200, 'height' =&gt; 150, 'time' =&gt; 0],\n        'medium' =&gt; ['width' =&gt; 600, 'height' =&gt; 400, 'time' =&gt; 0],\n        'large' =&gt; ['width' =&gt; 1200, 'height' =&gt; 800, 'time' =&gt; 0],\n    ];\n    \n    $configs = array_merge($defaultConfigs, $configs);\n    $snapshots = [];\n    \n    foreach ($configs as $name =&gt; $config) {\n        $snapshots[$name] = $this-&gt;getVideoSnapshot(\n            $objectName,\n            $config['width'],\n            $config['height'],\n            $config['time']\n        );\n    }\n    \n    return $snapshots;\n}<\/code><\/pre>\n<h2>\u56db\u3001\u63a7\u5236\u5668\u5b9e\u73b0<\/h2>\n<h3>4.1 \u89c6\u9891\u4e0a\u4f20\u63a7\u5236\u5668<\/h3>\n<pre><code class=\"php\">&lt;?php\nnamespace App\/Http\/Controllers;\n\nuse Illuminate\/Http\/Request;\nuse Illuminate\/Http\/JsonResponse;\nuse Illuminate\/Support\/Str;\n\nclass VideoController extends Controller\n{\n    protected $videoUploader;\n\n    public function __construct()\n    {\n        $this-&gt;videoUploader = new VideoUploader();\n    }\n\n    \/**\n     * \u4e0a\u4f20\u89c6\u9891\n     *\/\n    public function upload(Request $request): JsonResponse\n    {\n        $request-&gt;validate([\n            'video' =&gt; 'required|file|mimes:mp4,avi,mov,wmv,flv,webm|max:2048000' \/\/ \u6700\u59272GB\n        ]);\n\n        $file = $request-&gt;file('video');\n        $extension = $file-&gt;getClientOriginalExtension();\n        \n        \/\/ \u751f\u6210\u5b58\u50a8\u8def\u5f84\n        $objectName = 'videos\/' . date('Y\/m\/d\/') . Str::uuid() . '.' . $extension;\n\n        \/\/ \u6267\u884c\u4e0a\u4f20\n        $result = $this-&gt;videoUploader-&gt;multipartUpload($file-&gt;getRealPath(), $objectName);\n\n        if ($result['success']) {\n            \/\/ \u751f\u6210\u622a\u56fe\n            $snapshotUrl = $this-&gt;videoUploader-&gt;getVideoSnapshot($objectName);\n            \n            return response()-&gt;json([\n                'success' =&gt; true,\n                'data' =&gt; [\n                    'url' =&gt; $result['url'],\n                    'object_name' =&gt; $objectName,\n                    'snapshot_url' =&gt; $snapshotUrl,\n                    'size' =&gt; $file-&gt;getSize(),\n                ]\n            ]);\n        }\n\n        return response()-&gt;json([\n            'success' =&gt; false,\n            'message' =&gt; $result['error']\n        ], 500);\n    }\n\n    \/**\n     * \u83b7\u53d6\u89c6\u9891\u622a\u56fe\n     *\/\n    public function getSnapshot(Request $request): JsonResponse\n    {\n        $request-&gt;validate([\n            'object_name' =&gt; 'required|string',\n            'width' =&gt; 'integer|min:100|max:1920',\n            'height' =&gt; 'integer|min:100|max:1080',\n            'time' =&gt; 'integer|min:0',\n        ]);\n\n        $objectName = $request-&gt;input('object_name');\n        $width = $request-&gt;input('width', 800);\n        $height = $request-&gt;input('height', 600);\n        $time = $request-&gt;input('time', 0);\n\n        $snapshotUrl = $this-&gt;videoUploader-&gt;getVideoSnapshot($objectName, $width, $height, $time);\n\n        return response()-&gt;json([\n            'success' =&gt; true,\n            'data' =&gt; ['snapshot_url' =&gt; $snapshotUrl]\n        ]);\n    }\n}<\/code><\/pre>\n<h2>\u4e94\u3001\u8def\u7531\u914d\u7f6e<\/h2>\n<h3>5.1 API \u8def\u7531<\/h3>\n<p>\u5728 <code>routes\/api.php<\/code> \u4e2d\u6dfb\u52a0\uff1a<\/p>\n<pre><code class=\"php\">Route::prefix('video')-&gt;group(function () {\n    Route::post('upload', [VideoController::class, 'upload']);\n    Route::post('snapshot', [VideoController::class, 'getSnapshot']);\n});<\/code><\/pre>\n<h2>\u516d\u3001\u524d\u7aef\u8c03\u7528\u793a\u4f8b<\/h2>\n<h3>6.1 JavaScript \u4e0a\u4f20<\/h3>\n<pre><code class=\"javascript\">\/\/ \u4e0a\u4f20\u89c6\u9891\nasync function uploadVideo(file) {\n    const formData = new FormData();\n    formData.append('video', file);\n\n    try {\n        const response = await fetch('\/api\/video\/upload', {\n            method: 'POST',\n            body: formData,\n            headers: {\n                'X-CSRF-TOKEN': document.querySelector('meta[name=&quot;csrf-token&quot;]').getAttribute('content')\n            }\n        });\n\n        const result = await response.json();\n        \n        if (result.success) {\n            console.log('\u4e0a\u4f20\u6210\u529f:', result.data);\n            \/\/ \u663e\u793a\u89c6\u9891\u548c\u622a\u56fe\n            showVideo(result.data.url, result.data.snapshot_url);\n        } else {\n            console.error('\u4e0a\u4f20\u5931\u8d25:', result.message);\n        }\n    } catch (error) {\n        console.error('\u4e0a\u4f20\u9519\u8bef:', error);\n    }\n}\n\n\/\/ \u663e\u793a\u89c6\u9891\nfunction showVideo(videoUrl, snapshotUrl) {\n    const videoHtml = `\n        &lt;video controls poster=&quot;${snapshotUrl}&quot;&gt;\n            &lt;source src=&quot;${videoUrl}&quot; type=&quot;video\/mp4&quot;&gt;\n        &lt;\/video&gt;\n        &lt;img src=&quot;${snapshotUrl}&quot; alt=&quot;\u89c6\u9891\u622a\u56fe&quot; \/&gt;\n    `;\n    document.getElementById('video-container').innerHTML = videoHtml;\n}<\/code><\/pre>\n<h3>6.2 \u5e26\u8fdb\u5ea6\u6761\u7684\u4e0a\u4f20<\/h3>\n<pre><code class=\"javascript\">function uploadVideoWithProgress(file, progressCallback) {\n    const formData = new FormData();\n    formData.append('video', file);\n\n    const xhr = new XMLHttpRequest();\n    \n    \/\/ \u76d1\u542c\u4e0a\u4f20\u8fdb\u5ea6\n    xhr.upload.addEventListener('progress', (e) =&gt; {\n        if (e.lengthComputable) {\n            const progress = (e.loaded \/ e.total) * 100;\n            progressCallback(Math.round(progress));\n        }\n    });\n\n    xhr.addEventListener('load', () =&gt; {\n        if (xhr.status === 200) {\n            const result = JSON.parse(xhr.responseText);\n            console.log('\u4e0a\u4f20\u5b8c\u6210:', result);\n        }\n    });\n\n    xhr.open('POST', '\/api\/video\/upload');\n    xhr.setRequestHeader('X-CSRF-TOKEN', document.querySelector('meta[name=&quot;csrf-token&quot;]').getAttribute('content'));\n    xhr.send(formData);\n}<\/code><\/pre>\n<h2>\u4e03\u3001\u6700\u4f73\u5b9e\u8df5<\/h2>\n<h3>7.1 \u9519\u8bef\u5904\u7406<\/h3>\n<pre><code class=\"php\">try {\n    $result = $this-&gt;videoUploader-&gt;multipartUpload($filePath, $objectName);\n} catch (Exception $e) {\n    Log::error('\u89c6\u9891\u4e0a\u4f20\u5931\u8d25', [\n        'file' =&gt; $objectName,\n        'error' =&gt; $e-&gt;getMessage(),\n        'trace' =&gt; $e-&gt;getTraceAsString()\n    ]);\n    \n    return response()-&gt;json([\n        'success' =&gt; false,\n        'message' =&gt; '\u4e0a\u4f20\u5931\u8d25\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5'\n    ], 500);\n}<\/code><\/pre>\n<h3>7.2 \u6587\u4ef6\u9a8c\u8bc1<\/h3>\n<pre><code class=\"php\">public function validateVideoFile($file)\n{\n    $allowedMimes = ['video\/mp4', 'video\/avi', 'video\/mov', 'video\/wmv'];\n    $maxSize = 2 * 1024 * 1024 * 1024; \/\/ 2GB\n\n    if (!in_array($file-&gt;getMimeType(), $allowedMimes)) {\n        throw new InvalidArgumentException('\u4e0d\u652f\u6301\u7684\u89c6\u9891\u683c\u5f0f');\n    }\n\n    if ($file-&gt;getSize() &gt; $maxSize) {\n        throw new InvalidArgumentException('\u6587\u4ef6\u5927\u5c0f\u8d85\u8fc7\u9650\u5236');\n    }\n\n    return true;\n}<\/code><\/pre>\n<h3>7.3 \u6027\u80fd\u4f18\u5316<\/h3>\n<ul>\n<li>\u5206\u7247\u5927\u5c0f\u5efa\u8bae 5-10MB<\/li>\n<li>\u5e76\u53d1\u4e0a\u4f20\u65f6\u63a7\u5236\u540c\u65f6\u4e0a\u4f20\u7684\u5206\u7247\u6570\u91cf<\/li>\n<li>\u4f7f\u7528 CDN \u52a0\u901f\u8bbf\u95ee<\/li>\n<li>\u5bf9\u4e8e\u5c0f\u6587\u4ef6\u53ef\u4ee5\u76f4\u63a5\u4f7f\u7528\u7b80\u5355\u4e0a\u4f20<\/li>\n<\/ul>\n<h3>7.4 \u5b89\u5168\u8003\u8651<\/h3>\n<ul>\n<li>\u9a8c\u8bc1\u6587\u4ef6\u7c7b\u578b\u548c\u5927\u5c0f<\/li>\n<li>\u4f7f\u7528\u968f\u673a\u6587\u4ef6\u540d\u907f\u514d\u51b2\u7a81<\/li>\n<li>\u79c1\u6709 Bucket \u4f7f\u7528\u7b7e\u540d URL<\/li>\n<li>\u8bbe\u7f6e\u5408\u7406\u7684 URL \u8fc7\u671f\u65f6\u95f4<\/li>\n<\/ul>\n<h2>\u516b\u3001\u5e38\u89c1\u95ee\u9898<\/h2>\n<h3>8.1 \u4e0a\u4f20\u5931\u8d25\u91cd\u8bd5<\/h3>\n<pre><code class=\"php\">public function uploadWithRetry($filePath, $objectName, $maxRetries = 3)\n{\n    for ($i = 0; $i &lt; $maxRetries; $i++) {\n        $result = $this-&gt;multipartUpload($filePath, $objectName);\n        \n        if ($result['success']) {\n            return $result;\n        }\n        \n        if ($i &lt; $maxRetries - 1) {\n            sleep(pow(2, $i)); \/\/ \u6307\u6570\u9000\u907f\n        }\n    }\n    \n    return $result;\n}<\/code><\/pre>\n<h3>8.2 \u6e05\u7406\u672a\u5b8c\u6210\u7684\u5206\u7247\u4e0a\u4f20<\/h3>\n<pre><code class=\"php\">public function cleanupIncompleteUploads()\n{\n    try {\n        $listResult = $this-&gt;ossClient-&gt;listMultipartUploads($this-&gt;bucket);\n        \n        foreach ($listResult-&gt;getUploads() as $upload) {\n            \/\/ \u6e05\u7406\u8d85\u8fc724\u5c0f\u65f6\u7684\u672a\u5b8c\u6210\u4e0a\u4f20\n            if (time() - strtotime($upload-&gt;getInitiated()) &gt; 86400) {\n                $this-&gt;ossClient-&gt;abortMultipartUpload(\n                    $this-&gt;bucket,\n                    $upload-&gt;getKey(),\n                    $upload-&gt;getUploadId()\n                );\n            }\n        }\n    } catch (OssException $e) {\n        Log::error('\u6e05\u7406\u672a\u5b8c\u6210\u4e0a\u4f20\u5931\u8d25: ' . $e-&gt;getMessage());\n    }\n}<\/code><\/pre>\n<p><a href=\"https:\/\/segmentfault.com\/a\/1190000046829615\">https:\/\/segmentfault.com\/a\/1190000046829615<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u4e00\u3001\u73af\u5883\u51c6\u5907 1.1 \u5b89\u88c5 OSS SDK composer require aliyuncs\/oss-sdk [&hellip;]<\/p>\n","protected":false},"author":7,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[4],"tags":[],"class_list":["post-10020875","post","type-post","status-publish","format-standard","hentry","category-share"],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/itmax.cn\/index.php?rest_route=\/wp\/v2\/posts\/10020875","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/itmax.cn\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/itmax.cn\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/itmax.cn\/index.php?rest_route=\/wp\/v2\/users\/7"}],"replies":[{"embeddable":true,"href":"https:\/\/itmax.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=10020875"}],"version-history":[{"count":0,"href":"https:\/\/itmax.cn\/index.php?rest_route=\/wp\/v2\/posts\/10020875\/revisions"}],"wp:attachment":[{"href":"https:\/\/itmax.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=10020875"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/itmax.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=10020875"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/itmax.cn\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=10020875"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}