转一篇很不错的关于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); } }
最后,以上代码均为部分代码,参照转载文章的说明和实例即可实现自己的网关功能。
原文地址:https://www.cnblogs.com/li-zhi-long/p/10871012.html
时间: 2024-10-23 11:43:17