前后端分离博客】学习笔记04 --- 文件上传-策略模式

news2025/1/20 18:27:06

一、思路

  1. 我们定义一个接口(就比如接下来要实现的文件上传接口)
  2. 我们定义所需要实现的策略实现类 A、B、C、D(也就是项目中所使用的四种策略阿里云Oss上传、腾讯云Cos上传、七牛云Kodo上传、本地上传)
  3. 我们通过策略上下文来调用策略接口,并选择所需要使用的策略

   

 

二、策略模式的具体实现

2.1、策略接口的编写

首先我们新建一个名称为 strategy 的文件夹(在代码规范中,使用设计模式要明确的体现出来,便于后期维护)

如下就是我们的策略接口了,接下来我们去编写对应的实现类。

/**
 * 上传策略
 *
 * @author DarkClouds
 * @date 2023/05/13
 */
public interface UploadStrategy {

    /**
     * 上传文件
     *
     * @param file 文件
     * @param path 上传路径
     * @return {@link String} 文件地址
     */
    String uploadFile(MultipartFile file, String path);
}

   

2.3、完善配置文件

在编写对象存储实现类之前,我门会发现一个问题。我们需要去对应的云服务厂商开通对象存储服务,然后获取到accessKey、accessKeySecret、endpoint、bucket、domainUrl等必须的参数。
因为这些信息基本是不会发生改变,所以我们可以将这些信息存储在配置文件中。
除此之外我们还需要对文件上传进行配置,设置为最大文件为100MB

server:
  port: 8080

spring:
  servlet:
    multipart:
      max-file-size: 100MB
      max-request-size: 100MB

# 文件上传策略 local、oss、cos
upload:
  strategy: oss
  local:
    # nginx映射本地文件路径
    url: http://127.0.0.1:8800
    # 本地文件存储路径
    #path: /usr/local/upload
    path: D:\img\upload
  # oss存储
  oss:
    url: http://Bucket域名
    endpoint: OSS配置endpoint
    bucketName: OSS配置bucketName
    accessKeyId: OSS配置accessKeyId
    accesskeySecret: OSS配置accesskeySecret
  # cos存储
  cos:
    url: https://Bucket域名
    secretId: COS配置secretId
    secretKey: COS配置secretKey
    region: COS配置region
    bucketName: COS配置bucketName

配置文件的格式如上,我们获取配置文件的时候可以使用@Value()的注解进行获取。

我们使用@ConfigurationProperties()的方式来获取配置文件的内容。

引入自定义配置依赖 以及 云服务依赖

     <!--==============  项目版本号规定 ===============-->
    <properties>
        <!--==============  对象存储依赖  ==================-->
        <cos.version>5.6.89</cos.version>
        <kodo.version>[7.7.0, 7.10.99]</kodo.version>
        <oss.version>3.15.1</oss.version>
    </properties> 

    <dependencies>
       <!-- 自定义配置 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
        </dependency>
        <!--================== 对象存储依赖 =======================-->
        <!-- 腾讯云Cos对象存储 -->
        <dependency>
            <groupId>com.qcloud</groupId>
            <artifactId>cos_api</artifactId>
            <version>${cos.version}</version>
        </dependency>
        <!-- 七牛云Kodo对象存储 -->
        <dependency>
            <groupId>com.qiniu</groupId>
            <artifactId>qiniu-java-sdk</artifactId>
            <version>${kodo.version}</version>
        </dependency>
        <!--阿里云Oss对象存储-->
        <dependency>
            <groupId>com.aliyun.oss</groupId>
            <artifactId>aliyun-sdk-oss</artifactId>
            <version>${oss.version}</version>
        </dependency> 
    </dependencies>

我们编写properties实体类,通过@ConfigurationProperties()注解可以将配置文件中的内容读取到实体类中。
实体类中由于类继承关系不要使用@Data注解,而要使用@Getter和@Setter,某则可能会出现问题。
除此之外还要注意配置目录的对应关系

