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 访问接口,看到以下效果
我们看到,真是太神奇了,居然自动拼接成了完整路径。