grpc的使用

news2025/2/2 16:37:44

GRPC学习
本文包括grpc的入门使用和四种实现方式


文章目录

      • 一、GRPC 安装和hello world
        • 1、什么是GRPC
        • 2、安装grpc和代码
        • 3、服务端
          • 3.1、取出 server
          • 3.2、挂载方法
          • 3.3、注册服务
          • 3.4、创建监听
        • 4、客户端
      • 二、protobuf语法
      • 三、GRPC server 的使用
        • 1、普通服务
        • 2、流式传入(客户端流)
        • 3、流式返回(服务端流)
        • 4、流式出入(双向流)
      • 四、GRPC-gateway 简单使用

一、GRPC 安装和hello world

1、什么是GRPC

GRPC是谷歌开源的一个可以跨语言RPC的一个框架。可以实现跨语言远程调用

2、安装grpc和代码

快速开始(官网):https://grpc.io/docs/languages/go/quickstart/

go get google.golang.org/grpc
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1

protoc避坑

protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative ./hello_grpc.proto
option go_package="./;golang";

案例

hello_grpc.proto

syntax = "proto3";

option go_package="./;golang";

package hello_grpc;

message Req {
  string message=1;
}

message Res {
  string message=1;
}

service HelloGRPC {
  rpc SayHi(Req) returns (Res);
}

成功

在这里插入图片描述

3、服务端

https://github.com/grpc/grpc-go/blob/master/examples/helloworld/greeter_server/main.go

3.1、取出 server
type server struct {
	hello_grpc.UnimplementedHelloGRPCServer
}
3.2、挂载方法
func (s *server) SayHi(ctx context.Context,req *hello_grpc.Req) (res *hello_grpc.Res, err error) {
	fmt.Println(req.GetMessage())
	return &hello_grpc.Res{Message: "我是从服务端返回的grpc的内容"},nil
}
3.3、注册服务
l,_ := net.Listen("tcp",":8888")
s:=grpc.NewServer()
hello_grpc.RegisterHelloGRPCServer(s,&server{})
3.4、创建监听
s.Serve(l)

案例

import (
   "context"
   "fmt"
   hello_grpc "goclass/grpc_class/pb"
   "google.golang.org/grpc"
   "net"
)

//1.取出server
type server struct {
   hello_grpc.UnimplementedHelloGRPCServer
}

//复写掉了 server接口里面的SayHi方法
func (s *server) SayHi(ctx context.Context, req *hello_grpc.Req) (res *hello_grpc.Res, err error) {
   fmt.Println(req.GetMessage())
   return &hello_grpc.Res{Message: "我是从服务端返回的grpc内容"}, nil
}

func main() {
   l, e := net.Listen("tcp", ":8888")
   fmt.Println(e)
   s := grpc.NewServer()
   hello_grpc.RegisterHelloGRPCServer(s, &server{})
   s.Serve(l)
}

4、客户端

https://github.com/grpc/grpc-go/blob/master/examples/helloworld/greeter_client/main.go

  • 创建一个链接
  • new 一个 client
  • 调用clint的方法
  • 获取返回值

案例

package main

import (
   "context"
   "fmt"
   hello_grpc "goclass/grpc_class/pb"
   "google.golang.org/grpc"
)

func main() {
   conn, e := grpc.Dial("localhost:8888", grpc.WithInsecure())
   fmt.Println(e)
   defer conn.Close()
   client := hello_grpc.NewHelloGRPCClient(conn)
   req, _ := client.SayHi(context.Background(), &hello_grpc.Req{Message: "我从客户端来"})
   fmt.Println(req.GetMessage())
}

二、protobuf语法

0、初始化

控制台
go mod init github/pixel

1、syntax 声明语法版本

syntax = "proto3"

2、package 给当前的proto分配包名称

2.1、option

option go_package="包路径(从mod下开始写);别名"

3、message 定义结构

message 名称(大写开头) {
类型 key(下划线) = 唯一(标识)
}
message Req{
 string user_name = 1;
}

4、类型 声明参数类型

