【问题标题】:How to get public url of an uploaded file in Laravel 5如何在 Laravel 5 中获取上传文件的公共 url
【发布时间】:2015-04-24 00:20:28
【问题描述】:

你可能知道 Laravel 使用 Flysystem PHP 包来提供文件系统抽象。 我已经开始在我的项目中使用此功能,只是为了好玩将一些图像上传到我的 Amazon s3 存储桶,我也将 Cloudfront 实例链接到此存储桶。我的问题是当我尝试在我的 html 页面中显示这些图像时,我需要一个 url。

我找不到任何“干净”的方法来做到这一点,因为 flysystem 是通用库,我通过它可以做这样的事情:

Storage::disk('my_awesome_disk')->publicUrl("{$path}/{$image->name}")

对于“公共”文件,它很容易确定文件是否为“公共”,因为它包含在他们的 api 中,所以如果我使用 s3 存储桶作为我的磁盘驱动程序,我应该得到: "https://s3.amazonaws.com/my_awesome_bucket/path/image.png"

或者:

Storage::disk('my_awesome_disk')->signedUrl("{$path}/{$image->name}", $timeout)

对于“私人”文件 - 我应该得到一个临时网址,该网址将在一段时间后过期。

这是我只能通过特定实现才能实现的目标吗?例如,如果我使用 Amazon S3,我可以轻松运行:

$signed_url = $s3Client->getObjectUrl($bucket_name,
                        $resource_key,
                        "+{$expires} minutes");

但我不想做丑陋的“开关案例”来确定我使用的是什么驱动程序。 以及如何通过 FileSystem 接口从 cdn(如 cloudfront)获取 url? 有什么建议吗?

【问题讨论】:

  • 你做到了吗?
  • 我正在研究它,我想我将不得不编写一个库来处理它,使用装饰器设计模式扩展当前的 api。系统设计上还是有困难。

标签: php laravel filesystems


【解决方案1】:

我认为Flysystem 是磁盘(或其他存储机制)的接口,仅此而已。就像我不会要求本地文件系统计算公共 URI 一样,我也不会要求 Flysystem 这样做。

我创建了与我通过Flysystem 保存的文件相对应的对象。根据我的需要,我可能会将公共 URI 直接保存在数据库记录中,或者我可能会创建一个自定义 getter,以根据运行时环境构建公共 URI。

有了Flysystem,我在写文件的时候就知道了文件的路径。为了跟踪这些文件,我通常会创建一个代表已保存文件的对象:

class SavedFile extends Model
{
    protected $fillable = [
        'disk',
        'path',
        'publicURI',
    ];
}

当我保存文件时,我在数据库中创建了一条记录:

$disk     = 'local_example';
$path     = '/path/to/file.txt';
$contents = 'lorem ipsum';
$host     = 'https://example.com/path/to/storage/root/';

if (Store::disk($disk)->put($path, $contents)) {
    SavedFile::create([
        'disk'      => $disk,
        'path'      => $path,
        'publicURI' => $host . $path,
    ]);
}

每当我需要公共 URI 时,我都可以从 SavedFile 模型中获取它。这对于琐碎的应用程序很方便,但如果我需要切换存储提供程序,它就会崩溃。

利用继承

另一个巧妙的技巧是定义一个方法,该方法将基于在抽象 SavedFile 模型的子级中定义的变量解析公共 URI。这样我就不会在数据库中对 URI 进行硬编码,而且我可以只用几个变量定义为其他存储服务创建新类:

abstract class SavedFile extends Model
{
    protected $table = 'saved_files';

    protected $disk;

    protected $host;

    protected $fillable = [
        'path',
    ];

    public function getPublicURIAttribute()
    {
        return $this->host . $this->attributes['path'];
    }
}

class LocalSavedFile extends SavedFile
{
    $disk = 'local_example';
    $host = 'https://example.com/path/to/storage/root';
}

class AwsSavedFile extends SavedFile
{
    $disk = 'aws_example';
    $host = 'https://s3.amazonaws.com/my_awesome_bucket';
}

现在,如果我有一堆文件存储在我的本地文件系统中,并且有一天我将它们移动到 Amazon S3,我可以简单地复制这些文件并在我的 IoC 绑定定义中交换依赖关系,然后就完成了。由于 URI 的计算是由模型完成的,因此无需对海量数据库表进行耗时且具有潜在危险的查找和替换:

$localFile = LocalSavedFile::create([
    'path' => '/path/to/file.txt'
]);
echo $localFile->publicURI; // https://example.com/path/to/storage/root/path/to/file.txt

$awsFile = AwsSavedFile::find($localFile->id);
echo $awsFile->publicURI; // https://s3.amazonaws.com/my_awesome_bucket/path/to/file.txt

编辑:

支持公共和私人文件

只需为对象添加一个标志:

abstract class SavedFile extends Model
{
    protected $table = 'saved_files';

    protected $disk;

    protected $host;

    protected $fillable = [
        'path',
        'public',
    ];

    public function getPublicURIAttribute()
    {
        if ($this->attributes['public'] === false) {
            // you could throw an exception or return a default image if you prefer
            return false;
        }

        return $this->host . $this->attributes['path'];
    }
}

class LocalSavedFile extends SavedFile
{
    $disk = 'local_example';
    $host = 'https://example.com/path/to/storage/root';
}

$localFile = LocalSavedFile::create([
    'path'   => '/path/to/file.txt',
    'public' => false,
]);
var_dump($localFile->publicURI); // bool(false)

【讨论】:

  • 当我假设我的所有文件都是公开的时,那真是太棒了,但事实并非如此。
  • 我在这里看到的问题是使用绝对 url,这在切换服务器时在 prod 环境中处理起来很痛苦
  • 我用一个可能的解决方案编辑了我的答案:向SavedFile 对象添加一个标志。
猜你喜欢
  • 2017-10-11
  • 1970-01-01
  • 2021-09-08
  • 2018-04-14
  • 2019-06-27
  • 2015-08-26
  • 2016-03-29
  • 1970-01-01
相关资源
最近更新 更多