11 KiB
课程上线指南
面向「要往小程序里上一门新课」的人。按第 7 节的顺序做,每一步都有验证方法。
0. 先理解三件事
- 数据分两处:课程 / 课时的文字信息在数据库里(管理后台改的就是它); 视频和图片在数据端,是磁盘上的文件。
- 付费课时不是 mp4,是 HLS 目录(一个
playlist.m3u8+ 一堆.ts分片)。 - 媒体文件不在云服务器上。数据域名整站反代到隧道另一端(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
四条硬要求:
- 分片引用必须是相对路径(
0000.ts),不能写http://...绝对地址。 服务端会给每一行分片单独签名,绝对地址会被跳过——防盗链直接失效,播放器还会绕过服务端直连。 - 文件名必须叫
playlist.m3u8。这个名是服务端写死的,不是数据库里的字段。 - 目录名只用字母数字。
chapter_id/play_id参与播放地址的拼接与签名,中文和空格容易踩坑。 - 目录里不要放无关文件,整棵目录树都在签名保护下。
其余参数(切片时长、编码、加密)没有强制要求,按上面的形状切即可。
如果做了 HLS 加密,.key 文件要放在同一目录树内,EXT-X-KEY 的 URI 也用相对路径。
4. 首页新课长图的内容要求
「长图」不是一张图片文件,而是 home_courses.content 里的一段 HTML,
服务端原样返回,小程序用内置的 Parser 组件渲染。现网真实内容:
<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>
要求:
- 图片必须是绝对 URL(带
https://<数据域名>)。写相对路径会裂图——小程序会把/xxx.png当成包内的本地文件。 - 外层
<p>必须带font-size: 0(通常还带margin:0; padding:0)。少了它,图与图之间会露出白缝,长图就断了。 - 不要给
<img>写 width / height。渲染时会自动补width:100%按屏宽缩放;写死像素宽度会溢出屏幕。 - 底部要留白。长图页底部钉着两个悬浮按钮(购买 / 咨询客服),末尾留一段空白,否则按钮压住内容。
- 只能放静态标签。
<script>、<iframe>、<video>不会执行。 - 长图里的图片路径是手写在 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. 微信小程序后台要先做的
要在小程序里卖这门课,必须先建支付道具:
- 【虚拟支付 → 道具管理】新建道具,拿到道具 ID 与价格。
- 价格记下来,后台「支付商品」里要填一模一样的分值。
- 道具建好后要发布,未发布的道具下不了单。
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:整体设计与数据模型
- 数据端 README:数据端目录结构与路径约定