gRPC golang开发实践

news2024/9/21 12:43:20

gRPC golang开发实践

  • Protobuf
    • 定义消息类型
    • 标量类型
    • 复合类型
    • 引用其它消息类型
  • Protoc使用
    • 安装
    • 使用
    • 语言插件
  • Buf使用
    • 配置和构建buf模块
    • 代码生成
  • 实现gRPC API
    • 初始化go.mod文件
    • 实现服务端代码
    • 实现客户端代码
  • 测试gRPC API
    • 使用bloomRPC客户端工具
    • 使用grpcurl命令行工具
    • 使用buf curl命令
  • grpc-gateway插件
    • 安装
    • 启用REST API反向代理
  • protoc-gen-openapiv2插件
    • 安装
    • 生成swagger文档
  • 参考

Protobuf

gRPC使用protobuf,首先使用protobuf定义服务,然后使用这个文件来生成客户端和服务端的代码。因为pb是跨语言的,因此即使服务端和客户端语言并不一致也是可以互相序列化和反序列化的。

首先介绍protobuf怎么写。protobuf最新版本为proto3,在这里你可以看到详细的文档说明:Language Guide (proto 3)

定义消息类型

protobuf里最基本的类型就是message,每一个message都会有一个或者多个字段(field),其中字段包含如下元素
在这里插入图片描述

  • 类型:类型不仅可以是标量类型(int、string等),也可以是复合类型(enum等),也可以是其他message
  • 字段名:字段名比较推荐的是使用下划线/分隔名称
  • 字段编号:一个message内每一个字段编号都必须唯一的,在编码后其实传递的是这个编号而不是字段名
  • 字段标签:消息字段可以是以下字段之一
    • optional:表示字段不是必需的,也就是说,在序列化的数据中可以不包含这个字段。字段在消息中是可有可无的。如果字段有值,就序列化这个值;如果没有值,就不序列化这个字段。从 Protobuf 版本 3 开始,optional 关键字不再使用,因为所有字段默认都是可选的。字段可以有默认值,如果没有明确赋值,就会使用这个默认值。
    • repeated:此字段类型可以重复零次或多次,也就是数组。系统会保留重复值的顺序。
    • map:这是一个成对的键值对字段
  • 保留字段:为了避免再次使用到已移除的字段可以设定保留字段。如果任何未来用户尝试使用这些字段标识符,协议缓冲区编译器就会报错

标量类型

标量类型会涉及到不同语言和编码方式,以下是部分proto type和Go type的对应关系。

proto TypeGo TypeNotes
doublefloat64
floatfloat32
int32int32使用可变长度的编码。对负数的编码效率低下 - 如果您的字段可能包含负值,请改用 sint32。
uint32uint32使用可变长度的编码。
sint32int32使用可变长度的编码。有符号整数值。与常规 int32 相比,这些函数可以更高效地对负数进行编码。
fixed32uint32始终为 4 个字节。如果值通常大于 2^28,则比 uint32 更高效。
boolbool
stringstring字符串必须始终包含 UTF-8 编码或 7 位 ASCII 文本,并且长度不得超过 232.
bytes[]byte可以包含任意长度的 2^32 字节。

复合类型

  • 数组
message SearchResponse {
  repeated Result results = 1;
}

message Result {
  string url = 1;
  string title = 2;
  repeated string snippets = 3;
}
  • 枚举
message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
  enum Corpus {
    UNIVERSAL = 0;
    WEB = 1;
    IMAGES = 2;
    LOCAL = 3;
    NEWS = 4;
    PRODUCTS = 5;
    VIDEO = 6;
  }
  Corpus corpus = 4;
}
  • 服务
    定义的method仅能有一个入参和出参数。如果需要传递多个参数需要定义成message
service SearchService {
  rpc Search(SearchRequest) returns (SearchResponse);
}

引用其它消息类型

使用import引用另外一个文件的pb

syntax = "proto3";

import "google/protobuf/wrappers.proto";

package ecommerce;

message Order {
  string id = 1;
  repeated string items = 2;
  string description = 3;
  float price = 4;
  google.protobuf.StringValue destination = 5;
}

Protoc使用

protoc就是protobuf的编译器,它把proto文件编译成不同的语言

安装

安装教程, 参见 Protocol Buffer Compiler Installation. 我们采用 Install pre-compiled binaries (any OS) 的方式,从github 版本发布的资源包中获取protoc可执行文件。
在这里插入图片描述

使用

$ protoc --help
Usage: protoc [OPTION] PROTO_FILES

  -IPATH, --proto_path=PATH   指定搜索路径
  --plugin=EXECUTABLE:
  
  ....
 
  --cpp_out=OUT_DIR           Generate C++ header and source.
  --csharp_out=OUT_DIR        Generate C# source file.
  --java_out=OUT_DIR          Generate Java source file.
  --js_out=OUT_DIR            Generate JavaScript source.
  --objc_out=OUT_DIR          Generate Objective C header and source.
  --php_out=OUT_DIR           Generate PHP source file.
  --python_out=OUT_DIR        Generate Python source file.
  --ruby_out=OUT_DIR          Generate Ruby source file
  
   @<filename>                proto文件的具体位置
  • 搜索路径参数
    第一个比较重要的参数就是搜索路径参数,即上述展示的-IPATH, --proto_path=PATH。它表示的是我们要在哪个路径下搜索.proto文件,这个参数既可以用-I指定,也可以使用–proto_path=指定。
    如果不指定该参数,则默认在当前路径下进行搜索;另外,该参数也可以指定多次,这也意味着我们可以指定多个路径进行搜索。

  • 语言插件参数
    语言参数即上述的–cpp_out=,–python_out=等,protoc支持的语言长达13种,且都是比较常见的
    运行help出现的语言参数,说明protoc本身已经内置该语言对应的编译插件,我们无需安装。

Language
C++ (include C++ runtime and protoc)
Java
Python
Objective-C
C#
Ruby
PHP

下面的语言是由google维护,通过protoc的插件机制来实现,所以仓库单独维护

Language
Dart
Go
  • proto文件位置参数
    proto文件位置参数即上述的@参数,指定了我们proto文件的具体位置,如proto/user.proto。

语言插件

  • golang插件
    非内置的语言支持就得自己单独安装语言插件,比如–go_out=对应的是protoc-gen-go,安装命令如下:
$ go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28

可以使用下面的命令来生成代码

$ protoc --go_out=gen --go_opt=paths=source_relative ./proto/user.proto
  • grpc go插件
    在google.golang.org/protobuf中,protoc-gen-go纯粹用来生成pb序列化相关的文件,不再承载gRPC代码生成功能。
    生成gRPC相关代码需要安装grpc-go相关的插件protoc-gen-go-grpc
$ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2

执行code gen命令

$ protoc --go-grpc_out=gen --go-grpc_opt=paths=source_relative ./proto/user.proto

以上两个命令会产生如下文件

  • xx.pb.go, protoc-gen-go的产出物,包含所有类型的序列化和反序列化代码
  • xx_grpc.pb.go, protoc-gen-go-grpc的产出物,包含
    • 定义在XX service中的用来给client调用的接口定义
    • 定义在 XX service中的用来给服务端实现的接口定义

Buf使用

可以看到使用protoc的时候,将遇到两个问题:

  1. 随着使用的插件逐渐增多,插件参数变多,命令行执行并不是很方便和直观。例如后面还将使用grpc-gateway+swagger插件。
$ protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative --grpc-gateway_out=logtostderr=true:. ./proto/user.proto
  1. 依赖某些外部的protobuf文件时,只能通过拷贝到本地的方式,也不够方便.
import "google/api/annotations.proto";

因此诞生了 Buf 这个项目,它除了能解决上述问题,还有额外的功能

  • 破坏性修改(不兼容性)检查
  • linter
  • 集中式的版本管理

配置和构建buf模块

在pb文件的根目录执行buf config init,初始化buf配置文件。

