Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
456b395860 | ||
|
|
f3c367a323 | ||
|
|
a32028c4fb | ||
|
|
b4572fa064 | ||
|
|
ccbabf6f58 | ||
|
|
5989444227 | ||
|
|
dc286a03f5 | ||
|
|
b82c02ed16 | ||
|
|
59ba4ecc5b | ||
|
|
5e7b514ae2 | ||
|
|
2b1466e41e | ||
|
|
9c9f80518f | ||
|
|
25973d6b59 | ||
|
|
6237d01948 | ||
|
|
49316b113e | ||
|
|
6a673e8cb0 | ||
|
|
0efa28ddbd | ||
|
|
0b6a13fe84 | ||
|
|
11aa6668e8 | ||
|
|
267a283328 | ||
|
|
2d8366b30e | ||
|
|
db83843558 | ||
|
|
50565c9765 |
@@ -1,3 +1,4 @@
|
|||||||
ignore:
|
ignore:
|
||||||
- "example/*"
|
- "doc"
|
||||||
- "tools/*"
|
- "example"
|
||||||
|
- "tools"
|
||||||
@@ -213,7 +213,10 @@ func TestTimingWheel_SetTimer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
|
test := test
|
||||||
t.Run(stringx.RandId(), func(t *testing.T) {
|
t.Run(stringx.RandId(), func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
var count int32
|
var count int32
|
||||||
ticker := timex.NewFakeTicker()
|
ticker := timex.NewFakeTicker()
|
||||||
tick := func() {
|
tick := func() {
|
||||||
@@ -291,7 +294,10 @@ func TestTimingWheel_SetAndMoveThenStart(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
|
test := test
|
||||||
t.Run(stringx.RandId(), func(t *testing.T) {
|
t.Run(stringx.RandId(), func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
var count int32
|
var count int32
|
||||||
ticker := timex.NewFakeTicker()
|
ticker := timex.NewFakeTicker()
|
||||||
tick := func() {
|
tick := func() {
|
||||||
@@ -376,7 +382,10 @@ func TestTimingWheel_SetAndMoveTwice(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
|
test := test
|
||||||
t.Run(stringx.RandId(), func(t *testing.T) {
|
t.Run(stringx.RandId(), func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
var count int32
|
var count int32
|
||||||
ticker := timex.NewFakeTicker()
|
ticker := timex.NewFakeTicker()
|
||||||
tick := func() {
|
tick := func() {
|
||||||
@@ -454,7 +463,10 @@ func TestTimingWheel_ElapsedAndSet(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
|
test := test
|
||||||
t.Run(stringx.RandId(), func(t *testing.T) {
|
t.Run(stringx.RandId(), func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
var count int32
|
var count int32
|
||||||
ticker := timex.NewFakeTicker()
|
ticker := timex.NewFakeTicker()
|
||||||
tick := func() {
|
tick := func() {
|
||||||
@@ -542,7 +554,10 @@ func TestTimingWheel_ElapsedAndSetThenMove(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
|
test := test
|
||||||
t.Run(stringx.RandId(), func(t *testing.T) {
|
t.Run(stringx.RandId(), func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
var count int32
|
var count int32
|
||||||
ticker := timex.NewFakeTicker()
|
ticker := timex.NewFakeTicker()
|
||||||
tick := func() {
|
tick := func() {
|
||||||
|
|||||||
4
core/discov/kubernetes/discov-namespace.yaml
Normal file
4
core/discov/kubernetes/discov-namespace.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: discov
|
||||||
368
core/discov/kubernetes/etcd.yaml
Normal file
368
core/discov/kubernetes/etcd.yaml
Normal file
@@ -0,0 +1,368 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: etcd
|
||||||
|
namespace: discov
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- name: etcd-port
|
||||||
|
port: 2379
|
||||||
|
protocol: TCP
|
||||||
|
targetPort: 2379
|
||||||
|
selector:
|
||||||
|
app: etcd
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: etcd
|
||||||
|
etcd_node: etcd0
|
||||||
|
name: etcd0
|
||||||
|
namespace: discov
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- command:
|
||||||
|
- /usr/local/bin/etcd
|
||||||
|
- --name
|
||||||
|
- etcd0
|
||||||
|
- --initial-advertise-peer-urls
|
||||||
|
- 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://etcd0:2379
|
||||||
|
- --initial-cluster
|
||||||
|
- 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: quay.io/coreos/etcd:latest
|
||||||
|
name: etcd0
|
||||||
|
ports:
|
||||||
|
- containerPort: 2379
|
||||||
|
name: client
|
||||||
|
protocol: TCP
|
||||||
|
- containerPort: 2380
|
||||||
|
name: server
|
||||||
|
protocol: TCP
|
||||||
|
affinity:
|
||||||
|
podAntiAffinity:
|
||||||
|
requiredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
- labelSelector:
|
||||||
|
matchExpressions:
|
||||||
|
- key: app
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- etcd
|
||||||
|
topologyKey: "kubernetes.io/hostname"
|
||||||
|
restartPolicy: Always
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
etcd_node: etcd0
|
||||||
|
name: etcd0
|
||||||
|
namespace: discov
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- name: client
|
||||||
|
port: 2379
|
||||||
|
protocol: TCP
|
||||||
|
targetPort: 2379
|
||||||
|
- name: server
|
||||||
|
port: 2380
|
||||||
|
protocol: TCP
|
||||||
|
targetPort: 2380
|
||||||
|
selector:
|
||||||
|
etcd_node: etcd0
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: etcd
|
||||||
|
etcd_node: etcd1
|
||||||
|
name: etcd1
|
||||||
|
namespace: discov
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- command:
|
||||||
|
- /usr/local/bin/etcd
|
||||||
|
- --name
|
||||||
|
- etcd1
|
||||||
|
- --initial-advertise-peer-urls
|
||||||
|
- 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://etcd1:2379
|
||||||
|
- --initial-cluster
|
||||||
|
- 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: quay.io/coreos/etcd:latest
|
||||||
|
name: etcd1
|
||||||
|
ports:
|
||||||
|
- containerPort: 2379
|
||||||
|
name: client
|
||||||
|
protocol: TCP
|
||||||
|
- containerPort: 2380
|
||||||
|
name: server
|
||||||
|
protocol: TCP
|
||||||
|
affinity:
|
||||||
|
podAntiAffinity:
|
||||||
|
requiredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
- labelSelector:
|
||||||
|
matchExpressions:
|
||||||
|
- key: app
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- etcd
|
||||||
|
topologyKey: "kubernetes.io/hostname"
|
||||||
|
restartPolicy: Always
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
etcd_node: etcd1
|
||||||
|
name: etcd1
|
||||||
|
namespace: discov
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- name: client
|
||||||
|
port: 2379
|
||||||
|
protocol: TCP
|
||||||
|
targetPort: 2379
|
||||||
|
- name: server
|
||||||
|
port: 2380
|
||||||
|
protocol: TCP
|
||||||
|
targetPort: 2380
|
||||||
|
selector:
|
||||||
|
etcd_node: etcd1
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: etcd
|
||||||
|
etcd_node: etcd2
|
||||||
|
name: etcd2
|
||||||
|
namespace: discov
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- command:
|
||||||
|
- /usr/local/bin/etcd
|
||||||
|
- --name
|
||||||
|
- etcd2
|
||||||
|
- --initial-advertise-peer-urls
|
||||||
|
- 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://etcd2:2379
|
||||||
|
- --initial-cluster
|
||||||
|
- 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: quay.io/coreos/etcd:latest
|
||||||
|
name: etcd2
|
||||||
|
ports:
|
||||||
|
- containerPort: 2379
|
||||||
|
name: client
|
||||||
|
protocol: TCP
|
||||||
|
- containerPort: 2380
|
||||||
|
name: server
|
||||||
|
protocol: TCP
|
||||||
|
affinity:
|
||||||
|
podAntiAffinity:
|
||||||
|
requiredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
- labelSelector:
|
||||||
|
matchExpressions:
|
||||||
|
- key: app
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- etcd
|
||||||
|
topologyKey: "kubernetes.io/hostname"
|
||||||
|
restartPolicy: Always
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
etcd_node: etcd2
|
||||||
|
name: etcd2
|
||||||
|
namespace: discov
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- name: client
|
||||||
|
port: 2379
|
||||||
|
protocol: TCP
|
||||||
|
targetPort: 2379
|
||||||
|
- name: server
|
||||||
|
port: 2380
|
||||||
|
protocol: TCP
|
||||||
|
targetPort: 2380
|
||||||
|
selector:
|
||||||
|
etcd_node: etcd2
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: etcd
|
||||||
|
etcd_node: etcd3
|
||||||
|
name: etcd3
|
||||||
|
namespace: discov
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- command:
|
||||||
|
- /usr/local/bin/etcd
|
||||||
|
- --name
|
||||||
|
- etcd3
|
||||||
|
- --initial-advertise-peer-urls
|
||||||
|
- 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://etcd3:2379
|
||||||
|
- --initial-cluster
|
||||||
|
- 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: quay.io/coreos/etcd:latest
|
||||||
|
name: etcd3
|
||||||
|
ports:
|
||||||
|
- containerPort: 2379
|
||||||
|
name: client
|
||||||
|
protocol: TCP
|
||||||
|
- containerPort: 2380
|
||||||
|
name: server
|
||||||
|
protocol: TCP
|
||||||
|
affinity:
|
||||||
|
podAntiAffinity:
|
||||||
|
requiredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
- labelSelector:
|
||||||
|
matchExpressions:
|
||||||
|
- key: app
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- etcd
|
||||||
|
topologyKey: "kubernetes.io/hostname"
|
||||||
|
restartPolicy: Always
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
etcd_node: etcd3
|
||||||
|
name: etcd3
|
||||||
|
namespace: discov
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- name: client
|
||||||
|
port: 2379
|
||||||
|
protocol: TCP
|
||||||
|
targetPort: 2379
|
||||||
|
- name: server
|
||||||
|
port: 2380
|
||||||
|
protocol: TCP
|
||||||
|
targetPort: 2380
|
||||||
|
selector:
|
||||||
|
etcd_node: etcd3
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: etcd
|
||||||
|
etcd_node: etcd4
|
||||||
|
name: etcd4
|
||||||
|
namespace: discov
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- command:
|
||||||
|
- /usr/local/bin/etcd
|
||||||
|
- --name
|
||||||
|
- etcd4
|
||||||
|
- --initial-advertise-peer-urls
|
||||||
|
- 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://etcd4:2379
|
||||||
|
- --initial-cluster
|
||||||
|
- 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: quay.io/coreos/etcd:latest
|
||||||
|
name: etcd4
|
||||||
|
ports:
|
||||||
|
- containerPort: 2379
|
||||||
|
name: client
|
||||||
|
protocol: TCP
|
||||||
|
- containerPort: 2380
|
||||||
|
name: server
|
||||||
|
protocol: TCP
|
||||||
|
affinity:
|
||||||
|
podAntiAffinity:
|
||||||
|
requiredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
- labelSelector:
|
||||||
|
matchExpressions:
|
||||||
|
- key: app
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- etcd
|
||||||
|
topologyKey: "kubernetes.io/hostname"
|
||||||
|
restartPolicy: Always
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
etcd_node: etcd4
|
||||||
|
name: etcd4
|
||||||
|
namespace: discov
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- name: client
|
||||||
|
port: 2379
|
||||||
|
protocol: TCP
|
||||||
|
targetPort: 2379
|
||||||
|
- name: server
|
||||||
|
port: 2380
|
||||||
|
protocol: TCP
|
||||||
|
targetPort: 2380
|
||||||
|
selector:
|
||||||
|
etcd_node: etcd4
|
||||||
@@ -4,9 +4,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/tal-tech/go-zero/core/fs"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/tal-tech/go-zero/core/fs"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -213,6 +213,7 @@ func Infof(format string, v ...interface{}) {
|
|||||||
func Must(err error) {
|
func Must(err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
msg := formatWithCaller(err.Error(), 3)
|
msg := formatWithCaller(err.Error(), 3)
|
||||||
|
log.Print(msg)
|
||||||
output(severeLog, levelFatal, msg)
|
output(severeLog, levelFatal, msg)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -195,6 +195,13 @@ ts需要指定webapi所在目录
|
|||||||
goctl api dart -api user/user.api -dir ./src
|
goctl api dart -api user/user.api -dir ./src
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 根据mysql ddl或者datasource生成model文件
|
||||||
|
|
||||||
|
```shell script
|
||||||
|
$ goctl model mysql -src={filename} -dir={dir} -cache={true|false}
|
||||||
|
```
|
||||||
|
详情参考[model文档](https://github.com/tal-tech/go-zero/blob/master/tools/goctl/model/sql/README.MD)
|
||||||
|
|
||||||
## 根据定义好的简单go文件生成mongo代码文件(仅限golang使用)
|
## 根据定义好的简单go文件生成mongo代码文件(仅限golang使用)
|
||||||
```shell
|
```shell
|
||||||
goctl model mongo -src {{yourDir}}/xiao/service/xhb/user/model/usermodel.go -cache yes
|
goctl model mongo -src {{yourDir}}/xiao/service/xhb/user/model/usermodel.go -cache yes
|
||||||
@@ -218,7 +225,7 @@ type User struct {
|
|||||||
o是改字段需要生产的操作函数 可以取得get,find,set 分别表示生成返回单个对象的查询方法,返回多个对象的查询方法,设置该字段方法
|
o是改字段需要生产的操作函数 可以取得get,find,set 分别表示生成返回单个对象的查询方法,返回多个对象的查询方法,设置该字段方法
|
||||||
生成的目标文件会覆盖该简单go文件
|
生成的目标文件会覆盖该简单go文件
|
||||||
|
|
||||||
## goctl rpc生成
|
## goctl rpc生成(业务剥离中,暂未开放)
|
||||||
|
|
||||||
命令 `goctl rpc proto -proto ${proto} -service ${serviceName} -project ${projectName} -dir ${directory} -shared ${shared}`
|
命令 `goctl rpc proto -proto ${proto} -service ${serviceName} -project ${projectName} -dir ${directory} -shared ${shared}`
|
||||||
如: `goctl rpc proto -proto test.proto -service test -project xjy -dir .`
|
如: `goctl rpc proto -proto test.proto -service test -project xjy -dir .`
|
||||||
|
|||||||
BIN
doc/images/architecture.png
Normal file
BIN
doc/images/architecture.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 333 KiB |
@@ -10,23 +10,28 @@
|
|||||||
|
|
||||||
## 2. 关键词替换
|
## 2. 关键词替换
|
||||||
|
|
||||||
|
支持关键词重叠,自动选用最长的关键词,代码示例如下:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
replacer := stringx.NewReplacer(map[string]string{
|
replacer := stringx.NewReplacer(map[string]string{
|
||||||
"PHP": "PPT",
|
"日本": "法国",
|
||||||
"世界上": "吹牛",
|
"日本的首都": "东京",
|
||||||
|
"东京": "日本的首都",
|
||||||
})
|
})
|
||||||
fmt.Println(replacer.Replace("PHP是世界上最好的语言!"))
|
fmt.Println(replacer.Replace("日本的首都是东京"))
|
||||||
```
|
```
|
||||||
|
|
||||||
可以得到:
|
可以得到:
|
||||||
```
|
```
|
||||||
PPT是吹牛最好的语言!
|
东京是日本的首都
|
||||||
```
|
```
|
||||||
|
|
||||||
示例代码见`example/stringx/replace/replace.go`
|
示例代码见`example/stringx/replace/replace.go`
|
||||||
|
|
||||||
## 3. 查找敏感词
|
## 3. 查找敏感词
|
||||||
|
|
||||||
|
代码示例如下:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
filter := stringx.NewTrie([]string{
|
filter := stringx.NewTrie([]string{
|
||||||
"AV演员",
|
"AV演员",
|
||||||
@@ -47,6 +52,8 @@ fmt.Println(keywords)
|
|||||||
|
|
||||||
## 4. 敏感词过滤
|
## 4. 敏感词过滤
|
||||||
|
|
||||||
|
代码示例如下:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
filter := stringx.NewTrie([]string{
|
filter := stringx.NewTrie([]string{
|
||||||
"AV演员",
|
"AV演员",
|
||||||
|
|||||||
@@ -8,8 +8,9 @@ import (
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
replacer := stringx.NewReplacer(map[string]string{
|
replacer := stringx.NewReplacer(map[string]string{
|
||||||
"PHP": "PPT",
|
"日本": "法国",
|
||||||
"世界上": "吹牛",
|
"日本的首都": "东京",
|
||||||
|
"东京": "日本的首都",
|
||||||
})
|
})
|
||||||
fmt.Println(replacer.Replace("PHP是世界上最好的语言!"))
|
fmt.Println(replacer.Replace("日本的首都是东京"))
|
||||||
}
|
}
|
||||||
|
|||||||
52
readme.md
52
readme.md
@@ -6,6 +6,23 @@
|
|||||||
[](https://github.com/tal-tech/go-zero)
|
[](https://github.com/tal-tech/go-zero)
|
||||||
[](https://opensource.org/licenses/MIT)
|
[](https://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
## 0. go-zero介绍
|
||||||
|
|
||||||
|
go-zero是一个集成了各种工程实践的web和rpc框架。通过弹性设计保障了大并发服务端的稳定性,经受了充分的实战检验。
|
||||||
|
|
||||||
|
go-zero 包含极简的 API 定义和生成工具 goctl,可以根据定义的 api 文件一键生成 Go, iOS, Android, Kotlin, Dart, TypeScript, JavaScript 代码,并可直接运行。
|
||||||
|
|
||||||
|
使用go-zero的好处:
|
||||||
|
|
||||||
|
* 轻松获得支撑千万日活服务的稳定性
|
||||||
|
* 内建级联超时控制、限流、自适应熔断、自适应降载等微服务治理能力,无需配置和额外代码
|
||||||
|
* 微服务治理中间件可无缝集成到其它现有框架使用
|
||||||
|
* 极简的API描述,一键生成各端代码
|
||||||
|
* 自动校验客户端请求参数合法性
|
||||||
|
* 大量微服务治理和并发工具包
|
||||||
|
|
||||||
|
<img src="doc/images/architecture.png" alt="架构图" width="1500" />
|
||||||
|
|
||||||
## 1. go-zero框架背景
|
## 1. go-zero框架背景
|
||||||
|
|
||||||
18年初,晓黑板后端在经过频繁的宕机后,决定从`Java+MongoDB`的单体架构迁移到微服务架构,经过仔细思考和对比,我们决定:
|
18年初,晓黑板后端在经过频繁的宕机后,决定从`Java+MongoDB`的单体架构迁移到微服务架构,经过仔细思考和对比,我们决定:
|
||||||
@@ -57,33 +74,20 @@ go-zero是一个集成了各种工程实践的包含web和rpc框架,有如下
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
## 4. go-zero框架收益
|
## 4. go-zero近期开发计划
|
||||||
|
|
||||||
* 保障大并发服务端的稳定性,经受了充分的实战检验
|
|
||||||
* 极简的API定义
|
|
||||||
* 一键生成Go, iOS, Android, Dart, TypeScript, JavaScript代码,并可直接运行
|
|
||||||
* 服务端自动校验参数合法性
|
|
||||||
|
|
||||||
## 5. go-zero近期开发计划
|
|
||||||
|
|
||||||
* 自动生成API mock server,便于客户端开发
|
* 自动生成API mock server,便于客户端开发
|
||||||
* 自动生成服务端功能测试
|
* 自动生成服务端功能测试
|
||||||
|
|
||||||
## 6. Installation
|
## 5. Installation
|
||||||
|
|
||||||
1. 在项目目录下通过如下命令安装:
|
在项目目录下通过如下命令安装:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
go get -u github.com/tal-tech/go-zero
|
go get -u github.com/tal-tech/go-zero
|
||||||
```
|
```
|
||||||
|
|
||||||
2. 代码里导入go-zero
|
## 6. Quick Start
|
||||||
|
|
||||||
```go
|
|
||||||
import "github.com/tal-tech/go-zero"
|
|
||||||
```
|
|
||||||
|
|
||||||
## 7. Quick Start
|
|
||||||
|
|
||||||
1. 编译goctl工具
|
1. 编译goctl工具
|
||||||
|
|
||||||
@@ -97,7 +101,7 @@ go-zero是一个集成了各种工程实践的包含web和rpc框架,有如下
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
type Request struct {
|
type Request struct {
|
||||||
Name string `path:"name"`
|
Name string `path:"name,options=you|me"` // 框架自动验证请求参数是否合法
|
||||||
}
|
}
|
||||||
|
|
||||||
type Response struct {
|
type Response struct {
|
||||||
@@ -173,17 +177,17 @@ go-zero是一个集成了各种工程实践的包含web和rpc框架,有如下
|
|||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
## 8. Benchmark
|
## 7. Benchmark
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
[测试代码见这里](https://github.com/smallnest/go-web-framework-benchmark)
|
[测试代码见这里](https://github.com/smallnest/go-web-framework-benchmark)
|
||||||
|
|
||||||
## 9. 文档
|
## 8. 文档 (逐步完善中)
|
||||||
|
|
||||||
* [goctl使用帮助](doc/goctl.md)
|
* [goctl使用帮助](doc/goctl.md)
|
||||||
* [关键字替换和敏感词过滤工具](doc/keywords.md)
|
* [关键字替换和敏感词过滤工具](doc/keywords.md)
|
||||||
|
|
||||||
## 10. 微信交流群
|
## 9. 微信交流群
|
||||||
|
|
||||||
添加我的微信:kevwan,请注明go-zero,我拉进go-zero社区群🤝
|
添加我的微信:kevwan,请注明go-zero,我拉进go-zero社区群🤝
|
||||||
|
|||||||
@@ -209,6 +209,6 @@ func (s *engine) use(middleware Middleware) {
|
|||||||
|
|
||||||
func convertMiddleware(ware Middleware) func(http.Handler) http.Handler {
|
func convertMiddleware(ware Middleware) func(http.Handler) http.Handler {
|
||||||
return func(next http.Handler) http.Handler {
|
return func(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(ware(next.ServeHTTP))
|
return ware(next.ServeHTTP)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package httpx
|
package httpx
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -17,6 +18,24 @@ func init() {
|
|||||||
logx.Disable()
|
logx.Disable()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestError(t *testing.T) {
|
||||||
|
const body = "foo"
|
||||||
|
w := tracedResponseWriter{
|
||||||
|
headers: make(map[string][]string),
|
||||||
|
}
|
||||||
|
Error(&w, errors.New(body))
|
||||||
|
assert.Equal(t, http.StatusBadRequest, w.code)
|
||||||
|
assert.Equal(t, body, strings.TrimSpace(w.builder.String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOk(t *testing.T) {
|
||||||
|
w := tracedResponseWriter{
|
||||||
|
headers: make(map[string][]string),
|
||||||
|
}
|
||||||
|
Ok(&w)
|
||||||
|
assert.Equal(t, http.StatusOK, w.code)
|
||||||
|
}
|
||||||
|
|
||||||
func TestOkJson(t *testing.T) {
|
func TestOkJson(t *testing.T) {
|
||||||
w := tracedResponseWriter{
|
w := tracedResponseWriter{
|
||||||
headers: make(map[string][]string),
|
headers: make(map[string][]string),
|
||||||
|
|||||||
62
rpcx/internal/auth/credential_test.go
Normal file
62
rpcx/internal/auth/credential_test.go
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"google.golang.org/grpc/metadata"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseCredential(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
withNil bool
|
||||||
|
withEmptyMd bool
|
||||||
|
app string
|
||||||
|
token string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil",
|
||||||
|
withNil: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty md",
|
||||||
|
withEmptyMd: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid",
|
||||||
|
app: "foo",
|
||||||
|
token: "bar",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
test := test
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var ctx context.Context
|
||||||
|
if test.withNil {
|
||||||
|
ctx = context.Background()
|
||||||
|
} else if test.withEmptyMd {
|
||||||
|
ctx = metadata.NewIncomingContext(context.Background(), metadata.MD{})
|
||||||
|
} else {
|
||||||
|
md := metadata.New(map[string]string{
|
||||||
|
"app": test.app,
|
||||||
|
"token": test.token,
|
||||||
|
})
|
||||||
|
ctx = metadata.NewIncomingContext(context.Background(), md)
|
||||||
|
}
|
||||||
|
cred := ParseCredential(ctx)
|
||||||
|
assert.False(t, cred.RequireTransportSecurity())
|
||||||
|
m, err := cred.GetRequestMetadata(context.Background())
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, test.app, m[appKey])
|
||||||
|
assert.Equal(t, test.token, m[tokenKey])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,9 @@ package p2c
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -33,19 +35,31 @@ func TestP2cPicker_Pick(t *testing.T) {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
candidates int
|
candidates int
|
||||||
|
threshold float64
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "single",
|
name: "single",
|
||||||
candidates: 1,
|
candidates: 1,
|
||||||
|
threshold: 0.9,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "two",
|
||||||
|
candidates: 2,
|
||||||
|
threshold: 0.5,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "multiple",
|
name: "multiple",
|
||||||
candidates: 100,
|
candidates: 100,
|
||||||
|
threshold: 0.95,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
|
test := test
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
const total = 10000
|
||||||
builder := new(p2cPickerBuilder)
|
builder := new(p2cPickerBuilder)
|
||||||
ready := make(map[resolver.Address]balancer.SubConn)
|
ready := make(map[resolver.Address]balancer.SubConn)
|
||||||
for i := 0; i < test.candidates; i++ {
|
for i := 0; i < test.candidates; i++ {
|
||||||
@@ -55,7 +69,9 @@ func TestP2cPicker_Pick(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
picker := builder.Build(ready)
|
picker := builder.Build(ready)
|
||||||
for i := 0; i < 10000; i++ {
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(total)
|
||||||
|
for i := 0; i < total; i++ {
|
||||||
_, done, err := picker.Pick(context.Background(), balancer.PickInfo{
|
_, done, err := picker.Pick(context.Background(), balancer.PickInfo{
|
||||||
FullMethodName: "/",
|
FullMethodName: "/",
|
||||||
Ctx: context.Background(),
|
Ctx: context.Background(),
|
||||||
@@ -64,11 +80,16 @@ func TestP2cPicker_Pick(t *testing.T) {
|
|||||||
if i%100 == 0 {
|
if i%100 == 0 {
|
||||||
err = status.Error(codes.DeadlineExceeded, "deadline")
|
err = status.Error(codes.DeadlineExceeded, "deadline")
|
||||||
}
|
}
|
||||||
done(balancer.DoneInfo{
|
go func() {
|
||||||
Err: err,
|
runtime.Gosched()
|
||||||
})
|
done(balancer.DoneInfo{
|
||||||
|
Err: err,
|
||||||
|
})
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
dist := make(map[interface{}]int)
|
dist := make(map[interface{}]int)
|
||||||
conns := picker.(*p2cPicker).conns
|
conns := picker.(*p2cPicker).conns
|
||||||
for _, conn := range conns {
|
for _, conn := range conns {
|
||||||
@@ -76,7 +97,8 @@ func TestP2cPicker_Pick(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
entropy := mathx.CalcEntropy(dist)
|
entropy := mathx.CalcEntropy(dist)
|
||||||
assert.True(t, entropy > .95, fmt.Sprintf("entropy is %f, less than .95", entropy))
|
assert.True(t, entropy > test.threshold, fmt.Sprintf("entropy is %f, less than %f",
|
||||||
|
entropy, test.threshold))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
47
rpcx/internal/chainclientinterceptors_test.go
Normal file
47
rpcx/internal/chainclientinterceptors_test.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync/atomic"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWithStreamClientInterceptors(t *testing.T) {
|
||||||
|
opts := WithStreamClientInterceptors()
|
||||||
|
assert.NotNil(t, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWithUnaryClientInterceptors(t *testing.T) {
|
||||||
|
opts := WithUnaryClientInterceptors()
|
||||||
|
assert.NotNil(t, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChainStreamClientInterceptors_zero(t *testing.T) {
|
||||||
|
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) {
|
||||||
|
return nil, nil
|
||||||
|
})
|
||||||
|
assert.Nil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChainStreamClientInterceptors_one(t *testing.T) {
|
||||||
|
var called int32
|
||||||
|
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
|
||||||
|
})
|
||||||
|
_, 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) {
|
||||||
|
return nil, nil
|
||||||
|
})
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, int32(1), atomic.LoadInt32(&called))
|
||||||
|
}
|
||||||
@@ -1,12 +1,15 @@
|
|||||||
package clientinterceptors
|
package clientinterceptors
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/tal-tech/go-zero/core/breaker"
|
"github.com/tal-tech/go-zero/core/breaker"
|
||||||
"github.com/tal-tech/go-zero/core/stat"
|
"github.com/tal-tech/go-zero/core/stat"
|
||||||
rcodes "github.com/tal-tech/go-zero/rpcx/internal/codes"
|
rcodes "github.com/tal-tech/go-zero/rpcx/internal/codes"
|
||||||
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
)
|
)
|
||||||
@@ -49,3 +52,30 @@ func TestBreakerInterceptorDeadlineExceeded(t *testing.T) {
|
|||||||
assert.True(t, errs[err] > 0)
|
assert.True(t, errs[err] > 0)
|
||||||
assert.True(t, errs[breaker.ErrServiceUnavailable] > 0)
|
assert.True(t, errs[breaker.ErrServiceUnavailable] > 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBreakerInterceptor(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil",
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with error",
|
||||||
|
err: errors.New("mock"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
cc := new(grpc.ClientConn)
|
||||||
|
err := BreakerInterceptor(context.Background(), "/foo", nil, nil, cc,
|
||||||
|
func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn,
|
||||||
|
opts ...grpc.CallOption) error {
|
||||||
|
return test.err
|
||||||
|
})
|
||||||
|
assert.Equal(t, test.err, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
37
rpcx/internal/clientinterceptors/durationinterceptor_test.go
Normal file
37
rpcx/internal/clientinterceptors/durationinterceptor_test.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package clientinterceptors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDurationInterceptor(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil",
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with error",
|
||||||
|
err: errors.New("mock"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
cc := new(grpc.ClientConn)
|
||||||
|
err := DurationInterceptor(context.Background(), "/foo", nil, nil, cc,
|
||||||
|
func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn,
|
||||||
|
opts ...grpc.CallOption) error {
|
||||||
|
return test.err
|
||||||
|
})
|
||||||
|
assert.Equal(t, test.err, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package clientinterceptors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPromMetricInterceptor(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil",
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with error",
|
||||||
|
err: errors.New("mock"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
cc := new(grpc.ClientConn)
|
||||||
|
err := PromMetricInterceptor(context.Background(), "/foo", nil, nil, cc,
|
||||||
|
func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn,
|
||||||
|
opts ...grpc.CallOption) error {
|
||||||
|
return test.err
|
||||||
|
})
|
||||||
|
assert.Equal(t, test.err, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
50
rpcx/internal/clientinterceptors/timeoutinterceptor_test.go
Normal file
50
rpcx/internal/clientinterceptors/timeoutinterceptor_test.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package clientinterceptors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTimeoutInterceptor(t *testing.T) {
|
||||||
|
timeouts := []time.Duration{0, time.Millisecond * 10}
|
||||||
|
for _, timeout := range timeouts {
|
||||||
|
t.Run(strconv.FormatInt(int64(timeout), 10), func(t *testing.T) {
|
||||||
|
interceptor := TimeoutInterceptor(timeout)
|
||||||
|
cc := new(grpc.ClientConn)
|
||||||
|
err := interceptor(context.Background(), "/foo", nil, nil, cc,
|
||||||
|
func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn,
|
||||||
|
opts ...grpc.CallOption) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTimeoutInterceptor_timeout(t *testing.T) {
|
||||||
|
const timeout = time.Millisecond * 10
|
||||||
|
interceptor := TimeoutInterceptor(timeout)
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(1)
|
||||||
|
cc := new(grpc.ClientConn)
|
||||||
|
err := interceptor(ctx, "/foo", nil, nil, cc,
|
||||||
|
func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn,
|
||||||
|
opts ...grpc.CallOption) error {
|
||||||
|
defer wg.Done()
|
||||||
|
tm, ok := ctx.Deadline()
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.True(t, tm.Before(time.Now().Add(timeout+time.Millisecond)))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
wg.Wait()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
}
|
||||||
53
rpcx/internal/clientinterceptors/tracinginterceptor_test.go
Normal file
53
rpcx/internal/clientinterceptors/tracinginterceptor_test.go
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
package clientinterceptors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/tal-tech/go-zero/core/trace"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/metadata"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTracingInterceptor(t *testing.T) {
|
||||||
|
var run int32
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(1)
|
||||||
|
cc := new(grpc.ClientConn)
|
||||||
|
err := TracingInterceptor(context.Background(), "/foo", nil, nil, cc,
|
||||||
|
func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn,
|
||||||
|
opts ...grpc.CallOption) error {
|
||||||
|
defer wg.Done()
|
||||||
|
atomic.AddInt32(&run, 1)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
wg.Wait()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, int32(1), atomic.LoadInt32(&run))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTracingInterceptor_GrpcFormat(t *testing.T) {
|
||||||
|
var run int32
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(1)
|
||||||
|
md := metadata.New(map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
})
|
||||||
|
carrier, err := trace.Inject(trace.GrpcFormat, md)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
ctx, _ := trace.StartServerSpan(context.Background(), carrier, "user", "/foo")
|
||||||
|
cc := new(grpc.ClientConn)
|
||||||
|
err = TracingInterceptor(ctx, "/foo", nil, nil, cc,
|
||||||
|
func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn,
|
||||||
|
opts ...grpc.CallOption) error {
|
||||||
|
defer wg.Done()
|
||||||
|
atomic.AddInt32(&run, 1)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
wg.Wait()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, int32(1), atomic.LoadInt32(&run))
|
||||||
|
}
|
||||||
@@ -11,7 +11,9 @@ type directBuilder struct{}
|
|||||||
func (d *directBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (
|
func (d *directBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (
|
||||||
resolver.Resolver, error) {
|
resolver.Resolver, error) {
|
||||||
var addrs []resolver.Address
|
var addrs []resolver.Address
|
||||||
endpoints := strings.Split(target.Endpoint, EndpointSep)
|
endpoints := strings.FieldsFunc(target.Endpoint, func(r rune) bool {
|
||||||
|
return r == EndpointSepChar
|
||||||
|
})
|
||||||
|
|
||||||
for _, val := range subset(endpoints, subsetSize) {
|
for _, val := range subset(endpoints, subsetSize) {
|
||||||
addrs = append(addrs, resolver.Address{
|
addrs = append(addrs, resolver.Address{
|
||||||
|
|||||||
52
rpcx/internal/resolver/directbuilder_test.go
Normal file
52
rpcx/internal/resolver/directbuilder_test.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package resolver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/tal-tech/go-zero/core/lang"
|
||||||
|
"github.com/tal-tech/go-zero/core/mathx"
|
||||||
|
"google.golang.org/grpc/resolver"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDirectBuilder_Build(t *testing.T) {
|
||||||
|
tests := []int{
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
subsetSize / 2,
|
||||||
|
subsetSize,
|
||||||
|
subsetSize * 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(strconv.Itoa(test), func(t *testing.T) {
|
||||||
|
var servers []string
|
||||||
|
for i := 0; i < test; i++ {
|
||||||
|
servers = append(servers, fmt.Sprintf("localhost:%d", i))
|
||||||
|
}
|
||||||
|
var b directBuilder
|
||||||
|
cc := new(mockedClientConn)
|
||||||
|
_, err := b.Build(resolver.Target{
|
||||||
|
Scheme: DirectScheme,
|
||||||
|
Endpoint: strings.Join(servers, ","),
|
||||||
|
}, cc, resolver.BuildOptions{})
|
||||||
|
assert.Nil(t, err)
|
||||||
|
size := mathx.MinInt(test, subsetSize)
|
||||||
|
assert.Equal(t, size, len(cc.state.Addresses))
|
||||||
|
m := make(map[string]lang.PlaceholderType)
|
||||||
|
for _, each := range cc.state.Addresses {
|
||||||
|
m[each.Addr] = lang.Placeholder
|
||||||
|
}
|
||||||
|
assert.Equal(t, size, len(m))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDirectBuilder_Scheme(t *testing.T) {
|
||||||
|
var b directBuilder
|
||||||
|
assert.Equal(t, DirectScheme, b.Scheme())
|
||||||
|
}
|
||||||
@@ -11,7 +11,9 @@ type discovBuilder struct{}
|
|||||||
|
|
||||||
func (d *discovBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (
|
func (d *discovBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (
|
||||||
resolver.Resolver, error) {
|
resolver.Resolver, error) {
|
||||||
hosts := strings.Split(target.Authority, EndpointSep)
|
hosts := strings.FieldsFunc(target.Authority, func(r rune) bool {
|
||||||
|
return r == EndpointSepChar
|
||||||
|
})
|
||||||
sub, err := discov.NewSubscriber(hosts, target.Endpoint)
|
sub, err := discov.NewSubscriber(hosts, target.Endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -1,17 +1,22 @@
|
|||||||
package resolver
|
package resolver
|
||||||
|
|
||||||
import "google.golang.org/grpc/resolver"
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"google.golang.org/grpc/resolver"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DirectScheme = "direct"
|
DirectScheme = "direct"
|
||||||
DiscovScheme = "discov"
|
DiscovScheme = "discov"
|
||||||
EndpointSep = ","
|
EndpointSepChar = ','
|
||||||
subsetSize = 32
|
subsetSize = 32
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
dirBuilder directBuilder
|
EndpointSep = fmt.Sprintf("%c", EndpointSepChar)
|
||||||
disBuilder discovBuilder
|
dirBuilder directBuilder
|
||||||
|
disBuilder discovBuilder
|
||||||
)
|
)
|
||||||
|
|
||||||
func RegisterResolver() {
|
func RegisterResolver() {
|
||||||
|
|||||||
36
rpcx/internal/resolver/resolver_test.go
Normal file
36
rpcx/internal/resolver/resolver_test.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package resolver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"google.golang.org/grpc/resolver"
|
||||||
|
"google.golang.org/grpc/serviceconfig"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNopResolver(t *testing.T) {
|
||||||
|
// make sure ResolveNow & Close don't panic
|
||||||
|
var r nopResolver
|
||||||
|
r.ResolveNow(resolver.ResolveNowOptions{})
|
||||||
|
r.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockedClientConn struct {
|
||||||
|
state resolver.State
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockedClientConn) UpdateState(state resolver.State) {
|
||||||
|
m.state = state
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockedClientConn) ReportError(err error) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockedClientConn) NewAddress(addresses []resolver.Address) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockedClientConn) NewServiceConfig(serviceConfig string) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockedClientConn) ParseServiceConfig(serviceConfigJSON string) *serviceconfig.ParseResult {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
200
rpcx/internal/serverinterceptors/authinterceptor_test.go
Normal file
200
rpcx/internal/serverinterceptors/authinterceptor_test.go
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
package serverinterceptors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/alicebob/miniredis"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/tal-tech/go-zero/core/stores/redis"
|
||||||
|
"github.com/tal-tech/go-zero/rpcx/internal/auth"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/metadata"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStreamAuthorizeInterceptor(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
app string
|
||||||
|
token string
|
||||||
|
strict bool
|
||||||
|
hasError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "strict=false",
|
||||||
|
strict: false,
|
||||||
|
hasError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "strict=true",
|
||||||
|
strict: true,
|
||||||
|
hasError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "strict=true,with token",
|
||||||
|
app: "foo",
|
||||||
|
token: "bar",
|
||||||
|
strict: true,
|
||||||
|
hasError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "strict=true,with error token",
|
||||||
|
app: "foo",
|
||||||
|
token: "error",
|
||||||
|
strict: true,
|
||||||
|
hasError: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
r := miniredis.NewMiniRedis()
|
||||||
|
assert.Nil(t, r.Start())
|
||||||
|
defer r.Close()
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
store := redis.NewRedis(r.Addr(), redis.NodeType)
|
||||||
|
if len(test.app) > 0 {
|
||||||
|
assert.Nil(t, store.Hset("apps", test.app, test.token))
|
||||||
|
defer store.Hdel("apps", test.app)
|
||||||
|
}
|
||||||
|
|
||||||
|
authenticator, err := auth.NewAuthenticator(store, "apps", test.strict)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
interceptor := StreamAuthorizeInterceptor(authenticator)
|
||||||
|
md := metadata.New(map[string]string{
|
||||||
|
"app": "foo",
|
||||||
|
"token": "bar",
|
||||||
|
})
|
||||||
|
ctx := metadata.NewIncomingContext(context.Background(), md)
|
||||||
|
stream := mockedStream{ctx: ctx}
|
||||||
|
err = interceptor(nil, stream, nil, func(srv interface{}, stream grpc.ServerStream) error {
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if test.hasError {
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
} else {
|
||||||
|
assert.Nil(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnaryAuthorizeInterceptor(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
app string
|
||||||
|
token string
|
||||||
|
strict bool
|
||||||
|
hasError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "strict=false",
|
||||||
|
strict: false,
|
||||||
|
hasError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "strict=true",
|
||||||
|
strict: true,
|
||||||
|
hasError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "strict=true,with token",
|
||||||
|
app: "foo",
|
||||||
|
token: "bar",
|
||||||
|
strict: true,
|
||||||
|
hasError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "strict=true,with error token",
|
||||||
|
app: "foo",
|
||||||
|
token: "error",
|
||||||
|
strict: true,
|
||||||
|
hasError: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
r := miniredis.NewMiniRedis()
|
||||||
|
assert.Nil(t, r.Start())
|
||||||
|
defer r.Close()
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
store := redis.NewRedis(r.Addr(), redis.NodeType)
|
||||||
|
if len(test.app) > 0 {
|
||||||
|
assert.Nil(t, store.Hset("apps", test.app, test.token))
|
||||||
|
defer store.Hdel("apps", test.app)
|
||||||
|
}
|
||||||
|
|
||||||
|
authenticator, err := auth.NewAuthenticator(store, "apps", test.strict)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
interceptor := UnaryAuthorizeInterceptor(authenticator)
|
||||||
|
md := metadata.New(map[string]string{
|
||||||
|
"app": "foo",
|
||||||
|
"token": "bar",
|
||||||
|
})
|
||||||
|
ctx := metadata.NewIncomingContext(context.Background(), md)
|
||||||
|
_, err = interceptor(ctx, nil, nil,
|
||||||
|
func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return nil, nil
|
||||||
|
})
|
||||||
|
if test.hasError {
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
} else {
|
||||||
|
assert.Nil(t, err)
|
||||||
|
}
|
||||||
|
if test.strict {
|
||||||
|
_, err = interceptor(context.Background(), nil, nil,
|
||||||
|
func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return nil, nil
|
||||||
|
})
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
|
var md metadata.MD
|
||||||
|
ctx := metadata.NewIncomingContext(context.Background(), md)
|
||||||
|
_, err = interceptor(ctx, nil, nil,
|
||||||
|
func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return nil, nil
|
||||||
|
})
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
|
md = metadata.New(map[string]string{
|
||||||
|
"app": "",
|
||||||
|
"token": "",
|
||||||
|
})
|
||||||
|
ctx = metadata.NewIncomingContext(context.Background(), md)
|
||||||
|
_, err = interceptor(ctx, nil, nil,
|
||||||
|
func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return nil, nil
|
||||||
|
})
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockedStream struct {
|
||||||
|
ctx context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockedStream) SetHeader(md metadata.MD) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockedStream) SendHeader(md metadata.MD) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockedStream) SetTrailer(md metadata.MD) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockedStream) Context() context.Context {
|
||||||
|
return m.ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockedStream) SendMsg(v interface{}) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockedStream) RecvMsg(v interface{}) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
31
rpcx/internal/serverinterceptors/crashinterceptor_test.go
Normal file
31
rpcx/internal/serverinterceptors/crashinterceptor_test.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package serverinterceptors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/tal-tech/go-zero/core/logx"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
logx.Disable()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStreamCrashInterceptor(t *testing.T) {
|
||||||
|
err := StreamCrashInterceptor(nil, nil, nil, func(
|
||||||
|
srv interface{}, stream grpc.ServerStream) error {
|
||||||
|
panic("mock panic")
|
||||||
|
})
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnaryCrashInterceptor(t *testing.T) {
|
||||||
|
interceptor := UnaryCrashInterceptor()
|
||||||
|
_, err := interceptor(context.Background(), nil, nil,
|
||||||
|
func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
panic("mock panic")
|
||||||
|
})
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
}
|
||||||
@@ -33,12 +33,12 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func UnaryPromMetricInterceptor() grpc.UnaryServerInterceptor {
|
func UnaryPromMetricInterceptor() grpc.UnaryServerInterceptor {
|
||||||
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (
|
||||||
|
interface{}, error) {
|
||||||
startTime := timex.Now()
|
startTime := timex.Now()
|
||||||
resp, err := handler(ctx, req)
|
resp, err := handler(ctx, req)
|
||||||
metricServerReqDur.Observe(int64(timex.Since(startTime)/time.Millisecond), info.FullMethod)
|
metricServerReqDur.Observe(int64(timex.Since(startTime)/time.Millisecond), info.FullMethod)
|
||||||
metricServerReqCodeTotal.Inc(info.FullMethod, strconv.Itoa(int(status.Code(err))))
|
metricServerReqCodeTotal.Inc(info.FullMethod, strconv.Itoa(int(status.Code(err))))
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package serverinterceptors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUnaryPromMetricInterceptor(t *testing.T) {
|
||||||
|
interceptor := UnaryPromMetricInterceptor()
|
||||||
|
_, err := interceptor(context.Background(), nil, &grpc.UnaryServerInfo{
|
||||||
|
FullMethod: "/",
|
||||||
|
}, func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return nil, nil
|
||||||
|
})
|
||||||
|
assert.Nil(t, err)
|
||||||
|
}
|
||||||
77
rpcx/internal/serverinterceptors/sheddinginterceptor_test.go
Normal file
77
rpcx/internal/serverinterceptors/sheddinginterceptor_test.go
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
package serverinterceptors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/tal-tech/go-zero/core/load"
|
||||||
|
"github.com/tal-tech/go-zero/core/stat"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUnarySheddingInterceptor(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
allow bool
|
||||||
|
handleErr error
|
||||||
|
expect error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "allow",
|
||||||
|
allow: true,
|
||||||
|
handleErr: nil,
|
||||||
|
expect: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "allow",
|
||||||
|
allow: true,
|
||||||
|
handleErr: context.DeadlineExceeded,
|
||||||
|
expect: context.DeadlineExceeded,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "reject",
|
||||||
|
allow: false,
|
||||||
|
handleErr: nil,
|
||||||
|
expect: load.ErrServiceOverloaded,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
test := test
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
shedder := mockedShedder{allow: test.allow}
|
||||||
|
metrics := stat.NewMetrics("mock")
|
||||||
|
interceptor := UnarySheddingInterceptor(shedder, metrics)
|
||||||
|
_, err := interceptor(context.Background(), nil, &grpc.UnaryServerInfo{
|
||||||
|
FullMethod: "/",
|
||||||
|
}, func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return nil, test.handleErr
|
||||||
|
})
|
||||||
|
assert.Equal(t, test.expect, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockedShedder struct {
|
||||||
|
allow bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockedShedder) Allow() (load.Promise, error) {
|
||||||
|
if m.allow {
|
||||||
|
return mockedPromise{}, nil
|
||||||
|
} else {
|
||||||
|
return nil, load.ErrServiceOverloaded
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockedPromise struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockedPromise) Pass() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockedPromise) Fail() {
|
||||||
|
}
|
||||||
32
rpcx/internal/serverinterceptors/statinterceptor_test.go
Normal file
32
rpcx/internal/serverinterceptors/statinterceptor_test.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package serverinterceptors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/tal-tech/go-zero/core/stat"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUnaryStatInterceptor(t *testing.T) {
|
||||||
|
metrics := stat.NewMetrics("mock")
|
||||||
|
interceptor := UnaryStatInterceptor(metrics)
|
||||||
|
_, err := interceptor(context.Background(), nil, &grpc.UnaryServerInfo{
|
||||||
|
FullMethod: "/",
|
||||||
|
}, func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return nil, nil
|
||||||
|
})
|
||||||
|
assert.Nil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnaryStatInterceptor_crash(t *testing.T) {
|
||||||
|
metrics := stat.NewMetrics("mock")
|
||||||
|
interceptor := UnaryStatInterceptor(metrics)
|
||||||
|
_, err := interceptor(context.Background(), nil, &grpc.UnaryServerInfo{
|
||||||
|
FullMethod: "/",
|
||||||
|
}, func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
panic("error")
|
||||||
|
})
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
}
|
||||||
41
rpcx/internal/serverinterceptors/timeoutinterceptor_test.go
Normal file
41
rpcx/internal/serverinterceptors/timeoutinterceptor_test.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package serverinterceptors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUnaryTimeoutInterceptor(t *testing.T) {
|
||||||
|
interceptor := UnaryTimeoutInterceptor(time.Millisecond * 10)
|
||||||
|
_, err := interceptor(context.Background(), nil, &grpc.UnaryServerInfo{
|
||||||
|
FullMethod: "/",
|
||||||
|
}, func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return nil, nil
|
||||||
|
})
|
||||||
|
assert.Nil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnaryTimeoutInterceptor_timeout(t *testing.T) {
|
||||||
|
const timeout = time.Millisecond * 10
|
||||||
|
interceptor := UnaryTimeoutInterceptor(timeout)
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(1)
|
||||||
|
_, err := interceptor(ctx, nil, &grpc.UnaryServerInfo{
|
||||||
|
FullMethod: "/",
|
||||||
|
}, func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
defer wg.Done()
|
||||||
|
tm, ok := ctx.Deadline()
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.True(t, tm.Before(time.Now().Add(timeout+time.Millisecond)))
|
||||||
|
return nil, nil
|
||||||
|
})
|
||||||
|
wg.Wait()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
}
|
||||||
48
rpcx/internal/serverinterceptors/tracinginterceptor_test.go
Normal file
48
rpcx/internal/serverinterceptors/tracinginterceptor_test.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package serverinterceptors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/tal-tech/go-zero/core/trace/tracespec"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/metadata"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUnaryTracingInterceptor(t *testing.T) {
|
||||||
|
interceptor := UnaryTracingInterceptor("foo")
|
||||||
|
var run int32
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(1)
|
||||||
|
_, err := interceptor(context.Background(), nil, &grpc.UnaryServerInfo{
|
||||||
|
FullMethod: "/",
|
||||||
|
}, func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
defer wg.Done()
|
||||||
|
atomic.AddInt32(&run, 1)
|
||||||
|
return nil, nil
|
||||||
|
})
|
||||||
|
wg.Wait()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, int32(1), atomic.LoadInt32(&run))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnaryTracingInterceptor_GrpcFormat(t *testing.T) {
|
||||||
|
interceptor := UnaryTracingInterceptor("foo")
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(1)
|
||||||
|
var md metadata.MD
|
||||||
|
ctx := metadata.NewIncomingContext(context.Background(), md)
|
||||||
|
_, err := interceptor(ctx, nil, &grpc.UnaryServerInfo{
|
||||||
|
FullMethod: "/",
|
||||||
|
}, func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
defer wg.Done()
|
||||||
|
assert.True(t, len(ctx.Value(tracespec.TracingKey).(tracespec.Trace).TraceId()) > 0)
|
||||||
|
assert.True(t, len(ctx.Value(tracespec.TracingKey).(tracespec.Trace).SpanId()) > 0)
|
||||||
|
return nil, nil
|
||||||
|
})
|
||||||
|
wg.Wait()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
}
|
||||||
@@ -8,7 +8,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func BuildDirectTarget(endpoints []string) string {
|
func BuildDirectTarget(endpoints []string) string {
|
||||||
return fmt.Sprintf("%s:///%s", resolver.DirectScheme, strings.Join(endpoints, resolver.EndpointSep))
|
return fmt.Sprintf("%s:///%s", resolver.DirectScheme,
|
||||||
|
strings.Join(endpoints, resolver.EndpointSep))
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildDiscovTarget(endpoints []string, key string) string {
|
func BuildDiscovTarget(endpoints []string, key string) string {
|
||||||
|
|||||||
17
rpcx/internal/target_test.go
Normal file
17
rpcx/internal/target_test.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBuildDirectTarget(t *testing.T) {
|
||||||
|
target := BuildDirectTarget([]string{"localhost:123", "localhost:456"})
|
||||||
|
assert.Equal(t, "direct:///localhost:123,localhost:456", target)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildDiscovTarget(t *testing.T) {
|
||||||
|
target := BuildDiscovTarget([]string{"localhost:123", "localhost:456"}, "foo")
|
||||||
|
assert.Equal(t, "discov://localhost:123,localhost:456/foo", target)
|
||||||
|
}
|
||||||
@@ -19,29 +19,24 @@ const apiTemplate = `info(
|
|||||||
email: {{.gitEmail}}
|
email: {{.gitEmail}}
|
||||||
)
|
)
|
||||||
|
|
||||||
type request struct{
|
type request struct {
|
||||||
// TODO: add members here and delete this comment
|
// TODO: add members here and delete this comment
|
||||||
}
|
}
|
||||||
|
|
||||||
type response struct{
|
type response struct {
|
||||||
// TODO: add members here and delete this comment
|
// TODO: add members here and delete this comment
|
||||||
}
|
}
|
||||||
|
|
||||||
@server(
|
|
||||||
port: // TODO: add port here and delete this comment
|
|
||||||
)
|
|
||||||
service {{.serviceName}} {
|
service {{.serviceName}} {
|
||||||
@server(
|
@server(
|
||||||
handler: // TODO: set handler name and delete this comment
|
handler: // TODO: set handler name and delete this comment
|
||||||
)
|
)
|
||||||
// TODO: edit the below line
|
get /users/id/:userId(request) returns(response)
|
||||||
// get /users/id/:userId(request) returns(response)
|
|
||||||
|
|
||||||
@server(
|
@server(
|
||||||
handler: // TODO: set handler name and delete this comment
|
handler: // TODO: set handler name and delete this comment
|
||||||
)
|
)
|
||||||
// TODO: edit the below line
|
post /users/create(request)
|
||||||
// post /users/create(request)
|
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|||||||
@@ -191,25 +191,64 @@ var (
|
|||||||
{
|
{
|
||||||
Name: "model",
|
Name: "model",
|
||||||
Usage: "generate model code",
|
Usage: "generate model code",
|
||||||
Flags: []cli.Flag{
|
Subcommands: []cli.Command{
|
||||||
cli.StringFlag{
|
{
|
||||||
Name: "src, s",
|
Name: "mysql",
|
||||||
Usage: "the file path of the ddl source file",
|
Usage: `generate mysql model"`,
|
||||||
},
|
Subcommands: []cli.Command{
|
||||||
cli.StringFlag{
|
{
|
||||||
Name: "dir, d",
|
Name: "ddl",
|
||||||
Usage: "the target dir",
|
Usage: `generate mysql model from ddl"`,
|
||||||
},
|
Flags: []cli.Flag{
|
||||||
cli.BoolFlag{
|
cli.StringFlag{
|
||||||
Name: "cache, c",
|
Name: "src, s",
|
||||||
Usage: "generate code with cache [optional]",
|
Usage: "the file path of the ddl source file",
|
||||||
},
|
},
|
||||||
cli.BoolFlag{
|
cli.StringFlag{
|
||||||
Name: "idea",
|
Name: "dir, d",
|
||||||
Usage: "for idea plugin [optional]",
|
Usage: "the target dir",
|
||||||
|
},
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "cache, c",
|
||||||
|
Usage: "generate code with cache [optional]",
|
||||||
|
},
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "idea",
|
||||||
|
Usage: "for idea plugin [optional]",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: command.MysqlDDL,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "datasource",
|
||||||
|
Usage: `generate model from datasource"`,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "url",
|
||||||
|
Usage: `the data source of database,like "root:password@tcp(127.0.0.1:3306)/database"`,
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "table, t",
|
||||||
|
Usage: `source table,tables separated by commas,like "user,course"`,
|
||||||
|
},
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "cache, c",
|
||||||
|
Usage: "generate code with cache [optional]",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "dir, d",
|
||||||
|
Usage: "the target dir",
|
||||||
|
},
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "idea",
|
||||||
|
Usage: "for idea plugin [optional]",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: command.MyDataSource,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: command.Mysql,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "config",
|
Name: "config",
|
||||||
|
|||||||
10
tools/goctl/model/sql/CHANGELOG.md
Normal file
10
tools/goctl/model/sql/CHANGELOG.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# Change log
|
||||||
|
|
||||||
|
# 2020-08-20
|
||||||
|
* 新增支持通过连接数据库生成model
|
||||||
|
* 支持数据库多表生成
|
||||||
|
* 优化stringx
|
||||||
|
|
||||||
|
# 2020-08-19
|
||||||
|
* 重构model代码生成逻辑
|
||||||
|
* 实现从ddl解析表信息生成代码
|
||||||
@@ -4,21 +4,28 @@ goctl model 为go-zero下的工具模块中的组件之一,目前支持识别m
|
|||||||
|
|
||||||
# 快速开始
|
# 快速开始
|
||||||
|
|
||||||
```
|
* 通过ddl生成
|
||||||
$ goctl model -src ./sql/user.sql -dir ./model -c true
|
|
||||||
```
|
|
||||||
|
|
||||||
详情用法请参考[example](https://github.com/tal-tech/go-zero/tools/goctl/model/sql/example)
|
```shell script
|
||||||
|
$ goctl model mysql ddl -src="./sql/user.sql" -dir="./sql/model" -c=true
|
||||||
|
```
|
||||||
|
|
||||||
执行上述命令后即可快速生成CURD代码。
|
执行上述命令后即可快速生成CURD代码。
|
||||||
|
|
||||||
```
|
```
|
||||||
model
|
model
|
||||||
│ ├── error.go
|
│ ├── error.go
|
||||||
│ └── usermodel.go
|
│ └── usermodel.go
|
||||||
```
|
```
|
||||||
|
* 通过datasource生成
|
||||||
|
|
||||||
|
```shell script
|
||||||
|
$ goctl model mysql datasource -url="user:password@tcp(127.0.0.1:3306)/database" -table="table1,table2" -dir="./model"
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
> 详情用法请参考[example](https://github.com/tal-tech/go-zero/tree/master/tools/goctl/model/sql/example)
|
||||||
|
|
||||||
> 注意:这里的目录结构中有usercoursemodel.go目录,在example中我为了体现带cache与不带cache代码的区别,因此将sql文件分别使用了独立的sql文件(user.sql&course.sql),在实际项目开发中你可以将ddl建表语句放在一个sql文件中,`goctl model`会自动解析并分割,最终按照每个ddl建表语句为单位生成独立的go文件。
|
|
||||||
|
|
||||||
* 生成代码示例
|
* 生成代码示例
|
||||||
|
|
||||||
@@ -174,22 +181,22 @@ model
|
|||||||
# 用法
|
# 用法
|
||||||
|
|
||||||
```
|
```
|
||||||
$ goctl model -h
|
$ goctl model mysql -h
|
||||||
```
|
```
|
||||||
|
|
||||||
```
|
```
|
||||||
NAME:
|
NAME:
|
||||||
goctl model - generate model code
|
goctl model mysql - generate mysql model"
|
||||||
|
|
||||||
USAGE:
|
USAGE:
|
||||||
goctl model [command options] [arguments...]
|
goctl model mysql command [command options] [arguments...]
|
||||||
|
|
||||||
|
COMMANDS:
|
||||||
|
ddl generate mysql model from ddl"
|
||||||
|
datasource generate model from datasource"
|
||||||
|
|
||||||
OPTIONS:
|
OPTIONS:
|
||||||
--src value, -s value the file path of the ddl source file
|
--help, -h show help
|
||||||
--dir value, -d value the target dir
|
|
||||||
--cache, -c generate code with cache [optional]
|
|
||||||
--idea for idea plugin [optional]
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
# 生成规则
|
# 生成规则
|
||||||
@@ -198,22 +205,43 @@ OPTIONS:
|
|||||||
|
|
||||||
我们默认用户在建表时会创建createTime、updateTime字段(忽略大小写、下划线命名风格)且默认值均为`CURRENT_TIMESTAMP`,而updateTime支持`ON UPDATE CURRENT_TIMESTAMP`,对于这两个字段生成`insert`、`update`时会被移除,不在赋值范畴内,当然,如果你不需要这两个字段那也无大碍。
|
我们默认用户在建表时会创建createTime、updateTime字段(忽略大小写、下划线命名风格)且默认值均为`CURRENT_TIMESTAMP`,而updateTime支持`ON UPDATE CURRENT_TIMESTAMP`,对于这两个字段生成`insert`、`update`时会被移除,不在赋值范畴内,当然,如果你不需要这两个字段那也无大碍。
|
||||||
* 带缓存模式
|
* 带缓存模式
|
||||||
|
* ddl
|
||||||
|
|
||||||
```
|
```shell script
|
||||||
$ goctl model -src {filename} -dir {dir} -cache true
|
$ goctl model mysql -src={filename} -dir={dir} -cache=true
|
||||||
```
|
```
|
||||||
|
* datasource
|
||||||
|
|
||||||
|
```shell script
|
||||||
|
$ goctl model mysql datasource -url={datasource} -table={tables} -dir={dir} -cache=true
|
||||||
|
```
|
||||||
|
|
||||||
目前仅支持redis缓存,如果选择带缓存模式,即生成的`FindOne(ByXxx)`&`Delete`代码会生成带缓存逻辑的代码,目前仅支持单索引字段(除全文索引外),对于联合索引我们默认认为不需要带缓存,且不属于通用型代码,因此没有放在代码生成行列,如example中user表中的`id`、`name`、`mobile`字段均属于单字段索引。
|
目前仅支持redis缓存,如果选择带缓存模式,即生成的`FindOne(ByXxx)`&`Delete`代码会生成带缓存逻辑的代码,目前仅支持单索引字段(除全文索引外),对于联合索引我们默认认为不需要带缓存,且不属于通用型代码,因此没有放在代码生成行列,如example中user表中的`id`、`name`、`mobile`字段均属于单字段索引。
|
||||||
|
|
||||||
* 不带缓存模式
|
* 不带缓存模式
|
||||||
|
|
||||||
```
|
* ddl
|
||||||
$ goctl model -src {filename} -dir {dir}
|
|
||||||
```
|
```shell script
|
||||||
|
$ goctl model -src={filename} -dir={dir}
|
||||||
|
```
|
||||||
|
* datasource
|
||||||
|
|
||||||
|
```shell script
|
||||||
|
$ goctl model mysql datasource -url={datasource} -table={tables} -dir={dir}
|
||||||
|
```
|
||||||
or
|
or
|
||||||
```
|
* ddl
|
||||||
$ goctl model -src {filename} -dir {dir} -cache false
|
|
||||||
```
|
```shell script
|
||||||
|
$ goctl model -src={filename} -dir={dir} -cache=false
|
||||||
|
```
|
||||||
|
* datasource
|
||||||
|
|
||||||
|
```shell script
|
||||||
|
$ goctl model mysql datasource -url={datasource} -table={tables} -dir={dir} -cache=false
|
||||||
|
```
|
||||||
|
|
||||||
生成代码仅基本的CURD结构。
|
生成代码仅基本的CURD结构。
|
||||||
|
|
||||||
# 缓存
|
# 缓存
|
||||||
@@ -238,10 +266,6 @@ OPTIONS:
|
|||||||
|
|
||||||
# QA
|
# QA
|
||||||
|
|
||||||
* goctl model支持根据数据库连接后选择表生成代码吗?
|
|
||||||
|
|
||||||
目前暂时不支持,在后面会向这个方向扩展。
|
|
||||||
|
|
||||||
* goctl model除了命令行模式,支持插件模式吗?
|
* goctl model除了命令行模式,支持插件模式吗?
|
||||||
|
|
||||||
很快支持idea插件。
|
很快支持idea插件。
|
||||||
|
|||||||
@@ -1,24 +1,84 @@
|
|||||||
package command
|
package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/tal-tech/go-zero/core/collection"
|
||||||
|
"github.com/tal-tech/go-zero/core/logx"
|
||||||
|
"github.com/tal-tech/go-zero/core/stores/sqlx"
|
||||||
"github.com/tal-tech/go-zero/tools/goctl/model/sql/gen"
|
"github.com/tal-tech/go-zero/tools/goctl/model/sql/gen"
|
||||||
|
"github.com/tal-tech/go-zero/tools/goctl/model/sql/model"
|
||||||
"github.com/tal-tech/go-zero/tools/goctl/util/console"
|
"github.com/tal-tech/go-zero/tools/goctl/util/console"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Mysql(ctx *cli.Context) error {
|
const (
|
||||||
src := ctx.String("src")
|
flagSrc = "src"
|
||||||
dir := ctx.String("dir")
|
flagDir = "dir"
|
||||||
cache := ctx.Bool("cache")
|
flagCache = "cache"
|
||||||
idea := ctx.Bool("idea")
|
flagIdea = "idea"
|
||||||
var log console.Console
|
flagUrl = "url"
|
||||||
if idea {
|
flagTable = "table"
|
||||||
log = console.NewIdeaConsole()
|
)
|
||||||
} else {
|
|
||||||
log = console.NewColorConsole()
|
func MysqlDDL(ctx *cli.Context) error {
|
||||||
|
src := ctx.String(flagSrc)
|
||||||
|
dir := ctx.String(flagDir)
|
||||||
|
cache := ctx.Bool(flagCache)
|
||||||
|
idea := ctx.Bool(flagIdea)
|
||||||
|
log := console.NewConsole(idea)
|
||||||
|
fileSrc, err := filepath.Abs(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
generator := gen.NewDefaultGenerator(src, dir, gen.WithConsoleOption(log))
|
data, err := ioutil.ReadFile(fileSrc)
|
||||||
err := generator.Start(cache)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
source := string(data)
|
||||||
|
generator := gen.NewDefaultGenerator(source, dir, gen.WithConsoleOption(log))
|
||||||
|
err = generator.Start(cache)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("%v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func MyDataSource(ctx *cli.Context) error {
|
||||||
|
url := strings.TrimSpace(ctx.String(flagUrl))
|
||||||
|
dir := strings.TrimSpace(ctx.String(flagDir))
|
||||||
|
cache := ctx.Bool(flagCache)
|
||||||
|
idea := ctx.Bool(flagIdea)
|
||||||
|
table := strings.TrimSpace(ctx.String(flagTable))
|
||||||
|
log := console.NewConsole(idea)
|
||||||
|
if len(url) == 0 {
|
||||||
|
log.Error("%v", "expected data source of mysql, but is empty")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if len(table) == 0 {
|
||||||
|
log.Error("%v", "expected table(s), but nothing found")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
logx.Disable()
|
||||||
|
conn := sqlx.NewMysql(url)
|
||||||
|
m := model.NewDDLModel(conn)
|
||||||
|
tables := collection.NewSet()
|
||||||
|
for _, item := range strings.Split(table, ",") {
|
||||||
|
item = strings.TrimSpace(item)
|
||||||
|
if len(item) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tables.AddStr(item)
|
||||||
|
}
|
||||||
|
ddl, err := m.ShowDDL(tables.KeysStr()...)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("%v", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
generator := gen.NewDefaultGenerator(strings.Join(ddl, "\n"), dir, gen.WithConsoleOption(log))
|
||||||
|
err = generator.Start(cache)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("%v", err)
|
log.Error("%v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# generate usermodel with cache
|
# generate model with cache from ddl
|
||||||
goctl model -src ./sql/user.sql -dir ./model -c true
|
goctl model mysql ddl -src="./sql/user.sql" -dir="./sql/model" -c=true
|
||||||
|
|
||||||
|
# generate model with cache from data source
|
||||||
|
goctl model mysql datasource -url="user:password@tcp(127.0.0.1:3306)/database" -table="table1,table2" -dir="./model"
|
||||||
@@ -27,14 +27,14 @@ func genDelete(table Table, withCache bool) (string, error) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
camel := table.Name.Snake2Camel()
|
camel := table.Name.ToCamel()
|
||||||
output, err := templatex.With("delete").
|
output, err := templatex.With("delete").
|
||||||
Parse(template.Delete).
|
Parse(template.Delete).
|
||||||
Execute(map[string]interface{}{
|
Execute(map[string]interface{}{
|
||||||
"upperStartCamelObject": camel,
|
"upperStartCamelObject": camel,
|
||||||
"withCache": withCache,
|
"withCache": withCache,
|
||||||
"containsIndexCache": containsIndexCache,
|
"containsIndexCache": containsIndexCache,
|
||||||
"lowerStartCamelPrimaryKey": stringx.From(table.PrimaryKey.Name.Snake2Camel()).LowerStart(),
|
"lowerStartCamelPrimaryKey": stringx.From(table.PrimaryKey.Name.ToCamel()).UnTitle(),
|
||||||
"dataType": table.PrimaryKey.DataType,
|
"dataType": table.PrimaryKey.DataType,
|
||||||
"keys": strings.Join(keySet.KeysStr(), "\n"),
|
"keys": strings.Join(keySet.KeysStr(), "\n"),
|
||||||
"originalPrimaryKey": table.PrimaryKey.Name.Source(),
|
"originalPrimaryKey": table.PrimaryKey.Name.Source(),
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ func genField(field parser.Field) (string, error) {
|
|||||||
output, err := templatex.With("types").
|
output, err := templatex.With("types").
|
||||||
Parse(template.Field).
|
Parse(template.Field).
|
||||||
Execute(map[string]interface{}{
|
Execute(map[string]interface{}{
|
||||||
"name": field.Name.Snake2Camel(),
|
"name": field.Name.ToCamel(),
|
||||||
"type": field.DataType,
|
"type": field.DataType,
|
||||||
"tag": tag,
|
"tag": tag,
|
||||||
"hasComment": field.Comment != "",
|
"hasComment": field.Comment != "",
|
||||||
|
|||||||
@@ -7,15 +7,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func genFindOne(table Table, withCache bool) (string, error) {
|
func genFindOne(table Table, withCache bool) (string, error) {
|
||||||
camel := table.Name.Snake2Camel()
|
camel := table.Name.ToCamel()
|
||||||
output, err := templatex.With("findOne").
|
output, err := templatex.With("findOne").
|
||||||
Parse(template.FindOne).
|
Parse(template.FindOne).
|
||||||
Execute(map[string]interface{}{
|
Execute(map[string]interface{}{
|
||||||
"withCache": withCache,
|
"withCache": withCache,
|
||||||
"upperStartCamelObject": camel,
|
"upperStartCamelObject": camel,
|
||||||
"lowerStartCamelObject": stringx.From(camel).LowerStart(),
|
"lowerStartCamelObject": stringx.From(camel).UnTitle(),
|
||||||
"originalPrimaryKey": table.PrimaryKey.Name.Source(),
|
"originalPrimaryKey": table.PrimaryKey.Name.Source(),
|
||||||
"lowerStartCamelPrimaryKey": stringx.From(table.PrimaryKey.Name.Snake2Camel()).LowerStart(),
|
"lowerStartCamelPrimaryKey": stringx.From(table.PrimaryKey.Name.ToCamel()).UnTitle(),
|
||||||
"dataType": table.PrimaryKey.DataType,
|
"dataType": table.PrimaryKey.DataType,
|
||||||
"cacheKey": table.CacheKey[table.PrimaryKey.Name.Source()].KeyExpression,
|
"cacheKey": table.CacheKey[table.PrimaryKey.Name.Source()].KeyExpression,
|
||||||
"cacheKeyVariable": table.CacheKey[table.PrimaryKey.Name.Source()].Variable,
|
"cacheKeyVariable": table.CacheKey[table.PrimaryKey.Name.Source()].Variable,
|
||||||
|
|||||||
@@ -12,23 +12,23 @@ import (
|
|||||||
func genFineOneByField(table Table, withCache bool) (string, error) {
|
func genFineOneByField(table Table, withCache bool) (string, error) {
|
||||||
t := templatex.With("findOneByField").Parse(template.FindOneByField)
|
t := templatex.With("findOneByField").Parse(template.FindOneByField)
|
||||||
var list []string
|
var list []string
|
||||||
camelTableName := table.Name.Snake2Camel()
|
camelTableName := table.Name.ToCamel()
|
||||||
for _, field := range table.Fields {
|
for _, field := range table.Fields {
|
||||||
if field.IsPrimaryKey || !field.IsKey {
|
if field.IsPrimaryKey || !field.IsKey {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
camelFieldName := field.Name.Snake2Camel()
|
camelFieldName := field.Name.ToCamel()
|
||||||
output, err := t.Execute(map[string]interface{}{
|
output, err := t.Execute(map[string]interface{}{
|
||||||
"upperStartCamelObject": camelTableName,
|
"upperStartCamelObject": camelTableName,
|
||||||
"upperField": camelFieldName,
|
"upperField": camelFieldName,
|
||||||
"in": fmt.Sprintf("%s %s", stringx.From(camelFieldName).LowerStart(), field.DataType),
|
"in": fmt.Sprintf("%s %s", stringx.From(camelFieldName).UnTitle(), field.DataType),
|
||||||
"withCache": withCache,
|
"withCache": withCache,
|
||||||
"cacheKey": table.CacheKey[field.Name.Source()].KeyExpression,
|
"cacheKey": table.CacheKey[field.Name.Source()].KeyExpression,
|
||||||
"cacheKeyVariable": table.CacheKey[field.Name.Source()].Variable,
|
"cacheKeyVariable": table.CacheKey[field.Name.Source()].Variable,
|
||||||
"primaryKeyLeft": table.CacheKey[table.PrimaryKey.Name.Source()].Left,
|
"primaryKeyLeft": table.CacheKey[table.PrimaryKey.Name.Source()].Left,
|
||||||
"lowerStartCamelObject": stringx.From(camelTableName).LowerStart(),
|
"lowerStartCamelObject": stringx.From(camelTableName).UnTitle(),
|
||||||
"lowerStartCamelField": stringx.From(camelFieldName).LowerStart(),
|
"lowerStartCamelField": stringx.From(camelFieldName).UnTitle(),
|
||||||
"upperStartCamelPrimaryKey": table.PrimaryKey.Name.Snake2Camel(),
|
"upperStartCamelPrimaryKey": table.PrimaryKey.Name.ToCamel(),
|
||||||
"originalField": field.Name.Source(),
|
"originalField": field.Name.Source(),
|
||||||
"originalPrimaryField": table.PrimaryKey.Name.Source(),
|
"originalPrimaryField": table.PrimaryKey.Name.Source(),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -23,21 +23,17 @@ const (
|
|||||||
type (
|
type (
|
||||||
defaultGenerator struct {
|
defaultGenerator struct {
|
||||||
source string
|
source string
|
||||||
src string
|
|
||||||
dir string
|
dir string
|
||||||
console.Console
|
console.Console
|
||||||
}
|
}
|
||||||
Option func(generator *defaultGenerator)
|
Option func(generator *defaultGenerator)
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewDefaultGenerator(src, dir string, opt ...Option) *defaultGenerator {
|
func NewDefaultGenerator(source, dir string, opt ...Option) *defaultGenerator {
|
||||||
if src == "" {
|
|
||||||
src = pwd
|
|
||||||
}
|
|
||||||
if dir == "" {
|
if dir == "" {
|
||||||
dir = pwd
|
dir = pwd
|
||||||
}
|
}
|
||||||
generator := &defaultGenerator{src: src, dir: dir}
|
generator := &defaultGenerator{source: source, dir: dir}
|
||||||
var optionList []Option
|
var optionList []Option
|
||||||
optionList = append(optionList, newDefaultOption())
|
optionList = append(optionList, newDefaultOption())
|
||||||
optionList = append(optionList, opt...)
|
optionList = append(optionList, opt...)
|
||||||
@@ -60,10 +56,6 @@ func newDefaultOption() Option {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *defaultGenerator) Start(withCache bool) error {
|
func (g *defaultGenerator) Start(withCache bool) error {
|
||||||
fileSrc, err := filepath.Abs(g.src)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
dirAbs, err := filepath.Abs(g.dir)
|
dirAbs, err := filepath.Abs(g.dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -72,21 +64,16 @@ func (g *defaultGenerator) Start(withCache bool) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
data, err := ioutil.ReadFile(fileSrc)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
g.source = string(data)
|
|
||||||
modelList, err := g.genFromDDL(withCache)
|
modelList, err := g.genFromDDL(withCache)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for tableName, code := range modelList {
|
for tableName, code := range modelList {
|
||||||
name := fmt.Sprintf("%smodel.go", strings.ToLower(stringx.From(tableName).Snake2Camel()))
|
name := fmt.Sprintf("%smodel.go", strings.ToLower(stringx.From(tableName).ToCamel()))
|
||||||
filename := filepath.Join(dirAbs, name)
|
filename := filepath.Join(dirAbs, name)
|
||||||
if util.FileExists(filename) {
|
if util.FileExists(filename) {
|
||||||
g.Warning("%s already exists,ignored.", name)
|
g.Warning("%s already exists, ignored.", name)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
err = ioutil.WriteFile(filename, []byte(code), os.ModePerm)
|
err = ioutil.WriteFile(filename, []byte(code), os.ModePerm)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ func genInsert(table Table, withCache bool) (string, error) {
|
|||||||
expressions := make([]string, 0)
|
expressions := make([]string, 0)
|
||||||
expressionValues := make([]string, 0)
|
expressionValues := make([]string, 0)
|
||||||
for _, filed := range table.Fields {
|
for _, filed := range table.Fields {
|
||||||
camel := filed.Name.Snake2Camel()
|
camel := filed.Name.ToCamel()
|
||||||
if camel == "CreateTime" || camel == "UpdateTime" {
|
if camel == "CreateTime" || camel == "UpdateTime" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -22,13 +22,13 @@ func genInsert(table Table, withCache bool) (string, error) {
|
|||||||
expressions = append(expressions, "?")
|
expressions = append(expressions, "?")
|
||||||
expressionValues = append(expressionValues, "data."+camel)
|
expressionValues = append(expressionValues, "data."+camel)
|
||||||
}
|
}
|
||||||
camel := table.Name.Snake2Camel()
|
camel := table.Name.ToCamel()
|
||||||
output, err := templatex.With("insert").
|
output, err := templatex.With("insert").
|
||||||
Parse(template.Insert).
|
Parse(template.Insert).
|
||||||
Execute(map[string]interface{}{
|
Execute(map[string]interface{}{
|
||||||
"withCache": withCache,
|
"withCache": withCache,
|
||||||
"upperStartCamelObject": camel,
|
"upperStartCamelObject": camel,
|
||||||
"lowerStartCamelObject": stringx.From(camel).LowerStart(),
|
"lowerStartCamelObject": stringx.From(camel).UnTitle(),
|
||||||
"expression": strings.Join(expressions, ", "),
|
"expression": strings.Join(expressions, ", "),
|
||||||
"expressionValues": strings.Join(expressionValues, ", "),
|
"expressionValues": strings.Join(expressionValues, ", "),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -26,14 +26,14 @@ type (
|
|||||||
func genCacheKeys(table parser.Table) (map[string]Key, error) {
|
func genCacheKeys(table parser.Table) (map[string]Key, error) {
|
||||||
fields := table.Fields
|
fields := table.Fields
|
||||||
m := make(map[string]Key)
|
m := make(map[string]Key)
|
||||||
camelTableName := table.Name.Snake2Camel()
|
camelTableName := table.Name.ToCamel()
|
||||||
lowerStartCamelTableName := stringx.From(camelTableName).LowerStart()
|
lowerStartCamelTableName := stringx.From(camelTableName).UnTitle()
|
||||||
for _, field := range fields {
|
for _, field := range fields {
|
||||||
if !field.IsKey {
|
if !field.IsKey {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
camelFieldName := field.Name.Snake2Camel()
|
camelFieldName := field.Name.ToCamel()
|
||||||
lowerStartCamelFieldName := stringx.From(camelFieldName).LowerStart()
|
lowerStartCamelFieldName := stringx.From(camelFieldName).UnTitle()
|
||||||
left := fmt.Sprintf("cache%s%sPrefix", camelTableName, camelFieldName)
|
left := fmt.Sprintf("cache%s%sPrefix", camelTableName, camelFieldName)
|
||||||
right := fmt.Sprintf("cache#%s#%s#", camelTableName, lowerStartCamelFieldName)
|
right := fmt.Sprintf("cache#%s#%s#", camelTableName, lowerStartCamelFieldName)
|
||||||
variable := fmt.Sprintf("%s%sKey", lowerStartCamelTableName, camelFieldName)
|
variable := fmt.Sprintf("%s%sKey", lowerStartCamelTableName, camelFieldName)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ func genNew(table Table, withCache bool) (string, error) {
|
|||||||
Parse(template.New).
|
Parse(template.New).
|
||||||
Execute(map[string]interface{}{
|
Execute(map[string]interface{}{
|
||||||
"withCache": withCache,
|
"withCache": withCache,
|
||||||
"upperStartCamelObject": table.Name.Snake2Camel(),
|
"upperStartCamelObject": table.Name.ToCamel(),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ func genTypes(table Table, withCache bool) (string, error) {
|
|||||||
Parse(template.Types).
|
Parse(template.Types).
|
||||||
Execute(map[string]interface{}{
|
Execute(map[string]interface{}{
|
||||||
"withCache": withCache,
|
"withCache": withCache,
|
||||||
"upperStartCamelObject": table.Name.Snake2Camel(),
|
"upperStartCamelObject": table.Name.ToCamel(),
|
||||||
"fields": fieldsString,
|
"fields": fieldsString,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
func genUpdate(table Table, withCache bool) (string, error) {
|
func genUpdate(table Table, withCache bool) (string, error) {
|
||||||
expressionValues := make([]string, 0)
|
expressionValues := make([]string, 0)
|
||||||
for _, filed := range table.Fields {
|
for _, filed := range table.Fields {
|
||||||
camel := filed.Name.Snake2Camel()
|
camel := filed.Name.ToCamel()
|
||||||
if camel == "CreateTime" || camel == "UpdateTime" {
|
if camel == "CreateTime" || camel == "UpdateTime" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -20,8 +20,8 @@ func genUpdate(table Table, withCache bool) (string, error) {
|
|||||||
}
|
}
|
||||||
expressionValues = append(expressionValues, "data."+camel)
|
expressionValues = append(expressionValues, "data."+camel)
|
||||||
}
|
}
|
||||||
expressionValues = append(expressionValues, "data."+table.PrimaryKey.Name.Snake2Camel())
|
expressionValues = append(expressionValues, "data."+table.PrimaryKey.Name.ToCamel())
|
||||||
camelTableName := table.Name.Snake2Camel()
|
camelTableName := table.Name.ToCamel()
|
||||||
output, err := templatex.With("update").
|
output, err := templatex.With("update").
|
||||||
Parse(template.Update).
|
Parse(template.Update).
|
||||||
Execute(map[string]interface{}{
|
Execute(map[string]interface{}{
|
||||||
@@ -29,7 +29,7 @@ func genUpdate(table Table, withCache bool) (string, error) {
|
|||||||
"upperStartCamelObject": camelTableName,
|
"upperStartCamelObject": camelTableName,
|
||||||
"primaryCacheKey": table.CacheKey[table.PrimaryKey.Name.Source()].DataKeyExpression,
|
"primaryCacheKey": table.CacheKey[table.PrimaryKey.Name.Source()].DataKeyExpression,
|
||||||
"primaryKeyVariable": table.CacheKey[table.PrimaryKey.Name.Source()].Variable,
|
"primaryKeyVariable": table.CacheKey[table.PrimaryKey.Name.Source()].Variable,
|
||||||
"lowerStartCamelObject": stringx.From(camelTableName).LowerStart(),
|
"lowerStartCamelObject": stringx.From(camelTableName).UnTitle(),
|
||||||
"originalPrimaryKey": table.PrimaryKey.Name.Source(),
|
"originalPrimaryKey": table.PrimaryKey.Name.Source(),
|
||||||
"expressionValues": strings.Join(expressionValues, ", "),
|
"expressionValues": strings.Join(expressionValues, ", "),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -13,12 +13,12 @@ func genVars(table Table, withCache bool) (string, error) {
|
|||||||
for _, v := range table.CacheKey {
|
for _, v := range table.CacheKey {
|
||||||
keys = append(keys, v.VarExpression)
|
keys = append(keys, v.VarExpression)
|
||||||
}
|
}
|
||||||
camel := table.Name.Snake2Camel()
|
camel := table.Name.ToCamel()
|
||||||
output, err := templatex.With("var").
|
output, err := templatex.With("var").
|
||||||
Parse(template.Vars).
|
Parse(template.Vars).
|
||||||
GoFmt(true).
|
GoFmt(true).
|
||||||
Execute(map[string]interface{}{
|
Execute(map[string]interface{}{
|
||||||
"lowerStartCamelObject": stringx.From(camel).LowerStart(),
|
"lowerStartCamelObject": stringx.From(camel).UnTitle(),
|
||||||
"upperStartCamelObject": camel,
|
"upperStartCamelObject": camel,
|
||||||
"cacheKeys": strings.Join(keys, "\r\n"),
|
"cacheKeys": strings.Join(keys, "\r\n"),
|
||||||
"autoIncrement": table.PrimaryKey.AutoIncrement,
|
"autoIncrement": table.PrimaryKey.AutoIncrement,
|
||||||
|
|||||||
33
tools/goctl/model/sql/model/ddlmodel.go
Normal file
33
tools/goctl/model/sql/model/ddlmodel.go
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/tal-tech/go-zero/core/stores/sqlx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
DDLModel struct {
|
||||||
|
conn sqlx.SqlConn
|
||||||
|
}
|
||||||
|
DDL struct {
|
||||||
|
Table string `db:"Table"`
|
||||||
|
DDL string `db:"Create Table"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewDDLModel(conn sqlx.SqlConn) *DDLModel {
|
||||||
|
return &DDLModel{conn: conn}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *DDLModel) ShowDDL(table ...string) ([]string, error) {
|
||||||
|
var ddl []string
|
||||||
|
for _, t := range table {
|
||||||
|
query := `show create table ` + t
|
||||||
|
var resp DDL
|
||||||
|
err := m.conn.QueryRow(&resp, query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ddl = append(ddl, resp.DDL)
|
||||||
|
}
|
||||||
|
return ddl, nil
|
||||||
|
}
|
||||||
@@ -81,7 +81,7 @@ func Parse(ddl string) (*Table, error) {
|
|||||||
}
|
}
|
||||||
column := index.Columns[0]
|
column := index.Columns[0]
|
||||||
columnName := column.Column.String()
|
columnName := column.Column.String()
|
||||||
camelColumnName := stringx.From(columnName).Snake2Camel()
|
camelColumnName := stringx.From(columnName).ToCamel()
|
||||||
// by default, createTime|updateTime findOne is not used.
|
// by default, createTime|updateTime findOne is not used.
|
||||||
if camelColumnName == "CreateTime" || camelColumnName == "UpdateTime" {
|
if camelColumnName == "CreateTime" || camelColumnName == "UpdateTime" {
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -19,6 +19,13 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func NewConsole(idea bool) Console {
|
||||||
|
if idea {
|
||||||
|
return NewIdeaConsole()
|
||||||
|
}
|
||||||
|
return NewColorConsole()
|
||||||
|
}
|
||||||
|
|
||||||
func NewColorConsole() *colorConsole {
|
func NewColorConsole() *colorConsole {
|
||||||
return &colorConsole{}
|
return &colorConsole{}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,10 +6,6 @@ import (
|
|||||||
"unicode"
|
"unicode"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
emptyString = ""
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
type (
|
||||||
String struct {
|
String struct {
|
||||||
source string
|
source string
|
||||||
@@ -31,15 +27,9 @@ func (s String) IsEmptyOrSpace() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s String) Lower() string {
|
func (s String) Lower() string {
|
||||||
if s.IsEmptyOrSpace() {
|
|
||||||
return s.source
|
|
||||||
}
|
|
||||||
return strings.ToLower(s.source)
|
return strings.ToLower(s.source)
|
||||||
}
|
}
|
||||||
func (s String) Upper() string {
|
func (s String) Upper() string {
|
||||||
if s.IsEmptyOrSpace() {
|
|
||||||
return s.source
|
|
||||||
}
|
|
||||||
return strings.ToUpper(s.source)
|
return strings.ToUpper(s.source)
|
||||||
}
|
}
|
||||||
func (s String) Title() string {
|
func (s String) Title() string {
|
||||||
@@ -50,10 +40,7 @@ func (s String) Title() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// snake->camel(upper start)
|
// snake->camel(upper start)
|
||||||
func (s String) Snake2Camel() string {
|
func (s String) ToCamel() string {
|
||||||
if s.IsEmptyOrSpace() {
|
|
||||||
return s.source
|
|
||||||
}
|
|
||||||
list := s.splitBy(func(r rune) bool {
|
list := s.splitBy(func(r rune) bool {
|
||||||
return r == '_'
|
return r == '_'
|
||||||
}, true)
|
}, true)
|
||||||
@@ -65,10 +52,7 @@ func (s String) Snake2Camel() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// camel->snake
|
// camel->snake
|
||||||
func (s String) Camel2Snake() string {
|
func (s String) ToSnake() string {
|
||||||
if s.IsEmptyOrSpace() {
|
|
||||||
return s.source
|
|
||||||
}
|
|
||||||
list := s.splitBy(func(r rune) bool {
|
list := s.splitBy(func(r rune) bool {
|
||||||
return unicode.IsUpper(r)
|
return unicode.IsUpper(r)
|
||||||
}, false)
|
}, false)
|
||||||
@@ -80,7 +64,7 @@ func (s String) Camel2Snake() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// return original string if rune is not letter at index 0
|
// return original string if rune is not letter at index 0
|
||||||
func (s String) LowerStart() string {
|
func (s String) UnTitle() string {
|
||||||
if s.IsEmptyOrSpace() {
|
if s.IsEmptyOrSpace() {
|
||||||
return s.source
|
return s.source
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,26 +17,26 @@ func TestString_IsEmptyOrSpace(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestString_Snake2Camel(t *testing.T) {
|
func TestString_Snake2Camel(t *testing.T) {
|
||||||
ret := From("____this_is_snake").Snake2Camel()
|
ret := From("____this_is_snake").ToCamel()
|
||||||
assert.Equal(t, "ThisIsSnake", ret)
|
assert.Equal(t, "ThisIsSnake", ret)
|
||||||
|
|
||||||
ret2 := From("测试_test_Data").Snake2Camel()
|
ret2 := From("测试_test_Data").ToCamel()
|
||||||
assert.Equal(t, "测试TestData", ret2)
|
assert.Equal(t, "测试TestData", ret2)
|
||||||
|
|
||||||
ret3 := From("___").Snake2Camel()
|
ret3 := From("___").ToCamel()
|
||||||
assert.Equal(t, "", ret3)
|
assert.Equal(t, "", ret3)
|
||||||
|
|
||||||
ret4 := From("testData_").Snake2Camel()
|
ret4 := From("testData_").ToCamel()
|
||||||
assert.Equal(t, "TestData", ret4)
|
assert.Equal(t, "TestData", ret4)
|
||||||
|
|
||||||
ret5 := From("testDataTestData").Snake2Camel()
|
ret5 := From("testDataTestData").ToCamel()
|
||||||
assert.Equal(t, "TestDataTestData", ret5)
|
assert.Equal(t, "TestDataTestData", ret5)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestString_Camel2Snake(t *testing.T) {
|
func TestString_Camel2Snake(t *testing.T) {
|
||||||
ret := From("ThisIsCCCamel").Camel2Snake()
|
ret := From("ThisIsCCCamel").ToSnake()
|
||||||
assert.Equal(t, "this_is_c_c_camel", ret)
|
assert.Equal(t, "this_is_c_c_camel", ret)
|
||||||
|
|
||||||
ret2 := From("测试Test_Data_test_data").Camel2Snake()
|
ret2 := From("测试Test_Data_test_data").ToSnake()
|
||||||
assert.Equal(t, "测试_test__data_test_data", ret2)
|
assert.Equal(t, "测试_test__data_test_data", ret2)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user