使用策略模式实现阿里云OSS和MINIO后端签名前端直传
Contents
使用策略模式实现阿里云OSS和MINIO后端签名前端直传
需求
- 无缝(通过配置,不改代码)切换外网模式和内网模式(阿里云和MINIO)
- 文件前端直传(需要后端签名),节约自己服务器的网络资源
- 私有读写,如果需要向后端发起请求获取签名
实现
UML图
工厂模式实现(后端代码)
-
配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
oss: # 使用oss的模式 # 1. ALIYUN # 2. MINIO mode: ALIYUN # 上传目录,这里如果不想加,别的地方到处加 dir: test/ # 上传url过期时间 单位 s updateExpiryTime: 600 # 查看过期时间 单位 s downloadExpiryTime: 600 #阿里oss配置 aliyun: endpoint: keyid: keysecret: bucketname: #minio配置 minio: endpoint: keyid: keysecret: bucketName:
-
编写OSS接口,每一种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
public interface OssInterface { /** * 文件上传 * @param file * @param objectName * @return */ String uploadFile(MultipartFile file, String objectName); /** * 判断文件是否存在 * @param objectName * @return */ boolean existFile(String objectName); /** * 获取上传签名 * @return */ OssRequestData getPolicy(String dir,String fileName); /** * 获取查看签名 */ String getExpiration(String objectName); }
-
编写实现类
-
AliOssService
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 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
@Service public class AliOssService implements OssInterface { String endpoint = AliyunConstantPropertiesUtils.END_POINT; String accessKeyId = AliyunConstantPropertiesUtils.KEY_ID; String accessKeySecret = AliyunConstantPropertiesUtils.KEY_SECRET; String bucketName = AliyunConstantPropertiesUtils.BUCKET_NAME; /** * 文件上传 * * @param file * @return */ @Override public String uploadFile(MultipartFile file, String objectName) { OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); try { // 上传文件流。 InputStream inputStream = file.getInputStream(); //获取文件名称 //第一个参数 Bucket名称 //第二个参数 文件路径和文件名称 //第三个参数 文件输入流 ossClient.putObject(bucketName, objectName, inputStream); // 关闭OSSClient。 ossClient.shutdown(); //上传之后的文件路径返回 //需要手动拼接 return "https://" + bucketName + "." + endpoint + "/" + objectName; } catch (Exception e) { e.printStackTrace(); return null; } } /** * 判断文件是否存在 */ @Override public boolean existFile(String objectName) { // 创建OSSClient实例。 OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); boolean found = ossClient.doesObjectExist(bucketName, objectName); ossClient.shutdown(); return found; } /** * 签名 */ @Override public OssRequestData getPolicy(String dir,String fileName){ fileName = UUID.randomUUID()+"_"+fileName; String host = "https://" + bucketName + "." + endpoint; // 创建OSSClient实例。 OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); OssRequestData ossRequestData =null; try { long expireEndTime = System.currentTimeMillis() + OssConstantPropertiesUtils.UPDATE_EXPIRY_TIME * 1000; Date expiration = new Date(expireEndTime); // PostObject请求最大可支持的文件大小为5 GB,即CONTENT_LENGTH_RANGE为5*1024*1024*1024。 PolicyConditions policyConds = new PolicyConditions(); policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000); policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir+fileName); String postPolicy = ossClient.generatePostPolicy(expiration, policyConds); byte[] binaryData = postPolicy.getBytes(StandardCharsets.UTF_8); String encodedPolicy = BinaryUtil.toBase64String(binaryData); String postSignature = ossClient.calculatePostSignature(postPolicy); FormData formData = new FormData(accessKeyId,encodedPolicy,postSignature,dir,dir+fileName,fileName,String.valueOf(expireEndTime / 1000)); ossRequestData = new OssRequestData(host,"file",true,"post",formData); } catch (Exception e) { System.out.println(e.getMessage()); } finally { ossClient.shutdown(); } return ossRequestData; } /** * 获取签名授权访问 * @param objectName * @return */ @Override public String getExpiration(String objectName){ OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); Date expiration = new Date(System.currentTimeMillis() + OssConstantPropertiesUtils.DOWNLOAD_EXPIRY_TIME * 1000); URL url = ossClient.generatePresignedUrl(bucketName, objectName, expiration); ossClient.shutdown(); return url.toString(); } }
-
MinIOOssService
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 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
@Service public class MinIOOssService implements OssInterface { String endpoint = MinIOConstantPropertiesUtils.END_POINT; String accessKeyId = MinIOConstantPropertiesUtils.KEY_ID; String accessKeySecret = MinIOConstantPropertiesUtils.KEY_SECRET; String bucketName = MinIOConstantPropertiesUtils.BUCKET_NAME; /** * 文件上传 * * @param file * @return */ @Override public String uploadFile(MultipartFile file, String objectName) { MinioClient minioClient = MinioClient.builder() .endpoint(endpoint) .credentials(accessKeyId, accessKeySecret) .build(); InputStream in = null; try { in = file.getInputStream(); minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName) .stream(in, file.getSize(), -1).contentType(file.getContentType()).build()); } catch (IOException e) { e.printStackTrace(); throw new RuntimeException("文件异常"); } catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | InvalidResponseException | NoSuchAlgorithmException | ServerException | XmlParserException e) { e.printStackTrace(); throw new RuntimeException("上传异常"); } finally { try { if (in != null) { in.close(); } } catch (IOException e) { e.printStackTrace(); } } return endpoint + "/" + objectName; } /** * 判断文件是否存在 */ @Override public boolean existFile(String objectName) { return false; } /** * 上传签名 */ @Override public OssRequestData getPolicy(String dir, String fileName) { fileName = UUID.randomUUID() + "_" + fileName; MinioClient minioClient = MinioClient.builder() .endpoint(endpoint) .credentials(accessKeyId, accessKeySecret) .build(); String product = null; OssRequestData ossRequestData =null; try { product = minioClient.getPresignedObjectUrl( GetPresignedObjectUrlArgs.builder() .method(Method.PUT) .bucket(bucketName) .object(dir + fileName) .expiry(OssConstantPropertiesUtils.UPDATE_EXPIRY_TIME) .build()); } catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | XmlParserException | ServerException e) { e.printStackTrace(); throw new RuntimeException("获取签名异常"); } FormData formData = new FormData(accessKeyId,null,null,dir,dir+fileName,fileName,null); ossRequestData = new OssRequestData(product,"data",false,"put",formData); return ossRequestData; } /** * 查看签名 */ @Override public String getExpiration(String objectName) { MinioClient minioClient = MinioClient.builder() .endpoint(endpoint) .credentials(accessKeyId, accessKeySecret) .build(); String presignedObjectUrl = null; try { presignedObjectUrl = minioClient.getPresignedObjectUrl( GetPresignedObjectUrlArgs.builder() .method(Method.GET) .bucket(bucketName) .object(objectName) .expiry(OssConstantPropertiesUtils.DOWNLOAD_EXPIRY_TIME) .build() ); } catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | XmlParserException | ServerException e) { e.printStackTrace(); throw new RuntimeException("获取签名异常"); } return presignedObjectUrl; } }
-
-
工厂实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
@Component public class OssFactory { @Value("${oss.mode}") private String endpoint; public OssInterface getOssService() { if ("ALIYUN".equals(endpoint)) { return new AliOssService(); } else if ("MINIO".equals(endpoint)) { return new MinIOOssService(); } else { throw new RuntimeException("没有这个选项,宝"); } } }
-
Controller使用
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
@RestController @CrossOrigin public class OssController { @Autowired private OssFactory ossFactory; @Operation(summary = "上传文件") @PostMapping(value = "uploadFile", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public String uploadFile(@RequestPart MultipartFile file){ OssInterface ossService = ossFactory.getOssService(); return ossService.uploadFile(file, OssConstantPropertiesUtils.DIR+UUID.randomUUID()+file.getOriginalFilename()); } /** * * @return */ @Operation(summary = "获取上传签名") @GetMapping(value = "getPolicy") public OssRequestData getPolicy(String fileName){ OssInterface ossService = ossFactory.getOssService(); return ossService.getPolicy(OssConstantPropertiesUtils.DIR,fileName); } /** * 可以添加redis对相同文件的请求进行缓存 * @param objectName * @return */ @Operation(summary = "获取查看签名") @GetMapping(value = "getExpiration") public String getExpiration(String objectName){ OssInterface ossService = ossFactory.getOssService(); return ossService.getExpiration(objectName); } }
-
请求签名时返回pojo
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 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
public class OssRequestData implements Serializable { /** 请求路径 */ private String host; /** 文件参数key值 */ private String fileFieldName; /** 参数是否使用FormData */ private boolean withFormData; /** 请求方式 */ private String method; /** formData参数 */ private FormData formData; public OssRequestData(String host, String fileFieldName, boolean withFormData, String method, FormData formData) { this.host = host; this.fileFieldName = fileFieldName; this.withFormData = withFormData; this.method = method; this.formData = formData; } public String getHost() { return host; } public void setHost(String host) { this.host = host; } public String getFileFieldName() { return fileFieldName; } public void setFileFieldName(String fileFieldName) { this.fileFieldName = fileFieldName; } public boolean isWithFormData() { return withFormData; } public void setWithFormData(boolean withFormData) { this.withFormData = withFormData; } public String getMethod() { return method; } public void setMethod(String method) { this.method = method; } public FormData getFormData() { return formData; } public void setFormData(FormData formData) { this.formData = formData; } } public class FormData implements Serializable { private String OSSAccessKeyId; private String policy; private String signature; private String dir; private String key; private String fileName; private String expire; public FormData(String OSSAccessKeyId, String policy, String signature, String dir, String key, String fileName, String expire) { this.OSSAccessKeyId = OSSAccessKeyId; this.policy = policy; this.signature = signature; this.dir = dir; this.key = key; this.fileName = fileName; this.expire = expire; } public String getOSSAccessKeyId() { return OSSAccessKeyId; } public void setOSSAccessKeyId(String OSSAccessKeyId) { this.OSSAccessKeyId = OSSAccessKeyId; } public String getPolicy() { return policy; } public void setPolicy(String policy) { this.policy = policy; } public String getSignature() { return signature; } public void setSignature(String signature) { this.signature = signature; } public String getDir() { return dir; } public void setDir(String dir) { this.dir = dir; } public String getKey() { return key; } public void setKey(String key) { this.key = key; } public String getFileName() { return fileName; } public void setFileName(String fileName) { this.fileName = fileName; } public String getExpire() { return expire; } public void setExpire(String expire) { this.expire = expire; } }
前端适配
|
|
关于签名直传
流程图
文件上传以及文件查看预览都是这个流程
项目地址
https://github.com/coderabbit214/parent-demo/tree/main/oss_demo