feat(system): 添加文件存储引擎功能
- 新增文件存储引擎配置选项,支持本地、阿里云oss和腾讯云cos - 在系统配置中添加文件存储相关设置- 实现阿里云oss和腾讯云cos的文件上传功能 - 更新文件列表视图,支持不同存储引擎的文件上传和访问
This commit is contained in:
@@ -399,8 +399,12 @@ DICTIONARY_CONFIG = {}
|
|||||||
# ================================================= #
|
# ================================================= #
|
||||||
# 租户共享app
|
# 租户共享app
|
||||||
TENANT_SHARED_APPS = []
|
TENANT_SHARED_APPS = []
|
||||||
|
# 普通租户独有app
|
||||||
|
TENANT_EXCLUSIVE_APPS = []
|
||||||
# 插件 urlpatterns
|
# 插件 urlpatterns
|
||||||
PLUGINS_URL_PATTERNS = []
|
PLUGINS_URL_PATTERNS = []
|
||||||
|
# 所有模式有的
|
||||||
|
SHARED_APPS = []
|
||||||
# ********** 一键导入插件配置开始 **********
|
# ********** 一键导入插件配置开始 **********
|
||||||
# 例如:
|
# 例如:
|
||||||
# from dvadmin_upgrade_center.settings import * # 升级中心
|
# from dvadmin_upgrade_center.settings import * # 升级中心
|
||||||
|
|||||||
@@ -546,5 +546,50 @@
|
|||||||
"children": []
|
"children": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "文件存储引擎",
|
||||||
|
"value": "file_engine",
|
||||||
|
"type": 0,
|
||||||
|
"color": null,
|
||||||
|
"is_value": false,
|
||||||
|
"status": true,
|
||||||
|
"sort": 9,
|
||||||
|
"remark": null,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"label": "本地",
|
||||||
|
"value": "local",
|
||||||
|
"type": 0,
|
||||||
|
"color": "primary",
|
||||||
|
"is_value": true,
|
||||||
|
"status": true,
|
||||||
|
"sort": 1,
|
||||||
|
"remark": null,
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "阿里云oss",
|
||||||
|
"value": "oss",
|
||||||
|
"type": 0,
|
||||||
|
"color": "success",
|
||||||
|
"is_value": true,
|
||||||
|
"status": true,
|
||||||
|
"sort": 2,
|
||||||
|
"remark": null,
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "腾讯cos",
|
||||||
|
"value": "cos",
|
||||||
|
"type": 0,
|
||||||
|
"color": "warning",
|
||||||
|
"is_value": true,
|
||||||
|
"status": true,
|
||||||
|
"sort": 3,
|
||||||
|
"remark": null,
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -235,5 +235,252 @@
|
|||||||
"children": []
|
"children": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
"title": "文件存储配置",
|
||||||
|
"key": "file_storage",
|
||||||
|
"value": null,
|
||||||
|
"sort": 0,
|
||||||
|
"status": true,
|
||||||
|
"data_options": null,
|
||||||
|
"form_item_type": 0,
|
||||||
|
"rule": null,
|
||||||
|
"placeholder": null,
|
||||||
|
"setting": null,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"title": "存储引擎",
|
||||||
|
"key": "file_engine",
|
||||||
|
"value": "local",
|
||||||
|
"sort": 1,
|
||||||
|
"status": true,
|
||||||
|
"data_options": null,
|
||||||
|
"form_item_type": 4,
|
||||||
|
"rule": [
|
||||||
|
{
|
||||||
|
"required": false,
|
||||||
|
"message": "必填项不能为空"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"placeholder": "请选择存储引擎",
|
||||||
|
"setting": "file_engine",
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "文件是否备份",
|
||||||
|
"key": "file_backup",
|
||||||
|
"value": false,
|
||||||
|
"sort": 2,
|
||||||
|
"status": true,
|
||||||
|
"data_options": null,
|
||||||
|
"form_item_type": 9,
|
||||||
|
"rule": [
|
||||||
|
{
|
||||||
|
"required": false,
|
||||||
|
"message": "必填项不能为空"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"placeholder": "启用云存储时,文件是否备份到本地",
|
||||||
|
"setting": null,
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "阿里云-AccessKey",
|
||||||
|
"key": "aliyun_access_key",
|
||||||
|
"value": null,
|
||||||
|
"sort": 3,
|
||||||
|
"status": false,
|
||||||
|
"data_options": null,
|
||||||
|
"form_item_type": 0,
|
||||||
|
"rule": [
|
||||||
|
{
|
||||||
|
"required": false,
|
||||||
|
"message": "必填项不能为空"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"placeholder": "请输入AccessKey",
|
||||||
|
"setting": null,
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "阿里云-Secret",
|
||||||
|
"key": "aliyun_access_secret",
|
||||||
|
"value": null,
|
||||||
|
"sort": 4,
|
||||||
|
"status": false,
|
||||||
|
"data_options": null,
|
||||||
|
"form_item_type": 0,
|
||||||
|
"rule": [
|
||||||
|
{
|
||||||
|
"required": false,
|
||||||
|
"message": "必填项不能为空"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"placeholder": "请输入Secret",
|
||||||
|
"setting": null,
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "阿里云-Endpoint",
|
||||||
|
"key": "aliyun_endpoint",
|
||||||
|
"value": null,
|
||||||
|
"sort": 5,
|
||||||
|
"status": false,
|
||||||
|
"data_options": null,
|
||||||
|
"form_item_type": 0,
|
||||||
|
"rule": [
|
||||||
|
{
|
||||||
|
"required": false,
|
||||||
|
"message": "必填项不能为空"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"placeholder": "请输入Endpoint",
|
||||||
|
"setting": null,
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "阿里云-上传路径",
|
||||||
|
"key": "aliyun_path",
|
||||||
|
"value": "/media/",
|
||||||
|
"sort": 5,
|
||||||
|
"status": false,
|
||||||
|
"data_options": null,
|
||||||
|
"form_item_type": 0,
|
||||||
|
"rule": [
|
||||||
|
{
|
||||||
|
"required": false,
|
||||||
|
"message": "必填项不能为空"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"placeholder": "请输入上传路径",
|
||||||
|
"setting": null,
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "阿里云-Bucket",
|
||||||
|
"key": "aliyun_bucket",
|
||||||
|
"value": null,
|
||||||
|
"sort": 7,
|
||||||
|
"status": false,
|
||||||
|
"data_options": null,
|
||||||
|
"form_item_type": 0,
|
||||||
|
"rule": [
|
||||||
|
{
|
||||||
|
"required": false,
|
||||||
|
"message": "必填项不能为空"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"placeholder": "请输入Bucket",
|
||||||
|
"setting": null,
|
||||||
|
"children": []
|
||||||
|
},{
|
||||||
|
"title": "阿里云-cdn地址",
|
||||||
|
"key": "aliyun_cdn_url",
|
||||||
|
"value": null,
|
||||||
|
"sort": 7,
|
||||||
|
"status": false,
|
||||||
|
"data_options": null,
|
||||||
|
"form_item_type": 0,
|
||||||
|
"rule": [
|
||||||
|
{
|
||||||
|
"required": false,
|
||||||
|
"message": "必填项不能为空"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"placeholder": "请输入cdn地址",
|
||||||
|
"setting": null,
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "腾讯云-SecretId",
|
||||||
|
"key": "tencent_secret_id",
|
||||||
|
"value": null,
|
||||||
|
"sort": 8,
|
||||||
|
"status": false,
|
||||||
|
"data_options": null,
|
||||||
|
"form_item_type": 0,
|
||||||
|
"rule": [
|
||||||
|
{
|
||||||
|
"required": false,
|
||||||
|
"message": "必填项不能为空"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"placeholder": "请输入SecretId",
|
||||||
|
"setting": null,
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "腾讯云-SecretKey",
|
||||||
|
"key": "tencent_secret_key",
|
||||||
|
"value": null,
|
||||||
|
"sort": 9,
|
||||||
|
"status": false,
|
||||||
|
"data_options": null,
|
||||||
|
"form_item_type": 0,
|
||||||
|
"rule": [
|
||||||
|
{
|
||||||
|
"required": false,
|
||||||
|
"message": "必填项不能为空"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"placeholder": "请输入SecretKey",
|
||||||
|
"setting": null,
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "腾讯云-Region",
|
||||||
|
"key": "tencent_region",
|
||||||
|
"value": null,
|
||||||
|
"sort": 10,
|
||||||
|
"status": false,
|
||||||
|
"data_options": null,
|
||||||
|
"form_item_type": 0,
|
||||||
|
"rule": [
|
||||||
|
{
|
||||||
|
"required": false,
|
||||||
|
"message": "必填项不能为空"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"placeholder": "请输入Region",
|
||||||
|
"setting": null,
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "腾讯云-Bucket",
|
||||||
|
"key": "tencent_bucket",
|
||||||
|
"value": null,
|
||||||
|
"sort": 11,
|
||||||
|
"status": false,
|
||||||
|
"data_options": null,
|
||||||
|
"form_item_type": 0,
|
||||||
|
"rule": [
|
||||||
|
{
|
||||||
|
"required": false,
|
||||||
|
"message": "必填项不能为空"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"placeholder": "请输入Bucket",
|
||||||
|
"setting": null,
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "腾讯云-上传路径",
|
||||||
|
"key": "tencent_path",
|
||||||
|
"value": "/media/",
|
||||||
|
"sort": 12,
|
||||||
|
"status": false,
|
||||||
|
"data_options": null,
|
||||||
|
"form_item_type": 0,
|
||||||
|
"rule": [
|
||||||
|
{
|
||||||
|
"required": false,
|
||||||
|
"message": "必填项不能为空"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"placeholder": "请输入上传路径",
|
||||||
|
"setting": null,
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
@@ -35,8 +35,8 @@ class FileSerializer(CustomModelSerializer):
|
|||||||
fields = "__all__"
|
fields = "__all__"
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
file_engine = dispatch.get_system_config_values("fileStorageConfig.file_engine") or 'local'
|
file_engine = dispatch.get_system_config_values("file_storage.file_engine") or 'local'
|
||||||
file_backup = dispatch.get_system_config_values("fileStorageConfig.file_backup")
|
file_backup = dispatch.get_system_config_values("file_storage.file_backup")
|
||||||
file = self.initial_data.get('file')
|
file = self.initial_data.get('file')
|
||||||
file_size = file.size
|
file_size = file.size
|
||||||
validated_data['name'] = str(file)
|
validated_data['name'] = str(file)
|
||||||
@@ -52,15 +52,15 @@ class FileSerializer(CustomModelSerializer):
|
|||||||
if file_backup:
|
if file_backup:
|
||||||
validated_data['url'] = file
|
validated_data['url'] = file
|
||||||
if file_engine == 'oss':
|
if file_engine == 'oss':
|
||||||
from dvadmin_cloud_storage.views.aliyun import ali_oss_upload
|
from dvadmin.utils.aliyunoss import ali_oss_upload
|
||||||
file_path = ali_oss_upload(file)
|
file_path = ali_oss_upload(file, file_name=validated_data['name'])
|
||||||
if file_path:
|
if file_path:
|
||||||
validated_data['file_url'] = file_path
|
validated_data['file_url'] = file_path
|
||||||
else:
|
else:
|
||||||
raise ValueError("上传失败")
|
raise ValueError("上传失败")
|
||||||
elif file_engine == 'cos':
|
elif file_engine == 'cos':
|
||||||
from dvadmin_cloud_storage.views.tencent import tencent_cos_upload
|
from dvadmin.utils.tencentcos import tencent_cos_upload
|
||||||
file_path = tencent_cos_upload(file)
|
file_path = tencent_cos_upload(file, file_name=validated_data['name'])
|
||||||
if file_path:
|
if file_path:
|
||||||
validated_data['file_url'] = file_path
|
validated_data['file_url'] = file_path
|
||||||
else:
|
else:
|
||||||
|
|||||||
62
backend/dvadmin/utils/aliyunoss.py
Normal file
62
backend/dvadmin/utils/aliyunoss.py
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import oss2
|
||||||
|
from rest_framework.exceptions import ValidationError
|
||||||
|
|
||||||
|
from application import dispatch
|
||||||
|
|
||||||
|
|
||||||
|
# 进度条
|
||||||
|
# 当无法确定待上传的数据长度时,total_bytes的值为None。
|
||||||
|
def percentage(consumed_bytes, total_bytes):
|
||||||
|
if total_bytes:
|
||||||
|
rate = int(100 * (float(consumed_bytes) / float(total_bytes)))
|
||||||
|
print('\r{0}% '.format(rate), end='')
|
||||||
|
|
||||||
|
|
||||||
|
def ali_oss_upload(file, file_name):
|
||||||
|
"""
|
||||||
|
阿里云OSS上传
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
file.seek(0)
|
||||||
|
file_read = file.read()
|
||||||
|
except Exception as e:
|
||||||
|
file_read = file
|
||||||
|
if not file:
|
||||||
|
raise ValidationError('请上传文件')
|
||||||
|
# 转存到oss
|
||||||
|
path_prefix = dispatch.get_system_config_values("file_storage.aliyun_path")
|
||||||
|
if not path_prefix.endswith('/'):
|
||||||
|
path_prefix = path_prefix + '/'
|
||||||
|
if path_prefix.startswith('/'):
|
||||||
|
path_prefix = path_prefix[1:]
|
||||||
|
base_fil_name = f'{path_prefix}{file_name}'
|
||||||
|
# 获取OSS配置
|
||||||
|
# 获取的AccessKey
|
||||||
|
access_key_id = dispatch.get_system_config_values("file_storage.aliyun_access_key")
|
||||||
|
access_key_secret = dispatch.get_system_config_values("file_storage.aliyun_access_secret")
|
||||||
|
auth = oss2.Auth(access_key_id, access_key_secret)
|
||||||
|
# 这个是需要用特定的地址,不同地域的服务器地址不同,不要弄错了
|
||||||
|
# 参考官网给的地址配置https://www.alibabacloud.com/help/zh/object-storage-service/latest/regions-and-endpoints#concept-zt4-cvy-5db
|
||||||
|
endpoint = dispatch.get_system_config_values("file_storage.aliyun_endpoint")
|
||||||
|
bucket_name = dispatch.get_system_config_values("file_storage.aliyun_bucket")
|
||||||
|
if bucket_name.endswith(endpoint):
|
||||||
|
bucket_name = bucket_name.replace(f'.{endpoint}', '')
|
||||||
|
# 你的项目名称,类似于不同的项目上传的图片前缀url不同
|
||||||
|
bucket = oss2.Bucket(auth, endpoint, bucket_name) # 项目名称
|
||||||
|
# 生成外网访问的文件路径
|
||||||
|
aliyun_cdn_url = dispatch.get_system_config_values("file_storage.aliyun_cdn_url")
|
||||||
|
if aliyun_cdn_url:
|
||||||
|
if aliyun_cdn_url.endswith('/'):
|
||||||
|
aliyun_cdn_url = aliyun_cdn_url[1:]
|
||||||
|
file_path = f"{aliyun_cdn_url}/{base_fil_name}"
|
||||||
|
else:
|
||||||
|
file_path = f"https://{bucket_name}.{endpoint}/{base_fil_name}"
|
||||||
|
# 这个是阿里提供的SDK方法
|
||||||
|
res = bucket.put_object(base_fil_name, file_read, progress_callback=percentage)
|
||||||
|
# 如果上传状态是200 代表成功 返回文件外网访问路径
|
||||||
|
if res.status == 200:
|
||||||
|
return file_path
|
||||||
|
else:
|
||||||
|
return None
|
||||||
56
backend/dvadmin/utils/tencentcos.py
Normal file
56
backend/dvadmin/utils/tencentcos.py
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from rest_framework.exceptions import ValidationError
|
||||||
|
|
||||||
|
from application import dispatch
|
||||||
|
from qcloud_cos import CosConfig
|
||||||
|
from qcloud_cos import CosS3Client
|
||||||
|
|
||||||
|
|
||||||
|
# 进度条
|
||||||
|
# 当无法确定待上传的数据长度时,total_bytes的值为None。
|
||||||
|
def percentage(consumed_bytes, total_bytes):
|
||||||
|
if total_bytes:
|
||||||
|
rate = int(100 * (float(consumed_bytes) / float(total_bytes)))
|
||||||
|
print('\r{0}% '.format(rate), end='')
|
||||||
|
|
||||||
|
def tencent_cos_upload(file, file_name):
|
||||||
|
try:
|
||||||
|
file.seek(0)
|
||||||
|
file_read = file.read()
|
||||||
|
except Exception as e:
|
||||||
|
file_read = file
|
||||||
|
if not file:
|
||||||
|
raise ValidationError('请上传文件')
|
||||||
|
# 生成文件名
|
||||||
|
path_prefix = dispatch.get_system_config_values("file_storage.tencent_path")
|
||||||
|
if not path_prefix.endswith('/'):
|
||||||
|
path_prefix = path_prefix + '/'
|
||||||
|
if path_prefix.startswith('/'):
|
||||||
|
path_prefix = path_prefix[1:]
|
||||||
|
base_fil_name = f'{path_prefix}{file_name}'
|
||||||
|
# 获取cos配置
|
||||||
|
# 1. 设置用户属性, 包括 secret_id, secret_key, region等。Appid 已在 CosConfig 中移除,请在参数 Bucket 中带上 Appid。Bucket 由 BucketName-Appid 组成
|
||||||
|
secret_id = dispatch.get_system_config_values("file_storage.tencent_secret_id") # 用户的 SecretId,建议使用子账号密钥,授权遵循最小权限指引,降低使用风险。子账号密钥获取可参见 https://cloud.tencent.com/document/product/598/37140
|
||||||
|
secret_key = dispatch.get_system_config_values("file_storage.tencent_secret_key") # 用户的 SecretKey,建议使用子账号密钥,授权遵循最小权限指引,降低使用风险。子账号密钥获取可参见 https://cloud.tencent.com/document/product/598/37140
|
||||||
|
region = dispatch.get_system_config_values("file_storage.tencent_region") # 替换为用户的 region,已创建桶归属的 region 可以在控制台查看,https://console.cloud.tencent.com/cos5/bucket # COS 支持的所有 region 列表参见https://cloud.tencent.com/document/product/436/6224
|
||||||
|
bucket = dispatch.get_system_config_values("file_storage.tencent_bucket") # 要访问的桶名称
|
||||||
|
config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key)
|
||||||
|
client = CosS3Client(config)
|
||||||
|
# 访问地址
|
||||||
|
base_file_url = f'https://{bucket}.cos.{region}.myqcloud.com'
|
||||||
|
# 生成外网访问的文件路径
|
||||||
|
if base_file_url.endswith('/'):
|
||||||
|
file_path = base_file_url + base_fil_name
|
||||||
|
else:
|
||||||
|
file_path = f'{base_file_url}/{base_fil_name}'
|
||||||
|
# 这个是阿里提供的SDK方法 bucket是调用的4.1中配置的变量名
|
||||||
|
try:
|
||||||
|
response = client.put_object(
|
||||||
|
Bucket=bucket,
|
||||||
|
Body=file_read,
|
||||||
|
Key=base_fil_name,
|
||||||
|
EnableMD5=False
|
||||||
|
)
|
||||||
|
return file_path
|
||||||
|
except:
|
||||||
|
return None
|
||||||
@@ -29,4 +29,6 @@ gunicorn==22.0.0
|
|||||||
gevent==24.2.1
|
gevent==24.2.1
|
||||||
Pillow==10.4.0
|
Pillow==10.4.0
|
||||||
pyinstaller==6.9.0
|
pyinstaller==6.9.0
|
||||||
dvadmin3-celery==3.1.6
|
dvadmin3-celery==3.1.6
|
||||||
|
oss2==2.19.1
|
||||||
|
cos-python-sdk-v5==1.9.37
|
||||||
Reference in New Issue
Block a user