四端上传

This commit is contained in:
tianmo committed 2026-09-29 15:25:40 +08:00
1 parent 25bbb5f9c8
commit c6e2cb4d5b
276 files changed
+17812 -3

No files matched your search

+263
View File
@@ -0,0 +1,263 @@
# 部署指南
面向接手部署的人。按顺序执行即可,每一步都有验证方法。
---
## 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 并同步更新新后端配置。