Son aktivite 5 months ago

前端根据 Content-Type 响应类型决定下载文件或提醒

Revizyon a182ef954086dc925647e920bb9ae770a5329b2b

DemoController.java Ham
1import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
2import com.demo.common.reply.Result;
3import io.swagger.v3.oas.annotations.Operation;
4import io.swagger.v3.oas.annotations.Parameter;
5import io.swagger.v3.oas.annotations.enums.ParameterIn;
6import io.swagger.v3.oas.annotations.tags.Tag;
7import org.springframework.core.io.ClassPathResource;
8import org.springframework.core.io.Resource;
9import org.springframework.http.HttpHeaders;
10import org.springframework.http.ResponseEntity;
11import org.springframework.web.bind.annotation.GetMapping;
12import org.springframework.web.bind.annotation.RequestMapping;
13import org.springframework.web.bind.annotation.RestController;
14
15import java.net.URLEncoder;
16import java.nio.charset.StandardCharsets;
17
18/**
19 * 学生信息
20 **/
21@RestController
22@RequestMapping("api/student")
23@Tag(name = "学生信息")
24public class StudentController {
25
26 @GetMapping("download-image")
27 @Operation(summary = "下载")
28 @Parameter(name = "filename", description = "文件名称", in = ParameterIn.QUERY)
29 @ApiOperationSupport(order = 5)
30 public ResponseEntity<Object> download(String filename) {
31 Resource image = new ClassPathResource("static/" + filename);
32 if (!image.exists()) {
33 return ResponseEntity.ok(Result.error(404, "文件不存在"));
34 }
35 String fileName = URLEncoder.encode("网站图标.png", StandardCharsets.UTF_8);
36 HttpHeaders headers = new HttpHeaders();
37 headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + fileName);
38 headers.add(HttpHeaders.CONTENT_TYPE, "image/png");
39 return ResponseEntity.ok().headers(headers).body(image);
40 }
41}
Result.java Ham
1import io.swagger.v3.oas.annotations.media.Schema;
2import lombok.AllArgsConstructor;
3import lombok.Data;
4import lombok.NoArgsConstructor;
5
6/**
7 * 响应结果
8 **/
9@Data
10@AllArgsConstructor
11@NoArgsConstructor
12public class Result<T> {
13 /**
14 * 状态码
15 */
16 @Schema(description = "状态码 0-响应成功")
17 private int code;
18
19 /**
20 * 消息
21 */
22 @Schema(description = "消息")
23 private String msg;
24
25 /**
26 * 响应数据
27 */
28 @Schema(description = "响应数据")
29 private T data;
30
31 public static <T> Result<T> ok() {
32 return new Result<>(0, "ok", null);
33 }
34
35 public static <T> Result<T> ok(T data) {
36 return new Result<>(0, "ok", data);
37 }
38
39 public static <T> Result<T> error(Integer code, String msg) {
40 return new Result<>(code, msg, null);
41 }
42}
WebMvcConfig.java Ham
1import org.springframework.context.annotation.Configuration;
2import org.springframework.web.servlet.config.annotation.CorsRegistry;
3import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
4
5@Configuration
6public class WebMvcConfig implements WebMvcConfigurer {
7
8 @Override
9 public void addCorsMappings(CorsRegistry registry) {
10 registry.addMapping("/**")
11 .allowedOriginPatterns("*")
12 .allowCredentials(true)
13 .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
14 .exposedHeaders("Content-Disposition") // 跨域时要开启此响应头 否则前端获取不到
15 .maxAge(3600);
16 }
17}
18
demo.html Ham
1<!DOCTYPE html>
2<html lang="en">
3
4<head>
5 <meta charset="UTF-8">
6 <meta name="viewport" content="width=device-width, initial-scale=1.0">
7 <title>测试文件下载</title>
8</head>
9
10<body>
11 <button onclick="download('http://localhost:8080/api/file/download-image?filename=favicon.png')">下载文件</button>
12 <script>
13 /**
14 * 使用 Promise 链式写法下载文件
15 * 处理:1. URL编码的文件名 2. 200状态码下的JSON错误响应(文件不存在等)
16 * @param {string} url - 后端文件下载接口地址
17 * @returns {Promise<void>} - 无返回值,通过alert提示结果
18 */
19 function download(url) {
20 // 返回Promise对象,开启链式调用
21 return fetch(url, {
22 method: 'GET',
23 headers: {
24 'Accept': '*/*' // 允许接收文件(blob)和JSON两种响应
25 }
26 })
27 // 第一步:解析响应(先判断Content-Type,区分文件/JSON)
28 .then(response => {
29 const contentType = response.headers.get('Content-Type');
30 // 场景1:响应为JSON(即使状态码200,也可能是错误信息)
31 if (contentType && contentType.includes('application/json')) {
32 return response.json().then(jsonData => {
33 // 抛出包含JSON错误信息的异常,交给catch处理
34 throw new Error(JSON.stringify(jsonData));
35 });
36 }
37 // 场景2:响应为文件(非JSON),直接返回response供后续处理
38 return response;
39 })
40 // 第二步:处理文件下载(仅当响应为文件时执行)
41 .then(fileResponse => {
42 // 解析URL编码的文件名
43 const disposition = fileResponse.headers.get('Content-Disposition');
44 let fileName = 'downloaded-file'; // 默认文件名
45 console.log('Content-Disposition:', disposition);
46 if (disposition && disposition.includes('filename=')) {
47 // 提取filename=后的内容,移除引号并解码URL编码
48 fileName = decodeURIComponent(
49 disposition.split('filename=')[1].replace(/["']/g, '')
50 );
51 }
52
53 // 转换为Blob并触发下载
54 return fileResponse.blob().then(blob => {
55 if (window.navigator.msSaveOrOpenBlob) {
56 navigator.msSaveBlob(blob, filename)
57 } else {
58 const blobUrl = URL.createObjectURL(blob);
59 const a = document.createElement('a');
60 a.href = blobUrl;
61 a.download = fileName;
62 document.body.appendChild(a);
63 a.click();
64
65 // 清理临时资源(避免内存泄漏)
66 setTimeout(() => {
67 document.body.removeChild(a);
68 URL.revokeObjectURL(blobUrl);
69 }, 100);
70 }
71 });
72 })
73 // 第三步:统一捕获所有错误(网络错误、JSON错误、业务异常等)
74 .catch(error => {
75 try {
76 // 尝试解析JSON格式的错误信息(文件不存在等后端自定义错误)
77 const errorData = JSON.parse(error.message);
78 // 匹配"文件不存在"的错误码(code=404)
79 if (errorData.code === 404) {
80 alert(`下载失败:${errorData.msg || '文件不存在'}`);
81 } else {
82 alert(`下载失败:${errorData.msg || '未知错误'}`);
83 }
84 } catch (parseErr) {
85 // 非JSON错误(网络异常、解析失败等)
86 alert(`下载出错:${error.message || '请检查网络或接口地址'}`);
87 }
88 console.error('文件下载完整错误信息:', error);
89 });
90 }
91 </script>
92</body>
93
94</html>