spring boot——自定义依赖实现自动配置

需求

要实现的功能是:实现一个可以支持minio+oss两种方式,上传下载文件的自定义依赖。其中还包括一些创建桶、删除桶、删除文件等功能,但是最主要的是实现自动配置。

如果对spring理解很深的话,自动配置这些东西很容易理解,但是对于技术小白来讲只能按葫芦画瓢。

首先,StorageManager是我的自定义依赖对外提供的要注入spring的对象,其中的方法是对外提供的方法,方法返回的是StorageService接口。

package cn.chinatelecom.dxsc.park.oss;

import cn.chinatelecom.dxsc.park.oss.entity.FileDomain;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.UUID;

public class StorageManager {

    private static final Logger log = LoggerFactory.getLogger(StorageManager.class);

    private final StorageService storageService;

    public StorageManager(StorageService storageService) {
        this.storageService = storageService;
    }

    /**
     * 创建存储桶
     *
     * @param bucketName 桶名称
     */
    public void createBucket(String bucketName) {
        storageService.createBucket(bucketName);
    }

    /**
     * 删除桶
     * @param bucketName 桶名称
     * @return
     */
    public Boolean removeBucket(String bucketName) {
        Boolean removeBucket = storageService.removeBucket(bucketName);
        return removeBucket;
    }

    /**
     * 删除文件
     * @param fileName
     * @return
     */
    public Boolean remove(String fileName){
        return storageService.remove(fileName);
    }

    /**
     * 下载文件
     *
     * @param filePath 文件路径
     * @return 文件流
     */
    public InputStream getObject(String filePath) {
        return storageService.getObject(filePath);
    }


    /**
     * 上传文件
     * @param file
     * @return
     */
    public FileDomain saveFile(MultipartFile file) {
        FileDomain result = new FileDomain();
        try {
            InputStream in = file.getInputStream();
            long size = file.getSize();
            String contentType = file.getContentType();
            String fileName = generateName(file.getOriginalFilename());
            String filePath = generateKey(fileName);
            String url = storageService.saveFile(in, size, contentType, filePath);
            result.setSize(size);
            result.setUrl(url);
            result.setKey(filePath);
            result.setType(contentType);
        } catch (IOException e) {
            log.error("保存文件失败", e);
            throw new RuntimeException(e);
        }
        return result;
    }

    private String generateName(String originalFilename) {
        return UUID.randomUUID().toString().replace("-", "")  + originalFilename.substring(originalFilename.lastIndexOf("."));
    }

    private String generateKey(String fileName) {
    // 2022-10/24/b4c9106e3f574a61841ce4624494f0cc.jpg  在删除和下载文件时需要传入路径才可以删除和下载
        return LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM/dd")) + "/" + fileName;
    }

}

StorageService接口定义:

package cn.chinatelecom.dxsc.park.oss;

import io.minio.errors.*;

import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

public interface StorageService {

    /**
     * 创建存储桶
     * @param bucketName 桶名称
     */
    void createBucket(String bucketName);

    /**
     * 存储文件
     * @param inputStream 文件流
     * @param fileSize 文件大小
     * @param contentType 文件类型
     * @param keyName 文件名及路径
     */
    String saveFile(InputStream inputStream, long fileSize, String contentType, String keyName);


    /**
     * 下载文件
     * @param filePath 文件路径
     * @return
     */
    InputStream getObject(String filePath);

    /**
     * 删除文件
     * @param fileName
     * @return
     */
    Boolean remove(String fileName);

    /**
     * 删除桶
     * @param bucketName 桶名称
     * @return
     */
    Boolean removeBucket(String bucketName);
}

FileDomain是上传文件时返回的对象,其中包括文件url,文件在服务器的存储路径,文件类型、大小等等可以自己定义,这里没有使用@Data注解而是手写get和set方法,没有引入Lombok依赖是因为在实现自定义依赖的时候,尽量能不引入依赖就不引入依赖。如果你在自定义依赖里面引入版本2.0的Lombok,但是当别人引用你的依赖的时候,同时也引用的1.0的Lombok,这样就会出现最让人头疼的版本冲突问题,为了避免这种问题出现,尽量不要在底层引用过多依赖。

package cn.chinatelecom.dxsc.park.oss.entity;

public class FileDomain {

    /**
     * 路径名称
     */
    private String key;
    /**
     * 文件url
     */
    private String url;

    /**
     * 文件类型(contentType)
     */
    private String type;
    /**
     * 文件大小
     */
    private Long size;

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public Long getSize() {
        return size;
    }

    public void setSize(Long size) {
        this.size = size;
    }
}

