mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-09-26 14:23:48 +00:00
chore: 完成项目多批次优化与功能迭代
本次提交包含多项改进: 1. 国际化补充:新增通知空状态文案中英文支持 2. 启动优化:重构banner输出、移除环境参数依赖,简化启动日志 3. 配置清理:移除冗余的REDIS_ENABLE/SQL_DB_ENABLE配置项,同步更新env示例与测试配置 4. 接口分页改造:将全局分页参数从Query改为Depends自动解析,统一排序逻辑 5. 菜单权限优化:调整菜单可见范围默认值、修复平台菜单路由路径错误 6. 异常处理增强:完善数据库异常捕获逻辑,新增连接失败专项处理 7. API令牌重构:重命名API令牌路由与权限标识,拆分前端API文件 8. 定时任务整合:将系统任务注册移入调度器初始化逻辑 9. 数据库连接优化:新增连接检查,简化建表/删表逻辑 10. 控制台美化:重构启动控制台面板,优化信息展示格式 11. 租户/角色服务优化:移除不必要的超级管理员装饰器,统一分页排序逻辑 12. 用户菜单适配:修复超级用户菜单过滤逻辑,移除scope强制校验
This commit is contained in:
@@ -28,7 +28,7 @@ async def invoice_apply_controller(
|
||||
@InvoiceRouter.get("/mine/list", summary="我的发票列表", response_model=ResponseSchema[PageResultSchema[InvoiceOutSchema]])
|
||||
async def invoice_list_my_controller(
|
||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_platform:invoice:query"]))],
|
||||
page: Annotated[PaginationQueryParam, Query(description="分页参数")],
|
||||
page: Annotated[PaginationQueryParam, Depends()],
|
||||
search: Annotated[InvoiceQueryParam, Query(description="发票查询参数")],
|
||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||
) -> JSONResponse:
|
||||
|
||||
@@ -41,8 +41,8 @@ class MenuCreateSchema(BaseModel):
|
||||
active_path: str | None = Field(default=None, max_length=200, description="激活菜单路径")
|
||||
show_badge: bool = Field(default=False, description="是否显示红点角标")
|
||||
show_text_badge: str | None = Field(default=None, max_length=20, description="文字角标内容")
|
||||
scope: Literal["platform", "tenant"] = Field(
|
||||
default="tenant",
|
||||
scope: Literal["platform", "tenant"] | None = Field(
|
||||
default=None,
|
||||
description="菜单可见范围(platform:仅平台 tenant:租户可用)",
|
||||
)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ from typing import Any
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.base_schema import AuthSchema, BatchSetAvailable
|
||||
from app.core.exceptions import CustomException, require_superadmin
|
||||
from app.core.exceptions import CustomException
|
||||
from app.utils.common_util import (
|
||||
get_child_id_map,
|
||||
get_child_recursion,
|
||||
@@ -80,7 +80,6 @@ class MenuService:
|
||||
menu_dict_list = [MenuTreeOutSchema.model_validate(menu).model_dump() for menu in menu_list]
|
||||
return traversal_to_tree(menu_dict_list)
|
||||
|
||||
@require_superadmin
|
||||
async def create(self, data: MenuCreateSchema) -> MenuOutSchema:
|
||||
search: dict[str, Any] = {}
|
||||
if data.title is not None:
|
||||
@@ -97,7 +96,6 @@ class MenuService:
|
||||
new_menu = await MenuCRUD(self.auth, self.db).create(data=data)
|
||||
return MenuOutSchema.model_validate(new_menu)
|
||||
|
||||
@require_superadmin
|
||||
async def update(self, id: int, data: MenuUpdateSchema) -> MenuOutSchema:
|
||||
_ = await MenuCRUD(self.auth, self.db).get_or_404(id=id, msg="更新失败,该菜单不存在")
|
||||
await self._validate_parent_child_type(data.parent_id, data.type)
|
||||
@@ -127,7 +125,6 @@ class MenuService:
|
||||
menu_out.parent_name = parent.name
|
||||
return menu_out
|
||||
|
||||
@require_superadmin
|
||||
async def delete(self, ids: list[int]) -> None:
|
||||
if not ids:
|
||||
raise CustomException(msg="删除失败,删除对象不能为空")
|
||||
@@ -143,7 +140,6 @@ class MenuService:
|
||||
delete_ids = list(delete_ids_set)
|
||||
await MenuCRUD(self.auth, self.db).delete(ids=delete_ids)
|
||||
|
||||
@require_superadmin
|
||||
async def set_available(self, data: BatchSetAvailable) -> None:
|
||||
menu_list = await MenuCRUD(self.auth, self.db).get_list()
|
||||
total_ids = []
|
||||
|
||||
@@ -55,7 +55,7 @@ async def order_detail_controller(
|
||||
async def order_list_controller(
|
||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_platform:order:query"]))],
|
||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||
page: Annotated[PaginationQueryParam, Query(description="分页参数")],
|
||||
page: Annotated[PaginationQueryParam, Depends()],
|
||||
search: Annotated[OrderQueryParam, Query(description="查询参数")],
|
||||
) -> JSONResponse:
|
||||
items, total = await OrderService.get_list(
|
||||
@@ -157,7 +157,7 @@ async def order_pay_mock_callback_controller(
|
||||
async def order_refund_list_controller(
|
||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_platform:order:query"]))],
|
||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||
page: Annotated[PaginationQueryParam, Query(description="分页参数")],
|
||||
page: Annotated[PaginationQueryParam, Depends()],
|
||||
status: Annotated[int | None, Query(description="退款状态筛选")] = None,
|
||||
) -> JSONResponse:
|
||||
offset = (page.page_no - 1) * page.page_size
|
||||
|
||||
@@ -43,7 +43,7 @@ async def get_obj_detail_controller(
|
||||
async def get_obj_list_controller(
|
||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_package:package:query"]))],
|
||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||
page: Annotated[PaginationQueryParam, Query(description="分页参数")],
|
||||
page: Annotated[PaginationQueryParam, Depends()],
|
||||
search: Annotated[PackageQueryParam, Query(description="查询参数")],
|
||||
) -> JSONResponse:
|
||||
result_dict = await PackageService(auth, db).page(
|
||||
|
||||
@@ -7,7 +7,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.api.v1.module_platform.menu.model import MenuModel
|
||||
from app.api.v1.module_platform.tenant.model import TenantModel
|
||||
from app.core.base_schema import AuthSchema, PageResultSchema
|
||||
from app.core.exceptions import CustomException, require_superadmin
|
||||
from app.core.exceptions import CustomException
|
||||
from app.core.logger import logger
|
||||
from app.utils.common_util import search_to_dict
|
||||
|
||||
@@ -29,17 +29,14 @@ class PackageService:
|
||||
self.auth = auth
|
||||
self.db = db
|
||||
|
||||
@require_superadmin
|
||||
async def get_options(self) -> list[dict[str, Any]]:
|
||||
"""获取套餐下拉选项,委托给 PackageCRUD"""
|
||||
return await PackageCRUD(self.auth, self.db).get_options()
|
||||
|
||||
@require_superadmin
|
||||
async def detail(self, id: int) -> PackageOutSchema:
|
||||
obj = await PackageCRUD(self.auth, self.db).get_or_404(id=id)
|
||||
return PackageOutSchema.model_validate(obj)
|
||||
|
||||
@require_superadmin
|
||||
async def page(
|
||||
self,
|
||||
page_no: int,
|
||||
@@ -55,7 +52,6 @@ class PackageService:
|
||||
out_schema=PackageOutSchema,
|
||||
)
|
||||
|
||||
@require_superadmin
|
||||
async def create(self, data: PackageCreateSchema) -> PackageOutSchema:
|
||||
if await PackageCRUD(self.auth, self.db).get(name=data.name):
|
||||
raise CustomException(msg="创建失败,套餐名称已存在")
|
||||
@@ -67,7 +63,6 @@ class PackageService:
|
||||
logger.info(f"创建套餐成功: {result.name}")
|
||||
return result
|
||||
|
||||
@require_superadmin
|
||||
async def update(self, id: int, data: PackageUpdateSchema) -> PackageOutSchema:
|
||||
obj = await PackageCRUD(self.auth, self.db).get_or_404(id=id)
|
||||
|
||||
@@ -86,7 +81,6 @@ class PackageService:
|
||||
updated = await PackageCRUD(self.auth, self.db).update(id=id, data=data)
|
||||
return PackageOutSchema.model_validate(updated)
|
||||
|
||||
@require_superadmin
|
||||
async def delete(self, ids: list[int]) -> None:
|
||||
if not ids:
|
||||
raise CustomException(msg="删除失败,删除对象不能为空")
|
||||
|
||||
@@ -53,17 +53,14 @@ async def get_obj_detail_controller(
|
||||
async def get_obj_list_controller(
|
||||
auth: Annotated[AuthSchema, Security(AuthPermission(["module_platform:tenant:query"]))],
|
||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||
page: Annotated[PaginationQueryParam, Query(description="分页参数")],
|
||||
search: Annotated[TenantQueryParam, Query(description="查询参数")],
|
||||
page: Annotated[PaginationQueryParam, Depends()],
|
||||
search: Annotated[TenantQueryParam, Depends()],
|
||||
) -> JSONResponse:
|
||||
order_by = [{"id": "asc"}]
|
||||
if page.order_by:
|
||||
order_by = page.order_by
|
||||
result_dict = await TenantService(auth, db).page(
|
||||
page_no=page.page_no,
|
||||
page_size=page.page_size,
|
||||
search=search,
|
||||
order_by=order_by,
|
||||
order_by=page.order_by,
|
||||
)
|
||||
return SuccessResponse(data=result_dict, msg="查询租户列表成功")
|
||||
|
||||
@@ -249,7 +246,7 @@ async def order_create_controller(
|
||||
async def order_list_controller(
|
||||
auth: Annotated[AuthSchema, Security(AuthPermission(["tenant:order:query"]))],
|
||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||
page: Annotated[PaginationQueryParam, Query(description="分页参数")],
|
||||
page: Annotated[PaginationQueryParam, Depends()],
|
||||
) -> JSONResponse:
|
||||
result = await TenantService.get_self_order_list(
|
||||
auth=auth,
|
||||
|
||||
@@ -26,7 +26,7 @@ class TenantCreateSchema(BaseModel):
|
||||
domain: str | None = Field(default=None, max_length=255, description="域名")
|
||||
logo_url: str | None = Field(default=None, max_length=500, description="Logo URL")
|
||||
sort: int = Field(default=0, ge=0, description="排序")
|
||||
package_id: int = Field(..., gt=0, description="关联套餐ID(必选,决定租户可用的菜单与配额)")
|
||||
package_id: int | None = Field(default=None, gt=0, description="关联套餐ID")
|
||||
version: str | None = Field(default=None, max_length=20, description="版本号")
|
||||
favicon: str | None = Field(default=None, max_length=500, description="favicon地址")
|
||||
login_bg: str | None = Field(default=None, max_length=500, description="登录背景地址")
|
||||
|
||||
@@ -28,7 +28,7 @@ from app.api.v1.module_system.user.schema import UserCreateSchema
|
||||
from app.common.enums import OrderTypeEnum, RedisInitKeyConfig
|
||||
from app.core.base_schema import AuthSchema, BatchSetAvailable, PageResultSchema
|
||||
from app.core.database import async_db_session
|
||||
from app.core.exceptions import CustomException, require_superadmin
|
||||
from app.core.exceptions import CustomException
|
||||
from app.core.logger import logger
|
||||
from app.core.redis_crud import RedisCURD
|
||||
from app.utils.common_util import search_to_dict
|
||||
@@ -119,7 +119,6 @@ class TenantService:
|
||||
out_schema=TenantOutSchema,
|
||||
)
|
||||
|
||||
@require_superadmin
|
||||
async def create(self, data: TenantCreateSchema) -> TenantCreateResult:
|
||||
# ① 预校验:name / code 唯一
|
||||
if await TenantCRUD(self.auth, self.db).get(name=data.name):
|
||||
@@ -215,7 +214,6 @@ class TenantService:
|
||||
),
|
||||
)
|
||||
|
||||
@require_superadmin
|
||||
async def update(self, id: int, data: TenantUpdateSchema) -> TenantOutSchema:
|
||||
"""更新租户
|
||||
|
||||
@@ -313,7 +311,6 @@ class TenantService:
|
||||
await self.db.flush()
|
||||
logger.info(f"租户[{tenant_id}]套餐变更:已清理角色中不再可用的菜单关联, available_menus={len(available_ids)}, roles_affected={len(tenant_role_ids)}")
|
||||
|
||||
@require_superadmin
|
||||
async def delete(self, ids: list[int]) -> None:
|
||||
"""批量删除租户(含级联资源检查:用户/部门/角色/岗位)
|
||||
|
||||
@@ -703,16 +700,20 @@ class TenantService:
|
||||
返回:
|
||||
- None
|
||||
"""
|
||||
async with async_db_session() as session, session.begin():
|
||||
stmt = select(TenantModel)
|
||||
result = await session.execute(stmt)
|
||||
tenants = result.scalars().all()
|
||||
try:
|
||||
async with async_db_session() as session, session.begin():
|
||||
stmt = select(TenantModel)
|
||||
result = await session.execute(stmt)
|
||||
tenants = result.scalars().all()
|
||||
|
||||
for tenant in tenants:
|
||||
config = {field: getattr(tenant, field, None) for field in TenantService.CONFIG_FIELDS}
|
||||
for tenant in tenants:
|
||||
config = {field: getattr(tenant, field, None) for field in TenantService.CONFIG_FIELDS}
|
||||
|
||||
await TenantService._sync_configs_to_redis(redis, tenant.id, config)
|
||||
logger.info(f"✅ 租户[{tenant.name}](id={tenant.id}) 配置已缓存到 Redis")
|
||||
await TenantService._sync_configs_to_redis(redis, tenant.id, config)
|
||||
logger.info(f"✅ 租户[{tenant.name}](id={tenant.id}) 配置已缓存到 Redis")
|
||||
except Exception as e:
|
||||
logger.error(f"❌️ 初始化租户配置到 Redis 失败: {e}")
|
||||
raise CustomException(msg="初始化租户配置到 Redis 失败") from e
|
||||
|
||||
async def renew(self, tenant_id: int, end_time: str) -> TenantOutSchema:
|
||||
"""租户续期:延长 end_time 并恢复为 active 状态
|
||||
|
||||
Reference in New Issue
Block a user