mirror of
https://github.com/fawney19/Aether.git
synced 2026-01-05 09:12:27 +08:00
Initial commit
This commit is contained in:
257
src/services/system/config.py
Normal file
257
src/services/system/config.py
Normal file
@@ -0,0 +1,257 @@
|
||||
"""
|
||||
系统配置服务
|
||||
"""
|
||||
|
||||
import json
|
||||
from enum import Enum
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from src.core.logger import logger
|
||||
from src.models.database import Provider, SystemConfig
|
||||
|
||||
|
||||
|
||||
class LogLevel(str, Enum):
|
||||
"""日志记录级别"""
|
||||
|
||||
BASIC = "basic" # 仅记录基本信息(tokens、成本等)
|
||||
HEADERS = "headers" # 记录基本信息+请求/响应头(敏感信息会脱敏)
|
||||
FULL = "full" # 记录完整请求和响应(包含body,敏感信息会脱敏)
|
||||
|
||||
|
||||
class SystemConfigService:
|
||||
"""系统配置服务类"""
|
||||
|
||||
# 默认配置
|
||||
DEFAULT_CONFIGS = {
|
||||
"request_log_level": {
|
||||
"value": LogLevel.BASIC.value,
|
||||
"description": "请求记录级别:basic(基本信息), headers(含请求头), full(完整请求响应)",
|
||||
},
|
||||
"max_request_body_size": {
|
||||
"value": 1048576, # 1MB
|
||||
"description": "最大请求体记录大小(字节),超过此大小的请求体将被截断(仅影响数据库记录,不影响真实API请求)",
|
||||
},
|
||||
"max_response_body_size": {
|
||||
"value": 1048576, # 1MB
|
||||
"description": "最大响应体记录大小(字节),超过此大小的响应体将被截断(仅影响数据库记录,不影响真实API响应)",
|
||||
},
|
||||
"sensitive_headers": {
|
||||
"value": ["authorization", "x-api-key", "api-key", "cookie", "set-cookie"],
|
||||
"description": "敏感请求头列表,这些请求头会被脱敏处理",
|
||||
},
|
||||
# 分级清理策略
|
||||
"detail_log_retention_days": {
|
||||
"value": 7,
|
||||
"description": "详细日志保留天数,超过此天数后压缩 request_body 和 response_body 到压缩字段",
|
||||
},
|
||||
"compressed_log_retention_days": {
|
||||
"value": 90,
|
||||
"description": "压缩日志保留天数,超过此天数后删除压缩的 body 字段(保留headers和统计)",
|
||||
},
|
||||
"header_retention_days": {
|
||||
"value": 90,
|
||||
"description": "请求头保留天数,超过此天数后清空 request_headers 和 response_headers 字段",
|
||||
},
|
||||
"log_retention_days": {
|
||||
"value": 365,
|
||||
"description": "完整日志保留天数,超过此天数后删除整条记录(保留核心统计)",
|
||||
},
|
||||
"enable_auto_cleanup": {
|
||||
"value": True,
|
||||
"description": "是否启用自动清理任务,每天凌晨执行分级清理",
|
||||
},
|
||||
"cleanup_batch_size": {
|
||||
"value": 1000,
|
||||
"description": "每批次清理的记录数,避免单次操作过大影响数据库性能",
|
||||
},
|
||||
"provider_priority_mode": {
|
||||
"value": "provider",
|
||||
"description": "优先级策略:provider(提供商优先模式) 或 global_key(全局Key优先模式)",
|
||||
},
|
||||
"auto_delete_expired_keys": {
|
||||
"value": False,
|
||||
"description": "是否自动删除过期的API Key(True=物理删除,False=仅禁用),仅管理员可配置",
|
||||
},
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_config(cls, db: Session, key: str, default: Any = None) -> Optional[Any]:
|
||||
"""获取系统配置值"""
|
||||
config = db.query(SystemConfig).filter(SystemConfig.key == key).first()
|
||||
if config:
|
||||
return config.value
|
||||
|
||||
# 如果配置不存在,检查默认值
|
||||
if key in cls.DEFAULT_CONFIGS:
|
||||
return cls.DEFAULT_CONFIGS[key]["value"]
|
||||
|
||||
return default
|
||||
|
||||
@staticmethod
|
||||
def set_config(db: Session, key: str, value: Any, description: str = None) -> SystemConfig:
|
||||
"""设置系统配置值"""
|
||||
config = db.query(SystemConfig).filter(SystemConfig.key == key).first()
|
||||
|
||||
if config:
|
||||
# 更新现有配置
|
||||
config.value = value
|
||||
if description:
|
||||
config.description = description
|
||||
else:
|
||||
# 创建新配置
|
||||
config = SystemConfig(key=key, value=value, description=description)
|
||||
db.add(config)
|
||||
|
||||
db.commit()
|
||||
db.refresh(config)
|
||||
return config
|
||||
|
||||
@staticmethod
|
||||
def get_default_provider(db: Session) -> Optional[str]:
|
||||
"""
|
||||
获取系统默认提供商
|
||||
优先级:1. 管理员设置的默认提供商 2. 数据库中第一个可用提供商
|
||||
"""
|
||||
# 首先尝试获取管理员设置的默认提供商
|
||||
default_provider = SystemConfigService.get_config(db, "default_provider")
|
||||
if default_provider:
|
||||
return default_provider
|
||||
|
||||
# 如果没有设置,fallback到数据库中第一个可用提供商
|
||||
first_provider = db.query(Provider).filter(Provider.is_active == True).first()
|
||||
|
||||
if first_provider:
|
||||
return first_provider.name
|
||||
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def set_default_provider(db: Session, provider_name: str) -> SystemConfig:
|
||||
"""设置系统默认提供商"""
|
||||
return SystemConfigService.set_config(
|
||||
db, "default_provider", provider_name, "系统默认提供商,当用户未设置个人提供商时使用"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_all_configs(db: Session) -> list:
|
||||
"""获取所有系统配置"""
|
||||
configs = db.query(SystemConfig).all()
|
||||
return [
|
||||
{
|
||||
"key": config.key,
|
||||
"value": config.value,
|
||||
"description": config.description,
|
||||
"updated_at": config.updated_at.isoformat(),
|
||||
}
|
||||
for config in configs
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def delete_config(db: Session, key: str) -> bool:
|
||||
"""删除系统配置"""
|
||||
config = db.query(SystemConfig).filter(SystemConfig.key == key).first()
|
||||
if config:
|
||||
db.delete(config)
|
||||
db.commit()
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def init_default_configs(cls, db: Session):
|
||||
"""初始化默认配置"""
|
||||
for key, default_config in cls.DEFAULT_CONFIGS.items():
|
||||
if not db.query(SystemConfig).filter(SystemConfig.key == key).first():
|
||||
config = SystemConfig(
|
||||
key=key,
|
||||
value=default_config["value"],
|
||||
description=default_config["description"],
|
||||
)
|
||||
db.add(config)
|
||||
|
||||
db.commit()
|
||||
logger.info("初始化默认系统配置完成")
|
||||
|
||||
@classmethod
|
||||
def get_log_level(cls, db: Session) -> LogLevel:
|
||||
"""获取日志记录级别"""
|
||||
level = cls.get_config(db, "request_log_level", LogLevel.BASIC.value)
|
||||
if isinstance(level, str):
|
||||
return LogLevel(level)
|
||||
return level
|
||||
|
||||
@classmethod
|
||||
def should_log_headers(cls, db: Session) -> bool:
|
||||
"""是否应该记录请求头"""
|
||||
log_level = cls.get_log_level(db)
|
||||
return log_level in [LogLevel.HEADERS, LogLevel.FULL]
|
||||
|
||||
@classmethod
|
||||
def should_log_body(cls, db: Session) -> bool:
|
||||
"""是否应该记录请求体和响应体"""
|
||||
log_level = cls.get_log_level(db)
|
||||
return log_level == LogLevel.FULL
|
||||
|
||||
@classmethod
|
||||
def should_mask_sensitive_data(cls, db: Session) -> bool:
|
||||
"""是否应该脱敏敏感数据(始终脱敏)"""
|
||||
_ = db # 保持接口一致性
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def get_sensitive_headers(cls, db: Session) -> list:
|
||||
"""获取敏感请求头列表"""
|
||||
return cls.get_config(db, "sensitive_headers", [])
|
||||
|
||||
@classmethod
|
||||
def mask_sensitive_headers(cls, db: Session, headers: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""脱敏敏感请求头"""
|
||||
if not cls.should_mask_sensitive_data(db):
|
||||
return headers
|
||||
|
||||
sensitive_headers = cls.get_sensitive_headers(db)
|
||||
masked_headers = {}
|
||||
|
||||
for key, value in headers.items():
|
||||
if key.lower() in [h.lower() for h in sensitive_headers]:
|
||||
# 保留前后各4个字符,中间用星号替换
|
||||
if len(str(value)) > 8:
|
||||
masked_value = str(value)[:4] + "****" + str(value)[-4:]
|
||||
else:
|
||||
masked_value = "****"
|
||||
masked_headers[key] = masked_value
|
||||
else:
|
||||
masked_headers[key] = value
|
||||
|
||||
return masked_headers
|
||||
|
||||
@classmethod
|
||||
def truncate_body(cls, db: Session, body: Any, is_request: bool = True) -> Any:
|
||||
"""截断过大的请求体或响应体"""
|
||||
max_size_key = "max_request_body_size" if is_request else "max_response_body_size"
|
||||
max_size = cls.get_config(db, max_size_key, 102400)
|
||||
|
||||
if not body:
|
||||
return body
|
||||
|
||||
# 转换为字符串以计算大小
|
||||
body_str = json.dumps(body) if isinstance(body, (dict, list)) else str(body)
|
||||
|
||||
if len(body_str) > max_size:
|
||||
# 截断并添加提示
|
||||
truncated_str = body_str[:max_size]
|
||||
if isinstance(body, (dict, list)):
|
||||
try:
|
||||
# 尝试保持JSON格式
|
||||
return {
|
||||
"_truncated": True,
|
||||
"_original_size": len(body_str),
|
||||
"_content": truncated_str,
|
||||
}
|
||||
except:
|
||||
pass
|
||||
return truncated_str + f"\n... (truncated, original size: {len(body_str)} bytes)"
|
||||
|
||||
return body
|
||||
Reference in New Issue
Block a user