jettask 0.2.23__py3-none-any.whl → 0.2.24__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.
Files changed (110) hide show
  1. jettask/__init__.py +2 -0
  2. jettask/cli.py +12 -8
  3. jettask/config/lua_scripts.py +37 -0
  4. jettask/config/nacos_config.py +1 -1
  5. jettask/core/app.py +313 -340
  6. jettask/core/container.py +4 -4
  7. jettask/{persistence → core}/namespace.py +93 -27
  8. jettask/core/task.py +16 -9
  9. jettask/core/unified_manager_base.py +136 -26
  10. jettask/db/__init__.py +67 -0
  11. jettask/db/base.py +137 -0
  12. jettask/{utils/db_connector.py → db/connector.py} +130 -26
  13. jettask/db/models/__init__.py +16 -0
  14. jettask/db/models/scheduled_task.py +196 -0
  15. jettask/db/models/task.py +77 -0
  16. jettask/db/models/task_run.py +85 -0
  17. jettask/executor/__init__.py +0 -15
  18. jettask/executor/core.py +76 -31
  19. jettask/executor/process_entry.py +29 -114
  20. jettask/executor/task_executor.py +4 -0
  21. jettask/messaging/event_pool.py +928 -685
  22. jettask/messaging/scanner.py +30 -0
  23. jettask/persistence/__init__.py +28 -103
  24. jettask/persistence/buffer.py +170 -0
  25. jettask/persistence/consumer.py +330 -249
  26. jettask/persistence/manager.py +304 -0
  27. jettask/persistence/persistence.py +391 -0
  28. jettask/scheduler/__init__.py +15 -3
  29. jettask/scheduler/{task_crud.py → database.py} +61 -57
  30. jettask/scheduler/loader.py +2 -2
  31. jettask/scheduler/{scheduler_coordinator.py → manager.py} +23 -6
  32. jettask/scheduler/models.py +14 -10
  33. jettask/scheduler/schedule.py +166 -0
  34. jettask/scheduler/scheduler.py +12 -11
  35. jettask/schemas/__init__.py +50 -1
  36. jettask/schemas/backlog.py +43 -6
  37. jettask/schemas/namespace.py +70 -19
  38. jettask/schemas/queue.py +19 -3
  39. jettask/schemas/responses.py +493 -0
  40. jettask/task/__init__.py +0 -2
  41. jettask/task/router.py +3 -0
  42. jettask/test_connection_monitor.py +1 -1
  43. jettask/utils/__init__.py +7 -5
  44. jettask/utils/db_init.py +8 -4
  45. jettask/utils/namespace_dep.py +167 -0
  46. jettask/utils/queue_matcher.py +186 -0
  47. jettask/utils/rate_limit/concurrency_limiter.py +7 -1
  48. jettask/utils/stream_backlog.py +1 -1
  49. jettask/webui/__init__.py +0 -1
  50. jettask/webui/api/__init__.py +4 -4
  51. jettask/webui/api/alerts.py +806 -71
  52. jettask/webui/api/example_refactored.py +400 -0
  53. jettask/webui/api/namespaces.py +390 -45
  54. jettask/webui/api/overview.py +300 -54
  55. jettask/webui/api/queues.py +971 -267
  56. jettask/webui/api/scheduled.py +1249 -56
  57. jettask/webui/api/settings.py +129 -7
  58. jettask/webui/api/workers.py +442 -0
  59. jettask/webui/app.py +46 -2329
  60. jettask/webui/middleware/__init__.py +6 -0
  61. jettask/webui/middleware/namespace_middleware.py +135 -0
  62. jettask/webui/services/__init__.py +146 -0
  63. jettask/webui/services/heartbeat_service.py +251 -0
  64. jettask/webui/services/overview_service.py +60 -51
  65. jettask/webui/services/queue_monitor_service.py +426 -0
  66. jettask/webui/services/redis_monitor_service.py +87 -0
  67. jettask/webui/services/settings_service.py +174 -111
  68. jettask/webui/services/task_monitor_service.py +222 -0
  69. jettask/webui/services/timeline_pg_service.py +452 -0
  70. jettask/webui/services/timeline_service.py +189 -0
  71. jettask/webui/services/worker_monitor_service.py +467 -0
  72. jettask/webui/utils/__init__.py +11 -0
  73. jettask/webui/utils/time_utils.py +122 -0
  74. jettask/worker/lifecycle.py +8 -2
  75. {jettask-0.2.23.dist-info → jettask-0.2.24.dist-info}/METADATA +1 -1
  76. jettask-0.2.24.dist-info/RECORD +142 -0
  77. jettask/executor/executor.py +0 -338
  78. jettask/persistence/backlog_monitor.py +0 -567
  79. jettask/persistence/base.py +0 -2334
  80. jettask/persistence/db_manager.py +0 -516
  81. jettask/persistence/maintenance.py +0 -81
  82. jettask/persistence/message_consumer.py +0 -259
  83. jettask/persistence/models.py +0 -49
  84. jettask/persistence/offline_recovery.py +0 -196
  85. jettask/persistence/queue_discovery.py +0 -215
  86. jettask/persistence/task_persistence.py +0 -218
  87. jettask/persistence/task_updater.py +0 -583
  88. jettask/scheduler/add_execution_count.sql +0 -11
  89. jettask/scheduler/add_priority_field.sql +0 -26
  90. jettask/scheduler/add_scheduler_id.sql +0 -25
  91. jettask/scheduler/add_scheduler_id_index.sql +0 -10
  92. jettask/scheduler/make_scheduler_id_required.sql +0 -28
  93. jettask/scheduler/migrate_interval_seconds.sql +0 -9
  94. jettask/scheduler/performance_optimization.sql +0 -45
  95. jettask/scheduler/run_scheduler.py +0 -186
  96. jettask/scheduler/schema.sql +0 -84
  97. jettask/task/task_executor.py +0 -318
  98. jettask/webui/api/analytics.py +0 -323
  99. jettask/webui/config.py +0 -90
  100. jettask/webui/models/__init__.py +0 -3
  101. jettask/webui/models/namespace.py +0 -63
  102. jettask/webui/namespace_manager/__init__.py +0 -10
  103. jettask/webui/namespace_manager/multi.py +0 -593
  104. jettask/webui/namespace_manager/unified.py +0 -193
  105. jettask/webui/run.py +0 -46
  106. jettask-0.2.23.dist-info/RECORD +0 -145
  107. {jettask-0.2.23.dist-info → jettask-0.2.24.dist-info}/WHEEL +0 -0
  108. {jettask-0.2.23.dist-info → jettask-0.2.24.dist-info}/entry_points.txt +0 -0
  109. {jettask-0.2.23.dist-info → jettask-0.2.24.dist-info}/licenses/LICENSE +0 -0
  110. {jettask-0.2.23.dist-info → jettask-0.2.24.dist-info}/top_level.txt +0 -0
