## # 创建服务

## # 创建聚合接口

## # 配置输入

- 配置输入的定义包括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会终止后续的操作并把这个对象返回给调用方。

## # 在线测试

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

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

## # 导入导出

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

## # 发布|下线和审核

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

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

### # 发布流程说明

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