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
jettask/core/container.py CHANGED
@@ -74,7 +74,7 @@ class ServiceContainer:
74
74
 
75
75
  def _get_sync_text_client(self) -> redis.Redis:
76
76
  """获取同步文本模式客户端(使用全局客户端实例)"""
77
- from jettask.utils.db_connector import get_sync_redis_client
77
+ from jettask.db.connector import get_sync_redis_client
78
78
  return get_sync_redis_client(
79
79
  redis_url=self.config.redis.url,
80
80
  decode_responses=True,
@@ -83,7 +83,7 @@ class ServiceContainer:
83
83
 
84
84
  def _get_sync_binary_client(self) -> redis.Redis:
85
85
  """获取同步二进制模式客户端(使用全局客户端实例)"""
86
- from jettask.utils.db_connector import get_sync_redis_client
86
+ from jettask.db.connector import get_sync_redis_client
87
87
  return get_sync_redis_client(
88
88
  redis_url=self.config.redis.url,
89
89
  decode_responses=False,
@@ -92,7 +92,7 @@ class ServiceContainer:
92
92
 
93
93
  def _get_async_text_client(self) -> aioredis.Redis:
94
94
  """获取异步文本模式客户端(使用全局客户端实例)"""
95
- from jettask.utils.db_connector import get_async_redis_client
95
+ from jettask.db.connector import get_async_redis_client
96
96
  return get_async_redis_client(
97
97
  redis_url=self.config.redis.url,
98
98
  decode_responses=True,
@@ -101,7 +101,7 @@ class ServiceContainer:
101
101
 
102
102
  def _get_async_binary_client(self) -> aioredis.Redis:
103
103
  """获取异步二进制模式客户端(使用全局客户端实例)"""
104
- from jettask.utils.db_connector import get_async_redis_client
104
+ from jettask.db.connector import get_async_redis_client
105
105
  return get_async_redis_client(
106
106
  redis_url=self.config.redis.url,
107
107
  decode_responses=False,
@@ -2,20 +2,16 @@
2
2
  命名空间数据访问层 - 支持多租户的数据隔离访问
3
3
  """
4
4
  import os
5
- import asyncio
6
- import json
7
5
  import logging
8
- import time
9
6
  import traceback
10
- from datetime import datetime, timedelta, timezone
11
- from typing import Dict, List, Optional, Tuple, Any
7
+ from typing import Dict, List, Optional
12
8
  import redis.asyncio as redis
13
- from sqlalchemy import text, bindparam
9
+ from sqlalchemy import text
14
10
  from sqlalchemy.ext.asyncio import AsyncSession
15
11
  import aiohttp
16
12
 
17
13
  # 导入统一的数据库连接工具
18
- from ..utils.db_connector import (
14
+ from jettask.db.connector import (
19
15
  get_dual_mode_async_redis_client,
20
16
  get_pg_engine_and_factory
21
17
  )
@@ -49,13 +45,20 @@ class NamespaceConnection:
49
45
  try:
50
46
  # 初始化 PostgreSQL 连接(使用全局单例)
51
47
  if self.pg_config:
52
- self.async_engine, self.AsyncSessionLocal = get_pg_engine_and_factory(
53
- config=self.pg_config,
54
- pool_size=10,
55
- max_overflow=5,
56
- pool_pre_ping=True,
57
- echo=False
58
- )
48
+ # pg_config 可以是字符串(DSN)或者字典(包含url字段)
49
+ pg_dsn = self.pg_config if isinstance(self.pg_config, str) else self.pg_config.get('url')
50
+ if pg_dsn:
51
+ # 将 postgresql:// 转换为 postgresql+asyncpg://
52
+ if pg_dsn.startswith('postgresql://'):
53
+ pg_dsn = pg_dsn.replace('postgresql://', 'postgresql+asyncpg://', 1)
54
+
55
+ self.async_engine, self.AsyncSessionLocal = get_pg_engine_and_factory(
56
+ dsn=pg_dsn,
57
+ pool_size=10,
58
+ max_overflow=5,
59
+ pool_recycle=3600,
60
+ echo=False
61
+ )
59
62
 
60
63
  # 初始化 Redis 连接(使用全局单例,双模式)
61
64
  if self.redis_config:
@@ -142,29 +145,92 @@ class NamespaceDataAccessManager:
142
145
  return self._session
143
146
 
144
147
  async def get_namespace_config(self, namespace_name: str) -> dict:
145
- """从任务中心API获取命名空间配置"""
148
+ """从任务中心API获取命名空间配置
149
+
150
+ 支持两种配置模式:
151
+ - nacos 模式:API 返回 nacos_key,本地通过 Nacos 获取真实配置
152
+ - direct 模式:API 直接返回完整的数据库 URL
153
+ """
146
154
  # 使用127.0.0.1替代localhost,确保容器内能正确连接
147
155
  base_url = self.task_center_base_url
148
156
  if 'localhost' in base_url:
149
157
  base_url = base_url.replace('localhost', '127.0.0.1')
150
158
  url = f"{base_url}/api/v1/namespaces/{namespace_name}"
151
-
159
+
152
160
  try:
153
161
  session = await self._get_session()
154
162
  async with session.get(url) as resp:
155
163
  if resp.status == 200:
156
164
  data = await resp.json()
157
- # API返回的是redis_config和pg_config,直接使用
158
- redis_config = data.get('redis_config', {})
159
- pg_config = data.get('pg_config', {})
160
-
161
- # 兼容旧格式:如果有redis_url和pg_url字段
162
- if not redis_config and data.get('redis_url'):
163
- redis_config = {'url': data.get('redis_url')}
164
-
165
- if not pg_config and data.get('pg_url'):
166
- pg_config = {'url': data.get('pg_url')}
167
-
165
+
166
+ # 处理新格式(带 config_mode 字段)
167
+ redis_config_mode = data.get('redis_config_mode', 'direct')
168
+ pg_config_mode = data.get('pg_config_mode', 'direct')
169
+
170
+ # 处理 Redis 配置
171
+ redis_config = {}
172
+ if redis_config_mode == 'nacos':
173
+ # Nacos 模式 - 需要通过 nacos_config 获取真实 URL
174
+ redis_nacos_key = data.get('redis_nacos_key')
175
+ if redis_nacos_key:
176
+ logger.info(f"命名空间 {namespace_name} 使用 Nacos 模式获取 Redis 配置,key: {redis_nacos_key}")
177
+ try:
178
+ from jettask.config.nacos_config import config as nacos_config
179
+ redis_url = nacos_config.get(redis_nacos_key)
180
+ if redis_url:
181
+ redis_config = {'url': redis_url}
182
+ logger.debug(f"从 Nacos 获取 Redis URL 成功: {redis_nacos_key}")
183
+ else:
184
+ logger.warning(f"Nacos 配置键 {redis_nacos_key} 未找到或为空")
185
+ except Exception as e:
186
+ logger.error(f"从 Nacos 获取 Redis 配置失败: {e}")
187
+ raise ValueError(f"无法从 Nacos 获取 Redis 配置 (key: {redis_nacos_key}): {e}")
188
+ else:
189
+ # Direct 模式 - 直接使用 API 返回的 URL
190
+ redis_url = data.get('redis_url')
191
+ if redis_url:
192
+ redis_config = {'url': redis_url}
193
+ logger.debug(f"命名空间 {namespace_name} 使用 Direct 模式,Redis URL 已获取")
194
+
195
+ # 处理 PostgreSQL 配置
196
+ pg_config = {}
197
+ if pg_config_mode == 'nacos':
198
+ # Nacos 模式 - 需要通过 nacos_config 获取真实 URL
199
+ pg_nacos_key = data.get('pg_nacos_key')
200
+ if pg_nacos_key:
201
+ logger.info(f"命名空间 {namespace_name} 使用 Nacos 模式获取 PG 配置,key: {pg_nacos_key}")
202
+ try:
203
+ from jettask.config.nacos_config import config as nacos_config
204
+ pg_url = nacos_config.get(pg_nacos_key)
205
+ if pg_url:
206
+ pg_config = {'url': pg_url}
207
+ logger.debug(f"从 Nacos 获取 PG URL 成功: {pg_nacos_key}")
208
+ else:
209
+ logger.warning(f"Nacos 配置键 {pg_nacos_key} 未找到或为空")
210
+ except Exception as e:
211
+ logger.error(f"从 Nacos 获取 PG 配置失败: {e}")
212
+ # PostgreSQL 是可选的,不抛出异常
213
+ logger.warning(f"PostgreSQL 配置获取失败,将跳过 PG 相关功能")
214
+ else:
215
+ # Direct 模式 - 直接使用 API 返回的 URL
216
+ pg_url = data.get('pg_url')
217
+ if pg_url:
218
+ pg_config = {'url': pg_url}
219
+ logger.debug(f"命名空间 {namespace_name} 使用 Direct 模式,PG URL 已获取")
220
+
221
+ # 兼容旧格式:如果没有 config_mode 字段(旧版 API)
222
+ if not redis_config:
223
+ if data.get('redis_config'):
224
+ redis_config = data.get('redis_config')
225
+ elif data.get('redis_url'):
226
+ redis_config = {'url': data.get('redis_url')}
227
+
228
+ if not pg_config:
229
+ if data.get('pg_config'):
230
+ pg_config = data.get('pg_config')
231
+ elif data.get('pg_url'):
232
+ pg_config = {'url': data.get('pg_url')}
233
+
168
234
  return {
169
235
  'name': data.get('name'),
170
236
  'redis_config': redis_config,
jettask/core/task.py CHANGED
@@ -36,36 +36,41 @@ class Task:
36
36
  trigger_time: float = None
37
37
  retry_config: Optional[dict] = None # 存储任务级别的重试配置
38
38
 
39
- def __call__(self, event_id: str, trigger_time: float, *args: Any, **kwds: Any) -> Any:
39
+ def __call__(self, event_id: str, trigger_time: float, queue:str, *args: Any, **kwds: Any) -> Any:
40
40
  # 检查函数签名以进行依赖注入
41
41
  injected_args, injected_kwargs = self._inject_dependencies(
42
- event_id, trigger_time, args, kwds
42
+ event_id, trigger_time, queue, args, kwds
43
43
  )
44
44
  return self.run(*injected_args, **injected_kwargs)
45
45
 
46
- def _inject_dependencies(self, event_id: str, trigger_time: float, args: tuple, kwargs: dict) -> tuple:
46
+ def _inject_dependencies(self, event_id: str, trigger_time: float, queue:str, args: tuple, kwargs: dict) -> tuple:
47
47
  """
48
48
  基于类型注解自动注入TaskContext
49
49
  """
50
+ import logging
51
+ logger = logging.getLogger(__name__)
52
+
50
53
  # 获取run方法的签名
51
54
  try:
52
55
  sig = inspect.signature(self.run)
53
56
  type_hints = get_type_hints(self.run)
54
- except (ValueError, TypeError, NameError):
57
+ logger.debug(f"[TaskContext注入] 任务 {self.name} - 签名: {sig}, 类型提示: {type_hints}")
58
+ except (ValueError, TypeError, NameError) as e:
55
59
  # 如果获取签名失败,返回原始参数
60
+ logger.warning(f"[TaskContext注入] 任务 {self.name} - 获取签名失败: {e}")
56
61
  return args, kwargs
57
62
 
58
- # 从kwargs中提取scheduled_task_id(如果存在)
59
- # 这个值由执行器从event_data中提取并传递
63
+ # 从kwargs中提取scheduled_task_id和真实队列名(如果存在)
64
+ # 这些值由执行器从event_data中提取并传递
60
65
  scheduled_task_id = kwargs.pop('__scheduled_task_id', None)
61
-
66
+
62
67
  # 创建TaskContext实例
63
68
  context = TaskContext(
64
69
  event_id=event_id,
65
70
  name=self.name,
66
71
  trigger_time=trigger_time,
67
72
  app=self._app,
68
- queue=self.queue,
73
+ queue=queue, # 优先使用真实队列名,fallback到任务定义的队列
69
74
  scheduled_task_id=scheduled_task_id, # 传递scheduled_task_id
70
75
  # worker_id和retry_count可以从其他地方获取
71
76
  # 暂时使用默认值
@@ -112,7 +117,9 @@ class Task:
112
117
  # 如果还有剩余的位置参数,添加到末尾(处理*args的情况)
113
118
  if args_consumed < len(args_list):
114
119
  final_args.extend(args_list[args_consumed:])
115
-
120
+
121
+ logger.debug(f"[TaskContext注入] 任务 {self.name} - 注入后参数: args={final_args}, kwargs={list(final_kwargs.keys())}")
122
+
116
123
  return tuple(final_args), final_kwargs
117
124
 
118
125
  def run(self, *args, **kwargs):
@@ -51,15 +51,15 @@ class UnifiedManagerBase(ABC):
51
51
  def _detect_mode(self) -> bool:
52
52
  """
53
53
  检测是单命名空间还是多命名空间模式
54
-
54
+
55
55
  Returns:
56
56
  True: 单命名空间模式
57
57
  False: 多命名空间模式
58
58
  """
59
59
  # 检查URL格式
60
- # 单命名空间: /api/v1/namespaces/{name} 或 /api/namespaces/{name}
60
+ # 单命名空间: /api/v1/namespaces/{name} 或 /api/namespaces/{name} 或 /namespaces/{name}
61
61
  # 多命名空间: 不包含这些路径或以 /api 结尾
62
-
62
+
63
63
  # 检查新格式
64
64
  if '/api/v1/namespaces/' in self.task_center_url:
65
65
  # 提取命名空间名称
@@ -68,7 +68,7 @@ class UnifiedManagerBase(ABC):
68
68
  self.namespace_name = match.group(1)
69
69
  logger.info(f"检测到单命名空间模式: {self.namespace_name}")
70
70
  return True
71
-
71
+
72
72
  # 兼容旧格式
73
73
  elif '/api/namespaces/' in self.task_center_url:
74
74
  # 提取命名空间名称
@@ -77,7 +77,16 @@ class UnifiedManagerBase(ABC):
77
77
  self.namespace_name = match.group(1)
78
78
  logger.info(f"检测到单命名空间模式: {self.namespace_name}")
79
79
  return True
80
-
80
+
81
+ # 支持简化格式(无/api前缀)
82
+ elif '/namespaces/' in self.task_center_url:
83
+ # 提取命名空间名称
84
+ match = re.search(r'/namespaces/([^/]+)/?$', self.task_center_url)
85
+ if match:
86
+ self.namespace_name = match.group(1)
87
+ logger.info(f"检测到单命名空间模式(简化格式): {self.namespace_name}")
88
+ return True
89
+
81
90
  # 多命名空间模式
82
91
  logger.info("检测到多命名空间模式")
83
92
  return False
@@ -85,7 +94,7 @@ class UnifiedManagerBase(ABC):
85
94
  def get_base_url(self) -> str:
86
95
  """
87
96
  获取任务中心的基础URL
88
-
97
+
89
98
  Returns:
90
99
  基础URL(去除命名空间路径)
91
100
  """
@@ -97,12 +106,16 @@ class UnifiedManagerBase(ABC):
97
106
  elif '/api/namespaces/' in self.task_center_url:
98
107
  # 兼容旧格式
99
108
  return self.task_center_url.split('/api/namespaces/')[0]
100
-
109
+ elif '/namespaces/' in self.task_center_url:
110
+ # 简化格式(无/api前缀)
111
+ # 从 http://localhost:8001/namespaces/test5 提取 http://localhost:8001
112
+ return self.task_center_url.split('/namespaces/')[0]
113
+
101
114
  # 多命名空间模式,如果URL以 /api/v1/ 结尾,去掉 /api/v1/ 部分
102
115
  if self.task_center_url.endswith('/api/v1/') or self.task_center_url.endswith('/api/v1'):
103
116
  # 从 http://localhost:8001/api/v1/ 提取 http://localhost:8001
104
117
  return self.task_center_url.rstrip('/').rsplit('/api/v1', 1)[0]
105
-
118
+
106
119
  return self.task_center_url
107
120
 
108
121
  def get_target_namespaces(self) -> Optional[Set[str]]:
@@ -144,18 +157,67 @@ class UnifiedManagerBase(ABC):
144
157
  async with session.get(url) as response:
145
158
  if response.status == 200:
146
159
  data = await response.json()
147
- # 构建redis和pg配置
160
+
161
+ # 处理新格式(带 config_mode 字段)
162
+ redis_config_mode = data.get('redis_config_mode', 'direct')
163
+ pg_config_mode = data.get('pg_config_mode', 'direct')
164
+
165
+ # 处理 Redis 配置
166
+ redis_config = {}
167
+ if redis_config_mode == 'nacos':
168
+ # Nacos 模式 - 需要通过 nacos_config 获取真实 URL
169
+ redis_nacos_key = data.get('redis_nacos_key')
170
+ if redis_nacos_key:
171
+ try:
172
+ from jettask.config.nacos_config import config as nacos_config
173
+ redis_url = nacos_config.get(redis_nacos_key)
174
+ if redis_url:
175
+ redis_config = {'url': redis_url}
176
+ except Exception as e:
177
+ logger.error(f"从 Nacos 获取 Redis 配置失败: {e}")
178
+ continue
179
+ else:
180
+ # Direct 模式 - 直接使用 API 返回的 URL
181
+ redis_url = data.get('redis_url')
182
+ if redis_url:
183
+ redis_config = {'url': redis_url}
184
+
185
+ # 处理 PostgreSQL 配置
186
+ pg_config = {}
187
+ if pg_config_mode == 'nacos':
188
+ # Nacos 模式 - 需要通过 nacos_config 获取真实 URL
189
+ pg_nacos_key = data.get('pg_nacos_key')
190
+ if pg_nacos_key:
191
+ try:
192
+ from jettask.config.nacos_config import config as nacos_config
193
+ pg_url = nacos_config.get(pg_nacos_key)
194
+ if pg_url:
195
+ pg_config = {'url': pg_url}
196
+ except Exception as e:
197
+ logger.warning(f"从 Nacos 获取 PG 配置失败: {e}")
198
+ # PostgreSQL 是可选的
199
+ else:
200
+ # Direct 模式 - 直接使用 API 返回的 URL
201
+ pg_url = data.get('pg_url')
202
+ if pg_url:
203
+ pg_config = {'url': pg_url}
204
+
205
+ # 兼容旧格式:如果没有 config_mode 字段(旧版 API)
206
+ if not redis_config:
207
+ redis_url = data.get('redis_url', '')
208
+ if redis_url:
209
+ redis_config = {'url': redis_url}
210
+
211
+ if not pg_config:
212
+ pg_url = data.get('pg_url', '')
213
+ if pg_url:
214
+ pg_config = {'url': pg_url}
215
+
148
216
  # 跳过没有有效配置的命名空间
149
- redis_url = data.get('redis_url', '')
150
- pg_url = data.get('pg_url', '')
151
-
152
- if not redis_url or not pg_url:
217
+ if not redis_config or not pg_config:
153
218
  # logger.warning(f"跳过命名空间 {data['name']}:缺少 Redis 或 PostgreSQL 配置")
154
219
  continue
155
-
156
- redis_config = {'url': redis_url}
157
- pg_config = {'url': pg_url}
158
-
220
+
159
221
  ns_info = {
160
222
  'id': data.get('id', data['name']), # 如果没有id,使用name作为id
161
223
  'name': data['name'],
@@ -179,18 +241,66 @@ class UnifiedManagerBase(ABC):
179
241
  if response.status == 200:
180
242
  data_list = await response.json()
181
243
  for data in data_list:
182
- # 构建redis和pg配置
244
+ # 处理新格式(带 config_mode 字段)
245
+ redis_config_mode = data.get('redis_config_mode', 'direct')
246
+ pg_config_mode = data.get('pg_config_mode', 'direct')
247
+
248
+ # 处理 Redis 配置
249
+ redis_config = {}
250
+ if redis_config_mode == 'nacos':
251
+ # Nacos 模式 - 需要通过 nacos_config 获取真实 URL
252
+ redis_nacos_key = data.get('redis_nacos_key')
253
+ if redis_nacos_key:
254
+ try:
255
+ from jettask.config.nacos_config import config as nacos_config
256
+ redis_url = nacos_config.get(redis_nacos_key)
257
+ if redis_url:
258
+ redis_config = {'url': redis_url}
259
+ except Exception as e:
260
+ logger.error(f"从 Nacos 获取 Redis 配置失败: {e}")
261
+ continue
262
+ else:
263
+ # Direct 模式 - 直接使用 API 返回的 URL
264
+ redis_url = data.get('redis_url')
265
+ if redis_url:
266
+ redis_config = {'url': redis_url}
267
+
268
+ # 处理 PostgreSQL 配置
269
+ pg_config = {}
270
+ if pg_config_mode == 'nacos':
271
+ # Nacos 模式 - 需要通过 nacos_config 获取真实 URL
272
+ pg_nacos_key = data.get('pg_nacos_key')
273
+ if pg_nacos_key:
274
+ try:
275
+ from jettask.config.nacos_config import config as nacos_config
276
+ pg_url = nacos_config.get(pg_nacos_key)
277
+ if pg_url:
278
+ pg_config = {'url': pg_url}
279
+ except Exception as e:
280
+ logger.warning(f"从 Nacos 获取 PG 配置失败: {e}")
281
+ # PostgreSQL 是可选的
282
+ else:
283
+ # Direct 模式 - 直接使用 API 返回的 URL
284
+ pg_url = data.get('pg_url')
285
+ if pg_url:
286
+ pg_config = {'url': pg_url}
287
+
288
+ # 兼容旧格式:如果没有 config_mode 字段(旧版 API)
289
+ if not redis_config:
290
+ redis_url = data.get('redis_url', '')
291
+ if redis_url:
292
+ redis_config = {'url': redis_url}
293
+
294
+ if not pg_config:
295
+ pg_url = data.get('pg_url', '')
296
+ if pg_url:
297
+ pg_config = {'url': pg_url}
298
+
183
299
  # 跳过没有有效配置的命名空间
184
- redis_url = data.get('redis_url', '')
185
- pg_url = data.get('pg_url', '')
186
-
187
- if not redis_url or not pg_url:
300
+ if not redis_config or not pg_config:
188
301
  # logger.warning(f"跳过命名空间 {data['name']}:缺少 Redis 或 PostgreSQL 配置")
189
302
  continue
190
-
191
- redis_config = {'url': redis_url}
192
- pg_config = {'url': pg_url}
193
-
303
+
194
304
  ns_info = {
195
305
  'id': data.get('id', data['name']), # 如果没有id,使用name作为id
196
306
  'name': data['name'],
jettask/db/__init__.py ADDED
@@ -0,0 +1,67 @@
1
+ """
2
+ 数据库模块
3
+
4
+ 提供统一的数据库模型定义、操作接口和连接管理
5
+ """
6
+
7
+ from .base import Base, get_engine, get_session, init_db
8
+ from .connector import (
9
+ # 连接池函数
10
+ get_sync_redis_pool,
11
+ get_async_redis_pool,
12
+ get_async_redis_pool_for_pubsub,
13
+ get_pg_engine_and_factory,
14
+ get_asyncpg_pool,
15
+ # 客户端实例函数
16
+ get_sync_redis_client,
17
+ get_async_redis_client,
18
+ get_dual_mode_async_redis_client,
19
+ # 缓存清理
20
+ clear_all_cache,
21
+ # 配置解析
22
+ DBConfig,
23
+ # 连接器类
24
+ SyncRedisConnector,
25
+ RedisConnector,
26
+ PostgreSQLConnector,
27
+ ConnectionManager,
28
+ # 便捷函数
29
+ create_redis_client,
30
+ create_pg_session,
31
+ )
32
+
33
+ __all__ = [
34
+ # SQLAlchemy 基础
35
+ 'Base',
36
+ 'get_engine',
37
+ 'get_session',
38
+ 'init_db',
39
+
40
+ # 连接池函数
41
+ 'get_sync_redis_pool',
42
+ 'get_async_redis_pool',
43
+ 'get_async_redis_pool_for_pubsub',
44
+ 'get_pg_engine_and_factory',
45
+ 'get_asyncpg_pool',
46
+
47
+ # 客户端实例函数
48
+ 'get_sync_redis_client',
49
+ 'get_async_redis_client',
50
+ 'get_dual_mode_async_redis_client',
51
+
52
+ # 缓存清理
53
+ 'clear_all_cache',
54
+
55
+ # 配置解析
56
+ 'DBConfig',
57
+
58
+ # 连接器类
59
+ 'SyncRedisConnector',
60
+ 'RedisConnector',
61
+ 'PostgreSQLConnector',
62
+ 'ConnectionManager',
63
+
64
+ # 便捷函数
65
+ 'create_redis_client',
66
+ 'create_pg_session',
67
+ ]