MongoEngine创建自己的文件代理

背景

mongodb可以储存文件,使用上来说非常方便。唯一的问题就是贵,把大量的图片之类的文件内容存储到mongodb中,导致mongodb占用的空间急剧增加。众所周知,云数据库的扩容远比添加一块相等大小的云盘昂贵。
出于节约不必要的成本问题,自己写一个mongoEngine 的文件代理,把文件实体存储到磁盘上,使用上与储存到数据库没有区别。

方法

本文介绍的是使用mongoEngine作为ORM,继承改写FileField,令其支持将文件存储到指定的目录下。

第一步 确定磁盘路径

先在云服务器上申请一块云盘,挂载到服务器上。
为什么要额外申请磁盘而不是直接存储到服务器自带的磁盘上?因为大部分云服务提供商都支持对云盘的扩容,为了避免日后扩容导致服务停机,还是额外申请一块云盘。
将挂载点的路径写入到配置文件里面。让代码可以读到这个信息即可。

第二步 继承FileField

class LocalFile(FileField):  # 文件代理
    proxy_class = LocalFileProxy

没错,就2行(先能用再说,有问题再改)。

第三步 确定文件存储路径

网上看了下都说Linux在某一级目录下的文件数量是有限的,所以在储存文件的时候,不能直接丢到这个目录下就完事,还是要随机分散到多级目录下面。

# FILE_ROOT 就是第一步要求的挂载点的配置信息
def get_path(grid):
    grid = str(grid)
    return '{}/{}/{}/{}/{}'.format(FILE_ROOT, grid[:3], grid[3:6], grid[6:9], grid[9:])

上面这个方法,通过文件的ID,确定存储路径,总共3级。

第四步 实现LocalFileProxy

在前一步有个LocalFileProxy,这是自己实现的。我写的代码如下

class LocalFileProxy(GridFSProxy):
    def get(self, grid_id=None):
        if grid_id:
            self.grid_id = grid_id

        if self.grid_id is None:
            return None

        try:
            if self.gridout is None:
                with open(get_path(self.grid_id), 'rb') as f:
                    self.gridout = BytesIO(f.read())
            return self.gridout
        except Exception as e:
            # File has been deleted
            return None

    def put(self, file_obj, **kwargs):
        if self.grid_id:
            raise GridFSError(
                "This document already has a file. Either delete "
                "it or call replace to overwrite it"
            )
        # 创建ID,并保存文件
        if 'grid_id' in kwargs:
            self.grid_id = kwargs['grid_id']
        else:
            while True:
                self.grid_id = ''.join(random.choice('1234567890abcdef') for i in range(24))
                if not os.path.exists(get_path(self.grid_id)): # 极低概率碰撞,但还是要检测下
                    break
        path = get_path(self.grid_id)
        dir_path = '/'.join(path.split('/')[:-1])
        if not os.path.exists(dir_path):
            os.makedirs(dir_path)
        with open(path, 'wb') as f:
            f.write(file_obj.read())
        self.grid_id = ObjectId(self.grid_id)
        self._mark_as_changed()

    def delete(self):
        if self.grid_id:
            path = get_path(self.grid_id)
            if os.path.exists(path):
                os.remove(path)
            self.grid_id = None
            self.gridout = None
            self._mark_as_changed()

总的来说,就是把关键的 get,put,delete三个方法实现,replace之类的不用实现,因为是调用这3个基础方法。

大功告成

还有最后一个小问题,怎么使用。
替换掉原来准备使用 FileField()的地方,用LocalFile()就可以了。文件本体将会从储存在数据库中变成储存到你指定的云盘里。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
禁止转载,如需转载请通过简信或评论联系作者。

推荐阅读更多精彩内容

  • 夜莺2517阅读 127,787评论 1 9
  • 版本:ios 1.2.1 亮点: 1.app角标可以实时更新天气温度或选择空气质量,建议处女座就不要选了,不然老想...
    我就是沉沉阅读 11,832评论 1 6
  • 我是黑夜里大雨纷飞的人啊 1 “又到一年六月,有人笑有人哭,有人欢乐有人忧愁,有人惊喜有人失落,有的觉得收获满满有...
    陌忘宇阅读 12,720评论 28 53
  • 兔子虽然是枚小硕 但学校的硕士四人寝不够 就被分到了博士楼里 两人一间 在学校的最西边 靠山 兔子的室友身体不好 ...
    待业的兔子阅读 7,499评论 2 9