15 KiB
Executable File
数字我 / Digital Me — 部署清单(DEPLOY)
目标:把
AlexLi2244/digital-me上线到阿里云 ECS(已配 CI/CD:pushmaster自动部署)。
0. 架构与端口
| 组件 | 位置 | 说明 |
|---|---|---|
| 后端 API | ECS /var/www/digitalme-api.invoratec.cn,PM2 digitalme-api,端口 3603 |
Node20+Express |
| 前端 SPA | ECS /var/www/digitalme-spa,Nginx 静态 |
Vue3 build |
| 域名 | API digitalme-api.invoratec.cn / 前端 digitalme.invoratec.cn |
需各自 DNS + 证书 |
| 数据库 | MongoDB creator_cn 库 + COLLECTION_PREFIX=dm_ 隔离 |
与其它产品共库不撞表 |
| 媒体 | 配齐 OSS_* → 阿里云 OSS(推荐);否则后端 data/uploads(Nginx 经 API 暴露 /uploads) |
见下方 .env OSS 段 |
CI/CD
rm:false保留服务器上的.env / node_modules / data——.env只在服务器上手建一次,不进仓库。
1. GitHub 仓库 Secrets(一次)
Settings → Secrets and variables → Actions:
ECS_HOSTECS_USERECS_PASSWORD
2. 服务器一次性准备
# Node 20 + PM2
node -v # >=20;如无:nvm install 20
npm i -g pm2
# 目录
mkdir -p /var/www/digitalme-api.invoratec.cn /var/www/digitalme-spa
# MongoDB:复用现有 creator_cn 库即可(dm_ 前缀隔离),或自建 digital_me 库
后端 .env(手建于 /var/www/digitalme-api.invoratec.cn/.env)
NODE_ENV=production
PORT=3603
PUBLIC_URL=https://digitalme-api.invoratec.cn # 必须公网可达(捏脸/声音/口播/作品 靠它回拉)
CORS_ORIGINS=https://digitalme.invoratec.cn
WEB_BASE_URL=https://digitalme.invoratec.cn # 支付后跳回
# 安全(生产强制:JWT_SECRET 缺省会拒绝启动)
JWT_SECRET=<强随机 32+ 位>
DATA_ENC_KEY=<强随机> # 访客记录加密,强烈建议
SUPER_ADMIN_EMAILS=lu.huang0912@gmail.com # 蓝V 认证后台
TRUST_PROXY=true # 在 Nginx 后,取真实 IP
# 数据库(共库方案)
MONGO_URI=mongodb://127.0.0.1:27017
MONGO_DB=creator_cn
COLLECTION_PREFIX=dm_
# AI(DashScope 一把钥匙)
DASHSCOPE_API_KEY=sk-...
AVATAR_PROVIDER=dashscope
MODERATION_MODE=llm # 用 Qwen 做审核(或接 MODERATION_URL 专业服务)
# 媒体存储(阿里云 OSS,推荐):配齐即自动用 OSS,否则回落本地磁盘 + /uploads。
# OSS 直链公网可达 → 免公网穿透、重部署不丢片、DashScope 直接按 URL 拉头像/音频。
OSS_REGION=oss-cn-hangzhou # 必带 oss- 前缀
OSS_BUCKET=invoratec-aimall # 复用共享 bucket,靠 OSS_PREFIX 隔离
OSS_ACCESS_KEY_ID=
OSS_ACCESS_KEY_SECRET=
OSS_PREFIX=digitalme
# bucket 非「公开读」时,填 CDN/自定义域名,否则存库的头像直链取不到:
# OSS_PUBLIC_BASE=https://cdn.invoratec.cn
# 支付(先 stub,拿到密钥后切 stripe/wechat)
PAYMENT_PROVIDER=stub
TOKENS_PER_YUAN=10
# STRIPE_SECRET_KEY=sk_live_...
# STRIPE_WEBHOOK_SECRET=whsec_...
改完
.env需pm2 restart digitalme-api才生效(CI/CD 也会重启)。
Nginx(两个 server 块)
# 后端 API
server {
server_name digitalme-api.invoratec.cn;
client_max_body_size 30m; # 头像/录音/试卷上传
location / { proxy_pass http://127.0.0.1:3603; proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme; }
# Stripe webhook 需原始 body——proxy 默认即可,勿在此改写 body。
# listen 443 ssl; + 证书
}
# 前端 SPA
server {
server_name digitalme.invoratec.cn;
root /var/www/digitalme-spa; index index.html;
location / { try_files $uri $uri/ /index.html; } # history 路由
# listen 443 ssl; + 证书
}
证书:certbot --nginx -d digitalme-api.invoratec.cn -d digitalme.invoratec.cn。
分享卡片 og meta(微信/IM 富卡片)——默认休眠,按需启用
分身分享链接 /me/:slug、/s/:token 甩进微信时,抓取器只读静态 HTML、不跑 JS,
默认卡片没有对方头像/名字。后端已内置 SSR:src/routes/share.js 会给这两个路径注入
per-分身 的 og meta(头像+名字+一句话)。代码常驻但默认不接,启用只需两步:
-
后端
.env增一行(让它注入真 SPA index、页面仍是完整 App):SPA_INDEX_PATH=/var/www/digitalme-spa/index.html # 可选:无头像时的兜底卡片图(绝对 URL) # OG_FALLBACK_IMAGE=https://digitalme.invoratec.cn/og-default.png不填
SPA_INDEX_PATH也能用——回落成「meta + 跳转到 web 页」的独立壳(爬虫拿卡片、 真人被转走)。pm2 restart digitalme-api生效。 -
前端 SPA 的 Nginx server 块:把这两个路径的文档请求回源到后端,其余静态照旧:
server { server_name digitalme.invoratec.cn; root /var/www/digitalme-spa; index index.html; # 分享页文档 → 后端注入 og meta(资产 /assets 仍走下面的静态) location ~ ^/(me|s)/ { proxy_pass http://127.0.0.1:3603; proxy_set_header Host $host; } location / { try_files $uri $uri/ /index.html; } # history 路由 }注入的 index.html 保留原
/assets/...相对引用 → 浏览器仍从 web 域名静态取,App 照常启动。
先验证:改前直接打 API 域名
https://digitalme-api.invoratec.cn/me/<slug>就能看到注入了 og:title/og:image 的 HTML(此时资产指向 API 域名会 404,仅用于验证卡片数据)。
微信「转发给朋友/朋友圈」自定义卡片(JS-SDK)——默认休眠,需公众号资质
上面的 og meta 解决被动抓取卡片;微信里点「•••→转发给朋友/分享到朋友圈」时的自定义 卡片(自定义标题/描述/缩略图)要用 JS-SDK。代码已内置、默认休眠,启用三步:
- 有一个认证公众号(服务号最佳),拿到它的 appId + appSecret。
- 后端
.env增两行(是【公众号】的,不是开放平台扫码登录那对):WX_MP_APP_ID=wx____ WX_MP_APP_SECRET=____pm2 restart digitalme-api。签名接口GET /api/wx/jssdk?url=即从{configured:false}变为返回真签名。 - 公众号后台 → 设置 → 公众号设置 → 功能设置:把
digitalme.invoratec.cn填进 「JS 接口安全域名」(需下载校验文件放到 SPA 根目录,Nginx 能返回即可)。
前端 composables/useWxShare.ts 只在微信内置浏览器且接口 configured:true 时才加载 jweixin
并注册分享卡;非微信/未配资质全程 no-op。已接入 /me/:slug、/s/:token 两个公开页,
分享卡自动用分身的名字+一句话+头像。
CSP 注意:SPA 若加了 CSP,
script-src要放行https://res.wx.qq.com(jweixin 从这里加载)。当前 Nginx 静态无 CSP,暂无需改。
3. 部署(日常)
push master 即自动部署(改 digital-me-backend/** 触发后端、digital-me-web/** 触发前端)。
- 前端 CI 用
build:fast(跳过 vue-tsc);本地请先npm run build过一遍严格类型检查再推。 - 手动兜底:后端 scp
src/ package*.json到目标目录 →npm install --production→pm2 restart digitalme-api;前端npm ci && npm run build:fast→ scpdist/*到/var/www/digitalme-spa→nginx -s reload。
4. 上线后冒烟(逐条核对)
API=https://digitalme-api.invoratec.cn
curl -s $API/health # {ok:true}
# 注册→建分身→喂知识→对话(带 RAG 来源)
# 公开页 https://digitalme.invoratec.cn/me/<slug> 能聊
# 广场 /plaza、排行榜 /leaderboard 可访问
/health通- 注册/登录、建分身、喂知识、对话有来源
- 形象/声音/口播(确认
PUBLIC_URL公网可达,DashScope 能回拉) - 社交卡片
/s/:token、广场/plaza、互考/challenge/:slug、排行榜 - 群聊
/room/:token、错题反考、作品集内联展示 - 钱包充值(stub 即时)、专家付费咨询扣费+分成、免责声明
- 超管
/admin可授蓝V(你的邮箱在SUPER_ADMIN_EMAILS) - 内容审核拦截、限流 429、账号注销物理删除
5. 支付正式上线(拿到密钥后)
- Stripe:填
STRIPE_SECRET_KEY/STRIPE_WEBHOOK_SECRET、PAYMENT_PROVIDER=stripe; Stripe 后台 Webhook 指向https://digitalme-api.invoratec.cn/api/wallet/webhook/stripe, 事件选checkout.session.completed。重启后端。无需改代码。 - 微信支付:拿到商户号后在
services/payments.js的wechat分支补 Native/H5 下单 + 回调验签(结构与 Stripe 一致),PAYMENT_PROVIDER=wechat。
5b. 两个独立系统(成人 / 青少年)
同一套代码,用 EDITION 锁定为单一系统,各自独立部署(独立域名 + 独立 DB + 独立用户体系):
| 系统 | EDITION | 功能 | 建议域名 / DB |
|---|---|---|---|
| 成人系统 | adult |
社交分身/广场/群聊/专家付费/钱包 + 建分身/对话 | digitalme.invoratec.cn / MONGO_DB=digital_me_adult(或 COLLECTION_PREFIX=dma_) |
| 青少年系统 | teen |
错题反考/学友互考/排行榜/家长监护 + 建分身/对话 | edu.digitalme.invoratec.cn / MONGO_DB=digital_me_teen(或 dmt_) |
每个系统 = 一套后端(PM2 各自端口) + 一套前端 build。前端 build 时设 VITE_EDITION=adult|teen
(隐藏非本系统导航、登录页文案:成人系统无选择器;青少年系统区分「学生/家长」)。
后端 EDITION 决定挂载哪些路由(非本系统 API 直接 404)。两系统 DB 独立 → 用户/数据完全隔离。
家长监护属于青少年系统:家长在青少年系统内注册(身份=家长),与孩子同库才能绑定。 成人系统不含监护/错题。两系统都共享「建分身/克隆/对话/审核/安全」内核代码。
CI/CD(已配 4 条独立工作流)
.github/workflows/:deploy-adult-backend.yml·deploy-adult-web.yml·deploy-teen-backend.yml·deploy-teen-web.yml。
push master 改 digital-me-backend/** → 两套后端都重部署(共享代码);改 digital-me-web/** → 两套前端都重 build。
| 系统 | 后端目录 / PM2 / 端口 | 前端目录 | API 域名 / 前端域名 |
|---|---|---|---|
| 成人 | /var/www/digitalme-adult-api · digitalme-adult-api · 3603 |
/var/www/digitalme-adult-spa |
digitalme-api.invoratec.cn / digitalme.invoratec.cn |
| 青少年 | /var/www/digitalme-teen-api · digitalme-teen-api · 3604 |
/var/www/digitalme-teen-spa |
edu-api.invoratec.cn / edu.invoratec.cn |
工作流在 pm2 启动命令里固定 EDITION+PORT(dotenv 不覆盖已存在的 env);前端 build 时注入 VITE_EDITION+VITE_API_BASE_URL。
每个后端目录的 .env 各自配 独立 MONGO_DB(digital_me_adult / digital_me_teen)+ AI/安全密钥。
一次性服务器准备:创建上述 4 个目录 + 两份 .env;4 个 Nginx server 块(2 API 反代 :3603/:3604 + 2 SPA history 路由)+ 4 域名证书。
迁移注意:旧单体
digitalme-api(PM2) 也用 3603——上线成人系统前先pm2 delete digitalme-api释放端口。
5c. 单点工具 MVP(独立打包)
3 个工具可作为独立单页产品发布(复用同一后端,无需全平台导航)。前端 build 时设
VITE_APP:根路径直达该工具、隐藏平台导航。
| 工具 | VITE_APP | 路由 | 复用 |
|---|---|---|---|
| 口播侠 TalkShot | talkshot |
/talkshot | avatar+voice+EMO |
| 反考机 ReQuiz | requiz |
/requiz | errors quiz(需后端含 twins,所有版本都有) |
| 互考擂台 Arena | arena |
/arena | publish+challenge+leaderboard(需 EDITION=teen/both) |
例:VITE_APP=talkshot VITE_API_BASE_URL=https://digitalme-api.invoratec.cn npm run build:fast,
dist 部署到该工具的独立域名即可。VITE_APP 未设=full(整平台,3 工具仍可经 /requiz 等访问)。
5d. 产品工厂(商店 + 购买即开通)
把多个 MVP 聚成一个「工厂」站点:商店列 SKU、每个 SKU 独立购买链接、买后
自动开通账号(权益) + magic link 即用。上架新 MVP = 在 src/products.js 加一条
SKU(无需新部署)。
部署形态:VITE_FACTORY=c(2C 工厂) / =b(2B 工厂)。根路径→/store,隐藏平台
导航,工具按 entitlement 门控(未购→/buy/:sku)。=all/不设=全量平台(不门控)。
# 2C 产品工厂
VITE_FACTORY=c VITE_API_BASE_URL=https://digitalme-api.invoratec.cn npm run build:fast
建议两个工厂各一域名:2C shop.invoratec.cn、2B b.invoratec.cn,同一后端。
购买链路:/buy/:sku 填邮箱 → POST /api/store/checkout → (stub 通道立即开通
并返回 redeemToken;真实通道返回 payUrl,支付回调里调 fulfill()) → 前端经
/api/store/redeem 兑换 magic token → 自动登入并进工具。magic token 30 分钟、单次。
接真实支付(微信 Native 扫码付,已实现):商户平台拿齐 WXPAY_APP_ID/MCH_ID/ API_V3_KEY/CERT_SERIAL + 商户私钥(WXPAY_PRIVATE_KEY 或 _PATH),设 STORE_PROVIDER=wechat。
回调地址 https://<api 域名>/api/store/webhook/wechat(微信商户平台配置 + 服务器需公网可达)。
链路:checkout→nativeOrder 拿 code_url→前端二维码→用户扫码付→微信 POST 回调→
decryptNotify(AES-256-GCM 认证解密)→fulfill(out_trade_no) 开通→前端轮询 /order/:id
拿 redeemToken→自动登入进工具。密钥未配齐时自动回落 stub(不中断)。
(Stripe 同理:在其 webhook 调 store.js#fulfill。)
渠道归因:进站捕获 utm_*(utm.ts) → 随 checkout 存订单;埋点
store_view/buy_view/checkout_start/purchase → 超管 /api/admin/funnel 看「哪条新媒体带来购买」。
6. 安全红线(上线必做)
JWT_SECRET、DATA_ENC_KEY用强随机;SUPER_ADMIN_EMAILS仅限你本人。MODERATION_MODE=llm或接MODERATION_URL专业审核(勿只靠本地正则)。- 备份 MongoDB;媒体走 OSS:配齐
OSS_*即自动启用(services/storage.js的saveMedia统一落地,OSS 失败自动回落本地磁盘,不中断上传/口播)。 - 合规:公开页已带「AI 数字分身,非真人」声明;专家回答强制免责声明。