设计模式-模板模式(清晰明了)

概述

最近在想做个cloud项目,gitee上找了个模板项目,后端使用到 Nacos、Gateway、Security等技术,看到 文件上传 模板模式+策略模式 用得很好,再次分享一下。

什么是模板模式

一个抽象类中,有一个主方法,再定义1…n个方法,可以是抽象的,也可以是实际的方法,定义一个类,继承该抽象类,重写抽象方法,通过调用抽象类,实现对子类的调用。

优点

  • 封装不变的部分,将不变的部分抽取出来;
  • 扩展可变部分,将可变的设置抽象方法,让具体子类来实现。
  • 抽取的公共代码,便于后期维护。
  • 行为有基类来控制,具体操作有子类实现。

缺点

  • 每一个不同的实现都需要有一个子类来实现,这样就会导致类的数量大大的增加,使得系统更加庞大。

模式实现及实践

在这里插入图片描述

需求

比如我们有一个文件上传功能,后端保存文件的方式很多,比如本地上传、阿里云OSS、华为、腾讯等等,根据模板模式 + 策略模式实现文件上传

Spring Boot 实现

yml

storage:
  enabled: true
  config:
    # 存储类型:local、aliyun、tencent、qiniu、huawei、minio
    type: local
    domain: http://localhost:8080
  local:
    # 存储地址
    path: /package/web_project/fast_shaking_cloud_package/

StorageProperties(yml映射)

**
 * 存储配置项
 *
 * @author lanys
 */
@Data
@ConfigurationProperties(prefix = "storage")
public class StorageProperties {
    /**
     * 是否开启存储
     */
    private boolean enabled;
    /**
     * 通用配置项
     */
    private StorageConfig config;
    /**
     * 本地配置项
     */
    private LocalStorageProperties local;
    /**
     * 阿里云配置项
     */
    private AliyunStorageProperties aliyun;

    @Data
    public static class StorageConfig {
        /**
         * 访问域名
         */
        private String domain;
        /**
         * 配置路径前缀
         */
        private String prefix;
        /**
         * 存储类型
         */
        private StorageTypeEnum type;
    }

    @Bean
    @ConfigurationProperties(prefix = "storage.local")
    public LocalStorageProperties localStorageProperties() {
        return new LocalStorageProperties();
    }

    @Bean
    @ConfigurationProperties(prefix = "storage.aliyun")
    public AliyunStorageProperties aliyunStorageProperties() {
        return new AliyunStorageProperties();
    }
}

LocalStorageProperties(本地映射实体)

/**
 * 本地存储配置项
 *
 * @author lanys
 */
@Data
public class LocalStorageProperties {
    /**
     * 本地存储路径
     */
    private String path;
    /**
     * 资源起始路径
     */
    private String url = "upload";
}

AliyunStorageProperties(阿里云映射实体)

/**
 * 阿里云存储配置项
 *
 * @author 阿沐 babamu@126.com
 */
@Data
public class AliyunStorageProperties {
    private String endPoint;
    private String accessKeyId;
    private String accessKeySecret;
    private String bucketName;
}

StorageConfiguration (映射配置,策略模式)

@Configuration
@EnableConfigurationProperties(StorageProperties.class)
@ConditionalOnProperty(prefix = "storage", value = "enabled")
public class StorageConfiguration {

    @Bean
    public StorageService storageService(StorageProperties properties) {
        if (properties.getConfig().getType() == StorageTypeEnum.LOCAL) {
            return new LocalStorageService(properties);
        } else if (properties.getConfig().getType() == StorageTypeEnum.ALIYUN) {
            return new AliyunStorageService(properties);
        } 
        return null;
    }

}

StorageService (抽象模板)

/**
 * 存储服务
 *
 * @author lanys
 */
public abstract class StorageService {
    public StorageProperties properties;

    /**
     * 根据文件名,生成带时间戳的新文件名
     *
     * @param fileName 文件名
     * @return 返回带时间戳的文件名
     */
    public String getNewFileName(String fileName) {
        // 主文件名,不包含扩展名
        String prefix = FileNameUtil.getPrefix(fileName);
        // 文件扩展名
        String suffix = FileNameUtil.getSuffix(fileName);
        // 把当天HH:mm:ss,转换成秒
        long time = DateUtil.timeToSecond(DateUtil.formatTime(new Date()));
        // 新文件名
        return prefix + "_" + time + "." + suffix;
    }

