去除上传图片的分辨率与文件大小限制

- app.py: 删除全部 MAX_FILE_BYTES(≤1MB→1006) 与 MIN_SHORT_SIDE/MIN_LONG_SIDE
  (分辨率→1002) 校验及对应常量; 同步清理 File 描述、图片要求说明、
  错误码表(移除1002/1006)与过时示例
- gateway/app.py: 删除注释掉的 1006 大小校验块与描述里的 ≤1MB
- run_worker.sh / hair-worker.service: 删除临时放开限制的环境变量
- tests: 移除已过时的 test_oversize_1006 / test_lowres_1002 及 oversize_file fixture

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
xsl
2026-06-24 22:21:39 +08:00
co-authored by Claude Opus 4.8
parent a9439e4975
commit a177dc2583
6 changed files with 17 additions and 99 deletions
+14 -60
View File
@@ -30,14 +30,6 @@ logger = logging.getLogger("hair.worker")
BASE_URL = "https://hair.xiangsilian.com"
SAMPLE_IMAGE_URL = f"{BASE_URL}/static/sample.jpg"
# 分辨率门槛(短边/长边,方向无关),可由环境变量覆盖(技术方案 §8.3)
MIN_SHORT_SIDE = int(os.getenv("MIN_SHORT_SIDE", "600"))
MIN_LONG_SIDE = int(os.getenv("MIN_LONG_SIDE", "800"))
# 文件大小上限(字节),可由环境变量覆盖;设为 0 表示不限制
MAX_FILE_BYTES = int(os.getenv("MAX_FILE_BYTES", "1000000")) # 默认 1 MB
if MAX_FILE_BYTES <= 0:
MAX_FILE_BYTES = float("inf")
# 运行期状态:模型就绪标志(/health 据此返回 200/503
_STATE = {"ready": False}
@@ -103,7 +95,7 @@ app = FastAPI(
| 方式 | 字段名 | 说明 |
|------|--------|------|
| 文件上传 | `image_file` | `multipart/form-data`,单文件 ≤ 1 MB |
| 文件上传 | `image_file` | `multipart/form-data`,单文件上传 |
| URL | `image_url` | 图片的完整 HTTP/HTTPS 地址 |
| base64 | `image_base64` | 需携带前缀,如 `data:image/jpeg;base64,xxxx` |
@@ -112,9 +104,9 @@ app = FastAPI(
## 图片要求
- 格式:**JPG / PNG**
- 分辨率:最低 1080×1920,最大 4000×5000
- 分辨率:不限制
- 人脸数量:仅支持**单人**,多人返回错误码 `1005`
- 文件大小:≤ 1 MB(文件上传方式)
- 文件大小:不限制
## 统一响应结构
@@ -134,11 +126,9 @@ app = FastAPI(
| code | 说明 |
|------|------|
| 1001 | 无法识别人像 |
| 1002 | 人像分辨率过低 |
| 1003 | 非正面照 / 角度过大 |
| 1004 | 性别标签无法判定 |
| 1005 | 检测到多张人脸(仅支持单人)|
| 1006 | 文件超出 1 MB 限制 |
| 1007 | 图片参数错误(未传或同时传多个)|
| 1008 | 图片格式不支持(仅 JPG / PNG)|
""",
@@ -202,7 +192,7 @@ async def resolve_image_bytes(image_file, image_url, image_base64):
"""三选一取图,返回 (raw_bytes, error_response)。
严格互斥:传 0 个或多个 → 1007;URL 下载失败/base64 解码失败 → 1008。
大小校验放到上层(统一 1006,此处只负责取到字节。
不限制文件大小,此处只负责取到字节。
"""
provided = [x for x in (image_file, image_url, image_base64) if x]
if len(provided) != 1:
@@ -280,7 +270,7 @@ def _parse_hair_styles(raw: Optional[str], max_styles: int) -> Optional[list[int
_image_fields_desc = (
"图片传参方式严格互斥,必须且只能选其一:\n"
"- **image_file**multipart/form-data 上传,≤ 1 MB\n"
"- **image_file**multipart/form-data 上传)\n"
"- **image_url**(完整 HTTP/HTTPS 地址)\n"
"- **image_base64**(需携带前缀,如 `data:image/jpeg;base64,xxxx`\n\n"
"同时传多个或一个都不传,均返回错误码 `1007`。"
@@ -331,20 +321,12 @@ async def _face_measure_impl(image_file, image_url, image_base64, variant="v1"):
if e is not None:
return None, e
# 2. 大小校验(≤ 1MB
if len(raw) > MAX_FILE_BYTES:
return None, err(1006, "文件超出 1 MB 限制")
# 3. 解码
# 2. 解码(不限制文件大小 / 分辨率
image = cv2.imdecode(np.frombuffer(raw, np.uint8), cv2.IMREAD_COLOR)
if image is None:
return None, err(1008, "图片格式不支持(仅 JPG / PNG)")
# 4. 分辨率(短边/长边,方向无关,可配置门槛)
h, w = image.shape[:2]
short_side, long_side = min(w, h), max(w, h)
if short_side < MIN_SHORT_SIDE or long_side < MIN_LONG_SIDE:
return None, err(1002, "人像分辨率过低")
try:
from face_analysis.detector import detector
@@ -488,7 +470,7 @@ async def _face_measure_impl(image_file, image_url, image_base64, variant="v1"):
},
)
async def face_measure(
image_file: Optional[UploadFile] = File(default=None, description="上传图片文件(JPG/PNG,≤ 1 MB"),
image_file: Optional[UploadFile] = File(default=None, description="上传图片文件(JPG/PNG"),
image_url: Optional[str] = Form(default=None, description="图片 URL"),
image_base64: Optional[str] = Form(default=None, description="图片 base64(需带 data:image/...;base64, 前缀)"),
):
@@ -587,7 +569,7 @@ async def face_measure(
},
)
async def face_measure_v2(
image_file: Optional[UploadFile] = File(default=None, description="上传图片文件(JPG/PNG,≤ 1 MB"),
image_file: Optional[UploadFile] = File(default=None, description="上传图片文件(JPG/PNG"),
image_url: Optional[str] = Form(default=None, description="图片 URL"),
image_base64: Optional[str] = Form(default=None, description="图片 base64(需带 data:image/...;base64, 前缀)"),
):
@@ -660,7 +642,7 @@ async def face_measure_v2(
},
)
async def hair_grow(
image_file: Optional[UploadFile] = File(default=None, description="上传图片文件(JPG/PNG,≤ 1 MB"),
image_file: Optional[UploadFile] = File(default=None, description="上传图片文件(JPG/PNG"),
image_url: Optional[str] = Form(default=None, description="图片 URL"),
image_base64: Optional[str] = Form(default=None, description="图片 base64(需带 data:image/...;base64, 前缀)"),
gender: Optional[str] = Form(default=None, description="性别 male/female(必填)"),
@@ -683,18 +665,11 @@ async def hair_grow(
raw, e = await resolve_image_bytes(image_file, image_url, image_base64)
if e is not None:
return e
if len(raw) > MAX_FILE_BYTES:
return err(1006, "文件超出 1 MB 限制")
image = cv2.imdecode(np.frombuffer(raw, np.uint8), cv2.IMREAD_COLOR)
if image is None:
return err(1008, "图片格式不支持(仅 JPG / PNG)")
h, w = image.shape[:2]
short_side, long_side = min(w, h), max(w, h)
if short_side < MIN_SHORT_SIDE or long_side < MIN_LONG_SIDE:
return err(1002, "人像分辨率过低")
try:
from fastapi.concurrency import run_in_threadpool
from hairline.service import generate_grow_results
@@ -781,7 +756,7 @@ _WORKFLOW2_PATH = os.path.join(os.path.dirname(__file__), "add_hair2.json")
},
)
async def hair_grow_v2(
image_file: Optional[UploadFile] = File(default=None, description="上传图片文件(JPG/PNG,≤ 1 MB"),
image_file: Optional[UploadFile] = File(default=None, description="上传图片文件(JPG/PNG"),
image_url: Optional[str] = Form(default=None, description="图片 URL"),
image_base64: Optional[str] = Form(default=None, description="图片 base64(需带 data:image/...;base64, 前缀)"),
gender: Optional[str] = Form(default=None, description="性别 male/female(必填)"),
@@ -804,18 +779,11 @@ async def hair_grow_v2(
raw, e = await resolve_image_bytes(image_file, image_url, image_base64)
if e is not None:
return e
if len(raw) > MAX_FILE_BYTES:
return err(1006, "文件超出 1 MB 限制")
image = cv2.imdecode(np.frombuffer(raw, np.uint8), cv2.IMREAD_COLOR)
if image is None:
return err(1008, "图片格式不支持(仅 JPG / PNG)")
h, w = image.shape[:2]
short_side, long_side = min(w, h), max(w, h)
if short_side < MIN_SHORT_SIDE or long_side < MIN_LONG_SIDE:
return err(1002, "人像分辨率过低")
try:
from fastapi.concurrency import run_in_threadpool
from hairline.service import generate_grow_results
@@ -883,7 +851,7 @@ async def hair_grow_v2(
},
)
async def hair_grow_b(
marked_image_file: Optional[UploadFile] = File(default=None, description="划线图片文件(JPG/PNG,≤ 1 MB"),
marked_image_file: Optional[UploadFile] = File(default=None, description="划线图片文件(JPG/PNG"),
marked_image_url: Optional[str] = Form(default=None, description="划线图片 URL"),
marked_image_base64: Optional[str] = Form(default=None, description="划线图片 base64"),
use_mask: bool = Form(default=True, description="是否画发际线(测试对比用)。false 时跳过划线检测、直接送划线图"),
@@ -893,18 +861,11 @@ async def hair_grow_b(
marked_raw, e = await resolve_image_bytes(marked_image_file, marked_image_url, marked_image_base64)
if e is not None:
return e
if len(marked_raw) > MAX_FILE_BYTES:
return err(1006, "文件超出 1 MB 限制")
marked = cv2.imdecode(np.frombuffer(marked_raw, np.uint8), cv2.IMREAD_COLOR)
if marked is None:
return err(1008, "图片格式不支持(仅 JPG / PNG)")
h, w = marked.shape[:2]
short_side, long_side = min(w, h), max(w, h)
if short_side < MIN_SHORT_SIDE or long_side < MIN_LONG_SIDE:
return err(1002, "人像分辨率过低")
try:
from fastapi.concurrency import run_in_threadpool
from hairline.service import generate_grow_b
@@ -985,7 +946,7 @@ async def hair_grow_b(
},
)
async def face_features(
image_file: Optional[UploadFile] = File(default=None, description="上传图片文件(JPG/PNG,≤ 1 MB"),
image_file: Optional[UploadFile] = File(default=None, description="上传图片文件(JPG/PNG"),
image_url: Optional[str] = Form(default=None, description="图片 URL"),
image_base64: Optional[str] = Form(default=None, description="图片 base64(需带 data:image/...;base64, 前缀)"),
):
@@ -1052,14 +1013,14 @@ async def face_features(
"description": "参数错误 / 图片识别失败",
"content": {
"application/json": {
"example": {"code": 1002, "message": "人像分辨率过低", "request_id": "x", "data": None}
"example": {"code": 1001, "message": "无法识别人像", "request_id": "x", "data": None}
}
},
},
},
)
async def hairline_generate(
image_file: Optional[UploadFile] = File(default=None, description="上传图片文件(JPG/PNG,≤ 1 MB"),
image_file: Optional[UploadFile] = File(default=None, description="上传图片文件(JPG/PNG"),
image_url: Optional[str] = Form(default=None, description="图片 URL"),
image_base64: Optional[str] = Form(default=None, description="图片 base64(需带 data:image/...;base64, 前缀)"),
gender: Optional[str] = Form(default=None, description="性别 male/female(必填)"),
@@ -1070,18 +1031,11 @@ async def hairline_generate(
raw, e = await resolve_image_bytes(image_file, image_url, image_base64)
if e is not None:
return e
if len(raw) > MAX_FILE_BYTES:
return err(1006, "文件超出 1 MB 限制")
image = cv2.imdecode(np.frombuffer(raw, np.uint8), cv2.IMREAD_COLOR)
if image is None:
return err(1008, "图片格式不支持(仅 JPG / PNG)")
h, w = image.shape[:2]
short_side, long_side = min(w, h), max(w, h)
if short_side < MIN_SHORT_SIDE or long_side < MIN_LONG_SIDE:
return err(1002, "人像分辨率过低")
try:
from fastapi.concurrency import run_in_threadpool
from hairline.service import generate_hairline_pngs