在 Salesforce REST 接口集成服务 一文中,主要讲述了如何调用外部服务器的接口,那么该如何开放Salesforce内部接口给外部系统呢?
将Apex类作为Rest Web服务可用非常简单
- 定义Class 为Global
- 定义Method为global static
- 在类和方法上添加注解,比如@HttpGet
类似这样:
@RestResource(urlMapping='/Account/*') global with sharing class MyRestResource { @HttpGet global static Account getRecord() { // 写入代码 return null; } }
在MyRestResource上,我们使用了@RestResource(urlMapping='/Account/*') 进行标注,那么访问该Class的URL路径即
https://yourInstance.salesforce.com/service/apexrest/Account/*
Tips: URL区分大小写,并且可以包含通配符(*)
其中https://yourInstance.salesforce.com为Salesforce组织域名,/service/apexrest是固定关键字,/Account/*是每个开放的接口映射的路径
Tips: 如果是托管包中包含Apex REST方法时,需要加上托管包命名空间,比如MyRestResource类在名为packageNamespace的托管程序包命名空间中,那么通过REST调用这些方法使用的URL格式为 https://instance.salesforce.com/services/apexrest/packageNamespace/Account/*
在每一个Salesforce类中,方法上的注解共有五种,且每种仅能出现一次
简单说,@HttpGet 用于读取检索Salesforce系统中的数据,@HttpPost用于创建记录到Salesforce中,@HttpDelete 用于删除Salesforce中的记录,@HttpPut用于更新Salesforce中的记录,特殊的是,如果有的字段没有传值,会将原本记录上的值置空,类似于Upsert,@HttpPatch 同样用于更新Salesforce中的值,不同于HttpPut的一点是,你传什么字段就更新什么字段,不会说覆盖没有传递的字段值
@RestResource(urlMapping='/Cases/*') global with sharing class CaseManager { @HttpGet global static Case getCaseById() { RestRequest request = RestContext.request; // 从URL末尾获取case Id String caseId = request.requestURI.substring( request.requestURI.lastIndexOf('/')+1); Case result = [SELECT CaseNumber,Subject,Status,Origin,Priority FROM Case WHERE Id = :caseId]; return result; } @HttpPost global static ID createCase(String subject, String status, String origin, String priority) { Case thisCase = new Case( Subject=subject, Status=status, Origin=origin, Priority=priority); insert thisCase; return thisCase.Id; } @HttpDelete global static void deleteCase() { RestRequest request = RestContext.request; String caseId = request.requestURI.substring( request.requestURI.lastIndexOf('/')+1); Case thisCase = [SELECT Id FROM Case WHERE Id = :caseId]; delete thisCase; } @HttpPut global static ID upsertCase(String subject, String status, String origin, String priority, String id) { Case thisCase = new Case( Id=id, Subject=subject, Status=status, Origin=origin, Priority=priority); // ID区分大小写 upsert thisCase; // 返回case Id return thisCase.Id; } @HttpPatch global static ID updateCaseFields() { RestRequest request = RestContext.request; String caseId = request.requestURI.substring( request.requestURI.lastIndexOf('/')+1); Case thisCase = [SELECT Id FROM Case WHERE Id = :caseId]; // 将JSON字符串反序列化 Map<String, Object> params = (Map<String, Object>)JSON.deserializeUntyped(request.requestbody.tostring()); // 遍历 for(String fieldName : params.keySet()) { // 赋值 thisCase.put(fieldName, params.get(fieldName)); } update thisCase; return thisCase.Id; } }
Apex REST支持OAuth2.0 和会话身份验证两种机制,不过在进行接口测试时,Salesforce提供了WorkBench来简化测试流程
使用Salesforce账号登录WorkBench,然后选择 utilities | REST Explorer,选择 POST方式,如下图:
点击Execute后,即可查看接口执行结果如下,点击Show Raw Response可以查看接口返回的原始信息
其他的GET等标准方式测试方式同POST.
Tips: Workbench是一套功能强大的基于Web的工具套件,供管理员和开发人员通过Lightning Platform API与组织进行交互。通过Workbench,可以使用会话身份验证,并使用用户名和密码登录到Salesforce,从而使用REST资源管理器调用REST服务
如果考虑对API端点进行版本控制,以便可以在不破坏现有代码的情况下提供功能升级,我们可以创建两个类来指定/ Cases / v1 / *和/ Cases / v2 / *的URL映射,实现接口版本的平滑切换
最后,测试Apex REST类与测试任何其他Apex类相似,只需通过传入参数值来调用类方法,然后验证结果。 对于不带参数或依赖REST请求中信息的方法,可以通过模拟REST请求类覆盖测试
// 模拟一个Rest请求 RestRequest request = new RestRequest(); // 模拟REST请求参数 request.requestUri = 'https://yourInstance.salesforce.com/services/apexrest/Cases/' + recordId; request.httpMethod = 'GET'; request.params.put('status', 'Working'); // 最后分配这个REST请求给RestContext RestContext.request = request; 所以CaseManager的测试类如下 @IsTest private class CaseManagerTest { @isTest static void testGetCaseById() { Id recordId = createTestRecord(); // 模拟Request 请求 RestRequest request = new RestRequest(); request.requestUri = 'https://yourInstance.salesforce.com/services/apexrest/Cases/‘ + recordId; request.httpMethod = 'GET'; RestContext.request = request; // 调用测试方法 Case thisCase = CaseManager.getCaseById(); // 断言验证结果 System.assert(thisCase != null); System.assertEquals('Test record', thisCase.Subject); } @isTest static void testCreateCase() { ID thisCaseId = CaseManager.createCase('Ferocious chipmunk', 'New', 'Phone', 'Low'); System.assert(thisCaseId != null); Case thisCase = [SELECT Id,Subject FROM Case WHERE Id=:thisCaseId]; System.assert(thisCase != null); System.assertEquals(thisCase.Subject, 'Ferocious chipmunk'); } @isTest static void testDeleteCase() { Id recordId = createTestRecord(); // 模拟Request 请求 RestRequest request = new RestRequest(); request.requestUri = 'https://yourInstance.salesforce.com/services/apexrest/Cases/‘ + recordId; request.httpMethod = 'GET'; RestContext.request = request; // 调用测试方法 CaseManager.deleteCase(); // 验证结果是否删除 List<Case> cases = [SELECT Id FROM Case WHERE Id=:recordId]; System.assert(cases.size() == 0); } @isTest static void testUpsertCase() { ID case1Id = CaseManager.upsertCase( 'Ferocious chipmunk', 'New', 'Phone', 'Low', null); System.assert(Case1Id != null); Case case1 = [SELECT Id,Subject FROM Case WHERE Id=:case1Id]; System.assert(case1 != null); System.assertEquals(case1.Subject, 'Ferocious chipmunk'); // 更新状态 New → Working ID case2Id = CaseManager.upsertCase('Ferocious chipmunk', 'Working', 'Phone', 'Low', case1Id); // 验证是否更新 System.assertEquals(case1Id, case2Id); Case case2 = [SELECT Id,Status FROM Case WHERE Id=:case2Id]; System.assert(case2 != null); System.assertEquals(case2.Status, 'Working'); } @isTest static void testUpdateCaseFields() { Id recordId = createTestRecord(); RestRequest request = new RestRequest(); request.requestUri = 'https://yourInstance.salesforce.com/services/apexrest/Cases/' + recordId; request.httpMethod = 'PATCH'; request.addHeader('Content-Type', 'application/json'); request.requestBody = Blob.valueOf('{"status": "Working"}'); RestContext.request = request; ID thisCaseId = CaseManager.updateCaseFields(); System.assert(thisCaseId != null); Case thisCase = [SELECT Id,Status FROM Case WHERE Id=:thisCaseId]; System.assert(thisCase != null); System.assertEquals(thisCase.Status, 'Working'); } // 测试工具类 static Id createTestRecord() { Case caseTest = new Case( Subject='Test record', Status='New', Origin='Phone', Priority='Medium'); insert caseTest; return caseTest.Id; } }
参考数据: