lesscode-flask 0.2.115__tar.gz → 0.2.117__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/PKG-INFO +1 -1
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/__init__.py +1 -1
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/setting/__init__.py +1 -1
- lesscode_flask-0.2.117/lesscode_flask/utils/oss/__init__.py +352 -0
- lesscode_flask-0.2.117/lesscode_flask/utils/oss/rustfs_oss.py +170 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/utils/sign/signature.py +13 -2
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask.egg-info/PKG-INFO +1 -1
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask.egg-info/SOURCES.txt +1 -0
- lesscode_flask-0.2.115/lesscode_flask/utils/oss/__init__.py +0 -165
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/README.md +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/app.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/db/__init__.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/db/datasource.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/db/executor.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/export_data/__init__.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/export_data/data_download_handler.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/log/access_log_handler.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/model/access_log.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/model/base_model.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/model/parameterized_query.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/model/resource_param_template.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/model/response_result.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/model/user.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/model/user_limit_policy.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/service/access_log_service.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/service/base_service.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/service/resource_param_template_service.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/setup/__init__.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/signals.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/static/swagger.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/utils/__init__.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/utils/decorator/__init__.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/utils/decorator/cache.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/utils/decorator/sql_injection.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/utils/decorator/swagger.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/utils/dify_utils.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/utils/file/file_exporter.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/utils/file/file_utils.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/utils/fs_util.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/utils/helpers.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/utils/json/NotSortJSONProvider.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/utils/limit/__init__.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/utils/limit/consecutive/consecutive_limiter_handler.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/utils/limit/consecutive/redis_consecutive_limiter.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/utils/limit/limit_util.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/utils/limit/req/rate_limiter_handler.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/utils/limit/req/redis_rate_limiter.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/utils/limit/req_count/count_limiter_handler.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/utils/limit/req_count/redis_count_limiter.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/utils/oss/aliyun_oss.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/utils/oss/ks3_oss.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/utils/oss/minio_oss.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/utils/redis/redis_helper.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/utils/request/request.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/utils/sign/__init__.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/utils/sign/body_canonical.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/utils/sign/sign_main.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/utils/swagger/swagger_template.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/utils/swagger/swagger_util.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/utils/task/__init__.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/utils/task/task_helper.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/utils/thread/thread_utils.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/wsgi.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask.egg-info/dependency_links.txt +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask.egg-info/requires.txt +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask.egg-info/top_level.txt +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/redash/query_runner/__init__.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/redash/query_runner/clickhouse.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/redash/query_runner/dameng.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/redash/query_runner/elasticsearch.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/redash/query_runner/kingbase.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/redash/query_runner/mysql.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/redash/query_runner/pg.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/redash/settings/__init__.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/redash/settings/helpers.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/redash/utils/__init__.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/redash/utils/requests_session.py +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/setup.cfg +0 -0
- {lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/setup.py +0 -0
|
@@ -100,7 +100,7 @@ class BaseConfig:
|
|
|
100
100
|
SIGN_NONCE_ENABLE: bool = True
|
|
101
101
|
# nonce缓存时长(秒)
|
|
102
102
|
SIGN_NONCE_TTL_SEC: int = 300
|
|
103
|
-
#
|
|
103
|
+
# 签名白名单路径,普通配置按前缀匹配;带 * 时支持任意内容通配,例如 /a/*/b
|
|
104
104
|
SIGN_WHITE_LIST: list = [SWAGGER_URL, SWAGGER_API_URL, f"{ROUTE_PREFIX}/oauth/token", f"{ROUTE_PREFIX}/oauth/captcha"]
|
|
105
105
|
# 签名IP白名单(支持单IP或CIDR,命中后跳过签名校验)
|
|
106
106
|
SIGN_IP_WHITE_LIST: list = []
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import os
|
|
3
|
+
from io import BytesIO
|
|
4
|
+
|
|
5
|
+
from lesscode_flask.utils.helpers import app_config
|
|
6
|
+
from lesscode_flask.utils.oss.aliyun_oss import AliYunOss
|
|
7
|
+
from lesscode_flask.utils.oss.ks3_oss import Ks3Oss
|
|
8
|
+
from lesscode_flask.utils.oss.minio_oss import MinioOss
|
|
9
|
+
from lesscode_flask.utils.oss.rustfs_oss import RustfsOss
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class CommonOss:
|
|
13
|
+
def __init__(self, storage_type=None, data_type="stream", config_key="default", **kwargs):
|
|
14
|
+
"""
|
|
15
|
+
初始化统一 OSS 入口。
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
storage_type (str): 存储类型,可选值:
|
|
19
|
+
- "ks3" : 金山云 KS3 对象存储,依赖 ks3sdk==1.5.0
|
|
20
|
+
- "aliyun" : 阿里云 OSS,依赖 oss2
|
|
21
|
+
- "minio" : MinIO / 自托管 S3,依赖 minio
|
|
22
|
+
- "rustfs" : RustFS(S3 兼容),依赖 boto3
|
|
23
|
+
- "file" : 本地文件系统存储,无额外依赖
|
|
24
|
+
data_type (str): 数据来源类型,可选值:
|
|
25
|
+
- "stream" : 从文件流(BytesIO / werkzeug FileStorage)上传(默认)
|
|
26
|
+
- "file_path" : 从本地文件路径上传
|
|
27
|
+
config_key (str): 读取 app.config["STORAGE_CONFIG"] 时使用的配置键名,默认 "default"。
|
|
28
|
+
STORAGE_CONFIG 示例结构:
|
|
29
|
+
STORAGE_CONFIG = {
|
|
30
|
+
"default": {
|
|
31
|
+
"storage_type": "<storage_type>",
|
|
32
|
+
"storage_config": { ...各存储类型连接参数... }
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
**kwargs:
|
|
36
|
+
storage_config (dict): 直接传入连接参数字典,会覆盖 STORAGE_CONFIG 中的 storage_config。
|
|
37
|
+
bucket_name (str) : 直接指定桶名,会覆盖 STORAGE_CONFIG 中的 bucket_name。
|
|
38
|
+
|
|
39
|
+
各存储类型的 storage_config 参数说明
|
|
40
|
+
=====================================
|
|
41
|
+
|
|
42
|
+
── ks3(金山云 KS3)──────────────────────────────────────────
|
|
43
|
+
依赖:pip install ks3sdk==1.5.0
|
|
44
|
+
storage_config 字段:
|
|
45
|
+
access_key_id (str, 必填) : KS3 Access Key ID
|
|
46
|
+
access_key_secret (str, 必填) : KS3 Access Key Secret
|
|
47
|
+
host (str, 必填) : KS3 服务域名,例如 "ks3-cn-beijing.ksyun.com"
|
|
48
|
+
is_secure (bool, 可选): 是否启用 HTTPS,默认 False
|
|
49
|
+
bucket_name (str, 必填) : 默认操作的桶名
|
|
50
|
+
STORAGE_CONFIG 示例:
|
|
51
|
+
"storage_config": {
|
|
52
|
+
"access_key_id": "your-access-key-id",
|
|
53
|
+
"access_key_secret": "your-access-key-secret",
|
|
54
|
+
"host": "ks3-cn-beijing.ksyun.com",
|
|
55
|
+
"is_secure": False,
|
|
56
|
+
"bucket_name": "your-bucket"
|
|
57
|
+
}
|
|
58
|
+
save() 返回的 URL 格式:
|
|
59
|
+
https://<bucket_name>.<region>.ksyun.com/<key>
|
|
60
|
+
region 默认 "ks3-cn-beijing",domain 默认 "ksyun.com"
|
|
61
|
+
|
|
62
|
+
── aliyun(阿里云 OSS)──────────────────────────────────────
|
|
63
|
+
依赖:pip install oss2
|
|
64
|
+
storage_config 字段:
|
|
65
|
+
access_key_id (str, 必填) : 阿里云 Access Key ID
|
|
66
|
+
access_key_secret (str, 必填) : 阿里云 Access Key Secret
|
|
67
|
+
endpoint (str, 必填) : OSS Endpoint,例如 "https://oss-cn-hangzhou.aliyuncs.com"
|
|
68
|
+
bucket_name (str, 必填) : 默认操作的桶名
|
|
69
|
+
region (str, 可选) : 地域标识,默认 "oss-cn-hangzhou",用于拼接公网 URL
|
|
70
|
+
domain (str, 可选) : 域名后缀,默认 "aliyuncs.com"
|
|
71
|
+
protocol (str, 可选) : URL 协议,默认 "https"
|
|
72
|
+
session (obj, 可选) : oss2 Session 对象
|
|
73
|
+
connect_timeout (int, 可选) : 连接超时秒数
|
|
74
|
+
proxies (dict, 可选): 代理配置
|
|
75
|
+
cloudbox_id (str, 可选) : 云盒 ID(云盒场景使用)
|
|
76
|
+
is_path_style (bool, 可选): 是否使用 Path-Style 访问,默认 False
|
|
77
|
+
STORAGE_CONFIG 示例:
|
|
78
|
+
"storage_config": {
|
|
79
|
+
"access_key_id": "your-access-key-id",
|
|
80
|
+
"access_key_secret": "your-access-key-secret",
|
|
81
|
+
"endpoint": "https://oss-cn-hangzhou.aliyuncs.com",
|
|
82
|
+
"bucket_name": "your-bucket",
|
|
83
|
+
"region": "oss-cn-hangzhou"
|
|
84
|
+
}
|
|
85
|
+
save() 返回的 URL 格式:
|
|
86
|
+
https://<bucket_name>.<region>.aliyuncs.com/<key>
|
|
87
|
+
|
|
88
|
+
── minio(MinIO / 自托管 S3 兼容)──────────────────────────
|
|
89
|
+
依赖:pip install minio
|
|
90
|
+
storage_config 字段:
|
|
91
|
+
service (str, 必填) : MinIO 服务地址(不含协议),例如 "127.0.0.1:9000"
|
|
92
|
+
access_key (str, 必填) : MinIO Access Key(用户名)
|
|
93
|
+
secret_key (str, 必填) : MinIO Secret Key(密码)
|
|
94
|
+
secure (bool, 可选): 是否启用 HTTPS,默认 False
|
|
95
|
+
bucket_name (str, 必填) : 默认操作的桶名
|
|
96
|
+
STORAGE_CONFIG 示例:
|
|
97
|
+
"storage_config": {
|
|
98
|
+
"service": "127.0.0.1:9000",
|
|
99
|
+
"access_key": "minioadmin",
|
|
100
|
+
"secret_key": "minioadmin",
|
|
101
|
+
"secure": False,
|
|
102
|
+
"bucket_name": "your-bucket"
|
|
103
|
+
}
|
|
104
|
+
save() 返回的 URL 格式:
|
|
105
|
+
presigned_get_object 生成的预签名 URL(有效期 3600 秒)
|
|
106
|
+
|
|
107
|
+
── rustfs(RustFS,S3 兼容协议)─────────────────────────────
|
|
108
|
+
依赖:pip install boto3
|
|
109
|
+
storage_config 字段:
|
|
110
|
+
endpoint (str, 必填) : RustFS 服务地址,可含协议头,
|
|
111
|
+
例如 "http://127.0.0.1:9000" 或 "127.0.0.1:9000"
|
|
112
|
+
access_key (str, 必填) : Access Key ID
|
|
113
|
+
secret_key (str, 必填) : Secret Access Key
|
|
114
|
+
secure (bool, 可选): 为 True 时自动补全 "https://",默认 False(即 "http://");
|
|
115
|
+
若 endpoint 已含协议头则忽略此参数
|
|
116
|
+
region (str, 可选) : S3 Region,默认 "us-east-1"(RustFS 通常不校验)
|
|
117
|
+
bucket_name (str, 必填) : 默认操作的桶名
|
|
118
|
+
STORAGE_CONFIG 示例:
|
|
119
|
+
"storage_config": {
|
|
120
|
+
"endpoint": "http://127.0.0.1:9000",
|
|
121
|
+
"access_key": "rustfsadmin",
|
|
122
|
+
"secret_key": "rustfsadmin",
|
|
123
|
+
"secure": False,
|
|
124
|
+
"region": "us-east-1",
|
|
125
|
+
"bucket_name": "your-bucket"
|
|
126
|
+
}
|
|
127
|
+
save() 返回的 URL 格式:
|
|
128
|
+
<endpoint_url>/<bucket_name>/<key>
|
|
129
|
+
例如:http://127.0.0.1:9000/your-bucket/path/to/file.jpg
|
|
130
|
+
|
|
131
|
+
── file(本地文件系统)──────────────────────────────────────
|
|
132
|
+
无额外依赖
|
|
133
|
+
storage_config 字段:
|
|
134
|
+
STORAGE_DIR (str, 必填) : 本地存储根目录的绝对路径,
|
|
135
|
+
例如 "/data/uploads"
|
|
136
|
+
bucket_name 不适用,文件直接按 key 的路径层级写入 STORAGE_DIR
|
|
137
|
+
STORAGE_CONFIG 示例:
|
|
138
|
+
"storage_config": {
|
|
139
|
+
"STORAGE_DIR": "/data/uploads"
|
|
140
|
+
}
|
|
141
|
+
save() 返回的 URL 格式:
|
|
142
|
+
文件在磁盘上的完整绝对路径,例如 "/data/uploads/2024/01/photo.jpg"
|
|
143
|
+
注意:
|
|
144
|
+
- key 中的路径分隔符("/" 或 "\\")会被转换为系统路径,目录不存在时自动创建
|
|
145
|
+
- data_type="file_path" 时会先读取源文件再写入目标路径
|
|
146
|
+
"""
|
|
147
|
+
storage_config_setting = app_config.get("STORAGE_CONFIG", {})
|
|
148
|
+
storage_config = copy.deepcopy(storage_config_setting) or dict()
|
|
149
|
+
_storage_config = storage_config.get(config_key, {}) or dict()
|
|
150
|
+
_storage_config_storage_type = _storage_config.get("storage_type", "")
|
|
151
|
+
_storage_type = storage_type if storage_type else _storage_config_storage_type
|
|
152
|
+
_config_storage_config = _storage_config.get("storage_config", {})
|
|
153
|
+
_config_bucket_name = _config_storage_config.pop("bucket_name", "")
|
|
154
|
+
self.storage_type = _storage_type
|
|
155
|
+
self.data_type = data_type
|
|
156
|
+
self.storage_config = kwargs.get("storage_config", {}) if kwargs.get("storage_config",
|
|
157
|
+
{}) else _config_storage_config
|
|
158
|
+
self.bucket_name = kwargs.get("bucket_name") or _config_bucket_name
|
|
159
|
+
|
|
160
|
+
def _save(self, key, io_stream: BytesIO = None, file_path: str = None, bucket_name: str = None):
|
|
161
|
+
file_url_obj = dict()
|
|
162
|
+
if self.storage_type == "ks3":
|
|
163
|
+
if self.data_type == "stream":
|
|
164
|
+
storage_config = self.storage_config or dict()
|
|
165
|
+
ks3 = Ks3Oss(bucket_name=bucket_name or self.bucket_name, **storage_config)
|
|
166
|
+
url = ks3.save(key=key, string_data=io_stream.getvalue(), content_type="string", policy="public-read",
|
|
167
|
+
bucket_name=bucket_name or self.bucket_name)
|
|
168
|
+
file_url_obj = {"key": key, "url": url}
|
|
169
|
+
elif self.data_type == "file_path":
|
|
170
|
+
storage_config = self.storage_config or dict()
|
|
171
|
+
ks3 = Ks3Oss(bucket_name=bucket_name or self.bucket_name, **storage_config)
|
|
172
|
+
url = ks3.save(key=key, filename=file_path, content_type="filename", policy="public-read")
|
|
173
|
+
file_url_obj = {"key": key, "url": url}
|
|
174
|
+
elif self.storage_type == "aliyun":
|
|
175
|
+
storage_config = self.storage_config or dict()
|
|
176
|
+
aliyun = AliYunOss(bucket_name=bucket_name or self.bucket_name, **storage_config)
|
|
177
|
+
url = aliyun.save(key=key, content_type="string", data=io_stream.getvalue(),
|
|
178
|
+
bucket_name=bucket_name or self.bucket_name)
|
|
179
|
+
file_url_obj = {"key": key, "url": url}
|
|
180
|
+
elif self.storage_type == "minio":
|
|
181
|
+
storage_config = self.storage_config or dict()
|
|
182
|
+
minio = MinioOss(**storage_config)
|
|
183
|
+
url = minio.save(key=key, content_type="string", data=io_stream.getvalue(),
|
|
184
|
+
bucket_name=bucket_name or self.bucket_name)
|
|
185
|
+
file_url_obj = {"key": key, "url": url}
|
|
186
|
+
elif self.storage_type == "rustfs":
|
|
187
|
+
storage_config = self.storage_config or dict()
|
|
188
|
+
rustfs = RustfsOss(**storage_config)
|
|
189
|
+
url = rustfs.save(key=key, content_type="string", data=io_stream.getvalue(),
|
|
190
|
+
bucket_name=bucket_name or self.bucket_name)
|
|
191
|
+
file_url_obj = {"key": key, "url": url}
|
|
192
|
+
elif self.storage_type == "file":
|
|
193
|
+
storage_path = ""
|
|
194
|
+
storage_dir = self.storage_config.get("STORAGE_DIR", "")
|
|
195
|
+
if not storage_dir:
|
|
196
|
+
raise Exception("storage_dir is empty")
|
|
197
|
+
if self.data_type == "stream":
|
|
198
|
+
if "\\" in key:
|
|
199
|
+
key_list = key.split("\\")
|
|
200
|
+
elif "/" in key:
|
|
201
|
+
key_list = key.split("/")
|
|
202
|
+
else:
|
|
203
|
+
key_list = [key]
|
|
204
|
+
storage_path = storage_dir
|
|
205
|
+
if key_list:
|
|
206
|
+
for k in key_list:
|
|
207
|
+
storage_path = os.path.join(storage_path, k)
|
|
208
|
+
dir_path = os.path.dirname(storage_path)
|
|
209
|
+
if not os.path.exists(dir_path):
|
|
210
|
+
os.makedirs(dir_path)
|
|
211
|
+
elif self.data_type == "file_path":
|
|
212
|
+
with open(file_path, 'rb') as infile:
|
|
213
|
+
io_stream = BytesIO(infile.read())
|
|
214
|
+
if storage_path:
|
|
215
|
+
with open(storage_path, 'wb') as outfile:
|
|
216
|
+
outfile.write(io_stream.getvalue())
|
|
217
|
+
file_url_obj = {"key": key, "url": storage_path}
|
|
218
|
+
return file_url_obj
|
|
219
|
+
|
|
220
|
+
def upload(self, **kwargs):
|
|
221
|
+
"""上传文件到对象存储。
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
files (list, 必填): 待上传的文件列表。根据 data_type 不同,列表元素格式不同:
|
|
225
|
+
|
|
226
|
+
data_type="stream" 时,每个元素可以是:
|
|
227
|
+
- werkzeug FileStorage 对象(Flask request.files 中的文件)
|
|
228
|
+
内部会读取 f.filename 作为 key,f.stream.read() 作为内容
|
|
229
|
+
- dict,格式:
|
|
230
|
+
{
|
|
231
|
+
"key": str, # 对象存储中的文件路径/名称,例如 "images/2024/photo.jpg"
|
|
232
|
+
"stream": werkzeug.FileStorage # 文件流对象
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
data_type="file_path" 时,每个元素可以是:
|
|
236
|
+
- str,本地文件的绝对路径,例如 "/tmp/photo.jpg"
|
|
237
|
+
key 自动取路径中的文件名部分
|
|
238
|
+
- dict,格式:
|
|
239
|
+
{
|
|
240
|
+
"key": str, # 对象存储中的文件路径/名称
|
|
241
|
+
"file_path": str # 本地文件的绝对路径
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
bucket_name (str, 可选): 上传到的目标桶名,默认使用初始化时指定的 bucket_name。
|
|
245
|
+
|
|
246
|
+
Returns:
|
|
247
|
+
list[dict]: 每个元素对应一个上传结果,格式为:
|
|
248
|
+
{
|
|
249
|
+
"key": str, # 上传时使用的对象 key
|
|
250
|
+
"url": str # 文件的访问地址:
|
|
251
|
+
# ks3 → https://<bucket>.<region>.ksyun.com/<key>
|
|
252
|
+
# aliyun → https://<bucket>.<region>.aliyuncs.com/<key>
|
|
253
|
+
# minio → presigned URL(有效期 3600 秒)
|
|
254
|
+
# rustfs → http(s)://<endpoint>/<bucket>/<key>
|
|
255
|
+
# file → 本地文件绝对路径
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
Raises:
|
|
259
|
+
Exception: files 为空时抛出 "files is empty"
|
|
260
|
+
"""
|
|
261
|
+
file_url_list = []
|
|
262
|
+
files = kwargs.get("files", [])
|
|
263
|
+
bucket_name = kwargs.get("bucket_name", self.bucket_name)
|
|
264
|
+
if not files:
|
|
265
|
+
raise Exception("files is empty")
|
|
266
|
+
if self.data_type == "stream":
|
|
267
|
+
for f in files:
|
|
268
|
+
if not isinstance(f, dict):
|
|
269
|
+
key = f.filename
|
|
270
|
+
stream = f.stream.read()
|
|
271
|
+
file_stream = BytesIO(stream)
|
|
272
|
+
else:
|
|
273
|
+
key = f.get("key", "")
|
|
274
|
+
_steam = f.get("stream")
|
|
275
|
+
stream = _steam.stream.read()
|
|
276
|
+
file_stream = BytesIO(stream)
|
|
277
|
+
file_url_obj = self._save(key=key, io_stream=file_stream, bucket_name=bucket_name)
|
|
278
|
+
file_url_list.append(file_url_obj)
|
|
279
|
+
|
|
280
|
+
elif self.data_type == "file_path":
|
|
281
|
+
for f in files:
|
|
282
|
+
if not isinstance(f, dict):
|
|
283
|
+
if "\\" in f:
|
|
284
|
+
file_name = f.split("\\")[-1]
|
|
285
|
+
elif "/" in f:
|
|
286
|
+
file_name = f.split("/")[-1]
|
|
287
|
+
else:
|
|
288
|
+
file_name = f
|
|
289
|
+
key = file_name
|
|
290
|
+
_file_path = f
|
|
291
|
+
else:
|
|
292
|
+
key = f.get("key", "")
|
|
293
|
+
_file_path = f.get("file_path")
|
|
294
|
+
file_url_obj = self._save(key=key, file_path=_file_path, bucket_name=bucket_name)
|
|
295
|
+
file_url_list.append(file_url_obj)
|
|
296
|
+
return file_url_list
|
|
297
|
+
|
|
298
|
+
def download(self, key, bucket_name=None):
|
|
299
|
+
"""从对象存储下载文件,返回文件内容。
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
key (str, 必填): 文件在对象存储中的 key(即上传时使用的路径/名称),
|
|
303
|
+
例如 "images/2024/photo.jpg"。
|
|
304
|
+
对于 storage_type="file",key 同样支持 "/" 或 "\\" 分隔的相对路径,
|
|
305
|
+
会与 STORAGE_DIR 拼接成完整本地路径。
|
|
306
|
+
bucket_name (str, 可选): 目标桶名,默认使用初始化时指定的 bucket_name。
|
|
307
|
+
|
|
308
|
+
Returns:
|
|
309
|
+
各存储类型返回值说明:
|
|
310
|
+
- ks3 : bytes,文件的原始字节内容
|
|
311
|
+
- aliyun : oss2 GetObjectResult 对象,支持 .read() 读取内容,
|
|
312
|
+
也可通过 get_file(file_path=...) 直接保存到本地
|
|
313
|
+
- minio : urllib3 HTTPResponse 对象,支持 .read() 或迭代流式读取,
|
|
314
|
+
使用后需调用 .close() 释放连接
|
|
315
|
+
- rustfs : botocore StreamingBody 对象,支持 .read() 或 .iter_chunks() 流式读取
|
|
316
|
+
- file : bytes,从本地文件读取的原始字节内容
|
|
317
|
+
若 storage_type 未匹配任何已知类型,返回 None。
|
|
318
|
+
|
|
319
|
+
Raises:
|
|
320
|
+
FileNotFoundError: storage_type="file" 时,若本地文件不存在则抛出
|
|
321
|
+
"""
|
|
322
|
+
if self.storage_type == "ks3":
|
|
323
|
+
storage_config = self.storage_config or dict()
|
|
324
|
+
ks3 = Ks3Oss(bucket_name=self.bucket_name, **storage_config)
|
|
325
|
+
return ks3.get_file(key=key, bucket_name=bucket_name or self.bucket_name)
|
|
326
|
+
elif self.storage_type == "aliyun":
|
|
327
|
+
storage_config = self.storage_config or dict()
|
|
328
|
+
aliyun = AliYunOss(bucket_name=self.bucket_name, **storage_config)
|
|
329
|
+
return aliyun.get_file(key=key, bucket_name=bucket_name or self.bucket_name)
|
|
330
|
+
elif self.storage_type == "minio":
|
|
331
|
+
storage_config = self.storage_config or dict()
|
|
332
|
+
minio = MinioOss(**storage_config)
|
|
333
|
+
return minio.get_file(bucket_name=bucket_name or self.bucket_name, key=key)
|
|
334
|
+
elif self.storage_type == "rustfs":
|
|
335
|
+
storage_config = self.storage_config or dict()
|
|
336
|
+
rustfs = RustfsOss(**storage_config)
|
|
337
|
+
return rustfs.get_file(bucket_name=bucket_name or self.bucket_name, key=key)
|
|
338
|
+
if self.storage_type == "file":
|
|
339
|
+
storage_dir = self.storage_config.get("STORAGE_DIR", "")
|
|
340
|
+
if "\\" in key:
|
|
341
|
+
key_list = key.split("\\")
|
|
342
|
+
elif "/" in key:
|
|
343
|
+
key_list = key.split("/")
|
|
344
|
+
else:
|
|
345
|
+
key_list = [key]
|
|
346
|
+
file_path = storage_dir
|
|
347
|
+
if key_list:
|
|
348
|
+
for k in key_list:
|
|
349
|
+
file_path = os.path.join(file_path, k)
|
|
350
|
+
with open(file_path, 'rb') as f:
|
|
351
|
+
return f.read()
|
|
352
|
+
return None
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import logging
|
|
3
|
+
from datetime import timedelta
|
|
4
|
+
from io import BytesIO
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class RustfsOss:
|
|
8
|
+
"""
|
|
9
|
+
RustFS 对象存储客户端(基于 S3 兼容协议,使用 boto3)
|
|
10
|
+
RustFS is an S3-compatible object storage written in Rust.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, endpoint, access_key, secret_key, secure=False, region="us-east-1"):
|
|
14
|
+
try:
|
|
15
|
+
boto3 = importlib.import_module("boto3")
|
|
16
|
+
except ImportError:
|
|
17
|
+
raise Exception("boto3 is not installed, run: pip install boto3")
|
|
18
|
+
|
|
19
|
+
protocol = "https" if secure else "http"
|
|
20
|
+
endpoint_url = endpoint if endpoint.startswith("http") else f"{protocol}://{endpoint}"
|
|
21
|
+
|
|
22
|
+
self.endpoint_url = endpoint_url
|
|
23
|
+
self.region = region
|
|
24
|
+
self.client = boto3.client(
|
|
25
|
+
"s3",
|
|
26
|
+
endpoint_url=endpoint_url,
|
|
27
|
+
aws_access_key_id=access_key,
|
|
28
|
+
aws_secret_access_key=secret_key,
|
|
29
|
+
region_name=region,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
# ------------------------------------------------------------------
|
|
33
|
+
# Bucket operations
|
|
34
|
+
# ------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
def exists_bucket(self, bucket_name: str) -> bool:
|
|
37
|
+
"""判断桶是否存在"""
|
|
38
|
+
try:
|
|
39
|
+
self.client.head_bucket(Bucket=bucket_name)
|
|
40
|
+
return True
|
|
41
|
+
except Exception:
|
|
42
|
+
return False
|
|
43
|
+
|
|
44
|
+
def create_bucket(self, bucket_name: str, public_read: bool = True) -> bool:
|
|
45
|
+
"""创建桶,可选设置公共读策略"""
|
|
46
|
+
if self.exists_bucket(bucket_name):
|
|
47
|
+
return False
|
|
48
|
+
self.client.create_bucket(Bucket=bucket_name)
|
|
49
|
+
if public_read:
|
|
50
|
+
import json
|
|
51
|
+
policy = json.dumps({
|
|
52
|
+
"Version": "2012-10-17",
|
|
53
|
+
"Statement": [
|
|
54
|
+
{
|
|
55
|
+
"Effect": "Allow",
|
|
56
|
+
"Principal": {"AWS": ["*"]},
|
|
57
|
+
"Action": ["s3:GetObject"],
|
|
58
|
+
"Resource": [f"arn:aws:s3:::{bucket_name}/*"],
|
|
59
|
+
}
|
|
60
|
+
],
|
|
61
|
+
})
|
|
62
|
+
self.client.put_bucket_policy(Bucket=bucket_name, Policy=policy)
|
|
63
|
+
return True
|
|
64
|
+
|
|
65
|
+
def get_bucket_list(self) -> list:
|
|
66
|
+
"""列出所有桶"""
|
|
67
|
+
resp = self.client.list_buckets()
|
|
68
|
+
return [
|
|
69
|
+
{"bucket_name": b["Name"], "create_time": b.get("CreationDate")}
|
|
70
|
+
for b in resp.get("Buckets", [])
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
def remove_bucket(self, bucket_name: str) -> bool:
|
|
74
|
+
"""删除桶(桶需为空)"""
|
|
75
|
+
try:
|
|
76
|
+
self.client.delete_bucket(Bucket=bucket_name)
|
|
77
|
+
return True
|
|
78
|
+
except Exception as e:
|
|
79
|
+
logging.error(f"[RustfsOss] remove_bucket error: {e}")
|
|
80
|
+
return False
|
|
81
|
+
|
|
82
|
+
# ------------------------------------------------------------------
|
|
83
|
+
# Object operations
|
|
84
|
+
# ------------------------------------------------------------------
|
|
85
|
+
|
|
86
|
+
def bucket_list_files(self, bucket_name: str, prefix: str = "") -> list:
|
|
87
|
+
"""列出桶内所有对象"""
|
|
88
|
+
try:
|
|
89
|
+
paginator = self.client.get_paginator("list_objects_v2")
|
|
90
|
+
files = []
|
|
91
|
+
for page in paginator.paginate(Bucket=bucket_name, Prefix=prefix):
|
|
92
|
+
for obj in page.get("Contents", []):
|
|
93
|
+
files.append(obj)
|
|
94
|
+
return files
|
|
95
|
+
except Exception as e:
|
|
96
|
+
logging.error(f"[RustfsOss] bucket_list_files error: {e}")
|
|
97
|
+
return []
|
|
98
|
+
|
|
99
|
+
def save(self, key: str, bucket_name: str, data, content_type: str = "string", **kwargs) -> str:
|
|
100
|
+
"""
|
|
101
|
+
上传对象并返回访问 URL
|
|
102
|
+
:param key: 对象名(含路径)
|
|
103
|
+
:param bucket_name: 桶名
|
|
104
|
+
:param data: bytes(content_type="string")或本地文件路径(content_type="filename")
|
|
105
|
+
:param content_type: "string" | "filename"
|
|
106
|
+
:return: 公共访问 URL
|
|
107
|
+
"""
|
|
108
|
+
if content_type == "filename":
|
|
109
|
+
with open(data, "rb") as f:
|
|
110
|
+
data = f.read()
|
|
111
|
+
|
|
112
|
+
if isinstance(data, str):
|
|
113
|
+
data = data.encode("utf-8")
|
|
114
|
+
|
|
115
|
+
self.client.put_object(
|
|
116
|
+
Bucket=bucket_name,
|
|
117
|
+
Key=key,
|
|
118
|
+
Body=BytesIO(data),
|
|
119
|
+
**kwargs,
|
|
120
|
+
)
|
|
121
|
+
return f"{self.endpoint_url}/{bucket_name}/{key}"
|
|
122
|
+
|
|
123
|
+
def get_file(self, bucket_name: str, key: str):
|
|
124
|
+
"""下载对象,返回响应对象(含 .read() 方法)"""
|
|
125
|
+
resp = self.client.get_object(Bucket=bucket_name, Key=key)
|
|
126
|
+
return resp["Body"]
|
|
127
|
+
|
|
128
|
+
def download_file(self, bucket_name: str, key: str, file_path: str):
|
|
129
|
+
"""下载对象并保存到本地文件"""
|
|
130
|
+
self.client.download_file(Bucket=bucket_name, Key=key, Filename=file_path)
|
|
131
|
+
|
|
132
|
+
def upload_file(self, bucket_name: str, key: str, file_path: str, content_type: str = "application/octet-stream"):
|
|
133
|
+
"""从本地文件上传对象"""
|
|
134
|
+
with open(file_path, "rb") as f:
|
|
135
|
+
self.client.put_object(
|
|
136
|
+
Bucket=bucket_name,
|
|
137
|
+
Key=key,
|
|
138
|
+
Body=f,
|
|
139
|
+
ContentType=content_type,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
def remove_file(self, bucket_name: str, key: str):
|
|
143
|
+
"""删除单个对象"""
|
|
144
|
+
self.client.delete_object(Bucket=bucket_name, Key=key)
|
|
145
|
+
|
|
146
|
+
def remove_files(self, bucket_name: str, key_list: list):
|
|
147
|
+
"""批量删除对象"""
|
|
148
|
+
objects = [{"Key": k} for k in key_list]
|
|
149
|
+
self.client.delete_objects(Bucket=bucket_name, Delete={"Objects": objects})
|
|
150
|
+
|
|
151
|
+
def stat_object(self, bucket_name: str, key: str):
|
|
152
|
+
"""获取对象元数据"""
|
|
153
|
+
try:
|
|
154
|
+
return self.client.head_object(Bucket=bucket_name, Key=key)
|
|
155
|
+
except Exception as e:
|
|
156
|
+
logging.error(f"[RustfsOss] stat_object error: {e}")
|
|
157
|
+
return None
|
|
158
|
+
|
|
159
|
+
def copy_file(self, src_bucket: str, src_key: str, dst_bucket: str, dst_key: str):
|
|
160
|
+
"""复制对象"""
|
|
161
|
+
copy_source = {"Bucket": src_bucket, "Key": src_key}
|
|
162
|
+
self.client.copy_object(CopySource=copy_source, Bucket=dst_bucket, Key=dst_key)
|
|
163
|
+
|
|
164
|
+
def presigned_get_file(self, bucket_name: str, key: str, days: int = 7) -> str:
|
|
165
|
+
"""生成预签名 GET URL"""
|
|
166
|
+
return self.client.generate_presigned_url(
|
|
167
|
+
"get_object",
|
|
168
|
+
Params={"Bucket": bucket_name, "Key": key},
|
|
169
|
+
ExpiresIn=int(timedelta(days=days).total_seconds()),
|
|
170
|
+
)
|
|
@@ -2,6 +2,7 @@ import hashlib
|
|
|
2
2
|
import hmac
|
|
3
3
|
import ipaddress
|
|
4
4
|
import logging
|
|
5
|
+
import re
|
|
5
6
|
import time
|
|
6
7
|
|
|
7
8
|
from flask import request
|
|
@@ -70,13 +71,23 @@ def _canonical_sign_content(timestamp: str, nonce: str) -> str:
|
|
|
70
71
|
return f"{method}\n{path}\n{query_string}\n{body_hash}\n{timestamp}\n{nonce}"
|
|
71
72
|
|
|
72
73
|
|
|
74
|
+
def _match_sign_white_path(request_path: str, white_item: str) -> bool:
|
|
75
|
+
white_path = (white_item or "").strip()
|
|
76
|
+
if not white_path:
|
|
77
|
+
return False
|
|
78
|
+
if "*" not in white_path:
|
|
79
|
+
return request_path.startswith(white_path)
|
|
80
|
+
pattern = re.escape(white_path).replace(r"\*", ".*")
|
|
81
|
+
return re.match(f"^{pattern}", request_path) is not None
|
|
82
|
+
|
|
83
|
+
|
|
73
84
|
def _in_sign_white_list(white_list: list) -> bool:
|
|
74
|
-
#
|
|
85
|
+
# 白名单匹配规则:普通配置按前缀匹配;带 * 时支持任意内容通配。
|
|
75
86
|
if not white_list:
|
|
76
87
|
return False
|
|
77
88
|
request_path = request.path or ""
|
|
78
89
|
for white_item in white_list:
|
|
79
|
-
if
|
|
90
|
+
if _match_sign_white_path(request_path, white_item):
|
|
80
91
|
return True
|
|
81
92
|
return False
|
|
82
93
|
|
|
@@ -51,6 +51,7 @@ lesscode_flask/utils/oss/__init__.py
|
|
|
51
51
|
lesscode_flask/utils/oss/aliyun_oss.py
|
|
52
52
|
lesscode_flask/utils/oss/ks3_oss.py
|
|
53
53
|
lesscode_flask/utils/oss/minio_oss.py
|
|
54
|
+
lesscode_flask/utils/oss/rustfs_oss.py
|
|
54
55
|
lesscode_flask/utils/redis/redis_helper.py
|
|
55
56
|
lesscode_flask/utils/request/request.py
|
|
56
57
|
lesscode_flask/utils/sign/__init__.py
|
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
import copy
|
|
2
|
-
import os
|
|
3
|
-
from io import BytesIO
|
|
4
|
-
|
|
5
|
-
from lesscode_flask.utils.helpers import app_config
|
|
6
|
-
from lesscode_flask.utils.oss.aliyun_oss import AliYunOss
|
|
7
|
-
from lesscode_flask.utils.oss.ks3_oss import Ks3Oss
|
|
8
|
-
from lesscode_flask.utils.oss.minio_oss import MinioOss
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class CommonOss:
|
|
12
|
-
def __init__(self, storage_type=None, data_type="stream", config_key="default", **kwargs):
|
|
13
|
-
"""
|
|
14
|
-
初始化OSS
|
|
15
|
-
Args:
|
|
16
|
-
storage_type (str): 存储类型,目前支持ks3和file
|
|
17
|
-
data_type (str): 数据类型,目前支持stream和file_path
|
|
18
|
-
storage_config (dict): 存储配置,目前支持ks3和file,file_name,aliyun,minio
|
|
19
|
-
"""
|
|
20
|
-
storage_config_setting = app_config.get("STORAGE_CONFIG", {})
|
|
21
|
-
storage_config = copy.deepcopy(storage_config_setting) or dict()
|
|
22
|
-
_storage_config = storage_config.get(config_key, {}) or dict()
|
|
23
|
-
_storage_config_storage_type = _storage_config.get("storage_type", "")
|
|
24
|
-
_storage_type = storage_type if storage_type else _storage_config_storage_type
|
|
25
|
-
_config_storage_config = _storage_config.get("storage_config", {})
|
|
26
|
-
_config_bucket_name = _config_storage_config.pop("bucket_name", "")
|
|
27
|
-
self.storage_type = _storage_type
|
|
28
|
-
self.data_type = data_type
|
|
29
|
-
self.storage_config = kwargs.get("storage_config", {}) if kwargs.get("storage_config",
|
|
30
|
-
{}) else _config_storage_config
|
|
31
|
-
self.bucket_name = kwargs.get("bucket_name") or _config_bucket_name
|
|
32
|
-
|
|
33
|
-
def _save(self, key, io_stream: BytesIO = None, file_path: str = None, bucket_name: str = None):
|
|
34
|
-
file_url_obj = dict()
|
|
35
|
-
if self.storage_type == "ks3":
|
|
36
|
-
if self.data_type == "stream":
|
|
37
|
-
storage_config = self.storage_config or dict()
|
|
38
|
-
ks3 = Ks3Oss(bucket_name=bucket_name or self.bucket_name, **storage_config)
|
|
39
|
-
url = ks3.save(key=key, string_data=io_stream.getvalue(), content_type="string", policy="public-read",
|
|
40
|
-
bucket_name=bucket_name or self.bucket_name)
|
|
41
|
-
file_url_obj = {"key": key, "url": url}
|
|
42
|
-
elif self.data_type == "file_path":
|
|
43
|
-
storage_config = self.storage_config or dict()
|
|
44
|
-
ks3 = Ks3Oss(bucket_name=bucket_name or self.bucket_name, **storage_config)
|
|
45
|
-
url = ks3.save(key=key, filename=file_path, content_type="filename", policy="public-read")
|
|
46
|
-
file_url_obj = {"key": key, "url": url}
|
|
47
|
-
elif self.storage_type == "aliyun":
|
|
48
|
-
storage_config = self.storage_config or dict()
|
|
49
|
-
aliyun = AliYunOss(bucket_name=bucket_name or self.bucket_name, **storage_config)
|
|
50
|
-
url = aliyun.save(key=key, content_type="string", data=io_stream.getvalue(),
|
|
51
|
-
bucket_name=bucket_name or self.bucket_name)
|
|
52
|
-
file_url_obj = {"key": key, "url": url}
|
|
53
|
-
elif self.storage_type == "minio":
|
|
54
|
-
storage_config = self.storage_config or dict()
|
|
55
|
-
minio = MinioOss(**storage_config)
|
|
56
|
-
url = minio.save(key=key, content_type="string", data=io_stream.getvalue(),
|
|
57
|
-
bucket_name=bucket_name or self.bucket_name)
|
|
58
|
-
file_url_obj = {"key": key, "url": url}
|
|
59
|
-
elif self.storage_type == "file":
|
|
60
|
-
storage_path = ""
|
|
61
|
-
storage_dir = self.storage_config.get("STORAGE_DIR", "")
|
|
62
|
-
if not storage_dir:
|
|
63
|
-
raise Exception("storage_dir is empty")
|
|
64
|
-
if self.data_type == "stream":
|
|
65
|
-
if "\\" in key:
|
|
66
|
-
key_list = key.split("\\")
|
|
67
|
-
elif "/" in key:
|
|
68
|
-
key_list = key.split("/")
|
|
69
|
-
else:
|
|
70
|
-
key_list = [key]
|
|
71
|
-
storage_path = storage_dir
|
|
72
|
-
if key_list:
|
|
73
|
-
for k in key_list:
|
|
74
|
-
storage_path = os.path.join(storage_path, k)
|
|
75
|
-
dir_path = os.path.dirname(storage_path)
|
|
76
|
-
if not os.path.exists(dir_path):
|
|
77
|
-
os.makedirs(dir_path)
|
|
78
|
-
elif self.data_type == "file_path":
|
|
79
|
-
with open(file_path, 'rb') as infile:
|
|
80
|
-
io_stream = BytesIO(infile.read())
|
|
81
|
-
if storage_path:
|
|
82
|
-
with open(storage_path, 'wb') as outfile:
|
|
83
|
-
outfile.write(io_stream.getvalue())
|
|
84
|
-
file_url_obj = {"key": key, "url": storage_path}
|
|
85
|
-
return file_url_obj
|
|
86
|
-
|
|
87
|
-
def upload(self, **kwargs):
|
|
88
|
-
"""上传文件
|
|
89
|
-
Args:
|
|
90
|
-
files (list): 文件列表 可以是文件流列表,也可以是字典列表,字典格式为{"key":"文件key,可以是带路径的文件","stream":"文件流"}
|
|
91
|
-
Returns:
|
|
92
|
-
file_url_list: 文件url列表[{"key":"文件key,可以是带路径的文件","url":"本地文件存储的文件的全路径,对象存储,存放的是文件的下载地址"}]
|
|
93
|
-
"""
|
|
94
|
-
file_url_list = []
|
|
95
|
-
files = kwargs.get("files", [])
|
|
96
|
-
bucket_name = kwargs.get("bucket_name", self.bucket_name)
|
|
97
|
-
if not files:
|
|
98
|
-
raise Exception("files is empty")
|
|
99
|
-
if self.data_type == "stream":
|
|
100
|
-
for f in files:
|
|
101
|
-
if not isinstance(f, dict):
|
|
102
|
-
key = f.filename
|
|
103
|
-
stream = f.stream.read()
|
|
104
|
-
file_stream = BytesIO(stream)
|
|
105
|
-
else:
|
|
106
|
-
key = f.get("key", "")
|
|
107
|
-
_steam = f.get("stream")
|
|
108
|
-
stream = _steam.stream.read()
|
|
109
|
-
file_stream = BytesIO(stream)
|
|
110
|
-
file_url_obj = self._save(key=key, io_stream=file_stream, bucket_name=bucket_name)
|
|
111
|
-
file_url_list.append(file_url_obj)
|
|
112
|
-
|
|
113
|
-
elif self.data_type == "file_path":
|
|
114
|
-
for f in files:
|
|
115
|
-
if not isinstance(f, dict):
|
|
116
|
-
if "\\" in f:
|
|
117
|
-
file_name = f.split("\\")[-1]
|
|
118
|
-
elif "/" in f:
|
|
119
|
-
file_name = f.split("/")[-1]
|
|
120
|
-
else:
|
|
121
|
-
file_name = f
|
|
122
|
-
key = file_name
|
|
123
|
-
_file_path = f
|
|
124
|
-
else:
|
|
125
|
-
key = f.get("key", "")
|
|
126
|
-
_file_path = f.get("file_path")
|
|
127
|
-
file_url_obj = self._save(key=key, file_path=_file_path, bucket_name=bucket_name)
|
|
128
|
-
file_url_list.append(file_url_obj)
|
|
129
|
-
return file_url_list
|
|
130
|
-
|
|
131
|
-
def download(self, key, bucket_name=None):
|
|
132
|
-
"""下载文件,返回文件流
|
|
133
|
-
Args:
|
|
134
|
-
key (str): 上面接口返回的文件key
|
|
135
|
-
Returns:
|
|
136
|
-
file_stream: 文件流
|
|
137
|
-
:param bucket_name:
|
|
138
|
-
"""
|
|
139
|
-
if self.storage_type == "ks3":
|
|
140
|
-
storage_config = self.storage_config or dict()
|
|
141
|
-
ks3 = Ks3Oss(bucket_name=self.bucket_name, **storage_config)
|
|
142
|
-
return ks3.get_file(key=key, bucket_name=bucket_name or self.bucket_name)
|
|
143
|
-
elif self.storage_type == "aliyun":
|
|
144
|
-
storage_config = self.storage_config or dict()
|
|
145
|
-
aliyun = AliYunOss(bucket_name=self.bucket_name, **storage_config)
|
|
146
|
-
return aliyun.get_file(key=key, bucket_name=bucket_name or self.bucket_name)
|
|
147
|
-
elif self.storage_type == "minio":
|
|
148
|
-
storage_config = self.storage_config or dict()
|
|
149
|
-
minio = MinioOss(**storage_config)
|
|
150
|
-
return minio.get_file(bucket_name=bucket_name or self.bucket_name, key=key)
|
|
151
|
-
if self.storage_type == "file":
|
|
152
|
-
storage_dir = self.storage_config.get("STORAGE_DIR", "")
|
|
153
|
-
if "\\" in key:
|
|
154
|
-
key_list = key.split("\\")
|
|
155
|
-
elif "/" in key:
|
|
156
|
-
key_list = key.split("/")
|
|
157
|
-
else:
|
|
158
|
-
key_list = [key]
|
|
159
|
-
file_path = storage_dir
|
|
160
|
-
if key_list:
|
|
161
|
-
for k in key_list:
|
|
162
|
-
file_path = os.path.join(file_path, k)
|
|
163
|
-
with open(file_path, 'rb') as f:
|
|
164
|
-
return f.read()
|
|
165
|
-
return None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/model/parameterized_query.py
RENAMED
|
File without changes
|
{lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/model/resource_param_template.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/service/access_log_service.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/utils/decorator/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/utils/decorator/sql_injection.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/utils/file/file_exporter.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/utils/json/NotSortJSONProvider.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/utils/redis/redis_helper.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/utils/sign/body_canonical.py
RENAMED
|
File without changes
|
|
File without changes
|
{lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/utils/swagger/swagger_template.py
RENAMED
|
File without changes
|
{lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/utils/swagger/swagger_util.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask/utils/thread/thread_utils.py
RENAMED
|
File without changes
|
|
File without changes
|
{lesscode_flask-0.2.115 → lesscode_flask-0.2.117}/lesscode_flask.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|