【Web总结】资源存储

前言

几乎所有的Web站点都需要存储文件资源,如图片、视频等。也有像百度网盘这样的平台专门做云存储,为用户提供了极大的便利。

基础知识

HTTP请求报文分为请求头、请求体。请求头中的字段,描述了请求体是什么类型的内容,它的字段值也叫做类型,也叫,在这里进行查询:http://www.w3school.com.cn/media/media_mimeref.asp有关Content-Type、媒体类型等详细知识可以参考《HTTP权威指南》有关使用HTTP协议上传文件的原理,可以参考:https://www.cnblogs.com/cswuyg/p/3185164.html

版本1

理解了上传文件的原理后,我们可以完成一个较为基础的文件保存函数,下面代码是基于Laravel5编写。

/**

* 保存上传的文件至服务器,并返回URL

* @param Illuminate\Http\UploadedFile $file 上传的文件

* @return string 文件在服务器中的URL,失败返回false

*/

functionstoreFile(UploadedFile$file) {

$store_path='uploads';

// 文件名称 xxx.yy

$name=time();

if($extension=$file->extension()) {

$name.=$extension;

}

if(Storage::putFileAs($store_path,$file,$name)) {

return'/storage/uploads/'.$name;

}else{

returnfalse;

}

}函数接受一个上传文件实例,保存成功后返回该文件对应的URL,失败返回false。做法很简单,把上传的文件都放到一个HTTP可访问的目录下,得到URL,用户访问此URL即可访问上传的资源。

第10行。获取上传文件的扩展名。

第13行。保存上传的文件到指定的目录,并指定的文件名。

但是这种做法有几个缺点甚至有安全漏洞:

可上传自定义脚本。比如用户可以上传自己编写的PHP文件,这是非常危险的。就算使用Java等编译语言进行后台开发,也应该避免脚本文件的上传。

文件都放在同一个目录,有的文件系统会有数量限制。可参考:https://blog.csdn.net/leonwei/article/details/3980179

文件名可能冲突(并发量比较大时)。

可能有重复文件。比如两个人在不同时间点上传了一样的文件,系统会保存两份。

版本2:校验和分类

上代码:

/**

* 保存上传的文件至服务器,并返回URL

* @param Illuminate\Http\UploadedFile $file 上传的文件

* @return string 文件在服务器中的URL,失败返回false

*/

functionstoreFile(UploadedFile$file) {

$time=time();

// 文件名称 xxx.yy

$name=$time;

if($extension=$file->extension()) {

$name.=$extension;

}

// 按日期将文件分类

$store_path='uploads'.DIRECTORY_SEPARATOR.date('Ymd',$time);

if(Storage::putFileAs($store_path,$file,$name)) {

return'/storage/'.str_replace('\\','/',$store_path) .'/'.$name;

}else{

returnfalse;

}

}

/**

* 检查上传的文件是否符合要求

* @param Illuminate\Http\UploadedFile $file 上传的文件

* @param array $rule 含mimetype, max_size(单位B)两个规则

* @return int 符合所有要求返回0,不符合mimetype返回1,不符合max_size返回2

*/

functioncheckFile(UploadedFile$file,$rule) {

if(!$rule)$rule=['mimetype'=>[],'max_size'=>];

if($rule['mimtype']&&!in_array($file->getMimeType(),$rule['mimetype'])) {

return1;

}elseif($rule['max_size']&&$file->getSize()>$rule['max_size']) {

return2;

}else{

return;

}

}先调用检查上传文件的mimetype、文件大小是否符合要求,函数返回0表示没有不符合的。再调用保存文件。这样可以尽可能的避免版本1的第一、二个问题。比如在上传头像时,可以规定只能上传jpg、bmp等图片格式,而且文件大小不得超过2M。下面解释部分代码:

第14行。是指当前操作系统的分隔字符,Linux是,windows是。

第16行。URL的分隔符都是所以需要替换一下。

文件名生成函数可以替换成,这是微秒级别的时间戳,所以就可以避免文件名冲突了(冲突可能性非常非常非常小,所以可以认为不会冲突)。此版本仍然不能解决资源重复问题。

版本3:统一资源存储

上代码:

/**

* 保存文件和model

* @param Illuminate\Http\UploadedFile $file

* @return ResourceModel

*/

publicfunctionstoreFile(UploadedFile$file) {

// 文件哈希值

$this->md=md5_file($file->getRealPath());

// 查找已存在的资源

if($exist_rs=ResourceModel::where('md',$this->md)->first()) {

$exist_rs->from_db=true;

return$exist_rs;

}

// 文件名称 xxx.jpeg

$this->name=$file->getClientOriginalName();

// mimetype image/jpeg

$this->mime=$file->getMimeType();

// 后缀 jpeg

$this->suffix=$file->extension();

DB::beginTransaction();

// 保存数据库

if(!$this->save()) {

DB::rollBack();

returnfalse;

}

// 文件保存路径 data/52/08/06

$store_path=$this->getStorePath();

// 520806eb60722ca0d10c89d3b20b370c

$store_name=$this->getStoreName();

// 保存文件

if(!Storage::exists($this->getFilename())) {

// 如果写入文件失败,则不存入数据库中

if(!Storage::putFileAs($store_path,$file,$store_name)) {

DB::rollBack();

returnfalse;

}

}

DB::commit();

return$this;

}

/**

* 显示文件

* @param Illuminate\Http\Request $req

* @param $md

* @return mixed

*/

publicfunctionshowResource(Request$req,$md) {

$rs=ResourceModel::where('md',$md)->first();

if(!$rs) {

return'resource not found';

}else{

if($rs->refer)returnredirect($rs->refer);

// uploads/7d/s8/7ds87x...

$filename=$rs->getFilename();

// 获取文件物理路径

$full_filename=storage_path("app/${filename}");

if(!file_exists($full_filename)) {

returnresponse('file not found',Response::HTTP_NOT_FOUND);

}

returnresponse()->file($full_filename,[

'Content-Type'=>$rs->mime

]);

}

}方法是里的,这里罗列一下的表结构:

是controller里的方法,用于处理HTTP请求。这个版本的核心思想是,先计算文件的MD5值,同一个文件具有一样的MD5值,不同的文件MD5值不一样。这个MD5值将作为文件名。文件的保存路径是取MD5值的前四位字符,两位分为一组,共两组,分别作为一级目录和二级目录名。此版本仍然使用检查文件类型和大小。代码解释:

第8行:计算文件的MD5值。

第10~13行:找到系统中已经存在的资源,直接返回该资源。这样可以避免资源重复。

第60行:返回文件的物理存储地址。

第64行:响应资源文件,并指定响应的Content-Type,让浏览器可以正常的显示资源。

这个版本已经可以满足绝大部分Web系统的需求了。并且还有可提升空间,可拓展性也比较强。举个扩展的例子,资源文件越多时,可以通过目录划分来做分布式存储。

视频资源

视频资源在存放于Web系统之前,往往要进行调整分辨率、编码格式、加水印之类的操作。对于大视频文件,也应该采取分片存储的方式。调整分辨率、编码格式、加水印等,可以使用完成。文件分片包含两个话题:上传时分片,保存时分片。上传时分片,保存时就比较好操作,直接按片划分保存就行;上传不分片,保存如果要分片的话,只能能采取视频截取的方式(使用ffmpeg)把视频分为几个部分保存,一般来说取前1分钟为第一片,后续每4分钟一片,是比较好的分片方法。视频文件分片后,播放时也是要分片播放的,如果采取1:4:4..:4的方法分片,前端需要先加载出第一片1分钟的视频,后面在依次加载4分钟的分片文件。这里解释下为什么第一片1分钟,因为用户一般看1分钟,没兴趣看就溜了,所以为了不浪费流量,第一片就1分钟比较合适。

分布式和同步

先看一个需要用到同步的场景,不同学校间共享教学视频,访问时又是自己内网的服务器。要实现这个功能有两个关键点:

学校的服务器至少有一台能和外部服务器通信。

采取分布式还是集中式同步。

集中式同步方案:选一个中心主机,视频都放在中心主机,并且通过文件共享手段,让服务器在读取资源时,直接从中心主机读取。文件共享手段比如NAS、rsync命令、基于xcopy的脚本等。需要注意的是,跨校区的同步速度是比较慢的。分布式同步方案:没有中心主机,上传的时候先放在本校服务器,每一个服务器定期向其他服务器询问是否有新资源,和推送资源给其他服务器。这种方案,也可以选一个中心主机,本地服务器在保存完资源后,将视频推送到中心主机,再由中心主机下发到其他学校服务器(类似git机制)。他们的核心区别是:集中式,不同服务器从同一台服务器上取视频资源;分布式,服务器从本地取视频资源。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180724G1TCVV00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券