springbootAI 1.8.0__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.
- spring/__init__.py +66 -0
- spring/ai/__init__.py +78 -0
- spring/ai/advisors.py +139 -0
- spring/ai/annotations.py +74 -0
- spring/ai/autoconfig.py +481 -0
- spring/ai/core.py +391 -0
- spring/ai/etl.py +188 -0
- spring/ai/memory.py +109 -0
- spring/ai/observability.py +129 -0
- spring/ai/providers.py +789 -0
- spring/ai/resilience.py +258 -0
- spring/ai/tools.py +106 -0
- spring/ai/vectorstore.py +303 -0
- spring/annotations/__init__.py +188 -0
- spring/annotations/cache.py +126 -0
- spring/annotations/cloud.py +207 -0
- spring/annotations/conditional.py +272 -0
- spring/annotations/core.py +864 -0
- spring/annotations/messaging.py +107 -0
- spring/aop/__init__.py +4 -0
- spring/aop/cloud_aop.py +404 -0
- spring/aop/comprehensive_aop.py +1015 -0
- spring/aop/method_interceptor.py +19 -0
- spring/aop/proxy_factory.py +55 -0
- spring/cloud/__init__.py +76 -0
- spring/cloud/discovery.py +364 -0
- spring/cloud/feign.py +469 -0
- spring/cloud/gateway.py +452 -0
- spring/cloud/load_balancer.py +149 -0
- spring/cloud/seata.py +557 -0
- spring/cloud/sentinel.py +525 -0
- spring/cloud/tracer.py +337 -0
- spring/config/__init__.py +21 -0
- spring/config/binding.py +206 -0
- spring/config/config_loader.py +405 -0
- spring/context/__init__.py +13 -0
- spring/context/application_context.py +589 -0
- spring/context/bean_definition.py +70 -0
- spring/context/bean_factory.py +1052 -0
- spring/context/registry.py +58 -0
- spring/context/scanner.py +106 -0
- spring/core/__init__.py +3 -0
- spring/core/graceful_shutdown.py +196 -0
- spring/core/typing_utils.py +50 -0
- spring/csv/__init__.py +52 -0
- spring/csv/annotations.py +402 -0
- spring/csv/converters.py +69 -0
- spring/csv/easy_csv.py +95 -0
- spring/csv/exceptions.py +27 -0
- spring/csv/reader.py +195 -0
- spring/csv/writer.py +155 -0
- spring/data/__init__.py +54 -0
- spring/data/page.py +181 -0
- spring/data/repository.py +274 -0
- spring/data/specification.py +228 -0
- spring/datasource/__init__.py +66 -0
- spring/datasource/annotations.py +133 -0
- spring/datasource/context.py +69 -0
- spring/datasource/dynamic.py +148 -0
- spring/event/__init__.py +7 -0
- spring/event/publisher.py +69 -0
- spring/excel/__init__.py +51 -0
- spring/excel/annotations.py +405 -0
- spring/excel/converters.py +231 -0
- spring/excel/easy_excel.py +94 -0
- spring/excel/exceptions.py +31 -0
- spring/excel/reader.py +254 -0
- spring/excel/style.py +95 -0
- spring/excel/writer.py +197 -0
- spring/i18n/__init__.py +97 -0
- spring/i18n/accessor.py +94 -0
- spring/i18n/auto_config.py +177 -0
- spring/i18n/holder.py +106 -0
- spring/i18n/locale.py +152 -0
- spring/i18n/locale_resolver.py +367 -0
- spring/i18n/message_source.py +250 -0
- spring/i18n/middleware.py +79 -0
- spring/i18n/properties.py +168 -0
- spring/i18n/sources.py +255 -0
- spring/logging/__init__.py +1 -0
- spring/logging/loguru_logger.py +228 -0
- spring/main.py +378 -0
- spring/messaging/__init__.py +1 -0
- spring/messaging/rabbitmq.py +302 -0
- spring/monitoring/__init__.py +1 -0
- spring/monitoring/prometheus.py +199 -0
- spring/orm/__init__.py +258 -0
- spring/orm/database.py +222 -0
- spring/orm/ddl_auto.py +1217 -0
- spring/orm/migration.py +419 -0
- spring/orm/mybatis_integration.py +400 -0
- spring/orm/pymybatis/__init__.py +86 -0
- spring/orm/pymybatis/annotations/__init__.py +30 -0
- spring/orm/pymybatis/annotations/annotations.py +332 -0
- spring/orm/pymybatis/cache/__init__.py +47 -0
- spring/orm/pymybatis/cache/cache.py +371 -0
- spring/orm/pymybatis/cache/redis_cache.py +434 -0
- spring/orm/pymybatis/circuit_breaker/__init__.py +21 -0
- spring/orm/pymybatis/circuit_breaker/circuit_breaker.py +424 -0
- spring/orm/pymybatis/configuration.py +525 -0
- spring/orm/pymybatis/core/__init__.py +10 -0
- spring/orm/pymybatis/core/sql_session.py +1382 -0
- spring/orm/pymybatis/core/sql_session_factory.py +76 -0
- spring/orm/pymybatis/dialect/__init__.py +9 -0
- spring/orm/pymybatis/dialect/dialect.py +445 -0
- spring/orm/pymybatis/dynamic_sql/__init__.py +9 -0
- spring/orm/pymybatis/dynamic_sql/dynamic_sql.py +900 -0
- spring/orm/pymybatis/interceptor/__init__.py +31 -0
- spring/orm/pymybatis/interceptor/interceptor.py +427 -0
- spring/orm/pymybatis/mapper/__init__.py +9 -0
- spring/orm/pymybatis/mapper/mapper.py +540 -0
- spring/orm/pymybatis/metrics/__init__.py +41 -0
- spring/orm/pymybatis/metrics/metrics.py +595 -0
- spring/orm/pymybatis/pool/__init__.py +9 -0
- spring/orm/pymybatis/pool/connection_pool.py +711 -0
- spring/orm/pymybatis/security/__init__.py +19 -0
- spring/orm/pymybatis/security/access_control.py +415 -0
- spring/orm/pymybatis/security/password_encoder.py +293 -0
- spring/orm/pymybatis/security/sensitive_data_masker.py +326 -0
- spring/orm/pymybatis/security/sql_injection_detector.py +675 -0
- spring/orm/pymybatis/transaction/__init__.py +9 -0
- spring/orm/pymybatis/transaction/transaction.py +288 -0
- spring/orm/pymybatis/type_handler/__init__.py +37 -0
- spring/orm/pymybatis/type_handler/type_handler.py +473 -0
- spring/orm/pymybatis/version.py +9 -0
- spring/orm/pymybatis/xml_parser/__init__.py +9 -0
- spring/orm/pymybatis/xml_parser/xml_parser.py +761 -0
- spring/retry/__init__.py +12 -0
- spring/retry/retry_annotations.py +71 -0
- spring/retry/retry_decorator.py +155 -0
- spring/scheduling/__init__.py +3 -0
- spring/scheduling/scheduler.py +389 -0
- spring/security/__init__.py +39 -0
- spring/security/jwt_utils.py +281 -0
- spring/security/replay_protection.py +206 -0
- spring/security/secret_manager.py +226 -0
- spring/security/security_aop.py +248 -0
- spring/security/security_context.py +172 -0
- spring/test/__init__.py +45 -0
- spring/test/slicing.py +341 -0
- spring/tracing/__init__.py +11 -0
- spring/tracing/skywalking.py +229 -0
- spring/tx/__init__.py +52 -0
- spring/tx/events.py +172 -0
- spring/tx/synchronization.py +143 -0
- spring/utils/__init__.py +5 -0
- spring/utils/banner.py +32 -0
- spring/utils/logger.py +73 -0
- spring/utils/redis_client.py +526 -0
- spring/validation/__init__.py +55 -0
- spring/validation/aop.py +141 -0
- spring/validation/constraints.py +357 -0
- spring/validation/exceptions.py +55 -0
- spring/validation/validator.py +139 -0
- spring/web/__init__.py +12 -0
- spring/web/actuator.py +319 -0
- spring/web/exception_handler.py +61 -0
- spring/web/health.py +399 -0
- spring/web/interceptor.py +91 -0
- spring/web/result.py +44 -0
- spring/web/swagger.py +601 -0
- spring/web/web_context.py +755 -0
- spring/websocket/__init__.py +86 -0
- spring/websocket/annotations.py +169 -0
- spring/websocket/broker.py +238 -0
- spring/websocket/exceptions.py +26 -0
- spring/websocket/handler.py +243 -0
- spring/websocket/router.py +526 -0
- spring/websocket/session.py +216 -0
- springbootai-1.8.0.dist-info/METADATA +2796 -0
- springbootai-1.8.0.dist-info/RECORD +175 -0
- springbootai-1.8.0.dist-info/WHEEL +5 -0
- springbootai-1.8.0.dist-info/entry_points.txt +2 -0
- springbootai-1.8.0.dist-info/licenses/LICENSE +7 -0
- springbootai-1.8.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PyMyBatis Redis二级缓存模块
|
|
3
|
+
|
|
4
|
+
实现基于Redis的分布式二级缓存,支持多实例部署时的数据一致性
|
|
5
|
+
|
|
6
|
+
核心特性:
|
|
7
|
+
- Redis作为缓存后端
|
|
8
|
+
- 支持缓存过期时间
|
|
9
|
+
- 支持缓存失效通知(Redis Pub/Sub)
|
|
10
|
+
- 支持表级别的缓存失效
|
|
11
|
+
- 可配置的序列化方式
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
import hashlib
|
|
16
|
+
import logging
|
|
17
|
+
from typing import Dict, Any, Optional, List
|
|
18
|
+
from enum import Enum
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class SerializationType(Enum):
|
|
24
|
+
"""序列化类型"""
|
|
25
|
+
JSON = 'json'
|
|
26
|
+
PICKLE = 'pickle'
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class RedisSecondLevelCache:
|
|
30
|
+
"""
|
|
31
|
+
Redis二级缓存实现
|
|
32
|
+
|
|
33
|
+
基于Redis实现的分布式缓存,支持多实例部署时的数据一致性。
|
|
34
|
+
通过Redis Pub/Sub机制实现缓存失效通知。
|
|
35
|
+
|
|
36
|
+
配置参数:
|
|
37
|
+
- host: Redis主机地址
|
|
38
|
+
- port: Redis端口
|
|
39
|
+
- db: Redis数据库编号
|
|
40
|
+
- password: Redis密码
|
|
41
|
+
- timeout: 连接超时时间
|
|
42
|
+
- ttl: 默认缓存过期时间(秒)
|
|
43
|
+
- serialization: 序列化方式(json/pickle)
|
|
44
|
+
- channel_prefix: Pub/Sub频道前缀
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(self,
|
|
48
|
+
host: str = 'localhost',
|
|
49
|
+
port: int = 6379,
|
|
50
|
+
db: int = 0,
|
|
51
|
+
password: Optional[str] = None,
|
|
52
|
+
timeout: int = 10,
|
|
53
|
+
ttl: int = 300,
|
|
54
|
+
serialization: str = 'json',
|
|
55
|
+
channel_prefix: str = 'pymybatis:cache:'):
|
|
56
|
+
"""
|
|
57
|
+
初始化Redis二级缓存
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
host: Redis主机地址
|
|
61
|
+
port: Redis端口
|
|
62
|
+
db: Redis数据库编号
|
|
63
|
+
password: Redis密码
|
|
64
|
+
timeout: 连接超时时间(秒)
|
|
65
|
+
ttl: 默认缓存过期时间(秒)
|
|
66
|
+
serialization: 序列化方式(json/pickle)
|
|
67
|
+
channel_prefix: Pub/Sub频道前缀
|
|
68
|
+
"""
|
|
69
|
+
self.host = host
|
|
70
|
+
self.port = port
|
|
71
|
+
self.db = db
|
|
72
|
+
self.password = password
|
|
73
|
+
self.timeout = timeout
|
|
74
|
+
self.ttl = ttl
|
|
75
|
+
self.serialization = SerializationType(serialization.lower())
|
|
76
|
+
self.channel_prefix = channel_prefix
|
|
77
|
+
|
|
78
|
+
# Redis连接
|
|
79
|
+
self._redis = None
|
|
80
|
+
self._pubsub = None
|
|
81
|
+
self._pubsub_thread = None
|
|
82
|
+
|
|
83
|
+
# 本地缓存(用于减少Redis访问)
|
|
84
|
+
self._local_cache: Dict[str, Any] = {}
|
|
85
|
+
|
|
86
|
+
# 启动缓存失效通知监听器
|
|
87
|
+
self._start_pubsub_listener()
|
|
88
|
+
|
|
89
|
+
def _connect(self):
|
|
90
|
+
"""建立Redis连接"""
|
|
91
|
+
if self._redis is None:
|
|
92
|
+
try:
|
|
93
|
+
import redis
|
|
94
|
+
self._redis = redis.Redis(
|
|
95
|
+
host=self.host,
|
|
96
|
+
port=self.port,
|
|
97
|
+
db=self.db,
|
|
98
|
+
password=self.password,
|
|
99
|
+
socket_timeout=self.timeout,
|
|
100
|
+
decode_responses=False # 使用字节模式
|
|
101
|
+
)
|
|
102
|
+
logger.info(f"Redis连接成功: {self.host}:{self.port}/{self.db}")
|
|
103
|
+
except ImportError:
|
|
104
|
+
raise ImportError("请安装redis: pip install redis")
|
|
105
|
+
except Exception as e:
|
|
106
|
+
logger.error(f"Redis连接失败: {e}")
|
|
107
|
+
raise
|
|
108
|
+
|
|
109
|
+
return self._redis
|
|
110
|
+
|
|
111
|
+
def _serialize(self, value: Any) -> bytes:
|
|
112
|
+
"""序列化值"""
|
|
113
|
+
if self.serialization == SerializationType.JSON:
|
|
114
|
+
return json.dumps(value, ensure_ascii=False).encode('utf-8')
|
|
115
|
+
else:
|
|
116
|
+
import pickle
|
|
117
|
+
return pickle.dumps(value)
|
|
118
|
+
|
|
119
|
+
def _deserialize(self, value: bytes) -> Any:
|
|
120
|
+
"""反序列化值"""
|
|
121
|
+
if value is None:
|
|
122
|
+
return None
|
|
123
|
+
|
|
124
|
+
if self.serialization == SerializationType.JSON:
|
|
125
|
+
try:
|
|
126
|
+
return json.loads(value.decode('utf-8'))
|
|
127
|
+
except (json.JSONDecodeError, UnicodeDecodeError):
|
|
128
|
+
logger.warning("JSON反序列化失败,尝试pickle")
|
|
129
|
+
import pickle
|
|
130
|
+
return pickle.loads(value)
|
|
131
|
+
else:
|
|
132
|
+
import pickle
|
|
133
|
+
return pickle.loads(value)
|
|
134
|
+
|
|
135
|
+
def _generate_key(self, table_name: str, params: Dict[str, Any]) -> str:
|
|
136
|
+
"""
|
|
137
|
+
生成缓存key
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
table_name: 表名
|
|
141
|
+
params: 参数
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
缓存key
|
|
145
|
+
"""
|
|
146
|
+
key = f"{table_name}_"
|
|
147
|
+
if params:
|
|
148
|
+
sorted_params = sorted(params.items())
|
|
149
|
+
key += str(sorted_params)
|
|
150
|
+
return hashlib.sha256(key.encode()).hexdigest()
|
|
151
|
+
|
|
152
|
+
def get(self, table_name: str, params: Dict[str, Any]) -> Optional[Any]:
|
|
153
|
+
"""
|
|
154
|
+
获取缓存
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
table_name: 表名
|
|
158
|
+
params: 查询参数
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
缓存值,不存在返回None
|
|
162
|
+
"""
|
|
163
|
+
key = self._generate_key(table_name, params)
|
|
164
|
+
full_key = f"{self.channel_prefix}{key}"
|
|
165
|
+
|
|
166
|
+
# 先从本地缓存查找
|
|
167
|
+
if key in self._local_cache:
|
|
168
|
+
logger.debug(f"本地缓存命中: {full_key}")
|
|
169
|
+
return self._local_cache[key]
|
|
170
|
+
|
|
171
|
+
try:
|
|
172
|
+
redis = self._connect()
|
|
173
|
+
value = redis.get(full_key)
|
|
174
|
+
if value is not None:
|
|
175
|
+
result = self._deserialize(value)
|
|
176
|
+
# 更新本地缓存
|
|
177
|
+
self._local_cache[key] = result
|
|
178
|
+
logger.debug(f"Redis缓存命中: {full_key}")
|
|
179
|
+
return result
|
|
180
|
+
except Exception as e:
|
|
181
|
+
logger.error(f"Redis缓存读取失败: {e}")
|
|
182
|
+
|
|
183
|
+
return None
|
|
184
|
+
|
|
185
|
+
def put(self, table_name: str, params: Dict[str, Any], value: Any) -> None:
|
|
186
|
+
"""
|
|
187
|
+
设置缓存
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
table_name: 表名
|
|
191
|
+
params: 查询参数
|
|
192
|
+
value: 缓存值
|
|
193
|
+
"""
|
|
194
|
+
key = self._generate_key(table_name, params)
|
|
195
|
+
full_key = f"{self.channel_prefix}{key}"
|
|
196
|
+
|
|
197
|
+
# 更新本地缓存
|
|
198
|
+
self._local_cache[key] = value
|
|
199
|
+
|
|
200
|
+
try:
|
|
201
|
+
redis = self._connect()
|
|
202
|
+
serialized_value = self._serialize(value)
|
|
203
|
+
|
|
204
|
+
if self.ttl > 0:
|
|
205
|
+
redis.setex(full_key, self.ttl, serialized_value)
|
|
206
|
+
else:
|
|
207
|
+
redis.set(full_key, serialized_value)
|
|
208
|
+
|
|
209
|
+
# 记录table_name到key的映射(用于缓存失效)
|
|
210
|
+
table_key = f"{self.channel_prefix}table:{table_name}"
|
|
211
|
+
redis.sadd(table_key, key)
|
|
212
|
+
|
|
213
|
+
logger.debug(f"Redis缓存设置成功: {full_key}")
|
|
214
|
+
except Exception as e:
|
|
215
|
+
logger.error(f"Redis缓存写入失败: {e}")
|
|
216
|
+
|
|
217
|
+
def invalidate_table(self, table_name: str) -> None:
|
|
218
|
+
"""
|
|
219
|
+
使指定表的所有缓存失效(广播通知)
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
table_name: 表名
|
|
223
|
+
"""
|
|
224
|
+
try:
|
|
225
|
+
redis = self._connect()
|
|
226
|
+
|
|
227
|
+
# 获取该表所有缓存key
|
|
228
|
+
table_key = f"{self.channel_prefix}table:{table_name}"
|
|
229
|
+
keys = redis.smembers(table_key)
|
|
230
|
+
|
|
231
|
+
# 删除所有相关缓存
|
|
232
|
+
for key in keys:
|
|
233
|
+
full_key = f"{self.channel_prefix}{key.decode()}"
|
|
234
|
+
redis.delete(full_key)
|
|
235
|
+
# 更新本地缓存
|
|
236
|
+
self._local_cache.pop(key.decode(), None)
|
|
237
|
+
|
|
238
|
+
# 删除表映射
|
|
239
|
+
redis.delete(table_key)
|
|
240
|
+
|
|
241
|
+
# 发布缓存失效通知
|
|
242
|
+
channel = f"{self.channel_prefix}invalidate"
|
|
243
|
+
message = json.dumps({'table_name': table_name})
|
|
244
|
+
redis.publish(channel, message)
|
|
245
|
+
|
|
246
|
+
logger.info(f"Redis缓存失效: 表 {table_name},共 {len(keys)} 个缓存项")
|
|
247
|
+
except Exception as e:
|
|
248
|
+
logger.error(f"Redis缓存失效失败: {e}")
|
|
249
|
+
|
|
250
|
+
def invalidate_key(self, table_name: str, params: Dict[str, Any]) -> None:
|
|
251
|
+
"""
|
|
252
|
+
使指定缓存项失效
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
table_name: 表名
|
|
256
|
+
params: 查询参数
|
|
257
|
+
"""
|
|
258
|
+
key = self._generate_key(table_name, params)
|
|
259
|
+
full_key = f"{self.channel_prefix}{key}"
|
|
260
|
+
|
|
261
|
+
# 更新本地缓存
|
|
262
|
+
self._local_cache.pop(key, None)
|
|
263
|
+
|
|
264
|
+
try:
|
|
265
|
+
redis = self._connect()
|
|
266
|
+
redis.delete(full_key)
|
|
267
|
+
|
|
268
|
+
# 从表映射中移除
|
|
269
|
+
table_key = f"{self.channel_prefix}table:{table_name}"
|
|
270
|
+
redis.srem(table_key, key)
|
|
271
|
+
|
|
272
|
+
logger.debug(f"Redis缓存项失效: {full_key}")
|
|
273
|
+
except Exception as e:
|
|
274
|
+
logger.error(f"Redis缓存项失效失败: {e}")
|
|
275
|
+
|
|
276
|
+
def invalidate_all(self) -> None:
|
|
277
|
+
"""使所有缓存失效"""
|
|
278
|
+
try:
|
|
279
|
+
redis = self._connect()
|
|
280
|
+
|
|
281
|
+
# 获取所有缓存key
|
|
282
|
+
pattern = f"{self.channel_prefix}*"
|
|
283
|
+
keys = redis.keys(pattern)
|
|
284
|
+
|
|
285
|
+
# 删除所有缓存
|
|
286
|
+
if keys:
|
|
287
|
+
redis.delete(*keys)
|
|
288
|
+
|
|
289
|
+
# 清空本地缓存
|
|
290
|
+
self._local_cache.clear()
|
|
291
|
+
|
|
292
|
+
# 发布缓存失效通知
|
|
293
|
+
channel = f"{self.channel_prefix}invalidate"
|
|
294
|
+
message = json.dumps({'table_name': '__ALL__'})
|
|
295
|
+
redis.publish(channel, message)
|
|
296
|
+
|
|
297
|
+
logger.info(f"Redis缓存全部失效,共 {len(keys)} 个缓存项")
|
|
298
|
+
except Exception as e:
|
|
299
|
+
logger.error(f"Redis缓存全部失效失败: {e}")
|
|
300
|
+
|
|
301
|
+
def clear(self) -> None:
|
|
302
|
+
"""清空缓存(同invalidate_all)"""
|
|
303
|
+
self.invalidate_all()
|
|
304
|
+
|
|
305
|
+
def size(self) -> int:
|
|
306
|
+
"""获取缓存大小"""
|
|
307
|
+
try:
|
|
308
|
+
redis = self._connect()
|
|
309
|
+
pattern = f"{self.channel_prefix}[0-9a-f]*"
|
|
310
|
+
keys = redis.keys(pattern)
|
|
311
|
+
return len(keys)
|
|
312
|
+
except Exception as e:
|
|
313
|
+
logger.error(f"Redis缓存大小获取失败: {e}")
|
|
314
|
+
return 0
|
|
315
|
+
|
|
316
|
+
def get_stats(self) -> Dict[str, Any]:
|
|
317
|
+
"""
|
|
318
|
+
获取缓存统计信息
|
|
319
|
+
|
|
320
|
+
Returns:
|
|
321
|
+
统计信息字典
|
|
322
|
+
"""
|
|
323
|
+
try:
|
|
324
|
+
redis = self._connect()
|
|
325
|
+
info = redis.info()
|
|
326
|
+
|
|
327
|
+
return {
|
|
328
|
+
'type': 'redis',
|
|
329
|
+
'host': self.host,
|
|
330
|
+
'port': self.port,
|
|
331
|
+
'db': self.db,
|
|
332
|
+
'ttl': self.ttl,
|
|
333
|
+
'serialization': self.serialization.value,
|
|
334
|
+
'local_cache_size': len(self._local_cache),
|
|
335
|
+
'redis_keys_count': self.size(),
|
|
336
|
+
'redis_info': {
|
|
337
|
+
'used_memory': info.get('used_memory_human', 'N/A'),
|
|
338
|
+
'used_cpu_sys': info.get('used_cpu_sys', 'N/A'),
|
|
339
|
+
'connected_clients': info.get('connected_clients', 'N/A'),
|
|
340
|
+
'keyspace_hits': info.get('keyspace_hits', 'N/A'),
|
|
341
|
+
'keyspace_misses': info.get('keyspace_misses', 'N/A')
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
except Exception as e:
|
|
345
|
+
logger.error(f"Redis缓存统计获取失败: {e}")
|
|
346
|
+
return {
|
|
347
|
+
'type': 'redis',
|
|
348
|
+
'host': self.host,
|
|
349
|
+
'port': self.port,
|
|
350
|
+
'db': self.db,
|
|
351
|
+
'ttl': self.ttl,
|
|
352
|
+
'serialization': self.serialization.value,
|
|
353
|
+
'local_cache_size': len(self._local_cache),
|
|
354
|
+
'error': str(e)
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
def _start_pubsub_listener(self):
|
|
358
|
+
"""启动缓存失效通知监听器"""
|
|
359
|
+
if self._pubsub_thread is not None:
|
|
360
|
+
return
|
|
361
|
+
|
|
362
|
+
try:
|
|
363
|
+
import threading
|
|
364
|
+
|
|
365
|
+
def listener():
|
|
366
|
+
redis = self._connect()
|
|
367
|
+
self._pubsub = redis.pubsub()
|
|
368
|
+
channel = f"{self.channel_prefix}invalidate"
|
|
369
|
+
self._pubsub.subscribe(channel)
|
|
370
|
+
|
|
371
|
+
logger.info(f"Redis缓存失效通知监听器已启动: {channel}")
|
|
372
|
+
|
|
373
|
+
for message in self._pubsub.listen():
|
|
374
|
+
if message['type'] == 'message':
|
|
375
|
+
try:
|
|
376
|
+
data = json.loads(message['data'].decode('utf-8'))
|
|
377
|
+
table_name = data.get('table_name')
|
|
378
|
+
|
|
379
|
+
if table_name == '__ALL__':
|
|
380
|
+
# 全部失效
|
|
381
|
+
self._local_cache.clear()
|
|
382
|
+
logger.info("收到缓存全部失效通知")
|
|
383
|
+
else:
|
|
384
|
+
# 特定表失效
|
|
385
|
+
# 移除该表相关的本地缓存
|
|
386
|
+
keys_to_remove = []
|
|
387
|
+
for key in self._local_cache:
|
|
388
|
+
if key.startswith(table_name):
|
|
389
|
+
keys_to_remove.append(key)
|
|
390
|
+
for key in keys_to_remove:
|
|
391
|
+
self._local_cache.pop(key, None)
|
|
392
|
+
logger.info(f"收到缓存失效通知: 表 {table_name}")
|
|
393
|
+
except Exception as e:
|
|
394
|
+
logger.error(f"处理缓存失效通知失败: {e}")
|
|
395
|
+
|
|
396
|
+
self._pubsub_thread = threading.Thread(target=listener, daemon=True)
|
|
397
|
+
self._pubsub_thread.start()
|
|
398
|
+
except Exception as e:
|
|
399
|
+
logger.error(f"启动缓存失效通知监听器失败: {e}")
|
|
400
|
+
|
|
401
|
+
def close(self):
|
|
402
|
+
"""关闭Redis连接"""
|
|
403
|
+
if self._pubsub:
|
|
404
|
+
self._pubsub.close()
|
|
405
|
+
|
|
406
|
+
if self._redis:
|
|
407
|
+
self._redis.close()
|
|
408
|
+
logger.info("Redis连接已关闭")
|
|
409
|
+
|
|
410
|
+
def __del__(self):
|
|
411
|
+
"""析构函数,确保连接被关闭"""
|
|
412
|
+
self.close()
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def create_redis_cache(config: Dict[str, Any]) -> RedisSecondLevelCache:
|
|
416
|
+
"""
|
|
417
|
+
根据配置创建Redis缓存实例
|
|
418
|
+
|
|
419
|
+
Args:
|
|
420
|
+
config: Redis配置字典
|
|
421
|
+
|
|
422
|
+
Returns:
|
|
423
|
+
Redis缓存实例
|
|
424
|
+
"""
|
|
425
|
+
return RedisSecondLevelCache(
|
|
426
|
+
host=config.get('host', 'localhost'),
|
|
427
|
+
port=config.get('port', 6379),
|
|
428
|
+
db=config.get('db', 0),
|
|
429
|
+
password=config.get('password'),
|
|
430
|
+
timeout=config.get('timeout', 10),
|
|
431
|
+
ttl=config.get('ttl', 300),
|
|
432
|
+
serialization=config.get('serialization', 'json'),
|
|
433
|
+
channel_prefix=config.get('channel_prefix', 'pymybatis:cache:')
|
|
434
|
+
)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PyMyBatis熔断降级模块
|
|
3
|
+
|
|
4
|
+
实现数据库连接熔断机制,防止数据库故障导致的级联失败
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .circuit_breaker import (
|
|
8
|
+
CircuitBreaker,
|
|
9
|
+
CircuitBreakerState,
|
|
10
|
+
CircuitBreakerError,
|
|
11
|
+
DatabaseCircuitBreaker,
|
|
12
|
+
with_circuit_breaker
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
'CircuitBreaker',
|
|
17
|
+
'CircuitBreakerState',
|
|
18
|
+
'CircuitBreakerError',
|
|
19
|
+
'DatabaseCircuitBreaker',
|
|
20
|
+
'with_circuit_breaker'
|
|
21
|
+
]
|