https://developers.google.com/protocol-buffers/docs/proto3#scalar

5、切片(数组)

需要添加关键字 repeated

6、map

map<key 类型,value 类型> key = 标识;

7、类型嵌套

message Req{
 message Person{
  string name = 1;
 }
int32 id  = 1;
Person person = 2;
}

案例
请添加图片描述

person.proto

syntax = "proto3";    //告诉编译器 用proto3 来解读

package person;

option go_package="github/pixel/pb/person;person";

message Home {
  repeated Person persons=1;
  message V {
    string name = 1;
  }
  V v = 2;
}

message Person{
  string name = 1;
  int32 age = 2;
  bool sex = 3;
  repeated string test = 4;
  map <string,string> test_map = 5;

}

build.bat

https://grpc.io/docs/languages/go/quickstart/

protoc --go_out=. --go_opt=paths=source_relative  --go-grpc_out=. --go-grpc_opt=paths=source_relative ./person/person.proto

8、注释

9、字段作废

message Req{
 string user_name = 1;
 string one = 2;
 string two = 3;
 string three = 4;
 string four = 5;
 string five = 6;
 reserved 2,4 to 6;
 reserved "one","five";   //one,five无法使用(类似于保留字)
}

10、枚举类型

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;
}
//一定要存在一个枚举常量值为 0
options allow_alias = true;

当你的枚举里面存在相同常量值的时候 必须设置这个选项

11、oneof

我们可以再message里用 oneof 来包裹某个 段落
在这个作用区域中 所有的值只要被设置过了其他的值会被清空

它不可以被 repeated (不能是数组)

12、

导入定义

import "grpc_class/hello_grpc/test.proto";

java语言不可用 因此 我们就不要用 grpc为了跨语言而定 所以 只要是不通用的 尽量不要用

13、定义服务

在这里插入图片描述

service SearchService {
rpc Search(SearchRequest) returns (SearchResponse);
}
service SearchService {
rpc Search(stream  SearchRequest) returns (SearchResponse);
}
service SearchService {
rpc Search(SearchRequest) returns (stream  SearchResponse);
}
service SearchService {
rpc Search(stream  SearchRequest) returns (stream SearchResponse);
}

三、GRPC server 的使用

1、普通服务

rpc SayHiCommon(Req)returns (Res){};

服务端

  • func (s *service) SayHiCommon(ctx context.Context, req *hello_grpc.Req) (res *hello_grpc.Res,err error) {
    	fmt.Println(req.GetName())
    	return
    }
    
  • func(grpc 服务结构体)方法名(ctx,入参指针)(回参指针,错误){
    }
    

客户端

  • req,_:= client.SayHiCommon(context.Background(),&hello_grpc.Req{Name: "普通传入"})
    fmt.Println(req)
    
  • 返回,错误:=创建好的客户端连接.方法名(ctx,入参)

案例

在这里插入图片描述

person.proto

syntax = "proto3";    //告诉编译器 用proto3 来解读

package person;

option go_package="github/pixel/pb/person;person";

message PersonReq{
  string name = 1;
  int32 age = 2;
}

message PersonRes{
  string name = 1;
  int32 age = 2;
}

service SearchService {
  rpc Search(PersonReq) returns (PersonRes);
  rpc SearchIn(stream PersonReq) returns (PersonRes);
  rpc SearchOut(PersonReq) returns (stream PersonRes);
  rpc SearchIO(stream PersonReq) returns (stream PersonRes);
}

client

package main

import (
	"context"
	"fmt"
	"github/pixel/pb/person"
	"google.golang.org/grpc"
)

func main() {
	l, _ := grpc.Dial("127.0.0.1:8888", grpc.WithInsecure())
	client := person.NewSearchServiceClient(l)
	res, err := client.Search(context.Background(), &person.PersonReq{Name: "我是奇妙"})
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(res)	//name:"我收到了我是奇妙的信息"
}

server

package main

import (
	"context"
	"github/pixel/pb/person"
	"google.golang.org/grpc"
	"net"
)

type personServe struct {
	person.UnimplementedSearchServiceServer
}

