7、代码

发布于:2026-07-08 Adobe AEM 0条评论

——————————目录大纲——————————

————————————————————————

一、说明

本文将尝试举例一些前后端代码进行功能说明,细节的说明主要看代码中的备注。


二、前端

1、组件文件结构

组件所在目录:lenovo-platform/ui.apps/src/main/content/jcr_root/apps/lenovo-platform/components/content

2、前端库文件(clientlibs和js、css)

2.1 clientlibs/.content.xml

primaryType=sling:Folder相当于指定当前层级为目录结构

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:sling="http://sling.apache.org/jcr/sling/1.0"
    jcr:primaryType="sling:Folder"/>

sling:Folder是AEM中的一种JCR节点类型,用于在内容仓库中组织和管理内容。它继承了JCR标准的nt:folder节点类型的特性,可以包含其他节点,帮助构建内容的层次结构。

2.2 clientlibs/site/.content.xml

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0"
    jcr:primaryType="cq:ClientLibraryFolder"
    allowProxy="{Boolean}true"
    categories="[lenovo.platform.m_test_ideacomponent]"/>

jcr:primaryType="cq:ClientLibraryFolder"表明该节点及其子节点包含客户端库的资源,如CSS文件、JavaScript文件、图像等,cq:ClientLibraryFolder是一个特定的节点类型,用于定义Client Library文件夹;

allowProxy="{Boolean}true"当设置为true时,使Client Library可以通过/etc.clientlibs路径访问(例如,设置allowProxy="true"后,位于/apps/my-project/clientlibs/my-library的资源可以通过/etc.clientlibs/my-project/clientlibs/my-library访问。),这种设置有助于提升缓存性能和安全性;

categories="[lenovo.platform.m_test_ideacomponent]表明当前组件隶属的类别(相当于组件定义的名称),值是一个字符串数组,定义了Client Library所属的一个或多个类别,这些类别用于在页面和组件中引用和包含相应的Client Library。

这三个属性用于定义和配置AEM的Client Library文件夹,允许通过代理路径访问资源,并通过类别在页面和组件中引用和加载客户端库资源。

2.3 clientlibs/site/js/index.js

(() => {
    // 页面加载时调用
    document.addEventListener('DOMContentLoaded', function () {

    });

    trans = function() {
        var query = $('#trans-content').val();
        var component = $('.cmp-translate');
        var appKey = component.attr('appId');
        var key = component.attr('appKey');
        var salt = (new Date).getTime();
        var curtime = Math.round(new Date().getTime()/1000);
        var to = 'zh-CHS';
        var from = 'en';
        var str1 = appKey + truncate(query) + salt + curtime + key;
        // 用户词表ID
        var vocabId =  '';
        var sign = CryptoJS.SHA256(str1).toString(CryptoJS.enc.Hex);
        $.ajax({
            url: 'http://openapi.youdao.com/api',
            type: 'post',
            dataType: 'jsonp',
            data: {
                q: query,
                appKey: appKey,
                salt: salt,
                from: from,
                to: to,
                sign: sign,
                signType: "v3",
                curtime: curtime,
                vocabId: vocabId,
            },
            success: function (data) {
                var translation = data.translation[0]
                $('#result').html('翻译结果:' + translation);
            }
        });
    }

    const truncate = (q)=> {
        var len = q.length;
        if(len<=20) return q;
        return q.substring(0, 10) + len + q.substring(len-10, len);
    }

    transByServlet = function(appId, appKey) {
        var query = $('#trans-content').val();
        $.ajax({
            url: '/exp-services/lenovo/youdao?content=' + query + '&appId=' + appId + '&appKey=' + appKey,
            type: 'get',
            success: function (data) {
                var translation = data.translation
                $('#result').html('翻译结果:' + translation);
            }
        });
    }

})();

2.4 clientlibs/site/js.txt

js.txt文件用于指定需要包含在客户端库中的JavaScript文件列表,它帮助AEM在打包和加载Client Libraries时正确地包含和排序这些文件。

当AEM页面或组件引用一个Client Library时,AEM会根据js.txt中的定义加载相应的JavaScript文件。

#base=js
index.js

#base=js是一个指令,用于指定一个相对路径,这行指令告诉AEM,接下来的文件路径是相对于js文件夹的,该路径是包含在js.txt文件中的所有JavaScript文件的路径。

index.js:在js.txt中列出的文件名,路径被解释为/apps/my-project/clientlibs/my-library/js/index.js和/apps/my-project/clientlibs/my-library/js/script2.js

这样配置简化了文件路径管理,使得在js.txt中列出文件时可以使用相对路径,而不需要重复写完整路径。

通过这种配置方法,AEM可以高效地管理和加载客户端库中的JavaScript资源,提高前端资源管理的灵活性和可维护性。

2.5 clientlibs/site/css/index.css

.cmp-translate {
    margin-top: 20px;
}

2.6 clientlibs/site/css.txt

含义同理js.txt

#base=css
index.css

3、HTL(组件HTML)

3.1 component/.content.xml

组件目录下的content.xml配置文件,为组件当前的配置:

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:sling="http://sling.apache.org/jcr/sling/1.0"
    cq:icon="textLeft"
    jcr:description="有道翻译组件"
    jcr:primaryType="cq:Component"
    jcr:title="m_test_ideacomponent"
    sling:resourceSuperType="lenovo-platform/components/content/base-component/v1/base-component"
    componentGroup="Lenovo MTEST"/>

cq:icon 指定该组件在AEM触屏UI(Touch UI)中的图标。textLeft是图标的名称,表示在组件浏览器中显示的图标。

jcr:description 提供该组件的描述。这通常用在AEM UI中,当用户悬停在组件上时,显示组件的简短说明。

jcr:title 指定该组件的标题。在AEM UI中,组件浏览器和组件选择对话框中显示的名称
jcr:primaryType 定义此节点的主要类型。cq:Component表示这是一个AEM组件

sling:resourceSuperType 定义该组件的资源超类型。这表示该组件继承自lenovo-platform/components/content/base-component/v1/base-component。通过继承,你可以复用和扩展已有组件的功能和资源。
componentGroup 定义该组件所属的组件组,用于页面或者模板去根据组进行筛选。在AEM组件浏览器中,组件组用于组织和分类组件,以便于查找和使用。Lenovo MTEST是组件组的名称。

3.2 component/component.html

<!--data-sly-unwrap="${!wcmmode.edit}"在AEM中用于控制HTML元素的条件移除。具体来说,它会在非编辑模式下移除包裹的元素,只保留其子元素。
这种用法帮助优化最终的HTML输出,同时确保在编辑模式下编辑器的正常操作-->
<div class="cq-placeholder cmp-title" data-emptytext="${component.title}:Click to configure"
     data-sly-unwrap="${!wcmmode.edit}"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/crypto-js/4.0.0/crypto-js.js"></script>

<!--data-sly-use.clientLib为固定语句,属性引用了clientLib.js文件,并将其返回的对象绑定到clientLib变量-->
<sly data-sly-use.clientLib="${'/libs/granite/sightly/templates/clientlib.html'}"/>
<!--data-sly-cal中可以使用clientLib.all、clientLib.js、clientLib.css,categories需要和在clientlibs目录里面的categories一一对应-->
<sly data-sly-call="${clientLib.all @ categories='lenovo.platform.youdao-component'}"/>

<!--需要用到的model实体指向后端Java的包路径和model名称-->
<sly data-sly-use.mtm="com.lenovo.platform.core.models.YouDaoModel">
    <!--<div class="cmp-translate" appId="${properties.appId}" appKey="${properties.appKey}">-->
    <div class="cmp-translate" appId="${mtm.appId}" appKey="${mtm.appKey}">
        <input id="trans-content" type="text" placeholder="请输入需要翻译的英文内容">
        <br/>
        the appId is ${mtm.appId} <br />
        the requestAppId is ${mtm.requestAppId}<br />
        the service data is ${mtm.serviceData}<br />
        the service class name is ${mtm.className}<br />
        <button onclick="trans()">翻译</button>
        <br>
        <button onclick="transByServlet('${mtm.appId}','b')">翻译 by servlet</button><br>
        <br>
        <span id="result"></span>
        <br/>

    </div>
</sly>

sly标签为HTL使用的标签

${}可以访问到JCR中的数据,component、properties为HTL中可直接访问的Java对象。

data-sly-unwrap="${!wcmmode.edit}" 在AEM中用于控制HTML元素的条件移除。具体来说,它会在非编辑模式下移除包裹的元素,只保留其子元素。这种用法帮助优化最终的HTML输出,同时确保在编辑模式下编辑器的正常操作。

data-sly-use:

  • 作用:data-sly-use用于在HTL脚本中引用Java对象、JavaScript文件或HTL模板,以便在模板中使用这些对象或逻辑。
  • 示例:引用JavaScript文件clientLib.js并使用其返回的对象。
  • 语法:data-sly-use.variableName="path/to/resource"
  • data-sly-use.clientLib="${'/libs/granite/sightly/templates/clientlib.html'}":
    该属性引用了clientLib.js文件,并将其返回的对象绑定到clientLib变量。

data-sly-call:

  • 作用:用于调用HTL模板定义的模板或模板方法。它允许你在一个HTL文件中调用另一个HTL文件中的模板或方法,从而实现代码复用和逻辑分离。
  • 示例:调用template.html中定义的模板sampleTemplate并传递参数。
    • 语法:data-sly-call="${templateName @ param1='value1', param2='value2'}"

三、后端

1、Model(SlingModel)

1.1 Model 说明及使用

后端实体Model,通过前端的代码引用:

<sly data-sly-use.mtm="com.lenovo.platform.core.models.YouDaoModel">

Model代码如下(具体说明大致参考注释):

 Expand source
package com.lenovo.platform.core.models;

import com.adobe.cq.export.json.ExporterConstants;
import com.day.cq.commons.jcr.JcrConstants;
import com.day.cq.wcm.api.Page;
import com.day.cq.wcm.api.PageManager;
import com.lenovo.platform.core.services.FlashHeaderService;
import com.lenovo.platform.core.services.YouDaoService;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.models.annotations.*;

import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.inject.Named;

import org.apache.sling.models.annotations.injectorspecific.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @Model声明这是一个SlingModel,组件的Java实体对象
 *  adaptables按照Resource声明这是一个JCR中的Resource节点,可以直接使用Resource匹配的API
 *  adapters属性表明在HTL中可以通过Translate类来适配这个SlingModel
 *  resourceType声明组件地址
 *  defaultInjectionStrategy默认注入策略为可选
 *  
 * @Exporter声明这个SlingModel可以按json格式导出
 * @PostConstruct 在组件初始化的过程中,可以在init方法内添加操作
 * @ValueMapValue 是AEM的API在做好组件地址适配后,可以从组件对应的Resource中获取内容
 */
@Model(
        adaptables = {SlingHttpServletRequest.class},
        resourceType = "lenovo-platform/components/content/m_test_ideacomponent",
        defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)
@Exporter(name = ExporterConstants.SLING_MODEL_EXPORTER_NAME, extensions = ExporterConstants.SLING_MODEL_EXTENSION,
        options = {
                @ExporterOption(name = "MapperFeature.SORT_PROPERTIES_ALPHABETICALLY", value = "true"),
                @ExporterOption(name = "SerializationFeature.WRITE_DATES_AS_TIMESTAMPS", value = "false") })
public class YouDaoModel {

    private static final Logger log = LoggerFactory.getLogger(YouDaoModel.class);

    @Inject
    @RequestAttribute(name = "appId")
    private String requestAppId;

    @Inject
    @Named("appId")
    @Via("resource")
    private String appId;

    @Inject
    @Named("appKey")
    @Via("resource")
    private String appKey;

    @Inject
    private Page currentPage;

    @ScriptVariable
    private ValueMap properties;

    @Inject
    private ResourceResolver resourceResolver;

    @Self
    private SlingHttpServletRequest request;

    @Self
    private SlingHttpServletResponse response;

    @OSGiService(filter = "(component.name=YouDaoServiceImpl)")
    YouDaoService youDaoService;

    @SlingObject
    private Resource resource;

    @PostConstruct
    public void init() {
        // AEM API:AEM API 提供了特定于产品化用例的抽象概念和功能
        // resourceResolver是资源转换类,可以通过此类获取对应的页面,也可以转换为其他API
        PageManager pageManager = resourceResolver.adaptTo(PageManager.class);
        // pageManager可以通过页面路径获取到对应的Page对象
        Page page = pageManager.getPage("/content/lenovo/www/global/en/t3");
        // 也可以直接使用currentPage
        log.info("page title = {}", page.getTitle());
        // 所以从pageManager中通过路径获取到Page对象和currentPage对象,表示的是JCR中同一个页面节点
        log.info("current page title = {}", currentPage.getTitle());

        // Sling API
        // 通过SlingHttpServletRequest也可以获取到当前页面的地址信息
        String path = request.getHeader("Referer");
        log.info("page request path : {}", path);

        // JCR API
        // 获取到地址信息后,就可以拿到这个页面下所有的节点和数据内容,由于page和currentPage表示同一个页面节点,所以拿到的页面内容是一致的
        ValueMap pageVM = page.getProperties();
        ValueMap currentPageVM = currentPage.getProperties();
        log.info("title = {}", pageVM.get(JcrConstants.JCR_TITLE, String.class));
        log.info("title = {}", currentPageVM.get(JcrConstants.JCR_TITLE, String.class));

        // 也可以通过Sling API来创建页面和资源,通过模板来创建页面
//        String templatePath = "/conf/wknd/settings/wcm/templates/steven";
//        pageManager.create("/content/wknd/us/en", "steven-new", templatePath, "steven new page", true);
//        resourceResolver.commit();
        // 获取组件属性及内容
        ValueMap valueMap = resource.getValueMap();
        log.info("appId from resource = {}", valueMap.get("appId", String.class));
        log.info("appId from properties = {}", properties.get("appId", String.class));

        // 获取request请求中的参数
        String requestAppId = String.valueOf(request.getRequestParameter("requestAppId"));
        log.info("requestAppId = {}", requestAppId);

        // 从OSGI Service中获取参数
        String appIdFromService = youDaoService.getAppId(resource);
        String appKeyFromService = youDaoService.getAppKey(resource);
        log.info("appId from service = {}", appIdFromService);
        log.info("appKey from service = {}", appKeyFromService);
    }

    public String className() {
        return youDaoService.getName();
    }

    public String getAppId() {
        return appId;
    }

    public void setAppId(String appId) {
        this.appId = appId;
    }

    public String getAppKey() {
        return appKey;
    }

    public void setAppKey(String appKey) {
        this.appKey = appKey;
    }

    public String getRequestAppId() {
        return requestAppId;
    }

    public void setRequestAppId(String requestAppId) {
        this.requestAppId = requestAppId;
    }
}

    

@Model(adaptables = {SlingHttpServletRequest.class})

  • 表明该Sling Model适配自HTTP请求,用于处理HTTP请求数据,如URL:http://localhost:4502/content/myapp/homepage.html?appId=12345。
  • 可以使用如下注解进行字段获取处理:
  • @RequestAttribute(name = "appId")
  • private String requestAppId;

@Model(adaptables = {Resource.class})

  • 表明该Sling Model适配自资源(Resource)
  • 资源适配用于访问JCR节点中的数据

@ValueMapValue

  • 用于注入资源属性值,通常与JCR属性绑定。

@RequestAttribute

  • 用于注入HTTP请求参数。

@Self

  • 直接注入当前上下文对象,如请求、响应或资源。

@Inject

  • 作用:@Inject注解用于指示Sling Model的Sling Model Injector从特定来源注入值。在这种情况下,值将从资源属性中注入。
  • 示例:在本例中,appId属性将被注入一个值,该值将从指定的来源(资源或请求)获取。

@Named("appId")

  • 作用:@Named注解用于指定要注入的属性的名称。它告诉Sling Model Injector从资源的哪个属性中获取值。
  • 示例:@Named("appId")指定要注入的属性名称是appId,即资源节点中的appId属性。

@Via("resource")

  • 作用:@Via注解用于指定从哪个来源注入值。在本例中,它指定从资源(resource)中获取值。
  • 示例:@Via("resource")告诉Sling Model Injector从绑定到当前模型的资源节点中获取appId属性的值。

1.2 接口访问数据结构

对应在CRX上查看配置的组件数据结构,路径/content/lenovo/www/global/en/t3/jcr:content/root/responsivegrid/m_test_ideacomponent:

对应请求接口地址:http://localhost:4502/content/lenovo/www/global/en/t3/jcr:content/root/responsivegrid/m_test_ideacomponent.json

返回结果如下:

 Expand source
{
    "jcr:primaryType": "nt:unstructured",
    "jcr:createdBy": "admin",
    "appKey": "A0FJKt9BU2nlmim4KLO83jk0xF48oPtC",
    "jcr:lastModifiedBy": "admin",
    "jcr:created": "Mon Jun 17 2024 16:20:10 GMT+0800",
    "appId": "2cf1e87331011491",
    "jcr:lastModified": "Mon Jun 17 2024 18:47:21 GMT+0800",
    "sling:resourceType": "lenovo-platform/components/content/m_test_ideacomponent"
}

2、OSGI Service

2.1 接口类

路径:lenovo-platform/core/src/main/java/com/lenovo/platform/core/services/YouDaoService.java

package com.lenovo.platform.core.services;

import com.day.cq.wcm.api.Page;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;

/**
 * 接口类
 */
public interface YouDaoService {

    public String sendRequest(SlingHttpServletRequest request, Resource currentPageResource, Page currentPage);

    String getAppId(Resource resource);
    String getAppKey(Resource resource);
}

2.2 实现类

路径:lenovo-platform/core/src/main/java/com/lenovo/platform/core/services/impl/YouDaoServiceImpl.java

 Expand source
package com.lenovo.platform.core.services.impl;

import com.day.cq.wcm.api.Page;
import com.lenovo.platform.api.client.RestClient;
import com.lenovo.platform.api.request.BaseRequest;
import com.lenovo.platform.api.request.impl.BaseRequestImpl;
import com.lenovo.platform.api.request.util.HttpMethodType;
import com.lenovo.platform.api.response.BaseResponse;
import com.lenovo.platform.core.constants.LenovoPlatformConstants;
import com.lenovo.platform.core.services.YouDaoService;
import com.lenovo.platform.core.services.configs.YouDaoConfiguration;
import com.lenovo.platform.core.utils.CommonHelper;
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.*;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.*;
import org.osgi.service.metatype.annotations.Designate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 接口实现类
 * 
 */
@Component(service = YouDaoService.class, immediate = true, name = "YouDaoServiceImpl")
@Designate(ocd = YouDaoConfiguration.class)
public class YouDaoServiceImpl implements YouDaoService {

    // Constant LOG
    private static final Logger log = LoggerFactory.getLogger(YouDaoServiceImpl.class);

    private YouDaoConfiguration config;

    @Reference
    private ResourceResolverFactory resourceResolverFactory;

    @Reference
    private RestClient restClient;

    private String headerDomainURL;

    private String headerEndpointURL;

    private String responseData = StringUtils.EMPTY;

    private String countrycode = "us";

    private String languagecode = "en";

    private String endPointURL;

    private String path;

    private String siteRoot;

    private String lenovoTemplate = "/conf/lenovo-www/settings/wcm/templates";

    private String pattern = "/maintenance/500page\\.html"; // Escape special characters

    public String sendRequest(SlingHttpServletRequest request, Resource currentPageResource, Page currentPage) {
        Map<String, String> parameterMap = new HashMap<>();
        log.info("Inside YouDaoServiceImpl");
        try (ResourceResolver resourceResolver = CommonHelper.getServiceResolver(resourceResolverFactory)) {
            headerDomainURL = config.headerDomainURL();
            headerEndpointURL = config.headerEndpointURL();
            path = currentPage.getPath();

            if (!path.isEmpty() && !headerDomainURL.isEmpty() && !headerEndpointURL.isEmpty()) {
                if (path.contains(LenovoPlatformConstants.WWW_XF_PATH)) {                   
                    siteRoot = getXFSiteRoot(path);

                } else if (path.contains(lenovoTemplate) || path.contains(LenovoPlatformConstants.WWW_GLOBAL_SLASH)) {
                    siteRoot = countrycode + LenovoPlatformConstants.SLASH + languagecode;

                } else {
                    if (path.contains(LenovoPlatformConstants.WWW_PAGE_PATH)
                            && !currentPage.getPath().contains(LenovoPlatformConstants.WWW_GLOBAL_SLASH)) {
                        getWWWSiteRoot(path);

                    } else {
                        siteRoot = countrycode + LenovoPlatformConstants.SLASH + languagecode;

                    }
                }

                endPointURL = headerDomainURL + siteRoot + headerEndpointURL;

                log.info("endPointURL -  {}", endPointURL);
                final BaseRequest baseRequest = new BaseRequestImpl(endPointURL, HttpMethodType.GET, StringUtils.EMPTY);
                parameterMap.put("USER-AGENT", request.getHeader("USER-AGENT"));
                BaseResponse baseResponse = restClient.sendRequest(baseRequest, parameterMap);

                if (baseResponse != null) {
                    responseData = baseResponse.getResponseData();
                    log.info("baseResponse not null");
                    if (responseData == null || !StringUtils.isNotBlank(responseData)) {
                        responseData = "";
                        log.info("No responseData");
                    }
                    Pattern regex = Pattern.compile(pattern);
                    Matcher matcher = regex.matcher(responseData);
                    if (matcher.find()) {
                        responseData = "";
                        log.info("No responseData");
                    }
                    log.info("responseData -  {}", responseData);
                }
            }
        } catch (LoginException exception) {
            log.error("LoginException in YouDao header request {}", exception.getMessage(), exception);
        }
        log.info("responseData -  {}", responseData);
        log.info("Exited YouDaoServiceImpl sendHeaderRequest");

        return responseData;
    }

    @Override
    public String getName() {
        return this.getClass().getName();
    }

    /**
     * @param pagePath
     */
    public void getWWWSiteRoot(String pagePath) {
        String countryLanguageCode = null;
        int firstSlashAfterPattern = pagePath.indexOf('/', LenovoPlatformConstants.WWW_PAGE_PATH.length());
        if (firstSlashAfterPattern != -1 && (pagePath.indexOf('/', firstSlashAfterPattern + 1) != -1)) {
            countryLanguageCode = pagePath.substring(LenovoPlatformConstants.WWW_PAGE_PATH.length(),
                    pagePath.indexOf('/', firstSlashAfterPattern + 1));
        }
        if (null != countryLanguageCode && !countryLanguageCode.isEmpty()) {
            siteRoot = countryLanguageCode;
        }
    }

    /**
     * Create site root for the pages using experience fragments for header
     * If the experience fragmnet falls back to global/en, set us/en as siteroot
     * 
     * @param pagePath
     * @return siteRoot
     */
    public String getXFSiteRoot(String pagePath) {
        String[] pathArray = StringUtils.split(pagePath, LenovoPlatformConstants.SLASH);
        String xfCountryCode = pathArray[4];
        String xfLanguageCode = pathArray[5];

        if(StringUtils.contains(pagePath, "global")) {
            return countrycode + LenovoPlatformConstants.SLASH + languagecode;
        } else {
            return xfCountryCode + LenovoPlatformConstants.SLASH + xfLanguageCode;
        }
    }

    /**
     * @param config
     */
    @Activate
    public void activate(YouDaoConfiguration config) {
        this.config = config;
        log.info("============== YouDaoService activate ==============");
    }

    @Deactivate
    public void deactivate(ComponentContext componentContext) {
        log.info("============== YouDaoService deactivate ==============");
    }

    @Modified
    public void modified(ComponentContext componentContext) {
        log.info("============== YouDaoService modified ==============");
    }

    @Override
    public String getAppId(Resource resource) {
        ValueMap valueMap = resource.getValueMap();
        // 查询appId属性,如果为null,则返回""
        return valueMap.get("appId", "");
    }

    @Override
    public String getAppKey(Resource resource) {
        ValueMap valueMap = resource.getValueMap();
        // 查询appKey属性,如果为null,则返回""
        return valueMap.get("appKey", "");
    }
}
    

2.3 多实现类

路径:lenovo-platform/core/src/main/java/com/lenovo/platform/core/services/impl/YouDaoAIServiceImpl.java

 Expand source
package com.lenovo.platform.core.services.impl;

import com.day.cq.wcm.api.Page;
import com.lenovo.platform.api.client.RestClient;
import com.lenovo.platform.api.request.BaseRequest;
import com.lenovo.platform.api.request.impl.BaseRequestImpl;
import com.lenovo.platform.api.request.util.HttpMethodType;
import com.lenovo.platform.api.response.BaseResponse;
import com.lenovo.platform.core.constants.LenovoPlatformConstants;
import com.lenovo.platform.core.services.YouDaoService;
import com.lenovo.platform.core.services.configs.YouDaoConfiguration;
import com.lenovo.platform.core.utils.CommonHelper;
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.*;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.*;
import org.osgi.service.metatype.annotations.Designate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 接口实现类
 * 
 */
