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 @PathVariable
recibir parámetros de tiempo de tipo Fecha y @RequestParam
tipo LocalDateTime, y queremos @RequestBody
recibir 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 @InitBinder
capacidad de inicializar el análisis de parámetros antes de recibir los parámetros. Luego podemos agregarlos en el Controlador @InitBinder
y luego ir al WebDataBinder
objeto y personalizar dos CustomEditors, LocalDateTime. y Fecha, para que podamos convertir automáticamente String al formato de hora cuando usemos @PathVariable
y . @RequestParam
Sin embargo, @RequestBody
Jackson 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 @JsonFormat
para especificar el formato de hora, de modo que @RequestBody
el 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:
@InitBinder
El alcance es solo el Controlador actual. Si uso 100 Controladores, ¿tengo que escribir 100 más?@InitBinder
@JsonFormat
Tambié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 @ControllerAdvice
utilizar@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 Mapper
para analizar JSON. Estamos @Configuration
agregando ObjectMapper
,
y luego personalizando LocalDateTimeSerializer
y LocalDateTimeDeserializer
serializando 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:
@ControllerAdvice
Para 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 Configuration
personalizamos 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. .