从零开始的Spring Cloud Gateway指南:构建强大微服务架构

news2024/11/28 14:31:33

目录

  • 一、 什么是Gateway?
    • 1. 网关的由来
    • 2. 网关的作用
    • 3. 网关的技术实现
  • 二、如何搭建一个简易网关服务
    • 1. 引入依赖
    • 2. 配置yml文件
  • 三、进阶话题:过滤器和路由配置
    • 1. gateway的执行原理
    • 2. 路由断言工厂: Predicate Factory
    • 3. 网关过滤器:GatewayFilter
      • 自定义网关过滤器
    • 4. 默认过滤器:default Filter
    • 5. 全局过滤器:GlobalFilter
    • 6. 过滤器的执行顺序
    • 7. GlobalFilter和GatewayFilter的区别
  • 四、实战经验分享:处理微服务间通信
    • 1. 跨域问题处理

微服务架构的兴起已经改变了软件开发的面貌,使得开发者能够更灵活地构建、部署和维护应用程序。而在这个微服务的时代,强大而灵活的网关是确保微服务之间通信顺畅的关键之一。在本文中,我们将深入研究Spring Cloud Gateway,一款开源的、基于Spring Framework的微服务网关,带领你从零开始构建强大的微服务架构。

一、 什么是Gateway?

1. 网关的由来

单体应用拆分成多个服务后,对外需要一个统一入口,解耦客户端与内部服务
在这里插入图片描述注:图片来自网络

2. 网关的作用

Spring Cloud Gateway是Spring Cloud生态系统中的一员,它被设计用于处理所有微服务的入口流量。作为一个反向代理,它不仅提供了负载均衡和路由功能,还支持灵活的过滤器机制,过滤器可以在请求进入网关和离开网关时执行,用于处理各种逻辑,如身份验证、日志记录和性能监测,使得开发者能够定制和扩展其功能。

在这里插入图片描述网关核心功能是路由转发,因此不要有耗时操作在网关上处理,让请求快速转发到后端服务上。
在这里插入图片描述

注:图片来自网络

3. 网关的技术实现

在SpringCloud中网关的实现包括两种:

  • gateway
  • zuul

Zuul 是基于Servlet的实现,在1.x版本属于阻塞式编程,在2.x后是基于netty,是非阻塞的。而SpringCloudGateway则是基于Spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能。

目前一般都选用SpringCloudGateway,毕竟都是Spring家族一员,支持性更好。

二、如何搭建一个简易网关服务

1. 引入依赖

需要先搭建一个SpringBoot项目,在父pom中引入依赖

<properties>
        <spring-cloud.version>2021.0.1</spring-cloud.version>
        <spring-cloud-alibaba.version>2021.0.1.0</spring-cloud-alibaba.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <!-- SpringCloud 微服务 -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!-- SpringCloud Alibaba 微服务 -->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring-cloud-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

    </dependencyManagement>

在gateway服务中引入相关依赖

<dependencies>
        <!--spring cloud gateway-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <!--nacos 服务发现-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

    </dependencies>

此外,请检查一下你的依赖中是否含有spring-boot-starter-web,如果有, 请干掉它 。因为我们的SpringCloud Gateway是一个netty+webflux实现的web服务器,和Springboot Web本身就是冲突的。

2. 配置yml文件

server:
  port: 8081 # 网关端口
spring:
  application:
    name: gateway # 服务名称
  cloud:
    nacos:
      server-addr: 127.0.0.1:8848 # nacos地址
    gateway:
      routes: # 网关路由配置
        - id: baidu # 路由id, 自定义,唯一即可
          # uri: 127.0.0.1:/order # - 路由目的地,支持lb和http两种
          uri: lb://orderService # 路由的目的地,lb是负载均衡,后面跟服务名称
          predicates: # 路由断言,也就是判断是否符合路由规则的条件
            - Path=/order-service/** # path 按照路径进行匹配,只要以/order-service/开头就符合规则
          filters:
            - StripPrefix=1 # 过滤器,去除请求的前缀路径,StripPrefix=1 表示去除请求路径的第一个路径片段

网关路由可以配置的内容包括:

  • 路由id:路由唯一标示
  • uri: 路由目的地,支持lb和http两种
  • predicates: 路由断言,判断请求是否符合要求,符合则转发到路由目的地
  • filters:路由过滤器,处理请求或响应

做到这里,一个简易的路由转发网关服务就搭建成功了,直接运行GatewayApplication服务,得到结果如图:

在这里插入图片描述
接下来在网页直接访问:http://127.0.0.1:8081/order-service/xx,网关就会将路由转发到orderService服务。

如果你是通过负载均衡路由到其他服务,这时候可能会报问题:

Whitelabel Error Page
This application has no configured error view, so you are seeing this as a fallback.

这是由于缺少一个相关的依赖

<!--客户端负载均衡loadbalancer-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

原因:

由于springcloud2020弃用了Ribbon,因此Alibaba在2021版本nacos中删除了Ribbon的jar包,因此无法通过lb路由到指定微服务,出现了503情况。
所以只需要引入springcloud loadbalancer包即可

引入依赖重新运行,OK!

三、进阶话题:过滤器和路由配置

网关服务一个主要作用就是路由配置,通过不同的路由配置实现相应的功能。

1)Route:路由是网关的基本构件。它由ID、目标URI、路由断言工厂(谓语动词)和过滤器集合定义。如果断言工厂判断为真,则匹配路由。
2)Predicate:路由断言工厂,参照Java8的新特性Predicate。这允许开发人员匹配HTTP请求中的任何内容,比如请求头或参数。
3)Filter:路由过滤器。可以在发送下游请求之前或之后修改请求和响应。

下面看一个案例:

spring:
  application:
    name: gateway # 服务名称
  cloud:
    nacos:
      server-addr: 127.0.0.1:8848 # nacos地址
    gateway:
      routes: # 网关路由配置
        - id: order# 路由id, 自定义,唯一即可
          # uri: 127.0.0.1:/order # - 路由目的地,支持lb和http两种
          uri: lb://orderService # 路由的目的地,lb是负载均衡,后面跟服务名称
          predicates: # 路由断言,也就是判断是否符合路由规则的条件
            - Path=/order-service/** # path 按照路径进行匹配,只要以/order-service/开头就符合规则
          filters: # 过滤器,对请求和响应进行处理
            - StripPrefix=1 # 过滤器,去除请求的前缀路径,StripPrefix=1 表示去除请求路径的第一个路径片段

在这里插入图片描述
注:图片来自网络

1. gateway的执行原理

网关的核心逻辑就是路由转发,执行过滤器链。在下面的处理过程中,Gateway Handler Mapping将请求和路由进行匹配,这时候就需要用到predicate,它是决定了一个请求是否走哪一个路由。
在这里插入图片描述

客户端向 Spring Cloud Gateway 发出请求。然后在 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。Handler 再通过指 定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑。
Filter在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,在“post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。

2. 路由断言工厂: Predicate Factory

我们在配置文件中写的断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为路由判断的条件。

例如Path=/user/**是按照路径匹配,这个规则是由org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory类来处理的。

像这样的断言工厂在SpringCloudGateway还有十几个:

在这里插入图片描述

在上图中,有很多类型的Predicate。
比如说时间类型的Predicated(AfterRoutePredicateFactory、 BeforeRoutePredicateFactory、BetweenRoutePredicateFactory),当只有满足特定时间要求的请求才会交由router处理;

spring:
  cloud:
    gateway:
      routes: # 网关路由配置
        - id: order
          uri: lb://orderService 
          predicates: 
            - Before=2023-12-25T14:33:47.789+08:00

这个路由规则会在东8区的2023-12-25 14:33:47后,将请求转跳到订单服务。

cookie类型的CookieRoutePredicateFactory,cookie名字和正则表达式的value作为两个输入参数,请求的cookie需要匹配cookie名和符合其中value的正则,才会进入此router;

spring:
  cloud:
    gateway:
      routes: # 网关路由配置
        - id: order
          uri: lb://orderService 
          predicates: 
            - Cookie=cookiename, cookievalue

路由匹配请求存在cookie名为cookiename,cookie内容匹配cookievalue的,将请求转发到订单服务。

以及host、method、path、querparam、remoteaddr类型的predicate,每一种predicate都会对当前的客户端请求进行判断,是否满足当前的要求,如果满足则交给当前请求处理。

如果有很多个Predicate,并且一个请求满足多个Predicate,则按照配置的顺序第一个生效。

请注意:一个请求满足多个路由的谓词条件时,请求只会被首个成功匹配的路由转发。

3. 网关过滤器:GatewayFilter

GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理

针对某些特殊的场景,Spring Cloud Gateway提供了31种不同的路由过滤器工厂。例如

在这里插入图片描述
在这里插入图片描述
注:图片来自网络

案例:

  1. AddRequestHeader:给所有进入orderService的请求添加一个请求头: mark=specifications
spring:
  cloud:
    gateway:
      routes: # 网关路由配置
        - id: order
          uri: lb://orderService 
          filters:
            - AddRequestHeader=mark, specifications

对匹配的请求,会额外添加mark:specifications的header。

AddRequestParameter、AddResponseHeader 类似。

  1. RemoveRequestHeader:移除请求的header
spring:
  cloud:
    gateway:
      routes: # 网关路由配置
        - id: order
          uri: lb://orderService 
          filters:
            - RemoveRequestHeader=mark

上面路由在发送请求给订单服务时,会将请求中的mark头信息去掉。

  1. StripPrefix:截断请求路径前缀
spring:
  cloud:
    gateway:
      routes: # 网关路由配置
        - id: order
          uri: lb://orderService
          predicates: 
            - Path=/order-service/** 
          filters:
            - StripPrefix=1

StripPrefix GatewayFilter Factory通过配置parts来表示截断路径前缀的数量。上面例子中,如果请求的路径为/order-service/order/1,则路径会修改为/order/1。

gateway提供的过滤器很多,这里不一一做展示。

自定义网关过滤器

定义方式是实现AbstractGatewayFilterFactory<>类。

public abstract class AbstractGatewayFilterFactory<C> extends AbstractConfigurable<C> implements GatewayFilterFactory<C>, ApplicationEventPublisherAware {
    private ApplicationEventPublisher publisher;

    public AbstractGatewayFilterFactory() {
        super(Object.class);
    }

    public AbstractGatewayFilterFactory(Class<C> configClass) {
        super(configClass);
    }

    protected ApplicationEventPublisher getPublisher() {
        return this.publisher;
    }

    public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }

    public static class NameConfig {
        private String name;

        public NameConfig() {
        }

        public String getName() {
            return this.name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }
}

自定义过滤器实现黑名单功能

@Component
public class BlackListUrlFilter extends AbstractGatewayFilterFactory<BlackListUrlFilter.Config>
{
    @Override
    public GatewayFilter apply(Config config)
    {
        return (exchange, chain) -> {

            String url = exchange.getRequest().getURI().getPath();
            if (config.matchBlacklist(url))
            {
                return ServletUtils.webFluxResponseWriter(exchange.getResponse(), "请求地址不允许访问");
            }

            return chain.filter(exchange);
        };
    }

    public BlackListUrlFilter()
    {
        super(Config.class);
    }

    public static class Config
    {
        private List<String> blacklistUrl;

        private List<Pattern> blacklistUrlPattern = new ArrayList<>();

        public boolean matchBlacklist(String url)
        {
            return !blacklistUrlPattern.isEmpty() && blacklistUrlPattern.stream().anyMatch(p -> p.matcher(url).find());
        }

        public List<String> getBlacklistUrl()
        {
            return blacklistUrl;
        }

        public void setBlacklistUrl(List<String> blacklistUrl)
        {
            this.blacklistUrl = blacklistUrl;
            this.blacklistUrlPattern.clear();
            this.blacklistUrl.forEach(url -> {
                this.blacklistUrlPattern.add(Pattern.compile(url.replaceAll("\\*\\*", "(.*?)"), Pattern.CASE_INSENSITIVE));
            });
        }
    }

}

配置自定义过滤器

spring:
  cloud:
    gateway:
      routes: # 网关路由配置
        - id: order
          uri: lb://orderService 
          filters:
            - name: BlackListUrlFilter
              args:
                blacklistUrl:
                  - /user/list
                  - 192.168.20.1

4. 默认过滤器:default Filter

如果要对所有的路由都生效,则可以将过滤器工厂写到default下。格式如下

spring:
  cloud:
    gateway:
      routes: # 网关路由配置
        - id: order # 路由id, 自定义,唯一即可
          # uri: http://www.baidu.com # - 路由目的地,支持lb和http两种
          uri: lb://orderService # 路由的目的地,lb是负载均衡,后面跟服务名称
          predicates: # 路由断言,也就是判断是否符合路由规则的条件
            - Path=/order-service/** 
          filters:
            - StripPrefix=1 
            - AddRequestHeader=mark, specifications
      default-filters: 
        - AddRequestHeader=mark, specifications 

default-filters下的过滤器将对所有的路由都生效。

5. 全局过滤器:GlobalFilter

全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与GatewayFilter的作用一样。
区别在于GatewayFilter通过配置定义,处理逻辑是固定的。而GlobalFilter的逻辑需要自己写代码实现。

我们可以通过GlobalFilter实现日志记录、权限校验、流量监控等功能

定义方式是实现GlobalFilter接口:
在这里插入图片描述
自定义全局过滤器实现网关鉴权:

@Component
public class AuthFilter implements GlobalFilter, Ordered {
    private static final Logger log = LoggerFactory.getLogger(AuthFilter.class);

    // 白名单,排除过滤的 uri 地址
    @Autowired
    private IgnoreWhiteProperties ignoreWhite;

    @Autowired
    private RedisService redisService;


    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        ServerHttpRequest.Builder mutate = request.mutate();

        String url = request.getURI().getPath();
        // 跳过不需要验证的路径
        if (StringUtils.matches(url, ignoreWhite.getWhites())) {
            return chain.filter(exchange);
        }
        String token = getToken(request);
        if (StringUtils.isEmpty(token)) {
            token=getWebsocketToken(request,response);
            if (StringUtils.isEmpty(token)){
                return unauthorizedResponse(exchange, "令牌不能为空");
            }
        }
        Claims claims = JwtUtils.parseToken(token);
        if (claims == null) {
            return unauthorizedResponse(exchange, "令牌已过期或验证不正确!");
        }
        String userkey = JwtUtils.getUserKey(claims);
        boolean islogin = redisService.hasKey(getTokenKey(userkey));
        if (!islogin) {
            return unauthorizedResponse(exchange, "登录状态已过期");
        }
        String userid = JwtUtils.getUserId(claims);
        String username = JwtUtils.getUserName(claims);
        if (StringUtils.isEmpty(userid) || StringUtils.isEmpty(username)) {
            return unauthorizedResponse(exchange, "令牌验证失败");
        }
        String nickName = JwtUtils.getNickName(claims);


        // 设置用户信息到请求
        addHeader(mutate, SecurityConstants.USER_KEY, userkey);
        addHeader(mutate, SecurityConstants.DETAILS_USER_ID, userid);
        addHeader(mutate, SecurityConstants.DETAILS_USERNAME, username);
       
        // 内部请求来源参数清除
        removeHeader(mutate, SecurityConstants.FROM_SOURCE);
        return chain.filter(exchange.mutate().request(mutate.build()).build());
    }

    private void addHeader(ServerHttpRequest.Builder mutate, String name, Object value) {
        if (value == null) {
            return;
        }
        String valueStr = value.toString();
        String valueEncode = ServletUtils.urlEncode(valueStr);
        mutate.header(name, valueEncode);
    }

    private void removeHeader(ServerHttpRequest.Builder mutate, String name) {
        mutate.headers(httpHeaders -> httpHeaders.remove(name)).build();
    }

    private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, String msg) {
        log.error("[鉴权异常处理]请求路径:{}", exchange.getRequest().getPath());
        return ServletUtils.webFluxResponseWriter(exchange.getResponse(), msg, HttpStatus.UNAUTHORIZED);
    }

    /**
     * 获取缓存key
     */
    private String getTokenKey(String token) {
        return CacheConstants.LOGIN_TOKEN_KEY + token;
    }

    /**
     * 获取请求token
     */
    private String getToken(ServerHttpRequest request) {
        String token = request.getHeaders().getFirst(TokenConstants.AUTHENTICATION);
        // 如果前端设置了令牌前缀,则裁剪掉前缀
        if (StringUtils.isNotEmpty(token) && token.startsWith(TokenConstants.PREFIX)) {
            token = token.replaceFirst(TokenConstants.PREFIX, StringUtils.EMPTY);
        }
        return token;
    }
    private String getWebsocketToken(ServerHttpRequest request,ServerHttpResponse response){
        String token = request.getHeaders().getFirst(TokenConstants.WEBSOCKET_AUTHENTICATION);
        ServerHttpRequest.Builder mutate = request.mutate();
        // 如果前端设置了令牌前缀,则裁剪掉前缀
        if (StringUtils.isNotEmpty(token) ) {
            response.getHeaders().set(TokenConstants.WEBSOCKET_AUTHENTICATION, token);
            removeHeader(mutate, TokenConstants.WEBSOCKET_AUTHENTICATION);
        }
        return token;
    }

    @Override
    public int getOrder() {
        return -200;
    }
}

