业务场景
公众号推送服务,推送用户量级较大,且qps较高
解决问题
微信公众号的模版推送,在调用推送api接口时,就已经知道是否推送成功,但是微信依然会触发推送回调事件,显著占用线上服务处理能力
解决方式
在spring cloud gateway中添加过滤器,如果识别为微信回调的推送请求,直接返回空白字符串
代码如下
maven依赖
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
JAVA 过滤器实现
import com.alibaba.nacos.api.utils.StringUtils;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import io.netty.buffer.ByteBufAllocator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import javax.annotation.PostConstruct;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
/**
* @author chunyang.leng
* @date 2022-02-10 9:52 AM
*/
@Component
public class WeChatFilter implements GlobalFilter, Ordered {
private static final Logger logger = LoggerFactory.getLogger(WeChatFilter.class);
private static final XmlMapper XML_MAPPER = new XmlMapper();
private static final byte[] RESPONSE_DATA = "".getBytes(StandardCharsets.UTF_8);
@PostConstruct
private void postConstruct() {
// 初始化xmlMapper相关配置
XML_MAPPER.enable(SerializationFeature.INDENT_OUTPUT);
XML_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL);
XML_MAPPER.setPropertyNamingStrategy(PropertyNamingStrategy.UPPER_CAMEL_CASE);
}
/**
* Process the Web request and (optionally) delegate to the next {@code WebFilter}
* through the given {@link GatewayFilterChain}.
*
* @param exchange the current server exchange
* @param chain provides a way to delegate to the next filter
* @return {@code Mono<Void>} to indicate when request processing is complete
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
URI requestUri = request.getURI();
AtomicReference<String> bodyStr = new AtomicReference<>();
HttpMethod method = request.getMethod();
if(!HttpMethod.POST.equals(method)){
// 非post请求,直接转发
return chain.filter(exchange);
}
return DataBufferUtils
.join(exchange.getRequest().getBody())
.switchIfEmpty(Mono.just(stringBuffer("")))
.flatMap(dataBuffer -> {
try {
if(Objects.isNull(dataBuffer)){
// 无body,直接放行
return getVoidMono(exchange, chain, request, requestUri, bodyStr.get());
}
// 缓存数组
byte[] bytes = new byte[dataBuffer.readableByteCount()];
// 将body数据加载到缓存数组中
dataBuffer.read(bytes);
String bodyString = new String(bytes, StandardCharsets.UTF_8);
// 释放直接内存
DataBufferUtils.release(dataBuffer);
if(StringUtils.isBlank(bodyString)){
// 空 body 放行
return getVoidMono(exchange, chain, request, requestUri, bodyStr.get());
}
// 用于打印日志
bodyStr.set(bodyString);
// 解析xml的节点
ObjectNode objectNode = XML_MAPPER.readValue(bodyStr.get(), ObjectNode.class);
// 获取消息类型
JsonNode msgType1 = objectNode.get("MsgType");
if (Objects.isNull(msgType1)) {
// 未知的消息类型,直接放行
return getVoidMono(exchange, chain, request, requestUri, bodyStr.get());
}
final String msgType = msgType1.textValue();
if (!"event".equals(msgType)) {
// 不是事件,正常转发请求
return getVoidMono(exchange, chain, request, requestUri, bodyStr.get());
}
JsonNode event1 = objectNode.get("Event");
if (Objects.isNull(event1)) {
return getVoidMono(exchange, chain, request, requestUri, bodyStr.get());
}
String event = event1.textValue();
if (!"TEMPLATESENDJOBFINISH".equals(event)) {
// 不是模版事件,正常转发请求
return getVoidMono(exchange, chain, request, requestUri, bodyStr.get());
}
ServerHttpResponse response = exchange.getResponse();
DataBuffer buffer = response.bufferFactory().wrap(RESPONSE_DATA);
response.setStatusCode(HttpStatus.OK);
response.getHeaders().add("Content-Type", "text/plain;charset=UTF-8");
logger.info("收到微信模版推送事件,已处理完毕,原始数据:{}", bodyStr);
return response.writeWith(Mono.just(buffer));
} catch (Exception e) {
logger.error("数据解析出现未知异常,原始数据:" + bodyStr.get(),e);
return chain.filter(exchange);
}
});
}
/**
* 构建request包装
*
* @param exchange
* @param chain
* @param request
* @param requestUri
* @param bodyStr
* @return
*/
private Mono<Void> getVoidMono(ServerWebExchange exchange, GatewayFilterChain chain, ServerHttpRequest request, URI requestUri, String bodyStr) {
URI ex = UriComponentsBuilder.fromUri(requestUri).build(true).toUri();
ServerHttpRequest newRequest = request.mutate().uri(ex).build();
DataBuffer bodyDataBuffer = stringBuffer(bodyStr);
Flux<DataBuffer> bodyFlux = Flux.just(bodyDataBuffer);
newRequest = new ServerHttpRequestDecorator(newRequest) {
@Override
public Flux<DataBuffer> getBody() {
return bodyFlux;
}
};
ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
return chain.filter(newExchange);
}
/**
* 过滤器顺序
*
* @return
*/
@Override
public int getOrder() {
return -9;
}
/**
* 转换数据
*
* @param value
* @return
*/
private DataBuffer stringBuffer(String value) {
byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length);
buffer.write(bytes);
return buffer;
}
}