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

"""配置管理服务 - 对应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}")