Cómo recibir elegantemente parámetros de tipo de tiempo en la interfaz

Primer lanzamiento: cuenta pública " Zhao Xiake "

Prefacio

En lo anterior, hemos resumido los métodos comunes para pasar parámetros en las interfaces Http de front-end y back-end. Este artículo resume principalmente cómo manejar el campo de tiempo en los parámetros. Dado que hay muchos formatos de tiempo, los más utilizados son formato de marca de tiempo, formato de hora UTC, formato de hora estándar, etc., y el parámetro de hora puede aparecer en la URL, en el cuerpo o en el encabezado, por lo que este artículo proporciona un conjunto de soluciones elegantes para procesar campos de formato de hora.

¿Qué pasará si el formato de hora no realiza el procesamiento de tareas?

Creamos una interfaz simple que quiere @PathVariablerecibir parámetros de tiempo de tipo Fecha y @RequestParamtipo LocalDateTime, y queremos @RequestBodyrecibir parámetros de tiempo en JSON a través de:

    @GetMapping("/time/{today}")
    public UserDTO time(@PathVariable Date today, @RequestParam LocalDateTime time,@RequestBody UserDTO userDTO) {
    
    
        return userDTO;
    }
    
    @Data
    public class UserDTO {
    
    
        private Long id;
        private String userName;
        private Date  now;
        private Date  day;
        private LocalDateTime time;
        private LocalDateTime timeStack;
    }

Mensaje de solicitud de prueba HTTP:

GET http://localhost:80/time/2023-09-10?time=2023-09-15 11:11:11
Accept: application/json
Content-Type: application/json

{
    
    
    "id":1,
    "now":"2023-09-15 13:50:10",
    "day":"2023-09-15",
    "time": "2023-09-15 13:50:10",
    "timeStack": 1694757010407
}

resultado:

Si no procesamos tareas, SpringBoot no puede ayudarnos automáticamente a convertir los parámetros de tiempo en la interfaz al formato de hora que queremos. El valor predeterminado es usar String para recibirlo. Si usa LocalDateTime o Date para recibirlo directamente, un tipo Se informará el error de conversión. Esto también es relativamente fácil de entender, porque hay demasiados formatos de hora. Sin conocer el formato de hora específico, el marco no puede analizar la hora y solo puede usar String para recibirla. Finalmente, si String se convierte en un tipo de hora, Definitivamente se informará un error. Por supuesto, podemos usar String para recibir y luego convertirlo manualmente al formato de hora correspondiente. Este método es demasiado primitivo. A continuación, echemos un vistazo a cómo se procesan los campos de hora en diferentes niveles.


