大文件上传,对接阿里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()]);
        }
    }
}
相关推荐
无名修道院1 天前
DVWA 靶场搭建:Windows11(phpstudy 搭建)(步骤 + 截图 + 常见问题)
数据库·网络安全·渗透测试·靶场·php·dvwa·phpstudy
爱倒腾的老唐1 天前
00、Altium Designer 23 使用问题记录
笔记·php
catchadmin1 天前
PHP 8.5 垃圾回收改进
php
云和数据.ChenGuang2 天前
`post_max_size`、`max_execution_time`、`max_input_time` 是 **PHP 核心配置参数**
开发语言·mysql·php·zabbix·mariadb
Evan芙2 天前
php多版本编译安装
开发语言·php
JaguarJack2 天前
PHP 8.6 即将支持部分函数应用
后端·php
云和数据.ChenGuang2 天前
PHP-FPM返回的File not found.”的本质
开发语言·php·运维工程师·运维技术
惜分飞2 天前
sql server 事务日志备份异常恢复案例---惜分飞
前端·数据库·php
qqssss121dfd2 天前
计算机网络(第8版,谢希仁)第三章习题解答
网络·计算机网络·php