将 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