背景
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()就可以了。文件本体将会从储存在数据库中变成储存到你指定的云盘里。