/**
 * oss配置属性
 *
 * @author DarkClouds
 * @date 2023/05/13
 */
@Getter
@Setter
@Configuration
@ConfigurationProperties(prefix = "upload.oss")
public class OssProperties {

    /**
     * oss域名
     */
    private String url;

    /**
     * 终点
     */
    private String endpoint;

    /**
     * 访问密钥id
     */
    private String accessKeyId;

    /**
     * 访问密钥密码
     */
    private String accessKeySecret;

    /**
     * bucket名称
     */
    private String bucketName;
}

 

2.4、策略实现类内部实现

我们在进行具体文件上传策略实现之前总结一下所涉及到的功能。

  • 上传对象初始化
  • 文件是否已经存在
  • 文件上传
  • 获取访问路径

我们会发现无论是通过哪个平台进行文件的上传,基本上都会使用到上述的步骤,也就是说都会使用到上述的方法。
所以在这里我们定义一个抽象类来规定具体所需要使用的方法,然后各个具体实现来继承我们的抽象类即可。

/**
 * 抽象上传模板
 *
 * @author DarkClouds
 * @date 2023/05/13
 */
@Service
public abstract class AbstractUploadStrategyImpl implements UploadStrategy {

    @Override
    public String uploadFile(MultipartFile file, String path) {
        try {
            // 获取文件md5值
            String md5 = FileUtils.getMd5(file.getInputStream());
            // 获取文件扩展名
            String extName = FileUtils.getExtension(file);
            // 重新生成文件名
            String fileName = md5 + "." + extName;
            // 判断文件是否已存在
            if (!exists(path + fileName)) {
                // 不存在则继续上传
                upload(path, fileName, file.getInputStream());
            }
            // 返回文件访问路径
            return getFileAccessUrl(path + fileName);
        } catch (Exception e) {
            e.printStackTrace();
            throw new ServiceException("文件上传失败");
        }
    }

    /**
     * 判断文件是否存在
     *
     * @param filePath 文件路径
     * @return {@link Boolean}
     */
    public abstract Boolean exists(String filePath);

    /**
     * 上传
     *
     * @param path        路径
     * @param fileName    文件名
     * @param inputStream 输入流
     * @throws IOException io异常
     */
    public abstract void upload(String path, String fileName, InputStream inputStream) throws IOException;

    /**
     * 获取文件访问url
     *
     * @param filePath 文件路径
     * @return {@link String} 文件url
     */
    public abstract String getFileAccessUrl(String filePath);
}

 

2.4.1、Oss上传策略具体实现

我们在OssUploadStrategyImpl实现文件上传至Oss平台,具体如何上传代码至阿里云Oss平台可以去看阿里云官方文档

/**
 * oss上传策略
 *
 * @author DarkClouds
 * @date 2023/05/13
 */
@Slf4j
@RequiredArgsConstructor
@Service("ossUploadStrategyImpl")
public class OssUploadStrategyImpl extends AbstractUploadStrategyImpl {

    //构造器注入bean
    private final OssProperties ossProperties;

    @Override
    public Boolean exists(String filePath) {
        return getOssClient().doesObjectExist(ossProperties.getBucketName(), filePath);
    }

