移动端文件上传校验需组合使用accept属性(推荐扩展名+mime双写)、javascript change事件(检查name扩展名、size大小并重置value)及表单提交前兜底检查,但所有前端限制均不可替代后端基于魔数、路径安全和文件重命名的真实校验。

移动端用原生 HTML 校验限制文件上传类型和大小,核心是组合使用 accept、required 和 JavaScript 的 change 事件校验,但必须清醒认识:这些都只是用户体验层的提示与拦截,**不能替代后端真实校验**。尤其在 iOS Safari、微信 WebView 等环境下,accept 常被忽略或弱支持,拖拽上传也绕过选择框过滤。
accept 属性:仅作类型提示,写法要兼顾兼容性
移动端浏览器对 MIME 类型识别差异大,Safari 尤其不信任 accept="application/pdf" 这类写法。推荐采用「扩展名 + MIME」双写方式,用英文逗号分隔、不加空格:
- 图片:
accept=".jpg,.jpeg,.png,.webp,image/jpeg,image/png,image/webp" - Pdf:
accept=".pdf,application/pdf" - 禁止 HTML/JS:不要写
accept="text/html"——这反而会让用户更容易选中 .html 文件
注意:accept 在 iOS 微信内置浏览器中基本失效,用户点“从手机相册选择”或“拍摄”时,系统直接跳过过滤逻辑;capture="camera" 会进一步覆盖 accept 行为,慎用。
JavaScript 校验:必须同时检查 name、type 和 size
监听 input[type="file"] 的 change 事件,在用户选完后立刻判断。关键点:
-
file.name提取扩展名(用file.name.split('.').pop().toLowerCase()),注意处理无扩展名、多点文件(如data.tar.gz) -
file.type不可靠(拖拽上传常为空字符串,重命名文件可伪造),但可作为辅助参考 -
file.size单位是字节,需换算(如 5MB =5 * 1024 * 1024) - 校验失败后务必执行
input.value = '',否则同名文件重复选择不会再次触发change
示例逻辑:
input.addEventListener('change', e => {
const file = e.target.files[0];
if (!file) return;
const ext = file.name.split('.').pop().toLowerCase();
const maxSize = 5 * 1024 * 1024;
if (!['jpg', 'jpeg', 'png'].includes(ext) || file.size > maxSize) {
alert('仅支持 JPG/PNG,且不超过 5MB');
e.target.value = '';
}
});
form 表单提交前的兜底检查
即使用了 accept 和 JS 校验,用户仍可通过 DevTools 删除属性、用 fetch 手动发请求绕过。因此表单提交时建议再做一次轻量级检查:
- 检查
input.files.length > 0 - 复核扩展名与大小(逻辑同上)
- 若不通过,
e.preventDefault()阻止提交,并聚焦输入框给出提示
注意:enctype="multipart/form-data" 必须显式设置在 <form></form> 标签上,否则后端收不到文件字段——这个错误在移动端调试时极难定位,常表现为“上传成功但后端没收到”。
真正有效的防线只在后端
前端所有限制在移动端更易被绕过。服务端必须做三件事:
- 读取文件前 12–32 字节(magic bytes),验证真实类型(如 PDF 是
%PDF,PNG 是89 50 4E 47) - 拒绝扩展名与魔数不匹配的文件(例如
report.jpg实际是 JS 脚本) - 检查上传临时路径是否安全(避免
../路径遍历)、存储文件名是否重命名(去掉原始 name,用 UUID + 白名单扩展名)
不复杂但容易忽略











