SpringBoot2.1+SpringCloud(二)——网关路由(Zuul)

一、版本说明

SpringBoot:2.1.6.RELEASE

SpringCloud:Greenwich.RELEASE

二、功能说明

上一章节我们介绍了“注册中心”,下面我们将开始配置网关路由,个人理解网关路由是对“注册中心”的所有组件进行分发调用,在这里可以进行权限的验证、访问的分流与限流等操作;

Zuul大部分功能都是通过过滤器来实现的。Zuul中定义了四种标准过滤器类型,这些过滤器类型对应于请求的典型生命周期。

(1) PRE:这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。

(2) ROUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用Apache HttpClient或Netfilx Ribbon请求微服务。

(3) POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。

(4) ERROR:在其他阶段发生错误时执行该过滤器。

三、Maven主要配置

 1 <modelVersion>4.0.0</modelVersion>
 2     <parent>
 3         <groupId>org.springframework.boot</groupId>
 4         <artifactId>spring-boot-starter-parent</artifactId>
 5         <version>2.1.6.RELEASE</version>
 6         <relativePath/> <!-- lookup parent from repository -->
 7     </parent>
 8     <groupId>【项目自定义】</groupId>
 9     <artifactId>【项目自定义】</artifactId>
10     <version>【版本自定义】</version>
11     <name>【名称自定义】</name>
12     <packaging>jar</packaging>
13     <description>路由控制中心——独立运行的服务</description>
14 
15     <properties>
16         <java.version>1.8</java.version>
17     </properties>
18 
19     <dependencies>
20         <!--eureka客户端-->
21         <dependency>
22             <groupId>org.springframework.cloud</groupId>
23             <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
24         </dependency>
25         <!--流量限流-->
26         <dependency>
27             <groupId>com.marcosbarbero.cloud</groupId>
28             <artifactId>spring-cloud-zuul-ratelimit</artifactId>
29             <version>1.5.0.RELEASE</version>
30         </dependency>
31         <!--网关路由-->
32         <dependency>
33             <groupId>org.springframework.cloud</groupId>
34             <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
35         </dependency>
36         <dependency>
37             <groupId>net.sf.json-lib</groupId>
38             <artifactId>json-lib</artifactId>
39             <version>2.4</version>
40             <classifier>jdk15</classifier>
41         </dependency>
42         <dependency>
43             <groupId>org.projectlombok</groupId>
44             <artifactId>lombok</artifactId>
45             <optional>true</optional>
46         </dependency>
47     </dependencies>
48 
49     <!--spring cloud版本-->
50     <dependencyManagement>
51         <dependencies>
52             <dependency>
53                 <groupId>org.springframework.cloud</groupId>
54                 <artifactId>spring-cloud-dependencies</artifactId>
55                 <version>Greenwich.RELEASE</version>
56                 <type>pom</type>
57                 <scope>import</scope>
58             </dependency>
59         </dependencies>
60     </dependencyManagement>
61 
62     <!-- 打包spring boot应用 -->
63     <build>
64         <finalName>gateway-center-independent-service</finalName>
65         <plugins>
66             <plugin>
67                 <groupId>org.springframework.boot</groupId>
68                 <artifactId>spring-boot-maven-plugin</artifactId>
69                 <configuration>
70                     <fork>true</fork>
71                 </configuration>
72             </plugin>
73         </plugins>
74         <resources>
75             <resource>
76                 <directory>src/main/java</directory>
77                 <includes>
78                     <include>**/*.xml</include>
79                 </includes>
80                 <filtering>true</filtering>
81             </resource>
82         </resources>
83     </build>    

四、配置文件(application.properties)

server.port=【服务端口号】

#服务名
spring.application.name=【服务名称】
#服务介绍
xinyan.server.message=这个是zuul组件
#远程SpringCloud配置
#eureka主机名,会在控制页面中显示
eureka.instance.hostname=【IP】
#eureka服务器页面中status的请求路径
eureka.instance.status-page-url=http://【IP】:【服务端口号】/index
eureka.instance.preferIpAddress=false
#eureka.instance.instance-id=${spring.application.name}:${spring.application.instance_id:${server.port}}
eureka.instance.appname=zuul-gateway
#eureka注册中心服务器地址
eureka.client.serviceUrl.defaultZone=http://【注册中心设置的用户名】:【注册中心设置的密码】@【注册中心IP】:【注册中心端口号】/eureka
#eureka.instance.instance-id=${spring.application.name}:${spring.application.instance_id:${server.port}}
eureka.instance.instance-id=${eureka.instance.hostname}:${server.port}
#新版配置,否则后面dashboard无法找到hystrix.stream
management.endpoints.web.exposure.include=*
ribbon.eureka.enabled=true

eureka.client.registerWithEureka=true
eureka.client.fetchRegistry=true

#忽略所有未配置的service,每一个微服务的路由配置都需要配置
zuul.ignored-services="*"
#需要忽略的头部信息,不在传播到其他服务
zuul.sensitive-headers=Access-Control-Allow-Origin
zuul.ignored-headers=Access-Control-Allow-Origin,H-APP-Id,Token,APPToken
zuul.routes.xinyan-test2-demo.path=/xinyan-test2-demo/**
zuul.routes.xinyan-test2-demo.serviceId=xinyan-test2-demo
zuul.routes.xinyan-test2-demo.sensitiveHeaders=true
zuul.routes.xinyan-test1-demo.path=/xinyan-test1-demo/**
zuul.routes.xinyan-test1-demo.serviceId=xinyan-test1-demo
zuul.routes.xinyan-test1-demo.sensitiveHeaders=true

zuul.ratelimit.enabled=true
zuul.ratelimit.repository=IN_MEMORY
zuul.ratelimit.behind-proxy=true

#每个刷新时间窗口对应的请求数量限制
zuul.ratelimit.policies.xinyan-test2-demo.limit=1
#每个刷新时间窗口对应的请求时间限制(秒)
zuul.ratelimit.policies.xinyan-test2-demo.quota=1
#刷新时间窗口的时间,默认值 (秒)
zuul.ratelimit.policies.【需要限制的组件名称】.refresh-interval=3
zuul.ratelimit.policies.【需要限制的组件名称】.type[0]=url
zuul.ratelimit.policies.【需要限制的组件名称】.type[1]=origin
zuul.ratelimit.policies.【需要限制的组件名称】.type[2]=user

这里特别说明:这里有个天坑,所有注册到“注册中心”的组件名称不能包含下划线,否则无法识别;

五、代码部分

5.1、重写异常交互

在调用网关发生异常或者限流异常等返回的信息不是非常友好,所以这里要对异常的交互Controller进行重写;

 1 /**
 2  * 类描述:  重写系统/error接口
 3  *
 4  * @author xxsd
 5  * @version 1.0.0
 6  * @date 2019/6/20 0020 下午 8:56
 7  */
 8 @RestController
 9 @Slf4j