$ buf config init

此时会在根目录多出一个buf.yaml文件,内容为

# buf.yaml
version: v1
breaking:
  use:
    - FILE
lint:
  use:
    - DEFAULT

我们需要显式的指定输入的buf模块(buf模块表示一组被配置、构建和版本控制的Protobuf 文件)。将.proto 文件的子目录路径,添加在buf.yaml 文件modules 字段下

# buf.yaml
version: v2
+modules:
+  - path: proto
lint:
  use:
    - DEFAULT
breaking:
  use:
    - FILE

如果proto文件中依赖第三方的protobuf文件,例如google/type/datetime.proto,可以将依赖添加到buf.yaml 文件的 deps 字段下. 这些protobuf文件是托管在BSR仓库中的模块,例如这里对应的BSR仓库地址是buf.build/googleapis/googleapis

# buf.yaml
version: v2
modules:
  - path: proto
lint:
  use:
    - DEFAULT
breaking:
  use:
    - FILE
+deps:
+  - buf.build/googleapis/googleapis

修改完依赖之后,记得执行命令buf dep update,把你所有的 deps 更新到最新版。此命令会生成 buf.lock 来固定版本。

buf.yaml 文件修改完之后,执行buf build命令将protobuf文件构建为buf镜像。如果没有报错,证明一切顺利,Buf模块设置正确。

代码生成

  • 插件配置
    创建一个buf.gen.yaml ,它是buf生成代码的配置。buf.gen.yaml相比protoc更加直观。
# buf.gen.yaml
version: v2
managed:
  enabled: true
plugins:
  - remote: buf.build/protocolbuffers/go
    out: gen/go
    opt:
      - paths=source_relative  
  - remote: buf.build/grpc/go
    out: gen/go
    opt:
      - paths=source_relative
  - remote: buf.build/grpc-ecosystem/gateway:v2.21.0
    # execute 'go get github.com/grpc-ecosystem/grpc-gateway/v2@v2.21.0' before using this plugin.
    out: gen/go
    opt:
      - paths=source_relative
  - remote: buf.build/grpc-ecosystem/openapiv2:v2.21.0
    out: gen/openapiv2
inputs:
  - directory: proto
  • 生成代码
$ buf generate

buf generate 命令将会

  • 搜索每一个buf.yaml配置里的所有protobuf文件
  • 复制所有protobuf文件到内存
  • 编译所有protobuf文件
  • 执行模版文件里的每一个插件

按以上示例,运行命令之后,就和直接使用protoc命令产出的golang代码一样。
该示例使用了4个插件:go插件、grpc go插件、grpc-gateway插件、swagger插件, 对应的生成代码包含go结构体与pb序列化代码、grpc客户端/服务端stub代码、http 反向代理grpc服务的代码、swagger文档。

实现gRPC API

我们创建一个golang项目,实现RPC服务的客户端和服务端。

初始化go.mod文件

如果你没有处在一个golang模块项目下,在编写golang代码之前,请首先初始化一个项目, 例如

$ go mod init github.com/bufbuild/buf-tour

实现服务端代码

创建一个main.go文件

$ mkdir server
$ touch server/main.go

服务端示例代码如下(来源:examples/helloworld/greeter_server/main.go)

// Package main implements a server for Greeter service.
package main

import (
        "context"
        "flag"
        "fmt"
        "log"
        "net"

        "google.golang.org/grpc"
        pb "google.golang.org/grpc/examples/helloworld/helloworld"
)

var (
        port = flag.Int("port", 50051, "The server port")
)

// server is used to implement helloworld.GreeterServer.
type server struct {
        pb.UnimplementedGreeterServer
}

// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
        log.Printf("Received: %v", in.GetName())
        return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}

func main() {
        flag.Parse()
        lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
        if err != nil {
                log.Fatalf("failed to listen: %v", err)
        }
        s := grpc.NewServer()
        pb.RegisterGreeterServer(s, &server{})
        log.Printf("server listening at %v", lis.Addr())
        if err := s.Serve(lis); err != nil {
                log.Fatalf("failed to serve: %v", err)
        }
}

