mdbq 4.0.80__py3-none-any.whl → 4.0.82__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.
mdbq/route/routes.py ADDED
@@ -0,0 +1,576 @@
1
+ """
2
+ 路由监控管理界面
3
+ 提供完整的监控数据查看和管理功能的Flask路由
4
+
5
+ 主要功能:
6
+ 1. 实时监控面板
7
+ 2. 统计数据查看
8
+ 3. 告警信息展示
9
+ 4. 报告生成
10
+ 5. 数据清理管理
11
+
12
+ """
13
+
14
+ from flask import Blueprint, jsonify, request, render_template_string
15
+ from datetime import datetime, timedelta
16
+ from mdbq.route.monitor import route_monitor, monitor_request, get_request_id
17
+ from mdbq.route.analytics import (
18
+ get_realtime_metrics, get_traffic_trend, get_endpoint_analysis,
19
+ get_user_behavior_analysis, get_performance_alerts, generate_daily_report
20
+ )
21
+ import json
22
+
23
+ # 创建监控管理蓝图
24
+ monitor_bp = Blueprint('monitor', __name__, url_prefix='/admin/monitor')
25
+
26
+
27
+ @monitor_bp.route('/dashboard', methods=['GET'])
28
+ @monitor_request
29
+ def dashboard():
30
+ """监控面板首页"""
31
+ try:
32
+ # 获取实时指标
33
+ realtime_data = get_realtime_metrics()
34
+
35
+ # 获取告警信息
36
+ alerts = get_performance_alerts()
37
+
38
+ return jsonify({
39
+ 'code': 0,
40
+ 'status': 'success',
41
+ 'message': 'success',
42
+ 'data': {
43
+ 'realtime_metrics': realtime_data,
44
+ 'alerts': alerts,
45
+ 'timestamp': datetime.now().isoformat()
46
+ }
47
+ })
48
+
49
+ except Exception as e:
50
+ return jsonify({
51
+ 'code': 500,
52
+ 'status': 'error',
53
+ 'message': 'Failed to get dashboard data',
54
+ 'error': str(e)
55
+ }), 500
56
+
57
+
58
+ @monitor_bp.route('/metrics/realtime', methods=['GET'])
59
+ @monitor_request
60
+ def realtime_metrics():
61
+ """获取实时监控指标"""
62
+ try:
63
+ data = get_realtime_metrics()
64
+ return jsonify({
65
+ 'code': 0,
66
+ 'status': 'success',
67
+ 'message': 'success',
68
+ 'data': data
69
+ })
70
+ except Exception as e:
71
+ return jsonify({
72
+ 'code': 500,
73
+ 'status': 'error',
74
+ 'message': 'Failed to get realtime metrics',
75
+ 'error': str(e)
76
+ }), 500
77
+
78
+
79
+ @monitor_bp.route('/traffic/trend', methods=['GET'])
80
+ @monitor_request
81
+ def traffic_trend():
82
+ """获取流量趋势分析"""
83
+ try:
84
+ days = request.args.get('days', 7, type=int)
85
+ if days < 1 or days > 90:
86
+ days = 7
87
+
88
+ data = get_traffic_trend(days)
89
+ return jsonify({
90
+ 'code': 0,
91
+ 'status': 'success',
92
+ 'message': 'success',
93
+ 'data': data
94
+ })
95
+ except Exception as e:
96
+ return jsonify({
97
+ 'code': 500,
98
+ 'status': 'error',
99
+ 'message': 'Failed to get traffic trend',
100
+ 'error': str(e)
101
+ }), 500
102
+
103
+
104
+ @monitor_bp.route('/endpoints/analysis', methods=['GET'])
105
+ @monitor_request
106
+ def endpoint_analysis():
107
+ """获取端点性能分析"""
108
+ try:
109
+ days = request.args.get('days', 7, type=int)
110
+ if days < 1 or days > 90:
111
+ days = 7
112
+
113
+ data = get_endpoint_analysis(days)
114
+ return jsonify({
115
+ 'code': 0,
116
+ 'status': 'success',
117
+ 'message': 'success',
118
+ 'data': data
119
+ })
120
+ except Exception as e:
121
+ return jsonify({
122
+ 'code': 500,
123
+ 'status': 'error',
124
+ 'message': 'Failed to get endpoint analysis',
125
+ 'error': str(e)
126
+ }), 500
127
+
128
+
129
+ @monitor_bp.route('/users/behavior', methods=['GET'])
130
+ @monitor_request
131
+ def user_behavior():
132
+ """获取用户行为分析"""
133
+ try:
134
+ days = request.args.get('days', 7, type=int)
135
+ if days < 1 or days > 90:
136
+ days = 7
137
+
138
+ data = get_user_behavior_analysis(days)
139
+ return jsonify({
140
+ 'code': 0,
141
+ 'status': 'success',
142
+ 'message': 'success',
143
+ 'data': data
144
+ })
145
+ except Exception as e:
146
+ return jsonify({
147
+ 'code': 500,
148
+ 'status': 'error',
149
+ 'message': 'Failed to get user behavior analysis',
150
+ 'error': str(e)
151
+ }), 500
152
+
153
+
154
+ @monitor_bp.route('/alerts', methods=['GET'])
155
+ @monitor_request
156
+ def alerts():
157
+ """获取性能告警信息"""
158
+ try:
159
+ data = get_performance_alerts()
160
+ return jsonify({
161
+ 'code': 0,
162
+ 'status': 'success',
163
+ 'message': 'success',
164
+ 'data': data
165
+ })
166
+ except Exception as e:
167
+ return jsonify({
168
+ 'code': 500,
169
+ 'status': 'error',
170
+ 'message': 'Failed to get alerts',
171
+ 'error': str(e)
172
+ }), 500
173
+
174
+
175
+ @monitor_bp.route('/reports/daily', methods=['GET'])
176
+ @monitor_request
177
+ def daily_report():
178
+ """获取日报告"""
179
+ try:
180
+ date_str = request.args.get('date')
181
+ target_date = None
182
+
183
+ if date_str:
184
+ try:
185
+ target_date = datetime.strptime(date_str, '%Y-%m-%d').date()
186
+ except ValueError:
187
+ return jsonify({
188
+ 'code': 400,
189
+ 'status': 'error',
190
+ 'message': 'Invalid date format. Use YYYY-MM-DD'
191
+ }), 400
192
+
193
+ data = generate_daily_report(target_date)
194
+ return jsonify({
195
+ 'code': 0,
196
+ 'status': 'success',
197
+ 'message': 'success',
198
+ 'data': data
199
+ })
200
+ except Exception as e:
201
+ return jsonify({
202
+ 'code': 500,
203
+ 'status': 'error',
204
+ 'message': 'Failed to generate daily report',
205
+ 'error': str(e)
206
+ }), 500
207
+
208
+
209
+ @monitor_bp.route('/requests/search', methods=['POST'])
210
+ @monitor_request
211
+ def search_requests():
212
+ """搜索请求记录"""
213
+ try:
214
+ data = request.get_json()
215
+ if not data:
216
+ return jsonify({
217
+ 'code': 400,
218
+ 'status': 'error',
219
+ 'message': 'Missing request data'
220
+ }), 400
221
+
222
+ # 搜索参数
223
+ page = data.get('page', 1)
224
+ page_size = min(data.get('page_size', 50), 200) # 限制页面大小
225
+
226
+ filters = data.get('filters', {})
227
+ start_time = filters.get('start_time')
228
+ end_time = filters.get('end_time')
229
+ endpoint = filters.get('endpoint')
230
+ client_ip = filters.get('client_ip')
231
+ method = filters.get('method')
232
+ status_code = filters.get('status_code')
233
+ min_response_time = filters.get('min_response_time')
234
+
235
+ connection = route_monitor.pool.connection()
236
+ try:
237
+ with connection.cursor() as cursor:
238
+ # 构建查询条件
239
+ where_conditions = ["1=1"]
240
+ params = []
241
+
242
+ if start_time:
243
+ where_conditions.append("timestamp >= %s")
244
+ params.append(start_time)
245
+
246
+ if end_time:
247
+ where_conditions.append("timestamp <= %s")
248
+ params.append(end_time)
249
+
250
+ if endpoint:
251
+ where_conditions.append("endpoint LIKE %s")
252
+ params.append(f"%{endpoint}%")
253
+
254
+ if client_ip:
255
+ where_conditions.append("client_ip = %s")
256
+ params.append(client_ip)
257
+
258
+ if method:
259
+ where_conditions.append("method = %s")
260
+ params.append(method)
261
+
262
+ if status_code:
263
+ where_conditions.append("response_status = %s")
264
+ params.append(status_code)
265
+
266
+ if min_response_time:
267
+ where_conditions.append("process_time >= %s")
268
+ params.append(min_response_time)
269
+
270
+ where_clause = " AND ".join(where_conditions)
271
+
272
+ # 获取总数
273
+ count_sql = f"SELECT COUNT(*) as total FROM api_request_logs WHERE {where_clause}"
274
+ cursor.execute(count_sql, params)
275
+ total_count = cursor.fetchone()['total']
276
+
277
+ # 分页查询
278
+ offset = (page - 1) * page_size
279
+ search_sql = f"""
280
+ SELECT
281
+ request_id, timestamp, method, endpoint, client_ip, real_ip,
282
+ response_status, process_time, user_agent, referer,
283
+ is_bot, is_mobile, browser_name, os_name
284
+ FROM api_request_logs
285
+ WHERE {where_clause}
286
+ ORDER BY timestamp DESC
287
+ LIMIT %s OFFSET %s
288
+ """
289
+
290
+ cursor.execute(search_sql, params + [page_size, offset])
291
+ results = cursor.fetchall()
292
+
293
+ return jsonify({
294
+ 'code': 0,
295
+ 'status': 'success',
296
+ 'message': 'success',
297
+ 'data': {
298
+ 'requests': results,
299
+ 'pagination': {
300
+ 'current_page': page,
301
+ 'page_size': page_size,
302
+ 'total_count': total_count,
303
+ 'total_pages': (total_count + page_size - 1) // page_size
304
+ }
305
+ }
306
+ })
307
+ finally:
308
+ connection.close()
309
+
310
+ except Exception as e:
311
+ return jsonify({
312
+ 'code': 500,
313
+ 'status': 'error',
314
+ 'message': 'Failed to search requests',
315
+ 'error': str(e)
316
+ }), 500
317
+
318
+
319
+ @monitor_bp.route('/requests/<request_id>', methods=['GET'])
320
+ @monitor_request
321
+ def get_request_detail(request_id):
322
+ """获取请求详细信息"""
323
+ try:
324
+ connection = route_monitor.pool.connection()
325
+ try:
326
+ with connection.cursor() as cursor:
327
+ cursor.execute("""
328
+ SELECT * FROM api_request_logs WHERE request_id = %s
329
+ """, (request_id,))
330
+
331
+ request_detail = cursor.fetchone()
332
+
333
+ if not request_detail:
334
+ return jsonify({
335
+ 'code': 404,
336
+ 'status': 'error',
337
+ 'message': 'Request not found'
338
+ }), 404
339
+
340
+ # 安全地转换JSON字段
341
+ json_fields = ['request_headers', 'request_params', 'request_body', 'device_info', 'business_data', 'tags']
342
+ for field in json_fields:
343
+ if request_detail.get(field):
344
+ try:
345
+ # 使用json.loads替代eval,更安全
346
+ if isinstance(request_detail[field], str):
347
+ request_detail[field] = json.loads(request_detail[field])
348
+ except (json.JSONDecodeError, TypeError):
349
+ # 如果解析失败,保持原值
350
+ pass
351
+
352
+ return jsonify({
353
+ 'code': 0,
354
+ 'status': 'success',
355
+ 'message': 'success',
356
+ 'data': request_detail
357
+ })
358
+ finally:
359
+ connection.close()
360
+
361
+ except Exception as e:
362
+ return jsonify({
363
+ 'code': 500,
364
+ 'status': 'error',
365
+ 'message': 'Failed to get request detail',
366
+ 'error': str(e)
367
+ }), 500
368
+
369
+
370
+ @monitor_bp.route('/statistics/summary', methods=['GET'])
371
+ @monitor_request
372
+ def statistics_summary():
373
+ """获取统计摘要"""
374
+ try:
375
+ days = request.args.get('days', 7, type=int)
376
+ if days < 1 or days > 90:
377
+ days = 7
378
+
379
+ connection = route_monitor.pool.connection()
380
+ try:
381
+ with connection.cursor() as cursor:
382
+ end_date = datetime.now().date()
383
+ start_date = end_date - timedelta(days=days)
384
+
385
+ # 综合统计
386
+ cursor.execute("""
387
+ SELECT
388
+ COUNT(*) as total_requests,
389
+ COUNT(DISTINCT client_ip) as unique_ips,
390
+ COUNT(DISTINCT endpoint) as unique_endpoints,
391
+ COUNT(DISTINCT DATE(timestamp)) as active_days,
392
+ AVG(process_time) as avg_response_time,
393
+ MAX(process_time) as max_response_time,
394
+ SUM(CASE WHEN response_status >= 400 THEN 1 ELSE 0 END) as error_count,
395
+ SUM(CASE WHEN response_status >= 400 THEN 1 ELSE 0 END) / COUNT(*) * 100 as error_rate,
396
+ SUM(CASE WHEN is_bot = 1 THEN 1 ELSE 0 END) as bot_requests,
397
+ SUM(CASE WHEN is_mobile = 1 THEN 1 ELSE 0 END) as mobile_requests,
398
+ SUM(request_size) as total_request_size,
399
+ SUM(response_size) as total_response_size
400
+ FROM api_request_logs
401
+ WHERE DATE(timestamp) BETWEEN %s AND %s
402
+ """, (start_date, end_date))
403
+
404
+ summary = cursor.fetchone()
405
+
406
+ # 每日趋势
407
+ cursor.execute("""
408
+ SELECT
409
+ DATE(timestamp) as date,
410
+ COUNT(*) as requests,
411
+ COUNT(DISTINCT client_ip) as unique_ips,
412
+ AVG(process_time) as avg_response_time,
413
+ SUM(CASE WHEN response_status >= 400 THEN 1 ELSE 0 END) as errors
414
+ FROM api_request_logs
415
+ WHERE DATE(timestamp) BETWEEN %s AND %s
416
+ GROUP BY DATE(timestamp)
417
+ ORDER BY date
418
+ """, (start_date, end_date))
419
+
420
+ daily_trend = cursor.fetchall()
421
+
422
+ return jsonify({
423
+ 'code': 0,
424
+ 'status': 'success',
425
+ 'message': 'success',
426
+ 'data': {
427
+ 'period': f'{start_date} to {end_date}',
428
+ 'summary': summary,
429
+ 'daily_trend': daily_trend
430
+ }
431
+ })
432
+ finally:
433
+ connection.close()
434
+
435
+ except Exception as e:
436
+ return jsonify({
437
+ 'code': 500,
438
+ 'status': 'error',
439
+ 'message': 'Failed to get statistics summary',
440
+ 'error': str(e)
441
+ }), 500
442
+
443
+
444
+ @monitor_bp.route('/data/cleanup', methods=['POST'])
445
+ @monitor_request
446
+ def data_cleanup():
447
+ """数据清理功能"""
448
+ try:
449
+ data = request.get_json()
450
+ if not data:
451
+ return jsonify({
452
+ 'code': 400,
453
+ 'status': 'error',
454
+ 'message': 'Missing request data'
455
+ }), 400
456
+
457
+ cleanup_type = data.get('type', 'old_logs')
458
+ days_to_keep = data.get('days_to_keep', 30)
459
+
460
+ if days_to_keep < 7: # 至少保留7天
461
+ return jsonify({
462
+ 'code': 400,
463
+ 'status': 'error',
464
+ 'message': 'Must keep at least 7 days of data'
465
+ }), 400
466
+
467
+ connection = route_monitor.pool.connection()
468
+ try:
469
+ with connection.cursor() as cursor:
470
+ cleanup_date = datetime.now() - timedelta(days=days_to_keep)
471
+
472
+ if cleanup_type == 'old_logs':
473
+ # 清理旧的请求日志
474
+ cursor.execute("""
475
+ DELETE FROM api_request_logs
476
+ WHERE timestamp < %s
477
+ """, (cleanup_date,))
478
+
479
+ deleted_count = cursor.rowcount
480
+
481
+ elif cleanup_type == 'old_statistics':
482
+ # 清理旧的统计数据
483
+ cursor.execute("""
484
+ DELETE FROM api_access_statistics
485
+ WHERE date < %s
486
+ """, (cleanup_date.date(),))
487
+
488
+ deleted_count = cursor.rowcount
489
+
490
+ elif cleanup_type == 'old_ip_stats':
491
+ # 清理旧的IP统计
492
+ cursor.execute("""
493
+ DELETE FROM ip_access_statistics
494
+ WHERE date < %s
495
+ """, (cleanup_date.date(),))
496
+
497
+ deleted_count = cursor.rowcount
498
+
499
+ else:
500
+ return jsonify({
501
+ 'code': 400,
502
+ 'status': 'error',
503
+ 'message': 'Invalid cleanup type'
504
+ }), 400
505
+
506
+ connection.commit()
507
+
508
+ return jsonify({
509
+ 'code': 0,
510
+ 'status': 'success',
511
+ 'message': 'Data cleanup completed',
512
+ 'data': {
513
+ 'cleanup_type': cleanup_type,
514
+ 'deleted_count': deleted_count,
515
+ 'cleanup_date': cleanup_date.isoformat()
516
+ }
517
+ })
518
+ finally:
519
+ connection.close()
520
+
521
+ except Exception as e:
522
+ return jsonify({
523
+ 'code': 500,
524
+ 'status': 'error',
525
+ 'message': 'Failed to cleanup data',
526
+ 'error': str(e)
527
+ }), 500
528
+
529
+
530
+ @monitor_bp.route('/health', methods=['GET'])
531
+ def health_check():
532
+ """监控系统健康检查"""
533
+ try:
534
+ # 检查数据库连接
535
+ connection = route_monitor.pool.connection()
536
+ try:
537
+ with connection.cursor() as cursor:
538
+ cursor.execute("SELECT 1")
539
+ db_status = "OK"
540
+
541
+ # 检查最近的数据
542
+ with connection.cursor() as cursor:
543
+ cursor.execute("""
544
+ SELECT COUNT(*) as recent_count
545
+ FROM api_request_logs
546
+ WHERE timestamp >= %s
547
+ """, (datetime.now() - timedelta(hours=1),))
548
+
549
+ recent_requests = cursor.fetchone()['recent_count']
550
+ finally:
551
+ connection.close()
552
+
553
+ return jsonify({
554
+ 'code': 0,
555
+ 'status': 'success',
556
+ 'message': 'Monitor system is healthy',
557
+ 'data': {
558
+ 'database_status': db_status,
559
+ 'recent_requests_count': recent_requests,
560
+ 'timestamp': datetime.now().isoformat()
561
+ }
562
+ })
563
+
564
+ except Exception as e:
565
+ return jsonify({
566
+ 'code': 500,
567
+ 'status': 'error',
568
+ 'message': 'Monitor system health check failed',
569
+ 'error': str(e)
570
+ }), 500
571
+
572
+
573
+ # 导出蓝图注册函数
574
+ def register_routes(app):
575
+ """注册监控路由到Flask应用"""
576
+ app.register_blueprint(monitor_bp)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mdbq
3
- Version: 4.0.80
3
+ Version: 4.0.82
4
4
  Home-page: https://pypi.org/project/mdbq
