# 脚本
# 简介
FizzGate
支持通过自定义脚本进行服务编排:
- 在 配置输入 中 通过 脚本校验 输入内容;
- 在 配置输出 中 通过 脚本 定义 输出内容,也可以细化到 某个输出字段的 脚本处理;
- 在 配置步骤 中 通过 脚本 定义 配置入参、配置响应 的返回内容;
- 在 结果校验 中 通过 脚本 完全自定义校验逻辑,校验输出内容。
FizzGate
支持 javascript
和 groovy
两种脚本语言,方便开发人员灵活的选择自己熟悉的语言进行服务编排。
其中,
如果使用javascript
,可以 通过 common
对象获取一系列工具函数,方便进行逻辑处理;
而在 groovy
中,所有的工具函数都挂载在 context
下,你可以很方便的使用它们。
# 脚本编写格式
# javascript
编写JavaScript脚本时,需要按照以下固定格式进行编写。 function name dyFunc
不可修改。
返回值只能是基本类型,如 string/number/boolean
,object/array
类型的必须通过JSON.stringify
序列化后返回。
FizzGate 是通过调用 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
字段名 【选填】,不传时返回整个步骤结果对象