前言

最近遇到一个场景需要把大量的资源文件存储到 OSS 里,这里选的是腾讯的 COS 对象存储

(话说我接下来想搞的 SnapMix 项目也是需要大量存储的,我打算搭个 MinIO 把 24T 的服务器利用起来~)

为啥腾讯不搞个兼容 Amazon S3 协议的啊…… 官方的 SDK 和文档都奇奇怪怪的,感觉国内的厂商都不怎么重视文档、SDK这些,开发体验很差(特别点名微信小程序)

因为腾讯的 COS 不在 django-storages 的支持中,所以本文就没有使用这个库了,而是自己封装了一个 Storage,其实 Django 里要自定义一个 Storage 是很简单的。

OK,我在参考了一些互联网资源(以及官方文档、Github)之后,把腾讯的这个 COS 集成到 DjangoStarter 里了,不得不说 Django 这套东西还是好用,只要把 DEFAULT_FILE_STORAGE 存储后端切换到 COS ,就能实现 FileField, ImageField 这些全都自动通过 OSS 去存储和使用。

为了方便管理文件,我还用上了 django-filer 这个也算是方便,开箱即用,不过中文的 locale 有点问题,默认安装之后只能显示英文,如果需要中文得自己 fork 之后改一下(重命名 locale 目录)

本文的代码都是在 DjangoStarter 框架的基础上进行修改,在普通的 Django 项目中使用也没有问题,只是需要根据实际情况做一些修改(文件路径不同)

配置

编辑 src/config/settings/components/tencent_cos.py 文件

DEFAULT_FILE_STORAGE = "django_starter.contrib.storages.backends.TencentCOSStorage"

TENCENTCOS_STORAGE = {
# 存储桶名称,必填
"BUCKET": "", # 存储桶文件根路径,选填,默认 '/'
"ROOT_PATH": "/",
# 上传文件时最大缓冲区大小(单位 MB),选填,默认 100
"UPLOAD_MAX_BUFFER_SIZE": 100,
# 上传文件时分块大小(单位 MB),选填,默认 10
"UPLOAD_PART_SIZE": 10,
# 上传并发上传时最大线程数,选填,默认 5
"UPLOAD_MAX_THREAD": 5, # 腾讯云存储 Python SDK 的配置参数,详细说明请参考腾讯云官方文档。
# 注意:CONFIG中字段的大小写请与python-sdk中CosConfig的构造参数保持一致
"CONFIG": {
"Region": "ap-guangzhou",
"SecretId": "",
"SecretKey": "",
}
}

这个配置里注释都很清楚了,根据实际情况填写 bucket、id、key 等配置即可。

Storage 实现

前面有说到我把 COS 集成到 DjangoStarter 里了,所以放到了 src/django_starter/contrib 下面

安装依赖

这里需要用到腾讯提供的 Python SDK,请先安装

pdm add cos-python-sdk-v5

编写代码

编辑 src/django_starter/contrib/storages/backends/cos.py 文件。

