Spring Boot 文件上传与下载

news/2024/4/27 7:02:48/文章来源:https://blog.csdn.net/forlinkext/article/details/127669437

文件上传

上传到服务器的磁盘路径

@RestController
public class UpDownloadController{@RequestMapping("uploadFile")public String uploadFile(MultipartFile file) throws IOException {// 要保存到服务器的路径(根据实际需要修改)String remorePath = "/home/test";String fileName = file.getOriginalFilename();String filePath = remorePath+"/"+fileName;file.transferTo(new File(filePath));// 返回前端的文件访问地址(一般是http请求的地址,不要直接返回remorePath这样的磁盘路径)return remorePath; }
}

文件上传,前端需设置请求头为:multipart/form-data。

MultipartFile常用方法说明

String getName():获取文件对应的表单参数名称

String getOriginalFilename():获取文件的名称

InputStream getInputStream():获取文件的输入流。我们的示例是上传到服务器的磁盘,很多项目可能是上传到文件服务器等。可通过该输入流将文件数据对接到文件服务器的对应接口上。

byte[] getBytes():获取文件的数据

void transferTo(File dest):将文件输出到定义的 dest 文件

带其他参数的文件上传

    @RequestMapping("uploadFile")public String uploadFile(Param param,MultipartFile file) throws IOException {log.error("文件上传参数:{}",param);// 要保存到服务器的路径(根据实际需要修改)String remorePath = "/home/test";String fileName = file.getOriginalFilename();String filePath = remorePath+"/"+fileName;file.transferTo(new File(filePath));// 返回前端的文件访问地址(一般是http请求的地址,不要直接返回remorePath这样的磁盘路径)return remorePath;}

其他参数的获取和普通的Controller获取一样。

文件下载

    @RequestMapping("downloadFile/{param1}/{param2}")public void downloadFile(@PathVariable Long param1, @PathVariable Long param2,String filePath, HttpServletResponse response) throws IOException {File f = new File("C:\\work\\test\\2022_11_01\\720513e0d488.txt");log.error("param1:{},param2:{},filePath:{}",param1,param2,filePath);// 下载为中文的文件名称String fileName = new String("测试下载".getBytes("UTF-8"),"ISO-8859-1") + ".txt";response.setContentType("application/force-download");// 设置强制下载不打开response.addHeader("Content-Disposition", "attachment;fileName=" + fileName);FileUtils.copyFile(f,response.getOutputStream());response.getOutputStream().close();}

注:fileName 如果直接使用汉字字符串,下载后文件名称变成了下划线。通过示例中的方式转换即可。

支持文件上传下载的配置说明

如果在 Spring MVC 下,需要配置 一个名称为 multipartResolver 的 MultipartResolver 类型的bean来支持文件上传。Spring-web包为其提供了两个实现CommonsMultipartResolver、StandardServletMultipartResolver

类名说明
CommonsMultipartResolver基于 Commons FileUpload 上仅适用于POST请求,但接受任何多部分/内容类型。
StandardServletMultipartResolver使用Servlet容器的多部分解析器,可能会使应用程序暴露于容器实现的差异。

如果要使用 CommonsMultipartResolver 需要引入 Commons FileUpload 依赖包。

spring boot 下,默认是可以不用什么配置,就支持文件的上传和下载的。我们来看看spring boot 的相关自动配置代码

@Bean(name = "multipartResolver")@ConditionalOnMissingBean(MultipartResolver.class)public StandardServletMultipartResolver multipartResolver() {StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver();multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily());return multipartResolver;}

说明Spring Boot 默认是使用的 StandardServletMultipartResolver。但其使用了条件注解,我们可以在我们的应用中使用 CommonsMultipartResolver 将其替换掉。

限制上传文件的大小

Spring Boot 自动配置中还有个类我们需要了解 MultipartProperties,文件上传的配置。其在application.properties 文件中的配置前缀为 spring.servlet.multipart

  • location:指定上载文件的存储目录。如果未指定,将使用临时目录。
  • max-file-size :指定上传文件允许的最大大小。默认值为1MB(意味着我们上传文件的默认最大大小为1MB)
  • max-request-size:指定多部分/表单数据请求允许的最大大小。默认值为10MB。
  • file-size-threshold :文件大小阈值指定文件将写入磁盘的大小阈值。默认值为0。
  • resolve-lazily:是否在文件或参数访问时延迟解析多部分请求

配置示例

# 上传文件的表单数据最大大小
spring.servlet.multipart.max-request-size= 20MB
# 上传文件的最大大小
spring.servlet.multipart.max-file-size= 5MB

常见问题解析

MaxUploadSizeExceededException 异常 @ControllerAdvice 标注的 ExceptionHandler 无法解析的问题

org.springframework.web.multipart.MaxUploadSizeExceededException: Maximum upload size exceeded; nested exception is java.lang.IllegalStateException: org.apache.tomcat.util.http.fileupload.impl.SizeLimitExceededException: the request was rejected because its size (336958720) exceeds the configured maximum (20971520)at org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.handleParseFailure(StandardMultipartHttpServletRequest.java:122)at org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.parseRequest(StandardMultipartHttpServletRequest.java:115)at org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.initializeMultipart(StandardMultipartHttpServletRequest.java:129)at org.springframework.web.multipart.support.AbstractMultipartHttpServletRequest.getMultipartFiles(AbstractMultipartHttpServletRequest.java:141)at org.springframework.web.multipart.support.AbstractMultipartHttpServletRequest.getFile(AbstractMultipartHttpServletRequest.java:88)at org.springframework.web.multipart.support.MultipartResolutionDelegate.resolveMultipartArgument(MultipartResolutionDelegate.java:108)at org.springframework.web.method.annotation.RequestParamMethodArgumentResolver.resolveName(RequestParamMethodArgumentResolver.java:166)at org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver.resolveArgument(AbstractNamedValueMethodArgumentResolver.java:108)at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:122)at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:179)at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:146)at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1067)at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963)at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)at javax.servlet.http.HttpServlet.service(HttpServlet.java:681)at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)at javax.servlet.http.HttpServlet.service(HttpServlet.java:764)at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:61)at org.apache.shiro.web.servlet.AdviceFilter.executeChain(AdviceFilter.java:108)at org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:137)at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:66)at org.apache.shiro.web.servlet.AdviceFilter.executeChain(AdviceFilter.java:108)at org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:137)at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:66)at org.apache.shiro.web.servlet.AbstractShiroFilter.executeChain(AbstractShiroFilter.java:450)at org.apache.shiro.web.servlet.AbstractShiroFilter$1.call(AbstractShiroFilter.java:365)at org.apache.shiro.subject.support.SubjectCallable.doCall(SubjectCallable.java:90)at org.apache.shiro.subject.support.SubjectCallable.call(SubjectCallable.java:83)at org.apache.shiro.subject.support.DelegatingSubject.execute(DelegatingSubject.java:387)at org.apache.shiro.web.servlet.AbstractShiroFilter.doFilterInternal(AbstractShiroFilter.java:362)at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)at com.yyoo.mybase.base.filter.CorsFilter.doFilter(CorsFilter.java:58)at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)at com.yyoo.mybase.base.filter.xss.XssFilter.doFilter(XssFilter.java:75)at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)at org.springframework.session.web.http.SessionRepositoryFilter.doFilterInternal(SessionRepositoryFilter.java:142)at org.springframework.session.web.http.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:82)at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197)at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:540)at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357)at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:382)at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:895)at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1732)at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.IllegalStateException: org.apache.tomcat.util.http.fileupload.impl.SizeLimitExceededException: the request was rejected because its size (336958720) exceeds the configured maximum (20971520)at org.apache.catalina.connector.Request.parseParts(Request.java:2974)at org.apache.catalina.connector.Request.getParts(Request.java:2834)at org.apache.catalina.connector.RequestFacade.getParts(RequestFacade.java:1098)at javax.servlet.http.HttpServletRequestWrapper.getParts(HttpServletRequestWrapper.java:361)at javax.servlet.http.HttpServletRequestWrapper.getParts(HttpServletRequestWrapper.java:361)at javax.servlet.http.HttpServletRequestWrapper.getParts(HttpServletRequestWrapper.java:361)at org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.parseRequest(StandardMultipartHttpServletRequest.java:95)... 81 common frames omitted
Caused by: org.apache.tomcat.util.http.fileupload.impl.SizeLimitExceededException: the request was rejected because its size (336958720) exceeds the configured maximum (20971520)at org.apache.tomcat.util.http.fileupload.impl.FileItemIteratorImpl.init(FileItemIteratorImpl.java:161)at org.apache.tomcat.util.http.fileupload.impl.FileItemIteratorImpl.getMultiPartStream(FileItemIteratorImpl.java:205)at org.apache.tomcat.util.http.fileupload.impl.FileItemIteratorImpl.findNextItem(FileItemIteratorImpl.java:224)at org.apache.tomcat.util.http.fileupload.impl.FileItemIteratorImpl.<init>(FileItemIteratorImpl.java:142)at org.apache.tomcat.util.http.fileupload.FileUploadBase.getItemIterator(FileUploadBase.java:252)at org.apache.tomcat.util.http.fileupload.FileUploadBase.parseRequest(FileUploadBase.java:276)at org.apache.catalina.connector.Request.parseParts(Request.java:2932)... 87 common frames omitted

