[ FDFS文件Django配置 ]


utils文件

FDFS_storage.py

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)
        # tmp拼接后缀
        imgdir = f'{os.path.join(settings.BASE_DIR, "utils/fastDFS/")}/{str(int(time.time() * 10000))}.{str(name).split(".").pop()}'
        with open(imgdir, 'wb+') as f:
            # for chunk in content.read().chunks():
            #     f.write(chunk)
            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(result)
        return filename  # 返回地址路径字符串

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

    def exists(self, name):  # FastDfs可以自己解决文件重名,
        '''
        :param name:文件名
        :return: False
        '''
        return False   # 如果给定名称所引用的文件已经存在于存储系统中,则返回 True;如果该名称可用于新文件,则返回 False。

    def delete(self, name):
        try:
            conf_path = get_tracker_conf(self.client_conf)
            client = Fdfs_client(conf_path)
            if not isinstance(name, bytes):
                name = bytes(name, encoding="utf-8")
            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()  # 返回删除的图片路径字符串

client.conf

connect_timeout = 30
network_timeout  = 60
tracker_server = 121.5.109.75:22122 
http.tracker_server_port = 8088

test.py

# from fdfs_client.client import Fdfs_client
# from fdfs_client.client import get_tracker_conf, Fdfs_client

# tracker_conf_path = get_tracker_conf("D:/MyServer/o_server/o_project/utils/fastDFS/client.conf")   # tracker配置文件的地址
# client = Fdfs_client(tracker_conf_path)
# 上传图片
# ret = client.upload_by_filename(r"D:/MyServer/o_server/o_project/test_1670059698725128.5.html")

# byte数据
# ret = client.upload_by_buffer(r"D:/MyServer/o_server/o_project/test_1670059698725128.5.html")
# print(ret)

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

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路径

MEDIA_ROOT = 'http://121.5.109.75:8088/'

视图

# 日记序列化器
class NoteDeserialization(serializers.ModelSerializer):
    class Meta:
        model = Diary
        fields = '__all__'
        depth = 1

    # 序列化后的钩子
    def to_representation(self, instance):
        data = super().to_representation(instance)

        data['status'] = [status[1]
                          for status in Diary.status_choice if data['status'] == status[0]][0]
        if data['image']:
            data['image'] = str(data['image']).split('/')[-1]
        return data


# 日记反序列化器
class NoteSerializers(serializers.ModelSerializer):
    upload_image = serializers.ListField(
        write_only=True, label="上传图片列表", required=False)

    class Meta:
        model = Diary
        fields = '__all__'

    # 自定义验证
    def validate(self, attrs):
        # 图片上传验证处理
        redis_conn = get_redis_connection('default')
        request = self.context.get('request')
        # 去除content保留存在的图片
        if images_list := attrs.get('upload_image'):
            content = attrs.get('content')
            for i in images_list:
                if re.search(i, content, re.S):
                    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()
            for image in delete_image_list:
                fdfs.delete(image)
            redis_conn.delete("upload_image_%s" % request.user.id)

        # 解析ip
        attrs['ip'] = get_client_ip(request)
        attrs['ip_info'] = analysis_ip_info(attrs['ip'])
        attrs.pop('upload_image', '')
        return attrs

    # 处理添加修改完成后的tag多对多序列化
    def to_representation(self, instance):
        data = super().to_representation(instance)
        validated_data = {}

        try:
            validated_data = self.validated_data
        except AssertionError as e:
            print(e)

        if tag := validated_data.get('tag'):
            data['tag'] = toDict(tag)
        else:
            data['tag'] = list(Tag.objects.filter(id__in=data.get('tag')).values())

        if author := validated_data.get('author'):
            data['author'] = toDict(author)
        else:
            data['author'] = list(User.objects.filter(id=data.get('author')).values())
        return data

    def update(self, instance, validated_data):
        return super().update(instance, validated_data)

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

    def get_serializer_class(self):
        if request := getattr(self, 'request'):
            if request.method == 'GET':
                return NoteDeserialization
        return NoteSerializers

    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(i)
        instance.delete()

    # /note/notemodel/get_random_diary/ 随机3个
    @action(methods=['get'], detail=False)
    def get_random_diary(self, request):
        queryset = self.get_queryset()
        random_list = random.sample(range(0, len(queryset)), 3)
        hot_queryset = [queryset[i] for i in random_list]
        ser = self.get_serializer(instance=hot_queryset, many=True)
        return Response(ser.data, status=200)


    def retrieve(self, request, *args, **kwargs):
        instance = self.get_object()
        serializer = self.get_serializer(instance)
        data = serializer.data
        # 添加点赞数量
        instance.click = F('click') + 1
        instance.save() 
        return Response(data)


# 删除日记内容中的图视图
class NoteImageUpload(APIView):
    def post(self, request):
        data = request.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.__str__,
            }

        return Response(data=message, content_type="application/json")