Build lightweight AI agent admin
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
SystemConfig API - 系统配置管理接口
|
||||
提供 SSO 登录和消息通知等系统配置的前端管理接口
|
||||
"""
|
||||
from typing import Dict, Any
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.database import get_db
|
||||
from app.base_schema import ResponseModel
|
||||
from app.config_manager import config_manager, GROUP_ENV_MAPPING
|
||||
from core.system_config.schema import GroupConfigUpdate
|
||||
from core.system_config.service import SystemConfigService
|
||||
|
||||
router = APIRouter(prefix="/system-config", tags=["系统配置管理"])
|
||||
|
||||
|
||||
@router.get("/groups", summary="获取所有配置分组定义")
|
||||
async def get_groups():
|
||||
"""获取所有配置分组及其字段定义"""
|
||||
return config_manager.get_group_list()
|
||||
|
||||
|
||||
@router.get("/all", summary="获取所有分组配置")
|
||||
async def get_all_configs():
|
||||
"""获取所有分组配置(敏感字段脱敏)"""
|
||||
return await SystemConfigService.get_all_groups_config(mask_secrets=True)
|
||||
|
||||
|
||||
@router.get("/group/{group}", summary="获取分组配置")
|
||||
async def get_group_config(group: str):
|
||||
"""
|
||||
获取指定分组的配置(敏感字段脱敏)
|
||||
|
||||
三级获取优先级: Redis → 数据库 → env 配置文件
|
||||
"""
|
||||
if group not in GROUP_ENV_MAPPING:
|
||||
raise HTTPException(status_code=400, detail=f"不支持的配置分组: {group}")
|
||||
|
||||
return await SystemConfigService.get_group_config(group, mask_secrets=True)
|
||||
|
||||
|
||||
@router.put("/group/{group}", response_model=ResponseModel, summary="更新分组配置")
|
||||
async def update_group_config(
|
||||
group: str,
|
||||
data: GroupConfigUpdate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""
|
||||
批量更新指定分组的配置
|
||||
|
||||
- 敏感字段如果传入脱敏值(含 ***),则跳过不更新
|
||||
- 更新后自动清除 Redis 缓存
|
||||
"""
|
||||
if group not in GROUP_ENV_MAPPING:
|
||||
raise HTTPException(status_code=400, detail=f"不支持的配置分组: {group}")
|
||||
|
||||
updated = await SystemConfigService.update_group_config(db, group, data.configs)
|
||||
return ResponseModel(message="更新成功", data=updated)
|
||||
|
||||
|
||||
@router.delete("/group/{group}", response_model=ResponseModel, summary="删除分组配置")
|
||||
async def delete_group_config(
|
||||
group: str,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""删除指定分组的数据库配置(恢复为 env 配置文件默认值)"""
|
||||
if group not in GROUP_ENV_MAPPING:
|
||||
raise HTTPException(status_code=400, detail=f"不支持的配置分组: {group}")
|
||||
|
||||
success = await SystemConfigService.delete_group_config(db, group)
|
||||
return ResponseModel(message="已恢复为默认配置" if success else "该分组无自定义配置")
|
||||
|
||||
|
||||
@router.post("/cache/warmup", response_model=ResponseModel, summary="预热配置缓存")
|
||||
async def warmup_cache():
|
||||
"""手动预热所有配置到 Redis 缓存"""
|
||||
await config_manager.warmup()
|
||||
return ResponseModel(message="缓存预热完成")
|
||||
|
||||
|
||||
@router.delete("/cache", response_model=ResponseModel, summary="清除配置缓存")
|
||||
async def clear_cache():
|
||||
"""清除所有配置的 Redis 缓存"""
|
||||
await config_manager.invalidate_all()
|
||||
return ResponseModel(message="缓存已清除")
|
||||
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
SystemConfig Model - 系统配置模型
|
||||
用于存储 SSO 登录和消息通知等可在前端管理的系统配置
|
||||
"""
|
||||
from sqlalchemy import Column, String, Text, Boolean
|
||||
|
||||
from app.base_model import BaseModel
|
||||
|
||||
|
||||
class SystemConfig(BaseModel):
|
||||
"""
|
||||
系统配置表
|
||||
|
||||
字段说明:
|
||||
- config_group: 配置分组(如 oauth_gitee, notify_email)
|
||||
- config_key: 配置键(同一分组内唯一)
|
||||
- config_value: 配置值(明文或加密存储)
|
||||
- description: 配置描述
|
||||
- is_secret: 是否为敏感字段(API 返回时脱敏)
|
||||
- status: 是否启用
|
||||
"""
|
||||
__tablename__ = "core_system_config"
|
||||
|
||||
config_group = Column(String(50), nullable=False, index=True, comment="配置分组")
|
||||
config_key = Column(String(100), nullable=False, index=True, comment="配置键")
|
||||
config_value = Column(Text, nullable=True, comment="配置值")
|
||||
description = Column(String(200), nullable=True, comment="配置描述")
|
||||
is_secret = Column(Boolean, default=False, comment="是否敏感字段")
|
||||
status = Column(Boolean, default=True, index=True, comment="是否启用")
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.config_group}.{self.config_key}"
|
||||
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
SystemConfig Schema - 系统配置 Pydantic Schema
|
||||
"""
|
||||
from datetime import datetime
|
||||
from typing import Optional, Dict, Any
|
||||
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
|
||||
|
||||
class SystemConfigBase(BaseModel):
|
||||
"""基础 Schema"""
|
||||
config_group: str
|
||||
config_key: str
|
||||
config_value: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
is_secret: bool = False
|
||||
status: bool = True
|
||||
|
||||
|
||||
class SystemConfigCreate(SystemConfigBase):
|
||||
"""创建 Schema"""
|
||||
pass
|
||||
|
||||
|
||||
class SystemConfigUpdate(BaseModel):
|
||||
"""更新 Schema"""
|
||||
config_value: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
is_secret: Optional[bool] = None
|
||||
status: Optional[bool] = None
|
||||
|
||||
|
||||
class SystemConfigResponse(SystemConfigBase):
|
||||
"""响应 Schema"""
|
||||
id: str
|
||||
sort: int = 0
|
||||
is_deleted: bool = False
|
||||
sys_create_datetime: Optional[datetime] = None
|
||||
sys_update_datetime: Optional[datetime] = None
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class GroupConfigUpdate(BaseModel):
|
||||
"""按分组批量更新配置"""
|
||||
configs: Dict[str, Optional[str]]
|
||||
|
||||
|
||||
class GroupConfigResponse(BaseModel):
|
||||
"""按分组返回配置(敏感字段脱敏)"""
|
||||
group: str
|
||||
configs: Dict[str, Any]
|
||||
@@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
SystemConfig Service - 系统配置服务层
|
||||
"""
|
||||
from typing import Dict, Any, List, Optional
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.base_service import BaseService
|
||||
from app.config_manager import config_manager, GROUP_ENV_MAPPING, SECRET_KEYS
|
||||
from core.system_config.model import SystemConfig
|
||||
from core.system_config.schema import SystemConfigCreate, SystemConfigUpdate
|
||||
|
||||
|
||||
class SystemConfigService(BaseService[SystemConfig, SystemConfigCreate, SystemConfigUpdate]):
|
||||
"""系统配置服务层"""
|
||||
|
||||
model = SystemConfig
|
||||
|
||||
@classmethod
|
||||
async def get_group_config(cls, group: str, mask_secrets: bool = True) -> Dict[str, Any]:
|
||||
"""
|
||||
获取分组配置(通过 config_manager 三级获取)
|
||||
|
||||
:param group: 配置分组
|
||||
:param mask_secrets: 是否对敏感字段脱敏
|
||||
:return: {key: value} 字典
|
||||
"""
|
||||
configs = await config_manager.get_group(group)
|
||||
|
||||
if mask_secrets:
|
||||
return {
|
||||
k: config_manager.mask_value(v) if config_manager.is_secret_key(k) and v else v
|
||||
for k, v in configs.items()
|
||||
}
|
||||
return configs
|
||||
|
||||
@classmethod
|
||||
async def update_group_config(
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
group: str,
|
||||
configs: Dict[str, Optional[str]],
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
批量更新分组配置
|
||||
|
||||
:param db: 数据库会话
|
||||
:param group: 配置分组
|
||||
:param configs: {key: value} 字典
|
||||
:return: 更新后的配置(脱敏)
|
||||
"""
|
||||
for key, value in configs.items():
|
||||
# 如果敏感字段传入的是脱敏值(含 ***),跳过不更新
|
||||
if config_manager.is_secret_key(key) and value and "***" in value:
|
||||
continue
|
||||
|
||||
result = await db.execute(
|
||||
select(SystemConfig).where(
|
||||
SystemConfig.config_group == group,
|
||||
SystemConfig.config_key == key,
|
||||
SystemConfig.is_deleted == False, # noqa: E712
|
||||
)
|
||||
)
|
||||
config = result.scalar_one_or_none()
|
||||
|
||||
if config:
|
||||
config.config_value = value
|
||||
else:
|
||||
config = SystemConfig(
|
||||
config_group=group,
|
||||
config_key=key,
|
||||
config_value=value,
|
||||
is_secret=key in SECRET_KEYS,
|
||||
status=True,
|
||||
)
|
||||
db.add(config)
|
||||
|
||||
await db.commit()
|
||||
|
||||
# 清除该分组的 Redis 缓存
|
||||
await config_manager.invalidate_group(group)
|
||||
|
||||
# 返回更新后的配置(脱敏)
|
||||
return await cls.get_group_config(group, mask_secrets=True)
|
||||
|
||||
@classmethod
|
||||
async def get_all_groups_config(cls, mask_secrets: bool = True) -> Dict[str, Dict[str, Any]]:
|
||||
"""获取所有分组配置"""
|
||||
result = {}
|
||||
for group in GROUP_ENV_MAPPING:
|
||||
result[group] = await cls.get_group_config(group, mask_secrets=mask_secrets)
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
async def get_group_db_records(cls, db: AsyncSession, group: str) -> List[SystemConfig]:
|
||||
"""获取分组在数据库中的记录"""
|
||||
result = await db.execute(
|
||||
select(SystemConfig).where(
|
||||
SystemConfig.config_group == group,
|
||||
SystemConfig.is_deleted == False, # noqa: E712
|
||||
).order_by(SystemConfig.sort)
|
||||
)
|
||||
return list(result.scalars().all())
|
||||
|
||||
@classmethod
|
||||
async def delete_group_config(cls, db: AsyncSession, group: str) -> bool:
|
||||
"""删除分组配置(软删除)"""
|
||||
result = await db.execute(
|
||||
select(SystemConfig).where(
|
||||
SystemConfig.config_group == group,
|
||||
SystemConfig.is_deleted == False, # noqa: E712
|
||||
)
|
||||
)
|
||||
configs = list(result.scalars().all())
|
||||
for config in configs:
|
||||
config.is_deleted = True
|
||||
await db.commit()
|
||||
|
||||
# 清除缓存
|
||||
await config_manager.invalidate_group(group)
|
||||
return len(configs) > 0
|
||||
Reference in New Issue
Block a user