weblog/doc/8、对象存储服务搭建与开发/8.3 策略模式 + 工厂模式:实现文件处理可扩展.md
2025-02-17 10:05:44 +08:00

10 KiB
Raw Blame History

上小节 中,我们已经将对象存储服务项目的基础骨架搭建好了。本小节中,进入到代码层的实现,通过设计模式中的策略模式 + 工厂模式实现文件处理的可扩展性。

业务背景

从业务层面分析,对象存储微服务底层用到的产品,暂时规划的是,要使用 Minio 和阿里云 OSS但是后续随着业务的推进可能还会引入新的产品如七牛云等等。那么当引入新的产品时如何保证代码的可扩展性、可维护性呢代码上要如何设计这就得今天的主角登场了。

什么是策略模式?什么是工厂模式?

策略模式: 定义一组算法类,将每个算法分别封装,让它们可以互相替代,属于行为型设计模式的一种。而工厂模式则属于创建型设计模式的一种,用于解耦对象的创建和使用。

听上去好像比较难理解,接下来通过实操,来感受一下它们的好处,先看下代码大致结构,如下:

策略接口

策略的定义

策略的定义比较简单,包含一个策略接口和一组实现该接口的策略类。因为所有的策略类都实现了相同的接口,调用者基于接口使用,所以可以灵活的替换不同策略,调用者是无感知的。

定义接口

编辑 xiaohashu-oss-biz 模块,创建一个 /strategy 包,用于存放策略相关代码,并创建 FileStrategy 文件策略接口,代码如下:

package com.quanxiaoha.xiaohashu.oss.biz.strategy;

import org.springframework.web.multipart.MultipartFile;

/**
 * @author: 犬小哈
 * @date: 2024/6/27 19:47
 * @version: v1.0.0
 * @description: 文件策略接口
 **/
public interface FileStrategy {

    /**
     * 文件上传
     * 
     * @param file
     * @param bucketName
     * @return
     */
    String uploadFile(MultipartFile file, String bucketName);

}

Tip

: 目前该接口内,只声明了一个当前业务需要的上传文件方法,等后续随着业务的迭代,如果有需要,我们再另行创建别的方法。

Strategy 策略实现类

Minio 策略类

接着,在 /strategy 包下创建 /impl 包,用于放置一组策略实现类。首先是 Minio 策略类,代码如下:

package com.quanxiaoha.xiaohashu.oss.biz.strategy.impl;

import com.quanxiaoha.xiaohashu.oss.biz.strategy.FileStrategy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.multipart.MultipartFile;

/**
 * @author: 犬小哈
 * @date: 2024/6/27 19:47
 * @version: v1.0.0
 * @description: TODO
 **/
@Slf4j
public class MinioFileStrategy implements FileStrategy  {

    @Override
    public String uploadFile(MultipartFile file, String bucketName) {
        log.info("## 上传文件至 Minio ...");
        return null;
    }
}

Tip

: 先只是把架子搭好,具体逻辑实现先不写,打印一行日志,用于等会测试时,观察正在使用的策略类型。

阿里云 OSS 策略类

接着是阿里云 OSS 策略类,代码如下:

package com.quanxiaoha.xiaohashu.oss.biz.strategy.impl;

import com.quanxiaoha.xiaohashu.oss.biz.strategy.FileStrategy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.multipart.MultipartFile;

/**
 * @author: 犬小哈
 * @date: 2024/6/27 19:47
 * @version: v1.0.0
 * @description: TODO
 **/
@Slf4j
public class AliyunOSSFileStrategy implements FileStrategy  {

    @Override
    public String uploadFile(MultipartFile file, String bucketName) {
        log.info("## 上传文件至阿里云 OSS ...");
        return null;
    }
}

添加配置

为了能够按需初始化具体的策略实现类,编辑 application.yml 文件,如下图所示:

添加一个类型标识,代码如下:

storage:
  type: aliyun # 对象存储类型

创建 Factory 工厂类

接着,再创建一个 /fatory 包,并在其中创建一个 FileStrategyFactory 文件策略工厂类,用于按需初始化具体的策略实现类,代码如下:

package com.quanxiaoha.xiaohashu.oss.biz.factory;

import com.quanxiaoha.xiaohashu.oss.biz.strategy.FileStrategy;
import com.quanxiaoha.xiaohashu.oss.biz.strategy.impl.AliyunOSSFileStrategy;
import com.quanxiaoha.xiaohashu.oss.biz.strategy.impl.MinioFileStrategy;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author: 犬小哈
 * @date: 2024/6/27 19:44
 * @version: v1.0.0
 * @description: TODO
 **/
@Configuration
public class FileStrategyFactory {

    @Value("${storage.type}")
    private String strategyType;

    @Bean
    public FileStrategy getFileStrategy() {
        if (StringUtils.equals(strategyType, "minio")) {
            return new MinioFileStrategy();
        } else if (StringUtils.equals(strategyType, "aliyun")) {
            return new AliyunOSSFileStrategy();
        }

        throw new IllegalArgumentException("不可用的存储类型");
    }

}

逻辑比较简单,读取配置文件中的 storage.type ,根据不同类型,初始化不同的策略实现类,并注入到 Spring 容器中。这种方式可以保证, Spring 容器中只有自己需要的策略实现类,而不是都注入到 Spring 容器中去。

编写 service 业务层

再创建一个 /service 业务层包,并创建 FileService 文件业务接口,声明一个上传文件方法,代码如下:

package com.quanxiaoha.xiaohashu.oss.biz.service;

import com.quanxiaoha.framework.common.response.Response;
import org.springframework.web.multipart.MultipartFile;

/**
 * @author: 犬小哈
 * @date: 2024/4/11 17:12
 * @version: v1.0.0
 * @description: TODO
 **/
public interface FileService {

    /**
     * 上传文件
     * 
     * @param file
     * @return
     */
    Response<?> uploadFile(MultipartFile file);
}

/service 包下创建 /impl 包,用于存放实现类,并创建 FileServiceImpl , 代码如下:

package com.quanxiaoha.xiaohashu.oss.biz.service.impl;

import com.quanxiaoha.framework.common.response.Response;
import com.quanxiaoha.xiaohashu.oss.biz.service.FileService;
import com.quanxiaoha.xiaohashu.oss.biz.strategy.FileStrategy;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

/**
 * @author: 犬小哈
 * @date: 2024/4/11 17:12
 * @version: v1.0.0
 * @description: TODO
 **/
@Service
@Slf4j
public class FileServiceImpl implements FileService {

    @Resource
    private FileStrategy fileStrategy;

    @Override
    public Response<?> uploadFile(MultipartFile file) {
        // 上传文件到
        fileStrategy.uploadFile(file, "xiaohashu");

        return Response.success();
    }
}

调用者直接面向 FileStrategy 接口,至于其底层具体的实现策略类,无需关心,直接调用相关方法就行了。这里依然只是搭个架子,具体的业务逻辑先不写,等后续小节中再来补充。

编写 controller 控制器

接下来创建 /controller 包,并新建 FileController 类,定义一个 /file/upload 接口,注意,提交方式是 MULTIPART_FORM_DATA_VALUE, 代表此接口通过表单方式提交,而不是 JSON 方式,因为涉及到文件上传。代码如下:

package com.quanxiaoha.xiaohashu.oss.biz.controller;

import com.quanxiaoha.framework.common.response.Response;
import com.quanxiaoha.xiaohashu.oss.biz.service.FileService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

/**
 * @author: 犬小哈
 * @date: 2024/4/4 13:22
 * @version: v1.0.0
 * @description: 文件
 **/
@RestController
@RequestMapping("/file")
@Slf4j
public class FileController {

    @Resource
    private FileService fileService;

    @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public Response<?> uploadFile(@RequestPart(value = "file") MultipartFile file) {
        return fileService.uploadFile(file);
    }

}

自测一波

整个编写完成后,重启认证服务。通过 Apipost 测试一波 localhost:8081/file/upload 接口,注意,这里我们直接调用的对象存储服务,而不是通过网关转发,因为服务注册发现还没有配置上。

  • 表单方式提交,需要在 Header 头中指定 Content-Typeapplication/x-www-form-urlencoded;

表单提交需勾选 form-data 选项,添加一个 file 字段,并选择一个本地图片,点击发送按钮上传文件。观察后台控制台输出,因为当前配置的对象存储类型为 aliyun, 可以看到,具体干活的实际上是 AliyunOSSFileStrategy 策略实现类:

编辑 applicaiton.yml , 将类型修改为 minio, 重启服务,再次测试文件上传接口:

storage:
  type: minio # 对象存储类型

可以看到,服务是能够根据配置的类型,实例化不同的策略实现类的。后续如果有新的产品引入,如七牛云,只需再新建一个七牛云的策略实现类,并在 FileStrategyFactory 工厂类中初始化即可,service 层无需修改任何东西,易于扩展与维护。

本小节源码下载

https://t.zsxq.com/X0m7o