From 361302db4df066c220dd3f34d5f5fc12c661cf69 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 28 Nov 2023 17:24:23 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat(gateway):=E6=96=B0=E5=A2=9Eswagger?= =?UTF-8?q?=E8=BF=87=E6=BB=A4=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ds-commons/ds-utils/pom.xml | 11 ++ .../com/ds/commons/utils/json/JsonUtil.java | 168 ++++++++++++++++++ ds-gateway/pom.xml | 10 ++ .../gateway/filter/SwaggerGlobalFilter.java | 98 ++++++++++ 4 files changed, 287 insertions(+) create mode 100644 ds-commons/ds-utils/src/main/java/com/ds/commons/utils/json/JsonUtil.java create mode 100644 ds-gateway/src/main/java/com/ds/gateway/filter/SwaggerGlobalFilter.java diff --git a/ds-commons/ds-utils/pom.xml b/ds-commons/ds-utils/pom.xml index e4dc142..f5110ab 100644 --- a/ds-commons/ds-utils/pom.xml +++ b/ds-commons/ds-utils/pom.xml @@ -17,4 +17,15 @@ UTF-8 + + + org.springframework.boot + spring-boot-starter-json + + + org.projectlombok + lombok + + + \ No newline at end of file diff --git a/ds-commons/ds-utils/src/main/java/com/ds/commons/utils/json/JsonUtil.java b/ds-commons/ds-utils/src/main/java/com/ds/commons/utils/json/JsonUtil.java new file mode 100644 index 0000000..a934568 --- /dev/null +++ b/ds-commons/ds-utils/src/main/java/com/ds/commons/utils/json/JsonUtil.java @@ -0,0 +1,168 @@ +package com.ds.commons.utils.json; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.extern.slf4j.Slf4j; + +import java.io.File; +import java.io.IOException; +import java.text.SimpleDateFormat; + +/** + * JSON 工具类 + */ +@Slf4j +public class JsonUtil { + + private static final ObjectMapper objectMapper = new ObjectMapper(); + + // 时间日期格式 + private static final String STANDARD_FORMAT = "yyyy-MM-dd HH:mm:ss"; + + //以静态代码块初始化 + static { + //对象的所有字段全部列入序列化 + objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS); + //取消默认转换timestamps形式 + objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); + //忽略空Bean转json的错误 + objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); + //所有的日期格式都统一为以下的格式,即yyyy-MM-dd HH:mm:ss + objectMapper.setDateFormat(new SimpleDateFormat(STANDARD_FORMAT)); + //忽略 在json字符串中存在,但在java对象中不存在对应属性的情况。防止错误 + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + } + + + /**===========================以下是从JSON中获取对象====================================*/ + public static T parseObject(String jsonString, Class object) { + T t = null; + try { + t = objectMapper.readValue(jsonString, object); + } catch (JsonProcessingException e) { + log.error("JsonString转为自定义对象失败:{}", e.getMessage()); + } + return t; + } + + public static T parseObject(File file, Class object) { + T t = null; + try { + t = objectMapper.readValue(file, object); + } catch (IOException e) { + log.error("从文件中读取json字符串转为自定义对象失败:{}", e.getMessage()); + } + return t; + } + + //将json数组字符串转为指定对象List列表或者Map集合 + public static T parseJSONArray(String jsonArray, TypeReference reference) { + T t = null; + try { + t = objectMapper.readValue(jsonArray, reference); + } catch (JsonProcessingException e) { + log.error("JSONArray转为List列表或者Map集合失败:{}", e.getMessage()); + } + return t; + } + + + /**=================================以下是将对象转为JSON=====================================*/ + public static String toJSONString(Object object) { + String jsonString = null; + try { + jsonString = objectMapper.writeValueAsString(object); + } catch (JsonProcessingException e) { + log.error("Object转JSONString失败:{}", e.getMessage()); + } + return jsonString; + } + + public static byte[] toByteArray(Object object) { + byte[] bytes = null; + try { + bytes = objectMapper.writeValueAsBytes(object); + } catch (JsonProcessingException e) { + log.error("Object转ByteArray失败:{}", e.getMessage()); + } + return bytes; + } + + public static void objectToFile(File file, Object object) { + try { + objectMapper.writeValue(file, object); + } catch (JsonProcessingException e) { + log.error("Object写入文件失败:{}", e.getMessage()); + } catch (IOException e) { + e.printStackTrace(); + } + } + + + /**=============================以下是与JsonNode相关的=======================================*/ + //JsonNode和JSONObject一样,都是JSON树形模型,只不过在jackson中,存在的是JsonNode + public static JsonNode parseJSONObject(String jsonString) { + JsonNode jsonNode = null; + try { + jsonNode = objectMapper.readTree(jsonString); + } catch (JsonProcessingException e) { + log.error("JSONString转为JsonNode失败:{}", e.getMessage()); + } + return jsonNode; + } + + public static JsonNode parseJSONObject(Object object) { + JsonNode jsonNode = objectMapper.valueToTree(object); + return jsonNode; + } + + public static String toJSONString(JsonNode jsonNode) { + String jsonString = null; + try { + jsonString = objectMapper.writeValueAsString(jsonNode); + } catch (JsonProcessingException e) { + log.error("JsonNode转JSONString失败:{}", e.getMessage()); + } + return jsonString; + } + + //JsonNode是一个抽象类,不能实例化,创建JSON树形模型,得用JsonNode的子类ObjectNode,用法和JSONObject大同小异 + public static ObjectNode newJSONObject() { + return objectMapper.createObjectNode(); + } + + //创建JSON数组对象,就像JSONArray一样用 + public static ArrayNode newJSONArray() { + return objectMapper.createArrayNode(); + } + + + /**===========以下是从JsonNode对象中获取key值的方法,个人觉得有点多余,直接用JsonNode自带的取值方法会好点,出于纠结症,还是补充进来了*/ + public static String getString(JsonNode jsonObject, String key) { + String s = jsonObject.get(key).asText(); + return s; + } + + public static Integer getInteger(JsonNode jsonObject, String key) { + Integer i = jsonObject.get(key).asInt(); + return i; + } + + public static Boolean getBoolean(JsonNode jsonObject, String key) { + Boolean bool = jsonObject.get(key).asBoolean(); + return bool; + } + + public static JsonNode getJSONObject(JsonNode jsonObject, String key) { + JsonNode json = jsonObject.get(key); + return json; + } +} + diff --git a/ds-gateway/pom.xml b/ds-gateway/pom.xml index bd12544..5ef80a4 100644 --- a/ds-gateway/pom.xml +++ b/ds-gateway/pom.xml @@ -30,6 +30,16 @@ org.springframework.cloud spring-cloud-loadbalancer + + org.projectlombok + lombok + + + com.ds + ds-utils + 0.0.1-SNAPSHOT + compile + ${project.artifactId} diff --git a/ds-gateway/src/main/java/com/ds/gateway/filter/SwaggerGlobalFilter.java b/ds-gateway/src/main/java/com/ds/gateway/filter/SwaggerGlobalFilter.java new file mode 100644 index 0000000..ad7f745 --- /dev/null +++ b/ds-gateway/src/main/java/com/ds/gateway/filter/SwaggerGlobalFilter.java @@ -0,0 +1,98 @@ +package com.ds.gateway.filter; + +import com.ds.commons.utils.json.JsonUtil; +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.extern.slf4j.Slf4j; +import org.reactivestreams.Publisher; +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.filter.GlobalFilter; +import org.springframework.core.Ordered; +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.core.io.buffer.DataBufferUtils; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.http.server.reactive.ServerHttpResponseDecorator; +import org.springframework.stereotype.Component; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +@Slf4j +@Component +public class SwaggerGlobalFilter implements GlobalFilter, Ordered { + + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + ServerHttpRequest request = exchange.getRequest(); + String path = request.getPath().toString(); + String host = request.getLocalAddress().getHostString(); + int port = request.getLocalAddress().getPort(); + if (!path.endsWith("/v3/api-docs")) { + return chain.filter(exchange); + } + String[] pathArray = path.split("/"); + System.out.println(pathArray); + String basePath = pathArray[1]; + ServerHttpResponse originalResponse = exchange.getResponse(); + // 定义新的消息头 + ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) { + @Override + public Mono writeWith(Publisher body) { + if (super.getStatusCode().equals(HttpStatus.OK) && body instanceof Flux) { + Flux fluxBody = Flux.from(body); + return super.writeWith(fluxBody.buffer().map(dataBuffers -> { + List list = new ArrayList<>(); + dataBuffers.forEach(dataBuffer -> { + byte[] content = new byte[dataBuffer.readableByteCount()]; + dataBuffer.read(content); + DataBufferUtils.release(dataBuffer); + list.add(new String(content, StandardCharsets.UTF_8)); + }); + String s = this.listToString(list); + ObjectNode jsonNodes = JsonUtil.parseObject(s,ObjectNode.class); + + jsonNodes.put("host", host + ":" + port); + jsonNodes.put("basePath", basePath); + s = jsonNodes.toString(); + // 设置更新后的header请求头长度 + int length = s.getBytes().length; + HttpHeaders headers = originalResponse.getHeaders(); + headers.setContentLength(length); + return bufferFactory().wrap(s.getBytes(StandardCharsets.UTF_8)); + })); + } + return super.writeWith(body); + } + + @Override + public HttpHeaders getHeaders() { + // 获取父类原始ServerHttpResponse的header请求头信息,这是代理Delegate类型 + HttpHeaders httpHeaders = super.getHeaders(); + httpHeaders.set(HttpHeaders.CONTENT_TYPE, "application/json;charset=UTF-8"); + return httpHeaders; + } + + private String listToString(List list) { + StringBuilder stringBuilder = new StringBuilder(); + for (String s : list) { + stringBuilder.append(s); + } + return stringBuilder.toString(); + } + }; + + // replace response with decorator + return chain.filter(exchange.mutate().response(decoratedResponse).build()); + } + + @Override + public int getOrder() { + return -2; + } + +} \ No newline at end of file From 984e0a9fb0339abf98252b86c267e857c734e9f2 Mon Sep 17 00:00:00 2001 From: dashan Date: Tue, 28 Nov 2023 22:36:44 +0800 Subject: [PATCH 2/2] =?UTF-8?q?feat(gateway):gateway=20=E8=BD=AC=E5=8F=91s?= =?UTF-8?q?wagger?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gateway/filter/SwaggerGlobalFilter.java | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/ds-gateway/src/main/java/com/ds/gateway/filter/SwaggerGlobalFilter.java b/ds-gateway/src/main/java/com/ds/gateway/filter/SwaggerGlobalFilter.java index ad7f745..e27fc8d 100644 --- a/ds-gateway/src/main/java/com/ds/gateway/filter/SwaggerGlobalFilter.java +++ b/ds-gateway/src/main/java/com/ds/gateway/filter/SwaggerGlobalFilter.java @@ -1,6 +1,7 @@ package com.ds.gateway.filter; import com.ds.commons.utils.json.JsonUtil; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; import org.reactivestreams.Publisher; @@ -14,6 +15,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.http.server.reactive.ServerHttpResponseDecorator; +import org.springframework.lang.NonNull; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Flux; @@ -23,6 +25,10 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; +/** + * 因为swagger配置在业务服务中,默认的url会比网关少一层; + * 这个过滤器主要给url添加上此网关这一层,并且设置到swagger的basePath中,使其可以正常通过swagger调用接口 + */ @Slf4j @Component public class SwaggerGlobalFilter implements GlobalFilter, Ordered { @@ -30,20 +36,18 @@ public class SwaggerGlobalFilter implements GlobalFilter, Ordered { public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); String path = request.getPath().toString(); - String host = request.getLocalAddress().getHostString(); - int port = request.getLocalAddress().getPort(); - if (!path.endsWith("/v3/api-docs")) { + if (!path.endsWith("/v3/api-docs/swagger-config")) { return chain.filter(exchange); } String[] pathArray = path.split("/"); - System.out.println(pathArray); String basePath = pathArray[1]; ServerHttpResponse originalResponse = exchange.getResponse(); // 定义新的消息头 ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) { @Override - public Mono writeWith(Publisher body) { - if (super.getStatusCode().equals(HttpStatus.OK) && body instanceof Flux) { + @NonNull + public Mono writeWith(@NonNull Publisher body) { + if (HttpStatus.OK.equals(super.getStatusCode()) && body instanceof Flux) { Flux fluxBody = Flux.from(body); return super.writeWith(fluxBody.buffer().map(dataBuffers -> { List list = new ArrayList<>(); @@ -54,9 +58,12 @@ public class SwaggerGlobalFilter implements GlobalFilter, Ordered { list.add(new String(content, StandardCharsets.UTF_8)); }); String s = this.listToString(list); + //获取到/v3/api-docs/swagger-config返回的json串 给文档url添加上网关层url ObjectNode jsonNodes = JsonUtil.parseObject(s,ObjectNode.class); + JsonNode urls = jsonNodes.get("urls"); + urls.elements().forEachRemaining(item-> ((ObjectNode)item).put("url","/"+basePath+item.get("url").asText())); - jsonNodes.put("host", host + ":" + port); + //设置basePath jsonNodes.put("basePath", basePath); s = jsonNodes.toString(); // 设置更新后的header请求头长度 @@ -70,6 +77,7 @@ public class SwaggerGlobalFilter implements GlobalFilter, Ordered { } @Override + @NonNull public HttpHeaders getHeaders() { // 获取父类原始ServerHttpResponse的header请求头信息,这是代理Delegate类型 HttpHeaders httpHeaders = super.getHeaders();