简介:Sora2小程序源码是一套覆盖前端与后端完整链路的小程序项目,核心功能是通过一张图片生成视频,适合对AI视频应用、微信小程序全栈开发感兴趣的初中级开发者学习与复用。压缩包共收录205个文件,其中101个PHP文件承担后端业务逻辑与接口输出,前端由WXML、WXSS、JS文件协同实现页面展示与交互,同时包含JSON配置、SQL数据库脚本和JPG/JPEG图片素材,整体仅2.58MB,结构轻巧明确。已有266人学习下载。压缩包内还附有部署教程文档,并对.htaccess、Git忽略规则等服务端配置做了整理,便于快速部署;代码目录按前后端职责划分,接口设计与素材管理清晰,可帮助读者理解“图片生成视频”类小程序的整体实现思路,并直接在此基础上进行功能扩展或二次开发。项目既适合作为课程设计参考,也可用于快速搭建AI视频生成类小程序原型。
1. Sora2小程序源码拆解:从一张图片到生成视频的完整链路
做小程序这行最常被问的一句话是:“你这个视频生成是怎么接的?”Sora2 这套源码给了一个很直接的回答:一张静图进去,几秒后出来一段可播放视频,前端是微信小程序,后端单独跑一套服务。它没有把逻辑全塞在客户端,而是用标准的前后端分离方式,把图片上传、任务下发、生成结果回传拆成了几个独立模块。对于想快速落地“图片生视频”场景的团队,这套代码最值得读的不是页面长什么样,而是它如何处理生成任务的异步状态——因为视频生成不可能像普通接口一样请求后立刻返回结果。
这套源码适合两类人:一类是已经在做小程序商城、内容社区,想给用户塞一个“AI 生成视频”玩法的产品经理和技术负责人;另一类是刚接触前后端分离项目实战的前端开发者,想找个真实案例看清微信小程序怎么跟后端服务做数据交换。下面的拆解围绕部署文档和源码目录中的几个核心文件展开,重点放在后端任务调度、前端上传与状态轮询、以及部署时最容易踩的坑。
2. 前后端分离架构:小程序端、后端服务与存储选型
2.1 总体结构:为什么必须把生成任务放到后端
Sora2 小程序源码的目录里同时存在.gitignore、.htaccess和后端目录,说明它本身就是一个完整的前后端项目。前端是微信小程序(或兼容 uniapp 微信小程序),后端通过 HTTP 接口对外提供能力。视频生成这种操作强依赖外部模型服务,如果把请求直接从小程序发到模型服务,会面临两个问题:一是小程序没法安全保存密钥,二是生成过程耗时较长,微信小程序默认请求超时时间很短,等不到结果就会中断。
因此这套源码的做法是把“生成视频”拆成三个环节:小程序上传图片到后端 → 后端把图片交给模型服务并记录任务 ID → 小程序轮询任务状态,直到拿到视频地址。这样小程序端只需要处理普通的上传和查询请求,耗时的生成过程完全由后端管理。这个设计也是大多数前后端分离项目实战里处理长耗时任务的标准模型,值得单独拉出来看。
2.2 后端技术栈与目录职责
从部署文档提示来看,后端不是单体 PHP 项目,而是带有.htaccess的 Web 服务,说明它可以跑在 Apache 或 Nginx + PHP 环境下。不过按现代小程序的对接习惯,我一般会把后端重写成 Python FastAPI 或 Node.js,因为这两者对异步任务支持更好。源码如果只是给你一个 PHP 入口,你仍然可以用同样的接口语义换成任意后端语言,前端不用改。
整个后端目录通常包含这几个核心部分:
| 目录/文件 | 职责 |
|---|---|
/api/upload | 接收小程序上传的图片,校验格式后存入本地或 OSS |
/api/create_task | 把图片提交给 Sora2 模型服务,生成一个任务 ID |
/api/task_status | 根据任务 ID 查询生成进度和结果 |
/api/notify | 接收模型服务的回调,更新任务状态 |
/static或/storage | 存放生成的临时视频或图片 |
注意.gitkeep文件的存在意味着某些目录原本是空的,部署后需要手动创建存储目录或写入配置。
2.3 存储与队列选型
图片和生成的视频不能全放在本地磁盘,尤其在小程序场景下,用户设备访问服务器文件需要公网地址。常见做法是用阿里云 OSS 或腾讯云 COS 存储文件,把返回的 URL 存到任务记录里。如果只是内部测试,也可以放在后端公开目录,但小程序生产环境必须用 HTTPS 域名,这点后面部署章节会细讲。
任务队列方面,如果生成并发量不大,直接用数据库表存任务状态加轮询就够用。源码可能没有引入 Redis,如果你的用户量上来,再用 Redis 做任务缓存,把task_status改成先查 Redis 再查数据库,能有效降低数据库压力。这里不展开,先按最简模型理解。
3. 后端视频生成接口实现:参数构造、任务轮询与回调通知
3.1 接口协议与请求参数
后端对外暴露的接口语义需要和小程序约定清楚。Sora2 生成的流程是“上传图片 → 发起生成 → 查询状态”,每次调用都离不开这几个参数:图片地址(或图片二进制)、生成时长、镜头运动方式、回调地址。以下是我在设计这类接口时固定的字段,你可以对照源码调整命名。
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
image_url | string | 是 | 上传后返回的图片访问链接 |
duration | int | 否 | 生成视频秒数,常见取 5、10、15 |
motion | string | 否 | 运镜方式,如zoom_in、pan_left、static |
callback_url | string | 否 | 生成完成后后端回调地址,用于主动通知 |
request_id | string | 是 | 调用方生成的唯一 ID,用于幂等去重 |
3.2 用 FastAPI 实现任务下发
如果原本源码是 PHP,我习惯重构成下面的 Python 版本,逻辑更清晰。这段代码假设你已经有图片上传接口,并且拿到图片 URL 后调用外部 Sora2 模型服务。
# api/task.py import uuid import httpx from fastapi import FastAPI, BackgroundTasks, HTTPException from pydantic import BaseModel app = FastAPI() class TaskCreate(BaseModel): image_url: str duration: int = 5 motion: str = "static" callback_url: str = "" request_id: str = "" @app.post("/api/create_task") async def create_task(params: TaskCreate): # 生成任务唯一 ID,客户端会拿着它去轮询 task_id = params.request_id or uuid.uuid4().hex # 调用 Sora2 模型服务,这里用异步请求避免长时间阻塞 async with httpx.AsyncClient() as client: resp = await client.post( "https://api.sora2.example.com/generate", json={ "image_url": params.image_url, "duration": params.duration, "motion": params.motion, "webhook": "https://your-domain.com/api/notify" }, timeout=30 ) if resp.status_code != 200: raise HTTPException(status_code=502, detail="模型服务错误") # 把任务状态写入数据库,初始为 pending save_task(task_id, status="pending", image_url=params.image_url) return {"task_id": task_id, "status": "pending"}这段代码的核心是:用task_id文件前端轮询的凭据;用异步 HTTP 请求避免线程阻塞;webhook指向后端回调接口,这样生成完成后不需要前端一直等。注意timeout=30只限制请求发起阶段,真正的生成耗时是通过回调或轮询感知的。
3.3 任务状态查询接口
小程序每隔 2~3 秒访问一次/api/task_status,后端查数据库返回状态。这里我会把状态设计成四态:pending、processing、completed、failed。前三态对应前端不同的 UI 展示。
# api/status.py from fastapi import Query @app.get("/api/task_status") async def get_task_status(task_id: str = Query(..., description="任务ID")): task = find_task_by_id(task_id) if not task: raise HTTPException(status_code=404, detail="任务不存在") # 如果已完成,返回视频地址;否则只返回状态 response = { "status": task["status"], "task_id": task_id } if task["status"] == "completed": response["video_url"] = task["video_url"] if task["status"] == "failed": response["error_msg"] = task["error_msg"] return response每次轮询都查一次完整数据库记录可能比较浪费,所以我会在task_status表里只放轻量字段,视频 URL 和错误信息单独存。当状态变为completed时,再把video_url拼进返回体。小程序端拿到video_url后,就能用<video>组件直接播放。
3.4 回调通知:让服务端主动推送结果
轮询是兜底方案,更高效的是回调。Sora2 模型服务在生成完成后会向预设的webhook地址发请求,你需要一个公开可访问的接口来接收通知。这个接口必须做好签名校验,否则任何人都能伪造请求把任务置为完成。
# api/notify.py from fastapi import Request @app.post("/api/notify") async def receive_notify(request: Request): body = await request.json() # 校验签名,一般模型服务会给一个 token 字段 if body.get("token") != YOUR_SECRET_TOKEN: raise HTTPException(status_code=403, detail="非法来源") task_id = body["task_id"] status = body["status"] if status == "success": update_task_status( task_id, status="completed", video_url=body["video_url"] ) else: update_task_status(task_id, status="failed", error_msg=body.get("error")) return {"code": 0}注意不要把YOUR_SECRET_TOKEN硬编码在代码里,放到环境变量或配置文件中。回调接口返回给模型服务的响应速度要快,所以内部只做更新数据库的操作,不要在这里再生成缩略图或处理视频。如果回调失败,小程序轮询仍然能正常工作,只是会多等一段时间。
4. 前端小程序适配:图片上传、进度展示与结果缓存
4.1 微信小程序端的上传逻辑
小程序前端源码大概率是用微信原生语法写的,也可能兼容 uniapp。无论哪种,核心都是先选图片,再调用wx.uploadFile上传,拿到后端返回的image_url,然后调用wx.request发起创建任务。下面这段代码以微信原生为例:
// pages/index/index.js Page({ data: { imageSrc: '', uploading: false, taskId: '', videoUrl: '', pollCount: 0 }, onChooseImage() { wx.chooseMedia({ count: 1, mediaType: ['image'], sourceType: ['album', 'camera'], success: (res) => { this.setData({ imageSrc: res.tempFiles[0].tempFilePath }) } }) }, uploadAndGenerate() { if (!this.data.imageSrc) { wx.showToast({ title: '请先选择图片', icon: 'none' }) return } this.setData({ uploading: true }) wx.uploadFile({ url: 'https://your-domain.com/api/upload', filePath: this.data.imageSrc, name: 'file', success: (uploadRes) => { const { image_url } = JSON.parse(uploadRes.data) this.createTask(image_url) }, fail: () => { this.setData({ uploading: false }) wx.showToast({ title: '上传失败', icon: 'none' }) } }) }, createTask(imageUrl) { wx.request({ url: 'https://your-domain.com/api/create_task', method: 'POST', data: { image_url: imageUrl, duration: 5, motion: 'zoom_in' }, success: (res) => { if (res.data.status === 'pending') { this.setData({ taskId: res.data.task_id }) this.startPolling() } } }) }, startPolling() { const poll = setInterval(() => { wx.request({ url: `https://your-domain.com/api/task_status?task_id=${this.data.taskId}`, success: (res) => { if (res.data.status === 'completed') { clearInterval(poll) this.setData({ videoUrl: res.data.video_url, uploading: false }) } else if (res.data.status === 'failed') { clearInterval(poll) this.setData({ uploading: false }) wx.showToast({ title: res.data.error_msg || '生成失败', icon: 'none' }) } } }) }, 2000) } })4.2 轮询间隔与体验优化
轮询间隔设在 2 秒比较合理,太快会打爆后端接口,太慢用户会认为卡死。上面代码里pollCount字段其实可以用来做超时控制,比如超过 30 次仍没有结果就停止轮询并提示用户稍后在“我的生成”里查看。实际产品中,我会把轮询逻辑抽到一个单独的 util 里,避免页面跳转后定时器还在跑导致内存泄漏。
注意wx.uploadFile的name字段必须和后端接口读取文件的字段名一致,否则上传会失败。如果后端是 PHP 的$_FILES['file'],前端就必须传name: 'file'。安全意识强一点的话,上传前要在小程序端校验图片类型和大小,chooseMedia后读tempFiles[0].size,超过 10MB 的图直接拒绝。
4.3 生成结果的本地缓存
视频生成完成后,如果用户重复进入页面,不应该每次都重新触发生成。我会把videoUrl和taskId存到小程序的wx.setStorageSync中,键名用图片的本地路径做哈希。这样用户在同一台设备上第二次进入,直接从缓存读取视频地址。
function cacheResult(imageLocalPath, videoUrl, taskId) { const key = `sora2_${imageLocalPath.slice(-20)}` try { wx.setStorageSync(key, { videoUrl, taskId, createTime: Date.now() }) } catch (e) { console.error('缓存写入失败', e) } }这里的缓存逻辑有一个坑:小程序本地存储有 10MB 上限,如果用户生成大量视频,缓存很快会满。所以我通常只缓存最近 20 条记录,写入前先清掉过期数据,用一个数组控制长度。另外缓存的是视频 URL 而不是视频本身,避免超出存储限制。
5. 部署上线与性能优化:Nginx配置、HTTPS与并发控制
5.1 部署环境准备与.htaccess注意事项
源码里的.htaccess文件如果是给 Apache 用的,迁移到 Nginx 后需要做对应改写。.htaccess常见的重写规则是拦截所有请求到index.php,在 Nginx 里对应为:
server { listen 443 ssl http2; server_name your-domain.com; ssl_certificate /etc/nginx/cert/your-domain.pem; ssl_certificate_key /etc/nginx/cert/your-domain.key; root /var/www/sora2/public; index index.php; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_pass unix:/run/php/php8.2-fpm.sock; } # 限制上传大小,图片一般不超过 10M client_max_body_size 10m; }如果你保留 PHP 原版后端,把端口443和证书路径换成自己的即可。如果你重写了后端为 Python FastAPI,则需要在location /中加反向代理:
location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_read_timeout 60s; }proxy_read_timeout尤其重要,因为小程序轮询接口响应很快,但如果你放开了视频直接通过后端流式下载,这个超时时间太短会导致视频下载一半被 Nginx 掐断。我一般单独给/video路径设置proxy_read_timeout 300s。
5.2 HTTPS 与小程序白名单配置
微信小程序正式环境要求所有请求域名必须为 HTTPS 且已在后台配置合法域名。在微信公众平台的小程序管理后台,你需要把https://your-domain.com加到request和uploadFile合法域名中。这里有个容易忽略的地方:如果调用的是 IP 地址,开发工具能跑,真机预览会被拦截,所以务必使用备案域名。
另外,后端如果调用了 Sora2 模型服务,而模型服务返回的video_url用的是http://,小程序端<video>组件无法播放。需要在回调接口里做一层转发或强制替换为https://。最常见的做法是后端下载视频到自己的 OSS,再返回 OSS 的 HTTPS 链接,这样也能避免外部连接不稳定。
5.3 并发处理与生成任务限流
图片生成视频消耗 GPU 资源,如果不做限流,后端可能被用户刷爆。源码未必自带限流中间件,我会在创建任务接口前加一个简单的令牌桶拦板,用 Redis 记录同一个用户的每日生成次数。
# 伪代码:用户级限流 async def rate_limit(request: Request): user_ip = request.client.host key = f"rate:{user_ip}" count = await redis.incr(key) if count == 1: await redis.expire(key, 86400) # 24小时过期 if count > 20: raise HTTPException(status_code=429, detail="今日生成次数已用完")限流阈值根据你的资源定,个人测试环境 20 次/天足够,生产环境可以放宽到 50 次/天并配合付费会员机制。这样做的目的不只是防刷,也是防止并发任务堆积导致模型服务超时。
5.4 任务生成超时与失败重试的最终兜底
在实际运行中,模型服务可能出现单次任务卡死。只靠轮询接口无法发现这种异常,我会在数据库里加一个updated_at字段,定时任务每分钟扫描一次,把超过 5 分钟仍处于processing状态的任务强制置为failed,并记录错误原因。对应的 SQL 操作像这样:
UPDATE task SET status = 'failed', error_msg = '生成超时' WHERE status = 'processing' AND updated_at < NOW() - INTERVAL 5 MINUTE;这个兜底很关键,否则用户会一直停留在“生成中”页面,体验极差。另外,失败的任务要允许用户重新发起,前端可以在收到failed状态后展示“重试”按钮,点击后重新调用createTask接口,传入相同图片但生成新的request_id。你还要在数据库里保留历史记录,方便用户查看以往生成过的视频链接,这也是源码里需要补全的一个常见功能点。
本文还有配套的精品资源,点击获取