diff --git a/doc/images/shorturl-benchmark.png b/doc/images/shorturl-benchmark.png new file mode 100644 index 00000000..1e6d5d6d Binary files /dev/null and b/doc/images/shorturl-benchmark.png differ diff --git a/doc/shorturl.md b/doc/shorturl.md index 459e4674..19e977c5 100644 --- a/doc/shorturl.md +++ b/doc/shorturl.md @@ -12,10 +12,14 @@ ## 2. 准备工作 -* 准备goctl工具,在任意目录下进行,目的是为了编译goctl工具 - 1. `git clone https://github.com/tal-tech/go-zero` - 2. 在`tools/goctl`目录下编译goctl工具`go build goctl.go` - 3. 将生成的goctl放到`$PATH`下,确保goctl命令可运行 +* 安装etcd, mysql, redis +* 准备goctl工具 +* 直接从`https://github.com/tal-tech/go-zero/releases`下载最新版,后续会加上自动更新 + * 也可以从源码编译,在任意目录下进行,目的是为了编译goctl工具 + + 1. `git clone https://github.com/tal-tech/go-zero` + 2. 在`tools/goctl`目录下编译goctl工具`go build goctl.go` + 3. 将生成的goctl放到`$PATH`下,确保goctl命令可运行 * 创建工作目录`shorturl` * 在`shorturl`目录下执行`go mod init shorturl`初始化`go.mod` @@ -128,6 +132,8 @@ * 可以通过`goctl`生成各种客户端语言的api调用代码 +* 到这里,你已经可以通过goctl生成客户端代码给客户端同学并行开发了,支持多种语言,详见文档 + ## 4. 编写shorten rpc服务 * 在`rpc/shorten`目录下编写`shorten.proto`文件 @@ -169,24 +175,24 @@ ``` rpc/shorten ├── etc -│   └── shorten.yaml // 配置文件 + │   └── shorten.yaml // 配置文件 ├── internal -│   ├── config + │   ├── config │   │   └── config.go // 配置定义 - │   ├── handler - │   │   └── shortenerhandler.go // api handler, 不需要修改 │   ├── logic -│   │   └── shortenlogic.go // api业务逻辑在这里实现 + │   │   └── shortenlogic.go // rpc业务逻辑在这里实现 + │   ├── server + │   │   └── shortenerserver.go // 调用入口, 不需要修改 │   └── svc │   └── servicecontext.go // 定义ServiceContext,传递依赖 ├── pb │   └── shorten.pb.go - ├── shared - │   ├── shortenermodel.go // 提供了外部调用方法,无需修改 - │   ├── shortenermodel_mock.go // mock方法,测试用 - │   └── types.go // request/response结构体定义 ├── shorten.go // rpc服务main函数 - └── shorten.proto + ├── shorten.proto + └── shortener + ├── shortener.go // 提供了外部调用方法,无需修改 + ├── shortener_mock.go // mock方法,测试用 + └── types.go // request/response结构体定义 ``` 直接可以运行,如下: @@ -239,24 +245,24 @@ ``` rpc/expand ├── etc -│   └── expand.yaml // 配置文件 + │   └── expand.yaml // 配置文件 + ├── expand.go // rpc服务main函数 + ├── expand.proto + ├── expander + │   ├── expander.go // 提供了外部调用方法,无需修改 + │   ├── expander_mock.go // mock方法,测试用 + │   └── types.go // request/response结构体定义 ├── internal -│   ├── config + │   ├── config │   │   └── config.go // 配置定义 - │   ├── handler - │   │   └── expanderhandler.go // api handler, 不需要修改 │   ├── logic -│   │   └── expandlogic.go // api业务逻辑在这里实现 + │   │   └── expandlogic.go // rpc业务逻辑在这里实现 + │   ├── server + │   │   └── expanderserver.go // 调用入口, 不需要修改 │   └── svc │   └── servicecontext.go // 定义ServiceContext,传递依赖 - ├── pb - │   └── expand.pb.go - ├── shared - │   ├── expandermodel.go // 提供了外部调用方法,无需修改 - │   ├── expandermodel_mock.go // mock方法,测试用 - │   └── types.go // request/response结构体定义 - ├── expand.go // rpc服务main函数 - └── expand.proto + └── pb + └── expand.pb.go ``` 修改`etc/expand.yaml`里面的`ListenOn`的端口为`8081`,因为`8080`已经被`shorten`服务占用了 @@ -270,7 +276,126 @@ `etc/expand.yaml`文件里可以修改侦听端口等配置 -## 6. 修改API Gateway代码调用shorten/expand rpc服务(未完) +## 6. 修改API Gateway代码调用shorten/expand rpc服务 + +* 修改配置文件`shorter-api.yaml`,增加如下内容 + + ```yaml + Shortener: + Etcd: + Hosts: + - localhost:2379 + Key: shorten.rpc + Expander: + Etcd: + Hosts: + - localhost:2379 + Key: expand.rpc + ``` + + 通过etcd自动去发现可用的shorten/expand服务 + +* 修改`internal/config/config.go`如下,增加shorten/expand服务依赖 + + ```go + type Config struct { + rest.RestConf + Shortener rpcx.RpcClientConf // 手动代码 + Expander rpcx.RpcClientConf // 手动代码 + } + ``` + +* 修改`internal/logic/expandlogic.go`,如下: + + ```go + type ExpandLogic struct { + ctx context.Context + logx.Logger + expander rpcx.Client // 手动代码 + } + + func NewExpandLogic(ctx context.Context, svcCtx *svc.ServiceContext) ExpandLogic { + return ExpandLogic{ + ctx: ctx, + Logger: logx.WithContext(ctx), + expander: svcCtx.Expander, // 手动代码 + } + } + + func (l *ExpandLogic) Expand(req types.ExpandReq) (*types.ExpandResp, error) { + // 手动代码开始 + resp, err := expander.NewExpander(l.expander).Expand(l.ctx, &expander.ExpandReq{ + Key: req.Key, + }) + if err != nil { + return nil, err + } + + return &types.ExpandResp{ + Url: resp.Url, + }, nil + // 手动代码结束 + } + ``` + + 增加了对`expander`服务的依赖,并通过调用`expander`的`Expand`方法实现短链恢复到url + +* 修改`internal/logic/shortenlogic.go`,如下: + + ```go + type ShortenLogic struct { + ctx context.Context + logx.Logger + shortener rpcx.Client // 手动代码 + } + + func NewShortenLogic(ctx context.Context, svcCtx *svc.ServiceContext) ShortenLogic { + return ShortenLogic{ + ctx: ctx, + Logger: logx.WithContext(ctx), + shortener: svcCtx.Shortener, // 手动代码 + } + } + + func (l *ShortenLogic) Shorten(req types.ShortenReq) (*types.ShortenResp, error) { + // 手动代码开始 + resp, err := shortener.NewShortener(l.shortener).Shorten(l.ctx, &shortener.ShortenReq{ + Url: req.Url, + }) + if err != nil { + return nil, err + } + + return &types.ShortenResp{ + ShortUrl: resp.Key, + }, nil + // 手动代码结束 + } + ``` + + 增加了对`shortener`服务的依赖,并通过调用`shortener`的`Shorten`方法实现url到短链的变换 + +* 修改`internal/svc/servicecontext.go`,如下: + + ```go + type ServiceContext struct { + Config config.Config + Shortener rpcx.Client // 手动代码 + Expander rpcx.Client // 手动代码 + } + + func NewServiceContext(config config.Config) *ServiceContext { + return &ServiceContext{ + Config: config, + Shortener: rpcx.MustNewClient(config.Shortener), // 手动代码 + Expander: rpcx.MustNewClient(config.Expander), // 手动代码 + } + } + ``` + + 通过ServiceContext在不同业务逻辑之间传递依赖 + + 至此,API Gateway修改完成,虽然贴的代码多,但是期中修改的是很少的一部分,为了方便理解上下文,我贴了完整代码,接下来处理CRUD+cache ## 7. 定义数据库表结构,并生成CRUD+cache代码 @@ -317,14 +442,206 @@ ## 8. 修改shorten/expand rpc代码调用crud+cache代码 +* 修改`rpc/expand/etc/expand.yaml`,增加如下内容: + + ```yaml + DataSource: root:@tcp(localhost:3306)/gozero + Table: shorturl + Cache: + - Host: localhost:6379 + ``` + + 可以使用多个redis作为cache,支持redis单点或者redis集群 + +* 修改`rpc/expand/internal/config.go`,如下: + + ```go + type Config struct { + rpcx.RpcServerConf + DataSource string // 手动代码 + Table string // 手动代码 + Cache cache.CacheConf // 手动代码 + } + ``` + + 增加了mysql和redis cache配置 + +* 修改`rpc/expand/internal/svc/servicecontext.go`,如下: + + ```go + type ServiceContext struct { + c config.Config + Model *model.ShorturlModel // 手动代码 + } + + func NewServiceContext(c config.Config) *ServiceContext { + return &ServiceContext{ + c: c, + Model: model.NewShorturlModel(sqlx.NewMysql(c.DataSource), c.Cache, c.Table), // 手动代码 + } + } + ``` + +* 修改`rpc/expand/internal/logic/expandlogic.go`,如下: + + ```go + type ExpandLogic struct { + ctx context.Context + logx.Logger + model *model.ShorturlModel // 手动代码 + } + + func NewExpandLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ExpandLogic { + return &ExpandLogic{ + ctx: ctx, + Logger: logx.WithContext(ctx), + model: svcCtx.Model, // 手动代码 + } + } + + func (l *ExpandLogic) Expand(in *expand.ExpandReq) (*expand.ExpandResp, error) { + // 手动代码开始 + res, err := l.model.FindOne(in.Key) + if err != nil { + return nil, err + } + + return &expand.ExpandResp{ + Url: res.Url, + }, nil + // 手动代码结束 + } + ``` + +* 修改`rpc/shorten/etc/shorten.yaml`,增加如下内容: + + ```yaml + DataSource: root:@tcp(localhost:3306)/gozero + Table: shorturl + Cache: + - Host: localhost:6379 + ``` + + 可以使用多个redis作为cache,支持redis单点或者redis集群 + +* 修改`rpc/shorten/internal/config.go`,如下: + + ```go + type Config struct { + rpcx.RpcServerConf + DataSource string // 手动代码 + Table string // 手动代码 + Cache cache.CacheConf // 手动代码 + } + ``` + + 增加了mysql和redis cache配置 + +* 修改`rpc/shorten/internal/svc/servicecontext.go`,如下: + + ```go + type ServiceContext struct { + c config.Config + Model *model.ShorturlModel // 手动代码 + } + + func NewServiceContext(c config.Config) *ServiceContext { + return &ServiceContext{ + c: c, + Model: model.NewShorturlModel(sqlx.NewMysql(c.DataSource), c.Cache, c.Table), // 手动代码 + } + } + ``` + +* 修改`rpc/shorten/internal/logic/shortenlogic.go`,如下: + + ```go + const keyLen = 6 + + type ShortenLogic struct { + ctx context.Context + logx.Logger + model *model.ShorturlModel // 手动代码 + } + + func NewShortenLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ShortenLogic { + return &ShortenLogic{ + ctx: ctx, + Logger: logx.WithContext(ctx), + model: svcCtx.Model, // 手动代码 + } + } + + func (l *ShortenLogic) Shorten(in *shorten.ShortenReq) (*shorten.ShortenResp, error) { + // 手动代码开始,生成短链接 + key := hash.Md5Hex([]byte(in.Url))[:keyLen] + _, err := l.model.Insert(model.Shorturl{ + Shorten: key, + Url: in.Url, + }) + if err != nil { + return nil, err + } + + return &shorten.ShortenResp{ + Key: key, + }, nil + // 手动代码结束 + } + ``` + + 至此代码修改完成,凡事手动修改的代码我加了标注 + ## 9. 完整调用演示 -## 10. Benchmark(未完) +* shorten api调用 + + ```shell + ~ curl -i "http://localhost:8888/shorten?url=http://www.xiaoheiban.cn" + ``` + + 返回如下: -## 11. 总结(未完) + ```http + HTTP/1.1 200 OK + Content-Type: application/json + Date: Sat, 29 Aug 2020 10:49:49 GMT + Content-Length: 21 + + {"shortUrl":"f35b2a"} + ``` -可以看到go-zero不只是一个框架,更是一个建立在框架+工具基础上的,简化和规范了整个微服务构建的技术体系。 +* expand api调用 + + ```shell + curl -i "http://localhost:8888/expand?key=f35b2a" + ``` + + 返回如下: + + ```http + HTTP/1.1 200 OK + Content-Type: application/json + Date: Sat, 29 Aug 2020 10:51:53 GMT + Content-Length: 34 + + {"url":"http://www.xiaoheiban.cn"} + ``` + +## 10. Benchmark + +因为写入依赖于mysql的写入速度,就相当于压mysql了,所以压测只测试了expand接口,相当于从mysql里读取并利用缓存,shorten.lua里随机从db里获取了100个热key来生成压测请求 + +![Benchmark](images/shorturl-benchmark.png) + +可以看出在我的MacBook Pro上能达到3万+的qps。 + +## 11. 总结 我们一直强调**工具大于约定和文档**。 -另外,我们在保持简单的同时也尽可能把微服务治理的复杂度封装到了框架内部,极大的降低了开发人员的心智负担,使得业务开发得以快速推进。 \ No newline at end of file +go-zero不只是一个框架,更是一个建立在框架+工具基础上的,简化和规范了整个微服务构建的技术体系。 + +我们在保持简单的同时也尽可能把微服务治理的复杂度封装到了框架内部,极大的降低了开发人员的心智负担,使得业务开发得以快速推进。 + +通过go-zero+goctl生成的代码,包含了微服务治理的各种组件,包括:并发控制、自适应熔断、自适应降载、自动缓存控制等,可以轻松部署以承载巨大访问量。 \ No newline at end of file diff --git a/readme.md b/readme.md index 0f532f89..741e213e 100644 --- a/readme.md +++ b/readme.md @@ -89,6 +89,8 @@ go get -u github.com/tal-tech/go-zero ## 6. Quick Start +0. 完整示例请查看[从0到1快速构建一个高并发的微服务系统](doc/shorturl.md) + 1. 编译goctl工具 ```shell diff --git a/tools/goctl/api/gogen/genhandlers.go b/tools/goctl/api/gogen/genhandlers.go index c6e02e4f..46c6ba4b 100644 --- a/tools/goctl/api/gogen/genhandlers.go +++ b/tools/goctl/api/gogen/genhandlers.go @@ -24,7 +24,6 @@ import ( func {{.handlerName}}(ctx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - l := logic.{{.logic}}(r.Context(), ctx) {{.handlerBody}} } } @@ -39,6 +38,7 @@ func {{.handlerName}}(ctx *svc.ServiceContext) http.HandlerFunc { } ` hasRespTemplate = ` + l := logic.{{.logic}}(r.Context(), ctx) {{.logicResponse}} l.{{.callee}}({{.req}}) if err != nil { httpx.Error(w, err) @@ -84,6 +84,7 @@ func genHandler(dir string, group spec.Group, route spec.Route) error { var logicBodyBuilder strings.Builder t := template.Must(template.New("hasRespTemplate").Parse(hasRespTemplate)) if err := t.Execute(&logicBodyBuilder, map[string]string{ + "logic": "New" + strings.TrimSuffix(strings.Title(handler), "Handler") + "Logic", "callee": strings.Title(strings.TrimSuffix(handler, "Handler")), "req": req, "logicResponse": logicResponse, @@ -134,7 +135,6 @@ func doGenToFile(dir, handler string, group spec.Group, route spec.Route, bodyBu t := template.Must(template.New("handlerTemplate").Parse(handlerTemplate)) buffer := new(bytes.Buffer) err = t.Execute(buffer, map[string]string{ - "logic": "New" + strings.TrimSuffix(strings.Title(handler), "Handler") + "Logic", "importPackages": genHandlerImports(group, route, parentPkg), "handlerName": handler, "handlerBody": strings.TrimSpace(bodyBuilder.String()), diff --git a/tools/goctl/api/gogen/genservicecontext.go b/tools/goctl/api/gogen/gensvc.go similarity index 92% rename from tools/goctl/api/gogen/genservicecontext.go rename to tools/goctl/api/gogen/gensvc.go index 0c380971..6d30ac15 100644 --- a/tools/goctl/api/gogen/genservicecontext.go +++ b/tools/goctl/api/gogen/gensvc.go @@ -20,10 +20,9 @@ type ServiceContext struct { Config {{.config}} } -func NewServiceContext(config {{.config}}) *ServiceContext { - return &ServiceContext{Config: config} +func NewServiceContext(c {{.config}}) *ServiceContext { + return &ServiceContext{Config: c} } - ` ) diff --git a/tools/goctl/model/sql/template/delete.go b/tools/goctl/model/sql/template/delete.go index 72d5db36..9ad97d85 100644 --- a/tools/goctl/model/sql/template/delete.go +++ b/tools/goctl/model/sql/template/delete.go @@ -2,7 +2,7 @@ package template var Delete = ` func (m *{{.upperStartCamelObject}}Model) Delete({{.lowerStartCamelPrimaryKey}} {{.dataType}}) error { - {{if .withCache}}{{if .containsIndexCache}}data,err:=m.FindOne({{.lowerStartCamelPrimaryKey}}) + {{if .withCache}}{{if .containsIndexCache}}_, err:=m.FindOne({{.lowerStartCamelPrimaryKey}}) if err!=nil{ return err }{{end}} diff --git a/tools/goctl/model/sql/template/import.go b/tools/goctl/model/sql/template/import.go index 83ec2abb..e765ad2c 100644 --- a/tools/goctl/model/sql/template/import.go +++ b/tools/goctl/model/sql/template/import.go @@ -5,7 +5,6 @@ var ( "database/sql" "fmt" "strings" - "time" "github.com/tal-tech/go-zero/core/stores/cache" "github.com/tal-tech/go-zero/core/stores/sqlc" diff --git a/tools/goctl/model/sql/template/insert.go b/tools/goctl/model/sql/template/insert.go index 5524c8b8..49d424e1 100644 --- a/tools/goctl/model/sql/template/insert.go +++ b/tools/goctl/model/sql/template/insert.go @@ -2,7 +2,7 @@ package template var Insert = ` func (m *{{.upperStartCamelObject}}Model) Insert(data {{.upperStartCamelObject}}) (sql.Result, error) { - query := ` + "`" + `insert into ` + "`" + ` + m.table + ` + "`(` + " + `{{.lowerStartCamelObject}}RowsExpectAutoSet` + " + `) value ({{.expression}})` " + ` + query := ` + "`" + `insert into ` + "`" + ` + m.table + ` + "` (` + " + `{{.lowerStartCamelObject}}RowsExpectAutoSet` + " + `) values ({{.expression}})` " + ` return m.{{if .withCache}}ExecNoCache{{else}}conn.Exec{{end}}(query, {{.expressionValues}}) } ` diff --git a/tools/goctl/rpc/ctx/ctx.go b/tools/goctl/rpc/ctx/ctx.go index 2e968437..4dd76d2c 100644 --- a/tools/goctl/rpc/ctx/ctx.go +++ b/tools/goctl/rpc/ctx/ctx.go @@ -16,27 +16,23 @@ import ( const ( flagSrc = "src" flagDir = "dir" - flagShared = "shared" flagService = "service" flagIdea = "idea" ) -type ( - RpcContext struct { - ProjectPath string - ProjectName stringx.String - ServiceName stringx.String - CurrentPath string - Module string - ProtoFileSrc string - ProtoSource string - TargetDir string - SharedDir string - console.Console - } -) +type RpcContext struct { + ProjectPath string + ProjectName stringx.String + ServiceName stringx.String + CurrentPath string + Module string + ProtoFileSrc string + ProtoSource string + TargetDir string + console.Console +} -func MustCreateRpcContext(protoSrc, targetDir, sharedDir, serviceName string, idea bool) *RpcContext { +func MustCreateRpcContext(protoSrc, targetDir, serviceName string, idea bool) *RpcContext { log := console.NewConsole(idea) info, err := prepare(log) log.Must(err) @@ -54,15 +50,9 @@ func MustCreateRpcContext(protoSrc, targetDir, sharedDir, serviceName string, id if stringx.From(targetDir).IsEmptyOrSpace() { targetDir = current } - if stringx.From(sharedDir).IsEmptyOrSpace() { - sharedDir = filepath.Join(current, "shared") - } targetDirFp, err := filepath.Abs(targetDir) log.Must(err) - sharedFp, err := filepath.Abs(sharedDir) - log.Must(err) - if stringx.From(serviceName).IsEmptyOrSpace() { serviceName = getServiceFromRpcStructure(targetDirFp) } @@ -80,7 +70,6 @@ func MustCreateRpcContext(protoSrc, targetDir, sharedDir, serviceName string, id ProtoFileSrc: srcFp, ProtoSource: filepath.Base(srcFp), TargetDir: targetDirFp, - SharedDir: sharedFp, Console: log, } } @@ -93,10 +82,9 @@ func MustCreateRpcContextFromCli(ctx *cli.Context) *RpcContext { } protoSrc := ctx.String(flagSrc) targetDir := ctx.String(flagDir) - sharedDir := ctx.String(flagShared) serviceName := ctx.String(flagService) idea := ctx.Bool(flagIdea) - return MustCreateRpcContext(protoSrc, targetDir, sharedDir, serviceName, idea) + return MustCreateRpcContext(protoSrc, targetDir, serviceName, idea) } func getServiceFromRpcStructure(targetDir string) string { diff --git a/tools/goctl/rpc/gen/gen.go b/tools/goctl/rpc/gen/gen.go index fbb526b4..ebd82e88 100644 --- a/tools/goctl/rpc/gen/gen.go +++ b/tools/goctl/rpc/gen/gen.go @@ -10,8 +10,7 @@ const ( dirConfig = "config" dirEtc = "etc" dirSvc = "svc" - dirShared = "shared" - dirHandler = "handler" + dirServer = "server" dirLogic = "logic" dirPb = "pb" dirInternal = "internal" @@ -19,13 +18,11 @@ const ( fileServiceContext = "servicecontext.go" ) -type ( - defaultRpcGenerator struct { - dirM map[string]string - Ctx *ctx.RpcContext - ast *parser.PbAst - } -) +type defaultRpcGenerator struct { + dirM map[string]string + Ctx *ctx.RpcContext + ast *parser.PbAst +} func NewDefaultRpcGenerator(ctx *ctx.RpcContext) *defaultRpcGenerator { return &defaultRpcGenerator{ @@ -80,7 +77,7 @@ func (g *defaultRpcGenerator) Generate() (err error) { return } - err = g.genShared() + err = g.genCall() if err != nil { return } diff --git a/tools/goctl/rpc/gen/genshared.go b/tools/goctl/rpc/gen/gencall.go similarity index 56% rename from tools/goctl/rpc/gen/genshared.go rename to tools/goctl/rpc/gen/gencall.go index a74b5cb0..c92d133c 100644 --- a/tools/goctl/rpc/gen/genshared.go +++ b/tools/goctl/rpc/gen/gencall.go @@ -13,9 +13,9 @@ import ( ) const ( - sharedTemplateText = `{{.head}} + callTemplateText = `{{.head}} -//go:generate mockgen -destination ./{{.name}}model_mock.go -package {{.filePackage}} -source $GOFILE +//go:generate mockgen -destination ./{{.name}}_mock.go -package {{.filePackage}} -source $GOFILE package {{.filePackage}} @@ -29,24 +29,24 @@ import ( ) type ( - {{.serviceName}}Model interface { + {{.serviceName}} interface { {{.interface}} } - default{{.serviceName}}Model struct { + default{{.serviceName}} struct { cli rpcx.Client } ) -func New{{.serviceName}}Model(cli rpcx.Client) {{.serviceName}}Model { - return &default{{.serviceName}}Model{ +func New{{.serviceName}}(cli rpcx.Client) {{.serviceName}} { + return &default{{.serviceName}}{ cli: cli, } } {{.functions}} ` - sharedTemplateTypes = `{{.head}} + callTemplateTypes = `{{.head}} package {{.filePackage}} @@ -56,11 +56,11 @@ var errJsonConvert = errors.New("json convert error") {{.types}} ` - sharedInterfaceFunctionTemplate = `{{if .hasComment}}{{.comment}} + callInterfaceFunctionTemplate = `{{if .hasComment}}{{.comment}} {{end}}{{.method}}(ctx context.Context,in *{{.pbRequest}}) {{if .hasResponse}}(*{{.pbResponse}},{{end}} error{{if .hasResponse}}){{end}}` - sharedFunctionTemplate = ` + callFunctionTemplate = ` {{if .hasComment}}{{.comment}}{{end}} -func (m *default{{.rpcServiceName}}Model) {{.method}}(ctx context.Context,in *{{.pbRequest}}) {{if .hasResponse}}(*{{.pbResponse}},{{end}} error{{if .hasResponse}}){{end}} { +func (m *default{{.rpcServiceName}}) {{.method}}(ctx context.Context,in *{{.pbRequest}}) {{if .hasResponse}}(*{{.pbResponse}},{{end}} error{{if .hasResponse}}){{end}} { var request {{.package}}.{{.pbRequest}} bts, err := jsonx.Marshal(in) if err != nil { @@ -98,59 +98,78 @@ func (m *default{{.rpcServiceName}}Model) {{.method}}(ctx context.Context,in *{{ ` ) -func (g *defaultRpcGenerator) genShared() error { - sharePackage := filepath.Base(g.Ctx.SharedDir) +func (g *defaultRpcGenerator) genCall() error { file := g.ast + if len(file.Service) == 0 { + return nil + } + if len(file.Service) > 1 { + return fmt.Errorf("we recommend only one service in a proto, currently %d", len(file.Service)) + } + typeCode, err := file.GenTypesCode() if err != nil { return err } + service := file.Service[0] + callPath, err := filepath.Abs(service.Name.Lower()) + if err != nil { + return err + } + + if err = util.MkdirIfNotExist(callPath); err != nil { + return err + } + pbPkg := file.Package remotePackage := fmt.Sprintf(`%v "%v"`, pbPkg, g.mustGetPackage(dirPb)) - filename := filepath.Join(g.Ctx.SharedDir, "types.go") + filename := filepath.Join(callPath, "types.go") head := util.GetHead(g.Ctx.ProtoSource) - err = util.With("types").GoFmt(true).Parse(sharedTemplateTypes).SaveTo(map[string]interface{}{ + err = util.With("types").GoFmt(true).Parse(callTemplateTypes).SaveTo(map[string]interface{}{ "head": head, - "filePackage": sharePackage, + "filePackage": service.Name.Lower(), "pbPkg": pbPkg, "serviceName": g.Ctx.ServiceName.Title(), "lowerStartServiceName": g.Ctx.ServiceName.UnTitle(), "types": typeCode, }, filename, true) + if err != nil { + return err + } _, err = exec.LookPath("mockgen") mockGenInstalled := err == nil - for _, service := range file.Service { - filename := filepath.Join(g.Ctx.SharedDir, fmt.Sprintf("%smodel.go", service.Name.Lower())) - functions, err := g.getFuncs(service) - if err != nil { - return err - } - iFunctions, err := g.getInterfaceFuncs(service) - if err != nil { - return err - } - mockFile := filepath.Join(g.Ctx.SharedDir, fmt.Sprintf("%smodel_mock.go", service.Name.Lower())) - os.Remove(mockFile) - err = util.With("shared").GoFmt(true).Parse(sharedTemplateText).SaveTo(map[string]interface{}{ - "name": service.Name.Lower(), - "head": head, - "filePackage": sharePackage, - "pbPkg": pbPkg, - "package": remotePackage, - "serviceName": service.Name.Title(), - "functions": strings.Join(functions, "\n"), - "interface": strings.Join(iFunctions, "\n"), - }, filename, true) - if err != nil { - return err - } - // if mockgen is already installed, it will generate code of gomock for shared files - _, err = exec.LookPath("mockgen") - if mockGenInstalled { - execx.Run(fmt.Sprintf("go generate %s", filename)) - } + filename = filepath.Join(callPath, fmt.Sprintf("%s.go", service.Name.Lower())) + functions, err := g.getFuncs(service) + if err != nil { + return err + } + + iFunctions, err := g.getInterfaceFuncs(service) + if err != nil { + return err + } + + mockFile := filepath.Join(callPath, fmt.Sprintf("%s_mock.go", service.Name.Lower())) + os.Remove(mockFile) + err = util.With("shared").GoFmt(true).Parse(callTemplateText).SaveTo(map[string]interface{}{ + "name": service.Name.Lower(), + "head": head, + "filePackage": service.Name.Lower(), + "pbPkg": pbPkg, + "package": remotePackage, + "serviceName": service.Name.Title(), + "functions": strings.Join(functions, "\n"), + "interface": strings.Join(iFunctions, "\n"), + }, filename, true) + if err != nil { + return err + } + // if mockgen is already installed, it will generate code of gomock for shared files + _, err = exec.LookPath("mockgen") + if mockGenInstalled { + execx.Run(fmt.Sprintf("go generate %s", filename)) } return nil @@ -169,7 +188,7 @@ func (g *defaultRpcGenerator) getFuncs(service *parser.RpcService) ([]string, er if len(method.Document) > 0 { comment = method.Document[0] } - buffer, err := util.With("sharedFn").Parse(sharedFunctionTemplate).Execute(map[string]interface{}{ + buffer, err := util.With("sharedFn").Parse(callFunctionTemplate).Execute(map[string]interface{}{ "rpcServiceName": service.Name.Title(), "method": method.Name.Title(), "package": pkgName, @@ -191,6 +210,7 @@ func (g *defaultRpcGenerator) getFuncs(service *parser.RpcService) ([]string, er func (g *defaultRpcGenerator) getInterfaceFuncs(service *parser.RpcService) ([]string, error) { file := g.ast functions := make([]string, 0) + for _, method := range service.Funcs { data, found := file.Strcuts[strings.ToLower(method.OutType)] if found { @@ -200,19 +220,21 @@ func (g *defaultRpcGenerator) getInterfaceFuncs(service *parser.RpcService) ([]s if len(method.Document) > 0 { comment = method.Document[0] } - buffer, err := util.With("interfaceFn").Parse(sharedInterfaceFunctionTemplate).Execute(map[string]interface{}{ - "hasComment": len(method.Document) > 0, - "comment": comment, - "method": method.Name.Title(), - "pbRequest": method.InType, - "pbResponse": method.OutType, - "hasResponse": found, - }) + buffer, err := util.With("interfaceFn").Parse(callInterfaceFunctionTemplate).Execute( + map[string]interface{}{ + "hasComment": len(method.Document) > 0, + "comment": comment, + "method": method.Name.Title(), + "pbRequest": method.InType, + "pbResponse": method.OutType, + "hasResponse": found, + }) if err != nil { return nil, err } functions = append(functions, buffer.String()) } + return functions, nil } diff --git a/tools/goctl/rpc/gen/gendir.go b/tools/goctl/rpc/gen/gendir.go index 62aadbc2..307e3989 100644 --- a/tools/goctl/rpc/gen/gendir.go +++ b/tools/goctl/rpc/gen/gendir.go @@ -23,11 +23,10 @@ func (g *defaultRpcGenerator) createDir() error { m[dirEtc] = filepath.Join(ctx.TargetDir, dirEtc) m[dirInternal] = filepath.Join(ctx.TargetDir, dirInternal) m[dirConfig] = filepath.Join(ctx.TargetDir, dirInternal, dirConfig) - m[dirHandler] = filepath.Join(ctx.TargetDir, dirInternal, dirHandler) + m[dirServer] = filepath.Join(ctx.TargetDir, dirInternal, dirServer) m[dirLogic] = filepath.Join(ctx.TargetDir, dirInternal, dirLogic) m[dirPb] = filepath.Join(ctx.TargetDir, dirPb) m[dirSvc] = filepath.Join(ctx.TargetDir, dirInternal, dirSvc) - m[dirShared] = g.Ctx.SharedDir for _, d := range m { err := util.MkdirIfNotExist(d) if err != nil { diff --git a/tools/goctl/rpc/gen/genetc.go b/tools/goctl/rpc/gen/genetc.go index 85089219..514dd15d 100644 --- a/tools/goctl/rpc/gen/genetc.go +++ b/tools/goctl/rpc/gen/genetc.go @@ -13,7 +13,7 @@ Log: ListenOn: 127.0.0.1:8080 Etcd: Hosts: - - 127.0.0.1:6379 + - 127.0.0.1:2379 Key: {{.serviceName}}.rpc ` diff --git a/tools/goctl/rpc/gen/genlogic.go b/tools/goctl/rpc/gen/genlogic.go index 4b34bb2c..4c2de0ed 100644 --- a/tools/goctl/rpc/gen/genlogic.go +++ b/tools/goctl/rpc/gen/genlogic.go @@ -36,11 +36,9 @@ func New{{.logicName}}(ctx context.Context,svcCtx *svc.ServiceContext) *{{.logic ` logicFunctionTemplate = `{{if .hasComment}}{{.comment}}{{end}} func (l *{{.logicName}}) {{.method}} (in *{{.package}}.{{.request}}) (*{{.package}}.{{.response}}, error) { - var resp {{.package}}.{{.response}} - // todo: add your logic here and delete this line - return &resp,nil + return &{{.package}}.{{.response}}{}, nil } ` ) diff --git a/tools/goctl/rpc/gen/genmain.go b/tools/goctl/rpc/gen/genmain.go index 73138abc..5512c4de 100644 --- a/tools/goctl/rpc/gen/genmain.go +++ b/tools/goctl/rpc/gen/genmain.go @@ -56,7 +56,7 @@ func (g *defaultRpcGenerator) genMain() error { imports := make([]string, 0) pbImport := fmt.Sprintf(`%v "%v"`, pkg, g.mustGetPackage(dirPb)) svcImport := fmt.Sprintf(`"%v"`, g.mustGetPackage(dirSvc)) - remoteImport := fmt.Sprintf(`"%v"`, g.mustGetPackage(dirHandler)) + remoteImport := fmt.Sprintf(`"%v"`, g.mustGetPackage(dirServer)) configImport := fmt.Sprintf(`"%v"`, g.mustGetPackage(dirConfig)) imports = append(imports, configImport, pbImport, remoteImport, svcImport) srv, registers := g.genServer(pkg, file.Service) @@ -76,7 +76,7 @@ func (g *defaultRpcGenerator) genServer(pkg string, list []*parser.RpcService) ( list2 := make([]string, 0) for _, item := range list { name := item.Name.UnTitle() - list1 = append(list1, fmt.Sprintf("%sSrv := handler.New%sServer(ctx)", name, item.Name.Title())) + list1 = append(list1, fmt.Sprintf("%sSrv := server.New%sServer(ctx)", name, item.Name.Title())) list2 = append(list2, fmt.Sprintf("%s.Register%sServer(grpcServer, %sSrv)", pkg, item.Name.Title(), name)) } return strings.Join(list1, "\n"), strings.Join(list2, "\n") diff --git a/tools/goctl/rpc/gen/genhandler.go b/tools/goctl/rpc/gen/genserver.go similarity index 87% rename from tools/goctl/rpc/gen/genhandler.go rename to tools/goctl/rpc/gen/genserver.go index e411f7f7..470c41a3 100644 --- a/tools/goctl/rpc/gen/genhandler.go +++ b/tools/goctl/rpc/gen/genserver.go @@ -10,9 +10,9 @@ import ( ) const ( - handlerTemplate = `{{.head}} + serverTemplate = `{{.head}} -package handler +package server import ( "context" @@ -43,7 +43,7 @@ func (s *{{.server}}Server) {{.method}} (ctx context.Context, in *{{.package}}.{ ) func (g *defaultRpcGenerator) genHandler() error { - handlerPath := g.dirM[dirHandler] + serverPath := g.dirM[dirServer] file := g.ast pkg := file.Package pbImport := fmt.Sprintf(`%v "%v"`, pkg, g.mustGetPackage(dirPb)) @@ -56,19 +56,19 @@ func (g *defaultRpcGenerator) genHandler() error { } head := util.GetHead(g.Ctx.ProtoSource) for _, service := range file.Service { - filename := fmt.Sprintf("%vhandler.go", service.Name.Lower()) - handlerFile := filepath.Join(handlerPath, filename) + filename := fmt.Sprintf("%vserver.go", service.Name.Lower()) + serverFile := filepath.Join(serverPath, filename) funcList, err := g.genFunctions(service) if err != nil { return err } - err = util.With("server").GoFmt(true).Parse(handlerTemplate).SaveTo(map[string]interface{}{ + err = util.With("server").GoFmt(true).Parse(serverTemplate).SaveTo(map[string]interface{}{ "head": head, "types": fmt.Sprintf(typeFmt, service.Name.Title()), "server": service.Name.Title(), "imports": strings.Join(imports, "\n\t"), "funcs": strings.Join(funcList, "\n"), - }, handlerFile, true) + }, serverFile, true) if err != nil { return err }