您的位置:首页 > 编程语言 > Java开发

SpringCloud系列之API网关(Gateway)服务Zuul

2020-08-06 17:41 906 查看
### 1、什么是API网关 API网关是所有请求的入口,承载了所有的流量,API Gateway是一个门户一样,也可以说是进入系统的唯一节点。这跟面向对象设计模式中的Facet模式很像。API Gateway封装内部系统的架构,并且提供API给各个客户端。它还可能有其他功能,如授权、监控、负载均衡、缓存、请求分片和管理、静态响应处理等 API Gateway负责请求转发、合成和协议转换。所有来自客户端的请求都要先经过API Gateway,然后路由这些请求到对应的微服务。API Gateway将经常通过调用多个微服务来处理一个请求以及聚合多个服务的结果。它可以在web协议与内部使用的非Web友好型协议间进行转换,如 HTTP协议、WebSocket协议。 画图表示,没有网关的情况,客户端的请求会直接落到后端的各个服务中,无法集中统一管理。 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200806145035456.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ0MjczOTE=,size_16,color_FFFFFF,t_70) 画图表示,有网关的情况,所有的请求都先经过网关,然后进行分发到对应服务 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200806144807748.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ0MjczOTE=,size_16,color_FFFFFF,t_70) ### 2、API网关的重要性 API网关在微服务项目中是很重要的,网关提供一个统一的管理,服务间的调度变得有序 引用nginx官方的一篇优质博客,[https://www.nginx.com/blog/building-microservices-using-an-api-gateway/](https://www.nginx.com/blog/building-microservices-using-an-api-gateway/),例子介绍了一个庞杂的电商系统,按照微服务理论进行设计,有如下各种服务: * 购物车服务:购物车中的物品数量 * 订单服务:订单历史记录 * 目录服务:基本产品信息,例如其名称,图像和价格 * 审核服务:客户审核 * 库存服务:库存不足警告 * 运送服务:运送选项,期限和费用与运送提供商的API分开提取 * 推荐服务:建议项目 在不使用网关的情况,客户端直接调用各服务: ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200805161910124.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ0MjczOTE=,size_16,color_FFFFFF,t_70) 理想情况,各服务调用是可以正常使用的,但是随着业务拓展,服务之间的调用越来越复杂,到时候系统就会变成如图: ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200806160707438.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ0MjczOTE=,size_16,color_FFFFFF,t_70) 如果没有一个统一的管理,肯定是不合理的,所以可以引入网关,作为一个统一的门户,如图: ![在这里插入图片描述](https://img-blog.csdnimg.cn/2020080516193558.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ0MjczOTE=,size_16,color_FFFFFF,t_70) ### 3、API Gateway的作用 ok,简单介绍网关之后,要说说网关的作用,在Spring cloud官网也有过归纳: ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200806163447995.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ0MjczOTE=,size_16,color_FFFFFF,t_70) 当然,我们可以自己挑几个重要的介绍 * 动态路由 网关可以做路由转发,假如服务信息变了,只要改网关配置既可,所以说网关有动态路由(Dynamic Routing)的作用,如图: ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200806144807748.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ0MjczOTE=,size_16,color_FFFFFF,t_70) * 请求监控 请求监控可以对整个系统的请求进行监控,详细地记录请求响应日志,如图,可以将日志丢到消息队列,如果没有使用网关的话,记录请求信息需要在各个服务中去做 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200806164032671.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ0MjczOTE=,size_16,color_FFFFFF,t_70) * 认证鉴权 认证鉴权可以对每一个访问请求做认证,拒绝非法请求,保护后端的服务,不需要每个服务都做鉴权,在项目中经常有加上OAuth2.0、JWT,Spring Security进行权限校验 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200806144807748.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ0MjczOTE=,size_16,color_FFFFFF,t_70) * 压力测试 有网关的系统,如果要要对某个服务进行压力测试,可以如图所示,改下网关配置既可,测试请求路由到测试服务,测试服务会有单独的测试数据库,这样测试的请求就不会影响到正式的服务和数据库 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200806165305643.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ0MjczOTE=,size_16,color_FFFFFF,t_70) ### 4、什么是Netflix Zuul? Netflix Zuul是Netflix公司的产品,是一款API网关中间件。Zuul是一个基于 JVM 路由和服务端的负载均衡器。提供了路由、监控、弹性、安全等服务。Zuul 能够与 Eureka、Ribbon、Hystrix 等组件配合使用,提供统一的API网关处理 ### 5、Netflix Zuul工作原理 参考[Zuul官网wiki](https://github.com/Netflix/zuul/wiki/How-it-Works),Zuul的核心如图其实就是过滤器,zuul基于Servlet实现。当一个请求进来时,会先进入 pre 过滤器,在 pre 过滤器执行完后,接着就到了 routing 过滤器中,开始路由到具体的服务中,错误的情况会被错误过滤器拦截 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200806145302914.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ0MjczOTE=,size_16,color_FFFFFF,t_70) * 过滤器类型: * 前置过滤器(PRE FILTER):在路由过滤器之前执行。功能可以包括请求身份验证,选择原始服务器以及记录调试信息。 * 路由过滤器(ROUTE FILTER):处理将请求路由到源的过程。这是使用Apache HttpClient或Netflix Ribbon构建和发送原始HTTP请求的地方。 * 后置过滤器(POST FILTER):在将请求路由过滤器之后执行。功能可以包括向响应中添加标准HTTP标头,收集统计信息和指标以及将响应从源流传输到客户端。 * 错误过滤器(ERR FILTER):在其他阶段之一发生错误时就会调用到错误过滤器 ### 6、Zuul实验环境准备 环境准备: * JDK 1.8 * SpringBoot2.2.3 * SpringCloud(Hoxton.SR6) * Maven 3.2+ * 开发工具 * IntelliJ IDEA * smartGit 创建一个SpringBoot Initialize项目,详情可以参考我之前博客:[SpringBoot系列之快速创建项目教程](https://blog.csdn.net/u014427391/article/details/102870300) ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200805160934459.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ0MjczOTE=,size_16,color_FFFFFF,t_70) ![在这里插入图片描述](https://img-blog.csdnimg.cn/2020080516100374.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ0MjczOTE=,size_16,color_FFFFFF,t_70) maven配置: ```xml org.springframework.cloud spring-cloud-starter-netflix-eureka-client org.springframework.cloud spring-cloud-starter-netflix-zuul ``` 本博客的是基于`spring-cloud-starter-netflix-eureka-client`进行试验,试验前要运行eureka服务端,eureka服务提供者,代码请参考[上一章博客](https://smilenicky.blog.csdn.net/article/details/107567035) 项目创建成功后,先在启动类加上`@EnableZuulProxy`: ```java import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.zuul.EnableZuulProxy; @SpringBootApplication @EnableZuulProxy public class SpringcloudZuulApplication { public static void main(String[] args) { SpringApplication.run(SpringcloudZuulApplication.class, args); } } ``` ### 7、eureka、zuul配置 eureka客户端配置: ```yaml server: port: 8082 # 指定application name,这个是微服务注册的serviceId spring: application: name: zuul-api-gateway eureka: client: # 服务注册中心url service-url: defaultZone: http://localhost:8761/eureka/ # 网关服务注册、发现都开放,所以 register-with-eureka、fetch-registry都是true register-with-eureka: true fetch-registry: true instance: status-page-url-path: http://localhost:8761/actuator/info health-check-url-path: http://localhost:8761/actuator/health prefer-ip-address: true instance-id: zuul-api-gateway8082 ``` Zuul 配置路由规则: ```yaml zuul: routes: provider: # 路由标识,可以自己定义 service-id: eureka-service-provider # 服务id(必须配置) path: /provider/** # 映射的路径,一般和routes.provider一致 url: http://localhost:8083 # 路由到的url,可以不配置 ``` Zuul配置访问前缀:访问时候需要加上前缀,eg:http://localhost:8082/api-gateway/provider/api/users/mojombo ```yaml zuul: # 配置前缀 prefix: /api-gateway ``` Zuul配置Header过滤: ```yaml zuul: # 配置过滤敏感的请求头信息,设置为空就不会过滤 sensitive-headers: Cookie,Set-Cookie,Authorization ``` Zuul配置重定向添加Host: ```yaml zuul: # 重定向会添加host请求头 add-proxy-headers: true ``` Zuul超时设置: ```yaml zuul: host: # 配置连接超时时间 connect-timeout-millis: 15000 # socker发送超时时间 socket-timeout-millis: 60000 ``` zuul所有配置参考,详情参考[官网](https://docs.spring.io/spring-cloud-netflix/docs/2.2.x-SNAPSHOT/reference/html/#router-and-filter-zuul): ```yaml zuul: # 配置前缀 prefix: /api-gateway routes: provider: # 路由标识,可以自己定义 service-id: eureka-service-provider # 服务id path: /provider/** # 映射的路径,一般和routes.provider一致 url: http://localhost:8083 # 路由到的url host: # 配置连接超时时间 connect-timeout-millis: 15000 # socker发送超时时间 socket-timeout-millis: 60000 # 请求url编码 decode-url: true # 查询字符串编码 force-original-query-string-encoding: false # 配置过滤敏感的请求头信息,设置为空就不会过滤 sensitive-headers: Cookie,Set-Cookie,Authorization # 重定向会添加host请求头 add-proxy-headers: true ``` 访问:http://localhost:8082/api-gateway/provider/api/users/mojombo,要加上前缀,配置的path 可能遇到的错误,504错误: ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200805171352774.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ0MjczOTE=,size_16,color_FFFFFF,t_70) ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200805171338864.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ0MjczOTE=,size_16,color_FFFFFF,t_70) 经过排查,需要加上超时设置,因为调用服务超时,导致504错误 ```yaml zuul: host: connect-timeout-millis: 15000 socket-timeout-millis: 60000 ``` 加上配置,调用服务成功 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200805171618603.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ0MjczOTE=,size_16,color_FFFFFF,t_70) ### 8、Zuul自定义过滤器 在前面的介绍中,已经介绍了几种过滤器,现在自定义实现这四种过滤器 ps:spring cloud官网也提供了zuul过滤器的例子,详情可以去github查看:[https://github.com/spring-cloud-samples/sample-zuul-filters](https://github.com/spring-chttps://github.com/spring-cloud-samples/sample-zuul-filters) 项目结构: ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200806172424585.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ0MjczOTE=,size_16,color_FFFFFF,t_70) ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200806172624203.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ0MjczOTE=,size_16,color_FFFFFF,t_70) 过滤器类型参考org.springframework.cloud.netflix.zuul.filters.supportFilterConstants.java: ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200806172517905.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ0MjczOTE=,size_16,color_FFFFFF,t_70) 实现一个前置过滤器:拦截请求,必须带token过来,不然抛出提示信息等等 ```java package com.example.springcloud.zuul.web.filter; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import lombok.extern.slf4j.Slf4j; import org.apache.http.HttpHeaders; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import javax.servlet.http.HttpServletRequest; import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.FORWARD_TO_KEY; import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE; import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.SERVICE_ID_KEY; /** *
*  API网关预过滤器
*
* *
* @author mazq
* 修改记录
*    修改后版本:     修改人:  修改日期: 2020/08/05 18:08  修改内容:
*
*/ @Slf4j //@Component public class ZuulApiGatewayPreFilter extends ZuulFilter { @Override public String filterType() { return PRE_TYPE; } @Override public int filterOrder() { return 0; } @Override public boolean shouldFilter() { return true; } @Override public Object run() throws ZuulException { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); String accessToken = request.getParameter("token"); if (StringUtils.isEmpty(accessToken)) { // zuul过滤该请求,不进行路由 ctx.setSendZuulResponse(false); // 设置返回的错误码 ctx.setResponseStatusCode(403); ctx.setResponseBody("AccessToken is Invalid "); return null; } log.info("accessToken: {}",accessToken); // 否则业务继续执行 return null; } } ``` 后置过滤器,经常被用于打印日志等等操作,代码参考:[https://www.baeldung.com/zuul-filter-modifying-response-body](https://www.baeldung.com/zuul-filter-modifying-response-body),实现效果时,路由过滤器执行之后,执行后置过滤器打印日志: ```java package com.example.springcloud.zuul.web.filter; import com.google.common.io.CharStreams; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import lombok.extern.slf4j.Slf4j; import org.apache.http.protocol.RequestContent; import org.springframework.stereotype.Component; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.POST_TYPE; import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR; /** *
*      API Gateway后置过滤器
*
* *
* @author mazq
* 修改记录
*    修改后版本:     修改人:  修改日期: 2020/08/06 10:05  修改内容:
*
*/ @Slf4j //@Component public class ZuulApiGatewayPostFilter extends ZuulFilter { @Override public String filterType() { return POST_TYPE; } @Override public int filterOrder() { return 0; } @Override public boolean shouldFilter() { return true; } @Override public Object run() throws ZuulException { RequestContext context = RequestContext.getCurrentContext(); try (final InputStream responseDataStream = context.getResponseDataStream()) { if(responseDataStream == null) { log.warn("RESPONSE BODY: {}", ""); return null; } String responseData = CharStreams.toString(new InputStreamReader(responseDataStream, "UTF-8")); log.info("RESPONSE BODY: {}", responseData); context.setResponseBody(responseData); } catch (Exception e) { throw new ZuulException(e, INTERNAL_SERVER_ERROR.value(), e.getMessage()); } return null; } } ``` 注册过滤器,将过滤器加载到Spring容器,也可以在过滤器类加上`@Component` ```java package com.example.springcloud.zuul; import com.example.springcloud.zuul.web.filter.ZuulApiGatewayErrFilter; import com.example.springcloud.zuul.web.filter.ZuulApiGatewayPostFilter; import com.example.springcloud.zuul.web.filter.ZuulApiGatewayPreFilter; import com.example.springcloud.zuul.web.filter.ZuulApiGatewayRouteFilter; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.zuul.EnableZuulProxy; import org.springframework.context.annotation.Bean; @SpringBootApplication @EnableZuulProxy public class SpringcloudZuulApplication { public static void main(String[] args) { SpringApplication.run(SpringcloudZuulApplication.class, args); } @Bean public ZuulApiGatewayPreFilter zuulApiGatewayPreFilter(){ return new ZuulApiGatewayPreFilter(); } @Bean public ZuulApiGatewayPostFilter zuulApiGatewayPostFilter(){ return new ZuulApiGatewayPostFilter(); } @Bean public ZuulApiGatewayRouteFilter zuulApiGatewayRouteFilter(){ return new ZuulApiGatewayRouteFilter(); } @Bean public ZuulApiGatewayErrFilter zuulApiGatewayErrFilter(){ return new ZuulApiGatewayErrFilter(); } } ``` 访问网关:http://localhost:8082/api-gateway/provider/api/users/mojombo,不带token的情况 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200806110439445.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ0MjczOTE=,size_16,color_FFFFFF,t_70) ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200806111814185.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ0MjczOTE=,size_16,color_FFFFFF,t_70) http://localhost:8082/api-gateway/provider/api/users/mojombo?token=?,带上token调用成功 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200806173042170.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ0MjczOTE=,size_16,color_FFFFFF,t_70) ### 9、查看Zuul路由信息 加上spring-boot-starter-actuator,进行路由信息监控: ```xml org.springframework.boot spring-boot-starter-actuator ``` spring-boot-starter-actuator配置: ```yaml management: endpoints: web: exposure: # 默认只支持info,health,开启对routes的监控 include: info,health,routes # 开启健康检查详细信息 endpoint: health: show-details: always ``` 查看路由详细,访问[http://localhost:8082/actuator/routes](http://localhost:8082/actuator/routes),SpringBoot2.2.3版本要加上actuator前缀,调用成功,返回json数据: ```json { "/api-gateway/provider/**":"eureka-service-provider", "/api-gateway/eureka-service-provider/**":"eureka-service-provider" } ``` 查看路由详细信息,访问链接:[http://localhost:8082/actuator/routes/details](http://localhost:8082/actuator/routes/details) ```json { "/api-gateway/provider/**":{ "id":"provider", "fullPath":"/api-gateway/provider/**", "location":"eureka-service-provider", "path":"/**", "prefix":"/api-gateway/provider", "retryable":false, "customSensitiveHeaders":false, "prefixStripped":true }, "/api-gateway/eureka-service-provider/**":{ "id":"eureka-service-provider", "fullPath":"/api-gateway/eureka-service-provider/**", "location":"eureka-service-provider", "path":"/**", "prefix":"/api-gateway/eureka-service-provider", "retryable":false, "customSensitiveHeaders":false, "prefixStripped":true } } ``` 本博客代码例子下载:[code download](https://github.com/u014427391/springCloudExamples/tree/master/springcloud-zuul) Zuul官网手册: spring Cloud官网zuul资料:[https://docs.spring.io/spring-cloud-netflix/docs/2.2.x-SNAPSHOT/reference/html/#router-and-filter-zuul](https://docs.spring.io/spring-cloud-netflix/docs/2.2.x-SNAPSHOT/reference/html/#router-and-filter-zuul) zuul github wiki:[https://github.com/Netflix/zuul/wiki/How-it-Works](https://github.com/Netflix/zuul/wiki/How-it-Works) github zuul过滤器例子:[https://github.com/spring-cloud-samples/sample-zuul-filters](https://github.com/spring-cloud-samples/sample-zuul-filters) 优质学习资料参考: * Nginx官网对微服务网关的介绍:[https://www.nginx.com/blog/building-microservices-using-an-api-gateway/](https://www.nginx.com/blog/building-microservices-using-an-api-gateway/) * SpringCloud组件之网关Zuul(Hoxton版本):[https://juejin.im/post/6847902220214763527](https://juejin.im/post/6847902220214763527) * 方志鹏大佬系列Spring Cloud博客:[https://www.fangzhipeng.com/spring-cloud.html](https://www.fangzhipeng.com/spring-cloud.html) * 使用Spring Cloud与Docker实战微服务:[https://eacdy.gitbooks.io/spring-cloud-book/content/](https://eacdy.gitbooks.io/spring-cloud-book/content/) * 程序员DD大佬系列Spring Cloud博客:[http://blog.didispace.com/spring-cloud-learning/](http://blog.didispace.com/spring-cloud-learning/)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: