Skip to content

Commit

Permalink
feat: 版本重构,支持本地存储方式
Browse files Browse the repository at this point in the history
  • Loading branch information
helingfeng committed Sep 23, 2021
1 parent 7fa6e7e commit a1e93ae
Show file tree
Hide file tree
Showing 19 changed files with 437 additions and 185 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ composer.phar

# Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control
# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file
# composer.lock
composer.lock
.idea
19 changes: 13 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
# ChunkUpload
Laravel 超大文件上传 OSS 存储,前端对文件进行分块上传,并监听每个文件块的上传事件;后端接收所有的文件块后,进行排序与合并,输出最终目标文件

Laravel 超大文件上传,前端对文件进行分块上传,并监听每个文件块的上传事件;后端接收所有的文件块后,进行排序与合并,输出最终目标文件。

## Composer 安装

```
composer require helingfeng/laravel-chunk-upload-oss
composer require helingfeng/laravel-chunk-upload
```

Laravel>=5.5 不需要配置 Provider, 如果不是,请在 `config/app.php` 下添加 `ChunkUpload\ChunkUploadServiceProvider::class`

```
php artisan vendor:publish --provider "ChunkUpload\ChunkUploadServiceProvider"
```
Expand All @@ -17,6 +16,10 @@ php artisan vendor:publish --provider "ChunkUpload\ChunkUploadServiceProvider"

