fastDFS是由淘宝余庆开发的。开源,免费。主要用于大规模的文件存储。Django对文件的支持,默认是放在工程目录一起,如果文件量大,增加服务压力。所以,尽量把文件服务器分离开了,专门存储文件。
本节主要介绍如何通过django的自定义存储,自动把文件存储到fastDFS上。
01 安装客户端插件
fastDFS客户端插件,网上代码基本上都是下载fdfs_client-py-master.zip,而fdfs_client-py-master.zip不能在线安装,必须单独下载文件,而且,有些文件还安装要报错。因此,我们采用py3Fdfs插件,在工程创建时已安装了。
02 下载fastDFS的client.conf配置文件。
在fastDFS服务器上,下载client.conf文件,并保存在Configurations目录下。
03 重写Storage类
Storage主要功能是上传文件,重写该类,把文件自动上传到fastDFS服务器。
有两点需要说明:
前端把文件上传到django服务器,如果保存之后再上传到fastDFS服务器,会导致效率低下,而fastDFS提供了缓存文件上传。也就是说,当前端把文件传到Django服务器,直接就上传到fastDFS服务器,不需要再存为文件。但此时,fastDFS返回的文件名没有扩展名,如果浏览器访问,浏览器不认识的文件,不会自动打开,而是直接下载。所以,我们必须要告诉fastDFS返回文件的扩展名。如图片,txt等文件,直接浏览器就可以打开。同时,处理文件扩展名的时候,特别要注意文件本身中有多个点,而扩展名只是最后一个点后面的名字。
另外,fdfs_client-py-master.zip和fastDFS函数名都相同,但使用方式不同,这段存储代码来自于网上,但已经过修改。能正常运行。
在GeneralTools目录下创建文件FastDFSStorage.py文件,内容如下:
from django.conf import settings
from django.core.files.storage import Storage
from django.utils.deconstruct import deconstructible
from fdfs_client.client import Fdfs_client, get_tracker_conf
import os
@deconstructible
class FastDFSStorage(Storage):
def __init__(self, base_url=None, client_conf=None):
"""
初始化
:param base_url: 用于构造图片完整路径使用,图片服务器的域名
:param client_conf: FastDFS客户端配置文件的路径
"""
if base_url is None:
base_url = settings.FDFS_URL
self.base_url = base_url
if client_conf is None:
client_conf = settings.FDFS_CLIENT_CONF
self.client_conf = client_conf
self.tracker_obj = get_tracker_conf(self.client_conf)
def _open(self, name, mode='rb'):
"""
用不到打开文件,所以省略
"""
pass
def _save(self, name, content):
"""
在FastDFS中保存文件
:param name: 传入的文件名
:param content: 文件内容
:return: 保存到数据库中的FastDFS的文件名
"""
client = Fdfs_client(self.tracker_obj)
# 告诉fastDFS服务器,返回文件的扩展名。
ext_name = os.path.splitext(name)[1][1:]
ret = client.upload_by_buffer(content.read(), ext_name)
if ret.get("Status") != "Upload successed.":
raise Exception("upload file failed")
file_name = bytes(ret.get("Remote file_id")).decode()
# 必须替换路径中的分隔符,否则,查询不到上传的文件。
return file_name
def url(self, name):
"""
返回文件的完整URL路径
:param name: 数据库中保存的文件名
:return: 完整的URL
"""
if name.startswith('http'):
return name
else:
return self.base_url + name
def exists(self, name):
"""
判断文件是否存在,FastDFS可以自行解决文件的重名问题
所以此处返回False,告诉Django上传的都是新文件
:param name: 文件名
:return: False
"""
return False
04 在settings.py中配置fastDFS
FDFS_SERVER = '49.235.156.156'
DEFAULT_FILE_STORAGE = 'GeneralTools.FastDFSStorage.FastDFSStorage'
# 自定义两个变量,分别表示client.conf文件的路径和fdfs的url
FDFS_CLIENT_CONF = os.path.join(BASE_DIR, 'Configurations', 'client.conf')
FDFS_URL = 'http://' + FDFS_SERVER + ':80/'
05 创建模型
在该模型中定义一个图片字段。我们改一下之前在Organizations/models.py中创建的UserInfo模型。并执行数据迁移。
from django.db import models
from django.contrib.auth.models import AbstractUser
from GeneralTools.BaseModel import BaseModel
class UserInfo(AbstractUser, BaseModel):
openid = models.CharField(max_length=30, unique=True, verbose_name='微信openID', help_text='微信openID')
mobile = models.CharField(max_length=11, unique=True, verbose_name='手机号', help_text='手机号')
# 默认的username是有唯一约束的,暂存入手机号。另增一个name字段存放姓名(微信昵称)
name = models.CharField(max_length=30, null=True, blank=True, verbose_name='姓名', help_text='姓名')
photo_url = models.ImageField(upload_to='user', null=True, blank=True, verbose_name='头像', help_text='头像')
def __str__(self):
return self.name
class Meta:
db_table = 'UserInfo'
verbose_name_plural = '001 用户信息表'
06 创建视图
在Organizations/views下创建UserSave.py文件。
from rest_framework import serializers
import re
from rest_framework_jwt.settings import api_settings
from django_redis import get_redis_connection
import logging
from rest_framework.generics import CreateAPIView
from Applications.Organizations.models import UserInfo
# 获取在配置文件中定义的logger,用来记录日志
logger = logging.getLogger('Organizations')
class UserRegisterSer(serializers.ModelSerializer):
"""
用户注册序列化器
"""
class Meta:
model = UserInfo
fields = ('id', 'name', 'photo_url', 'password', 'mobile', 'openid')
def create(self, validated_data):
"""
用户注册,向数据库保存用户
"""
# 移除数据库模型类中不存在的属性
validated_data['username'] = validated_data['mobile']
try:
user = super().create(validated_data)
# 调用django的认证系统加密密码
user.set_password(validated_data['password'])
user.save()
return user
except Exception as e:
logger.error(str(e))
return validated_data
class UserSave(CreateAPIView):
"""
用户注册第三步:创建用户
POST /OrgsAndUsers/user/register/create/
"""
serializer_class = UserRegisterSer
07 运行工程
在浏览器访问url,注:在接口文档中访问,不能选择文件。
08 查询数据库,我们看到的内容如下:
我们看到,第一条记录的路径分隔符没改,后面的都改好了。
但数据库中,只保存了相对路径,那么,查询的时候,是否需要自己手动去拼接完整路径呢?
先看查询结果:
09 编写查询接口
在Applications/Organizations/views/UserSave.py文件中,增加一个查询接口,并配置url,查看返回的数据。
class UserSaveList(ListAPIView):
queryset = UserInfo.objects.all()
serializer_class = UserRegisterSer
10 访问接口,看到以下效果
我们看到,真是太神奇了,居然自动拼接成了完整路径。