ZUUL网关
网关==转发,以后访问服务的时候,不需要每次都访问每个服务的地址,只需要访问网关就可以
主要是做请求拆分 , 消息合并 , 协议转换
注意:版本E以及之前,可以直接访问http:localhost://端口号/routes or filter
从版本H开始在前面就要访问 http:localhost://端口号/actuator/routes or filter
pom.xml
<span style="background-color:#f8f8f8"><span style="color:#333333">
<span style="color:#117700"><</span><span style="color:#117700">dependency</span><span style="color:#117700">></span>
<span style="color:#117700"><</span><span style="color:#117700">groupId</span><span style="color:#117700">></span>org.springframework.cloud<span style="color:#117700"></</span><span style="color:#117700">groupId</span><span style="color:#117700">></span>
<span style="color:#117700"><</span><span style="color:#117700">artifactId</span><span style="color:#117700">></span>spring-cloud-starter-netflix-zuul<span style="color:#117700"></</span><span style="color:#117700">artifactId</span><span style="color:#117700">></span>
<span style="color:#117700"></</span><span style="color:#117700">dependency</span><span style="color:#117700">></span>
<span style="color:#aa5500"><!--访问 eureka 的页面的时候需要密码--></span>
<span style="color:#117700"><</span><span style="color:#117700">dependency</span><span style="color:#117700">></span>
<span style="color:#117700"><</span><span style="color:#117700">groupId</span><span style="color:#117700">></span>org.springframework.boot<span style="color:#117700"></</span><span style="color:#117700">groupId</span><span style="color:#117700">></span>
<span style="color:#117700"><</span><span style="color:#117700">artifactId</span><span style="color:#117700">></span>spring-boot-starter-security<span style="color:#117700"></</span><span style="color:#117700">artifactId</span><span style="color:#117700">></span>
<span style="color:#117700"></</span><span style="color:#117700">dependency</span><span style="color:#117700">></span>
<span style="color:#aa5500"><!--通过 eureka 来拉取服务列表--></span>
<span style="color:#117700"><</span><span style="color:#117700">dependency</span><span style="color:#117700">></span>
<span style="color:#117700"><</span><span style="color:#117700">groupId</span><span style="color:#117700">></span>org.springframework.cloud<span style="color:#117700"></</span><span style="color:#117700">groupId</span><span style="color:#117700">></span>
<span style="color:#117700"><</span><span style="color:#117700">artifactId</span><span style="color:#117700">></span>spring-cloud-starter-netflix-eureka-client<span style="color:#117700"></</span><span style="color:#117700">artifactId</span><span style="color:#117700">></span>
<span style="color:#117700"></</span><span style="color:#117700">dependency</span><span style="color:#117700">></span></span></span>
application.yml
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#221199">server</span><span style="color:#555555">:</span>
<span style="color:#221199"> port</span><span style="color:#555555">: </span><span style="color:#116644">16500</span>
<span style="color:#221199">eureka</span><span style="color:#555555">: </span><span style="color:#aa5500">#注册中心的地址,用于获取服务列表</span>
<span style="color:#221199"> client</span><span style="color:#555555">:</span>
<span style="color:#221199"> service-url</span><span style="color:#555555">:</span>
<span style="color:#221199"> defaultZone</span><span style="color:#555555">: </span>http<span style="color:#555555">:</span>//zhangsan<span style="color:#555555">:</span>abc@localhost<span style="color:#555555">:</span>10000/eureka <span style="color:#aa5500">#curl风格</span>
<span style="color:#221199">spring</span><span style="color:#555555">:</span>
<span style="color:#221199"> application</span><span style="color:#555555">:</span>
<span style="color:#221199"> name</span><span style="color:#555555">: </span>22apigateway-zuul
<span style="color:#221199"> security</span><span style="color:#555555">:</span>
<span style="color:#221199"> user</span><span style="color:#555555">:</span>
<span style="color:#221199"> password</span><span style="color:#555555">: </span>xiaomei
<span style="color:#221199"> name</span><span style="color:#555555">: </span>laowang <span style="color:#aa5500">#默认是user</span>
<span style="color:#221199">management</span><span style="color:#555555">:</span>
<span style="color:#221199"> endpoints</span><span style="color:#555555">:</span>
<span style="color:#221199"> web</span><span style="color:#555555">:</span>
<span style="color:#221199"> exposure</span><span style="color:#555555">:</span>
<span style="color:#221199"> include</span><span style="color:#555555">: </span><span style="color:#aa1111">'*'</span> <span style="color:#aa5500">#允许访问所有管理地址,不然无法通过网页查看路由的列表页/actuator/routes,但是不影响路由功能更</span></span></span>
注解
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#555555">@EnableZuulProxy</span></span></span>
映射路径
自定义映射路径
因为我们注册中心设置的请求名字可能会比较复杂,我们可以通过yml文件来把这个地址映射到你想换的地址,如果多个域名需要修改,在下一行这样写就可以
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#221199">zuul</span><span style="color:#555555">:</span>
<span style="color:#221199"> routes</span><span style="color:#555555">:</span>
<span style="color:#221199"> 13orderconsumer-openfeign-hytrix</span> <span style="color:#555555">: </span>/jinwandalaohu/** <span style="color:#aa5500">#要想访问13orderconsumer-openfeign-hytrix路径需要通过jinwandalaohu来访问</span></span></span>
映射了请求地址以后,我们发现原本的域名也可以访问,如果不想之前的地址访问,我们就需要在yml文件里面添加忽略服务的列表
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#221199">zuul</span><span style="color:#555555">:</span>
<span style="color:#221199"> routes</span><span style="color:#555555">:</span>
<span style="color:#221199"> 13orderconsumer-openfeign-hytrix</span> <span style="color:#555555">: </span>/jinwandalaohu/** <span style="color:#aa5500">#要想访问13orderconsumer-openfeign-hytrix路径需要通过jinwandalaohu来访问</span>
<span style="color:#221199"> itemprovider-eureka-hystrix</span> <span style="color:#555555">: </span>/laohubanichile/**
<span style="color:#221199"> ignored-services</span><span style="color:#555555">:</span>
13orderconsumer-openfeign-hytrix , itemprovider-eureka-hystrix <span style="color:#aa5500">#添加忽略的服务列表,多个以,分开,如果是忽略所有则写'*'</span></span></span>
映射其他服务的集群
在实际开发中,我们的服务节点都是集群,当一个服务集群没有在eureka上面注册的时候,我们需要单独配置
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#221199">zuul</span><span style="color:#555555">:</span>
<span style="color:#221199"> routes</span><span style="color:#555555">:</span>
<span style="color:#221199"> aaaaa</span><span style="color:#555555">: </span><span style="color:#aa5500">#随便写,但是在配置文件里面不能重复</span>
<span style="color:#221199"> path</span><span style="color:#555555">: </span>/abcdefg/** <span style="color:#aa5500">#映射后的地址</span>
<span style="color:#221199"> serviceId</span><span style="color:#555555">: </span>05consumer-eureka <span style="color:#aa5500">#要给哪个服务设置路由地址</span>
<span style="color:#221199"> bbbb</span><span style="color:#555555">:</span>
<span style="color:#221199"> path</span><span style="color:#555555">: </span>/def/**
<span style="color:#221199"> serviceId</span><span style="color:#555555">: </span>04provider-eureka</span></span>
同一前缀
像controller一样,在zuul里面我们也可以给地址同一前缀,只需要在yml文件里设置就好了
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#221199">zuul</span><span style="color:#555555">:</span>
<span style="color:#221199"> prefix</span><span style="color:#555555">: </span>/suibian</span></span>
表达式映射(灰度发布)
当我们升级服务的时候版本不同,对应的接口可能也不同 ,(灰度发布==>会同时存在两个不同的版本服务)为了方便访问,我们的网关里面提供了一个表达式方式来方便我们进行操作
<span style="background-color:#f8f8f8"><span style="color:#333333"> <span style="color:#aa5500">/**</span>
<span style="color:#aa5500">* 设置服务的映射方式,当我们的服务的名字是 name-vx 的方式进行命名的时候,访问地址会被映射为 vx/name</span>
<span style="color:#aa5500">* 一般我们会通过这个方式来进行版本区分</span>
<span style="color:#aa5500">* 比如一个服务叫 gouwuche-v1,那么它的路径就是 v1/gouwuche</span>
<span style="color:#aa5500">* 当我们的这个服务升级后我们只需要将名字改成 gouwuche-v2 那么客户端就可以通过不同的版本前缀来访问不同版本的服务</span>
<span style="color:#aa5500">* @return</span>
<span style="color:#aa5500">*/</span>
<span style="color:#555555">@Bean</span>
<span style="color:#770088">public</span> <span style="color:#000000">PatternServiceRouteMapper</span> <span style="color:#0000ff">serviceRouteMapper</span>() {
<span style="color:#aa5500">//其中<name> <version>是一个别名,是对应表达式内容的别名 ,实际上 name 代表的是^. 也就是任意内容 <version>代表是 v.代表以 v 开始的内容,所以下面的表达式代表是 name-vx</span>
<span style="color:#770088">return</span> <span style="color:#770088">new</span> <span style="color:#000000">PatternServiceRouteMapper</span>(
<span style="color:#aa1111">"(?<name>^.+)-(?<version>v.+$)"</span>,
<span style="color:#aa1111">"${version}/${name}"</span>);
}</span></span>
网关降级
当网关出现异常,也可以实施fallback降级操作,实现FallbackProvider接口,根据实际业务需求,重写里面的方法
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#555555">@Component</span>
<span style="color:#770088">public</span> <span style="color:#770088">class</span> <span style="color:#0000ff">MyFallbackProvider</span> <span style="color:#770088">implements</span> <span style="color:#000000">FallbackProvider</span> {
<span style="color:#aa5500">/**</span>
<span style="color:#aa5500">* 当前fallback是给哪个服务用的,如果是所有的类,则返回'*'</span>
<span style="color:#aa5500">* @return</span>
<span style="color:#aa5500">*/</span>
<span style="color:#555555">@Override</span>
<span style="color:#770088">public</span> <span style="color:#008855">String</span> <span style="color:#000000">getRoute</span>() {
<span style="color:#770088">return</span> <span style="color:#aa1111">"*"</span>;
}
<span style="color:#aa5500">/**</span>
<span style="color:#aa5500">*</span>
<span style="color:#aa5500">* @param route 具体出现问题的服务</span>
<span style="color:#aa5500">* @param cause 出现问题的原因</span>
<span style="color:#aa5500">* @return</span>
<span style="color:#aa5500">*/</span>
<span style="color:#555555">@Override</span>
<span style="color:#770088">public</span> <span style="color:#000000">ClientHttpResponse</span> <span style="color:#000000">fallbackResponse</span>(<span style="color:#008855">String</span> <span style="color:#000000">route</span>, <span style="color:#000000">Throwable</span> <span style="color:#000000">cause</span>) {
<span style="color:#770088">return</span> <span style="color:#770088">new</span> <span style="color:#000000">ClientHttpResponse</span>() {
<span style="color:#555555">@Override</span>
<span style="color:#770088">public</span> <span style="color:#000000">HttpStatus</span> <span style="color:#000000">getStatusCode</span>() <span style="color:#770088">throws</span> <span style="color:#000000">IOException</span> {
<span style="color:#770088">return</span> <span style="color:#000000">HttpStatus</span>.<span style="color:#000000">OK</span>;<span style="color:#aa5500">//根据自己业务的实际情况返回对应的值</span>
}
<span style="color:#555555">@Override</span>
<span style="color:#770088">public</span> <span style="color:#008855">int</span> <span style="color:#000000">getRawStatusCode</span>() <span style="color:#770088">throws</span> <span style="color:#000000">IOException</span> {
<span style="color:#770088">return</span> <span style="color:#116644">200</span>;<span style="color:#aa5500">//状态码</span>
}
<span style="color:#555555">@Override</span>
<span style="color:#770088">public</span> <span style="color:#008855">String</span> <span style="color:#000000">getStatusText</span>() <span style="color:#770088">throws</span> <span style="color:#000000">IOException</span> {
<span style="color:#770088">return</span> <span style="color:#aa1111">"根据情况返回"</span>;
}
<span style="color:#555555">@Override</span>
<span style="color:#770088">public</span> <span style="color:#008855">void</span> <span style="color:#000000">close</span>() {
}
<span style="color:#aa5500">/**</span>
<span style="color:#aa5500">*</span>
<span style="color:#aa5500">* @return 我们要返回的内容,通过io流返回</span>
<span style="color:#aa5500">* @throws IOException</span>
<span style="color:#aa5500">*/</span>
<span style="color:#555555">@Override</span>
<span style="color:#770088">public</span> <span style="color:#000000">InputStream</span> <span style="color:#000000">getBody</span>() <span style="color:#770088">throws</span> <span style="color:#000000">IOException</span> {
<span style="color:#770088">return</span> <span style="color:#770088">new</span> <span style="color:#000000">ByteArrayInputStream</span>(<span style="color:#aa1111">"啥啊也不是"</span>.<span style="color:#000000">getBytes</span>(<span style="color:#000000">StandardCharsets</span>.<span style="color:#000000">UTF_8</span>));
}
<span style="color:#aa5500">/**</span>
<span style="color:#aa5500">* 响应头</span>
<span style="color:#aa5500">* @return</span>
<span style="color:#aa5500">*/</span>
<span style="color:#555555">@Override</span>
<span style="color:#770088">public</span> <span style="color:#000000">HttpHeaders</span> <span style="color:#000000">getHeaders</span>() {
<span style="color:#000000">HttpHeaders</span> <span style="color:#000000">httpHeaders</span> <span style="color:#981a1a">=</span> <span style="color:#770088">new</span> <span style="color:#000000">HttpHeaders</span>();
<span style="color:#000000">httpHeaders</span>.<span style="color:#000000">add</span>(<span style="color:#aa1111">"Content-Type"</span>,<span style="color:#aa1111">"text/html;charset=utf-8"</span>);
<span style="color:#770088">return</span> <span style="color:#000000">httpHeaders</span>;
}
};
}
}</span></span>
error地址转换
当我们的服务出现异常的时候,spring给我们提供了一个错误地址,我们可以将这个地址映射成我们自己的,返回我们给用户看到的内容
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.Sun Nov 07 14:29:38 CST 2021
There was an unexpected error (type=Not Found, status=404).
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#555555">@RestController</span>
<span style="color:#770088">public</span> <span style="color:#770088">class</span> <span style="color:#0000ff">ErrorController</span> <span style="color:#770088">implements</span> <span style="color:#000000">org</span>.<span style="color:#000000">springframework</span>.<span style="color:#000000">boot</span>.<span style="color:#000000">web</span>.<span style="color:#000000">servlet</span>.<span style="color:#000000">error</span>.<span style="color:#000000">ErrorController</span> {
<span style="color:#555555">@PostMapping</span>(<span style="color:#000000">value</span> <span style="color:#981a1a">=</span> <span style="color:#aa1111">"/error"</span>,<span style="color:#000000">produces</span> <span style="color:#981a1a">=</span> <span style="color:#aa1111">"text/html;charset=utf-8"</span>)
<span style="color:#770088">public</span> <span style="color:#008855">String</span> <span style="color:#000000">processError</span>(){
<span style="color:#770088">return</span> <span style="color:#aa1111">"你的电脑出现重大问题,撒冷给我送来"</span>;
}
<span style="color:#aa5500">/**</span>
<span style="color:#aa5500">*我们要替代哪个地址,返回值替对应的地址会映射成我们自己的,而不是spring自带的</span>
<span style="color:#aa5500">* 在2.3.0版本及之前,要实现这个方法,2.3.0之后可以不实现这个方法,系统自动会映射成我们的</span>
<span style="color:#aa5500">* @return</span>
<span style="color:#aa5500">*/</span>
<span style="color:#555555">@Override</span>
<span style="color:#770088">public</span> <span style="color:#008855">String</span> <span style="color:#000000">getErrorPath</span>() {
<span style="color:#770088">return</span> <span style="color:#aa1111">"/error"</span>;<span style="color:#aa5500">//替代这个地址</span>
}
}</span></span>
过滤器Filter
网关给我们提供了filter过滤器让我们来进行批量操作,如: 获取请求,中断请求,返回结果等..
我们只要实现ZuuFilter接口,重写里面的方法就可以实现过滤器功能
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#555555">@Component</span>
<span style="color:#770088">public</span> <span style="color:#770088">class</span> <span style="color:#0000ff">MyPreFilter</span> <span style="color:#770088">extends</span> <span style="color:#000000">ZuulFilter</span> {
<span style="color:#aa5500">/**</span>
<span style="color:#aa5500">* 当前过滤器的类型</span>
<span style="color:#aa5500">* @return</span>
<span style="color:#aa5500">*/</span>
<span style="color:#555555">@Override</span>
<span style="color:#770088">public</span> <span style="color:#008855">String</span> <span style="color:#000000">filterType</span>() {
<span style="color:#770088">return</span> <span style="color:#000000">FilterConstants</span>.<span style="color:#000000">PRE_TYPE</span>;
}
<span style="color:#aa5500">/**</span>
<span style="color:#aa5500">* 过滤器的顺序,在相同type的情况下下,数字越小,优先级越高</span>
<span style="color:#aa5500">* @return</span>
<span style="color:#aa5500">*/</span>
<span style="color:#555555">@Override</span>
<span style="color:#770088">public</span> <span style="color:#008855">int</span> <span style="color:#000000">filterOrder</span>() {
<span style="color:#770088">return</span> <span style="color:#116644">0</span>;
}
<span style="color:#aa5500">/**</span>
<span style="color:#aa5500">* 当前过滤器是否开启,</span>
<span style="color:#aa5500">* 此处根据实际的业务需求来决定是否开启</span>
<span style="color:#aa5500">* false不开启</span>
<span style="color:#aa5500">* @return</span>
<span style="color:#aa5500">*/</span>
<span style="color:#555555">@Override</span>
<span style="color:#770088">public</span> <span style="color:#008855">boolean</span> <span style="color:#000000">shouldFilter</span>() {
<span style="color:#770088">return</span> <span style="color:#221199">false</span>;
}
<span style="color:#aa5500">/**</span>
<span style="color:#aa5500">* 如果过滤器开启了,则会执行此操作</span>
<span style="color:#aa5500">* @return 无意义,因为不是返回用户的</span>
<span style="color:#aa5500">* @throws ZuulException</span>
<span style="color:#aa5500">*/</span>
<span style="color:#555555">@Override</span>
<span style="color:#770088">public</span> <span style="color:#008855">Object</span> <span style="color:#000000">run</span>() <span style="color:#770088">throws</span> <span style="color:#000000">ZuulException</span> {
<span style="color:#770088">return</span> <span style="color:#221199">null</span>;
}
}</span></span>
通过看源码我们发现,zuul给我们提供了前置过滤器PRE_TYPE ,后端过滤器POST_TYPE,路由过滤器ROUTE_TYPE,错误过滤器ERROR_TYPE ,我们根据也无需求来选择我们需要的过滤器,在相同的type情况下,filterOrder()方法返回的数字越小,优先级越高.
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#aa5500">//TODO 如果过滤器需要强制转发服务,那这个过滤器的 order 必须大于等于 5,因为 zuul 内部有其他的过滤器会进行封装操作,我强制更改后会导致它有些数据无法获取</span></span></span>
过滤器还给我们提供了一些封装好的方法:
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#000000">RequestContext</span> <span style="color:#000000">requestContext</span> <span style="color:#981a1a">=</span> <span style="color:#000000">RequestContext</span>.<span style="color:#000000">getCurrentContext</span>();
<span style="color:#000000">HttpServletRequest</span> <span style="color:#000000">request</span> <span style="color:#981a1a">=</span> <span style="color:#000000">requestContext</span>.<span style="color:#000000">getRequest</span>();<span style="color:#aa5500">//获取请求</span>
<span style="color:#000000">HttpServletResponse</span> <span style="color:#000000">response</span> <span style="color:#981a1a">=</span> <span style="color:#000000">requestContext</span>.<span style="color:#000000">getResponse</span>();<span style="color:#aa5500">//获取响应</span>
<span style="color:#008855">String</span> <span style="color:#000000">requestURI</span> <span style="color:#981a1a">=</span> <span style="color:#000000">request</span>.<span style="color:#000000">getRequestURI</span>();<span style="color:#aa5500">//获取请求的URI</span>
<span style="color:#000000">System</span>.<span style="color:#000000">err</span>.<span style="color:#000000">println</span>(<span style="color:#000000">requestURI</span>);
<span style="color:#aa5500">//前置通知可以中断请求的运行</span>
<span style="color:#aa5500">//requestContext.setSendZuulResponse(false);//拦截请求</span>
<span style="color:#aa5500">//拦截之后设置返回结果</span>
<span style="color:#000000">response</span>.<span style="color:#000000">setContentType</span>(<span style="color:#aa1111">"text/html;charset=utf-8"</span>);
<span style="color:#000000">requestContext</span>.<span style="color:#000000">put</span>(<span style="color:#aa1111">"suibian"</span>, <span style="color:#aa1111">"拦截后返回的内容"</span>);</span></span>
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#aa5500">//前置通知可以中断请求的运行</span>
<span style="color:#aa5500">//requestContext.setSendZuulResponse(false);//拦截请求</span></span></span>
<span style="background-color:#f8f8f8"><span style="color:#333333"> <span style="color:#000000">requestContext</span>.<span style="color:#000000">put</span>(<span style="color:#000000">FilterConstants</span>.<span style="color:#000000">SERVICE_ID_KEY</span>, <span style="color:#aa1111">"12itemprovider-eureka-hystrix"</span>);<span style="color:#aa5500">//将当前的请求强制转发到哪个服务</span>
<span style="color:#aa5500">// requestContext.put(FilterConstants.REQUEST_URI_KEY, "/items/item/test1");//强制转发到上面服务中的哪个地址</span></span></span>