11 KiB
部署指南
面向接手部署的人。按顺序执行即可,每一步都有验证方法。
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. 服务器准备(宝塔面板)
-
软件商店安装:MySQL 5.7、Nginx、Node 版本管理器(选 Node 22 LTS)。
-
防火墙放行 80 / 443。
-
建 3 个站点,各配 Let's Encrypt 证书:
站点 反向代理到 <API域名>http://127.0.0.1:3000<后台域名>http://127.0.0.1:3001这两个站点都不是静态站点,反向代理必须配,只上传文件不会工作。
-
建数据端站点
<数据域名>,根目录指向课程数据目录(见第 6 节)。
2. 数据库
- 新建数据库
yelaymaths,字符集utf8mb4。 - 新建数据库账号并授权。
关于 Prisma 权限:
prisma migrate dev需要CREATE DATABASE权限来建影子库,宝塔默认账号没有。 生产环境只用pnpm prisma:migrate(即prisma migrate deploy),它不建影子库,无需额外权限。
-
备份旧库(迁移前必做):
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. 配置小程序
-
编辑
yelaymaths/config/index.js:API_BASE_URL: 'https://<API域名>', STATIC_BASE_URL: 'https://<数据域名>', -
用微信开发者工具打开
yelaymaths/,执行【工具 → 构建 npm】。 -
上传并提交审核。
小程序不打包任何业务图片,只保留 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. 上线顺序
建议严格按此顺序,每步都可独立验证:
- 服务器与数据库就绪 → 备份旧库
- 部署服务端 →
/healthz返回db: ok - 建表 + 数据迁移 → 行数校验通过
- 部署管理后台 → 能登录,用户数与旧库一致
- 部署数据端 → 浏览器直接打开一张图片能显示
- 小程序改配置并上传 → 真机验证:首页图片、试听播放、已购课程播放
- 微信后台配域名与支付 → 用一笔小额真实订单验证:支付 → 发货 → 课程出现在「知识」页
9. 上线后验证清单
/healthz返回db: ok- 管理后台用户数 = 189(旧库 284 行中有 95 行是平台安全扫描留下的探针记录,已过滤)
- 随机挑一个老用户,其「已购课程」与升级前一致
- 首页轮播、新课长图、试听封面图片全部能加载
pages/VideoDetailsPage/VideoDetailsPage的公式图能显示- 免费试听能播放
- 已购课程能播放(走
/media/...签名地址) - 未登录状态下访问受保护页面会跳转登录
- 未购买课程看不到课时列表(直接请求接口返回 3000 错误码)
- 支付一笔小额订单,课程正确开通
- 管理后台能看到这笔订单,状态为「已开通」
- 数据端已加
Course/防盗链规则,且「知识」页课程封面正常显示(200)
10. 回滚
- 旧服务端与旧数据库保留不动,直到新系统稳定运行一段时间。
- 小程序出现严重问题时,可在微信后台回退到上一个版本。
- 数据库出问题时用第 2 节的备份恢复。
11. 安全提醒
以下密钥曾在旧代码与数据库导出文件中明文出现,必须全部轮换:
- 旧数据库账号密码
- 阿里云 AccessKey(旧短信模块使用,该模块已删除)
- 163 邮箱授权码(旧邮件脚本使用,已删除)
- 小程序 AppSecret
⚠️ AppSecret 不能提前换。旧 PHP 服务仍在使用它,一换线上小程序立刻无法登录。 正确做法是:新后端上线并确认可用后,再在微信后台重置 AppSecret 并同步更新新后端配置。