mirror of
https://github.com/insistence/RuoYi-Vue3-FastAPI.git
synced 2026-10-04 01:10:37 +00:00
* feat: 初始化插件系统 * refactor: 收口插件系统运行时重构 * perf: 优化插件系统类型提示 * fix&perf: 修复和优化插件系统 * fix: 修复gitignore规则误忽略插件文件的问题 * fix: 修复运行时插件根路径算错的问题 * fix: 加强插件发现和路由注册的防护措施 * revert: 回滚定时任务白名单 * fix: 移除未使用的应用路由注册探测 * revert: 恢复部分代码 * perf: 优化插件系统 * docs: 新增插件开发文档 * perf: 优化插件管理模块 * perf: 提升插件系统核心能力 * refactor: 重构生命周期 step runner * fix: 修复lint错误 * test: 清理测试用例 * test: 调整测试目录名称 * fix: 修复前后端目录硬编码的问题 * fix: 修复插件系统安全性缺口 * refactor: 重新设计插件生命周期 Migration 事务与回滚 * perf: 优化插件系统边界问题 * refactor: 重构当前插件系统的依赖体系设计 * perf: 优化代码 * perf: 优化代码 * fix: 修复代码合并问题 * fix: 修复bug * perf: 优化代码 * perf&fix: 优化代码和修复bug * docs: 优化文档格式 * feat: 适配Vue2版本 * docs: 更新README文档 * fix: 修复ruff lint错误 * chore: 更新后端依赖文件
430 lines
15 KiB
Python
430 lines
15 KiB
Python
import glob
|
|
import importlib
|
|
import os
|
|
import sys
|
|
from collections.abc import Callable, Sequence
|
|
from enum import Enum
|
|
from typing import Annotated, Any, Literal
|
|
|
|
from annotated_doc import Doc
|
|
from fastapi import FastAPI, params
|
|
from fastapi.datastructures import Default
|
|
from fastapi.routing import APIRoute, APIRouter
|
|
from fastapi.utils import generate_unique_id
|
|
from starlette.responses import JSONResponse, Response
|
|
from starlette.routing import BaseRoute
|
|
from starlette.types import ASGIApp, Lifespan
|
|
from typing_extensions import deprecated
|
|
|
|
|
|
class APIRouterPro(APIRouter):
|
|
"""
|
|
`APIRouterPro` class, inherited from the `APIRouter` class, it has all the functions of `APIRouter` and provides some additional parameter settings.
|
|
`APIRouter` class, used to group *path operations*, for example to structure
|
|
an app in multiple files. It would then be included in the `FastAPI` app, or
|
|
in another `APIRouter` (ultimately included in the app).
|
|
|
|
Read more about it in the
|
|
[FastAPI docs for Bigger Applications - Multiple Files](https://fastapi.tiangolo.com/tutorial/bigger-applications/).
|
|
|
|
## Example
|
|
|
|
```python
|
|
from common.router import APIRouterPro, FastAPI
|
|
|
|
app = FastAPI()
|
|
router = APIRouterPro()
|
|
|
|
|
|
@router.get('/users/', tags=['users'])
|
|
async def read_users():
|
|
return [{'username': 'Rick'}, {'username': 'Morty'}]
|
|
|
|
|
|
app.include_router(router)
|
|
```
|
|
"""
|
|
|
|
def __init__( # noqa: PLR0913
|
|
self,
|
|
*,
|
|
prefix: Annotated[str, Doc('An optional path prefix for the router.')] = '',
|
|
order_num: Annotated[int, Doc('An optional order number for the router.')] = 100,
|
|
auto_register: Annotated[bool, Doc('An optional auto register flag for the router.')] = True,
|
|
tags: Annotated[
|
|
list[str | Enum] | None,
|
|
Doc(
|
|
"""
|
|
A list of tags to be applied to all the *path operations* in this
|
|
router.
|
|
|
|
It will be added to the generated OpenAPI (e.g. visible at `/docs`).
|
|
|
|
Read more about it in the
|
|
[FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).
|
|
"""
|
|
),
|
|
] = None,
|
|
dependencies: Annotated[
|
|
Sequence[params.Depends] | None,
|
|
Doc(
|
|
"""
|
|
A list of dependencies (using `Depends()`) to be applied to all the
|
|
*path operations* in this router.
|
|
|
|
Read more about it in the
|
|
[FastAPI docs for Bigger Applications - Multiple Files](https://fastapi.tiangolo.com/tutorial/bigger-applications/#include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies).
|
|
"""
|
|
),
|
|
] = None,
|
|
default_response_class: Annotated[
|
|
type[Response],
|
|
Doc(
|
|
"""
|
|
The default response class to be used.
|
|
|
|
Read more in the
|
|
[FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#default-response-class).
|
|
"""
|
|
),
|
|
] = Default(JSONResponse),
|
|
responses: Annotated[
|
|
dict[int | str, dict[str, Any]] | None,
|
|
Doc(
|
|
"""
|
|
Additional responses to be shown in OpenAPI.
|
|
|
|
It will be added to the generated OpenAPI (e.g. visible at `/docs`).
|
|
|
|
Read more about it in the
|
|
[FastAPI docs for Additional Responses in OpenAPI](https://fastapi.tiangolo.com/advanced/additional-responses/).
|
|
|
|
And in the
|
|
[FastAPI docs for Bigger Applications](https://fastapi.tiangolo.com/tutorial/bigger-applications/#include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies).
|
|
"""
|
|
),
|
|
] = None,
|
|
callbacks: Annotated[
|
|
list[BaseRoute] | None,
|
|
Doc(
|
|
"""
|
|
OpenAPI callbacks that should apply to all *path operations* in this
|
|
router.
|
|
|
|
It will be added to the generated OpenAPI (e.g. visible at `/docs`).
|
|
|
|
Read more about it in the
|
|
[FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/).
|
|
"""
|
|
),
|
|
] = None,
|
|
routes: Annotated[
|
|
list[BaseRoute] | None,
|
|
Doc(
|
|
"""
|
|
**Note**: you probably shouldn't use this parameter, it is inherited
|
|
from Starlette and supported for compatibility.
|
|
|
|
---
|
|
|
|
A list of routes to serve incoming HTTP and WebSocket requests.
|
|
"""
|
|
),
|
|
deprecated(
|
|
"""
|
|
You normally wouldn't use this parameter with FastAPI, it is inherited
|
|
from Starlette and supported for compatibility.
|
|
|
|
In FastAPI, you normally would use the *path operation methods*,
|
|
like `router.get()`, `router.post()`, etc.
|
|
"""
|
|
),
|
|
] = None,
|
|
redirect_slashes: Annotated[
|
|
bool,
|
|
Doc(
|
|
"""
|
|
Whether to detect and redirect slashes in URLs when the client doesn't
|
|
use the same format.
|
|
"""
|
|
),
|
|
] = True,
|
|
default: Annotated[
|
|
ASGIApp | None,
|
|
Doc(
|
|
"""
|
|
Default function handler for this router. Used to handle
|
|
404 Not Found errors.
|
|
"""
|
|
),
|
|
] = None,
|
|
dependency_overrides_provider: Annotated[
|
|
Any | None,
|
|
Doc(
|
|
"""
|
|
Only used internally by FastAPI to handle dependency overrides.
|
|
|
|
You shouldn't need to use it. It normally points to the `FastAPI` app
|
|
object.
|
|
"""
|
|
),
|
|
] = None,
|
|
route_class: Annotated[
|
|
type[APIRoute],
|
|
Doc(
|
|
"""
|
|
Custom route (*path operation*) class to be used by this router.
|
|
|
|
Read more about it in the
|
|
[FastAPI docs for Custom Request and APIRoute class](https://fastapi.tiangolo.com/how-to/custom-request-and-route/#custom-apiroute-class-in-a-router).
|
|
"""
|
|
),
|
|
] = APIRoute,
|
|
on_startup: Annotated[
|
|
Sequence[Callable[[], Any]] | None,
|
|
Doc(
|
|
"""
|
|
A list of startup event handler functions.
|
|
|
|
You should instead use the `lifespan` handlers.
|
|
|
|
Read more in the [FastAPI docs for `lifespan`](https://fastapi.tiangolo.com/advanced/events/).
|
|
"""
|
|
),
|
|
] = None,
|
|
on_shutdown: Annotated[
|
|
Sequence[Callable[[], Any]] | None,
|
|
Doc(
|
|
"""
|
|
A list of shutdown event handler functions.
|
|
|
|
You should instead use the `lifespan` handlers.
|
|
|
|
Read more in the
|
|
[FastAPI docs for `lifespan`](https://fastapi.tiangolo.com/advanced/events/).
|
|
"""
|
|
),
|
|
] = None,
|
|
# the generic to Lifespan[AppType] is the type of the top level application
|
|
# which the router cannot know statically, so we use typing.Any
|
|
lifespan: Annotated[
|
|
Lifespan[Any] | None,
|
|
Doc(
|
|
"""
|
|
A `Lifespan` context manager handler. This replaces `startup` and
|
|
`shutdown` functions with a single context manager.
|
|
|
|
Read more in the
|
|
[FastAPI docs for `lifespan`](https://fastapi.tiangolo.com/advanced/events/).
|
|
"""
|
|
),
|
|
] = None,
|
|
deprecated: Annotated[
|
|
bool | None,
|
|
Doc(
|
|
"""
|
|
Mark all *path operations* in this router as deprecated.
|
|
|
|
It will be added to the generated OpenAPI (e.g. visible at `/docs`).
|
|
|
|
Read more about it in the
|
|
[FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).
|
|
"""
|
|
),
|
|
] = None,
|
|
include_in_schema: Annotated[
|
|
bool,
|
|
Doc(
|
|
"""
|
|
To include (or not) all the *path operations* in this router in the
|
|
generated OpenAPI.
|
|
|
|
This affects the generated OpenAPI (e.g. visible at `/docs`).
|
|
|
|
Read more about it in the
|
|
[FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi).
|
|
"""
|
|
),
|
|
] = True,
|
|
generate_unique_id_function: Annotated[
|
|
Callable[[APIRoute], str],
|
|
Doc(
|
|
"""
|
|
Customize the function used to generate unique IDs for the *path
|
|
operations* shown in the generated OpenAPI.
|
|
|
|
This is particularly useful when automatically generating clients or
|
|
SDKs for your API.
|
|
|
|
Read more about it in the
|
|
[FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function).
|
|
"""
|
|
),
|
|
] = Default(generate_unique_id),
|
|
) -> None:
|
|
self.order_num = order_num
|
|
self.auto_register = auto_register
|
|
super().__init__(
|
|
prefix=prefix,
|
|
tags=tags,
|
|
dependencies=dependencies,
|
|
default_response_class=default_response_class,
|
|
responses=responses,
|
|
callbacks=callbacks,
|
|
routes=routes,
|
|
redirect_slashes=redirect_slashes,
|
|
default=default,
|
|
dependency_overrides_provider=dependency_overrides_provider,
|
|
route_class=route_class,
|
|
on_startup=on_startup,
|
|
on_shutdown=on_shutdown,
|
|
lifespan=lifespan,
|
|
deprecated=deprecated,
|
|
include_in_schema=include_in_schema,
|
|
generate_unique_id_function=generate_unique_id_function,
|
|
)
|
|
|
|
|
|
class RouterRegister:
|
|
"""
|
|
路由注册器,用于自动注册所有controller目录下的路由
|
|
"""
|
|
|
|
def __init__(self, app: FastAPI) -> None:
|
|
"""
|
|
初始化路由注册器
|
|
|
|
:param app: FastAPI对象
|
|
"""
|
|
self.app = app
|
|
# 获取项目根目录
|
|
self.project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
|
|
sys.path.insert(0, self.project_root)
|
|
|
|
def _find_controller_files(self) -> list[str]:
|
|
"""
|
|
查找所有controller目录下的py文件
|
|
|
|
:return: py文件路径列表
|
|
"""
|
|
pattern = os.path.join(self.project_root, '*', 'controller', '[!_]*.py')
|
|
return sorted(glob.glob(pattern))
|
|
|
|
def _import_module_and_get_routers(self, controller_files: list[str]) -> list[tuple[str, APIRouter]]:
|
|
"""
|
|
导入模块并获取路由实例
|
|
|
|
:param controller_files: controller目录下的py文件路径列表
|
|
:return: 路由实例列表
|
|
"""
|
|
routers = []
|
|
for file_path in controller_files:
|
|
# 计算模块路径
|
|
relative_path = os.path.relpath(file_path, self.project_root)
|
|
module_name = relative_path.replace(os.sep, '.')[:-3]
|
|
|
|
# 动态导入模块
|
|
module = importlib.import_module(module_name)
|
|
# 直接遍历模块__dict__,只检查模块自身定义的属性
|
|
for attr_name, attr in module.__dict__.items():
|
|
# 对于APIRouterPro实例,只有当auto_register=True时才添加
|
|
if isinstance(attr, APIRouterPro):
|
|
if attr.auto_register:
|
|
routers.append((attr_name, attr))
|
|
# 对于APIRouter实例,直接添加
|
|
elif isinstance(attr, APIRouter):
|
|
routers.append((attr_name, attr))
|
|
return routers
|
|
|
|
def _sort_routers(self, routers: list[tuple[str, APIRouter]]) -> list[tuple[str, APIRouter]]:
|
|
"""
|
|
按规则排序路由
|
|
|
|
:param routers: 路由实例列表
|
|
:return: 排序后的路由实例列表
|
|
"""
|
|
|
|
# 按规则排序路由
|
|
def sort_key(item: tuple[str, APIRouter]) -> tuple[Literal[0], int, str] | tuple[Literal[1], str]:
|
|
attr_name, router = item
|
|
# APIRouterPro实例按order_num排序,序号越小越靠前
|
|
if isinstance(router, APIRouterPro):
|
|
return (0, router.order_num, attr_name)
|
|
# APIRouter实例按变量名首字母排序
|
|
return (1, attr_name)
|
|
|
|
return sorted(routers, key=sort_key)
|
|
|
|
def _register_routers_to_app(
|
|
self,
|
|
routers: list[tuple[str, APIRouter]],
|
|
dependencies: Sequence[params.Depends] | None = None,
|
|
) -> None:
|
|
"""
|
|
将路由注册到FastAPI应用
|
|
|
|
:param routers: 排序后的路由实例列表
|
|
:param dependencies: 注册时附加到路由上的依赖项
|
|
:return: None
|
|
"""
|
|
for _attr_name, router in routers:
|
|
self.app.include_router(router=router, dependencies=dependencies)
|
|
|
|
def register_routers(self) -> None:
|
|
"""
|
|
自动注册所有controller目录下的路由
|
|
|
|
:return: None
|
|
"""
|
|
# 查找所有controller目录下的py文件
|
|
controller_files = self._find_controller_files()
|
|
self._register_controller_files(controller_files)
|
|
|
|
def _register_controller_files(
|
|
self,
|
|
controller_files: list[str],
|
|
dependencies: Sequence[params.Depends] | None = None,
|
|
) -> None:
|
|
"""
|
|
注册指定 controller 文件中的路由。
|
|
|
|
:param controller_files: controller文件路径列表
|
|
:param dependencies: 注册时附加到路由上的依赖项
|
|
:return: None
|
|
"""
|
|
# 导入模块并获取路由实例
|
|
routers = self._import_module_and_get_routers(controller_files)
|
|
# 按规则排序路由
|
|
sorted_routers = self._sort_routers(routers)
|
|
# 注册路由到FastAPI应用
|
|
self._register_routers_to_app(sorted_routers, dependencies)
|
|
|
|
|
|
def auto_register_routers(app: FastAPI) -> None:
|
|
"""
|
|
自动注册所有controller目录下的路由
|
|
|
|
:param app: FastAPI对象
|
|
:return: None
|
|
"""
|
|
# 使用路由注册器进行注册
|
|
router_register = RouterRegister(app)
|
|
router_register.register_routers()
|
|
|
|
|
|
def auto_register_controller_files(
|
|
app: FastAPI,
|
|
controller_files: Sequence[str],
|
|
dependencies: Sequence[params.Depends] | None = None,
|
|
) -> None:
|
|
"""
|
|
自动注册指定 controller 文件中的路由。
|
|
|
|
:param app: FastAPI对象
|
|
:param controller_files: controller文件路径列表
|
|
:param dependencies: 注册时附加到路由上的依赖项
|
|
:return: None
|
|
"""
|
|
router_register = RouterRegister(app)
|
|
router_register._register_controller_files(list(controller_files), dependencies)
|