refactor: 大规模代码整理与功能优化

1. 重构后端API路由、CRUD与模块结构,整合日志管理,移除废弃demo代码
2. 优化前端组件类型定义、样式与路由配置,修复权限判断逻辑
3. 调整默认排序规则、滚动条样式与工具类函数,更新依赖与配置文件
4. 修复多处类型不匹配与默认值问题,完善表单与菜单验证逻辑
This commit is contained in:
zhangtao
2026-06-17 01:56:31 +08:00
parent 17b3cd0a4c
commit 73f2823692
500 changed files with 40763 additions and 26616 deletions
@@ -0,0 +1,3 @@
from .controller import PackageRouter
__all__ = ["PackageRouter"]
@@ -2,53 +2,73 @@ from typing import Annotated
from fastapi import APIRouter, Body, Depends, Path
from fastapi.responses import JSONResponse
from fastapi_cache import FastAPICache
from fastapi_cache.decorator import cache
from app.api.v1.module_system.auth.schema import AuthSchema
from app.common.response import ResponseSchema, SuccessResponse
from app.core.base_params import PaginationQueryParam
from app.core.base_schema import BatchDelete, BatchSetAvailable
from app.core.base_schema import AuthSchema, BatchSetAvailable, PageResultSchema
from app.core.dependencies import AuthPermission
from app.core.logger import log
from app.core.router_class import OperationLogRoute
from .schema import (
PackageCreateSchema,
PackageMenuSetSchema,
PackageOutSchema,
PackagePluginSetSchema,
PackageQueryParam,
PackageUpdateSchema,
)
from .service import PackageService
PackageRouter = APIRouter(route_class=OperationLogRoute, prefix="/package", tags=["套餐管理"])
PackageRouter = APIRouter(route_class=OperationLogRoute, prefix="/package", tags=["平台管理/套餐管理"])
_PKG_NS = "package"
@PackageRouter.get(
"/detail/{id}",
summary="获取套餐详情",
description="根据套餐ID获取套餐详情",
response_model=ResponseSchema[PackageOutSchema],
)
@cache(expire=300, namespace=_PKG_NS)
async def get_obj_detail_controller(
id: Annotated[int, Path(description="套餐ID")],
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_package:package:query"]))],
auth: Annotated[AuthSchema, Depends(AuthPermission(['module_package:package:query']))],
) -> JSONResponse:
"""
获取套餐详情
参数:
- id (int): 套餐 ID。
返回:
- JSONResponse: 包含套餐详情的 JSON 响应。
"""
result_dict = await PackageService.detail_service(auth=auth, id=id)
log.info(f"获取套餐详情成功 {id}")
return SuccessResponse(data=result_dict, msg="获取套餐详情成功")
@PackageRouter.get(
"/list",
summary="获取套餐列表",
description="分页获取套餐列表",
response_model=ResponseSchema[dict],
response_model=ResponseSchema[PageResultSchema[PackageOutSchema]],
)
async def get_obj_list_controller(
page: Annotated[PaginationQueryParam, Depends()],
search: Annotated[PackageQueryParam, Depends()],
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_package:package:query"]))],
auth: Annotated[AuthSchema, Depends(AuthPermission(['module_package:package:query']))],
) -> JSONResponse:
"""
获取套餐列表
参数:
- page (PaginationQueryParam): 分页查询参数。
- search (PackageQueryParam): 查询筛选参数。
返回:
- JSONResponse: 包含分页套餐列表的 JSON 响应。
"""
result_dict = await PackageService.page_service(
auth=auth,
page_no=page.page_no,
@@ -62,75 +82,116 @@ async def get_obj_list_controller(
@PackageRouter.post(
"/create",
summary="创建套餐",
description="创建新的租户套餐",
response_model=ResponseSchema[PackageOutSchema],
)
async def create_obj_controller(
data: Annotated[PackageCreateSchema, Body(description="套餐信息")],
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_package:package:create"]))],
auth: Annotated[AuthSchema, Depends(AuthPermission(['module_package:package:create']))],
) -> JSONResponse:
"""
创建套餐
参数:
- data (PackageCreateSchema): 套餐创建参数。
返回:
- JSONResponse: 包含创建后的套餐详情的 JSON 响应。
"""
result_dict = await PackageService.create_service(auth=auth, data=data)
log.info(f"创建套餐成功 {result_dict.get('name')}")
await FastAPICache.clear(namespace=_PKG_NS)
return SuccessResponse(data=result_dict, msg="创建成功")
@PackageRouter.put(
"/update/{id}",
summary="更新套餐",
description="更新套餐信息",
response_model=ResponseSchema[PackageOutSchema],
)
async def update_obj_controller(
id: Annotated[int, Path(description="套餐ID")],
data: Annotated[PackageUpdateSchema, Body(description="套餐信息")],
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_package:package:update"]))],
auth: Annotated[AuthSchema, Depends(AuthPermission(['module_package:package:update']))],
) -> JSONResponse:
"""
更新套餐
参数:
- id (int): 套餐 ID。
- data (PackageUpdateSchema): 套餐更新参数。
返回:
- JSONResponse: 包含更新后的套餐详情的 JSON 响应。
"""
result_dict = await PackageService.update_service(auth=auth, id=id, data=data)
log.info(f"更新套餐成功 {id}")
await FastAPICache.clear(namespace=_PKG_NS)
return SuccessResponse(data=result_dict, msg="更新成功")
@PackageRouter.delete(
"/delete",
summary="删除套餐",
description="批量删除套餐(已被租户使用的套餐无法删除)",
response_model=ResponseSchema,
)
async def delete_obj_controller(
data: Annotated[BatchDelete, Body(description="删除信息")],
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_package:package:delete"]))],
ids: Annotated[list[int], Body(description="ID列表")],
auth: Annotated[AuthSchema, Depends(AuthPermission(['module_package:package:delete']))],
) -> JSONResponse:
await PackageService.delete_service(auth=auth, ids=data.ids)
log.info(f"删除套餐成功 {data.ids}")
"""
删除套餐
参数:
- ids (list[int]): 套餐 ID 列表。
返回:
- JSONResponse: 删除结果。
"""
await PackageService.delete_service(auth=auth, ids=ids)
await FastAPICache.clear(namespace=_PKG_NS)
return SuccessResponse(msg="删除成功")
@PackageRouter.patch(
"/status/batch",
summary="批量修改状态",
description="批量启用/禁用套餐",
response_model=ResponseSchema,
)
async def set_available_controller(
data: Annotated[BatchSetAvailable, Body(description="状态设置")],
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_package:package:update"]))],
auth: Annotated[AuthSchema, Depends(AuthPermission(['module_package:package:update']))],
) -> JSONResponse:
"""
批量修改套餐状态
参数:
- data (BatchSetAvailable): 批量状态设置参数。
返回:
- JSONResponse: 操作结果。
"""
for id in data.ids:
await PackageService.update_service(auth=auth, id=id, data=PackageUpdateSchema(status=data.status))
log.info(f"套餐状态设置成功 {data.ids}")
await FastAPICache.clear(namespace=_PKG_NS)
return SuccessResponse(msg="状态设置成功")
@PackageRouter.get(
"/menus/{package_id}",
summary="获取套餐菜单",
description="获取套餐包含的菜单ID列表",
response_model=ResponseSchema[list[int]],
)
async def get_menus_controller(
package_id: Annotated[int, Path(description="套餐ID")],
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_package:package:query"]))],
auth: Annotated[AuthSchema, Depends(AuthPermission(['module_package:package:query']))],
) -> JSONResponse:
"""
获取套餐关联菜单
参数:
- package_id (int): 套餐 ID。
返回:
- JSONResponse: 包含菜单 ID 列表的 JSON 响应。
"""
result = await PackageService.get_menus_service(auth=auth, package_id=package_id)
return SuccessResponse(data=result, msg="获取成功")
@@ -138,14 +199,68 @@ async def get_menus_controller(
@PackageRouter.post(
"/menus/{package_id}/set",
summary="设置套餐菜单",
description="批量设置套餐包含的菜单(先清空再写入)",
response_model=ResponseSchema,
)
async def set_menus_controller(
package_id: Annotated[int, Path(description="套餐ID")],
data: Annotated[PackageMenuSetSchema, Body(description="菜单列表")],
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_package:package:update"]))],
auth: Annotated[AuthSchema, Depends(AuthPermission(['module_package:package:update']))],
) -> JSONResponse:
"""
设置套餐菜单权限
参数:
- package_id (int): 套餐 ID。
- data (PackageMenuSetSchema): 菜单 ID 列表。
返回:
- JSONResponse: 操作结果。
"""
await PackageService.set_menus_service(auth=auth, package_id=package_id, data=data)
log.info(f"套餐[{package_id}]菜单权限已设置, count={len(data.menu_ids)}")
return SuccessResponse(msg="设置成功")
@PackageRouter.get(
"/plugins/{package_id}",
summary="获取套餐插件",
response_model=ResponseSchema[list[int]],
)
async def get_plugins_controller(
package_id: Annotated[int, Path(description="套餐ID")],
auth: Annotated[AuthSchema, Depends(AuthPermission(['module_package:package:query']))],
) -> JSONResponse:
"""
获取套餐关联插件
参数:
- package_id (int): 套餐 ID。
返回:
- JSONResponse: 包含插件 ID 列表的 JSON 响应。
"""
result = await PackageService.get_plugins_service(auth=auth, package_id=package_id)
return SuccessResponse(data=result, msg="获取成功")
@PackageRouter.post(
"/plugins/{package_id}/set",
summary="设置套餐插件",
response_model=ResponseSchema,
)
async def set_plugins_controller(
package_id: Annotated[int, Path(description="套餐ID")],
data: Annotated[PackagePluginSetSchema, Body(description="插件列表")],
auth: Annotated[AuthSchema, Depends(AuthPermission(['module_package:package:update']))],
) -> JSONResponse:
"""
设置套餐插件
参数:
- package_id (int): 套餐 ID。
- data (PackagePluginSetSchema): 插件 ID 列表。
返回:
- JSONResponse: 操作结果。
"""
await PackageService.set_plugins_service(auth=auth, package_id=package_id, data=data)
return SuccessResponse(msg="设置成功")
@@ -1,11 +1,12 @@
from app.api.v1.module_system.auth.schema import AuthSchema
from app.core.base_crud import CRUDBase
from app.core.base_schema import AuthSchema
from .model import PackageModel
from .schema import PackageCreateSchema, PackageUpdateSchema
class PackageCRUD(CRUDBase):
class PackageCRUD(CRUDBase[PackageModel, PackageCreateSchema, PackageUpdateSchema]):
"""套餐模块 CRUD"""
def __init__(self, auth: AuthSchema):
super().__init__(PackageModel, auth)
super().__init__(model=PackageModel, auth=auth)
@@ -1,34 +1,34 @@
from datetime import datetime
from sqlalchemy import DateTime, ForeignKey, Integer, String, UniqueConstraint
from sqlalchemy import ForeignKey, Integer, String, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column, validates
from app.core.base_model import MappedBase
from app.core.base_model import MappedBase, ModelMixin
class PackageModel(MappedBase):
class PackageModel(ModelMixin):
"""
套餐模型 - 定义租户可用的功能套餐
status: 0=正常 1=禁用
"""
__tablename__: str = "platform_package"
__table_args__: dict[str, str] = {"comment": "租户套餐表"}
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True, comment="主键ID")
name: Mapped[str] = mapped_column(String(100), nullable=False, unique=True, comment="套餐名称")
code: Mapped[str] = mapped_column(String(100), nullable=False, unique=True, comment="套餐编码")
status: Mapped[str] = mapped_column(
String(10), nullable=False, default="0", comment="状态(0:正常 1:禁用)"
)
sort: Mapped[int] = mapped_column(Integer, nullable=False, default=0, comment="排序")
description: Mapped[str | None] = mapped_column(
String(255), nullable=True, default=None, comment="描述"
)
create_time: Mapped[datetime] = mapped_column(
DateTime, default=datetime.now, nullable=False, comment="创建时间"
)
update_time: Mapped[datetime] = mapped_column(
DateTime, default=datetime.now, onupdate=datetime.now, nullable=False, comment="更新时间"
# ─── 定价与计费 ───
price: Mapped[int] = mapped_column(Integer, nullable=False, default=0, comment="价格(分)")
period: Mapped[str] = mapped_column(String(10), nullable=False, default="month", comment="计费周期(month/year)")
trial_days: Mapped[int] = mapped_column(Integer, nullable=False, default=0, comment="免费试用天数")
# ─── 配额限制 ───
max_users: Mapped[int] = mapped_column(Integer, nullable=False, default=10, comment="最大用户数")
max_roles: Mapped[int] = mapped_column(Integer, nullable=False, default=5, comment="最大角色数")
max_depts: Mapped[int] = mapped_column(Integer, nullable=False, default=10, comment="最大部门数")
max_storage_mb: Mapped[int] = mapped_column(Integer, nullable=False, default=1024, comment="最大存储(MB)")
# ─── 速率限制 ───
rate_limit: Mapped[int] = mapped_column(
Integer, nullable=False, default=60, comment="API速率限制(请求/10秒)"
)
@validates("name")
@@ -67,8 +67,36 @@ class PackageMenuModel(MappedBase):
)
menu_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("sys_menu.id", ondelete="CASCADE", onupdate="CASCADE"),
ForeignKey("platform_menu.id", ondelete="CASCADE", onupdate="CASCADE"),
nullable=False,
index=True,
comment="菜单ID",
)
class PackagePluginModel(MappedBase):
"""
套餐-插件关联表 — 定义套餐包含的插件资源
"""
__tablename__: str = "platform_package_plugin"
__table_args__ = (
UniqueConstraint("package_id", "plugin_id", name="uq_package_plugin"),
{"comment": "套餐插件关联表"},
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True, comment="主键ID")
package_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("platform_package.id", ondelete="CASCADE", onupdate="CASCADE"),
nullable=False,
index=True,
comment="套餐ID",
)
plugin_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("platform_plugin.id", ondelete="CASCADE", onupdate="CASCADE"),
nullable=False,
index=True,
comment="插件ID",
)
@@ -1,3 +1,5 @@
from dataclasses import dataclass
from fastapi import Query
from pydantic import BaseModel, ConfigDict, Field, field_validator
@@ -10,9 +12,17 @@ class PackageCreateSchema(BaseModel):
name: str = Field(..., min_length=1, max_length=100, description="套餐名称")
code: str = Field(..., min_length=2, max_length=100, description="套餐编码")
status: str = Field(default="0", max_length=1, description="状态(0:正常 1:禁用)")
status: int = Field(default=0, ge=0, le=1, description="状态(0:正常 1:禁用)")
sort: int = Field(default=0, ge=0, description="排序")
description: str | None = Field(default=None, max_length=255, description="描述")
price: int = Field(default=0, ge=0, description="价格(分)")
period: str = Field(default="month", pattern=r"^(month|year)$", description="计费周期")
trial_days: int = Field(default=0, ge=0, description="免费试用天数")
max_users: int = Field(default=10, ge=0, description="最大用户数")
max_roles: int = Field(default=5, ge=0, description="最大角色数")
max_depts: int = Field(default=10, ge=0, description="最大部门数")
max_storage_mb: int = Field(default=1024, ge=0, description="最大存储(MB)")
rate_limit: int = Field(default=60, ge=10, description="API速率限制(请求/10秒)")
@field_validator("name")
@classmethod
@@ -34,20 +44,28 @@ class PackageCreateSchema(BaseModel):
@field_validator("status")
@classmethod
def _validate_status(cls, v: str) -> str:
if v not in {"0", "1"}:
def _validate_status(cls, v: int) -> int:
if v not in {0, 1}:
raise ValueError("状态仅支持 0(正常) 或 1(禁用)")
return v
class PackageUpdateSchema(BaseModel):
class PackageUpdateSchema(PackageCreateSchema):
"""更新套餐"""
name: str | None = Field(default=None, max_length=100, description="套餐名称")
code: str | None = Field(default=None, max_length=100, description="套餐编码")
status: str | None = Field(default=None, max_length=1, description="状态(0:正常 1:禁用)")
name: str | None = Field(default=None, max_length=100, description="套餐名称") # type: ignore[assignment]
code: str | None = Field(default=None, max_length=100, description="套餐编码") # type: ignore[assignment]
status: int | None = Field(default=None, ge=0, le=1, description="状态(0:正常 1:禁用)")
sort: int | None = Field(default=None, ge=0, description="排序")
description: str | None = Field(default=None, max_length=255, description="描述")
price: int | None = Field(default=None, ge=0, description="价格(分)")
period: str | None = Field(default=None, pattern=r"^(month|year)$", description="计费周期")
trial_days: int | None = Field(default=None, ge=0, description="免费试用天数")
max_users: int | None = Field(default=None, ge=0, description="最大用户数")
max_roles: int | None = Field(default=None, ge=0, description="最大角色数")
max_depts: int | None = Field(default=None, ge=0, description="最大部门数")
max_storage_mb: int | None = Field(default=None, ge=0, description="最大存储(MB)")
rate_limit: int | None = Field(default=None, ge=10, description="API速率限制(请求/10秒)")
@field_validator("code")
@classmethod
@@ -61,10 +79,10 @@ class PackageUpdateSchema(BaseModel):
@field_validator("status")
@classmethod
def _validate_status(cls, v: str | None) -> str | None:
def _validate_status(cls, v: int | None) -> int | None:
if v is None:
return v
if v not in {"0", "1"}:
if v not in {0, 1}:
raise ValueError("状态仅支持 0(正常) 或 1(禁用)")
return v
@@ -75,6 +93,7 @@ class PackageOutSchema(PackageCreateSchema, BaseSchema):
model_config = ConfigDict(from_attributes=True)
@dataclass
class PackageQueryParam:
"""套餐查询参数"""
@@ -96,3 +115,9 @@ class PackageMenuSetSchema(BaseModel):
"""批量设置套餐菜单权限"""
menu_ids: list[int] = Field(..., description="菜单ID列表")
class PackagePluginSetSchema(BaseModel):
"""批量设置套餐插件"""
plugin_ids: list[int] = Field(..., description="插件ID列表")
@@ -2,16 +2,17 @@ import sqlalchemy as sa
from sqlalchemy import func, select
from app.api.v1.module_platform.tenant.model import TenantModel
from app.api.v1.module_system.auth.schema import AuthSchema
from app.core.base_schema import AuthSchema
from app.core.exceptions import CustomException
from app.core.logger import log
from app.core.logger import logger
from .crud import PackageCRUD
from .model import PackageMenuModel, PackageModel
from .model import PackageMenuModel, PackageModel, PackagePluginModel
from .schema import (
PackageCreateSchema,
PackageMenuSetSchema,
PackageOutSchema,
PackagePluginSetSchema,
PackageQueryParam,
PackageUpdateSchema,
)
@@ -21,11 +22,11 @@ class PackageService:
"""套餐模块服务层"""
@classmethod
async def detail_service(cls, auth: AuthSchema, id: int) -> dict:
obj = await PackageCRUD(auth).get_by_id_crud(id=id)
async def detail_service(cls, auth: AuthSchema, id: int) -> PackageOutSchema:
obj = await PackageCRUD(auth).get(id=id)
if not obj:
raise CustomException(msg="套餐不存在")
return PackageOutSchema.model_validate(obj).model_dump()
return PackageOutSchema.model_validate(obj)
@classmethod
async def page_service(
@@ -36,7 +37,7 @@ class PackageService:
search: PackageQueryParam | None = None,
order_by: list[dict[str, str]] | None = None,
) -> dict:
return await PackageCRUD(auth).page_crud(
return await PackageCRUD(auth).page(
offset=(page_no - 1) * page_size,
limit=page_size,
order_by=order_by or [{"sort": "asc"}, {"id": "asc"}],
@@ -45,24 +46,22 @@ class PackageService:
)
@classmethod
async def create_service(cls, auth: AuthSchema, data: PackageCreateSchema) -> dict:
async def create_service(cls, auth: AuthSchema, data: PackageCreateSchema) -> PackageOutSchema:
if await PackageCRUD(auth).get(name=data.name):
raise CustomException(msg="创建失败,套餐名称已存在")
if await PackageCRUD(auth).get(code=data.code):
raise CustomException(msg="创建失败,套餐编码已存在")
obj = await PackageCRUD(auth).create_crud(data=data)
if not obj:
raise CustomException(msg="创建套餐失败")
result = PackageOutSchema.model_validate(obj).model_dump()
log.info(f"创建套餐成功: {result.get('name')}")
obj = await PackageCRUD(auth).create(data=data)
result = PackageOutSchema.model_validate(obj)
logger.info(f"创建套餐成功: {result.name}")
return result
@classmethod
async def update_service(
cls, auth: AuthSchema, id: int, data: PackageUpdateSchema
) -> dict:
obj = await PackageCRUD(auth).get_by_id_crud(id=id)
) -> PackageOutSchema:
obj = await PackageCRUD(auth).get(id=id)
if not obj:
raise CustomException(msg="套餐不存在")
@@ -75,10 +74,13 @@ class PackageService:
if exist and exist.id != id:
raise CustomException(msg="更新失败,编码重复")
updated = await PackageCRUD(auth).update_crud(id=id, data=data)
if not updated:
raise CustomException(msg="更新失败")
return PackageOutSchema.model_validate(updated).model_dump()
# 套餐禁用时记录受影响租户
if data.status is not None and data.status == 1 and obj.status == 0:
await cls.disable_cascade_service(auth=auth, package_id=id)
updated = await PackageCRUD(auth).update(id=id, data=data)
return PackageOutSchema.model_validate(updated)
@classmethod
async def delete_service(cls, auth: AuthSchema, ids: list[int]) -> None:
@@ -92,7 +94,20 @@ class PackageService:
if count and count > 0:
raise CustomException(msg=f"套餐 ID={pid} 已被 {count} 个租户使用,无法删除")
await PackageCRUD(auth).delete_crud(ids=ids)
await PackageCRUD(auth).delete(ids=ids)
@classmethod
async def disable_cascade_service(cls, auth: AuthSchema, package_id: int) -> None:
"""套餐禁用时日志记录受影响租户"""
stmt = select(TenantModel.id, TenantModel.name).where(
TenantModel.package_id == package_id,
TenantModel.status == 0,
)
result = await auth.db.execute(stmt)
rows = result.all()
if rows:
tenant_ids = [row[0] for row in rows]
logger.warning(f"套餐[{package_id}]已禁用,影响租户: {tenant_ids}")
@classmethod
async def get_menus_service(cls, auth: AuthSchema, package_id: int) -> list[int]:
@@ -105,12 +120,13 @@ class PackageService:
async def set_menus_service(
cls, auth: AuthSchema, package_id: int, data: PackageMenuSetSchema
) -> None:
"""批量设置套餐菜单权限(先清空再写入)"""
await auth.db.execute(sa.delete(PackageMenuModel).where(PackageMenuModel.package_id == package_id))
for menu_id in data.menu_ids:
auth.db.add(PackageMenuModel(package_id=package_id, menu_id=menu_id))
await auth.db.flush()
log.info(f"套餐[{package_id}]菜单权限已设置, count={len(data.menu_ids)}")
logger.info(f"套餐[{package_id}]菜单权限已设置, count={len(data.menu_ids)}")
@staticmethod
async def get_package_menu_ids(auth: AuthSchema, package_id: int) -> list[int]:
@@ -121,8 +137,17 @@ class PackageService:
@staticmethod
async def get_tenant_available_menu_ids(auth: AuthSchema, tenant_id: int) -> list[int]:
"""获取租户的完整可用菜单ID列表(套餐菜单 + 自定义授权菜单)"""
from app.api.v1.module_platform.tenant.model import TenantMenuModel, TenantModel
"""获取租户的完整可用菜单ID列表(仅从套餐菜单获取)
平台租户 (id=1) 返回全部启用菜单,不受套餐限制。
"""
from app.api.v1.module_platform.menu.model import MenuModel
from app.api.v1.module_platform.tenant.model import TenantModel
if tenant_id == 1:
menu_stmt = select(MenuModel.id).where(MenuModel.status == 0)
result = await auth.db.execute(menu_stmt)
return [row[0] for row in result.all()]
stmt = select(TenantModel).where(TenantModel.id == tenant_id).limit(1)
result = await auth.db.execute(stmt)
@@ -130,21 +155,60 @@ class PackageService:
if not tenant:
return []
all_menu_ids: set[int] = set()
if not tenant.package_id:
return []
if tenant.package_id:
pkg_stmt = select(PackageModel.status).where(PackageModel.id == tenant.package_id).limit(1)
pkg_result = await auth.db.execute(pkg_stmt)
pkg_status = pkg_result.scalar_one_or_none()
if pkg_status == "0":
stmt = select(PackageMenuModel.menu_id).where(PackageMenuModel.package_id == tenant.package_id)
result = await auth.db.execute(stmt)
for row in result.all():
all_menu_ids.add(row[0])
pkg_stmt = select(PackageModel.status).where(PackageModel.id == tenant.package_id).limit(1)
pkg_result = await auth.db.execute(pkg_stmt)
pkg_status = pkg_result.scalar_one_or_none()
if pkg_status != 0:
return []
stmt = select(TenantMenuModel.menu_id).where(TenantMenuModel.tenant_id == tenant_id)
menu_stmt = select(PackageMenuModel.menu_id).where(PackageMenuModel.package_id == tenant.package_id)
result = await auth.db.execute(menu_stmt)
return [row[0] for row in result.all()]
@staticmethod
async def get_tenant_available_plugin_ids(auth: AuthSchema, tenant_id: int) -> list[int]:
"""获取租户套餐可用的插件ID列表"""
from app.api.v1.module_platform.tenant.model import TenantModel
stmt = select(TenantModel).where(TenantModel.id == tenant_id).limit(1)
result = await auth.db.execute(stmt)
for row in result.all():
all_menu_ids.add(row[0])
tenant = result.scalar_one_or_none()
if not tenant or not tenant.package_id:
return []
return list(all_menu_ids)
pkg_stmt = select(PackageModel.status).where(PackageModel.id == tenant.package_id).limit(1)
pkg_result = await auth.db.execute(pkg_stmt)
pkg_status = pkg_result.scalar_one_or_none()
if pkg_status != 0:
return []
plugin_stmt = select(PackagePluginModel.plugin_id).where(
PackagePluginModel.package_id == tenant.package_id
)
result = await auth.db.execute(plugin_stmt)
return [row[0] for row in result.all()]
@classmethod
async def get_plugins_service(cls, auth: AuthSchema, package_id: int) -> list[int]:
"""获取套餐插件权限(返回 plugin_id 列表)"""
stmt = select(PackagePluginModel.plugin_id).where(
PackagePluginModel.package_id == package_id
)
result = await auth.db.execute(stmt)
return [row[0] for row in result.all()]
@classmethod
async def set_plugins_service(
cls, auth: AuthSchema, package_id: int, data: PackagePluginSetSchema
) -> None:
"""批量设置套餐插件(先清空再写入)"""
await auth.db.execute(
sa.delete(PackagePluginModel).where(PackagePluginModel.package_id == package_id)
)
for plugin_id in data.plugin_ids:
auth.db.add(PackagePluginModel(package_id=package_id, plugin_id=plugin_id))
await auth.db.flush()
logger.info(f"套餐[{package_id}]插件已设置, count={len(data.plugin_ids)}")