前端XMLHttpRequest、Fetch API、Axios实现文件上传、下载方法及后端Spring文件服务器处理方法

前言

本文总结Web应用开发中文件上传、下载的方法,即从前端表单输入文件并封装表单数据,然后请求后端服务器的处理过程;从基础的JavaScript中XmlHttpRequest对象、Fetch API实现上传、下载进行说明,并给出了前端常用的axios库的请求方式,然后给出了后端Spring实现文件服务器的方法,其中就文件上传中遇到的大小限制、跨域请求、外链请求等关键点进行了说明

上传文件

前端上传请求发起的方式:若不依赖其他库常使用XMLHttpRequest、Fetch API上传方式,若使用第三方库可用Axios请求上传

后端文件服务器的几种处理方式:

  • 文保存到磁盘目录

    文件存取请求直接根据url地址得出文件所在磁盘目录位置,访问速度快

  • 文件直接保存到数据库

    若文件比较大,影响数据库处理性能,但相比保存到磁盘目录的方法可以确保数据更安全可靠性

  • 文件二次请求上传至第三方服务器

    后端将接收的表单数据按照第三方接口封装进行请求上传

XMLHttpRequest文件上传

1.表单输入文件

html 复制代码
<!-- 设置multiple属性时可选择多个文件,可在读取文件时使用onchange监听读取到文件内容后预览、或立即上传,也可以单独用一个按钮事件控制上传 -->
<input type="file" multiple id="fileInput" onchange="onShowTextContent(event)"></input>
 <pre id="fileContent"></pre>
<button onclick="uploadFile()">上传文件</button>

预览文本内容并显示方法

html 复制代码
<script>
function onShowTextContent(event) { // 预览文本内容并显示方法
            const file = event.target.files[0];
            if (file && file.type.startsWith("text/plain") ) {
                const reader = new FileReader();
                // 定义文件读取完成后的处理逻辑
                reader.onload = function (e) {
                    const fileContent = e.target.result;
                    // 显示文本内容
                    document.getElementById('fileContent').textContent = fileContent;
                };
                // 读取文件为文本内容
                reader.readAsText(file);
            } else {
                console.error('No file selected');
            }
        }  
</script>

2.XMLHttpRequest上传文件

XMLHttpRequest对象简记为XHR,一个内置的浏览器对象,提供简单的方式来发送和接收从 URL 的数据而无需刷新整个页面,这使得它成为 AJAX(Asynchronous JavaScript and XML)编程的核心技术之一

AJAX是一种在 Web 应用中通过异步发送 HTTP 请求向服务器获取内容,并使用这些新内容更新页面中相关的部分,而无需重新加载整个页面的 Web 开发技术,这可以让网页更具有响应性,由于这种技术在现代Web开发中太过常见以至于AJAX这个术语很少被使用到

XMLHttpRequest具有广泛的兼容性,几乎所有浏览器都支持,包括老版本浏览器,而Fetch API 但在某些旧浏览器(如 IE)中不支持

js 复制代码
function uploadFile() {
    let fileInput = document.getElementById("fileInput");  // 获得input文件输入的引用
    let files = fileInput.files;  
    if (files.length === 0) {
        return;  // input节点的files是一个文件列表,若 files.length === 0 表示未选择文件,需要跳过
    }
    let formData = new FormData(); // 创建表单数据对象,封装文件
    for (let file of files) {
        //  file有一些属性常用于校验,如 file.name 文件名称,file.size 文件字节大小,file.type表示文件类型
        formData.append("files", file); 
    }
    let request = new XMLHttpRequest();   // 创建一个http请求
    let url = "http://127.0.0.1:7999/file/upload";
    request.open("POST", url, true);  // 参数1为请求方式,参数2为上传服务器地址,参数3取true表示异步
    request.onload = function () {
        if (request.status === 200) {
            console.log("上传完成:", request.responseText); 
        } else {
            console.log("上传出错:", request.statusText);
        }
    };
    request.onerror = function () {
        console.log("Error:", request.statusText);
    };
    request.send(formData); // 发送表单文件数据
}

3. 后端接收文件和保存

先对上传的文件大小、接收文件的类型做判断,然后将原始文件名称、大小、内容类型、存储位置等信息到数据库以提供分类、统计分析

Controller层接收上传的文件
java 复制代码
	// @RequestParam 注解标注表单的参数名,与form表单一致,若该注解缺省则默认参数名称为"multipart"
    // 入参 MultipartFile[] 数组表示接受多个文件,若写为 MultipartFile对象则只会接受第1个文件,cconsumes 指定匹配请求头 "content-type" 
    @PostMapping(value="/upload/list",consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
	public Map<String,Object> uploadFileList(@RequestParam("files") MultipartFile[] files);
Service层本地存储文件
java 复制代码
// controller层调用的文件保存方法,对 MultipartFile[] files 中每个对象处理,判断文件上传个数等
public Map<String,Object> saveMultipartFile(MultipartFile multipartFile) throws IOException {
        Map<String, Object> results = new HashMap<>();
        if (multipartFile.getSize() >= 1024 * 1024 * 10) {
            throw new RuntimeException("文件大小超过了最大限制10M");
        }
        // multipartFile.getContentType() 获取内容类型
        final String oriName = multipartFile.getOriginalFilename(); 
        // 原始文件名不能保证唯一性,需要生成一个唯一文件名,此处仅简单示例,实际开发中文件名称需要确保唯一性
        String newName = System.currentTimeMillis() + oriName.substring(oriName.lastIndexOf(".")); //
        String fullPath = System.getProperty("user.dir") + "/" + newName;
        final InputStream inputStream = multipartFile.getInputStream();
        File file = new File(fullPath);
        // 也可以直接使用 multipartFile.transferTo(file);
        if (file.createNewFile()) {
            FileCopyUtils.copy(inputStream, Files.newOutputStream(file.toPath()));
        }
        // 返回一些有用的信息,若是图片需要返回url等
        results.put("url","http://127.0.0.1:7999/file/"+newName); 
        return results;
    }
后端转发请求上传到第三方
java 复制代码
    public Result<FileUploadResponse> uploadImageForward(MultipartFile multipartFile) throws IOException {
        MultiValueMap request = new LinkedMultiValueMap(1);
        ByteArrayResource byteArrayResource = new ByteArrayResource(multipartFile.getBytes()) {
            @Override   // 需要覆盖默认的方法,不然无法获取名称
            public String getFilename() {
                return multipartFile.getOriginalFilename();
            }
        };
        // 此处从multipartFile获取byte[],如果是上传本地文件可以使用io获取byte[]
        request.add("fileName", byteArrayResource);
        String uploadUrl = this.serverBaseUrl+"/upload"; // 假如这是第三方文件上传地址
        FileUploadResponse response = restTemplate.postForObject(uploadUrl, request, FileUploadResponse.class);  
        return new Result<>(response);
    }

4.文件上传大小配置

在 Spring Boot 项目中,可通过配置文件或编程方式来设置文件上传的大小

配置文件方式(application.properties 或 application.yml)

application.properties

properties 复制代码
# 设置单个文件的最大大小为10MB(默认情况下为1MB)
spring.servlet.multipart.max-file-size=10MB
# 设置整个请求的最大大小为10MB(默认情况下是10MB)
spring.servlet.multipart.max-request-size=10MB

application.yml

yaml 复制代码
spring:
  servlet:
    multipart:
      max-file-size: 10MB
      max-request-size: 10MB
编程方式

可进行更细粒度的控制,可以通过编程配置 MultipartConfigElement实现

java 复制代码
@Configuration
public class ServletMultipartConfig {

     // 配置文件中添加示例:spring.server.MaxFileSize=1MB
    @Value("${spring.server.maxFileSize}")
    private DataSize maxFileSize;
    
    // 指定单个文件最大尺寸,如果超过这个大小,后端将不会接受该请求,前端会手到错误:Code	Details  Error: Request Entity Too Large
    @Bean
    public MultipartConfigElement multipartConfigElement() {
        // log.info("MaxFileSize="+maxFileSize.toMegabytes());
        MultipartConfigFactory factory = new MultipartConfigFactory();
        factory.setMaxFileSize(maxFileSize); // 但个文件最大大小
        factory.setMaxRequestSize(maxFileSize); // 整个请求的最大大小
        return factory.createMultipartConfig();
    }
}

5.后端跨域请求配置

文件上传在跨域的情况下会上传失败,因此有必要设置服务器的跨域请求规则

springboot的配置类中添加跨域请求配置,以拦截器方式实现为例:

java 复制代码
@Configuration
public class WebConfig implements WebMvcConfigurer { 
    @Bean
    public HandlerInterceptor accessInterceptor() {
        return new HandlerInterceptor(){
            @Override
            public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
                // 若请求头设置了Access-Control-Allow-Credentials为true,那"Access-control-Allow-Origin不能使用通配符*,而必须要使用确切的值
                response.setHeader("Access-control-Allow-Origin", request.getHeader("Origin"));
                response.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE,HEAD,PATCH");
                // 若前端传送了自定义的请求头而没有在这里面设置,某些情况下则会提示跨域请求错误:not allowed by Access-Control-Allow-Headers in preflight response.
                response.setHeader("Access-Control-Allow-Headers", "x-requested-with,Authorization");
                response.setHeader("Access-Control-Allow-Credentials", "true"); // 前端请求设置credentials: 'include', 这里就必须设置为true
                response.setHeader("Access-Control-Max-Age", "3600");
                // 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
                if (request.getMethod().equals(RequestMethod.OPTIONS.name())) {
                    response.setStatus(HttpStatus.OK.value());
                    return false;
                }
                return true;
            }
        };
    }
    @Override
    public void addInterceptors(InterceptorRegistry registry) { // 添加拦截器
        registry.addInterceptor(this.accessInterceptor())
            .excludePathPatterns("/error")
            .excludePathPatterns("/webjars/**", "/v2/api-docs/**");
    }
}

与上述拦截器实现的效果一样,也可以采用过滤器实现跨域请求配置:

java 复制代码
   @Bean // 注册过滤器
    public FilterRegistrationBean<CustomCorsFilter> customCorsFilterFilterRegistrationBean(){
        FilterRegistrationBean<CustomCorsFilter> registrationBean = new FilterRegistrationBean<>(this.customCorsFilter());
        registrationBean.setOrder(-1); // 设置序号,越小优先级越高越靠前执行,确保在其他业务处理之前执行到
        registrationBean.addUrlPatterns("/*");
        return registrationBean;
    }
    @Bean // 确保过滤器被加入到ioc容器
    public CustomCorsFilter customCorsFilter(){
        return new CustomCorsFilter();
    }
   // 实现一个过滤器,继承Filter接口,覆盖doFilter方法
   public static class CustomCorsFilter implements Filter{
       @Override
       public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
           HttpServletResponse response = (HttpServletResponse) servletResponse;
           HttpServletRequest request = (HttpServletRequest) servletRequest;
           response.setHeader("Access-control-Allow-Origin", request.getHeader("Origin"));
           response.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE,HEAD,PATCH");
           // 若前端传送了自定义的请求头而没有在这里面设置,某些情况下则会提示跨域请求错误:not allowed by Access-Control-Allow-Headers in preflight response.
           response.setHeader("Access-Control-Allow-Headers", "x-requested-with,Authorization");
           response.setHeader("Access-Control-Allow-Credentials", "true"); // 前端请求设置credentials: 'include', 这里就必须设置为true
           response.setHeader("Access-Control-Max-Age", "3600"); // 设置 Access-Control-Max-Age 为 3600,表示预检请求的结果可以缓存 3600 秒(即 1 小时),在此期间浏览器不需要为相同的跨域请求再次发送预检请求
           filterChain.doFilter(servletRequest,servletResponse);
       }
   }

Axios文件上传

Axios是一个基于promise的http库,可以用于Node.js中,可兼容所有主流浏览器,该库提供了简洁的API用于执行Http请求,可拦截请求和响应,转换请求和响应请求数据等

引入axios库,然后使用axios提供的方法进行文件上传,后端接收文件的方法与上述一致

html 复制代码
<!-- 引入axios库,实际开发中建议用 npm install axios 安装到本地 -->
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
function uploadFileByAxios() {
            const files = document.getElementById('fileInput').files;
            const formData = new FormData();
            for (let i = 0; i < files.length; i++) {
                formData.append('files', files[i]);
            }
    		let httpHeaders = {};  
     		// 表单Content-Type可以不用设置,默认就是 httpHeaders['Content-Type'] = 'multipart/form-data';
            // let uploadUrl = "http://127.0.0.1:7999/file/upload";
            axios.post(uploadUrl, formData, { headers: httpHeaders }) 
                .then(response => {
                    console.log('Files uploaded successfully:', response.data);
                })
                .catch(error => {
                    console.error('Error uploading files:', error);
                });
        }
 </script>

Fetch API实现文件上传

Fetch API 是一个用于现代Web应用执行网络请求的内置接口,它在XMLHttpRequest 之后出现

Fetch API 基于 Promise,可以使用链式结构来处理请求和响应,它提供了一种更简单、更灵活的方式来进行异步请求

js 复制代码
function uploadFilesByFetch() {
    let files = document.getElementById('fileInput').files;
    let formData = new FormData();
    for (let i = 0; i < files.length; i++) {
        formData.append('files', files[i]);
    }
    let uploadUrl = uploadUrl = "http://127.0.0.1:7999/file/upload";
    fetch(uploadUrl, {
        method: 'POST',
        body: formData
    })
        .then(response => {
        if (!response.ok) {
            throw new Error('Network response was not ok ' + response.statusText);
        }
        return response.json(); 
    })
        .then(data => {
        console.log('Files uploaded successfully:', data);
    })
        .catch(error => {
        console.error('Error uploading files:', error);
    });
}

下载文件

下载文件包括交互事件请求下载、超链接下载

XMLHttpRequest文件下载

1.下载按钮事件

html 复制代码
<div>
    <label>下载地址:</label>
    <input type="text" id="downloadUrl" value="http://127.0.0.1:7999/file/download/filename"></input>
</div>
<button onclick="downloadFileByXHR()">XMLHttpRequest下载</button>
<!-- 可以隐藏一个a标签,通过其他按钮事件触发 -->
<button onclick="downloadLink()">超链接下载方式</button><a id="link" style="display: none;"></a>
<!-- 超链接下载方法:直接指定带有href属性的a标签点击即可下载 -->
<a href="http://127.0.0.1:7999/file/download/24071411085841900.txt" download="filename.txt">点击下载文件</a>

2.XMLHttpRequest下载文件

关键是设置responseType,以及对响应数据二进制数据处理

js 复制代码
function downloadFileByXHR() {
    let downloadUrl = document.getElementById("downloadUrl").value; // 下载的url地址
    // let downloaduUrl = "http://192.168.95.59:7999/download/file/"+fileName;
    let request = new XMLHttpRequest();
    request.open("GET", downloadUrl, true);
    // 响应被处理为 Blob 对象,可以通过 `xhr.response` 访问响应数据,适用于处理二进制数据,如文件、图像等
    request.responseType = "blob";
    request.onload = function () {
        if (request.status === 200) {
            let contentType = request.getResponseHeader("Content-Type");
            //验证响应响应数据,若异常则响应 Content-Type 为 json 认定为下载失败
            if (contentType && contentType.startsWith('application/json')) {
                const reader = new FileReader();
                reader.onload = function () {
                    const text = reader.result; // 下载出错时后端返回的错误信息
                    console.log("text:", text);
                };
                reader.readAsText(request.response); // 尝试将blob数据读取为文本数据
                return;
            }
            let disposition = request.getResponseHeader("Content-Disposition");
            // Content-Disposition: attachment; filename=24070701440188300.png
            const data = new Blob([request.response], { type: request.getResponseHeader("Content-Type") });
            const url = window.URL.createObjectURL(data);
            const link = document.createElement("a");
            link.href = url;
            let fileName = getFileName(disposition);
            if (fileName !== "") {
                link.download = fileName;
            }
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
            window.URL.revokeObjectURL(url); // 释放内存
            link.remove();
        }
    };
    request.onerror = function () {
        console.low("error:", request.responseText);
    };
    request.send();
}

