capp_upload.py 2.9 KB

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