【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
  • 使用 RESTJSON 形式的 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的统合相关推荐

  1. 在Salesforce中调用外部系统所提供的的Web Service

    这里需要提供外部service所对应的WSDL文件(Salesforce只支持从本地上传),并且提供的WSDL文件有如下两点要求: 1):wsdl 文件只能有一个binding,Salesforce是 ...

  2. Android10以上之APEX格式介绍

    Android Pony EXpress (APEX) 是 Android 10 中引入的一种容器格式,用于较低级别系统模块的安装流程中.此格式可帮助更新不适用于标准 Android 应用模型的系统组 ...

  3. Apex Integration Overview

    Salesforce具有两种不同的桌面用户界面:Lightning Experience和Salesforce Classic 该模块是为Salesforce Classic设计的 Make Call ...

  4. [论文阅读笔记17]A Survey on Knowledge Graph-Based Recommender Systems

    一,题目 TKDE 2020 A Survey on Knowledge Graph-Based Recommender Systems 综述:基于知识图谱的推荐系统 In IEEE Transact ...

  5. Incorporating External Knowledge through Pre-training for Natural Language to Code Generation论文笔记

    Abstract 开放域代码生成是想要根据自然语言(NL)生成通用编程语言的代码(例如Python).因为我们发现开发人员在编写代码时通常会在网络上查找,作者探索了将两种外部知识整合到 NL-to-c ...

  6. 【干货】CRM大牛告诉你,Salesforce到底是个什么鬼?

    本期主题 | Salesforce到底是个什么鬼 分享嘉宾 | 裘思博 Celnet雨花石创始人&合伙人 文字整理 | 莜筱 入群请联系管理员 37℃ 微信号:erhuoyimei 裘思博 本 ...

  7. 多租户saas 架构_[译/注] Force.com 多租户互联网应用开发平台的设计

    原文地址  http://cloud.pubs.dbs.uni-leipzig.de/sites/cloud.pubs.dbs.uni-leipzig.de/files/p889-weissman-1 ...

  8. Salesforce Integration 概览(三) Remote Process Invocation—Fire and Forget(远程进程调用-发后即弃)

    本篇参考:https://resources.docs.salesforce.com/sfdc/pdf/integration_patterns_and_practices.pdf 我们在上一篇讲了远 ...

  9. 【论文】基于特定实体的文本情感分类总结(PART III)

    0. 写在前面 继续之前的系列,记录一些关于ABSA问题的paper 1. Targeted Aspect-Based Sentiment Analysis via Embedding Commons ...

最新文章

  1. c语言swatch的用法返回,Linux swatch系统监控程序命令详解
  2. Django之初步实现登录功能,APP及ORM
  3. Javascript高级调试——console.table()
  4. 细学PHP 08 数组-2
  5. 方立勋_30天掌握JavaWeb_JDBC、SQL防注入(一)
  6. jpa mysql乐观锁_【快学springboot】8.JPA乐观锁OptimisticLocking
  7. Android中libs目录下armeabi和armeabi-v7a的区别
  8. mysql 主从关系切换
  9. python手写一个迭代器_搞清楚 Python 的迭代器、可迭代对象、生成器
  10. vmware硬件兼容官方查询地址
  11. windows系统查看局域网内所有已使用的IP
  12. 电脑端登陆OneNote时提示0x8019001错误
  13. 《微微一笑很倾城》中肖奈大神说的平方根倒数速算法是什么鬼?三十分钟理解!
  14. 如何通过几何画板学这些定理
  15. 快手,抖音,美拍打造个人IP精准引流!
  16. 2022年7月22日,记录我的第一篇博客
  17. js点击箭头旋转的实现
  18. 问卷调查抽奖系统开发
  19. plotly绘制简单图形<7>--用plotly画图参数设置
  20. 微信支付签约委托代扣文档 - 月付会员或者定期支付服务 - 公众号纯签约

热门文章

  1. 如何搭建属于自己的阿里云服务器
  2. 虚幻引擎UE4背包系统(如何制作可拖动(Drag and Drop)的背包(Scrollbox))
  3. android接口调试工具
  4. 【音乐检索】基于matlab音乐检索系统【含Matlab源码 435期】
  5. 猿编程python怎么样_猿编程怎么练习编程 让你提前熟悉代码
  6. 基于javaweb的在线点餐+外卖配送系统
  7. Window拷贝文件到Ubuntu虚拟机
  8. Triangle程序编译
  9. minigui源码学习
  10. 文献管理软件//Zotero的常用插件——Sci-hub/shortdoi批量下载、Zotfile重命名PDF文件及ZoteroQuickLook快速预览(二)