首页 星云 工具 资源 星选 资讯 热门工具
:

PDF转图片 完全免费 小红书视频下载 无水印 抖音视频下载 无水印 数字星空

Django集成腾讯COS对象存储

编程知识
2024年08月23日 09:30

前言

最近遇到一个场景需要把大量的资源文件存储到 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 目录)

PS:另外说一下,为了使用简单,我使用 django-filer 实现了在 admin 里管理静态资源,但这样流量会经过服务器,更好的做法是在前端直接上传文件到 OSS 里

本文的代码都是在 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 方法,这个还是私有的,不然随便调用的… 真的逆天

参考资料

From:https://www.cnblogs.com/deali/p/18375466
本文地址: http://shuzixingkong.net/article/1366
0评论
提交 加载更多评论
其他文章 React项目接入代码编辑器aceEditor
不建议去查看aceEditor官方,最好去github查看 安装命令: npm install react-ace 引入包: import AceEditor from 'react-ace'; import 'ace-builds/src-noconflict/mode-s
React项目接入代码编辑器aceEditor React项目接入代码编辑器aceEditor React项目接入代码编辑器aceEditor
.NET 8 + Vue 3 极简 RABC 权限管理系统
前言 在日常工作中,几乎每家公司都需要一个后台管理系统来处理各种任务。为了帮助大家快速搭建这样一个系统,给大家介绍一个基于最新技术 .NET 8 和前端框架 Vue 3 实现的极简 RABC(基于角色的访问控制)权限管理系统。 该系统后端采用经过精心精简的 ABP框架,前端则使用了 vue-pure
.NET 8 + Vue 3 极简 RABC 权限管理系统 .NET 8 + Vue 3 极简 RABC 权限管理系统 .NET 8 + Vue 3 极简 RABC 权限管理系统
wiz 为知笔记服务器 docker 跨服务器迁移爬坑指北
本文主要是介绍 wiz 为知笔记服务器 docker 从旧服务器迁移到新服务器的步骤以及问题排查。 旧服务器升级 wiz docker 目的:保持和新服务器拉取的镜像版本一致。 官方只留了 wiz docker 镜像最新版,拉取不了旧版本镜像,所以先升级旧服务器上的 wiz docker。 升级方法
wiz 为知笔记服务器 docker 跨服务器迁移爬坑指北 wiz 为知笔记服务器 docker 跨服务器迁移爬坑指北 wiz 为知笔记服务器 docker 跨服务器迁移爬坑指北
SpringBoot 用的 spring-jcl 打印日志,与 LoggingSystem 有鸡毛关系?
开心一刻 现实中,我有一个异性游戏好友,昨天我心情不好,找她聊天 我:我们两个都好久没有坐下来好好聊天了 她:你不是有女朋友吗 我:人家不需要我这种穷人啊 她:难道我需要吗 前情回顾 从源码分析 SpringBoot 的 LoggingSystem → 它是如何绑定日志组件的 从源码的角度讲述了 S
SpringBoot 用的 spring-jcl 打印日志,与 LoggingSystem 有鸡毛关系? SpringBoot 用的 spring-jcl 打印日志,与 LoggingSystem 有鸡毛关系? SpringBoot 用的 spring-jcl 打印日志,与 LoggingSystem 有鸡毛关系?
HLK-RM60 + openwrt调试
1. 简介 HLK-RM60官网 https://www.hlktech.com/en/Goods-176.html 采用联发科SOC, MT7621/MT7905/MT7975 实际上采购的是MT7621, NOR Flash版本(注意:固件烧录时要选择NOR Flash对应的镜像,而不是NAND
HLK-RM60 + openwrt调试 HLK-RM60 + openwrt调试 HLK-RM60 + openwrt调试
Vue状态管理库Pinia详解
Pinia 是 Vue 的状态管理库,它提供了一种更简单、更不规范的 API 来管理应用的状态。Pinia 的设计哲学是简单性和易用性,它避免了 Vuex 中的许多复杂概念,如 mutations 和模块的嵌套结构,提供了一种更现代、更符合 Vue 3 Composition API 风格的状态管理
零基础学习人工智能—Python—Pytorch学习(八)
前言 本文介绍卷积神经网络的上半部分。 其实,学习还是需要老师的,因为我自己写文章的时候,就会想当然,比如下面的滑动窗口,我就会想当然的认为所有人都能理解,而实际上,我们在学习的过程中之所以卡顿的点多,就是因为学习资源中想当然的地方太多了。 概念 卷积神经网络,简称CNN, 即Convolution
零基础学习人工智能—Python—Pytorch学习(八) 零基础学习人工智能—Python—Pytorch学习(八)
使用Ollama本地离线体验SimpleRAG(手把手教程)
Ollama介绍 Ollama是一个开源项目,专注于开发和部署大语言模型,特别是像LLaMA这样的模型,用于生成高质量的文本和进行复杂的自然语言处理任务。Ollama的目标是让大语言模型的运行和使用变得更加容易和普及,而无需复杂的基础设施或深度的机器学习知识。 GitHub地址:https://gi
使用Ollama本地离线体验SimpleRAG(手把手教程) 使用Ollama本地离线体验SimpleRAG(手把手教程) 使用Ollama本地离线体验SimpleRAG(手把手教程)