Fizz Gateway網關腳本功能的高級用法

 

建立服務

#建立聚合接口

 

#配置輸入

  • 配置輸入的定義包括3部分:請求頭、請求體和Query參數
  • 基於JSON Schema規範
  • 自帶校驗規則
  • 支持自定義腳本實現複雜的邏輯校驗

JSON Schema規範,詳見:javascript

http://json-schema.org/specification.html(opens new window)html

http://json-schema.org/understanding-json-schema/(opens new window)java

#配置校驗結果

  • 校驗不經過時,Fizz會把校驗失敗的緣由(如:訂單ID不能爲空)放到上下文的validateMsg字段裏
  • 能夠自定義返回給調用方的報文格式,如 msgCode, message
  • 支持自定義響應頭
  • 支持自定義腳本處理校驗結果

#配置步驟

#配置步驟的基礎信息

#配置步驟的接口入出參

#步驟說明

  • 一個聚合接口可包含多個步驟
  • 一個步驟可包含多個請求(即調用多個接口)
  • 步驟間是串聯順序執行
  • 一個步驟內的多個請求並行執行

#數據轉換

支持配置固定值,引用值和腳本json

#固定值api

#引用值數組

#腳本網絡

#星號 *數據結構

星號通配符能夠接收一個返回對象類型的引用值,返回對象裏的字段會合併到目標對象裏運維

樣例:userInfo = {"userName": "Fizz", "userID": 1234}ide

#優先級與覆蓋順序

固定值 < 引用值 < 腳本 < 星號*

當一個字段配置了多種類型的值時按以上順序覆蓋,星號優先級最高

#引用值規範

# 獲取入參請求頭aaa的值
input.request.headers.aaa

# 獲取入參請求體bbb字段的值
input.request.body.bbb

# 獲取入參URL Query參數fff字段的值
input.request.params.fff

# 獲取步驟1裏request1的請求頭ccc的值
step1.request1.request.headers.ccc

# 獲取步驟1裏request1的響應體ddd的值
step1.request1.response.body.ddd

# 獲取步驟1結果裏eee的值
step1.result.eee
  • 支持單值引用,如:string,int等
  • 支持對象類型的引用

input: 表示調用方的輸入數據,如H5頁面提交上來的參數

stepN.requestN: 表示步驟N裏調用接口N的相關參數

stepN.result: 表示步驟N的轉換結果

#Fallback與預處理條件

Fallback:

當調用接口發生異常(如超時、網絡或系統異常)可配置fallback方案:

  • Stop: 終止請求並當即返回
  • Continue: 繼續後續的操做,且要設置默認的fallback json

預處理: 根據條件判斷是否要調用接口,腳本返回true時才調用接口

#配置步驟結果處理

  • 支持對步驟裏調用的每個接口的返回結果作數據轉換,若是配置數據轉換規則原樣返回並存儲到上下文裏供後續使用

  • 支持對步驟裏調用的一個或多個接口的返回結果作處理,並把處理完的結果存儲到上下文裏供後續使用,不配置則不處理

#配置輸出

配置返回給調用方的結果

  • 支持配置響應頭
  • 支持配置響應體
  • 支持自定腳本處理複雜的業務邏輯

#腳本

#簡介

Fizz 支持經過自定義腳本進行服務編排:

  • 在 配置輸入 中 經過 腳本校驗 輸入內容;
  • 在 配置輸出 中 經過 腳本 定義 輸出內容,也能夠細化到 某個輸出字段的 腳本處理;
  • 在 配置步驟 中 經過 腳本 定義 配置入參、配置響應 的返回內容;
  • 在 結果校驗 中 經過 腳本 徹底自定義校驗邏輯,校驗輸出內容。

Fizz支持 javascript 和 groovy 兩種腳本語言,方便開發人員靈活的選擇本身熟悉的語言進行服務編排。

其中,

若是使用javascript ,能夠 經過 common 對象獲取一系列工具函數,方便進行邏輯處理;

而在 groovy 中,全部的工具函數都掛載在 context下,你能夠很方便的使用它們。

#腳本編寫格式

#javascript

編寫JavaScript腳本時,須要按照如下固定格式進行編寫。 function name dyFunc 不可修改。

