4 Commits
1.5.0 ... 1.6.0

Author SHA1 Message Date
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
11 changed files with 169 additions and 41 deletions

View File

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

View File

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

View File

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

View File

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

View File

@@ -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;
fileName = FileNameUtils.getRandomFilename(fileName,
if (needRandomJudge) {
fileName = FileNameUtils.getRandomFilename(fileName,
properties.getRandomStringLength(), properties.getRandomFilenameMode());
}
this.fileName = fileName;
this.objectKey = properties.getObjectName(fileName);

View File

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

View File

@@ -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)) {
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域名
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

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;
import java.util.List;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
@@ -12,4 +13,38 @@ 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);
}
}