mdbq 4.2.22__tar.gz → 4.2.24__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.
Potentially problematic release.
This version of mdbq might be problematic. Click here for more details.
- {mdbq-4.2.22 → mdbq-4.2.24}/PKG-INFO +1 -1
- mdbq-4.2.24/mdbq/__version__.py +1 -0
- {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/redis/redis_cache.py +7 -1
- {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/route/monitor.py +184 -7
- {mdbq-4.2.22 → mdbq-4.2.24}/mdbq.egg-info/PKG-INFO +1 -1
- mdbq-4.2.22/mdbq/__version__.py +0 -1
- {mdbq-4.2.22 → mdbq-4.2.24}/README.txt +0 -0
- {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/__init__.py +0 -0
- {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/auth/__init__.py +0 -0
- {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/auth/auth_backend.py +0 -0
- {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/auth/crypto.py +0 -0
- {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/auth/rate_limiter.py +0 -0
- {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/js/__init__.py +0 -0
- {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/js/jc.py +0 -0
- {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/log/__init__.py +0 -0
- {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/log/mylogger.py +0 -0
- {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/myconf/__init__.py +0 -0
- {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/myconf/myconf.py +0 -0
- {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/mysql/__init__.py +0 -0
- {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/mysql/deduplicator.py +0 -0
- {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/mysql/mysql.py +0 -0
- {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/mysql/s_query.py +0 -0
- {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/mysql/unique_.py +0 -0
- {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/mysql/uploader.py +0 -0
- {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/other/__init__.py +0 -0
- {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/other/download_sku_picture.py +0 -0
- {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/other/error_handler.py +0 -0
- {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/other/otk.py +0 -0
- {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/other/pov_city.py +0 -0
- {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/other/ua_sj.py +0 -0
- {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/pbix/__init__.py +0 -0
- {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/pbix/pbix_refresh.py +0 -0
- {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/pbix/refresh_all.py +0 -0
- {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/redis/__init__.py +0 -0
- {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/redis/getredis.py +0 -0
- {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/route/__init__.py +0 -0
- {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/route/analytics.py +0 -0
- {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/route/routes.py +0 -0
- {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/selenium/__init__.py +0 -0
- {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/selenium/get_driver.py +0 -0
- {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/spider/__init__.py +0 -0
- {mdbq-4.2.22 → mdbq-4.2.24}/mdbq.egg-info/SOURCES.txt +0 -0
- {mdbq-4.2.22 → mdbq-4.2.24}/mdbq.egg-info/dependency_links.txt +0 -0
- {mdbq-4.2.22 → mdbq-4.2.24}/mdbq.egg-info/top_level.txt +0 -0
- {mdbq-4.2.22 → mdbq-4.2.24}/setup.cfg +0 -0
- {mdbq-4.2.22 → mdbq-4.2.24}/setup.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
VERSION = '4.2.24'
|
|
@@ -919,7 +919,13 @@ def flask_redis_cache(cache_key_func=None, ttl=1200, namespace="default",
|
|
|
919
919
|
# 如果没有Flask环境,直接执行原函数
|
|
920
920
|
return func(*args, **kwargs)
|
|
921
921
|
|
|
922
|
-
#
|
|
922
|
+
# OPTIONS 预检请求特殊处理:直接跳过缓存逻辑
|
|
923
|
+
if request.method == 'OPTIONS':
|
|
924
|
+
g.data_source = 'preflight' # 标记为预检请求
|
|
925
|
+
g.cache_hit = False
|
|
926
|
+
return func(*args, **kwargs)
|
|
927
|
+
|
|
928
|
+
# 初始化数据源标记(仅非 OPTIONS 请求)
|
|
923
929
|
g.data_source = 'mysql'
|
|
924
930
|
g.cache_hit = False
|
|
925
931
|
|
|
@@ -21,7 +21,7 @@ import functools
|
|
|
21
21
|
import hashlib
|
|
22
22
|
import socket
|
|
23
23
|
from datetime import datetime, timedelta
|
|
24
|
-
from typing import Dict, Any, Optional
|
|
24
|
+
from typing import Dict, Any, Optional, List
|
|
25
25
|
from dbutils.pooled_db import PooledDB # type: ignore
|
|
26
26
|
from flask import request, g
|
|
27
27
|
|
|
@@ -179,7 +179,7 @@ class RouteMonitor:
|
|
|
179
179
|
`请求来源` VARCHAR(500) COMMENT '请求来源(Referer)',
|
|
180
180
|
`状态码` SMALLINT COMMENT 'HTTP 状态码',
|
|
181
181
|
`响应耗时` DECIMAL(10,3) COMMENT '请求处理耗时(毫秒)',
|
|
182
|
-
`数据源` VARCHAR(20) COMMENT '数据源类型:redis/mysql/hybrid/none',
|
|
182
|
+
`数据源` VARCHAR(20) COMMENT '数据源类型:redis/mysql/hybrid/none/preflight',
|
|
183
183
|
`缓存命中` TINYINT(1) DEFAULT 0 COMMENT '是否命中缓存:1-命中,0-未命中',
|
|
184
184
|
`用户标识` VARCHAR(64) COMMENT '用户id或标识(如有)',
|
|
185
185
|
`用户代理` VARCHAR(500) COMMENT '浏览器 User-Agent(精简版)',
|
|
@@ -437,6 +437,138 @@ class RouteMonitor:
|
|
|
437
437
|
except Exception:
|
|
438
438
|
return None
|
|
439
439
|
|
|
440
|
+
def calculate_risk_score(self, client_ip: str, date: datetime.date) -> int:
|
|
441
|
+
"""
|
|
442
|
+
计算 IP 风险评分(0-100)
|
|
443
|
+
|
|
444
|
+
风险评分基于以下维度:
|
|
445
|
+
1. 请求频率异常(30分):单日请求数超过阈值
|
|
446
|
+
2. 失败率异常(25分):失败请求比例过高
|
|
447
|
+
3. 访问模式异常(20分):短时间访问大量不同接口
|
|
448
|
+
4. 时间分布异常(15分):非正常时间段高频访问
|
|
449
|
+
5. 响应时间异常(10分):平均响应时间过长(可能是攻击)
|
|
450
|
+
|
|
451
|
+
Args:
|
|
452
|
+
client_ip: 客户端 IP 地址
|
|
453
|
+
date: 统计日期
|
|
454
|
+
|
|
455
|
+
Returns:
|
|
456
|
+
int: 风险评分(0-100),分数越高风险越大
|
|
457
|
+
"""
|
|
458
|
+
risk_score = 0
|
|
459
|
+
|
|
460
|
+
try:
|
|
461
|
+
connection = self.pool.connection()
|
|
462
|
+
try:
|
|
463
|
+
with connection.cursor() as cursor:
|
|
464
|
+
self.ensure_database_context(cursor)
|
|
465
|
+
|
|
466
|
+
# 获取该 IP 当日的统计数据
|
|
467
|
+
cursor.execute("""
|
|
468
|
+
SELECT
|
|
469
|
+
`请求总数`, `成功次数`, `失败次数`,
|
|
470
|
+
`平均耗时`, `首次访问`, `最后访问`, `访问接口数`
|
|
471
|
+
FROM `api_ip记录`
|
|
472
|
+
WHERE `客户端ip` = %s AND `统计日期` = %s
|
|
473
|
+
""", (client_ip, date))
|
|
474
|
+
|
|
475
|
+
ip_stats = cursor.fetchone()
|
|
476
|
+
if not ip_stats:
|
|
477
|
+
return 0 # 无数据,无风险
|
|
478
|
+
|
|
479
|
+
total_requests = ip_stats['请求总数']
|
|
480
|
+
success_count = ip_stats['成功次数']
|
|
481
|
+
failure_count = ip_stats['失败次数']
|
|
482
|
+
avg_time = float(ip_stats['平均耗时']) if ip_stats['平均耗时'] else 0
|
|
483
|
+
first_access = ip_stats['首次访问']
|
|
484
|
+
last_access = ip_stats['最后访问']
|
|
485
|
+
endpoint_count = ip_stats['访问接口数'] or 0
|
|
486
|
+
|
|
487
|
+
# 1. 请求频率异常检测(30分)
|
|
488
|
+
# 阈值:正常用户单日请求 < 1000,可疑 1000-5000,高风险 > 5000
|
|
489
|
+
if total_requests > 10000:
|
|
490
|
+
risk_score += 30 # 极高频率
|
|
491
|
+
elif total_requests > 5000:
|
|
492
|
+
risk_score += 25 # 高频率
|
|
493
|
+
elif total_requests > 2000:
|
|
494
|
+
risk_score += 18 # 中等频率
|
|
495
|
+
elif total_requests > 1000:
|
|
496
|
+
risk_score += 10 # 略高
|
|
497
|
+
|
|
498
|
+
# 2. 失败率异常检测(25分)
|
|
499
|
+
# 高失败率可能是暴力破解、SQL注入等攻击
|
|
500
|
+
if total_requests > 0:
|
|
501
|
+
failure_rate = failure_count / total_requests
|
|
502
|
+
if failure_rate > 0.8:
|
|
503
|
+
risk_score += 25 # 80%以上失败
|
|
504
|
+
elif failure_rate > 0.5:
|
|
505
|
+
risk_score += 20 # 50%-80%失败
|
|
506
|
+
elif failure_rate > 0.3:
|
|
507
|
+
risk_score += 12 # 30%-50%失败
|
|
508
|
+
elif failure_rate > 0.15:
|
|
509
|
+
risk_score += 5 # 15%-30%失败
|
|
510
|
+
|
|
511
|
+
# 3. 访问模式异常检测(20分)
|
|
512
|
+
# 短时间访问大量不同接口可能是扫描行为
|
|
513
|
+
if endpoint_count > 0:
|
|
514
|
+
# 计算平均每个接口的访问次数
|
|
515
|
+
avg_per_endpoint = total_requests / endpoint_count
|
|
516
|
+
|
|
517
|
+
if endpoint_count > 50 and avg_per_endpoint < 5:
|
|
518
|
+
risk_score += 20 # 访问50+接口,每个接口少于5次(扫描特征)
|
|
519
|
+
elif endpoint_count > 30 and avg_per_endpoint < 10:
|
|
520
|
+
risk_score += 15 # 访问30+接口
|
|
521
|
+
elif endpoint_count > 20 and avg_per_endpoint < 15:
|
|
522
|
+
risk_score += 10 # 访问20+接口
|
|
523
|
+
elif endpoint_count > 10 and avg_per_endpoint < 20:
|
|
524
|
+
risk_score += 5 # 访问10+接口
|
|
525
|
+
|
|
526
|
+
# 4. 时间分布异常检测(15分)
|
|
527
|
+
# 查询该 IP 的小时分布,检测是否有非正常时间段高频访问
|
|
528
|
+
cursor.execute("""
|
|
529
|
+
SELECT
|
|
530
|
+
HOUR(`请求时间`) as hour,
|
|
531
|
+
COUNT(*) as count
|
|
532
|
+
FROM `api_访问日志`
|
|
533
|
+
WHERE `客户端ip` = %s
|
|
534
|
+
AND DATE(`请求时间`) = %s
|
|
535
|
+
GROUP BY HOUR(`请求时间`)
|
|
536
|
+
HAVING count > 100
|
|
537
|
+
ORDER BY count DESC
|
|
538
|
+
""", (client_ip, date))
|
|
539
|
+
|
|
540
|
+
hourly_distribution = cursor.fetchall()
|
|
541
|
+
|
|
542
|
+
# 检测是否有凌晨时段(0-5点)的异常高频访问
|
|
543
|
+
night_requests = sum(h['count'] for h in hourly_distribution if 0 <= h['hour'] <= 5)
|
|
544
|
+
if night_requests > 500:
|
|
545
|
+
risk_score += 15 # 凌晨大量请求(可能是爬虫/攻击)
|
|
546
|
+
elif night_requests > 200:
|
|
547
|
+
risk_score += 10
|
|
548
|
+
elif night_requests > 100:
|
|
549
|
+
risk_score += 5
|
|
550
|
+
|
|
551
|
+
# 5. 响应时间异常检测(10分)
|
|
552
|
+
# 过长的响应时间可能是 DDoS 攻击或资源耗尽攻击
|
|
553
|
+
if avg_time > 5000: # > 5秒
|
|
554
|
+
risk_score += 10
|
|
555
|
+
elif avg_time > 3000: # > 3秒
|
|
556
|
+
risk_score += 7
|
|
557
|
+
elif avg_time > 2000: # > 2秒
|
|
558
|
+
risk_score += 4
|
|
559
|
+
|
|
560
|
+
# 限制评分范围在 0-100
|
|
561
|
+
risk_score = min(100, max(0, risk_score))
|
|
562
|
+
|
|
563
|
+
finally:
|
|
564
|
+
connection.close()
|
|
565
|
+
|
|
566
|
+
except Exception as e:
|
|
567
|
+
# 计算失败时返回 0(保守策略)
|
|
568
|
+
return 0
|
|
569
|
+
|
|
570
|
+
return risk_score
|
|
571
|
+
|
|
440
572
|
# ==================== 核心数据收集 ====================
|
|
441
573
|
|
|
442
574
|
def collect_request_data(self, request) -> Dict[str, Any]:
|
|
@@ -619,6 +751,11 @@ class RouteMonitor:
|
|
|
619
751
|
request_data: 包含请求和响应信息的字典
|
|
620
752
|
"""
|
|
621
753
|
try:
|
|
754
|
+
# 过滤掉 OPTIONS 预检请求,不参与统计
|
|
755
|
+
data_source = request_data.get('数据源', 'none')
|
|
756
|
+
if data_source == 'preflight':
|
|
757
|
+
return # 跳过统计
|
|
758
|
+
|
|
622
759
|
connection = self.pool.connection()
|
|
623
760
|
try:
|
|
624
761
|
with connection.cursor() as cursor:
|
|
@@ -638,7 +775,6 @@ class RouteMonitor:
|
|
|
638
775
|
|
|
639
776
|
# 获取数据源信息
|
|
640
777
|
cache_hit = request_data.get('缓存命中', 0)
|
|
641
|
-
data_source = request_data.get('数据源', 'none')
|
|
642
778
|
is_cache_hit = 1 if cache_hit else 0
|
|
643
779
|
is_db_query = 1 if data_source in ['mysql', 'hybrid'] else 0
|
|
644
780
|
|
|
@@ -681,6 +817,9 @@ class RouteMonitor:
|
|
|
681
817
|
))
|
|
682
818
|
|
|
683
819
|
# 2. 更新 IP 统计表
|
|
820
|
+
client_ip = request_data.get('客户端ip', '')
|
|
821
|
+
|
|
822
|
+
# 首先更新基础统计
|
|
684
823
|
cursor.execute("""
|
|
685
824
|
INSERT INTO `api_ip记录` (
|
|
686
825
|
`统计日期`, `客户端ip`, `请求总数`, `成功次数`, `失败次数`,
|
|
@@ -691,13 +830,13 @@ class RouteMonitor:
|
|
|
691
830
|
`请求总数` = `请求总数` + 1,
|
|
692
831
|
`成功次数` = `成功次数` + %s,
|
|
693
832
|
`失败次数` = `失败次数` + %s,
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
833
|
+
`平均耗时` = (
|
|
834
|
+
(`平均耗时` * `请求总数` + %s) / (`请求总数` + 1)
|
|
835
|
+
),
|
|
697
836
|
`最后访问` = %s
|
|
698
837
|
""", (
|
|
699
838
|
date,
|
|
700
|
-
|
|
839
|
+
client_ip,
|
|
701
840
|
is_success, is_error,
|
|
702
841
|
response_time, now, now,
|
|
703
842
|
is_success, is_error,
|
|
@@ -705,8 +844,46 @@ class RouteMonitor:
|
|
|
705
844
|
now
|
|
706
845
|
))
|
|
707
846
|
|
|
847
|
+
# 计算该 IP 访问的不同接口数量
|
|
848
|
+
cursor.execute("""
|
|
849
|
+
SELECT COUNT(DISTINCT `路由地址`) as endpoint_count
|
|
850
|
+
FROM `api_访问日志`
|
|
851
|
+
WHERE `客户端ip` = %s AND DATE(`请求时间`) = %s
|
|
852
|
+
""", (client_ip, date))
|
|
853
|
+
|
|
854
|
+
endpoint_result = cursor.fetchone()
|
|
855
|
+
endpoint_count = endpoint_result['endpoint_count'] if endpoint_result else 0
|
|
856
|
+
|
|
857
|
+
# 更新访问接口数
|
|
858
|
+
cursor.execute("""
|
|
859
|
+
UPDATE `api_ip记录`
|
|
860
|
+
SET `访问接口数` = %s
|
|
861
|
+
WHERE `客户端ip` = %s AND `统计日期` = %s
|
|
862
|
+
""", (endpoint_count, client_ip, date))
|
|
863
|
+
|
|
708
864
|
connection.commit()
|
|
709
865
|
|
|
866
|
+
# 3. 计算并更新风险评分(在事务外执行,避免影响主流程性能)
|
|
867
|
+
try:
|
|
868
|
+
risk_score = self.calculate_risk_score(client_ip, date)
|
|
869
|
+
if risk_score > 0:
|
|
870
|
+
# 重新获取连接更新风险评分
|
|
871
|
+
connection2 = self.pool.connection()
|
|
872
|
+
try:
|
|
873
|
+
with connection2.cursor() as cursor2:
|
|
874
|
+
self.ensure_database_context(cursor2)
|
|
875
|
+
cursor2.execute("""
|
|
876
|
+
UPDATE `api_ip记录`
|
|
877
|
+
SET `风险评分` = %s
|
|
878
|
+
WHERE `客户端ip` = %s AND `统计日期` = %s
|
|
879
|
+
""", (risk_score, client_ip, date))
|
|
880
|
+
connection2.commit()
|
|
881
|
+
finally:
|
|
882
|
+
connection2.close()
|
|
883
|
+
except Exception:
|
|
884
|
+
# 风险评分计算失败不影响主流程
|
|
885
|
+
pass
|
|
886
|
+
|
|
710
887
|
finally:
|
|
711
888
|
connection.close()
|
|
712
889
|
|
mdbq-4.2.22/mdbq/__version__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
VERSION = '4.2.22'
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|