隨着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": "https://xxxx0x-xx-ed.my.salesforce.com", "id": "https://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,
- 查看是否配置正確的賬號/密碼, 最好從web 登錄一下試試看;
- 檢查請求的Url 格式是否正確,web直接輸入查看是否可達;
- 查看請求的參數是否都滿足條件,或者請示方式是否對;
Q2, invalid_session_id
- 查看access_token 是否超期,
- 請求的目標業務URL service 地址是否正確;
- 請求方式是否再業務 代碼中定義了比如 Get Post, Put.
Q3 目標業務地址不可達
- 查看profile中對應的app user 是否設置了IP 登錄限制;
- 檢查請求URL大小寫問題, 是否與RESTResource URL代碼中定義的是否相同,
Q4, 查詢結果顯示無權限
- 查看設置的profile 是否開放了代碼中查詢用到的apex class, object ,
- 查詢數據object的相應字段是否授權,對於object read/edit/delete 權限是否都已經放開相應權限;