diff --git a/fizz-bootstrap/src/main/resources/application.yml b/fizz-bootstrap/src/main/resources/application.yml index 7c74337..1ceccdc 100644 --- a/fizz-bootstrap/src/main/resources/application.yml +++ b/fizz-bootstrap/src/main/resources/application.yml @@ -104,18 +104,28 @@ refresh-local-cache: rpc-service-enabled: true fizz: + aggregate: writeMapNullValue: false + error: response: - http-status.enable: true # default - code-field: "msgCode" # default - message-field: "message" # default + http-status.enable: true + code-field: "msgCode" + message-field: "message" + + api.pairing: + enable: false + web-server.port: 8601 + request: + timeliness: 300 # default 300 sec + timeout: 0 # default no timeout + retry-count: 0 # default no retry + retry-interval: 0 # default no retry interval fizz-trace-id: - header: X-Trace-Id # default + header: X-Trace-Id value-strategy: requestId # default, or can be uuid - value-prefix: fizz # default + value-prefix: fizz cors: true # CORS switch, default true - diff --git a/fizz-common/src/main/java/we/util/ThreadContext.java b/fizz-common/src/main/java/we/util/ThreadContext.java index 79ddc7a..aa9b368 100644 --- a/fizz-common/src/main/java/we/util/ThreadContext.java +++ b/fizz-common/src/main/java/we/util/ThreadContext.java @@ -34,22 +34,27 @@ public abstract class ThreadContext { private static final int mapCap = 32; private static final String sb = "$sb"; - public static final String sb0 = "$sb0"; - private static final int sbCap = 256; - private static final String arrayListT = "arlstT"; - + private static final String arrayList = "arlstT"; public static final String arrayList0 = "arlst0T"; + private static final String hashMap = "hsMapT"; + private static final String hashSet = "hsSetT"; - private static final String hashMapT = "hsMapT"; - - private static final String hashSetT = "hsSetT"; + private static final String traId = "traIdT"; private ThreadContext() { } + public static void setTraceId(String traceId) { + set(traId, traceId); + } + + public String getTraceId() { + return (String) get(traId); + } + /** use me carefully! */ public static StringBuilder getStringBuilder() { return getStringBuilder(true); @@ -128,7 +133,7 @@ public abstract class ThreadContext { } public static ArrayList getArrayList() { - return getArrayList(arrayListT, true); + return getArrayList(arrayList, true); } public static ArrayList getArrayList(String key) { @@ -147,7 +152,7 @@ public abstract class ThreadContext { } public static HashMap getHashMap() { - return getHashMap(hashMapT, true); + return getHashMap(hashMap, true); } public static HashMap getHashMap(String key) { @@ -166,7 +171,7 @@ public abstract class ThreadContext { } public static HashSet getHashSet() { - return getHashSet(hashSetT, true); + return getHashSet(hashSet, true); } public static HashSet getHashSet(String key) { diff --git a/fizz-core/src/main/java/we/Fizz.java b/fizz-core/src/main/java/we/Fizz.java index 0b8d0da..733adf2 100644 --- a/fizz-core/src/main/java/we/Fizz.java +++ b/fizz-core/src/main/java/we/Fizz.java @@ -25,5 +25,5 @@ import org.springframework.context.ConfigurableApplicationContext; public class Fizz { - public static ConfigurableApplicationContext context; + public static ConfigurableApplicationContext context; // TODO: rename to CONTEXT } diff --git a/fizz-core/src/main/java/we/api/pairing/Api.java b/fizz-core/src/main/java/we/api/pairing/Api.java new file mode 100644 index 0000000..42bc3cb --- /dev/null +++ b/fizz-core/src/main/java/we/api/pairing/Api.java @@ -0,0 +1,52 @@ +/* + * 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 . + */ + +package we.api.pairing; + +import org.springframework.http.HttpMethod; +import we.plugin.auth.ApiConfig; +import we.util.JacksonUtils; + +/** + * @author hongqiaowei + */ + +public class Api { + + public Object method; + + public String path; + + public void setMethod(String m) { + method = HttpMethod.resolve(m); + if (method == null) { + method = ApiConfig.ALL_METHOD; + } + } + + public boolean methodMatch(HttpMethod m) { + if (method == ApiConfig.ALL_METHOD) { + return true; + } + return method.equals(m); + } + + @Override + public String toString() { + return JacksonUtils.writeValueAsString(this); + } +} diff --git a/fizz-core/src/main/java/we/api/pairing/FizzApiPairingController.java b/fizz-core/src/main/java/we/api/pairing/ApiPairingController.java similarity index 68% rename from fizz-core/src/main/java/we/api/pairing/FizzApiPairingController.java rename to fizz-core/src/main/java/we/api/pairing/ApiPairingController.java index a67630c..9ccc08a 100644 --- a/fizz-core/src/main/java/we/api/pairing/FizzApiPairingController.java +++ b/fizz-core/src/main/java/we/api/pairing/ApiPairingController.java @@ -19,7 +19,6 @@ package we.api.pairing; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -31,14 +30,20 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import we.config.SystemConfig; +import we.flume.clients.log4j2appender.LogService; import we.plugin.auth.App; import we.plugin.auth.AppService; import we.util.DateTimeUtils; +import we.util.JacksonUtils; +import we.util.ThreadContext; import we.util.WebUtils; import javax.annotation.Resource; import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; /** * @author hongqiaowei @@ -46,18 +51,18 @@ import java.util.List; @RestController @RequestMapping(SystemConfig.DEFAULT_GATEWAY_PREFIX + "/_fizz-pairing") -public class FizzApiPairingController { +public class ApiPairingController { - private static final Logger log = LoggerFactory.getLogger(FizzApiPairingController.class); + private static final Logger log = LoggerFactory.getLogger(ApiPairingController.class); @Resource - private SystemConfig systemConfig; + private SystemConfig systemConfig; @Resource - private AppService appService; + private AppService appService; - @Value("${fizz.api.pairing.request.timeliness:300}") - private int timeliness = 300; // unit: sec + @Resource + private ApiPairingDocSetService apiPairingDocSetService; @GetMapping("/pair") public Mono pair(ServerWebExchange exchange) { @@ -84,6 +89,7 @@ public class FizzApiPairingController { try { long ts = Long.parseLong(timestamp); LocalDateTime now = LocalDateTime.now(); + long timeliness = systemConfig.fizzApiPairingRequestTimeliness(); long start = DateTimeUtils.toMillis(now.minusSeconds(timeliness)); long end = DateTimeUtils.toMillis(now.plusSeconds (timeliness)); if (start <= ts && ts <= end) { @@ -100,17 +106,36 @@ public class FizzApiPairingController { return WebUtils.buildDirectResponse(response, null, null, "请求未签名"); } - boolean equals = PairingUtils.checkSign(appId, timestamp, app.secretkey, sign); - + boolean equals = ApiPairingUtils.checkSign(appId, timestamp, app.secretkey, sign); if (equals) { - // TODO: 响应文档集 - return Mono.empty(); + List docs = getAppDocSet(appId); + String docsJson = JacksonUtils.writeValueAsString(docs); + return response.writeWith(Mono.just(response.bufferFactory().wrap(docsJson.getBytes()))); } else { - log.warn("request authority: app {}, timestamp {}, sign {} invalid", appId, timestamp, sign); + log.warn("{}request authority: app {}, timestamp {}, sign {} invalid", + exchange.getLogPrefix(), appId, timestamp, sign, LogService.BIZ_ID, WebUtils.getTraceId(exchange)); return WebUtils.buildDirectResponse(response, null, null, "请求签名无效"); } } + private List getAppDocSet(String appId) { + Map docSetMap = apiPairingDocSetService.getDocSetMap(); + ArrayList result = ThreadContext.getArrayList(); + for (Map.Entry entry : docSetMap.entrySet()) { + ApiPairingDocSet ds = entry.getValue(); + AppApiPairingDocSet appDocSet = new AppApiPairingDocSet(); + appDocSet.id = ds.id; + appDocSet.name = ds.name; + appDocSet.services = ds.docs.stream().map(d -> d.service).collect(Collectors.toSet()); + appDocSet.enabled = false; + if (ds.appIds.contains(appId)) { + appDocSet.enabled = true; + } + result.add(appDocSet); + } + return result; + } + private String getSign(HttpHeaders headers) { List signHdrs = systemConfig.getSignHeaders(); for (int i = 0; i < signHdrs.size(); i++) { diff --git a/fizz-core/src/main/java/we/api/pairing/ApiPairingDoc.java b/fizz-core/src/main/java/we/api/pairing/ApiPairingDoc.java new file mode 100644 index 0000000..c05a753 --- /dev/null +++ b/fizz-core/src/main/java/we/api/pairing/ApiPairingDoc.java @@ -0,0 +1,39 @@ +/* + * 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 . + */ + +package we.api.pairing; + +import we.util.JacksonUtils; + +import java.util.Collections; +import java.util.List; + +/** + * @author hongqiaowei + */ + +public class ApiPairingDoc { + + public String service; + + public List apis = Collections.emptyList(); + + @Override + public String toString() { + return JacksonUtils.writeValueAsString(this); + } +} diff --git a/fizz-core/src/main/java/we/api/pairing/ApiPairingDocSet.java b/fizz-core/src/main/java/we/api/pairing/ApiPairingDocSet.java new file mode 100644 index 0000000..3085754 --- /dev/null +++ b/fizz-core/src/main/java/we/api/pairing/ApiPairingDocSet.java @@ -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 . + */ + +package we.api.pairing; + +import we.util.JacksonUtils; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +/** + * @author hongqiaowei + */ + +public class ApiPairingDocSet { + + public static final int DELETED = 1; + + public int isDeleted = 0; + + public int id; // 文档集 id + + public String name; + + public List docs = Collections.emptyList(); + + public Set appIds = Collections.emptySet(); + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ApiPairingDocSet that = (ApiPairingDocSet) o; + return id == that.id; + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + + @Override + public String toString() { + return JacksonUtils.writeValueAsString(this); + } +} diff --git a/fizz-core/src/main/java/we/api/pairing/ApiPairingDocSetService.java b/fizz-core/src/main/java/we/api/pairing/ApiPairingDocSetService.java new file mode 100644 index 0000000..af6c63e --- /dev/null +++ b/fizz-core/src/main/java/we/api/pairing/ApiPairingDocSetService.java @@ -0,0 +1,272 @@ +/* + * 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 . + */ + +package we.api.pairing; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.redis.core.ReactiveStringRedisTemplate; +import org.springframework.http.HttpMethod; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import we.Fizz; +import we.config.AggregateRedisConfig; +import we.plugin.auth.ApiConfig; +import we.util.JacksonUtils; +import we.util.ReactiveResult; +import we.util.Result; + +import javax.annotation.PostConstruct; +import javax.annotation.Resource; +import java.util.*; + +/** + * @author hongqiaowei + */ + +@Service +public class ApiPairingDocSetService { + + private static final Logger log = LoggerFactory.getLogger(ApiPairingDocSetService.class); + + private Map docSetMap = new HashMap<>(64); + + private Map> appDocSetMap = new HashMap<>(64); + + private Map> serviceExistsInDocSetMap = new HashMap<>(64); + + private Map>> pathMethodExistsInDocSetMap = new HashMap<>(64); + + @Resource(name = AggregateRedisConfig.AGGREGATE_REACTIVE_REDIS_TEMPLATE) + private ReactiveStringRedisTemplate rt; + + @PostConstruct + public void init() throws Throwable { + Result result = initApiPairingDocSet(); + if (result.code == Result.FAIL) { + throw new RuntimeException(result.msg, result.t); + } + result = lsnApiPairingDocSetChange(); + if (result.code == Result.FAIL) { + throw new RuntimeException(result.msg, result.t); + } + } + + private Result initApiPairingDocSet() { + Result result = Result.succ(); + Flux> resources = rt.opsForHash().entries("fizz_api_pairing_doc"); + resources.collectList() + .defaultIfEmpty(Collections.emptyList()) + .flatMap( + es -> { + if (Fizz.context != null) { + String json = null; + try { + for (Map.Entry e : es) { + json = (String) e.getValue(); + ApiPairingDocSet docSet = JacksonUtils.readValue(json, ApiPairingDocSet.class); + updateDocSetDataStruct(docSet); + } + } catch (Throwable t) { + result.code = Result.FAIL; + result.msg = "init api pairing doc set error, doc set: " + json; + result.t = t; + } + } + return Mono.empty(); + } + ) + .onErrorReturn( + throwable -> { + result.code = Result.FAIL; + result.msg = "init api pairing doc set error"; + result.t = throwable; + return true; + }, + result + ) + .block(); + return result; + } + + private Result lsnApiPairingDocSetChange() { + Result result = Result.succ(); + String channel = "fizz_api_pairing_doc_channel"; + rt.listenToChannel(channel) + .doOnError( + t -> { + result.code = ReactiveResult.FAIL; + result.msg = "lsn error, channel: " + channel; + result.t = t; + log.error("lsn channel {} error", channel, t); + } + ) + .doOnSubscribe( + s -> { + log.info("success to lsn on {}", channel); + } + ) + .doOnNext( + msg -> { + if (Fizz.context != null) { + String message = msg.getMessage(); + try { + ApiPairingDocSet docSet = JacksonUtils.readValue(message, ApiPairingDocSet.class); + updateDocSetDataStruct(docSet); + } catch (Throwable t) { + log.error("update api pairing doc set error, {}", message, t); + } + } + } + ) + .subscribe(); + return result; + } + + private void updateDocSetDataStruct(ApiPairingDocSet docSet) { + if (docSet.isDeleted == ApiPairingDocSet.DELETED) { + docSetMap.remove(docSet.id); + + for (String appId : docSet.appIds) { + Set dss = appDocSetMap.get(appId); + if (dss != null) { + dss.remove(docSet); + if (dss.isEmpty()) { + appDocSetMap.remove(appId); + } + } + } + + for (ApiPairingDoc doc : docSet.docs) { + Set dss = serviceExistsInDocSetMap.get(doc.service); + if (dss != null) { + dss.remove(docSet); + if (dss.isEmpty()) { + serviceExistsInDocSetMap.remove(doc.service); + } + } + for (Api api : doc.apis) { + Map> methodDocSetMap = pathMethodExistsInDocSetMap.get(api.path); + if (methodDocSetMap != null) { + dss = methodDocSetMap.get(api.method); + if (dss != null) { + dss.remove(docSet); + if (dss.isEmpty()) { + methodDocSetMap.remove(api.method); + if (methodDocSetMap.isEmpty()) { + pathMethodExistsInDocSetMap.remove(api.path); + } + } + } + } + } + } + + log.info("delete doc set: {}", docSet); + + } else { + docSetMap.put(docSet.id, docSet); + docSet.appIds.forEach( + appId -> { + Set dss = appDocSetMap.get(appId); + if (dss == null) { + dss = new HashSet<>(); + appDocSetMap.put(appId, dss); + } + dss.add(docSet); + } + ); + docSet.docs.forEach( + doc -> { + Set dss = serviceExistsInDocSetMap.get(doc.service); + if (dss == null) { + dss = new HashSet<>(); + serviceExistsInDocSetMap.put(doc.service, dss); + } + dss.add(docSet); + for (Api api : doc.apis) { + Map> methodDocSetMap = pathMethodExistsInDocSetMap.get(api.path); + if (methodDocSetMap == null) { + methodDocSetMap = new HashMap<>(8); + pathMethodExistsInDocSetMap.put(api.path, methodDocSetMap); + } + dss = methodDocSetMap.get(api.method); + if (dss == null) { + dss = new HashSet<>(); + methodDocSetMap.put(api.method, dss); + } + dss.add(docSet); + } + } + ); + log.info("update doc set: {}", docSet); + } + } + + public Map getDocSetMap() { + return docSetMap; + } + + public ApiPairingDocSet get(int id) { + return docSetMap.get(id); + } + + public Map> getAppDocSetMap() { + return appDocSetMap; + } + + public Map> getServiceExistsInDocSetMap() { + return serviceExistsInDocSetMap; + } + + public Map>> getPathMethodExistsInDocSetMap() { + return pathMethodExistsInDocSetMap; + } + + public boolean existsDocSetMatch(String appId, HttpMethod method, String service, String path) { + Set appDocSets = appDocSetMap.get(appId); + if (appDocSets == null) { + return false; + } + Set serviceDocSets = serviceExistsInDocSetMap.get(service); + if (serviceDocSets == null) { + return false; + } + Map> methodDocSetMap = pathMethodExistsInDocSetMap.get(path); + if (methodDocSetMap == null) { + return false; + } + Set pathMethodDocSets = methodDocSetMap.get(method); + if (pathMethodDocSets == null) { + pathMethodDocSets = methodDocSetMap.get(ApiConfig.ALL_METHOD); + if (pathMethodDocSets == null) { + return false; + } + } + Set s = new HashSet<>(appDocSets); + s.retainAll(serviceDocSets); + if (s.isEmpty()) { + return false; + } + s.retainAll(pathMethodDocSets); + if (s.isEmpty()) { + return false; + } + return true; + } +} diff --git a/fizz-core/src/main/java/we/api/pairing/ApiPairingInfo.java b/fizz-core/src/main/java/we/api/pairing/ApiPairingInfo.java new file mode 100644 index 0000000..b13bf25 --- /dev/null +++ b/fizz-core/src/main/java/we/api/pairing/ApiPairingInfo.java @@ -0,0 +1,49 @@ +/* + * 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 . + */ + +package we.api.pairing; + +import we.util.JacksonUtils; + +import java.util.Collections; +import java.util.List; + +/** + * @author hongqiaowei + */ + +public class ApiPairingInfo { + + public static final int DELETED = 1; + + public int isDeleted = 0; + + public String id; // uuid + + public String url; + + public String appId; + + public String secretKey; + + public List services = Collections.emptyList(); + + @Override + public String toString() { + return JacksonUtils.writeValueAsString(this); + } +} diff --git a/fizz-core/src/main/java/we/api/pairing/ApiPairingInfoService.java b/fizz-core/src/main/java/we/api/pairing/ApiPairingInfoService.java new file mode 100644 index 0000000..0d0f2c4 --- /dev/null +++ b/fizz-core/src/main/java/we/api/pairing/ApiPairingInfoService.java @@ -0,0 +1,155 @@ +/* + * 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 . + */ + +package we.api.pairing; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.redis.core.ReactiveStringRedisTemplate; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import we.Fizz; +import we.config.AggregateRedisConfig; +import we.util.JacksonUtils; +import we.util.ReactiveResult; +import we.util.Result; + +import javax.annotation.PostConstruct; +import javax.annotation.Resource; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * @author hongqiaowei + */ + +@Service +public class ApiPairingInfoService { + + private static final Logger log = LoggerFactory.getLogger(ApiPairingInfoService.class); + + private Map serviceApiPairingInfoMap = new HashMap<>(64); + + @Resource(name = AggregateRedisConfig.AGGREGATE_REACTIVE_REDIS_TEMPLATE) + private ReactiveStringRedisTemplate rt; + + @PostConstruct + public void init() throws Throwable { + Result result = initApiPairingInfo(); + if (result.code == Result.FAIL) { + throw new RuntimeException(result.msg, result.t); + } + result = lsnApiPairingInfoChange(); + if (result.code == Result.FAIL) { + throw new RuntimeException(result.msg, result.t); + } + } + + private Result initApiPairingInfo() { + Result result = Result.succ(); + Flux> resources = rt.opsForHash().entries("fizz_api_pairing_info"); + resources.collectList() + .defaultIfEmpty(Collections.emptyList()) + .flatMap( + es -> { + if (Fizz.context != null) { + String json = null; + try { + for (Map.Entry e : es) { + json = (String) e.getValue(); + ApiPairingInfo info = JacksonUtils.readValue(json, ApiPairingInfo.class); + for (String service : info.services) { + serviceApiPairingInfoMap.put(service, info); + } + log.info("init api pairing info: {}", info); + } + } catch (Throwable t) { + result.code = Result.FAIL; + result.msg = "init api pairing info error, info: " + json; + result.t = t; + } + } + return Mono.empty(); + } + ) + .onErrorReturn( + throwable -> { + result.code = Result.FAIL; + result.msg = "init api pairing info error"; + result.t = throwable; + return true; + }, + result + ) + .block(); + return result; + } + + private Result lsnApiPairingInfoChange() { + Result result = Result.succ(); + String channel = "fizz_api_pairing_info_channel"; + rt.listenToChannel(channel) + .doOnError( + t -> { + result.code = ReactiveResult.FAIL; + result.msg = "lsn error, channel: " + channel; + result.t = t; + log.error("lsn channel {} error", channel, t); + } + ) + .doOnSubscribe( + s -> { + log.info("success to lsn on {}", channel); + } + ) + .doOnNext( + msg -> { + if (Fizz.context != null) { + String message = msg.getMessage(); + try { + ApiPairingInfo info = JacksonUtils.readValue(message, ApiPairingInfo.class); + if (info.isDeleted == ApiPairingDocSet.DELETED) { + for (String service : info.services) { + serviceApiPairingInfoMap.remove(service); + } + log.info("remove api pairing info: {}", info); + } else { + for (String service : info.services) { + serviceApiPairingInfoMap.put(service, info); + } + log.info("update api pairing info: {}", info); + } + } catch (Throwable t) { + log.error("update api pairing info error, {}", message, t); + } + } + } + ) + .subscribe(); + return result; + } + + public Map getServiceApiPairingInfoMap() { + return serviceApiPairingInfoMap; + } + + public ApiPairingInfo get(String service) { + return serviceApiPairingInfoMap.get(service); + } +} diff --git a/fizz-core/src/main/java/we/api/pairing/PairingUtils.java b/fizz-core/src/main/java/we/api/pairing/ApiPairingUtils.java similarity index 93% rename from fizz-core/src/main/java/we/api/pairing/PairingUtils.java rename to fizz-core/src/main/java/we/api/pairing/ApiPairingUtils.java index 190636d..d767854 100644 --- a/fizz-core/src/main/java/we/api/pairing/PairingUtils.java +++ b/fizz-core/src/main/java/we/api/pairing/ApiPairingUtils.java @@ -24,9 +24,9 @@ import we.util.ThreadContext; * @author hongqiaowei */ -public abstract class PairingUtils extends org.apache.commons.codec.digest.DigestUtils { +public abstract class ApiPairingUtils extends org.apache.commons.codec.digest.DigestUtils { - private PairingUtils() { + private ApiPairingUtils() { } public static String sign(String app, String timestamp, String secretKey) { diff --git a/fizz-core/src/main/java/we/api/pairing/AppApiPairingDocSet.java b/fizz-core/src/main/java/we/api/pairing/AppApiPairingDocSet.java new file mode 100644 index 0000000..cd03524 --- /dev/null +++ b/fizz-core/src/main/java/we/api/pairing/AppApiPairingDocSet.java @@ -0,0 +1,43 @@ +/* + * 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 . + */ + +package we.api.pairing; + +import we.util.JacksonUtils; + +import java.util.Collections; +import java.util.Set; + +/** + * @author hongqiaowei + */ + +public class AppApiPairingDocSet { + + public int id; // 文档集 id + + public String name; + + public Set services; + + public boolean enabled; + + @Override + public String toString() { + return JacksonUtils.writeValueAsString(this); + } +} diff --git a/fizz-core/src/main/java/we/api/pairing/FizzApiPairingHttpHandler.java b/fizz-core/src/main/java/we/api/pairing/FizzApiPairingHttpHandler.java index 9281373..c90af9d 100644 --- a/fizz-core/src/main/java/we/api/pairing/FizzApiPairingHttpHandler.java +++ b/fizz-core/src/main/java/we/api/pairing/FizzApiPairingHttpHandler.java @@ -19,6 +19,7 @@ package we.api.pairing; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.NestedExceptionUtils; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; @@ -26,20 +27,22 @@ import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.http.server.reactive.HttpHandler; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; -import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; +import org.springframework.web.reactive.function.BodyExtractors; +import org.springframework.web.reactive.function.client.ClientResponse; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.adapter.DefaultServerWebExchange; import org.springframework.web.server.adapter.ForwardedHeaderTransformer; import org.springframework.web.server.i18n.LocaleContextResolver; import org.springframework.web.server.session.WebSessionManager; import reactor.core.publisher.Mono; -import we.Fizz; -import we.plugin.auth.ApiConfigService; -import we.spring.web.server.ext.FizzServerWebExchangeDecorator; -import we.util.JacksonUtils; +import we.config.SystemConfig; +import we.proxy.FizzWebClient; +import we.util.Consts; +import we.util.ThreadContext; import we.util.WebUtils; +import java.net.URI; import java.util.Arrays; import java.util.HashSet; import java.util.Set; @@ -48,7 +51,7 @@ import java.util.Set; * @author hongqiaowei */ -public class FizzApiPairingHttpHandler implements HttpHandler { +class FizzApiPairingHttpHandler implements HttpHandler { private static final String disconnected_client_log_category = "DisconnectedClient"; @@ -64,12 +67,21 @@ public class FizzApiPairingHttpHandler implements HttpHandler { private ForwardedHeaderTransformer forwardedHeaderTransformer; private boolean enableLoggingRequestDetails = false; - public FizzApiPairingHttpHandler(WebSessionManager sessionManager, ServerCodecConfigurer codecConfigurer, + private SystemConfig systemConfig; + private FizzWebClient fizzWebClient; + private ApiPairingInfoService apiPairingInfoService; + + public FizzApiPairingHttpHandler(ConfigurableApplicationContext applicationContext, WebSessionManager sessionManager, ServerCodecConfigurer codecConfigurer, LocaleContextResolver localeContextResolver, ForwardedHeaderTransformer forwardedHeaderTransformer) { + this.sessionManager = sessionManager; this.serverCodecConfigurer = codecConfigurer; this.localeContextResolver = localeContextResolver; this.forwardedHeaderTransformer = forwardedHeaderTransformer; + + systemConfig = applicationContext.getBean(SystemConfig.class); + fizzWebClient = applicationContext.getBean(FizzWebClient.class); + apiPairingInfoService = applicationContext.getBean(ApiPairingInfoService.class); } @Override @@ -87,38 +99,81 @@ public class FizzApiPairingHttpHandler implements HttpHandler { } DefaultServerWebExchange exchange = new DefaultServerWebExchange(request, response, sessionManager, serverCodecConfigurer, localeContextResolver); + String logPrefix = exchange.getLogPrefix(); + + URI requestURI = request.getURI(); + String path = requestURI.getPath(); + int secFS = path.indexOf(Consts.S.FORWARD_SLASH, 1); + String service = path.substring(1, secFS); + ApiPairingInfo apiPairingInfo = apiPairingInfoService.get(service); + if (apiPairingInfo == null) { + log.warn("{}{} 服务无配对信息", logPrefix, service); + return WebUtils.buildDirectResponse(response, HttpStatus.FORBIDDEN, null, service + " 服务无配对信息").then(response.setComplete()); + } + + StringBuilder b = ThreadContext.getStringBuilder(); + b.append(apiPairingInfo.url).append(path); + String rawQuery = requestURI.getRawQuery(); + if (StringUtils.hasText(rawQuery)) { + b.append(Consts.S.QUESTION).append(rawQuery); + } + String targetUrl = b.toString(); + String appId = apiPairingInfo.appId; + String secretKey = apiPairingInfo.secretKey; + String timestamp = String.valueOf(System.currentTimeMillis()); + String sign = ApiPairingUtils.sign(appId, timestamp, secretKey); + + HttpHeaders writableHttpHeaders = HttpHeaders.writableHttpHeaders(request.getHeaders()); + writableHttpHeaders.set(SystemConfig.FIZZ_APP_ID, appId); + writableHttpHeaders.set(SystemConfig.FIZZ_TIMESTAMP, timestamp); + writableHttpHeaders.set(SystemConfig.FIZZ_SIGN, sign); + + int requestTimeout = systemConfig.fizzApiPairingRequestTimeout(); + int retryCount = systemConfig.fizzApiPairingRequestRetryCount(); + int retryInterval = systemConfig.fizzApiPairingRequestRetryInterval(); - // XXX - String clientReqPath = WebUtils.getClientReqPath(exchange); - log.info("client request path: {}", clientReqPath); - Mono> formData = exchange.getFormData().defaultIfEmpty(FizzServerWebExchangeDecorator.EMPTY_FORM_DATA).flatMap( - dat -> { - log.info("form data: {}", JacksonUtils.writeValueAsString(dat)); - return Mono.just(dat); - } - ); try { - ApiConfigService apiConfigService = Fizz.context.getBean(ApiConfigService.class); - String apiConfigs = JacksonUtils.writeValueAsString(apiConfigService.serviceConfigMap); - return - formData.then( - response.writeWith(Mono.just(response.bufferFactory().wrap(apiConfigs.getBytes()))) - ) - .doOnSuccess ( v -> logResponse(exchange) ) - .onErrorResume( t -> handleUnresolvedError(exchange, t) ) - .then ( Mono.defer(response::setComplete) ); + Mono remoteResponseMono = fizzWebClient.send( request.getId(), request.getMethod(), targetUrl, writableHttpHeaders, request.getBody(), + requestTimeout, retryCount, retryInterval ); + + Mono respMono = remoteResponseMono.flatMap( + remoteResp -> { + response.setStatusCode(remoteResp.statusCode()); + HttpHeaders respHeaders = response.getHeaders(); + HttpHeaders remoteRespHeaders = remoteResp.headers().asHttpHeaders(); + respHeaders.putAll(remoteRespHeaders); + if (log.isDebugEnabled()) { + StringBuilder sb = ThreadContext.getStringBuilder(); + WebUtils.response2stringBuilder(logPrefix, remoteResp, sb); + log.debug(sb.toString()); + } + return response.writeWith ( remoteResp.body(BodyExtractors.toDataBuffers()) ) + .doOnError ( throwable -> cleanup(remoteResp) ) + .doOnCancel( () -> cleanup(remoteResp) ); + } + ); + + return respMono.doOnSuccess ( v -> logResponse(exchange) ) + .onErrorResume( t -> handleUnresolvedError(exchange, t) ) + .then ( Mono.defer(response::setComplete) ); + } catch (Throwable t) { - // throw t; - log.error(exchange.getLogPrefix() + " 500 Server Error for " + formatRequest(request), t); + log.error(logPrefix + "500 Server Error for " + formatRequest(request), t); response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR); return response.setComplete(); } } + private void cleanup(ClientResponse clientResponse) { + if (clientResponse != null) { + clientResponse.bodyToMono(Void.class).subscribe(); + } + } + private void logResponse(ServerWebExchange exchange) { WebUtils.traceDebug(log, traceOn -> { HttpStatus status = exchange.getResponse().getStatusCode(); - return exchange.getLogPrefix() + " Completed " + (status != null ? status : "200 OK") + (traceOn ? ", headers=" + formatHeaders(exchange.getResponse().getHeaders()) : ""); + return exchange.getLogPrefix() + "Completed " + (status != null ? status : "200 OK") + (traceOn ? ", headers=" + formatHeaders(exchange.getResponse().getHeaders()) : ""); }); } @@ -139,20 +194,20 @@ public class FizzApiPairingHttpHandler implements HttpHandler { String logPrefix = exchange.getLogPrefix(); if (response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR)) { - log.error(logPrefix + " 500 Server Error for " + formatRequest(request), t); + log.error(logPrefix + "500 Server Error for " + formatRequest(request), t); return Mono.empty(); } else if (isDisconnectedClientError(t)) { if (lostClientLog.isTraceEnabled()) { - lostClientLog.trace(logPrefix + " Client went away", t); + lostClientLog.trace(logPrefix + "Client went away", t); } else if (lostClientLog.isDebugEnabled()) { - lostClientLog.debug(logPrefix + " Client went away: " + t + " (stacktrace at TRACE level for '" + disconnected_client_log_category + "')"); + lostClientLog.debug(logPrefix + "Client went away: " + t + " (stacktrace at TRACE level for '" + disconnected_client_log_category + "')"); } return Mono.empty(); } else { // After the response is committed, propagate errors to the server... - log.error(logPrefix + " Error [" + t + "] for " + formatRequest(request) + ", but ServerHttpResponse already committed (" + response.getStatusCode() + ")"); + log.error(logPrefix + "Error [" + t + "] for " + formatRequest(request) + ", but ServerHttpResponse already committed (" + response.getStatusCode() + ")"); return Mono.error(t); } } diff --git a/fizz-core/src/main/java/we/api/pairing/FizzApiPairingWebServer.java b/fizz-core/src/main/java/we/api/pairing/FizzApiPairingWebServer.java index 9bba2a9..c18a4e3 100644 --- a/fizz-core/src/main/java/we/api/pairing/FizzApiPairingWebServer.java +++ b/fizz-core/src/main/java/we/api/pairing/FizzApiPairingWebServer.java @@ -25,6 +25,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration; import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory; import org.springframework.boot.web.server.WebServer; +import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Configuration; import org.springframework.http.server.reactive.HttpHandler; import org.springframework.web.server.adapter.HttpWebHandlerAdapter; @@ -46,9 +47,12 @@ public class FizzApiPairingWebServer { private static final Logger log = LoggerFactory.getLogger(FizzApiPairingWebServer.class); @Resource - private HttpHandler httpHandler; + private ConfigurableApplicationContext applicationContext; - private WebServer server; + @Resource + private HttpHandler httpHandler; + + private WebServer server; @Value("${fizz.api.pairing.web-server.port:8601}") private int port = 8601; @@ -59,6 +63,7 @@ public class FizzApiPairingWebServer { NettyReactiveWebServerFactory factory = new NettyReactiveWebServerFactory(port); server = factory.getWebServer( new FizzApiPairingHttpHandler( + applicationContext, new DefaultWebSessionManager(), adapter.getCodecConfigurer(), adapter.getLocaleContextResolver(), diff --git a/fizz-core/src/main/java/we/config/SystemConfig.java b/fizz-core/src/main/java/we/config/SystemConfig.java index a69fe1b..c23b90e 100644 --- a/fizz-core/src/main/java/we/config/SystemConfig.java +++ b/fizz-core/src/main/java/we/config/SystemConfig.java @@ -28,11 +28,7 @@ import we.util.Consts; import we.util.WebUtils; import javax.annotation.PostConstruct; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -47,26 +43,23 @@ public class SystemConfig { private static final Logger log = LoggerFactory.getLogger(SystemConfig.class); public static final String DEFAULT_GATEWAY_PREFIX = "/proxy"; - public static final String DEFAULT_GATEWAY_TEST_PREFIX = "/_proxytest"; - public static final String DEFAULT_GATEWAY_TEST = "_proxytest"; - public static final String DEFAULT_GATEWAY_TEST_PREFIX0 = "/_proxytest/"; public static boolean FIZZ_ERR_RESP_HTTP_STATUS_ENABLE = true; - public static String FIZZ_ERR_RESP_CODE_FIELD = "msgCode"; - public static String FIZZ_ERR_RESP_MSG_FIELD = "message"; + public static final String FIZZ_APP_ID = "fizz-appid"; + public static final String FIZZ_SIGN = "fizz-sign"; + public static final String FIZZ_TIMESTAMP = "fizz-ts"; + private String gatewayPrefix = DEFAULT_GATEWAY_PREFIX; - private List appHeaders = Stream.of("fizz-appid").collect(Collectors.toList()); - - private List signHeaders = Stream.of("fizz-sign") .collect(Collectors.toList()); - - private List timestampHeaders = Stream.of("fizz-ts") .collect(Collectors.toList()); + private List appHeaders = Stream.of(FIZZ_APP_ID) .collect(Collectors.toList()); + private List signHeaders = Stream.of(FIZZ_SIGN) .collect(Collectors.toList()); + private List timestampHeaders = Stream.of(FIZZ_TIMESTAMP).collect(Collectors.toList()); private List proxySetHeaders = new ArrayList<>(); @@ -99,6 +92,38 @@ public class SystemConfig { FIZZ_ERR_RESP_MSG_FIELD = fizzErrRespMsgField; } + + + @Value("${fizz.api.pairing.request.timeliness:300}") + private int fizzApiPairingRequestTimeliness = 300; // unit: sec + + @Value("${fizz.api.pairing.request.timeout:0}") + private int fizzApiPairingRequestTimeout = 0; // mills + + @Value("${fizz.api.pairing.request.retry-count:0}") + private int fizzApiPairingRequestRetryCount = 0; + + @Value("${fizz.api.pairing.request.retry-interval:0}") + private int fizzApiPairingRequestRetryInterval = 0; // mills + + public int fizzApiPairingRequestTimeout() { + return fizzApiPairingRequestTimeout; + } + + public int fizzApiPairingRequestRetryCount() { + return fizzApiPairingRequestRetryCount; + } + + public int fizzApiPairingRequestRetryInterval() { + return fizzApiPairingRequestRetryInterval; + } + + public int fizzApiPairingRequestTimeliness() { + return fizzApiPairingRequestTimeliness; + } + + + public String fizzTraceIdHeader() { return fizzTraceIdHeader; } @@ -200,8 +225,12 @@ public class SystemConfig { return aggregateTestAuth; } + + // TODO: below to X + + private boolean logResponseBody; private String logHeaders; diff --git a/fizz-core/src/main/java/we/filter/RouteFilter.java b/fizz-core/src/main/java/we/filter/RouteFilter.java index cfc985d..b118fb9 100644 --- a/fizz-core/src/main/java/we/filter/RouteFilter.java +++ b/fizz-core/src/main/java/we/filter/RouteFilter.java @@ -187,9 +187,9 @@ public class RouteFilter extends FizzWebFilter { ); if (log.isDebugEnabled()) { StringBuilder b = ThreadContext.getStringBuilder(); - String rid = WebUtils.getTraceId(exchange); - WebUtils.response2stringBuilder(rid, remoteResp, b); - log.debug(b.toString(), LogService.BIZ_ID, rid); + String traceId = WebUtils.getTraceId(exchange); + WebUtils.response2stringBuilder(traceId, remoteResp, b); + log.debug(b.toString(), LogService.BIZ_ID, traceId); } return clientResp.writeWith(remoteResp.body(BodyExtractors.toDataBuffers())) .doOnError(throwable -> cleanup(remoteResp)).doOnCancel(() -> cleanup(remoteResp)); diff --git a/fizz-core/src/main/java/we/global_resource/GlobalResourceService.java b/fizz-core/src/main/java/we/global_resource/GlobalResourceService.java index 8b664e4..71bdf3c 100644 --- a/fizz-core/src/main/java/we/global_resource/GlobalResourceService.java +++ b/fizz-core/src/main/java/we/global_resource/GlobalResourceService.java @@ -26,6 +26,7 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import we.Fizz; import we.config.AggregateRedisConfig; +import we.config.SystemConfig; import we.fizz.input.PathMapping; import we.util.JacksonUtils; import we.util.ReactiveResult; @@ -58,30 +59,15 @@ public class GlobalResourceService { @PostConstruct public void init() throws Throwable { - initGlobalResource().subscribe( - r -> { - if (r.code == ReactiveResult.SUCC) { - lsnGlobalResourceChange().subscribe( - res -> { - if (res.code == ReactiveResult.FAIL) { - log.error(res.toString()); - if (res.t == null) { - throw Utils.runtimeExceptionWithoutStack("lsn global resource error"); - } - throw new RuntimeException(res.t); - } - updateResNode(); - } - ); - } else { - log.error(r.toString()); - if (r.t == null) { - throw Utils.runtimeExceptionWithoutStack("init global resource error"); - } - throw new RuntimeException(r.t); - } - } - ); + Result result = initGlobalResource(); + if (result.code == Result.FAIL) { + throw new RuntimeException(result.msg, result.t); + } + result = lsnGlobalResourceChange(); + if (result.code == Result.FAIL) { + throw new RuntimeException(result.msg, result.t); + } + updateResNode(); } private void updateResNode() { @@ -89,42 +75,55 @@ public class GlobalResourceService { log.info("global resource node is updated, new keys: {}", objectMap.keySet()); } - private Mono> initGlobalResource() { + private Result initGlobalResource() { + Result result = Result.succ(); Flux> resources = rt.opsForHash().entries("fizz_global_resource"); resources.collectList() .defaultIfEmpty(Collections.emptyList()) .flatMap( es -> { if (Fizz.context != null) { - for (Map.Entry e : es) { - String json = (String) e.getValue(); - GlobalResource r = JacksonUtils.readValue(json, GlobalResource.class); - resourceMap.put(r.key, r); - objectMap.put(r.key, r.originalVal); - log.info("init global resource {}", r.key); + String json = null; + try { + for (Map.Entry e : es) { + json = (String) e.getValue(); + GlobalResource r = JacksonUtils.readValue(json, GlobalResource.class); + resourceMap.put(r.key, r); + objectMap.put(r.key, r.originalVal); + log.info("init global resource {}", r.key); + } + } catch (Throwable t) { + result.code = Result.FAIL; + result.msg = "init global resource error, json: " + json; + result.t = t; } } return Mono.empty(); } ) - .doOnError( - t -> { - log.error("init global resource", t); - } + .onErrorReturn( + throwable -> { + result.code = Result.FAIL; + result.msg = "init global resource error"; + result.t = throwable; + return true; + }, + result ) .block(); - return Mono.just(Result.succ()); + return result; } - private Mono> lsnGlobalResourceChange() { + private Result lsnGlobalResourceChange() { Result result = Result.succ(); String channel = "fizz_global_resource_channel"; rt.listenToChannel(channel) .doOnError( t -> { result.code = ReactiveResult.FAIL; - result.t = t; - log.error("lsn {}", channel, t); + result.msg = "lsn error, channel: " + channel; + result.t = t; + log.error("lsn channel {} error", channel, t); } ) .doOnSubscribe( @@ -155,7 +154,7 @@ public class GlobalResourceService { } ) .subscribe(); - return Mono.just(result); + return result; } public Map getResourceMap() { diff --git a/fizz-core/src/main/java/we/plugin/auth/ApiConfigService.java b/fizz-core/src/main/java/we/plugin/auth/ApiConfigService.java index 6a78b32..4de5894 100644 --- a/fizz-core/src/main/java/we/plugin/auth/ApiConfigService.java +++ b/fizz-core/src/main/java/we/plugin/auth/ApiConfigService.java @@ -86,29 +86,14 @@ public class ApiConfigService { @PostConstruct public void init() throws Throwable { this.init(this::lsnApiConfigChange); - initPlugin().subscribe( - r -> { - if (r.code == ReactiveResult.SUCC) { - lsnPluginConfigChange().subscribe( - res -> { - if (res.code == ReactiveResult.FAIL) { - log.error(res.toString()); - if (res.t == null) { - throw Utils.runtimeExceptionWithoutStack("lsn plugin config error"); - } - throw new RuntimeException(res.t); - } - } - ); - } else { - log.error(r.toString()); - if (r.t == null) { - throw Utils.runtimeExceptionWithoutStack("init plugin error"); - } - throw new RuntimeException(r.t); - } - } - ); + Result result = initPlugin(); + if (result.code == Result.FAIL) { + throw new RuntimeException(result.msg, result.t); + } + result = lsnPluginConfigChange(); + if (result.code == Result.FAIL) { + throw new RuntimeException(result.msg, result.t); + } } // TODO: no need like this @@ -210,7 +195,8 @@ public class ApiConfigService { return Mono.just(ReactorUtils.EMPTY_THROWABLE); } - private Mono> initPlugin() { + private Result initPlugin() { + Result result = Result.succ(); String key = apiConfigServiceProperties.getFizzPluginConfig(); Flux> plugins = rt.opsForHash().entries(key); plugins.collectList() @@ -218,45 +204,57 @@ public class ApiConfigService { .flatMap( es -> { if (Fizz.context != null) { - for (Map.Entry e : es) { - String json = (String) e.getValue(); - HashMap map = JacksonUtils.readValue(json, HashMap.class); - String plugin = (String) map.get("plugin"); - String pluginConfig = (String) map.get("fixedConfig"); - String currentPluginConfig = pluginConfigMap.get(plugin); - if (currentPluginConfig == null || !currentPluginConfig.equals(pluginConfig)) { - if (Fizz.context.containsBean(plugin)) { - FizzPluginFilter pluginFilter = (FizzPluginFilter) Fizz.context.getBean(plugin); - pluginFilter.init(pluginConfig); - pluginConfigMap.put(plugin, pluginConfig); - log.info("init {} with {}", plugin, pluginConfig); - } else { - log.warn("no {} bean", plugin); + String json = null; + try { + for (Map.Entry e : es) { + json = (String) e.getValue(); + HashMap map = JacksonUtils.readValue(json, HashMap.class); + String plugin = (String) map.get("plugin"); + String pluginConfig = (String) map.get("fixedConfig"); + String currentPluginConfig = pluginConfigMap.get(plugin); + if (currentPluginConfig == null || !currentPluginConfig.equals(pluginConfig)) { + if (Fizz.context.containsBean(plugin)) { + FizzPluginFilter pluginFilter = (FizzPluginFilter) Fizz.context.getBean(plugin); + pluginFilter.init(pluginConfig); + pluginConfigMap.put(plugin, pluginConfig); + log.info("init {} with {}", plugin, pluginConfig); + } else { + log.warn("no {} bean", plugin); + } } } + } catch (Throwable t) { + result.code = Result.FAIL; + result.msg = "init plugin error, config: " + json; + result.t = t; } } return Mono.empty(); } ) - .doOnError( - t -> { - log.error("init plugin", t); - } + .onErrorReturn( + throwable -> { + result.code = Result.FAIL; + result.msg = "init plugin error"; + result.t = throwable; + return true; + }, + result ) .block(); - return Mono.just(ReactiveResult.succ()); + return result; } - private Mono> lsnPluginConfigChange() { - ReactiveResult result = ReactiveResult.succ(); + private Result lsnPluginConfigChange() { + Result result = Result.succ(); String channel = apiConfigServiceProperties.getFizzPluginConfigChannel(); rt.listenToChannel(channel) .doOnError( t -> { result.code = ReactiveResult.FAIL; - result.t = t; - log.error("lsn {}", channel, t); + result.msg = "lsn error, channel: " + channel; + result.t = t; + log.error("lsn channel {} error", channel, t); } ) .doOnSubscribe( @@ -290,7 +288,7 @@ public class ApiConfigService { } ) .subscribe(); - return Mono.just(result); + return result; } private void updateServiceConfigMap(ApiConfig ac, Map serviceConfigMap) { @@ -316,6 +314,7 @@ public class ApiConfigService { /** * @deprecated */ + @Deprecated public enum Access { YES (null), diff --git a/fizz-core/src/main/java/we/proxy/CallbackService.java b/fizz-core/src/main/java/we/proxy/CallbackService.java index a08c5cf..bb1b5ec 100644 --- a/fizz-core/src/main/java/we/proxy/CallbackService.java +++ b/fizz-core/src/main/java/we/proxy/CallbackService.java @@ -196,9 +196,9 @@ public class CallbackService { ); if (log.isDebugEnabled()) { StringBuilder b = ThreadContext.getStringBuilder(); - String rid = WebUtils.getTraceId(exchange); - WebUtils.response2stringBuilder(rid, remoteResp, b); - log.debug(b.toString(), LogService.BIZ_ID, rid); + String traceId = WebUtils.getTraceId(exchange); + WebUtils.response2stringBuilder(traceId, remoteResp, b); + log.debug(b.toString(), LogService.BIZ_ID, traceId); } return clientResp.writeWith(remoteResp.body(BodyExtractors.toDataBuffers())) .doOnError(throwable -> clean(remoteResp)).doOnCancel(() -> clean(remoteResp)); diff --git a/fizz-core/src/test/java/we/api/pairing/ApiPairingDocSetServiceTests.java b/fizz-core/src/test/java/we/api/pairing/ApiPairingDocSetServiceTests.java new file mode 100644 index 0000000..10f21e5 --- /dev/null +++ b/fizz-core/src/test/java/we/api/pairing/ApiPairingDocSetServiceTests.java @@ -0,0 +1,71 @@ +package we.api.pairing; + +import org.junit.Assert; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.context.support.GenericApplicationContext; +import org.springframework.data.redis.core.ReactiveStringRedisTemplate; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.http.HttpMethod; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import we.Fizz; +import we.redis.RedisProperties; +import we.redis.RedisServerConfiguration; +import we.redis.RedisTemplateConfiguration; +import we.util.ReflectionUtils; + +import javax.annotation.Resource; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * @author hongqiaowei + */ + +@TestPropertySource("/application.properties") +@SpringJUnitConfig(classes = {RedisProperties.class, RedisTemplateConfiguration.class, RedisServerConfiguration.class}) +public class ApiPairingDocSetServiceTests { + + @Resource + StringRedisTemplate stringRedisTemplate; + + @Resource + ReactiveStringRedisTemplate reactiveStringRedisTemplate; + + ApiPairingDocSetService apiPairingDocSetService; + + @BeforeEach + void beforeEach() throws NoSuchFieldException { + apiPairingDocSetService = new ApiPairingDocSetService(); + ReflectionUtils.set(apiPairingDocSetService, "rt", reactiveStringRedisTemplate); + } + + @Test + void initTest() throws Throwable { + + Fizz.context = new GenericApplicationContext(); + Fizz.context.refresh(); + + Map hash = new HashMap<>(); + hash.put("c", "{\"id\":1,\"name\":\"DocSet1\",\"docs\":[{\"service\":\"we-meb\",\"apis\":[{\"method\":\"GET\",\"path\":\"/getMebInfo\"}]}],\"appIds\":[\"app1\"]}"); + hash.put("d", "{\"id\":2,\"name\":\"DocSet2\",\"docs\":[{\"service\":\"we-meb\",\"apis\":[{\"method\":\"GET\",\"path\":\"/getMebInfo\"}]}],\"appIds\":[\"app1\"]}"); +// hash.put("a", "{\"isDeleted\":1,\"id\":1,\"name\":\"DocSet1\",\"docs\":[{\"service\":\"we-meb\",\"apis\":[{\"method\":\"GET\",\"path\":\"/getMebInfo\"}]}],\"appIds\":[\"app1\"]}"); +// hash.put("b", "{\"isDeleted\":1,\"id\":2,\"name\":\"DocSet2\",\"docs\":[{\"service\":\"we-meb\",\"apis\":[{\"method\":\"GET\",\"path\":\"/getMebInfo\"}]}],\"appIds\":[\"app1\"]}"); + stringRedisTemplate.opsForHash().putAll("fizz_api_pairing_doc", hash); + + apiPairingDocSetService.init(); + Map docSetMap = apiPairingDocSetService.getDocSetMap(); + Map> appDocSetMap = apiPairingDocSetService.getAppDocSetMap(); + Map> serviceExistsInDocSetMap = apiPairingDocSetService.getServiceExistsInDocSetMap(); + Map>> pathMethodExistsInDocSetMap = apiPairingDocSetService.getPathMethodExistsInDocSetMap(); +// System.err.println("docSetMap: " + JacksonUtils.writeValueAsString(docSetMap)); +// System.err.println("appDocSetMap: " + JacksonUtils.writeValueAsString(appDocSetMap)); +// System.err.println("serviceExistsInDocSetMap: " + JacksonUtils.writeValueAsString(serviceExistsInDocSetMap)); +// System.err.println("pathMethodExistsInDocSetMap: " + JacksonUtils.writeValueAsString(pathMethodExistsInDocSetMap)); + + boolean b = apiPairingDocSetService.existsDocSetMatch("app1", HttpMethod.GET, "we-meb", "/getMebInfo"); + Assert.assertTrue(b); + } +}