diff --git a/youlai-common/common-core/src/main/java/com/youlai/common/util/ImgUtils.java b/youlai-common/common-core/src/main/java/com/youlai/common/util/ImgUtils.java new file mode 100644 index 000000000..83a27b08d --- /dev/null +++ b/youlai-common/common-core/src/main/java/com/youlai/common/util/ImgUtils.java @@ -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; + } + } +} diff --git a/youlai-common/common-file/pom.xml b/youlai-common/common-file/pom.xml index a8ce82efb..5d86fd5f7 100644 --- a/youlai-common/common-file/pom.xml +++ b/youlai-common/common-file/pom.xml @@ -28,5 +28,11 @@ io.minio minio + + + + net.coobird + thumbnailator + \ No newline at end of file diff --git a/youlai-common/common-file/src/main/java/com/youlai/common/file/controller/FileController.java b/youlai-common/common-file/src/main/java/com/youlai/common/file/controller/FileController.java index a8234dfee..832611387 100644 --- a/youlai-common/common-file/src/main/java/com/youlai/common/file/controller/FileController.java +++ b/youlai-common/common-file/src/main/java/com/youlai/common/file/controller/FileController.java @@ -15,7 +15,6 @@ import org.springframework.web.multipart.MultipartFile; @RequestMapping("/api/v1/files") @RequiredArgsConstructor public class FileController { - private final MinioService minioService; @PostMapping @@ -23,7 +22,7 @@ public class FileController { @SneakyThrows public Result uploadFile( @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); return Result.success(path); diff --git a/youlai-common/common-file/src/main/java/com/youlai/common/file/service/MinioService.java b/youlai-common/common-file/src/main/java/com/youlai/common/file/service/MinioService.java index 5ee0e65ef..874468365 100644 --- a/youlai-common/common-file/src/main/java/com/youlai/common/file/service/MinioService.java +++ b/youlai-common/common-file/src/main/java/com/youlai/common/file/service/MinioService.java @@ -1,19 +1,26 @@ 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.util.IdUtil; import cn.hutool.core.util.StrUtil; +import com.youlai.common.util.ImgUtils; import io.minio.*; import io.minio.http.Method; import lombok.Setter; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; +import net.coobird.thumbnailator.Thumbnails; import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; -import java.io.InputStream; +import java.io.*; +import java.time.LocalDateTime; @Component @@ -51,6 +58,11 @@ public class MinioService implements InitializingBean { @Setter private String defaultBucket; + + @Value("${minio.img_compression_enabled:false}") + private boolean imgCompressionEnabled; + + private MinioClient minioClient; @Override @@ -61,9 +73,7 @@ public class MinioService implements InitializingBean { Assert.notBlank(secretKey, "MinIO secretKey不能为空"); this.minioClient = MinioClient.builder() //.endpoint(endpoint, 443, true) - .endpoint(endpoint) - .credentials(accessKey, secretKey) - .build(); + .endpoint(endpoint).credentials(accessKey, secretKey).build(); } /** @@ -73,19 +83,15 @@ public class MinioService implements InitializingBean { */ @SneakyThrows public void createBucketIfAbsent(String bucketName) { - BucketExistsArgs bucketExistsArgs = BucketExistsArgs.builder() - .bucket(bucketName) - .build(); + BucketExistsArgs bucketExistsArgs = BucketExistsArgs.builder().bucket(bucketName).build(); if (!minioClient.bucketExists(bucketExistsArgs)) { - MakeBucketArgs makeBucketArgs = MakeBucketArgs.builder() - .bucket(bucketName) - .build(); + MakeBucketArgs makeBucketArgs = MakeBucketArgs.builder().bucket(bucketName).build(); minioClient.makeBucket(makeBucketArgs); // 设置存储桶访问权限为PUBLIC, 如果不配置,则新建的存储桶默认是PRIVATE,则存储桶文件会拒绝访问 Access Denied - SetBucketPolicyArgs setBucketPolicyArgs = SetBucketPolicyArgs.builder() - .bucket(bucketName) + SetBucketPolicyArgs setBucketPolicyArgs = SetBucketPolicyArgs + .builder().bucket(bucketName) .config(publicBucketPolicy(bucketName).toString()) .build(); minioClient.setBucketPolicy(setBucketPolicyArgs); @@ -108,7 +114,7 @@ public class MinioService implements InitializingBean { * * @param file MultipartFile文件对象 * @param bucketName 存储桶名称 - * @return + * @return 上传文件路径 */ @SneakyThrows public String putObject(MultipartFile file, String bucketName) { @@ -116,43 +122,61 @@ public class MinioService implements InitializingBean { if (StrUtil.isBlank(bucketName)) { bucketName = defaultBucket; } - + // 判断存储桶是否存在 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() .bucket(bucketName) .object(fileName) .contentType(file.getContentType()) .stream(inputStream, inputStream.available(), -1) .build(); - + // 上传 minioClient.putObject(putObjectArgs); String fileUrl; if (StrUtil.isBlank(customDomain)) { // 没有自定义文件路径域名 GetPresignedObjectUrlArgs getPresignedObjectUrlArgs = GetPresignedObjectUrlArgs.builder() - .bucket(bucketName) - .object(fileName) + .bucket(bucketName).object(fileName) .method(Method.GET) .build(); fileUrl = minioClient.getPresignedObjectUrl(getPresignedObjectUrlArgs); fileUrl = fileUrl.substring(0, fileUrl.indexOf("?")); - } else { // 自定义文件路径域名,Nginx配置方向代理转发MinIO - fileUrl = customDomain +'/'+ bucketName + "/" + fileName; + } else { + // 自定义文件路径域名,Nginx配置代理转发 + fileUrl = customDomain + '/' + bucketName + "/" + fileName; } return fileUrl; } public void removeObject(String bucket, String fileName) throws Exception { - RemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder() - .bucket(bucket) - .object(fileName) - .build(); + RemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder().bucket(bucket).object(fileName).build(); minioClient.removeObject(removeObjectArgs); } @@ -172,16 +196,17 @@ public class MinioService implements InitializingBean { * Action: 操作行为 */ StringBuilder builder = new StringBuilder(); - builder.append("{\"Version\":\"2012-10-17\"," + - "\"Statement\":[{\"Effect\":\"Allow\"," + - "\"Principal\":{\"AWS\":[\"*\"]}," + - "\"Action\":[\"s3:ListBucketMultipartUploads\",\"s3:GetBucketLocation\",\"s3:ListBucket\"]," + - "\"Resource\":[\"arn:aws:s3:::" + bucketName + "\"]}," + - "{\"Effect\":\"Allow\"," + - "\"Principal\":{\"AWS\":[\"*\"]}," + - "\"Action\":[\"s3:ListMultipartUploadParts\",\"s3:PutObject\",\"s3:AbortMultipartUpload\",\"s3:DeleteObject\",\"s3:GetObject\"]," + - "\"Resource\":[\"arn:aws:s3:::" + bucketName + "/*\"]}]}"); + builder.append("{\"Version\":\"2012-10-17\"," + + "\"Statement\":[{\"Effect\":\"Allow\"," + + "\"Principal\":{\"AWS\":[\"*\"]}," + + "\"Action\":[\"s3:ListBucketMultipartUploads\",\"s3:GetBucketLocation\",\"s3:ListBucket\"]," + + "\"Resource\":[\"arn:aws:s3:::" + bucketName + "\"]}," + + "{\"Effect\":\"Allow\"," + "\"Principal\":{\"AWS\":[\"*\"]}," + + "\"Action\":[\"s3:ListMultipartUploadParts\",\"s3:PutObject\",\"s3:AbortMultipartUpload\",\"s3:DeleteObject\",\"s3:GetObject\"]," + + "\"Resource\":[\"arn:aws:s3:::" + bucketName + "/*\"]}]}"); return builder; } + + }