0%

前言#

上一篇介绍了服务端流式RPC,客户端发送请求到服务器,拿到一个流去读取返回的消息序列。 客户端读取返回的流的数据。本篇将介绍客户端流式RPC

客户端流式RPC:与服务端流式RPC相反,客户端不断的向服务端发送数据流,而在发送结束后,由服务端返回一个响应。

情景模拟:客户端大量数据上传到服务端。

新建proto文件#

新建client_stream.proto文件

1.定义发送信息

Copy

// 定义流式请求信息 message StreamRequest{ //流式请求参数 string stream_data = 1; }

2.定义接收信息

Copy

// 定义响应信息 message SimpleResponse{ //响应码 int32 code = 1; //响应值 string value = 2; }

3.定义服务方法RouteList

客户端流式rpc,只要在请求的参数前添加stream即可

Copy

service StreamClient{ // 客户端流式rpc,在请求的参数前添加stream rpc RouteList (stream StreamRequest) returns (SimpleResponse){}; }

4.编译proto文件

进入client_stream.proto所在目录,运行指令:

protoc --go_out=plugins=grpc:./ ./client_stream.proto

创建Server端#

1.定义我们的服务,并实现RouteList方法

Copy

`type SimpleService struct{}

func (s *SimpleService) RouteList(srv pb.StreamClient_RouteListServer) error {
for {

    res, err := srv.Recv()
    if err == io.EOF {
        
        return srv.SendAndClose(&pb.SimpleResponse{Value: "ok"})
    }
    if err != nil {
        return err
    }
    log.Println(res.StreamData)
}

}`

2.启动gRPC服务器

Copy

`const (

Address string = ":8000"

Network string = "tcp"

)

func main() {

listener, err := net.Listen(Network, Address)
if err != nil {
    log.Fatalf("net.Listen err: %v", err)
}
log.Println(Address + " net.Listing...")

grpcServer := grpc.NewServer()

pb.RegisterStreamClientServer(grpcServer, &SimpleService{})


err = grpcServer.Serve(listener)
if err != nil {
    log.Fatalf("grpcServer.Serve err: %v", err)
}

}`

运行服务端

Copy

go run server.go :8000 net.Listing...

创建Client端#

1.创建调用服务端RouteList方法

Copy

`func routeList() {

stream, err := streamClient.RouteList(context.Background())
if err != nil {
    log.Fatalf("Upload list err: %v", err)
}
for n := 0; n < 5; n++ {
    
    err := stream.Send(&pb.StreamRequest{StreamData: "stream client rpc " + strconv.Itoa(n)})
    if err != nil {
        log.Fatalf("stream request err: %v", err)
    }
}

res, err := stream.CloseAndRecv()
if err != nil {
    log.Fatalf("RouteList get response err: %v", err)
}
log.Println(res)

}`

2.启动gRPC客户端

Copy

`const Address string = “:8000”

var streamClient pb.StreamClientClient

func main() {

conn, err := grpc.Dial(Address, grpc.WithInsecure())
if err != nil {
    log.Fatalf("net.Connect err: %v", err)
}
defer conn.Close()


streamClient = pb.NewStreamClientClient(conn)
routeList()

}`

运行客户端

Copy

go run client.go code:200 value:"hello grpc" value:"ok"

服务端不断从客户端获取到数据

Copy

stream client rpc 0 stream client rpc 1 stream client rpc 2 stream client rpc 3 stream client rpc 4

思考#

服务端在没有接受完消息时候能主动停止接收数据吗(很少有这种场景)?

答案:可以的,但是客户端代码需要注意EOF判断

1.我们把服务端的RouteList方法实现稍微修改,当接收到一条数据后马上调用SendAndClose()关闭stream.

Copy

`func (s *SimpleService) RouteList(srv pb.StreamClient_RouteListServer) error {
for {

    res, err := srv.Recv()
    if err == io.EOF {
        
        return srv.SendAndClose(&pb.SimpleResponse{Value: "ok"})
    }
    if err != nil {
        return err
    }
    log.Println(res.StreamData)
    return srv.SendAndClose(&pb.SimpleResponse{Value: "ok"})
}

}`

2.再把客户端调用RouteList方法的实现稍作修改

Copy

`func routeList() {

stream, err := streamClient.RouteList(context.Background())
if err != nil {
    log.Fatalf("Upload list err: %v", err)
}
for n := 0; n < 5; n++ {
    
    err := stream.Send(&pb.StreamRequest{StreamData: "stream client rpc " + strconv.Itoa(n)})
    
    if err == io.EOF {
        break
    }
    if err != nil {
        log.Fatalf("stream request err: %v", err)
    }
}

res, err := stream.CloseAndRecv()
if err != nil {
    log.Fatalf("RouteList get response err: %v", err)
}
log.Println(res)

}`

客户端Send()需要检测err是否为EOF,因为当服务端在消息没接收完前主动调用SendAndClose()关闭stream,若此时客户端继续执行Send(),则会返回EOF错误。

总结#

本篇介绍了客户端流式RPC的简单使用,下篇将介绍双向流式RPC

教程源码地址:https://github.com/Bingjian-Zhu/go-grpc-example
参考:gRPC官方文档中文版

前言#

上一篇介绍了简单模式RPC,当数据量大或者需要不断传输数据时候,我们应该使用流式RPC,它允许我们边处理边传输数据。本篇先介绍服务端流式RPC

服务端流式RPC:客户端发送请求到服务器,拿到一个流去读取返回的消息序列。 客户端读取返回的流,直到里面没有任何消息。

情景模拟:实时获取股票走势。

1.客户端要获取某原油股的实时走势,客户端发送一个请求

2.服务端实时返回该股票的走势

新建proto文件#

新建server_stream.proto文件

1.定义发送信息

Copy

// 定义发送请求信息 message SimpleRequest{ // 定义发送的参数,采用驼峰命名方式,小写加下划线,如:student_name // 请求参数 string data = 1; }

2.定义接收信息

Copy

// 定义流式响应信息 message StreamResponse{ // 流式响应数据 string stream_value = 1; }

3.定义服务方法ListValue

服务端流式rpc,只要在响应数据前添加stream即可

Copy

// 定义我们的服务(可定义多个服务,每个服务可定义多个接口) service StreamServer{ // 服务端流式rpc,在响应数据前添加stream rpc ListValue(SimpleRequest)returns(stream StreamResponse){}; }

4.编译proto文件

进入server_stream.proto所在目录,运行指令:

protoc --go_out=plugins=grpc:./ ./server_stream.proto

创建Server端#

1.定义我们的服务,并实现ListValue方法

Copy

`type StreamService struct{}

func (s *StreamService) ListValue(req *pb.SimpleRequest, srv pb.StreamServer_ListValueServer) error {
for n := 0; n < 5; n++ {

    err := srv.Send(&pb.StreamResponse{
        StreamValue: req.Data + strconv.Itoa(n),
    })
    if err != nil {
        return err
    }
}
return nil

}`

初学者可能觉得比较迷惑,ListValue的参数和返回值是怎样确定的。其实这些都是编译proto时生成的.pb.go文件中有定义,我们只需要实现就可以了。

2.启动gRPC服务器

Copy

`const (

Address string = ":8000"

Network string = "tcp"

)

func main() {

listener, err := net.Listen(Network, Address)
if err != nil {
    log.Fatalf("net.Listen err: %v", err)
}
log.Println(Address + " net.Listing...")



grpcServer := grpc.NewServer()

pb.RegisterStreamServerServer(grpcServer, &StreamService{})


err = grpcServer.Serve(listener)
if err != nil {
    log.Fatalf("grpcServer.Serve err: %v", err)
}

}`

运行服务端

Copy

go run server.go :8000 net.Listing...

创建Client端#

1.创建调用服务端ListValue方法

Copy

`func listValue() {

req := pb.SimpleRequest{
    Data: "stream server grpc ",
}

stream, err := grpcClient.ListValue(context.Background(), &req)
if err != nil {
    log.Fatalf("Call ListStr err: %v", err)
}
for {
    
    res, err := stream.Recv()
    
    if err == io.EOF {
        break
    }
    if err != nil {
        log.Fatalf("ListStr get stream err: %v", err)
    }
    
    log.Println(res.StreamValue)
}

}`

2.启动gRPC客户端

Copy

`const Address string = “:8000”

var grpcClient pb.StreamServerClient

func main() {

conn, err := grpc.Dial(Address, grpc.WithInsecure())
if err != nil {
    log.Fatalf("net.Connect err: %v", err)
}
defer conn.Close()


grpcClient = pb.NewStreamServerClient(conn)
route()
listValue()

}`

运行客户端

Copy

go run client.go stream server grpc 0 stream server grpc 1 stream server grpc 2 stream server grpc 3 stream server grpc 4

客户端不断从服务端获取数据

思考#

假如服务端不停发送数据,类似获取股票走势实时数据,客户端能自己停止获取数据吗?

答案:可以的

1.我们把服务端的ListValue方法稍微修改

Copy

`func (s *StreamService) ListValue(req *pb.SimpleRequest, srv pb.StreamServer_ListValueServer) error {
for n := 0; n < 15; n++ {

    err := srv.Send(&pb.StreamResponse{
        StreamValue: req.Data + strconv.Itoa(n),
    })
    if err != nil {
        return err
    }
    log.Println(n)
    time.Sleep(1 * time.Second)
}
return nil

}`

2.再把客户端调用ListValue方法的实现稍作修改,就可以得到结果了

Copy

`func listValue() {

req := pb.SimpleRequest{
    Data: "stream server grpc ",
}


stream, err := grpcClient.ListValue(context.Background(), &req)
if err != nil {
    log.Fatalf("Call ListStr err: %v", err)
}
for {
    
    res, err := stream.Recv()
    
    if err == io.EOF {
        break
    }
    if err != nil {
        log.Fatalf("ListStr get stream err: %v", err)
    }
    
    log.Println(res.StreamValue)
    break
}


stream.CloseSend()

}`

只需要调用CloseSend()方法,就可以关闭服务端的stream,让它停止发送数据。值得注意的是,调用CloseSend()后,若继续调用Recv(),会重新激活stream,接着当前的结果继续获取消息。

这能完美解决客户端暂停->继续获取数据的操作。

总结#

本篇介绍了服务端流式RPC的简单使用,客户端发起一个请求,服务端不停返回数据,直到服务端停止发送数据或客户端主动停止接收数据为止。下篇将介绍客户端流式RPC

教程源码地址:https://github.com/Bingjian-Zhu/go-grpc-example
参考:gRPC官方文档中文版

前言#

  • gRPC 是一个高性能、开源和通用的 RPC 框架,面向移动和 HTTP/2 设计,带来诸如双向流、流控、头部压缩、单 TCP 连接上的多复用请求等特。这些特性使得其在移动设备上表现更好,更省电和节省空间占用。

  • 在 gRPC 里客户端应用可以像调用本地对象一样直接调用另一台不同的机器上服务端应用的方法,使得您能够更容易地创建分布式应用和服务。

  • gRPC 默认使用 protocol buffers,这是 Google 开源的一套成熟的结构数据序列化机制,它的作用与 XML、json 类似,但它是二进制格式,性能好、效率高(缺点:可读性差)。

安装 protobuf#

1.下载地址:https://github.com/protocolbuffers/protobuf/releases

根据自身电脑的操作系统,选择最新的releases版本下载

2.解压后在bin目录找到protoc.exe,然后把它复制到GOBIN目录下

一般操作是把protoc.exe所在的目录配到环境变量里,这里直接把protoc.exe复制到GOBIN目录下,前提是环境变量已经配置了GOBIN环境变量。

3.打开cmd,运行protoc --version

成功打印当前版本信息证明安装成功了。

安装相关包#

安装 golang 的proto工具包

go get -u github.com/golang/protobuf/proto

安装 goalng 的proto编译支持

go get -u github.com/golang/protobuf/protoc-gen-go

安装 gRPC 包

go get -u google.golang.org/grpc

创建并编译proto文件#

1.新建proto文件夹,在里面新建simple.proto文件

Copy

`syntax = “proto3”;// 协议为proto3

package proto;

// 定义发送请求信息
message SimpleRequest{
// 定义发送的参数
// 参数类型 参数名 标识号(不可重复)
string data = 1;
}

// 定义响应信息
message SimpleResponse{
// 定义接收的参数
// 参数类型 参数名 标识号(不可重复)
int32 code = 1;
string value = 2;
}

// 定义我们的服务(可定义多个服务,每个服务可定义多个接口)
service Simple{
rpc Route (SimpleRequest) returns (SimpleResponse){};
}`

2.编译proto文件

cmd进入simple.proto所在目录,运行以下指令进行编译

protoc --go_out=plugins=grpc:./ ./simple.proto

VSCode-proto3插件介绍#

使用VSCode的朋友看这里,博主介绍一个VSCode插件,方便对编辑和编译proto文件。

  • 扩展程序中搜索 VSCode-proto3,然后点击安装。
  • 在设置中找到setting.json文件,添加vscode-proto3插件配置

Copy

`”protoc”: {

    "path": "C:\\Go\\bin\\protoc.exe",
    
    "compile_on_save": true,
    "options": [
        
        "--go_out=plugins=grpc:."
    ]
}` 

每次编辑完proto文件后,只需要保存,它就会自动帮助完成编译。而且代码有高亮显示,代码自动补全,代码格式化等功能。

教程源码地址:https://github.com/Bingjian-Zhu/go-grpc-example

前言#

gRPC主要有4种请求和响应模式,分别是简单模式(Simple RPC)服务端流式(Server-side streaming RPC)客户端流式(Client-side streaming RPC)、和双向流式(Bidirectional streaming RPC)

  • 简单模式(Simple RPC):客户端发起请求并等待服务端响应。

  • 服务端流式(Server-side streaming RPC):客户端发送请求到服务器,拿到一个流去读取返回的消息序列。 客户端读取返回的流,直到里面没有任何消息。

  • 客户端流式(Client-side streaming RPC):与服务端数据流模式相反,这次是客户端源源不断的向服务端发送数据流,而在发送结束后,由服务端返回一个响应。

  • 双向流式(Bidirectional streaming RPC):双方使用读写流去发送一个消息序列,两个流独立操作,双方可以同时发送和同时接收。

本篇文章先介绍简单模式。

新建proto文件#

主要是定义我们服务的方法以及数据格式,我们使用上一篇的simple.proto文件。

1.定义发送消息的信息

Copy

message SimpleRequest{ // 定义发送的参数,采用驼峰命名方式,小写加下划线,如:student_name string data = 1;//发送数据 }

2.定义响应信息

Copy

message SimpleResponse{ // 定义接收的参数 // 参数类型 参数名 标识号(不可重复) int32 code = 1; //状态码 string value = 2;//接收值 }

3.定义服务方法Route

Copy

// 定义我们的服务(可定义多个服务,每个服务可定义多个接口) service Simple{ rpc Route (SimpleRequest) returns (SimpleResponse){}; }

4.编译proto文件

我这里使用上一篇介绍的VSCode-proto3插件,保存后自动编译。

指令编译方法,进入simple.proto文件所在目录,运行:
protoc --go_out=plugins=grpc:./ ./simple.proto

创建Server端#

1.定义我们的服务,并实现Route方法

Copy

`import (
“context”
“log”
“net”

"google.golang.org/grpc"

pb "go-grpc-example/proto"

)

type SimpleService struct{}

func (s *SimpleService) Route(ctx context.Context, req *pb.SimpleRequest) (*pb.SimpleResponse, error) {
res := pb.SimpleResponse{
Code: 200,
Value: “hello “ + req.Data,
}
return &res, nil
}`

该方法需要传入RPC的上下文context.Context,它的作用结束超时取消的请求。更具体的说请参考该文章

2.启动gRPC服务器

Copy

`const (

Address string = ":8000"

Network string = "tcp"

)

func main() {

listener, err := net.Listen(Network, Address)
if err != nil {
    log.Fatalf("net.Listen err: %v", err)
}
log.Println(Address + " net.Listing...")

grpcServer := grpc.NewServer()

pb.RegisterSimpleServer(grpcServer, &SimpleService{})


err = grpcServer.Serve(listener)
if err != nil {
    log.Fatalf("grpcServer.Serve err: %v", err)
}

}`

里面每个方法的作用都有注释,这里就不解析了。
运行服务端

Copy

go run server.go :8000 net.Listing...

创建Client端#

Copy

`import (
“context”
“log”

"google.golang.org/grpc"

pb "go-grpc-example/proto"

)
const (

Address string = ":8000"

)

func main() {

conn, err := grpc.Dial(Address, grpc.WithInsecure())
if err != nil {
    log.Fatalf("net.Connect err: %v", err)
}
defer conn.Close()


grpcClient := pb.NewSimpleClient(conn)

req := pb.SimpleRequest{
    Data: "grpc",
}


res, err := grpcClient.Route(context.Background(), &req)
if err != nil {
    log.Fatalf("Call Route err: %v", err)
}

log.Println(res)

}`

运行客户端

Copy

go run client.go code:200 value:"hello grpc"

成功调用Server端的Route方法并获取返回的数据。

总结#

本篇介绍了简单RPC模式,客户端发起请求并等待服务端响应。下篇将介绍服务端流式RPC.

教程源码地址:https://github.com/Bingjian-Zhu/go-grpc-example
参考:gRPC官方文档中文版

前言#

前面篇章的gRPC都是明文传输的,容易被篡改数据。本章将介绍如何为gRPC添加安全机制,包括TLS证书认证和Token认证。

TLS证书认证#

什么是TLS

TLS(Transport Layer Security,安全传输层),TLS是建立在传输层TCP协议之上的协议,服务于应用层,它的前身是SSL(Secure Socket Layer,安全套接字层),它实现了将应用层的报文进行加密后再交由TCP进行传输的功能。

TLS的作用

TLS协议主要解决如下三个网络安全问题。

  • 保密(message privacy),保密通过加密encryption实现,所有信息都加密传输,第三方无法嗅探;
  • 完整性(message integrity),通过MAC校验机制,一旦被篡改,通信双方会立刻发现;
  • 认证(mutual authentication),双方认证,双方都可以配备证书,防止身份被冒充;

生成私钥

生成RSA私钥:openssl genrsa -out server.key 2048

生成RSA私钥,命令的最后一个参数,将指定生成密钥的位数,如果没有指定,默认512

生成ECC私钥:openssl ecparam -genkey -name secp384r1 -out server.key

生成ECC私钥,命令为椭圆曲线密钥参数生成及操作,本文中ECC曲线选择的是secp384r1

生成公钥

openssl req -new -x509 -sha256 -key server.key -out server.pem -days 3650

openssl req:生成自签名证书,-new指生成证书请求、-sha256指使用sha256加密、-key指定私钥文件、-x509指输出证书、-days 3650为有效期

此后则输入证书拥有者信息

Copy

Country Name (2 letter code) [AU]:CN State or Province Name (full name) [Some-State]:XxXx Locality Name (eg, city) []:XxXx Organization Name (eg, company) [Internet Widgits Pty Ltd]:XX Co. Ltd Organizational Unit Name (eg, section) []:Dev Common Name (e.g. server FQDN or YOUR name) []:go-grpc-example Email Address []:xxx@xxx.com

服务端构建TLS证书并认证

Copy

`func main() {

listener, err := net.Listen(Network, Address)
if err != nil {
    log.Fatalf("net.Listen err: %v", err)
}

creds, err := credentials.NewServerTLSFromFile("../pkg/tls/server.pem", "../pkg/tls/server.key")
if err != nil {
    log.Fatalf("Failed to generate credentials %v", err)
}

grpcServer := grpc.NewServer(grpc.Creds(creds))

pb.RegisterSimpleServer(grpcServer, &SimpleService{})
log.Println(Address + " net.Listing whth TLS and token...")

err = grpcServer.Serve(listener)
if err != nil {
    log.Fatalf("grpcServer.Serve err: %v", err)
}

}`

  • credentials.NewServerTLSFromFile:从输入证书文件和密钥文件为服务端构造TLS凭证
  • grpc.Creds:返回一个ServerOption,用于设置服务器连接的凭证。

完整server.go代码

客户端配置TLS连接

Copy

`var grpcClient pb.SimpleClient

func main() {

creds, err := credentials.NewClientTLSFromFile("../pkg/tls/server.pem", "go-grpc-example")
if err != nil {
    log.Fatalf("Failed to create TLS credentials %v", err)
}

conn, err := grpc.Dial(Address, grpc.WithTransportCredentials(creds))
if err != nil {
    log.Fatalf("net.Connect err: %v", err)
}
defer conn.Close()


grpcClient = pb.NewSimpleClient(conn)

}`

  • credentials.NewClientTLSFromFile:从输入的证书文件中为客户端构造TLS凭证。
  • grpc.WithTransportCredentials:配置连接级别的安全凭证(例如,TLS/SSL),返回一个DialOption,用于连接服务器。

完整client.go代码

到这里,已经完成TLS证书认证了,gRPC传输不再是明文传输。此外,添加自定义的验证方法能使gRPC相对更安全。下面以Token认证为例,介绍gRPC如何添加自定义验证方法。

Token认证#

客户端发请求时,添加Token到上下文context.Context中,服务器接收到请求,先从上下文中获取Token验证,验证通过才进行下一步处理。

客户端请求添加Token到上下文中

Copy

type PerRPCCredentials interface { GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) RequireTransportSecurity() bool }

gRPC 中默认定义了 PerRPCCredentials,是提供用于自定义认证的接口,它的作用是将所需的安全认证信息添加到每个RPC方法的上下文中。其包含 2 个方法:

  • GetRequestMetadata:获取当前请求认证所需的元数据
  • RequireTransportSecurity:是否需要基于 TLS 认证进行安全传输

接下来我们实现这两个方法

Copy

`type Token struct {
AppID string
AppSecret string
}

func (t *Token) GetRequestMetadata(ctx context.Context, uri …string) (map[string]string, error) {
return map[string]string{“app_id”: t.AppID, “app_secret”: t.AppSecret}, nil
}

func (t *Token) RequireTransportSecurity() bool {
return true
}`

然后再客户端中调用Dial时添加自定义验证方法进去

Copy

`token := auth.Token{
AppID: “grpc_token”,
AppSecret: “123456”,
}

conn, err := grpc.Dial(Address, grpc.WithTransportCredentials(creds), grpc.WithPerRPCCredentials(&token))` 

完整client.go代码

服务端验证Token

首先需要从上下文中获取元数据,然后从元数据中解析Token进行验证

Copy

`func Check(ctx context.Context) error {

md, ok := metadata.FromIncomingContext(ctx)
if !ok {
    return status.Errorf(codes.Unauthenticated, "获取Token失败")
}
var (
    appID     string
    appSecret string
)
if value, ok := md["app_id"]; ok {
    appID = value[0]
}
if value, ok := md["app_secret"]; ok {
    appSecret = value[0]
}
if appID != "grpc_token" || appSecret != "123456" {
    return status.Errorf(codes.Unauthenticated, "Token无效: app_id=%s, app_secret=%s", appID, appSecret)
}
return nil

}

func (s *SimpleService) Route(ctx context.Context, req *pb.SimpleRequest) (*pb.SimpleResponse, error) {

if err := Check(ctx); err != nil {
    return nil, err
}
res := pb.SimpleResponse{
    Code:  200,
    Value: "hello " + req.Data,
}
return &res, nil

}`

  • metadata.FromIncomingContext:从上下文中获取元数据

完整server.go代码

服务端代码中,每个服务的方法都需要添加Check(ctx)来验证Token,这样十分麻烦。gRPC拦截器,能很好地解决这个问题。gRPC拦截器功能类似中间件,拦截器收到请求后,先进行一些操作,然后才进入服务的代码处理。

服务端添加拦截器#

Copy

`func main() {

listener, err := net.Listen(Network, Address)
if err != nil {
    log.Fatalf("net.Listen err: %v", err)
}

creds, err := credentials.NewServerTLSFromFile("../pkg/tls/server.pem", "../pkg/tls/server.key")
if err != nil {
    log.Fatalf("Failed to generate credentials %v", err)
}

var interceptor grpc.UnaryServerInterceptor
interceptor = func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
    
    err = Check(ctx)
    if err != nil {
        return
    }
    
    return handler(ctx, req)
}

grpcServer := grpc.NewServer(grpc.Creds(creds), grpc.UnaryInterceptor(interceptor))

pb.RegisterSimpleServer(grpcServer, &SimpleService{})
log.Println(Address + " net.Listing whth TLS and token...")

err = grpcServer.Serve(listener)
if err != nil {
    log.Fatalf("grpcServer.Serve err: %v", err)
}

}`

  • grpc.UnaryServerInterceptor:为一元拦截器,只会拦截简单RPC方法。流式RPC方法需要使用流式拦截器grpc.StreamInterceptor进行拦截。

客户端发起请求,当Token不正确时候,会返回

Copy

Call Route err: rpc error: code = Unauthenticated desc = Token无效: app_id=grpc_token, app_secret=12345

总结#

本篇介绍如何为gRPC添加TLS证书认证和自定义认证,从而让gRPC更安全。添加gRPC拦截器,从而省略在每个方法前添加Token检测代码,使代码更简洁。

教程源码地址:https://github.com/Bingjian-Zhu/go-grpc-example

参考:

Go gRPC进阶-gRPC转换HTTP(十) - 烟花易冷人憔悴 - 博客园

Excerpt

前言 我们通常把 用作内部通信,而使用 进行外部通信。为了避免写两套应用,我们使用 “grpc gateway” 把 转成 。服务接收到 请求后, 把它转成 进行处理,然后以 形式返回数据。本篇代码以上篇为基础,最终转成的 支持 验证、数据验证,并添加 文档。 gRPC转成HTT


前言#

我们通常把RPC用作内部通信,而使用Restful Api进行外部通信。为了避免写两套应用,我们使用grpc-gatewaygRPC转成HTTP。服务接收到HTTP请求后,grpc-gateway把它转成gRPC进行处理,然后以JSON形式返回数据。本篇代码以上篇为基础,最终转成的Restful Api支持bearer token验证、数据验证,并添加swagger文档。

gRPC转成HTTP#

编写和编译proto

1.编写simple.proto

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<p>Copy</p><code id="code-lang-protobuf">syntax = <span>"proto3"</span>;

<span>package</span> proto;

<span>import</span> <span>"github.com/mwitkow/go-proto-validators/validator.proto"</span>;
<span>import</span> <span>"go-grpc-example/10-grpc-gateway/proto/google/api/annotations.proto"</span>;

<span>message </span><span>InnerMessage</span> {
<span>// some_integer can only be in range (1, 100).</span>
<span>int32</span> some_integer = <span>1</span> [(validator.field) = {int_gt: <span>0</span>, int_lt: <span>100</span>}];
<span>// some_float can only be in range (0;1).</span>
<span>double</span> some_float = <span>2</span> [(validator.field) = {float_gte: <span>0</span>, float_lte: <span>1</span>}];
}

<span>message </span><span>OuterMessage</span> {
<span>// important_string must be a lowercase alpha-numeric of 5 to 30 characters (RE2 syntax).</span>
<span>string</span> important_string = <span>1</span> [(validator.field) = {regex: <span>"^[a-z]{2,5}$"</span>}];
<span>// proto3 doesn't have `required`, the `msg_exist` enforces presence of InnerMessage.</span>
InnerMessage inner = <span>2</span> [(validator.field) = {msg_exists : <span>true</span>}];
}

<span>service </span><span>Simple</span>{
<span><span>rpc</span> Route (InnerMessage) <span>returns</span> (OuterMessage)</span>{
<span>option</span> (google.api.http) ={
post:<span>"/v1/example/route"</span>
body:<span>"*"</span>
};
}
}
</code>

可以看到,proto变化不大,只是添加了API的路由路径

1
2
3
4
5
<p>Copy</p><code id="code-lang-protobuf">      <span>option</span> (google.api.http) ={
post:<span>"/v1/example/route"</span>
body:<span>"*"</span>
};
</code>

2.编译simple.proto

simple.proto文件引用了google/api/annotations.proto来源),先要把它编译了。我这里是把google/文件夹直接复制到项目中的proto/目录中进行编译。发现annotations.proto引用了google/api/http.proto,那把它也编译了。

进入annotations.proto所在目录,编译:

1
2
3
<p>Copy</p><code id="code-lang-powershell">protoc <span>--go_out</span>=plugins=grpc:./ ./http.proto
protoc <span>--go_out</span>=plugins=grpc:./ ./annotations.proto
</code>

进入simple.proto所在目录,编译:

1
2
3
4
5
<p>Copy</p><code id="code-lang-powershell"><span>#生成simple.validator.pb.go和simple.pb.go</span>
protoc <span>--govalidators_out</span>=. <span>--go_out</span>=plugins=grpc:./ ./simple.proto
<span>#生成simple.pb.gw.go</span>
protoc <span>--grpc-gateway_out</span>=logtostderr=true:./ ./simple.proto
</code>

以上完成proto编译,接着修改服务端代码。

服务端代码修改

1.server/文件夹下新建gateway/目录,然后在里面新建gateway.go文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
<p>Copy</p><code id="code-lang-go"><span>package</span> gateway

<span>import</span> (
<span>"context"</span>
<span>"crypto/tls"</span>
<span>"io/ioutil"</span>
<span>"log"</span>
<span>"net/http"</span>
<span>"strings"</span>

pb <span>"go-grpc-example/10-grpc-gateway/proto"</span>
<span>"go-grpc-example/10-grpc-gateway/server/swagger"</span>

<span>"github.com/grpc-ecosystem/grpc-gateway/runtime"</span>
<span>"golang.org/x/net/http2"</span>
<span>"golang.org/x/net/http2/h2c"</span>
<span>"google.golang.org/grpc"</span>
<span>"google.golang.org/grpc/credentials"</span>
<span>"google.golang.org/grpc/grpclog"</span>
)

<span>// ProvideHTTP 把gRPC服务转成HTTP服务,让gRPC同时支持HTTP</span>
<span><span>func</span> <span>ProvideHTTP</span><span>(endpoint <span>string</span>, grpcServer *grpc.Server)</span></span> *http.Server {
ctx := context.Background()
<span>//获取证书</span>
creds, err := credentials.NewClientTLSFromFile(<span>"../tls/server.pem"</span>, <span>"go-grpc-example"</span>)
<span>if</span> err != <span>nil</span> {
log.Fatalf(<span>"Failed to create TLS credentials %v"</span>, err)
}
<span>//添加证书</span>
dopts := []grpc.DialOption{grpc.WithTransportCredentials(creds)}
<span>//新建gwmux,它是grpc-gateway的请求复用器。它将http请求与模式匹配,并调用相应的处理程序。</span>
gwmux := runtime.NewServeMux()
<span>//将服务的http处理程序注册到gwmux。处理程序通过endpoint转发请求到grpc端点</span>
err = pb.RegisterSimpleHandlerFromEndpoint(ctx, gwmux, endpoint, dopts)
<span>if</span> err != <span>nil</span> {
log.Fatalf(<span>"Register Endpoint err: %v"</span>, err)
}
<span>//新建mux,它是http的请求复用器</span>
mux := http.NewServeMux()
<span>//注册gwmux</span>
mux.Handle(<span>"/"</span>, gwmux)
log.Println(endpoint + <span>" HTTP.Listing whth TLS and token..."</span>)
<span>return</span> &amp;http.Server{
Addr: endpoint,
Handler: grpcHandlerFunc(grpcServer, mux),
TLSConfig: getTLSConfig(),
}
}

<span>// grpcHandlerFunc 根据不同的请求重定向到指定的Handler处理</span>
<span><span>func</span> <span>grpcHandlerFunc</span><span>(grpcServer *grpc.Server, otherHandler http.Handler)</span></span> http.Handler {
<span>return</span> h2c.NewHandler(http.HandlerFunc(<span><span>func</span><span>(w http.ResponseWriter, r *http.Request)</span></span> {
<span>if</span> r.ProtoMajor == <span>2</span> &amp;&amp; strings.Contains(r.Header.Get(<span>"Content-Type"</span>), <span>"application/grpc"</span>) {
grpcServer.ServeHTTP(w, r)
} <span>else</span> {
otherHandler.ServeHTTP(w, r)
}
}), &amp;http2.Server{})
}

<span>// getTLSConfig获取TLS配置</span>
<span><span>func</span> <span>getTLSConfig</span><span>()</span></span> *tls.Config {
cert, _ := ioutil.ReadFile(<span>"../tls/server.pem"</span>)
key, _ := ioutil.ReadFile(<span>"../tls/server.key"</span>)
<span>var</span> demoKeyPair *tls.Certificate
pair, err := tls.X509KeyPair(cert, key)
<span>if</span> err != <span>nil</span> {
grpclog.Fatalf(<span>"TLS KeyPair err: %v\n"</span>, err)
}
demoKeyPair = &amp;pair
<span>return</span> &amp;tls.Config{
Certificates: []tls.Certificate{*demoKeyPair},
NextProtos: []<span>string</span>{http2.NextProtoTLS}, <span>// HTTP2 TLS支持</span>
}
}

</code>

它主要作用是把不用的请求重定向到指定的服务处理,从而实现把HTTP请求转到gRPC服务。

2.gRPC支持HTTP

1
2
3
4
5
6
<p>Copy</p><code id="code-lang-go">    <span>//使用gateway把grpcServer转成httpServer</span>
httpServer := gateway.ProvideHTTP(Address, grpcServer)
<span>if</span> err = httpServer.Serve(tls.NewListener(listener, httpServer.TLSConfig)); err != <span>nil</span> {
log.Fatal(<span>"ListenAndServe: "</span>, err)
}
</code>

使用postman测试

在动图中可以看到,我们的gRPC服务已经同时支持RPCHTTP请求了,而且API接口支持bearer token验证和数据验证。为了方便对接,我们把API接口生成swagger文档。

生成swagger文档#

生成swagger文档-simple.swagger.json

1.安装protoc-gen-swagger

go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger

2.编译生成simple.swagger.json

到simple.proto文件目录下,编译:
protoc --swagger_out=logtostderr=true:./ ./simple.proto

再次提一下,本人在VSCode中使用VSCode-proto3插件,第一篇有介绍,只要保存,就会自动编译,很方便,无需记忆指令。完整配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<p>Copy</p><code id="code-lang-swift">    <span>// vscode-proto3插件配置</span>
<span>"protoc"</span>: {
<span>// protoc.exe所在目录</span>
<span>"path"</span>: <span>"C:<span>\\</span>Go<span>\\</span>bin<span>\\</span>protoc.exe"</span>,
<span>// 保存时自动编译</span>
<span>"compile_on_save"</span>: <span>true</span>,
<span>"options"</span>: [
<span>"--go_out=plugins=grpc:."</span>,<span>//在当前目录编译输出.pb.go文件</span>
<span>"--govalidators_out=."</span>,<span>//在当前目录编译输出.validator.pb文件</span>
<span>"--grpc-gateway_out=logtostderr=true:."</span>,<span>//在当前目录编译输出.pb.gw.go文件</span>
<span>"--swagger_out=logtostderr=true:."</span><span>//在当前目录编译输出.swagger.json文件</span>
]
}
</code>

编译生成后把需要的文件留下,不需要的删掉。

把swagger-ui转成Go代码,备用

1.下载swagger-ui

下载地址,把dist目录下的所有文件拷贝我们项目的server/swagger/swagger-ui/目录下。

2.把Swagger UI转换为Go代码

安装go-bindata
go get -u github.com/jteeuwen/go-bindata/...

回到server/所在目录,运行指令把Swagger UI转成Go代码。
go-bindata --nocompress -pkg swagger -o swagger/datafile.go swagger/swagger-ui/...

  • 这步有坑,必须要回到main函数所在的目录运行指令,因为生成的Go代码中的_bindata 映射了swagger-ui的路径,程序是根据这些路径来找页面的。如果没有在main函数所在的目录运行指令,则生成的路径不对,会报404,无法找到页面。本项目server/端的main函数在server.go中,所以在server/所在目录下运行指令。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<p>Copy</p><code id="code-lang-go"><span>var</span> _bindata = <span>map</span>[<span>string</span>]<span><span>func</span><span>()</span></span> (*asset, <span>error</span>){
<span>"swagger/swagger-ui/favicon-16x16.png"</span>: swaggerSwaggerUiFavicon16x16Png,
<span>"swagger/swagger-ui/favicon-32x32.png"</span>: swaggerSwaggerUiFavicon32x32Png,
<span>"swagger/swagger-ui/index.html"</span>: swaggerSwaggerUiIndexHtml,
<span>"swagger/swagger-ui/oauth2-redirect.html"</span>: swaggerSwaggerUiOauth2RedirectHtml,
<span>"swagger/swagger-ui/swagger-ui-bundle.js"</span>: swaggerSwaggerUiSwaggerUiBundleJs,
<span>"swagger/swagger-ui/swagger-ui-bundle.js.map"</span>: swaggerSwaggerUiSwaggerUiBundleJsMap,
<span>"swagger/swagger-ui/swagger-ui-standalone-preset.js"</span>: swaggerSwaggerUiSwaggerUiStandalonePresetJs,
<span>"swagger/swagger-ui/swagger-ui-standalone-preset.js.map"</span>: swaggerSwaggerUiSwaggerUiStandalonePresetJsMap,
<span>"swagger/swagger-ui/swagger-ui.css"</span>: swaggerSwaggerUiSwaggerUiCss,
<span>"swagger/swagger-ui/swagger-ui.css.map"</span>: swaggerSwaggerUiSwaggerUiCssMap,
<span>"swagger/swagger-ui/swagger-ui.js"</span>: swaggerSwaggerUiSwaggerUiJs,
<span>"swagger/swagger-ui/swagger-ui.js.map"</span>: swaggerSwaggerUiSwaggerUiJsMap,
}
</code>

对外提供swagger-ui

1.在swagger/目录下新建swagger.go文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<p>Copy</p><code id="code-lang-go"><span>package</span> swagger

<span>import</span> (
<span>"log"</span>
<span>"net/http"</span>
<span>"path"</span>
<span>"strings"</span>

assetfs <span>"github.com/elazarl/go-bindata-assetfs"</span>
)

<span>//ServeSwaggerFile 把proto文件夹中的swagger.json文件暴露出去</span>
<span><span>func</span> <span>ServeSwaggerFile</span><span>(w http.ResponseWriter, r *http.Request)</span></span> {
<span>if</span> !strings.HasSuffix(r.URL.Path, <span>"swagger.json"</span>) {
log.Printf(<span>"Not Found: %s"</span>, r.URL.Path)
http.NotFound(w, r)
<span>return</span>
}

p := strings.TrimPrefix(r.URL.Path, <span>"/swagger/"</span>)
<span>// "../proto/"为.swagger.json所在目录</span>
p = path.Join(<span>"../proto/"</span>, p)

log.Printf(<span>"Serving swagger-file: %s"</span>, p)

http.ServeFile(w, r, p)
}

<span>//ServeSwaggerUI 对外提供swagger-ui</span>
<span><span>func</span> <span>ServeSwaggerUI</span><span>(mux *http.ServeMux)</span></span> {
fileServer := http.FileServer(&amp;assetfs.AssetFS{
Asset: Asset,
AssetDir: AssetDir,
Prefix: <span>"swagger/swagger-ui"</span>, <span>//swagger-ui文件夹所在目录</span>
})
prefix := <span>"/swagger-ui/"</span>
mux.Handle(prefix, http.StripPrefix(prefix, fileServer))
}
</code>

2.注册swagger

gateway.go中添加如下代码

1
2
3
4
<p>Copy</p><code id="code-lang-go"><span>//注册swagger</span>
mux.HandleFunc(<span>"/swagger/"</span>, swagger.ServeSwaggerFile)
swagger.ServeSwaggerUI(mux)
</code>

到这里我们已经完成了swagger文档的添加工作了,由于谷歌浏览器不能使用自己制作的TLS证书,所以我们用火狐浏览器进行测试。

用火狐浏览器打开:https://127.0.0.1:8000/swagger-ui/

在最上面地址栏输入:https://127.0.0.1:8000/swagger/simple.swagger.json

然后就可以看到swagger生成的API文档了。

还有个问题,我们使用了bearer token进行接口验证的,怎么把bearer token也添加到swagger中呢?
最后我在grpc-gatewayGitHub上的这个Issues找到解决办法。

在swagger中配置bearer token

1.修改simple.proto文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
<p>Copy</p><code id="code-lang-protobuf">syntax = <span>"proto3"</span>;

<span>package</span> proto;

<span>import</span> <span>"github.com/mwitkow/go-proto-validators/validator.proto"</span>;
<span>import</span> <span>"go-grpc-example/10-grpc-gateway/proto/google/api/annotations.proto"</span>;
<span>import</span> <span>"go-grpc-example/10-grpc-gateway/proto/google/options/annotations.proto"</span>;

<span>message </span><span>InnerMessage</span> {
<span>// some_integer can only be in range (1, 100).</span>
<span>int32</span> some_integer = <span>1</span> [(validator.field) = {int_gt: <span>0</span>, int_lt: <span>100</span>}];
<span>// some_float can only be in range (0;1).</span>
<span>double</span> some_float = <span>2</span> [(validator.field) = {float_gte: <span>0</span>, float_lte: <span>1</span>}];
}

<span>message </span><span>OuterMessage</span> {
<span>// important_string must be a lowercase alpha-numeric of 5 to 30 characters (RE2 syntax).</span>
<span>string</span> important_string = <span>1</span> [(validator.field) = {regex: <span>"^[a-z]{2,5}$"</span>}];
<span>// proto3 doesn't have `required`, the `msg_exist` enforces presence of InnerMessage.</span>
InnerMessage inner = <span>2</span> [(validator.field) = {msg_exists : <span>true</span>}];
}

<span>option</span> (grpc.gateway.protoc_gen_swagger.options.openapiv2_swagger) = {
security_definitions: {
security: {
key: <span>"bearer"</span>
value: {
type: TYPE_API_KEY
in: IN_HEADER
name: <span>"Authorization"</span>
description: <span>"Authentication token, prefixed by Bearer: Bearer &lt;token&gt;"</span>
}
}
}

security: {
security_requirement: {
key: <span>"bearer"</span>
}
}

info: {
title: <span>"grpc gateway sample"</span>;
version: <span>"1.0"</span>;
license: {
name: <span>"MIT"</span>;
};
}

schemes: HTTPS
};

<span>service </span><span>Simple</span>{
<span><span>rpc</span> Route (InnerMessage) <span>returns</span> (OuterMessage)</span>{
<span>option</span> (google.api.http) ={
post:<span>"/v1/example/route"</span>
body:<span>"*"</span>
};
<span>// //禁用bearer token</span>
<span>// option (grpc.gateway.protoc_gen_swagger.options.openapiv2_operation) = {</span>
<span>// security: { } // Disable security key</span>
<span>// };</span>
}
}
</code>

2.重新编译生成simple.swagger.json

大功告成!

验证测试

1.添加bearer token

2.调用接口,正确返回数据

3.传递不合规则的数据,返回违反数据验证逻辑错误

总结#

本篇介绍了如何使用grpc-gatewaygRPC同时支持HTTP,最终转成的Restful Api支持bearer token验证、数据验证。同时生成swagger文档,方便API接口对接。

教程源码地址:https://github.com/Bingjian-Zhu/go-grpc-example

参考文档:
https://eddycjy.com/tags/grpc-gateway/
https://segmentfault.com/a/1190000008106582

Github上优秀的.NET Core开源项目的集合。内容包括:库、工具、框架、模板引擎、身份认证、数据库、ORM框架、图片处理、文本处理、机器学习、日志、代码分析、教程等。

Github地址:https://github.com/jasonhua95/awesome-dotnet-core ,【awesome-dotnet-core】

其中的翻译有可能有问题,大家发现了及时提出来,其他的比较好的项目也可以提出来,我会及时添加修改上去的。

一般

框架, 库和工具

API

  • autorest - Swagger(OpenAPI)规范代码生成器,具有C#和Razor模板。支持C#,Java,Node.js,TypeScript,Python和Ruby。
  • aspnet-api-versioning - 提供一组库,这些库可将服务API版本添加到ASP.NET Web API,具有ASP.NET Web API的OData和ASP.NET Core。
  • AspNetCoreRateLimit - ASP.NET限速中间件。
  • CondenserDotNet - 使用Kestrel和Consul的API Condenser / Reverse Proxy,包括轻量级consul库。
  • Flurl - 适用于.NET的Fluent URL构建器和可测试的HTTP。
  • GraphQL
    • Dapper.GraphQL - 一个旨在将Dapper和graphql-dotnet项目集成在一起的库,主要考虑的是易用性和性能。
    • graphql-aspnetcore - ASP.NET Core MiddleWare创建GraphQL端点。
    • graphql-convention - 该库是GraphQL的补充层,使您可以使用现有的属性和方法作为字段解析器,将.NET类自动包装到GraphQL模式定义中。
    • graphiql-dotnet - 用于ASP.NET Core的GraphiQL中间件。
    • graphql-dotnetcore - 基于graphql-js的.NETQL GraphQL。
    • graphql-dotnet - GraphQL for .NET。
    • graphql-dotnet-server - GraphQL for .NET - 订阅传输WebSockets。
    • Hot Chocolate - .Net Core和.NET Framework的GraphQL服务器。
    • FSharp.Data.GraphQL - Facebook GraphQL查询语言的FSharp实现。
    • parser - .NET中GraphQL的词法分析器和解析器。
    • tanka-graphql - GraphQL执行库和服务器库,支持SignalR,Apollo,模式操纵以及Apollo和graphql-js熟悉的其他功能。
  • halcyon - ASP.NET的HAL实现。
  • JSON API .NET Core - 用于ASP.Net Core的JSON API框架。
  • LightNode - 基于OWIN构建的Micro RPC / REST框架
  • NetCoreStack.Proxy - 适用于.NET Standard 2.0的类型安全的分布式REST库(NetCoreStack Flying Proxy)
  • NSwag - 用于.NET,Web API和TypeScript的Swagger / OpenAPI工具链。
  • OData - 开放数据协议(OData)支持创建基于HTTP的数据服务,允许使用统一资源标识符识别资源( URIs)并在抽象数据模型中定义,由Web客户端使用简单的HTTP消息进行发布和编辑。
  • OpenAPI Generator - 可以通过 OpenAPI Generator,在给定 OpenAPI 规范(v2, v3)的情况下自动生成 API 客户端库、server stubs、文档以及配置。
  • refit - 适用于.NET Core,Xamarin和.NET的自动类型安全REST库。
  • RestClient.Net - 适用于所有C#跨平台的REST客户端。
  • RestEase - 易于使用的类型安全REST API客户端库,简单且可自定义。
  • RestLess - .Net Standard的自动类型安全无反射REST API客户端库。
  • Restier - RESTier是一个RESTful API开发框架,用于在.NET平台上构建基于OData V4的标准化RESTful服务。
  • Restsharp - 用于.NET的简单REST和HTTP API客户端
  • Swashbuckle - Swagger工具,生成API文档,包括用于测试的UI。
  • WebAPIContrib for ASP.NET CORE - ASP.NET Core的附加组件和扩展库。

应用程序框架

  • ASP.NET Boilerplate - ABP是一个通用的WEB应用程序框架和项目模板。
  • Abp vNext - 该项目是ABP Web应用程序框架的下一代。
  • AsyncEx - async / await的帮助程序库。
  • Aeron.NET - 高效可靠的UDP单播,UDP组播和IPC消息传输。
  • akka.net - Akka是一个基于scala语言的Actor模型库,旨在构建一套高并发、分布式、自动容错、消息驱动应用的工具集。
  • Aggregates.NET - Aggregates.NET是一个框架,可以帮助开发人员将优秀的NServiceBus和EventStore库集成在一起。
  • ASP.NET MVC - 官方WEB应用程序框架,MVC。
  • ASP.NET Core - ASP.NET Core是一个跨平台的.NET框架。
  • Butterfly Server .NET - 允许用最少的工作量构建实时Web应用程序,分布式追踪的服务器端库。
  • CAP - CAP是处理分布式事务的解决方案,还具有EventBus功能,它轻巧,易于使用且高效。
  • Carter - Carter是一个路由框架,使代码更加简单明确。
  • Chromely - Electron.NET的轻量级替代品,构建HTML5桌面应用程序框架。
  • Cinchoo ETL - 用于.NET的ETL框架(用于CSV,Flat,Xml,JSON,键值对格式文件的分析器/写入器)。
  • CQRSlite - 用于帮助在C#中编写CQRS和Eventsourcing应用程序的轻量级框架。
  • dataaccess_aspnetcore - EF的UnitOfWork和Repositories的基类。
  • DNTFrameworkCore - DNTFrameworkCore 是一个轻量级且可扩展的基础结构,用于基于ASP.NET Core构建高质量的Web应用程序
  • DotNetCorePlugins - 用于动态加载.NET Core程序集,将其作为主应用程序的扩展来执行与Assembly.LoadFrom不同。
  • DotnetSpider - DotnetSpider,一个类似于WebMagic和Scrapy的.NET标准爬虫库。它是轻量级,高效且快速的高级Web爬网和抓取框架。
  • DotNetty - netty端口,事件驱动的异步网络应用程序框架。
  • dotvvm - Web应用程序的开源MVVM框架。
  • ElectronNET - 使用ASP.NET NET Core构建跨平台桌面应用程序。
  • EmbedIO - 一个小型的,跨平台,基于模块的Web服务器。
  • Ether.Network - Ether.Network是一个开源网络库,允许开发人员通过sockets创建简单,快速和可扩展的套接字服务器或客户端的基本库。
  • EventFlow - EventFlow是一个易于使用的基本CQRS + ES框架。
  • ExcelDataReader - 用C#编写的轻量级快速库,用于读取Microsoft Excel文件。
  • ExtCore - 用于创建模块化和可扩展的Web应用程序框架。
  • Finbuckle.MultiTenant - Finbuckle.MultiTenant是ASP.NET Core的多租户库。它提供用于租户解析,每个租户应用程序配置和每个租户数据隔离的功能。
  • fission - Fission 是一个构建在 Kubernetes 之上的 FaaS框架。ission 利用Kubernetes 集群管理、调度、网络管理等,将容器编排功能留给 Kubernetes,而 Fission 就专注于 FaaS 特性。
  • grpc - 远程过程调用(RPC)为构建分布式应用程序和服务提供了有用的抽象,grpc库。
  • Halibut - 使用基于SSL的JSON-RPC的.NET安全通信框架。
  • MagicOnion - MagicOnion是一个实时网络引擎,如SignalR,Socket.io和RPC-Web API框架。
  • MassTransit - .NET分布式应用程序框架。
  • microdot - 一个开源的.NET微服务框架。
  • MoreLINQ - LINQ to Objects的扩展。
  • Nancy - 用于在.NET和Mono上构建基于HTTP的服务的轻量级框架。
  • opencvsharp - OpenCV的跨平台库。
  • orleans - Orleans是一个跨平台的,用于构建分布式应用程序框架
  • protoactor-dotnet - Golang和C#的快速分布式Actor。
  • resin - 面向文档的搜索引擎,具有列索引,多重集合查询,基于JSON的查询语言和HTTP API。
  • RService.io - 用于ASP.NET Core的轻量级REST服务框架
  • ServiceStack - ServiceStack是一个简单,快速,通用和高效的全功能Web和 Web服务框架。
  • Steeltoe OSS - 用于常见微服务模式的.NET工具包。
  • Strathweb.TypedRouting.AspNetCore - 一个在ASP.NET Core MVC项目中启用强类型路由的库。
  • Xer.Cqrs - 轻巧易用的CQRS + DDD库。
  • X.PagedList - 用于轻松分页ASP.NET / ASP.NET Core中任何IEnumerable / IQueryable的库。

应用程序模板

  • .NET Boxed - .NET项目模板,包裹API,GraphQL,Orleans。
  • aspnet-core-react-template - ASP.NET Core/ React SPA应用程序模板。
  • AspNetCoreSpa - Asp.Net Core和Angular 8 SPA Fullstack应用程序模板。
  • ASP.NET-MVC-Template - 带有Angular的ASP.NET MVC 5,ASP.NET Core和ASP.NET Core的现成模板。
  • AddFeatureFolders - 为ASP.NET Core中的MVC控制器和视图启用功能文件夹。
  • Angular Visual Studio Webpack Starter - 用于Webpack,Visual Studio,ASP.NET Core和Angular的模板。应用程序的客户端和服务器端都在一个ASP.NET Core项目中实现,这使得部署更容易。
  • DNTFrameworkCoreTemplate - 基于[DNTFrameworkCore]的Boilerplate项目模板。
  • dotnet new caju - 代码整洁框架,clean-architecture-manga
  • JavaScriptServices - Microsoft ASP.NET核心JavaScript服务。
  • kendo-ui-core - 一个基于jQuery的HTML5小部件库,用于构建现代Web应用程序。
  • QuickApp - 具有完整登录,用户和角色管理的ASP.NET Core / Angular4启动项目模板。
  • Serenity - Serenity是一个ASP.NET MVC / TypeScript应用程序框架,旨在通过基于服务的体系结构,简化和缩短以数据为中心的应用程序开发。
  • Toucan - 用于构建单页应用程序的Boilerplate。服务器是围绕SOLID原则设计的多项目.Net Core解决方案。客户端是TypeScript 2,Vuejs 2,Vuex 2。

身份认证和授权

区块链

  • BTCPayServer - BTCPay Server是一个免费的开源加密货币支付处理器,它使您可以直接以比特币和山寨币接收支付,而无需任何费用,交易成本或中间商。
  • Meadow - 一个集成的以太坊实施和工具套件,专注于Solidity测试和开发。
  • NBitcoin - 用于.NET框架的综合比特币库。
  • NBlockchain - 用于构建支持区块链的应用程序的.NET标准库
  • NBXplorer - 比特币和NBitcoin资源管理器客户端。
  • NEO - 为智能经济打造的开放网络,Neo利用区块链技术。
  • Nethereum - 将以太坊的热爱带到.NET。
  • Nethermind - .NET Core以太坊客户端
  • StratisBitcoinFullNode - 简单且经济实惠的端到端解决方案,用于在.Net框架上开发,测试和部署本机C#区块链应用程序。
  • Trezor.Net - Trezor加密货币硬件钱包的跨平台C#库。
  • WalletWasabi - 注重隐私的比特币钱包。内置Tor,CoinJoin和硬币控制功能。

机器人

  • BotSharp - BotSharp是AI Bot平台构建者的开源机器学习框架。
  • NadekoBot - 用C#编写的开源,通用的Discord聊天机器人。
  • Telegram.Bot - Telegram Bot API客户端。
  • Funogram - F#Telegram Bot Api库。

自动部署

  • cake-build - 跨平台构建自动化系统。
  • Colorful.Console - 设置您的C#控制台输出样式!
  • dotnet-docker - 用于.NET Core和.NET Core Tools的基本Docker镜像。
  • Dockerize.NET - .NET Cli工具,用于将.NET Core应用程序打包到Docker映像中:“ dotnet dockerize”
  • FlubuCore - 跨平台构建和自动化部署系统,用C#代码构建项目,执行,部署脚本。
  • GitInfo - 来自MSBuild,C#和VB的Git和SemVer信息,一种MSBuild编译工具。
  • GitVersioning - 使用version.json文件生成的唯一版本标记程序集和程序包等,并包括用于非官方构建的git commit ID。
  • go-dotnet - .NET Core Runtime的PoC Go包装器。
  • Image2Docker - 将现有Windows应用程序工作,移植到Docker的PowerShell模块。
  • LocalAppVeyor - .NET Core全局工具,可将appveyor.yml部署AppVeyor到本地。
  • msbuild - Microsoft Build Engine是一个用于构建应用程序的平台。
  • Nuke - 跨平台构建自动化系统。
  • Opserver - Stack Exchange的监控系统。
  • vsts-agent - Visual Studio Team Services构建和发布代理。

css, js帮助工具

  • BundlerMinifier - Visual Studio扩展,让您可以配置JS,CSS和HTML文件的捆绑和缩小。
  • JavaScriptViewEngine - 用于在JavaScript环境中呈现标记的ASP.NET MVC ViewEngine。适用于React和Angular服务器端呈现。
  • Smidge - 用于ASP.NET Core的轻量级运行时CSS / JavaScript文件缩小,组合,压缩和管理库。
  • Web Markup Minifier - 包含一组标记最小化器的.NET库。该项目的目标是通过减少HTML,XHTML和XML代码的大小来提高Web应用程序的性能。

缓存

  • CacheManager - 用C#编写的.NET的开源缓存抽象层。它支持各种缓存提供程序并实现许多高级功能。
  • EasyCaching - 开源缓存库,包含基本用法和缓存的一些高级用法,可以帮助我们更轻松地处理缓存。
  • Faster - Microsoft的快速key,value存储库。
  • Foundatio - 用于构建分布式应用程序的可插入基础库。
  • Microsoft Caching - 用于内存缓存和分布式缓存的库。
  • Stack Exchange Redis - 用于.NET语言的高性能通用redis客户端(C#等)。

内容管理系统CMS

  • Awesome-CMS-Core - Awesome-CMS-Core是一个使用ASP.Net Core和ReactJS构建的开源CMS,考虑到模块分离问题并提供最新的技术趋势,如.Net Core,React,Webpack,SASS,后台作业,Message Queue。
  • Blogifier.Core - Blogifier是用ASP.NET Core编写的简单,美观,轻巧的开源博客。
  • Cofoundry - Cofoundry是一个可扩展且灵活的.NET Core CMS和应用程序框架,专注于代码优先开发。
  • CoreWiki - 一个简单的ASP.NET core wiki。
  • dasblog-core - DasBlog博客项目。
  • Lynicon - Lynicon CMS系统。
  • Miniblog - ASP.NET Core博客引擎。
  • NetCoreCMS - NetCoreCMS是使用ASP.Net Core 2.0 MVC开发的模块化主题支持的内容管理系统。
  • Orchard Core CMS - 在模块化和可扩展的应用程序框架之上使用ASP.NET Core构建的开源内容管理系统。
  • Piranha CMS - 用于ASP.NET核心和实体框架核心的轻量级且不显眼的开源CMS。
  • Platformus - 基于ASP.NET Core 1.0和ExtCore框架的免费,开源和跨平台的CMS。
  • SimpleContent - 用于ASP.NET Core的简单而灵活的内容和博客引擎,可以使用或不使用数据库。
  • Squidex - Squidex是一个开源的CMS,基于MongoDB,CQRS和事件。
  • Swastika I/O Core CMS - 基于SIOH框架的ASP.NET Core / Dotnet核心系统(例如CMS,电子商务,论坛,问题解答,CRM …)
  • Weapsy - 基于DDD和CQRS的开源ASP.NET核心CMS。它支持开箱即用的MSSQL,MySQL,SQLite和PostgreSQL。
  • Wyam - 模块化静态内容和静态站点生成器。
  • ZKEACMS - 视觉设计,通过拖放构建网站。

代码分析和指标

  • awesome-static-analysis - 针对各种编程语言的静态分析工具,链接和代码质量检查器的精选列表。
  • Code Analysis
    • CodeFormatter - CodeFormatter是使用Roslyn来自动重写我们的代码格式。
    • DevSkim - DevSkim是IDE扩展和语言分析器的框架,可在开发人员编写代码时在开发环境中提供内联安全性分析。
    • RefactoringEssentials - Visual Studio扩展工具,支持分析和重构代码。
    • roslyn-analyzers - Roslyn分析器分析您的代码的样式,质量和可维护性,设计和其他问题。
    • StyleCopAnalyzers - StyleCop规则实现的.NET编译器平台。
  • Metrics
    • AppMetrics - 用于记录和报告应用程序中的指标。
    • Audit.NET - 一个可扩展的框架,用于审核.NET和.NET Core中的执行操作。
    • BenchmarkDotNet - 用于基准测试的强大.NET库。
    • coverlet - Coverlet是.NET的跨平台代码覆盖框架。
    • MiniCover - 跨平台代码覆盖工具
    • NBench - .NET应用程序的性能基准测试框架
    • Nexogen.Libraries.Metrics - 用于在.NET中收集应用程序指标并将其导出到Prometheus的库。
    • OpenCover - 代码覆盖工具(仅适用于WINDOWS OS)
    • PerformanceMonitor - .NET应用程序性能监视器。
    • prometheus-net - .NET指标,监视系统,检测应用程序的prometheus库。
    • Prometheus.Client - Prometheus客户端。

压缩

  • lz4net - 适用于所有.NET平台的超快速压缩算法。
  • sharpcompress - 完全管理的C#库,用于处理许多压缩类型和格式。

编译器

  • Fable - F#到JavaScript编译器。
  • fparsec - F#和C#的解析器组合库。
  • IL2C - IL2C-ECMA-335 CIL / MSIL到C语言的翻译器。
  • Mond - 用C#编写的动态类型脚本语言,带有REPL,调试器和简单的嵌入API。
  • peachpie - .NET的开源PHP编译器。
  • Pidgin - 用于C#的轻量级,快速且灵活的解析库,由Stack Overflow开发。
  • roslyn - Roslyn .NET编译器提供具有丰富代码分析API的C#和Visual Basic语言。
  • Sprache - 小型,友好的C#解析器框架。

密码

  • BCrypt.Net - BCrypt密码库。
  • BCrypt.NET-Core - 用于安全存储密码的BCrypt.NET库。
  • BouncyCastle PCL - Bouncy Castle Crypto包是加密算法和协议的库。
  • multiformats - 用于编码/解码Multihashes的库,它是一个“容器”,用于描述计算摘要的散列算法。
  • nsec - NSec是基于libsodium的.NET Core新加密库。
  • SecurityDriven.Inferno - 专业的加密库。

数据库

  • DBreeze - C#.NET MONO NOSQL(嵌入式键值存储)ACID多范例数据库管理系统。
  • JsonFlatFileDataStore - 简单的JSON平面文件数据存储,支持打字和动态数据。
  • LiteDB - LiteDB是一个小型,快速,轻量的NoSQL嵌入式数据库。
  • NoDb - 文档数据库,个人博客和网站以及小型小册子网站是不使用数据库的不错的选择。
  • marten - Postgresql作为.NET应用程序的文档数据库和事件存储的库。
  • StringDB - StringDB是一个模块化的键/值对档案数据库,旨在消耗少量的ram并生成少量的数据库。
  • yessql - 适用于任何RDBMS的.NET文档数据库。

数据库驱动程序

数据库工具库

  • DbUp - 可帮助您将更改部署到SQL Server数据库,跟踪已经运行的SQL脚本,并运行使数据库更新所需的更改脚本。
  • Evolve - 使用纯SQL脚本的简单数据库迁移工具。受到Flyway的启发。
  • EFCorePowerTools - EF工具库 - reverse engineering, migrations and model。
  • fluentmigrator - .NET的迁移框架,就像Ruby on Rails Migrations一样。
  • monitor-table-change-with-sqltabledependency - 获取有关记录表更改的SQL Server通知。
  • roundhouse - RoundhousE是用于.NET的数据库迁移实用程序,它使用sql文件和基于源代码控制的版本控制。
  • SharpRepository - SharpRepository是一个用C#编写的通用存储库,它包括对各种关系,文档和对象数据库的支持,包括Entity Framework,RavenDB,MongoDb和Db4o。 SharpRepository还包括Xml和InMemory存储库实现。
  • TrackableEntities.Core - 使用.NET Core跨服务边界进行更改跟踪。
  • Mongo.Migration - MongoDB的即时迁移库。

日期和时间

  • Exceptionless.DateTimeExtensions - DateTimeRange,工作日和各种DateTime,DateTimeOffset,TimeSpan扩展方法。
  • FluentDateTime - 允许您编写更清晰的DateTime表达式和操作。部分灵感来自Ruby DateTime Extensions。
  • nodatime - 日期和时间API库。

分布式计算

  • AspNetCore.Diagnostics.HealthChecks - HealthChecks企业级核心诊断程序。
    • BeatPulse - ASP.NET Core应用程序的活动状况,健康检查库。
  • Foundatio - 可插拔的,用于构建松耦合的分布式应用程序库。
  • Rafty - RAFT 的实现库。
  • Obvs - 一个可观察微服务总线的库,基于Rx的接口。
  • Ocelot - Ocelot创建的API网关。
  • OpenTracing -API和分布式跟踪工具。
  • Polly - Polly是一个.NET弹性和瞬态故障处理库,允许开发人员以流畅和线程安全的方式表达诸如重试,断路器,超时,隔离头和回退之类的策略。
  • ProxyKit - HTTP反向代理的工具包。

电子商务与支付

  • nopCommerce - 免费的开源电子商务购物车(ASP.NET MVC / ASP.NET核心MVC),拥有庞大的社区和充满新功能的市场,主题和插件。
  • GrandNode - 基于ASP.NET Core 2.1和MongoDB的多平台免费开源电子商务购物车。
  • PayPal - 用于PayPal的RESTful API的.NET SDK。
  • SimplCommerce - 基于.NET Core构建的超级简单电子商务系统。
  • Stripe - 用于stripe.com REST API的类型.NET客户端。

异常

响应式编程

  • CSharpFunctionalExtensions - C#的功能扩展。
  • DynamicData - 基于Rx.NET的Reactive 集合。
  • echo-process - C#的Actor库,其中包含支持Redis持久性的其他模块,以及JS集成。
  • FsCheck - FsCheck是用于自动测试.NET程序的工具。
  • Giraffe - 适用于F#开发人员的本机功能ASP.NET核心Web框架。
  • language-ext - C#功能语言扩展。
  • LaYumba.Functional - C#中的函数式编程的代码示例。
  • NetMQ.ReactiveExtensions - 使用Reactive Extensions(RX)轻松地在网络上的任何位置发送消息。传输协议是ZeroMQ。
  • Optional - Optional类型库.
  • reactive-streams-dotnet - Reactive库。
  • ReactiveUI - 一个MVVM框架,它与Reactive Extensions for .NET集成,以创建在任何移动或桌面平台上运行的优雅,可测试的用户界面。
  • Rx.NET - Rx.NET库。
  • Qactive - Reactive 可查询库。
  • sodium - Reactive 多语言库。

图片

  • GLFWDotNet - GLFW的.NET绑定。
  • ImageProcessor - 一个流畅的System.Drawing包装器,用于处理图像文件。
  • ImageSharp - 图像文件处理库。
  • LibVLCSharp - LibVLCSharp是基于VideoLAN的LibVLC库的.NET平台的跨平台音频和视频API。
  • Magick.NET - 功能强大的图像处理库,支持超过100种主要文件格式(不包括子格式)。
  • MagicScaler - 适用于.NET的MagicScaler高性能,高质量图像处理管道
  • QRCoder - 二维码实现库
  • SharpBgfx - bgfx图形库的C#绑定。
  • Structure.Sketching - 用于支持.NET Core的.NET应用程序的图像处理库。
  • veldrid - 一个用于.NET的低级硬件加速3D图形库。
  • ZXing.Net 二维码、条形码的生成和读取

图形用户界面GUI

  • Avalonia - 跨平台UI框架。
  • AvaloniaEdit - 基于Avalonia的文本编辑器组件。
  • ShellProgressBar - 可视化(并行)控制台应用程序库。
  • Qml.Net - 使用Qml.Net在.NET中构建跨平台的桌面应用程序。
  • WinApi - 一个简单,直接,超薄的CLR库,用于高性能Win32 Native Interop,具有自动化,窗口,DirectX,OpenGL和Skia助手。

集成开发环境IDE

  • Mono - MonoDevelop使开发人员能够在Linux,Windows和Mac OS X上快速编写桌面和Web应用程序。
  • rider - 基于IntelliJ平台和ReSharper的跨平台C#IDE。
  • Omnisharp - 开源项目系列,每个项目都有一个目标:在您选择的编辑器中实现出色的.NET体验。
  • SharpDevelop - SharpDevelop是一个免费的集成开发环境(IDE),适用于Microsoft.NET平台上的C#,VB.NET,Boo,IronPython,IronRuby和F#项目。它(几乎)完全用C#编写,并带有您期望在IDE中使用的功能以及更多功能。
  • Visual Studio Code - 它结合了代码编辑器的简单性和开发人员的核心编辑 - 构建 - 调试周期所需的工具。VS Code提供全面的编辑和调试支持,可扩展性模型以及与现有工具的轻量级集成。
  • Visual Studio Community - 功能完备且可扩展的免费 IDE,可用于创建新式 Android、iOS、Windows 应用以及 Web 应用和云服务。

国际化

控制反转IOC

  • AutoDI - 使用IL编译的超快依赖注入库。
  • Autofac - IoC容器。
  • Castle.Windsor - IoC容器。
  • DryIoc - 快速,小巧,功能齐全的IoC。
  • Grace - Grace是一款功能丰富的依赖注入容器,其设计考虑了易用性和性能。
  • Inyector - AspNetCore的依赖注入自动化。
  • Lamar - 快速的IOC工具库。
  • LightInject - 超轻量级IoC容器。
  • SimpleInjector - 简单,灵活,快速的依赖注入库。
  • Stashbox - 基于.NET的解决方案的轻量级,可移植依赖注入框架。

日志

机器学习和科学研究

  • Accord - Accord.NET项目为.NET提供了机器学习,统计,人工智能,计算机视觉和图像处理方法。
  • ML.NET - ML.NET是.NET的开源和跨平台机器学习框架。
  • Spreads - 用于数据流实时探索和分析的库。
  • TensorFlowSharp - 适用于.NET语言的TensorFlow API。
  • WaveFunctionCollapse - 借助量子力学的思想,从单个例子生成itmap和tilemap。
  • SiaNet - 具有CUDA / OpenCL支持的易于使用的C#深度学习。

邮件

  • FluentEmail - 电子邮件发送库。
  • MailBody - 使用流畅的界面(.NET)创建电子邮件。
  • MailKit - 用于IMAP,POP3和SMTP的跨平台.NET库。
  • MailMergeLib - SMTP邮件客户端库,为文本,内嵌图像和附件提供邮件合并功能,以及发送邮件的良好吞吐量和容错能力。
  • MimeKit - 跨平台.NET MIME创建和解析器库,支持S/MIME, PGP, DKIM, TNEF and Unix mbox。
  • netDumbster - 用于测试的.Net假SMTP服务器。克隆流行的Dumbster。
  • Papercut - 简单桌面SMTP服务器。
  • PreMailer.Net - css和样式结合的邮件库。
  • SendGrid Client - C# library for the SendGrid v3 mail endpoint.
  • SmtpServer - 用于创建自己的SMTP服务器的库。
  • StrongGrid - SendGrid的v3 API客户端。不仅允许您发送电子邮件,还允许您批量导入联系人,管理列表和段,为列表创建自定义字段等。还包括SendGrid Webhooks的解析器。

数学

  • UnitConversion - 用于.NET Core和.NET Framework的可扩展单元转换库。
  • AutoDiff - 一个库,提供快速,准确和自动的数学函数微分(计算导数/梯度)。

大杂烩

  • AdvanceDLSupport - 基于P/Invoke的库。
  • AngleSharp - 尖括号解析器库。它解析HTML5,MathML,SVG和CSS,以构建基于官方W3C规范的DOM。可与python的beautifulsoup4相媲美。
  • AgileMapper - AgileMapper是一个零配置,高度可配置的对象 - 对象映射库,具有可查看的执行计划。
  • AspNetCore Extension Library - ASP.NET Core扩展库。
  • AutoMapper - .NET中基于约定的对象关系映射库。
  • Baget - 轻量级NuGet服务器。
  • Bleak - Windows本机DLL注入库。
  • Bullseye - 用于描述和运行目标及其依赖项的.NET包。
  • Castle.Core - Castle Core提供常见的Castle Project抽象,包括日志记录服务。
  • Chessie - Railway-oriented编程库。
  • CliWrap - 命令行界面的包装库。
  • commanddotnet - 在类中为您的命令行应用程序接口建模。
  • CommonMark.NET - 在C#中实现CommonMark规范,用于将Markdown文档转换为HTML。
  • ConsoleTableExt - 用于为.NET控制台应用程序创建表的Fluent库。
  • CoordinateSharp - 一个可以快速格式化和转换地理坐标以及提供基于位置的太阳和月亮信息(日落,日出,月亮照明等)的库。 )。
  • datatables - jQuery DataTables的帮助程序。
  • DinkToPdf - 用于wkhtmltopdf库的C#.NET包装库,它使用Webkit引擎将HTML页面转换为PDF。
  • dotnet-env - 用于从.env文件加载环境变量的.NET库。
  • DotNet.Glob - 快速通配库。优于正则表达式。
  • Dotnet outdated - 显示过时的NuGet的工具库。
  • Dotnet Script - 从.NET CLI运行C#脚本。
  • Dotnet Serve - 用于.NET Core CLI的简单命令行HTTP服务器。
  • Eighty - 一个简单的HTML生成库
  • Enums.NET - Enums.NET是一个高性能类型安全的.NET枚举实用程序库
  • FastExpressionCompiler - 快速ExpressionTree编译器。
  • FluentDocker - FluentDocker是一个与docker-machine,docker-compose和docker交互的库。
  • FluentFTP - FTP和FTPS客户端,具有广泛的FTP命令,SSL / TLS连接,散列/校验等。
  • Fody - 编辑.net程序集的可扩展工具。
  • HdrHistogram.NET - 高动态范围(HDR)直方图。
  • httpclient-interception - 用于拦截服务器端HTTP依赖关系的.NET标准库。
  • Humanizer - Humanizer满足您操作和显示字符串,枚举,日期,时间,时间跨度,数字和数量的所有.NET需求。
  • Humidifier - Humidifier允许您以编程方式构建AWS CloudFormation模板。
  • impromptu-interface - 将DLR与Reflect.Emit结合使用的库。
  • JqueryDataTablesServerSide - 用于jQuery DataTable的Asp.Net Core服务器端库,具有分页,过滤,排序和Excel导出功能。
  • LibSass Host - 围绕LibSass库的.NET包装器,能够支持虚拟文件系统。
  • markdig - 可兼容Markdown处理库。
  • NFlags - 解析CLI和开箱即用功能的库。
  • NReco.LambdaParser - 将字符串表达式(公式,方法调用,条件)解析为LINQ表达式树,可以编译为lambda并进行求值。
  • NuGet Trends - 查看NuGet软件包的采用情况以及NuGet的最新趋势程序。
  • NYoutubeDL - youtube-dl库。
  • Otp.NET - 在C#中实现TOTP RFC 6238和HOTP RFC 4226。
  • pose - 用委托替换任何.NET方法(包括静态和非虚拟)
  • PuppeteerSharp - Puppeteer Sharp是官方Node.JS Puppeteer API的.NET端口。
  • readline - 可以代替内置组件Console.ReadLine()的库。
  • ReflectionMagic - Framework to drastically simplify your private reflection code using C# dynamic
  • Relinq - 使用re-linq,现在比以往更容易创建功能齐全的LINQ提供商。
  • ReverseMarkdown - Html到Markdown转换器库,附带一些unix shell终端优势。
  • PdfReport.Core - PdfReport.Core是一个代码优先的报告引擎,它建立在iTextSharp.LGPLv2.Core和EPPlus.Core库之上。
  • Scientist - 用于重构关键路径的.NET库。它是GitHub的Ruby Scientist库的一个端口。
  • Scrutor - Microsoft.Extensions.DependencyInjection的程序集扫描扩展。
  • Sheller - 读取Shell脚本的库。
  • SmartFormat.NET - string.Format的可扩展替代品。
  • Stocks
    • Trady - Trady是一个用于计算技术指标的便捷库,它的目标是成为一个自动交易系统,提供股票数据馈送,指标计算,策略建立和自动交易。
  • System.Linq.Dynamic.Core - System Linq Dynamic功能。
  • UnitsNet - Units.NET为您提供所有常用的度量单位和它们之间的转换。
  • Validation
    • FluentValidation - 流行的.NET验证库,用于构建强类型的验证规则。
    • Guard - 高性能,可扩展的参数验证库。
    • Valit - Valit是对.NET Core的简单验证库,减少if的使用。
  • warden-stack - 针对您的应用程序,资源和基础架构的“运行状况检查”。让守望者守在手表上。
  • WebEssentials.AspNetCore.ServiceWorker - ASP.NET核心渐进式Web应用程序。
  • Xabe.FFmpeg - 用于FFmpeg的.NET标准包装器。它允许在不知道FFmpeg如何工作的情况下处理媒体,并且可以用于将自定义参数传递给来自C#应用程序的FFmpeg。
  • YoutubeExplode - 用于提取元数据和下载Youtube视频和播放列表的终极库。

网络

  • AspNetCore.Proxy - Proxy代理库。
  • CurlThin - 轻量级cURL绑定库,支持通过curl_multi接口进行多个同时传输。
  • NETStandard.HttpListener - HttpListener(NETStandard)。
  • Networker - 一个简单易用的.NET TCP和UDP网络库,旨在实现灵活,可扩展和快速。

办公软件

  • EPPlus - 使用.NET创建高级Excel电子表格。
  • npoi - 可以读取/写入未安装Microsoft Office的Office格式的.NET库。没有COM +,没有互操作。
  • Open-XML-SDK - Open XML SDK提供了使用Office Word,Excel和PowerPoint文档的工具。

操作系统

  • CosmosOS - Cosmos是操作系统的“构建工具包”。使用托管语言(例如C#,VB.NET等)构建自己的OS!

对象关系映射ORM

分析

  • Glimpse - 适用于.NET的轻量级,开源,实时诊断和洞察分析器。 不稳定的版本
  • MiniProfiler - 一个简单但有效的ASP.NET网站迷你探查器。

sql生成器

  • SqlKata - 优雅的Sql查询生成器,支持复杂查询,连接,子查询,嵌套条件,供应商引擎目标等等

消息队列

  • emitter - 连接所有设备的免费开源实时消息服务。此发布 - 订阅消息传递API是为了提高速度和安全性而构建的。
  • EventStore - 使用JavaScript中的复杂事件处理的开源,功能数据库。
  • Foundatio - 内存,redis和azure实现的通用接口。
  • MediatR - 中介模式库。
  • MediatR.Extensions.Microsoft.DependencyInjection - MediatR的扩展程序
  • Mediator.Net - .Net的简单中介,用于发送支持管道的命令,发布事件和请求响应。
  • MicroBus - MicroBus中介模式库。
  • MQTTnet - MQTTnet是一个用于基于MQTT的通信的高性能.NET库。
  • netmq - NetMQ是轻量级消息传递库。
  • OpenCQRS - 用于DDD,CQRS和事件的.NET核心库,具有Azure Service Bus集成。 Command和Event存储支持的数据库提供程序包括:DocumentDB,MongoDB,SQL Server,MySQL,PostgreSQL和SQLite。
  • rabbitmq-dotnet-client - RabbitMQ .NET客户端。
  • RawRabbit - 用于通过RabbitMq进行通信的现代.NET框架。
  • Rebus - .NET的简单和精简服务总线实现。
  • Restbus - RabbitMq的消息传递库。
  • Tossit - 简单易用的库,用于分布式作业/工作人员逻辑。内置RabbitMQ实现处理的分布式消息。

报表

  • FastReport - .NET Core 2.x / .Net Framework 4.x的开源报告生成器。 FastReport可用于MVC,Web API应用程序。

任务计划

  • Chroniton.NetCore - 用于在日程安排上运行任务(作业)的轻量级健壮库。
  • Coravel - .Net Core符合Laravel:调度,排队等
  • FluentScheduler - 具有流畅界面的自动作业调度程序。
  • Gofer.NET - 用于.NET Core的分布式后台任务/作业的简易C#API。
  • HangfireIO - 在ASP.NET应用程序内执行即发即忘,延迟和重复性工作。
  • LiquidState - 高效异步和同步状态机。
  • NCrontab - 用于.NET的Crontab。
  • quartznet - Quartz.NET任务计划程序。
  • stateless - 用于在C#代码中创建状态机的简单库。

开发工具包SDKs

  • AWS SDK - Amazon Web Services(AWS).NET Core SDK组件。每个AWS服务都有自己的NuGet包。
  • azure-event-hubs-dotnet - Azure事件中心的.NET标准客户端库。
  • Blockchain clients
  • CakeMail.RestClient - CakeMail API的客户端。允许您发送交易电子邮件,批量电子邮件,管理列表和联系人等。
  • consuldotnet - 面向领事的.NET API。
  • csharp-nats - 用于NATS消息传递系统的C#.NET客户端。
  • DarkSkyCore - .NET标准库,用于使用Dark Sky API
  • Docker.DotNet - 用于Docker API的.NET(C#)客户端库。
  • firebase-admin-dotnet - Firebase Admin .NET SDK
  • google-cloud-dotnet - 适用于.NET的Google Cloud Client Libraries。
  • Manatee.Trello - 一个完全面向对象的.Net包装器,用于Trello用C#编写的RESTful API。
  • Microphone - 使用Consul或ETCD集群的Web Api或NancyFx运行自托管REST服务的轻量级框架。
  • octokit.net - 用于.NET的GitHub API客户端库。
  • PreStorm - ArcGIS Server的并行REST客户端。
  • SendGrid-csharp - 用于使用完整SendGrid API的C#客户端库。
  • statsd-csharp-client - 与.NET标准兼容的C#客户端与Etsy的优秀服务器。
  • tweetinvi - 直观的.NET C#库,用于访问Twitter REST和STREAM API。

安全

  • aspnetcore-security-headers - 用于向ASP.NET Core应用程序添加安全标头的中间件。
  • HtmlSanitizer - 清除HTML以避免XSS攻击。
  • jose-jwt - 用于处理JOSE对象的库(JWT,JWA,JWS及相关)。
  • Jwt.Net - Jwt.Net,一个用于.NET的JWT(JSON Web令牌)实现。
  • JWT Simple Server - 用于ASP.NET Core的轻量级动态jwt服务器。
  • NWebsec - ASP.NET的安全库。
  • reCAPTCHA - 用于ASP.NET Core的reCAPTCHA 2.0。
  • roslyn-security-guard - 旨在帮助.NET应用程序进行安全审计的Roslyn分析器。
  • OwaspHeaders - .NET Core中间件,用于注入Owasp推荐的HTTP标头,以提高安全性。
  • Security - 于Web应用程序的安全性和授权的中间件。
  • SecurityHeaders - 允许向ASP.NET Core网站添加安全标头的小包。

搜索

  • Algolia.Search - 官方Algolia .NET客户端的存储库。
  • AutoComplete - 持久,简单,强大且可移植的自动完成库。
  • Elasticsearch.Net & NEST - NEST和Elasticsearch.Net的存储库,这是两个官方Elasticsearch .NET客户端。
  • ElasticsearchCRUD - Elasticsearch .NET API。
  • SearchExtensions - IQueryable接口的高级搜索功能,例如Entity Framework查询。
  • SimMetrics.Net - 相似度量标准库,例如从编辑距离(Levenshtein,Gotoh,Jaro等)到其他指标,(例如Soundex,Chapman)
  • SolrExpress - 用于Solr的简单轻量级查询.NET库,采用可控,可构建和快速失败的方式。

序列化

  • BinarySerializer - 二进制序列化库,用于控制字节和位级别的数据格式。
  • bond - 用于处理模式化数据的跨平台框架。它支持跨语言的序列化和强大的通用机制,可以有效地处理数据。 Bond广泛用于Microsoft的高规模服务。
  • Channels - 基于推送的.NET流。
  • CsvHelper - 帮助读写CSV文件的库。
  • Edi.Net - EDI Serializer / Deserializer。支持EDIFact,X12和TRADACOMS格式。
  • ExtendedXmlSerializer - 用于.NET的扩展Xml序列化程序。
  • Jil - 基于Sigil构建的快速.NET JSON(De)串行器。
  • MessagePack
  • Newtonsoft.Json - 适用于.NET的流行高性能JSON框架。
  • protobuf-net - 用于惯用.NET的协议缓冲库。
  • Schema.NET - Schema.org对象变成了强类型的C#POCO类,用于.NET。所有类都可以序列化为JSON / JSON-LD和XML,通常用于表示html页面头部的结构化数据。
  • ServiceStack.Text - JSON,JSV和CSV文本序列化器。
  • TinyCsvParser - 易于使用,易于扩展和高性能的库,用于使用.NET进行CSV解析。
  • Wire - POCO对象的二进制序列化程序。
  • YamlDotNet - .NET
  • ZeroFormatter - 用于.NET的快速二进制(de)序列化程序。
  • Utf8Json - 用于C#(.NET,.NET Core,Unity,Xamarin)的绝对最快和零分配JSON序列化器。
  • YAXLib - 用于.NET Framework和.NET Core的XML序列化库。非常灵活和强大。

模板引擎

  • dotliquid - TobiasLütke的Liquid模板语言的.NET端口。
  • fluid - 开源.NET模板引擎,尽可能接近Liquid模板语言。
  • Portable.Xaml - 用于读/写xaml文件的可移植.NET库。
  • Razor - 用于MVC Web应用程序视图页面的CSHTML文件的分析器和代码生成器。
  • RazorLight - 基于Microsoft针对.NET Core的Razor解析引擎的模板引擎。
  • Scriban - A fast, powerful, safe and lightweight text templating language and engine for .NET.

测试

  • Bogus - 简单而健全的C#假数据生成器。基于并从着名的faker.js移植。
  • CoreBDD - xUnit.net的BDD框架
  • FakeItEasy - .NET的简易模拟库。
  • FluentAssertions - 一组.NET扩展方法,允许您更自然地指定TDD或BDD样式测试的预期结果。
  • GenFu - 可用于生成实际测试数据的库。
  • LightBDD - BDD框架允许创建易于阅读和维护的测试。
  • mockhttp - 为Microsoft的HttpClient库测试图层。
  • moq.netcore - 最受欢迎且最友好的.NET模拟框架。
  • MSpec - 用于编写BDD样式测试的流行测试框架。
  • MyTested.AspNetCore.Mvc - 流畅的测试 framework for ASP.NET Core MVC.
  • Netling - 加载测试客户端,以便轻松进行Web测试。
  • NSpec - 针对C#的战斗强化测试框架,受Mocha和RSpec的启发。
  • NSubstitute - .NET模拟框架的友好替代品。
  • nunit - 面向.NET Core的NUnit测试运行器。
  • shouldly - 断言框架_Should_ be!
  • SpecFlow - SpecFlow是用于.NET的实用BDD解决方案。
  • Storyteller - 一种制定可执行规范的工具。
  • Stubbery - 一个用于在.NET中创建和运行Api存根的简单库。
  • Testavior - Testavior是一个轻量级解决方案,可帮助您开发ASP.NET Core的行为测试。
  • TestStack.BDDfy - 最简单的BDD框架!
  • xBehave.net - 一个xUnit.net扩展,用于描述使用自然语言的测试。
  • xUnit.net - 一个免费的,开源的,以社区为中心的.NET Framework单元测试工具。

工具

  • CommandLineUtils - .NET Core和.NET Framework的命令行解析和实用程序。

  • docfx - 用于构建和发布.NET项目API文档的工具

  • dotnetfiddle - .NET沙箱,供开发人员快速尝试代码和共享代码片段。

  • dotnet-tools - .NET Core命令行(dotnet CLI)的工具扩展列表。

  • EntryPoint - .Net Core和.Net Framework 4.5+的可组合CLI(命令行)参数解析器。

  • Fake JSON Server - 用于原型设计或作为CRUD后端的假REST API。无需定义类型,使用动态类型。数据存储在单个JSON文件中。具有身份验证,WebSocket通知,异步长时间运行操作,错误/延迟的随机生成以及实验性GraphQL支持。

  • gitignore.io - 为您的项目创建有用的.gitignore文件。

  • ICanHasDotnetCore - 扫描上传的packages.config文件或GitHub存储库,并确定nuget包是否针对.NET Standard。

  • json2csharp - 从JSON生成C#类。

  • letsencrypt-win-simple - 适用于Windows的简单ACME客户端。

  • Linq_Faster - 数组,Span 和List 的类似于Linq的扩展。

  • mRemoteNG - 下一代mRemote,开源,标签,多协议,远程连接管理器

  • NJsonSchema - NJsonSchema是一个.NET库,用于读取,生成和验证JSON Schema draft v4 + schemas。

  • NuKeeper - 自动更新.NET项目中的nuget包。

  • NuGetPackageExplorer - 使用GUI创建,更新和部署Nuget软件包。

  • NugetVisualizer - 为一组给定的git存储库或文件夹可视化所有nuget包及其相应的版本。

  • OctoLinker - 使用适用于GitHub的OctoLinker浏览器扩展,有效地浏览projects.json文件。

  • posh-dotnet - [dotnet CLI]的“PowerShell”标签完成(https://github.com/dotnet/cli)。%E3%80%82)

  • Rin - ASP.NET Core的请求/响应Inspector中间件。像Glimpse。

  • scoop - Windows的命令行安装程序。

  • SerilogAnalyzer - 使用Serilog日志库对基于Roslyn的代码进行分析。检查常见错误和使用问题。

  • SharpZipLib - #ziplib是一个完全用C#编写的适用于.NET平台的Zip,GZip,Tar和BZip2库。

  • ShareX - 免费的开源程序,可让您捕捉或记录屏幕的任何区域,只需按一下键即可共享。它还允许将图像,文本或其他类型的文件上传到80多个支持的目的地,您可以从中选择。 https://getsharex.com

  • SharpLab - .NET代码游乐场,显示代码编译的中间步骤和结果。 https://sharplab.io

  • sourcelink - SourceLink是一个语言和源代码控制不可知系统,用于为二进制文件提供一流的源代码调试体验。

  • System.CommandLine - System.CommandLine:命令行解析,调用和呈现终端输出。

  • X.Web.Sitemap – 简单站点地图生成器。

  • X.Web.RSS – 简单站点RSS生成器。

  • SmartCode – SmartCode= IDataSource -> IBuildTask -> IOutput => Build Everything!!! (Including [Code generator])

Web框架

  • WebAssembly
    • Blazor - Blazor是使用C#/ Razor和HTML的.NET Web框架,可在带有WebAssembly的浏览器中运行。
      • Awesome Blazor - Blazor的资源,Blazor是使用C#/ Razor和HTML的.NET Web框架,可在具有WebAssembly的浏览器中运行。
      • Blazor Redux - 将Redux状态存储与Blazor连接。
    • Ooui - 是使用Web技术的.NET跨平台的小型UI库。
  • ReactJS.NET - 用于JSX编译和React组件的服务器端呈现的.NET库。
  • redux.NET - .NET应用程序的可预测状态容器。

Web Socket

  • Fleck - Fleck是C#中的WebSocket服务器实现。 Fleck不需要继承,容器或其他引用。
  • SignalR Server - Web应用程序的实时Web功能,包括服务器端推送。
  • SuperSocket - 轻量级,跨平台和可扩展的套接字服务器应用程序框架。
  • WampSharp - [Web应用程序消息传递协议]的C#实现- 提供远程消息传递模式的协议过程通过WebSockets调用和发布/预订。
  • websocket-manager - ASP .NET Core的实时库。

Windows服务

工作流

  • CoreWF - Windows Workflow Foundation(WF)到.NET Core的端口。
  • workflow-core - .NET Standard的轻量级工作流引擎。
  • WorkflowEngine.NET - 在应用程序中添加工作流程的组件。
  • Wexflow - 高性能,可扩展,模块化和跨平台的工作流引擎。

线路图

入门套件

  • Arch - .NET Core库的集合。
    • AutoHistory - 自动记录数据更改历史记录的插件。
  • AspNetCore-Angular2-Universal - 跨平台 - 用于SEO,Bootstrap,i18n国际化(ngx-translate),Webpack的服务器端渲染,TypeScript,带Karma的单元测试,WebAPI REST设置,SignalR,Swagger文档等等!
  • ASP.NET Core Starter Kit - 使用Visual Studio Code,C#,F#,JavaScript,ASP.NET Core,EF Core,React(ReactJS),Redux,Babel进行跨平台的Web开发。单页应用样板。
  • aspnetcore-spa generator - Yeoman生成器,用于构建全新的ASP.NET Core单页面应用程序,该应用程序使用Angular 2 / React / React与Redux / Knockout / Aurelia在客户端上。
  • ASP.Net Core Vue Starter - Asp.NETCore 2.0 Vue 2(ES6)SPA入门套件,包含路由,Vuex等等!
  • bitwarden-core - 核心基础设施后端(API,数据库等)https://bitwarden.com
  • dotNetify - 构建实时HTML5 / C#.NET Web应用程序的简单,轻量级但功能强大的方法。
  • generator-aspnet - 用于ASP.NET Core的yo生成器。
  • Nucleus - 在后端使用ASP.NET Core API分层架构和基于JWT的身份验证的Vue启动应用程序模板
  • react-aspnet-boilerplate - 使用ASP.NET Core 1构建同构React应用程序的起点,利用现有技术。
  • saaskit - 用于构建SaaS应用程序的开发人员工具包。
  • serverlessDotNetStarter - .NET Core入门解决方案-通过无服务器框架进行部署,并且可以在VS Code中进行本地调试。

例子

文章

书籍

备忘录

视频学习

视频播客


虽然Gerrit 本身提供 Code Review和 Git 仓库的两大功能,但实际上很多项目用的是其他的Git仓库,例如GitLab和GitHub。
一般情况下,Gerrit位于最终代码库的前面一层,用于代码的人工审核和对CI任务的触发进行验证。

这里以GitLab为例
Gerrit和GitLab集成后,在Gerrit上的项目仓库有变化时,会自动同步到GitLab上对应的项目仓库中。
但Gerrit和GitLab的同步只能是单向同步(Gerrit–》GitLab),也就是说直接在GitLab上项目仓库的变动不会自动同步到Gerrit上。
因此建议在Gerrit和GitLab集成后,所有的操作都在Gerrit上完成。

Replication插件
如果想要将Gerrit上的改动自动同步到GitLab上,就需要用到Gerrit的Replication插件。
Replication 插件可以同时对接已有的 Git 仓库系统,通常用于提供 changes 的镜像或者热备份,自动地将 Gerrit Code Review创建的任何改动 push 到另外一个系统里。

2.1 配置Gerrit访问GitLab

将Gerrit的公钥添加到GitLab的管理员账号后,Gerrit服务可以通过SSH拉取GitLab上任意项目的代码。
特别注意:gerrit用户和root用户的公钥都要添加!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[gerrit@mt101 ~]$ pwd
/home/gerrit
[gerrit@mt101 ~]$ whoami
gerrit
[gerrit@mt101 ~]$ pwd
/home/gerrit
[gerrit@mt101 ~]$ cat .ssh/id_rsa.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDCE36kKv9cRTR/UU+7c620a6sYwptzVCRa8KBWBVijXNME+te4Nt2ZKp1uVvVOYKWv4akR/E5wMMTa9sYiE7EZJsC0cfg+FSuvc7WeoyT0hWYEWAabqp1cAApZUKSm7c06829dSTAPLj4MIEQOtEzID8iaq7+kGDf6RsGF6QRrAVx28k5ZJNvNFLpFqv4cjOaDbWOKaVXkrAgYYdLWWJ6xEeQTJ6yxCkk9KY7+rHEHd9zEoJYiA03J9UgxRRkyTX8vRW39RHVVM+GriOasAgwhvhFZXJsm6mJVXr2Y3AFcMNPo4YJNq68LGdU8bjqN78ysBbkxfIDq+r3ANc7+D+Az sshtest
[gerrit@mt101 ~]$
[gerrit@mt101 ~]$ su - root
Password:
Last login: Fri Dec 13 23:15:07 CST 2019 on pts/0
ABRT has detected 1 problem(s). For more info run: abrt-cli list --since 1576224692
[root@mt101 ~]
[root@mt101 ~]
/root
[root@mt101 ~]
root
[root@mt101 ~]
total 8
-rw------- 1 root root 1679 Dec 11 23:10 id_rsa
-rw-r--r-- 1 root root 392 Dec 11 23:10 id_rsa.pub
[root@mt101 ~]
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDkUOVXJ49OolxX34Lh/qlLmdEtYbPWlELx/Kxk6EWfmuGI6eYYVIOSE4b43nEXP0T0DM8j0jat75dZ4Zc+Z21PVplJ4uUuruBV/JMfOzx2vUD2zAFELq0TX4IMRJpPjYrk6asx1KYQUr/lQIxgqUbFsTDFWmIM186kcl4Q2daU8hznfP7pJBu4DAowxVPP+OsnhT93jq8IIwUFnaUBDUh5Fr/T56b0hOljUWFAIImB5j+SsYg5airaJK44AhuoeOnrUAQEApsdqOtRdyFdwpYgQVvN61WQKJlBOwmV8lGGUZeuvYmPeSf0VzUUPmhGIgtwyeTrr7I1TcpncffAILy/ root@mt101
[root@mt101 ~]
[root@mt101 ~]
logout
[gerrit@mt101 ~]$

root账户登录GitLab:Settings—》SSH Keys—》添加并保存。

2.2 配置GitLab访问Gerrit

GitLab:192.168.16.102

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
[root@mt102 ~]
/root
[root@mt102 ~]
root
[root@mt102 ~]
total 8
-rw------- 1 root root 1679 Dec 11 15:37 id_rsa
-rw-r--r-- 1 root root 392 Dec 11 15:37 id_rsa.pub
[root@mt102 ~]
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDGiLcDnAaj2gOemJKcnlGnGmy0cb9N2uaqyuzWdPZai+blPq7Mqt4DICnRKeU0MAoeEuSM8ST01zItwrTlsPZNMKEjJ60PqAohWhHHtJuHu0sl6fs2gdrgtGtjBeqRJ9C4iRTlD3PWCqzA9M7+vZhfPzKm3QffOacTpA6k00GSep2yIpko7vcwE7YwclulaSi+RQtwxBoJVK1x3vOrCVN0y4rK4RzbO0rLYByWDARZrzCjmh5Cfjs8vs4VyYAA0t5rDEcnU/mTSjBLT79Mhlzr9b9zpT21FkDphcatJoLbMLVEo02HYs3RzxWp+mtbrwt1hCMHcOdJ328UiA0JiDX1 root@mt102
[root@mt102 ~]
[root@mt102 ~]
[root@mt102 ~]
/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/root/.ssh/id_rsa.pub"
The authenticity of host '192.168.16.101 (192.168.16.101)' can't be established.
ECDSA key fingerprint is SHA256:oShRek8kc5ZO9vmSnwSPuWxKieGfPuTG8VorbWM6CiE.
ECDSA key fingerprint is MD5:94:44:a1:ea:8c:c9:2b:fd:85:5e:a5:78:2d:1e:b4:53.
Are you sure you want to continue connecting (yes/no)? yes
/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
root@192.168.16.101's password:

Number of key(s) added: 1

Now try logging into the machine, with: "ssh 'root@192.168.16.101'"
and check to make sure that only the key(s) you wanted were added.

[root@mt102 ~]
[root@mt102 ~]
Last login: Fri Dec 13 23:20:42 2019
[root@mt101 ~]
logout
Connection to 192.168.16.101 closed.
[root@mt102 ~]
[root@mt102 ~]
total 12
-rw------- 1 root root 1679 Dec 11 15:37 id_rsa
-rw-r--r-- 1 root root 392 Dec 11 15:37 id_rsa.pub
-rw-r--r-- 1 root root 176 Dec 13 23:27 known_hosts
[root@mt102 ~]
[root@mt102 ~]
192.168.16.101 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMtkxCPeK5boH0UWlpVYqNv3fIqsZSTj+9iW1l6XV6QHavtFg3QPpB37hAd3PIRPdOzIvwEoTWvk3vks2vzTPMI=
[root@mt102 ~]

Gerrit:192.168.16.101

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
[gerrit@mt101 ~]$ 
[gerrit@mt101 ~]$ su - root
Password:
Last login: Fri Dec 13 23:27:49 CST 2019 from 192.168.16.102 on pts/1
[root@mt101 ~]
[root@mt101 ~]
total 12
-rw------- 1 root root 392 Dec 13 23:27 authorized_keys
-rw------- 1 root root 1679 Dec 11 23:10 id_rsa
-rw-r--r-- 1 root root 392 Dec 11 23:10 id_rsa.pub
[root@mt101 ~]
[root@mt101 ~]
/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/root/.ssh/id_rsa.pub"
The authenticity of host '192.168.16.102 (192.168.16.102)' can't be established.
ECDSA key fingerprint is SHA256:oShRek8kc5ZO9vmSnwSPuWxKieGfPuTG8VorbWM6CiE.
ECDSA key fingerprint is MD5:94:44:a1:ea:8c:c9:2b:fd:85:5e:a5:78:2d:1e:b4:53.
Are you sure you want to continue connecting (yes/no)? yes
/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
root@192.168.16.102's password:

Number of key(s) added: 1

Now try logging into the machine, with: "ssh 'root@192.168.16.102'"
and check to make sure that only the key(s) you wanted were added.

[root@mt101 ~]
[root@mt101 ~]
Last login: Fri Dec 13 23:12:23 2019
[root@mt102 ~]
logout
Connection to 192.168.16.102 closed.
[root@mt101 ~]
[root@mt101 ~]
total 16
-rw------- 1 root root 392 Dec 13 23:27 authorized_keys
-rw------- 1 root root 1679 Dec 11 23:10 id_rsa
-rw-r--r-- 1 root root 392 Dec 11 23:10 id_rsa.pub
-rw-r--r-- 1 root root 176 Dec 13 23:31 known_hosts
[root@mt101 ~]
[root@mt101 ~]
192.168.16.102 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMtkxCPeK5boH0UWlpVYqNv3fIqsZSTj+9iW1l6XV6QHavtFg3QPpB37hAd3PIRPdOzIvwEoTWvk3vks2vzTPMI=
[root@mt101 ~]

在Gerrit服务器上编辑用户同步Gitlab的配置文件,需要手动创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[gerrit@mt101 ~]$ cd gerrit_testsite/etc/
[gerrit@mt101 etc]$ pwd
/home/gerrit/gerrit_testsite/etc
[gerrit@mt101 etc]$
[gerrit@mt101 etc]$ vim .ssh/config
[gerrit@mt101 etc]$ cat .ssh/config
Host 192.168.16.102
IdentityFile ~/.ssh/id_rsa
PreferredAuthentications publickey
[gerrit@mt101 etc]$
[gerrit@mt101 etc]$ ll .ssh/config
-rw------- 1 gerrit gerrit 86 Dec 13 23:37 .ssh/config
[gerrit@mt101 etc]$

在Gerrit配置文件中添加关于的设置。
replication插件用于实现Gerrit和远程代码仓库的自动同步。

1
2
[plugins]
allowRemoteAdmin = true

添加并重启Gerrit服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
[gerrit@mt101 ~]$ sudo vim gerrit_testsite/etc/gerrit.config 
[gerrit@mt101 ~]$ sudo cat gerrit_testsite/etc/gerrit.config
[gerrit]
basePath = git
canonicalWebUrl = http:
serverId = 0b911b9e-195a-46b0-a5cd-b407b776b344
[container]
javaOptions = "-Dflogger.backend_factory=com.google.common.flogger.backend.log4j.Log4jBackendFactory#getInstance"
javaOptions = "-Dflogger.logging_context=com.google.gerrit.server.logging.LoggingContext#getInstance"
user = root
javaHome = /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.161-2.b14.el7.x86_64/jre
[index]
type = lucene
[auth]
type = HTTP
[receive]
enableSignedPush = false
[sendemail]
smtpServer = localhost
[sshd]
listenAddress = *:29418
[httpd]
listenUrl = http:
[cache]
directory = cache
[gitweb]
type = gitweb
cgi = /var/www/git/gitweb.cgi
[plugins]
allowRemoteAdmin = true
[gerrit@mt101 ~]$
[gerrit@mt101 ~]$ sudo sh gerrit_testsite/bin/gerrit.sh restart
Stopping Gerrit Code Review: OK
Starting Gerrit Code Review: OK
[gerrit@mt101 ~]$

4.1 在GitLab中创建一个项目

  • 关闭Auto DevOps:Settings—》CI/CD—》Auto DevOps,去除勾选—》Save changes
  • 添加ReadMe文件

git@192.168.16.102:root/testrepo.git

4.2 在Gerrit创建一个空项目并同步GitLab

项目名与GitLab项目相同

  • 删除自动创建的目录:sudo rm -rf testrepo.git/
  • 重新从GitLab复制:git clone –bare git@192.168.16.102:root/testrepo.git
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
[gerrit@mt101 git]$ pwd
/home/gerrit/gerrit_testsite/git
[gerrit@mt101 git]$
[gerrit@mt101 git]$ ll
total 0
drwxr-xr-x 7 gerrit gerrit 119 Dec 10 14:43 All-Projects.git
drwxr-xr-x 7 gerrit gerrit 119 Dec 11 12:26 All-Users.git
drwxr-xr-x 7 root root 100 Dec 12 14:57 testrepo.git
[gerrit@mt101 git]$
[gerrit@mt101 git]$ sudo rm -rf testrepo.git/
[gerrit@mt101 git]$
[gerrit@mt101 git]$ git clone --bare git@192.168.16.102:root/testrepo.git
Cloning into bare repository 'testrepo.git'...
remote: Enumerating objects: 6, done.
remote: Counting objects: 100% (6/6), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 6 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (6/6), done.
[gerrit@mt101 git]$
[gerrit@mt101 git]$ ll
total 0
drwxr-xr-x 7 gerrit gerrit 119 Dec 10 14:43 All-Projects.git
drwxr-xr-x 7 gerrit gerrit 119 Dec 11 12:26 All-Users.git
drwxrwxr-x 7 gerrit gerrit 138 Dec 12 15:04 testrepo.git
[gerrit@mt101 git]$
[gerrit@mt101 git]$ ll testrepo.git/
total 16
drwxrwxr-x 2 gerrit gerrit 6 Dec 12 15:04 branches
-rw-rw-r-- 1 gerrit gerrit 128 Dec 12 15:04 config
-rw-rw-r-- 1 gerrit gerrit 73 Dec 12 15:04 description
-rw-rw-r-- 1 gerrit gerrit 23 Dec 12 15:04 HEAD
drwxrwxr-x 2 gerrit gerrit 242 Dec 12 15:04 hooks
drwxrwxr-x 2 gerrit gerrit 21 Dec 12 15:04 info
drwxrwxr-x 4 gerrit gerrit 30 Dec 12 15:04 objects
-rw-rw-r-- 1 gerrit gerrit 98 Dec 12 15:04 packed-refs
drwxrwxr-x 4 gerrit gerrit 31 Dec 12 15:04 refs
[gerrit@mt101 git]$
[gerrit@mt101 git]$ cat testrepo.git/config
[core]
repositoryformatversion = 0
filemode = true
bare = true
[remote "origin"]
url = git@192.168.16.102:root/testrepo.git
[gerrit@mt101 git]$

5.1 replication插件

通过Gerrit的replication插件来实现。
确认插件状态:已安装、已启用。

1
2
3
[gerrit@mt101 git]$ ssh -p 29418 admin@192.168.16.101 gerrit plugin ls |grep replication
replication v3.1.0 ENABLED replication.jar
[gerrit@mt101 git]$

5.2 配置插件

/home/gerrit/gerrit_testsite/etc目录下创建replication.config文件用于代码同步。
特别注意:以后每创建一个新的项目,都要在该配置文件中添加对应的配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[gerrit@mt101 etc]$ pwd
/home/gerrit/gerrit_testsite/etc
[gerrit@mt101 etc]$
[gerrit@mt101 etc]$ vim replication.config
[gerrit@mt101 etc]$
[gerrit@mt101 etc]$ cat replication.config
[remote "testrepo"]
projects = testrepo
url = git@192.168.16.102:root/testrepo.git
push = +refs/heads/*:refs/heads/*
push = +refs/tags/*:refs/tags/*
push = +refs/changes/*:refs/changes/*
threads = 3
[gerrit@mt101 etc]$
[gerrit@mt101 etc]$ ssh -p 29418 admin@192.168.16.101 gerrit plugin reload replication
[gerrit@mt101 etc]$

6.1 本地设置

  • 本地Git用户配置要和Gerrit用户信息一致
  • 本地公钥已添加到Gerrit用户配置中
  • 本地Git版本不能太低,否则会出现未知的错误
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Anliven@Anliven-PC /d/Project/testrepo (master)
λ git config --global user.name "admin"
Anliven@Anliven-PC /d/Project/testrepo (master)
λ git config --global user.email "anliven@126.com"
Anliven@Anliven-PC /d/Project
λ git config --list |grep user
user.name=admin
user.email=anliven@126.com
Anliven@Anliven-PC /d/Project
λ
Anliven@Anliven-PC /d/Project
λ git --version
git version 2.19.0.windows.1
Anliven@Anliven-PC /d/Project
λ
Anliven@Anliven-PC /d/Project
λ cat ~/.ssh/id_rsa.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDQSFpwR9eBCTKfTbPEE8TWIo75KfX3AczpXK6nTKGH248pxjfq7cDe2cGj1VOSqknMqvg0g1LLiXrgbIGbtclt49UKSFXxWnVm04rZHVsz9rtfzxuF/NmSqSv7isYLZvwaI4aGXkLwILOemqwCbPRybGlkIiU9sgFK6m5gBWpsA6WqbHGlll8o1jC1cIuiPcJuYwod4+ehI5GvH3Tr3rUjkWy0QEhFfV22Nt79mJ3D+xDpDP/SxhPtPWw1Ob1+10+0+xvSTvGR/gcLUnUXciIooJVSypTPLnGe+d4Y5i+jCzDRIxeXBEQrQXafoxTjPNMb/T84mkPH+MSSTnvAk6Oelcs9MJLDIlXcDGSKXSzl2XmjjiTglq0R5gnRtUL6jxcyR0Db5mEF5maumfyKz58/O5pSDGVRW13O6V0YGq6j8f5mcn8h1usdqabbirUGA5+7OB2Ek4KlLy/U6GkHtr5gY4zch08A8/xWowGppOjb8b/MqxJuLmQY1+vvUlupALd6nNmacugmlZK+ZsheTCJhdNHLBVl40b4UZdbD19LCI5cFonvJP979J4SIc8gHh7SXu7lSTRWciA0BKLrwhhHID1PHnF5o0re4kENZW4LYiXhBo998+7TtXiK+itnjUsfHRDPUvQC0OrhmLf/DAyanpRs65rXFONEaVGf4NS+PCQ== anliven@126.com
Anliven@Anliven-PC /d/Project
λ

6.2 下拉代码

这里使用了SSH方式下的Clone with commit-msg hook模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
Anliven@Anliven-PC /d/Project
λ pwd
/d/Project
Anliven@Anliven-PC /d/Project
λ ll
total 0
drwxr-xr-x 1 Anliven 197121 0 十一 28 00:05 testproject/
Anliven@Anliven-PC /d/Project
λ
Anliven@Anliven-PC /d/Project
λ ll
total 0
drwxr-xr-x 1 Anliven 197121 0 十一 28 00:05 testproject/
Anliven@Anliven-PC /d/Project
λ git clone "ssh://admin@192.168.16.101:29418/testrepo" && scp -p -P 29418 admin@192.168.16.101:hooks/commit-msg "testrepo/.git/hooks/"
Cloning into 'testrepo'...
remote: Counting objects: 12, done
remote: Finding sources: 100% (12/12)
remote: Total 12 (delta 0), reused 12 (delta 0)
Receiving objects: 100% (12/12), done.
commit-msg 100% 1790 297.0KB/s 00:00
Anliven@Anliven-PC /d/Project
λ
Anliven@Anliven-PC /d/Project
λ ll
total 0
drwxr-xr-x 1 Anliven 197121 0 十一 28 00:05 testproject/
drwxr-xr-x 1 Anliven 197121 0 十二 13 23:49 testrepo/
Anliven@Anliven-PC /d/Project
λ ll testrepo/
total 3
-rw-r--r-- 1 Anliven 197121 31 十二 13 23:49 README.md
-rw-r--r-- 1 Anliven 197121 41 十二 13 23:49 testlog.txt
-rw-r--r-- 1 Anliven 197121 9 十二 13 23:49 try.txt
Anliven@Anliven-PC /d/Project
λ

6.3 改动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Anliven@Anliven-PC /d/Project                                                                            
λ cd testrepo/
Anliven@Anliven-PC /d/Project/testrepo (master)
λ echo "1234567890" >> testlog.txt
Anliven@Anliven-PC /d/Project/testrepo (master)
λ git add *
warning: LF will be replaced by CRLF in testlog.txt.
The file will have its original line endings in your working directory
Anliven@Anliven-PC /d/Project/testrepo (master)
λ git commit -m "update testlog"
[master 6c8f1d2] update testlog
1 file changed, 1 insertion(+)
Anliven@Anliven-PC /d/Project/testrepo (master)
λ
Anliven@Anliven-PC /d/Project/testrepo (master)
λ cat testlog.txt
This is the first change from local PC.
1234567890
Anliven@Anliven-PC /d/Project/testrepo (master)
λ

6.4 提交改动

特别注意:
提交的命令变为“git push -u origin HEAD:refs/for/”格式。
“refs/for/*”会将变更提交放到暂存区中,等待代码审核和集成验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Anliven@Anliven-PC /d/Project/testrepo (master)                                                    
λ git push -u origin HEAD:refs/for/master
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 4 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 387 bytes | 387.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
remote: Processing changes: refs: 1, new: 1, done
remote:
remote: SUCCESS
remote:
remote: http://192.168.16.101:8083/c/testrepo/+/61 update testlog [NEW]
remote:
To ssh://192.168.16.101:29418/testrepo
* [new branch] HEAD -> refs/for/master
Anliven@Anliven-PC /d/Project/testrepo (master)
λ
Anliven@Anliven-PC /d/Project/testrepo (master)
λ git log -1
commit 6c8f1d20f23697d1d677ac86c30120fdd7d20c43 (HEAD -> master)
Author: admin <anliven@126.com>
Date: Fri Dec 13 23:53:08 2019 +0800

update testlog

Change-Id: I3513942d2b6d2b951b3470946170eb706c4ab8d6
Anliven@Anliven-PC /d/Project/testrepo (master)
λ

6.5 在Gerrit查看提交的改动

在登录界面的CHANGES栏目下可以看到提交的状态(Open、Merged和Abandoned)
这里可以看到改动正处于Open状态下。

点击“first change”这个Subject,可以看到具体信息

6.6 在Gerrit进行人工审核

  • 默认只有Project Owners和Administrator群组用户拥有“Code-Review”选项+2的权限(提交通过)
  • 普通用户的“Code-Review”选项只能选择+1(审核建议)

代码审核无误,点击“Code-Review+2”按钮

点击“Summit”按钮才能进入代码库

查看提交的最终状态
如果提交成功,页面History部分会出现“Change has been successfully merged”信息
点击页面中gitweb链接,可以直观的看到相关具体信息

CHANGES下的Merged信息

6.7 在GitLab确认同步信息

在GitLab的Master分支确认代码是否同步


6.8 Replication日志

可以从Replication日志获取同步状态信息。

1
2
3
4
5
6
[gerrit@mt101 ~]$ pwd
/home/gerrit
[gerrit@mt101 ~]$ ll gerrit_testsite/logs/replication_log
-rw-r--r-- 1 root root 1982 Dec 14 00:02 gerrit_testsite/logs/replication_log
[gerrit@mt101 ~]$

成功同步的日志

1
2
3
4
[2019-12-14 00:02:01,017] [c17c270d] Replication to git@192.168.16.102:root/testrepo.git started...
[2019-12-14 00:02:01,020] [c17c270d] Push to git@192.168.16.102:root/testrepo.git references: [RemoteRefUpdate[remoteName=refs/changes/61/61/meta, NOT_ATTEMPTED, (null)...6f1717ea24e438692c05ce9cb20255506e8167e6, srcRef=refs/changes/61/61/meta, forceUpdate, message=null], RemoteRefUpdate[remoteName=refs/heads/master, NOT_ATTEMPTED, (null)...6c8f1d20f23697d1d677ac86c30120fdd7d20c43, srcRef=refs/heads/master, forceUpdate, message=null]]
[2019-12-14 00:02:22,332] [c17c270d] Replication to git@192.168.16.102:root/testrepo.git completed in 21314ms, 15003ms delay, 0 retries

7.1 问题1 - 执行git commit命令报错

问题现象:执行git commit命令时报错“git: ‘interpret-trailers’ is not a git command. See ‘git –help’.”

1
2
3
4
5
6
7
[gerrit@mt101 testrepo]$ git commit -m "a new file"
git: 'interpret-trailers' is not a git command. See 'git --help'.
cannot insert change-id line in .git/COMMIT_EDITMSG
[gerrit@mt101 testrepo]$
[gerrit@mt101 testrepo]$ git --version
git version 1.8.3.1
[gerrit@mt101 testrepo]$

问题分析:Git版本版本太低导致。
处理方法:在不影响业务的情况下,升级git版本。
参考信息:https://blog.csdn.net/a10703060237/article/details/89704924

7.2 问题2:在本地执行“git clone”命令拉取代码时报错

问题现象:提示“Could not read from remote repository.  ”

1
2
3
4
5
6
7
8
Anliven@Anliven-PC /d/Project                                            
λ git clone "ssh://admin@192.168.16.101:29418/testrepo"
Cloning into 'testrepo'...
ssh: connect to host 192.168.16.101 port 29418: Connection timed out
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

问题分析:经排查,确认防火墙未开放端口,网络不通导致。
处理方法:防火墙开放端口,重新拉取代码。

1
2
3
4
5
[gerrit@mt101 ~]$ sudo firewall-cmd --zone=public --permanent --add-port=29418/tcp
success
[gerrit@mt101 ~]$ sudo firewall-cmd --reload
success
[gerrit@mt101 ~]$

CI持续集成系统环境–Gitlab+Gerrit+Jenkins完整对接:https://www.cnblogs.com/kevingrace/p/5651447.html


两种子仓库使用方式

  • git submodule(子模块)
  • git subtree(子树合并)

从1.5.2版本开始,官方新增Git Subtree并推荐使用这个功能来替代Git Submodule管理仓库共用(子仓库、子项目)

2.1 git submodule

  • 可以将其他仓库某个commit作为仓库的子目录
  • 克隆仓库需要额外的步骤 init 和 update
  • 产生.gitmodule文件记录和submodule版本信息
  • 删除submodule步骤繁琐
  • 可以在子仓库单独查看子仓库的修改记录,相当与在一个单独的仓库内,对外层父仓库不可见

2.2 git subtree

  • 官方推荐方式
  • 不增加.gitmodule等文件
  • 管理和更新流程简洁,对于项目中的其他成员透明(意味着可以不知道subtree的存在)
  • 本质就是把子项目目录作为一个普通的文件目录,对于父级的主项目来说是完全透明的,原来是怎么操作现在依旧是那么操作
  • 无法直接单独查看子仓库的修改记录,因为子仓库的修改包含在父仓库的记录中了。

3.1 在父仓库中新增子仓库

1
2
3
4
cd <父仓库>
git subtree add --prefix=<子仓库在父仓库的相对路径> <子仓库地址> <branch> --squash


如果不需要更新或推送子仓库的改动,那么对于其他项目人员来说,可以不需要知道子仓库的存在。
也就是说,在这种情况下,子仓库就相当于父仓库的一个普通目录。

注意:
如果在子仓库发生改动(更新和修改)后,在父仓库中运行git status查看到子仓库文件显示modified,需要在父仓库中使用 add commit push 提交推送。
也就是说,子仓库的更改是会反映在父仓库的更改上的,因此只要是对子仓库进行了修改,无论如何都需要对父仓库进行一次提交。

示例:

1
2
cd temp-test-1
git subtree add --prefix=sub/temp-test-2 <temp-test-2 address> master --squash

此时通过git log可以查看到新增两条commit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
commit b76c7b190760f33e7ae9dfeba40136e39309b737 (HEAD -> master)
Merge: b0aec0a e6a10bc
Author:
Date:

Merge commit 'e6a10bc748638240ff372ae19c747584b7d8d1af' as 'sub/temp-test-2'

commit e6a10bc748638240ff372ae19c747584b7d8d1af
Author:
Date:

Squashed 'sub/temp-test-2/' content from commit bfb58a3

git-subtree-dir: sub/temp-test-2
git-subtree-split: bfb58a379631813584d286ee00a19a79860f9562

3.2 父仓库的改动

在父仓库目录下查看状态和提交修改都和原来一样,保持不变。

3.3 拉取子仓库的更新

1
git subtree pull --prefix=sub/temp-test-2 <temp-test-2 address> master --squash

3.4 推送子仓库的修改

1
git subtree push --prefix=sub/temp-test-2 <temp-test-2 address> master

3.5 子仓库切出起点

可以将子项目当前版本切出为一个分支,作为 push 时遍历的新起点,这样以后每次遍历都只从上次切出的分支的起点开始,不会再遍历以前的了,节约时间。
这个分支只是作起点储存用的,不用管它不用修改不用推送到远程库。
需要更新这个起点时,只需要再在当前版本上再切出一个作起点的分支覆盖原来的,命令和第一次切出分支作起点时相同。

1
git subtree split [--rejoin] --prefix=<本地子项目目录> --branch <主项目中作为放置子项目的分支名>

注意:
如果 push 时使用了 –squash 参数合并提交,那么 split 时不能使用 –rejoin 参数,反之必须使用。

1
2
3
4
5
git clone <repository> --recursive  
git submodule add <repository address> <path>
git submodule init
git submodule update
git submodule foreach git pull

4.1 在父仓库中新增子仓库

1
2
cd <父仓库>
git submodule add <子仓库地址> <子仓库在父仓库的相对路径>

命令执行成功后

  • 父仓库根目录下会产生.gitmodules文件,包含子仓库的path和url信息, 并且.gitmodules在父仓库的git版本控制中
  • 父仓库的git配置文件中加入了submodule字段,包含子仓库的url信息
  • 父仓库.git目录下生成modudles文件夹,包含子仓库的所有相关信息

示例: 在父仓库中新增子仓库并提交子仓库信息

1
2
3
4
5
cd <project>
git submodule add <module repo addr> <module path>
git add *
git commit -m "add submodule"
git push origin master

4.2 拉取整个仓库

如果单纯使用git clone命令,克隆一个包含子仓库的仓库,并不会clone子仓库的内容。
需要执行本地.gitmodules初始化的命令,再同步远端submodule源码。

方式1: 获取父仓库和所有子仓库的内容

1
2
3
git clone <父仓库地址> --recursive  或者 git clone <父仓库地址> --recurse-submodules


方式2:

1
2
3
4
5
git clone <父仓库地址>
git submodule init && git submodule update 或者 git submodule update --init --recursive



4.3 修改子仓库

如果子仓库发生改动,需要先在子仓库提交,然后再到父仓库提交。
子仓库提交结束后,在父仓库的根目录执行 git status 命令会显示子仓库有新的提交。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
cd <project>/<module>
git branch
echo "This is a submodule." > sm.txt
git add *
git commit -m "add sm.txt"
git push

cd ..
git status
git diff
git add *
git commit -m "update submodule add sm.txt"
git push

4.4 更新子仓库

非子仓库的开发人员只需在父仓库下pull代码时,如果发现submodule有更改,执行git submodule update进行更新,然后将改动提交到父仓库。
默认的使用git status可以看到父仓库中submodule commit id的改变。

方式1: 先pull父项目,然后执行git submodule update

1
2
3
cd <project>
git pull
git submodule update

方式2: 先进入子模块,然后切换到需要的分支,然后对子模块pull

1
2
3
4
cd <project>/<module>
git checkout master
cd ..
git submodule foreach git pull

4.5 删除submodule

方式1:

1
2
3
4
git submodule deinit -f <submodule>  
git rm --cached <submodule>
git submodule
git commit -m "remove submodule"

方式2:

1
2
3
4
git rm -rf <子仓库在父仓库的相对路径>
rm -rf .git/modules/<子仓库名称>
vim .git/config # 删除submodule相关的内容
git commit -m "remove submodule"

我们已经从SVN 切换到Git很多年了,现在几乎所有的项目都在使用Github管理, 本篇文章讲一下为什么使用Git, 以及如何在团队中正确使用。

Git的优点

Git的优点很多,但是这里只列出我认为非常突出的几点。

  1. 由于是分布式,所有本地库包含了远程库的所有内容。
  2. 优秀的分支模型,打分支以及合并分支,机器方便。
  3. 快速,在这个时间就是金钱的时代,Git由于代码都在本地,打分支和合并分支机器快速,使用个SVN的能深刻体会到这种优势。

感兴趣的,可以去看一下Git本身的设计,内在的架构体现了很多的优势,不愧是出资天才程序员Linus (Linux之父) 之手

版本管理的挑战

虽然有这么优秀的版本管理工具,但是我们面对版本管理的时候,依然有非常大得挑战,我们都知道大家工作在同一个仓库上,那么彼此的代码协作必然带来很多问题和挑战,如下:

  1. 如何开始一个Feature的开发,而不影响别的Feature?
  2. 由于很容易创建新分支,分支多了如何管理,时间久了,如何知道每个分支是干什么的?
  3. 哪些分支已经合并回了主干?
  4. 如何进行Release的管理?开始一个Release的时候如何冻结Feature, 如何在Prepare Release的时候,开发人员可以继续开发新的功能?
  5. 线上代码出Bug了,如何快速修复?而且修复的代码要包含到开发人员的分支以及下一个Release?

大部分开发人员现在使用Git就只是用三个甚至两个分支,一个是Master, 一个是Develop, 还有一个是基于Develop打得各种分支。这个在小项目规模的时候还勉强可以支撑,因为很多人做项目就只有一个Release, 但是人员一多,而且项目周期一长就会出现各种问题。

Git Flow

就像代码需要代码规范一样,代码管理同样需要一个清晰的流程和规范

Vincent Driessen 同学为了解决这个问题提出了 A Successful Git Branching Model

下面是Git Flow的流程图

上面的图你理解不了? 没关系,这不是你的错,我觉得这张图本身有点问题,这张图应该左转90度,大家应该就很用以理解了。

Git Flow常用的分支

  • Production 分支

也就是我们经常使用的Master分支,这个分支最近发布到生产环境的代码,最近发布的Release, 这个分支只能从其他分支合并,不能在这个分支直接修改

  • Develop 分支

这个分支是我们是我们的主开发分支,包含所有要发布到下一个Release的代码,这个主要合并与其他分支,比如Feature分支

  • Feature 分支

这个分支主要是用来开发一个新的功能,一旦开发完成,我们合并回Develop分支进入下一个Release

  • Release分支

当你需要一个发布一个新Release的时候,我们基于Develop分支创建一个Release分支,完成Release后,我们合并到Master和Develop分支

  • Hotfix分支

当我们在Production发现新的Bug时候,我们需要创建一个Hotfix, 完成Hotfix后,我们合并回Master和Develop分支,所以Hotfix的改动会进入下一个Release

Git Flow如何工作

初始分支

所有在Master分支上的Commit应该Tag

Feature 分支

分支名 feature/*

Feature分支做完后,必须合并回Develop分支, 合并完分支后一般会删点这个Feature分支,但是我们也可以保留

Release分支

分支名 release/*

Release分支基于Develop分支创建,打完Release分之后,我们可以在这个Release分支上测试,修改Bug等。同时,其它开发人员可以基于开发新的Feature (记住:一旦打了Release分支之后不要从Develop分支上合并新的改动到Release分支)

发布Release分支时,合并Release到Master和Develop, 同时在Master分支上打个Tag记住Release版本号,然后可以删除Release分支了。

维护分支 Hotfix

分支名 hotfix/*

hotfix分支基于Master分支创建,开发完后需要合并回Master和Develop分支,同时在Master上打一个tag

Git Flow代码示例

a. 创建develop分支

1
2
git branch develop
git push -u origin develop

b. 开始新Feature开发

1
2
3
4
5
6
7
git checkout -b some-feature develop

git push -u origin some-feature

git status
git add some-file
git commit

c. 完成Feature

1
2
3
4
5
6
7
8
git pull origin develop
git checkout develop
git merge --no-ff some-feature
git push origin develop

git branch -d some-feature

git push origin --delete some-feature

d. 开始Relase

1
2
3
4
git checkout -b release-0.1.0 develop



e. 完成Release

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
git checkout master
git merge --no-ff release-0.1.0
git push

git checkout develop
git merge --no-ff release-0.1.0
git push

git branch -d release-0.1.0


git push origin --delete release-0.1.0


git tag -a v0.1.0 master
git push --tags

f. 开始Hotfix

1
git checkout -b hotfix-0.1.1 master    

g. 完成Hotfix

1
2
3
4
5
6
7
8
9
10
11
12
13
git checkout master
git merge --no-ff hotfix-0.1.1
git push


git checkout develop
git merge --no-ff hotfix-0.1.1
git push

git branch -d hotfix-0.1.1

git tag -a v0.1.1 master
git push --tags

Git flow工具

实际上,当你理解了上面的流程后,你完全不用使用工具,但是实际上我们大部分人很多命令就是记不住呀,流程就是记不住呀,肿么办呢?

总有聪明的人创造好的工具给大家用, 那就是Git flow script.

安装

  • OS X

brew install git-flow

  • Linux

apt-get install git-flow

  • Windows

wget -q -O - –no-check-certificate https://github.com/nvie/gitflow/raw/develop/contrib/gitflow-installer.sh | bash

使用

  • 初始化: git flow init

  • 开始新Feature: git flow feature start MYFEATURE

  • Publish一个Feature(也就是push到远程): git flow feature publish MYFEATURE

  • 获取Publish的Feature: git flow feature pull origin MYFEATURE

  • 完成一个Feature: git flow feature finish MYFEATURE

  • 开始一个Release: git flow release start RELEASE [BASE]

  • Publish一个Release: git flow release publish RELEASE

  • 发布Release: git flow release finish RELEASE
    别忘了git push –tags

  • 开始一个Hotfix: git flow hotfix start VERSION [BASENAME]

  • 发布一个Hotfix: git flow hotfix finish VERSION

Git Flow GUI

上面讲了这么多,我知道还有人记不住,那么又有人做出了GUI 工具,你只需要点击下一步就行,工具帮你干这些事!!!

SourceTree

当你用Git-flow初始化后,基本上你只需要点击git flow菜单选择start feature, release或者hotfix, 做完后再次选择git flow菜单,点击Done Action. 我勒个去,我实在想不到还有比这更简单的了。

目前SourceTree支持Mac, Windows, Linux.

这么好的工具请问多少钱呢? 免费!!!!

Git flow for visual studio

广大VS的福音
GitFlow for Visual Studio

最后

我经常收到邮件问我,他想使用Git, 但是公司还在坚持使用SVN等,问我最么办? 我的办法是:

第一: 把我这篇文章给他看

第二: 立即找我,加入我们公司,我的邮箱是wangdeshui@outlook.com QQ: 353275476