diff --git a/.github/workflows/build.yml b/.github/workflows/build_temp.yml similarity index 100% rename from .github/workflows/build.yml rename to .github/workflows/build_temp.yml diff --git a/youlai-admin/admin-boot/src/main/java/com/youlai/admin/config/SwaggerConfiguration.java b/youlai-admin/admin-boot/src/main/java/com/youlai/admin/config/SwaggerConfiguration.java index c33bc6262..3f58f72e7 100644 --- a/youlai-admin/admin-boot/src/main/java/com/youlai/admin/config/SwaggerConfiguration.java +++ b/youlai-admin/admin-boot/src/main/java/com/youlai/admin/config/SwaggerConfiguration.java @@ -1,6 +1,7 @@ package com.youlai.admin.config; import com.google.common.collect.Lists; +import io.swagger.annotations.Api; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; @@ -60,7 +61,9 @@ public class SwaggerConfiguration { List securityContexts = Lists.newArrayList(securityContext); return new Docket(DocumentationType.SWAGGER_2) .select() - .apis(RequestHandlerSelectors.basePackage("com.youlai.admin.controller")) + // .apis(RequestHandlerSelectors.basePackage("com.youlai.admin.controller")) + // .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) + .apis(RequestHandlerSelectors.withClassAnnotation(Api.class)) .paths(PathSelectors.any()) .build() .securityContexts(securityContexts) diff --git a/youlai-admin/admin-boot/src/main/java/com/youlai/admin/service/impl/MinioService.java b/youlai-admin/admin-boot/src/main/java/com/youlai/admin/service/impl/MinioService.java deleted file mode 100644 index 5c2bfd972..000000000 --- a/youlai-admin/admin-boot/src/main/java/com/youlai/admin/service/impl/MinioService.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.youlai.admin.service.impl; - -import cn.hutool.core.lang.Assert; -import io.minio.*; -import lombok.Setter; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.http.MediaType; -import org.springframework.stereotype.Component; - -import java.io.InputStream; - -@Component -@ConfigurationProperties(prefix = "minio") -public class MinioService implements InitializingBean { - - @Setter - private String endpoint; - - @Setter - private String accessKey; - - @Setter - private String secretKey; - - private MinioClient client; - - @Override - public void afterPropertiesSet() { - Assert.notBlank(endpoint, "MinIO URL 为空"); - Assert.notBlank(accessKey, "MinIO accessKey为空"); - Assert.notBlank(secretKey, "MinIO secretKey为空"); - this.client = new MinioClient.Builder() - .endpoint(endpoint) - .credentials(accessKey, secretKey) - .build(); - } - - @SneakyThrows - public void createBucketIfAbsent(String bucketName) { - BucketExistsArgs bucketExistsArgs = BucketExistsArgs.builder() - .bucket(bucketName) - .build(); - if (!client.bucketExists(bucketExistsArgs)) { - MakeBucketArgs makeBucketArgs = MakeBucketArgs.builder() - .bucket(bucketName).build(); - client.makeBucket(makeBucketArgs); - } - } - - public String putObject(String bucketName, String objectName, InputStream inputStream) throws Exception { - createBucketIfAbsent(bucketName); - PutObjectArgs putObjectArgs = PutObjectArgs.builder() - .bucket(bucketName) - .object(objectName) - .contentType(MediaType.ALL_VALUE) - .stream(inputStream, inputStream.available(), -1) - .build(); - client.putObject(putObjectArgs); - String path = client.getObjectUrl(bucketName, objectName); - return path; - } - - public void removeObject(String bucketName, String objectName) throws Exception { - RemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder() - .bucket(bucketName) - .object(objectName) - .build(); - client.removeObject(removeObjectArgs); - } -} 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 new file mode 100644 index 000000000..96427b3db --- /dev/null +++ b/youlai-common/common-file/src/main/java/com/youlai/common/file/service/MinioService.java @@ -0,0 +1,187 @@ +package com.youlai.common.file.service; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import io.minio.*; +import io.minio.http.Method; +import lombok.Setter; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; + +import java.io.InputStream; + + +@Component +@ConfigurationProperties(prefix = "minio") +@Slf4j +public class MinioService implements InitializingBean { + + /** + * MinIO的API地址 + */ + @Setter + private String endpoint; + + /** + * 用户名 + */ + @Setter + private String accessKey; + + /** + * 密钥 + */ + @Setter + private String secretKey; + + /** + * 自定义域名(非必须) + */ + @Setter + private String customDomain; + + /** + * 存储桶名称,默认微服务单独一个存储桶 + */ + @Setter + private String defaultBucket = "default"; + + private MinioClient minioClient; + + @Override + public void afterPropertiesSet() { + log.info("初始化 MinIO 客户端..."); + Assert.notBlank(endpoint, "MinIO endpoint不能为空"); + Assert.notBlank(accessKey, "MinIO accessKey不能为空"); + Assert.notBlank(secretKey, "MinIO secretKey不能为空"); + this.minioClient = MinioClient.builder() + //.endpoint(endpoint, 443, true) + .endpoint(endpoint) + .credentials(accessKey, secretKey) + .build(); + } + + /** + * 创建存储桶(存储桶不存在) + * + * @param bucketName + */ + @SneakyThrows + public void createBucketIfAbsent(String bucketName) { + BucketExistsArgs bucketExistsArgs = BucketExistsArgs.builder() + .bucket(bucketName) + .build(); + if (!minioClient.bucketExists(bucketExistsArgs)) { + MakeBucketArgs makeBucketArgs = MakeBucketArgs.builder() + .bucket(bucketName) + .build(); + + minioClient.makeBucket(makeBucketArgs); + + // 设置存储桶访问权限为PUBLIC, 如果不配置,则新建的存储桶默认是PRIVATE,则存储桶文件会拒绝访问 Access Denied + SetBucketPolicyArgs setBucketPolicyArgs = SetBucketPolicyArgs.builder() + .bucket(bucketName) + .config(publicBucketPolicy(bucketName).toString()) + .build(); + minioClient.setBucketPolicy(setBucketPolicyArgs); + } + } + + /** + * 上传文件对象(默认存储桶) + * + * @param file MultipartFile文件对象 + * @return + */ + public String putObject(MultipartFile file) { + String fileUrl = putObject(file, defaultBucket); + return fileUrl; + } + + /** + * 上传文件对象 + * + * @param file MultipartFile文件对象 + * @param bucketName 存储桶名称 + * @return + */ + @SneakyThrows + public String putObject(MultipartFile file, String bucketName) { + // 没有 + if (StrUtil.isBlank(bucketName)) { + bucketName = defaultBucket; + } + + createBucketIfAbsent(bucketName); + + String suffix = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".") + 1); + String fileName = IdUtil.simpleUUID() + "." + suffix; + + InputStream 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) + .method(Method.GET) + .build(); + + fileUrl = minioClient.getPresignedObjectUrl(getPresignedObjectUrlArgs); + fileUrl = fileUrl.substring(0, fileUrl.indexOf("?")); + } else { // 自定义文件路径域名,Nginx配置方向代理转发MinIO + fileUrl = customDomain + bucketName + "/" + fileName; + } + return fileUrl; + } + + public void removeObject(String bucket, String fileName) throws Exception { + RemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder() + .bucket(bucket) + .object(fileName) + .build(); + minioClient.removeObject(removeObjectArgs); + } + + + /** + * PUBLIC桶策略 + * 如果不配置,则新建的存储桶默认是PRIVATE,则存储桶文件会拒绝访问 Access Denied + * + * @param bucketName + * @return + */ + private static StringBuilder publicBucketPolicy(String bucketName) { + /** + * AWS的S3存储桶策略 + * Principal: 生效用户对象 + * Resource: 指定存储桶 + * 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 + "/*\"]}]}"); + + return builder; + } +}