Flutter Web无法通过代码保存跨子域API下载的文件
解决Flutter Web跨域下载文件静默失败的方案
问题根源分析
当前方案存在两个核心问题:
- 用Base64编码处理14MB的大文件会导致体积膨胀30%,部分浏览器对data URI长度有限制,可能触发静默失败;
- API响应缺少
Content-Disposition头,且CORS配置未暴露该头,浏览器对a.download属性的兼容性支持受限。
方案1:改用Blob + URL.createObjectURL替代Base64
Base64大文件处理效率低且易触发浏览器限制,换成Blob方式更稳定:
修改Flutter Web的downloadFile函数:
/// web.dart import 'dart:typed_data'; import 'package:logger/logger.dart'; import 'package:http/http.dart' as http; import 'package:universal_html/html.dart' as universal_html; var logger = Logger( printer: PrettyPrinter(), ); Future<void> downloadFile({ required String token, required String url, required String fileName, required String dataType, }) async { try { final Map<String, String> headers = { "Authorization": "Bearer $token", 'Accept': dataType, }; final http.Response r = await http.get(Uri.parse(url), headers: headers); final Uint8List data = r.bodyBytes; // 创建Blob对象存储文件字节 final universal_html.Blob blob = universal_html.Blob([data], dataType); // 生成临时访问URL final String blobUrl = universal_html.Url.createObjectUrlFromBlob(blob); final a = universal_html.AnchorElement(href: blobUrl); a.download = fileName; a.click(); // 清理临时URL释放内存 universal_html.Url.revokeObjectUrl(blobUrl); a.remove(); } catch (e) { logger.d(e); } }
方案2:优化API配置,完善响应头与CORS
让API返回规范的下载响应头,同时调整CORS配置暴露必要字段:
1. 修改Go API的文件下载接口
在返回文件的处理函数中添加以下响应头:
// 替换成实际的文件名,比如从数据库或文件元数据中获取 fileName := "audio_6.mp3" // 设置强制下载的Content-Disposition头 w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", fileName)) // 设置正确的媒体类型,替代默认的application/octet-stream w.Header().Set("Content-Type", "audio/mpeg")
2. 更新CORS配置,暴露Content-Disposition
修改CORS的ExposedHeaders字段,添加Content-Disposition:
r.Use(cors.Handler(cors.Options{ AllowedOrigins: []string{"*"}, AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, AllowedHeaders: []string{"Accept", "Origin", "Authorization", "Content-Type", "X-CSRF-Token", "X-Requested-With", "User-Agent", "Cache-Control", "Pragma", "Content-Disposition"}, ExposedHeaders: []string{"Link", "Content-Length", "Content-Disposition"}, AllowCredentials: false, MaxAge: 300, }))
3. 前端适配读取API返回的文件名(可选)
如果API返回了Content-Disposition,前端可以自动解析文件名,不需要手动传入:
// 在获取http.Response后添加这段代码 String? apiFileName; final contentDisposition = r.headers['content-disposition']; if (contentDisposition != null) { final match = RegExp(r'filename="([^"]+)"').firstMatch(contentDisposition); if (match != null) { apiFileName = match.group(1); } } // 优先使用API返回的文件名,没有则用传入的参数 final useFileName = apiFileName ?? fileName; // 创建a标签时使用useFileName a.download = useFileName;
方案3:超大文件优化(可选)
如果后续涉及更大的文件,建议让API生成带有效期的临时签名URL,前端直接跳转该URL下载,完全绕开前端处理字节流,避免内存溢出问题。
额外注意事项
- 检查浏览器弹窗拦截设置,确保下载弹窗能正常弹出;
- 测试Chrome、Firefox、Safari等主流浏览器的兼容性;
- 避免在请求头中强制设置
Content-Type: application/octet-stream,让API返回实际的媒体类型(如audio/mpeg)。
内容的提问来源于stack exchange,提问作者markhorrocks
相关产品推荐
相关产品推荐

