Introducción
Hola a todos, soy Anyin.
En lo que respecta a OpenFeign - artículo de uso , compartí con ustedes OpenFeign
algunos métodos de procesamiento y uso en ciertos escenarios, y hoy Anyin desbloqueó OpenFeign
otro 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 OpenFeign
en 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 OpenFeign
podamos 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.
Al mirar el documento de protocolo, sabemos que todo el proceso de acoplamiento estará diseñado para cumplir con los siguientes requisitos:
- El método de llamada utiliza el método POST de manera uniforme
- El formato de transferencia usa JSON
- Los datos comerciales deben cifrarse durante la transmisión
- 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.
Service
Al hacer llamadas de terceros, debe ser tan fluido como llamar a otros locales (comportamiento consistente)
realización de negocios
Para OpenFeign
lograr 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) {}
}
复制代码
- 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
RequestInterceptor
autenticaciónaccess_token
- 注入
CECOperatorProperties
属性,对于加解密、验签等操作需要的一些秘钥信息,从配置中心获取后,注入该属性类中 @Configuration(proxyBeanMethods = false)
配置该类配置类,并且不会在RootApplicationContext当中注册,只会在使用的时候才会进行相关配置。
这里注意哈,在这个类配置的@Bean
实例,只有在当前的FeignClient
实例的ApplicaitonContext当中可以访问到,其他地方访问不到。具体可以看
接着,我们需要2个基本的数据传输对象:Request
和 Response
@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>
的实例。另外,在构造器我们也初始化了HMac
和AES
两个实例,一个为了生成签名,一个为了加密业务数据。
在encode
方法,我们把传递进来的requestBody
包装了下,先对其进行加密,然后放在CECRequest
实例的data字段内,并且生成对应的签名,最终请求服务端的时候是一个CECRequest
实例的JSON化的结果。
Algunas personas pueden preguntarse, ¿por qué requestBody
los datos comerciales están directamente aquí, no la CECRequest<T>
instancia? Considere nuestro quinto requisito: hacer llamadas de terceros debe ser tan Service
fluido (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í Response
que obtenemos la cadena JSON correspondiente, luego obtenemos la CECResponse
instancia 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 OpenFeign
del método proporcionado para su posterior procesamiento.toBuilder
Response
SpringDecoder
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.Level
para 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));
}
}
复制代码
Service
Mira, ¿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:
finalmente
Creo que es bastante simple usarlo OpenFeign
para 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 Hutool
YYD!!!