safe: 安全升级(不影响飞书/Pipeline/Redis)

- bcrypt 密码哈希升级(兼容旧 SHA-256,登录时自动升级)
- auth_manager.secret_key 从环境变量读取
- 前端事件总线(on/off/emit)
- ConfigService 统一配置服务
- AI 监控错误详情修复(Conversation 没有 category/source)
- README.md 重写
This commit is contained in:
2026-04-08 09:56:23 +08:00
parent b4aa4c8d02
commit 26737747d9
6 changed files with 149 additions and 516 deletions

View File

@@ -0,0 +1,76 @@
# -*- coding: utf-8 -*-
"""
统一配置服务
优先级:环境变量 > system_settings.json > 代码默认值
"""
import os
import json
import logging
from typing import Any
logger = logging.getLogger(__name__)
_SETTINGS_PATH = os.path.join('data', 'system_settings.json')
class ConfigService:
def __init__(self):
self._file_cache = None
self._file_mtime = 0
def _load_file(self) -> dict:
try:
if os.path.exists(_SETTINGS_PATH):
mtime = os.path.getmtime(_SETTINGS_PATH)
if mtime != self._file_mtime or self._file_cache is None:
with open(_SETTINGS_PATH, 'r', encoding='utf-8') as f:
self._file_cache = json.load(f)
self._file_mtime = mtime
return self._file_cache or {}
except Exception as e:
logger.debug(f"加载配置文件失败: {e}")
return {}
def get(self, key: str, default: Any = None) -> Any:
env_key = key.upper().replace('.', '_')
env_val = os.environ.get(env_key)
if env_val is not None:
return self._cast(env_val, default)
settings = self._load_file()
parts = key.split('.')
val = settings
for part in parts:
if isinstance(val, dict):
val = val.get(part)
else:
return default
return val if val is not None else default
def get_section(self, section: str) -> dict:
return self._load_file().get(section, {})
def set(self, key: str, value: Any):
settings = self._load_file()
parts = key.split('.')
target = settings
for part in parts[:-1]:
target = target.setdefault(part, {})
target[parts[-1]] = value
os.makedirs('data', exist_ok=True)
with open(_SETTINGS_PATH, 'w', encoding='utf-8') as f:
json.dump(settings, f, ensure_ascii=False, indent=2)
self._file_cache = settings
@staticmethod
def _cast(value: str, default: Any) -> Any:
if default is None: return value
if isinstance(default, bool): return value.lower() in ('true', '1', 'yes')
if isinstance(default, int):
try: return int(value)
except: return default
if isinstance(default, float):
try: return float(value)
except: return default
return value
config_service = ConfigService()