Salesforce 中APEX REST API 怎麼調, 怎麼用?

隨著REST API 和微服務的興起, 越來越多的應用都需要用到介面Oauth2.0授權和Callout調用,今天就來和大家講一講如何在其他應用中恰當地調用Salesforce提供的Apex Rest API 服務!

 

 

基本組件:

–          Connected App

–          Integration Profile

–          Connect user with integration profile

–          APEX REST Class

 

組件用途:

Connected App:

主要可以生成專屬的client id 和client secret, 為兩串無規律字元,且一般生成以後不會發生變化,分享給消費者方調用時需要配置,有些鑒權方式下必須要具備這倆個參數時才可以成功調用。

 同時需要配置相應的授權範疇(哪些可以被訪問),以及IP限制,refresh token 的有效期 等等;

 

 

  授權機制可以根據需要進行設置和編輯, 例如, 設置不同的訪問授權範疇以及IP限制,refresh token 的有效期之類的。

 

 

 

Api Integration profile:

主要可以設置,該profile 只能授權訪問指定的connected app, 以及該連接需要的apex class(暴露具體業務api 的apex class), object(具體查詢獲取的數據對象),的許可權之類的,以及對應object 可訪問的欄位之類的,

這樣可以做到最大程度地限制該連接用戶所請求的內容在授權的範圍之內, 從而能有效地防止介面數據被濫用或者許可權被開放過大;

 

 

 

 

  

創建一個用戶來作為登錄連接connected app 的user.

 該user 必須設置為指定創建的profile.

 

 

 

編碼實現

根據需要創建相應的 apex class, 頭部必須標註:

@RestResource(urlMapping='/api/v1.0/salary/*')

global with sharing class TSP_SalaryRestApi {

      //… …

       }

 頭部的@RestResource 相對應的Url Mapping 為必須設置的項; 否則無法正確映射請求路徑;

 

Get 方法:

    @HttpGet

    global static void getSalaryByEmployeeIdApi()
    {
              //根據需要調用內部私有方法,獲取相應的業務數據並返回… …
    }

需要在頭部標註 @HttpGet

 

Post 方法:

  @HttpPost
    global static void getSalaryListApi()
  {
      //根據需要調用內部私有方法,獲取相應的業務數據並返回… …
  }

需要在頭部標註 @HttpPost

 

如何獲取請求參數:

RestResponse res = RestContext.response;
        res.addHeader('Content-Type', 'application/json');// 添加返回格式限制為json 
        RestRequest req = RestContext.request;// 獲取request body 中的請求參數

        TSP_GetSalaryReqVO reqBody = (TSP_GetSalaryReqVO)JSON.deserialize(req.requestBody.toString(),  TSP_GetSalaryReqVO.class);
     //
TSP_GetSalaryReqVO 為自定義的請求參數數據模型,可以進行json 泛解析,最好加上異常處理防止參數異常導致解析錯誤
     System.debug('this is the request body:>>>>> ' + reqBody); 
     List
<Salary__c> resList = getSalaryList(reqBody); // 通過內部方法獲取數據結果

 

建議通過 RestRequest 獲取requestBody 然後通過反解析將獲取的json 解析成相應的模型,通過requestModel 獲取需要的請求參數;

 

如何處理返回結果:

大家先看看這個方法? 覺得有沒有什麼問題呢?

   @HttpPost
    global static string getSalaryListApiStringReturn(){
        RestResponse res = RestContext.response;
        res.addHeader('Content-Type', 'application/json'); 

        RestRequest req = RestContext.request; 

        TSP_GetSalaryReqVO reqBody = (TSP_GetSalaryReqVO)JSON.deserialize(req.requestBody.toString(),  TSP_GetSalaryReqVO.class);

        System.debug('this is the request body:>>>>> ' + reqBody);

        List<Salary__c> resList = getSalaryList(reqBody); 

        if(resList != null){

            return JSON.serialize(resList); // 直接進行json 序列化,並返回序列化後的string

        }else {

            return '{"error_message":"No data found","error_code":"-1"}';  // 返回自定義的異常錯誤結果string

        } 
   }

很明顯, 返回結果為string 類型的, 雖然進行了json 序列化,但responseBody返回的本質內容還是 string,

返回結果如下:

"[{\"attributes\":{\"type\":\"Salary__c\",\"url\":\"/services/data/v50.0/sobjects/Salary__c/a007F00000BOCRDQA5\"},\"Id\":\"a007F00000BOCRDQA5\",\"Salary_Code__c\":\"MLFSLC-0002\",\"Income_Type_Reference__c\":\"Normal_Pay\",\"SalaryAmount__c\":12000.00,\"Salary_Level__c\":\"COO\",\"Active__c\":true,\"Start_Date__c\":\"2017-01-01\",\"End_Date__c\":\"2020-12-31\"}]"

 

由於定義的返回類型為 string, 所以導致最終返回的responseBody 並非標準的json 格式, 而是json 格式的字元串轉移後的字元串輸出,簡言之, 輸出的還是string 而非json. 這可能與接收方需要的結果不同。

/*

此處有人會說,為了程式便利,可以直接return string 然後接收方可以很方便的通過返回的string進行處理,

話雖如此,但這種方法在有些時候還是會有問題,帶項目上線你自己品就知道弊端在哪兒了,調用方的消費者可能會在背後罵你埋下的禍根… 而且還有一些特殊的字元,如果編碼沒有處理好,可能會有意想不到的結果,尤其對於處理多語言的內容。.

*/

 

各位看官, 先獨自思考3-5 分鐘,想想該怎麼改? 

 

怎麼改, 怎麼改?。。。。。。

 

怎麼改, 怎麼改?。。。。。。

 

 

 

好了, 不賣關子了,那咱們來看看具體正解的返回方式應該是怎樣的呢?

上程式碼:

  

  @HttpPost

    global static void getSalaryListApi()

    {

        RestResponse res = RestContext.response;

        res.addHeader('Content-Type', 'application/json');

 

        RestRequest req = RestContext.request;

 

        TSP_GetSalaryReqVO reqBody = (TSP_GetSalaryReqVO)JSON.deserialize(req.requestBody.toString(),  TSP_GetSalaryReqVO.class);

        System.debug('this is the request body:>>>>> ' + reqBody);

        List<Salary__c> resList = getSalaryList(reqBody);

 

        if(resList != null){

            res.responseBody = Blob.valueOf(JSON.serialize(resList)); 

        }else {

            res.responseBody = Blob.valueOf('{"error_message":"No data found","error_code":"-1"}'); 

        }  

    }

 

可能這乍一看, 沒啥區別啊?  你品, 你再細品。。。。

 

首先要將獲取的request body 發序列化,轉換為指定的 Model , 並獲取相應的請求參數,

拿請求參數做程式處理,或者數據查詢,經過業務邏輯和請求參數後,獲取查詢結果的數據並返回數據,然後將獲取的數據結果 json 序列化,並且用 Blob.Valueof(json string)  填充到responseBody 中,

這樣實際上所有的result 都被存進了 responseBody 中了, 你會發現根本沒有了return? 

 

原來定義的方法返回結果 也是void 類型??

WHY? 

 

這裡沒有直接return string 之說了,因為定義返回類型的時候, 用的就是 void 而非string, 這就是這兩種方法最大的區別;

實質上,時將返回結果已經存進了responseBody 裡面了,而沒有返回方法的結果。 

可以看一下返回結果:

 

[

    {

        "attributes": {

            "type": "Salary__c",

            "url": "/services/data/v50.0/sobjects/Salary__c/a007F00000BOCR8QAP"

        },

        "Id": "a007F00000BOCR8QAP",

        "Salary_Code__c": "MLFSLC-0001",

        "Income_Type_Reference__c": "Normal_Pay",

        "SalaryAmount__c": 5000,

        "Salary_Level__c": "Normal_Staff",

        "Active__c": true,

        "Start_Date__c": "2017-01-01",

        "End_Date__c": "2020-12-31"

    },

    {

        "attributes": {

            "type": "Salary__c",

            "url": "/services/data/v50.0/sobjects/Salary__c/a007F00000GZuP9QAL"

        },

        "Id": "a007F00000GZuP9QAL",

        "Salary_Code__c": "MLFSLC-0003",

        "Income_Type_Reference__c": "Bonus_Project",

        "SalaryAmount__c": 888.08,

        "Salary_Level__c": "Normal_Staff",

        "Active__c": true,

        "Start_Date__c": "2018-02-09",

        "End_Date__c": "2018-10-31"

    }

]

  

 

Or return as error:

{

    "error_message": "No data found",

    "error_code": "-1"

}

 

都是標準的json 格式 內容,而非string。

 

 

 

 Token 獲取:

另外, 再說一下需要請求的token 以及業務Api服務的參數,  :

 

請求的連接格式基本比較固定,除了域名有差異之外, 後面部分都是一樣的,可以直接套用,

這裡說一下 grant_type 這個參數, 這裡用的是password, 也有其他的方式,大家可以參考一下 Oauth2.0 的授權方式。

 

                                
{

    "access_token": "00D7F0000048IGHXXXXXXXXXXXXXXXXXX_OInU18cLuQ8BXxXXxxXxxbiifPASqZeheIm6KqluXXXXXXXXX_2fGlXXXX",

    "instance_url": "//xxxx0x-xx-ed.my.salesforce.com",

    "id": "//login.salesforce.com/id/00D7F00000X0x0x0XX0x/0057F00000X0X0X0X0",

    "token_type": "Bearer",

    "issued_at": "16086204000000",

    "signature": "xxxxFZl+h9Tzxxxxcm0dphNzrxmmxxxxxx/4nSgw="

}

  

然後使用獲取的access_token, 來訪問業務API;

  

 

requestBody 中的請求參數按照具體業務定義來填充即可。

 

{
   "rowNumber": 100,
     "incomeType":"",
     "isActive":true,
     "salaryType":""    
}

 

有了這些, 就可以大功告成了。

 

 

 

 

 

 

 

Tips :常見問題:

Q1, 請求token 返回400/401,

  1. 查看是否配置正確的帳號/密碼, 最好從web 登錄一下試試看;
  2. 檢查請求的Url 格式是否正確,web直接輸入查看是否可達;
  3. 查看請求的參數是否都滿足條件,或者請示方式是否對;

 

Q2, invalid_session_id

  1. 查看access_token 是否超期,
  2. 請求的目標業務URL service 地址是否正確;
  3. 請求方式是否再業務 程式碼中定義了比如 Get  Post, Put.

 

Q3  目標業務地址不可達

  1. 查看profile中對應的app user 是否設置了IP 登錄限制;
  2. 檢查請求URL大小寫問題, 是否與RESTResource URL程式碼中定義的是否相同,

 

Q4, 查詢結果顯示無許可權

  1. 查看設置的profile 是否開放了程式碼中查詢用到的apex class, object ,
  2. 查詢數據object的相應欄位是否授權,對於object read/edit/delete 許可權是否都已經放開相應許可權;