mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-09-21 04:46:26 +00:00
refactor: 整合仪表盘功能到监控模块,清理冗余代码
- 移除原监控仪表盘独立模块,将相关功能合并到在线监控模块 - 重构租户配置字段名,统一使用logo_url和name替代tenant_logo/tenant_name - 优化搜索工具函数,移除重复导入 - 调整参数配置模型字段长度限制,移除config_value的max_length约束 - 清理冗余的常量定义和导入语句 - 修复批量状态设置接口的redis依赖注入 - 增强OAuth登录安全性,添加租户默认归属和state一次性消费 - 优化资源目录缓存逻辑,减少重复计算 - 新增API Token模块基础框架 - 完善用户token版本管理,支持主动失效JWT - 调整AI模型配置缓存过期时间 - 修复菜单类型字段索引,提升查询性能 - 简化前端刷新token调用逻辑 - 新增滑块验证完成接口和忘记密码验证码校验 - 调整系统配置默认值,添加操作日志保留天数和接口白名单配置 - 限制Mock支付回调仅在开发环境可用 - 重构websocket认证方式,支持更安全的subprotocol传参
This commit is contained in:
@@ -79,11 +79,12 @@ async def delete_param_controller(
|
||||
|
||||
@ParamsRouter.patch("/status/batch", summary="批量设置参数状态", response_model=ResponseSchema)
|
||||
async def batch_set_status_controller(
|
||||
redis: Annotated[Redis, Depends(redis_getter)],
|
||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_system:param:patch"]))],
|
||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||
data: Annotated[BatchSetAvailable, Body(description="状态设置")],
|
||||
) -> JSONResponse:
|
||||
await ParamsService(auth, db).batch_set_status(ids=data.ids, status=data.status)
|
||||
await ParamsService(auth, db).batch_set_status(redis=redis, ids=data.ids, status=data.status)
|
||||
return SuccessResponse(msg="批量设置参数状态成功")
|
||||
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ class ParamsModel(ModelMixin, TenantMixin, UserMixin):
|
||||
|
||||
config_name: Mapped[str] = mapped_column(String(64), nullable=False, comment="参数名称")
|
||||
config_key: Mapped[str] = mapped_column(String(500), nullable=False, comment="参数键名")
|
||||
config_value: Mapped[str | None] = mapped_column(String(500), comment="参数键值")
|
||||
config_value: Mapped[str | None] = mapped_column(Text, comment="参数键值")
|
||||
config_type: Mapped[bool] = mapped_column(Boolean, default=False, nullable=True, comment="系统内置(True:是 False:否)", index=True)
|
||||
status: Mapped[int] = mapped_column(Integer, default=0, nullable=False, comment="状态(0:启动 1:停用)", index=True)
|
||||
description: Mapped[str | None] = mapped_column(Text, default=None, nullable=True, comment="备注")
|
||||
|
||||
@@ -12,7 +12,7 @@ class ParamsCreateSchema(BaseModel):
|
||||
|
||||
config_name: str = Field(..., min_length=1, max_length=64, description="参数名称")
|
||||
config_key: str = Field(..., min_length=1, max_length=500, description="参数键名(小写字母开头,仅允许字母数字_.-)")
|
||||
config_value: str | None = Field(default=None, max_length=500, description="参数键值")
|
||||
config_value: str | None = Field(default=None, description="参数键值")
|
||||
config_type: bool = Field(default=False, description="是否系统内置(True:是 False:否)")
|
||||
status: int = Field(default=0, ge=0, le=1, description="状态(0:正常 1:停用)")
|
||||
description: str | None = Field(default=None, max_length=500, description="参数描述")
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import json
|
||||
import time
|
||||
from collections.abc import Sequence
|
||||
|
||||
from redis.asyncio.client import Redis
|
||||
@@ -10,7 +9,9 @@ from app.core.base_schema import AuthSchema, PageResultSchema
|
||||
from app.core.database import async_db_session
|
||||
from app.core.exceptions import CustomException
|
||||
from app.core.logger import logger
|
||||
from app.core.middlewares import invalidate_middleware_config_cache
|
||||
from app.core.redis_crud import RedisCURD
|
||||
from app.utils.common_util import search_to_dict
|
||||
from app.utils.excel_util import ExcelUtil
|
||||
|
||||
from .crud import ParamsCRUD
|
||||
@@ -21,91 +22,6 @@ from .schema import (
|
||||
ParamsUpdateSchema,
|
||||
)
|
||||
|
||||
# 中间件 / 调度器高频读取的 sys_param 配置键集合。
|
||||
MIDDLEWARE_CONFIG_KEYS: tuple[str, ...] = (
|
||||
"demo_enable",
|
||||
"ip_white_list",
|
||||
"white_api_list_path",
|
||||
"ip_black_list",
|
||||
"operation_log_retention_days",
|
||||
)
|
||||
|
||||
# 内存缓存(按租户隔离)
|
||||
_MID_CONFIG_TTL: float = 60.0
|
||||
_mid_config_cache: dict[int, tuple[float, dict]] = {}
|
||||
|
||||
|
||||
def _parse_bool(value: object) -> bool:
|
||||
"""兼容字符串 / 布尔值 / JSON 布尔值的开关字段解析。
|
||||
|
||||
支持的字符串真值:true / 1 / yes / on
|
||||
支持的字符串假值:false / 0 / no / off(以及空字符串、None)
|
||||
"""
|
||||
if isinstance(value, bool):
|
||||
return value
|
||||
if isinstance(value, str):
|
||||
normalized = value.strip().lower()
|
||||
if normalized in {"true", "1", "yes", "on"}:
|
||||
return True
|
||||
if normalized in {"false", "0", "no", "off", ""}:
|
||||
return False
|
||||
try:
|
||||
return bool(json.loads(normalized))
|
||||
except (json.JSONDecodeError, ValueError):
|
||||
return False
|
||||
if value is None:
|
||||
return False
|
||||
return bool(value)
|
||||
|
||||
|
||||
def _parse_json_list(value: object) -> list:
|
||||
"""兼容 JSON 字符串 / 列表 / 空值的数组字段解析。"""
|
||||
if isinstance(value, list):
|
||||
return value
|
||||
if isinstance(value, str):
|
||||
try:
|
||||
parsed = json.loads(value)
|
||||
return parsed if isinstance(parsed, list) else []
|
||||
except (json.JSONDecodeError, ValueError):
|
||||
return []
|
||||
return []
|
||||
|
||||
|
||||
def _invalidate_mid_config_cache(tenant_id: int | None = None) -> None:
|
||||
"""失效中间件内存缓存。tenant_id 为 None 时清空所有租户。"""
|
||||
if tenant_id is None:
|
||||
_mid_config_cache.clear()
|
||||
else:
|
||||
_mid_config_cache.pop(tenant_id, None)
|
||||
|
||||
|
||||
def _default_for(key: str) -> object:
|
||||
"""缺省值表:新增 MIDDLEWARE_CONFIG_KEYS 时只需在这里登记默认值。"""
|
||||
if key in {"ip_white_list", "ip_black_list", "white_api_list_path"}:
|
||||
return []
|
||||
if key == "demo_enable":
|
||||
return False
|
||||
if key == "operation_log_retention_days":
|
||||
return 90
|
||||
return None
|
||||
|
||||
|
||||
def _parse_value(key: str, value: object) -> object:
|
||||
"""按 key 的语义解析 config_value。"""
|
||||
if key == "demo_enable":
|
||||
return _parse_bool(value)
|
||||
if key in {"ip_white_list", "ip_black_list", "white_api_list_path"}:
|
||||
return _parse_json_list(value)
|
||||
if key == "operation_log_retention_days":
|
||||
if value is None:
|
||||
return 90
|
||||
try:
|
||||
return int(str(value))
|
||||
except (TypeError, ValueError):
|
||||
return 90
|
||||
return value
|
||||
|
||||
|
||||
class ParamsService:
|
||||
"""参数管理服务
|
||||
|
||||
@@ -158,7 +74,7 @@ class ParamsService:
|
||||
返回:
|
||||
- list[ParamsOutSchema]: 参数响应模型列表
|
||||
"""
|
||||
obj_list = await ParamsCRUD(self.auth, self.db).get_list(search=vars(search) if search else None, order_by=order_by)
|
||||
obj_list = await ParamsCRUD(self.auth, self.db).get_list(search=search_to_dict(search), order_by=order_by)
|
||||
return [ParamsOutSchema.model_validate(obj) for obj in obj_list]
|
||||
|
||||
async def page(
|
||||
@@ -184,7 +100,7 @@ class ParamsService:
|
||||
offset=offset,
|
||||
limit=page_size,
|
||||
order_by=order_by or [{"id": "asc"}],
|
||||
search=vars(search) if search else None,
|
||||
search=search_to_dict(search),
|
||||
out_schema=ParamsOutSchema,
|
||||
)
|
||||
|
||||
@@ -268,7 +184,7 @@ class ParamsService:
|
||||
raise CustomException(msg="同步配置到缓存失败") from e
|
||||
|
||||
# 失效中间件内存缓存,让下次请求重新加载
|
||||
_invalidate_mid_config_cache(user.tenant_id)
|
||||
invalidate_middleware_config_cache(user.tenant_id)
|
||||
|
||||
return out
|
||||
|
||||
@@ -282,7 +198,7 @@ class ParamsService:
|
||||
返回:
|
||||
- None
|
||||
"""
|
||||
if len(ids) < 1:
|
||||
if not ids:
|
||||
raise CustomException(msg="删除失败,删除对象不能为空")
|
||||
# 批量校验参数存在性
|
||||
objs = await ParamsCRUD(self.auth, self.db).get_list(search={"id": ("in", ids)})
|
||||
@@ -309,12 +225,13 @@ class ParamsService:
|
||||
raise CustomException(msg="同步删除缓存失败") from e
|
||||
|
||||
# 失效中间件内存缓存
|
||||
_invalidate_mid_config_cache(user.tenant_id)
|
||||
invalidate_middleware_config_cache(user.tenant_id)
|
||||
|
||||
async def batch_set_status(self, ids: list[int], status: int) -> None:
|
||||
async def batch_set_status(self, redis: Redis, ids: list[int], status: int) -> None:
|
||||
"""批量设置系统参数状态
|
||||
|
||||
参数:
|
||||
- redis: Redis 客户端(用于同步缓存)
|
||||
- ids (list[int]): 系统参数ID列表
|
||||
- status (int): 状态值
|
||||
|
||||
@@ -324,7 +241,17 @@ class ParamsService:
|
||||
if not ids:
|
||||
raise CustomException(msg="请选择要操作的数据")
|
||||
|
||||
# 先查参数列表获取 config_key 和 tenant_id
|
||||
params = await ParamsCRUD(self.auth, self.db).get_list(search={"id": ("in", list(ids))})
|
||||
await ParamsCRUD(self.auth, self.db).set(ids=ids, status=status)
|
||||
# 同步删除对应 Redis 缓存
|
||||
for param in params:
|
||||
redis_key = f"{RedisInitKeyConfig.SYSTEM_CONFIG.key}:{param.tenant_id}:{param.config_key}"
|
||||
try:
|
||||
await RedisCURD(redis).delete(redis_key)
|
||||
except Exception as e:
|
||||
logger.error(f"同步删除系统配置缓存失败: {e}")
|
||||
invalidate_middleware_config_cache(None)
|
||||
|
||||
@staticmethod
|
||||
def export(data_list: list[dict]) -> bytes:
|
||||
@@ -405,56 +332,3 @@ class ParamsService:
|
||||
if config_obj:
|
||||
configs = await ParamsService._sync_configs_to_redis(redis, config_obj)
|
||||
return configs
|
||||
|
||||
@staticmethod
|
||||
async def get_system_config_for_middleware(redis: Redis, tenant_id: int = 1) -> dict:
|
||||
"""获取中间件 / 调度器所需的系统配置(带 60 秒内存缓存,按租户隔离)。
|
||||
|
||||
参数:
|
||||
- redis (Redis): Redis 客户端实例
|
||||
- tenant_id (int): 租户 ID
|
||||
|
||||
返回:
|
||||
- dict: 包含 MIDDLEWARE_CONFIG_KEYS 中所有 key 的解析后值。
|
||||
"""
|
||||
cached = _mid_config_cache.get(tenant_id)
|
||||
if cached and time.monotonic() - cached[0] < _MID_CONFIG_TTL:
|
||||
return cached[1]
|
||||
|
||||
config = await ParamsService._fetch_system_config_for_middleware(redis, tenant_id)
|
||||
_mid_config_cache[tenant_id] = (time.monotonic(), config)
|
||||
return config
|
||||
|
||||
@staticmethod
|
||||
async def _fetch_system_config_for_middleware(redis: Redis, tenant_id: int = 1) -> dict:
|
||||
"""从 Redis 批量拉取并解析 MIDDLEWARE_CONFIG_KEYS 中的配置。
|
||||
|
||||
停用(status=1)的配置视为未配置,使用默认值。
|
||||
"""
|
||||
config_keys = [f"{RedisInitKeyConfig.SYSTEM_CONFIG.key}:{tenant_id}:{key}" for key in MIDDLEWARE_CONFIG_KEYS]
|
||||
config_values = await RedisCURD(redis).mget(config_keys)
|
||||
|
||||
result: dict[str, object] = {}
|
||||
for key, raw in zip(MIDDLEWARE_CONFIG_KEYS, config_values, strict=True):
|
||||
if not raw:
|
||||
result[key] = _default_for(key)
|
||||
continue
|
||||
try:
|
||||
payload = json.loads(raw)
|
||||
except json.JSONDecodeError:
|
||||
logger.error("解析系统配置 %s 失败", key)
|
||||
result[key] = _default_for(key)
|
||||
continue
|
||||
|
||||
if not isinstance(payload, dict):
|
||||
result[key] = _default_for(key)
|
||||
continue
|
||||
|
||||
# 停用的配置视为未启用,使用默认值
|
||||
if payload.get("status", 0) != 0:
|
||||
result[key] = _default_for(key)
|
||||
continue
|
||||
|
||||
result[key] = _parse_value(key, payload.get("config_value"))
|
||||
|
||||
return result
|
||||
|
||||
Reference in New Issue
Block a user