【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. 今天说的是必须要熟练掌握的归并排序
  2. 扎心了!年薪100万,却还不起5000块的信用卡
  3. 刚学GDI+和.Net WinForm控件开发时做的Training Project
  4. Python学习笔记__10.4章 进程VS线程
  5. 浅谈管理数据平台的一些想法
  6. c语言中把各位上为奇数的数取出,下列给定程序中函数fun()的功能是:将长整型数中每一位上为奇数的数依次取出,构成一个新数放在冲。 - 赏学吧...
  7. springboot2 配置redis报错 redis timeout Value not a valid duration解决办法
  8. 新版opencv兼容旧版_【标准换版】关于家用和类似用途电器用外置电源适配器、充电器和内置开关电源产品认证执行新版标准的通知...
  9. JVM虚拟机-Class文件之类索引、父类索引和接口索引集合
  10. IDEA(2018版)实用快捷键整理
  11. sql server 用户'sa'登录失败(错误18456)(转载)
  12. Metasploit物联网安全渗透测试增加对硬件的支持
  13. 将VSCode添加到鼠标右键菜单
  14. 非致命战计算机病毒战属于,《信息化战争》章节
  15. Windows 8 激活信息备份还原方法与工具
  16. 流程型与离散型制造的区别【老外的分析】
  17. Java版Spring Cloud B2B2C o2o鸿鹄云商平台--概述
  18. js大屏导出图片_js将canvas保存成图片并下载
  19. 2023深圳大学计算机考研信息汇总
  20. java实现多媒体播放器_Java swing实现简单的视频多媒体播放器源码附带视频指导运行教程...

热门文章

  1. (电脑问题) win10飞行模式无法关闭和突然连接不上网络解决方法
  2. 基于Arduino的超声波悬浮
  3. 云仓一件代发模式与即时分账,会碰撞出什么火花?
  4. 锐捷服务器虚拟化技术_信息化的好拍档,锐捷助力南昌市第五医院再迎新征程...
  5. 信息安全服务资质认证实施规则
  6. 计算机加减乘除的公式,Word表格函数公式,轻松搞定加减乘除
  7. gtx660 linux驱动下载,佳能 NVIDIA GeForce GTX660 GPU 驱动程序下载-更新佳能软件(显卡)...
  8. 烤仔TVのCCW | 区块链中的 Merkle 树
  9. 计算机电子邮箱格式,英语邮箱格式,电子邮件格式范文!
  10. html中怎么让盒子模型居中,通过box盒子模型给元素内容设置居中