MinIO on Django: Quick Intro and Popular Python Bindings

Table of Contents

Only for minio==6.0.2 and django-minio-storage==0.3.8.

好吧,规定这个是因为 django-minio-storage 依赖里不写 minio 版本,而且 minio 在新版本中改动了 API。

Fuck you Amazon

但是其实要感谢 Amazon 的那个鬼签名机制我没自己写出来,否则我就不会想到去调库了(其实是俺的傻逼行为

使用 django-minio-storage

进行配置。

INSTALLED_APPS = [
    ...
    'minio_storage',
    ...
]

# Use Minio as file storage backend

STATIC_ROOT = './static_files/'
DEFAULT_FILE_STORAGE = "minio_storage.storage.MinioMediaStorage"
MINIO_STORAGE_ENDPOINT = environ['MINIO_ADDRESS']
MINIO_STORAGE_ACCESS_KEY = environ['MINIO_ACCESS_KEY']
MINIO_STORAGE_SECRET_KEY = environ['MINIO_SECRET_KEY']
# 这个选项控制手动连接 MinIO 的时候使用的 HTTP Scheme
MINIO_STORAGE_USE_HTTPS = False
MINIO_STORAGE_MEDIA_OBJECT_METADATA = {"Cache-Control": "max-age=1000"}
MINIO_STORAGE_MEDIA_URL = environ['MINIO_ADDRESS']
MINIO_STORAGE_MEDIA_BUCKET_NAME = 'media-bucket'
# 这个选项控制 Django 如何使用存储:从 Django 中写文件时是否会自动创建对应的存储桶
MINIO_STORAGE_AUTO_CREATE_MEDIA_BUCKET = True
MINIO_STORAGE_STATIC_BUCKET_NAME = 'static-bucket'
MINIO_STORAGE_AUTO_CREATE_STATIC_BUCKET = True

在你的 Django 应用中测试:

from django.utils import timezone

from rest_framework.permissions import AllowAny
from rest_framework.response import Response
from rest_framework.views import APIView

from django.core.files.base import ContentFile
from django.core.files.storage import default_storage
from django.core.files.storage import Storage
from django.core.files import File

class StatusView(APIView):
# Omit unrelated codes
    def minio_connected(self):
        try:
            testfile_content = b'minio here'
            path = default_storage.save('here/alive', ContentFile(testfile_content))
            testfile_content_read = default_storage.open(path).read()
            default_storage.delete(path)
            return testfile_content == testfile_content_read
        except Exception:
            return False

直接使用 minio

生成 PUT 文件需要的 URL

import json
import datetime

from minio import Minio
from minio.error import ResponseError
from datetime import timedelta
from os import environ

from .settings import MINIO_STORAGE_MEDIA_BUCKET_NAME as DEFAULT_BUCKET
from .settings import MINIO_STORAGE_USE_HTTPS

import random

# minio client to use
# TODO: when deployed and access through remote machine,
#       minio remote address should be changed to HTTP_HOST
#       with corresponding information.
local_minio_client = Minio(
    environ['MINIO_ADDRESS'],
    access_key=environ['MINIO_ACCESS_KEY'],
    secret_key=environ['MINIO_SECRET_KEY'],
    secure=MINIO_STORAGE_USE_HTTPS,
)
# default timeout = 15 min
DEFAULT_TIMEOUT = timedelta(minutes=15)

# PREFIX DIR

PREFIX = "******"

file_token = f"{PREFIX}/{file_display_name}"

put_url = local_minio_client.presigned_url("PUT",
                                            DEFAULT_BUCKET,
                                            file_token,
                                            expires=DEFAULT_TIMEOUT)

print(put_url)

生成 GET 文件所需要的 URL

# imports and minio client are omitted

file_token = "******"
result_url = local_minio_client.presigned_url("GET",
                                            DEFAULT_BUCKET,
                                            file_token,
                                            expires=DEFAULT_FILE_URL_TIMEOUT)

print(result_url)

生成预定义的,限制上传文件大小的 POST URL;上传时需要自定义 form data

# imports and minio client are omitted
from minio import PostPolicy

# MAX SIZE (MB)
MAX_SIZE = 4 * 1024 * 1024

post_policy = PostPolicy()
# set bucket name location for uploads.
post_policy.set_bucket_name(DEFAULT_BUCKET)
# set key prefix for all incoming uploads.
file_token = "******"
post_policy.set_key_startswith(file_token)
# set content length for incoming uploads.
post_policy.set_content_length_range(0, MAX_SIZE)
# set expiry.
expires_date = datetime.utcnow() + DEFAULT_FILE_URL_TIMEOUT
post_policy.set_expires(expires_date)


url, signed_form_data = local_minio_client.presigned_post_policy(post_policy)

print(url)
print(signed_form_data)

上传时:

  • Content-Type: multipart/form-data; boundary=<calculated when request is sent>
  • 将上一步的 signed_form_data 中的内容放入请求体。
  • 将需要上传的文件放入请求体中的 file 栏。

image

(在启动时)修改存储桶策略,使得某路径下的文件能被公开访问

# imports and minio client are omitted

# predefined anonymous read-only avatar policy
predefined_avatar_policy = {
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {"AWS": "*"},
            "Action": "s3:GetObject",
            "Resource": f"arn:aws:s3:::{DEFAULT_BUCKET}/{PREFIX}/*",
        },
    ],
}

local_minio_client.set_bucket_policy(DEFAULT_BUCKET, json.dumps(predefined_avatar_policy))
Nemo Xiong avatar
Nemo Xiong
我永远喜欢妃爱
comments powered by Disqus