mirror of
https://git.oceanpay.cc/danial/kami_apple_exchage.git
synced 2025-12-18 22:29:09 +00:00
- 新增链接权重字段,支持1-100范围设置 - 修改轮询算法为基于权重的选择机制 - 更新链接API接口返回统一使用LinkInfo模型 - 添加更新链接权重的PATCH端点 - 调整链接仓库查询逻辑,只包含激活状态链接 - 迁移链接相关Pydantic模型到task模块统一管理 - 修改分页响应格式为通用PaginatedResponse包装 - 禁用OpenTelemetry监控配置
248 lines
9.0 KiB
Python
248 lines
9.0 KiB
Python
"""
|
||
订单管理API端点
|
||
"""
|
||
|
||
import traceback
|
||
from urllib.parse import quote
|
||
|
||
from fastapi import APIRouter, Depends, HTTPException, Response, status
|
||
from fastapi.responses import StreamingResponse
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
||
from app.core.database import get_async_db
|
||
from app.core.log import get_logger
|
||
from app.models.orders import OrderStatus
|
||
from app.schemas.order import (
|
||
OrderDetailResponse,
|
||
OrderStatsResponse,
|
||
)
|
||
from app.schemas.task import CardInfo, UserInfo, LinkInfo
|
||
from app.services.order_business_service import OrderService
|
||
|
||
router = APIRouter()
|
||
logger = get_logger(__name__)
|
||
|
||
|
||
@router.get("/stats", summary="获取订单统计", response_model=OrderStatsResponse)
|
||
async def get_order_stats(
|
||
db: AsyncSession = Depends(get_async_db),
|
||
) -> OrderStatsResponse:
|
||
"""获取订单统计信息"""
|
||
try:
|
||
result = await OrderService(db).get_order_statistics()
|
||
return result
|
||
except Exception as e:
|
||
logger.error(f"获取订单统计失败: {traceback.format_exc()}", exc_info=True)
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail=f"获取订单统计失败: {str(e)}",
|
||
)
|
||
|
||
|
||
@router.get("/list", summary="获取订单列表")
|
||
async def get_orders(
|
||
skip: int = 0,
|
||
limit: int = 100,
|
||
status_filter: OrderStatus | None = None,
|
||
db: AsyncSession = Depends(get_async_db),
|
||
) -> list[OrderDetailResponse]:
|
||
"""获取订单列表"""
|
||
try:
|
||
orders = await OrderService(db).get_order_list(skip, limit, status_filter)
|
||
# 按更新时间逆向排序
|
||
orders = sorted(orders, key=lambda x: x.updated_at, reverse=True)
|
||
# Convert SQLAlchemy models to Pydantic response schemas
|
||
result = []
|
||
for order in orders:
|
||
link_response = LinkInfo(
|
||
weight=order.links.weight,
|
||
id=order.links.id,
|
||
url=order.links.url,
|
||
amount=order.links.amount,
|
||
created_at=order.links.created_at.isoformat(),
|
||
updated_at=order.links.updated_at.isoformat(),
|
||
status=order.links.status,
|
||
)
|
||
gift_cards = []
|
||
for gc in order.gift_cards_list:
|
||
gift_cards.append(
|
||
CardInfo(
|
||
id=gc.id,
|
||
card_code=gc.card_code,
|
||
card_value=gc.card_value,
|
||
status=gc.status.value,
|
||
failure_reason=gc.failure_reason,
|
||
order_id=gc.order_id,
|
||
created_at=gc.created_at.isoformat(),
|
||
updated_at=gc.updated_at.isoformat(),
|
||
)
|
||
)
|
||
|
||
user_data = UserInfo(
|
||
id=order.user_data.id,
|
||
email=order.user_data.email,
|
||
phone=order.user_data.phone,
|
||
created_at=order.user_data.created_at.isoformat(),
|
||
updated_at=order.user_data.updated_at.isoformat(),
|
||
first_name=order.user_data.first_name,
|
||
last_name=order.user_data.last_name,
|
||
street_address=order.user_data.street_address,
|
||
city=order.user_data.city,
|
||
state=order.user_data.state,
|
||
zip_code=order.user_data.zip_code,
|
||
)
|
||
|
||
# gift_cards is already built above, no need to reassign
|
||
order_detail = OrderDetailResponse(
|
||
id=order.id,
|
||
status=order.status,
|
||
created_at=order.created_at.isoformat(),
|
||
updated_at=order.updated_at.isoformat(),
|
||
final_order_url=order.final_order_url,
|
||
final_order_id=order.final_order_id,
|
||
failure_reason=order.failure_reason,
|
||
user_data_id=order.user_data_id,
|
||
links_id=order.links_id,
|
||
user_data=user_data,
|
||
links=link_response,
|
||
gift_cards=gift_cards,
|
||
)
|
||
result.append(order_detail)
|
||
return result
|
||
except ValueError as e:
|
||
logger.warning(f"获取订单列表失败 - 参数验证错误: {str(e)}")
|
||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
||
except Exception as e:
|
||
logger.error(f"获取订单列表失败: {traceback.format_exc()}")
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail=f"获取订单列表失败: {str(e)}",
|
||
)
|
||
|
||
|
||
@router.get(
|
||
"/export",
|
||
summary="导出订单数据",
|
||
response_class=Response,
|
||
responses={
|
||
200: {
|
||
"content": {
|
||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": {
|
||
"schema": {"type": "string", "format": "binary"}
|
||
}
|
||
},
|
||
"description": "Excel文件下载",
|
||
}
|
||
},
|
||
)
|
||
async def export_orders(
|
||
status_filter: str | None = None,
|
||
db: AsyncSession = Depends(get_async_db),
|
||
):
|
||
"""导出订单数据为Excel文件"""
|
||
try:
|
||
excel_content = OrderService(
|
||
db,
|
||
).export_orders_to_excel(status_filter)
|
||
logger.info(f"导出订单数据成功")
|
||
|
||
# 修复Unicode编码错误:正确处理中文文件名
|
||
filename = "订单数据.xlsx"
|
||
quoted_filename = quote(filename)
|
||
|
||
return StreamingResponse(
|
||
content=excel_content,
|
||
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||
headers={
|
||
"Content-Disposition": f'attachment; filename="{quoted_filename}"',
|
||
},
|
||
)
|
||
|
||
except ValueError as e:
|
||
logger.warning(
|
||
f"导出订单数据失败 - 参数验证错误: {str(traceback.format_exc())}"
|
||
)
|
||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
||
except Exception as e:
|
||
logger.error(f"导出订单数据失败: {e}", exc_info=True)
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail=f"导出订单数据失败: {str(e)}",
|
||
)
|
||
|
||
|
||
@router.get("/{order_id}", summary="获取订单详情", response_model=OrderDetailResponse)
|
||
async def get_order(
|
||
order_id: str, db: AsyncSession = Depends(get_async_db)
|
||
) -> OrderDetailResponse:
|
||
"""获取订单详情"""
|
||
try:
|
||
order = await OrderService(db).get_order_detail(order_id)
|
||
|
||
if not order:
|
||
logger.warning(f"获取订单详情失败 - 订单不存在: {order_id}")
|
||
raise HTTPException(
|
||
status_code=status.HTTP_404_NOT_FOUND, detail="订单不存在"
|
||
)
|
||
|
||
# Convert SQLAlchemy model to Pydantic response schema
|
||
gift_cards = []
|
||
for gc in order.gift_cards_list:
|
||
gift_cards.append(
|
||
CardInfo(
|
||
id=gc.id,
|
||
card_code=gc.card_code,
|
||
card_value=gc.card_value,
|
||
status=gc.status.value,
|
||
failure_reason=gc.failure_reason,
|
||
order_id=gc.order_id,
|
||
created_at=gc.created_at.isoformat(),
|
||
updated_at=gc.updated_at.isoformat(),
|
||
)
|
||
)
|
||
order_detail = OrderDetailResponse(
|
||
id=order.id,
|
||
status=order.status,
|
||
created_at=order.created_at.isoformat(),
|
||
updated_at=order.updated_at.isoformat(),
|
||
final_order_url=order.final_order_url,
|
||
final_order_id=order.final_order_id,
|
||
failure_reason=order.failure_reason,
|
||
user_data_id=order.user_data_id,
|
||
links_id=order.links_id,
|
||
user_data=UserInfo(
|
||
id=order.user_data.id,
|
||
email=order.user_data.email,
|
||
phone=order.user_data.phone,
|
||
created_at=order.user_data.created_at.isoformat(),
|
||
updated_at=order.user_data.updated_at.isoformat(),
|
||
first_name=order.user_data.first_name,
|
||
last_name=order.user_data.last_name,
|
||
street_address=order.user_data.street_address,
|
||
city=order.user_data.city,
|
||
state=order.user_data.state,
|
||
zip_code=order.user_data.zip_code,
|
||
),
|
||
links=LinkInfo(
|
||
id=order.links.id,
|
||
created_at=order.links.created_at.isoformat(),
|
||
updated_at=order.links.updated_at.isoformat(),
|
||
url=order.links.url,
|
||
amount=order.links.amount,
|
||
status=order.links.status,
|
||
),
|
||
gift_cards=gift_cards,
|
||
)
|
||
|
||
logger.info(f"获取订单详情成功: {order_id}")
|
||
return order_detail
|
||
|
||
except HTTPException:
|
||
raise
|
||
except Exception as e:
|
||
logger.error(f"获取订单详情失败: {e}", order_id=order_id, exc_info=True)
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail=f"获取订单详情失败: {str(e)}",
|
||
)
|