对象存储 (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,我们使用时通过Storageput操作存储文件,本质上其实使用的就是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-ossOssAdapter

<?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 并继承自 laravelFilesystem

<?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,并且自己扩展FilesystemOssAdapter


阿里云客服提供回复

如果需要使用设置的headers生效必须设置x-oss-metadata-directiveREPLACE 点击查看文档

$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;
}

参考资料

flysystem-oss
优雅的 Oss Flysystem 扩展
对象存储服务OSS
OSS PHP SDK