在日常的后端开发中, 不可避免的就是要处理各种文件上传的需要, 随之而来的就是各种下载的需求, 我遇到的大部分开发者都会考虑到在上传的时候做简单的文件校验(有的也没有做), 然而在文件下载的问题上, 则采用依赖于web服务器的静态文件传输, 经常造成很多的麻烦。
上传
大部分开发者实际上在上传的时候都会对文件进行校验, 并且为了安全性考虑甚至会将文件更换名称。但是更换名称后简单的依赖于web服务器进行下载则会让用户拥有一种我的文件被篡改过的感觉(文件名发生了变化)。
依赖于web服务器的文件上传,则可以认为是相对依赖于浏览器的url的一种操作,一旦我们将web应用部署在二级目录下,如:
则很容易出现存储问题,比如我们存储的目录很可能因为这个原因需要多嵌套一层path
目录
下载
下载遇到的问题同理,最经常遇到的则是静态文件仅仅通过web服务器来做下载,不方便做权限管理,并且容易发生盗用资源的现象,其他的web应用用着你的资源,但是却耗费着你的带宽。
另一个则和上传的问题一样,下载的时候也避免不了遇到二级目录的问题,这时候web服务器很容易就带着二级目录去做寻找。
并且在资源进行删除的时候,其相关的静态文件如果没有进行处理也不好做清理,容易造成垃圾文件堆叠。
解决
我个人在这方面也是有吃过很多的亏,上面的问题全部都碰到过,最后决定使用各种编程语言中读取文件的api来进行解决:
其中文件名更换非常的简单,我习惯于在文件进行存储时,对其旧的文件名进行持久化存储,以PHP为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <?php
$uniqid = uniqid(); $path = DATA_DIR . "/attached/{$uniqid}"; $ext = pathinfo($file['name'], PATHINFO_EXTENSION); $name = date("YmdHis") . '_' . rand(10000, 99999) . '.' . $ext; if (is_dir($path) && is_uploaded_file($file['tmp_name']) && move_uploaded_file($file['tmp_name'], "{$path}/{$name}")) { $old = $file['name']; $dir = $path; $path = "{$path}/{$name}"; $mime = $file['type']; }
|
我们可以获得文件相关的四个变量,其中$old
则为我们解决了文件名更换的问题,我们会将用户文件的文件名进行存储,而在我们本地则使用了$name
来重命名保证安全文件
同时,二级目录问题也得到了解决,这里使用的是我们自定义的目录$path
,不会受到部署的影响。
在下载的时候,我们也可以依赖于编程语言提供的api:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <?php
$article; if (!$article->id) $this->redirect('error/404'); $fullpath = APP_PATH . '/' . $article->path; header("Content-Type: {$article->mime}"); header('Accept-Ranges: bytes'); header('Accept-Length:' . filesize($fullpath)); header("Content-Disposition: attachment; filename=\"{$article->old}\""); ob_clean(); echo file_get_contents($fullpath); exit;
|
这里由于是编程语言的实现,那么我们依赖于权限的问题也得到了解决,如第5行中代码返回404
错误一样,我们也可以进行逻辑判断并返回401
错误
并且同时,这里的下载也会从我们的数据库里面寻找文件,不用再被web服务器的二级目录所困扰,并且如果更加深入一下,额外的代码也可以降低我们的资源被盗用的风险。
以下是node的http框架koa
的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const router = require('koa-router')() const send = require('koa-send');
router.get('/image', async ctx => { const mongo = new Database() const db = await mongo.init() let platform = await db.collection('platform').findOne({ "_id": "10010" }) if (platform) { ctx.attachment(platform.fileName); await send(ctx, platform.fileName, { root: `${__dirname}/../${platform.filePath}` }); } else { ctx.body = { data: {}, code: 400, message: '获取失败' } } db.close() })
|
也很简单方便