Files
YelayMaths/docs/课程上线指南.md
T
2026-10-01 07:36:39 +08:00

248 lines
11 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.
# 课程上线指南
面向「要往小程序里上一门新课」的人。按第 7 节的顺序做,每一步都有验证方法。
---
## 0. 先理解三件事
1. **数据分两处**:课程 / 课时的**文字信息**在数据库里(管理后台改的就是它);
**视频和图片**在数据端,是磁盘上的文件。
2. **付费课时不是 mp4**,是 HLS 目录(一个 `playlist.m3u8` + 一堆 `.ts` 分片)。
3. **媒体文件不在云服务器上**。数据域名整站反代到隧道另一端(NAS),服务端播放视频也走同一条路。
所以文件最终要落到 NAS 上对应的目录里,放对位置就立刻生效。
> ⚠️ 目前**还没有后台上传功能**。文件要手工(或打包成 zip)放到 NAS 的对应目录,
> 再回管理后台填表单。
---
## 1. 三个不能随便起的名字
这三个名字一旦定下来就**不要改**,它们是文件路径的一部分:
| 名字 | 同时还是 |
|---|---|
| `courses.code` | 视频目录名 `Course/{code}/` 和封面名 `Course/{code}.png` |
| `lessons.chapter_id` | 目录中的一级:`Course/{code}/{chapter_id}/` |
| `lessons.play_id` | 目录中的二级:`Course/{code}/{chapter_id}/{play_id}/` |
改其中任何一个,已经发出去的播放地址、封面、以及用户端的缓存全部失效。
另外一批名字是**拼出来的**,由后台填的文字决定,同样不能随手改:
| 小程序里的地址 | 由什么拼出来 |
|---|---|
| `Course/{code}.png` | 课程 `code` |
| `Images/NewCourse/{条目名}.png` | 首页新课的 `course_name` |
| `Images/FreeCourse/{条目名}.png` | 首页试听的 `course_name` |
| `Images/Banner/{文件名}` | 轮播图的 `filename`(含扩展名) |
| `WebPages/{网页id}.png` | 文章的 `legacy_page_id` |
现网示例:`code` 是 64 位十六进制,`chapter_id` 是 `1`、`2`、`3`,
`play_id` 是 13 位十六进制(如 `65eefb35e29da`)。
---
## 2. 要准备的文件
假设新课的 `code` 是 `AbC...`(64 位),有三个章节、每章两个课时:
```
Course/AbC....png ← 课程封面,1 张
Course/AbC.../1/65eefb35e29da/playlist.m3u8 ← 课时 1
Course/AbC.../1/65eefb35e29da/0000.ts 0001.ts ... ← 该课时的分片
Course/AbC.../1/65eefb35e29db/playlist.m3u8 ← 课时 2
Course/AbC.../2/65eefb50f02b5/playlist.m3u8 ← 课时 3
...
```
如果这门课还要上首页,另外准备:
| 用途 | 文件 | 引用方式 |
|---|---|---|
| 首页「新课上线」列表图 | `Images/NewCourse/{条目名}.png` | 按 `course_name` 拼 |
| 首页「新课上线」长图内页 | `Images/NewCourseWebPages/{分组名}/{n}.png` | 写在长图 HTML 里 |
| 首页「试听」列表图 | `Images/FreeCourse/{条目名}.png` | 按 `course_name` 拼 |
| 免费试听视频 | `FreeVideos/{任意目录}/{任意名}.m3u8` + 分片 | 路径存在数据库字段里 |
**图片规格参考**
- 课程封面:现网是 **900×500**(后台卡片按 9:5 显示,整张完整展示不裁剪)。
- 新课长图:现网每张 **800px 宽**,高度按内容(实测一张 800×564)。所有图宽度保持一致。
- 首页列表图:按名称拼 `.png`,建议和长图同宽或按 2 倍图准备。
---
## 3. HLS 打包要求
现网一个真实的 `playlist.m3u8`:
```
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:4
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:4.166667,
0000.ts
#EXTINF:4.166667,
0001.ts
```
四条硬要求:
1. **分片引用必须是相对路径**(`0000.ts`),不能写 `http://...` 绝对地址。
服务端会给每一行分片单独签名,绝对地址会被跳过——防盗链直接失效,播放器还会绕过服务端直连。
2. **文件名必须叫 `playlist.m3u8`**。这个名是服务端写死的,不是数据库里的字段。
3. **目录名只用字母数字**。`chapter_id` / `play_id` 参与播放地址的拼接与签名,中文和空格容易踩坑。
4. **目录里不要放无关文件**,整棵目录树都在签名保护下。
其余参数(切片时长、编码、加密)没有强制要求,按上面的形状切即可。
如果做了 HLS 加密,`.key` 文件要放在同一目录树内,`EXT-X-KEY` 的 URI 也用相对路径。
---
## 4. 首页新课长图的内容要求
「长图」不是一张图片文件,而是 `home_courses.content` 里的一段 **HTML**,
服务端原样返回,小程序用内置的 Parser 组件渲染。现网真实内容:
```html
<p style="font-size: 0; margin: 0; padding: 0;">
<img src="https://<数据域名>/Images/NewCourseWebPages/几何模型/1.png"/>
<img src="https://<数据域名>/Images/NewCourseWebPages/几何模型/2.png"/>
<img src="https://<数据域名>/Images/NewCourseWebPages/几何模型/3.png"/>
</p>
```
要求:
1. **图片必须是绝对 URL**(带 `https://<数据域名>`)。写相对路径会裂图——小程序会把 `/xxx.png` 当成包内的本地文件。
2. **外层 `<p>` 必须带 `font-size: 0`**(通常还带 `margin:0; padding:0`)。少了它,图与图之间会露出白缝,长图就断了。
3. **不要给 `<img>` 写 width / height**。渲染时会自动补 `width:100%` 按屏宽缩放;写死像素宽度会溢出屏幕。
4. **底部要留白**。长图页底部钉着两个悬浮按钮(购买 / 咨询客服),末尾留一段空白,否则按钮压住内容。
5. **只能放静态标签**。`<script>`、`<iframe>`、`<video>` 不会执行。
6. 长图里的图片路径是**手写在 HTML 里**的,改名或挪目录内容不会跟着变,会直接 404。所以图片一次定稿再填。
---
## 5. 后台要填的表单
### 课程(管理后台 → 课程)
| 字段 | 说明 |
|---|---|
| code | 64 位十六进制串,**创建后不可改** |
| title | 课程名 |
| intro | 一句话介绍 |
| price_fen | 价格,单位**分**(1.10 元填 110) |
| status / sort_order | 上架状态、排序 |
### 课时(管理后台 → 课时,每个视频一行)
| 字段 | 说明 |
|---|---|
| course_id | 所属课程 |
| chapter_id / play_id | 必须和磁盘目录**逐字对应** |
| chapter_title / lesson_title | 章节名、视频名 |
| duration | 现网存的是分钟数,如 `14`、`21` |
| published_at / sort_order | 发布日期、排序 |
约束:`(course_id, chapter_id, play_id)` 唯一——同一章里不能有重复的 `play_id`。
### 支付商品(管理后台 → 支付商品,要卖就得填)
| 字段 | 说明 |
|---|---|
| course_id | 对应课程 |
| wx_product_id | 微信后台的道具 ID |
| price_fen | **必须和微信后台道具价格完全一致**,否则下单会被拒 |
| name / status | 商品名、是否启用 |
### 首页新课(管理后台 → 首页新课,要上首页才填)
| 字段 | 说明 |
|---|---|
| course_name | 条目名,**决定列表缩略图** `Images/NewCourse/{条目名}.png` |
| content | 第 4 节那段长图 HTML |
| course_id | 关联到可购买的课程;**留空则长图页不显示购买按钮**,只剩客服按钮 |
| intro / published_at / sort_order / status | 列表展示用 |
### 首页试听(管理后台 → 首页试听 + 免费试听课)
- 首页试听:`series_no`(要和免费试听课的序号对上)、`course_name`(决定 `Images/FreeCourse/{条目名}.png`)、排序、状态。
- 免费试听课:`series_no`、`course_name`、`video_title`、`duration`、`video_m3u8`(如 `FreeVideos/9102/9102.m3u8`)、`cover_image`(如 `Images/FreeCourse/xxx.png`)。
**这两个字段是数据库里存的全路径**,不是拼出来的,可以自由命名。
> 注意:课程表 / 首页新课 / 课时表里都有一个「封面文件 / cover_image」字段,
> **接口目前不使用它们**,真正生效的是第 1 节里「拼出来的」那些地址。
> 只有免费试听课的 `cover_image` 是真正生效的。
---
## 6. 微信小程序后台要先做的
要在小程序里卖这门课,必须先建支付道具:
1. 【虚拟支付 → 道具管理】新建道具,拿到**道具 ID** 与价格。
2. 价格记下来,后台「支付商品」里要填一模一样的分值。
3. 道具建好后要**发布**,未发布的道具下不了单。
---
## 7. 操作顺序
| 步骤 | 做什么 | 怎么验证 |
|---|---|---|
| 1 | 微信后台建支付道具,记下道具 ID 和价格 | 道具状态是已发布 |
| 2 | 定下课程的 `code`(64 位十六进制) | —— |
| 3 | 把封面和所有课时目录放到数据端对应路径 | 浏览器直接打开 `Course/{code}.png` 能显示 |
| 4 | 后台建**课程**行,`code` 填上面那个 | 小程序「知识」页能看到课程、封面正常 |
| 5 | 后台建**课时**行,`chapter_id` / `play_id` 和目录逐字对上 | 买过课之后能播放;没买时报「尚未拥有该课程」 |
| 6 | 后台建**支付商品**行,价格与微信一致 | 走一笔小额真实订单,能下单成功 |
| 7 | 首页要展示的话,建**首页新课 / 首页试听**并填长图 | 首页能看到条目、点进去长图完整、底部按钮不挡内容 |
| 8 | 课程 `status` 改为 1(上架) | 小程序下拉刷新后可见 |
> ⚠️ **上架前先确认后台左上角的「查看的数据源」是生产库。**
> 后台有两个概念:「查看的数据源」(只影响你自己看到什么)和「线上小程序数据源」(影响所有用户)。
> 在开发库建的课程,用户在线上是看不到的。
---
## 8. 上线后验证清单
- [ ] 首页能看到新课条目,缩略图正常
- [ ] 点进长图页:图片连成一张、没有白缝、底部按钮不挡住内容
- [ ] 长图页的「立即支付」按钮存在(说明 `course_id` 关联上了)
- [ ] 「知识」页能看到这门课的封面
- [ ] 未购买的账号点课程,看不到课时列表
- [ ] 走一笔小额真实支付:能拉起支付 → 支付成功后课程出现在「已购」里
- [ ] 已购账号能播放每一个课时(不是只测第一个)
- [ ] 后台「订单」页能看到这笔订单,状态「已开通」
- [ ] 后台「数据看板」的累计订单 +1、成交金额增加
---
## 9. 常见坑
| 现象 | 原因 | 处理 |
|---|---|---|
| 封面 404 | 文件名和 `code` 对不上,或图片没放上去 | 地址必须是 `Course/{code}.png`,逐字核对 |
| 播放地址 404 / 播不了 | `chapter_id`、`play_id` 和目录名不一致 | 三处(数据库、目录、表单)必须逐字相同 |
| 长图断了、有白缝 | 外层 `<p>` 少了 `font-size: 0` | 见第 4 节 |
| 长图裂图 | 图片用了相对路径 | 必须写完整的 `https://<数据域名>/...` |
| 换了同名图片但没变 | 数据端对图片有 1 分钟缓存 | 等 1 分钟,或清 Nginx 缓存后 reload |
| 换了同名视频但播还是旧的 | 播放地址响应头带 5 分钟缓存 | 等 5 分钟;播放地址本身有效期是 30 分钟 |
| 下单提示「该课程尚未配置支付商品」 | `products` 里没有启用的商品行 | 见第 5 节 |
| 下单后拉不起支付 | 微信道具没发布,或价格与后台不一致 | 见第 6 节 |
| 长图页没有购买按钮 | `home_courses.course_id` 为空 | 填上关联课程 |
| 改完没生效 | 后台看的是开发库 | 切回生产库再操作 |
---
## 10. 相关文档
- [部署指南.md](部署指南.md):服务端 / 后台 / 数据端怎么部署
- [重构开发文档.md](重构开发文档.md):整体设计与数据模型
- [数据端 README](../yelaymaths_data.yeomai.site/README.md):数据端目录结构与路径约定