feat: 图片上传日期路径优化和添加图片上传压缩功能

This commit is contained in:
郝先瑞 2022-07-17 17:17:56 +08:00
parent a6baf6d452
commit 2e8fb1bd19
4 changed files with 121 additions and 37 deletions

View File

@ -0,0 +1,54 @@
package com.youlai.common.util;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
/**
* 文件工具类
*
* @author
* @date 2022/7/17
*/
public class ImgUtils {
private static final String[] imgSuffixArr = {"bmp", "dib", "gif", "jfif", "jpe", "jpeg", "jpg", "png", "tif", "tiff", "ico"};
/**
* 是否图片判断
*
* @param fileName
* @return
*/
public static boolean isImg(String fileName) {
if (StrUtil.isBlank(fileName)) {
return false;
}
String fileSuffix = FileUtil.getSuffix(fileName);
for (String imgSuffix : imgSuffixArr) {
if (fileSuffix.equals(imgSuffix)) {
return true;
}
}
return false;
}
/**
* 根据图片大小获取压缩比
*
* @param size 图片大小(单位Bytes)
* @return 压缩比例
*/
public static float getCompressQuality(long size) {
if (size <= 0.1 * 1024 * 1024) {
return 0.5f;
} else if (size > 0.1 * 1024 * 1024 && size <= 1 * 1024 * 1024) {
return 0.3f;
} else {
// 大于1M
return 0.1f;
}
}
}

View File

@ -28,5 +28,11 @@
<groupId>io.minio</groupId> <groupId>io.minio</groupId>
<artifactId>minio</artifactId> <artifactId>minio</artifactId>
</dependency> </dependency>
<!-- 图片压缩 -->
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -15,7 +15,6 @@ import org.springframework.web.multipart.MultipartFile;
@RequestMapping("/api/v1/files") @RequestMapping("/api/v1/files")
@RequiredArgsConstructor @RequiredArgsConstructor
public class FileController { public class FileController {
private final MinioService minioService; private final MinioService minioService;
@PostMapping @PostMapping
@ -23,7 +22,7 @@ public class FileController {
@SneakyThrows @SneakyThrows
public Result<String> uploadFile( public Result<String> uploadFile(
@ApiParam("文件") @RequestParam(value = "file") MultipartFile file, @ApiParam("文件") @RequestParam(value = "file") MultipartFile file,
@ApiParam("存储桶名称(非必须,微服务有单独默认存储桶)") @RequestParam(value = "bucketName", required = false) String bucketName @ApiParam("存储桶名称(没值默认存储桶)") @RequestParam(value = "bucketName", required = false) String bucketName
) { ) {
String path = minioService.putObject(file, bucketName); String path = minioService.putObject(file, bucketName);
return Result.success(path); return Result.success(path);

View File

@ -1,19 +1,26 @@
package com.youlai.common.file.service; package com.youlai.common.file.service;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.youlai.common.util.ImgUtils;
import io.minio.*; import io.minio.*;
import io.minio.http.Method; import io.minio.http.Method;
import lombok.Setter; import lombok.Setter;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.coobird.thumbnailator.Thumbnails;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream; import java.io.*;
import java.time.LocalDateTime;
@Component @Component
@ -51,6 +58,11 @@ public class MinioService implements InitializingBean {
@Setter @Setter
private String defaultBucket; private String defaultBucket;
@Value("${minio.img_compression_enabled:false}")
private boolean imgCompressionEnabled;
private MinioClient minioClient; private MinioClient minioClient;
@Override @Override
@ -61,9 +73,7 @@ public class MinioService implements InitializingBean {
Assert.notBlank(secretKey, "MinIO secretKey不能为空"); Assert.notBlank(secretKey, "MinIO secretKey不能为空");
this.minioClient = MinioClient.builder() this.minioClient = MinioClient.builder()
//.endpoint(endpoint, 443, true) //.endpoint(endpoint, 443, true)
.endpoint(endpoint) .endpoint(endpoint).credentials(accessKey, secretKey).build();
.credentials(accessKey, secretKey)
.build();
} }
/** /**
@ -73,19 +83,15 @@ public class MinioService implements InitializingBean {
*/ */
@SneakyThrows @SneakyThrows
public void createBucketIfAbsent(String bucketName) { public void createBucketIfAbsent(String bucketName) {
BucketExistsArgs bucketExistsArgs = BucketExistsArgs.builder() BucketExistsArgs bucketExistsArgs = BucketExistsArgs.builder().bucket(bucketName).build();
.bucket(bucketName)
.build();
if (!minioClient.bucketExists(bucketExistsArgs)) { if (!minioClient.bucketExists(bucketExistsArgs)) {
MakeBucketArgs makeBucketArgs = MakeBucketArgs.builder() MakeBucketArgs makeBucketArgs = MakeBucketArgs.builder().bucket(bucketName).build();
.bucket(bucketName)
.build();
minioClient.makeBucket(makeBucketArgs); minioClient.makeBucket(makeBucketArgs);
// 设置存储桶访问权限为PUBLIC 如果不配置则新建的存储桶默认是PRIVATE则存储桶文件会拒绝访问 Access Denied // 设置存储桶访问权限为PUBLIC 如果不配置则新建的存储桶默认是PRIVATE则存储桶文件会拒绝访问 Access Denied
SetBucketPolicyArgs setBucketPolicyArgs = SetBucketPolicyArgs.builder() SetBucketPolicyArgs setBucketPolicyArgs = SetBucketPolicyArgs
.bucket(bucketName) .builder().bucket(bucketName)
.config(publicBucketPolicy(bucketName).toString()) .config(publicBucketPolicy(bucketName).toString())
.build(); .build();
minioClient.setBucketPolicy(setBucketPolicyArgs); minioClient.setBucketPolicy(setBucketPolicyArgs);
@ -108,7 +114,7 @@ public class MinioService implements InitializingBean {
* *
* @param file MultipartFile文件对象 * @param file MultipartFile文件对象
* @param bucketName 存储桶名称 * @param bucketName 存储桶名称
* @return * @return 上传文件路径
*/ */
@SneakyThrows @SneakyThrows
public String putObject(MultipartFile file, String bucketName) { public String putObject(MultipartFile file, String bucketName) {
@ -116,43 +122,61 @@ public class MinioService implements InitializingBean {
if (StrUtil.isBlank(bucketName)) { if (StrUtil.isBlank(bucketName)) {
bucketName = defaultBucket; bucketName = defaultBucket;
} }
// 判断存储桶是否存在
createBucketIfAbsent(bucketName); createBucketIfAbsent(bucketName);
String suffix = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".") + 1); // 获取文件后缀
String fileName = IdUtil.simpleUUID() + "." + suffix; String suffix = FileUtil.getSuffix(file.getOriginalFilename());
// 文件名
String uuid = IdUtil.simpleUUID();
String fileName = DateUtil.format(LocalDateTime.now(), "yyyy/MM/dd") + "/" + uuid + "." + suffix;
InputStream inputStream = file.getInputStream(); InputStream inputStream;
// 是否开启压缩
if (ImgUtils.isImg(fileName) && imgCompressionEnabled) {
long fileSize = file.getSize();
log.info("图片({})压缩前大小:{}KB", uuid, fileSize / 1024);
float compressQuality = ImgUtils.getCompressQuality(fileSize);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(Convert.toInt(fileSize));
Thumbnails.of(file.getInputStream())
.scale(1f) // 图片大小比例
.outputQuality(compressQuality) // 图片质量压缩比
.toOutputStream(outputStream);
inputStream = new ByteArrayInputStream(outputStream.toByteArray());
log.info("图片({})压缩后大小:{}KB", uuid, inputStream.available() / 1024);
} else {
inputStream = file.getInputStream();
}
// 上传参数构建
PutObjectArgs putObjectArgs = PutObjectArgs.builder() PutObjectArgs putObjectArgs = PutObjectArgs.builder()
.bucket(bucketName) .bucket(bucketName)
.object(fileName) .object(fileName)
.contentType(file.getContentType()) .contentType(file.getContentType())
.stream(inputStream, inputStream.available(), -1) .stream(inputStream, inputStream.available(), -1)
.build(); .build();
// 上传
minioClient.putObject(putObjectArgs); minioClient.putObject(putObjectArgs);
String fileUrl; String fileUrl;
if (StrUtil.isBlank(customDomain)) { // 没有自定义文件路径域名 if (StrUtil.isBlank(customDomain)) { // 没有自定义文件路径域名
GetPresignedObjectUrlArgs getPresignedObjectUrlArgs = GetPresignedObjectUrlArgs.builder() GetPresignedObjectUrlArgs getPresignedObjectUrlArgs = GetPresignedObjectUrlArgs.builder()
.bucket(bucketName) .bucket(bucketName).object(fileName)
.object(fileName)
.method(Method.GET) .method(Method.GET)
.build(); .build();
fileUrl = minioClient.getPresignedObjectUrl(getPresignedObjectUrlArgs); fileUrl = minioClient.getPresignedObjectUrl(getPresignedObjectUrlArgs);
fileUrl = fileUrl.substring(0, fileUrl.indexOf("?")); fileUrl = fileUrl.substring(0, fileUrl.indexOf("?"));
} else { // 自定义文件路径域名Nginx配置方向代理转发MinIO } else {
fileUrl = customDomain +'/'+ bucketName + "/" + fileName; // 自定义文件路径域名Nginx配置代理转发
fileUrl = customDomain + '/' + bucketName + "/" + fileName;
} }
return fileUrl; return fileUrl;
} }
public void removeObject(String bucket, String fileName) throws Exception { public void removeObject(String bucket, String fileName) throws Exception {
RemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder() RemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder().bucket(bucket).object(fileName).build();
.bucket(bucket)
.object(fileName)
.build();
minioClient.removeObject(removeObjectArgs); minioClient.removeObject(removeObjectArgs);
} }
@ -172,16 +196,17 @@ public class MinioService implements InitializingBean {
* Action: 操作行为 * Action: 操作行为
*/ */
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
builder.append("{\"Version\":\"2012-10-17\"," + builder.append("{\"Version\":\"2012-10-17\","
"\"Statement\":[{\"Effect\":\"Allow\"," + + "\"Statement\":[{\"Effect\":\"Allow\","
"\"Principal\":{\"AWS\":[\"*\"]}," + + "\"Principal\":{\"AWS\":[\"*\"]},"
"\"Action\":[\"s3:ListBucketMultipartUploads\",\"s3:GetBucketLocation\",\"s3:ListBucket\"]," + + "\"Action\":[\"s3:ListBucketMultipartUploads\",\"s3:GetBucketLocation\",\"s3:ListBucket\"],"
"\"Resource\":[\"arn:aws:s3:::" + bucketName + "\"]}," + + "\"Resource\":[\"arn:aws:s3:::" + bucketName + "\"]},"
"{\"Effect\":\"Allow\"," + + "{\"Effect\":\"Allow\"," + "\"Principal\":{\"AWS\":[\"*\"]},"
"\"Principal\":{\"AWS\":[\"*\"]}," + + "\"Action\":[\"s3:ListMultipartUploadParts\",\"s3:PutObject\",\"s3:AbortMultipartUpload\",\"s3:DeleteObject\",\"s3:GetObject\"],"
"\"Action\":[\"s3:ListMultipartUploadParts\",\"s3:PutObject\",\"s3:AbortMultipartUpload\",\"s3:DeleteObject\",\"s3:GetObject\"]," + + "\"Resource\":[\"arn:aws:s3:::" + bucketName + "/*\"]}]}");
"\"Resource\":[\"arn:aws:s3:::" + bucketName + "/*\"]}]}");
return builder; return builder;
} }
} }