Files
fizz-gateway-node/src/main/java/we/fizz/ConfigLoader.java
Francis Dong 38a60fc00e format code
2020-12-08 12:35:58 +08:00

416 lines
12 KiB
Java

/*
* 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 we.fizz;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.nacos.api.config.annotation.NacosValue;
import we.config.AppConfigProperties;
import we.fizz.input.ClientInputConfig;
import we.fizz.input.Input;
import we.fizz.input.InputType;
import org.apache.commons.io.FileUtils;
import org.noear.snack.ONode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.PostConstruct;
import javax.annotation.Resource;
import static we.listener.AggregateRedisConfig.AGGREGATE_REACTIVE_REDIS_TEMPLATE;
import static we.util.Constants.Symbol.FORWARD_SLASH;
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;
/**
*
* @author francis
* @author zhongjie
*
*/
@Component
public class ConfigLoader {
private static final Logger LOGGER = LoggerFactory.getLogger(ConfigLoader.class);
/**
* 聚合配置存放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;
@NacosValue(value = "${fizz.aggregate.read-local-config-flag:false}", autoRefreshed = true)
@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 (clientInputConfig.getPath().startsWith(TEST_PATH_PREFIX)) {
// always enable debug for testing
clientInputConfig.setDebug(true);
} else {
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"));
}
}
}
@PostConstruct
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();
LOGGER.debug("add aggregation config, key={} config={}", resourceKey, configStr);
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)) {
LOGGER.debug("delete aggregation config: {}", 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;
}
}
}