Spring Cloud Gateway从注册中心自动注册配置路由信息

news2024/11/27 16:43:13

环境信息

Spring Boot:2.0.8.RELEASE

Spring Boot内置的tomcat:tomcat-embed-core 8.5.37

Spring Cloud Gateway:2.0.4.RELEASE

Nacos:2.0.4.RELEASE

需求

Spring Cloud Gateway注册到注册中心(这里是Nacos,其它注册中心也一样,如Eureka,Consul,Zookeeper),自动从注册中心里获取注册上去的服务,并使用负载均衡策略将对应的请求路由到对应的服务上。

目前注册到注册中心的服务,服务名和服务的根路径是一样的,比如服务名service-a,根路径是/service-a。

访问路径规则:http://网关ip:端口/目标服务名/目标服务具体路径

自动重定向到:http://目标服务ip:端口/目标服务名/目标服务具体路径

如:

访问网关的路径:http://localhost:8000/service-a/logSetting/getLogLevel

直接访问目标服务service-a的路径:http://localhost:6001/service-a/logSetting/getLogLevel

分析

Spring Cloud Gateway官方提供了从注册中心自动获取服务并注册路由信息的功能,前提是gateway网关工程注册到了注册中心(如Eureka、Consul、Nacos、Zookeeper等)

官方文档:Spring Cloud Gateway

这种自动注册的路由信息:

断言predicates是Path,规则是"'/'+serviceId+'/**'",即匹配服务名

过滤器filters是RewritePath,规则是:

regexp: "'/' + serviceId + '/(?<remaining>.*)'"

replacement: "'/${remaining}'"

比如:

访问网关的路径:http://localhost:8000/service-a/logSetting/getLogLevel

会重定向到:http://localhost:6001/logSetting/getLogLevel

和需求相比,目标路径少了/service-a,会导致无法访问到目标服务,报错404

因此,需要设置自定义规则用来代替默认的规则。

以下是官方文档里的说明、示例

