docs: add development-machine handoff and server deploy configs

换开发机继续开发。新机器只需要拉仓库 + 自备工具链 + 单独拷贝两个机密文件,
服务器/聚石塔/SAE 全部沿用,不需要重建。

- docs/HANDOFF-2026-11-02.md:开发机交接(环境、仓库地图、三套测试、
  本地怎么跑、云端资源清单、上线方式、11 条关键结论、9 项待办、故障对照表)
- deploy/:服务器侧配置入库(原先只存在于机器上)
  - systemd/juzi-taobao.service(18081,独立数据目录与凭据文件)
  - nginx/tb-location.conf(/tb/ 反代 + 只放行聚石塔 NAT EIP)
  - add-tb-nginx.sh(幂等插入,先备份,nginx -t 通过才 reload)
  - README.md(重建与回滚步骤)
- tools/env-java.ps1:优先用仓库内便携工具链,缺失时回退 JAVA_HOME/PATH,
  换机器不用再下便携 JDK/Maven
- README.md:交接文档入口更新
This commit is contained in:
xsl
2026-11-01 23:34:26 +08:00
parent fed52930cc
commit 43a5373233
7 changed files with 564 additions and 15 deletions
+3 -1
View File
@@ -5,7 +5,9 @@
- `h5/`:现有浏览器/微信公众号 H5、管理后台,以及共享后端 `server.js`。
- `taobao/`:淘宝原生小程序。开发、联调、测试说明见 [taobao/README.md](taobao/README.md)。
- `backend-java/`:SAE + 淘宝云网关的 Java 最小连通性 MVP;操作步骤见 [docs/SAE-Java-MVP-操作步骤.md](docs/SAE-Java-MVP-操作步骤.md)。
- 当前交接:[docs/HANDOFF-2026-11-01.md](docs/HANDOFF-2026-11-01.md)。
- 当前交接(**换开发机先读这份**):[docs/HANDOFF-2026-11-02.md](docs/HANDOFF-2026-11-02.md)。
- 上一轮交接:[docs/HANDOFF-2026-11-01.md](docs/HANDOFF-2026-11-01.md)。
- 服务器侧配置(systemd / nginx `/tb/` 白名单 / 安装脚本):[deploy/README.md](deploy/README.md)。
- `rednote/`:复用 H5 的持久浏览器身份入口,独立运行和存储。说明见 [rednote/README.md](rednote/README.md)。
- `h5/js/skin-score.js`、`report-ui.js`、`report-copy.js`:业务规则的源文件。小程序内的副本由同步脚本生成并检查哈希。
- `h5/lib/taobao-*.js`:淘宝网关验签、短期上传/异步任务和独立 API 路由。
+127
View File
@@ -0,0 +1,127 @@
# deploy —— 1me.xajuzi.com 上的「淘宝专用」Node 实例
这份目录存放**不在 git 里自动生效**的服务器侧配置:systemd unit、nginx 片段、安装脚本。
换机器/换人接手时,按本文重建即可。
## 拓扑
```
淘宝小程序(taobao/,AppKey 35486228)
→ 淘宝云网关(注入 app_key / open_id / sign,单请求 10s / 128KB 上限)
→ 聚石塔云应用 1me-net(62829) → 私网 CLB 192.168.65.185:80
→ SAE 应用 nnn(taobao-mvp-backend-0.0.4-forward.jar,mode=forward,纯透传)
→ 聚石塔 NAT 网关出网(EIP 47.92.113.176)
→ https://1me.xajuzi.com/tb/ (nginx 白名单只放行上面这个 EIP)
→ juzi-taobao.service(Node,127.0.0.1:18081,本文档这套配置)
→ 旷视 Face++ skinanalyze_pro
```
生产 H5(`/state/`)和红书渠道(`/rednote/`)跑在同机另外两个 systemd 服务上,**8080/8081 端口,和本实例完全隔离**。
## 三个服务的分工
| 服务 | 端口 | 代码目录 | 用途 |
|---|---|---|---|
| `juzi-h5.service` | 8080 | `/home/ubuntu/juzi/h5` | 生产 H5(微信登录、报告、后台) |
| `juzi-rednote.service` | 8081 | `/home/ubuntu/juzi-rednote/current/h5` | 小红书渠道隔离实例 |
| `juzi-taobao.service` | 18081 | `/home/ubuntu/juzi-tb/h5` | **本目录新增**:淘宝小程序专用 |
## 重建步骤
### 1. 代码
`/home/ubuntu/juzi-tb` 是**独立的一份 h5 代码**(不是生产目录的软链),来自本仓库的 `h5/`。
部署方式(本地执行,把仓库里的 h5 打包传上去):
```powershell
# 本地仓库根目录
tar -czf temp/juzi-h5.tgz --exclude=h5/data --exclude=h5/test --exclude=h5/scripts -C . h5
scp temp/juzi-h5.tgz ubuntu@1me.xajuzi.com:/tmp/
```
```bash
# 服务器上
sudo mkdir -p /home/ubuntu/juzi-tb /home/ubuntu/juzi-tb-data
sudo chown -R ubuntu:ubuntu /home/ubuntu/juzi-tb /home/ubuntu/juzi-tb-data
chmod 700 /home/ubuntu/juzi-tb-data
tar -xzf /tmp/juzi-h5.tgz -C /home/ubuntu/juzi-tb
# 旷视凭据复用生产那份(gitignored,不随仓库走)
mkdir -p /home/ubuntu/juzi-tb/reference/facepp-skinanalyze
cp /home/ubuntu/juzi/reference/facepp-skinanalyze/credentials.env \
/home/ubuntu/juzi-tb/reference/facepp-skinanalyze/credentials.env
chmod 600 /home/ubuntu/juzi-tb/reference/facepp-skinanalyze/credentials.env
```
> `h5/server.js` 启动时如果找不到旷视 key 会直接 `exit(1)`,所以这一步不能漏。
### 2. 淘宝凭据
```bash
sudo mkdir -p /etc/juzi
sudo tee /etc/juzi/taobao.env >/dev/null <<'EOF'
TAOBAO_APP_KEY=35486228
TAOBAO_APP_SECRET=<35486228 的 AppSecret,向项目负责人索取>
EOF
sudo chmod 600 /etc/juzi/taobao.env && sudo chown root:root /etc/juzi/taobao.env
```
这个文件被 `h5/lib/taobao-auth.js` 读取,用于校验云网关签名。**没有它,`/session` 一律 401。**
### 3. systemd
```bash
scp deploy/systemd/juzi-taobao.service ubuntu@1me.xajuzi.com:/tmp/
ssh ubuntu@1me.xajuzi.com "sudo install -m 644 -o root -g root /tmp/juzi-taobao.service /etc/systemd/system/ \
&& sudo systemctl daemon-reload && sudo systemctl enable --now juzi-taobao && systemctl is-active juzi-taobao"
```
### 4. nginx
```bash
# 本地
scp deploy/add-tb-nginx.sh deploy/nginx/tb-location.conf ubuntu@1me.xajuzi.com:/tmp/
# 服务器
ssh ubuntu@1me.xajuzi.com "sed -i 's/\r\$//' /tmp/add-tb-nginx.sh && bash /tmp/add-tb-nginx.sh /tmp/tb-location.conf"
```
脚本是幂等的(检测到 `location /tb/` 就跳过),会先备份成 `juzi.bak-tb-<时间戳>`。
### 5. 验收
```bash
# 服务器本机(127.0.0.1 在白名单里)
curl -sk -H 'Host: 1me.xajuzi.com' https://127.0.0.1/tb/api/health
# 期望 {"ok":true}
# 公网(应被白名单挡掉)
curl -s -o /dev/null -w '%{http_code}\n' https://1me.xajuzi.com/tb/api/health
# 期望 403
# 生产 H5 不受影响
curl -s -o /dev/null -w '%{http_code}\n' https://1me.xajuzi.com/state/api/health
# 期望 200
```
最后在聚石塔 SAE 容器里再验一次(这是唯一走 NAT 的路径):
```bash
curl -s https://1me.xajuzi.com/tb/api/health # 期望 {"ok":true}
```
## 回滚
```bash
sudo systemctl disable --now juzi-taobao
sudo cp /etc/nginx/sites-available/juzi.bak-tb-<时间戳> /etc/nginx/sites-available/juzi
sudo nginx -t && sudo systemctl reload nginx
```
生产两个服务完全不受影响(端口、目录、数据都是独立的)。
## 数据
- 运行数据:`/home/ubuntu/juzi-tb-data`(700)—— 照片 `photos/`、原始返回 `results/`、记录 `records.jsonl`、账号 `accounts/`
- 与生产的 `/home/ubuntu/juzi/h5/data` 完全分开,两边用户/报告不互通
- **没有任何自动清理**:每次检测约 10–12MB 常驻磁盘,长期要换 OSS + RDS
+47
View File
@@ -0,0 +1,47 @@
#!/bin/bash
# 幂等地把 deploy/nginx/tb-location.conf 插入 /etc/nginx/sites-available/juzi。
# 只做「插入一段 location」,不改动任何已有 location;先备份,nginx -t 通过才 reload。
#
# 用法(在 1me.xajuzi.com 上):
# scp deploy/add-tb-nginx.sh deploy/nginx/tb-location.conf ubuntu@1me.xajuzi.com:/tmp/
# ssh ubuntu@1me.xajuzi.com "sed -i 's/\r$//' /tmp/add-tb-nginx.sh && bash /tmp/add-tb-nginx.sh"
set -euo pipefail
CONF=/etc/nginx/sites-available/juzi
SNIPPET=${1:-/tmp/tb-location.conf}
STAMP=$(date +%Y%m%d%H%M%S)
BACKUP="$CONF.bak-tb-$STAMP"
if grep -q 'location /tb/' "$CONF"; then
echo "SKIP: /tb/ 已存在,不重复插入"
exit 0
fi
if [ ! -f "$SNIPPET" ]; then
echo "缺少片段文件: $SNIPPET" >&2
exit 1
fi
sudo cp "$CONF" "$BACKUP"
echo "BACKUP: $BACKUP"
sudo python3 - "$CONF" "$SNIPPET" <<'PY'
import sys
conf_path, snippet_path = sys.argv[1], sys.argv[2]
text = open(conf_path, encoding='utf-8').read()
snippet = open(snippet_path, encoding='utf-8').read()
block = "# BEGIN taobao-debug 2026-11-01\n" + snippet + "# END taobao-debug 2026-11-01\n\n"
marker = " location / {\n"
if marker not in text:
sys.exit("marker 'location / {' not found")
text = text.replace(marker, block + marker, 1)
open(conf_path, 'w', encoding='utf-8').write(text)
print("INSERTED")
PY
echo "--- nginx -t ---"
sudo nginx -t
echo "--- reload ---"
sudo systemctl reload nginx
echo "RELOADED"
+25
View File
@@ -0,0 +1,25 @@
# 插入到 /etc/nginx/sites-available/juzi 的 HTTPS server { } 块内。
# 作用:给淘宝小程序云网关一个专用入口 /tb/,反代到 juzi-taobao.service(127.0.0.1:18081)。
#
# 为什么要 allow 白名单:/tb/ 暴露在公网,后面是真实旷视计费调用。
# 只允许聚石塔 NAT 的出口 EIP(以及本机自测)访问,其余一律 403。
#
# 注意:nginx 会剥掉 /tb/ 前缀,所以 /tb/api/taobao/session → Node 收到 /api/taobao/session,
# 与 h5/server.js 的路由完全一致。
location = /tb {
return 301 /tb/;
}
location /tb/ {
# 聚石塔 NAT 网关绑定的 EIP。换环境时改这一行。
allow 47.92.113.176;
# 本机自测(curl -H 'Host: 1me.xajuzi.com' https://127.0.0.1/tb/api/health)
allow 127.0.0.1;
deny all;
proxy_pass http://127.0.0.1:18081/;
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;
proxy_read_timeout 150s;
}
+29
View File
@@ -0,0 +1,29 @@
[Unit]
Description=1me taobao mini program backend (Node, /tb/ debug instance)
After=network.target
[Service]
Type=simple
User=ubuntu
WorkingDirectory=/home/ubuntu/juzi-tb/h5
# 独立于生产 juzi-h5(8080)/juzi-rednote(8081):独立端口、独立数据目录、独立凭据文件
Environment=JUZI_ENTRY_MODE=state
Environment=NODE_ENV=production
Environment=HOST=127.0.0.1
Environment=PORT=18081
Environment=JUZI_DATA_ROOT=/home/ubuntu/juzi-tb-data
Environment=TRUST_LOOPBACK_PROXY=1
# 内容格式:
# TAOBAO_APP_KEY=35486228
# TAOBAO_APP_SECRET=<35486228 的 AppSecret>
# 权限 600 root,绝不入库。
EnvironmentFile=/etc/juzi/taobao.env
ExecStart=/usr/bin/node /home/ubuntu/juzi-tb/h5/server.js
Restart=on-failure
RestartSec=3
NoNewPrivileges=true
PrivateTmp=true
UMask=0077
[Install]
WantedBy=multi-user.target
+301
View File
@@ -0,0 +1,301 @@
# 开发交接 —— 换一台开发机继续做(2026-11-02)
> **写给**:在新 Windows 机器上接手的人(或未来的 agent)
> **不涉及**:服务器 / 聚石塔 / SAE 都不用重建,云端资源全部现成可用
> **上一份**:[HANDOFF-2026-11-01.md](HANDOFF-2026-11-01.md)(那一轮只到「链路打通 + Java MVP」,UI 和真机测肤是这一轮做的)
---
## 0. 一句话状态
**真机全链路已跑通**:淘宝小程序 → 云网关(AppKey 35486228)→ 聚石塔云应用 1me-net → 私网 CLB → SAE `nnn`(只做透传)→ 聚石塔 NAT 出网(EIP `47.92.113.176`)→ `https://1me.xajuzi.com/tb/` → 现有 H5 的 Node 服务 → 旷视 Face++,**三视图拍摄后报告正常返回**。
小程序五个页面的 UI 已按 H5 重做(像素级对齐,见下)。剩下的是相机组件验证和一批工程化工作,清单在第 8 节。
---
## 1. 新机器准备
### 1.1 必装
| 工具 | 版本 | 用途 |
|---|---|---|
| Git | 任意近期版本 | 拉代码 |
| Node.js | **18+(推荐 20/22)** | `taobao/` 测试、`h5/` 本地跑、零依赖 |
| JDK | **17** | `backend-java/` 编译与测试 |
| Maven | 3.9.x | 同上 |
| 淘宝小程序开发者工具 | 最新 | 真机预览(**模拟器不支持云应用 SDK**) |
| PowerShell 7(可选) | 7.x | 仓库里的脚本按 pwsh 语法写 |
### 1.2 拉代码
```powershell
git clone https://git.xiangsilian.com/xsl/juzi.git
cd juzi
```
> 远端证书有效期到 2027-01-29;凭据失效时重新配置 Git 凭据管理器。
### 1.3 小程序依赖(必做,否则真机报 `Cannot find module`)
```powershell
cd taobao
npm ci
```
`@tbmp/mp-cloud-sdk` 固定 1.5.10,只有这一个运行期依赖。
### 1.4 工具链
仓库自带 `tools/env-java.ps1`,查找顺序:仓库内便携工具链 → 环境变量 → PATH。
```powershell
Set-ExecutionPolicy -Scope Process Bypass
. .\tools\env-java.ps1 # 输出 JAVA_HOME / MAVEN_HOME / java -version
```
- 用系统安装的 JDK 17 + Maven 即可,**不需要**再下便携版
- 想复原老机器的布局:把 JDK 放 `tools/jdk17/`、Maven 放 `tools/maven/apache-maven-3.9.9/`(这两个目录 gitignore)
### 1.5 必须单独拷贝的机密文件(**不在 git 里**)
| 文件 | 内容 | 没有会怎样 |
|---|---|---|
| `backend-java/src/main/resources/application-local.yml` | 淘宝 AppKey/AppSecret + `mode: forward` + `forward-base` | JAR 里没有 AppKey/Secret,`/session` 一定 401 |
| `reference/facepp-skinanalyze/credentials.env` | 旷视 `FACEPP_API_KEY/SECRET` | `h5/server.js` 启动即 `exit(1)` |
| SSH 私钥 | 免密登录 `ubuntu@1me.xajuzi.com` | 无法部署 Node 侧 / 看日志 |
```powershell
# 从老机器拷(示例)
scp <老机器>:D:/juzi/backend-java/src/main/resources/application-local.yml .\backend-java\src\main\resources\
scp <老机器>:D:/juzi/reference/facepp-skinanalyze/credentials.env .\reference\facepp-skinanalyze\
```
新机器要能 ssh 服务器的话:`ssh-keygen -t ed25519` 后把公钥追加到服务器 `~/.ssh/authorized_keys`。
> 老机器若已不可用:AppSecret / 旷视 key 找项目负责人索取,或在开放平台重置(重置后记得同步更新服务器 `/etc/juzi/taobao.env` 与 JAR)。
### 1.6 淘宝 IDE 的一个已知坑(Windows)
老机器上 IDE 曾因 `wmic` 缺失打不开,用 `tools/fix-taobao-ide-wmic.ps1` 生成过一个 shim(`C:\Users\<你>\.local\bin\wmic.cmd`)。新机器如果 IDE 报 `install miniapp extension fail: ... wmic ...`,跑这个脚本。
---
## 2. 仓库地图
```
juzi/
├── taobao/ 淘宝小程序(当前主战场)
│ ├── config.js ★ 环境/传输配置(cloud+tower+test、assetBase)
│ ├── app.acss ★ 全局设计 token(对齐 H5 的 .btn-primary/.card/.chev/.popup)
│ ├── pages/
│ │ ├── splash/ 启动页(登录、隐私弹窗、开发版入口)
│ │ ├── profile/ 档案填写(出生/性别/地区/肤质轮播)
│ │ ├── camera/ 拍照页(蒙版取景 or 系统相机兜底)
│ │ ├── report/ 报告页(环形分、7 视角、叠加层、标记点)
│ │ ├── history/ 历史报告
│ │ ├── mvp/ 连通性调试页(ping/whoami/echo)
│ │ └── camtest/ ★ 相机组件冒烟页(只放 <camera>,判断组件是否可用)
│ ├── utils/
│ │ ├── transport.js 云网关/direct 两种传输 + 信封解包
│ │ ├── api.js 9 个接口封装(challenge/session/me/photo/skin-analyze/…)
│ │ ├── permissions.js ★ 权限(scope 前缀、诊断 diagnose())
│ │ ├── image.js 1280 长边 + JPEG q0.88 压缩
│ │ ├── voice.js 语音播报(异常静默)
│ │ └── report-model.js payload → 报告页 data
│ └── test/ 20 个 node:test 用例
├── h5/ ★ 真正的测肤后端(Node,零依赖)
│ ├── server.js 入口:Face++ 调用、评分、报告、admin
│ ├── lib/taobao-*.js ★ 淘宝云网关路由/验签/会话(关键算法在这里)
│ ├── js/skin-score.js 评分引擎(6 类 → 3 维,4:3:3 加权)
│ └── test/ 一堆独立测试脚本(没有统一 runner)
├── backend-java/ SAE 上的透传层(Spring Boot 3.5.6 / Java 17)
│ ├── src/main/java/com/juzi/mvp/api/
│ │ ├── TaobaoForwardController.java ★ mode=forward 时字节级透传
│ │ ├── TaobaoEnvelope.java 客户端要的 data.ok + data.http_status
│ │ ├── TaobaoAuthController.java native 模式的登录接口(当前未启用)
│ │ └── TaobaoSessionService.java challenge + Bearer token
│ └── src/main/java/com/juzi/mvp/gateway/TaobaoSignatureVerifier.java 验签算法
├── deploy/ ★ 服务器侧配置(systemd / nginx / 安装脚本)
├── docs/ 交接与实施文档(本文件所在)
└── tools/ 构建与素材脚本
```
---
## 3. 三套测试
```powershell
# 1) 小程序:20 个用例
cd taobao ; npm test
# 2) Java:11 个用例(含验签向量的回归测试)
cd backend-java ; mvn test
# 3) H5 单测(独立脚本,逐个跑)
node h5/test/taobao-auth.js # 网关签名,必须 PASS
```
**改动提交前请保证**:`taobao` 20/20 + `backend-java` 11/11。老机器最后一次全绿是在 `fed5293`。
> `taobao/test/frontend.test.cjs` 会用 vm 真跑页面 JS,页面 data 字段改名要同步改测试。
---
## 4. 本地怎么跑
### 4.1 H5 后端(Node,测肤真正的实现)
```powershell
cd h5
$env:PORT=8099 ; $env:HOST='127.0.0.1' ; $env:JUZI_ENTRY_MODE='state'
$env:JUZI_DATA_ROOT='..\temp\h5-data'
node server.js
# http://127.0.0.1:8099/api/health → {"ok":true}
```
启动时缺旷视 key 会直接退出。只想调 UI/接口时给假值即可(真实检测会失败,但路由可用):
```powershell
$env:SKIN_API_KEY='dummy' ; $env:SKIN_API_SECRET='dummy'
```
### 4.2 Java(SAE 那一层)
```powershell
. .\tools\env-java.ps1
cd backend-java
mvn spring-boot:run # 或 mvn -DskipTests package 后 java -jar target\*.jar
curl.exe -X POST -H "Content-Type: application/json" -d "{}" http://127.0.0.1:8080/api/taobao/ping
```
- 默认 `mode: native`(Java 自己实现登录,便于单独调接口)
- 部署用的 `application-local.yml` 里写的是 `mode: forward`
- 本地想看透传:`$env:TAOBAO_MODE='forward'` + `$env:TAOBAO_FORWARD_BASE='http://127.0.0.1:8099'`
### 4.3 小程序
1. 淘宝 IDE 导入仓库里的 `taobao/`(`mini.project.json` 的 `appid` 留空,以 IDE 账号为准)
2. **确认 IDE 当前小程序是 35486228(`1me 肌肤状态检测`)**
3. `config.js` 保持 `transport: 'cloud'` / `cloudType: 'tower'` / `cloudEnv: 'test'`
4. 顶部「预览」→ **手机淘宝扫码**(模拟器不支持 `cloud.application.httpRequest`)
5. 启动页右上角有三个**开发版入口**(`environment !== 'production'` 时才显示):
| 入口 | 作用 |
|---|---|
| 权限诊断 | 打印 `my.canIUse`、`getSetting` 的 authSetting、`authorize` 的原始返回(真机上唯一能看到这些的地方) |
| 相机组件测试 | 整页只有一个 `<camera>`,用来判断组件是否可用 |
| SAE MVP 连通性 | `ping/whoami/echo`,看 `transport:"gateway"`、`server:"java"` |
---
## 5. 云端资源(只读,无需重建)
| 资源 | 值 |
|---|---|
| 开放平台应用 | `1me 肌肤状态检测`,AppKey **35486228** |
| 旧应用(废弃) | `1me肌肤检测` 35479221 —— **不要再用它的 Secret** |
| 聚石塔云应用 | `1me-net`,ID **62829**,默认应用 |
| 私网 CLB | `lb-k2j324dwux1svaf2akwqx` / `192.168.65.185:80` |
| SAE 应用 | `nnn`,`cn-zhangjiakou:my-namespace` |
| 代码包 | `taobao-mvp-backend-0.0.4-forward.jar`(**环境变量全空**,配置都在包里) |
| VPC / vSwitch | `vpc-8vb5mcvenp8jwa1roqcsa` / `vsw-8vbna5zb2rq03gaeiymch`(可用区 A) |
| NAT 出口 EIP | **47.92.113.176**(nginx `/tb/` 白名单按它放行) |
| 服务器 | `ubuntu@1me.xajuzi.com`,Node v20.20.2 |
| 生产 H5 | `https://1me.xajuzi.com/state/` → `juzi-h5.service`(8080) |
| 红书渠道 | `https://1me.xajuzi.com/rednote/` → `juzi-rednote.service`(8081) |
| 淘宝专用 | `https://1me.xajuzi.com/tb/` → `juzi-taobao.service`(18081) |
---
## 6. 改完怎么上线
### 6.1 小程序
IDE 编译 → 预览(自测)→ 「上传」发版本。**不需要碰服务器。**
### 6.2 Java(SAE)
```powershell
. .\tools\env-java.ps1 ; cd backend-java
mvn clean package # 测试会自动跑
# 产物 target\taobao-mvp-backend-0.0.4-forward.jar
```
SAE 控制台 → `nnn` → 部署应用 → 代码包部署 → 上传 JAR(**版本号要和上次不同**,否则会被当成同一个版本)→ 环境变量保持全空。
### 6.3 Node(`/tb` 实例)
见 [deploy/README.md](../deploy/README.md) 第 1 节。核心三步:
```powershell
tar -czf temp/juzi-h5.tgz --exclude=h5/data --exclude=h5/test -C . h5
scp temp/juzi-h5.tgz ubuntu@1me.xajuzi.com:/tmp/
```
```bash
ssh ubuntu@1me.xajuzi.com "tar -xzf /tmp/juzi-h5.tgz -C /home/ubuntu/juzi-tb && sudo systemctl restart juzi-taobao"
```
> ⚠️ **不要动** `/home/ubuntu/juzi`(生产 H5)和 `/home/ubuntu/juzi-rednote`。我们那份是独立拷贝 `/home/ubuntu/juzi-tb`。
---
## 7. 这一轮的关键结论(踩过的坑,别重复踩)
1. **云网关签名算法**:真实请求**不带 `sign_method`**,缺省等价于 `MD5(secret + content + secret)`(`content` = 排序后的 `key+value` 拼接 + **原始 body 字节**)。之前按 `MD5(content + secret)` 实现,是 10 月底那个「signature mismatch」的真因。改在 [h5/lib/taobao-auth.js](../h5/lib/taobao-auth.js)。
2. **淘宝的 scope 是带前缀的** `scope.camera` / `scope.album`;传裸名 `camera` 返回 `{"errorCode":"5022","errorMessage":"Scope:camera信息不存在"}`。
3. **`<camera>` 组件在真机上会让整页崩溃**,且 `my.canIUse('camera')` 返回 `yes` 是**假阳性**。当前默认走系统相机(`config.nativeCamera = false`)。怀疑原因是相机里嵌了**微信专有的 `<cover-view>`**,已改成兄弟节点 + 普通 view 覆盖,并加了 `pages/camtest` 冒烟页——**这个还没在真机验证,是新机器的第一件事**。
4. **网关限制**:单请求 **10 秒 / 128KB**。所以 `skin-analyze` 必须 **202 + 1 秒轮询(最长 90 秒)**,不能同步等旷视。
5. **旷视 Face++**:正式 key 有并发窗口,必须**串行 + 4 秒间隔**;60 秒超时,失败/5xx/并发超限重试 1 次;返回值含 `face_maps`,4000px 素材时约 3.2MB(真实链路客户端压到 1280,约 200–300KB)。
6. **`/api/taobao/reports` 详情带 3 张 base64 照片**,历史报告页有超时风险(网关大响应未验证)。
7. **会话/任务全在内存**:SAE 只能单副本;`juzi-taobao` 重启后登录态与进行中的任务都会丢。
8. **没有任何清理策略**:每次检测在磁盘留 10–12MB,长期必须换 OSS。
9. **转发必须字节级透传**:签名覆盖原始 body 和 query,Java 一旦重新序列化 JSON 就会 401。
10. **生产那台 Node 是旧代码**,没有 `/api/taobao/*`;我们另外起 `/home/ubuntu/juzi-tb`(18081)跑新代码,用 nginx `/tb/` 分流。
11. **图片资源**走 `https://1me.xajuzi.com/state/assets/`(`config.assetBase`)。之前是 `127.0.0.1:18180`,真机上全部加载失败。
---
## 8. 待办(建议顺序)
| # | 事项 | 说明 |
|---|---|---|
| 1 | **真机验证 `pages/camtest`** | 能出实时画面 → 把 `config.nativeCamera` 改 `true`,拍照页立刻有蒙版取景;仍然崩 → 维持系统相机 |
| 2 | `/reports` 详情瘦身 | 现在透传 3 张 base64 原图(约 0.5MB+),改缩略图或 OSS URL |
| 3 | 存储工程化 | 照片/原始 JSON/记录 → OSS + RDS;现在全在容器/单机磁盘,无清理 |
| 4 | 旷视请求裁剪 | `return_maps` 请求 10 层但报告只用 6 层,可少传 4 层省带宽与落盘 |
| 5 | WebP 转码(可选) | 实测:6 层图 WebP q80 省 54%,叠 50% 缩放省 84%;**先真机验证 `<image>` 支持 WebP data URL** |
| 6 | 密钥治理 | `application-local.yml` / `/etc/juzi/taobao.env` 里的 AppSecret、JAR 内嵌 Secret → 改 KMS/保密字典,并**重置一次 AppSecret** |
| 7 | Java 端可选迁移 | `mode=native` 的登录接口已写好并测过(4 个用例),可把 auth 从 Node 搬到 Java;测肤仍留在 Node |
| 8 | 仓库收拾 | `sdk-java-35479221/`、三个 `.pptx`、`tools/build_taobao_proposal_*.py` 仍未跟踪,建议加 ignore 或删除 |
| 9 | 启用签名校验(Java 侧) | 当前 forward 模式下由 Node 验签;若切 `native` 或要 `/ping` 也验签,把 `verify-sign` 打开即可(算法已修复) |
---
## 9. 故障对照表
| 现象 | 原因 | 处理 |
|---|---|---|
| 真机 `cloud init failed` | IDE 当前 app 不是 35486228 / 云应用没设默认 | 确认 IDE 账号与 AppKey |
| `淘宝身份校验失败,请重新授权登录` | 云网关签名不匹配 | 检查服务器 `/etc/juzi/taobao.env` 的 AppSecret;算法见第 7 节第 1 条 |
| `请先配置淘宝云应用和 HTTPS 服务地址` | `cloudType` 不是 `tower` 或 `transport` 不是 `cloud` | 看 `config.js` |
| 页面一片灰没有图 | `assetBase` 指向 127.0.0.1 | 应为 `https://1me.xajuzi.com/state/assets/` |
| 点拍照整页崩(「系统遇到一点问题」) | `<camera>` 组件 | 保持 `nativeCamera: false`,用系统相机/相册 |
| 报告页白屏 | `payload` 为空或 `report-model` 抛错 | 看启动页「SAE MVP 连通性」是否 `server:"java"` |
| SAE 侧 `上游 403` | 容器出口 IP 不在 nginx `/tb/` 白名单 | `curl -s https://myip.ipip.net` 取新 IP,改 nginx 后 reload |
| `Cannot find module '@tbmp/mp-cloud-sdk'` | 没装依赖 / IDE 没构建 npm | `npm ci`,IDE「工具 → 构建 npm」 |
| Java 打包报找不到 `application-local.yml` | 机密文件没拷过来 | 见 1.5 |
---
## 10. 交接检查清单
- [ ] `npm ci`(taobao)跑过
- [ ] `tools/env-java.ps1` 能加载出 JDK17 + Maven
- [ ] `application-local.yml` 与 `credentials.env` 已从老机器拷来
- [ ] `taobao` 20/20、`backend-java` 11/11、`node h5/test/taobao-auth.js` PASS
- [ ] IDE 打开 `taobao/` 并确认 AppKey 是 35486228
- [ ] 真机预览能看到图片(说明资产域名通)
- [ ] 真机点「权限诊断」→ `authorize(scope.camera)=ok`
- [ ] 真机点「相机组件测试」→ 记录结果,决定 `nativeCamera` 开关
- [ ] 能 `ssh ubuntu@1me.xajuzi.com`,`systemctl is-active juzi-taobao` 为 active
---
**给接手者的第一句话**:先按第 1 节把机器装好、把两个机密文件拷过来,跑通三套测试;然后去真机验 `camtest`——那是唯一还悬着的技术问题,也决定拍照页最终形态。
+32 -14
View File
@@ -1,26 +1,42 @@
# 在当前 PowerShell 会话中加载 JDK 17 + Maven 3.9.9
# 用法:. D:\juzi\tools\env-java.ps1
# 在当前 PowerShell 会话中加载 JDK 17 + Maven
# 用法:. <仓库根>\tools\env-java.ps1
#
# 查找顺序:
# 1) 仓库内便携工具链 tools\jdk17\* + tools\maven\apache-maven-3.9.9(不入库,换机器需自备)
# 2) 已导出的环境变量 $env:JAVA_HOME / $env:MAVEN_HOME
# 3) PATH 上的 java / mvn(例:scoop/choco/winget 安装的)
$ErrorActionPreference = 'Stop'
$projectRoot = Split-Path -Parent $PSScriptRoot
$jdkRoot = Join-Path $projectRoot 'tools\jdk17'
$jdkCandidates = @($jdkRoot)
if (Test-Path $jdkRoot) {
$jdkCandidates += Get-ChildItem $jdkRoot -Directory -ErrorAction SilentlyContinue |
ForEach-Object { $_.FullName }
function Find-Jdk {
$root = Join-Path $projectRoot 'tools\jdk17'
$candidates = @()
if (Test-Path $root) {
$candidates += $root
$candidates += Get-ChildItem $root -Directory -ErrorAction SilentlyContinue | ForEach-Object { $_.FullName }
}
if ($env:JAVA_HOME) { $candidates += $env:JAVA_HOME }
return $candidates | Where-Object { $_ -and (Test-Path (Join-Path $_ 'bin\java.exe')) } | Select-Object -First 1
}
$jdkHome = $jdkCandidates |
Where-Object { Test-Path (Join-Path $_ 'bin\java.exe') } |
Select-Object -First 1
function Find-Maven {
$portable = Join-Path $projectRoot 'tools\maven\apache-maven-3.9.9'
if (Test-Path (Join-Path $portable 'bin\mvn.cmd')) { return $portable }
if ($env:MAVEN_HOME -and (Test-Path (Join-Path $env:MAVEN_HOME 'bin\mvn.cmd'))) { return $env:MAVEN_HOME }
$onPath = Get-Command mvn -ErrorAction SilentlyContinue
if ($onPath) { return Split-Path -Parent (Split-Path -Parent $onPath.Source) }
return $null
}
$jdkHome = Find-Jdk
if (-not $jdkHome) {
throw "JDK 17 not found under $jdkRoot. Run the portable JDK setup first."
throw "找不到 JDK 17:请安装 JDK 17 并设置 JAVA_HOME,或把便携版放到 tools\jdk17\"
}
$mavenHome = Join-Path $projectRoot 'tools\maven\apache-maven-3.9.9'
if (-not (Test-Path (Join-Path $mavenHome 'bin\mvn.cmd'))) {
throw "Maven not found at $mavenHome. Run the portable Maven setup first."
$mavenHome = Find-Maven
if (-not $mavenHome) {
throw "找不到 Maven:请安装 Maven 3.9+ 并放到 PATH,或放便携版到 tools\maven\apache-maven-3.9.9"
}
$env:JAVA_HOME = $jdkHome
@@ -29,5 +45,7 @@ $env:PATH = "$jdkHome\bin;$mavenHome\bin;$env:PATH"
Write-Host "JAVA_HOME=$env:JAVA_HOME"
Write-Host "MAVEN_HOME=$env:MAVEN_HOME"
# java/mvn 往 stderr 写版本信息,在 ErrorActionPreference=Stop 下会被当成终止错误,这里放宽
$ErrorActionPreference = 'Continue'
java -version
mvn -version