feat: add MinIO support for attachment management. #1024 (#1097)

pull/1099/head
Wh1te 2020-10-07 10:52:20 +08:00 committed by GitHub
parent a4da9a7d58
commit ab74d8c28b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 205 additions and 1 deletions

View File

@ -49,6 +49,7 @@ ext {
aliyunSdkVersion = "3.9.1"
baiduSdkVersion = "0.10.36"
qcloudSdkVersion = "5.6.25"
minioSdkVersion = "7.1.4"
swaggerVersion = "2.9.2"
commonsLangVersion = "3.10"
httpclientVersion = "4.5.12"
@ -88,6 +89,7 @@ dependencies {
implementation "com.baidubce:bce-java-sdk:$baiduSdkVersion"
implementation "com.qcloud:cos_api:$qcloudSdkVersion"
implementation "com.huaweicloud:esdk-obs-java:$huaweiObsVersion"
implementation "io.minio:minio:$minioSdkVersion"
implementation "io.springfox:springfox-swagger2:$swaggerVersion"
implementation "io.springfox:springfox-swagger-ui:$swaggerVersion"
implementation "org.apache.commons:commons-lang3:$commonsLangVersion"

View File

@ -0,0 +1,131 @@
package run.halo.app.handler.file;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import io.minio.RemoveObjectArgs;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.web.multipart.MultipartFile;
import run.halo.app.exception.FileOperationException;
import run.halo.app.model.enums.AttachmentType;
import run.halo.app.model.properties.MinioProperties;
import run.halo.app.model.support.HaloConst;
import run.halo.app.model.support.UploadResult;
import run.halo.app.service.OptionService;
import run.halo.app.utils.FilenameUtils;
import run.halo.app.utils.ImageUtils;
import javax.imageio.ImageReader;
import java.util.Objects;
/**
* MinIO file handler.
*
* @author Wh1te
* @date 2020-10-03
*/
@Slf4j
@Component
public class MinioFileHandler implements FileHandler {
private final OptionService optionService;
public MinioFileHandler(OptionService optionService) {
this.optionService = optionService;
}
@NotNull
@Override
public UploadResult upload(@NotNull MultipartFile file) {
Assert.notNull(file, "Multipart file must not be null");
// Get config
String endpoint = optionService.getByPropertyOfNonNull(MinioProperties.ENDPOINT).toString();
String accessKey = optionService.getByPropertyOfNonNull(MinioProperties.ACCESS_KEY).toString();
String accessSecret = optionService.getByPropertyOfNonNull(MinioProperties.ACCESS_SECRET).toString();
String bucketName = optionService.getByPropertyOfNonNull(MinioProperties.BUCKET_NAME).toString();
String source = optionService.getByPropertyOrDefault(MinioProperties.SOURCE, String.class, "");
endpoint = StringUtils.appendIfMissing(endpoint, HaloConst.URL_SEPARATOR);
MinioClient minioClient = MinioClient.builder()
.endpoint(endpoint)
.credentials(accessKey, accessSecret)
.build();
try {
String basename = FilenameUtils.getBasename(Objects.requireNonNull(file.getOriginalFilename()));
String extension = FilenameUtils.getExtension(file.getOriginalFilename());
String timestamp = String.valueOf(System.currentTimeMillis());
String upFilePath = StringUtils.join(StringUtils.isNotBlank(source) ? source + HaloConst.URL_SEPARATOR : "",
basename, "_", timestamp, ".", extension);
String filePath = StringUtils.join(endpoint, bucketName, HaloConst.URL_SEPARATOR, upFilePath);
PutObjectArgs putObjectArgs = PutObjectArgs.builder()
.contentType(file.getContentType())
.bucket(bucketName)
.stream(file.getInputStream(), file.getSize(), -1)
.object(upFilePath)
.build();
minioClient.putObject(putObjectArgs);
UploadResult uploadResult = new UploadResult();
uploadResult.setFilename(basename);
uploadResult.setFilePath(filePath);
uploadResult.setKey(upFilePath);
uploadResult.setMediaType(MediaType.valueOf(Objects.requireNonNull(file.getContentType())));
uploadResult.setSuffix(extension);
uploadResult.setSize(file.getSize());
// Handle thumbnail
if (FileHandler.isImageType(uploadResult.getMediaType())) {
ImageReader image = ImageUtils.getImageReaderFromFile(file.getInputStream(), extension);
assert image != null;
uploadResult.setWidth(image.getWidth(0));
uploadResult.setHeight(image.getHeight(0));
uploadResult.setThumbPath(filePath);
}
return uploadResult;
} catch (Exception e) {
log.error("upload file to MINIO failed", e);
throw new FileOperationException("上传附件 " + file.getOriginalFilename() + " 到 MinIO 失败 ", e).setErrorData(e.getMessage());
}
}
@Override
public void delete(@NotNull String key) {
Assert.notNull(key, "File key must not be blank");
String endPoint = optionService.getByPropertyOfNonNull(MinioProperties.ENDPOINT).toString();
endPoint = StringUtils.appendIfMissing(endPoint, HaloConst.URL_SEPARATOR);
String accessKey = optionService.getByPropertyOfNonNull(MinioProperties.ACCESS_KEY).toString();
String accessSecret = optionService.getByPropertyOfNonNull(MinioProperties.ACCESS_SECRET).toString();
String bucketName = optionService.getByPropertyOfNonNull(MinioProperties.BUCKET_NAME).toString();
MinioClient minioClient = MinioClient.builder()
.endpoint(endPoint)
.credentials(accessKey, accessSecret)
.build();
try {
minioClient.removeObject(RemoveObjectArgs.builder()
.bucket(bucketName)
.object(key)
.build());
} catch (Exception e) {
log.error("delete MINIO file: [{}] failed", key, e);
throw new FileOperationException("附件 " + key + " 从 MinIO 删除失败", e).setErrorData(e.getMessage());
}
}
@Override
public AttachmentType getAttachmentType() {
return AttachmentType.MINIO;
}
}

View File

@ -46,7 +46,12 @@ public enum AttachmentType implements ValueEnum<Integer> {
/**
*
*/
HUAWEIOBS(7);
HUAWEIOBS(7),
/**
* MINIO
*/
MINIO(8);
private final Integer value;

View File

@ -0,0 +1,66 @@
package run.halo.app.model.properties;
/**
* Minio properties.
*
* @author Wh1te
* @date 2020-10-03
*/
public enum MinioProperties implements PropertyEnum {
/**
* Minio endpoint.
*/
ENDPOINT("minio_endpoint", String.class, ""),
/**
* Minio bucket name.
*/
BUCKET_NAME("minio_bucket_name", String.class, ""),
/**
* Minio access key.
*/
ACCESS_KEY("minio_access_key", String.class, ""),
/**
* Minio access secret.
*/
ACCESS_SECRET("minio_access_secret", String.class, ""),
/**
* Minio source
*/
SOURCE("minio_source", String.class, "");
private final String value;
private final Class<?> type;
private final String defaultValue;
MinioProperties(String value, Class<?> type, String defaultValue) {
this.defaultValue = defaultValue;
if (!PropertyEnum.isSupportedType(type)) {
throw new IllegalArgumentException("Unsupported blog property type: " + type);
}
this.value = value;
this.type = type;
}
@Override
public Class<?> getType() {
return type;
}
@Override
public String defaultValue() {
return defaultValue;
}
@Override
public String getValue() {
return value;
}
}