@Component(service = YouDaoService.class, immediate = true)
@Designate(ocd = YouDaoConfiguration.class)
public class YouDaoAIServiceImpl implements YouDaoService {

    // Constant LOG
    private static final Logger log = LoggerFactory.getLogger(YouDaoAIServiceImpl.class);

    private YouDaoConfiguration config;

    @Reference
    private ResourceResolverFactory resourceResolverFactory;

    @Reference
    private RestClient restClient;

    private String responseData = StringUtils.EMPTY;

    @Override
    public String getName() {
        return this.getClass().getName();
    }

    /**
     * @param config
     */
    @Activate
    public void activate(YouDaoConfiguration config) {
        this.config = config;
        log.info("============== YouDaoAIService activate ==============");
    }

    @Deactivate
    public void deactivate(ComponentContext componentContext) {
        log.info("============== YouDaoAIService deactivate ==============");
    }

    @Modified
    public void modified(ComponentContext componentContext) {
        log.info("============== YouDaoAIService modified ==============");
    }

    @Override
    public String sendRequest(SlingHttpServletRequest request, Resource currentPageResource, Page currentPage) {
        return "Helloworld";
    }

    @Override
    public String getAppId(Resource resource) {
        ValueMap valueMap = resource.getValueMap();
        // 查询appId属性,如果为null,则返回""
        return valueMap.get("appId", responseData);
    }

    @Override
    public String getAppKey(Resource resource) {
        ValueMap valueMap = resource.getValueMap();
        // 查询appKey属性,如果为null,则返回""
        return valueMap.get("appKey", responseData);
    }
}

interface加入:

String getName();

实现类加入:

@Override
public String getName() {
    return this.getClass().getName();
}

slingModel加入方法:

public String className() {
        return youDaoService.getName();
    }

组件代码加入:

the service class name is ${mtm.className}

界面上得到结果:

因为会默认没有配置,则会按照名称加载,因为A在前所以会先选取A类。

修改一下接口实现:

@Component(service = YouDaoService.class, immediate = true, name = "YouDaoServiceImpl")
@Designate(ocd = YouDaoConfiguration.class)
public class YouDaoServiceImpl implements YouDaoService {

增加name="YouDaoServiceImpl"

然后再slingModel指定:

@OSGiService(filter = "(component.name=YouDaoServiceImpl)")
YouDaoService youDaoService;

Build后再次查看结果:

精确找到了需要的service实现。

3、OSGI Configuration

路径:lenovo-platform/core/src/main/java/com/lenovo/platform/core/services/configs/YouDaoConfiguration.java

 Expand source
package com.lenovo.platform.core.services.configs;

import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.AttributeType;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;
import org.osgi.service.metatype.annotations.Option;

/**
 * YouDaoService的Configuration
 * @ObjectClassDefinition 声明此类是一个OSGi配置信息类
 */
@ObjectClassDefinition(name = "YouDao Configuration", description = "Configure YouDao Service")
public @interface YouDaoConfiguration {

    /**
     * @AttributeDefinition 声明字段名称和描述
     */
    @AttributeDefinition(name = "App Id", description = "App Id")
    String appId() default "";

    @AttributeDefinition(name = "App Key", description = "App Key")
    String appKey() default "";

    @AttributeDefinition(name = "Enable or Disable to translate", description = "Enable or Disable to translate")
    boolean isEnable() default true;

    @AttributeDefinition(name = "Translate Type", description = "Translate Type", options = {
            @Option(label = "YouDao", value = "1"),
            @Option(label = "BaiDu", value = "2")
    })
    String type() default "1";

    @AttributeDefinition(name = "Lenovo Domain URL", description = " Lenovo domain URL for Flash Header.", type = AttributeType.STRING)
    public String headerDomainURL() default "https://www.lenovo.com/";