    /**
     * 生成路径,不包含文件名
     *
     * @return 返回生成的路径
     */
    public String getPath() {
        // 文件路径
        String path = DateUtil.format(new Date(), "yyyyMMdd");

        // 如果有前缀,则也带上
        if (StringUtils.hasText(properties.getConfig().getPrefix())) {
            path = properties.getConfig().getPrefix() + "/" + path;
        }

        return path;
    }

    /**
     * 根据文件名,生成路径
     *
     * @param fileName 文件名
     * @return 生成文件路径
     */
    public String getPath(String fileName) {
        return getPath() + "/" + getNewFileName(fileName);
    }

    /**
     * 文件上传(抽象方法)
     *
     * @param data 文件字节数组
     * @param path 文件路径,包含文件名
     * @return 返回http地址
     */
    public abstract String upload(byte[] data, String path);

    /**
     * 文件上传(抽象方法)
     *
     * @param inputStream 字节流
     * @param path        文件路径,包含文件名
     * @return 返回http地址
     */
    public abstract String upload(InputStream inputStream, String path);
}

LocalStorageService(本地具体实现)

public class LocalStorageService extends StorageService {

    public LocalStorageService(StorageProperties properties) {
        this.properties = properties;
    }

    @Override
    public String upload(byte[] data, String path) {
        return upload(new ByteArrayInputStream(data), path);
    }


    @Override
    public String upload(InputStream inputStream, String path) {

        try {
            File file = new File(properties.getLocal().getPath() + File.separator + path);

            // 没有目录,则自动创建目录
            File parent = file.getParentFile();
            if (parent != null && !parent.mkdirs() && !parent.isDirectory()) {
                throw new IOException("目录 '" + parent + "' 创建失败");
            }

            FileCopyUtils.copy(inputStream, Files.newOutputStream(file.toPath()));
        } catch (Exception e) {
            throw new ServerException("上传文件失败:", e);
        }

        return properties.getConfig().getDomain() + "/" + properties.getLocal().getUrl() + "/" + path;
    }
}

AliyunStorageService(OSS具体实现)

public class AliyunStorageService extends StorageService {
    
    public AliyunStorageService(StorageProperties properties) {
        this.properties = properties;
    }

    @Override
    public String upload(byte[] data, String path) {
        return upload(new ByteArrayInputStream(data), path);
    }

    @Override
    public String upload(InputStream inputStream, String path) {
        OSS client = new OSSClientBuilder().build(properties.getAliyun().getEndPoint(),
                properties.getAliyun().getAccessKeyId(), properties.getAliyun().getAccessKeySecret());
        try {
            client.putObject(properties.getAliyun().getBucketName(), path, inputStream);
        } catch (Exception e) {
            throw new ServerException("上传文件失败:", e);
        } finally {
            if (client != null) {
                client.shutdown();
            }
        }

        return properties.getConfig().getDomain() + "/" + path;
    }

}

测试

@RestController
@RequestMapping("file")
@Tag(name = "文件上传")
@AllArgsConstructor
public class SysFileUploadController {
    private final StorageService storageService;

    @PostMapping("upload")
    @Operation(summary = "上传")
    public Result<SysFileUploadVO> upload(@RequestParam("file") MultipartFile file) throws Exception {
        if (file.isEmpty()) {
            return Result.error("请选择需要上传的文件");
        }

        // 上传路径
        String path = storageService.getPath(file.getOriginalFilename());
        // 上传文件
        String url = storageService.upload(file.getBytes(), path);

        // 封装vo
        SysFileUploadVO vo = new SysFileUploadVO();
        vo.setUrl(url);
        vo.setSize(file.getSize());
        vo.setName(file.getOriginalFilename());
        vo.setPlatform(storageService.properties.getConfig().getType().name());
        return Result.ok(vo);
    }
}

实现效果

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值