Files
kami_apple_exchage/backend/app/api/v1/orders.py
danial 8bc8e1c664 feat(links): 实现基于权重的轮询算法和链接管理功能
- 新增链接权重字段,支持1-100范围设置
- 修改轮询算法为基于权重的选择机制
- 更新链接API接口返回统一使用LinkInfo模型
- 添加更新链接权重的PATCH端点
- 调整链接仓库查询逻辑,只包含激活状态链接
- 迁移链接相关Pydantic模型到task模块统一管理
- 修改分页响应格式为通用PaginatedResponse包装
- 禁用OpenTelemetry监控配置
2025-09-30 17:02:02 +08:00

248 lines
9.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
订单管理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)}",
)