mirror of
https://github.com/halo-dev/plugin-s3.git
synced 2025-10-15 06:39:08 +00:00
Compare commits
6 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
07127d7e54 | ||
![]() |
565d3cfcaa | ||
![]() |
c0fb2b1017 | ||
![]() |
c79fee9ba1 | ||
![]() |
08d6ff49c8 | ||
![]() |
73112953ba |
@@ -2,7 +2,7 @@ plugins {
|
||||
id 'java'
|
||||
id "com.github.node-gradle.node" version "5.0.0"
|
||||
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'
|
||||
@@ -16,7 +16,7 @@ repositories {
|
||||
}
|
||||
|
||||
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'
|
||||
|
||||
implementation platform('software.amazon.awssdk:bom:2.19.8')
|
||||
@@ -33,7 +33,7 @@ configurations.runtimeClasspath {
|
||||
|
||||
|
||||
halo {
|
||||
version = '2.9.0'
|
||||
version = '2.10.0'
|
||||
}
|
||||
|
||||
haloPlugin {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import {definePlugin} 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";
|
||||
|
||||
export default definePlugin({
|
||||
@@ -12,8 +12,8 @@ export default definePlugin({
|
||||
{
|
||||
id: "s3-link",
|
||||
label: "关联S3文件",
|
||||
component: markRaw(HomeView),
|
||||
permissions: []
|
||||
component: markRaw(S3Link),
|
||||
permissions: ["plugin:s3os:link"]
|
||||
},
|
||||
];
|
||||
},
|
||||
|
@@ -242,9 +242,9 @@ const handleModalClose = () => {
|
||||
<template #header>
|
||||
<div class="block w-full bg-gray-50 px-4 py-3">
|
||||
<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
|
||||
v-model="checkedAll"
|
||||
class="h-4 w-4 rounded border-gray-300 text-indigo-600"
|
||||
@@ -274,8 +274,7 @@ const handleModalClose = () => {
|
||||
</VButton>
|
||||
</VSpace>
|
||||
</div>
|
||||
<div class="mt-4 flex sm:mt-0">
|
||||
<VSpace spacing="lg">
|
||||
<VSpace spacing="lg" class="flex-wrap">
|
||||
<FilterCleanButton
|
||||
v-if="selectedLinkedStatusItem != linkedStatusItems[0].value"
|
||||
@click="selectedLinkedStatusItem = linkedStatusItems[0].value"
|
||||
@@ -303,7 +302,6 @@ const handleModalClose = () => {
|
||||
</VSpace>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<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 -> {
|
||||
var objectDetail = new S3OsAttachmentHandler.ObjectDetail(
|
||||
new S3OsAttachmentHandler.UploadState(properties,
|
||||
FileNameUtils.extractFileNameFromS3Key(objectKey)),
|
||||
FileNameUtils.extractFileNameFromS3Key(objectKey), false),
|
||||
headObjectResponse);
|
||||
return handler.buildAttachment(properties, objectDetail);
|
||||
})
|
||||
|
@@ -65,6 +65,7 @@ import software.amazon.awssdk.utils.SdkAutoCloseable;
|
||||
public class S3OsAttachmentHandler implements AttachmentHandler {
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
@@ -157,6 +158,10 @@ public class S3OsAttachmentHandler implements AttachmentHandler {
|
||||
}
|
||||
var properties = getProperties(configMap);
|
||||
var objectURL = getObjectURL(properties, objectKey);
|
||||
var urlSuffix = getUrlSuffixAnnotation(attachment);
|
||||
if (StringUtils.isNotBlank(urlSuffix)) {
|
||||
objectURL += urlSuffix;
|
||||
}
|
||||
return Mono.just(URI.create(objectURL));
|
||||
}
|
||||
|
||||
@@ -169,6 +174,15 @@ public class S3OsAttachmentHandler implements AttachmentHandler {
|
||||
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) {
|
||||
var settingJson = configMap.getData().getOrDefault("default", "{}");
|
||||
return JsonUtils.jsonToObject(settingJson, S3OsProperties.class);
|
||||
@@ -176,12 +190,19 @@ public class S3OsAttachmentHandler implements AttachmentHandler {
|
||||
|
||||
Attachment buildAttachment(S3OsProperties properties, ObjectDetail objectDetail) {
|
||||
String externalLink = getObjectURL(properties, objectDetail.uploadState.objectKey);
|
||||
var urlSuffix = UrlUtils.findUrlSuffix(properties.getUrlSuffixes(),
|
||||
objectDetail.uploadState.fileName);
|
||||
|
||||
var metadata = new Metadata();
|
||||
metadata.setName(UUID.randomUUID().toString());
|
||||
metadata.setAnnotations(new HashMap<>(
|
||||
Map.of(OBJECT_KEY, objectDetail.uploadState.objectKey,
|
||||
Constant.EXTERNAL_LINK_ANNO_KEY, externalLink)));
|
||||
|
||||
var annotations = new HashMap<>(Map.of(OBJECT_KEY, objectDetail.uploadState.objectKey));
|
||||
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 spec = new AttachmentSpec();
|
||||
@@ -283,7 +304,7 @@ public class S3OsAttachmentHandler implements AttachmentHandler {
|
||||
Mono<ObjectDetail> upload(UploadContext uploadContext, S3OsProperties properties) {
|
||||
return Mono.using(() -> buildS3Client(properties),
|
||||
client -> {
|
||||
var uploadState = new UploadState(properties, uploadContext.file().filename());
|
||||
var uploadState = new UploadState(properties, uploadContext.file().filename(), true);
|
||||
|
||||
var content = uploadContext.file().content();
|
||||
|
||||
@@ -471,12 +492,14 @@ public class S3OsAttachmentHandler implements AttachmentHandler {
|
||||
String objectKey;
|
||||
boolean needRemoveMapKey = false;
|
||||
|
||||
public UploadState(S3OsProperties properties, String fileName) {
|
||||
public UploadState(S3OsProperties properties, String fileName, boolean needRandomJudge) {
|
||||
this.properties = properties;
|
||||
this.originalFileName = fileName;
|
||||
|
||||
if (needRandomJudge) {
|
||||
fileName = FileNameUtils.getRandomFilename(fileName,
|
||||
properties.getRandomStringLength(), properties.getRandomFilenameMode());
|
||||
}
|
||||
|
||||
this.fileName = fileName;
|
||||
this.objectKey = properties.getObjectName(fileName);
|
||||
|
@@ -1,6 +1,10 @@
|
||||
package run.halo.s3os;
|
||||
|
||||
import java.util.List;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.time.LocalDate;
|
||||
@@ -39,6 +43,16 @@ class S3OsProperties {
|
||||
|
||||
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) {
|
||||
var objectName = filename;
|
||||
var finalName = FilePathUtils.getFilePathByPlaceholder(getLocation());
|
||||
|
@@ -2,6 +2,7 @@ package run.halo.s3os;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
public class UrlUtils {
|
||||
private static final List<String> HTTP_PREFIXES = Arrays.asList("http://", "https://");
|
||||
@@ -17,4 +18,21 @@ public class UrlUtils {
|
||||
}
|
||||
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域名)
|
||||
placeholder: 如不设置,那么将使用 Bucket + EndPoint 作为域名
|
||||
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;
|
||||
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
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("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