from io import BytesIO
from shutil import copyfileobj
from tempfile import SpooledTemporaryFile from datetime import datetime, timezone
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.core.files.storage import Storage
from django.utils.deconstruct import deconstructible
from qcloud_cos import CosConfig, CosS3Client
from qcloud_cos.cos_exception import CosServiceError
from importlib import metadata
import os.path from django.core.files.base import File class TencentCOSFile(File):
def __init__(self, name, storage, file=None):
super().__init__(file, name)
self.name = name
self._storage = storage
self._file = None @property
def file(self):
if self._file is None:
self._file = SpooledTemporaryFile()
response = self._storage.client.get_object(
Bucket=self._storage.bucket,
Key=self.name,
)
raw_stream = response["Body"].get_raw_stream()
with BytesIO(raw_stream.data) as file_content:
copyfileobj(file_content, self._file)
self._file.seek(0)
return self._file @file.setter
def file(self, value):
self._file = value @deconstructible
class TencentCOSStorage(Storage):
"""Tencent Cloud Object Storage class for Django pluggable storage system.""" def path(self, name):
return super(TencentCOSStorage, self).path(name) def __init__(self, bucket=None, root_path=None, config=None):
setting = getattr(settings, "TENCENTCOS_STORAGE", {})
self.bucket = bucket or setting.get("BUCKET", None)
if self.bucket is None:
raise ImproperlyConfigured("Must configure bucket.") self.root_path = root_path or setting.get("ROOT_PATH", "/")
if not self.root_path.endswith("/"):
self.root_path += "/" self.upload_max_buffer_size = setting.get("UPLOAD_MAX_BUFFER_SIZE", None)
self.upload_part_size = setting.get("UPLOAD_PART_SIZE", None)
self.upload_max_thread = setting.get("UPLOAD_MAX_THREAD", None) config_kwargs = config or setting.get("CONFIG", {})
package_name = "cos-python-sdk-v5" # 替换为您要查询的包的名称
version = metadata.version(package_name)
config_kwargs["UA"] = "tencentcloud-django-plugin-cos/0.0.1;cos-python-sdk-v5/" + version
required = ["Region", "SecretId", "SecretKey"]
for key in required:
if key not in config_kwargs:
raise ImproperlyConfigured("{key} is required.".format(key=key)) config = CosConfig(**config_kwargs)
self.client = CosS3Client(config) def _full_path(self, name):
if name == "/":
name = ""
# p = safe_join(self.root_path, name).replace("\\", "/")
# 乱起名的问题(自动在路径前加上 D:\ 之类的)终于解决了
# 腾讯哪个人才想到用 Django 内部的 safe_join 方法代替 os.path.join 的?告诉我,我绝对不打死他!!!
p = os.path.join(self.root_path, name).replace("\\", "/")
return p def delete(self, name):
self.client.delete_object(Bucket=self.bucket, Key=self._full_path(name)) def exists(self, name):
try:
return bool(
self.client.head_object(Bucket=self.bucket, Key=self._full_path(name))
)
except CosServiceError as e:
if e.get_status_code() == 404 and e.get_error_code() == "NoSuchResource":
return False
raise def listdir(self, path):
directories, files = [], []
full_path = self._full_path(path) if full_path == "/":
full_path = "" contents = []
marker = ""
while True:
# return max 1000 objects every call
response = self.client.list_objects(
Bucket=self.bucket, Prefix=full_path.lstrip("/"), Marker=marker
)
contents.extend(response["Contents"])
if response["IsTruncated"] == "false":
break
marker = response["NextMarker"] for entry in contents:
if entry["Key"].endswith("/"):
directories.append(entry["Key"])
else:
files.append(entry["Key"])
# directories includes path itself
return directories, files def size(self, name):
head = self.client.head_object(Bucket=self.bucket, Key=self._full_path(name))
return head["Content-Length"] def get_modified_time(self, name):
head = self.client.head_object(Bucket=self.bucket, Key=self._full_path(name))
last_modified = head["Last-Modified"]
dt = datetime.strptime(last_modified, "%a, %d %b %Y %H:%M:%S %Z")
dt = dt.replace(tzinfo=timezone.utc)
if settings.USE_TZ:
return dt
# convert to local time
return datetime.fromtimestamp(dt.timestamp()) def get_accessed_time(self, name):
# Not implemented
return super().get_accessed_time(name) def get_created_time(self, name):
# Not implemented
return super().get_accessed_time(name) def url(self, name):
return self.client.get_conf().uri(
bucket=self.bucket, path=self._full_path(name)
) def _open(self, name, mode="rb"):
tencent_cos_file = TencentCOSFile(self._full_path(name), self)
return tencent_cos_file.file def _save(self, name, content):
upload_kwargs = {}
if self.upload_max_buffer_size is not None:
upload_kwargs["MaxBufferSize"] = self.upload_max_buffer_size
if self.upload_part_size is not None:
upload_kwargs["PartSize"] = self.upload_part_size
if self.upload_max_thread is not None:
upload_kwargs["MAXThread"] = self.upload_max_thread self.client.upload_file_from_buffer(
self.bucket, self._full_path(name), content, **upload_kwargs
)
return os.path.relpath(name, self.root_path) def get_available_name(self, name, max_length=None):
name = self._full_path(name)
return super().get_available_name(name, max_length)

一些絮絮叨叨:

  • 这个代码是根据腾讯github上的代码修改来的,实话说写的乱七八糟,不堪入目,不过想到这也都是腾讯打工人应付工作写出来的东西,也就能理解了……
  • Class 前面的 @deconstructible 装饰器是 Django 内置的,用于确保在迁移时类可以被正确序列化
  • 原版的代码运行起来有很多奇奇怪怪的问题,后面仔细分析了一下代码才发现,腾讯的人才好端端的 os.path.join 不用,非要去用 Django 内部的 safe_join 方法,这个还是私有的,不然随便调用的… 真的逆天

参考资料

