Refactor thumbnail processing to utilize ThumbnailService for improved image handling and srcset generation

John Niang 2025-09-29 16:24:26 +08:00
parent 230550d0df
commit 8c8ff4c1ea
No known key found for this signature in database
GPG Key ID: D7363C015BBCAA59
1 changed files with 29 additions and 62 deletions

View File

@ -3,21 +3,18 @@ package run.halo.app.core.attachment;
import static org.thymeleaf.templatemode.TemplateMode.HTML; import static org.thymeleaf.templatemode.TemplateMode.HTML;
import java.net.URI; import java.net.URI;
import java.util.Objects; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.thymeleaf.context.ITemplateContext; import org.thymeleaf.context.ITemplateContext;
import org.thymeleaf.engine.ElementNames; import org.thymeleaf.engine.ElementNames;
import org.thymeleaf.model.IAttribute; import org.thymeleaf.model.IAttribute;
import org.thymeleaf.model.IProcessableElementTag; import org.thymeleaf.model.IProcessableElementTag;
import org.thymeleaf.processor.element.MatchingElementName; import org.thymeleaf.processor.element.MatchingElementName;
import org.thymeleaf.spring6.context.SpringContextUtils;
import org.thymeleaf.spring6.context.webflux.SpringWebFluxThymeleafRequestContext;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import run.halo.app.infra.ExternalUrlSupplier; import run.halo.app.infra.ExternalUrlSupplier;
import run.halo.app.theme.dialect.ElementTagPostProcessor; import run.halo.app.theme.dialect.ElementTagPostProcessor;
@ -30,8 +27,12 @@ class ThumbnailImgTagPostProcessor implements ElementTagPostProcessor {
private final ExternalUrlSupplier externalUrlSupplier; private final ExternalUrlSupplier externalUrlSupplier;
public ThumbnailImgTagPostProcessor(ExternalUrlSupplier externalUrlSupplier) { private final ThumbnailService thumbnailService;
public ThumbnailImgTagPostProcessor(ExternalUrlSupplier externalUrlSupplier,
ThumbnailService thumbnailService) {
this.externalUrlSupplier = externalUrlSupplier; this.externalUrlSupplier = externalUrlSupplier;
this.thumbnailService = thumbnailService;
this.matchingElementName = this.matchingElementName =
MatchingElementName.forElementName(HTML, ElementNames.forHTMLName("img")); MatchingElementName.forElementName(HTML, ElementNames.forHTMLName("img"));
} }
@ -57,63 +58,29 @@ class ThumbnailImgTagPostProcessor implements ElementTagPostProcessor {
// get img tag // get img tag
var imageUri = srcValue.get(); var imageUri = srcValue.get();
if (imageUri.isAbsolute()) { return thumbnailService.get(imageUri)
// check if the uri is belonged to current site .filter(Predicate.not(Map::isEmpty))
var requestContext = SpringContextUtils.getRequestContext(context); .map(thumbnails -> {
if (!(requestContext instanceof SpringWebFluxThymeleafRequestContext wrc)) { var modelFactory = context.getModelFactory();
log.debug("Skip processing img tag with absolute url: {}, " var newTag = tag;
+ "because the request context is not webflux", imageUri); if (!newTag.hasAttribute("sizes")) {
return Mono.empty(); newTag = modelFactory.setAttribute(newTag, "sizes", """
} (max-width: 640px) 94vw, \
var externalUri = externalUrlSupplier.get(); (max-width: 768px) 92vw, \
if (!externalUri.isAbsolute()) { (max-width: 1024px) 88vw, \
externalUri = wrc.getServerWebExchange().getRequest().getURI(); min(800px, 85vw)\
} """);
if (!Objects.equals(externalUri.getAuthority(), imageUri.getAuthority())) { }
log.debug(""" var srcset = thumbnails.keySet().stream()
Skip processing img tag with external absolute url: {} because \ .map(size -> {
the url does not belong to the current site\ var uri = thumbnails.get(size);
""", imageUri); return uri + " " + size.getWidth() + "w";
return Mono.empty(); })
} .collect(Collectors.joining(", "));
} newTag = modelFactory.setAttribute(newTag, "srcset", srcset);
return newTag;
var path = imageUri.getPath();
if (!path.startsWith("/upload/")) {
log.debug("Skip processing img tag with non-upload path: {}", path);
return Mono.empty();
}
var fileSuffix = FilenameUtils.getExtension(imageUri.getPath());
if (!ThumbnailUtils.isSupportedImage(fileSuffix)) {
log.debug("Skip processing img tag with unsupported image suffix: {}", fileSuffix);
return Mono.empty();
}
// build thumbnails
var thumbnails = ThumbnailUtils.buildSrcsetMap(imageUri);
if (CollectionUtils.isEmpty(thumbnails)) {
log.debug("Skip processing img tag because the image is not supported: {}", imageUri);
return Mono.empty();
}
var modelFactory = context.getModelFactory();
if (!tag.hasAttribute("sizes")) {
tag = modelFactory.setAttribute(tag, "sizes", """
(max-width: 640px) 94vw, \
(max-width: 768px) 92vw, \
(max-width: 1024px) 88vw, \
min(800px, 85vw)\
""");
}
var srcset = thumbnails.keySet().stream()
.map(size -> {
var uri = thumbnails.get(size);
return uri + " " + size.getWidth() + "w";
}) })
.collect(Collectors.joining(", ")); .defaultIfEmpty(tag);
tag = modelFactory.setAttribute(tag, "srcset", srcset);
return Mono.just(tag);
} }
} }