尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

第28篇-CORS与文件上传

第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.WebMvcConfigurerConfigurationclassCorsConfig: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 生产环境 CORSConfigurationclassCorsConfig: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如果只有个别接口需要跨域RestControllerRequestMapping(/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# 单次请求最大 50MB2.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.UUIDRestControllerRequestMapping(/api/files)classFileController(// 从配置文件读取上传目录Value(\${mini-shop.upload-dir:uploads})privatevaluploadDir:String){PostMapping(/upload)funupload(RequestParam(file)file:MultipartFile,RequestParam(defaultValueproduct)type:String):ApiResponseMapString,String{// 1. 校验文件if(file.isEmpty){throwBusinessRuleException(文件不能为空)}valoriginalFilenamefile.originalFilename?:throwBusinessRuleException(文件名不能为空)// 2. 校验文件类型valallowedExtensionssetOf(jpg,jpeg,png,gif,webp)valextensionoriginalFilename.substringAfterLast(.,).lowercase()if(extension!inallowedExtensions){throwBusinessRuleException(不支持的文件类型.$extension)}// 3. 生成唯一文件名防止覆盖valnewFilename${type}/${UUID.randomUUID()}.$extension// 4. 创建目录并保存valtargetPath:PathPaths.get(uploadDir,newFilename)Files.createDirectories(targetPath.parent)// 5. 写入文件file.inputStream.use{input-Files.copy(input,targetPath,StandardCopyOption.REPLACE_EXISTING)}// 6. 返回文件信息returnApiResponse.success(mapOf(filenametonewFilename,originalNametooriginalFilename,sizetofile.size.toString(),urlto/uploads/$newFilename))}}2.3 上传测试curl-XPOST http://localhost:8080/api/files/upload\-Ffilekeyboard.jpg\-Ftypeproduct响应{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: attachmentFiles.copy()写入响应输出流浏览器触发下载GetMapping(/download/{filename})fundownload(PathVariablefilename:String,response:HttpServletResponse){valfilePath:PathPaths.get(uploadDir,filename)if(!Files.exists(filePath)){throwResourceNotFoundException(文件,filename)}valcontentTypeFiles.probeContentType(filePath)?:application/octet-streamresponse.contentTypecontentType 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 文档。如果本篇内容对你有帮助欢迎点赞收藏有任何疑问欢迎在评论区交流。
返回列表