激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - springcloud Zuul動態路由的實現

springcloud Zuul動態路由的實現

2021-06-10 14:07下一秒升華 Java教程

這篇文章主要介紹了springcloud Zuul動態路由的實現,詳細的介紹了什么是Zuu及其動態路由的實現,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧

前言

zuul 是netflix 提供的一個開源組件,致力于在云平臺上提供動態路由,監控,彈性,安全等邊緣服務的框架。也有很多公司使用它來作為網關的重要組成部分,碰巧今年公司的架構組決定自研一個網關產品,集動態路由,動態權限,限流配額等功能為一體,為其他部門的項目提供統一的外網調用管理,最終形成產品(這方面阿里其實已經有成熟的網關產品了,但是不太適用于個性化的配置,也沒有集成權限和限流降級)。

不過這里并不想介紹整個網關的架構,而是想著重于討論其中的一個關鍵點,并且也是經常在交流群中聽人說起的:動態路由怎么做?

再闡釋什么是動態路由之前,需要介紹一下架構的設計。

傳統互聯網架構圖

springcloud Zuul動態路由的實現

上圖是沒有網關參與的一個最典型的互聯網架構(本文中統一使用book代表應用實例,即真正提供服務的一個業務系統)

加入eureka的架構圖

springcloud Zuul動態路由的實現

book注冊到eureka注冊中心中,zuul本身也連接著同一個eureka,可以拉取book眾多實例的列表。服務中心的注冊發現一直是值得推崇的一種方式,但是不適用與網關產品。因為我們的網關是面向眾多的其他部門的已有或是異構架構的系統,不應該強求其他系統都使用eureka,這樣是有侵入性的設計。

最終架構圖

springcloud Zuul動態路由的實現

要強調的一點是,gateway最終也會部署多個實例,達到分布式的效果,在架構圖中沒有畫出,請大家自行腦補。

本博客的示例使用最后一章架構圖為例,帶來動態路由的實現方式,會有具體的代碼。

動態路由

動態路由需要達到可持久化配置,動態刷新的效果。如架構圖所示,不僅要能滿足從spring的配置文件properties加載路由信息,還需要從數據庫加載我們的配置。另外一點是,路由信息在容器啟動時就已經加載進入了內存,我們希望配置完成后,實施發布,動態刷新內存中的路由信息,達到不停機維護路由信息的效果。

zuul–helloworlddemo

項目結構

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<groupid>com.sinosoft</groupid>
<artifactid>zuul-gateway-demo</artifactid>
<packaging>pom</packaging>
<version>1.0</version>
 
<parent>
 <groupid>org.springframework.boot</groupid>
 <artifactid>spring-boot-starter-parent</artifactid>
 <version>1.5.2.release</version>
</parent>
 
<modules>
 <module>gateway</module>
 <module>book</module>
</modules>
 
<dependencymanagement>
 <dependencies>
  <dependency>
   <groupid>org.springframework.cloud</groupid>
   <artifactid>spring-cloud-dependencies</artifactid>
   <version>camden.sr6</version>
   <type>pom</type>
   <scope>import</scope>
  </dependency>
 </dependencies>
</dependencymanagement>

tip:springboot-1.5.2對應的springcloud的版本需要使用camden.sr6,一開始想專門寫這個demo時,只替換了springboot的版本1.4.0->1.5.2,結果啟動就報錯了,最后發現是版本不兼容的鍋。

gateway項目:

啟動類:gatewayapplication.java

?
1
2
3
4
5
6
7
8
9
@enablezuulproxy
@springbootapplication
public class gatewayapplication {
 
 public static void main(string[] args) {
  springapplication.run(gatewayapplication.class, args);
 }
 
}

配置:application.properties

?
1
2
3
4
5
6
7
#配置在配置文件中的路由信息
zuul.routes.books.url=http://localhost:8090
zuul.routes.books.path=/books/**
#不使用注冊中心,會帶來侵入性
ribbon.eureka.enabled=false
#網關端口
server.port=8080

book項目:

啟動類:bookapplication.java

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@restcontroller
@springbootapplication
public class bookapplication {
 
 @requestmapping(value = "/available")
 public string available() {
  system.out.println("spring in action");
  return "spring in action";
 }
 
 @requestmapping(value = "/checked-out")
 public string checkedout() {
  return "spring boot in action";
 }
 
 public static void main(string[] args) {
  springapplication.run(bookapplication.class, args);
 }
}

配置類:application.properties

?
1
server.port=8090

測試訪問:http://localhost:8080/books/available

上述demo是一個簡單的靜態路由,簡單看下源碼,zuul是怎么做到轉發,路由的。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
@configuration
@enableconfigurationproperties({ zuulproperties.class })
@conditionalonclass(zuulservlet.class)
@import(serverpropertiesautoconfiguration.class)
public class zuulconfiguration {
 
 @autowired
 //zuul的配置文件,對應了application.properties中的配置信息
 protected zuulproperties zuulproperties;
 
 @autowired
 protected serverproperties server;
 
 @autowired(required = false)
 private errorcontroller errorcontroller;
 
 @bean
 public hasfeatures zuulfeature() {
  return hasfeatures.namedfeature("zuul (simple)", zuulconfiguration.class);
 }
 
 //核心類,路由定位器,最最重要
 @bean
 @conditionalonmissingbean(routelocator.class)
 public routelocator routelocator() {
  //默認配置的實現是simpleroutelocator.class
  return new simpleroutelocator(this.server.getservletprefix(),
    this.zuulproperties);
 }
 
 //zuul的控制器,負責處理鏈路調用
 @bean
 public zuulcontroller zuulcontroller() {
  return new zuulcontroller();
 }
 
 //mvc handlermapping that maps incoming request paths to remote services.
 @bean
 public zuulhandlermapping zuulhandlermapping(routelocator routes) {
  zuulhandlermapping mapping = new zuulhandlermapping(routes, zuulcontroller());
  mapping.seterrorcontroller(this.errorcontroller);
  return mapping;
 }
 
 //注冊了一個路由刷新監聽器,默認實現是zuulrefreshlistener.class,這個是我們動態路由的關鍵
 @bean
 public applicationlistener<applicationevent> zuulrefreshrouteslistener() {
  return new zuulrefreshlistener();
 }
 
 @bean
 @conditionalonmissingbean(name = "zuulservlet")
 public servletregistrationbean zuulservlet() {
  servletregistrationbean servlet = new servletregistrationbean(new zuulservlet(),
    this.zuulproperties.getservletpattern());
  // the whole point of exposing this servlet is to provide a route that doesn't
  // buffer requests.
  servlet.addinitparameter("buffer-requests", "false");
  return servlet;
 }
 
 // pre filters
 
 @bean
 public servletdetectionfilter servletdetectionfilter() {
  return new servletdetectionfilter();
 }
 
 @bean
 public formbodywrapperfilter formbodywrapperfilter() {
  return new formbodywrapperfilter();
 }
 
 @bean
 public debugfilter debugfilter() {
  return new debugfilter();
 }
 
 @bean
 public servlet30wrapperfilter servlet30wrapperfilter() {
  return new servlet30wrapperfilter();
 }
 
 // post filters
 
 @bean
 public sendresponsefilter sendresponsefilter() {
  return new sendresponsefilter();
 }
 
 @bean
 public senderrorfilter senderrorfilter() {
  return new senderrorfilter();
 }
 
 @bean
 public sendforwardfilter sendforwardfilter() {
  return new sendforwardfilter();
 }
 
 @configuration
 protected static class zuulfilterconfiguration {
 
  @autowired
  private map<string, zuulfilter> filters;
 
  @bean
  public zuulfilterinitializer zuulfilterinitializer() {
   return new zuulfilterinitializer(this.filters);
  }
 
 }
 
 //上面提到的路由刷新監聽器
 private static class zuulrefreshlistener
   implements applicationlistener<applicationevent> {
 
  @autowired
  private zuulhandlermapping zuulhandlermapping;
 
  private heartbeatmonitor heartbeatmonitor = new heartbeatmonitor();
 
  @override
  public void onapplicationevent(applicationevent event) {
   if (event instanceof contextrefreshedevent
     || event instanceof refreshscoperefreshedevent
     || event instanceof routesrefreshedevent) {
    //設置為臟,下一次匹配到路徑時,如果發現為臟,則會去刷新路由信息
    this.zuulhandlermapping.setdirty(true);
   }
   else if (event instanceof heartbeatevent) {
    if (this.heartbeatmonitor.update(((heartbeatevent) event).getvalue())) {
     this.zuulhandlermapping.setdirty(true);
    }
   }
  }
 
 }
 
}

我們要解決動態路由的難題,第一步就得理解路由定位器的作用。

springcloud Zuul動態路由的實現

很失望,因為從接口關系來看,spring考慮到了路由刷新的需求,但是默認實現的simpleroutelocator沒有實現refreshableroutelocator接口,看來我們只能借鑒discoveryclientroutelocator去改造simpleroutelocator使其具備刷新能力。

?
1
2
3
public interface refreshableroutelocator extends routelocator {
 void refresh();
}

discoveryclientroutelocator比simpleroutelocator多了兩個功能,第一是從discoveryclient(如eureka)發現路由信息,之前的架構圖已經給大家解釋清楚了,我們不想使用eureka這種侵入式的網關模塊,所以忽略它,第二是實現了refreshableroutelocator接口,能夠實現動態刷新。

對simpleroutelocator.class的源碼加一些注釋,方便大家閱讀:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
public class simpleroutelocator implements routelocator {
 
 //配置文件中的路由信息配置
 private zuulproperties properties;
 //路徑正則配置器,即作用于path:/books/**
 private pathmatcher pathmatcher = new antpathmatcher();
 
 private string dispatcherservletpath = "/";
 private string zuulservletpath;
 
 private atomicreference<map<string, zuulroute>> routes = new atomicreference<>();
 
 public simpleroutelocator(string servletpath, zuulproperties properties) {
  this.properties = properties;
  if (servletpath != null && stringutils.hastext(servletpath)) {
   this.dispatcherservletpath = servletpath;
  }
 
  this.zuulservletpath = properties.getservletpath();
 }
 
 //路由定位器和其他組件的交互,是最終把定位的routes以list的方式提供出去,核心實現
 @override
 public list<route> getroutes() {
  if (this.routes.get() == null) {
   this.routes.set(locateroutes());
  }
  list<route> values = new arraylist<>();
  for (string url : this.routes.get().keyset()) {
   zuulroute route = this.routes.get().get(url);
   string path = route.getpath();
   values.add(getroute(route, path));
  }
  return values;
 }
 
 @override
 public collection<string> getignoredpaths() {
  return this.properties.getignoredpatterns();
 }
 
 //這個方法在網關產品中也很重要,可以根據實際路徑匹配到route來進行業務邏輯的操作,進行一些加工
 @override
 public route getmatchingroute(final string path) {
 
  if (log.isdebugenabled()) {
   log.debug("finding route for path: " + path);
  }
 
  if (this.routes.get() == null) {
   this.routes.set(locateroutes());
  }
 
  if (log.isdebugenabled()) {
   log.debug("servletpath=" + this.dispatcherservletpath);
   log.debug("zuulservletpath=" + this.zuulservletpath);
   log.debug("requestutils.isdispatcherservletrequest()="
     + requestutils.isdispatcherservletrequest());
   log.debug("requestutils.iszuulservletrequest()="
     + requestutils.iszuulservletrequest());
  }
 
  string adjustedpath = adjustpath(path);
 
  zuulroute route = null;
  if (!matchesignoredpatterns(adjustedpath)) {
   for (entry<string, zuulroute> entry : this.routes.get().entryset()) {
    string pattern = entry.getkey();
    log.debug("matching pattern:" + pattern);
    if (this.pathmatcher.match(pattern, adjustedpath)) {
     route = entry.getvalue();
     break;
    }
   }
  }
  if (log.isdebugenabled()) {
   log.debug("route matched=" + route);
  }
 
  return getroute(route, adjustedpath);
 
 }
 
 private route getroute(zuulroute route, string path) {
  if (route == null) {
   return null;
  }
  string targetpath = path;
  string prefix = this.properties.getprefix();
  if (path.startswith(prefix) && this.properties.isstripprefix()) {
   targetpath = path.substring(prefix.length());
  }
  if (route.isstripprefix()) {
   int index = route.getpath().indexof("*") - 1;
   if (index > 0) {
    string routeprefix = route.getpath().substring(0, index);
    targetpath = targetpath.replacefirst(routeprefix, "");
    prefix = prefix + routeprefix;
   }
  }
  boolean retryable = this.properties.getretryable();
  if (route.getretryable() != null) {
   retryable = route.getretryable();
  }
  return new route(route.getid(), targetpath, route.getlocation(), prefix,
    retryable,
    route.iscustomsensitiveheaders() ? route.getsensitiveheaders() : null);
 }
 
 //注意這個類并沒有實現refresh接口,但是卻提供了一個protected級別的方法,旨在讓子類不需要重復維護一個private atomicreference<map<string, zuulroute>> routes = new atomicreference<>();也可以達到刷新的效果
 protected void dorefresh() {
  this.routes.set(locateroutes());
 }
 
 
 //具體就是在這兒定位路由信息的,我們之后從數據庫加載路由信息,主要也是從這兒改寫
 /**
  * compute a map of path pattern to route. the default is just a static map from the
  * {@link zuulproperties}, but subclasses can add dynamic calculations.
  */
 protected map<string, zuulroute> locateroutes() {
  linkedhashmap<string, zuulroute> routesmap = new linkedhashmap<string, zuulroute>();
  for (zuulroute route : this.properties.getroutes().values()) {
   routesmap.put(route.getpath(), route);
  }
  return routesmap;
 }
 
 protected boolean matchesignoredpatterns(string path) {
  for (string pattern : this.properties.getignoredpatterns()) {
   log.debug("matching ignored pattern:" + pattern);
   if (this.pathmatcher.match(pattern, path)) {
    log.debug("path " + path + " matches ignored pattern " + pattern);
    return true;
   }
  }
  return false;
 }
 
 private string adjustpath(final string path) {
  string adjustedpath = path;
 
  if (requestutils.isdispatcherservletrequest()
    && stringutils.hastext(this.dispatcherservletpath)) {
   if (!this.dispatcherservletpath.equals("/")) {
    adjustedpath = path.substring(this.dispatcherservletpath.length());
    log.debug("stripped dispatcherservletpath");
   }
  }
  else if (requestutils.iszuulservletrequest()) {
   if (stringutils.hastext(this.zuulservletpath)
     && !this.zuulservletpath.equals("/")) {
    adjustedpath = path.substring(this.zuulservletpath.length());
    log.debug("stripped zuulservletpath");
   }
  }
  else {
   // do nothing
  }
 
  log.debug("adjustedpath=" + path);
  return adjustedpath;
 }
 
}