打开 `config/chunk_upload.php` 配置文件,并在 `.env` 添加相应配置项
```php
// 可选 oss 或 local本地文件存储方式
'driver' => env('UPLOAD_CHUNK_DRIVER', 'local'),

// 选择 oss 是,需要补充相关配置项
// OSS ACCESS_ID
'access_id' => env('OSS_ACCESS_ID', ''),
// OSS ACCESS_KEY
Expand All @@ -28,6 +31,10 @@ php artisan vendor:publish --provider "ChunkUpload\ChunkUploadServiceProvider"

## Example 示例

启动项目,并访问路径:https://yourhosts/example
启动项目,并访问路径:https://yourhosts/show-upload-example

![](./upload.jpg)

## 本地文件上传示例

![](./upload_demo.gif)
![](./file.jpg)
8 changes: 5 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "helingfeng/laravel-chunk-upload-oss",
"name": "helingfeng/laravel-chunk-upload",
"authors": [
{
"name": "helingfeng",
Expand All @@ -12,7 +12,9 @@
}
},
"require": {
"aliyuncs/oss-sdk-php": "^2.3"
"aliyuncs/oss-sdk-php": "^2.3",
"laravel/framework": ">=6.0",
"ext-json": "*"
},
"extra": {
"laravel": {
Expand All @@ -21,4 +23,4 @@
]
}
}
}
}
38 changes: 20 additions & 18 deletions config/chunk_upload.php
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
<?php

return [
'driver' => env('UPLOAD_CHUNK_DRIVER', 'local'),

// OSS ACCESS_ID
'access_id' => env('OSS_ACCESS_ID', ''),
// OSS ACCESS_KEY
'access_key' => env('OSS_ACCESS_KEY', ''),
// OSS bucket
'bucket' => env('OSS_BUCKET', ''),
// OSS endpoint
'endpoint' => env('OSS_END_POINT', ''),
// OSS endpoint_internal
'endpoint_internal' => env('OSS_END_POINT_INTERNAL', ''),
'oss' => [
// OSS ACCESS_ID
'access_id' => env('OSS_ACCESS_ID', ''),
// OSS ACCESS_KEY
'access_key' => env('OSS_ACCESS_KEY', ''),
// OSS bucket
'bucket' => env('OSS_BUCKET', ''),
// OSS endpoint
'endpoint' => env('OSS_END_POINT', ''),
// OSS endpoint_internal
'endpoint_internal' => env('OSS_END_POINT_INTERNAL', ''),

'cdnDomain' => env('OSS_CDN_DOMAIN', ''),

// 是否开启 HTTPS
'ssl' => env('OSS_SSL', true),
// 是否开启 CDN
'is_cdn' => env('OSS_IS_CDN', true),
// 是否调试模式
'debug' => env('OSS_DEBUG', true),
'cdnDomain' => env('OSS_CDN_DOMAIN', ''),
// 是否开启 HTTPS
'ssl' => env('OSS_SSL', true),
// 是否开启 CDN
'is_cdn' => env('OSS_IS_CDN', false),
// 是否调试模式
'debug' => env('OSS_DEBUG', true),
],

// 上传文件格式限制
'allow_extension' => ["png", "jpg", "jpeg", "gif"],
Expand Down
Binary file added file.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 8 additions & 11 deletions routes/routes.php
Original file line number Diff line number Diff line change
@@ -1,22 +1,19 @@
<?php

use Illuminate\Routing\Router;
use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------
| Chunk Upload Routes
|--------------------------------------------------------------------------
|
*/

Route::group([], function(Router $router){

$router->get('/example', function () {
return view('chunk-upload::example');
});

Route::namespace('\ChunkUpload\Controllers')->group(function (Router $router) {
// 上传预处理
$router->post('/preprocess', '\ChunkUpload\Controllers\UploadController@preprocess')->name('chunk-preprocess');
$router->post('/preprocess', 'UploadController@preprocess')->name('chunk-preprocess');
// 分块上传
$router->post('/uploading', '\ChunkUpload\Controllers\UploadController@uploading')->name('chunk-uploading');

});
$router->post('/uploading', 'UploadController@uploading')->name('chunk-uploading');
// 演示程序
$router->get('/show-upload-example', 'ExampleController@index');
});
15 changes: 15 additions & 0 deletions src/BaseController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace ChunkUpload;

use Illuminate\Http\JsonResponse;
use Illuminate\Routing\Controller;

class BaseController extends Controller
{
public function responseJson($data = [], $status = 200, array $headers = [], $options = 0): JsonResponse
{
is_string($data) && $data = ['message' => $data];
return response()->json($data, $status, $headers, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
}
13 changes: 9 additions & 4 deletions src/ChunkUploadServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@

class ChunkUploadServiceProvider extends ServiceProvider
{

protected $defer = false;

public function boot()
{
$this->loadViewsFrom(__DIR__ . '/../views', 'chunk-upload');
Expand All @@ -23,7 +20,15 @@ public function boot()

public function register()
{
//
$this->app->bind(MultipartUpload::class, function ($app) {
$driver = $app['config']->get('chunk_upload.driver');
switch (strtoupper($driver)) {
case 'OSS':
return new OssUploadClient();
default:
return new LocalUploadClient();
}
});
}

}
13 changes: 13 additions & 0 deletions src/Controllers/ExampleController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace ChunkUpload\Controllers;

use ChunkUpload\BaseController;

class ExampleController extends BaseController
{
public function index()
{
return view('chunk-upload::example');
}
}
157 changes: 43 additions & 114 deletions src/Controllers/UploadController.php
Original file line number Diff line number Diff line change
@@ -1,142 +1,71 @@
<?php

namespace ChunkUpload\Controllers;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use OSS\OssClient;
use ChunkUpload\BaseController;
use ChunkUpload\MultipartUpload;
use ChunkUpload\Requests\PartUploadRequest;
use ChunkUpload\Requests\PreprocessRequest;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Cache;

class UploadController extends Controller
class UploadController extends BaseController
{
// OSS SDK 客户端
protected $ossClient;

// 上传 Bucket
protected $bucket;

public function __construct()
{
$access_id = config('chunk_upload.access_id');
$access_key = config('chunk_upload.access_key');
$endpoint_internal = config('chunk_upload.endpoint_internal');
$is_cdn = config('chunk_upload.is_cdn');
$this->bucket = config('chunk_upload.bucket');
$this->ossClient = new OssClient($access_id, $access_key, $endpoint_internal, $is_cdn);
}

/**
* 大文件预处理,得到 UploadId 和 Pieces 信息
* @param PreprocessRequest $request
* @param MultipartUpload $multipartUpload
* @return JsonResponse
*/
public function preprocess(Request $request)
public function preprocess(PreprocessRequest $request, MultipartUpload $multipartUpload): JsonResponse
{
$resource_name = $request->input('resource_name');
$resource_size = $request->input('resource_size', 0);
$extension = explode('.', $resource_name);
$resourceName = $request->input('resource_name');
$resourceSize = $request->input('resource_size', 0);
$extension = explode('.', $resourceName);
$extension = end($extension);

$allow_extension = config('chunk_upload.allow_extension');
$allow_max_file_size = config('chunk_upload.allow_max_file_size');
$part_size = config('chunk_upload.part_size');
$allowExtension = config('chunk_upload.allow_extension');
$allowMaxFileSize = config('chunk_upload.allow_max_file_size');
$partSize = config('chunk_upload.part_size');

if (!in_array($extension, $allow_extension)) {
// return $this->fail('不支持上传文件格式:' . $extension);
if (!in_array($extension, $allowExtension)) {
return $this->responseJson('不支持上传文件格式:' . $extension, 400);
}
if ($resource_size >= $allow_max_file_size) {
// return $this->fail('文件大小超出限制:' . $allow_max_file_size);
if ($resourceSize >= $allowMaxFileSize) {
return $this->responseJson('文件大小超出限制:' . $allowMaxFileSize, 400);
}
$object = config('chunk_upload.upload_path') . DIRECTORY_SEPARATOR . uniqid() . DIRECTORY_SEPARATOR . $extension;
// 初始化分块上传,得到 upload_id
$upload_id = $this->ossClient->initiateMultipartUpload($this->bucket, $object);
$pieces = $this->ossClient->generateMultiuploadParts($resource_size, $part_size);
$filepath = config('chunk_upload.upload_path') . DIRECTORY_SEPARATOR . uniqid() . '.' . $extension;

$uploadId = $multipartUpload->initiateUpload($filepath);
$pieces = $multipartUpload->generateParts($resourceSize, $partSize);

$data['object'] = $object;
$data['upload_id'] = $upload_id;
$data['part_size'] = $part_size;
$data['file_path'] = $filepath;
$data['upload_id'] = $uploadId;
$data['part_size'] = $partSize;
$data['extension'] = $extension;
$data['resource_size'] = $resource_size;
$data['resource_name'] = $resource_name;
$data['resource_size'] = $resourceSize;
$data['resource_name'] = $resourceName;
$data['pieces'] = $pieces;
$data['pieces_count'] = count($pieces);

return response()->json($data, 200, [], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
Cache::put("{$uploadId}_preprocess", $data);
return $this->responseJson($data);
}

/**
* 分片上传处理
* @param PartUploadRequest $request
* @param MultipartUpload $multipartUpload
* @return JsonResponse|void
*/
public function uploading(Request $request)
{
$chunk_file = $request->file('chunk_file');
$part_index = $request->input('part_index');
$chunk_count = $request->input('chunk_count');
// 分块所属文件对象
$object = $request->input('oss_object');
// 分块所属 upload_id
$upload_id = $request->input('upload_id');
// 历史分块标签 Tags
$parts = $request->input('parts', '');

$options = array(
$this->ossClient::OSS_FILE_UPLOAD => $chunk_file->getRealPath(),
$this->ossClient::OSS_PART_NUM => $part_index,
$this->ossClient::OSS_SEEK_TO => 0,
$this->ossClient::OSS_LENGTH => filesize($chunk_file->getRealPath()) - 1,
$this->ossClient::OSS_CHECK_MD5 => false,
);

try {
// upload_part 是由每个分片的 ETag 和 分片号(PartNumber)组成的数组。
$res_upload_part = $this->ossClient->uploadPart($this->bucket, $object, $upload_id, $options);
} catch (OssException $e) {
printf($e->getMessage() . "\n");
return;
}

$result = ['part_index' => $part_index, 'chunk_count' => $chunk_count, 'part' => $res_upload_part];
// 是否完成所有分片上传
if ($part_index >= $chunk_count) {
// 组装所有分块 Tags 信息
$part_arr = explode(',', $parts);
$upload_parts = array();
$index = 1;
foreach ($part_arr as $part) {
if (!empty($part)) {
$upload_parts[] = array(
'PartNumber' => $index++,
'ETag' => $part,
);
}
}
// 最后一个完成上传的分块
$upload_parts[] = array(
'PartNumber' => $index,
'ETag' => $res_upload_part,
);
try {
$this->ossClient->completeMultipartUpload($this->bucket, $object, $upload_id, $upload_parts);
$result['resource'] = $object;
} catch (OssException $e) {
printf($e->getMessage() . "\n");
return;
}
}
return response()->json($result, 200, [], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}

/**
* 取消上传
*/
public function abortUpload()
public function uploading(PartUploadRequest $request, MultipartUpload $multipartUpload): JsonResponse
{
// todo
// try{
// $ossClient = new OssClient($accessKeyId, $accessKeySecret, $endpoint);
$partFile = $request->file('chunk_file');
$uploadId = $request->input('upload_id');
$partIndex = $request->input('part_index');

// $ossClient->abortMultipartUpload($bucket, $object, $upload_id);
// } catch(OssException $e) {
// printf(__FUNCTION__ . ": FAILED\n");
// printf($e->getMessage() . "\n");
// return;
// }
$preprocess = Cache::get("{$uploadId}_preprocess");
$result = $multipartUpload->uploadPart($partFile, $partIndex, $preprocess['pieces_count'], $preprocess['file_path'], $uploadId);
return $this->responseJson($result);
}

}
Loading

0 comments on commit a1e93ae

Please sign in to comment.