返回值只能是基本類型,如 string/number/booleanobject/array類型的必須經過JSON.stringify 序列化後返回。

Fizz 是經過調用 js引擎執行javascript腳本,而後捕獲執行結果,只能獲取基本的數據類型。

object/array類型捕獲前,會調用原型上的toString 方法,獲得的結果爲 [object type] 的字符串,並不是預期的數據,因此必須經過JSON.stringify()序列化爲jsonString後再返回。

請勿在js 中使用document等api

function dyFunc(paramsJsonStr) {
    var ctx = JSON.parse(paramsJsonStr)['context'];
    // do something...
    
    // return string/number/boolean/jsonString
    return JSON.stringify({});
}

#groovy

編寫groovy腳本時,支持返回groovy支持的任何數據類型。

import com.alibaba.fastjson.JSON
import com.alibaba.fastjson.JSONArray
import com.alibaba.fastjson.JSONObject

// do something...

// return any result
return result

#配置輸入——腳本校驗

在 編輯服務編排接口時,容許在 配置輸入 中,對輸入的數據進行自定義的腳本校驗,校驗 請求頭、請求體、query參數是否經過驗證。

返回的驗證結果,必須是一個 序列化後的 數組,且:

  • 若是校驗經過,則返回一個空數組;
  • 若是校驗不經過,將校驗不經過的錯誤信息,push到數組中後返回。

參考示例:

javascript

function dyFunc(paramsJsonStr) {
    var ctx = JSON.parse(paramsJsonStr)['context'];
    // 獲取聚合接口用戶輸入的數據
    // 獲取請求頭
    var token = common.getInputReqHeader(ctx, 'token');
    // 獲取請求參數
    var account = common.getInputReqParam(ctx, 'account');
    var validate = [];
    
    // 校驗請求參數
    if (!token) {
        // 將校驗不經過的錯誤信息push到validate中
        validate.push('缺乏 token');
    }
    
    if (!account) {
        validate.push('缺乏 account');
    }
    
    // 將 數組 validate 序列化後返回
    // 空數組表示校驗經過,非空表示校驗不經過
    return JSON.stringify(validate);
}

groovy

// 獲取聚合接口用戶輸入的數據
// 獲取請求頭
String token = context.getInputReqHeader('token')
// 獲取請求參數
String account = context.getInputReqAttr('params').get('account')
List<String> validate = new LinkedList<>()

// 校驗請求參數
if (token == null || token.trim().isEmpty()) {
    // 將校驗不經過的錯誤信息add到validate中
    validate.add('缺乏 token')
}

if (account == null || account.trim().isEmpty()) {
    validate.add('缺乏 account')
}

// 空數組表示校驗經過,非空表示校驗不經過
return validate

#配置輸出

#輸出 完整response

在 編輯服務編排接口時,容許在 配置輸出 中,自定義輸出結果。

對於返回結果,建議以 { 狀態碼, 請求信息,請求結果 } 的數據結構進行返回,示例以下:

{
    "msgCode": 0, // 狀態碼
    "message": "success", // 請求信息
    "data": { // 請求結果
        "count": 1
    }
}

當腳本內部執行時,檢查到發生異常,須要終止請求,可在 響應的結果中, 添加_stopAndResponse: true 用於中斷,直接將當前結果返回到用戶端。

{
    "msgCode": 1, // 狀態碼
    "message": "request error",
    "_stopAndResponse": true // 終止請求並返回響應結果
}

配置輸出腳本示例:

// javascript腳本函數名不能修改
function dyFunc(paramsJsonStr) {
  var context = JSON.parse(paramsJsonStr)['context'];
  var data = common.getStepRespBody(context, 'step2', 'request1', 'data');

  // do something


  // 自定義 返回結果,若是返回的Object裏含有_stopAndResponse=true字段時將會終止請求並把腳本結果響應給客戶端(主要用於有異常狀況要終止請求的場景)
  var result = { // 對於result 內的數據結構無其餘特殊要求,msgCode/message/data字段僅作示例
    // _stopAndResponse: true,
    msgCode: '0',
    message: '',
    data: data
  };
  // 返回結果爲Array或Object時要先轉爲json字符串
  return JSON.stringify(result);
}

#單個字段 輸出腳本處理

在 編輯服務編排接口時,容許在 配置輸出 中,經過腳本處理,自定義單個字段的值。