重寫過后的自定義路由定位器如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
public class customroutelocator extends simpleroutelocator implements refreshableroutelocator{
 
 public final static logger logger = loggerfactory.getlogger(customroutelocator.class);
 
 private jdbctemplate jdbctemplate;
 
 private zuulproperties properties;
 
 public void setjdbctemplate(jdbctemplate jdbctemplate){
  this.jdbctemplate = jdbctemplate;
 }
 
 public customroutelocator(string servletpath, zuulproperties properties) {
  super(servletpath, properties);
  this.properties = properties;
  logger.info("servletpath:{}",servletpath);
 }
 
 //父類已經提供了這個方法,這里寫出來只是為了說明這一個方法很重要!!!
// @override
// protected void dorefresh() {
//  super.dorefresh();
// }
 
 
 @override
 public void refresh() {
  dorefresh();
 }
 
 @override
 protected map<string, zuulroute> locateroutes() {
  linkedhashmap<string, zuulroute> routesmap = new linkedhashmap<string, zuulroute>();
  //從application.properties中加載路由信息
  routesmap.putall(super.locateroutes());
  //從db中加載路由信息
  routesmap.putall(locateroutesfromdb());
  //優化一下配置
  linkedhashmap<string, zuulroute> values = new linkedhashmap<>();
  for (map.entry<string, zuulroute> entry : routesmap.entryset()) {
   string path = entry.getkey();
   // prepend with slash if not already present.
   if (!path.startswith("/")) {
    path = "/" + path;
   }
   if (stringutils.hastext(this.properties.getprefix())) {
    path = this.properties.getprefix() + path;
    if (!path.startswith("/")) {
     path = "/" + path;
    }
   }
   values.put(path, entry.getvalue());
  }
  return values;
 }
 
