前提

首先你在阅读我这篇文字的时候可能不是很懂模板模式和策略模式,你可以先去网上看一些文章也可以直接看我写的这篇,仔细看我相信你会明白的。这是一篇有关于实战的文字,我会尽可能说的详细一点,我写这篇文字的初衷,

  1. 自己总结一下昨天干了些什么
  2. 巩固一下设计模式
  3. 给需要的小伙伴分享一下

正文部分

准备

首先在yml配置文件中配置一下上传策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
## 上传策略 可选 oss或local,如需自行扩展请创建impl并继承AbstractUploadStrategyImpl类
upload:
mode: oss
local:
# nginx映射本地文件路径,无域名则为 ip:83
url: http://你的文件上传子域名/
# 本地文件存储路径
path: /usr/local/upload/
## 阿里云oss配置
oss:
## Region信息,就是你选择的地步分布,如:上海、杭州
endpoint: 'xxxxx'
## 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
accessKeyId: 'xxxxxx'
accessKeySecret: 'xxxxxxx'
## 填写Bucket名称,例如examplebucket。
bucketName: 'xxxxxxx'

创建一个策略的枚举类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package com.zang.blogz.enmus;

import cn.hutool.core.util.StrUtil;
import com.zang.blogz.steam.stream.Steam;
import lombok.AllArgsConstructor;
import lombok.Getter;

import java.util.function.Predicate;

/**
* @Author: ZVerify
* @Description: TODO 上传策略枚举类
* @DateTime: 2022/8/31 17:20
**/

@Getter
@AllArgsConstructor
public enum UploadStrategyEnum {

/**
* oss
*/
OSS("oss", "ossUploadStrategyImpl"),
/**
* 本地
*/
LOCAL("local", "localUploadStrategyImpl");


/**
* 模式
*/
private final String mode;

/**
* 策略
*/
private final String strategy;

/**
* 获取策略
* @param mode 模式
* @return {@link String} 搜索策略
*/
public static String getStrategy(String mode){

return Steam.of(UploadStrategyEnum.values()).findFirst(strategy(mode)).orElse(OSS).getStrategy();

}

public static Predicate<? super UploadStrategyEnum> strategy(String mode){

return val-> StrUtil.equals(val.getMode(), mode);
}


}

到时候我们可以通过调用的方法在这里匹配到我们要选择的策略的服务名称,默认为OSS上传服务

创建一个UploadStrategy接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* @Author: ZVerify
* @Description: TODO 上传策略
* @DateTime: 2022/8/31 18:49
**/
public interface UploadStrategy {

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

我们的策略都会去实现这个接口,因为我们也使用了模板模式到时候只需要抽象模板去实现这个接口就可以了

上传策略上下文

这个类用于我们去进行策略的选择

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* @Author: ZVerify
* @Description: TODO 上传策略上下文
* @DateTime: 2022/8/31 18:42
**/
@Service
public class UploadStrategyContext {

/**
* 上传模式
*/
@Value("${upload.mode}")
private String uploadMode;

@Autowired
private Map<String, UploadStrategy> uploadStrategyMap;


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

Opp<String> ofTry = Opp.ofTry(() -> uploadStrategyMap.get(UploadStrategyEnum.getStrategy(uploadMode))
.uploadFile(file, path), NullPointerException.class);

if (Opp.of(ofTry.getException()).isNonNull()) {

throw new BizException("抱歉,请检查配置文件,当前没有你所选择的策略");
}
return ofTry.get();
}

}

首先从配置文件读到当前选择的上传模式,然后调用我们策略的枚举类里边的方法得到当前使用的策略的服务名称然后从map中通过服务名称拿到服务的实体去执行uploadFile(file, path)方法现在已经选择好了我们的策略了到了我们的抽象模板了

抽象模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/**
* @Author: ZVerify
* @Description: TODO 抽象上传模板
* @DateTime: 2022/8/31 18:55
**/
public abstract class AbstractUploadStrategyImpl implements UploadStrategy {


@Override
public String uploadFile(MultipartFile file, String path) {

Opp<String> uploadOpp = Opp.ofTry(() -> {
// 获取文件md5值
String md5 = FileUtils.getMd5(file.getInputStream());
// 获取文件扩展名
String extName = FileUtils.getExtName(file.getOriginalFilename());
// 重新生成文件名
String fileName = md5 + extName;
// 判断文件是否已存在
if (!exists(path + fileName)) {
// 不存在则继续上传
upload(path, fileName, file.getInputStream());
}
// 返回文件访问路径
return getFileAccessUrl(path + fileName);
});

if (Opp.of(uploadOpp.getException()).isNonNull()) {

throw new BizException("文件上传失败");
}

return uploadOpp.get();
}

/**
* 判断文件是否存在
*
* @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}
*/
public abstract String getFileAccessUrl(String filePath);
}

在抽象模板中去实现UploadStrategy,重写其uploadFile,然后声明三个抽象方法,因为我们的上传总体流程是一样的但是其内部的实现是不一样的,所以这就是我们的模板,我们策略的具体实现只需要继承抽象类并重写这三个方法就可以了

oss上传具体实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
* @Author: ZVerify
* @Description: TODO oss上传策略
* @DateTime: 2022/8/31 19:52
**/
@Service("ossUploadStrategyImpl")
public class OssUploadStrategyImpl extends AbstractUploadStrategyImpl {

@Autowired
private OssConfigProperties ossConfigProperties;

@Value("${upload.oss.bucketName}")
private String P;

@Value("${upload.oss.endpoint}")
private String I;

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

@Override
public void upload(String path, String fileName, InputStream inputStream) throws IOException {

getOssClient().putObject(ossConfigProperties.getBucketName(), path + fileName, inputStream);
}

@Override
public String getFileAccessUrl(String filePath) {

String URL = "https://" + P + "." + I + "/";

return URL + filePath;
}

private OSS getOssClient(){

return new OSSClientBuilder().build(ossConfigProperties.getEndpoint(), ossConfigProperties.getAccessKeyId(), ossConfigProperties.getAccessKeySecret());

}
}

local上传实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
@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 BizException("创建目录失败");
}
}
// 写入文件
File file = new File(localPath + path + fileName);

if (file.createNewFile()) {

BufferedInputStream bis = new BufferedInputStream(inputStream);
BufferedOutputStream bos = new BufferedOutputStream(Files.newOutputStream(file.toPath()));

byte[] bytes = new byte[1024];
int length;

while ((length = bis.read(bytes)) != -1) {

bos.write(bytes, 0, length);
}

bos.flush();
inputStream.close();
bis.close();
bos.close();
}
}

@Override
public String getFileAccessUrl(String filePath) {

return localUrl + filePath;
}
}