|
4 | 4 | **/ |
5 | 5 | package com.ligg.common.service.impl; |
6 | 6 |
|
| 7 | +import com.ligg.common.config.MinioConfig; |
| 8 | +import com.ligg.common.enums.BusinessStates; |
7 | 9 | import com.ligg.common.enums.ImageType; |
8 | 10 | import com.ligg.common.constants.Constant; |
9 | 11 | import com.ligg.common.service.FileService; |
| 12 | +import io.minio.*; |
| 13 | +import io.minio.errors.*; |
| 14 | +import io.minio.http.Method; |
| 15 | +import lombok.RequiredArgsConstructor; |
10 | 16 | import lombok.extern.slf4j.Slf4j; |
11 | 17 | import net.coobird.thumbnailator.Thumbnails; |
12 | 18 | import org.springframework.beans.factory.annotation.Value; |
|
21 | 27 | import java.nio.file.Files; |
22 | 28 | import java.nio.file.Path; |
23 | 29 | import java.nio.file.Paths; |
| 30 | +import java.security.InvalidKeyException; |
| 31 | +import java.security.NoSuchAlgorithmException; |
24 | 32 | import java.util.UUID; |
25 | 33 |
|
26 | 34 | @Slf4j |
27 | 35 | @Service |
| 36 | +@RequiredArgsConstructor |
28 | 37 | public class FileServiceImpl implements FileService { |
29 | 38 |
|
30 | 39 | // 图片存储根路径 |
31 | 40 | @Value("${file.image.base-path}") |
32 | 41 | private String IMAGE_PATH; |
33 | 42 |
|
| 43 | + private final MinioConfig minioConfig; |
| 44 | + |
| 45 | + private final MinioClient minioClient; |
| 46 | + |
| 47 | + |
34 | 48 | @Override |
35 | 49 | public String uploadImage(MultipartFile imageFile, String path) { |
36 | 50 | if (imageFile.isEmpty()) { |
@@ -76,6 +90,75 @@ public String uploadImage(MultipartFile imageFile, String path) { |
76 | 90 | } |
77 | 91 | } |
78 | 92 |
|
| 93 | + @Override |
| 94 | + public String minioFileUpload(MultipartFile imageFile, String filePath) { |
| 95 | + String bucketName = minioConfig.getBucketName(); |
| 96 | + String datePath = java.time.LocalDate.now().toString(); |
| 97 | + String fullFilePath = String.join("/", (filePath != null ? filePath : ""), datePath, UUID.randomUUID() + "_" + imageFile.getOriginalFilename()); |
| 98 | + //创建存储桶(如果不存在) |
| 99 | + try { |
| 100 | + if (!minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())) { |
| 101 | + minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build()); |
| 102 | + //设置访问权限 |
| 103 | + minioClient.setBucketPolicy(SetBucketPolicyArgs.builder() |
| 104 | + .bucket(bucketName) |
| 105 | + .config(getPublicReadPolicy(bucketName)) |
| 106 | + .build()); |
| 107 | + } |
| 108 | + } catch (Exception e) { |
| 109 | + log.error("创建存储桶失败: {}", e.getMessage()); |
| 110 | + throw new RuntimeException(BusinessStates.INTERNAL_SERVER_ERROR.getMessage()); |
| 111 | + } |
| 112 | + |
| 113 | + //上传文件 |
| 114 | + try (InputStream inputStream = imageFile.getInputStream()) { |
| 115 | + minioClient.putObject(PutObjectArgs.builder() |
| 116 | + .bucket(bucketName) |
| 117 | + .object(fullFilePath) |
| 118 | + .stream(inputStream, imageFile.getSize(), -1) |
| 119 | + .contentType(imageFile.getContentType()) |
| 120 | + .build()); |
| 121 | + } catch (Exception e) { |
| 122 | + log.error("上传文件失败: {}", e.getMessage()); |
| 123 | + } |
| 124 | + |
| 125 | + return minioConfig.getEndpoint() + "/" + bucketName + "/" + fullFilePath; |
| 126 | + } |
| 127 | + |
| 128 | + /** |
| 129 | + * url签名 |
| 130 | + */ |
| 131 | + private String getUrlSignature(String bucketName, String objectName) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException { |
| 132 | + return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder() |
| 133 | + .bucket(bucketName) |
| 134 | + .object(objectName) |
| 135 | + .method(Method.GET) |
| 136 | + .build()); |
| 137 | + } |
| 138 | + |
| 139 | + /** |
| 140 | + * 存储桶配置 |
| 141 | + * "Principal": "*" 表示允许所有用户访问。 |
| 142 | + * "Action": "s3:GetObject" 表示允许下载/读取对象 |
| 143 | + * "Action": "s3:PutObject" 表示允许上传/写入对象 |
| 144 | + * %s 是格式化占位符,会被传入的 bucketName 替换 |
| 145 | + */ |
| 146 | + private String getPublicReadPolicy(String bucketName) { |
| 147 | + return """ |
| 148 | + { |
| 149 | + "Version":"2012-10-17", |
| 150 | + "Statement":[ |
| 151 | + { |
| 152 | + "Effect":"Allow", |
| 153 | + "Principal":"*", |
| 154 | + "Action":["s3:GetObject"], |
| 155 | + "Resource":["arn:aws:s3:::%s/*"] |
| 156 | + } |
| 157 | + ] |
| 158 | + } |
| 159 | + """.formatted( bucketName); |
| 160 | + } |
| 161 | + |
79 | 162 | /** |
80 | 163 | * 获取多质量图片输入流 |
81 | 164 | * |
@@ -153,7 +236,7 @@ public StreamingResponseBody getImageInputStream(String path, String date, Strin |
153 | 236 | @Override |
154 | 237 | @Deprecated |
155 | 238 | public void deleteFile(String filePath) { |
156 | | - Path imagePath = Paths.get(getBasePath(), filePath).toAbsolutePath().normalize() ; |
| 239 | + Path imagePath = Paths.get(getBasePath(), filePath).toAbsolutePath().normalize(); |
157 | 240 | //检查文件是否存在 |
158 | 241 | if (Files.exists(imagePath)) { |
159 | 242 | try { |
@@ -189,6 +272,7 @@ public void deleteFileAsync(String filePath) { |
189 | 272 |
|
190 | 273 | /** |
191 | 274 | * 获取基础路径 |
| 275 | + * |
192 | 276 | * @return 绝对路径 |
193 | 277 | */ |
194 | 278 | private String getBasePath() { |
|
0 commit comments