mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-09-23 13:13:09 +00:00
refactor: 完成项目大规模代码重构与依赖清理
这是一次综合性的重构更新,包含以下主要变更: 1. 升级Python版本到3.12,更新依赖配置 2. 替换旧的.j2模板为.jinja2格式,新增代码生成模板 3. 重构权限过滤策略,更新权限枚举与模型配置 4. 移除Prefect依赖,替换为自研拓扑并行执行引擎 5. 重构认证与上下文管理,拆分租户/请求上下文 6. 简化响应模型、CRUD与服务层代码 7. 清理废弃的支付网关模块,重构订单定时任务 8. 更新在线用户、监控等模块的接口与路由 9. 优化邮件模板与工具类,新增邮件模板文件 10. 修复数据库会话配置与类型提示
This commit is contained in:
@@ -28,16 +28,10 @@ from .schema import (
|
||||
)
|
||||
from .service import OrderService, PaymentService, RefundService
|
||||
|
||||
OrderRouter = APIRouter(route_class=OperationLogRoute, prefix="/order", tags=["订单管理"])
|
||||
PaymentRouter = APIRouter(route_class=OperationLogRoute, prefix="/payment", tags=["支付管理"])
|
||||
RefundRouter = APIRouter(route_class=OperationLogRoute, prefix="/refund", tags=["退款管理"])
|
||||
TenantOrderRouter = APIRouter(route_class=OperationLogRoute, prefix="/order", tags=["租户订单"])
|
||||
|
||||
|
||||
def _make_bare_auth(db: AsyncSession) -> AuthSchema:
|
||||
"""构造无用户上下文的 AuthSchema(支付回调等场景)"""
|
||||
return AuthSchema(db=db, check_data_scope=False)
|
||||
|
||||
OrderRouter = APIRouter(route_class=OperationLogRoute, prefix="/order", tags=["平台管理", "订单管理"])
|
||||
PaymentRouter = APIRouter(route_class=OperationLogRoute, prefix="/payment", tags=["平台管理", "支付管理"])
|
||||
RefundRouter = APIRouter(route_class=OperationLogRoute, prefix="/refund", tags=["平台管理", "退款管理"])
|
||||
TenantOrderRouter = APIRouter(route_class=OperationLogRoute, prefix="/order", tags=["平台管理", "租户订单"])
|
||||
|
||||
@OrderRouter.post(
|
||||
"/create",
|
||||
@@ -48,20 +42,9 @@ async def order_create_controller(
|
||||
data: Annotated[OrderCreateSchema, Body()],
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_platform:order:create"]))],
|
||||
) -> JSONResponse:
|
||||
"""
|
||||
创建订单
|
||||
|
||||
参数:
|
||||
- data (OrderCreateSchema): 订单创建参数。
|
||||
- auth (AuthSchema): 认证信息模型。
|
||||
|
||||
返回:
|
||||
- JSONResponse: 包含订单详情的 JSON 响应。
|
||||
"""
|
||||
result = await OrderService.create_order(auth=auth, data=data)
|
||||
return SuccessResponse(data=result, msg="订单创建成功")
|
||||
|
||||
|
||||
@OrderRouter.get(
|
||||
"/detail/{order_id}",
|
||||
summary="订单详情",
|
||||
@@ -71,22 +54,11 @@ async def order_detail_controller(
|
||||
order_id: Annotated[int, Path(ge=1)],
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_platform:order:query"]))],
|
||||
) -> JSONResponse:
|
||||
"""
|
||||
订单详情
|
||||
|
||||
参数:
|
||||
- order_id (int): 订单 ID。
|
||||
- auth (AuthSchema): 认证信息模型。
|
||||
|
||||
返回:
|
||||
- JSONResponse: 包含订单详情的 JSON 响应。
|
||||
"""
|
||||
order = await OrderService.get_detail(auth=auth, order_id=order_id)
|
||||
if not order:
|
||||
raise HTTPException(status_code=404, detail="订单不存在")
|
||||
return SuccessResponse(data=order)
|
||||
|
||||
|
||||
@OrderRouter.get(
|
||||
"/list",
|
||||
summary="订单列表",
|
||||
@@ -100,20 +72,6 @@ async def order_list_controller(
|
||||
page: Annotated[int, Query(ge=1)] = 1,
|
||||
page_size: Annotated[int, Query(ge=1, le=100)] = 20,
|
||||
) -> JSONResponse:
|
||||
"""
|
||||
订单列表
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息模型。
|
||||
- tenant_id (int | None): 租户 ID 筛选。
|
||||
- status (int | None): 状态筛选。
|
||||
- order_type (str | None): 订单类型筛选。
|
||||
- page (int): 页码。
|
||||
- page_size (int): 每页条数。
|
||||
|
||||
返回:
|
||||
- JSONResponse: 包含分页订单列表的 JSON 响应。
|
||||
"""
|
||||
params = OrderQueryParam(tenant_id=tenant_id, status=status, order_type=order_type)
|
||||
offset = (page - 1) * page_size
|
||||
items, total = await OrderService.get_list(auth=auth, params=params, offset=offset, limit=page_size)
|
||||
@@ -126,7 +84,6 @@ async def order_list_controller(
|
||||
)
|
||||
return SuccessResponse(data=result)
|
||||
|
||||
|
||||
@OrderRouter.post(
|
||||
"/cancel/{order_id}",
|
||||
summary="取消订单",
|
||||
@@ -136,20 +93,9 @@ async def order_cancel_controller(
|
||||
order_id: Annotated[int, Path(ge=1)],
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_platform:order:update"]))],
|
||||
) -> JSONResponse:
|
||||
"""
|
||||
取消订单
|
||||
|
||||
参数:
|
||||
- order_id (int): 订单 ID。
|
||||
- auth (AuthSchema): 认证信息模型。
|
||||
|
||||
返回:
|
||||
- JSONResponse: 包含取消结果的 JSON 响应。
|
||||
"""
|
||||
result = await OrderService.cancel_order(auth=auth, order_id=order_id)
|
||||
return SuccessResponse(data=result, msg=result["message"])
|
||||
|
||||
|
||||
@PaymentRouter.post(
|
||||
"/pay/{order_id}",
|
||||
summary="创建支付(获取支付 URL/二维码)",
|
||||
@@ -161,25 +107,10 @@ async def payment_create_controller(
|
||||
request: Request,
|
||||
method: Annotated[str, Query(description="支付渠道: alipay / wxpay(留空=自动)")] = "",
|
||||
) -> JSONResponse:
|
||||
"""
|
||||
创建支付
|
||||
|
||||
调用支付网关生成支付 URL(H5 跳转)或二维码(Native 扫码)。
|
||||
|
||||
参数:
|
||||
- order_id (int): 订单 ID。
|
||||
- auth (AuthSchema): 认证信息模型。
|
||||
- request (Request): FastAPI 请求对象。
|
||||
- method (str): 支付渠道。
|
||||
|
||||
返回:
|
||||
- JSONResponse: 包含支付信息的 JSON 响应。
|
||||
"""
|
||||
base_url = str(request.base_url).rstrip("/")
|
||||
result = await PaymentService.create_payment(auth=auth, order_id=order_id, method=method, notify_base_url=base_url)
|
||||
return SuccessResponse(data=result, msg="支付信息已生成")
|
||||
|
||||
|
||||
@PaymentRouter.get(
|
||||
"/status/{order_id}",
|
||||
summary="查询支付状态(供前端轮询)",
|
||||
@@ -189,21 +120,10 @@ async def payment_status_controller(
|
||||
order_id: Annotated[int, Path(ge=1)],
|
||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||
) -> JSONResponse:
|
||||
"""
|
||||
查询支付状态(无需登录,前端轮询用)
|
||||
|
||||
参数:
|
||||
- order_id (int): 订单 ID。
|
||||
- db (AsyncSession): 数据库会话。
|
||||
|
||||
返回:
|
||||
- JSONResponse: 包含支付状态的 JSON 响应。
|
||||
"""
|
||||
auth = _make_bare_auth(db)
|
||||
auth = AuthSchema(db=db, check_data_scope=False)
|
||||
result = await OrderService.check_payment_status(auth=auth, order_id=order_id)
|
||||
return SuccessResponse(data=result)
|
||||
|
||||
|
||||
@PaymentRouter.post(
|
||||
"/callback/{method}",
|
||||
summary="支付回调(统一入口)",
|
||||
@@ -214,19 +134,8 @@ async def payment_callback_controller(
|
||||
data: Annotated[dict, Body()],
|
||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||
) -> JSONResponse:
|
||||
"""
|
||||
接收支付网关的异步通知(无需认证)
|
||||
|
||||
参数:
|
||||
- method (str): 支付渠道。
|
||||
- data (dict): 支付回调原始数据。
|
||||
- db (AsyncSession): 数据库会话。
|
||||
|
||||
返回:
|
||||
- JSONResponse: 包含处理结果的 JSON 响应。
|
||||
"""
|
||||
try:
|
||||
auth = _make_bare_auth(db)
|
||||
auth = AuthSchema(db=db, check_data_scope=False)
|
||||
result = await PaymentService.handle_callback(auth=auth, method=method, callback_data=data)
|
||||
logger.info(f"支付回调处理成功: {result}")
|
||||
return SuccessResponse(data=result)
|
||||
@@ -234,7 +143,6 @@ async def payment_callback_controller(
|
||||
logger.warning(f"支付回调处理失败: {e}")
|
||||
return SuccessResponse(data={"message": str(e)}, code=400)
|
||||
|
||||
|
||||
@PaymentRouter.post(
|
||||
"/mock/callback",
|
||||
summary="Mock 支付回调(开发环境触发模拟支付)",
|
||||
@@ -244,21 +152,11 @@ async def payment_mock_callback_controller(
|
||||
order_id: Annotated[int, Body(ge=1, description="订单 ID")],
|
||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||
) -> JSONResponse:
|
||||
"""
|
||||
开发环境下手动触发模拟支付成功回调
|
||||
|
||||
参数:
|
||||
- order_id (int): 订单 ID。
|
||||
- db (AsyncSession): 数据库会话。
|
||||
|
||||
返回:
|
||||
- JSONResponse: 包含模拟支付结果的 JSON 响应。
|
||||
"""
|
||||
from app.core.payment import get_mock_gateway
|
||||
from app.utils.payment import get_mock_gateway
|
||||
|
||||
from .crud import OrderCRUD
|
||||
|
||||
auth = _make_bare_auth(db)
|
||||
auth = AuthSchema(db=db, check_data_scope=False)
|
||||
order = await OrderCRUD(auth).get_by_id(order_id)
|
||||
if not order:
|
||||
raise HTTPException(status_code=404, detail="订单不存在")
|
||||
@@ -269,7 +167,6 @@ async def payment_mock_callback_controller(
|
||||
logger.info(f"Mock 支付回调触发: order_id={order_id}")
|
||||
return SuccessResponse(data=result, msg="模拟支付成功")
|
||||
|
||||
|
||||
@PaymentRouter.get(
|
||||
"/record/list",
|
||||
summary="支付记录列表",
|
||||
@@ -280,17 +177,6 @@ async def payment_record_list_controller(
|
||||
page: Annotated[int, Query(ge=1)] = 1,
|
||||
page_size: Annotated[int, Query(ge=1, le=100)] = 20,
|
||||
) -> JSONResponse:
|
||||
"""
|
||||
支付记录列表
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息模型。
|
||||
- page (int): 页码。
|
||||
- page_size (int): 每页条数。
|
||||
|
||||
返回:
|
||||
- JSONResponse: 包含分页支付记录列表的 JSON 响应。
|
||||
"""
|
||||
offset = (page - 1) * page_size
|
||||
items, total = await PaymentService.get_records(auth=auth, offset=offset, limit=page_size)
|
||||
result = PageResultSchema(
|
||||
@@ -302,7 +188,6 @@ async def payment_record_list_controller(
|
||||
)
|
||||
return SuccessResponse(data=result)
|
||||
|
||||
|
||||
@RefundRouter.get(
|
||||
"/list",
|
||||
summary="退款审核列表",
|
||||
@@ -314,18 +199,6 @@ async def refund_list_controller(
|
||||
page: Annotated[int, Query(ge=1)] = 1,
|
||||
page_size: Annotated[int, Query(ge=1, le=100)] = 20,
|
||||
) -> JSONResponse:
|
||||
"""
|
||||
退款审核列表
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息模型。
|
||||
- status (int | None): 状态筛选。
|
||||
- page (int): 页码。
|
||||
- page_size (int): 每页条数。
|
||||
|
||||
返回:
|
||||
- JSONResponse: 包含分页退款列表的 JSON 响应。
|
||||
"""
|
||||
offset = (page - 1) * page_size
|
||||
items, total = await RefundService.get_list(auth=auth, status=status, offset=offset, limit=page_size)
|
||||
result = PageResultSchema(
|
||||
@@ -337,7 +210,6 @@ async def refund_list_controller(
|
||||
)
|
||||
return SuccessResponse(data=result)
|
||||
|
||||
|
||||
@RefundRouter.put(
|
||||
"/approve/{refund_id}",
|
||||
summary="批准退款",
|
||||
@@ -347,16 +219,6 @@ async def refund_approve_controller(
|
||||
refund_id: Annotated[int, Path(ge=1)],
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_platform:order:update"]))],
|
||||
) -> JSONResponse:
|
||||
"""
|
||||
批准退款
|
||||
|
||||
参数:
|
||||
- refund_id (int): 退款申请 ID。
|
||||
- auth (AuthSchema): 认证信息模型。
|
||||
|
||||
返回:
|
||||
- JSONResponse: 包含批准结果的 JSON 响应。
|
||||
"""
|
||||
result = await RefundService.approve(
|
||||
auth=auth,
|
||||
refund_id=refund_id,
|
||||
@@ -365,7 +227,6 @@ async def refund_approve_controller(
|
||||
)
|
||||
return SuccessResponse(data=result, msg=result["message"])
|
||||
|
||||
|
||||
@RefundRouter.put(
|
||||
"/reject/{refund_id}",
|
||||
summary="驳回退款",
|
||||
@@ -376,17 +237,6 @@ async def refund_reject_controller(
|
||||
data: Annotated[RefundReviewSchema, Body()],
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_platform:order:update"]))],
|
||||
) -> JSONResponse:
|
||||
"""
|
||||
驳回退款
|
||||
|
||||
参数:
|
||||
- refund_id (int): 退款申请 ID。
|
||||
- data (RefundReviewSchema): 驳回原因。
|
||||
- auth (AuthSchema): 认证信息模型。
|
||||
|
||||
返回:
|
||||
- JSONResponse: 包含驳回结果的 JSON 响应。
|
||||
"""
|
||||
result = await RefundService.reject(
|
||||
auth=auth,
|
||||
refund_id=refund_id,
|
||||
@@ -396,7 +246,6 @@ async def refund_reject_controller(
|
||||
)
|
||||
return SuccessResponse(data=result, msg=result["message"])
|
||||
|
||||
|
||||
@TenantOrderRouter.post(
|
||||
"/create",
|
||||
summary="租户端创建订单",
|
||||
@@ -406,22 +255,11 @@ async def tenant_order_create_controller(
|
||||
data: Annotated[OrderCreateSchema, Body()],
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["tenant:order:create"]))],
|
||||
) -> JSONResponse:
|
||||
"""
|
||||
租户端创建订单
|
||||
|
||||
参数:
|
||||
- data (OrderCreateSchema): 订单创建参数。
|
||||
- auth (AuthSchema): 认证信息模型。
|
||||
|
||||
返回:
|
||||
- JSONResponse: 包含订单详情的 JSON 响应。
|
||||
"""
|
||||
if data.tenant_id != auth.tenant_id:
|
||||
raise HTTPException(status_code=403, detail="无权操作")
|
||||
result = await OrderService.create_order(auth=auth, data=data)
|
||||
return SuccessResponse(data=result, msg="订单创建成功")
|
||||
|
||||
|
||||
@TenantOrderRouter.post(
|
||||
"/refund/apply/{order_id}",
|
||||
summary="申请退款",
|
||||
@@ -432,16 +270,5 @@ async def tenant_refund_apply_controller(
|
||||
data: Annotated[RefundApplySchema, Body()],
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["tenant:order:refund"]))],
|
||||
) -> JSONResponse:
|
||||
"""
|
||||
租户端申请退款
|
||||
|
||||
参数:
|
||||
- order_id (int): 订单 ID。
|
||||
- data (RefundApplySchema): 退款申请参数。
|
||||
- auth (AuthSchema): 认证信息模型。
|
||||
|
||||
返回:
|
||||
- JSONResponse: 包含退款申请结果的 JSON 响应。
|
||||
"""
|
||||
result = await RefundService.apply(auth=auth, data=data, order_id=order_id)
|
||||
return SuccessResponse(data=result, msg="退款申请已提交")
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"""订单与支付 CRUD"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from app.core.base_crud import CRUDBase
|
||||
@@ -52,20 +51,6 @@ class OrderCRUD(CRUDBase[OrderModel, OrderCreateInternalSchema, OrderUpdateInter
|
||||
.values(status=3)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
async def cancel_expired_orders() -> None:
|
||||
"""定时任务:取消超时未支付订单"""
|
||||
from sqlalchemy import update as sa_update
|
||||
|
||||
from app.core.database import async_db_session
|
||||
from app.core.logger import logger
|
||||
|
||||
now = datetime.now()
|
||||
async with async_db_session() as session:
|
||||
async with session.begin():
|
||||
result = await session.execute(sa_update(OrderModel).where(OrderModel.status == 0).where(OrderModel.expire_time < now).where(OrderModel.is_deleted.is_(False)).values(status=2))
|
||||
logger.info(f"超时订单取消: 已取消 {result.rowcount} 条订单")
|
||||
|
||||
|
||||
class PaymentRecordCRUD(CRUDBase[PaymentRecordModel, PaymentRecordCreateSchema, Any]):
|
||||
"""支付记录 CRUD"""
|
||||
|
||||
@@ -8,7 +8,7 @@ from sqlalchemy import select
|
||||
from app.core.base_schema import AuthSchema
|
||||
from app.core.exceptions import CustomException
|
||||
from app.core.logger import logger
|
||||
from app.core.payment import create_payment_gateway
|
||||
from app.utils.payment import create_payment_gateway
|
||||
|
||||
from .crud import OrderCRUD, PaymentRecordCRUD, RefundCRUD
|
||||
from .model import OrderModel
|
||||
@@ -186,15 +186,23 @@ class OrderService:
|
||||
pay_time=order.pay_time.isoformat() if order.pay_time else None,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def cancel_expired_orders(cls) -> None:
|
||||
"""
|
||||
定时任务:取消超时未支付的订单
|
||||
@staticmethod
|
||||
async def cancel_expired_orders() -> None:
|
||||
from sqlalchemy import update as sa_update
|
||||
|
||||
返回:
|
||||
- None
|
||||
"""
|
||||
await OrderCRUD.cancel_expired_orders()
|
||||
from app.core.database import async_db_session
|
||||
|
||||
now = datetime.now()
|
||||
async with async_db_session() as session:
|
||||
async with session.begin():
|
||||
result = await session.execute(
|
||||
sa_update(OrderModel)
|
||||
.where(OrderModel.status == 0)
|
||||
.where(OrderModel.expire_time < now)
|
||||
.where(OrderModel.is_deleted == False) # noqa: E712
|
||||
.values(status=2)
|
||||
)
|
||||
logger.info(f"超时订单取消: 已取消 {result.rowcount} 条订单")
|
||||
|
||||
|
||||
class PaymentService:
|
||||
@@ -373,7 +381,7 @@ class PaymentService:
|
||||
await auth.db.flush()
|
||||
|
||||
if tenant.contact_email:
|
||||
await PaymentService._send_order_email(order, pkg, tenant)
|
||||
await PaymentService._send_order_email(auth, order, pkg, tenant)
|
||||
|
||||
@classmethod
|
||||
async def _activate_plugin(cls, auth: AuthSchema, order: OrderModel) -> None:
|
||||
@@ -422,7 +430,7 @@ class PaymentService:
|
||||
|
||||
tenant = await auth.db.get(TenantModel, order.tenant_id)
|
||||
if tenant and tenant.contact_email:
|
||||
await PaymentService._send_order_email(order, plugin, tenant, order_type_label="购买")
|
||||
await PaymentService._send_order_email(auth, order, plugin, tenant, order_type_label="购买")
|
||||
|
||||
@classmethod
|
||||
async def _check_downgrade_quota(cls, auth: AuthSchema, tenant_id: int, new_pkg: object) -> None:
|
||||
@@ -466,7 +474,7 @@ class PaymentService:
|
||||
raise CustomException(msg=f"降级失败:当前租户已有 {current} 个{label},超过目标套餐限额 {limit}")
|
||||
|
||||
@classmethod
|
||||
async def _send_order_email(cls, order: "OrderModel", product: object, tenant: object, order_type_label: str = "") -> None:
|
||||
async def _send_order_email(cls, auth: AuthSchema, order: "OrderModel", product: object, tenant: object, order_type_label: str = "") -> None:
|
||||
"""
|
||||
发送购买确认邮件(失败静默降级)
|
||||
|
||||
@@ -495,7 +503,7 @@ class PaymentService:
|
||||
product_name = getattr(product, "name", "") if product else ""
|
||||
amount_str = f"{order.amount / 100:.2f}" if order.amount else "0.00"
|
||||
|
||||
await EmailSendService.send_by_template(
|
||||
await EmailSendService(auth).send_by_template(
|
||||
to_email=tenant.contact_email,
|
||||
to_name=tenant.contact_name or tenant.name,
|
||||
template_code="order_confirmation",
|
||||
|
||||
Reference in New Issue
Block a user