update shorturl doc

master v1.0.7
kevin 4 years ago
parent 6e3d99e869
commit 6c4a4be5d2

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

@ -12,10 +12,14 @@
## 2. 准备工作 ## 2. 准备工作
* 准备goctl工具在任意目录下进行目的是为了编译goctl工具 * 安装etcd, mysql, redis
1. `git clone https://github.com/tal-tech/go-zero` * 准备goctl工具
2. 在`tools/goctl`目录下编译goctl工具`go build goctl.go` * 直接从`https://github.com/tal-tech/go-zero/releases`下载最新版,后续会加上自动更新
3. 将生成的goctl放到`$PATH`下确保goctl命令可运行 * 也可以从源码编译在任意目录下进行目的是为了编译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`
* 在`shorturl`目录下执行`go mod init shorturl`初始化`go.mod` * 在`shorturl`目录下执行`go mod init shorturl`初始化`go.mod`
@ -128,6 +132,8 @@
* 可以通过`goctl`生成各种客户端语言的api调用代码 * 可以通过`goctl`生成各种客户端语言的api调用代码
* 到这里你已经可以通过goctl生成客户端代码给客户端同学并行开发了支持多种语言详见文档
## 4. 编写shorten rpc服务 ## 4. 编写shorten rpc服务
* 在`rpc/shorten`目录下编写`shorten.proto`文件 * 在`rpc/shorten`目录下编写`shorten.proto`文件
@ -169,24 +175,24 @@
``` ```
rpc/shorten rpc/shorten
├── etc ├── etc
│   └── shorten.yaml // 配置文件 │   └── shorten.yaml // 配置文件
├── internal ├── internal
│   ├── config │   ├── config
│   │   └── config.go // 配置定义 │   │   └── config.go // 配置定义
│   ├── handler
│   │   └── shortenerhandler.go // api handler, 不需要修改
│   ├── logic │   ├── logic
│   │   └── shortenlogic.go // api业务逻辑在这里实现 │   │   └── shortenlogic.go // rpc业务逻辑在这里实现
│   ├── server
│   │   └── shortenerserver.go // 调用入口, 不需要修改
│   └── svc │   └── svc
│   └── servicecontext.go // 定义ServiceContext传递依赖 │   └── servicecontext.go // 定义ServiceContext传递依赖
├── pb ├── pb
│   └── shorten.pb.go │   └── shorten.pb.go
├── shared
│   ├── shortenermodel.go // 提供了外部调用方法,无需修改
│   ├── shortenermodel_mock.go // mock方法测试用
│   └── types.go // request/response结构体定义
├── shorten.go // rpc服务main函数 ├── 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 rpc/expand
├── etc ├── etc
│   └── expand.yaml // 配置文件 │   └── expand.yaml // 配置文件
├── expand.go // rpc服务main函数
├── expand.proto
├── expander
│   ├── expander.go // 提供了外部调用方法,无需修改
│   ├── expander_mock.go // mock方法测试用
│   └── types.go // request/response结构体定义
├── internal ├── internal
│   ├── config │   ├── config
│   │   └── config.go // 配置定义 │   │   └── config.go // 配置定义
│   ├── handler
│   │   └── expanderhandler.go // api handler, 不需要修改
│   ├── logic │   ├── logic
│   │   └── expandlogic.go // api业务逻辑在这里实现 │   │   └── expandlogic.go // rpc业务逻辑在这里实现
│   ├── server
│   │   └── expanderserver.go // 调用入口, 不需要修改
│   └── svc │   └── svc
│   └── servicecontext.go // 定义ServiceContext传递依赖 │   └── servicecontext.go // 定义ServiceContext传递依赖
├── pb └── pb
│   └── expand.pb.go └── expand.pb.go
├── shared
│   ├── expandermodel.go // 提供了外部调用方法,无需修改
│   ├── expandermodel_mock.go // mock方法测试用
│   └── types.go // request/response结构体定义
├── expand.go // rpc服务main函数
└── expand.proto
``` ```
修改`etc/expand.yaml`里面的`ListenOn`的端口为`8081`,因为`8080`已经被`shorten`服务占用了 修改`etc/expand.yaml`里面的`ListenOn`的端口为`8081`,因为`8080`已经被`shorten`服务占用了
@ -270,7 +276,126 @@
`etc/expand.yaml`文件里可以修改侦听端口等配置 `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代码 ## 7. 定义数据库表结构并生成CRUD+cache代码
@ -317,14 +442,206 @@
## 8. 修改shorten/expand rpc代码调用crud+cache代码 ## 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. 完整调用演示 ## 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. 总结
我们一直强调**工具大于约定和文档**。 我们一直强调**工具大于约定和文档**。
另外,我们在保持简单的同时也尽可能把微服务治理的复杂度封装到了框架内部,极大的降低了开发人员的心智负担,使得业务开发得以快速推进。 go-zero不只是一个框架更是一个建立在框架+工具基础上的,简化和规范了整个微服务构建的技术体系。
我们在保持简单的同时也尽可能把微服务治理的复杂度封装到了框架内部,极大的降低了开发人员的心智负担,使得业务开发得以快速推进。
通过go-zero+goctl生成的代码包含了微服务治理的各种组件包括并发控制、自适应熔断、自适应降载、自动缓存控制等可以轻松部署以承载巨大访问量。

