SpringMVC-文件上传

devtools/2025/3/17 12:12:07/

文章目录

  • 1. 前端表单
  • 2. 阿里云OSS
    • 2.1 什么是阿里云OSS
    • 2.2 实现阿里云OSS
      • 2.2.1 准备工作
      • 2.2.2 SpringBoot集成阿里云OSS
  • 3. 使用SpringCloud Alibaba快捷操作
  • 4. 前端直传
  • 5. 临时URL访问文件

  • 在实际的项目开发中,文件的上传和下载可以说是最常见的功能之一,例如图片、视频、音频等的上传和下载等。本文章将探讨在SpringMVC中如何将文件上传到服务器。

  • 实现文件上传服务,需要有存储的支持,那么我们的解决方案将以下几种:

    1. 直接将图片保存到服务器的磁盘目录中
      1. 优点:开发便捷,成本低
      2. 缺点:
        • 磁盘空间有限,扩容困难
        • 无法直接访问
        • 不安全,磁盘损坏,文件将会丢失
    2. 使用分布式文件系统进行存储
      1. 优点:容易实现扩容
      2. 缺点:开发复杂度稍大(有成熟的产品可以使用,比如:FastDFS,MinIO)
    3. 使用第三方的存储服务(例如OSS)
      1. 优点:开发简单,拥有强大功能,免维护(阿里云、华为云、腾讯云等)
      2. 缺点:付费

1. 前端表单

在SpringMVC中实现文件上传工作,前端表单需做如下操作:

  1. form表单的method属性必须设置为post,因为上传的文件可能会比较大。

  2. form表单的enctype(编码方式)必须设置为:multipart/form-data

  3. 至少提供一个type属性为file的input输入框。

    <form action="/upload" method="post" enctype="multipart/form-data">姓名: <input type="text" name="username"><br>年龄: <input type="text" name="age"><br>头像: <input type="file" name="image"><br><input type="submit" value="提交">
    </form>
    

2. 阿里云OSS

2.1 什么是阿里云OSS

阿里云对象存储OSS(Object Storage Service),是一款海量、安全、低成本、高可靠的云存储服务。使用OSS,您可以通过网络随时存储和调用包括文本、图片、音频和视频等在内的各种文件。

在这里插入图片描述

在我们使用了阿里云OSS对象存储服务之后,我们的项目当中如果涉及到文件上传这样的业务,在前端进行文件上传并请求到服务端时,在服务器本地磁盘当中就不需要再来存储文件了。我们直接将接收到的文件上传到oss,由 oss帮我们存储和管理,同时阿里云的oss存储服务还保障了我们所存储内容的安全可靠

在这里插入图片描述

2.2 实现阿里云OSS

在这里插入图片描述

2.2.1 准备工作

  1. 登录阿里云官网,注册阿里云账号:阿里云-计算,为了无法计算的价值。

  2. 通过控制台开通对象存储OSS服务。

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

  3. 在控制台左侧的Bucket列表,创建一个Bucket。

    在这里插入图片描述

    在这里插入图片描述

  4. 从右上角头像获取AccessKeyId和AccessKeySecret。

    在这里插入图片描述

  5. 在新创建的Bucket下方获取Endpoint(地域节点)。

    在这里插入图片描述

