API网关理解

news2024/11/17 6:43:57

项目背景介绍:

首先介绍一下项目背景,这个项目是API开发平台,需要完成的接口的功能是:统计谁调用了这个接口,并且将这个接口的调用次数+1,剩余次数-1。

首先看到这个需求第一反应:

得先建个表,建一张用户接口关系表

并且分析用户和接口时多对多关系,一个用户可以调用多个接口,一个接口也可以被多个用户调用。这里什么建表就简单提一句,不是这个文章的重点。

当我们建完表之后,我们再来思考,如果完成这个功能?

在我没有接触API网关之前,第一反应就是AOP,每个接口次数都要加一嘛,这不是很简单用AOP嘛,

后面看了鱼皮老师画的图:

如果单纯只在这个项目中用AOP,就会发现,不太行,因为我们调用的接口有可能来自不同的项目。

每个项目都写一次AOP嘛。这肯定不够优雅,并且也有可能出错。

所以就引入了这个API网关的概念。

给我第一感觉这更像是一个更大的拦截器,

原来写代码都是在一个单体项目里,并且都是一个一个接口为单位去思考问题

这个项目需要以一个更大的视角来看问题了。

 介绍先到这里,下面开始API网关的介绍及使用:


API网关:

先贴一个官网:Spring Cloud 网关 --- Spring Cloud Gateway

该项目提供了一个库,用于在 Spring WebFlux 或 Spring WebMVC 之上构建 API 网关。Spring Cloud Gateway 旨在提供一种简单而有效的方式来路由到 API,并为它们提供跨领域关注点,例如:安全性、监控/指标和弹性。

Features 特征

        Spring Cloud Gateway features:
        Spring Cloud Gateway功能:

  • Built on Spring Framework and Spring Boot
    基于 Spring Framework 和 Spring Boot 构建

  • Able to match routes on any request attribute.
    能够在任何请求属性上匹配路由。

  • Predicates and filters are specific to routes.
    谓词和筛选器特定于路由。

  • Circuit Breaker integration.
    断路器集成。

  • Spring Cloud DiscoveryClient integration
    Spring Cloud Discovery客户端集成

  • Easy to write Predicates and Filters
    易于编写的谓词和过滤器

  • Request Rate Limiting 请求速率限制

  • Path Rewriting 路径重写

这些都是官网的介绍和特征。

大概理解下来就是这个网关可以帮我们转发路径,

就比如我后端的接口地址是:http://localhost:8123/api/name

但是我肯定不能直接把我这个暴露出去,一个因为不安全,一个就是如果我暴露出去,那我上面提到的需求不是做不了了。

这个API网关就可以帮我们转发,比如这个网关项目的地址是:http://localhost:8090

然后我们在配置文件中定于路由的匹配规则就可以定向到http://localhost:8123/api/name

并且呢,我们转发了这个路由,我们就可以做一些我们想要的操作在上面,这个后面会介绍。

介绍完网关的特征和基础概念之后,下一步就是解释一下网关的三个核心概念和两种配置方式:


 网关的三个核心概念和两种配置方式:

路由

网关的基本构建块。它由 ID、目标 URI、谓词集合和筛选器集合定义。如果聚合谓词为 true,则匹配路由。

这个很好理解,就和前端的路由是一样的,当你访问某个url的时候帮你进行跳转到指定的页面,

API网关的路由就是当你访问某个url的时候跳转到特定的url。

谓词

这是 Java 8 函数谓词。输入类型是 Spring Framework ServerWebExchange。这使您可以匹配 HTTP 请求中的任何内容,例如标头或参数

