SpringBoot 系列教程(九十九):SpringBoot整合阿里云OSS实现文件上传下载删除


点击左上方“Thinking曹”,勾选“设为星标”


一、前言

之所以写这篇文章呢? 是因为最近在做文件上传时遇到一个问题,就是我们在以前使用传统Spring+SpringMVC+Mybatis框架开发Web项目的时候,都是将项目打包生成一个War包,然后将War包丢到Tomcat服务器的webapp目录下,启动后将会解压改War包,创建一个war包名称的文件夹。而且项目中一些关于文件的上传都是存储到Tomcat下,按日期分类的文件上传文件夹,来存储上传的文件。

那么在Springboot项目中,通过将文件上传到Springboot项目的根目录下,按日期分文件夹,文件访问也很方便,可以直接返回文件相对路径地址,并直接可以访问。但是这种方式存在一种问题。

问题: 当SpringBoot项目打包成jar包部署到阿里云服务器上后,出现类似io.NotFoundException...(No Such Directory)的异常,而如果像以前一样,打成war包部署到Tomcat就不会存在这种问题。

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

二、解决方案

  • 方案一: 我在网上找了一种方法,是通过打完jar包部署后,给springboot项目static下的文件上传文件夹单独分离出来(相当于是以相对路径换绝对路径),访问的时候直接相当通过服务器上和jar包同级目录下新建一个文件上传文件夹。
  • 方案二: 直接将文件上传到服务器指定路径下的文件上传位置,这种方式也相当于直接使用绝对路径。
  • 方案三: 在服务器上使用FastDFS和Nginx搭建分布式文件存储,这种方式比较复杂,而且学生及本来内存和带宽就小,在自己电脑的虚拟机可以试试这种方案,还是挺好用的,学生服务器就算了。
  • 方案四: 就是直接将文件上传到阿里云OSS文件存储系统上。

这里我采用了第四种方式,购买了阿里云Oss文件存储服务器,直接将文件上传到阿里云OSS文件存储系统上,就可以解决前面遇到的问题。

三、SpringBoot整合OSS

1. pom.xml

创建SpringBoot项目,引入下方依赖信息。

   <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.11.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>springboot-aliyunOss</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-aliyunOss</name>
    <description>SpringBoot整合阿里云OSS实现文件上传,下载,删除功能</description>

    <properties>
        <java.version>1.8</java.version>
        <swagger.version>2.9.2</swagger.version>
        <swagger-ui.version>2.9.2</swagger-ui.version>
        <fastjson.version>1.2.62</fastjson.version>
        <aliyun-sdk-oss.version>2.8.3</aliyun-sdk-oss.version>
        <commons-lang3.version>3.8.1</commons-lang3.version>
        <knife4j.version>2.0.2</knife4j.version>
    </properties>

    <dependencies>
        <!-- processor -->
        <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>${swagger.version}</version>
        </dependency>
        <!-- swagger ui -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>${swagger-ui.version}</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>
        <!-- fastJson -->

        <!-- aliyun-oos -->
        <dependency>
            <groupId>com.aliyun.oss</groupId>
            <artifactId>aliyun-sdk-oss</artifactId>
            <version>${aliyun-sdk-oss.version}</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>${commons-lang3.version}</version>
        </dependency>

        <!-- lombok 代码简化工具-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- knife4j API文档工具 -->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <!--在引用时请在maven中央仓库搜索最新版本号-->
            <version>${knife4j.version}</version>
        </dependency>
    </dependencies>

2.application.yml

#端口
server:
  port: 17790

#aliyun oss配置
aliyun:
  accessKeyId: LTAI4XXXXXXXzqD1saFQS
  accessKeySecret: 2WjxNXXXXXXXX4f2bGNl
  bucketName: csp-xxxx
  endPoint: oss-cn-beijing.aliyuncs.com
  fileHost: files
  urlPrefix: http://csp-xxxx.oss-cn-beijing.aliyuncs.com/
spring:
  servlet:
    multipart:
      max-file-size: 100MB
      max-request-size: 1000MB

# swagger开关(开发、测试开启,生成关闭)
swagger:
  isShow : true

3. 阿里云OSS参数配置类

package com.example.oss.config;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClient;
import lombok.Data;
import lombok.experimental.Accessors;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.Serializable;

/**
 * @desc: 阿里云OSS参数配置
 * @author: cao_wencao
 * @date: 2020-11-06 15:02
 */

//声明配置类,注入Spring容器
@Configuration
//指定读取的配置文件位置
//@PropertySource(value = {"classpath:application.yml"})
//指定配置文件中自定义属性前缀
@ConfigurationProperties(prefix = "aliyun")
@Data
@Accessors(chain = true)//开启链式调用
public class AliyunOssConfig implements Serializable {
    // 地域节点
    private String endPoint;
    // accessKeyId
    private String accessKeyId;
    // accessKeySecret
    private String accessKeySecret;
    // OSS的Bucket名称
    private String bucketName;
    // Bucket 域名
    private String urlPrefix;
    // 目标文件夹
    private String fileHost;

    //将OSS 客户端交给Spring容器托管
    @Bean
    public OSS OSSClient() {
        return new OSSClient(endPoint, accessKeyId, accessKeySecret);
    }
}

4. Swagger2的文档配置

这里使用了新一版的UI界面:Knife4jConfig

package com.example.oss.config;

import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import com.google.common.base.Predicates;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import springfox.bean.validators.configuration.BeanValidatorPluginsConfiguration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

/**
 * @desc:  swagger2文档基本参数配置
 * @author: cao_wencao
 * @date: 2020-11-06 15:07
 */

@Configuration
@EnableSwagger2// 开启swagger2
@EnableKnife4j
@Import(BeanValidatorPluginsConfiguration.class)
public class Knife4jConfig 
{

    @Value("${swagger.isShow:false}")
    private boolean isShow;

    @Bean
    public Docket webApiConfig() {

        return new Docket(DocumentationType.SWAGGER_2)
                .enable(isShow) //配置swagger是否开启显示,测试环境开启,生产环境关闭
                .groupName("Knife4j2.X版本")
                .apiInfo(webApiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.example"))
                .paths(Predicates.not(PathSelectors.regex("/error.*")))
                .build();
    }

    private ApiInfo webApiInfo() {
        return new ApiInfoBuilder()
                .title("SpringBoot整合阿里云OSS实现文件上传,下载,删除功能")
                .description("使用 knife4j 搭建的后台服务API接口文档")
                .termsOfServiceUrl("http://127.0.0.1:8080/doc.html")
                .version("1.0")
                .contact(new Contact("Thinkingcao""https://thinkingcao.blog.csdn.net/""Thinkingcao@163.com"))
                .build();
    }
}

5. 定义枚举类

package com.example.oss.result;

/**
 * @desc:  返回状态枚举类
 * @author: cao_wencao
 * @date: 2020-11-06 15:07
 */

public enum StatusCode {
    /**
     * 成功-状态码200
     */

    SUCCESS("success",200),

    /**
     * 失败-状态码500
     */

    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;
    }
}

6. 定义统一返回结果类RestResponse

package com.example.oss.result;

import lombok.Builder;
import lombok.Data;
import lombok.experimental.Accessors;

import java.io.Serializable;

/**
 * @desc:
 * @author: cao_wencao
 * @date: 2020-11-06 17:04
 */

@Data
@Accessors(chain = true)
@Builder
public class RestResponse implements Serializable {
    public static final int CODE_200 = 200;

    public static final int CODE_500 = 500;
    private static final long serialVersionUID = -1559957698621135646L;


    /**
     * 消息 CODE_200
     */

    private int code = CODE_500;

    /**
     * 信息
     */

    private String message;

    /**
     * 数据
     */

    private Object data;


    /**
     * Instantiates a new Api result.
     */

    public RestResponse() {
        super();
    }


    /**
     * Instantiates a new Api result.
     *
     * @param code    the code
     * @param message the message
     * @param data    the data
     */

    public RestResponse(int code, String message, Object data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }

    /**
     * Instantiates a new Api result.
     *
     * @param code    the code
     * @param message the message
     */

    public RestResponse(int code, String message) {
        this(code, message, null);
    }


    /**
     * 错误
     *
     * @param message the message
     * @return api result
     */

    public static RestResponse error(String message) {
        return new RestResponse(CODE_500, message, null);
    }

    /**
     * 错误
     *
     * @param code    the code
     * @param message the message
     * @return api result
     */

    public static RestResponse error(int code, String message) {
        return new RestResponse(code, message);
    }

    /**
     * 成功
     *
     * @param message the message
     * @return api result
     */

    public static RestResponse succee(String message) {
        return new RestResponse(CODE_200, message);
    }

    /**
     * 成功
     *
     * @param data    the data
     * @param message the message
     * @return api result
     */

    public static RestResponse succee(Object data, String message) {
        return new RestResponse(CODE_200, message, data);
    }

    /**
     * 成功
     *
     * @param data the data
     * @return api result
     */

    public RestResponse success(Object data) {

        return new RestResponse(CODE_200, null, data);
    }
}

7. 文件上传OSS的Service

package com.example.oss.service;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.model.OSSObject;
import com.aliyun.oss.model.ObjectMetadata;
import com.example.oss.config.AliyunOssConfig;
import com.example.oss.result.StatusCode;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;

/**
 * @desc:  aliyun oss文件上传业务层
 * @author: cao_wencao
 * @date: 2020-11-06 15:21
 */

@Service
public class OssFileUploadService {

    // 允许上传文件(图片)的格式
    private static final String[] IMAGE_TYPE = new String[]{".bmp"".jpg",
            ".jpeg"".gif"".png"};
    @Autowired
    private OSS ossClient;  //注入阿里云oss文件服务器客户端
    @Autowired
    private AliyunOssConfig aliyunOssConfig; //注入阿里云OSS基本配置类

    /**
     * 阿里云文件上传接口
     * @Link: 阿里云OSS文件上传官方文档链接:https://help.aliyun.com/document_detail/84781.html?spm=a2c4g.11186623.6.749.11987a7dRYVSzn
     * @param uploadFile
     * @return
     */

    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 response
     * @return
     * @throws UnsupportedEncodingException
     */

    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 fileName
     * @return
     */

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

}

8. 文件上传OSS的Controller

package com.example.oss.controller;

import com.example.oss.config.AliyunOssConfig;
import com.example.oss.result.RestResponse;
import com.example.oss.service.OssFileUploadService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;

/**
 * @desc:
 * @author: cao_wencao
 * @date: 2020-11-06 15:22
 */

@Api(tags = "阿里云OSS文件上传、下载、删除API")
@RequestMapping("api/oss/")
@RestController
public class OssFileController {
    @Autowired
    private OssFileUploadService fileUploadService;

    @Autowired
    private AliyunOssConfig aliyunOssConfig;

    /**
     * 阿里云文件上传API
     *
     * @param file
     * @return
     */

    @ApiOperation(value = "文件上传")
    @PostMapping("upload")
    public RestResponse upload(@RequestParam("file") MultipartFile file) {
        if (file != null) {
            String returnFileUrl = fileUploadService.upload(file);
            if (returnFileUrl.equals("error")) {
                return RestResponse.error("文件上传失败!");
            }
            return RestResponse.succee(returnFileUrl,"文件上传成功!");
        } else {
            return RestResponse.error("文件上传失败!");
        }
    }

    /**
     * 阿里云文件下载API
     *
     * @param fileName
     * @param response
     * @return
     * @throws Exception
     */

    @ApiOperation(value = "文件下载")
    @GetMapping(value = "download/{fileName}")
    public RestResponse download(@PathVariable("fileName") String fileName, HttpServletResponse response) throws Exception {

        String status = fileUploadService.download(fileName, response);
        if (status.equals("error")) {
            return RestResponse.error("文件下载失败!");
        }
        return RestResponse.succee("文件下载成功!");

    }

    /**
     * 阿里云文件删除API
     *
     * @param fileName
     * @return
     */

    @ApiOperation(value = "文件删除")
    @GetMapping("/delete/{fileName}")
    public RestResponse DeleteFile(@PathVariable("fileName") String fileName) {
        String status = fileUploadService.delete(fileName);
        if (status.equals("error")) {
            return RestResponse.error("文件删除失败!");
        } else {
            return RestResponse.succee("文件删除成功!");
        }
    }
}

四、访问Swagger API接口

接口文档: http://localhost:17790/doc.html

  • 文档首页
SpringBoot 系列教程(九十九):SpringBoot整合阿里云OSS实现文件上传下载删除
在这里插入图片描述
  • 接口文档
SpringBoot 系列教程(九十九):SpringBoot整合阿里云OSS实现文件上传下载删除
在这里插入图片描述
  • 接口调试
SpringBoot 系列教程(九十九):SpringBoot整合阿里云OSS实现文件上传下载删除
在这里插入图片描述
  • Swagger Models功能
SpringBoot 系列教程(九十九):SpringBoot整合阿里云OSS实现文件上传下载删除
在这里插入图片描述

五、参考文档

阿里云OSS官方文档: https://help.aliyun.com/document_detail/31883.html?spm=a2c4g.11186623.6.608.20f06d39Jq7p3d

六、源码

https://github.com/Thinkingcao/SpringBootLearning/tree/master/springboot-aliyunOss




长按下图二维码,关注公众号「Thinking曹」,在通往Java架构的路上我想与你一同前行,共同进步SpringBoot 系列教程(九十九):SpringBoot整合阿里云OSS实现文件上传下载删除


SpringBoot 系列教程(九十九):SpringBoot整合阿里云OSS实现文件上传下载删除


原创不易,如果您觉得文章对你有用,请动动你的小手指,点赞、在看、转发来一波,你的支持就是我创作的最大动力SpringBoot 系列教程(九十九):SpringBoot整合阿里云OSS实现文件上传下载删除SpringBoot 系列教程(九十九):SpringBoot整合阿里云OSS实现文件上传下载删除SpringBoot 系列教程(九十九):SpringBoot整合阿里云OSS实现文件上传下载删除



原文始发于微信公众号(Thinking曹):SpringBoot 系列教程(九十九):SpringBoot整合阿里云OSS实现文件上传下载删除

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/26847.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!