news 2026/8/7 11:22:26

第28篇-CORS与文件上传

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
第28篇-CORS与文件上传

【Kotlin + Spring Boot 4 从零到架构师】第 28 篇:CORS 与文件上传

本系列定位:零基础入门,从 Kotlin 语法一路到 Spring Boot 4 高级架构(DDD + Modulith),适合 Java 开发者转型,也适合纯新手系统学习。


本篇你将学到

  • CORS 跨域问题的原因与解决方案
  • Spring Boot 全局 CORS 配置
  • 文件上传:MultipartFile
  • 文件下载:ResponseEntity+ 流

学完本篇,你将能为 mini-shop 添加跨域支持和商品图片上传功能。


一、CORS 跨域

下面是 CORS 跨域请求的完整流程示意图:

后端 API(localhost:8080)前端页面(localhost:3000)浏览器后端 API(localhost:8080)前端页面(localhost:3000)浏览器浏览器检测到端口不同 → 跨域检查响应头是否允许当前源alt[允许跨域][不允许跨域]用户访问页面发送跨域请求 (GET /api/products)响应 + Access-Control-Allow-Origin 头正常处理响应数据抛出 CORS 错误,阻止 JS 读取

1.1 什么是跨域

当浏览器的前端页面和后端 API 不在同一个「源」(协议+域名+端口)时,浏览器会阻止请求:

前端:http://localhost:3000 (Vite/Vue 开发服务器) 后端:http://localhost:8080 (Spring Boot) 协议相同(http)、域名相同(localhost)、端口不同(3000 vs 8080) → 浏览器判定为跨域 → 默认阻止请求

1.2 错误现象

浏览器控制台报错:

Access to XMLHttpRequest at 'http://localhost:8080/api/products' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

1.3 解决方案:全局 CORS 配置

packagecom.example.minishop.configimportorg.springframework.context.annotation.Configurationimportorg.springframework.web.servlet.config.annotation.CorsRegistryimportorg.springframework.web.servlet.config.annotation.WebMvcConfigurer@ConfigurationclassCorsConfig:WebMvcConfigurer{overridefunaddCorsMappings(registry:CorsRegistry){registry.addMapping("/api/**") // 对 /api/ 下所有接口生效 .allowedOrigins( // 允许的前端源 "http://localhost:3000", // Vue/Vite 开发服务器 "http://localhost:5173", // Vue 默认端口 "http://localhost:4200" // Angular 默认端口 ) .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") // 允许所有请求头 .allowCredentials(true) // 允许携带 Cookie .maxAge(3600) // 预检请求缓存 1 小时 } }

1.4 生产环境 CORS

@ConfigurationclassCorsConfig:WebMvcConfigurer{overridefunaddCorsMappings(registry:CorsRegistry){registry.addMapping("/api/**") // 生产环境只允许前端域名 .allowedOrigins( "https://www.mini-shop.com", "https://mini-shop.com" ) .allowedMethods("GET", "POST", "PUT", "DELETE") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600) } }

安全提示:不要在生产环境用allowedOrigins("*")+allowCredentials(true),这会被浏览器拒绝。如果允许任意域名,用allowedOriginPatterns("*")

1.5 单个接口的 CORS

如果只有个别接口需要跨域:

@RestController@RequestMapping("/api/products")@CrossOrigin(origins=["http://localhost:3000"])// 类级别classProductController{@GetMapping("/{id}")@CrossOrigin(origins=["http://localhost:5173"])// 方法级别fungetById(@PathVariableid:Long):Product{...}}

二、文件上传

下面是文件上传的完整处理流程图:

客户端发起上传请求

文件是否为空?

抛出 BusinessRuleException
文件不能为空

文件类型是否允许?

抛出 BusinessRuleException
不支持的文件类型

生成唯一文件名
UUID + 原始扩展名

创建目标目录
Files.createDirectories()

写入文件到磁盘
Files.copy()

返回文件信息
文件名/大小/URL

前端获取 URL
展示上传结果

2.1 配置上传限制

spring:servlet:multipart:enabled:truemax-file-size:10MB# 单个文件最大 10MBmax-request-size:50MB# 单次请求最大 50MB

2.2 文件上传接口

packagecom.example.minishop.controllerimportcom.example.minishop.exception.BusinessRuleExceptionimportcom.example.minishop.dto.ApiResponseimportorg.springframework.beans.factory.annotation.Valueimportorg.springframework.web.bind.annotation.*importorg.springframework.web.multipart.MultipartFileimportjava.nio.file.Filesimportjava.nio.file.Pathimportjava.nio.file.Pathsimportjava.nio.file.StandardCopyOptionimportjava.util.UUID@RestController@RequestMapping("/api/files")classFileController(// 从配置文件读取上传目录@Value("\${mini-shop.upload-dir:uploads}")privatevaluploadDir:String){@PostMapping("/upload")funupload(@RequestParam("file")file:MultipartFile,@RequestParam(defaultValue="product")type:String):ApiResponse<Map<String,String>>{// 1. 校验文件if(file.isEmpty){throwBusinessRuleException("文件不能为空")}valoriginalFilename=file.originalFilename?:throwBusinessRuleException("文件名不能为空")// 2. 校验文件类型valallowedExtensions=setOf("jpg","jpeg","png","gif","webp")valextension=originalFilename.substringAfterLast('.',"").lowercase()if(extension!inallowedExtensions){throwBusinessRuleException("不支持的文件类型:.$extension")}// 3. 生成唯一文件名(防止覆盖)valnewFilename="${type}/${UUID.randomUUID()}.$extension"// 4. 创建目录并保存valtargetPath:Path=Paths.get(uploadDir,newFilename)Files.createDirectories(targetPath.parent)// 5. 写入文件file.inputStream.use{input->Files.copy(input,targetPath,StandardCopyOption.REPLACE_EXISTING)}// 6. 返回文件信息returnApiResponse.success(mapOf("filename"tonewFilename,"originalName"tooriginalFilename,"size"tofile.size.toString(),"url"to"/uploads/$newFilename"))}}

2.3 上传测试

curl-XPOST http://localhost:8080/api/files/upload\-F"file=@keyboard.jpg"\-F"type=product"

响应:

{"code":200,"message":"success","data":{"filename":"product/550e8400-e29b-41d4-a716-446655440000.jpg","originalName":"keyboard.jpg","size":"245678","url":"/uploads/product/550e8400-e29b-41d4-a716-446655440000.jpg"}}

2.4 静态资源映射

上传的文件需要能被访问到,配置静态资源映射:

@ConfigurationclassWebConfig:WebMvcConfigurer{overridefunaddResourceHandlers(registry:ResourceHandlerRegistry){// 把 /uploads/** URL 映射到本地文件目录registry.addResourceHandler("/uploads/**") .addResourceLocations("file:uploads/") } }

三、文件下载

下面是文件下载的流程示意图:

客户端请求下载
/download/{filename}

文件是否存在?

抛出 ResourceNotFoundException

探测文件 ContentType

设置响应头
Content-Disposition: attachment

Files.copy()
写入响应输出流

浏览器触发下载

@GetMapping("/download/{filename}")fundownload(@PathVariablefilename:String,response:HttpServletResponse){valfilePath:Path=Paths.get(uploadDir,filename)if(!Files.exists(filePath)){throwResourceNotFoundException("文件",filename)}valcontentType=Files.probeContentType(filePath)?:"application/octet-stream"response.contentType=contentType response.setHeader("Content-Disposition","attachment; filename=\"$filename\"")Files.copy(filePath,response.outputStream)}

本篇小结

知识点核心内容
CORS浏览器安全策略,阻止跨源请求
全局 CORS 配置WebMvcConfigurer.addCorsMappings()
@CrossOrigin单个接口的跨域配置
allowedOrigins允许的前端域名
allowCredentials允许携带 Cookie
文件上传@RequestParam("file") MultipartFile
上传限制spring.servlet.multipart.max-file-size
文件名防覆盖UUID 生成唯一文件名
文件下载Files.copy(path, response.outputStream)
静态资源映射addResourceHandler("/uploads/**")

下篇预告

第 29 篇:SpringDoc OpenAPI 3 — API 文档

一行代码不写就能生成漂亮的 API 文档?下一篇集成 SpringDoc,为 mini-shop 生成交互式 API 文档。


如果本篇内容对你有帮助,欢迎点赞收藏!有任何疑问,欢迎在评论区交流。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/7 11:21:53

华为MateBook 14 2019款升级2TB NVMe SSD全攻略:从兼容性确认到系统迁移

1. 项目概述&#xff1a;为什么选择升级MateBook 14 2019款的存储&#xff1f; 手头这台华为MateBook 14 2019款&#xff0c;陪伴我度过了好几个年头。它经典的2K全面屏、轻薄的设计和够用的性能&#xff0c;至今在移动办公场景下依然能打。但时间久了&#xff0c;最大的瓶颈就…

作者头像 李华
网站建设 2026/8/7 11:20:15

rk3506 cpuinfo两个板卡一致问题

cat /proc/cpuinfo两个板卡一致 1.原因分析 先说结论&#xff1a;这个 Serial 不是芯片真实 ID&#xff0c;是 U-Boot 塞进设备树的一个固定值&#xff1b;内核里真正按芯片唯一 ID 计算序列号的代码被一个永远不成立的宏编译掉了。 原因分析 /proc/cpuinfo 的 Serial 有两个来…

作者头像 李华
网站建设 2026/8/7 11:18:32

浏览器缓存迁移实战:符号链接与启动参数优化系统盘空间

1. 项目概述&#xff1a;为什么我们需要移动浏览器缓存&#xff1f; 作为一名长期与各种浏览器打交道的IT从业者&#xff0c;我几乎每天都要处理浏览器相关的性能优化和磁盘空间告急问题。无论是开发调试、日常办公还是个人娱乐&#xff0c;浏览器缓存都是一个既爱又恨的存在。…

作者头像 李华
网站建设 2026/8/7 11:18:21

DLSS Swapper:游戏性能优化的智能管家,让每一帧都更流畅

DLSS Swapper&#xff1a;游戏性能优化的智能管家&#xff0c;让每一帧都更流畅 【免费下载链接】dlss-swapper 项目地址: https://gitcode.com/GitHub_Trending/dl/dlss-swapper 还在为游戏卡顿而烦恼吗&#xff1f;想象一下&#xff0c;当你投入心爱的游戏世界时&…

作者头像 李华