@ -89,6 +89,8 @@ go get -u github.com/tal-tech/go-zero
## 6. Quick Start ## 6. Quick Start
0. 完整示例请查看[从0到1快速构建一个高并发的微服务系统](doc/shorturl.md)
1. 编译goctl工具 1. 编译goctl工具
```shell ```shell

@ -24,7 +24,6 @@ import (
func {{.handlerName}}(ctx *svc.ServiceContext) http.HandlerFunc { func {{.handlerName}}(ctx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
l := logic.{{.logic}}(r.Context(), ctx)
{{.handlerBody}} {{.handlerBody}}
} }
} }
@ -39,6 +38,7 @@ func {{.handlerName}}(ctx *svc.ServiceContext) http.HandlerFunc {
} }
` `
hasRespTemplate = ` hasRespTemplate = `
l := logic.{{.logic}}(r.Context(), ctx)
{{.logicResponse}} l.{{.callee}}({{.req}}) {{.logicResponse}} l.{{.callee}}({{.req}})
if err != nil { if err != nil {
httpx.Error(w, err) httpx.Error(w, err)
@ -84,6 +84,7 @@ func genHandler(dir string, group spec.Group, route spec.Route) error {
var logicBodyBuilder strings.Builder var logicBodyBuilder strings.Builder
t := template.Must(template.New("hasRespTemplate").Parse(hasRespTemplate)) t := template.Must(template.New("hasRespTemplate").Parse(hasRespTemplate))
if err := t.Execute(&logicBodyBuilder, map[string]string{ if err := t.Execute(&logicBodyBuilder, map[string]string{
"logic": "New" + strings.TrimSuffix(strings.Title(handler), "Handler") + "Logic",
"callee": strings.Title(strings.TrimSuffix(handler, "Handler")), "callee": strings.Title(strings.TrimSuffix(handler, "Handler")),
"req": req, "req": req,
"logicResponse": logicResponse, "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)) t := template.Must(template.New("handlerTemplate").Parse(handlerTemplate))
buffer := new(bytes.Buffer) buffer := new(bytes.Buffer)
err = t.Execute(buffer, map[string]string{ err = t.Execute(buffer, map[string]string{
"logic": "New" + strings.TrimSuffix(strings.Title(handler), "Handler") + "Logic",
"importPackages": genHandlerImports(group, route, parentPkg), "importPackages": genHandlerImports(group, route, parentPkg),
"handlerName": handler, "handlerName": handler,
"handlerBody": strings.TrimSpace(bodyBuilder.String()), "handlerBody": strings.TrimSpace(bodyBuilder.String()),

@ -20,10 +20,9 @@ type ServiceContext struct {
Config {{.config}} Config {{.config}}
} }
func NewServiceContext(config {{.config}}) *ServiceContext { func NewServiceContext(c {{.config}}) *ServiceContext {
return &ServiceContext{Config: config} return &ServiceContext{Config: c}
} }
` `
) )

@ -2,7 +2,7 @@ package template
var Delete = ` var Delete = `
func (m *{{.upperStartCamelObject}}Model) Delete({{.lowerStartCamelPrimaryKey}} {{.dataType}}) error { 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{ if err!=nil{
return err return err
}{{end}} }{{end}}

@ -5,7 +5,6 @@ var (
"database/sql" "database/sql"
"fmt" "fmt"
"strings" "strings"
"time"
"github.com/tal-tech/go-zero/core/stores/cache" "github.com/tal-tech/go-zero/core/stores/cache"
"github.com/tal-tech/go-zero/core/stores/sqlc" "github.com/tal-tech/go-zero/core/stores/sqlc"

@ -2,7 +2,7 @@ package template
var Insert = ` var Insert = `
func (m *{{.upperStartCamelObject}}Model) Insert(data {{.upperStartCamelObject}}) (sql.Result, error) { 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}}) return m.{{if .withCache}}ExecNoCache{{else}}conn.Exec{{end}}(query, {{.expressionValues}})
} }
` `

@ -16,27 +16,23 @@ import (
const ( const (
flagSrc = "src" flagSrc = "src"
flagDir = "dir" flagDir = "dir"
flagShared = "shared"
flagService = "service" flagService = "service"
flagIdea = "idea" flagIdea = "idea"
) )
type ( type RpcContext struct {
RpcContext struct { ProjectPath string
ProjectPath string ProjectName stringx.String
ProjectName stringx.String ServiceName stringx.String
ServiceName stringx.String CurrentPath string
CurrentPath string Module string
Module string ProtoFileSrc string
ProtoFileSrc string ProtoSource string
ProtoSource string TargetDir string
TargetDir string console.Console
SharedDir 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) log := console.NewConsole(idea)
info, err := prepare(log) info, err := prepare(log)
log.Must(err) log.Must(err)
@ -54,15 +50,9 @@ func MustCreateRpcContext(protoSrc, targetDir, sharedDir, serviceName string, id
if stringx.From(targetDir).IsEmptyOrSpace() { if stringx.From(targetDir).IsEmptyOrSpace() {
targetDir = current targetDir = current
} }
if stringx.From(sharedDir).IsEmptyOrSpace() {
sharedDir = filepath.Join(current, "shared")
}
targetDirFp, err := filepath.Abs(targetDir) targetDirFp, err := filepath.Abs(targetDir)
log.Must(err) log.Must(err)
sharedFp, err := filepath.Abs(sharedDir)
log.Must(err)
if stringx.From(serviceName).IsEmptyOrSpace() { if stringx.From(serviceName).IsEmptyOrSpace() {
serviceName = getServiceFromRpcStructure(targetDirFp) serviceName = getServiceFromRpcStructure(targetDirFp)
} }
@ -80,7 +70,6 @@ func MustCreateRpcContext(protoSrc, targetDir, sharedDir, serviceName string, id
ProtoFileSrc: srcFp, ProtoFileSrc: srcFp,
ProtoSource: filepath.Base(srcFp), ProtoSource: filepath.Base(srcFp),
TargetDir: targetDirFp, TargetDir: targetDirFp,
SharedDir: sharedFp,
Console: log, Console: log,
} }
} }
@ -93,10 +82,9 @@ func MustCreateRpcContextFromCli(ctx *cli.Context) *RpcContext {
} }
protoSrc := ctx.String(flagSrc) protoSrc := ctx.String(flagSrc)
targetDir := ctx.String(flagDir) targetDir := ctx.String(flagDir)
sharedDir := ctx.String(flagShared)
serviceName := ctx.String(flagService) serviceName := ctx.String(flagService)
idea := ctx.Bool(flagIdea) idea := ctx.Bool(flagIdea)
return MustCreateRpcContext(protoSrc, targetDir, sharedDir, serviceName, idea) return MustCreateRpcContext(protoSrc, targetDir, serviceName, idea)
} }
func getServiceFromRpcStructure(targetDir string) string { func getServiceFromRpcStructure(targetDir string) string {

@ -10,8 +10,7 @@ const (
dirConfig = "config" dirConfig = "config"
dirEtc = "etc" dirEtc = "etc"
dirSvc = "svc" dirSvc = "svc"
dirShared = "shared" dirServer = "server"
dirHandler = "handler"
dirLogic = "logic" dirLogic = "logic"
dirPb = "pb" dirPb = "pb"
dirInternal = "internal" dirInternal = "internal"
@ -19,13 +18,11 @@ const (
fileServiceContext = "servicecontext.go" fileServiceContext = "servicecontext.go"
) )
type ( type defaultRpcGenerator struct {
defaultRpcGenerator struct { dirM map[string]string
dirM map[string]string Ctx *ctx.RpcContext
Ctx *ctx.RpcContext ast *parser.PbAst
ast *parser.PbAst }
}
)
func NewDefaultRpcGenerator(ctx *ctx.RpcContext) *defaultRpcGenerator { func NewDefaultRpcGenerator(ctx *ctx.RpcContext) *defaultRpcGenerator {
return &defaultRpcGenerator{ return &defaultRpcGenerator{
@ -80,7 +77,7 @@ func (g *defaultRpcGenerator) Generate() (err error) {
return return
} }
err = g.genShared() err = g.genCall()
if err != nil { if err != nil {
return return
} }

@ -13,9 +13,9 @@ import (
) )
const ( 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}} package {{.filePackage}}
@ -29,24 +29,24 @@ import (
) )
type ( type (
{{.serviceName}}Model interface { {{.serviceName}} interface {
{{.interface}} {{.interface}}
} }
default{{.serviceName}}Model struct { default{{.serviceName}} struct {
cli rpcx.Client cli rpcx.Client
} }
) )
func New{{.serviceName}}Model(cli rpcx.Client) {{.serviceName}}Model { func New{{.serviceName}}(cli rpcx.Client) {{.serviceName}} {
return &default{{.serviceName}}Model{ return &default{{.serviceName}}{
cli: cli, cli: cli,
} }
} }
{{.functions}} {{.functions}}
` `
sharedTemplateTypes = `{{.head}} callTemplateTypes = `{{.head}}
package {{.filePackage}} package {{.filePackage}}
@ -56,11 +56,11 @@ var errJsonConvert = errors.New("json convert error")
{{.types}} {{.types}}
` `
sharedInterfaceFunctionTemplate = `{{if .hasComment}}{{.comment}} callInterfaceFunctionTemplate = `{{if .hasComment}}{{.comment}}
{{end}}{{.method}}(ctx context.Context,in *{{.pbRequest}}) {{if .hasResponse}}(*{{.pbResponse}},{{end}} error{{if .hasResponse}}){{end}}` {{end}}{{.method}}(ctx context.Context,in *{{.pbRequest}}) {{if .hasResponse}}(*{{.pbResponse}},{{end}} error{{if .hasResponse}}){{end}}`
sharedFunctionTemplate = ` callFunctionTemplate = `
{{if .hasComment}}{{.comment}}{{end}} {{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}} var request {{.package}}.{{.pbRequest}}
bts, err := jsonx.Marshal(in) bts, err := jsonx.Marshal(in)
if err != nil { if err != nil {
@ -98,59 +98,78 @@ func (m *default{{.rpcServiceName}}Model) {{.method}}(ctx context.Context,in *{{
` `
) )
func (g *defaultRpcGenerator) genShared() error { func (g *defaultRpcGenerator) genCall() error {
sharePackage := filepath.Base(g.Ctx.SharedDir)
file := g.ast 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() typeCode, err := file.GenTypesCode()
if err != nil { if err != nil {
return err 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 pbPkg := file.Package
remotePackage := fmt.Sprintf(`%v "%v"`, pbPkg, g.mustGetPackage(dirPb)) 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) 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, "head": head,
"filePackage": sharePackage, "filePackage": service.Name.Lower(),
"pbPkg": pbPkg, "pbPkg": pbPkg,
"serviceName": g.Ctx.ServiceName.Title(), "serviceName": g.Ctx.ServiceName.Title(),
"lowerStartServiceName": g.Ctx.ServiceName.UnTitle(), "lowerStartServiceName": g.Ctx.ServiceName.UnTitle(),
"types": typeCode, "types": typeCode,
}, filename, true) }, filename, true)
if err != nil {
return err
}
_, err = exec.LookPath("mockgen") _, err = exec.LookPath("mockgen")
mockGenInstalled := err == nil mockGenInstalled := err == nil
for _, service := range file.Service { filename = filepath.Join(callPath, fmt.Sprintf("%s.go", service.Name.Lower()))
filename := filepath.Join(g.Ctx.SharedDir, fmt.Sprintf("%smodel.go", service.Name.Lower())) functions, err := g.getFuncs(service)
functions, err := g.getFuncs(service) if err != nil {
if err != nil { return err
return err }
}
iFunctions, err := g.getInterfaceFuncs(service) iFunctions, err := g.getInterfaceFuncs(service)
if err != nil { if err != nil {
return err return err
} }
mockFile := filepath.Join(g.Ctx.SharedDir, fmt.Sprintf("%smodel_mock.go", service.Name.Lower()))
os.Remove(mockFile) mockFile := filepath.Join(callPath, fmt.Sprintf("%s_mock.go", service.Name.Lower()))
err = util.With("shared").GoFmt(true).Parse(sharedTemplateText).SaveTo(map[string]interface{}{ os.Remove(mockFile)
"name": service.Name.Lower(), err = util.With("shared").GoFmt(true).Parse(callTemplateText).SaveTo(map[string]interface{}{
"head": head, "name": service.Name.Lower(),
"filePackage": sharePackage, "head": head,
"pbPkg": pbPkg, "filePackage": service.Name.Lower(),
"package": remotePackage, "pbPkg": pbPkg,
"serviceName": service.Name.Title(), "package": remotePackage,
"functions": strings.Join(functions, "\n"), "serviceName": service.Name.Title(),
"interface": strings.Join(iFunctions, "\n"), "functions": strings.Join(functions, "\n"),
}, filename, true) "interface": strings.Join(iFunctions, "\n"),
if err != nil { }, filename, true)
return err if err != nil {
} return err
// if mockgen is already installed, it will generate code of gomock for shared files }
_, err = exec.LookPath("mockgen") // if mockgen is already installed, it will generate code of gomock for shared files
if mockGenInstalled { _, err = exec.LookPath("mockgen")
execx.Run(fmt.Sprintf("go generate %s", filename)) if mockGenInstalled {
} execx.Run(fmt.Sprintf("go generate %s", filename))
} }
return nil return nil
@ -169,7 +188,7 @@ func (g *defaultRpcGenerator) getFuncs(service *parser.RpcService) ([]string, er
if len(method.Document) > 0 { if len(method.Document) > 0 {
comment = 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(), "rpcServiceName": service.Name.Title(),
"method": method.Name.Title(), "method": method.Name.Title(),
"package": pkgName, "package": pkgName,
@ -191,6 +210,7 @@ func (g *defaultRpcGenerator) getFuncs(service *parser.RpcService) ([]string, er
func (g *defaultRpcGenerator) getInterfaceFuncs(service *parser.RpcService) ([]string, error) { func (g *defaultRpcGenerator) getInterfaceFuncs(service *parser.RpcService) ([]string, error) {
file := g.ast file := g.ast
functions := make([]string, 0) functions := make([]string, 0)
for _, method := range service.Funcs { for _, method := range service.Funcs {
data, found := file.Strcuts[strings.ToLower(method.OutType)] data, found := file.Strcuts[strings.ToLower(method.OutType)]
if found { if found {
@ -200,19 +220,21 @@ func (g *defaultRpcGenerator) getInterfaceFuncs(service *parser.RpcService) ([]s
if len(method.Document) > 0 { if len(method.Document) > 0 {
comment = method.Document[0] comment = method.Document[0]
} }
buffer, err := util.With("interfaceFn").Parse(sharedInterfaceFunctionTemplate).Execute(map[string]interface{}{ buffer, err := util.With("interfaceFn").Parse(callInterfaceFunctionTemplate).Execute(
"hasComment": len(method.Document) > 0, map[string]interface{}{
"comment": comment, "hasComment": len(method.Document) > 0,
"method": method.Name.Title(), "comment": comment,
"pbRequest": method.InType, "method": method.Name.Title(),
"pbResponse": method.OutType, "pbRequest": method.InType,
"hasResponse": found, "pbResponse": method.OutType,
}) "hasResponse": found,
})
if err != nil { if err != nil {
return nil, err return nil, err
} }
functions = append(functions, buffer.String()) functions = append(functions, buffer.String())
} }
return functions, nil return functions, nil
} }

@ -23,11 +23,10 @@ func (g *defaultRpcGenerator) createDir() error {
m[dirEtc] = filepath.Join(ctx.TargetDir, dirEtc) m[dirEtc] = filepath.Join(ctx.TargetDir, dirEtc)
m[dirInternal] = filepath.Join(ctx.TargetDir, dirInternal) m[dirInternal] = filepath.Join(ctx.TargetDir, dirInternal)
m[dirConfig] = filepath.Join(ctx.TargetDir, dirInternal, dirConfig) 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[dirLogic] = filepath.Join(ctx.TargetDir, dirInternal, dirLogic)
m[dirPb] = filepath.Join(ctx.TargetDir, dirPb) m[dirPb] = filepath.Join(ctx.TargetDir, dirPb)
m[dirSvc] = filepath.Join(ctx.TargetDir, dirInternal, dirSvc) m[dirSvc] = filepath.Join(ctx.TargetDir, dirInternal, dirSvc)
m[dirShared] = g.Ctx.SharedDir
for _, d := range m { for _, d := range m {
err := util.MkdirIfNotExist(d) err := util.MkdirIfNotExist(d)
if err != nil { if err != nil {

@ -13,7 +13,7 @@ Log:
ListenOn: 127.0.0.1:8080 ListenOn: 127.0.0.1:8080
Etcd: Etcd:
Hosts: Hosts:
- 127.0.0.1:6379 - 127.0.0.1:2379
Key: {{.serviceName}}.rpc Key: {{.serviceName}}.rpc
` `

@ -36,11 +36,9 @@ func New{{.logicName}}(ctx context.Context,svcCtx *svc.ServiceContext) *{{.logic
` `
logicFunctionTemplate = `{{if .hasComment}}{{.comment}}{{end}} logicFunctionTemplate = `{{if .hasComment}}{{.comment}}{{end}}
func (l *{{.logicName}}) {{.method}} (in *{{.package}}.{{.request}}) (*{{.package}}.{{.response}}, error) { func (l *{{.logicName}}) {{.method}} (in *{{.package}}.{{.request}}) (*{{.package}}.{{.response}}, error) {
var resp {{.package}}.{{.response}}
// todo: add your logic here and delete this line // todo: add your logic here and delete this line
return &resp,nil return &{{.package}}.{{.response}}{}, nil
} }
` `
) )

@ -56,7 +56,7 @@ func (g *defaultRpcGenerator) genMain() error {
imports := make([]string, 0) imports := make([]string, 0)
pbImport := fmt.Sprintf(`%v "%v"`, pkg, g.mustGetPackage(dirPb)) pbImport := fmt.Sprintf(`%v "%v"`, pkg, g.mustGetPackage(dirPb))
svcImport := fmt.Sprintf(`"%v"`, g.mustGetPackage(dirSvc)) 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)) configImport := fmt.Sprintf(`"%v"`, g.mustGetPackage(dirConfig))
imports = append(imports, configImport, pbImport, remoteImport, svcImport) imports = append(imports, configImport, pbImport, remoteImport, svcImport)
srv, registers := g.genServer(pkg, file.Service) srv, registers := g.genServer(pkg, file.Service)
@ -76,7 +76,7 @@ func (g *defaultRpcGenerator) genServer(pkg string, list []*parser.RpcService) (
list2 := make([]string, 0) list2 := make([]string, 0)
for _, item := range list { for _, item := range list {
name := item.Name.UnTitle() 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)) list2 = append(list2, fmt.Sprintf("%s.Register%sServer(grpcServer, %sSrv)", pkg, item.Name.Title(), name))
} }
return strings.Join(list1, "\n"), strings.Join(list2, "\n") return strings.Join(list1, "\n"), strings.Join(list2, "\n")

@ -10,9 +10,9 @@ import (
) )
const ( const (
handlerTemplate = `{{.head}} serverTemplate = `{{.head}}
package handler package server
import ( import (
"context" "context"
@ -43,7 +43,7 @@ func (s *{{.server}}Server) {{.method}} (ctx context.Context, in *{{.package}}.{
) )
func (g *defaultRpcGenerator) genHandler() error { func (g *defaultRpcGenerator) genHandler() error {
handlerPath := g.dirM[dirHandler] serverPath := g.dirM[dirServer]
file := g.ast file := g.ast
pkg := file.Package pkg := file.Package
pbImport := fmt.Sprintf(`%v "%v"`, pkg, g.mustGetPackage(dirPb)) pbImport := fmt.Sprintf(`%v "%v"`, pkg, g.mustGetPackage(dirPb))
@ -56,19 +56,19 @@ func (g *defaultRpcGenerator) genHandler() error {
} }
head := util.GetHead(g.Ctx.ProtoSource) head := util.GetHead(g.Ctx.ProtoSource)
for _, service := range file.Service { for _, service := range file.Service {
filename := fmt.Sprintf("%vhandler.go", service.Name.Lower()) filename := fmt.Sprintf("%vserver.go", service.Name.Lower())
handlerFile := filepath.Join(handlerPath, filename) serverFile := filepath.Join(serverPath, filename)
funcList, err := g.genFunctions(service) funcList, err := g.genFunctions(service)
if err != nil { if err != nil {
return err 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, "head": head,
"types": fmt.Sprintf(typeFmt, service.Name.Title()), "types": fmt.Sprintf(typeFmt, service.Name.Title()),
"server": service.Name.Title(), "server": service.Name.Title(),
"imports": strings.Join(imports, "\n\t"), "imports": strings.Join(imports, "\n\t"),
"funcs": strings.Join(funcList, "\n"), "funcs": strings.Join(funcList, "\n"),
}, handlerFile, true) }, serverFile, true)
if err != nil { if err != nil {
return err return err
} }
Loading…
Cancel
Save