zoukankan      html  css  js  c++  java
  • Spring Cloud Zuul Filter 和熔断

    转一篇很不错的关于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);
        }
    
    }

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

  • 相关阅读:
    SpringBoot页面访问处理
    体验SpringBoot
    体验SpringBoot
    Scala基础
    修改容器配置使其永久生效
    [徐培成系列实战课程]docker篇
    v1.0.2-2017.04.26
    修改容器的hosts文件
    配置spark集群
    配置docker容器上ssh无密登录
  • 原文地址:https://www.cnblogs.com/li-zhi-long/p/10871012.html
Copyright © 2011-2022 走看看