From d43abed086d051d5097b6cd11da4739b8464a96c Mon Sep 17 00:00:00 2001 From: andanyang <1218853253@qq.com> Date: Wed, 1 Nov 2023 13:38:14 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20=E4=BC=98=E9=9B=85?= =?UTF-8?q?=E7=9A=84=E4=BD=BF=E7=94=A8=E6=9E=9A=E4=B8=BE=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- enum-spring-boot-starter/pom.xml | 59 ++++++++++++++++ .../EnumConverterAutoConfigure.java | 49 ++++++++++++++ .../EnumConverterWebConfigure.java | 24 +++++++ .../framework/converter/EnumConverter.java | 16 +++++ .../factory/EnumConverterFactory.java | 67 +++++++++++++++++++ .../converter/impl/EnumValueConverter.java | 53 +++++++++++++++ .../converter/impl/JackosnEnumConverter.java | 55 +++++++++++++++ .../main/resources/META-INF/spring.factories | 2 + .../java/com/admin4j/framework/AppTest.java | 14 ++++ .../admin4j/framework/EnumConverterTest.java | 40 +++++++++++ .../framework/constant/UserStatus.java | 34 ++++++++++ .../controller/EnumConverterController.java | 21 ++++++ pom.xml | 1 + 13 files changed, 435 insertions(+) create mode 100644 enum-spring-boot-starter/pom.xml create mode 100644 enum-spring-boot-starter/src/main/java/com/admin4j/framework/autoconfigure/EnumConverterAutoConfigure.java create mode 100644 enum-spring-boot-starter/src/main/java/com/admin4j/framework/autoconfigure/EnumConverterWebConfigure.java create mode 100644 enum-spring-boot-starter/src/main/java/com/admin4j/framework/converter/EnumConverter.java create mode 100644 enum-spring-boot-starter/src/main/java/com/admin4j/framework/converter/factory/EnumConverterFactory.java create mode 100644 enum-spring-boot-starter/src/main/java/com/admin4j/framework/converter/impl/EnumValueConverter.java create mode 100644 enum-spring-boot-starter/src/main/java/com/admin4j/framework/converter/impl/JackosnEnumConverter.java create mode 100644 enum-spring-boot-starter/src/main/resources/META-INF/spring.factories create mode 100644 enum-spring-boot-starter/src/test/java/com/admin4j/framework/AppTest.java create mode 100644 enum-spring-boot-starter/src/test/java/com/admin4j/framework/EnumConverterTest.java create mode 100644 enum-spring-boot-starter/src/test/java/com/admin4j/framework/constant/UserStatus.java create mode 100644 enum-spring-boot-starter/src/test/java/com/admin4j/framework/controller/EnumConverterController.java diff --git a/enum-spring-boot-starter/pom.xml b/enum-spring-boot-starter/pom.xml new file mode 100644 index 0000000..5e2e245 --- /dev/null +++ b/enum-spring-boot-starter/pom.xml @@ -0,0 +1,59 @@ + + 4.0.0 + + com.admin4j + framework + 0.8.0 + + + com.admin4j.framework + enum-spring-boot-starter + jar + + enum-spring-boot-starter + 优雅的使用枚举参数 + + + + UTF-8 + + + + + com.baomidou + mybatis-plus-annotation + 3.5.3.1 + provided + + + com.fasterxml.jackson.core + jackson-annotations + 2.13.5 + provided + + + org.springframework.boot + spring-boot-autoconfigure-processor + provided + + + org.springframework.boot + spring-boot-starter-web + test + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework + spring-webmvc + + + org.springframework.boot + spring-boot-autoconfigure + + + diff --git a/enum-spring-boot-starter/src/main/java/com/admin4j/framework/autoconfigure/EnumConverterAutoConfigure.java b/enum-spring-boot-starter/src/main/java/com/admin4j/framework/autoconfigure/EnumConverterAutoConfigure.java new file mode 100644 index 0000000..a4205fa --- /dev/null +++ b/enum-spring-boot-starter/src/main/java/com/admin4j/framework/autoconfigure/EnumConverterAutoConfigure.java @@ -0,0 +1,49 @@ +package com.admin4j.framework.autoconfigure; + +import com.admin4j.framework.converter.EnumConverter; +import com.admin4j.framework.converter.impl.EnumValueConverter; +import com.admin4j.framework.converter.impl.JackosnEnumConverter; +import com.baomidou.mybatisplus.annotation.EnumValue; +import com.fasterxml.jackson.annotation.JsonValue; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.context.annotation.Bean; +import org.springframework.core.annotation.Order; +import org.springframework.core.convert.ConversionService; + +import java.util.List; + +/** + * @author andanyang + * @since 2023/11/1 11:37 + */ +public class EnumConverterAutoConfigure { + + @Bean + @Order(1) + @ConditionalOnClass(name = "com.baomidou.mybatisplus.annotation.EnumValue") + public EnumValueConverter enumValueConverter() { + return new EnumValueConverter(EnumValue.class); + } + + @Bean + @Order(2) + @ConditionalOnClass(name = "com.fasterxml.jackson.annotation.JsonValue") + public EnumValueConverter jsonEnumValueConverter() { + return new EnumValueConverter(JsonValue.class); + } + + @Bean + @Order(3) + @ConditionalOnClass(name = "com.fasterxml.jackson.annotation.JsonCreator") + public JackosnEnumConverter jackosnEnumConverter(ObjectProvider objectProvider) { + return new JackosnEnumConverter(objectProvider); + } + + @Bean + @ConditionalOnBean(EnumConverter.class) + public EnumConverterWebConfigure enumConverterWebConfigure(List enumConverters) { + return new EnumConverterWebConfigure(enumConverters); + } +} diff --git a/enum-spring-boot-starter/src/main/java/com/admin4j/framework/autoconfigure/EnumConverterWebConfigure.java b/enum-spring-boot-starter/src/main/java/com/admin4j/framework/autoconfigure/EnumConverterWebConfigure.java new file mode 100644 index 0000000..c2c4de7 --- /dev/null +++ b/enum-spring-boot-starter/src/main/java/com/admin4j/framework/autoconfigure/EnumConverterWebConfigure.java @@ -0,0 +1,24 @@ +package com.admin4j.framework.autoconfigure; + +import com.admin4j.framework.converter.EnumConverter; +import com.admin4j.framework.converter.factory.EnumConverterFactory; +import lombok.RequiredArgsConstructor; +import org.springframework.format.FormatterRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import java.util.List; + +/** + * @author andanyang + * @since 2023/11/1 13:26 + */ +@RequiredArgsConstructor +public class EnumConverterWebConfigure implements WebMvcConfigurer { + + private final List enumConverters; + + @Override + public void addFormatters(FormatterRegistry registry) { + registry.addConverterFactory(new EnumConverterFactory(enumConverters)); + } +} diff --git a/enum-spring-boot-starter/src/main/java/com/admin4j/framework/converter/EnumConverter.java b/enum-spring-boot-starter/src/main/java/com/admin4j/framework/converter/EnumConverter.java new file mode 100644 index 0000000..b940e00 --- /dev/null +++ b/enum-spring-boot-starter/src/main/java/com/admin4j/framework/converter/EnumConverter.java @@ -0,0 +1,16 @@ +package com.admin4j.framework.converter; + +import org.springframework.lang.Nullable; + +/** + * 枚举转化器 + * + * @author andanyang + * @since 2023/11/1 9:57 + */ +public interface EnumConverter { + + + @Nullable + T convert(String source, Class enumType); +} diff --git a/enum-spring-boot-starter/src/main/java/com/admin4j/framework/converter/factory/EnumConverterFactory.java b/enum-spring-boot-starter/src/main/java/com/admin4j/framework/converter/factory/EnumConverterFactory.java new file mode 100644 index 0000000..256ddc0 --- /dev/null +++ b/enum-spring-boot-starter/src/main/java/com/admin4j/framework/converter/factory/EnumConverterFactory.java @@ -0,0 +1,67 @@ +package com.admin4j.framework.converter.factory; + + +import com.admin4j.framework.converter.EnumConverter; +import lombok.RequiredArgsConstructor; +import org.springframework.core.convert.converter.Converter; +import org.springframework.core.convert.converter.ConverterFactory; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author andanyang + * @since 2023/11/1 9:22 + */ +@RequiredArgsConstructor +public class EnumConverterFactory implements ConverterFactory { + + @SuppressWarnings("rawtypes") + private static final Map CONVERTERS = new ConcurrentHashMap<>(64); + private final List enumConverters; + + /** + * Get the converter to convert from S to target type T, where T is also an instance of R. + * + * @param targetType the target type to convert to + * @return a converter from S to T + */ + @Override + public Converter getConverter(Class targetType) { + return CONVERTERS.computeIfAbsent(targetType, (key) -> new StringToEnum(targetType, enumConverters)); + } + + private static class StringToEnum implements Converter { + + private final Class enumType; + private final List enumConverters; + + StringToEnum(Class enumType, List enumConverters) { + this.enumType = enumType; + this.enumConverters = enumConverters; + } + + /** + * Convert the source object of type {@code S} to target type {@code T}. + * + * @param source the source object to convert, which must be an instance of {@code S} (never {@code null}) + * @return the converted object, which must be an instance of {@code T} (potentially {@code null}) + * @throws IllegalArgumentException if the source cannot be converted to the desired target type + */ + @Override + public T convert(String source) { + if (source.isEmpty()) { + // It's an empty enum identifier: reset the enum value to null. + return null; + } + for (EnumConverter enumConverter : enumConverters) { + T convert = enumConverter.convert(source, enumType); + if (convert != null) { + return convert; + } + } + return null; + } + } +} diff --git a/enum-spring-boot-starter/src/main/java/com/admin4j/framework/converter/impl/EnumValueConverter.java b/enum-spring-boot-starter/src/main/java/com/admin4j/framework/converter/impl/EnumValueConverter.java new file mode 100644 index 0000000..9e08056 --- /dev/null +++ b/enum-spring-boot-starter/src/main/java/com/admin4j/framework/converter/impl/EnumValueConverter.java @@ -0,0 +1,53 @@ +package com.admin4j.framework.converter.impl; + + +import com.admin4j.framework.converter.EnumConverter; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; + +/** + * 使用 EnumValue注解 字段进行匹配 + * + * @author andanyang + * @since 2023/11/1 10:07 + */ +// @Service +public class EnumValueConverter implements EnumConverter { + + private final Class targetAnnotation; + + public EnumValueConverter(Class targetAnnotation) { + this.targetAnnotation = targetAnnotation; + } + + @Override + public T convert(String source, Class enumType) { + + // 被注解的值字段 + Field valueField = null; + Field[] declaredFields = enumType.getDeclaredFields(); + for (Field field : declaredFields) { + Annotation annotation = field.getAnnotation(targetAnnotation); + if (annotation != null) { + valueField = field; + } + } + + if (valueField != null) { + T[] enumConstants = enumType.getEnumConstants(); + // 查找枚举 + for (T e : enumConstants) { + try { + valueField.setAccessible(true); + if (String.valueOf(valueField.get(e)).equals(source)) { + return e; + } + } catch (IllegalAccessException ex) { + throw new RuntimeException(ex); + } + } + } + return null; + } +} diff --git a/enum-spring-boot-starter/src/main/java/com/admin4j/framework/converter/impl/JackosnEnumConverter.java b/enum-spring-boot-starter/src/main/java/com/admin4j/framework/converter/impl/JackosnEnumConverter.java new file mode 100644 index 0000000..287fe3f --- /dev/null +++ b/enum-spring-boot-starter/src/main/java/com/admin4j/framework/converter/impl/JackosnEnumConverter.java @@ -0,0 +1,55 @@ +package com.admin4j.framework.converter.impl; + + +import com.admin4j.framework.converter.EnumConverter; +import com.fasterxml.jackson.annotation.JsonCreator; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.core.convert.ConversionService; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * 使用 @JsonCreator 方法注解静态字段进行匹配 + * + * @author andanyang + * @since 2023/11/1 10:07 + */ + +@RequiredArgsConstructor +public class JackosnEnumConverter implements EnumConverter { + + private final ObjectProvider conversionServiceObjectProvider; + + @Override + public T convert(String source, Class enumType) { + + Method[] declaredMethods = enumType.getDeclaredMethods(); + + for (Method method : declaredMethods) { + if (method.isAnnotationPresent(JsonCreator.class)) { + try { + Object invoke; + Class parameterType = method.getParameterTypes()[0]; + Object cast = conversionServiceObjectProvider.getIfAvailable().convert(source, parameterType); + invoke = method.invoke(enumType, cast); + + if (invoke == null) { + return null; + } + if (!enumType.isInstance(invoke)) { + throw new IllegalArgumentException("JsonCreator error: target " + enumType.getCanonicalName() + " but get " + invoke.getClass().getCanonicalName()); + } + + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } + } + } + + return null; + } +} diff --git a/enum-spring-boot-starter/src/main/resources/META-INF/spring.factories b/enum-spring-boot-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..f529124 --- /dev/null +++ b/enum-spring-boot-starter/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + com.admin4j.framework.autoconfigure.EnumConverterAutoConfigure \ No newline at end of file diff --git a/enum-spring-boot-starter/src/test/java/com/admin4j/framework/AppTest.java b/enum-spring-boot-starter/src/test/java/com/admin4j/framework/AppTest.java new file mode 100644 index 0000000..396d384 --- /dev/null +++ b/enum-spring-boot-starter/src/test/java/com/admin4j/framework/AppTest.java @@ -0,0 +1,14 @@ +package com.admin4j.framework; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * Unit test for simple App. + */ +@SpringBootApplication +public class AppTest { + public static void main(String[] args) { + SpringApplication.run(AppTest.class); + } +} diff --git a/enum-spring-boot-starter/src/test/java/com/admin4j/framework/EnumConverterTest.java b/enum-spring-boot-starter/src/test/java/com/admin4j/framework/EnumConverterTest.java new file mode 100644 index 0000000..58907ba --- /dev/null +++ b/enum-spring-boot-starter/src/test/java/com/admin4j/framework/EnumConverterTest.java @@ -0,0 +1,40 @@ +package com.admin4j.framework; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultHandlers; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; + +/** + * @author andanyang + * @since 2023/11/1 11:58 + */ +@SpringBootTest(classes = AppTest.class) +@AutoConfigureMockMvc +public class EnumConverterTest { + + @Autowired + private MockMvc mockMvc; + + @ParameterizedTest + @ValueSource(strings = {"3", "2", "1"}) + void genderIdCode(String userStatus) throws Exception { + final String result = mockMvc.perform( + MockMvcRequestBuilders.get("/UserStatus") + .param("userStatus", userStatus) + ) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andDo(MockMvcResultHandlers.print()) + .andReturn() + .getResponse() + .getContentAsString(); + + Assertions.assertEquals(result, userStatus); + } +} diff --git a/enum-spring-boot-starter/src/test/java/com/admin4j/framework/constant/UserStatus.java b/enum-spring-boot-starter/src/test/java/com/admin4j/framework/constant/UserStatus.java new file mode 100644 index 0000000..1011536 --- /dev/null +++ b/enum-spring-boot-starter/src/test/java/com/admin4j/framework/constant/UserStatus.java @@ -0,0 +1,34 @@ +package com.admin4j.framework.constant; + +import com.baomidou.mybatisplus.annotation.EnumValue; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * @author andanyang + * @since 2023/11/1 9:12 + */ +@Getter +@AllArgsConstructor +public enum UserStatus { + + NORMAL(1, "正常"), + FREEZE(2, "冻结"); + + @EnumValue + @JsonValue + private final int value; + private final String text; + + @JsonCreator + public static UserStatus valueOf(int value) { + for (UserStatus status : UserStatus.values()) { + if (status.value == value) { + return status; + } + } + return null; + } +} diff --git a/enum-spring-boot-starter/src/test/java/com/admin4j/framework/controller/EnumConverterController.java b/enum-spring-boot-starter/src/test/java/com/admin4j/framework/controller/EnumConverterController.java new file mode 100644 index 0000000..5fa1e49 --- /dev/null +++ b/enum-spring-boot-starter/src/test/java/com/admin4j/framework/controller/EnumConverterController.java @@ -0,0 +1,21 @@ +package com.admin4j.framework.controller; + +import com.admin4j.framework.constant.UserStatus; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author andanyang + * @since 2023/11/1 13:12 + */ +@RestController +@RequestMapping +public class EnumConverterController { + @RequestMapping("UserStatus") + public UserStatus userStatus(@RequestParam UserStatus userStatus) { + + return userStatus; + } + +} diff --git a/pom.xml b/pom.xml index ebbb84f..059f0d3 100644 --- a/pom.xml +++ b/pom.xml @@ -40,6 +40,7 @@ tenant-spring-boot-starter feign-spring-boot-starter ttl-spring-boot-starter + enum-spring-boot-starter 0.9.0-SNAPSHOT