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:
zhangtao
2026-07-15 01:21:53 +08:00
parent 8d0e76c694
commit 3f7d5aa4b9
51 changed files with 257 additions and 300 deletions
@@ -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 状态