10 public class ErrorController extends AbstractErrorController {
11 
12     public ErrorController(ErrorAttributes errorAttributes) {
13         super(new GlobalExceptionHandler());
14     }
15 
16     @Override
17     public String getErrorPath() {
18         return null;
19     }
20 
21     /**
22      * 属性描述:路径
23      *
24      * @date : 2019/6/20 0020 下午 9:09
25      */
26     private static final String ERROR_PATH = "/error";
27     /**
28      * 属性描述:正常参数
29      *
30      * @date : 2019/6/20 0020 下午 9:09
31      */
32     private static final int OK = 200;
33 //    private static final String ERROR_MESSAGE = "系统内部错误,请联系管理员!";
34 
35     @RequestMapping(ERROR_PATH)
36     public void exceptionHandler(HttpServletRequest request, HttpServletResponse response) {
37         // 返回成功状态
38         response.setStatus(OK);
39 
40         Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(request, false));
41         int status = Integer.valueOf(model.get("zuul_code").toString());
42         String message = (String) model.get("zuul_manageMessage");
43 
44         // 判断请求类型
45         String header = request.getHeader("X-Requested-With");
46         //是否是Ajax提交
47         boolean isAjax = "XMLHttpRequest".equalsIgnoreCase(header);
48 
49         Map<String, Object> error = new HashMap<>(2);
50         error.put("code", status);
51         error.put("manageMessage", message);
52 
53         if (isAjax) {
54             log.info("Ajax交互");
55         } else {
56             log.info("非Ajax交互");
57         }
58         writeJson(response, error);
59     }
60 
61     /**
62     * 功能描述:以Json的格式输出信息
63     * @author : xxsd
64     * @date : 2019/6/21 0021 下午 12:07
65     */
66     private void writeJson(HttpServletResponse response, Map<String, Object> error) {
67         try {
68             response.setContentType("text/html;charset=UTF-8");
69             response.getWriter().write(JSONObject.fromObject(error).toString());
70         } catch (IOException e) {
71             e.printStackTrace();
72         }
73     }
74 
75 }

 

