Api pairing service

This commit is contained in:
hongqiaowei
2021-10-23 12:05:35 +08:00
committed by GitHub
parent 5c20b0c81a
commit bbbc051ead
20 changed files with 1044 additions and 174 deletions

View File

@@ -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

View File

@@ -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 <T> ArrayList<T> getArrayList() {
return getArrayList(arrayListT, true);
return getArrayList(arrayList, true);
}
public static <T> ArrayList<T> getArrayList(String key) {
@@ -147,7 +152,7 @@ public abstract class ThreadContext {
}
public static <K, V> HashMap<K, V> getHashMap() {
return getHashMap(hashMapT, true);
return getHashMap(hashMap, true);
}
public static <K, V> HashMap<K, V> getHashMap(String key) {
@@ -166,7 +171,7 @@ public abstract class ThreadContext {
}
public static <E> HashSet<E> getHashSet() {
return getHashSet(hashSetT, true);
return getHashSet(hashSet, true);
}
public static <E> HashSet<E> getHashSet(String key) {

View File

@@ -25,5 +25,5 @@ import org.springframework.context.ConfigurableApplicationContext;
public class Fizz {
public static ConfigurableApplicationContext context;
public static ConfigurableApplicationContext context; // TODO: rename to CONTEXT
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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);
}
}

View File

@@ -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<Void> 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<AppApiPairingDocSet> 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<AppApiPairingDocSet> getAppDocSet(String appId) {
Map<Integer, ApiPairingDocSet> docSetMap = apiPairingDocSetService.getDocSetMap();
ArrayList<AppApiPairingDocSet> result = ThreadContext.getArrayList();
for (Map.Entry<Integer, ApiPairingDocSet> 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<String> signHdrs = systemConfig.getSignHeaders();
for (int i = 0; i < signHdrs.size(); i++) {

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<Api> apis = Collections.emptyList();
@Override
public String toString() {
return JacksonUtils.writeValueAsString(this);
}
}

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 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<ApiPairingDoc> docs = Collections.emptyList();
public Set<String> 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);
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<Integer/* doc set id */, ApiPairingDocSet> docSetMap = new HashMap<>(64);
private Map<String /* app id */, Set<ApiPairingDocSet>> appDocSetMap = new HashMap<>(64);
private Map<String /* service */, Set<ApiPairingDocSet>> serviceExistsInDocSetMap = new HashMap<>(64);
private Map<String /* path */, Map<Object /* method */, Set<ApiPairingDocSet>>> 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<Map.Entry<Object, Object>> 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<Object, Object> 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<ApiPairingDocSet> dss = appDocSetMap.get(appId);
if (dss != null) {
dss.remove(docSet);
if (dss.isEmpty()) {
appDocSetMap.remove(appId);
}
}
}
for (ApiPairingDoc doc : docSet.docs) {
Set<ApiPairingDocSet> dss = serviceExistsInDocSetMap.get(doc.service);
if (dss != null) {
dss.remove(docSet);
if (dss.isEmpty()) {
serviceExistsInDocSetMap.remove(doc.service);
}
}
for (Api api : doc.apis) {
Map<Object, Set<ApiPairingDocSet>> 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<ApiPairingDocSet> dss = appDocSetMap.get(appId);
if (dss == null) {
dss = new HashSet<>();
appDocSetMap.put(appId, dss);
}
dss.add(docSet);
}
);
docSet.docs.forEach(
doc -> {
Set<ApiPairingDocSet> 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<Object, Set<ApiPairingDocSet>> 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<Integer, ApiPairingDocSet> getDocSetMap() {
return docSetMap;
}
public ApiPairingDocSet get(int id) {
return docSetMap.get(id);
}
public Map<String, Set<ApiPairingDocSet>> getAppDocSetMap() {
return appDocSetMap;
}
public Map<String, Set<ApiPairingDocSet>> getServiceExistsInDocSetMap() {
return serviceExistsInDocSetMap;
}
public Map<String, Map<Object, Set<ApiPairingDocSet>>> getPathMethodExistsInDocSetMap() {
return pathMethodExistsInDocSetMap;
}
public boolean existsDocSetMatch(String appId, HttpMethod method, String service, String path) {
Set<ApiPairingDocSet> appDocSets = appDocSetMap.get(appId);
if (appDocSets == null) {
return false;
}
Set<ApiPairingDocSet> serviceDocSets = serviceExistsInDocSetMap.get(service);
if (serviceDocSets == null) {
return false;
}
Map<Object, Set<ApiPairingDocSet>> methodDocSetMap = pathMethodExistsInDocSetMap.get(path);
if (methodDocSetMap == null) {
return false;
}
Set<ApiPairingDocSet> pathMethodDocSets = methodDocSetMap.get(method);
if (pathMethodDocSets == null) {
pathMethodDocSets = methodDocSetMap.get(ApiConfig.ALL_METHOD);
if (pathMethodDocSets == null) {
return false;
}
}
Set<ApiPairingDocSet> s = new HashSet<>(appDocSets);
s.retainAll(serviceDocSets);
if (s.isEmpty()) {
return false;
}
s.retainAll(pathMethodDocSets);
if (s.isEmpty()) {
return false;
}
return true;
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<String> services = Collections.emptyList();
@Override
public String toString() {
return JacksonUtils.writeValueAsString(this);
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<String , ApiPairingInfo> 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<Map.Entry<Object, Object>> 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<Object, Object> 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<String, ApiPairingInfo> getServiceApiPairingInfoMap() {
return serviceApiPairingInfoMap;
}
public ApiPairingInfo get(String service) {
return serviceApiPairingInfoMap.get(service);
}
}

View File

@@ -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) {

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<String> services;
public boolean enabled;
@Override
public String toString() {
return JacksonUtils.writeValueAsString(this);
}
}

View File

@@ -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<MultiValueMap<String, String>> 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<ClientResponse> remoteResponseMono = fizzWebClient.send( request.getId(), request.getMethod(), targetUrl, writableHttpHeaders, request.getBody(),
requestTimeout, retryCount, retryInterval );
Mono<Void> 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);
}
}

