当用mongodb的gridfs存文件的时候,迁移和维护起来就方便多了。显示也方便,查出来直接输出(getBytes())二进制文件就好了。但是当文件很大的时候就会有内存不够的问题,设置内存的限制不可取,就算没有限制,加载出来也会很慢。于是分片的下载文件就变得很有用了,http协议中默认就有206这个协议,很好的解决了这个问题。nginx中已经有很好的实现,但是如果你用php来输出mongodb中的进制文件就得自己来实现了。于是我想到的方有如下两个:
方案一
mongodb会把大文件分片保存在chunks中,那我每次只取一部分应该可以解决吧,于是按照这个思路写了一版代码,发现并不理想,火狐浏览器OK,但是chrome却不行。各种折腾依然不行,应该是自己的姿势不对。
方案二
fseek操作文件指针,具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
public function showFile($id) { if (!empty($id) && strlen($id) == 24) { $mongo = new \MongoClient('mongodb://admin:admin@192.168.1.8:27017/test'); $db = $mongo->selectDB('test'); $model = $db->getGridFS('image')->findOne(['_id' => new \MongoId($id)]); $fp= $model->getResource(); $model = $model->file; $size = $model['length']; $begin = 0; $end = $size - 1; if (isset($_SERVER['HTTP_RANGE'])) { header('HTTP/1.1 206 Partial Content'); if (preg_match('/bytes=\h*(\d+)-(\d*)[\D.*]?/i', $_SERVER['HTTP_RANGE'], $matches)) { // 读取文件,起始节点 $begin = intval($matches[1]); // 读取文件,结束节点 if (!empty($matches[2])) { $end = intval($matches[2]); } } header('Content-Range: bytes ' . $begin . '-' . $end . '/' . $size); } else { header('HTTP/1.1 200 OK'); } header('Cache-control: public'); header('Pragma: no-cache'); header('Accept-Ranges: bytes'); header('Content-Type: ' . $model['contentType']); header('Content-Transfer-Encoding: binary'); header('Content-Length:' . (($end - $begin) + 1)); //header('Content-Disposition: attachment;filename="' . $model['filename'] . '"'); header('ETag:'. md5($id)); fseek($fp, $begin); while (!feof($fp)){ echo fread($fp, 8192); ob_flush(); flush(); } } exit; } |
总结一下,两个方案应该都可以得得通,方案一有空再试一下。这里的关键问题还是http 206的理解,以及每次输出文件的起始位置。