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