由于我们Spring Boot 默认使用了容器的文件上传支持,所以此处的错误是 tomcat 容器报的,此处修改办法有两个。

1. 修改配置文件

# 上传文件的表单数据最大大小
spring.servlet.multipart.max-request-size= 20MB
# 上传文件的最大大小
spring.servlet.multipart.max-file-size= 5MB
# 设置tomcat的允许大小为-1,不限制
server.tomcat.max-swallow-size = -1
# 是否在文件或参数访问时延迟解析多部分请求
spring.servlet.multipart.resolve-lazily=true

2. 使用 CommonsMultipartResolver

  1. 添加依赖
<dependency><groupId>commons-fileupload</groupId><artifactId>commons-fileupload</artifactId><version>1.4</version>
</dependency>
  1. 配置 CommonsMultipartResolver

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_411649.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CART回归树算法

【题目1】 表1为拖欠贷款人员训练样本数据集,使用CART算法基于该表数据构造决策树模型,并使用表2中测试样本集确定剪枝后的最优子树。 表1 拖欠贷款人员训练样本数据集编号 房产状况 婚姻情况 年收(千元) 拖欠贷款1 是 单身 125 否2 否 已婚 100 否3 否 单身 70 否4 是 已婚…

一本通1064;奥运奖牌计数

#include <iostream> using namespace std; int main() {int n, Jin, Yin, Tong;int JinSum 0, YinSum 0, TongSum 0, sum;cin >> n;for (int i 1; i < n; i) // 循环n次{cin >> Jin >> Yin >> Tong; // 输入一天获得的金银铜牌数JinSum …

IR信息检索前沿梳理

1. 检索预训练 1.1 PROP: Pre-training with Representative Words Prediction for Ad-hoc Retrieval three types of pre-training tasks have been proposed including: Inverse Cloze Task (ICT): The query is a sentence randomly drawn from the passage and the docu…

全志F1C芯片参数对比,供查阅

F1C600特性介绍 组合32M DDR1&#xff0c;QFN编解码模式&#xff0c;生产音频核心板&#xff08;CPUNORWIFI&#xff09;在WIFI站下播放的功率约0.5W组合I2S、SPDIF、CODEC等多功能接口支持全格式音频解码芯片 F1C600参数介绍 中央处理器 ARM926EJ-S 内存 SIP DDR1 SD2.0…

月入18000,0基础转行软件测试,实现薪资翻倍我只用了135天

在没做测试之前&#xff0c;我一直是个没自信的人&#xff0c;因为工作不稳定&#xff0c;收入也不高。 大学毕业做了2年酒店管理&#xff0c;月入4000提成&#xff0c;还经常上夜班&#xff0c;熬人又伤身体&#xff0c;于是不想再做服务行业&#xff0c;就转行做了电销。这之…

本地数据库IndexedDB - 学员管理系统之列表管理(二)

IndexedDB是浏览器提供的本地数据库&#xff0c;它可以被网页脚本创建和操作。IndexedDB允许存储大量数据&#xff0c;提供查找接口&#xff0c;还能建立索引。这些都是LocalStorage或Cookie不具备的。就数据库类型而言&#xff0c;IndexedDB不属于关系型数据库&#xff08;不支…

使用VMware16克隆功能快速准备CentOS 7.9操作系统集群

记录&#xff1a;305 场景&#xff1a;使用VMware16克隆功能快速准备CentOS 7.9操作系统集群&#xff0c;主要内容&#xff1a;VMware16克隆功能功能使用、CentOS 7.9操作系统常用指令使用、制作本地yum源、安装JDK、配置集群NTP时间同步等。 版本&#xff1a; 虚拟机工具&a…

数据结构-难点突破(C++/Java详解实现串匹配算法KMP,next数组求法,KMP算法优化nextval数组)

文章目录1. 暴力匹配算法BF2. KMP算法next数组求法Java代码&#xff1a;C代码&#xff1a;KMP算法优化nextval数组1. 暴力匹配算法BF 在了解KMP算法前&#xff0c;就必须介绍串的暴力匹配算法&#xff08;BF算法&#xff09; BF算法&#xff0c;即暴力(Brute Force)算法&…

大赛征集令|首届“万应杯”低代码应用开发大赛报名开启啦!

