jettask 0.2.18__py3-none-any.whl → 0.2.20__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.
- jettask/__init__.py +60 -2
- jettask/cli.py +314 -228
- jettask/config/__init__.py +9 -1
- jettask/config/config.py +245 -0
- jettask/config/env_loader.py +381 -0
- jettask/config/lua_scripts.py +158 -0
- jettask/config/nacos_config.py +132 -5
- jettask/core/__init__.py +1 -1
- jettask/core/app.py +1573 -666
- jettask/core/app_importer.py +33 -16
- jettask/core/container.py +532 -0
- jettask/core/task.py +1 -4
- jettask/core/unified_manager_base.py +2 -2
- jettask/executor/__init__.py +38 -0
- jettask/executor/core.py +625 -0
- jettask/executor/executor.py +338 -0
- jettask/executor/orchestrator.py +290 -0
- jettask/executor/process_entry.py +638 -0
- jettask/executor/task_executor.py +317 -0
- jettask/messaging/__init__.py +68 -0
- jettask/messaging/event_pool.py +2188 -0
- jettask/messaging/reader.py +519 -0
- jettask/messaging/registry.py +266 -0
- jettask/messaging/scanner.py +369 -0
- jettask/messaging/sender.py +312 -0
- jettask/persistence/__init__.py +118 -0
- jettask/persistence/backlog_monitor.py +567 -0
- jettask/{backend/data_access.py → persistence/base.py} +58 -57
- jettask/persistence/consumer.py +315 -0
- jettask/{core → persistence}/db_manager.py +23 -22
- jettask/persistence/maintenance.py +81 -0
- jettask/persistence/message_consumer.py +259 -0
- jettask/{backend/namespace_data_access.py → persistence/namespace.py} +66 -98
- jettask/persistence/offline_recovery.py +196 -0
- jettask/persistence/queue_discovery.py +215 -0
- jettask/persistence/task_persistence.py +218 -0
- jettask/persistence/task_updater.py +583 -0
- jettask/scheduler/__init__.py +2 -2
- jettask/scheduler/loader.py +6 -5
- jettask/scheduler/run_scheduler.py +1 -1
- jettask/scheduler/scheduler.py +7 -7
- jettask/scheduler/{unified_scheduler_manager.py → scheduler_coordinator.py} +18 -13
- jettask/task/__init__.py +16 -0
- jettask/{router.py → task/router.py} +26 -8
- jettask/task/task_center/__init__.py +9 -0
- jettask/task/task_executor.py +318 -0
- jettask/task/task_registry.py +291 -0
- jettask/test_connection_monitor.py +73 -0
- jettask/utils/__init__.py +31 -1
- jettask/{monitor/run_backlog_collector.py → utils/backlog_collector.py} +1 -1
- jettask/utils/db_connector.py +1629 -0
- jettask/{db_init.py → utils/db_init.py} +1 -1
- jettask/utils/rate_limit/__init__.py +30 -0
- jettask/utils/rate_limit/concurrency_limiter.py +665 -0
- jettask/utils/rate_limit/config.py +145 -0
- jettask/utils/rate_limit/limiter.py +41 -0
- jettask/utils/rate_limit/manager.py +269 -0
- jettask/utils/rate_limit/qps_limiter.py +154 -0
- jettask/utils/rate_limit/task_limiter.py +384 -0
- jettask/utils/serializer.py +3 -0
- jettask/{monitor/stream_backlog_monitor.py → utils/stream_backlog.py} +14 -6
- jettask/utils/time_sync.py +173 -0
- jettask/webui/__init__.py +27 -0
- jettask/{api/v1 → webui/api}/alerts.py +1 -1
- jettask/{api/v1 → webui/api}/analytics.py +2 -2
- jettask/{api/v1 → webui/api}/namespaces.py +1 -1
- jettask/{api/v1 → webui/api}/overview.py +1 -1
- jettask/{api/v1 → webui/api}/queues.py +3 -3
- jettask/{api/v1 → webui/api}/scheduled.py +1 -1
- jettask/{api/v1 → webui/api}/settings.py +1 -1
- jettask/{api.py → webui/app.py} +253 -145
- jettask/webui/namespace_manager/__init__.py +10 -0
- jettask/{multi_namespace_consumer.py → webui/namespace_manager/multi.py} +69 -22
- jettask/{unified_consumer_manager.py → webui/namespace_manager/unified.py} +1 -1
- jettask/{run.py → webui/run.py} +2 -2
- jettask/{services → webui/services}/__init__.py +1 -3
- jettask/{services → webui/services}/overview_service.py +34 -16
- jettask/{services → webui/services}/queue_service.py +1 -1
- jettask/{backend → webui/services}/queue_stats_v2.py +1 -1
- jettask/{services → webui/services}/settings_service.py +1 -1
- jettask/worker/__init__.py +53 -0
- jettask/worker/lifecycle.py +1507 -0
- jettask/worker/manager.py +583 -0
- jettask/{core/offline_worker_recovery.py → worker/recovery.py} +268 -175
- {jettask-0.2.18.dist-info → jettask-0.2.20.dist-info}/METADATA +2 -71
- jettask-0.2.20.dist-info/RECORD +145 -0
- jettask/__main__.py +0 -140
- jettask/api/__init__.py +0 -103
- jettask/backend/__init__.py +0 -1
- jettask/backend/api/__init__.py +0 -3
- jettask/backend/api/v1/__init__.py +0 -17
- jettask/backend/api/v1/monitoring.py +0 -431
- jettask/backend/api/v1/namespaces.py +0 -504
- jettask/backend/api/v1/queues.py +0 -342
- jettask/backend/api/v1/tasks.py +0 -367
- jettask/backend/core/__init__.py +0 -3
- jettask/backend/core/cache.py +0 -221
- jettask/backend/core/database.py +0 -200
- jettask/backend/core/exceptions.py +0 -102
- jettask/backend/dependencies.py +0 -261
- jettask/backend/init_meta_db.py +0 -158
- jettask/backend/main.py +0 -1426
- jettask/backend/main_unified.py +0 -78
- jettask/backend/main_v2.py +0 -394
- jettask/backend/models/__init__.py +0 -3
- jettask/backend/models/requests.py +0 -236
- jettask/backend/models/responses.py +0 -230
- jettask/backend/namespace_api_old.py +0 -267
- jettask/backend/services/__init__.py +0 -3
- jettask/backend/start.py +0 -42
- jettask/backend/unified_api_router.py +0 -1541
- jettask/cleanup_deprecated_tables.sql +0 -16
- jettask/core/consumer_manager.py +0 -1695
- jettask/core/delay_scanner.py +0 -256
- jettask/core/event_pool.py +0 -1700
- jettask/core/heartbeat_process.py +0 -222
- jettask/core/task_batch.py +0 -153
- jettask/core/worker_scanner.py +0 -271
- jettask/executors/__init__.py +0 -5
- jettask/executors/asyncio.py +0 -876
- jettask/executors/base.py +0 -30
- jettask/executors/common.py +0 -148
- jettask/executors/multi_asyncio.py +0 -309
- jettask/gradio_app.py +0 -570
- jettask/integrated_gradio_app.py +0 -1088
- jettask/main.py +0 -0
- jettask/monitoring/__init__.py +0 -3
- jettask/pg_consumer.py +0 -1896
- jettask/run_monitor.py +0 -22
- jettask/run_webui.py +0 -148
- jettask/scheduler/multi_namespace_scheduler.py +0 -294
- jettask/scheduler/unified_manager.py +0 -450
- jettask/task_center_client.py +0 -150
- jettask/utils/serializer_optimized.py +0 -33
- jettask/webui_exceptions.py +0 -67
- jettask-0.2.18.dist-info/RECORD +0 -150
- /jettask/{constants.py → config/constants.py} +0 -0
- /jettask/{backend/config.py → config/task_center.py} +0 -0
- /jettask/{pg_consumer → messaging/pg_consumer}/pg_consumer_v2.py +0 -0
- /jettask/{pg_consumer → messaging/pg_consumer}/sql/add_execution_time_field.sql +0 -0
- /jettask/{pg_consumer → messaging/pg_consumer}/sql/create_new_tables.sql +0 -0
- /jettask/{pg_consumer → messaging/pg_consumer}/sql/create_tables_v3.sql +0 -0
- /jettask/{pg_consumer → messaging/pg_consumer}/sql/migrate_to_new_structure.sql +0 -0
- /jettask/{pg_consumer → messaging/pg_consumer}/sql/modify_time_fields.sql +0 -0
- /jettask/{pg_consumer → messaging/pg_consumer}/sql_utils.py +0 -0
- /jettask/{models.py → persistence/models.py} +0 -0
- /jettask/scheduler/{manager.py → task_crud.py} +0 -0
- /jettask/{schema.sql → schemas/schema.sql} +0 -0
- /jettask/{task_center.py → task/task_center/client.py} +0 -0
- /jettask/{monitoring → utils}/file_watcher.py +0 -0
- /jettask/{services/redis_monitor_service.py → utils/redis_monitor.py} +0 -0
- /jettask/{api/v1 → webui/api}/__init__.py +0 -0
- /jettask/{webui_config.py → webui/config.py} +0 -0
- /jettask/{webui_models → webui/models}/__init__.py +0 -0
- /jettask/{webui_models → webui/models}/namespace.py +0 -0
- /jettask/{services → webui/services}/alert_service.py +0 -0
- /jettask/{services → webui/services}/analytics_service.py +0 -0
- /jettask/{services → webui/services}/scheduled_task_service.py +0 -0
- /jettask/{services → webui/services}/task_service.py +0 -0
- /jettask/{webui_sql → webui/sql}/batch_upsert_functions.sql +0 -0
- /jettask/{webui_sql → webui/sql}/verify_database.sql +0 -0
- {jettask-0.2.18.dist-info → jettask-0.2.20.dist-info}/WHEEL +0 -0
- {jettask-0.2.18.dist-info → jettask-0.2.20.dist-info}/entry_points.txt +0 -0
- {jettask-0.2.18.dist-info → jettask-0.2.20.dist-info}/licenses/LICENSE +0 -0
- {jettask-0.2.18.dist-info → jettask-0.2.20.dist-info}/top_level.txt +0 -0
jettask/backend/api/v1/tasks.py
DELETED
@@ -1,367 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Task management API v1
|
3
|
-
"""
|
4
|
-
from typing import List, Optional
|
5
|
-
from fastapi import APIRouter, Depends, HTTPException, Query, Body
|
6
|
-
from sqlalchemy.ext.asyncio import AsyncSession
|
7
|
-
import redis.asyncio as redis
|
8
|
-
|
9
|
-
from dependencies import (
|
10
|
-
get_validated_namespace, get_pg_connection, get_redis_client,
|
11
|
-
validate_page_params, validate_time_range, get_request_metrics, RequestMetrics
|
12
|
-
)
|
13
|
-
from models.requests import TaskListRequest, TaskActionRequest
|
14
|
-
from models.responses import TaskListResponse, TaskDetailResponse, BaseResponse
|
15
|
-
from core.cache import cache_result, CACHE_CONFIGS
|
16
|
-
from core.exceptions import TaskNotFoundError, ValidationError
|
17
|
-
from data_access import JetTaskDataAccess
|
18
|
-
import logging
|
19
|
-
|
20
|
-
logger = logging.getLogger(__name__)
|
21
|
-
router = APIRouter()
|
22
|
-
|
23
|
-
|
24
|
-
@router.post("/search", response_model=TaskListResponse)
|
25
|
-
async def search_tasks(
|
26
|
-
request: TaskListRequest,
|
27
|
-
namespace: str = Depends(get_validated_namespace),
|
28
|
-
pg_session: AsyncSession = Depends(get_pg_connection),
|
29
|
-
metrics: RequestMetrics = Depends(get_request_metrics)
|
30
|
-
):
|
31
|
-
"""搜索任务(支持复杂筛选条件)"""
|
32
|
-
metrics.start(namespace, "POST /tasks/search")
|
33
|
-
|
34
|
-
try:
|
35
|
-
# 创建数据访问实例
|
36
|
-
data_access = JetTaskDataAccess()
|
37
|
-
|
38
|
-
# 转换筛选条件
|
39
|
-
filters = []
|
40
|
-
for condition in request.filters:
|
41
|
-
filters.append({
|
42
|
-
'field': condition.field,
|
43
|
-
'operator': condition.operator.value,
|
44
|
-
'value': condition.value
|
45
|
-
})
|
46
|
-
|
47
|
-
# 添加基础筛选条件
|
48
|
-
if request.queue_name:
|
49
|
-
filters.append({'field': 'queue', 'operator': 'eq', 'value': request.queue_name})
|
50
|
-
if request.status:
|
51
|
-
filters.append({'field': 'status', 'operator': 'eq', 'value': request.status})
|
52
|
-
if request.consumer_group:
|
53
|
-
filters.append({'field': 'consumer_group', 'operator': 'eq', 'value': request.consumer_group})
|
54
|
-
if request.worker_id:
|
55
|
-
filters.append({'field': 'worker_id', 'operator': 'eq', 'value': request.worker_id})
|
56
|
-
if request.task_name:
|
57
|
-
filters.append({'field': 'task_name', 'operator': 'like', 'value': f"%{request.task_name}%"})
|
58
|
-
|
59
|
-
# 执行查询
|
60
|
-
result = await data_access.fetch_tasks_with_filters(
|
61
|
-
queue_name=request.queue_name or "",
|
62
|
-
page=request.page,
|
63
|
-
page_size=request.page_size,
|
64
|
-
filters=filters,
|
65
|
-
start_time=request.start_time,
|
66
|
-
end_time=request.end_time,
|
67
|
-
sort_field=request.sort_field,
|
68
|
-
sort_order=request.sort_order.value if request.sort_order else 'desc',
|
69
|
-
search=request.search
|
70
|
-
)
|
71
|
-
|
72
|
-
return TaskListResponse.create(
|
73
|
-
data=result['data'],
|
74
|
-
total=result['total'],
|
75
|
-
page=request.page,
|
76
|
-
page_size=request.page_size
|
77
|
-
)
|
78
|
-
|
79
|
-
except Exception as e:
|
80
|
-
logger.error(f"搜索任务失败: {e}")
|
81
|
-
raise HTTPException(status_code=500, detail=str(e))
|
82
|
-
finally:
|
83
|
-
metrics.finish()
|
84
|
-
|
85
|
-
|
86
|
-
@router.get("", response_model=TaskListResponse)
|
87
|
-
@cache_result(**CACHE_CONFIGS['task_details'])
|
88
|
-
async def list_tasks(
|
89
|
-
namespace: str = Depends(get_validated_namespace),
|
90
|
-
queue_name: Optional[str] = Query(None, description="队列名称"),
|
91
|
-
status: Optional[str] = Query(None, description="任务状态"),
|
92
|
-
consumer_group: Optional[str] = Query(None, description="消费者组"),
|
93
|
-
worker_id: Optional[str] = Query(None, description="工作者ID"),
|
94
|
-
task_name: Optional[str] = Query(None, description="任务名称"),
|
95
|
-
page: int = Query(1, ge=1, description="页码"),
|
96
|
-
page_size: int = Query(20, ge=1, le=100, description="每页大小"),
|
97
|
-
sort_field: Optional[str] = Query("created_at", description="排序字段"),
|
98
|
-
sort_order: str = Query("desc", pattern="^(asc|desc)$", description="排序方向"),
|
99
|
-
search: Optional[str] = Query(None, description="搜索关键词"),
|
100
|
-
time_params: dict = Depends(validate_time_range),
|
101
|
-
pg_session: AsyncSession = Depends(get_pg_connection),
|
102
|
-
metrics: RequestMetrics = Depends(get_request_metrics)
|
103
|
-
):
|
104
|
-
"""获取任务列表(简单查询)"""
|
105
|
-
metrics.start(namespace, "GET /tasks")
|
106
|
-
|
107
|
-
try:
|
108
|
-
# 创建数据访问实例
|
109
|
-
data_access = JetTaskDataAccess()
|
110
|
-
|
111
|
-
# 构建筛选条件
|
112
|
-
filters = []
|
113
|
-
if status:
|
114
|
-
filters.append({'field': 'status', 'operator': 'eq', 'value': status})
|
115
|
-
if consumer_group:
|
116
|
-
filters.append({'field': 'consumer_group', 'operator': 'eq', 'value': consumer_group})
|
117
|
-
if worker_id:
|
118
|
-
filters.append({'field': 'worker_id', 'operator': 'eq', 'value': worker_id})
|
119
|
-
if task_name:
|
120
|
-
filters.append({'field': 'task_name', 'operator': 'like', 'value': f"%{task_name}%"})
|
121
|
-
|
122
|
-
# 执行查询
|
123
|
-
result = await data_access.fetch_tasks_with_filters(
|
124
|
-
queue_name=queue_name or "",
|
125
|
-
page=page,
|
126
|
-
page_size=page_size,
|
127
|
-
filters=filters,
|
128
|
-
start_time=time_params.get('start_time'),
|
129
|
-
end_time=time_params.get('end_time'),
|
130
|
-
sort_field=sort_field,
|
131
|
-
sort_order=sort_order,
|
132
|
-
search=search
|
133
|
-
)
|
134
|
-
|
135
|
-
return TaskListResponse.create(
|
136
|
-
data=result['data'],
|
137
|
-
total=result['total'],
|
138
|
-
page=page,
|
139
|
-
page_size=page_size
|
140
|
-
)
|
141
|
-
|
142
|
-
except Exception as e:
|
143
|
-
logger.error(f"获取任务列表失败: {e}")
|
144
|
-
raise HTTPException(status_code=500, detail=str(e))
|
145
|
-
finally:
|
146
|
-
metrics.finish()
|
147
|
-
|
148
|
-
|
149
|
-
@router.get("/{task_id}", response_model=TaskDetailResponse)
|
150
|
-
@cache_result(**CACHE_CONFIGS['task_details'])
|
151
|
-
async def get_task_detail(
|
152
|
-
task_id: str,
|
153
|
-
namespace: str = Depends(get_validated_namespace),
|
154
|
-
consumer_group: Optional[str] = Query(None, description="消费者组"),
|
155
|
-
pg_session: AsyncSession = Depends(get_pg_connection),
|
156
|
-
metrics: RequestMetrics = Depends(get_request_metrics)
|
157
|
-
):
|
158
|
-
"""获取任务详情"""
|
159
|
-
metrics.start(namespace, f"GET /tasks/{task_id}")
|
160
|
-
|
161
|
-
try:
|
162
|
-
# 创建数据访问实例
|
163
|
-
data_access = JetTaskDataAccess()
|
164
|
-
|
165
|
-
# 获取任务详情
|
166
|
-
task_detail = await data_access.fetch_task_details(task_id, consumer_group)
|
167
|
-
|
168
|
-
if not task_detail:
|
169
|
-
raise TaskNotFoundError(task_id)
|
170
|
-
|
171
|
-
return TaskDetailResponse(data=task_detail)
|
172
|
-
|
173
|
-
except TaskNotFoundError:
|
174
|
-
raise
|
175
|
-
except Exception as e:
|
176
|
-
logger.error(f"获取任务详情失败: {e}")
|
177
|
-
raise HTTPException(status_code=500, detail=str(e))
|
178
|
-
finally:
|
179
|
-
metrics.finish()
|
180
|
-
|
181
|
-
|
182
|
-
@router.post("/actions", response_model=BaseResponse)
|
183
|
-
async def execute_task_actions(
|
184
|
-
action_request: TaskActionRequest,
|
185
|
-
namespace: str = Depends(get_validated_namespace),
|
186
|
-
redis_client: redis.Redis = Depends(get_redis_client),
|
187
|
-
pg_session: AsyncSession = Depends(get_pg_connection),
|
188
|
-
metrics: RequestMetrics = Depends(get_request_metrics)
|
189
|
-
):
|
190
|
-
"""批量执行任务操作"""
|
191
|
-
metrics.start(namespace, "POST /tasks/actions")
|
192
|
-
|
193
|
-
try:
|
194
|
-
action = action_request.action.lower()
|
195
|
-
task_ids = action_request.task_ids
|
196
|
-
parameters = action_request.parameters
|
197
|
-
|
198
|
-
if not task_ids:
|
199
|
-
raise ValidationError("task_ids cannot be empty")
|
200
|
-
|
201
|
-
# 验证任务ID格式
|
202
|
-
for task_id in task_ids:
|
203
|
-
if not task_id or not isinstance(task_id, str):
|
204
|
-
raise ValidationError(f"Invalid task_id: {task_id}")
|
205
|
-
|
206
|
-
success_count = 0
|
207
|
-
failed_count = 0
|
208
|
-
errors = []
|
209
|
-
|
210
|
-
if action == "retry":
|
211
|
-
# 重试失败任务
|
212
|
-
for task_id in task_ids:
|
213
|
-
try:
|
214
|
-
# 这里需要实现重试逻辑
|
215
|
-
# 1. 检查任务状态是否为failed
|
216
|
-
# 2. 重新入队任务
|
217
|
-
# 3. 更新任务状态
|
218
|
-
success_count += 1
|
219
|
-
except Exception as e:
|
220
|
-
failed_count += 1
|
221
|
-
errors.append(f"Task {task_id}: {str(e)}")
|
222
|
-
|
223
|
-
elif action == "cancel":
|
224
|
-
# 取消待处理任务
|
225
|
-
for task_id in task_ids:
|
226
|
-
try:
|
227
|
-
# 这里需要实现取消逻辑
|
228
|
-
# 1. 检查任务状态
|
229
|
-
# 2. 从队列中移除
|
230
|
-
# 3. 更新任务状态为cancelled
|
231
|
-
success_count += 1
|
232
|
-
except Exception as e:
|
233
|
-
failed_count += 1
|
234
|
-
errors.append(f"Task {task_id}: {str(e)}")
|
235
|
-
|
236
|
-
elif action == "delete":
|
237
|
-
# 删除任务记录
|
238
|
-
for task_id in task_ids:
|
239
|
-
try:
|
240
|
-
# 这里需要实现删除逻辑
|
241
|
-
# 1. 从数据库删除任务记录
|
242
|
-
# 2. 清理相关数据
|
243
|
-
success_count += 1
|
244
|
-
except Exception as e:
|
245
|
-
failed_count += 1
|
246
|
-
errors.append(f"Task {task_id}: {str(e)}")
|
247
|
-
|
248
|
-
else:
|
249
|
-
raise ValidationError(f"Unsupported action: {action}")
|
250
|
-
|
251
|
-
# 构造响应消息
|
252
|
-
message = f"Action '{action}' executed: {success_count} succeeded"
|
253
|
-
if failed_count > 0:
|
254
|
-
message += f", {failed_count} failed"
|
255
|
-
|
256
|
-
response_data = {
|
257
|
-
'action': action,
|
258
|
-
'total_tasks': len(task_ids),
|
259
|
-
'success_count': success_count,
|
260
|
-
'failed_count': failed_count,
|
261
|
-
'errors': errors[:10] # 只返回前10个错误
|
262
|
-
}
|
263
|
-
|
264
|
-
return BaseResponse(
|
265
|
-
message=message,
|
266
|
-
data=response_data
|
267
|
-
)
|
268
|
-
|
269
|
-
except ValidationError:
|
270
|
-
raise
|
271
|
-
except Exception as e:
|
272
|
-
logger.error(f"执行任务操作失败: {e}")
|
273
|
-
raise HTTPException(status_code=500, detail=str(e))
|
274
|
-
finally:
|
275
|
-
metrics.finish()
|
276
|
-
|
277
|
-
|
278
|
-
@router.get("/{task_id}/logs")
|
279
|
-
async def get_task_logs(
|
280
|
-
task_id: str,
|
281
|
-
namespace: str = Depends(get_validated_namespace),
|
282
|
-
lines: int = Query(100, ge=1, le=1000, description="日志行数"),
|
283
|
-
follow: bool = Query(False, description="是否跟踪日志"),
|
284
|
-
pg_session: AsyncSession = Depends(get_pg_connection),
|
285
|
-
metrics: RequestMetrics = Depends(get_request_metrics)
|
286
|
-
):
|
287
|
-
"""获取任务执行日志"""
|
288
|
-
metrics.start(namespace, f"GET /tasks/{task_id}/logs")
|
289
|
-
|
290
|
-
try:
|
291
|
-
# 这里需要实现日志获取逻辑
|
292
|
-
# 1. 从数据库或日志系统获取任务执行日志
|
293
|
-
# 2. 支持实时日志跟踪(WebSocket)
|
294
|
-
|
295
|
-
# 暂时返回模拟数据
|
296
|
-
logs = [
|
297
|
-
{"timestamp": "2025-09-08T12:00:00Z", "level": "INFO", "message": "Task started"},
|
298
|
-
{"timestamp": "2025-09-08T12:00:01Z", "level": "DEBUG", "message": "Processing data..."},
|
299
|
-
{"timestamp": "2025-09-08T12:00:02Z", "level": "INFO", "message": "Task completed successfully"}
|
300
|
-
]
|
301
|
-
|
302
|
-
return BaseResponse(
|
303
|
-
data={
|
304
|
-
"task_id": task_id,
|
305
|
-
"logs": logs[-lines:],
|
306
|
-
"total_lines": len(logs),
|
307
|
-
"truncated": len(logs) > lines
|
308
|
-
}
|
309
|
-
)
|
310
|
-
|
311
|
-
except Exception as e:
|
312
|
-
logger.error(f"获取任务日志失败: {e}")
|
313
|
-
raise HTTPException(status_code=500, detail=str(e))
|
314
|
-
finally:
|
315
|
-
metrics.finish()
|
316
|
-
|
317
|
-
|
318
|
-
@router.get("/statistics/summary")
|
319
|
-
async def get_task_statistics(
|
320
|
-
namespace: str = Depends(get_validated_namespace),
|
321
|
-
queue_name: Optional[str] = Query(None, description="队列名称"),
|
322
|
-
time_params: dict = Depends(validate_time_range),
|
323
|
-
pg_session: AsyncSession = Depends(get_pg_connection),
|
324
|
-
metrics: RequestMetrics = Depends(get_request_metrics)
|
325
|
-
):
|
326
|
-
"""获取任务统计摘要"""
|
327
|
-
metrics.start(namespace, "GET /tasks/statistics/summary")
|
328
|
-
|
329
|
-
try:
|
330
|
-
# 这里需要实现统计查询逻辑
|
331
|
-
# 1. 按状态统计任务数量
|
332
|
-
# 2. 按时间统计任务趋势
|
333
|
-
# 3. 按队列统计任务分布
|
334
|
-
# 4. 计算成功率、平均执行时间等指标
|
335
|
-
|
336
|
-
# 暂时返回模拟数据
|
337
|
-
statistics = {
|
338
|
-
"total_tasks": 12345,
|
339
|
-
"by_status": {
|
340
|
-
"pending": 123,
|
341
|
-
"running": 45,
|
342
|
-
"completed": 11000,
|
343
|
-
"failed": 1177
|
344
|
-
},
|
345
|
-
"by_queue": {
|
346
|
-
"shared_queue": 8000,
|
347
|
-
"priority_queue": 3000,
|
348
|
-
"background_queue": 1345
|
349
|
-
},
|
350
|
-
"metrics": {
|
351
|
-
"success_rate": 0.905,
|
352
|
-
"avg_execution_time": 12.34,
|
353
|
-
"tasks_per_hour": 234.5
|
354
|
-
},
|
355
|
-
"trends": {
|
356
|
-
"hourly_counts": [],
|
357
|
-
"error_rates": []
|
358
|
-
}
|
359
|
-
}
|
360
|
-
|
361
|
-
return BaseResponse(data=statistics)
|
362
|
-
|
363
|
-
except Exception as e:
|
364
|
-
logger.error(f"获取任务统计失败: {e}")
|
365
|
-
raise HTTPException(status_code=500, detail=str(e))
|
366
|
-
finally:
|
367
|
-
metrics.finish()
|
jettask/backend/core/__init__.py
DELETED
jettask/backend/core/cache.py
DELETED
@@ -1,221 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Caching utilities for JetTask WebUI Backend
|
3
|
-
"""
|
4
|
-
import json
|
5
|
-
import hashlib
|
6
|
-
from typing import Any, Optional, Dict, Callable, TypeVar, Union
|
7
|
-
from functools import wraps
|
8
|
-
import asyncio
|
9
|
-
import time
|
10
|
-
import logging
|
11
|
-
|
12
|
-
import redis.asyncio as redis
|
13
|
-
|
14
|
-
logger = logging.getLogger(__name__)
|
15
|
-
|
16
|
-
T = TypeVar('T')
|
17
|
-
|
18
|
-
|
19
|
-
class CacheManager:
|
20
|
-
"""缓存管理器"""
|
21
|
-
|
22
|
-
def __init__(self, redis_client: Optional[redis.Redis] = None):
|
23
|
-
self.redis_client = redis_client
|
24
|
-
self.local_cache: Dict[str, Dict[str, Any]] = {}
|
25
|
-
self.cache_stats = {
|
26
|
-
'hits': 0,
|
27
|
-
'misses': 0,
|
28
|
-
'sets': 0,
|
29
|
-
'deletes': 0
|
30
|
-
}
|
31
|
-
|
32
|
-
def _generate_key(self, prefix: str, *args, **kwargs) -> str:
|
33
|
-
"""生成缓存键"""
|
34
|
-
key_data = f"{prefix}:{args}:{sorted(kwargs.items())}"
|
35
|
-
return f"jettask:cache:{hashlib.md5(key_data.encode()).hexdigest()}"
|
36
|
-
|
37
|
-
async def get(self, key: str) -> Optional[Any]:
|
38
|
-
"""获取缓存值"""
|
39
|
-
try:
|
40
|
-
# 先检查本地缓存
|
41
|
-
if key in self.local_cache:
|
42
|
-
local_data = self.local_cache[key]
|
43
|
-
if local_data['expires'] > time.time():
|
44
|
-
self.cache_stats['hits'] += 1
|
45
|
-
return local_data['value']
|
46
|
-
else:
|
47
|
-
del self.local_cache[key]
|
48
|
-
|
49
|
-
# 检查Redis缓存
|
50
|
-
if self.redis_client:
|
51
|
-
value = await self.redis_client.get(key)
|
52
|
-
if value:
|
53
|
-
self.cache_stats['hits'] += 1
|
54
|
-
return json.loads(value)
|
55
|
-
|
56
|
-
self.cache_stats['misses'] += 1
|
57
|
-
return None
|
58
|
-
|
59
|
-
except Exception as e:
|
60
|
-
logger.warning(f"缓存获取失败: {e}")
|
61
|
-
self.cache_stats['misses'] += 1
|
62
|
-
return None
|
63
|
-
|
64
|
-
async def set(
|
65
|
-
self,
|
66
|
-
key: str,
|
67
|
-
value: Any,
|
68
|
-
ttl: int = 300,
|
69
|
-
local_only: bool = False
|
70
|
-
) -> bool:
|
71
|
-
"""设置缓存值"""
|
72
|
-
try:
|
73
|
-
# 设置本地缓存
|
74
|
-
if ttl <= 60 or local_only: # 短期缓存或仅本地缓存
|
75
|
-
self.local_cache[key] = {
|
76
|
-
'value': value,
|
77
|
-
'expires': time.time() + ttl
|
78
|
-
}
|
79
|
-
|
80
|
-
# 设置Redis缓存
|
81
|
-
if self.redis_client and not local_only:
|
82
|
-
await self.redis_client.setex(
|
83
|
-
key,
|
84
|
-
ttl,
|
85
|
-
json.dumps(value, default=str)
|
86
|
-
)
|
87
|
-
|
88
|
-
self.cache_stats['sets'] += 1
|
89
|
-
return True
|
90
|
-
|
91
|
-
except Exception as e:
|
92
|
-
logger.warning(f"缓存设置失败: {e}")
|
93
|
-
return False
|
94
|
-
|
95
|
-
async def delete(self, key: str) -> bool:
|
96
|
-
"""删除缓存"""
|
97
|
-
try:
|
98
|
-
# 删除本地缓存
|
99
|
-
if key in self.local_cache:
|
100
|
-
del self.local_cache[key]
|
101
|
-
|
102
|
-
# 删除Redis缓存
|
103
|
-
if self.redis_client:
|
104
|
-
await self.redis_client.delete(key)
|
105
|
-
|
106
|
-
self.cache_stats['deletes'] += 1
|
107
|
-
return True
|
108
|
-
|
109
|
-
except Exception as e:
|
110
|
-
logger.warning(f"缓存删除失败: {e}")
|
111
|
-
return False
|
112
|
-
|
113
|
-
async def clear_pattern(self, pattern: str) -> int:
|
114
|
-
"""根据模式删除缓存"""
|
115
|
-
try:
|
116
|
-
deleted = 0
|
117
|
-
|
118
|
-
# 清理本地缓存
|
119
|
-
keys_to_delete = [k for k in self.local_cache.keys() if pattern in k]
|
120
|
-
for key in keys_to_delete:
|
121
|
-
del self.local_cache[key]
|
122
|
-
deleted += 1
|
123
|
-
|
124
|
-
# 清理Redis缓存
|
125
|
-
if self.redis_client:
|
126
|
-
keys = await self.redis_client.keys(f"*{pattern}*")
|
127
|
-
if keys:
|
128
|
-
await self.redis_client.delete(*keys)
|
129
|
-
deleted += len(keys)
|
130
|
-
|
131
|
-
self.cache_stats['deletes'] += deleted
|
132
|
-
return deleted
|
133
|
-
|
134
|
-
except Exception as e:
|
135
|
-
logger.warning(f"批量删除缓存失败: {e}")
|
136
|
-
return 0
|
137
|
-
|
138
|
-
def cleanup_expired(self):
|
139
|
-
"""清理过期的本地缓存"""
|
140
|
-
current_time = time.time()
|
141
|
-
expired_keys = [
|
142
|
-
key for key, data in self.local_cache.items()
|
143
|
-
if data['expires'] <= current_time
|
144
|
-
]
|
145
|
-
for key in expired_keys:
|
146
|
-
del self.local_cache[key]
|
147
|
-
|
148
|
-
def get_stats(self) -> Dict[str, Any]:
|
149
|
-
"""获取缓存统计信息"""
|
150
|
-
hit_rate = (
|
151
|
-
self.cache_stats['hits'] /
|
152
|
-
(self.cache_stats['hits'] + self.cache_stats['misses'])
|
153
|
-
if (self.cache_stats['hits'] + self.cache_stats['misses']) > 0
|
154
|
-
else 0
|
155
|
-
)
|
156
|
-
|
157
|
-
return {
|
158
|
-
**self.cache_stats,
|
159
|
-
'hit_rate': hit_rate,
|
160
|
-
'local_cache_size': len(self.local_cache)
|
161
|
-
}
|
162
|
-
|
163
|
-
|
164
|
-
# 全局缓存管理器
|
165
|
-
cache_manager = CacheManager()
|
166
|
-
|
167
|
-
|
168
|
-
def cache_result(
|
169
|
-
ttl: int = 300,
|
170
|
-
key_prefix: str = "default",
|
171
|
-
local_only: bool = False
|
172
|
-
):
|
173
|
-
"""缓存装饰器"""
|
174
|
-
def decorator(func: Callable[..., T]) -> Callable[..., T]:
|
175
|
-
@wraps(func)
|
176
|
-
async def wrapper(*args, **kwargs) -> T:
|
177
|
-
# 生成缓存键
|
178
|
-
cache_key = cache_manager._generate_key(
|
179
|
-
f"{key_prefix}:{func.__name__}",
|
180
|
-
*args,
|
181
|
-
**kwargs
|
182
|
-
)
|
183
|
-
|
184
|
-
# 尝试从缓存获取
|
185
|
-
cached_result = await cache_manager.get(cache_key)
|
186
|
-
if cached_result is not None:
|
187
|
-
return cached_result
|
188
|
-
|
189
|
-
# 执行函数
|
190
|
-
result = await func(*args, **kwargs)
|
191
|
-
|
192
|
-
# 缓存结果
|
193
|
-
await cache_manager.set(cache_key, result, ttl, local_only)
|
194
|
-
|
195
|
-
return result
|
196
|
-
|
197
|
-
return wrapper
|
198
|
-
return decorator
|
199
|
-
|
200
|
-
|
201
|
-
def invalidate_cache(pattern: str):
|
202
|
-
"""缓存失效装饰器"""
|
203
|
-
def decorator(func: Callable[..., T]) -> Callable[..., T]:
|
204
|
-
@wraps(func)
|
205
|
-
async def wrapper(*args, **kwargs) -> T:
|
206
|
-
result = await func(*args, **kwargs)
|
207
|
-
# 执行后清理相关缓存
|
208
|
-
await cache_manager.clear_pattern(pattern)
|
209
|
-
return result
|
210
|
-
|
211
|
-
return wrapper
|
212
|
-
return decorator
|
213
|
-
|
214
|
-
|
215
|
-
# 预定义的缓存配置
|
216
|
-
CACHE_CONFIGS = {
|
217
|
-
'queue_stats': {'ttl': 300, 'key_prefix': 'queue_stats'}, # 5分钟
|
218
|
-
'task_details': {'ttl': 60, 'key_prefix': 'task_details'}, # 1分钟
|
219
|
-
'namespace_config': {'ttl': 1800, 'key_prefix': 'namespace_config'}, # 30分钟
|
220
|
-
'monitoring_data': {'ttl': 30, 'key_prefix': 'monitoring_data', 'local_only': True}, # 30秒,仅本地
|
221
|
-
}
|