File size: 5,724 Bytes
b4f2238 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 | from __future__ import annotations
import asyncio
import logging
from pathlib import Path
from django.conf import settings
from django.utils import timezone
from apps.express_query.models import ExpressQueryTask, ExpressQueryTaskStatus
from apps.express_query.services import ExpressBrowserQueryService, TrackingExtractionService
logger = logging.getLogger("apps.express_query")
def execute_express_query_task(task_id: int) -> None:
"""执行快递查询任务(从文件识别运单号)"""
logger.info("开始执行快递查询任务", extra={"task_id": task_id})
try:
task = ExpressQueryTask.objects.get(id=task_id)
except ExpressQueryTask.DoesNotExist:
logger.error("快递查询任务不存在", extra={"task_id": task_id})
return
task.status = ExpressQueryTaskStatus.OCR_PARSING
task.started_at = timezone.now()
task.error_message = ""
task.save(update_fields=["status", "started_at", "error_message", "updated_at"])
try:
extraction_service = TrackingExtractionService()
extraction = extraction_service.extract(Path(task.waybill_image.path))
task.ocr_text = extraction.ocr_text
task.carrier_type = extraction.carrier_type
task.tracking_number = extraction.tracking_number
# PDF 只用第一页做 OCR,截断多页 PDF 节省空间
if task.waybill_image and (task.waybill_image.name or "").endswith(".pdf"):
TrackingExtractionService.truncate_pdf_to_first_page(Path(task.waybill_image.path))
if not extraction.tracking_number:
raise ValueError("OCR 未识别到有效运单号")
if extraction.carrier_type not in {"sf", "ems"}:
raise ValueError("OCR 已识别运单号,但未能识别承运商(仅支持 EMS/顺丰)")
task.status = ExpressQueryTaskStatus.WAITING_LOGIN
task.save(
update_fields=[
"ocr_text",
"carrier_type",
"tracking_number",
"status",
"updated_at",
]
)
# 继续执行浏览器查询(复用代码)
_execute_browser_query(task)
except Exception as exc:
logger.error("快递查询任务执行失败", extra={"task_id": task_id, "error": str(exc)}, exc_info=True)
task.status = ExpressQueryTaskStatus.FAILED
task.error_message = str(exc)
task.finished_at = timezone.now()
task.save(update_fields=["status", "error_message", "finished_at", "updated_at"])
def execute_manual_express_query_task(task_id: int) -> None:
"""执行手动输入的快递查询任务(跳过OCR)"""
logger.info("开始执行手动输入快递查询任务", extra={"task_id": task_id})
try:
task = ExpressQueryTask.objects.get(id=task_id)
except ExpressQueryTask.DoesNotExist:
logger.error("快递查询任务不存在", extra={"task_id": task_id})
return
task.started_at = timezone.now()
task.error_message = ""
task.save(update_fields=["started_at", "error_message", "updated_at"])
try:
# 验证承运商和运单号已设置
if not task.tracking_number or not task.carrier_type:
raise ValueError("缺少运单号或承运商信息")
if task.carrier_type not in {"sf", "ems"}:
raise ValueError(f"不支持的承运商: {task.carrier_type}(仅支持 SF/EMS)")
# 直接执行浏览器查询
_execute_browser_query(task)
except Exception as exc:
logger.error("手动输入快递查询任务执行失败", extra={"task_id": task_id, "error": str(exc)}, exc_info=True)
task.status = ExpressQueryTaskStatus.FAILED
task.error_message = str(exc)
task.finished_at = timezone.now()
task.save(update_fields=["status", "error_message", "finished_at", "updated_at"])
def _execute_browser_query(task: ExpressQueryTask) -> None:
"""执行浏览器查询(公共逻辑)"""
task.status = ExpressQueryTaskStatus.QUERYING
task.save(update_fields=["status", "updated_at"])
output_rel_path = Path("express_query/results") / f"{task.id}_{task.carrier_type}_{task.tracking_number}.pdf"
output_abs_path = Path(settings.MEDIA_ROOT) / output_rel_path
browser_service = ExpressBrowserQueryService()
coro = browser_service.query_and_save_pdf(
carrier_type=task.carrier_type,
tracking_number=task.tracking_number,
output_pdf=output_abs_path,
)
# Django-Q2 worker 已有事件循环,不能直接用 asyncio.run()
try:
asyncio.get_running_loop()
# 已有运行中的循环 → 用线程隔离执行
import concurrent.futures
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool:
future = pool.submit(asyncio.run, coro)
final_url = future.result(timeout=600)
except RuntimeError:
# 没有运行中的循环 → 直接用 asyncio.run()
final_url = asyncio.run(coro)
task.status = ExpressQueryTaskStatus.SUCCESS
task.query_url = final_url
task.result_pdf.name = output_rel_path.as_posix()
task.result_payload = {
"carrier_type": task.carrier_type,
"tracking_number": task.tracking_number,
"query_url": final_url,
"pdf_path": output_rel_path.as_posix(),
}
task.finished_at = timezone.now()
task.save(
update_fields=[
"status",
"query_url",
"result_pdf",
"result_payload",
"finished_at",
"updated_at",
]
)
logger.info("快递查询任务执行成功", extra={"task_id": task.id})
|