码迷,mamicode.com
首页 > 编程语言 > 详细

Spring Cloud Zuul Filter 和熔断

时间:2019-05-15 17:58:37      阅读:162      评论:0      收藏:0      [点我收藏+]

标签:fast   logs   help   ide   ipa   实例   connect   throw   feign   

转一篇很不错的关于Spring Cloud Zuul 相关用法的文章,基本包含常用的一些场景,另外附上实际项目中的熔断、打印请求日志和登录验证的实例。

原文地址:https://www.cnblogs.com/shihaiming/p/8489006.html ,作者:https://www.cnblogs.com/shihaiming/

1.服务熔断

package com.ftk.hjs.zuul.server.hystrix;

import com.alibaba.fastjson.JSON;
import com.ftk.hjs.common.WebConstants;
import com.ftk.hjs.common.common.Response;
import com.netflix.zuul.context.RequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.netflix.zuul.filters.Route;
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.util.UrlPathHelper;

import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

@Component
public class ServiceFallbackProvider implements FallbackProvider {

    private static final Logger logger = LoggerFactory.getLogger(ServiceFallbackProvider.class);
    @Autowired
    private RouteLocator routeLocator;
    @Autowired
    private UrlPathHelper urlPathHelper;


    //服务id,可以用* 或者 null 代表所有服务都过滤
    @Override
    public String getRoute() {
        return null;
    }

    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
        return new ClientHttpResponse() {
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return HttpStatus.OK; //请求网关成功了,所以是ok
            }

            @Override
            public int getRawStatusCode() throws IOException {
                return HttpStatus.OK.value();
            }

            @Override
            public String getStatusText() throws IOException {
                return HttpStatus.OK.getReasonPhrase();
            }

            @Override
            public void close() {

            }

            @Override
            public InputStream getBody() throws IOException {
                RequestContext ctx = RequestContext.getCurrentContext();
                Route route = route(ctx.getRequest());
                logger.error(" >>>触发zuul-server断溶;zuulServletContextPath={{}}", route.getLocation());
                Response response = new Response(false);
                response.setMessage(WebConstants.SYSTEM_ERROR_MESSAGE);
                return new ByteArrayInputStream(JSON.toJSONString(response).getBytes("UTF-8")); //返回前端的内容
            }

            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders httpHeaders = new HttpHeaders();
                httpHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8); //设置头
                return httpHeaders;
            }
        };
    }

    //核心逻辑,获取请求路径,利用RouteLocator返回路由信息
    protected Route route(HttpServletRequest request) {
        String requestURI = urlPathHelper.getPathWithinApplication(request);
        return routeLocator.getMatchingRoute(requestURI);
    }

}

2.打印日志的拦截器

package com.ftk.hjs.zuul.server.filter;

import com.alibaba.fastjson.JSONObject;
import com.ftk.framework.redis.clients.collections.builder.RedisStrutureBuilder;
import com.ftk.framework.redis.clients.collections.keyValueRedisStructure;
import com.ftk.hjs.common.WebConstants;
import com.ftk.hjs.common.constant.RedisConstants;
import com.ftk.hjs.common.data.LoginUserData;
import com.ftk.hjs.common.utils.StringUtil;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.util.StreamUtils;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;

public class PrintRequestLogFilter extends ZuulFilter {
    private static Logger log = LoggerFactory.getLogger(PrintRequestLogFilter.class);

    @Override
    public String filterType() {
        return FilterConstants.POST_TYPE;//要打印返回信息,必须得用"post"
    }

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

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

    @Override
    public Object run() {
        try {
            RequestContext ctx = RequestContext.getCurrentContext();
            HttpServletRequest request = ctx.getRequest();
            InputStream in = request.getInputStream();
            String reqBbody = StreamUtils.copyToString(in, Charset.forName("UTF-8"));


            String principal = request.getHeader(WebConstants.TOKEN_KEY);

            if (StringUtil.isNotBlank(principal)) {
                String userToken = RedisConstants.UserNP.USER_TOKEN_KEY + principal;
                keyValueRedisStructure<String> structure = RedisStrutureBuilder.ofKeyValue(String.class)
                        .withNameSpace(RedisConstants.UserNP.USER_NAMESPACE)
                        .withttl(RedisConstants.UserNP.USER_TOKEN_EXPIRE).build();
                String token = structure.get(userToken);
                if (StringUtil.isNotBlank(token)) {
                    LoginUserData userData = JSONObject.parseObject(token, LoginUserData.class);
                    log.info("request token:{} , userNum:{} , mobilePhone:{} , channelNum:{}", principal, userData.getUserNum(), userData.getMobilePhone(), userData.getChannelNum());
                }

            }
            log.info("request url:{} , requestUrl:{}", request.getMethod(), request.getRequestURL().toString());

            if (reqBbody != null) {
                log.info("request body:{}", reqBbody);
            }
            String outBody = ctx.getResponseBody();
            if (outBody != null) {
                log.info("response body:{}", outBody);
            }
            ctx.getResponse().setContentType("text/html;charset=UTF-8");
            ctx.setResponseBody(outBody);
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        }

        return null;
    }

}