    @Override
    public void upload(String path, String fileName, InputStream inputStream) {
        OSS ossClient = getOssClient();
        try {
            //OSS上传不能以"/"开头,如果路径只有"/"则把路径设置为空
            String uploadPath = "/".equals(path) ? "" : path.split("/")[1] + "/";
            // 调用oss方法上传
            ossClient.putObject(ossProperties.getBucketName(), uploadPath + fileName, inputStream);
        } catch (OSSException oe) {
            log.error("Error Message:" + oe.getErrorMessage());
            log.error("Error Code:" + oe.getErrorCode());
            log.info("Request ID:" + oe.getRequestId());
            log.info("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            log.error("Caught an ClientException, Error Message:" + ce.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }

    @Override
    public String getFileAccessUrl(String filePath) {
        return ossProperties.getUrl() + filePath;
    }

    /**
     * 获取ossClient
     *
     * @return {@link OSS} ossClient
     */
    private OSS getOssClient() {
        return new OSSClientBuilder().build(ossProperties.getEndpoint(), ossProperties.getAccessKeyId(), ossProperties.getAccessKeySecret());
    }
}

 

 2.4.2、Cos上传策略具体实现

我们在CosUploadStrategyImpl实现文件上传至Cos平台,具体如何上传代码至腾讯云Cos平台可以去看腾讯云官方文档。

/**
 * cos上传策略
 *
 * @author DarkClouds
 * @date 2023/05/13
 */
@Slf4j
@RequiredArgsConstructor
@Service("cosUploadStrategyImpl")
public class CosUploadStrategyImpl extends AbstractUploadStrategyImpl {
    
    private final CosProperties cosProperties;

    @Override
    public Boolean exists(String filePath) {
        return getCosClient().doesObjectExist(cosProperties.getBucketName(), filePath);
    }

    @Override
    public void upload(String path, String fileName, InputStream inputStream) {
        COSClient cosClient = getCosClient();
        try {
            ObjectMetadata objectMetadata = new ObjectMetadata();
            // 上传的流如果能够获取准确的流长度,则推荐一定填写 content-length
            objectMetadata.setContentLength(inputStream.available());
            // 调用cos方法上传
            cosClient.putObject(cosProperties.getBucketName(), path + fileName, inputStream, objectMetadata);
        } catch (CosServiceException e) {
            log.error("Error Message:" + e.getErrorMessage());
            log.error("Error Code:" + e.getErrorCode());
            log.info("Request ID:" + e.getRequestId());
        } catch (CosClientException e) {
            log.error("Caught an CosClientException, Error Message:" + e.getMessage());
        } catch (IOException e) {
            log.error("Caught an IOException, Error Message:" + e.getMessage());
        } finally {
            cosClient.shutdown();
        }
    }

    @Override
    public String getFileAccessUrl(String filePath) {
        return cosProperties.getUrl() + filePath;
    }

    /**
     * 获取cosClient
     *
     * @return {@link COSClient} cosClient
     */
    private COSClient getCosClient() {
        // 1 初始化用户身份信息(secretId, secretKey)。
        COSCredentials cred = new BasicCOSCredentials(cosProperties.getSecretId(), cosProperties.getSecretKey());
        // 2 设置 bucket 的地域, COS 地域的简称请参照 https://cloud.tencent.com/document/product/436/6224
        Region region = new Region(cosProperties.getRegion());
        ClientConfig clientConfig = new ClientConfig(region);
        // 这里建议设置使用 https 协议
        // 从 5.6.54 版本开始,默认使用了 https
        clientConfig.setHttpProtocol(HttpProtocol.https);
        // 3 生成 cos 客户端。
        return new COSClient(cred, clientConfig);
    }
}

 

 2.4.3、Kodo上传策略具体实现

我们在KodoUploadStrategyImpl实现文件上传至七牛云平台,具体如何上传代码至七牛云Kodo平台可以去看七牛云官方文档。

@Slf4j
@RequiredArgsConstructor
@Service("kodoUploadServiceImpl")
public class KodoUploadStrategyImpl extends AbstractUploadStrategyImpl {

    /**
     * 构造器注入Bean
     */
    private final ObjectStoreProperties properties;

    /**
     * upToken
     */
    private String upToken;

    /**
     * 上传Manger
     */
    private UploadManager uploadManager;

    /**
     * 存储桶Manger
     */
    private BucketManager bucketManager;


    @Override
    public void initClient() {
        Auth auth = Auth.create(properties.getKodo().getAccessKey(), properties.getKodo().getAccessKeySecret());
        upToken = auth.uploadToken(properties.getKodo().getBucket());
        Configuration cfg = new Configuration(Region.region0());
        cfg.resumableUploadAPIVersion = Configuration.ResumableUploadAPIVersion.V2;
        uploadManager = new UploadManager(cfg);
        bucketManager = new BucketManager(auth, cfg);
        log.info("OssClient Init Success...");
    }

    @Override
    public boolean checkFileIsExisted(String fileRelativePath) {
        try {
            if (null == bucketManager.stat(properties.getKodo().getBucket(), fileRelativePath)) {
                return false;
            }
        } catch (QiniuException e) {
            return false;
        }
        return true;
    }

    @Override
    public void executeUpload(MultipartFile file, String fileRelativePath) throws IOException {
        try {
            uploadManager.put(file.getInputStream(), fileRelativePath, upToken, null, null);
        } catch (IOException e) {
            log.error("文件上传失败");
            throw new BaseException("文件上传失败");
        }
    }

    @Override
    public String getPublicNetworkAccessUrl(String fileRelativePath) {
        return properties.getKodo().getDomainUrl() + fileRelativePath;
    }
}

 

2.4.4、本地上传策略具体实现

我们在LocalUploadStrategyImpl实现文件上传至本地

/**
 * 本地上传策略
 *
 * @author DarkClouds
 * @date 2023/05/13
 */
@Service("localUploadStrategyImpl")
public class LocalUploadStrategyImpl extends AbstractUploadStrategyImpl {

    /**
     * 本地路径
     */
    @Value("${upload.local.path}")
    private String localPath;

    /**
     * 访问url
     */
    @Value("${upload.local.url}")
    private String localUrl;

    @Override
    public Boolean exists(String filePath) {
        return new File(localPath + filePath).exists();
    }

    @Override
    public void upload(String path, String fileName, InputStream inputStream) throws IOException {
        // 判断目录是否存在
        File directory = new File(localPath + path);
        if (!directory.exists()) {
            if (!directory.mkdirs()) {
                throw new ServiceException("创建目录失败");
            }
        }
        // 写入文件
        File file = new File(localPath + path + fileName);
        if (file.createNewFile()) {
            try (BufferedInputStream bis = new BufferedInputStream(inputStream);
                 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file))) {
                byte[] bytes = new byte[4096];
                int length;
                while ((length = bis.read(bytes)) != -1) {
                    bos.write(bytes, 0, length);
                }
            }
        }
    }

    @Override
    public String getFileAccessUrl(String filePath) {
        return localUrl + filePath;
    }

}

 

 2.5、策略上下文实现

我们通过策略上下文来选择使用哪种上传方式。注意点:

当Map集合的Value为接口类型时,Spring会自动对Map集合进行注入。

  • 其中map集合的key为接口对应实现类的BeanName
  • 其中map集合的vlaue为接口对应实现类的实例

其中传入的uploadServiceName就是对应策略类所规定的的BeanName,这里的BeanName就作为选择的条件。

/**
 * 上传策略上下文
 *
 * @author DarkClouds
 * @date 2023/05/13
 */
@Service
public class UploadStrategyContext {
    /**
     * 上传模式
     */
    @Value("${upload.strategy}")
    private String uploadStrategy;

    @Autowired
    private Map<String, UploadStrategy> uploadStrategyMap;

    /**
     * 上传文件
     *
     * @param file 文件
     * @param path 路径
     * @return {@link String} 文件地址
     */
    public String executeUploadStrategy(MultipartFile file, String path) {
        return uploadStrategyMap.get(getStrategy(uploadStrategy)).uploadFile(file, path);
    }

}

 

 

三、总结

上述只是对于策略模式的简单实践。

我们可以通过网站全局配制结合前端界面来完成选择使用哪个平台来进行文件的上传。

当我们选中哪种上传模式,那么后台则会执行该上传方式

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

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

相关文章

基础IO(一)

基础IO&#xff08;一&#xff09; 1.文件基础概念2.C语言接口回顾3.系统接口4.文件系统调用5.三个标准6.输出缓冲区 &#x1f31f;&#x1f31f;hello&#xff0c;各位读者大大们你们好呀&#x1f31f;&#x1f31f; &#x1f680;&#x1f680;系列专栏&#xff1a;【Linux的…

股票K线基础知识2

光头光脚阳线 光头光脚阳线形态与特征描述 光头光脚阳线表示股票的最高价与收盘价相同&#xff0c;最低价与开盘价一样。光头光脚阳线上下不带影线&#xff0c;表明从一开盘买方就积极进攻&#xff0c;中间也可能出现买方与卖方的斗争&#xff0c;但买方发挥了最大力量。始终占…

18.JAVA之三大框架Spring、IOC和DI、拦截器、MVC项目、Mybatis持久层、动态SQL、SSM

一、Spring框架 1.1概述 其中最核心的是&#xff1a;IoC控制反转、DI依赖注入、Bean工厂、SpringAOP面向切面编程、事务控制。 Spring是一个开源框架&#xff0c;是为了解决企业应用程序开发复杂性而创建的。 SpringMVC框架用来接受浏览器的请求和给浏览器做出响应 Mybatis…

C++:设计一个线程安全的队列

文章目录 1. 目的2. 实现&#xff1f;验证&#xff01;makefileQueue 类的 public 成员单元测试 3. 实现 Queue 类的方案 1. 目的 串行的程序只用到单个 CPU 核心&#xff0c; 希望加速整个程序&#xff0c; 考虑使用多线程加速。典型情况下可以找到生产者、消费者&#xff0c…

基于ESP或ESP8266 单通道Lorawan网关硬件制作

软件代码设计资料下载链接》》 基于 Comresult PCB 的单通道网关 介绍 这是 ESP8266 LoRa 网关的最新版本。基于 ESP8266 mcu 的 LoRa 网关在过去几年里有了很大的发展。您想构建一个小型网关并保持尽可能多的 GPIO 引脚可用&#xff0c;Hallard 板是无与伦比的。另一种解决…

C++多态练习题

文章目录 1.虚函数的调用过程2.虚函数例题例题一例题二例题三例题四例题四 1.虚函数的调用过程 从汇编上面来看&#xff1a; []代表指针解引用操作 1.op指向test对象的首地址&#xff08;存放vptr&#xff09;&#xff0c;并存放在eax里面&#xff1b; 2.将eax所指之物(虚表…

使用不同的梯度下降法优化算法

本篇将使用以下几个梯度下降算法进行对比&#xff1a; 不使用任何的优化算法&#xff08;即整批数据集作为一次迭代更新梯度&#xff0c;也叫batch梯度下降&#xff0c;BGD&#xff09; mini-batch梯度下降法&#xff08;Mini-batchGD&#xff09; 使用具有动量的梯度下降算法&…

无标签背景图(负样本)的拼图代码

训练目标检测模型有个很令人头疼的问题&#xff0c;就是有些特征与要训练的特征较为相似的背景区域也被误检出来&#xff08;作为本应不该检测出来的负样本却被误检出为正样本的FP&#xff09;。 根据这一问题的解决办法&#xff0c;除了可以对正样本特征较为模糊或者有歧义的样…

Intel SGX学习笔记(2):用数组向Enclave传递5个数实现自增操作

写在前面 1、实现一个简单的Intel SGX的应用&#xff1a;非安全区定义初始化一个数组&#xff0c;数组里面存储5个数&#xff0c;然后向安全区&#xff08;enclave&#xff09;传入&#xff0c;在安全区中进行加减乘除&#xff0c;然后返回。 2、Intel SGX开发初学整体思路&a…

代码随想录算法训练营day39 | 62.不同路径,63. 不同路径 II

代码随想录算法训练营day39 | 62.不同路径&#xff0c;63. 不同路径 II 62.不同路径解法一&#xff1a;动态规划解法二&#xff1a;深度搜索&#xff08;明天补充&#xff09;解法三&#xff1a;数论&#xff08;明天补充&#xff09; 63. 不同路径 II解法一&#xff1a;动态规…

RuoYi-Vue下载与运行

一、源码下载 若依官网&#xff1a;RuoYi 若依官方网站 鼠标放到"源码地址"上&#xff0c;点击"RuoYi-Vue 前端分离版"。 跳转至Gitee页面&#xff0c;点击"克隆/下载"&#xff0c;复制HTTPS链接即可。 源码地址为&#xff1a;https://gitee.…

左值引用、右值引用,std::move() 的汇编解释

1&#xff1a;左值引用 引用其实还是指针&#xff0c;但回避了指针这个名字。由编译器完成从地址中取值。以vs2019反汇编&#xff1a; 如图&#xff0c;指针和引用的汇编代码完全一样。但引用在高级语言层面更友好&#xff0c;对人脑。比如可以少写一个 * 号和 -> 。 &…

F280049C实现Simulink调制,以及多个PWM实例之间的同步

文章目录 前言基本概念调制发波载波同步问题 前言 最近作实验碰到了载波不同步的问题&#xff0c;以前也有碰到过这个问题&#xff0c;现在终于解决了&#xff0c;做个记录。 为了以示区分&#xff0c;实例指ePWMx&#xff0c;x1,2,3,4,5,6,7,8&#xff1b;通道指ePWMxA/B&am…

如何使用jmeter进行压测

目录 1.概述 2.测试计划、线程组、取样器 3.调试运行 4.请求默认值 5.流量录制 6.模拟时间间隔 7.压力测试 8.报表 1.概述 一款工具&#xff0c;功能往往是很多的&#xff0c;细枝末节的地方也很多&#xff0c;实际的测试工作中&#xff0c;绝大多数场景会用到的也就是…

看大老如何用Postman+Jmeter实现接口实例

一、接口基础 为什么要单独测试接口&#xff1f; 1. 程序是分开开发的&#xff0c;前端还没有开发&#xff0c;后端已经开发完了&#xff0c;可以提前进入测试 2. 接口直接返回的数据------越底层发现bug&#xff0c;修复成本是越低的 3. 接口测试能模拟功能测试不能测到的异常…

数十位高级测工联合讲解Selenium自动化测试框架工作原理

一、Selenium是什么&#xff1f;   用官网的一句话来讲&#xff1a;Selenium automates browsers. Thats it&#xff01;简单来讲&#xff0c;Selenium是一个用于Web应用程序自动化测试工具。Selenium测试直接运行在浏览器中&#xff0c;就像真正的用户在操作浏览器一样。支持…

uvm寄存器模型

一、基础知识 前门访问与后门访问是两种寄存器的访问方式。 所谓前门访问, 指的是通过模拟cpu在总线上发出读指令, 进行读写操作。 在这个过程中, 仿真时间( $time函数得到的时间) 是一直往前走的。而后门访问是与前门访问相对的概念。 它并不通过总线进行读写操作, 而是…

2023/5/14周报

目录 摘要 论文阅读 1、标题和现存问题 2、准备知识 3、模型结构 4、实验准备 5、实验结果 深度学习 1、大气数据和水质数据 2、数据清洗 3、项目框架设定 总结 摘要 本周在论文阅读上&#xff0c;阅读了一篇时空图卷积网络:交通预测的深度学习框架的论文。文章的时…

oracle使用with as创建临时表

一、业务需求 在oracle项目的开发过程中&#xff0c;使用sql编写好对应的分析报表内容后&#xff0c;由于sql分析报表涉及到的一些线别丢失&#xff0c;导致呈现的报表分类统计时固定用醒目颜色标识的统计行数据显示错位&#xff1b;因此需要修复分析报表填充完整的线别。 二、…

LeetCode 62 不同路径

题目&#xff1a; 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#xff09;。问总共有多少条不同的路径&#…