谓词的概念我刚上来也懵了,后面看了一下,可以把这个当成if,我们上面说会匹配到特定的url,但是并不是所有的路径都需要获取,或者换句话说,我的配置文件中用了很多的路由规则,就是靠这个匹配,

      routes:
        - id: api_route
          uri: http://localhost:8123
          predicates:
            - Path=/api/**

这是其中的一个路由,下面的predicates就是谓词,当你的这个是/api结尾并且后面是两个**说明任何路径都能匹配,当你匹配上这个路径之后就给你转发到localhost:8123这个地址去

过滤器

这些是使用特定工厂构建的 GatewayFilter 实例。在这里,您可以在发送下游请求之前或之后修改请求和响应。

过滤器也很好理解,就是我们转发了地址,我们可以对这个请求进行一下自己的加工,比如在请求头上加一点标识,这也是后面流量染色的思路。

两种配置方式:

  1. 配置式(方便、规范)
    1. 简化版
    2. 全称版

这个配置式其实就是在配置文件中写配置项,这样写的好处就很明白了简单,固定,但是不灵活

  1. 编程式(灵活、相对麻烦)

这个相对麻烦,但是灵活,我们还可以利用装饰器的设计模式进行增强

讲完了三个核心概念和两种配置方式,下面就具体讲网关的作用:


 网关的作用:

路由:

进行一个转发,上面已经介绍过了

Spring Cloud Gateway

里面这个路由谓词工厂有很多的谓词操作,可以进行使用。

负载均衡:

在nginx中也听过这个概念

就是一个更大的操作,对于一个集群来说,比如其中有一台服务器请求压力过大,就将一部分的请求转发到其它服务器上。

统一处理跨域

网关统一处理跨域,不用在每个项目里单独处理

这个在springbMVC中肯定有见过类似的表达式,就是配置跨域。

统一鉴权:

这里的统一鉴权应该要结合具体的实现逻辑,可以和AOP一起理解

这里可以和AOP一起理解是什么意思呢,就是你可以把AOP写的代码直接复制粘贴到这个网关的过滤器中

发布控制:

不同的接口分配不同的权值,比如要测试一个接口,就给这个接口分配较少的权值进行测试,等测试没问题再调回来或者像圆梦之星的图,新出的图有更多的机会被匹配到是一个道理

 流量染色:

流量染色,听名字好唬人

我们来看这个的中文翻译:

标头添加到所有匹配请求的下游请求的标头中

其实就是给这个请求头打个标记

复述一个鱼皮老师讲的例子

比如有个人绕过了网关知道了我们后端服务器的地址

直接进行访问,那我们做的鉴权就没有用了。

我们可以怎么办呢

就是用这个流量染色,我们给正常经过我们网关的请求头里面打上标记

比如flag = 一串加密后的东西

我们后端服务器再进行一次校验,有这个标记,允许访问,没有,就直接拒绝

 接口保护:
        限制请求:

                黑白名单,违规的用户不能访问

        信息脱敏:

                保护用户信息

        降级:

                如果你需要转化的页面访问出现问题,就让用户访问我们实现准备好的降级页面
        

        限流:

                限流简单理解就是当请求服务器的请求太多,稍微平衡一下。

限流这里的参数可能有点不理解:

首先想理解,需要先知道两个算法:漏桶算法和令牌桶算法

漏桶算法:很好理解,可以理解为一个漏斗,请求以匀速被处理,当这个请求很多的时候,这个漏斗就会溢出来,那溢出来的请求就不要了

 令牌桶算法:也是一个桶,这个桶比较高级一点,

我们往这个桶里匀速的放入令牌,每个请求来拿走一些令牌(不一定是一个),然后如果令牌没有了,那么请求就等待

上面使用的算法就是令牌桶算法:

The redis-rate-limiter.replenishRate property defines how many requests per second to allow (without any dropped requests). This is the rate at which the token bucket is filled.
redis-rate-limiter.replenishRate 属性定义每秒允许多少个请求(没有任何丢弃的请求)。这是令牌桶的填充率。

(这个可以理解为多块的往里面放令牌)

The redis-rate-limiter.burstCapacity property is the maximum number of requests a user is allowed in a single second (without any dropped requests). This is the number of tokens the token bucket can hold. Setting this value to zero blocks all requests.
redis-rate-limiter.burstCapacity 属性是用户在一秒钟内允许的最大请求数(没有任何丢弃的请求)。这是令牌存储桶可以容纳的令牌数。将此值设置为零将阻止所有请求。

(这个就是桶最多能有多少令牌)

The redis-rate-limiter.requestedTokens property is how many tokens a request costs. This is the number of tokens taken from the bucket for each request and defaults to 1.
redis-rate-limiter.requestedTokens 属性是请求花费的令牌数量。这是每个请求从存储桶中获取的令牌数,默认为 1

(这个就是每个请求需要几个令牌)

统一日志:

类似于我刚刚开始学习AOP的第一个案例:对数据库操作进行一个记录,比如记录什么时间点,谁,做了什么事

讲完了这个API网关的作用:

下面就是在具体项目中的应用了:


具体项目中的应用:

在项目中一般采取编程式和配置式向结合的方式来实现。

1:首先做一个大的路由配置:
server:
  port: 8090
spring:
  main:
    web-application-type: reactive
  cloud:
    gateway:
      routes:
        - id: api_route
          uri: http://localhost:8123
          predicates:
            - Path=/api/**
logging:
  level:
    org:
      springframework:
        cloud:
          gateway: trace 

这里就是上面看到的了,给用户的接口是localhost:8090/api

然后进行路由匹配到这个localhost:8123/api/**

后面就是真正的服务器接口地址。

这里还有一个配置logging:那个

这个就是降低日志级别,降到最低,把所有的日志信息都进行输出。

2:具体配置一个全局拦截器来执行我们想要的操作:

这个就是全局过滤器的编程式代码。直接复制

解释一下这个

ServerWebExchange exchange:

  • 定义ServerWebExchange 是 Spring WebFlux 中的一个接口,表示一次 HTTP 请求的上下文。它包含了请求和响应的所有信息。
  • 内容
    • 请求信息:可以获取请求的 URI、请求头、请求体等信息。
    • 响应信息:可以设置响应的状态码、响应头、响应体等。
    • 会话信息:可以访问和修改会话属性。

通过 exchange,你可以在过滤器中访问请求的详细信息,并根据需要对请求或响应进行修改。

我还没学过这个WebFlux,我现在就简单的理解为一个HttpServletRequest

里面包含了请求的信息。这里的上下文,在操作系统的书里也有出现过就是信息的意思。

 GatewayFilterChain chain:
  • 定义GatewayFilterChain 是一个接口,表示一系列过滤器的链条。在 Spring Cloud Gateway 中,过滤器可以是全局的或特定于路由的。
  • 功能
    • 通过调用 chain.filter(exchange),你可以将请求传递给下一个过滤器或目标服务。
    • 这使得过滤器可以按顺序执行,并且在每个过滤器中,你可以在请求到达目标服务之前或响应返回之前执行逻辑。

这个直接和AOP里面那个切点一起理解就行,放行就是执行下一步这个意思。

具体的代码:
private static ArrayList<String> IP_WHITE_LIST = new ArrayList<>();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //1:请求日志
        final ServerHttpRequest request = exchange.getRequest();
        log.info("请求唯一标识:"+request.getId());
        final URI uri = request.getURI();
        log.info("请求路径:"+uri);
        final HttpMethod method = request.getMethod();
        log.info("请求方法:"+method);
        MultiValueMap<String, String> queryParams = request.getQueryParams();
        log.info("请求参数:"+queryParams);
        String sourceAddress = request.getLocalAddress().getHostString();
        log.info("请求地址:"+sourceAddress);
        ServerHttpResponse response = exchange.getResponse();
        //2:访问控制 -黑白名单
        IP_WHITE_LIST.add("127.0.0.1");
        if(!IP_WHITE_LIST.contains(sourceAddress)){
            handleNoAuth(response);
        }
        //3:用户鉴权
        //4:从数据库中查询模拟接口是否存在
        //5:请求转化,调用模拟接口
        final Mono<Void> filter = chain.filter(exchange);
        return handleResponse(exchange,chain);
    }

这段代码的整体逻辑就是(其实单看这个项目完成用户调用接口次数+1根本不需要写这么多,这里作为一个学习的案例,调用一下里面的方法加深印象):

先请求日志,就是看一下请求的信息

再进行访问控制就是设置一个白名单

用户鉴权和查询接口是否存在后面好像鱼皮老师有其它办法,我这里先留一个todo

请求转化这一步就挺难的了

其实第五步后面还有两步就是调用接口次数+1,和打印日志

但是出现了一个问题:

在我和GPT大战500回合下,我总算是领悟到了一点皮毛。

首先描述一下这个问题:

先精简一下这个filter的代码:

@Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        一系列鉴权操作
        chain.filter(exchange);	
	打印日志和操作数据库
    }

我的本意是想就是在执行了这个放行操作之后呢,再打印日志,这也是肯定的,这个操作都没执行打印啥日志,可是我碰到了这个问题,就是我这个代码好像是异步执行的,就是先打印了日志才执行完这个方法

反应式编程:

反应式编程和命令式编程是两种方式

命令式编程就是我们常见的,一行一行代码执行下来,最后输出结果

反应式编程就是这里Mono这个例子

chain.filter(exchange) 返回的是一个 Mono<Void>,这意味着它会异步执行,而不是立即执行。因此,后面的日志打印和数据库操作会在 chain.filter(exchange) 被调用后立即执行,而不等待过滤器链的完成

换句话说就是执行chain.filter(exchange)这个方法太慢了,你这一个进程就一直堵在这里,后面的操作都没法执行了,然后程序就先执行下面的打印日志操作了。

再举一个例子:

你有过订阅报纸或者杂志的经历吗?互联网的确从传统的出版发行商那儿分得了一杯羹,但是在过去,订阅报纸确实是了解时事的最佳方式。那时,我们每天早上都会收到一份最新的报纸,并在早饭时间或上班路上阅读。
现在假设一下,在支付完订阅费用之后,几天的时间过去了,你却没有收到任何报纸。又过了几天,你打电话给报社的销售部门询问为什么还没有收到报纸,他们告诉你因为你支付的是一整年的订阅费用,而现在这一年还没有结束,当这一年结束时,你肯定可以一次性完整地收到它们,你会觉得他们有多么不可理喻。值得庆幸的是,这并非订阅的真正运作方式。报纸都具有一定的时效性。在出版后报纸需要及时投递,以确保读者阅读到的内容仍然是新鲜的。此外,你在阅读最新一期的报纸时,记者们正在为未来的某一期报纸撰写内容,同时印刷机正在满速运转,印刷下一期的内容————一切都是并行的。
在开发应用程序代码时,我们可以编写两种风格的代码一一命令式和反应式。

  • 命令式(imperative)的代码非常像上文所提到的那个荒谬的、假想的报纸订阅方式。它由一组串行的任务组成,每次只运行一项任务,每个任务又都依赖于前面的任务。教据会按批次进行处理,在前一项任务还没有完成对当前数据批次的处理时,不能将这些数据递交给下一项处理任务。
  • 反应式(reactive) 的代码则很像真实的报纸订阅方式。它会定义一组用来处数据的任务,但是这些任务可以并行。每项任务处理数据的一个子集,并且在将结果交给处理流程中下一项任务的同时,继续处理数据的另一个子集。

 其实到这里我也只是单纯理解了命令式编程和反应式编程的概念

我们具体来看鱼皮老师的解决方法:

/**
     * 处理响应
     *
     * @param exchange
     * @param chain
     * @return
     */
    public Mono<Void> handleResponse(ServerWebExchange exchange, GatewayFilterChain chain) {
        try {
            ServerHttpResponse originalResponse = exchange.getResponse();
            // 缓存数据的工厂
            DataBufferFactory bufferFactory = originalResponse.bufferFactory();
            // 拿到响应码
            HttpStatus statusCode = originalResponse.getStatusCode();
            if (statusCode == HttpStatus.OK) {
                // 装饰,增强能力
                ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
                    // 等调用完转发的接口后才会执行
                    @Override
                    public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                        log.info("body instanceof Flux: {}", (body instanceof Flux));
                        if (body instanceof Flux) {
                            Flux<? extends DataBuffer> fluxBody = Flux.from(body);
                            // 往返回值里写数据
                            // 拼接字符串
                            return super.writeWith(
                                    fluxBody.map(dataBuffer -> {
                                        // 7. 调用成功,接口调用次数 + 1 invokeCount
                                        byte[] content = new byte[dataBuffer.readableByteCount()];
                                        dataBuffer.read(content);
                                        DataBufferUtils.release(dataBuffer);//释放掉内存
                                        // 构建日志
                                        StringBuilder sb2 = new StringBuilder(200);
                                        List<Object> rspArgs = new ArrayList<>();
                                        rspArgs.add(originalResponse.getStatusCode());
                                        String data = new String(content, StandardCharsets.UTF_8); //data
                                        sb2.append(data);
                                        // 打印日志
                                        log.info("响应结果:" + data);
                                        return bufferFactory.wrap(content);
                                    }));
                        } else {
                            // 8. 调用失败,返回一个规范的错误码
                            log.error("<--- {} 响应code异常", getStatusCode());
                        }
                        return super.writeWith(body);
                    }
                };
                // 设置 response 对象为装饰过的
                return chain.filter(exchange.mutate().response(decoratedResponse).build());
            }
            return chain.filter(exchange); // 降级处理返回数据
        } catch (Exception e) {
            log.error("网关处理响应异常" + e);
            return chain.filter(exchange);
        }
    }

让GPT分析了一下这段代码:

  1. 方法签名:

    public Mono<Void> handleResponse(ServerWebExchange exchange, GatewayFilterChain chain) { 

    这个方法返回一个 Mono<Void>,表示一个异步操作的结果。它接收 ServerWebExchange 和 GatewayFilterChain 作为参数。

  2. 获取原始响应:

    ServerHttpResponse originalResponse = exchange.getResponse(); DataBufferFactory bufferFactory = originalResponse.bufferFactory(); HttpStatus statusCode = originalResponse.getStatusCode(); 
    • 获取当前的 HTTP 响应对象和数据缓冲工厂。
    • 检查响应状态码,如果是 HttpStatus.OK,则继续进行装饰。
  3. 响应装饰器:

    ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) { @Override public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) { 
    • 创建一个 ServerHttpResponseDecorator 的实例,对原始响应进行装饰。
    • 重写 writeWith 方法,以便在响应体被写入时执行自定义逻辑。
  4. 处理响应体:

    if (body instanceof Flux) { Flux<? extends DataBuffer> fluxBody = Flux.from(body); return super.writeWith( fluxBody.map(dataBuffer -> { byte[] content = new byte[dataBuffer.readableByteCount()]; dataBuffer.read(content); DataBufferUtils.release(dataBuffer); String data = new String(content, StandardCharsets.UTF_8); log.info("响应结果:" + data); return bufferFactory.wrap(content); })); } else { log.error("<--- {} 响应code异常", getStatusCode()); } 
    • 如果响应体是 Flux 类型,表示它是一个异步流。使用 map 操作符来处理每个 DataBuffer
    • 读取数据内容,释放内存,并打印日志。
    • 返回一个新的 DataBuffer 包装原始内容。
    • 如果不是 Flux,则记录错误信息。
  5. 设置装饰过的响应:

    return chain.filter(exchange.mutate().response(decoratedResponse).build()); 
    将装饰过的响应设置回 ServerWebExchange 中,并继续调用过滤链。
  6. 异常处理:

    } catch (Exception e) { log.error("网关处理响应异常" + e); return chain.filter(exchange); } 
    捕获异常并记录错误,确保在出现异常时仍然返回响应。

我再举一个生活中的例子来解释到底为什么上面这个案例可以先让 chain.filter(exchange)这个方法执行完再接着执行后面的打印日志(这个不就是我们想要的嘛)

  1. 提交订单(发送请求)

    • 你在外卖应用上选择并提交了订单。这就像你发起了一个HTTP请求。
  2. 餐厅准备菜品(鉴权或其他操作)

    • 餐厅接到订单后开始准备菜品。在这个阶段,可能会涉及到各种操作,比如确认订单、准备食材等。这相当于在接口的处理过程中进行鉴权、验证请求等操作。
  3. 外卖小哥送餐(执行chain.filter(exchange)

    • 当餐厅准备好菜品后,外卖小哥会把餐送到你手中。这个过程就像Flux中的chain.filter(exchange),它负责将请求传递到下一个处理环节。
  4. 拿到外卖并享用(执行写日志的操作)

    • 最后,你拿到外卖并开始享用美食,这就相当于在接口处理完成后执行日志记录、返回响应等操作。

其实是利用到了Flux这个东西提前把请求给完成了,我们才能继续执行后面写日志的操作。

(先简单这样理解,Flux这个东西我还没学过,前端react也不是很了解

先留个todo) 

todo
最后还有一个知识点:就是上面的装饰器设计模式:

装饰器设计模式就是在原有的基础上增强。感觉蛮好理解这个东西

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

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

相关文章

第三期书生大模型实战营之浦语提示词工程实践

一. 基础任务 背景问题&#xff1a;近期相关研究发现&#xff0c;LLM在对比浮点数字时表现不佳&#xff0c;经验证&#xff0c;internlm2-chat-1.8b (internlm2-chat-7b)也存在这一问题&#xff0c;例如认为13.8<13.11。 任务要求&#xff1a;利用LangGPT优化提示词&#…

林轩田机器学习基石——笔记1.2 Learn to Answer Yes/No(如何进行学习)

When can Mechine learn&#xff1f; 2.Learn to Answer Yes/No&#xff08;如何进行学习&#xff09; 2.1perceptron hypothesis set 2.2Perceptron Learning Algorithm 2.3Guarantee of PLA 2.4Non-Separate Data Why can Mechine learn&#xff1f; How can Mechine …

通向 AGI 之路:大型语言模型(LLM)技术精要

ChatGPT出现后惊喜或惊醒了很多人。惊喜是因为没想到大型语言模型&#xff08;LLM,Large Language Model&#xff09;效果能好成这样&#xff1b;惊醒是顿悟到我们对LLM的认知及发展理念&#xff0c;距离世界最先进的想法&#xff0c;差得有点远。我属于既惊喜又惊醒的那一批&a…

Android 12系统源码_Settings(一)认识Preference

前言 想刀一家公司的心事藏不住的&#xff0c;原本只了解一下Android系统应用Settings的配置开关列表中某个开关开启或关闭的时候&#xff0c;系统做了哪些响应操作&#xff0c;结果搞了半天发现完全看不懂。写界面就写界面吧&#xff0c;但是Settings模块完全没有使用Android…

STM32Cubemx在FreeRTOS中使用面向对象的方式使用串口

文章目录 前言一、创建FreeRTOS工程二、创建文件对串口进行封装三、代码编写总结 前言 本篇文章将带大家来学习使用面向对象的方式在FreeRTOS中使用串口&#xff0c;使用面向对象的方法非常适合编写可移植性强的代码&#xff0c;那么这篇文章就带大家来看一下这个代码要怎么写…

Evaluating the Generation Capabilities of Large Chinese Language Models

文章目录 题目摘要相关工作CG-Eval实验 题目 评估大型中文语言模型的生成能力 论文地址&#xff1a;https://arxiv.org/abs/2308.04823 项目地址&#xff1a;http://cgeval.besteasy.com/ 摘要 本文介绍了 CG-Eval&#xff0c;这是有史以来第一个全面的自动化评估框架&#xf…

《Milvus Cloud向量数据库指南》——什么是二进制嵌入?

引言 向量嵌入在现代机器学习和数据科学中已成为不可或缺的工具,它们能够将复杂数据以算法可以理解的数值格式表示。尽管密集嵌入因其能够以最小的信息损失保留语义含义而普遍存在,但随着数据量的增加,它们的计算需求和内存需求也在增加。这种增加促使开发者寻求更高效的数…

Python RPA流程自动化机器人简单案例

RPA&#xff08;Robotic Process Automation&#xff0c;机器人流程自动化&#xff09;是一种通过软件机器人模拟和执行人类用户在计算机上的操作的技术。 下面是pyautogui 键盘操作的常见参数说明&#xff1a; https://blog.csdn.net/wydyzq12/article/details/122008396 以…

TDEngine(taos) 涛思数据库-java写入数据

一、java写入taos简单案例&#xff1a; pom.xml 中加入以下依赖。 <dependency><groupId>com.taosdata.jdbc</groupId><artifactId>taos-jdbcdriver</artifactId><version>3.3.0</version> </dependency> java代码 import…

2024网络安全学习路线,最全保姆级教程,学完直接拿捏!

关键词&#xff1a; 网络安全入门、渗透测试学习、零基础学安全、网络安全学习路线 首先咱们聊聊&#xff0c;学习网络安全方向通常会有哪些问题 前排提示&#xff1a;文末有CSDN独家网络安全资料包&#xff01; 1、打基础时间太长 学基础花费很长时间&#xff0c;光语言都有…

医疗器械网络安全 | 第三方组件安全检测怎么做?

医疗器械软件安全中的第三方组件安全检测是确保医疗器械软件整体安全性的重要环节。以下是如何进行第三方组件安全检测的详细步骤&#xff1a; 一、明确检测目标 首先&#xff0c;需要明确检测的目标和范围&#xff0c;即确定哪些第三方组件需要进行安全检测。这通常包括操作系…

C++初学(10)

10.1、共用体 共用体是一种数据格式&#xff0c;它能够存储不同的数据类型&#xff0c;但只能同时存储其中的一种类型。比如说&#xff1a;结构可以同时存储int、long、和double&#xff0c;而共用体只能存储int、long、或double。共用体的句式与结构相似&#xff0c;但含义不…

计算复杂度论文解读系列(1)《通用顺序搜索问题》---L. A. Levin

《通用顺序搜索问题》 L. A. Levin 摘要 本文研究了几种著名的“顺序搜索类型”问题&#xff0c;并证明这些问题只能在解决任何同类型问题所需的时间内得到解决。 1 简介 在阐明算法的概念之后&#xff0c;证明了许多经典问题的算法不可解性&#xff08;例如&#xff0c;群元…

【时时三省】unity test 测试框架 使用 code blocks 移植

山不在高&#xff0c;有仙则名。水不在深&#xff0c;有龙则灵。 ----CSDN 时时三省 目录 1&#xff0c;使用 Code::Blocks 17.12 创建工程 2&#xff0c;移植文件至该工程下&#xff1a; 移入的文件为: 被移入的文件介绍&#xff1a; 更改代码&#xff1a; 向工程添加文…

案例:LVS负载均衡群集(NAT模式)

目录 集群 集群的含义 集群分类 类型 负载均衡集群&#xff08;Load Balance Cluster&#xff09; 高可用集群&#xff08;High Availability Cluster&#xff09; 高性能运算集群&#xff08;High Performance Computer Cluster&#xff09; 负载均衡集群架构 第一层…

【练习】使用DevEco Studio编写计数器案例

效果展示 默认状态 点击加号 点击减号 知识点 类型转换&#xff08;数字 和 字符串&#xff09; 字符串转数字 方法说明例子Number()字符串 直接转数字&#xff0c;转换失败返回NaN&#xff08;字符串包含非数字&#xff09; let str1: string 1.1 console.log(Number(str1)…

游戏服务架构之 网关

游戏服务架构 长连接 游戏服务的一个特点是&#xff0c;需要维护大量的长连接来保证用户与游戏间的通信&#xff0c;比如tcp长连接、websocket双工通信等。 多服务 游戏类服务器&#xff0c;为了各个模块之间不相互影响&#xff0c;一般都是微服务的方式实现&#xff0c;比…

二叉树前序后序中序遍历

二叉树前序后序中序遍历 创建二叉树代码 private static class TreeNode {public final int value;public TreeNode left;public TreeNode right;public TreeNode(int value) {this.value value;} }public static TreeNode createTree(int[] treeArr) {TreeNode[] root new…

HarmonyOS应用开发者高级认证多选题答案(95%高正确率,8.3更新)

【多选题分数占比高&#xff0c;多选高分&#xff0c;单选稍微做下就能考试通过&#xff01;】 【多选题分数占比高&#xff0c;多选高分&#xff0c;单选稍微做下就能考试通过&#xff01;】 【多选题分数占比高&#xff0c;多选高分&#xff0c;单选稍微做下就能考试通过&a…

MongoDB聚合操作详解

文章目录 聚合操作聚合管道管道&#xff08;Pipeline&#xff09;和阶段&#xff08;Stage&#xff09;常用的聚合阶段运算符准备数据集&#xff0c;执行脚本$project$match$count$group$unwind$limit$skip$sort$lookup聚合操作案例1聚合操作案例2 聚合优化执行顺序内存排序 整…