6 Commits
1.5.0 ... 1.6.1

Author SHA1 Message Date
John Niang
07127d7e54 Update gradle.properties 2023-10-27 02:18:56 -05:00
longjuan
565d3cfcaa Fix findUrlSuffix NPE when upgrading from old version (#94)
fixes https://github.com/halo-dev/plugin-s3/issues/93
```release-note
修复从旧版本升级后上传文件的NPE错误
```
/kind bug
2023-10-27 02:34:15 +00:00
longjuan
c0fb2b1017 fix: s3 link error when filename renaming enable (#91)
fixes https://github.com/halo-dev/plugin-s3/issues/90
请测试 `上传时重命名文件方式` 选项为**非**保留原文件名时,是否能正常关联s3文件
![image](https://github.com/halo-dev/plugin-s3/assets/28662535/22ab0467-60bd-4c4c-a486-38bc922b7f37)

```release-note
当上传时文件重命名功能开启时关联S3文件错误
```
2023-10-26 06:38:14 +00:00
longjuan
c79fee9ba1 perf: improve s3link role permissions (#89)
1. 新建用户,仅赋予 s3link 权限
2. 测试 关联s3文件功能是否 正常使用
请使用 <ff7af9f0d9> 之后的版本进行测试


```release-note
完善 s3link 角色权限
```
2023-10-26 05:44:12 +00:00
longjuan
08d6ff49c8 feat: add URL suffix to files of specified type (#79)
增加网址后缀功能
![image](https://github.com/halo-dev/plugin-s3/assets/28662535/cd56ed01-70b0-44fc-ae40-110f7c2c1aea)

此功能在文件上传和关联文件均生效

Fix #77 
Fix #68 
```release-note
增加给指定类型的文件加上特定的网址后缀功能
```
2023-10-26 05:42:17 +00:00
longjuan
73112953ba feat: improve the styles of the data list filter area on the mobile devices (#87)
同步 <https://github.com/halo-dev/halo/pull/4587> 中的数据列表过滤器区域的样式改进。
```release-note
同步移动设备上数据列表过滤器区域的样式改进
```
2023-10-16 04:14:13 +00:00
12 changed files with 175 additions and 42 deletions

View File

@@ -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 {

View File

@@ -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"]
}, },
]; ];
}, },

View File

@@ -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,34 +274,32 @@ 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" />
/> <FilterDropdown
<FilterDropdown v-model="selectedLinkedStatusItem"
v-model="selectedLinkedStatusItem" :label="$t('core.common.filters.labels.status')"
:label="$t('core.common.filters.labels.status')" :items="linkedStatusItems"
:items="linkedStatusItems" />
/>
<div class="flex flex-row gap-2"> <div class="flex flex-row gap-2">
<div <div
class="group cursor-pointer rounded p-1 hover:bg-gray-200" class="group cursor-pointer rounded p-1 hover:bg-gray-200"
@click="fetchObjects()" @click="fetchObjects()"
> >
<IconRefreshLine <IconRefreshLine
v-tooltip="$t('core.common.buttons.refresh')" v-tooltip="$t('core.common.buttons.refresh')"
:class="{ :class="{
'animate-spin text-gray-900': isFetching, 'animate-spin text-gray-900': isFetching,
}" }"
class="h-4 w-4 text-gray-600 group-hover:text-gray-900" class="h-4 w-4 text-gray-600 group-hover:text-gray-900"
/> />
</div>
</div> </div>
</VSpace> </div>
</div> </VSpace>
</div> </div>
</div> </div>
</template> </template>

View File

@@ -1 +1 @@
version=1.5.0-SNAPSHOT version=1.6.1-SNAPSHOT

View File

@@ -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);
}) })

View File

@@ -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;
fileName = FileNameUtils.getRandomFilename(fileName, if (needRandomJudge) {
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);

View File

@@ -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());

View File

@@ -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

View File

@@ -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

View 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" ]

View File

@@ -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);
}
} }