 private map<string, zuulroute> locateroutesfromdb(){
  map<string, zuulroute> routes = new linkedhashmap<>();
  list<zuulroutevo> results = jdbctemplate.query("select * from gateway_api_define where enabled = true ",new beanpropertyrowmapper<>(zuulroutevo.class));
  for (zuulroutevo result : results) {
   if(org.apache.commons.lang3.stringutils.isblank(result.getpath()) || org.apache.commons.lang3.stringutils.isblank(result.geturl()) ){
    continue;
   }
   zuulroute zuulroute = new zuulroute();
   try {
    org.springframework.beans.beanutils.copyproperties(result,zuulroute);
   } catch (exception e) {
    logger.error("=============load zuul route info from db with error==============",e);
   }
   routes.put(zuulroute.getpath(),zuulroute);
  }
  return routes;
 }
 
 public static class zuulroutevo {
 
  /**
   * the id of the route (the same as its map key by default).
   */
  private string id;
 
  /**
   * the path (pattern) for the route, e.g. /foo/**.
   */
  private string path;
 
  /**
   * the service id (if any) to map to this route. you can specify a physical url or
   * a service, but not both.
   */
  private string serviceid;
 
  /**
   * a full physical url to map to the route. an alternative is to use a service id
   * and service discovery to find the physical address.
   */
  private string url;
 