在以上代码中,pb.UnimplementedGreeterServer就是自动生成的grpc服务对象,它包含proto文件里定义的所有服务方法的stub实现。我们对服务方法进行重写,用真实的实现替换stub实现即可。

构建并运行grpc服务

$ go mod tidy
$ go run server/main.go

实现客户端代码

创建一个main.go文件

$ mkdir client
$ touch client/main.go

客户端主函数代码示例如下

// Package main implements a client for Greeter service.
package main

import (
        "context"
        "flag"
        "log"
        "time"

        "google.golang.org/grpc"
        "google.golang.org/grpc/credentials/insecure"
        pb "google.golang.org/grpc/examples/helloworld/helloworld"
)

const (
        defaultName = "world"
)

var (
        addr = flag.String("addr", "localhost:50051", "the address to connect to")
        name = flag.String("name", defaultName, "Name to greet")
)

func main() {
        flag.Parse()
        // Set up a connection to the server.
        conn, err := grpc.NewClient(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
        if err != nil {
                log.Fatalf("did not connect: %v", err)
        }
        defer conn.Close()
        c := pb.NewGreeterClient(conn)

        // Contact the server and print out its response.
        ctx, cancel := context.WithTimeout(context.Background(), time.Second)
        defer cancel()
        r, err := c.SayHello(ctx, &pb.HelloRequest{Name: *name})
        if err != nil {
                log.Fatalf("could not greet: %v", err)
        }
        log.Printf("Greeting: %s", r.GetMessage())
}

运行客户端代码,调用SayHello方法。

$ go run client/main.go

测试gRPC API

测试我们开发好的gRPC API接口,除了编写特定语言的客户端代码进行调用之外,还可以使用一些通用性的工具

使用bloomRPC客户端工具

在这里插入图片描述

该工具导入proto 接口文件之后,便可生成方法列表和请求模板。

如果proto文件中有import本地依赖模块,添加proto会失败,先将依赖的proto添加至导入路径
在这里插入图片描述

参考:解决导入路径的问题

使用grpcurl命令行工具

grpcurl是一个命令行工具,使用它可以在命令行中访问gRPC服务,就像使用curl访问http服务一样。

注意(注册reflection服务)
该方法要求先在gRPC服务中启用reflection服务

gRPC服务是使用Protobuf(PB)协议的,而PB提供了在运行时获取Proto定义信息的反射功能。grpc-go中的"google.golang.org/grpc/reflection"包就对这个反射功能提供了支持。

在grpc服务端代码,添加两行代码即可

// Package main implements a server for Greeter service.
package main

import (
        ...
        "google.golang.org/grpc/reflection"
)

...

func main() {
        flag.Parse()
        lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
        if err != nil {
                log.Fatalf("failed to listen: %v", err)
        }
        s := grpc.NewServer()
        
        // 注册grpcurl所需的reflection服务
        reflection.Register(s)
        
        pb.RegisterGreeterServer(s, &server{})
        log.Printf("server listening at %v", lis.Addr())
        if err := s.Serve(lis); err != nil {
                log.Fatalf("failed to serve: %v", err)
        }
}

grpcurl 功能和使用示例

  • 查看服务列表:
$ grpcurl -plaintext 127.0.0.1:8080 list
grpc.reflection.v1alpha.ServerReflection
proto.Greeter
  • 查看某个服务的方法列表:
$ grpcurl -plaintext 127.0.0.1:8080 list proto.Greeter
proto.Greeter.SayHello
  • 查看方法定义:
$ grpcurl -plaintext 127.0.0.1:8080 describe proto.Greeter.SayHello
proto.Greeter.SayHello is a method:
rpc SayHello ( .proto.HelloRequest ) returns ( .proto.HelloReply );
  • 查看请求参数:
$ grpcurl -plaintext 127.0.0.1:8080 describe proto.HelloRequest
proto.HelloRequest is a message:
message HelloRequest {
  string name = 1;
}
  • 调用服务,参数传json即可:
$ grpcurl -d '{"name": "abc"}' -plaintext 127.0.0.1:8080  proto.Greeter.SayHello
{
  "message": "hello"
}

使用buf curl命令

该命令可用于调用HTTP RPC 端点地址,要求服务端使用gRPC或者Connect。
buf curl 命令只有一个位置参数, 即被调用的RPC方法的URL。被调用的方法取自URL路径的最后两段。分别是服务名称(全称)和方法名称

$ buf curl --schema ./helloworld.proto --data '{"name": "abc"}' --protocol grpc --http2-prior-knowledge  http://127.0.0.1:50051/helloworld.Greeter/SayHello
{
  "message": "Hello abc"
}

grpc-gateway插件

gRPC-Gateway是protoc的一个插件,它可以从protobuf服务定义生成一个反向代理服务,将RESTful HTTP API 翻译成gRPC。这个反向代理服务根据proto服务定义中的google.api.http 注解生成。
在这里插入图片描述

安装

$ go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway

该命令将安装protoc-gen-grpc-gateway到 $GOBIN目录,请确保 $GOBIN 在 $PATH 环境变量中.

启用REST API反向代理

预先使用protobuf定义好gRPC服务,并代码生成gRPC stub代码,像寻常一样实现gRPC API。我们将使用protoc-gen-grpc-gateway 生成反向代理。

在proto文件中添加google.api.http注解

 syntax = "proto3";
 package your.service.v1;
 option go_package = "github.com/yourorg/yourprotos/gen/go/your/service/v1";
+
+import "google/api/annotations.proto";
+
 message StringMessage {
   string value = 1;
 }

 service YourService {
-  rpc Echo(StringMessage) returns (StringMessage) {}
+  rpc Echo(StringMessage) returns (StringMessage) {
+    option (google.api.http) = {
+      post: "/v1/example/echo"
+      body: "*"
+    };
+  }
 }

自动生成反向代理代码

  • 如果使用protoc

该插件依赖googleapis的proto文件,在protoc运行插件之前,需要把这些依赖准备好,把依赖的proto文件下载到本地目录下,proto 文件在gooleapis 的github仓库里可以找到。

google/api/annotations.proto
google/api/field_behavior.proto
google/api/http.proto
google/api/httpbody.proto

然后运行protoc命令

$ protoc -I . --grpc-gateway_out ./gen/go \
    --grpc-gateway_opt paths=source_relative \
    your/service/v1/your_service.proto
  • 如果使用buf

如果使用buf,可以在buf.yaml文件的 deps字段下添加依赖的proto模块。然后记得执行buf mod update更新依赖

#buf.yaml
version: v2
modules:
  - path: proto
lint:
  use:
    - DEFAULT
breaking:
  use:
    - FILE
+deps:
+  - buf.build/googleapis/googleapis

在 buf.gen.yaml 文件中添加grpc-gateway插件, 示例:

#buf.gen.yaml
version: v2
managed:
  enabled: true
  override:
    - file_option: go_package_prefix
      value: enginefaas.com/bufbuild/buf-tour/gen
plugins:
  # dependencies
  - remote: buf.build/protocolbuffers/go
    out: gen/go
    opt:
      - paths=source_relative  
  - remote: buf.build/grpc/go
    out: gen/go
    opt:
      - paths=source_relative
  - remote: buf.build/grpc-ecosystem/gateway:v2.21.0
    # execute 'go get github.com/grpc-ecosystem/grpc-gateway/v2@v2.21.0' before using this plugin.
    out: gen/go
    opt:
      - paths=source_relative
inputs:
  - directory: proto

然后运行buf generate命令即可

运行restful反向代理服务

我们可以在原来gRPC服务main文件的基础上添加如下代码:

// Package main implements a server for Greeter service.
package main

import (
        ...
        "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
)

var (
        port = flag.Int("port", 50051, "The server port")
)

// server is used to implement helloworld.GreeterServer.
type server struct {
        pb.UnimplementedGreeterServer
}

// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
        log.Printf("Received: %v", in.GetName())
        return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}

