You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
333 lines
12 KiB
333 lines
12 KiB
"""配置管理服务 - 对应Go的internal/service/config.go"""
|
|
import platform
|
|
import psutil
|
|
import threading
|
|
from datetime import datetime, timedelta
|
|
from typing import Optional, List, Callable, Dict, Any
|
|
|
|
from app.models import (
|
|
ConfigListRequest, ConfigListData, ConfigSection, ConfigItem,
|
|
ConfigUpdateRequest, ConfigUpdateData, ConfigType,
|
|
ReloadRequest, ReloadData, SystemStatusData, MemoryInfo
|
|
)
|
|
from app.core.config import get_config, reload_config, save_config, Config
|
|
from app.core.logger import info
|
|
|
|
|
|
class ConfigService:
|
|
"""配置管理服务"""
|
|
|
|
def __init__(self):
|
|
self.config = get_config()
|
|
self.start_time = datetime.now()
|
|
self.version = "1.0.0"
|
|
self.callbacks: Dict[ConfigType, List[Callable]] = {}
|
|
self.lock = threading.RLock()
|
|
|
|
def get_config_list(self, req: ConfigListRequest) -> ConfigListData:
|
|
"""获取配置列表"""
|
|
sections = []
|
|
|
|
# 服务器配置
|
|
if not req.type or req.type == ConfigType.SERVER:
|
|
sections.append(ConfigSection(
|
|
name="服务器配置",
|
|
type=ConfigType.SERVER,
|
|
description="HTTP服务器相关配置",
|
|
items=[
|
|
ConfigItem(
|
|
key="port",
|
|
value=self.config.server.port,
|
|
type="int",
|
|
description="服务端口",
|
|
editable=True,
|
|
required=True
|
|
),
|
|
ConfigItem(
|
|
key="mode",
|
|
value=self.config.server.mode,
|
|
type="string",
|
|
description="运行模式: debug/release",
|
|
editable=True,
|
|
required=True
|
|
),
|
|
ConfigItem(
|
|
key="api_key",
|
|
value=self.config.server.api_key,
|
|
type="string",
|
|
description="API认证密钥",
|
|
editable=True,
|
|
required=True
|
|
),
|
|
]
|
|
))
|
|
|
|
# 数据库配置
|
|
if not req.type or req.type == ConfigType.DATABASE:
|
|
sections.append(ConfigSection(
|
|
name="数据库配置",
|
|
type=ConfigType.DATABASE,
|
|
description="PostgreSQL数据库连接配置",
|
|
items=[
|
|
ConfigItem(
|
|
key="host",
|
|
value=self.config.database.host,
|
|
type="string",
|
|
description="数据库主机地址",
|
|
editable=True,
|
|
required=True
|
|
),
|
|
ConfigItem(
|
|
key="port",
|
|
value=self.config.database.port,
|
|
type="int",
|
|
description="数据库端口",
|
|
editable=True,
|
|
required=True
|
|
),
|
|
ConfigItem(
|
|
key="user",
|
|
value=self.config.database.user,
|
|
type="string",
|
|
description="数据库用户名",
|
|
editable=True,
|
|
required=True
|
|
),
|
|
ConfigItem(
|
|
key="password",
|
|
value="********",
|
|
type="password",
|
|
description="数据库密码",
|
|
editable=True,
|
|
required=True
|
|
),
|
|
ConfigItem(
|
|
key="database",
|
|
value=self.config.database.database,
|
|
type="string",
|
|
description="数据库名",
|
|
editable=True,
|
|
required=True
|
|
),
|
|
]
|
|
))
|
|
|
|
# Redis配置
|
|
if not req.type or req.type == ConfigType.REDIS:
|
|
sections.append(ConfigSection(
|
|
name="Redis配置",
|
|
type=ConfigType.REDIS,
|
|
description="Redis缓存配置",
|
|
items=[
|
|
ConfigItem(
|
|
key="host",
|
|
value=self.config.redis.host,
|
|
type="string",
|
|
description="Redis主机地址",
|
|
editable=True,
|
|
required=False
|
|
),
|
|
ConfigItem(
|
|
key="port",
|
|
value=self.config.redis.port,
|
|
type="int",
|
|
description="Redis端口",
|
|
editable=True,
|
|
required=False
|
|
),
|
|
ConfigItem(
|
|
key="password",
|
|
value="********",
|
|
type="password",
|
|
description="Redis密码",
|
|
editable=True,
|
|
required=False
|
|
),
|
|
ConfigItem(
|
|
key="db",
|
|
value=self.config.redis.db,
|
|
type="int",
|
|
description="Redis数据库编号",
|
|
editable=True,
|
|
required=False
|
|
),
|
|
]
|
|
))
|
|
|
|
# 数据源配置
|
|
if not req.type or req.type == ConfigType.SOURCE:
|
|
sections.append(ConfigSection(
|
|
name="数据源配置",
|
|
type=ConfigType.SOURCE,
|
|
description="股票和期货数据源配置",
|
|
items=[
|
|
ConfigItem(
|
|
key="stock_active",
|
|
value=self.config.sources.stock.active,
|
|
type="string",
|
|
description="股票数据源适配器",
|
|
editable=True,
|
|
required=True
|
|
),
|
|
ConfigItem(
|
|
key="futures_active",
|
|
value=self.config.sources.futures.active,
|
|
type="string",
|
|
description="期货数据源适配器",
|
|
editable=True,
|
|
required=True
|
|
),
|
|
]
|
|
))
|
|
|
|
return ConfigListData(
|
|
sections=sections,
|
|
version=self.version,
|
|
updated=datetime.now()
|
|
)
|
|
|
|
def update_config(self, req: ConfigUpdateRequest) -> ConfigUpdateData:
|
|
"""更新配置"""
|
|
need_restart = False
|
|
|
|
with self.lock:
|
|
if req.type == ConfigType.SERVER:
|
|
if "port" in req.items:
|
|
self.config.server.port = int(req.items["port"])
|
|
need_restart = True
|
|
if "mode" in req.items:
|
|
self.config.server.mode = req.items["mode"]
|
|
if "api_key" in req.items:
|
|
self.config.server.api_key = req.items["api_key"]
|
|
|
|
elif req.type == ConfigType.DATABASE:
|
|
if "host" in req.items:
|
|
self.config.database.host = req.items["host"]
|
|
need_restart = True
|
|
if "port" in req.items:
|
|
self.config.database.port = int(req.items["port"])
|
|
need_restart = True
|
|
if "user" in req.items:
|
|
self.config.database.user = req.items["user"]
|
|
need_restart = True
|
|
if "password" in req.items:
|
|
password = req.items["password"]
|
|
if password != "********":
|
|
self.config.database.password = password
|
|
need_restart = True
|
|
if "database" in req.items:
|
|
self.config.database.database = req.items["database"]
|
|
need_restart = True
|
|
|
|
elif req.type == ConfigType.SOURCE:
|
|
if "stock_active" in req.items:
|
|
self.config.sources.stock.active = req.items["stock_active"]
|
|
if "futures_active" in req.items:
|
|
self.config.sources.futures.active = req.items["futures_active"]
|
|
|
|
# 保存到文件
|
|
try:
|
|
save_config(self.config)
|
|
self._trigger_callbacks(req.type)
|
|
|
|
message = "配置更新成功"
|
|
if need_restart:
|
|
message += ",部分配置需要重启服务后生效"
|
|
|
|
return ConfigUpdateData(
|
|
success=True,
|
|
need_restart=need_restart,
|
|
message=message
|
|
)
|
|
except Exception as e:
|
|
return ConfigUpdateData(
|
|
success=False,
|
|
need_restart=False,
|
|
message=f"配置保存失败: {e}"
|
|
)
|
|
|
|
def reload_config(self, req: ReloadRequest) -> ReloadData:
|
|
"""热加载配置"""
|
|
try:
|
|
with self.lock:
|
|
new_config = reload_config()
|
|
|
|
# 根据类型选择性更新
|
|
if req.config_type is None:
|
|
self.config = new_config
|
|
else:
|
|
if req.config_type == ConfigType.SERVER:
|
|
self.config.server = new_config.server
|
|
elif req.config_type == ConfigType.DATABASE:
|
|
self.config.database = new_config.database
|
|
elif req.config_type == ConfigType.REDIS:
|
|
self.config.redis = new_config.redis
|
|
elif req.config_type == ConfigType.SOURCE:
|
|
self.config.sources = new_config.sources
|
|
|
|
self._trigger_callbacks(req.config_type)
|
|
|
|
return ReloadData(
|
|
success=True,
|
|
message="配置热加载成功"
|
|
)
|
|
except Exception as e:
|
|
return ReloadData(
|
|
success=False,
|
|
message=f"加载配置失败: {e}"
|
|
)
|
|
|
|
def get_system_status(self) -> SystemStatusData:
|
|
"""获取系统状态"""
|
|
# 获取内存信息
|
|
mem = psutil.virtual_memory()
|
|
|
|
# 计算运行时长
|
|
uptime = datetime.now() - self.start_time
|
|
uptime_str = self._format_duration(uptime)
|
|
|
|
return SystemStatusData(
|
|
status="running",
|
|
version=self.version,
|
|
start_time=self.start_time,
|
|
uptime=uptime_str,
|
|
python_version=platform.python_version(),
|
|
memory=MemoryInfo(
|
|
alloc=mem.used,
|
|
total_alloc=mem.total,
|
|
sys=mem.total,
|
|
num_gc=0 # Python不需要显式GC计数
|
|
),
|
|
threads=threading.active_count()
|
|
)
|
|
|
|
def _format_duration(self, d: timedelta) -> str:
|
|
"""格式化持续时间"""
|
|
days = d.days
|
|
hours, remainder = divmod(d.seconds, 3600)
|
|
minutes, _ = divmod(remainder, 60)
|
|
|
|
if days > 0:
|
|
return f"{days}天{hours}小时{minutes}分钟"
|
|
if hours > 0:
|
|
return f"{hours}小时{minutes}分钟"
|
|
return f"{minutes}分钟"
|
|
|
|
def register_callback(self, config_type: ConfigType, callback: Callable):
|
|
"""注册配置变更回调"""
|
|
with self.lock:
|
|
if config_type not in self.callbacks:
|
|
self.callbacks[config_type] = []
|
|
self.callbacks[config_type].append(callback)
|
|
|
|
def _trigger_callbacks(self, config_type: Optional[ConfigType]):
|
|
"""触发回调"""
|
|
with self.lock:
|
|
# 触发特定类型的回调
|
|
if config_type and config_type in self.callbacks:
|
|
for cb in self.callbacks[config_type]:
|
|
try:
|
|
cb()
|
|
except Exception as e:
|
|
info(f"Callback error: {e}")
|