创建服务
#创建聚合接口
#配置输入
- 配置输入的定义包括3部分:请求头、请求体和Query参数
- 基于JSON Schema规范
- 自带校验规则
- 支持自定义脚本实现复杂的逻辑校验
JSON Schema规范,详见:
http://json-schema.org/specification.html(opens new window)
http://json-schema.org/understanding-json-schema/(opens new window)
#配置校验结果
- 校验不通过时,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时才调用接口
#配置步骤结果处理
-
支持对步骤里调用的每一个接口的返回结果做数据转换,如果配置数据转换规则原样返回并存储到上下文里供后续使用
-
支持对步骤里调用的一个或多个接口的返回结果做处理,并把处理完的结果存储到上下文里供后续使用,不配置则不处理
#配置输出
配置返回给调用方的结果
- 支持配置响应头
- 支持配置响应体
- 支持自定脚本处理复杂的业务逻辑
#脚本
#简介
Fizz
支持通过自定义脚本进行服务编排:
- 在 配置输入 中 通过 脚本校验 输入内容;
- 在 配置输出 中 通过 脚本 定义 输出内容,也可以细化到 某个输出字段的 脚本处理;
- 在 配置步骤 中 通过 脚本 定义 配置入参、配置响应 的返回内容;
- 在 结果校验 中 通过 脚本 完全自定义校验逻辑,校验输出内容。
Fizz
支持 javascript
和 groovy
两种脚本语言,方便开发人员灵活的选择自己熟悉的语言进行服务编排。
其中,
如果使用javascript
,可以 通过 common
对象获取一系列工具函数,方便进行逻辑处理;
而在 groovy
中,所有的工具函数都挂载在 context
下,你可以很方便的使用它们。
#脚本编写格式
#javascript
编写JavaScript脚本时,需要按照以下固定格式进行编写。 function name dyFunc
不可修改。
返回值只能是基本类型,如 string/number/boolean
,object/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 namerequestName
:配置步骤中的 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 namerequestName
:配置步骤中的 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 namerequestName
:配置步骤中的 stepName 对应的 request name
Map<String, Object> req = context.getStepReq('step1', 'request1')
-
context.getStepResp(stepName, requestName)
获取上下文步骤中请求接口的响应对象
stepName
: 配置步骤中的 step namerequestName
:配置步骤中的 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上下文,异常信息样例:
#导入导出
导入导出主要用于在各个环境间同步接口配置,在开发环境配置好后导到测试环境中测试,测试完后导到生产环境进行发布
#发布|下线和审核
目前发布|下线申请有以上两个入口。
- 批量发布:对发布单里的接口进行批量发布
- 批量回滚:对发布单里的接口进行批量回滚
- 发布:实时发布到网关
- 回滚:支持回滚到历史任何一个版本,可在发布历史里指定一个版本进行回滚
- 下线:从网关删除接口,在后台可以通过发布功能再次上线
#发布流程说明
申请发布、审核、发布和下线功能的权限可根据需要灵活分配给不同角色,如:开发人员只能申请发布,上级领导审核,运维或测试人员执行发布、回滚或下线。在开发、测试和预生产环境为了方便开发人员调试也可把申请发布、审核、发布和下线功能都分配给开发人员。