    @AttributeDefinition(name = "Lenovo Endpoint URL", description = " Lenovo Endpoint URL for Flash Header.", type = AttributeType.STRING)
    public String headerEndpointURL() default "/get_nav_api/site_common_frag/masthead/header.html";
}
   

当配置了Configuration,那么可以在后台(http://localhost:4502/system/console/configMgr)查看对应配置:

甚至可以保存数据进入

4、Sling Servlet

路径:lenovo-platform/core/src/main/java/com/lenovo/platform/core/servlets/YouDaoServlet.java

 Expand source
package com.lenovo.platform.core.servlets;

import org.apache.http.HttpEntity;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.servlets.HttpConstants;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.Servlet;
import javax.servlet.ServletException;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.apache.http.Header;
import org.apache.http.NameValuePair;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.osgi.framework.Constants.SERVICE_DESCRIPTION;

@Component(service = {Servlet.class},
        property = {
                SERVICE_DESCRIPTION + "= Servlet to translate",
                "sling.servlet.paths=/exp-services/lenovo/youdao",
                "sling.servlet.methods=" + HttpConstants.METHOD_GET,
                "sling.servlet.extensions=json"})
public class YouDaoServlet extends SlingAllMethodsServlet {

    private static final Logger log = LoggerFactory.getLogger(YouDaoServlet.class);

    private static final String YOUDAO_URL = "https://openapi.youdao.com/api";

    private static final String APP_KEY = "abc";

    private static final String APP_SECRET = "cba";

    @Override
    protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response)
            throws ServletException, IOException {

        getResult(request, response);
    }

    @Override
    protected void doPost(SlingHttpServletRequest request, SlingHttpServletResponse response)
            throws ServletException, IOException {
        // Do nothing here
    }

    private void getResult(SlingHttpServletRequest request, SlingHttpServletResponse response) throws IOException{
        String q = request.getRequestParameter("content").toString();
        String appId = request.getRequestParameter("appId").toString();
        String appKey = request.getRequestParameter("appKey").toString();
        Map<String, String> params = new HashMap<>();
        String salt = String.valueOf(System.currentTimeMillis());
        params.put("from", "en");
        params.put("to", "zh-CHS");
        params.put("signType", "v3");
        String curtime = String.valueOf(System.currentTimeMillis() / 1000);
        params.put("curtime", curtime);
        String signStr = appId + truncate(q) + salt + curtime + APP_SECRET;
        String sign = getDigest(signStr);
        params.put("appKey", appKey);
        params.put("q", q);
        params.put("salt", salt);
        params.put("sign", sign);
        params.put("vocabId", "");
        /* 处理结果 */
        String result = requestForHttp(YOUDAO_URL, params);
        // 设置返回数据类型为json,编码格式为UTF-8
        response.setContentType("application/json;charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.print(result);
        writer.close();
    }

    public static String requestForHttp(String url, Map<String, String> params) throws IOException {

        /* 创建HttpClient */
        CloseableHttpClient httpClient = HttpClients.createDefault();

        /* httpPost */
        HttpPost httpPost = new HttpPost(url);
        List<NameValuePair> paramsList = new ArrayList<>();
        for (Map.Entry<String, String> en : params.entrySet()) {
            String key = en.getKey();
            String value = en.getValue();
            paramsList.add(new BasicNameValuePair(key, value));
        }
        httpPost.setEntity(new UrlEncodedFormEntity(paramsList, "UTF-8"));
        CloseableHttpResponse httpResponse = httpClient.execute(httpPost);
        try {
            Header[] contentType = httpResponse.getHeaders("Content-Type");
            log.info("Content-Type:" + contentType[0].getValue());
            if ("audio/mp3".equals(contentType[0].getValue())) {
                //如果响应是wav
                HttpEntity httpEntity = httpResponse.getEntity();
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                httpResponse.getEntity().writeTo(baos);
                byte[] result = baos.toByteArray();
                EntityUtils.consume(httpEntity);
                //合成成功
                String file = "合成的音频存储路径" + System.currentTimeMillis() + ".mp3";
                byte2File(result, file);
            } else {
                /* 响应不是音频流,直接显示结果 */
                HttpEntity httpEntity = httpResponse.getEntity();
                String json = EntityUtils.toString(httpEntity, "UTF-8");
                EntityUtils.consume(httpEntity);
                log.info(json);
                return json;
            }
        } finally {
            try {
                if (httpResponse != null) {
                    httpResponse.close();
                }
            } catch (IOException e) {
                log.info("## release resouce error ##" + e);
            }
        }
        return "";
    }

    /**
     * 生成加密字段
     */
    public static String getDigest(String string) {
        if (string == null) {
            return null;
        }
        char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
        byte[] btInput = string.getBytes(StandardCharsets.UTF_8);
        try {
            MessageDigest mdInst = MessageDigest.getInstance("SHA-256");
            mdInst.update(btInput);
            byte[] md = mdInst.digest();
            int j = md.length;
            char[] str = new char[j * 2];
            int k = 0;
            for (byte byte0 : md) {
                str[k++] = hexDigits[byte0 >>> 4 & 0xf];
                str[k++] = hexDigits[byte0 & 0xf];
            }
            return new String(str);
        } catch (NoSuchAlgorithmException e) {
            return null;
        }
    }

    /**
     * @param result 音频字节流
     * @param file   存储路径
     */
    private static void byte2File(byte[] result, String file) {
        File audioFile = new File(file);
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(audioFile);
            fos.write(result);

        } catch (Exception e) {
            log.error(e.getMessage(), e);
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static String truncate(String q) {
        if (q == null) {
            return null;
        }
        int len = q.length();
        return len <= 20 ? q : (q.substring(0, 10) + len + q.substring(len - 10, len));
    }
}
    

四、标签补充说明

1、前端

在Adobe Experience Manager (AEM)中,HTL(HTML Template Language)是一种用于构建动态Web内容的模板语言。

HTL提供了一些强大的属性和标签来处理数据和控制流。以下是一些常用的HTL标签和属性及其说明:

data-sly-use

  • 用于导入Sling Models或其他Java对象,使其在HTL模板中可用。
  • 示例:
<sly data-sly-use.model="com.example.core.models.MyModel">
    ${model.someProperty}
</sly>

data-sly-template

  • 定义一个可重用的模板块。
  • 示例:
<sly data-sly-template.example="message">
    <p>${message}</p>
</sly>

data-sly-call

  • 调用一个用data-sly-template定义的模板块。
  • 示例:
<sly data-sly-call="${example @ message='Hello, world!'}"></sly>

data-sly-repeat

  • 类似于data-sly-list,用于遍历一个集合。
  • 示例:
<sly data-sly-repeat.item="${items}">
    <p>${item}</p>
</sly>

data-sly-test

  • 用于条件判断,如果条件为真,则渲染元素。
  • 示例:
<div data-sly-test="${condition}">
    This will be displayed if condition is true.
</div>

data-sly-include

  • 用于包含其他HTL文件或片段。
  • 示例:
<sly data-sly-include="path/to/another/file.html"></sly>

data-sly-resource

  • 用于包含其他AEM资源或组件。
  • 示例:
<sly data-sly-resource="${'path/to/resource' @ resourceType='my/components/resource'}"></sly>

data-sly-unwrap

  • 用于在渲染时移除当前元素,仅渲染其内部内容。
  • 示例:
<div data-sly-unwrap>${content}</div>

data-sly-attribute

  • 用于动态设置HTML元素的属性。

  • 示例:
<button data-sly-attribute="${'onclick' @ 'handleClick()'}">Click Me</button>

data-sly-element

  • 用于动态设置HTML元素的标签。
  • 示例:
<sly data-sly-element="div">
    This is a div element.
</sly>

data-sly-text

  • 用于将变量渲染为纯文本内容,防止HTML注入。
  • 示例:
<span data-sly-text="${someText}"></span>

data-sly-html

  • 用于将变量渲染为HTML内容,允许HTML标签。
  • 示例:
<div data-sly-html="${someHtmlContent}"></div>

data-sly-include

  • 用于包含其他HTL文件内容,相当于服务器端的include操作。
  • 示例:
<sly data-sly-include="${'path/to/include.html'}"></sly>

绑定onclick传参注意事项

AEM的HTL中,为了确保在onclick事件处理器中正确地获取和使用Sling Model中的变量值,需要处理变量上下文和转义问题。

HTL会自动对变量进行转义,以防止XSS攻击,这可能导致在某些情况下变量值无法正确传递到JavaScript中:

<button id = "${mtm.appId @context='text'}" data-sly-test.onclickFunction="transByServlet('${mtm.appId}','${mtm.appKey}')" onclick="${onclickFunction @ context='attribute'}">翻译1 by servlet</button>

<a href="javascript:transByServlet('${mtm.appId}','${mtm.appKey}')" rel="external nofollow" target="_blank">翻译2 by servlet</a>
  • data-sly-use.mtm="com.example.core.models.MyModel":加载Sling Model,并将其实例赋给变量mtm

  • id="${mtm.appId @ context='text'}":使用context='text'确保appId作为纯文本处理,防止HTL对其进行不必要的转义。

  • data-sly-test.onclickFunction="transByServlet('${mtm.appId}','${mtm.appKey}')":定义一个HTL变量onclickFunction,其值为需要在onclick事件中执行的JavaScript函数调用。此时,appIdappKey被正确地拼接到JavaScript字符串中。

  • onclick="${onclickFunction @ context='attribute'}":将前面定义的onclickFunction变量赋给按钮的onclick属性,并使用context='attribute'确保其作为HTML属性值处理。这一步确保变量在onclick属性中被正确引用并执行。

通过这种方式,可以确保在AEM的HTL模板中正确地使用Sling Model中的变量,并在onclick事件中传递这些变量值。使用data-sly-testcontext属性处理变量上下文和转义问题,可以避免由于HTL自动转义导致的变量值无法正确传递的问题。

2、后端

@Model注解

  • 定义了一个Sling Model,adaptables指定了适配器类型,这里是SlingHttpServletRequest
  • 示例:
@Model(adaptables = SlingHttpServletRequest.class, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class ExampleModel {

@ScriptVariable注解

  • 用于注入HTL脚本中的全局变量,如currentPagerequestresourceproperties
  • 简化HTL中的复杂逻辑处理,通过注入全局变量和服务,将业务逻辑放在Java类中
  • 示例:
   @ScriptVariable
    private Page currentPage;

    @ScriptVariable
    private SlingHttpServletRequest request;

    @ScriptVariable
    private Resource resource;

    @ScriptVariable
    private ValueMap properties;

@SlingObject注解

  • 用于注入Sling特定对象,如ResourceResolver
  • 示例:
 @SlingObject
 private ResourceResolver resourceResolver;

@PostConstruct方法

  • 在模型初始化后执行的逻辑,用于设置模型的初始状态。
  • 示例:
   @PostConstruct
    protected void init() {
        if (currentPage != null) {
            this.pageTitle = currentPage.getTitle();
        }
        if (request != null) {
            this.requestPath = request.getRequestURI();
        }
    }

五、参考资料

HTL资料参考:https://experienceleague.adobe.com/zh-hans/docs/experience-manager-htl/content/getting-started

ClientLibs资料:https://experienceleague.adobe.com/zh-hans/docs/experience-manager-65/content/implementing/developing/introduction/clientlibs

上一篇

5、组件

下一篇

8、定时任务

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理