View File

@@ -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(),

View File

@@ -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<String> appHeaders = Stream.of("fizz-appid").collect(Collectors.toList());
private List<String> signHeaders = Stream.of("fizz-sign") .collect(Collectors.toList());
private List<String> timestampHeaders = Stream.of("fizz-ts") .collect(Collectors.toList());
private List<String> appHeaders = Stream.of(FIZZ_APP_ID) .collect(Collectors.toList());
private List<String> signHeaders = Stream.of(FIZZ_SIGN) .collect(Collectors.toList());
private List<String> timestampHeaders = Stream.of(FIZZ_TIMESTAMP).collect(Collectors.toList());
private List<String> 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;

View File

@@ -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));

View File

@@ -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<Result<?>> initGlobalResource() {
private Result<?> initGlobalResource() {
Result<?> result = Result.succ();
Flux<Map.Entry<Object, Object>> resources = rt.opsForHash().entries("fizz_global_resource");
resources.collectList()
.defaultIfEmpty(Collections.emptyList())
.flatMap(
es -> {
if (Fizz.context != null) {
for (Map.Entry<Object, Object> 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<Object, Object> 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<Result<?>> 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<String, GlobalResource> getResourceMap() {

View File

@@ -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<ReactiveResult<?>> initPlugin() {
private Result<?> initPlugin() {
Result<?> result = Result.succ();
String key = apiConfigServiceProperties.getFizzPluginConfig();
Flux<Map.Entry<Object, Object>> plugins = rt.opsForHash().entries(key);
plugins.collectList()
@@ -218,45 +204,57 @@ public class ApiConfigService {
.flatMap(
es -> {
if (Fizz.context != null) {
for (Map.Entry<Object, Object> 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<Object, Object> 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<ReactiveResult<?>> 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<String, ServiceConfig> serviceConfigMap) {
@@ -316,6 +314,7 @@ public class ApiConfigService {
/**
* @deprecated
*/
@Deprecated
public enum Access {
YES (null),

View File

@@ -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));

View File

@@ -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<String, String> 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<Integer, ApiPairingDocSet> docSetMap = apiPairingDocSetService.getDocSetMap();
Map<String, Set<ApiPairingDocSet>> appDocSetMap = apiPairingDocSetService.getAppDocSetMap();
Map<String, Set<ApiPairingDocSet>> serviceExistsInDocSetMap = apiPairingDocSetService.getServiceExistsInDocSetMap();
Map<String, Map<Object, Set<ApiPairingDocSet>>> 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);
}
}