在字段配置中,選擇 腳本後,便可經過腳本 配置 單個字段的值。

這裏的腳本執行的結果只賦值給單個字段。

// javascript腳本函數名不能修改
function dyFunc(paramsJsonStr) {
  var context = JSON.parse(paramsJsonStr)['context'];
  var token = common.getStepRespBody(context, 'step2', 'request1', 'token');

  // do something
  var memberId = parseToken(token);
  return memberId;
}

#配置步驟

與 上面的 配置輸入——腳本校驗 和__配置輸出__ 相同。

#結果校驗

結果校驗指爲最終返回給用戶端的數據進行校驗。

返回的驗證結果,必須是一個 序列化後的 數組,且:

  • 若是校驗經過,則返回一個空數組;
  • 若是校驗不經過,將校驗不經過的錯誤信息,push到數組中後返回。

參考示例:

javascript

function dyFunc(paramsJsonStr) {
    var ctx = JSON.parse(paramsJsonStr)['context'];
    // 獲取聚合接口用戶輸入的數據
    // 獲取請求頭
    var token = common.getInputReqHeader(ctx, 'token');
    // 獲取請求參數
    var account = common.getInputReqParam(ctx, 'account');
    var validate = [];
    
    // 校驗請求參數
    if (!token) {
        // 將校驗不經過的錯誤信息push到validate中
        validate.push('缺乏 token');
    }
    
    if (!account) {
        validate.push('缺乏 account');
    }
    
    // 將 數組 validate 序列化後返回
    // 空數組表示校驗經過,非空表示校驗不經過
    return JSON.stringify(validate);
}

groovy

// 獲取聚合接口用戶輸入的數據
// 獲取請求頭
String token = context.getInputReqHeader('token')
// 獲取請求參數
String account = context.getInputReqAttr('params').get('account')
List<String> validate = new LinkedList<>()

// 校驗請求參數
if (token == null || token.trim().isEmpty()) {
    // 將校驗不經過的錯誤信息add到validate中
    validate.add('缺乏 token')
}

if (account == null || account.trim().isEmpty()) {
    validate.add('缺乏 account')
}

// 空數組表示校驗經過,非空表示校驗不經過
return validate

#javascript 腳本中的context

javascript腳本中的context 是僅做用域函數做用域中的,做爲 function dyFunc(paramsJsonStr){} 的第一個入參傳入。

function dyFunc(paramsJsonStr) {
    // 傳入的 paramsJsonStr 僅是一個字符串,須要經過JSON.parse進行序列化後獲取`context`
    var ctx = JSON.parse(paramsJsonStr)['context'];
    // do something...
    
}

context 數據結構描述:

interface context {
    debug: boolean; // 是否DEBUG模式
    elapsedTimes: elapsedTime[]; // 各個操做的耗時
    input: { // 客戶輸入和接口的返回結果
        request: {  // 請求
            path: string; // 請求路徑
            method: string; // 請求方法 POST/GET/PUT/DELETE/...
            headers: {
                [head: string]: any;
            }; // 請求頭
            body: {
                [field: string]: any;
            }; // 請求體
            params: {
                [param: string]: any;
            }; // 響應體
        };
        response: { // 響應
            headers: {
                [head: string]: any;
            }; // 響應頭
            body: {
                [field: string]: any;
            }; // 響應體  聚合接口的響應
        };
    };
    [stepName: string]: { // 步驟
        [requestName: string]: { // 接口
            request: {  // // 請求相關參數
                url: string; // 請求路徑
                method: string; // 請求方法 POST/GET/PUT/DELETE/...
                headers: {
                    [head: string]: any;
                }; // 請求頭
                body: {
                    [body: string]: any;
                }; // 請求體
                params: {
                    [param: string]: any;
                }; // 響應體
            };
            response: { // 響應 根據轉換規則轉換後的接口響應
                headers: {
                    [head: string]: any;
                }; // 響應頭
                body: {
                    [field: string]: any;
                }; // 響應體
            };
        }
    };
    result: string | number | boolean; // object/array 須要使用 JSON.stirngify 序列化
}

interface elapsedTime {
    [acticeName: string]: number; // 操做名稱:耗時
}

爲了方便在腳本中使用context ,咱們提供了 javascript 和 groovy 兩種腳本的工具函數。

#工具函數——javascript

  • common.getInputReq(ctx):

    獲取上下文客戶端中請求對象

    • ctx: 上下文
    function dyFunc(paramsJsonStr) {
        var ctx = JSON.parse(paramsJsonStr)['context'];
        var req = common.getInputReq(ctx);
    
        var path = req.path; // 請求路徑
        var method = req.method; // 請求方法
        var headers = req.headers; // 請求頭
        var body = req.body; // 請求體
        var params = req.params; // 請求參數
        // do something...
        // return anything string
        return '';
    }
  • common.getStepReq(ctx, stepName, requestName):

    獲取上下文步驟中請求接口的請求對象

    • ctx: 上下文
    • stepName: 配置步驟中的 step name
    • requestName :配置步驟中的 stepName 對應的 request name
    function dyFunc(paramsJsonStr) {
        var ctx = JSON.parse(paramsJsonStr)['context'];
        var req = common.getStepReq(ctx, 'step1', 'request1');
        
        var url = req.url; // 請求路徑
        var method = req.method; // 請求方法
        var headers = req.headers; // 請求頭
        var body = req.body; // 請求體
        var params = req.params; // 請求參數
        // do something...
        // return anything string
        return '';
    }
  • common.getStepResp(ctx, stepName, requestName)

    獲取上下文步驟中請求接口的響應對象

    • ctx: 上下文
    • stepName: 配置步驟中的 step name
    • requestName :配置步驟中的 stepName 對應的 request name
    function dyFunc(paramsJsonStr) {
        var ctx = JSON.parse(paramsJsonStr)['context'];
        var stepResp = common.getStepResp(ctx, 'step1', 'request1');
        // do something...
        // return anything string
        return '';
    }
  • common.getInputReqHeader(ctx, headerName)

    獲取客戶端請求頭

    • ctx: 上下文
    • headerName: 請求頭字段名 【選填】,不傳時返回全部請求頭
    function dyFunc(paramsJsonStr) {
        var ctx = JSON.parse(paramsJsonStr)['context'];
        var contentType = common.getInputReqHeader(ctx, 'content-type');
        // do something...
        // return anything string
        return '';
    }
  • common.getInputReqParam(ctx, paramName)

    獲取客戶端URL請求參數(query string)

    • ctx: 上下文
    • paramName URL參數名 【選填】,不傳時返回全部請求參數
    function dyFunc(paramsJsonStr) {
        var ctx = JSON.parse(paramsJsonStr)['context'];
        var page = common.getInputReqParam(ctx, 'page');
        // do something...
        // return anything string
        return '';
    }
  • common.getInputReqBody(ctx, field)

    獲取客戶端請求體

    • ctx: 上下文
    • field 字段名 【選填】,不傳時返回整個請求體
    function dyFunc(paramsJsonStr) {
        var ctx = JSON.parse(paramsJsonStr)['context'];
        var page = common.getInputReqBody(ctx, 'page');
        // do something...
        // return anything string
        return '';
    }
  • common.getInputRespHeader(ctx, headerName)

    獲取返回給客戶端的響應頭

    • ctx: 上下文
    • headerName 響應頭字段名 【選填】,不傳時返回全部響應頭
    function dyFunc(paramsJsonStr) {
        var ctx = JSON.parse(paramsJsonStr)['context'];
        var page = common.getInputRespHeader(ctx, 'content-type');
        // do something...
        // return anything string
        return '';
    }
  • common.getInputRespBody(ctx, field)

    獲取返回給客戶端的響應體

    • ctx: 上下文
    • field 字段名 【選填】,不傳時返回整個響應體
    function dyFunc(paramsJsonStr) {
        var ctx = JSON.parse(paramsJsonStr)['context'];
        var page = common.getInputReqBody(ctx, 'page');
        // do something...
        // return anything string
        return '';
    }
  • common.getStepReqHeader(ctx, stepName, requestName, headerName)

    獲取步驟中調用的接口的請求頭

    • ctx 上下文 【必填】
    • stepName 步驟名【必填】
    • requestName 請求的接口名 【必填】
    • headerName 請求頭字段名 【選填】,不傳時返回全部請求頭
    function dyFunc(paramsJsonStr) {
        var ctx = JSON.parse(paramsJsonStr)['context'];
        var contentType = common.getStepReqHeader(ctx, 'step1', 'request1', 'content-type');
        // do something...
        // return anything string
        return '';
    }
  • common.getStepReqParam(ctx, stepName, requestName, paramName)

    獲取步驟中調用的接口的URL參數

    • ctx 上下文 【必填】
    • stepName 步驟名【必填】
    • requestName 請求的接口名 【必填】
    • paramName URL參數名 【選填】,不傳時返回全部URL參數
    function dyFunc(paramsJsonStr) {
        var ctx = JSON.parse(paramsJsonStr)['context'];
        var page = common.getStepReqParam(ctx, 'step1', 'request1', 'page');
        // do something...
        // return anything string
        return '';
    }
  • common.getStepReqBody(ctx, stepName, requestName, field)

    獲取步驟中調用的接口的請求體

    • ctx 上下文 【必填】
    • stepName 步驟名【必填】
    • requestName 請求的接口名 【必填】
    • field 字段名 【選填】,不傳時返回整個請求體
    function dyFunc(paramsJsonStr) {
        var ctx = JSON.parse(paramsJsonStr)['context'];
        var page = common.getStepReqBody(ctx, 'step1', 'request1', 'page');
        // do something...
        // return anything string
        return '';
    }
  • common.getStepRespHeader(ctx, stepName, requestName, headerName)

    獲取步驟中調用的接口的響應頭

    • ctx 上下文 【必填】
    • stepName 步驟名【必填】
    • requestName 請求的接口名 【必填】
    • headerName 響應頭字段名 【選填】,不傳時返回全部響應頭
    function dyFunc(paramsJsonStr) {
        var ctx = JSON.parse(paramsJsonStr)['context'];
        var contentType = common.getStepRespHeader(ctx, 'step1', 'request1', 'content-type');
        // do something...
        // return anything string
        return '';
    }
  • common.getStepRespBody(ctx, stepName, requestName, field)

    獲取步驟中調用的接口的響應頭

    • ctx 上下文 【必填】
    • stepName 步驟名【必填】
    • requestName 請求的接口名 【必填】
    • field 字段名 【選填】,不傳時返回整個響應頭
    function dyFunc(paramsJsonStr) {
        var ctx = JSON.parse(paramsJsonStr)['context'];
        var page = common.getStepRespBody(ctx, 'step1', 'request1', 'page');
        // do something...
        // return anything string
        return '';
    }
  • common.getStepResult(ctx, stepName, field)

    獲取步驟結果

    • ctx 上下文 【必填】
    • stepName 步驟名【必填】
    • field 字段名 【選填】,不傳時返回整個步驟結果對象
    function dyFunc(paramsJsonStr) {
        var ctx = JSON.parse(paramsJsonStr)['context'];
        var list = common.getStepResult(ctx, 'step1', 'list');
        // do something...
        // return anything string
        return '';
    }

