mirror of
https://github.com/halo-dev/plugin-s3.git
synced 2025-10-16 07:19:45 +00:00
Compare commits
6 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
07127d7e54 | ||
![]() |
565d3cfcaa | ||
![]() |
c0fb2b1017 | ||
![]() |
c79fee9ba1 | ||
![]() |
08d6ff49c8 | ||
![]() |
73112953ba |
@@ -2,7 +2,7 @@ plugins {
|
|||||||
id 'java'
|
id 'java'
|
||||||
id "com.github.node-gradle.node" version "5.0.0"
|
id "com.github.node-gradle.node" version "5.0.0"
|
||||||
id "io.freefair.lombok" version "8.0.1"
|
id "io.freefair.lombok" version "8.0.1"
|
||||||
id "run.halo.plugin.devtools" version "0.0.6"
|
id "run.halo.plugin.devtools" version "0.0.7"
|
||||||
}
|
}
|
||||||
|
|
||||||
group 'run.halo.s3os'
|
group 'run.halo.s3os'
|
||||||
@@ -16,7 +16,7 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation platform('run.halo.tools.platform:plugin:2.9.0-SNAPSHOT')
|
implementation platform('run.halo.tools.platform:plugin:2.10.0-SNAPSHOT')
|
||||||
compileOnly 'run.halo.app:api'
|
compileOnly 'run.halo.app:api'
|
||||||
|
|
||||||
implementation platform('software.amazon.awssdk:bom:2.19.8')
|
implementation platform('software.amazon.awssdk:bom:2.19.8')
|
||||||
@@ -33,7 +33,7 @@ configurations.runtimeClasspath {
|
|||||||
|
|
||||||
|
|
||||||
halo {
|
halo {
|
||||||
version = '2.9.0'
|
version = '2.10.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
haloPlugin {
|
haloPlugin {
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import {definePlugin} from "@halo-dev/console-shared";
|
import {definePlugin} from "@halo-dev/console-shared";
|
||||||
import type {PluginTab} from "@halo-dev/console-shared";
|
import type {PluginTab} from "@halo-dev/console-shared";
|
||||||
import HomeView from "./views/HomeView.vue";
|
import S3Link from "./views/S3Link.vue";
|
||||||
import {markRaw} from "vue";
|
import {markRaw} from "vue";
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
@@ -12,8 +12,8 @@ export default definePlugin({
|
|||||||
{
|
{
|
||||||
id: "s3-link",
|
id: "s3-link",
|
||||||
label: "关联S3文件",
|
label: "关联S3文件",
|
||||||
component: markRaw(HomeView),
|
component: markRaw(S3Link),
|
||||||
permissions: []
|
permissions: ["plugin:s3os:link"]
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
@@ -242,9 +242,9 @@ const handleModalClose = () => {
|
|||||||
<template #header>
|
<template #header>
|
||||||
<div class="block w-full bg-gray-50 px-4 py-3">
|
<div class="block w-full bg-gray-50 px-4 py-3">
|
||||||
<div
|
<div
|
||||||
class="relative flex flex-col items-start sm:flex-row sm:items-center"
|
class="relative flex flex-col flex-wrap items-start gap-4 sm:flex-row sm:items-center"
|
||||||
>
|
>
|
||||||
<div class="mr-4 hidden items-center sm:flex">
|
<div class="hidden items-center sm:flex">
|
||||||
<input
|
<input
|
||||||
v-model="checkedAll"
|
v-model="checkedAll"
|
||||||
class="h-4 w-4 rounded border-gray-300 text-indigo-600"
|
class="h-4 w-4 rounded border-gray-300 text-indigo-600"
|
||||||
@@ -274,8 +274,7 @@ const handleModalClose = () => {
|
|||||||
</VButton>
|
</VButton>
|
||||||
</VSpace>
|
</VSpace>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4 flex sm:mt-0">
|
<VSpace spacing="lg" class="flex-wrap">
|
||||||
<VSpace spacing="lg">
|
|
||||||
<FilterCleanButton
|
<FilterCleanButton
|
||||||
v-if="selectedLinkedStatusItem != linkedStatusItems[0].value"
|
v-if="selectedLinkedStatusItem != linkedStatusItems[0].value"
|
||||||
@click="selectedLinkedStatusItem = linkedStatusItems[0].value"
|
@click="selectedLinkedStatusItem = linkedStatusItems[0].value"
|
||||||
@@ -303,7 +302,6 @@ const handleModalClose = () => {
|
|||||||
</VSpace>
|
</VSpace>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<VLoading v-if="isFetching"/>
|
<VLoading v-if="isFetching"/>
|
@@ -1 +1 @@
|
|||||||
version=1.5.0-SNAPSHOT
|
version=1.6.1-SNAPSHOT
|
||||||
|
@@ -184,7 +184,7 @@ public class S3LinkServiceImpl implements S3LinkService {
|
|||||||
.map(headObjectResponse -> {
|
.map(headObjectResponse -> {
|
||||||
var objectDetail = new S3OsAttachmentHandler.ObjectDetail(
|
var objectDetail = new S3OsAttachmentHandler.ObjectDetail(
|
||||||
new S3OsAttachmentHandler.UploadState(properties,
|
new S3OsAttachmentHandler.UploadState(properties,
|
||||||
FileNameUtils.extractFileNameFromS3Key(objectKey)),
|
FileNameUtils.extractFileNameFromS3Key(objectKey), false),
|
||||||
headObjectResponse);
|
headObjectResponse);
|
||||||
return handler.buildAttachment(properties, objectDetail);
|
return handler.buildAttachment(properties, objectDetail);
|
||||||
})
|
})
|
||||||
|
@@ -65,6 +65,7 @@ import software.amazon.awssdk.utils.SdkAutoCloseable;
|
|||||||
public class S3OsAttachmentHandler implements AttachmentHandler {
|
public class S3OsAttachmentHandler implements AttachmentHandler {
|
||||||
|
|
||||||
public static final String OBJECT_KEY = "s3os.plugin.halo.run/object-key";
|
public static final String OBJECT_KEY = "s3os.plugin.halo.run/object-key";
|
||||||
|
public static final String URL_SUFFIX_ANNO_KEY = "s3os.plugin.halo.run/url-suffix";
|
||||||
public static final int MULTIPART_MIN_PART_SIZE = 5 * 1024 * 1024;
|
public static final int MULTIPART_MIN_PART_SIZE = 5 * 1024 * 1024;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -157,6 +158,10 @@ public class S3OsAttachmentHandler implements AttachmentHandler {
|
|||||||
}
|
}
|
||||||
var properties = getProperties(configMap);
|
var properties = getProperties(configMap);
|
||||||
var objectURL = getObjectURL(properties, objectKey);
|
var objectURL = getObjectURL(properties, objectKey);
|
||||||
|
var urlSuffix = getUrlSuffixAnnotation(attachment);
|
||||||
|
if (StringUtils.isNotBlank(urlSuffix)) {
|
||||||
|
objectURL += urlSuffix;
|
||||||
|
}
|
||||||
return Mono.just(URI.create(objectURL));
|
return Mono.just(URI.create(objectURL));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,6 +174,15 @@ public class S3OsAttachmentHandler implements AttachmentHandler {
|
|||||||
return annotations.get(OBJECT_KEY);
|
return annotations.get(OBJECT_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private String getUrlSuffixAnnotation(Attachment attachment) {
|
||||||
|
var annotations = attachment.getMetadata().getAnnotations();
|
||||||
|
if (annotations == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return annotations.get(URL_SUFFIX_ANNO_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
S3OsProperties getProperties(ConfigMap configMap) {
|
S3OsProperties getProperties(ConfigMap configMap) {
|
||||||
var settingJson = configMap.getData().getOrDefault("default", "{}");
|
var settingJson = configMap.getData().getOrDefault("default", "{}");
|
||||||
return JsonUtils.jsonToObject(settingJson, S3OsProperties.class);
|
return JsonUtils.jsonToObject(settingJson, S3OsProperties.class);
|
||||||
@@ -176,12 +190,19 @@ public class S3OsAttachmentHandler implements AttachmentHandler {
|
|||||||
|
|
||||||
Attachment buildAttachment(S3OsProperties properties, ObjectDetail objectDetail) {
|
Attachment buildAttachment(S3OsProperties properties, ObjectDetail objectDetail) {
|
||||||
String externalLink = getObjectURL(properties, objectDetail.uploadState.objectKey);
|
String externalLink = getObjectURL(properties, objectDetail.uploadState.objectKey);
|
||||||
|
var urlSuffix = UrlUtils.findUrlSuffix(properties.getUrlSuffixes(),
|
||||||
|
objectDetail.uploadState.fileName);
|
||||||
|
|
||||||
var metadata = new Metadata();
|
var metadata = new Metadata();
|
||||||
metadata.setName(UUID.randomUUID().toString());
|
metadata.setName(UUID.randomUUID().toString());
|
||||||
metadata.setAnnotations(new HashMap<>(
|
|
||||||
Map.of(OBJECT_KEY, objectDetail.uploadState.objectKey,
|
var annotations = new HashMap<>(Map.of(OBJECT_KEY, objectDetail.uploadState.objectKey));
|
||||||
Constant.EXTERNAL_LINK_ANNO_KEY, externalLink)));
|
if (StringUtils.isNotBlank(urlSuffix)) {
|
||||||
|
externalLink += urlSuffix;
|
||||||
|
annotations.put(URL_SUFFIX_ANNO_KEY, urlSuffix);
|
||||||
|
}
|
||||||
|
annotations.put(Constant.EXTERNAL_LINK_ANNO_KEY, externalLink);
|
||||||
|
metadata.setAnnotations(annotations);
|
||||||
|
|
||||||
var objectMetadata = objectDetail.objectMetadata();
|
var objectMetadata = objectDetail.objectMetadata();
|
||||||
var spec = new AttachmentSpec();
|
var spec = new AttachmentSpec();
|
||||||
@@ -283,7 +304,7 @@ public class S3OsAttachmentHandler implements AttachmentHandler {
|
|||||||
Mono<ObjectDetail> upload(UploadContext uploadContext, S3OsProperties properties) {
|
Mono<ObjectDetail> upload(UploadContext uploadContext, S3OsProperties properties) {
|
||||||
return Mono.using(() -> buildS3Client(properties),
|
return Mono.using(() -> buildS3Client(properties),
|
||||||
client -> {
|
client -> {
|
||||||
var uploadState = new UploadState(properties, uploadContext.file().filename());
|
var uploadState = new UploadState(properties, uploadContext.file().filename(), true);
|
||||||
|
|
||||||
var content = uploadContext.file().content();
|
var content = uploadContext.file().content();
|
||||||
|
|
||||||
@@ -471,12 +492,14 @@ public class S3OsAttachmentHandler implements AttachmentHandler {
|
|||||||
String objectKey;
|
String objectKey;
|
||||||
boolean needRemoveMapKey = false;
|
boolean needRemoveMapKey = false;
|
||||||
|
|
||||||
public UploadState(S3OsProperties properties, String fileName) {
|
public UploadState(S3OsProperties properties, String fileName, boolean needRandomJudge) {
|
||||||
this.properties = properties;
|
this.properties = properties;
|
||||||
this.originalFileName = fileName;
|
this.originalFileName = fileName;
|
||||||
|
|
||||||
|
if (needRandomJudge) {
|
||||||
fileName = FileNameUtils.getRandomFilename(fileName,
|
fileName = FileNameUtils.getRandomFilename(fileName,
|
||||||
properties.getRandomStringLength(), properties.getRandomFilenameMode());
|
properties.getRandomStringLength(), properties.getRandomFilenameMode());
|
||||||
|
}
|
||||||
|
|
||||||
this.fileName = fileName;
|
this.fileName = fileName;
|
||||||
this.objectKey = properties.getObjectName(fileName);
|
this.objectKey = properties.getObjectName(fileName);
|
||||||
|
@@ -1,6 +1,10 @@
|
|||||||
package run.halo.s3os;
|
package run.halo.s3os;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
@@ -39,6 +43,16 @@ class S3OsProperties {
|
|||||||
|
|
||||||
private String region = "Auto";
|
private String region = "Auto";
|
||||||
|
|
||||||
|
private List<urlSuffixItem> urlSuffixes;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public static class urlSuffixItem {
|
||||||
|
private String fileSuffix;
|
||||||
|
private String urlSuffix;
|
||||||
|
}
|
||||||
|
|
||||||
public String getObjectName(String filename) {
|
public String getObjectName(String filename) {
|
||||||
var objectName = filename;
|
var objectName = filename;
|
||||||
var finalName = FilePathUtils.getFilePathByPlaceholder(getLocation());
|
var finalName = FilePathUtils.getFilePathByPlaceholder(getLocation());
|
||||||
|
@@ -2,6 +2,7 @@ package run.halo.s3os;
|
|||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
public class UrlUtils {
|
public class UrlUtils {
|
||||||
private static final List<String> HTTP_PREFIXES = Arrays.asList("http://", "https://");
|
private static final List<String> HTTP_PREFIXES = Arrays.asList("http://", "https://");
|
||||||
@@ -17,4 +18,21 @@ public class UrlUtils {
|
|||||||
}
|
}
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String findUrlSuffix(List<S3OsProperties.urlSuffixItem> urlSuffixList,
|
||||||
|
String fileName) {
|
||||||
|
if (StringUtils.isBlank(fileName) || urlSuffixList == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
fileName = fileName.toLowerCase();
|
||||||
|
for (S3OsProperties.urlSuffixItem item : urlSuffixList) {
|
||||||
|
String[] fileSuffixes = item.getFileSuffix().split(",");
|
||||||
|
for (String suffix : fileSuffixes) {
|
||||||
|
if (fileName.endsWith("." + suffix.trim().toLowerCase())) {
|
||||||
|
return item.getUrlSuffix();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because one or more lines are too long
@@ -100,3 +100,20 @@ spec:
|
|||||||
label: 绑定域名(CDN域名)
|
label: 绑定域名(CDN域名)
|
||||||
placeholder: 如不设置,那么将使用 Bucket + EndPoint 作为域名
|
placeholder: 如不设置,那么将使用 Bucket + EndPoint 作为域名
|
||||||
help: 协议头请在上方设置,此处无需以"http://"或"https://"开头,系统会自动拼接
|
help: 协议头请在上方设置,此处无需以"http://"或"https://"开头,系统会自动拼接
|
||||||
|
- $formkit: repeater
|
||||||
|
name: urlSuffixes
|
||||||
|
label: 网址后缀
|
||||||
|
help: 用于对指定文件类型的网址添加后缀处理参数,优先级从上到下只取第一个匹配项
|
||||||
|
value: [ ]
|
||||||
|
min: 0
|
||||||
|
children:
|
||||||
|
- $formkit: text
|
||||||
|
name: fileSuffix
|
||||||
|
label: 文件后缀
|
||||||
|
placeholder: 以半角逗号分隔,例如:jpg,jpeg,png,gif
|
||||||
|
validation: required
|
||||||
|
- $formkit: text
|
||||||
|
name: urlSuffix
|
||||||
|
label: 网址后缀
|
||||||
|
placeholder: 例如:?imageMogr2/format/webp
|
||||||
|
validation: required
|
23
src/main/resources/extensions/s3os-role-template.yaml
Normal file
23
src/main/resources/extensions/s3os-role-template.yaml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
apiVersion: v1alpha1
|
||||||
|
kind: "Role"
|
||||||
|
metadata:
|
||||||
|
name: role-template-s3os-link
|
||||||
|
labels:
|
||||||
|
halo.run/role-template: "true"
|
||||||
|
annotations:
|
||||||
|
rbac.authorization.halo.run/dependencies: |
|
||||||
|
[ "role-template-manage-attachments", "role-template-view-plugins" ]
|
||||||
|
rbac.authorization.halo.run/module: "S3 Attachments Management"
|
||||||
|
rbac.authorization.halo.run/display-name: "S3 Link"
|
||||||
|
rbac.authorization.halo.run/ui-permissions: |
|
||||||
|
["plugin:s3os:link"]
|
||||||
|
rules:
|
||||||
|
- apiGroups: [ "s3os.halo.run" ]
|
||||||
|
resources: [ "policies" ]
|
||||||
|
resourceNames: [ "s3" ]
|
||||||
|
verbs: [ "get", "list" ]
|
||||||
|
- apiGroups: [ "s3os.halo.run" ]
|
||||||
|
resources: [ "objects" ]
|
||||||
|
verbs: [ "get", "list" ]
|
||||||
|
- nonResourceURLs: ["/apis/s3os.halo.run/v1alpha1/attachments/link"]
|
||||||
|
verbs: [ "create" ]
|
@@ -1,5 +1,6 @@
|
|||||||
package run.halo.s3os;
|
package run.halo.s3os;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
@@ -12,4 +13,43 @@ class UrlUtilsTest {
|
|||||||
assert UrlUtils.removeHttpPrefix("http://www.example.com").equals("www.example.com");
|
assert UrlUtils.removeHttpPrefix("http://www.example.com").equals("www.example.com");
|
||||||
assert UrlUtils.removeHttpPrefix("https://www.example.com").equals("www.example.com");
|
assert UrlUtils.removeHttpPrefix("https://www.example.com").equals("www.example.com");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFindUrlSuffix() {
|
||||||
|
List<S3OsProperties.urlSuffixItem> urlSuffixList = List.of(
|
||||||
|
new S3OsProperties.urlSuffixItem("jpg,png,gif", "?imageMogr2/format/webp"),
|
||||||
|
new S3OsProperties.urlSuffixItem("pdf", "?123=123"),
|
||||||
|
new S3OsProperties.urlSuffixItem("jpg", "?456=456")
|
||||||
|
);
|
||||||
|
|
||||||
|
// 测试文件名为"example.jpg",期望匹配到"?imageMogr2/format/webp",只匹配第一个后缀
|
||||||
|
String fileName1 = "example.jpg";
|
||||||
|
String result1 = UrlUtils.findUrlSuffix(urlSuffixList, fileName1);
|
||||||
|
assertEquals("?imageMogr2/format/webp", result1);
|
||||||
|
|
||||||
|
// 测试文件名为"Document.PDF",期望匹配到"?123=123",不区分大小写
|
||||||
|
String fileName2 = "Document.PDF";
|
||||||
|
String result2 = UrlUtils.findUrlSuffix(urlSuffixList, fileName2);
|
||||||
|
assertEquals("?123=123", result2);
|
||||||
|
|
||||||
|
// 测试文件名为"unknown.txt",期望没有匹配项,返回null
|
||||||
|
String fileName3 = "unknown.txt";
|
||||||
|
String result3 = UrlUtils.findUrlSuffix(urlSuffixList, fileName3);
|
||||||
|
assertNull(result3);
|
||||||
|
|
||||||
|
// 测试无后缀文件名"example",期望没有匹配项,返回null
|
||||||
|
String fileName4 = "example";
|
||||||
|
String result4 = UrlUtils.findUrlSuffix(urlSuffixList, fileName4);
|
||||||
|
assertNull(result4);
|
||||||
|
|
||||||
|
// 测试空文件名,期望返回null
|
||||||
|
String fileName5 = "";
|
||||||
|
String result5 = UrlUtils.findUrlSuffix(urlSuffixList, fileName5);
|
||||||
|
assertNull(result5);
|
||||||
|
|
||||||
|
// 测试urlSuffixList为null,期望返回null
|
||||||
|
String fileName6 = "example";
|
||||||
|
String result6 = UrlUtils.findUrlSuffix(null, fileName6);
|
||||||
|
assertNull(result6);
|
||||||
|
}
|
||||||
}
|
}
|
Reference in New Issue
Block a user