Spring Cloud OpenFeign:基于Ribbon和Hystrix的声明式服务调用

Spring Cloud OpenFeign:基于Ribbon和Hystrix的声明式服务调用

 

 

 

1、简介

官网:https://spring.io/projects/spring-cloud-openfeign

feign是一个声明式WebService 客户端,使用Feign能让编写Web Service客户端更加简单,我们只需创建一个接口并用注解的方式来配置它,就可以实现对某个服务接口的调用,简化了直接使用RestTemplate来调用服务接口的开发量。Feign具备可插拔的注解支持,同时支持Feign注解、JAX-RS注解及SpringMvc注解。当使用Feign时,Spring Cloud集成了Ribbon和Eureka以提供负载均衡的服务调用及基于Hystrix的服务容错保护功能。

他的使用方法是定义一个服务接口,然后在上面添加注解。Feign也支持可插拔式的编码和解码器。Spring  Cloud 对Feign进行了封装,使其支持Spring MVC 标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡。

 

 2、Feign能干什么?

Feign使编写java Http客户端更加容易

前面使用Ribbon + RestTemplate时,利用RestTemplate对Http请求的封装处理,形成了一套模板化的调用方法,但是在实际的开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多次调用。所以Feign在此基础上做了进一步的封装,由他来帮助我们定义和实现依赖服务接口。在Feign的实现下,我们只需要创建一个接口并使用注解的方式来配置它(以前是Dao接口上面标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解即可),即可完成对服务提供方的接口绑定,简化了使用Spring Cloud Ribbon,自动封装服务调用客户端的工作量。

Feign集成了Ribbon

  Ribbon维护了服务列表信息,并且通过轮询的方式实现客户端的负载均衡。而与Ribbon不同的是,通过Feign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务的调用。

Feign 与 OpenFeign的区别?

Feign

OpenFeign

Feign是Spring Cloud组件中的一个轻量级RestFul的Http服务客户端Feign内置了Ribbon,用来做客户端的负载均衡,去调用服务注册中心的服务。Feign的使用方式是:使用Feign的注解定义接口,调用这个接口就可以调用服务注册中心的服务。

OpenFeign是Spring Cloud在feign的基础上支持了SpringMvc的注解,如@RequestMapping等等

OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他的服务

<dependency>

    <groupId>org.springframework.cloud</ groupId >

    <artifactId>spring-cloud-starter-feign</ artifactId>

</ dependency >

 

<!--  openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId >
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

 

3、OpenFeign 的使用

3.1、在启动类上添加 @EnableFeignClients 注解来启用Feign的客户端功能。

@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class FeignServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(FeignServiceApplication.class, args);
    }
}

 

3.2、添加UserService接口完成对user-service服务的接口绑定

@FeignClient(value = "user-service")
public interface UserService {
    @PostMapping("/user/create")
    CommonResult create(@RequestBody User user);

    @GetMapping("/user/{id}")
    CommonResult<User> getUser(@PathVariable Long id);

    @GetMapping("/user/getByUsername")
    CommonResult<User> getByUsername(@RequestParam String username);

    @PostMapping("/user/update")
    CommonResult update(@RequestBody User user);

    @PostMapping("/user/delete/{id}")
    CommonResult delete(@PathVariable Long id);
}

 

3.3、修改yml,OpenFeign默认的等待时间1秒钟,超时后将报错:

server:
  port: 80

eureka:
  client:
    register-with-eureka: true
    service-url:
     defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka

spring:
  application:
    name: feign-customer-order

  # 设置feign客户端超时时间(openFeign默认支持Ribbon)
ribbon:
  # 建立连接后从服务器读取到可用资源的时间
  ReadTimeout: 5000
  # 建立连接所用的时间,适用于网络正常的情况下,两端连接所用的时间
  ConnectTimeout: 5000

 

4、Feign中的服务降级

Feign中的服务降级使用起来非常方便,只需要为Feign客户端定义的接口添加一个服务降级处理的实现类即可,下面我们为UserService接口添加一个服务降级实现类。

