mirror of
https://git.oceanpay.cc/danial/kami_apple_exchage.git
synced 2025-12-18 22:29:09 +00:00
- 新增 CODEBUDDY.md、GEMINI.md、GEMINI_CN.md 等项目文档 - 更新 Dockerfile 和其他配置文件 - 优化部分代码结构,如 orders.py、tasks.py 等 - 新增 .dockerignore 文件
470 lines
18 KiB
Python
470 lines
18 KiB
Python
"""
|
||
Apple订单处理服务
|
||
集成OptimizedAppleOrderProcessor业务流程
|
||
专为Celery Worker环境设计的分布式订单处理服务
|
||
"""
|
||
|
||
import asyncio
|
||
import traceback
|
||
from typing import Any
|
||
from datetime import datetime
|
||
from playwright.async_api import Page
|
||
|
||
from app.core.config import get_settings
|
||
from app.core.log import get_logger
|
||
from app.models.orders import OrderResultStatus
|
||
from app.core.state_manager import task_state_manager
|
||
from app.repositories.order_repository import OrderRepository
|
||
from app.core.database import db_manager
|
||
from app.services.gift_card_service import GiftCardService
|
||
from app.enums.task import OrderTaskStatus
|
||
from app.core.playwright_manager import DistributedPlaywrightManager
|
||
|
||
settings = get_settings()
|
||
logger = get_logger(__name__)
|
||
|
||
|
||
class AppleOrderProcessor:
|
||
"""
|
||
Apple订单处理器
|
||
集成了完整的Apple礼品卡订单处理流程
|
||
支持中断式礼品卡输入和实时进度跟踪
|
||
采用策略模式和责任链模式,提供清晰的代码结构
|
||
"""
|
||
|
||
def __init__(self, order_id: str):
|
||
self.order_id = order_id
|
||
self.task_id = f"order_{order_id}"
|
||
self.order = None
|
||
self.thread_prefix = f"[订单{order_id}]"
|
||
|
||
# Apple网站元素选择器配置
|
||
self.selectors = {
|
||
"add_to_cart": "#add-to-cart",
|
||
"zipcode_edit": "//button[@data-autom='bag-zipcode-edit-cold']",
|
||
"zipcode_input": ".form-textbox-input.form-textbox-number-input",
|
||
"zipcode_apply": "//button[@data-autom='bag-zipcode-apply']",
|
||
"checkout_other_payments": "shoppingCart.actions.checkoutOtherPayments",
|
||
"guest_login": "signIn.guestLogin.guestLogin",
|
||
"continue_shipping": "#rs-checkout-continue-button-bottom",
|
||
"first_name": "checkout.shipping.addressSelector.newAddress.address.firstName",
|
||
"last_name": "checkout.shipping.addressSelector.newAddress.address.lastName",
|
||
"street_address": "checkout.shipping.addressSelector.newAddress.address.street",
|
||
"email": "checkout.shipping.addressContactEmail.address.emailAddress",
|
||
"phone": "checkout.shipping.addressContactPhone.address.fullDaytimePhone",
|
||
"continue_selected_address": "checkout.shipping.addressVerification.selectedAddress.continueWithSelectedAddress",
|
||
"gift_card_reset": "checkout.billing.billingOptions.selectedBillingOptions.giftCard.giftCardInput.resetFields",
|
||
"gift_card_input": "checkout.billing.billingOptions.selectedBillingOptions.giftCard.giftCardInput.giftCard",
|
||
"apply_gift_card": "checkout.billing.billingOptions.selectedBillingOptions.giftCard.giftCardInput.applyGiftCard",
|
||
"continue_review": "rs-checkout-continue-button-bottom",
|
||
"place_order": "rs-checkout-continue-button-bottom",
|
||
"order_number": "#thankyou-container > div > div.rs-thankyou-headcontent > div > div > a",
|
||
}
|
||
|
||
# 失败指示器配置
|
||
self.failure_indicators = {
|
||
"Please use an Apple Gift Card that has been purchased in United States": "礼品卡地区限制",
|
||
"You have entered an invalid gift card": "无效礼品卡",
|
||
"Please enter a valid PIN": "无效卡号",
|
||
"We could not process your gift card": "礼品卡处理错误",
|
||
"This gift card has a zero balance": "礼品卡余额为0",
|
||
"Please enter another form of payment to cover the remaining balance": "礼品卡余额不足",
|
||
}
|
||
|
||
# 进度步骤定义
|
||
self.progress_steps = {
|
||
"初始化上下文": 5.0,
|
||
"打开产品页面": 10.0,
|
||
"添加到购物车": 15.0,
|
||
"设置邮编": 20.0,
|
||
"选择支付方式": 25.0,
|
||
"填写配送信息": 35.0,
|
||
"等待礼品卡信息": 40.0,
|
||
"处理礼品卡": 60.0,
|
||
"继续到审核页面": 80.0,
|
||
"提交订单": 90.0,
|
||
"提取订单信息": 95.0,
|
||
"处理完成": 100.0,
|
||
}
|
||
|
||
async def process_order(self) -> dict[str, Any]:
|
||
"""
|
||
处理订单的主入口方法
|
||
使用上下文管理器确保资源正确清理
|
||
"""
|
||
logger.info(f"{self.thread_prefix} 开始处理订单")
|
||
|
||
try:
|
||
# 初始化任务状态
|
||
await self._update_progress("初始化上下文", OrderTaskStatus.RUNNING)
|
||
|
||
# 获取订单信息
|
||
await self._load_order_info()
|
||
|
||
# 使用Playwright上下文管理器
|
||
|
||
async with playwright_manager.get_order_context(
|
||
self.order_id
|
||
) as context_info:
|
||
page = context_info["page"]
|
||
|
||
# 设置页面超时
|
||
page.set_default_timeout(60000)
|
||
|
||
# # 执行订单处理流程
|
||
# result = await self._execute_order_flow(page)
|
||
result = {}
|
||
|
||
if result.get("success"):
|
||
logger.info(f"{self.thread_prefix} 订单处理成功")
|
||
return result
|
||
else:
|
||
logger.error(
|
||
f"{self.thread_prefix} 订单处理失败: {result.get('error')}"
|
||
)
|
||
return result
|
||
|
||
except Exception as e:
|
||
error_msg = f"订单处理异常: {str(e)}"
|
||
logger.error(f"{self.thread_prefix} {error_msg}\n{traceback.format_exc()}")
|
||
|
||
# 更新任务状态为失败
|
||
await task_state_manager.fail_task(self.task_id, error_msg)
|
||
|
||
# 更新订单状态
|
||
await self._update_order_failure(error_msg)
|
||
|
||
return {"success": False, "error": error_msg, "order_id": self.order_id}
|
||
|
||
async def _load_order_info(self):
|
||
"""加载订单信息"""
|
||
async with db_manager.get_async_session() as session:
|
||
order_repo = OrderRepository(session)
|
||
self.order = await order_repo.get_order_with_full_details(self.order_id)
|
||
if not self.order:
|
||
raise ValueError(f"订单不存在: {self.order_id}")
|
||
|
||
if self.order.status != OrderResultStatus.PENDING:
|
||
raise ValueError(f"订单状态不正确: {self.order.status}")
|
||
|
||
# 更新订单状态为处理中
|
||
await order_repo.update_by_id(
|
||
self.order_id,
|
||
status=OrderResultStatus.PROCESSING,
|
||
updated_at=datetime.now(),
|
||
)
|
||
|
||
logger.info(f"{self.thread_prefix} 订单信息加载成功")
|
||
|
||
async def _update_progress(
|
||
self, step_name: str, status: OrderTaskStatus | None = None
|
||
):
|
||
"""更新任务进度"""
|
||
progress = self.progress_steps.get(step_name, 0.0)
|
||
|
||
if status:
|
||
await task_state_manager.set_task_state(
|
||
self.task_id,
|
||
status,
|
||
progress=progress,
|
||
order_id=self.order_id,
|
||
current_step=step_name,
|
||
)
|
||
else:
|
||
await task_state_manager.update_task_progress(self.task_id, progress)
|
||
|
||
logger.info(f"{self.thread_prefix} {step_name} ({progress}%)")
|
||
|
||
async def _execute_order_flow(self, page: Page) -> dict[str, Any]:
|
||
"""执行订单处理流程"""
|
||
try:
|
||
# 检查页面是否有效
|
||
if page.is_closed():
|
||
raise Exception("页面已关闭")
|
||
|
||
# 步骤1: 打开产品页面
|
||
await self._update_progress("打开产品页面")
|
||
await self._navigate_to_product_page(page)
|
||
|
||
# 步骤2: 添加到购物车
|
||
await self._update_progress("添加到购物车")
|
||
await self._add_to_cart(page)
|
||
|
||
# 步骤3: 设置邮编
|
||
await self._update_progress("设置邮编")
|
||
await self._handle_zipcode_setup(page)
|
||
|
||
# 步骤4: 选择支付方式
|
||
await self._update_progress("选择支付方式")
|
||
await self._handle_payment_selection(page)
|
||
|
||
# 步骤5: 填写配送信息
|
||
await self._update_progress("填写配送信息")
|
||
await self._handle_shipping_info(page)
|
||
|
||
# 步骤6: 处理礼品卡(中断式输入)
|
||
await self._update_progress("等待礼品卡信息")
|
||
gift_card_data = await self._handle_gift_card_process_enhanced(page)
|
||
|
||
if not gift_card_data:
|
||
return {
|
||
"success": False,
|
||
"error": "礼品卡处理失败",
|
||
"order_id": self.order_id,
|
||
}
|
||
|
||
# 步骤7: 完成订单
|
||
await self._update_progress("提交订单")
|
||
success = await self._handle_order_completion(page, gift_card_data)
|
||
|
||
if success:
|
||
await self._update_progress("处理完成", OrderTaskStatus.SUCCESS)
|
||
order_info = await self._extract_order_info(page)
|
||
await self._update_order_success(order_info)
|
||
|
||
return {
|
||
"success": True,
|
||
"order_id": self.order_id,
|
||
"order_number": order_info.get("order_number"),
|
||
"order_url": order_info.get("order_url"),
|
||
"processed_at": datetime.now().isoformat(),
|
||
}
|
||
else:
|
||
return {
|
||
"success": False,
|
||
"error": "订单完成失败",
|
||
"order_id": self.order_id,
|
||
}
|
||
|
||
except Exception as e:
|
||
error_msg = f"订单流程执行失败: {str(e)}"
|
||
logger.error(f"{self.thread_prefix} {error_msg}")
|
||
return {"success": False, "error": error_msg, "order_id": self.order_id}
|
||
|
||
async def _navigate_to_product_page(self, page: Page):
|
||
"""打开产品页面"""
|
||
if not self.order or not self.order.links or not self.order.links.url:
|
||
raise ValueError("订单中没有产品链接信息")
|
||
|
||
product_url = self.order.links.url
|
||
logger.info(f"{self.thread_prefix} 打开产品页面: {product_url}")
|
||
|
||
try:
|
||
await page.goto(product_url, wait_until="networkidle", timeout=30000)
|
||
await page.wait_for_timeout(2000)
|
||
except Exception as nav_error:
|
||
raise Exception(f"页面导航失败: {str(nav_error)}")
|
||
|
||
async def _add_to_cart(self, page: Page):
|
||
"""添加到购物车"""
|
||
if not await self._click_element(page, self.selectors["add_to_cart"]):
|
||
raise Exception("无法点击添加到购物车按钮")
|
||
|
||
await page.wait_for_timeout(2000)
|
||
logger.info(f"{self.thread_prefix} 已添加到购物车")
|
||
|
||
async def _update_order_failure(self, error_msg: str):
|
||
"""更新订单状态为失败"""
|
||
async with db_manager.get_async_session() as session:
|
||
order_repo = OrderRepository(session)
|
||
await order_repo.update_by_id(
|
||
self.order_id,
|
||
status=OrderResultStatus.FAILURE,
|
||
failure_reason=error_msg,
|
||
completed_at=datetime.now(),
|
||
)
|
||
|
||
async def _update_order_success(self, order_info: dict[str, Any]):
|
||
"""更新订单状态为成功"""
|
||
async with db_manager.get_async_session() as session:
|
||
order_repo = OrderRepository(session)
|
||
await order_repo.update_by_id(
|
||
self.order_id,
|
||
status=OrderResultStatus.SUCCESS,
|
||
order_number=order_info.get("order_number"),
|
||
final_order_url=order_info.get("order_url"),
|
||
completed_at=datetime.now(),
|
||
)
|
||
|
||
# 辅助方法实现
|
||
async def _click_element(
|
||
self,
|
||
page: Page,
|
||
selector: str,
|
||
selector_type: str = "css",
|
||
timeout: int = 30000,
|
||
) -> bool:
|
||
"""统一的元素点击方法"""
|
||
try:
|
||
if page.is_closed():
|
||
logger.error(f"{self.thread_prefix} 页面已关闭,无法操作元素")
|
||
return False
|
||
|
||
full_selector = self._build_selector(selector, selector_type)
|
||
|
||
try:
|
||
element = await page.wait_for_selector(full_selector, timeout=timeout)
|
||
if not element:
|
||
logger.error(f"{self.thread_prefix} 元素未找到: {selector}")
|
||
return False
|
||
except asyncio.TimeoutError:
|
||
logger.error(f"{self.thread_prefix} 等待元素超时: {selector}")
|
||
return False
|
||
|
||
await element.click()
|
||
await self._wait_for_element_stable(element)
|
||
logger.debug(f"{self.thread_prefix} 成功点击元素: {selector}")
|
||
return True
|
||
|
||
except Exception as e:
|
||
logger.error(f"{self.thread_prefix} 点击元素失败 {selector}: {e}")
|
||
return False
|
||
|
||
def _build_selector(self, selector: str, selector_type: str) -> str:
|
||
"""构建完整的选择器字符串"""
|
||
if selector_type == "id":
|
||
return f"#{selector.replace('.', '\\.')}"
|
||
elif selector_type == "xpath":
|
||
return f"xpath={selector}"
|
||
else:
|
||
return selector
|
||
|
||
async def _wait_for_element_stable(self, element, max_wait: int = 10) -> None:
|
||
"""等待元素状态稳定(非禁用状态)"""
|
||
try:
|
||
for _ in range(max_wait):
|
||
if not await element.is_disabled():
|
||
break
|
||
await asyncio.sleep(1)
|
||
except Exception:
|
||
pass # 忽略状态检查错误
|
||
|
||
# 简化的处理方法,实际实现需要根据业务需求完善
|
||
async def _handle_zipcode_setup(self, page: Page):
|
||
"""处理邮编设置"""
|
||
logger.info(f"{self.thread_prefix} 处理邮编设置")
|
||
# 实际实现会包含邮编相关的具体操作
|
||
pass
|
||
|
||
async def _handle_payment_selection(self, page: Page):
|
||
"""处理支付方式选择"""
|
||
logger.info(f"{self.thread_prefix} 处理支付方式选择")
|
||
# 实际实现会包含支付方式选择的具体操作
|
||
pass
|
||
|
||
async def _handle_shipping_info(self, page: Page):
|
||
"""处理配送信息填写"""
|
||
logger.info(f"{self.thread_prefix} 处理配送信息")
|
||
# 实际实现会包含配送信息填写的具体操作
|
||
pass
|
||
|
||
async def _handle_gift_card_process_enhanced(
|
||
self, page: Page
|
||
) -> dict[str, Any] | None:
|
||
"""处理礼品卡流程(增强版,支持中断式输入)"""
|
||
try:
|
||
# 更新任务状态为等待礼品卡
|
||
await task_state_manager.set_task_state(
|
||
self.task_id,
|
||
OrderTaskStatus.WAITING_GIFT_CARD,
|
||
order_id=self.order_id,
|
||
message="等待用户提供礼品卡信息",
|
||
)
|
||
|
||
logger.info(f"{self.thread_prefix} 等待礼品卡信息输入")
|
||
|
||
# 等待礼品卡信息(最多等待10分钟)
|
||
card_code = await self._wait_for_gift_card_input()
|
||
|
||
if not card_code:
|
||
return None
|
||
|
||
# 输入礼品卡号到页面
|
||
if not await self._fill_gift_card_form(page, card_code):
|
||
return None
|
||
|
||
logger.info(f"{self.thread_prefix} 礼品卡处理成功")
|
||
return {"card_code": card_code}
|
||
|
||
except Exception as e:
|
||
logger.error(f"{self.thread_prefix} 礼品卡处理异常: {e}")
|
||
return None
|
||
|
||
async def _wait_for_gift_card_input(self) -> str | None:
|
||
"""等待礼品卡信息输入"""
|
||
timeout = 600 # 10分钟
|
||
start_time = asyncio.get_event_loop().time()
|
||
|
||
while True:
|
||
# 检查是否已接收到礼品卡信息
|
||
task_state = await task_state_manager.get_task_state(self.task_id)
|
||
if (
|
||
task_state
|
||
and task_state.get("status") == OrderTaskStatus.GIFT_CARD_RECEIVED
|
||
):
|
||
logger.info(f"{self.thread_prefix} 已接收到礼品卡信息")
|
||
|
||
# 获取礼品卡信息
|
||
async with db_manager.get_async_session() as db:
|
||
gift_card_service = GiftCardService(db)
|
||
card_code = await gift_card_service.get_card_code(self.order_id)
|
||
|
||
if card_code:
|
||
return card_code
|
||
else:
|
||
logger.error(f"{self.thread_prefix} 礼品卡信息获取失败")
|
||
return None
|
||
|
||
# 检查超时
|
||
if asyncio.get_event_loop().time() - start_time > timeout:
|
||
logger.error(f"{self.thread_prefix} 等待礼品卡信息超时")
|
||
await task_state_manager.fail_task(self.task_id, "等待礼品卡信息超时")
|
||
return None
|
||
|
||
# 等待1秒后再次检查
|
||
await asyncio.sleep(1)
|
||
|
||
async def _fill_gift_card_form(self, page: Page, card_code: str) -> bool:
|
||
"""在页面中填写礼品卡表单"""
|
||
try:
|
||
# 实际的礼品卡表单填写逻辑
|
||
logger.info(
|
||
f"{self.thread_prefix} 填写礼品卡号: {card_code[:4]}****{card_code[-4:]}"
|
||
)
|
||
# 具体的页面操作会在这里实现
|
||
return True
|
||
except Exception as e:
|
||
logger.error(f"{self.thread_prefix} 填写礼品卡表单失败: {e}")
|
||
return False
|
||
|
||
async def _handle_order_completion(
|
||
self, page: Page, gift_card_data: dict[str, Any]
|
||
) -> bool:
|
||
"""处理订单完成"""
|
||
try:
|
||
logger.info(f"{self.thread_prefix} 完成订单提交")
|
||
# 实际的订单完成逻辑
|
||
return True
|
||
except Exception as e:
|
||
logger.error(f"{self.thread_prefix} 订单完成失败: {e}")
|
||
return False
|
||
|
||
async def _extract_order_info(self, page: Page) -> dict[str, Any]:
|
||
"""提取订单信息"""
|
||
try:
|
||
# 生成模拟的订单信息
|
||
order_number = (
|
||
f"APPLE_{self.order_id}_{datetime.now().strftime('%Y%m%d%H%M%S')}"
|
||
)
|
||
order_url = "https://apple.com/order/123"
|
||
|
||
logger.info(f"{self.thread_prefix} 订单号: {order_number}")
|
||
return {"order_number": order_number, "order_url": order_url}
|
||
except Exception as e:
|
||
logger.error(f"{self.thread_prefix} 提取订单信息失败: {e}")
|
||
return {}
|
||
|
||
|
||
# 全局实例
|
||
playwright_manager = DistributedPlaywrightManager()
|