3.登录验证

package com.ftk.hjs.zuul.server.filter;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ftk.framework.redis.clients.collections.MapStructure;
import com.ftk.framework.redis.clients.collections.builder.RedisStrutureBuilder;
import com.ftk.framework.redis.clients.collections.keyValueRedisStructure;
import com.ftk.hjs.common.WebConstants;
import com.ftk.hjs.common.common.Request;
import com.ftk.hjs.common.common.Response;
import com.ftk.hjs.common.constant.ErrCodeConstants;
import com.ftk.hjs.common.constant.RedisConstants;
import com.ftk.hjs.common.data.LoginUserData;
import com.ftk.hjs.common.utils.StringUtil;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.http.ServletInputStreamWrapper;
import lombok.Data;
import lombok.ToString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.netflix.zuul.filters.Route;
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.util.StreamUtils;
import org.springframework.web.util.UrlPathHelper;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.nio.charset.Charset;
import java.util.*;

import static com.alibaba.fastjson.JSON.parseObject;

/**
 * 登录验证
 * Created by Frank on 2016/12/8.
 */
public class LoginFilter extends ZuulFilter {

    private static Logger log = LoggerFactory.getLogger(LoginFilter.class);

    private final RouteLocator routeLocator;
    private final UrlPathHelper urlPathHelper;

    private static List<String> oldServers = new ArrayList<>();
    private static List<String> newServers = new ArrayList<>();

    private List<String> excludeSuffixs = new ArrayList<>();

    public LoginFilter(RouteLocator routeLocator, UrlPathHelper urlPathHelper) {
        this.routeLocator = routeLocator;
        this.urlPathHelper = urlPathHelper;
        oldServers.add(RedisConstants.SystemNP.SYSTEM_SERVICE_METHOD_FINANCIAL);
        oldServers.add(RedisConstants.SystemNP.SYSTEM_SERVICE_METHOD_COMMON);
        oldServers.add(RedisConstants.SystemNP.SYSTEM_SERVICE_METHOD_FUND);
        oldServers.add(RedisConstants.SystemNP.SYSTEM_SERVICE_METHOD_INSURANCE);


        newServers.add(RedisConstants.SystemNP.SYSTEM_SERVICE_METHOD_BANK);
        newServers.add(RedisConstants.SystemNP.SYSTEM_SERVICE_METHOD_MESSAGE);
        newServers.add(RedisConstants.SystemNP.SYSTEM_SERVICE_METHOD_USER);
        newServers.add(RedisConstants.SystemNP.SYSTEM_SERVICE_METHOD_ROUT);

        excludeSuffixs.addAll(Arrays.asList(".png", ".js", ".css"));
    }