链接下载方法

js 复制代码
// 设置隐藏表单下载链接,点击即可下载,这种方法不够友好,若后端错误了前端直接暴露原始错误信息
function downloadLink() {
    console.log("链接下载");
    let link = document.getElementById("link");
    link.href = "http://127.0.0.1:7999/file/download/24071415521018500.txt";
    // link.download = "test.txt"; // 可指定下载后的名称
    link.click();
}

3.后端文件下载处理

Controller层接收文件下载请求

	@ApiOperation(value = "下载文件")
	@RequestMapping(value = "/download/{fileName}", method = RequestMethod.GET)
	ResponseEntity<byte[]> getDownloadFileResponse(@PathVariable("fileName") String fileName);

Service层读取文件

java 复制代码
// 给前端响应一个字节数组,使用ResponseEntity封装以便设置HttpHeaders   
public ResponseEntity<byte[]> getDownloadFileResponse(String fileName) {
        byte[] imageBytes = this.getLocalFile(fileName); // 获取文件的字节数组
        String contentType = getFileMediaType(fileName).toString();
        try {
            fileName = URLEncoder.encode(fileName, "UTF-8"); // 中文字符串编码处理避免前端收到乱码
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
        HttpHeaders headers = new HttpHeaders();
        // headers.add("Custom-Action", "download");
        headers.add(HttpHeaders.CONTENT_TYPE, contentType); // 设置Content-Type
        headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + fileName); // 设置附件,前端点击超链接下载时该名称将作为下载的文件名称
        headers.add("Access-Control-Expose-Headers", "Content-Type,Content-Disposition,Custom-Action");
        return new ResponseEntity<>(imageBytes, headers, HttpStatus.OK);
    }
  
  // 读取磁盘指定文件为字节数组
  public byte[] getLocalFile(String fileName) {
        File file = new File(localFileProperties.getPath() + "/" + fileName); // 服务器文件位置
        FileInputStream inputStream = null;
        try {
            inputStream = new FileInputStream(file);
            byte[] bytes = new byte[inputStream.available()];
            inputStream.read(bytes, 0, inputStream.available());
            return bytes;
        } catch (IOException e) {
            e.printStackTrace();
            log.error("获取文件失败:",e);
            throw new RuntimeException("找不到指定的文件");
        }
    }
    // 根据文件名后缀确定图片的 MIME 类型
    public MediaType getFileMediaType(String imageName) {
        String fileExtension = imageName.substring(imageName.lastIndexOf(".") + 1).toLowerCase();
        switch (fileExtension) {
            case "jpeg":
            case "jpg":
                return MediaType.IMAGE_JPEG;
            case "png":
                return MediaType.IMAGE_PNG;
            case "gif":
                return MediaType.IMAGE_GIF;
            case "bmp":
                return MediaType.parseMediaType("image/bmp");
            default:
                return MediaType.APPLICATION_OCTET_STREAM; // 默认为二进制流
        }
    }

4.后端外链请求方法

Controller层接收一个下载外链的请求

java 复制代码
@ApiOperation(value = "第三方外链下载") 
@RequestMapping(value = "/outlink/download", method = RequestMethod.GET)
ResponseEntity<byte[]> getDownloadOutlinkResponse(@RequestParam("url") String url);

Service层读取第三方服务器的文件

这里为啥前端不直接访问第三方文件服务器,因为在一些业务场景中,前端可能没有权限访问直接访问、直接访问不安全或者在对第三方外链的访问情况做分析统计时需要

java 复制代码
    public ResponseEntity<byte[]> downloadOutLink(String fileUrl) {
        log.info("downloadOutLink fileUrl:{}", fileUrl);
        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<byte[]> responseEntity = restTemplate.exchange(fileUrl, HttpMethod.GET, null, byte[].class);
        byte[] bytes = responseEntity.getBody();
        log.info("return entity...");
        HttpHeaders oldHeaders = responseEntity.getHeaders();
        oldHeaders.entrySet().forEach(entry -> {
            log.info("headName:{}, headValue:{}", entry.getKey(), entry.getValue());
        });
        String contentDisposition = oldHeaders.getFirst(HttpHeaders.CONTENT_DISPOSITION);
        String contentType = oldHeaders.getFirst(HttpHeaders.CONTENT_TYPE);
        HttpHeaders responseHeaders = new HttpHeaders(); 
        //  新创建一个 responseHeaders 用于设置返回前端的请求头,此处没有直接使用oldHeaders,因为这样可能会暴露出现某些不被允许的请求头以至于出错
        if (contentDisposition != null && !Objects.equals("", contentDisposition)) {
            responseHeaders.add(HttpHeaders.CONTENT_DISPOSITION, contentDisposition);
        } else {
            // 获取一个文件扩展名
            String fileSuffix = "";
            if(fileUrl.contains(".")){
                String suffix = fileUrl.substring(fileUrl.lastIndexOf(".")+1).toLowerCase();
                if(suffix.length()>0  && suffix.length()<5){
                    fileSuffix = suffix;
                }
            }
            if(Objects.equals(fileSuffix,"")){
                fileSuffix = "download";
            }
            String fileName = System.currentTimeMillis()+"."+fileSuffix;
            responseHeaders.add(HttpHeaders.CONTENT_DISPOSITION, String.format("attachment; filename=%s",fileName));
        }
        responseHeaders.add(HttpHeaders.CONTENT_TYPE, contentType);
        return ResponseEntity.ok()
                .headers(responseHeaders)
                .body(bytes);
    }

Fetch API下载文件

java 复制代码
function downloadFileByFetch() {
    // const url = 'http://127.0.0.1:7999/file/upload'; // 文件的URL
    let url = document.getElementById("downloadUrl").value;
    fetch(url)
        .then(async (response) => {
            console.log("response:", response);
            if (!response.ok) {
                throw new Error('Network response was not ok ' + response.statusText);
            }
            const contentType = response.headers.get('Content-Type');
            // 后端抛出异常时,返回json数据
            if(contentType && contentType.startsWith("application/json")){
                const jsonResponse = await response.json();
                if (jsonResponse.code === 1) {
                    // 后端返回了统一错误响应
                    console.error('Request failed with message:', jsonResponse.msg);
                    showInfo(JSON.stringify(jsonResponse));
                    return;
                }
            }
            console.log("contentType:",contentType);
            const disposition = response.headers.get('Content-Disposition');
            console.log("disposition:", disposition);
            let fileName = "unknown.download";
            if (disposition !== null) {
                const fileNameMatch = disposition.match(/filename="?([^"]+)"?/);
                    if (fileNameMatch.length === 2) {
                        fileName = fileNameMatch[1];
                    }
                fileName = fileName.replace(/\+/g, ' ');
                fileName = decodeURI(fileName);
            }
            response.blob().then(blob => {
                const url = window.URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.style.display = 'none';
                a.href = url;
                a.download = fileName; // 设置下载文件名
                document.body.appendChild(a);
                a.click();
                window.URL.revokeObjectURL(url); // 释放URL对象
            });

        })
        .catch(error => {
            console.error('error:', error);
        });
}

Axios下载文件

js 复制代码
const downloadFileByAxios = () => {
    // 使用 Axios 发起 GET 请求
    // let url = "http://192.168.95.59:7999/download/file/xxx";
    let url = document.getElementById("downloadUrl").value;
    let httpHeaders = {};
    // httpHeaders = { "Custom-Action": "RQ-download" };
    axios.get(url, { responseType: 'blob', headers: httpHeaders })
        .then(response => {
        // 请求成功处理
        console.log("收到下载数据:", response);
        // let res = response.data;
        handleAxiosDownloadResponse(response.data, response.headers);
    }).catch(error => {
        // 请求失败处理
        console.error('Error fetching posts:', error);
    });
}    

