Grpc 整合 Nacos SpringBoot 日常使用(Java版本)包括 Jwt 认证

news2024/12/23 15:04:48

前言

最近感到有点子迷茫,天天写业务代码有点麻木,趁着有点空闲时间去了解了下 Grpc 这个框架,一方面是听说他很火,支持多种语言。另一方面也是为了将来可能需要用到他,未雨绸缪一下,当然了本文只是基于使用上来带大家入门 Grpc,应付基本的日常开发足够了,后续有时间给大家分析一波源码,帮助大家更好的理解 net.devh.boot.grpc 这个包里面关于自动装配、Grpc服务是如何注册、@GrpcService以及内置注解是如何生效的。

Grpc 传输介质介绍

Grpc 是基于 Protobuf 序列化传输的,为啥用 Protobuf 那就是传输效率高,Grpc 的基础代码是根据我们编写的 Protobuf 文件,通过 Protobuf 插件逆向生成的,有点类似于 Mybatis 的逆向生成,所以在学习 Grpc 前,我们要先了解下 Protobuf 的基础语法。

Protobuf 语法介绍

就着本文使用到的 proto 文件来介绍吧,如下几个参数类似于 java 中的 import、papackage 这些关键字,对应的作用已用注释说明。

//是否生成多个文件
option java_multiple_files = false;
//生成的代码放在所指定的包下面
option java_package = "com.zzh.grpcapi.rpc";
//生成文件名称
option java_outer_classname = "UserServiceProto";

当我们通过 Protobuf 插件编译完成后,项目对应的 com.zzh.grpcapi.rpc 目录下就会出现如下图的这些文件。我们日后的开发用的就是这些逆向生成的代码。

在这里插入图片描述
但是生成文件到指定目录需要一个前提就是我们做过相关的配置,也就是引入我们的 Protobuf 插件。

Protobuf 插件指定生成文件目录(Maven插件)

在 bulid 下面的 plugins 标签中添加一个 如下的一个 plugin 节点即可,plugin 代码在文章末尾的附录里有。
在这里插入图片描述
插件代码一般都是拿来即用的,唯一需要大家做出一点改动的地方就是如下这俩个节点,outputDirectory 改成你项目中,对应的生成文件需要放的位置,clearOutputDirectory 节点用 false 就行,开发中类中的方法会出现增增减减的情况,用 false 表示对应的逆向文件只更新我们 proto 文件中更新的内容,其他内容不变。而用 true 表示,先删除目录下的所有文件,然后重新生成我们的代码。

<!--指定生成文件目录位置-->
<outputDirectory>${basedir}/src/main/java</outputDirectory>
<!--false:追加,true:清除指定目录下的文件然后覆盖-->
<clearOutputDirectory>false</clearOutputDirectory>

