大文件上传,对接阿里oss采用前端分片技术。完成对应需求!

最近做了一个大文件分片上传的功能,记录下

1. 首先是安装阿里云 oss 扩展

复制代码
composer require aliyuncs/oss-sdk-php

去阿里云 oss 获取配置文件

复制代码
AccessKey ID = ***
AccessKey Secret = ***
Bucket名称 = ***
Endpoint = ***

2. 前端上传,对文件进行分片

复制代码
<form id="add-form" class="form-horizontal" role="form" data-toggle="validator" method="POST" action="">

    <div class="form-group">
        <label class="control-label col-xs-12 col-sm-2">{:__('选择本地文件')}:</label>
        <div class="col-xs-12 col-sm-8">
            <input type="file" id="fileInput">
            <div>
                <a href="#" onclick="startUpload()"><i class="fa fa-upload"></i>选择完点击上传(请等待上传完成)</a>
            </div>
            <div id="progress" style="margin-top:10px;"></div>
        </div>
    </div>
    <div class="form-group layer-footer">
        <label class="control-label col-xs-12 col-sm-2"></label>
        <div class="col-xs-12 col-sm-8">
            <button type="submit" class="btn btn-primary btn-embossed disabled">{:__('OK')}</button>
        </div>
    </div>
</form>

<script>
    let chunkSize = 5 * 1024 * 1024; // 分片大小5MB
    let uploadId = '';
    let objectName = '';
    let parts = [];

    const CHUNK_SIZE = 5 * 1024 * 1024; // 分片阈值5MB

    async function startUpload() {
        const file = document.getElementById('fileInput').files[0];
        if (!file) {
            layer.msg('请选择文件', {icon: 1});
        }

        // 根据文件大小选择上传方式
        if (file.size <= CHUNK_SIZE) {
            await directUpload(file);
        } else {
            await chunkedUpload(file);
        }
    }

    // 开始上传
    async function directUpload(file) {
        const formData = new FormData();
        formData.append('file', file);
        formData.append('file_name', file.name);

        // 显示进度条
        const progressBar = document.getElementById('progress');
        progressBar.innerHTML = '上传进度:0%';

        try {
            const res = await fetch('/api/directUpload', {
                method: 'POST',
                body: formData,
            });

            const data = await res.json();
            if (data.code === 1) {
                progressBar.innerHTML = '上传进度:100%';
                $("#c-name").val(data.name);
                $("#c-fullurl").val(data.fullurl);
                layer.msg('上传成功', {icon: 1});
            } else {
                throw new Error(data.msg);
            }
        } catch (error) {
            progressBar.innerHTML = '上传失败';
            console.error('直接上传失败:', error);
        }
    }

    async function chunkedUpload(file) {
        const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
        let uploadedChunks = 0;

        // 初始化分片上传
        const initRes = await fetch('api/initUpload', {
            method: 'POST',
            body: JSON.stringify({filename: file.name}),
            headers: {'Content-Type': 'application/json'}
        });
        const initData = await initRes.json();
        if (initData.code !== 1) return alert('初始化失败');

        const {uploadId, objectName} = initData;
        const parts = [];

        // 上传所有分片
        for (let i = 0; i < totalChunks; i++) {
            const start = i * CHUNK_SIZE;
            const end = Math.min(start + CHUNK_SIZE, file.size);
            const chunk = file.slice(start, end);

            const formData = new FormData();
            formData.append('part', chunk);
            formData.append('uploadId', uploadId);
            formData.append('objectName', objectName);
            formData.append('partNumber', i + 1);

            const uploadRes = await fetch('api/uploadPart', {
                method: 'POST',
                body: formData
            });
            const partData = await uploadRes.json();

            if (partData.code === 1) {
                parts.push({
                    PartNumber: partData.partNumber,
                    ETag: partData.etag
                });
                uploadedChunks++;

                // 更新进度
                const progress = (uploadedChunks / totalChunks * 100).toFixed(2);
                document.getElementById('progress').innerHTML = `上传进度:${progress}%`;
            }
        }

        // 合并分片
        const completeRes = await fetch('api/completeUpload', {
            method: 'POST',
            body: JSON.stringify({
                uploadId,
                objectName,
                parts: JSON.stringify(parts)
            }),
            headers: {'Content-Type': 'application/json'}
        });
        const completeData = await completeRes.json();

        if (completeData.code === 1) {
            $("#c-name").val(completeData.name);
            $("#c-fullurl").val(completeData.fullurl);
            layer.msg('上传成功', {icon: 1});
        } else {
            layer.msg('上传失败' + completeData.msg, {icon: 2});
        }
    }
