您的位置:首页 > 娱乐 > 八卦 > 前端XMLHttpRequest、Fetch API、Axios实现文件上传、下载方法及后端Spring文件服务器处理方法

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

2024/10/5 20:25:35 来源:https://blog.csdn.net/qq_43521551/article/details/140420166  浏览:    关键词:前端XMLHttpRequest、Fetch API、Axios实现文件上传、下载方法及后端Spring文件服务器处理方法

前言

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

上传文件

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

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

  • 文保存到磁盘目录

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

  • 文件直接保存到数据库

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

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

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

XMLHttpRequest文件上传

1.表单输入文件

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

预览文本内容并显示方法

<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)中不支持

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层接收上传的文件
	// @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层本地存储文件
// 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;}
后端转发请求上传到第三方
    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

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

application.yml

spring:servlet:multipart:max-file-size: 10MBmax-request-size: 10MB
编程方式

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

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

5.后端跨域请求配置

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

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

@Configuration
public class WebConfig implements WebMvcConfigurer { @Beanpublic HandlerInterceptor accessInterceptor() {return new HandlerInterceptor(){@Overridepublic 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', 这里就必须设置为trueresponse.setHeader("Access-Control-Max-Age", "3600");// 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态if (request.getMethod().equals(RequestMethod.OPTIONS.name())) {response.setStatus(HttpStatus.OK.value());return false;}return true;}};}@Overridepublic void addInterceptors(InterceptorRegistry registry) { // 添加拦截器registry.addInterceptor(this.accessInterceptor()).excludePathPatterns("/error").excludePathPatterns("/webjars/**", "/v2/api-docs/**");}
}

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

   @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{@Overridepublic 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', 这里就必须设置为trueresponse.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提供的方法进行文件上传,后端接收文件的方法与上述一致

<!-- 引入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,可以使用链式结构来处理请求和响应,它提供了一种更简单、更灵活的方式来进行异步请求

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.下载按钮事件

<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,以及对响应数据二进制数据处理

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.pngconst 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();
}

链接下载方法

// 设置隐藏表单下载链接,点击即可下载,这种方法不够友好,若后端错误了前端直接暴露原始错误信息
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层读取文件

// 给前端响应一个字节数组,使用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-Typeheaders.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层接收一个下载外链的请求

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

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

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

    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下载文件

function downloadFileByFetch() {// const url = 'http://127.0.0.1:7999/file/upload'; // 文件的URLlet 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下载文件

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 对象的 URLconst url = window.URL.createObjectURL(blob);// 创建一个 <a> 元素,并设置其 href 属性指向 Blob URLconst 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 URLdocument.body.removeChild(a);window.URL.revokeObjectURL(url);
}

Axios读取文件内容

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文档

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com