func main() {
        flag.Parse()
        lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
        if err != nil {
                log.Fatalf("failed to listen: %v", err)
        }
        s := grpc.NewServer()
        pb.RegisterGreeterServer(s, &server{})
        log.Printf("server listening at %v", lis.Addr())
        go func() {
                if err := s.Serve(lis); err != nil {
                        log.Fatalf("failed to serve: %v", err)
                }
        }()

        // Create a client connection to the gRPC server we just started
        // This is where the gRPC-Gateway proxies the requests
        conn, err := grpc.NewClient(fmt.Sprintf("%s:%d", "127.0.0.1", *port), grpc.WithTransportCredentials(insecure.NewCredentials()))
        if err != nil {
                log.Fatalf("did not connect: %v", err)
        }

        if err != nil {
                log.Fatalln("Failed to dial rpc server:", err)
        }

        gwmux := runtime.NewServeMux()
        // Register User Service
        err = pb.RegisterGreeterHandler(context.Background(), gwmux, conn)
        if err != nil {
                log.Fatalln("Failed to register gateway handlers:", err)
        }

        gwServer := &http.Server{
                Addr:    fmt.Sprintf(":%d", 8081),
                Handler: gwmux,
        }

        log.Printf("Serving gRPC-Gateway on port %d", 8081)
        log.Fatalln(gwServer.ListenAndServe())
}

然后执行go run main.py重新运行服务,将在8081端口暴露gRPC-Gateway代理服务。

protoc-gen-openapiv2插件

安装

$ go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2

该命令将安装protoc-gen-openapiv2到 $GOBIN目录,请确保 $GOBIN 在 $PATH 环境变量中.

生成swagger文档

  • 如果使用protoc

protoc-gen-openapiv2插件支持一些定制的protobuf注解,我们需要依赖额外的proto文件,把依赖的proto文件下载到本地目录下。在grpc-gateway 的github仓库的protoc-gen-openapiv2/options目录下,可以找到proto 文件。

然后运行protoc命令

$ protoc -I . --openapiv2_out ./gen/openapiv2 \
    your/service/v1/your_service.proto
  • 如果使用buf

如果使用buf,可以在buf.yaml文件的 deps字段下添加依赖的proto模块。记得执行buf mod update更新依赖

#buf.yaml
version: v2
modules:
  - path: proto
lint:
  use:
    - DEFAULT
breaking:
  use:
    - FILE
+deps:
+  - buf.build/grpc-ecosystem/grpc-gateway

在 buf.gen.yaml 文件中添加openapiv2插件, 示例:

#buf.gen.yaml
version: v2
managed:
  enabled: true
  override:
    - file_option: go_package_prefix
      value: enginefaas.com/bufbuild/buf-tour/gen
plugins:
  # dependencies
  - remote: buf.build/protocolbuffers/go
    out: gen/go
    opt:
      - paths=source_relative  
  - remote: buf.build/grpc/go
    out: gen/go
    opt:
      - paths=source_relative
  - remote: buf.build/grpc-ecosystem/gateway:v2.21.0
    # execute 'go get github.com/grpc-ecosystem/grpc-gateway/v2@v2.21.0' before using this plugin.
    out: gen/go
    opt:
      - paths=source_relative
  - remote: buf.build/grpc-ecosystem/openapiv2:v2.21.0
    out: gen/openapiv2      
 inputs:
  - directory: proto

然后运行buf generate命令即可生成swagger.json文件

参考

  • 写给go开发者的gRPC教程-protobuf基础
  • buf.build
  • grpc-ecosystem/grpc-gateway — github.com

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

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

相关文章

RCE之突破长度限制

我们在写webshell时通常会遇到过滤&#xff0c;但除了过滤之外还可能会有长度限制&#xff0c;这里就简单说一下关于RCE突破长度限制的技巧 突破16位 例如&#xff1a;PHP Eval函数参数限制在16个字符的情况下 &#xff0c;如何拿到Webshell&#xff1f; <?php $param …

