1
0
mirror of https://github.com/go-kratos/kratos.git synced 2025-01-10 00:29:01 +02:00

add protoc gen ecode (#274)

* add protoc gen ecode
* add protobuf example
This commit is contained in:
Felix Hao 2019-08-16 11:15:38 +08:00 committed by GitHub
parent c1f9b5ca81
commit 1481e14c12
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1438 additions and 43 deletions

1
.gitignore vendored
View File

@ -28,4 +28,5 @@ tool/kratos-gen-bts/kratos-gen-bts
tool/kratos-gen-mc/kratos-gen-mc tool/kratos-gen-mc/kratos-gen-mc
tool/kratos/kratos-protoc/kratos-protoc tool/kratos/kratos-protoc/kratos-protoc
tool/kratos/protobuf/protoc-gen-bm/protoc-gen-bm tool/kratos/protobuf/protoc-gen-bm/protoc-gen-bm
tool/kratos/protobuf/protoc-gen-ecode/protoc-gen-ecode
tool/kratos/protobuf/protoc-gen-bswagger/protoc-gen-bswagger tool/kratos/protobuf/protoc-gen-bswagger/protoc-gen-bswagger

View File

@ -1,38 +1,31 @@
### kratos tool protoc ### kratos tool protoc
``` ```shell
// generate all # generate all
kratos tool protoc api.proto kratos tool protoc api.proto
// generate gRPC # generate gRPC
kratos tool protoc --grpc api.proto kratos tool protoc --grpc api.proto
// generate BM HTTP # generate BM HTTP
kratos tool protoc --bm api.proto kratos tool protoc --bm api.proto
// generate swagger # generate ecode
kratos tool protoc --ecode api.proto
# generate swagger
kratos tool protoc --swagger api.proto kratos tool protoc --swagger api.proto
``` ```
执行对应生成 `api.pb.go/api.bm.go/api.swagger.json` 源文档。
> 该工具在Windows/Linux下运行,需提前安装好 protobuf 工具 执行生成如 `api.pb.go/api.bm.go/api.swagger.json/api.ecode.go` 的对应文件,需要注意的是:`ecode`生成有固定规则,需要首先是`enum`类型,且`enum`名字要以`ErrCode`结尾,如`enum UserErrCode`。详情可见:[example](https://github.com/bilibili/kratos/tree/master/example/protobuf)
该工具实际是一段`shell`脚本,其中自动将`protoc`命令进行了拼接,识别了需要的`*.proto`文件和当前目录下的`proto`文件,最终会拼接为如下命令进行执行: > 该工具在Windows/Linux下运行,需提前安装好 [protobuf](https://github.com/google/protobuf) 工具
`kratos tool protoc`本质上是拼接好了`protoc`命令然后进行执行,在执行时会打印出对应执行的`protoc`命令,如下可见:
```shell ```shell
export $KRATOS_HOME = kratos路径 protoc --proto_path=$GOPATH --proto_path=$GOPATH/github.com/bilibili/kratos/third_party --proto_path=. --bm_out=:. api.proto
export $KRATOS_DEMO = 项目路径 protoc --proto_path=$GOPATH --proto_path=$GOPATH/github.com/bilibili/kratos/third_party --proto_path=. --gofast_out=plugins=grpc:. api.proto
protoc --proto_path=$GOPATH --proto_path=$GOPATH/github.com/bilibili/kratos/third_party --proto_path=. --bswagger_out=:. api.proto
// 生成:api.pb.go protoc --proto_path=$GOPATH --proto_path=$GOPATH/github.com/bilibili/kratos/third_party --proto_path=. --ecode_out=:. api.proto
protoc -I$GOPATH/src:$KRATOS_HOME/third_party:$KRATOS_DEMO/api --gofast_out=plugins=grpc:$KRATOS_DEMO/api $KRATOS_DEMO/api/api.proto
// 生成:api.bm.go
protoc -I$GOPATH/src:$KRATOS_HOME/third_party:$KRATOS_DEMO/api --bm_out=$KRATOS_DEMO/api $KRATOS_DEMO/api/api.proto
// 生成:api.swagger.json
protoc -I$GOPATH/src:$KRATOS_HOME/third_party:$KRATOS_DEMO/api --bswagger_out=$KRATOS_DEMO/api $KRATOS_DEMO/api/api.proto
``` ```
大家也可以参考该命令进行`proto`生成,也可以参考 [protobuf](https://github.com/google/protobuf) 官方参数。
------------- -------------
[文档目录树](summary.md) [文档目录树](summary.md)

View File

@ -0,0 +1,40 @@
// Code generated by protoc-gen-bm v0.1, DO NOT EDIT.
// source: api.proto
package api
import (
"context"
bm "github.com/bilibili/kratos/pkg/net/http/blademaster"
"github.com/bilibili/kratos/pkg/net/http/blademaster/binding"
)
// to suppressed 'imported but not used warning'
var _ *bm.Context
var _ context.Context
var _ binding.StructValidator
var PathUserInfo = "/user.api.User/Info"
// UserBMServer is the server API for User service.
type UserBMServer interface {
Info(ctx context.Context, req *UserReq) (resp *InfoReply, err error)
}
var UserSvc UserBMServer
func userInfo(c *bm.Context) {
p := new(UserReq)
if err := c.BindWith(p, binding.Default(c.Request.Method, c.Request.Header.Get("Content-Type"))); err != nil {
return
}
resp, err := UserSvc.Info(c, p)
c.JSON(resp, err)
}
// RegisterUserBMServer Register the blademaster route
func RegisterUserBMServer(e *bm.Engine, server UserBMServer) {
UserSvc = server
e.GET("/user.api.User/Info", userInfo)
}

View File

@ -0,0 +1,17 @@
// Code generated by protoc-gen-ecode v0.1, DO NOT EDIT.
// source: api.proto
package api
import (
"github.com/bilibili/kratos/pkg/ecode"
)
// to suppressed 'imported but not used warning'
var _ ecode.Codes
// UserErrCode ecode
var (
UserNotExist = ecode.New(-404)
UserUpdateNameFailed = ecode.New(10000)
)

1000
example/protobuf/api.pb.go Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,33 @@
syntax = "proto3";
package user.api;
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
option go_package = "api";
enum UserErrCode {
OK = 0;
UserNotExist = -404;
UserUpdateNameFailed = 10000;
}
message Info {
int64 mid = 1 [(gogoproto.jsontag) = "mid"];
string name = 2 [(gogoproto.jsontag) = "name"];
string sex = 3 [(gogoproto.jsontag) = "sex"];
string face = 4 [(gogoproto.jsontag) = "face"];
string sign = 5 [(gogoproto.jsontag) = "sign"];
}
message UserReq {
int64 mid = 1 [(gogoproto.moretags) = "validate:\"gt=0,required\""];
}
message InfoReply {
Info info = 1;
}
service User {
rpc Info(UserReq) returns (InfoReply);
}

View File

@ -0,0 +1,96 @@
{
"swagger": "2.0",
"info": {
"title": "api.proto",
"version": ""
},
"schemes": [
"http",
"https"
],
"consumes": [
"application/json",
"multipart/form-data"
],
"produces": [
"application/json"
],
"paths": {
"/user.api.User/Info": {
"get": {
"summary": "/user.api.User/Info",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"type": "object",
"properties": {
"code": {
"type": "integer"
},
"message": {
"type": "string"
},
"data": {
"$ref": "#/definitions/.user.api.InfoReply"
}
}
}
}
},
"parameters": [
{
"name": "mid",
"in": "query",
"required": true,
"type": "integer"
}
],
"tags": [
"user.api.User"
]
}
}
},
"definitions": {
".user.api.Info": {
"type": "object",
"properties": {
"mid": {
"type": "integer"
},
"name": {
"type": "string"
},
"sex": {
"type": "string"
},
"face": {
"type": "string"
},
"sign": {
"type": "string"
}
}
},
".user.api.InfoReply": {
"type": "object",
"properties": {
"info": {
"$ref": "#/definitions/.user.api.Info"
}
}
},
".user.api.UserReq": {
"type": "object",
"properties": {
"mid": {
"type": "integer"
}
},
"required": [
"mid"
]
}
}
}

3
example/protobuf/gen.sh Normal file
View File

@ -0,0 +1,3 @@
#!/bin/bash
kratos tool protoc api.proto

View File

@ -8,7 +8,7 @@ import (
const ( const (
_getBMGen = "go get -u github.com/bilibili/kratos/tool/protobuf/protoc-gen-bm" _getBMGen = "go get -u github.com/bilibili/kratos/tool/protobuf/protoc-gen-bm"
_bmProtoc = "protoc --proto_path=%s --proto_path=%s --proto_path=%s --bm_out=explicit_http=true:." _bmProtoc = "protoc --proto_path=%s --proto_path=%s --proto_path=%s --bm_out=:."
) )
func installBMGen() error { func installBMGen() error {

View File

@ -0,0 +1,25 @@
package main
import (
"os/exec"
"github.com/urfave/cli"
)
const (
_getEcodeGen = "go get -u github.com/bilibili/kratos/tool/protobuf/protoc-gen-ecode"
_ecodeProtoc = "protoc --proto_path=%s --proto_path=%s --proto_path=%s --ecode_out=:."
)
func installEcodeGen() error {
if _, err := exec.LookPath("protoc-gen-ecode"); err != nil {
if err := goget(_getEcodeGen); err != nil {
return err
}
}
return nil
}
func genEcode(ctx *cli.Context) error {
return generate(ctx, _ecodeProtoc)
}

View File

@ -27,6 +27,11 @@ func main() {
Usage: "whether to use swagger for generation", Usage: "whether to use swagger for generation",
Destination: &withSwagger, Destination: &withSwagger,
}, },
cli.BoolFlag{
Name: "ecode",
Usage: "whether to use ecode for generation",
Destination: &withEcode,
},
} }
app.Action = func(c *cli.Context) error { app.Action = func(c *cli.Context) error {
return protocAction(c) return protocAction(c)

View File

@ -20,16 +20,18 @@ var (
withBM bool withBM bool
withGRPC bool withGRPC bool
withSwagger bool withSwagger bool
withEcode bool
) )
func protocAction(ctx *cli.Context) (err error) { func protocAction(ctx *cli.Context) (err error) {
if err = checkProtoc(); err != nil { if err = checkProtoc(); err != nil {
return err return err
} }
if !withGRPC && !withBM && !withSwagger { if !withGRPC && !withBM && !withSwagger && !withEcode {
withBM = true withBM = true
withGRPC = true withGRPC = true
withSwagger = true withSwagger = true
withEcode = true
} }
if withBM { if withBM {
if err = installBMGen(); err != nil { if err = installBMGen(); err != nil {
@ -55,6 +57,14 @@ func protocAction(ctx *cli.Context) (err error) {
return return
} }
} }
if withEcode {
if err = installEcodeGen(); err != nil {
return
}
if err = genEcode(ctx); err != nil {
return
}
}
log.Printf("generate %v success.\n", ctx.Args()) log.Printf("generate %v success.\n", ctx.Args())
return nil return nil
} }

View File

@ -8,7 +8,7 @@ import (
const ( const (
_getSwaggerGen = "go get -u github.com/bilibili/kratos/tool/protobuf/protoc-gen-bswagger" _getSwaggerGen = "go get -u github.com/bilibili/kratos/tool/protobuf/protoc-gen-bswagger"
_swaggerProtoc = "protoc --proto_path=%s --proto_path=%s --proto_path=%s --bswagger_out=explicit_http=true:." _swaggerProtoc = "protoc --proto_path=%s --proto_path=%s --proto_path=%s --bswagger_out=:."
) )
func installSwaggerGen() error { func installSwaggerGen() error {

View File

@ -2,6 +2,7 @@ package naming
import ( import (
"os" "os"
"path"
"path/filepath" "path/filepath"
"strings" "strings"
@ -25,6 +26,16 @@ func GetVersionPrefix(pkg string) string {
return "" return ""
} }
// GenFileName returns the output name for the generated Go file.
func GenFileName(f *descriptor.FileDescriptorProto, suffix string) string {
name := *f.Name
if ext := path.Ext(name); ext == ".pb" || ext == ".proto" || ext == ".protodevel" {
name = name[:len(name)-len(ext)]
}
name += suffix
return name
}
func ServiceName(service *descriptor.ServiceDescriptorProto) string { func ServiceName(service *descriptor.ServiceDescriptorProto) string {
return utils.CamelCase(service.GetName()) return utils.CamelCase(service.GetName())
} }

View File

@ -44,9 +44,6 @@ func (t *bm) Generate(in *plugin.CodeGeneratorRequest) *plugin.CodeGeneratorResp
func (t *bm) generateForFile(file *descriptor.FileDescriptorProto) *plugin.CodeGeneratorResponse_File { func (t *bm) generateForFile(file *descriptor.FileDescriptorProto) *plugin.CodeGeneratorResponse_File {
resp := new(plugin.CodeGeneratorResponse_File) resp := new(plugin.CodeGeneratorResponse_File)
//if len(file.Service) == 0 {
// return nil
//}
t.generateFileHeader(file, t.GenPkgName) t.generateFileHeader(file, t.GenPkgName)
t.generateImports(file) t.generateImports(file)
@ -56,11 +53,8 @@ func (t *bm) generateForFile(file *descriptor.FileDescriptorProto) *plugin.CodeG
count += t.generateBMInterface(file, service) count += t.generateBMInterface(file, service)
t.generateBMRoute(file, service, i) t.generateBMRoute(file, service, i)
} }
//if count == 0 {
// return nil
//}
resp.Name = proto.String(naming.GoFileName(file, ".bm.go")) resp.Name = proto.String(naming.GenFileName(file, ".bm.go"))
resp.Content = proto.String(t.FormattedOutput()) resp.Content = proto.String(t.FormattedOutput())
t.Output.Reset() t.Output.Reset()
@ -88,13 +82,13 @@ func (t *bm) generateFileHeader(file *descriptor.FileDescriptorProto, pkgName st
t.P("// source: ", file.GetName()) t.P("// source: ", file.GetName())
t.P() t.P()
if t.filesHandled == 0 { if t.filesHandled == 0 {
// doc for the first file
t.P("/*")
t.P("Package ", t.GenPkgName, " is a generated blademaster stub package.")
t.P("This code was generated with kratos/tool/protobuf/protoc-gen-bm ", generator.Version, ".")
t.P()
comment, err := t.Reg.FileComments(file) comment, err := t.Reg.FileComments(file)
if err == nil && comment.Leading != "" { if err == nil && comment.Leading != "" {
// doc for the first file
t.P("/*")
t.P("Package ", t.GenPkgName, " is a generated blademaster stub package.")
t.P("This code was generated with kratos/tool/protobuf/protoc-gen-bm ", generator.Version, ".")
t.P()
for _, line := range strings.Split(comment.Leading, "\n") { for _, line := range strings.Split(comment.Leading, "\n") {
line = strings.TrimPrefix(line, " ") line = strings.TrimPrefix(line, " ")
// ensure we don't escape from the block comment // ensure we don't escape from the block comment
@ -102,12 +96,12 @@ func (t *bm) generateFileHeader(file *descriptor.FileDescriptorProto, pkgName st
t.P(line) t.P(line)
} }
t.P() t.P()
t.P("It is generated from these files:")
for _, f := range t.GenFiles {
t.P("\t", f.GetName())
}
t.P("*/")
} }
t.P("It is generated from these files:")
for _, f := range t.GenFiles {
t.P("\t", f.GetName())
}
t.P("*/")
} }
t.P(`package `, pkgName) t.P(`package `, pkgName)
t.P() t.P()

View File

@ -66,7 +66,7 @@ func (t *swaggerGen) generateSwagger(file *descriptor.FileDescriptorProto) *plug
t.defsMap = map[string]*typemap.MessageDefinition{} t.defsMap = map[string]*typemap.MessageDefinition{}
out := &plugin.CodeGeneratorResponse_File{} out := &plugin.CodeGeneratorResponse_File{}
name := naming.GoFileName(file, ".swagger.json") name := naming.GenFileName(file, ".swagger.json")
for _, svc := range file.Service { for _, svc := range file.Service {
for _, meth := range svc.Method { for _, meth := range svc.Method {
if !t.ShouldGenForMethod(file, svc, meth) { if !t.ShouldGenForMethod(file, svc, meth) {

View File

@ -0,0 +1,117 @@
package generator
import (
"strconv"
"strings"
"github.com/bilibili/kratos/tool/protobuf/pkg/generator"
"github.com/bilibili/kratos/tool/protobuf/pkg/naming"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/protoc-gen-go/descriptor"
plugin "github.com/golang/protobuf/protoc-gen-go/plugin"
)
type ecode struct {
generator.Base
filesHandled int
}
// EcodeGenerator ecode generator.
func EcodeGenerator() *ecode {
t := &ecode{}
return t
}
// Generate ...
func (t *ecode) Generate(in *plugin.CodeGeneratorRequest) *plugin.CodeGeneratorResponse {
t.Setup(in)
// Showtime! Generate the response.
resp := new(plugin.CodeGeneratorResponse)
for _, f := range t.GenFiles {
respFile := t.generateForFile(f)
if respFile != nil {
resp.File = append(resp.File, respFile)
}
}
return resp
}
func (t *ecode) generateForFile(file *descriptor.FileDescriptorProto) *plugin.CodeGeneratorResponse_File {
var enums []*descriptor.EnumDescriptorProto
for _, enum := range file.EnumType {
if strings.HasSuffix(*enum.Name, "ErrCode") {
enums = append(enums, enum)
}
}
if len(enums) == 0 {
return nil
}
resp := new(plugin.CodeGeneratorResponse_File)
t.generateFileHeader(file, t.GenPkgName)
t.generateImports(file)
for _, enum := range enums {
t.generateEcode(file, enum)
}
resp.Name = proto.String(naming.GenFileName(file, ".ecode.go"))
resp.Content = proto.String(t.FormattedOutput())
t.Output.Reset()
t.filesHandled++
return resp
}
func (t *ecode) generateFileHeader(file *descriptor.FileDescriptorProto, pkgName string) {
t.P("// Code generated by protoc-gen-ecode ", generator.Version, ", DO NOT EDIT.")
t.P("// source: ", file.GetName())
t.P()
if t.filesHandled == 0 {
comment, err := t.Reg.FileComments(file)
if err == nil && comment.Leading != "" {
// doc for the first file
t.P("/*")
t.P("Package ", t.GenPkgName, " is a generated ecode package.")
t.P("This code was generated with kratos/tool/protobuf/protoc-gen-ecode ", generator.Version, ".")
t.P()
for _, line := range strings.Split(comment.Leading, "\n") {
line = strings.TrimPrefix(line, " ")
// ensure we don't escape from the block comment
line = strings.Replace(line, "*/", "* /", -1)
t.P(line)
}
t.P()
t.P("It is generated from these files:")
for _, f := range t.GenFiles {
t.P("\t", f.GetName())
}
t.P("*/")
}
}
t.P(`package `, pkgName)
t.P()
}
func (t *ecode) generateImports(file *descriptor.FileDescriptorProto) {
t.P(`import (`)
t.P(` "github.com/bilibili/kratos/pkg/ecode"`)
t.P(`)`)
t.P()
t.P(`// to suppressed 'imported but not used warning'`)
t.P(`var _ ecode.Codes`)
}
func (t *ecode) generateEcode(file *descriptor.FileDescriptorProto, enum *descriptor.EnumDescriptorProto) {
t.P("// ", *enum.Name, " ecode")
t.P("var (")
for _, item := range enum.Value {
if *item.Number == 0 {
continue
}
// NOTE: eg: t.P("UserNotExist = New(-404) ")
t.P(*item.Name, " = ", "ecode.New(", strconv.Itoa(int(*item.Number)), ")")
}
t.P(")")
}

View File

@ -0,0 +1,27 @@
package generator
import (
"os"
"os/exec"
"testing"
"github.com/golang/protobuf/proto"
plugin "github.com/golang/protobuf/protoc-gen-go/plugin"
)
func TestGenerateParseCommandLineParamsError(t *testing.T) {
if os.Getenv("BE_CRASHER") == "1" {
g := &ecode{}
g.Generate(&plugin.CodeGeneratorRequest{
Parameter: proto.String("invalid"),
})
return
}
cmd := exec.Command(os.Args[0], "-test.run=TestGenerateParseCommandLineParamsError")
cmd.Env = append(os.Environ(), "BE_CRASHER=1")
err := cmd.Run()
if e, ok := err.(*exec.ExitError); ok && !e.Success() {
return
}
t.Fatalf("process ran with err %v, want exit status 1", err)
}

View File

@ -0,0 +1,23 @@
package main
import (
"flag"
"fmt"
"os"
"github.com/bilibili/kratos/tool/protobuf/pkg/gen"
"github.com/bilibili/kratos/tool/protobuf/pkg/generator"
ecodegen "github.com/bilibili/kratos/tool/protobuf/protoc-gen-ecode/generator"
)
func main() {
versionFlag := flag.Bool("version", false, "print version and exit")
flag.Parse()
if *versionFlag {
fmt.Println(generator.Version)
os.Exit(0)
}
g := ecodegen.EcodeGenerator()
gen.Main(g)
}