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

news2024/12/26 11:10:53

需求

要实现的功能是:实现一个可以支持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);
    }

}

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/360639.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

php获取api接口数据的方法

API是应用程序的开发接口&#xff0c;在开发程序的时候&#xff0c;我们有些功能可能不需要从到到位去研发&#xff0c;我们可以拿现有的开发出来的功能模块来使用&#xff0c;而这个功能模块&#xff0c;就叫做库(libary)。比如说&#xff1a;要实现数据传输的安全&#xff0c…

传输层TCP与UDP协议

目录 传输层 传输层功能 传输层所提供的服务 传输层的两个协议 TCP协议与UDP协议 端口 端口分类 IP地址和端口的关系 UDP协议 前言&#xff1a; UDP报文格式 检验和的伪首部 伪首部内容 TCP协议 TCP报文格式 TCP协议数据段的理解 TCP的伪首部 伪首部内容 标…

基于Jeecgboot前后端分离的ERP系统开发系列--出库单(3)

继续对销售出库单进行完善与处理 一、列表显示状态 目前先给出库表单两种状态&#xff0c;未审核与审核通过状态&#xff0c;前端通过下面调整 { title:状态, align:"center", dataIndex: status, customRender:f…

Netty(四):优化与源码

文章目录1. 优化1.1 扩展序列化算法1.2 参数调优1&#xff09;CONNECT_TIMEOUT_MILLIS2&#xff09;SO_BACKLOG3&#xff09;ulimit -n4&#xff09;TCP_NODELAY5&#xff09;SO_SNDBUF & SO_RCVBUF6&#xff09;ALLOCATOR7&#xff09;RCVBUF_ALLOCATOR1.3 RPC 框架1&…

IDEA性能优化设置(解决卡顿问题)修改内存

在我们日常使用IDEA进行开发时&#xff0c;可能会遇到许多卡顿的瞬间&#xff0c;明明我们的机器配置也不低啊&#xff1f;为什么就会一直卡顿呢&#xff1f; 原来这是因为IDEA软件在我们安装的时候就设置了默认的内存使用上限&#xff08;通常很小&#xff09;&#xff0c;这就…

理解IM消息“可靠性”和“一致性”问题,以及解决方案探讨

试想如果一个IM连发出的消息都不知道对方到底能不能收到、发出的聊天内容对方看到的到底是不是“胡言乱语”&#xff08;严重乱序问题&#xff09;&#xff0c;这样的APP用户肯定不会让他在手机上过夜&#xff08;肯定第一时间卸载了&#xff09;&#xff0c;因为最基本的聊天逻…

python基础 | python基础语法

文章目录&#x1f4da;基础语法&#x1f407;输入和输出&#x1f955;print()输出&#x1f955;input()输入&#x1f407; 变量的命名&#x1f407;条件判断&#x1f955;单向判断&#x1f955;双向判断&#x1f955;多向判断&#x1f955;if嵌套&#x1f955;三元表达式&#…

漫画 | Python是一门烂语言?

这个电脑的主人是个程序员&#xff0c;他相继学习了C、Java、Python、Go&#xff0c; 但是似乎总是停留在Hello World的水平。 每天晚上&#xff0c;夜深人静的时候&#xff0c;这些Hello World程序都会热火朝天地聊天但是&#xff0c;这一天发生了可怕的事情随着各个Hello wor…

如何无报错运行代码YOLOv6,实现目标识别?

YOLOv6是由美团视觉团队开发的1.环境配置我们先把YOLOv6的代码clone下来git clone https://github.com/meituan/YOLOv6.git安装一些必要的包pip install pycocotools2.0作者要求pytorch的版本是1.8.0,我的环境是1.7.0&#xff0c;也是可以正常运行的pip install -r requirement…

RTOS之三裸机ADC转换与三轴加速计

参考&#xff1a;https://blog.csdn.net/qq_38427678/article/details/94607733各个pin口连接方式如下&#xff1a;// J1 J3 J4 J2// [ 1] [21] [40] [20]// [ 2] [22] [39] [19]// [ 3] [23] [38] [18]// [ 4] [24] [37] [17]// [ 5] [25] [36] [16]// [ 6] [26] [35] [15]// …