    //核心逻辑,获取请求路径,利用RouteLocator返回路由信息
    protected Route route(HttpServletRequest request) {
        String requestURI = urlPathHelper.getPathWithinApplication(request);
        return routeLocator.getMatchingRoute(requestURI);
    }

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

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

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

    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        String userNum = null;
        String body = "";
        String principal = request.getHeader(WebConstants.TOKEN_KEY);
        String osTypeKey = request.getHeader(WebConstants.OSTYPE_KEY);
        String channelNum = request.getHeader(WebConstants.CHANNEL_NUM);
        ctx.addZuulRequestHeader(WebConstants.OSTYPE_KEY, osTypeKey);
        ctx.addZuulRequestHeader(WebConstants.CHANNEL_NUM, channelNum);
        try {
            body = StreamUtils.copyToString(request.getInputStream(), Charset.forName("UTF-8"));
//            log.info(">>> zuul LoginFilter body={}", body);
            if(StringUtil.isBlank(principal)){
                principal = request.getParameter(WebConstants.TOKEN_KEY);
            }
            if (StringUtil.isNotBlank(principal)) {
                String userToken = RedisConstants.UserNP.USER_TOKEN_KEY + principal;
                //如果用户token为空,则肯定是没有登录
                if (StringUtil.isBlank(userToken)) {
                    return null;
                }

                keyValueRedisStructure<String> structure = RedisStrutureBuilder.ofKeyValue(String.class)
                        .withNameSpace(RedisConstants.UserNP.USER_NAMESPACE)
                        .withttl(RedisConstants.UserNP.USER_TOKEN_EXPIRE).build();
                String token = structure.get(userToken);
                if (StringUtil.isNotBlank(token)) {
                    LoginUserData userData = JSONObject.parseObject(token, LoginUserData.class);
                    if (userData != null) {
                        userNum = userData.getUserNum();
                        channelNum = userData.getChannelNum();
                        //延长用户token登录时间
                        structure.set(userToken, token);
                        ctx.addZuulRequestHeader(WebConstants.USER_NUM, userNum);
                        ctx.addZuulRequestHeader(WebConstants.CHANNEL_NUM, channelNum);
                        JSONObject jsonObject = new JSONObject();
                        if (!StringUtil.isEmpty(body)) {
                            jsonObject = JSONObject.parseObject(body);
                        }
                        jsonObject.put("userNum", userNum);
                        request.setAttribute("userNum", userNum);
                        final byte[] reqBodyBytes = jsonObject.toJSONString().getBytes();
                        ctx.setRequest(new HttpServletRequestWrapper(request) {
                            @Override
                            public ServletInputStream getInputStream() {
                                return new ServletInputStreamWrapper(reqBodyBytes);
                            }

                            @Override
                            public int getContentLength() {
                                return reqBodyBytes.length;
                            }

                            @Override
                            public long getContentLengthLong() {
                                return reqBodyBytes.length;
                            }
                        });
                    }
                }
            }

//            log.info(" >>> gateWay url={}, userTokenKey={}, userNum={}", request.getRequestURI(), principal, userNum);

            Route route = route(ctx.getRequest());
            String requestURI = request.getRequestURI();
            String zuulServletContextPath = route.getLocation().replace("-server", "");
            //验证接口是否需要登录
            MapStructure<Boolean> structure = RedisStrutureBuilder.ofMap(Boolean.class).withNameSpace(RedisConstants.SystemNP.SYSTEM_NAMESPACE).build();
            Map<String, Boolean> serviceMethod = structure.get(RedisConstants.SystemNP.SYSTEM_SERVICE_METHOD.concat(zuulServletContextPath));
            NeedLoginBean needLoginBean = adaptServiceMethod(requestURI, body, serviceMethod, zuulServletContextPath);
            log.info(">>> zuul LoginFilter needLoginBean={}", needLoginBean);
            //static 静态资源不进行接口验证

            for (String suffix : excludeSuffixs) {
                if (requestURI.endsWith(suffix)) {
                    return null;
                }
            }

            //选判断此接口是否存在zuul网关中
            if (!needLoginBean.isHasMethod()) {
                log.error(">>> 未知接口。requestType={}", requestURI);
                ctx.setSendZuulResponse(false); //不进行路由
                ctx.getResponse().setContentType("text/html;charset=UTF-8");
                Response response = new Response(needLoginBean.getRequestURI(), false);
                response.setMessage(WebConstants.SYSTEM_ERROR_MESSAGE);
                ctx.setResponseBody(JSON.toJSONString(response));
                return null;
            }
            boolean needLogin = needLoginBean.needLogin;
            if (needLogin && StringUtil.isBlank(userNum)) {
                log.error(">>> 当前接口需要登录,请先登录。requestType={}", requestURI);
                ctx.setSendZuulResponse(false); //不进行路由
                ctx.getResponse().setContentType("text/html;charset=UTF-8");
                Response response = new Response(needLoginBean.getRequestURI(), false);
                response.setErrorCode(ErrCodeConstants.NEED_LOGIN.getCode());
                response.setMessage(ErrCodeConstants.NEED_LOGIN.getMessage());
                ctx.setResponseBody(JSON.toJSONString(response));
            }
        } catch ( Exception e) {
            log.error(e.getMessage(), e);
            ctx.setSendZuulResponse(false);
            ctx.setResponseStatusCode(401);
            Response response = new Response(request.getRequestURI(), false);
            response.setMessage(WebConstants.SYSTEM_ERROR_MESSAGE);
            ctx.setResponseBody(JSON.toJSONString(response));
            ctx.getResponse().setContentType("text/html;charset=UTF-8");
        }
        return null;
    }

    public NeedLoginBean adaptServiceMethod(String requestURI, String req, Map<String, Boolean> serviceMethod, String zuulServletContextPath) {
        NeedLoginBean needLoginBean = new NeedLoginBean();//兼容老的服务调用方式
        if (oldServers.contains(zuulServletContextPath)) {
            Request bizRequest = parseObject(req, Request.class);
            needLoginBean.setRequestURI(bizRequest.getRequestType());
            Boolean needLogin = serviceMethod.get(bizRequest.getRequestType());
            if (needLogin == null) {
                //false说明此接口不在网关注册接口范围内
                needLoginBean.setHasMethod(false);
            } else {
                needLoginBean.setHasMethod(true);
                needLoginBean.setNeedLogin(needLogin);
            }

        } else if (newServers.contains(zuulServletContextPath)) {
            needLoginBean.setRequestURI(requestURI);
            //false说明此接口不在网关注册接口范围内
            PathMatcher matcher = new AntPathMatcher();
            Iterator it = serviceMethod.keySet().iterator();
            while (it.hasNext()) {
                String key = (String) it.next();
                boolean result = matcher.match(key, requestURI);
                if (result) {
                    needLoginBean.setHasMethod(true);
                    needLoginBean.setNeedLogin(serviceMethod.get(key));
                    break;
                }
            }

        } else {
            throw new RuntimeException(" >>>请求接口不存在");
        }
        return needLoginBean;
    }

    @ToString
    @Data
    private static class NeedLoginBean {
        String requestURI;
        boolean hasMethod;
        boolean needLogin;
    }
}