Resolved [org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'java.time.LocalDateTime'; 
Resolved [org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'java.util.Date';

solución de bronce

Sabemos que SpringMVC inyecta automáticamente parámetros en nuestro objeto JAVA cuando recibe parámetros WebDataBinder. SpringMVC nos brinda la @InitBindercapacidad de inicializar el análisis de parámetros antes de recibir los parámetros. Luego podemos agregarlos en el Controlador @InitBindery luego ir al WebDataBinderobjeto y personalizar dos CustomEditors, LocalDateTime. y Fecha, para que podamos convertir automáticamente String al formato de hora cuando usemos @PathVariabley . @RequestParamSin embargo, @RequestBodyJackson se usa para el análisis de datos JSON de forma predeterminada, por lo que aún no puede procesar el formato de hora en el objeto. Podemos agregar anotaciones al campo de hora @JsonFormatpara especificar el formato de hora, de modo que @RequestBodyel formato de hora se pueda analizar automáticamente.

    @InitBinder
    public void initBinder(WebDataBinder binder) {
    
    
        binder.registerCustomEditor(LocalDateTime.class, new PropertyEditorSupport() {
    
    
            @Override
            public void setAsText(String text) {
    
    
                setValue(DateUtil.parseLocalDateTime(text));
            }
        });

        binder.registerCustomEditor(Date.class, new PropertyEditorSupport() {
    
    
            @Override
            public void setAsText(String text) {
    
    
                setValue(DateUtil.parse(text,NORM_DATETIME_PATTERN,NORM_DATE_PATTERN));
            }
        });
    }
    
    
    @Data
    public class UserDTO {
    
    
        private Long id;
        private String userName;
        @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
        private Date  now;
        @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
        private Date  day;
        @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
        private LocalDateTime time;
        //private LocalDateTime timeStack;
    }

Problemas con la solución de análisis de bronce:

  1. @InitBinderEl alcance es solo el Controlador actual. Si uso 100 Controladores, ¿tengo que escribir 100 más?@InitBinder
  2. @JsonFormatTambién es necesario agregar una anotación a cada campo, y solo puede admitir un formato de hora, si también queremos admitir el formato de marca de tiempo, no podemos hacerlo.

solución de plata

Para el problema 1 de la solución de resolución de bronce , nuestra solución es @ControllerAdviceutilizar@InitBinder

@ControllerAdvice
public class GlobalControllerAdvice {
    
    

    @InitBinder
    public void initBinder(WebDataBinder binder) {
    
    
        binder.registerCustomEditor(LocalDateTime.class, new PropertyEditorSupport() {
    
    
            @Override
            public void setAsText(String text) {
    
    
                setValue(DateUtil.parseLocalDateTime(text));
            }
        });
        binder.registerCustomEditor(Date.class, new PropertyEditorSupport() {
    
    
            @Override
            public void setAsText(String text) {
    
    
                setValue(DateUtil.parse(text,NORM_DATETIME_PATTERN,NORM_DATE_PATTERN));
            }
        });
    }
}

Con respecto al problema 2 de la solución bronce , nuestro análisis es que dado que SpringMvc usa Jackson para analizar JSON, entonces podemos dejar que SpringMVC use nuestro personalizado Mapperpara analizar JSON. Estamos @Configurationagregando ObjectMapper,
y luego personalizando LocalDateTimeSerializery LocalDateTimeDeserializerserializando el procesador de respuesta, para que no No es necesario agregar cada campo @JsonFormat. Cuando Jaskson encuentra un tipo de recepción de parámetro que es LocalDateTime al analizar datos JSON, utilizará directamente nuestro procesador personalizado, de modo que no informará errores de conversión de campos. Ahora, ¿no es mucho más elegante @JsonFormat? escribir uno por uno?

@Configuration
public class WebConfig implements WebMvcConfigurer {
    
    
    @Bean
    @Primary
    public ObjectMapper objectMapper() {
    
    
        ObjectMapper mapper = new ObjectMapper();
        SimpleModule module = new SimpleModule();
        module.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer());
        module.addSerializer(Date.class, new DateTimeSerializer());
        module.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer());
        module.addDeserializer(Date.class, new DateTimeDeserializer());
        mapper.registerModule(module);
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        return JsonUtils.getMapper();
    }
}

public class DateTimeDeserializer extends StdDeserializer<Date> {
    
    

    public DateTimeDeserializer() {
    
    
        this(null);
    }

    public DateTimeDeserializer(Class<?> vc) {
    
    
        super(vc);
    }

    @Override
    public Date deserialize(JsonParser jp, DeserializationContext ctx)
            throws IOException {
    
    
        String value = jp.getValueAsString();
            return DateUtil.parse(value,NORM_DATETIME_PATTERN,NORM_DATE_PATTERN);
    }
}

public class DateTimeSerializer extends StdSerializer<Date> {
    
    

    public DateTimeSerializer() {
    
    
        this(null);
    }

    public DateTimeSerializer(Class<Date> t) {
    
    
        super(t);
    }

    @Override
    public void serialize(Date value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
    
    
        jgen.writeString(DateUtil.format(value, DatePattern.NORM_DATETIME_PATTERN));
    }
}

public class LocalDateTimeDeserializer extends StdDeserializer<LocalDateTime> {
    
    