探索&#xff0c;寻觅低码边界。 创新&#xff0c;做成未曾有人做过的事。 首届“万应杯”低代码应用开发大赛 报名正式启动啦&#xff01; 万元现金奖杯/证书项目转售收益 丰厚奖励&#xff0c;邀你来战&#xff01; 大赛时间 低码掘金&#xff0c;就在此时&#xff01; …

MySQL高级SQL语句(一)

MySQL高级SQL语句&#xff08;一&#xff09;MySQL高级SQL语句&#xff08;一&#xff09;一、高级SQL语句&#xff08;进阶查询&#xff09;1.1 select1.2 distinct1.3 where1.4 and 、or1.5 in1.6 between1.7 通配符1.8 like1.9 order by二、函数2.1 数学函数2.2 聚合函数2.3…

MSDC 4.3 接口规范(26)

MSDC 4.3 接口规范&#xff08;26&#xff09;7.4 组呼业务管理7.4.1 服务状态7.4.2 启动组呼业务7.4.2.1 接口函数7.4.2.2 先决条件7.4.2.3 说明7.4.2.4 调用流程7.4.2.4.1 启动组呼业务7.4.2.4.2 无法启动服务7.4.3 停止组呼服务7.4.3.1 接口函数7.4.3.2 先决条件7.4.3.3 说明…

SH-SSS丨《端到端音视频说话人日志网络》论文线上分享

SH Symposium Series on Speech (SH SSS 2022) SH SSS 是由语音之家打造的AI语音技术相关的前沿论文成果分享平台。 来自AI语音技术领域的优秀论文作者、专家学者&#xff0c;用最精炼的表达来解读最新的高质量论文。 分享的论文成果来自国内外顶级会议收录的优秀文章、前沿…

系统kafka不消费-topic问题

测试告诉说kafka的topic列表里面新加入了一个topic&#xff0c;然后就不消费数据了&#xff1b; 自己验证了一下&#xff0c;确实这样&#xff0c;如果去掉新的topic&#xff0c;数据就可以正常消费&#xff1b; 然后我查看定义发现&#xff0c;topicA是1个分区&#xff1b; …

段页式内存管理

文章目录分页、分段的优缺点分析段页式管理分段分页段页式管理的逻辑地址结构段页式存储的段表、页表的地址变换分页、分段的优缺点分析 分页管理它的缺点就是不方便按照逻辑块实现信息的共享和保护而分段管理&#xff0c;如果段长过大&#xff0c;为其分配很大的连续空间会很不…

WebDAV之葫芦儿·派盘+纸间书摘

纸间书摘 支持webdav方式连接葫芦儿派盘。 是专为喜欢做读书笔记的小伙伴量身打造的专属书摘app,不仅仅可以从别的app中导入图书,并且还能来帮助你选择性复制可以来轻松的搞定哦 所有功能完全免费,没有广告,不限制识别次数。 多种备份,本地备份和基于WebDAV协议的云端…

python基于PHP+MySQL的药店药品进销存管理系统

随着科技的发展,针对不同疾病的药品越来越多,不同的药品有不同的属性,用法用量等内容,如何让药店和医药公司更好的对药品进行管理,是很多人都在研究的问题,本系统就是在这样的一个基础上开发出来的 PHP药店药品进销存管理系统通过PHp&#xff1a;MySQL进行开发,主要完成了药店基…

狂神说java基础——面向对象编程

面向对象编程(oop) 1、什么是面向对象(00)面向过程:线性思维 面向对象:分类思维​ 本质:以类的方式组织代码,以对象的形式阻止(封装)数据三大特性:封装,继承,多态2、回顾方法的定义 方法的定义修饰符 返回值类型/** 修饰符 返回值类型 方法名(...){* 方法体* re…

Dropzone V4.5.1 for Mac 文件拖拽工具使用教程

简介 Dropzone 是一款Mac上的文件拖拽操作增强工具&#xff0c;这款软件可以让我们把大部分工作都通过拖拽来完成&#xff0c;比如保存文本、发送邮件、FTP上传、打开应用等等&#xff0c;只需要将文件拖拽到菜单栏上的窗口中即可&#xff0c;并且我们完全可以定制化这些操作&a…

移动测试Appium安装

移动测试Appium安装 一、环境搭建 1.Java sdk安装 并配置JAVA_HOME和PATH 2.Android SDK安装 &#xff08;1&#xff09;解压 &#xff08;2&#xff09;配置ANDROID_HOME和PATH 见教程&#xff1a;AndroidSDK下载及安装 Android SDK 下载安装及配置 3.虚拟机安装 这里下载的…