微信小程序上传图片视频到Flask后台:可运行Demo与避坑指南 简介这份资源面向微信小程序开发者与Flask后端初学者提供图片、视频上传至服务器存储的完整前后端示例可直接运行调试适合作为课程作业、功能原型或项目练手素材。压缩包共12个文件约5KB包含5个json配置、3个js逻辑脚本、2个wxss样式、1个wxml页面结构及1个py后端文件前端小程序与后端上传接口一一对应目录按前后端分置便于快速定位与二次修改。已有421人学习下载说明该示例在同类需求中具备一定参考价值。读者可从中获得小程序端选择媒体、发起上传请求的完整流程以及Flask端接收文件并落盘存储的实现思路配合配套博文教程可进一步理解参数传递与接口联调细节遇到问题也可与作者交流讨论适合希望打通小程序与后端文件上传链路的开发者参考。1. 微信小程序上传图片视频到后台存储一份能直接跑通的 Flask 落地 Demo小程序端选完图、点上传后台却只收到一个空文件或者 413这种场景我见过太多次。这份 Demo 干的事很具体把微信小程序里wx.chooseMedia选出来的图片和视频通过wx.uploadFile传到 Flask 后台落盘存储并返回可访问路径。它解决的是「前端选得了、后端收得住、文件找得回」这条最短链路适合刚接手小程序文件上传需求的开发者、要做课程设计的学生以及想给现有 Flask 项目补上传能力的后端同学。整套东西不依赖对象存储本地磁盘就能跑先把链路打通再谈扩展这是我的一贯顺序。2. 拆开这份 DemoFlask 接收端与小程序上传端的职责边界拿到一个上传 Demo先别急着跑得看清两端各自管什么。很多翻车不是代码错是职责没分清——前端以为后端会压缩后端以为前端已经限制了大小结果两边都没做一个 200MB 的视频直接把服务打挂。这一章把 Demo 的结构拆开讲让你知道每个文件该放哪、每段逻辑为什么这么写。2.1 项目结构与依赖清单这份 Demo 的目录结构不复杂但每个位置都有讲究。我一般会按下面这样组织前后端分开上传目录独立出来方便后面挂载和清理upload-demo/ ├── server/ │ ├── app.py # Flask 主入口 │ ├── config.py # 上传大小、允许类型等配置 │ ├── uploads/ # 落盘目录运行时生成 │ └── requirements.txt └── miniprogram/ ├── pages/upload/ │ ├── upload.js # 选图 上传逻辑 │ ├── upload.wxml │ └── upload.wxss └── app.json依赖就三个别装一堆用不上的# server/requirements.txt Flask3.0.3 Flask-Cors4.0.1 Werkzeug3.0.3Flask是主框架Flask-Cors处理小程序开发阶段的跨域预检Werkzeug里的secure_filename用来清洗文件名。这里有个血泪经验secure_filename会把中文文件名几乎全部剥掉如果你的业务需要保留原始中文名得自己写一套清洗逻辑不能直接用它返回的结果当最终文件名否则用户上传「产品图.jpg」最后变成「.jpg」排查起来很费劲。2.2 Flask 接收端从 request.files 到安全落盘后端接收的核心是request.files但真正决定稳不稳的是落盘前的校验。下面这段是我在这个 Demo 里用的接收逻辑能直接抄# server/app.py import os import uuid from flask import Flask, request, jsonify from flask_cors import CORS from werkzeug.utils import secure_filename app Flask(__name__) CORS(app) # 配置区这几个值建议放 config.py别硬编码在路由里 UPLOAD_DIR os.path.join(os.path.dirname(__file__), uploads) ALLOWED_EXT {jpg, jpeg, png, gif, mp4, mov} MAX_SIZE 50 * 1024 * 1024 # 单文件 50MB 上限 os.makedirs(UPLOAD_DIR, exist_okTrue) app.config[MAX_CONTENT_LENGTH] MAX_SIZE def allowed_file(filename: str) - bool: # 只认扩展名且必须带点防止无扩展名文件混入 return . in filename and filename.rsplit(., 1)[1].lower() in ALLOWED_EXT app.route(/api/upload, methods[POST]) def upload(): # 1. 字段名必须和小程序端 wx.uploadFile 的 name 一致 if file not in request.files: return jsonify(code400, msg缺少 file 字段), 400 f request.files[file] if f.filename : return jsonify(code400, msg文件名为空), 400 if not allowed_file(f.filename): return jsonify(code400, msg不支持的文件类型), 400 # 2. 用 uuid 重命名避免同名覆盖和路径穿越 ext f.filename.rsplit(., 1)[1].lower() safe_name f{uuid.uuid4().hex}.{ext} save_path os.path.join(UPLOAD_DIR, safe_name) # 3. 分块写入大文件不至于一次性吃满内存 with open(save_path, wb) as out: while chunk : f.stream.read(1024 * 1024): out.write(chunk) # 4. 返回相对路径前端拼域名即可访问 return jsonify(code0, msgok, data{path: f/uploads/{safe_name}}) if __name__ __main__: app.run(host0.0.0.0, port5000, debugTrue)逐段说下为什么这么写。第一字段名file是硬约定小程序端wx.uploadFile的name参数写什么这里就必须是什么两边对不上就是「缺少 file 字段」这是最高频的报错。第二用uuid4().hex重命名而不是保留原名一是防同名覆盖二是防../这类路径穿越secure_filename虽然也能防但会吃掉中文所以我干脆自己生成。第三f.stream.read(1024 * 1024)分块写1MB 一块50MB 的文件也就循环 50 次内存占用可控如果直接f.save()小文件没问题大视频在并发下容易把内存顶上去。第四MAX_CONTENT_LENGTH是 Flask 层面的硬闸超过直接返回 413比在业务代码里判断更早生效。参数上MAX_SIZE设 50MB 是个折中值微信小程序wx.uploadFile单文件本身有上限视频稍微大一点就传不动实际项目里视频建议走分片或者直传对象存储这个 Demo 先覆盖图片和短视频场景。ALLOWED_EXT里我加了mov因为 iOS 拍出来的视频常见这个格式不加的话苹果用户一传就报「不支持的类型」。2.3 小程序端wx.chooseMedia 与 wx.uploadFile 的配合前端这块选媒体和上传是两个 API中间要处理好临时路径。核心代码如下// miniprogram/pages/upload/upload.js Page({ data: { filePath: , mediaType: image, uploading: false, }, // 选图或选视频mediaType 决定选哪种 chooseMedia() { const that this; wx.chooseMedia({ count: 1, mediaType: [this.data.mediaType], // image / video / mix sourceType: [album, camera], maxDuration: 30, // 视频最长 30 秒和后台大小限制呼应 success(res) { const file res.tempFiles[0]; that.setData({ filePath: file.tempFilePath, mediaType: file.fileType video ? video : image, }); }, fail(err) { console.error(选择失败, err); }, }); }, // 上传到 Flask 后台 doUpload() { if (!this.data.filePath) { wx.showToast({ title: 请先选择文件, icon: none }); return; } if (this.data.uploading) return; // 防重复点击 this.setData({ uploading: true }); const that this; wx.uploadFile({ url: http://127.0.0.1:5000/api/upload, // 真机调试换成局域网 IP filePath: this.data.filePath, name: file, // 必须和后台 request.files 的键一致 formData: { source: miniprogram }, // 附加字段后台可选用 timeout: 60000, success(res) { // 注意res.data 是字符串要手动 JSON.parse const body JSON.parse(res.data); if (body.code 0) { wx.showToast({ title: 上传成功 }); console.log(访问路径, body.data.path); } else { wx.showToast({ title: body.msg || 上传失败, icon: none }); } }, fail(err) { console.error(上传失败, err); wx.showToast({ title: 网络异常, icon: none }); }, complete() { that.setData({ uploading: false }); }, }); }, });几个关键点。wx.chooseMedia的mediaType传数组[image]只选图[video]只选视频[mix]混选Demo 里用data.mediaType控制方便切换。maxDuration: 30是视频时长上限单位秒和后台 50MB 的大小限制配合避免用户选了个 5 分钟的视频传半天失败。wx.uploadFile的name必须和后台request.files[file]对上这是硬约束。还有一个新手常踩的点success回调里的res.data是字符串不是对象直接res.data.path会拿到undefined必须JSON.parse一次这个坑我在早期项目里栽过排查了半天以为是后台没返回。url里的127.0.0.1只在开发者工具里有效真机调试必须换成电脑的局域网 IP比如http://192.168.x.x:5000/api/upload而且手机和电脑要在同一个网络下。这一步不做真机上永远是「网络异常」。3. 跑通全链路从本地启动到真机验证的完整步骤代码看懂了不代表能跑起来上传这条链路涉及端口、网络、权限、格式四道关任何一道没过都是失败。这一章按启动顺序走一遍每步给出验证方法让你知道卡在哪一环。3.1 启动 Flask 并验证接口可用先把后端跑起来别急着开小程序。启动命令cd server pip install -r requirements.txt python app.py看到Running on http://0.0.0.0:5000就说明起来了。但「起来了」不等于「能用」我习惯先用 curl 打一发确认接口本身没问题# 用一个本地图片测试-F 的 file 对应后台字段名 curl -X POST http://127.0.0.1:5000/api/upload \ -F file./test.jpg \ -v正常返回应该是{code:0,msg:ok,data:{path:/uploads/xxxx.jpg}}。如果返回 413说明文件超了MAX_CONTENT_LENGTH返回 400 且提示「不支持的文件类型」检查扩展名是否在ALLOWED_EXT里返回 400 提示「缺少 file 字段」说明-F的键名写错了。这一步用 curl 先过能把「后端自身的问题」和「前后端联调的问题」隔离开省得两头猜。还要注意uploads目录的写权限。Linux 下如果 Flask 以低权限用户跑目录属主不对open(save_path, wb)会直接抛PermissionError日志里能看到但小程序端只会显示「上传失败」容易误判成网络问题。启动前ls -ld uploads看一眼属主不对就chown一下。3.2 开发者工具里的域名与跨域配置微信开发者工具默认会校验合法域名本地127.0.0.1不在白名单里直接请求会被拦。开发阶段的做法是在开发者工具右上角「详情 → 本地设置」里勾选「不校验合法域名、web-view业务域名、TLS 版本以及 HTTPS 证书」。这一步勾上本地 HTTP 请求才能发出去。跨域这块Flask 端已经用CORS(app)全局放开了开发阶段够用。但要注意CORS(app)是允许所有来源上线前必须收紧到具体域名否则等于把接口裸奔在公网。我一般会在config.py里留一个ALLOWED_ORIGINS列表开发时是[*]上线前改成小程序后台配置的合法域名。还有一个容易忽略的点wx.uploadFile走的是multipart/form-data不是普通POST所以后台不能用request.get_json()去取参数附加字段要用request.form.get(source)。这个区别在联调时经常让人困惑——明明前端formData传了值后台get_json()却是None。3.3 真机调试局域网 IP、HTTPS 与大小限制开发者工具跑通后真机是另一道坎。三个必须处理的点第一url换成局域网 IP。电脑上ipconfigWindows或ifconfigmacOS/Linux查到本机 IP比如192.168.1.100小程序里改成http://192.168.1.100:5000/api/upload。手机连同一个 WiFi浏览器先访问http://192.168.1.100:5000看能不能通通了再传。第二真机默认要求 HTTPS。开发阶段可以在小程序后台「开发管理 → 开发设置 → 服务器域名」里配置但 HTTP 的 IP 加不进去。常规做法是真机调试时打开「调试模式」或者在开发者工具里用「真机调试」功能它会临时放宽限制。正式环境必须上 HTTPS这是硬要求Demo 阶段先用调试模式绕过。第三大小限制要对齐。微信小程序wx.uploadFile单文件有上限视频稍大就传不动后台的 50MB 和前端maxDuration: 30要一起调。我见过前端不限时长、后台限 50MB 的配置用户选个 2 分钟视频传到 80% 失败体验极差。两边阈值必须一致或者前端选完就先判断tempFiles[0].size超了直接提示别等传一半才报错。验证顺序建议curl 通 → 开发者工具通 → 真机同 WiFi 通 → 真机调试模式通。每步都过了这条链路才算真正跑通。4. 避坑与排查上传链路上最容易翻车的五个点上传这活儿代码量不大坑却不少而且大多表现为「上传失败」四个字信息量极低。这一章按「现象 → 原因 → 解决」把高频问题列清楚照着对能省下大量瞎猜的时间。4.1 现象后台收到空文件或文件名丢失原因通常是前端name和后台字段名不一致或者用了secure_filename处理中文名。前者表现为request.files里根本没有对应键后者表现为落盘文件名变成.jpg这种残缺形态。解决先确认wx.uploadFile的name和request.files[file]完全一致中文名场景不要直接用secure_filename的返回值改成「uuid 原扩展名」原始名单独存数据库字段。4.2 现象413 Request Entity Too Large原因就是文件超了MAX_CONTENT_LENGTH。但要注意这个限制是 Flask 层面的Nginx 反代时还有一层client_max_body_size两层都要调。解决Flask 里设app.config[MAX_CONTENT_LENGTH]Nginx 里设client_max_body_size 50m;两边数值对齐。只调一边另一边照样拦。4.3 现象真机上一直「网络异常」原因多半是url还是127.0.0.1或者手机和电脑不在同一网段。解决换成电脑局域网 IP手机浏览器先访问确认连通再检查电脑防火墙是否放行了 5000 端口Windows 防火墙默认会拦入站连接这个特别隐蔽。4.4 现象iOS 上传视频报「不支持的类型」原因是 iOS 拍的视频常见.mov格式ALLOWED_EXT里没加。解决把mov加进白名单或者前端选完后判断fileType非白名单格式直接提示用户。别指望用户自己转格式体验上过不去。4.5 现象并发上传时部分请求失败或文件损坏原因是多个请求写同名文件或者磁盘 IO 打满。用 uuid 重命名能解决同名覆盖但磁盘 IO 这块要靠限流。解决前端加uploading状态防重复点击Demo 里已有后端如果并发高考虑加队列或者直接上对象存储本地磁盘不适合高并发写入场景。5. 从 Demo 到可用三个让上传链路更稳的改造技巧Demo 跑通只是起点真要用起来还得补几块。这一章说三个我每次都会加的改造都是踩过坑之后形成的习惯。第一个是文件类型二次校验。光看扩展名不够用户把.exe改成.jpg照样能过。稳妥做法是读文件头几个字节判断真实类型图片看 magic numberJPEG 是FF D8 FFPNG 是89 50 4E 47视频看ftyp标识。下面这段是图片校验的简化版def sniff_image_type(path: str) - str | None: # 读前 8 字节判断真实图片类型防止改扩展名绕过 with open(path, rb) as f: head f.read(8) if head[:3] b\xff\xd8\xff: return jpeg if head[:8] b\x89PNG\r\n\x1a\n: return png if head[:6] in (bGIF87a, bGIF89a): return gif return None落盘后调一次返回None就删文件并报错。这一步能挡掉大部分伪装文件成本很低。第二个是上传进度反馈。wx.uploadFile返回的是UploadTask对象可以监听onProgressUpdate把百分比推到页面用户不至于盯着转圈干等。视频文件尤其需要30 秒的视频在弱网下能传一分多钟没进度条用户早退了。第三个是清理策略。uploads目录只增不减跑几个月就撑爆磁盘。我一般加个定时任务按修改时间清理超过 N 天的文件或者落库时记录路径和创建时间定期对账删除孤儿文件。这个不做迟早有一天磁盘满了上传全线挂掉那时候再补就晚了。改造项解决什么问题实现成本文件头校验改扩展名绕过类型限制低一个函数进度反馈大文件上传无感知低监听回调定时清理磁盘只增不减中需定时任务这三个改造里文件头校验是我最坚持的一个。早些年有个项目没做用户传了个改名的脚本上来虽然没造成实际影响但事后复盘吓出一身冷汗。从那以后我每次做上传功能落盘后的类型校验都强制走一遍不管前端有没有限制——前端的限制永远只是体验优化真正的安全边界必须在后端。希望这套东西能帮到你少走几个我走过的弯路。本文还有配套的精品资源点击获取