lumen实现断点上传



前言

前端使用Resumable.js对文件进行分片上传、后端根据Resumable.php扩展包改写(主要是展开扩展包中封装的请求和响应),接收分片合成完整文件


一、环境

1、php7.3.4
2、laravel/lumen-framework:8.2.1
3、cakephp/filesystem": “^4.1”

二、结果

1、前端分片上传
前端分片上传
2、服务器接收的分片文件
服务器接收的分片文件3、接收成功的文件
接收成功的文件

二、代码

1.controller

代码如下(具体看注释):

<?php

namespace App\Http\Controllers;

use Cake\Filesystem\File;
use Cake\Filesystem\Folder;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class ResumableController extends Controller
{

    public $tempFolder = '';

    public $uploadFolder = 'D:\project\manageCompanyService\public\upload';
//是否删除存储分块的文件夹
    public $deleteTmpFolder = false;

    protected $request;

    protected $response;

    protected $params;

    protected $chunkFile;

    /**
     * Resumable constructor.
     * @param Request $request
     * @param Response $response
     */
    public function __construct(Request $request, Response $response)
    {
        $this->request = $request;
        $this->response = $response;
    }
    /**
     *开始续传
     */
    public function process()
    {
            if (!empty($this->request->file())) {
                 return $this->handleChunk();
            } else {
                return 'ncaknc';
            }
    }
    /**
     *
     * @return mixed
     */
    public function handleChunk()
    {
        //获取请求的参数
        $file = $this->file();
        $identifier = $this->request->input('resumableIdentifier');
        $filename = $this->request->input('resumableFilename');
        $chunkNumber = $this->request->input('resumableChunkNumber');
        $chunkSize = $this->request->input('resumableChunkSize');
        $totalSize = $this->request->input('resumableTotalSize');
//判断上传的分块是否存在  如果不存在则存到tmp 下相关分块文件夹
        if (!$this->isChunkUploaded($identifier,$filename,$chunkNumber)) {
            $chunkFile = $this->tmpChunkDir($identifier).DIRECTORY_SEPARATOR.$this->tmpChunkFilename($filename, $chunkNumber);
            $this->moveUploadedFile($file['tmp_name'], $chunkFile);
        }
//合成完整文件并且删除分块路径
        if ($this->isFileUploadComplete($filename,$identifier,$chunkSize, $totalSize)) {
            $tmpFolder = new Folder($this->tmpChunkDir($identifier));
            //Returns an array of the contents of the current directory. 文件数组
            $chunkFiles = $tmpFolder->read(true,true,true)[1];
            //合成
            $this->createFileFromChunks($chunkFiles, $this->uploadFolder.DIRECTORY_SEPARATOR.$filename);
            if ($this->deleteTmpFolder) {
                $tmpFolder->delete();
            }
        }

        return $this->header(200);
    }

    private function _resumableParam($shortName) {
        $resumableParams = $this->resumableParams();
        if (!isset($resumableParams['resumable'.ucfirst($shortName)])) {
            return null;
        }
        return $resumableParams['resumable'.ucfirst($shortName)];
    }

    /**
     * 获取项目的数组
     * @return array|mixed
     */
    public function file()
    {
        if (!isset($_FILES) || empty($_FILES)) {
            return array();
        }
        $files = array_values($_FILES);
        return array_shift($files);
    }

    /**
     * 返回头部信息
     * @param $statusCode
     */
    public function header($statusCode)
    {
        if (200==$statusCode) {
            return header("HTTP/1.0 200 Ok");
        } else if (404==$statusCode) {
            return header("HTTP/1.0 404 Not Found");
        }
        return header("HTTP/1.0 404 Not Found");
    }

    /**
     * 获取请求的数据
     *
     * @return mixed
     */
    public function resumableParams()
    {
        if ($this->request->is('get')) {
            return $this->request->data('get');
        }
        if ($this->request->is('post')) {
            return $this->request->data('post');
        }
    }

    /**
     * 判断上传分块是否完成
     * @param $filename
     * @param $identifier
     * @param $chunkSize
     * @param $totalSize
     * @return bool
     */
    public function isFileUploadComplete($filename, $identifier, $chunkSize, $totalSize)
    {
        if ($chunkSize <= 0) {
            return false;
        }
        $numOfChunks = intval($totalSize / $chunkSize) + ($totalSize % $chunkSize == 0 ? 0 : 1);
        for ($i = 1; $i < $numOfChunks; $i++) {
            if (!$this->isChunkUploaded($identifier, $filename, $i)) {
                return false;
            }
        }
        return true;
    }

    /**
     * 判断分块文件是否存在
     * @param $identifier 唯一标志
     * @param $filename 原始文件名
     * @param $chunkNumber 当前快的索引
     * @return bool
     */
    public function isChunkUploaded($identifier, $filename, $chunkNumber)
    {
        $file = new File($this->tmpChunkDir($identifier) . DIRECTORY_SEPARATOR . $this->tmpChunkFilename($filename, $chunkNumber));
        return $file->exists();
    }

    /**
     * 用于创建路径
     * @param $identifier
     * @return string
     */
    public function tmpChunkDir($identifier)
    {
        $tmpChunkDir = 'tmp' . DIRECTORY_SEPARATOR . $identifier;
        if (!file_exists($tmpChunkDir)) {
            mkdir($tmpChunkDir);
        }
        return $tmpChunkDir;
    }

    /**
     * 用于创建文件名
     * @param $filename
     * @param $chunkNumber
     * @return string
     */
    public function tmpChunkFilename($filename, $chunkNumber)
    {
        return $filename . '.part' . $chunkNumber;
    }

    /**
     * @param $chunkFiles
     * @param $destFile
     * @return bool
     *合成文件
     */
    public function createFileFromChunks($chunkFiles, $destFile)
    {
        $destFile = new File($destFile, true);
        foreach ($chunkFiles as $chunkFile) {
            $file = new File($chunkFile);
            $destFile->append($file->read());
        }
        return $destFile->exists();
    }

    /**
     * @param $file
     * @param $destFile
     * @return bool
     */
    public function moveUploadedFile($file, $destFile)
    {
        $file = new File($file);
        if ($file->exists()) {
            return $file->copy($destFile);
        }
        return false;
    }

    public function setRequest($request)
    {
        $this->request = $request;
    }

    public function setResponse($response)
    {
        $this->response = $response;
    }

}

