forked from erp-dev/erp
feat: command for tiia(tencent)
This commit is contained in:
185
printing/management/commands/tiia_upload_plate_order_images.py
Normal file
185
printing/management/commands/tiia_upload_plate_order_images.py
Normal file
@@ -0,0 +1,185 @@
|
||||
"""
|
||||
TIIA manual uploader (management command).
|
||||
|
||||
This command is intended for one-off backfill / verification:
|
||||
- Iterate PlateOrder(s)
|
||||
- Upload all image URLs in plate_image JSON field to Tencent Cloud TIIA gallery (CreateImage)
|
||||
- Respect QPS limit
|
||||
- Record failures into PlateOrderTiiaUploadFailure (and continue)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import date, datetime, time, timedelta
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.db import transaction
|
||||
from django.utils import timezone
|
||||
|
||||
from printing.models import PlateOrder, PlateOrderTiiaUploadFailure
|
||||
|
||||
|
||||
def _get_day_range(day: date) -> tuple[datetime, datetime]:
|
||||
tz = timezone.get_current_timezone()
|
||||
start = datetime.combine(day, time.min)
|
||||
end = start + timedelta(days=1)
|
||||
if timezone.is_naive(start):
|
||||
start = timezone.make_aware(start, tz)
|
||||
if timezone.is_naive(end):
|
||||
end = timezone.make_aware(end, tz)
|
||||
return start, end
|
||||
|
||||
|
||||
@transaction.atomic
|
||||
def _record_failure(*, run_date: date, plate_order_id: int, error: str, details):
|
||||
now = timezone.now()
|
||||
obj, created = PlateOrderTiiaUploadFailure.objects.select_for_update().get_or_create(
|
||||
run_date=run_date,
|
||||
plate_order_id=plate_order_id,
|
||||
defaults={
|
||||
'error': error or '',
|
||||
'details': details or [],
|
||||
'attempts': 1,
|
||||
'last_attempt_at': now,
|
||||
},
|
||||
)
|
||||
if not created:
|
||||
obj.error = error or obj.error or ''
|
||||
obj.details = details or obj.details or []
|
||||
obj.attempts = (obj.attempts or 0) + 1
|
||||
obj.last_attempt_at = now
|
||||
obj.save(update_fields=['error', 'details', 'attempts', 'last_attempt_at', 'updated_at'])
|
||||
return obj
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '手动上传 PlateOrder.plate_image 中的图片到腾讯云 TIIA 图库(CreateImage),带限速与失败落库'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
'--all',
|
||||
action='store_true',
|
||||
help='处理所有 PlateOrder(建议仅用于一次性跑批/补数据)',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--yesterday',
|
||||
action='store_true',
|
||||
help='处理“昨天创建”的 PlateOrder(同定时任务筛选口径)',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--date',
|
||||
type=str,
|
||||
help='处理某一天创建的 PlateOrder(YYYY-MM-DD,本地时区)',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--plate-order-id',
|
||||
type=int,
|
||||
help='仅处理指定 plate_order_id',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--qps',
|
||||
type=float,
|
||||
default=None,
|
||||
help='覆盖 settings.TENCENTCLOUD_TIIA_QPS(默认 10)',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--dry-run',
|
||||
action='store_true',
|
||||
help='仅展示将要处理的 PlateOrder 数量,不执行上传',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--limit',
|
||||
type=int,
|
||||
default=None,
|
||||
help='最多处理多少个 PlateOrder(按 id 升序),用于小批量验证',
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
from api_v1.utils.tencentcloud_tiia import SimpleRateLimiter, upload_plate_order_images_to_tencent_tiia
|
||||
|
||||
do_all = bool(options['all'])
|
||||
yesterday = bool(options['yesterday'])
|
||||
date_str = options.get('date')
|
||||
plate_order_id = options.get('plate_order_id')
|
||||
dry_run = bool(options['dry_run'])
|
||||
limit = options.get('limit')
|
||||
|
||||
chosen = sum([1 if do_all else 0, 1 if yesterday else 0, 1 if bool(date_str) else 0, 1 if plate_order_id else 0])
|
||||
if chosen != 1:
|
||||
raise CommandError('必须且只能指定一种模式:--all / --yesterday / --date / --plate-order-id')
|
||||
|
||||
qps_opt = options.get('qps')
|
||||
qps = float(qps_opt) if qps_opt is not None else float(getattr(settings, 'TENCENTCLOUD_TIIA_QPS', 10) or 10)
|
||||
limiter = SimpleRateLimiter(qps=qps)
|
||||
|
||||
run_date = timezone.localdate()
|
||||
|
||||
qs = PlateOrder.objects.all().only('id', 'plate_image').order_by('id')
|
||||
mode = 'all'
|
||||
if yesterday:
|
||||
mode = 'yesterday'
|
||||
day = timezone.localdate() - timedelta(days=1)
|
||||
start, end = _get_day_range(day)
|
||||
qs = qs.filter(created_at__gte=start, created_at__lt=end)
|
||||
elif date_str:
|
||||
mode = f'date={date_str}'
|
||||
try:
|
||||
day = date.fromisoformat(date_str)
|
||||
except Exception as e:
|
||||
raise CommandError('--date 必须为 YYYY-MM-DD') from e
|
||||
start, end = _get_day_range(day)
|
||||
qs = qs.filter(created_at__gte=start, created_at__lt=end)
|
||||
elif plate_order_id:
|
||||
mode = f'plate_order_id={plate_order_id}'
|
||||
qs = qs.filter(id=int(plate_order_id))
|
||||
|
||||
if limit is not None:
|
||||
qs = qs[: int(limit)]
|
||||
|
||||
total_orders = qs.count() if dry_run else 0
|
||||
if dry_run:
|
||||
self.stdout.write(f'[TIIA] dry-run: mode={mode} qps={qps} run_date={run_date} total_orders={total_orders}')
|
||||
return
|
||||
|
||||
total_orders = 0
|
||||
processed_images = 0
|
||||
failed_ids: list[int] = []
|
||||
|
||||
self.stdout.write(f'[TIIA] start: mode={mode} qps={qps} run_date={run_date}')
|
||||
for po in qs.iterator(chunk_size=200):
|
||||
total_orders += 1
|
||||
try:
|
||||
results = upload_plate_order_images_to_tencent_tiia(
|
||||
plate_order_id=po.id,
|
||||
rate_limiter=limiter,
|
||||
)
|
||||
except Exception as e:
|
||||
failed_ids.append(po.id)
|
||||
_record_failure(run_date=run_date, plate_order_id=po.id, error=str(e), details=[{'error': str(e)}])
|
||||
self.stderr.write(f'[TIIA] PlateOrder {po.id} 上传异常(已记录,继续):{e}')
|
||||
continue
|
||||
|
||||
processed_images += len(results)
|
||||
bad = [r for r in results if not r.get('ok')]
|
||||
if bad:
|
||||
failed_ids.append(po.id)
|
||||
summary = '; '.join([(b.get('error') or '') for b in bad][:5])
|
||||
_record_failure(run_date=run_date, plate_order_id=po.id, error=summary, details=bad)
|
||||
self.stderr.write(f'[TIIA] PlateOrder {po.id} 部分失败(已记录,继续):{summary}')
|
||||
|
||||
self.stdout.write(self.style.SUCCESS('[TIIA] done'))
|
||||
self.stdout.write(
|
||||
str(
|
||||
{
|
||||
'run_date': str(run_date),
|
||||
'mode': mode,
|
||||
'qps': qps,
|
||||
'total_orders': total_orders,
|
||||
'processed_images': processed_images,
|
||||
'failed_count': len(failed_ids),
|
||||
'failed_ids_sample': failed_ids[:50],
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user