</script>

2. 后端控制器

复制代码
<?php

namespace app\****;

use OSS\Core\OssException;
use OSS\OssClient;

class Attachment
{
    // 初始化分片上传
    public function initUpload()
    {
        $object = 'uploads/' . date('Ymd') . '/' . $this->request->post('filename');
        try {
            $ossClient = new OssClient(
                config('alioss.accessKeyId'),
                config('alioss.accessKeySecret'),
                config('alioss.endpoint')
            );
            $uploadId = $ossClient->initiateMultipartUpload(config('alioss.bucket'), $object);
            return json([
                'code' => 1,
                'uploadId' => $uploadId,
                'objectName' => $object
            ]);
        } catch (OssException $e) {
            return json(['code' => 0, 'msg' => $e->getMessage()]);
        }
    }

    // 上传分片
    public function uploadPart()
    {
        $data = $this->request->post();
        try {
            $ossClient = new OssClient(
                config('alioss.accessKeyId'),
                config('alioss.accessKeySecret'),
                config('alioss.endpoint')
            );
            $options = [
                OssClient::OSS_FILE_UPLOAD => $_FILES['part']['tmp_name'],
                OssClient::OSS_PART_NUM => $data['partNumber'],
                OssClient::OSS_CHECK_MD5 => true
            ];
            $etag = $ossClient->uploadPart(
                config('alioss.bucket'),
                $data['objectName'],
                $data['uploadId'],
                $options
            );
            return json([
                'code' => 1,
                'etag' => $etag,
                'partNumber' => $data['partNumber']
            ]);
        } catch (OssException $e) {
            return json(['code' => 0, 'msg' => $e->getMessage()]);
        }
    }

    // 完成上传
    public function completeUpload()
    {
        $data = $this->request->post();
        try {
            $ossClient = new OssClient(
                config('alioss.accessKeyId'),
                config('alioss.accessKeySecret'),
                config('alioss.endpoint')
            );
            $result = $ossClient->completeMultipartUpload(
                config('alioss.bucket'),
                $data['objectName'],
                $data['uploadId'],
                json_decode($data['parts'], true)
            );
            return json([
                'code' => 1,
                'url' => $result['oss-request-url'],
                'name' => pathinfo($data['objectName'], PATHINFO_FILENAME),
                'fullurl' => strstr($result['oss-request-url'], '?', true),
            ]);
        } catch (OssException $e) {
            return json(['code' => 0, 'msg' => $e->getMessage()]);
        }
    }

    // 直接上传完整文件
    public function directUpload()
    {
        try {
            $ossClient = new OssClient(
                config('alioss.accessKeyId'),
                config('alioss.accessKeySecret'),
                config('alioss.endpoint')
            );

            $file = $_FILES['file'];
            $file_name = $this->request->request('file_name', '');
            $object = 'uploads/' . date('Ymd') . '/' . $file['name'];

            $result = $ossClient->uploadFile(
                config('alioss.bucket'),
                $object,
                $file['tmp_name']
            );

            return json([
                'code' => 1,
                'url' => $result['oss-request-url'],
                'name' => pathinfo($file_name, PATHINFO_FILENAME),
                'fullurl' => $result['oss-request-url'],
            ]);
        } catch (OssException $e) {
            return json(['code' => 0, 'msg' => $e->getMessage()]);
        }
    }
}
相关推荐
BingoGo5 小时前
PHP 泛型之殇 泛型 RFC 提案被拒绝
后端·php
JaguarJack6 小时前
PHP 泛型之殇 泛型 RFC 提案被拒绝
后端·php
用户3074596982071 天前
PHP 扩展——从入门到理解
php
鹏仔先生2 天前
拷贝漫画APP下载页PHP程序,后台带免费AI写作
php
云水一下2 天前
从零开始学 PHP 系列(一):PHP 的前世今生与开发环境搭建
开发语言·php
xingpanvip2 天前
星盘接口开发文档:本命盘接口指南
android·开发语言·css·php·lua
酉鬼女又兒2 天前
零基础入门计算机网络运输层:端到端通信核心作用、端口号分类规则、复用分用工作机制及UDP与TCP协议全方位对比详解
网络·网络协议·tcp/ip·计算机网络·考研·udp·php
dog2502 天前
不要再继续优化 TCP
网络协议·tcp/ip·php
Channing Lewis2 天前
PHP 解析 Excel 的那些坑:一次“行号错位”引发的数据丢失
开发语言·php·excel
Cheng小攸2 天前
渗透行为分析与检测
开发语言·php