基于MongoDb的S3实现
jopen
13年前
<p>原理是利用MongoDb的GridFS,伸展性方面交由MongoDb的auto sharding去实现,这里用PHP给MongoDb绑了个S3出来,支持选择文件存储节点,支持文件分目录存储,这样的好处是对于一些受时间影响比较 明显的文件,可以按照年月的形式存储,减轻历史包袱。</p> <p>首先,配置MongoDb GridFS节点信息:</p> <div> <div> <pre><?php $s3Config = array( 'foo' => array( 'server' => '127.0.0.1', 'database' => 'test', 'user' => 'test', 'password' => 'foobar', 'domain' => 'http://s3.foobar.com' ), 'bar' => array( 'server' => '127.0.0.1', 'database' => 'test', 'user' => 'test', 'password' => 'foobar', 'domain' => 'http://s3.foobar.com' ), );</pre> </div> </div> <p>MongoDb的S3绑定:</p> <div> <div> <pre><?php /** * 统一文件存储 * */ class Api_S3 { protected $_node; protected $_dir; protected $_config; /** * 构造函数 * * @param string $node * @param string $dir * @param array $config */ public function __construct($node, $dir = null, $config = null) { $this->_config = $config; $this->path($node, $dir, false); } /** * 设置文件路径 * * @param string $node * @param string $dir * @return Api_S3 */ public function path($node, $dir, $connect = true) { $this->_node = $node; $this->_dir = empty($dir) ? 'fs' : $dir; if (empty($this->_config[$this->_node])) { throw new Cola_Exception('Api_S3: invalidate node'); } if ($connect) { $this->_gridFS = $this->_gridFS(); } return $this; } /** * GridFS * * @return MongDbGridFS */ protected function _gridFS() { $mongo = new Cola_Com_Mongo($this->_config[$this->_node]); return $mongo->gridFS($this->_dir); } /** * 获得文件句柄 * * @param string $name * @return MongoGridFSFile */ public function file($name) { if (empty($this->_gridFS)) { $this->_gridFS = $this->_gridFS(); } return $this->_gridFS->findOne(array('filename' => $name)); } /** * 获得文件内容 * * @param string $name */ public function read($name) { $file = $this->file($name); return $file->getBytes(); } /** * 写入文件 * * @param string $name * @param string $data * @param array $extra * @param boolean $overWrite * @return boolean */ public function write($name, $data, $extra = array(), $overWrite = false) { $extra = (array)$extra + array('filename' => basename($name)); if ($filetype = $this->_type($name)) { $extra['filetype'] = $filetype; } if ($this->file($extra['filename'])) { if ($overWrite) { $this->delete($extra['filename']); } else { throw new Cola_Exception('Api_S3: file exists'); } } return $this->_gridFS->storeBytes($data, $extra); } /** * 复制系统文件 * * @param string $file * @param array $extra * @param boolean $overWrite * @return boolean */ public function copy($file, $extra = array(), $overWrite = false) { $extra = (array)$extra + array('filename' => basename($file)); if ($filetype = $this->_type($file)) { $extra['filetype'] = $filetype; } if ($this->file($extra['filename'])) { if ($overWrite) { $this->delete($extra['filename']); } else { throw new Cola_Exception('Api_S3: file exists'); } } return $this->_gridFS->storeFile($file, $extra); } /** * 删除文件 * * @param string $name * @return boolean */ public function delete($name) { if (empty($this->_gridFS)) { $this->_gridFS = $this->_gridFS(); } return $this->_gridFS->remove(array('filename' => $name)); } /** * 获得文件地址 * * @param string $name * @param string $default * @return string */ public function getUrl($name, $default = false) { $data = array( 'domain' => rtrim($this->_config[$this->_node]['domain'], '/'), 'path' => $this->_node . (('fs' == $this->_dir) ? '' : $this->_dir), 'name' => $name ); return implode('/', $data); } /** * 设置文件属性 * * @param string $name * @param array $attr * @return boolean */ public function setAttr($name, $attr) { if (!$file = $this->file($name)) { throw new Cola_Exception('Api_S3: file not exists'); } $file->file = $attr + $file->file; return $this->_gridFS->save($file->file); } /** * 获得文件属性 * * @param string $name * @return array */ public function getAttr($name) { $file = $this->file($name); return $file->file; } /** * 获得文件类型 * * @param string $file * @return string */ protected function _type($file) { return mime_content_type($file); } }</pre> </div> </div> <p>文件存入,支持自选节点,自定义目录,自定义文件名,可以自动添加文件类型:</p> <div> <div> <pre><?php $s3 = new Api_S3($node, $dir, $s3Config); $s3->copy($file, array('filename' => $name, 'filetype' => $type));</pre> </div> </div> <p>文件读取,以”http://s3.foobar.com/foo/201005/foobar.jpg”为例,foo映射到节点名,201005映射到目录名,foobar.jpg映射到文件名:</p> <div> <div> <pre><?php $s3 = new Api_S3($node, $dir, $s3Config); $file = $s3->file($name); Cola_Response::lastModified($file->file['uploadDate']->sec); Cola_Response::etag($file->file['md5']); if (isset($file->file['filetype'])) { header("Content-Type: {$file->file['filetype']}"); } echo $file->getBytes();</pre> </div> </div> <p>注意到我们利用了文件的修改时间设置http头的last modified,以及用文件的md5信息设置etag值,这样的好处是可以大大减少带宽使用,当然,你也可以设置expire时间来减少重复请求。</p> <p>关于性能问题,可以在PHP读取的上一层,加一个Squid之类的反向代理服务,基本上就不会有问题。</p> <p>转自:http://www.fuchaoqun.com/2010/05/s3-on-mongodb-with-php/</p>