mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-09-21 12:52:26 +00:00
refactor: 项目代码整理与依赖优化
主要变更: 1. 替换默认部门名称为系统部门 2. 简化SQLAlchemy异步导入语句 3. 移除request_context.py与相关依赖,重构请求上下文逻辑 4. 替换旧的日志链路ID实现,将代码整合到logger.py 5. 简化WebSocket限流逻辑,移除冗余import 6. 删除多个模块的测试文件:common/ai/example/generator/monitor/task 7. 移除cron_util.py,改用croniter库实现Cron表达式校验 8. 优化系统参数Redis缓存键格式,移除多余的:1:前缀 9. 简化初始化脚本逻辑,移除日期自动转换逻辑 10. 简化动态路由发现代码,移除热重载相关逻辑 11. 简化认证逻辑,移除request.state.ctx相关操作 12. 修复定时任务调度器调用方式,移除await 13. 新增修改定时任务接口 14. 新增croniter依赖并更新相关配置 15. 优化数据库异常日志格式化方式 16. 简化base_schema.py空行格式 17. 移除initialize.py中的冗余依赖导入
This commit is contained in:
@@ -1,83 +1,35 @@
|
||||
"""简化的动态路由发现与注册。
|
||||
|
||||
目录与命名规范(不满足则无法注册或导入失败):
|
||||
- 插件必须放在 ``app/plugin`` 下,且**顶级目录名**必须以 ``module_`` 开头,例如
|
||||
``module_example``、``module_yourfeature``(扫描模式:``module_*/**/controller.py``)。
|
||||
- 控制器文件名必须为 ``controller.py``(大小写敏感,Linux 上 ``Controller.py`` 无效)。
|
||||
- 从 ``module_xxx`` 到 ``controller.py`` 的**每一级目录名**须为合法 Python 标识符
|
||||
(仅字母数字下划线、不以数字开头;不要使用中划线、空格、中文目录名等)。
|
||||
- 每一级目录应可作为包导入:通常需有 ``__init__.py``(或符合 namespace package 规则)。
|
||||
- 在 ``controller.py`` 的**模块顶层**定义 ``APIRouter`` 实例并赋值给变量
|
||||
(如 ``DemoRouter = APIRouter(...)``);定义在函数内部的 router **不会被**扫描到。
|
||||
目录与命名规范:
|
||||
- 插件放在 ``app/plugin`` 下,顶级目录名以 ``module_`` 开头(如 ``module_example``)。
|
||||
- 控制器文件必须为 ``controller.py``。
|
||||
- 从 ``module_xxx`` 到 ``controller.py`` 的每级目录名须为合法 Python 标识符。
|
||||
- 每级目录应有 ``__init__.py``(或符合 namespace package 规则)。
|
||||
- 在 ``controller.py`` 模块顶层定义 ``APIRouter`` 实例并赋值给变量。
|
||||
|
||||
路由前缀:顶级目录 ``module_xxx`` 映射为容器前缀 ``/xxx``(去掉前缀 ``module_`` 共 7 个字符)。
|
||||
|
||||
常见「路由没注册」原因:
|
||||
- 目录不叫 ``module_*``,或 ``controller.py`` 不在该树下的任意子路径中。
|
||||
- 包无法导入:缺 ``__init__.py``、目录名非法、拼写不一致。
|
||||
- ``controller.py`` 无语法错误但模块内没有任何顶层 ``APIRouter`` 变量。
|
||||
路由前缀:``module_xxx`` 映射为 ``/xxx``。
|
||||
"""
|
||||
|
||||
# 标准库导入
|
||||
import importlib
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# 第三方库导入
|
||||
from fastapi import APIRouter
|
||||
from fastapi import FastAPI as _FastAPI
|
||||
from fastapi import APIRouter, FastAPI
|
||||
|
||||
# 内部库导入
|
||||
from app.core.logger import logger
|
||||
|
||||
|
||||
class DynamicRouterRegistry:
|
||||
"""动态路由注册器,管理插件路由的扫描、注册与热重载。
|
||||
|
||||
用法::
|
||||
|
||||
app.include_router(dynamic_router.init_app(app))
|
||||
"""
|
||||
"""动态路由注册器,管理插件路由的扫描与注册。"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._app: _FastAPI | None = None
|
||||
self._cache: APIRouter | None = None
|
||||
self._registered_prefixes: set[str] = set()
|
||||
|
||||
# ── 公开 API ─────────────────────────────────────
|
||||
|
||||
def init_app(self, app: _FastAPI) -> "DynamicRouterRegistry":
|
||||
"""构建动态路由、注册到 app、保存引用——返回 self 支持链式调用。"""
|
||||
self._app = app
|
||||
def init_app(self, app: FastAPI) -> "DynamicRouterRegistry":
|
||||
"""构建动态路由、注册到 app——返回 self 支持链式调用。"""
|
||||
router = self._build()
|
||||
app.include_router(router)
|
||||
return self
|
||||
|
||||
def reload(self) -> APIRouter:
|
||||
"""清除缓存并重新扫描,同时更新运行中的 app 路由。"""
|
||||
if not self._app:
|
||||
logger.warning("⚠️ app 未初始化(未调用 init_app),无法更新运行中的路由")
|
||||
return self._build()
|
||||
|
||||
# ── 1. 从运行中的 app 移除旧的插件路由 ──
|
||||
if self._registered_prefixes:
|
||||
before = len(self._app.routes)
|
||||
self._app.router.routes = [r for r in self._app.routes if not any(getattr(r, "path", "").startswith(p) for p in self._registered_prefixes)]
|
||||
removed = before - len(self._app.routes)
|
||||
logger.info(f"🧹 已移除 {removed} 条旧插件路由(前缀: {self._registered_prefixes})")
|
||||
|
||||
# ── 2. 清除插件模块缓存 ──
|
||||
self._purge_modules()
|
||||
|
||||
# ── 3. 清除内部缓存,重新构建并注册 ──
|
||||
self._cache = None
|
||||
router = self._build()
|
||||
self._app.include_router(router)
|
||||
logger.info("✅ 新插件路由已挂载到运行中的 app")
|
||||
return router
|
||||
|
||||
# ── 内部方法 ─────────────────────────────────────
|
||||
|
||||
def _build(self) -> APIRouter:
|
||||
"""扫描并构建动态路由(带缓存)。"""
|
||||
if self._cache is not None:
|
||||
@@ -103,7 +55,7 @@ class DynamicRouterRegistry:
|
||||
|
||||
suffix = top_module[7:] if top_module.startswith("module_") else ""
|
||||
if not suffix:
|
||||
logger.error(f"❌ 跳过异常顶级目录名(须为 module_ 前缀且后面还有名称): {top_module!r},文件: {file}")
|
||||
logger.error(f"❌ 跳过异常顶级目录名(须为 module_ 前缀): {top_module!r},文件: {file}")
|
||||
continue
|
||||
prefix = f"/{suffix}"
|
||||
|
||||
@@ -128,10 +80,7 @@ class DynamicRouterRegistry:
|
||||
if registered_here == 0:
|
||||
logger.warning(
|
||||
f"⚠️ 模块已加载但未注册任何路由: {module_path}\n"
|
||||
f" 原因:该文件中未找到**顶层** APIRouter 实例。\n"
|
||||
f" 规范:在 controller.py 模块顶层定义,例如 "
|
||||
f"`XxxRouter = APIRouter(route_class=..., prefix=..., tags=[...])`,"
|
||||
f"不要仅在函数内创建 APIRouter。",
|
||||
f" 文件中未找到顶层 APIRouter 实例",
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
@@ -142,10 +91,9 @@ class DynamicRouterRegistry:
|
||||
route_count = len(container_router.routes)
|
||||
root_router.include_router(container_router)
|
||||
if route_count == 0:
|
||||
logger.warning(f"⚠️ 容器前缀 {prefix} 下未挂载任何子路由(可能该 module 下所有 controller 均未导出 APIRouter)")
|
||||
logger.warning(f"⚠️ 容器前缀 {prefix} 下未挂载任何子路由")
|
||||
logger.info(f"✅ 注册容器: {prefix} (子路由数: {route_count})")
|
||||
|
||||
self._registered_prefixes = set(container_routers.keys())
|
||||
logger.info(f"✅ 动态路由发现完成: 共 {len(container_routers)} 个容器前缀")
|
||||
self._cache = root_router
|
||||
return root_router
|
||||
@@ -154,46 +102,29 @@ class DynamicRouterRegistry:
|
||||
logger.error(f"❌ 动态路由发现整体失败: {e!s}")
|
||||
return root_router
|
||||
|
||||
@staticmethod
|
||||
def _purge_modules() -> None:
|
||||
"""从 sys.modules 中清除所有 app.plugin.module_* 模块。"""
|
||||
prefix = "app.plugin.module_"
|
||||
purged: list[str] = []
|
||||
for mod_name in list(sys.modules):
|
||||
if mod_name.startswith(prefix):
|
||||
del sys.modules[mod_name]
|
||||
purged.append(mod_name)
|
||||
|
||||
if purged:
|
||||
logger.info(f"🧹 已清除 {len(purged)} 个插件模块缓存,将重新导入")
|
||||
else:
|
||||
logger.info("🧹 未发现需要清除的插件模块缓存")
|
||||
|
||||
|
||||
# ── 模块级单例 ─────────────────────────────────
|
||||
# 模块级单例
|
||||
dynamic_router = DynamicRouterRegistry()
|
||||
|
||||
|
||||
def _import_failure_hint(exc: BaseException) -> str:
|
||||
"""根据异常类型给出简短排查提示(中文日志)。"""
|
||||
"""根据异常类型给出简短排查提示。"""
|
||||
if isinstance(exc, ModuleNotFoundError):
|
||||
missing = getattr(exc, "name", None) or str(exc)
|
||||
return (
|
||||
f"无法解析模块(ModuleNotFoundError: {missing})。"
|
||||
"常见原因:① 从 app.plugin 到 controller 的某级目录缺少 __init__.py;"
|
||||
"② 目录名不是合法 Python 标识符(禁用连字符、空格、中文等);"
|
||||
"③ 磁盘路径与 import 路径不一致(大小写、子目录名拼写)。"
|
||||
"常见原因:"
|
||||
"① 从 app.plugin 到 controller 的某级目录缺少 __init__.py;"
|
||||
"② 目录名不是合法 Python 标识符;"
|
||||
"③ 磁盘路径与 import 路径不一致。"
|
||||
)
|
||||
if isinstance(exc, ImportError):
|
||||
return "导入失败(ImportError)。常见原因:controller 或其依赖模块循环导入、第三方依赖未安装、或相对导入路径错误。"
|
||||
return "导入失败(ImportError),常见原因:循环导入、依赖未安装、或相对导入路径错误。"
|
||||
if isinstance(exc, SyntaxError):
|
||||
return f"controller.py 存在语法错误:{exc.msg}(约第 {exc.lineno} 行)。"
|
||||
if isinstance(exc, PermissionError):
|
||||
return (
|
||||
"权限错误(PermissionError)。多见于受限环境(沙箱、部分 CI):import 链上某模块初始化时调用了被禁止的系统能力(如进程池),与目录命名无关。在完整操作系统下重试;若仍失败再结合堆栈排查。"
|
||||
"权限错误(PermissionError)。多见于受限环境:import 链上某模块初始化时调用了被禁止的系统能力。"
|
||||
"在完整操作系统下重试;若仍失败再结合堆栈排查。"
|
||||
)
|
||||
return f"未分类异常({type(exc).__name__})。请查看下方堆栈;若与命名/包结构无关,可能是 controller 顶层 import 的依赖在加载时失败。"
|
||||
|
||||
|
||||
# 重新导出函数供外部使用
|
||||
__all__ = ["dynamic_router"]
|
||||
return f"未分类异常({type(exc).__name__})。请查看下方堆栈排查。"
|
||||
|
||||
Reference in New Issue
Block a user