重温一下C#的时间类型,并简单写一个定时器功能

&#x1f389;&#x1f389; 时间是一个非常抽象的概念&#xff0c;本篇文章我们不深究目前电脑上的时候是如何保持全网同步。主要是讲讲在使用C#编程语言里的时间类型。最后使用定时任务简单写一个提醒功能&#xff0c;比如&#xff1a;每天10点准时打开一次csdn首页&#xff…

sk_buff结构体成员变量说明

一. 前言 Socket Buffer的数据包在穿越内核空间的TCP/IP协议栈过程中&#xff0c;数据内容不会被修改&#xff0c;只是数据包缓冲区中的协议头信息发生变化。大量操作都是围绕sk_buff结构体来进行的。 sk_buff结构的成员大致分为3类&#xff1a;结构管理域&#xff0c;常规数据…

cocos2d-x4.0 win10环境搭建

cocos2d-x默认只支持win32的版本&#xff0c;win64的我测试link不行 我搭建使用的环境 windows10vs2019py2.7cmake3 从GitHub上clone源码https://github.com/cocos2d/cocos2d-x coco2d-x默认带了submodule&#xff0c;有协同子模块&#xff0c;最好是一起下载&#xff0c;搞个…

国内知名插画培训机构有哪些

国内知名插画培训机构有哪些&#xff1f;给大家梳理了国内5家专业的插画师培训班&#xff0c;最新无大插画班排行榜&#xff0c;各有优势和特色&#xff01; 一&#xff1a;国内知名插画培训机构排名 1、轻微课&#xff08;五颗星&#xff09; 主打课程有日系插画、游戏原画、古…

MYSQL 配置优化

max_connections 允许客户端并发连接的最大数量&#xff0c;默认值是151。 show status like %connections%; 设置参数值应大于Max_used_connections。如果使用连接池&#xff0c;可参考连接池的最大连接数和每个连接池的数量作为参考设置 innodb_buffe_pool_instances Inno…

【华为云-开发者专属集市】DevCloud+ECS、MySQL搭建WordPress

文章目录AppBazaar官网选择与购买项目项目概况操作过程购买DevCloud服务创建项目添加制品库应用部署购买ECS添加部署模板并执行任务故障排除安装及访问WordPress登录网站管理后台访问网站完善部署模板资源释放使用总结AppBazaar官网 首先&#xff0c;我们来到AppBazaar的官网&…

【Kubernetes】【十五】Service详解 Service介绍 类型 使用

第七章 Service详解 本章节主要介绍kubernetes的流量负载组件&#xff1a;Service和Ingress。 Service介绍 ​ 在kubernetes中&#xff0c;pod是应用程序的载体&#xff0c;我们可以通过pod的ip来访问应用程序&#xff0c;但是pod的ip地址不是固定的&#xff0c;这也就意味着…

数据库必知必会:TiDB(11)TiDB集群安装

数据库必知必会&#xff1a;TiDB&#xff08;11&#xff09;TiDB集群安装TiDB集群安装单机环境上安装集群下载并安装TiUP工具安装TiUP cluster组件创建拓扑文件配置SSH免密登录检查安装要求创建安装目录部署集群启动集群验证集群启动使用命令验证通过Dashboard查看通过Grafana查…

2023年1月冰箱品牌销量排行:销量环比增长26%,销售额36亿+

鲸参谋电商大数据2023年1月京东平台“冰箱”销售数据出炉&#xff01; 根据鲸参谋平台电商数据显示&#xff0c;2023年1月份&#xff0c;在京东平台上&#xff0c;冰箱的销量将近130万件&#xff0c;环比增长26%&#xff0c;同比下滑8%&#xff1b;销售额达36亿&#xff0c;环比…

嵌入式开发:C++在深度嵌入式系统中的应用

深度嵌入式系统通常在C语言中实现。为什么会这样?这样的系统是否也能从C中获益?嵌入式开发人员在将广泛、高效的深度嵌入式代码库从C转换为C方面的实践经验的贡献。嵌入式和深度嵌入式系统通常用C而不是C实现。软件开发人员必须放弃C作为强类型系统、模板元编程(TMP)和面向对…