mdbq 4.2.9__py3-none-any.whl → 4.2.11__py3-none-any.whl
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.
Potentially problematic release.
This version of mdbq might be problematic. Click here for more details.
- mdbq/__version__.py +1 -1
- mdbq/auth/crypto.py +41 -7
- mdbq/redis/redis_cache.py +288 -1
- {mdbq-4.2.9.dist-info → mdbq-4.2.11.dist-info}/METADATA +2 -2
- {mdbq-4.2.9.dist-info → mdbq-4.2.11.dist-info}/RECORD +7 -7
- {mdbq-4.2.9.dist-info → mdbq-4.2.11.dist-info}/WHEEL +1 -1
- {mdbq-4.2.9.dist-info → mdbq-4.2.11.dist-info}/top_level.txt +0 -0
mdbq/__version__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
VERSION = '4.2.
|
|
1
|
+
VERSION = '4.2.11'
|
mdbq/auth/crypto.py
CHANGED
|
@@ -175,8 +175,29 @@ class CryptoService:
|
|
|
175
175
|
def decrypt_token(self, encrypted_token: str) -> Optional[Dict[str, Any]]:
|
|
176
176
|
"""解密令牌"""
|
|
177
177
|
try:
|
|
178
|
+
# 验证输入参数
|
|
179
|
+
if not encrypted_token or not isinstance(encrypted_token, str):
|
|
180
|
+
self.logger.error("无效的加密令牌")
|
|
181
|
+
return None
|
|
182
|
+
|
|
178
183
|
# 解析加密数据
|
|
179
|
-
|
|
184
|
+
try:
|
|
185
|
+
encrypted_data = json.loads(base64.b64decode(encrypted_token))
|
|
186
|
+
except (json.JSONDecodeError, ValueError, TypeError) as e:
|
|
187
|
+
self.logger.error(f"解析加密数据失败: {str(e)}")
|
|
188
|
+
return None
|
|
189
|
+
|
|
190
|
+
# 验证数据类型
|
|
191
|
+
if not isinstance(encrypted_data, dict):
|
|
192
|
+
self.logger.error("加密数据格式错误,应为字典类型")
|
|
193
|
+
return None
|
|
194
|
+
|
|
195
|
+
# 验证必要的字段是否存在
|
|
196
|
+
required_fields = ['key', 'iv', 'ciphertext']
|
|
197
|
+
for field in required_fields:
|
|
198
|
+
if field not in encrypted_data:
|
|
199
|
+
self.logger.error(f"加密数据缺少必要字段: {field}")
|
|
200
|
+
return None
|
|
180
201
|
|
|
181
202
|
# 获取私钥
|
|
182
203
|
private_key = self.key_manager.get_private_key()
|
|
@@ -185,7 +206,11 @@ class CryptoService:
|
|
|
185
206
|
return None
|
|
186
207
|
|
|
187
208
|
# 解密AES密钥
|
|
188
|
-
|
|
209
|
+
try:
|
|
210
|
+
encrypted_aes_key = base64.b64decode(encrypted_data['key'])
|
|
211
|
+
except (ValueError, TypeError) as e:
|
|
212
|
+
self.logger.error(f"解码AES密钥失败: {str(e)}")
|
|
213
|
+
return None
|
|
189
214
|
|
|
190
215
|
# 使用SHA-512加密算法
|
|
191
216
|
try:
|
|
@@ -199,16 +224,25 @@ class CryptoService:
|
|
|
199
224
|
)
|
|
200
225
|
except Exception as decrypt_error:
|
|
201
226
|
self.logger.error("RSA解密失败", {'error': str(decrypt_error)})
|
|
227
|
+
return None
|
|
202
228
|
|
|
203
229
|
# 解密数据
|
|
204
|
-
|
|
205
|
-
|
|
230
|
+
try:
|
|
231
|
+
iv = base64.b64decode(encrypted_data['iv'])
|
|
232
|
+
ciphertext = base64.b64decode(encrypted_data['ciphertext'])
|
|
233
|
+
except (ValueError, TypeError) as e:
|
|
234
|
+
self.logger.error(f"解码IV或密文失败: {str(e)}")
|
|
235
|
+
return None
|
|
206
236
|
|
|
207
237
|
# 检查是否有认证标签(AES-GCM需要)
|
|
208
238
|
if 'tag' in encrypted_data:
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
239
|
+
try:
|
|
240
|
+
tag = base64.b64decode(encrypted_data['tag'])
|
|
241
|
+
# 将tag附加到密文末尾(AES-GCM标准做法)
|
|
242
|
+
ciphertext_with_tag = ciphertext + tag
|
|
243
|
+
except (ValueError, TypeError) as e:
|
|
244
|
+
self.logger.error(f"解码认证标签失败: {str(e)}")
|
|
245
|
+
return None
|
|
212
246
|
else:
|
|
213
247
|
# 如果没有tag,假设密文已经包含tag
|
|
214
248
|
ciphertext_with_tag = ciphertext
|
mdbq/redis/redis_cache.py
CHANGED
|
@@ -879,4 +879,291 @@ class CacheManager:
|
|
|
879
879
|
|
|
880
880
|
|
|
881
881
|
# 全局缓存管理器实例
|
|
882
|
-
cache_manager = CacheManager()
|
|
882
|
+
cache_manager = CacheManager()
|
|
883
|
+
|
|
884
|
+
|
|
885
|
+
# ===== 装饰器功能 =====
|
|
886
|
+
|
|
887
|
+
def flask_redis_cache(cache_key_func=None, ttl=1200, namespace="default",
|
|
888
|
+
data_validator=None, skip_cache_on_error=True):
|
|
889
|
+
"""
|
|
890
|
+
Flask路由函数的Redis缓存装饰器
|
|
891
|
+
|
|
892
|
+
Args:
|
|
893
|
+
cache_key_func: 缓存键生成函数,接收请求数据作为参数,返回缓存键字符串
|
|
894
|
+
如果为None,则使用默认的键生成策略
|
|
895
|
+
ttl: 缓存过期时间(秒),默认20分钟
|
|
896
|
+
namespace: 缓存命名空间,默认为"default"
|
|
897
|
+
data_validator: 数据验证函数,用于验证数据是否应该被缓存
|
|
898
|
+
skip_cache_on_error: 当缓存操作出错时是否跳过缓存,默认True
|
|
899
|
+
|
|
900
|
+
Usage:
|
|
901
|
+
@flask_redis_cache(
|
|
902
|
+
cache_key_func=lambda data: f"tables_{data.get('database', 'unknown')}",
|
|
903
|
+
ttl=1200,
|
|
904
|
+
namespace="sycm_tables"
|
|
905
|
+
)
|
|
906
|
+
def my_flask_route():
|
|
907
|
+
pass
|
|
908
|
+
"""
|
|
909
|
+
def decorator(func):
|
|
910
|
+
import functools
|
|
911
|
+
import hashlib
|
|
912
|
+
|
|
913
|
+
@functools.wraps(func)
|
|
914
|
+
def wrapper(*args, **kwargs):
|
|
915
|
+
# 导入Flask相关模块(延迟导入避免依赖问题)
|
|
916
|
+
try:
|
|
917
|
+
from flask import request, jsonify
|
|
918
|
+
except ImportError:
|
|
919
|
+
# 如果没有Flask环境,直接执行原函数
|
|
920
|
+
return func(*args, **kwargs)
|
|
921
|
+
|
|
922
|
+
# 获取缓存系统
|
|
923
|
+
cache_system = cache_manager.get_cache()
|
|
924
|
+
|
|
925
|
+
# 如果缓存系统不可用,直接执行原函数
|
|
926
|
+
if not cache_system:
|
|
927
|
+
return func(*args, **kwargs)
|
|
928
|
+
|
|
929
|
+
try:
|
|
930
|
+
# 获取请求数据用于生成缓存键
|
|
931
|
+
request_data = {}
|
|
932
|
+
if request.method == 'POST':
|
|
933
|
+
try:
|
|
934
|
+
request_data = request.get_json() or {}
|
|
935
|
+
except Exception:
|
|
936
|
+
request_data = {}
|
|
937
|
+
elif request.method == 'GET':
|
|
938
|
+
request_data = dict(request.args)
|
|
939
|
+
|
|
940
|
+
# 生成缓存键
|
|
941
|
+
if cache_key_func:
|
|
942
|
+
cache_key = cache_key_func(request_data)
|
|
943
|
+
else:
|
|
944
|
+
# 默认缓存键生成策略
|
|
945
|
+
func_name = func.__name__
|
|
946
|
+
# 将请求数据转换为字符串并生成哈希
|
|
947
|
+
data_str = str(sorted(request_data.items()))
|
|
948
|
+
data_hash = hashlib.md5(data_str.encode()).hexdigest()[:8]
|
|
949
|
+
cache_key = f"{func_name}_{data_hash}"
|
|
950
|
+
|
|
951
|
+
# 尝试从缓存获取数据
|
|
952
|
+
try:
|
|
953
|
+
cached_result = cache_system.get(cache_key, namespace)
|
|
954
|
+
if cached_result is not None:
|
|
955
|
+
return jsonify(cached_result)
|
|
956
|
+
except Exception as e:
|
|
957
|
+
if not skip_cache_on_error:
|
|
958
|
+
raise
|
|
959
|
+
|
|
960
|
+
# 缓存未命中,执行原函数
|
|
961
|
+
result = func(*args, **kwargs)
|
|
962
|
+
|
|
963
|
+
# 如果结果是Flask Response对象,提取JSON数据进行缓存
|
|
964
|
+
if hasattr(result, 'get_json'):
|
|
965
|
+
try:
|
|
966
|
+
response_data = result.get_json()
|
|
967
|
+
if response_data:
|
|
968
|
+
# 使用安全缓存写入
|
|
969
|
+
_safe_cache_set(
|
|
970
|
+
cache_system=cache_system,
|
|
971
|
+
cache_key=cache_key,
|
|
972
|
+
response_data=response_data,
|
|
973
|
+
ttl=ttl,
|
|
974
|
+
namespace=namespace,
|
|
975
|
+
data_validator=data_validator
|
|
976
|
+
)
|
|
977
|
+
except Exception as e:
|
|
978
|
+
if not skip_cache_on_error:
|
|
979
|
+
raise
|
|
980
|
+
elif isinstance(result, tuple) and len(result) == 2:
|
|
981
|
+
# 处理 (response, status_code) 格式的返回值
|
|
982
|
+
try:
|
|
983
|
+
response_data, status_code = result
|
|
984
|
+
if hasattr(response_data, 'get_json'):
|
|
985
|
+
json_data = response_data.get_json()
|
|
986
|
+
elif isinstance(response_data, dict):
|
|
987
|
+
json_data = response_data
|
|
988
|
+
else:
|
|
989
|
+
json_data = None
|
|
990
|
+
|
|
991
|
+
if json_data and status_code == 200:
|
|
992
|
+
_safe_cache_set(
|
|
993
|
+
cache_system=cache_system,
|
|
994
|
+
cache_key=cache_key,
|
|
995
|
+
response_data=json_data,
|
|
996
|
+
ttl=ttl,
|
|
997
|
+
namespace=namespace,
|
|
998
|
+
data_validator=data_validator
|
|
999
|
+
)
|
|
1000
|
+
except Exception as e:
|
|
1001
|
+
if not skip_cache_on_error:
|
|
1002
|
+
raise
|
|
1003
|
+
|
|
1004
|
+
return result
|
|
1005
|
+
|
|
1006
|
+
except Exception as e:
|
|
1007
|
+
if not skip_cache_on_error:
|
|
1008
|
+
raise
|
|
1009
|
+
return func(*args, **kwargs)
|
|
1010
|
+
|
|
1011
|
+
return wrapper
|
|
1012
|
+
return decorator
|
|
1013
|
+
|
|
1014
|
+
|
|
1015
|
+
def function_redis_cache(cache_key_func=None, ttl=1800, namespace="default",
|
|
1016
|
+
skip_cache_on_error=True):
|
|
1017
|
+
"""
|
|
1018
|
+
普通函数的Redis缓存装饰器
|
|
1019
|
+
|
|
1020
|
+
Args:
|
|
1021
|
+
cache_key_func: 缓存键生成函数,接收函数参数作为输入,返回缓存键字符串
|
|
1022
|
+
如果为None,则使用默认的键生成策略
|
|
1023
|
+
ttl: 缓存过期时间(秒),默认30分钟
|
|
1024
|
+
namespace: 缓存命名空间,默认为"default"
|
|
1025
|
+
skip_cache_on_error: 当缓存操作出错时是否跳过缓存,默认True
|
|
1026
|
+
|
|
1027
|
+
Usage:
|
|
1028
|
+
@function_redis_cache(
|
|
1029
|
+
cache_key_func=lambda _key, shop_name: f"cookies_{_key}_{shop_name}",
|
|
1030
|
+
ttl=1800,
|
|
1031
|
+
namespace="cookies_cache"
|
|
1032
|
+
)
|
|
1033
|
+
def my_function(_key, shop_name):
|
|
1034
|
+
pass
|
|
1035
|
+
"""
|
|
1036
|
+
def decorator(func):
|
|
1037
|
+
import functools
|
|
1038
|
+
import inspect
|
|
1039
|
+
import hashlib
|
|
1040
|
+
|
|
1041
|
+
@functools.wraps(func)
|
|
1042
|
+
def wrapper(*args, **kwargs):
|
|
1043
|
+
# 获取缓存系统
|
|
1044
|
+
cache_system = cache_manager.get_cache()
|
|
1045
|
+
|
|
1046
|
+
# 如果缓存系统不可用,直接执行原函数
|
|
1047
|
+
if not cache_system:
|
|
1048
|
+
return func(*args, **kwargs)
|
|
1049
|
+
|
|
1050
|
+
try:
|
|
1051
|
+
# 获取函数签名和参数
|
|
1052
|
+
sig = inspect.signature(func)
|
|
1053
|
+
bound_args = sig.bind(*args, **kwargs)
|
|
1054
|
+
bound_args.apply_defaults()
|
|
1055
|
+
|
|
1056
|
+
# 生成缓存键
|
|
1057
|
+
if cache_key_func:
|
|
1058
|
+
cache_key = cache_key_func(*args, **kwargs)
|
|
1059
|
+
else:
|
|
1060
|
+
# 默认缓存键生成策略
|
|
1061
|
+
func_name = func.__name__
|
|
1062
|
+
# 将参数转换为字符串并生成哈希
|
|
1063
|
+
args_str = str(args) + str(sorted(kwargs.items()))
|
|
1064
|
+
args_hash = hashlib.md5(args_str.encode()).hexdigest()[:8]
|
|
1065
|
+
cache_key = f"{func_name}_{args_hash}"
|
|
1066
|
+
|
|
1067
|
+
# 尝试从缓存获取数据
|
|
1068
|
+
try:
|
|
1069
|
+
cached_result = cache_system.get(cache_key, namespace)
|
|
1070
|
+
if cached_result is not None:
|
|
1071
|
+
return cached_result
|
|
1072
|
+
except Exception as e:
|
|
1073
|
+
if not skip_cache_on_error:
|
|
1074
|
+
raise
|
|
1075
|
+
|
|
1076
|
+
# 缓存未命中,执行原函数
|
|
1077
|
+
result = func(*args, **kwargs)
|
|
1078
|
+
|
|
1079
|
+
# 缓存结果(只缓存非空结果)
|
|
1080
|
+
if result is not None and result != {} and result != []:
|
|
1081
|
+
try:
|
|
1082
|
+
cache_system.set(cache_key, result, ttl=ttl, namespace=namespace)
|
|
1083
|
+
except Exception as e:
|
|
1084
|
+
if not skip_cache_on_error:
|
|
1085
|
+
raise
|
|
1086
|
+
|
|
1087
|
+
return result
|
|
1088
|
+
|
|
1089
|
+
except Exception as e:
|
|
1090
|
+
if not skip_cache_on_error:
|
|
1091
|
+
raise
|
|
1092
|
+
return func(*args, **kwargs)
|
|
1093
|
+
|
|
1094
|
+
return wrapper
|
|
1095
|
+
return decorator
|
|
1096
|
+
|
|
1097
|
+
|
|
1098
|
+
def _safe_cache_set(cache_system, cache_key, response_data, ttl, namespace,
|
|
1099
|
+
data_validator=None):
|
|
1100
|
+
"""
|
|
1101
|
+
安全的缓存写入函数,只有数据有效时才写入缓存。
|
|
1102
|
+
|
|
1103
|
+
Args:
|
|
1104
|
+
cache_system: 缓存系统实例
|
|
1105
|
+
cache_key: 缓存键
|
|
1106
|
+
response_data: 要缓存的响应数据
|
|
1107
|
+
ttl: 缓存过期时间
|
|
1108
|
+
namespace: 缓存命名空间
|
|
1109
|
+
data_validator: 数据验证函数,返回True表示数据有效
|
|
1110
|
+
|
|
1111
|
+
Returns:
|
|
1112
|
+
bool: 是否成功写入缓存
|
|
1113
|
+
"""
|
|
1114
|
+
if not cache_system:
|
|
1115
|
+
return False
|
|
1116
|
+
|
|
1117
|
+
# 默认验证逻辑:检查响应数据结构
|
|
1118
|
+
if data_validator is None:
|
|
1119
|
+
def default_validator(data):
|
|
1120
|
+
if not isinstance(data, dict):
|
|
1121
|
+
return False
|
|
1122
|
+
|
|
1123
|
+
# 更宽松的验证逻辑,支持多种响应格式
|
|
1124
|
+
# 检查状态字段(支持多种成功状态格式)
|
|
1125
|
+
status_ok = (
|
|
1126
|
+
data.get('status') == 'success' or # 新格式
|
|
1127
|
+
data.get('code') == 0 or # 旧格式
|
|
1128
|
+
data.get('code') == 200 # HTTP状态码格式
|
|
1129
|
+
)
|
|
1130
|
+
|
|
1131
|
+
if not status_ok:
|
|
1132
|
+
return False
|
|
1133
|
+
|
|
1134
|
+
# 检查 message 字段是否包含"失败"字样,如果包含则跳过缓存
|
|
1135
|
+
message = data.get('message', '')
|
|
1136
|
+
if isinstance(message, str) and ('失败' in message or 'error' in message or 'fail' in message):
|
|
1137
|
+
return False
|
|
1138
|
+
|
|
1139
|
+
# 检查数据部分(支持多种数据字段名)
|
|
1140
|
+
has_data_fields = (
|
|
1141
|
+
'data' in data or # 标准data字段
|
|
1142
|
+
'logs' in data or # 更新日志专用
|
|
1143
|
+
'announcements' in data or # 公告数据
|
|
1144
|
+
'databases' in data or # 数据库列表
|
|
1145
|
+
'tables' in data or # 表列表
|
|
1146
|
+
'rows' in data or # 数据行
|
|
1147
|
+
'message' in data # 包含消息即认为有数据
|
|
1148
|
+
)
|
|
1149
|
+
|
|
1150
|
+
# 如果有数据字段,基本认为有效
|
|
1151
|
+
return has_data_fields
|
|
1152
|
+
|
|
1153
|
+
data_validator = default_validator
|
|
1154
|
+
|
|
1155
|
+
# 验证数据
|
|
1156
|
+
try:
|
|
1157
|
+
is_valid = data_validator(response_data)
|
|
1158
|
+
except Exception:
|
|
1159
|
+
return False
|
|
1160
|
+
|
|
1161
|
+
if is_valid:
|
|
1162
|
+
try:
|
|
1163
|
+
cache_system.set(cache_key, response_data, ttl=ttl, namespace=namespace)
|
|
1164
|
+
return True
|
|
1165
|
+
except Exception:
|
|
1166
|
+
return False
|
|
1167
|
+
else:
|
|
1168
|
+
return False
|
|
1169
|
+
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
mdbq/__init__.py,sha256=Il5Q9ATdX8yXqVxtP_nYqUhExzxPC_qk_WXQ_4h0exg,16
|
|
2
|
-
mdbq/__version__.py,sha256=
|
|
2
|
+
mdbq/__version__.py,sha256=MqhMtZv7l-q_wRQzpu6U2VaOi2Ddz7_1SDL7MMVF9vs,18
|
|
3
3
|
mdbq/auth/__init__.py,sha256=pnPMAt63sh1B6kEvmutUuro46zVf2v2YDAG7q-jV_To,24
|
|
4
4
|
mdbq/auth/auth_backend.py,sha256=iLN7AqiSq7fQgFtNtge_TIlVOR1hrCSZXH6oId6uGX4,116924
|
|
5
|
-
mdbq/auth/crypto.py,sha256=
|
|
5
|
+
mdbq/auth/crypto.py,sha256=M0i4dRljJuE30WH_13ythA2QGKPXZm6TgpnYp6aHOzw,17431
|
|
6
6
|
mdbq/auth/rate_limiter.py,sha256=1m_Paxp8pDNpmyoFGRpFMVOJpbmeIvfVcfiQ2oH72qM,32850
|
|
7
7
|
mdbq/js/__init__.py,sha256=hpMi3_ZKwIWkzc0LnKL-SY9AS-7PYFHq0izYTgEvxjc,30
|
|
8
8
|
mdbq/js/jc.py,sha256=6Rgf1WqaJJ1oevpn-pt08gXKbX5hjoQaV6uZGCAGbYw,13177
|
|
@@ -27,7 +27,7 @@ mdbq/pbix/pbix_refresh.py,sha256=JUjKW3bNEyoMVfVfo77UhguvS5AWkixvVhDbw4_MHco,239
|
|
|
27
27
|
mdbq/pbix/refresh_all.py,sha256=OBT9EewSZ0aRS9vL_FflVn74d4l2G00wzHiikCC4TC0,5926
|
|
28
28
|
mdbq/redis/__init__.py,sha256=YtgBlVSMDphtpwYX248wGge1x-Ex_mMufz4-8W0XRmA,12
|
|
29
29
|
mdbq/redis/getredis.py,sha256=vdg7YQEjhoMp5QzxygNGx5DQKRnePrcwPYgUrDypA6g,23672
|
|
30
|
-
mdbq/redis/redis_cache.py,sha256=
|
|
30
|
+
mdbq/redis/redis_cache.py,sha256=JWarX_l7LvdKyxtUNPANAqd-y20Jg5uqmllCbT-fyv8,45752
|
|
31
31
|
mdbq/route/__init__.py,sha256=BT_dAY7V-U2o72bevq1B9Mq9QA7GodwtkxyLNdGaoE8,22
|
|
32
32
|
mdbq/route/analytics.py,sha256=dngj5hVwKddEUy59nSYbOoJ9C7OVrtCmCkvW6Uj9RYM,28097
|
|
33
33
|
mdbq/route/monitor.py,sha256=lyowGUU8c2GykeZLrdxd7nXpNMqXWcOsuQsbS8l0pwU,36595
|
|
@@ -35,7 +35,7 @@ mdbq/route/routes.py,sha256=QVGfTvDgu0CpcKCvk1ra74H8uojgqTLUav1fnVAqLEA,29433
|
|
|
35
35
|
mdbq/selenium/__init__.py,sha256=AKzeEceqZyvqn2dEDoJSzDQnbuENkJSHAlbHAD0u0ZI,10
|
|
36
36
|
mdbq/selenium/get_driver.py,sha256=1NTlVUE6QsyjTrVVVqTO2LOnYf578ccFWlWnvIXGtic,20903
|
|
37
37
|
mdbq/spider/__init__.py,sha256=RBMFXGy_jd1HXZhngB2T2XTvJqki8P_Fr-pBcwijnew,18
|
|
38
|
-
mdbq-4.2.
|
|
39
|
-
mdbq-4.2.
|
|
40
|
-
mdbq-4.2.
|
|
41
|
-
mdbq-4.2.
|
|
38
|
+
mdbq-4.2.11.dist-info/METADATA,sha256=HjK2Bl76B_V2Xpxh0lUkdAfk3thD2VXAmCAgkh23pWk,364
|
|
39
|
+
mdbq-4.2.11.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
40
|
+
mdbq-4.2.11.dist-info/top_level.txt,sha256=2FQ-uLnCSB-OwFiWntzmwosW3X2Xqsg0ewh1axsaylA,5
|
|
41
|
+
mdbq-4.2.11.dist-info/RECORD,,
|
|
File without changes
|