大厂的对象存储都提供了完善的SDK,使用非常简单。django使用对象存储有两种场景,一种结合django的Storage存储类,图片经由模型上传至COS,然后由django端删除,本地不保留;另外一种自定义场景,比如结合django-mdeditor使用。本文介绍的是第二种,结合django-mdeditor编辑器,通过“添加图片”直接上传到腾讯COS中。
腾讯COSpython文档地址:https://cloud.tencent.com/document/product/436/12269
未来一段时间内将会一直研究django场景下使用腾讯COS的用法。腾讯COS提供了对象的上传、下载、查看对象列表、初始化客户端、创建桶、查看桶列表等众多接口,本文着重学习对象的上传。
实现整合的关键点
了解django-mdeditor的工作流程
django-mdeditor默认上传图片到本地,在选中图片后就会完成上传并返回相对路径到编辑器,编辑器和后端使用json通信。编辑器工作的js文件为site-packages/mdeditor/static/mdeditor/js/plugins/image-dialog/image-dialog.js
,python文件为site-packages/mdeditor/views.py
。大约流程为:
1、渲染图片上传form
该form由image-dialog.js
渲染,action的值为/mdeditor/uploads/?guid=1639898526591
,guid是渲染时的时间戳,暂时不清楚用来干啥,在view中看不到利用这个参数的利用。
2、编辑器的urls.py
uploads相关的请求均交由这个路由,并调用views.py中的UploadView接收图片,保存后返回路径给编辑器
3、views.py的UploadView
class UploadView(generic.View):
""" upload image file """
@method_decorator(csrf_exempt)
def dispatch(self, *args, **kwargs):
return super(UploadView, self).dispatch(*args, **kwargs)
def post(self, request, *args, **kwargs):
upload_image = request.FILES.get("editormd-image-file", None)
print('upload:', upload_image)
media_root = settings.MEDIA_ROOT
# image none check
if not upload_image:
return JsonResponse({
'success': 0,
'message': "未获取到要上传的图片",
'url': ""
})
# image format check
file_name_list = upload_image.name.split('.')
file_extension = file_name_list.pop(-1)
file_name = '.'.join(file_name_list)
if file_extension not in MDEDITOR_CONFIGS['upload_image_formats']:
return JsonResponse({
'success': 0,
'message': "上传图片格式错误,允许上传图片格式为:%s" % ','.join(
MDEDITOR_CONFIGS['upload_image_formats']),
'url': ""
})
# image floder check
file_path = os.path.join(media_root, MDEDITOR_CONFIGS['image_folder'])
print('上传路径:', file_path)
if not os.path.exists(file_path):
try:
os.makedirs(file_path)
except Exception as err:
return JsonResponse({
'success': 0,
'message': "上传失败:%s" % str(err),
'url': ""
})
# save image
file_full_name = '%s_%s.%s' % (file_name,
'{0:%Y%m%d%H%M%S%f}'.format(datetime.datetime.now()),
file_extension)
with open(os.path.join(file_path, file_full_name), 'wb+') as file:
for chunk in upload_image.chunks():
file.write(chunk)
return JsonResponse({'success': 1,
'message': "上传成功!",
'url': os.path.join(settings.MEDIA_URL,
MDEDITOR_CONFIGS['image_folder'],
file_full_name)})
编辑器部分相对容易理解,不过要修改编辑器界面的内容,就有点麻烦了,自定义工具栏可以通过设置来实现,但要调整其他就有点难度了,因为作者封装得太好了,压根看不懂js文件。
了解腾讯COS的接口和使用方法
腾讯COS文档齐全,用心读一读练一练就会了。COS工作的流程为:下载SDK,初始化COS客户端 => 配置好腾讯id、密码和存储桶id => 选择文件 => 携带必要参数(如上传后的图片命名和后缀,这个由参数key控制) => 读取图片的url。整个过程文档有详细的示例代码。
整合的思路
模仿编辑器的UploadView,写一个新的view,把保存图片到本地这部分改为上传到腾讯COS。
实现代码
在编辑器的view中写一个COS的类
该类实现客户端初始化、上传和读取url。不过,为了规范点,先在setting文件中配置一下COS的基础信息。
安装COS的SDK
pip install -U cos-python-sdk-v5
from qcloud_cos import CosConfig
from qcloud_cos import CosS3Client
import sys
import logging
TENCENT_COS = {
'secret_id': '腾讯云的id',
'secret_key': '腾讯云的密钥',
'bucket': '存储桶id',
'region': '桶的区域',
}
以上这些信息需要到腾讯云中能找得到。
COS类写在了编辑器的类中:
class TencentCOS:
def __init__(self):
self.secret_id = settings.TENCENT_COS['secret_id']
self.secret_key = settings.TENCENT_COS['secret_key']
self.bucket = settings.TENCENT_COS['bucket']
self.region = settings.TENCENT_COS['region']
self.token = None
self.scheme = 'https'
#初始化客户端
def client(self):
logging.basicConfig(level=logging.INFO, stream=sys.stdout)
config = CosConfig(
Region=self.region,
SecretId=self.secret_id,
SecretKey=self.secret_key,
Token=self.token,
Scheme=self.scheme
)
return CosS3Client(config) #返回一个初始化的客户端,后续操作都需要基于这个客户端实例
# 获取对象url
def get_obj_url(self, bucket, key):
return self.client().get_object_url(bucket, key)
#上传文件,返回上传后的url
def upload_cos(self, image, key):
with open(image, 'rb') as fp:
response = self.client().put_object(
Bucket = self.bucket,
Body = fp,
Key = key, #key是保存在cos时的名称
StorageClass = 'STANDARD',
EnableMD5 = False
)
return self.get_obj_url(self.bucket, key)
1、在upload_cos方法中,参数image是要上传图片的路径,使用绝对路径上传,参数key是上传到COS后保存的名称,需要文件名+后缀,本地的图片名称可能是中文,需要自己重新组织key,等于图片重命名,然后传参给upload_cos,COS将会使用key来保存这个图片。
2、get_obj_url方法用来获取上传后图片的url,有网友按照COS的链接规律自己组织了url,这也是可以的。
后半部分的代码,可浏览我个人博客http://www.nodepro.cn/nodes/node/5