plugin 配置都加好了后 ,依次点击 protobuf: compile , protobuf: compile-custom 即可。(我们的 proto 文件夹必须是在 main 文件下面,不然 proto 文件找不到

在这里插入图片描述

Grpc 服务端配置(YML文件)

除了需要配置一下 Grpc 服务的端口号,其他配置均为 Nacos 的大众配置,为什么 Grpc 需要额外的配置一下端口号呢?原因也很简单 Grpc 是基于 Netty 的二次开发,不管是 Grpc 还是 Netty 最终实现远程通信都离不开 Scoket 这个东西,而 server port 指定的是我们 tomcat 的端口号,grpc server port 指定的则是 Scoket 的端口号。这俩个端口号是俩码事(后续推出一遍源码博客帮助大家了解吧~)

server:
  port: 8089
grpc:
  server:
    port: 8090
spring:
  application:
    name: grpcservice
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
      config:
        server-addr: 127.0.0.1:8848

配置好了后记得引入我们的 api 模块,加入到 dependencies 中即可。

<dependency>
    <groupId>com.zzh</groupId>
    <artifactId>grpcapi</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <scope>compile</scope>
</dependency>

Grpc 服务端 Api 接口实现

我们实现 UserServiceGrpc 中的以 Base 结尾的那个类即可,这个类也是大家业务开发中需要实现的类,然后客户端调用的也是我们实现类中的方法逻辑。指的注意的是:Grpc 的返回数据、异常返回和我们 Java 中接口的返回有点不太一样。

  1. Grpc 返回数据:通过 onNext 方法返回客户端数据。且 onCompleted 方法必须调用,代表此次 Grpc 服务通信结束。
  2. Grpc 异常返回: 通过 ReponseObserver.onError() 方法返回。

这些代码都是固定的,为了加深理解,读者自行去测试看效果将会事半功倍。本文只阐述最基本的代码通用模版。常用的开发中也就是这些个东西了。最后我们用 @GrpcService 注解标注此类为 Grpc 服务类即可。

在这里插入图片描述

Grpc 通信客户端配置(YML文件)

里面就是一些 Nacos 注册的配置还有 Grpc 探测服务的配置,值得一提的是,如项目整合了 Nacos,那么 address: static://127.0.0.1:8090 下文配置中的这一行代码将无需配置,原因很简单,客户端和服务端注册在同一个 Nacos 里面,当客户端需要用到服务端时,根据服务名称寻找即可找到对应的 Grpc 服务,但是当项目没整合 Nacos 时,则需要我们手动配置 Grpc 服务地址。

server:
  port: 8088
grpc:
  client:
    grpc-service-test:
      #address: static://127.0.0.1:8090 #整合nacos后无需设置,grrpc服务端地址,根据服务名寻找对应的服务
      negotiation-type: plaintext
      enableKeepAlive: true
      keepAliveWithoutCalls: true
spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
      config:
        server-addr: 127.0.0.1:8848
  application:
    name: grpcclient

注意我们的 grpc-service-test 不是乱配的,需要和 Nacos 中的服务名对应。
在这里插入图片描述

Grpc 客户端服务调用(阻塞式)

客户端都配置好了之后,我们利用 @GrpcClient 注解,注入我们的 Grpc 服务端实现类,即可在客户端完成调用。日常开发用下图中的阻塞客户端即可,意思就是这次 Grpc 远程调用必须等待服务端处理好请求,客户端收到请求后接着处理客户端后续的逻辑(日常开发最常用的也是这种模式),为阻塞模式。那有的读者就会问了,有没有非阻塞的调用呀,有的请看下文。
在这里插入图片描述

Grpc 客户端服务调用(服务端单向流)

大致代码和阻塞式差不多,但是区别在于 Grpc 的调用是靠 UserServiceStub 完成的,也就是非阻塞客户端(使用场景:聊天室、股票代码、实时天气),非阻塞模式的调用,在不阻塞客户端逻辑的情况下,先出给接口响应,后续的 Grpc 推送过来的消息采用监听的模式接收,整个过程是异步的。因此 Grpc 贴心的为我们提供了 onNext、onError、onCompleted 这三大方法分别代表如下意思。

  1. onNext:Grpc 服务端每推送一条消息给客户端,onNext 方法里面都会同步监听到这条数据
  2. onError:Grpc 服务端发生异常时,onError 方法里面会同步监听到错误信息
  3. onCompleted: Grpc 服务端调用 onCompleted 方法时,客户端同步触发 onCompleted 方法。

在这里插入图片描述
当然非阻塞式方法需要对应的 proto 文件支持,需要哪边(客户端、服务端)具备主动推送消息的能力就在对应的参数(请求参数、返回参数)前面添加 stream 。

rpc loginStream (LoginVo) returns (stream LoginDto) {}

Grpc 服务端非阻塞模式(服务端单向流)

客户端改造好了后,服务端用 for 循环每隔 2 秒,依次调用 onNext 方法即可,客户端的 onNext 监听方法里面也会每隔 2 秒收到来自 Grpc 服务端的消息。

在这里插入图片描述

演示效果:每隔 2 秒收到来自服务端推送过来的消息

在这里插入图片描述

Grpc 双向流

Grpc 还有一个功能就是可以实现,客户端与服务端互推消息。 有点类似于 WebScoket 。使用起来也很简单,客户端直接调用双向流的方法,里面还是那三个监听方法,监听 Grpc 服务端推送过来的消息,同时客户端利用调用方法返回的 StreamObserver 可以去主动推送给 Grpc 服务端。完成了客户端的主动推送。

在这里插入图片描述

而服务端也是利用 StreamObserver 完成去监听客户端发来的消息和推送给客户端消息。下图的代码我是客户端发过来一条,服务端就处理一条推送回给客户端的,各位可以根据自己的业务逻辑来。

在这里插入图片描述

到此 Grpc 几种常用的模式就介绍完了,接下来在说一下 Grpc 中的拦截器吧,这个大家也有可能会用到。

客户端 Grpc 拦截器日志记录、jwt 请求头统一添加

有的时候为了保证 Grpc 服务的安全,一般都需要做认证才能进行调用,利用客户端拦截器我们可以对 Grpc 调用进行日志记录,也可以对所有的 Grpc 调用添加统一的请求头,做 Jwt 校验啥的。我们只需实现 ClientInterceptor 接口,然后在所在类上面加上 @GrpcGlobalClientInterceptor 注解即可。标注这个是全局客户端拦截器。下面的代码我会在所有 Grpc 请求调用前为当期请求设置一个 jwt 请求头,这个 jwt 是服务端给我们颁发的,如果客户端没有检测到 jwt 的存在会携带 ak、sk 参数主动去请求服务端的 jwt 获取接口,完成后存储于客户端,可能有人会说了,jwt 不是无状态的吗?你这样不就成了有状态的了。答:jwt 存储在客户端,相当于我们的前端页面,对于服务端依旧是无状态的。

/**
 * 全局设置 jwt 请求头
 */
@Slf4j
@GrpcGlobalClientInterceptor
public class ClientInterceptor implements io.grpc.ClientInterceptor {
    @Value("${grpc.auth.ak}")
    private String ak;
    @Value("${grpc.auth.sk}")
    private String sk;
    @Value("${grpc.auth.host}")
    private String host;
    @Value("${grpc.auth.port}")
    private int port;
    @Value("${grpc.auth.name}")
    private String serviceName;
    @Autowired
    private RedisTemplate redisTemplate;
    @Override
    public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
        CallOptions myCallOptions = callOptions
                //.withDeadlineAfter(2, TimeUnit.SECONDS) //设置超时
                .withCallCredentials(new CallCredentials() {
                    @Override
                    public void applyRequestMetadata(RequestInfo requestInfo, Executor appExecutor, MetadataApplier applier) {
                        Metadata metadata = new Metadata();
                        Object token = redisTemplate.opsForValue().get(serviceName);
                        String jwt = "";
                        if (null == token) {
                            //根据服务名进行寻找服务,利用 ak、sk 自动登录,将 token 存储于 redis
                            ManagedChannel managedChannel = ManagedChannelBuilder.forTarget(serviceName).usePlaintext().build();
                            UserServiceGrpc.UserServiceBlockingStub userServiceBlockingStub = UserServiceGrpc.newBlockingStub(managedChannel);
                            UserServiceProto.LoginDto login = userServiceBlockingStub
                                    .login(UserServiceProto.LoginVo.newBuilder()
                                            .setName(ak)
                                            .setPassword(sk)
                                            .build());
                            jwt = login.getToken();
                            redisTemplate.opsForValue().set(serviceName, jwt);
                        } else {
                            jwt = String.valueOf(token);
                        }
                        //设置 jwt 请求头
                        metadata.put(Metadata.Key.of("Authorization", Metadata.ASCII_STRING_MARSHALLER), "Bearer " + jwt);
                        applier.apply(metadata);
                    }
                    @Override
                    public void thisUsesUnstableApi() {
                    }
                });
        return new ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(next.newCall(method, myCallOptions)) {
            @Override
            public void sendMessage(ReqT message) {
                //日志记录
                log.info("ClientInterceptor#interceptCall#SimpleForwardingClientCall#sendMessage## request method: {} , param: {} ", method.getFullMethodName(), message.toString());
                super.sendMessage(message);
            }
        };
    }
}

但是上面的代码有一个问题,如果当前项目需要调用多个不同的 Grpc 服务,且每个 Grpc 服务都有各自的认证方式的话,那么这种全局添加请求头这种方式就不太适合了,那么这个时候我们根据调用的 Grpc 服务名(通过 requestInfo.getAuthority()获取),去请求对应的认证接口,然后把拿到的 token 放到请求头或者是哪,这个属于业务问题了,读者根据自己的需求来即可。
在这里插入图片描述

服务端 Grpc 拦截器权限校验

大致逻辑就是如果没带 jwt 请求头提示客户端没权限访问,带了 jwt 请求头去校验 jwt 是否合法,合法了放行。放行和阻塞的代码都是固定的,大家根据自己需要设置对应的 Status、还有上下文参数即可。

@GrpcGlobalServerInterceptor
public class AuthInterceptor implements ServerInterceptor {
    private JwtParser parser = Jwts.parser().setSigningKey(AuthConstant.JWT_KEY);
    @Override
    public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> serverCall, Metadata metadata, ServerCallHandler<ReqT, RespT> serverCallHandler) {
        String authorization = metadata.get(Metadata.Key.of(AuthConstant.AUTH_HEADER, Metadata.ASCII_STRING_MARSHALLER));
        Status status = Status.OK;
        //简单模拟了一下白名单,实际开发中放配置文件
        if ("UserService/login".equals(serverCall.getMethodDescriptor().getFullMethodName()))
            //放行
            return Contexts.interceptCall(Context.current(), serverCall, metadata, serverCallHandler);
        if (authorization == null) {
            status = Status.UNAUTHENTICATED.withDescription("miss authentication token");
        } else if (!authorization.startsWith(AuthConstant.AUTH_TOKEN_TYPE)) {
            status = Status.UNAUTHENTICATED.withDescription("unknown token type");
        } else {
            Jws<Claims> claims = null;
            String token = authorization.substring(AuthConstant.AUTH_TOKEN_TYPE.length()).trim();
            try {
                claims = parser.parseClaimsJws(token);
            } catch (JwtException e) {
                status = Status.UNAUTHENTICATED.withDescription(e.getMessage()).withCause(e);
            }
            if (claims != null) {
                //设置全局上下文属性,下游通过 AuthConstant.AUTH_CLIENT_ID.get(Context.current()) 获取设置的值
                Context ctx = Context.current().withValue(AuthConstant.AUTH_CLIENT_ID, claims.getBody().getSubject());
                //放行
                return Contexts.interceptCall(ctx, serverCall, metadata, serverCallHandler);
            }
        }
        //阻塞
        serverCall.close(status, new Metadata());
        return new ServerCall.Listener<ReqT>() {
        };
    }
}

public interface AuthConstant {
    SecretKey JWT_KEY = Keys.hmacShaKeyFor("zzhhaoshuaizzhhaoshuaizzhhaoshuaizzhhaoshuai".getBytes());
    Context.Key<String> AUTH_CLIENT_ID = Context.key("userId");
    String AUTH_HEADER = "Authorization";
    String AUTH_TOKEN_TYPE = "Bearer";
}

到此你已经可以利用 Grpc 进行日常业务开发了,剩下的就是搬砖操作了~~~~~~~~~~~~

附录(本文所用到的Grpc插件、proto文件、maven依赖)

本文使用到的 Grpc 插件

   <extensions>
        <!-- 兼容eclipse和netbeans中protobuf代码生成插件-->
        <extension>
            <groupId>kr.motd.maven</groupId>
            <artifactId>os-maven-plugin</artifactId>
            <version>1.6.2</version>
        </extension>
    </extensions>
    <plugins>
        <!-- grpc代码生成插件 -->
        <plugin>
            <groupId>org.xolstice.maven.plugins</groupId>
            <artifactId>protobuf-maven-plugin</artifactId>
            <version>0.6.1</version>
            <configuration>
                <protocArtifact>com.google.protobuf:protoc:${protoc.version}:exe:${os.detected.classifier}</protocArtifact>
                <pluginId>grpc-java</pluginId>
                <pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
                <outputDirectory>${basedir}/src/main/java</outputDirectory>
                <clearOutputDirectory>false</clearOutputDirectory>
            </configuration>
            <executions>
                <execution>
                    <goals>
                        <goal>compile</goal>
                        <goal>compile-custom</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>

本文用到的 Nacos、Grpc、Jwt Maven 依赖

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <grpc.version>1.44.0</grpc.version>
        <protobuf.version>3.19.2</protobuf.version>
        <protoc.version>3.19.2</protoc.version>
        <gson.version>2.8.9</gson.version>
        <spring-boot.version>2.3.9.RELEASE</spring-boot.version>

    </properties>

    <!-- gRPC公共依赖管理 -->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!-- gRPC bom -->
            <dependency>
                <groupId>io.grpc</groupId>
                <artifactId>grpc-bom</artifactId>
                <version>${grpc.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!-- protobuf依赖 -->
            <dependency>
                <groupId>com.google.protobuf</groupId>
                <artifactId>protobuf-java-util</artifactId>
                <version>${protobuf.version}</version>
            </dependency>
            <!-- prevent downgrade via protobuf-java-util -->
            <dependency>
                <groupId>com.google.code.gson</groupId>
                <artifactId>gson</artifactId>
                <version>${gson.version}</version>
            </dependency>
        </dependencies>



    </dependencyManagement>


    <dependencies>

        <!-- nacos注册中心-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-nacos-config</artifactId>
            <version>2.2.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <version>2.2.5.RELEASE</version>
        </dependency>
<!--jwt-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.11.5</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.11.5</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <version>0.11.5</version>
            <scope>runtime</scope>
        </dependency>
        <!-- gRPC依赖 -->
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-netty-shaded</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-protobuf</artifactId>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-stub</artifactId>
        </dependency>

        <!-- protobuf依赖 -->
        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java-util</artifactId>
        </dependency>
        <!-- prevent downgrade via protobuf-java-util -->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
        </dependency>
        <dependency>
            <groupId>net.devh</groupId>
            <artifactId>grpc-spring-boot-starter</artifactId>
            <version>2.14.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.8</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.zzh</groupId>
            <artifactId>grpcapi</artifactId>
            <version>0.0.1-SNAPSHOT</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>

本文测试用到的 proto 文件

syntax = "proto3";
//是否生成多个文件
option java_multiple_files = false;
//生成的代码放在所指定的包下面
option java_package = "com.zzh.grpcapi.rpc";
//生成文件名称
option java_outer_classname = "UserServiceProto";
service UserService {
  rpc login (LoginVo) returns (LoginDto) {}
  rpc loginStream (LoginVo) returns (stream LoginDto) {}
  rpc loginDoubleStream (stream LoginVo) returns (stream LoginDto) {}
}

message LoginVo {
  string name = 1;
  string password = 2;

}

message LoginDto {
  string name = 1;
  repeated string ids = 3;
  string password = 2;
  string token=4;
}

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

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

相关文章

Python数据可视化入门教程

什么是数据可视化&#xff1f; 数据可视化是为了使得数据更高效地反应数据情况&#xff0c;便于让读者更高效阅读&#xff0c;通过数据可视化突出数据背后的规律&#xff0c;以此突出数据中的重要因素&#xff0c;如果使用Python做数据可视化&#xff0c;建议学好如下这四个Pyt…

数据可视化是什么?怎么做?看这篇文章就够了

数据可视化是什么 数据可视化主要旨在借助于图形化手段&#xff0c;清晰有效地传达与沟通信息。也就是说可视化的存在是为了帮助我们更好的去传递信息。 我们需要对我们现有的数据进行分析&#xff0c;得出自己的结论&#xff0c;明确要表达的信息和主题&#xff08;即你通过…

https 建立连接过程

从真实的抓包开始 根据抓包结果可以看到 从客户端发起https 请求开始&#xff0c;主要经过以下几个过程&#xff1a; 1、tcp 三次握手 2、浏览器发送 Client Hello 到服务器 3、服务器对Hello 进行响应 4、服务器发送Server Hello 、证书、证书状态、服务器密钥&#xff0c;到…

【Linux服务】web基础与HTTP协议

web基础与HTTP协议 一、域名概述1.1域名空间结构1.2域名注册 二、网页的概念三、HTML概述3.1HTML超文本标记语言 四、Web概述4.1Web1.0与Web2.04.2静态网页4.3动态网页 五、HTTP协议概述5.1HTTP协议版本5.2http请求方法5.3GET 和 POST 比较5.4HTTP状态码5.5HTTP请求流程 一、域…

无代码开发:让程序员更高效,让非编程人员也能参与

说起无代码开发&#xff0c;可能大多数人的第一反应就是&#xff1a;“我不知道&#xff01;” 作为一种能快速实现复杂系统的软件开发模式&#xff0c;无代码开发目前还处于推广阶段。但在我们看来&#xff0c;无代码开发是一个很好的尝试&#xff0c;它能让程序员更高效&…

《汇编语言》- 读书笔记 - 第4章-第一个程序

《汇编语言》- 读书笔记 - 第4章-第一个程序 4.1 一个源程序从写出到执行的过程4.2 源程序程序 4.11. 伪指令1.1 segment ends 声明段1.2 end 结束标记1.3 assume 关联 2. 源程序中的“程序”3. 标号4. 程序的结构5. 程序返回6. 语法错误和逻辑错误 4.3 编辑源程序4.4 编译4.5 …

Electron 我与你,今天不谈技术谈感情!

目录 前言一、无知二、初见三、再见四、相遇五、行动总结 前言 今天不谈技术&#xff0c;谈谈我和 Electron 的缘分。可能有人觉得&#xff0c;或许有些人认为&#xff0c;和一个框架谈感情这不是疯了吗&#xff1f;但是&#xff0c;我相信每个开发者都会有同样的经历&#xf…

数字化浪潮下,运维绕不开的需求升级

伴随企业数据中心规模化、复杂度、设备多样性的发展&#xff0c;运维也迎来史无前例的巨大挑战&#xff0c;运维的重要性被推向高点&#xff0c;对运维平台而言无疑是最好的时代&#xff0c;充分利用大数据和人工智能技术融合来解决实际问题&#xff0c;建立数据要素全周期管理…

XSS基础环境及实验演示教程(适合新手)

目录 前言 环境说明&#xff1a; 1、轻量级 Web 服务器 PHP 2、易受XSS攻击的PHP程序 3、非持久性 XSS 攻击 4、窃取会话cookie 5 注入表单窃取密码 前言 花了一点时间&#xff0c;做了一个“XSS基础环境及实验演示教程”&#xff0c;当然教程很简单&#xff0c;适合刚接触和安…

Electron 如何创建模态窗口?

目录 前言一、模态窗口1.Web页面模态框2.Electron中的模态窗口3.区分父子窗口与模态窗口 二、实际案例使用总结 前言 模态框是一种常用的交互元素&#xff0c;无论是在 Web 网站、桌面应用还是移动 APP 中&#xff0c;都有其应用场景。模态框指的是一种弹出窗口&#xff0c;它…

leetcode 1383. Maximum Performance of a Team(团队的最大performance)

n个工程师&#xff0c;长度为n的speed数组和efficiency数组。 每次最多选k个工程师&#xff0c;取出k个对应的speed和efficiency数字。 performancesum(k个speed) ✖ min(k个efficiency) 可以理解为k个人一起干&#xff0c;效率按最慢的人算&#xff08;一个环节干不完其他人都…

Linux——IO之系统接口+文件描述符详解

IO 文件再次理解系统接口文件操作理解文件描述符 fd 文件再次理解 文件 文件内容 文件属性 其中文件属性也是数据–>即便你创建一个空文件&#xff0c;其也是要占据磁盘攻坚的。 文件操作 文件内容的操作 文件属性的操作 有可能在操作文件的过程中即改变文件的内容&…

Linux---echo命令、反引号`、tail命令、重定向符

1. echo命令 可以使用echo命令在命令行内输出指定内容 语法&#xff1a;echo 输出的内容 无需选项&#xff0c;只有一个参数&#xff0c;表示要输出的内容&#xff0c;复杂内容可以用 ”” 包围 带有空格或 \ 等特殊符号&#xff0c;建议使用双引号包围。 如果不使用双引号…

华为OD机试真题 Java 实现【统计匹配的二元组个数】【2023Q2 200分】

一、题目描述 给定两个数组A和B&#xff0c;若数组A的某个元素A[i]与数组B中的某个元素B[j]满足 A[i] B[j]&#xff0c;则寻找到一个值匹配的二元组(i, j)。 请统计在这两个数组A和B中&#xff0c;一共存在多少个这样的二元组。 二、输入描述 第一行输入数组A的长度M&…

复习之[ 查询帮助 ] 和 [ 输入输出管理 ]

1.查询命令用途--whatis # whatis 命令 : 查询命令的用法 -如果结果出现nothing , 有两种情况&#xff1a; &#xff08;1&#xff09;查询数据库没有更新&#xff0c;此时输入命令 mandb更新数据库即可。 &#xff08;2&#xff09;查询的命令不存在。 2.获得命令的简要帮…

想学渗透,如何入门?

首先 渗透是计算机技术应用的一种&#xff0c;脱离不了基础&#xff0c;您需要学会一门编程语言&#xff0c;任何与计算机相关的都是从学习编程语言开始的&#xff0c;让你对计算机有个初步的认识&#xff0c;将您认识的数字转化为用0和1表示的编码。这个阶段推荐学习Python&a…

​LeetCode解法汇总LCP 33. 蓄水

目录链接&#xff1a; 力扣编程题-解法汇总_分享记录-CSDN博客 GitHub同步刷题项目&#xff1a; https://github.com/September26/java-algorithms 原题链接&#xff1a;力扣 描述&#xff1a; 给定 N 个无限容量且初始均空的水缸&#xff0c;每个水缸配有一个水桶用来打水&…

华芯微特SWM34-IO速度优化

对比测试了一下IO翻转速度在各种函数调用的情况下的差异 CPU运行速度150Mhz,SDRAM开 直接调用翻转函数 while(1) {GPIO_InvBit(GPIOA, PIN0)&#xff1b; }速度大约5Mhz&#xff0c;主要是因为函数调用开销和函数内部的移位和异或操作&#xff0c;增加了指令的运行数量。 vo…

这是JWT 简单使用

JWT 是 Json Web Token的缩写 JSON Web Tokens - jwt.ioJSON Web Token (JWT) is a compact URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is digitally signed using JSON Web Sig…

6-索引

目录 1.什么是索引&#xff1f; PS&#xff1a;数据库引擎简介&#xff08;InnoDB VS MyISAM&#xff09; 2.为什么需要索引&#xff1f; PS&#xff1a;存储数据模组 PS&#xff1a;查询数据存储的目录&#xff1a; 3.索引的作用 PS&#xff1a;索引 VS 书的目录 4.索…