如何在Laravel 5中获取已上传文件的公共URL

9

你可能知道,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)

对于“私有”文件-我应该得到一个临时的URL,在一段时间后会过期。

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

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

但我不想用丑陋的“switch cases”来确定我正在使用哪个驱动程序。 那么,如何通过FileSystem接口获取CDN(如CloudFront)的URL? 有什么建议吗?


你已经成功完成了吗? - Christopher Francisco
我正在处理这个问题,我认为我需要编写一个库来处理它,使用装饰器设计模式扩展当前API。仍然难以思考系统设计。 - Lihai
1个回答

3
我认为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';
}

现在如果我有一堆文件存储在本地文件系统中,有一天我把它们移到亚马逊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)

这真是太棒了,当我假设所有我的文件都是公开的时候,但事实并非如此。 - Lihai
我在这里看到的问题是使用绝对URL,在切换服务器时在生产环境中处理起来很麻烦。 - Christopher Francisco
我编辑了我的答案并提供了一个可能的解决方案:向“SavedFile”对象添加一个标志。 - Ben Harold

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接