mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-10-10 03:24:12 +00:00
refactor(test, config, router): 整理代码结构与配置,优化导入顺序
1. 调整多个测试文件中TestClient的导入顺序,统一放到conftest导入之后 2. 删除后端配置中无用的WebSocket限流配置,统一使用全局200次/10秒的限流策略 3. 精简平台、系统模块的路由初始化注释文档 4. 优化conftest.py中的数据库初始化导入顺序 5. 调整初始化日志输出格式,更清晰易懂 6. 前端开发环境配置切回本地后端地址,注释远程服务地址 7. 重构路由类的变量命名,提升可读性 8. 简化动态路由热重载逻辑,移除冗余缓存变量 9. 重构注册路由的代码,统一限流配置,调整导入顺序和挂载逻辑 10. 删除冗余的v1接口层统一导出文件
This commit is contained in:
@@ -1,75 +0,0 @@
|
|||||||
"""数据模型层 — 统一导入入口
|
|
||||||
|
|
||||||
本模块集中导出所有 ORM 模型,方便 Alembic 迁移脚本、
|
|
||||||
Service 层及其他模块引用。
|
|
||||||
|
|
||||||
模型自动发现机制(ImportUtil.find_models)已覆盖以下路径:
|
|
||||||
- app/api/v1/module_system/*/model.py
|
|
||||||
- app/api/v1/module_system/*/*/model.py
|
|
||||||
- app/api/v1/module_platform/*/model.py
|
|
||||||
- app/api/v1/module_platform/*/*/model.py
|
|
||||||
- app/plugin/*/model.py
|
|
||||||
- app/plugin/*/*/model.py
|
|
||||||
|
|
||||||
新增模型可直接参考已有文件完成定义。
|
|
||||||
"""
|
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
|
||||||
|
|
||||||
# 使用 TYPE_CHECKING 避免循环导入
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from app.api.v1.module_platform.email.model import (
|
|
||||||
EmailConfigModel,
|
|
||||||
EmailLogModel,
|
|
||||||
EmailTemplateModel,
|
|
||||||
)
|
|
||||||
from app.api.v1.module_platform.invoice.model import InvoiceModel
|
|
||||||
from app.api.v1.module_platform.menu.model import MenuModel
|
|
||||||
from app.api.v1.module_platform.order.model import OrderModel, PaymentRecordModel, RefundModel
|
|
||||||
from app.api.v1.module_platform.package.model import PackageMenuModel, PackageModel
|
|
||||||
from app.api.v1.module_platform.plugin.model import PluginModel
|
|
||||||
from app.api.v1.module_platform.tenant.model import TenantModel, TenantUserModel
|
|
||||||
from app.api.v1.module_system.dept.model import DeptModel
|
|
||||||
from app.api.v1.module_system.dict.model import DictDataModel, DictTypeModel
|
|
||||||
from app.api.v1.module_system.log.model import LoginLogModel, OperationLogModel
|
|
||||||
from app.api.v1.module_system.notice.model import NoticeModel
|
|
||||||
from app.api.v1.module_system.params.model import ParamsModel
|
|
||||||
from app.api.v1.module_system.position.model import PositionModel
|
|
||||||
from app.api.v1.module_system.role.model import RoleDeptsModel, RoleMenusModel, RoleModel
|
|
||||||
from app.api.v1.module_system.ticket.model import TicketModel
|
|
||||||
from app.api.v1.module_system.user.model import (
|
|
||||||
UserModel,
|
|
||||||
UserPositionsModel,
|
|
||||||
UserRolesModel,
|
|
||||||
)
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"DeptModel",
|
|
||||||
"DictDataModel",
|
|
||||||
"DictTypeModel",
|
|
||||||
"EmailConfigModel",
|
|
||||||
"EmailLogModel",
|
|
||||||
"EmailTemplateModel",
|
|
||||||
"InvoiceModel",
|
|
||||||
"LoginLogModel",
|
|
||||||
"MenuModel",
|
|
||||||
"NoticeModel",
|
|
||||||
"OperationLogModel",
|
|
||||||
"OrderModel",
|
|
||||||
"PackageMenuModel",
|
|
||||||
"PackageModel",
|
|
||||||
"ParamsModel",
|
|
||||||
"PaymentRecordModel",
|
|
||||||
"PluginModel",
|
|
||||||
"PositionModel",
|
|
||||||
"RefundModel",
|
|
||||||
"RoleDeptsModel",
|
|
||||||
"RoleMenusModel",
|
|
||||||
"RoleModel",
|
|
||||||
"TenantModel",
|
|
||||||
"TenantUserModel",
|
|
||||||
"TicketModel",
|
|
||||||
"UserModel",
|
|
||||||
"UserPositionsModel",
|
|
||||||
"UserRolesModel",
|
|
||||||
]
|
|
||||||
|
|||||||
@@ -1,17 +1,3 @@
|
|||||||
"""
|
|
||||||
平台级模块 - module_platform
|
|
||||||
|
|
||||||
包含平台级管理功能,不受租户隔离限制:
|
|
||||||
- 租户管理 (tenant)
|
|
||||||
- 套餐管理 (package)
|
|
||||||
- 插件管理 (plugin)
|
|
||||||
- 邮件服务 (email)
|
|
||||||
- 订单与支付 (order)
|
|
||||||
- 发票管理 (invoice)
|
|
||||||
- 租户自助服务 (self_service)
|
|
||||||
- 菜单管理 (menu)
|
|
||||||
"""
|
|
||||||
|
|
||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
|
|
||||||
from app.api.v1.module_platform.email.controller import EmailRouter
|
from app.api.v1.module_platform.email.controller import EmailRouter
|
||||||
|
|||||||
@@ -1,19 +1,3 @@
|
|||||||
"""
|
|
||||||
系统级模块 - module_system
|
|
||||||
|
|
||||||
租户内部管理功能,受租户隔离限制:
|
|
||||||
- 认证授权 (auth)
|
|
||||||
- 用户管理 (user)
|
|
||||||
- 角色管理 (role)
|
|
||||||
- 部门管理 (dept)
|
|
||||||
- 岗位管理 (position)
|
|
||||||
- 字典管理 (dict)
|
|
||||||
- 公告管理 (notice)
|
|
||||||
- 参数管理 (params)
|
|
||||||
- 日志管理 (log)
|
|
||||||
- 工单管理 (ticket)
|
|
||||||
"""
|
|
||||||
|
|
||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
|
|
||||||
from app.api.v1.module_system.auth.controller import AuthRouter
|
from app.api.v1.module_system.auth.controller import AuthRouter
|
||||||
|
|||||||
@@ -250,8 +250,6 @@ class Settings(BaseSettings):
|
|||||||
REQUEST_LIMITER_REDIS_PREFIX: str = "fastapiadmin:request_limiter:"
|
REQUEST_LIMITER_REDIS_PREFIX: str = "fastapiadmin:request_limiter:"
|
||||||
RATE_LIMITER_TIMES: int = 200
|
RATE_LIMITER_TIMES: int = 200
|
||||||
RATE_LIMITER_SECONDS: int = 10
|
RATE_LIMITER_SECONDS: int = 10
|
||||||
WS_RATE_LIMITER_TIMES: int = 1
|
|
||||||
WS_RATE_LIMITER_SECONDS: int = 5
|
|
||||||
|
|
||||||
# ================================================= #
|
# ================================================= #
|
||||||
# ******************* 重构配置 ******************* #
|
# ******************* 重构配置 ******************* #
|
||||||
|
|||||||
@@ -178,22 +178,14 @@ def reload_dynamic_router() -> APIRouter:
|
|||||||
# ── 2. 清除插件模块缓存,迫使 importlib 重新执行模块代码 ──
|
# ── 2. 清除插件模块缓存,迫使 importlib 重新执行模块代码 ──
|
||||||
_purge_plugin_modules()
|
_purge_plugin_modules()
|
||||||
|
|
||||||
# ── 3. 重新扫描并构建 ──
|
# ── 3. 将新路由挂载回 app ──
|
||||||
_dynamic_router_cache = _build_dynamic_router()
|
|
||||||
|
|
||||||
# ── 4. 将新路由挂载回 app ──
|
|
||||||
if app:
|
if app:
|
||||||
# 构造与 init_app.py 中相同的依赖项
|
# 构造与 init_app.py 中相同的依赖项
|
||||||
try:
|
app.include_router(_build_dynamic_router())
|
||||||
from fastapi_limiter.depends import RateLimiter
|
|
||||||
rate_deps = [Depends(RateLimiter(times=200, seconds=10))]
|
|
||||||
except Exception:
|
|
||||||
rate_deps = []
|
|
||||||
app.include_router(_dynamic_router_cache, dependencies=rate_deps)
|
|
||||||
logger.info("✅ 新插件路由已挂载到运行中的 app")
|
logger.info("✅ 新插件路由已挂载到运行中的 app")
|
||||||
|
|
||||||
logger.info("✅ 插件动态路由热重载完成")
|
logger.info("✅ 插件动态路由热重载完成")
|
||||||
return _dynamic_router_cache
|
return _build_dynamic_router()
|
||||||
|
|
||||||
|
|
||||||
def _purge_plugin_modules() -> None:
|
def _purge_plugin_modules() -> None:
|
||||||
|
|||||||
@@ -29,11 +29,11 @@ class OperationLogRoute(APIRoute):
|
|||||||
"""操作日志路由 — 自动记录请求/响应并后台异步写入"""
|
"""操作日志路由 — 自动记录请求/响应并后台异步写入"""
|
||||||
|
|
||||||
def get_route_handler(self) -> Callable[[Request], Coroutine[Any, Any, Response]]:
|
def get_route_handler(self) -> Callable[[Request], Coroutine[Any, Any, Response]]:
|
||||||
original = super().get_route_handler()
|
original_route_handler = super().get_route_handler()
|
||||||
|
|
||||||
async def custom_route_handler(request: Request) -> Response:
|
async def custom_route_handler(request: Request) -> Response:
|
||||||
start = time.time()
|
start = time.time()
|
||||||
response: Response = await original(request)
|
response: Response = await original_route_handler(request)
|
||||||
|
|
||||||
if not settings.OPERATION_LOG_RECORD or request.method not in settings.OPERATION_RECORD_METHOD:
|
if not settings.OPERATION_LOG_RECORD or request.method not in settings.OPERATION_RECORD_METHOD:
|
||||||
return response
|
return response
|
||||||
|
|||||||
@@ -97,18 +97,19 @@ def register_routers(app: FastAPI) -> None:
|
|||||||
from app.api.v1.module_monitor import monitor_router
|
from app.api.v1.module_monitor import monitor_router
|
||||||
from app.api.v1.module_platform import platform_router
|
from app.api.v1.module_platform import platform_router
|
||||||
from app.api.v1.module_system import system_router
|
from app.api.v1.module_system import system_router
|
||||||
from app.core.discover import get_dynamic_router, set_app_ref
|
|
||||||
|
app.include_router(common_router, dependencies=[Depends(RateLimiter(times=200, seconds=10))])
|
||||||
|
app.include_router(monitor_router, dependencies=[Depends(RateLimiter(times=200, seconds=10))])
|
||||||
|
app.include_router(platform_router, dependencies=[Depends(RateLimiter(times=200, seconds=10))])
|
||||||
|
app.include_router(system_router, dependencies=[Depends(RateLimiter(times=200, seconds=10))])
|
||||||
|
|
||||||
from app.plugin.module_ai.chat.ws import WS_AI
|
from app.plugin.module_ai.chat.ws import WS_AI
|
||||||
|
app.include_router(router=WS_AI, dependencies=[Depends(WebSocketRateLimiter(times=200, seconds=10))])
|
||||||
|
|
||||||
for router in (common_router, system_router, platform_router, monitor_router):
|
from app.core.discover import get_dynamic_router, set_app_ref
|
||||||
app.include_router(router, dependencies=[Depends(RateLimiter(times=settings.RATE_LIMITER_TIMES, seconds=settings.RATE_LIMITER_SECONDS))])
|
app.include_router(router=get_dynamic_router(), dependencies=[Depends(RateLimiter(times=200, seconds=10))])
|
||||||
|
|
||||||
app.include_router(router=get_dynamic_router(), dependencies=[Depends(RateLimiter(times=settings.RATE_LIMITER_TIMES, seconds=settings.RATE_LIMITER_SECONDS))])
|
|
||||||
set_app_ref(app)
|
set_app_ref(app)
|
||||||
|
|
||||||
app.include_router(router=WS_AI, dependencies=[Depends(WebSocketRateLimiter(times=settings.WS_RATE_LIMITER_TIMES, seconds=settings.WS_RATE_LIMITER_SECONDS))])
|
|
||||||
|
|
||||||
|
|
||||||
def register_files(app: FastAPI) -> None:
|
def register_files(app: FastAPI) -> None:
|
||||||
if settings.STATIC_ENABLE:
|
if settings.STATIC_ENABLE:
|
||||||
settings.STATIC_ROOT.mkdir(parents=True, exist_ok=True)
|
settings.STATIC_ROOT.mkdir(parents=True, exist_ok=True)
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ class InitializeData:
|
|||||||
if table_name in ("sys_login_log", "sys_operation_log"):
|
if table_name in ("sys_login_log", "sys_operation_log"):
|
||||||
count = await db.execute(select(func.count()).select_from(model))
|
count = await db.execute(select(func.count()).select_from(model))
|
||||||
if count.scalar():
|
if count.scalar():
|
||||||
logger.info(f"⏭️ {table_name} 已有数据,跳过")
|
logger.info(f"⏭️ 跳过 {table_name} 表数据初始化(表已有数据)")
|
||||||
continue
|
continue
|
||||||
objs = [model(**item) for item in data]
|
objs = [model(**item) for item in data]
|
||||||
db.add_all(objs)
|
db.add_all(objs)
|
||||||
|
|||||||
@@ -186,11 +186,12 @@ async def _test_lifespan(app) -> AsyncGenerator[Any, None]:
|
|||||||
app.state.redis = _mock_redis
|
app.state.redis = _mock_redis
|
||||||
|
|
||||||
# 将 admin 密码重置为已知密码 "admin123"
|
# 将 admin 密码重置为已知密码 "admin123"
|
||||||
from app.core.database import async_db_session
|
|
||||||
from app.api.v1.module_system.user.model import UserModel
|
|
||||||
from app.utils.hash_bcrpy_util import PwdUtil
|
|
||||||
from sqlalchemy import update
|
from sqlalchemy import update
|
||||||
|
|
||||||
|
from app.api.v1.module_system.user.model import UserModel
|
||||||
|
from app.core.database import async_db_session
|
||||||
|
from app.utils.hash_bcrpy_util import PwdUtil
|
||||||
|
|
||||||
async with async_db_session() as db:
|
async with async_db_session() as db:
|
||||||
await db.execute(
|
await db.execute(
|
||||||
update(UserModel)
|
update(UserModel)
|
||||||
|
|||||||
@@ -5,9 +5,8 @@
|
|||||||
每个接口一个测试用例,覆盖会话 CRUD 与 AI 对话。
|
每个接口一个测试用例,覆盖会话 CRUD 与 AI 对话。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from fastapi.testclient import TestClient
|
|
||||||
|
|
||||||
from conftest import assert_route
|
from conftest import assert_route
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
|
|
||||||
class TestAiChat:
|
class TestAiChat:
|
||||||
|
|||||||
@@ -3,9 +3,8 @@
|
|||||||
每个接口一个测试用例,验证路由存在且返回码正确。
|
每个接口一个测试用例,验证路由存在且返回码正确。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from fastapi.testclient import TestClient
|
|
||||||
|
|
||||||
from conftest import assert_route
|
from conftest import assert_route
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
|
|
||||||
class TestHealth:
|
class TestHealth:
|
||||||
|
|||||||
@@ -5,9 +5,8 @@
|
|||||||
每个接口一个测试用例,覆盖查询 / 新增 / 修改 / 删除 等操作。
|
每个接口一个测试用例,覆盖查询 / 新增 / 修改 / 删除 等操作。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from fastapi.testclient import TestClient
|
|
||||||
|
|
||||||
from conftest import assert_route
|
from conftest import assert_route
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
|
|
||||||
class TestExampleDemo:
|
class TestExampleDemo:
|
||||||
|
|||||||
@@ -5,9 +5,8 @@
|
|||||||
每个接口一个测试用例,覆盖查询 / 新增 / 修改 / 删除 等操作。
|
每个接口一个测试用例,覆盖查询 / 新增 / 修改 / 删除 等操作。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from fastapi.testclient import TestClient
|
|
||||||
|
|
||||||
from conftest import assert_route
|
from conftest import assert_route
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
|
|
||||||
class TestGenerator:
|
class TestGenerator:
|
||||||
|
|||||||
@@ -3,9 +3,8 @@
|
|||||||
认证数据测试:admin 登录后验证监控接口数据。
|
认证数据测试:admin 登录后验证监控接口数据。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from fastapi.testclient import TestClient
|
|
||||||
|
|
||||||
from conftest import assert_route # noqa: F401
|
from conftest import assert_route # noqa: F401
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
|
|
||||||
class TestCache:
|
class TestCache:
|
||||||
|
|||||||
@@ -3,9 +3,8 @@
|
|||||||
认证数据测试:admin 登录后验证 CRUD 真实数据。
|
认证数据测试:admin 登录后验证 CRUD 真实数据。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from fastapi.testclient import TestClient
|
|
||||||
|
|
||||||
from conftest import assert_route # noqa: F401
|
from conftest import assert_route # noqa: F401
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
|
|
||||||
class TestTenant:
|
class TestTenant:
|
||||||
|
|||||||
@@ -3,9 +3,8 @@
|
|||||||
认证数据测试:admin 登录后验证 CRUD 真实数据。
|
认证数据测试:admin 登录后验证 CRUD 真实数据。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from fastapi.testclient import TestClient
|
|
||||||
|
|
||||||
from conftest import assert_route # noqa: F401
|
from conftest import assert_route # noqa: F401
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
|
|
||||||
class TestAuth:
|
class TestAuth:
|
||||||
|
|||||||
@@ -7,10 +7,8 @@
|
|||||||
每个接口一个测试用例,覆盖查询 / 新增 / 修改 / 删除 等操作。
|
每个接口一个测试用例,覆盖查询 / 新增 / 修改 / 删除 等操作。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from fastapi.testclient import TestClient
|
|
||||||
|
|
||||||
from conftest import assert_route
|
from conftest import assert_route
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# /task/cronjob — 调度器与任务
|
# /task/cronjob — 调度器与任务
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ VITE_APP_TITLE = FastapiAdmin
|
|||||||
VITE_API_URL = /
|
VITE_API_URL = /
|
||||||
|
|
||||||
# 代理目标:本机后端(若出现 ENOTFOUND,说明上面域名在当前网络/DNS 下不可解析,改用本地或可用地址)
|
# 代理目标:本机后端(若出现 ENOTFOUND,说明上面域名在当前网络/DNS 下不可解析,改用本地或可用地址)
|
||||||
# VITE_API_BASE_URL = http://127.0.0.1:8001
|
VITE_API_BASE_URL = http://127.0.0.1:8001
|
||||||
VITE_API_BASE_URL = https://service.fastapiadmin.com
|
# VITE_API_BASE_URL = https://service.fastapiadmin.com
|
||||||
|
|
||||||
# 是否删除控制台输出
|
# 是否删除控制台输出
|
||||||
VITE_DROP_CONSOLE = false
|
VITE_DROP_CONSOLE = false
|
||||||
|
|
||||||
# WebSocket 端点(AI对话功能需要配置)
|
# WebSocket 端点(AI对话功能需要配置)
|
||||||
# VITE_APP_WS_ENDPOINT = ws://localhost:8001
|
VITE_APP_WS_ENDPOINT = ws://localhost:8001
|
||||||
VITE_APP_WS_ENDPOINT = wss://service.fastapiadmin.com
|
# VITE_APP_WS_ENDPOINT = wss://service.fastapiadmin.com
|
||||||
|
|||||||
Reference in New Issue
Block a user