Compare commits

..

50 Commits

Author SHA1 Message Date
kingxt
33eb2936e8 fix: root path on windows bug (#34)
* rebase upstream

* rebase

* trim no need line

* trim no need line

* trim no need line

* fix bug on windows

Co-authored-by: kingxt <dream4kingxt@163.com>
2020-09-02 15:14:27 +08:00
kevin
b7a018b33a add shorturl example code 2020-09-01 16:04:39 +08:00
kevin
ea1c9aa250 support go 1.13 2020-09-01 15:04:01 +08:00
kevin
fbad810cd1 update shorturl doc 2020-09-01 13:46:05 +08:00
kevin
6b15475ccd update shorturl doc 2020-09-01 13:38:16 +08:00
kingxt
5c0c3ea467 trim space (#31)
* rebase upstream

* rebase

* trim no need line

* trim no need line

* trim no need line

* trim space

Co-authored-by: kingxt <dream4kingxt@163.com>
2020-09-01 11:46:10 +08:00
kingxt
89f3712347 remove no need empty line (#29)
* rebase upstream

* rebase

* trim no need line

* trim no need line

* trim no need line

* trim no need line

Co-authored-by: kingxt <dream4kingxt@163.com>
2020-09-01 11:08:19 +08:00
kevin
af7acdd843 fix doc errors 2020-09-01 10:26:16 +08:00
kevin
7ffa3349a9 update readme 2020-08-31 22:45:55 +08:00
kevin
f03862c378 update docs 2020-08-31 22:37:43 +08:00
kevin
fe3e70a60f update shorturl doc 2020-08-31 20:52:29 +08:00
kevin
36174ba5cc make svcCtx as a member for better code generation 2020-08-31 12:32:13 +08:00
kevin
7b17b3604a fix dockerfile generation 2020-08-31 12:27:38 +08:00
kevin
eb40c2731d remove files 2020-08-30 23:56:51 +08:00
kevin
618bec5075 remove makefile generation 2020-08-30 23:52:51 +08:00
kevin
5821b7324e update readme 2020-08-30 23:34:57 +08:00
kevin
befdaab542 update goctl makefile 2020-08-30 18:22:46 +08:00
kevin
431be8ed9d make goctl work on linux 2020-08-30 16:37:34 +08:00
kevin
3c688c319e update shorturl doc 2020-08-29 23:42:42 +08:00
kevin
59ffa75c00 fix typo in doc 2020-08-29 23:33:34 +08:00
kevin
09340e82a7 fix doc error 2020-08-29 22:51:48 +08:00
kevin
6c4a4be5d2 update shorturl doc 2020-08-29 20:27:52 +08:00
kevin
6e3d99e869 reorg imports 2020-08-29 14:31:51 +08:00
Keson
0f97b2019a rpc generation support windows (#28)
* add execute files

* add protoc-osx

* add rpc generation

* add rpc generation

* add: rpc template generation

* update usage

* fixed env prepare for project in go path

* optimize gomod cache

* add README.md

* format error

* reactor templatex.go

* remove waste code

* update project.go & README.md

* update project.go & README.md

* rpc generation supports windows
2020-08-29 14:30:17 +08:00
kevin
0cf4ed46a1 update shorturl doc 2020-08-29 00:36:36 +08:00
kevin
3affe62ae4 update shorturl doc 2020-08-29 00:28:57 +08:00
Keson
0734bbcab3 update handler generation (#27)
* add execute files

* add protoc-osx

* add rpc generation

* add rpc generation

* add: rpc template generation

* update usage

* fixed env prepare for project in go path

* optimize gomod cache

* add README.md

* format error

* reactor templatex.go

* remove waste code

* update project.go & README.md

* update project.go & README.md
2020-08-29 00:15:15 +08:00
kevin
f411178a4f refine rpc generator 2020-08-28 22:44:41 +08:00
kevin
72132ce399 refine goctl rpc generator 2020-08-28 21:22:35 +08:00
Keson
db16115037 rpc service generation (#26)
* add execute files

* add protoc-osx

* add rpc generation

* add rpc generation

* add: rpc template generation

* update usage

* fixed env prepare for project in go path

* optimize gomod cache

* add README.md

* format error

* reactor templatex.go

* remove waste code
2020-08-28 19:24:58 +08:00
kevin
71bbf91a63 update shorturl doc 2020-08-27 23:29:56 +08:00
kevin
69ccc61cfe update shorturl doc 2020-08-27 23:16:07 +08:00
kevin
a94cf653f0 better image rendering 2020-08-27 23:00:40 +08:00
kevin
77e23ad65d add quick example 2020-08-27 22:54:18 +08:00
kingxt
38806e7237 fix config yaml gen (#25)
* optimized

* format

Co-authored-by: kingxt <dream4kingxt@163.com>
2020-08-27 15:23:19 +08:00
kevin
a987d12237 sort imports on api generation 2020-08-27 14:40:05 +08:00
kevin
33208e6ef6 return zero value instead of nil on generated logic 2020-08-27 13:49:31 +08:00
kevin
5d8a3c07cd disable cpu stat in wsl linux 2020-08-27 13:22:44 +08:00
kevin
1c24e71568 use yaml, and detect go.mod in current dir 2020-08-27 11:44:35 +08:00
kevin
229544f3ca move test code into internal package 2020-08-26 15:18:45 +08:00
kevin
c575fa7f95 fix ci script 2020-08-26 14:59:04 +08:00
kevin
fe2252184a update ci configuration 2020-08-26 14:53:12 +08:00
kevin
1a8014c704 add more tests 2020-08-26 14:32:35 +08:00
kevin
30e52707ae add more tests 2020-08-26 14:19:16 +08:00
kingxt
73b61e09ed fix format (#23)
* fir format

* fix bug

Co-authored-by: kingxt <dream4kingxt@163.com>
2020-08-26 11:32:55 +08:00
kevin
9b8595a85e add more tests 2020-08-25 22:42:42 +08:00
kevin
015e284515 add more tests 2020-08-25 20:21:59 +08:00
kevin
456b395860 use predefined endpoint separator 2020-08-25 18:36:30 +08:00
kevin
f3c367a323 add fatal to stderr 2020-08-25 16:59:14 +08:00
kevin
a32028c4fb add etcd deploy yaml 2020-08-25 16:32:01 +08:00
142 changed files with 4813 additions and 971 deletions

View File

@@ -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=)'

View File

@@ -1,4 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: discovery
name: discov

View File

@@ -1,16 +1,16 @@
apiVersion: v1
kind: Service
metadata:
name: discov
namespace: discovery
name: etcd
namespace: discov
spec:
ports:
- name: discov-port
- name: etcd-port
port: 2379
protocol: TCP
targetPort: 2379
selector:
app: discov
app: etcd
---
@@ -18,30 +18,30 @@ apiVersion: v1
kind: Pod
metadata:
labels:
app: discov
discov_node: discov0
name: discov0
namespace: discovery
app: etcd
etcd_node: etcd0
name: etcd0
namespace: discov
spec:
containers:
- command:
- /usr/local/bin/etcd
- --name
- discov0
- etcd0
- --initial-advertise-peer-urls
- http://discov0:2380
- http://etcd0: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
- http://etcd0:2379
- --initial-cluster
- discov0=http://discov0:2380,discov1=http://discov1:2380,discov2=http://discov2:2380,discov3=http://discov3:2380,discov4=http://discov4:2380
- etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380,etcd4=http://etcd4:2380
- --initial-cluster-state
- new
image: registry-vpc.cn-hangzhou.aliyuncs.com/xapp/etcd:latest
name: discov0
image: quay.io/coreos/etcd:latest
name: etcd0
ports:
- containerPort: 2379
name: client
@@ -49,8 +49,6 @@ spec:
- containerPort: 2380
name: server
protocol: TCP
imagePullSecrets:
- name: aliyun
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
@@ -59,7 +57,7 @@ spec:
- key: app
operator: In
values:
- discov
- etcd
topologyKey: "kubernetes.io/hostname"
restartPolicy: Always
@@ -69,9 +67,9 @@ apiVersion: v1
kind: Service
metadata:
labels:
discov_node: discov0
name: discov0
namespace: discovery
etcd_node: etcd0
name: etcd0
namespace: discov
spec:
ports:
- name: client
@@ -83,7 +81,7 @@ spec:
protocol: TCP
targetPort: 2380
selector:
discov_node: discov0
etcd_node: etcd0
---
@@ -91,30 +89,30 @@ apiVersion: v1
kind: Pod
metadata:
labels:
app: discov
discov_node: discov1
name: discov1
namespace: discovery
app: etcd
etcd_node: etcd1
name: etcd1
namespace: discov
spec:
containers:
- command:
- /usr/local/bin/etcd
- --name
- discov1
- etcd1
- --initial-advertise-peer-urls
- http://discov1:2380
- http://etcd1: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
- http://etcd1:2379
- --initial-cluster
- discov0=http://discov0:2380,discov1=http://discov1:2380,discov2=http://discov2:2380,discov3=http://discov3:2380,discov4=http://discov4:2380
- etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380,etcd4=http://etcd4:2380
- --initial-cluster-state
- new
image: registry-vpc.cn-hangzhou.aliyuncs.com/xapp/etcd:latest
name: discov1
image: quay.io/coreos/etcd:latest
name: etcd1
ports:
- containerPort: 2379
name: client
@@ -122,8 +120,6 @@ spec:
- containerPort: 2380
name: server
protocol: TCP
imagePullSecrets:
- name: aliyun
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
@@ -132,7 +128,7 @@ spec:
- key: app
operator: In
values:
- discov
- etcd
topologyKey: "kubernetes.io/hostname"
restartPolicy: Always
@@ -142,9 +138,9 @@ apiVersion: v1
kind: Service
metadata:
labels:
discov_node: discov1
name: discov1
namespace: discovery
etcd_node: etcd1
name: etcd1
namespace: discov
spec:
ports:
- name: client
@@ -156,7 +152,7 @@ spec:
protocol: TCP
targetPort: 2380
selector:
discov_node: discov1
etcd_node: etcd1
---
@@ -164,30 +160,30 @@ apiVersion: v1
kind: Pod
metadata:
labels:
app: discov
discov_node: discov2
name: discov2
namespace: discovery
app: etcd
etcd_node: etcd2
name: etcd2
namespace: discov
spec:
containers:
- command:
- /usr/local/bin/etcd
- --name
- discov2
- etcd2
- --initial-advertise-peer-urls
- http://discov2:2380
- http://etcd2: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
- http://etcd2:2379
- --initial-cluster
- discov0=http://discov0:2380,discov1=http://discov1:2380,discov2=http://discov2:2380,discov3=http://discov3:2380,discov4=http://discov4:2380
- etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380,etcd4=http://etcd4:2380
- --initial-cluster-state
- new
image: registry-vpc.cn-hangzhou.aliyuncs.com/xapp/etcd:latest
name: discov2
image: quay.io/coreos/etcd:latest
name: etcd2
ports:
- containerPort: 2379
name: client
@@ -195,8 +191,6 @@ spec:
- containerPort: 2380
name: server
protocol: TCP
imagePullSecrets:
- name: aliyun
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
@@ -205,7 +199,7 @@ spec:
- key: app
operator: In
values:
- discov
- etcd
topologyKey: "kubernetes.io/hostname"
restartPolicy: Always
@@ -215,9 +209,9 @@ apiVersion: v1
kind: Service
metadata:
labels:
discov_node: discov2
name: discov2
namespace: discovery
etcd_node: etcd2
name: etcd2
namespace: discov
spec:
ports:
- name: client
@@ -229,7 +223,7 @@ spec:
protocol: TCP
targetPort: 2380
selector:
discov_node: discov2
etcd_node: etcd2
---
@@ -237,30 +231,30 @@ apiVersion: v1
kind: Pod
metadata:
labels:
app: discov
discov_node: discov3
name: discov3
namespace: discovery
app: etcd
etcd_node: etcd3
name: etcd3
namespace: discov
spec:
containers:
- command:
- /usr/local/bin/etcd
- --name
- discov3
- etcd3
- --initial-advertise-peer-urls
- http://discov3:2380
- http://etcd3: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
- http://etcd3:2379
- --initial-cluster
- discov0=http://discov0:2380,discov1=http://discov1:2380,discov2=http://discov2:2380,discov3=http://discov3:2380,discov4=http://discov4:2380
- etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380,etcd4=http://etcd4:2380
- --initial-cluster-state
- new
image: registry-vpc.cn-hangzhou.aliyuncs.com/xapp/etcd:latest
name: discov3
image: quay.io/coreos/etcd:latest
name: etcd3
ports:
- containerPort: 2379
name: client
@@ -268,8 +262,6 @@ spec:
- containerPort: 2380
name: server
protocol: TCP
imagePullSecrets:
- name: aliyun
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
@@ -278,7 +270,7 @@ spec:
- key: app
operator: In
values:
- discov
- etcd
topologyKey: "kubernetes.io/hostname"
restartPolicy: Always
@@ -288,9 +280,9 @@ apiVersion: v1
kind: Service
metadata:
labels:
discov_node: discov3
name: discov3
namespace: discovery
etcd_node: etcd3
name: etcd3
namespace: discov
spec:
ports:
- name: client
@@ -302,7 +294,7 @@ spec:
protocol: TCP
targetPort: 2380
selector:
discov_node: discov3
etcd_node: etcd3
---
@@ -310,30 +302,30 @@ apiVersion: v1
kind: Pod
metadata:
labels:
app: discov
discov_node: discov4
name: discov4
namespace: discovery
app: etcd
etcd_node: etcd4
name: etcd4
namespace: discov
spec:
containers:
- command:
- /usr/local/bin/etcd
- --name
- discov4
- etcd4
- --initial-advertise-peer-urls
- http://discov4:2380
- http://etcd4: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
- http://etcd4:2379
- --initial-cluster
- discov0=http://discov0:2380,discov1=http://discov1:2380,discov2=http://discov2:2380,discov3=http://discov3:2380,discov4=http://discov4:2380
- etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380,etcd4=http://etcd4:2380
- --initial-cluster-state
- new
image: registry-vpc.cn-hangzhou.aliyuncs.com/xapp/etcd:latest
name: discov4
image: quay.io/coreos/etcd:latest
name: etcd4
ports:
- containerPort: 2379
name: client
@@ -341,8 +333,6 @@ spec:
- containerPort: 2380
name: server
protocol: TCP
imagePullSecrets:
- name: aliyun
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
@@ -351,7 +341,7 @@ spec:
- key: app
operator: In
values:
- discov
- etcd
topologyKey: "kubernetes.io/hostname"
restartPolicy: Always
@@ -361,9 +351,9 @@ apiVersion: v1
kind: Service
metadata:
labels:
discov_node: discov4
name: discov4
namespace: discovery
etcd_node: etcd4
name: etcd4
namespace: discov
spec:
ports:
- name: client
@@ -375,4 +365,4 @@ spec:
protocol: TCP
targetPort: 2380
selector:
discov_node: discov4
etcd_node: etcd4

View File

@@ -213,6 +213,7 @@ func Infof(format string, v ...interface{}) {
func Must(err error) {
if err != nil {
msg := formatWithCaller(err.Error(), 3)
log.Print(msg)
output(severeLog, levelFatal, msg)
os.Exit(1)
}

View File

@@ -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-api.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

BIN
doc/images/shorturl-rpc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

528
doc/shorturl.md Normal file
View File

@@ -0,0 +1,528 @@
# 快速构建高并发微服务
## 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. goctl各层代码生成一览
所有绿色背景的功能模块是自动生成的,按需激活,红色模块是需要自己写的,也就是增加下依赖,编写业务特有逻辑,各层示意图分别如下:
* API Gateway
<img src="images/shorturl-api.png" alt="api" width="800" />
* RPC
<img src="images/shorturl-rpc.png" alt="架构图" width="800" />
* model
<img src="images/shorturl-model.png" alt="model" width="800" />
下面我们来一起完整走一遍快速构建微服务的流程Lets `Go`!🏃‍♂️
## 4. 准备工作
* 安装etcd, mysql, redis
* 安装goctl工具
```shell
GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get github.com/tal-tech/go-zero/tools/goctl
```
* 创建工作目录`shorturl`
* 在`shorturl`目录下执行`go mod init shorturl`初始化`go.mod`
## 5. 编写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生成客户端代码给客户端同学并行开发了支持多种语言详见文档
## 6. 编写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`文件里可以修改侦听端口等配置
## 7. 修改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
## 8. 定义数据库表结构并生成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 // 定义常量和变量
```
## 9. 修改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
// 手动代码结束
}
```
至此代码修改完成,凡事手动修改的代码我加了标注
## 10. 完整调用演示
* 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"}
```
## 11. Benchmark
因为写入依赖于mysql的写入速度就相当于压mysql了所以压测只测试了expand接口相当于从mysql里读取并利用缓存shorten.lua里随机从db里获取了100个热key来生成压测请求
![Benchmark](images/shorturl-benchmark.png)
可以看出在我的MacBook Pro上能达到3万+的qps。
## 12. 总结
我们一直强调**工具大于约定和文档**。
go-zero不只是一个框架更是一个建立在框架+工具基础上的,简化和规范了整个微服务构建的技术体系。
我们在保持简单的同时也尽可能把微服务治理的复杂度封装到了框架内部,极大的降低了开发人员的心智负担,使得业务开发得以快速推进。
通过go-zero+goctl生成的代码包含了微服务治理的各种组件包括并发控制、自适应熔断、自适应降载、自动缓存控制等可以轻松部署以承载巨大访问量。
有任何好的提升工程效率的想法,随时欢迎交流!👏

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,3 @@
Name: shorturl-api
Host: 0.0.0.0
Port: 8888

View File

@@ -0,0 +1,7 @@
package config
import "github.com/tal-tech/go-zero/rest"
type Config struct {
rest.RestConf
}

View File

@@ -0,0 +1,29 @@
package handler
import (
"net/http"
"shorturl/api/internal/logic"
"shorturl/api/internal/svc"
"shorturl/api/internal/types"
"github.com/tal-tech/go-zero/rest/httpx"
)
func expandHandler(ctx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.ExpandReq
if err := httpx.Parse(r, &req); err != nil {
httpx.Error(w, err)
return
}
l := logic.NewExpandLogic(r.Context(), ctx)
resp, err := l.Expand(req)
if err != nil {
httpx.Error(w, err)
} else {
httpx.WriteJson(w, http.StatusOK, resp)
}
}
}

View File

@@ -0,0 +1,25 @@
// DO NOT EDIT, generated by goctl
package handler
import (
"net/http"
"shorturl/api/internal/svc"
"github.com/tal-tech/go-zero/rest"
)
func RegisterHandlers(engine *rest.Server, serverCtx *svc.ServiceContext) {
engine.AddRoutes([]rest.Route{
{
Method: http.MethodGet,
Path: "/shorten",
Handler: shortenHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/expand",
Handler: expandHandler(serverCtx),
},
})
}

View File

@@ -0,0 +1,29 @@
package handler
import (
"net/http"
"shorturl/api/internal/logic"
"shorturl/api/internal/svc"
"shorturl/api/internal/types"
"github.com/tal-tech/go-zero/rest/httpx"
)
func shortenHandler(ctx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.ShortenReq
if err := httpx.Parse(r, &req); err != nil {
httpx.Error(w, err)
return
}
l := logic.NewShortenLogic(r.Context(), ctx)
resp, err := l.Shorten(req)
if err != nil {
httpx.Error(w, err)
} else {
httpx.WriteJson(w, http.StatusOK, resp)
}
}
}

View File

@@ -0,0 +1,26 @@
package logic
import (
"context"
"shorturl/api/internal/svc"
"shorturl/api/internal/types"
"github.com/tal-tech/go-zero/core/logx"
)
type ExpandLogic struct {
ctx context.Context
logx.Logger
}
func NewExpandLogic(ctx context.Context, svcCtx *svc.ServiceContext) ExpandLogic {
return ExpandLogic{
ctx: ctx,
Logger: logx.WithContext(ctx),
}
}
func (l *ExpandLogic) Expand(req types.ExpandReq) (*types.ExpandResp, error) {
return &types.ExpandResp{}, nil
}

View File

@@ -0,0 +1,26 @@
package logic
import (
"context"
"shorturl/api/internal/svc"
"shorturl/api/internal/types"
"github.com/tal-tech/go-zero/core/logx"
)
type ShortenLogic struct {
ctx context.Context
logx.Logger
}
func NewShortenLogic(ctx context.Context, svcCtx *svc.ServiceContext) ShortenLogic {
return ShortenLogic{
ctx: ctx,
Logger: logx.WithContext(ctx),
}
}
func (l *ShortenLogic) Shorten(req types.ShortenReq) (*types.ShortenResp, error) {
return &types.ShortenResp{}, nil
}

View File

@@ -0,0 +1,11 @@
package svc
import "shorturl/api/internal/config"
type ServiceContext struct {
Config config.Config
}
func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{Config: c}
}

View File

@@ -0,0 +1,18 @@
// DO NOT EDIT, generated by goctl
package types
type ExpandReq struct {
Shorten string `form:"shorten"`
}
type ExpandResp struct {
Url string `json:"url"`
}
type ShortenReq struct {
Url string `form:"url"`
}
type ShortenResp struct {
Shorten string `json:"shorten"`
}

View File

@@ -0,0 +1,33 @@
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)
}

View File

@@ -0,0 +1,28 @@
package main
import (
"flag"
"shorturl/api/internal/config"
"shorturl/api/internal/handler"
"shorturl/api/internal/svc"
"github.com/tal-tech/go-zero/core/conf"
"github.com/tal-tech/go-zero/rest"
)
var configFile = flag.String("f", "etc/shorturl-api.yaml", "the config file")
func main() {
flag.Parse()
var c config.Config
conf.MustLoad(*configFile, &c)
ctx := svc.NewServiceContext(c)
server := rest.MustNewServer(c.RestConf)
defer server.Stop()
handler.RegisterHandlers(server, ctx)
server.Start()
}

11
example/shorturl/go.mod Normal file
View File

@@ -0,0 +1,11 @@
module shorturl
go 1.15
require (
github.com/golang/mock v1.4.3
github.com/golang/protobuf v1.4.2
github.com/tal-tech/go-zero v1.0.7
golang.org/x/net v0.0.0-20200707034311-ab3426394381
google.golang.org/grpc v1.29.1
)

360
example/shorturl/go.sum Normal file
View File

@@ -0,0 +1,360 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/DATA-DOG/go-sqlmock v1.4.1/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/StackExchange/wmi v0.0.0-20170410192909-ea383cf3ba6e/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk=
github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/coreos/go-semver v0.2.0 h1:3Jm3tLmsgAYcjC+4Up7hJrFBPr+n7rAqYeSw/SZazuY=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd/v22 v22.0.0 h1:XJIw/+VlJ+87J+doOxznsAWIdmWuViOVhkQamW5YV28=
github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
github.com/go-redis/redis v6.15.7+incompatible h1:3skhDh95XQMpnqeqNftPkQD9jL9e5e36z/1SUm6dy1U=
github.com/go-redis/redis v6.15.7+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-xorm/builder v0.3.4 h1:FxkeGB4Cggdw3tPwutLCpfjng2jugfkg6LDMrd/KsoY=
github.com/go-xorm/builder v0.3.4/go.mod h1:KxkQkNN1DpPKTedxXyTQcmH+rXfvk4LZ9SOOBoZBAxw=
github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:56xuuqnHyryaerycW3BfssRdxQstACi0Epw/yC5E2xM=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gops v0.3.7/go.mod h1:bj0cwMmX1X4XIJFTjR99R5sCxNssNJ8HebFNvoQlmgY=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.14.3/go.mod h1:6CwZWGDSPRJidgKAtJVvND6soZe6fT7iteq8wDPdhb0=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334 h1:VHgatEHNcBFEB7inlalqfNqw65aNkM1lGX2yt3NmbS8=
github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/justinas/alice v1.2.0 h1:+MHSA/vccVCF4Uq37S42jwlkvI2Xzl7zTPCN5BnZNVo=
github.com/justinas/alice v1.2.0/go.mod h1:fN5HRH/reO/zrUflLfTN43t3vXvKzvZIENsNEe7i7qA=
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
github.com/keybase/go-ps v0.0.0-20161005175911-668c8856d999/go.mod h1:hY+WOq6m2FpbvyrI93sMaypsttvaIL5nhVR92dTMUcQ=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kshvakov/clickhouse v1.3.11/go.mod h1:/SVBAcqF3u7rxQ9sTWCZwf8jzzvxiZGeQvtmSF2BBEc=
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pierrec/lz4 v2.5.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.5.1 h1:bdHYieyGlH+6OLEk2YQha8THib30KP0/yD0YH9m6xcA=
github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.9.1 h1:KOMtN28tlbam3/7ZKEYKHhKoJZYYj3gMH4uc62x7X7U=
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shirou/gopsutil v0.0.0-20180427012116-c95755e4bcd7/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/tal-tech/go-zero v1.0.7 h1:OrXl+1jRwnB6FGvhylmoFBRxkhyCw9h5Ob395PsGa8Y=
github.com/tal-tech/go-zero v1.0.7/go.mod h1:/e0i8rMFzFO6Lha+UG9/nkzLSvv5dyYCCN+TFP1JcB0=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA=
github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg=
github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2 h1:zzrxE1FKn5ryBNl9eKOeqQ58Y/Qpo3Q9QNxKHX5uzzQ=
github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2/go.mod h1:hzfGeIUDq/j97IG+FhNqkowIyEcD88LrW6fyU3K3WqY=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
go.etcd.io/etcd v0.0.0-20200402134248-51bdeb39e698 h1:jWtjCJX1qxhHISBMLRztWwR+EXkI7MJAF2HjHAE/x/I=
go.etcd.io/etcd v0.0.0-20200402134248-51bdeb39e698/go.mod h1:YoUyTScD3Vcv2RBm3eGVOq7i1ULiz3OuXoQFWOirmAM=
go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/automaxprocs v1.3.0 h1:II28aZoGdaglS5vVNnspf28lnZpXScxtIozx1lAjdb0=
go.uber.org/automaxprocs v1.3.0/go.mod h1:9CWT6lKIep8U41DDaPiH6eFscnTyjfTANNQNx6LrIcA=
go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.14.1 h1:nYDKopTbvAPq/NrUVZwT15y2lpROBiLLyoRTbXOYWOo=
go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20171017063910-8dbc5d05d6ed/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200410132612-ae9902aceb98/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f h1:ohwtWcCwB/fZUxh/vjazHorYmBnua3NmY3CAjwC7mEA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/goversion v1.0.0/go.mod h1:Eih9y/uIBS3ulggl7KNJ09xGSLcuNaLgmvvqa07sgfo=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=

View File

@@ -0,0 +1,12 @@
Name: transform.rpc
Log:
Mode: console
ListenOn: 127.0.0.1:8081
Etcd:
Hosts:
- 127.0.0.1:2379
Key: transform.rpc
DataSource: root:@tcp(localhost:3306)/gozero
Table: shorturl
Cache:
- Host: localhost:6379

View File

@@ -0,0 +1,13 @@
package config
import (
"github.com/tal-tech/go-zero/core/stores/cache"
"github.com/tal-tech/go-zero/rpcx"
)
type Config struct {
rpcx.RpcServerConf
DataSource string
Table string
Cache cache.CacheConf
}

View File

@@ -0,0 +1,35 @@
package logic
import (
"context"
"shorturl/rpc/transform/internal/svc"
transform "shorturl/rpc/transform/pb"
"github.com/tal-tech/go-zero/core/logx"
)
type ExpandLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewExpandLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ExpandLogic {
return &ExpandLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
func (l *ExpandLogic) Expand(in *transform.ExpandReq) (*transform.ExpandResp, error) {
res, err := l.svcCtx.Model.FindOne(in.Shorten)
if err != nil {
return nil, err
}
return &transform.ExpandResp{
Url: res.Url,
}, nil
}

View File

@@ -0,0 +1,41 @@
package logic
import (
"context"
"shorturl/rpc/transform/internal/svc"
"shorturl/rpc/transform/model"
transform "shorturl/rpc/transform/pb"
"github.com/tal-tech/go-zero/core/hash"
"github.com/tal-tech/go-zero/core/logx"
)
type ShortenLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewShortenLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ShortenLogic {
return &ShortenLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
func (l *ShortenLogic) Shorten(in *transform.ShortenReq) (*transform.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
}

View File

@@ -0,0 +1,32 @@
// Code generated by goctl. DO NOT EDIT!
// Source: transform.proto
package server
import (
"context"
"shorturl/rpc/transform/internal/logic"
"shorturl/rpc/transform/internal/svc"
transform "shorturl/rpc/transform/pb"
)
type TransformerServer struct {
svcCtx *svc.ServiceContext
}
func NewTransformerServer(svcCtx *svc.ServiceContext) *TransformerServer {
return &TransformerServer{
svcCtx: svcCtx,
}
}
func (s *TransformerServer) Expand(ctx context.Context, in *transform.ExpandReq) (*transform.ExpandResp, error) {
l := logic.NewExpandLogic(ctx, s.svcCtx)
return l.Expand(in)
}
func (s *TransformerServer) Shorten(ctx context.Context, in *transform.ShortenReq) (*transform.ShortenResp, error) {
l := logic.NewShortenLogic(ctx, s.svcCtx)
return l.Shorten(in)
}

View File

@@ -0,0 +1,20 @@
package svc
import (
"shorturl/rpc/transform/internal/config"
"shorturl/rpc/transform/model"
"github.com/tal-tech/go-zero/core/stores/sqlx"
)
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),
}
}

View File

@@ -0,0 +1,6 @@
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;

View File

@@ -0,0 +1,86 @@
package model
import (
"database/sql"
"fmt"
"strings"
"github.com/tal-tech/go-zero/core/stores/cache"
"github.com/tal-tech/go-zero/core/stores/sqlc"
"github.com/tal-tech/go-zero/core/stores/sqlx"
"github.com/tal-tech/go-zero/core/stringx"
"github.com/tal-tech/go-zero/tools/goctl/model/sql/builderx"
)
var (
shorturlFieldNames = builderx.FieldNames(&Shorturl{})
shorturlRows = strings.Join(shorturlFieldNames, ",")
shorturlRowsExpectAutoSet = strings.Join(stringx.Remove(shorturlFieldNames, "create_time", "update_time"), ",")
shorturlRowsWithPlaceHolder = strings.Join(stringx.Remove(shorturlFieldNames, "shorten", "create_time", "update_time"), "=?,") + "=?"
cacheShorturlShortenPrefix = "cache#Shorturl#shorten#"
)
type (
ShorturlModel struct {
sqlc.CachedConn
table string
}
Shorturl struct {
Shorten string `db:"shorten"` // shorten key
Url string `db:"url"` // original url
}
)
func NewShorturlModel(conn sqlx.SqlConn, c cache.CacheConf, table string) *ShorturlModel {
return &ShorturlModel{
CachedConn: sqlc.NewConn(conn, c),
table: table,
}
}
func (m *ShorturlModel) Insert(data Shorturl) (sql.Result, error) {
query := `insert into ` + m.table + ` (` + shorturlRowsExpectAutoSet + `) values (?, ?)`
return m.ExecNoCache(query, data.Shorten, data.Url)
}
func (m *ShorturlModel) FindOne(shorten string) (*Shorturl, error) {
shorturlShortenKey := fmt.Sprintf("%s%v", cacheShorturlShortenPrefix, shorten)
var resp Shorturl
err := m.QueryRow(&resp, shorturlShortenKey, func(conn sqlx.SqlConn, v interface{}) error {
query := `select ` + shorturlRows + ` from ` + m.table + ` where shorten = ? limit 1`
return conn.QueryRow(v, query, shorten)
})
switch err {
case nil:
return &resp, nil
case sqlc.ErrNotFound:
return nil, ErrNotFound
default:
return nil, err
}
}
func (m *ShorturlModel) Update(data Shorturl) error {
shorturlShortenKey := fmt.Sprintf("%s%v", cacheShorturlShortenPrefix, data.Shorten)
_, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
query := `update ` + m.table + ` set ` + shorturlRowsWithPlaceHolder + ` where shorten = ?`
return conn.Exec(query, data.Url, data.Shorten)
}, shorturlShortenKey)
return err
}
func (m *ShorturlModel) Delete(shorten string) error {
_, err := m.FindOne(shorten)
if err != nil {
return err
}
shorturlShortenKey := fmt.Sprintf("%s%v", cacheShorturlShortenPrefix, shorten)
_, err = m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
query := `delete from ` + m.table + ` where shorten = ?`
return conn.Exec(query, shorten)
}, shorturlShortenKey)
return err
}

View File

@@ -0,0 +1,5 @@
package model
import "github.com/tal-tech/go-zero/core/stores/sqlx"
var ErrNotFound = sqlx.ErrNotFound

View File

@@ -0,0 +1,230 @@
// Code generated by protoc-gen-go.
// source: transform.proto
// DO NOT EDIT!
/*
Package transform is a generated protocol buffer package.
It is generated from these files:
transform.proto
It has these top-level messages:
ExpandReq
ExpandResp
ShortenReq
ShortenResp
*/
package transform
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 ExpandReq struct {
Shorten string `protobuf:"bytes,1,opt,name=shorten" json:"shorten,omitempty"`
}
func (m *ExpandReq) Reset() { *m = ExpandReq{} }
func (m *ExpandReq) String() string { return proto.CompactTextString(m) }
func (*ExpandReq) ProtoMessage() {}
func (*ExpandReq) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
func (m *ExpandReq) GetShorten() string {
if m != nil {
return m.Shorten
}
return ""
}
type ExpandResp struct {
Url string `protobuf:"bytes,1,opt,name=url" json:"url,omitempty"`
}
func (m *ExpandResp) Reset() { *m = ExpandResp{} }
func (m *ExpandResp) String() string { return proto.CompactTextString(m) }
func (*ExpandResp) ProtoMessage() {}
func (*ExpandResp) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
func (m *ExpandResp) GetUrl() string {
if m != nil {
return m.Url
}
return ""
}
type ShortenReq struct {
Url string `protobuf:"bytes,1,opt,name=url" json:"url,omitempty"`
}
func (m *ShortenReq) Reset() { *m = ShortenReq{} }
func (m *ShortenReq) String() string { return proto.CompactTextString(m) }
func (*ShortenReq) ProtoMessage() {}
func (*ShortenReq) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
func (m *ShortenReq) GetUrl() string {
if m != nil {
return m.Url
}
return ""
}
type ShortenResp struct {
Shorten string `protobuf:"bytes,1,opt,name=shorten" json:"shorten,omitempty"`
}
func (m *ShortenResp) Reset() { *m = ShortenResp{} }
func (m *ShortenResp) String() string { return proto.CompactTextString(m) }
func (*ShortenResp) ProtoMessage() {}
func (*ShortenResp) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} }
func (m *ShortenResp) GetShorten() string {
if m != nil {
return m.Shorten
}
return ""
}
func init() {
proto.RegisterType((*ExpandReq)(nil), "transform.expandReq")
proto.RegisterType((*ExpandResp)(nil), "transform.expandResp")
proto.RegisterType((*ShortenReq)(nil), "transform.shortenReq")
proto.RegisterType((*ShortenResp)(nil), "transform.shortenResp")
}
// 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 Transformer service
type TransformerClient interface {
Expand(ctx context.Context, in *ExpandReq, opts ...grpc.CallOption) (*ExpandResp, error)
Shorten(ctx context.Context, in *ShortenReq, opts ...grpc.CallOption) (*ShortenResp, error)
}
type transformerClient struct {
cc *grpc.ClientConn
}
func NewTransformerClient(cc *grpc.ClientConn) TransformerClient {
return &transformerClient{cc}
}
func (c *transformerClient) Expand(ctx context.Context, in *ExpandReq, opts ...grpc.CallOption) (*ExpandResp, error) {
out := new(ExpandResp)
err := grpc.Invoke(ctx, "/transform.transformer/expand", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *transformerClient) Shorten(ctx context.Context, in *ShortenReq, opts ...grpc.CallOption) (*ShortenResp, error) {
out := new(ShortenResp)
err := grpc.Invoke(ctx, "/transform.transformer/shorten", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Server API for Transformer service
type TransformerServer interface {
Expand(context.Context, *ExpandReq) (*ExpandResp, error)
Shorten(context.Context, *ShortenReq) (*ShortenResp, error)
}
func RegisterTransformerServer(s *grpc.Server, srv TransformerServer) {
s.RegisterService(&_Transformer_serviceDesc, srv)
}
func _Transformer_Expand_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ExpandReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TransformerServer).Expand(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/transform.transformer/Expand",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TransformerServer).Expand(ctx, req.(*ExpandReq))
}
return interceptor(ctx, in, info, handler)
}
func _Transformer_Shorten_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ShortenReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TransformerServer).Shorten(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/transform.transformer/Shorten",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TransformerServer).Shorten(ctx, req.(*ShortenReq))
}
return interceptor(ctx, in, info, handler)
}
var _Transformer_serviceDesc = grpc.ServiceDesc{
ServiceName: "transform.transformer",
HandlerType: (*TransformerServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "expand",
Handler: _Transformer_Expand_Handler,
},
{
MethodName: "shorten",
Handler: _Transformer_Shorten_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "transform.proto",
}
func init() { proto.RegisterFile("transform.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{
// 163 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0x2f, 0x29, 0x4a, 0xcc,
0x2b, 0x4e, 0xcb, 0x2f, 0xca, 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x84, 0x0b, 0x28,
0xa9, 0x72, 0x71, 0xa6, 0x56, 0x14, 0x24, 0xe6, 0xa5, 0x04, 0xa5, 0x16, 0x0a, 0x49, 0x70, 0xb1,
0x17, 0x67, 0xe4, 0x17, 0x95, 0xa4, 0xe6, 0x49, 0x30, 0x2a, 0x30, 0x6a, 0x70, 0x06, 0xc1, 0xb8,
0x4a, 0x72, 0x5c, 0x5c, 0x30, 0x65, 0xc5, 0x05, 0x42, 0x02, 0x5c, 0xcc, 0xa5, 0x45, 0x39, 0x50,
0x35, 0x20, 0x26, 0x48, 0x1e, 0xaa, 0x14, 0x64, 0x0e, 0xa6, 0xbc, 0x3a, 0x17, 0x37, 0x5c, 0xbe,
0xb8, 0x00, 0xb7, 0x45, 0x46, 0x75, 0x5c, 0xdc, 0x70, 0xc7, 0xa5, 0x16, 0x09, 0x99, 0x72, 0xb1,
0x41, 0xec, 0x15, 0x12, 0xd1, 0x43, 0xf8, 0x02, 0xee, 0x62, 0x29, 0x51, 0x2c, 0xa2, 0xc5, 0x05,
0x42, 0x16, 0x70, 0xf3, 0x85, 0x90, 0x55, 0x20, 0x9c, 0x28, 0x25, 0x86, 0x4d, 0xb8, 0xb8, 0x20,
0x89, 0x0d, 0x1c, 0x42, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x8a, 0x27, 0x78, 0x05, 0x34,
0x01, 0x00, 0x00,
}

View File

@@ -0,0 +1,40 @@
// Code generated by goctl. DO NOT EDIT!
// Source: transform.proto
package main
import (
"flag"
"fmt"
"log"
"shorturl/rpc/transform/internal/config"
"shorturl/rpc/transform/internal/server"
"shorturl/rpc/transform/internal/svc"
transform "shorturl/rpc/transform/pb"
"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/transform.yaml", "the config file")
func main() {
flag.Parse()
var c config.Config
conf.MustLoad(*configFile, &c)
ctx := svc.NewServiceContext(c)
transformerSrv := server.NewTransformerServer(ctx)
s, err := rpcx.NewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
transform.RegisterTransformerServer(grpcServer, transformerSrv)
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Starting rpc server at %s...\n", c.ListenOn)
s.Start()
}

View File

@@ -0,0 +1,24 @@
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);
}

View File

@@ -0,0 +1,96 @@
// Code generated by goctl. DO NOT EDIT!
// Source: transform.proto
//go:generate mockgen -destination ./transformer_mock.go -package transformer -source $GOFILE
package transformer
import (
"context"
transform "shorturl/rpc/transform/pb"
"github.com/tal-tech/go-zero/core/jsonx"
"github.com/tal-tech/go-zero/rpcx"
)
type (
Transformer interface {
Expand(ctx context.Context, in *ExpandReq) (*ExpandResp, error)
Shorten(ctx context.Context, in *ShortenReq) (*ShortenResp, error)
}
defaultTransformer struct {
cli rpcx.Client
}
)
func NewTransformer(cli rpcx.Client) Transformer {
return &defaultTransformer{
cli: cli,
}
}
func (m *defaultTransformer) Expand(ctx context.Context, in *ExpandReq) (*ExpandResp, error) {
var request transform.ExpandReq
bts, err := jsonx.Marshal(in)
if err != nil {
return nil, errJsonConvert
}
err = jsonx.Unmarshal(bts, &request)
if err != nil {
return nil, errJsonConvert
}
client := transform.NewTransformerClient(m.cli.Conn())
resp, err := client.Expand(ctx, &request)
if err != nil {
return nil, err
}
var ret ExpandResp
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
}
func (m *defaultTransformer) Shorten(ctx context.Context, in *ShortenReq) (*ShortenResp, error) {
var request transform.ShortenReq
bts, err := jsonx.Marshal(in)
if err != nil {
return nil, errJsonConvert
}
err = jsonx.Unmarshal(bts, &request)
if err != nil {
return nil, errJsonConvert
}
client := transform.NewTransformerClient(m.cli.Conn())
resp, err := client.Shorten(ctx, &request)
if err != nil {
return nil, err
}
var ret ShortenResp
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
}

View File

@@ -0,0 +1,64 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: transformer.go
// Package transformer is a generated GoMock package.
package transformer
import (
context "context"
gomock "github.com/golang/mock/gomock"
reflect "reflect"
)
// MockTransformer is a mock of Transformer interface
type MockTransformer struct {
ctrl *gomock.Controller
recorder *MockTransformerMockRecorder
}
// MockTransformerMockRecorder is the mock recorder for MockTransformer
type MockTransformerMockRecorder struct {
mock *MockTransformer
}
// NewMockTransformer creates a new mock instance
func NewMockTransformer(ctrl *gomock.Controller) *MockTransformer {
mock := &MockTransformer{ctrl: ctrl}
mock.recorder = &MockTransformerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockTransformer) EXPECT() *MockTransformerMockRecorder {
return m.recorder
}
// Expand mocks base method
func (m *MockTransformer) Expand(ctx context.Context, in *ExpandReq) (*ExpandResp, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Expand", ctx, in)
ret0, _ := ret[0].(*ExpandResp)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Expand indicates an expected call of Expand
func (mr *MockTransformerMockRecorder) Expand(ctx, in interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Expand", reflect.TypeOf((*MockTransformer)(nil).Expand), ctx, in)
}
// Shorten mocks base method
func (m *MockTransformer) Shorten(ctx context.Context, in *ShortenReq) (*ShortenResp, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Shorten", ctx, in)
ret0, _ := ret[0].(*ShortenResp)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Shorten indicates an expected call of Shorten
func (mr *MockTransformerMockRecorder) Shorten(ctx, in interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Shorten", reflect.TypeOf((*MockTransformer)(nil).Shorten), ctx, in)
}

View File

@@ -0,0 +1,26 @@
// Code generated by goctl. DO NOT EDIT!
// Source: transform.proto
package transformer
import "errors"
var errJsonConvert = errors.New("json convert error")
type (
ExpandReq struct {
Shorten string `json:"shorten,omitempty"`
}
ExpandResp struct {
Url string `json:"url,omitempty"`
}
ShortenReq struct {
Url string `json:"url,omitempty"`
}
ShortenResp struct {
Shorten string `json:"shorten,omitempty"`
}
)

5
go.mod
View File

@@ -1,6 +1,6 @@
module github.com/tal-tech/go-zero
go 1.14
go 1.13
require (
github.com/DATA-DOG/go-sqlmock v1.4.1
@@ -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
View File

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

View File

@@ -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
View 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
View 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())
}

View File

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

View 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)
}

View 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)
}

View 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,
}

View 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);
}

View 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
}

View File

@@ -12,7 +12,7 @@ func (d *directBuilder) Build(target resolver.Target, cc resolver.ClientConn, op
resolver.Resolver, error) {
var addrs []resolver.Address
endpoints := strings.FieldsFunc(target.Endpoint, func(r rune) bool {
return r == EndpointSep
return r == EndpointSepChar
})
for _, val := range subset(endpoints, subsetSize) {

View File

@@ -12,7 +12,7 @@ type discovBuilder struct{}
func (d *discovBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (
resolver.Resolver, error) {
hosts := strings.FieldsFunc(target.Authority, func(r rune) bool {
return r == EndpointSep
return r == EndpointSepChar
})
sub, err := discov.NewSubscriber(hosts, target.Endpoint)
if err != nil {

View File

@@ -1,17 +1,22 @@
package resolver
import "google.golang.org/grpc/resolver"
import (
"fmt"
"google.golang.org/grpc/resolver"
)
const (
DirectScheme = "direct"
DiscovScheme = "discov"
EndpointSep = ','
subsetSize = 32
DirectScheme = "direct"
DiscovScheme = "discov"
EndpointSepChar = ','
subsetSize = 32
)
var (
dirBuilder directBuilder
disBuilder discovBuilder
EndpointSep = fmt.Sprintf("%c", EndpointSepChar)
dirBuilder directBuilder
disBuilder discovBuilder
)
func RegisterResolver() {

View 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)
}

View 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)
}

View File

@@ -8,11 +8,11 @@ import (
)
func BuildDirectTarget(endpoints []string) string {
return fmt.Sprintf("%s:///%s", resolver.DirectScheme, strings.Join(
endpoints, fmt.Sprintf("%c", resolver.EndpointSep)))
return fmt.Sprintf("%s:///%s", resolver.DirectScheme,
strings.Join(endpoints, resolver.EndpointSep))
}
func BuildDiscovTarget(endpoints []string, key string) string {
return fmt.Sprintf("%s://%s/%s", resolver.DiscovScheme, strings.Join(
endpoints, fmt.Sprintf("%c", resolver.EndpointSep)), key)
return fmt.Sprintf("%s://%s/%s", resolver.DiscovScheme,
strings.Join(endpoints, resolver.EndpointSep), key)
}

View File

@@ -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
View 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())
}
}
}
})
}
}

View File

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

View File

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

View File

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

View File

@@ -13,9 +13,7 @@ const (
configFile = "config.go"
configTemplate = `package config
import (
{{.authImport}}
)
import {{.authImport}}
type Config struct {
rest.RestConf

View File

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

View File

@@ -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")
}

View File

@@ -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")
}

View File

@@ -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")
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,12 +1,16 @@
package util
import "github.com/tal-tech/go-zero/tools/goctl/api/spec"
import (
"strings"
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
)
func GetAnnotationValue(annos []spec.Annotation, key, field string) (string, bool) {
for _, anno := range annos {
if anno.Name == key {
value, ok := anno.Properties[field]
return value, ok
return strings.TrimSpace(value), ok
}
}
return "", false

View File

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

View File

@@ -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")
}

View File

@@ -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(),

View 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
}

View File

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

Some files were not shown because too many files have changed in this diff Show More