ES启动器实现及应用间fegin调用fastJson数据解析时间类型转换bug修复

1.ES启动器实现      1.1 EsAutoConfigure自动装配类    1.2  在resources下新建一个META-INFO文件夹下新增一个spring.factories文件2.应用间fegin调用fastJson数据解析时间类型转换bug修复       2.1 FastJsonConfig       2.2 JacksonConfig       2.3 LocalDateTimeFormatConfig       2.4 FeignClientConfig       2.5 WebMvcAndJackson2OrFastJsonConfig3.ES启动器和代码和应用间fegin调用fastJson数据解析时间类型转换bug修复代码



1.ES启动器实现

1.1 EsAutoConfigure自动装配类

package com.zlf.es.spring.boot.autoconfigure;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.ArrayList;
import java.util.List;

/**
* @author zlf
* @description:
* @time: 2022/06/24
*/
@Configuration
@RefreshScope
public class EsAutoConfigure {

   /**
    * 创建单例模式的RequestOptions,使得所有请求共用
    */
   public static final RequestOptions COMMON_OPTIONS;

   static {
       RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
       COMMON_OPTIONS = builder.build();
  }

   private final String NPE = "null";

   /**
    * 协议
    */
   @Value("${elasticsearch.schema:http}")
   private String schema;

   /**
    * 集群地址,如果有多个用“,”隔开
    */
   @Value("${elasticsearch.address}")
   private String address;

   /**
    * 账户
    */
   @Value("${elasticsearch.username:null}")
   private String username;

   /**
    * 密码
    */
   @Value("${elasticsearch.password:null}")
   private String password;

   /**
    * 连接超时时间
    */
   @Value("${elasticsearch.connectTimeout:5000}")
   private int connectTimeout;

   /**
    * Socket 连接超时时间
    */
   @Value("${elasticsearch.socketTimeout:30000}")
   private int socketTimeout;

   /**
    * 获取连接的超时时间
    */
   @Value("${elasticsearch.connectionRequestTimeout:5000}")
   private int connectionRequestTimeout;

   /**
    * 最大连接数
    */
   @Value("${elasticsearch.maxConnectNum:100}")
   private int maxConnectNum;

   /**
    * 最大路由连接数
    */
   @Value("${elasticsearch.maxConnectPerRoute:100}")
   private int maxConnectPerRoute;

   @Bean("esClient")
   @ConditionalOnClass(value = {RequestOptions.class, RestHighLevelClient.class})
   public RestHighLevelClient restHighLevelClient() {
       // 拆分地址
       List<HttpHost> hostLists = new ArrayList<>();
       if (address.equals(NPE)) {
           throw new RuntimeException("es的address列表没有配置,请检查配置");
      }
       String[] hostList = address.split(",");
       if (hostList.length == 1) {
           String host = hostList[0].split(":")[0];
           String port = hostList[0].split(":")[1];
           hostLists.add(new HttpHost(host, Integer.parseInt(port), schema));
      } else {
           for (String addr : hostList) {
               String host = addr.split(":")[0];
               String port = addr.split(":")[1];
               hostLists.add(new HttpHost(host, Integer.parseInt(port), schema));
          }
      }
       // 转换成 HttpHost 数组
       HttpHost[] httpHost = hostLists.toArray(new HttpHost[]{});
       // 构建连接对象
       RestClientBuilder builder = RestClient.builder(httpHost);
       if (StringUtils.isNotEmpty(username) && !NPE.equals(username) && StringUtils.isNotEmpty(password) && !NPE.equals(password)) {
           //设置用户名和密码
           final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
           credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password));

           builder.setHttpClientConfigCallback(httpClientBuilder -> {
               httpClientBuilder.disableAuthCaching();
               return httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
          });
      }
       // 异步连接延时配置
       builder.setRequestConfigCallback(requestConfigBuilder -> {
           requestConfigBuilder.setConnectTimeout(connectTimeout);
           requestConfigBuilder.setSocketTimeout(socketTimeout);
           requestConfigBuilder.setConnectionRequestTimeout(connectionRequestTimeout);
           return requestConfigBuilder;
      });
       // 异步连接数配置
       builder.setHttpClientConfigCallback(httpClientBuilder -> {
           httpClientBuilder.setMaxConnTotal(maxConnectNum);
           httpClientBuilder.setMaxConnPerRoute(maxConnectPerRoute);
           return httpClientBuilder;
      });
       return new RestHighLevelClient(builder);
  }

}

1.2  在resources下新建一个META-INFO文件夹下新增一个spring.factories文件

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.zlf.es.spring.boot.autoconfigure.EsAutoConfigure,\
com.zlf.es.spring.boot.autoconfigure.service.impl.DocServiceImpl,\
com.zlf.es.spring.boot.autoconfigure.service.impl.IndexServiceImpl

     该文件就是Start启动器的核心配置文件,Start启动的原理就不做过多的讲解,本文针对如何封装一个Start的代码示例,EsAutoConfigure这个是ES自动装配的类,IndexServiceImpl是我分装的一套关于操作index索引的API,DocServiceImpl是我封装的一套关于文档操作的API.

1.3 使用方法

只需要在nacos的yml中配置:

elasticsearch:
address: ip:9200
username:
password:

然后在项目中引入下依赖:

  <properties>
      <es.rest.high.level.client>7.14.2es.rest.high.level.client>
properties>
 <dependency>
      <groupId>org.zlfgroupId>
      <artifactId>es-spring-boot-startartifactId>
      <version>1.0-SNAPSHOTversion>
 dependency>
 <dependency>
      <groupId>org.elasticsearch.clientgroupId>
      <artifactId>elasticsearch-rest-high-level-clientartifactId>
      <version>${es.rest.high.level.client}version>
 dependency>
 <dependency>
       <groupId>org.elasticsearchgroupId>
       <artifactId>elasticsearchartifactId>
       <version>${es.rest.high.level.client}version>
 dependency>
 <dependency>
        <groupId>org.elasticsearch.clientgroupId>
        <artifactId>elasticsearch-rest-clientartifactId>
        <version>${es.rest.high.level.client}version>
 dependency>

       即可轻松使用es,该pom中的ES的版本使用的是7.14.2,后续可以丰富和完成这个Start.


2.应用间fegin调用fastJson数据解析时间类型转换bug修复

      之前做一个项目使用feign来调用SpringCloudAliBaBa的服务,结果调用报了一个莫名其妙的错误

Caused by: com.alibaba.fastjson.JSONException: write javaBean error, fastjson version 1.2.76, class com.xxx.framework.api.response.RestResponse, java.time.LocalDateTime cannot be cast to java.util.Date

       遇到这种莫名其妙的错误也是让人头大,这个问题让我搞了一个星期才完美的解决了,通过去看网上一些教程和自己看源码之后,走过了一些坑,最后给它解决了,我还搞了一个fegin调用HttpMessageConverter支持jackSon和fastJson两种编解码转换器,废话不多说,直接上代码:

      项目中需要有fastJson的依赖:

        <dependency>
           <groupId>com.alibabagroupId>
           <artifactId>fastjsonartifactId>
           <version>1.2.79version>
       dependency>-->

      在项目下新建一个包名为com.alibaba.fastjson.serializer下放一个SimpleDateFormatSerializer类:

package com.alibaba.fastjson.serializer;

import java.io.IOException;
import java.lang.reflect.Type;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;

/**
* 1).jackson和fastjson序列化和反序列化混用,
* 只要json的格式都是一个标准都可以解析的
* (除非两个库的编码实现上有差异,有特特殊的字符或类型的序列化和反序列化有差异的话,就会不能相互解析
*


* 2).fastJson的bug
* fastJson的JSON.toJSONString(object obj)序列化
* 只要实体中含有LocalDateTime的字段,在fastJson没有指定SerializeConfig序列化时间的方式时会默认的序列化为java的UTC时间类型。格式为:
* yyyy-MM-dd'T' HH:mm:ss,如果指定了SerializeConfig序列化LocalDateTime的方式时,fastJson默认支持的时间字段累类型是Date,所以指定
* LocalDateTime的序列化方式的时候,JSON.toJSONString(object obj)会执行失败,失败报错就是LocalDateTime不能直接转为Date类型,所以我们要重写fastJson的时间格式化解析器的源码
*/

/**
* @author zlf
* @description:
* @time: 2022/06/24
*/
public class SimpleDateFormatSerializer implements ObjectSerializer {

   private final String pattern;

   public SimpleDateFormatSerializer(String pattern) {
       this.pattern = pattern;
  }

