mirror of https://github.com/halo-dev/halo
chore: rename thumbnail parameter from width to size for clarity (#6533)
#### What type of PR is this? /kind improvement /area core /milestone 2.19.x #### What this PR does / why we need it: 重命名缩略图大小的参数名以便和主题端 finder 用法保持一致 同时确保通过 encode 或者没有 encode 的 uri 都可以获取到缩略图 #### Does this PR introduce a user-facing change? ```release-note None ```pull/6536/head
parent
ac0700e668
commit
a5c6d6672f
|
@ -5,12 +5,9 @@ import static run.halo.app.infra.FileCategoryMatcher.IMAGE;
|
|||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import org.springframework.web.util.UriUtils;
|
||||
import run.halo.app.core.extension.attachment.Attachment;
|
||||
|
||||
@UtilityClass
|
||||
|
@ -41,16 +38,4 @@ public class AttachmentUtils {
|
|||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode uri string to URI.
|
||||
* This method will decode the uri string first and then encode it.
|
||||
*/
|
||||
public static URI encodeUri(String uriStr) {
|
||||
var decodedUriStr = UriUtils.decode(uriStr, StandardCharsets.UTF_8);
|
||||
return UriComponentsBuilder.fromUriString(decodedUriStr)
|
||||
.encode(StandardCharsets.UTF_8)
|
||||
.build()
|
||||
.toUri();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ public interface LocalThumbnailService {
|
|||
* @return The original image URI, {@link NotFoundException} will be thrown if the thumbnail
|
||||
* record does not exist by the given thumbnail URI
|
||||
*/
|
||||
Mono<URI> getOriginalImageUri(String thumbnailUri);
|
||||
Mono<URI> getOriginalImageUri(URI thumbnailUri);
|
||||
|
||||
/**
|
||||
* <p>Gets thumbnail file resource for the given year, size and filename.</p>
|
||||
|
@ -29,7 +29,7 @@ public interface LocalThumbnailService {
|
|||
* @param thumbnailUri The thumbnail URI string
|
||||
* @return The thumbnail file resource
|
||||
*/
|
||||
Mono<Resource> getThumbnail(String thumbnailUri);
|
||||
Mono<Resource> getThumbnail(URI thumbnailUri);
|
||||
|
||||
/**
|
||||
* <p>Gets thumbnail file resource for the given URI and size.</p>
|
||||
|
@ -77,5 +77,5 @@ public interface LocalThumbnailService {
|
|||
|
||||
Path toFilePath(String thumbRelativeUnixPath);
|
||||
|
||||
String buildThumbnailUri(String year, ThumbnailSize size, String filename);
|
||||
URI buildThumbnailUri(String year, ThumbnailSize size, String filename);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ import java.io.File;
|
|||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
@ -138,8 +140,15 @@ public class ThumbnailGenerator {
|
|||
|
||||
static class ImageDownloader {
|
||||
public Path downloadFile(URL url) throws IOException {
|
||||
var encodedUri = AttachmentUtils.encodeUri(url.toString());
|
||||
return downloadFileInternal(encodedUri.toURL());
|
||||
return downloadFileInternal(encodedUrl(url));
|
||||
}
|
||||
|
||||
private static URL encodedUrl(URL url) {
|
||||
try {
|
||||
return new URL(url.toURI().toASCIIString());
|
||||
} catch (MalformedURLException | URISyntaxException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
Path downloadFileInternal(URL url) throws IOException {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package run.halo.app.core.attachment;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
@ -34,4 +35,8 @@ public class ThumbnailSigner {
|
|||
throw new RuntimeException(ALGORITHM + " algorithm not found", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static String generateSignature(URI uri) {
|
||||
return generateSignature(uri.toASCIIString());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -74,13 +74,13 @@ public class LocalThumbnailServiceImpl implements LocalThumbnailService {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Mono<URI> getOriginalImageUri(String thumbnailUri) {
|
||||
public Mono<URI> getOriginalImageUri(URI thumbnailUri) {
|
||||
return fetchThumbnail(thumbnailUri)
|
||||
.map(local -> URI.create(local.getSpec().getImageUri()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Resource> getThumbnail(String thumbnailUri) {
|
||||
public Mono<Resource> getThumbnail(URI thumbnailUri) {
|
||||
Assert.notNull(thumbnailUri, "Thumbnail URI must not be null.");
|
||||
return fetchThumbnail(thumbnailUri)
|
||||
.flatMap(thumbnail -> {
|
||||
|
@ -100,7 +100,7 @@ public class LocalThumbnailServiceImpl implements LocalThumbnailService {
|
|||
.flatMap(this::generate);
|
||||
}
|
||||
|
||||
private Mono<LocalThumbnail> fetchThumbnail(String thumbnailUri) {
|
||||
private Mono<LocalThumbnail> fetchThumbnail(URI thumbnailUri) {
|
||||
Assert.notNull(thumbnailUri, "Thumbnail URI must not be null.");
|
||||
var thumbSignature = ThumbnailSigner.generateSignature(thumbnailUri);
|
||||
return client.listBy(LocalThumbnail.class, ListOptions.builder()
|
||||
|
@ -171,10 +171,10 @@ public class LocalThumbnailServiceImpl implements LocalThumbnailService {
|
|||
thumbnail.setSpec(new LocalThumbnail.Spec()
|
||||
.setImageSignature(signatureForImageUri(imageUri))
|
||||
.setFilePath(toRelativeUnixPath(filePath))
|
||||
.setImageUri(ensureInSiteUriIsRelative(imageUri).toString())
|
||||
.setImageUri(ensureInSiteUriIsRelative(imageUri).toASCIIString())
|
||||
.setSize(size)
|
||||
.setThumbSignature(thumbSignature)
|
||||
.setThumbnailUri(thumbnailUri));
|
||||
.setThumbnailUri(thumbnailUri.toASCIIString()));
|
||||
return client.create(thumbnail);
|
||||
});
|
||||
}
|
||||
|
@ -236,8 +236,9 @@ public class LocalThumbnailServiceImpl implements LocalThumbnailService {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String buildThumbnailUri(String year, ThumbnailSize size, String filename) {
|
||||
return "/upload/thumbnails/%s/w%s/%s".formatted(year, size.getWidth(), filename);
|
||||
public URI buildThumbnailUri(String year, ThumbnailSize size, String filename) {
|
||||
return URI.create("/upload/thumbnails/%s/w%s/%s".formatted(year, size.getWidth(),
|
||||
filename));
|
||||
}
|
||||
|
||||
private String toRelativeUnixPath(Path filePath) {
|
||||
|
@ -266,7 +267,7 @@ public class LocalThumbnailServiceImpl implements LocalThumbnailService {
|
|||
* image URL to the external URL.</p>
|
||||
*/
|
||||
String signatureForImageUri(URI imageUri) {
|
||||
var uriToSign = ensureInSiteUriIsRelative(imageUri).toString();
|
||||
var uriToSign = ensureInSiteUriIsRelative(imageUri);
|
||||
return ThumbnailSigner.generateSignature(uriToSign);
|
||||
}
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ public class ThumbnailServiceImpl implements ThumbnailService {
|
|||
var imageUrl = imageUrlOpt.get();
|
||||
return fetchThumbnail(imageUri, size)
|
||||
.map(thumbnail -> URI.create(thumbnail.getSpec().getThumbnailUri()))
|
||||
.switchIfEmpty(create(imageUrl, size))
|
||||
.switchIfEmpty(Mono.defer(() -> create(imageUrl, size)))
|
||||
.onErrorResume(Throwable.class, e -> {
|
||||
log.warn("Failed to generate thumbnail for image: {}", imageUrl, e);
|
||||
return Mono.just(URI.create(imageUrl.toString()));
|
||||
|
@ -108,8 +108,8 @@ public class ThumbnailServiceImpl implements ThumbnailService {
|
|||
thumb.getMetadata().setGenerateName("thumb-");
|
||||
thumb.setSpec(new Thumbnail.Spec()
|
||||
.setSize(size)
|
||||
.setThumbnailUri(uri.toString())
|
||||
.setImageUri(imageUri.toString())
|
||||
.setThumbnailUri(uri.toASCIIString())
|
||||
.setImageUri(imageUri.toASCIIString())
|
||||
.setImageSignature(signatureFor(imageUri))
|
||||
);
|
||||
return client.create(thumb)
|
||||
|
@ -119,7 +119,7 @@ public class ThumbnailServiceImpl implements ThumbnailService {
|
|||
|
||||
private String signatureFor(URI imageUri) {
|
||||
var uri = localThumbnailService.ensureInSiteUriIsRelative(imageUri);
|
||||
return ThumbnailSigner.generateSignature(uri.toString());
|
||||
return ThumbnailSigner.generateSignature(uri);
|
||||
}
|
||||
|
||||
Mono<Thumbnail> fetchThumbnail(URI imageUri, ThumbnailSize size) {
|
||||
|
|
|
@ -10,7 +10,6 @@ import lombok.Getter;
|
|||
import lombok.experimental.Accessors;
|
||||
import org.springframework.lang.NonNull;
|
||||
import run.halo.app.core.attachment.AttachmentRootGetter;
|
||||
import run.halo.app.core.attachment.ThumbnailSigner;
|
||||
import run.halo.app.core.attachment.ThumbnailSize;
|
||||
import run.halo.app.extension.AbstractExtension;
|
||||
import run.halo.app.extension.GVK;
|
||||
|
@ -33,10 +32,6 @@ public class LocalThumbnail extends AbstractExtension {
|
|||
this.status = (status == null ? new Status() : status);
|
||||
}
|
||||
|
||||
public static String signatureFor(String imageUri) {
|
||||
return ThumbnailSigner.generateSignature(imageUri);
|
||||
}
|
||||
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(name = "LocalThumbnailSpec")
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package run.halo.app.theme.endpoint;
|
||||
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
|
||||
import static org.apache.commons.lang3.StringUtils.removeStart;
|
||||
import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder;
|
||||
import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder;
|
||||
|
||||
|
@ -27,7 +26,6 @@ import org.springframework.web.reactive.function.server.ServerRequest;
|
|||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import org.springframework.web.server.ServerWebInputException;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.core.attachment.AttachmentUtils;
|
||||
import run.halo.app.core.attachment.LocalThumbnailService;
|
||||
import run.halo.app.core.attachment.ThumbnailService;
|
||||
import run.halo.app.core.attachment.ThumbnailSize;
|
||||
|
@ -64,7 +62,7 @@ public class ThumbnailEndpoint implements CustomEndpoint {
|
|||
|
||||
private Mono<ServerResponse> getThumbnailByUri(ServerRequest request) {
|
||||
var query = new ThumbnailQuery(request.queryParams());
|
||||
return thumbnailService.generate(query.getUri(), query.getWidth())
|
||||
return thumbnailService.generate(query.getUri(), query.getSize())
|
||||
.defaultIfEmpty(query.getUri())
|
||||
.flatMap(uri -> ServerResponse.permanentRedirect(uri).build());
|
||||
}
|
||||
|
@ -82,17 +80,20 @@ public class ThumbnailEndpoint implements CustomEndpoint {
|
|||
if (StringUtils.isBlank(uriStr)) {
|
||||
throw new ServerWebInputException("Required parameter 'uri' is missing");
|
||||
}
|
||||
return AttachmentUtils.encodeUri(uriStr);
|
||||
try {
|
||||
return URI.create(uriStr);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new ServerWebInputException("Invalid URI: " + uriStr);
|
||||
}
|
||||
}
|
||||
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
public ThumbnailSize getWidth() {
|
||||
var width = params.getFirst("width");
|
||||
if (StringUtils.isBlank(width)) {
|
||||
throw new ServerWebInputException("Required parameter 'width' is missing");
|
||||
public ThumbnailSize getSize() {
|
||||
var size = params.getFirst("size");
|
||||
if (StringUtils.isBlank(size)) {
|
||||
throw new ServerWebInputException("Required parameter 'size' is missing");
|
||||
}
|
||||
// Remove the 'w' prefix
|
||||
return ThumbnailSize.fromWidth(removeStart(width, 'w'));
|
||||
return ThumbnailSize.fromName(size);
|
||||
}
|
||||
|
||||
public static void buildParameters(Builder builder) {
|
||||
|
@ -103,8 +104,8 @@ public class ThumbnailEndpoint implements CustomEndpoint {
|
|||
.required(true))
|
||||
.parameter(parameterBuilder()
|
||||
.in(ParameterIn.QUERY)
|
||||
.name("width")
|
||||
.description("The width of the thumbnail")
|
||||
.name("size")
|
||||
.description("The size of the thumbnail,available values are s,m,l,xl")
|
||||
.required(true));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,10 +11,10 @@ import reactor.core.publisher.Mono;
|
|||
public interface ThumbnailFinder {
|
||||
|
||||
/**
|
||||
* Generate thumbnail url from given image url and size.
|
||||
* Generate thumbnail uri from given image uri and size.
|
||||
*
|
||||
* @param size the size of thumbnail to generate
|
||||
* @return the generated thumbnail url
|
||||
*/
|
||||
Mono<String> gen(String url, String size);
|
||||
Mono<String> gen(String uri, String size);
|
||||
}
|
||||
|
|
|
@ -14,9 +14,9 @@ public class ThumbnailFinderImpl implements ThumbnailFinder {
|
|||
private final ThumbnailService thumbnailService;
|
||||
|
||||
@Override
|
||||
public Mono<String> gen(String url, String size) {
|
||||
return thumbnailService.generate(URI.create(url), ThumbnailSize.fromName(size))
|
||||
public Mono<String> gen(String uriStr, String size) {
|
||||
return thumbnailService.generate(URI.create(uriStr), ThumbnailSize.fromName(size))
|
||||
.map(URI::toString)
|
||||
.defaultIfEmpty(url);
|
||||
.defaultIfEmpty(uriStr);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
package run.halo.app.core.attachment;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Tests for {@link AttachmentUtils}.
|
||||
*
|
||||
* @author guqing
|
||||
* @since 2.19.0
|
||||
*/
|
||||
class AttachmentUtilsTest {
|
||||
|
||||
@Test
|
||||
void encodeUriTest() {
|
||||
String urlStr =
|
||||
"http://localhost:8090/upload/2022/06/CleanShot 2022-06-12 at 23.30.22@2x.webp";
|
||||
var result = AttachmentUtils.encodeUri(urlStr);
|
||||
var targetUriStr =
|
||||
"http://localhost:8090/upload/2022/06/CleanShot%202022-06-12%20at%2023.30.22@2x.webp";
|
||||
assertThat(result.toString()).isEqualTo(targetUriStr);
|
||||
|
||||
result = AttachmentUtils.encodeUri(targetUriStr);
|
||||
assertThat(result.toString()).isEqualTo(targetUriStr);
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ package run.halo.app.core.attachment;
|
|||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.net.URI;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
|
@ -18,4 +19,21 @@ class ThumbnailSignerTest {
|
|||
assertThat(signature).isEqualTo(
|
||||
"ed2e575a1d298e08df19dca9733da3f46eab5b157b1b8f9ed02f31b2f907ec79");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateSignatureWithUri() {
|
||||
var uri = URI.create("https://example.com/example.jpg");
|
||||
var signature = ThumbnailSigner.generateSignature(uri);
|
||||
assertThat(signature).isEqualTo(
|
||||
"35b8dc8b5b518222245b3f74ea3c3b292e31eb0d888102b5e5a9cdbe4fb7a976");
|
||||
|
||||
uri = URI.create("/upload/%E4%B8%AD%E6%96%87%E5%A4%B4%E5%83%8F%20hello%25avatar.png");
|
||||
signature = ThumbnailSigner.generateSignature(uri);
|
||||
var avatarPngHash = "7dc5019aea284094c5ba9c4404e3cedf779a2fec0d836cbdf542da3cb373dd3c";
|
||||
assertThat(signature).isEqualTo(avatarPngHash);
|
||||
|
||||
uri = URI.create("/upload/中文头像%20hello%25avatar.png");
|
||||
signature = ThumbnailSigner.generateSignature(uri);
|
||||
assertThat(signature).isEqualTo(avatarPngHash);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import static org.mockito.ArgumentMatchers.isA;
|
|||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static run.halo.app.core.attachment.ThumbnailSigner.generateSignature;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
|
@ -54,7 +55,7 @@ class LocalThumbnailServiceImplTest {
|
|||
void endpointForTest() {
|
||||
var endpoint =
|
||||
localThumbnailService.buildThumbnailUri("2024", ThumbnailSize.L, "example.jpg");
|
||||
assertThat(endpoint).isEqualTo("/upload/thumbnails/2024/w1200/example.jpg");
|
||||
assertThat(endpoint.toString()).isEqualTo("/upload/thumbnails/2024/w1200/example.jpg");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -90,23 +91,20 @@ class LocalThumbnailServiceImplTest {
|
|||
void signatureForImageUriTest() throws MalformedURLException {
|
||||
when(externalUrlSupplier.getRaw()).thenReturn(new URL("http://localhost:8090"));
|
||||
var signature = signatureForImageUriStr("http://localhost:8090/example.jpg");
|
||||
assertThat(signature).isEqualTo(LocalThumbnail.signatureFor("/example.jpg"));
|
||||
assertThat(signature).isEqualTo(generateSignature("/example.jpg"));
|
||||
|
||||
signature = signatureForImageUriStr("http://localhost:8090/example.jpg");
|
||||
assertThat(signature).isEqualTo(LocalThumbnail.signatureFor("/example.jpg"));
|
||||
assertThat(signature).isEqualTo(generateSignature("/example.jpg"));
|
||||
|
||||
signature = signatureForImageUriStr("http://localhost:8091/example.jpg");
|
||||
assertThat(signature).isEqualTo(
|
||||
LocalThumbnail.signatureFor("http://localhost:8091/example.jpg"));
|
||||
assertThat(signature).isEqualTo(generateSignature("http://localhost:8091/example.jpg"));
|
||||
|
||||
signature = signatureForImageUriStr("localhost:8090/example.jpg");
|
||||
assertThat(signature).isEqualTo(
|
||||
LocalThumbnail.signatureFor("localhost:8090/example.jpg"));
|
||||
assertThat(signature).isEqualTo(generateSignature("localhost:8090/example.jpg"));
|
||||
|
||||
when(externalUrlSupplier.getRaw()).thenReturn(null);
|
||||
signature = signatureForImageUriStr("http://localhost:8090/example.jpg");
|
||||
assertThat(signature).isEqualTo(
|
||||
LocalThumbnail.signatureFor("http://localhost:8090/example.jpg"));
|
||||
assertThat(signature).isEqualTo(generateSignature("http://localhost:8090/example.jpg"));
|
||||
}
|
||||
|
||||
String signatureForImageUriStr(String uriStr) {
|
||||
|
|
Loading…
Reference in New Issue