Files
YelayMaths/docs/部署指南.md
T
2026-09-29 15:25:40 +08:00

264 lines
9.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 部署指南
面向接手部署的人。按顺序执行即可,每一步都有验证方法。
---
## 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`),它不建影子库,无需额外权限。
3. **备份旧库**(迁移前必做):
```bash
mysqldump -u <用户> -p api_yeomai_site > /root/backup/api_yeomai_site_$(date +%F).sql
```
---
## 3. 部署服务端
```bash
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` | 虚拟支付参数,未开通可留空 |
| `DATA_ROOT` | 课程数据目录的**绝对路径** |
> 生成随机密钥:`openssl rand -hex 32`
>
> ⚠️ `STATIC_BASE_URL` 必须在跑数据迁移**之前**填对。旧库里有 48 处内容写死了旧域名,
> 迁移时会按这个变量改写;填错会导致图片与视频全部 404。
### 建表、迁移、启动
```bash
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. 部署管理后台
```bash
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
```
**目录名与文件名不可更改**,前端是拼路径访问的。完整约定见
[`yelaymaths_data.yeomai.site/README.md`](../yelaymaths_data.yeomai.site/README.md)。
### ⚠️ 付费视频目录必须挡住
付费视频的鉴权由服务端 `/media/*` 签名通道提供(地址有效期 30 分钟,HLS 的 `.ts` 分片也逐行带签名)。
但如果 `Course/` 目录同时被数据域名公开暴露,任何人猜到路径就能直接下载,这套防护等于没有。
**在数据端站点的 Nginx 配置里加一条:**
```nginx
location ^~ /Course/ {
deny all;
}
```
免费内容(`Images/`、`WebPages/`、`FreeVideos/`)保持公开即可。
> 若后续改用 CDN,把 `MediaService.buildSignedUrl` 换成 CDN 厂商的签名方式,并在 CDN 侧对 `Course/` 开鉴权。
---
## 6. 配置小程序
1. 编辑 `yelaymaths/config/index.js`:
```js
API_BASE_URL: 'https://<API域名>',
STATIC_BASE_URL: 'https://<数据域名>',
```
2. 用微信开发者工具打开 `yelaymaths/`,执行【工具 → 构建 npm】。
3. 上传并提交审核。
> 小程序**不打包任何业务图片**,只保留 6 个 tabBar 图标(约 19 KB)。
> tabBar 图标必须本地,这是微信的限制,无法改为网络图。
---
## 7. 微信小程序后台配置
| 配置项 | 位置 | 值 |
|---|---|---|
| request 合法域名 | 开发 → 开发管理 → 开发设置 | `https://<API域名>` |
| 发货推送 URL | 虚拟支付 → 基本配置 | `https://<API域名>/v1/payments/notify` |
| 小程序简称 | 虚拟支付 → 基础配置 | iOS 支付必需 |
| 苹果 IAP 支付 | 虚拟支付 → 基础配置 | iOS 支付需开启 |
| 支付道具 | 虚拟支付 → 道具管理 | 每门课建一个,记下道具 ID 与价格 |
> `<image>` 与 `<video>` 的资源域名**不受合法域名限制**,所以数据域名与 CDN 都不需要加白名单。
创建完道具后,在管理后台「支付商品」里把 `课程 ID`、`微信道具 ID`、`价格(分)` 填进去,
价格必须与微信后台完全一致(单位:分)。
---
## 8. 上线顺序
建议严格按此顺序,每步都可独立验证:
1. 服务器与数据库就绪 → 备份旧库
2. 部署服务端 → `/healthz` 返回 `db: ok`
3. 建表 + 数据迁移 → 行数校验通过
4. 部署管理后台 → 能登录,用户数与旧库一致
5. 部署数据端 → 浏览器直接打开一张图片能显示
6. 小程序改配置并上传 → 真机验证:首页图片、试听播放、已购课程播放
7. 微信后台配域名与支付 → 用一笔小额真实订单验证:支付 → 发货 → 课程出现在「知识」页
---
## 9. 上线后验证清单
- [ ] `/healthz` 返回 `db: ok`
- [ ] 管理后台用户数 = 284(迁移前旧库数量)
- [ ] 随机挑一个老用户,其「已购课程」与升级前一致
- [ ] 首页轮播、新课长图、试听封面图片全部能加载
- [ ] `pages/VideoDetailsPage/VideoDetailsPage` 的公式图能显示
- [ ] 免费试听能播放
- [ ] 已购课程能播放(走 `/media/...` 签名地址)
- [ ] 未登录状态下访问受保护页面会跳转登录
- [ ] 未购买课程看不到课时列表(直接请求接口返回 3000 错误码)
- [ ] 支付一笔小额订单,课程正确开通
- [ ] 管理后台能看到这笔订单,状态为「已开通」
---
## 10. 回滚
- 旧服务端与旧数据库**保留不动**,直到新系统稳定运行一段时间。
- 小程序出现严重问题时,可在微信后台回退到上一个版本。
- 数据库出问题时用第 2 节的备份恢复。
---
## 11. 安全提醒
以下密钥曾在旧代码与数据库导出文件中明文出现,**必须全部轮换**:
- 旧数据库账号密码
- 阿里云 AccessKey(旧短信模块使用,该模块已删除)
- 163 邮箱授权码(旧邮件脚本使用,已删除)
- 小程序 AppSecret
> ⚠️ **AppSecret 不能提前换**。旧 PHP 服务仍在使用它,一换线上小程序立刻无法登录。
> 正确做法是:新后端上线并确认可用后,再在微信后台重置 AppSecret 并同步更新新后端配置。