Files
2026-09-30 11:02:58 +08:00

11 KiB
Raw Permalink Blame History

部署指南

面向接手部署的人。按顺序执行即可,每一步都有验证方法。


0. 变量约定

下文用占位符代替真实域名,部署时替换:

只需要三个域名:管理后台直接调用 <API域名>(服务端同一进程内含 /admin/v1/*), 不需要单独的接口域名。

占位符 含义 举例
<API域名> 服务端 API 域名 yelaymaths-api.example.com
<后台域名> 管理后台域名 yelaymaths-admin.example.com
<数据域名> 课程数据(图片 / 视频)域名 yelaymaths-data.example.com

⚠️ 三个域名都不能含下划线。 下划线不是合法的主机名字符(RFC 1034/1035 只允许字母、数字、连字符), 后果是 Let's Encrypt 拒签证书、微信「request 合法域名」校验也会拒绝。

用中划线:yelaymaths-api.example.com,不要用 yelaymaths_api.example.com。


1. 服务器准备(宝塔面板)

  1. 软件商店安装:MySQL 5.7、Nginx、Node 版本管理器(选 Node 22 LTS)。

  2. 防火墙放行 80 / 443。

  3. 建 3 个站点,各配 Let's Encrypt 证书:

    站点 反向代理到
    <API域名> http://127.0.0.1:3000
    <后台域名> http://127.0.0.1:3001

    这两个站点都不是静态站点,反向代理必须配,只上传文件不会工作。

  4. 建数据端站点 <数据域名>,根目录指向课程数据目录(见第 6 节)。


2. 数据库

  1. 新建数据库 yelaymaths,字符集 utf8mb4。
  2. 新建数据库账号并授权。

关于 Prisma 权限:prisma migrate dev 需要 CREATE DATABASE 权限来建影子库,宝塔默认账号没有。 生产环境只用 pnpm prisma:migrate(即 prisma migrate deploy),它不建影子库,无需额外权限。

  1. 备份旧库(迁移前必做):

    mysqldump -u <用户> -p api_yeomai_site > /root/backup/api_yeomai_site_$(date +%F).sql
    

3. 部署服务端

cd /www/wwwroot
git clone <仓库地址> yelaymaths
cd yelaymaths/yelaymaths_api
pnpm install
cp .env.example .env
vi .env          # 按下表填写

必须填写的环境变量

变量 说明
DATABASE_URL mysql://用户:密码@127.0.0.1:3306/yelaymaths
LEGACY_DATABASE_URL 旧库连接串,仅迁移脚本使用
API_BASE_URL https://<API域名>
STATIC_BASE_URL https://<数据域名>
ADMIN_ORIGIN https://<后台域名>(跨域白名单)
JWT_ACCESS_SECRET 随机 32 位以上
JWT_REFRESH_SECRET 随机 32 位以上
SESSION_KEY_ENC_SECRET 随机 32 位以上,用于加密存储用户 sessionKey
MEDIA_SIGN_SECRET 随机 32 位以上,用于播放地址签名
WX_APPID wx3fbaf897825bf04f
WX_APPSECRET 小程序密钥
WX_OFFER_ID / WX_PAY_APPKEY 虚拟支付参数(现网),未开通可留空
WX_PAY_APPKEY_SANDBOX 虚拟支付沙箱密钥,用于上线前试跑
WX_PAY_ENV 0 现网 / 1 沙箱。改完需重启服务端
DATA_ROOT 课程数据目录的绝对路径

生成随机密钥:openssl rand -hex 32

⚠️ STATIC_BASE_URL 必须在跑数据迁移之前填对。旧库里有 48 处内容写死了旧域名, 迁移时会按这个变量改写;填错会导致图片与视频全部 404。

建表、迁移、启动

pnpm prisma:generate
pnpm prisma:migrate                    # 建表
pnpm legacy:check                      # 体检
pnpm legacy:migrate -- --dry-run       # 核对行数
pnpm legacy:migrate                    # 正式迁移
pnpm build

在宝塔「Node 项目」里新建项目:

  • 项目目录:/www/wwwroot/yelaymaths/yelaymaths_api
  • 启动文件:dist/main.js
  • 端口:3000
  • 运行方式:PM2

验证:curl https://<API域名>/healthz 应返回 {"code":0,...,"db":"ok"}。 若 db 为 down,说明数据库连接串不对。


4. 部署管理后台

cd /www/wwwroot/yelaymaths/yelaymaths_admin
pnpm install
cp .env.example .env.local
vi .env.local          # NEXT_PUBLIC_API_BASE_URL=https://<API域名>
pnpm build

同样用宝塔「Node 项目」启动:

  • 启动文件:node_modules/next/dist/bin/next,参数 start -p 3001
  • 或直接用 pnpm start
  • 端口:3001

验证:打开 https://<后台域名>,应看到登录页。 首次使用点「创建管理员账号」,创建完成后切回登录。

该接口只在系统中没有任何管理员时生效,之后调用会返回「已存在」,因此可以安全保留。


5. 部署课程数据端

把旧服务器的资源目录复制到 <数据域名> 站点根目录:

来源 目标 体积参考
旧站 Images/ <根>/Images/ 约 195 MB
旧站 WebPages/ <根>/WebPages/ 约 21 MB
旧站 Course/ <根>/Course/ 视频文件
旧站 FreeVideos/ <根>/FreeVideos/ 视频文件

再从旧小程序的 static/image/ 复制三张占位图到 <根>/Images/Placeholder/:

Images/Placeholder/user.png
Images/Placeholder/defaultAvatar.png
Images/Placeholder/weixin.png

这三张图是必需的,缺了会导致「我的」页头像位、「登录」页背景显示为裂图。 验证:

curl -o /dev/null -s -w "%{http_code}\n" https://<数据域名>/Images/Placeholder/user.png
curl -o /dev/null -s -w "%{http_code}\n" https://<数据域名>/Images/Placeholder/weixin.png

两条都返回 200 才算就绪。

目录名与文件名不可更改,前端是拼路径访问的。完整约定见 yelaymaths_data.yeomai.site/README.md。

⚠️ 付费视频目录必须挡住

付费视频的鉴权由服务端 /media/* 签名通道提供(地址有效期 30 分钟,HLS 的 .ts 分片也逐行带签名)。 但如果 Course/ 目录同时被数据域名公开暴露,任何人猜到路径就能直接下载,这套防护等于没有。

注意 Course/ 目录里有两类东西,规则要区分对待:

路径 内容 处理
Course/{课程code}.png 课程封面图 必须公开,小程序课程列表要用
Course/{code}/{章节}/{播放}/playlist.m3u8 付费视频 必须挡住

在数据端站点的 Nginx 配置里加这两条(顺序不能反):

# Course 目录下的图片(课程封面)放行
location ~* ^/Course/.*\.(png|jpe?g|webp|gif)$ {
    try_files $uri =404;
}

# Course 目录下的其它内容(视频切片、播放列表)一律拒绝
location ~ ^/Course/ {
    deny all;
}

两条都用正则 location,放行规则写在前面。Nginx 对正则 location 按书写顺序匹配, 第一条命中就不再往下走。不能用 location ^~ /Course/ —— ^~ 优先级高于正则, 会跳过放行规则,导致课程封面也变成 403。

免费内容(Images/、WebPages/、FreeVideos/)保持公开即可。

若后续改用 CDN,把 MediaService.buildSignedUrl 换成 CDN 厂商的签名方式,并在 CDN 侧对 Course/ 开鉴权。


6. 配置小程序

  1. 编辑 yelaymaths/config/index.js:

    API_BASE_URL: 'https://<API域名>',
    STATIC_BASE_URL: 'https://<数据域名>',
    
  2. 用微信开发者工具打开 yelaymaths/,执行【工具 → 构建 npm】。

  3. 上传并提交审核。

小程序不打包任何业务图片,只保留 6 个 tabBar 图标(约 19 KB)。 tabBar 图标必须本地,这是微信的限制,无法改为网络图。


7. 微信小程序后台配置

配置项 位置 值
request 合法域名 开发 → 开发管理 → 开发设置 https://<API域名>
消息推送 URL / Token 开发 → 开发管理 → 消息推送(也是虚拟支付的发货推送地址) URL 填 https://<API域名>/v1/payments/notify;Token 随便生成一串,要和 .env 里的 WX_MSG_TOKEN 一致
发货推送 URL 虚拟支付 → 基本配置 https://<API域名>/v1/payments/notify
小程序简称 虚拟支付 → 基础配置 iOS 支付必需
苹果 IAP 支付 虚拟支付 → 基础配置 iOS 支付需开启
支付道具 虚拟支付 → 道具管理 每门课建一个,记下道具 ID 与价格

消息推送的 URL 一保存,微信就会发一个 GET 来验签。服务端要是没配 WX_MSG_TOKEN 或两边不一致,页面会一直显示「服务器地址验证失败,请检查服务器配置」,发货推送也就配不上。 加解密方式选明文模式只需 Token;选安全模式则 WX_MSG_AES_KEY 也要填上后台那串 EncodingAESKey。

<image> 与 <video> 的资源域名不受合法域名限制,所以数据域名与 CDN 都不需要加白名单。

创建完道具后,在管理后台「支付商品」里把 课程 ID、微信道具 ID、价格(分) 填进去, 价格必须与微信后台完全一致(单位:分)。


8. 上线顺序

建议严格按此顺序,每步都可独立验证:

  1. 服务器与数据库就绪 → 备份旧库
  2. 部署服务端 → /healthz 返回 db: ok
  3. 建表 + 数据迁移 → 行数校验通过
  4. 部署管理后台 → 能登录,用户数与旧库一致
  5. 部署数据端 → 浏览器直接打开一张图片能显示
  6. 小程序改配置并上传 → 真机验证:首页图片、试听播放、已购课程播放
  7. 微信后台配域名与支付 → 用一笔小额真实订单验证:支付 → 发货 → 课程出现在「知识」页

9. 上线后验证清单

  • /healthz 返回 db: ok
  • 管理后台用户数 = 189(旧库 284 行中有 95 行是平台安全扫描留下的探针记录,已过滤)
  • 随机挑一个老用户,其「已购课程」与升级前一致
  • 首页轮播、新课长图、试听封面图片全部能加载
  • pages/VideoDetailsPage/VideoDetailsPage 的公式图能显示
  • 免费试听能播放
  • 已购课程能播放(走 /media/... 签名地址)
  • 未登录状态下访问受保护页面会跳转登录
  • 未购买课程看不到课时列表(直接请求接口返回 3000 错误码)
  • 支付一笔小额订单,课程正确开通
  • 管理后台能看到这笔订单,状态为「已开通」
  • 数据端已加 Course/ 防盗链规则,且「知识」页课程封面正常显示(200)

10. 回滚

  • 旧服务端与旧数据库保留不动,直到新系统稳定运行一段时间。
  • 小程序出现严重问题时,可在微信后台回退到上一个版本。
  • 数据库出问题时用第 2 节的备份恢复。

11. 安全提醒

以下密钥曾在旧代码与数据库导出文件中明文出现,必须全部轮换:

  • 旧数据库账号密码
  • 阿里云 AccessKey(旧短信模块使用,该模块已删除)
  • 163 邮箱授权码(旧邮件脚本使用,已删除)
  • 小程序 AppSecret

⚠️ AppSecret 不能提前换。旧 PHP 服务仍在使用它,一换线上小程序立刻无法登录。 正确做法是:新后端上线并确认可用后,再在微信后台重置 AppSecret 并同步更新新后端配置。