Files
FastapiAdmin/backend/app/api/v1/module_platform/package/service.py
T
zhangtao 6a5f8cf0dd refactor: 完成项目大规模重构与功能优化
这是一次综合性的项目迭代,包含以下核心变更:
1.  **目录与模块重构**
    - 调整工作流节点类型模块目录结构,迁移节点类型相关代码
    - 重命名platform模块为system模块,更新插件配置信息
    - 重构代码生成模块导入路径
2.  **数据库与CRUD优化**
    - 统一所有CRUD类构造函数,新增数据库会话参数
    - 修复权限过滤器数据库会话使用问题
    - 更新模板生成器的CRUD代码模板
3.  **认证与安全改进**
    - 重构JWT密钥配置,移除默认密钥强制要求环境变量
    - 重命名密码工具类,统一密码加密校验逻辑
    - 优化OAuth认证流程,修复匿名认证使用问题
4.  **前端与静态资源**
    - 重构前端挂载逻辑,增加目录存在性校验
    - 使用标准StaticFiles替换自定义前端挂载实现
5.  **工具类与依赖更新**
    - 修复导入工具的表名重复检测逻辑
    - 优化限流回调代码,移除冗余依赖
    - 更新用户、租户等模块的响应模型字段
6.  **数据与配置修正**
    - 修复系统版本数据字段命名不统一问题
    - 简化枚举类校验逻辑,移除冗余注释
    - 修复测试用例中的密码工具类导入错误
2026-07-11 13:03:28 +08:00

157 lines
6.3 KiB
Python

from typing import Any
import sqlalchemy as sa
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
from app.api.v1.module_platform.tenant.model import TenantModel
from app.core.base_schema import AuthSchema, PageResultSchema
from app.core.dependencies import require_superadmin
from app.core.exceptions import CustomException
from app.core.logger import logger
from .crud import PackageCRUD
from .model import PackageMenuModel, PackageModel
from .schema import (
PackageCreateSchema,
PackageMenuSetSchema,
PackageOutSchema,
PackageQueryParam,
PackageUpdateSchema,
)
class PackageService:
"""套餐管理服务(仅超级管理员可操作)"""
def __init__(self, auth: AuthSchema, db: AsyncSession) -> None:
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,
page_size: int,
search: PackageQueryParam | None = None,
order_by: list[dict[str, str]] | None = None,
) -> PageResultSchema[PackageOutSchema]:
return await PackageCRUD(self.auth, self.db).page(
offset=(page_no - 1) * page_size,
limit=page_size,
order_by=order_by or [{"sort": "asc"}, {"id": "asc"}],
search=vars(search) if search else None,
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="创建失败,套餐名称已存在")
if await PackageCRUD(self.auth, self.db).get(code=data.code):
raise CustomException(msg="创建失败,套餐编码已存在")
obj = await PackageCRUD(self.auth, self.db).create(data=data)
result = PackageOutSchema.model_validate(obj)
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)
if data.name is not None:
exist = await PackageCRUD(self.auth, self.db).get(name=data.name)
if exist and exist.id != id:
raise CustomException(msg="更新失败,名称重复")
if data.code is not None:
exist = await PackageCRUD(self.auth, self.db).get(code=data.code)
if exist and exist.id != id:
raise CustomException(msg="更新失败,编码重复")
if data.status is not None and data.status == 1 and obj.status == 0:
await self.disable_cascade(package_id=id)
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="删除失败,删除对象不能为空")
for pid in ids:
stmt = select(func.count()).select_from(TenantModel).where(TenantModel.package_id == pid)
result = await self.db.execute(stmt)
count = result.scalar()
if count and count > 0:
raise CustomException(msg=f"套餐 ID={pid} 已被 {count} 个租户使用,无法删除")
await PackageCRUD(self.auth, self.db).delete(ids=ids)
async def disable_cascade(self, package_id: int) -> None:
stmt = select(TenantModel.id, TenantModel.name).where(
TenantModel.package_id == package_id,
TenantModel.status == 0,
)
result = await self.db.execute(stmt)
rows = result.all()
if rows:
tenant_ids = [row[0] for row in rows]
logger.warning(f"套餐[{package_id}]已禁用,影响租户: {tenant_ids}")
async def get_menus(self, package_id: int) -> list[int]:
stmt = select(PackageMenuModel.menu_id).where(PackageMenuModel.package_id == package_id)
result = await self.db.execute(stmt)
return [row[0] for row in result.all()]
async def set_menus(self, package_id: int, data: PackageMenuSetSchema) -> None:
await self.db.execute(sa.delete(PackageMenuModel).where(PackageMenuModel.package_id == package_id))
for menu_id in data.menu_ids:
self.db.add(PackageMenuModel(package_id=package_id, menu_id=menu_id))
await self.db.flush()
logger.info(f"套餐[{package_id}]菜单权限已设置, count={len(data.menu_ids)}")
async def get_package_menu_ids(self, package_id: int) -> list[int]:
stmt = select(PackageMenuModel.menu_id).where(PackageMenuModel.package_id == package_id)
result = await self.db.execute(stmt)
return [row[0] for row in result.all()]
async def get_tenant_available_menu_ids(self, tenant_id: int) -> list[int]:
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 self.db.execute(menu_stmt)
return [row[0] for row in result.all()]
stmt = select(TenantModel).where(TenantModel.id == tenant_id).limit(1)
result = await self.db.execute(stmt)
tenant = result.scalar_one_or_none()
if not tenant:
return []
if not tenant.package_id:
return []
pkg_stmt = select(PackageModel.status).where(PackageModel.id == tenant.package_id).limit(1)
pkg_result = await self.db.execute(pkg_stmt)
pkg_status = pkg_result.scalar_one_or_none()
if pkg_status != 0:
return []
menu_stmt = select(PackageMenuModel.menu_id).where(PackageMenuModel.package_id == tenant.package_id)
result = await self.db.execute(menu_stmt)
return [row[0] for row in result.all()]