Files

214 lines
24 KiB
Markdown
Raw Permalink 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.
# 1'me肌肤状态检测 H5
引导式自动三连拍(正脸/右脸/左脸),调用 **Face++ 皮肤分析(专业版 skinanalyze_pro)** 三视图真实检测,即时生成三维度×6分类肌肤状态分析与个性化护理建议的移动端 H5。
- 前端:原生 HTML/CSS/JS,零依赖、零构建(face-api.js 已本地化到 vendor/,运行时零外部请求)
- 后端:`server.js`(Node ≥ 18,零依赖)静态托管 + Face++ API 中转,**api_secret 只存在服务端**
## 目录结构
```
h5/
├── index.html 页面结构(启动 / 档案 / 拍照 / 报告 + 弹层 + 内联 SVG 取景蒙版)
├── admin.html 后台查看页(记录列表 / 失败与异常 / 统计概览,零依赖,见下文「后台查看」)
├── server.js 后端:静态托管 + POST /api/skin-analyze(Face++ skinanalyze_pro 三视图中转)+ /api/admin/*(记录查询/重试/统计/导出)
├── css/style.css C 端全部样式(375px 基准)
├── css/admin.css 后台查看页样式
├── js/app.js 全部交互(屏幕切换、肤质轮播、引导式三连拍、报告渲染)
├── js/admin.js 后台查看页脚本(纯原生 ES5)
├── js/skin-score.js 三维度×6分类评分引擎(纯逻辑 UMD,window.SkinScore,可 node 自测;服务端评分复用同一份)
├── js/head-pose.js 头部姿态估算(纯逻辑 UMD,window.HeadPose,可 node 自测)
├── data/records.jsonl 检测记录库(一行一条 JSON,零依赖嵌入式存储,已 .gitignore)
├── data/photos/ 检测照片 data/photos/<record_id>/{front,left,right}.jpg(本地磁盘代替对象存储)
├── data/seed.js 演示种子数据(一次性:node h5/data/seed.js,覆盖重写 records.jsonl)
├── data/results/ 每次检测的 Face++ 原始返回落盘(复核用,已 .gitignore)
├── js/head-pose.js 头部姿态估算(纯逻辑 UMD,window.HeadPose,可 node 自测)
├── test/regression.js 真值回归(node h5/test/regression.js,对照 docs xlsx 两组三视图真值)
├── vendor/faceapi/ face-api.js 0.22.2 + 模型(本地化,见该目录 README)
├── assets/
│ ├── favicon.svg 站点图标
│ ├── img/ 背景图、肤质插画、拍照底图、报告检测/产品图
│ ├── mock/ mock 报告页数据源(report-fixture.js,真实案例1数据)
│ └── svg/ 品牌 Logo 与控件图标
└── preview/ 开发期整页截图(仅存档,部署时可整目录剔除)
../apikey.bash Face++ 密钥(export SKIN_API_KEY=... / SKIN_API_SECRET=...,已 .gitignore)
```
## 密钥配置
二选一:
1. 环境变量:`export SKIN_API_KEY=xxx SKIN_API_SECRET=xxx` 后启动 server.js
2. 密钥文件:项目根目录放 `apikey.bash`(内容为 `export SKIN_API_KEY=...` 等,server.js 会自动解析)
**密钥文件已被 .gitignore 排除,严禁提交到版本库。**
## 本地运行
```bash
cd h5
node server.js # 默认 8080,可用 PORT=xxx 修改
# 打开 http://127.0.0.1:8080
```
调试:
- `http://127.0.0.1:8080/?mock=1` 使用内置样本(`SkinScore.MOCK_RESULT`/`MOCK_SIDE`)跳过真实检测;不加载 face-api、不跑姿态检测,显示快门手动逐步推进(离线/自动化验证用)。
- `http://127.0.0.1:8080/?mock=1&screen=report` 直渲 mock 报告页(**仅调试用**,供无头浏览器截图验证布局;档案缺省按 28 岁/干性肌肤);mock 数据源为 `assets/mock/report-fixture.js`(真实案例1三视图检测返回,含 face_maps 图层 data URL 与 marks 坐标),改该文件即可换样本。
## 拍照交互(动态捕捉自动三连拍)
三步严格按顺序,本步拍到合格照片才进入下一步(左右一律以用户本人为准):1/3 正对屏幕(target `front`)→ 2/3 右脸出镜(用户头向自己的左侧转,target `right`,产物作 `right_image`)→ 3/3 左脸出镜(头向自己的右侧转,target `left`,产物作 `left_image`)。送检前 `photosByView` 按 target 重排为 [正, 左, 右]。页面内没有返回按钮,微信/浏览器返回键经 History API 回到档案页并释放摄像头。
- **取景蒙版**:内联 SVG(`#cam-mask`)按步骤切换轮廓——正脸步为正脸剪影(含耳部),侧脸步为对应方向的侧脸剪影;**轮廓路径由 Figma 拍照示意图(`design/assets/images/*-拍照示意.png` 的 alpha 镂空区)逐像素描摹生成**,坐标为 Figma 375×812 整帧,viewBox 只取 y 86–812;框外 85% 黑,轮廓线 2.5px 半透明白。检测运行时轮廓内有自上而下的扫描带(`#cam-clip` 由 app.js 复制轮廓生成);未检测到人脸时轮廓呼吸动画;条件合格时轮廓变绿。
- **预览铺满**:`#cam-video` 保持现有 `object-fit:cover` 铺满与镜像策略,不改取景视觉。
- **动态提示**:根据真机难拍反馈适度放宽(参数版本 `2026-10-12-fourth-v2`),单人、检测置信度≥0.4、人脸框高占原始画面 30%~68%、水平中心偏离≤16% 屏宽、完整入镜且距原始帧边缘≥1%。正脸 |yaw|≤15°/|pitch|≤18°,侧脸转头幅度 30°~70°/|pitch|≤20°,两者 |roll|≤15°;脸部平均亮度 65~235。参数集中在 `js/capture-quality.js` 与 `js/head-pose.js`,均为经验值,不等价于服务端质量分。
- **合格后倒计时拍摄**:连续稳定至少 450ms 且至少 2 个合格检测帧;容许 yaw/pitch 漂移≤8°、中心移动≤原帧 5.5%、框高占比变化≤6.5 个百分点。漏检、超限漂移或帧间隔超过 2 秒均重置稳定累计;每张照片播放原有 `countdown-321.mp3`,等音频完整结束后再白屏抓拍,本地复验实际 JPEG,合格才推进。倒计时期间锁定快门及翻转,退出或隐藏页面取消未完成拍摄。网页内自动/降级手动抓拍均适用;系统相机降级的原生快门由操作系统控制。音频不支持、未解锁或加载失败时沿用无声抓拍,避免阻塞流程。
- **两张之间停顿**:正脸、右脸拍完后中央弹出停顿卡约 2s(「正脸拍摄完成 / 下一张:右脸出镜,请向左转头」),停顿期间不检测,结束后再切到下一步并播下一步引导,让用户对下一张有心理预期。
- **实时检测**:face-api 从本地 `vendor/faceapi/` 加载(`tinyFaceDetector` + `faceLandmark68Net`),进入拍照页开始加载(状态提示「检测引擎加载中…」)。检测循环 ~150ms 一帧,特征点取自原始帧(预览 CSS 镜像不影响),个别前置帧已镜像的机型由侧脸自校准翻转 `sideYawFlip`;连续漏检 ≥3 帧才报「未检测到人脸」。
- **镜像策略**:前置镜头预览镜像(照镜子习惯);后置预览不镜像,侧脸轮廓随之翻转(`.cam-mask.flip`)。**抓帧一律不镜像**,三张照片都是摄像头原始视角。
- **白屏补光**:全屏纯白覆盖层瞬间点亮,~300ms 后抓全分辨率帧(原始视角 + 最长边 1280 JPEG),保持 ~300ms 再淡出,同步 `navigator.vibrate(30)`。
- **快门只在无法实时识别时出现**:系统相机降级、face-api/模型加载失败、`?mock=1` 调试。文件照片共用同一套本地单人/距离/完整入镜/角度/光线校验,不合格停在当前步;系统照片可能已镜像,侧脸不判断左右,并标记 `direction_checked:false`。本地模型加载中最多等 8 秒;确实不可用时保留原有降级通路,但后台明确记录 `unchecked/model-unavailable`,不冒充检查通过。服务端质量结果仅记录,不新增重拍阻断。
- **报告等待与质量留痕**:本次正脸实拍覆盖 PSD 白色人脸占位,保持与拍摄页相同的等比铺满和前置预览镜像(仅显示,不改送检 JPEG);保留 PSD 框、文案、装饰图标与约 1 秒上下往返扫描。成功跳转等待至少一次实际动画循环,错误/取消立即恢复并清理等待页照片引用。PSD 素材加载失败仍显示照片、文字和取消入口。拍摄质量备注不在用户报告展示,后台列表、详情和 CSV 保留侧脸质量状态与前端参数版本。前端诊断为客户端自报,仅供校准,不作为服务端质量真值。
- **相册选图**:相机页「相册」按钮可选当前步的图片,与降级拍照同管线(压 1280 → 校验 → 推进)。
- **语音播报**:步骤引导/姿态提示/拍摄完成/分析中/失败,文案与文件名契约见 `../docs/语音播报内容设计文档_V1.0.md`。新增的提示(居中、抬头/低头、光线)暂无音频,清单缺失的条目静默跳过,只显示文字。
- **无进展提示**:单步 12s 仍未拍到,底部浮出「识别不到?请正对镜头,并到光线充足处」(不阻塞流程)。
- **资源管理**:离开拍照页停止检测循环、清全部定时器、释放摄像头;`camSeq`(流程)/`camSeqCam`(相机)双令牌防竞态;face-api 模型只加载一次。
## 档案页与报告页
2026-10-13 当前留存规则:仅微信登录记住本机基础信息、在后端保留最近 5 份完整报告并支持跨设备查看。游客使用仅内存的临时会话,不持久化身份/基础信息/历史报告,也不新增后台检测记录、照片或原始结果;本次报告仍可正常生成并查看,刷新后基础信息不回填。旧服务器数据不自动删除。详见 [游客不留存与样式调整](../docs/游客不留存与弹窗护理间距调整-2026-10-13.md)。下方早期的 localStorage 报告历史说明已被此规则替代。
- **档案页**:出生年月 / 性别 / 所在地区三个滚轮选择器同一风格(`vendor/mobile-select`,MIT),出生年月自动换算年龄;地区为省–市两级联动,数据为 `js/region-data.js`(由 china-division 2.7.0 `pc.json` 生成,另含港澳台与「海外」)。当前账号在本机浏览器记住已确认的基础信息和日常肤质,再次进入自动回填,滚轮也定位到原值;年龄按出生年月重新计算。肤质支持跟手拖动、松手吸附、轻扫切换及边界回弹,纵向手势仍滚动页面。详见 [基础信息记忆与肤质拖动](../docs/基础信息记忆与肤质拖动-2026-10-12.md)。
- **报告页**:无返回按钮与页头标题;总分环形图 + 肌肤年龄与「比实际年龄年轻/大 N 岁」;三维度分数 + 进度条(最低分标红并挂「优先关注」);维度详情为原布局——一张卡内紧塑力/活力感/稳定力三个 Tab,每个 Tab 两条「竖排标题 + 左图右文」;点检测图全屏放大,再点或按返回键回到报告。
## 检测流程与评分渲染
1. 三视图送检:POST `/api/skin-analyze`,请求 `{ front_image, left_image, right_image, profile? }`(base64 可带 dataURL 前缀;正脸=photos[0],左=photos[2],右=photos[1];`profile` 为可选档案 `{real_age,gender,region,daily_skin_type}`,后台记录用)。
2. 服务端转发 Face++ **专业版 `skinanalyze_pro`**(三视图必须走 `image_file` 文件形式),请求 `return_side_results=jawline_info` 取左右下颌线角度,`return_maps`/`return_marks` 取叠加图与标注;响应 `{ ok:true, result, face_rectangle, side:{ left, right } }`,失败 `{ ok:false, code, message }`(message 已是用户可读文案)。`result.face_maps` 为对象,含报告页所需图层的完整 data URL(`red_area`/`brown_area`/`texture_enhanced_lines`/`texture_enhanced_pores`/`texture_enhanced_blackheads`,由 server.js 的 `REPORT_MAPS` 常量控制);`marks` 坐标(`acne`/`brown_spot`/`mole`/`closed_comedones`/`acne_pustule`/`acne_nodule`/`wrinkle_mark`/`melanin_mark`/`dark_circle_mark`)随 `result` 原样透传,与上传图片同一像素空间。
3. 评分由 `js/skin-score.js` 引擎完成,公式/锁值/话术/产品推荐以 `docs/三维度评分与话术生成需求文档_V2.3.docx` 为最高契约(V2.3 口径,含 §8.5 护理建议五段):
- 6 分类:轮廓 = min(眼袋映射分, 下颌线角度分);皱纹直接采用 `score_info.wrinkle_score` 整体分(不反向、不按分区严重度限分;分区仅作说明/可视化,缺失整体分则不回退旧规则);光滑(毛孔/黑头/纹理);肤色水润(色素×水润);敏感(泛红面积取 `red_spot.red_spot_area`/红区分);痘痘(分级映射×丘疹 `acne.count` 计数扣分);
- 三维度木桶 min:紧塑力(轮廓+皱纹)、活力感(光滑+肤色水润)、稳定力(敏感+痘痘);
- 痘痘计数:闭口、丘疹、脓包、结节、痘印分别取正/左/右三视图最大数量(不相加),权重1/2/4/6/1.5、合计扣分封顶50;整体 `acne_score` 仍取正脸。报告文案使用相同计分数量;缺失不当0,后台详情显示三视图及最终取值、扣分。
- 肤色项:色素扣斑分80%+`dark_circle_score`黑眼圈评分20%,与水分项各占50%得到肤色水润P;缺项按有效权重归一。肤色页在原照片上以30%强度叠加色素图(缺图时用真实色素坐标),黄褐斑/雀斑显示检出状态,没有对应坐标不画伪造定位框。
- S = 7 + 3×P/100;档位:良好(P≥90)/改善(80–90)/预警(<80);生物可视年龄限制在用户实际年龄±5岁:`max(实际年龄−5, min(实际年龄+5, 原始肌龄))`,如实际25、接口40展示30;原始肌龄和推荐规则不变。缺少实际年龄展示原始肌龄,缺少肌龄显示`--`;三视图版 18 条话术;
- 精华推荐三重判据(均分档/最差项约束/年龄组档)取最强档,triggers 记录命中判据。
4. 前端 `SkinScore.buildViewModel(data.result, data.side, { realAge, skinType })` → viewModel 直渲报告页:
- 头部:综合得分通过 `SkinScore.weightedTotal(vm.dims)` 计算,紧塑力×4+透亮感×3+稳韧力×3,使用维度S分数(两位小数)加权、四舍五入取整数,不使用接口 `total_score`;任一维度缺失显示 `--`。后台列表、详情、CSV采用相同公式,历史报告按已保存维度计算展示,不改写原文件;肤龄读取 `SkinScore.reportSkinAge(vm, result)`,原值缺失显示 `--`;三维度 S(1 位小数)+ 档位配色(良好绿/改善琥珀/预警红);
- 三个 Tab 面板各展示所辖两分类:分类名、S + 档位徽章、话术正文(`vm.cats.*.text`);P 值不直接展示,`P=null` 的分类显示"本次未检出有效数据";
- 报告底部:降级标注(`vm.notes` 中与侧脸/下颌线/补拍相关的条目,去掉接口质量码后展示)、护理建议五段(`vm.careAdvice`)、精华推荐卡(`vm.essence.name` + 命中判据;推荐正文已在护理建议第 4 段,卡内不重复);
- 近期优先关注 = 最差维度(`vm.focus`);6 分类各配检测可视化图:轮廓=正脸实拍、皱纹=正脸压暗+`texture_enhanced_lines` 皱纹增强线(线图层几乎全透明,直挂白底不可读,故与压暗沉脸合成)、光滑=正脸+色斑/痣/痤疮标记叠加、肤色=`brown_area` 色沉图、敏感=`red_area` 红区图、痘痘=正脸+闭口粉刺/痤疮标记叠加(marks 按原像素坐标叠加到正脸实拍)。
5. 浏览器冒烟:`node h5/test/e2e-browser.mjs`(需全局 `playwright` + `npx playwright install chromium`,服务跑在 8080)——mock 三连拍到报告页,并核对 14 条语音可拉取、流程语音完整播完不被截断。
6. 真值回归:`node h5/test/regression.js` 以 `docs/皮肤检测测试结果_三视图_20260926.xlsx`(sheet「三视图检测汇总」,数据第 3/4 行=案例1/案例2)两组三视图真值对照评分引擎输出。
7. **报告历史**:检测成功后按「检测」为单位存入 localStorage(`juzi.reports.v1`,上限 5 条,超配额逐条淘汰)——含三张照片、档案、全部评分数据与 marks 坐标,**不含 face_maps 整幅图层**(体积原因;回看时皱纹/肤色/敏感图降级为占位,光滑/痘痘标记叠加仍可用)。当前无 UI 入口(第9轮移除),数据仅作本地追溯。
8. **原始返回落盘**:服务端每次检测成功后把 Face++ 完整原始返回写入 `h5/data/results/<时间戳>-<request_id>.json`(复核/真值用,已 .gitignore;写盘失败不影响响应)。
9. 检测失败按服务端 message 提示并退回档案页。
## 后台查看(admin.html)
本地查看版后台,浏览器打开 **`http://127.0.0.1:8080/admin.html`**(无登录鉴权,仅内网/本地使用;上线前需加网关或鉴权)。请求体除三张照片外还带可选档案 `profile:{real_age,gender,region,daily_skin_type}`(前端 `js/app.js` 自动附带,年龄非法按 null),服务端据此落库并在检测成功时用 `SkinScore.buildViewModel` 现场评分(与 C 端同一份公式)。
- **数据落库**:每次有效调用(正脸照片校验通过起)生成 `record_id`,照片写 `data/photos/<record_id>/`,记录(档案/照片地址/拍摄与上传状态/检测状态与失败原因/评分/文案/精华/耗时/raw 文件名/重试关系)追加到 `data/records.jsonl`。C 端响应结构不变。
- **记录列表**:多条件筛选(时间区间/检测状态/失败原因/年龄/产品组别/维度+档位/记录ID),行点击开详情抽屉(照片大图与下载、状态、六分类与三维度评分表、文案全文、护理建议、精华与触发判据、计算备注、接口原始 JSON 折叠查看、手动重试)。
- **时间口径**:后台列表/异常/详情显示、纯日期筛选、今日及每日统计、CSV时间统一为北京时间(UTC+8),由 `js/beijing-time.js` 共享处理,不依赖服务器或浏览器时区。原始记录及JSON接口时间仍为UTC ISO;完整ISO筛选保留显式时区对应的时间点。
- **失败与异常**:失败原因分布条 + 失败列表(每条可一键重试:用原照片重新检测,生成新记录并挂版本链)。
- **统计概览**:今日/累计/成功率/平均耗时卡片,三维度档位分布,产品组别分布(纯 CSS 饼图),近 14 天每日检测量柱状图,按日期区间导出 CSV(UTF-8 BOM,不含 raw_json)。
- **演示数据**:`node h5/data/seed.js` 生成约 10 条演示记录(覆盖重写 records.jsonl,照片复制自 `reference/facepp-skinanalyze/examples/images/` 真实三视图)。
### Admin API(统一返回 `{ok:false,message}` 错误)
| 接口 | 说明 |
| --- | --- |
| `GET /api/admin/records` | 分页查询。参数:`from/to`(ISO 日期)、`detect_status`、`fail_code`、`age_min/age_max`、`group`、`dim`(firm/vita/stab)+`dim_level`、`q`(record_id 前缀)、`page`、`page_size`(默认 20)、`only_fail=1`。返回 `{total,page,page_size,items}`(倒序,含照片 URL,不含 raw_json 与文案全文) |
| `GET /api/admin/records/:id` | 完整记录 + `raw_json` 全文(读失败为 null)+ `photos` URL + `versions`(版本链:父记录与重试子记录) |
| `POST /api/admin/records/:id/retry` | 手动重试:原照片重新检测,生成新记录(`retry_of`=原 id),原记录 `retry_count+1`,返回新记录 |
| `GET /api/admin/photos/:id/:which` | 照片回显(`which`=front/left/right),`image/jpeg` inline;文件不存在 404 |
| `GET /api/admin/stats` | 今日/累计检测量、成功率、平均耗时、失败原因分布、三维度档位分布、产品组别分布、近 14 天每日检测量/成功数 |
| `GET /api/admin/export?from=&to=` | CSV 导出(UTF-8 BOM,Content-Disposition attachment 带日期区间文件名);禁止 raw_json 进单元格(Excel 单格 32767 字符截断) |
## 10.09 更新
新版已增加登录入口、后端账号隔离的最近 5 份完整报告、七项独立检测视图和新版话术/素材。
本机浏览器只缓存随机身份凭据,报告保存在服务器;微信需公网 HTTPS、具备网页授权权限的服务号及相应环境变量。
详见 [新版实施与验收](../docs/10.09新版H5实施与验收.md) 和 [微信登录接入与公网部署说明](../docs/微信登录接入与公网部署说明.md)。下方旧功能说明以这两份更新文档为准。
2026-10-15 护理展示更新:`ReportUI.build` 从七项独立小维度分中选最低三项,再将属于当前优先关注大维度的入选项前置;同分按关注优先、文档顺序确定。肤色、水分分析及护理各自独立分档,不再共用肤色水润合成分。新版护理全文以 `reference/AI测肤报告页话术修改.md` 为准;不改变原三维度/综合得分/产品推荐,不批量改写历史presentation。详见 [七项护理选三与关注优先](../docs/七项护理选三与关注优先-2026-10-15.md)。
随后用户确认取消合成分参与维度计分:透亮感改为独立肤色、水分、平滑度三项P的最低有效值,不再平均肤色和水分。新分数用于总分、优先关注及原有边界推荐判断。后台新记录展示七项独立分数,历史记录不重算;旧tone字段仅为兼容。详见 [新版透亮感公式](../docs/透亮感改为三项独立最低分-2026-10-15.md)。
## 部署
需要 Node ≥ 18 运行 `server.js`(负责静态与 API 中转),**不能用纯静态托管**(密钥不能进前端)。
```bash
PORT=8080 node server.js # 建议 pm2 / systemd 常驻
```
### 线上部署(juzi.xiangsilian.com)
- Node 只绑回环(`HOST` 默认 `127.0.0.1`,公网入口仅 nginx;需局域网调试才 `HOST=0.0.0.0`),nginx 反代 + certbot HTTPS,见 `/etc/nginx/sites-available/juzi`。
- **后台 Basic Auth**:nginx 对 `admin.html`、`css/admin.css`、`js/admin.js`、`/api/admin/` 做 `auth_basic`(账密 `/etc/nginx/.htpasswd-juzi`),`/data/`(照片/原始 JSON/记录库)由 nginx 404 + server.js 静态层 403 双重封锁,照片只能走鉴权后的 `/api/admin/photos`。**上线正式版前应替换为文档要求的后台账号体系。**
- 服务常驻建议 systemd:
```ini
[Unit]
Description=juzi skin h5
After=network.target
[Service]
WorkingDirectory=/home/ubuntu/juzi/h5
ExecStart=/usr/bin/node server.js
Restart=always
[Install]
WantedBy=multi-user.target
```
### Windows 本机部署(提测用)
```bat
h5\deploy-local.bat :: 默认 PORT=8088,日志写 h5\server.log
```
登记开机自启(用户级计划任务,需系统允许;若提示"拒绝访问",直接双击 `deploy-local.bat` 或手动运行即可):
```bat
schtasks /create /tn "JuziSkinH5" /tr "\"C:\Windows\System32\cmd.exe\" /c \"D:\juzi\h5\deploy-local.bat\"" /sc onlogon /f
:: 移除: schtasks /delete /tn "JuziSkinH5" /f
```
局域网提测访问 `http://<本机局域网IP>:8088/`。注意:① 首次监听若弹 Windows 防火墙提示需允许 node.exe;② **手机走局域网 HTTP 时 getUserMedia 不可用**,会自动降级为系统相机三连拍(照片仍按步校验,分析链路完整),要测动态捕捉自动拍照需在本机浏览器访问 `http://127.0.0.1:8088`,或上 HTTPS。
nginx 反代示例(静态与 API 都交给 server.js,nginx 只做 HTTPS 终结):
```nginx
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
client_max_body_size 20m; # 与 server.js 请求上限一致
}
}
```
> **HTTPS 必需**:getUserMedia 相机权限仅在安全上下文可用(localhost 除外)。正式域名必须上 HTTPS,否则前端自动降级为系统相机拍照。
## 已知占位清单
- 产品图(prod-1.jpg)为设计稿素材;检测图已替换为用户实拍。
- 语音 14 条已由 `tools/gen_voice.py` 生成;改文案后重跑脚本并同步 `manifest.json`。
- 「了解更多」为开发中占位(Toast 提示);拍照页右下按钮为切换前后镜头(系统相机降级模式下提示在系统相机内切换)。
- 产品推荐卡(COREⅡ 双仓精华液)为设计稿固定素材,不随精华推荐档位(25+/35+/50+)变化,待产品提供三档对应素材。
- **姿态与距离参数已用模拟摄像头校准**(2026-09-27):以 C3 三视图(+45° 侧脸)生成 Y4M 模拟摄像头视频,闭环修正了 yaw 增益(2.2→3.8)、pitch 基线(0.32→0.45)、check() 双重镜像符号 bug、距离阈值(0.35/0.8→0.26/0.58;2026-10 预览缩小 15% 后按比例换算为 0.30/0.68)、检测参数(320/0.4→416/0.3)与漏检 3 帧容忍;自动三连拍在无头浏览器全程通过(e2e E7)。**真机仍建议复核一次**(尤其 2026-10 新增的居中/亮度阈值):不同机型前置镜像策略/取景比例可能微调体验(校准接口:调用方 `mirrored` 标志与 `HeadPose.config`)。
## 技术说明
- 尺寸换算:设计稿 1080px 基准 ÷ 2.88 = 375px 落地基准。
- 图片性能:首屏背景 preload;首屏外 `loading="lazy"`。
- 快门防连点/双流程令牌(camSeq/camSeqCam)防竞态;拍照页取景框外纯黑(对齐 Figma),白字直接落在黑色上。
- face-api.js 0.22.2 与模型全部本地化(`vendor/faceapi/`,约 1.4MB),运行时零外部请求,微信 webview / iOS Safari 弱网与内网部署可用;许可证(MIT / Apache-2.0)见 `vendor/faceapi/README.md`。