From bf2e7962815e107d5726ca5d7ead5eb780a1e6a7 Mon Sep 17 00:00:00 2001 From: WJY Date: Tue, 28 Jun 2022 13:47:05 +0800 Subject: [PATCH 01/24] =?UTF-8?q?=E5=85=A8=E9=9D=A2=E5=85=BC=E5=AE=B9Feign?= =?UTF-8?q?Client=E6=B3=A8=E8=A7=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/me/danwi/kato/client/KatoClient.java | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/client/src/main/java/me/danwi/kato/client/KatoClient.java b/client/src/main/java/me/danwi/kato/client/KatoClient.java index 75c27e2..61fe0fa 100644 --- a/client/src/main/java/me/danwi/kato/client/KatoClient.java +++ b/client/src/main/java/me/danwi/kato/client/KatoClient.java @@ -13,8 +13,32 @@ @FeignClient(configuration = KatoClientConfig.class) public @interface KatoClient { @AliasFor(annotation = FeignClient.class) - String value(); + String value() default ""; @AliasFor(annotation = FeignClient.class) - String url(); + String contextId() default ""; + + @AliasFor(annotation = FeignClient.class) + String name() default ""; + + @AliasFor(annotation = FeignClient.class) + String[] qualifiers() default {}; + + @AliasFor(annotation = FeignClient.class) + String url() default ""; + + @AliasFor(annotation = FeignClient.class) + boolean decode404() default false; + + @AliasFor(annotation = FeignClient.class) + Class fallback() default void.class; + + @AliasFor(annotation = FeignClient.class) + Class fallbackFactory() default void.class; + + @AliasFor(annotation = FeignClient.class) + String path() default ""; + + @AliasFor(annotation = FeignClient.class) + boolean primary() default true; } From a9816ba21eb87667b41b9fbd91f487d1d934e031 Mon Sep 17 00:00:00 2001 From: WJY Date: Tue, 28 Jun 2022 13:47:50 +0800 Subject: [PATCH 02/24] =?UTF-8?q?KatoUndeclaredException=20=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E6=9E=84=E9=80=A0=E5=87=BD=E6=95=B0=EF=BC=8C=E5=9C=A8?= =?UTF-8?q?Client=E6=97=B6=E4=BC=9A=E8=B0=83=E7=94=A8=E5=8D=95String?= =?UTF-8?q?=E6=9E=84=E9=80=A0=E5=87=BD=E6=95=B0=E5=AE=9E=E4=BE=8B=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kato/common/exception/KatoUndeclaredException.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/common/src/main/java/me/danwi/kato/common/exception/KatoUndeclaredException.java b/common/src/main/java/me/danwi/kato/common/exception/KatoUndeclaredException.java index ea9e8e4..c1cd5a1 100644 --- a/common/src/main/java/me/danwi/kato/common/exception/KatoUndeclaredException.java +++ b/common/src/main/java/me/danwi/kato/common/exception/KatoUndeclaredException.java @@ -4,6 +4,14 @@ * 未被业务代码处理的异常,通常出现这种异常需要引起开发人员的注意 */ public class KatoUndeclaredException extends KatoException { + public KatoUndeclaredException(String message) { + super(message); + } + + public KatoUndeclaredException(String message, Throwable cause) { + super(message, cause); + } + public KatoUndeclaredException(Throwable cause) { super(cause); } From dee58c5912875bf4247b85b5f7c63fd30ed19e4d Mon Sep 17 00:00:00 2001 From: WJY Date: Fri, 1 Jul 2022 15:08:12 +0800 Subject: [PATCH 03/24] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E5=BD=93response?= =?UTF-8?q?=E6=98=AF=E7=A9=BA=E6=97=B6=EF=BC=8Cjson=E5=8F=8D=E5=BA=8F?= =?UTF-8?q?=E5=88=97=E5=8C=96=E9=94=99=E8=AF=AF=E9=97=AE=E9=A2=98=EF=BC=9B?= =?UTF-8?q?=20com.fasterxml.jackson.databind.exc.MismatchedInputException:?= =?UTF-8?q?=20No=20content=20to=20map=20due=20to=20end-of-input=20=20at=20?= =?UTF-8?q?[Source:=20(String)"";=20line:=201,=20column:=200]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/main/java/me/danwi/kato/client/KatoResultDecoder.java | 1 + 1 file changed, 1 insertion(+) diff --git a/client/src/main/java/me/danwi/kato/client/KatoResultDecoder.java b/client/src/main/java/me/danwi/kato/client/KatoResultDecoder.java index 053873c..720dc2f 100644 --- a/client/src/main/java/me/danwi/kato/client/KatoResultDecoder.java +++ b/client/src/main/java/me/danwi/kato/client/KatoResultDecoder.java @@ -20,6 +20,7 @@ public Object decode(Response response, Type type) throws IOException, FeignExce return null; //反序列化结果 String bodyStr = Util.toString(response.body().asReader(Util.UTF_8)); + if (Util.isBlank()) return null return mapper.readValue(bodyStr, TypeFactory.defaultInstance().constructType(type)); } } \ No newline at end of file From 823f849840db0a68009cf5f4a1814b2c6bd7c534 Mon Sep 17 00:00:00 2001 From: WJY Date: Fri, 1 Jul 2022 15:31:41 +0800 Subject: [PATCH 04/24] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E5=BD=93response?= =?UTF-8?q?=E6=98=AF=E7=A9=BA=E6=97=B6=EF=BC=8Cjson=E5=8F=8D=E5=BA=8F?= =?UTF-8?q?=E5=88=97=E5=8C=96=E9=94=99=E8=AF=AF=E9=97=AE=E9=A2=98=EF=BC=9B?= =?UTF-8?q?=20com.fasterxml.jackson.databind.exc.MismatchedInputException:?= =?UTF-8?q?=20No=20content=20to=20map=20due=20to=20end-of-input=20=20at=20?= =?UTF-8?q?[Source:=20(String)"";=20line:=201,=20column:=200]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/me/danwi/kato/client/KatoResultDecoder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/main/java/me/danwi/kato/client/KatoResultDecoder.java b/client/src/main/java/me/danwi/kato/client/KatoResultDecoder.java index 720dc2f..b0511ab 100644 --- a/client/src/main/java/me/danwi/kato/client/KatoResultDecoder.java +++ b/client/src/main/java/me/danwi/kato/client/KatoResultDecoder.java @@ -20,7 +20,7 @@ public Object decode(Response response, Type type) throws IOException, FeignExce return null; //反序列化结果 String bodyStr = Util.toString(response.body().asReader(Util.UTF_8)); - if (Util.isBlank()) return null + if (Util.isBlank(bodyStr)) return null; return mapper.readValue(bodyStr, TypeFactory.defaultInstance().constructType(type)); } } \ No newline at end of file From 7443be194bbdef2165282c2167f2cb7bdba0ce7b Mon Sep 17 00:00:00 2001 From: WJY Date: Wed, 6 Jul 2022 10:21:48 +0800 Subject: [PATCH 05/24] =?UTF-8?q?=E5=A2=9E=E5=8A=A0MultiRequestBody.java?= =?UTF-8?q?=E6=B3=A8=E8=A7=A3=EF=BC=8C=E6=94=AF=E6=8C=81=E5=AF=B9=E8=B1=A1?= =?UTF-8?q?=E5=B1=95=E5=BC=80=E6=8E=A5=E6=94=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/build.gradle.kts | 5 + .../argument/MethodArgumentHandlerConfig.java | 26 ++++ .../common/argument/MultiRequestBody.java | 29 ++++ ...uestBodyMethodArgumentHandlerResolver.java | 125 ++++++++++++++++++ .../danwi/kato/example/ExampleApplication.kt | 3 + .../controller/MultiRequestBodyController.kt | 29 ++++ .../MultiRequestBodyControllerTest.java | 48 +++++++ 7 files changed, 265 insertions(+) create mode 100644 common/src/main/java/me/danwi/kato/common/argument/MethodArgumentHandlerConfig.java create mode 100644 common/src/main/java/me/danwi/kato/common/argument/MultiRequestBody.java create mode 100644 common/src/main/java/me/danwi/kato/common/argument/MultiRequestBodyMethodArgumentHandlerResolver.java create mode 100644 example/src/main/kotlin/me/danwi/kato/example/controller/MultiRequestBodyController.kt create mode 100644 example/src/test/java/me/danwi/kato/example/MultiRequestBodyControllerTest.java diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 87a7534..3649da9 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -6,7 +6,12 @@ plugins { dependencies { implementation("com.fasterxml.jackson.core:jackson-databind:2.13.3") + implementation("cn.hutool:hutool-core:5.8.4.M1") + compileOnly("org.springframework.boot:spring-boot-autoconfigure:2.6.7") + compileOnly("org.springframework:spring-context:5.3.19") + compileOnly("org.springframework:spring-webmvc:5.3.19") + compileOnly("org.apache.tomcat.embed:tomcat-embed-core:9.0.62") compileOnly("org.projectlombok:lombok:1.18.24") annotationProcessor("org.projectlombok:lombok:1.18.24") } \ No newline at end of file diff --git a/common/src/main/java/me/danwi/kato/common/argument/MethodArgumentHandlerConfig.java b/common/src/main/java/me/danwi/kato/common/argument/MethodArgumentHandlerConfig.java new file mode 100644 index 0000000..df68fdd --- /dev/null +++ b/common/src/main/java/me/danwi/kato/common/argument/MethodArgumentHandlerConfig.java @@ -0,0 +1,26 @@ +package me.danwi.kato.common.argument; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import java.util.List; + +/** + * 参数处理器自动配置 + */ +@Configuration +public class MethodArgumentHandlerConfig implements WebMvcConfigurer { + + private final ObjectMapper objectMapper; + + public MethodArgumentHandlerConfig(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + + @Override + public void addArgumentResolvers(List resolvers) { + resolvers.add(new MultiRequestBodyMethodArgumentHandlerResolver(objectMapper)); + } +} diff --git a/common/src/main/java/me/danwi/kato/common/argument/MultiRequestBody.java b/common/src/main/java/me/danwi/kato/common/argument/MultiRequestBody.java new file mode 100644 index 0000000..491992f --- /dev/null +++ b/common/src/main/java/me/danwi/kato/common/argument/MultiRequestBody.java @@ -0,0 +1,29 @@ +package me.danwi.kato.common.argument; + +import java.lang.annotation.*; + +/** + * @see MultiRequestBodyMethodArgumentHandlerResolver + */ +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface MultiRequestBody { + + /** + * 设置解析key + */ + String value() default ""; + + /** + * 是否必填参数 + */ + boolean required() default true; + + /** + * 当body不是JSONObject或者不能匹配到key时 + * 是否将整个body作为参数解析 + */ + boolean parseBodyIfMissKey() default true; + +} diff --git a/common/src/main/java/me/danwi/kato/common/argument/MultiRequestBodyMethodArgumentHandlerResolver.java b/common/src/main/java/me/danwi/kato/common/argument/MultiRequestBodyMethodArgumentHandlerResolver.java new file mode 100644 index 0000000..fc9fd8d --- /dev/null +++ b/common/src/main/java/me/danwi/kato/common/argument/MultiRequestBodyMethodArgumentHandlerResolver.java @@ -0,0 +1,125 @@ +package me.danwi.kato.common.argument; + +import cn.hutool.core.io.IoUtil; +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 org.springframework.core.MethodParameter; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.context.request.WebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; + +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; +import java.util.Objects; + +/** + * 解析请求:body + *

支持特性: + *

    + *
  • 不限制请求类型,支持GET及其他类型请求 + *
+ *

如果JSON是Object: + *

    + *
  • 支持通过注解value,指定JSON的key来解析对象 + *
  • 支持注解无value,直接根据参数名来解析对象 + *
  • 支持注解无value且参数名不匹配JSON串key时,整个body作为参数解析 + *
  • 支持参数“共用”(不指定value时,参数名不为JSON串的key时,解析整个body) + *
  • 支持多余属性不报错(需配置{@link ObjectMapper#configure(DeserializationFeature, boolean)}为FAIL_ON_UNKNOWN_PROPERTIES(false)) + *
+ *

如果JSON不是Object: + *

    + *
  • 默认整个body作为参数解析 + *
+ */ +public class MultiRequestBodyMethodArgumentHandlerResolver implements HandlerMethodArgumentResolver { + + private static final String BODY_KEY = "BODY_KEY"; + + private final ObjectMapper mapper; + + public MultiRequestBodyMethodArgumentHandlerResolver(ObjectMapper mapper) { + this.mapper = mapper; + } + + @Override + public boolean supportsParameter(MethodParameter methodParameter) { + return methodParameter.hasParameterAnnotation(MultiRequestBody.class); + } + + @Override + public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception { + // 定义结果 + Object result = null; + + // 获取注解 + final MultiRequestBody parameterAnnotation = methodParameter.getParameterAnnotation(MultiRequestBody.class); + // 获取key + String key = parameterAnnotation.value(); + if (ObjectUtils.isEmpty(key)) { + key = methodParameter.getParameterName(); + } + // 获取请求body字符串 + final String body = getBody(nativeWebRequest); + + if (!ObjectUtils.isEmpty(body)) { + final JsonNode jsonNode = mapper.readTree(body); + // key解析 + if (jsonNode.has(key)) { + // String key + final JsonNode node = jsonNode.get(key); + result = readValue(methodParameter, node.toString()); + } else { + // 如果没有指定key,则判断是否可以解析整个body + if (parameterAnnotation.parseBodyIfMissKey()) { + try { + result = readValue(methodParameter, body); + } catch (Exception e) { + // ignore + } + } + } + } + + // 是否必填验证 + if (Objects.isNull(result) && parameterAnnotation.required()) { + throw new IllegalArgumentException(String.format("required param %s is not present", key)); + } + + return result; + } + + private Object readValue(MethodParameter methodParameter, String content) throws IOException { + return mapper.readValue(content, new TypeReference() { + @Override + public Type getType() { + return methodParameter.getGenericParameterType(); + } + }); + } + + /** + * 多个参数解析时,从attribute中获取数据 + * + * @param nativeWebRequest + * @return + * @throws IOException + */ + private String getBody(NativeWebRequest nativeWebRequest) throws IOException { + Object attribute = nativeWebRequest.getAttribute(BODY_KEY, WebRequest.SCOPE_REQUEST); + if (ObjectUtils.isEmpty(attribute)) { + HttpServletRequest servletRequest = nativeWebRequest.getNativeRequest(HttpServletRequest.class); + Assert.state(servletRequest != null, "No HttpServletRequest"); + attribute = IoUtil.read(servletRequest.getInputStream(), StandardCharsets.UTF_8); + nativeWebRequest.setAttribute(BODY_KEY, attribute, WebRequest.SCOPE_REQUEST); + } + return (String) attribute; + } +} diff --git a/example/src/main/kotlin/me/danwi/kato/example/ExampleApplication.kt b/example/src/main/kotlin/me/danwi/kato/example/ExampleApplication.kt index 6db6c5f..6f60df1 100644 --- a/example/src/main/kotlin/me/danwi/kato/example/ExampleApplication.kt +++ b/example/src/main/kotlin/me/danwi/kato/example/ExampleApplication.kt @@ -1,16 +1,19 @@ package me.danwi.kato.example +import me.danwi.kato.common.argument.MethodArgumentHandlerConfig import me.danwi.kato.common.exception.ExceptionExtraDataHolder import me.danwi.kato.common.exception.KatoCommonException import me.danwi.kato.common.exception.KatoException import me.danwi.kato.server.EnableKatoServer import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication +import org.springframework.context.annotation.Import import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController @SpringBootApplication @EnableKatoServer +@Import(MethodArgumentHandlerConfig::class) class ExampleApplication fun main(args: Array) { diff --git a/example/src/main/kotlin/me/danwi/kato/example/controller/MultiRequestBodyController.kt b/example/src/main/kotlin/me/danwi/kato/example/controller/MultiRequestBodyController.kt new file mode 100644 index 0000000..4001b73 --- /dev/null +++ b/example/src/main/kotlin/me/danwi/kato/example/controller/MultiRequestBodyController.kt @@ -0,0 +1,29 @@ +package me.danwi.kato.example.controller + +import me.danwi.kato.common.argument.MultiRequestBody +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RestController + +/** + * @author wjy + */ +@RestController +class MultiRequestBodyController { + + @PostMapping("/multiRequest") + fun multiRequest(@MultiRequestBody id: Int, @MultiRequestBody(required = false) name: String): TestEntity { + return TestEntity(id, name) + } + + @PostMapping("/multiRequestSingle") + fun multiRequestSingle(@MultiRequestBody id: Int): TestEntity { + return TestEntity(id, null) + } + + @PostMapping("/multiRequestObj") + fun multiRequestObj(@MultiRequestBody obj: TestEntity): TestEntity { + return obj + } +} + +data class TestEntity(val id: Int, val name: String?) \ No newline at end of file diff --git a/example/src/test/java/me/danwi/kato/example/MultiRequestBodyControllerTest.java b/example/src/test/java/me/danwi/kato/example/MultiRequestBodyControllerTest.java new file mode 100644 index 0000000..684074c --- /dev/null +++ b/example/src/test/java/me/danwi/kato/example/MultiRequestBodyControllerTest.java @@ -0,0 +1,48 @@ +package me.danwi.kato.example; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import me.danwi.kato.example.controller.TestEntity; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.ResponseEntity; + +import java.util.Objects; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author wjy + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class MultiRequestBodyControllerTest { + @Autowired + private TestRestTemplate restTemplate; + @Autowired + private ObjectMapper objectMapper; + + @Test + void testMultiRequest() throws JsonProcessingException { + final TestEntity test = new TestEntity(1, "test"); + final ResponseEntity result = restTemplate.postForEntity("/multiRequest", objectMapper.writeValueAsString(test), TestEntity.class); + assertThat(result.getBody()).isEqualTo(test); + } + + + @Test + void testMultiRequestSingle() { + final int id = 19; + final ResponseEntity result = restTemplate.postForEntity("/multiRequestSingle", id, TestEntity.class); + assertThat(Objects.requireNonNull(result.getBody()).getId()).isEqualTo(id); + } + + + @Test + void testMultiRequestObj() throws JsonProcessingException { + final TestEntity test = new TestEntity(1234, "tesdfst"); + final ResponseEntity result = restTemplate.postForEntity("/multiRequestObj", objectMapper.writeValueAsString(test), TestEntity.class); + assertThat(result.getBody()).isEqualTo(test); + } +} From 9631b5ff48c86cdfc21b3a30969067a1694a5a76 Mon Sep 17 00:00:00 2001 From: WJY Date: Wed, 6 Jul 2022 11:42:03 +0800 Subject: [PATCH 06/24] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=A7=A3=E6=9E=90?= =?UTF-8?q?=E5=8F=82=E6=95=B0=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/build.gradle.kts | 1 - ...uestBodyMethodArgumentHandlerResolver.java | 53 ++++++------------- .../controller/MultiRequestBodyController.kt | 12 ++++- .../MultiRequestBodyControllerTest.java | 9 +++- 4 files changed, 34 insertions(+), 41 deletions(-) diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 3649da9..63bb261 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -6,7 +6,6 @@ plugins { dependencies { implementation("com.fasterxml.jackson.core:jackson-databind:2.13.3") - implementation("cn.hutool:hutool-core:5.8.4.M1") compileOnly("org.springframework.boot:spring-boot-autoconfigure:2.6.7") compileOnly("org.springframework:spring-context:5.3.19") diff --git a/common/src/main/java/me/danwi/kato/common/argument/MultiRequestBodyMethodArgumentHandlerResolver.java b/common/src/main/java/me/danwi/kato/common/argument/MultiRequestBodyMethodArgumentHandlerResolver.java index fc9fd8d..5451738 100644 --- a/common/src/main/java/me/danwi/kato/common/argument/MultiRequestBodyMethodArgumentHandlerResolver.java +++ b/common/src/main/java/me/danwi/kato/common/argument/MultiRequestBodyMethodArgumentHandlerResolver.java @@ -1,7 +1,5 @@ package me.danwi.kato.common.argument; -import cn.hutool.core.io.IoUtil; -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; @@ -16,8 +14,6 @@ import javax.servlet.http.HttpServletRequest; import java.io.IOException; -import java.lang.reflect.Type; -import java.nio.charset.StandardCharsets; import java.util.Objects; /** @@ -41,7 +37,7 @@ */ public class MultiRequestBodyMethodArgumentHandlerResolver implements HandlerMethodArgumentResolver { - private static final String BODY_KEY = "BODY_KEY"; + private static final String KATO_JSON_NODE_KEY = "_KATO_JSON_NODE_KEY_"; private final ObjectMapper mapper; @@ -51,6 +47,7 @@ public MultiRequestBodyMethodArgumentHandlerResolver(ObjectMapper mapper) { @Override public boolean supportsParameter(MethodParameter methodParameter) { + // 类(katoservice),方法,参数没有passby, return methodParameter.hasParameterAnnotation(MultiRequestBody.class); } @@ -67,25 +64,16 @@ public Object resolveArgument(MethodParameter methodParameter, ModelAndViewConta key = methodParameter.getParameterName(); } // 获取请求body字符串 - final String body = getBody(nativeWebRequest); + final JsonNode rootNode = getJsonNode(nativeWebRequest); - if (!ObjectUtils.isEmpty(body)) { - final JsonNode jsonNode = mapper.readTree(body); - // key解析 - if (jsonNode.has(key)) { - // String key - final JsonNode node = jsonNode.get(key); - result = readValue(methodParameter, node.toString()); - } else { - // 如果没有指定key,则判断是否可以解析整个body - if (parameterAnnotation.parseBodyIfMissKey()) { - try { - result = readValue(methodParameter, body); - } catch (Exception e) { - // ignore - } - } + if (!ObjectUtils.isEmpty(rootNode)) { + // 尝试获取field + JsonNode node = rootNode.get(key); + // 如果json中不存在与参数名称相对应的field,且开启了全body映射 + if (node == null && parameterAnnotation.parseBodyIfMissKey()) { + node = rootNode; } + result = mapper.treeToValue(node, methodParameter.getParameterType()); } // 是否必填验证 @@ -96,30 +84,21 @@ public Object resolveArgument(MethodParameter methodParameter, ModelAndViewConta return result; } - private Object readValue(MethodParameter methodParameter, String content) throws IOException { - return mapper.readValue(content, new TypeReference() { - @Override - public Type getType() { - return methodParameter.getGenericParameterType(); - } - }); - } - /** * 多个参数解析时,从attribute中获取数据 * * @param nativeWebRequest - * @return + * @return JsonNode * @throws IOException */ - private String getBody(NativeWebRequest nativeWebRequest) throws IOException { - Object attribute = nativeWebRequest.getAttribute(BODY_KEY, WebRequest.SCOPE_REQUEST); + private JsonNode getJsonNode(NativeWebRequest nativeWebRequest) throws IOException { + Object attribute = nativeWebRequest.getAttribute(KATO_JSON_NODE_KEY, WebRequest.SCOPE_REQUEST); if (ObjectUtils.isEmpty(attribute)) { HttpServletRequest servletRequest = nativeWebRequest.getNativeRequest(HttpServletRequest.class); Assert.state(servletRequest != null, "No HttpServletRequest"); - attribute = IoUtil.read(servletRequest.getInputStream(), StandardCharsets.UTF_8); - nativeWebRequest.setAttribute(BODY_KEY, attribute, WebRequest.SCOPE_REQUEST); + attribute = mapper.readTree(servletRequest.getInputStream()); + nativeWebRequest.setAttribute(KATO_JSON_NODE_KEY, attribute, WebRequest.SCOPE_REQUEST); } - return (String) attribute; + return (JsonNode) attribute; } } diff --git a/example/src/main/kotlin/me/danwi/kato/example/controller/MultiRequestBodyController.kt b/example/src/main/kotlin/me/danwi/kato/example/controller/MultiRequestBodyController.kt index 4001b73..00ec33b 100644 --- a/example/src/main/kotlin/me/danwi/kato/example/controller/MultiRequestBodyController.kt +++ b/example/src/main/kotlin/me/danwi/kato/example/controller/MultiRequestBodyController.kt @@ -24,6 +24,16 @@ class MultiRequestBodyController { fun multiRequestObj(@MultiRequestBody obj: TestEntity): TestEntity { return obj } + + @PostMapping("/multiRequestObj2") + fun multiRequestObj2( + @MultiRequestBody obj: TestEntity, + @MultiRequestBody id: Int, + @MultiRequestBody obj2: TestEntity2 + ): TestEntity { + return obj + } } -data class TestEntity(val id: Int, val name: String?) \ No newline at end of file +data class TestEntity(val id: Int, val name: String?) +data class TestEntity2(val id: Int, val unkonw: String?) \ No newline at end of file diff --git a/example/src/test/java/me/danwi/kato/example/MultiRequestBodyControllerTest.java b/example/src/test/java/me/danwi/kato/example/MultiRequestBodyControllerTest.java index 684074c..3bfe093 100644 --- a/example/src/test/java/me/danwi/kato/example/MultiRequestBodyControllerTest.java +++ b/example/src/test/java/me/danwi/kato/example/MultiRequestBodyControllerTest.java @@ -30,7 +30,6 @@ void testMultiRequest() throws JsonProcessingException { assertThat(result.getBody()).isEqualTo(test); } - @Test void testMultiRequestSingle() { final int id = 19; @@ -38,11 +37,17 @@ void testMultiRequestSingle() { assertThat(Objects.requireNonNull(result.getBody()).getId()).isEqualTo(id); } - @Test void testMultiRequestObj() throws JsonProcessingException { final TestEntity test = new TestEntity(1234, "tesdfst"); final ResponseEntity result = restTemplate.postForEntity("/multiRequestObj", objectMapper.writeValueAsString(test), TestEntity.class); assertThat(result.getBody()).isEqualTo(test); } + + @Test + void multiRequestObj2() throws JsonProcessingException { + final TestEntity test = new TestEntity(1234, "tesdfst"); + final ResponseEntity result = restTemplate.postForEntity("/multiRequestObj2", objectMapper.writeValueAsString(test), TestEntity.class); + assertThat(result.getBody()).isEqualTo(test); + } } From dcf3775a481970abec16b1c6c21618b7c1b8d89a Mon Sep 17 00:00:00 2001 From: WJY Date: Wed, 6 Jul 2022 11:49:31 +0800 Subject: [PATCH 07/24] =?UTF-8?q?argument=E8=A7=A3=E6=9E=90=E8=BF=81?= =?UTF-8?q?=E7=A7=BB=E5=88=B0server?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/kotlin/me/danwi/kato/example/ExampleApplication.kt | 2 +- server/build.gradle.kts | 1 + .../src/main/java/me/danwi/kato/server/EnableKatoServer.java | 3 ++- .../kato/server}/argument/MethodArgumentHandlerConfig.java | 2 +- .../MultiRequestBodyMethodArgumentHandlerResolver.java | 5 +++-- 5 files changed, 8 insertions(+), 5 deletions(-) rename {common/src/main/java/me/danwi/kato/common => server/src/main/java/me/danwi/kato/server}/argument/MethodArgumentHandlerConfig.java (95%) rename {common/src/main/java/me/danwi/kato/common => server/src/main/java/me/danwi/kato/server}/argument/MultiRequestBodyMethodArgumentHandlerResolver.java (96%) diff --git a/example/src/main/kotlin/me/danwi/kato/example/ExampleApplication.kt b/example/src/main/kotlin/me/danwi/kato/example/ExampleApplication.kt index 6f60df1..a2d201f 100644 --- a/example/src/main/kotlin/me/danwi/kato/example/ExampleApplication.kt +++ b/example/src/main/kotlin/me/danwi/kato/example/ExampleApplication.kt @@ -1,6 +1,6 @@ package me.danwi.kato.example -import me.danwi.kato.common.argument.MethodArgumentHandlerConfig +import me.danwi.kato.server.argument.MethodArgumentHandlerConfig import me.danwi.kato.common.exception.ExceptionExtraDataHolder import me.danwi.kato.common.exception.KatoCommonException import me.danwi.kato.common.exception.KatoException diff --git a/server/build.gradle.kts b/server/build.gradle.kts index c826dfc..cd3e9a0 100644 --- a/server/build.gradle.kts +++ b/server/build.gradle.kts @@ -13,6 +13,7 @@ dependencies { compileOnly("org.springframework.security:spring-security-core:5.6.3") compileOnly("org.springframework.boot:spring-boot-autoconfigure:2.6.7") compileOnly("com.fasterxml.jackson.core:jackson-databind:2.13.2.2") + compileOnly("org.apache.tomcat.embed:tomcat-embed-core:9.0.62") testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.2") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") diff --git a/server/src/main/java/me/danwi/kato/server/EnableKatoServer.java b/server/src/main/java/me/danwi/kato/server/EnableKatoServer.java index 920a63b..08c2f42 100644 --- a/server/src/main/java/me/danwi/kato/server/EnableKatoServer.java +++ b/server/src/main/java/me/danwi/kato/server/EnableKatoServer.java @@ -1,5 +1,6 @@ package me.danwi.kato.server; +import me.danwi.kato.server.argument.MethodArgumentHandlerConfig; import org.springframework.context.annotation.Import; import java.lang.annotation.ElementType; @@ -9,7 +10,7 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) -@Import({KatoConfig.class, KatoConfigWithSecurity.class}) +@Import({KatoConfig.class, KatoConfigWithSecurity.class, MethodArgumentHandlerConfig.class}) public @interface EnableKatoServer { } diff --git a/common/src/main/java/me/danwi/kato/common/argument/MethodArgumentHandlerConfig.java b/server/src/main/java/me/danwi/kato/server/argument/MethodArgumentHandlerConfig.java similarity index 95% rename from common/src/main/java/me/danwi/kato/common/argument/MethodArgumentHandlerConfig.java rename to server/src/main/java/me/danwi/kato/server/argument/MethodArgumentHandlerConfig.java index df68fdd..341b566 100644 --- a/common/src/main/java/me/danwi/kato/common/argument/MethodArgumentHandlerConfig.java +++ b/server/src/main/java/me/danwi/kato/server/argument/MethodArgumentHandlerConfig.java @@ -1,4 +1,4 @@ -package me.danwi.kato.common.argument; +package me.danwi.kato.server.argument; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.context.annotation.Configuration; diff --git a/common/src/main/java/me/danwi/kato/common/argument/MultiRequestBodyMethodArgumentHandlerResolver.java b/server/src/main/java/me/danwi/kato/server/argument/MultiRequestBodyMethodArgumentHandlerResolver.java similarity index 96% rename from common/src/main/java/me/danwi/kato/common/argument/MultiRequestBodyMethodArgumentHandlerResolver.java rename to server/src/main/java/me/danwi/kato/server/argument/MultiRequestBodyMethodArgumentHandlerResolver.java index 5451738..e95a4ae 100644 --- a/common/src/main/java/me/danwi/kato/common/argument/MultiRequestBodyMethodArgumentHandlerResolver.java +++ b/server/src/main/java/me/danwi/kato/server/argument/MultiRequestBodyMethodArgumentHandlerResolver.java @@ -1,8 +1,9 @@ -package me.danwi.kato.common.argument; +package me.danwi.kato.server.argument; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import me.danwi.kato.common.argument.MultiRequestBody; import org.springframework.core.MethodParameter; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; @@ -47,7 +48,7 @@ public MultiRequestBodyMethodArgumentHandlerResolver(ObjectMapper mapper) { @Override public boolean supportsParameter(MethodParameter methodParameter) { - // 类(katoservice),方法,参数没有passby, + // TODO 类(katoservice),方法,参数没有passby, return methodParameter.hasParameterAnnotation(MultiRequestBody.class); } From b1a9ff2b23a5d7347c14bf0d66e2d88537573b9c Mon Sep 17 00:00:00 2001 From: WJY Date: Wed, 6 Jul 2022 15:22:48 +0800 Subject: [PATCH 08/24] =?UTF-8?q?=E8=87=AA=E5=8A=A8=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E8=BF=81=E7=A7=BB=E5=88=B0@EnableKatoServer=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/kotlin/me/danwi/kato/example/ExampleApplication.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/example/src/main/kotlin/me/danwi/kato/example/ExampleApplication.kt b/example/src/main/kotlin/me/danwi/kato/example/ExampleApplication.kt index a2d201f..5e2e489 100644 --- a/example/src/main/kotlin/me/danwi/kato/example/ExampleApplication.kt +++ b/example/src/main/kotlin/me/danwi/kato/example/ExampleApplication.kt @@ -13,7 +13,6 @@ import org.springframework.web.bind.annotation.RestController @SpringBootApplication @EnableKatoServer -@Import(MethodArgumentHandlerConfig::class) class ExampleApplication fun main(args: Array) { From c4cd41b417cf459341fb7ff662d631b4790808f8 Mon Sep 17 00:00:00 2001 From: WJY Date: Wed, 6 Jul 2022 15:23:49 +0800 Subject: [PATCH 09/24] =?UTF-8?q?MultiRequestBody=E6=B3=A8=E8=A7=A3?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E9=BB=98=E8=AE=A4=E4=B8=8D=E5=86=99=E5=BD=A2?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/argument/MultiRequestBody.java | 2 +- .../java/me/danwi/kato/server/PassByKato.java | 2 +- ...uestBodyMethodArgumentHandlerResolver.java | 67 ++++++++++++++----- 3 files changed, 54 insertions(+), 17 deletions(-) diff --git a/common/src/main/java/me/danwi/kato/common/argument/MultiRequestBody.java b/common/src/main/java/me/danwi/kato/common/argument/MultiRequestBody.java index 491992f..5bcf67f 100644 --- a/common/src/main/java/me/danwi/kato/common/argument/MultiRequestBody.java +++ b/common/src/main/java/me/danwi/kato/common/argument/MultiRequestBody.java @@ -3,7 +3,7 @@ import java.lang.annotation.*; /** - * @see MultiRequestBodyMethodArgumentHandlerResolver + * 参数解析 源数据 */ @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) diff --git a/server/src/main/java/me/danwi/kato/server/PassByKato.java b/server/src/main/java/me/danwi/kato/server/PassByKato.java index d5b9fb0..e28f2d0 100644 --- a/server/src/main/java/me/danwi/kato/server/PassByKato.java +++ b/server/src/main/java/me/danwi/kato/server/PassByKato.java @@ -9,6 +9,6 @@ * 注解在Controller/方法上,其结果不再由kato来处理 */ @Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE, ElementType.METHOD}) +@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER}) public @interface PassByKato { } diff --git a/server/src/main/java/me/danwi/kato/server/argument/MultiRequestBodyMethodArgumentHandlerResolver.java b/server/src/main/java/me/danwi/kato/server/argument/MultiRequestBodyMethodArgumentHandlerResolver.java index e95a4ae..74828cb 100644 --- a/server/src/main/java/me/danwi/kato/server/argument/MultiRequestBodyMethodArgumentHandlerResolver.java +++ b/server/src/main/java/me/danwi/kato/server/argument/MultiRequestBodyMethodArgumentHandlerResolver.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import me.danwi.kato.common.argument.MultiRequestBody; +import me.danwi.kato.server.PassByKato; import org.springframework.core.MethodParameter; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; @@ -15,6 +16,7 @@ import javax.servlet.http.HttpServletRequest; import java.io.IOException; +import java.lang.reflect.Method; import java.util.Objects; /** @@ -48,8 +50,15 @@ public MultiRequestBodyMethodArgumentHandlerResolver(ObjectMapper mapper) { @Override public boolean supportsParameter(MethodParameter methodParameter) { - // TODO 类(katoservice),方法,参数没有passby, - return methodParameter.hasParameterAnnotation(MultiRequestBody.class); + final Method method = methodParameter.getMethod(); + if (method == null) { + return false; + } + + // TODO 类(katoService) + return methodParameter.getDeclaringClass().getAnnotation(PassByKato.class) == null + && method.getAnnotation(PassByKato.class) == null + && !methodParameter.hasParameterAnnotation(PassByKato.class); } @Override @@ -57,34 +66,56 @@ public Object resolveArgument(MethodParameter methodParameter, ModelAndViewConta // 定义结果 Object result = null; - // 获取注解 - final MultiRequestBody parameterAnnotation = methodParameter.getParameterAnnotation(MultiRequestBody.class); - // 获取key - String key = parameterAnnotation.value(); - if (ObjectUtils.isEmpty(key)) { - key = methodParameter.getParameterName(); - } + // 获取参数配置信息 + final ParamInfo paramInfo = getParamInfo(methodParameter); + // 获取请求body字符串 final JsonNode rootNode = getJsonNode(nativeWebRequest); if (!ObjectUtils.isEmpty(rootNode)) { // 尝试获取field - JsonNode node = rootNode.get(key); + JsonNode node = rootNode.get(paramInfo.key); // 如果json中不存在与参数名称相对应的field,且开启了全body映射 - if (node == null && parameterAnnotation.parseBodyIfMissKey()) { - node = rootNode; + if ((node == null || node.isNull()) && paramInfo.parseBodyIfMissKey) { + try { + result = mapper.treeToValue(rootNode, methodParameter.getParameterType()); + } catch (Exception e) { + // 忽略 此时result=null,后续进行是否必填验证 + } + } else { + result = mapper.treeToValue(node, methodParameter.getParameterType()); } - result = mapper.treeToValue(node, methodParameter.getParameterType()); } // 是否必填验证 - if (Objects.isNull(result) && parameterAnnotation.required()) { - throw new IllegalArgumentException(String.format("required param %s is not present", key)); + if (Objects.isNull(result) && paramInfo.required) { + throw new IllegalArgumentException(String.format("缺少 %s 参数", paramInfo.key)); } return result; } + private ParamInfo getParamInfo(MethodParameter methodParameter) { + final ParamInfo paramInfo = new ParamInfo(); + MultiRequestBody parameterAnnotation = methodParameter.getParameterAnnotation(MultiRequestBody.class); + // 获取key + if (parameterAnnotation == null) { + paramInfo.key = methodParameter.getParameterName(); + } else { + paramInfo.key = parameterAnnotation.value(); + if (ObjectUtils.isEmpty(paramInfo.key)) { + paramInfo.key = methodParameter.getParameterName(); + } + paramInfo.required = parameterAnnotation.required(); + paramInfo.parseBodyIfMissKey = parameterAnnotation.parseBodyIfMissKey(); + } + // 校验是否获取到 key + if (ObjectUtils.isEmpty(paramInfo.key)) { + throw new IllegalArgumentException("JVM 版本不支持自动获取参数名,请手动使用 MultiRequestBody 注解指定value作为key"); + } + return paramInfo; + } + /** * 多个参数解析时,从attribute中获取数据 * @@ -102,4 +133,10 @@ private JsonNode getJsonNode(NativeWebRequest nativeWebRequest) throws IOExcepti } return (JsonNode) attribute; } + + private static class ParamInfo { + String key = ""; + boolean required = true; + boolean parseBodyIfMissKey = true; + } } From 8d048b4149bcf69d30d062444764da0ea2cbd726 Mon Sep 17 00:00:00 2001 From: WJY Date: Wed, 6 Jul 2022 15:24:37 +0800 Subject: [PATCH 10/24] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=8D=95=E5=85=83?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E5=B7=A5=E7=A8=8B=20-=20=E5=86=99MultiReques?= =?UTF-8?q?tBody=E6=B3=A8=E8=A7=A3=E6=B5=8B=E8=AF=95=20-=20=E4=B8=8D?= =?UTF-8?q?=E5=86=99MultiRequestBody=E6=B3=A8=E8=A7=A3=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=20-=20PassByKato=E6=B3=A8=E8=A7=A3=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../me/danwi/kato/example/argument/Entity.kt | 12 +++ .../MultiRequestBodyController.kt | 20 +++-- .../MultiRequestBodyWithOutAnnoController.kt | 59 ++++++++++++ .../MultiRequestBodyControllerTest.java | 29 +++++- ...iRequestBodyWithOutAnnoControllerTest.java | 89 +++++++++++++++++++ 5 files changed, 198 insertions(+), 11 deletions(-) create mode 100644 example/src/main/kotlin/me/danwi/kato/example/argument/Entity.kt rename example/src/main/kotlin/me/danwi/kato/example/{controller => argument}/MultiRequestBodyController.kt (66%) create mode 100644 example/src/main/kotlin/me/danwi/kato/example/argument/MultiRequestBodyWithOutAnnoController.kt create mode 100644 example/src/test/java/me/danwi/kato/example/MultiRequestBodyWithOutAnnoControllerTest.java diff --git a/example/src/main/kotlin/me/danwi/kato/example/argument/Entity.kt b/example/src/main/kotlin/me/danwi/kato/example/argument/Entity.kt new file mode 100644 index 0000000..ba15027 --- /dev/null +++ b/example/src/main/kotlin/me/danwi/kato/example/argument/Entity.kt @@ -0,0 +1,12 @@ +package me.danwi.kato.example.argument + +data class TestEntity(val id: Int?, val name: String?) +data class TestEntity2(val id: Int?, val unknown: String?) +data class TestEntity3(val id: Int?, val obj: TestEntity?) + +data class TestEntityAll( + val id: Int? = null, + val obj: TestEntity2? = null, + val unknown: String? = null, + val name: String? = null +) \ No newline at end of file diff --git a/example/src/main/kotlin/me/danwi/kato/example/controller/MultiRequestBodyController.kt b/example/src/main/kotlin/me/danwi/kato/example/argument/MultiRequestBodyController.kt similarity index 66% rename from example/src/main/kotlin/me/danwi/kato/example/controller/MultiRequestBodyController.kt rename to example/src/main/kotlin/me/danwi/kato/example/argument/MultiRequestBodyController.kt index 00ec33b..34366da 100644 --- a/example/src/main/kotlin/me/danwi/kato/example/controller/MultiRequestBodyController.kt +++ b/example/src/main/kotlin/me/danwi/kato/example/argument/MultiRequestBodyController.kt @@ -1,4 +1,4 @@ -package me.danwi.kato.example.controller +package me.danwi.kato.example.argument import me.danwi.kato.common.argument.MultiRequestBody import org.springframework.web.bind.annotation.PostMapping @@ -11,7 +11,7 @@ import org.springframework.web.bind.annotation.RestController class MultiRequestBodyController { @PostMapping("/multiRequest") - fun multiRequest(@MultiRequestBody id: Int, @MultiRequestBody(required = false) name: String): TestEntity { + fun multiRequest(@MultiRequestBody id: Int, name: String): TestEntity { return TestEntity(id, name) } @@ -30,10 +30,16 @@ class MultiRequestBodyController { @MultiRequestBody obj: TestEntity, @MultiRequestBody id: Int, @MultiRequestBody obj2: TestEntity2 - ): TestEntity { - return obj + ): TestEntity3 { + return TestEntity3(id, obj) } -} -data class TestEntity(val id: Int, val name: String?) -data class TestEntity2(val id: Int, val unkonw: String?) \ No newline at end of file + @PostMapping("/multiRequestObj3") + fun multiRequestObj3( + @MultiRequestBody obj: TestEntity2, + @MultiRequestBody id: Int, + @MultiRequestBody obj2: TestEntity3 + ): TestEntity3 { + return obj2 + } +} diff --git a/example/src/main/kotlin/me/danwi/kato/example/argument/MultiRequestBodyWithOutAnnoController.kt b/example/src/main/kotlin/me/danwi/kato/example/argument/MultiRequestBodyWithOutAnnoController.kt new file mode 100644 index 0000000..a7733ee --- /dev/null +++ b/example/src/main/kotlin/me/danwi/kato/example/argument/MultiRequestBodyWithOutAnnoController.kt @@ -0,0 +1,59 @@ +package me.danwi.kato.example.argument + +import me.danwi.kato.server.PassByKato +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +/** + * @author wjy + */ +@RestController +@RequestMapping("/withOutAnno") +class MultiRequestBodyWithOutAnnoController { + + @PassByKato + @PostMapping("/multiRequestPassByMethod") + fun multiRequestPassByMethod(id: Int?, name: String?): TestEntity { + return TestEntity(id, name) + } + + + @PostMapping("/multiRequestPassByParam") + fun multiRequestPassByParam(id: Int?, @PassByKato name: String?): TestEntity { + return TestEntity(id, name) + } + + @PostMapping("/multiRequest") + fun multiRequest(id: Int, name: String): TestEntity { + return TestEntity(id, name) + } + + @PostMapping("/multiRequestSingle") + fun multiRequestSingle(id: Int): TestEntity { + return TestEntity(id, null) + } + + @PostMapping("/multiRequestObj") + fun multiRequestObj(obj: TestEntity): TestEntity { + return obj + } + + @PostMapping("/multiRequestObj2") + fun multiRequestObj2( + obj: TestEntity, + id: Int, + obj2: TestEntity2 + ): TestEntity3 { + return TestEntity3(id, obj) + } + + @PostMapping("/multiRequestObj3") + fun multiRequestObj3( + obj: TestEntity2, + id: Int, + obj2: TestEntity3 + ): TestEntity3 { + return obj2 + } +} diff --git a/example/src/test/java/me/danwi/kato/example/MultiRequestBodyControllerTest.java b/example/src/test/java/me/danwi/kato/example/MultiRequestBodyControllerTest.java index 3bfe093..470fee5 100644 --- a/example/src/test/java/me/danwi/kato/example/MultiRequestBodyControllerTest.java +++ b/example/src/test/java/me/danwi/kato/example/MultiRequestBodyControllerTest.java @@ -2,13 +2,17 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import me.danwi.kato.example.controller.TestEntity; +import me.danwi.kato.example.argument.TestEntity; +import me.danwi.kato.example.argument.TestEntity3; +import me.danwi.kato.example.argument.TestEntityAll; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.http.ResponseEntity; +import java.util.Map; import java.util.Objects; import static org.assertj.core.api.Assertions.assertThat; @@ -23,6 +27,12 @@ public class MultiRequestBodyControllerTest { @Autowired private ObjectMapper objectMapper; + @Test + void testMultiRequestNoJson() throws JsonProcessingException { + final ResponseEntity result = restTemplate.postForEntity("/multiRequest", 123, TestEntity.class); + assertThat(new TestEntity(123, "123")).isEqualTo(result.getBody()); + } + @Test void testMultiRequest() throws JsonProcessingException { final TestEntity test = new TestEntity(1, "test"); @@ -46,8 +56,19 @@ void testMultiRequestObj() throws JsonProcessingException { @Test void multiRequestObj2() throws JsonProcessingException { - final TestEntity test = new TestEntity(1234, "tesdfst"); - final ResponseEntity result = restTemplate.postForEntity("/multiRequestObj2", objectMapper.writeValueAsString(test), TestEntity.class); - assertThat(result.getBody()).isEqualTo(test); + final TestEntityAll test = new TestEntityAll(1234, null, "unknow", "name"); + final ResponseEntity result = restTemplate.postForEntity("/multiRequestObj2", objectMapper.writeValueAsString(test), TestEntity3.class); + assertThat(new TestEntity3(test.getId(), new TestEntity(test.getId(), test.getName()))).isEqualTo(result.getBody()); } + + @Test + void multiRequestObjLessParam() throws JsonProcessingException { + final TestEntityAll test = new TestEntityAll(null, null, "unknow", null); + final ResponseEntity result = restTemplate.postForEntity("/multiRequestObj2", objectMapper.writeValueAsString(test), Map.class); + Assertions.assertTrue(result.getBody().get("message").toString().contains("缺少 id 参数")); + } + + + // 不使用注解 + } diff --git a/example/src/test/java/me/danwi/kato/example/MultiRequestBodyWithOutAnnoControllerTest.java b/example/src/test/java/me/danwi/kato/example/MultiRequestBodyWithOutAnnoControllerTest.java new file mode 100644 index 0000000..f289928 --- /dev/null +++ b/example/src/test/java/me/danwi/kato/example/MultiRequestBodyWithOutAnnoControllerTest.java @@ -0,0 +1,89 @@ +package me.danwi.kato.example; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import me.danwi.kato.example.argument.TestEntity; +import me.danwi.kato.example.argument.TestEntity3; +import me.danwi.kato.example.argument.TestEntityAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.ResponseEntity; + +import java.util.Map; +import java.util.Objects; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author wjy + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class MultiRequestBodyWithOutAnnoControllerTest { + @Autowired + private TestRestTemplate restTemplate; + @Autowired + private ObjectMapper objectMapper; + + + @Test + void multiRequestPassByMethod() throws JsonProcessingException { + final TestEntity test = new TestEntity(1, "test"); + final ResponseEntity result = restTemplate.postForEntity("/withOutAnno/multiRequestPassByMethod", objectMapper.writeValueAsString(test), TestEntity.class); + assertThat(result.getBody()).isEqualTo(new TestEntity(null, null)); + } + + @Test + void multiRequestPassByParam() throws JsonProcessingException { + final TestEntity test = new TestEntity(1, "test"); + final ResponseEntity result = restTemplate.postForEntity("/withOutAnno/multiRequestPassByParam", objectMapper.writeValueAsString(test), TestEntity.class); + assertThat(result.getBody()).isEqualTo(new TestEntity(test.getId(), null)); + } + + @Test + void testMultiRequestNoJson() throws JsonProcessingException { + final ResponseEntity result = restTemplate.postForEntity("/withOutAnno/multiRequest", 123, TestEntity.class); + assertThat(new TestEntity(123, "123")).isEqualTo(result.getBody()); + } + + @Test + void testMultiRequest() throws JsonProcessingException { + final TestEntity test = new TestEntity(1, "test"); + final ResponseEntity result = restTemplate.postForEntity("/withOutAnno/multiRequest", objectMapper.writeValueAsString(test), TestEntity.class); + assertThat(result.getBody()).isEqualTo(test); + } + + @Test + void testMultiRequestSingle() { + final int id = 19; + final ResponseEntity result = restTemplate.postForEntity("/withOutAnno/multiRequestSingle", id, TestEntity.class); + assertThat(Objects.requireNonNull(result.getBody()).getId()).isEqualTo(id); + } + + @Test + void testMultiRequestObj() throws JsonProcessingException { + final TestEntity test = new TestEntity(1234, "tesdfst"); + final ResponseEntity result = restTemplate.postForEntity("/withOutAnno/multiRequestObj", objectMapper.writeValueAsString(test), TestEntity.class); + assertThat(result.getBody()).isEqualTo(test); + } + + @Test + void multiRequestObj2() throws JsonProcessingException { + final TestEntityAll test = new TestEntityAll(1234, null, "unknow", "name"); + final ResponseEntity result = restTemplate.postForEntity("/withOutAnno/multiRequestObj2", objectMapper.writeValueAsString(test), TestEntity3.class); + assertThat(new TestEntity3(test.getId(), new TestEntity(test.getId(), test.getName()))).isEqualTo(result.getBody()); + } + + @Test + void multiRequestObjLessParam() throws JsonProcessingException { + final TestEntityAll test = new TestEntityAll(null, null, "unknow", null); + final ResponseEntity result = restTemplate.postForEntity("/withOutAnno/multiRequestObj2", objectMapper.writeValueAsString(test), Map.class); + Assertions.assertTrue(result.getBody().get("message").toString().contains("缺少 id 参数")); + } + + + // 不使用注解 + +} From d7a9923cfa08cc2f0d2f229e8f2ab45ae02edffc Mon Sep 17 00:00:00 2001 From: WJY Date: Wed, 6 Jul 2022 17:01:32 +0800 Subject: [PATCH 11/24] =?UTF-8?q?=E5=A2=9E=E5=8A=A0kotlin=E5=8F=AF?= =?UTF-8?q?=E6=8E=A7=E7=B1=BB=E5=9E=8B=E6=94=AF=E6=8C=81=20-=20kotlin?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=EF=BC=8C=E4=BB=A5kotlin=E5=8F=AF=E7=A9=BA?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E4=BD=9C=E4=B8=BA=E6=98=AF=E5=90=A6=E5=BF=85?= =?UTF-8?q?=E9=A1=BB=E5=88=A4=E6=96=AD=20-=20java=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E9=9D=9E=E7=A9=BA=EF=BC=8C=E5=8F=AF=E4=BB=A5?= =?UTF-8?q?=E5=9C=A8MultiRequestBody=E6=B3=A8=E8=A7=A3=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/build.gradle.kts | 3 ++- ...MultiRequestBodyMethodArgumentHandlerResolver.java | 11 ++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/server/build.gradle.kts b/server/build.gradle.kts index cd3e9a0..64f38fa 100644 --- a/server/build.gradle.kts +++ b/server/build.gradle.kts @@ -13,7 +13,8 @@ dependencies { compileOnly("org.springframework.security:spring-security-core:5.6.3") compileOnly("org.springframework.boot:spring-boot-autoconfigure:2.6.7") compileOnly("com.fasterxml.jackson.core:jackson-databind:2.13.2.2") - compileOnly("org.apache.tomcat.embed:tomcat-embed-core:9.0.62") + compileOnly("javax.servlet:javax.servlet-api:4.0.1") + implementation("org.jetbrains.kotlin:kotlin-reflect:1.7.0") testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.2") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") diff --git a/server/src/main/java/me/danwi/kato/server/argument/MultiRequestBodyMethodArgumentHandlerResolver.java b/server/src/main/java/me/danwi/kato/server/argument/MultiRequestBodyMethodArgumentHandlerResolver.java index 74828cb..b1fb17a 100644 --- a/server/src/main/java/me/danwi/kato/server/argument/MultiRequestBodyMethodArgumentHandlerResolver.java +++ b/server/src/main/java/me/danwi/kato/server/argument/MultiRequestBodyMethodArgumentHandlerResolver.java @@ -3,6 +3,8 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import kotlin.reflect.KType; +import kotlin.reflect.jvm.ReflectJvmMapping; import me.danwi.kato.common.argument.MultiRequestBody; import me.danwi.kato.server.PassByKato; import org.springframework.core.MethodParameter; @@ -87,8 +89,15 @@ public Object resolveArgument(MethodParameter methodParameter, ModelAndViewConta } } + final KType type = ReflectJvmMapping + .getKotlinFunction(methodParameter.getMethod()) + .getParameters() + .get(methodParameter.getParameterIndex() + 1) + .getType(); + final boolean isJavaCode = type.toString().endsWith("!"); + // 是否必填验证 - if (Objects.isNull(result) && paramInfo.required) { + if (Objects.isNull(result) && (isJavaCode ? paramInfo.required : !type.isMarkedNullable())) { throw new IllegalArgumentException(String.format("缺少 %s 参数", paramInfo.key)); } From 543d104afc7a3a4bba552ef2b70681521d4d31ae Mon Sep 17 00:00:00 2001 From: WJY Date: Wed, 6 Jul 2022 17:01:56 +0800 Subject: [PATCH 12/24] =?UTF-8?q?=E5=A2=9E=E5=8A=A0kotlin=E5=8F=AF?= =?UTF-8?q?=E6=8E=A7=E7=B1=BB=E5=9E=8B=E6=94=AF=E6=8C=81=20-=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E5=8D=95=E5=85=83=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../argument/MultiRequestBodyController.kt | 10 +++++++ .../MultiRequestBodyWithOutAnnoController.kt | 1 - .../MultiRequestBodyControllerTest.java | 28 +++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/example/src/main/kotlin/me/danwi/kato/example/argument/MultiRequestBodyController.kt b/example/src/main/kotlin/me/danwi/kato/example/argument/MultiRequestBodyController.kt index 34366da..d98afc7 100644 --- a/example/src/main/kotlin/me/danwi/kato/example/argument/MultiRequestBodyController.kt +++ b/example/src/main/kotlin/me/danwi/kato/example/argument/MultiRequestBodyController.kt @@ -10,6 +10,16 @@ import org.springframework.web.bind.annotation.RestController @RestController class MultiRequestBodyController { + @PostMapping("/multiRequestKotlinNullable") + fun multiRequestKotlinNullable(@MultiRequestBody id: Int?, name: String): TestEntity { + return TestEntity(id, name) + } + + @PostMapping("/multiRequestKotlinNullableErr") + fun multiRequestKotlinNullableErr(@MultiRequestBody id: Int, name: String): TestEntity { + return TestEntity(id, name) + } + @PostMapping("/multiRequest") fun multiRequest(@MultiRequestBody id: Int, name: String): TestEntity { return TestEntity(id, name) diff --git a/example/src/main/kotlin/me/danwi/kato/example/argument/MultiRequestBodyWithOutAnnoController.kt b/example/src/main/kotlin/me/danwi/kato/example/argument/MultiRequestBodyWithOutAnnoController.kt index a7733ee..01a174a 100644 --- a/example/src/main/kotlin/me/danwi/kato/example/argument/MultiRequestBodyWithOutAnnoController.kt +++ b/example/src/main/kotlin/me/danwi/kato/example/argument/MultiRequestBodyWithOutAnnoController.kt @@ -18,7 +18,6 @@ class MultiRequestBodyWithOutAnnoController { return TestEntity(id, name) } - @PostMapping("/multiRequestPassByParam") fun multiRequestPassByParam(id: Int?, @PassByKato name: String?): TestEntity { return TestEntity(id, name) diff --git a/example/src/test/java/me/danwi/kato/example/MultiRequestBodyControllerTest.java b/example/src/test/java/me/danwi/kato/example/MultiRequestBodyControllerTest.java index 470fee5..cf5c2e0 100644 --- a/example/src/test/java/me/danwi/kato/example/MultiRequestBodyControllerTest.java +++ b/example/src/test/java/me/danwi/kato/example/MultiRequestBodyControllerTest.java @@ -27,6 +27,34 @@ public class MultiRequestBodyControllerTest { @Autowired private ObjectMapper objectMapper; + @Test + void multiRequestKotlinNullableJava() throws JsonProcessingException { + final TestEntity test = new TestEntity(null, "test"); + final ResponseEntity result = restTemplate.postForEntity("/multiRequestKotlinNullableJava", objectMapper.writeValueAsString(test), TestEntity.class); + assertThat(new TestEntity(null, null)).isEqualTo(result.getBody()); + } + + @Test + void multiRequestKotlinNullableJavaErr() throws JsonProcessingException { + final TestEntity test = new TestEntity(null, "test"); + final ResponseEntity result = restTemplate.postForEntity("/multiRequestKotlinNullableJavaErr", objectMapper.writeValueAsString(test), Map.class); + Assertions.assertTrue(result.getBody().get("message").toString().contains("缺少 id 参数")); + } + + @Test + void multiRequestKotlinNullableErr() throws JsonProcessingException { + final TestEntity test = new TestEntity(null, "test"); + final ResponseEntity result = restTemplate.postForEntity("/multiRequestKotlinNullableErr", objectMapper.writeValueAsString(test), Map.class); + Assertions.assertTrue(result.getBody().get("message").toString().contains("缺少 id 参数")); + } + + @Test + void multiRequestKotlinNullable() throws JsonProcessingException { + final TestEntity test = new TestEntity(null, "test"); + final ResponseEntity result = restTemplate.postForEntity("/multiRequestKotlinNullable", objectMapper.writeValueAsString(test), TestEntity.class); + assertThat(test).isEqualTo(result.getBody()); + } + @Test void testMultiRequestNoJson() throws JsonProcessingException { final ResponseEntity result = restTemplate.postForEntity("/multiRequest", 123, TestEntity.class); From 162eded5731ac9bb953a7c8c5eeec512485949d4 Mon Sep 17 00:00:00 2001 From: WJY Date: Wed, 6 Jul 2022 17:04:23 +0800 Subject: [PATCH 13/24] =?UTF-8?q?=E5=A2=9E=E5=8A=A0kotlin=E5=8F=AF?= =?UTF-8?q?=E6=8E=A7=E7=B1=BB=E5=9E=8B=E6=94=AF=E6=8C=81=20-=20java?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E4=BE=8B=E5=AD=90=EF=BC=88=E6=BC=8Fcommit?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../JavaMultiRequestBodyController.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 example/src/main/java/me/danwi/kato/example/argument/JavaMultiRequestBodyController.java diff --git a/example/src/main/java/me/danwi/kato/example/argument/JavaMultiRequestBodyController.java b/example/src/main/java/me/danwi/kato/example/argument/JavaMultiRequestBodyController.java new file mode 100644 index 0000000..91e188f --- /dev/null +++ b/example/src/main/java/me/danwi/kato/example/argument/JavaMultiRequestBodyController.java @@ -0,0 +1,21 @@ +package me.danwi.kato.example.argument; + +import me.danwi.kato.common.argument.MultiRequestBody; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author wjy + */ +@RestController +public class JavaMultiRequestBodyController { + @PostMapping("/multiRequestKotlinNullableJava") + public TestEntity multiRequestKotlinNullableJava(@MultiRequestBody(required = false) Integer id) { + return new TestEntity(id, null); + } + + @PostMapping("/multiRequestKotlinNullableJavaErr") + public TestEntity multiRequestKotlinNullableJavaErr(@MultiRequestBody Integer id) { + return new TestEntity(id, null); + } +} From 7853b032df2d2552f559a1ab906289d23c0863b4 Mon Sep 17 00:00:00 2001 From: WJY Date: Thu, 7 Jul 2022 09:58:35 +0800 Subject: [PATCH 14/24] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E8=BF=BD=E8=B8=AA=E6=89=93=E5=8D=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/build.gradle.kts | 1 + .../MultiRequestBodyMethodArgumentHandlerResolver.java | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/server/build.gradle.kts b/server/build.gradle.kts index 64f38fa..01abee9 100644 --- a/server/build.gradle.kts +++ b/server/build.gradle.kts @@ -15,6 +15,7 @@ dependencies { compileOnly("com.fasterxml.jackson.core:jackson-databind:2.13.2.2") compileOnly("javax.servlet:javax.servlet-api:4.0.1") implementation("org.jetbrains.kotlin:kotlin-reflect:1.7.0") + implementation("org.slf4j:slf4j-api:1.7.36") testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.2") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") diff --git a/server/src/main/java/me/danwi/kato/server/argument/MultiRequestBodyMethodArgumentHandlerResolver.java b/server/src/main/java/me/danwi/kato/server/argument/MultiRequestBodyMethodArgumentHandlerResolver.java index b1fb17a..28f429c 100644 --- a/server/src/main/java/me/danwi/kato/server/argument/MultiRequestBodyMethodArgumentHandlerResolver.java +++ b/server/src/main/java/me/danwi/kato/server/argument/MultiRequestBodyMethodArgumentHandlerResolver.java @@ -7,6 +7,8 @@ import kotlin.reflect.jvm.ReflectJvmMapping; import me.danwi.kato.common.argument.MultiRequestBody; import me.danwi.kato.server.PassByKato; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.core.MethodParameter; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; @@ -42,6 +44,8 @@ */ public class MultiRequestBodyMethodArgumentHandlerResolver implements HandlerMethodArgumentResolver { + private static final Logger logger = LoggerFactory.getLogger(MultiRequestBodyMethodArgumentHandlerResolver.class); + private static final String KATO_JSON_NODE_KEY = "_KATO_JSON_NODE_KEY_"; private final ObjectMapper mapper; @@ -100,7 +104,7 @@ public Object resolveArgument(MethodParameter methodParameter, ModelAndViewConta if (Objects.isNull(result) && (isJavaCode ? paramInfo.required : !type.isMarkedNullable())) { throw new IllegalArgumentException(String.format("缺少 %s 参数", paramInfo.key)); } - + logger.debug("解析参数:key={},value={}", paramInfo.key, result); return result; } @@ -139,6 +143,8 @@ private JsonNode getJsonNode(NativeWebRequest nativeWebRequest) throws IOExcepti Assert.state(servletRequest != null, "No HttpServletRequest"); attribute = mapper.readTree(servletRequest.getInputStream()); nativeWebRequest.setAttribute(KATO_JSON_NODE_KEY, attribute, WebRequest.SCOPE_REQUEST); + + logger.debug("解析请求数据 [{}] 为 [{}]", nativeWebRequest.getHeader("accept"), attribute); } return (JsonNode) attribute; } From 7d1f577c158de594842ab3ba0fe339bb82317a00 Mon Sep 17 00:00:00 2001 From: WJY Date: Thu, 7 Jul 2022 10:05:47 +0800 Subject: [PATCH 15/24] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E8=BF=BD=E8=B8=AA=E6=89=93=E5=8D=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/me/danwi/kato/common/ExceptionResult.java | 10 ++++++++++ .../me/danwi/kato/server/KatoResponseBodyAdvice.java | 7 +++++++ .../MultiRequestBodyMethodArgumentHandlerResolver.java | 6 +++--- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/common/src/main/java/me/danwi/kato/common/ExceptionResult.java b/common/src/main/java/me/danwi/kato/common/ExceptionResult.java index 99b79bc..407a20c 100644 --- a/common/src/main/java/me/danwi/kato/common/ExceptionResult.java +++ b/common/src/main/java/me/danwi/kato/common/ExceptionResult.java @@ -33,4 +33,14 @@ public Map getData() { public void setData(Map data) { this.data = data; } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("ExceptionResult{"); + sb.append("id='").append(id).append('\''); + sb.append(", message='").append(message).append('\''); + sb.append(", data=").append(data); + sb.append('}'); + return sb.toString(); + } } diff --git a/server/src/main/java/me/danwi/kato/server/KatoResponseBodyAdvice.java b/server/src/main/java/me/danwi/kato/server/KatoResponseBodyAdvice.java index f621af4..7311836 100644 --- a/server/src/main/java/me/danwi/kato/server/KatoResponseBodyAdvice.java +++ b/server/src/main/java/me/danwi/kato/server/KatoResponseBodyAdvice.java @@ -6,6 +6,8 @@ import me.danwi.kato.common.exception.ExceptionExtraDataHolder; import me.danwi.kato.common.exception.KatoException; import me.danwi.kato.common.exception.KatoUndeclaredException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.core.MethodParameter; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; @@ -21,6 +23,9 @@ @RestControllerAdvice public class KatoResponseBodyAdvice implements ResponseBodyAdvice { + + private final static Logger LOGGER = LoggerFactory.getLogger(KatoResponseBodyAdvice.class); + private final ObjectMapper mapper = new ObjectMapper(); @Override @@ -46,6 +51,8 @@ public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType } //设置异常状态码 response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR); + LOGGER.debug("捕获异常:", (KatoException) body); + LOGGER.debug("异常:{} 转换为:{}", body.getClass().getName(), exceptionResult); return exceptionResult; } diff --git a/server/src/main/java/me/danwi/kato/server/argument/MultiRequestBodyMethodArgumentHandlerResolver.java b/server/src/main/java/me/danwi/kato/server/argument/MultiRequestBodyMethodArgumentHandlerResolver.java index 28f429c..f60f073 100644 --- a/server/src/main/java/me/danwi/kato/server/argument/MultiRequestBodyMethodArgumentHandlerResolver.java +++ b/server/src/main/java/me/danwi/kato/server/argument/MultiRequestBodyMethodArgumentHandlerResolver.java @@ -44,7 +44,7 @@ */ public class MultiRequestBodyMethodArgumentHandlerResolver implements HandlerMethodArgumentResolver { - private static final Logger logger = LoggerFactory.getLogger(MultiRequestBodyMethodArgumentHandlerResolver.class); + private static final Logger LOGGER = LoggerFactory.getLogger(MultiRequestBodyMethodArgumentHandlerResolver.class); private static final String KATO_JSON_NODE_KEY = "_KATO_JSON_NODE_KEY_"; @@ -104,7 +104,7 @@ public Object resolveArgument(MethodParameter methodParameter, ModelAndViewConta if (Objects.isNull(result) && (isJavaCode ? paramInfo.required : !type.isMarkedNullable())) { throw new IllegalArgumentException(String.format("缺少 %s 参数", paramInfo.key)); } - logger.debug("解析参数:key={},value={}", paramInfo.key, result); + LOGGER.debug("解析参数:key={},value={}", paramInfo.key, result); return result; } @@ -144,7 +144,7 @@ private JsonNode getJsonNode(NativeWebRequest nativeWebRequest) throws IOExcepti attribute = mapper.readTree(servletRequest.getInputStream()); nativeWebRequest.setAttribute(KATO_JSON_NODE_KEY, attribute, WebRequest.SCOPE_REQUEST); - logger.debug("解析请求数据 [{}] 为 [{}]", nativeWebRequest.getHeader("accept"), attribute); + LOGGER.debug("解析请求数据 [{}] 为 [{}]", nativeWebRequest.getHeader("accept"), attribute); } return (JsonNode) attribute; } From 89fc590725b32e27ebcbce2b4278eebe01b2939b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=8A=A0=E9=9B=A8?= Date: Fri, 8 Jul 2022 11:03:22 +0800 Subject: [PATCH 16/24] =?UTF-8?q?=E6=8C=87=E5=AE=9A=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E5=90=8D=E4=B8=BA=E5=85=A8=E9=99=90=E5=AE=9A=E7=B1=BB=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../danwi/kato/server/argument/MethodArgumentHandlerConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/me/danwi/kato/server/argument/MethodArgumentHandlerConfig.java b/server/src/main/java/me/danwi/kato/server/argument/MethodArgumentHandlerConfig.java index 341b566..7788df8 100644 --- a/server/src/main/java/me/danwi/kato/server/argument/MethodArgumentHandlerConfig.java +++ b/server/src/main/java/me/danwi/kato/server/argument/MethodArgumentHandlerConfig.java @@ -10,7 +10,7 @@ /** * 参数处理器自动配置 */ -@Configuration +@Configuration("me.danwi.kato.server.argument.MethodArgumentHandlerConfig") public class MethodArgumentHandlerConfig implements WebMvcConfigurer { private final ObjectMapper objectMapper; From 3ae6d2416ef6fb719bc3fd3409ee9527d2696118 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=8A=A0=E9=9B=A8?= Date: Fri, 8 Jul 2022 14:03:06 +0800 Subject: [PATCH 17/24] =?UTF-8?q?=E5=85=A8=E9=9D=A2=E5=85=BC=E5=AE=B9Enabl?= =?UTF-8?q?eFeignClients=E6=B3=A8=E8=A7=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/me/danwi/kato/client/ImportKatoClients.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/client/src/main/java/me/danwi/kato/client/ImportKatoClients.java b/client/src/main/java/me/danwi/kato/client/ImportKatoClients.java index 6118db2..7f5af23 100644 --- a/client/src/main/java/me/danwi/kato/client/ImportKatoClients.java +++ b/client/src/main/java/me/danwi/kato/client/ImportKatoClients.java @@ -1,6 +1,7 @@ package me.danwi.kato.client; import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.cloud.openfeign.FeignClientsConfiguration; import org.springframework.core.annotation.AliasFor; import java.lang.annotation.ElementType; @@ -17,4 +18,14 @@ @AliasFor(annotation = EnableFeignClients.class) Class[] clients() default {}; + + @AliasFor(annotation = EnableFeignClients.class) + String[] basePackages() default {}; + + @AliasFor(annotation = EnableFeignClients.class) + Class[] basePackageClasses() default {}; + + @AliasFor(annotation = EnableFeignClients.class) + Class[] defaultConfiguration() default {}; + } From d943a87388c00cd04c28a84f564df8907cb21118 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=8A=A0=E9=9B=A8?= Date: Fri, 8 Jul 2022 17:12:21 +0800 Subject: [PATCH 18/24] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=97=A0=E7=94=A8?= =?UTF-8?q?=E7=9A=84=E8=87=AA=E5=AE=9A=E4=B9=89Decoder?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../danwi/kato/client/KatoClientConfig.java | 5 ---- .../danwi/kato/client/KatoResultDecoder.java | 26 ------------------- 2 files changed, 31 deletions(-) delete mode 100644 client/src/main/java/me/danwi/kato/client/KatoResultDecoder.java diff --git a/client/src/main/java/me/danwi/kato/client/KatoClientConfig.java b/client/src/main/java/me/danwi/kato/client/KatoClientConfig.java index 2983759..9bfbae2 100644 --- a/client/src/main/java/me/danwi/kato/client/KatoClientConfig.java +++ b/client/src/main/java/me/danwi/kato/client/KatoClientConfig.java @@ -1,16 +1,11 @@ package me.danwi.kato.client; -import feign.codec.Decoder; import feign.codec.ErrorDecoder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration("me.danwi.kato.client.Config") public class KatoClientConfig { - @Bean("me.danwi.kato.client.ResultDecoder") - Decoder resultDecoder() { - return new KatoResultDecoder(); - } @Bean("me.danwi.kato.client.ErrorDecoder") ErrorDecoder errorDecoder() { diff --git a/client/src/main/java/me/danwi/kato/client/KatoResultDecoder.java b/client/src/main/java/me/danwi/kato/client/KatoResultDecoder.java deleted file mode 100644 index b0511ab..0000000 --- a/client/src/main/java/me/danwi/kato/client/KatoResultDecoder.java +++ /dev/null @@ -1,26 +0,0 @@ -package me.danwi.kato.client; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.type.TypeFactory; -import feign.FeignException; -import feign.Response; -import feign.Util; -import feign.codec.Decoder; - -import java.io.IOException; -import java.lang.reflect.Type; - -public class KatoResultDecoder implements Decoder { - private final ObjectMapper mapper = new ObjectMapper(); - - @Override - public Object decode(Response response, Type type) throws IOException, FeignException { - //空body - if (response.body() == null) - return null; - //反序列化结果 - String bodyStr = Util.toString(response.body().asReader(Util.UTF_8)); - if (Util.isBlank(bodyStr)) return null; - return mapper.readValue(bodyStr, TypeFactory.defaultInstance().constructType(type)); - } -} \ No newline at end of file From c103d38a53c595cd429bdb6fafb2593033abffe9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=8A=A0=E9=9B=A8?= Date: Fri, 8 Jul 2022 17:13:10 +0800 Subject: [PATCH 19/24] =?UTF-8?q?=E5=A2=9E=E5=8A=A0client=E5=8D=95?= =?UTF-8?q?=E5=85=83=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../danwi/kato/client/ImportKatoClients.java | 1 - example/build.gradle.kts | 7 +++++ .../danwi/kato/example/ExampleApplication.kt | 4 +-- .../me/danwi/kato/example/rpc/RpcClient.kt | 21 +++++++++++++ .../me/danwi/kato/example/KatoClientTest.java | 30 +++++++++++++++++++ example/src/test/resources/application.yml | 2 ++ 6 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 example/src/main/kotlin/me/danwi/kato/example/rpc/RpcClient.kt create mode 100644 example/src/test/java/me/danwi/kato/example/KatoClientTest.java create mode 100644 example/src/test/resources/application.yml diff --git a/client/src/main/java/me/danwi/kato/client/ImportKatoClients.java b/client/src/main/java/me/danwi/kato/client/ImportKatoClients.java index 7f5af23..55bd2f8 100644 --- a/client/src/main/java/me/danwi/kato/client/ImportKatoClients.java +++ b/client/src/main/java/me/danwi/kato/client/ImportKatoClients.java @@ -1,7 +1,6 @@ package me.danwi.kato.client; import org.springframework.cloud.openfeign.EnableFeignClients; -import org.springframework.cloud.openfeign.FeignClientsConfiguration; import org.springframework.core.annotation.AliasFor; import java.lang.annotation.ElementType; diff --git a/example/build.gradle.kts b/example/build.gradle.kts index 01f87a9..d0d4429 100644 --- a/example/build.gradle.kts +++ b/example/build.gradle.kts @@ -12,14 +12,21 @@ configurations { } dependencies { + implementation(project(":client")) implementation(project(":server")) + + implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation("org.springframework.boot:spring-boot-starter-web") + implementation("org.springframework.cloud:spring-cloud-starter-openfeign:3.1.2") + developmentOnly("org.springframework.boot:spring-boot-devtools") annotationProcessor("org.springframework.boot:spring-boot-configuration-processor") + + testImplementation("org.springframework.boot:spring-boot-starter-test") } diff --git a/example/src/main/kotlin/me/danwi/kato/example/ExampleApplication.kt b/example/src/main/kotlin/me/danwi/kato/example/ExampleApplication.kt index 5e2e489..fa2be49 100644 --- a/example/src/main/kotlin/me/danwi/kato/example/ExampleApplication.kt +++ b/example/src/main/kotlin/me/danwi/kato/example/ExampleApplication.kt @@ -1,17 +1,17 @@ package me.danwi.kato.example -import me.danwi.kato.server.argument.MethodArgumentHandlerConfig +import me.danwi.kato.client.ImportKatoClients import me.danwi.kato.common.exception.ExceptionExtraDataHolder import me.danwi.kato.common.exception.KatoCommonException import me.danwi.kato.common.exception.KatoException import me.danwi.kato.server.EnableKatoServer import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication -import org.springframework.context.annotation.Import import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController @SpringBootApplication +@ImportKatoClients @EnableKatoServer class ExampleApplication diff --git a/example/src/main/kotlin/me/danwi/kato/example/rpc/RpcClient.kt b/example/src/main/kotlin/me/danwi/kato/example/rpc/RpcClient.kt new file mode 100644 index 0000000..2cf77f0 --- /dev/null +++ b/example/src/main/kotlin/me/danwi/kato/example/rpc/RpcClient.kt @@ -0,0 +1,21 @@ +package me.danwi.kato.example.rpc + +import me.danwi.kato.client.KatoClient +import me.danwi.kato.example.TestData +import org.springframework.web.bind.annotation.RequestMapping + +/** + * @author wjy + */ +@KatoClient("test", url = "http://localhost:8888") +interface RpcClient { + + @RequestMapping("/") + fun index(): TestData + + @RequestMapping("/common-exception") + fun commonException() + + @RequestMapping("/exception") + fun exception() +} \ No newline at end of file diff --git a/example/src/test/java/me/danwi/kato/example/KatoClientTest.java b/example/src/test/java/me/danwi/kato/example/KatoClientTest.java new file mode 100644 index 0000000..7941cb3 --- /dev/null +++ b/example/src/test/java/me/danwi/kato/example/KatoClientTest.java @@ -0,0 +1,30 @@ +package me.danwi.kato.example; + +import me.danwi.kato.common.exception.KatoCommonException; +import me.danwi.kato.example.rpc.RpcClient; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) +public class KatoClientTest { + @Autowired + private RpcClient rpcClient; + + @Test + public void test1() { + final TestData index = rpcClient.index(); + Assertions.assertEquals(new TestData("kato"), index); + } + + @Test + public void test2() { + try { + rpcClient.commonException(); + } catch (Exception e) { + Assertions.assertTrue(e instanceof KatoCommonException); + } + } + +} diff --git a/example/src/test/resources/application.yml b/example/src/test/resources/application.yml new file mode 100644 index 0000000..84c6fb1 --- /dev/null +++ b/example/src/test/resources/application.yml @@ -0,0 +1,2 @@ +server: + port: 8888 \ No newline at end of file From 2473cc651fb749831f7114426ca2689b7d47e617 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=8A=A0=E9=9B=A8?= Date: Fri, 8 Jul 2022 17:16:26 +0800 Subject: [PATCH 20/24] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E5=90=8D=E7=A7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../rpc/{RpcClient.kt => ExceptionRpcClient.kt} | 2 +- ...atoClientTest.java => KatoClientExceptionTest.java} | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) rename example/src/main/kotlin/me/danwi/kato/example/rpc/{RpcClient.kt => ExceptionRpcClient.kt} (93%) rename example/src/test/java/me/danwi/kato/example/{KatoClientTest.java => KatoClientExceptionTest.java} (72%) diff --git a/example/src/main/kotlin/me/danwi/kato/example/rpc/RpcClient.kt b/example/src/main/kotlin/me/danwi/kato/example/rpc/ExceptionRpcClient.kt similarity index 93% rename from example/src/main/kotlin/me/danwi/kato/example/rpc/RpcClient.kt rename to example/src/main/kotlin/me/danwi/kato/example/rpc/ExceptionRpcClient.kt index 2cf77f0..dfb9d43 100644 --- a/example/src/main/kotlin/me/danwi/kato/example/rpc/RpcClient.kt +++ b/example/src/main/kotlin/me/danwi/kato/example/rpc/ExceptionRpcClient.kt @@ -8,7 +8,7 @@ import org.springframework.web.bind.annotation.RequestMapping * @author wjy */ @KatoClient("test", url = "http://localhost:8888") -interface RpcClient { +interface ExceptionRpcClient { @RequestMapping("/") fun index(): TestData diff --git a/example/src/test/java/me/danwi/kato/example/KatoClientTest.java b/example/src/test/java/me/danwi/kato/example/KatoClientExceptionTest.java similarity index 72% rename from example/src/test/java/me/danwi/kato/example/KatoClientTest.java rename to example/src/test/java/me/danwi/kato/example/KatoClientExceptionTest.java index 7941cb3..c54ffc8 100644 --- a/example/src/test/java/me/danwi/kato/example/KatoClientTest.java +++ b/example/src/test/java/me/danwi/kato/example/KatoClientExceptionTest.java @@ -1,27 +1,27 @@ package me.danwi.kato.example; import me.danwi.kato.common.exception.KatoCommonException; -import me.danwi.kato.example.rpc.RpcClient; +import me.danwi.kato.example.rpc.ExceptionRpcClient; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) -public class KatoClientTest { +public class KatoClientExceptionTest { @Autowired - private RpcClient rpcClient; + private ExceptionRpcClient exceptionRpcClient; @Test public void test1() { - final TestData index = rpcClient.index(); + final TestData index = exceptionRpcClient.index(); Assertions.assertEquals(new TestData("kato"), index); } @Test public void test2() { try { - rpcClient.commonException(); + exceptionRpcClient.commonException(); } catch (Exception e) { Assertions.assertTrue(e instanceof KatoCommonException); } From cdbf79d2750f1e79bee36c8138a600802b9d45b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=8A=A0=E9=9B=A8?= Date: Fri, 8 Jul 2022 18:25:42 +0800 Subject: [PATCH 21/24] temp --- .../danwi/kato/client/KatoClientConfig.java | 39 +++++++++++++++++++ .../me/danwi/kato/client/KatoContract.java | 37 ++++++++++++++++++ .../me/danwi/kato/client/KatoEncoder.java | 22 +++++++++++ .../kato/example/rpc/ExceptionRpcClient.kt | 3 -- .../danwi/kato/example/rpc/ParamRpcClient.kt | 14 +++++++ .../kato/example/KatoClientParamTest.java | 21 ++++++++++ 6 files changed, 133 insertions(+), 3 deletions(-) create mode 100644 client/src/main/java/me/danwi/kato/client/KatoContract.java create mode 100644 client/src/main/java/me/danwi/kato/client/KatoEncoder.java create mode 100644 example/src/main/kotlin/me/danwi/kato/example/rpc/ParamRpcClient.kt create mode 100644 example/src/test/java/me/danwi/kato/example/KatoClientParamTest.java diff --git a/client/src/main/java/me/danwi/kato/client/KatoClientConfig.java b/client/src/main/java/me/danwi/kato/client/KatoClientConfig.java index 9bfbae2..3c7839b 100644 --- a/client/src/main/java/me/danwi/kato/client/KatoClientConfig.java +++ b/client/src/main/java/me/danwi/kato/client/KatoClientConfig.java @@ -1,14 +1,53 @@ package me.danwi.kato.client; +import feign.Contract; import feign.codec.ErrorDecoder; +import me.danwi.kato.common.argument.MultiRequestBody; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.openfeign.AnnotatedParameterProcessor; +import org.springframework.cloud.openfeign.FeignClientProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.convert.ConversionService; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; @Configuration("me.danwi.kato.client.Config") public class KatoClientConfig { + @Autowired(required = false) + private FeignClientProperties feignClientProperties; + + @Bean("me.danwi.kato.client.ErrorDecoder") ErrorDecoder errorDecoder() { return new KatoErrorDecoder(); } + + @Bean + public Contract katoContract(ObjectProvider> parameterProcessors, ObjectProvider feignClientProperties, ConversionService feignConversionService) { + AtomicBoolean decodeSlash = new AtomicBoolean(true); + feignClientProperties.ifAvailable(fc -> decodeSlash.set(fc.isDecodeSlash())); + + List processors = new ArrayList<>(); + processors.add(new AnnotatedParameterProcessor() { + @Override + public Class getAnnotationType() { + return MultiRequestBody.class; + } + + @Override + public boolean processArgument(AnnotatedParameterContext context, Annotation annotation, Method method) { + return true; + } + }); + parameterProcessors.ifAvailable(processors::addAll); + + return new KatoContract(processors, feignConversionService, decodeSlash.get()); + } } diff --git a/client/src/main/java/me/danwi/kato/client/KatoContract.java b/client/src/main/java/me/danwi/kato/client/KatoContract.java new file mode 100644 index 0000000..ed3146d --- /dev/null +++ b/client/src/main/java/me/danwi/kato/client/KatoContract.java @@ -0,0 +1,37 @@ +package me.danwi.kato.client; + +import feign.MethodMetadata; +import org.springframework.cloud.openfeign.AnnotatedParameterProcessor; +import org.springframework.cloud.openfeign.support.SpringMvcContract; +import org.springframework.core.convert.ConversionService; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.List; + +/** + * @author wjy + */ +public class KatoContract extends SpringMvcContract { + + + public KatoContract(List annotatedParameterProcessors, ConversionService conversionService, boolean decodeSlash) { + super(annotatedParameterProcessors, conversionService, decodeSlash); + } + + @Override + public MethodMetadata parseAndValidateMetadata(Class targetType, Method method) { + final MethodMetadata methodMetadata = super.parseAndValidateMetadata(targetType, method); + + return methodMetadata; + } + + + /** + * 如果没有注解 或者注解是 MultiRequestBody 返回ture + */ + @Override + protected boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[] annotations, int paramIndex) { + return super.processAnnotationsOnParameter(data, annotations, paramIndex); + } +} diff --git a/client/src/main/java/me/danwi/kato/client/KatoEncoder.java b/client/src/main/java/me/danwi/kato/client/KatoEncoder.java new file mode 100644 index 0000000..356f222 --- /dev/null +++ b/client/src/main/java/me/danwi/kato/client/KatoEncoder.java @@ -0,0 +1,22 @@ +package me.danwi.kato.client; + +import feign.RequestTemplate; +import feign.codec.EncodeException; +import feign.codec.Encoder; + +import java.lang.reflect.Type; + +public class KatoEncoder implements Encoder { + + private final Encoder delegate; + + public KatoEncoder(Encoder delegate) { + this.delegate = delegate; + } + + + @Override + public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException { + + } +} diff --git a/example/src/main/kotlin/me/danwi/kato/example/rpc/ExceptionRpcClient.kt b/example/src/main/kotlin/me/danwi/kato/example/rpc/ExceptionRpcClient.kt index dfb9d43..0eef7e9 100644 --- a/example/src/main/kotlin/me/danwi/kato/example/rpc/ExceptionRpcClient.kt +++ b/example/src/main/kotlin/me/danwi/kato/example/rpc/ExceptionRpcClient.kt @@ -4,9 +4,6 @@ import me.danwi.kato.client.KatoClient import me.danwi.kato.example.TestData import org.springframework.web.bind.annotation.RequestMapping -/** - * @author wjy - */ @KatoClient("test", url = "http://localhost:8888") interface ExceptionRpcClient { diff --git a/example/src/main/kotlin/me/danwi/kato/example/rpc/ParamRpcClient.kt b/example/src/main/kotlin/me/danwi/kato/example/rpc/ParamRpcClient.kt new file mode 100644 index 0000000..7e1baa9 --- /dev/null +++ b/example/src/main/kotlin/me/danwi/kato/example/rpc/ParamRpcClient.kt @@ -0,0 +1,14 @@ +package me.danwi.kato.example.rpc + +import me.danwi.kato.client.KatoClient +import me.danwi.kato.example.argument.TestEntity +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody + +@KatoClient(value = "testParam", url = "http://localhost:8888") +interface ParamRpcClient { + + @PostMapping("/withOutAnno/multiRequest") + fun multiRequest(@RequestBody() id: Int, name: String): TestEntity; + +} \ No newline at end of file diff --git a/example/src/test/java/me/danwi/kato/example/KatoClientParamTest.java b/example/src/test/java/me/danwi/kato/example/KatoClientParamTest.java new file mode 100644 index 0000000..2034d2e --- /dev/null +++ b/example/src/test/java/me/danwi/kato/example/KatoClientParamTest.java @@ -0,0 +1,21 @@ +package me.danwi.kato.example; + +import me.danwi.kato.example.argument.TestEntity; +import me.danwi.kato.example.rpc.ParamRpcClient; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) +public class KatoClientParamTest { + @Autowired + private ParamRpcClient paramRpcClient; + + @Test + public void test1() { + final TestEntity entity = new TestEntity(1, "name"); + final TestEntity entity2 = paramRpcClient.multiRequest(entity.getId(), entity.getName()); + Assertions.assertEquals(entity, entity2); + } +} From ea921d6a17cd9d71a53712397c2faf7edd42485f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=8A=A0=E9=9B=A8?= Date: Sat, 9 Jul 2022 02:45:48 +0800 Subject: [PATCH 22/24] =?UTF-8?q?=E8=87=AA=E5=AE=9A=E4=B9=89constract?= =?UTF-8?q?=EF=BC=8C=E5=AE=9E=E7=8E=B0=E5=8F=82=E6=95=B0=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E8=A7=A3=E6=9E=90=E4=B8=BAMultiRequestBody=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../danwi/kato/client/KatoClientConfig.java | 63 ++- .../me/danwi/kato/client/KatoContract.java | 365 +++++++++++++++++- .../me/danwi/kato/client/KatoEncoder.java | 35 +- .../MultiRequestBodyWithOutAnnoController.kt | 5 + .../kato/example/rpc/ExceptionRpcClient.kt | 2 - .../danwi/kato/example/rpc/NoUseKatoClient.kt | 16 + .../danwi/kato/example/rpc/ParamRpcClient.kt | 4 +- .../java/me/danwi/kato/example/Config.java | 14 + .../kato/example/KatoClientParamTest.java | 37 ++ example/src/test/resources/application.yml | 6 +- 10 files changed, 509 insertions(+), 38 deletions(-) create mode 100644 example/src/main/kotlin/me/danwi/kato/example/rpc/NoUseKatoClient.kt create mode 100644 example/src/test/java/me/danwi/kato/example/Config.java diff --git a/client/src/main/java/me/danwi/kato/client/KatoClientConfig.java b/client/src/main/java/me/danwi/kato/client/KatoClientConfig.java index 3c7839b..25f2224 100644 --- a/client/src/main/java/me/danwi/kato/client/KatoClientConfig.java +++ b/client/src/main/java/me/danwi/kato/client/KatoClientConfig.java @@ -1,53 +1,80 @@ package me.danwi.kato.client; import feign.Contract; +import feign.codec.Encoder; import feign.codec.ErrorDecoder; -import me.danwi.kato.common.argument.MultiRequestBody; +import feign.form.MultipartFormContentProcessor; +import feign.form.spring.SpringFormEncoder; +import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.http.HttpMessageConverters; import org.springframework.cloud.openfeign.AnnotatedParameterProcessor; import org.springframework.cloud.openfeign.FeignClientProperties; +import org.springframework.cloud.openfeign.support.AbstractFormWriter; +import org.springframework.cloud.openfeign.support.FeignEncoderProperties; +import org.springframework.cloud.openfeign.support.HttpMessageConverterCustomizer; +import org.springframework.cloud.openfeign.support.SpringEncoder; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.core.convert.ConversionService; -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; -@Configuration("me.danwi.kato.client.Config") +import static feign.form.ContentType.MULTIPART; + public class KatoClientConfig { + @Autowired + private ObjectFactory messageConverters; @Autowired(required = false) - private FeignClientProperties feignClientProperties; - + private FeignEncoderProperties encoderProperties; @Bean("me.danwi.kato.client.ErrorDecoder") ErrorDecoder errorDecoder() { return new KatoErrorDecoder(); } + @Bean + public Encoder katoEncoder( + ObjectProvider formWriterProvider, + ObjectProvider customizer + ) { + return new KatoEncoder(springEncoder(formWriterProvider, encoderProperties, customizer)); + } + @Bean public Contract katoContract(ObjectProvider> parameterProcessors, ObjectProvider feignClientProperties, ConversionService feignConversionService) { AtomicBoolean decodeSlash = new AtomicBoolean(true); feignClientProperties.ifAvailable(fc -> decodeSlash.set(fc.isDecodeSlash())); List processors = new ArrayList<>(); - processors.add(new AnnotatedParameterProcessor() { - @Override - public Class getAnnotationType() { - return MultiRequestBody.class; - } - - @Override - public boolean processArgument(AnnotatedParameterContext context, Annotation annotation, Method method) { - return true; - } - }); parameterProcessors.ifAvailable(processors::addAll); return new KatoContract(processors, feignConversionService, decodeSlash.get()); } + + private Encoder springEncoder(ObjectProvider formWriterProvider, + FeignEncoderProperties encoderProperties, ObjectProvider customizers) { + AbstractFormWriter formWriter = formWriterProvider.getIfAvailable(); + + if (formWriter != null) { + return new SpringEncoder(new SpringPojoFormEncoder(formWriter), messageConverters, encoderProperties, + customizers); + } else { + return new SpringEncoder(new SpringFormEncoder(), messageConverters, encoderProperties, customizers); + } + } + + private static class SpringPojoFormEncoder extends SpringFormEncoder { + + SpringPojoFormEncoder(AbstractFormWriter formWriter) { + super(); + + MultipartFormContentProcessor processor = (MultipartFormContentProcessor) getContentProcessor(MULTIPART); + processor.addFirstWriter(formWriter); + } + + } } diff --git a/client/src/main/java/me/danwi/kato/client/KatoContract.java b/client/src/main/java/me/danwi/kato/client/KatoContract.java index ed3146d..c8a2c75 100644 --- a/client/src/main/java/me/danwi/kato/client/KatoContract.java +++ b/client/src/main/java/me/danwi/kato/client/KatoContract.java @@ -1,37 +1,374 @@ package me.danwi.kato.client; +import feign.AlwaysEncodeBodyContract; import feign.MethodMetadata; +import feign.Param; +import feign.Request; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.cloud.openfeign.AnnotatedParameterProcessor; -import org.springframework.cloud.openfeign.support.SpringMvcContract; +import org.springframework.cloud.openfeign.CollectionFormat; +import org.springframework.cloud.openfeign.annotation.*; +import org.springframework.cloud.openfeign.encoding.HttpEncoding; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.ResourceLoaderAware; +import org.springframework.core.DefaultParameterNameDiscoverer; +import org.springframework.core.MethodParameter; +import org.springframework.core.ParameterNameDiscoverer; +import org.springframework.core.ResolvableType; +import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.ResourceLoader; +import org.springframework.http.InvalidMediaTypeException; +import org.springframework.http.MediaType; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; import java.lang.annotation.Annotation; import java.lang.reflect.Method; -import java.util.List; +import java.lang.reflect.Parameter; +import java.lang.reflect.Type; +import java.util.*; + +import static feign.Util.checkState; +import static feign.Util.emptyToNull; +import static java.util.Optional.ofNullable; +import static org.springframework.core.annotation.AnnotatedElementUtils.findMergedAnnotation; /** - * @author wjy + * 参考 SpringMvcContract 实现 */ -public class KatoContract extends SpringMvcContract { +public class KatoContract extends AlwaysEncodeBodyContract implements ResourceLoaderAware { + + private static final Log LOG = LogFactory.getLog(KatoContract.class); + + private static final String ACCEPT = "Accept"; + + private static final String CONTENT_TYPE = "Content-Type"; + + private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class); + + private static final TypeDescriptor ITERABLE_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Iterable.class); + + private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer(); + + private final ConversionService conversionService; + + private final ConvertingExpanderFactory convertingExpanderFactory; + + private ResourceLoader resourceLoader = new DefaultResourceLoader(); + + private final boolean decodeSlash; + + + public KatoContract() { + this(Collections.emptyList()); + } + + public KatoContract(List annotatedParameterProcessors) { + this(annotatedParameterProcessors, new DefaultConversionService()); + } + + public KatoContract(List annotatedParameterProcessors, + ConversionService conversionService) { + this(annotatedParameterProcessors, conversionService, true); + } + + public KatoContract(List annotatedParameterProcessors, + ConversionService conversionService, boolean decodeSlash) { + Assert.notNull(annotatedParameterProcessors, "Parameter processors can not be null."); + Assert.notNull(conversionService, "ConversionService can not be null."); + + this.conversionService = conversionService; + convertingExpanderFactory = new KatoContract.ConvertingExpanderFactory(conversionService); + this.decodeSlash = decodeSlash; + init(annotatedParameterProcessors); + } + + + public void init(List annotatedParameterProcessors) { + // 注册类注解处理器 + registerClassAnnotationProcessor(); + // 注册方法注解处理器 + registerMethodAnnotationProcessor(); + // 注册参数注解处理 + registerParameterAnnotationProcessor(annotatedParameterProcessors); + } + + private void registerClassAnnotationProcessor() { + registerClassAnnotation( + RequestMapping.class, + (annotation, metadata) -> { + LOG.error("Cannot process class: " + metadata.targetType().getName() + + ". @RequestMapping annotation is not allowed on @FeignClient interfaces."); + throw new IllegalArgumentException("@RequestMapping annotation not allowed on @FeignClient interfaces"); + } + ); + registerClassAnnotation( + CollectionFormat.class, + (annotation, metadata) -> metadata.template().collectionFormat(annotation.value()) + ); + } + + private void registerMethodAnnotationProcessor() { + registerMethodAnnotation( + CollectionFormat.class, + (annotation, metadata) -> metadata.template().collectionFormat(annotation.value()) + ); + registerMethodAnnotation( + (annotation) -> annotation instanceof RequestMapping || annotation.annotationType().isAnnotationPresent(RequestMapping.class), + (annotation, data) -> { + Method method = data.method(); + RequestMapping methodMapping = findMergedAnnotation(method, RequestMapping.class); + // HTTP Method + RequestMethod[] methods = methodMapping.method(); + if (methods.length == 0) { + methods = new RequestMethod[]{RequestMethod.GET}; + } + checkOne(method, methods, "method"); + data.template().method(Request.HttpMethod.valueOf(methods[0].name())); + + // path + checkAtMostOne(method, methodMapping.value(), "value"); + if (methodMapping.value().length > 0) { + String pathValue = emptyToNull(methodMapping.value()[0]); + if (pathValue != null) { + pathValue = resolve(pathValue); + // Append path from @RequestMapping if value is present on method + if (!pathValue.startsWith("/") && !data.template().path().endsWith("/")) { + pathValue = "/" + pathValue; + } + data.template().uri(pathValue, true); + if (data.template().decodeSlash() != this.decodeSlash) { + data.template().decodeSlash(this.decodeSlash); + } + } + } + + // produces + parseProduces(data, method, methodMapping); + + // consumes + parseConsumes(data, method, methodMapping); + + // headers + parseHeaders(data, method, methodMapping); + + data.indexToExpander(new LinkedHashMap<>()); + } + ); + } + + private static TypeDescriptor createTypeDescriptor(Method method, int paramIndex) { + Parameter parameter = method.getParameters()[paramIndex]; + MethodParameter methodParameter = MethodParameter.forParameter(parameter); + TypeDescriptor typeDescriptor = new TypeDescriptor(methodParameter); + + // Feign applies the Param.Expander to each element of an Iterable, so in those + // cases we need to provide a TypeDescriptor of the element. + if (typeDescriptor.isAssignableTo(ITERABLE_TYPE_DESCRIPTOR)) { + TypeDescriptor elementTypeDescriptor = getElementTypeDescriptor(typeDescriptor); + + checkState(elementTypeDescriptor != null, + "Could not resolve element type of Iterable type %s. Not declared?", typeDescriptor); + + typeDescriptor = elementTypeDescriptor; + } + return typeDescriptor; + } + + private static TypeDescriptor getElementTypeDescriptor(TypeDescriptor typeDescriptor) { + TypeDescriptor elementTypeDescriptor = typeDescriptor.getElementTypeDescriptor(); + // that means it's not a collection but it is iterable, gh-135 + if (elementTypeDescriptor == null && Iterable.class.isAssignableFrom(typeDescriptor.getType())) { + ResolvableType type = typeDescriptor.getResolvableType().as(Iterable.class).getGeneric(0); + if (type.resolve() == null) { + return null; + } + return new TypeDescriptor(type, null, typeDescriptor.getAnnotations()); + } + return elementTypeDescriptor; + } - public KatoContract(List annotatedParameterProcessors, ConversionService conversionService, boolean decodeSlash) { - super(annotatedParameterProcessors, conversionService, decodeSlash); + @Override + public void setResourceLoader(ResourceLoader resourceLoader) { + this.resourceLoader = resourceLoader; } @Override public MethodMetadata parseAndValidateMetadata(Class targetType, Method method) { - final MethodMetadata methodMetadata = super.parseAndValidateMetadata(targetType, method); + return super.parseAndValidateMetadata(targetType, method); + } - return methodMetadata; + private String resolve(String value) { + if (StringUtils.hasText(value) && resourceLoader instanceof ConfigurableApplicationContext) { + return ((ConfigurableApplicationContext) resourceLoader).getEnvironment().resolvePlaceholders(value); + } + return value; } + private void checkAtMostOne(Method method, Object[] values, String fieldName) { + checkState(values != null && (values.length == 0 || values.length == 1), + "Method %s can only contain at most 1 %s field. Found: %s", method.getName(), fieldName, + values == null ? null : Arrays.asList(values)); + } + + private void checkOne(Method method, Object[] values, String fieldName) { + checkState(values != null && values.length == 1, "Method %s can only contain 1 %s field. Found: %s", + method.getName(), fieldName, values == null ? null : Arrays.asList(values)); + } + + private void parseProduces(MethodMetadata md, Method method, RequestMapping annotation) { + String[] serverProduces = annotation.produces(); + String clientAccepts = serverProduces.length == 0 ? null : emptyToNull(serverProduces[0]); + if (clientAccepts != null) { + md.template().header(ACCEPT, clientAccepts); + } + } + + private void parseConsumes(MethodMetadata md, Method method, RequestMapping annotation) { + String[] serverConsumes = annotation.consumes(); + String clientProduces = serverConsumes.length == 0 ? null : emptyToNull(serverConsumes[0]); + if (clientProduces != null) { + md.template().header(CONTENT_TYPE, clientProduces); + } + } + + private void parseHeaders(MethodMetadata md, Method method, RequestMapping annotation) { + // TODO: only supports one header value per key + if (annotation.headers() != null && annotation.headers().length > 0) { + for (String header : annotation.headers()) { + int index = header.indexOf('='); + if (!header.contains("!=") && index >= 0) { + md.template().header(resolve(header.substring(0, index)), + resolve(header.substring(index + 1).trim())); + } + } + } + } + + + private void registerParameterAnnotationProcessor(List annotatedArgumentResolvers) { + + annotatedArgumentResolvers.add(new MatrixVariableParameterProcessor()); + annotatedArgumentResolvers.add(new PathVariableParameterProcessor()); + annotatedArgumentResolvers.add(new RequestParamParameterProcessor()); + annotatedArgumentResolvers.add(new RequestHeaderParameterProcessor()); + annotatedArgumentResolvers.add(new QueryMapParameterProcessor()); + annotatedArgumentResolvers.add(new RequestPartParameterProcessor()); + annotatedArgumentResolvers.add(new CookieValueParameterProcessor()); + + annotatedArgumentResolvers.forEach(annotatedParameterProcessor -> registerParameterAnnotation( + annotatedParameterProcessor.getAnnotationType(), + (ParameterAnnotationProcessor) (annotation, metadata, paramIndex) -> { + // synthesize, handling @AliasFor, while falling back to parameter name on + // missing String #value(): + Annotation processParameterAnnotation = synthesizeWithMethodParameterNameAsFallbackValue(annotation, metadata.method(), paramIndex); + boolean isHttpAnnotation = annotatedParameterProcessor.processArgument(new SimpleAnnotatedParameterContext(metadata, paramIndex), processParameterAnnotation, metadata.method()); + if (!isMultipartFormData(metadata) && isHttpAnnotation && metadata.indexToExpander().get(paramIndex) == null) { + TypeDescriptor typeDescriptor = createTypeDescriptor(metadata.method(), paramIndex); + if (conversionService.canConvert(typeDescriptor, STRING_TYPE_DESCRIPTOR)) { + Param.Expander expander = convertingExpanderFactory.getExpander(typeDescriptor); + metadata.indexToExpander().put(paramIndex, expander); + } + } + } + )); + } + + + private Annotation synthesizeWithMethodParameterNameAsFallbackValue(Annotation parameterAnnotation, Method + method, + int parameterIndex) { + Map annotationAttributes = AnnotationUtils.getAnnotationAttributes(parameterAnnotation); + Object defaultValue = AnnotationUtils.getDefaultValue(parameterAnnotation); + if (defaultValue instanceof String && defaultValue.equals(annotationAttributes.get(AnnotationUtils.VALUE))) { + Type[] parameterTypes = method.getGenericParameterTypes(); + String[] parameterNames = PARAMETER_NAME_DISCOVERER.getParameterNames(method); + if (shouldAddParameterName(parameterIndex, parameterTypes, parameterNames)) { + annotationAttributes.put(AnnotationUtils.VALUE, parameterNames[parameterIndex]); + } + } + return AnnotationUtils.synthesizeAnnotation(annotationAttributes, parameterAnnotation.annotationType(), null); + } + + private boolean shouldAddParameterName(int parameterIndex, Type[] parameterTypes, String[] parameterNames) { + // has a parameter name + return parameterNames != null && parameterNames.length > parameterIndex + // has a type + && parameterTypes != null && parameterTypes.length > parameterIndex; + } + + private boolean isMultipartFormData(MethodMetadata data) { + Collection contentTypes = data.template().headers().get(HttpEncoding.CONTENT_TYPE); + + if (contentTypes != null && !contentTypes.isEmpty()) { + String type = contentTypes.iterator().next(); + try { + return Objects.equals(MediaType.valueOf(type), MediaType.MULTIPART_FORM_DATA); + } catch (InvalidMediaTypeException ignored) { + return false; + } + } + + return false; + } + + private static class ConvertingExpanderFactory { + + private final ConversionService conversionService; + + ConvertingExpanderFactory(ConversionService conversionService) { + this.conversionService = conversionService; + } + + Param.Expander getExpander(TypeDescriptor typeDescriptor) { + return value -> { + Object converted = conversionService.convert(value, typeDescriptor, STRING_TYPE_DESCRIPTOR); + return (String) converted; + }; + } + + } + + private class SimpleAnnotatedParameterContext implements AnnotatedParameterProcessor.AnnotatedParameterContext { + + private final MethodMetadata methodMetadata; + + private final int parameterIndex; + + SimpleAnnotatedParameterContext(MethodMetadata methodMetadata, int parameterIndex) { + this.methodMetadata = methodMetadata; + this.parameterIndex = parameterIndex; + } + + @Override + public MethodMetadata getMethodMetadata() { + return methodMetadata; + } + + @Override + public int getParameterIndex() { + return parameterIndex; + } + + @Override + public void setParameterName(String name) { + nameParam(methodMetadata, name, parameterIndex); + } + + @Override + public Collection setTemplateParameter(String name, Collection rest) { + Collection params = ofNullable(rest).map(ArrayList::new).orElse(new ArrayList<>()); + params.add(String.format("{%s}", name)); + return params; + } - /** - * 如果没有注解 或者注解是 MultiRequestBody 返回ture - */ - @Override - protected boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[] annotations, int paramIndex) { - return super.processAnnotationsOnParameter(data, annotations, paramIndex); } } diff --git a/client/src/main/java/me/danwi/kato/client/KatoEncoder.java b/client/src/main/java/me/danwi/kato/client/KatoEncoder.java index 356f222..1893118 100644 --- a/client/src/main/java/me/danwi/kato/client/KatoEncoder.java +++ b/client/src/main/java/me/danwi/kato/client/KatoEncoder.java @@ -3,8 +3,13 @@ import feign.RequestTemplate; import feign.codec.EncodeException; import feign.codec.Encoder; +import me.danwi.kato.common.argument.MultiRequestBody; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.Map; public class KatoEncoder implements Encoder { @@ -14,9 +19,37 @@ public KatoEncoder(Encoder delegate) { this.delegate = delegate; } - @Override public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException { + if (object.getClass() != Object[].class) { + this.delegate.encode(object, bodyType, template); + } else { + Object[] objects = (Object[]) object; + if (objects.length == 0) { + this.delegate.encode(new HashMap(), Map.class, template); + } else if (objects.length == 1) { + this.delegate.encode(objects[0], objects[0].getClass(), template); + } else { + Map map = converToMap(objects, template.methodMetadata().method()); + this.delegate.encode(map, Map.class, template); + } + } + } + + private Map converToMap(Object[] objects, Method method) { + HashMap map = new HashMap<>(); + Parameter[] parameters = method.getParameters(); + for (int i = 0; i < parameters.length; i++) { + map.put(getKey(parameters[i]), objects[i]); + } + return map; + } + private String getKey(Parameter parameters) { + MultiRequestBody annotation = parameters.getAnnotation(MultiRequestBody.class); + if (annotation != null && annotation.value() != null && !annotation.value().equals("")) { + return annotation.value(); + } + return parameters.getName(); } } diff --git a/example/src/main/kotlin/me/danwi/kato/example/argument/MultiRequestBodyWithOutAnnoController.kt b/example/src/main/kotlin/me/danwi/kato/example/argument/MultiRequestBodyWithOutAnnoController.kt index 01a174a..21cf5b4 100644 --- a/example/src/main/kotlin/me/danwi/kato/example/argument/MultiRequestBodyWithOutAnnoController.kt +++ b/example/src/main/kotlin/me/danwi/kato/example/argument/MultiRequestBodyWithOutAnnoController.kt @@ -28,6 +28,11 @@ class MultiRequestBodyWithOutAnnoController { return TestEntity(id, name) } + @PostMapping("/multiRequest2") + fun multiRequest2(id: Int, name: String): TestEntity { + return TestEntity(id, name) + } + @PostMapping("/multiRequestSingle") fun multiRequestSingle(id: Int): TestEntity { return TestEntity(id, null) diff --git a/example/src/main/kotlin/me/danwi/kato/example/rpc/ExceptionRpcClient.kt b/example/src/main/kotlin/me/danwi/kato/example/rpc/ExceptionRpcClient.kt index 0eef7e9..cf9eb37 100644 --- a/example/src/main/kotlin/me/danwi/kato/example/rpc/ExceptionRpcClient.kt +++ b/example/src/main/kotlin/me/danwi/kato/example/rpc/ExceptionRpcClient.kt @@ -13,6 +13,4 @@ interface ExceptionRpcClient { @RequestMapping("/common-exception") fun commonException() - @RequestMapping("/exception") - fun exception() } \ No newline at end of file diff --git a/example/src/main/kotlin/me/danwi/kato/example/rpc/NoUseKatoClient.kt b/example/src/main/kotlin/me/danwi/kato/example/rpc/NoUseKatoClient.kt new file mode 100644 index 0000000..db5e073 --- /dev/null +++ b/example/src/main/kotlin/me/danwi/kato/example/rpc/NoUseKatoClient.kt @@ -0,0 +1,16 @@ +package me.danwi.kato.example.rpc + +import me.danwi.kato.example.argument.TestEntity +import org.springframework.cloud.openfeign.FeignClient +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestMapping + +@FeignClient(value = "testParamFeign", url = "http://localhost:8888") +interface NoUseKatoClient { + + @PostMapping("/withOutAnno/multiRequest2") + fun multiRequest(id: Int): TestEntity; + + @RequestMapping("/exception") + fun exception() +} \ No newline at end of file diff --git a/example/src/main/kotlin/me/danwi/kato/example/rpc/ParamRpcClient.kt b/example/src/main/kotlin/me/danwi/kato/example/rpc/ParamRpcClient.kt index 7e1baa9..08d92dd 100644 --- a/example/src/main/kotlin/me/danwi/kato/example/rpc/ParamRpcClient.kt +++ b/example/src/main/kotlin/me/danwi/kato/example/rpc/ParamRpcClient.kt @@ -3,12 +3,12 @@ package me.danwi.kato.example.rpc import me.danwi.kato.client.KatoClient import me.danwi.kato.example.argument.TestEntity import org.springframework.web.bind.annotation.PostMapping -import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestMapping @KatoClient(value = "testParam", url = "http://localhost:8888") interface ParamRpcClient { @PostMapping("/withOutAnno/multiRequest") - fun multiRequest(@RequestBody() id: Int, name: String): TestEntity; + fun multiRequest(id: Int, name: String): TestEntity; } \ No newline at end of file diff --git a/example/src/test/java/me/danwi/kato/example/Config.java b/example/src/test/java/me/danwi/kato/example/Config.java new file mode 100644 index 0000000..d338893 --- /dev/null +++ b/example/src/test/java/me/danwi/kato/example/Config.java @@ -0,0 +1,14 @@ +package me.danwi.kato.example; + +import feign.Logger; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class Config { + + @Bean + public Logger.Level level() { + return Logger.Level.FULL; + } +} diff --git a/example/src/test/java/me/danwi/kato/example/KatoClientParamTest.java b/example/src/test/java/me/danwi/kato/example/KatoClientParamTest.java index 2034d2e..a5a982e 100644 --- a/example/src/test/java/me/danwi/kato/example/KatoClientParamTest.java +++ b/example/src/test/java/me/danwi/kato/example/KatoClientParamTest.java @@ -1,6 +1,10 @@ package me.danwi.kato.example; +import feign.FeignException; +import me.danwi.kato.common.exception.KatoCommonException; import me.danwi.kato.example.argument.TestEntity; +import me.danwi.kato.example.rpc.ExceptionRpcClient; +import me.danwi.kato.example.rpc.NoUseKatoClient; import me.danwi.kato.example.rpc.ParamRpcClient; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -12,10 +16,43 @@ public class KatoClientParamTest { @Autowired private ParamRpcClient paramRpcClient; + @Autowired + private ExceptionRpcClient exceptionRpcClient; + + @Autowired + private NoUseKatoClient noUseKatoClient; + @Test public void test1() { final TestEntity entity = new TestEntity(1, "name"); final TestEntity entity2 = paramRpcClient.multiRequest(entity.getId(), entity.getName()); Assertions.assertEquals(entity, entity2); } + + @Test + public void test2() { + final TestEntity entity = new TestEntity(1, "name"); + final TestEntity entity2 = noUseKatoClient.multiRequest(entity.getId()); + Assertions.assertEquals(new TestEntity(entity.getId(), entity.getId().toString()), entity2); + } + + @Test + public void useKatoClientTest() { + try { + exceptionRpcClient.commonException(); + } catch (Exception e) { + Assertions.assertTrue(e instanceof KatoCommonException); + } + } + + @Test + public void noUseKatoClientTest() { + try { + noUseKatoClient.exception(); + } catch (Exception e) { + Assertions.assertTrue(e instanceof FeignException); + } + } + + } diff --git a/example/src/test/resources/application.yml b/example/src/test/resources/application.yml index 84c6fb1..770596e 100644 --- a/example/src/test/resources/application.yml +++ b/example/src/test/resources/application.yml @@ -1,2 +1,6 @@ server: - port: 8888 \ No newline at end of file + port: 8888 + +logging: + level: + me.danwi: debug \ No newline at end of file From c6ac473eabdda73ff094b0d98a39305422ba347a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=8A=A0=E9=9B=A8?= Date: Sat, 9 Jul 2022 02:50:05 +0800 Subject: [PATCH 23/24] =?UTF-8?q?=E5=AF=B9=E8=87=AA=E5=AE=9A=E4=B9=89const?= =?UTF-8?q?ruct=E5=92=8Cencode=E5=A2=9E=E5=8A=A0RequestParam=E5=8F=82?= =?UTF-8?q?=E6=95=B0=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/me/danwi/kato/example/ExampleApplication.kt | 6 ++++++ .../kotlin/me/danwi/kato/example/rpc/ParamRpcClient.kt | 6 +++++- .../java/me/danwi/kato/example/KatoClientParamTest.java | 7 ++++++- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/example/src/main/kotlin/me/danwi/kato/example/ExampleApplication.kt b/example/src/main/kotlin/me/danwi/kato/example/ExampleApplication.kt index fa2be49..e6482ce 100644 --- a/example/src/main/kotlin/me/danwi/kato/example/ExampleApplication.kt +++ b/example/src/main/kotlin/me/danwi/kato/example/ExampleApplication.kt @@ -8,6 +8,7 @@ import me.danwi.kato.server.EnableKatoServer import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController @SpringBootApplication @@ -28,6 +29,11 @@ class TestController { return TestData("kato") } + @RequestMapping("/param") + fun index(@RequestParam name: String): TestData { + return TestData(name) + } + @RequestMapping("/common-exception") fun commonException() { throw KatoCommonException("通用异常") diff --git a/example/src/main/kotlin/me/danwi/kato/example/rpc/ParamRpcClient.kt b/example/src/main/kotlin/me/danwi/kato/example/rpc/ParamRpcClient.kt index 08d92dd..2acf7fb 100644 --- a/example/src/main/kotlin/me/danwi/kato/example/rpc/ParamRpcClient.kt +++ b/example/src/main/kotlin/me/danwi/kato/example/rpc/ParamRpcClient.kt @@ -1,14 +1,18 @@ package me.danwi.kato.example.rpc import me.danwi.kato.client.KatoClient +import me.danwi.kato.example.TestData import me.danwi.kato.example.argument.TestEntity import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam @KatoClient(value = "testParam", url = "http://localhost:8888") interface ParamRpcClient { @PostMapping("/withOutAnno/multiRequest") - fun multiRequest(id: Int, name: String): TestEntity; + fun multiRequest(id: Int, name: String): TestEntity + @RequestMapping("/param") + fun index(@RequestParam name: String): TestData } \ No newline at end of file diff --git a/example/src/test/java/me/danwi/kato/example/KatoClientParamTest.java b/example/src/test/java/me/danwi/kato/example/KatoClientParamTest.java index a5a982e..a3aa3b6 100644 --- a/example/src/test/java/me/danwi/kato/example/KatoClientParamTest.java +++ b/example/src/test/java/me/danwi/kato/example/KatoClientParamTest.java @@ -54,5 +54,10 @@ public void noUseKatoClientTest() { } } - + @Test + public void requestParamTest() { + TestData test = new TestData("test"); + TestData index = paramRpcClient.index(test.getName()); + Assertions.assertEquals(test, index); + } } From c90f44d60c9eb0def5b468fd9c232e429ff4a381 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=8A=A0=E9=9B=A8?= Date: Sat, 9 Jul 2022 09:44:17 +0800 Subject: [PATCH 24/24] =?UTF-8?q?=E8=A7=A3=E5=86=B3javadoc=E6=9E=84?= =?UTF-8?q?=E5=BB=BA=E8=AD=A6=E5=91=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../me/danwi/kato/common/argument/MultiRequestBody.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/common/src/main/java/me/danwi/kato/common/argument/MultiRequestBody.java b/common/src/main/java/me/danwi/kato/common/argument/MultiRequestBody.java index 5bcf67f..d15695a 100644 --- a/common/src/main/java/me/danwi/kato/common/argument/MultiRequestBody.java +++ b/common/src/main/java/me/danwi/kato/common/argument/MultiRequestBody.java @@ -12,17 +12,20 @@ /** * 设置解析key + * + * @return 解析key */ String value() default ""; /** - * 是否必填参数 + * @return 是否必填参数 */ boolean required() default true; /** * 当body不是JSONObject或者不能匹配到key时 - * 是否将整个body作为参数解析 + * + * @return 是否将整个body作为参数解析 */ boolean parseBodyIfMissKey() default true;