Zuul 是 Netflix 开源的微服务网关,Spring Cloud 对 Zuul 进行了整合和增强。在 SpringCloud 体系中,Zuul 担任着网关的角色,对发送到服务端的请求进行一些预处理,比如安全验证、动态路由、负载分配等。
Zuul 的核心是 Filters,根据执行时期分为以下几类:
PRE:这种过滤器在请求被路由之前调用
ROUTING:这种过滤器将请求路由到微服务
POST:这种过滤器在路由到微服务以后执行
ERROR:在其他阶段发生错误时执行该过滤器
下面对于配置进行说明:
pom的配置:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
由于还没有使用eureka,所以只引入zuul的集成包就可以。版本由 spring-boot-starter-parent自动管理。
启动类添加 @EnableZuulProxy 注解:
@SpringBootApplication
@EnableZuulProxy
public class MainWebApplication {
private static Logger logger=LoggerFactory.getLogger(MainWebApplication.class);
public static void main(String[] args) {
SpringApplication.run(MainWebApplication.class, args);
logger.info("***>>>>>>>Server startup>>>");
}
}
application.properties的配置:
#服务器不用注册到其他服务器
eureka.client.registerWithEureka=false
#服务器不用去服务器抓取注册信息
eureka.client.fetchRegistry=false
application-cloudConfig.properties 配置:
#本地环境配置zuul转发的规则:
spring.application.name=acs-msettle-gateway
zuul.routes.acs-deposit.path=/acsDeposit/**
zuul.routes.acs-deposit.url=http://127.0.0.1:8260/
zuul 转发 有两种配置:
1、根据 eureka server 的serviceId 转发:
zuul.routes.serviceName.path=/exampleService/**
zuul.routes.serviceName.serviceId=serviceId
注:
zuul.routes 是固定的
serviceName 是可以随便写的,但最好根据要路由的服务取
serviceId 是 eureka 服务注册时的名称
exampleService 是前端请求某个微服务的一个公共的路径名,如下面的acsDeposit
//根据Id查询
url1 = "${basePath}/acsDeposit/propConfig/getPropConfigById?param=" + param;
//新增配置
url2 = "${basePath}/acsDeposit/propConfigSub/addProportionConfig.do";
而微服务在 RequestMapping 时可以不包含acsDeposit
2、根据具体 URL 转发:
zuul.routes.serviceName.path=/exampleService/**
zuul.routes.serviceName.url=http://127.0.0.1:8260/
上述参数与1的取名规则相同,不再解释。
项目当前由于尚未使用eureka,所以采用了第二种转发规则。这种转发有很多好处,最大的好处就是不用再写一大堆的dubbo接口了,项目的页面都放在一个服务上,在使用 Zuul 之前,所有的交互都要写成dubbo接口,使用Zuul可以直接HTTP调用,方便很多。
下面讲下Zuul使用中遇到的问题:
1、Zuul 转发404,没有转到微服务上
(一)微服务需要构造成web服务结构,转发请求404还是因为请求url的问题,微服务需要配置下url匹配规则:
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
@Configuration
public class WebConfig extends WebMvcConfigurationSupport {
/**
* setUseSuffixPatternMatch : 设置是否是后缀模式匹配,如“/user”是否匹配/user.*,默认真即匹配;
* setUseTrailingSlashMatch : 设置是否自动后缀路径模式匹配,如“/user”是否匹配“/user/”,默认真即匹配;
*/
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseSuffixPatternMatch(true).setUseTrailingSlashMatch(true);
}
}
(注:配置类要放在可以使注解生效的路径下)
(二)application.properties 中没有配置对application-cloudConfig.properties的加载:
spring.profiles.active=dataSource,cloudConfig
2、Zuul 转发请求,原Session丢失
Zuul 的核心是Filters,在请求转发的各个时期Zuul都自定义了一些Filter用来对原请求进行封装,从而转发。由于尚未引入Ribbon,所以没有被 RibbonRoutingFilter 拦截。
转发的处理是发生在 route 时期,所有的Filter都要继承ZuulFilter,下面说一下ZuulFilter的结构,下面是自定义的一个Filter:
import com.alibaba.fastjson.JSONObject;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;
public class ZuulUserFilter extends ZuulFilter {
/**
* 返回过滤类型,有:pre,route,post,error
*/
@Override
public String filterType() {
return PRE_TYPE;
}
/**
* 返回过滤的优先级,数字越大,优先级越高,越早执行
*/
@Override
public int filterOrder() {
return 101;
}
/**
* 设置过滤条件
*/
@Override
public boolean shouldFilter() {
return RequestContext.getCurrentContext().getRouteHost() != null
&& RequestContext.getCurrentContext().sendZuulResponse();
}
/**
* 具体的操作
*/
@Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
User user = (User) request.getSession().getAttribute(Cons.S_USER);
Map<String, List<String>> requestQueryParams = context.getRequestQueryParams();
if (requestQueryParams==null) {
requestQueryParams=new HashMap<>();
}
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add(JSONObject.toJSONString(user));
requestQueryParams.put(Cons.S_USER, arrayList);
context.setRequestQueryParams(requestQueryParams);
return null;
}
}
每个方法都有详细注释,这里不在解释。
Zuul 在没有使用 Ribbon 的情况下,在route时期执行的Filter是SimpleHostRoutingFilter,在这个过滤器中,对原请求进行了重组,但是却因为清空了Cookie,使得原本可能存在于Session中的一些重要数据丢失。比如登录的用户信息就保存在Session中,转发后Session丢失,使得微服务不能获取到请求用户的信息。
网上解决方案-在application.properties中清空敏感header:
zuul.sensitive-headers=
(但是配置后却没有生效,知道原因的小伙伴请留言哈)
最后,解决方案就是上面代码中的做法,将用户相关参数封装成请求参数,因为请求参数不会被过滤掉。而后在微服务中获取:
/**
* 新增占比配置
*/
@RequestMapping("addProportionConfig")
@ResponseBody
public Object addProportionConfig(HttpServletRequest request, ProportionConfig config) {
SettleResponse response = new SettleResponse(RetCode.SUCCESS);
try {
String userInfo = request.getParameterMap().get(Constant.S_USER)[0];
User user = JSONObject.parseObject(userInfo, User.class);
logger.info("【新增占比配置】操作人:{}, 请求参数={}", user.getLoginName(), config);
proportionConfigService.addProportionConfig(config);
} catch (Exception e) {
response.setResult(RetCode.FAILURE, "新增占比配置系统异常!");
logger.error("【新增占比配置】系统异常,异常原因", e);
}
return response;
}
是的,很难看,大家可以放在某个工具类里封装一下。嗯,还是很怪异。但是能暂时的解决问题,因为是系统内部的微服务调用,所以还可以忍。只能坐等大神指导啦。