大文件上传,对接阿里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()]);
        }
    }
}
相关推荐
西部森林牧歌26 分钟前
Arbess零基础学习 - 使用Arbess+GitLab实现PHP项目构建/主机部署
ci/cd·gitlab·php·tiklab devops
Q_Q5110082854 小时前
python+django/flask的校园活动中心场地预约系统
spring boot·python·django·flask·node.js·php
蒲公英源码5 小时前
基于PHP+Vue+小程序快递比价寄件系统
vue.js·小程序·php
Q_Q19632884757 小时前
python+django/flask基于机器学习的就业岗位推荐系统
spring boot·python·django·flask·node.js·php
韩立学长7 小时前
【开题答辩实录分享】以《奇妙英语角小程序的设计与实现》为例进行答辩实录分享
小程序·php
Tigshop开源商城系统10 小时前
Tigshop 开源商城系统 php v5.1.9.1版本正式发布
java·大数据·开源·php·开源软件
拾忆,想起12 小时前
超时重传 vs 快速重传:TCP双保险如何拯救网络丢包?
java·开发语言·网络·数据库·网络协议·tcp/ip·php
BingoGo12 小时前
15 个 Eloquent 高级技巧,瞬间提升你的 Laravel 应用性能
后端·php
michaelzhouh13 小时前
php项目ueditor上传pdf文件,防止XSS攻击
pdf·php·xss·ueditor
合作小小程序员小小店14 小时前
web网页开发,在线%商城,电商,商品购买%系统demo,基于vscode,apache,html,css,jquery,php,mysql数据库
开发语言·前端·数据库·mysql·html·php·电商