3.启动类

package com.ftk.hjs.zuul.server;

import com.ftk.framework.redis.clients.collections.factory.RedisConfig;
import com.ftk.framework.redis.clients.collections.factory.RedisConnection;
import com.ftk.hjs.zuul.server.filter.LoginFilter;
import com.ftk.hjs.zuul.server.filter.PrintRequestLogFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.util.UrlPathHelper;

import java.util.ArrayList;
import java.util.List;

@SpringBootApplication
@EnableZuulProxy
@EnableEurekaClient
@SpringCloudApplication
@EnableFeignClients
@ComponentScan(basePackages = {"com.ftk.hjs","com.ftk.framework"})
public class ZuulServerLauncher implements CommandLineRunner {

    private static final Logger logger = LoggerFactory.getLogger(ZuulServerLauncher.class);

    @Autowired
    private RedisConfig redisConfig;

    public static void main(String[] args) {
        SpringApplication.run(ZuulServerLauncher.class, args);
    }

    @Override
    public void run(String... strings){
        //初始化redis
//        RedisConnection.init(redisConfig);
        logger.info("ZuulServerLauncher has run !!! {} ", strings);

    }

    @Bean
    public LoginFilter accessFilter(RouteLocator routeLocator) {
        return new LoginFilter(routeLocator,new UrlPathHelper());
    }

    @Bean
    public PrintRequestLogFilter printRequestLogFilter() {
        return new PrintRequestLogFilter();
    }


    private CorsConfiguration addcorsConfig() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        List<String> list = new ArrayList<>();
        list.add("*");
        corsConfiguration.setAllowedOrigins(list);
        /*
        // 请求常用的三种配置,*代表允许所有,当时你也可以自定义属性(比如header只能带什么,只能是post方式等等)
        */
        corsConfiguration.addAllowedOrigin("*");
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedMethod("*");
        corsConfiguration.setMaxAge(3600L);
        corsConfiguration.setAllowCredentials(true);
        return corsConfiguration;
    }

    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", addcorsConfig());
        return new CorsFilter(source);
    }

}

最后,以上代码均为部分代码,参照转载文章的说明和实例即可实现自己的网关功能。

 

Spring Cloud Zuul Filter 和熔断

标签:fast   logs   help   ide   ipa   实例   connect   throw   feign   

原文地址:https://www.cnblogs.com/li-zhi-long/p/10871012.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!