Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89f3712347 | ||
|
|
af7acdd843 | ||
|
|
7ffa3349a9 | ||
|
|
f03862c378 | ||
|
|
fe3e70a60f | ||
|
|
36174ba5cc | ||
|
|
7b17b3604a | ||
|
|
eb40c2731d | ||
|
|
618bec5075 | ||
|
|
5821b7324e | ||
|
|
befdaab542 | ||
|
|
431be8ed9d | ||
|
|
3c688c319e | ||
|
|
59ffa75c00 | ||
|
|
09340e82a7 | ||
|
|
6c4a4be5d2 | ||
|
|
6e3d99e869 | ||
|
|
0f97b2019a | ||
|
|
0cf4ed46a1 | ||
|
|
3affe62ae4 | ||
|
|
0734bbcab3 | ||
|
|
f411178a4f | ||
|
|
72132ce399 | ||
|
|
db16115037 | ||
|
|
71bbf91a63 | ||
|
|
69ccc61cfe | ||
|
|
a94cf653f0 | ||
|
|
77e23ad65d | ||
|
|
38806e7237 | ||
|
|
a987d12237 | ||
|
|
33208e6ef6 | ||
|
|
5d8a3c07cd | ||
|
|
1c24e71568 | ||
|
|
229544f3ca | ||
|
|
c575fa7f95 | ||
|
|
fe2252184a | ||
|
|
1a8014c704 | ||
|
|
30e52707ae | ||
|
|
73b61e09ed | ||
|
|
9b8595a85e | ||
|
|
015e284515 |
@@ -1,36 +0,0 @@
|
||||
run:
|
||||
# concurrency: 6
|
||||
timeout: 5m
|
||||
skip-dirs:
|
||||
- core
|
||||
- doc
|
||||
- example
|
||||
- rest
|
||||
- rpcx
|
||||
- tools
|
||||
|
||||
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
- bodyclose
|
||||
- deadcode
|
||||
- errcheck
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- staticcheck
|
||||
- structcheck
|
||||
- typecheck
|
||||
- unused
|
||||
- varcheck
|
||||
# - dupl
|
||||
|
||||
|
||||
linters-settings:
|
||||
|
||||
issues:
|
||||
exclude-rules:
|
||||
- linters:
|
||||
- staticcheck
|
||||
text: 'SA1019: (baseresponse.BoolResponse|oldresponse.FormatBadRequestResponse|oldresponse.FormatResponse)|SA5008: unknown JSON option ("optional"|"default=|"range=|"options=)'
|
||||
@@ -22,19 +22,30 @@ var (
|
||||
cores uint64
|
||||
)
|
||||
|
||||
// if /proc not present, ignore the cpu calcuation, like wsl linux
|
||||
func init() {
|
||||
cpus, err := perCpuUsage()
|
||||
logx.Must(err)
|
||||
cores = uint64(len(cpus))
|
||||
if err != nil {
|
||||
logx.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
cores = uint64(len(cpus))
|
||||
sets, err := cpuSets()
|
||||
logx.Must(err)
|
||||
if err != nil {
|
||||
logx.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
quota = float64(len(sets))
|
||||
cq, err := cpuQuota()
|
||||
if err == nil {
|
||||
if cq != -1 {
|
||||
period, err := cpuPeriod()
|
||||
logx.Must(err)
|
||||
if err != nil {
|
||||
logx.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
limit := float64(cq) / float64(period)
|
||||
if limit < quota {
|
||||
@@ -44,10 +55,16 @@ func init() {
|
||||
}
|
||||
|
||||
preSystem, err = systemCpuUsage()
|
||||
logx.Must(err)
|
||||
if err != nil {
|
||||
logx.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
preTotal, err = totalCpuUsage()
|
||||
logx.Must(err)
|
||||
if err != nil {
|
||||
logx.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func RefreshCpu() uint64 {
|
||||
|
||||
BIN
doc/images/shorturl-arch.png
Normal file
BIN
doc/images/shorturl-arch.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 126 KiB |
BIN
doc/images/shorturl-benchmark.png
Normal file
BIN
doc/images/shorturl-benchmark.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 111 KiB |
507
doc/shorturl.md
Normal file
507
doc/shorturl.md
Normal file
@@ -0,0 +1,507 @@
|
||||
# 快速构建高并发微服务
|
||||
|
||||
## 0. 为什么说做好微服务很难?
|
||||
|
||||
要想做好微服务,我们需要理解和掌握的知识点非常多,从几个维度上来说:
|
||||
|
||||
* 基本功能层面
|
||||
1. 并发控制&限流,避免服务被突发流量击垮
|
||||
2. 服务注册与服务发现,确保能够动态侦测增减的节点
|
||||
3. 负载均衡,需要根据节点承受能力分发流量
|
||||
4. 超时控制,避免对已超时请求做无用功
|
||||
5. 熔断设计,快速失败,保障故障节点的恢复能力
|
||||
|
||||
* 高阶功能层面
|
||||
1. 请求认证,确保每个用户只能访问自己的数据
|
||||
2. 链路追踪,用于理解整个系统和快速定位特定请求的问题
|
||||
3. 日志,用于数据收集和问题定位
|
||||
4. 可观测性,没有度量就没有优化
|
||||
|
||||
对于其中每一点,我们都需要用很长的篇幅来讲述其原理和实现,那么对我们后端开发者来说,要想把这些知识点都掌握并落实到业务系统里,难度是非常大的,不过我们可以依赖已经被大流量验证过的框架体系。[go-zero微服务框架](https://github.com/tal-tech/go-zero)就是为此而生。
|
||||
|
||||
另外,我们始终秉承**工具大于约定和文档**的理念。我们希望尽可能减少开发人员的心智负担,把精力都投入到产生业务价值的代码上,减少重复代码的编写,所以我们开发了`goctl`工具。
|
||||
|
||||
下面我通过短链微服务来演示通过[go-zero](https://github.com/tal-tech/go-zero)快速的创建微服务的流程,走完一遍,你就会发现:原来编写微服务如此简单!
|
||||
|
||||
## 1. 什么是短链服务?
|
||||
|
||||
短链服务就是将长的URL网址,通过程序计算等方式,转换为简短的网址字符串。
|
||||
|
||||
写此短链服务是为了从整体上演示go-zero构建完整微服务的过程,算法和实现细节尽可能简化了,所以这不是一个高阶的短链服务。
|
||||
|
||||
## 2. 短链微服务架构图
|
||||
|
||||
<img src="images/shorturl-arch.png" alt="架构图" width="800" />
|
||||
|
||||
* 这里只用了`Transform RPC`一个微服务,并不是说API Gateway只能调用一个微服务,只是为了最简演示API Gateway如何调用RPC微服务而已
|
||||
* 在真正项目里要尽可能每个微服务使用自己的数据库,数据边界要清晰
|
||||
|
||||
## 3. 准备工作
|
||||
|
||||
* 安装etcd, mysql, redis
|
||||
|
||||
* 安装goctl工具
|
||||
|
||||
```shell
|
||||
export GO111MODULE=on export GOPROXY=https://goproxy.cn/,direct go get github.com/tal-tech/go-zero/tools/goctl
|
||||
```
|
||||
|
||||
* 创建工作目录`shorturl`
|
||||
|
||||
* 在`shorturl`目录下执行`go mod init shorturl`初始化`go.mod`
|
||||
|
||||
## 4. 编写API Gateway代码
|
||||
|
||||
* 通过goctl生成`api/shorturl.api`并编辑,为了简洁,去除了文件开头的`info`,代码如下:
|
||||
|
||||
```go
|
||||
type (
|
||||
expandReq struct {
|
||||
shorten string `form:"shorten"`
|
||||
}
|
||||
|
||||
expandResp struct {
|
||||
url string `json:"url"`
|
||||
}
|
||||
)
|
||||
|
||||
type (
|
||||
shortenReq struct {
|
||||
url string `form:"url"`
|
||||
}
|
||||
|
||||
shortenResp struct {
|
||||
shorten string `json:"shorten"`
|
||||
}
|
||||
)
|
||||
|
||||
service shorturl-api {
|
||||
@server(
|
||||
handler: ShortenHandler
|
||||
)
|
||||
get /shorten(shortenReq) returns(shortenResp)
|
||||
|
||||
@server(
|
||||
handler: ExpandHandler
|
||||
)
|
||||
get /expand(expandReq) returns(expandResp)
|
||||
}
|
||||
```
|
||||
|
||||
type用法和go一致,service用来定义get/post/head/delete等api请求,解释如下:
|
||||
|
||||
* `service shorturl-api {`这一行定义了service名字
|
||||
* `@server`部分用来定义server端用到的属性
|
||||
* `handler`定义了服务端handler名字
|
||||
* `get /shorten(shortenReq) returns(shortenResp)`定义了get方法的路由、请求参数、返回参数等
|
||||
|
||||
* 使用goctl生成API Gateway代码
|
||||
|
||||
```shell
|
||||
goctl api go -api shorturl.api -dir .
|
||||
```
|
||||
|
||||
生成的文件结构如下:
|
||||
|
||||
```
|
||||
.
|
||||
├── api
|
||||
│ ├── etc
|
||||
│ │ └── shorturl-api.yaml // 配置文件
|
||||
│ ├── internal
|
||||
│ │ ├── config
|
||||
│ │ │ └── config.go // 定义配置
|
||||
│ │ ├── handler
|
||||
│ │ │ ├── expandhandler.go // 实现expandHandler
|
||||
│ │ │ ├── routes.go // 定义路由处理
|
||||
│ │ │ └── shortenhandler.go // 实现shortenHandler
|
||||
│ │ ├── logic
|
||||
│ │ │ ├── expandlogic.go // 实现ExpandLogic
|
||||
│ │ │ └── shortenlogic.go // 实现ShortenLogic
|
||||
│ │ ├── svc
|
||||
│ │ │ └── servicecontext.go // 定义ServiceContext
|
||||
│ │ └── types
|
||||
│ │ └── types.go // 定义请求、返回结构体
|
||||
│ ├── shorturl.api
|
||||
│ └── shorturl.go // main入口定义
|
||||
├── go.mod
|
||||
└── go.sum
|
||||
```
|
||||
|
||||
* 启动API Gateway服务,默认侦听在8888端口
|
||||
|
||||
```shell
|
||||
go run shorturl.go -f etc/shorturl-api.yaml
|
||||
```
|
||||
|
||||
* 测试API Gateway服务
|
||||
|
||||
```shell
|
||||
curl -i "http://localhost:8888/shorten?url=http://www.xiaoheiban.cn"
|
||||
```
|
||||
|
||||
返回如下:
|
||||
|
||||
```http
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
Date: Thu, 27 Aug 2020 14:31:39 GMT
|
||||
Content-Length: 15
|
||||
|
||||
{"shortUrl":""}
|
||||
```
|
||||
|
||||
可以看到我们API Gateway其实啥也没干,就返回了个空值,接下来我们会在rpc服务里实现业务逻辑
|
||||
|
||||
* 可以修改`internal/svc/servicecontext.go`来传递服务依赖(如果需要)
|
||||
|
||||
* 实现逻辑可以修改`internal/logic`下的对应文件
|
||||
|
||||
* 可以通过`goctl`生成各种客户端语言的api调用代码
|
||||
|
||||
* 到这里,你已经可以通过goctl生成客户端代码给客户端同学并行开发了,支持多种语言,详见文档
|
||||
|
||||
## 5. 编写transform rpc服务
|
||||
|
||||
* 在`rpc/transform`目录下编写`transform.proto`文件
|
||||
|
||||
可以通过命令生成proto文件模板
|
||||
|
||||
```shell
|
||||
goctl rpc template -o transform.proto
|
||||
```
|
||||
|
||||
修改后文件内容如下:
|
||||
|
||||
```protobuf
|
||||
syntax = "proto3";
|
||||
|
||||
package transform;
|
||||
|
||||
message expandReq {
|
||||
string shorten = 1;
|
||||
}
|
||||
|
||||
message expandResp {
|
||||
string url = 1;
|
||||
}
|
||||
|
||||
message shortenReq {
|
||||
string url = 1;
|
||||
}
|
||||
|
||||
message shortenResp {
|
||||
string shorten = 1;
|
||||
}
|
||||
|
||||
service transformer {
|
||||
rpc expand(expandReq) returns(expandResp);
|
||||
rpc shorten(shortenReq) returns(shortenResp);
|
||||
}
|
||||
```
|
||||
|
||||
* 用`goctl`生成rpc代码,在`rpc/transform`目录下执行命令
|
||||
|
||||
```shell
|
||||
goctl rpc proto -src transform.proto
|
||||
```
|
||||
|
||||
文件结构如下:
|
||||
|
||||
```
|
||||
rpc/transform
|
||||
├── etc
|
||||
│ └── transform.yaml // 配置文件
|
||||
├── internal
|
||||
│ ├── config
|
||||
│ │ └── config.go // 配置定义
|
||||
│ ├── logic
|
||||
│ │ ├── expandlogic.go // expand业务逻辑在这里实现
|
||||
│ │ └── shortenlogic.go // shorten业务逻辑在这里实现
|
||||
│ ├── server
|
||||
│ │ └── transformerserver.go // 调用入口, 不需要修改
|
||||
│ └── svc
|
||||
│ └── servicecontext.go // 定义ServiceContext,传递依赖
|
||||
├── pb
|
||||
│ └── transform.pb.go
|
||||
├── transform.go // rpc服务main函数
|
||||
├── transform.proto
|
||||
└── transformer
|
||||
├── transformer.go // 提供了外部调用方法,无需修改
|
||||
├── transformer_mock.go // mock方法,测试用
|
||||
└── types.go // request/response结构体定义
|
||||
```
|
||||
|
||||
直接可以运行,如下:
|
||||
|
||||
```shell
|
||||
$ go run transform.go -f etc/transform.yaml
|
||||
Starting rpc server at 127.0.0.1:8080...
|
||||
```
|
||||
|
||||
`etc/transform.yaml`文件里可以修改侦听端口等配置
|
||||
|
||||
## 6. 修改API Gateway代码调用transform rpc服务
|
||||
|
||||
* 修改配置文件`shorturl-api.yaml`,增加如下内容
|
||||
|
||||
```yaml
|
||||
Transform:
|
||||
Etcd:
|
||||
Hosts:
|
||||
- localhost:2379
|
||||
Key: transform.rpc
|
||||
```
|
||||
|
||||
通过etcd自动去发现可用的transform服务
|
||||
|
||||
* 修改`internal/config/config.go`如下,增加transform服务依赖
|
||||
|
||||
```go
|
||||
type Config struct {
|
||||
rest.RestConf
|
||||
Transform rpcx.RpcClientConf // 手动代码
|
||||
}
|
||||
```
|
||||
|
||||
* 修改`internal/svc/servicecontext.go`,如下:
|
||||
|
||||
```go
|
||||
type ServiceContext struct {
|
||||
Config config.Config
|
||||
Transformer rpcx.Client // 手动代码
|
||||
}
|
||||
|
||||
func NewServiceContext(c config.Config) *ServiceContext {
|
||||
return &ServiceContext{
|
||||
Config: c,
|
||||
Transformer: rpcx.MustNewClient(c.Transform), // 手动代码
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
通过ServiceContext在不同业务逻辑之间传递依赖
|
||||
|
||||
* 修改`internal/logic/expandlogic.go`里的`Expand`方法,如下:
|
||||
|
||||
```go
|
||||
func (l *ExpandLogic) Expand(req types.ExpandReq) (*types.ExpandResp, error) {
|
||||
// 手动代码开始
|
||||
trans := transformer.NewTransformer(l.svcCtx.Transformer)
|
||||
resp, err := trans.Expand(l.ctx, &transformer.ExpandReq{
|
||||
Shorten: req.Shorten,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.ExpandResp{
|
||||
Url: resp.Url,
|
||||
}, nil
|
||||
// 手动代码结束
|
||||
}
|
||||
```
|
||||
|
||||
通过调用`transformer`的`Expand`方法实现短链恢复到url
|
||||
|
||||
* 修改`internal/logic/shortenlogic.go`,如下:
|
||||
|
||||
```go
|
||||
func (l *ShortenLogic) Shorten(req types.ShortenReq) (*types.ShortenResp, error) {
|
||||
// 手动代码开始
|
||||
trans := transformer.NewTransformer(l.svcCtx.Transformer)
|
||||
resp, err := trans.Shorten(l.ctx, &transformer.ShortenReq{
|
||||
Url: req.Url,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.ShortenResp{
|
||||
Shorten: resp.Shorten,
|
||||
}, nil
|
||||
// 手动代码结束
|
||||
}
|
||||
```
|
||||
|
||||
通过调用`transformer`的`Shorten`方法实现url到短链的变换
|
||||
|
||||
至此,API Gateway修改完成,虽然贴的代码多,但是期中修改的是很少的一部分,为了方便理解上下文,我贴了完整代码,接下来处理CRUD+cache
|
||||
|
||||
## 7. 定义数据库表结构,并生成CRUD+cache代码
|
||||
|
||||
* shorturl下创建`rpc/transform/model`目录:`mkdir -p rpc/transform/model`
|
||||
|
||||
* 在rpc/transform/model目录下编写创建shorturl表的sql文件`shorturl.sql`,如下:
|
||||
|
||||
```sql
|
||||
CREATE TABLE `shorturl`
|
||||
(
|
||||
`shorten` varchar(255) NOT NULL COMMENT 'shorten key',
|
||||
`url` varchar(255) NOT NULL COMMENT 'original url',
|
||||
PRIMARY KEY(`shorten`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
```
|
||||
|
||||
* 创建DB和table
|
||||
|
||||
```sql
|
||||
create database gozero;
|
||||
```
|
||||
|
||||
```sql
|
||||
source shorturl.sql;
|
||||
```
|
||||
|
||||
* 在`rpc/transform/model`目录下执行如下命令生成CRUD+cache代码,`-c`表示使用`redis cache`
|
||||
|
||||
```shell
|
||||
goctl model mysql ddl -c -src shorturl.sql -dir .
|
||||
```
|
||||
|
||||
也可以用`datasource`命令代替`ddl`来指定数据库链接直接从schema生成
|
||||
|
||||
生成后的文件结构如下:
|
||||
|
||||
```
|
||||
rpc/transform/model
|
||||
├── shorturl.sql
|
||||
├── shorturlmodel.go // CRUD+cache代码
|
||||
└── vars.go // 定义常量和变量
|
||||
```
|
||||
|
||||
## 8. 修改shorten/expand rpc代码调用crud+cache代码
|
||||
|
||||
* 修改`rpc/transform/etc/transform.yaml`,增加如下内容:
|
||||
|
||||
```yaml
|
||||
DataSource: root:@tcp(localhost:3306)/gozero
|
||||
Table: shorturl
|
||||
Cache:
|
||||
- Host: localhost:6379
|
||||
```
|
||||
|
||||
可以使用多个redis作为cache,支持redis单点或者redis集群
|
||||
|
||||
* 修改`rpc/transform/internal/config.go`,如下:
|
||||
|
||||
```go
|
||||
type Config struct {
|
||||
rpcx.RpcServerConf
|
||||
DataSource string // 手动代码
|
||||
Table string // 手动代码
|
||||
Cache cache.CacheConf // 手动代码
|
||||
}
|
||||
```
|
||||
|
||||
增加了mysql和redis cache配置
|
||||
|
||||
* 修改`rpc/transform/internal/svc/servicecontext.go`,如下:
|
||||
|
||||
```go
|
||||
type ServiceContext struct {
|
||||
c config.Config
|
||||
Model *model.ShorturlModel // 手动代码
|
||||
}
|
||||
|
||||
func NewServiceContext(c config.Config) *ServiceContext {
|
||||
return &ServiceContext{
|
||||
c: c,
|
||||
Model: model.NewShorturlModel(sqlx.NewMysql(c.DataSource), c.Cache, c.Table), // 手动代码
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
* 修改`rpc/transform/internal/logic/expandlogic.go`,如下:
|
||||
|
||||
```go
|
||||
func (l *ExpandLogic) Expand(in *expand.ExpandReq) (*expand.ExpandResp, error) {
|
||||
// 手动代码开始
|
||||
res, err := l.svcCtx.Model.FindOne(in.Shorten)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &transform.ExpandResp{
|
||||
Url: res.Url,
|
||||
}, nil
|
||||
// 手动代码结束
|
||||
}
|
||||
```
|
||||
|
||||
* 修改`rpc/shorten/internal/logic/shortenlogic.go`,如下:
|
||||
|
||||
```go
|
||||
func (l *ShortenLogic) Shorten(in *shorten.ShortenReq) (*shorten.ShortenResp, error) {
|
||||
// 手动代码开始,生成短链接
|
||||
key := hash.Md5Hex([]byte(in.Url))[:6]
|
||||
_, err := l.svcCtx.Model.Insert(model.Shorturl{
|
||||
Shorten: key,
|
||||
Url: in.Url,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &transform.ShortenResp{
|
||||
Shorten: key,
|
||||
}, nil
|
||||
// 手动代码结束
|
||||
}
|
||||
```
|
||||
|
||||
至此代码修改完成,凡事手动修改的代码我加了标注
|
||||
|
||||
## 9. 完整调用演示
|
||||
|
||||
* shorten api调用
|
||||
|
||||
```shell
|
||||
curl -i "http://localhost:8888/shorten?url=http://www.xiaoheiban.cn"
|
||||
```
|
||||
|
||||
返回如下:
|
||||
|
||||
```http
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
Date: Sat, 29 Aug 2020 10:49:49 GMT
|
||||
Content-Length: 21
|
||||
|
||||
{"shorten":"f35b2a"}
|
||||
```
|
||||
|
||||
* expand api调用
|
||||
|
||||
```shell
|
||||
curl -i "http://localhost:8888/expand?shorten=f35b2a"
|
||||
```
|
||||
|
||||
返回如下:
|
||||
|
||||
```http
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
Date: Sat, 29 Aug 2020 10:51:53 GMT
|
||||
Content-Length: 34
|
||||
|
||||
{"url":"http://www.xiaoheiban.cn"}
|
||||
```
|
||||
|
||||
## 10. Benchmark
|
||||
|
||||
因为写入依赖于mysql的写入速度,就相当于压mysql了,所以压测只测试了expand接口,相当于从mysql里读取并利用缓存,shorten.lua里随机从db里获取了100个热key来生成压测请求
|
||||
|
||||

|
||||
|
||||
可以看出在我的MacBook Pro上能达到3万+的qps。
|
||||
|
||||
## 11. 总结
|
||||
|
||||
我们一直强调**工具大于约定和文档**。
|
||||
|
||||
go-zero不只是一个框架,更是一个建立在框架+工具基础上的,简化和规范了整个微服务构建的技术体系。
|
||||
|
||||
我们在保持简单的同时也尽可能把微服务治理的复杂度封装到了框架内部,极大的降低了开发人员的心智负担,使得业务开发得以快速推进。
|
||||
|
||||
通过go-zero+goctl生成的代码,包含了微服务治理的各种组件,包括:并发控制、自适应熔断、自适应降载、自动缓存控制等,可以轻松部署以承载巨大访问量。
|
||||
@@ -1,13 +0,0 @@
|
||||
version := v$(shell /bin/date "+%y%m%d%H%M%S")
|
||||
|
||||
build:
|
||||
#docker pull alpine
|
||||
#docker pull golang:alpine
|
||||
cd $(GOPATH)/src/zero && docker build -t registry.cn-hangzhou.aliyuncs.com/xapp/etcdmon:$(version) . -f example/etcd/demo/Dockerfile
|
||||
#docker image prune --filter label=stage=gobuilder -f
|
||||
|
||||
push: build
|
||||
docker push registry.cn-hangzhou.aliyuncs.com/xapp/etcdmon:$(version)
|
||||
|
||||
deploy: push
|
||||
kubectl -n xx-xiaoheiban set image deployment/etcdmon-deployment etcdmon=registry-vpc.cn-hangzhou.aliyuncs.com/xapp/etcdmon:$(version)
|
||||
@@ -1,15 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: etcdmon
|
||||
namespace: discov
|
||||
spec:
|
||||
containers:
|
||||
- name: etcdmon
|
||||
image: registry-vpc.cn-hangzhou.aliyuncs.com/xapp/etcdmon:v200620093045
|
||||
imagePullPolicy: Always
|
||||
env:
|
||||
- name: ETCD_CLUSTER
|
||||
value: etcd.discov:2379
|
||||
imagePullSecrets:
|
||||
- name: aliyun
|
||||
@@ -1,4 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: discovery
|
||||
@@ -1,378 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: discov
|
||||
namespace: discovery
|
||||
spec:
|
||||
ports:
|
||||
- name: discov-port
|
||||
port: 2379
|
||||
protocol: TCP
|
||||
targetPort: 2379
|
||||
selector:
|
||||
app: discov
|
||||
|
||||
---
|
||||
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
labels:
|
||||
app: discov
|
||||
discov_node: discov0
|
||||
name: discov0
|
||||
namespace: discovery
|
||||
spec:
|
||||
containers:
|
||||
- command:
|
||||
- /usr/local/bin/etcd
|
||||
- --name
|
||||
- discov0
|
||||
- --initial-advertise-peer-urls
|
||||
- http://discov0:2380
|
||||
- --listen-peer-urls
|
||||
- http://0.0.0.0:2380
|
||||
- --listen-client-urls
|
||||
- http://0.0.0.0:2379
|
||||
- --advertise-client-urls
|
||||
- http://discov0:2379
|
||||
- --initial-cluster
|
||||
- discov0=http://discov0:2380,discov1=http://discov1:2380,discov2=http://discov2:2380,discov3=http://discov3:2380,discov4=http://discov4:2380
|
||||
- --initial-cluster-state
|
||||
- new
|
||||
image: registry-vpc.cn-hangzhou.aliyuncs.com/xapp/etcd:latest
|
||||
name: discov0
|
||||
ports:
|
||||
- containerPort: 2379
|
||||
name: client
|
||||
protocol: TCP
|
||||
- containerPort: 2380
|
||||
name: server
|
||||
protocol: TCP
|
||||
imagePullSecrets:
|
||||
- name: aliyun
|
||||
affinity:
|
||||
podAntiAffinity:
|
||||
requiredDuringSchedulingIgnoredDuringExecution:
|
||||
- labelSelector:
|
||||
matchExpressions:
|
||||
- key: app
|
||||
operator: In
|
||||
values:
|
||||
- discov
|
||||
topologyKey: "kubernetes.io/hostname"
|
||||
restartPolicy: Always
|
||||
|
||||
---
|
||||
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
discov_node: discov0
|
||||
name: discov0
|
||||
namespace: discovery
|
||||
spec:
|
||||
ports:
|
||||
- name: client
|
||||
port: 2379
|
||||
protocol: TCP
|
||||
targetPort: 2379
|
||||
- name: server
|
||||
port: 2380
|
||||
protocol: TCP
|
||||
targetPort: 2380
|
||||
selector:
|
||||
discov_node: discov0
|
||||
|
||||
---
|
||||
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
labels:
|
||||
app: discov
|
||||
discov_node: discov1
|
||||
name: discov1
|
||||
namespace: discovery
|
||||
spec:
|
||||
containers:
|
||||
- command:
|
||||
- /usr/local/bin/etcd
|
||||
- --name
|
||||
- discov1
|
||||
- --initial-advertise-peer-urls
|
||||
- http://discov1:2380
|
||||
- --listen-peer-urls
|
||||
- http://0.0.0.0:2380
|
||||
- --listen-client-urls
|
||||
- http://0.0.0.0:2379
|
||||
- --advertise-client-urls
|
||||
- http://discov1:2379
|
||||
- --initial-cluster
|
||||
- discov0=http://discov0:2380,discov1=http://discov1:2380,discov2=http://discov2:2380,discov3=http://discov3:2380,discov4=http://discov4:2380
|
||||
- --initial-cluster-state
|
||||
- new
|
||||
image: registry-vpc.cn-hangzhou.aliyuncs.com/xapp/etcd:latest
|
||||
name: discov1
|
||||
ports:
|
||||
- containerPort: 2379
|
||||
name: client
|
||||
protocol: TCP
|
||||
- containerPort: 2380
|
||||
name: server
|
||||
protocol: TCP
|
||||
imagePullSecrets:
|
||||
- name: aliyun
|
||||
affinity:
|
||||
podAntiAffinity:
|
||||
requiredDuringSchedulingIgnoredDuringExecution:
|
||||
- labelSelector:
|
||||
matchExpressions:
|
||||
- key: app
|
||||
operator: In
|
||||
values:
|
||||
- discov
|
||||
topologyKey: "kubernetes.io/hostname"
|
||||
restartPolicy: Always
|
||||
|
||||
---
|
||||
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
discov_node: discov1
|
||||
name: discov1
|
||||
namespace: discovery
|
||||
spec:
|
||||
ports:
|
||||
- name: client
|
||||
port: 2379
|
||||
protocol: TCP
|
||||
targetPort: 2379
|
||||
- name: server
|
||||
port: 2380
|
||||
protocol: TCP
|
||||
targetPort: 2380
|
||||
selector:
|
||||
discov_node: discov1
|
||||
|
||||
---
|
||||
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
labels:
|
||||
app: discov
|
||||
discov_node: discov2
|
||||
name: discov2
|
||||
namespace: discovery
|
||||
spec:
|
||||
containers:
|
||||
- command:
|
||||
- /usr/local/bin/etcd
|
||||
- --name
|
||||
- discov2
|
||||
- --initial-advertise-peer-urls
|
||||
- http://discov2:2380
|
||||
- --listen-peer-urls
|
||||
- http://0.0.0.0:2380
|
||||
- --listen-client-urls
|
||||
- http://0.0.0.0:2379
|
||||
- --advertise-client-urls
|
||||
- http://discov2:2379
|
||||
- --initial-cluster
|
||||
- discov0=http://discov0:2380,discov1=http://discov1:2380,discov2=http://discov2:2380,discov3=http://discov3:2380,discov4=http://discov4:2380
|
||||
- --initial-cluster-state
|
||||
- new
|
||||
image: registry-vpc.cn-hangzhou.aliyuncs.com/xapp/etcd:latest
|
||||
name: discov2
|
||||
ports:
|
||||
- containerPort: 2379
|
||||
name: client
|
||||
protocol: TCP
|
||||
- containerPort: 2380
|
||||
name: server
|
||||
protocol: TCP
|
||||
imagePullSecrets:
|
||||
- name: aliyun
|
||||
affinity:
|
||||
podAntiAffinity:
|
||||
requiredDuringSchedulingIgnoredDuringExecution:
|
||||
- labelSelector:
|
||||
matchExpressions:
|
||||
- key: app
|
||||
operator: In
|
||||
values:
|
||||
- discov
|
||||
topologyKey: "kubernetes.io/hostname"
|
||||
restartPolicy: Always
|
||||
|
||||
---
|
||||
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
discov_node: discov2
|
||||
name: discov2
|
||||
namespace: discovery
|
||||
spec:
|
||||
ports:
|
||||
- name: client
|
||||
port: 2379
|
||||
protocol: TCP
|
||||
targetPort: 2379
|
||||
- name: server
|
||||
port: 2380
|
||||
protocol: TCP
|
||||
targetPort: 2380
|
||||
selector:
|
||||
discov_node: discov2
|
||||
|
||||
---
|
||||
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
labels:
|
||||
app: discov
|
||||
discov_node: discov3
|
||||
name: discov3
|
||||
namespace: discovery
|
||||
spec:
|
||||
containers:
|
||||
- command:
|
||||
- /usr/local/bin/etcd
|
||||
- --name
|
||||
- discov3
|
||||
- --initial-advertise-peer-urls
|
||||
- http://discov3:2380
|
||||
- --listen-peer-urls
|
||||
- http://0.0.0.0:2380
|
||||
- --listen-client-urls
|
||||
- http://0.0.0.0:2379
|
||||
- --advertise-client-urls
|
||||
- http://discov3:2379
|
||||
- --initial-cluster
|
||||
- discov0=http://discov0:2380,discov1=http://discov1:2380,discov2=http://discov2:2380,discov3=http://discov3:2380,discov4=http://discov4:2380
|
||||
- --initial-cluster-state
|
||||
- new
|
||||
image: registry-vpc.cn-hangzhou.aliyuncs.com/xapp/etcd:latest
|
||||
name: discov3
|
||||
ports:
|
||||
- containerPort: 2379
|
||||
name: client
|
||||
protocol: TCP
|
||||
- containerPort: 2380
|
||||
name: server
|
||||
protocol: TCP
|
||||
imagePullSecrets:
|
||||
- name: aliyun
|
||||
affinity:
|
||||
podAntiAffinity:
|
||||
requiredDuringSchedulingIgnoredDuringExecution:
|
||||
- labelSelector:
|
||||
matchExpressions:
|
||||
- key: app
|
||||
operator: In
|
||||
values:
|
||||
- discov
|
||||
topologyKey: "kubernetes.io/hostname"
|
||||
restartPolicy: Always
|
||||
|
||||
---
|
||||
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
discov_node: discov3
|
||||
name: discov3
|
||||
namespace: discovery
|
||||
spec:
|
||||
ports:
|
||||
- name: client
|
||||
port: 2379
|
||||
protocol: TCP
|
||||
targetPort: 2379
|
||||
- name: server
|
||||
port: 2380
|
||||
protocol: TCP
|
||||
targetPort: 2380
|
||||
selector:
|
||||
discov_node: discov3
|
||||
|
||||
---
|
||||
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
labels:
|
||||
app: discov
|
||||
discov_node: discov4
|
||||
name: discov4
|
||||
namespace: discovery
|
||||
spec:
|
||||
containers:
|
||||
- command:
|
||||
- /usr/local/bin/etcd
|
||||
- --name
|
||||
- discov4
|
||||
- --initial-advertise-peer-urls
|
||||
- http://discov4:2380
|
||||
- --listen-peer-urls
|
||||
- http://0.0.0.0:2380
|
||||
- --listen-client-urls
|
||||
- http://0.0.0.0:2379
|
||||
- --advertise-client-urls
|
||||
- http://discov4:2379
|
||||
- --initial-cluster
|
||||
- discov0=http://discov0:2380,discov1=http://discov1:2380,discov2=http://discov2:2380,discov3=http://discov3:2380,discov4=http://discov4:2380
|
||||
- --initial-cluster-state
|
||||
- new
|
||||
image: registry-vpc.cn-hangzhou.aliyuncs.com/xapp/etcd:latest
|
||||
name: discov4
|
||||
ports:
|
||||
- containerPort: 2379
|
||||
name: client
|
||||
protocol: TCP
|
||||
- containerPort: 2380
|
||||
name: server
|
||||
protocol: TCP
|
||||
imagePullSecrets:
|
||||
- name: aliyun
|
||||
affinity:
|
||||
podAntiAffinity:
|
||||
requiredDuringSchedulingIgnoredDuringExecution:
|
||||
- labelSelector:
|
||||
matchExpressions:
|
||||
- key: app
|
||||
operator: In
|
||||
values:
|
||||
- discov
|
||||
topologyKey: "kubernetes.io/hostname"
|
||||
restartPolicy: Always
|
||||
|
||||
---
|
||||
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
discov_node: discov4
|
||||
name: discov4
|
||||
namespace: discovery
|
||||
spec:
|
||||
ports:
|
||||
- name: client
|
||||
port: 2379
|
||||
protocol: TCP
|
||||
targetPort: 2379
|
||||
- name: server
|
||||
port: 2380
|
||||
protocol: TCP
|
||||
targetPort: 2380
|
||||
selector:
|
||||
discov_node: discov4
|
||||
@@ -1,11 +0,0 @@
|
||||
version := v$(shell /bin/date "+%y%m%d%H%M%S")
|
||||
|
||||
build:
|
||||
cd $(GOPATH)/src/zero && docker build -t registry.cn-hangzhou.aliyuncs.com/xapp/pub:$(version) . -f example/etcd/pub/Dockerfile
|
||||
docker image prune --filter label=stage=gobuilder -f
|
||||
|
||||
push: build
|
||||
docker push registry.cn-hangzhou.aliyuncs.com/xapp/pub:$(version)
|
||||
|
||||
deploy: push
|
||||
kubectl -n adhoc set image deployment/pub-deployment pub=registry-vpc.cn-hangzhou.aliyuncs.com/xapp/pub:$(version)
|
||||
@@ -1,26 +0,0 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: pub-deployment
|
||||
namespace: adhoc
|
||||
labels:
|
||||
app: pub
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: pub
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: pub
|
||||
spec:
|
||||
containers:
|
||||
- name: pub
|
||||
image: registry-vpc.cn-hangzhou.aliyuncs.com/xapp/pub:v200213172101
|
||||
command:
|
||||
- /app/pub
|
||||
- -v
|
||||
- ccc
|
||||
imagePullSecrets:
|
||||
- name: aliyun
|
||||
@@ -1,11 +0,0 @@
|
||||
version := v$(shell /bin/date "+%y%m%d%H%M%S")
|
||||
|
||||
build:
|
||||
cd $(GOPATH)/src/zero && docker build -t registry.cn-hangzhou.aliyuncs.com/xapp/sub:$(version) . -f example/etcd/sub/Dockerfile
|
||||
docker image prune --filter label=stage=gobuilder -f
|
||||
|
||||
push: build
|
||||
docker push registry.cn-hangzhou.aliyuncs.com/xapp/sub:$(version)
|
||||
|
||||
deploy: push
|
||||
kubectl -n adhoc set image deployment/sub-deployment sub=registry-vpc.cn-hangzhou.aliyuncs.com/xapp/sub:$(version)
|
||||
@@ -1,16 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
labels:
|
||||
app: sub
|
||||
name: sub
|
||||
namespace: adhoc
|
||||
spec:
|
||||
containers:
|
||||
- command:
|
||||
- /app/sub
|
||||
image: registry-vpc.cn-hangzhou.aliyuncs.com/xapp/sub:v200213220509
|
||||
name: sub
|
||||
imagePullSecrets:
|
||||
- name: aliyun
|
||||
restartPolicy: Always
|
||||
@@ -1,11 +0,0 @@
|
||||
version := v$(shell /bin/date "+%y%m%d%H%M%S")
|
||||
|
||||
build:
|
||||
docker pull alpine
|
||||
cd $(GOPATH)/src/zero && docker build -t registry.cn-hangzhou.aliyuncs.com/xapp/graceful:$(version) . -f example/graceful/dns/api/Dockerfile
|
||||
|
||||
push: build
|
||||
docker push registry.cn-hangzhou.aliyuncs.com/xapp/graceful:$(version)
|
||||
|
||||
deploy: push
|
||||
kubectl -n kevin set image deployment/graceful-deployment graceful=registry-vpc.cn-hangzhou.aliyuncs.com/xapp/graceful:$(version)
|
||||
@@ -1,42 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: graceful
|
||||
namespace: kevin
|
||||
spec:
|
||||
selector:
|
||||
app: graceful
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- name: graceful-port
|
||||
port: 3333
|
||||
targetPort: 8888
|
||||
|
||||
---
|
||||
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: graceful-deployment
|
||||
namespace: kevin
|
||||
labels:
|
||||
app: graceful
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: graceful
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: graceful
|
||||
spec:
|
||||
containers:
|
||||
- name: graceful
|
||||
image: registry-vpc.cn-hangzhou.aliyuncs.com/xapp/graceful:v191022133857
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 8888
|
||||
imagePullSecrets:
|
||||
- name: aliyun
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
version := v$(shell /bin/date "+%y%m%d%H%M%S")
|
||||
|
||||
build:
|
||||
docker pull alpine
|
||||
cd $(GOPATH)/src/zero && docker build -t registry.cn-hangzhou.aliyuncs.com/xapp/gracefulrpc:$(version) . -f example/graceful/dns/rpc/Dockerfile
|
||||
|
||||
push: build
|
||||
docker push registry.cn-hangzhou.aliyuncs.com/xapp/gracefulrpc:$(version)
|
||||
|
||||
deploy: push
|
||||
kubectl -n kevin set image deployment/gracefulrpc-deployment gracefulrpc=registry-vpc.cn-hangzhou.aliyuncs.com/xapp/gracefulrpc:$(version)
|
||||
@@ -1,46 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: gracefulrpc
|
||||
namespace: kevin
|
||||
spec:
|
||||
selector:
|
||||
app: gracefulrpc
|
||||
type: ClusterIP
|
||||
clusterIP: None
|
||||
ports:
|
||||
- name: gracefulrpc-port
|
||||
port: 3456
|
||||
|
||||
---
|
||||
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: gracefulrpc-deployment
|
||||
namespace: kevin
|
||||
labels:
|
||||
app: gracefulrpc
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: gracefulrpc
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: gracefulrpc
|
||||
spec:
|
||||
containers:
|
||||
- name: gracefulrpc
|
||||
image: registry-vpc.cn-hangzhou.aliyuncs.com/xapp/gracefulrpc:v191022143425
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 3456
|
||||
env:
|
||||
- name: POD_IP
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: status.podIP
|
||||
imagePullSecrets:
|
||||
- name: aliyun
|
||||
@@ -1,13 +0,0 @@
|
||||
version := v$(shell /bin/date "+%y%m%d%H%M%S")
|
||||
|
||||
build:
|
||||
docker pull alpine
|
||||
docker pull golang:alpine
|
||||
cd $(GOPATH)/src/zero && docker build -t registry.cn-hangzhou.aliyuncs.com/xapp/graceful:$(version) . -f example/graceful/etcd/api/Dockerfile
|
||||
docker image prune --filter label=stage=gobuilder -f
|
||||
|
||||
push: build
|
||||
docker push registry.cn-hangzhou.aliyuncs.com/xapp/graceful:$(version)
|
||||
|
||||
deploy: push
|
||||
kubectl -n kevin set image deployment/graceful-deployment graceful=registry-vpc.cn-hangzhou.aliyuncs.com/xapp/graceful:$(version)
|
||||
@@ -1,42 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: graceful
|
||||
namespace: kevin
|
||||
spec:
|
||||
selector:
|
||||
app: graceful
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- name: graceful-port
|
||||
port: 3333
|
||||
targetPort: 8888
|
||||
|
||||
---
|
||||
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: graceful-deployment
|
||||
namespace: kevin
|
||||
labels:
|
||||
app: graceful
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: graceful
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: graceful
|
||||
spec:
|
||||
containers:
|
||||
- name: graceful
|
||||
image: registry-vpc.cn-hangzhou.aliyuncs.com/xapp/graceful:v191031145905
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 8888
|
||||
imagePullSecrets:
|
||||
- name: aliyun
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
version := v$(shell /bin/date "+%y%m%d%H%M%S")
|
||||
|
||||
build:
|
||||
docker pull alpine
|
||||
docker pull golang:alpine
|
||||
cd $(GOPATH)/src/zero && docker build -t registry.cn-hangzhou.aliyuncs.com/xapp/gracefulrpc:$(version) . -f example/graceful/etcd/rpc/Dockerfile
|
||||
docker image prune --filter label=stage=gobuilder -f
|
||||
|
||||
push: build
|
||||
docker push registry.cn-hangzhou.aliyuncs.com/xapp/gracefulrpc:$(version)
|
||||
|
||||
deploy: push
|
||||
kubectl -n kevin set image deployment/gracefulrpc-deployment gracefulrpc=registry-vpc.cn-hangzhou.aliyuncs.com/xapp/gracefulrpc:$(version)
|
||||
@@ -1,30 +0,0 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: gracefulrpc-deployment
|
||||
namespace: kevin
|
||||
labels:
|
||||
app: gracefulrpc
|
||||
spec:
|
||||
replicas: 9
|
||||
selector:
|
||||
matchLabels:
|
||||
app: gracefulrpc
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: gracefulrpc
|
||||
spec:
|
||||
containers:
|
||||
- name: gracefulrpc
|
||||
image: registry-vpc.cn-hangzhou.aliyuncs.com/xapp/gracefulrpc:v191031144304
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 3456
|
||||
env:
|
||||
- name: POD_IP
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: status.podIP
|
||||
imagePullSecrets:
|
||||
- name: aliyun
|
||||
@@ -1,41 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: gracefulrpc
|
||||
namespace: kevin
|
||||
spec:
|
||||
selector:
|
||||
app: gracefulrpc
|
||||
type: ClusterIP
|
||||
clusterIP: None
|
||||
ports:
|
||||
- name: gracefulrpc-port
|
||||
port: 3456
|
||||
|
||||
---
|
||||
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: gracefulrpc-deployment
|
||||
namespace: kevin
|
||||
labels:
|
||||
app: gracefulrpc
|
||||
spec:
|
||||
replicas: 9
|
||||
selector:
|
||||
matchLabels:
|
||||
app: gracefulrpc
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: gracefulrpc
|
||||
spec:
|
||||
containers:
|
||||
- name: gracefulrpc
|
||||
image: registry-vpc.cn-hangzhou.aliyuncs.com/xapp/gracefulrpc:v191031144304
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 3456
|
||||
imagePullSecrets:
|
||||
- name: aliyun
|
||||
@@ -1,25 +0,0 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: gracefulrpc-deployment
|
||||
namespace: kevin
|
||||
labels:
|
||||
app: gracefulrpc
|
||||
spec:
|
||||
replicas: 9
|
||||
selector:
|
||||
matchLabels:
|
||||
app: gracefulrpc
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: gracefulrpc
|
||||
spec:
|
||||
containers:
|
||||
- name: gracefulrpc
|
||||
image: registry-vpc.cn-hangzhou.aliyuncs.com/xapp/gracefulrpc:v191031144304
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 3456
|
||||
imagePullSecrets:
|
||||
- name: aliyun
|
||||
@@ -1,13 +0,0 @@
|
||||
version := v1
|
||||
|
||||
build:
|
||||
cd $(GOPATH)/src/zero && docker build -t registry.cn-hangzhou.aliyuncs.com/xapp/shedding:$(version) . -f example/load/simulate/cpu/Dockerfile
|
||||
|
||||
push: build
|
||||
docker push registry.cn-hangzhou.aliyuncs.com/xapp/shedding:$(version)
|
||||
|
||||
deploy: push
|
||||
kubectl apply -f shedding.yaml
|
||||
|
||||
clean:
|
||||
kubectl delete -f shedding.yaml
|
||||
@@ -1,17 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: shedding
|
||||
namespace: adhoc
|
||||
spec:
|
||||
containers:
|
||||
- name: shedding
|
||||
image: registry-vpc.cn-hangzhou.aliyuncs.com/xapp/shedding:v1
|
||||
imagePullPolicy: Always
|
||||
resources:
|
||||
requests:
|
||||
cpu: 1000m
|
||||
limits:
|
||||
cpu: 1000m
|
||||
imagePullSecrets:
|
||||
- name: aliyun
|
||||
@@ -1,10 +0,0 @@
|
||||
version := v$(shell /bin/date "+%y%m%d%H%M%S")
|
||||
|
||||
build:
|
||||
cd $(GOPATH)/src/zero && docker build -t registry.cn-hangzhou.aliyuncs.com/xapp/unarydirect:$(version) . -f example/rpc/client/direct/Dockerfile
|
||||
|
||||
push: build
|
||||
docker push registry.cn-hangzhou.aliyuncs.com/xapp/unarydirect:$(version)
|
||||
|
||||
deploy: push
|
||||
kubectl -n adhoc set image deployment/unarydirect-deployment unarydirect=registry-vpc.cn-hangzhou.aliyuncs.com/xapp/unarydirect:$(version)
|
||||
@@ -1,23 +0,0 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: unarydirect-deployment
|
||||
namespace: adhoc
|
||||
labels:
|
||||
app: unarydirect
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: unarydirect
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: unarydirect
|
||||
spec:
|
||||
containers:
|
||||
- name: unarydirect
|
||||
image: registry-vpc.cn-hangzhou.aliyuncs.com/xapp/unarydirect:v1
|
||||
imagePullPolicy: Always
|
||||
imagePullSecrets:
|
||||
- name: aliyun
|
||||
@@ -42,7 +42,7 @@ func main() {
|
||||
ListenOn: *listen,
|
||||
}, func(grpcServer *grpc.Server) {
|
||||
unary.RegisterGreeterServer(grpcServer, &GreetServer{
|
||||
RpcProxy: rpcx.NewRpcProxy(*server),
|
||||
RpcProxy: rpcx.NewProxy(*server),
|
||||
})
|
||||
})
|
||||
proxy.Start()
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: unaryproxy
|
||||
namespace: kevin
|
||||
spec:
|
||||
selector:
|
||||
app: unaryproxy
|
||||
ports:
|
||||
- name: unaryproxy-port
|
||||
port: 3456
|
||||
|
||||
---
|
||||
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: unaryproxy-deployment
|
||||
namespace: kevin
|
||||
labels:
|
||||
app: unaryproxy
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: unaryproxy
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: unaryproxy
|
||||
spec:
|
||||
containers:
|
||||
- name: unaryproxy
|
||||
image: registry-vpc.cn-hangzhou.aliyuncs.com/xapp/unaryproxy:v1
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 3456
|
||||
volumeMounts:
|
||||
- name: timezone
|
||||
mountPath: /etc/localtime
|
||||
imagePullSecrets:
|
||||
- name: aliyun
|
||||
volumes:
|
||||
- name: timezone
|
||||
hostPath:
|
||||
path: /usr/share/zoneinfo/Asia/Shanghai
|
||||
@@ -1,11 +0,0 @@
|
||||
version := v1
|
||||
|
||||
build:
|
||||
cd $(GOPATH)/src/zero && docker build -t registry.cn-hangzhou.aliyuncs.com/xapp/unaryserver:$(version) . -f example/rpc/server/unary/Dockerfile
|
||||
docker image prune --filter label=stage=gobuilder -f
|
||||
|
||||
push: build
|
||||
docker push registry.cn-hangzhou.aliyuncs.com/xapp/unaryserver:$(version)
|
||||
|
||||
deploy: push
|
||||
kubectl -n adhoc set image deployment/unaryserver-deployment unaryserver=registry-vpc.cn-hangzhou.aliyuncs.com/xapp/unaryserver:$(version)
|
||||
@@ -1,25 +0,0 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: unaryserver-deployment
|
||||
namespace: adhoc
|
||||
labels:
|
||||
app: unaryserver
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: unaryserver
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: unaryserver
|
||||
spec:
|
||||
containers:
|
||||
- name: unaryserver
|
||||
image: registry-vpc.cn-hangzhou.aliyuncs.com/xapp/unaryserver:v1
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 3456
|
||||
imagePullSecrets:
|
||||
- name: aliyun
|
||||
3
go.mod
3
go.mod
@@ -8,6 +8,7 @@ require (
|
||||
github.com/alicebob/miniredis v2.5.0+incompatible
|
||||
github.com/dchest/siphash v1.2.1
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/dsymonds/gotoc v0.0.0-20160928043926-5aebcfc91819
|
||||
github.com/fatih/color v1.9.0 // indirect
|
||||
github.com/frankban/quicktest v1.7.2 // indirect
|
||||
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8
|
||||
@@ -55,7 +56,7 @@ require (
|
||||
golang.org/x/tools v0.0.0-20200410132612-ae9902aceb98 // indirect
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f // indirect
|
||||
google.golang.org/grpc v1.29.1
|
||||
google.golang.org/protobuf v1.25.0 // indirect
|
||||
google.golang.org/protobuf v1.25.0
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.28
|
||||
gopkg.in/yaml.v2 v2.2.8
|
||||
honnef.co/go/tools v0.0.1-2020.1.4 // indirect
|
||||
|
||||
2
go.sum
2
go.sum
@@ -48,6 +48,8 @@ github.com/dchest/siphash v1.2.1 h1:4cLinnzVJDKxTCl9B01807Yiy+W7ZzVHj/KIroQRvT4=
|
||||
github.com/dchest/siphash v1.2.1/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dsymonds/gotoc v0.0.0-20160928043926-5aebcfc91819 h1:9778zj477h/VauD8kHbOtbytW2KGQefJ/wUGE5w+mzw=
|
||||
github.com/dsymonds/gotoc v0.0.0-20160928043926-5aebcfc91819/go.mod h1:MvzMVHq8BH2Ji/o8TGDocVA70byvLrAgFTxkEnmjO4Y=
|
||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4 h1:qk/FSDDxo05wdJH28W+p5yivv7LuLYLRXPPD8KQCtZs=
|
||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
|
||||
29
readme.md
29
readme.md
@@ -89,13 +89,17 @@ go get -u github.com/tal-tech/go-zero
|
||||
|
||||
## 6. Quick Start
|
||||
|
||||
1. 编译goctl工具
|
||||
0. 完整示例请查看
|
||||
|
||||
[快速构建高并发微服务](doc/shorturl.md)
|
||||
|
||||
1. 安装goctl工具
|
||||
|
||||
```shell
|
||||
go build tools/goctl/goctl.go
|
||||
export GO111MODULE=on export GOPROXY=https://goproxy.cn/,direct go get github.com/tal-tech/go-zero/tools/goctl
|
||||
```
|
||||
|
||||
把goctl放到$PATH的目录下,确保goctl可执行
|
||||
确保goctl可执行
|
||||
|
||||
2. 定义API文件,比如greet.api,可以在vs code里安装`goctl`插件,支持api语法
|
||||
|
||||
@@ -133,7 +137,7 @@ go get -u github.com/tal-tech/go-zero
|
||||
```
|
||||
├── greet
|
||||
│ ├── etc
|
||||
│ │ └── greet-api.json // 配置文件
|
||||
│ │ └── greet-api.yaml // 配置文件
|
||||
│ ├── greet.go // main文件
|
||||
│ └── internal
|
||||
│ ├── config
|
||||
@@ -150,18 +154,24 @@ go get -u github.com/tal-tech/go-zero
|
||||
└── greet.api // api描述文件
|
||||
```
|
||||
生成的代码可以直接运行:
|
||||
|
||||
|
||||
```shell
|
||||
cd greet
|
||||
go run greet.go -f etc/greet-api.json
|
||||
go run greet.go -f etc/greet-api.yaml
|
||||
```
|
||||
|
||||
默认侦听在8888端口(可以在配置文件里修改),可以通过curl请求:
|
||||
|
||||
```shell
|
||||
➜ go-zero git:(master) curl -w "\ncode: %{http_code}\n" http://localhost:8888/greet/from/kevin
|
||||
{"code":0}
|
||||
code: 200
|
||||
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
|
||||
```
|
||||
|
||||
编写业务代码:
|
||||
@@ -185,6 +195,7 @@ go get -u github.com/tal-tech/go-zero
|
||||
|
||||
## 8. 文档 (逐步完善中)
|
||||
|
||||
* [快速构建高并发微服务](doc/shorturl.md)
|
||||
* [goctl使用帮助](doc/goctl.md)
|
||||
* [关键字替换和敏感词过滤工具](doc/keywords.md)
|
||||
|
||||
|
||||
101
rpcx/client_test.go
Normal file
101
rpcx/client_test.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package rpcx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tal-tech/go-zero/core/logx"
|
||||
"github.com/tal-tech/go-zero/rpcx/internal/mock"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/grpc/test/bufconn"
|
||||
)
|
||||
|
||||
func init() {
|
||||
logx.Disable()
|
||||
}
|
||||
|
||||
func dialer() func(context.Context, string) (net.Conn, error) {
|
||||
listener := bufconn.Listen(1024 * 1024)
|
||||
server := grpc.NewServer()
|
||||
mock.RegisterDepositServiceServer(server, &mock.DepositServer{})
|
||||
|
||||
go func() {
|
||||
if err := server.Serve(listener); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
return func(context.Context, string) (net.Conn, error) {
|
||||
return listener.Dial()
|
||||
}
|
||||
}
|
||||
|
||||
func TestDepositServer_Deposit(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
amount float32
|
||||
res *mock.DepositResponse
|
||||
errCode codes.Code
|
||||
errMsg string
|
||||
}{
|
||||
{
|
||||
"invalid request with negative amount",
|
||||
-1.11,
|
||||
nil,
|
||||
codes.InvalidArgument,
|
||||
fmt.Sprintf("cannot deposit %v", -1.11),
|
||||
},
|
||||
{
|
||||
"valid request with non negative amount",
|
||||
0.00,
|
||||
&mock.DepositResponse{Ok: true},
|
||||
codes.OK,
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
directClient := MustNewClient(RpcClientConf{
|
||||
Endpoints: []string{"foo"},
|
||||
App: "foo",
|
||||
Token: "bar",
|
||||
Timeout: 1000,
|
||||
}, WithDialOption(grpc.WithInsecure()), WithDialOption(grpc.WithContextDialer(dialer())))
|
||||
targetClient, err := NewClientWithTarget("foo", WithDialOption(grpc.WithInsecure()),
|
||||
WithDialOption(grpc.WithContextDialer(dialer())))
|
||||
assert.Nil(t, err)
|
||||
clients := []Client{
|
||||
directClient,
|
||||
targetClient,
|
||||
}
|
||||
for _, tt := range tests {
|
||||
for _, client := range clients {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cli := mock.NewDepositServiceClient(client.Conn())
|
||||
request := &mock.DepositRequest{Amount: tt.amount}
|
||||
response, err := cli.Deposit(context.Background(), request)
|
||||
if response != nil {
|
||||
assert.True(t, len(response.String()) > 0)
|
||||
if response.GetOk() != tt.res.GetOk() {
|
||||
t.Error("response: expected", tt.res.GetOk(), "received", response.GetOk())
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
if e, ok := status.FromError(err); ok {
|
||||
if e.Code() != tt.errCode {
|
||||
t.Error("error code: expected", codes.InvalidArgument, "received", e.Code())
|
||||
}
|
||||
if e.Message() != tt.errMsg {
|
||||
t.Error("error message: expected", tt.errMsg, "received", e.Message())
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
42
rpcx/config_test.go
Normal file
42
rpcx/config_test.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package rpcx
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tal-tech/go-zero/core/discov"
|
||||
"github.com/tal-tech/go-zero/core/service"
|
||||
"github.com/tal-tech/go-zero/core/stores/redis"
|
||||
)
|
||||
|
||||
func TestRpcClientConf(t *testing.T) {
|
||||
conf := NewDirectClientConf([]string{"localhost:1234"}, "foo", "bar")
|
||||
assert.True(t, conf.HasCredential())
|
||||
conf = NewEtcdClientConf([]string{"localhost:1234", "localhost:5678"}, "key", "foo", "bar")
|
||||
assert.True(t, conf.HasCredential())
|
||||
}
|
||||
|
||||
func TestRpcServerConf(t *testing.T) {
|
||||
conf := RpcServerConf{
|
||||
ServiceConf: service.ServiceConf{},
|
||||
ListenOn: "",
|
||||
Etcd: discov.EtcdConf{
|
||||
Hosts: []string{"localhost:1234"},
|
||||
Key: "key",
|
||||
},
|
||||
Auth: true,
|
||||
Redis: redis.RedisKeyConf{
|
||||
RedisConf: redis.RedisConf{
|
||||
Type: redis.NodeType,
|
||||
},
|
||||
Key: "foo",
|
||||
},
|
||||
StrictControl: false,
|
||||
Timeout: 0,
|
||||
CpuThreshold: 0,
|
||||
}
|
||||
assert.True(t, conf.HasEtcd())
|
||||
assert.NotNil(t, conf.Validate())
|
||||
conf.Redis.Host = "localhost:5678"
|
||||
assert.Nil(t, conf.Validate())
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -20,28 +19,105 @@ func TestWithUnaryClientInterceptors(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestChainStreamClientInterceptors_zero(t *testing.T) {
|
||||
var vals []int
|
||||
interceptors := chainStreamClientInterceptors()
|
||||
_, err := interceptors(context.Background(), nil, new(grpc.ClientConn), "/foo",
|
||||
func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string,
|
||||
opts ...grpc.CallOption) (grpc.ClientStream, error) {
|
||||
vals = append(vals, 1)
|
||||
return nil, nil
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
assert.ElementsMatch(t, []int{1}, vals)
|
||||
}
|
||||
|
||||
func TestChainStreamClientInterceptors_one(t *testing.T) {
|
||||
var called int32
|
||||
var vals []int
|
||||
interceptors := chainStreamClientInterceptors(func(ctx context.Context, desc *grpc.StreamDesc,
|
||||
cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (
|
||||
grpc.ClientStream, error) {
|
||||
atomic.AddInt32(&called, 1)
|
||||
return nil, nil
|
||||
vals = append(vals, 1)
|
||||
return streamer(ctx, desc, cc, method, opts...)
|
||||
})
|
||||
_, err := interceptors(context.Background(), nil, new(grpc.ClientConn), "/foo",
|
||||
func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string,
|
||||
opts ...grpc.CallOption) (grpc.ClientStream, error) {
|
||||
vals = append(vals, 2)
|
||||
return nil, nil
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, int32(1), atomic.LoadInt32(&called))
|
||||
assert.ElementsMatch(t, []int{1, 2}, vals)
|
||||
}
|
||||
|
||||
func TestChainStreamClientInterceptors_more(t *testing.T) {
|
||||
var vals []int
|
||||
interceptors := chainStreamClientInterceptors(func(ctx context.Context, desc *grpc.StreamDesc,
|
||||
cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (
|
||||
grpc.ClientStream, error) {
|
||||
vals = append(vals, 1)
|
||||
return streamer(ctx, desc, cc, method, opts...)
|
||||
}, func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string,
|
||||
streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
|
||||
vals = append(vals, 2)
|
||||
return streamer(ctx, desc, cc, method, opts...)
|
||||
})
|
||||
_, err := interceptors(context.Background(), nil, new(grpc.ClientConn), "/foo",
|
||||
func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string,
|
||||
opts ...grpc.CallOption) (grpc.ClientStream, error) {
|
||||
vals = append(vals, 3)
|
||||
return nil, nil
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
assert.ElementsMatch(t, []int{1, 2, 3}, vals)
|
||||
}
|
||||
|
||||
func TestWithUnaryClientInterceptors_zero(t *testing.T) {
|
||||
var vals []int
|
||||
interceptors := chainUnaryClientInterceptors()
|
||||
err := interceptors(context.Background(), "/foo", nil, nil, new(grpc.ClientConn),
|
||||
func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn,
|
||||
opts ...grpc.CallOption) error {
|
||||
vals = append(vals, 1)
|
||||
return nil
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
assert.ElementsMatch(t, []int{1}, vals)
|
||||
}
|
||||
|
||||
func TestWithUnaryClientInterceptors_one(t *testing.T) {
|
||||
var vals []int
|
||||
interceptors := chainUnaryClientInterceptors(func(ctx context.Context, method string, req,
|
||||
reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
|
||||
vals = append(vals, 1)
|
||||
return invoker(ctx, method, req, reply, cc, opts...)
|
||||
})
|
||||
err := interceptors(context.Background(), "/foo", nil, nil, new(grpc.ClientConn),
|
||||
func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn,
|
||||
opts ...grpc.CallOption) error {
|
||||
vals = append(vals, 2)
|
||||
return nil
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
assert.ElementsMatch(t, []int{1, 2}, vals)
|
||||
}
|
||||
|
||||
func TestWithUnaryClientInterceptors_more(t *testing.T) {
|
||||
var vals []int
|
||||
interceptors := chainUnaryClientInterceptors(func(ctx context.Context, method string, req,
|
||||
reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
|
||||
vals = append(vals, 1)
|
||||
return invoker(ctx, method, req, reply, cc, opts...)
|
||||
}, func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn,
|
||||
invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
|
||||
vals = append(vals, 2)
|
||||
return invoker(ctx, method, req, reply, cc, opts...)
|
||||
})
|
||||
err := interceptors(context.Background(), "/foo", nil, nil, new(grpc.ClientConn),
|
||||
func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn,
|
||||
opts ...grpc.CallOption) error {
|
||||
vals = append(vals, 3)
|
||||
return nil
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
assert.ElementsMatch(t, []int{1, 2, 3}, vals)
|
||||
}
|
||||
|
||||
111
rpcx/internal/chainserverinterceptors_test.go
Normal file
111
rpcx/internal/chainserverinterceptors_test.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func TestWithStreamServerInterceptors(t *testing.T) {
|
||||
opts := WithStreamServerInterceptors()
|
||||
assert.NotNil(t, opts)
|
||||
}
|
||||
|
||||
func TestWithUnaryServerInterceptors(t *testing.T) {
|
||||
opts := WithUnaryServerInterceptors()
|
||||
assert.NotNil(t, opts)
|
||||
}
|
||||
|
||||
func TestChainStreamServerInterceptors_zero(t *testing.T) {
|
||||
var vals []int
|
||||
interceptors := chainStreamServerInterceptors()
|
||||
err := interceptors(nil, nil, nil, func(srv interface{}, stream grpc.ServerStream) error {
|
||||
vals = append(vals, 1)
|
||||
return nil
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
assert.ElementsMatch(t, []int{1}, vals)
|
||||
}
|
||||
|
||||
func TestChainStreamServerInterceptors_one(t *testing.T) {
|
||||
var vals []int
|
||||
interceptors := chainStreamServerInterceptors(func(srv interface{}, ss grpc.ServerStream,
|
||||
info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
|
||||
vals = append(vals, 1)
|
||||
return handler(srv, ss)
|
||||
})
|
||||
err := interceptors(nil, nil, nil, func(srv interface{}, stream grpc.ServerStream) error {
|
||||
vals = append(vals, 2)
|
||||
return nil
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
assert.ElementsMatch(t, []int{1, 2}, vals)
|
||||
}
|
||||
|
||||
func TestChainStreamServerInterceptors_more(t *testing.T) {
|
||||
var vals []int
|
||||
interceptors := chainStreamServerInterceptors(func(srv interface{}, ss grpc.ServerStream,
|
||||
info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
|
||||
vals = append(vals, 1)
|
||||
return handler(srv, ss)
|
||||
}, func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
|
||||
vals = append(vals, 2)
|
||||
return handler(srv, ss)
|
||||
})
|
||||
err := interceptors(nil, nil, nil, func(srv interface{}, stream grpc.ServerStream) error {
|
||||
vals = append(vals, 3)
|
||||
return nil
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
assert.ElementsMatch(t, []int{1, 2, 3}, vals)
|
||||
}
|
||||
|
||||
func TestChainUnaryServerInterceptors_zero(t *testing.T) {
|
||||
var vals []int
|
||||
interceptors := chainUnaryServerInterceptors()
|
||||
_, err := interceptors(context.Background(), nil, nil,
|
||||
func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
vals = append(vals, 1)
|
||||
return nil, nil
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
assert.ElementsMatch(t, []int{1}, vals)
|
||||
}
|
||||
|
||||
func TestChainUnaryServerInterceptors_one(t *testing.T) {
|
||||
var vals []int
|
||||
interceptors := chainUnaryServerInterceptors(func(ctx context.Context, req interface{},
|
||||
info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
|
||||
vals = append(vals, 1)
|
||||
return handler(ctx, req)
|
||||
})
|
||||
_, err := interceptors(context.Background(), nil, nil,
|
||||
func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
vals = append(vals, 2)
|
||||
return nil, nil
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
assert.ElementsMatch(t, []int{1, 2}, vals)
|
||||
}
|
||||
|
||||
func TestChainUnaryServerInterceptors_more(t *testing.T) {
|
||||
var vals []int
|
||||
interceptors := chainUnaryServerInterceptors(func(ctx context.Context, req interface{},
|
||||
info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
|
||||
vals = append(vals, 1)
|
||||
return handler(ctx, req)
|
||||
}, func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo,
|
||||
handler grpc.UnaryHandler) (resp interface{}, err error) {
|
||||
vals = append(vals, 2)
|
||||
return handler(ctx, req)
|
||||
})
|
||||
_, err := interceptors(context.Background(), nil, nil,
|
||||
func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
vals = append(vals, 3)
|
||||
return nil, nil
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
assert.ElementsMatch(t, []int{1, 2, 3}, vals)
|
||||
}
|
||||
30
rpcx/internal/client_test.go
Normal file
30
rpcx/internal/client_test.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func TestWithDialOption(t *testing.T) {
|
||||
var options ClientOptions
|
||||
agent := grpc.WithUserAgent("chrome")
|
||||
opt := WithDialOption(agent)
|
||||
opt(&options)
|
||||
assert.Contains(t, options.DialOptions, agent)
|
||||
}
|
||||
|
||||
func TestWithTimeout(t *testing.T) {
|
||||
var options ClientOptions
|
||||
opt := WithTimeout(time.Second)
|
||||
opt(&options)
|
||||
assert.Equal(t, time.Second, options.Timeout)
|
||||
}
|
||||
|
||||
func TestBuildDialOptions(t *testing.T) {
|
||||
agent := grpc.WithUserAgent("chrome")
|
||||
opts := buildDialOptions(WithDialOption(agent))
|
||||
assert.Contains(t, opts, agent)
|
||||
}
|
||||
159
rpcx/internal/mock/deposit.pb.go
Normal file
159
rpcx/internal/mock/deposit.pb.go
Normal file
@@ -0,0 +1,159 @@
|
||||
// Code generated by protoc-gen-go.
|
||||
// source: deposit.proto
|
||||
// DO NOT EDIT!
|
||||
|
||||
/*
|
||||
Package mock is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
deposit.proto
|
||||
|
||||
It has these top-level messages:
|
||||
DepositRequest
|
||||
DepositResponse
|
||||
*/
|
||||
package mock
|
||||
|
||||
import proto "github.com/golang/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
|
||||
import (
|
||||
context "golang.org/x/net/context"
|
||||
grpc "google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
|
||||
|
||||
type DepositRequest struct {
|
||||
Amount float32 `protobuf:"fixed32,1,opt,name=amount" json:"amount,omitempty"`
|
||||
}
|
||||
|
||||
func (m *DepositRequest) Reset() { *m = DepositRequest{} }
|
||||
func (m *DepositRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*DepositRequest) ProtoMessage() {}
|
||||
func (*DepositRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
|
||||
|
||||
func (m *DepositRequest) GetAmount() float32 {
|
||||
if m != nil {
|
||||
return m.Amount
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type DepositResponse struct {
|
||||
Ok bool `protobuf:"varint,1,opt,name=ok" json:"ok,omitempty"`
|
||||
}
|
||||
|
||||
func (m *DepositResponse) Reset() { *m = DepositResponse{} }
|
||||
func (m *DepositResponse) String() string { return proto.CompactTextString(m) }
|
||||
func (*DepositResponse) ProtoMessage() {}
|
||||
func (*DepositResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
|
||||
|
||||
func (m *DepositResponse) GetOk() bool {
|
||||
if m != nil {
|
||||
return m.Ok
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*DepositRequest)(nil), "mock.DepositRequest")
|
||||
proto.RegisterType((*DepositResponse)(nil), "mock.DepositResponse")
|
||||
}
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ context.Context
|
||||
var _ grpc.ClientConn
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
const _ = grpc.SupportPackageIsVersion4
|
||||
|
||||
// Client API for DepositService service
|
||||
|
||||
type DepositServiceClient interface {
|
||||
Deposit(ctx context.Context, in *DepositRequest, opts ...grpc.CallOption) (*DepositResponse, error)
|
||||
}
|
||||
|
||||
type depositServiceClient struct {
|
||||
cc *grpc.ClientConn
|
||||
}
|
||||
|
||||
func NewDepositServiceClient(cc *grpc.ClientConn) DepositServiceClient {
|
||||
return &depositServiceClient{cc}
|
||||
}
|
||||
|
||||
func (c *depositServiceClient) Deposit(ctx context.Context, in *DepositRequest, opts ...grpc.CallOption) (*DepositResponse, error) {
|
||||
out := new(DepositResponse)
|
||||
err := grpc.Invoke(ctx, "/mock.DepositService/Deposit", in, out, c.cc, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Server API for DepositService service
|
||||
|
||||
type DepositServiceServer interface {
|
||||
Deposit(context.Context, *DepositRequest) (*DepositResponse, error)
|
||||
}
|
||||
|
||||
func RegisterDepositServiceServer(s *grpc.Server, srv DepositServiceServer) {
|
||||
s.RegisterService(&_DepositService_serviceDesc, srv)
|
||||
}
|
||||
|
||||
func _DepositService_Deposit_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(DepositRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(DepositServiceServer).Deposit(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/mock.DepositService/Deposit",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(DepositServiceServer).Deposit(ctx, req.(*DepositRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
var _DepositService_serviceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "mock.DepositService",
|
||||
HandlerType: (*DepositServiceServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "Deposit",
|
||||
Handler: _DepositService_Deposit_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "deposit.proto",
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("deposit.proto", fileDescriptor0) }
|
||||
|
||||
var fileDescriptor0 = []byte{
|
||||
// 139 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0x4d, 0x49, 0x2d, 0xc8,
|
||||
0x2f, 0xce, 0x2c, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0xc9, 0xcd, 0x4f, 0xce, 0x56,
|
||||
0xd2, 0xe0, 0xe2, 0x73, 0x81, 0x08, 0x07, 0xa5, 0x16, 0x96, 0xa6, 0x16, 0x97, 0x08, 0x89, 0x71,
|
||||
0xb1, 0x25, 0xe6, 0xe6, 0x97, 0xe6, 0x95, 0x48, 0x30, 0x2a, 0x30, 0x6a, 0x30, 0x05, 0x41, 0x79,
|
||||
0x4a, 0x8a, 0x5c, 0xfc, 0x70, 0x95, 0xc5, 0x05, 0xf9, 0x79, 0xc5, 0xa9, 0x42, 0x7c, 0x5c, 0x4c,
|
||||
0xf9, 0xd9, 0x60, 0x65, 0x1c, 0x41, 0x4c, 0xf9, 0xd9, 0x46, 0x1e, 0x70, 0xc3, 0x82, 0x53, 0x8b,
|
||||
0xca, 0x32, 0x93, 0x53, 0x85, 0xcc, 0xb8, 0xd8, 0xa1, 0x22, 0x42, 0x22, 0x7a, 0x20, 0x0b, 0xf5,
|
||||
0x50, 0x6d, 0x93, 0x12, 0x45, 0x13, 0x85, 0x98, 0x9c, 0xc4, 0x06, 0x76, 0xa3, 0x31, 0x20, 0x00,
|
||||
0x00, 0xff, 0xff, 0x62, 0x37, 0xf2, 0x36, 0xb4, 0x00, 0x00, 0x00,
|
||||
}
|
||||
15
rpcx/internal/mock/deposit.proto
Normal file
15
rpcx/internal/mock/deposit.proto
Normal file
@@ -0,0 +1,15 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package mock;
|
||||
|
||||
message DepositRequest {
|
||||
float amount = 1;
|
||||
}
|
||||
|
||||
message DepositResponse {
|
||||
bool ok = 1;
|
||||
}
|
||||
|
||||
service DepositService {
|
||||
rpc Deposit(DepositRequest) returns (DepositResponse);
|
||||
}
|
||||
19
rpcx/internal/mock/depositserver.go
Normal file
19
rpcx/internal/mock/depositserver.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package mock
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
type DepositServer struct {
|
||||
}
|
||||
|
||||
func (*DepositServer) Deposit(ctx context.Context, req *DepositRequest) (*DepositResponse, error) {
|
||||
if req.GetAmount() < 0 {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "cannot deposit %v", req.GetAmount())
|
||||
}
|
||||
|
||||
return &DepositResponse{Ok: true}, nil
|
||||
}
|
||||
16
rpcx/internal/rpcserver_test.go
Normal file
16
rpcx/internal/rpcserver_test.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tal-tech/go-zero/core/stat"
|
||||
)
|
||||
|
||||
func TestWithMetrics(t *testing.T) {
|
||||
metrics := stat.NewMetrics("foo")
|
||||
opt := WithMetrics(metrics)
|
||||
var options rpcServerOptions
|
||||
opt(&options)
|
||||
assert.Equal(t, metrics, options.metrics)
|
||||
}
|
||||
53
rpcx/internal/server_test.go
Normal file
53
rpcx/internal/server_test.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tal-tech/go-zero/core/stat"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func TestBaseRpcServer_AddOptions(t *testing.T) {
|
||||
metrics := stat.NewMetrics("foo")
|
||||
server := newBaseRpcServer("foo", metrics)
|
||||
server.SetName("bar")
|
||||
var opt grpc.EmptyServerOption
|
||||
server.AddOptions(opt)
|
||||
assert.Contains(t, server.options, opt)
|
||||
}
|
||||
|
||||
func TestBaseRpcServer_AddStreamInterceptors(t *testing.T) {
|
||||
metrics := stat.NewMetrics("foo")
|
||||
server := newBaseRpcServer("foo", metrics)
|
||||
server.SetName("bar")
|
||||
var vals []int
|
||||
f := func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
|
||||
vals = append(vals, 1)
|
||||
return nil
|
||||
}
|
||||
server.AddStreamInterceptors(f)
|
||||
for _, each := range server.streamInterceptors {
|
||||
assert.Nil(t, each(nil, nil, nil, nil))
|
||||
}
|
||||
assert.ElementsMatch(t, []int{1}, vals)
|
||||
}
|
||||
|
||||
func TestBaseRpcServer_AddUnaryInterceptors(t *testing.T) {
|
||||
metrics := stat.NewMetrics("foo")
|
||||
server := newBaseRpcServer("foo", metrics)
|
||||
server.SetName("bar")
|
||||
var vals []int
|
||||
f := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (
|
||||
resp interface{}, err error) {
|
||||
vals = append(vals, 1)
|
||||
return nil, nil
|
||||
}
|
||||
server.AddUnaryInterceptors(f)
|
||||
for _, each := range server.unaryInterceptors {
|
||||
_, err := each(context.Background(), nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
assert.ElementsMatch(t, []int{1}, vals)
|
||||
}
|
||||
@@ -18,7 +18,7 @@ type RpcProxy struct {
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
func NewRpcProxy(backend string, opts ...internal.ClientOption) *RpcProxy {
|
||||
func NewProxy(backend string, opts ...internal.ClientOption) *RpcProxy {
|
||||
return &RpcProxy{
|
||||
backend: backend,
|
||||
clients: make(map[string]Client),
|
||||
@@ -56,5 +56,5 @@ func (p *RpcProxy) TakeConn(ctx context.Context) (*grpc.ClientConn, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return val.(*RpcClient).Conn(), nil
|
||||
return val.(Client).Conn(), nil
|
||||
}
|
||||
|
||||
66
rpcx/proxy_test.go
Normal file
66
rpcx/proxy_test.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package rpcx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tal-tech/go-zero/rpcx/internal/mock"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
func TestProxy(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
amount float32
|
||||
res *mock.DepositResponse
|
||||
errCode codes.Code
|
||||
errMsg string
|
||||
}{
|
||||
{
|
||||
"invalid request with negative amount",
|
||||
-1.11,
|
||||
nil,
|
||||
codes.InvalidArgument,
|
||||
fmt.Sprintf("cannot deposit %v", -1.11),
|
||||
},
|
||||
{
|
||||
"valid request with non negative amount",
|
||||
0.00,
|
||||
&mock.DepositResponse{Ok: true},
|
||||
codes.OK,
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
proxy := NewProxy("foo", WithDialOption(grpc.WithInsecure()),
|
||||
WithDialOption(grpc.WithContextDialer(dialer())))
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
conn, err := proxy.TakeConn(context.Background())
|
||||
assert.Nil(t, err)
|
||||
cli := mock.NewDepositServiceClient(conn)
|
||||
request := &mock.DepositRequest{Amount: tt.amount}
|
||||
response, err := cli.Deposit(context.Background(), request)
|
||||
if response != nil {
|
||||
assert.True(t, len(response.String()) > 0)
|
||||
if response.GetOk() != tt.res.GetOk() {
|
||||
t.Error("response: expected", tt.res.GetOk(), "received", response.GetOk())
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
if e, ok := status.FromError(err); ok {
|
||||
if e.Code() != tt.errCode {
|
||||
t.Error("error code: expected", codes.InvalidArgument, "received", e.Code())
|
||||
}
|
||||
if e.Message() != tt.errMsg {
|
||||
t.Error("error message: expected", tt.errMsg, "received", e.Message())
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -2,3 +2,9 @@ version := $(shell /bin/date "+%Y-%m-%d %H:%M")
|
||||
|
||||
build:
|
||||
go build -ldflags="-s -w" -ldflags="-X 'main.BuildTime=$(version)'" goctl.go && upx goctl
|
||||
mac:
|
||||
GOOS=darwin go build -ldflags="-s -w" -ldflags="-X 'main.BuildTime=$(version)'" -o goctl-darwin goctl.go && upx goctl-darwin
|
||||
win:
|
||||
GOOS=windows go build -ldflags="-s -w" -ldflags="-X 'main.BuildTime=$(version)'" -o goctl.exe goctl.go && upx goctl.exe
|
||||
linux:
|
||||
GOOS=linux go build -ldflags="-s -w" -ldflags="-X 'main.BuildTime=$(version)'" -o goctl-linux goctl.go && upx goctl-linux
|
||||
|
||||
@@ -98,6 +98,7 @@ func ApiFormat(path string, printToConsole bool) error {
|
||||
_, err := fmt.Print(result)
|
||||
return err
|
||||
}
|
||||
result = strings.TrimSpace(result)
|
||||
return ioutil.WriteFile(path, []byte(result), os.ModePerm)
|
||||
}
|
||||
|
||||
|
||||
@@ -140,35 +140,27 @@ func createGoModFileIfNeed(dir string) {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var tempPath = absDir
|
||||
var hasGoMod = false
|
||||
for {
|
||||
if tempPath == filepath.Dir(tempPath) {
|
||||
break
|
||||
}
|
||||
tempPath = filepath.Dir(tempPath)
|
||||
if util.FileExists(filepath.Join(tempPath, goModeIdentifier)) {
|
||||
hasGoMod = true
|
||||
break
|
||||
}
|
||||
_, hasGoMod := util.FindGoModPath(dir)
|
||||
if hasGoMod {
|
||||
return
|
||||
}
|
||||
if !hasGoMod {
|
||||
gopath := os.Getenv("GOPATH")
|
||||
parent := path.Join(gopath, "src")
|
||||
pos := strings.Index(absDir, parent)
|
||||
if pos < 0 {
|
||||
moduleName := absDir[len(filepath.Dir(absDir))+1:]
|
||||
cmd := exec.Command("go", "mod", "init", moduleName)
|
||||
cmd.Dir = dir
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
}
|
||||
outStr, errStr := string(stdout.Bytes()), string(stderr.Bytes())
|
||||
fmt.Printf(outStr + "\n" + errStr)
|
||||
}
|
||||
|
||||
gopath := os.Getenv("GOPATH")
|
||||
parent := path.Join(gopath, "src")
|
||||
pos := strings.Index(absDir, parent)
|
||||
if pos >= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
moduleName := absDir[len(filepath.Dir(absDir))+1:]
|
||||
cmd := exec.Command("go", "mod", "init", moduleName)
|
||||
cmd.Dir = dir
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
if err = cmd.Run(); err != nil {
|
||||
fmt.Println(err.Error())
|
||||
}
|
||||
outStr, errStr := string(stdout.Bytes()), string(stderr.Bytes())
|
||||
fmt.Printf(outStr + "\n" + errStr)
|
||||
}
|
||||
|
||||
@@ -13,9 +13,7 @@ const (
|
||||
configFile = "config.go"
|
||||
configTemplate = `package config
|
||||
|
||||
import (
|
||||
{{.authImport}}
|
||||
)
|
||||
import {{.authImport}}
|
||||
|
||||
type Config struct {
|
||||
rest.RestConf
|
||||
|
||||
@@ -13,15 +13,14 @@ import (
|
||||
const (
|
||||
defaultPort = 8888
|
||||
etcDir = "etc"
|
||||
etcTemplate = `{
|
||||
"Name": "{{.serviceName}}",
|
||||
"Host": "{{.host}}",
|
||||
"Port": {{.port}}
|
||||
}`
|
||||
etcTemplate = `Name: {{.serviceName}}
|
||||
Host: {{.host}}
|
||||
Port: {{.port}}
|
||||
`
|
||||
)
|
||||
|
||||
func genEtc(dir string, api *spec.ApiSpec) error {
|
||||
fp, created, err := util.MaybeCreateFile(dir, etcDir, fmt.Sprintf("%s.json", api.Service.Name))
|
||||
fp, created, err := util.MaybeCreateFile(dir, etcDir, fmt.Sprintf("%s.yaml", api.Service.Name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
@@ -25,7 +24,6 @@ import (
|
||||
|
||||
func {{.handlerName}}(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
l := logic.{{.logic}}(r.Context(), ctx)
|
||||
{{.handlerBody}}
|
||||
}
|
||||
}
|
||||
@@ -40,6 +38,7 @@ func {{.handlerName}}(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
}
|
||||
`
|
||||
hasRespTemplate = `
|
||||
l := logic.{{.logic}}(r.Context(), ctx)
|
||||
{{.logicResponse}} l.{{.callee}}({{.req}})
|
||||
if err != nil {
|
||||
httpx.Error(w, err)
|
||||
@@ -85,6 +84,7 @@ func genHandler(dir string, group spec.Group, route spec.Route) error {
|
||||
var logicBodyBuilder strings.Builder
|
||||
t := template.Must(template.New("hasRespTemplate").Parse(hasRespTemplate))
|
||||
if err := t.Execute(&logicBodyBuilder, map[string]string{
|
||||
"logic": "New" + strings.TrimSuffix(strings.Title(handler), "Handler") + "Logic",
|
||||
"callee": strings.Title(strings.TrimSuffix(handler, "Handler")),
|
||||
"req": req,
|
||||
"logicResponse": logicResponse,
|
||||
@@ -135,7 +135,6 @@ func doGenToFile(dir, handler string, group spec.Group, route spec.Route, bodyBu
|
||||
t := template.Must(template.New("handlerTemplate").Parse(handlerTemplate))
|
||||
buffer := new(bytes.Buffer)
|
||||
err = t.Execute(buffer, map[string]string{
|
||||
"logic": "New" + strings.TrimSuffix(strings.Title(handler), "Handler") + "Logic",
|
||||
"importPackages": genHandlerImports(group, route, parentPkg),
|
||||
"handlerName": handler,
|
||||
"handlerBody": strings.TrimSpace(bodyBuilder.String()),
|
||||
@@ -162,14 +161,13 @@ func genHandlers(dir string, api *spec.ApiSpec) error {
|
||||
|
||||
func genHandlerImports(group spec.Group, route spec.Route, parentPkg string) string {
|
||||
var imports []string
|
||||
imports = append(imports, fmt.Sprintf("\"%s/rest/httpx\"", vars.ProjectOpenSourceUrl))
|
||||
imports = append(imports, fmt.Sprintf("\"%s\"", util.JoinPackages(parentPkg, contextDir)))
|
||||
if len(route.RequestType.Name) > 0 || len(route.ResponseType.Name) > 0 {
|
||||
imports = append(imports, fmt.Sprintf("\"%s\"", util.JoinPackages(parentPkg, typesDir)))
|
||||
}
|
||||
imports = append(imports, fmt.Sprintf("\"%s\"",
|
||||
util.JoinPackages(parentPkg, getLogicFolderPath(group, route))))
|
||||
sort.Strings(imports)
|
||||
imports = append(imports, fmt.Sprintf("\"%s\"", util.JoinPackages(parentPkg, contextDir)))
|
||||
if len(route.RequestType.Name) > 0 || len(route.ResponseType.Name) > 0 {
|
||||
imports = append(imports, fmt.Sprintf("\"%s\"\n", util.JoinPackages(parentPkg, typesDir)))
|
||||
}
|
||||
imports = append(imports, fmt.Sprintf("\"%s/rest/httpx\"", vars.ProjectOpenSourceUrl))
|
||||
|
||||
return strings.Join(imports, "\n\t")
|
||||
}
|
||||
|
||||
@@ -20,19 +20,22 @@ import (
|
||||
)
|
||||
|
||||
type {{.logic}} struct {
|
||||
ctx context.Context
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
logx.Logger
|
||||
}
|
||||
|
||||
func New{{.logic}}(ctx context.Context, svcCtx *svc.ServiceContext) {{.logic}} {
|
||||
return {{.logic}}{
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
Logger: logx.WithContext(ctx),
|
||||
}
|
||||
// TODO need set model here from svc
|
||||
}
|
||||
|
||||
func (l *{{.logic}}) {{.function}}({{.request}}) {{.responseType}} {
|
||||
// todo: add your logic here and delete this line
|
||||
|
||||
{{.returnString}}
|
||||
}
|
||||
`
|
||||
@@ -77,8 +80,9 @@ func genLogicByRoute(dir string, group spec.Group, route spec.Route) error {
|
||||
returnString := ""
|
||||
requestString := ""
|
||||
if len(route.ResponseType.Name) > 0 {
|
||||
responseString = "(*types." + strings.Title(route.ResponseType.Name) + ", error)"
|
||||
returnString = "return nil, nil"
|
||||
resp := strings.Title(route.ResponseType.Name)
|
||||
responseString = "(*types." + resp + ", error)"
|
||||
returnString = fmt.Sprintf("return &types.%s{}, nil", resp)
|
||||
} else {
|
||||
responseString = "error"
|
||||
returnString = "return nil"
|
||||
@@ -98,7 +102,7 @@ func genLogicByRoute(dir string, group spec.Group, route spec.Route) error {
|
||||
"request": requestString,
|
||||
})
|
||||
if err != nil {
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
formatCode := formatCode(buffer.String())
|
||||
_, err = fp.WriteString(formatCode)
|
||||
@@ -120,12 +124,11 @@ func getLogicFolderPath(group spec.Group, route spec.Route) string {
|
||||
|
||||
func genLogicImports(route spec.Route, parentPkg string) string {
|
||||
var imports []string
|
||||
imports = append(imports, `"context"`)
|
||||
imports = append(imports, "\n")
|
||||
imports = append(imports, fmt.Sprintf("\"%s/core/logx\"", vars.ProjectOpenSourceUrl))
|
||||
if len(route.ResponseType.Name) > 0 || len(route.RequestType.Name) > 0 {
|
||||
imports = append(imports, fmt.Sprintf("\"%s\"", ctlutil.JoinPackages(parentPkg, typesDir)))
|
||||
}
|
||||
imports = append(imports, `"context"`+"\n")
|
||||
imports = append(imports, fmt.Sprintf("\"%s\"", ctlutil.JoinPackages(parentPkg, contextDir)))
|
||||
if len(route.ResponseType.Name) > 0 || len(route.RequestType.Name) > 0 {
|
||||
imports = append(imports, fmt.Sprintf("\"%s\"\n", ctlutil.JoinPackages(parentPkg, typesDir)))
|
||||
}
|
||||
imports = append(imports, fmt.Sprintf("\"%s/core/logx\"", vars.ProjectOpenSourceUrl))
|
||||
return strings.Join(imports, "\n\t")
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package gogen
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
@@ -21,7 +20,7 @@ import (
|
||||
{{.importPackages}}
|
||||
)
|
||||
|
||||
var configFile = flag.String("f", "etc/{{.serviceName}}.json", "the config file")
|
||||
var configFile = flag.String("f", "etc/{{.serviceName}}.yaml", "the config file")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
@@ -73,13 +72,11 @@ func genMain(dir string, api *spec.ApiSpec) error {
|
||||
}
|
||||
|
||||
func genMainImports(parentPkg string) string {
|
||||
imports := []string{
|
||||
fmt.Sprintf("\"%s/core/conf\"", vars.ProjectOpenSourceUrl),
|
||||
fmt.Sprintf("\"%s/rest\"", vars.ProjectOpenSourceUrl),
|
||||
}
|
||||
var imports []string
|
||||
imports = append(imports, fmt.Sprintf("\"%s\"", ctlutil.JoinPackages(parentPkg, configDir)))
|
||||
imports = append(imports, fmt.Sprintf("\"%s\"", ctlutil.JoinPackages(parentPkg, handlerDir)))
|
||||
imports = append(imports, fmt.Sprintf("\"%s\"", ctlutil.JoinPackages(parentPkg, contextDir)))
|
||||
sort.Strings(imports)
|
||||
imports = append(imports, fmt.Sprintf("\"%s\"\n", ctlutil.JoinPackages(parentPkg, contextDir)))
|
||||
imports = append(imports, fmt.Sprintf("\"%s/core/conf\"", vars.ProjectOpenSourceUrl))
|
||||
imports = append(imports, fmt.Sprintf("\"%s/rest\"", vars.ProjectOpenSourceUrl))
|
||||
return strings.Join(imports, "\n\t")
|
||||
}
|
||||
|
||||
@@ -131,7 +131,6 @@ func genRoutes(dir string, api *spec.ApiSpec) error {
|
||||
|
||||
func genRouteImports(parentPkg string, api *spec.ApiSpec) string {
|
||||
var importSet = collection.NewSet()
|
||||
importSet.AddStr(fmt.Sprintf("\"%s/rest\"", vars.ProjectOpenSourceUrl))
|
||||
importSet.AddStr(fmt.Sprintf("\"%s\"", util.JoinPackages(parentPkg, contextDir)))
|
||||
for _, group := range api.Service.Groups {
|
||||
for _, route := range group.Routes {
|
||||
@@ -148,7 +147,9 @@ func genRouteImports(parentPkg string, api *spec.ApiSpec) string {
|
||||
}
|
||||
imports := importSet.KeysStr()
|
||||
sort.Strings(imports)
|
||||
return strings.Join(imports, "\n\t")
|
||||
projectSection := strings.Join(imports, "\n\t")
|
||||
depSection := fmt.Sprintf("\"%s/rest\"", vars.ProjectOpenSourceUrl)
|
||||
return fmt.Sprintf("%s\n\n\t%s", projectSection, depSection)
|
||||
}
|
||||
|
||||
func getRoutes(api *spec.ApiSpec) ([]group, error) {
|
||||
|
||||
@@ -20,10 +20,9 @@ type ServiceContext struct {
|
||||
Config {{.config}}
|
||||
}
|
||||
|
||||
func NewServiceContext(config {{.config}}) *ServiceContext {
|
||||
return &ServiceContext{Config: config}
|
||||
func NewServiceContext(c {{.config}}) *ServiceContext {
|
||||
return &ServiceContext{Config: c}
|
||||
}
|
||||
|
||||
`
|
||||
)
|
||||
|
||||
@@ -15,8 +15,6 @@ import (
|
||||
goctlutil "github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
)
|
||||
|
||||
const goModeIdentifier = "go.mod"
|
||||
|
||||
func getParentPackage(dir string) (string, error) {
|
||||
absDir, err := filepath.Abs(dir)
|
||||
if err != nil {
|
||||
@@ -24,36 +22,22 @@ func getParentPackage(dir string) (string, error) {
|
||||
}
|
||||
|
||||
absDir = strings.ReplaceAll(absDir, `\`, `/`)
|
||||
var rootPath string
|
||||
var tempPath = absDir
|
||||
var hasGoMod = false
|
||||
for {
|
||||
if tempPath == filepath.Dir(tempPath) {
|
||||
break
|
||||
}
|
||||
tempPath = filepath.Dir(tempPath)
|
||||
if goctlutil.FileExists(filepath.Join(tempPath, goModeIdentifier)) {
|
||||
tempPath = filepath.Dir(tempPath)
|
||||
rootPath = absDir[len(tempPath)+1:]
|
||||
hasGoMod = true
|
||||
break
|
||||
}
|
||||
if tempPath == string(filepath.Separator) {
|
||||
break
|
||||
}
|
||||
rootPath, hasGoMod := goctlutil.FindGoModPath(dir)
|
||||
if hasGoMod {
|
||||
return rootPath, nil
|
||||
}
|
||||
if !hasGoMod {
|
||||
gopath := os.Getenv("GOPATH")
|
||||
parent := path.Join(gopath, "src")
|
||||
pos := strings.Index(absDir, parent)
|
||||
if pos < 0 {
|
||||
fmt.Printf("%s not in gomod project path, or not in GOPATH of %s directory\n", absDir, gopath)
|
||||
tempPath = filepath.Dir(absDir)
|
||||
rootPath = absDir[len(tempPath)+1:]
|
||||
} else {
|
||||
rootPath = absDir[len(parent)+1:]
|
||||
}
|
||||
|
||||
gopath := os.Getenv("GOPATH")
|
||||
parent := path.Join(gopath, "src")
|
||||
pos := strings.Index(absDir, parent)
|
||||
if pos < 0 {
|
||||
fmt.Printf("%s not in go.mod project path, or not in GOPATH of %s directory\n", absDir, gopath)
|
||||
tempPath := filepath.Dir(absDir)
|
||||
rootPath = absDir[len(tempPath)+1:]
|
||||
} else {
|
||||
rootPath = absDir[len(parent)+1:]
|
||||
}
|
||||
|
||||
return rootPath, nil
|
||||
}
|
||||
|
||||
@@ -77,7 +61,7 @@ func writeProperty(writer io.Writer, name, tp, tag, comment string, indent int)
|
||||
}
|
||||
|
||||
func getAuths(api *spec.ApiSpec) []string {
|
||||
var authNames = collection.NewSet()
|
||||
authNames := collection.NewSet()
|
||||
for _, g := range api.Service.Groups {
|
||||
if value, ok := util.GetAnnotationValue(g.Annotations, "server", "jwt"); ok {
|
||||
authNames.Add(value)
|
||||
@@ -94,5 +78,6 @@ func formatCode(code string) string {
|
||||
if err != nil {
|
||||
return code
|
||||
}
|
||||
|
||||
return string(ret)
|
||||
}
|
||||
|
||||
@@ -9,17 +9,14 @@ import (
|
||||
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
|
||||
)
|
||||
|
||||
const (
|
||||
// struct匹配
|
||||
typeRegex = `(?m)(?m)(^ *type\s+[a-zA-Z][a-zA-Z0-9_-]+\s+(((struct)\s*?\{[\w\W]*?[^\{]\})|([a-zA-Z][a-zA-Z0-9_-]+)))|(^ *type\s*?\([\w\W]+\}\s*\))`
|
||||
)
|
||||
// struct匹配
|
||||
const typeRegex = `(?m)(?m)(^ *type\s+[a-zA-Z][a-zA-Z0-9_-]+\s+(((struct)\s*?\{[\w\W]*?[^\{]\})|([a-zA-Z][a-zA-Z0-9_-]+)))|(^ *type\s*?\([\w\W]+\}\s*\))`
|
||||
|
||||
var (
|
||||
emptyStrcut = errors.New("struct body not found")
|
||||
emptyType spec.Type
|
||||
)
|
||||
|
||||
var emptyType spec.Type
|
||||
|
||||
func GetType(api *spec.ApiSpec, t string) spec.Type {
|
||||
for _, tp := range api.Types {
|
||||
if tp.Name == t {
|
||||
|
||||
@@ -10,26 +10,27 @@ import (
|
||||
"text/template"
|
||||
|
||||
"github.com/logrusorgru/aurora"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/vars"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
const configTemplate = `package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"{{.import}}"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var c config.Config
|
||||
template, err := json.MarshalIndent(c, "", " ")
|
||||
template, err := yaml.Marshal(c)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = ioutil.WriteFile("config.json", template, os.ModePerm)
|
||||
err = ioutil.WriteFile("config.yaml", template, os.ModePerm)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -41,9 +42,9 @@ func GenConfigCommand(c *cli.Context) error {
|
||||
if err != nil {
|
||||
return errors.New("abs failed: " + c.String("path"))
|
||||
}
|
||||
xi := strings.Index(path, vars.ProjectName)
|
||||
if xi <= 0 {
|
||||
return errors.New("path should the absolute path of config go file")
|
||||
goModPath, hasFound := util.FindGoModPath(path)
|
||||
if !hasFound {
|
||||
return errors.New("go mod not initial")
|
||||
}
|
||||
path = strings.TrimSuffix(path, "/config.go")
|
||||
location := path + "/tmp"
|
||||
@@ -62,16 +63,28 @@ func GenConfigCommand(c *cli.Context) error {
|
||||
|
||||
t := template.Must(template.New("template").Parse(configTemplate))
|
||||
if err := t.Execute(fp, map[string]string{
|
||||
"import": path[xi:],
|
||||
"import": filepath.Dir(goModPath),
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := exec.Command("go", "run", goPath)
|
||||
_, err = cmd.Output()
|
||||
gen := exec.Command("go", "run", "config.go")
|
||||
gen.Dir = filepath.Dir(goPath)
|
||||
gen.Stderr = os.Stderr
|
||||
gen.Stdout = os.Stdout
|
||||
err = gen.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
panic(err)
|
||||
}
|
||||
path, err = os.Getwd()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = os.Rename(filepath.Dir(goPath)+"/config.yaml", path+"/config.yaml")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(aurora.Green("Done."))
|
||||
return nil
|
||||
}
|
||||
@@ -9,14 +9,9 @@ import (
|
||||
|
||||
func DockerCommand(c *cli.Context) error {
|
||||
goFile := c.String("go")
|
||||
namespace := c.String("namespace")
|
||||
if len(goFile) == 0 || len(namespace) == 0 {
|
||||
return errors.New("-go and -namespace can't be empty")
|
||||
if len(goFile) == 0 {
|
||||
return errors.New("-go can't be empty")
|
||||
}
|
||||
|
||||
if err := gen.GenerateDockerfile(goFile, "-f", "etc/config.json"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gen.GenerateMakefile(goFile, namespace)
|
||||
return gen.GenerateDockerfile(goFile, "-f", "etc/config.yaml")
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package gen
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
@@ -9,11 +10,16 @@ import (
|
||||
)
|
||||
|
||||
func GenerateDockerfile(goFile string, args ...string) error {
|
||||
relPath, err := util.PathFromGoSrc()
|
||||
projPath, err := getFilePath(filepath.Dir(goFile))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pos := strings.IndexByte(projPath, '/')
|
||||
if pos >= 0 {
|
||||
projPath = projPath[pos+1:]
|
||||
}
|
||||
|
||||
out, err := util.CreateIfNotExist("Dockerfile")
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -28,7 +34,7 @@ func GenerateDockerfile(goFile string, args ...string) error {
|
||||
t := template.Must(template.New("dockerfile").Parse(dockerTemplate))
|
||||
return t.Execute(out, map[string]string{
|
||||
"projectName": vars.ProjectName,
|
||||
"goRelPath": relPath,
|
||||
"goRelPath": projPath,
|
||||
"goFile": goFile,
|
||||
"exeFile": util.FileNameWithoutExt(goFile),
|
||||
"argument": builder.String(),
|
||||
|
||||
26
tools/goctl/gen/filepath.go
Normal file
26
tools/goctl/gen/filepath.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package gen
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
)
|
||||
|
||||
func getFilePath(file string) (string, error) {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
projPath, ok := util.FindGoModPath(filepath.Join(wd, file))
|
||||
if !ok {
|
||||
projPath, err = util.PathFromGoSrc()
|
||||
if err != nil {
|
||||
return "", errors.New("no go.mod found, or not in GOPATH")
|
||||
}
|
||||
}
|
||||
|
||||
return projPath, nil
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package gen
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
)
|
||||
|
||||
func GenerateMakefile(goFile, namespace string) error {
|
||||
relPath, err := util.PathFromGoSrc()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
movePath, err := getMovePath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out, err := util.CreateIfNotExist("Makefile")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
t := template.Must(template.New("makefile").Parse(makefileTemplate))
|
||||
return t.Execute(out, map[string]string{
|
||||
"rootRelPath": movePath,
|
||||
"relPath": relPath,
|
||||
"exeFile": util.FileNameWithoutExt(goFile),
|
||||
"namespace": namespace,
|
||||
})
|
||||
}
|
||||
|
||||
func getMovePath() (string, error) {
|
||||
relPath, err := util.PathFromGoSrc()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var builder strings.Builder
|
||||
for range strings.Split(relPath, "/") {
|
||||
builder.WriteString("../")
|
||||
}
|
||||
|
||||
if move := builder.String(); len(move) == 0 {
|
||||
return ".", nil
|
||||
} else {
|
||||
return move, nil
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package gen
|
||||
|
||||
const (
|
||||
dockerTemplate = `FROM golang:alpine AS builder
|
||||
const dockerTemplate = `FROM golang:alpine AS builder
|
||||
|
||||
LABEL stage=gobuilder
|
||||
|
||||
@@ -26,19 +25,3 @@ COPY --from=builder /app/{{.exeFile}} /app/{{.exeFile}}
|
||||
|
||||
CMD ["./{{.exeFile}}"{{.argument}}]
|
||||
`
|
||||
|
||||
makefileTemplate = `version := v$(shell /bin/date "+%y%m%d%H%M%S")
|
||||
|
||||
build:
|
||||
docker pull alpine
|
||||
docker pull golang:alpine
|
||||
cd $(GOPATH)/src/xiao && docker build -t registry.cn-hangzhou.aliyuncs.com/xapp/{{.exeFile}}:$(version) . -f {{.relPath}}/Dockerfile
|
||||
docker image prune --filter label=stage=gobuilder -f
|
||||
|
||||
push: build
|
||||
docker push registry.cn-hangzhou.aliyuncs.com/xapp/{{.exeFile}}:$(version)
|
||||
|
||||
deploy: push
|
||||
kubectl -n {{.namespace}} set image deployment/{{.exeFile}}-deployment {{.exeFile}}=registry-vpc.cn-hangzhou.aliyuncs.com/xapp/{{.exeFile}}:$(version)
|
||||
`
|
||||
)
|
||||
|
||||
@@ -17,7 +17,8 @@ import (
|
||||
"github.com/tal-tech/go-zero/tools/goctl/configgen"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/docker"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/feature"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/model/sql/command"
|
||||
model "github.com/tal-tech/go-zero/tools/goctl/model/sql/command"
|
||||
rpc "github.com/tal-tech/go-zero/tools/goctl/rpc/command"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
@@ -188,17 +189,65 @@ var (
|
||||
},
|
||||
Action: docker.DockerCommand,
|
||||
},
|
||||
{
|
||||
Name: "rpc",
|
||||
Usage: "generate rpc code",
|
||||
Subcommands: []cli.Command{
|
||||
{
|
||||
Name: "template",
|
||||
Usage: `generate proto template`,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "out, o",
|
||||
Usage: "the target path of proto",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "idea",
|
||||
Usage: "whether the command execution environment is from idea plugin. [option]",
|
||||
},
|
||||
},
|
||||
Action: rpc.RpcTemplate,
|
||||
},
|
||||
{
|
||||
Name: "proto",
|
||||
Usage: `generate rpc from proto`,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "src, s",
|
||||
Usage: "the file path of the proto source file",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "dir, d",
|
||||
Usage: `the target path of the code,default path is "${pwd}". [option]`,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "service, srv",
|
||||
Usage: `the name of rpc service. [option]`,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "shared",
|
||||
Usage: `the dir of the shared file,default path is "${pwd}/shared. [option]`,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "idea",
|
||||
Usage: "whether the command execution environment is from idea plugin. [option]",
|
||||
},
|
||||
},
|
||||
Action: rpc.Rpc,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "model",
|
||||
Usage: "generate model code",
|
||||
Subcommands: []cli.Command{
|
||||
{
|
||||
Name: "mysql",
|
||||
Usage: `generate mysql model"`,
|
||||
Usage: `generate mysql model`,
|
||||
Subcommands: []cli.Command{
|
||||
{
|
||||
Name: "ddl",
|
||||
Usage: `generate mysql model from ddl"`,
|
||||
Usage: `generate mysql model from ddl`,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "src, s",
|
||||
@@ -217,19 +266,19 @@ var (
|
||||
Usage: "for idea plugin [optional]",
|
||||
},
|
||||
},
|
||||
Action: command.MysqlDDL,
|
||||
Action: model.MysqlDDL,
|
||||
},
|
||||
{
|
||||
Name: "datasource",
|
||||
Usage: `generate model from datasource"`,
|
||||
Usage: `generate model from datasource`,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "url",
|
||||
Usage: `the data source of database,like "root:password@tcp(127.0.0.1:3306)/database"`,
|
||||
Usage: `the data source of database,like "root:password@tcp(127.0.0.1:3306)/database`,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "table, t",
|
||||
Usage: `source table,tables separated by commas,like "user,course"`,
|
||||
Usage: `source table,tables separated by commas,like "user,course`,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "cache, c",
|
||||
@@ -244,7 +293,7 @@ var (
|
||||
Usage: "for idea plugin [optional]",
|
||||
},
|
||||
},
|
||||
Action: command.MyDataSource,
|
||||
Action: model.MyDataSource,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
# generate model with cache from ddl
|
||||
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
|
||||
|
||||
# generate model with cache from data source
|
||||
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"
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
|
||||
"github.com/tal-tech/go-zero/core/collection"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/model/sql/template"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/templatex"
|
||||
)
|
||||
|
||||
func genDelete(table Table, withCache bool) (string, error) {
|
||||
@@ -28,7 +28,7 @@ func genDelete(table Table, withCache bool) (string, error) {
|
||||
}
|
||||
}
|
||||
camel := table.Name.ToCamel()
|
||||
output, err := templatex.With("delete").
|
||||
output, err := util.With("delete").
|
||||
Parse(template.Delete).
|
||||
Execute(map[string]interface{}{
|
||||
"upperStartCamelObject": camel,
|
||||
|
||||
@@ -2,6 +2,4 @@ package gen
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
ErrCircleQuery = errors.New("circle query with other fields")
|
||||
)
|
||||
var ErrCircleQuery = errors.New("circle query with other fields")
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/model/sql/parser"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/model/sql/template"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/templatex"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
)
|
||||
|
||||
func genFields(fields []parser.Field) (string, error) {
|
||||
@@ -25,7 +25,7 @@ func genField(field parser.Field) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
output, err := templatex.With("types").
|
||||
output, err := util.With("types").
|
||||
Parse(template.Field).
|
||||
Execute(map[string]interface{}{
|
||||
"name": field.Name.ToCamel(),
|
||||
|
||||
@@ -2,13 +2,13 @@ package gen
|
||||
|
||||
import (
|
||||
"github.com/tal-tech/go-zero/tools/goctl/model/sql/template"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/templatex"
|
||||
)
|
||||
|
||||
func genFindOne(table Table, withCache bool) (string, error) {
|
||||
camel := table.Name.ToCamel()
|
||||
output, err := templatex.With("findOne").
|
||||
output, err := util.With("findOne").
|
||||
Parse(template.FindOne).
|
||||
Execute(map[string]interface{}{
|
||||
"withCache": withCache,
|
||||
|
||||
@@ -5,12 +5,12 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/model/sql/template"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/templatex"
|
||||
)
|
||||
|
||||
func genFineOneByField(table Table, withCache bool) (string, error) {
|
||||
t := templatex.With("findOneByField").Parse(template.FindOneByField)
|
||||
t := util.With("findOneByField").Parse(template.FindOneByField)
|
||||
var list []string
|
||||
camelTableName := table.Name.ToCamel()
|
||||
for _, field := range table.Fields {
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/console"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/templatex"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -82,7 +81,7 @@ func (g *defaultGenerator) Start(withCache bool) error {
|
||||
}
|
||||
}
|
||||
// generate error file
|
||||
filename := filepath.Join(dirAbs, "error.go")
|
||||
filename := filepath.Join(dirAbs, "vars.go")
|
||||
if !util.FileExists(filename) {
|
||||
err = ioutil.WriteFile(filename, []byte(template.Error), os.ModePerm)
|
||||
if err != nil {
|
||||
@@ -119,7 +118,7 @@ type (
|
||||
)
|
||||
|
||||
func (g *defaultGenerator) genModel(in parser.Table, withCache bool) (string, error) {
|
||||
t := templatex.With("model").
|
||||
t := util.With("model").
|
||||
Parse(template.Model).
|
||||
GoFmt(true)
|
||||
|
||||
@@ -172,7 +171,7 @@ func (g *defaultGenerator) genModel(in parser.Table, withCache bool) (string, er
|
||||
"types": typesCode,
|
||||
"new": newCode,
|
||||
"insert": insertCode,
|
||||
"find": strings.Join(findCode, "\r\n"),
|
||||
"find": strings.Join(findCode, "\n"),
|
||||
"update": updateCode,
|
||||
"delete": deleteCode,
|
||||
})
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/model/sql/template"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/templatex"
|
||||
)
|
||||
|
||||
func genInsert(table Table, withCache bool) (string, error) {
|
||||
@@ -23,7 +23,7 @@ func genInsert(table Table, withCache bool) (string, error) {
|
||||
expressionValues = append(expressionValues, "data."+camel)
|
||||
}
|
||||
camel := table.Name.ToCamel()
|
||||
output, err := templatex.With("insert").
|
||||
output, err := util.With("insert").
|
||||
Parse(template.Insert).
|
||||
Execute(map[string]interface{}{
|
||||
"withCache": withCache,
|
||||
|
||||
@@ -2,11 +2,11 @@ package gen
|
||||
|
||||
import (
|
||||
"github.com/tal-tech/go-zero/tools/goctl/model/sql/template"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/templatex"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
)
|
||||
|
||||
func genNew(table Table, withCache bool) (string, error) {
|
||||
output, err := templatex.With("new").
|
||||
output, err := util.With("new").
|
||||
Parse(template.New).
|
||||
Execute(map[string]interface{}{
|
||||
"withCache": withCache,
|
||||
|
||||
@@ -2,14 +2,14 @@ package gen
|
||||
|
||||
import (
|
||||
"github.com/tal-tech/go-zero/tools/goctl/model/sql/template"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/templatex"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
)
|
||||
|
||||
func genTag(in string) (string, error) {
|
||||
if in == "" {
|
||||
return in, nil
|
||||
}
|
||||
output, err := templatex.With("tag").
|
||||
output, err := util.With("tag").
|
||||
Parse(template.Tag).
|
||||
Execute(map[string]interface{}{
|
||||
"field": in,
|
||||
|
||||
@@ -2,7 +2,7 @@ package gen
|
||||
|
||||
import (
|
||||
"github.com/tal-tech/go-zero/tools/goctl/model/sql/template"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/templatex"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
)
|
||||
|
||||
func genTypes(table Table, withCache bool) (string, error) {
|
||||
@@ -11,7 +11,7 @@ func genTypes(table Table, withCache bool) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
output, err := templatex.With("types").
|
||||
output, err := util.With("types").
|
||||
Parse(template.Types).
|
||||
Execute(map[string]interface{}{
|
||||
"withCache": withCache,
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/model/sql/template"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/templatex"
|
||||
)
|
||||
|
||||
func genUpdate(table Table, withCache bool) (string, error) {
|
||||
@@ -22,7 +22,7 @@ func genUpdate(table Table, withCache bool) (string, error) {
|
||||
}
|
||||
expressionValues = append(expressionValues, "data."+table.PrimaryKey.Name.ToCamel())
|
||||
camelTableName := table.Name.ToCamel()
|
||||
output, err := templatex.With("update").
|
||||
output, err := util.With("update").
|
||||
Parse(template.Update).
|
||||
Execute(map[string]interface{}{
|
||||
"withCache": withCache,
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/model/sql/template"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/templatex"
|
||||
)
|
||||
|
||||
func genVars(table Table, withCache bool) (string, error) {
|
||||
@@ -14,13 +14,13 @@ func genVars(table Table, withCache bool) (string, error) {
|
||||
keys = append(keys, v.VarExpression)
|
||||
}
|
||||
camel := table.Name.ToCamel()
|
||||
output, err := templatex.With("var").
|
||||
output, err := util.With("var").
|
||||
Parse(template.Vars).
|
||||
GoFmt(true).
|
||||
Execute(map[string]interface{}{
|
||||
"lowerStartCamelObject": stringx.From(camel).UnTitle(),
|
||||
"upperStartCamelObject": camel,
|
||||
"cacheKeys": strings.Join(keys, "\r\n"),
|
||||
"cacheKeys": strings.Join(keys, "\n"),
|
||||
"autoIncrement": table.PrimaryKey.AutoIncrement,
|
||||
"originalPrimaryKey": table.PrimaryKey.Name.Source(),
|
||||
"withCache": withCache,
|
||||
|
||||
@@ -2,10 +2,11 @@ package template
|
||||
|
||||
var Delete = `
|
||||
func (m *{{.upperStartCamelObject}}Model) Delete({{.lowerStartCamelPrimaryKey}} {{.dataType}}) error {
|
||||
{{if .withCache}}{{if .containsIndexCache}}data,err:=m.FindOne({{.lowerStartCamelPrimaryKey}})
|
||||
{{if .withCache}}{{if .containsIndexCache}}_, err:=m.FindOne({{.lowerStartCamelPrimaryKey}})
|
||||
if err!=nil{
|
||||
return err
|
||||
}{{end}}
|
||||
|
||||
{{.keys}}
|
||||
_, err {{if .containsIndexCache}}={{else}}:={{end}} m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
|
||||
query := ` + "`" + `delete from ` + "` +" + ` m.table + ` + " `" + ` where {{.originalPrimaryKey}} = ?` + "`" + `
|
||||
|
||||
@@ -4,8 +4,5 @@ var Error = `package model
|
||||
|
||||
import "github.com/tal-tech/go-zero/core/stores/sqlx"
|
||||
|
||||
var (
|
||||
ErrNotFound = sqlx.ErrNotFound
|
||||
)
|
||||
|
||||
var ErrNotFound = sqlx.ErrNotFound
|
||||
`
|
||||
|
||||
@@ -5,7 +5,6 @@ var (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tal-tech/go-zero/core/stores/cache"
|
||||
"github.com/tal-tech/go-zero/core/stores/sqlc"
|
||||
|
||||
@@ -2,7 +2,7 @@ package template
|
||||
|
||||
var Insert = `
|
||||
func (m *{{.upperStartCamelObject}}Model) Insert(data {{.upperStartCamelObject}}) (sql.Result, error) {
|
||||
query := ` + "`" + `insert into ` + "`" + ` + m.table + ` + "`(` + " + `{{.lowerStartCamelObject}}RowsExpectAutoSet` + " + `) value ({{.expression}})` " + `
|
||||
query := ` + "`" + `insert into ` + "`" + ` + m.table + ` + "` (` + " + `{{.lowerStartCamelObject}}RowsExpectAutoSet` + " + `) values ({{.expression}})` " + `
|
||||
return m.{{if .withCache}}ExecNoCache{{else}}conn.Exec{{end}}(query, {{.expressionValues}})
|
||||
}
|
||||
`
|
||||
|
||||
9
tools/goctl/rpc/CHANGELOG.md
Normal file
9
tools/goctl/rpc/CHANGELOG.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Change log
|
||||
|
||||
# 2020-08-29
|
||||
* 新增支持windows生成
|
||||
|
||||
# 2020-08-27
|
||||
* 新增支持rpc模板生成
|
||||
* 新增支持rpc服务生成
|
||||
|
||||
140
tools/goctl/rpc/README.md
Normal file
140
tools/goctl/rpc/README.md
Normal file
@@ -0,0 +1,140 @@
|
||||
# Rpc Generation
|
||||
Goctl Rpc是`goctl`脚手架下的一个rpc服务代码生成模块,支持proto模板生成和rpc服务代码生成,通过此工具生成代码你只需要关注业务逻辑编写而不用去编写一些重复性的代码。这使得我们把精力重心放在业务上,从而加快了开发效率且降低了代码出错率。
|
||||
|
||||
# 特性
|
||||
* 简单易用
|
||||
* 快速提升开发效率
|
||||
* 出错率低
|
||||
|
||||
# 快速开始
|
||||
|
||||
### 生成proto模板
|
||||
|
||||
```shell script
|
||||
$ goctl rpc template -o=user.proto
|
||||
```
|
||||
|
||||
```golang
|
||||
syntax = "proto3";
|
||||
|
||||
package remote;
|
||||
|
||||
message Request {
|
||||
// 用户名
|
||||
string username = 1;
|
||||
// 用户密码
|
||||
string password = 2;
|
||||
}
|
||||
|
||||
message Response {
|
||||
// 用户名称
|
||||
string name = 1;
|
||||
// 用户性别
|
||||
string gender = 2;
|
||||
}
|
||||
|
||||
service User {
|
||||
// 登录
|
||||
rpc Login(Request)returns(Response);
|
||||
}
|
||||
```
|
||||
### 生成rpc服务代码
|
||||
|
||||
生成user rpc服务
|
||||
```
|
||||
$ goctl rpc proto -src=user.proto
|
||||
```
|
||||
|
||||
代码tree
|
||||
|
||||
```
|
||||
user
|
||||
├── etc
|
||||
│ └── user.json
|
||||
├── internal
|
||||
│ ├── config
|
||||
│ │ └── config.go
|
||||
│ ├── handler
|
||||
│ │ ├── loginhandler.go
|
||||
│ ├── logic
|
||||
│ │ └── loginlogic.go
|
||||
│ └── svc
|
||||
│ └── servicecontext.go
|
||||
├── pb
|
||||
│ └── user.pb.go
|
||||
├── shared
|
||||
│ ├── mockusermodel.go
|
||||
│ ├── types.go
|
||||
│ └── usermodel.go
|
||||
├── user.go
|
||||
└── user.proto
|
||||
|
||||
```
|
||||
# 准备工作
|
||||
* 安装了go环境
|
||||
* 安装了protoc&protoc-gen-go,并且已经设置环境变量
|
||||
* mockgen(可选)
|
||||
|
||||
# 用法
|
||||
```shell script
|
||||
$ goctl rpc proto -h
|
||||
```
|
||||
|
||||
```shell script
|
||||
NAME:
|
||||
goctl rpc proto - generate rpc from proto
|
||||
|
||||
USAGE:
|
||||
goctl rpc proto [command options] [arguments...]
|
||||
|
||||
OPTIONS:
|
||||
--src value, -s value the file path of the proto source file
|
||||
--dir value, -d value the target path of the code,default path is "${pwd}". [option]
|
||||
--service value, --srv value the name of rpc service. [option]
|
||||
--shared value the dir of the shared file,default path is "${pwd}/shared. [option]"
|
||||
--idea whether the command execution environment is from idea plugin. [option]
|
||||
|
||||
```
|
||||
|
||||
* 参数说明
|
||||
* --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文件。
|
||||
|
||||
# 注意事项
|
||||
* proto不支持暂多文件同时生成
|
||||
* proto不支持外部依赖包引入,message不支持inline
|
||||
* 目前main文件、shared文件、handler文件会被强制覆盖,而和开发人员手动需要编写的则不会覆盖生成,这一类在代码头部均有
|
||||
```shell script
|
||||
// Code generated by goctl. DO NOT EDIT!
|
||||
// Source: xxx.proto
|
||||
```
|
||||
的标识,请注意不要将也写业务性代码写在里面。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
22
tools/goctl/rpc/command/command.go
Normal file
22
tools/goctl/rpc/command/command.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"github.com/tal-tech/go-zero/tools/goctl/rpc/ctx"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/rpc/gen"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
func Rpc(c *cli.Context) error {
|
||||
rpcCtx := ctx.MustCreateRpcContextFromCli(c)
|
||||
generator := gen.NewDefaultRpcGenerator(rpcCtx)
|
||||
rpcCtx.Must(generator.Generate())
|
||||
return nil
|
||||
}
|
||||
|
||||
func RpcTemplate(c *cli.Context) error {
|
||||
out := c.String("out")
|
||||
idea := c.Bool("idea")
|
||||
generator := gen.NewRpcTemplate(out, idea)
|
||||
generator.MustGenerate()
|
||||
return nil
|
||||
}
|
||||
94
tools/goctl/rpc/ctx/ctx.go
Normal file
94
tools/goctl/rpc/ctx/ctx.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package ctx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/tal-tech/go-zero/core/logx"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/console"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
const (
|
||||
flagSrc = "src"
|
||||
flagDir = "dir"
|
||||
flagService = "service"
|
||||
flagIdea = "idea"
|
||||
)
|
||||
|
||||
type RpcContext struct {
|
||||
ProjectPath string
|
||||
ProjectName stringx.String
|
||||
ServiceName stringx.String
|
||||
CurrentPath string
|
||||
Module string
|
||||
ProtoFileSrc string
|
||||
ProtoSource string
|
||||
TargetDir string
|
||||
console.Console
|
||||
}
|
||||
|
||||
func MustCreateRpcContext(protoSrc, targetDir, serviceName string, idea bool) *RpcContext {
|
||||
log := console.NewConsole(idea)
|
||||
info, err := prepare(log)
|
||||
log.Must(err)
|
||||
|
||||
if stringx.From(protoSrc).IsEmptyOrSpace() {
|
||||
log.Fatalln("expected proto source, but nothing found")
|
||||
}
|
||||
srcFp, err := filepath.Abs(protoSrc)
|
||||
log.Must(err)
|
||||
|
||||
if !util.FileExists(srcFp) {
|
||||
log.Fatalln("%s is not exists", srcFp)
|
||||
}
|
||||
current := filepath.Dir(srcFp)
|
||||
if stringx.From(targetDir).IsEmptyOrSpace() {
|
||||
targetDir = current
|
||||
}
|
||||
targetDirFp, err := filepath.Abs(targetDir)
|
||||
log.Must(err)
|
||||
|
||||
if stringx.From(serviceName).IsEmptyOrSpace() {
|
||||
serviceName = getServiceFromRpcStructure(targetDirFp)
|
||||
}
|
||||
serviceNameString := stringx.From(serviceName)
|
||||
if serviceNameString.IsEmptyOrSpace() {
|
||||
log.Fatalln("service name is not found")
|
||||
}
|
||||
|
||||
return &RpcContext{
|
||||
ProjectPath: info.Path,
|
||||
ProjectName: stringx.From(info.Name),
|
||||
ServiceName: serviceNameString,
|
||||
CurrentPath: current,
|
||||
Module: info.GoMod.Module,
|
||||
ProtoFileSrc: srcFp,
|
||||
ProtoSource: filepath.Base(srcFp),
|
||||
TargetDir: targetDirFp,
|
||||
Console: log,
|
||||
}
|
||||
}
|
||||
func MustCreateRpcContextFromCli(ctx *cli.Context) *RpcContext {
|
||||
os := runtime.GOOS
|
||||
switch os {
|
||||
case "darwin", "linux", "windows":
|
||||
default:
|
||||
logx.Must(fmt.Errorf("unexpected os: %s", os))
|
||||
}
|
||||
protoSrc := ctx.String(flagSrc)
|
||||
targetDir := ctx.String(flagDir)
|
||||
serviceName := ctx.String(flagService)
|
||||
idea := ctx.Bool(flagIdea)
|
||||
return MustCreateRpcContext(protoSrc, targetDir, serviceName, idea)
|
||||
}
|
||||
|
||||
func getServiceFromRpcStructure(targetDir string) string {
|
||||
targetDir = filepath.Clean(targetDir)
|
||||
suffix := filepath.Join("cmd", "rpc")
|
||||
return filepath.Base(strings.TrimSuffix(targetDir, suffix))
|
||||
}
|
||||
121
tools/goctl/rpc/ctx/project.go
Normal file
121
tools/goctl/rpc/ctx/project.go
Normal file
@@ -0,0 +1,121 @@
|
||||
package ctx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/rpc/execx"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/console"
|
||||
)
|
||||
|
||||
const (
|
||||
constGo = "go"
|
||||
constProtoC = "protoc"
|
||||
constGoMod = "go env GOMOD"
|
||||
constGoPath = "go env GOPATH"
|
||||
constProtoCGenGo = "protoc-gen-go"
|
||||
)
|
||||
|
||||
type (
|
||||
Project struct {
|
||||
Path string
|
||||
Name string
|
||||
GoMod GoMod
|
||||
}
|
||||
|
||||
GoMod struct {
|
||||
Module string
|
||||
}
|
||||
)
|
||||
|
||||
func prepare(log console.Console) (*Project, error) {
|
||||
log.Info("checking go env...")
|
||||
_, err := exec.LookPath(constGo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = exec.LookPath(constProtoC)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = exec.LookPath(constProtoCGenGo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var (
|
||||
goMod, module string
|
||||
goPath string
|
||||
name, path string
|
||||
)
|
||||
|
||||
ret, err := execx.Run(constGoMod)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
goMod = strings.TrimSpace(ret)
|
||||
|
||||
ret, err = execx.Run(constGoPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
goPath = strings.TrimSpace(ret)
|
||||
src := filepath.Join(goPath, "src")
|
||||
if len(goMod) > 0 {
|
||||
path = filepath.Dir(goMod)
|
||||
name = filepath.Base(path)
|
||||
data, err := ioutil.ReadFile(goMod)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
module, err = matchModule(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
pwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(pwd, src) {
|
||||
return nil, fmt.Errorf("%s: project is not in go mod and go path", pwd)
|
||||
}
|
||||
r := strings.TrimPrefix(pwd, src+string(filepath.Separator))
|
||||
name = filepath.Dir(r)
|
||||
if name == "." {
|
||||
name = r
|
||||
}
|
||||
path = filepath.Join(src, name)
|
||||
module = name
|
||||
}
|
||||
|
||||
return &Project{
|
||||
Name: name,
|
||||
Path: path,
|
||||
GoMod: GoMod{
|
||||
Module: module,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func matchModule(data []byte) (string, error) {
|
||||
text := string(data)
|
||||
re := regexp.MustCompile(`(?m)^\s*module\s+[a-z0-9/\-.]+$`)
|
||||
matches := re.FindAllString(text, -1)
|
||||
if len(matches) == 1 {
|
||||
target := matches[0]
|
||||
index := strings.Index(target, "module")
|
||||
return strings.TrimSpace(target[index+6:]), nil
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
36
tools/goctl/rpc/execx/execx.go
Normal file
36
tools/goctl/rpc/execx/execx.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package execx
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func Run(arg string) (string, error) {
|
||||
goos := runtime.GOOS
|
||||
var cmd *exec.Cmd
|
||||
switch goos {
|
||||
case "darwin", "linux":
|
||||
cmd = exec.Command("sh", "-c", arg)
|
||||
case "windows":
|
||||
cmd = exec.Command("cmd.exe", "/c", arg)
|
||||
default:
|
||||
return "", fmt.Errorf("unexpected os: %v", goos)
|
||||
}
|
||||
|
||||
dtsout := new(bytes.Buffer)
|
||||
stderr := new(bytes.Buffer)
|
||||
cmd.Stdout = dtsout
|
||||
cmd.Stderr = stderr
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
if stderr.Len() > 0 {
|
||||
return "", errors.New(stderr.String())
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
return dtsout.String(), nil
|
||||
}
|
||||
86
tools/goctl/rpc/gen/gen.go
Normal file
86
tools/goctl/rpc/gen/gen.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package gen
|
||||
|
||||
import (
|
||||
"github.com/tal-tech/go-zero/tools/goctl/rpc/ctx"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/rpc/parser"
|
||||
)
|
||||
|
||||
const (
|
||||
dirTarget = "dirTarget"
|
||||
dirConfig = "config"
|
||||
dirEtc = "etc"
|
||||
dirSvc = "svc"
|
||||
dirServer = "server"
|
||||
dirLogic = "logic"
|
||||
dirPb = "pb"
|
||||
dirInternal = "internal"
|
||||
fileConfig = "config.go"
|
||||
fileServiceContext = "servicecontext.go"
|
||||
)
|
||||
|
||||
type defaultRpcGenerator struct {
|
||||
dirM map[string]string
|
||||
Ctx *ctx.RpcContext
|
||||
ast *parser.PbAst
|
||||
}
|
||||
|
||||
func NewDefaultRpcGenerator(ctx *ctx.RpcContext) *defaultRpcGenerator {
|
||||
return &defaultRpcGenerator{
|
||||
Ctx: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
func (g *defaultRpcGenerator) Generate() (err error) {
|
||||
g.Ctx.Info("generating code...")
|
||||
defer func() {
|
||||
if err == nil {
|
||||
g.Ctx.Success("Done.")
|
||||
}
|
||||
}()
|
||||
err = g.createDir()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = g.genEtc()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = g.genPb()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = g.genConfig()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = g.genSvc()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = g.genLogic()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = g.genHandler()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = g.genMain()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = g.genCall()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
240
tools/goctl/rpc/gen/gencall.go
Normal file
240
tools/goctl/rpc/gen/gencall.go
Normal file
@@ -0,0 +1,240 @@
|
||||
package gen
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/rpc/execx"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/rpc/parser"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
)
|
||||
|
||||
const (
|
||||
callTemplateText = `{{.head}}
|
||||
|
||||
//go:generate mockgen -destination ./{{.name}}_mock.go -package {{.filePackage}} -source $GOFILE
|
||||
|
||||
package {{.filePackage}}
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
{{.package}}
|
||||
|
||||
"github.com/tal-tech/go-zero/core/jsonx"
|
||||
"github.com/tal-tech/go-zero/rpcx"
|
||||
)
|
||||
|
||||
type (
|
||||
{{.serviceName}} interface {
|
||||
{{.interface}}
|
||||
}
|
||||
|
||||
default{{.serviceName}} struct {
|
||||
cli rpcx.Client
|
||||
}
|
||||
)
|
||||
|
||||
func New{{.serviceName}}(cli rpcx.Client) {{.serviceName}} {
|
||||
return &default{{.serviceName}}{
|
||||
cli: cli,
|
||||
}
|
||||
}
|
||||
|
||||
{{.functions}}
|
||||
`
|
||||
callTemplateTypes = `{{.head}}
|
||||
|
||||
package {{.filePackage}}
|
||||
|
||||
import "errors"
|
||||
|
||||
var errJsonConvert = errors.New("json convert error")
|
||||
|
||||
{{.types}}
|
||||
`
|
||||
callInterfaceFunctionTemplate = `{{if .hasComment}}{{.comment}}
|
||||
{{end}}{{.method}}(ctx context.Context,in *{{.pbRequest}}) {{if .hasResponse}}(*{{.pbResponse}},{{end}} error{{if .hasResponse}}){{end}}`
|
||||
callFunctionTemplate = `
|
||||
{{if .hasComment}}{{.comment}}{{end}}
|
||||
func (m *default{{.rpcServiceName}}) {{.method}}(ctx context.Context,in *{{.pbRequest}}) {{if .hasResponse}}(*{{.pbResponse}},{{end}} error{{if .hasResponse}}){{end}} {
|
||||
var request {{.package}}.{{.pbRequest}}
|
||||
bts, err := jsonx.Marshal(in)
|
||||
if err != nil {
|
||||
return {{if .hasResponse}}nil, {{end}}errJsonConvert
|
||||
}
|
||||
|
||||
err = jsonx.Unmarshal(bts, &request)
|
||||
if err != nil {
|
||||
return {{if .hasResponse}}nil, {{end}}errJsonConvert
|
||||
}
|
||||
|
||||
client := {{.package}}.New{{.rpcServiceName}}Client(m.cli.Conn())
|
||||
{{if .hasResponse}}resp, err := {{else}}_, err = {{end}}client.{{.method}}(ctx, &request)
|
||||
{{if .hasResponse}}if err != nil{
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ret {{.pbResponse}}
|
||||
bts, err = jsonx.Marshal(resp)
|
||||
if err != nil{
|
||||
return nil, errJsonConvert
|
||||
}
|
||||
|
||||
err = jsonx.Unmarshal(bts, &ret)
|
||||
if err != nil{
|
||||
return nil, errJsonConvert
|
||||
}
|
||||
|
||||
return &ret, nil{{else}}if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil{{end}}
|
||||
}
|
||||
`
|
||||
)
|
||||
|
||||
func (g *defaultRpcGenerator) genCall() error {
|
||||
file := g.ast
|
||||
if len(file.Service) == 0 {
|
||||
return nil
|
||||
}
|
||||
if len(file.Service) > 1 {
|
||||
return fmt.Errorf("we recommend only one service in a proto, currently %d", len(file.Service))
|
||||
}
|
||||
|
||||
typeCode, err := file.GenTypesCode()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
service := file.Service[0]
|
||||
callPath, err := filepath.Abs(service.Name.Lower())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = util.MkdirIfNotExist(callPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pbPkg := file.Package
|
||||
remotePackage := fmt.Sprintf(`%v "%v"`, pbPkg, g.mustGetPackage(dirPb))
|
||||
filename := filepath.Join(callPath, "types.go")
|
||||
head := util.GetHead(g.Ctx.ProtoSource)
|
||||
err = util.With("types").GoFmt(true).Parse(callTemplateTypes).SaveTo(map[string]interface{}{
|
||||
"head": head,
|
||||
"filePackage": service.Name.Lower(),
|
||||
"pbPkg": pbPkg,
|
||||
"serviceName": g.Ctx.ServiceName.Title(),
|
||||
"lowerStartServiceName": g.Ctx.ServiceName.UnTitle(),
|
||||
"types": typeCode,
|
||||
}, filename, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = exec.LookPath("mockgen")
|
||||
mockGenInstalled := err == nil
|
||||
filename = filepath.Join(callPath, fmt.Sprintf("%s.go", service.Name.Lower()))
|
||||
functions, err := g.getFuncs(service)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
iFunctions, err := g.getInterfaceFuncs(service)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mockFile := filepath.Join(callPath, fmt.Sprintf("%s_mock.go", service.Name.Lower()))
|
||||
os.Remove(mockFile)
|
||||
err = util.With("shared").GoFmt(true).Parse(callTemplateText).SaveTo(map[string]interface{}{
|
||||
"name": service.Name.Lower(),
|
||||
"head": head,
|
||||
"filePackage": service.Name.Lower(),
|
||||
"pbPkg": pbPkg,
|
||||
"package": remotePackage,
|
||||
"serviceName": service.Name.Title(),
|
||||
"functions": strings.Join(functions, "\n"),
|
||||
"interface": strings.Join(iFunctions, "\n"),
|
||||
}, filename, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// if mockgen is already installed, it will generate code of gomock for shared files
|
||||
_, err = exec.LookPath("mockgen")
|
||||
if mockGenInstalled {
|
||||
execx.Run(fmt.Sprintf("go generate %s", filename))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *defaultRpcGenerator) getFuncs(service *parser.RpcService) ([]string, error) {
|
||||
file := g.ast
|
||||
pkgName := file.Package
|
||||
functions := make([]string, 0)
|
||||
for _, method := range service.Funcs {
|
||||
data, found := file.Strcuts[strings.ToLower(method.OutType)]
|
||||
if found {
|
||||
found = len(data.Field) > 0
|
||||
}
|
||||
var comment string
|
||||
if len(method.Document) > 0 {
|
||||
comment = method.Document[0]
|
||||
}
|
||||
buffer, err := util.With("sharedFn").Parse(callFunctionTemplate).Execute(map[string]interface{}{
|
||||
"rpcServiceName": service.Name.Title(),
|
||||
"method": method.Name.Title(),
|
||||
"package": pkgName,
|
||||
"pbRequest": method.InType,
|
||||
"pbResponse": method.OutType,
|
||||
"hasResponse": found,
|
||||
"hasComment": len(method.Document) > 0,
|
||||
"comment": comment,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
functions = append(functions, buffer.String())
|
||||
}
|
||||
return functions, nil
|
||||
}
|
||||
|
||||
func (g *defaultRpcGenerator) getInterfaceFuncs(service *parser.RpcService) ([]string, error) {
|
||||
file := g.ast
|
||||
functions := make([]string, 0)
|
||||
|
||||
for _, method := range service.Funcs {
|
||||
data, found := file.Strcuts[strings.ToLower(method.OutType)]
|
||||
if found {
|
||||
found = len(data.Field) > 0
|
||||
}
|
||||
var comment string
|
||||
if len(method.Document) > 0 {
|
||||
comment = method.Document[0]
|
||||
}
|
||||
buffer, err := util.With("interfaceFn").Parse(callInterfaceFunctionTemplate).Execute(
|
||||
map[string]interface{}{
|
||||
"hasComment": len(method.Document) > 0,
|
||||
"comment": comment,
|
||||
"method": method.Name.Title(),
|
||||
"pbRequest": method.InType,
|
||||
"pbResponse": method.OutType,
|
||||
"hasResponse": found,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
functions = append(functions, buffer.String())
|
||||
}
|
||||
|
||||
return functions, nil
|
||||
}
|
||||
27
tools/goctl/rpc/gen/genconfig.go
Normal file
27
tools/goctl/rpc/gen/genconfig.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package gen
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
)
|
||||
|
||||
const configTemplate = `package config
|
||||
|
||||
import "github.com/tal-tech/go-zero/rpcx"
|
||||
|
||||
type Config struct {
|
||||
rpcx.RpcServerConf
|
||||
}
|
||||
`
|
||||
|
||||
func (g *defaultRpcGenerator) genConfig() error {
|
||||
configPath := g.dirM[dirConfig]
|
||||
fileName := filepath.Join(configPath, fileConfig)
|
||||
if util.FileExists(fileName) {
|
||||
return nil
|
||||
}
|
||||
return ioutil.WriteFile(fileName, []byte(configTemplate), os.ModePerm)
|
||||
}
|
||||
53
tools/goctl/rpc/gen/gendir.go
Normal file
53
tools/goctl/rpc/gen/gendir.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package gen
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
)
|
||||
|
||||
// target
|
||||
// ├── etc
|
||||
// ├── internal
|
||||
// │ ├── config
|
||||
// │ ├── handler
|
||||
// │ ├── logic
|
||||
// │ ├── pb
|
||||
// │ └── svc
|
||||
func (g *defaultRpcGenerator) createDir() error {
|
||||
ctx := g.Ctx
|
||||
m := make(map[string]string)
|
||||
m[dirTarget] = ctx.TargetDir
|
||||
m[dirEtc] = filepath.Join(ctx.TargetDir, dirEtc)
|
||||
m[dirInternal] = filepath.Join(ctx.TargetDir, dirInternal)
|
||||
m[dirConfig] = filepath.Join(ctx.TargetDir, dirInternal, dirConfig)
|
||||
m[dirServer] = filepath.Join(ctx.TargetDir, dirInternal, dirServer)
|
||||
m[dirLogic] = filepath.Join(ctx.TargetDir, dirInternal, dirLogic)
|
||||
m[dirPb] = filepath.Join(ctx.TargetDir, dirPb)
|
||||
m[dirSvc] = filepath.Join(ctx.TargetDir, dirInternal, dirSvc)
|
||||
for _, d := range m {
|
||||
err := util.MkdirIfNotExist(d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
g.dirM = m
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *defaultRpcGenerator) mustGetPackage(dir string) string {
|
||||
target := g.dirM[dir]
|
||||
projectPath := g.Ctx.ProjectPath
|
||||
relativePath := strings.TrimPrefix(target, projectPath)
|
||||
os := runtime.GOOS
|
||||
switch os {
|
||||
case "windows":
|
||||
relativePath = filepath.ToSlash(relativePath)
|
||||
case "darwin", "linux":
|
||||
default:
|
||||
g.Ctx.Fatalln("unexpected os: %s", os)
|
||||
}
|
||||
return g.Ctx.Module + relativePath
|
||||
}
|
||||
28
tools/goctl/rpc/gen/genetc.go
Normal file
28
tools/goctl/rpc/gen/genetc.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package gen
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
)
|
||||
|
||||
const etcTemplate = `Name: {{.serviceName}}.rpc
|
||||
ListenOn: 127.0.0.1:8080
|
||||
Etcd:
|
||||
Hosts:
|
||||
- 127.0.0.1:2379
|
||||
Key: {{.serviceName}}.rpc
|
||||
`
|
||||
|
||||
func (g *defaultRpcGenerator) genEtc() error {
|
||||
etdDir := g.dirM[dirEtc]
|
||||
fileName := filepath.Join(etdDir, fmt.Sprintf("%v.yaml", g.Ctx.ServiceName.Lower()))
|
||||
if util.FileExists(fileName) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return util.With("etc").Parse(etcTemplate).SaveTo(map[string]interface{}{
|
||||
"serviceName": g.Ctx.ServiceName.Lower(),
|
||||
}, fileName, false)
|
||||
}
|
||||
93
tools/goctl/rpc/gen/genlogic.go
Normal file
93
tools/goctl/rpc/gen/genlogic.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package gen
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/tal-tech/go-zero/core/collection"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/rpc/parser"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
)
|
||||
|
||||
const (
|
||||
logicTemplate = `package logic
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
{{.imports}}
|
||||
|
||||
"github.com/tal-tech/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type {{.logicName}} struct {
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
logx.Logger
|
||||
}
|
||||
|
||||
func New{{.logicName}}(ctx context.Context,svcCtx *svc.ServiceContext) *{{.logicName}} {
|
||||
return &{{.logicName}}{
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
Logger: logx.WithContext(ctx),
|
||||
}
|
||||
}
|
||||
{{.functions}}
|
||||
`
|
||||
logicFunctionTemplate = `{{if .hasComment}}{{.comment}}{{end}}
|
||||
func (l *{{.logicName}}) {{.method}} (in *{{.package}}.{{.request}}) (*{{.package}}.{{.response}}, error) {
|
||||
// todo: add your logic here and delete this line
|
||||
|
||||
return &{{.package}}.{{.response}}{}, nil
|
||||
}
|
||||
`
|
||||
)
|
||||
|
||||
func (g *defaultRpcGenerator) genLogic() error {
|
||||
logicPath := g.dirM[dirLogic]
|
||||
protoPkg := g.ast.Package
|
||||
service := g.ast.Service
|
||||
for _, item := range service {
|
||||
for _, method := range item.Funcs {
|
||||
logicName := fmt.Sprintf("%slogic.go", method.Name.Lower())
|
||||
filename := filepath.Join(logicPath, logicName)
|
||||
functions, err := genLogicFunction(protoPkg, method)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
imports := collection.NewSet()
|
||||
pbImport := fmt.Sprintf(`%v "%v"`, protoPkg, g.mustGetPackage(dirPb))
|
||||
svcImport := fmt.Sprintf(`"%v"`, g.mustGetPackage(dirSvc))
|
||||
imports.AddStr(pbImport, svcImport)
|
||||
err = util.With("logic").GoFmt(true).Parse(logicTemplate).SaveTo(map[string]interface{}{
|
||||
"logicName": fmt.Sprintf("%sLogic", method.Name.Title()),
|
||||
"functions": functions,
|
||||
"imports": strings.Join(imports.KeysStr(), "\n"),
|
||||
}, filename, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func genLogicFunction(packageName string, method *parser.Func) (string, error) {
|
||||
var functions = make([]string, 0)
|
||||
buffer, err := util.With("fun").Parse(logicFunctionTemplate).Execute(map[string]interface{}{
|
||||
"logicName": fmt.Sprintf("%sLogic", method.Name.Title()),
|
||||
"method": method.Name.Title(),
|
||||
"package": packageName,
|
||||
"request": method.InType,
|
||||
"response": method.OutType,
|
||||
"hasComment": len(method.Document) > 0,
|
||||
"comment": strings.Join(method.Document, "\n"),
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
functions = append(functions, buffer.String())
|
||||
return strings.Join(functions, "\n"), nil
|
||||
}
|
||||
83
tools/goctl/rpc/gen/genmain.go
Normal file
83
tools/goctl/rpc/gen/genmain.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package gen
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/rpc/parser"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
)
|
||||
|
||||
const mainTemplate = `{{.head}}
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
{{.imports}}
|
||||
|
||||
"github.com/tal-tech/go-zero/core/conf"
|
||||
"github.com/tal-tech/go-zero/rpcx"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
var configFile = flag.String("f", "etc/{{.serviceName}}.yaml", "the config file")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
var c config.Config
|
||||
conf.MustLoad(*configFile, &c)
|
||||
ctx := svc.NewServiceContext(c)
|
||||
{{.srv}}
|
||||
|
||||
s, err := rpcx.NewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
|
||||
{{.registers}}
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Printf("Starting rpc server at %s...\n", c.ListenOn)
|
||||
s.Start()
|
||||
}
|
||||
`
|
||||
|
||||
func (g *defaultRpcGenerator) genMain() error {
|
||||
mainPath := g.dirM[dirTarget]
|
||||
file := g.ast
|
||||
pkg := file.Package
|
||||
|
||||
fileName := filepath.Join(mainPath, fmt.Sprintf("%v.go", g.Ctx.ServiceName.Lower()))
|
||||
imports := make([]string, 0)
|
||||
pbImport := fmt.Sprintf(`%v "%v"`, pkg, g.mustGetPackage(dirPb))
|
||||
svcImport := fmt.Sprintf(`"%v"`, g.mustGetPackage(dirSvc))
|
||||
remoteImport := fmt.Sprintf(`"%v"`, g.mustGetPackage(dirServer))
|
||||
configImport := fmt.Sprintf(`"%v"`, g.mustGetPackage(dirConfig))
|
||||
imports = append(imports, configImport, pbImport, remoteImport, svcImport)
|
||||
srv, registers := g.genServer(pkg, file.Service)
|
||||
head := util.GetHead(g.Ctx.ProtoSource)
|
||||
return util.With("main").GoFmt(true).Parse(mainTemplate).SaveTo(map[string]interface{}{
|
||||
"head": head,
|
||||
"package": pkg,
|
||||
"serviceName": g.Ctx.ServiceName.Lower(),
|
||||
"srv": srv,
|
||||
"registers": registers,
|
||||
"imports": strings.Join(imports, "\n"),
|
||||
}, fileName, true)
|
||||
}
|
||||
|
||||
func (g *defaultRpcGenerator) genServer(pkg string, list []*parser.RpcService) (string, string) {
|
||||
list1 := make([]string, 0)
|
||||
list2 := make([]string, 0)
|
||||
for _, item := range list {
|
||||
name := item.Name.UnTitle()
|
||||
list1 = append(list1, fmt.Sprintf("%sSrv := server.New%sServer(ctx)", name, item.Name.Title()))
|
||||
list2 = append(list2, fmt.Sprintf("%s.Register%sServer(grpcServer, %sSrv)", pkg, item.Name.Title(), name))
|
||||
}
|
||||
return strings.Join(list1, "\n"), strings.Join(list2, "\n")
|
||||
}
|
||||
87
tools/goctl/rpc/gen/genpb.go
Normal file
87
tools/goctl/rpc/gen/genpb.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package gen
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/dsymonds/gotoc/parser"
|
||||
|
||||
"github.com/tal-tech/go-zero/core/lang"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/rpc/execx"
|
||||
astParser "github.com/tal-tech/go-zero/tools/goctl/rpc/parser"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
|
||||
)
|
||||
|
||||
func (g *defaultRpcGenerator) genPb() error {
|
||||
importPath, filename := filepath.Split(g.Ctx.ProtoFileSrc)
|
||||
tree, err := parser.ParseFiles([]string{filename}, []string{importPath})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(tree.Files) == 0 {
|
||||
return errors.New("proto ast parse failed")
|
||||
}
|
||||
|
||||
file := tree.Files[0]
|
||||
if len(file.Package) == 0 {
|
||||
return errors.New("expected package, but nothing found")
|
||||
}
|
||||
|
||||
targetStruct := make(map[string]lang.PlaceholderType)
|
||||
for _, item := range file.Messages {
|
||||
if len(item.Messages) > 0 {
|
||||
return fmt.Errorf(`line %v: unexpected inner message near: "%v""`, item.Messages[0].Position.Line, item.Messages[0].Name)
|
||||
}
|
||||
|
||||
name := stringx.From(item.Name)
|
||||
if _, ok := targetStruct[name.Lower()]; ok {
|
||||
return fmt.Errorf("line %v: duplicate %v", item.Position.Line, name)
|
||||
}
|
||||
targetStruct[name.Lower()] = lang.Placeholder
|
||||
}
|
||||
|
||||
pbPath := g.dirM[dirPb]
|
||||
protoFileName := filepath.Base(g.Ctx.ProtoFileSrc)
|
||||
err = g.protocGenGo(pbPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pbGo := strings.TrimSuffix(protoFileName, ".proto") + ".pb.go"
|
||||
pbFile := filepath.Join(pbPath, pbGo)
|
||||
bts, err := ioutil.ReadFile(pbFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
aspParser := astParser.NewAstParser(bts, targetStruct, g.Ctx.Console)
|
||||
ast, err := aspParser.Parse()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(ast.Service) == 0 {
|
||||
return fmt.Errorf("service not found")
|
||||
}
|
||||
g.ast = ast
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *defaultRpcGenerator) protocGenGo(target string) error {
|
||||
src := filepath.Dir(g.Ctx.ProtoFileSrc)
|
||||
sh := fmt.Sprintf(`protoc -I=%s --go_out=plugins=grpc:%s %s`, src, target, g.Ctx.ProtoFileSrc)
|
||||
stdout, err := execx.Run(sh)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(stdout) > 0 {
|
||||
g.Ctx.Info(stdout)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
100
tools/goctl/rpc/gen/genserver.go
Normal file
100
tools/goctl/rpc/gen/genserver.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package gen
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/rpc/parser"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
)
|
||||
|
||||
const (
|
||||
serverTemplate = `{{.head}}
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
{{.imports}}
|
||||
)
|
||||
|
||||
type {{.types}}
|
||||
|
||||
func New{{.server}}Server(svcCtx *svc.ServiceContext) *{{.server}}Server {
|
||||
return &{{.server}}Server{
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
{{.funcs}}
|
||||
`
|
||||
functionTemplate = `
|
||||
{{if .hasComment}}{{.comment}}{{end}}
|
||||
func (s *{{.server}}Server) {{.method}} (ctx context.Context, in *{{.package}}.{{.request}}) (*{{.package}}.{{.response}}, error) {
|
||||
l := logic.New{{.logicName}}(ctx,s.svcCtx)
|
||||
return l.{{.method}}(in)
|
||||
}
|
||||
`
|
||||
typeFmt = `%sServer struct {
|
||||
svcCtx *svc.ServiceContext
|
||||
}`
|
||||
)
|
||||
|
||||
func (g *defaultRpcGenerator) genHandler() error {
|
||||
serverPath := g.dirM[dirServer]
|
||||
file := g.ast
|
||||
pkg := file.Package
|
||||
pbImport := fmt.Sprintf(`%v "%v"`, pkg, g.mustGetPackage(dirPb))
|
||||
logicImport := fmt.Sprintf(`"%v"`, g.mustGetPackage(dirLogic))
|
||||
svcImport := fmt.Sprintf(`"%v"`, g.mustGetPackage(dirSvc))
|
||||
imports := []string{
|
||||
pbImport,
|
||||
logicImport,
|
||||
svcImport,
|
||||
}
|
||||
head := util.GetHead(g.Ctx.ProtoSource)
|
||||
for _, service := range file.Service {
|
||||
filename := fmt.Sprintf("%vserver.go", service.Name.Lower())
|
||||
serverFile := filepath.Join(serverPath, filename)
|
||||
funcList, err := g.genFunctions(service)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = util.With("server").GoFmt(true).Parse(serverTemplate).SaveTo(map[string]interface{}{
|
||||
"head": head,
|
||||
"types": fmt.Sprintf(typeFmt, service.Name.Title()),
|
||||
"server": service.Name.Title(),
|
||||
"imports": strings.Join(imports, "\n\t"),
|
||||
"funcs": strings.Join(funcList, "\n"),
|
||||
}, serverFile, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *defaultRpcGenerator) genFunctions(service *parser.RpcService) ([]string, error) {
|
||||
file := g.ast
|
||||
pkg := file.Package
|
||||
var functionList []string
|
||||
for _, method := range service.Funcs {
|
||||
buffer, err := util.With("func").Parse(functionTemplate).Execute(map[string]interface{}{
|
||||
"server": service.Name.Title(),
|
||||
"logicName": fmt.Sprintf("%sLogic", method.Name.Title()),
|
||||
"method": method.Name.Title(),
|
||||
"package": pkg,
|
||||
"request": method.InType,
|
||||
"response": method.OutType,
|
||||
"hasComment": len(method.Document),
|
||||
"comment": strings.Join(method.Document, "\n"),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
functionList = append(functionList, buffer.String())
|
||||
}
|
||||
return functionList, nil
|
||||
}
|
||||
31
tools/goctl/rpc/gen/gensvc.go
Normal file
31
tools/goctl/rpc/gen/gensvc.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package gen
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
)
|
||||
|
||||
const svcTemplate = `package svc
|
||||
|
||||
import {{.imports}}
|
||||
|
||||
type ServiceContext struct {
|
||||
c config.Config
|
||||
}
|
||||
|
||||
func NewServiceContext(c config.Config) *ServiceContext {
|
||||
return &ServiceContext{
|
||||
c:c,
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
func (g *defaultRpcGenerator) genSvc() error {
|
||||
svcPath := g.dirM[dirSvc]
|
||||
fileName := filepath.Join(svcPath, fileServiceContext)
|
||||
return util.With("svc").GoFmt(true).Parse(svcTemplate).SaveTo(map[string]interface{}{
|
||||
"imports": fmt.Sprintf(`"%v"`, g.mustGetPackage(dirConfig)),
|
||||
}, fileName, false)
|
||||
}
|
||||
43
tools/goctl/rpc/gen/template.go
Normal file
43
tools/goctl/rpc/gen/template.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package gen
|
||||
|
||||
import (
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/console"
|
||||
)
|
||||
|
||||
const rpcTemplateText = `syntax = "proto3";
|
||||
|
||||
package remote;
|
||||
|
||||
message Request {
|
||||
string username = 1;
|
||||
string password = 2;
|
||||
}
|
||||
|
||||
message Response {
|
||||
string name = 1;
|
||||
string gender = 2;
|
||||
}
|
||||
|
||||
service User {
|
||||
rpc Login(Request) returns(Response);
|
||||
}
|
||||
`
|
||||
|
||||
type rpcTemplate struct {
|
||||
out string
|
||||
console.Console
|
||||
}
|
||||
|
||||
func NewRpcTemplate(out string, idea bool) *rpcTemplate {
|
||||
return &rpcTemplate{
|
||||
out: out,
|
||||
Console: console.NewConsole(idea),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *rpcTemplate) MustGenerate() {
|
||||
err := util.With("t").Parse(rpcTemplateText).SaveTo(nil, r.out, false)
|
||||
r.Must(err)
|
||||
r.Success("Done.")
|
||||
}
|
||||
483
tools/goctl/rpc/parser/pbast.go
Normal file
483
tools/goctl/rpc/parser/pbast.go
Normal file
@@ -0,0 +1,483 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/tal-tech/go-zero/core/lang"
|
||||
sx "github.com/tal-tech/go-zero/core/stringx"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/console"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
|
||||
)
|
||||
|
||||
const (
|
||||
flagStar = "*"
|
||||
suffixServer = "Server"
|
||||
referenceContext = "context."
|
||||
unknownPrefix = "XXX_"
|
||||
ignoreJsonTagExpression = `json:"-"`
|
||||
)
|
||||
|
||||
var (
|
||||
errorParseError = errors.New("pb parse error")
|
||||
typeTemplate = `type (
|
||||
{{.types}}
|
||||
)`
|
||||
structTemplate = `{{if .type}}type {{end}}{{.name}} struct {
|
||||
{{.fields}}
|
||||
}`
|
||||
fieldTemplate = `{{if .hasDoc}}{{.doc}}
|
||||
{{end}}{{.name}} {{.type}} {{.tag}}{{if .hasComment}}{{.comment}}{{end}}`
|
||||
objectM = make(map[string]*Struct)
|
||||
)
|
||||
|
||||
type (
|
||||
astParser struct {
|
||||
golang []byte
|
||||
filterStruct map[string]lang.PlaceholderType
|
||||
console.Console
|
||||
fileSet *token.FileSet
|
||||
}
|
||||
Field struct {
|
||||
Name stringx.String
|
||||
TypeName string
|
||||
JsonTag string
|
||||
Document []string
|
||||
Comment []string
|
||||
}
|
||||
Struct struct {
|
||||
Name stringx.String
|
||||
Document []string
|
||||
Comment []string
|
||||
Field []*Field
|
||||
}
|
||||
Func struct {
|
||||
Name stringx.String
|
||||
InType string
|
||||
InTypeName string // remove *Context,such as LoginRequest、UserRequest
|
||||
OutTypeName string // remove *Context
|
||||
OutType string
|
||||
Document []string
|
||||
}
|
||||
RpcService struct {
|
||||
Name stringx.String
|
||||
Funcs []*Func
|
||||
}
|
||||
// parsing for rpc
|
||||
PbAst struct {
|
||||
Package string
|
||||
// external reference
|
||||
Imports map[string]string
|
||||
Strcuts map[string]*Struct
|
||||
// rpc server's functions,not all functions
|
||||
Service []*RpcService
|
||||
}
|
||||
)
|
||||
|
||||
func NewAstParser(golang []byte, filterStruct map[string]lang.PlaceholderType, log console.Console) *astParser {
|
||||
return &astParser{
|
||||
golang: golang,
|
||||
filterStruct: filterStruct,
|
||||
Console: log,
|
||||
fileSet: token.NewFileSet(),
|
||||
}
|
||||
}
|
||||
func (a *astParser) Parse() (*PbAst, error) {
|
||||
fSet := a.fileSet
|
||||
f, err := parser.ParseFile(fSet, "", a.golang, parser.ParseComments)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
commentMap := ast.NewCommentMap(fSet, f, f.Comments)
|
||||
f.Comments = commentMap.Filter(f).Comments()
|
||||
var pbAst PbAst
|
||||
pbAst.Package = a.mustGetIndentName(f.Name)
|
||||
imports := make(map[string]string)
|
||||
for _, item := range f.Imports {
|
||||
if item == nil {
|
||||
continue
|
||||
}
|
||||
if item.Path == nil {
|
||||
continue
|
||||
}
|
||||
key := a.mustGetIndentName(item.Name)
|
||||
value := item.Path.Value
|
||||
imports[key] = value
|
||||
}
|
||||
structs, funcs := a.mustScope(f.Scope)
|
||||
pbAst.Imports = imports
|
||||
pbAst.Strcuts = structs
|
||||
pbAst.Service = funcs
|
||||
return &pbAst, nil
|
||||
}
|
||||
|
||||
func (a *astParser) mustScope(scope *ast.Scope) (map[string]*Struct, []*RpcService) {
|
||||
if scope == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
objects := scope.Objects
|
||||
structs := make(map[string]*Struct)
|
||||
serviceList := make([]*RpcService, 0)
|
||||
for name, obj := range objects {
|
||||
decl := obj.Decl
|
||||
if decl == nil {
|
||||
continue
|
||||
}
|
||||
typeSpec, ok := decl.(*ast.TypeSpec)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
tp := typeSpec.Type
|
||||
|
||||
switch v := tp.(type) {
|
||||
|
||||
case *ast.StructType:
|
||||
st, err := a.parseObject(name, v)
|
||||
a.Must(err)
|
||||
structs[st.Name.Lower()] = st
|
||||
|
||||
case *ast.InterfaceType:
|
||||
if !strings.HasSuffix(name, suffixServer) {
|
||||
continue
|
||||
}
|
||||
list := a.mustServerFunctions(v)
|
||||
serviceList = append(serviceList, &RpcService{
|
||||
Name: stringx.From(strings.TrimSuffix(name, suffixServer)),
|
||||
Funcs: list,
|
||||
})
|
||||
}
|
||||
}
|
||||
targetStruct := make(map[string]*Struct)
|
||||
for st := range a.filterStruct {
|
||||
lower := strings.ToLower(st)
|
||||
targetStruct[lower] = structs[lower]
|
||||
}
|
||||
return targetStruct, serviceList
|
||||
}
|
||||
|
||||
func (a *astParser) mustServerFunctions(v *ast.InterfaceType) []*Func {
|
||||
funcs := make([]*Func, 0)
|
||||
methodObject := v.Methods
|
||||
if methodObject == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, method := range methodObject.List {
|
||||
var item Func
|
||||
name := a.mustGetIndentName(method.Names[0])
|
||||
doc := a.parseCommentOrDoc(method.Doc)
|
||||
item.Name = stringx.From(name)
|
||||
item.Document = doc
|
||||
types := method.Type
|
||||
if types == nil {
|
||||
funcs = append(funcs, &item)
|
||||
continue
|
||||
}
|
||||
v, ok := types.(*ast.FuncType)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
params := v.Params
|
||||
if params != nil {
|
||||
inList, err := a.parseFields(params.List, true)
|
||||
a.Must(err)
|
||||
|
||||
for _, data := range inList {
|
||||
if strings.HasPrefix(data.TypeName, referenceContext) {
|
||||
continue
|
||||
}
|
||||
// currently,does not support external references
|
||||
item.InTypeName = data.TypeName
|
||||
item.InType = strings.TrimPrefix(data.TypeName, flagStar)
|
||||
break
|
||||
}
|
||||
}
|
||||
results := v.Results
|
||||
if results != nil {
|
||||
outList, err := a.parseFields(results.List, true)
|
||||
a.Must(err)
|
||||
|
||||
for _, data := range outList {
|
||||
if strings.HasPrefix(data.TypeName, referenceContext) {
|
||||
continue
|
||||
}
|
||||
// currently,does not support external references
|
||||
item.OutTypeName = data.TypeName
|
||||
item.OutType = strings.TrimPrefix(data.TypeName, flagStar)
|
||||
break
|
||||
}
|
||||
}
|
||||
funcs = append(funcs, &item)
|
||||
}
|
||||
return funcs
|
||||
}
|
||||
|
||||
func (a *astParser) parseObject(structName string, tp *ast.StructType) (*Struct, error) {
|
||||
if data, ok := objectM[structName]; ok {
|
||||
return data, nil
|
||||
}
|
||||
var st Struct
|
||||
st.Name = stringx.From(structName)
|
||||
if tp == nil {
|
||||
return &st, nil
|
||||
}
|
||||
|
||||
fields := tp.Fields
|
||||
if fields == nil {
|
||||
objectM[structName] = &st
|
||||
return &st, nil
|
||||
}
|
||||
|
||||
fieldList := fields.List
|
||||
members, err := a.parseFields(fieldList, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, m := range members {
|
||||
var field Field
|
||||
field.Name = m.Name
|
||||
field.TypeName = m.TypeName
|
||||
field.JsonTag = m.JsonTag
|
||||
field.Document = m.Document
|
||||
field.Comment = m.Comment
|
||||
st.Field = append(st.Field, &field)
|
||||
}
|
||||
objectM[structName] = &st
|
||||
return &st, nil
|
||||
}
|
||||
|
||||
func (a *astParser) parseFields(fields []*ast.Field, onlyType bool) ([]*Field, error) {
|
||||
ret := make([]*Field, 0)
|
||||
for _, field := range fields {
|
||||
var item Field
|
||||
tag := a.parseTag(field.Tag)
|
||||
if tag == "" && !onlyType {
|
||||
continue
|
||||
}
|
||||
if tag == ignoreJsonTagExpression {
|
||||
continue
|
||||
}
|
||||
|
||||
item.JsonTag = tag
|
||||
name := a.parseName(field.Names)
|
||||
if strings.HasPrefix(name, unknownPrefix) {
|
||||
continue
|
||||
}
|
||||
item.Name = stringx.From(name)
|
||||
typeName, err := a.parseType(field.Type)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
item.TypeName = typeName
|
||||
if onlyType {
|
||||
ret = append(ret, &item)
|
||||
continue
|
||||
}
|
||||
docs := a.parseCommentOrDoc(field.Doc)
|
||||
comments := a.parseCommentOrDoc(field.Comment)
|
||||
|
||||
item.Document = docs
|
||||
item.Comment = comments
|
||||
|
||||
isInline := name == ""
|
||||
if isInline {
|
||||
return nil, a.wrapError(field.Pos(), "unexpected inline type:%s", name)
|
||||
}
|
||||
|
||||
ret = append(ret, &item)
|
||||
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (a *astParser) parseTag(basicLit *ast.BasicLit) string {
|
||||
if basicLit == nil {
|
||||
return ""
|
||||
}
|
||||
value := basicLit.Value
|
||||
splits := strings.Split(value, " ")
|
||||
if len(splits) == 1 {
|
||||
return fmt.Sprintf("`%s`", strings.ReplaceAll(splits[0], "`", ""))
|
||||
} else {
|
||||
return fmt.Sprintf("`%s`", strings.ReplaceAll(splits[1], "`", ""))
|
||||
}
|
||||
}
|
||||
|
||||
// returns
|
||||
// resp1:type's string expression,like int、string、[]int64、map[string]User、*User
|
||||
// resp2:error
|
||||
func (a *astParser) parseType(expr ast.Expr) (string, error) {
|
||||
if expr == nil {
|
||||
return "", errorParseError
|
||||
}
|
||||
|
||||
switch v := expr.(type) {
|
||||
case *ast.StarExpr:
|
||||
stringExpr, err := a.parseType(v.X)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
e := fmt.Sprintf("*%s", stringExpr)
|
||||
return e, nil
|
||||
|
||||
case *ast.Ident:
|
||||
return a.mustGetIndentName(v), nil
|
||||
case *ast.MapType:
|
||||
keyStringExpr, err := a.parseType(v.Key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
valueStringExpr, err := a.parseType(v.Value)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
e := fmt.Sprintf("map[%s]%s", keyStringExpr, valueStringExpr)
|
||||
return e, nil
|
||||
case *ast.ArrayType:
|
||||
stringExpr, err := a.parseType(v.Elt)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
e := fmt.Sprintf("[]%s", stringExpr)
|
||||
return e, nil
|
||||
case *ast.InterfaceType:
|
||||
return "interface{}", nil
|
||||
case *ast.SelectorExpr:
|
||||
join := make([]string, 0)
|
||||
xIdent, ok := v.X.(*ast.Ident)
|
||||
xIndentName := a.mustGetIndentName(xIdent)
|
||||
if ok {
|
||||
join = append(join, xIndentName)
|
||||
}
|
||||
sel := v.Sel
|
||||
join = append(join, a.mustGetIndentName(sel))
|
||||
return strings.Join(join, "."), nil
|
||||
case *ast.ChanType:
|
||||
return "", a.wrapError(v.Pos(), "unexpected type 'chan'")
|
||||
case *ast.FuncType:
|
||||
return "", a.wrapError(v.Pos(), "unexpected type 'func'")
|
||||
case *ast.StructType:
|
||||
return "", a.wrapError(v.Pos(), "unexpected inline struct type")
|
||||
default:
|
||||
return "", a.wrapError(v.Pos(), "unexpected type '%v'", v)
|
||||
}
|
||||
}
|
||||
func (a *astParser) parseName(names []*ast.Ident) string {
|
||||
if len(names) == 0 {
|
||||
return ""
|
||||
}
|
||||
name := names[0]
|
||||
return a.mustGetIndentName(name)
|
||||
}
|
||||
|
||||
func (a *astParser) parseCommentOrDoc(cg *ast.CommentGroup) []string {
|
||||
if cg == nil {
|
||||
return nil
|
||||
}
|
||||
comments := make([]string, 0)
|
||||
for _, comment := range cg.List {
|
||||
if comment == nil {
|
||||
continue
|
||||
}
|
||||
text := strings.TrimSpace(comment.Text)
|
||||
if text == "" {
|
||||
continue
|
||||
}
|
||||
comments = append(comments, text)
|
||||
}
|
||||
return comments
|
||||
}
|
||||
|
||||
func (a *astParser) mustGetIndentName(ident *ast.Ident) string {
|
||||
if ident == nil {
|
||||
return ""
|
||||
}
|
||||
return ident.Name
|
||||
}
|
||||
|
||||
func (a *astParser) wrapError(pos token.Pos, format string, arg ...interface{}) error {
|
||||
file := a.fileSet.Position(pos)
|
||||
return fmt.Errorf("line %v: %s", file.Line, fmt.Sprintf(format, arg...))
|
||||
}
|
||||
|
||||
func (a *PbAst) GenTypesCode() (string, error) {
|
||||
types := make([]string, 0)
|
||||
sts := make([]*Struct, 0)
|
||||
for _, item := range a.Strcuts {
|
||||
sts = append(sts, item)
|
||||
}
|
||||
sort.Slice(sts, func(i, j int) bool {
|
||||
return sts[i].Name.Source() < sts[j].Name.Source()
|
||||
})
|
||||
for _, s := range sts {
|
||||
structCode, err := s.genCode(false)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if structCode == "" {
|
||||
continue
|
||||
}
|
||||
types = append(types, structCode)
|
||||
}
|
||||
buffer, err := util.With("type").Parse(typeTemplate).Execute(map[string]interface{}{
|
||||
"types": strings.Join(types, "\n\n"),
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return buffer.String(), nil
|
||||
}
|
||||
|
||||
func (s *Struct) genCode(containsTypeStatement bool) (string, error) {
|
||||
if len(s.Field) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
fields := make([]string, 0)
|
||||
for _, f := range s.Field {
|
||||
var comment, doc string
|
||||
if len(f.Comment) > 0 {
|
||||
comment = f.Comment[0]
|
||||
}
|
||||
doc = strings.Join(f.Document, "\n")
|
||||
buffer, err := util.With(sx.Rand()).Parse(fieldTemplate).Execute(map[string]interface{}{
|
||||
"name": f.Name.Title(),
|
||||
"type": f.TypeName,
|
||||
"tag": f.JsonTag,
|
||||
"hasDoc": len(f.Document) > 0,
|
||||
"doc": doc,
|
||||
"hasComment": len(f.Comment) > 0,
|
||||
"comment": comment,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
fields = append(fields, buffer.String())
|
||||
}
|
||||
buffer, err := util.With("struct").Parse(structTemplate).Execute(map[string]interface{}{
|
||||
"type": containsTypeStatement,
|
||||
"name": s.Name.Title(),
|
||||
"fields": strings.Join(fields, "\n"),
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return buffer.String(), nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user