django restframework+fastdfs 或者Minio实现django自定义存储,上传文件
fastdfs client库windows调用报错报错坑1:
ModuleNotFoundError: No module named 'mutagen._compat'
解决办法:找到utils.py文件修复导入报问题如下:
from mutagen._senf._compat import StringIO
坑2:进行settings.py client.conf配置路径settings.FASTDFS_CLIENT_CONF引入报错,实际上我已经配置了在settings.py很狗血:
Requested setting , but settings are not configured. You must either define the environment variable DJANGO_SETTINGS_MODULE or call settings.configure() before accessing settings.
解决办法:
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mall.settings')
坑三: fastdfs 使用方法client.upload_by_file()报错Error: 'sendfile' system call only available on linux.此方法只能在linux系统使用windows使用错误
client=Fdfs_client(conf_path=conf)
client.upload_by_file()
解决办法:使用client.upload_by_buffer(data.read())方法调试:
1 2 3 4 5 6 7 8 9 | conf = settings.FASTDFS_CLIENT_CONF client = Fdfs_client(conf_path = conf) with open ( 'hi.png' , 'rb' )as data: ret = client.upload_by_buffer(data.read()) print (ret) if ret.get( "Status" )! = "Upload successed." : raise Exception( 'fastdfs upload failed !!!' ) file_name = ret.get( "Remote file_id" ) print (file_name) |
fastdfs 自定义存储实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | import os os.environ.setdefault( 'DJANGO_SETTINGS_MODULE' , 'mall.settings' ) 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 @deconstructible class FastDfsStorage(Storage): """ org-doc: https://www.pianshen.com/article/1299301803/ :param server_url: fast-dfs服务器ip:port :param: client_conf """ def __init__( self , server_url = None , client_conf = None ): if not server_url: self .server_url = server_url self .server_url = settings.FASTDFS_URL if not client_conf: self .client_conf = client_conf self .client_conf = settings.FASTDFS_CLIENT_CONF def _open( self , name, mode = 'rb' ): """ for read file : Retrieve the specified file from storage.""" return super (). open (name, 'rb' ) def _save( self , name, content, max_length = None ): client = Fdfs_client(conf_path = self .client_conf) extend_name = name.split( '.' )[ - 1 ] ret = client.upload_by_buffer(content.read(),file_ext_name = extend_name) if ret.get( "Status" )! = "Upload successed." : raise Exception( 'fastdfs upload failed !!!' ) file_name = ret.get( "Remote file_id" ).replace( '\\',' / ') print ( '测试获取文件路径' ,file_name) return file_name def url( self , name): path = self .server_url + '/' + name return path def exists( self , name): """ means filename always is available is new filename, fast-dfs always storage one same file """ return False |
接下来,使用drf编写模型类,序列化器以及view视图测试代码:
model如下:
1 2 3 4 5 6 7 | class FileModels(models.Model): file_id = models.AutoField(primary_key = True ,max_length = 200 ) file = models.FileField() class Meta: db_table = 'tbl_file' verbose_name = '文件测试' |
序列化器如下:
1 2 3 4 5 6 7 8 | from rest_framework import serializers from .models import FileModels class SerialFiles(serializers.ModelSerializer): class Meta: model = FileModels fields = "__all__" |
视图:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | class FileView(APIView): def post( self , request, * args, * * kwargs): print (request.POST) print (request.data) ser = SerialFiles(data = request.data, many = False ) if ser.is_valid(): ser.save() return Response(data = ser.data, status = status.HTTP_200_OK) else : return Response(data = ser.errors, status = status.HTTP_404_NOT_FOUND) def get( self ,request, * args, * * kwargs): inst = FileModels.objects. all () ser = SerialFiles(instance = inst,many = True ) return Response(data = ser.data) |
注意事项起初,我用postman form-data 上传一直报参数f不存在,匪夷所思,怀疑是没有指明使用muti-formdata导致的,于是使用html写个简单的上传表单提交文件,果然
最后点击上传可以看到页面返回:
最后送上我的表单html上传文件基于 enctype=
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <!DOCTYPE html> <html lang = "en" > <head> <meta charset = "UTF-8" > <title>Title< / title> < / head> <body> <h1><div align = center style = "color:green" display = "block" > hello world < / div><p> this is a first text for test web page and we will sart from hello < / p>< / h1> <form action = "http://127.0.0.1:8000/file/" method = "post" enctype = "multipart/form-data" > < input type = "file" name = 'file' > <! - - input type = "file" name = 'file22' - - > <! - - input type = "username" name = 'username' - - > <! - - input type = "password" name = 'password' - - > <! - - input type = "file" accept = ".xlsx" - - > < input type = "submit" value = "提交" > < / form> < / body> © codemao < / html> |
结语:其实自定义文件储存,在企业使用是非常多的oss,fastdfs,minio ,qiniu云,腾讯cos等等,django默认自带FilesystemStorage,实现一般存在django项目所在服务器时间久了影响磁盘空间以及性能,企业很少这样做实际开发
关于minio自定义存储可以这样:
可能遇到的坑:
第一:settings 定义MINIO_CONF找不到,我用的django2.2,好像必须要大写变量在settings
事实上我已经导入了
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mall.settings')
坑2:save方法返回name是文件名,不然找不到,其次我在上传minio是object_name修改了用了uuid加随机字符串避免多用户使用上传文件名相同覆盖问题
1 2 3 4 5 6 7 8 9 10 11 12 13 | def _save( self , name, content, max_length = None ): if not self .minio_client.bucket_exists( self .bucket_name): self .minio_client.make_bucket( self .bucket_name) try : self .minio_client.get_bucket_policy( self .bucket_name) except NoSuchBucketPolicy: self .set_bucket_policy_public( self .bucket_name) (etag, vid) = self .minio_client.put_object(bucket_name = self .bucket_name,object_name = name,data = content,length = content.size) if etag: print ( "save return minio is {}" . format ( '/{}/{}' . format ( self .bucket_name,name))) # 这里返回name,就是url方法的入参name return name |
送上settings配置:
DEFAULT_FILE_STORAGE='utils.minio_fdfs.minio_storage.MinioStorage'
MINIO_CONF = {
'endpoint': '192.168.110.151:9000',
'access_key': 'admin',
'secret_key': 'admin123456',
'secure': False
}
BUCKET_NAME='mybucket'
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | import os os.environ.setdefault( 'DJANGO_SETTINGS_MODULE' , 'mall.settings' ) from django.conf import settings from django.core.files.storage import Storage from django.utils.deconstruct import deconstructible from minio import Minio, ResponseError from minio.error import NoSuchBucketPolicy import json import uuid import random import string @deconstructible class MinioStorage(Storage): """ python docs: https://docs.min.io/docs/python-client-api-reference.html @:param fp : fileobject @:param object_name is the object name which will save to minio bucket @:param bucket_name the bucket name of minio . """ def __init__( self , bucket_name = None , object_name = None ): if not settings.MINIO_CONF: raise ValueError( 'required MINIO_CONF config in django.settings: format is:\n{}' . format ( { 'endpoint' : '192.168.110.151:9000' , 'access_key' : 'username' , 'secret_key' : 'password' , 'secure' : False , } )) self .minio_conf = settings.MINIO_CONF if not settings.BUCKET_NAME: self .bucket_name = bucket_name self .bucket_name = settings.BUCKET_NAME self .object_name = object_name self .endpoint_minio = settings.MINIO_CONF.get( 'endpoint' , None ) self .minio_client = Minio( * * self .minio_conf) def _open( self , name, mode = 'rb' ): """ for read file : Retrieve the specified file from storage.""" return super (). open (name, 'rb' ) def _save( self , name, content, max_length = None ): filename, extendname = name.split( '.' ) salt = ''.join(random.sample(string.ascii_letters + string.digits, 7 )) name = name.replace(filename, uuid.uuid1(). hex + salt + '_' + filename) if not self .minio_client.bucket_exists( self .bucket_name): self .minio_client.make_bucket( self .bucket_name) try : self .minio_client.get_bucket_policy( self .bucket_name) except NoSuchBucketPolicy: self .set_bucket_policy_public( self .bucket_name) (etag, vid) = self .minio_client.put_object(bucket_name = self .bucket_name,object_name = name,data = content,length = content.size) if etag: # print("save return minio is {}".format('/{}/{}'.format(self.bucket_name,name))) # 这里返回name,就是url方法的入参name name_file = '{}/{}' . format ( self .bucket_name,name) return name_file def url( self , name): print ( "url minio return is {}" . format ( self .endpoint_minio + '/{}/{}' . format ( self .bucket_name,name))) return self .endpoint_minio + '/' + name def exists( self , name): """ means filename always is available is new filename, fast-dfs always storage one same file """ return False def set_bucket_policy_public( self ,bucket_name): """set file to public download by url: http:endpoint/bucket_name/object-name""" policy = { 'Version' : '2012-10-17' , 'Statement' : [{ 'Effect' : 'Allow' , 'Principal' : { 'AWS' : [ '*' ]}, 'Action' : [ 's3:GetBucketLocation' , 's3:ListBucket' ], 'Resource' : [ 'arn:aws:s3:::{}' . format (bucket_name)]}, { 'Effect' : 'Allow' , 'Principal' : { 'AWS' : [ '*' ]}, 'Action' : [ 's3:GetObject' ], 'Resource' : [ 'arn:aws:s3:::{}/*' . format (bucket_name)]}]} # set bucket to public download self .minio_client.set_bucket_policy(bucket_name = bucket_name, policy = json.dumps(policy)) def get_minio_object( self ,bucket_name,object_name): """ :return minio object of file,if get bytes by read()""" response = None try : response = self .minio_client.get_object(bucket_name = bucket_name,object_name = object_name) finally : response.close() response.release_conn() |
去minio查看上传情况:
下一篇docker 部署minio(https://www.cnblogs.com/SunshineKimi/p/13975581.html),对比了下fastdfs,我还是喜欢minio,真的很爽:
数据库存储可以带文件拓展名;第一条是minio,第二条fastdfs
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 字符编码:从基础到乱码解决
2019-11-15 读写json文件