zuul 验证,重写返回报文,解析gzip压缩response,使用案例
业务是调用另一个平台API,用他们的接口能力实现一些功能。
真正请求前的filter,我把一些请求前的验证和日志入库放在了这里。
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson2.util.DateUtils; import com.iMagine.iMagine_common.exception.MyException; import com.iMagine.iMagine_common.result.CommonResult; import com.iMagine.iMagine_common.utils.UUIDUtil; import com.iMagine.iMagine_mapper.entity.User; import com.iMagine.iMagine_pro.constant.ErrorEnum; import com.iMagine.iMagine_pro.constant.SysConstant; import com.iMagine.iMagine_pro.feign.body.AiServerResponseBody; import com.iMagine.iMagine_pro.service.MjToAiService; import com.iMagine.iMagine_pro.utils.TokenUtil; import com.iMagine.iMagine_pro.utils.ZuulParameterUtil; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.util.HTTPRequestUtils; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; import org.springframework.util.StreamUtils; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.nio.charset.Charset; import java.util.Date; import java.util.concurrent.ConcurrentHashMap; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; /** * Zuul过滤器,必须继承ZuulFilter父类。 * 当前类型的对象必须交由Spring容器管理。使用@Component注解描述。 * 继承父类后,必须实现父类中定义的4个抽象方法。 * shouldFilter、 run、 filterType、 filterOrder */ @Component @Slf4j public class LoggerPostFilter extends ZuulFilter { @Autowired private MjToAiService mjToAiService; /** * 返回boolean类型。代表当前filter是否生效。 * 默认值为false。 * 返回true代表开启filter。 */ @Override public boolean shouldFilter() { return true; } /** * run方法就是过滤器的具体逻辑。 * return 可以返回任意的对象,当前实现忽略。(spring-cloud-zuul官方解释) * 直接返回null即可。 */ @Override public Object run() { log.info("LoggerPostFilter任务处理开始..."); // 获取zuul提供的上下文对象 RequestContext context = RequestContext.getCurrentContext(); HttpServletRequest request = context.getRequest(); String requestUrl = request.getRequestURI(); log.info("requestUrl:{}",requestUrl); //创建统一UUID String uuid = UUIDUtil.getUUID(); ConcurrentHashMap<String,Object> params = new ConcurrentHashMap<>(4); HttpServletResponse response = context.getResponse(); params.put("uuid",uuid); params.put("date",new Date()); try { InputStream responseDataStream = context.getResponseDataStream(); String contentEncoding = context.getZuulRequestHeaders().get("accept-encoding"); if (responseDataStream != null && "gzip".equals(contentEncoding) && context.getResponseGZipped()) { responseDataStream = new GZIPInputStream(context.getResponseDataStream()); }else{ responseDataStream = context.getResponseDataStream(); } String body = StreamUtils.copyToString(responseDataStream, Charset.forName("UTF-8")); //根据code值替换提示内容 JSONObject requestJson = JSON.parseObject(body); log.info("requestJson:{}",requestJson); // 处理一些返回的数据到params params.put("code",response.getStatus()); params.put("returnMj",body); if(!ObjectUtils.isEmpty(response) && response.getStatus() == 200){ //设置接口记录为执行中状态 params.put("operationResult",SysConstant.STATUS_TWO); //收集一些埋点需要的参数 handlerSomething(body,params); }else{ //设置接口记录为失败状态 params.put("operationResult",SysConstant.STATUS_ONE); //根据code替换返回前端的提示信息 log.error("返回异常报文{}",JSON.toJSONString(requestJson)); String code = ""; if(ObjectUtils.isEmpty(requestJson)){ code = String.valueOf(response.getStatus()); requestJson = new JSONObject(); requestJson.put("code",code); }else{ code = requestJson.get("code").toString(); } String reason = ErrorEnum.ErrorEnumType.getModelUploadType(code); if(StringUtils.isBlank(reason)) reason = ErrorEnum.ErrorEnumType.getModelUploadType("1000"); requestJson.put("reason",reason); } //对请求的入参进行筛选记录 log.info("开始调用handlerRequest...,{}",params); handlerRequest(context,params); response.setCharacterEncoding("UTF-8"); response.setContentType("application/json"); // 将修改后的内容重新压缩为 GZIP 格式 if (responseDataStream != null && "gzip".equals(contentEncoding) && context.getResponseGZipped()) { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); GZIPOutputStream gzipOutputStream = new GZIPOutputStream(byteArrayOutputStream); gzipOutputStream.write(JSON.toJSONString(CommonResult.success(requestJson)).getBytes("UTF-8")); gzipOutputStream.close(); // 设置新的压缩后的响应数据流 context.setResponseDataStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray())); }else{ // 设置新的压缩后的响应数据流 context.setResponseBody(JSON.toJSONString(CommonResult.success(requestJson))); } } catch (Exception e) { log.error("请求异常!{}",e); // 异常数据记录 User user = TokenUtil.getUserByToken(); mjToAiService.handlerException(uuid,e,DateUtils.parseDate(params.get("date")+""),user,context.getRequest()); throw new MyException("请求异常!"); } log.info("LoggerPostFilter任务处理结束!"); return null; } /** * 收集一些埋点需要的参数 * @param body 接口返回的报文 * @param params 需要存储的集合 */ private void handlerSomething( String body,ConcurrentHashMap<String,Object> params){ String paramString = body.replaceAll(" ", "").replaceAll(System.getProperty("line.separator"), ""); JSONObject responseBody = JSON.parseObject(paramString); if (!ObjectUtils.isEmpty(responseBody)){ log.info("【返回参数】{}" , responseBody); //不确定接口返回的是jobid 还是 id //如果是正常返回,则覆盖uuid,届时用来对回调接口做关联 if (ObjectUtils.isEmpty(responseBody.get("id"))){ params.put("uuid",responseBody.get("jobid")); }else{ params.put("uuid",responseBody.get("id")); } } } /** * 对请求的入参进行筛选记录 * 统一记录生图埋点数据 * @param context */ private void handlerRequest(RequestContext context,ConcurrentHashMap<String,Object> params){ //对MJ相关生图接口做埋点 log.info("对MJ相关生图接口做埋点..."); HttpServletRequest request = context.getRequest(); String requestUrl = request.getRequestURI(); Date dateHandler = DateUtils.parseDate(String.valueOf(params.get("date"))); JSONObject requestMap = JSON.parseObject(JSON.toJSONString(ZuulParameterUtil.getRequestParams(context))); User user = TokenUtil.getUserByToken(); try { log.info("接口url:{},参数列表:{}", requestUrl,requestMap); String clientIp = TokenUtil.getRequest().getRemoteHost(); //1.请求悠船文生图接口 //2.封装AiServerResponseBody AiServerResponseBody result = mjToAiService.handlerSpecial( Integer.valueOf(params.get("code")+""), params.get("returnMj") + "", params.get("uuid")+"", dateHandler,user); log.info("AiServerResponseBody 存入数据{}",result); //3.异步插入调用记录img_ai_record,日活记录img_dau_record,异步处理 mjToAiService.addRecord(params.get("returnMj") + "",Integer.valueOf(params.get("code")+""), JSON.toJSONString(requestMap),params.get("uuid")+"",dateHandler,request.getRequestURI(), params.get("operationResult")+"",user,clientIp,1); log.info("diffusion uuid:{}, result:{}", params.get("uuid")+"", result); } catch (Exception e) { //5.处理异常 log.error("handlerRequest异常:{}",e); mjToAiService.handlerException(params.get("uuid")+"",e,dateHandler,user,request); } } /** * 过滤器的类型。可选值有: * pre - 前置过滤 * route - 路由后过滤 * error - 异常过滤 * post - 远程服务调用后过滤 */ @Override public String filterType() { return "post"; } /** * 同种类的过滤器的执行顺序。 * 按照返回值的自然升序执行。 */ @Override public int filterOrder() { return 2; } }
请求回来拦截的filter,解析返回的报文,分析数据和业务处理,最终gzip重构返回报文。
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson2.util.DateUtils;
import com.iMagine.iMagine_common.exception.MyException;
import com.iMagine.iMagine_common.result.CommonResult;
import com.iMagine.iMagine_common.utils.UUIDUtil;
import com.iMagine.iMagine_mapper.entity.User;
import com.iMagine.iMagine_pro.constant.ErrorEnum;
import com.iMagine.iMagine_pro.constant.SysConstant;
import com.iMagine.iMagine_pro.feign.body.AiServerResponseBody;
import com.iMagine.iMagine_pro.service.MjToAiService;
import com.iMagine.iMagine_pro.utils.TokenUtil;
import com.iMagine.iMagine_pro.utils.ZuulParameterUtil;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.util.HTTPRequestUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StreamUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.Date;
import java.util.concurrent.ConcurrentHashMap;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
/**
* Zuul过滤器,必须继承ZuulFilter父类。
* 当前类型的对象必须交由Spring容器管理。使用@Component注解描述。
* 继承父类后,必须实现父类中定义的4个抽象方法。
* shouldFilter、 run、 filterType、 filterOrder
*/
@Component
@Slf4j
public class LoggerPostFilter extends ZuulFilter {
@Autowired
private MjToAiService mjToAiService;
/**
* 返回boolean类型。代表当前filter是否生效。
* 默认值为false。
* 返回true代表开启filter。
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* run方法就是过滤器的具体逻辑。
* return 可以返回任意的对象,当前实现忽略。(spring-cloud-zuul官方解释)
* 直接返回null即可。
*/
@Override
public Object run() {
log.info("LoggerPostFilter任务处理开始...");
// 获取zuul提供的上下文对象
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
String requestUrl = request.getRequestURI();
log.info("requestUrl:{}",requestUrl);
//创建统一UUID
String uuid = UUIDUtil.getUUID();
ConcurrentHashMap<String,Object> params = new ConcurrentHashMap<>(4);
HttpServletResponse response = context.getResponse();
params.put("uuid",uuid);
params.put("date",new Date());
try {
InputStream responseDataStream = context.getResponseDataStream();
//这里的accept-encoding 看源码会自动转为小写
String contentEncoding = context.getZuulRequestHeaders().get("accept-encoding");
if (responseDataStream != null && "gzip".equals(contentEncoding) && context.getResponseGZipped()) {
responseDataStream = new GZIPInputStream(context.getResponseDataStream());
}else{
responseDataStream = context.getResponseDataStream();
}
String body = StreamUtils.copyToString(responseDataStream, Charset.forName("UTF-8"));
//根据code值替换提示内容
JSONObject requestJson = JSON.parseObject(body);
log.info("requestJson:{}",requestJson);
// 处理一些返回的数据到params
params.put("code",response.getStatus());
params.put("returnMj",body);
if(!ObjectUtils.isEmpty(response) && response.getStatus() == 200){
//设置接口记录为执行中状态
params.put("operationResult",SysConstant.STATUS_TWO);
//收集一些埋点需要的参数
handlerSomething(body,params);
}else{
//设置接口记录为失败状态
params.put("operationResult",SysConstant.STATUS_ONE);
//根据code替换返回前端的提示信息
log.error("返回异常报文{}",JSON.toJSONString(requestJson));
String code = "";
if(ObjectUtils.isEmpty(requestJson)){
code = String.valueOf(response.getStatus());
requestJson = new JSONObject();
requestJson.put("code",code);
}else{
code = requestJson.get("code").toString();
}
String reason = ErrorEnum.ErrorEnumType.getModelUploadType(code);
if(StringUtils.isBlank(reason)) reason = ErrorEnum.ErrorEnumType.getModelUploadType("1000");
requestJson.put("reason",reason);
}
//对请求的入参进行筛选记录
log.info("开始调用handlerRequest...,{}",params);
handlerRequest(context,params);
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
// 将修改后的内容重新压缩为 GZIP 格式
if (responseDataStream != null && "gzip".equals(contentEncoding) && context.getResponseGZipped()) {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
GZIPOutputStream gzipOutputStream = new GZIPOutputStream(byteArrayOutputStream);
gzipOutputStream.write(JSON.toJSONString(CommonResult.success(requestJson)).getBytes("UTF-8"));
gzipOutputStream.close();
// 设置新的压缩后的响应数据流
context.setResponseDataStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
}else{
// 设置新的压缩后的响应数据流
context.setResponseBody(JSON.toJSONString(CommonResult.success(requestJson)));
}
} catch (Exception e) {
log.error("请求异常!{}",e);
// 异常数据记录
User user = TokenUtil.getUserByToken();
mjToAiService.handlerException(uuid,e,DateUtils.parseDate(params.get("date")+""),user,context.getRequest());
throw new MyException("请求异常!");
}
log.info("LoggerPostFilter任务处理结束!");
return null;
}
/**
* 收集一些埋点需要的参数
* @param body 接口返回的报文
* @param params 需要存储的集合
*/
private void handlerSomething( String body,ConcurrentHashMap<String,Object> params){
String paramString = body.replaceAll(" ", "").replaceAll(System.getProperty("line.separator"), "");
JSONObject responseBody = JSON.parseObject(paramString);
if (!ObjectUtils.isEmpty(responseBody)){
log.info("【返回参数】{}" , responseBody);
//不确定接口返回的是jobid 还是 id
//如果是正常返回,则覆盖uuid,届时用来对回调接口做关联
if (ObjectUtils.isEmpty(responseBody.get("id"))){
params.put("uuid",responseBody.get("jobid"));
}else{
params.put("uuid",responseBody.get("id"));
}
}
}
/**
* 对请求的入参进行筛选记录
* 统一记录生图埋点数据
* @param context
*/
private void handlerRequest(RequestContext context,ConcurrentHashMap<String,Object> params){
//对MJ相关生图接口做埋点
log.info("对MJ相关生图接口做埋点...");
HttpServletRequest request = context.getRequest();
String requestUrl = request.getRequestURI();
Date dateHandler = DateUtils.parseDate(String.valueOf(params.get("date")));
JSONObject requestMap = JSON.parseObject(JSON.toJSONString(ZuulParameterUtil.getRequestParams(context)));
User user = TokenUtil.getUserByToken();
try {
log.info("接口url:{},参数列表:{}", requestUrl,requestMap);
String clientIp = TokenUtil.getRequest().getRemoteHost();
//1.请求悠船文生图接口
//2.封装AiServerResponseBody
AiServerResponseBody result = mjToAiService.handlerSpecial(
Integer.valueOf(params.get("code")+""),
params.get("returnMj") + "",
params.get("uuid")+"",
dateHandler,user);
log.info("AiServerResponseBody 存入数据{}",result);
//3.异步插入调用记录img_ai_record,日活记录img_dau_record,异步处理
mjToAiService.addRecord(params.get("returnMj") + "",Integer.valueOf(params.get("code")+""),
JSON.toJSONString(requestMap),params.get("uuid")+"",dateHandler,request.getRequestURI(),
params.get("operationResult")+"",user,clientIp,1);
log.info("diffusion uuid:{}, result:{}", params.get("uuid")+"", result);
} catch (Exception e) {
//5.处理异常
log.error("handlerRequest异常:{}",e);
mjToAiService.handlerException(params.get("uuid")+"",e,dateHandler,user,request);
}
}
/**
* 过滤器的类型。可选值有:
* pre - 前置过滤
* route - 路由后过滤
* error - 异常过滤
* post - 远程服务调用后过滤
*/
@Override
public String filterType() {
return "post";
}
/**
* 同种类的过滤器的执行顺序。
* 按照返回值的自然升序执行。
*/
@Override
public int filterOrder() {
return 2;
}
}