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
- 同意
 - ConcurTestStaticVar
 - MockHttpResponseGenerator
 - 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
