微信小程序图片视频上传Flask后端存储完整demo与避坑指南

发布时间:2026/10/13 12:34:08
微信小程序图片视频上传Flask后端存储完整demo与避坑指南
简介面向微信小程序开发者与Flask后端初学者这份demo完整演示了图片和视频从前端采集、预览到后端接收存储的链路。前端是不需要复杂配置的微信小程序工程可直接用微信开发者工具打开涵盖照片/视频选择、预览与上传交互后端uploadTest.py是一个可运行的Flask接口文件接收上传后将文件写入images或videos目录目录规划清晰、命名直观便于查看落盘结果。压缩包共12个文件以5个json配置、3个js逻辑、2个wxss样式、1个wxml页面结构加1个py后端文件为主空间仅5KB代码量小、易读易改适合快速本地跑通。目前已有420人学习下载特别适合想快速跑通上传流程、理解前后端联调方式以及准备课程作业的入门者。结合配套教程可一步步复现环境如遇问题也可私信作者交流。1. 微信小程序上传图片视频到后台存储这份 demo 到底解决什么问题把图片和视频从小程序端传到服务器是几乎每个带内容发布、实名认证、案例展示功能的微信小程序都绕不过去的一步。而这一步的坑远比想象中多不是后端收不到文件而是前端选完文件之后wx.uploadFile只认本地临时路径、tempFilePath真机上会失效、Flask 端还有大小限制和文件名编码问题。这份「微信小程序上传图片视频到后台存储」demo我拆过一遍之后可以负责任地说它把一条完整的上传链路从零打通了——小程序端负责选文件和发起上传后端用 Flask 接收并落盘再返回一个能直接回显的 URL。适合正在做小程序毕设、内部工具或者简单内容社区的开发者拿来改改就能用。2. 先看传什么与存在哪上传链路的三个关键选型动手改代码前先想清楚整条链路分几段小程序端用wx.chooseMedia拿到资源再用wx.uploadFile以 multipart/form-data 形式发到后端后端 Flask 通过request.files接收写入磁盘或对象存储最后把访问 URL 返回给前端用于回显。这三段每一段都有选型问题选错一个后面全白干。2.1 filePath 的玄学wx.uploadFile 只认本地临时路径很多第一次做上传的人会直接把wx.chooseMedia返回的tempFilePath当成网络地址塞给image或video标签页面上一时能显示就觉得万事大吉。实际上这个本地临时路径是设备上的缓存文件有效期很短小程序退出重进后大概率会失效。正确做法是tempFilePath只用来做两件事——本地预览以及作为wx.uploadFile的filePath参数。wx.chooseMedia({ count: 1, mediaType: [image, video], sourceType: [album, camera], maxDuration: 30, success: (res) { const file res.tempFiles[0]; const tempFilePath file.tempFilePath; // 先用临时路径做本地预览 this.setData({ previewSrc: tempFilePath }); // 再上传到后台 wx.uploadFile({ url: http://127.0.0.1:5000/api/upload, filePath: tempFilePath, name: file, formData: { scene: avatar, token: wx.getStorageSync(token) || }, success: (uploadRes) { const data JSON.parse(uploadRes.data); if (data.code 0) { this.setData({ remoteUrl: data.data.url }); } } }); } });这段代码里有两个参数容易改错。name: file是表单字段名必须和后端request.files.get(file)里的 key 完全一致大小写都不能错。formData是额外业务字段后端在request.form里取和文件字段是分开的。注意JSON.parse(uploadRes.data)wx.uploadFile返回的data是字符串直接当成对象用会得到 undefined。2.2 本地存储还是对象存储demo 默认方案与替换边界这份 demo 默认把文件存到 Flask 所在服务器的本地目录逻辑简单、上手快适合开发环境和个人项目。但如果你要部署到云服务器或者小程序审核时需要可访问的 HTTPS 域名本地存储很快会成为瓶颈磁盘空间有限、备份麻烦、服务重启后路径可能丢。对比维度本地磁盘存储对象存储COS/OSS/S3接入成本低一个目录搞定中需要额外引入 SDK 和密钥配置访问速度局域网快公网依赖服务器带宽CDN 加速大文件体验更好扩容方式手动清理磁盘按量付费自动扩容典型适用demo、内网工具、低并发线上内容社区、电商资质上传demo 里 Flask 端我一般会写一个write_media函数把「存哪里」的逻辑收敛到一个函数里。这样后期换成对象存储只改这一个函数的实现路由和响应结构完全不动。这也是这份 demo 最值得借鉴的一点——边界划清楚替换成本低。2.3 格式与大小限制先拦在前面别等落盘后悔后端如果不做任何限制一张 4K 图片或一段 1GB 视频会把开发服务器直接干趴下。Flask 里最简单的兜底是MAX_CONTENT_LENGTH它作用于整个请求体超过直接抛 413。但光靠这一个配置不够前端也要配合压缩否则用户拍一段 10 分钟视频上传等到天荒地老。import os from datetime import date from uuid import uuid4 from flask import Flask, request, jsonify from werkzeug.utils import secure_filename app Flask(__name__) app.config[MAX_CONTENT_LENGTH] 200 * 1024 * 1024 # 200MB单位是字节 MEDIA_FOLDER os.path.join(os.path.dirname(os.path.abspath(__file__)), media) ALLOWED_EXTENSIONS {jpg, jpeg, png, webp, gif, mp4, mov, avi} def allowed_file(filename): return . in filename and filename.rsplit(., 1)[1].lower() in ALLOWED_EXTENSIONS def write_media(file_storage): ext file_storage.filename.rsplit(., 1)[1].lower() day_dir date.today().strftime(%Y%m%d) target_dir os.path.join(MEDIA_FOLDER, day_dir) os.makedirs(target_dir, exist_okTrue) filename f{uuid4().hex[:12]}.{ext} filepath os.path.join(target_dir, filename) # 先写临时文件再重命名避免半包写入导致文件损坏 tmp_path filepath .tmp file_storage.save(tmp_path) os.rename(tmp_path, filepath) return f/media/{day_dir}/{filename}这里MAX_CONTENT_LENGTH设成了 200MB视频能传但图片也被放宽了。更精细的做法是分别限制比如图片 10MB、视频 100MB这需要在小程序端先判断tempFiles[0].size再决定是否压缩。uuid4().hex[:12]用来生成随机文件名避免用户原始文件名里的中文、空格和各种特殊字符直接落到磁盘上这一条属于血泪经验后面避坑章会细说。3. Flask 后端接收与落盘目录规划、安全文件名与统一响应后端是这条链路的承重墙。这一段我会拆开讲路由怎么组织、文件怎么安全落盘、响应结构怎么定抄完这段你就能得到一个能跑的最小后端。3.1 目录结构与配置项media 按日期分目录建议后端项目按「配置文件 蓝图路由 工具函数」三层组织别把全部代码堆在一个app.py里。demo 规模小但目录规划决定你后期改 bug 的时候是十分钟找到问题还是翻半天文件。# app.py from flask import Flask from flask_cors import CORS from api.upload import upload_bp app Flask(__name__) app.config[MAX_CONTENT_LENGTH] 200 * 1024 * 1024 # 开发联调放开跨域生产环境用反向代理处理 app.config[UPLOAD_FOLDER] media/ CORS(app) app.register_blueprint(upload_bp, url_prefix/api) if __name__ __main__: app.run(host0.0.0.0, port5000, debugFalse)配置项值说明MAX_CONTENT_LENGTH200MB超限返回 413防止大文件拖垮进程UPLOAD_FOLDERmedia/基础存储目录子目录按日期自动生成CORS全放开开发期方便从开发者工具访问线上收窄到具体域名3.2 路由组织与 secure_filename别信任用户的文件名secure_filename是 Werkzeug 自带的安全函数会把中文文件名转成 ASCII、去掉路径分隔符和危险字符能防路径穿越。但代价是中文文件名全变成一串乱码英文所以大多数实际项目的做法是只从原文件名里取扩展名文件名主体用uuid生成。两步结合既保证安全又保证可读性。# api/upload.py import os import uuid from datetime import date from flask import Blueprint, request, jsonify, current_app, send_from_directory from werkzeug.utils import secure_filename upload_bp Blueprint(upload_bp, __name__) ALLOWED_EXTENSIONS {jpg, jpeg, png, webp, gif, mp4, mov, avi} def allowed_file(filename): return . in filename and filename.rsplit(., 1)[1].lower() in ALLOWED_EXTENSIONS def save_media(file_storage): ext file_storage.filename.rsplit(., 1)[1].lower() # 校验扩展名 if ext not in ALLOWED_EXTENSIONS: raise ValueError(file type not allowed) day_dir date.today().strftime(%Y%m%d) target_dir os.path.join(current_app.config[UPLOAD_FOLDER], day_dir) os.makedirs(target_dir, exist_okTrue) filename f{uuid.uuid4().hex[:12]}.{ext} filepath os.path.join(target_dir, filename) file_storage.save(filepath) return f/media/{day_dir}/{filename} upload_bp.route(/upload, methods[POST]) def upload_file(): if file not in request.files: return jsonify({code: 1, msg: missing file}), 400 file request.files.get(file) if file.filename : return jsonify({code: 1, msg: empty filename}), 400 if not allowed_file(file.filename): return jsonify({code: 1, msg: file type not allowed}), 400 # 常见做法是用 MIME 扩展名双重校验 try: url save_media(file) return jsonify({code: 0, data: {url: url}}) except ValueError as e: return jsonify({code: 1, msg: str(e)}), 400这里值得说清楚的两点request.files.get(file)和request.files[file]的区别前者在 key 不存在时返回 None不会抛异常对校验友好后者会直接抛KeyError。返回的url是相对路径前端拼接上域名就能回显后面 3.3 会补上静态文件映射。3.3 访问映射与静态资源服务让上传后能立即回显只落盘不提供访问入口等于白传。Flask 里最干净的方式是用send_from_directory配合as_view挂一个路由让/media/path:filename能映射到磁盘目录。这样就不需要单独配 Nginx 也能在小程序开发者工具里直接看回显。# api/media.py import os from flask import Blueprint, send_from_directory, current_app media_bp Blueprint(media_bp, __name__) media_bp.route(/media/path:filename) def media_file(filename): # 不开放目录浏览只允许访问具体文件 media_root os.path.abspath(current_app.config[UPLOAD_FOLDER]) return send_from_directory(media_root, filename) # 别忘了在 app.py 里注册 # app.register_blueprint(media_bp)send_from_directory底层会处理 URL 解码和路径穿越比手动拼os.path.join再send_file安全。生产环境如果上了 Nginx会直接alias /media/路径就不再走 Flask这段代码留给开发和内网环境用。3.4 响应结构与 token 鉴权最简单但必须有demo 的响应统一是{code, msg, data}三段式code:0代表成功非 0 为失败。这个小习惯能避免前端拿到结果后还要猜状态。token 鉴权在 demo 里可以用request.headers.get(X-Token)读取和formData里的 token 二选一规范做法是放 header避免业务字段和二进制文件混在 multipart 里。# 上传接口里加一段硬校验 token request.headers.get(X-Token, ) if not token or token ! current_app.config.get(UPLOAD_TOKEN): return jsonify({code: 401, msg: unauthorized}), 401生产环境这里会换成 JWT 解析demo 里写死一个常量即可目的是让你在联调时养成「带鉴权头」的习惯。很多翻车现场都是因为后端没任何认证上线后被刷存储空间。4. 联调避坑记录真机预览、413 与回显路径的四个翻车现场这部分是我在真实环境里踩过的坑每条都是「现象 → 原因 → 解决」的结构直接对照你遇到的报错来翻。4.1 真机上图片视频缩略图黑屏本地临时路径存进了业务表现象开发者工具里一切正常真机测试时上传成功后视频缩略图黑屏点开也加载不出内容。原因前端在wx.chooseMedia后把tempFilePath当正式地址传给后端存库后端原样返回小程序重启后临时路径失效。解决后端落盘后返回自己的/media/...URL前端只在选完文件到上传完成之间用临时路径做预览。从那以后我每次接手这类 demo都先检查「前端传给后端的是临时路径还是已落盘的 URL」。4.2 上传大视频报 413Flask 的限制和前端超时撞车现象小于 50MB 的图片秒传超过 100MB 的视频要么直接 fail要么等很久之后报statusCode: 413。原因MAX_CONTENT_LENGTH设了 200MB 但没生效大概率是改了配置没重启服务。另一层原因是wx.uploadFile默认 60 秒超时大视频请求还没传完就被前端掐断。解决改完后端配置必须重启 Flask 进程前端手动设置timeout: 120000同时用sizeType压缩图片。视频时长在chooseMedia里用maxDuration限制比如 30 秒内。4.3 回显页面一片空白后端返回了被转义的 HTML现象上传返回code:0但前端渲染出空白或者看到一串带\u003c之类的乱码。原因后端如果用了render_template_string渲染上传结果HTML 标签会被转义成文本返回小程序端小程序端直接把字符串塞进video的src属性自然什么都显示不了。解决统一走jsonify前端拿到data.url后先console.log确认是http(s)://开头再赋值。用video标签时再检查一下autoplay和controls属性安卓机对autoplay限制很严格。4.4 安卓手机传视频提示文件损坏临时文件没落盘就重命名现象传一张图没问题传同目录的视频播放时报「无法播放此视频」。原因某些场景下file_storage.save()写完文件但未完全 flush紧接着os.rename把半包文件盖到正式文件名上文件损坏。SD 卡或网络挂载盘上尤其容易出现。解决先把文件保存成.tmp后缀再重命名为正式文件。代码就是 2.3 里写过的那个tmp_path先写、os.rename再换名的流程。如果还不行改成流式分块写入每读一段写一段避免一次性读进内存。4.5 并发上传请求偶发 404开发服务器线程数不够现象快速连续传两张图第一张成功第二张偶发 404 或连接重置。原因Flask 开发服务器默认单进程处理完一个请求再处理下一个两个大请求同时打过来第二个可能被连接池拒绝。解决开发阶段把这个 demo 当学习工具没问题线上务必换 Gunicorn 或 uWSGI 做容器配 4 个 worker。如果你用的是 Windows 自带 Flask 跑也可以临时用threadedTrue缓解但这只是权宜之计。5. 验证与收尾本地起一个最小闭环与几点小习惯资源拿到手之后先别急着改需求按下面三步验证一遍链路是否完整第一步装依赖并启动后端pip install flask flask-cors python app.py第二步用 curl 模拟小程序端上传curl -X POST http://127.0.0.1:5000/api/upload \ -H X-Token: dev-token \ -F file/path/to/test.jpg如果返回{code: 0, data: {url: /media/20250917/abc123.jpg}}说明后端落盘正常直接在浏览器打开http://127.0.0.1:5000/media/20250917/abc123.jpg能看到图片链路就通了。第三步再做真机联调。开发者工具里关闭「不校验合法域名」选项把url换成你电脑局域网 IP手机上用 4G/5G 访问重点观察视频预览和超时时间。做完这三步再上手改自己的需求能省下大量排错时间。我自己的习惯是每次改动后端字段先跑一遍 curl确认响应结构没变化每次改动前端上传逻辑先在开发者工具里用「清缓存 重新编译」验证再上真机。另外别把debugTrue开到生产环境Flask 的调试模式会把堆栈直接回显给前端等于把服务器信息公开。这份 demo 的边界也很清楚本地磁盘存储、单机部署、无队列无任务系统支撑不了大规模生产环境。但如果你顺着这条链路把save_media换成对象存储 SDK、把鉴权换成 JWT很快就能变成一个可上线的上传模块。希望帮到你。本文还有配套的精品资源点击获取