下面是一个feign的配置类,它配置了feign的自定义解析器与编码器,和feign调用前的拦截器(主要是调用前带上请求头信息token). import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.type.TypeFactory; import com.macro.mall.common.api.CommonResult; import com.macro.mall.common.exception.ApiException; import com.qianda.mall.common.constant.Constants; import feign.Logger; import feign.RequestInterceptor; import feign.RequestTemplate; import feign.Response; import feign.codec.DecodeException; import feign.codec.Decoder; import feign.codec.Encoder; import feign.form.spring.SpringFormEncoder; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.http.HttpMessageConverters; import org.springframework.cloud.openfeign.support.SpringEncoder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Scope; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import java.io.BufferedReader; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.nio.charset.Charset; import java.util.*; import java.util.function.Predicate; import java.util.stream.Collectors; /** * @author * @version 1.0 * @since 2020/11/5 */ @Configuration @RestControllerAdvice() public class FeignConfig { @Autowired private ObjectMapper objectMapper; /** * 通用feign响应值解析器 */ @Bean public Decoder feignCommonDecoder() { return (response, type) -> { Response.Body body = response.body(); try (BufferedReader bufferedReader = new BufferedReader(body.asReader(Charset.forName("utf-8")));) { String bodyStr = bufferedReader.lines().collect(Collectors.joining()); if (isDirectParse.test(type)) { return objectMapper.readValue(bodyStr, TypeFactory.defaultInstance().constructType(type)); } Type real; if (type instanceof ParameterizedType) { real = ((ParameterizedType) type).getRawType(); } else { real = type; } JsonNode jsonNode = objectMapper.readTree(bodyStr); int code = jsonNode.get(Constants.RESULT_CODE).intValue(); if (code != 200) { throw new ApiException(jsonNode.get(Constants.RESULT_MSG).textValue()); } String data = jsonNode.get(Constants.RESULT_DATA) == null ? "" : jsonNode.get(Constants.RESULT_DATA).toString(); if (StringUtils.isBlank(data)) { if (real == Optional.class) { return Optional.empty(); } else { return null; } } else { if (real == Optional.class) { return Optional.of(objectMapper.readValue(data, TypeFactory.defaultInstance().constructType(((ParameterizedType) type).getActualTypeArguments()[0]))); } else { return objectMapper.readValue(data, TypeFactory.defaultInstance().constructType(type)); } } } }; } @Bean public Encoder feignFormEncoder(ObjectFactory<HttpMessageConverters> messageConverters) { return new SpringFormEncoder(new SpringEncoder(messageConverters)); } /** * 可以带上请求头,一般都把token转到下个服务请求中 * @return */ @Bean RequestInterceptor requestInterceptor() { return requestTemplate -> { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder .getRequestAttributes(); if (attributes != null) { HttpServletRequest request = attributes.getRequest(); Enumeration<String> headerNames = request.getHeaderNames(); if (headerNames != null) { while (headerNames.hasMoreElements()) { String name = headerNames.nextElement(); if (exclude_headers.contains(name.toLowerCase())) { continue; } String values = request.getHeader(name); requestTemplate.header(name, values); } } } }; } private static final List<String> exclude_headers = Arrays.asList("content-type","content-length"); private static Predicate<Type> isDirectParse = type -> type == CommonResult.class || (type instanceof ParameterizedType && ((ParameterizedType) type).getRawType() == CommonResult.class); /** * Feign 客户端的日志记录,默认级别为NONE * Logger.Level 的具体级别如下: * NONE:不记录任何信息 * BASIC:仅记录请求方法、URL以及响应状态码和执行时间 * HEADERS:除了记录 BASIC级别的信息外,还会记录请求和响应的头信息 * FULL:记录所有请求与响应的明细,包括头信息、请求体、元数据 */ @Bean Logger.Level feignLoggerLevel() { return Logger.Level.FULL; } /** * Feign支持文件上传 * @param messageConverters * @return */ @Bean @Primary @Scope("prototype") public Encoder multipartFormEncoder(ObjectFactory<HttpMessageConverters> messageConverters) { return new SpringFormEncoder(new SpringEncoder(messageConverters)); } @ExceptionHandler(value = DecodeException.class) public CommonResult handleValidException(DecodeException e) { Throwable cause = e.getCause(); if (cause != null && cause instanceof ApiException) { return CommonResult.failed(e.getMessage()); } e.printStackTrace(); return CommonResult.success("系统异常"); } }
下面是一个自定义参数校验(3步):
1:可以定义校验组:分为create,update之类的
import javax.validation.groups.Default; /** * @author * @version 1.0 * @since 2020/11/2 */ public interface Create extends Default { }
2:在参数限定时带上组标识
实体类上如:
@ApiModelProperty(value = "车牌号") @NotNull(message = INVALID,groups = Create.class) @Length(max = 20, message = "车牌号长度不得超过20") private String plateNum; @ApiModelProperty(value = "载重量") @NotNull(message = INVALID,groups = Create.class) private BigDecimal carryWeight;
3:在controller的方法中使用, 这里@Validated(Create.class)表明只验证groups中带有Create.class的和没有groups标识的。
@ApiOperation("新增信息") @PostMapping public CommonResult post(@RequestBody @Validated(Create.class) BannerManageVo banner){ return iBannerService.saveOrUpdate(banner.toBanner())?CommonResult.success(banner):CommonResult.failed(); }
全局异常处理一般为:
/** * 全局异常处理 * Created by macro on 2020/2/27. */ @RestControllerAdvice public class GlobalRestExceptionHandler { @ExceptionHandler(value = ApiException.class) public CommonResult handle(ApiException e) { if (e.getErrorCode() != null) { return CommonResult.failed(e.getErrorCode()); } return CommonResult.failed(e.getMessage()); }
springboot中参数映射,
SpringBoot2.x会自动装载MappingJackson2HttpMessageConverter
进行消息转换。而MappingJackson2HttpMessageConverter
会获取Spring容器中的ObjectMapper
配置,来进行Jackson的序列化和反序列化。ObjectMapper
是JSON操作的核心,Jackson
的JSON操作都是在ObjectMapper
中实现的。
import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.deser.ContextualDeserializer; import com.fasterxml.jackson.databind.deser.std.EnumDeserializer; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import com.fasterxml.jackson.databind.util.EnumResolver; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; import com.qianda.mall.common.config.converter.BaseEnumConverterFactory; import com.qianda.mall.common.enums.BaseEnum; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import org.springframework.util.StringUtils; import java.io.IOException; import java.math.BigDecimal; import java.time.LocalDateTime; import static com.fasterxml.jackson.databind.DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS; import static com.macro.mall.common.util.DateFormatters.local_datetime_formatter; /** * @author heqiwen 这里处理枚举的逻辑是: * @version 1.0 * @since 2020/11/4 */ @Configuration public class JacksonConfig { @Bean @ConditionalOnMissingBean(ObjectMapper.class) public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) { ObjectMapper objectMapper = builder.createXmlMapper(false).build(); // 全局配置序列化返回 JSON 处理 SimpleModule simpleModule = new SimpleModule(); //JSON Long ==> String simpleModule.addSerializer(Long.class, ToStringSerializer.instance); simpleModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(local_datetime_formatter)); simpleModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(local_datetime_formatter)); //序列化枚举, 因为每个枚举都继承了BaseEnum。 simpleModule.addSerializer(BaseEnum.class, new JsonSerializer<BaseEnum>() { @Override public void serialize(BaseEnum value, JsonGenerator gen, SerializerProvider serializers) throws IOException { gen.writeObject(value.getValue()); } }); simpleModule.addSerializer(BigDecimal.class, ToStringSerializer.instance); /* * TODO 类型条件限定过宽且仅做了名称处理 ,这里处理枚举的反序列化。 * */ simpleModule.addDeserializer(Enum.class, new CommonDeserializer()); objectMapper.registerModule(simpleModule); return objectMapper; } public static class CommonDeserializer extends JsonDeserializer implements ContextualDeserializer { private final Class<BaseEnum> aClass; private static BaseEnumConverterFactory baseEnumConverterFactory = BaseEnumConverterFactory.INSTANCE; public CommonDeserializer(Class<BaseEnum> aClass) { this.aClass = aClass; } public CommonDeserializer() { aClass = null; } @Override public BaseEnum deserialize(JsonParser jsonParser, DeserializationContext ctx) throws IOException { if(StringUtils.isEmpty(jsonParser.getText())){ return null; } return baseEnumConverterFactory.getConverter(aClass).convert(jsonParser.getText()); } public JsonDeserializer createContextual(DeserializationContext ctx, BeanProperty property) throws JsonMappingException { Class rawCls = ctx.getContextualType().getRawClass(); if(BaseEnum.class.isAssignableFrom(rawCls)){ return new CommonDeserializer(rawCls); } return new EnumDeserializer(EnumResolver.constructFor((Class<Enum<?>>) rawCls, ctx.getConfig().getAnnotationIntrospector()),true); } } }
restTemplate发送请求
1.post请求需要用LinkedMultiValueMap来传递参数
String url = "https://xx.xx.com/app/order/getNumber";
MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<String, Object>();
paramMap.add("name", "name");
HttpHeaders headers = new HttpHeaders();
//headers可以详细设置请求头中的信息等
//HttpEntity里面包含了请求方和相应方的请求头和请求体,类似于@RequestBody和@ResponseBody
HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<MultiValueMap<String, Object>>(paramMap,headers);
ResponseEntity<String> response = template.exchange(url, HttpMethod.POST, httpEntity, String.class)
2.Get请求
get请求的话可以直接在拼接请求路径后直接拼接参数/app/order/getNumber?name=name,也可以使用占位符/app/order/getNumber?name={name},通过Map来传参,但不能使用LinkedMultiValueMap,否则会报错
String url = "https://xx.xx.com/app/order/getNumber?name={name}";
Map<String, Object> paramMap = new HashMap<String, Object>();
paramMap.add("name", "name");
HttpHeaders headers = new HttpHeaders();
HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<MultiValueMap<String, Object>>(null,headers);
//使用另一个重载的方法传递参数集合paramMap
ResponseEntity<String> response = template.exchange(url, HttpMethod.GET, httpEntity, String.class,paramMap);
redis序列化设置:
@Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class); ObjectMapper objectMapper = new ObjectMapper(); objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); //必须设置,否则无法将JSON转化为对象,会转化成Map类型 objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL); serializer.setObjectMapper(objectMapper);//得到json序列化 RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(redisConnectionFactory); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(serializer); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); redisTemplate.setHashValueSerializer(serializer); redisTemplate.afterPropertiesSet(); return redisTemplate; }