func (*personServe) Search(ctx context.Context, req *person.PersonReq) (*person.PersonRes, error) {
	name := req.GetName()
	res := &person.PersonRes{Name: "我收到了" + name + "的信息"}
	return res, nil
}
func (*personServe) SearchIn(context.Context, *person.PersonReq) (*person.PersonRes, error) {
	return nil, nil
}
func (*personServe) SearchOut(context.Context, *person.PersonReq) (*person.PersonRes, error) {
	return nil, nil
}
func (*personServe) SearchIO(context.Context, *person.PersonReq) (*person.PersonRes, error) {
	return nil, nil
}

func main() {
	l, _ := net.Listen("tcp", ":8888")
	s := grpc.NewServer()
	person.RegisterSearchServiceServer(s, &personServe{})
	s.Serve(l)
}

2、流式传入(客户端流)

rpc SayHiInStream(stream Req)returns (Res){};

服务端

  • func (s *service) SayHiInStream(serve hello_grpc.HelloService_SayHiInStreamServer)(err error){
    var res hello_grpc.Res
    res.NameRes = "success"
    for{
    if tem,err:= serve.Recv();err!=nil{
    fmt.Println("传输完了",err)
    serve.SendAndClose(&res)
    break
    }else{
    fmt.Println(tem)
    }
    }
    return
    }
    
  • func(grpc 服务结构体)方法名(grpc 给定好的一个流式回参server)(错误){
     serve.Recv() 进行接收 判断是否 eof 然后 可以在最后关闭之前 用 sendAndClose传输res回去
    }
    

客户端

  • inClient,_ := client.SayHiInStream(context.Background())
    i:=1
    for{
    inClient.Send(&hello_grpc.Req{Sex: "99",Name: "我是流"})
    time.Sleep(1 * time.Second)
    i++
    if i>10 {
    res, err := inClient.CloseAndRecv()
    fmt.Println(res.NameRes,err)
    break
    }
    }
    
  • 通过client上面的grpc提前剑豪的一个流方法 船舰一个 client

    然后对流进行写入操作 在准备关闭的时候 调用 closeAndRecv()关闭并且拿到服务端返回的信息

client

package main

import (
	"context"
	"fmt"
	"github/pixel/pb/person"
	"google.golang.org/grpc"
	"time"
)

func main() {
	l, _ := grpc.Dial("127.0.0.1:8888", grpc.WithInsecure())
	client := person.NewSearchServiceClient(l)
	c, _ := client.SearchIn(context.Background())
	i := 0
	for {
		if i > 10 {
			res, _ := c.CloseAndRecv()
			fmt.Println(res)
			break
		}
		time.Sleep(1 * time.Second)
		c.Send(&person.PersonReq{Name: "我是进来的信息"})
		i++
	}
}
name:"完成了"

server

package main

import (
	"context"
	"fmt"
	"github/pixel/pb/person"
	"google.golang.org/grpc"
	"net"
)

type personServe struct {
	person.UnimplementedSearchServiceServer
}

func (*personServe) Search(ctx context.Context, req *person.PersonReq) (*person.PersonRes, error) {
	name := req.GetName()
	res := &person.PersonRes{Name: "我收到了" + name + "的信息"}
	return res, nil
}

func (*personServe) SearchIn(server person.SearchService_SearchInServer) error {
	for {
		req, err := server.Recv()
		fmt.Println(req)
		if err != nil {
			server.SendAndClose(&person.PersonRes{Name: "完成了"})
			//fmt.Println(err)
			break
		}
	}
	return nil
}
func (*personServe) SearchOut(*person.PersonReq, person.SearchService_SearchOutServer) error {
	return nil
}
func (*personServe) SearchIO(person.SearchService_SearchIOServer) error {
	return nil
}

func main() {
	l, _ := net.Listen("tcp", ":8888")
	s := grpc.NewServer()
	person.RegisterSearchServiceServer(s, &personServe{})
	s.Serve(l)
}

