Python + 微信小程序实现视频点播系统:从架构到踩坑全记录
最近接手了一个挺典型的全栈项目:用 Python 做后端支撑,微信小程序做前端载体,实现一套短视频播放加点播功能的系统。
这个项目看起来不算复杂,但真正动手后才发现,里面藏着不少细节坑——从视频转码策略、播放器组件选型,到支付合规、iOS 端适配,每一环都有讲究。这篇文章把我从需求拆解到上线的完整思路和踩坑记录整理出来,给同样想做视频类小程序的朋友一个参考。
1. 项目整体设计与技术选型
1.1 核心需求拆解:不只是“能放视频”这么简单
很多人拿到“短视频播放系统”这类需求,第一反应就是页面套一个 video 标签就完事。但实际做起来,需求会拆成好几层。
用户端要的是:刷视频流畅、切换顺滑、不卡顿、封面好看、能点赞评论、能追更连载内容。运营端要的是:能上传视频、能管理视频上下架、能看到播放量数据、能对内容做审核。商业化要的是:会员体系、付费点播、广告位预留。
所以我在设计第一版的时候,把整个系统分成了三个端: 用户小程序端、Python 管理后台、服务端 API 。小程序端只管展示和交互,管理后台给运营人员用,API 层做数据控制和业务逻辑。这种前后端分离的设计,后期扩展功能或者换客户端(比如再做 App 端)都很方便。
1.2 为什么选择 Python 做后端,小程序做前端
技术选型这块,我刚开始也犹豫过,要不要用 Java Spring Boot,毕竟企业级项目用得多。但考虑到团队成员的熟悉度和项目快速迭代的需求,最后还是定了 Python。
Python 在这类项目的优势很明显:生态里有现成的视频处理库(FFmpeg 的 Python 封装)、Web 框架轻量(Django/Flask 都可选)、写后台逻辑效率极高。而且如果你后续想加推荐算法、做数据分析,Python 这套技术栈可以无缝衔接。
小程序端,我选了 原生开发 而不是 uni-app。为什么?因为短视频场景对播放性能要求高,原生 video 组件的控制力更强,而且小程序的 video 组件本身有同层渲染的特性,用原生方式写踩坑更少、排查问题更方便。如果只是做简单的信息展示类小程序,uni-app 是没问题的,但视频场景我建议原生。
1.3 系统架构设计的整体思路
我画了一张逻辑架构图(这里用文字描述):
小程序客户端 -> 微信网关(登录鉴权) -> Nginx(反向代理 + 静态资源) -> Python Web 服务(Django) -> MySQL(业务数据)+ Redis(缓存) -> 对象存储/本地磁盘(视频文件)
之所以加一层 Nginx,是因为 Python 自带的开发服务器处理高并发不行,生产环境必须要用 Nginx 做反向代理和静态文件服务。视频文件如果直接由 Django 读磁盘返回,IO 压力会非常大,正确做法是:小程序端拿到视频地址后,直接请求 Nginx 或者 CDN 上的静态文件,完全不经过 Python 进程。
提示:千万不要让 Django 像代理一样帮你转发视频流,那样并发一上来,CPU 直接打满。视频文件应该走 Nginx 的静态资源服务或者 CDN 分发。
1.4 数据库设计要点:视频表字段怎么规划
视频相关的表我踩过几次坑,核心字段分享出来:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| title | varchar(128) | 视频标题 |
| cover_url | varchar(512) | 封面图地址 |
| video_url | varchar(512) | 视频播放地址 |
| duration | int | 时长(秒) |
| category_id | int | 分类 ID |
| tags | varchar(255) | 标签,逗号分隔 |
| status | tinyint | 0草稿 1上架 2下架 |
| audit_status | tinyint | 0待审 1通过 2驳回 |
| like_count | int | 点赞数(冗余字段) |
| play_count | int | 播放数(冗余字段) |
| is_vip | tinyint | 是否仅会员可看 |
| price | decimal(10,2) | 点播价格 |
| created_at | datetime | 创建时间 |
| updated_at | datetime | 更新时间 |
冗余字段 like_count 和 play_count 很多人会犹豫要不要放,我建议一定放。因为这个系统的读请求远大于写请求,每次展示视频列表都去实时统计点赞数,代价太大。后面用定时任务或者消息队列去更新冗余计数即可。
2. 核心功能模块的详细设计
2.1 用户登录与账号体系设计
小程序登录用的是微信的静默授权机制。前端调用
wx.login()
拿到临时 code,传给后端,后端再用 code 去微信接口换 openid 和 session_key。
这里有个关键点: 绝对不要在信任用户前端传入的身份信息 。以前见过有人直接把用户昵称头像当登录凭证,这是极大的安全隐患。正确流程是:
前端在小程序端
wx.login()
获得 code,通过
wx.request
发送到后端
/api/auth/login
,后端拿 code 请求微信接口,拿到
openid
和
session_key
,用 openid 查库——如果不存在就自动注册一个用户,然后签发自定义登录态 token 返回给前端,token 存到小程序的 storage 里,后续请求头里带上 token。
用户头像昵称这类信息,在小程序里必须用官方提供的头像昵称填写能力,直接引导用户授权获取新版本已经不行了。我当时的做法是:每次用户进入个人中心时,检查数据库里有没有头像昵称信息,如果在完善资料页让用户主动填写,拿到临时 URL 再传到自己的服务器或对象存储上。
2.2 视频模块:轮播推荐、分类、搜索、播放历史
视频模块是重头戏,我拆成了几个子模块:
- 轮播推荐 :查数据库里 status=1 且人工置顶的视频,返回给前端一个列表。前端在页面顶部的 swiper 里做轮播展示。
- 分类视频 :短视频场景分类不用太多,我设置了推荐、生活、搞笑、影视、知识、音乐等几个大类,用户点击 tab 切换时,按 category_id 去过滤。
- 搜索 :这里有个细节,用 MySQL 的 LIKE 做模糊搜索在小体量项目里已经是够了。但如果视频量级上来,建议接入 Elasticsearch 或者用 MySQL 全文索引(ngram parser),否则搜索会拖垮数据库性能。
- 播放历史 :这个表记录 user_id + video_id + progress + update_time。用户再次点击某个视频时,可从上次的进度继续播放。
每条视频的详情数据,用了 Django REST Framework 来序列化返回,接口大致长这样:
{
"code": 0,
"data": {
"id": 123,
"title": "Python入门教程第1集",
"cover_url": "https://yourcdn.com/cover/123.jpg",
"video_url": "https://yourcdn.com/video/123/index.m3u8",
"duration": 348,
"play_count": 15234,
"like_count": 231,
"tags": ["Python", "教程"],
"is_vip": false
}
}
2.3 评论、点赞与互动功能的“隐藏成本”
评论和点赞,表面看就是两张表,但实际有两个隐藏的坑:
第一个坑:
点赞的幂等性
。用户疯狂点点赞,后端接口不能每次都被击穿。我的做法是在 Redis 里存
like:{video_id}:{user_id}
,第一次点赞时写入并计数,再点就是取消点赞删除 key,同时把操作写入队列异步落库。
第二个坑: 评论的富文本安全 。小程序端没有太多富文本编辑器,但用户也可能输入表情或者特殊字符。后端入库前必须做转义和长度校验,并且展示时要对内容做 HTML 转义防范 XSS。
点赞和评论如果做到了实时通知,那又是一个大模块,需要用到 WebSocket 长连接。这个项目的初期版本里我没做实时通知,而是用定时拉取未读数的方式。
2.4 Redis 缓存策略:视频列表和播放量的加速方案
视频列表的接口如果每次都查 MySQL,数据库压力很大。我设计了一套多级缓存策略:
第一层缓存:视频列表的 JSON 数据存到 Redis 里,Key 为
video_list:{category}:{page}
,TTL 设置 60~120 秒。刷新列表、上下架操作时主动删除相关缓存。
第二层:播放量的更新不直接写数据库,用 Redis 的 INCR 命令累加,每隔 5 分钟批量同步到 MySQL 一次。这样播放量最高的视频能在榜单接口秒出,也不用担心数据库写入过大。
实操经验:排行榜别在 SQL 里用 ORDER BY play_count DESC,直接维护一个 Redis 的 ZSET,member 是 video_id,score 是播放量。取 Top 20 的时间复杂度是 O(1),非常高效。
3. 视频处理与转码方案
3.1 为什么不能把原始 mp4 直接给前端播放
这个问题我特意拿出来说一说,因为真的见过太多人直接把 mp4 文件塞给前端播放。结果是什么?短视频还能凑合,长视频一点播:
- 播放器要等整个文件下载完才知道总时长,体验很糟糕
- 拖动进度条时有可能整个视频卡住
- 不同 Android 机型的兼容性没法保证
- 原始视频动辄几百 MB,服务器带宽会被拖垮
正确的做法是转码为 HLS 协议(m3u8 + ts 切片)。HLS 把一个视频切成很多小片段,播放器可以边下边播,而且天然支持多码率切换。
转码用的 FFmpeg,在服务器上装好之后用命令行或者 Python 调用都可以。我用的命令大致是:
ffmpeg -i input.mp4 -profile:v baseline -level 3.0 -start_number 0 \
-hls_time 10 -hls_list_size 0 \
-f hls output/index.m3u8
这里
-hls_time 10
表示每 10 秒切成一个切片文件,
-hls_list_size 0
表示生成完整的播放列表文件。输出目录里会有一堆 ts 切片和 index.m3u8,播放器只需要读取 m3u8 文件就能自动加载所有切片。
3.2 用 Python + FFmpeg 做视频上传转码的完整流程
我设计的上传转码流程是这样的:
前端小程序用
wx.uploadFile
把视频传到后端的上传接口,这个接口先不看大小直接接收存到临时目录。然后发一个异步任务给 Celery/后台线程去调 FFmpeg 转码。为什么要异步?因为转码是 CPU 密集型操作,如果同步处理,一个 100MB 的视频能让你接口请求超时。
异步任务结束后,把转码完成的 m3u8 和 ts 文件存到指定目录,同时用 ffmpeg 截取视频中间帧做封面图,再更新数据库里的 video_url 和 cover_url。
from django.shortcuts import get_object_or_404
from rest_framework.decorators import api_view
from rest_framework.response import Response
from .tasks import transcode_video
@api_view(['POST'])
def upload_video(request):
# 接收上传文件,保存到临时目录
video_file = request.FILES.get('file')
temp_path = f'/tmp/uploads/{video_file.name}'
with open(temp_path, 'wb+') as f:
for chunk in video_file.chunks():
f.write(chunk)
# 触发异步转码任务
task = transcode_video.delay(temp_path, request.user.id)
return Response({'code': 0, 'task_id': task.id})
3.3 多码率与清晰度切换:给用户更好的体验
如果只是做短视频,清晰度切换可以不做,但如果你希望系统有更专业的表现,多码率是加分项。HLS 支持在一个 m3u8 文件里用变量列表(Master Playlist)来定义多个子播放列表。
例如:
#EXTM3U
#EXT-X-STREAM-INF:BANDWIDTH=1280000,RESOLUTION=720x404
720p/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=640000,RESOLUTION=360x202
360p/index.m3u8
前端可以在设置菜单里让用户选择清晰度,小程序视频组件支持切换 src 实现这个能力。
3.4 视频防盗链的三层方案
视频资源被人家直接下载盗用屡见不鲜,防盗链至少要加三层:
第一层:
签名 URL
。生成类似
https://yourdomain.com/video/123/index.m3u8?token=xxxx&expire=3600
这种带过期时间的地址,服务端在 Nginx 的 Lua 脚本或者业务层校验 token。
第二层:
Referer 校验
。在 Nginx 配置的
valid_referers
中,只允许自己的合法域名过来才返回视频文件。不过现在的小程序设计有些可能不会带 Referer,所以这个只能作为辅助手段。
第三层: 切片文件乱序或混淆 。这个方法稍微复杂一些,就是把 ts 文件名故意设计成无规律形式,并且在 m3u8 里把顺序打乱,通过自定义的 key 来解析真实顺序。好处是反爬的人就算拿到 m3u8 也拼不出来正常的视频文件。
4. 小程序端功能实现与踩坑
4.1 首页短视频流:swiper + video 实现滑动播放
短视频信息流的核心交互是上下滑动切换视频。微信小程序里最直接的方式是用 swiper 组件,把每个 swiper-item 里塞一个 video 组件。
但这里有个关键配置:swiper 得用
vertical
属性设置纵向滑动,同时设置高度为 100vh,让每个 swiper-item 撑满全屏。video 组件要设置
autoplay
为 false,因为我们只在滑动停止后才触发播放,否则页面一加载所有视频同时播,性能直接爆炸。
<swiper
vertical="true"
class="video-swiper"
current="{{currentIndex}}"
bindchange="onSwiperChange"
duration="300"
>
<swiper-item wx:for="{{videoList}}" wx:key="id">
<video
src="{{item.video_url}}"
class="swiper-video"
object-fit="cover"
autoplay="{{item.id === videoList[currentIndex].id}}"
id="video_{{item.id}}"
show-center-play-btn="{{false}}"
controls="{{false}}"
enable-progress-gesture="{{false}}"
></video>
<!-- 覆盖层:标题、作者、点赞按钮等 -->
</swiper-item>
</swiper>
bindchange
事件触发后,拿到新的
current
值,停掉上一个视频的播放,播当前这个。这里不能靠 video 组件的自动连播,必须手动控制。
4.2 iOS 端全屏错位问题的完整解决方案
这个坑我印象深刻。在 iOS 上,swiper-item 内嵌 video 组件,一旦点击全屏按钮,video 会弹出到原生全屏层,页面其他元素全部错位甚至黑屏。网上的解法五花八门,我实测下来最稳定的是:全屏不用系统自带的全屏能力,而是用小程序提供的一个特殊配置。
video 组件上有一个
fullscreen
属性和
bindfullscreenchange
事件。当用户点击自定义的全屏按钮时,不要调用方法让它进入全屏,而是通过改变 video 的样式让它铺满整个屏幕,同时加绝对定位遮住其他元素。退出时还原样式。这样绕开了原生全屏的渲染层,从根本上避免错位。
还有一个更彻底的思路:把视频播放页独立成一个
video_play
页面,从列表页跳转过去,通过
wx.setNavigationBarColor
把沉浸式做好。短视频信息流场景一般不推荐单独页面跳转,但如果你做的不是上下滑信息流,而是点击进的详情页,那独立页面就是最优解。
4.3 video 组件在部分 Android 上无法播放
这个现象也很迷。我遇到过在小米和部分华为机型的微信里,video 组件加载 m3u8 失败,但是同样的链接在 iOS 上完全正常。
后来排查出来的原因是:个别安卓机的微信 WebView 对 HLS 协议的支持有问题,它不能通过
src
直接加载 m3u8 流。解决办法是把视频地址转成 mp4 源,或者通过后端把 m3u8 转封装为渐进式流。
还有一个更常见的坑:视频地址必须是 HTTPS。微信小程序对请求和媒体资源有强制 HTTPS 要求,如果开发工具里没设置不校验合法域名开关,线上访问时会被直接拦截,表现为视频一直 loading 不能播放。遇到这种情况先在 mp 管理后台把视频域名加到 downloadFile 合法域名白名单里。
4.4 自定义顶部导航栏与适配细节
短视频应用大多数是沉浸式风格,顶部导航栏我们都用自定义的。在小程序页面 json 里配置:
{
"navigationStyle": "custom"
}
然后前端要拿到状态栏高度和导航栏高度来撑开布局。状态栏高度可以通过
wx.getWindowInfo()
接口拿到
statusBarHeight
,胶囊按钮位置通过
wx.getMenuButtonBoundingClientRect()
拿到,导航栏高度一般是胶囊按钮的 top 减去状态栏 height 再乘 2 加上胶囊按钮的高度。
我在封装一个
navbar-helper.js
的时候,把它写在全局的工具函数里,所有页面共用,不用每页重复计算。
export function getNavBarInfo() {
const winInfo = wx.getWindowInfo()
const menuBtn = wx.getMenuButtonBoundingClientRect()
const statusBarHeight = winInfo.statusBarHeight
const navBarHeight = (menuBtn.top - statusBarHeight) * 2 + menuBtn.height
return { statusBarHeight, navBarHeight }
}
下沉式布局时,还要同时兼顾底部 tabBar 和 iPhone 底部的 SafeArea(安全区)。短视频全屏播放时,这部分必须考虑,否则 iPhone X 以下机型底部按钮会被 home indicator 遮住。
4.5 用户交互:点赞、评论、分享的实现细节
点赞按钮如果在列表里,最好做 关联当前的 video id 的局部状态管理 ,响应要快,不能等请求返回后才更新 UI。我通常是先改前端 state(乐观更新),然后异步发请求给后端,如果失败了再回滚状态并提示用户“网络异常”。
评论弹层用小程序的原生 input 做半屏弹出是可以的,但要注意键盘弹出的时候会不会遮挡输入框。搜索词里提到的"手机软键盘遮挡查询内容"问题,最简单有效的方案是用
adjust-position
属性和
keyboard-height-change
事件。这里我建议直接监听
bindkeyboardheightchange
,拿到键盘高度后动态把输入框用
position: fixed; bottom: 键盘高度px
顶上去。
分享这块没什么技术含量,onShareAppMessage 里设置好标题和封面即可,但要注意设置小程序后台的分享穿透参数,用户打开分享链接后能直达内容。
4.6 网络不可用的全局提示
“当网络不可用或者网络不好的时候,如何全局统一显示网络不可用”——这个问题我的方案是:封装一个全局请求方法,在
wx.request
的 fail 回调里,dispatch 一个状态给全局 store(我用的是小程序原生 store,也可以用 mobx)。页面统一监听这个状态,显示一个全屏的插画 + 重试按钮,同时阻止下拉刷新等后续无意义操作。
网络恢复的判断,可以监听 wx 的
onNetworkStatusChange
,如果 isConnected 变回 true 自动隐藏错误页。这里要注意 iOS 和 Android 对网络状态恢复的反馈延迟不一样,可以做一个 500ms 的延迟再更新状态。
5. 微信支付与合规开发要点
5.1 小程序支付 v3 对接全流程拆解
小程序支付和公众号支付不同,它的支付流程是:用户在
小程序端
调用
wx.requestPayment
,但是发起支付订单的后端接口一般是调用微信支付的服务端 API 来生成预支付单。
微信支付 v3 接口最核心的一点是 APIv3 密钥和证书序列号 。很多人对接时就是折腾这一段。大致流程:
- 在小程序商户平台申请 API 证书,下载证书时会获得私钥和证书序列号。
-
后端用商户私钥对请求做签名,签名算法格式是
HTTP方法 + "\n" + URL + "\n" + 时间戳 + "\n" + 随机串 + "\n" + 消息体 + "\n"。 -
调用
POST https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi下单,传入 appid、mchid、描述、金额、payer 的 openid。 - 接口返回 prepay_id,后端再用它构造小程序需要的支付参数(timeStamp、nonceStr、package 包,以及用商户私钥计算的 paySign)传给前端。
-
前端
wx.requestPayment,拉起收银台。
Python 实现签名需要用到
cryptography
库,我封装过一个方法,核心部分长这样:
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
import time, uuid, base64, json
def build_pay_params(appid, mchid, description, amount, openid, notify_url):
url = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi"
timestamp = str(int(time.time()))
nonce_str = uuid.uuid4().hex
body = json.dumps({
"appid": appid,
"mchid": mchid,
"description": description,
"notify_url": notify_url,
"amount": {"total": amount},
"payer": {"openid": openid}
}, ensure_ascii=False)
# 构造待签名串
message = f"POST\n{url}\n{timestamp}\n{nonce_str}\n{body}\n"
# 用商户私钥签名
with open("apiclient_key.pem", "rb") as f:
private_key = serialization.load_pem_private_key(f.read(), password=None)
signature = private_key.sign(message.encode("utf-8"), padding.PKCS1v15(), hashes.SHA256())
sign_base64 = base64.b64encode(signature).decode()
headers = {
"Authorization": f'WECHATPAY2-SHA256-RSA2048 mchid="{mchid}",nonce_str="{nonce_str}",signature="{sign_base64}",timestamp="{timestamp}",serial_no="你的证书序列号"',
"Content-Type": "application/json",
"Accept": "application/json"
}
# 发起请求...
5.2 支付回调验签的注意事项
支付回调是微信服务器往你的 notify_url 发 POST,携带密文,需要用 APIv3 密钥解密。很多人在这里做漏了:
解密前必须先做签名验证
。正确顺序是:收到通知 -> 从请求头取 Wechatpay-Signature、Wechatpay-Timestamp、Wechatpay-Nonce -> 用微信支付平台证书验签 -> 验签通过后用 APIv3 密钥做 AES-256-GCM 解密 -> 得到明文订单数据 -> 更新订单状态 -> 返回
{"code":"SUCCESS", "message":"成功"}
。
注意:一定要对 notify_url 收到的通知做幂等处理,同一次支付回调可能因为网络重试收到多次。我一般用订单号在数据库里做唯一索引,重复插入会报错,直接返回成功,避免重复发货。
5.3 小程序违规与支付功能被禁用
在热搜词里出现了“小程序违规,支付功能暂时无法使用”这句话,做支付类应用十有八九会遇到。出现这种情况最常见的原因有:
- iOS 虚拟支付违规 :微信对 iOS 端虚拟支付管得很严。如果你的视频点播是纯虚拟内容,在 iOS 上不可直接用微信支付购买,否则会被封禁支付能力。合规的做法是 iOS 端只做非支付性的操作,比如引导用户去 H5 或 App 充值,或者使用积分兑换。
- 类目资质不全 :视频类小程序需要选择“社交-直播”或“视频-视频播放”等类目,并且需要提供对应的许可证(比如网络文化经营许可证)。
- 被投诉诱导分享或内容违规 :内容安全没有审核到位,被用户投诉。
解决办法是去微信公众平台后台查看站内信,按提示申诉和整改。如果是 iOS 虚拟支付问题,参照官方规则调整自己的玩法,不能硬刚。支付能力恢复后记得第一时间测试商户号和 API 证书是否正常。
5.4 内容安全审核必须提前做
视频内容如果不做审核,上线被投诉甚至封号的风险极大。我接入的是微信自带的内容安全检测接口,
security.msgSecCheck
可以检测文本,
security.mediaCheckAsync
可以检测图片和音视频。
视频审核建议在上传时截帧 + 文字识别(把封面图提交图片检测),同时把视频标题和简介跑一遍文本检测。人工抽检流程也要建立,管理后台要能看每个视频的审核状态和违规命中类型。
6. 项目梳理与上线运维心得
6.1 开发环境配置的几个关键点
Python 开发环境建议用虚拟环境,不同项目之间的依赖隔离非常重要。我见过太多人把 pandas、numpy 这些重型库装到全局环境里,结果项目依赖混乱、版本冲突,排查起来极其痛苦。
开发板和线上的 Python 版本尽量保持一致,Django 版本建议选 LTS 版本,安全补丁多,社区资料也丰富。前端小程序开发时,在开发者工具里勾选“不校验合法域名”,本地调试可以免去配置域名的麻烦,但上线前一定要关闭并在后台配置合法域名。
6.2 部署上线与性能优化清单
部署流程比较常规:git 拉代码 -> 安装依赖 -> 收集静态文件 -> 迁移数据库 -> 重启 uWSGI/Gunicorn -> 重载 Nginx。
性能优化我总结了一份自检清单,照着做基本能避免 90% 的坑:
| 检查项 | 优化动作 |
|---|---|
| 视频文件存储 | 使用对象存储,开启 CDN 加速 |
| 图片压缩 | 封面图统一转 WebP 格式,压缩到 200KB 以内 |
| 列表接口响应 | 开启 Redis 缓存,分页限制每页 10~20 条 |
| 播放域名 | 确保和业务域名分开,避免接口限流 |
| 小程序包体积 | 主包控制在 1MB 左右,视频资源全部走远程 URL |
| 数据库慢查询 | 给 video 表加索引,比如 category_id + status 联合索引 |
6.3 常见问题速查表:从播放失败到白屏
| 异常现象 | 可能原因 | 解决办法 |
|---|---|---|
| 视频黑屏无响应 | 播放地址不是 HTTPS | 检查合法域名配置、地址协议 |
| iOS 全屏错位 | swiper 嵌套 video 原生全屏 | 改用自定义全屏样式方案 |
| 安卓特定机型无法播放 | HLS 兼容性问题 | 换成 mp4 源或做多格式兼容 |
| 登录态丢失 | token 过期 | 请求拦截器统一做 401 跳转 |
| 支付回调不触发 | notify_url 没有外网可访问 | 确保回调接口可外网访问且无鉴权 |
| 支付弹窗报签名错误 | 签名串拼接错误 | 对照 v3 规范逐项检查时间和随机串 |
| 分享卡片无图 | 封面域名没在 downloadFile 白名单 | 添加缩略图域名到配置 |
像
video
组件在真机上黑屏这种问题,这类问题很常见,当时我花了好几天才定位到是域名证书的问题。开发工具里预览正常不代表真机正常,所以
遇到播放不正常,第一步永远是先抓真机日志和 Network
。
6.4 小程序页面跳转的配置细节
小程序页面跳转,如果是跳转到带参数的视频详情页,路径格式是
/pages/detail/detail?id=123
,接收参数用
onLoad(options)
里的 options.id。如果是从分享卡片打开小程序,这个参数会在
onLoad
里带上
scene
字段,需要额外做一次 decodeURIComponent 解码,这是新用户从分享链接进入时最容易忽略的点。
对于视频点播系统,还有一个跳转场景是用户想知道具体某个视频的章节列表,这种用普通页面跳转就很好,不用做强行 tab 切换。
7. 支付和会员体系的设计心得
会员体系跟支付强相关。视频点播系统的典型变现方式是:部分免费视频吸引流量,VIP 专享内容和单集点播来收费。
我设计的会员体系包含三个层面:
- 普通用户 :看免费视频,有广告(如果后续接入广告)。
- VIP 会员 :全场免费视频都可看,部分内容免除广告,提供高清码率。
- 单片点播 :非 VIP 用户可单独购买某个视频。
会员到期时间的判断核心在数据库里维护一个
expire_time
字段,前端展示 VIP 状态时,读取用户详情的 vip_expire_time 和当前时间做对比。如果小于当前时间,会员失效。
注意:小程序端时间不要依赖本地时间,部分用户手机时间不准可能提前过期。所有时间校验以后端返回的服务器时间为准,前端只做展示。
这里还涉及一个用户体验问题:如果用户是 VIP 但调接口时没有传用户身份的 token,后端会返回 401,前端要判断并引导到登录页,而不是直接弹"无法播放"。登录失败的情况也要做降级方案,保持基础浏览功能可用,不要让非登录用户完全被拒之门外。
8. 写在最后:这套系统后续还能怎么演进
这个项目从设计到上线,核心成员就我和一个后端同事。Python 极大压缩了后端开发时间,小程序原生开发在前端这块也没有卡脖子。如果重新让我选一次,还是会做同样的技术选型。
关于后续演进,我在实际使用中积累了几个方向:一是把推荐算法做厚,基于用户观看历史和标签做协同过滤,Python 生态里做这件事很顺手;二是引入内容运营后台的可视化数据大屏,方便运营调整内容策略;三是把视频审核流程和转码流程打通成自动化流水线,减少人工干预。
最后再分享一个小技巧: 开发小程序的时候,一定养成看日志和上报的习惯 。上线后如果有用户反馈视频打不开,没有日志的话你只能靠猜。我当时在请求入口和播放事件里都加了数据埋点,播放失败会自动上报错误码。这个习惯在排查疑难 bug 时帮了大忙。希望这篇记录能给正在做视频类小程序的朋友一些参考,少走几个我走过的弯路。
火山引擎视频云技术社区,是面向 AI 音视频开发者的技术交流平台。这里汇聚源自抖音、豆包等亿级 DAU 产品的 RTC、直播、点播、AI 媒体处理、音视频互动技术,提供接入指南、最佳实践、性能调优、场景案例、Demo 代码、开源项目、白皮书和 API 文档。社区汇聚官方工程师与一线开发者,为 AI 视频通话、数字人、AI 视频处理等应用的开发与落地提供技术支持。
更多推荐
所有评论(0)