Markdown lint (#58)

* markdown linter

* format markdown docs

* format exiting markdown docs
master
Sergey Cheung 4 years ago committed by GitHub
parent 7f0ec14704
commit 21e811887c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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"'

@ -0,0 +1,6 @@
{
"MD010": false,
"MD013": false,
"MD033": false,
"MD034": false
}

@ -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
Lets 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, youll find that its 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, lets 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, lets 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, lets 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
@ -243,7 +243,6 @@ syntax = "proto3";
└── add.pb.go
```
just run it, looks like:
```shell
@ -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, weve 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, weve 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

@ -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接口定义
@ -245,7 +245,6 @@
└── add.pb.go
```
直接可以运行,如下:
```shell
@ -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生成的代码包含了微服务治理的各种组件包括并发控制、自适应熔断、自适应降载、自动缓存控制等可以轻松部署以承载巨大访问量。
有任何好的提升工程效率的想法,随时欢迎交流!👏

@ -1,4 +1,5 @@
# 熔断机制设计
## 设计目的
* 依赖的服务出现大规模故障时,调用方应该尽可能少调用,降低故障服务的压力,使之尽快恢复服务

@ -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
```

@ -2,28 +2,28 @@
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
@ -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,22 +196,23 @@ OPTIONS:
--help, -h show help
```
# 生成规则
## 生成规则
* 默认规则
我们默认用户在建表时会创建createTime、updateTime字段(忽略大小写、下划线命名风格)且默认值均为`CURRENT_TIMESTAMP`而updateTime支持`ON UPDATE CURRENT_TIMESTAMP`,对于这两个字段生成`insert`、`update`时会被移除,不在赋值范畴内,当然,如果你不需要这两个字段那也无大碍。
* 带缓存模式
* ddl
* ddl
```shell script
goctl model mysql -src={filename} -dir={dir} -cache=true
```
```shell script
$ goctl model mysql -src={filename} -dir={dir} -cache=true
```
* datasource
* datasource
```shell script
$ goctl model mysql datasource -url={datasource} -table={tables} -dir={dir} -cache=true
```
```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外其他的代码均属于<i>业务型</i>代码,这个我觉得开发人员根据业务需要进行编写更好。
# QA
## QA
* goctl model除了命令行模式支持插件模式吗
很快支持idea插件。

@ -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一键生成常见问题解决见 <a href="#常见问题解决">常见问题解决</a>
### 方式二通过指定proto生成rpc服务
* 生成proto模板
```shell script
$ goctl rpc template -o=user.proto
goctl rpc template -o=user.proto
```
```golang
@ -78,15 +81,16 @@ rpc一键生成常见问题解决见 <a href="#常见问题解决">常见问题
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一键生成常见问题解决见 <a href="#常见问题解决">常见问题
└── user.proto
```
# 准备工作
## 准备工作
* 安装了go环境
* 安装了protoc&protoc-gen-go并且已经设置环境变量
* mockgen(可选,将移除)
* 更多问题请见 <a href="#注意事项">注意事项</a>
# 用法
## 用法
### rpc服务生成用法
```shell script
$ goctl rpc proto -h
goctl rpc proto -h
```
```shell script
@ -145,12 +150,14 @@ 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)/sharedrpc client逻辑代码存放目录。
@ -158,31 +165,38 @@ OPTIONS:
* --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.2see [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及一下
* 错误二:

@ -1,14 +1,16 @@
# 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
@ -17,15 +19,22 @@
`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, redisrpc等
* 在定义的get/post/put/delete等请求的handler和logic里增加处理业务逻辑的代码
* 在`servicecontext.go`里面增加需要传递给logic的一些资源比如mysql, redisrpc等
* 在定义的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
package model
type User struct {
Name string `o:"find,get,set" c:"姓名"`
Age int `o:"find,get,set" c:"年纪"`
School string `c:"学校"`
}
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文件
注意 目前rpc目录生成的proto文件暂不支持import外部proto文件

@ -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 |

@ -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)`
@ -36,11 +39,13 @@
* 1e3表示1000毫秒minRT单位也是毫秒QPS*minRT/1e3得到的就是平均每个时间点有多少并发请求
## 降载的使用
* 已经在ngin和rpcx框架里增加了可选激活配置
* CpuThreshold如果把值设置为大于0的值则激活该服务的自动降载机制
* 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)

