下面是一个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;
}
512

被折叠的 条评论
为什么被折叠?



