├── gin ├── a.txt ├── b.txt ├── key.pem ├── go.mod ├── cert.pem ├── README.md ├── gin.go └── go.sum ├── timer ├── go.mod ├── main.go └── README.md ├── oauth2 ├── go.mod ├── public │ ├── index.tmpl │ └── welcome.html └── main.go ├── os-exec ├── go.mod ├── testcmd │ ├── testcmd │ └── main.go ├── main.go └── README.md ├── recover ├── go.mod ├── recover.go └── README.md ├── windows_api ├── go.mod ├── go.sum └── main_windows.go ├── swaggo-gin ├── Makefile ├── api.md ├── doc.go ├── main.go ├── go.mod ├── docs │ ├── swagger.yaml │ ├── swagger.json │ └── docs.go └── handle.go ├── benchmark ├── main.go ├── go.mod ├── main_test.go ├── go.sum └── README.md ├── grpc ├── README.md ├── go.mod ├── demo1 │ ├── helloworld │ │ ├── hello_world.proto │ │ └── hello_world.pb.go │ └── README.md ├── demo2 │ ├── helloworld │ │ ├── hello_world.proto │ │ └── hello_world.pb.go │ ├── client │ │ └── client.go │ ├── server │ │ └── server.go │ └── README.md ├── demo3 │ ├── helloworld │ │ └── hello_world.proto │ ├── client │ │ ├── certs │ │ │ ├── root.pem │ │ │ ├── test_client.key │ │ │ └── test_client.pem │ │ └── client.go │ ├── server │ │ ├── certs │ │ │ ├── root.pem │ │ │ ├── test_server.key │ │ │ └── test_server.pem │ │ └── server.go │ └── README.md └── Makefile ├── .gitignore ├── gomod ├── main.go ├── go.mod └── README.md ├── json └── tag │ └── tag.go ├── restful-api ├── go.mod ├── main.go ├── README.md └── go.sum └── README.md /gin/a.txt: -------------------------------------------------------------------------------- 1 | hello world , I`m file a.txt -------------------------------------------------------------------------------- /gin/b.txt: -------------------------------------------------------------------------------- 1 | hello world , I`m file b.txt -------------------------------------------------------------------------------- /timer/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/razeencheng/demo-go/timer 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /oauth2/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/razeencheng/demo-go/oauth2 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /os-exec/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/razeencheng/demo-go/os-exec 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /recover/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/razeencheng/demo-go/recover 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /os-exec/testcmd/testcmd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/razeencheng/demo-go/HEAD/os-exec/testcmd/testcmd -------------------------------------------------------------------------------- /windows_api/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/razeencheng/demo-go/windows_api 2 | 3 | go 1.18 4 | 5 | require github.com/pkg/errors v0.9.1 6 | -------------------------------------------------------------------------------- /swaggo-gin/Makefile: -------------------------------------------------------------------------------- 1 | build: swag 2 | go build -tags "doc" 3 | build_prod: 4 | go build 5 | swag: 6 | swag init --markdownFiles . 7 | run: build 8 | ./swaggo-gin -------------------------------------------------------------------------------- /windows_api/go.sum: -------------------------------------------------------------------------------- 1 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 2 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 3 | -------------------------------------------------------------------------------- /gin/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | MHcCAQEEIEGYhoYypHmtEdwkQZqOp1ADqTaw5zSKek6zMaCHpE9ZoAoGCCqGSM49 3 | AwEHoUQDQgAEsLzd9fZ1JyfanPY6vrVrGmFeIMIJTDsaJsjxryEbJj/dLTvYHoJ+ 4 | ps4yK6MnSd3T6dEKWipD7YB3NZo1oTYXug== 5 | -----END EC PRIVATE KEY----- 6 | 7 | -------------------------------------------------------------------------------- /benchmark/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | ) 7 | 8 | func EncodeA(b []byte) string { 9 | return fmt.Sprintf("%x", b) 10 | } 11 | 12 | func EncodeB(b []byte) string { 13 | return hex.EncodeToString(b) 14 | } 15 | -------------------------------------------------------------------------------- /oauth2/public/index.tmpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Login with github 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /swaggo-gin/api.md: -------------------------------------------------------------------------------- 1 | # Test Example Makedown 2 | 3 | ### 关于使用说明 4 | 5 | 吧啦吧啦吧啦 6 | 7 | ![](https://camo.githubusercontent.com/4752126ebe1c5fe33cd179136fcbcf55e2074c8bacf90c378231256df809d687/68747470733a2f2f62616467652e667572792e696f2f67682f676f2d73776167676572253246676f2d737761676765722e737667) -------------------------------------------------------------------------------- /benchmark/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/razeencheng/demo-go/benchmark 2 | 3 | go 1.18 4 | 5 | require github.com/stretchr/testify v1.7.4 6 | 7 | require ( 8 | github.com/davecgh/go-spew v1.1.1 // indirect 9 | github.com/pmezard/go-difflib v1.0.0 // indirect 10 | gopkg.in/yaml.v3 v3.0.1 // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /grpc/README.md: -------------------------------------------------------------------------------- 1 | 在gRPC官网用了一句话来介绍:“一个高性能、开源的通用RPC框架”,同时介绍了其四大特点: 2 | 3 | * 定义简单 4 | * 支持多种编程语言多种平台 5 | * 快速启动和缩放 6 | * 双向流媒体和集成身份验证 7 | 8 | 9 | 10 | 在`gRPC在go中使用`系列中,关于其简介与性能我就不多介绍,相信在社区也有很多关于这些的讨论。这里我主要从三个层次来总结我以往在Go中使用gRPC的一些经验,主要分为: 11 | 12 | 1. Protocol Buffers语法与相关使用 13 | 2. gRPC实现简单通讯 14 | 3. gRPC服务认证与双向流通讯 -------------------------------------------------------------------------------- /swaggo-gin/doc.go: -------------------------------------------------------------------------------- 1 | //go:build doc 2 | // +build doc 3 | 4 | package main 5 | 6 | import ( 7 | _ "github.com/razeencheng/demo-go/swaggo-gin/docs" 8 | 9 | swaggerfiles "github.com/swaggo/files" 10 | ginSwagger "github.com/swaggo/gin-swagger" 11 | ) 12 | 13 | func init() { 14 | swagHandler = ginSwagger.WrapHandler(swaggerfiles.Handler) 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # Output of the go coverage tool, specifically when used with LiteIDE 11 | *.out 12 | 13 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 14 | .glide/ 15 | .vscode/ 16 | .idea/ 17 | .DS_Store 18 | swaggo-gin/swaggo-gin 19 | restful-api/restful-api -------------------------------------------------------------------------------- /grpc/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/razeencheng/demo-go/grpc 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/golang/protobuf v1.5.2 7 | golang.org/x/net v0.0.0-20220621193019-9d032be2e588 8 | google.golang.org/grpc v1.47.0 9 | ) 10 | 11 | require ( 12 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect 13 | golang.org/x/text v0.3.8 // indirect 14 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect 15 | google.golang.org/protobuf v1.27.1 // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /benchmark/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | var buf = []byte("skdjadialsdgasadasdhsakdjsahlskdjagloqweiqwo") 10 | 11 | func TestEqual(t *testing.T) { 12 | should := require.New(t) 13 | should.Equal(EncodeA(buf), EncodeB(buf)) 14 | } 15 | 16 | func BenchmarkEncodeA(b *testing.B) { 17 | for i := 0; i < b.N; i++ { 18 | EncodeA(buf) 19 | } 20 | } 21 | 22 | func BenchmarkEncodeB(b *testing.B) { 23 | for i := 0; i < b.N; i++ { 24 | EncodeB(buf) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /gomod/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/coreos/etcd/pkg/transport" 5 | "github.com/google/certificate-transparency-go/tls" 6 | "github.com/qiniu/api.v7/auth/qbox" 7 | "go.etcd.io/etcd/clientv3" 8 | "google.golang.org/grpc" 9 | "qiniupkg.com/x/log.v7" 10 | ) 11 | 12 | func main() { 13 | 14 | _ = transport.TLSInfo{} 15 | 16 | _ = clientv3.WatchResponse{} 17 | 18 | _, _ = clientv3.New(clientv3.Config{}) 19 | 20 | _ = qbox.NewMac("", "") 21 | 22 | _ = tls.DigitallySigned{} 23 | 24 | _ = grpc.ClientConn{} 25 | 26 | log.Info("hello world") 27 | } 28 | -------------------------------------------------------------------------------- /grpc/demo1/helloworld/hello_world.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "github.com/razeencheng/demo-go/grpc/demo1/helloworld"; 4 | 5 | package helloworld; 6 | 7 | import "github.com/golang/protobuf/ptypes/any/any.proto"; 8 | 9 | message HelloWorldRequest { 10 | string greeting = 1; 11 | map infos = 2; 12 | } 13 | 14 | message HelloWorldResponse { 15 | string reply = 1; 16 | repeated google.protobuf.Any details = 2; 17 | } 18 | 19 | service HelloWorldService { 20 | rpc SayHelloWorld(HelloWorldRequest) returns (HelloWorldResponse){} 21 | } -------------------------------------------------------------------------------- /oauth2/public/welcome.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Hello 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /os-exec/testcmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "time" 8 | ) 9 | 10 | func main() { 11 | 12 | var ( 13 | start bool 14 | e bool 15 | ) 16 | 17 | flag.BoolVar(&start, "s", false, "start output") 18 | flag.BoolVar(&e, "e", false, "output err") 19 | flag.Parse() 20 | 21 | if start { 22 | for i := 5; i > 0; i-- { 23 | fmt.Fprintln(os.Stdout, "test cmd output", i) 24 | time.Sleep(1 * time.Second) 25 | } 26 | } 27 | 28 | if e { 29 | fmt.Fprintln(os.Stderr, "a err occur") 30 | os.Exit(1) 31 | } 32 | 33 | fmt.Fprintln(os.Stdout, "test cmd stdout") 34 | } 35 | -------------------------------------------------------------------------------- /recover/recover.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | 11 | ch := make(chan int, 10) 12 | 13 | for i := 2; i > 0; i-- { 14 | go func(i int) { 15 | defer func() { 16 | err := recover() 17 | if err != nil { 18 | log.Println(err) 19 | } 20 | }() 21 | for val := range ch { 22 | fmt.Println("---->", val, "Go", i) 23 | if val%2 == 1 && i == 1 { 24 | panic("BOOM BOOM") 25 | } 26 | time.Sleep(2 * time.Second) 27 | } 28 | }(i) 29 | } 30 | 31 | var i int 32 | for { 33 | ch <- i 34 | time.Sleep(1 * time.Second) 35 | fmt.Println(i, "<---") 36 | i++ 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /grpc/demo2/helloworld/hello_world.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "github.com/razeencheng/demo-go/grpc/demo2/helloworld"; 4 | 5 | package helloworld; 6 | 7 | import "github.com/golang/protobuf/ptypes/any/any.proto"; 8 | 9 | message HelloWorldRequest { 10 | string greeting = 1; 11 | map infos = 2; 12 | } 13 | 14 | message HelloWorldResponse { 15 | string reply = 1; 16 | repeated google.protobuf.Any details = 2; 17 | } 18 | 19 | service HelloWorldService { 20 | rpc SayHelloWorld(HelloWorldRequest) returns (HelloWorldResponse){} 21 | } 22 | 23 | message HelloWorld { 24 | string msg = 1; 25 | } 26 | 27 | message Error { 28 | repeated string msg = 1; 29 | } 30 | -------------------------------------------------------------------------------- /timer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "os/signal" 6 | "runtime/trace" 7 | "sync/atomic" 8 | "syscall" 9 | "time" 10 | ) 11 | 12 | func main(){ 13 | 14 | trace.Start(os.Stderr) 15 | defer trace.Stop() 16 | 17 | sigs := make(chan os.Signal,1) 18 | signal.Notify(sigs,syscall.SIGINT,syscall.SIGTERM) 19 | 20 | //time.AfterFunc(time.Second, func() { 21 | // println("done") 22 | //}) 23 | 24 | 25 | var num int64 26 | 27 | for i:=0; i< 1e3 ; i++ { 28 | time.AfterFunc(time.Second, func() { 29 | atomic.AddInt64(&num,1) 30 | }) 31 | } 32 | 33 | t:= 0 34 | for i:=0;i<1e10; i++ { 35 | t ++ 36 | } 37 | _ = t 38 | 39 | <- sigs 40 | 41 | // println(num,"timers created,",t,"iterations done") 42 | } 43 | -------------------------------------------------------------------------------- /grpc/demo2/client/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "log" 6 | 7 | "google.golang.org/grpc" 8 | 9 | pb "github.com/razeencheng/demo-go/grpc/demo2/helloworld" 10 | ) 11 | 12 | func main() { 13 | conn, err := grpc.Dial("localhost:8080", grpc.WithInsecure()) 14 | if err != nil { 15 | log.Fatalf("Dial failed:%v", err) 16 | } 17 | defer conn.Close() 18 | 19 | client := pb.NewHelloWorldServiceClient(conn) 20 | resp1, err := client.SayHelloWorld(context.Background(), &pb.HelloWorldRequest{ 21 | Greeting: "Hello Server 1 !!", 22 | Infos: map[string]string{"hello": "world"}, 23 | }) 24 | 25 | log.Printf("Resp1:%+v", resp1) 26 | 27 | resp2, err := client.SayHelloWorld(context.Background(), &pb.HelloWorldRequest{ 28 | Greeting: "Hello Server 2 !!", 29 | }) 30 | 31 | log.Printf("Resp2:%+v", resp2) 32 | } 33 | -------------------------------------------------------------------------------- /grpc/demo3/helloworld/hello_world.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "github.com/razeencheng/demo-go/grpc/demo3/helloworld"; 4 | 5 | package helloworld; 6 | 7 | import "github.com/golang/protobuf/ptypes/any/any.proto"; 8 | 9 | message HelloWorldRequest { 10 | string greeting = 1; 11 | map infos = 2; 12 | } 13 | 14 | message HelloWorldResponse { 15 | string reply = 1; 16 | repeated google.protobuf.Any details = 2; 17 | } 18 | 19 | service HelloWorldService { 20 | rpc SayHelloWorld(HelloWorldRequest) returns (HelloWorldResponse){} 21 | rpc ListHello(HelloWorldRequest) returns (stream HelloWorldResponse) {} 22 | rpc SayMoreHello(stream HelloWorldRequest) returns (HelloWorldResponse) {} 23 | rpc SayHelloChat(stream HelloWorldRequest) returns (stream HelloWorldRequest) {} 24 | } 25 | 26 | message HelloWorld { 27 | string msg = 1; 28 | } 29 | 30 | message Error { 31 | repeated string msg = 1; 32 | } 33 | -------------------------------------------------------------------------------- /json/tag/tag.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | ) 7 | 8 | type Person struct { 9 | FirstName string `json:"first_name"` 10 | LastName string `json:"last_name"` 11 | Nickname string `json:"nickname,omitempty"` 12 | Sex int `json:"sex,string"` 13 | Age int `json:"age,omitempty"` 14 | AgeStr int `json:"age,omitempty,string"` 15 | Merried bool `json:"merried,omitempty"` 16 | Ms bool `json:"ms,omitempty,string"` 17 | Relation *Relation `json:"relation"` 18 | } 19 | 20 | type Relation struct { 21 | Mma *Person `json:"mma"` 22 | Son *Person `json:"son,omitempty"` 23 | } 24 | 25 | func main() { 26 | xiaoming := &Person{ 27 | FirstName: "xiaoming", 28 | Nickname: "", 29 | AgeStr: 18, 30 | Ms: true, 31 | Relation: &Relation{}, 32 | } 33 | buf, err := json.MarshalIndent(xiaoming, "", "") 34 | if err != nil { 35 | panic(err) 36 | } 37 | fmt.Println(string(buf)) 38 | } 39 | -------------------------------------------------------------------------------- /grpc/Makefile: -------------------------------------------------------------------------------- 1 | 2 | demo1_mac_gen: 3 | protoc -I $${GOPATH}/src --go_out=plugins=grpc:$${GOPATH}/src $${GOPATH}/src/github.com/razeencheng/demo-go/grpc/demo1/helloworld/hello_world.proto 4 | 5 | demo1_win_gen: 6 | protoc -I $$env:GOPATH\src --go_out=plugins=grpc:$$env:GOPATH\src $$env:GOPATH\src\github.com\razeencheng\demo-go\grpc\demo1\helloworld\hello_world.proto 7 | 8 | 9 | 10 | demo2_mac_gen: 11 | protoc -I $${GOPATH}/src --go_out=plugins=grpc:$${GOPATH}/src $${GOPATH}/src/github.com/razeencheng/demo-go/grpc/demo2/helloworld/hello_world.proto 12 | 13 | demo2_win_gen: 14 | protoc -I $$env:GOPATH\src --go_out=plugins=grpc:$$env:GOPATH\src $$env:GOPATH\src\github.com\razeencheng\demo-go\grpc\demo2\helloworld\hello_world.proto 15 | 16 | 17 | demo3_mac_gen: 18 | protoc -I $${GOPATH}/src --go_out=plugins=grpc:$${GOPATH}/src $${GOPATH}/src/github.com/razeencheng/demo-go/grpc/demo3/helloworld/hello_world.proto 19 | 20 | demo3_win_gen: 21 | protoc -I $$env:GOPATH\src --go_out=plugins=grpc:$$env:GOPATH\src $$env:GOPATH\src\github.com\razeencheng\demo-go\grpc\demo3\helloworld\hello_world.proto -------------------------------------------------------------------------------- /gomod/go.mod: -------------------------------------------------------------------------------- 1 | module demo-go/gomod 2 | 3 | go 1.16 4 | 5 | replace qiniupkg.com/x => qiniupkg.com/x v1.7.8 6 | 7 | replace github.com/qiniu/x => github.com/qiniu/x v1.7.8 8 | 9 | replace go.etcd.io/etcd => go.etcd.io/etcd v3.3.20+incompatible 10 | 11 | replace github.com/coreos/bbolt v1.3.6 => go.etcd.io/bbolt v1.3.6 12 | 13 | replace github.com/coreos/etcd => github.com/coreos/etcd v3.3.20+incompatible 14 | 15 | replace google.golang.org/grpc => google.golang.org/grpc v1.26.0 16 | 17 | require ( 18 | github.com/coreos/bbolt v1.3.6 // indirect 19 | github.com/coreos/etcd v3.3.10+incompatible 20 | github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect 21 | github.com/google/certificate-transparency-go v1.1.1 22 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect 23 | github.com/qiniu/api.v7 v7.2.5+incompatible 24 | github.com/qiniu/x v0.0.0-00010101000000-000000000000 // indirect 25 | github.com/soheilhy/cmux v0.1.5 // indirect 26 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect 27 | go.etcd.io/etcd v0.0.0-20200513171258-e048e166ab9c 28 | google.golang.org/grpc v1.29.1 29 | qiniupkg.com/x v0.0.0-00010101000000-000000000000 30 | sigs.k8s.io/yaml v1.2.0 // indirect 31 | ) 32 | -------------------------------------------------------------------------------- /grpc/demo2/server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "net" 7 | 8 | "github.com/golang/protobuf/ptypes" 9 | "github.com/golang/protobuf/ptypes/any" 10 | "google.golang.org/grpc" 11 | 12 | pb "github.com/razeencheng/demo-go/grpc/demo2/helloworld" 13 | ) 14 | 15 | type SayHelloServer struct{} 16 | 17 | func (s *SayHelloServer) SayHelloWorld(ctx context.Context, in *pb.HelloWorldRequest) (res *pb.HelloWorldResponse, err error) { 18 | log.Printf("Client Greeting:%s", in.Greeting) 19 | log.Printf("Client Info:%v", in.Infos) 20 | 21 | var an *any.Any 22 | if in.Infos["hello"] == "world" { 23 | an, err = ptypes.MarshalAny(&pb.HelloWorld{Msg: "Good Request"}) 24 | } else { 25 | an, err = ptypes.MarshalAny(&pb.Error{Msg: []string{"Bad Request", "Wrong Info Msg"}}) 26 | } 27 | 28 | if err != nil { 29 | return 30 | } 31 | return &pb.HelloWorldResponse{ 32 | Reply: "Hello World !!", 33 | Details: []*any.Any{an}, 34 | }, nil 35 | } 36 | 37 | func main() { 38 | lis, err := net.Listen("tcp", ":8080") 39 | if err != nil { 40 | panic(err) 41 | } 42 | 43 | grpcServer := grpc.NewServer() 44 | pb.RegisterHelloWorldServiceServer(grpcServer, &SayHelloServer{}) 45 | grpcServer.Serve(lis) 46 | } 47 | -------------------------------------------------------------------------------- /grpc/demo3/client/certs/root.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDizCCAnOgAwIBAgIRAMfjPkDKfELTo07l3A3cUSYwDQYJKoZIhvcNAQELBQAw 3 | XzELMAkGA1UEBhMCQ04xDjAMBgNVBAoTBU15U1NMMSwwKgYDVQQLEyNNeVNTTCBU 4 | ZXN0IFJvb3QgLSBGb3IgdGVzdCB1c2Ugb25seTESMBAGA1UEAxMJTXlTU0wuY29t 5 | MB4XDTE3MTExNjA1MzUzNVoXDTM3MTExNjA1MzUzNVowXzELMAkGA1UEBhMCQ04x 6 | DjAMBgNVBAoTBU15U1NMMSwwKgYDVQQLEyNNeVNTTCBUZXN0IFJvb3QgLSBGb3Ig 7 | dGVzdCB1c2Ugb25seTESMBAGA1UEAxMJTXlTU0wuY29tMIIBIjANBgkqhkiG9w0B 8 | AQEFAAOCAQ8AMIIBCgKCAQEAt2vNDivdBzQbYsatQNV7JoQ5z8nT0AVMBDbkb1zX 9 | tP6CyVp+nBdxMjqihrm1AsePciItsEmhYjxEi9JCp0BgXipHEN/Ecf2iNXwTIB8N 10 | X0oOg1pbIBa/ULscpc11JtGHzfHdydsqLKpM7dqdXqos9sn8Eh2+jAHAIcLjw8l7 11 | K4KpAS3ZL1NY7hCxDsXK2T03o6hqeiXIKsf4DPtQGbg3q0qmNluV0cMFWgsYsah0 12 | wyJNvAGGPtIK/2D40IjAFc+1yGcjbsgUAuhr1//DGYMgZNA3rDwPfqxJ/KMczdau 13 | 1Hy2P47QpidB2rZY5i2I12bwdtBFBLPtTYb0AoOAqIY4ewIDAQABo0IwQDAOBgNV 14 | HQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUmvGfuSUSEons 15 | cxi5l7zF+KAKflMwDQYJKoZIhvcNAQELBQADggEBALTy4qdRSUC7XGKnbN9JfQWH 16 | bxs0McAETrMlpz/HJJzBuQ1fB+qngxfTZxCzufQzEGkGxwwCm98EccyeMdokMiWK 17 | 0SKKmiavDiHImvnXYJ606UXZ8eKITp9C+F+pwWjIkYT2uO2mja1B1GbzkK1sKfFk 18 | fEZAcHSfES1K8HU/XWEUiTe0OijX9+oWtedi0MUp3enJvMjR3hiWJgSmukawNBj1 19 | a+Yvmia1M6z7KmSbQzL/jyq69QWmpO0dAaD0DVUB4/YFWHTw4J0qirS/dP0A8dAk 20 | CjWTnYPhCcO2uIcnqMt7zCVs5LXBK/XSwlAXKMvKT0uuzw9VxeMfEabflKu0By8= 21 | -----END CERTIFICATE----- -------------------------------------------------------------------------------- /grpc/demo3/server/certs/root.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDizCCAnOgAwIBAgIRAMfjPkDKfELTo07l3A3cUSYwDQYJKoZIhvcNAQELBQAw 3 | XzELMAkGA1UEBhMCQ04xDjAMBgNVBAoTBU15U1NMMSwwKgYDVQQLEyNNeVNTTCBU 4 | ZXN0IFJvb3QgLSBGb3IgdGVzdCB1c2Ugb25seTESMBAGA1UEAxMJTXlTU0wuY29t 5 | MB4XDTE3MTExNjA1MzUzNVoXDTM3MTExNjA1MzUzNVowXzELMAkGA1UEBhMCQ04x 6 | DjAMBgNVBAoTBU15U1NMMSwwKgYDVQQLEyNNeVNTTCBUZXN0IFJvb3QgLSBGb3Ig 7 | dGVzdCB1c2Ugb25seTESMBAGA1UEAxMJTXlTU0wuY29tMIIBIjANBgkqhkiG9w0B 8 | AQEFAAOCAQ8AMIIBCgKCAQEAt2vNDivdBzQbYsatQNV7JoQ5z8nT0AVMBDbkb1zX 9 | tP6CyVp+nBdxMjqihrm1AsePciItsEmhYjxEi9JCp0BgXipHEN/Ecf2iNXwTIB8N 10 | X0oOg1pbIBa/ULscpc11JtGHzfHdydsqLKpM7dqdXqos9sn8Eh2+jAHAIcLjw8l7 11 | K4KpAS3ZL1NY7hCxDsXK2T03o6hqeiXIKsf4DPtQGbg3q0qmNluV0cMFWgsYsah0 12 | wyJNvAGGPtIK/2D40IjAFc+1yGcjbsgUAuhr1//DGYMgZNA3rDwPfqxJ/KMczdau 13 | 1Hy2P47QpidB2rZY5i2I12bwdtBFBLPtTYb0AoOAqIY4ewIDAQABo0IwQDAOBgNV 14 | HQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUmvGfuSUSEons 15 | cxi5l7zF+KAKflMwDQYJKoZIhvcNAQELBQADggEBALTy4qdRSUC7XGKnbN9JfQWH 16 | bxs0McAETrMlpz/HJJzBuQ1fB+qngxfTZxCzufQzEGkGxwwCm98EccyeMdokMiWK 17 | 0SKKmiavDiHImvnXYJ606UXZ8eKITp9C+F+pwWjIkYT2uO2mja1B1GbzkK1sKfFk 18 | fEZAcHSfES1K8HU/XWEUiTe0OijX9+oWtedi0MUp3enJvMjR3hiWJgSmukawNBj1 19 | a+Yvmia1M6z7KmSbQzL/jyq69QWmpO0dAaD0DVUB4/YFWHTw4J0qirS/dP0A8dAk 20 | CjWTnYPhCcO2uIcnqMt7zCVs5LXBK/XSwlAXKMvKT0uuzw9VxeMfEabflKu0By8= 21 | -----END CERTIFICATE----- -------------------------------------------------------------------------------- /benchmark/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 5 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 6 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 7 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 8 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 9 | github.com/stretchr/testify v1.7.4 h1:wZRexSlwd7ZXfKINDLsO4r7WBt3gTKONc6K/VesHvHM= 10 | github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 11 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 12 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 13 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 14 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 15 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 16 | -------------------------------------------------------------------------------- /swaggo-gin/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/gin-gonic/contrib/sessions" 5 | "github.com/gin-gonic/gin" 6 | ) 7 | 8 | // 文档Handle 9 | var swagHandler gin.HandlerFunc 10 | 11 | // @title Swagger Example API 12 | // @version 1.0 13 | // @description This is a sample server celler server. 14 | // @termsOfService https://razeen.me 15 | 16 | // @contact.name Razeen 17 | // @contact.url https://razeen.me 18 | // @contact.email me@razeen.me 19 | 20 | // @tag.name TestTag1 21 | // @tag.description This is a test tag 22 | // @tag.docs.url https://razeen.me 23 | // @tag.docs.description This is my blog site 24 | 25 | // @license.name Apache 2.0 26 | // @license.url http://www.apache.org/licenses/LICENSE-2.0.html 27 | 28 | // @host 127.0.0.1:8080 29 | // @BasePath /api/v1 30 | 31 | // @schemes http https 32 | // @x-example-key {"key": "value"} 33 | 34 | // @description.markdown 35 | 36 | func main() { 37 | 38 | r := gin.Default() 39 | store := sessions.NewCookieStore([]byte("secret")) 40 | r.Use(sessions.Sessions("mysession", store)) 41 | 42 | v1 := r.Group("/api/v1") 43 | { 44 | v1.GET("/hello", HandleHello) 45 | v1.POST("/login", HandleLogin) 46 | v1Auth := v1.Use(HandleAuth) 47 | { 48 | v1Auth.POST("/upload", HandleUpload) 49 | v1Auth.GET("/list", HandleList) 50 | v1Auth.GET("/file/:id", HandleGetFile) 51 | v1Auth.POST("/json", HandleJSON) 52 | } 53 | } 54 | 55 | if swagHandler != nil { 56 | r.GET("/swagger/*any", swagHandler) 57 | } 58 | 59 | r.Run(":8080") 60 | } 61 | -------------------------------------------------------------------------------- /restful-api/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/razeencheng/demo-go/restful-api 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/gin-gonic/contrib v0.0.0-20201101042839-6a891bf89f19 7 | github.com/gin-gonic/gin v1.8.1 8 | ) 9 | 10 | require ( 11 | github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff // indirect 12 | github.com/gin-contrib/sse v0.1.0 // indirect 13 | github.com/go-playground/locales v0.14.0 // indirect 14 | github.com/go-playground/universal-translator v0.18.0 // indirect 15 | github.com/go-playground/validator/v10 v10.11.0 // indirect 16 | github.com/goccy/go-json v0.9.7 // indirect 17 | github.com/gomodule/redigo v2.0.0+incompatible // indirect 18 | github.com/gorilla/context v1.1.1 // indirect 19 | github.com/gorilla/securecookie v1.1.1 // indirect 20 | github.com/gorilla/sessions v1.2.1 // indirect 21 | github.com/json-iterator/go v1.1.12 // indirect 22 | github.com/leodido/go-urn v1.2.1 // indirect 23 | github.com/mattn/go-isatty v0.0.14 // indirect 24 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 25 | github.com/modern-go/reflect2 v1.0.2 // indirect 26 | github.com/pelletier/go-toml/v2 v2.0.2 // indirect 27 | github.com/ugorji/go/codec v1.2.7 // indirect 28 | golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect 29 | golang.org/x/net v0.0.0-20220621193019-9d032be2e588 // indirect 30 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect 31 | golang.org/x/text v0.3.8 // indirect 32 | google.golang.org/protobuf v1.28.0 // indirect 33 | gopkg.in/yaml.v2 v2.4.0 // indirect 34 | ) 35 | -------------------------------------------------------------------------------- /gin/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/razeencheng/demo-go/gin 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/gin-contrib/size v0.0.0-20220501091047-44dc10afe2e0 7 | github.com/gin-gonic/contrib v0.0.0-20201101042839-6a891bf89f19 8 | github.com/gin-gonic/gin v1.8.1 9 | ) 10 | 11 | require ( 12 | github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff // indirect 13 | github.com/gin-contrib/sse v0.1.0 // indirect 14 | github.com/go-playground/locales v0.14.0 // indirect 15 | github.com/go-playground/universal-translator v0.18.0 // indirect 16 | github.com/go-playground/validator/v10 v10.10.0 // indirect 17 | github.com/goccy/go-json v0.9.7 // indirect 18 | github.com/gomodule/redigo v2.0.0+incompatible // indirect 19 | github.com/gorilla/context v1.1.1 // indirect 20 | github.com/gorilla/securecookie v1.1.1 // indirect 21 | github.com/gorilla/sessions v1.2.1 // indirect 22 | github.com/json-iterator/go v1.1.12 // indirect 23 | github.com/leodido/go-urn v1.2.1 // indirect 24 | github.com/mattn/go-isatty v0.0.14 // indirect 25 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect 26 | github.com/modern-go/reflect2 v1.0.2 // indirect 27 | github.com/pelletier/go-toml/v2 v2.0.1 // indirect 28 | github.com/ugorji/go/codec v1.2.7 // indirect 29 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect 30 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 // indirect 31 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect 32 | golang.org/x/text v0.3.8 // indirect 33 | google.golang.org/protobuf v1.28.0 // indirect 34 | gopkg.in/yaml.v2 v2.4.0 // indirect 35 | ) 36 | -------------------------------------------------------------------------------- /grpc/demo3/server/certs/test_server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEA0d8HO5kgssjpu3bBpaOWKUepnelB1Be84/24lPyq1IpdePXU 3 | xCZbjLd082g0gocShUGHyYAEEDJ3Gng3e6iBhaj90RDbUYBQO09anKCRezbCh5g5 4 | Ei4g72PwPVxIym/i4h6GH6zRExXjtwMCS3zNvijTYB24vvaDGLdQAzqHtkRBzGis 5 | vGFGp+Dq9YDUnvDtZUdEceEUbG+8oW6VI5LrRCNS4xQkPEQDGIVdGQ53EZwAw4oz 6 | wjbct8kpO/oIDLTnFmm9y0iKr6SL1pZNsEPdc8FE3bsfb84VjckunCqjjA7NtVJQ 7 | w4RctvabaEXtcdMMIXgzinOg6kVR+p8ipI4fKwIDAQABAoIBABUCr/WuedCMPqIO 8 | 3IoZm3Hcg8WUUYvLKJTmlLENroiCpaLzcW6FrIqk3ydzGjarERaYHeD1WPLKKpRS 9 | zrMphnX8GhTxDBPXtbCKXDbr+ESOkeEDQ0tnpNJyfd2WvI7PrrYWejT3lyLDAT4U 10 | Jgmqn+pWqfqoXxN3GF7EE1WPaYC8nDNNfubMa7xtZ9DsFpKkoKSyjob+5ko2QvLU 11 | StKLPqZHvAozqaXywngkZcxPe/MQ3BBvCMW3sROcUC4TkkeUUQvZMbWYauwwZPO1 12 | Y2j4VbVpPIVrJw34tecKwhA04lRMfthFg4ofLOYd0Fo9rDRvm7/dJv1rq9KYCHxx 13 | C5GBWykCgYEA5FeqK1NWnBVbxnWAz7MCtITLMQoXsETMT+AAJMFqKRgjWUayZJ+R 14 | pFenNarght5Cg0x60//S/AgsW0E1XYRfmP2ynRW5PlUK/5a516EGNCO8D8ysL+3T 15 | qPoJVbQve2ppEinB6iKEBCXXfnHIEoh3UZcO5L4heAaNtbo3Zl31hmcCgYEA60qd 16 | uNgTWLey+tDFoNmLiIo1A7LNcCEPz9HjRhgFhGyZdgsMu6+4yrij3OCmBuNKinxc 17 | 200pH7pdzElcbxoBPCRBYyp0PP743UloTHOvLbZKMc1zvaMnLuYNc2urncPQm7cY 18 | O0Eu5Oei+igBSnYG7zJ3thi7dgTFf2/8qyAMfp0CgYBdT0mVrNeersezIL9DoNZB 19 | KGbUIlityl9nYJUOcA9OvbNsPwyVO/PLIZqNvsdDtb2eY/lpeoggP8LIg6woC/j4 20 | zutXb9IkG6KD8Xb+G6oqUlP+bNgMOfadfZek8x3YJBLNvkykvfgOrdwSqrJkiGmh 21 | 6MXISb3pi1wLYA5VgZ3cjwKBgBZ7XLKqwr55XvqkNB34a2KygfpGfWa3YgFjdqnd 22 | 5bkPf2JOD/tnAOst1UpnlLWXximRVHYH22QStJ+uasya2X9bn3vQNKcXXcQXHYYg 23 | j554ioJTtTlre3T5ulNK+GzspuQaDJCs28Q3ddUdOXJZ8LOuSM33cwIF1rEkjCn1 24 | t/vpAoGBAMc/FAy+xR0rpSwy9BhCyolA6TsJJfw4T9A3HPT1Y5D8X4wyeRMZEN2x 25 | 0HARojnNYlHUEF2diZvDXCHya/TIT8NCxvAqJ2h6jADjpwh8PsTcqCJ8KosCq5P0 26 | nAGKgcLt4KUS0HimRt4cBfsj/o9QnKqkeds4zPvh3fvTtDRPwno3 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /grpc/demo3/client/certs/test_client.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEA3ucIeuuNCk03p1dqs7t5K4DLl9A+JJmn6JAF1xGJn1ItMc/B 3 | HrI99DnMzRVWBXBbSxElXKgmjGdteAA8RXpzpWBlm0DBuQl8RpipSXxrInvE2tfe 4 | NtkAxmUW6I4b0Wq3DPZQoUAlZnqsWbVMt4BGUOYFRizMhEwEIaKfyznVtuxkOX8l 5 | ZYx1cZl1YMv6G81hNTU0VvPk4xhTJIvcZqFcytLQbP2LgQvH8VKH6ZKbFbdcWksT 6 | vk85F3yOXGs9hFHNZZ0nLOKw8Ycc3VcO1ymEMtA4vTeW8tQX5vnM6Gfi+AQHuKa1 7 | YJeseb7MedWohny68EfxxbMdDuD9CnVGH1dIhwIDAQABAoIBAQCerL5nqli7lZ5h 8 | nKQTkAhAxK2pw48JROy3bBcMM6rRZJFRnUAvltjti1vUPjT/KC2E8Y60N+tBFbti 9 | qmA9YV8DwexyqZV+IFd8dd5EtrIZWRwYYYUJbE7V7E5ze/4nhJ0MOCfLsjstiXQ5 10 | ZAtZsZHcsvHcM7XfE7H5M6gm5mPRk3+q4VoJyLf8z1FDVMdIIK0s1k+QbBL412DL 11 | QQR4r8jkepCpGXNyGsrRCuPjZfZZYO7Ks+C9NgXtPZiK4rXVPL7ZciZko38CfDcu 12 | /tOZzrsnV3jmB2kz8MNVCuEUX5aSXT0AfC78oiRPfnEP9dv7cvwyBZtKNyfi2S2z 13 | 81dgDJ/xAoGBAOYf0YVWOddL5Mou0VmNQIdU0alkGwmo2bf4t5Bhp+EWLzsBVttS 14 | 5ldjOuD8KDDM9HDAa+K2Ruq395kwjsA8+qsRZOPDiOAscLTbgTaG1fhd1f029uJd 15 | +pAmVFV+m1xbOEddhgGvCij3iFbvKdM0W4AGxXE7fhV59U7KuqiuUp9pAoGBAPf3 16 | VT3MMpm6Ite/rCIXT5mVkQkzzthm6jf3y0b9OwPfPtuOSVcj0sM7wbn6UTPLEqDS 17 | ZJ/R5VWuSBDrBvZ40P2Vt2kyyRhGd2KxvKIJAuTO9yW3Dieo3LeIHWQlWhaKKXkL 18 | VIFQwt9q9A3UQEsHFHO9Ioha08FcRk/M6uEzx5pvAoGAcq9GxtGbD93lzpEYii2o 19 | DAnMV0PpaZ35qwLL0Kuqc7WPojNfqvwciU6NqFRiXze1Vn+/BIRcwVsfjPuzGMEL 20 | F7gadJwdGcNsA+Yk9hyqhBWXsJL05Ql69t3zR4xKNvPLD13fi/VE9feuvcyBJp3A 21 | QASf33eLtX5LL5I/BhOiX4kCgYEAqy6BFz9vszaPtTeqIoLLPfDGBn9QjY5GpUqY 22 | d7J72kl1AGcy9EhTyNno/HX1Nvc7LfDw7HAfjU3ajGtkDCUNyfJgguw/bVXAN08S 23 | NR5ZdBH5Bn1f9Tsa3EzIVYl/rs3Eob6ToQ5a6ZfRUfa0R9dkZB4ux8lEJFmKZK7H 24 | e56eblsCgYAZzii5W/InmoO9Vax3v9PzxF6vTchYsZDXso5LhvGl4fu1iF1dklVw 25 | AdLt0lDIV4tBgTqRkZgB7MStW/x8nbjOfMki4AM7R+55Zmk3/rvlsfZFlwcO9cHv 26 | p5fF1aRmhOVy/alXjEMqmCLIuZpz336C5WecwX/1nPnQ7Docg2gD7g== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /recover/README.md: -------------------------------------------------------------------------------- 1 | # [Go学习笔记(二) | 我对recover的一点误解](https://razeen.me/post/daily-go-recover.html) 2 | 3 | 在golang的官方介绍中是这么介绍**Recover**函数的。 4 | 5 | 6 | 7 | ```doc 8 | Recover is a built-in function that regains control of a panicking goroutine. Recover is only useful inside deferred functions. During normal execution, a call to recover will return nil and have no other effect. If the current goroutine is panicking, a call to recover will capture the value given to panic and resume normal execution. 9 | ``` 10 | 11 | 也就是说当一个协程发生panic时,recover函数会捕捉到panic同时恢复正常的顺序。 12 | 13 | 14 | 15 | 像如下这样的代码: 16 | 17 | ``` golang 18 | package main 19 | 20 | import ( 21 | "fmt" 22 | "log" 23 | "time" 24 | ) 25 | 26 | func main() { 27 | 28 | ch := make(chan int, 10) 29 | 30 | for i := 2; i > 0; i-- { 31 | go func(i int) { 32 | defer func() { 33 | err := recover() 34 | if err != nil { 35 | log.Println(err) 36 | } 37 | }() 38 | for val := range ch { 39 | fmt.Println("---->", val, "Go", i) 40 | if val%2 == 1 && i == 1 { 41 | panic("BOOM BOOM") 42 | } 43 | time.Sleep(2 * time.Second) 44 | } 45 | }(i) 46 | } 47 | 48 | var i int 49 | for { 50 | ch <- i 51 | time.Sleep(1 * time.Second) 52 | fmt.Println(i, "<---") 53 | i++ 54 | } 55 | } 56 | ``` 57 | 58 | 我们向通道中写入,两个协成在接收,同时设计一个协程在一定的时候panic。那么在panic后,再recover,那在该协程的管道还能接收么? 59 | 60 | 过去,我一直以为,recover函数会重新恢复,该协程会类似重启一般==。 61 | 62 | ```bash 63 | $ go run recover.go 64 | ----> 0 Go 2 65 | 0 <--- 66 | ----> 1 Go 1 67 | 2018/01/21 21:52:54 BOOM BOOM 68 | 1 <--- 69 | ----> 2 Go 2 70 | 2 <--- 71 | 3 <--- 72 | ----> 3 Go 2 73 | 4 <--- 74 | ----> 4 Go 2 75 | 5 <--- 76 | 6 <--- 77 | ----> 5 Go 2 78 | 7 <--- 79 | 8 <--- 80 | ----> 6 Go 2 81 | ... 82 | ``` 83 | 84 | 但从执行的结果看: 85 | 86 | ​ 只有一个协程在工作,另外一个还是挂了。这时,我才意识到,recover函数并没有恢复原有的协程。只是当该协程panic后会执行defer。而在defer中,recover函数将panic拦截下来了,不会向外面抛出,从而导致其他协程的执行并不受到影响。但,已经panic的协程还是挂了。 -------------------------------------------------------------------------------- /gin/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICTDCCAfKgAwIBAgIQCT2hVW+1RvCyxHrquA7xCjAKBggqhkjOPQQDAjBeMQsw 3 | CQYDVQQGEwJDTjEOMAwGA1UEChMFTXlTU0wxKzApBgNVBAsTIk15U1NMIFRlc3Qg 4 | RUNDIC0gRm9yIHRlc3QgdXNlIG9ubHkxEjAQBgNVBAMTCU15U1NMLmNvbTAeFw0x 5 | ODAzMTYwODAwMDVaFw0xOTAzMTYwODAwMDVaMCExCzAJBgNVBAYTAkNOMRIwEAYD 6 | VQQDEwlsb2NhbGhvc3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASwvN319nUn 7 | J9qc9jq+tWsaYV4gwglMOxomyPGvIRsmP90tO9gegn6mzjIroydJ3dPp0QpaKkPt 8 | gHc1mjWhNhe6o4HOMIHLMA4GA1UdDwEB/wQEAwIHgDAdBgNVHSUEFjAUBggrBgEF 9 | BQcDAQYIKwYBBQUHAwIwHwYDVR0jBBgwFoAUWxGyVxD0fBhTy3tH4eKznRFXFCYw 10 | YwYIKwYBBQUHAQEEVzBVMCEGCCsGAQUFBzABhhVodHRwOi8vb2NzcC5teXNzbC5j 11 | b20wMAYIKwYBBQUHMAKGJGh0dHA6Ly9jYS5teXNzbC5jb20vbXlzc2x0ZXN0ZWNj 12 | LmNydDAUBgNVHREEDTALgglsb2NhbGhvc3QwCgYIKoZIzj0EAwIDSAAwRQIgack7 13 | wDoc/nmD9zIWa8QdbQGKwWONPwDue4cIeDQKhcQCIQDwfasEIe7/vWtjNNZenkJH 14 | KhwjJ6SmdRl6wA6sgYT+AQ== 15 | -----END CERTIFICATE----- 16 | -----BEGIN CERTIFICATE----- 17 | MIIC8TCCAdmgAwIBAgIRALLkLesqekT2roCALo98PuswDQYJKoZIhvcNAQELBQAw 18 | XzELMAkGA1UEBhMCQ04xDjAMBgNVBAoTBU15U1NMMSwwKgYDVQQLEyNNeVNTTCBU 19 | ZXN0IFJvb3QgLSBGb3IgdGVzdCB1c2Ugb25seTESMBAGA1UEAxMJTXlTU0wuY29t 20 | MB4XDTE3MTExNjA1MzUzNVoXDTI3MTExNjA1MzUzNVowXjELMAkGA1UEBhMCQ04x 21 | DjAMBgNVBAoTBU15U1NMMSswKQYDVQQLEyJNeVNTTCBUZXN0IEVDQyAtIEZvciB0 22 | ZXN0IHVzZSBvbmx5MRIwEAYDVQQDEwlNeVNTTC5jb20wWTATBgcqhkjOPQIBBggq 23 | hkjOPQMBBwNCAARVzwT3bIs+d8QIISfVGWXwsWcFbxIFd/bhWj/rp1RDmAmQIVEe 24 | wRa/pQxCe575ClwV5fgRFRxbZ1A2nsUV6nN5o3QwcjAOBgNVHQ8BAf8EBAMCAYYw 25 | DwYDVR0lBAgwBgYEVR0lADAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFJrx 26 | n7klEhKJ7HMYuZe8xfigCn5TMB0GA1UdDgQWBBRbEbJXEPR8GFPLe0fh4rOdEVcU 27 | JjANBgkqhkiG9w0BAQsFAAOCAQEAs07fxWwDfhuAhxl31QjVVV7RBAUzLr/PLUAX 28 | y32r/WJhgq9Uio3b/EIv52EpFnF/uX4D0zppW4VDh18mo9ALeGAfV69MKqP2m0M4 29 | 5khVVLj1Xq+Clgvx+WxdMLgGsvGE2WwjagPVcwpH6TPUpn2mPwvpA3TW1ajHCH1D 30 | bp5ZPLC51FD5EUx1HfRRsO3m6nhtxM0fwTFSjTp2Gai87tY5p6zToiyS2KDKApYE 31 | OXuog0ixQhBv2w3l+kX2MbB1WWhQfpD+O8tWn0A/IxXgjTep3a3uybk8t9j7LyYM 32 | lPMMWy7ciA3RO4+UJ9lftpWiRT4b8hmwWrpnai2JmMVpih5cNA== 33 | -----END CERTIFICATE----- 34 | -------------------------------------------------------------------------------- /swaggo-gin/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/razeencheng/demo-go/swaggo-gin 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/gin-gonic/contrib v0.0.0-20201101042839-6a891bf89f19 7 | github.com/gin-gonic/gin v1.8.1 8 | github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe 9 | github.com/swaggo/gin-swagger v1.5.0 10 | github.com/swaggo/swag v1.8.2 11 | ) 12 | 13 | require ( 14 | github.com/KyleBanks/depth v1.2.1 // indirect 15 | github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff // indirect 16 | github.com/gin-contrib/sse v0.1.0 // indirect 17 | github.com/go-openapi/jsonpointer v0.19.5 // indirect 18 | github.com/go-openapi/jsonreference v0.20.0 // indirect 19 | github.com/go-openapi/spec v0.20.6 // indirect 20 | github.com/go-openapi/swag v0.21.1 // indirect 21 | github.com/go-playground/locales v0.14.0 // indirect 22 | github.com/go-playground/universal-translator v0.18.0 // indirect 23 | github.com/go-playground/validator/v10 v10.11.0 // indirect 24 | github.com/goccy/go-json v0.9.7 // indirect 25 | github.com/gomodule/redigo v2.0.0+incompatible // indirect 26 | github.com/gorilla/context v1.1.1 // indirect 27 | github.com/gorilla/securecookie v1.1.1 // indirect 28 | github.com/gorilla/sessions v1.2.1 // indirect 29 | github.com/josharian/intern v1.0.0 // indirect 30 | github.com/json-iterator/go v1.1.12 // indirect 31 | github.com/leodido/go-urn v1.2.1 // indirect 32 | github.com/mailru/easyjson v0.7.7 // indirect 33 | github.com/mattn/go-isatty v0.0.14 // indirect 34 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 35 | github.com/modern-go/reflect2 v1.0.2 // indirect 36 | github.com/pelletier/go-toml/v2 v2.0.2 // indirect 37 | github.com/ugorji/go/codec v1.2.7 // indirect 38 | golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect 39 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect 40 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect 41 | golang.org/x/text v0.3.8 // indirect 42 | golang.org/x/tools v0.1.12 // indirect 43 | google.golang.org/protobuf v1.28.0 // indirect 44 | gopkg.in/yaml.v2 v2.4.0 // indirect 45 | ) 46 | -------------------------------------------------------------------------------- /grpc/demo3/client/certs/test_client.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIID3DCCAsSgAwIBAgIQP1gd4oJ5QxeYSFtS2qjQnzANBgkqhkiG9w0BAQsFADBe 3 | MQswCQYDVQQGEwJDTjEOMAwGA1UEChMFTXlTU0wxKzApBgNVBAsTIk15U1NMIFRl 4 | c3QgUlNBIC0gRm9yIHRlc3QgdXNlIG9ubHkxEjAQBgNVBAMTCU15U1NMLmNvbTAe 5 | Fw0xODA4MDUwNzQ5NDJaFw0yMzA4MDQwNzQ5NDJaMCgxCzAJBgNVBAYTAkNOMRkw 6 | FwYDVQQDExBjbGllbnQucmF6ZWVuLm1lMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A 7 | MIIBCgKCAQEA3ucIeuuNCk03p1dqs7t5K4DLl9A+JJmn6JAF1xGJn1ItMc/BHrI9 8 | 9DnMzRVWBXBbSxElXKgmjGdteAA8RXpzpWBlm0DBuQl8RpipSXxrInvE2tfeNtkA 9 | xmUW6I4b0Wq3DPZQoUAlZnqsWbVMt4BGUOYFRizMhEwEIaKfyznVtuxkOX8lZYx1 10 | cZl1YMv6G81hNTU0VvPk4xhTJIvcZqFcytLQbP2LgQvH8VKH6ZKbFbdcWksTvk85 11 | F3yOXGs9hFHNZZ0nLOKw8Ycc3VcO1ymEMtA4vTeW8tQX5vnM6Gfi+AQHuKa1YJes 12 | eb7MedWohny68EfxxbMdDuD9CnVGH1dIhwIDAQABo4HLMIHIMA4GA1UdDwEB/wQE 13 | AwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAjAfBgNVHSMEGDAWgBQogSYF0TQaP8Fz 14 | D7uTzxUcPwO/fzBjBggrBgEFBQcBAQRXMFUwIQYIKwYBBQUHMAGGFWh0dHA6Ly9v 15 | Y3NwLm15c3NsLmNvbTAwBggrBgEFBQcwAoYkaHR0cDovL2NhLm15c3NsLmNvbS9t 16 | eXNzbHRlc3Ryc2EuY3J0MBsGA1UdEQQUMBKCEGNsaWVudC5yYXplZW4ubWUwDQYJ 17 | KoZIhvcNAQELBQADggEBAA8jhr1/Lf5fmH4gCsRNSQ3ur19UM8F0Q0iXCeBIJUg0 18 | lL57UIR2phIN5PpsaLRlIfsR0EicQaEFJBShskqFCH5AWhxFo2+obBTu9KPSB10o 19 | uey8nOHqqV0yrhqZJr9NIU3QqGtNRn1H8JKHYVGSgThLBSq/cFwwhU4A8lxN1Pxt 20 | I3/RtwY8dPY6TuaRGNmyHOftJSPseASx+79IqrmCmVM2prNBJCdmkH/bk30ujroi 21 | tGGkBlf3FYQhFX7vWLQaXu7ZjOt2sh5RruY63bbS/uGAxzS4QYGjpgaFlZA98+LQ 22 | X48DNJpvoiXeDFZfLIaorTRoNDSYUj3G6OMbcRobOnM= 23 | -----END CERTIFICATE----- 24 | -----BEGIN CERTIFICATE----- 25 | MIIDuzCCAqOgAwIBAgIQSEIWDPfWTDKZcWNyL2O+fjANBgkqhkiG9w0BAQsFADBf 26 | MQswCQYDVQQGEwJDTjEOMAwGA1UEChMFTXlTU0wxLDAqBgNVBAsTI015U1NMIFRl 27 | c3QgUm9vdCAtIEZvciB0ZXN0IHVzZSBvbmx5MRIwEAYDVQQDEwlNeVNTTC5jb20w 28 | HhcNMTcxMTE2MDUzNTM1WhcNMjcxMTE2MDUzNTM1WjBeMQswCQYDVQQGEwJDTjEO 29 | MAwGA1UEChMFTXlTU0wxKzApBgNVBAsTIk15U1NMIFRlc3QgUlNBIC0gRm9yIHRl 30 | c3QgdXNlIG9ubHkxEjAQBgNVBAMTCU15U1NMLmNvbTCCASIwDQYJKoZIhvcNAQEB 31 | BQADggEPADCCAQoCggEBAMBOtZk0uzdG4dcIIdcAdSSYDbua0Bdd6N6s4hZaCOup 32 | q7G7lwXkCyViTYAFa3wZ0BMQ4Bl9Q4j82R5IaoqG7WRIklwYnQh4gZ14uRde6Mr8 33 | yzvPRbAXKVoVh4NPqpE6jWMTP38mh94bKc+ITAE5QBRhCTQ0ah2Hq846ZiDAj6sY 34 | hMJuhUWegVGd0vh0rvtzvYNx7NGyxzoj6MxkDiYfFiuBhF2R9Tmq2UW9KCZkEBVL 35 | Q/YKQuvZZKFqR7WUU8GpCwzUm1FZbKtaCyRRvzLa5otghU2teKS5SKVI+Tpxvasp 36 | fu4eXBvveMgyWwDpKlzLCLgvoC9YNpbmdiVxNNkjwNsCAwEAAaN0MHIwDgYDVR0P 37 | AQH/BAQDAgGGMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAfBgNV 38 | HSMEGDAWgBSa8Z+5JRISiexzGLmXvMX4oAp+UzAdBgNVHQ4EFgQUKIEmBdE0Gj/B 39 | cw+7k88VHD8Dv38wDQYJKoZIhvcNAQELBQADggEBAEl01ufit9rUeL5kZ31ox2vq 40 | 648azH/r/GR1S+mXci0Mg6RrDdLzUO7VSf0JULJf98oEPr9fpIZuRTyWcxiP4yh0 41 | wVd35OIQBTToLrMOWYWuApU4/YLKvg4A86h577kuYeSsWyf5kk0ngXsL1AFMqjOk 42 | Tc7p8PuW68S5/88Pe+Bq3sAaG3U5rousiTIpoN/osq+GyXisgv5jd2M4YBtl/NlD 43 | ppZs5LAOjct+Aaofhc5rNysonKjkd44K2cgBkbpOMj0dbVNKyL2/2I0zyY1FU2Mk 44 | URUHyMW5Qd5Q9g6Y4sDOIm6It9TF7EjpwMs42R30agcRYzuUsN72ZFBYFJwnBX8= 45 | -----END CERTIFICATE----- -------------------------------------------------------------------------------- /grpc/demo3/server/certs/test_server.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIID5zCCAs+gAwIBAgIRANDUuMTieUHqoJRd+L1RWfQwDQYJKoZIhvcNAQELBQAw 3 | XjELMAkGA1UEBhMCQ04xDjAMBgNVBAoTBU15U1NMMSswKQYDVQQLEyJNeVNTTCBU 4 | ZXN0IFJTQSAtIEZvciB0ZXN0IHVzZSBvbmx5MRIwEAYDVQQDEwlNeVNTTC5jb20w 5 | HhcNMTgwODA1MDc0OTAzWhcNMjMwODA0MDc0OTAzWjAoMQswCQYDVQQGEwJDTjEZ 6 | MBcGA1UEAxMQc2VydmVyLnJhemVlbi5tZTCCASIwDQYJKoZIhvcNAQEBBQADggEP 7 | ADCCAQoCggEBANHfBzuZILLI6bt2waWjlilHqZ3pQdQXvOP9uJT8qtSKXXj11MQm 8 | W4y3dPNoNIKHEoVBh8mABBAydxp4N3uogYWo/dEQ21GAUDtPWpygkXs2woeYORIu 9 | IO9j8D1cSMpv4uIehh+s0RMV47cDAkt8zb4o02AduL72gxi3UAM6h7ZEQcxorLxh 10 | Rqfg6vWA1J7w7WVHRHHhFGxvvKFulSOS60QjUuMUJDxEAxiFXRkOdxGcAMOKM8I2 11 | 3LfJKTv6CAy05xZpvctIiq+ki9aWTbBD3XPBRN27H2/OFY3JLpwqo4wOzbVSUMOE 12 | XLb2m2hF7XHTDCF4M4pzoOpFUfqfIqSOHysCAwEAAaOB1TCB0jAOBgNVHQ8BAf8E 13 | BAMCB4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB8GA1UdIwQYMBaA 14 | FCiBJgXRNBo/wXMPu5PPFRw/A79/MGMGCCsGAQUFBwEBBFcwVTAhBggrBgEFBQcw 15 | AYYVaHR0cDovL29jc3AubXlzc2wuY29tMDAGCCsGAQUFBzAChiRodHRwOi8vY2Eu 16 | bXlzc2wuY29tL215c3NsdGVzdHJzYS5jcnQwGwYDVR0RBBQwEoIQc2VydmVyLnJh 17 | emVlbi5tZTANBgkqhkiG9w0BAQsFAAOCAQEAoKWnuLCLtrgcWEwh8jIZXpbrmUWj 18 | VmXVyOJw/TuF3UiZlAwyb8Cb0nArlVdMkdwhWUCtl6ZktMTTeCMeylJYY04obvEZ 19 | V4mIFCcTYFHJsW8m7SK2QVS2ssFIec/+xaVptzQB5+DUuD/Ldn2g1QslzRX4DFa3 20 | 30b3HKLLWQBWqa+lfMaGrOiTbWqEPt4cwWsKhFEQ9j3IFoMvEn+l/CozVEPPbsN2 21 | lJRlA7V1Xf/J1khjWHFDNUSivKuTw/1//jbKSFQ0bevqBlUEkqdwVzenRG0VU9cy 22 | j74s89EiEaAMm/MFTuSJrZrrkZLJfK7TDThf2kPqykVtWExJ+GpaBpQZOg== 23 | -----END CERTIFICATE----- 24 | -----BEGIN CERTIFICATE----- 25 | MIIDuzCCAqOgAwIBAgIQSEIWDPfWTDKZcWNyL2O+fjANBgkqhkiG9w0BAQsFADBf 26 | MQswCQYDVQQGEwJDTjEOMAwGA1UEChMFTXlTU0wxLDAqBgNVBAsTI015U1NMIFRl 27 | c3QgUm9vdCAtIEZvciB0ZXN0IHVzZSBvbmx5MRIwEAYDVQQDEwlNeVNTTC5jb20w 28 | HhcNMTcxMTE2MDUzNTM1WhcNMjcxMTE2MDUzNTM1WjBeMQswCQYDVQQGEwJDTjEO 29 | MAwGA1UEChMFTXlTU0wxKzApBgNVBAsTIk15U1NMIFRlc3QgUlNBIC0gRm9yIHRl 30 | c3QgdXNlIG9ubHkxEjAQBgNVBAMTCU15U1NMLmNvbTCCASIwDQYJKoZIhvcNAQEB 31 | BQADggEPADCCAQoCggEBAMBOtZk0uzdG4dcIIdcAdSSYDbua0Bdd6N6s4hZaCOup 32 | q7G7lwXkCyViTYAFa3wZ0BMQ4Bl9Q4j82R5IaoqG7WRIklwYnQh4gZ14uRde6Mr8 33 | yzvPRbAXKVoVh4NPqpE6jWMTP38mh94bKc+ITAE5QBRhCTQ0ah2Hq846ZiDAj6sY 34 | hMJuhUWegVGd0vh0rvtzvYNx7NGyxzoj6MxkDiYfFiuBhF2R9Tmq2UW9KCZkEBVL 35 | Q/YKQuvZZKFqR7WUU8GpCwzUm1FZbKtaCyRRvzLa5otghU2teKS5SKVI+Tpxvasp 36 | fu4eXBvveMgyWwDpKlzLCLgvoC9YNpbmdiVxNNkjwNsCAwEAAaN0MHIwDgYDVR0P 37 | AQH/BAQDAgGGMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAfBgNV 38 | HSMEGDAWgBSa8Z+5JRISiexzGLmXvMX4oAp+UzAdBgNVHQ4EFgQUKIEmBdE0Gj/B 39 | cw+7k88VHD8Dv38wDQYJKoZIhvcNAQELBQADggEBAEl01ufit9rUeL5kZ31ox2vq 40 | 648azH/r/GR1S+mXci0Mg6RrDdLzUO7VSf0JULJf98oEPr9fpIZuRTyWcxiP4yh0 41 | wVd35OIQBTToLrMOWYWuApU4/YLKvg4A86h577kuYeSsWyf5kk0ngXsL1AFMqjOk 42 | Tc7p8PuW68S5/88Pe+Bq3sAaG3U5rousiTIpoN/osq+GyXisgv5jd2M4YBtl/NlD 43 | ppZs5LAOjct+Aaofhc5rNysonKjkd44K2cgBkbpOMj0dbVNKyL2/2I0zyY1FU2Mk 44 | URUHyMW5Qd5Q9g6Y4sDOIm6It9TF7EjpwMs42R30agcRYzuUsN72ZFBYFJwnBX8= 45 | -----END CERTIFICATE----- -------------------------------------------------------------------------------- /grpc/demo3/client/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "crypto/x509" 7 | "fmt" 8 | "io" 9 | "io/ioutil" 10 | "log" 11 | 12 | "google.golang.org/grpc" 13 | "google.golang.org/grpc/credentials" 14 | 15 | pb "github.com/razeencheng/demo-go/grpc/demo3/helloworld" 16 | ) 17 | 18 | func main() { 19 | cert, err := tls.LoadX509KeyPair("certs/test_client.pem", "certs/test_client.key") 20 | if err != nil { 21 | panic(err) 22 | } 23 | 24 | // 将根证书加入证书池 25 | certPool := x509.NewCertPool() 26 | bs, err := ioutil.ReadFile("certs/root.pem") 27 | if err != nil { 28 | panic(err) 29 | } 30 | 31 | if !certPool.AppendCertsFromPEM(bs) { 32 | panic("cc") 33 | } 34 | 35 | // 新建凭证 36 | transportCreds := credentials.NewTLS(&tls.Config{ 37 | ServerName: "server.razeen.me", 38 | Certificates: []tls.Certificate{cert}, 39 | RootCAs: certPool, 40 | }) 41 | 42 | dialOpt := grpc.WithTransportCredentials(transportCreds) 43 | 44 | conn, err := grpc.Dial("localhost:8080", dialOpt) 45 | if err != nil { 46 | log.Fatalf("Dial failed:%v", err) 47 | } 48 | defer conn.Close() 49 | 50 | client := pb.NewHelloWorldServiceClient(conn) 51 | resp1, err := client.SayHelloWorld(context.Background(), &pb.HelloWorldRequest{ 52 | Greeting: "Hello Server 1 !!", 53 | Infos: map[string]string{"hello": "world"}, 54 | }) 55 | if err != nil { 56 | log.Fatal(err) 57 | } 58 | log.Printf("Resp1:%+v", resp1) 59 | 60 | resp2, err := client.SayHelloWorld(context.Background(), &pb.HelloWorldRequest{ 61 | Greeting: "Hello Server 2 !!", 62 | }) 63 | if err != nil { 64 | log.Fatalf("%v", err) 65 | } 66 | log.Printf("Resp2:%+v", resp2) 67 | 68 | // 服务器端流式 RPC; 69 | recvListHello, err := client.ListHello(context.Background(), &pb.HelloWorldRequest{Greeting: "Hello Server List Hello"}) 70 | if err != nil { 71 | log.Fatalf("ListHello err: %v", err) 72 | } 73 | 74 | for { 75 | resp, err := recvListHello.Recv() 76 | if err == io.EOF { 77 | break 78 | } 79 | if err != nil { 80 | log.Fatal(err) 81 | } 82 | 83 | log.Printf("ListHello Server Resp: %v", resp.Reply) 84 | } 85 | 86 | // 客户端流式 RPC; 87 | sayMoreClient, err := client.SayMoreHello(context.Background()) 88 | if err != nil { 89 | log.Fatal(err) 90 | } 91 | for i := 0; i < 3; i++ { 92 | sayMoreClient.Send(&pb.HelloWorldRequest{Greeting: fmt.Sprintf("SayMoreHello Hello Server %d", i)}) 93 | } 94 | 95 | sayMoreResp, err := sayMoreClient.CloseAndRecv() 96 | if err != nil { 97 | log.Fatal(err) 98 | } 99 | log.Printf("SayMoreHello Server Resp: %v", sayMoreResp.Reply) 100 | 101 | // 双向流式 RPC; 102 | sayHelloChat, err := client.SayHelloChat(context.Background()) 103 | if err != nil { 104 | log.Fatal(err) 105 | } 106 | 107 | go func() { 108 | for i := 0; i < 3; i++ { 109 | sayHelloChat.Send(&pb.HelloWorldRequest{Greeting: fmt.Sprintf("SayHelloChat Hello Server %d", i)}) 110 | } 111 | }() 112 | 113 | for { 114 | resp, err := sayHelloChat.Recv() 115 | if err == io.EOF { 116 | break 117 | } 118 | if err != nil { 119 | log.Fatal(err) 120 | } 121 | 122 | log.Printf("SayHelloChat Server Say: %v", resp.Greeting) 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /gin/README.md: -------------------------------------------------------------------------------- 1 | # [gin文件上传与下载]() 2 | 3 | Gin是用Go编写的web框架。性能还不错,而且使用比较简单,还支持RESTful API。 4 | 5 | 日常的使用中我们可能要处理一些文件的上传与下载,我这里简单总结一下。 6 | 7 | 8 | ### 单文件上传 9 | 10 | 我们使用`multipart/form-data`格式上传文件,利用`c.Request.FormFile`解析文件。 11 | 12 | ``` golang 13 | // HandleUploadFile 上传单个文件 14 | func HandleUploadFile(c *gin.Context) { 15 | file, header, err := c.Request.FormFile("file") 16 | if err != nil { 17 | c.JSON(http.StatusBadRequest, gin.H{"msg": "文件上传失败"}) 18 | return 19 | } 20 | 21 | content, err := ioutil.ReadAll(file) 22 | if err != nil { 23 | c.JSON(http.StatusBadRequest, gin.H{"msg": "文件读取失败"}) 24 | return 25 | } 26 | 27 | fmt.Println(header.Filename) 28 | fmt.Println(string(content)) 29 | c.JSON(http.StatusOK, gin.H{"msg": "上传成功"}) 30 | } 31 | ``` 32 | 33 | 我们上传文件可以看到。 34 | 35 | ![jietu20180906-002227](https://st.razeen.me/bcj/201809/jietu20180906-002227.png) 36 | 37 | 我们已经看到文件上传成功,已经文件名字与内容。 38 | 39 | 40 | ### 多文件上传 41 | 42 | 多文件的上传利用`c.Request.MultipartForm`解析。 43 | 44 | ``` golang 45 | // HandleUploadMutiFile 上传多个文件 46 | func HandleUploadMutiFile(c *gin.Context) { 47 | // 限制放入内存的文件大小 48 | err := c.Request.ParseMultipartForm(4 << 20) 49 | if err != nil { 50 | c.JSON(http.StatusBadRequest, gin.H{"msg": "文件太大"}) 51 | return 52 | } 53 | formdata := c.Request.MultipartForm 54 | files := formdata.File["file"] 55 | 56 | for _, v := range files { 57 | file, err := v.Open() 58 | if err != nil { 59 | c.JSON(http.StatusBadRequest, gin.H{"msg": "文件读取失败"}) 60 | return 61 | } 62 | defer file.Close() 63 | 64 | content, err := ioutil.ReadAll(file) 65 | if err != nil { 66 | c.JSON(http.StatusBadRequest, gin.H{"msg": "文件读取失败"}) 67 | return 68 | } 69 | 70 | fmt.Println(v.Filename) 71 | fmt.Println(string(content)) 72 | } 73 | 74 | c.JSON(http.StatusOK, gin.H{"msg": "上传成功"}) 75 | } 76 | ``` 77 | 78 | 多个文件,遍历文件内容即可读取。 79 | 80 | ~~利用`c.Request.ParseMultipartForm()`可设置上传文件的大小,这里限制了4MB。~~ 81 | `c.Request.ParseMultipartForm()`并不能限制上传文件的大小,只是限制了上传的文件读取到内存部分的大小,如果超过了就存入了系统的临时文件中。 82 | 如果需要限制文件大小,需要使用`github.com/gin-contrib/size`中间件,如demo中使用`r.Use(limits.RequestSizeLimiter(4 << 20))`限制最大4Mb。 83 | 84 | 我们看到 85 | 86 | ![jietu20180906-002143](https://st.razeen.me/bcj/201809/jietu20180906-002143.png) 87 | 88 | 两个文件已经上传成功。 89 | 90 | 91 | ### 文件下载 92 | 93 | 文件的下载主要是注意设置文件名,文件类型等。 94 | 95 | ``` golang 96 | // HandleDownloadFile 下载文件 97 | func HandleDownloadFile(c *gin.Context) { 98 | content := c.Query("content") 99 | 100 | content = "hello world, 我是一个文件," + content 101 | 102 | c.Writer.WriteHeader(http.StatusOK) 103 | c.Header("Content-Disposition", "attachment; filename=hello.txt") 104 | c.Header("Content-Type", "application/text/plain") 105 | c.Header("Accept-Length", fmt.Sprintf("%d", len(content))) 106 | c.Writer.Write([]byte(content)) 107 | } 108 | ``` 109 | 110 | 通过 111 | - `Content-Disposition`设置文件名字; 112 | - `Content-Type`设置文件类型,可以到[这里](http://www.runoob.com/http/http-content-type.html)查阅; 113 | - `Accept-Length`这个设置文件长度; 114 | - `c.Writer.Write`写出文件。 115 | 116 | 成功下载可以看到: 117 | 118 | ![jietu20180906-004014](https://st.razeen.me/bcj/201809/jietu20180906-004014.png) 119 | 120 | 121 | * 完整demo[在这里](https://github.com/razeencheng/demo-go/blob/master/gin/gin.go) 122 | -------------------------------------------------------------------------------- /timer/README.md: -------------------------------------------------------------------------------- 1 | # [Go学习笔记(九) 计时器的生命周期[译]](https://razeencheng.com/post/go-timers-life-cycle.html) 2 | 3 | 4 | 5 | ![Illustration created for “A Journey With Go”, made from the original Go Gopher, created by Renee French.](https://st.razeen.cn/image/go-timer.png) 6 | 7 | 8 | 9 | *全文基于GO 1.14* 10 | 11 | 12 | 13 | 计时器在定时执行一些任务时很有用。Go内部依靠调度器来管理创建的计时器。而Go的调度程序是协作式的调度方式,这会让整个调度看起来比较复杂,因为goroutune必须自己停止(依赖channel阻塞或system call), 或者由调度器自己在某个调度点暂停。 14 | 15 | 16 | 17 | 18 | 19 | *有关抢占的更多信息,建议您阅读作者的文章[Go: Goroutine and Preemption](https://medium.com/a-journey-with-go/go-goroutine-and-preemption-d6bc2aa2f4b7)*. 20 | 21 | 22 | 23 | ### 生命周期 24 | 25 | 下面是一段简单示例代码: 26 | 27 | ```go 28 | func main(){ 29 | sigs := make(chan os.Signal,1) 30 | signal.Notify(sigs,syscall.SIGINT,syscall.SIGTERM) 31 | 32 | time.AfterFunc(time.Second, func() { 33 | println("done") 34 | }) 35 | 36 | <- sigs 37 | } 38 | ``` 39 | 40 | 计时器创建后,他会保存到一个链接到当前P的计时器内部列表上,下图就是这段代码的表示形式: 41 | 42 | ![](https://st.razeen.cn/image/timer-on-p.png) 43 | 44 | *有关G,M,P模型的更多信息,建议您阅读作者的文章[Go: Goroutine, OS Thread and CPU Management](https://medium.com/a-journey-with-go/go-goroutine-os-thread-and-cpu-management-2f5a5eaf518a)* 45 | 46 | 47 | 48 | 49 | 50 | 从图中可以看到,一旦创建了计时器,它就会注册一个内部回调,该内部回调将使用`go`回调用户函数,并将其转换为goroutine。 51 | 52 | 53 | 54 | 然后,将通过调度程序管理计时器。在每一轮调度中,它都会检查计时器是否准备好运行,如果准备就绪,则准备运行。实际上,由于Go调度程序本身不会运行任何代码,因此运行计时器的回调会将其goroutine排队到本地队列中。然后,当调度程序在队列中将其接收时,goroutine将运行。如选图所示: 55 | 56 | ![](https://st.razeen.cn/image/timer-on-p2.png) 57 | 58 | 根据本地队列的大小,计时器运行可能会稍有延迟。不过,由于Go 1.14中的异步抢占,goroutines在运行时间10ms后会被抢占,降低了延迟的可能性。 59 | 60 | 61 | 62 | ### 延迟? 63 | 64 | 为了了解计时器可能存在的延迟,我们创造一个场景:从同一goroutine创建大量计时器。 65 | 66 | 由于计时器都链接到当前`P`,因此繁忙的`P`无法及时运行其链接的计时器。代码如下: 67 | 68 | ``` go 69 | func main(){ 70 | 71 | trace.Start(os.Stderr) 72 | defer trace.Stop() 73 | 74 | sigs := make(chan os.Signal,1) 75 | signal.Notify(sigs,syscall.SIGINT,syscall.SIGTERM) 76 | 77 | //time.AfterFunc(time.Second, func() { 78 | // println("done") 79 | //}) 80 | 81 | 82 | var num int64 83 | 84 | for i:=0; i< 1e3 ; i++ { 85 | time.AfterFunc(time.Second, func() { 86 | atomic.AddInt64(&num,1) 87 | }) 88 | } 89 | 90 | t:= 0 91 | for i:=0;i<1e10; i++ { 92 | t ++ 93 | } 94 | _ = t 95 | 96 | <- sigs 97 | 98 | println(num,"timers created,",t,"iterations done") 99 | } 100 | ``` 101 | 102 | 通过go tool trace, 我们可以看到goroutine正在占用处理器: 103 | 104 | ![](https://st.razeen.cn/image/timer-on-p3.png) 105 | 106 | 由于异步抢占的原因,代表正在运行的goroutine图形被分成了大量较小的块。 107 | 108 | 109 | 110 | 在这些块中,一个空间看起来比其他空间大。让我们放大一下: 111 | 112 | ![](https://st.razeen.cn/image/timer-on-p4.png) 113 | 114 | 115 | 116 | 在该计时器需要运行时,就会发生改情况。此时,当前goroutine已被Go调度程序抢占和取代。调度程序将计时器转换为可运行的goroutine,如图所示。 117 | 118 | 但是,当前线程的Go调度程序并不是唯一运行计时器的调度程序。Go实施了一种计时器窃取策略,以确保在当前P繁忙时可以由另一个P运行计时器。由于异步抢占,它不太可能发生,但是在我们的示例中,由于使用了大量的计时器,它发生了。如下图所示: 119 | 120 | ![](https://st.razeen.cn/image/timer-on-p5.png) 121 | 122 | 123 | 124 | 如果我们不考虑计时器窃取,将发生以下情况: 125 | 126 | ![](https://st.razeen.cn/image/timer-on-p6.png) 127 | 128 | 129 | 130 | 持有计时器的所有goroutine都会添加到本地队列中。然后,由于 `P`之间的窃取,将准确的调度计时器。 131 | 132 | 所以,由于异步抢占和工作窃取,延迟几乎不可能发生。 133 | 134 | 135 | 136 | > 原文 [Go: Timers’ Life Cycle](https://medium.com/a-journey-with-go/go-timers-life-cycle-403f3580093a) -------------------------------------------------------------------------------- /oauth2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "html/template" 7 | "log" 8 | "net/http" 9 | "os" 10 | ) 11 | 12 | // 你在注册时得到的 13 | const ( 14 | clientID = "你的客户端ID" 15 | clientSecret = "你的客户端密钥" 16 | ) 17 | 18 | var httpClient = http.Client{} 19 | 20 | type OAuthAccessResponse struct { 21 | AccessToken string `json:"access_token"` 22 | } 23 | 24 | type GithubUserInfo struct { 25 | Name string `json:"name"` 26 | } 27 | 28 | func main() { 29 | fs := http.FileServer(http.Dir("public")) 30 | http.Handle("/", fs) 31 | http.HandleFunc("/oauth/redirect", HandleOAuthRedirect) 32 | 33 | http.ListenAndServe(":8080", nil) 34 | } 35 | 36 | // HandleOAuthRedirect doc 37 | func HandleOAuthRedirect(w http.ResponseWriter, r *http.Request) { 38 | // 首先,我们从URI中解析出code参数 39 | // 如: http://localhost:8080/oauth/redirect?code=260f17a7308f2c566725 40 | err := r.ParseForm() 41 | if err != nil { 42 | log.Printf("could not parse query: %v", err) 43 | w.WriteHeader(http.StatusBadRequest) 44 | } 45 | code := r.FormValue("code") 46 | 47 | // 通过github返回的code码,再向github获取access token,只有使用access token才能获取用户资源 48 | accessToken, err := getAccessTokenByCode(code) 49 | if err != nil { 50 | w.WriteHeader(http.StatusInternalServerError) 51 | return 52 | } 53 | 54 | // 通过access token获取用户资源,这里的资源为用户的名称 55 | username, err := getUsername(accessToken) 56 | if err != nil { 57 | w.WriteHeader(http.StatusInternalServerError) 58 | return 59 | } 60 | 61 | // 最后获取到用户信息后,我们重定向到欢迎页面,也就是表示用户登录成功 62 | w.Header().Set("Location", "/welcome.html?username="+username) 63 | w.WriteHeader(http.StatusFound) 64 | } 65 | 66 | func getAccessTokenByCode(code string) (string, error) { 67 | reqURL := fmt.Sprintf("https://github.com/login/oauth/access_token?client_id=%s&client_secret=%s&code=%s", 68 | clientID, clientSecret, code) 69 | req, err := http.NewRequest(http.MethodPost, reqURL, nil) 70 | if err != nil { 71 | log.Printf("could not create HTTP request: %v", err) 72 | return "", err 73 | } 74 | 75 | // 设置我们期待返回的格式为json 76 | req.Header.Set("accept", "application/json") 77 | 78 | // 发送http请求 79 | res, err := httpClient.Do(req) 80 | if err != nil { 81 | log.Printf("could not send HTTP request: %v", err) 82 | return "", err 83 | } 84 | defer res.Body.Close() 85 | 86 | // 解析 87 | var t OAuthAccessResponse 88 | if err := json.NewDecoder(res.Body).Decode(&t); err != nil { 89 | log.Printf("could not parse JSON response: %v", err) 90 | return "", err 91 | } 92 | 93 | return t.AccessToken, nil 94 | } 95 | 96 | func getUsername(accessToken string) (string, error) { 97 | req, err := http.NewRequest(http.MethodGet, "https://api.github.com/user", nil) 98 | if err != nil { 99 | log.Printf("could not create HTTP request: %v", err) 100 | return "", err 101 | } 102 | 103 | // 设置我们期待返回的格式为json 104 | req.Header.Set("accept", "application/json") 105 | req.Header.Set("Authorization", "Bearer "+accessToken) 106 | 107 | // 发送http请求 108 | res, err := httpClient.Do(req) 109 | if err != nil { 110 | log.Printf("could not send HTTP request: %v", err) 111 | return "", err 112 | } 113 | defer res.Body.Close() 114 | 115 | // 解析 116 | var u GithubUserInfo 117 | if err := json.NewDecoder(res.Body).Decode(&u); err != nil { 118 | log.Printf("could not parse JSON response: %v", err) 119 | return "", err 120 | } 121 | 122 | return u.Name, nil 123 | } 124 | 125 | func init() { 126 | tmpl, err := template.ParseFiles("public/index.tmpl") 127 | if err != nil { 128 | log.Fatalf("parse html templ err: %v", err) 129 | } 130 | 131 | file, err := os.OpenFile("public/index.html", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0660) 132 | if err != nil { 133 | log.Fatalf("create index.html err: %v", err) 134 | } 135 | defer file.Close() 136 | 137 | err = tmpl.Execute(file, map[string]interface{}{ 138 | "ClientId": clientID, 139 | }) 140 | if err != nil { 141 | log.Fatalf("exec tmpl err: %v", err) 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 学习golang的一些Demo 2 | 3 | *更多内容请看博客 👉 https://razeencheng.com* 4 | 5 | ### Demo 一览 6 | 7 | 1. [Go学习笔记(二) | 我对recover的一点误解](https://razeen.me/post/daily-go-recover.html) 👉 [recover](https://github.com/razeencheng/demo-go/tree/master/recover) 8 | 2. [Go学习笔记(三) | 怎么写Go基准测试(性能测试)](https://razeen.me/post/go-how-to-write-benchmark.html) 👉 [benchmark](https://github.com/razeencheng/demo-go/tree/master/benchmark) 9 | 3. [gRPC在Go中的使用(一)Protocol Buffers语法与相关使用](https://razeen.me/post/how-to-use-grpc-in-golang-01.html) 👉 [grpc/demo1](https://github.com/razeencheng/demo-go/tree/master/grpc/demo1) 10 | 4. [gRPC在Go中的使用(二)gRPC实现简单通讯](https://razeen.me/post/how-to-use-grpc-in-golang-02.html) 👉 [grpc/demo2](https://github.com/razeencheng/demo-go/tree/master/grpc/demo2) 11 | 5. [gRPC在Go中的使用(三)gRPC实现TLS加密通信与流模式](https://razeen.me/post/how-to-use-grpc-in-golang-03.html) 👉 [grpc/demo3](https://github.com/razeencheng/demo-go/tree/master/grpc/demo3) 12 | 13 | 6. json tag 使用 👉 [json/tag](https://github.com/razeencheng/demo-go/tree/master/json/tag) 14 | 15 | 7. [gin文件上传与下载](https://newb.razeen.me/gin-file-download-and-upload/) 👉 [gin/gin.go](https://github.com/razeencheng/demo-go/blob/master/gin/gin.go) 16 | 17 | 8. [Go学习笔记(六) 使用swaggo自动生成Restful API文档](https://razeen.me/post/go-swagger.html) 👉 [swaggo-gin](https://github.com/razeencheng/demo-go/tree/master/swaggo-gin) 18 | 19 | 9. [Go学习笔记(七) | 理解并实现 OAuth 2.0](https://razeen.me/post/oauth2-protocol-details.html) 👉 [oauth2](https://github.com/razeencheng/demo-go/tree/master/oauth2) 20 | 21 | 10. [如何用Go调用Windows API](https://razeencheng.com/post/breaking-all-the-rules-using-go-to-call-windows-api.html) 👉 [windows_api](https://github.com/razeencheng/demo-go/tree/master/windows_api) 22 | 23 | 11. [Go学习笔记(八) | 使用 os/exec 执行命令](https://razeencheng.com/post/simple-use-go-exec-command.html) 👉 [os-exec](https://github.com/razeencheng/demo-go/tree/master/os-exec) 24 | 25 | 12. [Golang中的RESTful API最佳实践](https://razeencheng.com/post/golang-and-restful-api.html) 👉[restful-api](https://github.com/razeencheng/demo-go/tree/master/restful-api) 26 | 27 | 13. [Go学习笔记(九) 计时器的生命周期[译]](https://razeencheng.com/post/go-timers-life-cycle.html) 👉 [timer](https://github.com/razeencheng/demo-go/tree/master/timer) 28 | 29 | 14. [Go学习笔记(十)老项目迁移 go module 大型灾难记录](https://razeencheng.com/post/accidents-of-migrating-to-go-modules.html) 👉 [gomod](https://github.com/razeencheng/demo-go/tree/master/gomod) 30 | 31 | ### 目录 32 | 33 | ```` 34 | . 35 | ├── README.md 36 | ├── benchmark 37 | │   ├── main.go 38 | │   └── main_test.go 39 | ├── gin 40 | │   ├── gin.go 41 | ├── grpc 42 | │   ├── demo1 43 | │   │   └── helloworld 44 | │   │   ├── hello_world.pb.go 45 | │   │   └── hello_world.proto 46 | │   ├── demo2 47 | │   │   ├── client 48 | │   │   │   └── client.go 49 | │   │   ├── helloworld 50 | │   │   │   ├── hello_world.pb.go 51 | │   │   │   └── hello_world.proto 52 | │   │   └── server 53 | │   │   └── server.go 54 | │   └── demo3 55 | │   ├── client 56 | │   │   ├── certs 57 | │   │   └── client.go 58 | │   ├── helloworld 59 | │   │   ├── hello_world.pb.go 60 | │   │   └── hello_world.proto 61 | │   └── server 62 | │   ├── certs 63 | │   └── server.go 64 | ├── json 65 | │   └── tag 66 | │   └── tag.go 67 | │── recover 68 | │ └── recover.go 69 | ├── oauth2 70 | │   ├── README.md 71 | │   ├── main.go 72 | │   └── public 73 | │   ├── index.html 74 | │   └── welcome.html 75 | ├── os-exec 76 | │   ├── README.md 77 | │   ├── main.go 78 | │   └── testcmd 79 | │   ├── main.go 80 | │   └── testcmd 81 | ├── recover 82 | │   ├── README.md 83 | │   └── recover.go 84 | ├── swaggo-gin 85 | │   ├── Makefile 86 | │   ├── README.md 87 | │   ├── doc.go 88 | │   ├── docs 89 | │   │   ├── docs.go 90 | │   │   └── swagger 91 | │   │   ├── swagger.json 92 | │   │   └── swagger.yaml 93 | │   ├── handle.go 94 | │   └── main.go 95 | │   96 | └── windows_api 97 | ├── README.md 98 | └── main_windows.go 99 | 100 | ```` 101 | -------------------------------------------------------------------------------- /grpc/demo3/server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "crypto/x509" 7 | "io" 8 | "io/ioutil" 9 | "log" 10 | "net" 11 | "time" 12 | 13 | "google.golang.org/grpc/credentials" 14 | 15 | "github.com/golang/protobuf/ptypes" 16 | "github.com/golang/protobuf/ptypes/any" 17 | "google.golang.org/grpc" 18 | 19 | pb "github.com/razeencheng/demo-go/grpc/demo3/helloworld" 20 | ) 21 | 22 | type SayHelloServer struct{} 23 | 24 | // 服务器端流式 RPC, 接收一次客户端请求,返回一个流 25 | func (s *SayHelloServer) ListHello(in *pb.HelloWorldRequest, stream pb.HelloWorldService_ListHelloServer) error { 26 | log.Printf("Client Say: %v", in.Greeting) 27 | 28 | stream.Send(&pb.HelloWorldResponse{Reply: "ListHello Reply " + in.Greeting + " 1"}) 29 | time.Sleep(1 * time.Second) 30 | stream.Send(&pb.HelloWorldResponse{Reply: "ListHello Reply " + in.Greeting + " 2"}) 31 | time.Sleep(1 * time.Second) 32 | stream.Send(&pb.HelloWorldResponse{Reply: "ListHello Reply " + in.Greeting + " 3"}) 33 | time.Sleep(1 * time.Second) 34 | return nil 35 | } 36 | 37 | // 客户端流式 RPC, 客户端流式请求,服务器可返回一次 38 | func (s *SayHelloServer) SayMoreHello(stream pb.HelloWorldService_SayMoreHelloServer) error { 39 | // 接受客户端请求 40 | for { 41 | req, err := stream.Recv() 42 | if err == io.EOF { 43 | break 44 | } 45 | 46 | if err != nil { 47 | return err 48 | } 49 | 50 | log.Printf("SayMoreHello Client Say: %v", req.Greeting) 51 | } 52 | 53 | // 流读取完成后,返回 54 | return stream.SendAndClose(&pb.HelloWorldResponse{Reply: "SayMoreHello Recv Muti Greeting"}) 55 | } 56 | 57 | func (s *SayHelloServer) SayHelloChat(stream pb.HelloWorldService_SayHelloChatServer) error { 58 | 59 | go func() { 60 | for { 61 | req, err := stream.Recv() 62 | if err == io.EOF { 63 | break 64 | } 65 | 66 | if err != nil { 67 | return 68 | } 69 | 70 | log.Printf("SayHelloChat Client Say: %v", req.Greeting) 71 | } 72 | }() 73 | 74 | stream.Send(&pb.HelloWorldRequest{Greeting: "SayHelloChat Server Say Hello 1"}) 75 | time.Sleep(1 * time.Second) 76 | stream.Send(&pb.HelloWorldRequest{Greeting: "SayHelloChat Server Say Hello 2"}) 77 | time.Sleep(1 * time.Second) 78 | stream.Send(&pb.HelloWorldRequest{Greeting: "SayHelloChat Server Say Hello 3"}) 79 | time.Sleep(1 * time.Second) 80 | return nil 81 | } 82 | 83 | func (s *SayHelloServer) SayHelloWorld(ctx context.Context, in *pb.HelloWorldRequest) (res *pb.HelloWorldResponse, err error) { 84 | log.Printf("Client Greeting:%s", in.Greeting) 85 | log.Printf("Client Info:%v", in.Infos) 86 | 87 | var an *any.Any 88 | if in.Infos["hello"] == "world" { 89 | an, err = ptypes.MarshalAny(&pb.HelloWorld{Msg: "Good Request"}) 90 | } else { 91 | an, err = ptypes.MarshalAny(&pb.Error{Msg: []string{"Bad Request", "Wrong Info Msg"}}) 92 | } 93 | 94 | if err != nil { 95 | return 96 | } 97 | return &pb.HelloWorldResponse{ 98 | Reply: "Hello World !!", 99 | Details: []*any.Any{an}, 100 | }, nil 101 | } 102 | 103 | func main() { 104 | lis, err := net.Listen("tcp", ":8080") 105 | if err != nil { 106 | panic(err) 107 | } 108 | 109 | // 加载证书和密钥 (同时能验证证书与私钥是否匹配) 110 | cert, err := tls.LoadX509KeyPair("certs/test_server.pem", "certs/test_server.key") 111 | if err != nil { 112 | panic(err) 113 | } 114 | 115 | // 将根证书加入证书词 116 | // 测试证书的根如果不加入可信池,那么测试证书将视为不可惜,无法通过验证。 117 | certPool := x509.NewCertPool() 118 | rootBuf, err := ioutil.ReadFile("certs/root.pem") 119 | if err != nil { 120 | panic(err) 121 | } 122 | 123 | if !certPool.AppendCertsFromPEM(rootBuf) { 124 | panic("fail to append test ca") 125 | } 126 | 127 | tlsConf := &tls.Config{ 128 | ClientAuth: tls.RequireAndVerifyClientCert, 129 | Certificates: []tls.Certificate{cert}, 130 | ClientCAs: certPool, 131 | } 132 | 133 | serverOpt := grpc.Creds(credentials.NewTLS(tlsConf)) 134 | grpcServer := grpc.NewServer(serverOpt) 135 | 136 | pb.RegisterHelloWorldServiceServer(grpcServer, &SayHelloServer{}) 137 | 138 | log.Println("Server Start...") 139 | grpcServer.Serve(lis) 140 | } 141 | -------------------------------------------------------------------------------- /gin/gin.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "net/http" 7 | 8 | limits "github.com/gin-contrib/size" 9 | "github.com/gin-gonic/contrib/sessions" 10 | "github.com/gin-gonic/gin" 11 | ) 12 | 13 | // some 14 | const ( 15 | USER = "admin" 16 | PWD = "admin" 17 | ) 18 | 19 | func main() { 20 | r := gin.Default() 21 | 22 | store := sessions.NewCookieStore([]byte("jdagldagsdadhsbdgaj")) 23 | store.Options(sessions.Options{ 24 | MaxAge: 7200, 25 | Path: "/", 26 | Secure: true, 27 | HttpOnly: true, 28 | }) 29 | // 限制文件大小 30 | r.Use(limits.RequestSizeLimiter(4 << 20)) 31 | r.Use(sessions.Sessions("httpsgateway", store)) 32 | r.NoRoute(func(c *gin.Context) { c.JSON(http.StatusNotFound, "Invaild api request") }) 33 | 34 | r.POST("login", HandlleLogin) 35 | api := r.Group("api", Auth()) 36 | { 37 | api.GET("logout", HandleLogout) 38 | api.GET("hello_world", HandleHelloWorld) 39 | } 40 | 41 | t := r.Group("test") 42 | { 43 | t.POST("upload_file", HandleUploadFile) 44 | t.POST("upload_muti_file", HandleUploadMutiFile) 45 | t.GET("download", HandleDownloadFile) 46 | } 47 | 48 | // r.RunTLS(":8443", "./cert.pem", "./key.pem") 49 | r.Run(":8888") 50 | } 51 | 52 | // Auth doc 53 | func Auth() gin.HandlerFunc { 54 | return func(c *gin.Context) { 55 | session := sessions.Default(c) 56 | u := session.Get("user") 57 | if u == nil { 58 | c.JSON(http.StatusUnauthorized, gin.H{"msg": "您暂未登录"}) 59 | c.Abort() 60 | return 61 | } 62 | } 63 | } 64 | 65 | // HandlleLogin doc 66 | func HandlleLogin(c *gin.Context) { 67 | user := c.PostForm("user") 68 | password := c.PostForm("password") 69 | 70 | if user != USER || password != PWD { 71 | c.JSON(http.StatusBadRequest, gin.H{"msg": "用户名或密码不正确"}) 72 | return 73 | } 74 | 75 | session := sessions.Default(c) 76 | session.Set("user", USER) 77 | session.Save() 78 | c.JSON(http.StatusOK, gin.H{"msg": "login succeed"}) 79 | 80 | } 81 | 82 | // HandleLogout doc 83 | func HandleLogout(c *gin.Context) { 84 | session := sessions.Default(c) 85 | session.Delete("user") 86 | session.Save() 87 | c.JSON(http.StatusOK, gin.H{"data": "See you!"}) 88 | } 89 | 90 | // HandleHelloWorld doc 91 | func HandleHelloWorld(c *gin.Context) { 92 | c.JSON(http.StatusOK, gin.H{"data": "Hello World!"}) 93 | } 94 | 95 | // HandleUploadFile 上传单个文件 96 | func HandleUploadFile(c *gin.Context) { 97 | file, header, err := c.Request.FormFile("file") 98 | if err != nil { 99 | c.JSON(http.StatusBadRequest, gin.H{"msg": "文件上传失败"}) 100 | return 101 | } 102 | 103 | content, err := ioutil.ReadAll(file) 104 | if err != nil { 105 | c.JSON(http.StatusBadRequest, gin.H{"msg": "文件读取失败"}) 106 | return 107 | } 108 | 109 | fmt.Println(header.Filename) 110 | fmt.Println(string(content)) 111 | c.JSON(http.StatusOK, gin.H{"msg": "上传成功"}) 112 | } 113 | 114 | // HandleUploadMutiFile 上传多个文件 115 | func HandleUploadMutiFile(c *gin.Context) { 116 | 117 | // 限制上传文件大小 118 | c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 4<<20) 119 | 120 | // 限制放入内存的文件大小 121 | err := c.Request.ParseMultipartForm(4 << 20) 122 | if err != nil { 123 | c.JSON(http.StatusBadRequest, gin.H{"msg": "文件读取失败"}) 124 | return 125 | } 126 | formdata := c.Request.MultipartForm 127 | files := formdata.File["file"] 128 | 129 | for _, v := range files { 130 | file, err := v.Open() 131 | if err != nil { 132 | c.JSON(http.StatusBadRequest, gin.H{"msg": "文件读取失败"}) 133 | return 134 | } 135 | defer file.Close() 136 | 137 | content, err := ioutil.ReadAll(file) 138 | if err != nil { 139 | c.JSON(http.StatusBadRequest, gin.H{"msg": "文件读取失败"}) 140 | return 141 | } 142 | 143 | fmt.Println(v.Filename) 144 | fmt.Println(string(content)) 145 | } 146 | 147 | c.JSON(http.StatusOK, gin.H{"msg": "上传成功"}) 148 | } 149 | 150 | // HandleDownloadFile 下载文件 151 | func HandleDownloadFile(c *gin.Context) { 152 | content := c.Query("content") 153 | 154 | content = "hello world, 我是一个文件," + content 155 | 156 | c.Writer.WriteHeader(http.StatusOK) 157 | c.Header("Content-Disposition", "attachment; filename=hello.txt") 158 | c.Header("Content-Type", "application/text/plain") 159 | c.Header("Accept-Length", fmt.Sprintf("%d", len(content))) 160 | c.Writer.Write([]byte(content)) 161 | } 162 | -------------------------------------------------------------------------------- /grpc/demo2/README.md: -------------------------------------------------------------------------------- 1 | # [gRPC在Go中的使用(二)gRPC实现简单通讯](https://razeen.me/post/how-to-use-grpc-in-golang-02.html) 2 | 3 | Desc:gRPC实现简单通讯,Google 开源 RPC 框架 gRPC 初探 4 | 5 | 6 | 7 | 在上一篇中,我们用protobuf定义了两个消息`HelloWorldRequest`与`HelloWorldResponse`以及一个`HelloWorldService`服务。同时,我们还生成了相应的go代码`.pb.go`。 8 | 9 | 那么客户端与服务端怎么去通过这些接口去完成通讯呢?下面我们一起实现一个简单的gRPC通讯。 10 | 11 | 12 | 13 | 14 | 15 | 在RPC通讯中,客户端使用存根(SayHelloWorld)发送请求到服务器并且等待响应返回,整个过程就像我们平常函数调用一样。 16 | 17 | ```rpc 18 | service HelloWorldService { 19 | rpc SayHelloWorld(HelloWorldRequest) returns (HelloWorldResponse){} 20 | } 21 | ``` 22 | 23 | 那么接下来,我们先创建一个服务端。 24 | 25 | 26 | 27 | ### 创建服务端 28 | 29 | 30 | 在生成的`hello_world.pb.go`中,已经为我们生成了服务端的接口: 31 | 32 | ```go 33 | // HelloWorldServiceServer is the server API for HelloWorldService service. 34 | type HelloWorldServiceServer interface { 35 | SayHelloWorld(context.Context, *HelloWorldRequest) (*HelloWorldResponse, error) 36 | } 37 | ``` 38 | 39 | 在服务端我们首先要做的就是实现这个接口。 40 | 41 | ```go 42 | package main 43 | 44 | import ( 45 | "context" 46 | "log" 47 | "net" 48 | 49 | pb "github.com/razeencheng/demo-go/grpc/demo2/helloworld" 50 | 51 | "github.com/golang/protobuf/ptypes" 52 | "github.com/golang/protobuf/ptypes/any" 53 | "google.golang.org/grpc" 54 | ) 55 | 56 | type SayHelloServer struct{} 57 | 58 | func (s *SayHelloServer) SayHelloWorld(ctx context.Context, in *pb.HelloWorldRequest) (res *pb.HelloWorldResponse, err error) { 59 | log.Printf("Client Greeting:%s", in.Greeting) 60 | log.Printf("Client Info:%v", in.Infos) 61 | 62 | var an *any.Any 63 | if in.Infos["hello"] == "world" { 64 | an, err = ptypes.MarshalAny(&pb.HelloWorld{Msg: "Good Request"}) 65 | } else { 66 | an, err = ptypes.MarshalAny(&pb.Error{Msg: []string{"Bad Request", "Wrong Info Msg"}}) 67 | } 68 | 69 | if err != nil { 70 | return 71 | } 72 | return &pb.HelloWorldResponse{ 73 | Reply: "Hello World !!", 74 | Details: []*any.Any{an}, 75 | }, nil 76 | } 77 | ``` 78 | 79 | 简单如上面的几行,实现了这个接口我们只需要创建一个结构`SayHelloServer`,同时实现`HelloWorldServiceServer`的所有方法即可。 80 | 81 | 这里为了演示效果我打印了一些数据,同时利用`any.Any`在不同的情况下返回不同的类型数据。 82 | 83 | 84 | 85 | 当然,只是现实了接口还不够,我们还需要启动一个服务,这样客户端才能使用该服务。启动服务很简单,就像我们平常启用一个Server一样。 86 | 87 | ```go 88 | func main() { 89 | // 我们首先须监听一个tcp端口 90 | lis, err := net.Listen("tcp", ":8080") 91 | if err != nil { 92 | panic(err) 93 | } 94 | 95 | // 新建一个grpc服务器 96 | grpcServer := grpc.NewServer() 97 | // 向grpc服务器注册SayHelloServer 98 | pb.RegisterHelloWorldServiceServer(grpcServer, &SayHelloServer{}) 99 | // 启动服务 100 | grpcServer.Serve(lis) 101 | } 102 | ``` 103 | 104 | 从上面的代码,我们可以看到,简单的4步即可启动一个服务。 105 | 106 | 1. 监听一个服务端口,供客户端调用; 107 | 2. 创建一个grpc服务器,当然这里可以设置`授权认证`,这个在下一篇中我们将详细介绍; 108 | 3. 注册服务,其实是调用生存的`.pb.go`中的`RegisterHelloWorldServiceServer`方法,将我们这里实现的`SayHelloServer`加入到该服务中。 109 | 4. 启动服务,等待客户端连接。 110 | 111 | 我们` go run server.go`,无任何报错,这样一个简单的grpc服务的服务端就准备就绪了。接下来我们看看客户端。 112 | 113 | 114 | 115 | ### 创建客户端 116 | 117 | 118 | 例如: 119 | 120 | ```go 121 | package main 122 | 123 | import ( 124 | "context" 125 | "log" 126 | 127 | "google.golang.org/grpc" 128 | 129 | pb "github.com/razeencheng/demo-go/grpc/demo2/helloworld" 130 | ) 131 | 132 | func main() { 133 | // 创建一个 gRPC channel 和服务器交互 134 | conn, err := grpc.Dial("localhost:8080", grpc.WithInsecure()) 135 | if err != nil { 136 | log.Fatalf("Dial failed:%v", err) 137 | } 138 | defer conn.Close() 139 | 140 | // 创建客户端 141 | client := pb.NewHelloWorldServiceClient(conn) 142 | 143 | // 直接调用 144 | resp1, err := client.SayHelloWorld(context.Background(), &pb.HelloWorldRequest{ 145 | Greeting: "Hello Server 1 !!", 146 | Infos: map[string]string{"hello": "world"}, 147 | }) 148 | 149 | log.Printf("Resp1:%+v", resp1) 150 | 151 | resp2, err := client.SayHelloWorld(context.Background(), &pb.HelloWorldRequest{ 152 | Greeting: "Hello Server 2 !!", 153 | }) 154 | 155 | log.Printf("Resp2:%+v", resp2) 156 | } 157 | ``` 158 | 159 | 客户端的实现比服务端更简洁,三步即可。 160 | 161 | 1. 创建一个 gRPC channel 和服务器交互。这里也是可以设置`授权认证`的; 162 | 2. 创建一个客户端去执行RPC。用到的也是`.pb.go`内的`NewHelloWorldServiceClient`方法; 163 | 3. 像函数调用一样去调用RPC服务。 164 | 165 | 166 | 167 | 我直接RUN起来,如下,我们可以看到客户端发送到服务的消息以及服务端对不同消息的不同回复。 168 | 169 | ![](https://st.razeen.me/essay/image/grpc/grpc-result.png) 170 | 171 | 172 | 173 | 那么到这里,我们简单的实现了一个gRPC通讯。但很多时候,我们可能希望客户端与服务器能更安全的通信,或者客户端与服务器不再是一种固定的结构的传输,需要流式的去处理一些问题等等。针对这些问题,在下一篇博客中,我将结合实例详细说明。 174 | 175 | 176 | 177 | *文中完整代码在[这里](https://github.com/razeencheng/demo-go/tree/master/grpc/demo2)。* -------------------------------------------------------------------------------- /windows_api/main_windows.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | "syscall" 7 | "unicode/utf16" 8 | "unsafe" 9 | 10 | "github.com/pkg/errors" 11 | ) 12 | 13 | func main() { 14 | var order uint32 = 1 // True 15 | var ulAf uint32 = AF_INET 16 | var tableClass uint32 = 0 17 | 18 | buffer, err := GetExtendedTcpTable(order, ulAf, tableClass) 19 | if err != nil { 20 | fmt.Println(err) 21 | return 22 | } 23 | 24 | fmt.Println(hex.Dump(buffer)) 25 | 26 | pTable := (*MIB_TCPTABLE_OWNER_PID)(unsafe.Pointer(&buffer[0])) 27 | 28 | rows := make([]MIB_TCPROW_OWNER_PID, int(pTable.dwNumEntries)) 29 | for i := 0; i < int(pTable.dwNumEntries); i++ { 30 | rows[i] = *(*MIB_TCPROW_OWNER_PID)(unsafe.Pointer( 31 | uintptr(unsafe.Pointer(&pTable.table[0])) + 32 | uintptr(i)*unsafe.Sizeof(pTable.table[0]))) 33 | } 34 | show(rows) 35 | 36 | rows2 := ((*[1 << 30]MIB_TCPROW_OWNER_PID)(unsafe.Pointer(&pTable.table[0]))[:int(pTable.dwNumEntries):int(pTable.dwNumEntries)]) 37 | show(rows2) 38 | 39 | } 40 | 41 | var ( 42 | kernel32DLL = syscall.NewLazyDLL("kernel32.dll") 43 | procCreateJobObjectW = kernel32DLL.NewProc("CreateJobObjectW") 44 | 45 | iphlpapiDLL = syscall.NewLazyDLL("iphlpapi.dll") 46 | procGetExtendedTcpTable = iphlpapiDLL.NewProc("GetExtendedTcpTable") 47 | ) 48 | 49 | const ( 50 | AF_INET = 2 51 | ) 52 | 53 | type MIB_TCPTABLE_OWNER_PID struct { 54 | dwNumEntries uint32 55 | table [1]MIB_TCPROW_OWNER_PID 56 | } 57 | 58 | type MIB_TCPROW_OWNER_PID struct { 59 | dwState uint32 60 | dwLocalAddr [4]byte 61 | dwLocalPort uint32 62 | dwRemoteAddr [4]byte 63 | dwRemotePort uint32 64 | dwOwningPid uint32 65 | } 66 | 67 | func show(raws []MIB_TCPROW_OWNER_PID) { 68 | for _, v := range raws { 69 | v.show() 70 | } 71 | } 72 | 73 | func (m *MIB_TCPROW_OWNER_PID) show() { 74 | fmt.Printf(` 75 | state: %d 76 | loadAddr: %s 77 | localPort: %d 78 | remoteAddr: %s 79 | remotePort: %d 80 | pid: %d`, 81 | m.dwState, 82 | string(m.dwLocalAddr[:]), 83 | m.dwLocalPort, 84 | string(m.dwRemoteAddr[:]), 85 | m.dwRemotePort, 86 | m.dwOwningPid) 87 | } 88 | 89 | // GetExtendedTcpTable function retrieves a table that contains a list of TCP endpoints available to the application. 90 | func GetExtendedTcpTable(order, ulAf, tableClass uint32) ([]byte, error) { 91 | 92 | var dwSize uint32 93 | ret, _, err := procGetExtendedTcpTable.Call( 94 | 0, // PVOID 95 | uintptr(unsafe.Pointer(&dwSize)), // PDWORD 96 | uintptr(order), // BOOL 97 | uintptr(ulAf), // ULONG 98 | uintptr(tableClass), // TCP_TABLE_CLASS 99 | 0, // ULONG 100 | ) 101 | if ret == 0 { 102 | return nil, errors.Wrapf(err, "get extended tcp table size failed code %x", ret) 103 | } 104 | 105 | if syscall.Errno(ret) == syscall.ERROR_INSUFFICIENT_BUFFER { 106 | buffer := make([]byte, int(dwSize)) 107 | 108 | ret, _, err := procGetExtendedTcpTable.Call( 109 | uintptr(unsafe.Pointer(&buffer[0])), 110 | uintptr(unsafe.Pointer(&dwSize)), 111 | uintptr(order), 112 | uintptr(ulAf), 113 | uintptr(tableClass), 114 | uintptr(uint32(0)), 115 | ) 116 | 117 | if ret != 0 { 118 | return nil, errors.Wrapf(err, "get extended tcp table failed code %x", ret) 119 | } 120 | 121 | return buffer, nil 122 | } 123 | 124 | return nil, errors.Wrapf(err, "get extended tcp table size failed code %x", ret) 125 | } 126 | 127 | // CreateJobObject uses the CreateJobObjectW Windows API Call to create and return a Handle to a new JobObject 128 | func CreateJobObject(attr *syscall.SecurityAttributes, name string) (syscall.Handle, error) { 129 | r1, _, err := procCreateJobObjectW.Call( 130 | uintptr(unsafe.Pointer(attr)), 131 | uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(name))), 132 | ) 133 | if err != syscall.Errno(0) { 134 | return 0, err 135 | } 136 | return syscall.Handle(r1), nil 137 | } 138 | 139 | // StringToCharPtr converts a Go string into pointer to a null-terminated cstring. 140 | // This assumes the go string is already ANSI encoded. 141 | func StringToCharPtr(str string) *uint8 { 142 | chars := append([]byte(str), 0) // null terminated 143 | return &chars[0] 144 | } 145 | 146 | // StringToUTF16Ptr converts a Go string into a pointer to a null-terminated UTF-16 wide string. 147 | // This assumes str is of a UTF-8 compatible encoding so that it can be re-encoded as UTF-16. 148 | func StringToUTF16Ptr(str string) *uint16 { 149 | wchars := utf16.Encode([]rune(str + "\x00")) 150 | return &wchars[0] 151 | } 152 | -------------------------------------------------------------------------------- /benchmark/README.md: -------------------------------------------------------------------------------- 1 | # [Go学习笔记(三) | 怎么写Go基准测试(性能测试)](https://razeen.me/post/go-how-to-write-benchmark.html) 2 | 3 | 4 | 5 | 或许你经常会思考这样的问题,我用不同的方法实现了同样的效果,哪个会更快?哪个内存消耗更小?这时候你一个简单的基准测试就能解决你的疑惑。 6 | 7 | 8 | 9 | 10 | 11 | Go向来是以工具丰富而著称的,在学习Go的过程中,你会发现无论是写一个单元测试,还是做一些竞争检测都能很快的上手,而且用的很痛快。当然,接下来要说的基准测试也一样。 12 | 13 | 基准测试工具就在Go的测试包中,下面就用[一个例子](https://github.com/razeencheng/demo-go/tree/master/benchmark)来介绍。 14 | 15 | 16 | 17 | ### 举个栗子 18 | 19 | 20 | 21 | 由于一些场景需要,我需要将`[]byte`输出16进制字符。 22 | 23 | 有时候我会这么写: 24 | 25 | ``` golang 26 | fmt.Sprintf("%x", b) 27 | ``` 28 | 29 | 但有时候我会这么写: 30 | 31 | ``` 32 | hex.EncodeToString(b) 33 | ``` 34 | 35 | 但到底哪种写法更好呢?今天我就来比较一下。 36 | 37 | 直接写了个`main.go` 38 | 39 | ```Golang 40 | func EncodeA(b []byte) string { 41 | return fmt.Sprintf("%x", b) 42 | } 43 | 44 | func EncodeB(b []byte) string { 45 | return hex.EncodeToString(b) 46 | } 47 | ``` 48 | 49 | 再写个测试`main_test.go` 50 | 51 | ```golang 52 | var buf = []byte("skdjadialsdgasadasdhsakdjsahlskdjagloqweiqwo") 53 | 54 | func BenchmarkEncodeA(b *testing.B) { 55 | for i := 0; i < b.N; i++ { 56 | EncodeA(buf) 57 | } 58 | } 59 | 60 | func BenchmarkEncodeB(b *testing.B) { 61 | for i := 0; i < b.N; i++ { 62 | EncodeB(buf) 63 | } 64 | } 65 | ``` 66 | 67 | 68 | 69 | 就这么简单,我们的基本测试就写完了。从我的写法中你也许就知道: 70 | 71 | - 和单元测试一样,都写在`_test.go`文件中; 72 | - 需要以`Benchmark`为函数名开头; 73 | - 和单元测试类似,必须接受一个`*testing.B`参数; 74 | - 被测试代码放在一个循环中。 75 | 76 | 77 | 78 | 我们直接跑一下。当然我们也是用`go test`来执行测试,简单的测试只要带上`-bench=.`就可以了。 79 | 80 | ```Shell 81 | $ go test -bench=. 82 | goos: darwin 83 | goarch: amd64 84 | pkg: github.com/razeencheng/demo-go/benchmark 85 | BenchmarkEncodeA-8 5000000 265 ns/op 86 | BenchmarkEncodeB-8 10000000 161 ns/op 87 | PASS 88 | ok github.com/razeencheng/demo-go/benchmark 3.397s 89 | ``` 90 | 91 | 前两行是平台信息,第三行包名。第四、五行就是测试的结果了。 92 | 93 | - `BenchmarkEncodeA-8 ` ,`BenchmarkEncodeB-8 ` 基准测试函数名-GOMAXPROCS 94 | - `5000000`,`10000000` 被测试的函数执行次数,也就是`EncodeA()`被执行了5000000次,`EncodeB()`被执行了10000000次,也就是`b.N`的值了。 95 | - `265 ns/op`,`161 ns/op`表示每次调用被测试函数花费的时间。 96 | 97 | 从花费的时间上来看,我们知道`EncodeB()`要快一点。 98 | 99 | 100 | 101 | ### 更多 102 | 103 | 你以为就这么简单的结束了么?NONONO。 104 | 105 | - `-bench` 可接收一个有效的正则表达式来执行符合条件的测试函数。当你的函数很多时,可以用它来过滤. 106 | 107 | ```Shell 108 | $ go test -bench=BenchmarkEncodeA 109 | goos: darwin 110 | goarch: amd64 111 | pkg: github.com/razeencheng/demo-go/benchmark 112 | BenchmarkEncodeA-8 5000000 256 ns/op 113 | PASS 114 | ok github.com/razeencheng/demo-go/benchmark 1.575s 115 | ``` 116 | 117 | - ` -benchmem`可以查看内存分配 118 | 119 | ``` Shell 120 | $ go test -bench=. -benchmem 121 | goos: darwin 122 | goarch: amd64 123 | pkg: github.com/razeencheng/demo-go/benchmark 124 | BenchmarkEncodeA-8 5000000 261 ns/op 128 B/op 2 allocs/op 125 | BenchmarkEncodeB-8 10000000 162 ns/op 192 B/op 2 allocs/op 126 | PASS 127 | ok github.com/razeencheng/demo-go/benchmark 3.408s 128 | ``` 129 | 130 | 其中`B/op` 表示每次执行会分配多少内存,`allocs/op`表示每次执行会发生多少次内存分配。 131 | 132 | - `-benchtime`指定每个测试执行的时间。默认`1s`,当你的函数比较耗时你可以设置更长一点。因为`b.N`是与这个时间有关的。 133 | 134 | 当你的运行时间没达到`-benchtime`制定的时间前,`b.N`将以1,2,5,10,20,50…增加,然后重新运行测试代码。 135 | 136 | ```Shell 137 | $ go test -bench=. -benchmem -benchtime=5s 138 | goos: darwin 139 | goarch: amd64 140 | pkg: github.com/razeencheng/demo-go/benchmark 141 | BenchmarkEncodeA-8 30000000 254 ns/op 128 B/op 2 allocs/op 142 | BenchmarkEncodeB-8 50000000 160 ns/op 192 B/op 2 allocs/op 143 | PASS 144 | ok github.com/razeencheng/demo-go/benchmark 16.113s 145 | ``` 146 | 147 | - `-count`指定每个测试执行的次数。 148 | 149 | ```Shell 150 | $ go test -bench=. -benchmem -count=3 151 | goos: darwin 152 | goarch: amd64 153 | pkg: github.com/razeencheng/demo-go/benchmark 154 | BenchmarkEncodeA-8 5000000 256 ns/op 128 B/op 2 allocs/op 155 | BenchmarkEncodeA-8 5000000 255 ns/op 128 B/op 2 allocs/op 156 | BenchmarkEncodeA-8 5000000 253 ns/op 128 B/op 2 allocs/op 157 | BenchmarkEncodeB-8 10000000 163 ns/op 192 B/op 2 allocs/op 158 | BenchmarkEncodeB-8 10000000 160 ns/op 192 B/op 2 allocs/op 159 | BenchmarkEncodeB-8 10000000 160 ns/op 192 B/op 2 allocs/op 160 | PASS 161 | ok github.com/razeencheng/demo-go/benchmark 9.984s 162 | ``` 163 | 164 | 我常用的也就这些了。 165 | 166 | 但对于`testing.B`来说,它拥有了`testing.T`的全部接口,所以`Fail,Skip,Error`这些都可以用,而且还增加了 167 | 168 | - `SetBytes( i uint64)` 统计内存消耗。 169 | - `SetParallelism(p int)` 制定并行数目。 170 | - `StartTimer / StopTimer / ResertTimer` 操作计时器。 171 | 172 | 你可以按需使用。 173 | 174 | 175 | 176 | ### 注意 177 | 178 | `b.N`为一个自增字段,谨慎用它做函数参数。 179 | -------------------------------------------------------------------------------- /restful-api/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "strconv" 6 | "time" 7 | 8 | "github.com/gin-gonic/contrib/sessions" 9 | "github.com/gin-gonic/gin" 10 | ) 11 | 12 | func main() { 13 | 14 | r := gin.Default() 15 | 16 | store := sessions.NewCookieStore([]byte("dashdjkasdhaksda")) 17 | store.Options(sessions.Options{ 18 | MaxAge: 7200, 19 | Path: "/", 20 | Secure: true, 21 | HttpOnly: true, 22 | }) 23 | r.Use(sessions.Sessions("mydemo", store)) 24 | r.NoRoute(func(c *gin.Context) { c.JSON(http.StatusNotFound, "Invaild api request") }) 25 | 26 | v1 := r.Group("/api/v1") 27 | { 28 | v1.POST("/login", HandleLogin) 29 | v1.GET("/articles", HandleGetArticles) 30 | 31 | v1Auth := v1.Use(Auth) 32 | { 33 | v1Auth.POST("/logout", HandleLogout) 34 | v1Auth.POST("/articles", HandlePostArticles) 35 | v1Auth.PUT("/articles", HandleUpdateArticles) 36 | v1Auth.DELETE("/articles/:id", HandleDeleteArticles) 37 | v1.GET("/articles/:id/comments", HandleGetComments) 38 | v1Auth.POST("/articles/:id/comments", HandleAddComments) 39 | v1Auth.PUT("/articles/:id/comments/:id", HandleUpdateComments) 40 | v1Auth.DELETE("/articles/:id/comments/:id", HandleDeleteComments) 41 | } 42 | } 43 | 44 | r.Run(":8080") 45 | } 46 | 47 | const sessionsKey = "user" 48 | 49 | // Auth doc 50 | func Auth(c *gin.Context) { 51 | session := sessions.Default(c) 52 | u := session.Get(sessionsKey) 53 | if u == nil { 54 | c.JSON(http.StatusUnauthorized, &Resp{Error: "please login"}) 55 | c.Abort() 56 | return 57 | } 58 | } 59 | 60 | // Resp doc 61 | type Resp struct { 62 | Data interface{} `json:"data"` 63 | Error string `json:"error"` 64 | } 65 | 66 | // LoginParams doc 67 | type LoginParams struct { 68 | UserID string `json:"user_id"` 69 | Password string `json:"password"` 70 | } 71 | 72 | // HandleLogin doc 73 | func HandleLogin(c *gin.Context) { 74 | param := &LoginParams{} 75 | if err := c.BindJSON(param); err != nil { 76 | c.JSON(http.StatusBadRequest, &Resp{Error: "parameters error"}) 77 | return 78 | } 79 | 80 | // 做一些校验 81 | // ... 82 | 83 | session := sessions.Default(c) 84 | session.Set(sessionsKey, param.UserID) 85 | session.Save() 86 | c.JSON(http.StatusOK, &Resp{Data: "login succeed"}) 87 | } 88 | 89 | // 模拟数据库 90 | var tempStorage = []*Article{} 91 | 92 | // HandleLogout doc 93 | func HandleLogout(c *gin.Context) { 94 | session := sessions.Default(c) 95 | session.Delete(sessionsKey) 96 | session.Save() 97 | c.JSON(http.StatusOK, &Resp{Data: "logout succeed"}) 98 | } 99 | 100 | // HandleGetArticles doc 101 | func HandleGetArticles(c *gin.Context) { 102 | 103 | page := c.Query("page") 104 | pageSize := c.Query("page_size") 105 | orderby := c.Query("order") 106 | searchKey := c.Query("search") 107 | 108 | // 分页 109 | // 查询 110 | // 排序 111 | // ... 112 | _, _, _, _ = page, pageSize, orderby, searchKey 113 | 114 | c.JSON(http.StatusOK, &Resp{Data: map[string]interface{}{ 115 | "result": tempStorage, 116 | "total": len(tempStorage), 117 | }}) 118 | } 119 | 120 | // Article doc 121 | type Article struct { 122 | ID int `json:"id"` 123 | Titile string `json:"titile"` 124 | Tags []string `json:"tags"` 125 | Content string `json:"content"` 126 | UpdateAt time.Time `json:"update_at"` 127 | CreateAt time.Time `json:"create_at"` 128 | } 129 | 130 | // HandlePostArticles doc 131 | func HandlePostArticles(c *gin.Context) { 132 | param := &Article{} 133 | if err := c.BindJSON(param); err == nil { 134 | c.JSON(http.StatusBadRequest, &Resp{Error: "parameters error"}) 135 | return 136 | } 137 | 138 | // 参数判断 139 | // 保存文章 140 | // ... 141 | param.ID = len(tempStorage) 142 | param.CreateAt = time.Now() 143 | tempStorage = append(tempStorage, param) 144 | 145 | c.JSON(http.StatusOK, &Resp{Data: param}) 146 | } 147 | 148 | // HandleUpdateArticles doc 149 | func HandleUpdateArticles(c *gin.Context) { 150 | param := &Article{} 151 | if err := c.BindJSON(param); err == nil { 152 | c.JSON(http.StatusBadRequest, &Resp{Error: "parameters error"}) 153 | return 154 | } 155 | 156 | // 参数判断 157 | // 保存文章 158 | // ... 159 | param.UpdateAt = time.Now() 160 | for i, v := range tempStorage { 161 | if v.ID == param.ID { 162 | param.CreateAt = v.CreateAt 163 | tempStorage[i] = param 164 | break 165 | } 166 | } 167 | 168 | c.JSON(http.StatusOK, &Resp{Data: param}) 169 | } 170 | 171 | // HandleDeleteArticles doc 172 | func HandleDeleteArticles(c *gin.Context) { 173 | id, err := strconv.Atoi(c.Param("id")) 174 | if err != nil { 175 | c.JSON(http.StatusBadRequest, &Resp{Error: "parameters error"}) 176 | return 177 | } 178 | 179 | // 删除 180 | // ... 181 | for i, v := range tempStorage { 182 | if v.ID == id { 183 | tempStorage = append(tempStorage[:i], tempStorage[i+1:]...) 184 | break 185 | } 186 | } 187 | 188 | c.JSON(http.StatusOK, &Resp{Data: "delete succeed"}) 189 | } 190 | 191 | // HandleGetComments doc 192 | func HandleGetComments(c *gin.Context) { 193 | 194 | } 195 | 196 | // HandleAddComments doc 197 | func HandleAddComments(c *gin.Context) { 198 | 199 | } 200 | 201 | // HandleUpdateComments doc 202 | func HandleUpdateComments(c *gin.Context) { 203 | 204 | } 205 | 206 | // HandleDeleteComments doc 207 | func HandleDeleteComments(c *gin.Context) { 208 | 209 | } 210 | -------------------------------------------------------------------------------- /swaggo-gin/docs/swagger.yaml: -------------------------------------------------------------------------------- 1 | basePath: /api/v1 2 | definitions: 3 | main.File: 4 | properties: 5 | id: 6 | type: integer 7 | len: 8 | type: integer 9 | name: 10 | type: string 11 | type: object 12 | main.Files: 13 | properties: 14 | files: 15 | items: 16 | $ref: '#/definitions/main.File' 17 | type: array 18 | len: 19 | type: integer 20 | type: object 21 | main.JSONParams: 22 | properties: 23 | array: 24 | description: 这是一个字符串数组 25 | items: 26 | type: string 27 | type: array 28 | int: 29 | description: 这是一个数字 30 | type: integer 31 | str: 32 | description: 这是一个字符串 33 | type: string 34 | struct: 35 | description: 这是一个结构 36 | properties: 37 | field: 38 | type: string 39 | type: object 40 | type: object 41 | host: 127.0.0.1:8080 42 | info: 43 | contact: 44 | email: me@razeen.me 45 | name: Razeen 46 | url: https://razeen.me 47 | description: |- 48 | # Test Example Makedown 49 | 50 | ### 关于使用说明 51 | 52 | 吧啦吧啦吧啦 53 | 54 | ![](https://camo.githubusercontent.com/4752126ebe1c5fe33cd179136fcbcf55e2074c8bacf90c378231256df809d687/68747470733a2f2f62616467652e667572792e696f2f67682f676f2d73776167676572253246676f2d737761676765722e737667) 55 | license: 56 | name: Apache 2.0 57 | url: http://www.apache.org/licenses/LICENSE-2.0.html 58 | termsOfService: https://razeen.me 59 | title: Swagger Example API 60 | version: "1.0" 61 | paths: 62 | /file/{id}: 63 | get: 64 | consumes: 65 | - multipart/form-data 66 | description: 获取文件 67 | parameters: 68 | - description: 文件ID 69 | in: path 70 | name: id 71 | required: true 72 | type: integer 73 | produces: 74 | - application/octet-stream 75 | responses: 76 | "200": 77 | description: OK 78 | schema: 79 | type: string 80 | summary: 获取某个文件 81 | tags: 82 | - 文件处理 83 | /hello: 84 | get: 85 | consumes: 86 | - multipart/form-data 87 | description: 向你说Hello 88 | parameters: 89 | - description: 人名 90 | in: query 91 | name: who 92 | required: true 93 | type: string 94 | produces: 95 | - application/json 96 | responses: 97 | "200": 98 | description: '{"msg": "hello Razeen"}' 99 | schema: 100 | type: string 101 | "400": 102 | description: '{"msg": "who are you"}' 103 | schema: 104 | type: string 105 | summary: 测试SayHello 106 | tags: 107 | - 测试 108 | /json: 109 | post: 110 | consumes: 111 | - application/json 112 | description: 获取JSON的示例 113 | parameters: 114 | - description: 需要上传的JSON 115 | in: body 116 | name: param 117 | required: true 118 | schema: 119 | $ref: '#/definitions/main.JSONParams' 120 | produces: 121 | - application/json 122 | responses: 123 | "200": 124 | description: 返回 125 | schema: 126 | $ref: '#/definitions/main.JSONParams' 127 | summary: 获取JSON的示例 128 | tags: 129 | - JSON 130 | /list: 131 | get: 132 | consumes: 133 | - multipart/form-data 134 | description: 文件列表 135 | produces: 136 | - application/json 137 | responses: 138 | "200": 139 | description: OK 140 | schema: 141 | $ref: '#/definitions/main.Files' 142 | summary: 查看文件列表 143 | tags: 144 | - 文件处理 145 | /login: 146 | post: 147 | consumes: 148 | - multipart/form-data 149 | description: 登入 150 | parameters: 151 | - default: admin 152 | description: 用户名 153 | in: formData 154 | name: user 155 | required: true 156 | type: string 157 | - description: 密码 158 | in: formData 159 | name: password 160 | required: true 161 | type: string 162 | produces: 163 | - application/json 164 | responses: 165 | "200": 166 | description: '{"msg":"login success"}' 167 | schema: 168 | type: string 169 | "400": 170 | description: '{"msg": "user or password error"}' 171 | schema: 172 | type: string 173 | summary: 登陆 174 | tags: 175 | - 登陆 176 | /upload: 177 | post: 178 | consumes: 179 | - multipart/form-data 180 | description: 上传文件 181 | parameters: 182 | - description: 文件 183 | in: formData 184 | name: file 185 | required: true 186 | type: file 187 | produces: 188 | - application/json 189 | responses: 190 | "200": 191 | description: OK 192 | schema: 193 | $ref: '#/definitions/main.File' 194 | summary: 上传文件 195 | tags: 196 | - 文件处理 197 | schemes: 198 | - http 199 | - https 200 | swagger: "2.0" 201 | tags: 202 | - externalDocs: 203 | description: This is my blog site 204 | url: https://razeen.me 205 | name: TestTag1 206 | x-example-key: 207 | key: value 208 | -------------------------------------------------------------------------------- /restful-api/README.md: -------------------------------------------------------------------------------- 1 | # [Golang中的RESTful API最佳实践](https://razeencheng.com/post/golang-and-restful-api.html) 2 | 3 | 4 | RESRful API已经流行很多年了,我也一直在使用它。最佳实践也看过不少,但当一个项目完成,再次回顾/梳理项目时,会发现很多API和规范还是多少有些出入。在这篇文章中,我们结合Go Web再次梳理一下RESTful API的相关最佳实践。 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | ### 关于RESTful API 13 | 14 | 关于什么是RESTful API,不再累述。推荐几个相关链接。 15 | 16 | - [理解RESTful架构](https://www.ruanyifeng.com/blog/2011/09/restful.html) 17 | - [REST API Tutorial](https://restfulapi.net/) 18 | 19 | 20 | 21 | ### 1.使用JSON 22 | 23 | 不管是接收还是返回数据都推荐使用JSON。 24 | 25 | 通常返回数据的格式有JSON和XML,但XML过于冗长,可读性差,而且各种语言的解析上也不如JSON,使用JSON的好处,显而易见。 26 | 27 | 而接收数据,我们这里也推荐使用JSON,对于后端开发而言,入参直接与模型绑定,省去冗长的参数解析能简化不少代码,而且JSON能更简单的传递一些更复杂的结构等。 28 | 29 | 正如示例代码中的这一段,我们以`gin`框架为例。 30 | 31 | ```go 32 | // HandleLogin doc 33 | func HandleLogin(c *gin.Context) { 34 | param := &LoginParams{} 35 | if err := c.BindJSON(param); err != nil { 36 | c.JSON(http.StatusBadRequest, &Resp{Error: "parameters error"}) 37 | return 38 | } 39 | 40 | // 做一些校验 41 | // ... 42 | 43 | session := sessions.Default(c) 44 | session.Set(sessionsKey, param.UserID) 45 | session.Save() 46 | c.JSON(http.StatusOK, &Resp{Data: "login succeed"}) 47 | } 48 | ``` 49 | 50 | 通过`c.BindJSON`,轻松的将入参于模型`LoginParams`绑定;通过`c.JSON`轻松的将数据JSON序列化返回。 51 | 52 | 53 | 54 | 但所有接口都必须用JSON么?那也未必。比如文件上传,这时我们使用`FormData`比把文件base64之类的放到JSON里面更高效。 55 | 56 | 57 | 58 | ### 2.路径中不包含动词 59 | 60 | 我们的HTTP请求方法中已经有`GET`,`POST`等这些动作了,完全没有必要再路径中加上动词。 61 | 62 | 我们常用HTTP请求方法包括`GET`,`POST`,`PUT`和`DELETE`, 这也对应了我们经常需要做的数据库操作。`GET`查找/获取资源,`POST`新增资源,`PUT`修改资源,`DELETE`删除资源。 63 | 64 | 如下,这些路径中没有任何动词,简洁明了。 65 | 66 | ```go 67 | // 获取文章列表 68 | v1.GET("/articles", HandleGetArticles) 69 | // 发布文章 70 | v1.POST("/articles", HandlePostArticles) 71 | // 修改文章 72 | v1.PUT("/articles", HandleUpdateArticles) 73 | // 删除文章 74 | v1.DELETE("/articles/:id", HandleDeleteArticles) 75 | ``` 76 | 77 | 78 | 79 | ### 3.路径中对应资源用复数 80 | 81 | 就像我们上面这段代码,`articles`对于的是我们的文章资源,背后就是一张数据库表`articles`, 所以操作这个资源的应该都用复数形式。 82 | 83 | 84 | 85 | ### 4.次要资源可分层展示 86 | 87 | 一个博客系统中,最主要的应该是文章了,而评论应该是其子资源,我们可以评论嵌套在它的父资源后面,如: 88 | 89 | ``` go 90 | // 获取评论列表 91 | v1.GET("/articles/:articles_id/comments", HandleGetComments) 92 | // 添加评论 93 | v1.POST("/articles/:articles_id/comments", HandleAddComments) 94 | // 修改评论 95 | v1.PUT("/articles/:articles_id/comments/:id", HandleUpdateComments) 96 | // 删除评论 97 | v1.DELETE("/articles/:articles_id/comments/:id", HandleDeleteComments) 98 | ``` 99 | 100 | 那么,我们需要获取所有文章的评论怎么办?可以这么写: 101 | 102 | ``` go 103 | v1.GET("/articles/-/comments", HandleGetComments) 104 | ``` 105 | 106 | 但这也不是决对的,资源虽然有层级关系,但这种层级关系不宜太深,个人感觉两层最多了,如果超过,可以直接拿出来放在一级。 107 | 108 | 109 | 110 | ### 5.分页、排序、过滤 111 | 112 | 获取列表时,会使用到分页、排序过滤。一般: 113 | 114 | ``` sh 115 | ?page=1&page_size=10 # 指定页面page与分页大小page_size 116 | ?sort=-create_at,+author # 按照创建时间create_at降序,作者author升序排序 117 | ?title=helloworld # 按字段title搜索 118 | ``` 119 | 120 | 121 | 122 | ### 6.统一数据格式 123 | 124 | 不管是路径的格式,还是参数的格式,还是返回值的格式建议统一形式。 125 | 126 | 一般常用的格式有`蛇形`,`大驼峰`和`小驼峰`,个人比较喜欢`蛇形`。Anyway, 不管哪种,只要统一即可。 127 | 128 | 除了参数的命名统一外,返回的数据格式,最好统一,方便前端对接。 129 | 130 | 如下,我们定义`Resp`为通用返回数据结构,`Data`中存放反会的数据,如果出错,将错误信息放在`Error`中。 131 | 132 | ```go 133 | // Resp doc 134 | type Resp struct { 135 | Data interface{} `json:"data"` 136 | Error string `json:"error"` 137 | } 138 | 139 | // 登陆成功返回 140 | c.JSON(http.StatusOK, &Resp{Data: "login succeed"}) 141 | // 查询列表 142 | c.JSON(http.StatusOK, &Resp{Data: map[string]interface{}{ 143 | "result": tempStorage, 144 | "total": len(tempStorage), 145 | }}) 146 | // 参数错误 147 | c.JSON(http.StatusBadRequest, &Resp{Error: "parameters error"}) 148 | ``` 149 | 150 | 151 | 152 | ### 7.善用HTTP状态码 153 | 154 | HTTP状态码有很多,我们没有必要也不可能全部用上,常用如下: 155 | 156 | - 200 StatusOK - 只有成功请求都返回200。 157 | - 400 StatusBadRequest - 当出现参数不对,用户参数校验不通过时,给出该状态,并返回Error 158 | - 401 StatusUnauthorized - 没有登陆/经过认证 159 | - 403 Forbidden - 服务端拒绝授权(如密码错误),不允许访问 160 | - 404 Not Found - 路径不存在 161 | - 500 Internal Server Error - 所请求的服务器遇到意外的情况并阻止其执行请求 162 | - 502 Bad Gateway - 网关或代理从上游接收到了无效的响应 163 | - 503 Service Unavailable - 服务器尚未处于可以接受请求的状态 164 | 165 | 其中`502`,`503`,我们写程序时并不会明确去抛出。所以我们平常用6个状态码已经能很好的展示服务端状态了。 166 | 167 | 同时,我们将状态与返回值对应起来,`200`状态下,返回`Data`数据;其他状态返回`Error`。 168 | 169 | 170 | 171 | ### 8.API版本化 172 | 173 | 正如Demo中所示,我们将路由分组到了`/api/v1`路径下面,版本化API。如果后续的服务端升级,但可能仍有很大部分客户端请求未升级,依然请求老版本的API,那么我们只需要增加`/api/v2`,然后在该路径下为已升级的客户端提供服务。这样,我们就做到了API的版本控制,可以平滑的从一个版本切换到另外一个版本。 174 | 175 | ```go 176 | v1 := r.Group("/api/v1") 177 | { 178 | v1.POST("/login", HandleLogin) 179 | v1.GET("/articles", HandleGetArticles) 180 | v1.GET("/articles/:id/comments", HandleGetComments) 181 | // .... 182 | ``` 183 | 184 | 185 | 186 | ### 9. 统一 ‘/‘ 开头 187 | 188 | 所以路由中,路径都以’/‘开头,虽然框架会为我们做这件事,但还是建议统一加上。 189 | 190 | 191 | 192 | ### 10. 增加/更新操作 返回资源 193 | 194 | 对于`POST`,`PUT`操作,建议操作后,返回更新后的资源。 195 | 196 | 197 | 198 | ### 11. 使用HTTPS 199 | 200 | 对于暴露出去的接口/OpenAPI,一定使用HTTPS。一般时候,我们可以直接在服务前面架设一个WebServer,在WebServer内部署证书即可。当然,如果是直接由后端暴露出的接口,有必要直接在后端开启HTTPS! 201 | 202 | 203 | 204 | ### 12. 规范的API文档 205 | 206 | 对于我们这种前后端分离的架构,API文档是很重要。在Go中,我们很容易的能用swag结合代码注释自动生成API文档,在[ <使用swaggo自动生成Restful API文档>](https://razeencheng.com/post/go-swagger.html)中,我详细的介绍了怎么生成以及怎么写注释。 207 | 208 | 209 | 210 | ### 总结 211 | 212 | API写的好不好,重要的还是看是否遵循WEB标准和保持一致性,最终目的也是让这些API更清晰,易懂,安全,希望这些建议对你有所帮助。 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | -------------------------------------------------------------------------------- /swaggo-gin/handle.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | "strconv" 8 | 9 | "github.com/gin-gonic/contrib/sessions" 10 | "github.com/gin-gonic/gin" 11 | ) 12 | 13 | const isLoginKey = "is_login" 14 | 15 | // HandleAuth doc 16 | func HandleAuth(c *gin.Context) { 17 | session := sessions.Default(c) 18 | isLogin := session.Get(isLoginKey) 19 | if isLogin == nil || !isLogin.(bool) { 20 | c.JSON(http.StatusUnauthorized, gin.H{"msg": "please login"}) 21 | c.Abort() 22 | return 23 | } 24 | } 25 | 26 | // HandleHello doc 27 | // @Summary 测试SayHello 28 | // @Description 向你说Hello 29 | // @Tags 测试 30 | // @Accept mpfd 31 | // @Produce json 32 | // @Param who query string true "人名" 33 | // @Success 200 {string} string "{"msg": "hello Razeen"}" 34 | // @Failure 400 {string} string "{"msg": "who are you"}" 35 | // @Router /hello [get] 36 | func HandleHello(c *gin.Context) { 37 | who := c.Query("who") 38 | 39 | if who == "" { 40 | c.JSON(http.StatusBadRequest, gin.H{"msg": "who are u?"}) 41 | return 42 | } 43 | 44 | c.JSON(http.StatusOK, gin.H{"msg": "hello " + who}) 45 | } 46 | 47 | // HandleLogin doc 48 | // @Summary 登陆 49 | // @Tags 登陆 50 | // @Description 登入 51 | // @Accept mpfd 52 | // @Produce json 53 | // @Param user formData string true "用户名" default(admin) 54 | // @Param password formData string true "密码" 55 | // @Success 200 {string} string "{"msg":"login success"}" 56 | // @Failure 400 {string} string "{"msg": "user or password error"}" 57 | // @Router /login [post] 58 | func HandleLogin(c *gin.Context) { 59 | user := c.PostForm("user") 60 | pwd := c.PostForm("password") 61 | 62 | if user == "admin" && pwd == "123456" { 63 | session := sessions.Default(c) 64 | session.Set(isLoginKey, true) 65 | session.Save() 66 | c.JSON(http.StatusOK, gin.H{"msg": "login success"}) 67 | return 68 | } 69 | 70 | c.JSON(http.StatusUnauthorized, gin.H{"msg": "user or password error"}) 71 | } 72 | 73 | // HandleUpload doc 74 | // @Summary 上传文件 75 | // @Tags 文件处理 76 | // @Description 上传文件 77 | // @Accept mpfd 78 | // @Produce json 79 | // @Param file formData file true "文件" 80 | // @Success 200 {object} main.File 81 | // @Router /upload [post] 82 | func HandleUpload(c *gin.Context) { 83 | 84 | fileHeader, err := c.FormFile("file") 85 | if err != nil { 86 | c.JSON(http.StatusBadRequest, gin.H{"msg": err}) 87 | return 88 | } 89 | 90 | file, err := fileHeader.Open() 91 | if err != nil { 92 | c.JSON(http.StatusBadRequest, gin.H{"msg": err}) 93 | return 94 | } 95 | 96 | fileCon := make([]byte, 1<<20) 97 | n, err := file.Read(fileCon) 98 | if err != nil { 99 | if err != io.EOF { 100 | c.JSON(http.StatusBadRequest, gin.H{"msg": err}) 101 | return 102 | } 103 | } 104 | 105 | id++ 106 | f := &File{ID: id, Name: fileHeader.Filename, Len: int(fileHeader.Size), Content: fileCon[:n]} 107 | files.Files = append(files.Files, f) 108 | files.Len++ 109 | c.JSON(http.StatusOK, f) 110 | } 111 | 112 | var files = Files{Files: []*File{}} 113 | var id int 114 | 115 | // File doc 116 | type File struct { 117 | ID int `json:"id"` 118 | Name string `json:"name"` 119 | Len int `json:"len"` 120 | Content []byte `json:"-"` 121 | } 122 | 123 | // Files doc 124 | type Files struct { 125 | Files []*File `json:"files"` 126 | Len int `json:"len"` 127 | } 128 | 129 | // HandleList doc 130 | // @Summary 查看文件列表 131 | // @Tags 文件处理 132 | // @Description 文件列表 133 | // @Accept mpfd 134 | // @Produce json 135 | // @Success 200 {object} main.Files 136 | // @Router /list [get] 137 | func HandleList(c *gin.Context) { 138 | c.JSON(http.StatusOK, files) 139 | } 140 | 141 | // HandleGetFile doc 142 | // @Summary 获取某个文件 143 | // @Tags 文件处理 144 | // @Description 获取文件 145 | // @Accept mpfd 146 | // @Produce octet-stream 147 | // @Param id path integer true "文件ID" 148 | // @Success 200 {string} string "" 149 | // @Router /file/{id} [get] 150 | func HandleGetFile(c *gin.Context) { 151 | fid, err := strconv.Atoi(c.Param("id")) 152 | if err != nil { 153 | c.JSON(http.StatusBadRequest, gin.H{"msg": err}) 154 | return 155 | } 156 | 157 | for _, f := range files.Files { 158 | if f.ID == fid { 159 | c.Writer.WriteHeader(http.StatusOK) 160 | c.Header("Access-Control-Expose-Headers", "Content-Disposition") 161 | c.Header("Content-Disposition", "attachment; "+f.Name) 162 | c.Header("Content-Type", "application/octet-stream") 163 | c.Header("Accept-Length", fmt.Sprintf("%d", len(f.Content))) 164 | c.Writer.Write(f.Content) 165 | return 166 | } 167 | } 168 | 169 | c.JSON(http.StatusBadRequest, gin.H{"msg": "no avail file"}) 170 | } 171 | 172 | // JSONParams doc 173 | type JSONParams struct { 174 | // 这是一个字符串 175 | Str string `json:"str"` 176 | // 这是一个数字 177 | Int int `json:"int"` 178 | // 这是一个字符串数组 179 | Array []string `json:"array"` 180 | // 这是一个结构 181 | Struct struct { 182 | Field string `json:"field"` 183 | } `json:"struct"` 184 | } 185 | 186 | // HandleJSON doc 187 | // @Summary 获取JSON的示例 188 | // @Tags JSON 189 | // @Description 获取JSON的示例 190 | // @Accept json 191 | // @Produce json 192 | // @Param param body main.JSONParams true "需要上传的JSON" 193 | // @Success 200 {object} main.JSONParams "返回" 194 | // @Router /json [post] 195 | func HandleJSON(c *gin.Context) { 196 | param := JSONParams{} 197 | if err := c.BindJSON(¶m); err != nil { 198 | c.JSON(http.StatusBadRequest, gin.H{"msg": err.Error()}) 199 | return 200 | } 201 | 202 | c.JSON(http.StatusOK, param) 203 | } 204 | -------------------------------------------------------------------------------- /os-exec/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "context" 6 | "fmt" 7 | "log" 8 | "os/exec" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | func main() { 14 | 15 | _, err := exec.LookPath("./testcmd/testcmd") 16 | if err != nil { 17 | panic("可执行文件不可用 " + err.Error()) 18 | } 19 | 20 | fmt.Println("\nRun Test 1") 21 | test1() 22 | 23 | fmt.Println("\nRun Test 2") 24 | test2() 25 | 26 | fmt.Println("\nRun Test 3") 27 | test3() 28 | 29 | fmt.Println("\nRun Test 4") 30 | test4() 31 | 32 | fmt.Println("\nRun Test 5") 33 | test5() 34 | } 35 | 36 | // 持续输入 37 | func test5() { 38 | cmd := exec.Command("openssl") 39 | 40 | stdout, _ := cmd.StdoutPipe() 41 | stderr, _ := cmd.StderrPipe() 42 | 43 | stdin, _ := cmd.StdinPipe() 44 | 45 | cmd.Start() 46 | 47 | // 读 48 | var wg sync.WaitGroup 49 | wg.Add(3) 50 | go func() { 51 | defer wg.Done() 52 | for { 53 | buf := make([]byte, 1024) 54 | n, err := stderr.Read(buf) 55 | 56 | if n > 0 { 57 | fmt.Println(string(buf[:n])) 58 | } 59 | 60 | if n == 0 { 61 | break 62 | } 63 | 64 | if err != nil { 65 | log.Printf("read err %v", err) 66 | return 67 | } 68 | } 69 | }() 70 | 71 | go func() { 72 | defer wg.Done() 73 | for { 74 | buf := make([]byte, 1024) 75 | n, err := stdout.Read(buf) 76 | 77 | if n == 0 { 78 | break 79 | } 80 | 81 | if n > 0 { 82 | fmt.Println(string(buf[:n])) 83 | } 84 | 85 | if n == 0 { 86 | break 87 | } 88 | 89 | if err != nil { 90 | log.Printf("read out %v", err) 91 | return 92 | } 93 | 94 | } 95 | }() 96 | 97 | // 写 98 | go func() { 99 | stdin.Write([]byte("version\n\n")) 100 | stdin.Write([]byte("ciphers -v\n\n")) 101 | stdin.Write([]byte("s_client -connect razeencheng.com:443")) 102 | stdin.Close() 103 | wg.Done() 104 | }() 105 | 106 | wg.Wait() 107 | err := cmd.Wait() 108 | if err != nil { 109 | log.Printf("cmd wait %v", err) 110 | return 111 | } 112 | 113 | } 114 | 115 | // 通过上下文控制超时 116 | func test4() { 117 | 118 | ctx, calcel := context.WithTimeout(context.Background(), 2*time.Second) 119 | defer calcel() 120 | 121 | cmd := exec.CommandContext(ctx, "./testcmd/testcmd", "-s", "-e") 122 | 123 | stdout, _ := cmd.StdoutPipe() 124 | stderr, _ := cmd.StderrPipe() 125 | oReader := bufio.NewReader(stdout) 126 | eReader := bufio.NewReader(stderr) 127 | 128 | cmd.Start() 129 | 130 | go func() { 131 | for { 132 | line, err := oReader.ReadString('\n') 133 | 134 | if line != "" { 135 | log.Printf("read line %s", line) 136 | } 137 | 138 | if err != nil || line == "" { 139 | log.Printf("read line err %v", err) 140 | return 141 | } 142 | 143 | } 144 | }() 145 | 146 | go func() { 147 | for { 148 | line, err := eReader.ReadString('\n') 149 | 150 | if line != "" { 151 | log.Printf("read err %s", line) 152 | } 153 | 154 | if err != nil || line == "" { 155 | log.Printf("read err %v", err) 156 | return 157 | } 158 | 159 | } 160 | }() 161 | 162 | err := cmd.Wait() 163 | if err != nil { 164 | log.Printf("cmd wait %v", err) 165 | return 166 | } 167 | } 168 | 169 | // 按行读输出的内容 170 | func test3() { 171 | cmd := exec.Command("./testcmd/testcmd", "-s", "-e") 172 | stdout, _ := cmd.StdoutPipe() 173 | stderr, _ := cmd.StderrPipe() 174 | oReader := bufio.NewReader(stdout) 175 | eReader := bufio.NewReader(stderr) 176 | 177 | cmd.Start() 178 | 179 | go func() { 180 | for { 181 | line, err := oReader.ReadString('\n') 182 | 183 | if line != "" { 184 | log.Printf("read line %s", line) 185 | } 186 | 187 | if err != nil || line == "" { 188 | log.Printf("read line err %v", err) 189 | return 190 | } 191 | 192 | } 193 | }() 194 | 195 | go func() { 196 | for { 197 | line, err := eReader.ReadString('\n') 198 | 199 | if line != "" { 200 | log.Printf("read err %s", line) 201 | } 202 | 203 | if err != nil || line == "" { 204 | log.Printf("read err %v", err) 205 | return 206 | } 207 | 208 | } 209 | }() 210 | 211 | err := cmd.Wait() 212 | if err != nil { 213 | log.Printf("cmd wait %v", err) 214 | return 215 | } 216 | 217 | } 218 | 219 | // stdout & stderr 分开输出 220 | func test2() { 221 | cmd := exec.Command("./testcmd/testcmd", "-s", "-e") 222 | stdout, _ := cmd.StdoutPipe() 223 | stderr, _ := cmd.StderrPipe() 224 | cmd.Start() 225 | 226 | go func() { 227 | for { 228 | buf := make([]byte, 1024) 229 | n, err := stderr.Read(buf) 230 | 231 | if n > 0 { 232 | log.Printf("read err %s", string(buf[:n])) 233 | } 234 | 235 | if n == 0 { 236 | break 237 | } 238 | 239 | if err != nil { 240 | log.Printf("read err %v", err) 241 | return 242 | } 243 | } 244 | }() 245 | 246 | go func() { 247 | for { 248 | buf := make([]byte, 1024) 249 | n, err := stdout.Read(buf) 250 | 251 | if n == 0 { 252 | break 253 | } 254 | 255 | if n > 0 { 256 | log.Printf("read out %s", string(buf[:n])) 257 | 258 | } 259 | 260 | if n == 0 { 261 | break 262 | } 263 | 264 | if err != nil { 265 | log.Printf("read out %v", err) 266 | return 267 | } 268 | 269 | } 270 | }() 271 | 272 | err := cmd.Wait() 273 | if err != nil { 274 | log.Printf("cmd wait %v", err) 275 | return 276 | } 277 | 278 | } 279 | 280 | // 简单执行 281 | func test1() { 282 | cmd := exec.Command("./testcmd/testcmd", "-s") 283 | 284 | // 使用CombinedOutput 将stdout stderr合并输出 285 | out, err := cmd.CombinedOutput() 286 | if err != nil { 287 | log.Printf("test1 failed %s\n", err) 288 | } 289 | log.Println("test1 output ", string(out)) 290 | } 291 | -------------------------------------------------------------------------------- /grpc/demo1/README.md: -------------------------------------------------------------------------------- 1 | # [gRPC在Go中的使用(一)Protocol Buffers语法与相关使用](https://razeen.me/post/how-to-use-grpc-in-golang-01.html) 2 | 3 | Desc:protobuf语法介绍,怎么写proto文件,grpc的使用入门 4 | 5 | 在gRPC官网用了一句话来介绍:“一个高性能、开源的通用RPC框架”,同时介绍了其四大特点: 6 | 7 | * 定义简单 8 | * 支持多种编程语言多种平台 9 | * 快速启动和缩放 10 | * 双向流媒体和集成身份验证 11 | 12 | 13 | 14 | 在`gRPC在go中使用`系列中,关于其简介与性能我就不多介绍,相信在社区也有很多关于这些的讨论。这里我主要从三个层次来总结我以往在Go中使用gRPC的一些经验,主要分为: 15 | 16 | 1. Protocol Buffers语法与相关使用 17 | 2. gRPC实现简单通讯 18 | 3. gRPC服务认证与双向流通讯 19 | 20 | **注:下面Protocol Buffers简写protobuf.* 21 | 22 | 23 | 这篇我们先介绍protobuf的相关语法、怎么书写`.proto`文件以及go代码生成。 24 | 25 | ### 简介 26 | 27 | 要熟练的使用GRPC,protobuf的熟练使用必不可少。 28 | 29 | gRPC使用[protobuf](https://github.com/google/protobuf)来定义服务。protobuf是由Google开发的一种数据序列化协议,可以把它想象成是XML或JSON格式,但更小,更快更简洁。而且一次定义,可生成多种语言的代码。 30 | 31 | ### 定义 32 | 33 | 首先我们需要编写一些`.proto`文件,定义我们在程序中需要处理的结构化数据。我们直接从一个实例开始讲起,下面是一个proto文件: 34 | 35 | 36 | ``` protobuf 37 | syntax = "proto3"; 38 | 39 | option go_package = "github.com/razeencheng/demo-go/grpc/demo1/helloworld"; 40 | 41 | package helloworld; 42 | 43 | import "github.com/golang/protobuf/ptypes/any/any.proto"; 44 | 45 | message HelloWorldRequest { 46 | string greeting = 1; 47 | map infos = 2; 48 | } 49 | 50 | message HelloWorldResponse { 51 | string reply = 1; 52 | repeated google.protobuf.Any details = 2; 53 | } 54 | 55 | service HelloWorldService { 56 | rpc SayHelloWorld(HelloWorldRequest) returns (HelloWorldResponse){} 57 | } 58 | ``` 59 | 60 | 61 | #### 版本 62 | 63 | 文件的开头`syntax="proto3"`也就指明版本,主要有`proto2`与`proto3`,他们在语法上有一定的差异,我这里主要使用的是后者。 64 | 65 | #### 包名 66 | 67 | 第二行,指定生成go文件的包名,可选项,默认使用第三行包名。 68 | 69 | 第三行,包名。 70 | 71 | #### 导包 72 | 73 | 第四行,类似你写go一样,protobuf也可以导入其他的包。 74 | 75 | #### 消息定义 76 | 77 | 后面message开头的两个结构就是我们需要传递的消息类型。所有的消息类型都是以`message`开始,然后定义类型名称。结构内字段的定义为`字段规则 字段类型 字段名=字段编号` 78 | 79 | - 字段规则主要有 `singular`和`repeated`。如其中`greeting`和`reply`的字段规则为`singular`,允许该消息中出现0个或1个该字段(但不能超过一个),而像`details`字段允许重复任意次数。其实对应到go里面也就是基本类型和切片类型。 80 | - 字段类型,下表是proto内类型与go类型的对应表。 81 | 82 | | .proto Type | Notes | Go Type | 83 | | ----------- | ------------------------------------------------------------ | ------- | 84 | | double | | float64 | 85 | | float | | float32 | 86 | | int32 | 使用可变长度编码。 无效编码负数 - 如果您的字段可能具有负值, 请改用sint32。 | int32 | 87 | | int64 | 使用可变长度编码。 无效编码负数 - 如果您的字段可能具有负值,请改用sint64。 | int64 | 88 | | uint32 | 使用可变长度编码。 | uint32 | 89 | | uint64 | 使用可变长度编码。 | uint64 | 90 | | sint32 | 使用可变长度编码。 带符号的int值。 这些比常规的int32更有效地编码负数。 | int32 | 91 | | sint64 | 使用可变长度编码。 带符号的int值。 这些比常规的int64更有效地编码负数。 | int64 | 92 | | fixed32 | 总是四个字节。 如果值通常大于228,则比uint32效率更高。 | uint32 | 93 | | fixed64 | 总是八个字节。 如果值通常大于256,则会比uint64更高效。 | uint64 | 94 | | sfixed32 | 总是四个字节。 | int32 | 95 | | sfixed64 | 总是八个字节。 | int64 | 96 | | bool | | bool | 97 | | string | 字符串必须始终包含UTF-8编码或7位ASCII文本。 | string | 98 | | bytes | 可能包含任何字节序列。 | []byte | 99 | 100 | 看到这里你也许会疑惑,go里面的切片,map,接口等类型我怎么定义呢?别急,下面一一替你解答。 101 | 102 | 1.map类型,`HelloWorldRequest`的`infos`就是一个map类型,它的结构为`map map_field = N` 但是在使用的时候你需要注意map类型不能`repetead`。 103 | 104 | 2.切片类型,我们直接定义其规则为`repeated`就可以了。就像`HelloWorldResponse`中的`details`字段一样,它就是一个切片类型。那么你会问了它是什么类型的切片?这就看下面了~ 105 | 106 | 3.接口类型在proto中没有直接实现,但在[google/protobuf/any.proto](https://github.com/golang/protobuf/blob/master/ptypes/any/any.proto)中定义了一个`google.protobuf.Any`类型,然后结合[protobuf/go](https://github.com/golang/protobuf/blob/master/ptypes/any.go)也算是曲线救国了~ 107 | 108 | - 字段编号 109 | 110 | 最后的1,2代表的是每个字段在该消息中的唯一标签,在与消息二进制格式中标识这些字段,而且当你的消息在使用的时候该值不能改变。1到15都是用一个字节编码的,通常用于标签那些频繁发生修改的字段。16到2047用两个字节编码,最大的是2^29-1(536,870,911),其中19000-19999为预留的,你也不可使用。 111 | 112 | 113 | 114 | #### 服务定义 115 | 116 | 如果你要使用RPC(远程过程调用)系统的消息类型,那就需要定义RPC服务接口,protobuf编译器将会根据所选择的不同语言生成服务接口代码及存根。就如: 117 | 118 | ``` 119 | service HelloWorldService { 120 | rpc SayHelloWorld(HelloWorldRequest) returns (HelloWorldResponse){} 121 | } 122 | ``` 123 | 124 | protobuf编译器将产生一个抽象接口`HelloWorldService`以及一个相应的存根实现。存根将所有的调用指向RpcChannel(SayHelloWorld),它是一个抽象接口,必须在RPC系统中对该接口进行实现。具体如何使用,将在下一篇博客中详细介绍。 125 | 126 | 127 | ### 生成Go代码 128 | 129 | #### 安装protoc 130 | 131 | 首先要安装`protoc`,可直接到[这里](https://github.com/google/protobuf/releases/tag/v3.0.0)下载二进制安装到 `$PATH`里面,也可以直接下载源码编译。除此之外,你还需要安装go的proto插件`protoc-gen-go`。 132 | 133 | ```Go 134 | // mac terminal 135 | go get -u github.com/golang/protobuf/{proto,protoc-gen-go} 136 | // win powershell 137 | go get -u github.com/golang/protobuf/proto 138 | go get -u github.com/golang/protobuf/protoc-gen-go 139 | ``` 140 | 141 | #### 生成go代码 142 | 143 | 接下来,使用`protoc`命令即可生成。 144 | 145 | ```Bash 146 | ### mac terminal 147 | protoc -I ${GOPATH}/src --go_out=plugins=grpc:${GOPATH}/src ${GOPATH}/src/github.com/razeencheng/demo-go/grpc/demo1/helloworld/hello_world.proto 148 | ### win powershell 149 | protoc -I $env:GOPATH\src --go_out=plugins=grpc:$env:GOPATH\src $env:GOPATH\src\github.com\razeencheng\demo-go\grpc\demo1\helloworld\hello_world.proto 150 | ``` 151 | 152 | 如上所示 `-I`指定搜索proto文件的目录,`--go_out=plugins=grpc:`指定生成go代码的文件夹,后面就是需要生成的proto文件路径。 153 | 154 | > *注意:* 如果你使用到了其他包的结构,`-I`需要将该资源包括在内。 155 | > 156 | > 例如我导入了`github.com/golang/protobuf/ptypes/any/any.proto` 我首先需要 157 | > 158 | > `go get -u github.com/golang/protobuf`获取该包,然后在使用时资源路径(`-I`)直接为`GOPATH\src`。 159 | 160 | 最后生成的`hello-world.pb.go`文件。内容大概如下图所示,点[这里](https://github.com/razeencheng/demo-go/blob/master/grpc/demo1/helloworld/hello_world.pb.go)可查看全部。 161 | 162 | 163 | 164 | 165 | 166 | 图中我们可以看到两个`message`对应生成了两个结构体,同时会生成一些序列化的方法等。 167 | 168 | 169 | 170 | 171 | 172 | 而定义的`service`则是生成了对应的`client`与`server`接口,那么这到底有什么用?怎么去用呢?[下一篇博客](https://razeen.me/post/how-to-use-grpc-in-golang-02.html)将为你详细讲解~ 173 | 174 | 175 | 看到这,我们简单的了解一下protobuf语法,如果你想了解更多,点[这里](https://developers.google.com/protocol-buffers/docs/proto3)看官方文档。 176 | -------------------------------------------------------------------------------- /restful-api/go.sum: -------------------------------------------------------------------------------- 1 | github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff h1:RmdPFa+slIr4SCBg4st/l/vZWVe9QJKMXGO60Bxbe04= 2 | github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw= 3 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 8 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 9 | github.com/gin-gonic/contrib v0.0.0-20201101042839-6a891bf89f19 h1:J2LPEOcQmWaooBnBtUDV9KHFEnP5LYTZY03GiQ0oQBw= 10 | github.com/gin-gonic/contrib v0.0.0-20201101042839-6a891bf89f19/go.mod h1:iqneQ2Df3omzIVTkIfn7c1acsVnMGiSLn4XF5Blh3Yg= 11 | github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8= 12 | github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= 13 | github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= 14 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 15 | github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= 16 | github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= 17 | github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= 18 | github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= 19 | github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw= 20 | github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= 21 | github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM= 22 | github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 23 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 24 | github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0= 25 | github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= 26 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 27 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 28 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 29 | github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= 30 | github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= 31 | github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= 32 | github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= 33 | github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= 34 | github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= 35 | github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= 36 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 37 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 38 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 39 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 40 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 41 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 42 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 43 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 44 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 45 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 46 | github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= 47 | github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= 48 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= 49 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 50 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 51 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 52 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 53 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 54 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 55 | github.com/pelletier/go-toml/v2 v2.0.2 h1:+jQXlF3scKIcSEKkdHzXhCTDLPFi5r1wnK6yPS+49Gw= 56 | github.com/pelletier/go-toml/v2 v2.0.2/go.mod h1:MovirKjgVRESsAvNZlAjtFwV867yGuwRkXbG66OzopI= 57 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 58 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 59 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 60 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 61 | github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= 62 | github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= 63 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 64 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 65 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 66 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 67 | github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= 68 | github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= 69 | github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= 70 | github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= 71 | github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= 72 | golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 73 | golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM= 74 | golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 75 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 76 | golang.org/x/net v0.0.0-20220621193019-9d032be2e588 h1:9ubFuySsnAJYGyJrZ3koiEv8FyqofCBdz3G9Mbf2YFc= 77 | golang.org/x/net v0.0.0-20220621193019-9d032be2e588/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 78 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 79 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 80 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 81 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 82 | golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 83 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= 84 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 85 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 86 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 87 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 88 | golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= 89 | golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= 90 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 91 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 92 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 93 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 94 | google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= 95 | google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 96 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 97 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 98 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 99 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 100 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 101 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 102 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 103 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 104 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 105 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 106 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 107 | -------------------------------------------------------------------------------- /grpc/demo1/helloworld/hello_world.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: github.com/razeencheng/demo-go/grpc/demo1/helloworld/hello_world.proto 3 | 4 | package helloworld // import "github.com/razeencheng/demo-go/grpc/demo1/helloworld" 5 | 6 | import proto "github.com/golang/protobuf/proto" 7 | import fmt "fmt" 8 | import math "math" 9 | import any "github.com/golang/protobuf/ptypes/any" 10 | 11 | import ( 12 | context "golang.org/x/net/context" 13 | grpc "google.golang.org/grpc" 14 | ) 15 | 16 | // Reference imports to suppress errors if they are not otherwise used. 17 | var _ = proto.Marshal 18 | var _ = fmt.Errorf 19 | var _ = math.Inf 20 | 21 | // This is a compile-time assertion to ensure that this generated file 22 | // is compatible with the proto package it is being compiled against. 23 | // A compilation error at this line likely means your copy of the 24 | // proto package needs to be updated. 25 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package 26 | 27 | type HelloWorldRequest struct { 28 | Greeting string `protobuf:"bytes,1,opt,name=greeting,proto3" json:"greeting,omitempty"` 29 | Infos map[string]string `protobuf:"bytes,2,rep,name=infos,proto3" json:"infos,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` 30 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 31 | XXX_unrecognized []byte `json:"-"` 32 | XXX_sizecache int32 `json:"-"` 33 | } 34 | 35 | func (m *HelloWorldRequest) Reset() { *m = HelloWorldRequest{} } 36 | func (m *HelloWorldRequest) String() string { return proto.CompactTextString(m) } 37 | func (*HelloWorldRequest) ProtoMessage() {} 38 | func (*HelloWorldRequest) Descriptor() ([]byte, []int) { 39 | return fileDescriptor_hello_world_5bbefe74e8f48f56, []int{0} 40 | } 41 | func (m *HelloWorldRequest) XXX_Unmarshal(b []byte) error { 42 | return xxx_messageInfo_HelloWorldRequest.Unmarshal(m, b) 43 | } 44 | func (m *HelloWorldRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 45 | return xxx_messageInfo_HelloWorldRequest.Marshal(b, m, deterministic) 46 | } 47 | func (dst *HelloWorldRequest) XXX_Merge(src proto.Message) { 48 | xxx_messageInfo_HelloWorldRequest.Merge(dst, src) 49 | } 50 | func (m *HelloWorldRequest) XXX_Size() int { 51 | return xxx_messageInfo_HelloWorldRequest.Size(m) 52 | } 53 | func (m *HelloWorldRequest) XXX_DiscardUnknown() { 54 | xxx_messageInfo_HelloWorldRequest.DiscardUnknown(m) 55 | } 56 | 57 | var xxx_messageInfo_HelloWorldRequest proto.InternalMessageInfo 58 | 59 | func (m *HelloWorldRequest) GetGreeting() string { 60 | if m != nil { 61 | return m.Greeting 62 | } 63 | return "" 64 | } 65 | 66 | func (m *HelloWorldRequest) GetInfos() map[string]string { 67 | if m != nil { 68 | return m.Infos 69 | } 70 | return nil 71 | } 72 | 73 | type HelloWorldResponse struct { 74 | Reply string `protobuf:"bytes,1,opt,name=reply,proto3" json:"reply,omitempty"` 75 | Details []*any.Any `protobuf:"bytes,2,rep,name=details,proto3" json:"details,omitempty"` 76 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 77 | XXX_unrecognized []byte `json:"-"` 78 | XXX_sizecache int32 `json:"-"` 79 | } 80 | 81 | func (m *HelloWorldResponse) Reset() { *m = HelloWorldResponse{} } 82 | func (m *HelloWorldResponse) String() string { return proto.CompactTextString(m) } 83 | func (*HelloWorldResponse) ProtoMessage() {} 84 | func (*HelloWorldResponse) Descriptor() ([]byte, []int) { 85 | return fileDescriptor_hello_world_5bbefe74e8f48f56, []int{1} 86 | } 87 | func (m *HelloWorldResponse) XXX_Unmarshal(b []byte) error { 88 | return xxx_messageInfo_HelloWorldResponse.Unmarshal(m, b) 89 | } 90 | func (m *HelloWorldResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 91 | return xxx_messageInfo_HelloWorldResponse.Marshal(b, m, deterministic) 92 | } 93 | func (dst *HelloWorldResponse) XXX_Merge(src proto.Message) { 94 | xxx_messageInfo_HelloWorldResponse.Merge(dst, src) 95 | } 96 | func (m *HelloWorldResponse) XXX_Size() int { 97 | return xxx_messageInfo_HelloWorldResponse.Size(m) 98 | } 99 | func (m *HelloWorldResponse) XXX_DiscardUnknown() { 100 | xxx_messageInfo_HelloWorldResponse.DiscardUnknown(m) 101 | } 102 | 103 | var xxx_messageInfo_HelloWorldResponse proto.InternalMessageInfo 104 | 105 | func (m *HelloWorldResponse) GetReply() string { 106 | if m != nil { 107 | return m.Reply 108 | } 109 | return "" 110 | } 111 | 112 | func (m *HelloWorldResponse) GetDetails() []*any.Any { 113 | if m != nil { 114 | return m.Details 115 | } 116 | return nil 117 | } 118 | 119 | func init() { 120 | proto.RegisterType((*HelloWorldRequest)(nil), "helloworld.HelloWorldRequest") 121 | proto.RegisterMapType((map[string]string)(nil), "helloworld.HelloWorldRequest.InfosEntry") 122 | proto.RegisterType((*HelloWorldResponse)(nil), "helloworld.HelloWorldResponse") 123 | } 124 | 125 | // Reference imports to suppress errors if they are not otherwise used. 126 | var _ context.Context 127 | var _ grpc.ClientConn 128 | 129 | // This is a compile-time assertion to ensure that this generated file 130 | // is compatible with the grpc package it is being compiled against. 131 | const _ = grpc.SupportPackageIsVersion4 132 | 133 | // HelloWorldServiceClient is the client API for HelloWorldService service. 134 | // 135 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. 136 | type HelloWorldServiceClient interface { 137 | SayHelloWorld(ctx context.Context, in *HelloWorldRequest, opts ...grpc.CallOption) (*HelloWorldResponse, error) 138 | } 139 | 140 | type helloWorldServiceClient struct { 141 | cc *grpc.ClientConn 142 | } 143 | 144 | func NewHelloWorldServiceClient(cc *grpc.ClientConn) HelloWorldServiceClient { 145 | return &helloWorldServiceClient{cc} 146 | } 147 | 148 | func (c *helloWorldServiceClient) SayHelloWorld(ctx context.Context, in *HelloWorldRequest, opts ...grpc.CallOption) (*HelloWorldResponse, error) { 149 | out := new(HelloWorldResponse) 150 | err := c.cc.Invoke(ctx, "/helloworld.HelloWorldService/SayHelloWorld", in, out, opts...) 151 | if err != nil { 152 | return nil, err 153 | } 154 | return out, nil 155 | } 156 | 157 | // HelloWorldServiceServer is the server API for HelloWorldService service. 158 | type HelloWorldServiceServer interface { 159 | SayHelloWorld(context.Context, *HelloWorldRequest) (*HelloWorldResponse, error) 160 | } 161 | 162 | func RegisterHelloWorldServiceServer(s *grpc.Server, srv HelloWorldServiceServer) { 163 | s.RegisterService(&_HelloWorldService_serviceDesc, srv) 164 | } 165 | 166 | func _HelloWorldService_SayHelloWorld_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 167 | in := new(HelloWorldRequest) 168 | if err := dec(in); err != nil { 169 | return nil, err 170 | } 171 | if interceptor == nil { 172 | return srv.(HelloWorldServiceServer).SayHelloWorld(ctx, in) 173 | } 174 | info := &grpc.UnaryServerInfo{ 175 | Server: srv, 176 | FullMethod: "/helloworld.HelloWorldService/SayHelloWorld", 177 | } 178 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 179 | return srv.(HelloWorldServiceServer).SayHelloWorld(ctx, req.(*HelloWorldRequest)) 180 | } 181 | return interceptor(ctx, in, info, handler) 182 | } 183 | 184 | var _HelloWorldService_serviceDesc = grpc.ServiceDesc{ 185 | ServiceName: "helloworld.HelloWorldService", 186 | HandlerType: (*HelloWorldServiceServer)(nil), 187 | Methods: []grpc.MethodDesc{ 188 | { 189 | MethodName: "SayHelloWorld", 190 | Handler: _HelloWorldService_SayHelloWorld_Handler, 191 | }, 192 | }, 193 | Streams: []grpc.StreamDesc{}, 194 | Metadata: "github.com/razeencheng/demo-go/grpc/demo1/helloworld/hello_world.proto", 195 | } 196 | 197 | func init() { 198 | proto.RegisterFile("github.com/razeencheng/demo-go/grpc/demo1/helloworld/hello_world.proto", fileDescriptor_hello_world_5bbefe74e8f48f56) 199 | } 200 | 201 | var fileDescriptor_hello_world_5bbefe74e8f48f56 = []byte{ 202 | // 316 bytes of a gzipped FileDescriptorProto 203 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x51, 0x41, 0x4b, 0xf3, 0x40, 204 | 0x10, 0xfd, 0xd2, 0xd2, 0x4f, 0x1d, 0x11, 0x74, 0xe9, 0xa1, 0x04, 0x94, 0xd2, 0x53, 0x2f, 0xee, 205 | 0x62, 0x15, 0x29, 0x1e, 0x04, 0x05, 0x45, 0x6f, 0x92, 0x1e, 0x84, 0x5e, 0x24, 0x4d, 0xa7, 0xdb, 206 | 0xe0, 0x76, 0x27, 0x6e, 0x36, 0x95, 0xf8, 0x8f, 0xfc, 0x97, 0xb2, 0x59, 0x63, 0x0a, 0xa2, 0x07, 207 | 0x0f, 0x81, 0x79, 0x33, 0xef, 0xcd, 0x7b, 0x99, 0x85, 0x5b, 0x99, 0xda, 0x65, 0x31, 0xe3, 0x09, 208 | 0xad, 0x84, 0x89, 0xdf, 0x10, 0x75, 0xb2, 0x44, 0x2d, 0xc5, 0x1c, 0x57, 0x74, 0x2c, 0x49, 0x48, 209 | 0x93, 0x25, 0x15, 0x38, 0x11, 0x4b, 0x54, 0x8a, 0x5e, 0xc9, 0xa8, 0xb9, 0x2f, 0x9f, 0xaa, 0x9a, 210 | 0x67, 0x86, 0x2c, 0x31, 0x68, 0xa6, 0xa1, 0xd8, 0xd8, 0x29, 0x49, 0xc5, 0x5a, 0x8a, 0x8a, 0x34, 211 | 0x2b, 0x16, 0x22, 0xb3, 0x65, 0x86, 0xb9, 0x88, 0x75, 0xe9, 0x3e, 0x2f, 0x1e, 0xbc, 0x07, 0x70, 212 | 0x70, 0xe7, 0xf4, 0x8f, 0x4e, 0x1f, 0xe1, 0x4b, 0x81, 0xb9, 0x65, 0x21, 0x6c, 0x4b, 0x83, 0x68, 213 | 0x53, 0x2d, 0x7b, 0x41, 0x3f, 0x18, 0xee, 0x44, 0x5f, 0x98, 0x5d, 0x42, 0x27, 0xd5, 0x0b, 0xca, 214 | 0x7b, 0xad, 0x7e, 0x7b, 0xb8, 0x3b, 0x1a, 0xf2, 0xc6, 0x9e, 0x7f, 0xdb, 0xc4, 0xef, 0x1d, 0xf5, 215 | 0x46, 0x5b, 0x53, 0x46, 0x5e, 0x16, 0x8e, 0x01, 0x9a, 0x26, 0xdb, 0x87, 0xf6, 0x33, 0x96, 0x9f, 216 | 0x26, 0xae, 0x64, 0x5d, 0xe8, 0xac, 0x63, 0x55, 0x60, 0xaf, 0x55, 0xf5, 0x3c, 0xb8, 0x68, 0x8d, 217 | 0x83, 0xc1, 0x14, 0xd8, 0xa6, 0x41, 0x9e, 0x91, 0xce, 0xd1, 0xf1, 0x0d, 0x66, 0xaa, 0xde, 0xe1, 218 | 0x01, 0xe3, 0xb0, 0x35, 0x47, 0x1b, 0xa7, 0xaa, 0xce, 0xd9, 0xe5, 0x92, 0x48, 0x2a, 0xe4, 0xf5, 219 | 0x3d, 0xf8, 0x95, 0x2e, 0xa3, 0x9a, 0x34, 0xc2, 0xcd, 0x33, 0x4c, 0xd0, 0xac, 0xd3, 0x04, 0xd9, 220 | 0x03, 0xec, 0x4d, 0xe2, 0xb2, 0xe9, 0xb3, 0xc3, 0x5f, 0x7f, 0x36, 0x3c, 0xfa, 0x69, 0xec, 0xa3, 221 | 0x0e, 0xfe, 0x5d, 0x9f, 0x4f, 0xcf, 0xfe, 0xf2, 0xea, 0xb3, 0xff, 0x55, 0xea, 0xd3, 0x8f, 0x00, 222 | 0x00, 0x00, 0xff, 0xff, 0x38, 0x6b, 0xfd, 0x41, 0x34, 0x02, 0x00, 0x00, 223 | } 224 | -------------------------------------------------------------------------------- /swaggo-gin/docs/swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemes": [ 3 | "http", 4 | "https" 5 | ], 6 | "swagger": "2.0", 7 | "info": { 8 | "description": "# Test Example Makedown\n\n### 关于使用说明\n\n吧啦吧啦吧啦\n\n![](https://camo.githubusercontent.com/4752126ebe1c5fe33cd179136fcbcf55e2074c8bacf90c378231256df809d687/68747470733a2f2f62616467652e667572792e696f2f67682f676f2d73776167676572253246676f2d737761676765722e737667)", 9 | "title": "Swagger Example API", 10 | "termsOfService": "https://razeen.me", 11 | "contact": { 12 | "name": "Razeen", 13 | "url": "https://razeen.me", 14 | "email": "me@razeen.me" 15 | }, 16 | "license": { 17 | "name": "Apache 2.0", 18 | "url": "http://www.apache.org/licenses/LICENSE-2.0.html" 19 | }, 20 | "version": "1.0" 21 | }, 22 | "host": "127.0.0.1:8080", 23 | "basePath": "/api/v1", 24 | "paths": { 25 | "/file/{id}": { 26 | "get": { 27 | "description": "获取文件", 28 | "consumes": [ 29 | "multipart/form-data" 30 | ], 31 | "produces": [ 32 | "application/octet-stream" 33 | ], 34 | "tags": [ 35 | "文件处理" 36 | ], 37 | "summary": "获取某个文件", 38 | "parameters": [ 39 | { 40 | "type": "integer", 41 | "description": "文件ID", 42 | "name": "id", 43 | "in": "path", 44 | "required": true 45 | } 46 | ], 47 | "responses": { 48 | "200": { 49 | "description": "OK", 50 | "schema": { 51 | "type": "string" 52 | } 53 | } 54 | } 55 | } 56 | }, 57 | "/hello": { 58 | "get": { 59 | "description": "向你说Hello", 60 | "consumes": [ 61 | "multipart/form-data" 62 | ], 63 | "produces": [ 64 | "application/json" 65 | ], 66 | "tags": [ 67 | "测试" 68 | ], 69 | "summary": "测试SayHello", 70 | "parameters": [ 71 | { 72 | "type": "string", 73 | "description": "人名", 74 | "name": "who", 75 | "in": "query", 76 | "required": true 77 | } 78 | ], 79 | "responses": { 80 | "200": { 81 | "description": "{\"msg\": \"hello Razeen\"}", 82 | "schema": { 83 | "type": "string" 84 | } 85 | }, 86 | "400": { 87 | "description": "{\"msg\": \"who are you\"}", 88 | "schema": { 89 | "type": "string" 90 | } 91 | } 92 | } 93 | } 94 | }, 95 | "/json": { 96 | "post": { 97 | "description": "获取JSON的示例", 98 | "consumes": [ 99 | "application/json" 100 | ], 101 | "produces": [ 102 | "application/json" 103 | ], 104 | "tags": [ 105 | "JSON" 106 | ], 107 | "summary": "获取JSON的示例", 108 | "parameters": [ 109 | { 110 | "description": "需要上传的JSON", 111 | "name": "param", 112 | "in": "body", 113 | "required": true, 114 | "schema": { 115 | "$ref": "#/definitions/main.JSONParams" 116 | } 117 | } 118 | ], 119 | "responses": { 120 | "200": { 121 | "description": "返回", 122 | "schema": { 123 | "$ref": "#/definitions/main.JSONParams" 124 | } 125 | } 126 | } 127 | } 128 | }, 129 | "/list": { 130 | "get": { 131 | "description": "文件列表", 132 | "consumes": [ 133 | "multipart/form-data" 134 | ], 135 | "produces": [ 136 | "application/json" 137 | ], 138 | "tags": [ 139 | "文件处理" 140 | ], 141 | "summary": "查看文件列表", 142 | "responses": { 143 | "200": { 144 | "description": "OK", 145 | "schema": { 146 | "$ref": "#/definitions/main.Files" 147 | } 148 | } 149 | } 150 | } 151 | }, 152 | "/login": { 153 | "post": { 154 | "description": "登入", 155 | "consumes": [ 156 | "multipart/form-data" 157 | ], 158 | "produces": [ 159 | "application/json" 160 | ], 161 | "tags": [ 162 | "登陆" 163 | ], 164 | "summary": "登陆", 165 | "parameters": [ 166 | { 167 | "type": "string", 168 | "default": "admin", 169 | "description": "用户名", 170 | "name": "user", 171 | "in": "formData", 172 | "required": true 173 | }, 174 | { 175 | "type": "string", 176 | "description": "密码", 177 | "name": "password", 178 | "in": "formData", 179 | "required": true 180 | } 181 | ], 182 | "responses": { 183 | "200": { 184 | "description": "{\"msg\":\"login success\"}", 185 | "schema": { 186 | "type": "string" 187 | } 188 | }, 189 | "400": { 190 | "description": "{\"msg\": \"user or password error\"}", 191 | "schema": { 192 | "type": "string" 193 | } 194 | } 195 | } 196 | } 197 | }, 198 | "/upload": { 199 | "post": { 200 | "description": "上传文件", 201 | "consumes": [ 202 | "multipart/form-data" 203 | ], 204 | "produces": [ 205 | "application/json" 206 | ], 207 | "tags": [ 208 | "文件处理" 209 | ], 210 | "summary": "上传文件", 211 | "parameters": [ 212 | { 213 | "type": "file", 214 | "description": "文件", 215 | "name": "file", 216 | "in": "formData", 217 | "required": true 218 | } 219 | ], 220 | "responses": { 221 | "200": { 222 | "description": "OK", 223 | "schema": { 224 | "$ref": "#/definitions/main.File" 225 | } 226 | } 227 | } 228 | } 229 | } 230 | }, 231 | "definitions": { 232 | "main.File": { 233 | "type": "object", 234 | "properties": { 235 | "id": { 236 | "type": "integer" 237 | }, 238 | "len": { 239 | "type": "integer" 240 | }, 241 | "name": { 242 | "type": "string" 243 | } 244 | } 245 | }, 246 | "main.Files": { 247 | "type": "object", 248 | "properties": { 249 | "files": { 250 | "type": "array", 251 | "items": { 252 | "$ref": "#/definitions/main.File" 253 | } 254 | }, 255 | "len": { 256 | "type": "integer" 257 | } 258 | } 259 | }, 260 | "main.JSONParams": { 261 | "type": "object", 262 | "properties": { 263 | "array": { 264 | "description": "这是一个字符串数组", 265 | "type": "array", 266 | "items": { 267 | "type": "string" 268 | } 269 | }, 270 | "int": { 271 | "description": "这是一个数字", 272 | "type": "integer" 273 | }, 274 | "str": { 275 | "description": "这是一个字符串", 276 | "type": "string" 277 | }, 278 | "struct": { 279 | "description": "这是一个结构", 280 | "type": "object", 281 | "properties": { 282 | "field": { 283 | "type": "string" 284 | } 285 | } 286 | } 287 | } 288 | } 289 | }, 290 | "tags": [ 291 | { 292 | "name": "TestTag1", 293 | "externalDocs": { 294 | "description": "This is my blog site", 295 | "url": "https://razeen.me" 296 | } 297 | } 298 | ], 299 | "x-example-key": { 300 | "key": "value" 301 | } 302 | } -------------------------------------------------------------------------------- /swaggo-gin/docs/docs.go: -------------------------------------------------------------------------------- 1 | // Package docs GENERATED BY SWAG; DO NOT EDIT 2 | // This file was generated by swaggo/swag 3 | package docs 4 | 5 | import "github.com/swaggo/swag" 6 | 7 | const docTemplate = `{ 8 | "schemes": {{ marshal .Schemes }}, 9 | "swagger": "2.0", 10 | "info": { 11 | "description": "{{escape .Description}}", 12 | "title": "{{.Title}}", 13 | "termsOfService": "https://razeen.me", 14 | "contact": { 15 | "name": "Razeen", 16 | "url": "https://razeen.me", 17 | "email": "me@razeen.me" 18 | }, 19 | "license": { 20 | "name": "Apache 2.0", 21 | "url": "http://www.apache.org/licenses/LICENSE-2.0.html" 22 | }, 23 | "version": "{{.Version}}" 24 | }, 25 | "host": "{{.Host}}", 26 | "basePath": "{{.BasePath}}", 27 | "paths": { 28 | "/file/{id}": { 29 | "get": { 30 | "description": "获取文件", 31 | "consumes": [ 32 | "multipart/form-data" 33 | ], 34 | "produces": [ 35 | "application/octet-stream" 36 | ], 37 | "tags": [ 38 | "文件处理" 39 | ], 40 | "summary": "获取某个文件", 41 | "parameters": [ 42 | { 43 | "type": "integer", 44 | "description": "文件ID", 45 | "name": "id", 46 | "in": "path", 47 | "required": true 48 | } 49 | ], 50 | "responses": { 51 | "200": { 52 | "description": "OK", 53 | "schema": { 54 | "type": "string" 55 | } 56 | } 57 | } 58 | } 59 | }, 60 | "/hello": { 61 | "get": { 62 | "description": "向你说Hello", 63 | "consumes": [ 64 | "multipart/form-data" 65 | ], 66 | "produces": [ 67 | "application/json" 68 | ], 69 | "tags": [ 70 | "测试" 71 | ], 72 | "summary": "测试SayHello", 73 | "parameters": [ 74 | { 75 | "type": "string", 76 | "description": "人名", 77 | "name": "who", 78 | "in": "query", 79 | "required": true 80 | } 81 | ], 82 | "responses": { 83 | "200": { 84 | "description": "{\"msg\": \"hello Razeen\"}", 85 | "schema": { 86 | "type": "string" 87 | } 88 | }, 89 | "400": { 90 | "description": "{\"msg\": \"who are you\"}", 91 | "schema": { 92 | "type": "string" 93 | } 94 | } 95 | } 96 | } 97 | }, 98 | "/json": { 99 | "post": { 100 | "description": "获取JSON的示例", 101 | "consumes": [ 102 | "application/json" 103 | ], 104 | "produces": [ 105 | "application/json" 106 | ], 107 | "tags": [ 108 | "JSON" 109 | ], 110 | "summary": "获取JSON的示例", 111 | "parameters": [ 112 | { 113 | "description": "需要上传的JSON", 114 | "name": "param", 115 | "in": "body", 116 | "required": true, 117 | "schema": { 118 | "$ref": "#/definitions/main.JSONParams" 119 | } 120 | } 121 | ], 122 | "responses": { 123 | "200": { 124 | "description": "返回", 125 | "schema": { 126 | "$ref": "#/definitions/main.JSONParams" 127 | } 128 | } 129 | } 130 | } 131 | }, 132 | "/list": { 133 | "get": { 134 | "description": "文件列表", 135 | "consumes": [ 136 | "multipart/form-data" 137 | ], 138 | "produces": [ 139 | "application/json" 140 | ], 141 | "tags": [ 142 | "文件处理" 143 | ], 144 | "summary": "查看文件列表", 145 | "responses": { 146 | "200": { 147 | "description": "OK", 148 | "schema": { 149 | "$ref": "#/definitions/main.Files" 150 | } 151 | } 152 | } 153 | } 154 | }, 155 | "/login": { 156 | "post": { 157 | "description": "登入", 158 | "consumes": [ 159 | "multipart/form-data" 160 | ], 161 | "produces": [ 162 | "application/json" 163 | ], 164 | "tags": [ 165 | "登陆" 166 | ], 167 | "summary": "登陆", 168 | "parameters": [ 169 | { 170 | "type": "string", 171 | "default": "admin", 172 | "description": "用户名", 173 | "name": "user", 174 | "in": "formData", 175 | "required": true 176 | }, 177 | { 178 | "type": "string", 179 | "description": "密码", 180 | "name": "password", 181 | "in": "formData", 182 | "required": true 183 | } 184 | ], 185 | "responses": { 186 | "200": { 187 | "description": "{\"msg\":\"login success\"}", 188 | "schema": { 189 | "type": "string" 190 | } 191 | }, 192 | "400": { 193 | "description": "{\"msg\": \"user or password error\"}", 194 | "schema": { 195 | "type": "string" 196 | } 197 | } 198 | } 199 | } 200 | }, 201 | "/upload": { 202 | "post": { 203 | "description": "上传文件", 204 | "consumes": [ 205 | "multipart/form-data" 206 | ], 207 | "produces": [ 208 | "application/json" 209 | ], 210 | "tags": [ 211 | "文件处理" 212 | ], 213 | "summary": "上传文件", 214 | "parameters": [ 215 | { 216 | "type": "file", 217 | "description": "文件", 218 | "name": "file", 219 | "in": "formData", 220 | "required": true 221 | } 222 | ], 223 | "responses": { 224 | "200": { 225 | "description": "OK", 226 | "schema": { 227 | "$ref": "#/definitions/main.File" 228 | } 229 | } 230 | } 231 | } 232 | } 233 | }, 234 | "definitions": { 235 | "main.File": { 236 | "type": "object", 237 | "properties": { 238 | "id": { 239 | "type": "integer" 240 | }, 241 | "len": { 242 | "type": "integer" 243 | }, 244 | "name": { 245 | "type": "string" 246 | } 247 | } 248 | }, 249 | "main.Files": { 250 | "type": "object", 251 | "properties": { 252 | "files": { 253 | "type": "array", 254 | "items": { 255 | "$ref": "#/definitions/main.File" 256 | } 257 | }, 258 | "len": { 259 | "type": "integer" 260 | } 261 | } 262 | }, 263 | "main.JSONParams": { 264 | "type": "object", 265 | "properties": { 266 | "array": { 267 | "description": "这是一个字符串数组", 268 | "type": "array", 269 | "items": { 270 | "type": "string" 271 | } 272 | }, 273 | "int": { 274 | "description": "这是一个数字", 275 | "type": "integer" 276 | }, 277 | "str": { 278 | "description": "这是一个字符串", 279 | "type": "string" 280 | }, 281 | "struct": { 282 | "description": "这是一个结构", 283 | "type": "object", 284 | "properties": { 285 | "field": { 286 | "type": "string" 287 | } 288 | } 289 | } 290 | } 291 | } 292 | }, 293 | "tags": [ 294 | { 295 | "name": "TestTag1", 296 | "externalDocs": { 297 | "description": "This is my blog site", 298 | "url": "https://razeen.me" 299 | } 300 | } 301 | ], 302 | "x-example-key": { 303 | "key": "value" 304 | } 305 | }` 306 | 307 | // SwaggerInfo holds exported Swagger Info so clients can modify it 308 | var SwaggerInfo = &swag.Spec{ 309 | Version: "1.0", 310 | Host: "127.0.0.1:8080", 311 | BasePath: "/api/v1", 312 | Schemes: []string{"http", "https"}, 313 | Title: "Swagger Example API", 314 | Description: "# Test Example Makedown\n\n### 关于使用说明\n\n吧啦吧啦吧啦\n\n![](https://camo.githubusercontent.com/4752126ebe1c5fe33cd179136fcbcf55e2074c8bacf90c378231256df809d687/68747470733a2f2f62616467652e667572792e696f2f67682f676f2d73776167676572253246676f2d737761676765722e737667)", 315 | InfoInstanceName: "swagger", 316 | SwaggerTemplate: docTemplate, 317 | } 318 | 319 | func init() { 320 | swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) 321 | } 322 | -------------------------------------------------------------------------------- /gin/go.sum: -------------------------------------------------------------------------------- 1 | github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff h1:RmdPFa+slIr4SCBg4st/l/vZWVe9QJKMXGO60Bxbe04= 2 | github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw= 3 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/gin-contrib/size v0.0.0-20220501091047-44dc10afe2e0 h1:sIBZ780MIR5kGBTF40N21lAPDE+dqZbcQhcvJh1rbus= 8 | github.com/gin-contrib/size v0.0.0-20220501091047-44dc10afe2e0/go.mod h1:TNclj+D/YdCIzSgy+uog3TrPw30GpZjgcF/LXS3qpoA= 9 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 10 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 11 | github.com/gin-gonic/contrib v0.0.0-20201101042839-6a891bf89f19 h1:J2LPEOcQmWaooBnBtUDV9KHFEnP5LYTZY03GiQ0oQBw= 12 | github.com/gin-gonic/contrib v0.0.0-20201101042839-6a891bf89f19/go.mod h1:iqneQ2Df3omzIVTkIfn7c1acsVnMGiSLn4XF5Blh3Yg= 13 | github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= 14 | github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8= 15 | github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= 16 | github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= 17 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 18 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= 19 | github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= 20 | github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= 21 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= 22 | github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= 23 | github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= 24 | github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= 25 | github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0= 26 | github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= 27 | github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM= 28 | github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 29 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 30 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 31 | github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0= 32 | github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= 33 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 34 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 35 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 36 | github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= 37 | github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= 38 | github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= 39 | github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= 40 | github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= 41 | github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= 42 | github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= 43 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 44 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 45 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 46 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 47 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 48 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 49 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 50 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 51 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 52 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 53 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 54 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= 55 | github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= 56 | github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= 57 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 58 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= 59 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 60 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= 61 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 62 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 63 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 64 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 65 | github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU= 66 | github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= 67 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 68 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 69 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 70 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 71 | github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= 72 | github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= 73 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 74 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 75 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 76 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 77 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 78 | github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= 79 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 80 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= 81 | github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= 82 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= 83 | github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= 84 | github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= 85 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 86 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 87 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI= 88 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 89 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 90 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= 91 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 92 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 93 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 94 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 95 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 96 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 97 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 98 | golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 99 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= 100 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 101 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 102 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 103 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 104 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 105 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 106 | golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= 107 | golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= 108 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 109 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 110 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 111 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 112 | google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= 113 | google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 114 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 115 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 116 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 117 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 118 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 119 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 120 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 121 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 122 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 123 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 124 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= 125 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 126 | -------------------------------------------------------------------------------- /gomod/README.md: -------------------------------------------------------------------------------- 1 | # [Go学习笔记(十)老项目迁移 go module 大型灾难记录](https://razeencheng.com/post/accidents-of-migrating-to-go-modules.html) 2 | 3 | 4 | 5 | 最近在改造一个比较早期的一个项目,其中就涉及到用将原来 `Vendor` 管理依赖换成 `Go Modules` 来管理。 然而过程真是一波三折,在这里总结一下此次 `Go Modules` 改造中遇到的问题,以及解决方法。 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | ### 背景 14 | 15 | - go version: 16 | 17 | ```bash 18 | $ go version 19 | go version go1.16.5 darwin/amd64 20 | ``` 21 | 22 | - 简化的 demo 如下, 很 “简单” 我们只要把 `hello world` 输出即可。 23 | 24 | ``` go 25 | package main 26 | 27 | import ( 28 | "github.com/coreos/etcd/pkg/transport" 29 | "github.com/google/certificate-transparency-go/tls" 30 | "github.com/qiniu/api.v7/auth/qbox" 31 | "go.etcd.io/etcd/clientv3" 32 | "google.golang.org/grpc" 33 | "qiniupkg.com/x/log.v7" 34 | ) 35 | 36 | func main() { 37 | 38 | _ = transport.TLSInfo{} 39 | 40 | _ = clientv3.WatchResponse{} 41 | 42 | _, _ = clientv3.New(clientv3.Config{}) 43 | 44 | _ = qbox.NewMac("", "") 45 | 46 | _ = tls.DigitallySigned{} 47 | 48 | _ = grpc.ClientConn{} 49 | 50 | log.Info("hello world") 51 | } 52 | ``` 53 | 54 | 55 | 56 | ### 实战 57 | 58 | 直接初始化,并 tidy 一下。 59 | 60 | ```bash 61 | $ go mod init demo-go/gomod 62 | go: creating new go.mod: module demo-go/gomod 63 | go: to add module requirements and sums: 64 | go mod tidy 65 | 66 | $ go mod tidy 67 | go: finding module for ... 68 | demo-go/gomod imports 69 | qiniupkg.com/x/log.v7: module qiniupkg.com/x@latest found (v1.11.5), but does not contain package qiniupkg.com/x/log.v7 70 | demo-go/gomod imports 71 | github.com/qiniu/api.v7/auth/qbox imports 72 | github.com/qiniu/x/bytes.v7/seekable: module github.com/qiniu/x@latest found (v1.11.5), but does not contain package github.com/qiniu/x/bytes.v7/seekable 73 | demo-go/gomod imports 74 | go.etcd.io/etcd/clientv3 imports 75 | github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context: package github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context provided by github.com/coreos/etcd at latest version v2.3.8+incompatible but not at required version v3.3.10+incompatible 76 | demo-go/gomod imports 77 | go.etcd.io/etcd/clientv3 imports 78 | github.com/coreos/etcd/Godeps/_workspace/src/google.golang.org/grpc: package github.com/coreos/etcd/Godeps/_workspace/src/google.golang.org/grpc provided by github.com/coreos/etcd at latest version v2.3.8+incompatible but not at required version v3.3.10+incompatible 79 | demo-go/gomod imports 80 | go.etcd.io/etcd/clientv3 imports 81 | github.com/coreos/etcd/Godeps/_workspace/src/google.golang.org/grpc/credentials: package github.com/coreos/etcd/Godeps/_workspace/src/google.golang.org/grpc/credentials provided by github.com/coreos/etcd at latest version v2.3.8+incompatible but not at required version v3.3.10+incompatible 82 | demo-go/gomod imports 83 | go.etcd.io/etcd/clientv3 imports 84 | github.com/coreos/etcd/storage/storagepb: package github.com/coreos/etcd/storage/storagepb provided by github.com/coreos/etcd at latest version v2.3.8+incompatible but not at required version v3.3.10+incompatible 85 | ``` 86 | 87 | 88 | 89 | 好家伙,报错了。我们先看到前两行 90 | 91 | 1. `qiniupkg.com/x@latest` 中没有 `qiniupkg.com/x/log.v7`; 92 | 2. `github.com/qiniu/x@latest` 中没有 `github.com/qiniu/x/bytes.v7/seekable`; 93 | 94 | 这看起来应该是一个问题, `qiniupkg.com/x` 和`github.com/qiniu/x` 应该是同一个包,不同镜像。于是我到 Github 看一下 `@lastet` 版本的代码,确实没有`bytes.v7` 包了。人肉查找,最后在 `v1.7.8` 版本,我们找到了 `bytes.v7` 包。 95 | 96 | 97 | 98 | 于是,我们可以指定一下版本。 99 | 100 | ```bash 101 | go mod edit -replace qiniupkg.com/x=qiniupkg.com/x@v1.7.8 102 | go mod edit -replace github.com/qiniu/x=github.com/qiniu/x@v1.7.8 103 | ``` 104 | 105 | 106 | 107 | 继续往下看,接下来的几个问题是一类的,都是`etcd`导致的。 108 | 109 | 意思是 `go.etcd.io/etcd/clientv3` 导入了 `github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context`, 同时 `github.com/coreos/etcd@v2.3.8` 中 提供了 `github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context` 。 但是,我们这里需要 `github.com/coreos/etcd@v3.3.10`, 而该版本并不提供 `github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context` 。 110 | 111 | 我们直接更新 etcd 到的 `v3.3.10` 试试。 112 | 113 | ```bash 114 | go mod edit -replace go.etcd.io/etcd=go.etcd.io/etcd@v3.3.20+incompatible 115 | ``` 116 | 117 | 118 | 119 | 120 | 121 | 我们再 ` go mod tidy` 下。 122 | 123 | ```bash 124 | $ go mod tidy 125 | go: demo-go/gomod imports 126 | go.etcd.io/etcd/clientv3 tested by 127 | go.etcd.io/etcd/clientv3.test imports 128 | github.com/coreos/etcd/auth imports 129 | github.com/coreos/etcd/mvcc/backend imports 130 | github.com/coreos/bbolt: github.com/coreos/bbolt@v1.3.6: parsing go.mod: 131 | module declares its path as: go.etcd.io/bbolt 132 | but was required as: github.com/coreos/bbolt 133 | ``` 134 | 135 | 这个错误和鸟窝这篇 [Etcd使用go module的灾难](https://colobu.com/2020/04/09/accidents-of-etcd-and-go-module/)一致,`go.etcd.io/bbolt` 和 `github.com/coreos/bbolt` 包名不一致,我们替换一下。 136 | 137 | ``` bash 138 | go mod edit -replace github.com/coreos/bbolt@v1.3.6=go.etcd.io/bbolt@v1.3.6 139 | ``` 140 | 141 | 142 | 143 | 144 | 145 | 继续,`go mod tidy` 一下。 146 | 147 | ```bash 148 | $ go mod tidy 149 | ... 150 | demo-go/gomod imports 151 | go.etcd.io/etcd/clientv3 imports 152 | github.com/coreos/etcd/clientv3/balancer: module github.com/coreos/etcd@latest found (v2.3.8+incompatible), but does not contain package github.com/coreos/etcd/clientv3/balancer 153 | demo-go/gomod imports 154 | go.etcd.io/etcd/clientv3 imports 155 | github.com/coreos/etcd/clientv3/balancer/picker: module github.com/coreos/etcd@latest found (v2.3.8+incompatible), but does not contain package github.com/coreos/etcd/clientv3/balancer/picker 156 | demo-go/gomod imports 157 | go.etcd.io/etcd/clientv3 imports 158 | github.com/coreos/etcd/clientv3/balancer/resolver/endpoint: module github.com/coreos/etcd@latest found (v2.3.8+incompatible), but does not contain package github.com/coreos/etcd/clientv3/balancer/resolver/endpoint 159 | demo-go/gomod imports 160 | go.etcd.io/etcd/clientv3 imports 161 | github.com/coreos/etcd/clientv3/credentials: module github.com/coreos/etcd@latest found (v2.3.8+incompatible), but does not contain package github.com/coreos/etcd/clientv3/credentials 162 | demo-go/gomod imports 163 | go.etcd.io/etcd/clientv3 tested by 164 | go.etcd.io/etcd/clientv3.test imports 165 | github.com/coreos/etcd/integration imports 166 | github.com/coreos/etcd/proxy/grpcproxy imports 167 | google.golang.org/grpc/naming: module google.golang.org/grpc@latest found (v1.39.0), but does not contain package google.golang.org/grpc/naming 168 | ``` 169 | 170 | 好家伙,又是`etcd`。 仔细一看,我们导入了`github.com/coreos/etcd` 和 `go.etcd.io/etcd` 两个版本`etcd`, 我们前面只替换了一个。现在我们把另外一个也替换了。 171 | 172 | ```bash 173 | go mod edit -replace github.com/coreos/etcd=github.com/coreos/etcd@v3.3.20+incompatible 174 | ``` 175 | 176 | 177 | 178 | 179 | 180 | 再`go mod tidy`下,这个错误没有了,但还有个`grpc`的错误,继续找原因。原来是` google.golang.org/grpc` `v1.39.0` 版本没有` google.golang.org/grpc/naming` 包。 上 Github 仓库, 找了一下历史版本,`v1.29.1`上是有这个包的,我们继续替换。 181 | 182 | ```bash 183 | go mod edit -replace google.golang.org/grpc=google.golang.org/grpc@v1.29.1 184 | ``` 185 | 186 | 187 | 188 | 189 | 190 | 这下,终于,`go mod tidy`通过了,可以开心的输出`hello world` 了。 191 | 192 | 193 | 194 | 195 | 196 | 然而, 197 | 198 | ``` bash 199 | $ go run main.go 200 | # github.com/coreos/etcd/clientv3/balancer/resolver/endpoint 201 | ../../../go/pkg/mod/github.com/coreos/etcd@v3.3.20+incompatible/clientv3/balancer/resolver/endpoint/endpoint.go:114:78: undefined: resolver.BuildOption 202 | ../../../go/pkg/mod/github.com/coreos/etcd@v3.3.20+incompatible/clientv3/balancer/resolver/endpoint/endpoint.go:182:31: undefined: resolver.ResolveNowOption 203 | # github.com/coreos/etcd/clientv3/balancer/picker 204 | ../../../go/pkg/mod/github.com/coreos/etcd@v3.3.20+incompatible/clientv3/balancer/picker/err.go:37:44: undefined: balancer.PickOptions 205 | ../../../go/pkg/mod/github.com/coreos/etcd@v3.3.20+incompatible/clientv3/balancer/picker/roundrobin_balanced.go:55:54: undefined: balancer.PickOptions 206 | ``` 207 | 208 | 意不意外,惊不惊喜!! 209 | 210 | 原来`etcd`包依赖了`grpc`的`resolver`包,但我导入的`v1.29.1`版本的`grpc`是没有这个包的。到 `grpc`[仓库](https://github.com/grpc/grpc-go/blob/v1.26.0/resolver/resolver.go) 挨个版本看了一下,确实只有`v1.26.0`版本才声明了`type BuildOption` 。于是,我们再次使用替换大法。 211 | 212 | ```bash 213 | go mod edit -replace google.golang.org/grpc=google.golang.org/grpc@v1.26.0 214 | ``` 215 | 216 | 217 | 218 | 再次`tidy`, 运行! 终于,看到了久违的`hello world!` 219 | 220 | ```bash 221 | $ go run main.go 222 | 2021/07/20 12:27:09.642431 [INFO] /Users/razeen/wspace/github/demo-go/gomod/main.go:26: hello world 223 | ``` 224 | 225 | 226 | 227 | 228 | 229 | ### 总结 230 | 231 | #### 项目规范 232 | 233 | 现在我们回过头看下这个 demo 项目,其实很有问题。 234 | 235 | ``` go 236 | "github.com/coreos/etcd/pkg/transport" 237 | "github.com/google/certificate-transparency-go/tls" 238 | "github.com/qiniu/api.v7/auth/qbox" 239 | "go.etcd.io/etcd/clientv3" 240 | "google.golang.org/grpc" 241 | "qiniupkg.com/x/log.v7" 242 | ``` 243 | 244 | `etcd` 和 `qiniupkg`的包完全可以统一,只导入一种!而且,后来我们发现`log.v7`这包也是意外导入的.... 245 | 246 | 这也是在改造我们一些老的项目时遇到的问题,以前用`vendor` `go get` 没有注意到这些问题,这是需要提前规范的。 247 | 248 | 249 | 250 | #### 看懂 `go.mod` 251 | 252 | 我们来简单看一下,经历各种坎坷后,得出的`go.mod` 文件。 253 | 254 | ``` go 255 | module demo-go/gomod 256 | 257 | go 1.16 258 | 259 | replace qiniupkg.com/x => qiniupkg.com/x v1.7.8 260 | 261 | replace github.com/qiniu/x => github.com/qiniu/x v1.7.8 262 | 263 | replace go.etcd.io/etcd => go.etcd.io/etcd v3.3.20+incompatible 264 | 265 | replace github.com/coreos/bbolt v1.3.6 => go.etcd.io/bbolt v1.3.6 266 | 267 | replace github.com/coreos/etcd => github.com/coreos/etcd v3.3.20+incompatible 268 | 269 | replace google.golang.org/grpc => google.golang.org/grpc v1.26.0 270 | 271 | require ( 272 | github.com/coreos/bbolt v1.3.6 // indirect 273 | github.com/coreos/etcd v3.3.10+incompatible 274 | github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect 275 | github.com/google/certificate-transparency-go v1.1.1 276 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect 277 | github.com/qiniu/api.v7 v7.2.5+incompatible 278 | github.com/qiniu/x v0.0.0-00010101000000-000000000000 // indirect 279 | github.com/soheilhy/cmux v0.1.5 // indirect 280 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect 281 | go.etcd.io/etcd v0.0.0-20200513171258-e048e166ab9c 282 | google.golang.org/grpc v1.29.1 283 | qiniupkg.com/x v0.0.0-00010101000000-000000000000 284 | sigs.k8s.io/yaml v1.2.0 // indirect 285 | ) 286 | ``` 287 | 288 | 289 | 290 | 我们先看一个常见的这几个[指令](https://golang.org/ref/mod#go-mod-file-module), 291 | 292 | - `module` 定义主模块的路径; 293 | - `go` 编写该`mod`文件时的go版本; 294 | - `require` 声明给定模块依赖项的最低要求版本; 295 | - `replace` 手动指定的依赖模块 (可以替换全部的版本、指定的版本、本地的版本等等 ); 296 | 297 | 298 | 299 | 还有就是 `v3.3.20+incompatible` 后面的 `+incompatible` , 这是指兼容的版本,指依赖库的版本是`v2` 或以上,但`go.mod`和 依赖库路径 没有按照官方指定的方式命名,会加上这个。 300 | 301 | 302 | 303 | `v0.0.0-00010101000000-000000000000` 这是一个伪版本,在和 不兼容 module 或 标记的版本不可用的时候,回打上这个伪版本。 304 | 305 | 306 | 307 | `// indirect` 这指明这些不是我们直接引用的依赖。 308 | 309 | 310 | 311 | 除此之外,以下指令也可了解一下。 312 | 313 | ``` bash 314 | # 查看当前模块以及所有的依赖模块 315 | go list -m all 316 | 317 | # 查看某个模块的以及打标签的版本 318 | go list -m -versions go.etcd.io/etcd 319 | 320 | # 升级特定的包 321 | go get xx@version 升级特定的包 322 | 323 | # 了解为什么需要模块 324 | go mod why -m all 325 | 326 | # 为什么需要指定(google.golang.org/grpc)的模块 327 | go mod why -m google.golang.org/grpc 328 | ``` 329 | 330 | 331 | 332 | 更多可以细读[官方文档](https://golang.org/ref/mod#incompatible-versions),感谢阅读。 333 | 334 | 335 | 336 | 337 | 338 | ### 参考 339 | 340 | - [Using Go Modules](https://blog.golang.org/using-go-modules) 341 | - [Minimal Version Selection](https://research.swtch.com/vgo-mvs) 342 | - [跳出Go module的泥潭](https://colobu.com/2018/08/27/learn-go-module/) 343 | - [Etcd使用go module的灾难](https://colobu.com/2020/04/09/accidents-of-etcd-and-go-module/) 344 | - [浅谈Go Modules原理](https://duyanghao.github.io/golang-module/) 345 | 346 | -------------------------------------------------------------------------------- /os-exec/README.md: -------------------------------------------------------------------------------- 1 | [#Go学习笔记(八) | 使用 os/exec 执行命令](https://razeencheng.com/post/simple-use-go-exec-command.html) 2 | 3 | 用Go去调用一些外部的命令其实很愉快的,这遍文章就总结一下我自己日常用的比较多的几种方法。 4 | 5 | 6 | 7 | ### 关于Unix标准输入输出 8 | 9 | 在具体聊`os/exec`的使用前,了解一下shell的标准输出是很有必要的。 10 | 11 | 我们平常会用到或看到这样的命令: 12 | 13 | ```shell 14 | $ ls xxx 1>out.txt 2>&1 15 | $ nohup xxx 2>&1 & 16 | ``` 17 | 18 | 你知道这里`1,2`含义么? 19 | 20 | 21 | 22 | 其实这里的`1,2`指的就是Unix文件描述符。文件描述符其实就一数字,每一个文件描述符代表的都是一个文件。如果你打开100个文件,你就会获取到100个文件描述符。 23 | 24 | 25 | 26 | 这里需要注意的一点就是,在Unix中[**一切皆文件**]()。当然,这里我们不必去深究,我们需要知道的是`1,2`代表的是标准输出`stdout`与标准错误输出`stderr`。还有`0`代表标准输入`stdin`。 27 | 28 | 29 | 30 | 在`os/exec`中就用到了`Stdin`,`Stdout`,`Stderr`,这些基本Unix知识或能帮助我们更好理解这些参数。 31 | 32 | 33 | 34 | ### os/exec 35 | 36 | `os/exec`包内容并不多,我们大概过一下。 37 | 38 | 1. [LookPath(file string) (string, error)](https://godoc.org/os/exec#LookPath) 39 | 40 | 寻找可执行文件路径,如果你指定的可执行文件在`$PATH`中,就会返回这个可执行文件的相对/绝对路径;如果你指定的是一个文件路径,他就是去判断文件是否可读取/执行,返回的是一样的路径。 41 | 42 | 在我们需要使用一些外部命令/可执行文件的时候,我们可以先使用该函数判断一下该命令/可执行文件是否有效。 43 | 44 | 2. [Command(name string, arg ...string) *Cmd](https://godoc.org/os/exec#Command) 45 | 46 | 使用你输入的参数,返回Cmd指针,可用于执行Cmd的方法。 47 | 48 | 这里`name`就是我们的命令/可执行文件,后面的参数可以一个一个输入。 49 | 50 | 3. [CommandContext(ctx context.Context, name string, arg ...string) *Cmd](https://godoc.org/os/exec#CommandContext) 51 | 52 | 和上面功能一样,不过我们可以用上下文做一些超时等控制。 53 | 54 | 4. 之后几个就是Cmd的一些方法。 55 | 56 | - [(c *Cmd) CombinedOutput() ([]byte, error)](https://godoc.org/os/exec#Cmd.CombinedOutput) 将标准输出,错误输出一起返回; 57 | 58 | - [(c *Cmd) Output() ([]byte, error)](https://godoc.org/os/exec#Cmd.Output) 输出标准输出,错误从error返回; 59 | 60 | - [(c *Cmd) Run() error](https://godoc.org/os/exec#Cmd.Run) 执行任务,等待执行完成; 61 | 62 | - [(c *Cmd) Start() error](https://godoc.org/os/exec#Cmd.Start), [(c *Cmd) Wait() error](https://godoc.org/os/exec#Cmd.Wait) 前者执行任务,不等待完成,用后者等待,并释放资源; 63 | 64 | - [(c *Cmd) StderrPipe() (io.ReadCloser, error)](https://godoc.org/os/exec#Cmd.StderrPipe) 65 | 66 | [(c *Cmd) StdinPipe() (io.WriteCloser, error)](https://godoc.org/os/exec#Cmd.StdinPipe) 67 | 68 | [(c *Cmd) StdoutPipe() (io.ReadCloser, error)](https://godoc.org/os/exec#Cmd.StdoutPipe) 69 | 70 | 这三个功能类似,就是提供一个标准输入/输出/错误输出的管道,我们可用这些管道中去输入输出。 71 | 72 | 其实读完,结合官方的一些example,使用很简单,下面具体写几个场景。 73 | 74 | 75 | 76 | *注* 77 | 78 | 1. 本文全部的Demo在[这里](https://github.com/razeencheng/demo-go/tree/master/os-exec)。 79 | 80 | 2. `./testcmd/testcmd`是我用Go写的一个简单的可执行文件,可以根据指定的参数 输出/延时输出/输出错误,方便我们演示。如下 81 | 82 | ```go 83 | func main() { 84 | 85 | var ( 86 | start bool 87 | e bool 88 | ) 89 | 90 | flag.BoolVar(&start, "s", false, "start output") 91 | flag.BoolVar(&e, "e", false, "output err") 92 | flag.Parse() 93 | 94 | if start { 95 | for i := 5; i > 0; i-- { 96 | fmt.Fprintln(os.Stdout, "test cmd output", i) 97 | time.Sleep(1 * time.Second) 98 | } 99 | } 100 | 101 | if e { 102 | fmt.Fprintln(os.Stderr, "a err occur") 103 | os.Exit(1) 104 | } 105 | 106 | fmt.Fprintln(os.Stdout, "test cmd stdout") 107 | } 108 | ``` 109 | 110 | 111 | 112 | ### 简单执行 113 | 114 | ```go 115 | // 简单执行 116 | func test1() { 117 | cmd := exec.Command("./testcmd/testcmd", "-s") 118 | 119 | // 使用CombinedOutput 将stdout stderr合并输出 120 | out, err := cmd.CombinedOutput() 121 | if err != nil { 122 | log.Printf("test1 failed %s\n", err) 123 | } 124 | log.Println("test1 output ", string(out)) 125 | } 126 | ``` 127 | 128 | 输出: 129 | 130 | ```shell 131 | Run Test 1 132 | 2019/06/06 18:02:39 test1 output test cmd output 5 133 | test cmd output 4 134 | test cmd output 3 135 | test cmd output 2 136 | test cmd output 1 137 | done 138 | ``` 139 | 140 | 整个过程等待5秒,所有结果一次输出。 141 | 142 | 143 | 144 | ### 分离标准输出与错误输出 145 | 146 | 将错误分开输出,同时开了两个协成,同步的接收命令的输出内容。 147 | 148 | ```go 149 | // stdout & stderr 分开输出 150 | func test2() { 151 | cmd := exec.Command("./testcmd/testcmd", "-s", "-e") 152 | stdout, _ := cmd.StdoutPipe() 153 | stderr, _ := cmd.StderrPipe() 154 | cmd.Start() 155 | 156 | go func() { 157 | for { 158 | buf := make([]byte, 1024) 159 | n, err := stderr.Read(buf) 160 | 161 | if n > 0 { 162 | log.Printf("read err %s", string(buf[:n])) 163 | } 164 | 165 | if n == 0 { 166 | break 167 | } 168 | 169 | if err != nil { 170 | log.Printf("read err %v", err) 171 | return 172 | } 173 | } 174 | }() 175 | 176 | go func() { 177 | for { 178 | buf := make([]byte, 1024) 179 | n, err := stdout.Read(buf) 180 | 181 | if n == 0 { 182 | break 183 | } 184 | 185 | if n > 0 { 186 | log.Printf("read out %s", string(buf[:n])) 187 | 188 | } 189 | 190 | if n == 0 { 191 | break 192 | } 193 | 194 | if err != nil { 195 | log.Printf("read out %v", err) 196 | return 197 | } 198 | 199 | } 200 | }() 201 | 202 | err := cmd.Wait() 203 | if err != nil { 204 | log.Printf("cmd wait %v", err) 205 | return 206 | } 207 | } 208 | ``` 209 | 210 | 输出: 211 | 212 | ```shell 213 | Run Test 2 214 | 2019/06/06 18:02:39 read out test cmd output 5 215 | 2019/06/06 18:02:40 read out test cmd output 4 216 | 2019/06/06 18:02:41 read out test cmd output 3 217 | 2019/06/06 18:02:42 read out test cmd output 2 218 | 2019/06/06 18:02:43 read out test cmd output 1 219 | 2019/06/06 18:02:44 read err a err occur 220 | 2019/06/06 18:02:44 cmd wait exit status 1 221 | ``` 222 | 223 | 224 | 225 | ### 按行读取输出内容 226 | 227 | 使用`bufio`按行读取输出内容。 228 | 229 | ``` go 230 | // 按行读输出的内容 231 | func test3() { 232 | cmd := exec.Command("./testcmd/testcmd", "-s", "-e") 233 | stdout, _ := cmd.StdoutPipe() 234 | stderr, _ := cmd.StderrPipe() 235 | oReader := bufio.NewReader(stdout) 236 | eReader := bufio.NewReader(stderr) 237 | 238 | cmd.Start() 239 | 240 | go func() { 241 | for { 242 | line, err := oReader.ReadString('\n') 243 | 244 | if line != "" { 245 | log.Printf("read line %s", line) 246 | } 247 | 248 | if err != nil || line == "" { 249 | log.Printf("read line err %v", err) 250 | return 251 | } 252 | 253 | } 254 | }() 255 | 256 | go func() { 257 | for { 258 | line, err := eReader.ReadString('\n') 259 | 260 | if line != "" { 261 | log.Printf("read err %s", line) 262 | } 263 | 264 | if err != nil || line == "" { 265 | log.Printf("read err %v", err) 266 | return 267 | } 268 | 269 | } 270 | }() 271 | 272 | err := cmd.Wait() 273 | if err != nil { 274 | log.Printf("cmd wait %v", err) 275 | return 276 | } 277 | } 278 | ``` 279 | 280 | 输出: 281 | 282 | ``` shell 283 | Run Test 3 284 | 2019/06/06 18:06:44 read line test cmd output 5 285 | 2019/06/06 18:06:45 read line test cmd output 4 286 | 2019/06/06 18:06:46 read line test cmd output 3 287 | 2019/06/06 18:06:47 read line test cmd output 2 288 | 2019/06/06 18:06:48 read line test cmd output 1 289 | 2019/06/06 18:06:49 read err a err occur 290 | 2019/06/06 18:06:49 cmd wait exit status 1 291 | ``` 292 | 293 | 294 | 295 | 296 | 297 | ### 设置执行超时时间 298 | 299 | 有时候我们要控制命令的执行时间,这是就可以使用上下文去控制了。 300 | 301 | ``` go 302 | // 通过上下文控制超时 303 | func test4() { 304 | 305 | ctx, calcel := context.WithTimeout(context.Background(), 2*time.Second) 306 | defer calcel() 307 | 308 | cmd := exec.CommandContext(ctx, "./testcmd/testcmd", "-s", "-e") 309 | 310 | stdout, _ := cmd.StdoutPipe() 311 | stderr, _ := cmd.StderrPipe() 312 | oReader := bufio.NewReader(stdout) 313 | eReader := bufio.NewReader(stderr) 314 | 315 | cmd.Start() 316 | 317 | go func() { 318 | for { 319 | line, err := oReader.ReadString('\n') 320 | 321 | if line != "" { 322 | log.Printf("read line %s", line) 323 | } 324 | 325 | if err != nil || line == "" { 326 | log.Printf("read line err %v", err) 327 | return 328 | } 329 | 330 | } 331 | }() 332 | 333 | go func() { 334 | for { 335 | line, err := eReader.ReadString('\n') 336 | 337 | if line != "" { 338 | log.Printf("read err %s", line) 339 | } 340 | 341 | if err != nil || line == "" { 342 | log.Printf("read err %v", err) 343 | return 344 | } 345 | 346 | } 347 | }() 348 | 349 | err := cmd.Wait() 350 | if err != nil { 351 | log.Printf("cmd wait %v", err) 352 | return 353 | } 354 | } 355 | ``` 356 | 357 | 输出: 358 | 359 | ```shell 360 | Run Test 4 361 | 2019/06/06 18:06:49 read line err EOF 362 | 2019/06/06 18:06:49 read err EOF 363 | 2019/06/06 18:06:49 read line test cmd output 5 364 | 2019/06/06 18:06:50 read line test cmd output 4 365 | 2019/06/06 18:06:51 read line err EOF 366 | 2019/06/06 18:06:51 read err EOF 367 | 2019/06/06 18:06:51 cmd wait signal: killed 368 | ``` 369 | 370 | 371 | 372 | ### 持续输入指令,交互模式 373 | 374 | 有很多命令支持交互模式,我们进入之后就可以持续的输入一些命令,同时获取输出。如`openssl`命令。 375 | 376 | 下面我们需要进入交换模式,执行输入三个命令,并获取输出。 377 | 378 | ``` go 379 | // 持续输入 380 | func test5() { 381 | cmd := exec.Command("openssl") 382 | 383 | stdout, _ := cmd.StdoutPipe() 384 | stderr, _ := cmd.StderrPipe() 385 | 386 | stdin, _ := cmd.StdinPipe() 387 | 388 | cmd.Start() 389 | 390 | // 读 391 | var wg sync.WaitGroup 392 | wg.Add(3) 393 | go func() { 394 | defer wg.Done() 395 | for { 396 | buf := make([]byte, 1024) 397 | n, err := stderr.Read(buf) 398 | 399 | if n > 0 { 400 | fmt.Println(string(buf[:n])) 401 | } 402 | 403 | if n == 0 { 404 | break 405 | } 406 | 407 | if err != nil { 408 | log.Printf("read err %v", err) 409 | return 410 | } 411 | } 412 | }() 413 | 414 | go func() { 415 | defer wg.Done() 416 | for { 417 | buf := make([]byte, 1024) 418 | n, err := stdout.Read(buf) 419 | 420 | if n == 0 { 421 | break 422 | } 423 | 424 | if n > 0 { 425 | fmt.Println(string(buf[:n])) 426 | } 427 | 428 | if n == 0 { 429 | break 430 | } 431 | 432 | if err != nil { 433 | log.Printf("read out %v", err) 434 | return 435 | } 436 | 437 | } 438 | }() 439 | 440 | // 写 441 | go func() { 442 | stdin.Write([]byte("version\n\n")) 443 | stdin.Write([]byte("ciphers -v\n\n")) 444 | stdin.Write([]byte("s_client -connect razeencheng.com:443")) 445 | stdin.Close() 446 | wg.Done() 447 | }() 448 | 449 | wg.Wait() 450 | err := cmd.Wait() 451 | if err != nil { 452 | log.Printf("cmd wait %v", err) 453 | return 454 | } 455 | 456 | } 457 | ``` 458 | 459 | 这里,我们就用到了`stdin`标准输入了。输出如下: 460 | 461 | ``` shell 462 | Run Test 5 463 | OpenSSL> LibreSSL 2.6.5 464 | OpenSSL> OpenSSL> 465 | ECDHE-RSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH Au=RSA Enc=AESGCM(256) Mac=AEAD 466 | ECDHE-ECDSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH Au=ECDSA Enc=AESGCM(256) Mac=AEAD 467 | ECDHE-RSA-AES256-SHA384 TLSv1.2 Kx=ECDH Au=RSA Enc=AES(256) Mac=SHA384 468 | ECDHE-ECDSA-AES256-SHA384 TLSv1.2 Kx=ECDH Au=ECDSA Enc=AES(256) Mac=SHA384 469 | ECDHE-RSA-AES256-SHA SSLv3 Kx=ECDH Au=RSA Enc=AES(256) Mac=SHA1 470 | ECDHE-ECDSA-AES256-SHA SSLv3 Kx=ECDH Au=ECDSA Enc=AES(256) Mac=SHA1 471 | DHE-RSA-AES256-GCM-SHA384 TLSv1.2 Kx=DH Au=RSA Enc=AESGCM(256) Mac=AEAD 472 | DHE-RSA-AES256-SHA256 TLSv1.2 Kx=DH Au=RSA Enc=AES(256) Mac=SHA256 473 | DES-CBC-SHA SSLv3 Kx=RSA Au=RSA Enc=DES(56) Mac=SHA1 474 | ... 475 | OpenSSL> OpenSSL> 476 | 4466583148:error:14004410:SSL routines:CONNECT_CR_SRVR_HELLO:sslv3 alert handshake failure:/BuildRoot/Library/Caches/com.apple.xbs/Sources/libressl/libressl-22.260.1/libressl-2.6/ssl/ssl_pkt.c:1205:SSL alert number 40 477 | 4466583148:error:140040E5:SSL routines:CONNECT_CR_SRVR_HELLO:ssl handshake failure:/BuildRoot/Library/Caches/com.apple.xbs/Sources/libressl/libressl-22.260.1/libressl-2.6/ssl/ssl_pkt.c:585: 478 | 479 | CONNECTED(00000005) 480 | --- 481 | no peer certificate available 482 | --- 483 | No client certificate CA names sent 484 | --- 485 | SSL handshake has read 7 bytes and written 0 bytes 486 | --- 487 | New, (NONE), Cipher is (NONE) 488 | Secure Renegotiation IS NOT supported 489 | Compression: NONE 490 | Expansion: NONE 491 | No ALPN negotiated 492 | SSL-Session: 493 | Protocol : TLSv1.2 494 | Cipher : 0000 495 | Session-ID: 496 | Session-ID-ctx: 497 | Master-Key: 498 | Start Time: 1559815613 499 | Timeout : 7200 (sec) 500 | Verify return code: 0 (ok) 501 | --- 502 | 503 | ``` 504 | 505 | 506 | 507 | 508 | 509 | —END--- -------------------------------------------------------------------------------- /grpc/demo3/README.md: -------------------------------------------------------------------------------- 1 | 2 | # [gRPC在Go中的使用(三)gRPC实现TLS加密通信与流通信](https://razeen.me/post/how-to-use-grpc-in-golang-03.html) 3 | 4 | 在前面的两篇博客中,我们已经知道了如何利用gRPC建立简单RPC通信。但这样简单的实现有时候满足不了我们的业务需求。在一些场景中我们需要防止数据被劫持,或是一些场景中我们希望客户端与服务器不是简单的一问一答,而是建立起一个流式的RPC通信,那么该怎么做到呢? 5 | 6 | 7 | 8 | ### TLS加密通信 9 | 10 | TLS加密无非就是认证客户端与服务器,如果对SSL/TLS加密通信有所了解的童鞋都知道我们首先需要两张证书。 11 | 12 | 所以作为准备工作,我们首先要申请两张测试证书。一张客户端证书,一张服务器证书。 13 | 14 | #### 生成测试证书 15 | 16 | 利用[MySSL测试证书生成工具](https://myssl.com/create_test_cert.html)我们可以很简单的生成两张证书,如下所示: 17 | 18 | 如图,填入域名生成一张服务器证书,然后将私钥,证书链,根证书都下载下来,保存到文件。 19 | 20 | ![](https://st.razeen.me/essay/image/grpc-demo3-001.png) 21 | 22 | 同样,生成一张客户端证书并保存。 23 | 24 | ![](https://st.razeen.me/essay/image/grpc-demo3-002.png) 25 | 26 | #### 客户端与服务器TLS认证 27 | 28 | 在gRPC通信中,我们完成服务器认证与客户端认证主要使用的是grpc下的[credentials](https://godoc.org/google.golang.org/grpc/credentials)库。下面通过实例来看看怎么使用。 29 | 30 | [代码实例]() 31 | 32 | **服务端实现** 33 | 34 | ```go 35 | func main() { 36 | lis, err := net.Listen("tcp", ":8080") 37 | if err != nil { 38 | panic(err) 39 | } 40 | 41 | // 加载证书和密钥 (同时能验证证书与私钥是否匹配) 42 | cert, err := tls.LoadX509KeyPair("certs/test_server.pem", "certs/test_server.key") 43 | if err != nil { 44 | panic(err) 45 | } 46 | 47 | // 将根证书加入证书池 48 | // 测试证书的根如果不加入可信池,那么测试证书将视为不可惜,无法通过验证。 49 | certPool := x509.NewCertPool() 50 | rootBuf, err := ioutil.ReadFile("certs/root.pem") 51 | if err != nil { 52 | panic(err) 53 | } 54 | 55 | if !certPool.AppendCertsFromPEM(rootBuf) { 56 | panic("fail to append test ca") 57 | } 58 | 59 | tlsConf := &tls.Config{ 60 | ClientAuth: tls.RequireAndVerifyClientCert, 61 | Certificates: []tls.Certificate{cert}, 62 | ClientCAs: certPool, 63 | } 64 | 65 | serverOpt := grpc.Creds(credentials.NewTLS(tlsConf)) 66 | grpcServer := grpc.NewServer(serverOpt) 67 | 68 | pb.RegisterHelloWorldServiceServer(grpcServer, &SayHelloServer{}) 69 | 70 | log.Println("Server Start...") 71 | grpcServer.Serve(lis) 72 | } 73 | ``` 74 | 75 | **客户端实现** 76 | 77 | ```go 78 | func main() { 79 | cert, err := tls.LoadX509KeyPair("certs/test_client.pem", "certs/test_client.key") 80 | if err != nil { 81 | panic(err) 82 | } 83 | // 将根证书加入证书池 84 | certPool := x509.NewCertPool() 85 | bs, err := ioutil.ReadFile("certs/root.pem") 86 | if err != nil { 87 | panic(err) 88 | } 89 | 90 | if !certPool.AppendCertsFromPEM(bs) { 91 | panic("fail to append test ca") 92 | } 93 | 94 | // 新建凭证 95 | // ServerName 需要与服务器证书内的通用名称一致 96 | transportCreds := credentials.NewTLS(&tls.Config{ 97 | ServerName: "server.razeen.me", 98 | Certificates: []tls.Certificate{cert}, 99 | RootCAs: certPool, 100 | }) 101 | 102 | dialOpt := grpc.WithTransportCredentials(transportCreds) 103 | 104 | conn, err := grpc.Dial("localhost:8080", dialOpt) 105 | if err != nil { 106 | log.Fatalf("Dial failed:%v", err) 107 | } 108 | defer conn.Close() 109 | 110 | client := pb.NewHelloWorldServiceClient(conn) 111 | resp1, err := client.SayHelloWorld(context.Background(), &pb.HelloWorldRequest{ 112 | Greeting: "Hello Server 1 !!", 113 | Infos: map[string]string{"hello": "world"}, 114 | }) 115 | if err != nil { 116 | log.Printf("%v", err) 117 | } 118 | 119 | log.Printf("Resp1:%+v", resp1) 120 | 121 | resp2, err := client.SayHelloWorld(context.Background(), &pb.HelloWorldRequest{ 122 | Greeting: "Hello Server 2 !!", 123 | }) 124 | if err != nil { 125 | log.Printf("%v", err) 126 | } 127 | 128 | log.Printf("Resp2:%+v", resp2) 129 | } 130 | ``` 131 | 132 | 从代码中,我们不难看出,主要是创建一个通信凭证(TransportCredentials)。利用`credentials`库的`NewTLS`方法从`tls`加载一个通信凭证用于通信。而在其中需要注意的是: 133 | 134 | - 如果你使用的是自签发的证书,注意将根加入证书池。如果你使用的是可信CA签发的证书大部分不用添加,因为系统的可信CA库已经有了。如果没有成功添加, 在通信时会出现以下错误: 135 | 136 | > rpc error: code = Unavailable desc = all SubConns are in TransientFailure, latest connection error: connection error: desc = "transport: authentication handshake failed: x509: certificate signed by unknown authority" 137 | 138 | 或 139 | 140 | > rpc error: code = Unavailable desc = all SubConns are in TransientFailure, latest connection error: connection error: desc = "transport: authentication handshake failed: remote error: tls: bad certificate" 141 | 142 | - 客户端凭证内 `ServerName` 需要与服务器证书内的通用名称一致,如果不一致会出现如下错误: 143 | 144 | > rpc error: code = Unavailable desc = all SubConns are in TransientFailure, latest connection error: connection error: desc = "transport: authentication handshake failed: x509: certificate is valid for server.razeen.me, not xxxxx" 145 | 146 | 之后,我们就可安心的通信了,在私钥不泄漏的情况下,基本不再担心数据劫持问题了。 147 | 148 | 149 | 150 | **这里我想多说一句:**我们经常在提交代码时会直接 `git add .` ,这是个不好的习惯,有时后我们会将一些不必要的文件提交上去,特别是一些**证书**、**私钥**、**密码**之类的文件。 151 | 152 | 153 | 154 | ### 流式的RPC通信 155 | 156 | 流式PRC通信可以分为: 157 | 158 | - 服务器端流式 RPC; 159 | 160 | 客户端发送请求到服务器,拿到一个流去读取返回的消息序列。 客户端读取返回的流,直到里面没有任何消息。如: 161 | 162 | ```protobuf 163 | rpc ListHello(HelloWorldRequest) returns (stream HelloWorldResponse) {} 164 | ``` 165 | 166 | 167 | 168 | - 客户端流式 RPC; 169 | 170 | 客户端写入一个消息序列并将其发送到服务器,同样也是使用流。一旦客户端完成写入消息,它等待服务器完成读取返回它的响应。如: 171 | 172 | ```protobuf 173 | rpc SayMoreHello(stream HelloWorldRequest) returns (HelloWorldResponse) {} 174 | ``` 175 | 176 | 177 | 178 | - 双向流式 RPC; 179 | 180 | 双方使用读写流去发送一个消息序列。两个流独立操作,因此客户端和服务器可以以任意喜欢的顺序读写。如: 181 | 182 | ```protobuf 183 | rpc SayHelloChat(stream HelloWorldRequest) returns (stream HelloWorldRequest) {} 184 | ``` 185 | 186 | 187 | 188 | 从上面的定义不难看出,用`stream`可以定义一个流式消息。下面我们就通过实例来演示一下流式通信的使用方法。 189 | 190 | 首先,我们将上面三个rpc server加入`.proto` , 并且生成新的`.pb.go`代码。 191 | 192 | 在生成的代码`hello_world.pb.go`中,我们可以看到客户端接口如下: 193 | 194 | ```go 195 | type HelloWorldServiceClient interface { 196 | SayHelloWorld(ctx context.Context, in *HelloWorldRequest, opts ...grpc.CallOption) (*HelloWorldResponse, error) 197 | ListHello(ctx context.Context, in *HelloWorldRequest, opts ...grpc.CallOption) (HelloWorldService_ListHelloClient, error) 198 | SayMoreHello(ctx context.Context, opts ...grpc.CallOption) (HelloWorldService_SayMoreHelloClient, error) 199 | SayHelloChat(ctx context.Context, opts ...grpc.CallOption) (HelloWorldService_SayHelloChatClient, error) 200 | } 201 | ``` 202 | 203 | 服务端接口如下: 204 | 205 | ```go 206 | // HelloWorldServiceServer is the server API for HelloWorldService service. 207 | type HelloWorldServiceServer interface { 208 | SayHelloWorld(context.Context, *HelloWorldRequest) (*HelloWorldResponse, error) 209 | ListHello(*HelloWorldRequest, HelloWorldService_ListHelloServer) error 210 | SayMoreHello(HelloWorldService_SayMoreHelloServer) error 211 | SayHelloChat(HelloWorldService_SayHelloChatServer) error 212 | } 213 | ``` 214 | 215 | 在客户段的接口中,生成了`HelloWorldService_XXXXClient`接口类型。 在服务端的接口中,生成了`HelloWorldService_XXXXServer`接口类型。 我们再查看这些接口的定义,发现这这几个接口都是实现了以下几个方法中的数个: 216 | 217 | ```go 218 | Send(*HelloWorldRequest) error 219 | Recv() (*HelloWorldRequest, error) 220 | CloseAndRecv() (*HelloWorldResponse, error) 221 | grpc.ClientStream 222 | ``` 223 | 224 | 看其名字,我们不难知道,流式RPC的使用,或者说流的收发也就离不开这几个方法了。下面我们通过几个实例来验证一下。 225 | 226 | 227 | 228 | 在服务端,我们实现这三个接口。 229 | 230 | ```go 231 | // 服务器端流式 RPC, 接收一次客户端请求,返回一个流 232 | func (s *SayHelloServer) ListHello(in *pb.HelloWorldRequest, stream pb.HelloWorldService_ListHelloServer) error { 233 | log.Printf("Client Say: %v", in.Greeting) 234 | 235 | // 我们返回多条数据 236 | stream.Send(&pb.HelloWorldResponse{Reply: "ListHello Reply " + in.Greeting + " 1"}) 237 | time.Sleep(1 * time.Second) 238 | stream.Send(&pb.HelloWorldResponse{Reply: "ListHello Reply " + in.Greeting + " 2"}) 239 | time.Sleep(1 * time.Second) 240 | stream.Send(&pb.HelloWorldResponse{Reply: "ListHello Reply " + in.Greeting + " 3"}) 241 | time.Sleep(1 * time.Second) 242 | return nil 243 | } 244 | 245 | // 客户端流式 RPC, 客户端流式请求,服务器可返回一次 246 | func (s *SayHelloServer) SayMoreHello(stream pb.HelloWorldService_SayMoreHelloServer) error { 247 | // 接受客户端请求 248 | for { 249 | req, err := stream.Recv() 250 | if err == io.EOF { 251 | break 252 | } 253 | 254 | if err != nil { 255 | return err 256 | } 257 | 258 | log.Printf("SayMoreHello Client Say: %v", req.Greeting) 259 | } 260 | 261 | // 流读取完成后,返回 262 | return stream.SendAndClose(&pb.HelloWorldResponse{Reply: "SayMoreHello Recv Muti Greeting"}) 263 | } 264 | 265 | // 双向流式 RPC 266 | func (s *SayHelloServer) SayHelloChat(stream pb.HelloWorldService_SayHelloChatServer) error { 267 | // 开一个协程去处理客户端数据 268 | go func() { 269 | for { 270 | req, err := stream.Recv() 271 | if err == io.EOF { 272 | break 273 | } 274 | 275 | if err != nil { 276 | return 277 | } 278 | 279 | log.Printf("SayHelloChat Client Say: %v", req.Greeting) 280 | } 281 | }() 282 | 283 | // 向客户端写入多条数据 284 | stream.Send(&pb.HelloWorldRequest{Greeting: "SayHelloChat Server Say Hello 1"}) 285 | time.Sleep(1 * time.Second) 286 | stream.Send(&pb.HelloWorldRequest{Greeting: "SayHelloChat Server Say Hello 2"}) 287 | time.Sleep(1 * time.Second) 288 | stream.Send(&pb.HelloWorldRequest{Greeting: "SayHelloChat Server Say Hello 3"}) 289 | time.Sleep(1 * time.Second) 290 | return nil 291 | } 292 | ``` 293 | 294 | 295 | 296 | 之后我们就可以在客户端分别请求这几个rpc服务。 297 | 298 | ```go 299 | // 服务器端流式 RPC; 300 | // 我们向服务器SayHello 301 | recvListHello, err := client.ListHello(context.Background(), &pb.HelloWorldRequest{Greeting: "Hello Server List Hello"}) 302 | if err != nil { 303 | log.Fatalf("ListHello err: %v", err) 304 | } 305 | 306 | // 服务器以流式返回 307 | // 直到 err==io.EOF时,表示接收完毕。 308 | for { 309 | resp, err := recvListHello.Recv() 310 | if err == io.EOF { 311 | break 312 | } 313 | if err != nil { 314 | log.Fatal(err) 315 | } 316 | 317 | log.Printf("ListHello Server Resp: %v", resp.Reply) 318 | } 319 | // Client Out: 320 | // 2018/08/06 01:27:55 ListHello Server Resp: ListHello Reply Hello Server List Hello 1 321 | // 2018/08/06 01:27:56 ListHello Server Resp: ListHello Reply Hello Server List Hello 2 322 | // 2018/08/06 01:27:57 ListHello Server Resp: ListHello Reply Hello Server List Hello 3 323 | // Server Out: 324 | // 2018/08/06 01:27:55 Client Say: Hello Server List Hello 325 | 326 | 327 | // 客户端流式 RPC; 328 | sayMoreClient, err := client.SayMoreHello(context.Background()) 329 | if err != nil { 330 | log.Fatal(err) 331 | } 332 | for i := 0; i < 3; i++ { 333 | sayMoreClient.Send(&pb.HelloWorldRequest{Greeting: fmt.Sprintf("SayMoreHello Hello Server %d", i)}) 334 | } 335 | 336 | sayMoreResp, err := sayMoreClient.CloseAndRecv() 337 | if err != nil { 338 | log.Fatal(err) 339 | } 340 | log.Printf("SayMoreHello Server Resp: %v", sayMoreResp.Reply) 341 | 342 | // Client Out: 343 | // 2018/08/06 01:31:11 SayMoreHello Server Resp: SayMoreHello Recv Muti Greeting 344 | // Server Out: 345 | // 2018/08/06 01:31:11 SayMoreHello Client Say: SayMoreHello Hello Server 0 346 | // 2018/08/06 01:31:11 SayMoreHello Client Say: SayMoreHello Hello Server 1 347 | // 2018/08/06 01:31:11 SayMoreHello Client Say: SayMoreHello Hello Server 2 348 | 349 | 350 | // 双向流式 RPC; 351 | sayHelloChat, err := client.SayHelloChat(context.Background()) 352 | if err != nil { 353 | log.Fatal(err) 354 | } 355 | 356 | go func() { 357 | for i := 0; i < 3; i++ { 358 | sayHelloChat.Send(&pb.HelloWorldRequest{Greeting: fmt.Sprintf("SayHelloChat Hello Server %d", i)}) 359 | } 360 | }() 361 | 362 | for { 363 | resp, err := sayHelloChat.Recv() 364 | if err == io.EOF { 365 | break 366 | } 367 | if err != nil { 368 | log.Fatal(err) 369 | } 370 | 371 | log.Printf("SayHelloChat Server Say: %v", resp.Greeting) 372 | } 373 | // Client Out: 374 | // 2018/08/06 01:31:11 SayHelloChat Server Say: SayHelloChat Server Say Hello 1 375 | // 2018/08/06 01:31:12 SayHelloChat Server Say: SayHelloChat Server Say Hello 2 376 | // 2018/08/06 01:31:13 SayHelloChat Server Say: SayHelloChat Server Say Hello 3 377 | // Server Out: 378 | // 2018/08/06 01:31:11 SayHelloChat Client Say: SayHelloChat Hello Server 0 379 | // 2018/08/06 01:31:11 SayHelloChat Client Say: SayHelloChat Hello Server 1 380 | // 2018/08/06 01:31:11 SayHelloChat Client Say: SayHelloChat Hello Server 2 381 | ``` 382 | 383 | 看了实例,是不是觉得很简单~。三种方式大同小异,只要掌握了怎么去收发流,怎么判断流的结束,基本就可以了。 384 | 385 | 386 | 387 | 388 | 389 | 好了,gRPC在Go中的使用三篇文章到这里也就结束了,如果博客中有错误或者你还有想知道的,记得留言哦。 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | -------------------------------------------------------------------------------- /grpc/demo2/helloworld/hello_world.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: github.com/razeencheng/demo-go/grpc/demo2/helloworld/hello_world.proto 3 | 4 | package helloworld // import "github.com/razeencheng/demo-go/grpc/demo2/helloworld" 5 | 6 | import proto "github.com/golang/protobuf/proto" 7 | import fmt "fmt" 8 | import math "math" 9 | import any "github.com/golang/protobuf/ptypes/any" 10 | 11 | import ( 12 | context "golang.org/x/net/context" 13 | grpc "google.golang.org/grpc" 14 | ) 15 | 16 | // Reference imports to suppress errors if they are not otherwise used. 17 | var _ = proto.Marshal 18 | var _ = fmt.Errorf 19 | var _ = math.Inf 20 | 21 | // This is a compile-time assertion to ensure that this generated file 22 | // is compatible with the proto package it is being compiled against. 23 | // A compilation error at this line likely means your copy of the 24 | // proto package needs to be updated. 25 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package 26 | 27 | type HelloWorldRequest struct { 28 | Greeting string `protobuf:"bytes,1,opt,name=greeting,proto3" json:"greeting,omitempty"` 29 | Infos map[string]string `protobuf:"bytes,2,rep,name=infos,proto3" json:"infos,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` 30 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 31 | XXX_unrecognized []byte `json:"-"` 32 | XXX_sizecache int32 `json:"-"` 33 | } 34 | 35 | func (m *HelloWorldRequest) Reset() { *m = HelloWorldRequest{} } 36 | func (m *HelloWorldRequest) String() string { return proto.CompactTextString(m) } 37 | func (*HelloWorldRequest) ProtoMessage() {} 38 | func (*HelloWorldRequest) Descriptor() ([]byte, []int) { 39 | return fileDescriptor_hello_world_e1e511201326df80, []int{0} 40 | } 41 | func (m *HelloWorldRequest) XXX_Unmarshal(b []byte) error { 42 | return xxx_messageInfo_HelloWorldRequest.Unmarshal(m, b) 43 | } 44 | func (m *HelloWorldRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 45 | return xxx_messageInfo_HelloWorldRequest.Marshal(b, m, deterministic) 46 | } 47 | func (dst *HelloWorldRequest) XXX_Merge(src proto.Message) { 48 | xxx_messageInfo_HelloWorldRequest.Merge(dst, src) 49 | } 50 | func (m *HelloWorldRequest) XXX_Size() int { 51 | return xxx_messageInfo_HelloWorldRequest.Size(m) 52 | } 53 | func (m *HelloWorldRequest) XXX_DiscardUnknown() { 54 | xxx_messageInfo_HelloWorldRequest.DiscardUnknown(m) 55 | } 56 | 57 | var xxx_messageInfo_HelloWorldRequest proto.InternalMessageInfo 58 | 59 | func (m *HelloWorldRequest) GetGreeting() string { 60 | if m != nil { 61 | return m.Greeting 62 | } 63 | return "" 64 | } 65 | 66 | func (m *HelloWorldRequest) GetInfos() map[string]string { 67 | if m != nil { 68 | return m.Infos 69 | } 70 | return nil 71 | } 72 | 73 | type HelloWorldResponse struct { 74 | Reply string `protobuf:"bytes,1,opt,name=reply,proto3" json:"reply,omitempty"` 75 | Details []*any.Any `protobuf:"bytes,2,rep,name=details,proto3" json:"details,omitempty"` 76 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 77 | XXX_unrecognized []byte `json:"-"` 78 | XXX_sizecache int32 `json:"-"` 79 | } 80 | 81 | func (m *HelloWorldResponse) Reset() { *m = HelloWorldResponse{} } 82 | func (m *HelloWorldResponse) String() string { return proto.CompactTextString(m) } 83 | func (*HelloWorldResponse) ProtoMessage() {} 84 | func (*HelloWorldResponse) Descriptor() ([]byte, []int) { 85 | return fileDescriptor_hello_world_e1e511201326df80, []int{1} 86 | } 87 | func (m *HelloWorldResponse) XXX_Unmarshal(b []byte) error { 88 | return xxx_messageInfo_HelloWorldResponse.Unmarshal(m, b) 89 | } 90 | func (m *HelloWorldResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 91 | return xxx_messageInfo_HelloWorldResponse.Marshal(b, m, deterministic) 92 | } 93 | func (dst *HelloWorldResponse) XXX_Merge(src proto.Message) { 94 | xxx_messageInfo_HelloWorldResponse.Merge(dst, src) 95 | } 96 | func (m *HelloWorldResponse) XXX_Size() int { 97 | return xxx_messageInfo_HelloWorldResponse.Size(m) 98 | } 99 | func (m *HelloWorldResponse) XXX_DiscardUnknown() { 100 | xxx_messageInfo_HelloWorldResponse.DiscardUnknown(m) 101 | } 102 | 103 | var xxx_messageInfo_HelloWorldResponse proto.InternalMessageInfo 104 | 105 | func (m *HelloWorldResponse) GetReply() string { 106 | if m != nil { 107 | return m.Reply 108 | } 109 | return "" 110 | } 111 | 112 | func (m *HelloWorldResponse) GetDetails() []*any.Any { 113 | if m != nil { 114 | return m.Details 115 | } 116 | return nil 117 | } 118 | 119 | type HelloWorld struct { 120 | Msg string `protobuf:"bytes,1,opt,name=msg,proto3" json:"msg,omitempty"` 121 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 122 | XXX_unrecognized []byte `json:"-"` 123 | XXX_sizecache int32 `json:"-"` 124 | } 125 | 126 | func (m *HelloWorld) Reset() { *m = HelloWorld{} } 127 | func (m *HelloWorld) String() string { return proto.CompactTextString(m) } 128 | func (*HelloWorld) ProtoMessage() {} 129 | func (*HelloWorld) Descriptor() ([]byte, []int) { 130 | return fileDescriptor_hello_world_e1e511201326df80, []int{2} 131 | } 132 | func (m *HelloWorld) XXX_Unmarshal(b []byte) error { 133 | return xxx_messageInfo_HelloWorld.Unmarshal(m, b) 134 | } 135 | func (m *HelloWorld) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 136 | return xxx_messageInfo_HelloWorld.Marshal(b, m, deterministic) 137 | } 138 | func (dst *HelloWorld) XXX_Merge(src proto.Message) { 139 | xxx_messageInfo_HelloWorld.Merge(dst, src) 140 | } 141 | func (m *HelloWorld) XXX_Size() int { 142 | return xxx_messageInfo_HelloWorld.Size(m) 143 | } 144 | func (m *HelloWorld) XXX_DiscardUnknown() { 145 | xxx_messageInfo_HelloWorld.DiscardUnknown(m) 146 | } 147 | 148 | var xxx_messageInfo_HelloWorld proto.InternalMessageInfo 149 | 150 | func (m *HelloWorld) GetMsg() string { 151 | if m != nil { 152 | return m.Msg 153 | } 154 | return "" 155 | } 156 | 157 | type Error struct { 158 | Msg []string `protobuf:"bytes,1,rep,name=msg,proto3" json:"msg,omitempty"` 159 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 160 | XXX_unrecognized []byte `json:"-"` 161 | XXX_sizecache int32 `json:"-"` 162 | } 163 | 164 | func (m *Error) Reset() { *m = Error{} } 165 | func (m *Error) String() string { return proto.CompactTextString(m) } 166 | func (*Error) ProtoMessage() {} 167 | func (*Error) Descriptor() ([]byte, []int) { 168 | return fileDescriptor_hello_world_e1e511201326df80, []int{3} 169 | } 170 | func (m *Error) XXX_Unmarshal(b []byte) error { 171 | return xxx_messageInfo_Error.Unmarshal(m, b) 172 | } 173 | func (m *Error) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 174 | return xxx_messageInfo_Error.Marshal(b, m, deterministic) 175 | } 176 | func (dst *Error) XXX_Merge(src proto.Message) { 177 | xxx_messageInfo_Error.Merge(dst, src) 178 | } 179 | func (m *Error) XXX_Size() int { 180 | return xxx_messageInfo_Error.Size(m) 181 | } 182 | func (m *Error) XXX_DiscardUnknown() { 183 | xxx_messageInfo_Error.DiscardUnknown(m) 184 | } 185 | 186 | var xxx_messageInfo_Error proto.InternalMessageInfo 187 | 188 | func (m *Error) GetMsg() []string { 189 | if m != nil { 190 | return m.Msg 191 | } 192 | return nil 193 | } 194 | 195 | func init() { 196 | proto.RegisterType((*HelloWorldRequest)(nil), "helloworld.HelloWorldRequest") 197 | proto.RegisterMapType((map[string]string)(nil), "helloworld.HelloWorldRequest.InfosEntry") 198 | proto.RegisterType((*HelloWorldResponse)(nil), "helloworld.HelloWorldResponse") 199 | proto.RegisterType((*HelloWorld)(nil), "helloworld.HelloWorld") 200 | proto.RegisterType((*Error)(nil), "helloworld.Error") 201 | } 202 | 203 | // Reference imports to suppress errors if they are not otherwise used. 204 | var _ context.Context 205 | var _ grpc.ClientConn 206 | 207 | // This is a compile-time assertion to ensure that this generated file 208 | // is compatible with the grpc package it is being compiled against. 209 | const _ = grpc.SupportPackageIsVersion4 210 | 211 | // HelloWorldServiceClient is the client API for HelloWorldService service. 212 | // 213 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. 214 | type HelloWorldServiceClient interface { 215 | SayHelloWorld(ctx context.Context, in *HelloWorldRequest, opts ...grpc.CallOption) (*HelloWorldResponse, error) 216 | } 217 | 218 | type helloWorldServiceClient struct { 219 | cc *grpc.ClientConn 220 | } 221 | 222 | func NewHelloWorldServiceClient(cc *grpc.ClientConn) HelloWorldServiceClient { 223 | return &helloWorldServiceClient{cc} 224 | } 225 | 226 | func (c *helloWorldServiceClient) SayHelloWorld(ctx context.Context, in *HelloWorldRequest, opts ...grpc.CallOption) (*HelloWorldResponse, error) { 227 | out := new(HelloWorldResponse) 228 | err := c.cc.Invoke(ctx, "/helloworld.HelloWorldService/SayHelloWorld", in, out, opts...) 229 | if err != nil { 230 | return nil, err 231 | } 232 | return out, nil 233 | } 234 | 235 | // HelloWorldServiceServer is the server API for HelloWorldService service. 236 | type HelloWorldServiceServer interface { 237 | SayHelloWorld(context.Context, *HelloWorldRequest) (*HelloWorldResponse, error) 238 | } 239 | 240 | func RegisterHelloWorldServiceServer(s *grpc.Server, srv HelloWorldServiceServer) { 241 | s.RegisterService(&_HelloWorldService_serviceDesc, srv) 242 | } 243 | 244 | func _HelloWorldService_SayHelloWorld_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 245 | in := new(HelloWorldRequest) 246 | if err := dec(in); err != nil { 247 | return nil, err 248 | } 249 | if interceptor == nil { 250 | return srv.(HelloWorldServiceServer).SayHelloWorld(ctx, in) 251 | } 252 | info := &grpc.UnaryServerInfo{ 253 | Server: srv, 254 | FullMethod: "/helloworld.HelloWorldService/SayHelloWorld", 255 | } 256 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 257 | return srv.(HelloWorldServiceServer).SayHelloWorld(ctx, req.(*HelloWorldRequest)) 258 | } 259 | return interceptor(ctx, in, info, handler) 260 | } 261 | 262 | var _HelloWorldService_serviceDesc = grpc.ServiceDesc{ 263 | ServiceName: "helloworld.HelloWorldService", 264 | HandlerType: (*HelloWorldServiceServer)(nil), 265 | Methods: []grpc.MethodDesc{ 266 | { 267 | MethodName: "SayHelloWorld", 268 | Handler: _HelloWorldService_SayHelloWorld_Handler, 269 | }, 270 | }, 271 | Streams: []grpc.StreamDesc{}, 272 | Metadata: "github.com/razeencheng/demo-go/grpc/demo2/helloworld/hello_world.proto", 273 | } 274 | 275 | func init() { 276 | proto.RegisterFile("github.com/razeencheng/demo-go/grpc/demo2/helloworld/hello_world.proto", fileDescriptor_hello_world_e1e511201326df80) 277 | } 278 | 279 | var fileDescriptor_hello_world_e1e511201326df80 = []byte{ 280 | // 336 bytes of a gzipped FileDescriptorProto 281 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x52, 0x4b, 0x4b, 0xc3, 0x40, 282 | 0x10, 0x36, 0x2d, 0xf5, 0x31, 0x22, 0xe8, 0xd2, 0x43, 0x0d, 0x58, 0x4a, 0x4e, 0xbd, 0xb8, 0x0b, 283 | 0x55, 0xa4, 0x78, 0x10, 0x14, 0x2a, 0x7a, 0x93, 0xf4, 0x20, 0xf4, 0x22, 0x69, 0x3a, 0xdd, 0x06, 284 | 0xb7, 0x3b, 0x71, 0x93, 0x54, 0xe2, 0x3f, 0xf2, 0x5f, 0xca, 0x26, 0x8d, 0x09, 0x88, 0x1e, 0x3c, 285 | 0x04, 0xe6, 0xdb, 0xef, 0x31, 0x0f, 0x02, 0xf7, 0x32, 0x4a, 0x57, 0xd9, 0x9c, 0x87, 0xb4, 0x16, 286 | 0x26, 0xf8, 0x40, 0xd4, 0xe1, 0x0a, 0xb5, 0x14, 0x0b, 0x5c, 0xd3, 0xb9, 0x24, 0x21, 0x4d, 0x1c, 287 | 0x16, 0x60, 0x24, 0x56, 0xa8, 0x14, 0xbd, 0x93, 0x51, 0x8b, 0xb2, 0x7c, 0x29, 0x6a, 0x1e, 0x1b, 288 | 0x4a, 0x89, 0x41, 0xcd, 0xba, 0xa2, 0x91, 0x29, 0x49, 0x05, 0x5a, 0x8a, 0x42, 0x34, 0xcf, 0x96, 289 | 0x22, 0x4e, 0xf3, 0x18, 0x13, 0x11, 0xe8, 0xdc, 0x7e, 0xa5, 0xd9, 0xfb, 0x74, 0xe0, 0xe4, 0xc1, 290 | 0xfa, 0x9f, 0xad, 0xdf, 0xc7, 0xb7, 0x0c, 0x93, 0x94, 0xb9, 0xb0, 0x2f, 0x0d, 0x62, 0x1a, 0x69, 291 | 0xd9, 0x73, 0x06, 0xce, 0xf0, 0xc0, 0xff, 0xc6, 0xec, 0x06, 0x3a, 0x91, 0x5e, 0x52, 0xd2, 0x6b, 292 | 0x0d, 0xda, 0xc3, 0xc3, 0xd1, 0x90, 0xd7, 0xed, 0xf9, 0x8f, 0x24, 0xfe, 0x68, 0xa5, 0x13, 0x9d, 293 | 0x9a, 0xdc, 0x2f, 0x6d, 0xee, 0x18, 0xa0, 0x7e, 0x64, 0xc7, 0xd0, 0x7e, 0xc5, 0x7c, 0xdb, 0xc4, 294 | 0x96, 0xac, 0x0b, 0x9d, 0x4d, 0xa0, 0x32, 0xec, 0xb5, 0x8a, 0xb7, 0x12, 0x5c, 0xb7, 0xc6, 0x8e, 295 | 0x37, 0x03, 0xd6, 0x6c, 0x90, 0xc4, 0xa4, 0x13, 0xb4, 0x7a, 0x83, 0xb1, 0xaa, 0x32, 0x4a, 0xc0, 296 | 0x38, 0xec, 0x2d, 0x30, 0x0d, 0x22, 0x55, 0xcd, 0xd9, 0xe5, 0x92, 0x48, 0x2a, 0xe4, 0xd5, 0x3d, 297 | 0xf8, 0xad, 0xce, 0xfd, 0x4a, 0xe4, 0xf5, 0x01, 0xea, 0x6c, 0x3b, 0xd5, 0x3a, 0xa9, 0x56, 0xb7, 298 | 0xa5, 0x77, 0x0a, 0x9d, 0x89, 0x31, 0x64, 0x6a, 0xaa, 0xbd, 0xa5, 0x46, 0xd8, 0xbc, 0xe0, 0x14, 299 | 0xcd, 0x26, 0x0a, 0x91, 0x3d, 0xc1, 0xd1, 0x34, 0xc8, 0x1b, 0x91, 0x67, 0x7f, 0xde, 0xc9, 0xed, 300 | 0xff, 0x46, 0x97, 0x5b, 0x7a, 0x3b, 0x77, 0x57, 0xb3, 0xcb, 0xff, 0xfc, 0x30, 0xf3, 0xdd, 0x62, 301 | 0xe1, 0x8b, 0xaf, 0x00, 0x00, 0x00, 0xff, 0xff, 0xcf, 0x1c, 0x94, 0xd7, 0x6f, 0x02, 0x00, 0x00, 302 | } 303 | --------------------------------------------------------------------------------