4.1、添加服务降级实现类UserFallbackService, 需要注意的是它实现了UserService接口,并且对接口中的每个实现方法进行了服务降级逻辑的实现。

public class UserFallbackService implements UserService {
    @Override
    public CommonResult create(User user) {
        User defaultUser = new User(-1L, "defaultUser", "123456");
        return new CommonResult<>(defaultUser);
    }

    @Override
    public CommonResult<User> getUser(Long id) {
        User defaultUser = new User(-1L, "defaultUser", "123456");
        return new CommonResult<>(defaultUser);
    }

    @Override
    public CommonResult<User> getByUsername(String username) {
        User defaultUser = new User(-1L, "defaultUser", "123456");
        return new CommonResult<>(defaultUser);
    }

    @Override
    public CommonResult update(User user) {
        return new CommonResult("调用失败,服务被降级",500);
    }

    @Override
    public CommonResult delete(Long id) {
        return new CommonResult("调用失败,服务被降级",500);
    }
}

4.2、修改UserService接口,设置服务降级处理类为UserFallbackService

修改@FeignClient注解中的参数,设置fallback为UserFallbackService.class即可。

@FeignClient(value = "user-service",  fallback = UserFallbackService.class)
public interface UserService {
.... }

4.3、修改application.yml,开启Hystrix功能

feign:
  hystrix:
    enabled: true #在Feign中开启Hystrix

 

5、日志打印功能

Feign提供了日志打印功能,我们可以通过配置来调整日志级别,从而了解Feign中Http请求的细节。

日志级别:

  • NONE:默认的,不显示任何日志;
  • BASIC:仅记录请求方法、URL、响应状态码及执行时间;
  • HEADERS:除了BASIC中定义的信息之外,还有请求和响应的头信息;
  • FULL:除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据。

我们通过java配置来使Feign打印最详细的Http请求日志信息。

@Configuration
public class FeignConfig {
    @Bean
    Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
}

在application.yml中配置需要开启日志的Feign客户端

logging:
  level:
    com.openapi.service: debug

 这里的com.openapi.service是openFeign接口所在的包名,当然你也可以配置一个特定的openFeign接口。

 

6、Feign的常用配置

feign:
  hystrix:
    enabled: true #在Feign中开启Hystrix
  compression:
    request:
      enabled: false #是否对请求进行GZIP压缩
      mime-types: text/xml,application/xml,application/json #指定压缩的请求数据类型
      min-request-size: 2048 #超过该大小的请求会被压缩
    response:
      enabled: false #是否对响应进行GZIP压缩
logging:
  level: #修改日志级别
    com.dw.cloud.service.UserService: debug

 如果openFeign没有设置对应得超时时间,那么将会采用Ribbon的默认超时时间。

理解了超时设置的原理,由之产生两种方案也是很明了了,如下:

  • 设置openFeign的超时时间
  • 设置Ribbon的超时时间(默认1s)

1、设置Ribbon的超时时间(不推荐)

设置很简单,在配置文件中添加如下设置:

ribbon:
  # 值的是建立链接所用的时间,适用于网络状况正常的情况下, 两端链接所用的时间
  ReadTimeout: 5000
  # 指的是建立链接后从服务器读取可用资源所用的时间
  ConectTimeout: 5000

2、设置openFeign的超时时间(推荐)

feign:
  client:
    config:
      ## default 设置的全局超时时间,指定服务名称可以设置单个服务的超时时间
      default:
        connectTimeout: 5000
        readTimeout: 5000

 

 

7、OpenFeign GET 请求使用 @SpringQueryMap 传递对象参数

4.1、SpringCloud在2.1.x版本中提供了 @SpringQueryMap 注解,可以传递对象参数。 但是传递的对象参数中只能包含基本类型,如果需要在传递的参数对象中, 包含其他的对象类型, 需要自定义

类实现 QueryMapEncode 接口,自定义参数解析。具体配置如下:

@Configuration
public class FeignConfiguration {
    /**
     * 使用@SpringQueryMap可以解决GET请求的时候,传递对象
     * 关于使用 @SpringQueryMap不能解析对象中的对象的问题
     * @return
     */
    @Bean
    public Feign.Builder feignBuilder() {
        return Feign.builder()
                .queryMapEncoder(new BeanQueryMapNestEncoder())
                .retryer(Retryer.NEVER_RETRY);
    }

    /**
     * 配置feign日志级别
     * @return
     */
    @Bean
    public Logger.Level feignLoggerLevel(){
        return Logger.Level.FULL;
    }
}

 

4.2、自定义 BeanQueryMapNestEncoder 传递复杂参数解析配置:

import feign.Param;
import feign.QueryMapEncoder;
import feign.codec.EncodeException;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanUtils;

public class BeanQueryMapNestEncoder implements QueryMapEncoder {
    private final Map<Class<?>, BeanQueryMapNestEncoder.ObjectParamMetadata> classToMetadata = new HashMap();

    public BeanQueryMapNestEncoder() {
    }

    public Map<String, Object> encode(Object object) {
        try {
            Map<String, Object> propertyNameToValue = new HashMap();
            this.putPropertiesToMap((String)null, object, propertyNameToValue);
            return propertyNameToValue;
        } catch (IntrospectionException | InvocationTargetException | IllegalAccessException var3) {
            throw new EncodeException("Failure encoding object into query map", var3);
        }
    }

    private void putPropertiesToMap(String parentProperty, Object object, Map<String, Object> propertyNameToValue) 
throws IntrospectionException, InvocationTargetException, IllegalAccessException { BeanQueryMapNestEncoder.ObjectParamMetadata metadata = this.getMetadata(object.getClass()); Iterator var5 = metadata.objectProperties.iterator(); while(true) { while(true) { PropertyDescriptor pd; Method method; Object value; StringBuffer nameBuffer; do { do { if (!var5.hasNext()) { return; } pd = (PropertyDescriptor)var5.next(); method = pd.getReadMethod(); value = method.invoke(object); nameBuffer = new StringBuffer(); if (StringUtils.isNotEmpty(parentProperty)) { nameBuffer.append(parentProperty).append('.'); } } while(value == null); } while(value == object); Param alias = (Param)method.getAnnotation(Param.class); nameBuffer.append(alias != null ? alias.value() : pd.getName()); if (BeanUtils.isSimpleProperty(value.getClass())) { propertyNameToValue.put(nameBuffer.toString(), value); } else if (!(value instanceof Collection)) { this.putPropertiesToMap(nameBuffer.toString(), value, propertyNameToValue); } else { StringBuffer arrValueBuffer = new StringBuffer(); Object arrObj; for(Iterator var12 = ((Collection)value).iterator(); var12.hasNext(); arrValueBuffer.append(arrObj)) { arrObj = var12.next(); if (!BeanUtils.isSimpleProperty(arrObj.getClass())) { break; } if (arrValueBuffer.length() != 0) { arrValueBuffer.append(','); } } if (arrValueBuffer.length() > 0) { propertyNameToValue.put(nameBuffer.toString(), arrValueBuffer.toString()); } } } } } private BeanQueryMapNestEncoder.ObjectParamMetadata getMetadata(Class<?> objectType) throws IntrospectionException { BeanQueryMapNestEncoder.ObjectParamMetadata metadata = (BeanQueryMapNestEncoder.ObjectParamMetadata)this.classToMetadata.get(objectType); if (metadata == null) { metadata = BeanQueryMapNestEncoder.ObjectParamMetadata.parseObjectType(objectType); this.classToMetadata.put(objectType, metadata); } return metadata; } private static class ObjectParamMetadata { private final List<PropertyDescriptor> objectProperties; private ObjectParamMetadata(List<PropertyDescriptor> objectProperties) { this.objectProperties = Collections.unmodifiableList(objectProperties); } private static BeanQueryMapNestEncoder.ObjectParamMetadata parseObjectType(Class<?> type) throws IntrospectionException { List<PropertyDescriptor> properties = new ArrayList(); PropertyDescriptor[] var2 = Introspector.getBeanInfo(type).getPropertyDescriptors(); int var3 = var2.length; for(int var4 = 0; var4 < var3; ++var4) { PropertyDescriptor pd = var2[var4]; boolean isGetterMethod = pd.getReadMethod() != null && !"class".equals(pd.getName()); if (isGetterMethod) { properties.add(pd); } } return new BeanQueryMapNestEncoder.ObjectParamMetadata(properties); } } }

 

8、Feign 异常处理

5.1、自定义Feign解析器:

import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.crecgec.baseboot.jsoncore.exception.BaseException;
import feign.Response;
import feign.Util;
import feign.codec.ErrorDecoder;
import org.springframework.context.annotation.Configuration;

import java.io.IOException;

@Configuration
public class FeignErrorDecoder implements ErrorDecoder {

    @Override
    public Exception decode(String methodKey, Response response) {
        try {
            // 这里直接拿到我们抛出的异常信息
            String message = Util.toString(response.body().asReader());
            try {
                JSONObject jsonObject = JSONObject.parseObject(message);
                return new BaseException(jsonObject.getString("resultMsg"), jsonObject.getInteger("resultCode"));
            } catch (JSONException e) {
                e.printStackTrace();
            }

        } catch (IOException ignored) {
        }
        return decode(methodKey, response);
    }
}

5.2、定义系统的异常类

public class BaseException extends RuntimeException {
    private int status ;
 
    public int getStatus() {
        return status;
    }
 
    public void setStatus(int status) {
        this.status = status;
    }
 
    public BaseException() {
    }
 
    public BaseException(String message, int status) {
        super(message);
        this.status = status;
    }
 
    public BaseException(String message) {
        super(message);
    }
 
    public BaseException(String message, Throwable cause) {
        super(message, cause);
    }
 
    public BaseException(Throwable cause) {
        super(cause);
    }
 
    public BaseException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}

5.3、统一异常拦截转换对应的异常信息返回前端

public class ResultSet {

    /**
     * 返回的状态码
     */
    private Integer resultCode;

    /**
     * 返回的消息
     */
    private String resultMsg;

    /**
     * 返回的数据
     */
    private Object data;

    public ResultSet() {
    }

    public ResultSet(Integer resultCode, String resultMsg) {
        this.resultCode = resultCode;
        this.resultMsg = resultMsg;
    }

    public ResultSet(Integer resultCode, String resultMsg, Object data) {
        this.resultCode = resultCode;
        this.resultMsg = resultMsg;
        this.data = data;
    }

    public Integer getResultCode() {
        return resultCode;
    }

    public void setResultCode(Integer resultCode) {
        this.resultCode = resultCode;
    }

    public String getResultMsg() {
        return resultMsg;
    }

    public void setResultMsg(String resultMsg) {
        this.resultMsg = resultMsg;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }
}

5.4、全局异常类处理配置:

@ExceptionHandler(value = BaseException.class)
public ResultSet defaultErrorHandler(HttpServletRequest req, HttpServletResponse resp, BaseException e) {
    ResultSet resultSet = new ResultSet();

    if (e.getStatus() == 400) {
        resultSet.setResultCode(-1);
        resultSet.setResultMsg(e.getMessage());
        resultSet.setData(null);
        resp.setStatus(400);
    } else {
        resp.setStatus(500);
        if(logger.isErrorEnabled()){
            logger.error("系统异常,请联系系统开发人员进行处理", e);
        }
        resultSet.setResultCode(-1);
        resultSet.setResultMsg(e.getMessage());
        resultSet.setData(null);
    }

    return resultSet;
}

 

9、使用OpenFeign 实现文件传输

参考 github上 的实现方式:https://github.com/OpenFeign/feign-form

6.1、引入依赖(如果引入的 OpenFeign 中没有以下依赖):

<dependencies>
  <dependency>
    <groupId>io.github.openfeign.form</groupId>
    <artifactId>feign-form</artifactId>
    <version>3.8.0</version>
  </dependency>
  <dependency>
    <groupId>io.github.openfeign.form</groupId>
    <artifactId>feign-form-spring</artifactId>
    <version>3.8.0</version>
  </dependency>
</dependencies>

 

6.2、Feign 调用端接口:

/**
 * @Author dw
 * @ClassName TestFileService
 * @Description
 * @Date 2021/9/20 10:11
 * @Version 1.0
 */
@FeignClient(value = "cloud-test-server")
public interface ITestFileService {

    @RequestMapping(value = "/file/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE )
    public Object fileUpload(@RequestPart("file")MultipartFile multipartFile, @SpringQueryMap MyUser myUser);
}

 

6.3、调用端的Controller

@RestController
@RequestMapping("file")
public class TestFileController {

    @Autowired
    private ITestFileService testFileService;

    @RequestMapping("upload")
    public Object fileUpload(@RequestPart("file") MultipartFile multipartFile){
        Role role = new Role("123", "管理員", 3);
        MyUser user = new MyUser("张三", 23, role);
        Object o = testFileService.fileUpload(multipartFile, user);
        return o;
    }
}

 

6.4、服务端Controller

@RestController
@RequestMapping("file")
public class TestController {
    
    @RequestMapping("upload")
    public Object fileUpload(@RequestPart("file") MultipartFile multipartFile, MyUser myUser){
        System.out.println(multipartFile);
        return myUser;
    }
}

OK...文件传输完成了

 

10、如何替换默认的httpclient?

Feign在默认情况下使用的是JDK原生的URLConnection发送HTTP请求,没有连接池,但是对每个地址会保持一个长连接,即利用HTTP的persistence connection。

在生产环境中,通常不使用默认的http client,通常有如下两种选择:

  • 使用ApacheHttpClient
  • 使用OkHttp

至于哪个更好,其实各有千秋,我比较倾向于ApacheHttpClient,毕竟老牌子了,稳定性不在话下。

那么如何替换掉呢?其实很简单,下面演示使用ApacheHttpClient替换。

1、添加ApacheHttpClient依赖

在openFeign接口服务的pom文件添加如下依赖:

<!--     使用Apache HttpClient替换Feign原生httpclient-->
    <dependency>
      <groupId>org.apache.httpcomponents</groupId>
      <artifactId>httpclient</artifactId>
    </dependency>
    
    <dependency>
      <groupId>io.github.openfeign</groupId>
      <artifactId>feign-httpclient</artifactId>
    </dependency>

为什么要添加上面的依赖呢?从源码中不难看出,请看org.springframework.cloud.openfeign.FeignAutoConfiguration.HttpClientFeignConfiguration这个类,代码如下:

 上述红色框中的生成条件,其中的@ConditionalOnClass(ApacheHttpClient.class),必须要有ApacheHttpClient这个类才会生效,并且feign.httpclient.enabled这个配置要设置为true

2、配置文件中开启

在配置文件中要配置开启,代码如下:

feign:
  client:
    httpclient:
      # 开启 Http Client
      enabled: true

3、如何验证已经替换成功?

其实很简单,在feign.SynchronousMethodHandler#executeAndDecode()这个方法中可以清楚的看出调用哪个client,如下图:

 上图中可以看到最终调用的是ApacheHttpClient

 

11、Feign请求及响应拦截器

1、feign请求拦截,处理head、param、body参数,附加解密定制化处理,也可以使用原生解码器;

package com.config;
 
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Maps;
import com.utils.EncryptAES;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import feign.Util;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
 
import javax.servlet.http.HttpServletRequest;
import java.util.*;
 
@Slf4j
@Configuration
public class FeignClientConfig {
    @Bean
    public RequestInterceptor headerInterceptor() {
        //重写请求拦截器
        return new RequestInterceptor() {
            @Override
            public void apply(RequestTemplate requestTemplate) {
                //设置单独处理feign请求
                requestTemplate.header("feign-aes", "true");
                
                ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
                if (attributes == null) {
                    return;
                }
                
                //处理head
                HttpServletRequest request = attributes.getRequest();
                Enumeration<String> headerNames = request.getHeaderNames();
                if (null != headerNames) {
                    while (headerNames.hasMoreElements()) {
                        String name = headerNames.nextElement();
                        String values = request.getHeader(name);
                        requestTemplate.header(name, values);
                    }
                }
 
                //处理RequestParam
                Map<String, Collection<String>> queries = requestTemplate.queries();
 
                if (null != queries) {
                    Map<String, Collection<String>> newQueries = new HashMap<>();
                    for (Map.Entry<String, Collection<String>> entry : queries.entrySet()) {
                        Collection<String> value = new ArrayList<>();
                        Collection<String> pram = entry.getValue();
                        value.add(EncryptAES.encryptAES((List)pram.get(0)));
                        newQueries.put(entry.getKey(), value);
                    }
                    requestTemplate.queries(null);// 替换原有对象 原对象设为空,否则会追加
                    requestTemplate.queries(newQueries);// 替换原有对象
                }
 
                //处理请求body
                String method = requestTemplate.method();
                if (null != requestTemplate.body()) {
                    int length = requestTemplate.body().length;
                    if (0 < length) {
                        String body = requestTemplate.requestBody().asString();
                        if (StringUtils.isNotBlank(body)) {
                            String decryptStr = EncryptAES.encryptAES(body);
                            Map<String, Object> map = Maps.newHashMap();
                            map.put("encryData", decryptStr);
                            String jsonString = JSON.toJSONString(map);
                            byte[] bodyData = jsonString.getBytes(Util.UTF_8);// 替换请求体
                            requestTemplate.body(bodyData, Util.UTF_8);
                        }
                    }
                }
                log.info("FeignClient request headers:{}.", requestTemplate.headers());
 
            }
        };
    }
 
}

 

2、feign响应拦截,处理head、param、body参数,附加解密定制化处理,也可以使用原生解码器;

package com.config;
 
import com.alibaba.fastjson.JSON;
import com.utils.EncryptAES;
import feign.FeignException;
import feign.Response;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.openfeign.support.SpringDecoder;
 
import java.io.IOException;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
 
 
public class FeignClientResponseInterceptor extends SpringDecoder {
 
    public FeignClientResponseInterceptor(ObjectFactory<HttpMessageConverters> messageConverters) {
        super(messageConverters);
    }
 
    @Override
    public Object decode(final Response response, Type type) throws IOException, FeignException {
        Map<String, Collection<String>> requestHeadMap = response.request().headers();
        //设置单独处理feign请求
        List<String> strings = (List<String>) requestHeadMap.get("feign-aes");
        String header = strings.get(0);
        Response.Body body = response.body();
        String resultStr = "";
        if ("true".equals(header)) {
            String bodyString = IOUtils.toString(body.asReader(StandardCharsets.UTF_8));
            HashMap<String, String> hashMap = JSON.parseObject(bodyString, HashMap.class);
            String value = hashMap.get("value");
            String decryptAES = EncryptAES.decryptAES(value);
            resultStr = decryptAES;
        }
        Object o = super.decode(response.toBuilder().body(resultStr, StandardCharsets.UTF_8).build(), type);
        return o;
    }
 
}

 

3、附件加解密工具(支持格式化\r\n、\r\t)等格式化代码加解密

package com.utils;
 
import lombok.extern.slf4j.Slf4j;
 
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.util.Base64;
 
@Slf4j
public class EncryptAES {
    private static final String ENCODING = "UTF-8";
    private static final String PASSWORD = "PASSWORD_KEY"; //加密秘钥
 
 
    /**
     * 对字符串加密
     */
    public static String encryptAES(String content) {
        byte[] encryptResult = encrypt(content);
        String encryptResultStr = parseByte2HexStr(encryptResult);
        // BASE64位加密
        encryptResultStr = strEncrypt(encryptResultStr);
        return encryptResultStr;
    }
 
 
    /**
     * 对字符串解密
     */
    public static String decryptAES(String content) {
        // BASE64位解密
        try {
            String decrypt = strDecrypt(content);
            byte[] decryptFrom = parseHexStr2Byte(decrypt);
            byte[] decryptResult = decrypt(decryptFrom);
            return new String(decryptResult);
        } catch (Exception e) {
            log.error("解密异常", e);
            return null;
        }
    }
 
 
    /**
     * 加密
     */
    private static byte[] encrypt(String content) {
        try {
            KeyGenerator kgen = KeyGenerator.getInstance("AES");
            // 注意这句是关键,防止linux下 随机生成key。用其他方式在Windows上正常,但Linux上会有问题
            SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
            secureRandom.setSeed(PASSWORD.getBytes());
            kgen.init(128, secureRandom);
            SecretKey secretKey = kgen.generateKey();
            byte[] enCodeFormat = secretKey.getEncoded();
            SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");
            Cipher cipher = Cipher.getInstance("AES");// 创建密码器
            byte[] byteContent = content.getBytes("utf-8");
            cipher.init(Cipher.ENCRYPT_MODE, key);// 初始化
            byte[] result = cipher.doFinal(byteContent);
            return result; // 加密
        } catch (Exception e) {
            log.error("加密异常", e);
        }
        return null;
    }
 
