diff --git a/backend/application/settings.py b/backend/application/settings.py index 1d0adf7..71ed7d5 100644 --- a/backend/application/settings.py +++ b/backend/application/settings.py @@ -399,8 +399,12 @@ DICTIONARY_CONFIG = {} # ================================================= # # 租户共享app TENANT_SHARED_APPS = [] +# 普通租户独有app +TENANT_EXCLUSIVE_APPS = [] # 插件 urlpatterns PLUGINS_URL_PATTERNS = [] +# 所有模式有的 +SHARED_APPS = [] # ********** 一键导入插件配置开始 ********** # 例如: # from dvadmin_upgrade_center.settings import * # 升级中心 diff --git a/backend/dvadmin/system/fixtures/init_dictionary.json b/backend/dvadmin/system/fixtures/init_dictionary.json index f750c40..c4bd186 100644 --- a/backend/dvadmin/system/fixtures/init_dictionary.json +++ b/backend/dvadmin/system/fixtures/init_dictionary.json @@ -546,5 +546,50 @@ "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": [] + } + ] } ] \ No newline at end of file diff --git a/backend/dvadmin/system/fixtures/init_systemconfig.json b/backend/dvadmin/system/fixtures/init_systemconfig.json index 98c95cd..cc692f2 100644 --- a/backend/dvadmin/system/fixtures/init_systemconfig.json +++ b/backend/dvadmin/system/fixtures/init_systemconfig.json @@ -235,5 +235,252 @@ "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": [] + } + ] + } ] \ No newline at end of file diff --git a/backend/dvadmin/system/views/file_list.py b/backend/dvadmin/system/views/file_list.py index c0fed8d..a155ea1 100644 --- a/backend/dvadmin/system/views/file_list.py +++ b/backend/dvadmin/system/views/file_list.py @@ -35,8 +35,8 @@ class FileSerializer(CustomModelSerializer): fields = "__all__" def create(self, validated_data): - file_engine = dispatch.get_system_config_values("fileStorageConfig.file_engine") or 'local' - file_backup = dispatch.get_system_config_values("fileStorageConfig.file_backup") + file_engine = dispatch.get_system_config_values("file_storage.file_engine") or 'local' + file_backup = dispatch.get_system_config_values("file_storage.file_backup") file = self.initial_data.get('file') file_size = file.size validated_data['name'] = str(file) @@ -52,15 +52,15 @@ class FileSerializer(CustomModelSerializer): if file_backup: validated_data['url'] = file if file_engine == 'oss': - from dvadmin_cloud_storage.views.aliyun import ali_oss_upload - file_path = ali_oss_upload(file) + from dvadmin.utils.aliyunoss import ali_oss_upload + file_path = ali_oss_upload(file, file_name=validated_data['name']) if file_path: validated_data['file_url'] = file_path else: raise ValueError("上传失败") elif file_engine == 'cos': - from dvadmin_cloud_storage.views.tencent import tencent_cos_upload - file_path = tencent_cos_upload(file) + from dvadmin.utils.tencentcos import tencent_cos_upload + file_path = tencent_cos_upload(file, file_name=validated_data['name']) if file_path: validated_data['file_url'] = file_path else: diff --git a/backend/dvadmin/utils/aliyunoss.py b/backend/dvadmin/utils/aliyunoss.py new file mode 100644 index 0000000..b4e2894 --- /dev/null +++ b/backend/dvadmin/utils/aliyunoss.py @@ -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 diff --git a/backend/dvadmin/utils/tencentcos.py b/backend/dvadmin/utils/tencentcos.py new file mode 100644 index 0000000..a515124 --- /dev/null +++ b/backend/dvadmin/utils/tencentcos.py @@ -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 diff --git a/backend/requirements.txt b/backend/requirements.txt index 9cae2cc..2e6a68a 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -29,4 +29,6 @@ gunicorn==22.0.0 gevent==24.2.1 Pillow==10.4.0 pyinstaller==6.9.0 -dvadmin3-celery==3.1.6 \ No newline at end of file +dvadmin3-celery==3.1.6 +oss2==2.19.1 +cos-python-sdk-v5==1.9.37 \ No newline at end of file