Files
crawler-plugin/backend/ali_oss.py
T
123buzhidao56 4775748447 modified: backend/ali_oss.py
modified:   backend/config.py
2026-07-31 14:36:08 +08:00

125 lines
4.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""对象存储上传工具(MinIO,S3 协议兼容)
原实现基于阿里云 OSS SDKalibabacloud_oss_v2),现改为 boto3 对接 MinIO。
对外函数名与返回值保持不变,业务代码无需修改。
"""
import base64
import io
import mimetypes
import re
import threading
import time
import boto3
import requests
from botocore.config import Config
from config import (
region,
endpoint,
bucket,
file_url_pre,
bucket_path,
accessKeyId,
accessKeySecret,
)
_client = None
_client_lock = threading.Lock()
def get_client():
"""获取(懒加载)S3 客户端,MinIO 必须使用 path-style 寻址"""
global _client
if _client is None:
with _client_lock:
if _client is None:
_client = boto3.client(
"s3",
endpoint_url=endpoint,
aws_access_key_id=accessKeyId,
aws_secret_access_key=accessKeySecret,
region_name=region,
config=Config(
signature_version="s3v4",
s3={"addressing_style": "path"}, # MinIO 必须
retries={"max_attempts": 3, "mode": "standard"},
),
)
return _client
def _guess_content_type(key: str) -> str:
content_type, _ = mimetypes.guess_type(key)
return content_type or "application/octet-stream"
def get_presigned_url(key: str, expires: int = 7 * 24 * 3600) -> str:
"""生成临时访问链接(存储桶未开放匿名读时使用),默认有效 7 天"""
return get_client().generate_presigned_url(
"get_object",
Params={"Bucket": bucket, "Key": key.lstrip("/")},
ExpiresIn=expires,
)
def upload_file(file_content: bytes, key: str):
"""上传字节内容到 MinIO,返回可访问链接"""
key = key.lstrip("/")
client = get_client()
client.put_object(
Bucket=bucket, # 存储桶名称
Key=key, # 对象名称
Body=io.BytesIO(file_content) if isinstance(file_content, (bytes, bytearray)) else file_content,
ContentType=_guess_content_type(key),
)
return file_url_pre + key
def upload_data_url(data_url: str, prefix: str = "history", key_hint: str = "") -> str:
"""
将 base64 data URL 上传到对象存储,返回图片链接
data_url: data:image/png;base64,xxxx 或 data:image/jpeg;base64,xxxx
prefix: 对象 key 前缀
key_hint: 可选后缀避免重名,如 "_0", "_1"
"""
match = re.match(r'data:image/(\w+);base64,(.+)', data_url)
if not match:
raise ValueError('无效的 data URL 格式')
ext = 'png' if match.group(1).lower() in ('png', 'webp') else 'jpg'
file_content = base64.b64decode(match.group(2))
ts = int(time.time() * 1000)
key = f"{bucket_path}{prefix}/{ts}{key_hint}.{ext}"
return upload_file(file_content, key)
def upload_data_urls(data_urls: list, prefix: str = "history") -> list:
"""批量上传 base64 图片到对象存储,返回图片链接列表"""
urls = []
ts = int(time.time() * 1000)
for i, data_url in enumerate(data_urls or []):
if not data_url or not isinstance(data_url, str):
continue
if not data_url.startswith("http"):
match = re.match(r'data:image/(\w+);base64,(.+)', data_url)
if not match:
continue
ext = 'png' if match.group(1).lower() in ('png', 'webp') else 'jpg'
file_content = base64.b64decode(match.group(2))
else:
file_content = requests.get(data_url).content
ext = "png"
key = f"{bucket_path}{prefix}/{ts}_{i}.{ext}"
urls.append(upload_file(file_content, key))
return urls
# 脚本入口,当文件被直接运行时调用main函数
if __name__ == "__main__":
with open("D:\\pack\\nanri\\main.dist\\SHUFU.zip", "rb") as f:
file_content = f.read()
res = upload_file(file_content, key=bucket_path + "versions/1.0.46.zip")
print(res)