AEM获取页面子资源构建文章推荐Teaser列表方案咨询
问题原因分析
原有代码无法正常运行、实现不合理的核心原因有4点:
- 仅遍历
jcr:content节点的一级子节点,只要文章组件嵌套在布局容器(响应式网格、分栏组件、体验片段等)下就无法匹配到,这是代码不生效的最常见原因 - 直接使用底层JCR Node API,需要手动处理大量受检异常,代码冗余且容易触发空指针、节点不存在等运行时错误
- 缺少全链路非空校验,任意一篇文章缺少jcr:content节点、目标组件、对应属性时,都会抛出异常中断整个推荐列表生成
- 硬编码资源类型、属性名,后期维护成本高
正确实现方案
遵循Sling/AEM开发最佳实践,改用Sling Resource/Page API替代底层Node API,新增递归组件查找逻辑和全链路非空校验,完整实现代码如下:
import com.day.cq.wcm.api.Page; import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.ValueMap; import org.apache.sling.models.annotations.Model; import org.apache.sling.models.annotations.injectorspecific.ScriptVariable; import org.apache.sling.models.annotations.injectorspecific.Self; import org.apache.sling.models.annotations.injectorspecific.SlingObject; import javax.annotation.PostConstruct; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.stream.Collectors; import java.util.stream.StreamSupport; @Model(adaptables = SlingHttpServletRequest.class) public class ArticleDetailsComponentModel { // 替换为你项目中article_details_component实际的sling:resourceType private static final String ARTICLE_COMPONENT_RT = "myproj/components/content/article-details-component/v1/article-details-component"; private static final String PN_IS_TEASABLE = "isTeasable"; private static final String PN_TEASER_TITLE = "teaserTitle"; private static final String PN_TEASER_IMAGE = "teaserImage"; @Self private SlingHttpServletRequest request; @SlingObject private ResourceResolver resourceResolver; @ScriptVariable private Page currentPage; // 如果newsPage不是当前页直接父级,可替换为固定路径/按模板查找逻辑获取 @ScriptVariable private Page newsPage; private List<ArticleTeaser> teasers = new ArrayList<>(); @PostConstruct protected void init() { List<Page> eligiblePages = filterEligibleArticlePages(); buildTeaserFromPages(eligiblePages); } /** * 过滤符合推荐条件的文章页:排除当前页、开启isTeasable开关 */ private List<Page> filterEligibleArticlePages() { Iterable<Page> childPages = () -> newsPage.listChildren(); return StreamSupport.stream(childPages.spliterator(), false) .filter(page -> !page.getPath().equals(currentPage.getPath())) .filter(page -> { ValueMap pageProps = page.getProperties(); return Boolean.TRUE.equals(pageProps.get(PN_IS_TEASABLE, Boolean.class)); }) .collect(Collectors.toList()); } /** * 遍历符合条件的页面,查找文章组件构建Teaser对象 */ private void buildTeaserFromPages(List<Page> articlePages) { for (Page articlePage : articlePages) { Resource jcrContent = articlePage.getContentResource(); if (jcrContent == null) { continue; } // 递归查找页面下所有层级的目标文章组件 Resource articleComponent = findComponentRecursively(jcrContent, ARTICLE_COMPONENT_RT); if (articleComponent == null) { continue; } ValueMap componentProps = articleComponent.getValueMap(); String title = componentProps.get(PN_TEASER_TITLE, String.class); String imagePath = componentProps.get(PN_TEASER_IMAGE, String.class); // 缺少必要字段的无效数据直接跳过 if (title == null || imagePath == null) { continue; } teasers.add(ArticleTeaser.builder() .teaserTitle(title) .teaserImage(imagePath) .teaserLink(articlePage.getPath()) .build()); } } /** * 递归遍历资源树,匹配指定resourceType的第一个组件 */ private Resource findComponentRecursively(Resource rootResource, String targetResourceType) { if (rootResource == null) { return null; } if (targetResourceType.equals(rootResource.getResourceType())) { return rootResource; } Iterator<Resource> childIterator = rootResource.listChildren(); while (childIterator.hasNext()) { Resource found = findComponentRecursively(childIterator.next(), targetResourceType); if (found != null) { return found; } } return null; } public List<ArticleTeaser> getTeasers() { return teasers; } }
额外优化建议
- 如果newsPage下文章数量超过50篇,建议给推荐列表加数量限制,比如按
cq:lastModified倒序取最新的6-10篇,避免遍历过多节点影响页面性能 - 如果文章组件的图片是通过AEM核心Image组件存储的,不要直接取路径字符串,建议直接适配Image模型获取不同尺寸的渲染URL,自动处理裁剪、WebP转换等逻辑
- 如果后续需要扩展推荐规则,可以把
isTeasable的手动勾选逻辑替换为标签匹配、同分类关联等规则,减少作者手动配置成本
内容的提问来源于stack exchange,提问作者k.h.t
相关产品推荐
相关产品推荐

