网关继续盲转发,该字段是坐标 JSON,不参与 base64 改写。 Co-authored-by: Cursor <cursoragent@cursor.com>
12 KiB
网关侧改动清单(worker 近期变更引发)
给网关开发:以下是 worker/契约近期变化里与网关有关的点。标 ✅ 的我已在本仓库
gateway/改好(你 review/拉取即可);标 🔲 的是建议你确认或改。功能必需只有第 1 条。
接口52(2026-11-04 已接入main)
客户端新增请求头X-API-Token校验,默认yonghe1999,可在网关配置interface52_token覆盖;缺少或不匹配返回HTTP401/code1009,不进入队列。其他接口不增加该校验。
新增POST /api/v1/hairline/generate-52,由网关转发到GPU worker同路径。5号保持原样,52号的normalize_image等参数原样透传;非空图片沿用公共base64→URL改写,空图片仍保留*_base64: null。不在网关增加IP限制,不合入worker算法。
测试页/static/test_interface52.html不包含内部密码。部署前确认主/备worker均支持52号,并检查生发请求的上游超时。详见接口52网关接入。
1. ✅【功能必需】base64→URL 落盘扩展名按内容嗅探(支持 JPG)
背景:接口 2/3/5 的返回图改成了 JPG(体积约小 9×),接口 1 标注图仍是 PNG(含透明)。
网关把 *_base64 落盘时若硬编码 .png,JPG 会被存成 .png(内容是 JPG、扩展名错)。
改动(gateway/forward.py 的 rewrite_base64_to_url,已改):
# 原:filename = f"{uuid.uuid4().hex}.png"
ext = "png" if img_bytes[:8] == b"\x89PNG\r\n\x1a\n" else "jpg" # 按内容嗅探
filename = f"{uuid.uuid4().hex}.{ext}"
这样接口1 存
.png、接口2/3/5 存.jpg,对外 URL 后缀也就正确。若你的网关是独立部署/独立代码,按上面这两行改一下即可。
2. 🔲【建议】生发接口超时调大
接口 2(一次 N 张 Flux,~18s)/ 3(~6s) 经 ComfyUI 同步出图较慢。
gateway/config.json 的 dispatch.request_timeout_seconds 建议 ≥ 120,否则网关会先超时换 worker 重试。
3. 🔲【确认】base64→URL 通用改写仍覆盖这些场景
- 数组里的图片字段:接口2
results[].image_base64/results[].grown_image_base64、 接口5hairline_images[].image_base64在数组元素内——改写要递归进数组(你现有的递归实现已覆盖)。 - 可空字段:接口2/3 的生发图(ComfyUI 没起/失败时)
*_base64为 null → 保留 null、不落盘。
4. 🔲【可选·仅影响 /docs】OpenAPI 表单声明
纯文档展示,不影响转发功能(网关是盲转发)。若想让网关 /docs 准确:
- 接口2
/hair/grow、接口5/hairline/generate入参新增必填gender(male/female)。 - 接口3
/hair/grow-b入参只剩marked_image_*(已去掉original_image_*)。 - (
gateway/app.py里的_*_FORMS字典当前未被路由引用,所以不改也不影响实际行为。)
5. 🔲【新增】接口7 C端生发 v2(/api/v1/hair/grow-v2)
背景:worker 侧已新增接口 7,功能与接口 2 完全一致,区别仅在于 ComfyUI 工作流使用 add_hair2.json(而非 add_hair.json)。
网关需新增一个路由,代理转发到 worker 的同路径:
# gateway/app.py
@app.post("/api/v1/hair/grow-v2", tags=["生发"])
async def hair_grow_v2(request: Request):
"""接口7:C端生发 v2(add_hair2 工作流)"""
return await _proxy(request, "/api/v1/hair/grow-v2")
无需额外改动:
- 请求:multipart/form-data,参数与接口 2 完全相同(
image_file/url/base64三选一 +gender+hair_style+beauty_enabled+use_mask+prompt),网关盲转发即可 - 响应:结构与接口 2 完全一致,
results[].image_base64/results[].grown_image_base64经现有rewrite_base64_to_url自动改写为 URL - base64→URL:数组内图片字段递归改写已覆盖,无需修改
入参(与接口 2 一致)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| image_file / image_url / image_base64 | — | 三选一 | 用户正面照 |
| gender | string | 是 | male / female |
| hair_style | string | 是 | 发型序号,逗号分隔多选(如 1,2,3)。female / male 均为 1–7 |
| beauty_enabled | bool | 否 | 美颜开关(本期不生效) |
| use_mask | bool | 否 | 默认 true,false 跳过遮罩 |
| prompt | string | 否 | ComfyUI 提示词 |
出参(与接口 2 一致)
{
"code": 0,
"message": "success",
"request_id": "gw-xxxxxxxx",
"data": {
"results": [
{
"image_url": "https://hair.xiangsilian.com/static/annotations/xxx.jpg",
"grown_image_url": "https://hair.xiangsilian.com/static/annotations/xxx.jpg",
"hairline_type": "ellipse",
"order": 1
}
]
}
}
worker 侧信息
| 项目 | 值 |
|---|---|
| worker 路径 | /api/v1/hair/grow-v2 |
| 工作流文件 | add_hair2.json |
| 输入/遮罩节点 | 26(LoadImage) |
| 输出节点 | 75(SaveImage,自动检测) |
| 提示词节点 | 60(JjkText) |
🔲【可选·仅影响 /docs】OpenAPI 表单声明
若想让网关 /docs 展示准确,在 gateway/app.py 新增 _GROW_V2_FORMS(或复用 _GROW_FORMS 并补充 gender/hair_style 字段),然后将路由函数签名改为显式声明 Form 参数(参考接口 4 的写法)。不改也不影响实际转发。
6. ✅【已完成】接口6 四庭七眼测量 v2(/api/v1/face/measure-v2)
背景:worker 侧接口 6 是接口 1 的去顶庭变体(不再完全一致):不画头顶横线/不返回顶庭数据、竖线范围发际线→下巴尖、不画人头最左/最右端线。worker 侧与接口 1 共用 _face_measure_impl(variant="v6")。
网关已新增路由(gateway/app.py 已实现,网关机器 pull 后生效):
# gateway/app.py
@app.post("/api/v1/face/measure-v2", tags=["人脸分析"])
async def face_measure_v2(request: Request):
"""接口6:四庭七眼测量 v2(去顶庭 + 去头部端线)"""
return await _proxy(request, "/api/v1/face/measure-v2")
无需额外改动:
- 入参:与接口 1 完全相同(image_file/url/base64 三选一)
- 出参:
annotated_image_base64→ 经现有rewrite_base64_to_url自动改写为annotated_image_url - worker 侧 v6 差异(去顶庭字段、变体标注)由
_face_measure_impl内部处理,网关透明转发
7. ✅【已完成】接口9/10 头部遮罩(/api/v1/head/mask、/api/v1/head/band)
gateway/app.py 已新增两条盲转发路由(同接口6 的写法):
@app.post("/api/v1/head/mask", tags=["人脸分析"])
@app.post("/api/v1/head/band", tags=["人脸分析"])
- 入参:图片三选一 +
erode_cm(接口10 另有dilate_cm)。 - 出参:
steps_common.*_base64与bisenet/segformer组内的*_base64字段——现有递归rewrite_base64_to_url已覆盖(嵌套 dict/数组),无需额外改动。 - 两个接口响应较慢(双分割模型跑两遍),注意
request_timeout_seconds。
8. 🔲【新增】接口11/12 发际线生发/带重绘(/api/v1/hairline/grow*)
worker 侧已上线 4 条路由,网关尚无对应转发路由(当前仅测试页直连 worker)。如需对外提供,参考接口9 加法:
@app.post("/api/v1/hairline/grow", tags=["生发"]) # 接口11
@app.post("/api/v1/hairline/grow_v2", tags=["生发"]) # 接口12 全参数
@app.post("/api/v1/hairline/grow_v2_final", tags=["生发"]) # 接口12 final 精简版
@app.post("/api/v1/hairline/grow_v2_final_v2", tags=["生发"])
- 入参:图片三选一 +
hairline_id(接口12 final 两变体仅需这两个)。接口11/12 全参数版还有 20+ 个调优 Form 参数,网关盲转发即可,无需逐个声明。 - 出参:
data.steps.*_base64(input/final/redraw_full/redraw_band 等各步图)→ 现有递归改写覆盖。 - ⚠️ 依赖 worker 机的 change_hair(:8801) 与 ComfyUI(:8188) 都在跑;单请求 10s+,超时建议 ≥120s。
- 契约细节见
接口文档.md接口11/12 章节。
9. ✅【新增】接口99 人脸特殊点(/api/v1/face/special-points)— 网关本机实现,不转发 worker
背景:接口99(固定编号 MediaPipe 关键点 6 组 60 点 + 5 个发际线特殊点,返回像素坐标 JSON)
纯 CPU 可跑(MediaPipe 本就 CPU;BiSeNet 分割单核实测 <1s)且调用频率低。
按约定:不用 GPU、低频的接口默认网关本机实现,不占 worker 队列
(worker 池 per_worker_concurrency=1,转发会排在生发任务后面等)。
网关代码已在本仓库 gateway/app.py 改好——与接口4 同款本机实现(显式 Form 参数 +
惰性导入 + run_in_threadpool + gw- request_id),直接 import 同仓库的
face_analysis/special_points.detect_special_points。单路排队:_if99_lock 全局互斥,
同一时刻只跑一路推理(限制 CPU 峰值 + 规避 MediaPipe 单例线程安全问题),请求 FIFO 排队。
网关机部署前检查
- Python 3.10~3.12(mediapipe 0.10.14 无 3.13 wheel)。
- 系统库(无 GUI 服务器跑 opencv/mediapipe 必需,已装可跳过):
sudo apt install -y libgl1 libglib2.0-0 libegl1
- 安装依赖(网关 venv,国内镜像):
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple \
mediapipe==0.10.14 opencv-python==4.10.0.84 numpy==1.26.4
# 可选:BiSeNet 头发分割(发际线 5 点需要)。CPU 版 torch 仅 ~200MB:
pip install torch==2.2.2 torchvision==0.17.2 --index-url https://download.pytorch.org/whl/cpu
# 不装 torch 也能跑:60 个关键点正常返回,发际线 5 点为 null(hairline_source="unavailable")
# ⚠️ 网关无 GPU——务必用上面的 CPU 专用源装 torch;装默认 PyPI 版会带 2.5G 的 CUDA 包,浪费但也能跑(自动回退 CPU)
- 权重已入 git,
git clone/pull自动到位:face_analysis/weights/79999_iter.pth(51MB)。 无需手动下载任何模型文件。 - 无 GPU 已实测:
CUDA_VISIBLE_DEVICES=""模拟验证通过(70/70 点,与 GPU 结果一致); CPU 推理 16 核 0.18s/次,网关机核少时约 0.5~2s/次,低频可接受。
部署与冒烟
cd /path/to/hair && git pull
sudo systemctl restart hair-gateway # 或网关实际服务名(uvicorn gateway.app:app)
# 冒烟(multipart 传图;经网关无需 X-Internal-Token)
curl -s -X POST http://127.0.0.1:8080/api/v1/face/special-points \
-F "image_file=@/tmp/face.jpg" | python3 -m json.tool | head -30
# 期望 code=0;data.points 含 eyebrow_107 等 60 个关键点 + hair_line_* 5 点
# 缺依赖时返回 code=1007 且 message 提示"网关依赖未安装"
注意
- 超时无需调大:单路 CPU 推理 <1s;即使排队,低频下积压可忽略。排队本身无超时, 调用方按需自带客户端超时。
- 错误码透传契约:1001(无人脸)/ 1003(非正面)/ 1007(参数/处理失败/缺依赖)/ 1008(图片格式/下载失败),HTTP 一律 200。
- worker 侧同路径接口保留(
app.py),供测试页static/test_interface99.html直连调试(需 token);生产路径是网关本机实现。 - 契约详见
接口文档.md接口99 章节。
10. ✅【无需改代码】接口1/6 增加竖线坐标 vertical_lines
worker(ubuntu3090_debug,提交 b64313d)已在接口 1、接口 6,以及接口 5/52 内嵌的 face_measure 中返回 vertical_lines。字段说明见 接口文档.md 接口 1。
网关保持现有盲转发即可:
vertical_lines是坐标 JSON,不是*_base64,rewrite_base64_to_url不会改写它。- 接口 1 含人头两端
left_head_edge/right_head_edge(耳朵不可见时该条不出现)。接口 6 只有中间 6 条,y_top/y_bottom为发际线到下巴尖。
已经做好、无需再动的
- 接口4 在网关本机实现(调豆包,不转发 worker)——已完成;config 里配
ark。 - 接口4 业务错误 HTTP 状态已统一为 200(与其余接口一致)。
- 接口3 去 original / best_hairline——网关盲转发,无需改(映射表里也没有 best_hairline)。