@@ -1,17 +1,29 @@
1
1
  """
2
2
  定时任务调度模块
3
- 支持 Redis + PostgreSQL 双存储方案
3
+
4
+ 文件说明:
5
+ - models.py: 数据模型(ScheduledTask, TaskExecutionHistory)
6
+ - schedule.py: Schedule 定义类(用于定义定时任务)
7
+ - scheduler.py: 核心调度器(TaskScheduler)
8
+ - loader.py: 任务加载器(TaskLoader)
9
+ - database.py: 数据库操作(ScheduledTaskManager)
10
+ - manager.py: 统一调度器管理器(UnifiedSchedulerManager,用于CLI)
11
+ - sql/: SQL文件目录
12
+ - schema.sql: 数据库表结构
13
+ - migrations/: 数据库迁移脚本
4
14
  """
5
15
 
6
16
  from .models import ScheduledTask, TaskExecutionHistory
7
17
  from .scheduler import TaskScheduler
8
18
  from .loader import TaskLoader
9
- from .task_crud import ScheduledTaskManager
19
+ from .database import ScheduledTaskManager
20
+ from .schedule import Schedule
10
21
 
11
22
  __all__ = [
12
23
  'ScheduledTask',
13
24
  'TaskExecutionHistory',
14
25
  'TaskScheduler',
15
26
  'TaskLoader',
16
- 'ScheduledTaskManager'
27
+ 'ScheduledTaskManager',
28
+ 'Schedule'
17
29
  ]
@@ -8,6 +8,7 @@ from datetime import datetime, timedelta
8
8
  import json
9
9
 
10
10
  from .models import ScheduledTask, TaskExecutionHistory, TaskType, TaskStatus
11
+ from jettask.db.connector import get_asyncpg_pool
11
12
 
12
13
 
13
14
  class ScheduledTaskManager:
@@ -34,14 +35,24 @@ class ScheduledTaskManager:
34
35
 
35
36
  self.pool: Optional[asyncpg.Pool] = None
36
37
 
37
- async def connect(self):
38
- """建立数据库连接池"""
38
+ async def connect(self, max_retries: int = 3, retry_delay: int = 5):
39
+ """
40
+ 建立数据库连接池(使用统一的连接池管理)
41
+
42
+ Args:
43
+ max_retries: 最大重试次数,默认3次
44
+ retry_delay: 重试间隔(秒),默认5秒
45
+ """
39
46
  if not self.pool:
40
- self.pool = await asyncpg.create_pool(
41
- self.db_url,
47
+ # 使用统一的 get_asyncpg_pool 函数,它内部已经包含了重试机制和日志
48
+ self.pool = await get_asyncpg_pool(
49
+ dsn=self.db_url,
42
50
  min_size=2,
43
51
  max_size=10,
44
- command_timeout=60
52
+ command_timeout=60,
53
+ timeout=10, # 连接超时10秒
54
+ max_retries=max_retries,
55
+ retry_delay=retry_delay
45
56
  )
46
57
 
47
58
  async def disconnect(self):
@@ -53,7 +64,7 @@ class ScheduledTaskManager:
53
64
  async def init_schema(self):
54
65
  """初始化数据库表结构(幂等操作)"""
55
66
  import os
56
- schema_path = os.path.join(os.path.dirname(__file__), 'schema.sql')
67
+ schema_path = os.path.join(os.path.dirname(__file__), 'sql', 'schema.sql')
57
68
 
58
69
  with open(schema_path, 'r') as f:
59
70
  schema_sql = f.read()
@@ -76,19 +87,18 @@ class ScheduledTaskManager:
76
87
  """创建定时任务"""
77
88
  sql = """
78
89
  INSERT INTO scheduled_tasks (
79
- scheduler_id, task_name, task_type, queue_name, namespace,
90
+ scheduler_id, task_type, queue_name, namespace,
80
91
  task_args, task_kwargs, cron_expression, interval_seconds,
81
92
  next_run_time, enabled, max_retries, retry_delay, timeout,
82
93
  priority, description, tags, metadata
83
- ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18)
94
+ ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17)
84
95
  RETURNING *
85
96
  """
86
-
97
+
87
98
  async with self.pool.acquire() as conn:
88
99
  row = await conn.fetchrow(
89
100
  sql,
90
101
  task.scheduler_id,
91
- task.task_name,
92
102
  task.task_type.value,
93
103
  task.queue_name,
94
104
  task.namespace, # 添加namespace
@@ -106,7 +116,7 @@ class ScheduledTaskManager:
106
116
  json.dumps(task.tags),
107
117
  json.dumps(task.metadata)
108
118
  )
109
-
119
+
110
120
  return self._row_to_task(row)
111
121
 
112
122
  async def get_task(self, task_id: int) -> Optional[ScheduledTask]:
@@ -134,34 +144,32 @@ class ScheduledTaskManager:
134
144
  sql = """
135
145
  UPDATE scheduled_tasks SET
136
146
  scheduler_id = $2,
137
- task_name = $3,
138
- task_type = $4,
139
- queue_name = $5,
140
- namespace = $6,
141
- task_args = $7,
142
- task_kwargs = $8,
143
- cron_expression = $9,
144
- interval_seconds = $10,
145
- next_run_time = $11,
146
- last_run_time = $12,
147
- enabled = $13,
148
- max_retries = $14,
149
- retry_delay = $15,
150
- timeout = $16,
151
- priority = $17,
152
- description = $18,
153
- tags = $19,
154
- metadata = $20
147
+ task_type = $3,
148
+ queue_name = $4,
149
+ namespace = $5,
150
+ task_args = $6,
151
+ task_kwargs = $7,
152
+ cron_expression = $8,
153
+ interval_seconds = $9,
154
+ next_run_time = $10,
155
+ last_run_time = $11,
156
+ enabled = $12,
157
+ max_retries = $13,
158
+ retry_delay = $14,
159
+ timeout = $15,
160
+ priority = $16,
161
+ description = $17,
162
+ tags = $18,
163
+ metadata = $19
155
164
  WHERE id = $1
156
165
  RETURNING *
157
166
  """
158
-
167
+
159
168
  async with self.pool.acquire() as conn:
160
169
  row = await conn.fetchrow(
161
170
  sql,
162
171
  task.id,
163
172
  task.scheduler_id,
164
- task.task_name,
165
173
  task.task_type.value,
166
174
  task.queue_name,
167
175
  task.namespace, # 添加namespace
@@ -300,10 +308,9 @@ class ScheduledTaskManager:
300
308
  return
301
309
 
302
310
  sql = """
303
- UPDATE scheduled_tasks
304
- SET next_run_time = u.next_run_time,
305
- last_run_time = u.last_run_time,
306
- execution_count = COALESCE(execution_count, 0) + 1
311
+ UPDATE scheduled_tasks
312
+ SET next_run_time = u.next_run_time,
313
+ last_run_time = u.last_run_time
307
314
  FROM (VALUES ($1::int, $2::timestamptz, $3::timestamptz)) AS u(id, next_run_time, last_run_time)
308
315
  WHERE scheduled_tasks.id = u.id
309
316
  """
@@ -439,12 +446,12 @@ class ScheduledTaskManager:
439
446
  for history in histories:
440
447
  task_info = task_info_map.get(history.task_id, {})
441
448
  queue_name = task_info.get('queue_name', 'default')
442
- task_name = task_info.get('task_name', 'scheduled_task')
443
-
449
+ # task_name 字段已移除,改为 queue_name
450
+
444
451
  data.append((
445
452
  history.event_id, # id字段使用event_id
446
453
  queue_name,
447
- task_name, # task_name
454
+ queue_name, # task_name(保留以兼容旧表结构,后续可删除)
448
455
  history.status.value if isinstance(history.status, TaskStatus) else history.status,
449
456
  history.task_id, # scheduled_task_id
450
457
  history.scheduled_time,
@@ -469,25 +476,24 @@ class ScheduledTaskManager:
469
476
  sql = """
470
477
  UPDATE scheduled_tasks SET
471
478
  scheduler_id = $2,
472
- task_name = $3,
473
- task_type = $4,
474
- queue_name = $5,
475
- task_args = $6,
476
- task_kwargs = $7,
477
- cron_expression = $8,
478
- interval_seconds = $9,
479
- next_run_time = $10,
480
- last_run_time = $11,
481
- enabled = $12,
482
- max_retries = $13,
483
- retry_delay = $14,
484
- timeout = $15,
485
- description = $16,
486
- metadata = $17,
487
- updated_at = $18
479
+ task_type = $3,
480
+ queue_name = $4,
481
+ task_args = $5,
482
+ task_kwargs = $6,
483
+ cron_expression = $7,
484
+ interval_seconds = $8,
485
+ next_run_time = $9,
486
+ last_run_time = $10,
487
+ enabled = $11,
488
+ max_retries = $12,
489
+ retry_delay = $13,
490
+ timeout = $14,
491
+ description = $15,
492
+ metadata = $16,
493
+ updated_at = $17
488
494
  WHERE id = $1
489
495
  """
490
-
496
+
491
497
  # 准备批量数据
492
498
  data = []
493
499
  now = datetime.now()
@@ -495,7 +501,6 @@ class ScheduledTaskManager:
495
501
  data.append((
496
502
  task.id,
497
503
  task.scheduler_id,
498
- task.task_name,
499
504
  task.task_type.value if isinstance(task.task_type, TaskType) else task.task_type,
500
505
  task.queue_name,
501
506
  json.dumps(task.task_args) if task.task_args else '[]',
@@ -531,7 +536,6 @@ class ScheduledTaskManager:
531
536
  return ScheduledTask(
532
537
  id=row['id'],
533
538
  scheduler_id=row['scheduler_id'],
534
- task_name=row['task_name'],
535
539
  task_type=TaskType(row['task_type']),
536
540
  queue_name=row['queue_name'],
537
541
  namespace=row.get('namespace', 'default'), # 添加namespace字段
@@ -8,7 +8,7 @@ from datetime import datetime, timedelta
8
8
  import json
9
9
 
10
10
  from ..utils.task_logger import get_task_logger, LogContext
11
- from .task_crud import ScheduledTaskManager
11
+ from .database import ScheduledTaskManager
12
12
  from .models import ScheduledTask
13
13
 
14
14
 
@@ -55,7 +55,7 @@ class TaskLoader:
55
55
  async def connect(self):
56
56
  """建立Redis连接(使用统一的连接池管理)"""
57
57
  if not self.redis:
58
- from jettask.utils.db_connector import get_async_redis_client
58
+ from jettask.db.connector import get_async_redis_client
59
59
 
60
60
  self.redis = get_async_redis_client(
61
61
  redis_url=self.redis_url,
@@ -10,7 +10,7 @@ from jettask.core.unified_manager_base import UnifiedManagerBase
10
10
  from jettask import Jettask
11
11
  from jettask.task.task_center.client import TaskCenter
12
12
  from .scheduler import TaskScheduler
13
- from .task_crud import ScheduledTaskManager
13
+ from .database import ScheduledTaskManager
14
14
 
15
15
  logger = logging.getLogger(__name__)
16
16
 
@@ -50,20 +50,32 @@ class UnifiedSchedulerManager(UnifiedManagerBase):
50
50
  async def run_single_namespace(self, namespace_name: str):
51
51
  """
52
52
  运行单命名空间模式
53
-
53
+
54
54
  Args:
55
55
  namespace_name: 命名空间名称
56
56
  """
57
57
  logger.info(f"启动单命名空间调度器: {namespace_name}")
58
58
  logger.info(f"扫描间隔: {self.scan_interval}秒")
59
59
  logger.info(f"批处理大小: {self.batch_size}")
60
-
60
+
61
61
  try:
62
+ # 构建任务中心URL
63
+ # 如果URL已经是完整的API格式,直接使用;否则需要转换为标准格式
64
+ if '/api/v1/namespaces/' in self.task_center_url or '/api/namespaces/' in self.task_center_url:
65
+ # 已经是完整的API格式
66
+ namespace_url = self.task_center_url
67
+ else:
68
+ # 简化格式,需要转换为标准API格式
69
+ base_url = self.get_base_url()
70
+ namespace_url = f"{base_url}/api/v1/namespaces/{namespace_name}"
71
+
72
+ logger.debug(f"任务中心API URL: {namespace_url}")
73
+
62
74
  # 创建任务中心连接
63
- tc = TaskCenter(self.task_center_url)
75
+ tc = TaskCenter(namespace_url)
64
76
  if not tc._connect_sync():
65
- raise Exception(f"无法连接到任务中心: {self.task_center_url}")
66
-
77
+ raise Exception(f"无法连接到任务中心: {namespace_url}")
78
+
67
79
  # 创建 Jettask 应用
68
80
  app = Jettask(task_center=tc)
69
81
 
@@ -175,6 +187,7 @@ class UnifiedSchedulerManager(UnifiedManagerBase):
175
187
  # 1. http://localhost:8001 -> http://localhost:8001/api/v1/namespaces/{name}
176
188
  # 2. http://localhost:8001/api/v1 -> http://localhost:8001/api/v1/namespaces/{name}
177
189
  # 3. http://localhost:8001/api/v1/namespaces/old -> http://localhost:8001/api/v1/namespaces/{name}
190
+ # 4. http://localhost:8001/namespaces/old -> http://localhost:8001/api/v1/namespaces/{name}
178
191
 
179
192
  if '/api/v1/namespaces/' in task_center_url:
180
193
  # 替换现有的命名空间
@@ -184,6 +197,10 @@ class UnifiedSchedulerManager(UnifiedManagerBase):
184
197
  # 兼容旧格式
185
198
  base_url = task_center_url.split('/api/namespaces/')[0]
186
199
  url = f"{base_url}/api/namespaces/{namespace_name}"
200
+ elif '/namespaces/' in task_center_url:
201
+ # 简化格式(无/api前缀),转换为标准格式
202
+ base_url = task_center_url.split('/namespaces/')[0]
203
+ url = f"{base_url}/api/v1/namespaces/{namespace_name}"
187
204
  elif task_center_url.endswith('/api/v1') or task_center_url.endswith('/api/v1/'):
188
205
  # 多命名空间模式,URL 是 http://localhost:8001/api/v1
189
206
  url = f"{task_center_url.rstrip('/')}/namespaces/{namespace_name}"
@@ -29,11 +29,15 @@ class TaskStatus(Enum):
29
29
 
30
30
  @dataclass
31
31
  class ScheduledTask:
32
- """定时任务模型"""
33
- task_name: str # 要执行的函数名(对应@app.task注册的函数名)
34
- task_type: TaskType # 任务类型
35
- queue_name: str # 目标队列
36
-
32
+ """
33
+ 定时任务模型
34
+
35
+ 定时任务以 queue 为核心,定期向指定队列发送消息。
36
+ 队列中的消息可以被多个消费者处理,具体处理逻辑由消费者决定。
37
+ """
38
+ task_type: TaskType # 任务类型(once/interval/cron)
39
+ queue_name: str # 目标队列(必填)
40
+
37
41
  # 可选字段
38
42
  id: Optional[int] = None # 数据库自增ID(唯一标识)
39
43
  scheduler_id: Optional[str] = None # 任务的唯一标识符(用于去重)
@@ -71,14 +75,14 @@ class ScheduledTask:
71
75
  def _validate(self):
72
76
  """验证任务配置"""
73
77
  if self.task_type == TaskType.CRON and not self.cron_expression:
74
- raise ValueError(f"Task {self.task_name} with type CRON must have cron_expression")
75
-
78
+ raise ValueError(f"Task (queue={self.queue_name}) with type CRON must have cron_expression")
79
+
76
80
  if self.task_type == TaskType.INTERVAL and not self.interval_seconds:
77
- raise ValueError(f"Task {self.task_name} with type INTERVAL must have interval_seconds")
78
-
81
+ raise ValueError(f"Task (queue={self.queue_name}) with type INTERVAL must have interval_seconds")
82
+
79
83
  # ONCE类型任务不应该有interval_seconds参数
80
84
  if self.task_type == TaskType.ONCE and self.interval_seconds is not None:
81
- raise ValueError(f"Task {self.task_name} with type ONCE should not have interval_seconds. Use next_run_time to specify when to run the task")
85
+ raise ValueError(f"Task (queue={self.queue_name}) with type ONCE should not have interval_seconds. Use next_run_time to specify when to run the task")
82
86
 
83
87
  def calculate_next_run_time(self, from_time: Optional[datetime] = None) -> Optional[datetime]:
84
88
  """计算下次执行时间"""
@@ -0,0 +1,166 @@
1
+ """
2
+ 定时任务定义类 - 类似 TaskMessage 的设计模式
3
+ 用于定义定时任务,然后统一注册到数据库
4
+ """
5
+ from typing import Optional, Dict, Any, List
6
+ from dataclasses import dataclass, field
7
+ from datetime import datetime, timedelta
8
+ from .models import TaskType
9
+
10
+
11
+ @dataclass
12
+ class Schedule:
13
+ """
14
+ 定时任务定义对象
15
+
16
+ 这是一个定义定时任务的类,类似 TaskMessage 的设计模式。
17
+ 定时任务会定期向指定队列发送消息,队列中的消息可以被多个消费者处理。
18
+
19
+ 使用示例:
20
+ # 1. 定义定时任务
21
+ schedule1 = Schedule(
22
+ scheduler_id="notify_every_30s",
23
+ queue="notification_queue",
24
+ interval_seconds=30,
25
+ kwargs={"user_id": "user_123", "message": "定时提醒"}
26
+ )
27
+
28
+ schedule2 = Schedule(
29
+ scheduler_id="report_cron",
30
+ queue="report_queue",
31
+ cron_expression="0 9 * * *", # 每天9点
32
+ description="每天生成报告"
33
+ )
34
+
35
+ # 2. 批量注册
36
+ await app.register_schedules([schedule1, schedule2])
37
+
38
+ # 3. 或单个注册
39
+ await app.register_schedules(schedule1)
40
+ """
41
+
42
+ # 必需参数
43
+ scheduler_id: str # 任务唯一标识符(必填)
44
+ queue: str # 目标队列(必填)
45
+
46
+ # 调度类型(三选一)
47
+ interval_seconds: Optional[float] = None # 间隔任务(秒)
48
+ cron_expression: Optional[str] = None # Cron表达式任务
49
+ next_run_time: Optional[datetime] = None # 一次性任务(指定执行时间)
50
+
51
+ # 任务参数
52
+ args: List[Any] = field(default_factory=list)
53
+ kwargs: Dict[str, Any] = field(default_factory=dict)
54
+
55
+ # 执行选项
56
+ priority: Optional[int] = None # 优先级(1最高,数字越大优先级越低)
57
+ timeout: int = 300 # 超时时间(秒)
58
+ max_retries: int = 3 # 最大重试次数
59
+ retry_delay: int = 60 # 重试延迟(秒)
60
+
61
+ # 控制选项
62
+ enabled: bool = True # 是否启用
63
+ description: Optional[str] = None # 任务描述
64
+ tags: List[str] = field(default_factory=list) # 标签
65
+ metadata: Dict[str, Any] = field(default_factory=dict) # 额外元数据
66
+
67
+ # 注册选项
68
+ skip_if_exists: bool = True # 如果已存在则跳过
69
+ at_once: bool = True # 是否立即执行一次
70
+
71
+ def __post_init__(self):
72
+ """初始化后处理"""
73
+ self._validate()
74
+
75
+ def _validate(self):
76
+ """验证配置"""
77
+ if not self.scheduler_id:
78
+ raise ValueError("scheduler_id is required")
79
+
80
+ if not self.queue:
81
+ raise ValueError("queue is required")
82
+
83
+ # 检查调度类型(必须指定一种)
84
+ schedule_types = [
85
+ self.interval_seconds is not None,
86
+ self.cron_expression is not None,
87
+ self.next_run_time is not None
88
+ ]
89
+
90
+ if sum(schedule_types) == 0:
91
+ raise ValueError(
92
+ "Must specify one schedule type: "
93
+ "interval_seconds, cron_expression, or next_run_time"
94
+ )
95
+
96
+ if sum(schedule_types) > 1:
97
+ raise ValueError(
98
+ "Can only specify one schedule type: "
99
+ "interval_seconds, cron_expression, or next_run_time"
100
+ )
101
+
102
+ # 验证具体参数
103
+ if self.interval_seconds is not None and self.interval_seconds <= 0:
104
+ raise ValueError(f"interval_seconds must be positive, got {self.interval_seconds}")
105
+
106
+ if self.priority is not None and self.priority < 1:
107
+ raise ValueError(f"priority must be positive (1 is highest), got {self.priority}")
108
+
109
+ def get_task_type(self) -> TaskType:
110
+ """获取任务类型"""
111
+ if self.interval_seconds is not None:
112
+ return TaskType.INTERVAL
113
+ elif self.cron_expression is not None:
114
+ return TaskType.CRON
115
+ else:
116
+ return TaskType.ONCE
117
+
118
+ def to_dict(self) -> dict:
119
+ """
120
+ 转换为字典格式(用于注册到数据库)
121
+
122
+ Returns:
123
+ dict: 任务字典
124
+ """
125
+ data = {
126
+ 'scheduler_id': self.scheduler_id,
127
+ 'queue': self.queue,
128
+ 'task_type': self.get_task_type().value,
129
+ 'task_args': self.args,
130
+ 'task_kwargs': self.kwargs,
131
+ 'enabled': self.enabled,
132
+ 'priority': self.priority,
133
+ 'timeout': self.timeout,
134
+ 'max_retries': self.max_retries,
135
+ 'retry_delay': self.retry_delay,
136
+ 'description': self.description,
137
+ 'tags': self.tags,
138
+ 'metadata': self.metadata,
139
+ }
140
+
141
+ # 添加调度相关参数
142
+ if self.interval_seconds is not None:
143
+ data['interval_seconds'] = self.interval_seconds
144
+ elif self.cron_expression is not None:
145
+ data['cron_expression'] = self.cron_expression
146
+ elif self.next_run_time is not None:
147
+ data['next_run_time'] = self.next_run_time
148
+
149
+ return data
150
+
151
+ def __repr__(self) -> str:
152
+ """友好的字符串表示"""
153
+ parts = [f"Schedule(scheduler_id='{self.scheduler_id}'"]
154
+ parts.append(f"queue='{self.queue}'")
155
+
156
+ if self.interval_seconds:
157
+ parts.append(f"interval={self.interval_seconds}s")
158
+ elif self.cron_expression:
159
+ parts.append(f"cron='{self.cron_expression}'")
160
+ elif self.next_run_time:
161
+ parts.append(f"at={self.next_run_time}")
162
+
163
+ if self.kwargs:
164
+ parts.append(f"kwargs={self.kwargs}")
165
+
166
+ return ", ".join(parts) + ")"
@@ -10,7 +10,7 @@ from typing import Optional, List, TYPE_CHECKING
10
10
  from datetime import datetime
11
11
 
12
12
  from ..utils.task_logger import get_task_logger, LogContext
13
- from .task_crud import ScheduledTaskManager
13
+ from .database import ScheduledTaskManager
14
14
  from .models import ScheduledTask, TaskExecutionHistory, TaskType, TaskStatus as ScheduledTaskStatus
15
15
  from .loader import TaskLoader
16
16
 
@@ -499,12 +499,12 @@ class TaskScheduler:
499
499
  from ..core.message import TaskMessage
500
500
  task_messages = []
501
501
  for task in bulk_tasks:
502
- # 准备kwargs
503
- kwargs = task.task_kwargs or {}
504
- # 添加任务名称和scheduled_task_id用于路由和跟踪
505
- kwargs['__task_name'] = task.task_name
502
+ # 准备kwargs(合并task定义的kwargs)
503
+ kwargs = task.task_kwargs.copy() if task.task_kwargs else {}
504
+
505
+ # 添加scheduled_task_id用于跟踪
506
506
  kwargs['__scheduled_task_id'] = task.id
507
-
507
+
508
508
  # 将timeout、max_retries等参数放入kwargs中传递
509
509
  if task.timeout:
510
510
  kwargs['__timeout'] = task.timeout
@@ -512,17 +512,18 @@ class TaskScheduler:
512
512
  kwargs['__max_retries'] = task.max_retries
513
513
  if task.retry_delay:
514
514
  kwargs['__retry_delay'] = task.retry_delay
515
-
516
- # 创建TaskMessage
515
+
516
+ # 创建TaskMessage(直接发送到queue,由queue的消费者处理)
517
517
  task_msg = TaskMessage(
518
518
  queue=task.queue_name,
519
+ args=task.task_args or [],
519
520
  kwargs=kwargs,
520
521
  priority=task.priority
521
522
  )
522
523
  task_messages.append(task_msg)
523
524
 
524
- # 使用send_tasks方法发送
525
- event_ids = await self.app.send_tasks(task_messages)
525
+ # 使用send_tasks方法发送(异步模式)
526
+ event_ids = await self.app.send_tasks(task_messages, asyncio=True)
526
527
  logger.info(f"Triggered {len(task_messages)} tasks via send_tasks")
527
528
 
528
529
  # 准备批量操作的数据
@@ -620,7 +621,7 @@ class TaskScheduler:
620
621
  """运行调度器主循环"""
621
622
  # 建立Redis连接(使用统一的连接池管理)
622
623
  if not self.redis:
623
- from jettask.utils.db_connector import get_async_redis_client
624
+ from jettask.db.connector import get_async_redis_client
624
625
 
625
626
  self.redis = get_async_redis_client(
626
627
  redis_url=self.redis_url,
@@ -57,6 +57,7 @@ from .namespace import (
57
57
 
58
58
  # 积压监控相关模型
59
59
  from .backlog import (
60
+ BacklogLatestRequest,
60
61
  BacklogTrendRequest,
61
62
  BacklogSnapshot,
62
63
  BacklogStatistics,
@@ -91,6 +92,31 @@ from .monitoring import (
91
92
  WorkerMetrics
92
93
  )
93
94
 
95
+ # API 响应模型
96
+ from .responses import (
97
+ SuccessResponse,
98
+ DataResponse,
99
+ PaginatedDataResponse,
100
+ SystemStatsData,
101
+ SystemStatsResponse,
102
+ DashboardStatsData,
103
+ DashboardStatsResponse,
104
+ QueueRankingItem,
105
+ TopQueuesResponse,
106
+ NamespaceStatistics,
107
+ NamespaceStatisticsResponse,
108
+ WorkerHeartbeatInfo,
109
+ WorkersResponse,
110
+ WorkerSummary,
111
+ WorkerSummaryResponse,
112
+ WorkerOfflineRecord,
113
+ WorkerOfflineHistoryResponse,
114
+ DatabaseStatus,
115
+ SystemSettings,
116
+ SystemSettingsResponse,
117
+ DatabaseStatusResponse
118
+ )
119
+
94
120
  __all__ = [
95
121
  # Task
96
122
  'TasksRequest',
@@ -162,5 +188,28 @@ __all__ = [
162
188
  'SystemHealth',
163
189
  'DashboardOverviewRequest',
164
190
  'DashboardOverview',
165
- 'WorkerMetrics'
191
+ 'WorkerMetrics',
192
+
193
+ # Responses
194
+ 'SuccessResponse',
195
+ 'DataResponse',
196
+ 'PaginatedDataResponse',
197
+ 'SystemStatsData',
198
+ 'SystemStatsResponse',
199
+ 'DashboardStatsData',
200
+ 'DashboardStatsResponse',
201
+ 'QueueRankingItem',
202
+ 'TopQueuesResponse',
203
+ 'NamespaceStatistics',
204
+ 'NamespaceStatisticsResponse',
205
+ 'WorkerHeartbeatInfo',
206
+ 'WorkersResponse',
207
+ 'WorkerSummary',
208
+ 'WorkerSummaryResponse',
209
+ 'WorkerOfflineRecord',
210
+ 'WorkerOfflineHistoryResponse',
211
+ 'DatabaseStatus',
212
+ 'SystemSettings',
213
+ 'SystemSettingsResponse',
214
+ 'DatabaseStatusResponse'
166
215
  ]