jenkins 安装以及自动构建maven项目并且运行

在这里找到你对应jdk的版本的jenkins包 War Jenkins Packages 我这里用的使java8,所以下载 https://mirrors.jenkins.io/war-stable/2.60.1/jenkins.war 然后jenkins可以安装到centos系统 在本地windows系统运行命令行 scp C:\Users\98090\Downloads\jenkins.war root@192…

在Oxygen中插入图形的三种方法

在Oxygen中有以下几种在内容中插入图形的方法&#xff1a; 方法一 1. 将光标放在想要插入图形的地方&#xff0c;并点击插入图形工具栏 2. 在弹出窗口选择需要插入的图形路径&#xff0c;并做相关的设置 注&#xff1a;图形最好是使用相对路径&#xff0c;这样不依赖于本地路…

NVIDIA H100 GPU,它将如何改变AI和计算领域的游戏规则?

大语言模型 (LLM) 的兴起标志着人工智能 (AI) 时代的重大进步。在这一背景下&#xff0c;Paperspace DigitalOcean 提供的云图形处理单元 (GPU) 已成为高质量 NVIDIA GPU 云服务的领先者&#xff0c;推动了计算技术的前沿发展。 NVIDIA 成立于 1993 年&#xff0c;由三位有远见…

软件测试需要具备的基础知识【功能测试】---后端知识(三)

​ ​ 您好&#xff0c;我是程序员小羊&#xff01; 前言 为了更好的学习软件测试的相关技能&#xff0c;需要具备一定的基础知识。需要学习的基础知识包括&#xff1a; 1、计算机基础 2、前端知识 3、后端知识 4、软件测试理论 后期分四篇文章进行编写&#xff0c;这是第三篇 …

Cycript安装报错 Library not loaded终极解决方案

一、下载安装 Cycript 官方完整 资源下载完成后&#xff0c;解压。目录如下&#xff1a; 二、执行 打开命令终端,cd到对应目录&#xff0c;然后执行./cycript #第一步&#xff1a;cd到解压的目录 cd /xx/cycrpt_0#执行&#xff1a; ./cycript 2.1、报错Library not Loaded …

oled使用 f4软件iic 数字 汉字 小图片 HAL库

基于江科大的oled标准库进行移植 到Hal库上 本人参考了许多大佬的源码 进行更改 由于F4和F1主频不一样 由于F4主频太高 在进行软件iic时需要延时一下 才可驱动oled 本人在网上找了一个开源的us延时函数 已经添加进入 文件分享 通过百度网盘分享的文件&#xff1a;delay&#…

如何自动抓取岗位数据?五种采集技巧

摘要&#xff1a; 本文将深入探讨如何从前程无忧网站自动抓取岗位信息&#xff0c;通过分享五大实用的采集技巧&#xff0c;助您轻松掌握大数据时代的招聘情报。无需编程基础&#xff0c;也能高效获取目标职位详情&#xff0c;优化人力资源管理与市场分析。 正文&#xff1a;…

电脑图片损坏打不开怎么办?能修复吗?

照片和视频是记录和保存现实生活中的事件的最好方式。由于手机储存空间有限&#xff0c;一般我们会把有纪念意义的照片放到电脑上进行保存&#xff0c;但有时难免会遇到照片被损坏打不开的情况&#xff0c;一旦遇到这种情况&#xff0c;先不要急&#xff0c;也不要因为照片打不…

【RISC-V设计-12】- RISC-V处理器设计K0A之验证环境

【RISC-V设计-12】- RISC-V处理器设计K0A之验证环境 文章目录 【RISC-V设计-12】- RISC-V处理器设计K0A之验证环境1.简介2.验证顶层3.顶层代码4.模型结构4.1 地址映射4.2 特殊功能寄存器 5.模型代码6.运行脚本7.总结 1.简介 在前几篇文章中&#xff0c;分别介绍了各个模块的设…

订单增长40%,磁性元件下半年还有哪些挑战?

