mirror of
https://github.com/halo-dev/plugin-s3.git
synced 2026-01-14 07:03:32 +08:00
Support to get shared URL and permalink of attachment in handler (#35)
On the Halo side, PR https://github.com/halo-dev/halo/pull/3740 has already added two new methods (`getSharedURL` and `getPermalink`) into AttachmentHandler. Now It's time to implement these two methods so that users can correctly and easily use these two methods.
This PR mainly implements [new AttachmentHandler](11a5807682/api/src/main/java/run/halo/app/core/extension/attachment/endpoint/AttachmentHandler.java). At the same time, I also refactored the build script for a better development experience.
Please note that, those changes might not influence compatibility with Halo 2.0.0. You can have test against Halo 2.0.0 manually.
/kind feature
```release-note
支持获取分享链接和永久链接
```
This commit is contained in:
18
build.gradle
18
build.gradle
@@ -1,5 +1,6 @@
|
||||
plugins {
|
||||
id "io.github.guqing.plugin-development" version "0.0.6-SNAPSHOT"
|
||||
id "io.github.guqing.plugin-development" version "0.0.7-SNAPSHOT"
|
||||
id "io.freefair.lombok" version "8.0.0-rc2"
|
||||
id 'java'
|
||||
}
|
||||
|
||||
@@ -8,7 +9,7 @@ sourceCompatibility = JavaVersion.VERSION_17
|
||||
|
||||
repositories {
|
||||
maven { url 'https://s01.oss.sonatype.org/content/repositories/releases' }
|
||||
maven { url 'https://repo.spring.io/milestone' }
|
||||
maven { url 'https://s01.oss.sonatype.org/content/repositories/snapshots/' }
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
@@ -23,9 +24,8 @@ jar {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly platform("run.halo.dependencies:halo-dependencies:1.0.0")
|
||||
|
||||
compileOnly files("lib/halo-2.0.0-SNAPSHOT-plain.jar")
|
||||
implementation platform('run.halo.tools.platform:plugin:2.5.0-SNAPSHOT')
|
||||
compileOnly 'run.halo.app:api'
|
||||
|
||||
implementation platform('software.amazon.awssdk:bom:2.19.8')
|
||||
implementation 'software.amazon.awssdk:s3'
|
||||
@@ -33,14 +33,8 @@ dependencies {
|
||||
implementation "javax.activation:activation:1.1.1"
|
||||
implementation "org.glassfish.jaxb:jaxb-runtime:2.3.3"
|
||||
|
||||
compileOnly 'org.projectlombok:lombok'
|
||||
annotationProcessor 'org.projectlombok:lombok:1.18.22'
|
||||
|
||||
testImplementation platform("run.halo.dependencies:halo-dependencies:1.0.0")
|
||||
testImplementation files("lib/halo-2.0.0-SNAPSHOT-plain.jar")
|
||||
testImplementation 'run.halo.app:api'
|
||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.0'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.0'
|
||||
}
|
||||
|
||||
test {
|
||||
|
||||
@@ -1 +1 @@
|
||||
version=1.3.0-SNAPSHOT
|
||||
version=1.4.0-SNAPSHOT
|
||||
|
||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
Binary file not shown.
@@ -1,9 +1,11 @@
|
||||
package run.halo.s3os;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -15,6 +17,7 @@ import org.pf4j.Extension;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.MediaTypeFactory;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.web.server.ServerErrorException;
|
||||
import org.springframework.web.server.ServerWebInputException;
|
||||
import org.springframework.web.util.UriUtils;
|
||||
@@ -31,6 +34,7 @@ import run.halo.app.extension.ConfigMap;
|
||||
import run.halo.app.extension.Metadata;
|
||||
import run.halo.app.infra.utils.JsonUtils;
|
||||
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
|
||||
import software.amazon.awssdk.awscore.presigner.SdkPresigner;
|
||||
import software.amazon.awssdk.core.SdkResponse;
|
||||
import software.amazon.awssdk.core.sync.RequestBody;
|
||||
import software.amazon.awssdk.http.SdkHttpResponse;
|
||||
@@ -42,10 +46,13 @@ import software.amazon.awssdk.services.s3.model.CompletedMultipartUpload;
|
||||
import software.amazon.awssdk.services.s3.model.CompletedPart;
|
||||
import software.amazon.awssdk.services.s3.model.CreateMultipartUploadRequest;
|
||||
import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
|
||||
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
|
||||
import software.amazon.awssdk.services.s3.model.HeadObjectRequest;
|
||||
import software.amazon.awssdk.services.s3.model.HeadObjectResponse;
|
||||
import software.amazon.awssdk.services.s3.model.NoSuchKeyException;
|
||||
import software.amazon.awssdk.services.s3.model.UploadPartRequest;
|
||||
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
|
||||
import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest;
|
||||
import software.amazon.awssdk.utils.SdkAutoCloseable;
|
||||
|
||||
@Slf4j
|
||||
@@ -71,29 +78,88 @@ public class S3OsAttachmentHandler implements AttachmentHandler {
|
||||
public Mono<Attachment> delete(DeleteContext deleteContext) {
|
||||
return Mono.just(deleteContext).filter(context -> this.shouldHandle(context.policy()))
|
||||
.flatMap(context -> {
|
||||
var annotations = context.attachment().getMetadata().getAnnotations();
|
||||
if (annotations == null || !annotations.containsKey(OBJECT_KEY)) {
|
||||
var objectKey = getObjectKey(context.attachment());
|
||||
if (objectKey == null) {
|
||||
return Mono.just(context);
|
||||
}
|
||||
var objectName = annotations.get(OBJECT_KEY);
|
||||
var properties = getProperties(deleteContext.configMap());
|
||||
return Mono.using(() -> buildS3Client(properties),
|
||||
client -> Mono.fromCallable(
|
||||
() -> client.deleteObject(DeleteObjectRequest.builder()
|
||||
.bucket(properties.getBucket())
|
||||
.key(objectName)
|
||||
.key(objectKey)
|
||||
.build())).subscribeOn(Schedulers.boundedElastic()),
|
||||
S3Client::close)
|
||||
.doOnNext(response -> {
|
||||
checkResult(response, "delete object");
|
||||
log.info("Delete object {} from bucket {} successfully",
|
||||
objectName, properties.getBucket());
|
||||
objectKey, properties.getBucket());
|
||||
})
|
||||
.thenReturn(context);
|
||||
})
|
||||
.map(DeleteContext::attachment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<URI> getSharedURL(Attachment attachment, Policy policy, ConfigMap configMap,
|
||||
Duration ttl) {
|
||||
if (!this.shouldHandle(policy)) {
|
||||
return Mono.empty();
|
||||
}
|
||||
var objectKey = getObjectKey(attachment);
|
||||
if (objectKey == null) {
|
||||
return Mono.error(new IllegalArgumentException(
|
||||
"Cannot obtain object key from attachment " + attachment.getMetadata().getName()));
|
||||
}
|
||||
var properties = getProperties(configMap);
|
||||
|
||||
return Mono.using(() -> buildS3Presigner(properties),
|
||||
s3Presigner -> {
|
||||
var getObjectRequest = GetObjectRequest.builder()
|
||||
.bucket(properties.getBucket())
|
||||
.key(objectKey)
|
||||
.build();
|
||||
var presignedRequest = GetObjectPresignRequest.builder()
|
||||
.signatureDuration(Duration.ofMinutes(5))
|
||||
.getObjectRequest(getObjectRequest)
|
||||
.build();
|
||||
var presignedGetObjectRequest = s3Presigner.presignGetObject(presignedRequest);
|
||||
var presignedURL = presignedGetObjectRequest.url();
|
||||
try {
|
||||
return Mono.just(presignedURL.toURI());
|
||||
} catch (URISyntaxException e) {
|
||||
return Mono.error(
|
||||
new RuntimeException("Failed to convert URL " + presignedURL + " to URI."));
|
||||
}
|
||||
},
|
||||
SdkPresigner::close)
|
||||
.subscribeOn(Schedulers.boundedElastic());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<URI> getPermalink(Attachment attachment, Policy policy, ConfigMap configMap) {
|
||||
if (!this.shouldHandle(policy)) {
|
||||
return Mono.empty();
|
||||
}
|
||||
var objectKey = getObjectKey(attachment);
|
||||
if (objectKey == null) {
|
||||
return Mono.error(new IllegalArgumentException(
|
||||
"Cannot obtain object key from attachment " + attachment.getMetadata().getName()));
|
||||
}
|
||||
var properties = getProperties(configMap);
|
||||
var objectName = getObjectName(properties, objectKey);
|
||||
return Mono.just(URI.create(objectName));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getObjectKey(Attachment attachment) {
|
||||
var annotations = attachment.getMetadata().getAnnotations();
|
||||
if (annotations == null) {
|
||||
return null;
|
||||
}
|
||||
return annotations.get(OBJECT_KEY);
|
||||
}
|
||||
|
||||
S3OsProperties getProperties(ConfigMap configMap) {
|
||||
var settingJson = configMap.getData().getOrDefault("default", "{}");
|
||||
return JsonUtils.jsonToObject(settingJson, S3OsProperties.class);
|
||||
@@ -130,6 +196,15 @@ public class S3OsAttachmentHandler implements AttachmentHandler {
|
||||
return attachment;
|
||||
}
|
||||
|
||||
private String getObjectName(S3OsProperties properties, String objectKey) {
|
||||
if (StringUtils.isBlank(properties.getDomain())) {
|
||||
var host = properties.getBucket() + "." + properties.getEndpoint();
|
||||
return properties.getProtocol() + "://" + host + "/" + objectKey;
|
||||
} else {
|
||||
return properties.getProtocol() + "://" + properties.getDomain() + "/" + objectKey;
|
||||
}
|
||||
}
|
||||
|
||||
S3Client buildS3Client(S3OsProperties properties) {
|
||||
return S3Client.builder()
|
||||
.region(Region.of(properties.getRegion()))
|
||||
@@ -144,6 +219,20 @@ public class S3OsAttachmentHandler implements AttachmentHandler {
|
||||
.build();
|
||||
}
|
||||
|
||||
private S3Presigner buildS3Presigner(S3OsProperties properties) {
|
||||
return S3Presigner.builder()
|
||||
.region(Region.of(properties.getRegion()))
|
||||
.endpointOverride(
|
||||
URI.create(properties.getEndpointProtocol() + "://" + properties.getEndpoint()))
|
||||
.credentialsProvider(() -> AwsBasicCredentials.create(properties.getAccessKey(),
|
||||
properties.getAccessSecret()))
|
||||
.serviceConfiguration(S3Configuration.builder()
|
||||
.chunkedEncodingEnabled(false)
|
||||
.pathStyleAccessEnabled(properties.getEnablePathStyleAccess())
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
Mono<ObjectDetail> upload(UploadContext uploadContext, S3OsProperties properties) {
|
||||
return Mono.using(() -> buildS3Client(properties),
|
||||
client -> {
|
||||
|
||||
@@ -4,7 +4,7 @@ metadata:
|
||||
name: PluginS3ObjectStorage
|
||||
spec:
|
||||
enabled: true
|
||||
version: 1.3.0
|
||||
version: 1.4.0
|
||||
requires: ">=2.0.0"
|
||||
author:
|
||||
name: longjuan
|
||||
|
||||
Reference in New Issue
Block a user