Django集成腾讯COS对象存储的更多相关文章

  1. 腾讯云COS对象存储

    一.腾讯云COS 腾讯云对象存储 COS 是一种存储海量数据的分布式存储服务.COS 提供了多种对象的存储类型:标准存储.低频存储.归档存储. 二.为什么要使用TA 便宜: 个人用户有6个月的免费使用 ...

  2. Go操作腾讯云COS对象存储的简单使用案例

    准备环境 安装Go环境 Golang:用于下载和安装 Go 编译运行环境,请前往 Golang 官网进行下载 安装SDK go get -u github.com/tencentyun/cos-go- ...

  3. 腾讯云COS对象存储 Web 端直传实践(JAVA实现)

    使用 腾讯云COS对象存储做第三方存储云服务,把一些文件都放在上面,这里主要有三中实现方式:第一种就是在控制台去设置好,直接上传文件.第二种就是走服务端,上传文件,就是说,上传文件是从服务端去上传上去 ...

  4. Docsify+腾讯云对象存储 COS,一键搭建云上静态博客

    最近一直在想如何利用 COS 简化静态博客的搭建过程.搜了很多的静态博客搭建过程,发现大部分的静态博客都要通过编译才能生成静态页面.功夫不负有心人,终于让我找到了一个超简洁博客的搭建方法. 效果预览 ...

  5. 腾讯云COS对象存储占据数据容灾C位

    说到公有云容灾,大家首先想到的是云上数据备份. 然而,随着企业核心业务逐渐从线下迁移到云上,客户提出了更高的要求.如何确保云上业务的高可用.数据的高可靠,这对云厂商提出了新的挑战. 腾讯云作为全球领先 ...

  6. 腾讯云对象存储 COS搭建个人网站

    腾讯云对象存储 COS搭建个人网站,简单易操作,方便快捷.   只需要将你的网站资源上传即可,然后设置上你的自定义 CDN 加速域名,一个个人网站就上线啦!当然,你也可以不用设置自定义 CDN 加速域 ...

  7. 微信小程序基于腾讯云对象存储的图片上传

    在使用腾讯云对象存储之前,公司一直使用的是传统的FTP的上传模式,而随着用户量的不断增加,FTP所暴露出来的问题也越来越多,1.传输效率低,上传速度慢.2.时常有上传其他文件来攻击服务器,安全上得不到 ...

  8. php 腾讯云 对象存储V5版本 获取返回的上传文件的链接方法

    腾讯云 对象存储V5版本 文档地址:https://github.com/tencentyun/cos-php-sdk-v5 调用简单文件上传方法: 返回数据如下 Array ( [data:prot ...

  9. Laravel项目使用腾讯云对象存储上传图片(cos-php-sdk-v5版本)

    为了加快网站访问速度.降低网站负载,现在越来越多的网站选择把图片等静态文件放在云上,这里介绍一下腾讯云对象存储在Laravel项目中的使用 1.申请腾讯云对象存储.创建Bucket.获取APPID等参 ...

  10. 腾讯云 COS 对象存储使用

    目前使用腾讯云的对象存储cos服务,将本地的文件同步到cos中,看了腾讯云的用户文档,发现使用COS Migration 工具还是挺适合的. 原因 因为服务器已经安装有java环境,而cos的几个用户 ...

随机推荐

  1. 【FAQ】HarmonyOS SDK 闭源开放能力 —Asset Store Kit

    1.问题描述 使用关键资产API需要配置SystemCapability.Security.Asset,但不知道syscap.json文件应该配置在哪里,文档也没找到. 解决方案 新增关键资产等API ...

  2. 在Linux驱动中使用input子系统

    在Linux驱动中使用输入子系统 参考: https://www.cnblogs.com/lifexy/p/7553861.html https://www.cnblogs.com/linux-37g ...

  3. Linux系统获取开发板的文件系统并打包成img文件

    应用情形: 在实际的开发中,由于原系统包含的功能有限,而根据项目的需要,安装了相应的库及运行项目程序所创建的各种文件,和所做 的各种配置,想将调试好的系统打包发布,进行批量生产,就可参考本文提供的方法 ...

  4. vba--数组

    Sub shishi() Range("e2") = Split(Range("e1"), "-")(0) '用短横线分隔后取第1个值 En ...

  5. 树莓派4B-Python-控制超声波模块

    树莓派4B-Python-控制超声波模块 超声波模块: 超声波模块为常用的HC-SR04型号,有四个引脚,分别为Vcc.Trig(控制端).Echo(接收端).GND,使用起来也比较简单.在树莓派最新 ...

  6. CF2B

    非常恶心的dp题,测试数据也恶心,坑多,特别是0的坑 #include <iostream> #include <utility> #include <string> ...

  7. 全新发布!桌面端效率工具RunFlow

    RunFlow是一款跨平台的生产力工具,可以启动应用程序和搜索文件等,类似于Windows平台的Wox和PowerToys,同样也类似于Mac平台的Alfred和Raycast.但我们并不与这些工具相 ...

  8. 浅谈地址,section,vstart

    地址:地址只是数字,描述各种符号在源程序中的位置,它是源代码文件中各符号偏移文件开头的距离.由于指令和变量所占内存大小不同,故他们的偏移量参差不齐.由编译器给各符号编址,编译器给程序中各符号(变量名和 ...

  9. 基于MindSpore实现BERT对话情绪识别

    本文分享自华为云社区<[昇思25天学习打卡营打卡指南-第二十四天]基于 MindSpore 实现 BERT 对话情绪识别>,作者:JeffDing. 模型简介 BERT全称是来自变换器的双 ...

  10. Django配置为连接到Microsoft SQL Server

    可以将Django配置为连接到Microsoft SQL Server 2019.为此,你需要更改数据库设置中的一些配置选项.首先,确保你已经安装了 django 和 pyodbc 这两个库:   p ...