diff --git a/.github/workflows/markdown.yml b/.github/workflows/markdown.yml new file mode 100644 index 00000000..339536e4 --- /dev/null +++ b/.github/workflows/markdown.yml @@ -0,0 +1,22 @@ +name: Markdown linter + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + + linter: + name: markdown linter + runs-on: ubuntu-latest + steps: + + - name: Check out code + uses: actions/checkout@v2 + + - name: Markdown Linting Action + uses: avto-dev/markdown-lint@v1.3.1 + with: + args: 'find . -name "*.md"' diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 00000000..d2468b4a --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,6 @@ +{ + "MD010": false, + "MD013": false, + "MD033": false, + "MD034": false +} diff --git a/doc/bookstore-en.md b/doc/bookstore-en.md index 612b6ce9..017d30d4 100644 --- a/doc/bookstore-en.md +++ b/doc/bookstore-en.md @@ -1,8 +1,8 @@ -English | [简体中文](bookstore.md) - # Rapid development of microservices - multiple RPCs -## 0. Why building microservices are so difficult? +English | [简体中文](bookstore.md) + +## 0. Why building microservices are so difficult To build a well working microservice, we need lots of knowledges from different aspects. @@ -25,7 +25,7 @@ As well, we always adhere to the idea that **prefer tools over conventions and d Let’s take the shorturl microservice as a quick example to demonstrate how to quickly create microservices by using [go-zero](https://github.com/tal-tech/go-zero). After finishing this tutorial, you’ll find that it’s so easy to write microservices! -## 1. What is a bookstore service? +## 1. What is a bookstore service For simplicity, the bookstore service only contains two functionalities, adding books and quering prices. @@ -71,7 +71,7 @@ And now, let’s walk through the complete flow of quickly create a microservice * use goctl to generate `api/bookstore.api` - ``` + ```Plain Text goctl api -o bookstore.api ``` @@ -128,7 +128,7 @@ And now, let’s walk through the complete flow of quickly create a microservice the generated file structure looks like: - ``` + ```Plain Text api ├── bookstore.api // api definition ├── bookstore.go // main entrance @@ -188,13 +188,13 @@ And now, let’s walk through the complete flow of quickly create a microservice * under directory `rpc/add` create `add.proto` file ```shell -goctl rpc template -o add.proto + goctl rpc template -o add.proto ``` edit the file and make the code looks like: ```protobuf -syntax = "proto3"; + syntax = "proto3"; package add; @@ -220,7 +220,7 @@ syntax = "proto3"; the generated file structure looks like: - ``` + ```Plain Text rpc/add ├── add.go // rpc main entrance ├── add.proto // rpc definition @@ -242,7 +242,6 @@ syntax = "proto3"; └── pb └── add.pb.go ``` - just run it, looks like: @@ -258,13 +257,13 @@ you can change the listening port in file `etc/add.yaml`. * under directory `rpc/check` create `check.proto` file ```shell -goctl rpc template -o check.proto + goctl rpc template -o check.proto ``` edit the file and make the code looks like: ```protobuf -syntax = "proto3"; + syntax = "proto3"; package check; @@ -290,7 +289,7 @@ syntax = "proto3"; the generated file structure looks like: - ``` + ```Plain Text rpc/check ├── check.go // rpc main entrance ├── check.proto // rpc definition @@ -315,7 +314,7 @@ syntax = "proto3"; you can change the listening port in `etc/check.yaml`. - we need to change the port in `etc/check.yaml` to `8081`, because `8080 ` is used by `add` service. + we need to change the port in `etc/check.yaml` to `8081`, because `8080` is used by `add` service. just run it, looks like: @@ -444,7 +443,7 @@ Till now, we’ve done the modification of API Gateway. All the manually added c source book.sql; ``` -* under the directory `rpc/model execute the following command to genrate CRUD+cache code, `-c` means using `redis cache` +* under the directory `rpc/model` execute the following command to genrate CRUD+cache code, `-c` means using `redis cache` ```shell goctl model mysql ddl -c -src book.sql -dir . @@ -454,7 +453,7 @@ Till now, we’ve done the modification of API Gateway. All the manually added c the generated file structure looks like: - ``` + ```Plain Text rpc/model ├── bookstore.sql ├── bookstoremodel.go // CRUD+cache code @@ -614,4 +613,4 @@ We not only keep the framework simple, but also encapsulate the complexity into For the generated code by goctl, lots of microservice components are included, like concurrency control, adaptive circuit breaker, adaptive load shedding, auto cache control etc. And it’s easy to deal with the busy sites. -If you have any ideas that can help us to improve the productivity, tell me any time! 👏 \ No newline at end of file +If you have any ideas that can help us to improve the productivity, tell me any time! 👏 diff --git a/doc/bookstore.md b/doc/bookstore.md index 435206d4..914ad67d 100644 --- a/doc/bookstore.md +++ b/doc/bookstore.md @@ -1,8 +1,8 @@ -[English](bookstore-en.md) | 简体中文 - # 快速构建微服务-多RPC版 -## 0. 为什么说做好微服务很难? +[English](bookstore-en.md) | 简体中文 + +## 0. 为什么说做好微服务很难 要想做好微服务,我们需要理解和掌握的知识点非常多,从几个维度上来说: @@ -71,7 +71,7 @@ * 在`bookstore/api`目录下通过goctl生成`api/bookstore.api`: - ``` + ```bash goctl api -o bookstore.api ``` @@ -128,7 +128,7 @@ 生成的文件结构如下: - ``` + ```Plain Text api ├── bookstore.api // api定义 ├── bookstore.go // main入口定义 @@ -222,7 +222,7 @@ 文件结构如下: - ``` + ```Plain Text rpc/add ├── add.go // rpc服务main函数 ├── add.proto // rpc接口定义 @@ -244,7 +244,6 @@ └── pb └── add.pb.go ``` - 直接可以运行,如下: @@ -294,7 +293,7 @@ 文件结构如下: - ``` + ```Plain Text rpc/check ├── check.go // rpc服务main函数 ├── check.proto // rpc接口定义 @@ -454,7 +453,7 @@ 生成后的文件结构如下: - ``` + ```Plain Text rpc/model ├── bookstore.sql ├── bookstoremodel.go // CRUD+cache代码 @@ -615,4 +614,3 @@ go-zero不只是一个框架,更是一个建立在框架+工具基础上的, 通过go-zero+goctl生成的代码,包含了微服务治理的各种组件,包括:并发控制、自适应熔断、自适应降载、自动缓存控制等,可以轻松部署以承载巨大访问量。 有任何好的提升工程效率的想法,随时欢迎交流!👏 - diff --git a/doc/breaker.md b/doc/breaker.md index 52eece74..05e9e10b 100644 --- a/doc/breaker.md +++ b/doc/breaker.md @@ -1,4 +1,5 @@ # 熔断机制设计 ## 设计目的 -* 依赖的服务出现大规模故障时,调用方应该尽可能少调用,降低故障服务的压力,使之尽快恢复服务 \ No newline at end of file + +* 依赖的服务出现大规模故障时,调用方应该尽可能少调用,降低故障服务的压力,使之尽快恢复服务 diff --git a/doc/collection.md b/doc/collection.md index 5eaa439b..3fb6f7d4 100644 --- a/doc/collection.md +++ b/doc/collection.md @@ -29,6 +29,7 @@ v, err := c.Take("key", func() (interface{}, error) { ``` cache 实现的建的功能包括 + * 缓存自动失效,可以指定过期时间 * 缓存大小限制,可以指定缓存个数 * 缓存增删改 @@ -38,6 +39,7 @@ cache 实现的建的功能包括 实现原理: Cache 自动失效,是采用 TimingWheel(https://github.com/tal-tech/go-zero/blob/master/core/collection/timingwheel.go) 进行管理的 + ``` go timingWheel, err := NewTimingWheel(time.Second, slots, func(k, v interface{}) { key, ok := k.(string) @@ -50,6 +52,7 @@ timingWheel, err := NewTimingWheel(time.Second, slots, func(k, v interface{}) { ``` Cache 大小限制,是采用 LRU 淘汰策略,在新增缓存的时候会去检查是否已经超出过限制,具体代码在 keyLru 中实现 + ``` go func (klru *keyLru) add(key string) { if elem, ok := klru.elements[key]; ok { @@ -71,6 +74,7 @@ func (klru *keyLru) add(key string) { Cache 的命中率统计,是在代码中实现 cacheStat,在缓存命中丢失的时候自动统计,并且会定时打印使用的命中率, qps 等状态. 打印的具体效果如下 + ```go cache(proc) - qpm: 2, hit_ratio: 50.0%, elements: 0, hit: 1, miss: 1 ``` diff --git a/doc/goctl-model-sql.md b/doc/goctl-model-sql.md index 62e3fb9a..f9c96f28 100644 --- a/doc/goctl-model-sql.md +++ b/doc/goctl-model-sql.md @@ -2,63 +2,63 @@ goctl model 为go-zero下的工具模块中的组件之一,目前支持识别mysql ddl进行model层代码生成,通过命令行或者idea插件(即将支持)可以有选择地生成带redis cache或者不带redis cache的代码逻辑。 -# 快速开始 +## 快速开始 * 通过ddl生成 ```shell script - $ goctl model mysql ddl -src="./sql/user.sql" -dir="./sql/model" -c=true + goctl model mysql ddl -src="./sql/user.sql" -dir="./sql/model" -c=true ``` 执行上述命令后即可快速生成CURD代码。 - ``` + ```Plain Text model │   ├── error.go │   └── usermodel.go ``` + * 通过datasource生成 ```shell script - $ goctl model mysql datasource -url="user:password@tcp(127.0.0.1:3306)/database" -table="table1,table2" -dir="./model" + goctl model mysql datasource -url="user:password@tcp(127.0.0.1:3306)/database" -table="table1,table2" -dir="./model" ``` - * 生成代码示例 ``` go package model - + import ( "database/sql" "fmt" "strings" "time" - + "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/sqlx" "github.com/tal-tech/go-zero/core/stringx" "github.com/tal-tech/go-zero/tools/goctl/model/sql/builderx" ) - + var ( userFieldNames = builderx.FieldNames(&User{}) userRows = strings.Join(userFieldNames, ",") userRowsExpectAutoSet = strings.Join(stringx.Remove(userFieldNames, "id", "create_time", "update_time"), ",") userRowsWithPlaceHolder = strings.Join(stringx.Remove(userFieldNames, "id", "create_time", "update_time"), "=?,") + "=?" - + cacheUserMobilePrefix = "cache#User#mobile#" cacheUserIdPrefix = "cache#User#id#" cacheUserNamePrefix = "cache#User#name#" ) - + type ( UserModel struct { sqlc.CachedConn table string } - + User struct { Id int64 `db:"id"` Name string `db:"name"` // 用户名称 @@ -70,19 +70,19 @@ goctl model 为go-zero下的工具模块中的组件之一,目前支持识别m UpdateTime time.Time `db:"update_time"` } ) - + func NewUserModel(conn sqlx.SqlConn, c cache.CacheConf, table string) *UserModel { return &UserModel{ CachedConn: sqlc.NewConn(conn, c), table: table, } } - + func (m *UserModel) Insert(data User) (sql.Result, error) { query := `insert into ` + m.table + `(` + userRowsExpectAutoSet + `) value (?, ?, ?, ?, ?)` return m.ExecNoCache(query, data.Name, data.Password, data.Mobile, data.Gender, data.Nickname) } - + func (m *UserModel) FindOne(id int64) (*User, error) { userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, id) var resp User @@ -99,7 +99,7 @@ goctl model 为go-zero下的工具模块中的组件之一,目前支持识别m return nil, err } } - + func (m *UserModel) FindOneByName(name string) (*User, error) { userNameKey := fmt.Sprintf("%s%v", cacheUserNamePrefix, name) var resp User @@ -124,7 +124,7 @@ goctl model 为go-zero下的工具模块中的组件之一,目前支持识别m return nil, err } } - + func (m *UserModel) FindOneByMobile(mobile string) (*User, error) { userMobileKey := fmt.Sprintf("%s%v", cacheUserMobilePrefix, mobile) var resp User @@ -149,7 +149,7 @@ goctl model 为go-zero下的工具模块中的组件之一,目前支持识别m return nil, err } } - + func (m *UserModel) Update(data User) error { userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, data.Id) _, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) { @@ -158,7 +158,7 @@ goctl model 为go-zero下的工具模块中的组件之一,目前支持识别m }, userIdKey) return err } - + func (m *UserModel) Delete(id int64) error { data, err := m.FindOne(id) if err != nil { @@ -175,13 +175,13 @@ goctl model 为go-zero下的工具模块中的组件之一,目前支持识别m } ``` -# 用法 +### 用法 -``` -$ goctl model mysql -h +```Plain Text +goctl model mysql -h ``` -``` +```Plain Text NAME: goctl model mysql - generate mysql model" @@ -196,23 +196,24 @@ OPTIONS: --help, -h show help ``` -# 生成规则 +## 生成规则 * 默认规则 我们默认用户在建表时会创建createTime、updateTime字段(忽略大小写、下划线命名风格)且默认值均为`CURRENT_TIMESTAMP`,而updateTime支持`ON UPDATE CURRENT_TIMESTAMP`,对于这两个字段生成`insert`、`update`时会被移除,不在赋值范畴内,当然,如果你不需要这两个字段那也无大碍。 * 带缓存模式 - * ddl - - ```shell script - $ goctl model mysql -src={filename} -dir={dir} -cache=true - ``` - * datasource - - ```shell script - $ goctl model mysql datasource -url={datasource} -table={tables} -dir={dir} -cache=true - ``` - + * ddl + + ```shell script + goctl model mysql -src={filename} -dir={dir} -cache=true + ``` + + * datasource + + ```shell script + goctl model mysql datasource -url={datasource} -table={tables} -dir={dir} -cache=true + ``` + 目前仅支持redis缓存,如果选择带缓存模式,即生成的`FindOne(ByXxx)`&`Delete`代码会生成带缓存逻辑的代码,目前仅支持单索引字段(除全文索引外),对于联合索引我们默认认为不需要带缓存,且不属于通用型代码,因此没有放在代码生成行列,如example中user表中的`id`、`name`、`mobile`字段均属于单字段索引。 * 不带缓存模式 @@ -220,28 +221,31 @@ OPTIONS: * ddl ```shell script - $ goctl model -src={filename} -dir={dir} + goctl model -src={filename} -dir={dir} ``` + * datasource ```shell script - $ goctl model mysql datasource -url={datasource} -table={tables} -dir={dir} + goctl model mysql datasource -url={datasource} -table={tables} -dir={dir} ``` + or * ddl - + ```shell script - $ goctl model -src={filename} -dir={dir} -cache=false + goctl model -src={filename} -dir={dir} -cache=false ``` + * datasource ```shell script - $ goctl model mysql datasource -url={datasource} -table={tables} -dir={dir} -cache=false + goctl model mysql datasource -url={datasource} -table={tables} -dir={dir} -cache=false ``` 生成代码仅基本的CURD结构。 -# 缓存 +## 缓存 对于缓存这一块我选择用一问一答的形式进行罗列。我想这样能够更清晰的描述model中缓存的功能。 @@ -261,14 +265,8 @@ OPTIONS: 目前,我认为除了基本的CURD外,其他的代码均属于业务型代码,这个我觉得开发人员根据业务需要进行编写更好。 -# QA +## QA * goctl model除了命令行模式,支持插件模式吗? 很快支持idea插件。 - - - - - - diff --git a/doc/goctl-rpc.md b/doc/goctl-rpc.md index 9ac26164..b85a8378 100644 --- a/doc/goctl-rpc.md +++ b/doc/goctl-rpc.md @@ -1,12 +1,14 @@ # Rpc Generation + Goctl Rpc是`goctl`脚手架下的一个rpc服务代码生成模块,支持proto模板生成和rpc服务代码生成,通过此工具生成代码你只需要关注业务逻辑编写而不用去编写一些重复性的代码。这使得我们把精力重心放在业务上,从而加快了开发效率且降低了代码出错率。 -# 特性 +## 特性 + * 简单易用 * 快速提升开发效率 * 出错率低 -# 快速开始 +## 快速开始 ### 方式一:快速生成greet服务 @@ -15,7 +17,7 @@ Goctl Rpc是`goctl`脚手架下的一个rpc服务代码生成模块,支持prot 如生成greet rpc服务: ```shell script - $ goctl rpc new greet + goctl rpc new greet ``` 执行后代码结构如下: @@ -46,12 +48,13 @@ Goctl Rpc是`goctl`脚手架下的一个rpc服务代码生成模块,支持prot ``` rpc一键生成常见问题解决见 常见问题解决 + ### 方式二:通过指定proto生成rpc服务 * 生成proto模板 ```shell script - $ goctl rpc template -o=user.proto + goctl rpc template -o=user.proto ``` ```golang @@ -78,15 +81,16 @@ rpc一键生成常见问题解决见 常见问题 rpc Login(Request)returns(Response); } ``` + * 生成rpc服务代码 - ``` - $ goctl rpc proto -src=user.proto + ```shell script + goctl rpc proto -src=user.proto ``` 代码tree - ``` + ```Plain Text user ├── etc │   └── user.json @@ -109,19 +113,20 @@ rpc一键生成常见问题解决见 常见问题 └── user.proto ``` -# 准备工作 + +## 准备工作 * 安装了go环境 * 安装了protoc&protoc-gen-go,并且已经设置环境变量 * mockgen(可选,将移除) * 更多问题请见 注意事项 -# 用法 +## 用法 ### rpc服务生成用法 ```shell script -$ goctl rpc proto -h +goctl rpc proto -h ``` ```shell script @@ -145,44 +150,53 @@ OPTIONS: * --src 必填,proto数据源,目前暂时支持单个proto文件生成,这里不支持(不建议)外部依赖 * --dir 非必填,默认为proto文件所在目录,生成代码的目标目录 * --service 服务名称,非必填,默认为proto文件所在目录名称,但是,如果proto所在目录为一下结构: + ```shell script user ├── cmd │   └── rpc │   └── user.proto ``` + 则服务名称亦为user,而非proto所在文件夹名称了,这里推荐使用这种结构,可以方便在同一个服务名下建立不同类型的服务(api、rpc、mq等),便于代码管理与维护。 * --shared[⚠️已废弃] 非必填,默认为$dir(xxx.proto)/shared,rpc client逻辑代码存放目录。 > 注意:这里的shared文件夹名称将会是代码中的package名称。 * --idea 非必填,是否为idea插件中执行,保留字段,终端执行可以忽略 - -# 开发人员需要做什么 + +## 开发人员需要做什么 关注业务代码编写,将重复性、与业务无关的工作交给goctl,生成好rpc服务代码后,开饭人员仅需要修改 + * 服务中的配置文件编写(etc/xx.json、internal/config/config.go) * 服务中业务逻辑编写(internal/logic/xxlogic.go) * 服务中资源上下文的编写(internal/svc/servicecontext.go) -# 扩展 +## 扩展 + 对于需要进行rpc mock的开发人员,在安装了`mockgen`工具的前提下可以在rpc的shared文件中生成好对应的mock文件。 -# 注意事项 +## 注意事项 + * `google.golang.org/grpc`需要降级到v1.26.0,且protoc-gen-go版本不能高于v1.3.2(see [https://github.com/grpc/grpc-go/issues/3347](https://github.com/grpc/grpc-go/issues/3347))即 - ``` + + ```shell script replace google.golang.org/grpc => google.golang.org/grpc v1.26.0 ``` + * proto不支持暂多文件同时生成 * proto不支持外部依赖包引入,message不支持inline * 目前main文件、shared文件、handler文件会被强制覆盖,而和开发人员手动需要编写的则不会覆盖生成,这一类在代码头部均有 + ```shell script // Code generated by goctl. DO NOT EDIT! // Source: xxx.proto ``` + 的标识,请注意不要将也写业务性代码写在里面。 -# 常见问题解决(go mod工程) +## 常见问题解决(go mod工程) * 错误一: @@ -192,6 +206,7 @@ OPTIONS: pb/xx.pb.go:234:5: undefined: grpc.ClientConnInterface pb/xx.pb.go:237:24: undefined: grpc.ClientConnInterface ``` + 解决方法:请将`protoc-gen-go`版本降至v1.3.2及一下 * 错误二: diff --git a/doc/goctl.md b/doc/goctl.md index 0a4ae6cb..fcaa1145 100644 --- a/doc/goctl.md +++ b/doc/goctl.md @@ -1,31 +1,40 @@ -# goctl使用说明 +# goctl使用 ## goctl用途 + * 定义api请求 * 根据定义的api自动生成golang(后端), java(iOS & Android), typescript(web & 晓程序),dart(flutter) * 生成MySQL CURD+Cache -* 生成MongoDB CURD+Cache +* 生成MongoDB CURD+Cache ## goctl使用说明 -#### 快速生成服务 +### 快速生成服务 + * api: goctl api new xxxx -* rpc: goctl rpc new xxxx +* rpc: goctl rpc new xxxx #### goctl参数说明 `goctl api [go/java/ts] [-api user/user.api] [-dir ./src]` > api 后面接生成的语言,现支持go/java/typescript - + > > -api 自定义api所在路径 - + > > -dir 自定义生成目录 +#### 保持goctl总是最新版 + + 第一次运行会在~/.goctl里增加下面两行: + + ```Plain Text + url = http://47.97.184.41:7777/ + ``` + #### API 语法说明 ``` golang - info( title: doc title desc: > @@ -132,6 +141,7 @@ service user-api { 请求getRequest里面的属性赋值,getResponse为返回的结构体,这两个类型都定义在2描述的类型中。 #### api vscode插件 + 开发者可以在vscode中搜索goctl的api插件,它提供了api语法高亮,语法检测和格式化相关功能。 1. 支持语法高亮和类型导航。 @@ -143,7 +153,7 @@ service user-api { 命令如下: `goctl api go -api user/user.api -dir user` - ``` + ```Plain Text . ├── internal @@ -173,17 +183,20 @@ service user-api { └── user.go ``` + 生成的代码可以直接跑,有几个地方需要改: - * 在`servicecontext.go`里面增加需要传递给logic的一些资源,比如mysql, redis,rpc等 - * 在定义的get/post/put/delete等请求的handler和logic里增加处理业务逻辑的代码 +* 在`servicecontext.go`里面增加需要传递给logic的一些资源,比如mysql, redis,rpc等 +* 在定义的get/post/put/delete等请求的handler和logic里增加处理业务逻辑的代码 #### 根据定义好的api文件生成java代码 + ```shell goctl api java -api user/user.api -dir ./src ``` #### 根据定义好的api文件生成typescript代码 + ```shell goctl api ts -api user/user.api -dir ./src -webapi *** @@ -191,6 +204,7 @@ ts需要指定webapi所在目录 ``` #### 根据定义好的api文件生成Dart代码 + ```shell goctl api dart -api user/user.api -dir ./src ``` @@ -198,32 +212,37 @@ goctl api dart -api user/user.api -dir ./src ## 根据mysql ddl或者datasource生成model文件 ```shell script -$ goctl model mysql -src={filename} -dir={dir} -cache={true|false} +goctl model mysql -src={filename} -dir={dir} -cache={true|false} ``` + 详情参考[model文档](https://github.com/tal-tech/go-zero/blob/master/tools/goctl/model/sql/README.MD) ## 根据定义好的简单go文件生成mongo代码文件(仅限golang使用) + ```shell goctl model mongo -src {{yourDir}}/xiao/service/xhb/user/model/usermodel.go -cache yes +``` + +* src需要提供简单的usermodel.go文件,里面只需要提供一个结构体即可 +* cache 控制是否需要缓存 yes=需要 no=不需要 --src需要提供简单的usermodel.go文件,里面只需要提供一个结构体即可 --cache 控制是否需要缓存 yes=需要 no=不需要 src 示例代码如下 -``` + ```go -package model - -type User struct { - Name string `o:"find,get,set" c:"姓名"` - Age int `o:"find,get,set" c:"年纪"` - School string `c:"学校"` -} + package model + + type User struct { + Name string `o:"find,get,set" c:"姓名"` + Age int `o:"find,get,set" c:"年纪"` + School string `c:"学校"` + } ``` - 结构体中不需要提供Id,CreateTime,UpdateTime三个字段,会自动生成 - 结构体中每个tag有两个可选标签 c 和 o - c是改字段的注释 - o是改字段需要生产的操作函数 可以取得get,find,set 分别表示生成返回单个对象的查询方法,返回多个对象的查询方法,设置该字段方法 - 生成的目标文件会覆盖该简单go文件 + +结构体中不需要提供Id,CreateTime,UpdateTime三个字段,会自动生成 +结构体中每个tag有两个可选标签 c 和 o +c 是改字段的注释 +o 是改字段需要生产的操作函数 可以取得get,find,set 分别表示生成返回单个对象的查询方法,返回多个对象的查询方法,设置该字段方法 +生成的目标文件会覆盖该简单go文件 ## goctl rpc生成(业务剥离中,暂未开放) @@ -232,15 +251,15 @@ type User struct { 参数说明: - - ${proto}: proto文件 - - ${serviceName}: rpc服务名称 - - ${projectName}: 所属项目,如xjy,xhb,crm,hera,具体查看help,主要为了根据不同项目服务往redis注册key,可选 - - ${directory}: 输出目录 - - ${shared}: shared文件生成目录,可选,默认为${pwd}/shared +* ${proto}: proto文件 +* ${serviceName}: rpc服务名称 +* ${projectName}: 所属项目,如xjy,xhb,crm,hera,具体查看help,主要为了根据不同项目服务往redis注册key,可选 +* ${directory}: 输出目录 +* ${shared}: shared文件生成目录,可选,默认为${pwd}/shared - 生成目录结构示例: +生成目录结构示例: - ``` go + ```Plain Text . ├── shared [示例目录,可自己指定,强制覆盖更新] │   └── contentservicemodel.go @@ -276,4 +295,5 @@ type User struct { │   └── test.go [强制覆盖更新] └── test.proto ``` - - 注意 :目前rpc目录生成的proto文件暂不支持import外部proto文件 \ No newline at end of file + +注意 :目前rpc目录生成的proto文件暂不支持import外部proto文件 diff --git a/doc/keywords.md b/doc/keywords.md index fd9ee05d..ec4ece2d 100644 --- a/doc/keywords.md +++ b/doc/keywords.md @@ -22,7 +22,8 @@ fmt.Println(replacer.Replace("日本的首都是东京")) ``` 可以得到: -``` + +```Plain Text 东京是日本的首都 ``` @@ -46,7 +47,7 @@ fmt.Println(keywords) 可以得到: -``` +```Plain Text [苍井空 日本AV女优 AV演员色情 AV AV演员] ``` @@ -70,7 +71,7 @@ fmt.Println(found) 可以得到: -``` +```Plain Text 日本????兼电视、电影演员。?????女优是xx出道, ??????们最精彩的表演是??????表演 [苍井空 日本AV女优 AV演员色情 AV AV演员] true @@ -83,4 +84,3 @@ true | Sentences | Keywords | Regex | go-zero | | --------- | -------- | -------- | ------- | | 10000 | 10000 | 16min10s | 27.2ms | - diff --git a/doc/loadshedding.md b/doc/loadshedding.md index b35239c9..d3676da9 100644 --- a/doc/loadshedding.md +++ b/doc/loadshedding.md @@ -1,23 +1,26 @@ # 服务自适应降载保护设计 ## 设计目的 + * 保证系统不被过量请求拖垮 * 在保证系统稳定的前提下,尽可能提供更高的吞吐量 ## 设计考虑因素 + * 如何衡量系统负载 - * 是否处于虚机或容器内,需要读取cgroup相关负载 - * 用1000m表示100%CPU,推荐使用800m表示系统高负载 + * 是否处于虚机或容器内,需要读取cgroup相关负载 + * 用1000m表示100%CPU,推荐使用800m表示系统高负载 * 尽可能小的Overhead,不显著增加RT * 不考虑服务本身所依赖的DB或者缓存系统问题,这类问题通过熔断机制来解决 ## 机制设计 + * 计算CPU负载时使用滑动平均来降低CPU负载抖动带来的不稳定,关于滑动平均见参考资料 - * 滑动平均就是取之前连续N次值的近似平均,N取值可以通过超参beta来决定 - * 当CPU负载大于指定值时触发降载保护机制 + * 滑动平均就是取之前连续N次值的近似平均,N取值可以通过超参beta来决定 + * 当CPU负载大于指定值时触发降载保护机制 * 时间窗口机制,用滑动窗口机制来记录之前时间窗口内的QPS和RT(response time) - * 滑动窗口使用5秒钟50个桶的方式,每个桶保存100ms时间内的请求,循环利用,最新的覆盖最老的 - * 计算maxQPS和minRT时需要过滤掉最新的时间没有用完的桶,防止此桶内只有极少数请求,并且RT处于低概率的极小值,所以计算maxQPS和minRT时按照上面的50个桶的参数只会算49个 + * 滑动窗口使用5秒钟50个桶的方式,每个桶保存100ms时间内的请求,循环利用,最新的覆盖最老的 + * 计算maxQPS和minRT时需要过滤掉最新的时间没有用完的桶,防止此桶内只有极少数请求,并且RT处于低概率的极小值,所以计算maxQPS和minRT时按照上面的50个桶的参数只会算49个 * 满足以下所有条件则拒绝该请求 1. 当前CPU负载超过预设阈值,或者上次拒绝时间到现在不超过1秒(冷却期)。冷却期是为了不能让负载刚下来就马上增加压力导致立马又上去的来回抖动 2. `averageFlying > max(1, QPS*minRT/1e3)` @@ -27,7 +30,7 @@ 1. 请求增加后更新一次averageFlying,见图中橙色曲线 2. 请求结束后更新一次averageFlying,见图中绿色曲线 3. 请求增加后更新一次averageFlying,请求结束后更新一次averageFlying - + 我们使用的是第二种,这样可以更好的防止抖动,如图: ![flying策略对比](images/shedding_flying.jpg) * QPS = maxPass * bucketsPerSecond @@ -36,11 +39,13 @@ * 1e3表示1000毫秒,minRT单位也是毫秒,QPS*minRT/1e3得到的就是平均每个时间点有多少并发请求 ## 降载的使用 + * 已经在ngin和rpcx框架里增加了可选激活配置 - * CpuThreshold,如果把值设置为大于0的值,则激活该服务的自动降载机制 -* 如果请求被drop,那么错误日志里会有`dropreq`关键字 + * CpuThreshold,如果把值设置为大于0的值,则激活该服务的自动降载机制 +* 如果请求被drop,那么错误日志里会有`dropreq`关键字 ## 参考资料 + * [滑动平均](https://www.cnblogs.com/wuliytTaotao/p/9479958.html) * [Sentinel自适应限流](https://github.com/alibaba/Sentinel/wiki/%E7%B3%BB%E7%BB%9F%E8%87%AA%E9%80%82%E5%BA%94%E9%99%90%E6%B5%81) -* [Kratos自适应限流保护](https://github.com/bilibili/kratos/blob/master/doc/wiki-cn/ratelimit.md) \ No newline at end of file +* [Kratos自适应限流保护](https://github.com/bilibili/kratos/blob/master/doc/wiki-cn/ratelimit.md) diff --git a/doc/mapreduce.md b/doc/mapreduce.md index a1a84013..2e7f4f12 100644 --- a/doc/mapreduce.md +++ b/doc/mapreduce.md @@ -24,7 +24,7 @@ MapReduce主要有三个参数,第一个参数为generate用以生产数据, 场景一: 某些功能的结果往往需要依赖多个服务,比如商品详情的结果往往会依赖用户服务、库存服务、订单服务等等,一般被依赖的服务都是以rpc的形式对外提供,为了降低依赖的耗时我们往往需要对依赖做并行处理 -``` +```go func productDetail(uid, pid int64) (*ProductDetail, error) { var pd ProductDetail err := mr.Finish(func() (err error) { @@ -42,15 +42,16 @@ func productDetail(uid, pid int64) (*ProductDetail, error) { log.Printf("product detail error: %v", err) return nil, err } - + return &pd, nil } ``` + 该示例中返回商品详情依赖了多个服务获取数据,因此做并发的依赖处理,对接口的性能有很大的提升 场景二: 很多时候我们需要对一批数据进行处理,比如对一批用户id,效验每个用户的合法性并且效验过程中有一个出错就认为效验失败,返回的结果为效验合法的用户id -``` +```go func checkLegal(uids []int64) ([]int64, error) { r, err := mr.MapReduce(func(source chan<- interface{}) { for _, uid := range uids { @@ -85,9 +86,11 @@ func check(uid int64) (bool, error) { return true, nil } ``` + 该示例中,如果check过程出现错误则通过cancel方法结束效验过程,并返回error整个效验过程结束,如果某个uid效验结果为false则最终结果不返回该uid -**MapReduce使用注意事项** +***MapReduce使用注意事项*** + * mapper和reducer中都可以调用cancel,参数为error,调用后立即返回,返回结果为nil, error * mapper中如果不调用writer.Write则item最终不会被reducer聚合 * reducer中如果不调用writer.Wirte则返回结果为nil, ErrReduceNoOutput @@ -96,12 +99,13 @@ func check(uid int64) (bool, error) { ***实现原理分析:*** MapReduce中首先通过buildSource方法通过执行generate(参数为无缓冲channel)产生数据,并返回无缓冲的channel,mapper会从该channel中读取数据 -``` + +```go func buildSource(generate GenerateFunc) chan interface{} { source := make(chan interface{}) go func() { defer close(source) - generate(source) + generate(source) }() return source @@ -109,7 +113,8 @@ func buildSource(generate GenerateFunc) chan interface{} { ``` 在MapReduceWithSource方法中定义了cancel方法,mapper和reducer中都可以调用该方法,调用后主线程收到close信号会立马返回 -``` + +```go cancel := once(func(err error) { if err != nil { retErr.Set(err) @@ -125,7 +130,8 @@ cancel := once(func(err error) { ``` 在mapperDispatcher方法中调用了executeMappers,executeMappers消费buildSource产生的数据,每一个item都会起一个goroutine单独处理,默认最大并发数为16,可以通过WithWorkers进行设置 -``` + +```go var wg sync.WaitGroup defer func() { wg.Wait() // 保证所有的item都处理完成 @@ -159,7 +165,8 @@ for { ``` reducer单goroutine对数mapper写入collector的数据进行处理,如果reducer中没有手动调用writer.Write则最终会执行finish方法对output进行close避免死锁 -``` + +```go go func() { defer func() { if r := recover(); r != nil { @@ -172,13 +179,12 @@ go func() { }() ``` -在该工具包中还提供了许多针对不同业务场景的方法,实现原理与MapReduce大同小异,感兴趣的同学可以查看源码学习 +在该工具包中还提供了许多针对不同业务场景的方法,实现原理与MapReduce大同小异,感兴趣的同学可以查看源码学习 + * MapReduceVoid 功能和MapReduce类似但没有结果返回只返回error * Finish 处理固定数量的依赖,返回error,有一个error立即返回 * FinishVoid 和Finish方法功能类似,没有返回值 * Map 只做generate和mapper处理,返回channel * MapVoid 和Map功能类似,无返回 - 本文主要介绍了go-zero框架中的MapReduce工具,在实际的项目中非常实用。用好工具对于提升服务性能和开发效率都有很大的帮助,希望本篇文章能给大家带来一些收获。 - diff --git a/doc/periodicalexecutor.md b/doc/periodicalexecutor.md index bec04c35..37968869 100644 --- a/doc/periodicalexecutor.md +++ b/doc/periodicalexecutor.md @@ -1,6 +1,6 @@ # PeriodicalExecutor设计 -# 添加任务 +## 添加任务 * 当前没有未执行的任务 * 添加并启动定时器 @@ -9,7 +9,7 @@ * 如到,执行所有缓存任务 * 未到,只添加 -# 定时器到期 +## 定时器到期 * 清除并执行所有缓存任务 -* 再等待N个定时周期,如果等待过程中一直没有新任务,则退出 \ No newline at end of file +* 再等待N个定时周期,如果等待过程中一直没有新任务,则退出 diff --git a/doc/shorturl-en.md b/doc/shorturl-en.md index d2d45dcb..f0f3a518 100644 --- a/doc/shorturl-en.md +++ b/doc/shorturl-en.md @@ -1,8 +1,8 @@ -English | [简体中文](shorturl.md) - # Rapid development of microservices -## 0. Why building microservices are so difficult? +English | [简体中文](shorturl.md) + +## 0. Why building microservices are so difficult To build a well working microservice, we need lots of knowledges from different aspects. @@ -25,7 +25,7 @@ As well, we always adhere to the idea that **prefer tools over conventions and d Let’s take the shorturl microservice as a quick example to demonstrate how to quickly create microservices by using [go-zero](https://github.com/tal-tech/go-zero). After finishing this tutorial, you’ll find that it’s so easy to write microservices! -## 1. What is a shorturl service? +## 1. What is a shorturl service A shorturl service is that it converts a long url into a short one, by well designed algorithms. @@ -129,7 +129,7 @@ And now, let’s walk through the complete flow of quickly create a microservice the generated file structure looks like: - ``` + ```Plain Text . ├── api │   ├── etc @@ -232,7 +232,7 @@ And now, let’s walk through the complete flow of quickly create a microservice the generated file structure looks like: - ``` + ```Plain Text rpc/transform ├── etc │   └── transform.yaml // configuration file @@ -375,7 +375,7 @@ Till now, we’ve done the modification of API Gateway. All the manually added c source shorturl.sql; ``` -* under the directory `rpc/transform/model execute the following command to genrate CRUD+cache code, `-c` means using `redis cache` +* under the directory `rpc/transform/model` execute the following command to genrate CRUD+cache code, `-c` means using `redis cache` ```shell goctl model mysql ddl -c -src shorturl.sql -dir . @@ -385,7 +385,7 @@ Till now, we’ve done the modification of API Gateway. All the manually added c the generated file structure looks like: - ``` + ```Plain Text rpc/transform/model ├── shorturl.sql ├── shorturlmodel.go // CRUD+cache code @@ -533,4 +533,3 @@ We not only keep the framework simple, but also encapsulate the complexity into For the generated code by goctl, lots of microservice components are included, like concurrency control, adaptive circuit breaker, adaptive load shedding, auto cache control etc. And it’s easy to deal with the busy sites. If you have any ideas that can help us to improve the productivity, tell me any time! 👏 - diff --git a/doc/shorturl.md b/doc/shorturl.md index e7fa66bf..4925693e 100644 --- a/doc/shorturl.md +++ b/doc/shorturl.md @@ -1,8 +1,8 @@ -[English](shorturl-en.md) | 简体中文 - # 快速构建高并发微服务 -## 0. 为什么说做好微服务很难? +[English](shorturl-en.md) | 简体中文 + +## 0. 为什么说做好微服务很难 要想做好微服务,我们需要理解和掌握的知识点非常多,从几个维度上来说: @@ -25,7 +25,7 @@ 下面我通过短链微服务来演示通过[go-zero](https://github.com/tal-tech/go-zero)快速的创建微服务的流程,走完一遍,你就会发现:原来编写微服务如此简单! -## 1. 什么是短链服务? +## 1. 什么是短链服务 短链服务就是将长的URL网址,通过程序计算等方式,转换为简短的网址字符串。 @@ -123,7 +123,7 @@ 生成的文件结构如下: - ``` + ```Plain Text . ├── api │   ├── etc @@ -228,7 +228,7 @@ 文件结构如下: - ``` + ```Plain Text rpc/transform ├── etc │   └── transform.yaml // 配置文件 @@ -320,7 +320,6 @@ // 手动代码结束 } ``` - 通过调用`transformer`的`Expand`方法实现短链恢复到url @@ -342,7 +341,6 @@ // 手动代码结束 } ``` - 通过调用`transformer`的`Shorten`方法实现url到短链的变换 @@ -383,7 +381,7 @@ 生成后的文件结构如下: - ``` + ```Plain Text rpc/transform/model ├── shorturl.sql ├── shorturlmodel.go // CRUD+cache代码 @@ -531,4 +529,3 @@ go-zero不只是一个框架,更是一个建立在框架+工具基础上的, 通过go-zero+goctl生成的代码,包含了微服务治理的各种组件,包括:并发控制、自适应熔断、自适应降载、自动缓存控制等,可以轻松部署以承载巨大访问量。 有任何好的提升工程效率的想法,随时欢迎交流!👏 - diff --git a/doc/sql-cache.md b/doc/sql-cache.md index ca8a9cd7..508e8b6e 100644 --- a/doc/sql-cache.md +++ b/doc/sql-cache.md @@ -21,4 +21,3 @@ * 通过Primary到DB查询行记录,并写入缓存 * 有Primary到行记录的缓存 * 直接返回缓存结果 - diff --git a/example/load/simulate/cpu/cpu-accuracy.md b/example/load/simulate/cpu/cpu-accuracy.md index 0a7ac96b..b21cf5cd 100644 --- a/example/load/simulate/cpu/cpu-accuracy.md +++ b/example/load/simulate/cpu/cpu-accuracy.md @@ -25,4 +25,3 @@ `watch -n 5 kubectl top pod -n adhoc` 可以看到`kubectl`报告的`CPU`使用率,两者进行对比,即可知道是否准确 - diff --git a/readme-en.md b/readme-en.md index b5147bdb..01012483 100644 --- a/readme-en.md +++ b/readme-en.md @@ -1,14 +1,14 @@ -English | [简体中文](readme.md) - # go-zero +English | [简体中文](readme.md) + [![Go](https://github.com/tal-tech/go-zero/workflows/Go/badge.svg?branch=master)](https://github.com/tal-tech/go-zero/actions) [![codecov](https://codecov.io/gh/tal-tech/go-zero/branch/master/graph/badge.svg)](https://codecov.io/gh/tal-tech/go-zero) [![Go Report Card](https://goreportcard.com/badge/github.com/tal-tech/go-zero)](https://goreportcard.com/report/github.com/tal-tech/go-zero) [![Release](https://img.shields.io/github/v/release/tal-tech/go-zero.svg?style=flat-square)](https://github.com/tal-tech/go-zero) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -## 0. what is go-zero? +## 0. what is go-zero go-zero is a web and rpc framework that with lots of engineering practices builtin. It’s born to ensure the stability of the busy services with resilience design, and has been serving sites with tens of millions users for years. @@ -99,7 +99,7 @@ go get -u github.com/tal-tech/go-zero 1. install goctl - `goctl`can be read as `go control`. `goctl means not to be controlled by code, instead, we control it. The inside `go` is not `golang`. At the very beginning, I was expecting it to help us improve the productivity, and make our lives easier. + `goctl`can be read as `go control`. `goctl` means not to be controlled by code, instead, we control it. The inside `go` is not `golang`. At the very beginning, I was expecting it to help us improve the productivity, and make our lives easier. ```shell GO111MODULE=on go get -u github.com/tal-tech/go-zero/tools/goctl @@ -113,11 +113,11 @@ go get -u github.com/tal-tech/go-zero type Request struct { Name string `path:"name,options=you|me"` // parameters are auto validated } - + type Response struct { Message string `json:"message"` } - + service greet-api { @server( handler: GreetHandler @@ -140,7 +140,7 @@ go get -u github.com/tal-tech/go-zero the generated files look like: - ``` + ```Plain Text ├── greet │   ├── etc │   │   └── greet-api.yaml // configuration file @@ -159,6 +159,7 @@ go get -u github.com/tal-tech/go-zero │   └── types.go // request/response defined here └── greet.api // api description file ``` + the generated code can be run directly: ```shell @@ -184,8 +185,9 @@ go get -u github.com/tal-tech/go-zero 4. Write the business logic code -* the dependencies can be passed into the logic within servicecontext.go, like mysql, reds etc. -* add the logic code in logic package according to .api file + * the dependencies can be passed into the logic within servicecontext.go, like mysql, reds etc. + * add the logic code in logic package according to .api file + 5. Generate code like Java, TypeScript, Dart, JavaScript etc. just from the api file ```shell diff --git a/readme.md b/readme.md index c92d9505..ba081b32 100644 --- a/readme.md +++ b/readme.md @@ -1,7 +1,7 @@ -[English](readme-en.md) | 简体中文 - # go-zero +[English](readme-en.md) | 简体中文 + [![Go](https://github.com/tal-tech/go-zero/workflows/Go/badge.svg?branch=master)](https://github.com/tal-tech/go-zero/actions) [![codecov](https://codecov.io/gh/tal-tech/go-zero/branch/master/graph/badge.svg)](https://codecov.io/gh/tal-tech/go-zero) [![Go Report Card](https://goreportcard.com/badge/github.com/tal-tech/go-zero)](https://goreportcard.com/report/github.com/tal-tech/go-zero) @@ -108,40 +108,42 @@ go get -u github.com/tal-tech/go-zero 确保goctl可执行 2. 快速生成api服务 - -```shell - goctl api new greet - cd greet - go run greet.go -f etc/greet-api.yaml -``` -默认侦听在8888端口(可以在配置文件里修改),可以通过curl请求: + ```shell + goctl api new greet + cd greet + go run greet.go -f etc/greet-api.yaml + ``` -```shell -curl -i http://localhost:8888/greet/from/you -``` + 默认侦听在8888端口(可以在配置文件里修改),可以通过curl请求: -返回如下: + ```shell + curl -i http://localhost:8888/greet/from/you + ``` -```http -HTTP/1.1 200 OK -Date: Sun, 30 Aug 2020 15:32:35 GMT -Content-Length: 0 -``` + 返回如下: -编写业务代码: + ```http + HTTP/1.1 200 OK + Date: Sun, 30 Aug 2020 15:32:35 GMT + Content-Length: 0 + ``` + + 编写业务代码: + + * 可以在servicecontext.go里面传递依赖给logic,比如mysql, redis等 + * 在api定义的get/post/put/delete等请求对应的logic里增加业务处理逻辑 + * api文件定义了服务对外暴露的路由,可参考[api规范](https://github.com/tal-tech/go-zero/blob/master/doc/goctl.md) + * 可以在servicecontext.go里面传递依赖给logic,比如mysql, redis等 + * 在api定义的get/post/put/delete等请求对应的logic里增加业务处理逻辑 -* api文件定义了服务对外暴露的路由,可参考[api规范](https://github.com/tal-tech/go-zero/blob/master/doc/goctl.md) -* 可以在servicecontext.go里面传递依赖给logic,比如mysql, redis等 - * 在api定义的get/post/put/delete等请求对应的logic里增加业务处理逻辑 - 3. 可以根据api文件生成前端需要的Java, TypeScript, Dart, JavaScript代码 - ```shell - goctl api java -api greet.api -dir greet - goctl api dart -api greet.api -dir greet - ... - ``` + ```shell + goctl api java -api greet.api -dir greet + goctl api dart -api greet.api -dir greet + ... + ``` ## 7. Benchmark diff --git a/tools/goctl/goctl.md b/tools/goctl/goctl.md index 90651aa5..cb2584e6 100644 --- a/tools/goctl/goctl.md +++ b/tools/goctl/goctl.md @@ -1,31 +1,34 @@ -# goctl使用说明 +# goctl使用 ## goctl用途 + * 定义api请求 * 根据定义的api自动生成golang(后端), java(iOS & Android), typescript(web & 晓程序),dart(flutter) * 生成MySQL CURD 详情见[goctl model模块](model/sql) + ## goctl使用说明 -#### goctl参数说明 + +### goctl 参数说明 `goctl api [go/java/ts] [-api user/user.api] [-dir ./src]` > api 后面接生成的语言,现支持go/java/typescript - + > > -api 自定义api所在路径 - + > > -dir 自定义生成目录 #### 保持goctl总是最新版 第一次运行会在~/.goctl里增加下面两行: - ``` + ```Plain Text url = http://47.97.184.41:7777/ ``` #### API 语法说明 -``` +```Plain Text info( title: doc title desc: > @@ -122,6 +125,7 @@ service user-api { head /api/ping() } ``` + 1. info部分:描述了api基本信息,比如Auth,api是哪个用途。 2. type部分:type类型声明和golang语法兼容。 3. service部分:service代表一组服务,一个服务可以由多组名称相同的service组成,可以针对每一组service配置jwt和auth认证,另外通过folder属性可以指定service生成所在子目录。 @@ -130,6 +134,7 @@ service user-api { 请求getRequest里面的属性赋值,getResponse为返回的结构体,这两个类型都定义在2描述的类型中。 #### api vscode插件 + 开发者可以在vscode中搜索goctl的api插件,它提供了api语法高亮,语法检测和格式化相关功能。 1. 支持语法高亮和类型导航。 @@ -141,7 +146,7 @@ service user-api { 命令如下: `goctl api go -api user/user.api -dir user` - ``` + ```Plain Text . ├── internal @@ -171,20 +176,28 @@ service user-api { └── user.go ``` + 生成的代码可以直接跑,有几个地方需要改: - * 在`servicecontext.go`里面增加需要传递给logic的一些资源,比如mysql, redis,rpc等 - * 在定义的get/post/put/delete等请求的handler和logic里增加处理业务逻辑的代码 +* 在`servicecontext.go`里面增加需要传递给logic的一些资源,比如mysql, redis,rpc等 +* 在定义的get/post/put/delete等请求的handler和logic里增加处理业务逻辑的代码 #### 根据定义好的api文件生成java代码 - `goctl api java -api user/user.api -dir ./src` + +```Plain Text + goctl api java -api user/user.api -dir ./src +``` #### 根据定义好的api文件生成typescript代码 - `goctl api ts -api user/user.api -dir ./src -webapi ***` - ts需要指定webapi所在目录 - +```Plain Text + goctl api ts -api user/user.api -dir ./src -webapi *** +``` + +ts需要指定webapi所在目录 + #### 根据定义好的api文件生成Dart代码 - `goctl api dart -api user/user.api -dir ./src` - -* 如有不理解的地方,随时问Kim/Kevin \ No newline at end of file + +```Plain Text + goctl api dart -api user/user.api -dir ./src +``` diff --git a/tools/goctl/model/sql/CHANGELOG.md b/tools/goctl/model/sql/CHANGELOG.md index 845cee50..93af6678 100644 --- a/tools/goctl/model/sql/CHANGELOG.md +++ b/tools/goctl/model/sql/CHANGELOG.md @@ -1,10 +1,12 @@ # Change log -# 2020-08-20 +## 2020-08-20 + * 新增支持通过连接数据库生成model * 支持数据库多表生成 * 优化stringx -# 2020-08-19 +## 2020-08-19 + * 重构model代码生成逻辑 -* 实现从ddl解析表信息生成代码 \ No newline at end of file +* 实现从ddl解析表信息生成代码 diff --git a/tools/goctl/model/sql/README.MD b/tools/goctl/model/sql/README.MD index 62e3fb9a..d78f9d5f 100644 --- a/tools/goctl/model/sql/README.MD +++ b/tools/goctl/model/sql/README.MD @@ -2,186 +2,186 @@ goctl model 为go-zero下的工具模块中的组件之一,目前支持识别mysql ddl进行model层代码生成,通过命令行或者idea插件(即将支持)可以有选择地生成带redis cache或者不带redis cache的代码逻辑。 -# 快速开始 +## 快速开始 * 通过ddl生成 ```shell script - $ goctl model mysql ddl -src="./sql/user.sql" -dir="./sql/model" -c=true + goctl model mysql ddl -src="./sql/user.sql" -dir="./sql/model" -c=true ``` 执行上述命令后即可快速生成CURD代码。 - ``` + ```Plain Text model │   ├── error.go │   └── usermodel.go ``` + * 通过datasource生成 ```shell script - $ goctl model mysql datasource -url="user:password@tcp(127.0.0.1:3306)/database" -table="table1,table2" -dir="./model" + goctl model mysql datasource -url="user:password@tcp(127.0.0.1:3306)/database" -table="table1,table2" -dir="./model" ``` - * 生成代码示例 ``` go - package model - - import ( - "database/sql" - "fmt" - "strings" - "time" - - "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/sqlx" - "github.com/tal-tech/go-zero/core/stringx" - "github.com/tal-tech/go-zero/tools/goctl/model/sql/builderx" - ) - - var ( - userFieldNames = builderx.FieldNames(&User{}) - userRows = strings.Join(userFieldNames, ",") - userRowsExpectAutoSet = strings.Join(stringx.Remove(userFieldNames, "id", "create_time", "update_time"), ",") - userRowsWithPlaceHolder = strings.Join(stringx.Remove(userFieldNames, "id", "create_time", "update_time"), "=?,") + "=?" - - cacheUserMobilePrefix = "cache#User#mobile#" - cacheUserIdPrefix = "cache#User#id#" - cacheUserNamePrefix = "cache#User#name#" - ) - - type ( - UserModel struct { - sqlc.CachedConn - table string - } - - User struct { - Id int64 `db:"id"` - Name string `db:"name"` // 用户名称 - Password string `db:"password"` // 用户密码 - Mobile string `db:"mobile"` // 手机号 - Gender string `db:"gender"` // 男|女|未公开 - Nickname string `db:"nickname"` // 用户昵称 - CreateTime time.Time `db:"create_time"` - UpdateTime time.Time `db:"update_time"` - } - ) - - func NewUserModel(conn sqlx.SqlConn, c cache.CacheConf, table string) *UserModel { - return &UserModel{ - CachedConn: sqlc.NewConn(conn, c), - table: table, - } - } - - func (m *UserModel) Insert(data User) (sql.Result, error) { - query := `insert into ` + m.table + `(` + userRowsExpectAutoSet + `) value (?, ?, ?, ?, ?)` - return m.ExecNoCache(query, data.Name, data.Password, data.Mobile, data.Gender, data.Nickname) - } - - func (m *UserModel) FindOne(id int64) (*User, error) { - userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, id) - var resp User - err := m.QueryRow(&resp, userIdKey, func(conn sqlx.SqlConn, v interface{}) error { - query := `select ` + userRows + ` from ` + m.table + ` where id = ? limit 1` - return conn.QueryRow(v, query, id) - }) - switch err { - case nil: - return &resp, nil - case sqlc.ErrNotFound: - return nil, ErrNotFound - default: - return nil, err - } - } - - func (m *UserModel) FindOneByName(name string) (*User, error) { - userNameKey := fmt.Sprintf("%s%v", cacheUserNamePrefix, name) - var resp User - err := m.QueryRowIndex(&resp, userNameKey, func(primary interface{}) string { - return fmt.Sprintf("%s%v", cacheUserIdPrefix, primary) - }, func(conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { - query := `select ` + userRows + ` from ` + m.table + ` where name = ? limit 1` - if err := conn.QueryRow(&resp, query, name); err != nil { - return nil, err - } - return resp.Id, nil - }, func(conn sqlx.SqlConn, v, primary interface{}) error { - query := `select ` + userRows + ` from ` + m.table + ` where id = ? limit 1` - return conn.QueryRow(v, query, primary) - }) - switch err { - case nil: - return &resp, nil - case sqlc.ErrNotFound: - return nil, ErrNotFound - default: - return nil, err - } - } - - func (m *UserModel) FindOneByMobile(mobile string) (*User, error) { - userMobileKey := fmt.Sprintf("%s%v", cacheUserMobilePrefix, mobile) - var resp User - err := m.QueryRowIndex(&resp, userMobileKey, func(primary interface{}) string { - return fmt.Sprintf("%s%v", cacheUserIdPrefix, primary) - }, func(conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { - query := `select ` + userRows + ` from ` + m.table + ` where mobile = ? limit 1` - if err := conn.QueryRow(&resp, query, mobile); err != nil { - return nil, err - } - return resp.Id, nil - }, func(conn sqlx.SqlConn, v, primary interface{}) error { - query := `select ` + userRows + ` from ` + m.table + ` where id = ? limit 1` - return conn.QueryRow(v, query, primary) - }) - switch err { - case nil: - return &resp, nil - case sqlc.ErrNotFound: - return nil, ErrNotFound - default: - return nil, err - } - } - - func (m *UserModel) Update(data User) error { - userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, data.Id) - _, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) { - query := `update ` + m.table + ` set ` + userRowsWithPlaceHolder + ` where id = ?` - return conn.Exec(query, data.Name, data.Password, data.Mobile, data.Gender, data.Nickname, data.Id) - }, userIdKey) - return err - } - - func (m *UserModel) Delete(id int64) error { - data, err := m.FindOne(id) - if err != nil { - return err - } - userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, id) - userNameKey := fmt.Sprintf("%s%v", cacheUserNamePrefix, data.Name) - userMobileKey := fmt.Sprintf("%s%v", cacheUserMobilePrefix, data.Mobile) - _, err = m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) { - query := `delete from ` + m.table + ` where id = ?` - return conn.Exec(query, id) - }, userIdKey, userNameKey, userMobileKey) - return err - } + package model + + import ( + "database/sql" + "fmt" + "strings" + "time" + + "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/sqlx" + "github.com/tal-tech/go-zero/core/stringx" + "github.com/tal-tech/go-zero/tools/goctl/model/sql/builderx" + ) + + var ( + userFieldNames = builderx.FieldNames(&User{}) + userRows = strings.Join(userFieldNames, ",") + userRowsExpectAutoSet = strings.Join(stringx.Remove(userFieldNames, "id", "create_time", "update_time"), ",") + userRowsWithPlaceHolder = strings.Join(stringx.Remove(userFieldNames, "id", "create_time", "update_time"), "=?,") + "=?" + + cacheUserMobilePrefix = "cache#User#mobile#" + cacheUserIdPrefix = "cache#User#id#" + cacheUserNamePrefix = "cache#User#name#" + ) + + type ( + UserModel struct { + sqlc.CachedConn + table string + } + + User struct { + Id int64 `db:"id"` + Name string `db:"name"` // 用户名称 + Password string `db:"password"` // 用户密码 + Mobile string `db:"mobile"` // 手机号 + Gender string `db:"gender"` // 男|女|未公开 + Nickname string `db:"nickname"` // 用户昵称 + CreateTime time.Time `db:"create_time"` + UpdateTime time.Time `db:"update_time"` + } + ) + + func NewUserModel(conn sqlx.SqlConn, c cache.CacheConf, table string) *UserModel { + return &UserModel{ + CachedConn: sqlc.NewConn(conn, c), + table: table, + } + } + + func (m *UserModel) Insert(data User) (sql.Result, error) { + query := `insert into ` + m.table + `(` + userRowsExpectAutoSet + `) value (?, ?, ?, ?, ?)` + return m.ExecNoCache(query, data.Name, data.Password, data.Mobile, data.Gender, data.Nickname) + } + + func (m *UserModel) FindOne(id int64) (*User, error) { + userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, id) + var resp User + err := m.QueryRow(&resp, userIdKey, func(conn sqlx.SqlConn, v interface{}) error { + query := `select ` + userRows + ` from ` + m.table + ` where id = ? limit 1` + return conn.QueryRow(v, query, id) + }) + switch err { + case nil: + return &resp, nil + case sqlc.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } + } + + func (m *UserModel) FindOneByName(name string) (*User, error) { + userNameKey := fmt.Sprintf("%s%v", cacheUserNamePrefix, name) + var resp User + err := m.QueryRowIndex(&resp, userNameKey, func(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheUserIdPrefix, primary) + }, func(conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := `select ` + userRows + ` from ` + m.table + ` where name = ? limit 1` + if err := conn.QueryRow(&resp, query, name); err != nil { + return nil, err + } + return resp.Id, nil + }, func(conn sqlx.SqlConn, v, primary interface{}) error { + query := `select ` + userRows + ` from ` + m.table + ` where id = ? limit 1` + return conn.QueryRow(v, query, primary) + }) + switch err { + case nil: + return &resp, nil + case sqlc.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } + } + + func (m *UserModel) FindOneByMobile(mobile string) (*User, error) { + userMobileKey := fmt.Sprintf("%s%v", cacheUserMobilePrefix, mobile) + var resp User + err := m.QueryRowIndex(&resp, userMobileKey, func(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheUserIdPrefix, primary) + }, func(conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := `select ` + userRows + ` from ` + m.table + ` where mobile = ? limit 1` + if err := conn.QueryRow(&resp, query, mobile); err != nil { + return nil, err + } + return resp.Id, nil + }, func(conn sqlx.SqlConn, v, primary interface{}) error { + query := `select ` + userRows + ` from ` + m.table + ` where id = ? limit 1` + return conn.QueryRow(v, query, primary) + }) + switch err { + case nil: + return &resp, nil + case sqlc.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } + } + + func (m *UserModel) Update(data User) error { + userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, data.Id) + _, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) { + query := `update ` + m.table + ` set ` + userRowsWithPlaceHolder + ` where id = ?` + return conn.Exec(query, data.Name, data.Password, data.Mobile, data.Gender, data.Nickname, data.Id) + }, userIdKey) + return err + } + + func (m *UserModel) Delete(id int64) error { + data, err := m.FindOne(id) + if err != nil { + return err + } + userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, id) + userNameKey := fmt.Sprintf("%s%v", cacheUserNamePrefix, data.Name) + userMobileKey := fmt.Sprintf("%s%v", cacheUserMobilePrefix, data.Mobile) + _, err = m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) { + query := `delete from ` + m.table + ` where id = ?` + return conn.Exec(query, id) + }, userIdKey, userNameKey, userMobileKey) + return err + } ``` -# 用法 +## 用法 -``` -$ goctl model mysql -h +```Plain Text +goctl model mysql -h ``` -``` +```Plain Text NAME: goctl model mysql - generate mysql model" @@ -196,52 +196,56 @@ OPTIONS: --help, -h show help ``` -# 生成规则 +## 生成规则 * 默认规则 我们默认用户在建表时会创建createTime、updateTime字段(忽略大小写、下划线命名风格)且默认值均为`CURRENT_TIMESTAMP`,而updateTime支持`ON UPDATE CURRENT_TIMESTAMP`,对于这两个字段生成`insert`、`update`时会被移除,不在赋值范畴内,当然,如果你不需要这两个字段那也无大碍。 * 带缓存模式 - * ddl - - ```shell script - $ goctl model mysql -src={filename} -dir={dir} -cache=true - ``` - * datasource - - ```shell script - $ goctl model mysql datasource -url={datasource} -table={tables} -dir={dir} -cache=true - ``` + * ddl + + ```shell script + goctl model mysql -src={filename} -dir={dir} -cache=true + ``` + + * datasource + + ```shell script + goctl model mysql datasource -url={datasource} -table={tables} -dir={dir} -cache=true + ``` 目前仅支持redis缓存,如果选择带缓存模式,即生成的`FindOne(ByXxx)`&`Delete`代码会生成带缓存逻辑的代码,目前仅支持单索引字段(除全文索引外),对于联合索引我们默认认为不需要带缓存,且不属于通用型代码,因此没有放在代码生成行列,如example中user表中的`id`、`name`、`mobile`字段均属于单字段索引。 * 不带缓存模式 - + * ddl ```shell script - $ goctl model -src={filename} -dir={dir} + goctl model -src={filename} -dir={dir} ``` + * datasource ```shell script - $ goctl model mysql datasource -url={datasource} -table={tables} -dir={dir} + goctl model mysql datasource -url={datasource} -table={tables} -dir={dir} ``` + or * ddl - + ```shell script - $ goctl model -src={filename} -dir={dir} -cache=false + goctl model -src={filename} -dir={dir} -cache=false ``` + * datasource ```shell script - $ goctl model mysql datasource -url={datasource} -table={tables} -dir={dir} -cache=false + goctl model mysql datasource -url={datasource} -table={tables} -dir={dir} -cache=false ``` - 生成代码仅基本的CURD结构。 +生成代码仅基本的CURD结构。 -# 缓存 +## 缓存 对于缓存这一块我选择用一问一答的形式进行罗列。我想这样能够更清晰的描述model中缓存的功能。 @@ -261,14 +265,8 @@ OPTIONS: 目前,我认为除了基本的CURD外,其他的代码均属于业务型代码,这个我觉得开发人员根据业务需要进行编写更好。 -# QA +## QA * goctl model除了命令行模式,支持插件模式吗? 很快支持idea插件。 - - - - - - diff --git a/tools/goctl/rpc/CHANGELOG.md b/tools/goctl/rpc/CHANGELOG.md index 3518f698..d5bf5de7 100644 --- a/tools/goctl/rpc/CHANGELOG.md +++ b/tools/goctl/rpc/CHANGELOG.md @@ -1,14 +1,16 @@ # Change log -# 2020-08-29 +## 2020-09-10 + * rpc greet服务一键生成 * 修复相对路径生成rpc服务package引入错误bug * 移除`--shared`参数 -# 2020-08-29 +## 2020-08-29 + * 新增支持windows生成 -# 2020-08-27 +## 2020-08-27 + * 新增支持rpc模板生成 * 新增支持rpc服务生成 - diff --git a/tools/goctl/rpc/README.md b/tools/goctl/rpc/README.md index 9ac26164..60d1adf3 100644 --- a/tools/goctl/rpc/README.md +++ b/tools/goctl/rpc/README.md @@ -1,12 +1,14 @@ # Rpc Generation + Goctl Rpc是`goctl`脚手架下的一个rpc服务代码生成模块,支持proto模板生成和rpc服务代码生成,通过此工具生成代码你只需要关注业务逻辑编写而不用去编写一些重复性的代码。这使得我们把精力重心放在业务上,从而加快了开发效率且降低了代码出错率。 -# 特性 +## 特性 + * 简单易用 * 快速提升开发效率 * 出错率低 -# 快速开始 +## 快速开始 ### 方式一:快速生成greet服务 @@ -15,7 +17,7 @@ Goctl Rpc是`goctl`脚手架下的一个rpc服务代码生成模块,支持prot 如生成greet rpc服务: ```shell script - $ goctl rpc new greet + goctl rpc new greet ``` 执行后代码结构如下: @@ -46,12 +48,13 @@ Goctl Rpc是`goctl`脚手架下的一个rpc服务代码生成模块,支持prot ``` rpc一键生成常见问题解决见 常见问题解决 + ### 方式二:通过指定proto生成rpc服务 * 生成proto模板 ```shell script - $ goctl rpc template -o=user.proto + goctl rpc template -o=user.proto ``` ```golang @@ -78,15 +81,16 @@ rpc一键生成常见问题解决见 常见问题 rpc Login(Request)returns(Response); } ``` + * 生成rpc服务代码 - ``` - $ goctl rpc proto -src=user.proto + ```shell script + goctl rpc proto -src=user.proto ``` 代码tree - ``` + ```Plain Text user ├── etc │   └── user.json @@ -107,21 +111,21 @@ rpc一键生成常见问题解决见 常见问题 │   └── usermodel.go ├── user.go └── user.proto - ``` -# 准备工作 + +## 准备工作 * 安装了go环境 * 安装了protoc&protoc-gen-go,并且已经设置环境变量 * mockgen(可选,将移除) * 更多问题请见 注意事项 -# 用法 +## 用法 ### rpc服务生成用法 ```shell script -$ goctl rpc proto -h +goctl rpc proto -h ``` ```shell script @@ -145,44 +149,53 @@ OPTIONS: * --src 必填,proto数据源,目前暂时支持单个proto文件生成,这里不支持(不建议)外部依赖 * --dir 非必填,默认为proto文件所在目录,生成代码的目标目录 * --service 服务名称,非必填,默认为proto文件所在目录名称,但是,如果proto所在目录为一下结构: + ```shell script user ├── cmd │   └── rpc │   └── user.proto ``` + 则服务名称亦为user,而非proto所在文件夹名称了,这里推荐使用这种结构,可以方便在同一个服务名下建立不同类型的服务(api、rpc、mq等),便于代码管理与维护。 * --shared[⚠️已废弃] 非必填,默认为$dir(xxx.proto)/shared,rpc client逻辑代码存放目录。 > 注意:这里的shared文件夹名称将会是代码中的package名称。 * --idea 非必填,是否为idea插件中执行,保留字段,终端执行可以忽略 - -# 开发人员需要做什么 + +### 开发人员需要做什么 关注业务代码编写,将重复性、与业务无关的工作交给goctl,生成好rpc服务代码后,开饭人员仅需要修改 + * 服务中的配置文件编写(etc/xx.json、internal/config/config.go) * 服务中业务逻辑编写(internal/logic/xxlogic.go) * 服务中资源上下文的编写(internal/svc/servicecontext.go) -# 扩展 +## 扩展 + 对于需要进行rpc mock的开发人员,在安装了`mockgen`工具的前提下可以在rpc的shared文件中生成好对应的mock文件。 -# 注意事项 +### 注意事项 + * `google.golang.org/grpc`需要降级到v1.26.0,且protoc-gen-go版本不能高于v1.3.2(see [https://github.com/grpc/grpc-go/issues/3347](https://github.com/grpc/grpc-go/issues/3347))即 - ``` + + ```shell script replace google.golang.org/grpc => google.golang.org/grpc v1.26.0 ``` + * proto不支持暂多文件同时生成 * proto不支持外部依赖包引入,message不支持inline * 目前main文件、shared文件、handler文件会被强制覆盖,而和开发人员手动需要编写的则不会覆盖生成,这一类在代码头部均有 - ```shell script + +```shell script // Code generated by goctl. DO NOT EDIT! // Source: xxx.proto - ``` - 的标识,请注意不要将也写业务性代码写在里面。 +``` + +的标识,请注意不要将也写业务性代码写在里面。 -# 常见问题解决(go mod工程) +## 常见问题解决(go mod工程) * 错误一: @@ -192,6 +205,7 @@ OPTIONS: pb/xx.pb.go:234:5: undefined: grpc.ClientConnInterface pb/xx.pb.go:237:24: undefined: grpc.ClientConnInterface ``` + 解决方法:请将`protoc-gen-go`版本降至v1.3.2及一下 * 错误二: