目录

一、概述

二、 开发前的准备

三、 配置阿里云 OSS 存储相关属性

四、工具类相关方法编写

五、 Controller 层编写相关测试方法

六、 上传图片相关前端页面

七、 测试我们的图床


一、概述

这是一篇之前在公众号写的关于一篇 SpringBoot 整合阿里云OSS 文章,最近公司有上传服务需求,便把以前的OSS上传功能做了一下封装,封装组件模板,可以提供各个服务之间调用。该文章包含了上传文件自带进度条功能、多文件上传、下载、删除和 获取文件列表功能。

上传流程架构图

阿里 oss 图片 python 阿里云oss 图床_阿里 oss 图片 python

 

介绍一下OSS

  阿里云对象存储服务,是阿里云提供的海量、安全、低成本、高可靠的云存储服务。它具有与平台无关的RESTful API接口,您可以在任何应用、任何时间、任何地点存储和访问任意类型的数据 您可以使用阿里云提供的API/SDK接口或者OSS迁移工具轻松地将海量数据移入或移出阿里云OSS。数据存储到阿里云OSS以后,您可以选择标准类型(Standard)的阿里云OSS服务作为移动应用、大型网站、图片分享或热点音视频的主要存储方式,也可以选择成本更低、存储期限更长的低频访问类型(Infrequent Access)和归档类型(Archive)的阿里云OSS服务作为不经常访问数据的备份和归档。

阿里云OSS官方介绍地址

https://help.aliyun.com/document_detail/31947.html

本篇文章会介绍到 SpringBoot 整合阿里云OSS 存储服务实现文件上传下载以及简单的查看。其实今天将的应该算的上是一个简单的小案例了,涉及到的知识点还算是比较多

 

二、 开发前的准备

1. 前置知识

   具有 Java 基础以及SpringBoot 简单基础知识即可

 2. 环境参数

  • 开发工具:IDEA
  • 基础工具:Maven+JDK8
  • 所用技术:SpringBoot+阿里云OSS 存储服务 Java 相关API
  • SpringBoot版本:2.1.2

 3.  能学到什么

  • SpringBoot 整合 阿里云OSS 存储服务并编写相关工具类
  • Swagger接口文档
  • SpringBoot 整合 thymeleaf 并实现前后端传值
  • SpringBoot 从配置文件读取值并注入到类中
  • 如何自己搭建一个图床使用(通过前端选择图片,支持预览,但不支持修改图片)

4. 项目结构

阿里 oss 图片 python 阿里云oss 图床_文件上传_02

5. 配置 pom.xml 文件中的相关依赖

<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.2.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
   <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- Thymeleaf-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <!-- 阿里云OSS-->
        <dependency>
            <groupId>com.aliyun.oss</groupId>
            <artifactId>aliyun-sdk-oss</artifactId>
            <version>2.4.0</version>
        </dependency>
        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.3.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
       <dependency>
           <groupId>org.projectlombok</groupId>
           <artifactId>lombok</artifactId>
           <version>1.18.12</version>
       </dependency>
       <!--hutool-all-->
       <dependency>
           <groupId>cn.hutool</groupId>
           <artifactId>hutool-all</artifactId>
           <version>5.3.5</version>
       </dependency>
       <!--整合Knife4j-->
       <dependency>
           <groupId>com.github.xiaoymin</groupId>
           <artifactId>knife4j-spring-boot-starter</artifactId>
           <version>2.0.4</version>
       </dependency>
    </dependencies>
    
  	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

三、 配置阿里云 OSS 存储相关属性

我在项目中使用properties配置,然后@ConfigurationProperties 注解注入到类中。

1 通过Properties注入配置文件

@Data
@Component("ossProperties")
@ConfigurationProperties(prefix = "oss")
public class OSSProperties {

    /**
     * 默认的存储桶名称
     */
    private String bucketname;

    /**
     * 地域节点
     */
    private String endpoint;

    /**
     * 你的AccessKeyID
     */
    private String accessKeyId;

    /**
     * 秘钥
     */
    private String accessKeySecret;

}

 

到阿里云 OSS 控制台:https://oss.console.aliyun.com/overview 获取上述相关信息:如图

阿里 oss 图片 python 阿里云oss 图床_spring_03

获取AccessKey ID和Access Key Secret第一步:如图

阿里 oss 图片 python 阿里云oss 图床_OSS_04

获取AccessKey ID  Access Key Secret 第二步:如图

阿里 oss 图片 python 阿里云oss 图床_spring_05

 

  • SpringBoot 整合 阿里云OSS 存储服务并编写相关工具类
  • SpringBoot 整合 thymeleaf 并实现前后端传值
  • SpringBoot 从配置文件读取值并注入到类中
  • 如何自己搭建一个图床使用(通过前端选择图片,支持预览,但不支持修改图片)

四、工具类相关方法编写

该工具类主要提供了基本上传方法:如果需要增加新的方法,参考阿里云官方提供的相关文档来根据自己的需求来优化。

Java API文档地址如下

https://help.aliyun.com/document_detail/32008.html?spm=a2c4g.11186623.6.703.238374b4PsMzWf

 

整体搭建OSS思路

  • 从properties配置文件中获取OSS配置信息
  • 封装一个配置类来组装OSS客户端,只初始化一次
  • 封装一个OSS上传文件模板提供外部调用

 

1. OSSConfiguration

@Configuration
@EnableConfigurationProperties(OSSProperties.class)
public class OSSConfiguration
{
   @Resource(name = "ossProperties")
   private OSSProperties ossProperties;
   
   @Bean("ossClient")
   public OSSClient ossClient() {
      @SuppressWarnings("deprecation")
        OSSClient client = new OSSClient(
            ossProperties.getEndpoint(), 
            ossProperties.getAccessKeyId(), 
            ossProperties.getAccessKeySecret());
      return client;
   }

   @Bean
   @ConditionalOnBean(OSSClient.class)
   @ConditionalOnMissingBean(OSSTemplate.class)
   public OSSTemplate ossTemplate(OSSClient ossClient) {
      return new OSSTemplate(ossClient);
   }
@Slf4j
public class OSSTemplate {

   @Resource
   private  OSSClient ossClient;

   public OSSTemplate(OSSClient ossClient) {
      this.ossClient = ossClient;
   }
   /**
    * 目前所有文件都存放在该命名空间下
    */
   private final static String bucketName = OSSConstant.BUCKE_NAME;

   /**
    *  上传文件
    * @param bucketName  仓库名称
    * @param fileUrl   生成文件url
    * @param input       InputStream
    * @date: 2021/3/19 10:39
    * @return: void
    */
   @SneakyThrows
   public void putObject(String bucketName, String fileUrl, InputStream input) {

      ossClient.putObject(bucketName, fileUrl, input);
   }
   /**
    * 上传文件带进度条
    * @param bucketName 仓库名称
    * @param fileUrl  生成文件url
    * @param file    File
    * @param uploadNo  上传文件编号,为了获文件进度条(唯一标识)
    * @param fileSize 文件大小(单位 KB)
    * @date: 2021/3/19 10:36
    * @return: void
    */
   @SneakyThrows
   public void putObject(String bucketName, String fileUrl, File file,String uploadNo,
                    Long fileSize) {
      ossClient.putObject(new PutObjectRequest(bucketName,
            fileUrl, file).withProgressListener(new PutObjectProgressListener(
            uploadNo, fileSize)));
   }


   /** 
    * 生成过期URL链接地址
    * @param bucketName 仓库名称
    * @param key
    * @date: 2021/3/19 10:37
    * @return: java.lang.String 
    */
   @SneakyThrows
   public String generatePresignedUrl(String bucketName, String key) {
      
      Date expiration = new Date(new Date().getTime() + 3600l * 1000 * 24 * 365 * 10);   
      return ossClient.generatePresignedUrl(bucketName, key, expiration).toString();
   }

   /**
    *  创建仓库
    * @param bucketName  仓库名称
    * @date: 2021/3/19 10:39
    * @return: void
    */
   @SneakyThrows
   public void createBucket(String bucketName) {

      ossClient.createBucket(bucketName);
   }

   /**
    *  删除仓库
    * @param bucketName  仓库名称
    * @date: 2021/3/19 10:39
    * @return: void
    */
   @SneakyThrows
   public void deleteBucket(String bucketName) {

      ossClient.deleteBucket(bucketName);
   }

   /**
    * 删除OSS 文件
    * @param delFileUrl  删除指定文件URL
    */
   @SneakyThrows
   public void delFile(String delFileUrl){
      // key是文件名
      ossClient.deleteObject(bucketName, delFileUrl);
//    ossClient.shutdown();//关闭连接
   }


    /**
     * 下载文件
     * @param downFileUrl
     * @date: 2021/3/18 17:30
     * @return: com.aliyun.oss.model.OSSObject
     */
   public void getObject(String downFileUrl, HttpServletResponse response) {

      OSSObject ossObject = ossClient.getObject(bucketName, downFileUrl);
      BufferedInputStream bis = null;
      try {
         String filename=System.currentTimeMillis()+downFileUrl.substring(downFileUrl.lastIndexOf("."));
         response.setContentType("application/octet-stream");
         response.setHeader("Content-disposition", "attachment; filename=" + new String(filename.getBytes("utf-8"), "ISO8859-1"));

         bis = new BufferedInputStream(ossObject.getObjectContent());
         OutputStream os = response.getOutputStream();
         byte[] buffer = new byte[2048];
         int i = bis.read(buffer);
         while (i != -1) {
            os.write(buffer, 0, i);
            i = bis.read(buffer);
         }
      } catch (IOException e) {
         e.printStackTrace();
      }finally {
         if (bis != null) {
            try {
               bis.close();
            } catch (IOException e) {
               e.printStackTrace();
            }
         }
      }
      // 关闭OSSClient。
      ossClient.shutdown();
   }

   /**
    * 通过文件名下载文件到本地
    *
    * @param objectName    要下载的文件名
    * @param localFileName 本地要创建的文件名
    */
   public void downloadFile(String objectName, String localFileName) {
      // 下载OSS文件到本地文件。如果指定的本地文件存在会覆盖,不存在则新建。
      ossClient.getObject(new GetObjectRequest(bucketName, objectName), new File(localFileName+StrUtil.DOT+"jpg"));
      // 关闭OSSClient。
//    ossClient.shutdown();
   }

   /**
    * 列举文件下所有的文件根据目录
     */
   public List<String> listFile(String filePath) {

      List<String> fileList = new ArrayList<>(12);
      // 构造ListObjectsRequest请求。
      ListObjectsRequest listObjectsRequest = new ListObjectsRequest(bucketName);

      // 设置prefix参数来获取fun目录下的所有文件。
      listObjectsRequest.setPrefix(filePath+ StrUtil.SLASH);
      // 列出文件。
      ObjectListing listing = ossClient.listObjects(listObjectsRequest);
      // 遍历所有文件。
      for (OSSObjectSummary objectSummary : listing.getObjectSummaries()) {
         fileList.add(objectSummary.getKey());
      }
      log.info("遍历所有commonPrefix:");
      for (String commonPrefix : listing.getCommonPrefixes()) {
         System.out.println(commonPrefix);
         fileList.add(commonPrefix);
      }
      return fileList;
   }
}

   监听上传文件进度条

@Slf4j
public class PutObjectProgressListener implements ProgressListener {


    private String uploadNo;
    private long bytesWritten = 0;
    private long totalBytes = -1;
    private boolean succeed = false;
    private BigDecimal percent = new BigDecimal(0.00);
    Long sum = 0L;


    public PutObjectProgressListener(String uploadNo, Long sum) {
        this.uploadNo = uploadNo;
        this.sum = sum;
    }

    @Override
    public void progressChanged(ProgressEvent progressEvent) {

        long bytes = progressEvent.getBytes();
        ProgressEventType eventType = progressEvent.getEventType();
        switch (eventType) {
            case TRANSFER_STARTED_EVENT:
                // logger.info("Start to upload......");
                break;
            case REQUEST_CONTENT_LENGTH_EVENT:
                this.totalBytes = bytes;
                // logger.info(this.totalBytes +
                // " bytes in total will be uploaded to OSS");
                break;
            case REQUEST_BYTE_TRANSFER_EVENT:
                this.bytesWritten += bytes;
                if (this.totalBytes != -1) {
                    double progress = this.bytesWritten * 100.0 / this.totalBytes;
                    percent = new BigDecimal(progress).setScale(2, BigDecimal.ROUND_HALF_UP);
                    // 将进度percent放入session中
                    CacheUpload.putProgressBar(uploadNo,percent);
                }
                break;
            case TRANSFER_COMPLETED_EVENT:
                this.succeed = true;

                break;
            case TRANSFER_FAILED_EVENT:

                break;
            default:
                break;
        }

        //图片进度打印控制台 
       log.info("percent:" + percent);
    }

    public boolean isSucceed() {
        return succeed;
    }


}

五、 Controller 层编写相关测试方法

注意将下面的相关路径改成自己的,不然会报错!!!

/**
 *
 * @description: 测试OSS功能
 * @author: ZouLiPing
 * @contact: 请关注微信公众号“给自己一个smile”里面有详细电商项目文档,weixin:zlp865391093
 * @date: 2019年3月29日15:18:33
 * @version V1.0
 */
@Slf4j
@RestController
@RequestMapping("/file")
@Api(value = "file", tags = "文件模块")
public class FileController {

    @Autowired
    private FileService fileService;

    //仓库中的某个文件夹
    public static final String FILE_HOST = "test";


    /**
     * 测试上传文件到阿里云OSS存储(不支持上传文件,推荐作为图床使用)
     *
     * @return
     */
    @PostMapping("/testUpload")
    @ApiOperation(value = "测试上传文件")
    public String testUpload() {
        String picPath = "C:\\Users\\Administrator\\Desktop\\WeChat_20210319223645.jpg";
        MultipartFile file = FileUtil.getMulFileByPath(picPath);
        String url = fileService.uploadFile(FILE_HOST,file);
        return url;
    }

    /**
     * 文件上传
     */
    @PostMapping("/uploadFile")
    @ApiOperation(value = "文件上传")
    public String uploadFile(@RequestParam("file") MultipartFile file) {

        String url = fileService.uploadFile(FILE_HOST, file);
        return url;
    }

    @PostMapping("/uploadFileProgress")
    @ApiOperation(value = "文件上传带进度条")
    public UploadResp uploadFileProgress(@RequestParam("file") MultipartFile file,
                                         @RequestParam("uploadNo") String uploadNo
    ){

        long fileSize = (file.getSize()/1024*100)/100;
        UploadResp uploadResp = fileService.uploadFileProgress(file,FILE_HOST,fileSize,uploadNo);
        return uploadResp;
    }

    /**
     * 获取进度条
     */
    @GetMapping(value = "/getProgressBar")
    @ApiOperation(value = "获取进度条")
    public UploadVO getProgressBar(@RequestParam("uploadNo") String uploadNo) {

        UploadVO uploadVO ;
        for (;;) {
            uploadVO = CacheUpload.getProgressBar(uploadNo);
            if (Objects.nonNull(uploadVO)) {
                log.info("getProgressBar.resp uploadVO={}", uploadVO.toString());
                if (uploadVO.getProgressBar().compareTo(new BigDecimal(100.00)) == 0) {
                    break;
                }
            }
        }
        CacheUpload.removeProgressBar(uploadNo);

        return uploadVO;
    }

    /**
     * 列出某个文件夹下的所有文件
     */
    @GetMapping("/testListFile")
    @ApiOperation(value = "列出某个文件夹下的所有文件")
    public List<String> testListFile(@RequestParam("filePath") String filePath) {

        return  fileService.listFile(filePath);
    }

    /**
     * 删除文件
     */
    @GetMapping("/delFile")
    @ApiOperation(value = "删除文件")
    public String delFile(@RequestParam("filePath") String delFileUrl) {
        fileService.delFile(delFileUrl);
        return "success";
    }

}

六、 上传图片相关前端页面

注意引入jquery ,避免前端出错。

index.html

JS 的内容主要是让我们上传的图片可以预览,就像我们在网站更换头像的时候一样

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>基于阿里云OSS存储的图床</title>
<script th:src="@{/js/jquery-3.3.1.js}"></script>
<style>
        * {
margin: 0;
padding: 0;
        }

#submit {
margin-left: 15px;
        }

.preview_box img {
width: 200px;
        }
</style>
</head>
<body>

<form action="/oss/uploadFile" enctype="multipart/form-data" method="post">
<div class="form-group" id="group">
<input type="file" id="img_input" name="file" accept="image/*">
<label for="img_input" ></label>
</div>
<button type="submit" id="submit">上传</button>
<!--预览图片-->
<div class="preview_box"></div>
</form>
<script type="text/javascript">

    $("#img_input").on("change", function (e) {
var file = e.target.files[0]; //获取图片资源
// 只选择图片文件
if (!file.type.match('image.*')) {
return false;
        }
var reader = new FileReader();
        reader.readAsDataURL(file); // 读取文件
// 渲染文件
        reader.onload = function (arg) {

var img = '<img class="preview" src="' + arg.target.result + '" alt="preview"/>';
            $(".preview_box").empty().append(img);
        }
    });
</script>
</body>
</html>

 

success.html

通过 <span th:text="${url}"></span> 引用后端传过来的值。

 

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>上传结果</title>
</head>
<body>
<h1>上传成功!</h1>
图片地址为:<span th:text="${url}"></span>
</body>
</html>

七、 测试我们的图床

访问 :http://localhost:8080/oss

① 上传图片

阿里 oss 图片 python 阿里云oss 图床_阿里 oss 图片 python_06

② 图片上传成功返回图片地址

阿里 oss 图片 python 阿里云oss 图床_spring_07

③ 通过图片 URL 访问图片

阿里 oss 图片 python 阿里云oss 图床_阿里 oss 图片 python_08

swagger文档测试

阿里 oss 图片 python 阿里云oss 图床_文件上传_09