   @Override
   public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features) throws IOException {
       if (object == null) {
           serializer.out.writeNull();
           return;
      }
       Date date = null;
       if (object instanceof LocalDateTime) {
           LocalDateTime localDateTime = (LocalDateTime) object;
           date = Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
      } else if (object instanceof LocalDate) {
           LocalDate localDate = (LocalDate) object;
           date = Date.from(localDate.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant());
      } else {
           date = (Date) object;
      }
       SimpleDateFormat format = new SimpleDateFormat(pattern, serializer.locale);
       format.setTimeZone(serializer.timeZone);

       String text = format.format(date);
       serializer.write(text);
  }

}

      这个类是用来解决fastJson时间类型转换的序列化解析器,fastJson默认是不支持LocalDateTime、LocalDate类型直接转换为Date类型的,所以这个是使用fastJson的一个大坑,这个算是fastJson的一个bug,本来想给fastJsong官方提一个bug的,后面想想算了,这个坑也是坑的,官方解决不了,那只能自己解决了,自己改它的源码在自己项目中修复这个bug了。

      在config包下新增FastJsonConfig(fastJson配置)、JacksonConfig(jackSon配置)、LocalDateTimeFormatConfig(spring的@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")实体注解格式解析配置)、FeignClientConfig(fegin的编解码配置,支持jackSon和fasJson根据需要配置支持,Spring默认的HttpMessageConverter解析的是jackSon解析,不支持fastJson解析,所以需要自己实现)、WebMvcAndJackson2OrFastJsonConfig(配置jackSon和fastJson的转换器加入到springMvc的List中,默认jackSon的顺序是在fastJson前面的)这几个配置类:

2.1 FastJsonConfig

package com.zlf.es.spring.boot.autoconfigure.config;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializeConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.serializer.SimpleDateFormatSerializer;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;

import java.nio.charset.Charset;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
* @author zlf
* @description:
* @time: 2022/06/24
*/
@Data
@Configuration
@RefreshScope
public class FastJsonConfig {

   /**
    * 可以配置为:NO,YES,ALL
    */
   @Value("${isUseFastJsonHMC:NO}")
   private String isUseFastJsonHMC;

   /**
    * jackson的消息解析器优先,同时也支持fastJson的消息解析的
    */
   public static final String ALL = "ALL";

   /**
    * fastJson消息转换器
    *
    * @return
    */
   @Bean
   @ConditionalOnClass(JSON.class)
   public FastJsonHttpMessageConverter fastJsonHttpMessageConverter() {
       FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
       //1.添加fastJson的配置信息;
       com.alibaba.fastjson.support.config.FastJsonConfig fastJsonConfig = new com.alibaba.fastjson.support.config.FastJsonConfig();
       fastJsonConfig.setCharset(Charset.forName("UTF-8"));
       fastJsonConfig.setSerializerFeatures(
               SerializerFeature.PrettyFormat,//PrettyFormat漂亮的json格式
               SerializerFeature.WriteNonStringKeyAsString, //不是String类型的key转换成String类型,否则前台无法将Json字符串转换成Json对象
               SerializerFeature.WriteMapNullValue,        // 是否输出值为null的字段,默认为false,我们将它打开
               SerializerFeature.WriteNullListAsEmpty,     // 将Collection类型字段的字段空值输出为[]
               SerializerFeature.WriteNullStringAsEmpty,   // 将字符串类型字段的空值输出为空字符串
               SerializerFeature.WriteNullNumberAsZero,    // 将数值类型字段的空值输出为0
               SerializerFeature.WriteDateUseDateFormat,
               SerializerFeature.DisableCircularReferenceDetect    // 禁用循环引用
      );
       // 缺点,指定后,将不会使用@JSONField注解上的format属性,包括并不限于Date类,LocalDateTime类,LocalDate类。(慎用)
       //文本date的格式化方式
       fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss");
       //2.序列化方式配置
       SerializeConfig serializeConfig = fastJsonConfig.getSerializeConfig();
       serializeConfig.put(LocalDateTime.class, new SimpleDateFormatSerializer("yyyy-MM-dd HH:mm:ss"));
       serializeConfig.put(Timestamp.class, new SimpleDateFormatSerializer("yyyy-MM-dd HH:mm:ss"));
       serializeConfig.put(LocalDate.class, new SimpleDateFormatSerializer("yyyy-MM-dd"));
       serializeConfig.put(Date.class, new SimpleDateFormatSerializer("yyyy-MM-dd"));
       //3处理中文乱码问题
       List<MediaType> fastMediaTypes = new ArrayList<>();
       fastMediaTypes.add(MediaType.APPLICATION_JSON);
       fastMediaTypes.add(MediaType.parseMediaType(MediaType.TEXT_PLAIN_VALUE + ";charset=ISO-8859-1"));

       //4.在convert中添加配置信息.
       fastJsonHttpMessageConverter.setSupportedMediaTypes(fastMediaTypes);
       fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig);

       return fastJsonHttpMessageConverter;
  }

}

2.2 JacksonConfig

package com.zlf.es.spring.boot.autoconfigure.config;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
import java.util.TimeZone;

/**
* @author zlf
* @description:
* @time: 2022/06/24
*/
@Configuration
public class JacksonConfig {

   /**
    * Date格式化字符串
    */
   private static final String DATE_FORMAT = "yyyy-MM-dd";
   /**
    * DateTime格式化字符串
    */
   private static final String DATETIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
   /**
    * Time格式化字符串
    */
   private static final String TIME_FORMAT = "HH:mm:ss";

   @Bean
   @ConditionalOnClass(ObjectMapper.class)
   public Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder() {
       return new Jackson2ObjectMapperBuilder();
  }

   /**
    * 自定义了一个Jackson2ObjectMapperBuilder
    *
    * @return
    */
   @Bean
   @ConditionalOnClass(Jackson2ObjectMapperBuilder.class)
   public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
       return builder -> {
           builder.serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DATETIME_FORMAT)));
           builder.deserializerByType(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DATETIME_FORMAT)));
           builder.serializerByType(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DATE_FORMAT)));
           builder.deserializerByType(LocalDate.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DATE_FORMAT)));
           builder.serializerByType(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(TIME_FORMAT)));
           builder.deserializerByType(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(TIME_FORMAT)));
           builder.locale(Locale.SIMPLIFIED_CHINESE);
           /**spring:
            *   jackson:
            *     time-zone: Asia/Shanghai
            *     date-format: yyyy-MM-dd HH:mm:ss
            * 一些常用配置可以配置
            */
           builder.simpleDateFormat(DATETIME_FORMAT);
           builder.timeZone(TimeZone.getTimeZone("Asia/Shanghai"));
      };
  }

   /**
    * 1.禁止springBoot的空对象序列化,否则springMvc返回给前端要被序列化为json的实体中含有空对象就会报错
    * 2.忽略为null的字段
    * 3.反序列化实体中缺少字段不报com.fasterxml.jackson.databind.exc.MismatchedInputException错
    * 4.通过Jackson2ObjectMapperBuilder构建一个单例的ObjectMapper
    * 自定义LocalDateTime类型的编解码
    *
    * @return
    */
   @Bean
   @ConditionalOnClass(Jackson2ObjectMapperBuilder.class)
   public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder) {
       //针对于Date类型,文本格式化
       //jackson2ObjectMapperBuilder.simpleDateFormat(PATTERN1);
       //针对于JDK新时间类。序列化时带有T的问题,自定义格式化字符串
       JavaTimeModule javaTimeModule = new JavaTimeModule();
       javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DATETIME_FORMAT)));
       javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DATETIME_FORMAT)));
       javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DATE_FORMAT)));
       javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DATE_FORMAT)));
       javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(TIME_FORMAT)));
       javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(TIME_FORMAT)));
       ObjectMapper objectMapper = jackson2ObjectMapperBuilder.build();
       objectMapper.registerModule(javaTimeModule);
       objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS).setSerializationInclusion(JsonInclude.Include.NON_NULL);
       objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
       return objectMapper;
  }

   /*
   jackson消息转换器(默认使用这个消息转换器)
    * 注意:
    * 在配置springBoot支付jakson序列化返回给前端的配置中要配置ObjectMapper的序列化方式的话需要如下这种配置:
    * 在有feigin集成调用的时候然后直接配置一个单例的ObjectMapper就会让fegin反序列化时间字段报错,
    * 原因是由于单例的ObjectMapper会让feigin的解码器和springDecode的jackson支持的解码器相互冲突影响。
    *
    * */
   @Bean
   @ConditionalOnClass(ObjectMapper.class)
   public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
       MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter(
               this.objectMapper(this.jackson2ObjectMapperBuilder()));
       /**设置mediaType支持可以不用设置.默认是这个设置的
        * public MappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {
        *         super(objectMapper, new MediaType[]{MediaType.APPLICATION_JSON, new MediaType("application", "*+json")});
        *     }
        */
      /* List mediaType = new ArrayList();
       mediaType.add(MediaType.APPLICATION_JSON);
       mappingJackson2HttpMessageConverter.setSupportedMediaTypes(mediaType);*/
       return mappingJackson2HttpMessageConverter;
  }

}

2.3 LocalDateTimeFormatConfig

package com.zlf.es.spring.boot.autoconfigure.config;

import com.sun.istack.Nullable;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.Formatter;
import org.springframework.util.StringUtils;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
import java.util.Objects;

/**
* @author zlf
* @description:
* @time: 2022/06/24
* 时间全局处理
* 可以使用@DateTimeFormat(pattern = "xxxx")标注在指定的字段上
*/
@Configuration
public class LocalDateTimeFormatConfig {

   @Bean
   public Formatter<LocalDate> localDateFormatter() {
       return new Formatter<LocalDate>() {
           @Override
           public @Nullable
           String print(@Nullable LocalDate object, @Nullable Locale locale) {
               if (Objects.isNull(object)) {
                   return null;
              }
               return object.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
          }

           @Override
           public @Nullable
           LocalDate parse(@Nullable String text, @Nullable Locale locale) {
               if (!StringUtils.hasText(text)) {
                   return null;
              }
               return LocalDate.parse(text, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
          }
      };
  }

   @Bean
   public Formatter<LocalDateTime> localDateTimeFormatter() {
       return new Formatter<LocalDateTime>() {
           @Override
           public @Nullable
           String print(@Nullable LocalDateTime object, @Nullable Locale locale) {
               if (Objects.isNull(object)) {
                   return null;
              }
               return object.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
          }

           @Override
           public @Nullable
           LocalDateTime parse(@Nullable String text, @Nullable Locale locale) {
               if (!StringUtils.hasText(text)) {
                   return null;
              }
               return LocalDateTime.parse(text, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
          }
      };
  }

   @Bean
   public Formatter<LocalTime> localTimeFormatter() {
       return new Formatter<LocalTime>() {
           @Override
           public @Nullable
           String print(@Nullable LocalTime object, @Nullable Locale locale) {
               if (Objects.isNull(object)) {
                   return null;
              }
               return object.format(DateTimeFormatter.ofPattern("HH:mm:ss"));
          }

           @Override
           public @Nullable
           LocalTime parse(@Nullable String text, @Nullable Locale locale) {
               if (!StringUtils.hasText(text)) {
                   return null;
              }
               return LocalTime.parse(text, DateTimeFormatter.ofPattern("HH:mm:ss"));
          }
      };
  }

}

2.4 FeignClientConfig

package com.zlf.es.spring.boot.autoconfigure.config;

import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import feign.codec.Decoder;
import feign.codec.Encoder;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.openfeign.support.SpringDecoder;
import org.springframework.cloud.openfeign.support.SpringEncoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

/**
* @author zlf
* @description:
* @time: 2022/06/24
* fegin的
*/
@Configuration
public class FeignClientConfig {

  /* @Autowired(required = false)
   private JacksonConfig jacksonConfig;

   @Autowired(required = false)
   private FastJsonConfig fastJsonConfig;*/

   @Bean
   public Encoder feignEncoder() {
       return new SpringEncoder(feignHttpMessageConverter());
  }

   @Bean
   public Decoder feignDecoder() {
       return new SpringDecoder(feignHttpMessageConverter());
  }

   private ObjectFactory<HttpMessageConverters> feignHttpMessageConverter() {
       JacksonConfig jacksonConfig = new JacksonConfig();
       FastJsonConfig fastJsonConfig = new FastJsonConfig();
       Collection<HttpMessageConverter> converters = new ArrayList<>();
       MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = jacksonConfig.mappingJackson2HttpMessageConverter();
       FastJsonHttpMessageConverter fsc = fastJsonConfig.fastJsonHttpMessageConverter();
       converters.add(mappingJackson2HttpMessageConverter);
       if (Boolean.valueOf(fastJsonConfig.getIsUseFastJsonHMC())) {
           converters.add(fsc);
           Iterator<HttpMessageConverter> iterator = converters.iterator();
           while (iterator.hasNext()) {
               HttpMessageConverter next = iterator.next();
               if (next instanceof MappingJackson2HttpMessageConverter) {
                   iterator.remove();
              }
          }
      } else if (String.valueOf(fastJsonConfig.getIsUseFastJsonHMC()).equals(FastJsonConfig.ALL)) {
           converters.add(fastJsonConfig.fastJsonHttpMessageConverter());
      }
       //设置中文编码格式
       List<MediaType> list = new ArrayList<MediaType>();
       list.add(MediaType.APPLICATION_JSON_UTF8);
       list.add(MediaType.APPLICATION_JSON);
       list.add(MediaType.TEXT_PLAIN);
       list.add(MediaType.ALL);
       mappingJackson2HttpMessageConverter.setSupportedMediaTypes(list);
       final HttpMessageConverters httpMessageConverters = new HttpMessageConverters(converters);
       return () -> httpMessageConverters;
  }

}

2.5 WebMvcAndJackson2OrFastJsonConfig

package com.zlf.es.spring.boot.autoconfigure.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.Iterator;
import java.util.List;

/**
* @author zlf
* @description:
* @time: 2022/06/24
* https://blog.csdn.net/lvyuanj/article/details/108554170
* 1.WebMvcConfigurerAdapter springBoot1.x
* springBoot2.x推荐使用WebMvcConfigurer
* 2.实现WebMvcConfigurer类不会让WebMvcAutoConfiguration自动装配置失效和静态资源访问失效
* 3.继承WebMvcConfigurationSupport会覆盖原有的WebMvcConfigurationSupport类,、
* 还会让WebMvcAutoConfiguration自动装配置失效和静态资源访问失效
*/
@Slf4j
@Configuration
public class WebMvcAndJackson2OrFastJsonConfig implements WebMvcConfigurer {

   /*@Autowired(required = false)
   private JacksonConfig jacksonConfig;

   @Autowired(required = false)
   private FastJsonConfig fastJsonConfig;*/

   @Override
   public void configureMessageConverters(List<HttpMessageConverter> converters) {
       JacksonConfig jacksonConfig = new JacksonConfig();
       FastJsonConfig fastJsonConfig = new FastJsonConfig();
       /**
        * 此处已经设置了MappingJackson2HttpMessageConverter jackson转换器,但是未设置任何转换格式,
        * 所以当需要反序列化String类型为Timestamp类型时,
        * 用的格式仍然Jackson默认的类型格式-yyyy-MM-dd'T'HH:mm:ss.SSSZ,转
        * @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
        * private LocalDateTime timestamp 报错
        * 导致反序列化失败,报错,而且此处设置便会覆盖配置文件的 jackson的配置项,配置一直不生效的原因就是这里
        **/
       converters.add(0, jacksonConfig.mappingJackson2HttpMessageConverter());
       /**
        * 如果使用fastJson消息转换器必须把默认的jackson的消息转换器移除,在把fastJson的转换器加入进入,
        * 还是可以自动匹配?
        */
       if (Boolean.valueOf(fastJsonConfig.getIsUseFastJsonHMC())) {
           Iterator<HttpMessageConverter> iterator = converters.iterator();
           while (iterator.hasNext()) {
               HttpMessageConverter next = iterator.next();
               if (next instanceof MappingJackson2HttpMessageConverter) {
                   iterator.remove();
              }
          }
           converters.add(0, fastJsonConfig.fastJsonHttpMessageConverter());
           log.info("==============配置fastJsonHttpMessageConverter成功!===============");
      } else if (String.valueOf(fastJsonConfig.getIsUseFastJsonHMC()).equals(FastJsonConfig.ALL)) {
           converters.add(1, fastJsonConfig.fastJsonHttpMessageConverter());
      }
  }

}

      在nacos的项目配置yml文件中需要加入如下的配置,开启fegin调用支持jackSon和fastJson的转化解析,且fastJson解析时间类型不会出现异常:

isUseFastJsonHMC: ALL

        这几个类可以单独拿出来放到其它项目中使用,加上相关的pom依赖即可,可以很好的解决应用间fegin调用fastJson数据解析时间类型转换bug


3.ES启动器和代码和应用间fegin调用fastJson数据解析时间类型转换bug修复代码

分享如下:

链接:https://pan.baidu.com/s/1PNcb8phCJp4IVPMsM9-WbA 
提取码:3bs3


请使用浏览器的分享功能分享到微信等