[ fastdfs文件服务器 ]


\安装 pip install py3Fdfs 客户端

虚拟机环境

  • 桥接模式, 使用固定
#安装
docker pull delron/fastdfs

# 启动调度服务器
docker run -dit --network=host --name tracker -v /var/fdfs/tracker:/var/fdfs -v /etc/localtime:/etc/localtime delron/fastdfs tracker


# 启动存储服务器
docker run -dit --network=host --name storage -e TRACKER_SERVER=192.168.10.3:22122 -v /var/fdfs/storage:/var/fdfs -v /etc/localtime:/etc/localtime delron/fastdfs storage


# 防火墙开放22122 23000 8888 三个端口


# 总日志
/var/fdfs/logs

# storage
/var/fdfs/storage/logs


# 1台tracker调度服务器, 多台storage存储服务器的方案(storage在同一个组中, 组中全部成员资料都会复制)
docker run -dit --network=host --name tracker -v /var/fdfs/tracker:/var/fdfs -v /etc/localtime:/etc/localtime delron/fastdfs tracker

docker run -dit --network=host --name storage -e TRACKER_SERVER="tracker主机地址":22122 -v /var/fdfs/storage:/var/fdfs -v /etc/localtime:/etc/localtime delron/fastdfs storage
docker run -dit --network=host --name storage -e TRACKER_SERVER="tracker主机地址":22122 -v /var/fdfs/storage:/var/fdfs -v /etc/localtime:/etc/localtime delron/fastdfs storage

python客户端基本使用

from fdfs_client.client import Fdfs_client,get_tracker_conf

conf_path = get_tracker_conf("/home/just/Django02/FlaskIhome/ihome/utils/fdfs/client.conf")
client = Fdfs_client(client=conf_path)
# 上传图片
ret = client.upload_by_filename("./20191023111957.png")  # 上传图片名
# ret =  client.upload_by_buffer()  上传byte数据
print(ret)

# 删除图片
file_id = ret["Remote file_id"]     # 删除图片需要上传时返回的id
ret = client.delete_file(file_id)  # file_id = b"xxxxxxxxxxxxx"
print(ret)

案例

  • 重写django文件储存类
from fdfs_client.client import Fdfs_client , get_tracker_conf
from django.conf import settings
from django.core.files.storage import Storage
from django.utils.deconstruct import deconstructible
import os
import logging
log = logging.getLogger(__name__)


class FastDFSStorage(Storage):
    def __init__(self,base_url=None,client_conf=None):# 构造路径,服务器配置文件路径
        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

    def save(self, name, content, max_length=None):
        '''save方法'''
        conf_path = get_tracker_conf(self.client_conf)
        client = Fdfs_client(conf_path)
        imgdir = f'{os.path.join(settings.BASE_DIR, "utils/fastdfs")}/tmp.{str(name).split(".")[1]}' # tmp拼接后缀
        with open(imgdir,'wb+') as f:
            f.write(content.read())  # 读取文件数据写入f
        result = client.upload_by_filename(imgdir)  # 写入后上传至数据库
        os.remove(imgdir) # 删除刚刚本地创建的文件
        if result.get('Status') != 'Upload successed.': # 如果返回结果
            logging.error('上传文件到FastDFS失败')
            raise Exception('上传文件到FastDfs失败')
        filename = result.get('Remote file_id')  # 服务器返回的地址路径Byte类型
        return filename.decode() # 返回地址路径字符串

    def url(self, name):
        '''
        返回文件的完整路径
        :name  数据库中保存的文件
        '''
        return self.base_url + name

    def exists(self, name):  # FastDfs可以自己解决文件重名,
        '''
        :param name:文件名
        :return: False
        '''
        return False   # 返回False,告诉Django 上传的都是新文件

    def delete(self, name):
        conf_path = get_tracker_conf(self.client_conf)
        client = Fdfs_client(conf_path)
        result = client.delete_file(name)
        if 'Delete file successed.' == result[0]:  # 如果返回结果
            logging.error('删除文件到FastDFS失败')
            raise Exception('删除文件到FastDfs失败')
        return result[1].decode()
  • settings.py
# FDFS配置
# 自定义文件系统
# django文件存储
DEFAULT_FILE_STORAGE = 'utils.fastDFS.FDFS_storage.FastDFSStorage'
FDFS_URL = "http://121.5.109.75:8088/"
FDFS_CLIENT_CONF = os.path.join(BASE_DIR, 'utils/fastDFS/client.conf')  # conf路径
  • view视图使用
from django_china.utils.fastdfs.fdfd_storage import FastDFSStorage

def xxxx():
    file = request.FILES.get('file')
        if not file:
            return http.JsonResponse({'code':4001,'errmsg':'没有图片数据'})

        filename = file.name
        content = file.file
    try:
        fafs = FastDFSStorage()
        image_url = fafs.save(name=filename,content=content)
    except Exception as e:
        logging.error(e)
        return http.JsonResponse({'code':4001,'errmsg':'储存用户图片失败'})

    try:
        User.objects.filter(id=user_id).update(url=image_url)
    except Exception as e:
        logging.error(e)
        fafs.delete(bytes(image_url))
        return http.JsonResponse({'code': 4001, 'errmsg': '数据库储存用户图片失败'})

案例2

  • 视图
class NoteSerializers(serializers.ModelSerializer):
    upload_image = serializers.ListField(write_only=True, label="上传图片列表")
    class Meta:
        model = Diary
        fields = '__all__'

    def validate(self, attrs):
        redis_conn = get_redis_connection('default')
        request = self.context.get('request')
        if images_list := attrs.get('upload_image'):
            content = attrs.get('content')
            print("文章上传的图片:",images_list)
            for i in images_list:
                # 查找用户上传的图片是否存在于文本中
                if re.search(i, content, re.S): # 如果图片还存在于文本中 = 1
                    # redis中去除这些图片(在列表中删除了, 后面就不会执行delete操作)
                    print("列表中准备去除的图片:", i)
                    redis_conn.lrem("upload_image_%s" % request.user.id, 0, i)

        # 剩下redis列表中没删除的图片, 全部执行delete()
        if delete_image_list := redis_conn.lrange("upload_image_%s" % request.user.id, 0, -1):
            fdfs = FastDFSStorage()
            print("redis准备删除的图片:",delete_image_list)
            # 删除列表图片
            for image in delete_image_list:
                print(image)
                fdfs.delete(image)
            # 删除列表
            redis_conn.delete("upload_image_%s" % request.user.id)

        print("upload_image")        
        del attrs['upload_image']
        return attrs


# 日记视图
class NoteModelViewSet(ModelViewSet):
    queryset = Diary.objects.all()
    serializer_class = NoteSerializers
    pagination_class = NoteLimitOffsetPagination

    def perform_destroy(self, instance):
        if upload_image_list := re.findall(f'{settings.FDFS_URL}(.*?)"', instance.content, re.S):
            print("准备删除的图片:", upload_image_list)
            fdfs = FastDFSStorage()
            for i in upload_image_list:
                fdfs.delete(bytes(i, encoding="utf-8"))
        instance.delete()


class NoteImageUpload(APIView):
    def post(self, request):
        data = request.data
        print(data)
        image = data.get('uploadImage')
        fdfs = FastDFSStorage()
        try:
            upload_name = fdfs.save(name=image.name, content=image.file)
            redis_conn = get_redis_connection('default')
            redis_conn.lpush("upload_image_%s" % request.user.id, upload_name)
            message = {
                "errno": 0,  # 是数字
                "data": {
                    "url": settings.FDFS_URL + upload_name,   # 图片src, 必须
                    "alt": "image_alt",   # 图片描述, 非必须
                    "href": "image_href",  # 图片的链接,非必须
                    "upload_name": upload_name
                }
            }
        except Exception as e:
            print(e)
            message = {
                "errno": 1,
                "message": e,
            }

        return Response(data=message, content_type="application/json")
  • 重写的文件存储系统
from fdfs_client.client import Fdfs_client , get_tracker_conf
from django.conf import settings
from django.core.files.storage import Storage
from django.utils.deconstruct import deconstructible
from django_redis import get_redis_connection
from fdfs_client.exceptions import DataError
import os
import logging
import time
logger = logging.getLogger(__name__)


class FastDFSStorage(Storage):
    def __init__(self,base_url=None,client_conf=None):# 构造路径,服务器配置文件路径
        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

    def save(self, name, content, max_length=None):
        '''save方法'''
        conf_path = get_tracker_conf(self.client_conf)
        client = Fdfs_client(conf_path)
        imgdir = f'{os.path.join(settings.BASE_DIR, "utils/fastDFS/")}/{str(int(time.time() * 10000))}.{str(name).split(".").pop()}' # tmp拼接后缀
        with open(imgdir,'wb+') as f:
            f.write(content.read())  # 读取文件数据写入f
        result = client.upload_by_filename(imgdir)  # 写入后上传至数据库
        os.remove(imgdir) # 删除刚刚本地创建的文件
        if result.get('Status') != 'Upload successed.': # 如果返回结果
            logger.error('上传文件到FastDFS失败')
            raise Exception('上传文件到FastDfs失败')
        filename = result.get('Remote file_id').decode()  # 服务器返回的地址路径Byte类型

        print(filename)
        return filename # 返回地址路径字符串

    def url(self, name):
        '''
        返回文件的完整路径
        :name  数据库中保存的文件
        '''
        return self.base_url + name

    def exists(self, name):  # FastDfs可以自己解决文件重名,
        '''
        :param name:文件名
        :return: False
        '''
        return False   # 返回False,告诉Django 上传的都是新文件

    def delete(self, name):
        try:
            conf_path = get_tracker_conf(self.client_conf)
            client = Fdfs_client(conf_path)
            result = client.delete_file(name)
            print(result)
            if 'Delete file successed.' == result[0]:  # 如果返回结果
                print("成功删除图片:",result[1])
            else:
                logger.error('删除文件到FastDFS失败')
                raise Exception('删除文件到FastDfs失败')
        except DataError as e:
            print(e)
            return "Error: 2, No such file or directory"
        return result[1].decode()  # 返回删除的图片路径字符串
  • setting.py配置文件
# FDFS配置
# 自定义文件系统
# django文件存储
DEFAULT_FILE_STORAGE = 'utils.fastDFS.FDFS_storage.FastDFSStorage'
FDFS_URL = "http://121.5.109.75:8088/"
FDFS_CLIENT_CONF = os.path.join(BASE_DIR, 'utils/fastDFS/client.conf')  # conf路径
  • client.conf配置
connect_timeout = 30
network_timeout = 60
tracker_server = 192.168.50.225:22122  # 主机ip
http.tracker_server_port = 8888  # nginx端口号默认8888