// 处理axios下载的响应文件
const handleAxiosDownloadResponse = async (data, headers) => {
    const contentType = headers['content-type'];
    // 后端返回的不是文件而是json对象,判断为异常
    if (contentType && contentType.startsWith("application/json")) {
        // data.text()返回一个Promise对象,因此要使用await,此时方法使用使用async关键字标识为异步方法
        let text = await data.text();
        console.log("text:", text);
        const jsonObj = JSON.parse(text);
        if (jsonObj.code === 1) {
            console.log("请求异常:", text);
        }
        showInfo(text);
        return;
    }
    const blob = new Blob([data], { type: headers['content-type'] });
    // 创建一个指向 Blob 对象的 URL
    const url = window.URL.createObjectURL(blob);
    // 创建一个 <a> 元素,并设置其 href 属性指向 Blob URL
    const a = document.createElement('a');
    a.href = url;
    // 从响应头或其他方式获取文件名
    const contentDisposition = headers['content-disposition'];
    console.log("content-disposition:", contentDisposition)
    let fileName = '';
    if (contentDisposition) {
        const fileNameMatch = contentDisposition.match(/filename="?([^"]+)"?/);
        if (fileNameMatch.length === 2) {
            fileName = fileNameMatch[1];
        }
        fileName = fileName.replace(/\+/g, ' ');  // 将符号+替换为空格
        fileName = decodeURI(fileName);
        // console.log("fileName:",fileName);
    }
    a.download = fileName; // 设置下载文件名
    // 将 <a> 元素添加到 DOM 并触发点击事件
    document.body.appendChild(a);
    a.click();
    // 移除 <a> 元素并释放 Blob URL
    document.body.removeChild(a);
    window.URL.revokeObjectURL(url);
}

Axios读取文件内容

js 复制代码
const readFileContentByAxios = () => {
    // 使用 Axios 发起 GET 请求
    // let url = "http://192.168.95.59:7999/download/file/xxx";
    let url = document.getElementById("downloadUrl").value;
    let httpHeaders = {};
    // responseType选项取值与XMLHttpRequest的responseType一样,指定为arraybuffer,表示响应被处理为 ArrayBuffer 对象,适用于处理原始二进制数据
    axios.get(url, { responseType: 'arraybuffer', headers: httpHeaders })
        .then(async (response) => {
        // 请求成功处理
        console.log("收到数据:", response);
        // let res = response.data;
        const text = new TextDecoder('utf-8').decode(new Uint8Array(response.data));
        console.log(text); 
    })
        .catch(error => {
        // 请求失败处理
        console.error('Error fetching posts:', error);
    });
}

参考资料

1.XMLHttpRequest(XHR)对象

2.Fetch API接口指南

3.Axios API中文文档

4.Spring Framework文档

相关推荐
GraduationDesign25 分钟前
基于SpringBoot的蜗牛兼职网的设计与实现
java·spring boot·后端
customer0840 分钟前
【开源免费】基于SpringBoot+Vue.JS安康旅游网站(JAVA毕业设计)
java·vue.js·spring boot·后端·kafka·开源·旅游
罗政4 小时前
PDF书籍《手写调用链监控APM系统-Java版》第10章 插件与链路的结合:SpringBoot环境插件获取应用名
java·spring boot·pdf
sin22014 小时前
springboot测试类里注入不成功且运行报错
spring boot·后端·sqlserver
kirito学长-Java5 小时前
springboot/ssm网上宠物店系统Java代码编写web宠物用品商城项目
java·spring boot·后端
键盘侠0075 小时前
springboot 上传图片 转存成webp
android·spring boot·okhttp
AI人H哥会Java6 小时前
【Spring】基于XML的Spring容器配置——<bean>标签与属性解析
java·开发语言·spring boot·后端·架构
计算机学长felix6 小时前
基于SpringBoot的“大学生社团活动平台”的设计与实现(源码+数据库+文档+PPT)
数据库·spring boot·后端
sin22016 小时前
springboot数据校验报错
spring boot·后端·python
大大怪将军~~~~8 小时前
SpringBoot 入门
java·spring boot·后端