全局异常拦截器:

 1 /**
 2  * 类描述:  全局异常拦截器
 3  *
 4  * @author xxsd
 5  * @version 1.0.0
 6  * @date 2019/6/20 0020 下午 5:53
 7  */
 8 public class GlobalExceptionHandler extends DefaultErrorAttributes {
 9 
10     @Override
11     public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
12         Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, includeStackTrace);
13 
14         try {
15             final Integer status = (Integer) errorAttributes.get("status");
16             //TODO 下面还可以直接添加系统级别的异常码及说明
17             ErrorCodeEntity errorCodeEntity = ErrorCodeEntity.valueOf("Code_" + status);
18             assert errorCodeEntity != null;
19             errorAttributes.put("zuul_code", errorCodeEntity.giveCodeNumber());
20             errorAttributes.put("zuul_manageMessage", errorCodeEntity.giveCodeMessage());
21         } catch (Exception e) {
22             errorAttributes.put("zuul_code", 0);
23             errorAttributes.put("zuul_manageMessage", "未知状态码");
24         }
25         return errorAttributes;
26     }
27 }

 

5.2、定义Zuul过滤器抽象基类

  1 /**
  2  * 类描述:  自定义过滤器
  3  *
  4  * @author xxsd
  5  * @version 1.0.0
  6  * @date 2019/6/19 0019 下午 3:59
  7  */
  8 @Slf4j
  9 public abstract class AbstractZuulFilter extends ZuulFilter {
 10     protected RequestContext context;
 11 
 12     @Override
 13     public boolean shouldFilter() {
 14         RequestContext ctx = RequestContext.getCurrentContext();
 15         return (boolean) (ctx.getOrDefault(ContantValue.NEXT_FILTER, true));
 16     }
 17 
 18     @Override
 19     public Object run() {
 20         context = RequestContext.getCurrentContext();
 22         return doRun();
 23     }
 24 
 25     public abstract Object doRun();
 26 
 27     /**
 28      * 功能描述:异常信息输出
 29      *
 30      * @param code    错误编码
 31      * @param message 错误信息
 32      * @return : java.lang.Object
 33      * @author : xxsd
 34      * @date : 2019/6/21 0021 上午 11:35
 35      */
 36     protected Object fail(Integer code, String message) {
 37         context.set(ContantValue.NEXT_FILTER, false);
 38         context.setSendZuulResponse(false);
 39         context.getResponse().setContentType("text/html;charset=UTF-8");
 40         context.setResponseStatusCode(code);
 41         context.setResponseBody(String.format("{\"result\":\"%s!\"}", message));
 42         return null;
 43     }
 44 
 45     /**
 46     * 功能描述:成功输出
 47     * @author : xxsd
 48     * @date : 2019/6/21 0021 上午 11:36
 49     */
 50     protected Object success() {
 51         context.set(ContantValue.NEXT_FILTER, true);
 52         return null;
 53     }
 54 
 55     /**
 56     * 功能描述:获取提交的数据内容及类型
 57     * @author : xxsd
 58     * @date : 2019/6/27 0027 下午 7:13
 59     */
 60     protected Map<String, Object> giveSubDataOrType(){
 61 
 62         Map<String, Object> returnMap = new HashMap<>(2);
 63 
 64         HttpServletRequest req = RequestContext.getCurrentContext().getRequest();
 65         System.err.println("REQUEST:: " + req.getScheme() + " " + req.getRemoteAddr() + ":" + req.getRemotePort());
 66         System.err.println("REQUEST:: " + req.getScheme() + " " + req.getRemoteAddr() + ":" + req.getRemotePort());
 67         StringBuilder params = new StringBuilder("?");
 68         Enumeration<String> names = req.getParameterNames();
 69         if(ContantValue.SUP_TYPE.equals(req.getMethod())) {
 70             while (names.hasMoreElements()) {
 71                 String name = names.nextElement();
 72                 params.append(name);
 73                 params.append("=");
 74                 params.append(req.getParameter(name));
 75                 params.append("&");
 76             }
 77         }
 78 
 79         if (params.length() > 0) {
 80             params.delete(params.length()-1, params.length());
 81         }
 82 
 83         returnMap.put("method", req.getMethod());
 84 
 85         System.err.println("REQUEST:: > " + req.getMethod() + " " + req.getRequestURI() + params + " " + req.getProtocol());
 86 
 87         Enumeration<String> headers = req.getHeaderNames();
 88 
 89         while (headers.hasMoreElements()) {
 90             String name = headers.nextElement();
 91             String value = req.getHeader(name);
 92             System.err.println("REQUEST:: > " + name + ":" + value);
 93         }
 94 
 95         final RequestContext ctx = RequestContext.getCurrentContext();
 96         if (!ctx.isChunkedRequestBody()) {
 97             ServletInputStream inp;
 98             try {
 99                 inp = ctx.getRequest().getInputStream();
100                 String body;
101                 if (inp != null) {
102                     body = IOUtils.toString(inp);
103                     System.err.println("REQUEST:: > " + body);
104                     returnMap.put("inputStream", JSONObject.fromObject(body));
105                 }
106             } catch (IOException e) {
107                 e.printStackTrace();
108             }
109         }
110 
111         return returnMap;
112     }
113 }

 