5
5
  Author: xigua,
6
6
  Author-email: 2587125111@qq.com
@@ -1,5 +1,5 @@
1
1
  mdbq/__init__.py,sha256=Il5Q9ATdX8yXqVxtP_nYqUhExzxPC_qk_WXQ_4h0exg,16
2
- mdbq/__version__.py,sha256=ByDzwMBbC7L3kfw6c0OURW9HaWVQGz1aeGMC2izh4Go,18
2
+ mdbq/__version__.py,sha256=EhiuwCH5w9iu_L2CjC33SiyfBk24L8BEGAIfGV49q3E,18
3
3
  mdbq/log/__init__.py,sha256=Mpbrav0s0ifLL7lVDAuePEi1hJKiSHhxcv1byBKDl5E,15
4
4
  mdbq/log/mylogger.py,sha256=DyBftCMNLe1pTTXsa830pUtDISJxpJHFIradYtE3lFA,26418
5
5
  mdbq/myconf/__init__.py,sha256=jso1oHcy6cJEfa7udS_9uO5X6kZLoPBF8l3wCYmr5dM,18
@@ -21,10 +21,14 @@ mdbq/pbix/pbix_refresh.py,sha256=JUjKW3bNEyoMVfVfo77UhguvS5AWkixvVhDbw4_MHco,239
21
21
  mdbq/pbix/refresh_all.py,sha256=OBT9EewSZ0aRS9vL_FflVn74d4l2G00wzHiikCC4TC0,5926
