# -*- coding: utf-8 -*- """ C 端拍摄图 MinIO 上传 + 请求图来源解析(8020 serve_card_match_v2 专用公共模块)。 从 serve_card_match.py 抽出(那里模块级就加载 MATCHER,不能被 import)。 配置读 config.CAPP_MINIO:SDK 走本机 endpoint(127.0.0.1:9000),对外回显 public_base。 """ import os import re import uuid import hashlib import traceback from datetime import datetime # 上传文件名白名单(与 serve_card_match._resolve_src 一致):保留调用方原始文件名全链路统一 _SAFE_FNAME = re.compile(r"[A-Za-z0-9._\-()()一-鿿]+") _IMG_EXTS = (".jpg", ".jpeg", ".png", ".webp", ".bmp") def upload_to_capp_minio(local_path, name_prefix="capp_front"): """C 端拍摄图落到 249 MinIO grading/,返回对外 URL;失败返回 None。 对象名 = capp_front_<时间戳>.(与 8000 /match_fields 命名规则一致)。""" if not local_path or not os.path.exists(local_path): return None import config cfg = getattr(config, "CAPP_MINIO", None) if not cfg: return None try: from minio import Minio client = Minio( cfg["endpoint"], access_key=cfg["access_key"], secret_key=cfg["secret_key"], secure=bool(cfg.get("secure")), ) ts = datetime.now().strftime("%Y-%m-%d_%H-%M-%S_%f") ext = os.path.splitext(local_path)[1].lower() if ext not in _IMG_EXTS: ext = ".jpg" object_name = "%s/%s_%s%s" % (cfg["prefix"], name_prefix, ts, ext) ctype = "image/jpeg" if ext in (".jpg", ".jpeg") else "image/%s" % ext.lstrip(".") client.fput_object(cfg["bucket"], object_name, local_path, content_type=ctype) return "%s/%s/%s" % (cfg["public_base"].rstrip("/"), cfg["bucket"], object_name) except Exception: traceback.print_exc() return None def save_upload_to_tmp(blob, filename, tmp_dir): """multipart 上传字节落临时文件(推理后删)。返回落盘路径。 磁盘名一律加 uuid 短后缀:并发同名上传互踩会导致 A 删文件 B 读 400 / 读到半截图 YOLO 秒回空(2026-09-03 压测实锤);缓存 key 按内容 md5,不依赖磁盘名。""" raw = os.path.basename((filename or "").replace("\\", "/")).strip() if raw and _SAFE_FNAME.fullmatch(raw) and raw.lower().endswith(_IMG_EXTS): stem, ext = os.path.splitext(raw) else: stem, ext = hashlib.md5(blob).hexdigest(), ".jpg" dst = os.path.join(tmp_dir, "%s_%s%s" % (stem, uuid.uuid4().hex[:8], ext)) with open(dst, "wb") as fo: fo.write(blob) return dst def file_md5(path): """文件内容 md5(流式读,缓存 key 用)。""" h = hashlib.md5() with open(path, "rb") as f: for chunk in iter(lambda: f.read(1 << 20), b""): h.update(chunk) return h.hexdigest()