2.2.2 SpringBoot集成阿里云OSS

  1. 引入依赖:

    java"><dependency><groupId>com.aliyun.oss</groupId><artifactId>aliyun-sdk-oss</artifactId><version>3.17.4</version>
    </dependency>
    

    如果使用的是Java 9及以上的版本,则需要添加以下JAXB相关依赖。

    java"><dependency><groupId>javax.xml.bind</groupId><artifactId>jaxb-api</artifactId><version>2.3.1</version>
    </dependency>
    <dependency><groupId>javax.activation</groupId><artifactId>activation</artifactId><version>1.1.1</version>
    </dependency>
    <!-- no more than 2.3.3-->
    <dependency><groupId>org.glassfish.jaxb</groupId><artifactId>jaxb-runtime</artifactId><version>2.3.3</version>
    </dependency>
    
  2. 在配置文件中配置OSS相关信息:

    alioss:endpoint: https://oss-cn-beijing.aliyuncs.comaccess-key-id: xxxxxxxxxxxaccess-key-secret: xxxxxxxxxbucket-name: test-tlias-1
    
    java">@Data
    @Component
    @ConfigurationProperties(prefix = "aliyun.oss")
    public class AliOSSProperties {//节点private String endpoint;//身份IDprivate String accessKeyId ;//身份密钥private String accessKeySecret ;//存储空间private String bucketName;
    }
    

    在我们添加上注解后,会发现idea窗口上面出现一个红色警告:这个警告提示是告知我们还需要引入一个依赖:

    在这里插入图片描述

    java"><dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    </dependency>
    
  3. 阿里云OSS工具类:

    java">@Component
    public class AliOSSUtil {@Autowiredprivate AliOSSProperties aliOSSProperties;public String upload(MultipartFile multipartFile) throws IOException {//1.获取上传的文件的输入流InputStream inputStream = multipartFile.getInputStream();//2.避免文件覆盖String originalFilename = multipartFile.getOriginalFilename();String fileName = UUID.randomUUID() + originalFilename.substring(originalFilename.lastIndexOf("."));//3.上传文件到 OSSOSS ossClient = new OSSClientBuilder().build(aliOSSProperties.getEndpoint(),aliOSSProperties.getAccessKeyId(), aliOSSProperties.getAccessKeySecret());ossClient.putObject(aliOSSProperties.getBucketName(), fileName, inputStream);//4.文件访问路径String url = aliOSSProperties.getEndpoint().split("//")[0] + "//" + aliOSSProperties.getBucketName() + "." + aliOSSProperties.getEndpoint().split("//")[1] + "/" + fileName;//5.关闭ossClientossClient.shutdown();return url;}
    }
    
  4. 文件上传控制器:

    java">@RestController
    public class TestController {@Autowiredprivate AliOSSUtil aliOSSUtils;@PostMapping("/upload")public Result upload(MultipartFile image) throws IOException {//调用阿里云OSS工具类,将上传上来的文件存入阿里云String url = aliOSSUtils.upload(image);//将图片上传完成后的url返回,用于浏览器回显展示return Result.success(url);}
    }
    

    在这里插入图片描述

3. 使用SpringCloud Alibaba快捷操作

  • 之前我们是自己创建OSS对象来进行操作,实际上还可以整合SpringCloud Alibaba来更加方便地使用OSS。打开官方文档,找到OSS相关,在SpringBoot项目中引入OSS的starter。

    java"><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alicloud-oss</artifactId>
    </dependency>
    
  • 然后在yml配置文件中配置endpoint、accessKeyId、accessKeySecret。

    java">server:port: 30000
    spring:cloud:alicloud:oss:endpoint: oss-cn-hangzhou.aliyuncs.comaccess-key: ...secret-key: ...
    
  • 在测试代码中无需创建ossClient,只需要将其注入进来即可使用。

    java">@RunWith(SpringRunner.class)
    @SpringBootTest
    public class GulimallThirdpartyApplicationTests {// 注入ossClient@Autowiredprivate OSS ossClient;@Testpublic void testUploadFile() throws FileNotFoundException {// 上传文件流。InputStream inputStream = new FileInputStream("C:\\Users\\baobao\\Desktop\\guojia.jpg");ossClient.putObject("gulimall-baobao", "guojia222.jpg", inputStream);// 关闭OSSClientossClient.shutdown();}
    }
    

4. 前端直传

如果我们用之前的方式上传文件到OSS,那么后端需要先接收前端表单上传的文件,然后再通过SDK将文件上传到OSS。但是实际开发中不推荐这么做,因为这种方式使得文件流先从客户端到后端应用服务器,然后再由服务器转给OSS,相当于多了1次中转,十分浪费服务器带宽,当很多用户都在上传的时候对后端应用服务器的带宽以及处理压力很大。

我们可以采取的优化方式是:在浏览器提交上传Policy请求给服务器,服务器返回上传Policy和签名,将上传地址等重要信息告诉浏览器,这样就可以由浏览器在客户端直接完成上传到oss的操作,分担了服务器压力。

新建一个OssController,编写生成Policy和前面的方法,参考阿里云官方SDK文档。

java">@RestController
public class OssController {// 注入ossClient@Autowiredprivate OSS ossClient;// 从配置文件中获取accessKey、endpoint等必要信息@Value("${spring.cloud.alicloud.oss.endpoint}")private String endpoint;@Value("${spring.cloud.alicloud.access-key}")private String accessKey;@Value("${spring.cloud.alicloud.secret-key}")private String secretKey;@Value("${spring.cloud.alicloud.bucket-name}")private String bucketName;@RequestMapping("oss/policy")public Map<String, String> policy() {// 用户上传文件时指定的前缀,指定当前日期String date = DateTimeFormatter.ofPattern("yyyy-MM-dd").format(LocalDate.now());String dir = date + "/";// host的格式为 bucketname.endpointString host = "https://" + bucketName + "." + endpoint;try {long expireTime = 30;long expireEndTime = System.currentTimeMillis() + expireTime * 1000;Date expiration = new Date(expireEndTime);// PostObject请求最大可支持的文件大小为5 GB,即CONTENT_LENGTH_RANGE为5*1024*1024*1024。PolicyConditions policyConds = new PolicyConditions();policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);byte[] binaryData = postPolicy.getBytes("utf-8");String encodedPolicy = BinaryUtil.toBase64String(binaryData);String postSignature = ossClient.calculatePostSignature(postPolicy);Map<String, String> respMap = new LinkedHashMap<>();respMap.put("accessid", accessKey);respMap.put("policy", encodedPolicy);respMap.put("signature", postSignature);respMap.put("dir", dir);respMap.put("host", host);respMap.put("expire", String.valueOf(expireEndTime / 1000));// respMap.put("expire", formatISO8601Date(expiration));return respMap;} catch (Exception e) {// Assert.fail(e.getMessage());System.out.println(e.getMessage());return null;} finally {ossClient.shutdown();}}
}

然后注意由于我们自定义了bucketName也从yml配置文件中取,所以要在yml中定义:

server:port: 30000
spring:cloud:alicloud:oss:endpoint: oss-cn-hangzhou.aliyuncs.comaccess-key: ...secret-key: ...bucket-name: gulimall-baobao  # 配置bucket-name

5. 临时URL访问文件

之前我们对Bucket的读写权限设置为公共读,这样只要获取了文件的url,就可以在浏览器随意访问了,安全性不好。

一般建议是将Bucket的读写权限设置为私有,此时再通过文件url访问将会提示没有权限。

需要在url上附带一些参数,用这个附带参数的临时url才能访问到文件,而临时url过期后就无法再访问了。我们进入oss控制台,点击文件的详情就会显示临时url。

分析一下这个url的结构:

java">https://gulimall-baobao.oss-cn-hangzhou.aliyuncs.com/guojia.jpg?Expires=1616766718&OSSAccessKeyId=TMP.3KjvuWJovuqLjNppoo3D3jvDbZFbrfovBR39zhHkdmzrm6M9mdwJAkbc1Ffdhn77nyVWTi2PATz97t5zSKr2TDhoKiE1SY&Signature=jAVkArqRd8K2jFswLy9%2BzH62Emk%3D

可以看出其携带了3个参数:

  • Expires:临时url的过期时间,默认5分钟
  • OSSAccessKeyId:临时的accessKey
  • Signature:签名
    我们每点击一次详情,都会生成一个新的临时url,过期时间为点击后的5分钟,临时的accessKey都相同,但是签名不同。可以推测生成签名的参数中有过期时间

在我们自己的应用中,公共读的方式下,后端数据库只要保存文件上传后的url即可,前端需要展示图片的时候只要向后端请求获取图片url并交给图片组件即可。然而改成私有以后,前端如何向后端请求临时url并显示图片文件呢?此时后端接收到前端访问图片的请求,并获取数据库中图片的url后,不能直接返回给前端,需要先解析出图片文件在Bucket中的路径,然后根据bucketName、临时url超时时间、要访问文件在Bucket中的路径这3个参数生成一个临时的url返回给前端:

java">@Component
public class AliyunOssService {// Endpoint以杭州为例,其它Region请按实际情况填写。String endpoint = "oss-cn-hangzhou.aliyuncs.com";// 阿里云主账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM账号进行API访问或日常运维,请登录RAM控制台创建RAM账号。String accessKeyId = "...";String accessKeySecret = "...";String bucketName = "gulimall-baobao";// objectName即文件在Bucket中的路径public String getTempUrl(String objectName){// 创建OSSClient实例。OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);// 设置URL过期时间为1小时。Date expiration = new Date(System.currentTimeMillis() + 3600 * 1000);// 生成以GET方法访问的签名URL,访客可以直接通过浏览器访问相关内容。其中objectName即文件在Bucket中的路径URL url = ossClient.generatePresignedUrl(bucketName, objectName, expiration);// 关闭OSSClient。ossClient.shutdown();return url.toString();}
}
@SpringBootTest
class AliyunOssDemoApplicationTests {@Autowiredprivate AliyunOssService aliyunOssService;@Testvoid contextLoads() {String tempUrl = aliyunOssService.getTempUrl("guojia.jpg");System.out.println(tempUrl);}
}	

测试生成的临时url如下,生成的规则是http://bucketName.endPoint/要访问的文件在Bucket中的路径,后面携带超时时间、临时的accessKey、签名等参数。

如果要访问的文件位于Bucket中的某个文件夹下,获取临时url时传入的文件路径参数只需要带上文件夹即可,注意最外层文件夹前面不能带/

参考博客:阿里云对象存储Java-SDK实战


http://www.ppmy.cn/devtools/167817.html

相关文章

深度学习常用操作笔记

深度学习常用操作笔记 指令报错cannot import name Config from mmcvImportError: cannot import name print_log from mmcvImportError: cannot import name init_dist from mmengine.runnerWARNING: Retrying (Retry(total4, connectNone, readNone, redirectNone, statusNon…

Spring Validation参数校验

Spring Validation是Spring框架中用于数据校验的核心模块&#xff0c;通过注解简化数据校验逻辑。 1. 依赖引入&#xff08;SpringBoot项目&#xff09; Spring Boot项目&#xff1a;自动包含spring-boot-starter-validation <dependency><groupId>org.springfra…

正则表达式的基本应用以及查询工具

首先&#xff0c;是对于基本的正则表达式的应用以及部分介绍(见代码注释部分): 以javaScript为例 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-wi…

智能家居分享

因为最近沉迷智能家居&#xff0c;所以来给大家分享一些轻松改变生活体验的小家具 1&#xff1a; 智能门锁 出门忘记带钥匙是许多人都遇到过的尴尬事&#xff0c;智能门锁的出现完美解决了这个困扰。智能门锁采用指纹识别、密码、刷卡、手机等多种开锁方式&#xff0c;大大增…

神聖的綫性代數速成例題5. 矩陣運算的定義、轉置的性質、方陣多項式的概念

矩陣運算的定義&#xff08;補充&#xff09;&#xff1a;矩陣乘法&#xff1a;如前所述&#xff0c;設是矩陣&#xff0c;是矩陣&#xff0c;乘積的元&#xff0c;是矩陣。轉置的性質&#xff1a;若是矩陣&#xff0c;則。&#xff0c;其中和是同型矩陣。&#xff0c;為數。&a…

Ubuntu下安装后anaconda出现conda:command not found

今天在ubuntu系统上安装好anaconda之后&#xff0c;输入conda --version后遇到了如下问题 conda:command not found原因通常是由于anaconda的安装路径没有正常的被添加到系统的PATH环境变量下&#xff0c;解决步骤如下&#xff1a; 在终端中输入&#xff1a;echo $PATH观察输…

从被动响应到主动预见:智能可观测性技术的变革与实践

思维导图 一、引言 &#x1f303; 想象一下&#xff0c;在一个深夜 &#x1f319;&#xff0c;你的关键业务系统突然出现故障 &#x1f6a8;。传统情况下&#xff0c;你可能会收到大量不相关的告警 &#x1f4f1;&#x1f4ac;&#x1f4ac;&#x1f4ac;&#xff0c;然后花费…

奇安信面试题

《网安面试指南》https://mp.weixin.qq.com/s/RIVYDmxI9g_TgGrpbdDKtA?token1860256701&langzh_CN 5000篇网安资料库https://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247486065&idx2&snb30ade8200e842743339d428f414475e&chksmc0e4732df793fa3bf39…