6. 过滤器的执行顺序

请求进入网关会碰到三类过滤器: 当前路由的过滤器、DefaultFilter、GlobalFilter。

请求路由后,会将当前路由过滤器和DefaultFilter、GlobalFilter,合并到一个过滤器链(集合)中,排序后依次执行每个过滤器。
在这里插入图片描述

  • 每一个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前
  • GlobalFilter通过实现Ordered接口,或者添加@Order注解来指定order值,由我们自己指定
  • 路由过滤器和defaultFilter的order由Spring指定,默认是按照声明顺序从1递增
  • 当过滤器的order值一样时,会按照 defaultFilter > 路由过滤器>GlobalFilter的顺序执行

可以参考下面几个类的源码查看:

在这里插入图片描述

7. GlobalFilter和GatewayFilter的区别

Spring Cloud Gateway 根据作用范围划分为 GatewayFilter 和 GlobalFilter,二者区别如下:

  • GatewayFilter:网关过滤器,需要通过 spring.cloud.routes.filters 配置在具体路由下,只作用在当前路由上或通过 spring.cloud.default-filters 配置在全局,作用在所有路由上。
  • GlobalFilter:全局过滤器,不需要在配置文件中配置,作用在所有的路由上,最终通过 GatewayFilterAdapter 包装成 GatewayFilterChain 可识别的过滤器,它为请求业务以及路由的 URI 转换为真实业务服务请求地址的核心过滤器,不需要配置系统初始化时加载,并作用在每个路由上。

