【Knowledge】Apex callout 与外部service的统合
【Knowledge】使用 Apex REST 服务统合外部service
- 概要
- web service 和 http callout 的区别
- http callout 实装
- 外部 endpoint 的承认
- callout的示意图
- get 数据测试
- post 数据测试
- 合并上面两者到一个类中
- 代码测试方法
- 方法一 使用 StaticResourceCalloutMock
- 方法二 使用 HttpCalloutMock
- 公开Apex 类为Web service
- 以REST service形式公开
- 以SOAP service形式公开
- APEX REST 的示例
- 示例代码
- 测试
- 测试方法
- APEX REST 认证机制
- REST Explorer的测试
- cURL的测试
- Apex REST 测试类的生成
概要
web service 和 http callout 的区别
通过Apex callout实现与外部service的接续,主要有2种类型
- 以 WSDL 为 base 的 callout,使用 xml 形式接续外部soap web service。
- 使用 REST 和 JSON 形式的 http callout。
区别
- WSDL 的 callout 主要适用于 SOAP Web service。
- Http 的 callout 使用的是 http service,既可以是 SOAP 也可以是 REST。
应用场景
- 目前主流使用 REST,工作在应用层,代码少,JSON格式易读。
- SOAP工作在网络层,主要是企业使用,主要为了统合原有application。
http callout 实装
外部 endpoint 的承认
连接外部service时,需要在sf系统首先承认外部endpoint
- 設定 ⇒ クイック検索 ⇒ リモートサイトの設定
- リモートサイトのURL 設定
- 例① https://th-apex-http-callout.herokuapp.com
- 例② https://th-apex-soap-service.herokuapp.com
callout的示意图
get 数据测试
在开发者console的匿名窗口中,输入下列代码测试
Http http = new Http();
HttpRequest request = new HttpRequest();
request.setEndpoint('https://th-apex-http-callout.herokuapp.com/animals');
request.setMethod('GET');
HttpResponse response = http.send(request);
// If the request is successful, parse the JSON response.
if (response.getStatusCode() == 200) {// Deserialize the JSON string into collections of primitive data types.Map<String, Object> results = (Map<String, Object>) JSON.deserializeUntyped(response.getBody());// Cast the values in the 'animals' key as a listList<Object> animals = (List<Object>) results.get('animals');System.debug('Received the following animals:');for (Object animal: animals) {System.debug(animal);}
}
post 数据测试
开发者console的匿名窗口中,输入下列代码测试
Http http = new Http();
HttpRequest request = new HttpRequest();
request.setEndpoint('https://th-apex-http-callout.herokuapp.com/animals');
request.setMethod('POST');
request.setHeader('Content-Type', 'application/json;charset=UTF-8');
// Set the body as a JSON object
request.setBody('{"name":"mighty moose"}');
HttpResponse response = http.send(request);
// Parse the JSON response
if (response.getStatusCode() != 201) {System.debug('The status code returned was not expected: ' +response.getStatusCode() + ' ' + response.getStatus());
} else {System.debug(response.getBody());
}
合并上面两者到一个类中
public class AnimalsCallouts {public static HttpResponse makeGetCallout() {Http http = new Http();HttpRequest request = new HttpRequest();request.setEndpoint('https://th-apex-http-callout.herokuapp.com/animals');request.setMethod('GET');HttpResponse response = http.send(request);// If the request is successful, parse the JSON response.if (response.getStatusCode() == 200) {// Deserializes the JSON string into collections of primitive data types.Map<String, Object> results = (Map<String, Object>) JSON.deserializeUntyped(response.getBody());// Cast the values in the 'animals' key as a listList<Object> animals = (List<Object>) results.get('animals');System.debug('Received the following animals:');for (Object animal: animals) {System.debug(animal);}}return response;}public static HttpResponse makePostCallout() {Http http = new Http();HttpRequest request = new HttpRequest();request.setEndpoint('https://th-apex-http-callout.herokuapp.com/animals');request.setMethod('POST');request.setHeader('Content-Type', 'application/json;charset=UTF-8');request.setBody('{"name":"mighty moose"}');HttpResponse response = http.send(request);// Parse the JSON responseif (response.getStatusCode() != 201) {System.debug('The status code returned was not expected: ' +response.getStatusCode() + ' ' + response.getStatus());} else {System.debug(response.getBody());}return response;}
}
代码测试方法
由于apex的测试类不支持callout,所有需要使用 模拟的callout来测试
方法一 使用 StaticResourceCalloutMock
开发者console中,新建 StaticResource
json格式的contents定义
{"animals": ["pesky porcupine", "hungry hippo", "squeaky squirrel"]}
新建测试类
@isTest
private class AnimalsCalloutsTest {@isTest static void testGetCallout() {// Create the mock response based on a static resourceStaticResourceCalloutMock mock = new StaticResourceCalloutMock();mock.setStaticResource('GetAnimalResource');mock.setStatusCode(200);mock.setHeader('Content-Type', 'application/json;charset=UTF-8');// Associate the callout with a mock responseTest.setMock(HttpCalloutMock.class, mock);// Call method to testHttpResponse result = AnimalsCallouts.makeGetCallout();// Verify mock response is not nullSystem.assertNotEquals(null,result,'The callout returned a null response.');// Verify status codeSystem.assertEquals(200,result.getStatusCode(),'The status code is not 200.');// Verify content type System.assertEquals('application/json;charset=UTF-8',result.getHeader('Content-Type'),'The content type value is not expected.'); // Verify the array contains 3 items Map<String, Object> results = (Map<String, Object>) JSON.deserializeUntyped(result.getBody());List<Object> animals = (List<Object>) results.get('animals');System.assertEquals(3, animals.size(),'The array should only contain 3 items.'); }
}
方法二 使用 HttpCalloutMock
需要实装 HttpCalloutMock 接口类,
@isTest
global class AnimalsHttpCalloutMock implements HttpCalloutMock {// Implement this interface methodglobal HTTPResponse respond(HTTPRequest request) {// Create a fake responseHttpResponse response = new HttpResponse();response.setHeader('Content-Type', 'application/json');response.setBody('{"animals": ["majestic badger", "fluffy bunny", "scary bear", "chicken", "mighty moose"]}');response.setStatusCode(200);return response; }
}
实装测试类
@isTest static void testPostCallout() {// Set mock callout class Test.setMock(HttpCalloutMock.class, new AnimalsHttpCalloutMock()); // This causes a fake response to be sent// from the class that implements HttpCalloutMock. HttpResponse response = AnimalsCallouts.makePostCallout();// Verify that the response received contains fake valuesString contentType = response.getHeader('Content-Type');System.assert(contentType == 'application/json');String actualValue = response.getBody();System.debug(response.getBody());String expectedValue = '{"animals": ["majestic badger", "fluffy bunny", "scary bear", "chicken", "mighty moose"]}';System.assertEquals(actualValue, expectedValue);System.assertEquals(200, response.getStatusCode());
}
公开Apex 类为Web service
以REST service形式公开
apex 类 声明为 global
apex 方法 声明为 global static
付加各种宣言
- @RestResource
- @HttpGet
- @HttpPost
- @HttpDelete
- @HttpPut
- @HttpPatch
endpoint 是;https://yourInstance.salesforce.com/services/apexrest/
例:
@RestResource(urlMapping='/Account/*')
global with sharing class MyRestResource {@HttpGetglobal static Account getRecord() {// Add your code}
}
以SOAP service形式公开
- apex 类 声明为 global
- apex 方法 声明为 webservice static
例:
global with sharing class MySOAPWebService {webservice static Account getRecord(String id) {// Add your code}
}
注意: 一般情况下,是在SF中生成 WSDL 文件连携给第三方,进行实装
APEX REST 的示例
示例代码
@RestResource(urlMapping='/Cases/*')
global with sharing class CaseManager {@HttpGetglobal static Case getCaseById() {RestRequest request = RestContext.request;// grab the caseId from the end of the URLString caseId = request.requestURI.substring(request.requestURI.lastIndexOf('/')+1);Case result = [SELECT CaseNumber,Subject,Status,Origin,PriorityFROM CaseWHERE Id = :caseId];return result;}@HttpPostglobal 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;} @HttpDeleteglobal 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;} @HttpPutglobal 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);// Match case by Id, if present.// Otherwise, create new case.upsert thisCase;// Return the case ID.return thisCase.Id;}@HttpPatchglobal 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];// Deserialize the JSON string into name-value pairsMap<String, Object> params = (Map<String, Object>)JSON.deserializeUntyped(request.requestbody.tostring());// Iterate through each parameter field and valuefor(String fieldName : params.keySet()) {// Set the field and value on the Case sObjectthisCase.put(fieldName, params.get(fieldName));}update thisCase;return thisCase.Id;}
}
测试
测试方法
- 独自的 API 客户端(Postman等)
- cURL 命令
- PHP 的 cURL库等
- WorkBench(REST Explorer)
- XXXX等其他方法
APEX REST 认证机制
- OAuth 2.0
- session认证
REST Explorer的测试
site;WorkBench
方法;Post
相对URL;/services/apexrest/Cases/
Body;
{"subject" : "Bigfoot Sighting!","status" : "New","origin" : "Phone","priority" : "Low"
}
cURL的测试
【前提条件】事先作成接续app,生成 client_id 和 client_secret
【步骤】如何通过Postman客户端测试Salesforce的REST API(用户名密码的接续app的方式
认证
curl -v https://login.salesforce.com/services/oauth2/token -d "grant_type=password" -d "client_id=<your_consumer_key>" -d "client_secret=<your_consumer_secret>" -d "username=<your_username>" -d "password=<your_password_and_security_token>" -H 'X-PrettyPrint:1'
测试
curl https://yourInstance.salesforce.com/services/apexrest/Cases/<Record_ID> -H 'Authorization: Bearer <your_session_id>' -H 'X-PrettyPrint:1'
Apex REST 测试类的生成
@IsTest
private class CaseManagerTest {@isTest static void testGetCaseById() {Id recordId = createTestRecord();// Set up a test requestRestRequest request = new RestRequest();request.requestUri ='https://yourInstance.salesforce.com/services/apexrest/Cases/'+ recordId;request.httpMethod = 'GET';RestContext.request = request;// Call the method to testCase thisCase = CaseManager.getCaseById();// Verify resultsSystem.assert(thisCase != null);System.assertEquals('Test record', thisCase.Subject);}@isTest static void testCreateCase() {// Call the method to testID thisCaseId = CaseManager.createCase('Ferocious chipmunk', 'New', 'Phone', 'Low');// Verify resultsSystem.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();// Set up a test requestRestRequest request = new RestRequest();request.requestUri ='https://yourInstance.salesforce.com/services/apexrest/Cases/'+ recordId;request.httpMethod = 'GET';RestContext.request = request;// Call the method to testCaseManager.deleteCase();// Verify record is deletedList<Case> cases = [SELECT Id FROM Case WHERE Id=:recordId];System.assert(cases.size() == 0);}@isTest static void testUpsertCase() {// 1. Insert new recordID case1Id = CaseManager.upsertCase('Ferocious chipmunk', 'New', 'Phone', 'Low', null);// Verify new record was createdSystem.assert(Case1Id != null);Case case1 = [SELECT Id,Subject FROM Case WHERE Id=:case1Id];System.assert(case1 != null);System.assertEquals(case1.Subject, 'Ferocious chipmunk');// 2. Update status of existing record to WorkingID case2Id = CaseManager.upsertCase('Ferocious chipmunk', 'Working', 'Phone', 'Low', case1Id);// Verify record was updatedSystem.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;// Update status of existing record to WorkingID thisCaseId = CaseManager.updateCaseFields();// Verify record was updatedSystem.assert(thisCaseId != null);Case thisCase = [SELECT Id,Status FROM Case WHERE Id=:thisCaseId];System.assert(thisCase != null);System.assertEquals(thisCase.Status, 'Working');} // Helper methodstatic Id createTestRecord() {// Create test recordCase caseTest = new Case(Subject='Test record',Status='New',Origin='Phone',Priority='Medium');insert caseTest;return caseTest.Id;}
}
【Knowledge】Apex callout 与外部service的统合相关推荐
- 在Salesforce中调用外部系统所提供的的Web Service
这里需要提供外部service所对应的WSDL文件(Salesforce只支持从本地上传),并且提供的WSDL文件有如下两点要求: 1):wsdl 文件只能有一个binding,Salesforce是 ...
- Android10以上之APEX格式介绍
Android Pony EXpress (APEX) 是 Android 10 中引入的一种容器格式,用于较低级别系统模块的安装流程中.此格式可帮助更新不适用于标准 Android 应用模型的系统组 ...
- Apex Integration Overview
Salesforce具有两种不同的桌面用户界面:Lightning Experience和Salesforce Classic 该模块是为Salesforce Classic设计的 Make Call ...
- [论文阅读笔记17]A Survey on Knowledge Graph-Based Recommender Systems
一,题目 TKDE 2020 A Survey on Knowledge Graph-Based Recommender Systems 综述:基于知识图谱的推荐系统 In IEEE Transact ...
- Incorporating External Knowledge through Pre-training for Natural Language to Code Generation论文笔记
Abstract 开放域代码生成是想要根据自然语言(NL)生成通用编程语言的代码(例如Python).因为我们发现开发人员在编写代码时通常会在网络上查找,作者探索了将两种外部知识整合到 NL-to-c ...
- 【干货】CRM大牛告诉你,Salesforce到底是个什么鬼?
本期主题 | Salesforce到底是个什么鬼 分享嘉宾 | 裘思博 Celnet雨花石创始人&合伙人 文字整理 | 莜筱 入群请联系管理员 37℃ 微信号:erhuoyimei 裘思博 本 ...
- 多租户saas 架构_[译/注] Force.com 多租户互联网应用开发平台的设计
原文地址 http://cloud.pubs.dbs.uni-leipzig.de/sites/cloud.pubs.dbs.uni-leipzig.de/files/p889-weissman-1 ...
- Salesforce Integration 概览(三) Remote Process Invocation—Fire and Forget(远程进程调用-发后即弃)
本篇参考:https://resources.docs.salesforce.com/sfdc/pdf/integration_patterns_and_practices.pdf 我们在上一篇讲了远 ...
- 【论文】基于特定实体的文本情感分类总结(PART III)
0. 写在前面 继续之前的系列,记录一些关于ABSA问题的paper 1. Targeted Aspect-Based Sentiment Analysis via Embedding Commons ...
最新文章
- c语言swatch的用法返回,Linux swatch系统监控程序命令详解
- Django之初步实现登录功能,APP及ORM
- Javascript高级调试——console.table()
- 细学PHP 08 数组-2
- 方立勋_30天掌握JavaWeb_JDBC、SQL防注入(一)
- jpa mysql乐观锁_【快学springboot】8.JPA乐观锁OptimisticLocking
- Android中libs目录下armeabi和armeabi-v7a的区别
- mysql 主从关系切换
- python手写一个迭代器_搞清楚 Python 的迭代器、可迭代对象、生成器
- vmware硬件兼容官方查询地址
- windows系统查看局域网内所有已使用的IP
- 电脑端登陆OneNote时提示0x8019001错误
- 《微微一笑很倾城》中肖奈大神说的平方根倒数速算法是什么鬼?三十分钟理解!
- 如何通过几何画板学这些定理
- 快手,抖音,美拍打造个人IP精准引流!
- 2022年7月22日,记录我的第一篇博客
- js点击箭头旋转的实现
- 问卷调查抽奖系统开发
- plotly绘制简单图形<7>--用plotly画图参数设置
- 微信支付签约委托代扣文档 - 月付会员或者定期支付服务 - 公众号纯签约
热门文章
- 如何搭建属于自己的阿里云服务器
- 虚幻引擎UE4背包系统(如何制作可拖动(Drag and Drop)的背包(Scrollbox))
- android接口调试工具
- 【音乐检索】基于matlab音乐检索系统【含Matlab源码 435期】
- 猿编程python怎么样_猿编程怎么练习编程 让你提前熟悉代码
- 基于javaweb的在线点餐+外卖配送系统
- Window拷贝文件到Ubuntu虚拟机
- Triangle程序编译
- minigui源码学习
- 文献管理软件//Zotero的常用插件——Sci-hub/shortdoi批量下载、Zotfile重命名PDF文件及ZoteroQuickLook快速预览(二)