name:"我是进来的信息"
name:"我是进来的信息"
name:"我是进来的信息"
name:"我是进来的信息"
name:"我是进来的信息"
name:"我是进来的信息"
name:"我是进来的信息"
name:"我是进来的信息"
name:"我是进来的信息"
name:"我是进来的信息"
name:"我是进来的信息"
<nil>

3、流式返回(服务端流)

rpc SayHiOutStream(Req)returns (stream Res){};

服务端

  • func (s 结构体) proto定义的方法名(请求结构,grpc帮我们做好的流式serve) (err error)
  • 通过 serve.Send流式发送数据即可

客户端

  • outClient,_ := client.SayHiOutStream(context.Background(),&hello_grpc.Req{Name: "我来拿流了"})
    for{
    req,err := outClient.Recv()
    if err!=nil{
    fmt.Println(err)
    outClient.CloseSend()
    break
    }else{
    fmt.Println(req)
    }
    }
    
  • 通过grpc给我们定义好的方法 传入 ctx和入参 然后开始流式接收(grpc给的一个 client)
    
    outClient.Recv() 不断读取 等待一个 eof CloseSend()方法可以强制断开来源 但是不安全 issue 查看一下 官方告知 serve只要跳出循环 就可以中断链接 
    

server

package main

import (
	"context"
	"fmt"
	"github/pixel/pb/person"
	"google.golang.org/grpc"
	"net"
	"time"
)

type personServe struct {
	person.UnimplementedSearchServiceServer
}

func (*personServe) Search(ctx context.Context, req *person.PersonReq) (*person.PersonRes, error) {
	name := req.GetName()
	res := &person.PersonRes{Name: "我收到了" + name + "的信息"}
	return res, nil
}

func (*personServe) SearchIn(server person.SearchService_SearchInServer) error {
	for {
		req, err := server.Recv()
		fmt.Println(req)
		if err != nil {
			server.SendAndClose(&person.PersonRes{Name: "完成了"})
			//fmt.Println(err)
			break
		}
	}
	return nil
}
func (*personServe) SearchOut(req *person.PersonReq, server person.SearchService_SearchOutServer) error {
	name := req.Name
	i := 0
	for {
		if i > 10 {
			break
		}
		time.Sleep(1 * time.Second)
		server.Send(&person.PersonRes{Name: "我拿到了" + name})
		i++
	}

	return nil
}
func (*personServe) SearchIO(person.SearchService_SearchIOServer) error {
	return nil
}

func main() {
	l, _ := net.Listen("tcp", ":8888")
	s := grpc.NewServer()
	person.RegisterSearchServiceServer(s, &personServe{})
	s.Serve(l)
}

client

package main

import (
	"context"
	"fmt"
	"github/pixel/pb/person"
	"google.golang.org/grpc"
)

func main() {
	l, _ := grpc.Dial("127.0.0.1:8888", grpc.WithInsecure())
	client := person.NewSearchServiceClient(l)
	c, _ := client.SearchOut(context.Background(), &person.PersonReq{Name: "奇妙"})
	for {
		req, err := c.Recv()
		if err != nil {
			fmt.Println(req)
			break
		}
		fmt.Println(req)
	}
}
name:"我拿到了奇妙"
name:"我拿到了奇妙"
name:"我拿到了奇妙"
name:"我拿到了奇妙"
name:"我拿到了奇妙"
name:"我拿到了奇妙"
name:"我拿到了奇妙"
name:"我拿到了奇妙"
name:"我拿到了奇妙"
name:"我拿到了奇妙"
name:"我拿到了奇妙"
<nil>

4、流式出入(双向流)

rpc SayHiIOStream(stream Req)returns (stream Res){};

服务端

package main

import (
	"context"
	"fmt"
	"github/pixel/pb/person"
	"google.golang.org/grpc"
	"net"
	"time"
)

type personServe struct {
	person.UnimplementedSearchServiceServer
}

func (*personServe) Search(ctx context.Context, req *person.PersonReq) (*person.PersonRes, error) {
	name := req.GetName()
	res := &person.PersonRes{Name: "我收到了" + name + "的信息"}
	return res, nil
}

func (*personServe) SearchIn(server person.SearchService_SearchInServer) error {
	for {
		req, err := server.Recv()
		fmt.Println(req)
		if err != nil {
			server.SendAndClose(&person.PersonRes{Name: "完成了"})
			//fmt.Println(err)
			break
		}
	}
	return nil
}
func (*personServe) SearchOut(req *person.PersonReq, server person.SearchService_SearchOutServer) error {
	name := req.Name
	i := 0
	for {
		if i > 10 {
			break
		}
		time.Sleep(1 * time.Second)
		server.Send(&person.PersonRes{Name: "我拿到了" + name})
		i++
	}

	return nil
}
func (*personServe) SearchIO(server person.SearchService_SearchIOServer) error {
	i := 0
	str := make(chan string)
	go func() {
		for {
			i++
			req, _ := server.Recv()
			if i > 10 {
				str <- "结束"
				break
			}
			str <- req.Name
		}
	}()

	for {
		s := <-str
		if s == "结束" {
			server.Send(&person.PersonRes{Name: s})
			break
		}
		server.Send(&person.PersonRes{Name: s})
	}
	return nil
}

func main() {
	l, _ := net.Listen("tcp", ":8888")
	s := grpc.NewServer()
	person.RegisterSearchServiceServer(s, &personServe{})
	s.Serve(l)
}

客户端

package main

import (
	"context"
	"fmt"
	"github/pixel/pb/person"
	"google.golang.org/grpc"
	"sync"
	"time"
)

func main() {
	l, _ := grpc.Dial("127.0.0.1:8888", grpc.WithInsecure())
	client := person.NewSearchServiceClient(l)
	c, _ := client.SearchIO(context.Background())
	wg := sync.WaitGroup{}
	wg.Add(2)
	go func() {
		for {
			time.Sleep(1 * time.Second)
			err := c.Send(&person.PersonReq{Name: "奇妙"})
			if err != nil {
				wg.Done()
				break

			}
		}

	}()
	go func() {
		for {
			req, err := c.Recv()
			if err != nil {
				fmt.Println(err)
				wg.Done()

				break
			}
			fmt.Println(req)
		}
		wg.Done()
	}()

	wg.Wait()
}

四、GRPC-gateway 简单使用

文档:Introduction to the gRPC-Gateway | gRPC-Gateway (grpc-ecosystem.github.io)

  • 给grpc生成的文件附加一个http1.1的restful供外界访问

  • 安装

    go get github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway
    
  • 两个依赖包

    • https://github.com/googleapis/googleapis/blob/master/google/api/annotations.proto
      
    • https://github.com/googleapis/googleapis/blob/master/google/api/http.proto
      
  • 生成语句变更 添加 一句gateway生成方式

    • --grpc-gateway_out . --grpc-gateway_opt paths=source_relative
      
    • //完整:
      protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative --grpc-gateway_out . --grpc-gateway_opt paths=source_relative ./hello_grpc/hello_grpc.proto
      
  • proto变更

    • //server增加option
      option (google.api.http) = {
      	post: "/api/hello"
      	body: "*"
      };
      
  • 书写网关代码

    • //创建一个客户端
      conn, _ := grpc.DialContext(
      context.Background(),
      "127.0.0.1:8888",
      grpc.WithBlock(),
      grpc.WithInsecure(),
      )
      
    • 创建一个mux
      mux := runtime.NewServeMux()
      
    • //创建http服务
      gwServer := &http.Server{ Addr: ":8090", Handler: gwmux, }
      
    • //注册网关handle
      _ = hello_grpc.RegisterHelloServiceHandler(context.Background(),gwmux,conn)
      
    • //监听网关
      gwServer.ListenAndServe()
      

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

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

相关文章

通达信破解接口怎么委托下单?

通达信破解接口主要是利用数学公式建立模型&#xff0c;通过大量数据判断未来价格走势&#xff0c;通过程序选股。虽然选股也比较广泛&#xff0c;但也能覆盖A股市场的四千多只股票&#xff0c;能排除强行涨跌等人为因素&#xff0c;执行的纪律性强。所以对于通达信破解接口对股…

