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 }

 

七、項目開源地址

目前項目正在進行大規模調整,后期再進行開放;

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM