如何通过Fizz网关做服务编排


home: false
title: 服务编排配置


创建服务

创建聚合接口

配置输入

  • 配置输入的定义包括3部分:请求头、请求体和Query参数
  • 基于JSON Schema规范
  • 自带校验规则
  • 支持自定义脚本实现复杂的逻辑校验

JSON Schema规范,详见:

http://json-schema.org/specification.html

http://json-schema.org/understanding-json-schema/

配置校验结果

  • 校验不通过时,Fizz会把校验失败的原因(如:订单ID不能为空)放到上下文的validateMsg字段里
  • 可以自定义返回给调用方的报文格式,如 msgCode, message
  • 支持自定义响应头
  • 支持自定义脚本处理校验结果

配置步骤

配置步骤的基础信息

配置步骤的接口入出参

步骤说明

  • 一个聚合接口可包含多个步骤
  • 一个步骤可包含多个请求(即调用多个接口)
  • 步骤间是串联顺序执行
  • 一个步骤内的多个请求并行执行

数据转换

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

固定值

引用值

脚本

星号 *

星号通配符可以接收一个返回对象类型的引用值,返回对象里的字段会合并到目标对象里

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

优先级与覆盖顺序

固定值 < 引用值 < 脚本 < 星号*

当一个字段配置了多种类型的值时按以上顺序覆盖,星号优先级最高

引用值规范

# 获取入参请求头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时才调用接口

配置步骤结果处理

  • 支持对步骤里调用的每一个接口的返回结果做数据转换,如果配置数据转换规则原样返回并存储到上下文里供后续使用

  • 支持对步骤里调用的一个或多个接口的返回结果做处理,并把处理完的结果存储到上下文里供后续使用,不配置则不处理

配置输出

配置返回给调用方的结果

  • 支持配置响应头
  • 支持配置响应体
  • 支持自定脚本处理复杂的业务逻辑

脚本

目前支持以下脚本语言:

Javascript (推荐) - ECMAScript 5标准

JS脚本只支持单函数,且函数名不可变,在创建脚本时系统会自动生成初始模板,模板里包含相关使用说明

Groovy

common.js 提供了操作context上下文的便捷操作函数

/**
 * context 上下文便捷操作函数
 *
 */
var common = {
  /* *********** private function begin *********** */

  // 获取上下文中客户端请求对象
  getInputReq: function (ctx) {
    if (!ctx || !ctx['input'] || !ctx['input']['request']) {
      return {};
    }
    return ctx['input']['request']
  },

  // 获取上下文步骤中请求接口的请求对象
  getStepReq: function (ctx, stepName, requestName) {
    if (!ctx || !stepName || !requestName) {
      return {};
    }
    if (!ctx[stepName] || !ctx[stepName]['requests'] || !ctx[stepName]['requests'][requestName] ||
      !ctx[stepName]['requests'][requestName]['request']) {
      return {};
    }
    return ctx[stepName]['requests'][requestName]['request'];
  },

  // 获取上下文步骤中请求接口的响应对象
  getStepResp: function (ctx, stepName, requestName) {
    if (!ctx || !stepName || !requestName) {
      return {};
    }
    if (!ctx[stepName] || !ctx[stepName]['requests'] || !ctx[stepName]['requests'][requestName] ||
      !ctx[stepName]['requests'][requestName]['response']) {
      return {};
    }
    return ctx[stepName]['requests'][requestName]['response'];
  },

  /* *********** private function end *********** */

  /* *********** input begin ************ */

  /**
   * 获取客户端请求头
   * @param {*} ctx 上下文 【必填】
   * @param {*} headerName 请求头字段名 【选填】,不传时返回所有请求头
   */
  getInputReqHeader: function (ctx, headerName) {
    var req = this.getInputReq(ctx);
    var headers = req['headers'] || {};
    return headerName ? headers[headerName] : headers;
  },

  /**
   * 获取客户端URL请求参数(query string)
   * @param {*} ctx 上下文 【必填】
   * @param {*} paramName URL参数名 【选填】,不传时返回所有请求参数
   */
  getInputReqParam: function (ctx, paramName) {
    var req = this.getInputReq(ctx);
    var params = req['params'] || {};
    return paramName ? params[paramName] : params;
  },

  /**
   * 获取客户端请求体
   * @param {*} ctx 上下文 【必填】
   * @param {*} field 字段名 【选填】,不传时返回整个请求体
   */
  getInputReqBody: function (ctx, field) {
    var req = this.getInputReq(ctx);
    var body = req['body'] || {};
    return field ? body[field] : body;
  },

  /**
   * 获取返回给客户端的响应头
   * @param {*} ctx 上下文 【必填】
   * @param {*} headerName 响应头字段名 【选填】,不传时返回所有响应头
   */
  getInputRespHeader: function (ctx, headerName) {
    var req = this.getInputReq(ctx);
    var headers = req['headers'] || {};
    return headerName ? headers[headerName] : headers;
  },

  /**
   * 获取返回给客户端的响应体
   * @param {*} ctx 上下文 【必填】
   * @param {*} field 字段名 【选填】,不传时返回整个响应体
   */
  getInputRespBody: function (ctx, field) {
    var req = this.getInputReq(ctx);
    var body = req['body'] || {};
    return field ? body[field] : body;
  },

  /* *********** input begin ************ */

  /* *********** step request begin ************ */

  /**
   * 获取步骤中调用的接口的请求头
   * @param {*} ctx 上下文 【必填】
   * @param {*} stepName 步骤名【必填】
   * @param {*} requestName 请求的接口名 【必填】
   * @param {*} headerName 请求头字段名 【选填】,不传时返回所有请求头
   */
  getStepReqHeader: function (ctx, stepName, requestName, headerName) {
    var req = this.getStepReq(ctx, stepName, requestName);
    var headers = req['headers'] || {};
    return headerName ? headers[headerName] : headers;
  },

  /**
   * 获取步骤中调用的接口的URL参数
   * @param {*} ctx 上下文 【必填】
   * @param {*} stepName 步骤名【必填】
   * @param {*} requestName 请求的接口名 【必填】
   * @param {*} paramName URL参数名 【选填】,不传时返回所有URL参数
   */
  getStepReqParam: function (ctx, stepName, requestName, paramName) {
    var req = this.getStepReq(ctx, stepName, requestName);
    var params = req['params'] || {};
    return paramName ? params[paramName] : params;
  },

  /**
   * 获取步骤中调用的接口的请求体
   * @param {*} ctx 上下文 【必填】
   * @param {*} stepName 步骤名【必填】
   * @param {*} requestName 请求的接口名 【必填】
   * @param {*} field 字段名 【选填】,不传时返回整个请求体
   */
  getStepReqBody: function (ctx, stepName, requestName, field) {
    var req = this.getStepReq(ctx, stepName, requestName);
    var body = req['body'] || {};
    return field ? body[field] : body;
  },

  /**
   * 获取步骤中调用的接口的响应头
   * @param {*} ctx 上下文 【必填】
   * @param {*} stepName 步骤名【必填】
   * @param {*} requestName 请求的接口名 【必填】
   * @param {*} headerName 响应头字段名 【选填】,不传时返回所有响应头
   */
  getStepRespHeader: function (ctx, stepName, requestName, headerName) {
    var resp = this.getStepResp(ctx, stepName, requestName);
    var headers = resp['headers'] || {};
    return headerName ? headers[headerName] : headers;
  },

  /**
   * 获取步骤中调用的接口的响应头
   * @param {*} ctx 上下文 【必填】
   * @param {*} stepName 步骤名【必填】
   * @param {*} requestName 请求的接口名 【必填】
   * @param {*} field 字段名 【选填】,不传时返回整个响应头
   */
  getStepRespBody: function (ctx, stepName, requestName, field) {
    var resp = this.getStepResp(ctx, stepName, requestName);
    var body = resp['body'] || {};
    return field ? body[field] : body;
  },

  /**
   * 获取步骤结果
   * @param {*} ctx 上下文 【必填】
   * @param {*} stepName 步骤名【必填】
   * @param {*} field 字段名 【选填】,不传时返回整个步骤结果对象
   */
  getStepResult: function (ctx, stepName, field) {
    if (!ctx || !stepName || !ctx[stepName]) {
      return {};
    }
    var result = ctx[stepName]['result'] || {};
    return field ? result[field] : result;
  }

  /* *********** step request end ************ */

};

context.js 数据结构


// 上下文,用于保存客户输入输出和每个步骤的输入与输出结果
var context = {
    // 是否DEBUG模式
    debug:false,

    // 各个操作的耗时
    elapsedTimes: [{
        [actionName]: 123, // 操作名称:耗时
    }],

  // 客户输入和接口的返回结果
  input: {
      request:{
        path: "",
          method: "GET/POST",
          headers: {},
          body: {},
          params: {}
      },
      response: { // 聚合接口的响应
          headers: {},
          body: {}
      }
  },

  // 步骤
  step1: {
      requests: {
        // 接口1
          request1: {
            // 请求相关参数
              request:{
                  url: "",
                  method: "GET/POST",
                  headers: {},
                  body: {}
              },
              // 根据转换规则转换后的接口响应
              response: {
                  headers: {},
                  body: {}
              }
          },
          // 接口2
          request2: {
              request:{
                  url: "",
                  method: "GET/POST",
                  headers: {},
                  body: {}
              },
              response: {
              headers: {},
                  body: {}
              }
          }
          //...
      },

      // 步骤结果
      result: {}

  }
};

异常处理

当要在脚本里中止请求时可以通过以下方式来实现

返回一个对象且这个对象包含一个_stopAndResponse等于true的属性,Fizz会终止后续的操作并把这个对象返回给调用方。

在线测试

  • 支持在线实时测试
  • 支持测试接口和正式接口隔离
  • 支持返回上下文,可以查看整个执行过程中各个步骤及请求的输入与输出
  • 支持保存历史测试记录

支持调试模式,在测试接口和正式接口均可使用,修改后重新发布可实时生效,在调试模式下会打印请求日志及报文,主要用于排查线上问题

导入导出

导入导出主要用于在各个环境间同步接口配置,在开发环境配置好后导到测试环境中测试,测试完后导到生产环境进行发布

发布|下线和审核

目前发布|下线申请有以上两个入口。

  • 批量发布:对发布单里的接口进行批量发布
  • 批量回滚:对发布单里的接口进行批量回滚
  • 发布:实时发布到网关
  • 回滚:支持回滚到历史任何一个版本,可在发布历史里指定一个版本进行回滚
  • 下线:从网关删除接口,在后台可以通过发布功能再次上线

发布流程说明

申请发布、审核、发布和下线功能的权限可根据需要灵活分配给不同角色,如:开发人员只能申请发布,上级领导审核,运维或测试人员执行发布、回滚或下线。在开发、测试和预生产环境为了方便开发人员调试也可把申请发布、审核、发布和下线功能都分配给开发人员。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章