By default, the gateway defines a single predicate and filter for routes created with a DiscoveryClient.
The default predicate is a path predicate defined with the pattern /serviceId/**, where serviceId is the ID of the service from the DiscoveryClient.
The default filter is a rewrite path filter with the regex /serviceId/(?<remaining>.*) and the replacement /${remaining}. This strips the service ID from the path before the request is sent downstream.
If you want to customize the predicates or filters used by the DiscoveryClient routes, set spring.cloud.gateway.discovery.locator.predicates[x] and spring.cloud.gateway.discovery.locator.filters[y]. When doing so, you need to make sure to include the default predicate and filter shown earlier, if you want to retain that functionality. The following example shows what this looks like:

Example 71. application.properties

spring.cloud.gateway.discovery.locator.predicates[0].name: Path
spring.cloud.gateway.discovery.locator.predicates[0].args[pattern]: "'/'+serviceId+'/**'"
spring.cloud.gateway.discovery.locator.predicates[1].name: Host
spring.cloud.gateway.discovery.locator.predicates[1].args[pattern]: "'**.foo.com'"
spring.cloud.gateway.discovery.locator.filters[0].name: Hystrix
spring.cloud.gateway.discovery.locator.filters[0].args[name]: serviceId
spring.cloud.gateway.discovery.locator.filters[1].name: RewritePath
spring.cloud.gateway.discovery.locator.filters[1].args[regexp]: "'/' + serviceId + '/(?<remaining>.*)'"
spring.cloud.gateway.discovery.locator.filters[1].args[replacement]: "'/${remaining}'"

但是要注意:

  1. 官网上的配置的格式有问题,不能直接使用。

  1. 使用了自定义配置之后,默认的配置就不会生效了。如果需要和默认配置一样的功能,那么需要手动配置。

如果是applicaton.properties格式的配置文件,那么和默认配置一样的功能的话,应该这样子配置:注意args[pattern]等的值,不能有双引号(")!这些值是SPEL表达式。

# 断言配置
spring.cloud.gateway.discovery.locator.predicates[0].name: Path
spring.cloud.gateway.discovery.locator.predicates[0].args[pattern]: '/api/'+serviceId+'/**'
# 过滤器设置
spring.cloud.gateway.discovery.locator.filters[0].name: RewritePath
spring.cloud.gateway.discovery.locator.filters[0].args[regexp]: '/' + serviceId + '/(?<remaining>.*)'
spring.cloud.gateway.discovery.locator.filters[0].args[replacement]: '/${remaining}'

如果是application.yml格式的配置文件的话:

注意args的pattern、regex等的值,要有双引号(")!这些值是SPEL表达式。

spring:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
          lowerCaseServiceId: true
          predicates:
            - name: Path
              args:
                pattern: "'/'+serviceId+'/**'"
          filters:
            - name: RewritePath
              args:
                regexp: "'/' + serviceId + '/?(?<remaining>.*)'"
                replacement: "'/' + '/${remaining}'"

实现

官方文档:Spring Cloud Gateway

  1. 开启自动注册功能

在application.properties里配置属性:

# 开启自动注册
spring.cloud.gateway.discovery.locator.enabled=true
# serviceId使用小写
spring.cloud.gateway.discovery.locator.lowerCaseServiceId=true
  1. 配置自定义规则。

application.properties版:主要是配置了路径重写过滤器,不将serviceId过滤掉。注意不要有双引号

spring.cloud.gateway.discovery.locator.filters[0].name: RewritePath
spring.cloud.gateway.discovery.locator.filters[0].args[regexp]: '/' + serviceId + '/(?<remaining>.*)'
spring.cloud.gateway.discovery.locator.filters[0].args[replacement]: '/' + serviceId + '/${remaining}

其它常见需求

  1. 手动注册路由信息

如果需要手动注册路由信息,而且访问路径还是和自动注册的一样(即:http://网关ip:端口/目标服务名/目标服务具体路径),那么需要设置路由的优先级order比0小,因为自动注册的路由信息优先级order是0,order值越小,优先级越高。如果没配置order,默认是0,但是手动注册的路由信息优先级会低于自动注册的,具体原因后面说。

spring.cloud.gateway.routes[0].id=service-a
spring.cloud.gateway.routes[0].uri=http://localhost:6001/service-a,http://localhost:16001/service-a
spring.cloud.gateway.routes[0].predicates[0]=Path=/service-a/**
spring.cloud.gateway.routes[0].order=-1
  1. 配置自动注册的服务

如果需要从注册中心里自动注册路由,但是想要排除一些不需要暴露给网关的服务,那么可以使用一下方式来排除:

spring.cloud.gateway.discovery.locator.includeExpression=!serviceId.contains('service-b')

扩展:includeExpression的值是SPEL表达式,serviceId是注册中心里的服务id。

表达式可以用contains、eq等等,来实现不同的筛选。

这个属性对应的是DiscoveryLocatorProperties类

@ConfigurationProperties("spring.cloud.gateway.discovery.locator")
public class DiscoveryLocatorProperties {
    private boolean enabled = false;
    private String routeIdPrefix;
    private String includeExpression = "true";
    private String urlExpression = "'lb://'+serviceId";
    private boolean lowerCaseServiceId = false;
    private List<PredicateDefinition> predicates = new ArrayList();
    private List<FilterDefinition> filters = new ArrayList();
    // 具体代码略...
}
  1. 配置全局的过滤器

spring:
  cloud:
    gateway:
      default-filters:
        - DedupeResponseHeader=Access-Control-Allow-Origin Access-Control-Allow-Credentials Vary, RETAIN_UNIQUE
  1. 配置全局的cors

spring:
  cloud:
    gateway:
      globalcors:
        add-to-simple-url-handler-mapping: true
        cors-configurations:
          '[/**]':
            allowedOriginPatterns: "*"
            allowedMethods: "*"
            allowedHeaders: "*"
            allowedOrigin: "*"
            allowCredentials: true
            maxAge: 360000

完整版的application.properties

# 开启自动注册
spring.cloud.gateway.discovery.locator.enabled=true
# serviceId使用小写
spring.cloud.gateway.discovery.locator.lowerCaseServiceId=true

# 过滤器设置
spring.cloud.gateway.discovery.locator.filters[0].name: RewritePath
spring.cloud.gateway.discovery.locator.filters[0].args[regexp]: '/' + serviceId + '/(?<remaining>.*)'
spring.cloud.gateway.discovery.locator.filters[0].args[replacement]: '/' + serviceId + '/${remaining}'

# 自定义路由信息
spring.cloud.gateway.routes[0].id=service-a
#spring.cloud.gateway.routes[0].uri=lb://service-a
spring.cloud.gateway.routes[0].uri=http://localhost:6001/service-a,http://localhost:16001/service-a
spring.cloud.gateway.routes[0].predicates[0]=Path=/service-a/**
spring.cloud.gateway.routes[0].order=-1

spring.cloud.gateway.default-filters[0]=DedupeResponseHeader=Access-Control-Allow-Origin Access-Control-Allow-Credentials Vary, RETAIN_UNIQUE

application.yml

spring:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
          lowerCaseServiceId: true
          predicates:
            - name: Path
              args:
                pattern: "'/'+serviceId+'/**'"
          filters:
            - name: RewritePath
              args:
                regexp: "'/' + serviceId + '/?(?<remaining>.*)'"
                replacement: "'/' + serviceId + '/${remaining}'"
      routes:
        - id: service-a
#          uri: lb://service-a
          uri: http://localhost:6001/service-a,http://localhost:16001/service-a
          predicates:
            - Path=/tfb-biz-common-service-app/**
          order: -1

源码解析

配置相关类

  1. GatewayDiscoveryClientAutoConfiguration

主要是根据属性spring.cloud.gateway.discovery.locator.enabled=true开启自动注册,并初始化自动注册的默认断言、过滤器

  1. DiscoveryLocatorProperties

@ConfigurationProperties("spring.cloud.gateway.discovery.locator")
public class DiscoveryLocatorProperties {
    private boolean enabled = false;
    private String routeIdPrefix;
    private String includeExpression = "true";
    private String urlExpression = "'lb://'+serviceId";
    private boolean lowerCaseServiceId = false;
    private List<PredicateDefinition> predicates = new ArrayList();
    private List<FilterDefinition> filters = new ArrayList();

    public DiscoveryLocatorProperties() {
    }// 59

GatewayDiscoveryClientAutoConfiguration类里的discoveryLocatorProperties()初始化了默认配置

    @Bean
    public DiscoveryLocatorProperties discoveryLocatorProperties() {
        DiscoveryLocatorProperties properties = new DiscoveryLocatorProperties();// 66
        properties.setPredicates(initPredicates());// 67
        properties.setFilters(initFilters());// 68
        return properties;// 69
    }

DiscoveryLocatorProperties初始化的时候,又因为@ConfigurationProperties("spring.cloud.gateway.discovery.locator")会加载配置文件里的spring.cloud.gateway.discovery.locator对应的属性,从而覆盖掉默认配置。

上面常见需求里说的用includeExpression来设置需要注册或不注册的服务,就是在这个类里的属性。

RouteDefinitionRouteLocator

  1. RouteDefinitionLocator

路由定义定位器接口,有多个实现类:

CachingRouteDefinitionLocator
CompositeRouteDefinitionLocator
DiscoveryClientRouteDefinitionLocator
InMemoryRouteDefinitionRepository
PropertiesRouteDefinitionLocator
RouteDefinitionRepository
  1. DiscoveryClientRouteDefinitionLocator

自动注册的路由信息,具体是在这里设置的。

网关请求的流程:

路由入口:org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping#lookupRoute

RoutePredicateHandlerMapping

接着访问org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#getRoutes

这里的routeDefinitionLocator是CompositeRouteDefinitionLocator对象:

是一个代理对象,包含了一个List:

iterable = {ArrayList@9085} size = 3
0 = {DiscoveryClientRouteDefinitionLocator@9087}
1 = {PropertiesRouteDefinitionLocator@9088}
2 = {InMemoryRouteDefinitionRepository@9089}

由此也可以看出先使用自动注册的路由信息,再使用配置文件里配置的路由信息。

这三个路由定义定位器,都是实现了RouteDefinitionLocator接口

DiscoveryClientRouteDefinitionLocator这个是从注册中心里主动注册的路由定义定位器

里面有个属性是DiscoveryLocatorProperties,在getRouteDefinitions()里有对于该属性的详细使用方式,比如includeExpression属性、lowerCaseServiceId属性等

PropertiesRouteDefinitionLocator是基于属性的路由定义定位器

public class PropertiesRouteDefinitionLocator implements RouteDefinitionLocator {
    private final GatewayProperties properties;

    public PropertiesRouteDefinitionLocator(GatewayProperties properties) {
        this.properties = properties;// 33
    }// 34

    public Flux<RouteDefinition> getRouteDefinitions() {
        return Flux.fromIterable(this.properties.getRoutes());// 38
    }
}

属性GatewayProperties:

@ConfigurationProperties("spring.cloud.gateway")
@Validated
public class GatewayProperties {
    private final Log logger = LogFactory.getLog(this.getClass());
    @NotNull
    @Valid
    private List<RouteDefinition> routes = new ArrayList();
    private List<FilterDefinition> defaultFilters = new ArrayList();
    private List<MediaType> streamingMediaTypes;

    public GatewayProperties() {
        this.streamingMediaTypes = Arrays.asList(MediaType.TEXT_EVENT_STREAM, MediaType.APPLICATION_STREAM_JSON);// 55
    }
    // 省略
}

RouteDefinition是路由定义信息,不管是注册中心自动配置的,还是通过属性文件配置的,或者通过代码方式配置的

@Validated
public class RouteDefinition {
    @NotEmpty
    private String id = UUID.randomUUID().toString();
    @NotEmpty
    @Valid
    private List<PredicateDefinition> predicates = new ArrayList();
    @Valid
    private List<FilterDefinition> filters = new ArrayList();
    @NotNull
    private URI uri;
    private int order = 0;
    // 省略
}

回到入口RoutePredicateHandlerMapping#lookupRoute里的具体代码:

获取到路由信息之后,会调用路由信息的断言predicate的apply(T t)方法,来判断该请求是否满足断言。如果满足,那么就会使用该路由信息来进行路由转发。

比如使用路径断言(predicate的name是Path),对应的是PathRoutePredicateFactory:

RouteDefinitionRouteLocator#getRoutes这里还调用了convertToRoute(),里面调用了getFilters,这个是过滤器

    public Flux<Route> getRoutes() {
        return this.routeDefinitionLocator.getRouteDefinitions().map(this::convertToRoute).map((route) -> {// 109 110 112
            if (this.logger.isDebugEnabled()) {// 113
                this.logger.debug("RouteDefinition matched: " + route.getId());// 114
            }

            return route;// 116
        });
    }

    private Route convertToRoute(RouteDefinition routeDefinition) {
        AsyncPredicate<ServerWebExchange> predicate = this.combinePredicates(routeDefinition);// 127
        List<GatewayFilter> gatewayFilters = this.getFilters(routeDefinition);// 128
        return ((AsyncBuilder)Route.async(routeDefinition).asyncPredicate(predicate).replaceFilters(gatewayFilters)).build();// 130 131 132 133
    }

后续的处理会调用到各个过滤器

RouteDefinitionRouteLocator.loadGatewayFilters(),重点在于调用了GatewayFilter gatewayFilter = factory.apply(configuration);

    private List<GatewayFilter> loadGatewayFilters(String id, List<FilterDefinition> filterDefinitions) {
        List<GatewayFilter> filters = (List)filterDefinitions.stream().map((definition) -> {// 138 139
            GatewayFilterFactory factory = (GatewayFilterFactory)this.gatewayFilterFactories.get(definition.getName());// 140
            if (factory == null) {// 141
                throw new IllegalArgumentException("Unable to find GatewayFilterFactory with name " + definition.getName());// 142
            } else {
                Map<String, String> args = definition.getArgs();// 144
                if (this.logger.isDebugEnabled()) {// 145
                    this.logger.debug("RouteDefinition " + id + " applying filter " + args + " to " + definition.getName());// 146
                }

                Map<String, Object> properties = factory.shortcutType().normalize(args, factory, this.parser, this.beanFactory);// 149
                Object configuration = factory.newConfig();// 151
                ConfigurationUtils.bind(configuration, properties, factory.shortcutFieldPrefix(), definition.getName(), this.validator);// 153 154
                GatewayFilter gatewayFilter = factory.apply(configuration);// 156
                if (this.publisher != null) {// 157
                    this.publisher.publishEvent(new FilterArgsEvent(this, id, properties));// 158
                }

                return gatewayFilter;// 160
            }
        }).collect(Collectors.toList());// 162
        ArrayList<GatewayFilter> ordered = new ArrayList(filters.size());// 164

        for(int i = 0; i < filters.size(); ++i) {// 165
            GatewayFilter gatewayFilter = (GatewayFilter)filters.get(i);// 166
            if (gatewayFilter instanceof Ordered) {// 167
                ordered.add(gatewayFilter);// 168
            } else {
                ordered.add(new OrderedGatewayFilter(gatewayFilter, i + 1));// 171
            }
        }

        return ordered;// 175
    }

比如说常见的路径重写过滤器RewritePathGatewayFilterFactory。

可以看到这里有两个参数regexp和replacement

public class RewritePathGatewayFilterFactory extends AbstractGatewayFilterFactory<RewritePathGatewayFilterFactory.Config> {
    public static final String REGEXP_KEY = "regexp";
    public static final String REPLACEMENT_KEY = "replacement";

    public RewritePathGatewayFilterFactory() {
        super(RewritePathGatewayFilterFactory.Config.class);// 38
    }// 39

    public List<String> shortcutFieldOrder() {
        return Arrays.asList("regexp", "replacement");// 43
    }

    public GatewayFilter apply(RewritePathGatewayFilterFactory.Config config) {
        String replacement = config.replacement.replace("$\\", "$");// 48
        return (exchange, chain) -> {// 49
            ServerHttpRequest req = exchange.getRequest();// 50
            ServerWebExchangeUtils.addOriginalRequestUrl(exchange, req.getURI());// 51
            String path = req.getURI().getRawPath();// 52
            String newPath = path.replaceAll(config.regexp, replacement);// 53
            ServerHttpRequest request = req.mutate().path(newPath).build();// 55 56 57
            exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, request.getURI());// 59
            return chain.filter(exchange.mutate().request(request).build());// 61
        };
    }
    // 省略
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/166837.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Spring学习系列(二)

Spring_特殊值的注入问题和各种类型的自动装配1.set两种方式的区别第4中赋值方式&#xff1a;自动装配&#xff08;只适用于ref类型&#xff09;使用注解定义bean1.set两种方式的区别 &#xff08;1&#xff09;把值写到value属性里面&#xff0c;必须加引号&#xff0c;写到子…

【学习】计算机系统硬件和数据结构

学习内容描述&#xff1a; 1、CPU包含哪些部分&#xff1f; 2、数据结构基础知识。 重点知识&#xff1a; 1、CPU(中央处理器&#xff0c;Central Processing Unit) 主要包括运算器、控制器两大部件&#xff1b;内部结构包含控制单元、运算单元、存储单元和时钟等几个主要部…

虚拟直播(虚拟场景直播)要怎么做?

阿酷TONY / 2022-11-21 / 长沙 绿幕抠像 虚拟场景&#xff08;三维场景&#xff09;实时渲染&#xff0c;来一场虚拟直播。 附案例效果&#xff1a;PC端案例、移动端案例效果。 目录 1. 绿幕虚拟直播间 2. 虚拟场景(炫酷舞台) 3. 案例&#xff1a;PC端 4. 案例&#xff1a…

光纤内窥镜物镜光学设计

光纤内窥镜物镜光学设计 工作原理 典型的光纤传像束内窥镜主要由前置物镜、光纤传像束、目镜/耦接镜、 探测器等组成&#xff0c;如图1所示。通过物镜把目标成像于光纤传像束的前端面上&#xff0c;该端面上的图像被离散分布的大量光纤采样&#xff0c;每根光纤都有良好的光学…

[极客大挑战 2019]Upload

目录 解题步骤 常见的绕过思路 解题步骤 直接上传shell 回显&#xff1a;Not image! bp抓包 修改类型 Content-Type: application/octet-stream改为Content-Type: image/jpg 回显&#xff1a;NOT&#xff01;php! 修改后缀为phtml 回显&#xff1a;NO! HACKER! your file inc…

SAP MM 物料分类账的启用配置

一、前言 物料账就是一本账&#xff0c;管理物料的数量和价值。 通常物料有两种计价方法&#xff1a; 移动平均价V&#xff08;移动加权平均&#xff09; 标准价S 移动平均价的优点&#xff1a;能够及时响应市场原材料价格的波动。 价差科目&#xff08;总账科目&#xff09;…

MyBatis-Plus基本操作

依赖 <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.3</version> </dependency>基础操作 DAO层 public interface UserMapper extends BaseMapper<User…

nohup + 命令实现后台不挂断地运行程序

nohup&#xff1a; nohup&#xff1a;不挂断地运行程序&#xff0c;就是即便关闭终端的窗口&#xff0c;程序还会继续执行。 $ nohup python merge_reasons.py可以看到终端仍在被占用&#xff0c;同级目录下生成了一个nohup.out文件&#xff0c;本来输出到终端的信息&#xf…

python人工智能数据算法(下)

文章目录差分法逼近微分背景引入差分法简介差分的不同形式及其代码实现蒙特卡罗方法背景引入蒙特卡罗方法原理蒙特卡罗方法应用计算圆周率计算定积分梯度下降算法算法简介方向导数与梯度梯度下降基于梯度下降算法的线性回归算法总结差分法逼近微分 背景引入 几乎所有的机器学…

我收集的PDF电子书

刚出来&#xff0c;要整理一下自己的资源了&#xff0c;好多都没有了&#xff0c;需要下载的自行联系niunanniunan.net 目录 软件工程 构建之法&#xff08;第1版&#xff09; 实现领域驱动设计 领域驱动设计&#xff1a;软件核心复杂性应对之道 人月神话 算法 算法基础…

C语言文件操作(一)

我们之前写程序&#xff0c;得到运行结果&#xff0c;退出运行&#xff0c;运行结果就不见了&#xff0c;因为运行的结果放到了内存中&#xff0c;退出程序的时候数据就会消失&#xff0c;等下一次运行又要重新输入数据&#xff0c;这样就显得很麻烦。那么我们如何保存之前的运…

Opengl ES之RGB转NV21

前言 在上一篇理论文章中我们介绍了YUV到RGB之间转换的几种公式与一些优化算法&#xff0c;今天我们再来介绍一下RGB到YUV的转换&#xff0c;顺便使用Opengl ES做个实践&#xff0c;将一张RGB的图片通过Shader 的方式转换YUV格式图&#xff0c;然后保存到本地。 可能有的童鞋…

SSL协议未开启是什么意思?

SSL协议未开启是指服务器中的服务没有开启或者没有SSL模块。在互联网中&#xff0c;数据交互传输基于http明文协议&#xff0c;随着互联网的不断发展&#xff0c;http协议展现出它的缺陷&#xff0c;通过http协议传输的数据容易被攻击者窃取、篡改或仿冒。为适应新形势下的网络…

【C语言进阶】文件操作详解

文章目录一.文件指针1.什么是文件指针2.如何使用文件指针二.文件操作函数1.流与文件2.顺序读写函数三.文本文件和二进制文件四.文件缓冲区一.文件指针 1.什么是文件指针 文件指针其实是文件类型指针的简称&#xff0c;我们常常会使用文件保存数据&#xff0c;而每个被使用的文…

SMILEtrack:基于相似度学习的多目标跟踪

文章目录摘要1、简介2、相关工作2.1、Tracking-by-Detection2.1.1、检测方法2.1.2、数据关联方法2.2、Tracking-by-Attention3、方法3.1、体系结构概述3.2.1、图像切片注意(ISA)块3.2.3、Q-K-V注意力块3.3、基于相似匹配级联(SMC)的目标跟踪算法4、实验结果4.1、数据集4.2、MOT…

XMind导入Markdown(利用Typora导出opml)

安装Xmind XMind 是一款非常实用的商业思维导图软件 首先&#xff0c;安装Xmind并打开。通过"帮助"——>“关于Xmind”&#xff0c;可以获取到当前的版本号为 XMind 8 Update 9 在"文件"——>“导入”&#xff0c;可以看到Xmind支持的导入格式仅有…

get请求和post请求

get请求 1.get请求的特点 通过一个URL来访问一个地址&#xff0c;就比如说127.0.0.1:7001,这种请求方式就是get请求&#xff0c;get请求可以直接在URL中添加参数&#xff0c;通过URL来传递参数。 优点&#xff1a;使用简单&#xff0c;清晰有条理&#xff0c;比较适合网站和…

十五天学会Autodesk Inventor,看完这一系列就够了(三),拉伸命令

众所周知&#xff0c;Autocad是一款用于二维绘图、详细绘制、设计文档和基本三维设计&#xff0c;现已经成为国际上广为流行的绘图工具。Autodesk Inventor软件也是美国AutoDesk公司推出的三维可视化实体模拟软件。因为很多人都熟悉Autocad&#xff0c;所以再学习Inventor&…

盘点2022年度A站UE神作top

A站大家都应该很熟悉了&#xff0c;在全球的CG行业都是属于专业化十分高的网站&#xff0c;平台内展示的内容包括影视、动画、娱乐等等板块&#xff0c;更是收录了众多大神艺术家的作品&#xff0c;多看看可以最直接的了解整个行业的审美趋势与技术动向。正好最近2022年A站人气…

数据结构:线性表的链式表示和实现

顺序表仅适用于不常进行插人和删除的线性表。因为在顺序存储表示的线性表中插入或删除一个数据元素,平均约需移动表中一半元素,这个缺陷是由于顺序存储要求线性表的元素依次“紧挨”存放造成的。因此对于经常需要进行插人和删除操作的线性表,就需要选择其他的存储表示方法。现在…