#工具函數——groovy

  • context.getInputReq()

    獲取上下文客戶端中請求對象

    Map<String, Object> req = context.getInputReq()
  • context.getStepReq(stepName, requestName):

    獲取上下文步驟中請求接口的請求對象

    • stepName: 配置步驟中的 step name
    • requestName :配置步驟中的 stepName 對應的 request name
    Map<String, Object> req = context.getStepReq('step1', 'request1')
  • context.getStepResp(stepName, requestName)

    獲取上下文步驟中請求接口的響應對象

    • stepName: 配置步驟中的 step name
    • requestName :配置步驟中的 stepName 對應的 request name
  • context.getInputReqHeader(headerName)

    獲取客戶端請求頭

    • headerName: 請求頭字段名 【選填】,不傳時返回全部請求頭
  • context.getInputReqParam(paramName)

    獲取客戶端URL請求參數(query string)

    • paramName URL參數名 【選填】,不傳時返回全部請求參數
  • context.getInputReqBody(field)

    獲取客戶端請求體

    • field 字段名 【選填】,不傳時返回整個請求體
  • context.getInputRespHeader(headerName)

    獲取返回給客戶端的響應頭

    • headerName 響應頭字段名 【選填】,不傳時返回全部響應頭
  • context.getInputRespBody(field)

    獲取返回給客戶端的響應體

    • field 字段名 【選填】,不傳時返回整個響應體
  • context.getStepReqHeader(ctx, stepName, requestName, headerName)

    獲取步驟中調用的接口的請求頭

    • stepName 步驟名【必填】
    • requestName 請求的接口名 【必填】
    • headerName 請求頭字段名 【選填】,不傳時返回全部請求頭
  • context.getStepReqParam(stepName, requestName, paramName)

    獲取步驟中調用的接口的URL參數

    • stepName 步驟名【必填】
    • requestName 請求的接口名 【必填】
    • paramName URL參數名 【選填】,不傳時返回全部URL參數
  • context.getStepReqBody(stepName, requestName, field)

    獲取步驟中調用的接口的請求體

    • stepName 步驟名【必填】
    • requestName 請求的接口名 【必填】
    • field 字段名 【選填】,不傳時返回整個請求體
  • context.getStepRespHeader(stepName, requestName, headerName)

    獲取步驟中調用的接口的響應頭

    • stepName 步驟名【必填】
    • requestName 請求的接口名 【必填】
    • headerName 響應頭字段名 【選填】,不傳時返回全部響應頭
  • context.getStepRespBody(stepName, requestName, field)

    獲取步驟中調用的接口的響應頭

    • stepName 步驟名【必填】
    • requestName 請求的接口名 【必填】
    • field 字段名 【選填】,不傳時返回整個響應頭
  • context.getStepResult(stepName, field)

    獲取步驟結果

    • stepName 步驟名【必填】
    • field 字段名 【選填】,不傳時返回整個步驟結果對象

