Files
hair/docs/网关待改动.md
T
xsl 544c2a83d6 feat(gateway): 接入52号发际线接口与公开测试页
新增52号同路径转发,沿用现有主备派发、鉴权、队列和图片URL改写。空图片保留既有base64字段名,不带入worker算法及IP限制。

同步52号测试页、图片解析和矩形叠加依赖,公开页面不包含内部Token。补全网关契约与部署文档,Nginx示例读取超时延长至660秒。

验证:42项网关测试通过;临时网关连接真实GPU worker,5号三档与52号七型URL、长图裁切和边框均通过浏览器检查。
2026-11-04 11:15:58 +08:00

253 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 网关侧改动清单(worker 近期变更引发)
> 给网关开发:以下是 worker/契约近期变化里**与网关有关**的点。标 ✅ 的我已在本仓库 `gateway/`
> 改好(你 review/拉取即可);标 🔲 的是**建议你确认或改**。功能必需只有第 1 条。
---
## 接口52(2026-11-04 已接入main)
新增`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网关接入](接口52网关接入.md)。
---
## 1. ✅【功能必需】base64→URL 落盘扩展名按内容嗅探(支持 JPG)
**背景**:接口 **2/3/5 的返回图改成了 JPG**(体积约小 9×),**接口 1 标注图仍是 PNG**(含透明)。
网关把 `*_base64` 落盘时若**硬编码 `.png`**,JPG 会被存成 `.png`(内容是 JPG、扩展名错)。
**改动**(`gateway/forward.py` 的 `rewrite_base64_to_url`,已改):
```python
# 原: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`、
接口5 `hairline_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 的同路径:
```python
# 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 一致)
```json
{
"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 后生效):
```python
# 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 的写法):
```python
@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 加法:
```python
@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`](接口文档.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 排队。
### 网关机部署前检查
1. **Python 3.10~3.12**(mediapipe 0.10.14 无 3.13 wheel)。
2. **系统库**(无 GUI 服务器跑 opencv/mediapipe 必需,已装可跳过):
```bash
sudo apt install -y libgl1 libglib2.0-0 libegl1
```
3. **安装依赖**(网关 venv,国内镜像):
```bash
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)
```
4. **权重已入 git**,`git clone/pull` 自动到位:`face_analysis/weights/79999_iter.pth`(51MB)。
无需手动下载任何模型文件。
5. **无 GPU 已实测**:`CUDA_VISIBLE_DEVICES=""` 模拟验证通过(70/70 点,与 GPU 结果一致);
CPU 推理 16 核 0.18s/次,网关机核少时约 0.5~2s/次,低频可接受。
### 部署与冒烟
```bash
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`](接口文档.md) 接口99 章节。
---
## 已经做好、无需再动的
- **接口4 在网关本机实现**(调豆包,不转发 worker)——已完成;config 里配 `ark`。
- **接口4 业务错误 HTTP 状态**已统一为 200(与其余接口一致)。
- **接口3 去 original / best_hairline**——网关盲转发,无需改(映射表里也没有 best_hairline)。
---
> 对外字段映射总表见 [`实现说明.md`](实现说明.md) §1;契约以 [`接口文档.md`](接口文档.md) 为准。