[集成]{class="badge positive"}

使用AEM Sites生成Experience PlatformFPID

[AEM Sitesas a Cloud Service、AEM Sites 6.5]{class="badge informative"}

将通过AEM Publish交付的Adobe Experience Manager (AEM)站点与Adobe Experience Platform (AEP)集成需要AEM生成和维护唯一的第一方设备ID (FPID) Cookie,以便唯一跟踪用户活动。

阅读支持文档,以便了解第一部分设备ID和Experience CloudID如何协同工作的详细信息

以下是使用AEM作为Web主机时FPID的工作方式概述。

使用AEM的 FPID和ECID

使用AEM生成并保留FPID

AEM Publish服务通过尽可能多地在CDN和AEM Dispatcher缓存中缓存请求来优化性能。

绝不会缓存生成每用户独特FPID Cookie并返回FPID值的HTTP请求并直接从AEM Publish提供服务,后者可以实施逻辑来保证唯一性,这一点至关重要。

避免在网页或其他可缓存资源的请求中生成FPID Cookie,因为FPID的唯一性要求组合会导致这些资源无法缓存。

下图描述了AEM Publish服务如何管理FPID。

FPID和AEM流程图

  1. Web浏览器请求由AEM托管的网页。 可以使用来自CDN或AEM Dispatcher缓存的网页缓存副本来提供请求。
  2. 如果网页无法从CDN或AEM Dispatcher缓存提供服务,则该请求将发送到AEM Publish服务,该服务将生成所请求的网页。
  3. 随后,该网页将返回到Web浏览器,并填充无法为请求提供服务的缓存。 使用AEM时,预计CDN和AEM Dispatcher缓存命中率将大于90%。
  4. 该网页包含JavaScript,用于向AEM Publish服务中的自定义FPID servlet发出不可缓存的异步XHR (AJAX)请求。 由于这是一个不可缓存的请求(由于其随机查询参数和缓存控制标头),因此CDN或AEM Dispatcher从不缓存它,并且始终会访问AEM Publish服务以生成响应。
  5. AEM Publish服务中的自定义FPID servlet处理该请求,在没有找到现有FPID Cookie时生成新的FPID,或者延长任何现有FPID Cookie的生命周期。 此servlet还会返回响应正文中的FPID,以供客户端JavaScript使用。 幸运的是,自定义FPID servlet逻辑是轻量级的,防止此请求影响AEM Publish服务性能。
  6. XHR请求的响应将返回给浏览器,并在响应正文中将FPID Cookie和FPID作为JSON,以供Platform Web SDK使用。

代码示例

以下代码和配置可以部署到AEM Publish服务,以创建端点,该端点生成或延长现有FPID Cookie的生命周期,并将FPID作为JSON返回。

必须创建AEM Publish HTTP终结点,以使用Sling servlet生成或扩展FPID Cookie。

  • 此servlet已绑定到/bin/aem/fpid,因为访问它不需要身份验证。 如果需要进行身份验证,请绑定到Sling资源类型。
  • 此servlet接受HTTPGET请求。 响应标有Cache-Control: no-store以防止缓存,但还应使用唯一的缓存无效查询参数请求此端点。

当HTTP请求到达servlet时,此servlet检查请求上是否存在FPID Cookie:

  • 如果存在FPID Cookie,请延长Cookie的生命周期,并收集其值以写入响应。
  • 如果FPID Cookie不存在,请生成新的FPID Cookie并保存该值以写入响应。

然后,Servlet将FPID作为JSON对象写入响应,格式为: { fpid: "<FPID VALUE>" }

在正文中向客户端提供FPID很重要,因为FPID Cookie标记为HttpOnly,这意味着只有服务器才能读取其值,而客户端JavaScript则不能。 为了避免在每次加载页面时不必要地重新获取FPID,系统还设置了FPID_CLIENT Cookie,以指示已生成FPID并将值公开给客户端JavaScript以供使用。

FPID值用于通过Platform Web SDK将调用参数化。

以下是AEM servlet端点(通过HTTP GET /bin/aep/fpid提供)的示例代码,该端点生成或刷新FPID Cookie,并将FPID作为JSON返回。

  • core/src/main/java/com/adobe/aem/guides/wkndexamples/core/aep/impl/FpidServlet.java
package com.adobe.aem.guides.wkndexamples.core.aep.impl;

import com.google.gson.JsonObject;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
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.http.Cookie;
import java.io.IOException;
import java.util.UUID;

import static org.apache.sling.api.servlets.ServletResolverConstants.SLING_SERVLET_PATHS;
import static org.apache.sling.api.servlets.ServletResolverConstants.SLING_SERVLET_METHODS;

@Component(
        service = {Servlet.class},
        property = {
                SLING_SERVLET_PATHS + "=/bin/aep/fpid",
                SLING_SERVLET_METHODS + "=GET"
        }
)
public class FpidServlet extends SlingAllMethodsServlet {
    private static final Logger log = LoggerFactory.getLogger(FpidServlet.class);
    private static final String COOKIE_NAME = "FPID";
    private static final String CLIENT_COOKIE_NAME = "FPID_CLIENT";
    private static final String COOKIE_PATH = "/";
    private static final int COOKIE_MAX_AGE = 60 * 60 * 24 * 30 * 13; // 13 months
    private static final String JSON_KEY = "fpid";

    @Override
    protected final void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) throws IOException {
        // Try to get an existing FPID cookie, this will give us the user's current FPID if it exists
        final Cookie existingCookie = request.getCookie(COOKIE_NAME);

        String cookieValue;

        if (existingCookie == null) {
            //  If no FPID cookie exists, create a new FPID UUID
            cookieValue = UUID.randomUUID().toString();
        } else {
            // If a FPID cookie exists, get its FPID UUID so its life can be extended
            cookieValue = existingCookie.getValue();
        }

        // Add the FPID value to the response, either newly generated or the extended one
        // This can be read by the Server (AEM Publish) due to HttpOnly flag.
        response.addHeader("Set-Cookie",
                COOKIE_NAME + "=" + cookieValue + "; " +
                        "Max-Age=" + COOKIE_MAX_AGE + "; " +
                        "Path=" + COOKIE_PATH + "; " +
                        "HttpOnly; " +
                        "Secure; " +
                        "SameSite=Lax");

        // Also set FPID_CLIENT cookie to avoid further server-side FPID generation
        // This can be read by the client-side JavaScript to check if FPID is already generated
        // or if it needs to be requested from server (AEM Publish)
        response.addHeader("Set-Cookie",
                CLIENT_COOKIE_NAME + "=" + cookieValue + "; " +
                        "Max-Age=" + COOKIE_MAX_AGE + "; " +
                        "Path=" + COOKIE_PATH + "; " +
                        "Secure; " +
                        "SameSite=Lax");

        // Avoid caching the response
        response.addHeader("Cache-Control", "no-store");

        // Return FPID in the response as JSON for client-side access
        final JsonObject json = new JsonObject();
        json.addProperty(JSON_KEY, cookieValue);

        response.setContentType("application/json");
        response.getWriter().write(json.toString());

HTML脚本

必须向页面添加自定义客户端JavaScript才能异步调用servlet,生成或刷新FPID Cookie并在响应中返回FPID。

通常使用下列方法之一将此JavaScript脚本添加到页面中:

对自定义AEM FPID servlet的XHR调用非常快(尽管是异步调用),因此用户可能会访问AEM提供的网页,并在请求完成之前导航离开。
如果发生这种情况,在从AEM加载网页的下一页时,将重新尝试执行相同的过程。

AEM FPID servlet (/bin/aep/fpid)的HTTPGET使用随机查询参数进行参数化,以确保浏览器和AEM Publish服务之间的任何基础结构都不会缓存请求的响应。
同样,添加Cache-Control: no-store请求标头以支持避免缓存。

调用AEM FPID servlet时,将从JSON响应中检索FPID,并由Platform Web SDK用来将其发送到Experience PlatformAPI。

有关在identityMap🔗中使用FPID的的更多信息,请参阅Experience Platform文档

...
<script>
    // Wrap in anonymous function to avoid global scope pollution

    (function() {
        // Utility function to get a cookie value by name
        function getCookie(name) {
            const value = `; ${document.cookie}`;
            const parts = value.split(`; ${name}=`);
            if (parts.length === 2) return parts.pop().split(';').shift();
        }

        // Async function to handle getting the FPID via fetching from AEM, or reading an existing FPID_CLIENT cookie
        async function getFpid() {
            let fpid = getCookie('FPID_CLIENT');

            // If FPID can be retrieved from FPID_CLIENT then skip fetching FPID from server
            if (!fpid) {
                // Fetch FPID from the server if no FPID_CLIENT cookie value is present
                try {
                    const response = await fetch(`/bin/aep/fpid?_=${new Date().getTime() + '' + Math.random()}`, {
                        method: 'GET',
                        headers: {
                            'Cache-Control': 'no-store'
                        }
                    });
                    const data = await response.json();
                    fpid = data.fpid;
                } catch (error) {
                    console.error('Error fetching FPID:', error);
                }
            }

            console.log('My FPID is: ', fpid);
            return fpid;
        }

        // Invoke the async function to fetch or skip FPID
        const fpid = await getFpid();

        // Add the fpid to the identityMap in the Platform Web SDK
        // and/or send to AEP via AEP tags or direct AEP Web SDK calls (alloy.js)
    })();
</script>

Dispatcher允许过滤器

最后,必须通过AEM Dispatcher的filter.any配置允许对自定义FPID servlet的HTTPGET请求。

如果未正确实施此Dispatcher配置,则对/bin/aep/fpid的HTTPGET请求会导致404错误。

  • dispatcher/src/conf.dispatcher.d/filters/filters.any
/1099 { /type "allow" /method "GET" /url "/bin/aep/fpid" }

Experience Platform资源

请查看以下Experience Platform文档,了解第一方设备ID (FPID)以及如何通过Platform Web SDK管理身份数据。

recommendation-more-help
bb44cebf-d964-4e3c-b64e-ce882243fe4d