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

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

05-17 04:55