在项目中,我们难免会遇到要上传文件的需求,例如头像,文章图片,等等相关的文件需求,那么如何才能做好文件上传呢?我将从 上传方式,存储方式等几个方向来做说明.
跟随表单上传是我们最早接触的一种上传方式了
<html>
<body>
<form action="upload_file.php" method="post"
enctype="multipart/form-data">
<label for="file">Filename:</label>
<input type="file" name="file" id="file" />
<br />
<input type="submit" name="submit" value="Submit" />
</form>
</body>
</html>
它要求 form表单 的 是 multipart/form-data形式.
浏览器将会渲染 input type=file的输入框作为选择文件,选择成功之后
只需要点击提交,文件即可和表单的其他内容一同上传
这个做法在前后分离之后,就基本很少见了.
新版的跟随表单上传,通过了ajax 形式进行一次性提交
跟随表单的做法目前还有,但是已经越来越少了
优点是每次都随着自己的业务表单提交,不会出现垃圾文件
缺点是如果文件太大,提交表单的时间将会很长,而且看不到进度
为了解决提交表单时文件太多/太大,导致提交表单的时间延长,开发者采用了新的方式进行上传,也就是 异步上传
表单还是原来的表单,在选择文件之后,前端将会立即请求另一个 文件上传的接口,直接将文件上传.同时返回文件上传后的路径交给前端的表单
在提交表单时,前端只需要将文件路径提交即可.
优点是 用户体验非常好,表单提交很快,同时将文件上传的逻辑跟表单保存逻辑分离,便于修改管理
缺点是 在用户选择完之后,如果此表单没有提交,这个文件就会成为垃圾文件一直存储在服务器中.
异步上传的垃圾文件解决方案如下:
- 用户上传文件到临时文件夹(/temp/)
- 用户提交表单之后,从临时文件夹移动文件到新目录中,表示该文件有效
- 如果用户是更新表单,从临时文件夹移动文件到新目录,同时删除原有文件
一般情况下,为了方便,我们通常都是直接将文件存储到本地服务器中,直接通过nginx代理获取文件
这样的做法优点是管理文件方便,实现简单,缺点是会占用服务器的带宽,使得带宽成本上升.
为了节省服务器带宽,可以直接使用 oss(对象存储) 服务,使用阿里云/腾讯云对象存储进行存储文件
用户访问也是直接访问 oss 不需要占用服务器的带宽,节省服务器带宽成本
一般情况下,我们都是存储文件的相对路径,例如:"./Upload/avatar/1.jpg"
如果是通过服务器存储的话,在网页中浏览器会自动加上当前域名进行访问文件 :"http://www.php20.cn/Upload/avatar/1.jpg"
但是如果通过oss存储方案的话,很明显,直接存储相对路径的话,没法直接访问,但是存储绝对路径的话,如果阿里云oss域名改了,或者说服务器迁移了,就会造成原先数据全部不能使用,那该怎么解决这个问题呢?
我们可以通过 存储相对路径+取出数据额外组装数据的方式实现oss文件的存储.
例如:oss host为:"http://oss.xxx.aliyunoss.com/",文件路径为:"./Upload/avatar/1.jpg"
在数据库中只存储相对路径,然后在获取数据时,获取到oss host 数据,进行拼接组装,即可解决此问题.
在刚刚的oss 存储绝对路径的时候,又引来了新的问题
当第一次上传的时候,前端提交的是"./Temp/avatar/1.jpg"(临时路径+复制文件到新路径) 一个绝对路径
如果前端需要编辑,更新表单,会因为 后端额外拼接组装了数据,而导致下次提交变成了 "http://oss.xxx.aliyunoss.com/./Upload/avatar/1.jpg";这个时候存储路径则额外增加了 一个host.
所以我们需要在更新文件路径时做好判断,具体步骤如下:
1:第一次上传,temp/xx.jpg
2:判断该路径是否为 "temp/"前缀开头,如果是,则代表是临时文件,代表修改了文件
3:将临时文件复制一份正式文件"Upload/avatar/1.jpg",存储到数据库
4:前端获取信息时,后端自动拼接:"http://oss.xxx.aliyunoss.com/Upload/avatar/1.jpg"
5:第二次提交,不更新文件路径,则将提交:"http://oss.xxx.aliyunoss.com/Upload/avatar/1.jpg" 通过第二步的判断,表示此文件不是临时文件,则不做文件路径更新
6:第二次提交,更新文件路径,则将提交 :"temp/xx.jpg",由于第二点的判断,则将复制一份正式文件 "Upload/avatar/2.jpg",存储到数据库
7:同时删除原来数据库存储的 "Upload/avatar/1.jpg" 文件