  /**
   * flag to determine whether the prefix for this route (the path, minus pattern
   * patcher) should be stripped before forwarding.
   */
  private boolean stripprefix = true;
 
  /**
   * flag to indicate that this route should be retryable (if supported). generally
   * retry requires a service id and ribbon.
   */
  private boolean retryable;
 
  private boolean enabled;
 
  public string getid() {
   return id;
  }
 
  public void setid(string id) {
   this.id = id;
  }
 
  public string getpath() {
   return path;
  }
 
  public void setpath(string path) {
   this.path = path;
  }
 
  public string getserviceid() {
   return serviceid;
  }
 
  public void setserviceid(string serviceid) {
   this.serviceid = serviceid;
  }
 
  public string geturl() {
   return url;
  }
 
  public void seturl(string url) {
   this.url = url;
  }
 
  public boolean isstripprefix() {
   return stripprefix;
  }
 
  public void setstripprefix(boolean stripprefix) {
   this.stripprefix = stripprefix;
  }
 
  public boolean getretryable() {
   return retryable;
  }
 
  public void setretryable(boolean retryable) {
   this.retryable = retryable;
  }
 
  public boolean getenabled() {
   return enabled;
  }
 
  public void setenabled(boolean enabled) {
   this.enabled = enabled;
  }
 }
}

配置這個自定義的路由定位器:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@configuration
public class customzuulconfig {
 
 @autowired
 zuulproperties zuulproperties;
 @autowired
 serverproperties server;
 @autowired
 jdbctemplate jdbctemplate;
 
 @bean
 public customroutelocator routelocator() {
  customroutelocator routelocator = new customroutelocator(this.server.getservletprefix(), this.zuulproperties);
  routelocator.setjdbctemplate(jdbctemplate);
  return routelocator;
 }
 
}

現在容器啟動時,就可以從數據庫和配置文件中一起加載路由信息了,離動態路由還差最后一步,就是實時刷新,前面已經說過了,默認的zuulconfigure已經配置了事件監聽器,我們只需要發送一個事件就可以實現刷新了。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class refreshrouteservice {
 
 @autowired
 applicationeventpublisher publisher;
 
 @autowired
 routelocator routelocator;
 
 public void refreshroute() {
  routesrefreshedevent routesrefreshedevent = new routesrefreshedevent(routelocator);
  publisher.publishevent(routesrefreshedevent);
 }
 
}

具體的刷新流程其實就是從數據庫重新加載了一遍,有人可能會問,為什么不自己是手動重新加載locator.dorefresh?非要用事件去刷新。這牽扯到內部的zuul內部組件的工作流程,不僅僅是locator本身的一個變量,具體想要了解的還得去看源碼。

到這兒我們就實現了動態路由了,所以的實例代碼和建表語句我會放到github上,下載的時候記得給我star qaq ?。?!

鏈接:https://github.com/lexburner/zuul-gateway-demo

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。

原文鏈接:https://blog.csdn.net/u013815546/article/details/68944039

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 美女啪网站| 一级黄色免费观看 | 久久精品一区二区三区国产主播 | 亚洲一区二区 | 中文字幕国产一区 | 国产精品久久久久久模特 | 91久久一区| 久久久免费电影 | 欧美一级特黄a | 黄色片网站免费看 | 欧美综合日韩 | 国产精品视频免费在线观看 | a视频在线播放 | 欧美a∨一区二区三区久久黄 | 亚洲欧美成aⅴ人在线观看 免费看欧美黑人毛片 | 成年免费视频黄网站在线观看 | 欧美国产精品久久 | 国产一区二区三区四区五区在线 | 国产亚洲精品久久久久久久久久 | 精品国产一区二区三区久久久狼牙 | 国产午夜精品理论片a级探花 | 欧美精品在线视频观看 | 成人国产精品一区二区毛片在线 | 91小视频在线观看免费版高清 | 日韩欧美电影一区二区三区 | 毛片免费一区二区三区 | 国产精品片一区二区三区 | 免费网站看v片在线a | 综合色视频 | av在线高清观看 | 久色成人网 | 欧美 日韩 国产 成人 | 日本免费靠逼视频 | 亚洲综合无码一区二区 | 99激情| 国产一区二区三区在线免费观看 | 免费男女视频 | 永久av在线免费观看 | 久久不射电影网 | 欧美日本91精品久久久久 | 黄色小视频在线免费看 |