5.3、自定义Zuul异常拦截器

 1 /**
 2 * 类描述: 自定义异常拦截器
 3 * @author : xxsd
 4 * @date : 2019/6/21 0021 上午 11:38
 5 */
 6 @Component
 7 public class ErrorHandlerFilter extends AbstractZuulFilter {
 8     @Override
 9     public String filterType() {
10         return ERROR_TYPE;
11     }
12 
13     @Override
14     public int filterOrder() {
15         return SEND_RESPONSE_FILTER_ORDER + 1;
16     }
17 
18     @Override
19     public Object doRun() {
20         return success();
21     }
22 
23 }

 

5.4、自定义请求PRE拦截器

 1 /**
 2  * 类描述:  自定义PRE过滤器
 3  *
 4  * @author xxsd
 5  * @version 1.0.0
 6  * @date 2019/6/19 0019 下午 4:23
 7  */
 8 @Slf4j
 9 @Component
10 public class PreHandlerFilter extends AbstractZuulFilter {
11 
12     @Override
13     public String filterType() {
14         return PRE_TYPE;
15     }
16 
17     /**
18      * 每秒允许处理的量是50
19      */
20     RateLimiter rateLimiter = RateLimiter.create(50);
21 
22     @Override
23     public int filterOrder() {
24         return FilterOrder.RATE_LIMITER_ORDER;
25     }
26 
27     private static String INPUT_STREAM = "inputStream";
28 
29     @Override
30     public Object doRun() {
31         HttpServletRequest request = context.getRequest();
32         String url = request.getRequestURI();
33         if (rateLimiter.tryAcquire()) {
34             Map<String, Object> stringObjectMap = giveSubDataOrType();
35 
36             System.out.println(SystemAttribute.getDebugType());
37             System.out.println(SystemAttribute.getSsoCodeId());
38             if (SystemAttribute.getDebugType()) {
39                 System.out.println(stringObjectMap.get("method").toString());
40                 System.out.println(stringObjectMap.get("inputStream").toString());
41             }
42 
43             if (stringObjectMap.get(INPUT_STREAM) != null && !"".equals(stringObjectMap.get(INPUT_STREAM).toString().trim())) {
44                 if (SystemAttribute.getSsoCodeId() != null && !"".equals(SystemAttribute.getSsoCodeId())) {
45                     //TODO 这里开始验证逻辑
46                 }
47             }
48 
49             return success();
50         } else {
51             log.info("rate limit:{}", url);
52             return fail(401, String.format("rate limit:{}", url));
53         }
54     }
55 }

 

5.4、启动入口

/**
* 类描述: 路由控制中心启动入口
* @author : xxsd
* @date : 2019/6/27 0027 下午 6:19
*/
@SpringBootApplication
@EnableDiscoveryClient
@EnableZuulProxy
public class GatewayCenterIndependentServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(GatewayCenterIndependentServiceApplication.class, args);
    }

}

 

六、跨域处理

目前开发模式已经从原来的“一锅端”发展到现在的前后端分离,那么很多人都在问我跨域如何处理,目前我使用的方式没有发现有其他的问题出现,现在将这个代码发出来,大家一起看看;

 1 /**
 2  * 类描述:  跨域配置
 3  *
 4  * @author xxsd
 5  * @version 1.0.0
 6  * @date 2019/6/19 0019 下午 5:08
 7  */
 8 @Configuration
 9 public class CorsConfig {
10     @Bean
11     public FilterRegistrationBean corsFilter() {
12         final ResourceHttpRequestHandler resourceHttpRequestHandler = new ResourceHttpRequestHandler();
13         final CorsConfiguration corsConfiguration = new CorsConfiguration();
14         corsConfiguration.setAllowCredentials(true);
15         corsConfiguration.addAllowedOrigin("*");
16         corsConfiguration.addAllowedHeader("*");
17         corsConfiguration.addAllowedMethod("*");
18         //corsConfiguration.addExposedHeader("X-forwared-port, X-forwarded-host");
19         resourceHttpRequestHandler.setCorsConfiguration(corsConfiguration);
20         FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(resourceHttpRequestHandler));
21         bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
22         return bean;
23     }
24 }

 

七、项目开源地址

目前项目正在进行大规模调整,后期再进行开放;

 

posted @ 2019-07-03 15:29  xxsd  阅读(2491)  评论(0编辑  收藏  举报