开张大吉。

This commit is contained in:
linwaiwai
2020-09-02 18:35:03 +08:00
parent 73f71ebd00
commit c49aa2fe7a
94 changed files with 11220 additions and 3 deletions

View File

@@ -0,0 +1,53 @@
/*
* Copyright (C) 2020 the original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.wehotel.fizz;
import com.wehotel.fizz.input.Input;
/**
*
* @author francis
*
*/
public class AggregateResource {
private Pipeline pipeline;
private Input input;
public AggregateResource(Pipeline pipeline, Input input) {
super();
this.pipeline = pipeline;
this.input = input;
}
public Pipeline getPipeline() {
return pipeline;
}
public void setPipeline(Pipeline pipeline) {
this.pipeline = pipeline;
}
public Input getInput() {
return input;
}
public void setInput(Input input) {
this.input = input;
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright (C) 2020 the original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.wehotel.fizz;
import java.util.Map;
import org.springframework.util.MultiValueMap;
/**
*
* @author francis
*
*/
public class AggregateResult {
private MultiValueMap<String, String> headers;
private Map<String, Object> body;
public MultiValueMap<String, String> getHeaders() {
return headers;
}
public void setHeaders(MultiValueMap<String, String> headers) {
this.headers = headers;
}
public Map<String, Object> getBody() {
return body;
}
public void setBody(Map<String, Object> body) {
this.body = body;
}
}

View File

@@ -0,0 +1,393 @@
/*
* Copyright (C) 2020 the original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.wehotel.fizz;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.wehotel.config.AppConfigProperties;
import com.wehotel.fizz.input.ClientInputConfig;
import com.wehotel.fizz.input.Input;
import com.wehotel.fizz.input.InputType;
import org.apache.commons.io.FileUtils;
import org.noear.snack.ONode;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.ReactiveStringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import static com.wehotel.listener.AggregateRedisConfig.AGGREGATE_REACTIVE_REDIS_TEMPLATE;
import static com.wehotel.util.Constants.Symbol.FORWARD_SLASH;
/**
*
* @author francis
* @author zhongjie
*
*/
@Component
public class ConfigLoader {
/**
* 聚合配置存放Hash的Key
*/
private static final String AGGREGATE_HASH_KEY = "fizz_aggregate_config";
private static Map<String, String> aggregateResources = null;
private static Map<String, ConfigInfo> resourceKey2ConfigInfoMap = null;
private static Map<String, String> aggregateId2ResourceKeyMap = null;
@Resource
private AppConfigProperties appConfigProperties;
@Resource(name = AGGREGATE_REACTIVE_REDIS_TEMPLATE)
private ReactiveStringRedisTemplate reactiveStringRedisTemplate;
@Value("${fizz.aggregate.read-local-config-flag:false}")
private Boolean readLocalConfigFlag;
public Input createInput(String configStr) throws IOException {
ONode cfgNode = ONode.loadStr(configStr);
Input input = new Input();
input.setName(cfgNode.select("$.name").getString());
ClientInputConfig clientInputConfig = new ClientInputConfig();
clientInputConfig.setDataMapping(cfgNode.select("$.dataMapping").toObject(Map.class));
clientInputConfig.setHeaders(cfgNode.select("$.headers").toObject(Map.class));
clientInputConfig.setMethod(cfgNode.select("$.method").getString());
clientInputConfig.setPath(cfgNode.select("$.path").getString());
if(cfgNode.select("$.debug") != null) {
clientInputConfig.setDebug(cfgNode.select("$.debug").getBoolean());
}
clientInputConfig.setType(InputType.valueOf(cfgNode.select("$.type").getString()));
clientInputConfig.setLangDef(cfgNode.select("$.langDef").toObject(Map.class));
clientInputConfig.setBodyDef(cfgNode.select("$.bodyDef").toObject(Map.class));
clientInputConfig.setHeadersDef(cfgNode.select("$.headersDef").toObject(Map.class));
clientInputConfig.setParamsDef(cfgNode.select("$.paramsDef").toObject(Map.class));
clientInputConfig.setScriptValidate(cfgNode.select("$.scriptValidate").toObject(Map.class));
clientInputConfig.setValidateResponse(cfgNode.select("$.validateResponse").toObject(Map.class));
input.setConfig(clientInputConfig);
return input;
}
public Pipeline createPipeline(String configStr) throws IOException {
ONode cfgNode = ONode.loadStr(configStr);
Pipeline pipeline = new Pipeline();
List<Map<String, Object>> stepConfigs = cfgNode.select("$.stepConfigs").toObject(List.class);
for (Map<String, Object> stepConfig : stepConfigs) {
// set the specified env URL
this.handleRequestURL(stepConfig);
Step step = new Step.Builder().read(stepConfig);
step.setName((String) stepConfig.get("name"));
if (stepConfig.get("stop") != null) {
step.setStop((Boolean) stepConfig.get("stop"));
} else {
step.setStop(false);
}
step.setDataMapping((Map<String, Object>) stepConfig.get("dataMapping"));
pipeline.addStep(step);
}
return pipeline;
}
public List<ConfigInfo> getConfigInfo() {
if (aggregateResources == null) {
try {
this.init();
} catch (Exception e) {
e.printStackTrace();
}
}
return new ArrayList<>(resourceKey2ConfigInfoMap.values());
}
public String getConfigStr(String configId) {
if (aggregateResources == null) {
try {
this.init();
} catch (Exception e) {
e.printStackTrace();
}
}
String resourceKey = aggregateId2ResourceKeyMap.get(configId);
if (resourceKey == null) {
return null;
}
return aggregateResources.get(resourceKey);
}
private void handleRequestURL(Map<String, Object> stepConfig) {
List<Object> requests = (List<Object>) stepConfig.get("requests");
for (Object obj : requests) {
Map<String, Object> request = (Map<String, Object>) obj;
String envUrl = (String) request.get(appConfigProperties.getEnv() + "Url");
if(!StringUtils.isEmpty(envUrl)) {
request.put("url", request.get(appConfigProperties.getEnv() + "Url"));
}
}
}
public synchronized void init() throws Exception {
if (aggregateResources == null) {
aggregateResources = new ConcurrentHashMap<>(1024);
resourceKey2ConfigInfoMap = new ConcurrentHashMap<>(1024);
aggregateId2ResourceKeyMap = new ConcurrentHashMap<>(1024);
}
if (readLocalConfigFlag) {
File dir = new File("json");
if (dir.exists() && dir.isDirectory()) {
File[] files = dir.listFiles();
if (files != null && files.length > 0) {
for (File file : files) {
if (!file.exists()) {
throw new IOException("File not found");
}
String configStr = FileUtils.readFileToString(file, Charset.forName("UTF-8"));
this.addConfig(configStr);
}
}
}
} else {
// 从Redis缓存中获取配置
reactiveStringRedisTemplate.opsForHash().scan(AGGREGATE_HASH_KEY).subscribe(entry -> {
String configStr = (String) entry.getValue();
this.addConfig(configStr);
});
}
}
public synchronized void addConfig(String configStr) {
if (aggregateResources == null) {
try {
this.init();
} catch (Exception e) {
e.printStackTrace();
}
}
ONode cfgNode = ONode.loadStr(configStr);
String method = cfgNode.select("$.method").getString();
String path = cfgNode.select("$.path").getString();
String resourceKey = method.toUpperCase() + ":" + path;
String configId = cfgNode.select("$.id").getString();
String configName = cfgNode.select("$.name").getString();
long version = cfgNode.select("$.version").getLong();
if (StringUtils.hasText(configId)) {
String existResourceKey = aggregateId2ResourceKeyMap.get(configId);
if (StringUtils.hasText(existResourceKey)) {
// 删除旧有的配置
aggregateResources.remove(existResourceKey);
resourceKey2ConfigInfoMap.remove(existResourceKey);
}
aggregateId2ResourceKeyMap.put(configId, resourceKey);
}
aggregateResources.put(resourceKey, configStr);
resourceKey2ConfigInfoMap.put(resourceKey, this.buildConfigInfo(configId, configName, method, path, version));
}
public synchronized void deleteConfig(String configIds) {
if (CollectionUtils.isEmpty(aggregateId2ResourceKeyMap)) {
return;
}
JSONArray idArray = JSON.parseArray(configIds);
idArray.forEach(it -> {
String configId = (String) it;
String existResourceKey =aggregateId2ResourceKeyMap.get(configId);
if (StringUtils.hasText(existResourceKey)) {
aggregateResources.remove(existResourceKey);
resourceKey2ConfigInfoMap.remove(existResourceKey);
aggregateId2ResourceKeyMap.remove(configId);
}
});
}
public AggregateResource matchAggregateResource(String method, String path) {
if (aggregateResources == null) {
try {
init();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
String key = method.toUpperCase() + ":" + path;
if(aggregateResources.containsKey(key) && aggregateResources.get(key) != null) {
String configStr = aggregateResources.get(key);
Input input = null;
Pipeline pipeline = null;
try {
input = createInput(configStr);
pipeline = createPipeline(configStr);
} catch (IOException e) {
e.printStackTrace();
return null;
}
if (pipeline != null && input != null) {
ClientInputConfig cfg = (ClientInputConfig) input.getConfig();
return new AggregateResource(pipeline, input);
}
}
return null;
}
private ConfigInfo buildConfigInfo(String configId, String configName, String method, String path, long version) {
String serviceName = this.extractServiceName(path);
ConfigInfo configInfo = new ConfigInfo();
configInfo.setConfigId(configId);
configInfo.setConfigName(configName);
configInfo.setServiceName(serviceName);
configInfo.setMethod(method);
configInfo.setPath(path);
configInfo.setVersion(version == 0 ? null : version);
return configInfo;
}
private static final String FORMAL_PATH_PREFIX = "/proxy/";
private static final int FORMAL_PATH_SERVICE_NAME_START_INDEX = 7;
private static final String TEST_PATH_PREFIX = "/proxytest/";
private static final int TEST_PATH_SERVICE_NAME_START_INDEX = 11;
private String extractServiceName(String path) {
if (path != null) {
if (path.startsWith(FORMAL_PATH_PREFIX)) {
int endIndex = path.indexOf(FORWARD_SLASH, FORMAL_PATH_SERVICE_NAME_START_INDEX);
if (endIndex > FORMAL_PATH_SERVICE_NAME_START_INDEX) {
return path.substring(FORMAL_PATH_SERVICE_NAME_START_INDEX, endIndex);
}
} else if (path.startsWith(TEST_PATH_PREFIX)) {
int endIndex = path.indexOf(FORWARD_SLASH, TEST_PATH_SERVICE_NAME_START_INDEX);
if (endIndex > TEST_PATH_SERVICE_NAME_START_INDEX) {
return path.substring(TEST_PATH_SERVICE_NAME_START_INDEX, endIndex);
}
}
}
return null;
}
public static class ConfigInfo implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 配置ID
*/
private String configId;
/**
* 配置名
*/
private String configName;
/**
* 服务名
*/
private String serviceName;
/**
* 接口请求method类型
*/
private String method;
/**
* 接口请求路径
*/
private String path;
/**
* 版本号
*/
private Long version;
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ConfigInfo that = (ConfigInfo) o;
return Objects.equals(configId, that.configId) &&
Objects.equals(configName, that.configName) &&
Objects.equals(serviceName, that.serviceName) &&
Objects.equals(method, that.method) &&
Objects.equals(path, that.path) &&
Objects.equals(version, that.version);
}
@Override
public int hashCode() {
return Objects.hash(configId, configName, serviceName, method, path, version);
}
public String getConfigId() {
return configId;
}
public void setConfigId(String configId) {
this.configId = configId;
}
public String getConfigName() {
return configName;
}
public void setConfigName(String configName) {
this.configName = configName;
}
public String getServiceName() {
return serviceName;
}
public void setServiceName(String serviceName) {
this.serviceName = serviceName;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public Long getVersion() {
return version;
}
public void setVersion(Long version) {
this.version = version;
}
}
}

View File

@@ -0,0 +1,351 @@
/*
* Copyright (C) 2020 the original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.wehotel.fizz;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.script.ScriptException;
import com.wehotel.fizz.input.ClientInputConfig;
import com.wehotel.fizz.input.InputConfig;
import com.wehotel.schema.util.I18nUtils;
import com.wehotel.util.JsonSchemaUtils;
import org.noear.snack.ONode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import com.alibaba.fastjson.JSON;
import com.wehotel.flume.clients.log4j2appender.LogService;
import com.wehotel.fizz.input.Input;
import com.wehotel.fizz.input.PathMapping;
import com.wehotel.fizz.input.ScriptHelper;
import com.wehotel.util.MapUtil;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
*
* @author linwaiwai
* @author francis
* @author zhongjie
*
*/
public class Pipeline {
private static final Logger LOGGER = LoggerFactory.getLogger(Pipeline.class);
private LinkedList<Step> steps = new LinkedList<Step>();
private StepContext<String, Object> stepContext= new StepContext<>();
public void addStep(Step step) {
steps.add(step);
}
static void displayValue(String n) {
System.out.println("input : " + n);
}
public StepContext<String, Object> getStepContext(){
return this.stepContext;
}
public Mono<AggregateResult> run(Input input, Map<String, Object> clientInput, String traceId) {
ClientInputConfig config = (ClientInputConfig)input.getConfig();
this.initialStepContext(clientInput);
this.stepContext.setDebug(config.isDebug());
if(traceId != null) {
this.stepContext.setTraceId(traceId);
}
long t1 = System.currentTimeMillis();
List<String> validateErrorList = inputValidate(input, clientInput);
this.stepContext.addElapsedTime("入参校验", System.currentTimeMillis()-t1);
if (!CollectionUtils.isEmpty(validateErrorList)) {
long t2 = System.currentTimeMillis();
String validateMsg = StringUtils.collectionToCommaDelimitedString(validateErrorList);
// 将验证错误信息放入上下文
stepContext.put("validateMsg", validateMsg);
Map<String, Object> validateResponse = config.getValidateResponse();
// 数据转换
AggregateResult aggregateResult = this.doInputDataMapping(input, validateResponse);
this.stepContext.addElapsedTime("入参校验结果转换", System.currentTimeMillis() - t2);
return Mono.just(aggregateResult);
}
LinkedList<Step> opSteps = (LinkedList<Step>) steps.clone();
Step step1 = opSteps.removeFirst();
step1.beforeRun(stepContext, null);
Mono<List<StepResponse>> result = createStep(step1).expand(response -> {
if (opSteps.isEmpty() || response.isStop()) {
return Mono.empty();
}
Step step = opSteps.pop();
step.beforeRun(stepContext, response);
return createStep(step);
}).flatMap(response -> Flux.just(response)).collectList();
return result.flatMap(clientResponse -> {
// 数据转换
long t3 = System.currentTimeMillis();
AggregateResult aggResult = this.doInputDataMapping(input, null);
this.stepContext.addElapsedTime(input.getName()+"聚合接口响应结果数据转换", System.currentTimeMillis() - t3);
if(this.stepContext.isDebug()) {
LogService.setBizId(this.stepContext.getTraceId());
String jsonString = JSON.toJSONString(aggResult);
LOGGER.info("aggResult {}", jsonString);
LOGGER.info("stepContext {}", JSON.toJSONString(stepContext));
}
return Mono.just(aggResult);
});
}
@SuppressWarnings("unchecked")
public Mono<StepResponse> createStep(Step step) {
long start = System.currentTimeMillis();
List<Mono> monos = step.run();
Mono<Map>[] monoArray = monos.stream().toArray(Mono[]::new);
Mono<StepResponse>result = Flux.merge(monoArray).reduce(new HashMap(), (item1, item2)-> {
Input input = (Input)item2.get("request");
item1.put(input.getName() , item2.get("data"));
return item1;
}).flatMap(item -> {
// stepResult 数据转换
long t1 = System.currentTimeMillis();
StepResponse stepResponse = this.doStepDataMapping(step);
stepResponse.setStop(step.isStop());
long t2 = System.currentTimeMillis();
this.stepContext.addElapsedTime(step.getName() + "结果数据转换", t2 - t1);
this.stepContext.addElapsedTime(step.getName() + "耗时", System.currentTimeMillis() - start);
return Mono.just(stepResponse);
});
return result;
}
/**
* 初始化上下文
* @param clientInput 客户端提交上来的信息
*/
public void initialStepContext(Map<String,Object> clientInput) {
Map<String,Object> input = new HashMap<>();
Map<String,Object> inputRequest = new HashMap<>();
Map<String,Object> inputResponse = new HashMap<>();
input.put("request", inputRequest);
input.put("response", inputResponse);
if(clientInput != null) {
inputRequest.put("path", clientInput.get("path"));
inputRequest.put("method", clientInput.get("method"));
inputRequest.put("headers", clientInput.get("headers"));
inputRequest.put("params", clientInput.get("params"));
inputRequest.put("body", clientInput.get("body"));
}
stepContext.put("input", input);
}
private StepResponse doStepDataMapping(Step step) {
StepResponse stepResponse = (StepResponse)stepContext.get(step.getName());
if (step.getDataMapping() != null) {
Map<String, Object> responseMapping = (Map<String, Object>) step.getDataMapping().get("response");
if(responseMapping != null && !StringUtils.isEmpty(responseMapping)) {
ONode ctxNode = PathMapping.toONode(stepContext);
// body
stepResponse.setResult(PathMapping.transform(ctxNode, stepContext,
(Map<String, Object>) responseMapping.get("fixedBody"),
(Map<String, Object>) responseMapping.get("body")));
// script
if(responseMapping.get("script") != null) {
Map<String, Object> scriptCfg = (Map<String, Object>) responseMapping.get("script");
try {
Map<String, Object> stepBody = ScriptHelper.execute(scriptCfg, ctxNode, stepContext, Map.class);
if(stepBody != null) {
stepResponse.setResult(stepBody);
}
} catch (ScriptException e) {
LOGGER.warn("execute script failed, {}", e);
throw new RuntimeException("execute script failed");
}
}
}
}
return stepResponse;
}
/**
* 当validateResponse不为空表示验参失败使用该配置响应数据
*/
private AggregateResult doInputDataMapping(Input input, Map<String, Object> validateResponse) {
AggregateResult aggResult = new AggregateResult();
Map<String, Map<String,Object>> group = (Map<String, Map<String, Object>>) stepContext.get("input");
if(group == null) {
group = new HashMap<String, Map<String,Object>>();
stepContext.put("input", group);
}
Map<String,Object> response = null;
if(group.get("response") == null) {
response = new HashMap<>();
group.put("response", response);
}
response = group.get("response");
if (input != null && input.getConfig() != null && input.getConfig().getDataMapping() != null) {
Map<String, Object> responseMapping = (Map<String, Object>) input.getConfig().getDataMapping()
.get("response");
if (validateResponse != null) {
responseMapping = validateResponse;
}
if (responseMapping != null && !StringUtils.isEmpty(responseMapping)) {
ONode ctxNode = PathMapping.toONode(stepContext);
// headers
response.put("headers",
PathMapping.transform(ctxNode, stepContext,
(Map<String, Object>) responseMapping.get("fixedHeaders"),
(Map<String, Object>) responseMapping.get("headers")));
// body
Map<String,Object> body = PathMapping.transform(ctxNode, stepContext,
(Map<String, Object>) responseMapping.get("fixedBody"),
(Map<String, Object>) responseMapping.get("body"));
// script
if (responseMapping.get("script") != null) {
Map<String, Object> scriptCfg = (Map<String, Object>) responseMapping.get("script");
try {
Object respBody = ScriptHelper.execute(scriptCfg, ctxNode, stepContext);
if(respBody != null) {
body.putAll((Map<String, Object>) respBody);
}
} catch (ScriptException e) {
LOGGER.warn("execute script failed, {}", e);
throw new RuntimeException("execute script failed");
}
}
response.put("body", body);
}
}
Map<String, Object> respBody = (Map<String, Object>) response.get("body");
// 测试模式返回StepContext
if(stepContext.returnContext()) {
respBody.put("_context", stepContext);
}
aggResult.setBody((Map<String, Object>) response.get("body"));
aggResult.setHeaders(MapUtil.toMultiValueMap((Map<String, Object>) response.get("headers")));
return aggResult;
}
@SuppressWarnings("unchecked")
private List<String> inputValidate(Input input, Map<String, Object> clientInput) {
try {
InputConfig config = input.getConfig();
if (config instanceof ClientInputConfig) {
Map<String, Object> langDef = ((ClientInputConfig) config).getLangDef();
this.handleLangDef(langDef);
Map<String, Object> headersDef = ((ClientInputConfig) config).getHeadersDef();
if (!CollectionUtils.isEmpty(headersDef)) {
// 验证headers入参是否符合要求
List<String> errorList = JsonSchemaUtils.validateAllowValueStr(JSON.toJSONString(headersDef), JSON.toJSONString(clientInput.get("headers")));
if (!CollectionUtils.isEmpty(errorList)) {
return errorList;
}
}
Map<String, Object> paramsDef = ((ClientInputConfig) config).getParamsDef();
if (!CollectionUtils.isEmpty(paramsDef)) {
// 验证params入参是否符合要求
List<String> errorList = JsonSchemaUtils.validateAllowValueStr(JSON.toJSONString(paramsDef), JSON.toJSONString(clientInput.get("params")));
if (!CollectionUtils.isEmpty(errorList)) {
return errorList;
}
}
Map<String, Object> bodyDef = ((ClientInputConfig) config).getBodyDef();
if (!CollectionUtils.isEmpty(bodyDef)) {
// 验证body入参是否符合要求
List<String> errorList = JsonSchemaUtils.validate(JSON.toJSONString(bodyDef), JSON.toJSONString(clientInput.get("body")));
if (!CollectionUtils.isEmpty(errorList)) {
return errorList;
}
}
Map<String, Object> scriptValidate = ((ClientInputConfig) config).getScriptValidate();
if (!CollectionUtils.isEmpty(scriptValidate)) {
ONode ctxNode = PathMapping.toONode(stepContext);
// 验证入参是否符合脚本要求
try {
List<String> errorList = (List<String>) ScriptHelper.execute(scriptValidate, ctxNode, stepContext, List.class);
if (!CollectionUtils.isEmpty(errorList)) {
return errorList;
}
} catch (ScriptException e) {
LOGGER.warn("execute script failed", e);
throw new RuntimeException("execute script failed");
}
}
}
return null;
} finally {
I18nUtils.removeContextLocale();
}
}
@SuppressWarnings("unchecked")
private void handleLangDef(Map<String, Object> langDef) {
if (!CollectionUtils.isEmpty(langDef)) {
// 存在提示语言定义配置
Object langParamObj = langDef.get("langParam");
String langParam = null;
if (langParamObj instanceof String) {
langParam = (String) langParamObj;
}
Object langMappingObj = langDef.get("langMapping");
Map<String, Object> langMapping = null;
if (langMappingObj instanceof Map) {
langMapping = (Map<String, Object>) langMappingObj;
}
if (langParam != null && !CollectionUtils.isEmpty(langMapping)) {
ONode ctxNode = PathMapping.toONode(stepContext);
Map<String, Object> langParamMap = new HashMap<>(2);
langParamMap.put("langParam", langParam);
Map<String, Object> transformMap = PathMapping.transform(ctxNode, stepContext, null, langParamMap);
Object langParamValue = transformMap.get("langParam");
if (langParamValue != null) {
// 判断使用哪种语言
Object zh = langMapping.get("zh");
if (zh != null && zh.toString().equals(langParamValue.toString())) {
I18nUtils.setContextLocale(new Locale("zh"));
}
Object en = langMapping.get("en");
if (en != null && en.toString().equals(langParamValue.toString())) {
I18nUtils.setContextLocale(new Locale("en"));
}
}
}
}
}
}

View File

@@ -0,0 +1,148 @@
/*
* Copyright (C) 2020 the original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.wehotel.fizz;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ReactiveHttpOutputMessage;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
import com.alibaba.fastjson.JSON;
import com.wehotel.fizz.input.Input;
import com.wehotel.fizz.input.InputConfig;
import com.wehotel.fizz.input.InputContext;
import com.wehotel.fizz.input.InputFactory;
import com.wehotel.fizz.input.InputType;
import reactor.core.publisher.Mono;
/**
*
* @author linwaiwai
* @author francis
*
*/
public class Step {
private String name;
// 是否在执行完当前step就返回
private boolean stop;
private Map<String, Object> dataMapping;
private Map<String, InputConfig> requestConfigs = new HashMap<String, InputConfig>();
public static class Builder {
public Step read(Map<String, Object> config) {
Step step = new Step();
List<Map> requests= (List<Map>) config.get("requests");
for(Map requestConfig: requests) {
InputConfig inputConfig = InputFactory.createInputConfig(requestConfig);
step.addRequestConfig((String)requestConfig.get("name"), inputConfig);
}
return step;
}
}
private StepContext<String, Object> stepContext;
private StepResponse lastStepResponse = null;
private Map<String, Input> inputs = new HashMap<String, Input>();
public void beforeRun(StepContext<String, Object> stepContext2, StepResponse response ) {
stepContext = stepContext2;
lastStepResponse = response;
StepResponse stepResponse = new StepResponse(this, null, new HashMap<String, Map<String, Object>>());
stepContext.put(name, stepResponse);
Map<String, InputConfig> configs = this.getRequestConfigs();
for(String configName :configs.keySet()) {
InputConfig inputConfig = configs.get(configName);
InputType type = inputConfig.getType();
Input input = InputFactory.createInput(type.toString());
input.setConfig(inputConfig);
input.setName(configName);
input.setStepResponse(stepResponse);
InputContext context = new InputContext(stepContext, lastStepResponse);
input.beforeRun(context);
inputs.put(input.getName(), input);
}
}
public List<Mono> run() {
List<Mono> monos = new ArrayList<Mono>();
for(String name :inputs.keySet()) {
Input input = inputs.get(name);
if (input.needRun(stepContext)) {
Mono<Map> singleMono = input.run();
monos.add(singleMono);
}
}
return monos;
}
public void afeterRun() {
}
public InputConfig addRequestConfig(String name, InputConfig requestConfig) {
return requestConfigs.put(name, requestConfig);
}
public Map<String, InputConfig> getRequestConfigs() {
return requestConfigs;
}
public String getName() {
if (name == null) {
return name = "step" + (int)(Math.random()*100);
}
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isStop() {
return stop;
}
public void setStop(boolean stop) {
this.stop = stop;
}
public Map<String, Object> getDataMapping() {
return dataMapping;
}
public void setDataMapping(Map<String, Object> dataMapping) {
this.dataMapping = dataMapping;
}
}

View File

@@ -0,0 +1,549 @@
/*
* Copyright (C) 2020 the original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.wehotel.fizz;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.wehotel.constants.CommonConstants;
/**
*
* @author linwaiwai
* @author francis
*
* @param <K>
* @param <V>
*/
@SuppressWarnings("unchecked")
public class StepContext<K, V> extends ConcurrentHashMap<K, V> {
public static final String ELAPSED_TIMES = "elapsedTimes";
public static final String DEBUG = "debug";
public static final String RETURN_CONTEXT = "returnContext";
public void setDebug(Boolean debug) {
this.put((K)DEBUG, (V)debug);
}
public String getTraceId() {
return (String) this.get(CommonConstants.TRACE_ID);
}
public void setTraceId(String traceId) {
this.put((K)CommonConstants.TRACE_ID, (V)traceId);
}
/**
* 是否调试模式
* @return
*/
public Boolean isDebug() {
return this.get(DEBUG) == null ? false : (Boolean) this.get(DEBUG);
}
/**
* 是否在响应体里返回上下文
* @return
*/
public boolean returnContext() {
return Boolean.valueOf((String)getInputReqHeader(RETURN_CONTEXT));
}
public synchronized void addElapsedTime(String actionName, Long milliSeconds) {
List<Map<String, Long>> elapsedTimes = (List<Map<String, Long>>) this.get(ELAPSED_TIMES);
if (elapsedTimes == null) {
elapsedTimes = new ArrayList<Map<String, Long>>();
this.put((K) ELAPSED_TIMES, (V) elapsedTimes);
}
Map<String, Long> record = new HashMap<>();
record.put(actionName, milliSeconds);
elapsedTimes.add(record);
}
public V getElapsedTimes() {
return this.get(ELAPSED_TIMES);
}
private Map<String, Object> getStepRequest(String stepName, String requestName) {
StepResponse stepResponse = (StepResponse) this.get(stepName);
if (stepResponse == null) {
return null;
}
Map<String, Map<String, Object>> requests = (Map<String, Map<String, Object>>) stepResponse.getRequests();
if (requests == null) {
requests = new HashMap<>();
stepResponse.setRequests(requests);
requests.put(requestName, new HashMap<String, Object>());
}
return (Map<String, Object>) requests.get(requestName);
}
/**
* 设置Step里调用接口的请求头
*
* @param stepName
* @param requestName
* @param headerName
* @param headerValue
*/
public void setStepReqHeader(String stepName, String requestName, String headerName, Object headerValue) {
Map<String, Object> request = getStepRequest(stepName, requestName);
if (request == null) {
return;
}
Map<String, Object> req = (Map<String, Object>) request.get("request");
if (req == null) {
req = new HashMap<>();
request.put("request", req);
}
Map<String, Object> headers = (Map<String, Object>) req.get("headers");
if (headers == null) {
headers = new HashMap<>();
req.put("headers", headers);
}
headers.put(headerName, headerValue);
}
/**
* 获取Step里调用接口的请求头
*
* @param stepName
* @param requestName
* @param headerName
*/
public Object getStepReqHeader(String stepName, String requestName, String headerName) {
Map<String, Object> request = getStepRequest(stepName, requestName);
if (request == null) {
return null;
}
Map<String, Object> req = (Map<String, Object>) request.get("request");
if (req == null) {
return null;
}
Map<String, Object> headers = (Map<String, Object>) req.get("headers");
if (headers == null) {
return null;
}
return headers.get(headerName);
}
/**
* 设置Step里调用接口的请求body
*
* @param stepName
* @param requestName
* @param key
* @param value
*/
public void setStepReqBody(String stepName, String requestName, String key, Object value) {
Map<String, Object> request = getStepRequest(stepName, requestName);
if (request == null) {
return;
}
Map<String, Object> req = (Map<String, Object>) request.get("request");
if (req == null) {
req = new HashMap<>();
request.put("request", req);
}
Map<String, Object> body = (Map<String, Object>) req.get("body");
if (body == null) {
body = new HashMap<>();
req.put("body", body);
}
body.put(key, value);
}
/**
* 设置Step里调用接口的请求body
*
* @param stepName
* @param requestName
* @param key
*/
public Object getStepReqBody(String stepName, String requestName, String key) {
Map<String, Object> request = getStepRequest(stepName, requestName);
if (request == null) {
return null;
}
Map<String, Object> req = (Map<String, Object>) request.get("request");
if (req == null) {
req = new HashMap<>();
request.put("request", req);
}
Map<String, Object> body = (Map<String, Object>) req.get("body");
if (body == null) {
return null;
}
return body.get(key);
}
/**
* 设置Step里调用接口的请求body
*
* @param stepName
* @param requestName
*/
public Object getStepReqBody(String stepName, String requestName) {
Map<String, Object> request = getStepRequest(stepName, requestName);
if (request == null) {
return null;
}
Map<String, Object> req = (Map<String, Object>) request.get("request");
if (req == null) {
req = new HashMap<>();
request.put("request", req);
}
return req.get("body");
}
/**
* 设置Step里调用接口响应头
*
* @param stepName
* @param requestName
* @param headerName
* @param headerValue
*/
public void setStepRespHeader(String stepName, String requestName, String headerName, Object headerValue) {
Map<String, Object> request = getStepRequest(stepName, requestName);
if (request == null) {
return;
}
Map<String, Object> response = (Map<String, Object>) request.get("response");
if (response == null) {
response = new HashMap<>();
request.put("response", response);
}
Map<String, Object> headers = (Map<String, Object>) response.get("headers");
if (headers == null) {
headers = new HashMap<>();
response.put("headers", headers);
}
headers.put(headerName, headerValue);
}
/**
* 获取Step里调用接口响应头
*
* @param stepName
* @param requestName
* @param headerName
*/
public Object getStepRespHeader(String stepName, String requestName, String headerName) {
Map<String, Object> request = getStepRequest(stepName, requestName);
if (request == null) {
return null;
}
Map<String, Object> response = (Map<String, Object>) request.get("response");
if (response == null) {
return null;
}
Map<String, Object> headers = (Map<String, Object>) response.get("headers");
if (headers == null) {
return null;
}
return headers.get(headerName);
}
/**
* 设置Step里调用接口的响应body
*
* @param stepName
* @param requestName
* @param key
* @param value
*/
public void setStepRespBody(String stepName, String requestName, String key, Object value) {
Map<String, Object> request = getStepRequest(stepName, requestName);
if (request == null) {
return;
}
Map<String, Object> response = (Map<String, Object>) request.get("response");
if (response == null) {
response = new HashMap<>();
request.put("response", response);
}
Map<String, Object> body = (Map<String, Object>) response.get("body");
if (body == null) {
body = new HashMap<>();
response.put("body", body);
}
body.put(key, value);
}
/**
* 获取Step里调用接口的响应body
*
* @param stepName
* @param requestName
* @param key
*/
public Object getStepRespBody(String stepName, String requestName, String key) {
Map<String, Object> request = getStepRequest(stepName, requestName);
if (request == null) {
return null;
}
Map<String, Object> response = (Map<String, Object>) request.get("response");
if (response == null) {
return null;
}
Map<String, Object> body = (Map<String, Object>) response.get("body");
if (body == null) {
return null;
}
return body.get(key);
}
/**
* 获取Step里调用接口的响应body
*
* @param stepName
* @param requestName
*/
public Object getStepRespBody(String stepName, String requestName) {
Map<String, Object> request = getStepRequest(stepName, requestName);
if (request == null) {
return null;
}
Map<String, Object> response = (Map<String, Object>) request.get("response");
if (response == null) {
return null;
}
return response.get("body");
}
/**
* 设置Step的结果
*
* @param stepName
* @param key
* @param value
*/
public void setStepResult(String stepName, String key, Object value) {
StepResponse stepResponse = (StepResponse) this.get(stepName);
if (stepResponse == null) {
return;
}
Map<String, Object> result = (Map<String, Object>) stepResponse.getResult();
if (result == null) {
result = new HashMap<>();
stepResponse.setResult(result);
}
result.put(key, value);
}
/**
* 获取Step的结果
*
* @param stepName
* @param key
*/
public Object getStepResult(String stepName, String key) {
StepResponse stepResponse = (StepResponse) this.get(stepName);
if (stepResponse == null) {
return null;
}
Map<String, Object> result = (Map<String, Object>) stepResponse.getResult();
if (result == null) {
return null;
}
return result.get(key);
}
/**
* 获取Step的结果
*
* @param stepName
*/
public Object getStepResult(String stepName) {
StepResponse stepResponse = (StepResponse) this.get(stepName);
if (stepResponse == null) {
return null;
}
return stepResponse.getResult();
}
/**
* 设置聚合接口的响应头
*
* @param headerName
* @param headerValue
*/
public void setInputRespHeader(String headerName, Object headerValue) {
Map<String, Object> input = (Map<String, Object>) this.get("input");
if (input == null) {
return;
}
Map<String, Object> response = (Map<String, Object>) input.get("response");
if (response == null) {
return;
}
Map<String, Object> headers = (Map<String, Object>) response.get("headers");
if (headers == null) {
headers = new HashMap<>();
response.put("headers", headers);
}
headers.put(headerName, headerValue);
}
/**
* 获取聚合接口的响应头
*
* @param headerName
*/
public Object getInputRespHeader(String headerName) {
Map<String, Object> input = (Map<String, Object>) this.get("input");
if (input == null) {
return null;
}
Map<String, Object> response = (Map<String, Object>) input.get("response");
if (response == null) {
return null;
}
Map<String, Object> headers = (Map<String, Object>) response.get("headers");
if (headers == null) {
return null;
}
return headers.get(headerName);
}
/**
* 获取聚合接口的请求头
*
* @param headerName
*/
public Object getInputReqHeader(String headerName) {
Map<String, Object> input = (Map<String, Object>) this.get("input");
if (input == null) {
return null;
}
Map<String, Object> request = (Map<String, Object>) input.get("request");
if (request == null) {
return null;
}
Map<String, Object> headers = (Map<String, Object>) request.get("headers");
if (headers == null) {
return null;
}
return headers.get(headerName);
}
/**
* 设置聚合接口的响应body
*
* @param key
* @param value
*/
public void setInputRespBody(String key, Object value) {
Map<String, Object> input = (Map<String, Object>) this.get("input");
if (input == null) {
return;
}
Map<String, Object> response = (Map<String, Object>) input.get("response");
if (response == null) {
response = new HashMap<>();
input.put("response", response);
}
Map<String, Object> body = (Map<String, Object>) response.get("body");
if (body == null) {
body = new HashMap<>();
response.put("body", body);
}
body.put(key, value);
}
/**
* 获取聚合接口的响应body
*
* @param key
*/
public Object getInputRespBody(String key) {
Map<String, Object> input = (Map<String, Object>) this.get("input");
if (input == null) {
return null;
}
Map<String, Object> response = (Map<String, Object>) input.get("response");
if (response == null) {
return null;
}
Map<String, Object> body = (Map<String, Object>) response.get("body");
if (body == null) {
return null;
}
return body.get(key);
}
/**
* 获取聚合接口的响应body
*
*/
public Object getInputRespBody() {
Map<String, Object> input = (Map<String, Object>) this.get("input");
if (input == null) {
return null;
}
Map<String, Object> response = (Map<String, Object>) input.get("response");
if (response == null) {
return null;
}
return response.get("body");
}
/**
* 获取聚合接口的请求body
*
* @param key
*/
public Object getInputReqBody(String key) {
Map<String, Object> body = (Map<String, Object>) getInputReqAttr("body");
if (body == null) {
return null;
}
return body.get(key);
}
/**
* 获取聚合接口的请求body
*
*/
public Object getInputReqBody() {
return getInputReqAttr("body");
}
/**
* 获取聚合接口请求属性<br/>
* 可选属性path,method,headers,params,body
*
*/
public Object getInputReqAttr(String key) {
Map<String, Object> input = (Map<String, Object>) this.get("input");
if (input == null) {
return null;
}
Map<String, Object> request = (Map<String, Object>) input.get("request");
if (request == null) {
return null;
}
return request.get(key);
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright (C) 2020 the original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.wehotel.fizz;
import java.util.HashMap;
import java.util.Map;
/**
* @author linwaiwai
*/
public class StepResponse {
private String stepName;
private Map<String, Map<String, Object>> requests;
private Map result;
private boolean stop;
public StepResponse(Step aStep, HashMap item, Map<String, Map<String, Object>> requests) {
setStepName(aStep.getName());
setResult(item);
setRequests(requests);
}
public StepResponse(Step aStep, HashMap item) {
setStepName(aStep.getName());
setResult(item);
}
public boolean isStop() {
return stop;
}
public void setStop(boolean stop) {
this.stop = stop;
}
public String getStepName() {
return stepName;
}
public void setStepName(String stepName) {
this.stepName = stepName;
}
public Map<String, Map<String, Object>> getRequests() {
return requests;
}
public void setRequests(Map<String, Map<String, Object>> requests) {
this.requests = requests;
}
public Map getResult() {
return result;
}
public void setResult(Map result) {
this.result = result;
}
}

View File

@@ -0,0 +1,160 @@
/*
* Copyright (C) 2020 the original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.wehotel.fizz.input;
import java.util.HashMap;
import java.util.Map;
/**
*
* @author linwaiwai
* @author francis
*
*/
public class ClientInputConfig extends InputConfig {
private boolean debug;
private String path;
private String method;
private Map<String, Object> headers = new HashMap<String, Object>();
private Map<String, Object> langDef;
private Map<String, Object> bodyDef;
private Map<String, Object> headersDef;
private Map<String, Object> paramsDef;
private Map<String, Object> scriptValidate;
private Map<String, Object> validateResponse;
@SuppressWarnings("unchecked")
public ClientInputConfig(Map configBody) {
if(configBody.get("debug") != null) {
this.debug = (boolean) configBody.get("debug");
}
this.path = (String) configBody.get("path");
if (configBody.get("headers") != null) {
setHeaders((Map) configBody.get("headers"));
}
if (configBody.get("method") != null) {
setMethod((String) configBody.get("method"));
} else {
setMethod("GET");
}
if (configBody.get("langDef") != null) {
langDef = ((Map) configBody.get("langDef"));
}
if (configBody.get("bodyDef") != null) {
bodyDef = ((Map) configBody.get("bodyDef"));
}
if (configBody.get("paramsDef") != null) {
paramsDef = ((Map) configBody.get("paramsDef"));
}
if (configBody.get("headersDef") != null) {
headersDef = ((Map) configBody.get("headersDef"));
}
if (configBody.get("scriptValidate") != null) {
scriptValidate = ((Map) configBody.get("scriptValidate"));
}
if (configBody.get("validateResponse") != null) {
validateResponse = ((Map) configBody.get("validateResponse"));
}
}
public ClientInputConfig() {
}
public boolean isDebug() {
return debug;
}
public void setDebug(boolean debug) {
this.debug = debug;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public Map<String, Object> getLangDef() {
return langDef;
}
public void setLangDef(Map<String, Object> langDef) {
this.langDef = langDef;
}
public Map<String, Object> getHeaders() {
return headers;
}
public void setHeaders(Map<String, Object> headers) {
this.headers = headers;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public Map<String, Object> getBodyDef() {
return bodyDef;
}
public void setBodyDef(Map<String, Object> bodyDef) {
this.bodyDef = bodyDef;
}
public Map<String, Object> getHeadersDef() {
return headersDef;
}
public void setHeadersDef(Map<String, Object> headersDef) {
this.headersDef = headersDef;
}
public Map<String, Object> getParamsDef() {
return paramsDef;
}
public void setParamsDef(Map<String, Object> paramsDef) {
this.paramsDef = paramsDef;
}
public Map<String, Object> getScriptValidate() {
return scriptValidate;
}
public void setScriptValidate(Map<String, Object> scriptValidate) {
this.scriptValidate = scriptValidate;
}
public Map<String, Object> getValidateResponse() {
return validateResponse;
}
public void setValidateResponse(Map<String, Object> validateResponse) {
this.validateResponse = validateResponse;
}
}

View File

@@ -0,0 +1,83 @@
/*
* Copyright (C) 2020 the original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.wehotel.fizz.input;
import java.util.HashMap;
import java.util.Map;
import com.wehotel.fizz.StepContext;
import com.wehotel.fizz.StepResponse;
import reactor.core.publisher.Mono;
/**
*
* @author linwaiwai
*
*/
public class Input {
protected String name;
protected InputConfig config;
protected InputContext inputContext;
protected StepResponse lastStepResponse = null;
protected StepResponse stepResponse;
public void setConfig(InputConfig inputConfig) {
config = inputConfig;
}
public InputConfig getConfig() {
return config;
}
public void beforeRun(InputContext context) {
this.inputContext = context;
}
public String getName() {
if (name == null) {
return name = "input" + (int)(Math.random()*100);
}
return name;
}
/**
* 检查该Input是否需要运行默认都运行
* @stepContext Step上下文
* @return TRUE运行
*/
public boolean needRun(StepContext<String, Object> stepContext) {
return Boolean.TRUE;
}
public Mono<Map> run() {
return null;
}
public void setName(String configName) {
this.name = configName;
}
public StepResponse getStepResponse() {
return stepResponse;
}
public void setStepResponse(StepResponse stepResponse) {
this.stepResponse = stepResponse;
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright (C) 2020 the original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.wehotel.fizz.input;
import java.util.Map;
/**
*
* @author linwaiwai
*
*/
public class InputConfig {
private InputType type;
protected Map<String, Object> dataMapping;
public InputType getType() {
return type;
}
public void setType(InputType typeEnum) {
this.type = typeEnum;
}
public Map<String, Object> getDataMapping() {
return dataMapping;
}
public void setDataMapping(Map<String, Object> dataMapping) {
this.dataMapping = dataMapping;
}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright (C) 2020 the original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.wehotel.fizz.input;
import java.util.HashMap;
import java.util.Map;
import com.wehotel.fizz.StepContext;
import com.wehotel.fizz.StepResponse;
/**
*
* @author linwaiwai
*
*/
public class InputContext {
private StepContext<String, Object> stepContext;
private StepResponse lastStepResponse = null;
public InputContext(StepContext<String, Object> stepContext2, StepResponse lastStepResponse2) {
this.stepContext = stepContext2;
this.lastStepResponse = lastStepResponse2;
}
public StepContext<String, Object> getStepContext() {
return stepContext;
}
public void setStepContext(StepContext<String, Object> stepContext) {
this.stepContext = stepContext;
}
public StepResponse getLastStepResponse() {
return lastStepResponse;
}
public void setLastStepResponse(StepResponse lastStepResponse) {
this.lastStepResponse = lastStepResponse;
}
// public Map<String, Object> getResponses() {
// //TODO:
// if (stepContext != null) {
// Map<String, Object> responses = new HashMap<String, Object>();
// for( String key :stepContext.keySet()) {
// StepResponse stepResponse = (StepResponse)stepContext.get(key);
// responses.put(key, stepResponse.getResponse());
// }
// return responses;
// } else {
// return null;
// }
//
//
//
// }
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright (C) 2020 the original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.wehotel.fizz.input;
import java.util.Map;
/**
*
* @author linwaiwai
*
*/
public class InputFactory {
public static InputConfig createInputConfig(Map config) {
String type = (String) config.get("type");
InputType typeEnum = InputType.valueOf(type.toUpperCase());
InputConfig inputConfig = null;
switch(typeEnum) {
case REQUEST:
inputConfig = new RequestInputConfig(config);
break;
case MYSQL:
inputConfig = new MySQLInputConfig(config);
break;
}
inputConfig.setType(typeEnum);
inputConfig.setDataMapping((Map<String, Object>) config.get("dataMapping"));
return inputConfig;
}
public static Input createInput(String type) {
InputType typeEnum = InputType.valueOf(type.toUpperCase());
Input input = null;
switch(typeEnum) {
case REQUEST:
input = new RequestInput();
break;
case MYSQL:
input = new MySQLInput();
break;
}
return input;
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (C) 2020 the original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.wehotel.fizz.input;
/**
*
* @author linwaiwai
*
*/
public enum InputType {
REQUEST("REQUEST"),
MYSQL("MYSQL");
private final String type;
private InputType(String aType) {
this.type = aType;
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2020 the original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.wehotel.fizz.input;
/**
*
* @author linwaiwai
*
*/
public class MySQLInput extends Input {
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright (C) 2020 the original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.wehotel.fizz.input;
import java.util.Map;
/**
*
* @author linwaiwai
*
*/
public class MySQLInputConfig extends InputConfig {
public MySQLInputConfig(Map configBody) {
// TODO Auto-generated constructor stub
}
}

View File

@@ -0,0 +1,315 @@
/*
* Copyright (C) 2020 the original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.wehotel.fizz.input;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import org.noear.snack.ONode;
import org.springframework.util.CollectionUtils;
import com.wehotel.constants.CommonConstants;
import com.wehotel.fizz.StepContext;
/**
*
* @author francis
*
*/
public class PathMapping {
public static ONode toONode(Object obj) {
ONode o = null;
synchronized (obj) {
o = ONode.loadObj(obj);
}
return o;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public static void setByPath(ONode target, String path, Object obj) {
if (CommonConstants.WILDCARD_STAR.equals(path)) {
if (obj instanceof ONode) {
ONode node = (ONode) obj;
if(node.isObject()) {
target.setAll(node);
}
} else if (obj instanceof Map) {
target.setAll((Map) obj);
}
} else {
String[] keys = path.split("\\.");
ONode cur = target;
for (int i = 0; i < keys.length - 1; i++) {
cur = cur.get(keys[i]);
}
if ((obj instanceof ONode && ((ONode) obj).isArray()) || obj instanceof Collection ||
(obj instanceof ONode && ((ONode) obj).isObject()) || obj instanceof Map) {
if (cur == null) {
target.get(keys[keys.length - 1]).fill(obj);
} else {
cur.get(keys[keys.length - 1]).fill(obj);
}
} else {
if (cur == null) {
target.set(keys[keys.length - 1], obj);
} else {
cur.set(keys[keys.length - 1], obj);
}
}
}
}
public static Map<String, Object> transformToMap(ONode ctxNode, Map<String, Object> rules) {
ONode target = transform(ctxNode, rules);
return target.toObject(Map.class);
}
public static ONode transform(ONode ctxNode, Map<String, Object> rules) {
ONode target = ONode.load(new HashMap());
if (rules.isEmpty()) {
return target;
}
Map<String, String> rs = new HashMap<>();
Map<String, String> types = new HashMap<>();
for (Entry<String, Object> entry : rules.entrySet()) {
if (entry.getValue() instanceof String) {
String val = (String) entry.getValue();
String[] vals = val.split(" ");
if (vals.length > 1) {
rs.put(entry.getKey(), vals[1]);
types.put(entry.getKey(), vals[0]);
} else {
rs.put(entry.getKey(), val);
}
}
}
if (rs.isEmpty()) {
return target;
}
// wildcard star entry
Object starValObj = null;
String starEntryKey = null;
for (Entry<String, String> entry : rs.entrySet()) {
ONode val = ctxNode.select("$." + handlePath(entry.getValue()));
Object obj = val;
if (val != null && types.containsKey(entry.getKey())) {
switch (types.get(entry.getKey())) {
case "Integer":
case "int": {
obj = val.val().isNull() ? null : val.val().getInt();
break;
}
case "Boolean":
case "boolean": {
obj = val.val().isNull() ? null : val.val().getBoolean();
break;
}
case "Float":
case "float": {
obj = val.val().isNull() ? null : val.val().getFloat();
break;
}
case "Double":
case "double": {
obj = val.val().isNull() ? null : val.val().getDouble();
break;
}
case "String": {
obj = val.val().isNull() ? null : val.val().getString();
break;
}
case "Long":
case "long": {
obj = val.val().isNull() ? null : val.val().getLong();
break;
}
}
}
if (CommonConstants.WILDCARD_STAR.equals(entry.getKey())) {
starValObj = obj;
starEntryKey = entry.getKey();
}else {
setByPath(target, entry.getKey(), obj);
}
}
if(starEntryKey != null) {
setByPath(target, starEntryKey, starValObj);
}
return target;
}
public static Map<String, Object> getScriptRules(Map<String, Object> rules) {
if (rules.isEmpty()) {
return new HashMap<>();
}
Map<String, Object> rs = new HashMap<>();
for (Entry<String, Object> entry : rules.entrySet()) {
if (!(entry.getValue() instanceof String)) {
rs.put(entry.getKey(), entry.getValue());
}
}
return rs;
}
/**
* 把Path转为context里的实际路径<br/>
* 步骤兼容以下几种写法,把后几种转换为第一种标准路径<br/>
*
* 例子1<br/>
* step1.requests.request1.request.headers<br/>
* step1.request1.request.headers<br/>
* step1.request1.requestHeaders<br/>
* step1.requests.request1.requestHeaders<br/>
*
* 例子2<br/>
* step1.requests.request1.response.body<br/>
* step1.request1.response.body<br/>
* step1.request1.responseBody<br/>
* step1.requests.request1.responseBody<br/>
*
* input兼容以下写法把第二种转换为第一种标准路径<br/>
*
* 例子1<br/>
* input.request.headers<br/>
* input.requestHeaders<br/>
*
* 例子2<br/>
* input.response.body<br/>
* input.responseBody<br/>
*
* @param path
* @return
*/
public static String handlePath(String path) {
if(path.startsWith("step")) {
String[] arr = path.split("\\.");
List<String> list = Arrays.stream(arr).collect(Collectors.toList());
// 补齐 requests
if(list.size() >= 2 && !"requests".equals(list.get(1))) {
list.add(1,"requests");
}
// 拆分一级为两级requestBody -> request.body
if(list.size() >= 4) {
String s = list.get(3);
switch (s) {
case "requestHeaders":
list.set(3, "headers");
list.add(3, "request");
break;
case "requestParams":
list.set(3, "params");
list.add(3, "request");
break;
case "requestBody":
list.set(3, "body");
list.add(3, "request");
break;
case "responseHeaders":
list.set(3, "headers");
list.add(3, "response");
break;
case "responseBody":
list.set(3, "body");
list.add(3, "response");
break;
}
}
return String.join(".", list);
}else if(path.startsWith("input")) {
String[] arr = path.split("\\.");
List<String> list = Arrays.stream(arr).collect(Collectors.toList());
// 拆分一级为两级requestBody -> request.body
if(list.size() >= 2) {
String s = list.get(1);
switch (s) {
case "requestHeaders":
list.set(1, "headers");
list.add(1, "request");
break;
case "requestParams":
list.set(1, "params");
list.add(1, "request");
break;
case "requestBody":
list.set(1, "body");
list.add(1, "request");
break;
case "responseHeaders":
list.set(1, "headers");
list.add(1, "response");
break;
case "responseBody":
list.set(1, "body");
list.add(1, "response");
break;
}
}
return String.join(".", list);
}else {
return path;
}
}
/**
* 数据转换
*
* @param ctxNode
* @param stepContext
* @param fixed optional
* @param mappingRules optional
* @return
*/
public static Map<String, Object> transform(ONode ctxNode, StepContext<String, Object> stepContext,
Map<String, Object> fixed, Map<String, Object> mappingRules) {
Map<String, Object> result = new HashMap<>();
if (fixed != null) {
result.putAll((Map<String, Object>) fixed);
}
if (mappingRules != null) {
// 路径映射
ONode target = PathMapping.transform(ctxNode, mappingRules);
// 脚本转换
Map<String, Object> scriptRules = PathMapping.getScriptRules(mappingRules);
Map<String, Object> scriptResult = ScriptHelper.executeScripts(target, scriptRules, ctxNode, stepContext);
if (scriptResult != null && !scriptResult.isEmpty()) {
result.putAll(scriptResult);
}
}
return result;
}
}

View File

@@ -0,0 +1,334 @@
/*
* Copyright (C) 2020 the original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.wehotel.fizz.input;
import java.util.HashMap;
import java.util.Map;
import javax.script.ScriptException;
import org.noear.snack.ONode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import com.alibaba.fastjson.JSON;
import com.wehotel.flume.clients.log4j2appender.LogService;
import com.wehotel.FizzGatewayApplication;
import com.wehotel.constants.CommonConstants;
import com.wehotel.fizz.StepContext;
import com.wehotel.fizz.StepResponse;
import com.wehotel.proxy.FizzWebClient;
import com.wehotel.util.MapUtil;
import reactor.core.publisher.Mono;
/**
*
* @author linwaiwai
* @author francis
*
*/
@SuppressWarnings("unchecked")
public class RequestInput extends Input {
private static final Logger LOGGER = LoggerFactory.getLogger(RequestInput.class);
private InputType type;
protected Map<String, Object> dataMapping;
protected Map<String, Object> request = new HashMap<>();
protected Map<String, Object> response = new HashMap<>();
private static final String FALLBACK_MODE_STOP = "stop";
private static final String FALLBACK_MODE_CONTINUE = "continue";
public InputType getType() {
return type;
}
public void setType(InputType typeEnum) {
this.type = typeEnum;
}
public Map<String, Object> getDataMapping() {
return dataMapping;
}
public void setDataMapping(Map<String, Object> dataMapping) {
this.dataMapping = dataMapping;
}
private void doRequestMapping(InputConfig aConfig, InputContext inputContext) {
RequestInputConfig config = (RequestInputConfig) aConfig;
// 把请求信息放入stepContext
Map<String, Object> group = new HashMap<>();
group.put("request", request);
group.put("response", response);
this.stepResponse.getRequests().put(name, group);
HttpMethod method = HttpMethod.valueOf(config.getMethod().toUpperCase());
request.put("method", method);
Map<String, Object> params = new HashMap<>();
params.putAll(MapUtil.toHashMap(config.getQueryParams()));
request.put("params", params);
// 数据转换
if (inputContext != null && inputContext.getStepContext() != null) {
StepContext<String, Object> stepContext = inputContext.getStepContext();
Map<String, Object> dataMapping = this.getConfig().getDataMapping();
if (dataMapping != null) {
Map<String, Object> requestMapping = (Map<String, Object>) dataMapping.get("request");
if (requestMapping != null && !StringUtils.isEmpty(requestMapping)) {
ONode ctxNode = PathMapping.toONode(stepContext);
// headers
request.put("headers",
PathMapping.transform(ctxNode, stepContext,
(Map<String, Object>) requestMapping.get("fixedHeaders"),
(Map<String, Object>) requestMapping.get("headers")));
// params
params.putAll(PathMapping.transform(ctxNode, stepContext,
(Map<String, Object>) requestMapping.get("fixedParams"),
(Map<String, Object>) requestMapping.get("params")));
request.put("params", params);
// body
Map<String,Object> body = PathMapping.transform(ctxNode, stepContext,
(Map<String, Object>) requestMapping.get("fixedBody"),
(Map<String, Object>) requestMapping.get("body"));
// script
if (requestMapping.get("script") != null) {
Map<String, Object> scriptCfg = (Map<String, Object>) requestMapping.get("script");
try {
Object reqBody = ScriptHelper.execute(scriptCfg, ctxNode, stepContext);
if (reqBody != null) {
body.putAll((Map<String, Object>) reqBody);
}
} catch (ScriptException e) {
LogService.setBizId(inputContext.getStepContext().getTraceId());
LOGGER.warn("execute script failed, {}", e);
throw new RuntimeException("execute script failed");
}
}
request.put("body", body);
}
}
}
UriComponents uriComponents = UriComponentsBuilder.fromUriString(config.getBaseUrl() + config.getPath())
.queryParams(MapUtil.toMultiValueMap(params)).build();
request.put("url", uriComponents.toUriString());
}
private void doResponseMapping(InputConfig aConfig, InputContext inputContext, String responseBody) {
RequestInputConfig config = (RequestInputConfig) aConfig;
response.put("body", JSON.parse(responseBody));
// 数据转换
if (inputContext != null && inputContext.getStepContext() != null) {
StepContext<String, Object> stepContext = inputContext.getStepContext();
Map<String, Object> dataMapping = this.getConfig().getDataMapping();
if (dataMapping != null) {
Map<String, Object> responseMapping = (Map<String, Object>) dataMapping.get("response");
if (responseMapping != null && !StringUtils.isEmpty(responseMapping)) {
ONode ctxNode = PathMapping.toONode(stepContext);
// headers
Map<String, Object> fixedHeaders = (Map<String, Object>) responseMapping.get("fixedHeaders");
Map<String, Object> headerMapping = (Map<String, Object>) responseMapping.get("headers");
if ((fixedHeaders != null && !fixedHeaders.isEmpty())
|| (headerMapping != null && !headerMapping.isEmpty())) {
Map<String, Object> headers = new HashMap<>();
headers.putAll(PathMapping.transform(ctxNode, stepContext, fixedHeaders, headerMapping));
response.put("headers", headers);
}
// body
Map<String, Object> fixedBody = (Map<String, Object>) responseMapping.get("fixedBody");
Map<String, Object> bodyMapping = (Map<String, Object>) responseMapping.get("body");
Map<String, Object> scriptCfg = (Map<String, Object>) responseMapping.get("script");
if ((fixedBody != null && !fixedBody.isEmpty()) || (bodyMapping != null && !bodyMapping.isEmpty())
|| (scriptCfg != null && scriptCfg.get("type") != null
&& scriptCfg.get("source") != null)) {
// body
Map<String, Object> body = new HashMap<>();
body.putAll(PathMapping.transform(ctxNode, stepContext, fixedBody, bodyMapping));
// script
if (scriptCfg != null && scriptCfg.get("type") != null && scriptCfg.get("source") != null) {
try {
Object respBody = ScriptHelper.execute(scriptCfg, ctxNode, stepContext);
if (respBody != null) {
body.putAll((Map<String, Object>) respBody);
}
} catch (ScriptException e) {
LogService.setBizId(inputContext.getStepContext().getTraceId());
LOGGER.warn("execute script failed, {}", e);
throw new RuntimeException("execute script failed");
}
}
response.put("body", body);
}
}
} else {
response.put("body", JSON.parse(responseBody));
}
}
}
private Mono<ClientResponse> getClientSpecFromContext(InputConfig aConfig, InputContext inputContext) {
RequestInputConfig config = (RequestInputConfig) aConfig;
int timeout = config.getTimeout() < 1 ? 3000 : config.getTimeout() > 10000 ? 10000 : config.getTimeout();
HttpMethod method = HttpMethod.valueOf(config.getMethod());
String url = (String) request.get("url");
Map<String, Object> headers = (Map<String, Object>) request.get("headers");
if (headers == null) {
headers = new HashMap<>();
}
if (!headers.containsKey("Content-Type")) {
// defalut content-type
headers.put("Content-Type", "application/json; charset=UTF-8");
}
headers.put(CommonConstants.HEADER_TRACE_ID, inputContext.getStepContext().getTraceId());
HttpMethod aggrMethod = HttpMethod.valueOf(inputContext.getStepContext().getInputReqAttr("method").toString());
String aggrPath = (String)inputContext.getStepContext().getInputReqAttr("path");
String aggrService = aggrPath.split("\\/")[2];
FizzWebClient client = FizzGatewayApplication.appContext.getBean(FizzWebClient.class);
return client.aggrSend(aggrService, aggrMethod, aggrPath, null, method, url,
MapUtil.toHttpHeaders(headers), request.get("body"), (long)timeout);
}
private Map<String, Object> getResponses(Map<String, StepResponse> stepContext2) {
// TODO Auto-generated method stub
return null;
}
@Override
@SuppressWarnings("unchecked")
public boolean needRun(StepContext<String, Object> stepContext) {
Map<String, Object> condition = ((RequestInputConfig) config).getCondition();
if (CollectionUtils.isEmpty(condition)) {
// 没有配置condition直接运行
return Boolean.TRUE;
}
ONode ctxNode = PathMapping.toONode(stepContext);
try {
Boolean needRun = ScriptHelper.execute(condition, ctxNode, stepContext, Boolean.class);
return needRun != null ? needRun : Boolean.TRUE;
} catch (ScriptException e) {
LogService.setBizId(inputContext.getStepContext().getTraceId());
LOGGER.warn("execute script failed", e);
throw new RuntimeException("execute script failed");
}
}
@Override
public Mono<Map> run() {
long t1 = System.currentTimeMillis();
this.doRequestMapping(config, inputContext);
inputContext.getStepContext().addElapsedTime(stepResponse.getStepName() + "-" + this.name + "-RequestMapping",
System.currentTimeMillis() - t1);
String prefix = stepResponse.getStepName() + "-" + "调用接口";
long start = System.currentTimeMillis();
Mono<ClientResponse> clientResponse = this.getClientSpecFromContext(config, inputContext);
Mono<String> body = clientResponse.flatMap(cr->{
return Mono.just(cr).doOnError(throwable -> cleanup(cr));
}).doOnSuccess(cr -> {
long elapsedMillis = System.currentTimeMillis() - start;
HttpHeaders httpHeaders = cr.headers().asHttpHeaders();
Map<String, Object> headers = new HashMap<>();
httpHeaders.forEach((key, value) -> {
if (value.size() > 1) {
headers.put(key, value);
} else {
headers.put(key, httpHeaders.getFirst(key));
}
});
headers.put("elapsedTime", elapsedMillis + "ms");
this.response.put("headers", headers);
inputContext.getStepContext().addElapsedTime(prefix + request.get("url"),
elapsedMillis);
}).flatMap(cr -> cr.bodyToMono(String.class)).doOnSuccess(resp -> {
long elapsedMillis = System.currentTimeMillis() - start;
if(inputContext.getStepContext().isDebug()) {
LogService.setBizId(inputContext.getStepContext().getTraceId());
LOGGER.info("{} 耗时:{}ms URL={}, reqHeader={} req={} resp={}", prefix, elapsedMillis, request.get("url"),
JSON.toJSONString(this.request.get("headers")),
JSON.toJSONString(this.request.get("body")), resp);
}
}).doOnError(ex -> {
LogService.setBizId(inputContext.getStepContext().getTraceId());
LOGGER.warn("failed to call {}", request.get("url"), ex);
long elapsedMillis = System.currentTimeMillis() - start;
inputContext.getStepContext().addElapsedTime(
stepResponse.getStepName() + "-" + "调用接口 failed " + request.get("url"), elapsedMillis);
});
// fallback handler
RequestInputConfig reqConfig = (RequestInputConfig) config;
if (reqConfig.getFallback() != null) {
Map<String, String> fallback = reqConfig.getFallback();
String mode = fallback.get("mode");
if (FALLBACK_MODE_STOP.equals(mode)) {
body = body.onErrorStop();
} else if (FALLBACK_MODE_CONTINUE.equals(mode)) {
body = body.onErrorResume(ex -> {
return Mono.just(fallback.get("defaultResult"));
});
} else {
body = body.onErrorStop();
}
}
return body.flatMap(item -> {
Map<String, Object> result = new HashMap<String, Object>();
result.put("data", item);
result.put("request", this);
long t3 = System.currentTimeMillis();
this.doResponseMapping(config, inputContext, item);
inputContext.getStepContext().addElapsedTime(
stepResponse.getStepName() + "-" + this.name + "-ResponseMapping", System.currentTimeMillis() - t3);
return Mono.just(result);
});
}
private void cleanup(ClientResponse clientResponse) {
if (clientResponse != null) {
clientResponse.bodyToMono(Void.class).subscribe();
}
}
}

View File

@@ -0,0 +1,129 @@
/*
* Copyright (C) 2020 the original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.wehotel.fizz.input;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriComponentsBuilder;
/**
*
* @author linwaiwai
* @author francis
*
*/
public class RequestInputConfig extends InputConfig{
private URL url ;
private String method ;
private int timeout = 3;
private Map<String,String> fallback = new HashMap<String, String>();
private Map<String, Object> condition;
public RequestInputConfig(Map configBody) {
String url = (String) configBody.get("url");
if(StringUtils.isEmpty(url)) {
throw new RuntimeException("Request URL can not be blank");
}
setUrl(url);
if (configBody.get("method") != null) {
setMethod(((String)configBody.get("method")).toUpperCase());
} else {
setMethod("GET");
}
if (configBody.get("timeout") != null) {
timeout = Integer.valueOf(configBody.get("timeout").toString());
}
if (configBody.get("fallback") != null) {
fallback = (Map<String,String>)configBody.get("fallback");
}
if (configBody.get("condition") != null) {
setCondition((Map)configBody.get("condition"));
}
}
public String getQueryStr(){
return url.getQuery();
}
public MultiValueMap<String, String> getQueryParams(){
MultiValueMap<String, String> parameters =
UriComponentsBuilder.fromUriString(url.toString()).build().getQueryParams();
return parameters;
}
public String getBaseUrl() {
return url.getProtocol()+ "://"+ url.getHost() + (url.getPort() == -1 ? "" : ":" + url.getPort());
}
public String getPath() {
return url.getPath();
}
public void setUrl(String string) {
try {
url = new URL(string);
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public int getTimeout() {
return timeout;
}
public void setTimeout(int timeout) {
this.timeout = timeout;
}
public Map<String, String> getFallback() {
return fallback;
}
public void setFallback(Map<String, String> fallback) {
this.fallback = fallback;
}
public Map<String, Object> getCondition() {
return condition;
}
public void setCondition(Map<String, Object> condition) {
this.condition = condition;
}
}

View File

@@ -0,0 +1,151 @@
/*
* Copyright (C) 2020 the original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.wehotel.fizz.input;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import javax.script.ScriptException;
import org.noear.snack.ONode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON;
import com.ctrip.framework.apollo.core.utils.StringUtils;
import com.wehotel.constants.CommonConstants;
import com.wehotel.exception.StopAndResponseException;
import com.wehotel.fizz.StepContext;
import com.wehotel.util.Script;
import com.wehotel.util.ScriptUtils;
/**
*
* @author francis
*
*/
public class ScriptHelper {
private static final Logger LOGGER = LoggerFactory.getLogger(ScriptHelper.class);
public static Object execute(Map<String, Object> scriptCfg, ONode ctxNode, StepContext<String, Object> stepContext)
throws ScriptException {
return execute(scriptCfg, ctxNode, stepContext, Object.class);
}
@SuppressWarnings("unchecked")
public static <T> T execute(Map<String, Object> scriptCfg, ONode ctxNode, StepContext<String, Object> stepContext, Class<T> clazz)
throws ScriptException {
Script script = new Script();
script.setType((String) scriptCfg.get("type"));
script.setSource((String) scriptCfg.get("source"));
if (StringUtils.isBlank(script.getType()) || StringUtils.isBlank(script.getSource())) {
return null;
}
Map<String, Object> ctx = new HashMap<>();
ctx.put("context", stepContext);
Object rs = ScriptUtils.execute(script, ctx);
if (ScriptUtils.GROOVY.equals(script.getType())) {
return (T) handleStopResponse(stepContext, rs);
} else if (ScriptUtils.JAVA_SCRIPT.equals(script.getType())) {
if(rs != null) {
if(rs instanceof Collection || rs instanceof Map) {
return (T) rs;
}else {
String json = rs.toString();
if(json.startsWith("[") && json.endsWith("]")) {
return JSON.parseArray(json).toJavaObject(clazz);
}else if(json.startsWith("{") && json.endsWith("}")) {
if(clazz.isAssignableFrom(Map.class)) {
return (T)handleStopResponse(stepContext, JSON.parseObject(json).toJavaObject(clazz));
}else {
handleStopResponse(stepContext, JSON.parseObject(json).toJavaObject(Map.class));
return JSON.parseObject(json).toJavaObject(clazz);
}
}
}
return (T) rs;
}
return null;
} else {
return (T) rs;
}
}
public static Map<String, Object> executeScripts(ONode target, Map<String, Object> scriptRules, ONode ctxNode,
StepContext<String, Object> stepContext) {
return executeScripts(target, scriptRules, ctxNode, stepContext, Object.class);
}
@SuppressWarnings("unchecked")
public static <T> Map<String, T> executeScripts(ONode target, Map<String, Object> scriptRules, ONode ctxNode,
StepContext<String, Object> stepContext, Class<T> clazz) {
if(target == null) {
target = ONode.load(new HashMap());
}
if (scriptRules != null && !scriptRules.isEmpty()) {
// wildcard star entry
Object starValObj = null;
String starEntryKey = null;
for (Entry<String, Object> entry : scriptRules.entrySet()) {
Map<String, Object> scriptCfg = (Map<String, Object>) entry.getValue();
try {
if (CommonConstants.WILDCARD_STAR.equals(entry.getKey())) {
starValObj = execute(scriptCfg, ctxNode, stepContext, clazz);
starEntryKey = entry.getKey();
}else {
PathMapping.setByPath(target, entry.getKey(), execute(scriptCfg, ctxNode, stepContext, clazz));
}
} catch (ScriptException e) {
LOGGER.warn("execute script failed, {}", e);
throw new RuntimeException("execute script failed");
}
}
if(starEntryKey != null) {
PathMapping.setByPath(target, starEntryKey, starValObj);
}
}
return target.toObject(Map.class);
}
public static Object handleStopResponse(StepContext<String, Object> stepContext, Object result) {
if(result instanceof Map) {
Map<String, Object> rs = (Map<String, Object>) result;
if (rs.containsKey(CommonConstants.STOP_AND_RESPONSE_KEY)) {
if (rs.get(CommonConstants.STOP_AND_RESPONSE_KEY) != null
&& rs.get(CommonConstants.STOP_AND_RESPONSE_KEY) instanceof Boolean
&& (Boolean) rs.get(CommonConstants.STOP_AND_RESPONSE_KEY)) {
rs.remove(CommonConstants.STOP_AND_RESPONSE_KEY);
// 测试模式返回StepContext
if (stepContext.returnContext()) {
rs.put("_context", stepContext);
}
throw new StopAndResponseException("stop and response", JSON.toJSONString(rs));
} else {
rs.remove(CommonConstants.STOP_AND_RESPONSE_KEY);
}
}
}
return result;
}
}