183 lines
11 KiB
Markdown
183 lines
11 KiB
Markdown
# 数字分身 H5 生产接入与部署
|
||
|
||
## 1. 接入方式
|
||
|
||
生产会会在用户已登录后打开以下地址:
|
||
|
||
```text
|
||
https://digital.99hui.com/#/avatar/manage?token=<encodeURIComponent(会会 access token)>
|
||
```
|
||
|
||
测试环境示例:
|
||
|
||
```text
|
||
http://192.168.1.188:8099/#/avatar/manage?token=<encodeURIComponent(token)>
|
||
```
|
||
|
||
兼容参数位于域名查询串的形式,但生产统一使用上面的 hash 路由形式。必须对 token 调用 `encodeURIComponent`,不能拼接用户 ID 代替 token。
|
||
|
||
免登录流程如下:
|
||
|
||
1. H5 在页面渲染前读取 `token`,立即通过 `history.replaceState` 从地址栏和浏览器历史中移除。
|
||
2. H5 调用 `POST /api/huihui/token/login`,不会把会会 token 当作数字分身接口 token 直接使用。
|
||
3. 后端通过会会生产接口 `/im/box/netease` 换取 BOXIM 凭证,再调用 BOXIM `/user/self` 校验用户身份。
|
||
4. 后端以返回的 `huihuiUserId` 绑定本地用户,保存会会凭证供 BOXIM 接管功能使用,并签发本系统 `app_token`。
|
||
5. 浏览器只保存 `app_token` 和非敏感用户资料。会会原始 token 不返回浏览器存储。
|
||
6. token 无效、过期或上游校验失败时清除旧会话并进入登录页,不会沿用上一位用户的缓存身份。
|
||
|
||
## 2. 生产配置
|
||
|
||
后端 `.env` 至少配置以下内容,密钥由部署平台注入,禁止提交 Git:
|
||
|
||
```dotenv
|
||
HUIHUI_DEV_MOCK=false
|
||
HUIHUI_AUTH_BASE_URL=https://99hui.com/api/usercenter
|
||
HUIHUI_PLATFORM_BASE_URL=https://open.99hui.com/api
|
||
BOXIM_API_BASE_URL=https://im.99hui.com/api
|
||
HUIHUI_APP_ID=<production-app-id>
|
||
HUIHUI_ACCESS_ID=<production-access-id>
|
||
HUIHUI_ACCESS_SECRET=<production-access-secret>
|
||
HUIHUI_CLIENT_CODE=<production-client-code>
|
||
BOXIM_TIMEOUT_SECONDS=20
|
||
BOXIM_POLL_CONCURRENCY=8
|
||
BOXIM_MAX_MESSAGE_AGE_SECONDS=600
|
||
HUIHUI_PAYMENT_BASE_URL=https://open.99hui.com/api/payment-v3
|
||
HUIHUI_PAYMENT_CALLBACK_BASE_URL=https://digital.99hui.com
|
||
HUIHUI_PAYMENT_CALLBACK_SECRET=<至少32位随机密钥>
|
||
HUIHUI_PAYMENT_TIMEOUT_SECONDS=30
|
||
HUIHUI_PAYMENT_REFUND_PATH=/payment/refund
|
||
AVATAR_FINANCE_ADMIN_SECRET=<至少32位随机密钥,与管理后台一致>
|
||
|
||
# 微信小程序虚拟支付;联调先使用 sandbox
|
||
WECHAT_MP_APP_ID=<小程序AppID>
|
||
WECHAT_MP_APP_SECRET=<小程序AppSecret>
|
||
WECHAT_VIRTUAL_ENV=sandbox
|
||
WECHAT_VIRTUAL_SANDBOX_APP_KEY=<沙箱AppKey>
|
||
WECHAT_VIRTUAL_APP_KEY=<正式AppKey>
|
||
WECHAT_VIRTUAL_OFFER_ID=<offer-id>
|
||
WECHAT_VIRTUAL_CALLBACK_TOKEN=<回调校验Token>
|
||
WECHAT_VIRTUAL_PRODUCT_1=<10元套餐商品ID>
|
||
WECHAT_VIRTUAL_PRODUCT_2=<100元套餐商品ID>
|
||
WECHAT_VIRTUAL_PRODUCT_3=<1000元套餐商品ID>
|
||
WECHAT_VIRTUAL_PRODUCT_4=<10000元套餐商品ID>
|
||
|
||
AVATAR_DB_DIR=/srv/digital-avatar/data/db
|
||
AVATAR_UPLOAD_DIR=/srv/digital-avatar/data/uploads
|
||
DATABASE_URL=sqlite:////data/db/avatar.db
|
||
UPLOAD_DIR=/data/uploads
|
||
CHAT_MODEL_CONFIG_URL=http://<huihuisquare-api>/api/ai-models/runtime/digital-avatar
|
||
EMBEDDING_API_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
|
||
EMBEDDING_API_KEY=<production-embedding-api-key>
|
||
EMBEDDING_MODEL=text-embedding-v3
|
||
EMBEDDING_BATCH_SIZE=10
|
||
|
||
VISION_MODEL=qwen3.6-flash
|
||
VISION_OCR_MODEL=qwen-vl-ocr
|
||
VISION_MAX_OUTPUT_TOKENS=2048
|
||
VISION_TIMEOUT_SECONDS=90
|
||
VISION_TOKEN_RESERVE=12000
|
||
APP_GIT_SHA=<本次发布的完整提交SHA>
|
||
APP_BUILD_TIME=<UTC ISO-8601构建时间>
|
||
CHAT_IMAGE_MAX_BYTES=8388608
|
||
CHAT_IMAGE_MAX_PIXELS=16000000
|
||
CHAT_ATTACHMENT_RETENTION_HOURS=24
|
||
CHAT_ATTACHMENT_CLEANUP_MINUTES=60
|
||
```
|
||
|
||
如生产 AI 配置中心不可用,还应提供当前项目支持的 `OPENAI_API_KEY`、`OPENAI_BASE_URL`、`CHAT_MODEL` 等兜底配置。数据库文件与上传目录必须从宿主机显式挂载,不能存放在容器临时层。
|
||
|
||
`AVATAR_DB_DIR` 和 `AVATAR_UPLOAD_DIR` 必须是已备份的宿主机绝对路径,编排缺少任一变量都会直接拒绝构建或启动,防止误挂空卷造成用户、分身或知识库“丢失”的假象。SQLite 必须挂载整个数据库目录,不能只挂载 `avatar.db` 单文件,否则 `avatar.db-wal` 和 `avatar.db-shm` 会留在容器临时层,换容器后可能出现数据状态回退。
|
||
|
||
`EMBEDDING_API_URL` 同时支持 OpenAI 兼容基础地址(如上面的 `/v1`)和完整的 `/v1/embeddings` 地址,后端会统一请求 `/embeddings`。发布后必须在后端容器内执行一次最小向量探针,确认返回向量数量和维度,而不能只检查 `/api/health`。
|
||
|
||
App 与 H5 积分充值使用会会支付体系的 `payment-v3/payment/pay`,渠道值为 `WECHAT` / `ALIPAY`;App 场景为 `APP`,普通浏览器为 `H5`,微信内 H5 为 `JSAPI`。`HUIHUI_PAYMENT_CALLBACK_SECRET` 只用于为每笔订单生成 HMAC 回调签名,不会发送到前端或直接出现在回调地址中。支付回调确认状态成功且金额与套餐价格完全一致后才增加积分,重复回调不会重复到账。
|
||
|
||
微信小程序使用微信虚拟支付:小程序先通过 `POST /api/token/wechat/session` 交换临时登录码,再由 `POST /api/token/charge`(`payScene=LITE`)返回已签名的 `requestVirtualPayment` 参数。微信回调地址配置为 `https://digital.99hui.com/api/token/payment/wechat/virtual/notify`。回调会复核签名、OpenID、环境、商品 ID 与实付金额,退款回调确认后才扣回积分。AppKey、AppSecret、session_key 均不得下发前端或写日志。
|
||
|
||
管理后台需要配置相同的 `AVATAR_FINANCE_ADMIN_SECRET` 和 `AVATAR_BACKEND_URL=https://digital.99hui.com`。退款只支持整单原路退款;供应商受理后显示“处理中”,收到渠道成功回调(或经渠道后台核对后人工确认)才将订单置为已退款。已消费掉本订单积分时,后台会拒绝主动退款;若渠道外部退款先发生,积分账户允许形成负数以记录欠额并阻止继续消费。
|
||
|
||
## 3. 构建与发布
|
||
|
||
首次发布前备份数据:
|
||
|
||
```bash
|
||
BACKUP_DIR="backups/$(date +%Y%m%d-%H%M%S)"
|
||
mkdir -p "$BACKUP_DIR"
|
||
cp /srv/digital-avatar/data/db/avatar.db "$BACKUP_DIR/"
|
||
tar -C /srv/digital-avatar/data -czf "$BACKUP_DIR/uploads.tgz" uploads
|
||
```
|
||
|
||
在发布目录执行:
|
||
|
||
```bash
|
||
git fetch origin
|
||
git checkout <已验收的提交SHA>
|
||
cd digital-avatar-app
|
||
export APP_GIT_SHA="$(git rev-parse HEAD)"
|
||
export APP_BUILD_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||
docker compose build --pull --no-cache avatar-backend avatar-frontend
|
||
docker compose up -d --force-recreate --wait avatar-backend avatar-frontend
|
||
docker compose ps
|
||
python3 scripts/verify-deployment.py \
|
||
https://digital.99hui.com "$APP_GIT_SHA" \
|
||
--backend-container avatar-backend \
|
||
--frontend-container avatar-frontend \
|
||
--expected-db-source /srv/digital-avatar/data/db \
|
||
--expected-upload-source /srv/digital-avatar/data/uploads
|
||
docker compose exec avatar-backend python -c 'import embeddings; v=embeddings.embed(["部署向量探针"]); print(len(v), len(v[0]))'
|
||
```
|
||
|
||
Jenkins 必须以 `verify-deployment.py` 返回成功作为发布成功条件,不能只以镜像构建或容器启动成功作为条件。脚本会同时核对公网前后端 Git SHA、数据库可读、上传目录可写、PDF OCR 依赖和宿主机数据挂载;任意一项不一致都会返回非零状态并阻止发布标绿。镜像使用 Git SHA 标签,不再依赖可被旧缓存覆盖的 `latest`。
|
||
|
||
生产编排应把示例中的测试端口改为内网暴露,由统一 HTTPS 网关接入。后端暂时使用 SQLite,必须保持单实例写入;若扩展为多后端实例,应先迁移到 PostgreSQL,并把延迟接管任务改为共享队列。
|
||
|
||
## 4. 网关要求
|
||
|
||
必须使用 HTTPS。同域部署时,H5 静态资源与 `/api/` 由同一域名提供,可避免跨域和 Cookie/来源策略问题。Nginx 关键配置示例:
|
||
|
||
```nginx
|
||
server_name digital.99hui.com;
|
||
|
||
location / {
|
||
try_files $uri $uri/ /index.html;
|
||
}
|
||
|
||
location /api/ {
|
||
proxy_pass http://avatar-backend: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;
|
||
proxy_buffering off;
|
||
proxy_read_timeout 300s;
|
||
client_max_body_size 100m;
|
||
}
|
||
```
|
||
|
||
`proxy_buffering off` 用于数字分身 SSE 流式吐字,`client_max_body_size` 同时用于知识库文件和聊天图片上传。应用只保存图片识别结果,不保存原图;识别结果 24 小时失效,后台默认每小时清理一次。公开分享图片识别会消耗分身所有者积分,生产网关应针对 `/api/public/avatar/*/chat/images` 设置每 IP 和每分享令牌的上传频率限制,防止恶意消耗。
|
||
|
||
网关和应用日志必须关闭完整 URL 查询参数记录,任何异常日志都不得输出 token、Authorization、图片 Base64、病例正文或平台密钥。建议同时设置严格的 `Referrer-Policy: no-referrer`。
|
||
|
||
## 5. 发布验收
|
||
|
||
1. 已登录会会用户通过带 token 链接打开后直接进入 `/avatar/manage`,不出现登录页或创建新账号页。
|
||
2. 页面加载后地址栏中不再包含 `token`,刷新页面仍使用本地 `app_token` 正常访问。
|
||
3. 后端用户绑定的是 BOXIM 返回的 `huihuiUserId`,不是 BOXIM 内部 `id`;原有数字分身、独立知识库和积分余额均存在。
|
||
4. A、B 两个会会用户分别进入时只能看到各自的数字分身与知识库,不会继承上一用户缓存。
|
||
5. 使用过期或伪造 token 时进入登录页并显示凭证失效,不得继续访问旧用户数据。
|
||
6. 分身聊天 SSE 逐段输出正常,Markdown 正常渲染,知识库优先级和积分扣费正常。
|
||
7. 开启 BOXIM 主动接管后保持在线,默认三分钟回复、自定义等待时间、已读回执、分身防回环和主人发言暂停均正常。
|
||
8. 重建容器后数据库、头像、知识库文档仍存在,`/api/health` 的 `gitSha` 与发布 SHA 一致,`database`、`uploads`、`pdfOcr` 三项检查均为 `true`。
|
||
9. `https://digital.99hui.com/api/health` 可访问,证书域名和有效期正确,HTTP 自动跳转 HTTPS。
|
||
10. 微信和支付宝各创建一笔最小套餐订单,未付款时积分不变;支付成功后回调到账一次,重复回调积分不重复增加。
|
||
11. 微信虚拟支付在沙箱环境完成下单、支付回调、查单兜底和退款回调;错误 OpenID、商品、环境或金额均被拒绝。
|
||
12. 财务后台能筛选订单、关闭待支付订单、发起整单退款、登记退款对账结果,并处理个人/企业电子发票申请。
|
||
13. 私聊和公开分享各上传 JPG、PNG、WebP 图片并完成追问;上传非图片、超过 8MB 或跨分身附件时必须拒绝。
|
||
12. 病例图片可以提取可见文字并标记待核对内容,医学影像不作确定诊断;视觉与 OCR 调用分别扣减积分。
|
||
13. 检查服务器上传目录不残留聊天原图,数据库过期图片识别记录在清理周期后删除,日志不出现 Base64 或病例正文。
|
||
|
||
## 6. 回滚
|
||
|
||
保留上一版前后端镜像标签和发布前数据库/上传文件备份。代码回滚优先切回上一镜像;只有新版本执行了不可逆数据变更时才恢复数据库。恢复前先停止后端写入,恢复后对比用户数、分身数、知识库文档数并完成一次免登录和聊天验收。
|