对象存储 (Object Storage Service, 简称 OSS) OSS 相信大家都听过,它是阿里云对外提供的海量,安全和高可靠的云存储服务。
flysystem-oss
安装
$ composer require "iidestiny/flysystem-oss" -vvv
配置Storage
新建一个Provider
$ php artisan make:provider OssServiceProvider
修改 app/Providers/OssServiceProvider
<?php
namespace App\Providers;
use League\Flysystem\Filesystem;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\ServiceProvider;
use Iidestiny\Flysystem\Oss\OssAdapter;
use Iidestiny\Flysystem\Oss\Plugins\FileUrl;
use Iidestiny\Flysystem\Oss\Plugins\SignUrl;
use Iidestiny\Flysystem\Oss\Plugins\TemporaryUrl;
use Iidestiny\Flysystem\Oss\Plugins\SignatureConfig;
/**
* Class OssServiceProvider
* @auth kingofzihua
* @blog http://blog.kingofzihua.top
* @email kingofzihua@outlook.com
* @package App\Providers
*/
class OssServiceProvider extends ServiceProvider
{
/**
* Bootstrap services.
*
* @return void
*/
public function boot()
{
Storage::extend('oss', function ($app, $config) {
$root = $config['root'] ?? null;
$adapter = new OssAdapter(
$config['access_id'], $config['access_key'], $config['endpoint'],
$config['bucket'], $config['isCName'], $root
);
$filesystem = new Filesystem($adapter,['disable_asserts'=>true]);
$filesystem->addPlugin(new FileUrl());
$filesystem->addPlugin(new SignUrl());
$filesystem->addPlugin(new TemporaryUrl());
$filesystem->addPlugin(new SignatureConfig());
return $filesystem;
});
}
}
编辑配置文件
.env
FILESYSTEM_DRIVER=oss
OSS_ACCESS_KEY=xxxxxxxxx
OSS_SECRET_KEY=xxxxxxxxxx
OSS_ENDPOINT=xxxxxxxx
OSS_BUCKET=xxxxxxx
app/config/filesystems.php
# 增加oss配置项
'oss' => [
'driver' => 'oss',
'access_key' => env('OSS_ACCESS_KEY'),
'secret_key' => env('OSS_SECRET_KEY'),
'endpoint' => env('OSS_ENDPOINT'),
'bucket' => env('OSS_BUCKET'),
'isCName' => env('OSS_IS_CNAME', false), // 如果 isCname 为 false,endpoint 应配置 oss 提供的域名如:`oss-cn-beijing.aliyuncs.com`,否则为自定义域名,,cname 或 cdn 请自行到阿里 oss 后台配置并绑定 bucket
],
注册OssServiceProvider
app/config/app.php
...
'providers' => [
...
//阿里云OSS
App\Providers\OssServiceProvider::class,
],
...
OSS 设置文件元信息
项目需求
下载的文件名称要和数据的编号相对应。
项目分析
由于后台有批量倒入,而且倒入的是oss的文件地址,并且是分好类的,直接修改文件,有点不太友好,所以暂时不考虑,查找阿里云OSS文档,发现OSS可以修改文件的Content-Disposition
,wtf,这不就解决了吗?
解决方案
- 文件后台批量上传
- 上传成功后请求阿里云修改
Content-Disposition
为数据编号
问题
flysystem-oss
这个是封装的一个Filesystem
,我们使用时通过Storage
来 put
操作存储文件,本质上其实使用的就是Filesystem
,查看Filesystem
的定义,发现里面没有提供修改的接口,
解决问题
阿里云OSS 提供一个修改文件原信息的接口,是通过copyObject
来实现的,
CopyObject使用限制
- CopyObject接口仅支持拷贝小于1 GB的Object。如果要拷贝大于1 GB的Object,您可以使用UploadPartCopy接口进行操作。
- CopyObject接口支持修改Object的元数据(源Object与目标Object一致),最大可支持修改48.8 TB的Object。
- 使用此接口须对源Object有读权限。
- 源Object和目标Object必须属于同一地域。
- 不能拷贝通过追加上传方式产生的Object。
- 如果源Object为软链接,则只拷贝软链接,无法拷贝软链接指向的文件内容。
这里有个坑,就是说
源Object与目标Object一致
时才可以修改,sdk和文档中并没有说,但是自己测试的时候就是这样的。这个已提交工单,但是还没有回复
封装自己的OssAdapter
由于 flysystem-oss
并没有只修改元数据
的方法所以我们要自己实现
新建一个OssAdapter
并继承自 flysystem-oss
的OssAdapter
<?php
namespace App\Filesystem;
use OSS\Core\OssException;
use Iidestiny\Flysystem\Oss\OssAdapter as BaseOssAdapter;
/**
* Class OssAdapter
* @auth kingofzihua
* @blog http://blog.kingofzihua.top
* @email kingofzihua@outlook.com
* @package App\Filesystem
*/
class OssAdapter extends BaseOssAdapter
{
/**
* @auth kingofzihua
* @return \OSS\OssClient
*/
public function getClient()
{
return $this->client;
}
/**
* 修改数据元信息
* @param $path
* @param array $option
* @auth kingofzihua
* @return bool
*/
public function updateMeta($path, array $option)
{
$path = $this->applyPathPrefix($path);
try {
$this->client->copyObject($this->bucket, $path, $this->bucket, $path, $option);
} catch (OssException $exception) {
return false;
}
return true;
}
}
封装自己的Filesystem
由于laravel
自带的Filesystem
并没有oss所需要的接口,所以我们需要自己定义一个Filesystem
并继承自 laravel
的Filesystem
<?php
namespace App\Filesystem;
use League\Flysystem\Util;
use League\Flysystem\FileNotFoundException;
use League\Flysystem\Filesystem as BaseFilesystem;
/**
* Class Filesystem
* @auth kingofzihua
* @blog http://blog.kingofzihua.top
* @email kingofzihua@outlook.com
* @package App\Filesystem
*/
class Filesystem extends BaseFilesystem
{
/**
* @param $path
* @param $newPath
* @param null $option
* @auth kingofzihua
* @return mixed
* @throws FileNotFoundException
*/
public function updateMeta($path, array $option)
{
$path = Util::normalizePath($path);
//判断文件是否存在 如果不存在,就抛出异常
if (!$this->has($path)) {
throw new FileNotFoundException($path);
}
return $this->getAdapter()->updateMeta($path, $option);
}
}
Storage中使用自己定义的 Filesystem
AppProviders/OssServiceProvider.php
<?php
namespace App\Providers;
//use League\Flysystem\Filesystem; 注释掉
//use Iidestiny\Flysystem\Oss\OssAdapter; 注释掉
use App\Filesystem\Filesystem;
use App\Filesystem\OssAdapter;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\ServiceProvider;
use Iidestiny\Flysystem\Oss\Plugins\FileUrl;
use Iidestiny\Flysystem\Oss\Plugins\SignUrl;
use Iidestiny\Flysystem\Oss\Plugins\TemporaryUrl;
use Iidestiny\Flysystem\Oss\Plugins\SignatureConfig;
/**
* Class OssServiceProvider
* @auth kingofzihua
* @blog http://blog.kingofzihua.top
* @email kingofzihua@outlook.com
* @package App\Providers
*/
class OssServiceProvider extends ServiceProvider
{
/**
* Bootstrap services.
*
* @return void
*/
public function boot()
{
Storage::extend('oss', function ($app, $config) {
$root = $config['root'] ?? null;
$adapter = new OssAdapter(
$config['access_id'], $config['access_key'], $config['endpoint'],
$config['bucket'], $config['isCName'], $root
);
$filesystem = new Filesystem($adapter,['disable_asserts'=>true]);
$filesystem->addPlugin(new FileUrl());
$filesystem->addPlugin(new SignUrl());
$filesystem->addPlugin(new TemporaryUrl());
$filesystem->addPlugin(new SignatureConfig());
return $filesystem;
});
}
}
测试
use OSS\OssClient;
use App\Models\Model;
use App\Filesystem\Filesystem;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;
use League\Flysystem\FileNotFoundException;
...
/**
* 通过模型修改用户的文件meta
* @param $model
* @auth kingofzihua
*/
public function updateFileMetaByModel($model){
$download_name = $model->id . '.' . File::extension($model->file_path);
$copyOptions = [
OssClient::OSS_HEADERS => [
OssClient::OSS_CONTENT_DISPOSTION => 'attachment;filename=' . $download_name,
OssClient::OSS_CACHE_CONTROL => 'No-cache',
OssClient::OSS_EXPIRES => '0',
]
];
try {
/**
* @var Filesystem $storage
*/
$storage = Storage::disk();
$storage->updateMeta($model->file_path, $copyOptions);
} catch (FileNotFoundException $fileNotFoundException) {
//文件不存在,不用处理
}
}
总结
我们在laravel
中使用 OSS储存,首先需要将OSS储存扩展为Filesystem
,给Filesystem
自定义方法,调用OSSAPI 就可以实现,主要麻烦的地方就是你需要自己注册Provider
,并且自己扩展Filesystem
和OssAdapter
。
阿里云客服提供回复
如果需要使用设置的headers生效必须设置x-oss-metadata-directive
为 REPLACE
点击查看文档
$copyOptions = array(
OssClient::OSS_HEADERS => array(
'Expires' => '0',
'Content-Disposition' => 'attachment; filename="10086.zip"',
'x-oss-meta-location' => 'location',
'x-oss-metadata-directive' => 'REPLACE', //这一句必须得加
),
);
try{
$ossClient = new OssClient($accessKeyId, $accessKeySecret, $endpoint);
$ossClient->copyObject($fromBucket, $fromObject, $toBucket, $toObject, $copyOptions);
} catch(OssException $e) {
printf(__FUNCTION__ . ": FAILED\n");
printf($e->getMessage() . "\n");
return;
}