2.跨域问题

中间件如下:

<?php namespace App\Http\Middleware;

class CorsMiddleware {

    public function handle($request, \Closure $next)
    {
    //laravel—excel 下载时的response类型
        $SymfonyResopnse = 'Symfony\Component\HttpFoundation\BinaryFileResponse';
        $headers = [
            'Access-Control-Allow-Origin'      => '*',
            'Access-Control-Allow-Methods'     => 'HEAD, GET, POST, PUT, PATCH, DELETE',
            'Access-Control-Allow-Headers'     => $request->header('Access-Control-Request-Headers')
        ];
        if ($request->isMethod('OPTIONS')){
            return response('ok', 200, $headers);
        }
        $response = $next($request);
        //如果是SymfonyResopnse返回类型 则用如下方法添加返回头
        if ($response instanceof $SymfonyResopnse) {
            foreach ($headers as $key => $value) {
                $response->headers->set($key, $value);
            }
            return $response;
        }else{
        //普通lumen返回类型
        $response->header('Access-Control-Allow-Methods', 'HEAD, GET, POST, PUT, PATCH, DELETE');
        $response->header('Access-Control-Allow-Headers', $request->header('Access-Control-Request-Headers'));
        $response->header('Access-Control-Allow-Origin', '*');
        }
        return $response;
    }

}


总结

controller中的代码可以拓展更多对上传文件的操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值