chore: 完成多批次代码优化与重构

- 重构工作流模块目录结构,迁移代码文件
- 修复类型断言空值安全问题,添加 ! 操作符
- 优化样式类名,替换 flex-cc 为标准 flex 工具类
- 更新路由标签简化文案,移除冗余注释
- 调整 ruff 配置,放宽行长度限制
- 更新 README 与多语言文案,优化项目描述
- 修复表单、图表组件的类型与样式问题
- 简化搜索表单、数据卡片的布局代码
This commit is contained in:
zhangtao
2026-06-20 05:31:46 +08:00
parent 82b742620b
commit d34d4a4c50
378 changed files with 4149 additions and 6375 deletions
@@ -1,4 +1,5 @@
"""订单与支付 Controller"""
from typing import Annotated
from fastapi import APIRouter, Body, Depends, Path, Query, Request
@@ -27,10 +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=["平台管理/租户订单"])
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:
@@ -44,7 +45,7 @@ def _make_bare_auth(db: AsyncSession) -> AuthSchema:
@OrderRouter.post("/create", summary="创建订单", response_model=ResponseSchema[OrderOutSchema])
async def order_create(
data: Annotated[OrderCreateSchema, Body()],
auth: Annotated[AuthSchema, Depends(AuthPermission(['module_platform:order:create']))],
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_platform:order:create"]))],
) -> JSONResponse:
"""
创建订单
@@ -62,7 +63,7 @@ async def order_create(
@OrderRouter.get("/detail/{order_id}", summary="订单详情", response_model=ResponseSchema[OrderOutSchema])
async def order_detail(
order_id: Annotated[int, Path(ge=1)],
auth: Annotated[AuthSchema, Depends(AuthPermission(['module_platform:order:query']))],
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_platform:order:query"]))],
) -> JSONResponse:
"""
订单详情
@@ -81,7 +82,7 @@ async def order_detail(
@OrderRouter.get("/list", summary="订单列表", response_model=ResponseSchema[PageResultSchema[OrderOutSchema]])
async def order_list(
auth: Annotated[AuthSchema, Depends(AuthPermission(['module_platform:order:query']))],
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_platform:order:query"]))],
tenant_id: Annotated[int | None, Query()] = None,
status: Annotated[int | None, Query()] = None,
order_type: Annotated[str | None, Query()] = None,
@@ -117,7 +118,7 @@ async def order_list(
@OrderRouter.post("/cancel/{order_id}", summary="取消订单", response_model=ResponseSchema[OrderStatusMessage])
async def order_cancel(
order_id: Annotated[int, Path(ge=1)],
auth: Annotated[AuthSchema, Depends(AuthPermission(['module_platform:order:update']))],
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_platform:order:update"]))],
) -> JSONResponse:
"""
取消订单
@@ -131,13 +132,14 @@ async def order_cancel(
result = await OrderService.cancel_order(auth, order_id)
return SuccessResponse(data=result, msg=result["message"])
# ─── 支付 ──────────────────────────────────────────────
@PaymentRouter.post("/pay/{order_id}", summary="创建支付(获取支付 URL/二维码)", response_model=ResponseSchema[PaymentCreateOut])
async def payment_create(
order_id: Annotated[int, Path(ge=1)],
auth: Annotated[AuthSchema, Depends(AuthPermission(['module_platform:order:update']))],
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_platform:order:update"]))],
request: Request,
method: Annotated[str, Query(description="支付渠道: alipay / wxpay(留空=自动)")] = "",
) -> JSONResponse:
@@ -160,6 +162,7 @@ async def payment_status(
result = await OrderService.check_payment_status(auth, order_id)
return SuccessResponse(data=result)
# ─── 支付回调 ──────────────────────────────────────────
@@ -201,12 +204,13 @@ async def payment_mock_callback(
logger.info(f"Mock 支付回调触发: order_id={order_id}")
return SuccessResponse(data=result, msg="模拟支付成功")
# ─── 支付记录 ──────────────────────────────────────────
@PaymentRouter.get("/record/list", summary="支付记录列表", response_model=ResponseSchema[PageResultSchema[PaymentRecordOutSchema]])
async def payment_record_list(
auth: Annotated[AuthSchema, Depends(AuthPermission(['module_platform:order:query']))],
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_platform:order:query"]))],
page: Annotated[int, Query(ge=1)] = 1,
page_size: Annotated[int, Query(ge=1, le=100)] = 20,
) -> JSONResponse:
@@ -231,12 +235,13 @@ async def payment_record_list(
)
return SuccessResponse(data=result)
# ─── 退款管理 ──────────────────────────────────────────
@RefundRouter.get("/list", summary="退款审核列表", response_model=ResponseSchema[PageResultSchema[RefundOutSchema]])
async def refund_list(
auth: Annotated[AuthSchema, Depends(AuthPermission(['module_platform:order:query']))],
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_platform:order:query"]))],
status: Annotated[int | None, Query(description="状态筛选")] = None,
page: Annotated[int, Query(ge=1)] = 1,
page_size: Annotated[int, Query(ge=1, le=100)] = 20,
@@ -267,7 +272,7 @@ async def refund_list(
@RefundRouter.put("/approve/{refund_id}", summary="批准退款", response_model=ResponseSchema[OrderStatusMessage])
async def refund_approve(
refund_id: Annotated[int, Path(ge=1)],
auth: Annotated[AuthSchema, Depends(AuthPermission(['module_platform:order:update']))],
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_platform:order:update"]))],
) -> JSONResponse:
"""
批准退款
@@ -279,7 +284,8 @@ async def refund_approve(
- JSONResponse: 包含批准结果的 JSON 响应。
"""
result = await RefundService.approve(
auth, refund_id,
auth,
refund_id,
auth.user.id if auth.user else 0,
auth.user.name if auth.user else "",
)
@@ -290,7 +296,7 @@ async def refund_approve(
async def refund_reject(
refund_id: Annotated[int, Path(ge=1)],
data: Annotated[RefundReviewSchema, Body()],
auth: Annotated[AuthSchema, Depends(AuthPermission(['module_platform:order:update']))],
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_platform:order:update"]))],
) -> JSONResponse:
"""
驳回退款
@@ -303,19 +309,22 @@ async def refund_reject(
- JSONResponse: 包含驳回结果的 JSON 响应。
"""
result = await RefundService.reject(
auth, refund_id,
auth.user.id if auth.user else 0, data,
auth,
refund_id,
auth.user.id if auth.user else 0,
data,
auth.user.name if auth.user else "",
)
return SuccessResponse(data=result, msg=result["message"])
# ─── 租户端订单 ────────────────────────────────────────
@TenantOrderRouter.post("/create", summary="租户端创建订单", response_model=ResponseSchema[OrderOutSchema])
async def tenant_order_create(
data: Annotated[OrderCreateSchema, Body()],
auth: Annotated[AuthSchema, Depends(AuthPermission(['tenant:order:create']))],
auth: Annotated[AuthSchema, Depends(AuthPermission(["tenant:order:create"]))],
) -> JSONResponse:
"""
租户端创建订单
@@ -336,7 +345,7 @@ async def tenant_order_create(
async def tenant_refund_apply(
order_id: Annotated[int, Path(ge=1)],
data: Annotated[RefundApplySchema, Body()],
auth: Annotated[AuthSchema, Depends(AuthPermission(['tenant:order:refund']))],
auth: Annotated[AuthSchema, Depends(AuthPermission(["tenant:order:refund"]))],
) -> JSONResponse:
"""
租户端申请退款
@@ -1,4 +1,5 @@
"""订单与支付 CRUD"""
from datetime import datetime
from typing import Any
@@ -43,6 +44,7 @@ class OrderCRUD(CRUDBase[OrderModel, OrderCreateInternalSchema, OrderUpdateInter
async def mark_refunded(self, order_id: int) -> None:
from sqlalchemy import update as sa_update
await self.db.execute(
sa_update(OrderModel)
.where(OrderModel.id == order_id)
@@ -77,9 +79,7 @@ class PaymentRecordCRUD(CRUDBase[PaymentRecordModel, PaymentRecordCreateSchema,
def __init__(self, auth: AuthSchema) -> None:
super().__init__(model=PaymentRecordModel, auth=auth)
async def query(
self, offset: int = 0, limit: int = 20
) -> tuple[list[PaymentRecordModel], int]:
async def query(self, offset: int = 0, limit: int = 20) -> tuple[list[PaymentRecordModel], int]:
result = await self.page(
order_by=[{"created_time": "desc"}],
offset=offset,
@@ -98,9 +98,7 @@ class RefundCRUD(CRUDBase[RefundModel, RefundCreateSchema, RefundUpdateSchema]):
async def get_by_order_id(self, order_id: int) -> RefundModel | None:
return await self.get(order_id=order_id)
async def query(
self, status: int | None = None, offset: int = 0, limit: int = 20
) -> tuple[list[RefundModel], int]:
async def query(self, status: int | None = None, offset: int = 0, limit: int = 20) -> tuple[list[RefundModel], int]:
result = await self.page(
search={"status": status},
order_by=[{"created_time": "desc"}],
@@ -1,4 +1,5 @@
"""订单与支付 Model"""
from datetime import datetime
from sqlalchemy import DateTime, ForeignKey, Integer, String, Text
@@ -29,6 +30,8 @@ class OrderModel(ModelMixin, TenantMixin):
pay_method: Mapped[str | None] = mapped_column(String(20), nullable=True, comment="alipay/wxpay")
pay_time: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, comment="支付时间")
expire_time: Mapped[datetime] = mapped_column(DateTime, nullable=False, comment="订单过期时间(15分钟)")
status: Mapped[int] = mapped_column(Integer, default=0, nullable=False, comment="状态(0:启动 1:停用)", index=True)
description: Mapped[str | None] = mapped_column(Text, default=None, nullable=True, comment="备注")
class PaymentRecordModel(ModelMixin, TenantMixin):
@@ -46,6 +49,8 @@ class PaymentRecordModel(ModelMixin, TenantMixin):
amount: Mapped[int] = mapped_column(Integer, nullable=False, comment="支付金额(分)")
raw_response: Mapped[str | None] = mapped_column(Text, nullable=True, comment="原始回调JSON")
pay_time: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, comment="支付完成时间")
status: Mapped[int] = mapped_column(Integer, default=0, nullable=False, comment="状态(0:启动 1:停用)", index=True)
description: Mapped[str | None] = mapped_column(Text, default=None, nullable=True, comment="备注")
class RefundModel(ModelMixin, TenantMixin):
@@ -65,3 +70,5 @@ class RefundModel(ModelMixin, TenantMixin):
reviewer_id: Mapped[int | None] = mapped_column(ForeignKey("sys_user.id"), nullable=True, comment="审核人")
review_time: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, comment="审核时间")
reject_reason: Mapped[str | None] = mapped_column(Text, nullable=True, comment="驳回原因")
status: Mapped[int] = mapped_column(Integer, default=0, nullable=False, comment="状态(0:启动 1:停用)", index=True)
description: Mapped[str | None] = mapped_column(Text, default=None, nullable=True, comment="备注")
@@ -1,10 +1,13 @@
"""订单与支付 Schema"""
from __future__ import annotations
from datetime import datetime
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
from app.common.enums import QueueEnum
from app.core.base_params import BaseQueryParam
from app.core.base_schema import BaseSchema
# ─── Internal Create/Update SchemasCRUD 层用,字段映射 Model 1:1)───
@@ -12,6 +15,7 @@ from app.core.base_schema import BaseSchema
class OrderCreateInternalSchema(BaseModel):
"""订单创建(内部 CRUD 用,包含所有业务字段)"""
order_no: str
tenant_id: int
package_id: int | None = None
@@ -27,6 +31,7 @@ class OrderCreateInternalSchema(BaseModel):
class OrderUpdateInternalSchema(BaseModel):
"""订单更新(内部 CRUD 用)"""
status: int | None = None
pay_method: str | None = None
pay_time: datetime | None = None
@@ -34,6 +39,7 @@ class OrderUpdateInternalSchema(BaseModel):
class PaymentRecordCreateSchema(BaseModel):
"""支付记录创建"""
order_id: int
transaction_id: str | None = None
pay_method: str
@@ -45,6 +51,7 @@ class PaymentRecordCreateSchema(BaseModel):
class RefundCreateSchema(BaseModel):
"""退款记录创建"""
order_id: int
refund_no: str
amount: int
@@ -54,6 +61,7 @@ class RefundCreateSchema(BaseModel):
class RefundUpdateSchema(BaseModel):
"""退款记录更新"""
status: int | None = None
reviewer_id: int | None = None
review_time: datetime | None = None
@@ -65,6 +73,7 @@ class RefundUpdateSchema(BaseModel):
class OrderCreateSchema(BaseModel):
"""创建订单(套餐或插件)"""
tenant_id: int
package_id: int | None = Field(default=None, description="套餐ID(套餐订单必填)")
plugin_id: int | None = Field(default=None, description="插件ID(插件订单必填)")
@@ -91,6 +100,7 @@ class OrderCreateSchema(BaseModel):
class OrderOutSchema(BaseSchema):
"""订单输出"""
order_no: str
tenant_id: int
package_id: int | None = None
@@ -105,17 +115,32 @@ class OrderOutSchema(BaseSchema):
model_config = ConfigDict(from_attributes=True)
class OrderQueryParam(BaseModel):
class OrderQueryParam(BaseQueryParam):
"""订单查询参数"""
tenant_id: int | None = None
status: int | None = None
order_type: str | None = None
def __init__(
self,
tenant_id: int | None = None,
status: int | None = None,
order_type: str | None = None,
*args,
**kwargs,
) -> None:
super().__init__(*args, **kwargs)
if tenant_id is not None:
self.tenant_id = (QueueEnum.eq.value, tenant_id)
if status is not None:
self.status = (QueueEnum.eq.value, status)
if order_type:
self.order_type = (QueueEnum.eq.value, order_type)
# ─── Payment ────────────────────────────────────────────
class PaymentCallbackSchema(BaseModel):
"""支付回调数据"""
transaction_id: str | None = None
amount: int
order_id: int | None = None
@@ -124,6 +149,7 @@ class PaymentCallbackSchema(BaseModel):
class PaymentRecordOutSchema(BaseSchema):
"""支付记录输出"""
order_id: int
transaction_id: str | None = None
pay_method: str
@@ -135,6 +161,7 @@ class PaymentRecordOutSchema(BaseSchema):
class PaymentCreateOut(BaseModel):
"""创建支付结果"""
pay_url: str
qr_code_url: str
trade_no: str
@@ -145,6 +172,7 @@ class PaymentCreateOut(BaseModel):
class PaymentStatusOut(BaseModel):
"""支付状态查询结果"""
exists: bool
order_id: int | None = None
status: int | None = None
@@ -155,6 +183,7 @@ class PaymentStatusOut(BaseModel):
class OrderStatusMessage(BaseModel):
"""订单/退款操作结果消息"""
id: int
status: int
message: str
@@ -162,18 +191,22 @@ class OrderStatusMessage(BaseModel):
# ─── Refund ─────────────────────────────────────────────
class RefundApplySchema(BaseModel):
"""退款申请"""
reason: str = Field(min_length=1, max_length=500)
class RefundReviewSchema(BaseModel):
"""退款审核"""
reject_reason: str | None = Field(default=None, max_length=500)
class RefundOutSchema(BaseSchema):
"""退款记录输出"""
order_id: int
refund_no: str
amount: int
@@ -1,4 +1,5 @@
"""订单与支付 Service"""
import random
from datetime import datetime, timedelta
@@ -45,7 +46,6 @@ def _generate_refund_no() -> str:
class OrderService:
@staticmethod
async def create_order(auth: AuthSchema, data: OrderCreateSchema, amount: int | None = None) -> OrderOutSchema:
"""创建订单
@@ -56,10 +56,12 @@ class OrderService:
if amount is None:
if data.order_type == "plugin":
from app.api.v1.module_platform.plugin.model import PluginModel
plugin = await auth.db.get(PluginModel, data.plugin_id)
amount = plugin.price if plugin and hasattr(plugin, "price") else 0
else:
from app.api.v1.module_platform.package.model import PackageModel
pkg = await auth.db.get(PackageModel, data.package_id)
amount = pkg.price if pkg and hasattr(pkg, "price") else 0
@@ -92,12 +94,13 @@ class OrderService:
return OrderOutSchema.model_validate(order) if order else None
@staticmethod
async def get_list(
auth: AuthSchema, params: OrderQueryParam, offset: int, limit: int
) -> tuple[list, int]:
async def get_list(auth: AuthSchema, params: OrderQueryParam, offset: int, limit: int) -> tuple[list, int]:
rows, total = await OrderCRUD(auth).query(
tenant_id=params.tenant_id, status=params.status,
order_type=params.order_type, offset=offset, limit=limit,
tenant_id=params.tenant_id,
status=params.status,
order_type=params.order_type,
offset=offset,
limit=limit,
)
items = [OrderOutSchema.model_validate(r) for r in rows]
return items, total
@@ -135,11 +138,8 @@ class OrderService:
class PaymentService:
@staticmethod
async def create_payment(
auth: AuthSchema, order_id: int, method: str, notify_base_url: str
) -> PaymentCreateOut:
async def create_payment(auth: AuthSchema, order_id: int, method: str, notify_base_url: str) -> PaymentCreateOut:
"""创建支付(调用支付网关)"""
from app.api.v1.module_platform.package.model import PackageModel
@@ -153,6 +153,7 @@ class PaymentService:
if order.order_type == "plugin":
from app.api.v1.module_platform.plugin.model import PluginModel
plugin = await auth.db.get(PluginModel, order.plugin_id)
subject = f"FastapiAdmin - 插件 {plugin.name}" if plugin else "FastapiAdmin 插件"
else:
@@ -178,9 +179,7 @@ class PaymentService:
)
@staticmethod
async def handle_callback(
auth: AuthSchema, method: str, callback_data: dict
) -> dict:
async def handle_callback(auth: AuthSchema, method: str, callback_data: dict) -> dict:
"""处理支付回调"""
gateway = create_payment_gateway(method)
callback_result = await gateway.verify_callback(callback_data)
@@ -230,10 +229,7 @@ class PaymentService:
order.order_type = otype
await PaymentService._activate_tenant_package(auth, order)
logger.info(
f"支付回调处理完成: order_id={oid} method={method} "
f"tenant_id={tid} type={otype}"
)
logger.info(f"支付回调处理完成: order_id={oid} method={method} tenant_id={tid} type={otype}")
return {"order_id": oid, "status": 1, "message": "支付成功"}
@staticmethod
@@ -296,10 +292,12 @@ class PaymentService:
return
result = await auth.db.execute(
select(TenantPluginModel).where(
select(TenantPluginModel)
.where(
TenantPluginModel.tenant_id == order.tenant_id,
TenantPluginModel.plugin_id == order.plugin_id,
).limit(1)
)
.limit(1)
)
existing = result.scalar_one_or_none()
if existing:
@@ -318,14 +316,13 @@ class PaymentService:
logger.info(f"租户[{order.tenant_id}]已购买插件[{plugin.name}]")
from app.api.v1.module_platform.tenant.model import TenantModel
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="购买")
@staticmethod
async def _check_downgrade_quota(
auth: AuthSchema, tenant_id: int, new_pkg: object
) -> None:
async def _check_downgrade_quota(auth: AuthSchema, tenant_id: int, new_pkg: object) -> None:
"""降级前检查:租户当前资源数是否超过新套餐限额"""
from sqlalchemy import func, select
@@ -342,21 +339,21 @@ class PaymentService:
for label, (model, limit) in checks.items():
if limit <= 0:
continue
count_stmt = select(func.count()).select_from(model).where(
model.tenant_id == tenant_id,
model.is_deleted.is_(False),
count_stmt = (
select(func.count())
.select_from(model)
.where(
model.tenant_id == tenant_id,
model.is_deleted.is_(False),
)
)
result = await auth.db.execute(count_stmt)
current = result.scalar() or 0
if current > limit:
raise CustomException(
msg=f"降级失败:当前租户已有 {current}{label},超过目标套餐限额 {limit}"
)
raise CustomException(msg=f"降级失败:当前租户已有 {current}{label},超过目标套餐限额 {limit}")
@staticmethod
async def _send_order_email(
order: "OrderModel", product: object, tenant: object, order_type_label: str = ""
) -> None:
async def _send_order_email(order: "OrderModel", product: object, tenant: object, order_type_label: str = "") -> None:
"""发送购买确认邮件(失败静默降级)"""
try:
from app.api.v1.module_platform.email.service import EmailSendService
@@ -392,16 +389,13 @@ class PaymentService:
pass # 邮件发送失败不阻塞业务流程
@staticmethod
async def get_records(
auth: AuthSchema, offset: int, limit: int
) -> tuple[list, int]:
async def get_records(auth: AuthSchema, offset: int, limit: int) -> tuple[list, int]:
rows, total = await PaymentRecordCRUD(auth).query(offset, limit)
items = [PaymentRecordOutSchema.model_validate(r) for r in rows]
return items, total
class RefundService:
@staticmethod
async def apply(auth: AuthSchema, data: RefundApplySchema, order_id: int) -> RefundOutSchema:
o_crud = OrderCRUD(auth)
@@ -428,16 +422,13 @@ class RefundService:
return RefundOutSchema.model_validate(refund)
@staticmethod
async def get_list(
auth: AuthSchema, status: int | None, offset: int, limit: int
) -> tuple[list, int]:
async def get_list(auth: AuthSchema, status: int | None, offset: int, limit: int) -> tuple[list, int]:
rows, total = await RefundCRUD(auth).query(status, offset, limit)
items = [RefundOutSchema.model_validate(r) for r in rows]
return items, total
@staticmethod
async def approve(auth: AuthSchema, refund_id: int, reviewer_id: int,
operator_name: str = "") -> OrderStatusMessage:
async def approve(auth: AuthSchema, refund_id: int, reviewer_id: int, operator_name: str = "") -> OrderStatusMessage:
crud = RefundCRUD(auth)
refund = await crud.get_by_id(refund_id)
if not refund:
@@ -453,8 +444,11 @@ class RefundService:
@staticmethod
async def reject(
auth: AuthSchema, refund_id: int, reviewer_id: int,
data: RefundReviewSchema, operator_name: str = "",
auth: AuthSchema,
refund_id: int,
reviewer_id: int,
data: RefundReviewSchema,
operator_name: str = "",
) -> OrderStatusMessage:
crud = RefundCRUD(auth)
refund = await crud.get_by_id(refund_id)