一小时学会使用springboot操作阿里云OSS实现文件上传,下载,删除(附源码)

本文涉及的产品
对象存储 OSS,20GB 3个月
对象存储 OSS,恶意文件检测 1000次 1年
对象存储 OSS,内容安全 1000次 1年
简介: 一小时学会使用springboot操作阿里云OSS实现文件上传,下载,删除(附源码)

image.png

参考资料:Java操作阿里云OSS操作官方文档


学会看文档,并实际运用也是一种习惯和技能


下面就来简单入门一下,用当下比较热门的Springboot 去操作阿里云OSS文件存储。


1.需求

(没踩过下面的坑的小伙伴可以直接跳过这一章节)


问题简述


首先,我在之前自己做一些开源小项目案例中遇到一些文件上传下载的问题,比如在本机文件上传和下载都可以正常使用,通过将文件上传到Springboot项目的根目录下,按日期分文件夹,文件访问也很方便,可以直接返回文件相对路径地址,并直接可以访问。


问题


然而,这种方式存在弊端,因为当项目打包(jar包)部署阿里云学生机后,出现类似io.NotFoundException...(No Such Directory)的问题,,而如果打war包部署到tomcat则没问题,可以正常使用,经过排查很久,找出问题所在:

因为jar打包封装后是不能改变其内部目录结构的,也就是说,按日期分类的文件上传文件夹,如果当需要创建新日期的文件夹的时候,是无法在jar包中新增文件夹的,这时候就会出现IO异常问题。而对于放在tomcat中的war包,当tomcat运行的时候会自动解压war包,其在服务器上是存在真实路径的。


解决方案


方案一:我在网上找了一种方法,是通过打完jar包部署后,给springboot项目static下的文件上传文件夹单独分离出来(相当于是以相对路径换绝对路径),访问的时候直接相当通过服务器上和jar包同级目录下新建一个文件上传文件夹。

方案二:直接将文件上传到服务器指定路径下的文件上传位置,这种方式也相当于直接使用绝对路径。

方案三:在服务器上使用FastDFS和Nginx搭建分布式文件存储,这种方式比较复杂,而且学生及本来内存和带宽就小,在自己电脑的虚拟机可以试试这种方案,还是挺好用的,学生服务器就算了。

方案四:就是直接将文件上传到阿里云OSS文件存储系统上

2. 阿里云OSS购买和配置

这个比较简单,给大家推荐一篇博文自己了解下阿里云oss购买和配置,也可以参考阿里云OSS官方文档。


3. Springboot操作OSS

创建一个spring boot项目,pom文件需要引入依赖:


pom.xml

<dependencies>
    <!--
        个人版本踩坑:
        不加这个依赖的话,当在配置类中
        使用@ConfigurationProperties(prefix = "aliyun")注解时,
        我这个版本的spring boot会提示有问题
    -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-configuration-processor</artifactId>
        <optional>true</optional>
    </dependency>
    <!-- swagger2 -->
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger2</artifactId>
        <version>2.9.2</version>
    </dependency>
    <!-- swagger ui -->
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger-ui</artifactId>
        <version>2.9.2</version>
    </dependency>
    <!-- thymeleaf 可不加,个人习惯性引入 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <!-- web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- 热部署,看个人习惯 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <!-- 小辣椒插件,推荐使用,可以节省javaBean的setter/getter,还可以使用链式调用 -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <!-- fastJson -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.62</version>
    </dependency>
    <!-- aliyun-oos -->
    <dependency>
        <groupId>com.aliyun.oss</groupId>
        <artifactId>aliyun-sdk-oss</artifactId>
        <version>2.8.3</version>
    </dependency>
    <dependency>
        <groupId>joda-time</groupId>
        <artifactId>joda-time</artifactId>
        <version>2.10.1</version>
    </dependency>
    <!-- apache-common-lang3 -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>3.8.1</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>

image.png

我们使用自己添加的application-aliyun-oss.properties配置文件,去配置OSS相关信息,之所以不在application.yml 中配置,看个人习惯了,因为自定义的配置属性还是提出来配比较好,没必要所有的都配到application.yml(properties)中去。


application-aliyun-oss.properties


# 文件上传大小限制
spring.servlet.multipart.max-file-size=100MB
spring.servlet.multipart.max-request-size=1000MB
# 地域节点
aliyun.endPoint=oss-cn-beijing.aliyuncs.com
# Bucket 域名
aliyun.urlPrefix=http://csp-xxxx.oss-cn-beijing.aliyuncs.com/
# accessKey Id
aliyun.accessKeyId=LTAI4XXXXXXXzqD1saGFZ
# accessKey Secret
aliyun.accessKeySecret=2WjxNXXXXXXXX4f2bREc
# 你的Bucket名称
aliyun.bucketName=csp-xxxx
# 目标文件夹
aliyun.fileHost=files

config包下的相关配置类

AliyunOssConfig.java

/**
 * @Auther: csp1999
 * @Date: 2020/10/31/13:33
 * @Description: 阿里云 OSS 基本配置
 */
// 声明配置类,放入Spring容器
@Configuration
// 指定配置文件位置
@PropertySource(value = {"classpath:application-aliyun-oss.properties"})
// 指定配置文件中自定义属性前缀
@ConfigurationProperties(prefix = "aliyun")
@Data// lombok
@Accessors(chain = true)// 开启链式调用
public class AliyunOssConfig {
    private String endPoint;// 地域节点
    private String accessKeyId;
    private String accessKeySecret;
    private String bucketName;// OSS的Bucket名称
    private String urlPrefix;// Bucket 域名
    private String fileHost;// 目标文件夹
    // 将OSS 客户端交给Spring容器托管
    @Bean
    public OSS OSSClient() {
        return new OSSClient(endPoint, accessKeyId, accessKeySecret);
    }
}

Swagger2Config.java

/**
 * @Auther: csp1999
 * @Date: 2020/10/31/16:30
 * @Description: Swagger 配置类
 */
@Configuration
@EnableSwagger2// 开启swagger2
public class Swagger2Config {
    @Bean
    public Docket webApiConfig() {
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("webApi")
                .apiInfo(webApiInfo())
                .select()
                .paths(Predicates.not(PathSelectors.regex("/error.*")))
                .build();
    }
    private ApiInfo webApiInfo() {
        return new ApiInfoBuilder()
                .title("SpringBoot整合OSS-API文档")
                .description("阿里云OSS-文件上传下载测试")
                .version("1.0")
                .contact(new Contact("CSP", "https://blog.csdn.net/weixin_43591980", ""))
                .build();
    }
}

定义一个关于执行状态结果的枚举类

/**
 * @Auther: csp1999
 * @Date: 2020/10/31/17:03
 * @Description: 状态码枚举类
 */
public enum StatusCode {
    SUCCESS("success",200),ERROR("error",500);
    private String msg;
    private Integer code;
    StatusCode(String msg,Integer code){
        this.msg = msg;
        this.code = code;
    }
    StatusCode(Integer code){
        this.code = code;
    }
    StatusCode(String msg){
        this.msg = msg;
    }
    public String getMsg() {
        return msg;
    }
    public void setMsg(String msg) {
        this.msg = msg;
    }
    public Integer getCode() {
        return code;
    }
    public void setCode(Integer code) {
        this.code = code;
    }
}

service层

在service使用ossClient操作阿里云OSS,进行上传、下载、删除、查看所有文件等操作,同时可以将图片的url进行入库操作:

FileUploadService.java

/**
 * @Auther: csp1999
 * @Date: 2020/10/31/14:30
 * @Description: 文件上传Service (为节省文章中的代码篇幅,不再做接口实现类处理)
 */
@Service("fileUploadService")
public class FileUploadService {
    // 允许上传文件(图片)的格式
    private static final String[] IMAGE_TYPE = new String[]{".bmp", ".jpg",
            ".jpeg", ".gif", ".png"};
    @Autowired
    private OSS ossClient;// 注入阿里云oss文件服务器客户端
    @Autowired
    private AliyunOssConfig aliyunOssConfig;// 注入阿里云OSS基本配置类
    /*
     * 文件上传
     * 注:阿里云OSS文件上传官方文档链接:https://help.aliyun.com/document_detail/84781.html?spm=a2c4g.11186623.6.749.11987a7dRYVSzn
     * @param: uploadFile
     * @return: string
     * @create: 2020/10/31 14:36
     * @author: csp1999
     */
    public String upload(MultipartFile uploadFile) {
        // 获取oss的Bucket名称
        String bucketName = aliyunOssConfig.getBucketName();
        // 获取oss的地域节点
        String endpoint = aliyunOssConfig.getEndPoint();
        // 获取oss的AccessKeySecret
        String accessKeySecret = aliyunOssConfig.getAccessKeySecret();
        // 获取oss的AccessKeyId
        String accessKeyId = aliyunOssConfig.getAccessKeyId();
        // 获取oss目标文件夹
        String filehost = aliyunOssConfig.getFileHost();
        // 返回图片上传后返回的url
        String returnImgeUrl = "";
        // 校验图片格式
        boolean isLegal = false;
        for (String type : IMAGE_TYPE) {
            if (StringUtils.endsWithIgnoreCase(uploadFile.getOriginalFilename(), type)) {
                isLegal = true;
                break;
            }
        }
        if (!isLegal) {// 如果图片格式不合法
            return StatusCode.ERROR.getMsg();
        }
        // 获取文件原名称
        String originalFilename = uploadFile.getOriginalFilename();
        // 获取文件类型
        String fileType = originalFilename.substring(originalFilename.lastIndexOf("."));
        // 新文件名称
        String newFileName = UUID.randomUUID().toString() + fileType;
        // 构建日期路径, 例如:OSS目标文件夹/2020/10/31/文件名
        String filePath = new SimpleDateFormat("yyyy/MM/dd").format(new Date());
        // 文件上传的路径地址
        String uploadImgeUrl = filehost + "/" + filePath + "/" + newFileName;
        // 获取文件输入流
        InputStream inputStream = null;
        try {
            inputStream = uploadFile.getInputStream();
        } catch (IOException e) {
            e.printStackTrace();
        }
        /**
         * 下面两行代码是重点坑:
         * 现在阿里云OSS 默认图片上传ContentType是image/jpeg
         * 也就是说,获取图片链接后,图片是下载链接,而并非在线浏览链接,
         * 因此,这里在上传的时候要解决ContentType的问题,将其改为image/jpg
         */
        ObjectMetadata meta = new ObjectMetadata();
        meta.setContentType("image/jpg");
        //文件上传至阿里云OSS
        ossClient.putObject(bucketName, uploadImgeUrl, inputStream, meta);
        /**
         * 注意:在实际项目中,文件上传成功后,数据库中存储文件地址
         */
        // 获取文件上传后的图片返回地址
        returnImgeUrl = "http://" + bucketName + "." + endpoint + "/" + uploadImgeUrl;
        return returnImgeUrl;
    }
    /*
     * 文件下载
     * @param: fileName
     * @param: outputStream
     * @return: void
     * @create: 2020/10/31 16:19
     * @author: csp1999
     */
    public String download(String fileName, HttpServletResponse response) throws UnsupportedEncodingException {
//        // 设置响应头为下载
//        response.setContentType("application/x-download");
//        // 设置下载的文件名
//        response.addHeader("Content-Disposition", "attachment;fileName=" + fileName);
//        response.setCharacterEncoding("UTF-8");
        // 文件名以附件的形式下载
        response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
        // 获取oss的Bucket名称
        String bucketName = aliyunOssConfig.getBucketName();
        // 获取oss目标文件夹
        String filehost = aliyunOssConfig.getFileHost();
        // 日期目录
        // 注意,这里虽然写成这种固定获取日期目录的形式,逻辑上确实存在问题,但是实际上,filePath的日期目录应该是从数据库查询的
        String filePath = new DateTime().toString("yyyy/MM/dd");
        String fileKey = filehost + "/" + filePath + "/" + fileName;
        // ossObject包含文件所在的存储空间名称、文件名称、文件元信息以及一个输入流。
        OSSObject ossObject = ossClient.getObject(bucketName, fileKey);
        try {
            // 读取文件内容。
            InputStream inputStream = ossObject.getObjectContent();
            BufferedInputStream in = new BufferedInputStream(inputStream);// 把输入流放入缓存流
            ServletOutputStream outputStream = response.getOutputStream();
            BufferedOutputStream out = new BufferedOutputStream(outputStream);// 把输出流放入缓存流
            byte[] buffer = new byte[1024];
            int len = 0;
            while ((len = in.read(buffer)) != -1) {
                out.write(buffer, 0, len);
            }
            if (out != null) {
                out.flush();
                out.close();
            }
            if (in != null) {
                in.close();
            }
            return StatusCode.SUCCESS.getMsg();
        } catch (Exception e) {
            return StatusCode.ERROR.getMsg();
        }
    }
    /*
     * 文件删除
     * @param: objectName
     * @return: java.lang.String
     * @create: 2020/10/31 16:50
     * @author: csp1999
     */
    public String delete(String fileName) {
        // 获取oss的Bucket名称
        String bucketName = aliyunOssConfig.getBucketName();
        // 获取oss的地域节点
        String endpoint = aliyunOssConfig.getEndPoint();
        // 获取oss的AccessKeySecret
        String accessKeySecret = aliyunOssConfig.getAccessKeySecret();
        // 获取oss的AccessKeyId
        String accessKeyId = aliyunOssConfig.getAccessKeyId();
        // 获取oss目标文件夹
        String filehost = aliyunOssConfig.getFileHost();
        // 日期目录
        // 注意,这里虽然写成这种固定获取日期目录的形式,逻辑上确实存在问题,但是实际上,filePath的日期目录应该是从数据库查询的
        String filePath = new DateTime().toString("yyyy/MM/dd");
        try {
            /**
             * 注意:在实际项目中,不需要删除OSS文件服务器中的文件,
             * 只需要删除数据库存储的文件路径即可!
             */
            // 建议在方法中创建OSSClient 而不是使用@Bean注入,不然容易出现Connection pool shut down
            OSSClient ossClient = new OSSClient(endpoint,
                    accessKeyId, accessKeySecret);
            // 根据BucketName,filetName删除文件
            // 删除目录中的文件,如果是最后一个文件fileoath目录会被删除。
            String fileKey = filehost + "/" + filePath + "/" + fileName;
            ossClient.deleteObject(bucketName, fileKey);
            try {
            } finally {
                ossClient.shutdown();
            }
            System.out.println("文件删除!");
            return StatusCode.SUCCESS.getMsg();
        } catch (Exception e) {
            e.printStackTrace();
            return StatusCode.ERROR.getMsg();
        }
    }
}

controller层

controller提供测试接口

/**
 * @Auther: csp1999
 * @Date: 2020/10/31/16:40
 * @Description: OSS 文件上传controller
 */
@Api(description = "阿里云OSS文件上传、下载、删除API")
@RequestMapping("api/pri/file")
@RestController
public class OssFileController {
    @Autowired
    private FileUploadService fileUploadService;
    /*
     * 文件上传api
     * @param: file
     * @return: com.alibaba.fastjson.JSONObject
     * @create: 2020/10/31 17:35
     * @author: csp1999
     */
    @ApiOperation(value = "文件上传")
    @PostMapping("upload")
    public JSONObject upload(@RequestParam("file") MultipartFile file) {
        JSONObject jsonObject = new JSONObject();
        if (file != null) {
            String returnFileUrl = fileUploadService.upload(file);
            if (returnFileUrl.equals("error")) {
                jsonObject.put("error", "文件上传失败!");
                return jsonObject;
            }
            jsonObject.put("success", "文件上传成功!");
            jsonObject.put("returnFileUrl", returnFileUrl);
            return jsonObject;
        } else {
            jsonObject.put("error", "文件上传失败!");
            return jsonObject;
        }
    }
    /*
     * 文件下载api
     * @param: fileName
     * @param: response
     * @return: com.alibaba.fastjson.JSONObject
     * @create: 2020/10/31 17:35
     * @author: csp1999
     */
    @ApiOperation(value = "文件下载")
    @GetMapping(value = "download/{fileName}")
    public JSONObject download(@PathVariable("fileName") String fileName, HttpServletResponse response) throws Exception {
        JSONObject jsonObject = new JSONObject();
        String status = fileUploadService.download(fileName, response);
        if (status.equals("error")) {
            jsonObject.put("error", "文件下载失败!");
            return jsonObject;
        } else {
            jsonObject.put("success", "文件下载成功!");
            return jsonObject;
        }
    }
    /*
     * 文件删除api
     * @param: fileName
     * @return: com.alibaba.fastjson.JSONObject
     * @create: 2020/10/31 17:35
     * @author: csp1999
     */
    @ApiOperation(value = "文件删除")
    @GetMapping("/delete/{fileName}")
    public JSONObject DeleteFile(@PathVariable("fileName") String fileName) {
        JSONObject jsonObject = new JSONObject();
        String status = fileUploadService.delete(fileName);
        if (status.equals("error")) {
            jsonObject.put("error", "文件删除失败!");
            return jsonObject;
        } else {
            jsonObject.put("success", "文件删除成功!");
            return jsonObject;
        }
    }
}


4.运行项目测试API接口

本机访问:http://localhost:8083/swagger-ui.html

image.png

image.png

image.png

上传成功!

源码地址:Gitee代码仓库


  • 如果对大家有帮助,请三连支持一下!
  • 有问题欢迎评论区留言,及时帮大家解决!
  • image.png
相关实践学习
借助OSS搭建在线教育视频课程分享网站
本教程介绍如何基于云服务器ECS和对象存储OSS,搭建一个在线教育视频课程分享网站。
相关文章
|
3天前
|
Java 应用服务中间件 测试技术
深入探索Spring Boot Web应用源码及实战应用
【5月更文挑战第11天】本文将详细解析Spring Boot Web应用的源码架构,并通过一个实际案例,展示如何构建一个基于Spring Boot的Web应用。本文旨在帮助读者更好地理解Spring Boot的内部工作机制,以及如何利用这些机制优化自己的Web应用开发。
14 3
|
2天前
|
监控 Java 应用服务中间件
Spring Boot 源码面试知识点
【5月更文挑战第12天】Spring Boot 是一个强大且广泛使用的框架,旨在简化 Spring 应用程序的开发过程。深入了解 Spring Boot 的源码,有助于开发者更好地使用和定制这个框架。以下是一些关键的知识点:
18 6
|
5天前
|
前端开发 Java 关系型数据库
Java医院绩效考核系统源码B/S架构+springboot三级公立医院绩效考核系统源码 医院综合绩效核算系统源码
作为医院用综合绩效核算系统,系统需要和his系统进行对接,按照设定周期,从his系统获取医院科室和医生、护士、其他人员工作量,对没有录入信息化系统的工作量,绩效考核系统设有手工录入功能(可以批量导入),对获取的数据系统按照设定的公式进行汇算,且设置审核机制,可以退回修正,系统功能强大,完全模拟医院实际绩效核算过程,且每步核算都可以进行调整和参数设置,能适应医院多种绩效核算方式。
27 2
|
5天前
|
运维 监控 Java
springboot基层区域HIS系统源码
医疗(医院)机构正式使用云HIS系统之前,要先进行院内基础数据的配置,主要在数据管理模块中进行,由系统管理员来操作。
11 0
|
7天前
|
传感器 人工智能 前端开发
JAVA语言VUE2+Spring boot+MySQL开发的智慧校园系统源码(电子班牌可人脸识别)Saas 模式
智慧校园电子班牌,坐落于班级的门口,适合于各类型学校的场景应用,班级学校日常内容更新可由班级自行管理,也可由学校统一管理。让我们一起看看,电子班牌有哪些功能呢?
98 4
JAVA语言VUE2+Spring boot+MySQL开发的智慧校园系统源码(电子班牌可人脸识别)Saas 模式
|
15天前
|
缓存 Java 开发者
10个点介绍SpringBoot3工作流程与核心组件源码解析
Spring Boot 是Java开发中100%会使用到的框架,开发者不仅要熟练使用,对其中的核心源码也要了解,正所谓知其然知其所以然,V 哥建议小伙伴们在学习的过程中,一定要去研读一下源码,这有助于你在开发中游刃有余。欢迎一起交流学习心得,一起成长。
|
15天前
|
数据采集 SQL DataWorks
DataWorks操作报错合集之在阿里云DataWorks的数据同步任务中,过滤条件不生效,如何解决
DataWorks是阿里云提供的一站式大数据开发与治理平台,支持数据集成、数据开发、数据服务、数据质量管理、数据安全管理等全流程数据处理。在使用DataWorks过程中,可能会遇到各种操作报错。以下是一些常见的报错情况及其可能的原因和解决方法。
24 2
|
15天前
|
SQL DataWorks Java
DataWorks操作报错合集之在阿里云 DataWorks 中,代码在开发测试阶段能够成功运行,但在提交后失败并报错“不支持https”如何解决
DataWorks是阿里云提供的一站式大数据开发与治理平台,支持数据集成、数据开发、数据服务、数据质量管理、数据安全管理等全流程数据处理。在使用DataWorks过程中,可能会遇到各种操作报错。以下是一些常见的报错情况及其可能的原因和解决方法。
26 1
DataWorks操作报错合集之在阿里云 DataWorks 中,代码在开发测试阶段能够成功运行,但在提交后失败并报错“不支持https”如何解决
|
16天前
|
运维 Serverless PHP
Serverless 应用引擎产品使用之在阿里云Serverless中耗时操作执行如何解决
阿里云Serverless 应用引擎(SAE)提供了完整的微服务应用生命周期管理能力,包括应用部署、服务治理、开发运维、资源管理等功能,并通过扩展功能支持多环境管理、API Gateway、事件驱动等高级应用场景,帮助企业快速构建、部署、运维和扩展微服务架构,实现Serverless化的应用部署与运维模式。以下是对SAE产品使用合集的概述,包括应用管理、服务治理、开发运维、资源管理等方面。
22 0
|
16天前
|
安全 数据管理 数据库
数据管理DMS操作报错合集之阿里云DMS控制台上展示出了已经删除的数据库信息,如何解决
数据管理DMS(Data Management Service)是阿里云提供的数据库管理和运维服务,它支持多种数据库类型,包括RDS、PolarDB、MongoDB等。在使用DMS进行数据库操作时,可能会遇到各种报错情况。以下是一些常见的DMS操作报错及其可能的原因与解决措施的合集。