ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Python+微信小程序实战:从零搭建短视频点播系统的完整路径

Python+微信小程序实战:从零搭建短视频点播系统的完整路径 做一套属于自己的短视频点播系统这事我想了很久。Python做后端微信小程序做前端一套能上传视频、能刷Feed流、能加评论点赞的小程序听起来是个标准毕业设计题但真要做扎实里面的坑比想象中多得多。从数据库表结构怎么设计到视频转码怎么不把服务器CPU打满再到小程序里那个让人头疼的顶部导航栏适配每一步都有值得记录的细节。这篇文章就是把我从零搭建这套系统的完整过程整理出来。不是纯理论分析而是我实际写代码、跑流程、上线部署后沉淀下来的一套可以直接参考的路径。适合正在做相关课题的学生也适合工作后想快速验证一个产品想法的开发者。1. 先想清楚这套系统到底解决什么问题很多人在动手前容易陷入一个误区上来就分前端后端先把登录注册写了再去搞视频上传结果做到一半发现页面跳来跳去接口互相不匹配整个项目变成一团乱麻。做这类系统最忌讳的就是没想清楚就开写。1.1 用户真正需要的是什么短视频系统的用户行为链路非常清晰打开小程序看到视频列表点进去播放然后点赞、评论、关注。还有一个核心行为就是发布自己的视频。但这里面有个容易被忽视的点用户上传视频和观看视频是不同的场景。上传发生在创作者端观看发生在消费端两者对系统的要求截然不同。上传要考虑稳定性、大文件处理、转码能力观看要考虑加载速度、播放流畅度、流量成本。我的做法是先把这两条链路分开设计。观看链路走公开的Feed流接口加CDN加速上传链路走独立的上传接口加分片机制。这样后端逻辑清晰出了问题也容易定位。1.2 为什么选择Python和微信小程序这个组合选Python做后端最重要原因是生态成熟。视频处理有MoviePy和FFmpeg的Python绑定Web框架有Flask和Django两个主流选择数据库操作有SQLAlchemy缓存有Redis客户端几乎每个环节都有现成的库可以用。微信小程序则是目前触达用户成本最低的载体。不需要下载App扫码就能用而且微信生态内的分享传播机制天然适合短视频这种内容形态。小程序端的API对视频播放有专门的VideoContext控制对上传有wx.uploadFile和wx.chooseMedia这些都让开发省了很多事。当然也要说清楚Python在后端高并发场景下不占优势。但对一个中小规模的视频点播系统来说Python完全够用只要架构设计得当支撑上万日活没有压力。等真的需要撑起百万级流量的时候再考虑用Go或Java重写核心服务也不迟前期没必要为了不存在的性能瓶颈过度设计。2. Python后端骨架从技术选型到数据库设计后端是整个系统的大本营视频数据、用户数据、评论数据都要在这里汇聚和分发。这一节我把自己最终采用的方案和理由详细拆解一下涉及具体的选择逻辑而不是简单给个清单。2.1 技术栈选型与项目结构我最终选择Flask框架。Django固然自带了Admin后台和ORM功能完整但对视频系统来说显得笨重而且Django的ORM在处理复杂查询时并不比SQLAlchemy灵活。Flask轻量、灵活配合蓝图Blueprint做模块化非常契合这个项目的规模。项目结构我采用了类似下面这种划分videoproject/ ├── app.py # 应用入口 ├── config.py # 配置管理 ├── models/ # 数据模型 │ ├── user.py │ ├── video.py │ ├── comment.py │ └── like.py ├── api/ # 蓝图路由 │ ├── auth.py # 登录鉴权 │ ├── video.py # 视频接口 │ ├── comment.py # 评论接口 │ └── upload.py # 上传接口 ├── services/ # 业务逻辑 │ ├── oss_service.py # 对象存储 │ ├── transcode_service.py # 转码服务 │ └── feed_service.py # Feed流 ├── utils/ # 工具函数 │ ├── response.py # 统一响应格式 │ └── token.py # JWT工具 └── requirements.txt这种按业务模块划分的方式比单纯按函数堆砌要好维护得多。每个蓝图负责一组相关的API每个服务类负责一项独立的能力逻辑边界清晰。后期加功能时只需要新增蓝图文件不需要动已有的代码。数据库我采用MySQL 8.0存储引擎InnoDB字符集utf8mb4。Redis则用来做会话缓存和Feed流的热数据缓存。2.2 数据库表结构的设计思路表结构设计决定了后续接口怎么写得顺手。这里花的时间值得后面能少走很多弯路。用户表是最基础的我用微信小程序的openid作为唯一标识。注意这里有个关键点不要直接用openid做主键而是用一个自增的id做业务主键openid加唯一索引。原因在于后续可能需要支持多种登录方式而且openid在跨平台时可能会变比如从小程序迁移到公众号用独立主键更灵活。视频表是核心表我这样设计CREATE TABLE video ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, title VARCHAR(100) NOT NULL, description TEXT, video_url VARCHAR(500) NOT NULL, cover_url VARCHAR(500), duration INT DEFAULT 0, width INT DEFAULT 0, height INT DEFAULT 0, status TINYINT DEFAULT 0, like_count INT DEFAULT 0, comment_count INT DEFAULT 0, play_count INT DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_user_id (user_id), INDEX idx_status_create (status, create_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里的status字段很关键我用了几个值来标记视频的状态。0表示转码中1表示转码完成可播放2表示审核不通过。为什么要保留这个字段而不直接把视频URL存上去就完事因为用户上传的视频源文件往往体积大、编码格式不统一直接播放会非常卡。一定要先转码成适合网络播放的H.264格式这个过程中视频还不能对外展示所以需要状态位来控制可见性。评论表和点赞表的设计相对简单但要注意点赞表需要加唯一约束防止重复点赞。评论表则要支持分页查询所以索引设计要合理按视频ID和时间排序建立联合索引。CREATE TABLE comment ( id BIGINT PRIMARY KEY AUTO_INCREMENT, video_id BIGINT NOT NULL, user_id BIGINT NOT NULL, content VARCHAR(500) NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_video_time (video_id, create_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;2.3 统一响应格式与全局异常处理小程序端的网络请求解析能力有限如果每个接口返回的数据格式不一样前端要写一堆兼容逻辑。我定义了统一的响应格式这是写正式项目时容易被忽略但极其有用的习惯def success(dataNone, messageok): return { code: 0, message: message, data: data } def error(code, message): return { code: code, message: message, data: None }配合Flask的app.errorhandler做全局异常捕获任何未预期的异常都会返回格式化的错误响应而不是一堆HTML错误页。前端只需判断code是否为0省去大量兼容代码。3. 微信小程序前端页面搭建与核心交互小程序端我选择了原生开发而不是uni-app。这个决定基于一个考量视频类小程序对性能要求较高原生小程序的性能和调试体验更好而且我只需要支持微信一个平台不需要跨端的红利。如果未来要上支付宝小程序再迁移也不迟小程序基本语法是相通的迁移成本可控。3.1 首页Feed流设计首页是整个产品的门面。短视频产品的Feed流通常采用上下滑动切换视频的方式这在小程序里可以用swiper组件来实现。swiper classvideo-swiper verticaltrue bindchangeonSwiperChange current{{currentIndex}} swiper-item wx:for{{videoList}} wx:keyid video idvideo-{{item.id}} src{{item.videoUrl}} poster{{item.coverUrl}} autoplay{{index currentIndex}} object-fitcontain bindplayonVideoPlay bindpauseonVideoPause /video /swiper-item /swiper这里面有个非常关键的性能细节不要给每个swiper-item里的video都设置autoplay否则页面初始化时会同时加载多个视频导致缓冲和卡顿。正确做法是只让当前项自动播放其余全部不自动播放。我通过index currentIndex这个判断来控制。swiper的bindchange事件会在滑动后触发这时要维护一个当前索引的变量并且对上一个不再可见的视频调用pause()暂停播放对当前新的视频调用play()。逻辑不复杂但直接影响用户体验。3.2 如何解决顶层导航栏高度适配问题热搜词里我看到“微信小程序顶部导航栏高度”这个话题这说明很多人踩过这个坑。自定义导航栏是实现沉浸式体验的常用手段但不同机型的胶囊按钮位置和导航栏高度完全不同写死高度就是灾难。解决方案是动态获取const systemInfo wx.getSystemInfoSync(); const menuButtonInfo wx.getMenuButtonBoundingClientRect(); // 胶囊按钮到屏幕顶部的距离包含状态栏 const capsuleTop menuButtonInfo.top; // 胶囊按钮到屏幕右边的距离 const capsuleRight menuButtonInfo.right; // 导航栏高度 胶囊顶部位置 - 状态栏高度 胶囊高度 胶囊底部间隔 const navBarHeight (capsuleTop - systemInfo.statusBarHeight) * 2 menuButtonInfo.height;这里的关键推导是胶囊按钮是垂直居中对齐的所以导航栏总高度等于胶囊上方间距乘以2加胶囊高度。有了这两个值就能在wx.setNavigationBarColor失效时自己绘制定位的导航栏。封装成一个工具函数后全项目所有自定义导航栏页面都可以复用。提示wx.getMenuButtonBoundingClientRect()在基础库2.1.0以上可用如果兼容老版本可以在App.onLaunch时获取并存入globalData避免每个页面重复调用。3.3 播放页面的横竖屏切换与锁屏播放页是用户停留时间最长的页面交互体验一定要做细。我实现了全屏播放并且在进入全屏后自动横屏退出全屏后恢复竖屏。// 进入全屏 const videoContext wx.createVideoContext(myVideo); videoContext.requestFullScreen({ direction: 90 }); // 监听全屏切换 this.videoContext.onFullScreenChange((res) { if (res.direction 90 || res.direction -90) { wx.setPageOrientation(landscape); } else { wx.setPageOrientation(portrait); } });还有一个细节很多开发者会忽略视频播放页在页面栈里不能压太多层否则小程序内存占用会持续升高。我的方案是做一个专门的播放页从Feed流跳转时只跳转到这个页面并传入视频ID播放页内通过ID请求详情数据。这样即使刷了几十个视频页面栈里始终只有两层。4. 核心链路视频上传、转码与分发这一部分是整套系统技术含量最高的地方也是最容易出现问题的环节。用户在小程序端选择视频上传到服务器服务器做转码处理再发布到Feed流。任何一步出问题用户看到的就是“上传失败”或者“视频打不开”。4.1 小程序端视频选材与本地压缩真实用户手机上录制的视频动辄几十上百MB4K分辨率也不少。直接上传不现实既慢又消耗流量。所以小程序端要先做压缩处理。wx.chooseMedia提供了compressed参数设置为true时会对视频进行一定程度的压缩。实测下来一个1080P的1分钟视频压缩后大概在5-10MB左右画质还能接受。但要注意这个参数不是万能的在某些低端机型上压缩效果不理想还需要在服务端做二次转码兜底。选择逻辑也很关键。我设置了时长限制和大小限制。超过5分钟的视频直接提示用户裁剪后上传超过100MB的视频警告用户将消耗较多流量。4.2 服务端分片上传处理大文件上传不能使用wx.uploadFile一把梭原因在于小程序端上传时如果网络波动整个请求就断了就要从头再来。分片上传是最好的解决方式。小程序端将本地视频文件切成小块通过wx.uploadFile依次上传每个分片每个分片带有文件标识和分片序号服务端收到后暂存在临时目录全部上传完成后触发合并请求将临时文件拼接成完整文件。app.route(/api/upload/merge, methods[POST]) def merge_video(): file_id request.json.get(fileId) total_parts request.json.get(totalParts) with open(final_path, wb) as output: for i in range(total_parts): part_path os.path.join(tmp_dir, f{file_id}_{i}.part) with open(part_path, rb) as part_file: output.write(part_file.read()) os.remove(part_path) # 触发转码任务 transcode_service.submit(file_id, final_path) return success({fileId: file_id})这里有个小经验分片大小不要设太大也不要太小。我实测的最佳范围是1-2MB既不会因为请求过多导致网络延迟累积也不会因为单片过大导致失败重传成本高。分片数建议限制在100个以内超过的话就让用户重新选择较小的视频。4.3 转码服务与FFmpeg调用视频上传后不能立即发布。手机录制的视频编码五花八门有的是H.265有的是VP9甚至有的是MPEG-4。这些编码在小程序端不一定能播或者播放起来不流畅。需要统一转码成H.264 AAC的MP4格式。我用的是FFmpeg命令行调用配合Python的subprocess模块def transcode_video(input_path, output_path): command [ ffmpeg, -i, input_path, -c:v, libx264, -preset, fast, -crf, 23, -c:a, aac, -b:a, 128k, -movflags, faststart, -vf, scale1280:-2, output_path ] subprocess.run(command, checkTrue)几个参数值得解释一下。-crf 23是画质和体积的平衡点数值越小画质越好但文件越大实测23对大多数视频来说视觉上无损文件大小也合理。-preset fast压缩速度快虽然体积比slow稍大一点但能显著提升转码效率。-movflags faststart非常关键这个参数把MP4的索引信息移到文件开头用户播放时不需要下载完成就能开始播放对视频点播的体验提升巨大。-vf scale1280:-2把视频等比例缩放到宽度1280。为什么要缩放因为大多数手机录制的视频分辨率在1920以上但手机上播放时肉眼很难分辨1080P和720P的差别。缩放到720P级别可以减少近一半的带宽消耗加载速度更快播放更流畅。转码是CPU密集型任务不能放在请求线程里同步执行否则用户会等很久。我的方案是用Redis列表作为任务队列后台有一个Python脚本常驻监听队列取出任务后交FFmpeg处理。这样上传接口秒回转码异步完成通过视频状态字段同步给前端。4.4 视频封面截取与存储策略封面图的重要性容易被低估。没有封面的视频在Feed流里是黑色方块点击率会断崖式下降。封面在转码时用FFmpeg截取中间帧生成ffmpeg -i input.mp4 -ss 00:00:02 -vframes 1 -vf scale640:-2 cover.jpg-ss 00:00:02表示在第2秒位置截取。截图后封面图和视频本体需要存储到对象存储或本地磁盘的媒体目录。如果购买阿里云或腾讯云服务器可以搭配对象存储OSS服务视频文件和图片放云端服务器只承担计算任务。带宽压力小很多也方便后续扩容。5. 商业化环节支付对接和其他避坑实录影视点播类小程序天然有商业化需求会员付费、单视频购买、打赏都是常见的变现手段。微信支付v3的对接是整个项目中另一个容易出问题的环节热搜词里“微信支付v3对接 由于小程序违规支付功能暂时无法使用”的搜索量很高说明这是个普遍痛点。5.1 微信支付v3对接的核心流程微信支付v3和v2相比API签名方式从MD5签名变成了RSA非对称签名对安全性要求更高但对接链路反而更清晰。核心步骤是构建请求参数包含商户号、AppID、描述、金额、回调地址等用商户私钥对参数签名请求微信支付统一下单接口获取prepay_id小程序端用prepay_id调起支付Python对接时推荐使用wechatpayv3这个库。它封装了大部分细节但签名私钥和处理证书的逻辑仍然需要理解否则出了问题无从排查。小程序端调起支付的代码非常简洁wx.requestPayment({ timeStamp: res.data.timeStamp, nonceStr: res.data.nonceStr, package: res.data.package, signType: RSA, paySign: res.data.paySign, success: (res) { /* 支付成功 */ }, fail: (err) { /* 支付失败 */ } });5.2 “小程序违规支付功能暂时无法使用”的成因与解决路径搜索量极高的这个关键词实际指向的问题往往不是代码层面的而是账号类目和资质审核。小程序开通微信支付需要经过平台审核且需要在小程序管理后台关联同主体的商户号。如果小程序选择的服务类目与支付场景不符或者没有提供对应资质文件比如营业执照经营范围不符就会导致支付功能被限制。还有一种常见情况是虚拟支付违规。虚拟内容比如视频观看券、VIP会员在小程序端是不能直接用微信支付收款的平台明确要求虚拟支付必须走Android/iOS的虚拟支付通道或使用小程序自身的虚拟支付组件。我之前就见过一个影视类小程序因为直接在小程序里售卖“观看券”被判定为虚拟支付违规支付功能被冻结了很久。解决办法是调整经营策略。影视点播类小程序通常的做法是小程序内只提供免费内容和观看广告解锁会员付费迁移到公众号H5页面或App端完成。当然这在用户体验上会有割裂感是很多内容类小程序的常态化妥协。5.3 小程序跳转链接失败的排查经验热搜词里还有“微信小程序跳转链接weixin://dl/business从生成到触发的全流程避坑”这个weixin://dl/business链接用于跳转到指定的微信业务页面比如跳转到某个小程序、某个卡包页面等。但它触发条件比较苛刻失败时通常没有明显报错排查起来很费劲。我踩过的一个坑是开发版和体验版能正常跳转但线上正式版跳转失败。原因是没有在小程序管理后台配置业务域名。微信对跳转链接有严格的安全校验weixin://dl/business链接需要在后台添加绑定否则线上环境直接拦截。另外这类链接还分URL Scheme和URL Link两种小程序内跳转需要用URL Link外部短信或邮件跳转需要用URL Scheme两者参数不同不要混用。还有一个细节weixin://dl/business携带的path参数必须是编码后的字符串不能带中文和特殊字符否则解析失败。排查时可以先在开发者工具中打开“不校验合法域名”开关试通之后再把坑逐个填上。6. 系统上线部署与性能优化开发完成只是第一步真正上线后才会遇到各种环境问题。这一节我把上线部署中的关键步骤和调优经验记录下来。6.1 Linux服务器环境搭建我用的是一台4核8G的云服务器。系统装的是Ubuntu 22.04 LTS这个版本对Python 3.10支持良好各种系统库也很全。安装Python、MySQL、Redis和Nginx这套常规操作就不展开了这里重点说几个容易忽略的系统依赖。FFmpeg不能只用apt install ffmpeg装基础版一定要装全插件版本否则转码时会遇到各种编码不支持的错误。我使用的是ffmpeg官方静态编译版解压到/usr/local/bin即可功能完整而且不用担心库冲突。Nginx的配置比较关键。小程序的video组件播放视频时如果服务器没有正确配置Content-Type视频会无法播放或无法拖动进度条。需要在Nginx配置中加上location ~* \.(mp4|flv|m3u8)$ { add_header Content-Type video/mp4; add_header Accept-Ranges bytes; }Accept-Ranges bytes这个响应头尤其重要没有它小程序端视频组件就无法拖动进度条因为原生播放器不支持流式播放带范围请求的视频。6.2 HTTPS证书的配置小程序正式版要求所有请求域名必须是HTTPS且证书必须是有效的不能用自签证书。服务器上我用Certbot申请Lets Encrypt免费证书配置Nginx启用SSL。需要注意的一点是证书有效期只有90天一定要设置定时续期任务否则证书过期后小程序所有请求都会失败用户看到的就是一片空白。6.3 小程序后台的合法域名配置不要忘记在小程序管理后台要把服务器的域名配置成request、uploadFile、downloadFile的合法域名。这个配置大概是开发者最常见的“能编译但请求不了数据”的原因。域名配置后不是立即生效需要等几分钟到十几分钟并且后续版本更新时会提示确认这些域名是否继续使用需要手动勾选确认。6.4 被忽略的数据库连接池配置上线后第一个星期我就遇到了MySQL连接数被打满的问题。原因是Python的PyMySQL每次请求都新建连接高并发下直接耗尽数据库连接。解决办法是使用DBUtils连接池from dbutils.pooled_db import PooledDB pool PooledDB( creatorpymysql, maxconnections20, mincached5, maxcached10, maxusageNone, blockingTrue, hostlocalhost, userroot, passwordxxx, databasevideo_db, charsetutf8mb4 )配置这个连接池后数据库连接复用率提升明显再也没出现过连接被占满的情况。另外所有查询尽量走索引避免全表扫描。我遇到过一个很典型的问题视频列表页加载耗时从500ms飙升到2秒排查后发现是order by create_time desc没有走索引加了一个联合索引后回归到了200ms以内。6.5 视频CDN加速的实际效果视频分发是带宽消耗的大头。如果视频文件全部走服务器出口带宽传统的按固定带宽计费模式费用感人。我测试过几种方案最终选择把视频文件上传到对象存储如阿里云OSS或腾讯云COS然后开启CDN加速域名。服务器只负责API请求和动态数据处理视频播放流量全走CDN。这样一来视频加载速度从原来的平均2-3秒提升到500ms以内服务器带宽压力大幅缓解4核8G的配置支撑几千日活毫无压力流量费用反而比单纯买高带宽更便宜这里提醒一句CDN域名和API域名要分开不要共用一个域名。因为CDN主要为GET请求设计而API有POST、PUT等复杂请求两者在Nginx配置和CDN缓存策略上的要求不一样混用会导致配置文件既繁琐又容易出现逻辑错误。7. 复盘总结这些经验值得记住整套系统从需求分析到完成开发上线我前后用了一个多月的时间。过程中踩过的坑、走过的弯路不少。如果时间倒流我会把下面的经验告诉当时的自己关于架构设计第一版就要规划好表结构尤其是状态字段和索引。前期花一小时设计后期能省一天的返工。关于视频处理转码是必然要做的事情不要跳过。以为手机拍了MP4就能直接播是个常见误区编码格式不统一带来的兼容问题会让你夜不能寐。提前准备好FFmpeg流水线后面的一切都顺畅。关于小程序适配顶部导航栏高度和底部安全区适配是小程序开发的高频坑一定不要写死样式代码。封装成工具函数统一处理不仅当前项目复用后续新项目也能直接借鉴。关于后端性能数据库连接池一定要配异步任务队列一定要上统一的异常处理一定要有。这三个看起来不起眼的细节决定了系统上线后的稳定性和定位问题的效率。关于微信生态微信支付、跳转链接这些能力代码本身不复杂复杂的永远是人家的规则。开发前后一定要确认好类目资质提前阅读最新政策避免上线后被封功能来回折腾。这套系统后续的方向我也在规划。基于用户观看历史和点赞记录做推荐算法把Feed流变得更智能接入IM做评论互动升级让用户能实时交流感受给创作者开发内容管理后台支持数据分析和收益查看。这条产品演进的路还有很长。如果你正在做类似的系统或者正准备用这套技术栈做个视频类的个人项目希望这些实战记录能帮你少踩一些坑。有问题欢迎评论区交流我看到都会回复。
返回列表