四、实战经验分享:处理微服务间通信

1. 跨域问题处理

跨域问题:浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题。它是由浏览器的同源策略所造成的,是浏览器对于JavaScript所定义的安全限制策略。

同源策略:是指协议,域名,端口都要相同,其中有一个不同都会产生跨域。

  • 同一协议, 如http或https
  • 同一IP地址或域名, 如127.0.0.1
  • 同一端口, 如8080

以上三个条件中有一个条件不同就会产生跨域问题。

例如:

  • 域名不同: www.taobao.com 和 www.taobao.org 和 www.jd.com
  • 域名相同,端口不同: localhost:8080和localhost:8081

解决方案:

跨源资源共享 (CORS):(或通俗地译为跨域资源共享)是一种基于 HTTP 头的机制,该机制通过允许服务器标示除了它自己以外的其它origin(域,协议和端口),这样浏览器可以访问加载这些资源。现代浏览器支持在 API 容器中(例如 XMLHttpRequest 或 Fetch)使用 CORS,以降低跨源 HTTP 请求所带来的风险。

网关处理跨域采用的同样是CORS方案,并且只需要简单配置即可实现。

CORS跨域要配置的参数包括:

  • 允许哪些域名跨域?
  • 允许哪些请求头?
  • 允许哪些请求方式?
  • 是否允许使用cookie?
  • 有效期是多久?
spring: 
  cloud:
    gateway:
      globalcors: # 全局跨域处理
      	add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
        cors-configurations:
          '[/**]':
            # 允许跨域的源(网站域名/ip),设置*为全部
            # 允许跨域请求里的head字段,设置*为全部
            # 允许跨域的method, 默认为GET和OPTIONS,设置*为全部
            allow-credentials: true # 是否允许携带cookie
            allowed-origins: # 运行的跨域请求的网站
              - "http://taobao.123.com"
              - "http://www.xx.com"
            allowed-headers: "*" # 允许在请求中携带的头信息
            allowed-methods: # 允许跨域ajax请求方式
              - OPTIONS
              - GET
              - POST
              - DELETE
              - PUT
              - PATCH
            max-age: 36000  # 跨域检测的有效期

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

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

相关文章

Cannot resolve symbol ‘ActivityResultLauncher‘ 报错处理方法

修改 app/build.gradle implementation ‘androidx.appcompat:appcompat:1.2.0’ 为 implementation ‘androidx.appcompat:appcompat:1.4.0’

【模型报错记录】‘PromptForGeneration‘ object has no attribute ‘can_generate‘

通过这个连接中的方法解决&#xff1a; “PromptForGeneration”对象没有属性“can_generate” 期刊 #277 thunlp/OpenPrompt GitHub的 问题描述&#xff1a;在使用model.generate() 的时候报错&#xff1a;PromptForGeneration object has no attribute can_generate 解决方法…

在Spring Cloud中使用组件Zuul网关,并注册到Eureka中去

在上一篇中&#xff0c;我们搭建了Spring Cloud的父子模块&#xff0c;并实现了一个Eureka子模块的启动&#xff0c;可以通过浏览器地址去访问Eureka主页了&#xff0c;相信了解过的童鞋应该看到&#xff0c;主页上并未有任何服务去注册&#xff0c;那么我们就在这篇&#xff0…

【1】一文读懂PyQt简介和环境搭建

目录 1. PyQt简介 1.1. Qt 1.2. PyQt 1.3. 关于PyQt和PySide 2. 通过pip安装PyQt5 3. 无法运行处理 4. VSCode配置PYQT插件 PyQt官网:Riverbank Computing | Introduction 1. PyQt简介 PyQt是一套Python的GUI开发框架,即图形用户界面开发框架. Python中经常使用的GUI…

NXT : 十年源头代码的回溯与展望

11月28日,区块链领域的开山鼻祖之一NXT上线十周年。十年弹指一挥间,从2013年最初基于密码学证明思想构建的NXT,到今日仍在不断拓展与优化的Ardor(NXT 2.0)生态。我们不妨利用这个契机,回溯一下这个源头级公链的创新之路。 2013年,在区块链意味着复制比特币代码并调整挖矿算法的…

Verilog基础:$time、$stime和$realtime系统函数的使用

相关阅读 Verilog基础https://blog.csdn.net/weixin_45791458/category_12263729.html $time、 $stime和$realtime这三个系统函数提供了返回当前仿真时间方法。注意&#xff0c;这里的仿真时间的最小分辨能力是由仿真时间精度决定的&#xff0c;简单来说&#xff0c;可以理解为…

在线测量大尺寸管材的测径仪有哪些?

工业高速发展的背后&#xff0c;离不开与之匹配的高端设备作为科研的支撑。品质检测仪器也在随着现代科技的发展而不断变化&#xff0c;随着科技的进步&#xff0c;各种大口径的管材、管道被生产制造出来&#xff0c;而对其外径尺寸的检测则因口径范围大而使得很少有仪器能进行…

js选中起始时间使用标准时间毫秒值计算一年后的当前少一天的日期(并考虑闰年)

js选中起始时间使用标准时间毫秒值计算一年后的当前少一天的日期 实际代码里面带入默认日期’20230301’这个特殊日期&#xff0c;因为下一年的当前日期少一天为闰年的2月会有29天&#xff0c;使用特殊值校验代码效果图 HTML部分代码 <el-button click"chengTime()&q…

多线程--11--ConcurrentHashMap

ConcurrentHashMap与HashMap等的区别 HashMap线程不安全 我们知道HashMap是线程不安全的&#xff0c;在多线程环境下&#xff0c;使用Hashmap进行put操作会引起死循环&#xff0c;导致CPU利用率接近100%&#xff0c;所以在并发情况下不能使用HashMap。 ConcurrentHashMap 主…

读书笔记-《数据结构与算法》-摘要2[冒泡排序]

冒泡排序 核心&#xff1a;冒泡&#xff0c;持续比较相邻元素&#xff0c;大的挪到后面&#xff0c;因此大的会逐步往后挪&#xff0c;故称之为冒泡。 public class BubbleSort {public static void main(String[] args) {int unsortedArray[] new int[]{6, 5, 3, 1, 8, 7, 2…

软件设计之适配器模式

类模式 我们知道插座的电压为交流电220V&#xff0c;而日常电器使用的是直流电且电压会较小&#xff0c;比如手机充电会通过插头适配器达到额定的输入电流。下面我们实现这个案例&#xff1a;将220V电压转化为5V的电压。 package Adapter.Class;public class Adapter extends …

外包干了8个月,技术退步明显.......

先说一下自己的情况&#xff0c;大专生&#xff0c;18年通过校招进入武汉某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落! 而我已经在一个企业干了四年的功能测…

Hello World

世界上最著名的程序 from fastapi import FastAPIapp FastAPI()app.get("/") async def root():return {"message": "Hello World"}app.get("/hello/{name}") async def say_hello(name: str):return {"message": f"…

Python实现FA萤火虫优化算法优化BP神经网络回归模型(BP神经网络回归算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 萤火虫算法&#xff08;Fire-fly algorithm&#xff0c;FA&#xff09;由剑桥大学Yang于2009年提出 , …

通过断点调试解决node 运行js程序直接退出(没有任何报错提示)的问题

现象&#xff1a; node运行程序直接退出&#xff0c;但是从echo $?的返回值可以知道&#xff1a; 一定出现了错误&#xff0c;但是没有显示出来 解决办法&#xff1a; 1. 使用node --inspect-brk 启动程序 然后在浏览器访问chrome://inspect 然后点击inspect 进行单步调试 …

BearPi Std 板从入门到放弃 - 引气入体篇(6)(定时器TIM2)

简介 基于前面几篇, 增加定时器方面内容&#xff0c; LED定时闪动&#xff0c; 串口定时打印; 如前几篇所说&#xff0c; 使用BearPi Std板进行学习测试, 本例 开发板 &#xff1a; Bearpi Std(小熊派标准板) 主芯片: STM32L431RCT6 LED : PC13 \ 推挽输出即可 \ 高电平点亮 …

股价暴涨192%后,夏威夷控股股票还值得买入吗?

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 这两家公司计划组建一个横跨太平洋的航空公司 阿拉斯加航空(ALK )近期宣布它已和夏威夷航空(HA)达成协议&#xff0c;将以19亿美元现金和承担债务的方式收购夏威夷控股的母公司。 但这一消息却使两家公司的股价走向了相反的…

QT 无法打开包括文件 “xxxx.h”: No such file or direcotry 提升控件后提示找不到头文件

问题复现 UI文件提升控件后&#xff0c;提示找不到头文件。 原因 Qt中的ui文件会经过moc编辑器生成ui_xxx.h头文件。 在主页面的ui文件中因为使用了提升的widget&#xff0c;所以ui的ui头文件因该包含自定义控件的头文件。但是头文件的路径可以看出已经错误了。 #include &…

vue3 vue-router的安装及配置 (一)

文章目录 一、安装二、Vue Router配置2.1 vue-router两种引入方式的区别2.2 不同的历史模式 三、router-link四、router-view Vue Router作用&#xff1a;在应用程序中实现优雅的导航和路由管理。 一、安装 注意&#xff1a;vue3安装的是vue-router4,vue2安装的是vue-router3…

Java核心知识点整理大全27-笔记(已完结)

目录 30. 云计算 30.1.1. SaaS 30.1.2. PaaS 30.1.3. IaaS 30.1.4. Docker 30.1.4.1. 概念 30.1.4.2. Namespaces 30.1.4.3. 进程(CLONE_NEWPID 实现的进程隔离) 30.1.4.4. Libnetwork 与网络隔离 30.1.4.5. 资源隔离与 CGroups 30.1.4.6. 镜像与 UnionFS 30.1.4.7.…