feat: add rpc gate way command

This commit is contained in:
zhuyasen 2022-11-08 21:09:17 +08:00
parent 169d63cfc0
commit 022bb99323
9 changed files with 386 additions and 23 deletions

View File

@ -105,7 +105,7 @@ enum GenderType {
FEMALE = 2;
};
// 使grpc gatewaytarget
// 使grpc gatewaytarget
// string email = 2 [(tagger.tags) = "binding:\"email\"" ];
message CreateUserExampleRequest {
@ -119,7 +119,7 @@ message CreateUserExampleRequest {
}
message CreateUserExampleReply {
uint64 id = 1 [(validate.rules).uint64.gte = 1];
uint64 id = 1;
}
message DeleteUserExampleByIDRequest {

View File

@ -0,0 +1,246 @@
package generate
import (
"bytes"
"errors"
"fmt"
"os"
"path/filepath"
"regexp"
"github.com/zhufuyi/sponge/pkg/replacer"
"github.com/huandu/xstrings"
"github.com/spf13/cobra"
)
// RPCGwCommand generate rpc gateway server codes
func RPCGwCommand() *cobra.Command {
var (
moduleName string // go.mod文件的module名称
serverName string // 服务名称
projectName string // 项目名称
repoAddr string // 镜像仓库地址
outPath string // 输出目录
protobufFile string // proto file文件指定这个文件生成路由和service
)
//nolint
cmd := &cobra.Command{
Use: "rpc-gw",
Short: "Generate rpc gateway server codes based on protobuf",
Long: `generate rpc gateway server codes based on protobuf.
Examples:
# generate rpc gateway server codes.
sponge micro rpc-gw --module-name=yourModuleName --server-name=yourServerName --project-name=yourProjectName --protobuf-file=./userExample.proto
# generate rpc gateway server codes and specify the output directory, Note: if the file already exists, code generation will be canceled.
sponge micro rpc-gw --module-name=yourModuleName --server-name=yourServerName --project-name=yourProjectName --protobuf-file=./userExample.proto --out=./yourServerDir
# generate rpc gateway server codes and specify the docker image repository address.
sponge micro rpc-gw --module-name=yourModuleName --server-name=yourServerName --project-name=yourProjectName --repo-addr=192.168.3.37:9443/user-name --protobuf-file=./userExample.proto
`,
SilenceErrors: true,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
return runGenRPCGwCommand(moduleName, serverName, projectName, protobufFile, repoAddr, outPath)
},
}
cmd.Flags().StringVarP(&moduleName, "module-name", "m", "", "module-name is the name of the module in the 'go.mod' file")
_ = cmd.MarkFlagRequired("module-name")
cmd.Flags().StringVarP(&serverName, "server-name", "s", "", "server name")
_ = cmd.MarkFlagRequired("server-name")
cmd.Flags().StringVarP(&projectName, "project-name", "p", "", "project name")
_ = cmd.MarkFlagRequired("project-name")
cmd.Flags().StringVarP(&protobufFile, "protobuf-file", "f", "", "proto file")
_ = cmd.MarkFlagRequired("protobuf-file")
cmd.Flags().StringVarP(&repoAddr, "repo-addr", "r", "", "docker image repository address, excluding http and repository names")
cmd.Flags().StringVarP(&outPath, "out", "o", "", "output directory, default is ./serverName_rpc_<time>")
return cmd
}
func runGenRPCGwCommand(moduleName string, serverName string, projectName string, protobufFile string, repoAddr string, outPath string) error {
protoData, err := os.ReadFile(protobufFile)
if err != nil {
return err
}
err = getServiceName(protoData)
if err != nil {
return err
}
subTplName := "rpc-gw"
r := Replacers[TplNameSponge]
if r == nil {
return errors.New("replacer is nil")
}
// 设置模板信息
subDirs := []string{} // 只处理的子目录,如果为空或者没有指定的子目录,表示所有文件
ignoreDirs := []string{"cmd/sponge", "cmd/protoc-gen-go-gin", "cmd/serverNameExample_mixExample",
"cmd/serverNameExample_grpcExample", "cmd/serverNameExample_httpExample",
"sponge/.github", "sponge/.git", "sponge/pkg", "sponge/assets", "sponge/test",
"internal/model", "internal/cache", "internal/dao", "internal/ecode", "internal/service",
"internal/handler", "internal/types", "api/serverNameExample",
} // 指定子目录下忽略处理的目录
ignoreFiles := []string{"grpc.go", "grpc_option.go", "grpc_test.go", "LICENSE",
"grpc_userExample.go", "grpc_systemCode.go", "grpc_systemCode_test.go",
"codecov.yml", "routers.go", "routers_test.go", "userExample_gwExample.go", "userExample.go",
"routers_gwExample_test.go", "userExample_gwExample.go", "types.pb.validate.go", "types.pb.go",
"swagger.json", "swagger.yaml", "apis.swagger.json", "proto.html", "docs.go", "doc.go",
} // 指定子目录下忽略处理的文件
if !bytes.Contains(protoData, []byte("api/types/types.proto")) {
ignoreFiles = append(ignoreFiles, "types.proto")
}
r.SetSubDirs(subDirs...)
r.SetIgnoreSubDirs(ignoreDirs...)
r.SetIgnoreFiles(ignoreFiles...)
fields := addRPCGwFields(moduleName, serverName, projectName, repoAddr, r)
r.SetReplacementFields(fields)
_ = r.SetOutputDir(outPath, serverName+"_"+subTplName)
if err = r.SaveFiles(); err != nil {
return err
}
fmt.Printf("generate %s's rpc gateway server codes successfully, out = %s\n\n", serverName, r.GetOutputDir())
// 保存moduleName和serverName到指定文件给外部使用
genInfo := moduleName + "," + serverName
file := r.GetOutputDir() + "/docs/gen.info"
err = os.WriteFile(file, []byte(genInfo), 0666)
if err != nil {
fmt.Printf("save file %s error, %v\n", file, err)
}
// 复制protobuf文件
_, name := filepath.Split(protobufFile)
dir := r.GetOutputDir() + "/api/" + serverName + "/v1"
_ = os.MkdirAll(dir, 0666)
file = dir + "/" + name
err = os.WriteFile(file, protoData, 0666)
if err != nil {
fmt.Printf("save file %s error, %v\n", file, err)
}
return nil
}
func addRPCGwFields(moduleName string, serverName string, projectName string, repoAddr string,
r replacer.Replacer) []replacer.Field {
var fields []replacer.Field
repoHost, _ := parseImageRepoAddr(repoAddr)
fields = append(fields, deleteFieldsMark(r, modelFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, daoFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, daoTestFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, handlerFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, handlerTestFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, httpFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, dockerFile, wellStartMark, wellEndMark)...)
fields = append(fields, deleteFieldsMark(r, dockerFileBuild, wellStartMark, wellEndMark)...)
fields = append(fields, deleteFieldsMark(r, dockerComposeFile, wellStartMark, wellEndMark)...)
fields = append(fields, deleteFieldsMark(r, k8sDeploymentFile, wellStartMark, wellEndMark)...)
fields = append(fields, deleteFieldsMark(r, k8sServiceFile, wellStartMark, wellEndMark)...)
fields = append(fields, deleteFieldsMark(r, imageBuildFile, wellOnlyGrpcStartMark, wellOnlyGrpcEndMark)...)
fields = append(fields, deleteFieldsMark(r, makeFile, wellStartMark, wellEndMark)...)
fields = append(fields, deleteFieldsMark(r, gitIgnoreFile, wellStartMark, wellEndMark)...)
fields = append(fields, deleteFieldsMark(r, protoShellFile, wellStartMark, wellEndMark)...)
fields = append(fields, replaceFileContentMark(r, readmeFile, "## "+serverName)...)
//fields = append(fields, replaceFileContentMark(r, protoFile, string(protoData))...)
fields = append(fields, []replacer.Field{
{ // 替换Dockerfile文件内容
Old: dockerFileMark,
New: dockerFileHTTPCode,
},
{ // 替换Dockerfile_build文件内容
Old: dockerFileBuildMark,
New: dockerFileBuildHTTPCode,
},
{ // 替换docker-compose.yml文件内容
Old: dockerComposeFileMark,
New: dockerComposeFileHTTPCode,
},
{ // 替换*-deployment.yml文件内容
Old: k8sDeploymentFileMark,
New: k8sDeploymentFileHTTPCode,
},
{ // 替换*-svc.yml文件内容
Old: k8sServiceFileMark,
New: k8sServiceFileHTTPCode,
},
{ // 替换proto.sh文件内容
Old: protoShellFileMark,
New: protoShellServiceCode,
},
{
Old: "github.com/zhufuyi/sponge",
New: moduleName,
},
{
Old: moduleName + "/pkg",
New: "github.com/zhufuyi/sponge/pkg",
},
{
Old: "sponge api docs",
New: serverName + " api docs",
},
{
Old: "serverNameExample",
New: serverName,
},
// docker镜像和k8s部署脚本替换
{
Old: "server-name-example",
New: xstrings.ToKebabCase(serverName),
},
{
Old: "projectNameExample",
New: projectName,
},
// docker镜像和k8s部署脚本替换
{
Old: "project-name-example",
New: xstrings.ToKebabCase(projectName),
},
{
Old: "repo-addr-example",
New: repoAddr,
},
{
Old: "image-repo-host",
New: repoHost,
},
{
Old: "_httpExample",
New: "",
},
{
Old: "_mixExample",
New: "",
},
{
Old: "_gwExample",
New: "",
},
}...)
return fields
}
func getServiceName(data []byte) error {
servicePattern := `\nservice (\w+)`
re := regexp.MustCompile(servicePattern)
matchArr := re.FindStringSubmatch(string(data))
if len(matchArr) < 2 {
return fmt.Errorf("not found service name in protobuf file, the protobuf file requires at least one service")
}
return nil
}

1
go.mod
View File

@ -12,7 +12,6 @@ require (
github.com/felixge/fgprof v0.9.3
github.com/fsnotify/fsnotify v1.5.4
github.com/gin-contrib/cors v1.3.1
github.com/gin-contrib/pprof v1.4.0
github.com/gin-gonic/gin v1.8.1
github.com/go-playground/validator/v10 v10.11.0
github.com/go-redis/redis/extra/redisotel v0.3.0

2
go.sum
View File

@ -187,8 +187,6 @@ github.com/gin-contrib/cors v1.3.1 h1:doAsuITavI4IOcd0Y19U4B+O0dNWihRyX//nn4sEmg
github.com/gin-contrib/cors v1.3.1/go.mod h1:jjEJ4268OPZUcU7k9Pm653S7lXUGcqMADzFA61xsmDk=
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
github.com/gin-contrib/pprof v1.4.0 h1:XxiBSf5jWZ5i16lNOPbMTVdgHBdhfGRD5PZ1LWazzvg=
github.com/gin-contrib/pprof v1.4.0/go.mod h1:RrehPJasUVBPK6yTUwOl8/NP6i0vbUgmxtis+Z5KE90=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do=

View File

@ -6,7 +6,6 @@ import (
"github.com/gin-gonic/gin"
)
// nolint
func init() {
routerFns = append(routerFns, func(group *gin.RouterGroup) {
userExampleRouter(group, handler.NewUserExampleHandler())

View File

@ -9,7 +9,6 @@ import (
"github.com/gin-gonic/gin"
)
// nolint
func init() {
rootRouterFns = append(rootRouterFns, func(r *gin.Engine) {
userExampleRouter_gwExample(r, service.NewUserExampleServiceClient())

View File

@ -176,11 +176,20 @@ func (t tmplField) GoTypeZero() string {
return t.GoType
}
// AddOne 加一
// AddOne counter
func (t tmplField) AddOne(i int) int {
return i + 1
}
// AddOneWithTag counter and add id tag
func (t tmplField) AddOneWithTag(i int) string {
if t.ColName == "id" {
return fmt.Sprintf(`%d [(tagger.tags) = "uri:\"id\"" ]`, i+1)
}
return fmt.Sprintf("%d", i+1)
}
const (
__mysqlModel__ = "__mysqlModel__" //nolint
__type__ = "__type__" //nolint

View File

@ -89,17 +89,97 @@ type Get{{.TableName}}ByIDRespond struct {
package api.serverNameExample.v1;
import "api/types/types.proto";
// import "validate/validate.proto";
import "google/api/annotations.proto";
import "protoc-gen-openapiv2/options/annotations.proto";
import "tagger/tagger.proto";
//import "validate/validate.proto";
option go_package = "github.com/zhufuyi/sponge/api/serverNameExample/v1;v1";
// Default settings for generating swagger documents
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = {
host: "localhost:8080"
base_path: ""
info: {
title: "serverNameExample api docs";
version: "v0.0.0";
};
schemes: HTTP;
schemes: HTTPS;
consumes: "application/json";
produces: "application/json";
};
service {{.TName}}Service {
rpc Create(Create{{.TableName}}Request) returns (Create{{.TableName}}Reply) {}
rpc DeleteByID(Delete{{.TableName}}ByIDRequest) returns (Delete{{.TableName}}ByIDReply) {}
rpc UpdateByID(Update{{.TableName}}ByIDRequest) returns (Update{{.TableName}}ByIDReply) {}
rpc GetByID(Get{{.TableName}}ByIDRequest) returns (Get{{.TableName}}ByIDReply) {}
rpc ListByIDs(List{{.TableName}}ByIDsRequest) returns (List{{.TableName}}ByIDsReply) {}
rpc List(List{{.TableName}}Request) returns (List{{.TableName}}Reply) {}
rpc Create(Create{{.TableName}}Request) returns (Create{{.TableName}}Reply) {
option (google.api.http) = {
post: "/api/v1/{{.TName}}"
body: "*"
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
summary: "create a new {{.TName}}",
description: "submit information to create a new {{.TName}}",
tags: "{{.TName}}",
};
}
rpc DeleteByID(Delete{{.TableName}}ByIDRequest) returns (Delete{{.TableName}}ByIDReply) {
option (google.api.http) = {
delete: "/api/v1/{{.TName}}/{id}"
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
summary: "delete {{.TName}}",
description: "delete {{.TName}} by id",
tags: "{{.TName}}",
};
}
rpc UpdateByID(Update{{.TableName}}ByIDRequest) returns (Update{{.TableName}}ByIDReply) {
option (google.api.http) = {
put: "/api/v1/{{.TName}}/{id}"
body: "*"
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
summary: "update {{.TName}} info",
description: "update {{.TName}} info by id",
tags: "{{.TName}}",
};
}
rpc GetByID(Get{{.TableName}}ByIDRequest) returns (Get{{.TableName}}ByIDReply) {
option (google.api.http) = {
get: "/api/v1/{{.TName}}/{id}"
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
summary: "get {{.TName}} details",
description: "get {{.TName}} details by id",
tags: "{{.TName}}",
};
}
rpc ListByIDs(List{{.TableName}}ByIDsRequest) returns (List{{.TableName}}ByIDsReply) {
option (google.api.http) = {
post: "/api/v1/{{.TName}}s/ids"
body: "*"
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
summary: "get a list of {{.TName}} based on multiple ids",
description: "get a list of {{.TName}} based on multiple ids",
tags: "{{.TName}}",
};
}
rpc List(List{{.TableName}}Request) returns (List{{.TableName}}Reply) {
option (google.api.http) = {
post: "/api/v1/{{.TName}}s"
body: "*"
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
summary: "get a list of {{.TName}} based on query parameters",
description: "get a list of {{.TName}} based on query parameters",
tags: "{{.TName}}",
};
}
}
// todo fill in the validate rules https://github.com/envoyproxy/protoc-gen-validate#constraint-rules
@ -111,7 +191,7 @@ message Create{{.TableName}}Reply {
}
message Delete{{.TableName}}ByIDRequest {
uint64 id =1;
uint64 id =1 [(tagger.tags) = "uri:\"id\"" ];
}
message Delete{{.TableName}}ByIDReply {
@ -127,7 +207,7 @@ message Update{{.TableName}}ByIDReply {
// protoMessageDetailCode
message Get{{.TableName}}ByIDRequest {
uint64 id =1;
uint64 id =1 [(tagger.tags) = "uri:\"id\"" ];
}
message Get{{.TableName}}ByIDReply {
@ -162,7 +242,7 @@ message List{{.TableName}}Reply {
protoMessageUpdateTmpl *template.Template
protoMessageUpdateTmplRaw = `message Update{{.TableName}}ByIDRequest {
{{- range $i, $v := .Fields}}
{{$v.GoType}} {{$v.ColName}} = {{$v.AddOne $i}}; {{if $v.Comment}} // {{$v.Comment}}{{end}}
{{$v.GoType}} {{$v.ColName}} = {{$v.AddOneWithTag $i}}; {{if $v.Comment}} // {{$v.Comment}}{{end}}
{{- end}}
}`

View File

@ -16,13 +16,22 @@ function checkResult() {
fi
}
function listFiles(){
# 把生成*.pb.go代码中导入无用的package添加到这里
function deleteUnusedPkg() {
file=$1
sed -i "s#_ \"github.com/envoyproxy/protoc-gen-validate/validate\"##g" ${file}
sed -i "s#_ \"github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options\"##g" ${file}
sed -i "s#_ \"github.com/srikrsna/protoc-gen-gotag/tagger\"##g" ${file}
sed -i "s#_ \"google.golang.org/genproto/googleapis/api/annotations\"##g" ${file}
}
function listProtoFiles(){
cd $1
items=$(ls)
for item in $items; do
if [ -d "$item" ]; then
listFiles $item
listProtoFiles $item
else
if [ "${item#*.}"x = "proto"x ];then
file=$(pwd)/${item}
@ -34,8 +43,24 @@ function listFiles(){
cd ..
}
function listPbGoFiles(){
cd $1
items=$(ls)
for item in $items; do
if [ -d "$item" ]; then
listPbGoFiles $item
else
if [ "${item#*.}"x = "pb.go"x ];then
deleteUnusedPkg $item
fi
fi
done
cd ..
}
# 获取所有proto文件路径
listFiles $protoBasePath
listProtoFiles $protoBasePath
# 生成文件 *_pb.go, *_grpc_pb.go
protoc --proto_path=. --proto_path=./third_party \
@ -66,11 +91,19 @@ protoc --proto_path=. --proto_path=./third_party \
checkResult $?
# 生成_*router.pb.go
# todo generate router code for gin here
# delete the templates code start
# 生成_*router.pb.go和*_logic.go其中*_logic.go保存路径自定义
protoc --proto_path=. --proto_path=./third_party \
--go-gin_out=. --go-gin_opt=paths=source_relative --go-gin_opt=plugins=service \
--go-gin_out=. --go-gin_opt=paths=source_relative --go-gin_opt=plugin=service \
--go-gin_opt=moduleName=github.com/zhufuyi/sponge --go-gin_opt=serverName=serverNameExample --go-gin_opt=out=internal/service \
$allProtoFiles
checkResult $?
# delete the templates code end
listPbGoFiles $protoBasePath
go mod tidy
checkResult $?
echo "exec protoc command successfully."