在 Apex 功能中连接

本主题介绍用于使用公共 Connect 的类和方法 Apex 功能。

也可以直接转到 ConnectApi 命名空间参考内容。

  • 使用操作链接 操作链接
    是源元素上的按钮。单击操作链接可以将用户带到网页、启动文件下载或调用对 Salesforce 或外部服务器的 API 调用。操作链接包括 URL 和 HTTP 方法,并且可以包含请求正文和标头信息,例如用于身份验证的 OAuth 令牌。使用操作链接将 Salesforce 和第三方服务集成到源中,以便用户可以提高工作效率并加速创新。
  • 使用摘要和摘要元素
    Chatter 摘要是摘要元素的容器。抽象类是类的父类,表示源帖子,类表示源中的捆绑包和建议。ConnectApi.FeedElementConnectApi.FeedItemConnectApi.GenericFeedElement
  • 在 Experience Cloud 站点中访问 ConnectApi 数据 许多方法在单个 Experience Cloud 站点
    的上下文中工作。ConnectApi
  • 可供 Experience Cloud 访客用户
    使用的方法 如果您的 Experience Cloud 站点允许在不登录的情况下进行访问,则访客用户可以访问许多 Apex 方法。这些方法返回来宾用户有权访问的信息。
  • DBT 段支持的验证 创建或更新段
    时,ConnectApi.CdpSegmentInput 类需要接受一些 SQL 验证。

使用操作链接

操作链接是源元素上的按钮。单击操作链接可以将用户 到网页,启动文件下载,或调用对 Salesforce 或外部的 API 调用 服务器。操作链接包括 URL 和 HTTP 方法,并且可以包含请求正文和 标头信息,例如用于身份验证的 OAuth 令牌。使用操作链接进行集成 将 Salesforce 和第三方服务添加到源中,以便用户可以提高生产力和 加速创新。

工作流程

此源项包含一个操作链接组,其中包含一个 可见操作链接,加入

Feed 项上的加入操作链接

使用 Feed 元素创建和发布操作链接的工作流:

  1. (可选)创建操作链接 模板。
  2. 调用 ConnectApi.ActionLinks.createActionLinkGroupDefinition(communityId, actionLinkGroup) 来定义一个操作链接组,该操作链接组至少包含 一个操作链接。
  3. 调用 ConnectApi.ChatterFeeds.postFeedElement(communityId, feedElement) 以 发布 Feed 元素并将操作链接与其关联。

使用这些方法处理操作链接。

ConnectApi 方法任务
ActionLinks.createActionLinkGroupDefinition​(communityId, actionLinkGroup)ActionLinks.deleteActionLinkGroupDefinition(communityId, actionLinkGroupId)ActionLinks.getActionLinkGroupDefinition(communityId, actionLinkGroupId)创建操作链接组定义。将操作链接组与 源元素,首先创建操作链接组定义。然后发布提要 元素。
ChatterFeeds.postFeedElement(communityId, feedElement)发布具有关联操作功能的源元素。关联多达 10 个 带有 Feed 元素的操作链接组。
ActionLinks.getActionLink(communityId, actionLinkId)获取有关操作链接的信息,包括上下文的状态 用户。
ActionLinks.getActionLinkGroup(communityId, actionLinkGroupId)获取有关操作链接组的信息,包括上下文的状态 用户。
ActionLinks.getActionLinkDiagnosticInfo(communityId, actionLinkId)获取执行操作链接时返回的诊断信息。诊断 仅向可以访问操作链接的用户提供信息。
ChatterFeeds.getFeedElementsFromFeed()从指定的源类型获取源元素。如果 Feed 元素具有操作 链接,则操作链接数据将返回到 Feed 元素的 关联的操作功能。
  • 操作链接概述、身份验证和安全性
    了解 Apex 操作链接安全性、身份验证、标签和错误。
  • 操作链接用例
    使用操作链接将 Salesforce 和第三方服务与源集成。操作链接可以向 Salesforce 或第三方 API 发出 HTTP 请求。操作链接还可以下载文件或打开网页。本主题包含一个示例用例。

操作链接概述、身份验证和安全性

了解 Apex 操作链接安全性、身份验证、标签和错误。

工作流程

此源项包含一个操作链接组 带有一个可见的操作链接,即加入

Feed 项上的加入操作链接

使用 Feed 元素创建和发布操作链接的工作流:

  1. (可选)创建操作链接 模板。
  2. 调用 ConnectApi.ActionLinks.createActionLinkGroupDefinition(communityId, actionLinkGroup) 来定义一个操作链接组,该操作链接组至少包含 一个操作链接。
  3. 调用 ConnectApi.ChatterFeeds.postFeedElement(communityId, feedElement) 以 发布 Feed 元素并将操作链接与其关联。

操作链接模板

在安装程序中创建操作链接模板,以实例化具有通用的操作链接组 性能。您可以打包模板并将其分发给其他 Salesforce 组织。

在模板中指定绑定变量,并在以下情况下设置变量的值 实例化操作链接组。例如,对 API 版本使用绑定变量 number、用户 ID 或 OAuth 令牌。

您还可以在模板中指定上下文变量。当用户执行操作时 link,Salesforce 为这些变量提供值,例如谁执行了链接和 哪个组织。

若要实例化操作链接组,请调用 ConnectApi.ActionLinks.createActionLinkGroupDefinition(communityId, actionLinkGroup) 方法。指定模板 ID 和任何值 模板中定义的绑定变量。

请参阅设计操作链接模板。

操作链接的类型

定义操作链接时,在属性中指定操作链接类型。actionType

有四种类型的操作链接:

  • Api– 操作链接调用同步 操作 URL 中的 API。Salesforce 将状态设置为或基于您返回的 HTTP 状态代码 服务器。SuccessfulStatusFailedStatus
  • ApiAsync– 操作链接调用 异步 API 位于操作 URL。该操作将保持该状态,直到第三方做出 请求将 异步操作的状态或异步操作为 完成。PendingStatus/connect/action-links/actionLinkIdSuccessfulStatusFailedStatus
  • Download– 操作链接下载文件 从操作 URL。
  • Ui– 操作链接将用户带到 操作 URL 中的网页。

认证

定义操作链接时,请指定 URL () 和 HTTP 标头 () 需要向该 URL 发出请求。actionUrlheaders

如果外部资源需要身份验证,请在 资源需要。

如果 Salesforce 资源需要身份验证,您可以在 HTTP 标头,或者可以在 URL 中包含持有者令牌。

Salesforce 会自动对这些资源进行身份验证。

  • 模板中的相对 URL
  • 从 Apex 实例化操作链接组时开始的相对 URL/services/apexrest

请勿将这些资源用于敏感操作。

安全

HTTPS协议操作链接中的操作 URL 必须以 https:// 开头,或者是 与上一“身份验证”部分中的规则之一匹配的相对 URL。加密API 详细信息以加密方式存储,并为客户端进行模糊处理。的 、 和 数据 未从模板实例化的操作链接使用 组织的加密密钥。操作 URL、HTTP 标头和操作链接的 HTTP 请求正文 模板未加密。实例化操作链接时使用的绑定值 模板中的组使用组织的加密密钥进行加密。actionURLheadersrequestBody操作链接模板只有具有“自定义应用程序”用户权限的用户才能创建、编辑、删除和 安装程序中的包操作链接模板。不要在模板中存储敏感信息。使用绑定变量添加敏感变量 实例化操作链接组时的信息。操作链接组是 实例化后,值以加密格式存储。请参阅定义绑定变量 in 设计 操作链接模板。连接的应用程序通过连接的应用程序创建操作链接时,最好使用 使用使用者密钥连接的应用 这永远不会离开你的控制。连接的应用程序用于 服务器到服务器通信,并且不会编译到可能 反编译。有效期定义操作链接组时,请指定到期日期 ()。在该日期之后,操作链接在 组无法执行并从源中消失。如果操作链接组 定义包括一个 OAuth 令牌,将组的到期日期设置为与 OAuth 令牌的到期日期。expirationDate操作链接模板使用略有不同的机制来排除用户。请参阅设置 设计中的操作链接组过期时间 操作链接模板。排除用户或指定用户使用操作链接的属性 定义输入,以排除单个用户执行操作。excludeUserId使用操作链接的属性 定义输入,用于指定只有该用户才能执行操作的用户的 ID。如果你 不要指定属性,或者如果传递,则任何用户都可以执行该操作。你不能 为操作链接指定 和userIduserIdnullexcludeUserIduserId操作链接模板使用略有不同的机制来排除用户。请参阅设置 谁可以看到设计中的操作链接 操作链接模板。读取、修改或删除操作链接组定义操作链接和 操作链接组:定义和上下文用户的视图。定义 包括潜在的敏感信息,例如身份验证信息。这 上下文用户的视图按可见性选项进行筛选,这些值反映 上下文用户的状态。操作链接组定义可以包含敏感信息(例如 OAuth 令牌)。因此,要读取、修改或删除定义,用户必须具有 创建了定义或具有“查看所有数据”权限。此外,在 Connect REST 中 API,则必须通过创建定义的同一连接应用发出请求。在 Apex,则必须从创建定义的同一命名空间进行调用。

上下文变量

使用上下文变量传递有关 执行操作链接的用户以及调用该链接的上下文 通过调用操作链接发出的 HTTP 请求。您可以在操作链接定义输入请求正文或对象的 、 和 属性中使用上下文变量。你 还可以在操作链接模板的“操作 URL”、“HTTP 请求正文”和“HTTP 标头”字段中使用上下文变量。您可以编辑这些字段,包括添加和 在发布模板后删除上下文变量。actionUrlheadersrequestBodyConnectApi.ActionLinkDefinitionInput

上下文变量包括:

上下文变量描述
{!actionLinkId}用户执行的操作链接的 ID。
{!actionLinkGroupId}包含用户操作链接的操作链接组的 ID 执行。
{!communityId}用户在其中执行操作链接的站点的 ID。的值 您的内部组织是空键。“000000000000000000”
{!communityUrl}用户在其中执行操作链接的网站的 URL。的值 您的内部组织是空字符串。“”
{!orgId}用户在其中执行操作链接的组织 ID。
{!userId}执行操作链接的用户的 ID。

版本控制

为避免由于 API 中的升级或功能更改而导致的问题,我们建议使用 定义操作链接时的版本控制。例如,ConnectApi.ActionLinkDefinitionInput中的属性类似于 https://www.example.com/api/v1/exampleResource。actionUrl

可以使用模板更改 、 或 属性的值,即使在分发模板之后也是如此 在包中。假设您发布了需要新输入的新 API 版本。管理员可以 更改安装程序中操作链接模板中的输入,甚至更改操作链接 与源元素关联,使用新输入。但是,不能添加新绑定 变量添加到已发布的操作链接模板。actionUrlheadersrequestBody

如果 API 未进行版本控制,则可以使用 ConnectApi.ActionLinkGroupDefinitionInput 的属性来避免由于以下原因导致的问题 升级或更改 API 中的功能。请参阅设置操作链接组过期 设计操作中的时间 链接模板。expirationDate

错误

使用操作链接诊断信息方法 (ConnectApi.ActionLinks.getActionLinkDiagnosticInfo(communityId, actionLinkId)) 返回执行操作链接时的状态代码和错误。诊断信息仅提供给以下用户 可以访问操作链接。Api

本地化标签

操作链接使用在 ConnectApi.ActionLinkDefinitionInput 请求正文的属性和操作链接模板的 Label 字段中指定的一组预定义的本地化标签。labelKey

有关标签的列表,请参阅操作链接标签。

注意

如果标签键值对操作链接没有意义,请指定自定义标签 在操作链接模板的“标签”字段中,将“标签”设置为“标签” 键到无。但是,自定义标签未本地化。

操作链接用例

使用操作链接将 Salesforce 和第三方服务与源集成。一 操作链接可以向 Salesforce 或第三方 API 发出 HTTP 请求。操作链接还可以 下载文件或打开网页。本主题包含一个示例用例。

从源开始视频聊天

假设您在一家拥有 Salesforce 组织的公司担任 Salesforce 开发人员,并且 一家名为“VideoChat”的虚构公司的帐户。用户一直在说他们想要 通过移动设备执行更多操作。系统会要求你创建一个应用,让用户创建和 直接从他们的移动设备加入视频聊天。当用户在 Salesforce 中打开 VideoChat 应用程序时,系统会要求他们为视频聊天命名 聊天室,并邀请组或个人用户加入视频聊天室。当用户 单击确定,VideoChat 应用程序启动视频聊天室并发布 将项目提要给选定的组或用户,要求他们加入视频 通过单击标记为“加入”的操作链接进行聊天。当 被邀请者单击“加入”,操作链接将打开一个包含 视频聊天室。

批准和拒绝操作链接

作为考虑如何创建操作链接 URL 的开发人员,您会想到这些 要求:

  1. 当用户单击“加入”时,操作链接 URL 必须打开 他们被邀请到视频聊天室。
  2. 操作链接 URL 必须告诉视频聊天室谁正在加入。

若要动态创建操作链接 URL,请在 设置。

对于第一个要求,在“操作 URL 模板”字段中创建一个绑定变量。当用户单击“确定”以创建视频聊天时 room,您的 Apex 代码会生成一个唯一的房间 ID。Apex 代码使用该唯一房间 ID 作为 绑定变量值,当它实例化操作链接组时,将其与 源项,并发布源项。{!Bindings.roomId}

对于第二个要求,操作链接必须包含用户 ID。 操作链接支持 一组预定义的上下文变量。调用操作链接时,Salesforce 将变量替换为值。上下文变量包括有关人员的信息 单击操作链接以及在什么上下文中调用该链接。您决定在操作中包含上下文变量 URL,以便当用户单击源中的操作链接时,Salesforce 替换用户的 ID,视频聊天室知道谁在进入。{!userId}

这是“加入”操作链接的操作链接模板。

设置中操作 URL 字段中的上下文变量和绑定变量。

每个操作链接都必须与一个操作链接组相关联。该组定义 与其关联的所有操作链接共享的属性。即使您使用的是单个 操作链接(如本示例所示),它必须与组相关联。第一个字段 操作链接模板是操作链接组模板,在本例中为 是视频聊天,这是动作链接组模板的动作 链接模板与。

操作链接组模板

使用源和源元素

Chatter 摘要是摘要元素的容器。摘要 class 是 的父类 表示源的类 帖子和班级, 表示 Feed 中的捆绑包和建议。ConnectApi.FeedElementConnectApi.FeedItemConnectApi.GenericFeedElement

注意

Salesforce 帮助将源项目称为帖子,将捆绑包称为捆绑帖子。

能力

作为使饲料多样化的努力的一部分,饲料中的一些功能 元素已分解为功能。功能提供一致的方式 与源进行交互。请勿检查源元素类型以确定哪个 功能可用于 Feed 元素。检查功能,它会告诉你 明确说明什么是可用的。检查是否存在某种功能,以确定 客户端可以对 Feed 元素执行操作。

ConnectApi.FeedElement.capabilities 属性包含一组 能力。

功能既包括指示功能是可能的,也包括关联的数据 具有该功能。如果源元素上存在功能属性,则该功能为 可用,即使尚无与该功能关联的任何数据。例如 如果 capability 属性存在 在源元素上,上下文用户可以喜欢该源元素。如果能力 属性在 Feed 元素上不存在,因此不可能喜欢该 Feed 元素。chatterLikes

发布源元素时,请在 ConnectApi.FeedElementInput.capabilities 属性中指定其特征。

Salesforce UI 如何显示源项

客户端可以使用该属性 以确定它可以对 Feed 元素执行哪些操作以及如何呈现 Feed 元素。为 除 之外的所有源元素子类,客户端不必知道子类类型。 相反,客户端可以查看功能。源项确实具有功能,但 它们也有一些属性,例如 , 这些功能未公开为功能。因此,客户端必须处理 Feed 项 与其他 Feed 元素略有不同。ConnectApi.FeedElement.capabilitiesConnectApi.FeedItemactor

Salesforce UI 使用一种布局来 显示每个 Feed 项。这种单一布局为客户提供了一致的 Feed 视图 项目,并为开发人员提供了一种创建 UI 的简单方法。布局始终包含相同的 碎片和碎片始终处于同一位置。仅布局的内容 件数变化。

具有突出显示的布局元素的 Feed 项

源项 (ConnectApi.FeedItem) 布局元素包括:

  1. 执行组件 ()- A 源项创建者的照片或图标。(您可以在 Feed 商品类型级别。例如,仪表板快照源项类型显示 仪表板作为创建者。ConnectApi.FeedItem.actor
  2. 标头 () – 源项的上下文。一样 Feed 项可以具有不同的标题,具体取决于发布者及其位置 张贴。例如,Ted 将此源项发布到一个组。ConnectApi.FeedElement.header时间戳 () – 日期和时间 Feed 项的发布时间。如果源项的存续时间少于两天,则 日期和时间的格式为相对的本地化字符串,例如“17M 前”。否则,日期和时间的格式将设置为绝对的本地化 字符串。ConnectApi.FeedElement.relativeCreatedDate
  3. 正文 () – 所有源项都有一个正文。正文 可以是 ,这是当用户 不为源项提供文本。因为 body 可以是 ,所以不能将其用作默认情况 用于呈现文本。请改用该属性,该属性始终包含 价值。ConnectApi.FeedElement.bodynullnullConnectApi.FeedElement.header.text
  4. 辅助主体 () – 可视化 能力。请参阅功能。ConnectApi.FeedElement.capabilities

Salesforce 如何显示源项以外的源元素

一个 客户端可以使用该属性来确定它可以执行的操作 替换为 Feed 元素以及如何呈现 Feed 元素。本部分使用捆绑包作为 如何呈现源元素的示例,但这些属性可用于每个 feed 元素。通过这些功能,您可以处理源中的所有内容 一贯。ConnectApi.FeedElement.capabilities

注意

捆绑的帖子包含源跟踪的更改,并记录在案 仅限 Feed。

为了给客户提供干净、有条理的提要,Salesforce 聚合了 将 Feed 跟踪的更改整合到捆绑包中。要查看各个 Feed 元素,请点击 捆。

一系列 Feed 跟踪的更改

丛是具有 的对象(它是 的具体子类)。捆绑包布局 元素包括:ConnectApi.GenericFeedElementConnectApi.FeedElementConenctApi.BundleCapability

  • 标头 () – 对于源跟踪的更改捆绑包,此 文本为“更新此记录”。ConnectApi.FeedElement.headerUser Name
  • 时间戳 () – 日期和时间 Feed 项的发布时间。如果 Feed 项的发布时间不足 2 天,则日期 和 time 的格式为相对的本地化字符串,例如“17m 前”。否则,日期和时间的格式将设置为绝对的本地化 字符串。ConnectApi.FeedElement.relativeCreatedDate
  • 辅助主体 () – 束 显示 和 和 属性,用于 捆。如果 Feed 跟踪的更改超过两个,则捆绑包会显示 “显示所有更新”链接。ConnectApi.FeedElement.capabilities.bundle.changesfieldNameoldValuenewValue

Feed 元素可见性

用户看到的 Feed 元素取决于管理员配置 Feed 的方式 跟踪、共享规则和字段级安全性。例如,如果用户不这样做 有权访问记录,但他们看不到该记录的更新。如果用户可以 查看 Feed 元素的父元素,用户可以看到 Feed 元素。通常,用户 查看以下各项的 Feed 更新:

  • @mention用户的源元素(如果用户可以访问 Feed) 元素的父元素)
  • @mention用户所属组的 Feed 元素
  • 其父级记录是用户可以查看的记录的记录上的记录字段更改,包括 用户、组和文件记录
  • 发布给用户的 Feed 元素
  • 发布到用户所属或所属群组的 Feed 元素
  • 标准记录和自定义记录的源元素,例如任务、事件、潜在顾客、 帐户、文件

饲料类型

Feed 有很多种类型。每种 Feed 类型都定义了 Feed 元素的集合。

重要

Feed 元素的集合可能会在版本之间更改。除收藏夹之外的所有源类型都在枚举中公开,并传递给其中一个方法。此示例从上下文用户的新闻源中获取源元素 和主题 饲料。

ConnectApi.FeedTypeConnectApi.ChatterFeeds.getFeedElementsFromFeed

ConnectApi.FeedElementPage newsFeedElementPage = 
   ConnectApi.ChatterFeeds.getFeedElementsFromFeed(null, 
      ConnectApi.FeedType.News, 'me');

ConnectApi.FeedElementPage topicsFeedElementPage = 
   ConnectApi.ChatterFeeds.getFeedElementsFromFeed(null, 
      ConnectApi.FeedType.Topics, '0TOD00000000cld');

若要获取筛选器源,请调用其中一个方法。要获取 收藏夹源,请调用其中一个方法。ConnectApi.ChatterFeeds.getFeedElementsFromFilterFeedConnectApi.ChatterFavorites.getFeedElements

Feed 类型及其说明如下:

  • Bookmarks包含保存为书签的所有源项 上下文用户。
  • Company包含除 类型的 Feed 项之外的所有 Feed 项。要查看 Feed 项,请执行以下操作: 用户必须具有对其父级的共享访问权限。TrackedChange
  • DirectMessageModeration包含标记为 适度。私信审核源仅适用于具有以下条件的用户 审核体验 Chatter 消息权限。
  • DirectMessages包含上下文的所有源项 用户的直接消息。
  • Draft– 包含所有源项 上下文用户起草的。
  • Files包含包含已发布文件的所有源项 按上下文用户关注的人员或组。
  • Filter– 包含新闻源 筛选以包含其父级为指定对象类型的源项。
  • Groups包含来自所有组的所有源项 上下文用户拥有或属于其成员。
  • Home– 包含所有源项 与 Experience Cloud 站点中的任何托管主题相关联。
  • Landing包含 在请求 Feed 时最能提高用户参与度的所有 Feed 项。允许 客户端,以避免在个性化源不多时出现空源 项目。
  • Moderation包含标记为要审核的所有 Feed 项, 除了直接消息。审核源仅适用于 “审核体验源”权限。
  • Mute– 包含所有 上下文用户静音的源项。
  • News包含上下文用户人员的所有更新 关注用户所属的组,并记录用户所属的组 以后。包含其父级为上下文的记录的所有更新 用户。
  • PendingReview包含所有 Feed 项和注释 正在等待审查。
  • People包含所有人发布的所有提要项 上下文用户关注。
  • Record包含其父级为 指定的记录,可以是组、用户、对象、文件或任何其他 标准或自定义对象。当记录为组时,源还包含源 提及该组的项目。当记录是用户时,源仅包含 该用户的源项目。您可以获取其他用户的记录 Feed。
  • Streams包含最多 25 个启用源的实体的任意组合的所有源项 上下文用户在流中订阅的。启用 Feed 的实体示例 包括人员、组和记录,
  • To包含提及上下文用户的所有源项。包含源项 上下文用户注释的 Context 用户和 Feed 项目由 Context 用户创建,这些项目是 评论。
  • Topics包含包含指定 主题。
  • UserProfile– 包含源项 在用户更改可在源中跟踪的记录时创建。包含饲料 父级为用户的项目和@mention用户的源项目。此提要是 与新闻源不同,后者返回更多源项,包括组更新。 您可以获取其他用户的用户个人资料 Feed。
  • Favorites– 包含保存者 上下文用户。收藏夹包括源搜索、列表视图和主题。

使用 postFeedElement 发布源项

提示

方法有 发布提要项目的最简单、最有效的方法,因为与这些方法不同,它们不需要您 传递源类型。Feed 项是您可以发布的唯一 Feed 元素类型。postFeedElementpostFeedItem

使用这些方法发布源项。postFeedElement(communityId, subjectId, feedElementType, text)发布纯文本 Feed 元素。postFeedElement(communityId, feedElement, feedElementFileUpload)(版本 35.0 及更早版本)发布富文本 Feed 元素。包括提及和主题标签主题,附加文件 添加到源元素,并将操作链接组与源元素相关联。您可以 还可以使用此方法共享 Feed 元素并添加注释。postFeedElement(communityId, feedElement) (版本 36.0 和 稍后)发布富文本 Feed 元素。包括提及和主题标签主题,附上 已将文件上传到 Feed 元素,并将操作链接组与 feed 元素。您还可以使用此方法共享 Feed 元素并添加 评论。

发布源项时,将创建标准对象或自定义对象的子对象。指定 参数中的父对象或在参数中传递的对象的属性中的父对象。这 该参数的值决定了在其中显示源项的源。返回对象中的属性包含以下信息 父对象。subjectIdsubjectIdConnectApi.FeedElementInputfeedElementsubjectIdparentConnectApi.FeedItem

使用这些方法可以完成这些任务。发给自己此代码将源项发布到上下文用户。指定 ,这是上下文的别名 用户的 ID。它还可以指定上下文用户的 同上。subjectIdme

ConnectApi.FeedElement feedElement = ConnectApi.ChatterFeeds.postFeedElement(null, 'me', ConnectApi.FeedElementType.FeedItem, 'Working from home today.');

新发布的属性 源项包含上下文用户的parentConnectApi.UserSummary发布给其他用户此代码将源项发布到上下文用户以外的用户。指定用户 目标的 ID 用户。subjectId

ConnectApi.FeedElement feedElement = ConnectApi.ChatterFeeds.postFeedElement(null, '005D00000016Qxp', ConnectApi.FeedElementType.FeedItem, 'Kevin, do you have information about the new categories?');

新发布的属性 源项包含目标用户的parentConnectApi.UserSummary发帖到小组此代码将源项发布到组。指定组 同上。subjectId

ConnectApi.FeedItemInput feedItemInput = new ConnectApi.FeedItemInput();
ConnectApi.MentionSegmentInput mentionSegmentInput = new ConnectApi.MentionSegmentInput();
ConnectApi.MessageBodyInput messageBodyInput = new ConnectApi.MessageBodyInput();
ConnectApi.TextSegmentInput textSegmentInput = new ConnectApi.TextSegmentInput();

messageBodyInput.messageSegments = new List<ConnectApi.MessageSegmentInput>();

mentionSegmentInput.id = '005RR000000Dme9';
messageBodyInput.messageSegments.add(mentionSegmentInput);

textSegmentInput.text = 'Could you take a look?';
messageBodyInput.messageSegments.add(textSegmentInput);

feedItemInput.body = messageBodyInput;
feedItemInput.feedElementType = ConnectApi.FeedElementType.FeedItem;
feedItemInput.subjectId = '0F9RR0000004CPw';

ConnectApi.FeedElement feedElement = ConnectApi.ChatterFeeds.postFeedElement(Network.getNetworkId(), feedItemInput);

新发布的属性 Feed 项包含指定组的 。parentConnectApi.ChatterGroupSummary发布到记录(如文件或帐户)此代码将源项发布到记录并提及组。指定 记录 同上。subjectId

ConnectApi.FeedItemInput feedItemInput = new ConnectApi.FeedItemInput();
ConnectApi.MentionSegmentInput mentionSegmentInput = new ConnectApi.MentionSegmentInput();
ConnectApi.MessageBodyInput messageBodyInput = new ConnectApi.MessageBodyInput();
ConnectApi.TextSegmentInput textSegmentInput = new ConnectApi.TextSegmentInput();

messageBodyInput.messageSegments = new List<ConnectApi.MessageSegmentInput>();

textSegmentInput.text = 'Does anyone know anyone with contacts here?';
messageBodyInput.messageSegments.add(textSegmentInput);

// Mention a group.
mentionSegmentInput.id = '0F9D00000000oOT';
messageBodyInput.messageSegments.add(mentionSegmentInput);

feedItemInput.body = messageBodyInput;
feedItemInput.feedElementType = ConnectApi.FeedElementType.FeedItem;

// Use a record ID for the subject ID.
feedItemInput.subjectId = '001D000000JVwL9';

ConnectApi.FeedElement feedElement = ConnectApi.ChatterFeeds.postFeedElement(null, feedItemInput);

新源项的属性 取决于 中指定的记录类型。如果记录类型为 File,则 父级是 。如果 记录类型为 Group,父类型为 。如果记录类型为 User,则 父级是 。对于所有人 其他记录类型,如本使用 Account 的示例中所示,父记录类型为 。parentsubjectIdConnectApi.FileSummaryConnectApi.ChatterGroupSummaryConnectApi.UserSummaryConnectApi.RecordSummary

从 Feed 中获取 Feed 元素

提示

若要返回包含源元素的源,请调用这些方法。饲料 元素类型包括 Feed Item、Bundle 和 Recommendation。

对于每种 Feed 类型,从 Feed 中获取 Feed 项的方式相似,但不完全相同。从 、 、 、 和 源中获取源元素CompanyDirectMessageModerationDirectMessagesHomeModerationPendingReview若要从这些源中获取源元素,请使用这些不需要 .subjectId

  • getFeedElementsFromFeed(communityId, feedType)
  • getFeedElementsFromFeed(communityId, feedType, pageParam, pageSize, sortParam)
  • getFeedElementsFromFeed(communityId, feedType, recentCommentCount, density, pageParam, pageSize, sortParam)
  • getFeedElementsFromFeed(communityId, feedType, recentCommentCount, density, pageParam, pageSize, sortParam, filter)
  • getFeedElementsFromFeed(communityId, feedType, recentCommentCount, density, pageParam, pageSize, sortParam, filter, threadedCommentsCollapsed)

从源中获取源元素Favorites若要从收藏夹源中获取源元素,请指定 .对于这些 Feed,必须 上下文用户的 ID 或别名。favoriteIdsubjectIdme

  • getFeedElements(communityId, subjectId, favoriteId)
  • getFeedElements(communityId, subjectId, favoriteId, pageParam, pageSize, sortParam)
  • getFeedElements(communityId, subjectId, favoriteId, recentCommentCount, elementsPerBundle, pageParam, pageSize, sortParam)

从源中获取源元素Filter若要从筛选器源中获取源元素,请指定 .指示对象 type 和 是对象 ID 的前三个字符。必须是 上下文用户或别名 。keyPrefixkeyPrefixsubjectIdme

  • getFeedElementsFromFilterFeed(communityId, subjectId, keyPrefix)
  • getFeedElementsFromFilterFeed(communityId, subjectId, keyPrefix, pageParam, pageSize, sortParam)
  • getFeedElementsFromFilterFeed(communityId, subjectId, keyPrefix, recentCommentCount, elementsPerBundle, density, pageParam, pageSize, sortParam)

从 、 、 、 、 、 、 和 源 中获取源元素BookmarksFilesGroupsMuteNewsPeopleRecordStreamsToTopicsUserProfile要从这些 Feed 类型中获取 Feed 元素,请指定主题 ID。如果是,则可以是任何记录 ID,包括组 ID。如果是,则必须是流 ID。如果是,则必须是主题 ID。如果是,则可以是任何用户 ID。如果 是任何其他值,则必须是上下文用户的 ID 或别名。feedTypeRecordsubjectIdfeedTypeStreamssubjectIdfeedTypeTopicssubjectIdfeedTypeUserProfilesubjectIdfeedTypesubjectIdme

  • getFeedElementsFromFeed(communityId, feedType, subjectId)
  • getFeedElementsFromFeed(communityId, feedType, subjectId, pageParam, pageSize, sortParam)
  • getFeedElementsFromFeed(communityId, feedType, subjectId, recentCommentCount, density, pageParam, pageSize, sortParam)
  • getFeedElementsFromFeed(communityId, feedType, subjectId, recentCommentCount, density, pageParam, pageSize, sortParam, filter)
  • getFeedElementsFromFeed(communityId, feedType, subjectId, recentCommentCount, density, pageParam, pageSize, sortParam, filter, threadedCommentsCollapsed)

从 Feed 中获取 Feed 元素Record对于 ,请指定 记录 ID。subjectId

提示

记录可以是支持源的任何类型的记录, 包括组。Salesforce UI 中组页面上的源是一条记录 饲料。

  • getFeedElementsFromFeed(communityId, feedType, subjectId, recentCommentCount, density, pageParam, pageSize, sortParam, showInternalOnly)
  • getFeedElementsFromFeed(communityId, feedType, subjectId, recentCommentCount, density, pageParam, pageSize, sortParam, customFilter)
  • getFeedElementsFromFeed(communityId, feedType, subjectId, recentCommentCount, elementsPerBundle, density, pageParam, pageSize, sortParam, showInternalOnly)
  • getFeedElementsFromFeed(communityId, feedType, subjectId, recentCommentCount, elementsPerBundle, density, pageParam, pageSize, sortParam, showInternalOnly, filter)
  • getFeedElementsFromFeed(communityId, feedType, subjectId, recentCommentCount, elementsPerBundle, density, pageParam, pageSize, sortParam, showInternalOnly, customFilter)
  • getFeedElementsFromFeed(communityId, feedType, subjectId, recentCommentCount, elementsPerBundle, density, pageParam, pageSize, sortParam, showInternalOnly, filter, threadedCommentsCollapsed)
  • getFeedElementsFromFeed(communityId, feedType, subjectId, recentCommentCount, elementsPerBundle, density, pageParam, pageSize, sortParam, showInternalOnly, customFilter, threadedCommentsCollapsed)

在 Experience Cloud 中访问 ConnectApi 数据 网站

许多方法在上下文中工作 单个 Experience Cloud 站点。

ConnectApi

许多方法都包含作为第一个参数。如果 您没有启用、使用或用于此目的的数字体验 论点。ConnectApicommunityIdinternalnull

如果启用了数字体验,则参数 指定是否在默认 Experience Cloud 站点的上下文中执行方法 (通过指定 或 ) 或在特定站点的上下文中 (通过 指定 ID)。其他实体引用的任何实体,例如评论或提要项 方法中的参数必须位于指定的站点中。该 ID 包含在返回的 URL 中 在输出中。communityIdinternalnull

某些方法包括作为参数。不像 ,如果你没有 启用数字体验后,您不能使用这些方法。站点 ID 包含在 URL 中 在输出中返回。ConnectApisiteIdcommunityId

输出对象中返回的大多数 URL 是 连接 REST API 资源。ConnectApi如果指定 ID,则输出中返回的 URL 将使用以下命令 格式:

/connect/communities/communityId/resource

如果 指定 ,输出中返回的 URL 使用相同的 格式:

internal

/connect/communities/internal/resource

如果指定 ,则输出中返回的 URL 使用其中之一 格式:

null

/chatter/resource

/connect/resource

可供 Experience Cloud 访客用户使用的方法

如果您的 Experience Cloud 站点允许在不登录的情况下进行访问,则访客用户有权访问 到许多 Apex 方法。这些方法返回来宾用户有权访问的信息 自。这些方法的所有重载都可供来宾用户使用。

重要

如果 此处列出的方法的重载表示 Chatter 是必需的,您还必须启用 public 访问您的 Experience Cloud 站点,使该方法可用于 来宾用户。如果未启用公共访问,则通过以下方法检索的数据 require Chatter 无法在公共站点页面上正确加载。

  • Announcements方法:
    • getAnnouncements()
  • ChatterFeeds方法:
    • getComment()
    • getCommentInContext()
    • getCommentsForFeedElement()
    • getExtensions()
    • getFeed()
    • getFeedElement()
    • getFeedElementBatch()
    • getFeedElementPoll()
    • getFeedElementsFromFeed()
    • getFeedElementsUpdatedSince()
    • getFeedKWithFeedElements()
    • getLike()
    • getLikeKsForComment()
    • getLikesForFeedElement()
    • getLinkMetadata()
    • getPinnedFeedElementsFromFeed()
    • getRelatedPosts()
    • getThreadsForFeedComment()
    • getVotesForComment()
    • getVotesForFeedElement()
    • searchFeedElements()
    • searchFeedElementsInFeed()
    • updatePinnedFeedElements()
  • ChatterGroups方法:
    • getGroup()
    • getGroups()
    • getMembers()
    • searchGroups()
  • ChatterUsers方法:
    • getFollowers()
    • getFollowings()
    • getReputation()
    • getUser()
    • getUserBatch()
    • getUserGroups()
    • getUsers()
    • searchUserGroupDetails()
    • searchUsers()
  • CommerceCart methods:
    • addItemsToCart()
    • addItemToCart()
    • applyCartCoupon()
    • copyCartToWishlist()
    • createCart()
    • deleteCart()
    • deleteCartCoupon()
    • deleteCartItem()
    • deleteInventoryReservation() (developer preview)
    • getCartCoupons()
    • getCartItems()
    • getCartSummary()
    • getOrCreateActiveCartSummary()
    • makeCartPrimary()
    • setCartMessagesVisibility()
    • updateCartItem()
    • upsertInventoryReservation() (developer preview)
  • CommerceCatalog methods:
    • getProduct()
    • getProductCategory()
    • getProductCategoryChildren()
    • getProductCategoryPath()
    • getProductChildCollection()
  • CommercePromotions methods:
    • decreaseRedemption()
    • evaluate()
    • increaseRedemption()
  • CommerceSearch methods:
    • getSortRules()
    • getSuggestions()
    • searchProducts()
  • CommerceStorePricing methods:
    • getProductPrice()
    • getProductPrices()
  • Communities methods:
    • getCommunity()
  • EmployeeProfiles methods:
    • getPhoto()
  • Knowledge methods:
    • getTopViewedArticlesForTopic()
    • getTrendingArticles()
    • getTrendingArticlesForTopic()
  • ManagedContent methods:
    • getAllContent()
    • getAllDeliveryChannels()
    • getAllManagedContent()
    • getContentByContentKeys()
    • getContentByIds()
    • getManagedContentByContentKeys()
    • getManagedContentByIds()
    • getManagedContentByTopics()
    • getManagedContentByTopicsAndContentKeys()
    • getManagedContentByTopicsAndIds()
  • ManagedContentDelivery methods:
    • getCollectionItemsForChannel()
    • getCollectionItemsForSite()
    • getManagedContentChannel()
    • getManagedContentForChannel()
    • getManagedContentForSite()
    • getManagedContentsForChannel()
    • getManagedContentsForSite()
  • ManagedTopics methods:
    • getManagedTopic()
    • getManagedTopics()
  • MarketingIntegration methods:
    • submitForm()
  • NavigationMenu methods:
    • getCommunityNavigationMenu()
  • NextBestActions methods:
    • executeStrategy()
    • setRecommendationReaction()
  • Personalization methods:
    • getAudience()
    • getAudienceBatch()
    • getAudiences()
    • getTarget()
    • getTargetBatch()
    • getTargets()
  • Recommendations方法:
    • getRecommendationsForUser()注意客人只能使用文章和文件推荐 用户。
  • Sites方法:
    • searchSite()
  • Topics方法:
    • getGroupsRecentlyTalkingAboutTopic()
    • getRecentlyTalkingAboutTopicsForGroup()
    • getRecentlyTalkingAboutTopicsForUser()
    • getRelatedTopics()
    • getTopic()
    • getTopics()
    • getTrendingTopics()
  • UserProfiles方法:
    • getPhoto()

DBT 段支持的验证

创建或更新区段时,ConnectApi.CdpSegmentInput 类受 一些 SQL 验证。

您可以使用带有类的 createSegment(input) 方法创建区段。同样,您可以使用具有相同输入类的 updateSegment(segmentApiName, input) 方法更新区段。 嵌套在类中的 ConnectApi.CdpSegmentDbtModelInput 输入类提供 验证 SQL。ConnectApi.CdpSegmentInputConnectApi.CdpSegmentInput

的财产受以下条款的约束 验证。sqlConnectApi.CdpSegmentDbtModelInput

  • select 语句中只允许使用 SegmentOn DMO 的主键。第一张桌子 在 JOIN 子句中必须是配置文件表。
    • 顶层不允许聚合(最小值、最大值、平均值、计数) 选择。---FAIL select max(Individual_dense_viv__dlm.age__c) from Individual_dense_viv__dlm ---FAIL select count(Individual_dense_viv__dlm.individualid__c) from Individual_dense_viv__dlm ---
    • 顶层不允许全选 (*) 表达式 选择。---FAIL select * from Individual_dense_viv__dlm
    • 只有 table 上段的主键(sql 的 from 子句中的第一个表 语句)被允许选择。---PASS select Individual_dense_viv__dlm.individualid__c from Individual_dense_viv__dlm
    • 无法选择多个列,即使其中一个列是 桌子。---FAIL select Individual_dense_viv__dlm.individualid__c, Individual_dense_viv__dlm.age__c from Individual_dense_viv__dlm
    • 主数据中不允许有大小写语句 选择。---FAIL select case when Individual_dense_viv__dlm.individualid__c > 10 then Individual_dense_viv__dlm.individualid__c else null end from Individual_dense_viv__dlm
    • 如果 segmentOn 实体的主键具有键限定符,则必须投影该键 限定符,以及在初选中。首先投影主键,然后投影 限定 符。分组依据还必须包含密钥 限定 符。---PASS select Individual__dlm.id__c, Individual__dlm.fq__id__c from Individual__dlm
    • 如果 segmentOn 实体的主键具有键限定符,则可以提供 加入条件中的附加条件。---PASS select Individual__dlm.id__c from Individual__dlm left join Sales__dlm on Individual__dlm.id__c = Sales__dlm.soldToCustomerId__c and Individual__dlm.kq__id__c is not distinct from Sales__dlm.kq__soldToCustomerId__c
  • 所有列都必须在查询和子选择中由 tablename 完全限定 查询。---FAIL select individualid__c from Individual_dense_viv__dlm
  • 子查询仅在 where 子句中受支持,并且只能发出一个子查询 列。---FAIL select Individual_dense_viv__dlm.individualid__c from (select * from Individual_dense_viv__dlm)
  • 比较相同数据类型的列。若要比较不同数据类型的列,请强制转换一个 或两个操作数,以便它们具有相同的 类型。---PASS select t.id__c from Individual__dlm as t where cast(t.id__c as varchar(100)) = t1.name
  • 支持限制和偏移。---FAIL select Individual_dense_viv__dlm.individualid__c from Individual_dense_viv__dlm limit 10
  • 除 select 语句以外的任何 sql 语句都不是 允许。---FAIL update Individual_dense_viv__dlm set Individual_dense_viv__dlm.individualid__c = 'aa'
  • 只有查询的 from 块中的 DMO 才支持别名。列不能是 锯齿。---PASS select t.id__c from Individual__dlm as t
  • 要加入两个 DMO,DMO 之间必须存在关系,并且必须使用 它们在 join on 条件中的相关 join 键。join on 条件只能包含一个 连接键之间的相等性比较和用于比较的可选附加条件 FQK 字段。---PASS select Individual__dlm.id__c from Individual__dlm left join Sales__dlm on Individual__dlm.id__c = Sales__dlm.soldToCustomerId__c--PASS Individual__dlm left join Sales__dlm on Individual__dlm.id__c = Sales__dlm.soldToCustomerId__c and Individual__dlm.kq__id__c is not distinct from Sales__dlm.kq__soldToCustomerId__c

使用 ConnectApi 输入和输出类

命名空间中的某些类包含 访问 Connect REST API 数据的静态方法。命名空间还包含要传递的输入类 调用静态方法返回的参数和输出类。

ConnectApiConnectApi

ConnectApi方法采取 简单类型或复杂类型。简单类型是原始的 Apex 数据,如整数和字符串。复杂类型是输入对象。ConnectApi

成功执行方法可以 从命名空间返回输出对象。 输出对象可以由其他输出组成 对象。例如,ConnectApi.ActorWithId 输出对象 包含属性,例如 和 ,这些属性包含基元 数据类型。它还包含一个属性,该属性包含一个对象。ConnectApiConnectApiConnectApiidurlmySubscriptionConnectApi.Reference

注意

输出对象中的所有 Salesforce ID 均为 18 个字符 ID。输入对象可以使用 15 个字符的 ID 或 18 个字符的 ID。ConnectApi

了解 ConnectApi 类的限制

命名空间中方法的限制与其他 Apex 类的限制不同。

ConnectApi

对于命名空间中的类, 每个写入操作都会根据 Apex 调控器限制花费一个 DML 语句。 方法调用也是主题 到速率限制。 速率限制 匹配 Connect REST API 速率限制。两者都具有每个用户、每个命名空间、每小时 速率限制。当超过速率限制时,将引发 a。您的 Apex 代码必须捕获和 处理此异常。ConnectApiConnectApiConnectApiConnectApi.RateLimitException

测试代码时,将开始调用 Apex 方法 新的速率限制计数。调用该方法会将速率限制计数设置为该值 那是在你打电话之前.Test.startTestTest.stopTestTest.startTest

打包 ConnectApi 类

如果将类包含在 软件包中,请注意 Chatter 依赖关系。

ConnectApi如果一个类依赖于 Chatter,代码可以编译并安装在未启用 Chatter 的组织中。 但是,如果未启用 Chatter,则代码会在运行时抛出错误 时间。

ConnectApi

System.NoAccessException: Insufficient Privileges: This feature is not currently enabled for this user.

在其参考文档中,每个方法都指示它是否支持 Chatter。ConnectApi

序列化和反序列化 ConnectApi 对象

序列化输出对象时 转换为 JSON,其结构类似于从 Connect REST API 返回的 JSON。当输入对象从 JSON 反序列化时, 格式也类似于 Connect REST API。

ConnectApiConnectApi

Apex 中的 Connect 支持这些 Apex 上下文中的序列化和反序列化。

  • JSON和类 – 将 Apex 输出中的 Connect 序列化为 JSON 并从 JSON 反序列化 Apex 输入中的 Connect。JSONParser
  • Apex REST,将 Apex 输出中的 Connect 序列化为 JSON 作为返回值 值并将 JSON 中的 Apex 输入中的 Connect 反序列化为参数。@RestResource
  • JavaScript Remoting with —serialize 将 Apex 输出中的 Connect 作为返回值转换为 JSON 值并将 JSON 中的 Apex 输入中的 Connect 反序列化为参数。@RemoteAction

Apex 中的 Connect 遵循这些序列化和反序列化规则。

  • 只能序列化输出对象。
  • 只能对顶级输入对象进行反序列化。
  • 枚举值和异常不能序列化或反序列化。

连接Api版本控制和相等性 检查

类中的版本控制如下 与其他 Apex 类的规则不同的特定规则。

ConnectApi

类的版本控制遵循以下命令 规则。ConnectApi

  • 方法调用在 包含方法调用的类版本的上下文。用途 版本类似于 Connect 的部分 REST API URL。ConnectApi/vXX.X
  • 每个输出对象都公开一个方法。此方法 返回创建输出对象的方法所使用的版本 调用。ConnectApigetBuildVersion
  • 与输入对象交互时,Apex 只能访问 封闭的 Apex 类的版本。
  • 传递给方法的输入对象 可能仅包含 Apex 版本支持的非 null 属性 执行方法的类。如果输入对象包含版本不合适的内容 属性,则抛出异常。ConnectApi
  • 仅方法的输出 返回与交互的代码版本中支持的属性 对象。对于输出对象,返回的属性也必须在 内部版本。toString
  • Apex REST、 和序列化仅包括 适合版本的属性。JSON.serialize@RemoteAction
  • Apex REST、和反序列化拒绝属性 版本不合适。JSON.deserialize@RemoteAction
  • 枚举不受版本控制。枚举值在所有 API 版本中都返回。客户 应该优雅地处理他们不理解的值。

以下为类的相等性检查 这些规则。ConnectApi

  • 输入对象 – 比较属性。
  • 输出对象 – 比较属性和生成版本。例如,如果 两个对象具有相同的属性和相同的值,但具有不同的构建 版本,则对象不相等。要获取内部版本,请调用 。getBuildVersion

强制转换 ConnectApi 对象

将某些输出对象向下转换为更多可能很有用 特定类型。

ConnectApi

此技术对于消息段、源项功能和记录特别有用 领域。源项中的消息段是 键入为 。饲料 项目功能类型为 。记录字段的类型为 。这些类 都是抽象的,并且有几个具体的子类。在运行时,您可以使用它来检查具体类型 然后安全地进行相应的下沉。当你 向下,您必须具有处理未知子类的默认大小写。ConnectApi.MessageSegmentConnectApi.FeedItemCapabilityConnectApi.AbstractRecordFieldinstanceof以下示例将 a 向下转换为 :

ConnectApi.MessageSegmentConnectApi.MentionSegment

if(segment instanceof ConnectApi.MentionSegment) {
	ConnectApi.MentionSegment = (ConnectApi.MentionSegment)segment;
}

重要

饲料的成分可以在 释放。编写代码以处理未知子类的实例。

通配符

使用通配符匹配 Connect REST API 和 Connect in Apex 中的文本模式 搜索。通配符的常见用途是搜索源。在参数中传递搜索字符串和通配符。此示例是 Connect REST API 请求:

q

/chatter/feed-elements?q=chat*

此示例是 Connect in Apex 方法 叫:

ConnectApi.ChatterFeeds.searchFeedElements(null, 'chat*');

您可以指定以下通配符来匹配搜索中的文本模式:

通配符描述
*星号与搜索词中间或末尾的零个或多个字符匹配。例如,搜索 john* 会查找以 john 开头的项目, 例如,JohnJohnson 或 Johnny。搜索 mi* meyers 会找到带有 mike meyers 或 michael meyers 的项目。如果要在单词或短语中搜索字面上的星号,请转义星号(在星号前面加上字符)。\
?问号仅匹配搜索词中间或末尾的一个字符。例如,搜索 jo?n 会查找术语为 john 或 joan 的项目,但不会查找 jon 或 johan 的项目。您不能使用 ?在查找搜索中。

使用通配符时,请考虑以下注意事项:

  • 通配符搜索越集中,搜索结果返回的速度就越快,结果就越有可能反映您的意图。例如,要搜索所有 出现单词(或复数形式)时,在搜索字符串中指定比指定限制性较小的通配符搜索(例如 )更有效,后者可以 返回无关的匹配项(如 )。prospectprospectsprospect*prosp*prosperity
  • 定制搜索以查找单词的所有变体。例如,要查找 和 ,则 将指定 .propertypropertiespropert*
  • 标点符号已编入索引。若要查找短语或在短语中查找,必须将搜索字符串括在引号中 你必须逃脱特殊字符。例如,找到短语 。 转义字符 () 是必需的,以便此搜索正常工作。*?“where are you\?”where are you?\

测试 ConnectApi 代码

与所有 Apex 代码一样,Connect in Apex 代码需要测试覆盖率。

Connect in Apex 方法不会在系统模式下运行,而是在 当前用户(也称为上下文用户)的上下文。方法 有权访问上下文用户有权访问的任何内容。Apex 中的 Connect 不会 支持系统方法。runAs

大多数 Connect in Apex 方法都需要访问真实的组织数据,除非在 标记为 的测试方法。@IsTest(SeeAllData=true)

但是,不允许某些 Connect in Apex 方法(例如 )访问 tests,并且必须与特殊的测试方法一起使用,这些方法注册要返回的输出 测试上下文。如果某个方法需要某个方法,则该要求在该方法的“用法”部分中说明。getFeedElementsFromFeedsetTest

测试方法名称是带有前缀的常规方法名称。测试方法签名(参数组合) 匹配常规方法的签名。例如,如果常规方法有三个 过载,测试方法有三个过载。setTest

在 Apex 测试方法中使用 Connect 类似于在 Apex 中测试 Web 服务。首先,构建 您希望方法返回的数据。要构建数据,请创建输出对象并设置其 性能。若要创建对象,可以对任何非抽象对象使用无参数构造函数 输出类。

生成数据后,调用测试方法注册数据。调用测试方法 与正在测试的常规方法具有相同的签名。

注册测试数据后,运行常规方法。当您运行常规方法时, 返回已注册的数据。

重要

使用与常规方法签名匹配的测试方法签名。如果 调用常规时,数据未注册到匹配的参数集 方法,则会收到异常。此示例显示了一个测试,该测试构造并注册它,以便在使用特定 参数组合。

ConnectApi.FeedElementPagegetFeedElementsFromFeed

global class NewsFeedClass {
    global static Integer getNewsFeedCount() {
        ConnectApi.FeedElementPage elements = 
            ConnectApi.ChatterFeeds.getFeedElementsFromFeed(null,
                ConnectApi.FeedType.News, 'me');
        return elements.elements.size();
    }
}
@isTest
private class NewsFeedClassTest {
    @IsTest
    static void doTest() {
        // Build a simple feed item
        ConnectApi.FeedElementPage testPage = new ConnectApi.FeedElementPage();
        List<ConnectApi.FeedItem> testItemList = new List<ConnectApi.FeedItem>();
        testItemList.add(new ConnectApi.FeedItem());
        testItemList.add(new ConnectApi.FeedItem());
        testPage.elements = testItemList;

        // Set the test data
        ConnectApi.ChatterFeeds.setTestGetFeedElementsFromFeed(null,
            ConnectApi.FeedType.News, 'me', testPage);

        // The method returns the test page, which we know has two items in it.
        Test.startTest();
        System.assertEquals(2, NewsFeedClass.getNewsFeedCount());
        Test.stopTest();
    }
}

ConnectApi 类与其他 Apex 之间的差异 类

请注意类和其他 Apex 类之间的这些附加差异。

ConnectApi系统模式和上下文用户Connect in Apex 方法不会在系统模式下运行,而是在 当前用户(也称为上下文用户)的上下文。方法 有权访问上下文用户有权访问的任何内容。Apex 中的 Connect 不会 支持系统方法。runAs当 方法采用subjectId论点 通常,该主题必须是上下文用户。在这些情况下,您可以使用字符串来指定上下文用户,而不是 同上。me默认情况下,Apex 中的 Connect 对 Automated Process 用户不可用。在 Apex 中连接 可供以下用户使用:

  • 仅限 Chatter 的用户
  • 来宾用户
  • 门户用户
  • 标准用户

with sharingwithout sharingApex 中的 Connect 会忽略 和 关键字。相反,上下文用户控制所有安全性、字段级共享和 能见度。例如,如果上下文用户是专用组的成员,则类可以发布到该组。如果 上下文用户不是专用组的成员,代码无法查看 该群组,但无法发布到该群组。with sharingwithout sharingConnectApi异步操作Apex 中的某些 Connect 操作是异步的,也就是说,它们不是 立即发生。例如,如果代码为用户添加了源项,则不会 立即在新闻提要中可用。另一个例子:当你添加一张照片时,它是 不能立即使用。对于测试,如果您添加照片,则无法检索 它立即。Apex 中不支持 XML 休息Apex REST 不支持 XML 序列化和反序列化 在 Apex 对象中连接。Apex REST支持JSON序列化和反序列化 在 Apex 对象中连接。空日志条目有关 Apex 对象中 Connect 的信息不会显示在日志事件中。VARIABLE_ASSIGNMENT没有 Apex SOAP Web 服务 支持Apex 对象中的 Connect 不能在 Apex SOAP Web 服务中使用 用关键字 表示。webservice

在 Apex 中连接

在 Apex 中使用 Connect 开发自定义体验 Salesforce的。Connect in Apex 提供对 B2B Commerce 的编程访问, CMS 托管内容、Experience Cloud 站点、主题等。创建 Apex 显示 Chatter 摘要的页面,发布带有提及和主题的摘要项目, 并更新用户和群组照片。创建更新 Chatter 的触发器 饲料。

许多 Connect REST API 资源操作都以静态方法的形式公开 命名空间中的 Apex 类。这些方法使用其他类 输入和返回信息。命名空间称为ConnectApiConnectApiConnectApi在 Apex 中连接.

在 Apex 中,您可以使用 SOQL 查询访问一些 Connect 数据,并且 对象。但是,在类和数据中公开数据更简单 已本地化和结构化以进行显示。例如,代替 进行多次调用以访问和组合源,您可以做到这一点 只需一个电话。ConnectApi

在 Apex 方法中连接在用户执行方法的上下文中执行。代码有 访问上下文用户有权访问的任何内容。它不会磨合 系统模式与其他 Apex 代码一样。

有关 Connect in Apex 参考信息,请参阅 ConnectApi 命名空间。

  • Connect in Apex 示例 使用这些示例
    在 Apex 中使用 Connect 执行常见任务。
  • Connect in Apex 功能 本主题介绍用于处理 Apex 中的常见 Connect 功能
    的类和方法。
  • 使用 ConnectApi 输入和输出类 命名空间中的某些类
    包含访问 Connect REST API 数据的静态方法。命名空间还包含要作为参数传递的输入类,以及调用静态方法返回的输出类。ConnectApiConnectApi
  • 了解 ConnectApi 类的限制 命名空间中方法的限制与其他 Apex 类
    的限制不同。ConnectApi
  • 打包 ConnectApi 类 如果在包中包含类
    ,请注意 Chatter 依赖关系。ConnectApi
  • 序列化和反序列化 ConnectApi 对象 将输出对象
    序列化为 JSON 时,其结构类似于从 Connect REST API 返回的 JSON。从 JSON 反序列化输入对象时,格式也类似于 Connect REST API。ConnectApiConnectApi
  • ConnectApi 版本控制和相等性检查
    类中的版本控制遵循与其他 Apex 类的规则不同的特定规则。ConnectApi
  • 强制转换 ConnectApi 对象 将某些输出对象
    下沉为更具体的类型可能很有用。ConnectApi
  • 配符:
    使用通配符匹配 Connect REST API 和 Connect in Apex 搜索中的文本模式。
  • 测试 ConnectApi 代码 与所有 Apex 代码一样,Apex 代码
    中的 Connect 需要测试覆盖率。
  • ConnectApi 类与其他 Apex 类之间的差异 请注意类和其他 Apex 类
    之间的这些额外差异。ConnectApi

在 Apex 中连接示例

使用这些示例在 Apex 中使用 Connect 执行常见任务。

  • 从源中获取源元素 调用方法以从源中获取源
    元素。
  • 从其他用户的源中获取源元素 调用方法以从其他用户的源中获取源
    元素。
  • 从源中获取特定于站点的源元素 调用一种方法以显示用户配置文件源,该源
    仅包含范围限定为特定 Experience Cloud 站点的源元素。
  • 发布 Feed 元素 调用以发布 Feed 元素
  • 发布带有提及
    的源元素 调用方法或使用 ConnectApiHelper 存储库发布源。
  • 使用现有文件
    发布源元素 调用方法以发布包含已上传文件的源元素。
  • 发布带有内联图像
    的富文本源元素 调用方法或使用 ConnectApiHelper 存储库发布包含已上传的内联图像的源元素。
  • 发布带有代码块
    的 RTF 源元素 调用方法以发布带有代码块的源元素。
  • 发布带有新文件(二进制)附件的源元素
    调用方法以将源元素与新文件一起发布。
  • 发布一批源元素
    使用触发器调用方法以批量发布到帐户的源。
  • 使用新(二进制)文件发布一批源元素 使用触发器调用方法将新文件
    批量发布到帐户的源。
  • 定义操作链接并使用源元素
    发布 在操作链接组中创建一个操作链接,将操作链接组与源项关联,然后发布源项。
  • 在模板中定义操作链接并使用源元素
    发布 创建操作链接和操作链接组,并从模板实例化操作链接组。
  • 编辑 Feed 元素 调用方法以编辑 Feed 元素
  • 编辑问题标题和帖子 调用用于编辑问题标题和帖子
    的方法。
  • Like a Feed 元素 调用一个方法来 like a feed 元素。
  • 为源元素添加书签 调用方法为源元素
    添加书签。
  • 共享源元素(V39.0 之前的版本)
    调用方法以共享源元素。
  • 共享源元素(在版本 39.0 及更高版本中)
    调用方法以共享源元素。
  • 发送私信 调用用于发送私信
    的方法。
  • 发表评论
    调用方法发表评论
  • 发布带有提及
    的评论 拨打电话或使用 ConnectApiHelper 存储库发布带有提及的评论。
  • 使用现有文件
    发表评论 拨打电话以使用已上传的文件发表评论。
  • 使用新文件发布注释 调用方法以使用新文件
    发布注释。
  • 发布带有内联图像
    的富文本注释 进行调用或使用 ConnectApiHelper 存储库发布包含已上传的内联图像的评论。
  • 发布带有代码块的富文本源注释 调用方法以发布带有代码块
    的注释。
  • 编辑注释 调用方法以编辑注释
  • 遵循记录
    调用一种方法来跟踪记录。
  • 取消关注记录 调用停止关注记录
    的方法。
  • 获取存储库
    调用方法获取存储库。
  • 获取存储库
    调用方法获取所有存储库。
  • 获取允许的项类型 调用方法以获取允许的项类型
  • 获取预览 调用方法以获取所有支持的预览
    格式及其各自的 URL。
  • 获取文件预览 调用方法以获取文件预览
  • 获取存储库文件夹项 调用方法以获取存储库文件夹项
    的集合。
  • 获取存储库文件夹 调用方法获取存储库文件夹
  • 获取没有权限信息
    的存储库文件 调用方法以获取没有权限信息的存储库文件。
  • 获取包含权限信息
    的存储库文件 调用方法以获取包含权限信息的存储库文件。
  • 创建不带内容的存储库文件(仅限元数据) 调用方法以在 Google Drive 存储库文件夹中创建不带二进制内容(仅元数据)
    的文件。
  • 创建包含内容
    的存储库文件 调用方法以在 Google Drive 存储库文件夹中创建包含二进制内容的文件。
  • 更新不带内容的存储库文件(仅限元数据)
    调用方法更新存储库文件的元数据。
  • 使用内容
    更新存储库文件 调用方法以使用内容更新存储库文件。
  • 获取身份验证 URL 调用方法以获取身份验证 URL。

从 Feed 中获取 Feed 元素

调用方法以从源中获取源元素。调用 getFeedElementsFromFeed(communityId, feedType, subjectId) 获取 上下文用户新闻中的源元素的第一页 饲料。

ConnectApi.FeedElementPage fep = ConnectApi.ChatterFeeds.getFeedElementsFromFeed(Network.getNetworkId(), ConnectApi.FeedType.News, 'me');

该方法重载, 这意味着方法名称具有许多不同的签名。签名是 方法及其参数的顺序。getFeedElementsFromFeed

每个签名都允许您发送不同的输入。 例如,一个签名可以指定 Feed 类型和主题 ID。另一个签名 可以具有这些参数和一个附加参数来指定 要为每个 Feed 元素返回的注释。

SiW(硅瓦)

每个签名都操作于 某些 Feed 类型。使用对 操作的签名来获取组源,因为组是一条记录 类型。ConnectApi.FeedType.Record

从其他用户的 Feed 中获取 Feed 元素

调用方法以从其他用户的源中获取源元素。致电 从其他用户的 Feed 元素获取第一页 饲料。

getFeedElementsFromFeed(communityId, feedType, subjectId)

ConnectApi.FeedElementPage fep = ConnectApi.ChatterFeeds.getFeedElementsFromFeed(Network.getNetworkId(), ConnectApi.FeedType.UserProfile, '005R0000000HwMA');

这 示例调用相同的方法从另一个用户的 记录 饲料。

ConnectApi.FeedElementPage fep = ConnectApi.ChatterFeeds.getFeedElementsFromFeed(Network.getNetworkId(), ConnectApi.FeedType.Record, '005R0000000HwMA');

该方法重载, 这意味着方法名称具有许多不同的签名。签名是 方法及其参数的顺序。getFeedElementsFromFeed

每个签名都允许您发送不同的输入。 例如,一个签名可以指定 Feed 类型和主题 ID。另一个签名 可以有这些参数和一个额外的参数来指定最大注释数 返回每个源元素。

从 Feed 中获取特定于网站的 Feed 元素

调用方法以显示仅包含以下源元素的用户配置文件源: 范围限定为特定 Experience Cloud 站点。具有“用户”或“组”父记录的源元素的范围限定为网站。饲料 其父元素是除“用户”或“组”以外的记录类型时,始终在所有元素中可见 网站。将来,其他父记录类型的范围可以限定为网站。

此示例调用 getFeedElementsFromFeed(communityId, feedType, subjectId, recentCommentCount, density, pageParam、pageSize、sortParam、filter) 仅获取特定于网站的 Feed 元素。

ConnectApi.FeedElementPage fep = ConnectApi.ChatterFeeds.getFeedElementsFromFeed(Network.getNetworkId(), ConnectApi.FeedType.UserProfile, 'me', 3, ConnectApi.FeedDensity.FewerUpdates, null, null, ConnectApi.FeedSortOrder.LastModifiedDateDesc, ConnectApi.FeedFilter.CommunityScoped);

发布源元素

进行调用以发布 Feed 元素。调用 postFeedElement(communityId, subjectId, feedElementType, text) 来发布一个字符串 发短信。

ConnectApi.FeedElement feedElement = ConnectApi.ChatterFeeds.postFeedElement(Network.getNetworkId(), '0F9d0000000TreH', ConnectApi.FeedElementType.FeedItem, 'On vacation this week.');

这 第二个参数,subjectId 是此源元素的父元素的 ID 被发布到。该值可以是用户、组或记录的 ID,也可以是指示上下文用户的字符串。me

发布带有提及的提要元素

调用方法或使用 ConnectApiHelper 存储库发布源。您可以通过两种方式发布带有提及的提要元素。使用 ConnectApiHelper 存储库 在 GitHub 上编写一行代码,或者使用此 示例,调用 postFeedElement(communityId, feedElement)。

ConnectApi.FeedItemInput feedItemInput = new ConnectApi.FeedItemInput();
ConnectApi.MentionSegmentInput mentionSegmentInput = new ConnectApi.MentionSegmentInput();
ConnectApi.MessageBodyInput messageBodyInput = new ConnectApi.MessageBodyInput();
ConnectApi.TextSegmentInput textSegmentInput = new ConnectApi.TextSegmentInput();

messageBodyInput.messageSegments = new List<ConnectApi.MessageSegmentInput>();

mentionSegmentInput.id = '005RR000000Dme9';
messageBodyInput.messageSegments.add(mentionSegmentInput);

textSegmentInput.text = 'Could you take a look?';
messageBodyInput.messageSegments.add(textSegmentInput);

feedItemInput.body = messageBodyInput;
feedItemInput.feedElementType = ConnectApi.FeedElementType.FeedItem;
feedItemInput.subjectId = '0F9RR0000004CPw';

ConnectApi.FeedElement feedElement = ConnectApi.ChatterFeeds.postFeedElement(Network.getNetworkId(), feedItemInput);

使用现有文件发布源元素

调用方法以发布包含已上传文件的源元素。调用 postFeedElement(communityId, feedElement) 以发布包含已上传文件的源项。

// Define the FeedItemInput object to pass to postFeedElement
ConnectApi.FeedItemInput feedItemInput = new ConnectApi.FeedItemInput();
feedItemInput.subjectId = 'me';

ConnectApi.TextSegmentInput textSegmentInput = new ConnectApi.TextSegmentInput();
textSegmentInput.text = 'Would you please review these docs?';

// The MessageBodyInput object holds the text in the post
ConnectApi.MessageBodyInput messageBodyInput = new ConnectApi.MessageBodyInput();
messageBodyInput.messageSegments = new List<ConnectApi.MessageSegmentInput>();
messageBodyInput.messageSegments.add(textSegmentInput);
feedItemInput.body = messageBodyInput;

// The FeedElementCapabilitiesInput object holds the capabilities of the feed item.
// For this feed item, we define a files capability to hold the file(s).

List<String> fileIds = new List<String>();
fileIds.add('069xx00000000QO');
fileIds.add('069xx00000000QT');
fileIds.add('069xx00000000Qn');
fileIds.add('069xx00000000Qi');
fileIds.add('069xx00000000Qd');

ConnectApi.FilesCapabilityInput filesInput = new ConnectApi.FilesCapabilityInput();
filesInput.items = new List<ConnectApi.FileIdInput>();

for (String fileId : fileIds) {
    ConnectApi.FileIdInput idInput = new ConnectApi.FileIdInput();
    idInput.id = fileId;
    filesInput.items.add(idInput);
}

ConnectApi.FeedElementCapabilitiesInput feedElementCapabilitiesInput = new ConnectApi.FeedElementCapabilitiesInput();
feedElementCapabilitiesInput.files = filesInput;

feedItemInput.capabilities = feedElementCapabilitiesInput;

// Post the feed item. 
ConnectApi.FeedElement feedElement = ConnectApi.ChatterFeeds.postFeedElement(Network.getNetworkId(), feedItemInput);

发布带有内嵌图像的富文本源元素

调用方法或使用 ConnectApiHelper 存储库发布带有 已上传的内嵌图像。您可以通过两种方式发布带有内嵌图片和提及的富文本 Feed 元素。使用 GitHub 上的 ConnectApiHelper 存储库编写单行 代码,或者使用此示例,该示例调用 postFeedElement(communityId, feedElement)。在此示例中,图像文件是具有 已上传到 Salesforce。该帖子还包括文本和 提到。

String communityId = null;
String imageId = '069D00000001INA'; 
String mentionedUserId = '005D0000001QNpr'; 
String targetUserOrGroupOrRecordId  = '005D0000001Gif0';
ConnectApi.FeedItemInput input = new ConnectApi.FeedItemInput();
input.subjectId = targetUserOrGroupOrRecordId;
input.feedElementType = ConnectApi.FeedElementType.FeedItem;

ConnectApi.MessageBodyInput messageInput = new ConnectApi.MessageBodyInput();
ConnectApi.TextSegmentInput textSegment;
ConnectApi.MentionSegmentInput mentionSegment;
ConnectApi.MarkupBeginSegmentInput markupBeginSegment;
ConnectApi.MarkupEndSegmentInput markupEndSegment;
ConnectApi.InlineImageSegmentInput inlineImageSegment;

messageInput.messageSegments = new List<ConnectApi.MessageSegmentInput>();

markupBeginSegment = new ConnectApi.MarkupBeginSegmentInput();
markupBeginSegment.markupType = ConnectApi.MarkupType.Bold;
messageInput.messageSegments.add(markupBeginSegment);

textSegment = new ConnectApi.TextSegmentInput();
textSegment.text = 'Hello ';
messageInput.messageSegments.add(textSegment);

mentionSegment = new ConnectApi.MentionSegmentInput();
mentionSegment.id = mentionedUserId;
messageInput.messageSegments.add(mentionSegment);

textSegment = new ConnectApi.TextSegmentInput();
textSegment.text = '!';
messageInput.messageSegments.add(textSegment);

markupEndSegment = new ConnectApi.MarkupEndSegmentInput();
markupEndSegment.markupType = ConnectApi.MarkupType.Bold;
messageInput.messageSegments.add(markupEndSegment);

inlineImageSegment = new ConnectApi.InlineImageSegmentInput();
inlineImageSegment.altText = 'image one';
inlineImageSegment.fileId = imageId;
messageInput.messageSegments.add(inlineImageSegment);

input.body = messageInput;

ConnectApi.ChatterFeeds.postFeedElement(communityId, input);

发布带有代码块的富文本源元素

调用方法以发布带有代码块的源元素。调用 postFeedElement(communityId, feedElement) 发布带有代码块的源项。

String communityId = null;
String targetUserOrGroupOrRecordId  = 'me';
String codeSnippet = '<html>\n\t<body>\n\t\tHello, world!\n\t</body>\n</html>';
ConnectApi.FeedItemInput input = new ConnectApi.FeedItemInput();
input.subjectId = targetUserOrGroupOrRecordId;
input.feedElementType = ConnectApi.FeedElementType.FeedItem;

ConnectApi.MessageBodyInput messageInput = new ConnectApi.MessageBodyInput();
ConnectApi.TextSegmentInput textSegment;
ConnectApi.MarkupBeginSegmentInput markupBeginSegment;
ConnectApi.MarkupEndSegmentInput markupEndSegment;

messageInput.messageSegments = new List<ConnectApi.MessageSegmentInput>();

markupBeginSegment = new ConnectApi.MarkupBeginSegmentInput();
markupBeginSegment.markupType = ConnectApi.MarkupType.Code;
messageInput.messageSegments.add(markupBeginSegment);

textSegment = new ConnectApi.TextSegmentInput();
textSegment.text = codeSnippet;
messageInput.messageSegments.add(textSegment);

markupEndSegment = new ConnectApi.MarkupEndSegmentInput();
markupEndSegment.markupType = ConnectApi.MarkupType.Code;
messageInput.messageSegments.add(markupEndSegment);

input.body = messageInput;

ConnectApi.ChatterFeeds.postFeedElement(communityId, input);

发布带有新文件(二进制)附件的源元素

调用方法以使用新文件发布源元素。

重要

在版本 36.0 及更高版本中,无法发布源 元素替换为同一调用中的新文件。将文件上传到 Salesforce 首先,然后在发布源时指定现有文件 元素。此示例调用 postFeedElement(communityId, feedElement, feedElementFileUpload) 发布 Feed 项 使用新文件(二进制) 附件。

ConnectApi.FeedItemInput input = new ConnectApi.FeedItemInput();
input.subjectId = 'me';

ConnectApi.ContentCapabilityInput contentInput = new ConnectApi.ContentCapabilityInput();
contentInput.title = 'Title';

ConnectApi.FeedElementCapabilitiesInput capabilities = new ConnectApi.FeedElementCapabilitiesInput();
capabilities.content = contentInput;

input.capabilities = capabilities;

String text = 'These are the contents of the new file.';
Blob myBlob = Blob.valueOf(text);
ConnectApi.BinaryInput binInput = new ConnectApi.BinaryInput(myBlob, 'text/plain', 'fileName');

ConnectApi.ChatterFeeds.postFeedElement(Network.getNetworkId(), input, binInput);

发布一批 Feed 元素

使用触发器调用方法以批量发布到帐户的源。此触发器调用 postFeedElementBatch(communityId, feedElements) 到 批量发布到新插入的源 帐户。

trigger postFeedItemToAccount on Account (after insert) {
    Account[] accounts = Trigger.new;
    
    // Bulk post to the account feeds.

    List<ConnectApi.BatchInput> batchInputs = new List<ConnectApi.BatchInput>();

    for (Account a : accounts) {
        ConnectApi.FeedItemInput input = new ConnectApi.FeedItemInput();

        input.subjectId = a.id;
        
        ConnectApi.MessageBodyInput body = new ConnectApi.MessageBodyInput();
        body.messageSegments = new List<ConnectApi.MessageSegmentInput>();

        ConnectApi.TextSegmentInput textSegment = new ConnectApi.TextSegmentInput();
        textSegment.text = 'Let\'s win the ' + a.name + ' account.';

        body.messageSegments.add(textSegment);
        input.body = body;

        ConnectApi.BatchInput batchInput = new ConnectApi.BatchInput(input);
        batchInputs.add(batchInput);
    }

    ConnectApi.ChatterFeeds.postFeedElementBatch(Network.getNetworkId(), batchInputs);
}

使用新的(二进制)文件发布一批源元素

使用触发器调用方法将新文件批量发布到 帐户。

重要

此示例在版本 32.0–35.0 中有效。在 版本 36.0 及更高版本中,您无法在 同样的电话。首先将文件上传到 Salesforce,然后指定上传的 文件。此触发器调用 postFeedElementBatch(communityId, feedElements) 以批量发布到 新插入的帐户的 Feed。每个帖子都有一个新文件(二进制) 附件。

trigger postFeedItemToAccountWithBinary on Account (after insert) {
    Account[] accounts = Trigger.new;
    
    // Bulk post to the account feeds.

    List<ConnectApi.BatchInput> batchInputs = new List<ConnectApi.BatchInput>();

    for (Account a : accounts) {
        ConnectApi.FeedItemInput input = new ConnectApi.FeedItemInput();

        input.subjectId = a.id;
        
        ConnectApi.MessageBodyInput body = new ConnectApi.MessageBodyInput();
        body.messageSegments = new List<ConnectApi.MessageSegmentInput>();

        ConnectApi.TextSegmentInput textSegment = new ConnectApi.TextSegmentInput();
        textSegment.text = 'Let\'s win the ' + a.name + ' account.';

        body.messageSegments.add(textSegment);
        input.body = body;

        ConnectApi.ContentCapabilityInput contentInput = new ConnectApi.ContentCapabilityInput();
        contentInput.title = 'Title';

        ConnectApi.FeedElementCapabilitiesInput capabilities = new ConnectApi.FeedElementCapabilitiesInput();
        capabilities.content = contentInput;

        input.capabilities = capabilities;

        String text = 'We are words in a file.';
        Blob myBlob = Blob.valueOf(text);
        ConnectApi.BinaryInput binInput = new ConnectApi.BinaryInput(myBlob, 'text/plain', 'fileName');

        ConnectApi.BatchInput batchInput = new ConnectApi.BatchInput(input, binInput);

        batchInputs.add(batchInput);
    }

    ConnectApi.ChatterFeeds.postFeedElementBatch(Network.getNetworkId(), batchInputs);

定义操作链接并使用源元素发布

在操作链接组中创建一个操作链接,将该操作链接组与 Feed 项目,然后发布 Feed 项目。

当用户单击操作链接时,操作链接将请求连接 REST API resource ,用于发布源 项目添加到用户的 Feed。用户单击操作链接并成功执行后, 其状态更改为“成功”,并且源项 UI 已更新。/chatter/feed-elements

刷新用户的 Feed 以查看新帖子。

这个简单示例向您展示了如何使用操作链接来调用 Salesforce 资源。

将操作链接视为 Feed 项上的按钮。就像一个按钮,一个动作 链接定义包括标签 ()。一个动作 链接组定义还包括其他属性,如 URL ()、HTTP 方法 () 和 可选请求正文 () 和 HTTP 标头 ().labelKeyactionUrlmethodrequestBodyheaders

当用户单击此操作链接时, 向 Connect REST API 资源发出 HTTP POST 请求,该资源将源项发布到 喋喋不休。该属性保留请求 资源的正文,包括 新的源项。在此示例中,新的源项仅包含文本,但可以 包括其他功能,例如文件附件、轮询甚至操作 链接。requestBodyactionUrl

就像单选按钮一样,操作链接必须嵌套在一个组中。操作链接 在组内共享组的属性,并且是互斥的(可以单击 一个组中只有一个操作链接)。即使只定义一个操作链接,它也必须 操作链接组的一部分。

此示例调用 ConnectApi.ActionLinks.createActionLinkGroupDefinition(communityId, actionLinkGroup) 创建操作链接组定义。

它 保存该调用中的操作链接组 ID,并将其与 调用 ConnectApi.ChatterFeeds.postFeedElement(communityId, feedElement)。

若要使用此代码,请将 OAuth 值替换为你自己的值 Salesforce组织。此外,请验证是否在将来。在 法典。expirationDate

ConnectApi.ActionLinkGroupDefinitionInput actionLinkGroupDefinitionInput = new ConnectApi.ActionLinkGroupDefinitionInput();
ConnectApi.ActionLinkDefinitionInput actionLinkDefinitionInput = new ConnectApi.ActionLinkDefinitionInput();
ConnectApi.RequestHeaderInput requestHeaderInput1 = new ConnectApi.RequestHeaderInput();
ConnectApi.RequestHeaderInput requestHeaderInput2 = new ConnectApi.RequestHeaderInput();

// Create the action link group definition.
actionLinkGroupDefinitionInput.actionLinks = New List<ConnectApi.ActionLinkDefinitionInput>();
actionLinkGroupDefinitionInput.executionsAllowed = ConnectApi.ActionLinkExecutionsAllowed.OncePerUser;
actionLinkGroupDefinitionInput.category = ConnectApi.PlatformActionGroupCategory.Primary;
// To Do: Verify that the date is in the future.
// Action link groups are removed from feed elements on the expiration date.
datetime myDate = datetime.newInstance(2016, 3, 1);
actionLinkGroupDefinitionInput.expirationDate = myDate;

// Create the action link definition.
actionLinkDefinitionInput.actionType = ConnectApi.ActionLinkType.Api;
actionLinkDefinitionInput.actionUrl = '/services/data/v33.0/chatter/feed-elements';
actionLinkDefinitionInput.headers = new List<ConnectApi.RequestHeaderInput>();
actionLinkDefinitionInput.labelKey = 'Post';
actionLinkDefinitionInput.method = ConnectApi.HttpRequestMethod.HttpPost;
actionLinkDefinitionInput.requestBody = '{\"subjectId\": \"me\",\"feedElementType\": \"FeedItem\",\"body\": {\"messageSegments\": [{\"type\": \"Text\",\"text\": \"This is a test post created via an API action link.\"}]}}';
actionLinkDefinitionInput.requiresConfirmation = true;

// To Do: Substitute an OAuth value for your Salesforce org. 
requestHeaderInput1.name = 'Authorization';
requestHeaderInput1.value = 'OAuth 00DD00000007WNP!ARsAQCwoeV0zzAV847FTl4zF.85w.EwsPbUgXR4SAjsp';
actionLinkDefinitionInput.headers.add(requestHeaderInput1);

requestHeaderInput2.name = 'Content-Type';
requestHeaderInput2.value = 'application/json';
actionLinkDefinitionInput.headers.add(requestHeaderInput2);

// Add the action link definition to the action link group definition.
actionLinkGroupDefinitionInput.actionLinks.add(actionLinkDefinitionInput);

// Instantiate the action link group definition.
ConnectApi.ActionLinkGroupDefinition actionLinkGroupDefinition = ConnectApi.ActionLinks.createActionLinkGroupDefinition(Network.getNetworkId(), actionLinkGroupDefinitionInput);

ConnectApi.FeedItemInput feedItemInput = new ConnectApi.FeedItemInput();
ConnectApi.FeedElementCapabilitiesInput feedElementCapabilitiesInput = new ConnectApi.FeedElementCapabilitiesInput();
ConnectApi.AssociatedActionsCapabilityInput associatedActionsCapabilityInput = new ConnectApi.AssociatedActionsCapabilityInput();
ConnectApi.MessageBodyInput messageBodyInput = new ConnectApi.MessageBodyInput();
ConnectApi.TextSegmentInput textSegmentInput = new ConnectApi.TextSegmentInput();

// Set the properties of the feedItemInput object.
feedItemInput.body = messageBodyInput;
feedItemInput.capabilities = feedElementCapabilitiesInput;
feedItemInput.subjectId = 'me';

// Create the text for the post.
messageBodyInput.messageSegments = new List<ConnectApi.MessageSegmentInput>();
textSegmentInput.text = 'Click to post a feed item.';  
messageBodyInput.messageSegments.add(textSegmentInput);


// The feedElementCapabilitiesInput object holds the capabilities of the feed item.
// Define an associated actions capability to hold the action link group.
// The action link group ID is returned from the call to create the action link group definition. 
feedElementCapabilitiesInput.associatedActions = associatedActionsCapabilityInput;
associatedActionsCapabilityInput.actionLinkGroupIds = new List<String>();
associatedActionsCapabilityInput.actionLinkGroupIds.add(actionLinkGroupDefinition.id);

// Post the feed item. 
ConnectApi.FeedElement feedElement = ConnectApi.ChatterFeeds.postFeedElement(Network.getNetworkId(), feedItemInput);

注意

如果 开机自检失败,请检查 OAuth ID。

在模板中定义操作链接并使用 Feed 元素发布

创建操作链接和操作链接组,并从 一个模板。

此示例创建与示例 Define an Action Link and Post with a Feed Element 相同的操作链接和操作链接组,但此示例 从模板实例化操作链接组。

步骤 1:创建操作链接模板

  1. 在“设置”中,输入“快速” “查找”框,然后选择“操作链接模板”。Action Link Templates
  2. 在新的操作链接组模板中使用以下值:田价值名字文档示例开发商名称Doc_Example类别主要操作允许执行每个用户一次
  3. 在新的操作链接模板中使用以下值:田价值操作链接组模板文档示例操作类型应用程序接口操作 URL/services/data/{!Bindings.ApiVersion}/聊天/馈送元素用户可视性每个人都可以看到HTTP 请求正文{ “subjectId”:“{!Bindings.SubjectId}“, ”feedElementType“:”FeedItem“, “body”:{ “messageSegments”:[ { “type”:“Text”, “text”:“{!Bindings.Text}“ } ] } }HTTP 标头内容类型:application/json位置0标签键发布HTTP 方法发布
  4. 返回到“操作链接组模板”,然后选择“已发布”。 点击保存

步骤 2:实例化操作链接组,将其与源项关联,然后发布 它

此示例调用 ConnectApi.ActionLinks.createActionLinkGroupDefinition(communityId, actionLinkGroup) 创建操作链接组定义。

它调用 ConnectApi.ChatterFeeds.postFeedElement(communityId, feedElement) 将操作链接组与源项关联并发布。

// Get the action link group template Id.
ActionLinkGroupTemplate template = [SELECT Id FROM ActionLinkGroupTemplate WHERE DeveloperName='Doc_Example'];

// Add binding name-value pairs to a map.
// The names are defined in the action link template(s) associated with the action link group template.
// Get them from Setup UI or SOQL.
Map<String, String> bindingMap = new Map<String, String>();
bindingMap.put('ApiVersion', 'v33.0');
bindingMap.put('Text', 'This post was created by an API action link.');
bindingMap.put('SubjectId', 'me');

// Create ActionLinkTemplateBindingInput objects from the map elements.
List<ConnectApi.ActionLinkTemplateBindingInput> bindingInputs = new List<ConnectApi.ActionLinkTemplateBindingInput>();

for (String key : bindingMap.keySet()) {
    ConnectApi.ActionLinkTemplateBindingInput bindingInput = new ConnectApi.ActionLinkTemplateBindingInput();
    bindingInput.key = key;
    bindingInput.value = bindingMap.get(key);
    bindingInputs.add(bindingInput);
}

// Set the template Id and template binding values in the action link group definition.
ConnectApi.ActionLinkGroupDefinitionInput actionLinkGroupDefinitionInput = new ConnectApi.ActionLinkGroupDefinitionInput();
actionLinkGroupDefinitionInput.templateId = template.id;
actionLinkGroupDefinitionInput.templateBindings = bindingInputs;

// Instantiate the action link group definition.
ConnectApi.ActionLinkGroupDefinition actionLinkGroupDefinition =
 ConnectApi.ActionLinks.createActionLinkGroupDefinition(Network.getNetworkId(), actionLinkGroupDefinitionInput);

ConnectApi.FeedItemInput feedItemInput = new ConnectApi.FeedItemInput();
ConnectApi.FeedElementCapabilitiesInput feedElementCapabilitiesInput = new ConnectApi.FeedElementCapabilitiesInput();
ConnectApi.AssociatedActionsCapabilityInput associatedActionsCapabilityInput = new ConnectApi.AssociatedActionsCapabilityInput();
ConnectApi.MessageBodyInput messageBodyInput = new ConnectApi.MessageBodyInput();
ConnectApi.TextSegmentInput textSegmentInput = new ConnectApi.TextSegmentInput();

// Define the FeedItemInput object to pass to postFeedElement
feedItemInput.body = messageBodyInput;
feedItemInput.capabilities = feedElementCapabilitiesInput;
feedItemInput.subjectId = 'me';

// The MessageBodyInput object holds the text in the post
messageBodyInput.messageSegments = new List<ConnectApi.MessageSegmentInput>();

textSegmentInput.text = 'Click to post a feed item.';
messageBodyInput.messageSegments.add(textSegmentInput);


// The FeedElementCapabilitiesInput object holds the capabilities of the feed item.
// For this feed item, we define an associated actions capability to hold the action link group.
// The action link group ID is returned from the call to create the action link group definition. 
feedElementCapabilitiesInput.associatedActions = associatedActionsCapabilityInput;
associatedActionsCapabilityInput.actionLinkGroupIds = new List<String>();
associatedActionsCapabilityInput.actionLinkGroupIds.add(actionLinkGroupDefinition.id);

// Post the feed item. 
ConnectApi.FeedElement feedElement = ConnectApi.ChatterFeeds.postFeedElement(Network.getNetworkId(), feedItemInput);

编辑 Feed 元素

调用方法以编辑 Feed 元素。调用 updateFeedElement(communityId, feedElementId、feedElement) 来编辑 Feed 元素。Feed 项是 唯一可以 编辑。

String communityId = Network.getNetworkId();

// Get the last feed item created by the context user.
List<FeedItem> feedItems = [SELECT Id FROM FeedItem WHERE CreatedById = :UserInfo.getUserId() ORDER BY CreatedDate DESC];
if (feedItems.isEmpty()) {
    // Return null within anonymous apex.
    return null;
}
String feedElementId = feedItems[0].id;

ConnectApi.FeedEntityIsEditable isEditable = ConnectApi.ChatterFeeds.isFeedElementEditableByMe(communityId, feedElementId);

if (isEditable.isEditableByMe == true){
    ConnectApi.FeedItemInput feedItemInput = new ConnectApi.FeedItemInput();
    ConnectApi.MessageBodyInput messageBodyInput = new ConnectApi.MessageBodyInput();
    ConnectApi.TextSegmentInput textSegmentInput = new ConnectApi.TextSegmentInput();

    messageBodyInput.messageSegments = new List<ConnectApi.MessageSegmentInput>();

    textSegmentInput.text = 'This is my edited post.';
    messageBodyInput.messageSegments.add(textSegmentInput);

    feedItemInput.body = messageBodyInput;

    ConnectApi.FeedElement editedFeedElement = ConnectApi.ChatterFeeds.updateFeedElement(communityId, feedElementId, feedItemInput);
}

编辑问题标题和帖子

调用方法以编辑问题标题和帖子。调用 updateFeedElement(communityId, feedElementId、feedElement) 编辑问题标题和帖子。

String communityId = Network.getNetworkId();

// Get the last feed item created by the context user.
List<FeedItem> feedItems = [SELECT Id FROM FeedItem WHERE CreatedById = :UserInfo.getUserId() ORDER BY CreatedDate DESC];
if (feedItems.isEmpty()) {
    // Return null within anonymous apex.
    return null;
}
String feedElementId = feedItems[0].id;

ConnectApi.FeedEntityIsEditable isEditable = ConnectApi.ChatterFeeds.isFeedElementEditableByMe(communityId, feedElementId);

if (isEditable.isEditableByMe == true){

    ConnectApi.FeedItemInput feedItemInput = new ConnectApi.FeedItemInput();
    ConnectApi.FeedElementCapabilitiesInput feedElementCapabilitiesInput = new ConnectApi.FeedElementCapabilitiesInput();
    ConnectApi.QuestionAndAnswersCapabilityInput questionAndAnswersCapabilityInput = new ConnectApi.QuestionAndAnswersCapabilityInput();
    ConnectApi.MessageBodyInput messageBodyInput = new ConnectApi.MessageBodyInput();
    ConnectApi.TextSegmentInput textSegmentInput = new ConnectApi.TextSegmentInput();

    messageBodyInput.messageSegments = new List<ConnectApi.MessageSegmentInput>();

    textSegmentInput.text = 'This is my edited question.';
    messageBodyInput.messageSegments.add(textSegmentInput);

    feedItemInput.body = messageBodyInput;
    feedItemInput.capabilities = feedElementCapabilitiesInput;

    feedElementCapabilitiesInput.questionAndAnswers = questionAndAnswersCapabilityInput;
    questionAndAnswersCapabilityInput.questionTitle = 'Where is my edited question?';

    ConnectApi.FeedElement editedFeedElement = ConnectApi.ChatterFeeds.updateFeedElement(communityId, feedElementId, feedItemInput);
}

像饲料元素一样

调用一个方法来喜欢一个源元素。调用 likeFeedElement(communityId, feedElementId) 来点赞 Feed 元素。

ConnectApi.ChatterLike chatterLike = ConnectApi.ChatterFeeds.likeFeedElement(null, '0D5D0000000KuGh');

为 Feed 元素添加书签

调用方法为源元素添加书签。调用 updateFeedElementBookmarks(communityId, feedElementId, isBookmarkedByCurrentUser) 为提要添加书签 元素。

ConnectApi.BookmarksCapability bookmark = ConnectApi.ChatterFeeds.updateFeedElementBookmarks(null, '0D5D0000000KuGh', true);

共享源元素(版本 39.0 之前)

调用方法以共享源元素。

重要

在 API 版本 39.0 及更高版本中,不受支持。请参阅共享 Feed 元素(在版本 39.0 及更高版本中)。shareFeedElement(communityId, subjectId, feedElementType, originalFeedElementId)调用 shareFeedElement(communityId, subjectId, feedElementType, originalFeedElementId)来共享源项(这是一种 进料元件)与 群。

ConnectApi.ChatterLike chatterLike = ConnectApi.ChatterFeeds.likeFeedElement(null, '0D5D0000000KuGh');

共享源元素(在版本 39.0 及更高版本中)

调用方法以共享源元素。调用 postFeedElement(communityId, feedElement) 共享源 元素。

// Define the FeedItemInput object to pass to postFeedElement
ConnectApi.FeedItemInput feedItemInput = new ConnectApi.FeedItemInput();
feedItemInput.subjectId = 'me';
ConnectApi.TextSegmentInput textSegmentInput = new ConnectApi.TextSegmentInput();
textSegmentInput.text = 'Look at this post I'm sharing.';
// The MessageBodyInput object holds the text in the post
ConnectApi.MessageBodyInput messageBodyInput = new ConnectApi.MessageBodyInput();
messageBodyInput.messageSegments = new List<ConnectApi.MessageSegmentInput>();
messageBodyInput.messageSegments.add(textSegmentInput);
feedItemInput.body = messageBodyInput;

ConnectApi.FeedEntityShareCapabilityInput shareInput = new ConnectApi.FeedEntityShareCapabilityInput();
shareInput.feedEntityId = '0D5R0000000SEbc';
ConnectApi.FeedElementCapabilitiesInput feedElementCapabilitiesInput = new
ConnectApi.FeedElementCapabilitiesInput();
feedElementCapabilitiesInput.feedEntityShare = shareInput;
feedItemInput.capabilities = feedElementCapabilitiesInput;
// Post the feed item.
ConnectApi.FeedElement feedElement =
ConnectApi.ChatterFeeds.postFeedElement(Network.getNetworkId(), feedItemInput);

发送私信

调用方法以发送直接消息。调用 postFeedElement(communityId, feedElement) 向两个人发送直接消息。

// Define the FeedItemInput object to pass to postFeedElement
ConnectApi.FeedItemInput feedItemInput = new ConnectApi.FeedItemInput();
 
ConnectApi.TextSegmentInput textSegmentInput = new ConnectApi.TextSegmentInput();
textSegmentInput.text = 'Thanks for attending my presentation test run this morning. Send me any feedback.';
 
// The MessageBodyInput object holds the text in the post
ConnectApi.MessageBodyInput messageBodyInput = new ConnectApi.MessageBodyInput();
messageBodyInput.messageSegments = new List<ConnectApi.MessageSegmentInput>();
messageBodyInput.messageSegments.add(textSegmentInput);
feedItemInput.body = messageBodyInput;
 
// The FeedElementCapabilitiesInput object holds the capabilities of the feed item.
// For this feed item, we define a direct message capability to hold the member(s) and the subject.
 
List<String> memberIds = new List<String>();
memberIds.add('005B00000016OUQ');
memberIds.add('005B0000001rIN6');
 
ConnectApi.DirectMessageCapabilityInput dmInput = new ConnectApi.DirectMessageCapabilityInput();
dmInput.subject = 'Thank you!';
dmInput.membersToAdd = memberIds;
 
ConnectApi.FeedElementCapabilitiesInput feedElementCapabilitiesInput = new ConnectApi.FeedElementCapabilitiesInput();
feedElementCapabilitiesInput.directMessage = dmInput;
 
feedItemInput.capabilities = feedElementCapabilitiesInput;
 
// Post the feed item. 
ConnectApi.FeedElement feedElement = ConnectApi.ChatterFeeds.postFeedElement(Network.getNetworkId(), feedItemInput);

发表评论

调用方法以发表评论。调用 postCommentToFeedElement(communityId, feedElementId, text) 以发布 对 Feed 的纯文本注释 元素。

ConnectApi.Comment comment = ConnectApi.ChatterFeeds.postCommentToFeedElement(null, '0D5D0000000KuGh', 'I agree with the proposal.' );

发表评论并提及

调用或使用 ConnectApiHelper 存储库发布带有 提到。您可以通过两种方式发布带有提及的评论。使用 GitHub 上的 ConnectApiHelper 存储库编写单行 的代码,或者使用此示例,该示例调用 postCommentToFeedElement(communityId, feedElementId, comment, feedElementFileUpload)。

String communityId = null;
String feedElementId = '0D5D0000000KtW3';

ConnectApi.CommentInput commentInput = new ConnectApi.CommentInput();
ConnectApi.MentionSegmentInput mentionSegmentInput = new ConnectApi.MentionSegmentInput();
ConnectApi.MessageBodyInput messageBodyInput = new ConnectApi.MessageBodyInput();
ConnectApi.TextSegmentInput textSegmentInput = new ConnectApi.TextSegmentInput();

messageBodyInput.messageSegments = new List<ConnectApi.MessageSegmentInput>();

textSegmentInput.text = 'Does anyone in this group have an idea? ';
messageBodyInput.messageSegments.add(textSegmentInput);

mentionSegmentInput.id = '005D00000000oOT';
messageBodyInput.messageSegments.add(mentionSegmentInput);

commentInput.body = messageBodyInput;

ConnectApi.Comment commentRep = ConnectApi.ChatterFeeds.postCommentToFeedElement(communityId, feedElementId, commentInput, null);

使用现有文件发表评论

拨打电话以使用已上传的文件发表评论。要发表评论并附加现有文件(已上传到 Salesforce) 添加到评论中,创建一个对象以传递给 postCommentToFeedElement(communityId, feedElementId, 评论 feedElementFileUpload)。

ConnectApi.CommentInput

String feedElementId = '0D5D0000000KtW3';

ConnectApi.CommentInput commentInput = new ConnectApi.CommentInput();

ConnectApi.MessageBodyInput messageBodyInput = new ConnectApi.MessageBodyInput();
ConnectApi.TextSegmentInput textSegmentInput = new ConnectApi.TextSegmentInput();

textSegmentInput.text = 'I attached this file from Salesforce Files.';

messageBodyInput.messageSegments = new List<ConnectApi.MessageSegmentInput>();
messageBodyInput.messageSegments.add(textSegmentInput);
commentInput.body = messageBodyInput;

ConnectApi.CommentCapabilitiesInput commentCapabilitiesInput = new ConnectApi.CommentCapabilitiesInput();
ConnectApi.ContentCapabilityInput contentCapabilityInput = new ConnectApi.ContentCapabilityInput();

commentCapabilitiesInput.content = contentCapabilityInput;
contentCapabilityInput.contentDocumentId = '069D00000001rNJ';

commentInput.capabilities = commentCapabilitiesInput;

ConnectApi.Comment commentRep = ConnectApi.ChatterFeeds.postCommentToFeedElement(Network.getNetworkId(), feedElementId, commentInput, null);

使用新文件发表评论

调用方法以使用新文件发布注释。要发布评论并上传新文件并将其附加到评论中,请创建一个对象和一个要传递给 postCommentToFeedElement(communityId, feedElementId, comment, feedElementFileUpload) 方法。

ConnectApi.CommentInputConnectApi.BinaryInput

String feedElementId = '0D5D0000000KtW3';

ConnectApi.CommentInput commentInput = new ConnectApi.CommentInput();

ConnectApi.MessageBodyInput messageBodyInput = new ConnectApi.MessageBodyInput();
ConnectApi.TextSegmentInput textSegmentInput = new ConnectApi.TextSegmentInput();

textSegmentInput.text = 'Enjoy this new file.';

messageBodyInput.messageSegments = new List<ConnectApi.MessageSegmentInput>();
messageBodyInput.messageSegments.add(textSegmentInput);
commentInput.body = messageBodyInput;

ConnectApi.CommentCapabilitiesInput commentCapabilitiesInput = new ConnectApi.CommentCapabilitiesInput();
ConnectApi.ContentCapabilityInput contentCapabilityInput = new ConnectApi.ContentCapabilityInput();

commentCapabilitiesInput.content = contentCapabilityInput;
contentCapabilityInput.title = 'Title';

commentInput.capabilities = commentCapabilitiesInput;

String text = 'These are the contents of the new file.';
Blob myBlob = Blob.valueOf(text);
ConnectApi.BinaryInput binInput = new ConnectApi.BinaryInput(myBlob, 'text/plain', 'fileName');

ConnectApi.Comment commentRep = ConnectApi.ChatterFeeds.postCommentToFeedElement(Network.getNetworkId(), feedElementId, commentInput, binInput);

发布带有内嵌图像的富文本评论

进行调用或使用 ConnectApiHelper 存储库发布评论,其中包含 上传的内嵌图像。您可以通过两种方式发布带有内嵌图像和提及的富文本评论。使用 GitHub 上的 ConnectApiHelper 存储库编写单行 的代码,或者使用此示例,该示例调用 postCommentToFeedElement(communityId, feedElementId, comment, feedElementFileUpload)。在此示例中,图像文件已存在 已上传到的内容 Salesforce的。

String communityId = null;
String feedElementId = '0D5R0000000SBEr';
String imageId = '069R00000000IgQ';
String mentionedUserId = '005R0000000DiMz'; 

ConnectApi.CommentInput input = new ConnectApi.CommentInput();
ConnectApi.MessageBodyInput messageInput = new ConnectApi.MessageBodyInput();
ConnectApi.TextSegmentInput textSegment;
ConnectApi.MentionSegmentInput mentionSegment;
ConnectApi.MarkupBeginSegmentInput markupBeginSegment;
ConnectApi.MarkupEndSegmentInput markupEndSegment;
ConnectApi.InlineImageSegmentInput inlineImageSegment;

messageInput.messageSegments = new List<ConnectApi.MessageSegmentInput>();


markupBeginSegment = new ConnectApi.MarkupBeginSegmentInput();
markupBeginSegment.markupType = ConnectApi.MarkupType.Bold;
messageInput.messageSegments.add(markupBeginSegment);

textSegment = new ConnectApi.TextSegmentInput();
textSegment.text = 'Hello ';
messageInput.messageSegments.add(textSegment);

mentionSegment = new ConnectApi.MentionSegmentInput();
mentionSegment.id = mentionedUserId;
messageInput.messageSegments.add(mentionSegment);

textSegment = new ConnectApi.TextSegmentInput();
textSegment.text = '!';
messageInput.messageSegments.add(textSegment);

markupEndSegment = new ConnectApi.MarkupEndSegmentInput();
markupEndSegment.markupType = ConnectApi.MarkupType.Bold;
messageInput.messageSegments.add(markupEndSegment);

inlineImageSegment = new ConnectApi.InlineImageSegmentInput();
inlineImageSegment.altText = 'image one';
inlineImageSegment.fileId = imageId;
messageInput.messageSegments.add(inlineImageSegment);

input.body = messageInput;

ConnectApi.ChatterFeeds.postCommentToFeedElement(communityId, feedElementId, input, null);

发布带有代码块的富文本源注释

调用方法以使用代码块发布注释。此示例调用 postCommentToFeedElement(communityId, feedElementId, comment, feedElementFileUpload) 发布带有代码的评论 块。

String communityId = null;
String feedElementId = '0D5R0000000SBEr';
String codeSnippet = '<html>\n\t<body>\n\t\tHello, world!\n\t</body>\n</html>';

ConnectApi.CommentInput input = new ConnectApi.CommentInput();
ConnectApi.MessageBodyInput messageInput = new ConnectApi.MessageBodyInput();
ConnectApi.TextSegmentInput textSegment;
ConnectApi.MarkupBeginSegmentInput markupBeginSegment;
ConnectApi.MarkupEndSegmentInput markupEndSegment;

messageInput.messageSegments = new List<ConnectApi.MessageSegmentInput>();

markupBeginSegment = new ConnectApi.MarkupBeginSegmentInput();
markupBeginSegment.markupType = ConnectApi.MarkupType.Code;
messageInput.messageSegments.add(markupBeginSegment);

textSegment = new ConnectApi.TextSegmentInput();
textSegment.text = codeSnippet;
messageInput.messageSegments.add(textSegment);

markupEndSegment = new ConnectApi.MarkupEndSegmentInput();
markupEndSegment.markupType = ConnectApi.MarkupType.Code;
messageInput.messageSegments.add(markupEndSegment);

input.body = messageInput;

ConnectApi.ChatterFeeds.postCommentToFeedElement(communityId, feedElementId, input, null);

编辑批注

调用方法以编辑注释。调用 updateComment(communityId, commentId, comment) 来编辑 评论。

String commentId;
String communityId = Network.getNetworkId();

// Get the last feed item created by the context user.
List<FeedItem> feedItems = [SELECT Id FROM FeedItem WHERE CreatedById = :UserInfo.getUserId() ORDER BY CreatedDate DESC];
if (feedItems.isEmpty()) {
    // Return null within anonymous apex.
    return null;
}
String feedElementId = feedItems[0].id;

ConnectApi.CommentPage commentPage = ConnectApi.ChatterFeeds.getCommentsForFeedElement(communityId, feedElementId);
if (commentPage.items.isEmpty()) {
    // Return null within anonymous apex.
    return null;
}
commentId = commentPage.items[0].id;

ConnectApi.FeedEntityIsEditable isEditable = ConnectApi.ChatterFeeds.isCommentEditableByMe(communityId, commentId);

if (isEditable.isEditableByMe == true){
    ConnectApi.CommentInput commentInput = new ConnectApi.CommentInput();
    ConnectApi.MessageBodyInput messageBodyInput = new ConnectApi.MessageBodyInput();
    ConnectApi.TextSegmentInput textSegmentInput = new ConnectApi.TextSegmentInput();

    messageBodyInput.messageSegments = new List<ConnectApi.MessageSegmentInput>();

    textSegmentInput.text = 'This is my edited comment.';
    messageBodyInput.messageSegments.add(textSegmentInput);

    commentInput.body = messageBodyInput;

    ConnectApi.Comment editedComment = ConnectApi.ChatterFeeds.updateComment(communityId, commentId, commentInput);
}

关注记录

调用方法以跟踪记录。调用 follow(communityId, userId, subjectId) 来跟随 记录。

ChatterUsers.ConnectApi.Subscription subscriptionToRecord = ConnectApi.ChatterUsers.follow(null, 'me', '001RR000002G4Y0');

取消关注记录

调用方法以停止关注记录。当您关注记录(如用户)时,对 的调用将返回一个对象。若要取消关注记录,请将该对象的属性传递给 deleteSubscription(communityId, subscriptionId)。

ConnectApi.ChatterUsers.followConnectApi.Subscriptionid

ConnectApi.Chatter.deleteSubscription(null, '0E8RR0000004CnK0AU');

获取存储库

调用方法以获取存储库。调用 getRepository(repositoryId) 获取 存储 库。

final string repositoryId = '0XCxx0000000123GAA';
final ConnectApi.ContentHubRepository repository = ConnectApi.ContentHub.getRepository(repositoryId);

获取存储库

调用方法以获取所有存储库。调用 getRepositories() 以获取所有存储库并获取第一个 SharePoint 在线存储库 发现。

final string sharePointOnlineProviderType ='ContentHubSharepointOffice365';
final ConnectApi.ContentHubRepositoryCollection repositoryCollection = ConnectApi.ContentHub.getRepositories();
ConnectApi.ContentHubRepository sharePointOnlineRepository = null;
for(ConnectApi.ContentHubRepository repository : repositoryCollection.repositories){
   if(sharePointOnlineProviderType.equalsIgnoreCase(repository.providerType.type)){
      sharePointOnlineRepository = repository;
      break;
   }
}

获取允许的项目类型

调用方法以获取允许的项类型。使用 of 调用 getAllowedItemTypes(repositoryId, repositoryFolderId, filter) 以获取文件的第一个。用户可以创建的上下文 外部存储库文件夹中允许的文件 系统。

filterFilesOnlyConnectApi.ContentHubItemTypeSummary.id

final ConnectApi.ContentHubAllowedItemTypeCollection allowedItemTypesColl = ConnectApi.ContentHub.getAllowedItemTypes(repositoryId, repositoryFolderId, ConnectApi.ContentHubItemType.FilesOnly);
final List<ConnectApi.ContentHubItemTypeSummary> allowedItemTypes = allowedItemTypesColl.allowedItemTypes;
string allowedFileItemTypeId = null;
if(allowedItemTypes.size() > 0){
   ConnectApi.ContentHubItemTypeSummary allowedItemTypeSummary = allowedItemTypes.get(0);
   allowedFileItemTypeId = allowedItemTypeSummary.id;
}

获取预览

调用方法以获取所有支持的预览格式及其各自的预览格式 URL。调用 getPreviews(repositoryId, repositoryFileId) 获取所有支持的预览格式及其 各自的 URL 和演绎版数量。对于每种受支持的预览格式,我们都会显示 演绎版 URL 可用。

final String gDriveRepositoryId = '0XCxx00000000ODGAY', gDriveFileId = 'document:1-zcA1BaeoQbo2_yNFiHCcK6QJTPmOke-kHFC4TYg3rk';
final ConnectApi.FilePreviewCollection previewsCollection = ConnectApi.ContentHub.getPreviews(gDriveRepositoryId, gDriveFileId);
for(ConnectApi.FilePreview filePreview : previewsCollection.previews){
   System.debug(String.format('Preview - URL: \'\'{0}\'\', format: \'\'{1}\'\', nbr of renditions for this format: {2}', new String[]{ filePreview.url, filePreview.format.name(),String.valueOf(filePreview.previewUrls.size())}));
   for(ConnectApi.FilePreviewUrl filePreviewUrl : filePreview.previewUrls){
      System.debug('-----> Rendition URL: ' + filePreviewUrl.previewUrl);
      }
}

获取文件预览

调用方法以获取文件预览。调用 getFilePreview(repositoryId, repositoryFileId, formatType) 替换为 of 来获取 缩略图格式预览及其各自的 URL 和缩略图演绎版的数量。为 每种缩略图格式,我们都会显示每个演绎版 URL 可用。

formatTypeThumbnail

final String gDriveRepositoryId = '0XCxx00000000ODGAY', gDriveFileId = 'document:1-zcA1BaeoQbo2_yNFiHCcK6QJTPmOke-kHFC4TYg3rk';
final ConnectApi.FilePreviewCollection previewsCollection = ConnectApi.ContentHub.getPreviews(gDriveRepositoryId, gDriveFileId);
for(ConnectApi.FilePreview filePreview : previewsCollection.previews){
   System.debug(String.format('Preview - URL: \'\'{0}\'\', format: \'\'{1}\'\', nbr of renditions for this format: {2}', new String[]{ filePreview.url, filePreview.format.name(),String.valueOf(filePreview.previewUrls.size())}));
   for(ConnectApi.FilePreviewUrl filePreviewUrl : filePreview.previewUrls){
      System.debug('-----> Rendition URL: ' + filePreviewUrl.previewUrl);
      }
}

获取存储库文件夹项

调用方法以获取存储库文件夹项的集合。调用 getRepositoryFolderItems(repositoryId, repositoryFolderId) 获取 存储库文件夹中的项目集合。对于文件,我们显示文件的名称、大小、外部 URL 和下载 URL。对于文件夹,我们显示文件夹的名称、描述和外部 网址。

final String gDriveRepositoryId = '0XCxx00000000ODGAY', gDriveFolderId = 'folder:0B0lTys1KmM3sSVJ2bjIzTGFqSWs';
final ConnectApi.RepositoryFolderItemsCollection folderItemsColl = ConnectApi.ContentHub.getRepositoryFolderItems(gDriveRepositoryId,gDriveFolderId);
final List<ConnectApi.RepositoryFolderItem> folderItems = folderItemsColl.items;
System.debug('Number of items in repository folder: ' + folderItems.size());
for(ConnectApi.RepositoryFolderItem item : folderItems){
   ConnectApi.RepositoryFileSummary fileSummary = item.file;
   if(fileSummary != null){
      System.debug(String.format('File item - name: \'\'{0}\'\', size: {1}, external URL: \'\'{2}\'\', download URL: \'\'{3}\'\'', new String[]{ fileSummary.name, String.valueOf(fileSummary.contentSize), fileSummary.externalDocumentUrl, fileSummary.downloadUrl}));
      }else{
         ConnectApi.RepositoryFolderSummary folderSummary = item.folder;
         System.debug(String.format('Folder item - name: \'\'{0}\'\', description: \'\'{1}\'\'',  new String[]{ folderSummary.name, folderSummary.description}));
      }
}

获取存储库文件夹

调用方法以获取存储库文件夹。调用 getRepositoryFolder(repositoryId, repositoryFolderId) 获取仓库 文件夹。

final String gDriveRepositoryId = '0XCxx00000000ODGAY', gDriveFolderId = 'folder:0B0lTys1KmM3sSVJ2bjIzTGFqSWs';
final ConnectApi.RepositoryFolderDetail folder = ConnectApi.ContentHub.getRepositoryFolder(gDriveRepositoryId, gDriveFolderId);
System.debug(String.format('Folder - name: \'\'{0}\'\', description: \'\'{1}\'\', external URL: \'\'{2}\'\', folder items URL: \'\'{3}\'\'', 
   new String[]{ folder.name, folder.description, folder.externalFolderUrl, folder.folderItemsUrl}));

获取没有权限信息的存储库文件

调用方法获取没有权限信息的存储库文件。调用 getRepositoryFile(repositoryId, repositoryFileId) 获取仓库 没有权限的文件 信息。

final String gDriveRepositoryId = '0XCxx00000000ODGAY', gDriveFileId = 'file:0B0lTys1KmM3sTmxKNjVJbWZja00';
final ConnectApi.RepositoryFileDetail file = ConnectApi.ContentHub.getRepositoryFile(gDriveRepositoryId, gDriveFileId);
System.debug(String.format('File - name: \'\'{0}\'\', size: {1}, external URL: \'\'{2}\'\', download URL: \'\'{3}\'\'', 
   new String[]{ file.name, String.valueOf(file.contentSize), file.externalDocumentUrl, file.downloadUrl}));

获取包含权限信息的存储库文件

调用方法以获取包含权限信息的存储库文件。调用 getRepositoryFile(repositoryId, repositoryFileId, includeExternalFilePermissionsInfo) 获取存储库文件 权限 信息。

final String gDriveRepositoryId = '0XCxx00000000ODGAY', gDriveFileId = 'file:0B0lTys1KmM3sTmxKNjVJbWZja00';

final ConnectApi.RepositoryFileDetail file = ConnectApi.ContentHub.getRepositoryFile(gDriveRepositoryId, gDriveFileId, true);
System.debug(String.format('File - name: \'\'{0}\'\', size: {1}, external URL: \'\'{2}\'\', download URL: \'\'{3}\'\'', new String[]{ file.name, String.valueOf(file.contentSize), file.externalDocumentUrl, file.downloadUrl}));
final ConnectApi.ExternalFilePermissionInformation externalFilePermInfo = file.externalFilePermissionInformation;

//permission types
final List<ConnectApi.ContentHubPermissionType> permissionTypes = externalFilePermInfo.externalFilePermissionTypes;
for(ConnectApi.ContentHubPermissionType permissionType : permissionTypes){
   System.debug(String.format('Permission type - id: \'\'{0}\'\', label: \'\'{1}\'\'', new String[]{ permissionType.id, permissionType.label}));
}

//permission groups
final List<ConnectApi.RepositoryGroupSummary> groups = externalFilePermInfo.repositoryPublicGroups;
for(ConnectApi.RepositoryGroupSummary ggroup : groups){
   System.debug(String.format('Group - id: \'\'{0}\'\', name: \'\'{1}\'\', type: \'\'{2}\'\'', new String[]{ ggroup.id, ggroup.name, ggroup.type.name()}));
}

创建不带内容的存储库文件(仅限元数据)

调用方法以在 Google Drive 中创建没有二进制内容(仅限元数据)的文件 存储库文件夹。调用 addRepositoryItem(repositoryId, repositoryFolderId, file) 创建一个 Google 云端硬盘存储库文件夹中没有二进制内容(仅限元数据)的文件。之后 文件,我们显示文件的 ID、名称、描述、外部 URL 和下载 网址。

final String gDriveRepositoryId = '0XCxx00000000ODGAY', gDriveFolderId = 'folder:0B0lTys1KmM3sSVJ2bjIzTGFqSWs';

final ConnectApi.ContentHubItemInput newItem = new ConnectApi.ContentHubItemInput();
newItem.itemTypeId = 'document'; //see getAllowedTypes for any file item types available for creation/update
newItem.fields = new List<ConnectApi.ContentHubFieldValueInput>();

//Metadata: name field
final ConnectApi.ContentHubFieldValueInput fieldValueInput = new ConnectApi.ContentHubFieldValueInput();
fieldValueInput.name = 'name';
fieldValueInput.value = 'new folder item name.txt';
newItem.fields.add(fieldValueInput);

//Metadata: description field
final ConnectApi.ContentHubFieldValueInput fieldValueInputDesc = new ConnectApi.ContentHubFieldValueInput();
fieldValueInputDesc.name = 'description';
fieldValueInputDesc.value = 'It does describe it';
newItem.fields.add(fieldValueInputDesc);

final ConnectApi.RepositoryFolderItem newFolderItem = ConnectApi.ContentHub.addRepositoryItem(gDriveRepositoryId, gDriveFolderId, newItem);
final ConnectApi.RepositoryFileSummary newFile = newFolderItem.file;
System.debug(String.format('New file - id: \'\'{0}\'\', name: \'\'{1}\'\', description: \'\'{2}\'\' \n external URL: \'\'{3}\'\', download URL: \'\'{4}\'\'', new String[]{ newFile.id, newFile.name, newFile.description, newFile.externalDocumentUrl, newFile.downloadUrl}));

创建包含内容的存储库文件

调用方法以在 Google Drive 存储库中创建包含二进制内容的文件 文件夹。调用 addRepositoryItem(repositoryId, repositoryFolderId, file, filedata) 来 在 Google Drive 存储库文件夹中创建包含二进制内容的文件。文件被 created,我们显示文件的 ID、名称、描述、外部 URL 和下载 网址。

final String gDriveRepositoryId = '0XCxx00000000ODGAY', gDriveFolderId = 'folder:0B0lTys1KmM3sSVJ2bjIzTGFqSWs';

final ConnectApi.ContentHubItemInput newItem = new ConnectApi.ContentHubItemInput();
newItem.itemTypeId = 'document'; //see getAllowedTypes for any file item types available for creation/update
newItem.fields = new List<ConnectApi.ContentHubFieldValueInput>();

//Metadata: name field
Final String newFileName = 'new folder item name.txt';
final ConnectApi.ContentHubFieldValueInput fieldValueInput = new ConnectApi.ContentHubFieldValueInput();
fieldValueInput.name = 'name';
fieldValueInput.value = newFileName;
newItem.fields.add(fieldValueInput);

//Metadata: description field
final ConnectApi.ContentHubFieldValueInput fieldValueInputDesc = new ConnectApi.ContentHubFieldValueInput();
fieldValueInputDesc.name = 'description';
fieldValueInputDesc.value = 'It does describe it';
newItem.fields.add(fieldValueInputDesc);

//Binary content
final Blob newFileBlob = Blob.valueOf('awesome content for brand new file');
final String newFileMimeType = 'text/plain';
final ConnectApi.BinaryInput fileBinaryInput = new ConnectApi.BinaryInput(newFileBlob, newFileMimeType, newFileName);

final ConnectApi.RepositoryFolderItem newFolderItem = ConnectApi.ContentHub.addRepositoryItem(gDriveRepositoryId, gDriveFolderId, newItem, fileBinaryInput);
final ConnectApi.RepositoryFileSummary newFile = newFolderItem.file;
System.debug(String.format('New file - id: \'\'{0}\'\', name: \'\'{1}\'\', description: \'\'{2}\'\' \n external URL: \'\'{3}\'\', download URL: \'\'{4}\'\'', new String[]{ newFile.id, newFile.name, newFile.description, newFile.externalDocumentUrl, newFile.downloadUrl}));

更新不带内容的存储库文件(仅限元数据)

调用方法更新存储库文件的元数据。调用 updateRepositoryFile(repositoryId, repositoryFileId, file) 以更新 存储库文件夹中文件的元数据。文件更新后,我们显示文件的 ID, 名称、描述、外部 URL、下载 网址。

final String gDriveRepositoryId = '0XCxx00000000ODGAY', gDriveFolderId = 'folder:0B0lTys1KmM3sSVJ2bjIzTGFqSWs', gDriveFileId = 'document:1q9OatVpcyYBK-JWzp_PhR75ulQghwFP15zhkamKrRcQ';

final ConnectApi.ContentHubItemInput updatedItem = new ConnectApi.ContentHubItemInput();
updatedItem.itemTypeId = 'document'; //see getAllowedTypes for any file item types available for creation/update
updatedItem.fields = new List<ConnectApi.ContentHubFieldValueInput>();

//Metadata: name field
final ConnectApi.ContentHubFieldValueInput fieldValueInputName = new ConnectApi.ContentHubFieldValueInput();
fieldValueInputName.name = 'name';
fieldValueInputName.value =  'updated file name.txt';
updatedItem.fields.add(fieldValueInputName);

final ConnectApi.RepositoryFileDetail updatedFile = ConnectApi.ContentHub.updateRepositoryFile(gDriveRepositoryId, gDriveFileId, updatedItem);
System.debug(String.format('Updated file - id: \'\'{0}\'\', name: \'\'{1}\'\', description: \'\'{2}\'\',\n external URL: \'\'{3}\'\', download URL: \'\'{4}\'\'',  new String[]{ updatedFile.id, updatedFile.name, updatedFile.description, updatedFile.externalDocumentUrl, updatedFile.downloadUrl}));

使用内容更新存储库文件

调用方法以使用内容更新存储库文件。调用 updateRepositoryFile(repositoryId, repositoryFileId, file, fileData) 来 更新存储库中文件的内容和元数据。文件更新后,我们显示 文件的 ID、名称、描述、外部 URL 和下载 网址。

final String gDriveRepositoryId = '0XCxx00000000ODGAY', gDriveFolderId = 'folder:0B0lTys1KmM3sSVJ2bjIzTGFqSWs', gDriveFileId = 'document:1q9OatVpcyYBK-JWzp_PhR75ulQghwFP15zhkamKrRcQ';

final ConnectApi.ContentHubItemInput updatedItem = new ConnectApi.ContentHubItemInput();
updatedItem.itemTypeId = 'document'; //see getAllowedTypes for any file item types available for creation/update
updatedItem.fields = new List<ConnectApi.ContentHubFieldValueInput>();

//Metadata: name field
final ConnectApi.ContentHubFieldValueInput fieldValueInputName = new ConnectApi.ContentHubFieldValueInput();
fieldValueInputName.name = 'name';
fieldValueInputName.value =  'updated file name.txt';
updatedItem.fields.add(fieldValueInputName);

//Binary content
final Blob updatedFileBlob = Blob.valueOf('even more awesome content for updated file');
final String updatedFileMimeType = 'text/plain';
final ConnectApi.BinaryInput fileBinaryInput = new ConnectApi.BinaryInput(updatedFileBlob, updatedFileMimeType, updatedFileName);

final ConnectApi.RepositoryFileDetail updatedFile = ConnectApi.ContentHub.updateRepositoryFile(gDriveRepositoryId, gDriveFileId, updatedItem);
System.debug(String.format('Updated file - id: \'\'{0}\'\', name: \'\'{1}\'\', description: \'\'{2}\'\',\n external URL: \'\'{3}\'\', download URL: \'\'{4}\'\'',  new String[]{ updatedFile.id, updatedFile.name, updatedFile.description, updatedFile.externalDocumentUrl, updatedFile.downloadUrl}));

获取身份验证 URL

调用方法以获取身份验证 URL。调用 getOAuthCredentialAuthUrl(requestBody) 以检索用户 必须访问才能开始身份验证流程,最终将身份验证令牌返回到 Salesforce的。接受表示特定外部凭据的输入参数,并且 (可选)命名主体。使用此方法作为构建自定义或品牌的一部分 用户界面,帮助用户启动身份验证。

ConnectApi.OAuthCredentialAuthUrlInput input = new ConnectApi.OAuthCredentialAuthUrlInput();

input.externalCredential = 'MyExternalCredentialDeveloperName';
input.principalType = ConnectApi.CredentialPrincipalType.PerUserPrincipal;
input.principalName = 'MyPrincipal'; // Only required when principalType = NamedPrincipal

ConnectApi.OAuthCredentialAuthUrl output = ConnectApi.NamedCredentials.getOAuthCredentialAuthUrl(input);

String authenticationUrl = output.authenticationUrl; // Redirect users to this URL to authenticate in the browser

CommercePayments 命名空间的用例

查看平台的演练、用例和参考资料。

CommercePayments

若要查看类参考文档,请转到 CommercePayments 命名空间。CommercePayments

  • 支付网关适配器 支付网关适配器
    代表了 Salesforce 中的支付平台与外部支付网关之间的桥梁。
  • 付款授权撤销服务
    授权撤销是通过解除客户付款方式中的资金冻结来否定授权的交易。
  • 令牌化服务
    信用卡令牌化过程将敏感的客户信息替换为在支付交易期间使用的一次性算法生成的数字(称为令牌)。Salesforce 存储令牌,然后使用该令牌作为用于交易的信用卡的表示形式。该令牌允许您存储有关信用卡的信息,而无需在 Salesforce 中存储敏感的客户数据,例如信用卡号。
  • 替代付款方式 替代付款方式允许客户存储和表示其他预定义的付款方式(如 或)未表示的付款方式
    信息。替代付款方式的常见示例包括 CashOnDeliver、Klarna 和直接借记。API v51.0 及更高版本中提供了其他付款方式。CardPaymentMethodDigitalWallet
  • 处理付款 在支付网关中处理付款
  • 处理退款 在支付网关中
    处理退款
  • 等性准则
    幂等性表示支付网关能够识别错误或恶意提交的重复请求,然后相应地处理重复请求。使用幂等网关时,请考虑以下重要准则。
  • CommercePayments 的示例支付网关实现 我们创建了一个 GitHub 存储库,其中包含具有 CommercePayments
    命名空间的示例 Payeezy 支付网关实现的代码示例。如果您在配置支付网关实施方面需要帮助,请查看示例代码。

支付网关适配器

支付网关适配器代表了您的支付平台之间的桥梁 Salesforce 和外部支付网关。

  • 构建同步网关适配器
    在同步付款配置中,Salesforce 付款平台将交易信息发送到网关,然后等待包含最终交易状态的网关响应。只有当事务在网关中成功时,Salesforce 才会创建事务。
  • 设置同步支付网关适配器
    对于支付交易,您可以将 Salesforce 配置为与同步支付网关适配器交互。
  • 构建异步网关适配器
    异步支付配置中,支付平台首先向网关发送交易信息。网关通过确认它已收到交易进行响应,然后平台创建待处理交易。网关发送通知,其中包含最终事务状态。然后,平台会相应地更新交易状态。
  • 设置异步支付网关适配器
    对于支付交易,您可以将 Salesforce 配置为与异步支付网关适配器交互。
  • 支付网关适配器的构建器示例 支付网关适配器的最后部分应定义适配器
    如何创建请求和响应。这些类的实现可能会因网关和平台要求而有很大差异。我们提供了几个泛型示例供查看。

构建同步网关适配器

在同步支付配置中,Salesforce 支付平台发送 事务信息发送到网关,然后等待包含 最终交易状态。Salesforce 仅在交易发生时才会创建交易 在网关中成功。

同步网关适配器实现 PaymentGatewayAdapter 接口。在本主题中,我们将分解一个示例异步 适配器,然后查看 类,它驱动了大部分 支付平台和支付网关之间的通信。PaymentGatewayAdapterprocessRequest

注意

支付网关适配器无法进行将来的调用、外部调用、异步调用、可排队的调用或执行 DML 使用 SOQL。System.Http

PaymentGateway适配器

所有同步网关都必须实现该接口。所有 PaymentGatewayAdapters 都是 实现该方法所必需的。

PaymentGatewayAdapterprocessRequest

global with sharing class SampleAdapter implements commercepayments.PaymentGatewayAdapter {
    global SampleAdapter() {}
    
    global commercepayments.GatewayResponse processRequest(commercepayments.paymentGatewayContext gatewayContext) {
    }
}

处理初始付款请求

当支付平台 接收付款 API 请求,并将请求传递给网关适配器 进一步评估。适配器通过调用 processRequest 方法开始请求评估过程,该方法表示同步的第一步 付款流程。我们可以将 processRequest 实现分为三个部分。

首先,它构建一个网关可以 理解。

commercepayments.RequestType requestType = gatewayContext.getPaymentRequestType();
if (requestType == commercepayments.RequestType.Capture) {
   req.setEndpoint('/pal/servlet/Payment/v52/capture');
    body = buildCaptureRequest((commercepayments.CaptureRequest)gatewayContext.getPaymentRequest());
} else if (requestType == commercepayments.RequestType.ReferencedRefund) {
    req.setEndpoint('/pal/servlet/Payment/v52/refund');
    body = buildRefundRequest((commercepayments.ReferencedRefundRequest)gatewayContext.getPaymentRequest());
}

然后 适配器将请求发送到付款 网关。

req.setBody(body);
req.setMethod('POST');
commercepayments.PaymentsHttp http = new commercepayments.PaymentsHttp();
HttpResponse res = null;
try {
    res = http.send(req);
} catch(CalloutException ce) {
    commercepayments.GatewayErrorResponse error = new commercepayments.GatewayErrorResponse('500', ce.getMessage());
    return error;
}

最后 适配器创建一个响应对象来存储来自网关响应的数据。这 响应对象的类型将根据您最初是否进行付款捕获而有所不同 申请或退款 请求。

if ( requestType == commercepayments.RequestType.Capture) {
   // Refer to the end of this doc for sample createCaptureResponse implementation
    response =  createCaptureResponse(res);
} else if ( requestType == commercepayments.RequestType.ReferencedRefund) {
    response =  createRefundResponse(res);
}
return response;

设置同步支付网关适配器

对于付款交易,您可以将 Salesforce 配置为与同步 支付网关适配器。

要访问 API,您需要 PaymentPlatform 组织权限。commercepayments

  1. 创建您的支付网关适配器 Apex 类。有关说明,请参阅构建同步网关适配器。
  2. 创建命名凭据。
    1. 在“设置”的“快速查找”框中,输入“命名凭据”,然后 然后选择新建。
    2. 填写必填字段,包括支付网关的 URL。
  3. 创建支付网关提供商。PaymentGatewayProvider 对象存储有关以下内容的详细信息 Salesforce Payments 在处理 交易。
    1. 根据以下方式连接以连接 REST API 中的说明生成访问令牌 OAuth。响应包括属性中指定的访问令牌和属性中指定的服务器实例。使用此信息进行 API 调用 构建支付网关提供商。access_tokeninstance_url
    2. 使用instance_url中的域对资源执行 POST 调用。例如,https:// instance_name.my.salesforce.com/services/data/v api_version/tooling/sobjects/PaymentGatewayProvider。使用此有效负载作为请求正文,替换为 正确的数据。value{ "ApexAdapterId": "value", "DeveloperName": "value", "MasterLabel": "value", "IdempotencySupported": "value", "Comments": "value" } Example: { "ApexAdapterId": "01pxx0000004UU8AAM", "DeveloperName": "MyNewGatewayProvider", "MasterLabel": "My New Gateway Provider", "IdempotencySupported": "Yes", "Comments": "Custom made gateway provider." }
  4. 创建支付网关记录。PaymentGateway 对象存储有关 连接到外部支付网关。记录需要这些字段值。
    • 支付网关名称:外部支付网关的名称。
    • 商家凭证 ID:您创建的指定凭证的 ID。
    • 支付网关提供商 ID:您拥有的支付网关提供商的 ID 创建。
    • 状态: 已上市

构建异步网关适配器

在异步支付配置中,支付平台首先发送 交易信息发送到网关。网关通过确认它进行响应 收到交易,然后平台创建一个待处理的交易。网关 发送通知,其中包含最终交易状态。然后,平台将更新 交易的状态。

异步过程与同步事务不同,在同步事务中,平台这样做 在初始网关请求之后不创建挂起的事务。取而代之的是, 平台仅在网关发送包含 最终交易状态。有关构建同步适配器的信息,请查看构建同步网关适配器。异步配置需要同步网关适配器和 异步适配器。在本主题中,我们将按以下方式分解示例异步适配器 着眼于几个重要领域。

  • 定义异步支付网关适配器
  • 处理初始付款请求
  • 处理来自支付网关的通知
  • 使用系统调试日志调试网关响应。

注意

支付网关适配器无法进行将来的调用、外部标注、异步调用、可排队的调用或 使用 SOQL 执行 DML。System.Http

异步支付网关适配器定义

异步网关适配器类必须同时实现 PaymentGatewayAdapter 接口和 PaymentGatewayAsyncAdapter 接口。适配器类 还必须实现 PaymentGatewayAdapter 的方法和 PaymentGatewayAsyncAdapter 的方法。processRequestprocessNotification

global with sharing class SampleAdapter implements commercepayments.PaymentGatewayAsyncAdapter, commercepayments.PaymentGatewayAdapter {
    global SampleAdapter() {}
    
    global commercepayments.GatewayResponse processRequest(commercepayments.paymentGatewayContext gatewayContext) {
    }
    
    global commercepayments.GatewayNotificationResponse processNotification(commercepayments.PaymentGatewayNotificationContext gatewayNotificationContext) {
    }
}

处理初始付款请求

当支付平台 接收付款 API 请求,并将请求传递给网关适配器 进一步评估。适配器通过调用 processRequest 方法开始请求评估过程,该方法表示异步的第一步 付款流程。我们可以将 processRequest 实现分为三个部分。

首先,它构建一个网关可以 理解。

commercepayments.RequestType requestType = gatewayContext.getPaymentRequestType();
if (requestType == commercepayments.RequestType.Capture) {
   req.setEndpoint('/pal/servlet/Payment/v52/capture');
    body = buildCaptureRequest((commercepayments.CaptureRequest)gatewayContext.getPaymentRequest());
} else if (requestType == commercepayments.RequestType.ReferencedRefund) {
    req.setEndpoint('/pal/servlet/Payment/v52/refund');
    body = buildRefundRequest((commercepayments.ReferencedRefundRequest)gatewayContext.getPaymentRequest());
}

然后 适配器将请求发送到付款 网关。

req.setBody(body);
req.setMethod('POST');
commercepayments.PaymentsHttp http = new commercepayments.PaymentsHttp();
HttpResponse res = null;
try {
    res = http.send(req);
} catch(CalloutException ce) {
    commercepayments.GatewayErrorResponse error = new commercepayments.GatewayErrorResponse('500', ce.getMessage());
    return error;
}

最后 适配器创建一个响应对象来存储来自网关响应的数据。这 响应对象的类型将根据您最初是否进行付款捕获而有所不同 申请或退款 请求。

if ( requestType == commercepayments.RequestType.Capture) {
   // Refer to the end of this doc for sample createCaptureResponse implementation
    response =  createCaptureResponse(res);
} else if ( requestType == commercepayments.RequestType.ReferencedRefund) {
    response =  createRefundResponse(res);
}
return response;

处理来自支付网关的通知

之后 客户银行处理交易并将结果发送到网关, 网关向适配器发送通知,指示它已准备好提供 最终交易状态。对于异步事务流的这一部分, 适配器需要调用 processNotification 类。我们可以拆分 processNotification 实现分为四个部分。

一、适配器 验证通知请求中的签名。欲了解更多信息 验证签名,审核 [主题]。

private Boolean verifySignature(NotificationRequest requestItem) {
    String payload = requestItem.pspReference + ':'
        + (requestItem.originalReference == null ? '' : requestItem.originalReference) + ':'
        + requestItem.merchantAccountCode + ':'
        + requestItem.merchantReference + ':'
        + requestItem.amount.value.intValue() + ':'
        + requestItem.amount.currencyCode + ':'
        + requestItem.eventCode + ':'
        + requestItem.success;
    String myHMacKey = getHMacKey();
    String generatedSign = EncodingUtil.base64Encode(Crypto.generateMac('hmacSHA256', Blob.valueOf(payload), 
                                EncodingUtil.convertFromHex(myHMacKey)));
    return generatedSign.equals(requestItem.additionalData.hmacSignature);
}

下一个 适配器分析网关的通知请求并生成通知对象。 方法 评估来自网关的通知请求项的数据,其中包括状态、 referenceNumber、event 和 amount。该对象设置为“成功”或“失败”,具体取决于 平台已成功收到通知。如果通知的事件代码 表示网关处理了付款捕获事务,适配器将构建一个 通知对象。如果事件代码指示网关 处理退款事务时,适配器使用该类生成通知对象。

getPaymentGatewayNotificationRequestnotificationStatusCaptureNotificationReferencedRefundNotification

commercepayments.PaymentGatewayNotificationRequest gatewayNotificationRequest = gatewayNotificationContext.getPaymentGatewayNotificationRequest();
Blob request = gatewayNotificationRequest.getRequestBody();
SampleNotificationRequest notificationRequest = SampleNotificationRequest.parse(request.toString().replace('currency', 'currencyCode'));                
List<SampleNotificationRequest.NotificationItems> notificationItems = notificationRequest.notificationItems;
SampleNotificationRequest.NotificationRequestItem notificationRequestItem = notificationItems[0].NotificationRequestItem;

Boolean success = Boolean.valueOf(notificationRequestItem.success);
String pspReference = notificationRequestItem.pspReference;
String eventCode = notificationRequestItem.eventCode;
Double amount = notificationRequestItem.amount.value;

commercepayments.NotificationStatus notificationStatus = null;
if (success) {
    notificationStatus = commercepayments.NotificationStatus.Success;
} else {
    notificationStatus = commercepayments.NotificationStatus.Failed;
}
commercepayments.BaseNotification notification = null;
if ('CAPTURE'.equals(eventCode)) {
    notification = new commercepayments.CaptureNotification();
} else if ('REFUND'.equals(eventCode)) {
    notification = new commercepayments.ReferencedRefundNotification();
}
notification.setStatus(notificationStatus);
notification.setGatewayReferenceNumber(pspReference);
notification.setAmount(amount);

这 然后,适配器请求支付平台记录 通知。

commercepayments.NotificationSaveResult saveResult = commercepayments.NotificationClient.record(notification);

都 异步网关要求平台确认它收到了 通知,无论平台是否成功保存了通知的 数据。平台调用该类来发送 确认。

GatewayNotificationResponse

commercepayments.GatewayNotificationResponse gnr = new commercepayments.GatewayNotificationResponse();
if (saveResult.isSuccess()) {
    system.debug('Notification accepted by platform');
} else {
    system.debug('Errors in the result '+ Blob.valueOf(saveResult.getErrorMessage()));
}
gnr.setStatusCode(200);
gnr.setResponseBody(Blob.valueOf('[accepted]'));
return gnr;

调试

通常,Apex 调试日志可在开发人员控制台中找到。但是,Salesforce 不会在开发人员控制台中存储来自该方法的调试日志。查看此部件 ,查看收集来宾用户的调试日志 设置调试日志记录部分。processNotification

设置异步支付网关适配器

对于支付交易,您可以将 Salesforce 配置为与 异步支付网关适配器。

要访问 API,您需要 PaymentPlatform 组织权限。commercepayments

  1. 创建 Salesforce 站点。在“设置”的“快速查找”框中,输入“站点”。在“站点和域”下,选择“站点”,请参阅设置 Salesforce 站点。将网站的公共访问设置设置为访客访问付款 API接口。
  2. 创建您的支付网关适配器 Apex 类。异步支付网关需要 实现异步适配器和同步适配器。有关建筑物的信息 网关适配器,请参阅构建异步网关适配器和构建同步网关适配器。
  3. 在 UI 中创建命名凭据。
    1. 在“设置”的“快速查找”框中,输入“命名凭据”,然后 ,然后选择新建
    2. 填写必填字段。对于 URL,请输入支付网关的 URL。
  4. 创建支付网关提供商。PaymentGatewayProvider 对象存储有关以下内容的详细信息 Salesforce Payments 在处理交易时与之通信的支付网关。
    1. 根据以下方式连接以连接 REST API 中的说明生成访问令牌 OAuth。响应包括属性中指定的访问令牌和属性中指定的服务器实例。使用此信息制作 API 调用以构建支付网关提供程序。access_tokeninstance_url
    2. 使用instance_url中的域对资源执行 POST 调用。例如,https:// instance_name.my.salesforce.com/services/data/v api_version/tooling/sobjects/PaymentGatewayProvider。使用此有效负载作为请求正文,替换为 正确的数据。value{ "ApexAdapterId": "value", "DeveloperName": "value", "MasterLabel": "value", "IdempotencySupported": "value", "Comments": "value" } Example: { "ApexAdapterId": "01pxx0000004UU8AAM", "DeveloperName": "MyNewGatewayProvider", "MasterLabel": "My New Gateway Provider", "IdempotencySupported": "Yes", "Comments": "Custom made gateway provider." }
  5. 创建支付网关记录。PaymentGateway 对象存储有关 连接到外部支付网关。记录需要这些字段值。
    • 支付网关名称:外部支付网关的名称。
    • Merchant Credential ID:您拥有的指定凭据的 ID 创建。
    • 支付网关提供商 ID:支付网关提供商的 ID 您创建的。
    • 状态: 已上市
  6. 通过在 外部支付网关。外部支付网关使用 Webhook 发送通知, 作为 HTTP POST 消息,发送到异步支付网关适配器。Webhook 是站点终端节点与支付网关 ID 的组合 供应商。
    1. 将以下 URL 用于您站点的端点,将 domain 替换为您站点的域和 URL。例如:https://MyDomainName.my.salesforce-sites.com/solutions/services/data/v58.0/commerce/payments/notify注意如果你不是 使用增强型域时,您组织的 Salesforce 站点 URL 会有所不同。有关详细信息,请参阅我的 Salesforce 帮助中的域 URL 格式。
    2. 查找支付网关提供商的 ID,并将 ?provider=ID 查询参数附加到终端节点。例如,https://MyDomainName.my.salesforce-sites.com/solutions/services/data/v58.0/commerce/payments/notify?provider=0cJR00000004CEhMAM
    3. 在外部支付网关的标准通知中输入 Webhook 设置。

支付网关适配器的生成器示例

支付网关适配器的最后部分应定义适配器的创建方式 请求和响应。这些类的实现可能会因 网关和平台要求。我们提供了几个泛型示例 回顾。

buildCapture请求

private String buildCaptureRequest(commercepayments.CaptureRequest captureRequest) {
   Boolean IS_MULTICURRENCY_ORG = UserInfo.isMultiCurrencyOrganization();
    QueryUtils qBuilderForAuth = new QueryUtils(PaymentAuthorization.SObjectType);
    qBuilderForAuth.getSelectClause().addField('GatewayRefNumber', false);
    qBuilderForAuth.setWhereClause(' WHERE Id =' + '\'' + captureRequest.paymentAuthorizationId + '\'');
    PaymentAuthorization authObject = (PaymentAuthorization)Database.query(qBuilderForAuth.buildSOQL())[0];

    JSONGenerator jsonGeneratorInstance = JSON.createGenerator(true);
    jsonGeneratorInstance.writeStartObject();
    jsonGeneratorInstance.writeStringField('merchantAccount', '{!$Credential.Username}');
    jsonGeneratorInstance.writeStringField('originalReference', authObject.GatewayRefNumber);

    jsonGeneratorInstance.writeFieldName('modificationAmount');
    jsonGeneratorInstance.writeStartObject();
    jsonGeneratorInstance.writeStringField('value', String.ValueOf((captureRequest.amount * 100.0).intValue()));
    jsonGeneratorInstance.writeEndObject();

    jsonGeneratorInstance.writeEndObject();
    return jsonGeneratorInstance.getAsString();
}

createCaptureResponse

private commercepayments.GatewayResponse createCaptureResponse(HttpResponse response) {
    Map<String, Object> mapOfResponseValues = (Map
                        <String, Object>) JSON.deserializeUntyped(response.getBody());
    Integer statusCode = response.getStatusCode();
    String responceValue = (String)mapOfResponseValues.get('response');
    if(statusCode == 200) {
        system.debug('Response - success - Capture received');
       commercepayments.CaptureResponse captureResponse = new commercepayments.CaptureResponse();
        captureResponse.setAsync(true); // Very important to treat this as an asynchronous transaction
        captureResponse.setGatewayReferenceNumber((String)mapOfResponseValues.get('pspReference'));
        captureResponse.setSalesforceResultCodeInfo(new commercepayments.SalesforceResultCodeInfo(commercepayments.SalesforceResultCode.Success));
        return captureResponse;
    } else {
        system.debug('Response - error - Capture not received by Gateway');
        String message = (String)mapOfResponseValues.get('message');
        commercepayments.GatewayErrorResponse error = new commercepayments.GatewayErrorResponse(String.valueOf(statusCode), message);
        return error;
    }
}

付款授权撤销服务

授权撤销是通过释放来否定授权的交易 客户付款方式中的资金冻结。

  • 授权反转Apex类实现
    授权反转服务使用 和 类来管理授权反转信息的创建和存储。在支付网关适配器中实现这些类。AuthorizationReversalRequestAuthorizationReversalResponse
  • 支付授权撤销服务 API
    授权撤销是指通过解除客户支付方式中的资金冻结来否定授权的交易。使用授权撤销服务,为用户提供撤销未完成的付款授权的能力。

授权撤销 Apex 类实现

授权撤销服务使用 和 类来管理 授权撤销信息。在支付网关中实现这些类 适配器。

AuthorizationReversalRequestAuthorizationReversalResponseAuthorizationReversal请求表示授权撤销请求。扩展并继承其所有方法。BaseRequestAuthorizationReversalRequest使用构造函数 在 Salesforce 中建立授权撤销请求记录。构造函数不带任何参数。 您可以按如下方式调用它。AuthorizationReversalRequest

CommercePayments.AuthorizationReversalRequest arr = new CommercePayments.AuthorizationReversalRequest();

如果要生成示例授权撤销,还可以使用 冲销金额和付款授权 ID 的参数。但是,构造函数会 仅适用于测试使用,如果在 Apex 测试之外使用,则会引发异常 上下文。

commercepayments.AuthorizationReversalRequest authorizationReversalRequest = 
new commercepayments.AuthorizationReversalRequest(80, authObj.id);

AuthorizationReversalResponse支付网关适配器发送此类作为授权撤销的响应 请求类型。扩展并继承其 方法。AbstractResponseAuthorizationReversalResponse使用构造函数 在 Salesforce 中建立授权撤销请求记录。构造函数不带任何参数。 您可以按如下方式调用它:AuthorizationReversalResponse

CommercePayments.AuthorizationReversalResponse arp = new CommercePayments.AuthorizationReversalResponse();

注意

Salesforce 不支持批量操作或授权撤销中的自定义字段 过程。

在网关适配器中实现反转类

将您的反转类添加到您的支付网关适配器。我们建议在调用网关的响应时添加一个可能的值。AuthorizationReversalrequestTypeprocessRequest

global commercepayments.GatewayResponse processRequest(commercepayments.paymentGatewayContext gatewayContext) {
        commercepayments.RequestType requestType = gatewayContext.getPaymentRequestType();
        commercepayments.GatewayResponse response;
        
        try {
        //add conditions for other requestType values here
        //..
        else if (requestType == commercepayments.RequestType.AuthorizationReversal) {
                response = createAuthReversalResponse((commercepayments.AuthorizationReversalRequest)gatewayContext.getPaymentRequest());}
        
        return response;

然后,添加一个设置授权撤销请求量的类 gateway 信息和 Salesforce 结果代码。

global commercepayments.GatewayResponse createAuthReversalResponse(commercepayments.AuthorizationReversalRequest authReversalRequest) {
        commercepayments.AuthorizationReversalResponse authReversalResponse = new commercepayments.AuthorizationReversalResponse();
        if(authReversalRequest.amount!=null )
        {
            authReversalResponse.setAmount(authReversalRequest.amount);
        }
        else
        {
             throw new SalesforceValidationException('Required Field Missing : Amount');             
        }
   
        system.debug('Response - success');
        authReversalResponse.setGatewayDate(system.now());
        authReversalResponse.setGatewayResultCode('00');
        authReversalResponse.setGatewayResultCodeDescription('Transaction Normal');
        //Replace 'xxxxx' with the gateway reference number.
        authReversalResponse.setGatewayReferenceNumber('SF'+xxxxx);
        authReversalResponse.setSalesforceResultCodeInfo(SUCCESS_SALESFORCE_RESULT_CODE_INFO);
        return authReversalResponse;
    }

示例 Apex 请求

String authorizationId = '0XcxXXXXXXXXXXXXXXX';
ConnectApi.AuthorizationReversalRequest authorizationReversalRequest = new ConnectApi.AuthorizationReversalRequest();
authorizationReversalRequest.amount = 1.0;
authorizationReversalRequest.comments = 'Captured from custom action';
authorizationReversalRequest.ipAddress = '192.162.10.3';
authorizationReversalRequest.email = 'testuser@example.com';

ConnectApi.AuthorizationReversalResponse authorizationReversalResponse = ConnectApi.Payments.reverseAuthorization(authorizationReversalRequest, authorizationId);
String authReversalId = authorizationReversalResponse.paymentAuthAdjustment.id;
System.debug(authorizationReversalResponse);
System.debug(authReversalId);

支付授权撤销服务API

授权撤销是通过释放来否定授权的交易 客户付款方式中的资金冻结。使用授权撤销服务可以 使用户能够撤销未完成的付款授权。

有时,客户执行付款授权,但随后需要取消全部或部分 稍后授权。例如,客户购买了三件商品,然后意识到 第一件商品已经在他们的库存中。Commerce Payments API 允许您撤消全部或部分 未完成的付款授权。

客户支付网关授权付款后,Commerce Payments 将创建付款 授权记录,用于存储有关授权的信息。当用户或进程 对授权执行撤销,授权撤销服务会创建一个 对存储信息进行支付授权调整。该调整与 授权。如果付款授权与订单付款摘要相关联,则撤销 金额将添加到订单付款摘要的 AuthorizationReversalAmount 中,并从其 AvailableToCaptureAmount 中减去。但 AvailableToCaptureAmount 永远不会低于 0,即使反转使其 计算负数。

注意

对于授权撤销,支付网关日志的 OrderPaymentSummaryId 始终默认为 null。如果有一个 关联订单付款汇总,您的代码可以设置值。

通过向以下终结点发出 POST 请求来调用授权撤销服务。

端点

/commerce/payments/authorizations/${*authorizationId*}/reversals

该服务每次调用接受一个授权撤销请求。以下付款 接受授权调整 API 参数。

参数必填描述
必填从授权中冲销的金额。必须大于零。Salesforce 不提供与 相比的验证。PaymentAuthorizationAdjustment.AmountPaymentAuthorization.Amount如果支付网关允许的撤销金额大于授权金额 金额,则授权的结果余额可以为负数。如果您的网关支持 授权余额低于零,并且您希望避免网关调用,请配置 适配器查询授权金额、余额和总冲销金额,不查询 如果余额小于零,则调用终结点。
帐户 ID自选此授权撤销链接到的帐户 ID。
生效日期自选撤销适用于授权的日期。
电子邮件自选欺诈参数
ip地址自选欺诈参数
mac地址自选欺诈参数
电话自选欺诈参数
评论自选用户提供的有关授权撤销的注释。必须小于 1000 字符。

示例请求和响应

此请求调用针对授权的 150 美元撤销。

{
  "accountId":"",
  "amount": "150",*  "comments": "authorization reversal request",
  "effectiveDate":"2020-10-18T11:32:27.000Z",
  "ipAddress": "202.95.77.70",
  "macAddress": "00-14-22-01-23-45",
  "phone": "100-456-67",
  "email": "test@example.org",
  "additionalData":{
       //add additional parameters if needed
      "key1":"value1",
      "key2":"value2",
      "key3":"value3",
      "key4":"value4",
      "key5":"value5"
    }
}

示例响应 – 成功

成功的授权撤销响应提供有关网关响应的信息 以及用于构造付款授权调整实体的值。

HPP Status Code: 201
{
  "gatewayResponse" : {
    "gatewayDate" : "2020-10-23T15:21:58.833Z",
    "gatewayReferenceNumber" : "439XXXXXXX",
    "gatewayResultCode" : "00",
    "gatewayResultCodeDescription" : "Transaction Normal",
    "salesforceResultCode" : "Success"
  },
  "paymentAuthAdjustment" : {
    "amount" : "150.0",
    "currencyIsoCode" : "USD",
    "effectiveDate" : "2020-10-18T11:32:27.000Z",
    "id" : "9tvR00000004Cf1MAE",
    "paymentAuthAdjustmentNumber" : "PAA-00XXXXXXX",
    "requestDate" : "2020-10-23T15:21:58.000Z",
    "status" : "Processed"
  },
  "paymentGatewayLogs" : [ {
    "createdDate" : "2020-10-23T15:21:58.000Z",
    "gatewayResultCode" : "00",
    "id" : "0XtXXXXXXXXXXXXXXX",
    "interactionStatus" : "Success"
  } ]
}

Salesforce 中生成的付款授权调整如下所示。

如果返回错误,则响应将包含网关的错误代码和错误消息。

示例响应 – 错误

{
    "errorCode":"",
    "errorMessage":""
}

代币化服务

信用卡令牌化过程将敏感的客户信息替换为 在支付交易期间使用的一次性算法生成的号码,称为令牌。 Salesforce 存储令牌,然后使用该令牌作为所用信用卡的表示形式 用于交易。令牌允许您存储有关信用卡的信息,而无需存储 Salesforce 中的敏感客户数据,例如信用卡号。

  • 令牌化服务 Apex 类实现
    使用令牌化服务隐藏敏感的客户付款方式数据。标记化服务使用 、 和 。在支付网关适配器中实现这些类。PaymentMethodTokenizationRequestPaymentMethodTokenizationResponseCardPaymentMethodRequest
  • 令牌化服务 API
    信用卡令牌化过程将敏感的客户信息替换为算法生成的一次性数字(称为令牌),以便在支付交易期间使用。Salesforce 存储令牌,然后使用该令牌作为用于交易的信用卡的表示形式。该令牌允许您存储有关信用卡的信息,而无需在 Salesforce 中实际存储敏感的客户数据(如信用卡号)。实施我们的令牌化 API,为您的支付服务添加令牌化功能。

令牌化服务 Apex 类实现

使用令牌化服务隐藏敏感的客户付款方式数据。这 标记化服务使用 、 和 。在支付网关中实现这些类 适配器。

PaymentMethodTokenizationRequestPaymentMethodTokenizationResponseCardPaymentMethodRequest

令牌化支付方式的加密

CommercePayments 使用 Salesforce 字段加密将网关令牌值安全地存储在 客户付款方式实体,例如 DigitalWallet、CardPaymentMethod 和 AlternativePaymentMethod。

CardPaymentMethod 和 DigitalWallet 包含 GatewayTokenEncrypted 字段,该字段在 API v52.0 及更高版本,以及 GatewayToken 字段,在 API v48.0 及更高版本中可用。双 字段存储网关令牌值。但是,GatewayTokenEncrypted 使用 Salesforce Classic Encryption for Custom 用于安全加密令牌的字段。GatewayToken 不使用加密。自 确保安全令牌化,我们建议在您的 DigitalWallets 上使用 GatewayTokenEncrypted 和 CardPaymentMethods。AlternativePaymentMethod 对象将 GatewayToken 字段用于 令牌存储,但是,此字段在 AlternativePaymentMethods 上加密。

在 API 版本 52.0 及更高版本中,CardPaymentMethods 和 DigitalWallets 无法存储 GatewayTokenEncryption 和 GatewayToken 同时位于同一记录上。如果您尝试 当另一个存在时分配一个,Salesforce 会抛出错误。

您的支付网关适配器使用 和 类来检索网关令牌 从支付网关,在 Salesforce 中对其进行加密,并将值存储在付款方式上 实体。让我们看看如何在支付网关适配器中配置这些类。PaymentMethodTokenizationRequestPaymentMethodTokenizationResponse

在网关适配器中实现标记化类

以下代码在 Apex 类中使用。PaymentGatewayAdapter

当类的方法收到 令牌化请求。如果请求类型为 ,则调用该方法 并传递类的实例。这 传递的对象包含地址和 cardPaymentMethod 支付网关管理令牌化所需的信息 过程。例如:GatewayResponseprocessRequestTokenizeGatewayResponsecreateTokenizeResponsePaymentMethodTokenizationRequestPaymentMethodTokenizationRequest

global commercepayments.GatewayResponse processRequest(commercepayments.paymentGatewayContext gatewayContext) {
         commercepayments.RequestType requestType = gatewayContext.getPaymentRequestType();
         commercepayments.GatewayResponse response;
         try
         {
             if (requestType == commercepayments.RequestType.Tokenize) {
                     response = createTokenizeResponse((commercepayments.PaymentMethodTokenizationRequest)gatewayContext.getPaymentRequest());
             }
             //Add other else if statements for different request types as needed.
             return response;
         }
         catch(SalesforceValidationException e)
         {
              commercepayments.GatewayErrorResponse error = new commercepayments.GatewayErrorResponse('400', e.getMessage());
              return error;
         }
     }

将方法配置为 接受 的实例,然后根据它收到的值生成 PaymentMethodTokenizationResponse 的实例 从支付网关。tokenizeResponse 包含网关的 标记化过程,如果成功,则标记化值。在此示例中,我们调用该方法来设置 标记化响应中的标记化值。createTokenizeResponsePaymentMethodTokenizationRequestsetGatewayTokenEncrypted

public commercepayments.GatewayResponse createTokenizeResponse(commercepayments.PaymentMethodTokenizationRequest tokenizeRequest) {
         commercepayments.PaymentMethodTokenizationResponse tokenizeResponse = new commercepayments.PaymentMethodTokenizationResponse();
         tokenizeResponse.setGatewayTokenEncrypted(encryptedValue);
         tokenizeResponse.setGatewayTokenDetails(tokenDetails);
         tokenizeResponse.setGatewayAvsCode(avsCode);
         tokenizeResponse.setGatewayMessage(gatewayMessage);
         tokenizeResponse.setGatewayResultCode(resultcode);
         tokenizeResponse.setGatewayResultCodeDescription(resultCodeDescription);
         tokenizeResponse.setSalesforceResultCodeInfo(resultCodeInfo);
         tokenizeResponse.setGatewayDate(system.now());
         return tokenizeResponse;
     }

该方法在 Salesforce API v52.0 及更高版本。它使用 Salesforce 经典加密来设置加密 可以存储在 CardPaymentMethod 上的 GatewayTokenEncrypted 中的令牌值,或者 DigitalWallet,或在 AlternativePaymentMethod 上的 GatewayToken 中。我们建议您使用来确保您的代币化付款 方法值是加密且安全的。setGatewayTokenEncryptedsetGatewayTokenEncrypted

/** @description Method to set Gateway token to persist in Encrypted Text */
     global void setGatewayTokenEncrypted(String gatewayTokenEncrypted) {
         if (gatewayTokenSet)  {
             throwTokenError();
         }
         this.delegate.setGatewayTokenEncrypted(gatewayTokenEncrypted);
         gatewayTokenEncryptedSet = true;
     }

如果实例化的类已具有网关令牌,则会引发错误。setGatewayTokenEncrypted

注意

虽然 PaymentMethodTokenizationResponse 的 setGatewayToken 方法(在 API v48.0 及更高版本中可用)也返回 付款方式令牌,则令牌化值未加密。

令牌化服务 API

信用卡令牌化过程将敏感的客户信息替换为 算法生成的一次性号码,称为令牌,用于支付交易期间。 Salesforce 存储令牌,然后使用该令牌作为所用信用卡的表示形式 用于交易。令牌允许您存储有关信用卡的信息,而无需实际 在 Salesforce 中存储敏感的客户数据,例如信用卡号。实施我们的 令牌化 API,用于为您的支付服务添加令牌化功能。

在典型的令牌化过程中,支付平台接受客户支付方式数据 并将其传递给 Salesforce 外部支付网关上的远程令牌服务服务器。 服务器为平台上的存储提供标记化值。例如,客户 提供 的信用卡号。令牌服务器存储 此值,将其与 的令牌相关联,并发送 用于在平台上存储的令牌。4111 1111 1111 12342537446225198291

在与商家通信期间,商家将令牌发送到令牌服务器。令牌服务器确认它 匹配客户的令牌,并授权商家针对 客户的卡。2537446225198291

Commerce Payments Tokenization API 接受信用卡信息并使用外部 通过客户的 Salesforce 组织配置的支付网关,用于对卡进行令牌化 信息。然后,它返回标记化表示形式。然后,API 将令牌保存在 中。CardPaymentMethod

通过向以下终结点发出 POST 请求来调用授权撤销服务。

端点

/commerce/payments/payment-method/tokens/

令牌化服务接受来自付款的以下请求参数,并相关 实体。

参数必填
cardPaymentMethod: { "cardHolderName":"", "expiryMonth":"", "expiryYear":"", "startMonth":"", "startYear":"", "cvv":"", "cardNumber":"", "cardCategory":"", "cardType":"", "nickName":"", "cardHolderFirstName":"", "cardHolderLastName":"", "email":"", "comments":"" }必填要令牌化的信用卡的详细信息。
帐户 ID自选持卡人的 Salesforce 帐户 ID。
"address":{ "street":"", "city":"", "state":"", "country":"", "postalCode":"", "companyName":"", }自选拥有信用卡付款方式的客户的地址信息 代币化。
paymentGatewayId必填与令牌化服务器相关的外部支付网关。
电子邮件自选欺诈参数。
ip地址自选欺诈参数。
mac地址自选欺诈参数。
电话自选欺诈参数。
additionalData自选网关令牌化信用卡付款所需的任何其他数据 方法。

示例请求和响应

此示例请求提供客户的信用卡信息以进行令牌化。

{
    "cardPaymentMethod": {
        "cardHolderName":"Carol Smith",
        "expiryMonth": "05",
        "expiryYear": "2025", 
        "startMonth": "",
        "startYear": "",
        "cvv": "000",
        "cardNumber": "4111111111111111",
        "cardCategory": "Credit",
        "cardType": "Visa",
        "nickName": "",
        "cardHolderFirstName": "Carol",
        "cardHolderLastName": "Smith",
        "email" : "csmith@example.com",
        "comments" : "",
        "accountId": "000XXXXXXXX"
    },
    "address":{
        "street": "128 1st Street",
        "city": "San Francisco",
        "state": "CA",
        "country": "USA",
        "postalCode": "94015",
        "companyName": "Salesforce"
    },
    "paymentGatewayId" : "000XXXXXXXX",
    "email": ""
    "ipAddress": "",
    "macAddress": "",
    "phone": "",
   
    "additionalData":{
         //add additional information if needed
        "key1":"value1",
        "key2":"value2",
        "key3":"value3",
        "key4":"value4",
        "key5":"value5"
    }
}

示例成功响应

成功的令牌化响应会更新付款方式,并提供以下信息 网关响应和任何支付网关日志。

{
  "paymentMethod": {
    "id": "03OR0000000xxxxxxx",
    "accountId" : "001xx000000xxxxxxx",
    "status" : "Active"
  },
  "gatewayResponse" : {
    "gatewayResultCode": "00",
    "gatewayResultCodeDescription": "Transaction Normal",
    "gatewayDate": "2020-12-08T04:03:20.000Z",
    "gatewayAvsCode" : "7638788018713617",
    "gatewayMessage" : "8313990738208498",
    "salesforceResultCode": "Success",
    "gatewayTokenEncrypted" : "SF701252"
  }
  "paymentGatewayLogs" : [ {
    "createdDate" : "2020-12-08T04:03:20.000Z",
    "gatewayResultCode" : "00",
    "id" : "0XtR0000000xxxxxxx",
    "interactionStatus" : "NoOp"
  } ],
}

其他付款方式

另一种付款方式允许客户存储和表示付款方式 未由其他预定义的付款方式(如 或 )表示的信息。替代支付方式的常见示例包括 CashOnDeliver、 Klarna 和直接借记。API v51.0 和 后。

CardPaymentMethodDigitalWallet

为组织中的每种替代付款方式创建唯一的记录类型。这边 每种替代付款方式都可以显示不同的选择列表值和页面布局 基于方法提供程序和网关提供程序的要求。例如,你可以有一个 直接借记的替代付款方式记录类型和现金支付的其他记录类型 提供。

我们还建议您为每种独特的替代付款方式记录类型创建一个。GtwyProviderPaymentMethodType

AlternativePaymentMethod 默认启用了私有共享模型,用于内部和 外部用户。只有记录所有者和具有较高所有权的用户才具有“读取”、“编辑”和“删除” 访问。

假设您想为 GiroPay 提供另一种付款方式。首先,创建记录类型。

AlternativePaymentMethod

新增功能 记录类型

/services/data/v51.0/sobjects/RecordType
{
 "Name" : "Giro Pay",
 "DeveloperName" : "GiroPay",
 "SobjectType" : "AlternativePaymentMethod"
}

下一个 为记录类型创建备用付款方式记录。AlternativePaymentMethod

新增功能 AlternativePaymentMethod

/services/data/v51.0/sobjects/AlternativePaymentMethod
{
 "ProcessingMode": "External",
 "status":"Active",
 "GatewayToken":"mHkDsh0oIA3mnWjo9UL",
"NickName" : "MyGiroPay",
"RecordTypeId" : "{record_type_id}"
}

您还可以创建网关提供商付款 方法类型。

新增功能 GtwyProvPaymentMethodType

{
 "PaymentGatewayProviderId": "XXXXXXXXXXXXXXX",
 "PaymentMethodType":"AlternativePaymentMethod",
 "GtwyProviderPaymentMethodType" : "PM_Giro",
 "DeveloperName" : "DevName",
 "MasterLabel" : "MasterLabel",
 "RecordTypeId" : "{record_type_id}"
}

处理付款

在支付网关中处理付款。

要访问 API,您需要 PaymentPlatform 组织 许可。

commercepayments

  1. 从 PaymentGatewayContext 类获取付款捕获请求对象。commercepayments.CaptureRequest = (commercepayments.CaptureRequest)gatewayContext.getPaymentRequest()
  2. 设置 HTTP 请求对象。HttpRequest req = new HttpRequest(); req.setHeader('Content-Type', 'application/json');
  3. 从 CaptureRequest 对象中读取参数并准备 HTTP 请求正文。
  4. 使用 PaymentsHttp 类对网关进行 HTTP 调用。commercepayments.PaymentsHttp http = new commercepayments.PaymentsHttp(); HttpResponse res = http.send(req);
  5. 分析 httpResponse 并准备 CaptureResponse 对象。commercepayments.CaptureResponse captureResponse = new commercepayments.CaptureResponse(); captureResponse.setGatewayResultCode(“”); captureResponse.setGatewayResultCodeDescription(“”); captureResponse.setGatewayReferenceNumber(“”); captureResponse.setSalesforceResultCodeInfo(getSalesforceResultCodeInfo(commercepayments.SalesforceResultCode.SUCCESS.name())); captureResponse.setGatewayReferenceDetails(“”); captureResponse.setAmount(double.valueOf(100);
  6. 返回 captureResponse

处理退款

在支付网关中处理退款。

要访问 API,您需要 PaymentPlatform 组织权限。

commercepayments

  1. 从 PaymentGatewayContext 类中获取引用的退款请求对象。commercepayments.ReferencedRefundRequest = (commercepayments.ReferencedRefundRequest)gatewayContext.getPaymentRequest();
  2. 设置 HTTP 请求对象。HttpRequest req = new HttpRequest(); req.setHeader('Content-Type', 'application/json');
  3. 从 ReferencedRefundRequest 对象中读取参数,并准备 HTTP 请求正文。
  4. 使用PaymentsHttp 类对网关进行 HTTP 调用。commercepayments.PaymentsHttp http = new commercepayments.PaymentsHttp(); HttpResponse res = http.send(req);
  5. 分析 httpResponse 并准备 ReferencedRefundResponse 对象。commercepayments.ReferencedRefundResponse referencedRefundResponse = new commercepayments.ReferencedRefundResponse(); referencedRefundResponse.setGatewayResultCode(“”); referencedRefundResponse.setGatewayResultCodeDescription(“”); referencedRefundResponse.setGatewayReferenceNumber(“”); referencedRefundResponse.setSalesforceResultCodeInfo(getSalesforceResultCodeInfo(commercepayments.SalesforceResultCode.SUCCESS.name())); referencedRefundResponse.setGatewayReferenceDetails(“”); referencedRefundResponse.setAmount(double.valueOf(100);
  6. 返回 referencedRefundResponse

幂等性准则

幂等性表示支付网关识别重复请求的能力 错误或恶意提交,然后相应地处理重复请求。 使用幂等网关时,请考虑以下重要准则。

要访问 API,您需要 PaymentPlatform 组织权限。

commercepayments

付款网关适配器类链接到 paymentGatewayProvider 对象记录。船级社 Payments 为自己的服务请求提供自己的幂等性层。每笔付款 网关还可以指定它们的值 在 paymentGatewayProvider 中 对象 记录。如果 Salesforce CCS Payment API 检测到重复请求和网关提供商 支持幂等性,请求正文的参数变为 .idempotencySupportedduplicateTrue

commercepayments.CaptureRequest request = 
(commercepayments.CaptureRequest)paymentGatewayContext.getPaymentRequest();
Boolean isDuplicate = requestObject.duplicate

幂等密钥可以从请求对象中获取。

String idempotencyKey = request.idempotencyKey

CommercePayments 的示例支付网关实施

我们创建了一个 GitHub 存储库,其中包含示例 Payeezy 付款的代码示例 具有 CommercePayments 命名空间的网关实现。如果需要,请查看示例代码 帮助配置支付网关实施。

查看 CommercePayments Gateway 参考实现中的代码示例,了解 Payeezy 存储库。

将 Salesforce 功能与 Apex 结合使用

Salesforce 用户界面的许多功能都在 Apex 中公开,因此您可以 在 Lightning 平台中以编程方式访问它们。例如,您可以编写 Apex 代码 发布到 Chatter 摘要,或使用审批方法提交和审批流程 请求。

  • 操作 创建快速操作
    ,并将其添加到 Salesforce Classic 主页、Chatter 选项卡、Chatter 小组和记录详细信息页面。从标准快速操作(例如创建和更新操作)中进行选择,或根据公司的需求创建自定义操作。
  • 审批流程 审批流程
    可自动执行 Salesforce 中记录的审批方式。审批流程指定审批的每个步骤,包括向谁请求审批以及在流程的每个点执行的操作。
  • 身份验证
    Salesforce 提供了多种对用户进行身份验证的方法。构建身份验证方法组合,以满足组织和用户使用模式的需求。
  • Chatter 答案和想法 在 Chatter 答案和想法中,使用区域将想法
    和答案组织成组。每个区域都可以有自己的重点,有独特的想法和答案主题来匹配该重点。
  • CommercePayments 命名空间的用例
    查看平台的演练、用例和参考资料。CommercePayments
  •  Apex 中连接 使用 Connect in Apex
    在 Salesforce 中开发自定义体验。Connect in Apex 提供对 B2B Commerce、CMS 托管内容、Experience Cloud 站点、主题等的编程访问。创建显示 Chatter 摘要的 Apex 页面,发布包含提及和主题的摘要项目,以及更新用户和群组照片。创建更新 Chatter 摘要的触发器。
  • 使用触发器
    审核 Chatter 私人消息 为 ChatterMessage 编写触发器,以自动审核组织或 Experience Cloud 站点中的私人消息。使用触发器确保邮件符合公司的邮件策略,并且不包含列入阻止列表的字词。
  • Apex
    中的 DataWeave Apex 中的 DataWeave 使用 Mulesoft DataWeave 库从一种格式读取和解析数据,对其进行转换,并以另一种格式导出。您可以将 DataWeave 脚本创建为元数据,并直接从 Apex 调用它们。与 Apex 一样,DataWeave 脚本在 Salesforce 应用程序服务器中运行,对执行代码实施相同的堆和 CPU 限制。
  • 使用触发器
    审核源项 为 FeedItem 编写触发器,以自动审核组织或 Experience Cloud 站点中的帖子。使用触发器确保帖子符合贵公司的沟通政策,并且不包含不需要的字词或短语。
  • Experience Cloud 站点 Experience Cloud 站点
    是供员工、客户和合作伙伴联系的品牌空间。您可以自定义和创建网站以满足您的业务需求,然后在它们之间无缝转换。
  • 电子邮件
    您可以使用 Apex 处理入站和出站电子邮件。
  • 外部服务 外部服务
    将您的 Salesforce 组织连接到 Salesforce 外部的服务,例如员工银行服务。注册外部服务后,可以在 Apex 代码中以本机方式调用它。在外部服务的已注册 API 规范中定义的对象和操作将成为命名空间中的 Apex 类和方法。已注册服务的架构类型映射到 Apex 类型,并且是强类型的,因此 Apex 编译器可以为您完成繁重的工作。例如,您可以从 Apex 对外部服务进行类型安全标注,而无需使用该类或对 JSON 字符串执行转换。ExternalServiceHttp
  • Flows Flow
    Builder 允许管理员构建应用程序(称为),通过收集数据并在 Salesforce 组织或外部系统中执行某些操作来自动执行业务流程。
  • 元数据 Salesforce 使用元数据类型和组件来表示组织配置和自定义。元数据用于管理员控制的组织设置,或已安装的应用和包应用的配置信息。
  • 权限集组 若要为权限集组
    提供 Apex 测试覆盖率,请使用类中的方法编写测试。calculatePermissionSetGroup()System.Test
  • 平台缓存
    Lightning 平台缓存层在缓存 Salesforce 会话和组织数据时提供更快的性能和更好的可靠性。指定要缓存的内容和时间,而无需使用自定义对象和设置或重载 Visualforce 视图状态。平台缓存通过分配缓存空间来提高性能,以便某些应用程序或操作不会从其他应用程序或操作窃取容量。
  • Salesforce Knowledge Salesforce Knowledge
    是一个知识库,用户可以在其中轻松创建和管理内容(称为文章),并快速查找和查看他们需要的文章。
  • Salesforce Files 使用 Apex 自定义 Salesforce Files
    的行为。
  • Salesforce Connect Apex 代码可以通过任何 Salesforce Connect
    适配器访问外部对象数据。使用 Apex 连接器框架为 Salesforce Connect 开发自定义适配器。自定义适配器可以从外部系统检索数据并在本地合成数据。Salesforce Connect 在 Salesforce 外部对象中表示该数据,使用户和 Lightning Platform 能够与存储在 Salesforce 组织外部的数据无缝交互。
  • 通过 Apex 的 Salesforce 报告和仪表板 API 通过 Apex
    的 Salesforce 报告和仪表板 API,您可以以编程方式访问报告生成器中定义的报告数据。
  • Salesforce Sites Salesforce Sites
    允许您通过继承 Lightning 平台功能(包括分析、工作流程和审批以及可编程逻辑)来构建自定义页面和 Web 应用程序。
  • 支持
    支持类允许您与支持中心常用的记录(如工作时间和案例)进行交互。
  • 区域管理 2.0
    通过对 Territory2 和 UserTerritory2Association 标准对象的触发器支持,可以自动执行与这些区域管理记录中的更改相关的操作和过程。

Action

创建快速操作,并将其添加到 Salesforce Classic 主页和 Chatter 中 选项卡,以添加到 Chatter 小组,并记录详细信息页面。从标准快速操作中进行选择,例如 创建和更新操作,或根据公司的需求创建自定义操作。

  • 创建操作允许用户创建记录,例如“新建联系人”、“新建” 机会和新的线索。
  • 自定义操作调用 Lightning 组件、流程、Visualforce 页面或 具有你定义的功能的画布应用。使用 Visualforce 页面、Lightning 组件或画布应用,用于为不需要的任务创建全局自定义操作 用户使用与特定对象有关系的记录。特定于对象的自定义 操作调用 Lightning 组件、流程、Visualforce 页面或画布应用程序,这些应用程序允许 用户与对象记录交互或创建与对象记录有关系的记录。

对于创建、记录呼叫和自定义操作,您可以创建特定于对象的操作或全局操作。更新操作必须特定于对象。

审批处理

审批流程可自动在 Salesforce 中审批记录。批准 流程指定审批的每个步骤,包括向谁请求审批以及要执行的操作 在过程的每个点。

  • 使用 Apex 流程类创建审批请求并处理这些请求的结果:
    • ProcessRequest 类
    • ProcessResult 类
    • ProcessSubmitRequest 类
    • ProcessWorkItemRequest 类
  • 使用该方法提交 批准请求和批准或拒绝现有批准请求。有关详细信息,请参阅审批类。Approval.process

注意

该方法计入 DML 限制 为您的组织。请参阅执行调控器和限制。process

有关审批流程的详细信息,请参阅 Salesforce 联机帮助。

  • Apex 审批处理示例

Apex 审批处理示例

以下示例代码首先提交记录以供审批,然后批准请求。 此示例假定帐户上预先存在的审批流程存在且有效 用于创建的帐户记录。

public class TestApproval {
    void submitAndProcessApprovalRequest() {
        // Insert an account
        Account a = new Account(Name='Test',annualRevenue=100.0);
        insert a;
           
        User user1 = [SELECT Id FROM User WHERE Alias='SomeStandardUser'];
            
        // Create an approval request for the account
        Approval.ProcessSubmitRequest req1 = 
            new Approval.ProcessSubmitRequest();
        req1.setComments('Submitting request for approval.');
        req1.setObjectId(a.id);
        
        // Submit on behalf of a specific submitter
        req1.setSubmitterId(user1.Id); 
        
        // Submit the record to specific process and skip the criteria evaluation
        req1.setProcessDefinitionNameOrId('PTO_Request_Process');
        req1.setSkipEntryCriteria(true);
        
        // Submit the approval request for the account
        Approval.ProcessResult result = Approval.process(req1);
        
        // Verify the result
        System.assert(result.isSuccess());
        
        System.assertEquals(
            'Pending', result.getInstanceStatus(), 
            'Instance Status'+result.getInstanceStatus());
        
        // Approve the submitted request
        // First, get the ID of the newly created item
        List<Id> newWorkItemIds = result.getNewWorkitemIds();
        
        // Instantiate the new ProcessWorkitemRequest object and populate it
        Approval.ProcessWorkitemRequest req2 = 
            new Approval.ProcessWorkitemRequest();
        req2.setComments('Approving request.');
        req2.setAction('Approve');
        req2.setNextApproverIds(new Id[] {UserInfo.getUserId()});
        
        // Use the ID from the newly created item to specify the item to be worked
        req2.setWorkitemId(newWorkItemIds.get(0));
        
        // Submit the request for approval
        Approval.ProcessResult result2 =  Approval.process(req2);
        
        // Verify the results
        System.assert(result2.isSuccess(), 'Result Status:'+result2.isSuccess());
        
        System.assertEquals(
            'Approved', result2.getInstanceStatus(), 
            'Instance Status'+result2.getInstanceStatus());
    }
}

认证

Salesforce 提供了多种对用户进行身份验证的方法。构建 满足组织需求和用户使用的身份验证方法 模式。

  • 创建自定义身份验证提供程序插件
    您可以使用 Apex 创建基于 OAuth 的自定义身份验证提供程序插件,以便单点登录 (SSO) 到 Salesforce。

创建自定义身份验证提供程序插件

您可以使用 Apex 为 单点登录 (SSO) 到 Salesforce。

开箱即用,Salesforce 支持多个外部身份验证提供商用于单个 登录,包括 Facebook、Google、LinkedIn 以及实现 OpenID Connect 协议。通过使用 Apex 创建插件,您可以添加自己的插件 基于 OAuth 的身份验证提供程序。然后,您的用户可以使用他们 已用于 Salesforce 组织的非 Salesforce 应用程序。

在创建 Apex 类之前,请为 身份验证提供程序。有关详细信息,请参阅创建自定义外部身份验证 提供程序。

示例类

此示例扩展抽象类以配置外部身份验证 提供程序称为 Concur。在 按照顺序。

Auth.AuthProviderPluginClass

  1. 同意
  2. ConcurTestStaticVar
  3. MockHttpResponseGenerator
  4. ConcurTest类
global class Concur extends Auth.AuthProviderPluginClass {
               
               public String redirectUrl; // use this URL for the endpoint that the authentication provider calls back to for configuration
               private String key;
               private String secret;
               private String authUrl;    // application redirection to the Concur website for authentication and authorization
               private String accessTokenUrl; // uri to get the new access token from concur  using the GET verb
               private String customMetadataTypeApiName; // api name for the custom metadata type created for this auth provider
               private String userAPIUrl; // api url to access the user in concur
               private String userAPIVersionUrl; // version of the user api url to access data from concur
               
       
               global String getCustomMetadataType() {
                   return customMetadataTypeApiName;
               }
       
               global PageReference initiate(Map<string,string> authProviderConfiguration, String stateToPropagate) {
                   authUrl = authProviderConfiguration.get('Auth_Url__c');
                   key = authProviderConfiguration.get('Key__c');
                   //Here the developer can build up a request of some sort
                   //Ultimately they’ll return a URL where we will redirect the user
                   String url = authUrl + '?client_id='+ key +'&scope=USER,EXPRPT,LIST&redirect_uri='+ redirectUrl + '&state=' + stateToPropagate;
                   return new PageReference(url);
                }
        
               global Auth.AuthProviderTokenResponse handleCallback(Map<string,string> authProviderConfiguration, Auth.AuthProviderCallbackState state ) {
                   //Here, the developer will get the callback with actual protocol.  
                   //Their responsibility is to return a new object called AuthProviderToken
                   //This will contain an optional accessToken and refreshToken
                   key = authProviderConfiguration.get('Key__c');
                   secret = authProviderConfiguration.get('Secret__c');
                   accessTokenUrl = authProviderConfiguration.get('Access_Token_Url__c');
                   
                   Map<String,String> queryParams = state.queryParameters;
                   String code = queryParams.get('code');
                   String sfdcState = queryParams.get('state');
                   
                   HttpRequest req = new HttpRequest();
                   String url = accessTokenUrl+'?code=' + code + '&client_id=' + key + '&client_secret=' + secret;
                   req.setEndpoint(url);
                   req.setHeader('Content-Type','application/xml');
                   req.setMethod('GET');
                   
                   Http http = new Http();
                   HTTPResponse res = http.send(req);
                   String responseBody = res.getBody();
                   String accessToken = getTokenValueFromResponse(responseBody, 'AccessToken', null);
                   //Parse access token value
                   String refreshToken = getTokenValueFromResponse(responseBody, 'RefreshToken', null);
                   //Parse refresh token value
                   
                   return new Auth.AuthProviderTokenResponse('Concur', accessToken, 'refreshToken', sfdcState);
                   //don’t hard-code the refresh token value!
                }
    
    
                 global Auth.UserData  getUserInfo(Map<string,string> authProviderConfiguration, Auth.AuthProviderTokenResponse response) { 
                     //Here the developer is responsible for constructing an Auth.UserData object
                     String token = response.oauthToken;
                     HttpRequest req = new HttpRequest();
                     userAPIUrl = authProviderConfiguration.get('API_User_Url__c');
                     userAPIVersionUrl = authProviderConfiguration.get('API_User_Version_Url__c');
                     req.setHeader('Authorization', 'OAuth ' + token);
                     req.setEndpoint(userAPIUrl);
                     req.setHeader('Content-Type','application/xml');
                     req.setMethod('GET');
                     
                     Http http = new Http();
                     HTTPResponse res = http.send(req);
                     String responseBody = res.getBody();
                     String id = getTokenValueFromResponse(responseBody, 'LoginId',userAPIVersionUrl);
                     String fname = getTokenValueFromResponse(responseBody, 'FirstName', userAPIVersionUrl);
                     String lname = getTokenValueFromResponse(responseBody, 'LastName', userAPIVersionUrl);
                     String flname = fname + ' ' + lname;
                     String uname = getTokenValueFromResponse(responseBody, 'EmailAddress', userAPIVersionUrl);
                     String locale = getTokenValueFromResponse(responseBody, 'LocaleName', userAPIVersionUrl);
                     Map<String,String> provMap = new Map<String,String>();
                     provMap.put('what1', 'noidea1');
                     provMap.put('what2', 'noidea2');
                     return new Auth.UserData(id, fname, lname, flname, uname,
                          'what', locale, null, 'Concur', null, provMap);
                }
                
                private String getTokenValueFromResponse(String response, String token, String ns) {
                    Dom.Document docx = new Dom.Document();
                    docx.load(response);
                    String ret = null;

                    dom.XmlNode xroot = docx.getrootelement() ;
                    if(xroot != null){
                       ret = xroot.getChildElement(token, ns).getText();
                    }
                    return ret;
                }  
    
}

示例测试类

下面的示例包含 Concur 类的测试类。

@IsTest
public class ConcurTestClass {

    private static final String OAUTH_TOKEN = 'testToken';
    private static final String STATE = 'mocktestState';
    private static final String REFRESH_TOKEN = 'refreshToken';
    private static final String LOGIN_ID = 'testLoginId';
    private static final String USERNAME = 'testUsername';
    private static final String FIRST_NAME = 'testFirstName';
    private static final String LAST_NAME = 'testLastName';
    private static final String EMAIL_ADDRESS = 'testEmailAddress';
    private static final String LOCALE_NAME = 'testLocalName';
    private static final String FULL_NAME = FIRST_NAME + ' ' + LAST_NAME;
    private static final String PROVIDER = 'Concur';
    private static final String REDIRECT_URL = 'http://localhost/services/authcallback/orgId/Concur';
    private static final String KEY = 'testKey';
    private static final String SECRET = 'testSecret';
    private static final String STATE_TO_PROPOGATE  = 'testState';
    private static final String ACCESS_TOKEN_URL = 'http://www.dummyhost.com/accessTokenUri';
    private static final String API_USER_VERSION_URL = 'http://www.dummyhost.com/user/20/1';
    private static final String AUTH_URL = 'http://www.dummy.com/authurl';
    private static final String API_USER_URL = 'www.concursolutions.com/user/api';

    // in the real world scenario , the key and value would be read from the (custom fields in) custom metadata type record
    private static Map<String,String> setupAuthProviderConfig () {
            Map<String,String> authProviderConfiguration = new Map<String,String>();
           authProviderConfiguration.put('Key__c', KEY);
           authProviderConfiguration.put('Auth_Url__c', AUTH_URL);
           authProviderConfiguration.put('Secret__c', SECRET);
           authProviderConfiguration.put('Access_Token_Url__c', ACCESS_TOKEN_URL);
           authProviderConfiguration.put('API_User_Url__c',API_USER_URL);
           authProviderConfiguration.put('API_User_Version_Url__c',API_USER_VERSION_URL);
           authProviderConfiguration.put('Redirect_Url__c',REDIRECT_URL);
           return authProviderConfiguration;
          
    }

    static testMethod void testInitiateMethod() {
           String stateToPropogate = 'mocktestState';
           Map<String,String> authProviderConfiguration = setupAuthProviderConfig();
           Concur concurCls = new Concur();
           concurCls.redirectUrl = authProviderConfiguration.get('Redirect_Url__c');
           
           PageReference expectedUrl =  new PageReference(authProviderConfiguration.get('Auth_Url__c') + '?client_id='+ 
                                               authProviderConfiguration.get('Key__c') +'&scope=USER,EXPRPT,LIST&redirect_uri='+ 
                                               authProviderConfiguration.get('Redirect_Url__c') + '&state=' + 
                                               STATE_TO_PROPOGATE);
           PageReference actualUrl = concurCls.initiate(authProviderConfiguration, STATE_TO_PROPOGATE);
           System.assertEquals(expectedUrl.getUrl(), actualUrl.getUrl());
       }
    
    static testMethod void testHandleCallback() {
           Map<String,String> authProviderConfiguration = setupAuthProviderConfig();
           Concur concurCls = new Concur();
           concurCls.redirectUrl = authProviderConfiguration.get('Redirect_Url_c');

           Test.setMock(HttpCalloutMock.class, new ConcurMockHttpResponseGenerator());

           Map<String,String> queryParams = new Map<String,String>();
           queryParams.put('code','code');
           queryParams.put('state',authProviderConfiguration.get('State_c'));
           Auth.AuthProviderCallbackState cbState = new Auth.AuthProviderCallbackState(null,null,queryParams);
           Auth.AuthProviderTokenResponse actualAuthProvResponse = concurCls.handleCallback(authProviderConfiguration, cbState);
           Auth.AuthProviderTokenResponse expectedAuthProvResponse = new Auth.AuthProviderTokenResponse('Concur', OAUTH_TOKEN, REFRESH_TOKEN, null);
           
           System.assertEquals(expectedAuthProvResponse.provider, actualAuthProvResponse.provider);
           System.assertEquals(expectedAuthProvResponse.oauthToken, actualAuthProvResponse.oauthToken);
           System.assertEquals(expectedAuthProvResponse.oauthSecretOrRefreshToken, actualAuthProvResponse.oauthSecretOrRefreshToken);
           System.assertEquals(expectedAuthProvResponse.state, actualAuthProvResponse.state);
           

    }
    
    
    static testMethod void testGetUserInfo() {
           Map<String,String> authProviderConfiguration = setupAuthProviderConfig();
           Concur concurCls = new Concur();
                      
           Test.setMock(HttpCalloutMock.class, new ConcurMockHttpResponseGenerator());

           Auth.AuthProviderTokenResponse response = new Auth.AuthProviderTokenResponse(PROVIDER, OAUTH_TOKEN ,'sampleOauthSecret', STATE);
           Auth.UserData actualUserData = concurCls.getUserInfo(authProviderConfiguration, response) ;
           
           Map<String,String> provMap = new Map<String,String>();
           provMap.put('key1', 'value1');
           provMap.put('key2', 'value2');
                     
           Auth.UserData expectedUserData = new Auth.UserData(LOGIN_ID, FIRST_NAME, LAST_NAME, FULL_NAME, EMAIL_ADDRESS,
                          null, LOCALE_NAME, null, PROVIDER, null, provMap);
          
           System.assertNotEquals(expectedUserData,null);
           System.assertEquals(expectedUserData.firstName, actualUserData.firstName);
           System.assertEquals(expectedUserData.lastName, actualUserData.lastName);
           System.assertEquals(expectedUserData.fullName, actualUserData.fullName);
           System.assertEquals(expectedUserData.email, actualUserData.email);
           System.assertEquals(expectedUserData.username, actualUserData.username);
           System.assertEquals(expectedUserData.locale, actualUserData.locale);
           System.assertEquals(expectedUserData.provider, actualUserData.provider);
           System.assertEquals(expectedUserData.siteLoginUrl, actualUserData.siteLoginUrl);
    }
    
    
   // implementing a mock http response generator for concur 
   public  class ConcurMockHttpResponseGenerator implements HttpCalloutMock {
     public HTTPResponse respond(HTTPRequest req) {
        String namespace = API_USER_VERSION_URL;
        String prefix = 'mockPrefix';

        Dom.Document doc = new Dom.Document();
        Dom.XmlNode xmlNode =  doc.createRootElement('mockRootNodeName', namespace, prefix);
        xmlNode.addChildElement('LoginId', namespace, prefix).addTextNode(LOGIN_ID);
        xmlNode.addChildElement('FirstName', namespace, prefix).addTextNode(FIRST_NAME);
        xmlNode.addChildElement('LastName', namespace, prefix).addTextNode(LAST_NAME);
        xmlNode.addChildElement('EmailAddress', namespace, prefix).addTextNode(EMAIL_ADDRESS);
        xmlNode.addChildElement('LocaleName', namespace, prefix).addTextNode(LOCALE_NAME);            
        xmlNode.addChildElement('AccessToken', null, null).addTextNode(OAUTH_TOKEN);
        xmlNode.addChildElement('RefreshToken', null, null).addTextNode(REFRESH_TOKEN);
        System.debug(doc.toXmlString());
        // Create a fake response
        HttpResponse res = new HttpResponse();
        res.setHeader('Content-Type', 'application/xml');
        res.setBody(doc.toXmlString());
        res.setStatusCode(200);
        return res;
    }
   
  }
}

Chatter 答案和想法

在 Chatter 答案和想法中,使用区域将想法和答案组织到组中。每 区域可以有自己的重点,有独特的想法和答案主题来匹配该重点。

要在 Apex 中使用区域,请使用 、 和 类。AnswersIdeasConnectApi.Zones

Apex 事务和调速器限制

Apex Transactions 确保数据的完整性。Apex 代码作为原子的一部分运行 交易。调速器执行限制确保有效利用资源 Lightning Platform 多租户平台。

大多数调控器限制是按事务设置的,有些则不是,例如 24 小时限制 限制。

为确保 Apex 遵守调速器限制,应使用某些设计模式, 例如查询中的批量调用和外键关系。

  • Apex 事务
    Apex 事务表示作为单个单元执行的一组操作。事务中的所有 DML 操作都必须成功完成。如果在一个操作中发生错误,则回滚整个事务,并且不会将任何数据提交到数据库。事务的边界可以是触发器、类方法、匿名代码块、Visualforce 页面或自定义 Web 服务方法。
  • 执行调控器和限制
    由于 Apex 在多租户环境中运行,因此 Apex 运行时引擎会严格执行限制,以便失控的 Apex 代码或进程不会独占共享资源。如果某些 Apex 代码超出限制,则关联的调控器会发出无法处理的运行时异常。
  • 设置调控器限制电子邮件警告
    您可以指定组织中的用户在调用超过 50% 的已分配调控器限制的 Apex 代码时接收电子邮件通知。仅检查每个请求的限制以发送电子邮件警告;不检查每个组织的限制,例如并发长时间运行的请求。这些电子邮件通知不计入每日单封电子邮件限制。
  • 在调控器执行限制
    内运行 Apex 当您在多租户云环境(如 Lightning 平台)中开发软件时,您不必扩展代码,因为 Lightning 平台会为您完成这些工作。由于资源在多租户平台中共享,因此 Apex 运行时引擎会强制执行一些限制,以确保没有一个事务独占共享资源。

Apex 事务

Apex 事务表示一组操作,这些操作作为 单个单元。事务中的所有 DML 操作都必须成功完成。如果错误 在一次操作中发生,整个事务被回滚,并且没有数据被提交到 数据库。事务的边界可以是触发器、类方法、匿名 代码块、Visualforce 页面或自定义 Web 服务方法。

注意

付款交易是 DML 操作错误的例外。即使出现错误 发生时,数据已提交并生成付款记录,因为交易具有 已经发生在支付网关上。

在事务边界内发生的所有操作都表示 操作,包括对外部代码的调用,例如在 事务边界。例如:自定义 Apex Web 服务方法导致触发器 触发,这反过来又调用类中的方法。在本例中,所有更改都是 只有在事务中的所有操作都完成执行后才提交到数据库 并且不会导致任何错误。如果在任何中间步骤中发生错误, 所有数据库更改都将回滚,并且不会提交事务。

Apex 事务有时称为执行上下文。本指南使用 术语 Apex 交易。

交易有什么用?

当多个操作相关且全部或无操作时,事务非常有用 的操作已提交。目标是使数据库保持一致 州。有许多业务方案可以从事务处理中受益。 例如,将资金从一个银行账户转移到另一个银行账户是很常见的 场景。它涉及从第一个账户中借记并记入第二个账户 与要转移的金额。这两个操作必须一起提交到 数据库。如果借方操作成功而贷方操作失败,则 账户余额变得不一致。

此示例演示当最后一个操作导致验证时,如何回滚方法中的所有 DML 操作 规则失败。在此示例中,方法是事务边界,即在此方法中运行的所有代码 要么将所有更改提交到平台数据库,要么回滚所有更改。在 在本例中,我们添加了一个发票对帐单,其中包含铅笔商品的行项目。 该行项目用于购买Units_Sold__c中指定的 5,000 支铅笔 字段,这比整个铅笔库存的 1,000 多。这个例子 假设已设置验证规则来检查 商品足以支付新购买的费用。insertinvoice

由于此示例尝试购买的铅笔 (5,000) 多于库存商品 (1,000),验证规则将失败并引发异常。代码执行在 此点以及在此异常之前处理的所有 DML 操作都将回滚。 发票对帐单和行项不会添加到数据库中,并且它们的 DML 操作将滚动 返回。insert在开发者控制台中,执行静态方法。

invoice

// Only 1,000 pencils are in stock.
// Purchasing 5,000 pencils cause the validation rule to fail,
// which results in an exception in the invoice method.
Id invoice = MerchandiseOperations.invoice('Pencils', 5000, 'test 1');

这个定义就是方法。这 由于验证规则失败,总库存的更新会导致异常。如 因此,发票对帐单和行项将回滚,并且不会插入 添加到数据库中。invoice

public class MerchandiseOperations {
    public static Id invoice( String pName, Integer pSold, String pDesc) {
        // Retrieve the pencils sample merchandise
        Merchandise__c m = [SELECT Price__c,Total_Inventory__c
            FROM Merchandise__c WHERE Name = :pName LIMIT 1];
        // break if no merchandise is found
        System.assertNotEquals(null, m);
        // Add a new invoice
        Invoice_Statement__c i = new Invoice_Statement__c(
            Description__c = pDesc);
        insert i;

        // Add a new line item to the invoice
        Line_Item__c li = new Line_Item__c(
            Name = '1',
            Invoice_Statement__c = i.Id,
            Merchandise__c = m.Id,
            Unit_Price__c = m.Price__c,
            Units_Sold__c = pSold);
        insert li;

        // Update the inventory of the merchandise item 
        m.Total_Inventory__c -= pSold;
        // This causes an exception due to the validation rule
        // if there is not enough inventory.
        update m;
        return i.Id;
    }
}

执行调控器和限制

因为 Apex 在多租户环境中运行,Apex 运行时引擎严格执行限制,因此 失控的 Apex 代码或进程不会垄断共享资源。如果某些 Apex 代码 超过限制,关联的调控器会发出运行时异常,而该异常不能 处理。Apex 限制或调控器跟踪并强制执行 下表和部分。

  • 每笔交易最高限额
  • 每笔交易认证托管 包装限制
  • 闪电平台Apex 限制
  • 静态Apex限制
  • 特定尺寸的Apex限制
  • 其他Apex限制

除了核心的 Apex 调控器限制外,本文稍后还包括电子邮件限制和推送通知限制 主题,以方便您。

每笔交易最高限额

这些限制计入每笔 Apex 交易。对于 Batch Apex,这些 每次执行方法中的一批记录时,都会重置限制。execute

下表列出了同步 Apex 和 异步 Apex(Batch Apex 和未来方法),当它们不同时。否则 此表仅列出了适用于同步和异步 Apex 的一个限制。

注意

  • 尽管计划的 Apex 是一项异步功能,但同步限制适用于 计划的 Apex 作业。
  • 对于批量 API 和批量 API 2.0 事务,有效限制是 同步和异步限制。例如,Bulk Apex 作业的最大数量 添加到队列中时,是 同步限制 (50),高于异步限制 (1)。System.enqueueJob
描述同步限制异步限制
SOQL 查询总数 发出1100200
SOQL 检索的记录总数 查询50,00050,000
检索到的记录总数Database.getQueryLocator10,00010,000
发出的 SOSL 查询总数2020
单个检索到的记录总数 SOSL 查询2,0002,000
DML 语句总数 发出2150150
因此处理的记录总数 DML 语句, , 或Approval.processdatabase.emptyRecycleBin10,00010,000
以递归方式触发到期触发器的任何 Apex 调用的总堆栈深度 至 、 或语句insertupdatedelete31616
标注总数(HTTP 请求或 Web 服务调用)在事务中100100
所有标注(HTTP 请求或 Web 服务)的最大累积超时 calls) 在事务中120 秒120 秒
每个 Apex 允许的具有注释的方法的最大数量 调用future50在批处理和未来上下文中为 0;50 在可排队的上下文中
添加到队列的最大 Apex 作业数System.enqueueJob501
允许的方法总数sendEmail1010
总堆大小46兆字节12 兆字节
Salesforce 上的最大 CPU 时间 服务器510,000 毫秒60,000 毫秒
每个 Apex 事务的最长执行时间10 分钟10 分钟
每个 Apex 允许的最大推送通知方法调用数 交易1010
每个推送通知中可以发送的最大推送通知数 方法调用2,0002,000
配置为立即发布的平台事件的最大调用数EventBus.publish150150

1在具有父子关系子查询的 SOQL 查询中,每个 父子关系计为一个额外的查询。这些类型的查询限制为 是顶级查询数量的三倍。子查询的限制对应于 返回的值。这些关系查询的行计数对 整体代码执行。此限制不适用于自定义元数据类型。在单个 Apex 中 事务,自定义元数据记录可以有无限的 SOQL 查询。除了静态 SOQL 语句、对以下方法的调用计入 SOQL 语句数 在请求中发出。

Limits.getLimitAggregateQueries()

  • Database.countQuery,Database.countQueryWithBinds
  • Database.getQueryLocator,Database.getQueryLocatorWithBinds
  • Database.query,Database.queryWithBinds

2对以下方法的调用计入 DML 数 在请求中发出的语句。

  • Approval.process
  • Database.convertLead
  • Database.emptyRecycleBin
  • Database.rollback
  • Database.setSavePoint
  • deleteDatabase.delete
  • insertDatabase.insert
  • mergeDatabase.merge
  • undeleteDatabase.undelete
  • updateDatabase.update
  • upsertDatabase.upsert
  • EventBus.publish对于平台事件 配置为在提交后发布
  • System.runAs

3递归Apex,不会用 、 、 触发任何触发器 或语句,存在于单个 调用,使用单个堆栈。相反,触发触发器的递归 Apex 会生成 在新的 Apex 调用中触发。新的调用与 导致它触发的代码。生成 Apex 的新调用是一项更昂贵的操作 而不是单次调用中的递归调用。因此,对 这些类型的递归调用的堆栈深度。insertupdatedelete

4电子邮件服务堆大小为50 兆字节.

5计算 Salesforce 上所有执行的 CPU 时间 在一个 Apex 事务中发生的应用程序服务器。CPU 时间是针对 执行 Apex 代码,以及从此代码调用的任何进程,例如 package 代码和工作流。CPU 时间对于事务是私有的,并且与其他事务隔离 交易。不占用应用程序服务器 CPU 时间的操作不计算在内 朝向 CPU 时间。例如,在数据库中花费的执行时间部分 DML、SOQL 和 SOSL 不计算在内,Apex 标注的等待时间也不计算在内。应用程序服务器 在 DML 操作中花费的 CPU 时间计入 Apex CPU 限制,但不计入预期 意义重大。批量 API 和批量 API 2.0 对 CPU 时间使用唯一的调控器限制 Salesforce 服务器,最大值为60,000毫秒。

注意

  • 限制分别适用于每个 .testMethod
  • 确定代码的代码执行限制,同时 它正在运行,请使用 Limits 方法。例如,可以使用该方法确定 程序已调用的 DML 语句。或者,您可以使用该方法确定 代码可用的 DML 语句总数。getDMLStatementsgetLimitDMLStatements

每笔交易认证的托管包限制

经认证的托管软件包 – 已通过 AppExchange 的安全审查 – 针对大多数每个事务的限制,获取自己的一组限制。 Salesforce ISV 合作伙伴开发经过认证的托管软件包,这些软件包安装在您的组织中 从 AppExchange 并具有唯一的命名空间。

以下示例说明了单独的认证托管 DML 语句的包限制。如果您安装经过认证的托管软件包,则所有 Apex 该包中的代码有自己的 150 个 DML 语句。这些 DML 语句是附加的 到组织的本机代码可以执行的 150 个 DML 语句。此限制增加意味着更多 超过 150 个 DML 语句可以在单个事务期间执行,如果代码来自 package 和您的本机组织都会执行。同样,经过认证的托管包会获得其 除了组织的原生代码限制外,还拥有同步 Apex 的 100-SOQL 查询限制 100 个 SOQL 查询。

认证命名空间的数量没有限制 在单个事务中调用。但是,可以在 每个命名空间不得超过每个事务的限制。对 事务中跨命名空间可执行的累计操作数。这 累积限制是每个命名空间限制的 11 倍。例如,如果每个命名空间 SOQL 查询限制为 100 个,单个事务最多可以执行 1,100 个 SOQL 查询。在 在这种情况下,累积限制是每个命名空间限制 100 的 11 倍。这些查询 可以跨无限数量的命名空间执行,只要任何一个命名空间 查询不超过 100 个。累积限制不会影响以下限制 在所有命名空间之间共享,例如对最大 CPU 时间的限制。

注意

  • 这些跨命名空间限制仅适用于经过认证的托管命名空间 包。
  • 未经认证的包中的命名空间没有自己单独的调控器限制。 他们使用的资源继续计入 组织的自定义代码。

下表列出了累积的跨命名空间 限制。

描述累积跨命名空间限制
发出的 SOQL 查询总数1,100
检索到的记录总数Database.getQueryLocator110,000
发出的 SOSL 查询总数220
发出的 DML 语句总数1,650
标注(HTTP 请求或 Web 服务调用)的总数 交易1,100
方法总数 允许sendEmail110

对于经过认证的托管软件包,所有每个事务的限制都单独计算在内,但以下情况除外:

  • 总堆大小
  • 最大 CPU 时间
  • 最大交易执行时间
  • 唯一命名空间的最大数量

这些限制对整个事务都很重要,无论有多少经过认证的托管事务 包在同一事务中运行。

来自 AppExchange 的软件包中的代码,不是由 Salesforce ISV 合作伙伴创建的,也不是由 经过认证,没有自己单独的调速器限制。包使用的任何资源 计入组织管理器总数限制。累积资源消息和警告电子邮件 也是基于托管包命名空间生成的。

有关 Salesforce ISV 合作伙伴包的详细信息,请参阅 Salesforce 合作伙伴计划。

Lightning 平台 Apex 限制

此表中的限制不是 特定于 Apex 交易;Lightning Platform 强制执行这些限制。

描述限制
异步的最大数量 Apex 方法执行(批处理 Apex、future 方法、Queueable Apex和预定Apex)每 24 小时 时期1,6250,000或用户数量 组织中的许可证乘以 200,以较大者为准
长时间运行的同步并发事务数 每个交易持续时间超过 5 秒 组织。210
同时安排的 Apex 类的最大数量100. 在 Developer Edition 组织中,限制为 5 个。
Apex flex 队列中 Apex 任务的最大批处理数 在状态中Holding100
排队或活动的批处理 Apex 作业的最大数量 同时35
批处理 Apex 作业方法并发执行的最大数量start41
在运行中可以提交的最大批处理作业数 测试5
每 24 小时可排队的最大测试类数 期间(开发人员以外的生产组织 版本)5,6以较大者为准500 或 10 乘以 按组织中的测试类数
每 24 小时可排队的最大测试类数 期间(沙盒和开发者版 组织)5,6以较大者为准500 或 20 乘以组织中的测试类数

1对于 Batch Apex,方法执行包括 、 和 方法的执行。startexecutefinish此限制适用于您的整个组织,并且是共享的 使用所有异步 Apex:Batch Apex、Queueable Apex、scheduled Apex 和 未来的方法。计入此限制的许可证类型包括完整 Salesforce 和 Salesforce Platform 用户许可证、应用程序订阅用户 许可证、仅限 Chatter 用户、身份用户和公司社区 用户。

2如果在 10 个长时间运行时启动了更多事务 事务仍在运行,但被拒绝。HTTP 标注处理时间不是 在计算此限制时包括在内。

3提交批处理作业后,它们将保留在弹性中 在系统将它们排队等待处理之前进行排队。

4尚未启动的批处理作业仍保留在队列中 直到它们开始。如果正在运行多个作业,则此限制不会导致任何 批处理作业失败。批处理方法 Apex 作业仍并行运行。execute

5此限制适用于异步运行的测试。这 测试组包括通过 Salesforce 用户界面启动的测试 包括开发人员控制台或使用 SOAP 插入 ApexTestQueueItem 对象 API接口。

6检查有多少个异步 Apex 执行 可用,请向 REST API 资源发出请求或使用 Apex 方法或 .查看清单 《REST API 开发人员指南》中的组织限制和《Apex 参考指南》中的 OrgLimits 类。limitsOrgLimits.getAll()OrgLimits.getMap()

静态Apex限制

描述限制
标注(HTTP 请求或 Web 服务调用)的默认超时 交易10 秒
标注请求或响应的最大大小(HTTP 请求或 Web 服务) 电话)1同步 Apex 为 6 MB,同步 Apex 为 12 MB 异步 Apex
Salesforce 取消事务之前的最大 SOQL 查询运行时间120 秒
Apex 部署中类和触发器代码单元的最大数量7500
Apex 触发器批处理大小2200
For 循环列表批处理大小200
为 Batch Apex 查询返回的最大记录数Database.QueryLocator5000万

1 HTTP 请求和响应大小计入总堆 大小。

2 平台事件和更改的 Apex 触发器批处理大小 数据捕获事件为 2,000。

特定尺寸的Apex限制

描述限制
类的最大字符数100万
触发器的最大字符数100万
组织中所有 Apex 代码使用的最大代码量16兆字节
方法大小限制2编译形式的 65,535 字节码指令

1此限制不适用于第一代 (1GP) 中的 Apex 代码,或者 第二代 (2GP) 托管软件包。这些类型的包中的代码属于 命名空间与组织中的代码不同。此限制也不适用于任何代码 包含在使用 @isTest 注释定义的类中。

2超过允许限制的大型方法会导致异常 在代码执行期间被抛出。

其他Apex限制

在 Apex 中连接对于命名空间中的类,每个 写入操作会根据 Apex 调控器限制消耗一个 DML 语句。 方法调用也受费率影响 限制。 速率限制与 Connect 匹配 REST API 速率限制。两者都有每个用户、每个命名空间、每小时的速率限制。当你 超过速率限制,则抛出 A。您的 Apex 代码必须捕获和 处理此异常。ConnectApiConnectApiConnectApiConnectApi.RateLimitExceptionData.com 清洁如果您使用 Data.com Clean 产品及其自动化作业,请考虑如何使用 Apex触发器。如果您在运行的客户、联系人或潜在顾客记录上有 Apex 触发器 SOQL 查询,SOQL 查询可能会干扰这些对象的清理作业。你 Apex 触发器(组合)每批不得超过 200 个 SOQL 查询。如果他们这样做,你的 该对象的清理作业失败。此外,如果触发器调用方法,则每批调用次数限制为 10 次。futurefuture事件报告事件报告为非 系统管理员为20,000;对于系统管理员,100,000。Apex 测试中的MAX_DML_ROWS限制可以在单个 同步 Apex 测试执行上下文,限制为 450,000。例如,Apex 类可以有 45 个方法,每个方法插入 10,000 行。如果达到限制,您会看到 此错误: 。Your runallTests is consuming too many DB resourcesSOQL 查询性能为了获得最佳性能,SOQL 查询必须是有选择性的,尤其是对于内部的查询 触发器。为了避免长时间的执行,系统可以终止非选择性 SOQL 查询。当触发器中的非选择性查询时,开发人员会收到错误消息 对包含超过 200,000 条记录的对象执行。为避免此错误, 确保查询是有选择性的。查看更高效的 SOQL 查询。

电子邮件限制

入站电子邮件限制

电子邮件服务:处理的电子邮件的最大数量(包括 按需电子邮件到案例的限制)用户许可证数乘以1,000;最大 1,000,000
电子邮件服务:电子邮件的最大大小(正文和 附件)25兆字节1
On-Demand Email-to-Case:最大电子邮件附件大小25兆字节
On-Demand Email-to-Case:最大电子邮件数 处理(计入电子邮件服务限制)用户许可证数量乘以 1,000;最大 1,000,000

1电子邮件服务的电子邮件的最大大小各不相同 取决于字符集和正文部分的传输编码。电子邮件的大小 邮件包括电子邮件标题、正文、附件和编码。因此,一个 带有 35 MB 附件的电子邮件可能超过电子邮件的 25 MB 大小限制 在考虑标头、正文和编码之后。什么时候 定义电子邮件服务时,请注意以下几点:

  • 电子邮件服务仅处理在其 地址。
  • Salesforce 限制 所有电子邮件服务(包括按需电子邮件到案例)都可以处理 日常。超过此限制的邮件将被退回、丢弃或排队等待 第二天处理,具体取决于您如何为每个电子邮件服务配置失败响应设置。Salesforce 通过将 用户许可证数量增加 1,000 个;最大 1,000,000。例如,如果您有 10 个许可证,您的组织最多可以处理 10,000 封电子邮件 日。
  • 您在沙盒中创建的电子邮件服务地址无法复制到 生产组织。
  • 对于每个电子邮件服务,您可以告诉 Salesforce 将错误电子邮件发送到 指定的地址,而不是发件人的电子邮件地址。
  • 电子邮件服务拒绝电子邮件并通知发件人 如果电子邮件(正文文本、正文 HTML 和附件组合)超过 大约25兆字节 (因语言和性格而异 设置).

出站电子邮件:使用 Apex 发送的单封电子邮件和群发电子邮件的限制

每个获得许可的组织都可以将一封电子邮件发送到 每天最多 5,000 个外部电子邮件地址基于 格林威治标准时间(GMT)。对于在 Spring ’19 之前创建的组织,将强制执行每日限制 仅适用于通过 Apex 和 Salesforce API 发送的电子邮件,REST 除外 应用程序接口。对于在 Spring ’19 及之后创建的组织,每日限制为 也适用于电子邮件警报、简单电子邮件操作、发送电子邮件 流中的操作和 REST API。如果其中一封新计数的电子邮件 无法发送,因为您的组织已达到限制,我们会通知您 并添加一个条目到调试日志。 使用 Salesforce 中的电子邮件作者或作曲家不计入此内容 限制。 发送没有限制 向联系人、潜在顾客、个人帐户和用户发送一封电子邮件 直接从客户、联系人、潜在客户、商机、案例、 营销活动或自定义对象页面。 在 Developer Edition 组织中 以及在试用期内评估 Salesforce 的组织,您可以 最多发送到50每天的收件人,每封电子邮件最多可以有 15 个 收件人。.发送电子邮件时,请牢记以下注意事项:

  • 发送时 单个电子邮件,您最多可以指定150每个 SingleEmailMessage 中“收件人”、“抄送”和“密件抄送”字段中的收件人。每 字段也仅限于4,000 字节.
  • 如果您使用 SingleEmailMessage 通过电子邮件发送 org 的内部用户,指定用户的 ID in setTargetObjectId 表示电子邮件 不计入每日限额。然而 在 setToAddresses 中指定内部用户的电子邮件地址意味着电子邮件这样做 计入限制。
  • 您可以向最多 5,000 个外部电子邮件地址发送群发电子邮件和列表电子邮件 每个获得许可的 Salesforce 组织的天数。一天是根据格林威治标准时间 (GMT) 计算的。
  • 单封电子邮件、群发电子邮件和列表电子邮件限制计算重复的电子邮件地址。为 例如,如果您的电子邮件中有 10 倍的 johndoe@example.com 计为 10 个。
  • API 或 Apex 单个电子邮件最多可以发送到 5,000 个外部电子邮件地址 日。
  • 您可以通过 UI 向组织的内部发送无限量的电子邮件 用户,包括门户用户。
  • 您只能向联系人、个人帐户、潜在客户和您的 org 的内部用户。
  • 在 Developer Edition 组织和试用期内评估 Salesforce 的组织中,您可以发送 每个组织每天使用群发电子邮件和列表电子邮件的外部电子邮件收件人不超过 10 个。
  • 您无法使用 Visualforce 电子邮件模板发送群发电子邮件。

推 通知限制

一个组织最多可以发送 20,000 个 iOS 推送和 10,000 个 Android 推送 每小时通知(例如,4:00 到 4:59 UTC)。

只有可送达通知才计入此限制。例如,向 1,000 发送通知 贵公司的员工,但有 100 名员工尚未安装移动应用。 只有发送给已安装移动应用程序的 900 名员工的通知才算数 朝这个极限迈进。

通过测试推送生成的每个测试推送通知 通知页面仅限于单个收件人。测试推送通知计入 org 的每小时推送通知限制。

当组织的每小时推送通知限制为 满足,仍会通过以下方式创建任何其他通知以进行应用内显示和检索 REST API。

设置调速器限制电子邮件警告

您可以指定组织中的用户在以下情况下接收电子邮件通知 调用超过分配调控器的 50% 的 Apex 代码 限制。仅按请求 检查发送电子邮件警告的限制;每个组织的限制,例如并发长时间运行 不检查请求。这些电子邮件通知不计入每日单封电子邮件 限制。

  1. 以管理员用户身份登录 Salesforce。
  2. 在“设置”中,在“快速查找”框中输入, ,然后选择用户Users
  3. 单击用户名称旁边的编辑以接收电子邮件 通知。
  4. 选择“发送 Apex 警告电子邮件”选项。
  5. 点击保存

注意

当前会检查这些限制以发送电子邮件警告。

SOQL总数 发出的查询

SOQL 查询检索的记录总数

总数 发出的 SOSL 查询

发出的 DML 语句总数

记录总数 作为 DML 语句的结果进行处理,或者Approval.processdatabase.emptyRecycleBin

总堆大小

标注总数 (HTTP 请求或 Web 服务调用)在事务中

允许的方法总数sendEmail

最大方法数 每个 Apex 允许的注释 调用future

添加到队列的最大 Apex 作业数System.enqueueJob

检索到的记录总数Database.getQueryLocator

手机总数 Apex 推送调用

在调控器执行限制内运行 Apex

当您在多租户云环境(如 Lightning)中开发软件时 平台,您不必扩展代码,因为 Lightning 平台会为您完成。 由于资源在多租户平台中共享,因此 Apex 运行时引擎会强制执行 一些限制,以确保没有一个事务垄断共享资源。

您的 Apex 代码必须在这些预定义的执行限制内执行。如果调速器限制 超出,则会引发无法处理的运行时异常。通过以下 最佳做法,可以避免达到这些限制。想象一下,你必须洗 100件T恤。你会一个接一个地洗——每堆衣服洗一件,还是会 您将它们分批分组,只加载几次?在云中编码的好处是 您将学习如何编写更高效的代码并浪费更少的资源。

调控器执行限制是针对每个事务的。例如,一笔交易可以发出 最多 100 个 SOQL 查询和 150 个 DML 语句。还有其他一些限制 不受事务限制,例如可以排队的批处理作业数或 一次处于活动状态。

以下是编写不超过一定范围的代码的一些最佳实践 调速器限制。

批量 DML 呼叫

对 sObject 列表而不是每个单独的 sObject 进行 DML 调用使它 不太可能达到 DML 语句限制。下面是一个示例, 不会批量化 DML 操作,下一个示例显示了建议的 调用 DML 语句的方式。

例:对单个 sObject 的 DML 调用

for 循环遍历 List 变量中包含的行项。对于每个行项,它会为 Description__c字段,然后更新订单项。如果列表包含超过 150 个项目,第 151 个更新调用返回超出 DML 的运行时异常 语句限制为 150。我们如何解决这个问题?查看第二个示例的简单 溶液。liList

for(Line_Item__c li : liList) {
    if (li.Units_Sold__c > 10) {
        li.Description__c = 'New description';
    }
    // Not a good practice since governor limits might be hit.
    update li;
}

推荐的替代方案:对 sObject 列表的 DML 调用

此增强版本的 DML 调用对整个列表执行更新,该列表 包含更新后的订单项。它首先创建一个新列表,然后在 循环将每个更新行项添加到新列表中。然后,它执行批量 更新新列表。

List<Line_Item__c> updatedList = new List<Line_Item__c>();

for(Line_Item__c li : liList) {
    if (li.Units_Sold__c > 10) {
        li.Description__c = 'New description';
        updatedList.add(li);
    }
}

// Once DML call for the entire list of line items
update updatedList;

更高效的 SOQL 查询

将 SOQL 查询放在循环块中 这不是一个好的做法,因为 SOQL 查询每次迭代都执行一次 并可能超过每个事务 100 个 SOQL 查询的限制。以下是 对 中的每个项目运行 SOQL 查询的示例,效率不高。另一个例子是 给定一个修改后的查询,该查询仅使用一个 SOQL 检索子项 查询。forTrigger.new

例:子项查询效率低下

此示例中的循环循环访问 中的所有发票对帐单。在循环中执行的 SOQL 查询检索 每个发票对帐单的子行项目。如果超过 100 份发票对帐单 插入或更新,并因此包含在 中,这会导致运行时异常,因为达到 SOQL 限制。第二个示例通过创建另一个 SOQL 来解决此问题 只能调用一次的查询。forTrigger.newTrigger.new

trigger LimitExample on Invoice_Statement__c (before insert, before update) {
    for(Invoice_Statement__c inv : Trigger.new) {
        // This SOQL query executes once for each item in Trigger.new.
        // It gets the line items for each invoice statement.
        List<Line_Item__c> liList = [SELECT Id,Units_Sold__c,Merchandise__c 
                                     FROM Line_Item__c 
                                     WHERE Invoice_Statement__c = :inv.Id];
        for(Line_Item__c li : liList) {
            // Do something
        }
    }
}

推荐的替代方案:使用一个 SOQL 查询查询子项

此示例绕过了为每个项目调用 SOQL 查询的问题。它 有一个修改后的 SOQL 查询,该查询检索属于其中的所有发票对帐单,并获取其行 项目通过嵌套查询。这样,只执行一个 SOQL 查询,并且 我们仍然在我们的极限之内。Trigger.new

trigger EnhancedLimitExample on Invoice_Statement__c (before insert, before update) {
    // Perform SOQL query outside of the for loop.
    // This SOQL query runs once for all items in Trigger.new.
    List<Invoice_Statement__c> invoicesWithLineItems = 
        [SELECT Id,Description__c,(SELECT Id,Units_Sold__c,Merchandise__c from Line_Items__r)
         FROM Invoice_Statement__c WHERE Id IN :Trigger.newMap.KeySet()];
    
    for(Invoice_Statement__c inv : invoicesWithLineItems) {
        for(Line_Item__c li : inv.Line_Items__r) {
            // Do something
        }
    }
}

SOQL For 循环

使用 SOQL for 循环以 200 条为一组对记录进行操作。这有助于避免堆 大小限制为 6 MB。请注意,此限制适用于同步运行的代码,它是 对于异步代码执行,该值更高。

例:不带 for 循环的查询

以下是检索所有商品和 将它们存储在 List 变量中。如果退货商品尺寸较大 并且返回了大量它们,可能会达到堆大小限制。

List<Merchandise__c> ml = [SELECT Id,Name FROM Merchandise__c];

推荐的替代方案:for 循环中的查询

为了防止这种情况发生,第二个版本使用了 SOQL for 循环,该循环 以 200 条记录为一组循环访问返回的结果。这样可以减小尺寸 现在包含 200 的 list 变量 项,而不是查询结果中的所有项,并为每个项重新创建 批。ml

for (List<Merchandise__c> ml : [SELECT Id,Name FROM Merchandise__c]){
    // Do something.
}

ref

Apex 电子邮件服务

您可以使用电子邮件服务来处理 入站电子邮件。例如,您可以创建自动创建的电子邮件服务 基于邮件中的联系人信息的联系人记录。您可以将每个电子邮件服务与一个或多个 Salesforce 生成的电子邮件相关联 用户可以向其发送消息进行处理的地址。授予多个用户访问权限 对于单个电子邮件服务,您可以:

  • 关联多个 Salesforce 生成的电子邮件地址与电子邮件服务并分配 这些地址给用户。
  • 关联单个 Salesforce 生成的 电子邮件地址,并编写一个执行 根据访问电子邮件服务的用户。例如,您可以 编写一个 Apex 类,该类根据用户的电子邮件地址标识用户 并代表该用户创建记录。

要使用电子邮件服务,请从“设置”中输入“快速查找”框,然后选择“电子邮件” 服务

Email Services

  • 单击“新建电子邮件服务”以定义新电子邮件 服务。
  • 选择现有电子邮件服务以查看其配置,激活或 停用它,然后查看或指定该电子邮件服务的地址。
  • 单击“编辑”以对现有电子邮件进行更改 服务。
  • 单击删除”以删除电子邮件服务。注意以前 删除电子邮件服务,您必须删除所有关联的电子邮件服务 地址。

什么时候 定义电子邮件服务时,请注意以下几点:

  • 电子邮件服务仅处理在其 地址。
  • Salesforce 限制 所有电子邮件服务(包括按需电子邮件到案例)都可以处理 日常。超过此限制的邮件将被退回、丢弃或排队等待 第二天处理,具体取决于您如何为每个电子邮件服务配置失败响应设置。Salesforce 通过将 用户许可证数量增加 1,000 个;最大 1,000,000。例如,如果您有 10 个许可证,您的组织最多可以处理 10,000 封电子邮件 日。
  • 您在沙盒中创建的电子邮件服务地址无法复制到 生产组织。
  • 对于每个电子邮件服务,您可以告诉 Salesforce 将错误电子邮件发送到 指定的地址,而不是发件人的电子邮件地址。
  • 电子邮件服务拒绝电子邮件并通知发件人 如果电子邮件(正文文本、正文 HTML 和附件组合)超过 大约25兆字节 (因语言和性格而异 设置).

使用 InboundEmail 对象

对于 Apex 电子邮件服务域收到的每封电子邮件,Salesforce 都会创建一个单独的 包含该电子邮件的内容和附件的 InboundEmail 对象。你可以使用 实现接口以处理入站电子邮件的 Apex 类。 使用该类中的方法, 您可以访问 InboundEmail 对象来检索 入站电子邮件,以及执行许多功能。

Messaging.InboundEmailHandlerhandleInboundEmail

示例 1:为联系人创建任务

以下示例说明了如何根据入站查找联系人 电子邮件地址并创建一个新任务。

public with sharing class CreateTaskEmailExample implements Messaging.InboundEmailHandler {
 
  public Messaging.InboundEmailResult handleInboundEmail(Messaging.inboundEmail email, 
                                                       Messaging.InboundEnvelope env){
 
    // Create an InboundEmailResult object for returning the result of the 
    // Apex Email Service
    Messaging.InboundEmailResult result = new Messaging.InboundEmailResult();
  
    String myPlainText= '';
    
    // Add the email plain text into the local variable 
    myPlainText = email.plainTextBody;
   
    // New Task object to be created
    Task[] newTask = new Task[0];
   
    // Try to look up any contacts based on the email from address
    // If there is more than one contact with the same email address,
    // an exception will be thrown and the catch statement will be called.
    try {
      Contact vCon = [SELECT Id, Name, Email
        FROM Contact
        WHERE Email = :email.fromAddress
        WITH USER_MODE
        LIMIT 1];
      
      // Add a new Task to the contact record we just found above.
      newTask.add(new Task(Description =  myPlainText,
           Priority = 'Normal',
           Status = 'Inbound Email',
           Subject = email.subject,
           IsReminderSet = true,
           ReminderDateTime = System.now()+1,
           WhoId =  vCon.Id));
     
     // Insert the new Task 
     insert as user newTask;    
     
     System.debug('New Task Object: ' + newTask );   
    }
    // If an exception occurs when the query accesses 
    // the contact record, a QueryException is called.
    // The exception is written to the Apex debug log.
   catch (QueryException e) {
       System.debug('Query Issue: ' + e);
   }
   
   // Set the result to true. No need to send an email back to the user 
   // with an error message
   result.success = true;
   
   // Return the result for the Apex Email Service
   return result;
  }
}

示例 2:处理退订电子邮件

向客户和潜在客户发送营销电子邮件的公司必须提供 让收件人退订的方式。以下是电子邮件如何 服务可以处理退订请求。该代码搜索 “取消订阅”一词的入站电子邮件。如果找到该单词,则 代码查找与发件人电子邮件地址匹配的所有联系人和潜在顾客,并将电子邮件选择退出字段 () 设置为 True。HasOptedOutOfEmail

public with sharing class unsubscribe implements Messaging.inboundEmailHandler{

    public Messaging.InboundEmailResult handleInboundEmail(Messaging.InboundEmail email, 
                         Messaging.InboundEnvelope env ) {
    
        // Create an inboundEmailResult object for returning 
        // the result of the email service.
        Messaging.InboundEmailResult result = new Messaging.InboundEmailResult();
         
        // Create contact and lead lists to hold all the updated records.
        List<Contact> lc = new List <contact>();
        List<Lead> ll = new List <lead>();
         
        // Convert the subject line to lower case so the program can match on lower case.
        String mySubject = email.subject.toLowerCase();
        // The search string used in the subject line.
        String s = 'unsubscribe';
         
        // Check the variable to see if the word "unsubscribe" was found in the subject line. 
        Boolean unsubMe;
        // Look for the word "unsubcribe" in the subject line. 
        // If it is found, return true; otherwise, return false.
        unsubMe = mySubject.contains(s);
         
         // If unsubscribe is found in the subject line, enter the IF statement.
         
        if (unsubMe == true) {
            
            try {
               
            // Look up all contacts with a matching email address.
               
            for (Contact c : [SELECT Id, Name, Email, HasOptedOutOfEmail
                          FROM Contact
                          WHERE Email = :env.fromAddress
                          AND hasOptedOutOfEmail = false
                          WITH USER_MODE
                          LIMIT 100]) {
                          
                // Add all the matching contacts into the list.   
                c.hasOptedOutOfEmail = true;
                lc.add(c);
            }
            // Update all of the contact records.
            update as user lc;
        }
        catch (System.QueryException e) {
            System.debug('Contact Query Issue: ' + e);
        }   
        
        try {
            // Look up all leads matching the email address.
            for (Lead l : [SELECT Id, Name, Email, HasOptedOutOfEmail
                     FROM Lead
                     WHERE Email = :env.fromAddress
                     AND isConverted = false
                     AND hasOptedOutOfEmail = false
                     WITH USER_MODE
                     LIMIT 100]) {
                // Add all the leads to the list.       
                l.hasOptedOutOfEmail = true;
                ll.add(l);
                   
                System.debug('Lead Object: ' + l);   
            } 
            // Update all lead records in the query.
            update as user ll;
        }
        
        catch (System.QueryException e) {
            System.debug('Lead Query Issue: ' + e);
        }   
        
        System.debug('Found the unsubscribe word in the subject line.');
         } 
         else {
            System.debug('No Unsuscribe word found in the subject line.' );
         }
        // Return True and exit.
        // True confirms program is complete and no emails 
        // should be sent to the sender of the unsubscribe request. 
        result.success = true;
        return result;
    }   
}
@isTest
private class unsubscribeTest {
    // The following test methods provide adequate code coverage 
    // for the unsubscribe email class.
    // There are two methods, one that does the testing
    // with a valid "unsubcribe" in the subject line
    // and one the does not contain "unsubscribe" in the
    // subject line.        
    static testMethod void testUnsubscribe() {
    
       // Create a new email and envelope object.
       Messaging.InboundEmail email = new Messaging.InboundEmail() ;
       Messaging.InboundEnvelope env    = new Messaging.InboundEnvelope();
    
       // Create a new test lead and insert it in the test method.
       Lead l = new lead(firstName='John', 
                lastName='Smith',
                Company='Salesforce', 
                Email='user@acme.com', 
                HasOptedOutOfEmail=false);
       insert l;
    
       // Create a new test contact and insert it in the test method.
       Contact c = new Contact(firstName='john', 
                    lastName='smith', 
                    Email='user@acme.com', 
                    HasOptedOutOfEmail=false);
       insert c;
       
       // Test with the subject that matches the unsubscribe statement.
       email.subject = 'test unsubscribe test';
       env.fromAddress = 'user@acme.com';
       
       // Call the class and test it with the data in the testMethod.
       unsubscribe unsubscribeObj = new unsubscribe();
       unsubscribeObj.handleInboundEmail(email, env );
                            
    }
     
    static testMethod void testUnsubscribe2() {
    
       // Create a new email and envelope object.
       Messaging.InboundEmail email = new Messaging.InboundEmail();
       Messaging.InboundEnvelope env = new Messaging.InboundEnvelope();
    
       // Create a new test lead and insert it in the test method.        
       Lead l = new lead(firstName='john', 
                lastName='smith',
                Company='Salesforce', 
                Email='user@acme.com', 
                HasOptedOutOfEmail=false);
       insert l;
    
       // Create a new test contact and insert it in the test method.    
       Contact c = new Contact(firstName='john', 
                    lastName='smith', 
                    Email='user@acme.com', 
                    HasOptedOutOfEmail=false);
       insert c;
       
       // Test with a subject that does not contain "unsubscribe."
       email.subject = 'test';
       env.fromAddress = 'user@acme.com';
    
       // Call the class and test it with the data in the test method.
       unsubscribe unsubscribeObj = new unsubscribe();
       unsubscribeObj.handleInboundEmail(email, env );                      
       // Assert that the Lead and Contact have been unsubscribed
       Lead updatedLead = [Select Id, HasOptedOutOfEmail from Lead where Id = :l.Id];
       Contact updatedContact = [Select Id, HasOptedOutOfEmail from Contact where Id = :c.Id];
       Assert.isTrue(l.HasOptedOutOfEmail);
       Assert.isTrue(c.HasOptedOutOfEmail);
    }     
}

Visualforce 类

除了使开发人员能够向 Salesforce 添加业务逻辑之外 系统事件,例如按钮点击和相关记录更新,Apex 也可用于 通过自定义 Visualforce 控制器为 Visualforce 页面提供自定义逻辑,以及 控制器扩展。

  • 自定义控制器是用 Apex 编写的类,它实现了页面的所有 逻辑,而无需使用标准控制器。如果您使用自定义控制器,则 可以定义新的导航元素或行为,但还必须重新实现任何 标准控制器中已提供的功能。像其他 Apex 一样 类,自定义控制器完全在系统模式下执行,其中对象 并且忽略当前用户的字段级权限。您可以指定 用户是否可以根据用户的 轮廓。
  • 控制器扩展是用 Apex 编写的类,用于添加或覆盖行为 在标准或自定义控制器中。扩展允许您利用 另一个控制器的功能,同时添加您自己的自定义逻辑。因为 标准控制器在用户模式下执行,其中权限为字段级 安全性,并强制执行当前用户的共享规则,从而扩展了 标准控制器允许您构建尊重用户的 Visualforce 页面 权限。尽管扩展类在系统模式下执行,但标准 控制器在用户模式下执行。与自定义控制器一样,您可以指定 用户是否可以根据用户的 轮廓。

在构建自定义 Visualforce 时,可以使用这些系统提供的 Apex 类 控制器和控制器扩展。

  • 行动
  • 动态组件
  • IdeaStandardController(理念标准控制器)
  • IdeaStandardSet控制器
  • KnowledgeArticleVersionStandardController
  • 消息
  • 页面参考
  • 选择选项
  • 标准控制器
  • 标准集控制器

除了这些类之外,在控制器和控制器扩展中声明方法时还可以使用关键字。为 更多信息,请参阅使用 瞬态关键字。transient

有关 Visualforce 的更多信息,请参阅 Visualforce 开发人员的 指南。

JavaScript 远程处理

在 Visualforce 中使用 JavaScript 远程处理从 JavaScript的。创建具有复杂动态行为的页面,这是 标准 Visualforce AJAX 组件。使用 JavaScript 远程处理实现的功能需要三个元素:

  • 您添加到 Visualforce 页面的远程方法调用,写在 JavaScript的。
  • Apex 控制器类中的远程方法定义。此方法 定义是用 Apex 编写的,但与 正常操作方法。
  • 您添加到或包含在 Visualforce 中的响应处理程序回调函数 页面,用 JavaScript 编写。

在控制器中,Apex 方法声明前面带有类似 这:

@RemoteAction

@RemoteAction
global static String getItemId(String objectName) { ... }

Apex 方法必须是 和 或 。

@RemoteActionstaticglobalpublic将 Apex 类作为自定义控制器或控制器扩展添加到 页。

<apex:page controller="MyController" extension="MyExtension">

警告

添加控制器或控制器扩展将授予对该 Apex 类中所有方法的访问权限, 即使页面中未使用这些方法。任何可以查看该页面的人都可以 执行所有方法并提供 向控制器发送虚假或恶意数据。@RemoteAction@RemoteAction然后,将请求添加为 JavaScript 函数调用。一个简单的 JavaScript 远程调用需要以下操作 形式。

[namespace.]MyController.method(
    [parameters...,]
    callbackFunction,
    [configuration]
);
元素描述
namespace控制器类的命名空间。namespace 元素是 如果您的组织定义了命名空间,或者如果 类来自已安装的包。
MyController,MyExtensionApex 控制器或扩展的名称。
method要调用的 Apex 方法的名称。
parameters方法的参数的逗号分隔列表 需要。
callbackFunction处理响应的 JavaScript 函数的名称 从控制器。您还可以声明匿名函数 内嵌。 接收方法调用的状态和结果 参数。callbackFunction
configuration配置远程呼叫和响应的处理。使用这个 元素来更改远程处理调用的行为,例如是否 或者不转义 Apex 方法的响应。

有关更多信息,请参阅《Visualforce 开发人员指南》中的适用于 Apex 控制器的 JavaScript 远程处理。

AJAX 中的 Apex

AJAX 工具包包括对通过匿名块调用 Apex 的内置支持 或公共方法。

webservice

要通过匿名块或公共方法调用 Apex,请在 AJAX 代码中包含以下行:webservice

<script src="/soap/ajax/59.0/connection.js" type="text/javascript"></script>
<script src="/soap/ajax/59.0/apex.js" type="text/javascript"></script>

注意

对于 AJAX 按钮,请使用这些包含的替代形式。若要调用 Apex,请使用以下两种方法之一:

  • 通过匿名执行。这 方法返回的结果类似于 API 的结果类型,但作为 JavaScript 结构。sforce.apex.executeAnonymous (script)
  • 使用类 WSDL。例如,您可以调用以下 Apex 类:global class myClass { webservice static Id makeContact(String lastName, Account a) { Contact c = new Contact(LastName = lastName, AccountId = a.Id); return c.id; } }通过使用以下 JavaScript 法典:var account = sforce.sObject("Account"); var id = sforce.apex.execute("myClass","makeContact", {lastName:"Smith", a:account});该方法采用原始数据 类型、sObject 和基元或 sObject 列表。execute要调用 不带参数的 webservice 方法,用作 的第三个参数。例如,调用以下 Apex 类:{}sforce.apex.executeglobal class myClass{ webservice static String getContextUserName() { return UserInfo.getFirstName(); } }使用以下 JavaScript 法典:var contextUser = sforce.apex.execute("myClass", "getContextUserName", {});注意如果已为您的 组织,则在调用 类。例如,要调用类, 上面的 JavaScript 代码将被重写为 遵循:myClassvar contextUser = sforce.apex.execute("myNamespace.myClass", "getContextUserName", {});若要验证组织是否具有命名空间,请记录 在您的 Salesforce 组织中,然后从“设置”中输入“快速” “查找”框,然后选择“包”。如果 命名空间已定义,它列在“开发人员”下 设置。Packages有关返回数据类型的详细信息,请参阅 AJAX 中的数据类型 工具箱

使用以下行显示包含调试信息的窗口:

sforce.debug.trace=true;

ref

将 Apex 方法公开为 SOAP Web 服务

您可以将 Apex 方法公开为 SOAP Web 服务,以便外部应用程序 可以访问您的代码和应用程序。

若要公开 Apex 方法,请使用 Web 服务方法。

提示

  • Apex SOAP Web 服务允许外部应用程序调用 Apex 方法 通过 SOAP Web 服务。Apex 标注使 Apex 能够调用外部 Web 或 HTTP 服务业。
  • Apex REST API 将您的 Apex 类和方法公开为 REST Web 服务。请参阅将 Apex 类公开为 REST Web 服务。
  • Webservice 方法
  • 使用 Web 服务方法公开数据
  • 使用 webservice 关键字的注意事项
  • 重载 Web 服务方法

Webservice 方法

Apex 类方法可以公开为自定义 SOAP Web 服务调用。 这允许外部应用程序调用 Apex Web 服务来执行 Salesforce 中的操作。使用关键字来定义这些方法。webservice为 例:

global class MyWebService {
    webservice static Id makeContact(String contactLastName, Account a) {
        Contact c = new Contact(lastName = contactLastName, AccountId = a.Id);
        insert c;
        return c.id;
    }
}

外部应用程序的开发人员可以通过为 类。要从 Apex 类详细信息页面生成 WSDL,请执行以下操作:

webservice

  1. 在“设置”的应用程序中,在“快速”中输入“Apex Classes” “查找”框,然后选择“Apex 类”。
  2. 单击包含方法的类的名称。webservice
  3. 单击生成 WSDL

使用 Web 服务方法公开数据

调用自定义方法始终使用 system 上下文。因此,不会使用当前用户的凭据,并且任何具有访问权限的用户 这些方法可以使用其全部功能,而不管权限、字段级安全性或 共享规则。因此,使用关键字公开方法的开发人员应注意不要无意中暴露 任何敏感数据。webservicewebservice

警告

通过带有关键字的 API 公开的 Apex 类方法不强制执行对象权限,并且 默认情况下为字段级安全性。我们建议您使用适当的对象或 字段描述结果方法,用于检查当前用户对对象和字段的访问级别 Web Service 方法正在访问。请参阅 DescribeSObjectResult 类和 DescribeFieldResult 类。webservice

此外,共享规则(记录级访问) 仅当使用 keyword 声明类时才强制执行。此要求适用于所有 Apex 类,包括类 包含 WebService 方法。要强制执行 Web 服务方法的共享规则,请声明 包含这些方法和关键字的类。请参阅使用有共享、无共享和继承共享关键字。with sharingwith sharing

使用 webservice 关键字的注意事项

使用关键字时,请记住以下注意事项:

webservice

  • 使用关键字定义顶级 方法和外部类方法。不能使用关键字来定义类或内部类方法。webservicewebservice
  • 不能使用关键字来定义 接口,或定义接口的方法和变量。webservice
  • 系统定义的枚举不能在 Web 服务方法中使用。
  • 您不能在 触发。webservice
  • 包含用关键字定义的方法的所有类都必须声明为 .如果方法或内部类声明为 ,则还必须定义外部顶级类 如。webserviceglobalglobalglobal
  • 使用关键字定义的方法包括 本质上是全球性的。任何有权访问该类的 Apex 代码都可以使用这些方法。你 可以将关键字视为一种 access 修饰符,用于启用比 更多的访问。webservicewebserviceglobal
  • 将关键字用作 的任何方法定义为 。webservicestatic
  • 不能弃用托管包中的方法或变量 法典。webservice
  • 由于某些 Apex 元素没有 SOAP 类似物,因此使用关键字定义的方法不能采用以下方法 元素作为参数。虽然这些元素可以在方法中使用,但它们也 不能标记为返回值。webservice
    • 地图
    • 模式对象
    • Matcher 对象
    • 异常对象
  • 将关键字与 要作为 Web 服务的一部分公开的任何成员变量。不要标记这些 成员变量作为 。webservicestatic

调用 Apex SOAP Web 服务方法的注意事项:

  • Salesforce 拒绝对 Web 服务的访问,并拒绝来自具有受限访问权限的 AppExchange 包的请求。executeanonymous
  • 使用 API 版本 15.0 及更高版本保存(编译)的 Apex 类和触发器会生成 如果为字段分配的 String 值太长,则运行时错误。
  • 如果从API对密码过期或临时密码的用户进行登录调用, 不支持对自定义 Apex SOAP Web 服务方法的后续 API 调用,并导致 在INVALID_OPERATION_WITH_EXPIRED_PASSWORD错误中。重置用户的密码并制作 使用未过期的密码进行调用,以便能够调用 Apex Web 服务方法。

下面的示例演示一个包含 Web 服务成员变量和 Web 服务的类 方法:

global class SpecialAccounts {

  global class AccountInfo {
     webservice String AcctName;
     webservice Integer AcctNumber;
  }

  webservice static Account createAccount(AccountInfo info) {
    Account acct = new Account();
    acct.Name = info.AcctName;
    acct.AccountNumber = String.valueOf(info.AcctNumber);
    insert acct;
    return acct;
  }

  webservice static Id [] createAccounts(Account parent, 
       Account child, Account grandChild) {

        insert parent;
        child.parentId = parent.Id;
        insert child;
        grandChild.parentId = child.Id;
        insert grandChild;

        Id [] results = new Id[3];
        results[0] = parent.Id;
        results[1] = child.Id;
        results[2] = grandChild.Id;
        return results;
    }
}
// Test class for the previous class.
@isTest
private class SpecialAccountsTest {
  testMethod static void testAccountCreate() {
    SpecialAccounts.AccountInfo info = new SpecialAccounts.AccountInfo();
    info.AcctName = 'Manoj Cheenath';
    info.AcctNumber = 12345;
    Account acct = SpecialAccounts.createAccount(info);
    System.assert(acct != null);
  }
}

可以使用 AJAX 调用此 Web 服务。有关详细信息,请参阅 AJAX 中的 Apex。

重载 Web 服务方法

SOAP 和 WSDL 不能为重载方法提供良好的支持。因此,Apex 不会 允许两个标有关键字的方法 具有相同的名称。在同一类中具有相同名称的 Web 服务方法会生成一个 编译时错误。webservice

将 Apex 类公开为 REST Web 服务

您可以公开 Apex 类和方法,以便外部应用程序可以访问 通过REST架构的代码和应用程序。

本文概述了如何将 Apex 类公开为 REST Web 服务。您将了解到 关于类和方法批注,并参阅演示如何 实现此功能。

提示

Apex SOAP Web 服务允许外部应用程序调用 Apex 方法 通过 SOAP Web 服务。请参阅将 Apex 方法公开为 SOAP Web 服务。

  • Apex REST 简介
  • Apex REST 注解
  • Apex REST 方法
  • 使用 Apex REST Web 服务方法公开数据
  • Apex REST 代码示例

Apex REST 简介

您可以公开 Apex 类和方法,以便外部应用程序可以访问您的代码 以及通过 REST 架构的应用程序。这是通过定义 Apex 类来完成的 带有注释以将其公开为 REST 资源。同样,向方法添加注释以通过 REST 公开它们。为 例如,您可以将注解添加到 方法将其公开为可由 HTTP 请求调用的 REST 资源。有关更多信息,请参阅 Apex REST 附注@RestResource@HttpGetGET

这些类包含可用于 Apex REST 的方法和属性。

描述
RestContext 类包含 和 对象。RestRequestRestResponse
request使用类可以 在 RESTful Apex 方法中访问和传递请求数据。System.RestRequest
response表示用于将数据从 Apex RESTful Web 服务方法传递到 HTTP 响应。

调速器限制

对 Apex REST 类的调用将计入组织的 API 调控器限制。都 标准 Apex 调控器限制适用于 Apex REST 类。例如,最大请求 或响应大小为 6 MB(同步 Apex)或 12 MB(异步 Apex)。查看更多 信息,请参阅执行调控器和限制。

认证

Apex REST 支持以下身份验证机制:

  • OAuth 2.0 操作系统
  • 会话 ID

Apex REST 注解

使用这些注解将 Apex 类公开为 RESTful Web 服务。

  • @ReadOnly
  • @RestResource(urlMapping=’/yourUrl‘)
  • @HttpDelete
  • @HttpGet
  • @HttpPatch
  • @HttpPost
  • @HttpPut

Apex REST 方法

Apex REST支持两种资源表示格式: JSON 和 XML。默认情况下,JSON 表示形式在请求正文中传递,或者 response,格式由 HTTP 标头中的属性指示。Content-Type您可以检索 body 作为 HttpRequest 对象中的 Blob(如果 Apex 没有参数) 方法。如果在 Apex 方法中定义了参数,则尝试反序列化 请求正文添加到这些参数中。如果 Apex 方法具有非 void 返回类型, 资源表示形式将序列化到响应正文中。

允许使用以下返回和参数类型:

  • Apex 原语(不包括 sObject 和 Blob)。
  • s对象
  • Apex 基元或 sObject 的列表或映射(仅映射 支持字符串键)。
  • 包含所列类型的成员变量的用户定义类型 以上。

注意

Apex REST 不支持 Apex 中 Connect 的 XML 序列化和反序列化 对象。Apex REST支持JSON序列化和反序列化Connect in Apex 对象。此外,某些集合类型(如地图和列表)则不是 受 XML 支持。请参阅请求和响应数据注意事项 详。

带批注或必须没有参数的方法。这是因为 GET 和 DELETE 请求没有请求正文,因此无需反序列化。@HttpGet@HttpDelete

@ReadOnly 注解支持 Apex REST 注解 对于所有 HTTP 请求:@HttpDelete @HttpGet @HttpPatch @HttpPost @HttpPut

带注释的单个 Apex 类不能有多个方法使用相同的 HTTP 请求方法进行注释。例如 同一个类不能有两个方法用 注释。@RestResource@HttpGet

注意

Apex REST 目前不支持 Content-Type 的请求。multipart/form-data

Apex REST 方法注意事项

以下是定义 Apex REST 方法时需要考虑的几点。

  • RestRequest和对象可通过以下方式获得 default 通过静态对象在 Apex 方法中。此示例演示如何访问这些对象 通过:RestResponseRestContextRestContextRestRequest req = RestContext.request; RestResponse res = RestContext.response;
  • 如果 Apex 方法没有参数,则 Apex REST 将复制 HTTP 请求正文 进入物业。如果该方法具有参数,则 Apex REST 会尝试反序列化 将数据反序列化到这些参数中,并且数据不会反序列化到属性中。RestRequest.requestBodyRestRequest.requestBody
  • Apex REST 对响应使用类似的序列化逻辑。具有 非 void 返回类型将返回值序列化为 。如果返回 type 包含具有 null 值的字段,这些字段不会序列化为 响应正文。RestResponse.responseBody
  • Apex REST 方法可用于托管和 非托管包。调用包含在 托管包,则必须在 REST 中包含托管包命名空间 调用 URL。例如,如果类包含在托管包中 命名空间调用和 Apex REST 方法使用 /MyMethod/* 的 URL 映射, 通过 REST 用于调用这些方法的 URL 的格式为 https:// instance.salesforce.com/services/apexrest/packageNamespace/MyMethod/packageNamespace有关托管包的详细信息,请参阅什么是 包?.
  • 如果从 API 为具有过期或临时用户的用户进行登录调用 password,则对自定义 Apex REST Web 服务方法的后续 API 调用不会 支持并导致MUTUAL_AUTHENTICATION_FAILED错误。重置用户的 密码并使用未过期的密码拨打电话,以便能够呼叫 Apex Web 服务方法。
  • 如果在序列化过程中超出堆限制,则返回代码并将错误追加到 部分 JSON 响应。从 REST 方法返回 sObject 的集合 涉及缓冲每个 sObject 的 JSON 序列化形式。堆和 CPU 限制 可能直到 HTTP 响应标头和初始数据 开始流式传输回客户端。要获得对 statusCode 和 的控制权,请使用 而不是直接返回 s对象。HTTP 200{“status”:”some error occurred”}responseBodyRestResponse

用户定义类型

您可以在 Apex REST 方法中对参数使用用户定义的类型。Apex REST的 将请求数据反序列化为 、 或用户定义类型的类成员变量,除非 变量声明为 或 。例如,一个 Apex REST 方法 包含用户定义的类型参数可能如下所示:publicprivateglobalstatictransient

@RestResource(urlMapping='/user_defined_type_example/*')
global with sharing class MyOwnTypeRestResource {

    @HttpPost
    global static MyUserDefinedClass echoMyType(MyUserDefinedClass ic) {
        return ic;
    }

    global class MyUserDefinedClass {

        global String string1;
        global String string2 { get; set; }
        private String privateString;
        global transient String transientString;
        global static String staticString;

    }
    
}

此方法的有效 JSON 和 XML 请求数据如下所示:

{
    "ic" : {
                "string1" : "value for string1",
                "string2" : "value for string2",
                "privateString" : "value for privateString"
            }
}
<request>
    <ic>
        <string1>value for string1</string1>
        <string2>value for string2</string2>
        <privateString>value for privateString</privateString>
    </ic>
</request>

如果在示例请求中提供了 或 的值 数据,则生成 HTTP 400 状态代码响应。、 或 类成员 变量必须是 Apex REST 允许的类型:staticStringtransientStringpublicprivateglobal

  • Apex 基元(不包括 sObject 和 Blob)。
  • s对象
  • Apex 基元或 sObject 的列表或映射(只有带有 String 键的映射是 支持)。

创建用作 Apex REST 方法参数的用户定义类型时,请避免 引入任何导致循环 (定义 它们相互依赖)在运行时的用户定义类型中。这里有一个简单的 例:

@RestResource(urlMapping='/CycleExample/*')
global with sharing class ApexRESTCycleExample {
 
    @HttpGet
    global static MyUserDef1 doCycleTest() {
        MyUserDef1 def1 = new MyUserDef1();
        MyUserDef2 def2 = new MyUserDef2();
        def1.userDef2 = def2;
        def2.userDef1 = def1;
        return def1;
    }
 
    global class MyUserDef1 {
        MyUserDef2 userDef2;
    }    
 
    global class MyUserDef2 {
        MyUserDef1 userDef1;
    }
    
}

上一个示例中的代码会编译,但在运行时 发出请求时,Apex REST 会检测到 和 实例之间的循环, 并生成 HTTP 400 状态代码错误响应。

请求和响应数据 考虑

对于 Apex REST 的请求数据,需要记住一些其他事项 方法:

  • Apex 参数的名称很重要,尽管 订单没有。例如,XML 和 JSON 格式的有效请求 如下所示:@HttpPost global static void myPostMethod(String s1, Integer i1, Boolean b1, String s2){ "s1" : "my first string", "i1" : 123, "s2" : "my second string", "b1" : false }<request> <s1>my first string</s1> <i1>123</i1> <s2>my second string</s2> <b1>false</b1> </request>
  • URL 模式和 /* 匹配相同的 URL。如果一个类的 urlMapping 和另一个类 类的 urlMapping 为 /*, 此 URL 模式的 REST 请求解析为首先保存的类。URLpatternURLpatternURLpatternURLpattern
  • 某些参数和返回类型不能与 XML 一起使用,作为 请求或作为响应的接受格式,因此,方法 这些参数或返回类型不能用于 XML。列表、地图或 例如,不支持集合的集合。但是,您可以使用 这些类型带有 JSON。如果参数列表包含对 XML 和 XML,则返回 HTTP 415 状态代码。如果返回类型为 对 XML 无效的类型和 XML 是请求的响应格式,即 HTTP 返回 406 状态码。List<List<String>>
  • 对于 JSON 或 XML 格式的请求数据,布尔参数的有效值为:、(两者都被视为不区分大小写)和(数值,而不是“1”或“0”的字符串)。任何 布尔参数的其他值会导致错误。truefalse10
  • 如果JSON或XML请求数据包含多个同名参数, 这会导致 HTTP 400 状态代码错误响应。例如,如果您的 方法指定一个名为 的输入参数,以下 JSON 请求数据结果为 错误:x{ "x" : "value1", "x" : "value2" }同样,对于用户定义的类型,如果请求数据包括 同一用户定义类型成员变量的数据多次出现,结果 在错误中。例如,给定此 Apex REST 方法和用户定义的 类型:@RestResource(urlMapping='/DuplicateParamsExample/*') global with sharing class ApexRESTDuplicateParamsExample { @HttpPost global static MyUserDef1 doDuplicateParamsTest(MyUserDef1 def) { return def; } global class MyUserDef1 { Integer i; } }以下 JSON 请求数据还会导致 错误:{ "def" : { "i" : 1, "i" : 2 } }
  • 如果必须在请求中为某个参数指定 null 值 数据,可以完全省略参数或指定 null 值。在 JSON,您可以指定为值。在 XML,则必须使用带有 nil 的命名空间 价值。nullhttp://www.w3.org/2001/XMLSchema-instance
  • 对于 XML 请求数据,必须指定引用任何 Apex 的 XML 命名空间 命名空间。因此,例如,如果您定义了 Apex REST 方法 这样 如:@RestResource(urlMapping='/namespaceExample/*') global class MyNamespaceTest { @HttpPost global static MyUDT echoTest(MyUDT def, String extraString) { return def; } global class MyUDT { Integer count; } }您可以使用以下 XML 请求 数据:<request> <def xmlns:MyUDT="http://soap.sforce.com/schemas/class/MyNamespaceTest"> <MyUDT:count>23</MyUDT:count> </def> <extraString>test</extraString> </request>

响应状态代码

响应的状态代码是自动设置的。下表列出了一些 HTTP 状态 代码及其在 HTTP 请求方法上下文中的含义。对于完整的 响应状态码列表,请参见statusCode

请求方式响应状态代码描述
获取200请求成功。
补丁200请求成功,返回类型为 非无效。
补丁204请求成功,返回类型无效。
删除、获取、修补、发布、放置400发生未处理的用户异常。
删除、获取、修补、发布、放置403您无权访问指定的 Apex 类。
删除、获取、修补、发布、放置404URL 在现有批注中未映射。@RestResource
删除、获取、修补、发布、放置404不支持 URL 扩展名。
删除、获取、修补、发布、放置404具有指定命名空间的 Apex 类不能 发现。
删除、获取、修补、发布、放置405request 方法没有相应的 Apex 方法。
删除、获取、修补、发布、放置406标头中的 Content-Type 属性设置为其他值 而不是 JSON 或 XML。
删除、获取、修补、发布、放置406不支持 HTTP 请求中指定的标头。
获取、修补、发布、放置406不支持为 format 指定的 XML 返回类型。
删除、获取、修补、发布、放置415不支持 XML 参数类型。
删除、获取、修补、发布、放置415HTTP 请求标头中指定的 Content-Header 类型为 支持。
删除、获取、修补、发布、放置500发生未处理的 Apex 异常。

使用 Apex REST Web 服务方法公开数据

调用自定义 Apex REST Web 服务方法始终使用系统上下文。因此,不会使用当前用户的凭据,任何有权访问这些方法的用户都可以使用 无论权限、字段级安全性或共享规则如何,它们都具有全部功能。因此,使用 Apex REST 注解公开方法的开发人员应注意它们 不会无意中暴露任何敏感数据。

默认情况下,通过 Apex REST API 公开的 Apex 类方法不强制执行对象权限和字段级安全性。在使用 SOQL SELECT 语句,请使用 WITH SECURITY_ENFORCED子句。你 可以使用 Security.stripInaccessible 方法从查询和子查询结果中删除用户无法访问的字段,或在 DML 操作之前删除无法访问的 sObject 字段。您也可以使用适当的 object or field 描述结果方法,用于检查当前用户对 Apex REST API 方法正在访问的对象和字段的访问级别。请参阅 DescribeSObjectResult 类和 DescribeFieldResult 类。

此外,仅当使用关键字声明类时,才会强制执行共享规则(记录级访问)。此要求适用于所有 Apex 类,包括通过 Apex REST API 公开的类。若要强制执行 Apex REST API 方法的共享规则,请使用关键字声明包含这些方法的类。请参阅使用有共享不共享关键字。with sharingwith sharing

Apex REST 代码示例

这些代码示例向您展示如何通过 REST 公开 Apex 类和方法 体系结构以及如何从客户端调用这些资源。

  • Apex REST 基本代码示例 此示例
    演示如何在 Apex 中实现简单的 REST API,其中包含三种 HTTP 请求方法来删除、检索和更新记录。
  • 使用 RestRequest 的 Apex REST 代码示例 此示例演示如何使用 RestRequest
    对象向记录添加附件。

Apex REST 基本代码示例

此示例演示如何使用三个 HTTP 请求在 Apex 中实现简单的 REST API 删除、检索和更新记录的方法。有关身份验证的更多信息,请参阅 REST API 的“快速入门”部分 开发人员指南。

cURL

  1. 通过设置在实例中创建一个 Apex 类。在“快速查找”框中输入,选择“Apex” 类,然后单击“新建”。将此代码添加到新的 顶点 类:Apex Classes@RestResource(urlMapping='/Account/*') global with sharing class MyRestResource { @HttpDelete global static void doDelete() { RestRequest req = RestContext.request; RestResponse res = RestContext.response; String accountId = req.requestURI.substring(req.requestURI.lastIndexOf('/')+1); Account account = [SELECT Id FROM Account WHERE Id = :accountId]; delete account; } @HttpGet global static Account doGet() { RestRequest req = RestContext.request; RestResponse res = RestContext.response; String accountId = req.requestURI.substring(req.requestURI.lastIndexOf('/')+1); Account result = [SELECT Id, Name, Phone, Website FROM Account WHERE Id = :accountId]; return result; } @HttpPost global static String doPost(String name, String phone, String website) { Account account = new Account(); account.Name = name; account.phone = phone; account.website = website; insert account; return account.Id; } }
  2. 要从客户端调用该方法,请打开 命令行窗口,并执行以下命令以按 ID 检索帐户:doGetcURLcurl -H “Authorization: Bearer sessionId” “https://instance.salesforce.com/services/apexrest/Account/accountId
    • 将 sessionId 替换为您在登录响应中记下的元素。<sessionId>
    • 将 instance 替换为您的元素。<serverUrl>
    • 将 accountId 替换为已存在的帐户的 ID 在您的组织中。
    调用该方法后, Salesforce 返回一个 JSON 响应,其中包含如下数据 以后:doGet{ "attributes" : { "type" : "Account", "url" : "/services/data/v22.0/sobjects/Account/accountId" }, "Id" : "accountId", "Name" : "Acme" }注意本节中的示例不 使用命名空间的 Apex 类,这样您就不会在 URL 中看到命名空间。cURL
  3. 创建一个名为 account.txt 的文件,以包含 您将在下一个创建的帐户 步。{ "name" : "Wingo Ducks", "phone" : "707-555-1234", "website" : "www.wingo.ca.us" }
  4. 使用命令行窗口,执行以下命令以创建新帐户:cURLcurl -H “Authorization: Bearer sessionId” -H “Content-Type: application/json” -d @account.txt “https://instance.salesforce.com/services/apexrest/Account/”后 调用该方法,Salesforce 将返回一个 使用诸如 以后:doPost"accountId"accountId 是您刚刚创建的帐户的 ID POST 请求。
  5. 使用命令行窗口,执行以下命令,通过指定 ID 来删除帐户:cURLcurl —X DELETE —H “Authorization: Bearer sessionId” “https://instance.salesforce.com/services/apexrest/Account/accountId

使用 RestRequest 的 Apex REST 代码示例

此示例演示如何使用 RestRequest 向记录添加附件 对象。有关使用 进行身份验证的详细信息,请 请参阅 REST 的“快速入门”部分 API 开发人员指南。在此代码中,二进制文件数据存储在 RestRequest 对象,并且 Apex 服务类访问 RestRequest 对象。

cURL

  1. 通过输入“快速查找”框,在“设置”中在组织中创建一个 Apex 类,然后 选择 Apex 类。单击“新建”,并将以下代码添加到新的 类:Apex Classes@RestResource(urlMapping='/CaseManagement/v1/*') global with sharing class CaseMgmtService { @HttpPost global static String attachPic(){ RestRequest req = RestContext.request; RestResponse res = Restcontext.response; Id caseId = req.requestURI.substring(req.requestURI.lastIndexOf('/')+1); Blob picture = req.requestBody; Attachment a = new Attachment (ParentId = caseId, Body = picture, ContentType = 'image/jpg', Name = 'VehiclePicture'); insert a; return a.Id; } }
  2. 打开命令行窗口并执行以下命令以将附件上传到案例:cURLcurl -H “Authorization: Bearer sessionId” -H “X-PrettyPrint: 1” -H “Content-Type: image/jpeg” –data-binary @file “https://MyDomainName.my.salesforce.com/services/apexrest/CaseManagement/v1/caseId
    • 将 sessionId 替换为您记下的元素 在登录响应中。<sessionId>
    • 将 MyDomainName 替换为 My Domain 组织的名称。
    • 将 caseId 替换为 you 案例的 ID 想要将附件添加到。
    • 将 file 替换为路径和文件名 的。
    您的命令应如下所示(sessionId 替换为您的会话 ID 并替换为 我的域名 你 组织):MyDomainNamecurl -H "Authorization: Bearer sessionId" -H "X-PrettyPrint: 1" -H "Content-Type: image/jpeg" --data-binary @c:\test\vehiclephoto1.jpg "https://MyDomainName.my.salesforce.com/services/apexrest/CaseManagement/v1/500D0000003aCts"注意这里的例子 部分不要使用命名空间的 Apex 类,因此您不会在 URL。cURLApex 类返回一个 JSON 响应,其中包含 附件 ID,例如 以后:"00PD0000001y7BfMAI"
  3. 要验证附件和图像是否已添加到案例中,请导航到“案例”,然后选择“所有未结案例”视图。点击 ,然后向下滚动到“附件”相关列表。您应该看到 您刚刚创建的附件。

触发器

可以使用触发器调用 Apex。Apex 触发器使您能够执行 在更改 Salesforce 记录之前或之后执行自定义操作,例如插入、更新或 删除。触发器是在以下类型的操作之前或之后执行的 Apex 代码:

  • 插入
  • 更新
  • 删除
  • 合并
  • 更新插入
  • 取消删除

例如,您可以在将对象的记录插入到 数据库,在删除记录之后,甚至在从回收中恢复记录之后 站。

您可以为支持触发器的顶级标准对象定义触发器,例如 联系人或客户、一些标准子对象(如 CaseComment)和自定义对象。 要定义触发器,请从触发其对象的对象管理设置中 想要访问,请转到触发器。有两种类型的触发器:

  • 在触发器用于更新或验证记录值之前 保存到数据库。
  • 使用触发器访问系统设置的字段值后 (例如记录或字段),并影响其他 记录,例如登录到审计表或异步触发 具有队列的事件。触发后触发器的记录是 只读。IdLastModifiedDate

触发器还可以修改与最初触发的记录类型相同的其他记录 触发器。例如,如果在更新联系人后触发触发器, 触发器还可以修改联系人 、 和 。因为触发器可能会导致其他记录更改,并且因为这些 反过来,更改可以触发更多触发器,Apex 运行时引擎会考虑所有这些 操作单个工作单元,并对可以 执行以防止无限递归。请参阅执行调控器和限制。

此外,如果在触发器之前更新或删除记录,或者删除 触发后,您将收到运行时错误。这包括直接和间接 操作。例如,如果更新帐户,则更新之前 触发帐户插入联系人,以及之后 插入客户联系人查询的触发器和 使用 DML 语句或数据库对其进行更新 方法,那么您正在间接更新其触发器中的帐户, 您将收到运行时错误。

实施注意事项

在创建触发器之前,请考虑以下事项:

  • upsert触发器在触发器之前和之后或触发器之前和之后触发。insertupdate
  • merge触发器在丢失记录之前和之后都会触发,并且在之前都会触发 以及触发获胜记录之后。 请参阅触发器和合并 语句。deleteupdate
  • 在取消删除记录后执行的触发器仅适用于特定对象。 查看触发器和恢复 记录。
  • 在触发器结束之前,不会记录字段历史记录。如果在 触发器时,您看不到当前交易的任何历史记录。
  • 字段历史记录跟踪遵循 当前用户。如果当前用户没有直接编辑对象的权限,或者 字段,但用户激活了一个触发器,该触发器使用历史记录更改对象或字段 启用跟踪后,不会记录任何更改历史记录。
  • 标注必须从触发器异步进行,以便触发器进程不会 在等待外部服务的响应时被阻止。异步标注是 在后台进程中制作,并在外部服务收到响应 返回它。要进行异步标注,请使用异步 Apex,例如 future 方法。有关更多信息,请参阅使用 Apex 调用标注。
  • 在 API 版本 20.0 及更早版本中,如果批量 API 请求导致触发器触发,则每个 触发器要处理的 200 条记录的块被拆分为 100 条记录的块。在 Salesforce API 版本 21.0 及更高版本,不会发生进一步的 API 区块拆分。如果批量 API 请求导致触发器多次触发 200 条记录的块,调控器 在对同一 HTTP 请求的这些触发器调用之间重置限制。
  1. 批量触发器
  2. 触发器语法
  3. 触发上下文变量
  4. 上下文变量注意事项
  5. 常见的批量触发习惯用语
  6. 定义触发器
  7. 触发器和合并语句
  8. 触发器和恢复的记录
  9. 触发器和执行顺序
  10. 不调用触发器的操作
    某些操作不调用触发器。
  11. 触发器中的实体和字段注意事项 创建触发器
    时,请考虑某些实体、字段和操作的行为。
  12. Chatter 对象的触发器 您可以为 FeedItem 和 FeedComment 对象
    编写触发器。
  13. 知识文章
    的触发器注意事项 可以为 KnowledgeArticleVersion 对象编写触发器。了解何时可以使用触发器,以及哪些操作不会触发触发器,例如存档文章。
  14. 触发异常
  15. 触发器和批量请求最佳实践

批量触发器

默认情况下,所有触发器都是批量触发器,并且可以同时处理多条记录 时间。您应始终计划一次处理多条记录。

注意

定义为重复的 Event 对象不会针对 、 或 触发器进行批量处理。insertdeleteupdate批量触发器可以处理单个记录更新和批量操作,例如:

  • 数据导入
  • Lightning 平台批量 API 调用
  • 批量操作,例如记录所有者更改和删除
  • 调用批量 DML 语句的递归 Apex 方法和触发器

触发器语法

定义 触发器,请使用以下命令 语法:

trigger TriggerName on ObjectName (trigger_events) {
                     code_block
                     }

其中可以是一个或多个逗号分隔的列表 以下事件:

trigger_events

  • before insert
  • before update
  • before delete
  • after insert
  • after update
  • after delete
  • after undelete

注意

  • 由 、 或 重复事件调用的触发器,或者 调用触发器时,重复任务会导致运行时错误 从 Lightning 平台 API 批量。insertdeleteupdate
  • 假设您使用插入后或更新后触发器来更改 潜在顾客、联系人或商机的所有权。如果您使用 API 执行以下操作 更改记录所有权,或者如果 Lightning Experience 用户更改了 记录的所有者,则不会发送电子邮件通知。发送电子邮件 通知记录的新所有者,将属性设置为 DMLOptions 更改为 .triggerUserEmailtrue

例如,以下代码定义了 Account 上 和 事件的触发器 对象:before insertbefore update

trigger myAccountTrigger on Account (before insert, before update) {
    // Your code here
}

触发器的代码块不能包含关键字。触发器只能 包含适用于内部类的关键字。此外,你做 无需手动提交触发器所做的任何数据库更改。static如果 Apex 触发器成功完成,则会自动更改任何数据库 承诺。如果您的 Apex 触发器未成功完成,则对 数据库已回滚。

触发上下文变量

所有触发器都定义了允许开发人员使用的隐式变量 以访问运行时上下文。这些变量包含在类中。System.Trigger

变量用法
isExecuting如果 Apex 代码的当前上下文是触发器,则返回 true, 不是 Visualforce 页面、Web 服务或 API 调用。executeanonymous()
isInsert如果此触发器,则返回 由于 Salesforce 用户的插入操作而触发 接口、Apex 或 API。true
isUpdate如果此触发器,则返回 由于 Salesforce 用户的更新操作而触发 接口、Apex 或 API。true
isDelete如果此触发器,则返回 由于删除操作而触发,从 Salesforce 用户界面, Apex 或 API。true
isBefore如果此触发器,则返回 在保存任何记录之前就被解雇了。true
isAfter如果此触发器,则返回 在保存所有记录后被解雇。true
isUndelete如果此触发器,则返回 在从回收站中恢复记录后被触发。这 在 Salesforce 用户执行撤消删除操作后,可能会发生恢复 接口、Apex 或 API。true
new返回 sObject 记录的新版本的列表。这 sObject 列表仅在 、 、 中可用 和触发器,以及 只能在触发器中修改记录。insertupdateundeletebefore
newMap指向 sObject 记录新版本的 ID 的映射。这张地图 仅在 、 、 和 触发器中可用。before updateafter insertafter updateafter undelete
old返回旧版本的 sObject 记录的列表。这 sObject 列表仅在 和 触发器中可用。updatedelete
oldMap旧版本的 sObject 记录的 ID 映射。这张地图 仅在 和 触发器中可用。updatedelete
operationType返回 System.TriggerOperation 类型的枚举,对应于 当前操作。System.TriggerOperation 枚举的可能值 分别是:、、,, , ,和。如果您改变您的 基于不同触发器类型的编程逻辑,考虑使用 带有 唯一触发器执行枚举的不同排列 国家。BEFORE_INSERTBEFORE_UPDATEBEFORE_DELETEAFTER_INSERTAFTER_UPDATEAFTER_DELETEAFTER_UNDELETEswitch
size触发器调用中的记录总数,包括旧的和 新增功能。

注意

触发触发器的记录可以包含无效字段 value,例如除以零的公式。在本例中,字段值在以下变量中设置为:null

  • new
  • newMap
  • old
  • oldMap

例如,在这个简单的触发器中,是一个可以迭代的 sObject 列表 循环过来。它也可以用作绑定 变量。Trigger.newforIN

Trigger simpleTrigger on Account (after insert) {
    for (Account a : Trigger.new) {
        // Iterate over each sObject
    }

    // This single query finds every contact that is associated with any of the
    // triggering accounts. Note that although Trigger.new is a collection of  
    // records, when used as a bind variable in a SOQL query, Apex automatically
    // transforms the list of records into a list of corresponding Ids.
    Contact[] cons = [SELECT LastName FROM Contact
                      WHERE AccountId IN :Trigger.new];
}

此触发器使用布尔上下文变量(如 和)来定义仅执行的代码 对于特定的触发条件:Trigger.isBeforeTrigger.isDelete

trigger myAccountTrigger on Account(before delete, before insert, before update, 
                                    after delete, after insert, after update) {
if (Trigger.isBefore) {
    if (Trigger.isDelete) {

        // In a before delete trigger, the trigger accesses the records that will be
        // deleted with the Trigger.old list.
        for (Account a : Trigger.old) {
            if (a.name != 'okToDelete') {
                a.addError('You can\'t delete this record!');
            } 
        }
    } else {

    // In before insert or before update triggers, the trigger accesses the new records
    // with the Trigger.new list.
        for (Account a : Trigger.new) {
            if (a.name == 'bad') {
                a.name.addError('Bad name');
            }
    }
    if (Trigger.isInsert) {
        for (Account a : Trigger.new) {
            System.assertEquals('xxx', a.accountNumber); 
            System.assertEquals('industry', a.industry); 
            System.assertEquals(100, a.numberofemployees);
            System.assertEquals(100.0, a.annualrevenue);
            a.accountNumber = 'yyy';
        }

// If the trigger is not a before trigger, it must be an after trigger.
} else {
    if (Trigger.isInsert) {
        List<Contact> contacts = new List<Contact>();
        for (Account a : Trigger.new) {        
            if(a.Name == 'makeContact') {
                contacts.add(new Contact (LastName = a.Name,
                                          AccountId = a.Id));
            }
        } 
      insert contacts;
    }
  }
}}}

上下文变量注意事项

请注意触发器上下文变量的以下注意事项:

  • trigger.new并且不能用于 Apex DML 操作。trigger.old
  • 您可以使用对象来更改其自己的字段值,但只能在触发器之前使用。在所有触发器之后,不会保存,因此运行时异常是 扔。trigger.newtrigger.new
  • trigger.old始终是只读的。
  • 您无法删除 .trigger.new

下表列出了有关不同触发器事件中某些操作的注意事项:

触发事件可以使用以下方法更改字段trigger.new可以使用更新 DML 操作更新原始对象可以使用删除 DML 操作删除原始对象
before insert允许。不適用。原始对象尚未创建; 没有任何东西可以引用它,所以没有任何东西可以更新它。不適用。原始对象尚未创建;没有什么可以引用它,所以 没有什么可以更新它。
after insert不允许。抛出运行时错误,就像已经保存的那样。trigger.new允许。允许,但不是必需的。对象在被删除后立即被删除 插入。
before update允许。不允许。引发运行时错误。不允许。引发运行时错误。
after update不允许。抛出运行时错误,就像已经保存的那样。trigger.new允许。即使糟糕的代码可能会导致无限递归错误地执行此操作, 该错误将由调控器限制发现。允许。更新在删除对象之前保存,因此,如果对象是 取消删除后,更新将变为可见。
before delete不允许。引发运行时错误。 在删除触发器之前不可用。trigger.new允许。更新在删除对象之前保存,因此,如果对象是 取消删除后,更新将变为可见。不允许。引发运行时错误。删除操作已在进行中。
after delete不允许。引发运行时错误。 在删除后触发器中不可用。trigger.new不適用。该对象已被删除。不適用。该对象已被删除。
after undelete不允许。引发运行时错误。允许。允许,但不是必需的。对象在被删除后立即被删除 插入。

常见的批量触发习惯用语

尽管批量触发器允许开发人员处理更多记录 在不超出执行调控器限制的情况下,它们可能会更加困难 供开发人员理解和编码,因为它们涉及处理 一次批处理多条记录。以下各节提供 写作时应经常使用的成语示例 散装。

在批量触发器中使用映射和集

设置 和 MAP 数据结构对于成功进行批量编码至关重要 触发器。集可用于隔离不同的记录,而映射 可用于保存按记录 ID 组织的查询结果。

为 例如,此批量触发器首先来自示例引用应用程序 添加与 OpportunityLineItem 关联的每个价目表条目 记录到 一个集合,确保该集合仅包含不同的元素。然后 查询 PricebookEntries 以获取其关联的产品颜色,以及 将结果放置在地图中。创建地图后,触发器 循环访问 中的 OpportunityLineItems,并使用映射来分配 适当的颜色。Trigger.newTrigger.new

// When a new line item is added to an opportunity, this trigger copies the value of the
// associated product's color to the new record.
trigger oppLineTrigger on OpportunityLineItem (before insert) {

    // For every OpportunityLineItem record, add its associated pricebook entry
    // to a set so there are no duplicates.
    Set<Id> pbeIds = new Set<Id>();
    for (OpportunityLineItem oli : Trigger.new) 
        pbeIds.add(oli.pricebookentryid);

    // Query the PricebookEntries for their associated product color and place the results
    // in a map.
    Map<Id, PricebookEntry> entries = new Map<Id, PricebookEntry>(
        [select product2.color__c from pricebookentry 
         where id in :pbeIds]);
         
    // Now use the map to set the appropriate color on every OpportunityLineItem processed
    // by the trigger.
    for (OpportunityLineItem oli : Trigger.new) 
        oli.color__c = entries.get(oli.pricebookEntryId).product2.color__c;  
}

在批量触发器中将记录与查询结果相关联

使用 和 ID to-sObject 映射以将记录与查询结果相关联。例如,此触发器 从示例引用应用程序用于创建一组唯一 ID ()。然后,该集用作查询的一部分,以创建报价列表 与触发器正在处理的商机相关联。 对于查询返回的每个报价,相关的商机是 检索自并防止被删除:Trigger.newMapTrigger.oldMapTrigger.oldMapTrigger.oldMap.keySet()Trigger.oldMap

trigger oppTrigger on Opportunity (before delete) {
    for (Quote__c q : [SELECT opportunity__c FROM quote__c 
                       WHERE opportunity__c IN :Trigger.oldMap.keySet()]) {
        Trigger.oldMap.get(q.opportunity__c).addError('Cannot delete 
                                                       opportunity with a quote');
    }
}

使用触发器插入或更新具有唯一值的记录 领域

当 或 事件导致 在另一条新记录中复制唯一字段值的记录 在该批处理中,重复记录的错误消息包括 第一条记录的 ID。但是,错误可能是 请求完成时,消息可能不正确。insertupsert

当存在触发器时,批量操作中的重试逻辑 导致发生回滚/重试周期。该重试周期会分配新的 新记录的键。例如,如果插入了两条记录 具有唯一字段的相同值,并且您还为触发器定义了一个事件, 第二条重复记录失败,报告第一条记录的 ID。 但是,一旦系统回滚更改并重新插入 第一条记录本身,该记录将收到一个新 ID。这意味着 第二条记录报告的错误消息不再有效。insert

定义触发器

触发器代码作为元数据存储在与其关联的对象下。要定义一个 Salesforce 中的触发器:

  1. 从要触发其对象的对象的对象管理设置中 访问,转到触发器。提示对于 Attachment、ContentDocument 和 Note 标准对象,无法创建 Salesforce 用户界面中的触发器。对于这些对象,创建一个触发器 使用开发工具,例如开发人员控制台或 Salesforce Visual Studio Code 的扩展。或者,您也可以使用元数据 应用程序接口。
  2. 在“触发器”列表中,单击“新建”。
  3. 要指定 Apex 的版本和用于此触发器的 API,请单击“版本” 设置。如果您的组织已从 AppExchange,您还可以指定要使用的每个托管软件包的版本 使用此触发器。将触发器与最新版本的 Apex 关联,然后 API 和每个托管包,使用所有版本的默认值。你 如果要访问组件,可以指定旧版本的托管包 或与最新包版本不同的功能。
  4. 单击 Apex Trigger,然后选中 Is Active 复选框,如果您 想要编译并启用触发器。如果仅 想要将代码存储在组织的元数据中。此复选框由 违约。
  5. 在正文文本框中,输入触发器的顶点。单个 触发器的长度最多为 100 万个字符。定义 触发器,请使用以下命令 语法:trigger TriggerName on ObjectName (trigger_events) { code_block }其中可以是一个或多个逗号分隔的列表 以下事件:trigger_events
    • before insert
    • before update
    • before delete
    • after insert
    • after update
    • after delete
    • after undelete
    注意
    • 由 、 或 重复事件调用的触发器,或者 调用触发器时,重复任务会导致运行时错误 从 Lightning 平台 API 批量。insertdeleteupdate
    • 假设您使用插入后或更新后触发器来更改 潜在顾客、联系人或商机的所有权。如果您使用 API 执行以下操作 更改记录所有权,或者如果 Lightning Experience 用户更改了 记录的所有者,则不会发送电子邮件通知。发送电子邮件 通知记录的新所有者,将属性设置为 DMLOptions 更改为 .triggerUserEmailtrue
  6. 点击保存

注意

触发器使用设置为 只要依赖元数据自上次编译触发器以来未更改。如果有的话 对触发器中使用的对象名称或字段进行更改,包括 表面更改(例如对对象或字段描述的编辑),该标志将设置为直到 Apex 编译器重新处理代码。重新编译发生在以下情况下 触发器将在下次执行时执行,或者当用户在元数据中重新保存触发器时执行。isValidtrueisValidfalse

如果 查找字段引用已删除的记录,Salesforce 将清除 默认的查找字段。或者,您可以选择阻止记录 如果它们处于查找关系中,则删除。

Apex 触发器编辑器

Apex 和 Visualforce 编辑器具有以下功能:语法高亮显示编辑器会自动对关键字和所有关键字应用语法高亮显示 函数和运算符。搜索 (Search icon)通过搜索,可以在当前页面、类或 触发。若要使用搜索,请在“搜索”文本框中输入字符串,然后单击“查找下一个”。

  • 要将找到的搜索字符串替换为另一个字符串,请输入新的 字符串,然后单击“替换”以仅替换该实例,或单击“全部替换”以替换该实例,然后单击“全部替换”以替换该实例,然后单击“替换” 页面中出现的搜索字符串的所有其他实例, 类或触发器。
  • 若要使搜索操作区分大小写,请选择“匹配大小写”选项。
  • 若要使用正则表达式作为搜索字符串,请选择“正则表达式”选项。常规 表达式遵循 JavaScript 的正则表达式规则。搜索 使用正则表达式可以找到换行超过 一行。如果将 replace 操作与 找到的字符串一起使用 一个正则表达式,替换操作也可以绑定 正则表达式组变量(、 等) 找到搜索字符串。例如,要将标签替换为标签,并保留所有 属性对原来完好无损,搜索并替换它 跟。$1$2<h1><h2><h1><h1(\s+)(.*)><h2$1$2>

转到行
此按钮允许您突出显示指定的行号。如果该行是 当前不可见,编辑器将滚动到该行。
撤消 和重做
使用撤消可撤消编辑操作,使用重做可重新创建编辑操作 那被撤消了。
字体大小
从下拉列表中选择字体大小以控制 编辑器中显示的字符。
行和列位置
光标的行和列位置显示在状态栏中 编辑器的底部。这可以与转到行 (Go To Line icon) 一起使用,以快速浏览编辑器。
行数和字符数
行数和字符总数显示在状态栏中 编辑器的底部。

触发器和合并语句

合并事件不会触发自己的触发事件。相反,他们 触发删除和更新事件,如下所示:删除丢失的记录单个合并操作会为所有记录触发单个删除事件 在合并中删除的。确定删除了哪些记录 合并操作的结果是使用 中的字段。在丢失合并后删除记录时 操作,则其字段设置为中奖记录的 ID。该字段仅设置 在触发事件中。 如果您的应用程序需要对已删除的记录进行特殊处理 由于合并而发生,则需要使用 Trigger 事件。MasterRecordIdTrigger.oldMasterRecordIdMasterRecordIdafter deleteafter delete更新中奖记录单个合并操作会为获胜的单个更新事件触发 仅记录。由于以下原因而重新设置父级的任何子记录 合并操作不会触发触发器。

例如,如果合并了两个联系人,则只有删除和更新 接触触发火灾。没有触发与联系人相关的记录, 如客户或商机,火。以下是合并发生时事件的顺序:

  1. 触发器 火灾。before delete
  2. 系统会因合并而删除必要的记录,分配 新的父记录到子记录,并在已删除的 记录。MasterRecordId
  3. 触发器 火灾。after delete
  4. 系统执行主记录所需的特定更新。 正常的更新触发器适用。

触发器和恢复的记录

触发事件仅适用于 恢复的记录 – 即已删除然后从回收站中恢复的记录 通过 DML 语句。这些也称为 未删除的记录。after undeleteundelete

触发器事件仅在顶级上运行 对象。例如,如果您删除一个帐户,则 Opportunity 也可能被删除。当您恢复时 回收站中的帐户,商机也会被恢复。如果存在与帐户和 Opportunity,只有 Account 触发器 事件执行。after undeleteafter undeleteafter undelete触发器 事件仅针对以下对象触发:

after undelete

  • 客户
  • 资产
  • 运动
  • 个案
  • 联系
  • 内容文档
  • 合同
  • 自定义对象
  • 事件
  • 潜客
  • 机会
  • 产品
  • 解决方案
  • 任务

触发器和执行顺序

当您使用 、 或语句保存记录时,Salesforce 会在某个 次序。insertupdateupsert

在 Salesforce 在服务器上执行这些事件之前,浏览器会运行 JavaScript 验证记录是否包含任何依赖的选择列表字段。验证限制 每个从属选择列表字段设置为其可用值。不会对 客户端。

注意

有关执行顺序的图示表示形式,请参阅执行顺序 Salesforce Architects 网站上的概述。这 diagram 特定于其上指示的 API 版本,并且可能与 信息在这里。此 Apex 开发人员指南页面包含最新的 有关此 API 版本的执行顺序的信息。访问其他 API 版本,请使用 Apex 开发人员的版本选择器 指南。

在服务器上,Salesforce 按此顺序执行事件。

  1. 从数据库加载原始记录或初始化语句的记录。upsert
  2. 从请求中加载新的记录字段值并覆盖旧的记录字段值 值。Salesforce 根据类型执行不同的验证检查 的请求。
    • 对于来自标准 UI 编辑页面的请求,Salesforce 会运行这些系统 对记录进行验证检查:
      • 符合特定于布局的规则
      • 布局级别和字段定义的必需值 水平
      • 有效字段格式
      • 最大视场长度
      此外,如果请求来自标准上的 User 对象 UI 编辑页面,Salesforce 运行自定义验证规则。
    • 对于来自创建多行项目(例如报价单项目)的请求,以及 商机行项时,Salesforce 会运行自定义验证规则。
    • 对于来自其他来源(如 Apex 应用程序或 SOAP)的请求 API 调用时,Salesforce 仅验证外键并受限制 选择列表。在执行触发器之前,Salesforce 会验证任何 自定义外键不引用对象本身。
  3. 执行记录触发的流,这些流配置为在记录之前运行 保存。
  4. 执行所有触发器。before
  5. 再次运行大多数系统验证步骤,例如验证所有必需的步骤 字段具有非值,并运行 任何自定义验证规则。Salesforce 唯一没有的系统验证 第二次运行(当请求来自标准 UI 编辑页面时)是 强制执行特定于布局的规则。null
  6. 执行重复的规则。如果重复规则将记录标识为 复制并使用阻止操作,则不会保存记录,也不会进一步保存 步骤,例如触发器和 工作流规则,被采用。after
  7. 将记录保存到数据库,但尚未提交。
  8. 执行所有触发器。after
  9. 执行分配规则。
  10. 执行自动响应规则。
  11. 执行工作流规则。如果有工作流字段更新:注意这个序列 仅适用于工作流规则。
    1. 再次更新记录。
    2. 再次运行系统验证。自定义验证规则、流程、重复 规则、流程和升级规则不会再次运行。
    3. 执行触发器 和触发器, 无论记录操作如何(插入或更新),再进行一次 (而且只有一次)before updateafter update
  12. 执行升级规则。
  13. 执行这些 Salesforce Flow 自动化,但不是按保证的顺序执行。
    • 过程
    • 进程启动的流
    • 工作流规则启动的流(流触发器 工作流操作试点)
    当进程或流执行 DML 操作时,受影响的记录 完成保存过程。
  14. 执行记录触发的流,这些流配置为在记录 保存
  15. 执行授权规则。
  16. 如果记录包含汇总摘要字段或属于跨对象 工作流,执行计算并更新 父记录。父记录经过保存过程。
  17. 如果父记录已更新,并且祖父级记录包含汇总 摘要字段或是跨对象工作流的一部分,执行计算和 更新祖父母记录中的汇总摘要字段。祖父母记录 完成保存过程。
  18. 执行基于条件的共享评估。
  19. 将所有 DML 操作提交到数据库。
  20. 将更改提交到数据库后,执行提交后逻辑是 执行。提交后逻辑的示例(排名不分先后)包括:
    • 发送电子邮件
    • 排队的异步 Apex 作业,包括可排队的作业和未来的 方法
    • 记录触发流中的异步路径

注意

在递归保存期间,Salesforce 会跳过第 9 步(分配规则)到 17 (祖父母记录中的汇总摘要字段)。

其他注意事项

使用触发器时,请注意以下注意事项。

  • 如果工作流规则字段更新由记录更新触发,则不会保存新更新的 字段。相反,在初始记录更新之前保留对象 䍬。例如,现有记录具有一个初始值的数字字段 的 1.用户将此字段更新为 10,并触发工作流规则字段更新 并将其递增为 11。在工作流字段更新后触发的触发器中, 从中获取的对象是 原始值为 1,而不是 10。请参阅更新前后的 Trigger.old 值 触发器。Trigger.oldTrigger.oldupdateTrigger.old
  • 如果在允许部分成功的情况下进行 DML 调用,则会在 第一次尝试,并在随后的尝试中再次发射。因为这些 触发器调用是同一事务的一部分,静态类变量 触发器访问的不会重置。请参阅批量 DML 异常处理。
  • 如果在同一个事件的对象上定义了多个触发器,则顺序 不保证触发器的执行。例如,如果 Case 有两个触发器,一个新的触发器 插入案例记录。这两个触发器的触发顺序不是 保证。before insert
  • 了解在 将联系人关联到多个帐户的组织,请参阅 AccountContactRelation。
  • 了解执行顺序 使用触发器设置阶段和预测类别, 请参阅商机。before
  • 在 API 版本 53.0 及更早版本中,保存后记录触发的流在 权利被执行。

不调用触发器的操作

某些操作不会调用触发器。触发器是为 Java 应用程序服务器启动或处理。因此,某些系统批量操作不会调用 触发器。一些例子包括:

  • 级联删除操作。未启动的记录不会导致触发器评估。delete
  • 由于合并而重新设置父级的子记录的级联更新 操作
  • 大规模广告系列状态更改
  • 分质转移
  • 群发地址更新
  • 批量批准请求转移
  • 群发电子邮件操作
  • 修改自定义字段数据类型
  • 重命名或替换选择列表
  • 管理价目表
  • 在选中传输分部选项的情况下更改用户的默认分部
  • 对以下对象的更改:
    • 品牌模板
    • MassEmailTemplate(马斯电子邮件模板)
    • 文件夹
  • 更新帐户触发器不会在企业帐户记录类型之前或之后触发 更改为个人帐户(或个人帐户记录类型更改为业务 帐户。
  • 当计数器增加时,更新触发器不会触发。FeedItemLikeCount

注意

在个人帐户上插入、更新和删除会触发帐户触发器,而不是联系人 触发器。与以下内容关联的触发器 仅当潜在客户的验证和触发时,才会在潜在客户转换期间触发操作 在组织中启用转换:

before

  • insert客户、联系人和 机会
  • update客户和联系人数量

当帐户所有者因 关联商机的所有者正在更改。

在以下情况下,不会触发 and 触发器和验证规则:beforeafter

  • 在商机上修改商机产品。
  • 商机产品计划会更改商机产品,即使商机 产品改变机会。

但是,汇总摘要字段确实会更新,并且与 机会确实在运行。

触发器中不允许使用 和 PageReference 方法。getContentgetContentAsPDF

请注意 ContentVersion 对象的以下事项:

  • 涉及 ContentVersion 对象的内容包操作,包括幻灯片和幻灯片 autorevision,不要调用触发器。注意当幻灯片内的内容包被修改时 包已修订。
  • TagCsv 和 VersionData 字段的值为 仅当请求创建或更新 ContentVersion 记录时,触发器中才可用 源自 API。
  • 不能将 ContentVersion 对象与 ContentVersion 对象一起使用或触发器。beforeafter delete

在以下情况下,不会触发 Attachment 对象上的触发器:

  • 附件是通过案例源发布者创建的。
  • 用户通过“电子邮件相关”列表发送电子邮件并添加附件文件。

当通过 Email-to-Case 或 UI 创建 Attachment 对象时,触发器将触发。

触发器中的实体和字段注意事项

创建触发器时,请考虑某些实体、字段和 操作。

QuestionDataCategorySelection 实体在插入后不可用 触发器

触发后触发的触发器 插入一条或多条记录 无权访问与 插入 s。例如, 以下查询不会在触发器中返回任何结果:

after insertQuestionQuestionDataCategorySelectionQuestionafter insert

QuestionDataCategorySelection[] dcList = 
    [select Id,DataCategoryName from QuestionDataCategorySelection where ParentId IN :questions];

在触发器之前不可更新的字段

某些字段值是在触发器触发后发生的系统保存操作期间设置的。作为 结果,这些字段无法在 OR 触发器中修改或准确检测。一些例子包括:

beforebefore insertbefore update

  • Task.isClosed
  • Opportunity.amount*
  • Opportunity.ForecastCategory
  • Opportunity.isWon
  • Opportunity.isClosed
  • Contract.activatedDate
  • Contract.activatedById
  • Case.isClosed
  • Solution.isReviewed
  • Id(所有记录)**
  • createdDate(对于所有人 记录)**
  • lastUpdated(所有记录)
  • Event.WhoId(当共享活动 已启用)
  • Task.WhoId(当共享活动 已启用)

* 当没有时,可以通过触发器进行修改。OpportunitylineitemsAmountbefore

** Id并且可以在触发器中检测到,但无法修改。createdDatebefore update

触发后无法更新的字段

以下字段不能由 或 触发器更新。after insertafter update

  • Event.WhoId
  • Task.WhoId

插入和更新触发器中事件日期时间字段的注意事项

我们建议使用以下日期和时间字段来创建或更新事件。

  • 创建或更新定时事件时,请使用以避免日期和时间不一致的问题 值。ActivityDateTime
  • 创建或更新全天活动时,请使用以避免日期和时间不一致的问题 值。ActivityDate
  • 我们建议您使用,因为它适用于所有更新和为事件创建。DurationInMinutes

插入和更新触发器中不支持的操作

和触发器不支持以下操作。insertupdate

  • 通过 or 对象操作活动关系(如果启用了共享活动)TaskRelationEventRelation
  • 通过对象操作组事件的受邀者关系,无论是否共享 活动已启用Invitee

取消删除后触发器中不支持的实体

某些对象无法还原,因此不应具有触发器。

after undelete

  • 协作组
  • 协作组成员
  • 饲料项目
  • FeedComment (英语)

更新触发器的注意事项

字段历史记录跟踪遵循当前用户的权限。 如果当前用户没有直接编辑对象或字段的权限,但 用户激活一个触发器,该触发器通过历史跟踪更改对象或字段 启用后,不会记录任何更改历史记录。

Salesforce for Outlook 的 Salesforce 侧面板的注意事项

当使用 Salesforce 侧面板将电子邮件关联到记录时 Salesforce for Outlook,电子邮件关联在任务记录的 or 字段中表示。关联在 任务已创建,因此 和 字段不会立即创建 可用于插入和更新的任务触发器 事件,其值最初是 。 和字段在后续任务记录中对保存的任务记录进行设置 但是,操作,以便以后可以检索其值。WhoId WhatId Task.WhoId Task.WhatId before after null WhoId WhatId

Chatter 对象的触发器

可以为 FeedItem 和 FeedComment 对象编写触发器。

FeedItem、FeedAttachment 和 FeedComment 的触发注意事项

  • 只能插入 、 、 、 和 类型的 FeedItems,因此可以调用 or 触发器。用户状态更新不会导致 FeedItem 触发触发器。TextPostQuestionPostLinkPostHasLinkContentPostHasContentbeforeafter insert
  • 虽然 API 版本 18.0、19.0 和 20.0 支持 FeedPost 对象, 不要使用针对 21.0 之前的版本保存的任何插入或删除触发器。
  • 对于 FeedItem,触发器中不提供以下字段:before insert
    • 内容大小
    • 内容类型
    此外,ContentData 字段不可用 在任何删除触发器中。
  • FeedItem 对象上的触发器在其 附件和功能信息被保存,这意味着信息和信息可能不会被保存 在触发器中可用。ConnectApi.FeedItem.attachmentConnectApi.FeedElement.capabilities附件和功能信息可能无法从以下位置获得 方法: 和ConnectApi.ChatterFeeds.getFeedItemConnectApi.ChatterFeeds.getFeedElementConnectApi.ChatterFeeds.getFeedPollConnectApi.ChatterFeeds.getFeedElementPollConnectApi.ChatterFeeds.postFeedItemConnectApi.ChatterFeeds.postFeedElementConnectApi.ChatterFeeds.shareFeedItemConnectApi.ChatterFeeds.shareFeedElementConnectApi.ChatterFeeds.voteOnFeedPollConnectApi.ChatterFeeds.voteOnFeedElementPoll
  • FeedAttachment 不是可触发的对象。您可以在 FeedItem 更新通过 SOQL 查询触发。为 例:trigger FeedItemTrigger on FeedItem (after update) { List<FeedAttachment> attachments = [SELECT Id, Title, Type, FeedEntityId FROM FeedAttachment WHERE FeedEntityId IN :Trigger.new ]; for (FeedAttachment attachment : attachments) { System.debug(attachment.Type); } }
  • 插入带有关联附件的源项时,FeedItem 为 首先插入,然后创建 FeedAttachment 记录。更新源时 项目,则首先插入 FeedAttachment 记录, 然后更新 FeedItem。由于这一系列操作,在 Salesforce Classic FeedAttachment 在 和 触发器中可用。当通过 Lightning Experience 完成附件时,它是 在 和 触发器中均可用;但在触发器中,使用 future 方法 访问 FeedAttachments。UpdateAfterInsertUpdateAfterInsertAfterInsert
  • 以下源附件操作会导致触发 FeedItem 更新触发器。
    • 将 FeedAttachment 添加到 FeedItem 中,并使 FeedItem 类型 改变。
    • 从 FeedItem 中删除 FeedAttachment 并导致 FeedItem 类型 来改变。
  • 插入或更新 FeedAttachment 时,不会触发 FeedItem 触发器 不会导致关联的 FeedItem 发生更改。
  • 不能在更新更新后 FeedItem 触发器中插入、更新或删除 FeedAttachments。
  • 对于插入插入后触发的 FeedComment, 与 FeedComment 关联的 ContentVersion 字段(通过 获取)不是 可用。FeedComment.RelatedRecordId

其他 Chatter 触发器注意事项

  • Apex 代码在 Chatter 上下文中执行时会使用额外的安全性。要发布到 专用组,则运行代码的用户必须是该组的成员。如果 running user 不是成员,可以将 CreatedById 字段设置为 FeedItem 记录中组的成员。
  • 更新 CollaborationGroupMember 时, CollaborationGroup 也会自动更新,以确保成员 计数是正确的。因此,当 CollaborationGroupMember 或触发器运行时,CollaborationGroup 触发器也会运行。updatedeleteupdate

知识文章的触发注意事项

可以为 KnowledgeArticleVersion 对象编写触发器。了解何时可以使用 触发器,以及哪些操作不会触发触发器,例如存档文章。通常,KnowledgeArticleVersion (KAV) 记录可以使用以下触发器:

  • 创建 KAV 记录会调用 和 触发器。这包括创建一个 文章,以及从存档、已发布和主语言文章创建草稿 使用“恢复”、“编辑为草稿”和“提交以供翻译” 行动。before insertafter insert
  • 编辑现有 KAV 记录会调用 和 触发器。before updateafter update
  • 删除 KAV 记录会调用 和 触发器。before deleteafter delete
  • 导入文章会调用 和 触发器。导入文章 translations 还调用 and 触发器。before insertafter insertbefore updateafter update

更改 KAV 记录的发布状态的操作(如“发布”和“存档”)执行 不触发 Apex 或流触发器。但是,有时从 UI 发布文章会导致 要保存的文章,在这些情况下,AND 触发器 叫。before updateafter update

知识操作和顶点触发器

在为 KnowledgeArticleVersion 上的操作编写 Apex 触发器时,请考虑以下事项:保存、保存和关闭保存文章时,将调用 和 触发器。当一个新的 文章首次保存,和触发器工作 相反。before updateafter updatebefore insertafter insert编辑、编辑为草稿

  • 编辑草稿翻译时,可以使用 和 触发器。before updateafter update
  • 这 编辑 由于草稿操作从已发布的文章创建草稿,因此 和触发器触发。before insertafter insert
  • 在 Salesforce Classic 中,当草稿主语言文章出现以下情况时,不会触发任何触发器 编辑。
  • 在 Salesforce Classic 中,在以下情况下调用 和 触发器 从“文章管理”选项卡编辑存档的文章。这将创建一个草稿 KAV记录。before insertafter insert

取消、删除

在以下情况下调用 和 触发器:before deleteafter delete

  • 删除翻译草稿时。
  • 在 Salesforce Classic 的“文章管理”或“知识”选项卡中,在 编辑已发布的文章,然后单击“取消”。这将删除新的 草案。

提交翻译此操作将创建草稿翻译,因此通常可以使用 和 触发器。在 Salesforce Classic 中,当您从“知识”选项卡创建新文章时,可以使用 和 触发器,保存 它,然后提交翻译。和触发点火 当主语言文章当前正在编辑时,但不是从列表视图或 查看文章时。before insertafter insertbefore updateafter updatebefore updateafter update分配只有在这样做时才会调用 和 触发器 首先导致记录保存。当文章在 单击“分配”按钮。before updateafter update

不触发触发器的操作

以下操作无法触发 Apex 触发器:

  • 从回收站中取消删除文章。
  • 预览和存档文章。

对闪电迁移的影响

从 Salesforce Classic 中的 Knowledge 迁移到 Lightning Knowledge 会影响 Apex 触发器。在 KnowledgeArticleVersion 对象上编写 Apex 触发器会创建依赖项 并防止删除 KAV 对象。迁移具有多个 文章类型,您必须删除任何引用 KAV 文章类型。在迁移过程中,如果 Apex 仍然触发,管理员会看到一条错误消息 参考文章类型 KAV 对象在迁移过程中删除。如果您取消 闪电知识迁移,而 Apex 触发器存在引用新 KAV 对象, 管理员会收到通知,您必须删除 Apex 法典。

示例知识触发器

例如,您可以定义一个触发器,该触发器在文章出现以下情况时输入摘要文本: 创建。

trigger KAVTrigger on KAV_Type__kav (before insert) {
    for (KAV_Type__kav kav : Trigger.New) {
        kav.Summary__c = 'Updated article summary before insert';
    }  
}

触发异常

触发器可用于通过对记录或字段调用方法来防止发生 DML 操作。当用于 和 触发器中的记录以及触发器中的记录时,自定义错误消息显示在 应用程序界面并记录。addError()Trigger.newinsertupdateTrigger.olddelete

注意

如果将错误添加到触发器中,则用户体验到的响应时间延迟较少。before可以使用以下方法标记正在处理的记录的子集:

addError()

  • 如果触发器是由 Apex 中的 DML 语句生成的,则任何一个错误都会导致 整个操作回滚。但是,运行时引擎仍会处理 编译错误综合列表的操作。
  • 如果触发器是由 Lightning 平台 API 中的批量 DML 调用生成的,则运行时 引擎将不良记录放在一边,并尝试对以下记录进行部分保存 未生成错误。请参阅批量 DML 异常处理。

如果触发器引发未经处理的异常,则所有记录都标记为错误,并且没有 进行进一步处理。

触发器和批量请求最佳实践

一个常见的开发陷阱是假设触发器调用永远不会包含更多 超过一条记录。Apex 触发器经过优化,可批量运行,根据定义, 要求开发人员编写支持批量操作的逻辑。这是一个有缺陷的编程模式的示例。它假定只有一条记录是 在触发器调用期间拉入。虽然这可能支持大多数用户界面事件, 它不支持通过 SOAP API 调用的批量操作或 视觉力。

trigger MileageTrigger on Mileage__c (before insert, before update) {
   User c = [SELECT Id FROM User WHERE mileageid__c = Trigger.new[0].id];
}

这是有缺陷的编程模式的另一个示例。它假设少于 100 在触发器调用期间,记录在范围内。如果发出的查询超过 100 个, 触发器将超出 SOQL 查询 限制。

trigger MileageTrigger on Mileage__c (before insert, before update) {
   for(mileage__c m : Trigger.new){ 
      User c = [SELECT Id FROM user WHERE mileageid__c = m.Id];
   }
}

有关调控器限制的更多信息,请参阅执行调控器和 限制。此示例演示了支持触发器批量性质的正确模式,而 尊重州长 限制:

Trigger MileageTrigger on Mileage__c (before insert, before update) {
   Set<ID> ids = Trigger.newMap.keySet();
   List<User> c = [SELECT Id FROM user WHERE mileageid__c in :ids];
}

此模式通过将集合传递给集合,然后使用 单个 SOQL 查询。此模式捕获请求中的所有传入记录,同时 限制 SOQL 查询的数量。Trigger.new

设计批量程序的最佳实践

以下是此设计模式的最佳实践:

  • 通过添加 记录到集合中,并针对这些记录执行 DML 操作 收集。
  • 通过预处理记录和生成 集合,可以放在与子句一起使用的单个 SOQL 语句中。IN

ref

运行 Apex

您可以在以下位置以编程方式访问 Salesforce 用户界面的许多功能 Apex,您可以与外部 SOAP 和 REST Web 服务集成。您可以运行 Apex 代码 使用多种机制。Apex 代码在原子事务中运行。

  • 调用 Apex 您可以使用触发器、异步或作为 SOAP 或 REST Web 服务运行 Apex
    代码。
  • Apex 事务和调速器限制
    Apex 事务可确保数据的完整性。Apex 代码作为原子事务的一部分运行。Governor 执行限制可确保有效利用 Lightning 平台多租户平台上的资源。
  • 将 Salesforce 功能与 Apex 结合使用 Salesforce 用户界面的许多功能都在 Apex
    中公开,以便您可以在 Lightning Platform 中以编程方式访问它们。例如,您可以编写 Apex 代码以发布到 Chatter 摘要,或使用审批方法提交和批准流程请求。
  • 集成和 Apex 实用程序
    Apex 允许您使用标注与外部 SOAP 和 REST Web 服务集成。您可以使用用于 JSON、XML、数据安全和编码的实用程序。还为带有文本字符串的正则表达式提供了一个通用实用程序。

调用 Apex

您可以使用触发器运行Apex代码,也可以异步运行,也可以作为SOAP或REST Web运行 服务业。

  1. 匿名块 匿名块
    是 Apex 代码,它不会存储在元数据中,但可以编译和执行。
  2. 触发器 可以使用触发器
    调用 Apex 触发器使您能够在更改 Salesforce 记录之前或之后执行自定义操作,例如插入、更新或删除。
  3. 异步 Apex 提供了多种异步运行 Apex
    代码的方法。选择最适合您需求的异步 Apex 功能。
  4. 将 Apex 方法公开为 SOAP Web 服务 您可以将 Apex 方法公开为 SOAP Web 服务
    ,以便外部应用程序可以访问您的代码和应用程序。
  5. 将 Apex 类公开为 REST Web 服务
    您可以公开 Apex 类和方法,以便外部应用程序可以通过 REST 体系结构访问您的代码和应用程序。
  6. Apex 电子邮件服务 您可以使用电子邮件服务
    来处理入站电子邮件的内容、标题和附件。例如,您可以创建一个电子邮件服务,该服务根据邮件中的联系人信息自动创建联系人记录。
  7. 使用 InboundEmail 对象 对于 Apex 电子邮件服务域收到的每封电子邮件,Salesforce 都会创建一个单独的 InboundEmail 对象
    ,其中包含该电子邮件的内容和附件。您可以使用实现接口的 Apex 类来处理入站电子邮件。使用该类中的方法,可以访问 InboundEmail 对象以检索入站电子邮件的内容、标头和附件,以及执行许多功能。Messaging.InboundEmailHandlerhandleInboundEmail
  8. Visualforce 类
    除了使开发人员能够向 Salesforce 系统事件(如按钮单击和相关记录更新)添加业务逻辑外,Apex 还可用于通过自定义 Visualforce 控制器和控制器扩展为 Visualforce 页面提供自定义逻辑。
  9. JavaScript 远程处理
    使用 Visualforce 中的 JavaScript 远程处理从 JavaScript 调用 Apex 控制器中的方法。创建具有复杂动态行为的页面,这是标准 Visualforce AJAX 组件无法实现的。
  10. AJAX
    中的 Apex AJAX 工具包包括对通过匿名块或公共方法调用 Apex 的内置支持。webservice

匿名区块

匿名块是 Apex 代码,它不会存储在元数据中,但可以 被编译和执行。

用户权限 需要
要执行匿名 Apex:(匿名 Apex 通过 API 允许在没有“作者顶点”的情况下限制访问 权限。“API 已启用”和“作者顶点”
如果匿名 Apex 标注引用命名凭据作为 端点:自定义应用程序

使用以下方法之一编译和执行匿名块:

  • 开发者控制台
  • 适用于 Visual Studio Code 的 Salesforce 扩展
  • The SOAP API 叫:executeAnonymous()ExecuteAnonymousResult executeAnonymous(String code)

您可以使用匿名块来快速评估 Apex,例如在 开发人员控制台或 Visual Studio Code 的 Salesforce 扩展。您还可以使用 匿名块,用于编写在运行时动态更改的代码。例如,让我们 假设您编写了一个客户端 Web 应用程序,该应用程序从用户那里获取输入,例如名称和 地址。然后,您可以使用 Apex 的匿名块插入具有该名称的联系人 并地址到数据库中。请注意以下有关匿名块的内容(对于 String):

executeAnonymous()code

  • 可以包括用户定义的方法和异常。
  • 用户定义的方法不能包含关键字 。static
  • 您不必手动提交任何数据库更改。
  • 如果 Apex 触发器成功完成,则任何数据库更改都是 自动提交。如果您的 Apex 触发器未完成 成功后,将对数据库所做的任何更改回滚。
  • 与类和触发器不同,匿名块以当前用户和 如果代码违反用户的对象级和字段级,则可能无法编译 权限。
  • 没有本地以外的范围。例如,尽管使用访问修饰符是合法的,但它没有 意义。该方法的范围仅限于匿名块。global
  • 当您在匿名块中定义类或接口(自定义类型)时, 默认情况下,当匿名块时,类或接口被视为虚拟的 执行。即使自定义类型未使用修饰符定义,也是如此。保存您的课程或 界面,以避免这种情况发生。类和接口 在匿名块中定义的内容不会保存在您的组织中。virtual

即使用户定义的方法可以引用自身或更高版本的方法,而无需 对于正向声明,变量不能在其实际声明之前引用。 在以下示例中,Integer 必须 被声明,而 not:intmyProcedure1

Integer int1 = 0;

void myProcedure1() {
    myProcedure2();
}

void myProcedure2() {
    int1++;
}

myProcedure1();

匿名区块的返回结果包括:

  • 调用的编译和执行阶段的状态信息,包括任何 发生的错误
  • 调试日志内容,包括对方法的任何调用的输出(请参阅调试日志System.debug)
  • 任何未捕获的代码执行异常的 Apex 堆栈跟踪,包括 每个调用堆栈元素的类、方法和行号

有关更多信息,请参阅使用 SOAP API 部署 Apex。另请参阅在开发人员控制台和 Visual Studio 的 Salesforce 扩展中使用日志 代码。executeAnonymous()

通过 API 和 Author Apex 权限执行 Anonymous Apex

要使用 API 调用运行任何 Apex 代码,包括保存在组织中的 Apex 方法, 用户必须具有 Author Apex 权限。对于没有 Author Apex 的用户 权限,API 允许限制匿名 Apex 的执行。此例外 仅当用户通过 API 或工具执行匿名 Apex 时才适用 ,但不在开发者控制台中使用。允许此类用户运行 以下内容在匿名块中。executeAnonymous()

  • 他们在匿名块中编写的代码
  • 保存在组织中的 Web 服务方法(使用 关键字声明的方法)webservice
  • 属于 Apex 语言的任何内置 Apex 方法

当用户没有作者 Apex 时,不允许运行任何其他 Apex 代码 许可。例如,调用保存在 不允许组织使用自定义类作为内置的参数 方法。

当没有 Author Apex 权限的用户以匿名方式运行 DML 语句时 块,触发器可能会因此被触发。

ref

自定义设置

自定义设置类似于自定义对象。应用程序开发人员可以创建自定义 数据集,并关联组织、配置文件或特定用户的自定义数据。都 自定义设置数据在应用程序缓存中公开,无需 对数据库进行重复查询的开销。公式字段、验证规则、流、Apex 和 然后,SOAP API 可以使用此数据。

警告

保护 仅适用于标记为受保护并安装到订阅者的自定义设置 组织作为托管包的一部分。否则,它们将被视为公共习俗 设置,并且对所有配置文件(包括来宾用户)都是可读的。不要存储机密, 个人身份信息,或这些设置中的任何私人数据。使用受保护的 仅在托管包中自定义设置。在托管包之外,使用命名凭据 或加密的自定义字段来存储机密,如 OAuth 令牌、密码等 机密材料。

注意

虽然自定义设置数据包含在沙盒副本中,但它被视为 Apex 测试隔离的目的。Apex 测试必须用于查看组织中的现有自定义设置数据。作为 最佳做法是在测试设置中创建所需的自定义设置数据。SeeAllData=true有两种类型的自定义设置。列出自定义设置一种自定义设置,它提供一组可重用的静态数据,这些静态数据可以 在整个组织中访问。如果您经常使用一组特定的数据 将该数据放在列表自定义设置中的应用程序可简化对它的访问。 列表设置中的数据不会因配置文件或用户而异,但可用 组织范围。列表数据的示例包括两个字母的状态缩写、 国际拨号前缀和产品目录号。因为数据是 缓存,访问成本低且效率高:您不必使用计数的 SOQL 查询 违反您的调速器限制。层次结构自定义设置一种使用内置分层逻辑的自定义设置,可让您 针对特定配置文件或用户的“个性化”设置。层次结构逻辑 检查当前用户的组织、配置文件和用户设置,并返回 最具体或“最低”的值。在层次结构中,设置 组织被配置文件设置覆盖,而配置文件设置又被用户覆盖 设置。以下示例说明如何使用自定义设置。

  • 运输应用程序要求用户填写国际国家/地区代码 交付。通过创建所有国家/地区代码的列表设置,用户可以快速访问 此数据无需查询数据库。
  • 应用程序显示帐户位置、最佳路线和交通的地图 条件。此信息对销售代表很有用,但客户经理只想 请参阅帐户位置。通过创建具有自定义复选框字段的层次结构设置 路线和流量,您可以仅为“销售代表”启用此数据 轮廓。

您可以在 Salesforce 用户界面中创建自定义设置:在“设置”中,输入“快速查找”框,然后选择“自定义” 设置。创建自定义设置并添加字段后, 通过单击详细信息中的“管理”,将数据提供给自定义设置 页。使用名称标识每个数据集。Custom Settings例如,如果您有一个名为 Foundation_Countries__c 的自定义设置,其中包含一个文本字段 Country_Code__c,您的数据集可能如下所示:

数据集名称国家/地区代码字段值
美国美国
加拿大
英国英国

还可以在包中包含自定义设置。自定义设置的可见性 包取决于“可见性”设置。

注意

包中仅包含自定义设置定义,而不包含数据。要包含数据,您需要 必须在之后使用订阅组织运行的 Apex 代码填充自定义设置 他们已经安装了该软件包。Apex 可以访问两种自定义设置类型(列表和层次结构)。

注意

如果自定义设置的隐私为“受保护”,并且 自定义设置包含在托管包中,订阅组织不能 编辑值或使用 Apex 访问它们。

访问列表自定义设置

以下示例返回自定义设置数据的映射。该方法返回所有自定义字段的值 与列表关联 设置。

getAll

Map<String_dataset_name, CustomSettingName__c> mcs = CustomSettingName__c.getAll();

下面的示例使用该方法执行以下操作 返回与指定数据集关联的所有字段值。可以使用此方法 使用列表和层次结构自定义设置,使用不同的 参数。

getValues

CustomSettingName__c mc = CustomSettingName__c.getValues(data_set_name);

访问层次结构自定义设置

以下示例使用该方法返回组织级别的数据集值:

getOrgDefaults

CustomSettingName__c mc = CustomSettingName__c.getOrgDefaults();

这 下面的示例使用该方法执行以下操作 返回指定配置文件的数据集值。该方法还可以与用户 ID 一起使用。

getInstancegetInstance

CustomSettingName__c mc = CustomSettingName__c.getInstance(Profile_ID);

ref