diff --git a/python/.gitignore b/python/.gitignore new file mode 100644 index 0000000..a846f87 --- /dev/null +++ b/python/.gitignore @@ -0,0 +1,2 @@ +venv/ +server/__pycache__/ diff --git a/python/DEPLOYMENT.md b/python/DEPLOYMENT.md new file mode 100644 index 0000000..23b185f --- /dev/null +++ b/python/DEPLOYMENT.md @@ -0,0 +1,133 @@ +# Face SDK Web 部署说明 + +本文说明如何在服务器或本机长期运行 **Face SDK Web**(FastAPI + 静态前端)。开发与接口文字说明见根目录 [README.md](./README.md)。 + +--- + +## API 文档是否已经写好? + +有两层: + +| 类型 | 说明 | +|------|------| +| **交互式文档(推荐联调)** | 服务启动后由 FastAPI **自动生成**:浏览器打开 **`/docs`**(Swagger UI)、**`/redoc`**(ReDoc),可直接试请求。机器可读的模式在 **`/openapi.json`**。 | +| **文字说明** | [README.md](./README.md) 的「API」一节描述了 `GET /health`、`GET /effects`、`POST /render` 的请求与响应;与代码保持一致即可。 | + +生产环境若不希望对外暴露 Swagger,应在网关层禁止访问 `/docs`、`/redoc`、`/openapi.json`(见下文反向代理示例)。 + +--- + +## 运行环境 + +- **Python**:3.10~3.12(MediaPipe 当前不支持 3.13)。 +- **CPU**:无需 GPU;推理与渲染在 CPU 上完成。 +- **系统**:Windows / Linux / macOS 均可;生产常见为 Linux。 + +--- + +## 部署时需携带的文件 + +在 **`python/`** 目录下至少包含: + +- `requirements.txt` +- `server/` 整个包,尤其: + - `server/main.py` 及依赖的 `.py` + - `server/assets/`(含 `face_picture_3dmax.obj`;`face_landmarker.task` 按 README 可选) + - `server/effects/`(`.png` 效果贴图) + - `server/static/`(前端 `index.html` 及静态资源) + +工作目录应为 **`python/`**(即包含 `server` 包的那一层),以便 `uvicorn server.main:app` 能正确导入。 + +--- + +## 安装依赖 + +```bash +cd /path/to/face_sdk/python +python3 -m venv venv +source venv/bin/activate # Windows: .\venv\Scripts\Activate.ps1 +pip install --upgrade pip +pip install -r requirements.txt +``` + +--- + +## 启动方式 + +### 开发 / 小规模使用 + +```bash +cd /path/to/face_sdk/python +source venv/bin/activate +uvicorn server.main:app --host 0.0.0.0 --port 8000 +``` + +- 根路径 **`/`**:返回 `server/static/index.html` +- **`/static/*`**:静态文件 +- 日志出现 **`Application startup complete.`** 即就绪 + +### 生产建议(Linux) + +- 使用 **进程管理器**(systemd、supervisor)或容器,保证崩溃自动拉起。 +- 前面挂 **Nginx / Caddy / 云 LB**,统一 TLS 与限流。 +- **Worker 数量**:README「已知限制」中说明 MediaPipe 检测在应用内通过锁串行化;单进程多 worker **不会**线性提升同一实例内的 GPU 式并行,且可能重复加载模型占内存。更高并发可采用:**多实例(每实例单 worker)+ 负载均衡**,或前置队列削峰。 + +示例(单 worker,监听本机 127.0.0.1,由 Nginx 对外): + +```bash +uvicorn server.main:app --host 127.0.0.1 --port 8000 --workers 1 +``` + +--- + +## 反向代理示例(Nginx) + +将 HTTPS 流量转到本机 Uvicorn,并可选屏蔽文档路径: + +```nginx +server { + listen 443 ssl; + server_name your.domain.example; + + # ssl_certificate / ssl_certificate_key ... + + location / { + proxy_pass http://127.0.0.1:8000; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + client_max_body_size 25m; # 略高于应用内图片上限(默认 20MB) + } + + # 生产可注释掉以下整块,禁止外网访问 API 探索页 + # location ~ ^/(docs|redoc|openapi\.json)$ { + # deny all; + # } +} +``` + +--- + +## 健康检查 + +负载均衡或编排(如 K8s)可使用: + +- **`GET /health`** — 成功返回 `200`,JSON:`{"ok": true}` + +--- + +## 防火墙与安全 + +- 若不经反向代理,直接监听 `0.0.0.0`,需在主机防火墙与安全组中 **仅开放必要端口**。 +- **`POST /render`** 接收用户上传文件,务必通过 HTTPS、合理 **`client_max_body_size`** / 限流,防止滥用。 + +--- + +## 常见问题 + +- **端口被占用**:更换 `--port` 或结束占用进程后再启动。 +- **找不到模型或 OBJ**:确认 `server/assets/` 与 `server/effects/` 已随代码一并部署,且工作目录为 `python/`。 + +更多行为与性能说明见 [README.md](./README.md)「已知限制」。 diff --git a/python/server/main.py b/python/server/main.py index 8b3ee79..78f0c83 100644 --- a/python/server/main.py +++ b/python/server/main.py @@ -13,7 +13,8 @@ from __future__ import annotations from pathlib import Path from fastapi import FastAPI, File, Form, HTTPException, UploadFile -from fastapi.responses import JSONResponse, Response +from fastapi.responses import FileResponse, JSONResponse, Response +from fastapi.staticfiles import StaticFiles from .face_renderer import ( EffectNotFoundError, @@ -32,6 +33,17 @@ _PATHS = RendererPaths( app = FastAPI(title="Face SDK Web", version="0.1.0") renderer = FaceRenderer(_PATHS) +_STATIC_DIR = _SERVER_DIR / "static" + + +@app.get("/") +def index() -> FileResponse: + return FileResponse(_STATIC_DIR / "index.html") + + +# /static/* 提供其它资源(CSS/JS 拆分时用得上;当前 index.html 内联) +app.mount("/static", StaticFiles(directory=_STATIC_DIR), name="static") + @app.get("/health") def health() -> dict: diff --git a/python/server/static/index.html b/python/server/static/index.html new file mode 100644 index 0000000..88b5d6e --- /dev/null +++ b/python/server/static/index.html @@ -0,0 +1,310 @@ + + + + + + + + +Face SDK Web · 测试 + + + +

Face SDK Web · 测试页

+
上传一张人脸图片,选择效果 ID,查看叠加渲染结果。
+ +
+
+
+ + +
+
+ + +
+ +
+
+
+ +
+
+

原图

+
未选择图片
+
+
+

渲染结果

+
等待渲染
+
+
+ + + +