#拋出異常

當要在腳本里停止請求時能夠經過如下方式來實現

返回一個對象且這個對象包含一個_stopAndResponse等於true的屬性,Fizz會終止後續的操做並把這個對象返回給調用方。

#重定向

經過腳本能夠實現重定向,腳本返回一個對象且這個對象同時包含_stopAndResponse=true和_redirectUrl屬性,_redirectUrl的值爲重定向的目標URL,Fizz會終止後續的操做並進行重定向。JavaScript腳本樣例以下:

#配置路由

至此服務編排的接口配置完成,但此時還不能經過網關訪問接口,須要到網關管理-路由管理裏配置路由

#在線測試

  • 支持在線實時測試
  • 支持測試接口和正式接口隔離
  • 支持返回上下文,能夠查看整個執行過程當中各個步驟及請求的輸入與輸出
  • 支持保存歷史測試記錄

支持調試模式,在測試接口和正式接口都可使用,修改後從新發布可實時生效,在調試模式下會打印請求日誌及報文,主要用於排查線上問題

#腳本執行異常

當腳本執行異常時context裏會記錄異常信息

  • exceptionMessage 異常信息
  • exceptionStacks 異常堆棧信息
  • exceptionData 引發異常的腳本數據
// 上下文數據結構設計
// 上下文,用於保存客戶輸入輸出和每一個步驟的輸入與輸出結果
var context = {
	// 是否DEBUG模式
	debug:false,
	
	// exception info
	exceptionMessage: "",
	exceptionStacks: "",
  exceptionData: "", // such as script source code that cause exception

  // ... other fields
}

在請求里加上returnContext=true能夠返回context上下文,異常信息樣例:

#導入導出

導入導出主要用於在各個環境間同步接口配置,在開發環境配置好後導到測試環境中測試,測試完後導到生產環境進行發佈

#發佈|下線和審覈

目前發佈|下線申請有以上兩個入口。

  • 批量發佈:對發佈單裏的接口進行批量發佈
  • 批量回滾:對發佈單裏的接口進行批量回滾
  • 發佈:實時發佈到網關
  • 回滾:支持回滾到歷史任何一個版本,可在發佈歷史裏指定一個版本進行回滾
  • 下線:從網關刪除接口,在後臺能夠經過發佈功能再次上線

#發佈流程說明

申請發佈、審覈、發佈和下線功能的權限可根據須要靈活分配給不一樣角色,如:開發人員只能申請發佈,上級領導審覈,運維或測試人員執行發佈、回滾或下線。在開發、測試和預生產環境爲了方便開發人員調試也可把申請發佈、審覈、發佈和下線功能都分配給開發人員。

相關文章
相關標籤/搜索