如何在Laravel中提供图像服务?

31

我将用户个人资料照片存储在laravel存储文件夹中,而不是公共文件夹中,因为我希望保持公共文件夹免于用户混乱。

为了从该文件夹提供图像,我创建了一个简单的控制器操作,如下所示:

public function profilePicture($person, $size = 40){
    $profile_picture_url = storage_path().'/profile_pictures/'.$person['id'].'/profile_'.$size.'.jpg';

    if(!File::exists( $profile_picture_url ))
        App::abort(404);

    return Image::make($profile_picture_url)->response('jpg');
}

这样做可以被认为是一个好的实践吗,还是我应该只在公共文件夹中保存图片呢?这样做会导致性能问题吗?


最好的方法是使用一个mutator。我现在会给你提供一个样例。 - Jilson Thomas
5个回答

57

问题的简短回答

这样做是否可以被视为良好的实践?或者我应该只是将图片保存在公共文件夹中?这样做会导致性能问题吗?

这不是建议的做法,因为您需要读取文件并重新生成它,这将花费处理时间并加载服务器,但这取决于请求数量、图像大小等。我使用此方法来保护图像/文件免受公众访问,因此只有经过身份验证的成员才能访问图像/文件,就像在这个answer中一样。再次取决于文件大小、请求数量和服务器规格,我已经使用了一段时间,并且性能没有任何问题,它运行得很好(我的服务器是512MB内存、1核处理器、20GB SSD硬盘VPS解决方案)。您可以试用一段时间并查看效果。

符号链接解决方案

也可以创建符号链接,例如

ln -s /pathof/laravel/storage/profile_pictures /pathof/laravel/public/profile

This solution won't affect performance, but you need to document the solution in your internal documentation, in case you move your setup to new provider or in case you need to re-link to the storage folder.
But if you still wish to have the full solution for returning image from storage folder, first of all we need to install Intervention Image for Laravel, I am not sure if this done already or not. If you have installed it, continue here, but if not, follow the last part of this answer and then continue with Laravel solution.
Laravel solution:
As mentioned, we assume your intervention works. First of all, you need to create a route that will forward all image request access to our controller.
Create Route.
Route::get('profile/{person}', 'ImagesController@profilePicture');

创建路由后,我们需要创建一个控制器来处理来自路由的图像请求。
创建ImagesController 从命令行输入:
php artisan make:controller ImagesController

而你的控制器应该长成这样。

class ImagesController extends Controller {

    public function profilePicture($person, $size = 40)
    {
        $storagePath = storage_path('/profile_pictures/' . $person . '/profile_' . $size . '.jpg');

        return Image::make($storagePath)->response();
    }
}


编辑

对于使用 Laravel 5.2 及以上版本的用户。Laravel 引入了新的、更好的方式来 服务文件,这种方式的开销较小(不像答案中提到的那样会重新生成文件):

File Responses

The file method can be used to display a file, such as an image or PDF, directly in the user's browser instead of initiating a download. This method accepts the path to the file as its first argument and an array of headers as its second argument:

return response()->file($pathToFile);

return response()->file($pathToFile, $headers);

记得添加

use Intervention\Image\Facades\Image;

在你的ImagesController类中

最后确保你已经创建了包含测试图片的文件夹结构。

storage/profile_pictures/person/profile_40.jpg

现在,如果您在浏览器中编写
http://laravelLocalhostUrl/profile/person

它将显示您的图像,我自己制作并测试过。 输入图像描述

注意:我已经尽最大努力使文件夹反映您的问题,但您可以轻松修改以适合您想要的方式。


安装Intervention(如果您已经安装了它,请跳过此部分)

按照以下指南:http://image.intervention.io/getting_started/installation

简要概述:php composer require intervention/image

并在您的config/app中的$providers数组中添加此软件包的服务提供者。

Intervention\Image\ImageServiceProvider::class

将此包的门面添加到$aliases数组中。
'Image' => Intervention\Image\Facades\Image::class

这个解决方案的灵感来自于 这个答案,但那个是一般情况下用身份验证保护图像的方法,而这个 答案


1
终于有一个好答案了!目前我已经按照你写的方式设置了它,但我对最少耗费资源的方法很感兴趣(即不使用路由)。你是在建议最好改变.htaccess并从那里处理请求吗? - clod986
@clod986 如果您不想保护您的文件,并且有很多文件需要处理,那么.htaccess或符号链接是可行的方法。但是路由解决方案也可以正常工作。由于我不知道您的配置、策略以及服务器对图像请求的大小,我个人使用nginx,但应该很容易通过htaccess创建新的访问权限到存储文件,只需确保在storage下创建一个名为public2的子文件夹,否则给予所有存储文件访问权限是不好的。 - Maytham Fahmi
@maytham-ɯɐɥʇʎɐɯ 我看到一些页面在<img src="">中使用GIF文件时会提供一个GIF文件,但是当相同的链接放在浏览器上时,它会显示一个包含GIF和其他信息的页面。这可以通过您描述的方法实现吗? - JCarlosR
@NikhilRadadiya,我不确定我是否完全明白你的意思,但我会尝试进行改进。例如,您可以通过Laravel代码访问存储文件夹,但是由于受到.htaccess的限制,它是不公开可访问的。如果我理解有误,请详细说明。 - Maytham Fahmi
2
非常详尽的答案,甚至在这里使用 Laravel 9 进行工作。 - maximus1127
显示剩余4条评论

20

使用 Laravel 5.2+ 的 Image::make($pathToFile)->response() 可能被视为不良实践。任何寻找 Laravel 图片服务解决方案的人都应该使用 return response() -> file($pathToFile, $headers);。这样做会产生更少的开销,因为它只是提供文件而不是“在 Photoshop 中打开”它 - 至少这是我的 CPU 监视器所显示的。

这里是文档链接


感谢您更新了这个答案并提供了新的信息。 - clod986
5
如果你从文档链接中删除 5.4,它将重定向到最新版本。此外,您可能需要为那些变量添加一些提示...我正在使用 $headers = ['Content-Type' => 'image/png'];$path = storage_path('app/.../myImage.png') - charles-allen

3
在你的User.php模型中:
protected $appends = ['avatar'];

public function getAvatarAttribute($size=null)
{
   return storage_path().'/profile_pictures/'.$this->id.'/profile_'.$size.'.jpg';
}

因此,每当您调用获取用户实例时,您将与其一起获得他的头像。


3
我已经实现了这个,谢谢你的提示,但我的问题完全不同。我想知道是否应该通过特定路由提供图像。 - clod986
不,您不应该发出附加请求来获取用户资料。信息已经附加到您的用户实例中,您可以从那里获取它。无需使用额外的路由。 - Jilson Thomas
1
抱歉@Jilson,但我想我表达得不够清楚。你的getAvatarAttribute()返回一个URL;我已经知道了。我不知道的是,一旦用户连接到该URL,最好做什么:是否应该创建一个路由来处理它? - clod986
你是在尝试检查用户配置文件是否存在吗? - Jilson Thomas
不,我只是在尝试找出一种最佳的方法,在请求 <img src="url-of-image" /> 后返回。 - clod986

3

0

@zhwei的答案对我很有用,只是稍作修改就可以与base64一起使用了。

return response()->stream(function() use ($data) {
    $im = imagecreatefromstring(base64_decode($data));
    try {
        imagejpeg($im);
    } finally {
        $im && imagedestroy($im);
        $im = null;
    }
}, 200, ['Content-type' => 'image/jpeg']);

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