    public LocalDateTimeDeserializer() {
    
    
        this(null);
    }

    public LocalDateTimeDeserializer(Class<?> vc) {
    
    
        super(vc);
    }

    @Override
    public LocalDateTime deserialize(JsonParser jp, DeserializationContext ctx)
            throws IOException {
    
    
        String value = jp.getValueAsString();
        if (StrUtil.isNumeric(value)) {
    
    
            Date date = new Date(jp.getLongValue());
            return LocalDateTime.ofInstant(date.toInstant(),  ZoneId.of("Asia/Shanghai"));
        } else {
    
    
            return DateUtil.parseLocalDateTime(value);
        }
    }
}

public class LocalDateTimeSerializer extends StdSerializer<LocalDateTime> {
    
    

    public LocalDateTimeSerializer() {
    
    
        this(null);
    }

    public LocalDateTimeSerializer(Class<LocalDateTime> t) {
    
    
        super(t);
    }

    @Override
    public void serialize(LocalDateTime value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
    
    
        jgen.writeString(LocalDateTimeUtil.formatNormal(value));
    }
}


Hay un problema:

  1. @ControllerAdvicePara interceptar en función de aspectos es necesario interceptar cada interfaz, el rendimiento y la elegancia no son muy buenos, ¿se puede manejar con tanta elegancia como Jackson?

solución rey

Agregamos y Configurationpersonalizamos el Convertidor para convertir el tipo de hora, de modo que ya sea que esté pasando parámetros de datos JSON, parámetros de URL o parámetros de encabezado, no importa si la hora que recibe es del tipo Fecha o LocalDateTime, y no No importa cuál sea su formato de hora. Ya sea un formato de hora estándar o una marca de tiempo, todos resuelven automáticamente el problema de la recepción automática de la hora. ¿No es esto más elegante?Converter<String, LocalDateTime> stringLocalDateTimeConverter()Converter<String, Date> stringDateTimeConverter()

  
@Configuration
public class WebConfig  {
    
    
    @Bean
    @Primary
    public ObjectMapper objectMapper() {
    
    
        ObjectMapper mapper = new ObjectMapper();
        SimpleModule module = new SimpleModule();
        module.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer());
        module.addSerializer(Date.class, new DateTimeSerializer());
        module.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer());
        module.addDeserializer(Date.class, new DateTimeDeserializer());
        mapper.registerModule(module);
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        return JsonUtils.getMapper();
    }

    @Bean
    public Converter<String, LocalDateTime> stringLocalDateTimeConverter() {
    
    
        return new Converter<String, LocalDateTime>() {
    
    
            @Override
            public LocalDateTime convert(String source) {
    
    
                if (StrUtil.isNumeric(source)) {
    
    
                    return LocalDateTimeUtil.of(Long.parseLong(source));
                } else {
    
    
                    return DateUtil.parseLocalDateTime(source);
                }
            }
        };

    }

    @Bean
    public Converter<String, Date> stringDateTimeConverter() {
    
    
        return new Converter<String, Date>() {
    
    
            @Override
            public Date convert(String source) {
    
    
                if (StrUtil.isNumeric(source)) {
    
    
                    return new Date(Long.parseLong(source));
                } else {
    
    
                    return DateUtil.parse(source);
                }
            }
        };
    }

}

Resumir

Este artículo presenta cómo recibir elegantemente parámetros de tipo de tiempo en el protocolo HTTP durante el desarrollo del proyecto SpringBoot. Los parámetros de tiempo pueden aparecer en los encabezados URL Path, queryString, FormData, BodyJSON y HTTP. El formato de tiempo puede ser formato de encabezado, marca de tiempo y el parámetro de tiempo de recepción puede ser Date o LocalDateTime. Resuelve el problema del campo de tipo de tiempo de la interfaz en la interfaz. muy elegantemente a nivel mundial. .

Supongo que te gusta

Origin blog.csdn.net/whzhaochao/article/details/132939209
Recomendado
Clasificación