@ -24,7 +24,7 @@ MapReduce主要有三个参数第一个参数为generate用以生产数据
场景一: 某些功能的结果往往需要依赖多个服务比如商品详情的结果往往会依赖用户服务、库存服务、订单服务等等一般被依赖的服务都是以rpc的形式对外提供为了降低依赖的耗时我们往往需要对依赖做并行处理
```
```go
func productDetail(uid, pid int64) (*ProductDetail, error) {
var pd ProductDetail
err := mr.Finish(func() (err error) {
@ -46,11 +46,12 @@ func productDetail(uid, pid int64) (*ProductDetail, error) {
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,7 +99,8 @@ func check(uid int64) (bool, error) {
***实现原理分析:***
MapReduce中首先通过buildSource方法通过执行generate(参数为无缓冲channel)产生数据并返回无缓冲的channelmapper会从该channel中读取数据
```
```go
func buildSource(generate GenerateFunc) chan interface{} {
source := make(chan interface{})
go func() {
@ -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方法中调用了executeMappersexecuteMappers消费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 {
@ -173,12 +180,11 @@ go func() {
```
在该工具包中还提供了许多针对不同业务场景的方法实现原理与MapReduce大同小异感兴趣的同学可以查看源码学习
* MapReduceVoid 功能和MapReduce类似但没有结果返回只返回error
* Finish 处理固定数量的依赖返回error有一个error立即返回
* FinishVoid 和Finish方法功能类似没有返回值
* Map 只做generate和mapper处理返回channel
* MapVoid 和Map功能类似无返回
本文主要介绍了go-zero框架中的MapReduce工具在实际的项目中非常实用。用好工具对于提升服务性能和开发效率都有很大的帮助希望本篇文章能给大家带来一些收获。

@ -1,6 +1,6 @@
# PeriodicalExecutor设计
# 添加任务
## 添加任务
* 当前没有未执行的任务
* 添加并启动定时器
@ -9,7 +9,7 @@
* 如到,执行所有缓存任务
* 未到,只添加
# 定时器到期
## 定时器到期
* 清除并执行所有缓存任务
* 再等待N个定时周期如果等待过程中一直没有新任务则退出

@ -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
Lets 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, youll find that its 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, lets 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, lets 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, weve 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, weve 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 its easy to deal with the busy sites.
If you have any ideas that can help us to improve the productivity, tell me any time! 👏

@ -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 // 配置文件
@ -321,7 +321,6 @@
}
```
通过调用`transformer`的`Expand`方法实现短链恢复到url
* 修改`internal/logic/shortenlogic.go`,如下:
@ -343,7 +342,6 @@
}
```
通过调用`transformer`的`Shorten`方法实现url到短链的变换
至此API Gateway修改完成虽然贴的代码多但是期中修改的是很少的一部分为了方便理解上下文我贴了完整代码接下来处理CRUD+cache
@ -383,7 +381,7 @@
生成后的文件结构如下:
```
```Plain Text
rpc/transform/model
├── shorturl.sql
├── shorturlmodel.go // CRUD+cache代码
@ -531,4 +529,3 @@ go-zero不只是一个框架更是一个建立在框架+工具基础上的,
通过go-zero+goctl生成的代码包含了微服务治理的各种组件包括并发控制、自适应熔断、自适应降载、自动缓存控制等可以轻松部署以承载巨大访问量。
有任何好的提升工程效率的想法,随时欢迎交流!👏

@ -21,4 +21,3 @@
* 通过Primary到DB查询行记录并写入缓存
* 有Primary到行记录的缓存
* 直接返回缓存结果

@ -25,4 +25,3 @@
`watch -n 5 kubectl top pod -n adhoc`
可以看到`kubectl`报告的`CPU`使用率,两者进行对比,即可知道是否准确

@ -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. Its 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
@ -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

@ -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)
@ -109,39 +109,41 @@ go get -u github.com/tal-tech/go-zero
2. 快速生成api服务
```shell
goctl api new greet
cd greet
go run greet.go -f etc/greet-api.yaml
```
```shell
goctl api new greet
cd greet
go run greet.go -f etc/greet-api.yaml
```
默认侦听在8888端口可以在配置文件里修改可以通过curl请求
默认侦听在8888端口可以在配置文件里修改可以通过curl请求
```shell
curl -i http://localhost:8888/greet/from/you
```
```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
```
编写业务代码:
编写业务代码:
* 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里增加业务处理逻辑
* 可以在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

@ -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基本信息比如Authapi是哪个用途。
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, redisrpc等
* 在定义的get/post/put/delete等请求的handler和logic里增加处理业务逻辑的代码
* 在`servicecontext.go`里面增加需要传递给logic的一些资源比如mysql, redisrpc等
* 在定义的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
```Plain Text
goctl api dart -api user/user.api -dir ./src
```

@ -1,10 +1,12 @@
# Change log
# 2020-08-20
## 2020-08-20
* 新增支持通过连接数据库生成model
* 支持数据库多表生成
* 优化stringx
# 2020-08-19
## 2020-08-19
* 重构model代码生成逻辑
* 实现从ddl解析表信息生成代码

@ -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,22 +196,23 @@ OPTIONS:
--help, -h show help
```
# 生成规则
## 生成规则
* 默认规则
我们默认用户在建表时会创建createTime、updateTime字段(忽略大小写、下划线命名风格)且默认值均为`CURRENT_TIMESTAMP`而updateTime支持`ON UPDATE CURRENT_TIMESTAMP`,对于这两个字段生成`insert`、`update`时会被移除,不在赋值范畴内,当然,如果你不需要这两个字段那也无大碍。
* 带缓存模式
* ddl
* ddl
```shell script
goctl model mysql -src={filename} -dir={dir} -cache=true
```
```shell script
$ goctl model mysql -src={filename} -dir={dir} -cache=true
```
* datasource
* datasource
```shell script
$ goctl model mysql datasource -url={datasource} -table={tables} -dir={dir} -cache=true
```
```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结构。
生成代码仅基本的CURD结构。
# 缓存
## 缓存
对于缓存这一块我选择用一问一答的形式进行罗列。我想这样能够更清晰的描述model中缓存的功能。
@ -261,14 +265,8 @@ OPTIONS:
目前我认为除了基本的CURD外其他的代码均属于<i>业务型</i>代码,这个我觉得开发人员根据业务需要进行编写更好。
# QA
## QA
* goctl model除了命令行模式支持插件模式吗
很快支持idea插件。

@ -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服务生成

@ -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一键生成常见问题解决见 <a href="#常见问题解决">常见问题解决</a>
### 方式二通过指定proto生成rpc服务
* 生成proto模板
```shell script
$ goctl rpc template -o=user.proto
goctl rpc template -o=user.proto
```
```golang
@ -78,15 +81,16 @@ rpc一键生成常见问题解决见 <a href="#常见问题解决">常见问题
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一键生成常见问题解决见 <a href="#常见问题解决">常见问题
│   └── usermodel.go
├── user.go
└── user.proto
```
# 准备工作
## 准备工作
* 安装了go环境
* 安装了protoc&protoc-gen-go并且已经设置环境变量
* mockgen(可选,将移除)
* 更多问题请见 <a href="#注意事项">注意事项</a>
# 用法
## 用法
### rpc服务生成用法
```shell script
$ goctl rpc proto -h
goctl rpc proto -h
```
```shell script
@ -145,12 +149,14 @@ 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)/sharedrpc client逻辑代码存放目录。
@ -158,31 +164,38 @@ OPTIONS:
* --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.2see [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及一下
* 错误二:

Loading…
Cancel
Save