接下来是自动配置的相关代码
DxscStorageProperties是需要配置的一些属性,比如accessKey以及accessSecret等等。
@ConfigurationProperties注解,在 SpringBoot 中,当想需要获取到配置文件数据时,除了可以用 Spring 自带的 @Value 注解外,SpringBoot 还提供了一种更加方便的方式:@ConfigurationProperties。只要在 Bean 上添加上了这个注解,指定好配置文件的前缀,那么对应的配置文件数据就会自动填充到 Bean 中。

package cn.chinatelecom.dxsc.park.oss.config;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "dxsc.storage")
public class DxscStorageProperties {

    /**
     * 激活哪种存储
     */
    private String active;

    /**
     * oss地址
     */
    private String endpoint;
    /**
     * 桶名称
     */
    private String bucketName;

    private String accessKey;

    private String accessSecret;


    public String getActive() {
        return active;
    }

    public void setActive(String active) {
        this.active = active;
    }

    public String getEndpoint() {
        return endpoint;
    }

    public void setEndpoint(String endpoint) {
        this.endpoint = endpoint;
    }

    public String getBucketName() {
        return bucketName;
    }

    public void setBucketName(String bucketName) {
        this.bucketName = bucketName;
    }

    public String getAccessKey() {
        return accessKey;
    }

    public void setAccessKey(String accessKey) {
        this.accessKey = accessKey;
    }

    public String getAccessSecret() {
        return accessSecret;
    }

    public void setAccessSecret(String accessSecret) {
        this.accessSecret = accessSecret;
    }

}

接下来的代码才是重中之重DxscStorageAutoConfiguration,此类将DxscStorageProperties注入到spring中,Spring Boot 会扫描到类路径下的META-INF/spring.factories配置文件,把DxscStorageAutoConfiguration对应的的Bean值添加到容器中。

package cn.chinatelecom.dxsc.park.oss.autoconfigure;

import cn.chinatelecom.dxsc.park.oss.StorageManager;
import cn.chinatelecom.dxsc.park.oss.StorageService;
import cn.chinatelecom.dxsc.park.oss.config.DxscStorageProperties;
import cn.chinatelecom.dxsc.park.oss.impl.MinioStorageServiceImpl;
import cn.chinatelecom.dxsc.park.oss.impl.OssStorageServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@EnableConfigurationProperties(DxscStorageProperties.class)
@Configuration
public class DxscStorageAutoConfiguration {

    @Autowired
    private DxscStorageProperties dxscStorageProperties;


    @Bean
    public StorageManager storageManager(StorageService storageService) {
        return new StorageManager(storageService);
    }

    @Bean
    @ConditionalOnProperty(prefix = "dxsc.storage", value = "active", havingValue = "minio")
    public StorageService minioService() {
        return new MinioStorageServiceImpl(dxscStorageProperties);
    }
//prefix为前缀,value为选择哪个属性,havingValue就是启用哪种方式,当active属性为minio时就调用上面的,oss就调用下面的,
//大概就是这个意思
    @Bean
    @ConditionalOnProperty(prefix = "dxsc.storage", value = "active", havingValue = "oss")
    public StorageService OssService() {
        return new OssStorageServiceImpl(dxscStorageProperties);
    }


}

spring.factories文件:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.chinatelecom.dxsc.park.oss.autoconfigure.DxscStorageAutoConfiguration

目录结构
其中MinioStorageServiceImpl和OssStorageServiceImpl就是实现的文件上传下载的接口,再此展示minion的接口:
oss接口可以查看官方文档,其中写得很清楚。

package cn.chinatelecom.dxsc.park.oss.impl;

import cn.chinatelecom.dxsc.park.oss.StorageService;
import cn.chinatelecom.dxsc.park.oss.config.DxscStorageProperties;
import io.minio.*;
import io.minio.http.Method;

import java.io.InputStream;

public class MinioStorageServiceImpl implements StorageService {

    private final DxscStorageProperties dxscStorageProperties;

    private final MinioClient minioClient;

    public MinioStorageServiceImpl(DxscStorageProperties dxscStorageProperties) {
        this.dxscStorageProperties =dxscStorageProperties;

        this.minioClient = MinioClient.builder()
                .endpoint(dxscStorageProperties.getEndpoint())
                .credentials(dxscStorageProperties.getAccessKey(), dxscStorageProperties.getAccessSecret())
                .build();
    }



