设计模式-模板模式(清晰明了)
迪丽瓦拉
2025-06-01 05:56:17
0

概述

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

什么是模板模式

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

优点

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

缺点

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

模式实现及实践

在这里插入图片描述

需求

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

Spring Boot 实现

yml

storage:enabled: trueconfig:# 存储类型:local、aliyun、tencent、qiniu、huawei、miniotype: localdomain: http://localhost:8080local:# 存储地址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;@Datapublic 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 {@Beanpublic 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;}@Overridepublic String upload(byte[] data, String path) {return upload(new ByteArrayInputStream(data), path);}@Overridepublic 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;}@Overridepublic String upload(byte[] data, String path) {return upload(new ByteArrayInputStream(data), path);}@Overridepublic 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 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);// 封装voSysFileUploadVO 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);}
}

实现效果

在这里插入图片描述

相关内容