You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 01:36:19