mirror of https://github.com/halo-dev/halo
refactor: add uniqueness check for local thumbnail original links and dimensions to avoid duplication (#7031)
#### What type of PR is this? /kind improvement /area core /milestone 2.20.x #### What this PR does / why we need it: 对本地缩略图的原图链接和尺寸增加唯一性检查避免重复 #### Does this PR introduce a user-facing change? ```release-note 对本地缩略图的原图链接和尺寸增加唯一性检查避免重复 ```pull/7075/head
parent
391aac62d3
commit
0b505a9050
|
@ -19,6 +19,7 @@ import run.halo.app.extension.GVK;
|
||||||
@GVK(group = "storage.halo.run", version = "v1alpha1", kind = "LocalThumbnail",
|
@GVK(group = "storage.halo.run", version = "v1alpha1", kind = "LocalThumbnail",
|
||||||
plural = "localthumbnails", singular = "localthumbnail")
|
plural = "localthumbnails", singular = "localthumbnail")
|
||||||
public class LocalThumbnail extends AbstractExtension {
|
public class LocalThumbnail extends AbstractExtension {
|
||||||
|
public static final String UNIQUE_IMAGE_AND_SIZE_INDEX = "uniqueImageAndSize";
|
||||||
public static final String REQUEST_TO_GENERATE_ANNO = "storage.halo.run/request-to-generate";
|
public static final String REQUEST_TO_GENERATE_ANNO = "storage.halo.run/request-to-generate";
|
||||||
|
|
||||||
@Schema(requiredMode = REQUIRED)
|
@Schema(requiredMode = REQUIRED)
|
||||||
|
@ -80,4 +81,13 @@ public class LocalThumbnail extends AbstractExtension {
|
||||||
public enum Phase {
|
public enum Phase {
|
||||||
PENDING, SUCCEEDED, FAILED
|
PENDING, SUCCEEDED, FAILED
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String uniqueImageAndSize(LocalThumbnail localThumbnail) {
|
||||||
|
return uniqueImageAndSize(localThumbnail.getSpec().getImageSignature(),
|
||||||
|
localThumbnail.getSpec().getSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String uniqueImageAndSize(String imageSignature, ThumbnailSize size) {
|
||||||
|
return imageSignature + "-" + size.name();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,8 +64,8 @@ public class LocalThumbnailServiceImpl implements LocalThumbnailService {
|
||||||
.resolve(fileName);
|
.resolve(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
static String geImageFileName(URL imageUrl) {
|
static String geImageFileName(URI imageUri) {
|
||||||
var fileName = substringAfterLast(imageUrl.getPath(), "/");
|
var fileName = substringAfterLast(imageUri.getPath(), "/");
|
||||||
fileName = defaultIfBlank(fileName, randomAlphanumeric(10));
|
fileName = defaultIfBlank(fileName, randomAlphanumeric(10));
|
||||||
return ThumbnailGenerator.sanitizeFileName(fileName);
|
return ThumbnailGenerator.sanitizeFileName(fileName);
|
||||||
}
|
}
|
||||||
|
@ -113,11 +113,12 @@ public class LocalThumbnailServiceImpl implements LocalThumbnailService {
|
||||||
|
|
||||||
private Mono<LocalThumbnail> fetchByImageHashAndSize(String imageSignature,
|
private Mono<LocalThumbnail> fetchByImageHashAndSize(String imageSignature,
|
||||||
ThumbnailSize size) {
|
ThumbnailSize size) {
|
||||||
|
var indexValue = LocalThumbnail.uniqueImageAndSize(imageSignature, size);
|
||||||
return client.listBy(LocalThumbnail.class, ListOptions.builder()
|
return client.listBy(LocalThumbnail.class, ListOptions.builder()
|
||||||
.fieldQuery(equal("spec.imageSignature", imageSignature))
|
.fieldQuery(equal(LocalThumbnail.UNIQUE_IMAGE_AND_SIZE_INDEX, indexValue))
|
||||||
.build(), PageRequestImpl.ofSize(ThumbnailSize.values().length))
|
.build(), PageRequestImpl.ofSize(ThumbnailSize.values().length)
|
||||||
|
)
|
||||||
.flatMapMany(result -> Flux.fromIterable(result.getItems()))
|
.flatMapMany(result -> Flux.fromIterable(result.getItems()))
|
||||||
.filter(thumbnail -> thumbnail.getSpec().getSize().equals(size))
|
|
||||||
.next();
|
.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,9 +159,15 @@ public class LocalThumbnailServiceImpl implements LocalThumbnailService {
|
||||||
public Mono<LocalThumbnail> create(URL imageUrl, ThumbnailSize size) {
|
public Mono<LocalThumbnail> create(URL imageUrl, ThumbnailSize size) {
|
||||||
Assert.notNull(imageUrl, "Image URL must not be null.");
|
Assert.notNull(imageUrl, "Image URL must not be null.");
|
||||||
Assert.notNull(size, "Thumbnail size must not be null.");
|
Assert.notNull(size, "Thumbnail size must not be null.");
|
||||||
var year = getYear();
|
|
||||||
var originalFileName = geImageFileName(imageUrl);
|
|
||||||
var imageUri = URI.create(imageUrl.toString());
|
var imageUri = URI.create(imageUrl.toString());
|
||||||
|
var imageHash = signatureForImageUri(imageUri);
|
||||||
|
return fetchByImageHashAndSize(imageHash, size)
|
||||||
|
.switchIfEmpty(Mono.defer(() -> doCreate(imageUri, size)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Mono<LocalThumbnail> doCreate(URI imageUri, ThumbnailSize size) {
|
||||||
|
var year = getYear();
|
||||||
|
var originalFileName = geImageFileName(imageUri);
|
||||||
return generateUniqueThumbFileName(originalFileName, year, size)
|
return generateUniqueThumbFileName(originalFileName, year, size)
|
||||||
.flatMap(thumbFileName -> {
|
.flatMap(thumbFileName -> {
|
||||||
var filePath =
|
var filePath =
|
||||||
|
|
|
@ -612,6 +612,14 @@ public class SchemeInitializer implements ApplicationListener<ApplicationContext
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
schemeManager.register(LocalThumbnail.class, indexSpec -> {
|
schemeManager.register(LocalThumbnail.class, indexSpec -> {
|
||||||
|
// make sure image and size are unique
|
||||||
|
indexSpec.add(new IndexSpec()
|
||||||
|
.setUnique(true)
|
||||||
|
.setName(LocalThumbnail.UNIQUE_IMAGE_AND_SIZE_INDEX)
|
||||||
|
.setIndexFunc(simpleAttribute(LocalThumbnail.class,
|
||||||
|
LocalThumbnail::uniqueImageAndSize)
|
||||||
|
)
|
||||||
|
);
|
||||||
indexSpec.add(new IndexSpec()
|
indexSpec.add(new IndexSpec()
|
||||||
.setName("spec.imageSignature")
|
.setName("spec.imageSignature")
|
||||||
.setIndexFunc(simpleAttribute(LocalThumbnail.class,
|
.setIndexFunc(simpleAttribute(LocalThumbnail.class,
|
||||||
|
|
|
@ -12,6 +12,7 @@ import static run.halo.app.core.attachment.ThumbnailSigner.generateSignature;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
@ -20,6 +21,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.springframework.web.util.UriUtils;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import reactor.test.StepVerifier;
|
import reactor.test.StepVerifier;
|
||||||
import run.halo.app.core.attachment.AttachmentRootGetter;
|
import run.halo.app.core.attachment.AttachmentRootGetter;
|
||||||
|
@ -61,14 +63,14 @@ class LocalThumbnailServiceImplTest {
|
||||||
@Test
|
@Test
|
||||||
void geImageFileNameTest() throws MalformedURLException {
|
void geImageFileNameTest() throws MalformedURLException {
|
||||||
var fileName =
|
var fileName =
|
||||||
LocalThumbnailServiceImpl.geImageFileName(new URL("https://halo.run/example.jpg"));
|
LocalThumbnailServiceImpl.geImageFileName(URI.create("https://halo.run/example.jpg"));
|
||||||
assertThat(fileName).isEqualTo("example.jpg");
|
assertThat(fileName).isEqualTo("example.jpg");
|
||||||
|
|
||||||
fileName = LocalThumbnailServiceImpl.geImageFileName(new URL("https://halo.run/"));
|
fileName = LocalThumbnailServiceImpl.geImageFileName(URI.create("https://halo.run/"));
|
||||||
assertThat(fileName).isNotBlank();
|
assertThat(fileName).isNotBlank();
|
||||||
|
|
||||||
fileName = LocalThumbnailServiceImpl.geImageFileName(
|
var encoded = UriUtils.encode("https://halo.run/.1fasfg(*&^%$.jpg", StandardCharsets.UTF_8);
|
||||||
new URL("https://halo.run/.1fasfg(*&^%$.jpg"));
|
fileName = LocalThumbnailServiceImpl.geImageFileName(URI.create(encoded));
|
||||||
assertThat(fileName).isNotBlank();
|
assertThat(fileName).isNotBlank();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue