¡Otra forma de usar OpenFeign está desbloqueada!

Introducción

Hola a todos, soy Anyin.

En lo que respecta a OpenFeign - artículo de uso , compartí con ustedes OpenFeignalgunos métodos de procesamiento y uso en ciertos escenarios, y hoy Anyin desbloqueó OpenFeignotro escenario de uso, que solo se puede decir que es realmente fragante.

En nuestro desarrollo diario, creo que todos han entrado en contacto con sistemas de terceros. El trabajo más molesto de conectarse con sistemas de terceros puede ser una serie de operaciones como la autenticación, el cifrado, la verificación de firmas, la serialización directa e inversa de JSON, etc. al comienzo de la conexión.

Sabemos que OpenFeignen realidad es un cliente http, y el escenario principal de la aplicación es realizar llamadas mutuas entre microservicios en el sistema de microservicios; entonces, ¿también puede implementar llamadas de terceros?

¡Obviamente puede! ! !

análisis de la demanda

Antes de verificar nuestro punto de vista: antes de que OpenFeignpodamos implementar una llamada de sistema de terceros , busquemos un protocolo de sistema de terceros abierto para un análisis de demanda simple.

Aquí usamos el documento de protocolo del Consejo de Electricidad de China (Estándar Conjunto de Empresas de Energía Eléctrica de China) como ejemplo. La dirección de descarga se adjunta aquí, los estudiantes que la necesiten pueden recogerla por sí mismos.

Norma conjunta para empresas chinas de energía eléctrica

Los siguientes son los requisitos para la clave en el documento de protocolo.

imagen.png

Al mirar el documento de protocolo, sabemos que todo el proceso de acoplamiento estará diseñado para cumplir con los siguientes requisitos:

  1. El método de llamada utiliza el método POST de manera uniforme
  2. El formato de transferencia usa JSON
  3. Los datos comerciales deben cifrarse durante la transmisión
  4. Durante el proceso de transmisión, todo el paquete de datos debe generar una firma, ya que el servidor verificará la firma para asegurarse de que los datos no hayan sido alterados.
  5. ServiceAl hacer llamadas de terceros, debe ser tan fluido como llamar a otros locales (comportamiento consistente)

realización de negocios

Para OpenFeignlograr los requisitos anteriores, primero definimos una clase de configuración para personalizar la clase de configuración del cliente.

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(CECOperatorProperties.class)
public class CECFeignClientConfig implements RequestInterceptor {
    @Autowired
    private CECOperatorProperties properties;
    @Override
    public void apply(RequestTemplate requestTemplate) {}    
}
复制代码
  1. Implemente la interfaz, aquí debe colocar la información del token correspondiente en el encabezado del encabezado a través del interceptor después de obtener la RequestInterceptorautenticaciónaccess_token
  2. 注入CECOperatorProperties属性,对于加解密、验签等操作需要的一些秘钥信息,从配置中心获取后,注入该属性类中
  3. @Configuration(proxyBeanMethods = false) 配置该类配置类,并且不会在RootApplicationContext当中注册,只会在使用的时候才会进行相关配置。

这里注意哈,在这个类配置的@Bean实例,只有在当前的FeignClient实例的ApplicaitonContext当中可以访问到,其他地方访问不到。具体可以看

关于OpenFeign那点事儿 - 源码篇

接着,我们需要2个基本的数据传输对象:RequestResponse

@Data
public class CECRequest<T> {
    @JsonProperty("OperatorID")
    private String operatorID;
    @JsonProperty("Data")
    private T data;
    @JsonProperty("TimeStamp")
    private String timeStamp;
    @JsonProperty("Seq")
    private String seq;
    @JsonProperty("Sig")
    private String sig;
}
@Data
public class CECResponse<T> {
    private Integer Ret;
    private T Data;
    private String Msg;
}
复制代码

这里使用@JsonProperty的原因是协议文档字段的首字母都是大写的,而我们一般的Java字段都是驼峰,为了在进行JSON转换的时候避免无法正常转换。

然后,我们开始自定义编解码器。这里不得不推荐下Hutool 这个类库,是真的强大,因为涉及到的加解密和签名生成,都是现成的。真香!!!

编码器

@Slf4j
public class CECEncoder extends SpringEncoder {
    private final CECOperatorProperties properties;
    private final HMac mac;
    private final AES aes;
    public CECEncoder(ObjectFactory<HttpMessageConverters> messageConverters,
                      CECOperatorProperties properties) {
        super(messageConverters);
        this.properties = properties;
        this.mac = new HMac(HmacAlgorithm.HmacMD5,
                properties.getSigSecret().getBytes(StandardCharsets.UTF_8));
        this.aes = new AES(Mode.CBC, Padding.PKCS5Padding,
                properties.getDataSecret().getBytes(),
                properties.getDataIv().getBytes());
    }

    @Override
    public void encode(Object requestBody, Type bodyType, RequestTemplate request) throws EncodeException {
        // 数据加密
        String data = this.getEncrypt(requestBody);
        CECRequest<String> req = new CECRequest<>();
        req.setData(data);
        req.setSeq("0001");
        req.setTimeStamp(DateUtil.formatDate(DateUtil.now(), DateEnum.YYYYMMDDHHMMSS));
        req.setOperatorID(properties.getOperatorID());
        // 签名计算
        String sig = this.getSig(req);
        req.setSig(sig.toUpperCase());
        super.encode(req, CECRequest.class.getGenericSuperclass(), request);
    }
    private String getEncrypt(Object requestBody){
        String json = JsonUtil.toJson(requestBody);
        return Base64.encode(aes.encrypt(json.getBytes()));
    }
    private String getSig(CECRequest<String> req){
        String str = req.getOperatorID() + req.getData() + req.getTimeStamp() + req.getSeq();
        return mac.digestHex(str);
    }
}
复制代码

可以看到,我们的编码器其实是继承了SpringEncoder,因为在最终编码之后,还是需要转换为JSON发送给服务端,所以在继承SpringEncoder之后,构造器还需要注入ObjectFactory<HttpMessageConverters>的实例。另外,在构造器我们也初始化了HMacAES两个实例,一个为了生成签名,一个为了加密业务数据。

encode方法,我们把传递进来的requestBody包装了下,先对其进行加密,然后放在CECRequest实例的data字段内,并且生成对应的签名,最终请求服务端的时候是一个CECRequest实例的JSON化的结果。

Algunas personas pueden preguntarse, ¿por qué requestBodylos datos comerciales están directamente aquí, no la CECRequest<T>instancia? Considere nuestro quinto requisito: hacer llamadas de terceros debe ser tan Servicefluido (comportamiento consistente) como otras llamadas locales . Para lograr este requisito, no expondremos parámetros no comerciales a las llamadas comerciales, sino que los procesaremos en el proceso de codificación y decodificación.

descifrador

@Slf4j
public class CECDecoder extends SpringDecoder {
    private final AES aes;
    public CECDecoder(ObjectFactory<HttpMessageConverters> messageConverters,
                      CECOperatorProperties properties) {
        super(messageConverters);
        this.aes = new AES(Mode.CBC, Padding.PKCS5Padding,
                properties.getDataSecret().getBytes(),
                properties.getDataIv().getBytes());
    }
    @Override
    public Object decode(Response response, Type type) throws IOException, FeignException {
        CECResponse<String> resp = this.getCECResponse(response);
        // TODO 应该做对应的异常判断然后抛出异常
        String json = this.aes.decryptStr(resp.getData());
        Response newResp = response.toBuilder().body(json, StandardCharsets.UTF_8).build();
        return super.decode(newResp, type);
    }
    private CECResponse<String> getCECResponse(Response response) throws IOException{
        try (InputStream inputStream = response.body().asInputStream()) {
            String json = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
            TypeReference<CECResponse<String>> reference = new TypeReference<CECResponse<String>>() {};
            return JSONUtil.toBean(json, reference.getType(), true);
        }
    }
}
复制代码

El decodificador será relativamente simple y solo necesitará descifrar los datos. Así Responseque obtenemos la cadena JSON correspondiente, luego obtenemos la CECResponseinstancia a través de la deserialización, luego hacemos el juicio de excepción correspondiente (mi código no está implementado aquí) y luego decodificamos los datos para obtener los datos comerciales reales La cadena JSON, y finalmente reconstruimos una nueva instancia a través OpenFeigndel método proporcionado para su posterior procesamiento.toBuilderResponseSpringDecoder

A continuación, registramos el códec en la clase de configuración. La información completa de la clase de configuración es la siguiente

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(CECOperatorProperties.class)
public class CECFeignClientConfig implements RequestInterceptor {
    @Autowired
    private CECOperatorProperties properties;
    @Autowired
    private ObjectFactory<HttpMessageConverters> messageConverters;
    @Bean
    Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
    @Bean
    public Encoder encoder(){
        return new CECEncoder(messageConverters, properties);
    }
    @Bean
    public Decoder decoder(){
        return new CECDecoder(messageConverters, properties);
    }

    @Override
    public void apply(RequestTemplate requestTemplate) {
        // TODO 添加Token
    }
}
复制代码

La clase de configuración completa inyectará la instancia obtenida de RootApplicationContext ObjectFactory<HttpMessageConverters>y configurará otra instancia de registro Logger.Levelpara imprimir el registro específico de la solicitud durante la depuración.

Finalmente, probemos si nuestro programa es normal. Un caso de prueba simple es el siguiente:

@Slf4j
public class CECTest extends BaseTest{
    @Autowired
    private CECTokenService tokenService;
    @Autowired
    private CECStationService stationService;
    @Autowired
    private CECOperatorProperties properties;

    @Test
    public void test(){
        QueryTokenReq req = new QueryTokenReq();
        req.setOperatorID(properties.getOperatorID());
        req.setOperatorSecret(properties.getOperatorSecret());
        QueryTokenResp resp = tokenService.queryToken(req);
        log.info("resp: {}", JsonUtil.toJson(resp));
    }
}
复制代码

ServiceMira, ¿no es tan sencillo como llamar al local ? Solo necesita construir los parámetros de entrada correspondientes y puede devolver los parámetros de salida correspondientes, sin preocuparse por operaciones molestas como el cifrado y la firma. Los registros relevantes son los siguientes:

imagen.png

finalmente

Creo que es bastante simple usarlo OpenFeignpara conectarse con sistemas de terceros. Al menos, es mejor que escribir manualmente cifrado, descifrado, conversión JSON y autenticación básicos. Y es mucho más simple de esta manera.

Últimas HutoolYYD!!!

Supongo que te gusta

Origin juejin.im/post/7079379888411115533
Recomendado
Clasificación