必须用 mediarecorder 录音并手动构造 file 上传,而非依赖 input type="file";accept 和 capture 属性不可靠,仅作提示;服务端必须校验音频文件头、元数据和时长,前端限制无效。

不能靠 HTML 本身“限制只能录音后上传”——input type="file" 是通用文件选择控件,不区分来源;所谓“录音后上传”,本质是绕过它,用 MediaRecorder 录制音频并手动构造 File 对象上传。
为什么 accept=".wav,.mp3" 或 capture="microphone" 不可靠
这两个属性常被误用:
-
accept="audio/*"只让系统文件选择器默认显示音频文件,用户仍可手动切换到“所有文件”并选任意文件(包括 exe、html) -
capture="microphone"在部分 Android 浏览器中会唤起录音界面,但 iOS Safari 完全忽略该属性,且 Chrome 桌面版也不支持——它不是标准录音 API,只是个提示性 hint - 即使触发了系统录音,录完保存的文件仍是用户可控的本地文件,和你网页里调用
MediaRecorder录的没有本质区别
真正能控制“只传录音”的做法:跳过 input,用 MediaRecorder + FormData
核心逻辑是:不给用户选文件的机会,只提供“开始/停止录音”按钮,录音结束立刻生成 Blob,转为 File,再塞进 FormData 发送。
- 必须在 HTTPS 或
localhost下运行,否则navigator.mediaDevices.getUserMedia会直接拒绝 - 录音前要显式请求麦克风权限:
await navigator.mediaDevices.getUserMedia({ audio: true }) - 用
new MediaRecorder(stream)录制,stop()后触发dataavailable事件拿到audio/webm或audio/mp4的Blob - 转
File时注意指定type,比如new File([blob], "recording.webm", { type: "audio/webm" }) - 上传时仍用
FormData.append("audio", file),字段名必须和服务端约定一致
示例关键片段:
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
const mediaRecorder = new MediaRecorder(stream);
let audioBlob;
mediaRecorder.ondataavailable = e => audioBlob = e.data;
mediaRecorder.stop();
// 停止后立即上传
const file = new File([audioBlob], "recording.webm", { type: "audio/webm" });
const fd = new FormData();
fd.append("audio", file);
fetch("/upload", { method: "POST", body: fd });
服务端必须校验音频内容,不能信前端 type 或扩展名
前端构造的 File 对象,type 和文件名完全可控,攻击者可伪造。服务端必须:
- 读取文件头几个字节(魔术字节),确认是真实 webm/mp4/wav,而非把 zip 改名成 .webm
- 用 FFmpeg 或类似工具解析音频元数据,验证采样率、声道数等是否合理(防超大静音文件)
- 限制最大时长(如 5 分钟),仅靠文件大小不够——低码率录音可长达数小时却只有几 MB
- 存储时重命名,去掉原始文件名,禁用执行权限,存到非 Web 目录下
真正的约束点不在前端“怎么选”,而在“不给选的机会”+“服务端死守内容校验”。只要漏掉后端校验,任何前端限制都形同虚设。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











