mdbq 4.0.101__py3-none-any.whl → 4.0.103__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/auth_backend.py +192 -7
- mdbq/auth/rate_limiter.py +173 -67
- {mdbq-4.0.101.dist-info → mdbq-4.0.103.dist-info}/METADATA +1 -1
- {mdbq-4.0.101.dist-info → mdbq-4.0.103.dist-info}/RECORD +7 -7
- {mdbq-4.0.101.dist-info → mdbq-4.0.103.dist-info}/WHEEL +0 -0
- {mdbq-4.0.101.dist-info → mdbq-4.0.103.dist-info}/top_level.txt +0 -0
mdbq/__version__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
VERSION = '4.0.
|
|
1
|
+
VERSION = '4.0.103'
|
mdbq/auth/auth_backend.py
CHANGED
|
@@ -1427,15 +1427,200 @@ class StandaloneAuthManager:
|
|
|
1427
1427
|
cursor.close()
|
|
1428
1428
|
conn.close()
|
|
1429
1429
|
|
|
1430
|
-
def logout_device(self, user_id, device_id):
|
|
1431
|
-
"""
|
|
1430
|
+
def logout_device(self, user_id, device_id=None, ip_address=None, user_agent=None, device_info=None, login_domain=''):
|
|
1431
|
+
"""
|
|
1432
|
+
登出设备(支持两种识别方式)
|
|
1433
|
+
|
|
1434
|
+
Args:
|
|
1435
|
+
user_id: 用户ID
|
|
1436
|
+
device_id: 设备ID(用于指定设备登出)
|
|
1437
|
+
ip_address: IP地址(用于当前设备登出)
|
|
1438
|
+
user_agent: 用户代理(用于当前设备登出)
|
|
1439
|
+
device_info: 设备信息(用于当前设备登出)
|
|
1440
|
+
login_domain: 登录域名(用于当前设备登出)
|
|
1441
|
+
|
|
1442
|
+
Returns:
|
|
1443
|
+
dict: 登出结果
|
|
1444
|
+
"""
|
|
1432
1445
|
conn = self.pool.connection()
|
|
1433
1446
|
cursor = conn.cursor()
|
|
1434
1447
|
|
|
1435
1448
|
try:
|
|
1436
1449
|
current_time_utc = datetime.now(timezone.utc)
|
|
1437
1450
|
|
|
1438
|
-
|
|
1451
|
+
if device_id:
|
|
1452
|
+
# 方式1:通过device_id查找设备(用于设备管理界面)
|
|
1453
|
+
cursor.execute('''
|
|
1454
|
+
SELECT id, device_name FROM device_sessions
|
|
1455
|
+
WHERE user_id = %s AND device_id = %s AND is_active = 1
|
|
1456
|
+
''', (user_id, device_id))
|
|
1457
|
+
|
|
1458
|
+
device = cursor.fetchone()
|
|
1459
|
+
logout_reason = 'single_device_logout'
|
|
1460
|
+
|
|
1461
|
+
if not device:
|
|
1462
|
+
return {'success': False, 'message': '设备不存在或已登出'}
|
|
1463
|
+
|
|
1464
|
+
else:
|
|
1465
|
+
# 方式2:通过设备指纹查找当前设备(用于当前设备登出)
|
|
1466
|
+
if not user_agent:
|
|
1467
|
+
return {'success': False, 'message': '缺少设备识别信息'}
|
|
1468
|
+
|
|
1469
|
+
# 解析用户代理获取基本信息
|
|
1470
|
+
parsed_ua = self._parse_user_agent(user_agent)
|
|
1471
|
+
|
|
1472
|
+
# 合并设备信息
|
|
1473
|
+
full_device_info = {
|
|
1474
|
+
'user_agent': user_agent,
|
|
1475
|
+
'platform': parsed_ua.get('platform'),
|
|
1476
|
+
**parsed_ua,
|
|
1477
|
+
**(device_info or {})
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
# 生成包含域名的设备指纹来识别当前设备
|
|
1481
|
+
device_fingerprint = self._generate_device_fingerprint(full_device_info, login_domain)
|
|
1482
|
+
|
|
1483
|
+
# 查找当前活跃的设备会话
|
|
1484
|
+
cursor.execute('''
|
|
1485
|
+
SELECT ds.id, ds.device_name
|
|
1486
|
+
FROM device_sessions ds
|
|
1487
|
+
WHERE ds.user_id = %s AND ds.device_fingerprint = %s AND ds.login_domain = %s
|
|
1488
|
+
AND ds.is_active = 1
|
|
1489
|
+
ORDER BY ds.session_version DESC
|
|
1490
|
+
LIMIT 1
|
|
1491
|
+
''', (user_id, device_fingerprint, login_domain))
|
|
1492
|
+
|
|
1493
|
+
device = cursor.fetchone()
|
|
1494
|
+
logout_reason = 'current_device_logout'
|
|
1495
|
+
|
|
1496
|
+
if not device:
|
|
1497
|
+
return {'success': False, 'message': '当前设备会话不存在或已失效'}
|
|
1498
|
+
|
|
1499
|
+
device_session_id = device['id']
|
|
1500
|
+
device_name = device['device_name']
|
|
1501
|
+
|
|
1502
|
+
# 撤销该设备的刷新令牌
|
|
1503
|
+
cursor.execute('''
|
|
1504
|
+
UPDATE refresh_tokens
|
|
1505
|
+
SET is_revoked = 1, revoked_at = %s, revoked_reason = %s
|
|
1506
|
+
WHERE device_session_id = %s AND is_revoked = 0
|
|
1507
|
+
''', (current_time_utc, logout_reason, device_session_id))
|
|
1508
|
+
|
|
1509
|
+
# 停用设备会话
|
|
1510
|
+
cursor.execute('''
|
|
1511
|
+
UPDATE device_sessions
|
|
1512
|
+
SET is_active = 0
|
|
1513
|
+
WHERE id = %s
|
|
1514
|
+
''', (device_session_id,))
|
|
1515
|
+
|
|
1516
|
+
message = f'设备 "{device_name}" 已成功登出' if device_id else f'已成功登出当前设备 "{device_name}"'
|
|
1517
|
+
return {'success': True, 'message': message}
|
|
1518
|
+
|
|
1519
|
+
except Exception as e:
|
|
1520
|
+
return {'success': False, 'message': f'登出设备失败: {str(e)}'}
|
|
1521
|
+
finally:
|
|
1522
|
+
cursor.close()
|
|
1523
|
+
conn.close()
|
|
1524
|
+
|
|
1525
|
+
def logout_current_device(self, user_id, access_token=None):
|
|
1526
|
+
"""
|
|
1527
|
+
退出当前设备(通过token识别)
|
|
1528
|
+
|
|
1529
|
+
Args:
|
|
1530
|
+
user_id: 用户ID
|
|
1531
|
+
access_token: 当前的访问令牌(用于识别设备会话)
|
|
1532
|
+
|
|
1533
|
+
Returns:
|
|
1534
|
+
dict: 登出结果
|
|
1535
|
+
"""
|
|
1536
|
+
conn = self.pool.connection()
|
|
1537
|
+
cursor = conn.cursor()
|
|
1538
|
+
|
|
1539
|
+
try:
|
|
1540
|
+
current_time_utc = datetime.now(timezone.utc)
|
|
1541
|
+
|
|
1542
|
+
if access_token:
|
|
1543
|
+
# 方式1:通过access_token解析出设备会话信息
|
|
1544
|
+
try:
|
|
1545
|
+
payload = self.verify_access_token(access_token)
|
|
1546
|
+
if not payload:
|
|
1547
|
+
return {'success': False, 'message': '无效的访问令牌'}
|
|
1548
|
+
|
|
1549
|
+
# 查找该用户最近活跃的设备(作为当前设备的近似)
|
|
1550
|
+
cursor.execute('''
|
|
1551
|
+
SELECT ds.id, ds.device_name, rt.id as token_id
|
|
1552
|
+
FROM device_sessions ds
|
|
1553
|
+
LEFT JOIN refresh_tokens rt ON ds.id = rt.device_session_id AND rt.is_revoked = 0
|
|
1554
|
+
WHERE ds.user_id = %s AND ds.is_active = 1
|
|
1555
|
+
ORDER BY ds.last_activity DESC
|
|
1556
|
+
LIMIT 1
|
|
1557
|
+
''', (user_id,))
|
|
1558
|
+
|
|
1559
|
+
except Exception:
|
|
1560
|
+
# token解析失败,使用备用方案
|
|
1561
|
+
cursor.execute('''
|
|
1562
|
+
SELECT id, device_name FROM device_sessions
|
|
1563
|
+
WHERE user_id = %s AND is_active = 1
|
|
1564
|
+
ORDER BY last_activity DESC
|
|
1565
|
+
LIMIT 1
|
|
1566
|
+
''', (user_id,))
|
|
1567
|
+
else:
|
|
1568
|
+
# 方式2:没有token时,登出最新活跃的设备
|
|
1569
|
+
cursor.execute('''
|
|
1570
|
+
SELECT id, device_name FROM device_sessions
|
|
1571
|
+
WHERE user_id = %s AND is_active = 1
|
|
1572
|
+
ORDER BY last_activity DESC
|
|
1573
|
+
LIMIT 1
|
|
1574
|
+
''', (user_id,))
|
|
1575
|
+
|
|
1576
|
+
device = cursor.fetchone()
|
|
1577
|
+
|
|
1578
|
+
if not device:
|
|
1579
|
+
return {'success': False, 'message': '没有找到活跃的设备会话'}
|
|
1580
|
+
|
|
1581
|
+
device_session_id = device['id']
|
|
1582
|
+
device_name = device['device_name']
|
|
1583
|
+
|
|
1584
|
+
# 撤销该设备的刷新令牌
|
|
1585
|
+
cursor.execute('''
|
|
1586
|
+
UPDATE refresh_tokens
|
|
1587
|
+
SET is_revoked = 1, revoked_at = %s, revoked_reason = 'current_device_logout'
|
|
1588
|
+
WHERE device_session_id = %s AND is_revoked = 0
|
|
1589
|
+
''', (current_time_utc, device_session_id))
|
|
1590
|
+
|
|
1591
|
+
# 停用设备会话
|
|
1592
|
+
cursor.execute('''
|
|
1593
|
+
UPDATE device_sessions
|
|
1594
|
+
SET is_active = 0
|
|
1595
|
+
WHERE id = %s
|
|
1596
|
+
''', (device_session_id,))
|
|
1597
|
+
|
|
1598
|
+
return {'success': True, 'message': f'已成功退出当前设备 "{device_name}"'}
|
|
1599
|
+
|
|
1600
|
+
except Exception as e:
|
|
1601
|
+
return {'success': False, 'message': f'退出当前设备失败: {str(e)}'}
|
|
1602
|
+
finally:
|
|
1603
|
+
cursor.close()
|
|
1604
|
+
conn.close()
|
|
1605
|
+
|
|
1606
|
+
def logout_specific_device(self, user_id, device_id):
|
|
1607
|
+
"""
|
|
1608
|
+
退出指定设备(通过device_id)
|
|
1609
|
+
|
|
1610
|
+
Args:
|
|
1611
|
+
user_id: 用户ID
|
|
1612
|
+
device_id: 要退出的设备ID
|
|
1613
|
+
|
|
1614
|
+
Returns:
|
|
1615
|
+
dict: 登出结果
|
|
1616
|
+
"""
|
|
1617
|
+
conn = self.pool.connection()
|
|
1618
|
+
cursor = conn.cursor()
|
|
1619
|
+
|
|
1620
|
+
try:
|
|
1621
|
+
current_time_utc = datetime.now(timezone.utc)
|
|
1622
|
+
|
|
1623
|
+
# 通过device_id查找设备,确保属于该用户
|
|
1439
1624
|
cursor.execute('''
|
|
1440
1625
|
SELECT id, device_name FROM device_sessions
|
|
1441
1626
|
WHERE user_id = %s AND device_id = %s AND is_active = 1
|
|
@@ -1444,7 +1629,7 @@ class StandaloneAuthManager:
|
|
|
1444
1629
|
device = cursor.fetchone()
|
|
1445
1630
|
|
|
1446
1631
|
if not device:
|
|
1447
|
-
return {'success': False, 'message': '
|
|
1632
|
+
return {'success': False, 'message': '设备不存在或已退出'}
|
|
1448
1633
|
|
|
1449
1634
|
device_session_id = device['id']
|
|
1450
1635
|
device_name = device['device_name']
|
|
@@ -1452,7 +1637,7 @@ class StandaloneAuthManager:
|
|
|
1452
1637
|
# 撤销该设备的刷新令牌
|
|
1453
1638
|
cursor.execute('''
|
|
1454
1639
|
UPDATE refresh_tokens
|
|
1455
|
-
SET is_revoked = 1, revoked_at = %s, revoked_reason = '
|
|
1640
|
+
SET is_revoked = 1, revoked_at = %s, revoked_reason = 'specific_device_logout'
|
|
1456
1641
|
WHERE device_session_id = %s AND is_revoked = 0
|
|
1457
1642
|
''', (current_time_utc, device_session_id))
|
|
1458
1643
|
|
|
@@ -1463,10 +1648,10 @@ class StandaloneAuthManager:
|
|
|
1463
1648
|
WHERE id = %s
|
|
1464
1649
|
''', (device_session_id,))
|
|
1465
1650
|
|
|
1466
|
-
return {'success': True, 'message': f'
|
|
1651
|
+
return {'success': True, 'message': f'已成功退出设备 "{device_name}"'}
|
|
1467
1652
|
|
|
1468
1653
|
except Exception as e:
|
|
1469
|
-
return {'success': False, 'message': f'
|
|
1654
|
+
return {'success': False, 'message': f'退出指定设备失败: {str(e)}'}
|
|
1470
1655
|
finally:
|
|
1471
1656
|
cursor.close()
|
|
1472
1657
|
conn.close()
|
mdbq/auth/rate_limiter.py
CHANGED
|
@@ -255,30 +255,60 @@ class AdvancedRateLimiter:
|
|
|
255
255
|
|
|
256
256
|
def check_sliding_window_log(self, key: str, rule: RateLimitRule) -> Tuple[bool, int]:
|
|
257
257
|
"""滑动窗口日志算法"""
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
if key not in self.storage:
|
|
263
|
-
self.storage[key] = {'requests': deque(), 'last_access': current_time}
|
|
264
|
-
|
|
265
|
-
# 更新最后访问时间
|
|
266
|
-
self.storage[key]['last_access'] = current_time
|
|
267
|
-
requests = self.storage[key]['requests']
|
|
268
|
-
|
|
269
|
-
# 清理过期请求
|
|
270
|
-
while requests and requests[0] < window_start:
|
|
271
|
-
requests.popleft()
|
|
272
|
-
|
|
273
|
-
# 检查是否超过限制
|
|
274
|
-
if len(requests) >= rule.requests:
|
|
275
|
-
return False, 0
|
|
276
|
-
|
|
277
|
-
# 记录当前请求
|
|
278
|
-
requests.append(current_time)
|
|
279
|
-
remaining = rule.requests - len(requests)
|
|
258
|
+
try:
|
|
259
|
+
if self.logger:
|
|
260
|
+
self.logger.debug(f"滑动窗口检查: key={key}, rule.requests={rule.requests}, rule.window={rule.window}")
|
|
280
261
|
|
|
281
|
-
|
|
262
|
+
with self.locks[key]:
|
|
263
|
+
current_time = time.time()
|
|
264
|
+
window_start = current_time - rule.window
|
|
265
|
+
|
|
266
|
+
if key not in self.storage:
|
|
267
|
+
if self.logger:
|
|
268
|
+
self.logger.debug(f"创建新的存储条目: key={key}")
|
|
269
|
+
self.storage[key] = {'requests': deque(), 'last_access': current_time}
|
|
270
|
+
|
|
271
|
+
# 更新最后访问时间
|
|
272
|
+
self.storage[key]['last_access'] = current_time
|
|
273
|
+
|
|
274
|
+
# 确保存储结构正确(可能被其他算法创建了不同结构)
|
|
275
|
+
if 'requests' not in self.storage[key]:
|
|
276
|
+
if self.logger:
|
|
277
|
+
self.logger.debug(f"修复存储结构: key={key}, 当前结构={list(self.storage[key].keys())}")
|
|
278
|
+
# 重新初始化为滑动窗口结构
|
|
279
|
+
self.storage[key] = {'requests': deque(), 'last_access': current_time}
|
|
280
|
+
|
|
281
|
+
requests = self.storage[key]['requests']
|
|
282
|
+
|
|
283
|
+
if self.logger:
|
|
284
|
+
self.logger.debug(f"当前请求队列长度: {len(requests)}")
|
|
285
|
+
# 清理过期请求
|
|
286
|
+
while requests and requests[0] < window_start:
|
|
287
|
+
requests.popleft()
|
|
288
|
+
|
|
289
|
+
# 检查是否超过限制
|
|
290
|
+
if len(requests) >= rule.requests:
|
|
291
|
+
if self.logger:
|
|
292
|
+
self.logger.debug(f"请求超限: {len(requests)} >= {rule.requests}")
|
|
293
|
+
return False, 0
|
|
294
|
+
|
|
295
|
+
# 记录当前请求
|
|
296
|
+
requests.append(current_time)
|
|
297
|
+
remaining = rule.requests - len(requests)
|
|
298
|
+
|
|
299
|
+
if self.logger:
|
|
300
|
+
self.logger.debug(f"请求允许: remaining={remaining}")
|
|
301
|
+
|
|
302
|
+
return True, remaining
|
|
303
|
+
|
|
304
|
+
except Exception as e:
|
|
305
|
+
if self.logger:
|
|
306
|
+
import traceback
|
|
307
|
+
self.logger.error(f"滑动窗口算法异常: {str(e)}")
|
|
308
|
+
self.logger.error(f"Key: {key}, Rule: {rule}")
|
|
309
|
+
self.logger.error(f"异常详情: {traceback.format_exc()}")
|
|
310
|
+
# 异常时允许通过
|
|
311
|
+
return True, 100
|
|
282
312
|
|
|
283
313
|
def check_token_bucket(self, key: str, rule: RateLimitRule) -> Tuple[bool, int]:
|
|
284
314
|
"""令牌桶算法"""
|
|
@@ -313,59 +343,122 @@ class AdvancedRateLimiter:
|
|
|
313
343
|
|
|
314
344
|
def check_fixed_window(self, key: str, rule: RateLimitRule) -> Tuple[bool, int]:
|
|
315
345
|
"""固定窗口算法"""
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
with self.locks[key]:
|
|
320
|
-
if key not in self.storage:
|
|
321
|
-
self.storage[key] = {'count': 0, 'window_start': window_start, 'last_access': current_time}
|
|
322
|
-
|
|
323
|
-
# 更新最后访问时间
|
|
324
|
-
self.storage[key]['last_access'] = current_time
|
|
325
|
-
data = self.storage[key]
|
|
326
|
-
|
|
327
|
-
# 检查是否是新窗口
|
|
328
|
-
if data['window_start'] != window_start:
|
|
329
|
-
data['count'] = 0
|
|
330
|
-
data['window_start'] = window_start
|
|
346
|
+
try:
|
|
347
|
+
if self.logger:
|
|
348
|
+
self.logger.debug(f"固定窗口检查: key={key}, rule.requests={rule.requests}, rule.window={rule.window}")
|
|
331
349
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
return False, 0
|
|
350
|
+
current_time = time.time()
|
|
351
|
+
window_start = int(current_time // rule.window) * rule.window
|
|
335
352
|
|
|
336
|
-
|
|
337
|
-
|
|
353
|
+
with self.locks[key]:
|
|
354
|
+
if key not in self.storage:
|
|
355
|
+
if self.logger:
|
|
356
|
+
self.logger.debug(f"创建新的固定窗口存储: key={key}")
|
|
357
|
+
self.storage[key] = {'count': 0, 'window_start': window_start, 'last_access': current_time}
|
|
358
|
+
|
|
359
|
+
# 更新最后访问时间
|
|
360
|
+
self.storage[key]['last_access'] = current_time
|
|
361
|
+
|
|
362
|
+
# 确保存储结构正确(可能被滑动窗口算法创建了不同结构)
|
|
363
|
+
if 'count' not in self.storage[key] or 'window_start' not in self.storage[key]:
|
|
364
|
+
if self.logger:
|
|
365
|
+
self.logger.debug(f"修复固定窗口存储结构: key={key}, 当前结构={list(self.storage[key].keys())}")
|
|
366
|
+
# 重新初始化为固定窗口结构
|
|
367
|
+
self.storage[key] = {'count': 0, 'window_start': window_start, 'last_access': current_time}
|
|
368
|
+
|
|
369
|
+
data = self.storage[key]
|
|
338
370
|
|
|
339
|
-
|
|
371
|
+
# 检查是否是新窗口
|
|
372
|
+
if data['window_start'] != window_start:
|
|
373
|
+
if self.logger:
|
|
374
|
+
self.logger.debug(f"新窗口开始: {window_start}")
|
|
375
|
+
data['count'] = 0
|
|
376
|
+
data['window_start'] = window_start
|
|
377
|
+
|
|
378
|
+
# 检查是否超过限制
|
|
379
|
+
if data['count'] >= rule.requests:
|
|
380
|
+
if self.logger:
|
|
381
|
+
self.logger.debug(f"固定窗口请求超限: {data['count']} >= {rule.requests}")
|
|
382
|
+
return False, 0
|
|
383
|
+
|
|
384
|
+
data['count'] += 1
|
|
385
|
+
remaining = rule.requests - data['count']
|
|
386
|
+
|
|
387
|
+
if self.logger:
|
|
388
|
+
self.logger.debug(f"固定窗口请求允许: count={data['count']}, remaining={remaining}")
|
|
389
|
+
|
|
390
|
+
return True, remaining
|
|
391
|
+
|
|
392
|
+
except Exception as e:
|
|
393
|
+
if self.logger:
|
|
394
|
+
import traceback
|
|
395
|
+
self.logger.error(f"固定窗口算法异常: {str(e)}")
|
|
396
|
+
self.logger.error(f"Key: {key}, Rule: {rule}")
|
|
397
|
+
self.logger.error(f"异常详情: {traceback.format_exc()}")
|
|
398
|
+
# 异常时允许通过
|
|
399
|
+
return True, 100
|
|
340
400
|
|
|
341
401
|
def check_rate_limit(self, api_type: str, key: str, level: RateLimitLevel,
|
|
342
402
|
strategy: RateLimitStrategy = None) -> Tuple[bool, int, dict]:
|
|
343
403
|
"""核心限流检查"""
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
404
|
+
try:
|
|
405
|
+
if self.logger:
|
|
406
|
+
self.logger.debug(f"check_rate_limit: api_type={api_type}, key={key}, level={level}")
|
|
407
|
+
|
|
408
|
+
# 检查是否被阻断
|
|
409
|
+
is_blocked, block_remaining = self.is_blocked(key)
|
|
410
|
+
if is_blocked:
|
|
411
|
+
if self.logger:
|
|
412
|
+
self.logger.debug(f"Key {key} is blocked, remaining: {block_remaining}")
|
|
413
|
+
return False, 0, {
|
|
414
|
+
'error': 'blocked',
|
|
415
|
+
'retry_after': block_remaining,
|
|
416
|
+
'reason': 'IP temporarily blocked due to excessive requests'
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
# 获取限流规则
|
|
420
|
+
if api_type not in self.rules or level not in self.rules[api_type]:
|
|
421
|
+
if self.logger:
|
|
422
|
+
self.logger.debug(f"Using default rule for api_type={api_type}, level={level}")
|
|
423
|
+
rule = RateLimitRule(100, 60) # 默认规则
|
|
424
|
+
else:
|
|
425
|
+
rule = self.rules[api_type][level]
|
|
426
|
+
if self.logger:
|
|
427
|
+
self.logger.debug(f"Using rule: requests={rule.requests}, window={rule.window}")
|
|
428
|
+
except Exception as e:
|
|
429
|
+
if self.logger:
|
|
430
|
+
import traceback
|
|
431
|
+
self.logger.error(f"check_rate_limit异常: {str(e)}")
|
|
432
|
+
self.logger.error(f"异常详情: {traceback.format_exc()}")
|
|
433
|
+
# 异常时返回允许通过
|
|
434
|
+
return True, 100, {}
|
|
358
435
|
|
|
359
436
|
# 选择限流策略
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
437
|
+
try:
|
|
438
|
+
strategy = strategy or rule.strategy
|
|
439
|
+
|
|
440
|
+
if self.logger:
|
|
441
|
+
self.logger.debug(f"Using strategy: {strategy}")
|
|
442
|
+
|
|
443
|
+
if strategy == RateLimitStrategy.SLIDING_WINDOW_LOG:
|
|
444
|
+
allowed, remaining = self.check_sliding_window_log(key, rule)
|
|
445
|
+
elif strategy == RateLimitStrategy.TOKEN_BUCKET:
|
|
446
|
+
allowed, remaining = self.check_token_bucket(key, rule)
|
|
447
|
+
else:
|
|
448
|
+
# 默认固定窗口
|
|
449
|
+
allowed, remaining = self.check_fixed_window(key, rule)
|
|
450
|
+
|
|
451
|
+
if self.logger:
|
|
452
|
+
self.logger.debug(f"Strategy result: allowed={allowed}, remaining={remaining}")
|
|
453
|
+
|
|
454
|
+
except Exception as e:
|
|
455
|
+
if self.logger:
|
|
456
|
+
import traceback
|
|
457
|
+
self.logger.error(f"限流策略执行异常: {str(e)}")
|
|
458
|
+
self.logger.error(f"策略: {strategy}, Key: {key}")
|
|
459
|
+
self.logger.error(f"异常详情: {traceback.format_exc()}")
|
|
460
|
+
# 策略执行异常时,允许通过
|
|
461
|
+
return True, 100, {}
|
|
369
462
|
|
|
370
463
|
# 如果超过限制,考虑是否阻断
|
|
371
464
|
if not allowed:
|
|
@@ -459,13 +552,22 @@ class RateLimitDecorators:
|
|
|
459
552
|
def decorated_function(*args, **kwargs):
|
|
460
553
|
try:
|
|
461
554
|
# 获取客户端信息
|
|
555
|
+
if self.limiter.logger:
|
|
556
|
+
self.limiter.logger.debug(f"限流检查开始: api_type={api_type}")
|
|
557
|
+
|
|
462
558
|
client_ip, user_level, rate_limit_key = self.limiter.get_client_info()
|
|
463
559
|
|
|
560
|
+
if self.limiter.logger:
|
|
561
|
+
self.limiter.logger.debug(f"客户端信息: ip={client_ip}, level={user_level}, key={rate_limit_key}")
|
|
562
|
+
|
|
464
563
|
# 执行限流检查
|
|
465
564
|
allowed, remaining, error_info = self.limiter.check_rate_limit(
|
|
466
565
|
api_type, rate_limit_key, user_level, strategy
|
|
467
566
|
)
|
|
468
567
|
|
|
568
|
+
if self.limiter.logger:
|
|
569
|
+
self.limiter.logger.debug(f"限流检查结果: allowed={allowed}, remaining={remaining}")
|
|
570
|
+
|
|
469
571
|
if not allowed:
|
|
470
572
|
# 记录限流事件
|
|
471
573
|
if self.limiter.logger:
|
|
@@ -510,7 +612,11 @@ class RateLimitDecorators:
|
|
|
510
612
|
|
|
511
613
|
except Exception as e:
|
|
512
614
|
if self.limiter.logger:
|
|
615
|
+
import traceback
|
|
513
616
|
self.limiter.logger.error(f"限流系统异常: {str(e)}")
|
|
617
|
+
self.limiter.logger.error(f"异常类型: {type(e).__name__}")
|
|
618
|
+
self.limiter.logger.error(f"异常详情: {traceback.format_exc()}")
|
|
619
|
+
self.limiter.logger.error(f"API类型: {api_type}, 策略: {strategy}")
|
|
514
620
|
# 限流系统故障时,允许请求通过
|
|
515
621
|
return f(*args, **kwargs)
|
|
516
622
|
|
|
@@ -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=qTcnY2bvQn-NzVBlZcbrJG8Y5EPJqPqlZRvmmiZhj_k,19
|
|
3
3
|
mdbq/auth/__init__.py,sha256=pnPMAt63sh1B6kEvmutUuro46zVf2v2YDAG7q-jV_To,24
|
|
4
|
-
mdbq/auth/auth_backend.py,sha256=
|
|
5
|
-
mdbq/auth/rate_limiter.py,sha256=
|
|
4
|
+
mdbq/auth/auth_backend.py,sha256=9hYqpILqlpOJ89qi6N0h35QS7No2QNT8qCu78tajwIs,86999
|
|
5
|
+
mdbq/auth/rate_limiter.py,sha256=1m_Paxp8pDNpmyoFGRpFMVOJpbmeIvfVcfiQ2oH72qM,32850
|
|
6
6
|
mdbq/js/__init__.py,sha256=hpMi3_ZKwIWkzc0LnKL-SY9AS-7PYFHq0izYTgEvxjc,30
|
|
7
7
|
mdbq/js/jc.py,sha256=FOc6HOOTJwnoZLZmgmaE1SQo9rUnVhXmefhKMD2MlDA,13229
|
|
8
8
|
mdbq/log/__init__.py,sha256=Mpbrav0s0ifLL7lVDAuePEi1hJKiSHhxcv1byBKDl5E,15
|
|
@@ -33,7 +33,7 @@ mdbq/route/routes.py,sha256=QVGfTvDgu0CpcKCvk1ra74H8uojgqTLUav1fnVAqLEA,29433
|
|
|
33
33
|
mdbq/selenium/__init__.py,sha256=AKzeEceqZyvqn2dEDoJSzDQnbuENkJSHAlbHAD0u0ZI,10
|
|
34
34
|
mdbq/selenium/get_driver.py,sha256=1NTlVUE6QsyjTrVVVqTO2LOnYf578ccFWlWnvIXGtic,20903
|
|
35
35
|
mdbq/spider/__init__.py,sha256=RBMFXGy_jd1HXZhngB2T2XTvJqki8P_Fr-pBcwijnew,18
|
|
36
|
-
mdbq-4.0.
|
|
37
|
-
mdbq-4.0.
|
|
38
|
-
mdbq-4.0.
|
|
39
|
-
mdbq-4.0.
|
|
36
|
+
mdbq-4.0.103.dist-info/METADATA,sha256=PdQ5SB6ffus_bjg8DWRonFLHRF_JYD_BMtuqbv238g8,365
|
|
37
|
+
mdbq-4.0.103.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
38
|
+
mdbq-4.0.103.dist-info/top_level.txt,sha256=2FQ-uLnCSB-OwFiWntzmwosW3X2Xqsg0ewh1axsaylA,5
|
|
39
|
+
mdbq-4.0.103.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|