导语 2024即将过半&#xff0c;哪些终端市场发展势头更好?海运价格上涨又会对磁性元件企业造成哪些影响? 2024年开春以来&#xff0c;比亚迪发起了新一轮价格战&#xff0c;让持续一年的新能源汽车价格战再度升级&#xff0c;也让2024年的市场走势更加扑朔迷离。 第二十二届(…

PMTiles介绍与MapboxGL中使用

概述 本文介绍PMTiles以及PMTiles在MapboxGL中的使用。 PMTiles简介 PMTiles 是一种对瓦片数据的单文件压缩格式。PMTiles 压缩包可以托管在如 S3 这样的商品级存储平台上&#xff0c;并允许创建低成本、零维护的“无服务器”地图应用程序——这些应用程序无需自定义瓦片后端…

手机误操作导致永久删除照片的恢复方法有哪些?

随着手机功能的不断增强和应用程序的不断丰富&#xff0c;人们越来越依赖手机&#xff0c;离不开手机。但有时因为我们自己的失误操作&#xff0c;导致我们手机上重要的照片素材被永久删除&#xff0c;这时我们需要怎么做&#xff0c;才能找回我们被永久删除的照片素材呢&#…

LeetCode.676.实现一个魔法字典

题目描述&#xff1a; 设计一个使用单词列表进行初始化的数据结构&#xff0c;单词列表中的单词 互不相同 。 如果给出一个单词&#xff0c;请判定能否只将这个单词中一个字母换成另一个字母&#xff0c;使得所形成的新单词存在于你构建的字典中。 实现 MagicDictionary 类&a…

前端工程化项目 用npm拉git项目的时候是在是太慢了怎么办

最近在家拉git项目发现npm i之后,开始下得挺快&#xff0c;过会就卡着不动了&#xff0c;大概几分钟后才下好。这对一个有强迫症的码农来说是不能容忍的。 只能退出去 重新下载 其实我们只要换一下国内的下载镜像源就好了 npm config set registry https://registry.npmmirror…

[C++][opencv]基于opencv实现photoshop算法灰度化图像

测试环境】 vs2019 opencv4.8.0 【效果演示】 【核心实现代码】 BlackWhite.hpp #ifndef OPENCV2_PS_BLACKWHITE_HPP_ #define OPENCV2_PS_BLACKWHITE_HPP_#include "opencv2/core.hpp"namespace cv {class BlackWhite { public:float red; //红色的灰度系…

vs code编辑区域右键菜单突然变短

今天打开vs code发现鼠标在编辑区域按右键&#xff0c;出来的菜单只显示一小段 显示不全&#xff0c;而之前的样子是 显示很多项&#xff0c;怎么设置回到显示很多项呢&#xff1f;

自动驾驶TPM技术杂谈 ———— 可行驶区域

文章目录 介绍基于传统计算机视觉的方法基于直接特征的可行驶区域检测基于颜色的可行驶区域检测基于纹理的可行驶区域检测基于边缘的可行驶区域检测 基于间接特征的可行驶区域检测 基于深度学习的方法语义分割基于FCN的可行驶区域分割 介绍 可行驶区域检测主要是为了自动驾驶提…

数据科学的定义,如果做数据科学,非计算机的你,一般来说最好还是选择R语言,图像挖掘除外

一、数据科学&#xff08;Data Science&#xff09; 数据科学的起源可以追溯到1962年&#xff0c;当时统计学家John W. Tukey在他的文章《数据分析的未来》中首次提出了数据分析作为一门独立的科学方法。1974年&#xff0c;计算机学家Peter Naur在《计算机方法的简明调研》中明…

shell外壳与Linux权限

&#x1f308;个人主页&#xff1a;Yui_ &#x1f308;Linux专栏&#xff1a;Linux &#x1f308;C语言笔记专栏&#xff1a;C语言笔记 &#x1f308;数据结构专栏&#xff1a;数据结构 文章目录 1.shell命令以及运行原理2. Linux权限的概念3.Linux权限管理3.1 文件访问者的分类…