【笔记】git 修改之前的提交记录信息(git commit -m ‘...‘)

文章目录一、修改最后一条提交记录信息二、修改前面某条或某几条提交记录信息一、修改最后一条提交记录信息 git commit --amend进入vi编辑器后&#xff1a; 按i下方出现’- - 插入 - -‘的提示时&#xff0c;便可编辑提交记录信息按ESC&#xff0c;输入:wq保存退出&#xff0…

ICG衍生物ICG-Sulfo-OSu的产品描述及保存建议

中文名称 ICG-Sulfo-OSu 英文名字 ICG-Sulfo-OSu 凯新生物描述: &#xff08;ICG&#xff09;是一种用于医学诊断的菁染料它用于测定心输出量、肝功能和肝血流&#xff0c;以及眼科血管造影它的峰值光谱吸收接近800 nm这些红外频率穿透视网膜层&#xff0c;使ICG血管造影能够比…

【STA】(2)概念

目录 1.CMOS逻辑设计 1.1 基本MOS结构 1.2 COMS逻辑门 1.3 标准单元 2.CMOS单元建模 3.电平翻转波形 4.传播延迟 5.波形的转换率 6.信号之间的偏移 7. 时序弧和单调性 8.最小和最大时序路径 9.时钟域 10.工作条件 1.CMOS逻辑设计 1.1 基本MOS结构 MOS(Metal Oxide…

2022年Python笔试选择题及答案(秋招)

2022年Python笔试选择题及答案&#xff08;秋招&#xff09; &#x1f3e0;个人主页&#xff1a;编程ID &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是编程ID&#xff0c;一个想要与大家共同进步的程序员儿 &#x1f9d1;如果各位哥哥姐姐在准备面试&#xff0c;找…

【Redis-11】Redis事务实现原理

Redis通过MULTI、EXEC、WATCH等命令来实现事务的功能&#xff0c;事务提供了一种将多个命令请求打包&#xff0c;然后一次性&#xff0c;顺序性的执行多个命令的机制。在事务执行期间&#xff0c;服务器不会中断事务去执行其他客户端的命令&#xff0c;他会讲事务中所有命令执行…

谈主成分分析/因子分析中的特征值“矩阵近似”

主成分分析和因子分析是数据降维的常用手段&#xff0c;其中以特征值为载体&#xff0c;在不断降维“近似”原本的协方差矩阵。 CSDN中一些文章在介绍这个问题或者叫“特征值分解”时&#xff0c;讲得都比较学术化&#xff0c;今天用一个小例子&#xff0c;还是面向新人&#…

Redis高可用之哨兵机制实现细节

Redis高可用之哨兵机制实现细节 本文来自我的 technotes [1] Redis篇&#xff0c;欢迎你常来逛逛。 正文 在上一篇的文章《Redis高可用全景一览》中&#xff0c;我们学习了 Redis 的高可用性。高可用性有两方面含义&#xff1a;一是服务少中断&#xff0c;二是数据少丢失。主…

【树莓派不吃灰】兄弟连篇⑥ Linux系统进程管理

目录1、进程查看1.1 ps1.2 top1.3 pstree2、终止进程2.1 kill2.2 killall2.3 pkill3、工作管理4、系统资源查看4.1 vmstat 监控系统资源4.2 dmesg 开机内核检测信息4.3 free 查看内存使用4.4 查看cpu信息4.5 uptime4.6 uname4.7 判断当前系统位数4.8 查询当前linux发行版本4.9 …

Windows及Kail安装配置

apache在kali环境搭建 Kali虚拟机中是包含有Apache的&#xff0c;在/etc目录下ls即可显示出来&#xff0c; 所以这里只需要进行配置就可以了。 图1.1 Apache2目录 打开Apache服务&#xff0c;开启后可以使用status命令查看服务状态。 /etc/init.d/apache2 start /etc/init.d…

【5】控制语句

指针 Go中不用“->”运算符&#xff0c;用的是 “.” 选择符“&”&#xff1a;取地址符“*”&#xff1a;访问目标对象符默认值为&#xff1a;nil (不是NULL)、- -:作为语句&#xff0c;只可以自己放一行&#xff0c;而且放在右边&#xff08;不是表达式&#xff09; …

一款强大的API接口文档管理工具(Smart-Doc + Torna)

【本文由龙飞同学供稿】 在团队协作开发项目的时候&#xff0c;接口文档承担着向其他开发人员说明接口相关信息的重要任务&#xff0c;因此&#xff0c;一份清晰而又相近的接口文档至关重要。 但是&#xff0c;写接口文档的痛苦想必各位开发人员都体验过&#xff0c;明明写接…

在职读研有意义么?来社科院与杜兰大学金融管理硕士项目探寻答案

在职场当我们遇到变化&#xff0c;尤其是发展上有瓶颈期的时候&#xff0c;我们会寻找向内突破&#xff0c;通过提升自己的核心竞争力来应对&#xff0c;通过再学习来增加自身的优势。那么在职读研有意义吗&#xff1f;我们来社科院与杜兰大学金融管理硕士项目来探寻。 一、读在…

Postman接口测试之Mock快速入门

一、Mock简介 1.Mock定义 Mock是一种比较特殊的测试技巧&#xff0c;可以在没有依赖项的情况下进行接口或单元测试。通常情况下&#xff0c;Mock与其他方法的区别是&#xff0c;用于模拟代码依赖对象&#xff0c;并允许设置对应的期望值。简单一点来讲&#xff0c;就是Mock创建…

【专业数据】八.2020~2022年北京交通大学【人工智能】专业复试线/分数线差/计划招生数/复试数/录取数/复试比例/录取率

文章目录 1.专业介绍2.2020-2022年国家线/复试线/分数线差2.1.数据总览2.2.数据指标2.2.1.复试分数线2.2.2.分数线差3.2020-2022年计划招生数/复试数/录取数/复试比例/录取率3.1.数据总览3.2.数据指标3.2.1.复试比例3.2.2.录取率4.参考资料欢迎订阅本专栏:《北交计算机复试经验…

python+appium(4)

conftest进阶使用 思路&#xff1a;我们平时导包多&#xff0c;而且经常使用某一段代码&#xff0c;一般我们使用封装的思想进行优化&#xff0c;pytest中conftest模块提供了更高效的方法 你可以添加任何想一次性封装的代码&#xff0c;这里吧代码变成变量&#xff0c;需要使…

Android APP 自动化测试搭建:Appium + Genymotion + [Robot Framework] Open Application

Android APP 自动化测试搭建&#xff1a; Appium Genymotion [Robot Framework] Open Application1. 安装 Android SDK2. 安装 ADB&#xff08;Android Debug Bridge&#xff09;3. 配置 platform-tools 目录(1) 复制 adb 等工具(2) 添加 apksigner.jar4. 配置 Appium Server…

C语言—函数

函数&#xff1a;将代码块封装成一个个不同的函数&#xff0c;在使用时可以多次的调用&#xff0c;不需要关心内部的实现&#xff1b; #include <stdio.h>void MyPrint() {printf("&&&&&&&&&&&\n");printf("…

BaseDet: 走过开发的弯路

BaseDet 开源啦&#xff01;该 repo 提供了一些经典的检测 SOTA 模型以及相关组件&#xff0c;欢迎大家按需取用~~GitHub&#xff1a;https://github.com/megvii-research/basedetMegStudio 使用示例&#xff1a;https://studio.brainpp.com/project/28826?nameBaseDet%E4%BD%…

重磅!TDengine 3.2.0 正式发布

近日&#xff0c;TDengine 3.0.2.0 正式发布了。这是自今年 8 月份 TDengine 3.0 发布以来的第一个重要改进版本。 TDengine 3.0 带来了几大核心特性&#xff0c;包括云原生架构、流式计算&#xff0c;还增强了数据订阅功能&#xff1b;更重要的是&#xff0c;3.0 系列版本开始…