22
22
  mdbq/redis/__init__.py,sha256=YtgBlVSMDphtpwYX248wGge1x-Ex_mMufz4-8W0XRmA,12
23
23
  mdbq/redis/getredis.py,sha256=vpBuNc22uj9Vr-_Dh25_wpwWM1e-072EAAIBdB_IpL0,23494
24
+ mdbq/route/__init__.py,sha256=M0NZuQ7-112l8K2h1eayVvSmvQrufrOcD5AYKgIf_Is,1
25
+ mdbq/route/analytics.py,sha256=iJ-LyE_LNICg4LB9XOd0L-N3Ucfl6BWUTVu9jUNAplg,28069
26
+ mdbq/route/monitor.py,sha256=jO5QnlZeug7SIbwm4DS7dIhfoYu4iF2rVPcoLobg6u4,42194
27
+ mdbq/route/routes.py,sha256=DHJg0eRNi7TKqhCHuu8ia3vdQ8cTKwrTm6mwDBtNboM,19111
24
28
  mdbq/selenium/__init__.py,sha256=AKzeEceqZyvqn2dEDoJSzDQnbuENkJSHAlbHAD0u0ZI,10
25
29
  mdbq/selenium/get_driver.py,sha256=1NTlVUE6QsyjTrVVVqTO2LOnYf578ccFWlWnvIXGtic,20903
26
30
  mdbq/spider/__init__.py,sha256=RBMFXGy_jd1HXZhngB2T2XTvJqki8P_Fr-pBcwijnew,18
27
- mdbq-4.0.80.dist-info/METADATA,sha256=n0XzuB1pJqR3VJvk5Vu8OWgMPP3pCfwwj1mvNAyEdTE,364
28
- mdbq-4.0.80.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
29
- mdbq-4.0.80.dist-info/top_level.txt,sha256=2FQ-uLnCSB-OwFiWntzmwosW3X2Xqsg0ewh1axsaylA,5
30
- mdbq-4.0.80.dist-info/RECORD,,
31
+ mdbq-4.0.82.dist-info/METADATA,sha256=jwoqL8TsKwNR_JEcxHOS1UMy4i0cVvHRJs-orjOdy6I,364
32
+ mdbq-4.0.82.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
33
+ mdbq-4.0.82.dist-info/top_level.txt,sha256=2FQ-uLnCSB-OwFiWntzmwosW3X2Xqsg0ewh1axsaylA,5
34
+ mdbq-4.0.82.dist-info/RECORD,,
File without changes