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.

Files changed (46) hide show
  1. {mdbq-4.2.22 → mdbq-4.2.24}/PKG-INFO +1 -1
  2. mdbq-4.2.24/mdbq/__version__.py +1 -0
  3. {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/redis/redis_cache.py +7 -1
  4. {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/route/monitor.py +184 -7
  5. {mdbq-4.2.22 → mdbq-4.2.24}/mdbq.egg-info/PKG-INFO +1 -1
  6. mdbq-4.2.22/mdbq/__version__.py +0 -1
  7. {mdbq-4.2.22 → mdbq-4.2.24}/README.txt +0 -0
  8. {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/__init__.py +0 -0
  9. {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/auth/__init__.py +0 -0
  10. {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/auth/auth_backend.py +0 -0
  11. {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/auth/crypto.py +0 -0
  12. {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/auth/rate_limiter.py +0 -0
  13. {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/js/__init__.py +0 -0
  14. {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/js/jc.py +0 -0
  15. {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/log/__init__.py +0 -0
  16. {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/log/mylogger.py +0 -0
  17. {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/myconf/__init__.py +0 -0
  18. {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/myconf/myconf.py +0 -0
  19. {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/mysql/__init__.py +0 -0
  20. {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/mysql/deduplicator.py +0 -0
  21. {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/mysql/mysql.py +0 -0
  22. {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/mysql/s_query.py +0 -0
  23. {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/mysql/unique_.py +0 -0
  24. {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/mysql/uploader.py +0 -0
  25. {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/other/__init__.py +0 -0
  26. {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/other/download_sku_picture.py +0 -0
  27. {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/other/error_handler.py +0 -0
  28. {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/other/otk.py +0 -0
  29. {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/other/pov_city.py +0 -0
  30. {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/other/ua_sj.py +0 -0
  31. {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/pbix/__init__.py +0 -0
  32. {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/pbix/pbix_refresh.py +0 -0
  33. {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/pbix/refresh_all.py +0 -0
  34. {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/redis/__init__.py +0 -0
  35. {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/redis/getredis.py +0 -0
  36. {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/route/__init__.py +0 -0
  37. {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/route/analytics.py +0 -0
  38. {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/route/routes.py +0 -0
  39. {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/selenium/__init__.py +0 -0
  40. {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/selenium/get_driver.py +0 -0
  41. {mdbq-4.2.22 → mdbq-4.2.24}/mdbq/spider/__init__.py +0 -0
  42. {mdbq-4.2.22 → mdbq-4.2.24}/mdbq.egg-info/SOURCES.txt +0 -0
  43. {mdbq-4.2.22 → mdbq-4.2.24}/mdbq.egg-info/dependency_links.txt +0 -0
  44. {mdbq-4.2.22 → mdbq-4.2.24}/mdbq.egg-info/top_level.txt +0 -0
  45. {mdbq-4.2.22 → mdbq-4.2.24}/setup.cfg +0 -0
  46. {mdbq-4.2.22 → mdbq-4.2.24}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: mdbq
3
- Version: 4.2.22
3
+ Version: 4.2.24
4
4
  Home-page: https://pypi.org/project/mdbq
5
5
  Author: xigua,
6
6
  Author-email: 2587125111@qq.com
@@ -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
- (`平均耗时` * `请求总数` + %s) / (`请求总数` + 1)
696
- ),
833
+ `平均耗时` = (
834
+ (`平均耗时` * `请求总数` + %s) / (`请求总数` + 1)
835
+ ),
697
836
  `最后访问` = %s
698
837
  """, (
699
838
  date,
700
- request_data.get('客户端ip', ''),
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
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: mdbq
3
- Version: 4.2.22
3
+ Version: 4.2.24
4
4
  Home-page: https://pypi.org/project/mdbq
5
5
  Author: xigua,
6
6
  Author-email: 2587125111@qq.com
@@ -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