    @Override
    public void createBucket(String bucketName){
        boolean isExist = false;
        // 检查存储桶是否已经存在
        try{
            isExist = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
            if (!isExist) {
                //创建桶
                minioClient.makeBucket(MakeBucketArgs.builder()
                        .bucket(bucketName)
                        .build());
                minioClient.setBucketPolicy(
                        SetBucketPolicyArgs.builder().bucket(bucketName)
                                .config(
                                        "{\"Version\":\"2023-2-20\",\"Statement\":[]}"
                                )
                                .build());
            }
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }

    @Override
    public String saveFile(InputStream inputStream, long fileSize, String contentType, String keyName) {
       try {
            PutObjectArgs objectArgs = PutObjectArgs.builder().bucket(dxscStorageProperties.getBucketName()).object(keyName)
                    .stream(inputStream, fileSize, -1).contentType(contentType).build();
            //文件名称相同会覆盖
            minioClient.putObject(objectArgs);
           // 查看文件地址
           GetPresignedObjectUrlArgs build = new GetPresignedObjectUrlArgs().builder().bucket(dxscStorageProperties.getBucketName()).object(keyName).method(Method.GET).build();
           try {
               return minioClient.getPresignedObjectUrl(build);
           } catch (Exception e) {
               throw new RuntimeException(e);
           }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public InputStream getObject(String filePath) {
        try{
            GetObjectArgs objectArgs = GetObjectArgs.builder().bucket(dxscStorageProperties.getBucketName())
                .object(filePath).build();
            return minioClient.getObject(objectArgs);
        }catch (Exception e){
            System.out.println("下载失败");
            throw new RuntimeException(e);
        }
    }

    @Override
    public Boolean remove(String fileName) {
        try {
            minioClient.removeObject(RemoveObjectArgs.builder().bucket(dxscStorageProperties.getBucketName()).object(fileName).build());
        } catch (Exception e) {
            return false;
        }
        return true;
    }

    @Override
    public Boolean removeBucket(String bucketName) {
        try {//在删除桶时,需要注意一下不能删除自身,当你的配置文件里的bucketName的test时还要删除test,是不能删除成功的
        //bucketName为test1时,删除test,是可以的
            minioClient.removeBucket(RemoveBucketArgs.builder()
                    .bucket(bucketName)
                    .build());
        } catch (Exception e) {
            return false;
        }
        return true;
    }
}

至此,自定义依赖完成,接下来是引用,引用方法为先将自定义依赖上传至本地maven库,然后才可以导入依赖,

		<dependency>
            <artifactId>dxsc-park-frame-oss</artifactId>
            <groupId>cn.chinatelecom</groupId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>

然后,配置minio或者oss的accessKey和accessSecret等属性:
(active为minio就是通过minion方式实现文件上传下载)

dxsc:
  storage:
    active: minio
    endpoint: http://***.168.30.27:9000
    bucketName: dxsc-park
    accessKey: minioadmin
    accessSecret: minioadmin
#  storage:
#    active: oss
#    endpoint: https://oss-cn-beijing.aliyuncs.com
#    bucketName: dxsc-park
#    accessKey: ***
#    accessSecret: ***
server:
  port: 1101

最后注入StorageManager,在一开始的时候就说过StorageManager是对外提供的:

package cn.chinatelecom.dxsc.park.test.controller;


import cn.chinatelecom.dxsc.park.oss.StorageManager;
import cn.chinatelecom.dxsc.park.oss.entity.FileDomain;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;

@RestController
@RequestMapping("/minio")
public class TestController {

    @Autowired
    private StorageManager storageManager;

    @PostMapping("/createBucket")
    public void createBucket(@RequestParam("bucketName") String bucketName){
        storageManager.createBucket(bucketName);
    }

    @PostMapping("/removeBucket")
    public void removeBucket(@RequestParam("bucketName") String bucketName){
        storageManager.removeBucket(bucketName);
    }

    @PostMapping("/upload")
    public String upload(@RequestParam("file") MultipartFile file){
        FileDomain fileDomain = storageManager.saveFile(file);
        System.out.println(fileDomain.getUrl());
        return fileDomain.getKey();
    }

    @PostMapping("/remove")
    public Boolean remove(@RequestParam("fileName") String fileName){
        Boolean remove = storageManager.remove(fileName);
        return remove;
    }

    @PostMapping("/getObject")
    public void getObject(@RequestParam("filePath") String filePath, HttpServletResponse response) throws IOException {
        InputStream object = storageManager.getObject(filePath);

        response.setContentType("application/octet-stream");
        response.addHeader("Content-Disposition", "attachment;filename=" + "fileName");

        IOUtils.copy(object, response.getOutputStream());
        IOUtils.closeQuietly(object);
    }

}

以上就是我在实现自动配置的全部内容

一个集坚强与自信于一身的菇凉。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

淡雅的惆怅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值