    /**
     * 解密
     */
    private static byte[] decrypt(byte[] content) {
        try {
            KeyGenerator kgen = KeyGenerator.getInstance("AES");
            // 防止linux下 随机生成key
            SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
            secureRandom.setSeed(PASSWORD.getBytes());
            kgen.init(128, secureRandom);
            SecretKey secretKey = kgen.generateKey();
            byte[] enCodeFormat = secretKey.getEncoded();
            SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");
            Cipher cipher = Cipher.getInstance("AES");// 创建密码器
            cipher.init(Cipher.DECRYPT_MODE, key);// 初始化
            byte[] result = cipher.doFinal(content);
            return result; // 解密
        } catch (Exception e) {
            log.error("解密异常", e);
        }
        return null;
    }
 
 
    /**
     * 加密字符串
     */
    private static String strEncrypt(String str) {
        String result = str;
        if (str != null && str.length() > 0) {
            try {
                byte[] encodeByte = str.getBytes(ENCODING);
                result = new String(Base64.getEncoder().encode(encodeByte));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        // base64加密超过一定长度会自动换行 需要去除换行符
        return result.replaceAll("\r\n", "").replaceAll("\r", "").replaceAll("\n", "");
    }
 
    /**
     * 解密字符串
     */
    private static String strDecrypt(String str) {
        try {
            byte[] encodeByte = Base64.getDecoder().decode(str.getBytes());
            return new String(encodeByte);
        } catch (Exception e) {
            log.error("IO 异常", e);
            return str;
        }
    }
 
 
    /**
     * 将二进制转换成16进制
     */
    private static String parseByte2HexStr(byte buf[]) {
        StringBuffer sb = new StringBuffer();
 
        for (int i = 0; i < buf.length; i++) {
            String hex = Integer.toHexString(buf[i] & 0xFF);
            if (hex.length() == 1) {
                hex = '0' + hex;
            }
            sb.append(hex.toUpperCase());
        }
 
        return sb.toString();
    }
 
    /**
     * 将16进制转换为二进制
     */
    private static byte[] parseHexStr2Byte(String hexStr) {
        if (hexStr.length() < 1) {
            return null;
        }
        byte[] result = new byte[hexStr.length() / 2];
 
        for (int i = 0; i < hexStr.length() / 2; i++) {
            int high = Integer.parseInt(hexStr.substring(i * 2, i * 2 + 1), 16);
            int low = Integer.parseInt(hexStr.substring(i * 2 + 1, i * 2 + 2), 16);
            result[i] = (byte) (high * 16 + low);
        }
        return result;
    }
 
}

 

解决Feign 的RequestInterceptor 中获取的RequestAttributes为空的问题

原因:Hystrix 的线程池隔离策略的原因。Feign在新的线程中执行,导致获取不到,解决方案:自定义线程隔离策略。

/**
 * @Author dw
 * @Description 自定义隔离策略 解决RequestContextHolder.getRequestAttributes()为null的问题
 * @Date 2024/5/20 16:40
 * @Version 1.0
 */
@Slf4j
@ConditionalOnProperty(prefix = "feign", name = "hystrix.enabled", havingValue = "true")
@Component
public class RequestAttributeHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy {

    private HystrixConcurrencyStrategy delegate;

    /**
     * 初始化当前自定义的 HystrixConcurrencyStrategy
     */
    public RequestAttributeHystrixConcurrencyStrategy() {
        try {
            this.delegate = HystrixPlugins.getInstance().getConcurrencyStrategy();
            if (this.delegate instanceof RequestAttributeHystrixConcurrencyStrategy) {
                // 防止重复初始化
                return;
            }
            HystrixCommandExecutionHook commandExecutionHook = HystrixPlugins.getInstance().getCommandExecutionHook();
            HystrixEventNotifier eventNotifier = HystrixPlugins.getInstance().getEventNotifier();
            HystrixMetricsPublisher metricsPublisher = HystrixPlugins.getInstance().getMetricsPublisher();
            HystrixPropertiesStrategy propertiesStrategy = HystrixPlugins.getInstance().getPropertiesStrategy();
            HystrixPlugins.reset();
            HystrixPlugins.getInstance().registerConcurrencyStrategy(this);
            HystrixPlugins.getInstance().registerCommandExecutionHook(commandExecutionHook);
            HystrixPlugins.getInstance().registerEventNotifier(eventNotifier);
            HystrixPlugins.getInstance().registerMetricsPublisher(metricsPublisher);
            HystrixPlugins.getInstance().registerPropertiesStrategy(propertiesStrategy);
        } catch (Exception e) {
            log.error("Failed to register Sleuth Hystrix Concurrency Strategy", e);
        }
    }

    @Override
    public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey,
                                            HystrixProperty<Integer> corePoolSize,
                                            HystrixProperty<Integer> maximumPoolSize,
                                            HystrixProperty<Integer> keepAliveTime, TimeUnit unit,
                                            BlockingQueue<Runnable> workQueue) {
        return this.delegate.getThreadPool(threadPoolKey, corePoolSize, maximumPoolSize,
                keepAliveTime, unit, workQueue);
    }

    @Override
    public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey,
                                            HystrixThreadPoolProperties threadPoolProperties) {
        return this.delegate.getThreadPool(threadPoolKey, threadPoolProperties);
    }

    @Override
    public BlockingQueue<Runnable> getBlockingQueue(int maxQueueSize) {
        return this.delegate.getBlockingQueue(maxQueueSize);
    }

    @Override
    public <T> HystrixRequestVariable<T> getRequestVariable(HystrixRequestVariableLifecycle<T> rv) {
        return this.delegate.getRequestVariable(rv);
    }

    @Override
    public <T> Callable<T> wrapCallable(Callable<T> callable) {
        return new WrappedCallable<>(callable, RequestContextHolder.getRequestAttributes());
    }

    static class WrappedCallable<T> implements Callable<T> {

        private final Callable<T> target;
        private final RequestAttributes requestAttributes;

        public WrappedCallable(Callable<T> target, RequestAttributes requestAttributes) {
            this.target = target;
            this.requestAttributes = requestAttributes;
        }

        @Override
        public T call() throws Exception {
            try {
                RequestContextHolder.setRequestAttributes(requestAttributes, true);
                return target.call();
            } finally {
                RequestContextHolder.resetRequestAttributes();
            }
        }
    }
}

 

posted @ 2020-04-05 11:46  邓维-java  阅读(684)  评论(0编辑  收藏  举报