jettask 0.2.14__py3-none-any.whl → 0.2.16__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 +14 -35
- jettask/{webui/__main__.py → __main__.py} +4 -4
- jettask/api/__init__.py +103 -0
- jettask/api/v1/__init__.py +29 -0
- jettask/api/v1/alerts.py +226 -0
- jettask/api/v1/analytics.py +323 -0
- jettask/api/v1/namespaces.py +134 -0
- jettask/api/v1/overview.py +136 -0
- jettask/api/v1/queues.py +530 -0
- jettask/api/v1/scheduled.py +420 -0
- jettask/api/v1/settings.py +44 -0
- jettask/{webui/api.py → api.py} +4 -46
- jettask/{webui/backend → backend}/main.py +21 -109
- jettask/{webui/backend → backend}/main_unified.py +1 -1
- jettask/{webui/backend → backend}/namespace_api_old.py +3 -30
- jettask/{webui/backend → backend}/namespace_data_access.py +2 -1
- jettask/{webui/backend → backend}/unified_api_router.py +14 -74
- jettask/{core/cli.py → cli.py} +106 -26
- jettask/config/nacos_config.py +386 -0
- jettask/core/app.py +8 -100
- jettask/core/db_manager.py +515 -0
- jettask/core/event_pool.py +5 -2
- jettask/core/unified_manager_base.py +47 -14
- jettask/{webui/db_init.py → db_init.py} +1 -1
- jettask/executors/asyncio.py +2 -2
- jettask/{webui/integrated_gradio_app.py → integrated_gradio_app.py} +1 -1
- jettask/{webui/multi_namespace_consumer.py → multi_namespace_consumer.py} +5 -2
- jettask/{webui/pg_consumer.py → pg_consumer.py} +137 -69
- jettask/{webui/run.py → run.py} +1 -1
- jettask/{webui/run_webui.py → run_webui.py} +4 -4
- jettask/scheduler/multi_namespace_scheduler.py +2 -2
- jettask/scheduler/unified_manager.py +5 -5
- jettask/scheduler/unified_scheduler_manager.py +1 -1
- jettask/schemas/__init__.py +166 -0
- jettask/schemas/alert.py +99 -0
- jettask/schemas/backlog.py +122 -0
- jettask/schemas/common.py +139 -0
- jettask/schemas/monitoring.py +181 -0
- jettask/schemas/namespace.py +168 -0
- jettask/schemas/queue.py +83 -0
- jettask/schemas/scheduled_task.py +128 -0
- jettask/schemas/task.py +70 -0
- jettask/services/__init__.py +24 -0
- jettask/services/alert_service.py +454 -0
- jettask/services/analytics_service.py +46 -0
- jettask/services/overview_service.py +978 -0
- jettask/services/queue_service.py +711 -0
- jettask/services/redis_monitor_service.py +151 -0
- jettask/services/scheduled_task_service.py +207 -0
- jettask/services/settings_service.py +758 -0
- jettask/services/task_service.py +157 -0
- jettask/{webui/task_center.py → task_center.py} +30 -8
- jettask/{webui/task_center_client.py → task_center_client.py} +1 -1
- jettask/{webui/config.py → webui_config.py} +6 -1
- jettask/webui_exceptions.py +67 -0
- jettask/webui_sql/verify_database.sql +72 -0
- {jettask-0.2.14.dist-info → jettask-0.2.16.dist-info}/METADATA +3 -1
- jettask-0.2.16.dist-info/RECORD +150 -0
- {jettask-0.2.14.dist-info → jettask-0.2.16.dist-info}/entry_points.txt +1 -1
- jettask/webui/backend/data_api.py +0 -3294
- jettask/webui/backend/namespace_api.py +0 -295
- jettask/webui/backend/queue_backlog_api.py +0 -727
- jettask/webui/backend/redis_monitor_api.py +0 -476
- jettask/webui/frontend/index.html +0 -13
- jettask/webui/frontend/package.json +0 -30
- jettask/webui/frontend/src/App.css +0 -109
- jettask/webui/frontend/src/App.jsx +0 -66
- jettask/webui/frontend/src/components/NamespaceSelector.jsx +0 -166
- jettask/webui/frontend/src/components/QueueBacklogChart.jsx +0 -298
- jettask/webui/frontend/src/components/QueueBacklogTrend.jsx +0 -638
- jettask/webui/frontend/src/components/QueueDetailsTable.css +0 -65
- jettask/webui/frontend/src/components/QueueDetailsTable.jsx +0 -487
- jettask/webui/frontend/src/components/QueueDetailsTableV2.jsx +0 -465
- jettask/webui/frontend/src/components/ScheduledTaskFilter.jsx +0 -423
- jettask/webui/frontend/src/components/TaskFilter.jsx +0 -425
- jettask/webui/frontend/src/components/TimeRangeSelector.css +0 -21
- jettask/webui/frontend/src/components/TimeRangeSelector.jsx +0 -160
- jettask/webui/frontend/src/components/charts/QueueChart.jsx +0 -111
- jettask/webui/frontend/src/components/charts/QueueTrendChart.jsx +0 -115
- jettask/webui/frontend/src/components/charts/WorkerChart.jsx +0 -40
- jettask/webui/frontend/src/components/common/StatsCard.jsx +0 -18
- jettask/webui/frontend/src/components/layout/AppLayout.css +0 -95
- jettask/webui/frontend/src/components/layout/AppLayout.jsx +0 -49
- jettask/webui/frontend/src/components/layout/Header.css +0 -106
- jettask/webui/frontend/src/components/layout/Header.jsx +0 -106
- jettask/webui/frontend/src/components/layout/SideMenu.css +0 -137
- jettask/webui/frontend/src/components/layout/SideMenu.jsx +0 -209
- jettask/webui/frontend/src/components/layout/TabsNav.css +0 -244
- jettask/webui/frontend/src/components/layout/TabsNav.jsx +0 -206
- jettask/webui/frontend/src/components/layout/UserInfo.css +0 -197
- jettask/webui/frontend/src/components/layout/UserInfo.jsx +0 -197
- jettask/webui/frontend/src/contexts/LoadingContext.jsx +0 -27
- jettask/webui/frontend/src/contexts/NamespaceContext.jsx +0 -72
- jettask/webui/frontend/src/contexts/TabsContext.backup.jsx +0 -245
- jettask/webui/frontend/src/index.css +0 -114
- jettask/webui/frontend/src/main.jsx +0 -22
- jettask/webui/frontend/src/pages/Alerts.jsx +0 -684
- jettask/webui/frontend/src/pages/Dashboard/index.css +0 -35
- jettask/webui/frontend/src/pages/Dashboard/index.jsx +0 -281
- jettask/webui/frontend/src/pages/Dashboard.jsx +0 -1330
- jettask/webui/frontend/src/pages/QueueDetail.jsx +0 -1117
- jettask/webui/frontend/src/pages/QueueMonitor.jsx +0 -527
- jettask/webui/frontend/src/pages/Queues.jsx +0 -12
- jettask/webui/frontend/src/pages/ScheduledTasks.jsx +0 -810
- jettask/webui/frontend/src/pages/Settings.jsx +0 -801
- jettask/webui/frontend/src/pages/Workers.jsx +0 -12
- jettask/webui/frontend/src/services/api.js +0 -159
- jettask/webui/frontend/src/services/queueTrend.js +0 -166
- jettask/webui/frontend/src/utils/suppressWarnings.js +0 -22
- jettask/webui/frontend/src/utils/userPreferences.js +0 -154
- jettask/webui/frontend/vite.config.js +0 -26
- jettask/webui/sql/init_database.sql +0 -640
- jettask-0.2.14.dist-info/RECORD +0 -172
- /jettask/{webui/backend → backend}/__init__.py +0 -0
- /jettask/{webui/backend → backend}/api/__init__.py +0 -0
- /jettask/{webui/backend → backend}/api/v1/__init__.py +0 -0
- /jettask/{webui/backend → backend}/api/v1/monitoring.py +0 -0
- /jettask/{webui/backend → backend}/api/v1/namespaces.py +0 -0
- /jettask/{webui/backend → backend}/api/v1/queues.py +0 -0
- /jettask/{webui/backend → backend}/api/v1/tasks.py +0 -0
- /jettask/{webui/backend → backend}/config.py +0 -0
- /jettask/{webui/backend → backend}/core/__init__.py +0 -0
- /jettask/{webui/backend → backend}/core/cache.py +0 -0
- /jettask/{webui/backend → backend}/core/database.py +0 -0
- /jettask/{webui/backend → backend}/core/exceptions.py +0 -0
- /jettask/{webui/backend → backend}/data_access.py +0 -0
- /jettask/{webui/backend → backend}/dependencies.py +0 -0
- /jettask/{webui/backend → backend}/init_meta_db.py +0 -0
- /jettask/{webui/backend → backend}/main_v2.py +0 -0
- /jettask/{webui/backend → backend}/models/__init__.py +0 -0
- /jettask/{webui/backend → backend}/models/requests.py +0 -0
- /jettask/{webui/backend → backend}/models/responses.py +0 -0
- /jettask/{webui/backend → backend}/queue_stats_v2.py +0 -0
- /jettask/{webui/backend → backend}/services/__init__.py +0 -0
- /jettask/{webui/backend → backend}/start.py +0 -0
- /jettask/{webui/cleanup_deprecated_tables.sql → cleanup_deprecated_tables.sql} +0 -0
- /jettask/{webui/gradio_app.py → gradio_app.py} +0 -0
- /jettask/{webui/__init__.py → main.py} +0 -0
- /jettask/{webui/models.py → models.py} +0 -0
- /jettask/{webui/run_monitor.py → run_monitor.py} +0 -0
- /jettask/{webui/schema.sql → schema.sql} +0 -0
- /jettask/{webui/unified_consumer_manager.py → unified_consumer_manager.py} +0 -0
- /jettask/{webui/models → webui_models}/__init__.py +0 -0
- /jettask/{webui/models → webui_models}/namespace.py +0 -0
- /jettask/{webui/sql → webui_sql}/batch_upsert_functions.sql +0 -0
- {jettask-0.2.14.dist-info → jettask-0.2.16.dist-info}/WHEEL +0 -0
- {jettask-0.2.14.dist-info → jettask-0.2.16.dist-info}/licenses/LICENSE +0 -0
- {jettask-0.2.14.dist-info → jettask-0.2.16.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
配置管理模块
|
|
5
|
+
使用 Nacos 作为配置中心
|
|
6
|
+
支持定时刷新和配置变更监听
|
|
7
|
+
"""
|
|
8
|
+
import os
|
|
9
|
+
import json
|
|
10
|
+
import yaml
|
|
11
|
+
import threading
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
from typing import Dict, Any, Callable, List
|
|
14
|
+
from dotenv import load_dotenv
|
|
15
|
+
from nacos import NacosClient
|
|
16
|
+
import logging
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Config:
|
|
24
|
+
"""配置管理类,支持定时刷新和配置监听"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, refresh_interval: int = 30):
|
|
27
|
+
"""初始化配置管理器
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
refresh_interval: 配置刷新间隔(秒),默认30秒
|
|
31
|
+
"""
|
|
32
|
+
self.nacos_config = None
|
|
33
|
+
|
|
34
|
+
self.nacos_group = None
|
|
35
|
+
self.nacos_data_id = None
|
|
36
|
+
self.local_dev_mode = None
|
|
37
|
+
# 服务注册信息
|
|
38
|
+
self.service_info = None
|
|
39
|
+
|
|
40
|
+
# 刷新相关
|
|
41
|
+
self.refresh_interval = refresh_interval
|
|
42
|
+
self.refresh_thread = None
|
|
43
|
+
self.stop_refresh = threading.Event()
|
|
44
|
+
self.last_refresh_time = None
|
|
45
|
+
self.config_version = 0
|
|
46
|
+
|
|
47
|
+
# 配置变更监听器
|
|
48
|
+
self.change_listeners: List[Callable[[Dict[str, Any]], None]] = []
|
|
49
|
+
|
|
50
|
+
# Nacos客户端(保持长连接)
|
|
51
|
+
self.nacos_client = None
|
|
52
|
+
|
|
53
|
+
# 配置锁,保证线程安全
|
|
54
|
+
self._config_lock = threading.RLock()
|
|
55
|
+
|
|
56
|
+
# 初始化配置
|
|
57
|
+
self._init_config()
|
|
58
|
+
|
|
59
|
+
self._config = self._load_config()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# 启动定时刷新
|
|
63
|
+
if not self.local_dev_mode and refresh_interval > 0:
|
|
64
|
+
self.start_refresh_thread()
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def config(self) -> dict:
|
|
68
|
+
"""获取当前配置(线程安全)"""
|
|
69
|
+
with self._config_lock:
|
|
70
|
+
if not hasattr(self, '_config'):
|
|
71
|
+
self._config = self._load_config()
|
|
72
|
+
return self._config
|
|
73
|
+
|
|
74
|
+
def _init_config(self):
|
|
75
|
+
"""初始化配置"""
|
|
76
|
+
try:
|
|
77
|
+
# 加载 .env 文件
|
|
78
|
+
load_dotenv()
|
|
79
|
+
self.nacos_config = {
|
|
80
|
+
'server_addresses': os.getenv('NACOS_SERVER', '127.0.0.1:8848'),
|
|
81
|
+
'namespace': os.getenv('NACOS_NAMESPACE', 'tamar_console_dev'),
|
|
82
|
+
'username': os.getenv('NACOS_USERNAME', 'nacos'),
|
|
83
|
+
'password': os.getenv('NACOS_PASSWORD', 'nacos')
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
self.nacos_group = os.getenv('NACOS_GROUP', 'DEFAULT_GROUP')
|
|
87
|
+
self.nacos_data_id = os.getenv('NACOS_DATA_ID', 'tamar-console-dev')
|
|
88
|
+
self.local_dev_mode = os.getenv('LOCAL_DEV_MODE', 'true').lower() == 'true'
|
|
89
|
+
|
|
90
|
+
# 服务注册信息
|
|
91
|
+
self.service_info = {
|
|
92
|
+
'name': os.getenv('SERVICE_NAME', 'tamar-console'),
|
|
93
|
+
'domain': os.getenv('SERVICE_DOMAIN', '127.0.0.1'),
|
|
94
|
+
'port': int(os.getenv('SERVICE_PORT', '8002'))
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
# 创建 Nacos 客户端(保持连接)
|
|
98
|
+
if not self.nacos_client:
|
|
99
|
+
self.nacos_client = NacosClient(
|
|
100
|
+
self.nacos_config['server_addresses'],
|
|
101
|
+
namespace=self.nacos_config['namespace'],
|
|
102
|
+
username=self.nacos_config['username'],
|
|
103
|
+
password=self.nacos_config['password']
|
|
104
|
+
)
|
|
105
|
+
logger.info(f"Nacos客户端初始化成功: {self.nacos_config['server_addresses']}")
|
|
106
|
+
except Exception as e:
|
|
107
|
+
logger.error(f"初始化配置失败: {e}")
|
|
108
|
+
raise
|
|
109
|
+
|
|
110
|
+
def _load_config(self):
|
|
111
|
+
"""加载配置"""
|
|
112
|
+
try:
|
|
113
|
+
# 尝试从 Nacos 加载配置
|
|
114
|
+
config = self._load_from_nacos()
|
|
115
|
+
self.last_refresh_time = datetime.now()
|
|
116
|
+
self.config_version += 1
|
|
117
|
+
logger.info(f"配置加载成功,版本: {self.config_version}")
|
|
118
|
+
return config
|
|
119
|
+
except Exception as e:
|
|
120
|
+
logger.error(f"从 Nacos 加载配置失败: {e}")
|
|
121
|
+
# 如果是首次加载失败,抛出异常
|
|
122
|
+
if not hasattr(self, '_config'):
|
|
123
|
+
raise
|
|
124
|
+
# 如果是刷新失败,保持现有配置
|
|
125
|
+
logger.warning("配置刷新失败,保持现有配置")
|
|
126
|
+
return self._config
|
|
127
|
+
|
|
128
|
+
def _load_from_nacos(self):
|
|
129
|
+
"""从 Nacos 加载配置"""
|
|
130
|
+
# if self.local_dev_mode:
|
|
131
|
+
# # 本地开发模式,使用默认配置
|
|
132
|
+
# logger.info("本地开发模式,使用默认配置")
|
|
133
|
+
# return self._get_default_config()
|
|
134
|
+
|
|
135
|
+
if not self.nacos_client:
|
|
136
|
+
raise ValueError("Nacos客户端未初始化")
|
|
137
|
+
|
|
138
|
+
# 获取配置
|
|
139
|
+
config_str = self.nacos_client.get_config(self.nacos_data_id, self.nacos_group)
|
|
140
|
+
|
|
141
|
+
if not config_str:
|
|
142
|
+
logger.warning(f"Nacos配置为空: {self.nacos_data_id}/{self.nacos_group}")
|
|
143
|
+
return self._get_default_config()
|
|
144
|
+
|
|
145
|
+
# 解析配置(支持 Properties、YAML 和 JSON)
|
|
146
|
+
if self._is_properties_format(config_str):
|
|
147
|
+
config = self._parse_properties(config_str)
|
|
148
|
+
else:
|
|
149
|
+
# 尝试 YAML 或 JSON
|
|
150
|
+
try:
|
|
151
|
+
config = yaml.safe_load(config_str)
|
|
152
|
+
except:
|
|
153
|
+
try:
|
|
154
|
+
config = json.loads(config_str)
|
|
155
|
+
except:
|
|
156
|
+
logger.error(f"无法解析配置格式: {config_str[:100]}...")
|
|
157
|
+
raise
|
|
158
|
+
logger.debug(f"成功加载配置,包含 {len(config)} 个配置项")
|
|
159
|
+
return config
|
|
160
|
+
|
|
161
|
+
def _get_default_config(self) -> Dict[str, Any]:
|
|
162
|
+
"""获取默认配置"""
|
|
163
|
+
return {
|
|
164
|
+
'PG_DB_HOST': 'localhost',
|
|
165
|
+
'PG_DB_PORT': 5432,
|
|
166
|
+
'PG_DB_USERNAME': 'jettask',
|
|
167
|
+
'PG_DB_PASSWORD': '123456',
|
|
168
|
+
'PG_DB_DATABASE': 'jettask',
|
|
169
|
+
'REDIS_HOST': 'localhost',
|
|
170
|
+
'REDIS_PORT': 6379,
|
|
171
|
+
'REDIS_DB': 0,
|
|
172
|
+
'REDIS_PASSWORD': None
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
def _is_properties_format(self, config_str):
|
|
176
|
+
"""判断是否为 Properties 格式"""
|
|
177
|
+
lines = config_str.strip().split('\n')
|
|
178
|
+
for line in lines:
|
|
179
|
+
line = line.strip()
|
|
180
|
+
if line and not line.startswith('#'):
|
|
181
|
+
return '=' in line
|
|
182
|
+
return False
|
|
183
|
+
|
|
184
|
+
def _parse_properties(self, config_str):
|
|
185
|
+
"""解析 Properties 格式配置"""
|
|
186
|
+
config = {}
|
|
187
|
+
lines = config_str.strip().split('\n')
|
|
188
|
+
|
|
189
|
+
for line in lines:
|
|
190
|
+
line = line.strip()
|
|
191
|
+
# 跳过空行和注释
|
|
192
|
+
if not line or line.startswith('#'):
|
|
193
|
+
continue
|
|
194
|
+
|
|
195
|
+
# 解析键值对
|
|
196
|
+
if '=' in line:
|
|
197
|
+
key, value = line.split('=', 1)
|
|
198
|
+
key = key.strip()
|
|
199
|
+
value = value.strip()
|
|
200
|
+
|
|
201
|
+
# 转换为嵌套字典
|
|
202
|
+
parts = key.split('.')
|
|
203
|
+
current = config
|
|
204
|
+
for i, part in enumerate(parts[:-1]):
|
|
205
|
+
if part not in current:
|
|
206
|
+
current[part] = {}
|
|
207
|
+
current = current[part]
|
|
208
|
+
|
|
209
|
+
# 尝试转换数据类型
|
|
210
|
+
final_value = value
|
|
211
|
+
if value.lower() == 'true':
|
|
212
|
+
final_value = True
|
|
213
|
+
elif value.lower() == 'false':
|
|
214
|
+
final_value = False
|
|
215
|
+
elif value.isdigit():
|
|
216
|
+
final_value = int(value)
|
|
217
|
+
elif self._is_float(value):
|
|
218
|
+
final_value = float(value)
|
|
219
|
+
|
|
220
|
+
current[parts[-1]] = final_value
|
|
221
|
+
|
|
222
|
+
return config
|
|
223
|
+
|
|
224
|
+
def _is_float(self, value):
|
|
225
|
+
"""判断字符串是否为浮点数"""
|
|
226
|
+
try:
|
|
227
|
+
float(value)
|
|
228
|
+
return '.' in value
|
|
229
|
+
except:
|
|
230
|
+
return False
|
|
231
|
+
|
|
232
|
+
def get_database_url(self):
|
|
233
|
+
"""构建数据库连接字符串(FastAPI 使用)"""
|
|
234
|
+
return self._get_database_uri()
|
|
235
|
+
|
|
236
|
+
def _get_database_uri(self):
|
|
237
|
+
"""构建数据库连接字符串"""
|
|
238
|
+
config = self.config
|
|
239
|
+
host = config.get("PG_DB_HOST")
|
|
240
|
+
port = config.get("PG_DB_PORT")
|
|
241
|
+
username = config.get("PG_DB_USERNAME")
|
|
242
|
+
password = config.get("PG_DB_PASSWORD")
|
|
243
|
+
database = config.get("PG_DB_DATABASE")
|
|
244
|
+
return f'postgresql+asyncpg://{username}:{password}@{host}:{port}/{database}'
|
|
245
|
+
|
|
246
|
+
def get_redis_connection(self):
|
|
247
|
+
"""获取 Redis 异步连接实例"""
|
|
248
|
+
config = self.config
|
|
249
|
+
|
|
250
|
+
redis_host = config.get("REDIS_HOST", "localhost")
|
|
251
|
+
redis_port = int(config.get("REDIS_PORT", "6379"))
|
|
252
|
+
redis_db = int(config.get("REDIS_DB", "0"))
|
|
253
|
+
redis_password = config.get("REDIS_PASSWORD") or None
|
|
254
|
+
redis_user = config.get("REDIS_USER") or None
|
|
255
|
+
|
|
256
|
+
# 构建 redis:// 或 rediss:// 的 URL(注意 password 可能为空)
|
|
257
|
+
if redis_user:
|
|
258
|
+
redis_url = f"redis://{redis_user}:{redis_password}@{redis_host}:{redis_port}/{redis_db}"
|
|
259
|
+
elif redis_password:
|
|
260
|
+
redis_url = f"redis://:{redis_password}@{redis_host}:{redis_port}/{redis_db}"
|
|
261
|
+
else:
|
|
262
|
+
redis_url = f"redis://{redis_host}:{redis_port}/{redis_db}"
|
|
263
|
+
print(f'{redis_url=}')
|
|
264
|
+
return redis_url
|
|
265
|
+
|
|
266
|
+
def get(self, key, default=None):
|
|
267
|
+
"""获取配置项(线程安全)"""
|
|
268
|
+
with self._config_lock:
|
|
269
|
+
return self.config.get(key, default)
|
|
270
|
+
|
|
271
|
+
def refresh(self) -> bool:
|
|
272
|
+
"""手动刷新配置
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
True if config was refreshed, False otherwise
|
|
276
|
+
"""
|
|
277
|
+
try:
|
|
278
|
+
logger.info("手动触发配置刷新")
|
|
279
|
+
old_config = self._config.copy() if hasattr(self, '_config') else {}
|
|
280
|
+
|
|
281
|
+
with self._config_lock:
|
|
282
|
+
new_config = self._load_config()
|
|
283
|
+
self._config = new_config
|
|
284
|
+
|
|
285
|
+
# 检查配置是否有变化
|
|
286
|
+
if old_config != new_config:
|
|
287
|
+
logger.info("配置已更新,通知监听器")
|
|
288
|
+
self._notify_listeners(new_config)
|
|
289
|
+
return True
|
|
290
|
+
else:
|
|
291
|
+
logger.debug("配置无变化")
|
|
292
|
+
return False
|
|
293
|
+
except Exception as e:
|
|
294
|
+
logger.error(f"刷新配置失败: {e}")
|
|
295
|
+
return False
|
|
296
|
+
|
|
297
|
+
def start_refresh_thread(self):
|
|
298
|
+
"""启动定时刷新线程"""
|
|
299
|
+
if self.refresh_thread and self.refresh_thread.is_alive():
|
|
300
|
+
logger.warning("刷新线程已在运行")
|
|
301
|
+
return
|
|
302
|
+
|
|
303
|
+
self.stop_refresh.clear()
|
|
304
|
+
self.refresh_thread = threading.Thread(
|
|
305
|
+
target=self._refresh_loop,
|
|
306
|
+
name="NacosConfigRefresh",
|
|
307
|
+
daemon=True
|
|
308
|
+
)
|
|
309
|
+
self.refresh_thread.start()
|
|
310
|
+
logger.info(f"配置刷新线程已启动,刷新间隔: {self.refresh_interval}秒")
|
|
311
|
+
|
|
312
|
+
def stop_refresh_thread(self):
|
|
313
|
+
"""停止定时刷新线程"""
|
|
314
|
+
if self.refresh_thread and self.refresh_thread.is_alive():
|
|
315
|
+
logger.info("正在停止配置刷新线程...")
|
|
316
|
+
self.stop_refresh.set()
|
|
317
|
+
self.refresh_thread.join(timeout=5)
|
|
318
|
+
logger.info("配置刷新线程已停止")
|
|
319
|
+
|
|
320
|
+
def _refresh_loop(self):
|
|
321
|
+
"""刷新循环"""
|
|
322
|
+
logger.info("配置刷新循环已开始")
|
|
323
|
+
|
|
324
|
+
while not self.stop_refresh.is_set():
|
|
325
|
+
try:
|
|
326
|
+
# 等待指定的刷新间隔
|
|
327
|
+
if self.stop_refresh.wait(self.refresh_interval):
|
|
328
|
+
break
|
|
329
|
+
|
|
330
|
+
# 执行刷新
|
|
331
|
+
self.refresh()
|
|
332
|
+
|
|
333
|
+
except Exception as e:
|
|
334
|
+
logger.error(f"配置刷新循环异常: {e}")
|
|
335
|
+
# 发生异常后等待一段时间再重试
|
|
336
|
+
if self.stop_refresh.wait(10):
|
|
337
|
+
break
|
|
338
|
+
|
|
339
|
+
logger.info("配置刷新循环已结束")
|
|
340
|
+
|
|
341
|
+
def add_change_listener(self, listener: Callable[[Dict[str, Any]], None]):
|
|
342
|
+
"""添加配置变更监听器
|
|
343
|
+
|
|
344
|
+
Args:
|
|
345
|
+
listener: 配置变更时的回调函数,接收新配置作为参数
|
|
346
|
+
"""
|
|
347
|
+
if listener not in self.change_listeners:
|
|
348
|
+
self.change_listeners.append(listener)
|
|
349
|
+
logger.info(f"添加配置变更监听器: {listener.__name__}")
|
|
350
|
+
|
|
351
|
+
def remove_change_listener(self, listener: Callable[[Dict[str, Any]], None]):
|
|
352
|
+
"""移除配置变更监听器"""
|
|
353
|
+
if listener in self.change_listeners:
|
|
354
|
+
self.change_listeners.remove(listener)
|
|
355
|
+
logger.info(f"移除配置变更监听器: {listener.__name__}")
|
|
356
|
+
|
|
357
|
+
def _notify_listeners(self, new_config: Dict[str, Any]):
|
|
358
|
+
"""通知所有监听器配置已变更"""
|
|
359
|
+
for listener in self.change_listeners:
|
|
360
|
+
try:
|
|
361
|
+
listener(new_config)
|
|
362
|
+
except Exception as e:
|
|
363
|
+
logger.error(f"配置变更监听器执行失败 {listener.__name__}: {e}")
|
|
364
|
+
|
|
365
|
+
def get_config_info(self) -> Dict[str, Any]:
|
|
366
|
+
"""获取配置信息"""
|
|
367
|
+
return {
|
|
368
|
+
'version': self.config_version,
|
|
369
|
+
'last_refresh_time': self.last_refresh_time.isoformat() if self.last_refresh_time else None,
|
|
370
|
+
'refresh_interval': self.refresh_interval,
|
|
371
|
+
'refresh_thread_active': self.refresh_thread.is_alive() if self.refresh_thread else False,
|
|
372
|
+
'nacos_server': self.nacos_config['server_addresses'] if self.nacos_config else None,
|
|
373
|
+
'nacos_namespace': self.nacos_config['namespace'] if self.nacos_config else None,
|
|
374
|
+
'nacos_group': self.nacos_group,
|
|
375
|
+
'nacos_data_id': self.nacos_data_id,
|
|
376
|
+
'local_dev_mode': self.local_dev_mode,
|
|
377
|
+
'config_items_count': len(self.config) if hasattr(self, '_config') else 0
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
def __del__(self):
|
|
381
|
+
"""析构函数,确保线程正确关闭"""
|
|
382
|
+
self.stop_refresh_thread()
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
# 创建全局配置实例(默认30秒刷新一次)
|
|
386
|
+
config = Config(refresh_interval=30)
|
jettask/core/app.py
CHANGED
|
@@ -298,76 +298,7 @@ class Jettask(object):
|
|
|
298
298
|
self._should_exit = False
|
|
299
299
|
self._worker_started = False
|
|
300
300
|
self._handlers_registered = False
|
|
301
|
-
|
|
302
|
-
async def _load_config_from_task_center_async(self):
|
|
303
|
-
"""异步加载任务中心配置"""
|
|
304
|
-
try:
|
|
305
|
-
if not self.task_center:
|
|
306
|
-
return False
|
|
307
|
-
|
|
308
|
-
# 连接并获取配置
|
|
309
|
-
connected = await self.task_center.connect()
|
|
310
|
-
if not connected:
|
|
311
|
-
return False
|
|
312
|
-
|
|
313
|
-
config = {
|
|
314
|
-
'redis_config': self.task_center.redis_config,
|
|
315
|
-
'pg_config': self.task_center.pg_config,
|
|
316
|
-
'namespace_name': self.task_center.namespace_name,
|
|
317
|
-
'version': self.task_center.version
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
if config['redis_config']:
|
|
321
|
-
# 任务中心配置优先级高于手动配置
|
|
322
|
-
redis_config = config.get('redis_config', {})
|
|
323
|
-
pg_config = config.get('pg_config', {})
|
|
324
|
-
# 构建Redis URL
|
|
325
|
-
if redis_config:
|
|
326
|
-
redis_host = redis_config.get('host', 'localhost')
|
|
327
|
-
redis_port = redis_config.get('port', 6379)
|
|
328
|
-
redis_password = redis_config.get('password')
|
|
329
|
-
redis_db = redis_config.get('db', 0)
|
|
330
|
-
|
|
331
|
-
if redis_password:
|
|
332
|
-
self.redis_url = f"redis://:{redis_password}@{redis_host}:{redis_port}/{redis_db}"
|
|
333
|
-
else:
|
|
334
|
-
self.redis_url = f"redis://{redis_host}:{redis_port}/{redis_db}"
|
|
335
|
-
|
|
336
|
-
logger.info(f"从任务中心加载Redis配置: {redis_host}:{redis_port}/{redis_db}")
|
|
337
|
-
|
|
338
|
-
# 构建PostgreSQL URL
|
|
339
|
-
if pg_config:
|
|
340
|
-
pg_host = pg_config.get('host', 'localhost')
|
|
341
|
-
pg_port = pg_config.get('port', 5432)
|
|
342
|
-
pg_user = pg_config.get('user', 'postgres')
|
|
343
|
-
pg_password = pg_config.get('password', '')
|
|
344
|
-
pg_database = pg_config.get('database', 'jettask')
|
|
345
|
-
|
|
346
|
-
self.pg_url = f"postgresql://{pg_user}:{pg_password}@{pg_host}:{pg_port}/{pg_database}"
|
|
347
|
-
logger.info(f"从任务中心加载PostgreSQL配置: {pg_host}:{pg_port}/{pg_database}")
|
|
348
|
-
|
|
349
|
-
# 保存配置供后续使用
|
|
350
|
-
self._task_center_config = config
|
|
351
|
-
|
|
352
|
-
# 更新Redis前缀为命名空间名称
|
|
353
|
-
if self.task_center and self.task_center.redis_prefix != "jettask":
|
|
354
|
-
self.redis_prefix = self.task_center.redis_prefix
|
|
355
|
-
# 更新相关前缀
|
|
356
|
-
self.STATUS_PREFIX = f"{self.redis_prefix}:STATUS:"
|
|
357
|
-
self.RESULT_PREFIX = f"{self.redis_prefix}:RESULT:"
|
|
358
|
-
|
|
359
|
-
# 清理已缓存的Redis连接,强制重新创建
|
|
360
|
-
if hasattr(self, '_redis'):
|
|
361
|
-
delattr(self, '_redis')
|
|
362
|
-
if hasattr(self, '_async_redis'):
|
|
363
|
-
delattr(self, '_async_redis')
|
|
364
|
-
if hasattr(self, '_ep'):
|
|
365
|
-
delattr(self, '_ep')
|
|
366
|
-
|
|
367
|
-
return True
|
|
368
|
-
except Exception as e:
|
|
369
|
-
logger.warning(f"从任务中心加载配置失败: {e}")
|
|
370
|
-
return False
|
|
301
|
+
|
|
371
302
|
|
|
372
303
|
def _load_config_from_task_center(self):
|
|
373
304
|
"""从任务中心加载配置"""
|
|
@@ -461,7 +392,7 @@ class Jettask(object):
|
|
|
461
392
|
task_center: TaskCenter实例
|
|
462
393
|
|
|
463
394
|
使用示例:
|
|
464
|
-
from jettask.
|
|
395
|
+
from jettask.task_center import TaskCenter
|
|
465
396
|
|
|
466
397
|
# 创建任务中心客户端(可复用)
|
|
467
398
|
task_center = TaskCenter("http://localhost:8001/api/namespaces/demo")
|
|
@@ -510,24 +441,6 @@ class Jettask(object):
|
|
|
510
441
|
'version': task_center.version
|
|
511
442
|
}
|
|
512
443
|
|
|
513
|
-
async def init_from_task_center(self):
|
|
514
|
-
"""
|
|
515
|
-
从任务中心初始化配置(异步方法)
|
|
516
|
-
|
|
517
|
-
在异步环境中发送任务前调用此方法,确保配置已加载
|
|
518
|
-
|
|
519
|
-
使用示例:
|
|
520
|
-
from jettask.webui.task_center import TaskCenter
|
|
521
|
-
|
|
522
|
-
task_center = TaskCenter("http://localhost:8001/api/namespaces/demo")
|
|
523
|
-
app = Jettask()
|
|
524
|
-
app.mount_task_center(task_center)
|
|
525
|
-
await app.init_from_task_center() # 会自动连接task_center
|
|
526
|
-
await task.apply_async(...)
|
|
527
|
-
"""
|
|
528
|
-
if self.task_center and self.task_center.is_enabled and not self._task_center_config:
|
|
529
|
-
return await self._load_config_from_task_center_async()
|
|
530
|
-
return True
|
|
531
444
|
|
|
532
445
|
def _setup_cleanup_handlers(self):
|
|
533
446
|
"""设置清理处理器"""
|
|
@@ -619,9 +532,9 @@ class Jettask(object):
|
|
|
619
532
|
return getattr(self, name)
|
|
620
533
|
|
|
621
534
|
# 如果配置了任务中心且还未加载配置,先加载配置
|
|
622
|
-
if self.task_center and self.task_center.is_enabled and not self._task_center_config:
|
|
623
|
-
|
|
624
|
-
|
|
535
|
+
# if self.task_center and self.task_center.is_enabled and not self._task_center_config:
|
|
536
|
+
# self._load_config_from_task_center()
|
|
537
|
+
print(f'{self.redis_url=}')
|
|
625
538
|
pool = get_redis_pool(self.redis_url, self.max_connections)
|
|
626
539
|
redis_cli = redis.StrictRedis(connection_pool=pool)
|
|
627
540
|
setattr(self, name, redis_cli)
|
|
@@ -1059,12 +972,7 @@ class Jettask(object):
|
|
|
1059
972
|
|
|
1060
973
|
event_queue = deque()
|
|
1061
974
|
|
|
1062
|
-
#
|
|
1063
|
-
try:
|
|
1064
|
-
self.ep.create_group()
|
|
1065
|
-
except Exception as e:
|
|
1066
|
-
logger.warning(f"创建消费者组时出错: {e}")
|
|
1067
|
-
# 继续执行,listening_event会自动处理
|
|
975
|
+
# 消费者组会在listening_event方法内部自动创建
|
|
1068
976
|
|
|
1069
977
|
# 根据执行器类型创建对应的执行器
|
|
1070
978
|
if execute_type == "asyncio":
|
|
@@ -1138,8 +1046,8 @@ class Jettask(object):
|
|
|
1138
1046
|
# 标记worker已启动
|
|
1139
1047
|
self._worker_started = True
|
|
1140
1048
|
|
|
1141
|
-
#
|
|
1142
|
-
if self.task_center and self.task_center.is_enabled:
|
|
1049
|
+
# 如果配置了任务中心且配置尚未加载,从任务中心获取配置
|
|
1050
|
+
if self.task_center and self.task_center.is_enabled and not self._task_center_config:
|
|
1143
1051
|
self._load_config_from_task_center()
|
|
1144
1052
|
|
|
1145
1053
|
# 注册清理处理器(只在启动worker时注册)
|