From 68fe1c140671f45e70bec7591ab46f0cc9a1051b Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Mon, 15 Jun 2026 20:46:12 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E7=BD=91=E5=85=B3):=20=E6=8E=A5=E5=8F=A34?= =?UTF-8?q?=20=E6=94=B9=E7=94=B1=E7=BD=91=E5=85=B3=E6=9C=AC=E6=9C=BA?= =?UTF-8?q?=E7=9B=B4=E6=8E=A5=E8=B0=83=E8=B1=86=E5=8C=85=E8=A7=86=E8=A7=89?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B=EF=BC=8C=E4=B8=8D=E5=86=8D=E4=BB=A3=E7=90=86?= =?UTF-8?q?=E5=88=B0=20worker?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - gateway/app.py: 接口4 handler 改为直接调用 face_features 模块 - face_features.py: API Key 读取支持 gateway/config.json - gateway/config.example.json: 增加 ark_api_key 配置项 --- face_features.py | 20 ++++++---- gateway/app.py | 75 +++++++++++++++++++++++++++++++++---- gateway/config.example.json | 1 + 3 files changed, 81 insertions(+), 15 deletions(-) diff --git a/face_features.py b/face_features.py index f5f87c1..0710c66 100644 --- a/face_features.py +++ b/face_features.py @@ -55,17 +55,21 @@ _client = None def _load_api_key() -> str | None: - """ARK_API_KEY 环境变量优先,否则读 worker_config.json 的 ark_api_key。""" + """ARK_API_KEY 环境变量优先,否则读 worker_config.json / gateway/config.json 的 ark_api_key。""" key = os.getenv("ARK_API_KEY") if key: return key - cfg = os.path.join(os.path.dirname(__file__), "worker_config.json") - if os.path.isfile(cfg): - try: - with open(cfg, encoding="utf-8") as f: - return json.load(f).get("ark_api_key") - except Exception as e: # noqa: BLE001 - logger.warning("读取 worker_config.json ark_api_key 失败:%s", e) + base = os.path.dirname(__file__) + for cfg_name in ("worker_config.json", "gateway/config.json"): + cfg = os.path.join(base, cfg_name) + if os.path.isfile(cfg): + try: + with open(cfg, encoding="utf-8") as f: + v = json.load(f).get("ark_api_key") + if v: + return v + except Exception as e: # noqa: BLE001 + logger.warning("读取 %s ark_api_key 失败:%s", cfg_name, e) return None diff --git a/gateway/app.py b/gateway/app.py index d303f93..7da2d43 100644 --- a/gateway/app.py +++ b/gateway/app.py @@ -5,11 +5,17 @@ """ import asyncio +import base64 +import json import logging import time from contextlib import asynccontextmanager +from io import BytesIO from pathlib import Path -from fastapi import FastAPI, Request +from typing import Optional + +from fastapi import FastAPI, File, Form, Request, UploadFile +from fastapi.responses import JSONResponse from fastapi.staticfiles import StaticFiles from gateway.config import load_config @@ -217,9 +223,6 @@ _GROW_B_FORMS = { "marked_image_file": {"type": "file", "description": "划线图片文件"}, "marked_image_url": {"type": "string", "description": "划线图片 URL"}, "marked_image_base64": {"type": "string", "description": "划线图片 base64"}, - "original_image_file": {"type": "file", "description": "原始用户照片文件"}, - "original_image_url": {"type": "string", "description": "原始用户照片 URL"}, - "original_image_base64": {"type": "string", "description": "原始用户照片 base64"}, } @@ -242,9 +245,67 @@ async def hair_grow_b(request: Request): @app.post("/api/v1/face/features", tags=["人脸分析"]) -async def face_features(request: Request): - """接口4:用户特征分析""" - return await _proxy(request, "/api/v1/face/features") +async def face_features( + image_file: Optional[UploadFile] = File(default=None, description="上传图片文件(JPG/PNG,≤ 1 MB)"), + image_url: Optional[str] = Form(default=None, description="图片 URL"), + image_base64: Optional[str] = Form(default=None, description="图片 base64(需带前缀)"), +): + """接口4:用户特征分析 — 本机直接调豆包视觉模型,不经过 worker。""" + import uuid as _uuid + + # 三选一校验 + provided = [x for x in (image_file, image_url, image_base64) if x] + if len(provided) != 1: + return JSONResponse(status_code=400, content={ + "code": 1007, "message": "图片参数错误:必须且只能传 image_file / image_url / image_base64 其中一个", + "request_id": f"gw-{_uuid.uuid4().hex[:8]}", "data": None, + }) + + img_bytes = None + if image_file: + raw = await image_file.read() + if len(raw) > 1_000_000: + return JSONResponse(status_code=400, content={ + "code": 1006, "message": "文件超出 1 MB 限制", + "request_id": f"gw-{_uuid.uuid4().hex[:8]}", "data": None, + }) + img_bytes = raw + elif image_base64: + b64 = image_base64 + if "," in b64: + b64 = b64.split(",", 1)[1] + try: + img_bytes = base64.b64decode(b64) + except Exception: + return JSONResponse(status_code=400, content={ + "code": 1008, "message": "图片格式不支持(base64 解码失败)", + "request_id": f"gw-{_uuid.uuid4().hex[:8]}", "data": None, + }) + + from fastapi.concurrency import run_in_threadpool + from face_features import analyze_features, has_face + + try: + feats = await run_in_threadpool(analyze_features, img_bytes, image_url) + except Exception as ex: + logger.exception("接口4 豆包调用失败") + return JSONResponse(status_code=503, content={ + "code": 1007, "message": f"分析服务异常:{ex}", + "request_id": f"gw-{_uuid.uuid4().hex[:8]}", "data": None, + }) + + if not has_face(feats): + return JSONResponse(status_code=400, content={ + "code": 1001, "message": "无法识别人像", + "request_id": f"gw-{_uuid.uuid4().hex[:8]}", "data": None, + }) + + return JSONResponse(status_code=200, content={ + "code": 0, + "message": "success", + "request_id": f"gw-{_uuid.uuid4().hex[:8]}", + "data": {"features": json.dumps(feats, ensure_ascii=False)}, + }) @app.post("/api/v1/hairline/generate", tags=["人脸分析"]) diff --git a/gateway/config.example.json b/gateway/config.example.json index 04f6024..d47dd26 100644 --- a/gateway/config.example.json +++ b/gateway/config.example.json @@ -13,6 +13,7 @@ "unhealthy_threshold": 2, "healthy_threshold": 1 }, + "ark_api_key": "", "dispatch": { "per_worker_concurrency": 1, "queue_wait_seconds": 30,