之前我写过一篇文章Spring Cloud Zuul(路由转发与过滤器)里边大概讲解了zuul的路由转发和过滤器,这篇文章我们实践一下zuul的过滤器在项目中的使用。

我们一般在项目中用网关要做很多的事情,一般有用户鉴权,路由转发,统一的错误返回格式等等

细化来分的话 我们可分三类过滤器。PreFilter、PostFilter、ErrorFilter。(当然可以在细化一个RoleFilter,一般情况下,在配置文件中配置就够了)

PreFilter 可以理解为前置拦截,在这里边你可以做一些请求前要做的事情,比如用户鉴权,当然如果你们项目是前后端分离的情况下,你还要放掉他们的OPTIONS请求。

具体的PreFilter代码如下:

/**
 * @author MrWang
 * @version v1.0
 * @date 2018/12/05
 * @Description pre前置过滤器
 */
@Component
public class PreFilter extends ZuulFilter {
    @Value("${default.login.prefix}")
    private String defaultLoginPrefix;

    @Value("${default.login.time}")
    private int loginTime;

    @Autowired
    private JedisCluster redisUtils;


    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 0;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    /**
     * 解决跨域/token校验
     * 拦截器具体的代码
     * @return
     */
    @Override
    public Object run() {
        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletRequest request = requestContext.getRequest();
        HttpServletResponse response = requestContext.getResponse();
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Headers", "Authorization,X-Requested-With");
        if (PassportCodeUtil.TOKEN_OPTION.equals(request.getMethod())) {
            JsonUtil.sendJsonMessage(response,"ok");
            requestContext.setSendZuulResponse(false);
            return null;
        }
        String requestURI = request.getRequestURI();
        //不对登录进行拦截
        if (defaultLoginPrefix.equals(requestURI)) {
            return null;
        }
        String accessToken = request.getHeader("Authorization");
        if (StrKit.isBlank(accessToken)) {
            JsonUtil.sendJsonMessage(response, ResponseUtil.rms(PassportCodeUtil.PFX, PassportCodeUtil.TOKEN_ERROR, "未登录或登录失效", null));
            requestContext.setSendZuulResponse(false);
            return null;
        }
        String user = redisUtils.get(accessToken);
        if (StrKit.notBlank(user)) {
            redisUtils.expire(accessToken, loginTime);
            try {
                requestContext.addZuulRequestHeader("userInfo", URLEncoder.encode(user, "UTF-8"));

            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            return null;
        } else {
            try {
               JsonUtil.sendJsonMessage(response, ResponseUtil.rms(PassportCodeUtil.PFX, PassportCodeUtil.TOKEN_ERROR, "未登录或登录失效", null));
            } catch (Exception e) {
                e.printStackTrace();
            }
            requestContext.setSendZuulResponse(false);
            return null;
        }
    }
}

PreFilter其大概逻辑为默认登录路由不进行拦截,直接进入后台服务,其他请求都要从header中获取Authorization,然后从redis根据Authorization查找用户信息,然后再把用户信息set到zuul的request中供后台服务使用。

现在我们有一个请求,成功调用到了后台的某一个服务,这样他就已经到了PostFilter,在这个过滤器中你可以捕获post异常进行拦截统一返回处理。(比如你现在调用的某一个服务,而这个服务抛了一个异常并没有处理,那这个PreFilter就可以上场了)

详细代码:

/**
 * @author MrWang
 * @version v1.0
 * @date 2018/12/05
 * @Description post过滤器
 */
@Component
public class PostFilter extends ZuulFilter {
    private static final Logger logger = LoggerFactory.getLogger(ZuulFilter.class);

    @Override
    public String filterType() {
        return "post";
    }

    @Override
    public int filterOrder() {
        return 1;
    }

    @Override
    public boolean shouldFilter() {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        if (PassportCodeUtil.TOKEN_OPTION.equals(request.getMethod())) {
            return false;
        }
        return ctx.getResponse().getStatus()!=HttpStatus.SC_OK;
    }

    @Override
    public Object run(){
        try {
            logger.error("进入PostFilter异常拦截");
            RequestContext context = RequestContext.getCurrentContext();
            HttpServletResponse response = context.getResponse();
            Map<String,String> map = new HashMap<>(3);
            map.put("code", ZuulCodeUtil.PFX+ZuulCodeUtil.CLIENT_EXCEPTION);
            map.put("message","provider server is exception");
            JsonUtil.sendJsonMessage(response,map);
        } catch (Exception var5) {
            ReflectionUtils.rethrowRuntimeException(var5);
        }
        return null;
    }
}

PostFilter的大概逻辑就是服务端报错,这边统一向消费者给出报错信息。当然他还可以做更多的事情。

ErrorFilter则为异常过滤器,只要是zuul报错了都会到这个里边来,包括上边的PostFilter,他报错了也会到这个过滤器里来。

我们在这个拦截器里可用记录报错日志并且也可以统一给消费者给出统一报错信息。

具体代码如下:

/**
 * @author MrWang
 * @version v1.0
 * @date 2018/12/05
 * @Description error过滤器
 */
@Component
public class ErrorFilter extends ZuulFilter {
    
    private static final Logger logger = LoggerFactory.getLogger(ErrorFilter.class);

    @Override
    public String filterType() {
        return "error";
    }

    @Override
    public int filterOrder() {
        return 2;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run(){
        try {
            RequestContext context = RequestContext.getCurrentContext();
            ZuulException exception = this.findZuulException(context.getThrowable());
            logger.error("进入ErrorFilter异常拦截", exception);
            HttpServletResponse response = context.getResponse();
            response.setContentType("application/json; charset=utf8");
            response.setStatus(exception.nStatusCode);
            Map<String,String> map = new HashMap<>(3);
            map.put("code", ZuulCodeUtil.PFX+ZuulCodeUtil.ZUUL_EXCEPTION);
            map.put("message",exception.getMessage());
            JsonUtil.sendJsonMessage(response,map);
        } catch (Exception var5) {
            ReflectionUtils.rethrowRuntimeException(var5);
        }

        return null;
    }

    private ZuulException findZuulException(Throwable throwable) {
        if (ZuulRuntimeException.class.isInstance(throwable.getCause())) {
            return (ZuulException)throwable.getCause().getCause();
        } else if (ZuulException.class.isInstance(throwable.getCause())) {
            return (ZuulException)throwable.getCause();
        }
        else {
            return ZuulException.class.isInstance(throwable) ? (ZuulException)throwable : new ZuulException(throwable, 500, (String)null);
        }
    }
}

这里JsonUtil.sendJsonMessage的方法是:

/**
     * 转换json
     * @param response
     * @param map
     */
    public static void sendJsonMessage(HttpServletResponse response, Object map) {
        Gson GSON = new Gson();
        PrintWriter printWriter = null;
        response.setContentType("application/json; charset=utf-8");
        try {
            printWriter = response.getWriter();
        } catch (IOException e) {
            e.printStackTrace();
        }
        printWriter.write(GSON.toJson(map));
        printWriter.close();
        try {
            response.flushBuffer();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }