mirror of https://github.com/halo-dev/halo
refactor: theme route pattern and permalink indexer (#2397)
#### What type of PR is this? /kind improvement /milestone 2.0 /area core #### What this PR does / why we need it: 使用正则细化主题端路由并优化 如何测试: 1. 在 admin 系统设置中修改文章文章详情页访问规则 2. 根据规则访问文章详情页,如规则为:`/{year:\d{4}}/{month:\d{2}}/{slug}` 而存在文章 slug 为 fake-slug 且发布日期为 2022-09-08 则 /2022/09/fake-slug 能访问, /2022/9/fake-slug 则不能访问 使用规则 `/{year:\d{4}}/{month:\d{2}}/{day:\d{2}}/{slug}`时 /2022/09/08/fake-slug 能访问 /2022/09/8/fake-slug ,则不能访问 #### Which issue(s) this PR fixes: Fixes #2396 #### Special notes for your reviewer: /cc @halo-dev/sig-halo #### Does this PR introduce a user-facing change? ```release-note None ```pull/2415/head
parent
9dc7bc1729
commit
069ff04c84
|
@ -58,8 +58,8 @@ public class CategoryPermalinkPolicy
|
|||
|
||||
@Override
|
||||
public void onPermalinkDelete(Category category) {
|
||||
applicationContext.publishEvent(new PermalinkIndexDeleteCommand(this, getLocator(category),
|
||||
category.getStatusOrDefault().getPermalink()));
|
||||
applicationContext.publishEvent(
|
||||
new PermalinkIndexDeleteCommand(this, getLocator(category)));
|
||||
}
|
||||
|
||||
private ExtensionLocator getLocator(Category category) {
|
||||
|
|
|
@ -11,6 +11,20 @@ import run.halo.app.extension.GroupVersionKind;
|
|||
* @param slug extension slug
|
||||
*/
|
||||
public record ExtensionLocator(GroupVersionKind gvk, String name, String slug) {
|
||||
|
||||
/**
|
||||
* Create a new {@link ExtensionLocator} instance.
|
||||
*
|
||||
* @param gvk group version kind
|
||||
* @param name extension name
|
||||
* @param slug extension slug
|
||||
*/
|
||||
public ExtensionLocator {
|
||||
Objects.requireNonNull(gvk, "Group version kind must not be null");
|
||||
Objects.requireNonNull(name, "Extension name must not be null");
|
||||
Objects.requireNonNull(slug, "Extension slug must not be null");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
|
|
|
@ -10,6 +10,7 @@ import org.springframework.context.ApplicationContext;
|
|||
import org.springframework.stereotype.Component;
|
||||
import run.halo.app.core.extension.Post;
|
||||
import run.halo.app.extension.GroupVersionKind;
|
||||
import run.halo.app.infra.utils.PathUtils;
|
||||
import run.halo.app.theme.DefaultTemplateEnum;
|
||||
import run.halo.app.theme.router.PermalinkIndexAddCommand;
|
||||
import run.halo.app.theme.router.PermalinkIndexDeleteCommand;
|
||||
|
@ -23,7 +24,6 @@ import run.halo.app.theme.router.PermalinkWatch;
|
|||
*/
|
||||
@Component
|
||||
public class PostPermalinkPolicy implements PermalinkPolicy<Post>, PermalinkWatch<Post> {
|
||||
|
||||
private final GroupVersionKind gvk = GroupVersionKind.fromExtension(Post.class);
|
||||
private static final NumberFormat NUMBER_FORMAT = new DecimalFormat("00");
|
||||
|
||||
|
@ -65,8 +65,7 @@ public class PostPermalinkPolicy implements PermalinkPolicy<Post>, PermalinkWatc
|
|||
|
||||
@Override
|
||||
public void onPermalinkDelete(Post post) {
|
||||
applicationContext.publishEvent(new PermalinkIndexDeleteCommand(this, getLocator(post),
|
||||
post.getStatusOrDefault().getPermalink()));
|
||||
applicationContext.publishEvent(new PermalinkIndexDeleteCommand(this, getLocator(post)));
|
||||
}
|
||||
|
||||
private ExtensionLocator getLocator(Post post) {
|
||||
|
@ -85,6 +84,8 @@ public class PostPermalinkPolicy implements PermalinkPolicy<Post>, PermalinkWatc
|
|||
properties.put("year", String.valueOf(zonedDateTime.getYear()));
|
||||
properties.put("month", NUMBER_FORMAT.format(zonedDateTime.getMonthValue()));
|
||||
properties.put("day", NUMBER_FORMAT.format(zonedDateTime.getDayOfMonth()));
|
||||
return PROPERTY_PLACEHOLDER_HELPER.replacePlaceholders(pattern, properties);
|
||||
|
||||
String simplifiedPattern = PathUtils.simplifyPathPattern(pattern);
|
||||
return PROPERTY_PLACEHOLDER_HELPER.replacePlaceholders(simplifiedPattern, properties);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,8 +57,7 @@ public class TagPermalinkPolicy implements PermalinkPolicy<Tag>, PermalinkWatch<
|
|||
|
||||
@Override
|
||||
public void onPermalinkDelete(Tag tag) {
|
||||
applicationContext.publishEvent(new PermalinkIndexDeleteCommand(this, getLocator(tag),
|
||||
tag.getStatusOrDefault().getPermalink()));
|
||||
applicationContext.publishEvent(new PermalinkIndexDeleteCommand(this, getLocator(tag)));
|
||||
}
|
||||
|
||||
private ExtensionLocator getLocator(Tag tag) {
|
||||
|
|
|
@ -133,8 +133,7 @@ public class SinglePageReconciler implements Reconciler<Reconciler.Request> {
|
|||
.setPermalink(PathUtils.combinePath(singlePage.getSpec().getSlug()));
|
||||
ExtensionLocator locator = new ExtensionLocator(GVK, singlePage.getMetadata().getName(),
|
||||
singlePage.getSpec().getSlug());
|
||||
applicationContext.publishEvent(new PermalinkIndexDeleteCommand(this, locator,
|
||||
singlePage.getStatusOrDefault().getPermalink()));
|
||||
applicationContext.publishEvent(new PermalinkIndexDeleteCommand(this, locator));
|
||||
templateRouteManager.changeTemplatePattern(DefaultTemplateEnum.SINGLE_PAGE.getValue());
|
||||
}
|
||||
|
||||
|
|
|
@ -47,4 +47,35 @@ public class PathUtils {
|
|||
public static String appendPathSeparatorIfMissing(String path) {
|
||||
return StringUtils.appendIfMissing(path, "/", "/");
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Remove the regex in the path pattern placeholder.</p>
|
||||
* <p>For example: </p>
|
||||
* <ul>
|
||||
* <li>'{@code /{year:\d{4}}/{month:\d{2}}}' → '{@code /{year}/{month}}'</li>
|
||||
* <li>'{@code /archives/{year:\d{4}}/{month:\d{2}}}' → '{@code /archives/{year}/{month}
|
||||
* }'</li>
|
||||
* <li>'{@code /archives/{year:\d{4}}/{slug}}' → '{@code /archives/{year}/{slug}}'</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param pattern path pattern
|
||||
* @return Simplified path pattern
|
||||
*/
|
||||
public static String simplifyPathPattern(String pattern) {
|
||||
if (StringUtils.isBlank(pattern)) {
|
||||
return StringUtils.EMPTY;
|
||||
}
|
||||
String[] parts = StringUtils.split(pattern, '/');
|
||||
for (int i = 0; i < parts.length; i++) {
|
||||
String part = parts[i];
|
||||
if (part.startsWith("{") && part.endsWith("}")) {
|
||||
int colonIdx = part.indexOf(':');
|
||||
if (colonIdx != -1) {
|
||||
parts[i] = part.substring(0, colonIdx) + part.charAt(part.length() - 1);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return combinePath(parts);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,19 +12,13 @@ import run.halo.app.content.permalinks.ExtensionLocator;
|
|||
*/
|
||||
public class PermalinkIndexDeleteCommand extends ApplicationEvent {
|
||||
private final ExtensionLocator locator;
|
||||
private final String permalink;
|
||||
|
||||
public PermalinkIndexDeleteCommand(Object source, ExtensionLocator locator, String permalink) {
|
||||
public PermalinkIndexDeleteCommand(Object source, ExtensionLocator locator) {
|
||||
super(source);
|
||||
this.locator = locator;
|
||||
this.permalink = permalink;
|
||||
}
|
||||
|
||||
public ExtensionLocator getLocator() {
|
||||
return locator;
|
||||
}
|
||||
|
||||
public String getPermalink() {
|
||||
return permalink;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,12 +3,11 @@ package run.halo.app.theme.router;
|
|||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import run.halo.app.content.permalinks.ExtensionLocator;
|
||||
import run.halo.app.extension.GroupVersionKind;
|
||||
|
||||
|
@ -21,9 +20,12 @@ import run.halo.app.extension.GroupVersionKind;
|
|||
@Component
|
||||
public class PermalinkIndexer {
|
||||
private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
|
||||
private final MultiValueMap<GroupVersionKind, String> permalinkLookup =
|
||||
new LinkedMultiValueMap<>();
|
||||
private final Map<String, ExtensionLocator> permalinkLocatorMap = new HashMap<>();
|
||||
|
||||
private final Map<GvkName, String> gvkNamePermalinkLookup = new HashMap<>();
|
||||
private final Map<String, ExtensionLocator> permalinkLocatorLookup = new HashMap<>();
|
||||
|
||||
record GvkName(GroupVersionKind gvk, String name) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Register extension and permalink mapping.
|
||||
|
@ -34,8 +36,9 @@ public class PermalinkIndexer {
|
|||
public void register(ExtensionLocator locator, String permalink) {
|
||||
readWriteLock.writeLock().lock();
|
||||
try {
|
||||
permalinkLookup.add(locator.gvk(), permalink);
|
||||
permalinkLocatorMap.put(permalink, locator);
|
||||
GvkName gvkName = new GvkName(locator.gvk(), locator.name());
|
||||
gvkNamePermalinkLookup.put(gvkName, permalink);
|
||||
permalinkLocatorLookup.put(permalink, locator);
|
||||
} finally {
|
||||
readWriteLock.writeLock().unlock();
|
||||
}
|
||||
|
@ -44,110 +47,87 @@ public class PermalinkIndexer {
|
|||
/**
|
||||
* Remove extension and permalink mapping.
|
||||
*
|
||||
* @param locator extension locator
|
||||
* @param permalink extension permalink for theme template route
|
||||
* @param locator extension info
|
||||
*/
|
||||
public void remove(ExtensionLocator locator, String permalink) {
|
||||
public void remove(ExtensionLocator locator) {
|
||||
readWriteLock.writeLock().lock();
|
||||
try {
|
||||
List<String> permalinks = permalinkLookup.get(locator.gvk());
|
||||
if (permalinks != null) {
|
||||
permalinks.remove(permalink);
|
||||
if (permalinks.isEmpty()) {
|
||||
permalinkLookup.remove(locator.gvk());
|
||||
}
|
||||
String permalink =
|
||||
gvkNamePermalinkLookup.remove(new GvkName(locator.gvk(), locator.name()));
|
||||
if (permalink != null) {
|
||||
permalinkLocatorLookup.remove(permalink);
|
||||
}
|
||||
permalinkLocatorMap.remove(permalink);
|
||||
} finally {
|
||||
readWriteLock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup extension locator by permalink.
|
||||
* Gets permalink by {@link GroupVersionKind}.
|
||||
*
|
||||
* @param gvk group version kind
|
||||
* @return permalinks
|
||||
*/
|
||||
@NonNull
|
||||
public List<String> getPermalinks(GroupVersionKind gvk) {
|
||||
readWriteLock.readLock().lock();
|
||||
try {
|
||||
return gvkNamePermalinkLookup.entrySet()
|
||||
.stream()
|
||||
.filter(entry -> entry.getKey().gvk.equals(gvk))
|
||||
.map(Map.Entry::getValue)
|
||||
.toList();
|
||||
} finally {
|
||||
readWriteLock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup extension info by permalink.
|
||||
*
|
||||
* @param permalink extension permalink for theme template route
|
||||
* @return extension locator
|
||||
*/
|
||||
@Nullable
|
||||
public ExtensionLocator lookup(String permalink) {
|
||||
readWriteLock.readLock().lock();
|
||||
try {
|
||||
return permalinkLocatorMap.get(permalink);
|
||||
return permalinkLocatorLookup.get(permalink);
|
||||
} finally {
|
||||
readWriteLock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets permalinks by extension's {@link GroupVersionKind}.
|
||||
* Lookup extension permalink by {@link GroupVersionKind} and {@code name}.
|
||||
*
|
||||
* @param gvk extension's {@link GroupVersionKind}
|
||||
* @return permalinks for extension's {@link GroupVersionKind}
|
||||
* @param gvk group version kind
|
||||
* @param name extension name
|
||||
* @return {@code true} if contains, otherwise {@code false}
|
||||
*/
|
||||
public List<String> getPermalinks(GroupVersionKind gvk) {
|
||||
public boolean containsName(GroupVersionKind gvk, String name) {
|
||||
readWriteLock.readLock().lock();
|
||||
try {
|
||||
return Objects.requireNonNullElse(permalinkLookup.get(gvk), List.of());
|
||||
return gvkNamePermalinkLookup.containsKey(new GvkName(gvk, name));
|
||||
} finally {
|
||||
readWriteLock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup extension resource names by {@link GroupVersionKind}.
|
||||
* Lookup extension permalink by {@link GroupVersionKind} and {@code slug}.
|
||||
*
|
||||
* @param gvk extension's {@link GroupVersionKind}
|
||||
* @return extension resource names
|
||||
* @param gvk group version kind
|
||||
* @param slug extension slug
|
||||
* @return {@code true} if contains, otherwise {@code false}
|
||||
*/
|
||||
public List<String> getNames(GroupVersionKind gvk) {
|
||||
public boolean containsSlug(GroupVersionKind gvk, String slug) {
|
||||
readWriteLock.readLock().lock();
|
||||
try {
|
||||
return permalinkLocatorMap.values()
|
||||
return permalinkLocatorLookup.values()
|
||||
.stream()
|
||||
.filter(locator -> locator.gvk().equals(gvk))
|
||||
.map(ExtensionLocator::name)
|
||||
.toList();
|
||||
} finally {
|
||||
readWriteLock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup extension resource slugs by {@link GroupVersionKind}.
|
||||
*
|
||||
* @param gvk extension's {@link GroupVersionKind}
|
||||
* @return extension resource slugs
|
||||
*/
|
||||
public List<String> getSlugs(GroupVersionKind gvk) {
|
||||
readWriteLock.readLock().lock();
|
||||
try {
|
||||
return permalinkLocatorMap.values()
|
||||
.stream()
|
||||
.filter(locator -> locator.gvk().equals(gvk))
|
||||
.map(ExtensionLocator::slug)
|
||||
.toList();
|
||||
} finally {
|
||||
readWriteLock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup extension slug by resource name.
|
||||
*
|
||||
* @param gvk extension's {@link GroupVersionKind}
|
||||
* @param name extension resource name
|
||||
* @return extension slug specified by resource name
|
||||
*/
|
||||
public String getSlugByName(GroupVersionKind gvk, String name) {
|
||||
readWriteLock.readLock().lock();
|
||||
try {
|
||||
return permalinkLocatorMap.values()
|
||||
.stream()
|
||||
.filter(locator -> locator.gvk().equals(gvk))
|
||||
.filter(locator -> locator.name().equals(name))
|
||||
.findFirst()
|
||||
.map(ExtensionLocator::slug)
|
||||
.orElseThrow();
|
||||
.anyMatch(locator -> locator.gvk().equals(gvk)
|
||||
&& locator.slug().equals(slug));
|
||||
} finally {
|
||||
readWriteLock.readLock().unlock();
|
||||
}
|
||||
|
@ -163,10 +143,10 @@ public class PermalinkIndexer {
|
|||
public String getNameBySlug(GroupVersionKind gvk, String slug) {
|
||||
readWriteLock.readLock().lock();
|
||||
try {
|
||||
return permalinkLocatorMap.values()
|
||||
return permalinkLocatorLookup.values()
|
||||
.stream()
|
||||
.filter(locator -> locator.gvk().equals(gvk))
|
||||
.filter(locator -> locator.slug().equals(slug))
|
||||
.filter(locator -> locator.gvk().equals(gvk)
|
||||
&& locator.slug().equals(slug))
|
||||
.findFirst()
|
||||
.map(ExtensionLocator::name)
|
||||
.orElseThrow();
|
||||
|
@ -180,8 +160,8 @@ public class PermalinkIndexer {
|
|||
*
|
||||
* @return permalinkLookup map size
|
||||
*/
|
||||
protected long permalinkLookupSize() {
|
||||
return permalinkLookup.size();
|
||||
protected long gvkNamePermalinkMapSize() {
|
||||
return gvkNamePermalinkLookup.size();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -190,7 +170,7 @@ public class PermalinkIndexer {
|
|||
* @return permalinkLocatorMap map size
|
||||
*/
|
||||
protected long permalinkLocatorMapSize() {
|
||||
return permalinkLocatorMap.size();
|
||||
return permalinkLocatorLookup.size();
|
||||
}
|
||||
|
||||
@EventListener(PermalinkIndexAddCommand.class)
|
||||
|
@ -200,12 +180,12 @@ public class PermalinkIndexer {
|
|||
|
||||
@EventListener(PermalinkIndexDeleteCommand.class)
|
||||
public void onPermalinkDelete(PermalinkIndexDeleteCommand deleteCommand) {
|
||||
remove(deleteCommand.getLocator(), deleteCommand.getPermalink());
|
||||
remove(deleteCommand.getLocator());
|
||||
}
|
||||
|
||||
@EventListener(PermalinkIndexUpdateCommand.class)
|
||||
public void onPermalinkUpdate(PermalinkIndexUpdateCommand updateCommand) {
|
||||
remove(updateCommand.getLocator(), updateCommand.getPermalink());
|
||||
remove(updateCommand.getLocator());
|
||||
register(updateCommand.getLocator(), updateCommand.getPermalink());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,9 +40,10 @@ public class ArchivesRouteStrategy implements TemplateRouterStrategy {
|
|||
public RouterFunction<ServerResponse> getRouteFunction(String template, String prefix) {
|
||||
return RouterFunctions
|
||||
.route(GET(prefix)
|
||||
.or(GET(PathUtils.combinePath(prefix, "/page/{page}")))
|
||||
.or(GET(PathUtils.combinePath(prefix, "/{year}/{month}")))
|
||||
.or(GET(PathUtils.combinePath(prefix, "/{year}/{month}/page/{page}")))
|
||||
.or(GET(PathUtils.combinePath(prefix, "/page/{page:\\d+}")))
|
||||
.or(GET(PathUtils.combinePath(prefix, "/{year:\\d{4}}/{month:\\d{2}}")))
|
||||
.or(GET(PathUtils.combinePath(prefix,
|
||||
"/{year:\\d{4}}/{month:\\d{2}}/page/{page:\\d+}")))
|
||||
.and(accept(MediaType.TEXT_HTML)),
|
||||
request -> ServerResponse.ok()
|
||||
.render(DefaultTemplateEnum.ARCHIVES.getValue(),
|
||||
|
|
|
@ -5,7 +5,6 @@ import static org.springframework.web.reactive.function.server.RequestPredicates
|
|||
import static run.halo.app.theme.router.TemplateRouterStrategy.PageUrlUtils.pageNum;
|
||||
import static run.halo.app.theme.router.TemplateRouterStrategy.PageUrlUtils.totalPage;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
@ -53,13 +52,12 @@ public class CategoryRouteStrategy implements TemplateRouterStrategy {
|
|||
public RouterFunction<ServerResponse> getRouteFunction(String template, String prefix) {
|
||||
return RouterFunctions
|
||||
.route(GET(PathUtils.combinePath(prefix, "/{slug}"))
|
||||
.or(GET(PathUtils.combinePath(prefix, "/{slug}/page/{page}")))
|
||||
.or(GET(PathUtils.combinePath(prefix, "/{slug}/page/{page:\\d+}")))
|
||||
.and(accept(MediaType.TEXT_HTML)),
|
||||
request -> {
|
||||
String slug = request.pathVariable("slug");
|
||||
GroupVersionKind gvk = GroupVersionKind.fromExtension(Category.class);
|
||||
List<String> slugs = permalinkIndexer.getSlugs(gvk);
|
||||
if (!slugs.contains(slug)) {
|
||||
if (!permalinkIndexer.containsSlug(gvk, slug)) {
|
||||
return ServerResponse.notFound().build();
|
||||
}
|
||||
String categoryName = permalinkIndexer.getNameBySlug(gvk, slug);
|
||||
|
|
|
@ -39,7 +39,7 @@ public class IndexRouteStrategy implements TemplateRouterStrategy {
|
|||
@Override
|
||||
public RouterFunction<ServerResponse> getRouteFunction(String template, String pattern) {
|
||||
return RouterFunctions
|
||||
.route(GET("/").or(GET("/page/{page}"))
|
||||
.route(GET("/").or(GET("/page/{page:\\d+}"))
|
||||
.and(accept(MediaType.TEXT_HTML)),
|
||||
request -> ServerResponse.ok()
|
||||
.render(DefaultTemplateEnum.INDEX.getValue(),
|
||||
|
|
|
@ -4,7 +4,6 @@ import static org.springframework.web.reactive.function.server.RequestPredicates
|
|||
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
@ -75,32 +74,27 @@ public class PostRouteStrategy implements TemplateRouterStrategy {
|
|||
}
|
||||
return ServerResponse.ok()
|
||||
.render(DefaultTemplateEnum.POST.getValue(),
|
||||
Map.of(PostRequestParamPredicate.NAME_PARAM, name)
|
||||
Map.of(PostRequestParamPredicate.NAME_PARAM, name,
|
||||
"post", postByName(name))
|
||||
);
|
||||
});
|
||||
}
|
||||
return RouterFunctions
|
||||
.route(GET(pattern).and(accept(MediaType.TEXT_HTML)),
|
||||
request -> {
|
||||
List<String> permalinks =
|
||||
permalinkIndexer.getPermalinks(
|
||||
PostRequestParamPredicate.GVK);
|
||||
boolean contains = permalinks.contains(request.path());
|
||||
if (!contains) {
|
||||
ExtensionLocator locator = permalinkIndexer.lookup(request.path());
|
||||
if (locator == null) {
|
||||
return ServerResponse.notFound().build();
|
||||
}
|
||||
ExtensionLocator extensionLocator =
|
||||
permalinkIndexer.lookup(request.path());
|
||||
PathPattern parse = PathPatternParser.defaultInstance.parse(pattern);
|
||||
PathPattern.PathMatchInfo pathMatchInfo =
|
||||
parse.matchAndExtract(PathContainer.parsePath(request.path()));
|
||||
Map<String, Object> model = new HashMap<>();
|
||||
model.put(PostRequestParamPredicate.NAME_PARAM,
|
||||
extensionLocator.name());
|
||||
model.put(PostRequestParamPredicate.NAME_PARAM, locator.name());
|
||||
if (pathMatchInfo != null) {
|
||||
model.putAll(pathMatchInfo.getUriVariables());
|
||||
}
|
||||
model.put("post", postByName(extensionLocator.name()));
|
||||
model.put("post", postByName(locator.name()));
|
||||
return ServerResponse.ok()
|
||||
.render(DefaultTemplateEnum.POST.getValue(), model);
|
||||
});
|
||||
|
@ -139,17 +133,13 @@ public class PostRouteStrategy implements TemplateRouterStrategy {
|
|||
}
|
||||
|
||||
if (NAME_PARAM.equals(placeholderName)) {
|
||||
return RequestPredicates.queryParam(paramName, name -> {
|
||||
List<String> names = permalinkIndexer.getNames(GVK);
|
||||
return names.contains(name);
|
||||
});
|
||||
return RequestPredicates.queryParam(paramName,
|
||||
name -> permalinkIndexer.containsName(GVK, name));
|
||||
}
|
||||
|
||||
if (SLUG_PARAM.equals(placeholderName)) {
|
||||
return RequestPredicates.queryParam(paramName, slug -> {
|
||||
List<String> slugs = permalinkIndexer.getSlugs(GVK);
|
||||
return slugs.contains(slug);
|
||||
});
|
||||
return RequestPredicates.queryParam(paramName,
|
||||
slug -> permalinkIndexer.containsSlug(GVK, slug));
|
||||
}
|
||||
throw new IllegalArgumentException(
|
||||
String.format("Unknown param value placeholder [%s] in pattern [%s]",
|
||||
|
|
|
@ -4,7 +4,6 @@ import static org.springframework.web.reactive.function.server.RequestPredicates
|
|||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
@ -48,8 +47,7 @@ public class SinglePageRouteStrategy implements TemplateRouterStrategy {
|
|||
|
||||
RequestPredicate requestPredicate = request -> false;
|
||||
|
||||
List<String> permalinks =
|
||||
Objects.requireNonNullElse(permalinkIndexer.getPermalinks(gvk), List.of());
|
||||
List<String> permalinks = permalinkIndexer.getPermalinks(gvk);
|
||||
for (String permalink : permalinks) {
|
||||
requestPredicate = requestPredicate.or(RequestPredicates.GET(permalink));
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ import static org.springframework.web.reactive.function.server.RequestPredicates
|
|||
import static run.halo.app.theme.router.TemplateRouterStrategy.PageUrlUtils.pageNum;
|
||||
import static run.halo.app.theme.router.TemplateRouterStrategy.PageUrlUtils.totalPage;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
@ -53,15 +52,15 @@ public class TagRouteStrategy implements TemplateRouterStrategy {
|
|||
public RouterFunction<ServerResponse> getRouteFunction(String template, String prefix) {
|
||||
return RouterFunctions
|
||||
.route(GET(PathUtils.combinePath(prefix, "/{slug}"))
|
||||
.or(GET(PathUtils.combinePath(prefix, "/{slug}/page/{page}")))
|
||||
.or(GET(PathUtils.combinePath(prefix, "/{slug}/page/{page:\\d+}")))
|
||||
.and(accept(MediaType.TEXT_HTML)),
|
||||
request -> {
|
||||
GroupVersionKind gvk = GroupVersionKind.fromExtension(Tag.class);
|
||||
List<String> slugs = permalinkIndexer.getSlugs(gvk);
|
||||
String slug = request.pathVariable("slug");
|
||||
if (!slugs.contains(slug)) {
|
||||
if (!permalinkIndexer.containsSlug(gvk, slug)) {
|
||||
return ServerResponse.notFound().build();
|
||||
}
|
||||
|
||||
String name = permalinkIndexer.getNameBySlug(gvk, slug);
|
||||
return ServerResponse.ok()
|
||||
.render(DefaultTemplateEnum.TAG.getValue(),
|
||||
|
|
|
@ -32,7 +32,7 @@ spec:
|
|||
label: "默认角色"
|
||||
name: defaultRole
|
||||
validation: required
|
||||
- group: themeRules
|
||||
- group: routeRules
|
||||
label: 主题模板路由设置
|
||||
formSchema:
|
||||
- $formkit: text
|
||||
|
@ -58,9 +58,9 @@ spec:
|
|||
- /archives/{name}
|
||||
- /?p={name}
|
||||
- /?p={slug}
|
||||
- /{year}/{slug}
|
||||
- /{year}/{month}/{slug}
|
||||
- /{year}/{month}/{day}/{slug}
|
||||
- /{year:\d{4}}/{slug}
|
||||
- /{year:\d{4}}/{month:\d{2}}/{slug}
|
||||
- /{year:\d{4}}/{month:\d{2}}/{day:\d{2}}/{slug}
|
||||
name: post
|
||||
validation: required
|
||||
- group: post
|
||||
|
|
|
@ -43,4 +43,17 @@ class PathUtilsTest {
|
|||
s = PathUtils.appendPathSeparatorIfMissing(null);
|
||||
assertThat(s).isEqualTo(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void simplifyPathPattern() {
|
||||
assertThat(PathUtils.simplifyPathPattern("/a/b/c")).isEqualTo("/a/b/c");
|
||||
assertThat(PathUtils.simplifyPathPattern("/a/{b}/c")).isEqualTo("/a/{b}/c");
|
||||
assertThat(PathUtils.simplifyPathPattern("/a/{b}/*")).isEqualTo("/a/{b}/*");
|
||||
assertThat(PathUtils.simplifyPathPattern("/archives/{year:\\d{4}}/{month:\\d{2}}"))
|
||||
.isEqualTo("/archives/{year}/{month}");
|
||||
assertThat(PathUtils.simplifyPathPattern("/archives/{year:\\d{4}}/{slug}"))
|
||||
.isEqualTo("/archives/{year}/{slug}");
|
||||
assertThat(PathUtils.simplifyPathPattern("/archives/{year:\\d{4}}/page/{page:\\d+}"))
|
||||
.isEqualTo("/archives/{year}/page/{page}");
|
||||
}
|
||||
}
|
|
@ -49,7 +49,7 @@ class PermalinkIndexerTest {
|
|||
assertThat(permalinkIndexer.permalinkLocatorMapSize()).isEqualTo(1);
|
||||
assertThat(permalinkIndexer.permalinkLocatorMapSize()).isEqualTo(1);
|
||||
|
||||
permalinkIndexer.remove(locator, "/fake-permalink");
|
||||
permalinkIndexer.remove(locator);
|
||||
assertThat(permalinkIndexer.permalinkLocatorMapSize()).isEqualTo(0);
|
||||
assertThat(permalinkIndexer.permalinkLocatorMapSize()).isEqualTo(0);
|
||||
}
|
||||
|
@ -77,8 +77,8 @@ class PermalinkIndexerTest {
|
|||
ExtensionLocator locator = new ExtensionLocator(gvk, "test-name", "test-slug");
|
||||
permalinkIndexer.register(locator, "/test-permalink");
|
||||
|
||||
List<String> names = permalinkIndexer.getNames(gvk);
|
||||
assertThat(names).isEqualTo(List.of("fake-name", "test-name"));
|
||||
assertThat(permalinkIndexer.containsName(gvk, "test-name")).isTrue();
|
||||
assertThat(permalinkIndexer.containsName(gvk, "nothing")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -86,24 +86,9 @@ class PermalinkIndexerTest {
|
|||
ExtensionLocator locator = new ExtensionLocator(gvk, "test-name", "test-slug");
|
||||
permalinkIndexer.register(locator, "/test-permalink");
|
||||
|
||||
List<String> slugs = permalinkIndexer.getSlugs(gvk);
|
||||
assertThat(slugs).isEqualTo(List.of("fake-slug", "test-slug"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getSlugByName() {
|
||||
ExtensionLocator locator = new ExtensionLocator(gvk, "test-name", "test-slug");
|
||||
permalinkIndexer.register(locator, "/test-permalink");
|
||||
|
||||
String slugByName = permalinkIndexer.getSlugByName(gvk, "test-name");
|
||||
assertThat(slugByName).isEqualTo("test-slug");
|
||||
|
||||
slugByName = permalinkIndexer.getSlugByName(gvk, "fake-name");
|
||||
assertThat(slugByName).isEqualTo("fake-slug");
|
||||
|
||||
assertThatThrownBy(() -> {
|
||||
permalinkIndexer.getSlugByName(gvk, "nothing");
|
||||
}).isInstanceOf(NoSuchElementException.class);
|
||||
assertThat(permalinkIndexer.containsSlug(gvk, "fake-slug")).isTrue();
|
||||
assertThat(permalinkIndexer.containsSlug(gvk, "test-slug")).isTrue();
|
||||
assertThat(permalinkIndexer.containsSlug(gvk, "nothing")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -82,14 +82,20 @@ class ArchivesRouteStrategyTest {
|
|||
.expectStatus().isOk();
|
||||
|
||||
client.get()
|
||||
.uri(prefix + "/year/month")
|
||||
.uri(prefix + "/2022/09")
|
||||
.exchange()
|
||||
.expectStatus().isOk();
|
||||
|
||||
client.get()
|
||||
.uri(prefix + "/year/month/page/1")
|
||||
.uri(prefix + "/2022/08/page/1")
|
||||
.exchange()
|
||||
.expectStatus().isOk();
|
||||
|
||||
client.get()
|
||||
.uri(prefix + "/2022/8/page/1")
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isEqualTo(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -20,6 +20,8 @@ import org.springframework.web.reactive.function.server.RouterFunction;
|
|||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import org.springframework.web.reactive.result.view.ViewResolver;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.core.extension.Category;
|
||||
import run.halo.app.extension.GroupVersionKind;
|
||||
import run.halo.app.extension.ListResult;
|
||||
import run.halo.app.theme.DefaultTemplateEnum;
|
||||
import run.halo.app.theme.finders.PostFinder;
|
||||
|
@ -47,8 +49,11 @@ class CategoryRouteStrategyTest {
|
|||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
when(permalinkIndexer.getSlugs(any()))
|
||||
.thenReturn(List.of("category-slug-1", "category-slug-2"));
|
||||
GroupVersionKind gvk = GroupVersionKind.fromExtension(Category.class);
|
||||
when(permalinkIndexer.containsSlug(eq(gvk), eq("category-slug-1")))
|
||||
.thenReturn(true);
|
||||
when(permalinkIndexer.containsSlug(eq(gvk), eq("category-slug-2")))
|
||||
.thenReturn(true);
|
||||
when(permalinkIndexer.getNameBySlug(any(), eq("category-slug-1")))
|
||||
.thenReturn("category-name-1");
|
||||
when(permalinkIndexer.getNameBySlug(any(), eq("category-slug-2")))
|
||||
|
|
|
@ -5,7 +5,6 @@ import static org.mockito.ArgumentMatchers.eq;
|
|||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
@ -64,9 +63,6 @@ class PostRouteStrategyTest {
|
|||
|
||||
piling();
|
||||
|
||||
when(permalinkIndexer.getPermalinks(any()))
|
||||
.thenReturn(List.of("/posts-test/fake-slug"));
|
||||
|
||||
client.get()
|
||||
.uri("/posts-test/fake-slug")
|
||||
.exchange()
|
||||
|
@ -84,9 +80,6 @@ class PostRouteStrategyTest {
|
|||
|
||||
piling();
|
||||
|
||||
when(permalinkIndexer.getPermalinks(any()))
|
||||
.thenReturn(List.of("/posts-test/fake-name"));
|
||||
|
||||
client.get()
|
||||
.uri("/posts-test/fake-name")
|
||||
.exchange()
|
||||
|
@ -104,9 +97,6 @@ class PostRouteStrategyTest {
|
|||
|
||||
piling();
|
||||
|
||||
when(permalinkIndexer.getPermalinks(any()))
|
||||
.thenReturn(List.of("/2022/08/fake-slug"));
|
||||
|
||||
client.get()
|
||||
.uri("/2022/08/fake-slug")
|
||||
.exchange()
|
||||
|
@ -144,14 +134,11 @@ class PostRouteStrategyTest {
|
|||
}
|
||||
|
||||
private void piling() {
|
||||
lenient().when(permalinkIndexer.getNames(any()))
|
||||
.thenReturn(List.of("fake-name"));
|
||||
|
||||
lenient().when(permalinkIndexer.getSlugs(any()))
|
||||
.thenReturn(List.of("fake-slug"));
|
||||
|
||||
lenient().when(permalinkIndexer.getSlugs(any()))
|
||||
.thenReturn(List.of("fake-slug"));
|
||||
GroupVersionKind postGvk = GroupVersionKind.fromExtension(Post.class);
|
||||
lenient().when(permalinkIndexer.containsName(eq(postGvk), eq("fake-name")))
|
||||
.thenReturn(true);
|
||||
lenient().when(permalinkIndexer.containsSlug(eq(postGvk), eq("fake-slug")))
|
||||
.thenReturn(true);
|
||||
|
||||
lenient().when(permalinkIndexer.getNameBySlug(any(), eq("fake-slug")))
|
||||
.thenReturn("fake-name");
|
||||
|
|
|
@ -20,6 +20,8 @@ import org.springframework.web.reactive.function.server.RouterFunction;
|
|||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import org.springframework.web.reactive.result.view.ViewResolver;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.core.extension.Tag;
|
||||
import run.halo.app.extension.GroupVersionKind;
|
||||
import run.halo.app.extension.ListResult;
|
||||
import run.halo.app.theme.DefaultTemplateEnum;
|
||||
import run.halo.app.theme.finders.PostFinder;
|
||||
|
@ -48,8 +50,9 @@ class TagRouteStrategyTest {
|
|||
void setUp() {
|
||||
lenient().when(postFinder.listByTag(anyInt(), anyInt(), any()))
|
||||
.thenReturn(new ListResult<>(1, 10, 0, List.of()));
|
||||
when(permalinkIndexer.getSlugs(any()))
|
||||
.thenReturn(List.of("fake-slug"));
|
||||
GroupVersionKind gvk = GroupVersionKind.fromExtension(Tag.class);
|
||||
when(permalinkIndexer.containsSlug(eq(gvk), eq("fake-slug")))
|
||||
.thenReturn(true);
|
||||
when(permalinkIndexer.getNameBySlug(any(), eq("fake-slug")))
|
||||
.thenReturn("fake-name");
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue