Compare commits

..

36 Commits

Author SHA1 Message Date
kingxt
e0afe0b4bb optimize api new (#216) 2020-11-19 16:48:48 +08:00
Keson
24fb29a356 patch model&rpc (#207)
* change column to read from information_schema

* reactor generate mode from datasource

* reactor generate mode from datasource

* add primary key check logic

* resolve rebase conflicts

* add naming style

* add filename test case

* resolve rebase conflicts

* reactor test

* add test case

* change shell script to makefile

* update rpc new

* update gen_test.go

* format code

* format code

* update test

* generates alias
2020-11-18 15:32:53 +08:00
kevin
71083b5e64 update readme 2020-11-17 19:01:14 +08:00
kingxt
1174f17bd9 modify image url (#213) 2020-11-17 18:50:22 +08:00
kingxt
d6d8fc21d8 type should not define nested (#212)
* nest type should not supported

* nest type should not supported

* nest type should not supported

* nest type should not supported

* new test

* new test
2020-11-17 18:08:55 +08:00
kevin
9592639cb4 add error handle tests 2020-11-17 18:04:48 +08:00
kevin
abcb28e506 support error customization 2020-11-17 17:11:06 +08:00
kingxt
a92f65580c support type def without struct token (#210)
* add comment support

* add comment support

* 1. group support multi level folder
2. remove force flag

* bug fix

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* support type def without struct token

* support type def without struct token

* support type def without struct token

* support type def without struct token

* support type def without struct token

* support type def without struct token

* support type def without struct token

* optimized

* optimized

* optimized

Co-authored-by: kim <xutao@xiaoheiban.cn>
2020-11-17 15:25:13 +08:00
bittoy
3819f67cf4 add redis geospatial (#209)
* add redis geospatial

* fix go test error
2020-11-16 19:45:43 +08:00
kevin
295c8d2934 fix issue #205 2020-11-16 19:23:24 +08:00
kingxt
88da8685dd optimize parser (#206)
* add comment support

* add comment support

* 1. group support multi level folder
2. remove force flag

* bug fix

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* optimized parser

Co-authored-by: kim <xutao@xiaoheiban.cn>
2020-11-16 10:08:28 +08:00
kevin
c7831ac96d update goctl readme 2020-11-15 21:18:02 +08:00
kevin
e898761762 update example 2020-11-15 21:15:29 +08:00
kevin
13d1c5cd00 update example 2020-11-14 22:01:35 +08:00
kingxt
16bfb1b7be refactor parser and remove deprecated code (#204)
* add comment support

* add comment support

* 1. group support multi level folder
2. remove force flag

* bug fix

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

Co-authored-by: kim <xutao@xiaoheiban.cn>
2020-11-13 23:01:19 +08:00
kingxt
ef4d4968d6 1. group support multi level folder 2. remove force flag (#203)
* add comment support

* add comment support

* 1. group support multi level folder
2. remove force flag

* bug fix

Co-authored-by: kim <xutao@xiaoheiban.cn>
2020-11-12 19:47:32 +08:00
kingxt
7b4a5e3ec6 api support for comment double slash // (#201)
* add comment support

* add comment support

Co-authored-by: kim <xutao@xiaoheiban.cn>
2020-11-12 16:57:28 +08:00
kevin
e6df21e0d2 format code 2020-11-11 17:20:56 +08:00
SunJun
0a2c2d1eca change grpc interceptor to chain interceptor (#200)
* change grpc interceptor to chain interceptor

* change server rpc interceptors, del testing code
2020-11-11 17:15:22 +08:00
kevin
a5fb29a6f0 update etcd yaml to avoid no such nost resolve problem 2020-11-11 11:06:23 +08:00
zhoushuguang
f8da301e57 no default metric (#199)
Co-authored-by: zhoushuguang <zhoushuguang@xiaoheiban.cn>
2020-11-10 11:47:08 +08:00
kevin
cb9075b737 add dockerfile into template 2020-11-09 18:02:16 +08:00
kingxt
3f389a55c2 format service and add test (#197)
Co-authored-by: kim <xutao@xiaoheiban.cn>
2020-11-09 17:41:07 +08:00
kevin
afbd565d87 rename postgres 2020-11-09 17:22:51 +08:00
zhoushuguang
d629acc2b7 default metric host (#196)
Co-authored-by: zhoushuguang <zhoushuguang@xiaoheiban.cn>
2020-11-09 16:03:07 +08:00
kingxt
f32c6a9b28 rewrite (#194)
Co-authored-by: kim <xutao@xiaoheiban.cn>
2020-11-09 10:06:45 +08:00
kevin
95aa65efb9 add dockerfile generator 2020-11-08 21:28:58 +08:00
kevin
3806e66cf1 simplify http server starter 2020-11-08 13:17:14 +08:00
kevin
bd430baf52 graceful shutdown refined 2020-11-08 13:08:00 +08:00
Keson
48f4154ea8 update doc (#193) 2020-11-08 13:02:48 +08:00
super_mario
2599e0d28d Close the process when shutdown is finished (#157)
Co-authored-by: Kevin Wan <wanjunfeng@gmail.com>
2020-11-08 12:50:58 +08:00
kingxt
12327fa07d break generator when happen error (#192)
Co-authored-by: kim <xutao@xiaoheiban.cn>
2020-11-07 21:25:52 +08:00
kevin
57079bf4a4 update cli package 2020-11-07 20:01:25 +08:00
kingxt
7f6eceb5a3 add more test (#189)
* new test

* import bug when with quotation

* new test

* add test condition

* rpc template command use -o param

Co-authored-by: kim <xutao@xiaoheiban.cn>
2020-11-07 17:13:40 +08:00
kevin
7d7cb836af fix issue #186 2020-11-06 12:25:48 +08:00
kevin
f87d9d1dda refine code style 2020-11-06 12:13:28 +08:00
127 changed files with 2052 additions and 2210 deletions

View File

@@ -204,6 +204,7 @@ func (tw *TimingWheel) removeTask(key interface{}) {
timer := val.(*positionEntry)
timer.item.removed = true
tw.timers.Del(key)
}
func (tw *TimingWheel) run() {
@@ -248,7 +249,6 @@ func (tw *TimingWheel) scanAndRunTasks(l *list.List) {
if task.removed {
next := e.Next()
l.Remove(e)
tw.timers.Del(task.key)
e = next
continue
} else if task.circle > 0 {
@@ -301,6 +301,7 @@ func (tw *TimingWheel) setTask(task *timingEntry) {
func (tw *TimingWheel) setTimerPosition(pos int, task *timingEntry) {
if val, ok := tw.timers.Get(task.key); ok {
timer := val.(*positionEntry)
timer.item = task
timer.pos = pos
} else {
tw.timers.Set(task.key, &positionEntry{

View File

@@ -594,6 +594,31 @@ func TestTimingWheel_ElapsedAndSetThenMove(t *testing.T) {
}
}
func TestMoveAndRemoveTask(t *testing.T) {
ticker := timex.NewFakeTicker()
tick := func(v int) {
for i := 0; i < v; i++ {
ticker.Tick()
}
}
var keys []int
tw, _ := newTimingWheelWithClock(testStep, 10, func(k, v interface{}) {
assert.Equal(t, "any", k)
assert.Equal(t, 3, v.(int))
keys = append(keys, v.(int))
ticker.Done()
}, ticker)
defer tw.Stop()
tw.SetTimer("any", 3, testStep*8)
tick(6)
tw.MoveTimer("any", testStep*7)
tick(3)
tw.RemoveTimer("any")
tick(30)
time.Sleep(time.Millisecond)
assert.Equal(t, 0, len(keys))
}
func BenchmarkTimingWheel(b *testing.B) {
b.ReportAllocs()

View File

@@ -35,7 +35,7 @@ spec:
- --listen-client-urls
- http://0.0.0.0:2379
- --advertise-client-urls
- http://etcd0:2379
- http://etcd0.discov: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
@@ -107,7 +107,7 @@ spec:
- --listen-client-urls
- http://0.0.0.0:2379
- --advertise-client-urls
- http://etcd1:2379
- http://etcd1.discov: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
@@ -179,7 +179,7 @@ spec:
- --listen-client-urls
- http://0.0.0.0:2379
- --advertise-client-urls
- http://etcd2:2379
- http://etcd2.discov: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
@@ -251,7 +251,7 @@ spec:
- --listen-client-urls
- http://0.0.0.0:2379
- --advertise-client-urls
- http://etcd3:2379
- http://etcd3.discov: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
@@ -323,7 +323,7 @@ spec:
- --listen-client-urls
- http://0.0.0.0:2379
- --advertise-client-urls
- http://etcd4:2379
- http://etcd4.discov: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

View File

@@ -82,6 +82,7 @@ func (pe *PeriodicalExecutor) Sync(fn func()) {
}
func (pe *PeriodicalExecutor) Wait() {
pe.Flush()
pe.wgBarrier.Guard(func() {
pe.waitGroup.Wait()
})

View File

@@ -3,7 +3,7 @@ package limit
import (
"testing"
"github.com/alicebob/miniredis"
"github.com/alicebob/miniredis/v2"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/stores/redis"
"github.com/tal-tech/go-zero/core/stores/redis/redistest"

View File

@@ -4,7 +4,7 @@ import (
"testing"
"time"
"github.com/alicebob/miniredis"
"github.com/alicebob/miniredis/v2"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/logx"
"github.com/tal-tech/go-zero/core/stores/redis"

View File

@@ -175,12 +175,12 @@ func (c cacheNode) doTake(v interface{}, key string, query func(v interface{}) e
}
if fresh {
return nil
} else {
// got the result from previous ongoing query
c.stat.IncrementTotal()
c.stat.IncrementHit()
}
// got the result from previous ongoing query
c.stat.IncrementTotal()
c.stat.IncrementHit()
return jsonx.Unmarshal(val.([]byte), v)
}

View File

@@ -9,7 +9,7 @@ import (
"testing"
"time"
"github.com/alicebob/miniredis"
"github.com/alicebob/miniredis/v2"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/logx"
"github.com/tal-tech/go-zero/core/mathx"

View File

@@ -4,7 +4,7 @@ import (
"testing"
"time"
"github.com/alicebob/miniredis"
"github.com/alicebob/miniredis/v2"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/hash"
"github.com/tal-tech/go-zero/core/stores/cache"

View File

@@ -5,8 +5,8 @@ import (
"github.com/tal-tech/go-zero/core/stores/sqlx"
)
const postgreDriverName = "postgres"
const postgresDriverName = "postgres"
func NewPostgre(datasource string, opts ...sqlx.SqlOption) sqlx.SqlConn {
return sqlx.NewSqlConn(postgreDriverName, datasource, opts...)
func NewPostgres(datasource string, opts ...sqlx.SqlOption) sqlx.SqlConn {
return sqlx.NewSqlConn(postgresDriverName, datasource, opts...)
}

View File

@@ -42,6 +42,12 @@ type (
red.Cmdable
}
// GeoLocation is used with GeoAdd to add geospatial location.
GeoLocation = red.GeoLocation
// GeoRadiusQuery is used with GeoRadius to query geospatial index.
GeoRadiusQuery = red.GeoRadiusQuery
GeoPos = red.GeoPos
Pipeliner = red.Pipeliner
// Z represents sorted set member.
@@ -173,6 +179,107 @@ func (s *Redis) Expireat(key string, expireTime int64) error {
}, acceptable)
}
func (s *Redis) GeoAdd(key string, geoLocation ...*GeoLocation) (val int64, err error) {
err = s.brk.DoWithAcceptable(func() error {
conn, err := getRedis(s)
if err != nil {
return err
}
if v, err := conn.GeoAdd(key, geoLocation...).Result(); err != nil {
return err
} else {
val = v
return nil
}
}, acceptable)
return
}
func (s *Redis) GeoDist(key string, member1, member2, unit string) (val float64, err error) {
err = s.brk.DoWithAcceptable(func() error {
conn, err := getRedis(s)
if err != nil {
return err
}
if v, err := conn.GeoDist(key, member1, member2, unit).Result(); err != nil {
return err
} else {
val = v
return nil
}
}, acceptable)
return
}
func (s *Redis) GeoHash(key string, members ...string) (val []string, err error) {
err = s.brk.DoWithAcceptable(func() error {
conn, err := getRedis(s)
if err != nil {
return err
}
if v, err := conn.GeoHash(key, members...).Result(); err != nil {
return err
} else {
val = v
return nil
}
}, acceptable)
return
}
func (s *Redis) GeoRadius(key string, longitude, latitude float64, query *GeoRadiusQuery) (val []GeoLocation, err error) {
err = s.brk.DoWithAcceptable(func() error {
conn, err := getRedis(s)
if err != nil {
return err
}
if v, err := conn.GeoRadius(key, longitude, latitude, query).Result(); err != nil {
return err
} else {
val = v
return nil
}
}, acceptable)
return
}
func (s *Redis) GeoRadiusByMember(key, member string, query *GeoRadiusQuery) (val []GeoLocation, err error) {
err = s.brk.DoWithAcceptable(func() error {
conn, err := getRedis(s)
if err != nil {
return err
}
if v, err := conn.GeoRadiusByMember(key, member, query).Result(); err != nil {
return err
} else {
val = v
return nil
}
}, acceptable)
return
}
func (s *Redis) GeoPos(key string, members ...string) (val []*GeoPos, err error) {
err = s.brk.DoWithAcceptable(func() error {
conn, err := getRedis(s)
if err != nil {
return err
}
if v, err := conn.GeoPos(key, members...).Result(); err != nil {
return err
} else {
val = v
return nil
}
}, acceptable)
return
}
func (s *Redis) Get(key string) (val string, err error) {
err = s.brk.DoWithAcceptable(func() error {
conn, err := getRedis(s)

View File

@@ -6,7 +6,7 @@ import (
"testing"
"time"
"github.com/alicebob/miniredis"
"github.com/alicebob/miniredis/v2"
red "github.com/go-redis/redis"
"github.com/stretchr/testify/assert"
)
@@ -816,6 +816,38 @@ func TestRedisBlpopEx(t *testing.T) {
})
}
func TestRedisGeo(t *testing.T) {
runOnRedis(t, func(client *Redis) {
client.Ping()
var geoLocation = []*GeoLocation{{Longitude: 13.361389, Latitude: 38.115556, Name: "Palermo"}, {Longitude: 15.087269, Latitude: 37.502669, Name: "Catania"}}
v, err := client.GeoAdd("sicily", geoLocation...)
assert.Nil(t, err)
assert.Equal(t, int64(2), v)
v2, err := client.GeoDist("sicily", "Palermo", "Catania", "m")
assert.Nil(t, err)
assert.Equal(t, 166274, int(v2))
// GeoHash not support
v3, err := client.GeoPos("sicily", "Palermo", "Catania")
assert.Nil(t, err)
assert.Equal(t, int64(v3[0].Longitude), int64(13))
assert.Equal(t, int64(v3[0].Latitude), int64(38))
assert.Equal(t, int64(v3[1].Longitude), int64(15))
assert.Equal(t, int64(v3[1].Latitude), int64(37))
v4, err := client.GeoRadius("sicily", 15, 37, &red.GeoRadiusQuery{WithDist: true, Unit: "km", Radius: 200})
assert.Nil(t, err)
assert.Equal(t, int64(v4[0].Dist), int64(190))
assert.Equal(t, int64(v4[1].Dist), int64(56))
var geoLocation2 = []*GeoLocation{{Longitude: 13.583333, Latitude: 37.316667, Name: "Agrigento"}}
v5, err := client.GeoAdd("sicily", geoLocation2...)
assert.Nil(t, err)
assert.Equal(t, int64(1), v5)
v6, err := client.GeoRadiusByMember("sicily", "Agrigento", &red.GeoRadiusQuery{Unit: "km", Radius: 100})
assert.Nil(t, err)
assert.Equal(t, v6[0].Name, "Agrigento")
assert.Equal(t, v6[1].Name, "Palermo")
})
}
func runOnRedis(t *testing.T, fn func(client *Redis)) {
s, err := miniredis.Run()
assert.Nil(t, err)

View File

@@ -3,7 +3,7 @@ package redistest
import (
"time"
"github.com/alicebob/miniredis"
"github.com/alicebob/miniredis/v2"
"github.com/tal-tech/go-zero/core/lang"
"github.com/tal-tech/go-zero/core/stores/redis"
)

View File

@@ -14,7 +14,7 @@ import (
"testing"
"time"
"github.com/alicebob/miniredis"
"github.com/alicebob/miniredis/v2"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/fx"
"github.com/tal-tech/go-zero/core/logx"

View File

@@ -5,7 +5,7 @@ go 1.15
require (
github.com/golang/mock v1.4.3
github.com/golang/protobuf v1.4.2
github.com/tal-tech/go-zero v1.0.16
github.com/tal-tech/go-zero v1.0.27
golang.org/x/net v0.0.0-20200707034311-ab3426394381
google.golang.org/grpc v1.29.1
)

View File

@@ -36,6 +36,7 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee
github.com/coreos/go-systemd/v22 v22.0.0 h1:XJIw/+VlJ+87J+doOxznsAWIdmWuViOVhkQamW5YV28=
github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -46,6 +47,7 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm
github.com/dsymonds/gotoc v0.0.0-20160928043926-5aebcfc91819/go.mod h1:MvzMVHq8BH2Ji/o8TGDocVA70byvLrAgFTxkEnmjO4Y=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4 h1:qk/FSDDxo05wdJH28W+p5yivv7LuLYLRXPPD8KQCtZs=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/emicklei/proto v1.9.0/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@@ -122,9 +124,11 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.14.3 h1:OCJlWkOUoTnl0neNGlf4fUm3TmbEtguw7vR+nGtnDjY=
github.com/grpc-ecosystem/grpc-gateway v1.14.3/go.mod h1:6CwZWGDSPRJidgKAtJVvND6soZe6fT7iteq8wDPdhb0=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE=
github.com/iancoleman/strcase v0.1.2/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=
@@ -175,6 +179,7 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@@ -210,6 +215,7 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shirou/gopsutil v0.0.0-20180427012116-c95755e4bcd7/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
@@ -232,11 +238,14 @@ github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/tal-tech/go-zero v1.0.16 h1:oT7sOFftEUdD/XcXF0xEugX9yhnw4DcQkeMNFLi5KO8=
github.com/tal-tech/go-zero v1.0.16/go.mod h1:y2wBHTkxNJw79K9/wCSeDKzv2pCT6x45oOmXEsJdQK8=
github.com/tal-tech/go-zero v1.0.27 h1:QMIbaTxibMc/OsO5RTAuKZ8ndbl2dGN6pITQEtp2x/A=
github.com/tal-tech/go-zero v1.0.27/go.mod h1:JtNXlsh/CgeIHyQnt5C5M2IcSevW7V0NAnqO93TQgm8=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6 h1:lYIiVDtZnyTWlNwiAxLj0bbpTcx1BWCFhXjfsvmPdNc=
github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg=
@@ -382,6 +391,7 @@ gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qS
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
@@ -393,6 +403,8 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=

View File

@@ -18,7 +18,7 @@ var (
bookRowsExpectAutoSet = strings.Join(stringx.Remove(bookFieldNames, "create_time", "update_time"), ",")
bookRowsWithPlaceHolder = strings.Join(stringx.Remove(bookFieldNames, "book", "create_time", "update_time"), "=?,") + "=?"
cacheBookBookPrefix = "cache#Book#book#"
bookPrefix = "cache#Book#book#"
)
type (
@@ -46,9 +46,9 @@ func (m *BookModel) Insert(data Book) (sql.Result, error) {
}
func (m *BookModel) FindOne(book string) (*Book, error) {
bookBookKey := fmt.Sprintf("%s%v", cacheBookBookPrefix, book)
bookKey := fmt.Sprintf("%s%v", bookPrefix, book)
var resp Book
err := m.QueryRow(&resp, bookBookKey, func(conn sqlx.SqlConn, v interface{}) error {
err := m.QueryRow(&resp, bookKey, func(conn sqlx.SqlConn, v interface{}) error {
query := `select ` + bookRows + ` from ` + m.table + ` where book = ? limit 1`
return conn.QueryRow(v, query, book)
})
@@ -63,20 +63,19 @@ func (m *BookModel) FindOne(book string) (*Book, error) {
}
func (m *BookModel) Update(data Book) error {
bookBookKey := fmt.Sprintf("%s%v", cacheBookBookPrefix, data.Book)
bookKey := fmt.Sprintf("%s%v", bookPrefix, data.Book)
_, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
query := `update ` + m.table + ` set ` + bookRowsWithPlaceHolder + ` where book = ?`
return conn.Exec(query, data.Price, data.Book)
}, bookBookKey)
}, bookKey)
return err
}
func (m *BookModel) Delete(book string) error {
bookBookKey := fmt.Sprintf("%s%v", cacheBookBookPrefix, book)
bookKey := fmt.Sprintf("%s%v", bookPrefix, book)
_, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
query := `delete from ` + m.table + ` where book = ?`
return conn.Exec(query, book)
}, bookBookKey)
}, bookKey)
return err
}

View File

@@ -5,7 +5,7 @@ go 1.15
require (
github.com/golang/mock v1.4.3
github.com/golang/protobuf v1.4.2
github.com/tal-tech/go-zero v1.0.16
github.com/tal-tech/go-zero v1.0.27
golang.org/x/net v0.0.0-20200707034311-ab3426394381
google.golang.org/grpc v1.29.1
)

View File

@@ -37,6 +37,7 @@ github.com/coreos/go-systemd/v22 v22.0.0 h1:XJIw/+VlJ+87J+doOxznsAWIdmWuViOVhkQa
github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -48,6 +49,7 @@ github.com/dsymonds/gotoc v0.0.0-20160928043926-5aebcfc91819 h1:9778zj477h/VauD8
github.com/dsymonds/gotoc v0.0.0-20160928043926-5aebcfc91819/go.mod h1:MvzMVHq8BH2Ji/o8TGDocVA70byvLrAgFTxkEnmjO4Y=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4 h1:qk/FSDDxo05wdJH28W+p5yivv7LuLYLRXPPD8KQCtZs=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/emicklei/proto v1.9.0/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@@ -124,10 +126,12 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.14.3 h1:OCJlWkOUoTnl0neNGlf4fUm3TmbEtguw7vR+nGtnDjY=
github.com/grpc-ecosystem/grpc-gateway v1.14.3/go.mod h1:6CwZWGDSPRJidgKAtJVvND6soZe6fT7iteq8wDPdhb0=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334 h1:VHgatEHNcBFEB7inlalqfNqw65aNkM1lGX2yt3NmbS8=
github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE=
github.com/iancoleman/strcase v0.1.2/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=
@@ -179,6 +183,7 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@@ -215,6 +220,7 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shirou/gopsutil v0.0.0-20180427012116-c95755e4bcd7/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
@@ -238,12 +244,15 @@ github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/tal-tech/go-zero v1.0.16 h1:oT7sOFftEUdD/XcXF0xEugX9yhnw4DcQkeMNFLi5KO8=
github.com/tal-tech/go-zero v1.0.16/go.mod h1:y2wBHTkxNJw79K9/wCSeDKzv2pCT6x45oOmXEsJdQK8=
github.com/tal-tech/go-zero v1.0.27 h1:QMIbaTxibMc/OsO5RTAuKZ8ndbl2dGN6pITQEtp2x/A=
github.com/tal-tech/go-zero v1.0.27/go.mod h1:JtNXlsh/CgeIHyQnt5C5M2IcSevW7V0NAnqO93TQgm8=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6 h1:lYIiVDtZnyTWlNwiAxLj0bbpTcx1BWCFhXjfsvmPdNc=
github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA=
github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg=
@@ -390,6 +399,7 @@ gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qS
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
@@ -401,6 +411,8 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=

13
go.mod
View File

@@ -5,8 +5,8 @@ go 1.13
require (
github.com/ClickHouse/clickhouse-go v1.4.3
github.com/DATA-DOG/go-sqlmock v1.4.1
github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6 // indirect
github.com/alicebob/miniredis v2.5.0+incompatible
github.com/alicebob/miniredis/v2 v2.14.1
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
github.com/dchest/siphash v1.2.1
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/emicklei/proto v1.9.0
@@ -20,12 +20,11 @@ require (
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
github.com/golang/mock v1.4.3
github.com/golang/protobuf v1.4.2
github.com/gomodule/redigo v2.0.0+incompatible // indirect
github.com/google/gops v0.3.7
github.com/google/uuid v1.1.1
github.com/gorilla/websocket v1.4.2 // indirect
github.com/grpc-ecosystem/grpc-gateway v1.14.3 // indirect
github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334
github.com/iancoleman/strcase v0.1.2
github.com/justinas/alice v1.2.0
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
@@ -40,12 +39,12 @@ require (
github.com/pierrec/lz4 v2.5.1+incompatible // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_golang v1.5.1
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/spaolacci/murmur3 v1.1.0
github.com/stretchr/testify v1.5.1
github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6 // indirect
github.com/urfave/cli v1.22.4
github.com/urfave/cli v1.22.5
github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb // indirect
go.etcd.io/etcd v0.0.0-20200402134248-51bdeb39e698
go.uber.org/automaxprocs v1.3.0
golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect
@@ -59,7 +58,7 @@ require (
google.golang.org/protobuf v1.25.0
gopkg.in/cheggaaa/pb.v1 v1.0.28
gopkg.in/h2non/gock.v1 v1.0.15
gopkg.in/yaml.v2 v2.2.8
gopkg.in/yaml.v2 v2.3.0
honnef.co/go/tools v0.0.1-2020.1.4 // indirect
sigs.k8s.io/yaml v1.2.0 // indirect
)

26
go.sum
View File

@@ -11,10 +11,10 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafo
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6 h1:45bxf7AZMwWcqkLzDAQugVEwedisr5nRJ1r+7LYnv0U=
github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
github.com/alicebob/miniredis v2.5.0+incompatible h1:yBHoLpsyjupjz3NL3MhKMVkR41j82Yjf3KFv7ApYzUI=
github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk=
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk=
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
github.com/alicebob/miniredis/v2 v2.14.1 h1:GjlbSeoJ24bzdLRs13HoMEeaRZx9kg5nHoRW7QV/nCs=
github.com/alicebob/miniredis/v2 v2.14.1/go.mod h1:uS970Sw5Gs9/iK3yBg0l9Uj9s25wXxSpQUE9EaJ/Blg=
github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
@@ -42,6 +42,8 @@ github.com/coreos/go-systemd/v22 v22.0.0 h1:XJIw/+VlJ+87J+doOxznsAWIdmWuViOVhkQa
github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -112,8 +114,6 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@@ -145,8 +145,8 @@ github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslC
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334 h1:VHgatEHNcBFEB7inlalqfNqw65aNkM1lGX2yt3NmbS8=
github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE=
github.com/iancoleman/strcase v0.1.2 h1:gnomlvw9tnV3ITTAxzKSgTF+8kFWcU/f+TgttpXGz1U=
github.com/iancoleman/strcase v0.1.2/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=
@@ -207,6 +207,7 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
@@ -248,6 +249,8 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shirou/gopsutil v0.0.0-20180427012116-c95755e4bcd7/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 h1:udFKJ0aHUL60LboW/A+DfgoHVedieIzIXE8uylPue0U=
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
@@ -275,8 +278,8 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1
github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6 h1:lYIiVDtZnyTWlNwiAxLj0bbpTcx1BWCFhXjfsvmPdNc=
github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA=
github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU=
github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6 h1:YdYsPAZ2pC6Tow/nPZOPQ96O3hm/ToAkGsPLzedXERk=
@@ -417,6 +420,7 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
@@ -446,6 +450,8 @@ gopkg.in/yaml.v2 v2.2.5 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=

View File

@@ -1,4 +1,4 @@
<img align="right" width="150px" src="doc/images/go-zero.png">
<img align="right" width="150px" src="https://gitee.com/kevwan/static/raw/master/doc/images/go-zero.png">
# go-zero
@@ -25,7 +25,7 @@ go-zero 包含极简的 API 定义和生成工具 goctl可以根据定义的
* 自动校验客户端请求参数合法性
* 大量微服务治理和并发工具包
<img src="doc/images/architecture.png" alt="架构图" width="1500" />
<img src="https://gitee.com/kevwan/static/raw/master/doc/images/architecture.png" alt="架构图" width="1500" />
## 1. go-zero 框架背景
@@ -77,7 +77,7 @@ go-zero 是一个集成了各种工程实践的包含 web 和 rpc 框架,有
如下图,我们从多个层面保障了整体服务的高可用:
![弹性设计](doc/images/resilience.jpg)
![弹性设计](https://gitee.com/kevwan/static/raw/master/doc/images/resilience.jpg)
觉得不错的话,别忘 **star** 👏
@@ -150,7 +150,7 @@ GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/tal-tech/
## 6. Benchmark
![benchmark](doc/images/benchmark.png)
![benchmark](https://gitee.com/kevwan/static/raw/master/doc/images/benchmark.png)
[测试代码见这里](https://github.com/smallnest/go-web-framework-benchmark)
@@ -189,5 +189,3 @@ GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/tal-tech/
项目地址:[https://github.com/tal-tech/go-zero](https://github.com/tal-tech/go-zero)
码云地址:[https://gitee.com/kevwan/go-zero](https://gitee.com/kevwan/go-zero) (国内用户可访问gitee每日自动从github同步代码)
开源中国年度评选,给 **go-zero** 投上一票:[https://www.oschina.net/p/go-zero](https://www.oschina.net/p/go-zero)

View File

@@ -26,11 +26,11 @@ func ContentSecurityHandler(decrypters map[string]codec.RsaDecrypter, tolerance
case http.MethodDelete, http.MethodGet, http.MethodPost, http.MethodPut:
header, err := security.ParseContentSecurity(decrypters, r)
if err != nil {
logx.Infof("Signature parse failed, X-Content-Security: %s, error: %s",
logx.Errorf("Signature parse failed, X-Content-Security: %s, error: %s",
r.Header.Get(contentSecurity), err.Error())
executeCallbacks(w, r, next, strict, httpx.CodeSignatureInvalidHeader, callbacks)
} else if code := security.VerifySignature(r, header, tolerance); code != httpx.CodeSignaturePass {
logx.Infof("Signature verification failed, X-Content-Security: %s",
logx.Errorf("Signature verification failed, X-Content-Security: %s",
r.Header.Get(contentSecurity))
executeCallbacks(w, r, next, strict, code, callbacks)
} else if r.ContentLength > 0 && header.Encrypted() {
@@ -54,7 +54,7 @@ func executeCallbacks(w http.ResponseWriter, r *http.Request, next http.Handler,
func handleVerificationFailure(w http.ResponseWriter, r *http.Request, next http.Handler, strict bool, code int) {
if strict {
w.WriteHeader(http.StatusUnauthorized)
w.WriteHeader(http.StatusForbidden)
} else {
next.ServeHTTP(w, r)
}

View File

@@ -113,7 +113,7 @@ func TestContentSecurityHandler(t *testing.T) {
strict: true,
crypt: true,
timestamp: time.Now().Add(timeDiff).Unix(),
statusCode: http.StatusUnauthorized,
statusCode: http.StatusForbidden,
},
{
method: http.MethodPost,
@@ -122,7 +122,7 @@ func TestContentSecurityHandler(t *testing.T) {
strict: true,
crypt: true,
timestamp: time.Now().Add(-timeDiff).Unix(),
statusCode: http.StatusUnauthorized,
statusCode: http.StatusForbidden,
},
{
method: http.MethodPost,
@@ -148,7 +148,7 @@ func TestContentSecurityHandler(t *testing.T) {
crypt: true,
timestamp: time.Now().Add(-timeDiff).Unix(),
fingerprint: "badone",
statusCode: http.StatusUnauthorized,
statusCode: http.StatusForbidden,
},
{
method: http.MethodPost,
@@ -157,7 +157,7 @@ func TestContentSecurityHandler(t *testing.T) {
strict: true,
crypt: true,
missHeader: true,
statusCode: http.StatusUnauthorized,
statusCode: http.StatusForbidden,
},
{
method: http.MethodHead,
@@ -171,7 +171,7 @@ func TestContentSecurityHandler(t *testing.T) {
strict: true,
crypt: false,
signature: "badone",
statusCode: http.StatusUnauthorized,
statusCode: http.StatusForbidden,
},
}

View File

@@ -3,12 +3,33 @@ package httpx
import (
"encoding/json"
"net/http"
"sync"
"github.com/tal-tech/go-zero/core/logx"
)
var (
errorHandler func(error) (int, interface{})
lock sync.RWMutex
)
func Error(w http.ResponseWriter, err error) {
http.Error(w, err.Error(), http.StatusBadRequest)
lock.RLock()
handler := errorHandler
lock.RUnlock()
if handler == nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
code, body := errorHandler(err)
e, ok := body.(error)
if ok {
http.Error(w, e.Error(), code)
} else {
WriteJson(w, code, body)
}
}
func Ok(w http.ResponseWriter) {
@@ -19,6 +40,12 @@ func OkJson(w http.ResponseWriter, v interface{}) {
WriteJson(w, http.StatusOK, v)
}
func SetErrorHandler(handler func(error) (int, interface{})) {
lock.Lock()
defer lock.Unlock()
errorHandler = handler
}
func WriteJson(w http.ResponseWriter, code int, v interface{}) {
w.Header().Set(ContentType, ApplicationJson)
w.WriteHeader(code)

View File

@@ -19,13 +19,65 @@ func init() {
}
func TestError(t *testing.T) {
const body = "foo"
w := tracedResponseWriter{
headers: make(map[string][]string),
const (
body = "foo"
wrappedBody = `"foo"`
)
tests := []struct {
name string
input string
errorHandler func(error) (int, interface{})
expectBody string
expectCode int
}{
{
name: "default error handler",
input: body,
expectBody: body,
expectCode: http.StatusBadRequest,
},
{
name: "customized error handler return string",
input: body,
errorHandler: func(err error) (int, interface{}) {
return http.StatusForbidden, err.Error()
},
expectBody: wrappedBody,
expectCode: http.StatusForbidden,
},
{
name: "customized error handler return error",
input: body,
errorHandler: func(err error) (int, interface{}) {
return http.StatusForbidden, err
},
expectBody: body,
expectCode: http.StatusForbidden,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
w := tracedResponseWriter{
headers: make(map[string][]string),
}
if test.errorHandler != nil {
lock.RLock()
prev := errorHandler
lock.RUnlock()
SetErrorHandler(test.errorHandler)
defer func() {
lock.Lock()
errorHandler = prev
lock.Unlock()
}()
}
Error(&w, errors.New(test.input))
assert.Equal(t, test.expectCode, w.code)
assert.Equal(t, test.expectBody, strings.TrimSpace(w.builder.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) {

View File

@@ -2,7 +2,6 @@ package internal
import (
"context"
"crypto/tls"
"fmt"
"net/http"
@@ -10,43 +9,27 @@ import (
)
func StartHttp(host string, port int, handler http.Handler) error {
addr := fmt.Sprintf("%s:%d", host, port)
server := buildHttpServer(addr, handler)
gracefulOnShutdown(server)
return server.ListenAndServe()
return start(host, port, handler, func(srv *http.Server) error {
return srv.ListenAndServe()
})
}
func StartHttps(host string, port int, certFile, keyFile string, handler http.Handler) error {
addr := fmt.Sprintf("%s:%d", host, port)
if server, err := buildHttpsServer(addr, handler, certFile, keyFile); err != nil {
return err
} else {
gracefulOnShutdown(server)
return start(host, port, handler, func(srv *http.Server) error {
// certFile and keyFile are set in buildHttpsServer
return server.ListenAndServeTLS("", "")
}
}
func buildHttpServer(addr string, handler http.Handler) *http.Server {
return &http.Server{Addr: addr, Handler: handler}
}
func buildHttpsServer(addr string, handler http.Handler, certFile, keyFile string) (*http.Server, error) {
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return nil, err
}
config := tls.Config{Certificates: []tls.Certificate{cert}}
return &http.Server{
Addr: addr,
Handler: handler,
TLSConfig: &config,
}, nil
}
func gracefulOnShutdown(srv *http.Server) {
proc.AddWrapUpListener(func() {
srv.Shutdown(context.Background())
return srv.ListenAndServeTLS(certFile, keyFile)
})
}
func start(host string, port int, handler http.Handler, run func(srv *http.Server) error) error {
server := &http.Server{
Addr: fmt.Sprintf("%s:%d", host, port),
Handler: handler,
}
waitForCalled := proc.AddWrapUpListener(func() {
server.Shutdown(context.Background())
})
defer waitForCalled()
return run(server)
}

View File

@@ -40,7 +40,7 @@ func genDoc(api *spec.ApiSpec, dir string, filename string) error {
defer fp.Close()
var builder strings.Builder
for index, route := range api.Service.Routes {
for index, route := range api.Service.Routes() {
routeComment, _ := util.GetAnnotationValue(route.Annotations, "doc", "summary")
if len(routeComment) == 0 {
routeComment = "N/A"

View File

@@ -1,15 +1,13 @@
package format
import (
"bufio"
"errors"
"fmt"
"go/format"
"go/scanner"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"github.com/tal-tech/go-zero/core/errorx"
@@ -18,8 +16,11 @@ import (
"github.com/urfave/cli"
)
var (
reg = regexp.MustCompile("type (?P<name>.*)[\\s]+{")
const (
leftParenthesis = "("
rightParenthesis = ")"
leftBrace = "{"
rightBrace = "}"
)
func GoFormatApi(c *cli.Context) error {
@@ -94,69 +95,32 @@ func ApiFormatByPath(apiFilePath string) error {
}
func apiFormat(data string) (string, error) {
r := reg.ReplaceAllStringFunc(data, func(m string) string {
parts := reg.FindStringSubmatch(m)
if len(parts) < 2 {
return m
}
if !strings.Contains(m, "struct") {
return "type " + parts[1] + " struct {"
}
return m
})
apiStruct, err := parser.ParseApi(r)
_, err := parser.ParseApi(data)
if err != nil {
return "", err
}
info := strings.TrimSpace(apiStruct.Info)
if len(apiStruct.Service) == 0 {
return data, nil
}
fs, err := format.Source([]byte(strings.TrimSpace(apiStruct.StructBody)))
if err != nil {
str := err.Error()
lineNumber := strings.Index(str, ":")
if lineNumber > 0 {
ln, err := strconv.ParseInt(str[:lineNumber], 10, 64)
if err != nil {
return "", err
}
pn := 0
if len(info) > 0 {
pn = countRune(info, '\n') + 1
}
number := int(ln) + pn + 1
return "", errors.New(fmt.Sprintf("line: %d, %s", number, str[lineNumber+1:]))
var builder strings.Builder
s := bufio.NewScanner(strings.NewReader(data))
var tapCount = 0
for s.Scan() {
line := strings.TrimSpace(s.Text())
noCommentLine := util.RemoveComment(line)
if noCommentLine == rightParenthesis || noCommentLine == rightBrace {
tapCount -= 1
}
return "", err
}
var result string
if len(strings.TrimSpace(info)) > 0 {
result += strings.TrimSpace(info) + "\n\n"
}
if len(strings.TrimSpace(apiStruct.Imports)) > 0 {
result += strings.TrimSpace(apiStruct.Imports) + "\n\n"
}
if len(strings.TrimSpace(string(fs))) > 0 {
result += strings.TrimSpace(string(fs)) + "\n\n"
}
if len(strings.TrimSpace(apiStruct.Service)) > 0 {
result += strings.TrimSpace(apiStruct.Service) + "\n\n"
}
return result, nil
}
func countRune(s string, r rune) int {
count := 0
for _, c := range s {
if c == r {
count++
if tapCount < 0 {
line = strings.TrimSuffix(line, rightBrace)
line = strings.TrimSpace(line)
if strings.HasSuffix(line, leftBrace) {
tapCount += 1
}
}
util.WriteIndent(&builder, tapCount)
builder.WriteString(line + "\n")
if strings.HasSuffix(noCommentLine, leftParenthesis) || strings.HasSuffix(noCommentLine, leftBrace) {
tapCount += 1
}
}
return count
return strings.TrimSpace(builder.String()), nil
}

View File

@@ -0,0 +1,47 @@
package format
import (
"testing"
"github.com/stretchr/testify/assert"
)
const (
notFormattedStr = `
type Request struct {
Name string
}
type Response struct {
Message string
}
service A-api {
@server(
handler: GreetHandler
)
get /greet/from/:name(Request) returns (Response)
}
`
formattedStr = `type Request struct {
Name string
}
type Response struct {
Message string
}
service A-api {
@server(
handler: GreetHandler
)
get /greet/from/:name(Request) returns (Response)
}`
)
func TestInlineTypeNotExist(t *testing.T) {
r, err := apiFormat(notFormattedStr)
assert.Nil(t, err)
assert.Equal(t, r, formattedStr)
}

View File

@@ -1,11 +1,9 @@
package gogen
import (
"bytes"
"errors"
"fmt"
"os"
"os/exec"
"path"
"path/filepath"
"strconv"
@@ -30,7 +28,6 @@ var tmpDir = path.Join(os.TempDir(), "goctl")
func GoCommand(c *cli.Context) error {
apiFile := c.String("api")
dir := c.String("dir")
force := c.Bool("force")
if len(apiFile) == 0 {
return errors.New("missing -api")
}
@@ -38,10 +35,10 @@ func GoCommand(c *cli.Context) error {
return errors.New("missing -dir")
}
return DoGenProject(apiFile, dir, force)
return DoGenProject(apiFile, dir)
}
func DoGenProject(apiFile, dir string, force bool) error {
func DoGenProject(apiFile, dir string) error {
p, err := parser.NewParser(apiFile)
if err != nil {
return err
@@ -56,11 +53,10 @@ func DoGenProject(apiFile, dir string, force bool) error {
logx.Must(genConfig(dir, api))
logx.Must(genMain(dir, api))
logx.Must(genServiceContext(dir, api))
logx.Must(genTypes(dir, api, force))
logx.Must(genTypes(dir, api))
logx.Must(genHandlers(dir, api))
logx.Must(genRoutes(dir, api, force))
logx.Must(genRoutes(dir, api))
logx.Must(genLogic(dir, api))
createGoModFileIfNeed(dir)
if err := backupAndSweep(apiFile); err != nil {
return err
@@ -129,34 +125,3 @@ func sweep() error {
return nil
})
}
func createGoModFileIfNeed(dir string) {
absDir, err := filepath.Abs(dir)
if err != nil {
panic(err)
}
_, hasGoMod := util.FindGoModPath(dir)
if hasGoMod {
return
}
gopath := os.Getenv("GOPATH")
parent := path.Join(gopath, "src")
pos := strings.Index(absDir, parent)
if pos >= 0 {
return
}
moduleName := absDir[len(filepath.Dir(absDir))+1:]
cmd := exec.Command("go", "mod", "init", moduleName)
cmd.Dir = dir
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err = cmd.Run(); err != nil {
fmt.Println(err.Error())
}
outStr, errStr := string(stdout.Bytes()), string(stderr.Bytes())
fmt.Printf(outStr + "\n" + errStr)
}

View File

@@ -10,6 +10,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/tools/goctl/api/parser"
"github.com/tal-tech/go-zero/tools/goctl/rpc/execx"
)
const testApiTemplate = `
@@ -21,24 +22,33 @@ info(
version: 1.0
)
type Request struct {
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
}
// TODO: test
// {
type Request struct { // TODO: test
// TOOD
Name string ` + "`" + `path:"name,options=you|me"` + "`" + ` // }
} // TODO: test
// TODO: test
type Response struct {
Message string ` + "`" + `json:"message"` + "`" + `
}
@server(
// C0
group: greet/s1
)
// C1
service A-api {
@server(
// C2
@server( // C3
handler: GreetHandler
)
get /greet/from/:name(Request) returns (Response)
@server(
handler: NoResponseHandler
)
get /greet/get(Request) returns
get /greet/from/:name(Request) returns (Response) // hello
// C4
@handler NoResponseHandler // C5
get /greet/get(Request)
}
`
@@ -204,6 +214,111 @@ service A-api {
}
`
const hasCommentApiTest = `
type Inline struct {
}
type Request struct {
Inline
Name string ` + "`" + `path:"name,options=you|me"` + "`" + ` // name in path
}
type Response struct {
Message string ` + "`" + `json:"msg"` + "`" + ` // message
}
service A-api {
@doc(helloworld)
@server(
handler: GreetHandler
)
get /greet/from/:name(Request) returns (Response)
}
`
const hasInlineNoExistTest = `
type Request struct {
Inline
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
}
type Response struct {
Message string ` + "`" + `json:"message"` + "`" + ` // message
}
service A-api {
@doc(helloworld)
@server(
handler: GreetHandler
)
get /greet/from/:name(Request) returns (Response)
}
`
const importApi = `
type ImportData struct {
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
}
`
const hasImportApi = `
import "importApi.api"
type Request struct {
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
}
type Response struct {
Message string ` + "`" + `json:"message"` + "`" + ` // message
}
service A-api {
@server(
handler: GreetHandler
)
get /greet/from/:name(Request) returns (Response)
}
`
const noStructTagApi = `
type Request {
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
}
type XXX {}
type (
Response {
Message string ` + "`" + `json:"message"` + "`" + `
}
A {}
B struct {}
)
service A-api {
@handler GreetHandler
get /greet/from/:name(Request) returns (Response)
}
`
const nestTypeApi = `
type Request {
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
XXX struct {
}
}
service A-api {
@handler GreetHandler
get /greet/from/:name(Request)
}
`
func TestParser(t *testing.T) {
filename := "greet.api"
err := ioutil.WriteFile(filename, []byte(testApiTemplate), os.ModePerm)
@@ -217,13 +332,13 @@ func TestParser(t *testing.T) {
assert.Nil(t, err)
assert.Equal(t, len(api.Types), 2)
assert.Equal(t, len(api.Service.Routes), 2)
assert.Equal(t, len(api.Service.Routes()), 2)
assert.Equal(t, api.Service.Routes[0].Path, "/greet/from/:name")
assert.Equal(t, api.Service.Routes[1].Path, "/greet/get")
assert.Equal(t, api.Service.Routes()[0].Path, "/greet/from/:name")
assert.Equal(t, api.Service.Routes()[1].Path, "/greet/get")
assert.Equal(t, api.Service.Routes[1].RequestType.Name, "Request")
assert.Equal(t, api.Service.Routes[1].ResponseType.Name, "")
assert.Equal(t, api.Service.Routes()[1].RequestType.Name, "Request")
assert.Equal(t, api.Service.Routes()[1].ResponseType.Name, "")
validate(t, filename)
}
@@ -240,7 +355,7 @@ func TestMultiService(t *testing.T) {
api, err := parser.Parse()
assert.Nil(t, err)
assert.Equal(t, len(api.Service.Routes), 2)
assert.Equal(t, len(api.Service.Routes()), 2)
assert.Equal(t, len(api.Service.Groups), 2)
validate(t, filename)
@@ -267,10 +382,7 @@ func TestInvalidApiFile(t *testing.T) {
assert.Nil(t, err)
defer os.Remove(filename)
parser, err := parser.NewParser(filename)
assert.Nil(t, err)
_, err = parser.Parse()
_, err = parser.NewParser(filename)
assert.NotNil(t, err)
}
@@ -286,8 +398,8 @@ func TestAnonymousAnnotation(t *testing.T) {
api, err := parser.Parse()
assert.Nil(t, err)
assert.Equal(t, len(api.Service.Routes), 1)
assert.Equal(t, api.Service.Routes[0].Annotations[0].Value, "GreetHandler")
assert.Equal(t, len(api.Service.Routes()), 1)
assert.Equal(t, api.Service.Routes()[0].Annotations[0].Value, "GreetHandler")
validate(t, filename)
}
@@ -367,9 +479,94 @@ func TestApiRoutes(t *testing.T) {
validate(t, filename)
}
func TestHasCommentRoutes(t *testing.T) {
filename := "greet.api"
err := ioutil.WriteFile(filename, []byte(hasCommentApiTest), os.ModePerm)
assert.Nil(t, err)
defer os.Remove(filename)
parser, err := parser.NewParser(filename)
assert.Nil(t, err)
_, err = parser.Parse()
assert.Nil(t, err)
validate(t, filename)
}
func TestInlineTypeNotExist(t *testing.T) {
filename := "greet.api"
err := ioutil.WriteFile(filename, []byte(hasInlineNoExistTest), os.ModePerm)
assert.Nil(t, err)
defer os.Remove(filename)
parser, err := parser.NewParser(filename)
assert.Nil(t, err)
_, err = parser.Parse()
assert.Nil(t, err)
validate(t, filename)
}
func TestHasImportApi(t *testing.T) {
filename := "greet.api"
err := ioutil.WriteFile(filename, []byte(hasImportApi), os.ModePerm)
assert.Nil(t, err)
defer os.Remove(filename)
importApiName := "importApi.api"
err = ioutil.WriteFile(importApiName, []byte(importApi), os.ModePerm)
assert.Nil(t, err)
defer os.Remove(importApiName)
parser, err := parser.NewParser(filename)
assert.Nil(t, err)
api, err := parser.Parse()
assert.Nil(t, err)
var hasInline bool
for _, ty := range api.Types {
if ty.Name == "ImportData" {
hasInline = true
break
}
}
assert.True(t, hasInline)
validate(t, filename)
}
func TestNoStructApi(t *testing.T) {
filename := "greet.api"
err := ioutil.WriteFile(filename, []byte(noStructTagApi), os.ModePerm)
assert.Nil(t, err)
defer os.Remove(filename)
parser, err := parser.NewParser(filename)
assert.Nil(t, err)
spec, err := parser.Parse()
assert.Nil(t, err)
assert.Equal(t, len(spec.Types), 5)
validate(t, filename)
}
func TestNestTypeApi(t *testing.T) {
filename := "greet.api"
err := ioutil.WriteFile(filename, []byte(nestTypeApi), os.ModePerm)
assert.Nil(t, err)
defer os.Remove(filename)
_, err = parser.NewParser(filename)
assert.NotNil(t, err)
}
func validate(t *testing.T, api string) {
dir := "_go"
err := DoGenProject(api, dir, true)
os.RemoveAll(dir)
err := DoGenProject(api, dir)
defer os.RemoveAll(dir)
assert.Nil(t, err)
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
@@ -380,6 +577,9 @@ func validate(t *testing.T, api string) {
}
return nil
})
_, err = execx.Run("go test ./...", dir)
assert.Nil(t, err)
}
func validateCode(code string) error {

View File

@@ -60,8 +60,9 @@ func genConfig(dir string, api *spec.ApiSpec) error {
"auth": strings.Join(auths, "\n"),
})
if err != nil {
return nil
return err
}
formatCode := formatCode(buffer.String())
_, err = fp.WriteString(formatCode)
return err

View File

@@ -31,11 +31,11 @@ func genEtc(dir string, api *spec.ApiSpec) error {
defer fp.Close()
service := api.Service
host, ok := util.GetAnnotationValue(service.Annotations, "server", "host")
host, ok := util.GetAnnotationValue(service.Groups[0].Annotations, "server", "host")
if !ok {
host = "0.0.0.0"
}
port, ok := util.GetAnnotationValue(service.Annotations, "server", "port")
port, ok := util.GetAnnotationValue(service.Groups[0].Annotations, "server", "port")
if !ok {
port = strconv.Itoa(defaultPort)
}
@@ -55,6 +55,7 @@ func genEtc(dir string, api *spec.ApiSpec) error {
if err != nil {
return err
}
formatCode := formatCode(buffer.String())
_, err = fp.WriteString(formatCode)
return err

View File

@@ -103,7 +103,7 @@ func doGenToFile(dir, handler string, group spec.Group, route spec.Route, handle
buffer := new(bytes.Buffer)
err = template.Must(template.New("handlerTemplate").Parse(text)).Execute(buffer, handleObj)
if err != nil {
return nil
return err
}
formatCode := formatCode(buffer.String())

View File

@@ -72,8 +72,9 @@ func genMain(dir string, api *spec.ApiSpec) error {
"serviceName": api.Service.Name,
})
if err != nil {
return nil
return err
}
formatCode := formatCode(buffer.String())
_, err = fp.WriteString(formatCode)
return err

View File

@@ -49,8 +49,9 @@ func genMiddleware(dir string, middlewares []string) error {
"name": strings.Title(name),
})
if err != nil {
return nil
return err
}
formatCode := formatCode(buffer.String())
_, err = fp.WriteString(formatCode)
return err

View File

@@ -3,6 +3,7 @@ package gogen
import (
"bytes"
"fmt"
"os"
"path"
"sort"
"strings"
@@ -52,7 +53,7 @@ type (
jwtEnabled bool
signatureEnabled bool
authName string
middleware []string
middlewares []string
}
route struct {
method string
@@ -61,7 +62,7 @@ type (
}
)
func genRoutes(dir string, api *spec.ApiSpec, force bool) error {
func genRoutes(dir string, api *spec.ApiSpec) error {
var builder strings.Builder
groups, err := getRoutes(api)
if err != nil {
@@ -92,9 +93,9 @@ func genRoutes(dir string, api *spec.ApiSpec, force bool) error {
}
var routes string
if len(g.middleware) > 0 {
if len(g.middlewares) > 0 {
gbuilder.WriteString("\n}...,")
var params = g.middleware
var params = g.middlewares
for i := range params {
params[i] = "serverCtx." + params[i]
}
@@ -121,11 +122,7 @@ func genRoutes(dir string, api *spec.ApiSpec, force bool) error {
}
filename := path.Join(dir, handlerDir, routesFilename)
if !force {
if err := util.RemoveOrQuit(filename); err != nil {
return err
}
}
os.Remove(filename)
fp, created, err := apiutil.MaybeCreateFile(dir, handlerDir, routesFilename)
if err != nil {
@@ -143,8 +140,9 @@ func genRoutes(dir string, api *spec.ApiSpec, force bool) error {
"routesAdditions": strings.TrimSpace(builder.String()),
})
if err != nil {
return nil
return err
}
formatCode := formatCode(buffer.String())
_, err = fp.WriteString(formatCode)
return err
@@ -162,8 +160,7 @@ func genRouteImports(parentPkg string, api *spec.ApiSpec) string {
continue
}
}
importSet.AddStr(fmt.Sprintf("%s \"%s\"", folder,
util.JoinPackages(parentPkg, handlerDir, folder)))
importSet.AddStr(fmt.Sprintf("%s \"%s\"", toPrefix(folder), util.JoinPackages(parentPkg, handlerDir, folder)))
}
}
imports := importSet.KeysStr()
@@ -186,11 +183,11 @@ func getRoutes(api *spec.ApiSpec) ([]group, error) {
handler = getHandlerBaseName(handler) + "Handler(serverCtx)"
folder, ok := apiutil.GetAnnotationValue(r.Annotations, "server", groupProperty)
if ok {
handler = folder + "." + strings.ToUpper(handler[:1]) + handler[1:]
handler = toPrefix(folder) + "." + strings.ToUpper(handler[:1]) + handler[1:]
} else {
folder, ok = apiutil.GetAnnotationValue(g.Annotations, "server", groupProperty)
if ok {
handler = folder + "." + strings.ToUpper(handler[:1]) + handler[1:]
handler = toPrefix(folder) + "." + strings.ToUpper(handler[:1]) + handler[1:]
}
}
groupedRoutes.routes = append(groupedRoutes.routes, route{
@@ -206,7 +203,7 @@ func getRoutes(api *spec.ApiSpec) ([]group, error) {
}
if value, ok := apiutil.GetAnnotationValue(g.Annotations, "server", "middleware"); ok {
for _, item := range strings.Split(value, ",") {
groupedRoutes.middleware = append(groupedRoutes.middleware, item)
groupedRoutes.middlewares = append(groupedRoutes.middlewares, item)
}
}
routes = append(routes, groupedRoutes)
@@ -214,3 +211,7 @@ func getRoutes(api *spec.ApiSpec) ([]group, error) {
return routes, nil
}
func toPrefix(folder string) string {
return strings.ReplaceAll(folder, "/", "")
}

View File

@@ -90,8 +90,9 @@ func genServiceContext(dir string, api *spec.ApiSpec) error {
"middlewareAssignment": middlewareAssignment,
})
if err != nil {
return nil
return err
}
formatCode := formatCode(buffer.String())
_, err = fp.WriteString(formatCode)
return err

View File

@@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"io"
"os"
"path"
"strings"
"text/template"
@@ -42,18 +43,14 @@ func BuildTypes(types []spec.Type) (string, error) {
return builder.String(), nil
}
func genTypes(dir string, api *spec.ApiSpec, force bool) error {
func genTypes(dir string, api *spec.ApiSpec) error {
val, err := BuildTypes(api.Types)
if err != nil {
return err
}
filename := path.Join(dir, typesDir, typesFile)
if !force {
if err := util.RemoveOrQuit(filename); err != nil {
return err
}
}
os.Remove(filename)
fp, created, err := apiutil.MaybeCreateFile(dir, typesDir, typesFile)
if err != nil {
@@ -71,8 +68,9 @@ func genTypes(dir string, api *spec.ApiSpec, force bool) error {
"containsTime": api.ContainsTime(),
})
if err != nil {
return nil
return err
}
formatCode := formatCode(buffer.String())
_, err = fp.WriteString(formatCode)
return err
@@ -90,12 +88,6 @@ func convertTypeCase(types []spec.Type, t string) (string, error) {
if typ.Name == tp {
defTypes = append(defTypes, tp)
}
if len(typ.Annotations) > 0 {
if value, ok := apiutil.GetAnnotationValue(typ.Annotations, "serverReplacer", tp); ok {
t = strings.ReplaceAll(t, tp, value)
}
}
}
}

View File

@@ -26,14 +26,8 @@ func getParentPackage(dir string) (string, error) {
return filepath.ToSlash(filepath.Join(projectCtx.Path, strings.TrimPrefix(projectCtx.WorkDir, projectCtx.Dir))), nil
}
func writeIndent(writer io.Writer, indent int) {
for i := 0; i < indent; i++ {
fmt.Fprint(writer, "\t")
}
}
func writeProperty(writer io.Writer, name, tp, tag, comment string, indent int) error {
writeIndent(writer, indent)
util.WriteIndent(writer, indent)
var err error
if len(comment) > 0 {
comment = strings.TrimPrefix(comment, "//")

View File

@@ -77,7 +77,7 @@ public class {{.packetName}} extends HttpRequestPacket<{{.packetName}}.{{.packet
`
func genPacket(dir, packetName string, api *spec.ApiSpec) error {
for _, route := range api.Service.Routes {
for _, route := range api.Service.Routes() {
if err := createWith(dir, api, route, packetName); err != nil {
return err
}

View File

@@ -3,6 +3,7 @@ package new
import (
"os"
"path/filepath"
"strings"
"text/template"
"github.com/tal-tech/go-zero/tools/goctl/api/gogen"
@@ -11,25 +12,25 @@ import (
)
const apiTemplate = `
type Request struct {
Name string ` + "`" + `path:"name,options=you|me"` + "`" + ` // 框架自动验证请求参数是否合法
type Request {
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
}
type Response struct {
type Response {
Message string ` + "`" + `json:"message"` + "`" + `
}
service {{.name}}-api {
@handler GreetHandler
get /greet/from/:name(Request) returns (Response);
@handler {{.handler}}Handler
get /from/:name(Request) returns (Response);
}
`
func NewService(c *cli.Context) error {
args := c.Args()
dirName := "greet"
if len(args) > 0 {
dirName = args.First()
dirName := args.First()
if len(dirName) == 0 {
dirName = "greet"
}
abs, err := filepath.Abs(dirName)
@@ -53,11 +54,12 @@ func NewService(c *cli.Context) error {
defer fp.Close()
t := template.Must(template.New("template").Parse(apiTemplate))
if err := t.Execute(fp, map[string]string{
"name": dirName,
"name": dirName,
"handler": strings.Title(dirName),
}); err != nil {
return err
}
err = gogen.DoGenProject(apiFilePath, abs, true)
err = gogen.DoGenProject(apiFilePath, abs)
return err
}

View File

@@ -0,0 +1,268 @@
package parser
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"strings"
"github.com/tal-tech/go-zero/core/stringx"
"github.com/tal-tech/go-zero/tools/goctl/api/util"
)
const (
tokenInfo = "info"
tokenImport = "import"
tokenType = "type"
tokenService = "service"
tokenServiceAnnotation = "@server"
tokenStruct = "struct"
)
type (
ApiStruct struct {
Info string
Type string
Service string
Imports string
serviceBeginLine int
}
apiFileState interface {
process(api *ApiStruct, token string) (apiFileState, error)
}
apiRootState struct {
*baseState
}
apiInfoState struct {
*baseState
}
apiImportState struct {
*baseState
}
apiTypeState struct {
*baseState
}
apiServiceState struct {
*baseState
}
)
func ParseApi(src string) (*ApiStruct, error) {
var buffer = new(bytes.Buffer)
buffer.WriteString(src)
api := new(ApiStruct)
var lineNumber = api.serviceBeginLine
apiFile := baseState{r: bufio.NewReader(buffer), lineNumber: &lineNumber}
st := apiRootState{&apiFile}
for {
st, err := st.process(api, "")
if err == io.EOF {
return api, nil
}
if err != nil {
return nil, fmt.Errorf("near line: %d, %s", lineNumber, err.Error())
}
if st == nil {
return api, nil
}
}
}
func (s *apiRootState) process(api *ApiStruct, _ string) (apiFileState, error) {
var builder strings.Builder
for {
ch, err := s.readSkipComment()
if err != nil {
return nil, err
}
switch {
case isSpace(ch) || isNewline(ch) || ch == leftParenthesis:
token := builder.String()
token = strings.TrimSpace(token)
if len(token) == 0 {
continue
}
builder.Reset()
switch token {
case tokenInfo:
info := apiInfoState{s.baseState}
return info.process(api, token+string(ch))
case tokenImport:
tp := apiImportState{s.baseState}
return tp.process(api, token+string(ch))
case tokenType:
ty := apiTypeState{s.baseState}
return ty.process(api, token+string(ch))
case tokenService:
server := apiServiceState{s.baseState}
return server.process(api, token+string(ch))
case tokenServiceAnnotation:
server := apiServiceState{s.baseState}
return server.process(api, token+string(ch))
default:
if strings.HasPrefix(token, "//") {
continue
}
return nil, errors.New(fmt.Sprintf("invalid token %s at line %d", token, *s.lineNumber))
}
default:
builder.WriteRune(ch)
}
}
}
func (s *apiInfoState) process(api *ApiStruct, token string) (apiFileState, error) {
for {
line, err := s.readLine()
if err != nil {
return nil, err
}
api.Info += newline + token + line
token = ""
if strings.TrimSpace(line) == string(rightParenthesis) {
return &apiRootState{s.baseState}, nil
}
}
}
func (s *apiImportState) process(api *ApiStruct, token string) (apiFileState, error) {
line, err := s.readLine()
if err != nil {
return nil, err
}
line = token + line
line = util.RemoveComment(line)
if len(strings.Fields(line)) != 2 {
return nil, errors.New("import syntax error: " + line)
}
api.Imports += newline + line
return &apiRootState{s.baseState}, nil
}
func (s *apiTypeState) process(api *ApiStruct, token string) (apiFileState, error) {
var blockCount = 0
var braceCount = 0
for {
line, err := s.readLine()
if err != nil {
return nil, err
}
line = token + line
if braceCount == 0 {
line = mayInsertStructKeyword(line)
}
api.Type += newline + newline + line
line = strings.TrimSpace(line)
line = util.RemoveComment(line)
token = ""
if strings.HasSuffix(line, leftBrace) {
blockCount++
braceCount++
}
if strings.HasSuffix(line, string(leftParenthesis)) {
blockCount++
}
if strings.HasSuffix(line, string(rightBrace)) {
blockCount--
braceCount--
}
if strings.HasSuffix(line, string(rightParenthesis)) {
blockCount--
}
if braceCount >= 2 {
return nil, errors.New("nested type not supported: " + line)
}
if braceCount < 0 {
line = strings.TrimSuffix(line, string(rightBrace))
line = strings.TrimSpace(line)
if strings.HasSuffix(line, leftBrace) {
blockCount++
braceCount++
}
}
if blockCount == 0 {
return &apiRootState{s.baseState}, nil
}
}
}
func (s *apiServiceState) process(api *ApiStruct, token string) (apiFileState, error) {
var blockCount = 0
for {
line, err := s.readLineSkipComment()
if err != nil {
return nil, err
}
line = token + line
token = ""
api.Service += newline + line
line = strings.TrimSpace(line)
line = util.RemoveComment(line)
if strings.HasSuffix(line, leftBrace) {
blockCount++
}
if strings.HasSuffix(line, string(leftParenthesis)) {
blockCount++
}
if line == string(rightBrace) {
blockCount--
}
if line == string(rightParenthesis) {
blockCount--
}
if blockCount == 0 {
return &apiRootState{s.baseState}, nil
}
}
}
func mayInsertStructKeyword(line string) string {
line = util.RemoveComment(line)
if !strings.HasSuffix(line, leftBrace) && !strings.HasSuffix(line, string(rightBrace)) {
return line
}
fields := strings.Fields(line)
if stringx.Contains(fields, tokenStruct) ||
stringx.Contains(fields, tokenStruct+leftBrace) ||
stringx.Contains(fields, tokenStruct+leftBrace+string(rightBrace)) ||
len(fields) <= 1 {
return line
}
var insertIndex int
if fields[0] == tokenType {
insertIndex = 2
} else {
insertIndex = 1
}
if insertIndex >= len(fields) {
return line
}
var result []string
result = append(result, fields[:insertIndex]...)
result = append(result, tokenStruct)
result = append(result, fields[insertIndex:]...)
return strings.Join(result, " ")
}

View File

@@ -34,7 +34,7 @@ func (s *baseState) parseProperties() (map[string]string, error) {
var st = startState
for {
ch, err := s.read()
ch, err := s.readSkipComment()
if err != nil {
return nil, err
}
@@ -164,6 +164,60 @@ func (s *baseState) read() (rune, error) {
return value, nil
}
func (s *baseState) readSkipComment() (rune, error) {
ch, err := s.read()
if err != nil {
return 0, err
}
if isSlash(ch) {
value, err := s.mayReadToEndOfLine()
if err != nil {
return 0, err
}
if value > 0 {
ch = value
}
}
return ch, nil
}
func (s *baseState) mayReadToEndOfLine() (rune, error) {
ch, err := s.read()
if err != nil {
return 0, err
}
if isSlash(ch) {
for {
value, err := s.read()
if err != nil {
return 0, err
}
if isNewline(value) {
return value, nil
}
}
}
err = s.unread()
return 0, err
}
func (s *baseState) readLineSkipComment() (string, error) {
line, err := s.readLine()
if err != nil {
return "", err
}
var commentIdx = strings.Index(line, "//")
if commentIdx >= 0 {
return line[:commentIdx], nil
}
return line, nil
}
func (s *baseState) readLine() (string, error) {
line, _, err := s.r.ReadLine()
if err != nil {

View File

@@ -30,7 +30,7 @@ func newEntity(state *baseState, api *spec.ApiSpec, parser entityParser) entity
}
func (s *entity) process() error {
line, err := s.state.readLine()
line, err := s.state.readLineSkipComment()
if err != nil {
return err
}
@@ -59,7 +59,7 @@ func (s *entity) process() error {
var annos []spec.Annotation
memberLoop:
for {
ch, err := s.state.read()
ch, err := s.state.readSkipComment()
if err != nil {
return err
}
@@ -70,13 +70,13 @@ memberLoop:
case ch == at:
annotationLoop:
for {
next, err := s.state.read()
next, err := s.state.readSkipComment()
if err != nil {
return err
}
switch {
case isSpace(next):
if builder.Len() > 0 {
if builder.Len() > 0 && annoName == "" {
annoName = builder.String()
builder.Reset()
}
@@ -84,6 +84,7 @@ memberLoop:
if builder.Len() == 0 {
return errors.New("invalid annotation format")
}
if len(annoName) > 0 {
value := builder.String()
if value != string(leftParenthesis) {
@@ -127,7 +128,7 @@ memberLoop:
}
var line string
line, err = s.state.readLine()
line, err = s.state.readLineSkipComment()
if err != nil {
return err
}

View File

@@ -3,6 +3,7 @@ package parser
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
@@ -14,9 +15,8 @@ import (
)
type Parser struct {
r *bufio.Reader
typeDef string
api *ApiStruct
r *bufio.Reader
api *ApiStruct
}
func NewParser(filename string) (*Parser, error) {
@@ -34,39 +34,57 @@ func NewParser(filename string) (*Parser, error) {
if err != nil {
return nil, err
}
for _, item := range strings.Split(apiStruct.Imports, "\n") {
ip := strings.TrimSpace(item)
if len(ip) > 0 {
item := strings.TrimPrefix(item, "import")
importLine := strings.TrimSpace(item)
if len(importLine) > 0 {
item := strings.TrimPrefix(importLine, "import")
item = strings.TrimSpace(item)
item = strings.TrimPrefix(item, `"`)
item = strings.TrimSuffix(item, `"`)
var path = item
if !util.FileExists(item) {
path = filepath.Join(filepath.Dir(apiAbsPath), item)
}
content, err := ioutil.ReadFile(path)
if err != nil {
return nil, errors.New("import api file not exist: " + item)
}
importStruct, err := ParseApi(string(content))
if err != nil {
return nil, err
}
apiStruct.StructBody += "\n" + string(content)
if len(importStruct.Imports) > 0 {
return nil, errors.New("import api should not import another api file recursive")
}
apiStruct.Type += "\n" + importStruct.Type
apiStruct.Service += "\n" + importStruct.Service
}
}
if len(strings.TrimSpace(apiStruct.Service)) == 0 {
return nil, errors.New("api has no service defined")
}
var buffer = new(bytes.Buffer)
buffer.WriteString(apiStruct.Service)
return &Parser{
r: bufio.NewReader(buffer),
typeDef: apiStruct.StructBody,
api: apiStruct,
r: bufio.NewReader(buffer),
api: apiStruct,
}, nil
}
func (p *Parser) Parse() (api *spec.ApiSpec, err error) {
api = new(spec.ApiSpec)
var sp = StructParser{Src: p.typeDef}
var sp = StructParser{Src: p.api.Type}
types, err := sp.Parse()
if err != nil {
return nil, err
}
api.Types = types
var lineNumber = p.api.serviceBeginLine
st := newRootState(p.r, &lineNumber)

View File

@@ -23,7 +23,7 @@ func (s rootState) process(api *spec.ApiSpec) (state, error) {
var annos []spec.Annotation
var builder strings.Builder
for {
ch, err := s.read()
ch, err := s.readSkipComment()
if err != nil {
return nil, err
}
@@ -33,6 +33,7 @@ func (s rootState) process(api *spec.ApiSpec) (state, error) {
if builder.Len() == 0 {
continue
}
token := builder.String()
builder.Reset()
return s.processToken(token, annos)
@@ -44,10 +45,11 @@ func (s rootState) process(api *spec.ApiSpec) (state, error) {
var annoName string
annoLoop:
for {
next, err := s.read()
next, err := s.readSkipComment()
if err != nil {
return nil, err
}
switch {
case isSpace(next):
if builder.Len() > 0 {
@@ -58,6 +60,7 @@ func (s rootState) process(api *spec.ApiSpec) (state, error) {
if err := s.unread(); err != nil {
return nil, err
}
if builder.Len() > 0 {
annoName = builder.String()
builder.Reset()
@@ -66,6 +69,7 @@ func (s rootState) process(api *spec.ApiSpec) (state, error) {
if err != nil {
return nil, err
}
annos = append(annos, spec.Annotation{
Name: annoName,
Properties: attrs,
@@ -79,9 +83,11 @@ func (s rootState) process(api *spec.ApiSpec) (state, error) {
if builder.Len() == 0 {
return nil, fmt.Errorf("incorrect %q at the beginning of the line", leftParenthesis)
}
if err := s.unread(); err != nil {
return nil, err
}
token := builder.String()
builder.Reset()
return s.processToken(token, annos)

View File

@@ -40,9 +40,7 @@ func (s *serviceState) process(api *spec.ApiSpec) (state, error) {
}
api.Service = spec.Service{
Name: name,
Annotations: append(api.Service.Annotations, s.annos...),
Routes: append(api.Service.Routes, routes...),
Name: name,
Groups: append(api.Service.Groups, spec.Group{
Annotations: s.annos,
Routes: routes,

View File

@@ -1,95 +0,0 @@
package parser
import (
"fmt"
"strings"
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
"github.com/tal-tech/go-zero/tools/goctl/util"
)
type typeState struct {
*baseState
annos []spec.Annotation
}
func newTypeState(state *baseState, annos []spec.Annotation) state {
return &typeState{
baseState: state,
annos: annos,
}
}
func (s *typeState) process(api *spec.ApiSpec) (state, error) {
var name string
var members []spec.Member
parser := &typeEntityParser{
acceptName: func(n string) {
name = n
},
acceptMember: func(member spec.Member) {
members = append(members, member)
},
}
ent := newEntity(s.baseState, api, parser)
if err := ent.process(); err != nil {
return nil, err
}
api.Types = append(api.Types, spec.Type{
Name: name,
Annotations: s.annos,
Members: members,
})
return newRootState(s.r, s.lineNumber), nil
}
type typeEntityParser struct {
acceptName func(name string)
acceptMember func(member spec.Member)
}
func (p *typeEntityParser) parseLine(line string, api *spec.ApiSpec, annos []spec.Annotation) error {
index := strings.Index(line, "//")
comment := ""
if index >= 0 {
comment = line[index+2:]
line = strings.TrimSpace(line[:index])
}
fields := strings.Fields(line)
if len(fields) == 0 {
return nil
}
if len(fields) == 1 {
p.acceptMember(spec.Member{
Annotations: annos,
Name: fields[0],
Type: fields[0],
IsInline: true,
})
return nil
}
name := fields[0]
tp := fields[1]
var tag string
if len(fields) > 2 {
tag = fields[2]
} else {
tag = fmt.Sprintf("`json:\"%s\"`", util.Untitle(name))
}
p.acceptMember(spec.Member{
Annotations: annos,
Name: name,
Type: tp,
Tag: tag,
Comment: comment,
IsInline: false,
})
return nil
}
func (p *typeEntityParser) setEntityName(name string) {
p.acceptName(name)
}

View File

@@ -2,22 +2,12 @@ package parser
import (
"bufio"
"errors"
"strings"
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
)
var emptyType spec.Type
type ApiStruct struct {
Info string
StructBody string
Service string
Imports string
serviceBeginLine int
}
func GetType(api *spec.ApiSpec, t string) spec.Type {
for _, tp := range api.Types {
if tp.Name == t {
@@ -36,6 +26,10 @@ func isSpace(r rune) bool {
return r == ' ' || r == '\t'
}
func isSlash(r rune) bool {
return r == '/'
}
func isNewline(r rune) bool {
return r == '\n' || r == '\r'
}
@@ -69,82 +63,3 @@ func skipSpaces(r *bufio.Reader) error {
func unread(r *bufio.Reader) error {
return r.UnreadRune()
}
func ParseApi(api string) (*ApiStruct, error) {
var result ApiStruct
scanner := bufio.NewScanner(strings.NewReader(api))
var parseInfo = false
var parseImport = false
var parseType = false
var parseService = false
var segment string
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "info(" {
parseInfo = true
}
if line == ")" && parseInfo {
parseInfo = false
result.Info = segment + ")"
segment = ""
continue
}
if isImportBeginLine(line) {
parseImport = true
}
if parseImport && (isTypeBeginLine(line) || isServiceBeginLine(line)) {
parseImport = false
result.Imports = segment
segment = line + "\n"
continue
}
if isTypeBeginLine(line) {
parseType = true
}
if isServiceBeginLine(line) {
parseService = true
if parseType {
parseType = false
result.StructBody = segment
segment = line + "\n"
continue
}
}
segment += scanner.Text() + "\n"
}
if !parseService {
return nil, errors.New("no service defined")
}
result.Service = segment
result.serviceBeginLine = lineBeginOfService(api)
return &result, nil
}
func isImportBeginLine(line string) bool {
return strings.HasPrefix(line, "import") && strings.HasSuffix(line, ".api")
}
func isTypeBeginLine(line string) bool {
return strings.HasPrefix(line, "type")
}
func isServiceBeginLine(line string) bool {
return strings.HasPrefix(line, "@server") || (strings.HasPrefix(line, "service") && strings.HasSuffix(line, "{"))
}
func lineBeginOfService(api string) int {
scanner := bufio.NewScanner(strings.NewReader(api))
var number = 0
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if isServiceBeginLine(line) {
break
}
number++
}
return number
}

View File

@@ -40,7 +40,7 @@ func (p *Parser) validateDuplicateProperty(tp spec.Type) (bool, string) {
func (p *Parser) validateDuplicateRouteHandler(api *spec.ApiSpec) (bool, string) {
var names []string
for _, r := range api.Service.Routes {
for _, r := range api.Service.Routes() {
handler, ok := util.GetAnnotationValue(r.Annotations, "server", "handler")
if !ok {
return false, fmt.Sprintf("missing handler annotation for %s", r.Path)

View File

@@ -16,4 +16,5 @@ const (
multilineBeginTag = '>'
multilineEndTag = '<'
semicolon = ';'
newline = "\n"
)

View File

@@ -27,6 +27,14 @@ type Attribute struct {
value string
}
func (s Service) Routes() []Route {
var result []Route
for _, group := range s.Groups {
result = append(result, group.Routes...)
}
return result
}
func (m Member) IsOptional() bool {
var option string

View File

@@ -57,10 +57,8 @@ type (
}
Service struct {
Name string
Annotations []Annotation
Routes []Route
Groups []Group
Name string
Groups []Group
}
Type struct {

View File

@@ -36,7 +36,7 @@ func genHandler(dir, webApi, caller string, api *spec.ApiSpec, unwrapApi bool) e
defer fp.Close()
var localTypes []spec.Type
for _, route := range api.Service.Routes {
for _, route := range api.Service.Routes() {
rts := apiutil.GetLocalTypes(api, route)
localTypes = append(localTypes, rts...)
}
@@ -121,7 +121,7 @@ func genTypes(localTypes []spec.Type, inlineType func(string) (*spec.Type, error
func genApi(api *spec.ApiSpec, localTypes []spec.Type, caller string, prefixForType func(string) string) (string, error) {
var builder strings.Builder
for _, route := range api.Service.Routes {
for _, route := range api.Service.Routes() {
handler, ok := apiutil.GetAnnotationValue(route.Annotations, "server", "handler")
if !ok {
return "", fmt.Errorf("missing handler annotation for route %q", route.Path)

View File

@@ -130,7 +130,7 @@ func GetSharedTypes(api *spec.ApiSpec) []spec.Type {
}
return false
}
for _, route := range api.Service.Routes {
for _, route := range api.Service.Routes() {
var rts []spec.Type
getTypeRecursive(route.RequestType, types, &rts)
getTypeRecursive(route.ResponseType, types, &rts)

View File

@@ -26,19 +26,6 @@ func MaybeCreateFile(dir, subdir, file string) (fp *os.File, created bool, err e
return
}
func ClearAndOpenFile(fpath string) (*os.File, error) {
f, err := os.OpenFile(fpath, os.O_WRONLY|os.O_TRUNC, 0600)
if err != nil {
return nil, err
}
_, err = f.WriteString("")
if err != nil {
return nil, err
}
return f, nil
}
func WrapErr(err error, message string) error {
return errors.New(message + ", " + err.Error())
}
@@ -75,3 +62,17 @@ func ComponentName(api *spec.ApiSpec) string {
}
return name + "Components"
}
func WriteIndent(writer io.Writer, indent int) {
for i := 0; i < indent; i++ {
fmt.Fprint(writer, "\t")
}
}
func RemoveComment(line string) string {
var commentIdx = strings.Index(line, "//")
if commentIdx >= 0 {
return strings.TrimSpace(line[:commentIdx])
}
return strings.TrimSpace(line)
}

View File

@@ -2,16 +2,113 @@ package docker
import (
"errors"
"os"
"path/filepath"
"strings"
"text/template"
"github.com/tal-tech/go-zero/tools/goctl/gen"
"github.com/tal-tech/go-zero/tools/goctl/util"
ctlutil "github.com/tal-tech/go-zero/tools/goctl/util"
"github.com/urfave/cli"
)
const (
etcDir = "etc"
yamlEtx = ".yaml"
)
func DockerCommand(c *cli.Context) error {
goFile := c.String("go")
if len(goFile) == 0 {
return errors.New("-go can't be empty")
}
return gen.GenerateDockerfile(goFile, "-f", "etc/config.yaml")
cfg, err := findConfig(goFile, etcDir)
if err != nil {
return err
}
return generateDockerfile(goFile, "-f", "etc/"+cfg)
}
func findConfig(file, dir string) (string, error) {
var files []string
err := filepath.Walk(dir, func(path string, f os.FileInfo, _ error) error {
if !f.IsDir() {
if filepath.Ext(f.Name()) == yamlEtx {
files = append(files, f.Name())
}
}
return nil
})
if err != nil {
return "", err
}
if len(files) == 0 {
return "", errors.New("no yaml file")
}
name := strings.TrimSuffix(filepath.Base(file), ".go")
for _, f := range files {
if strings.Index(f, name) == 0 {
return f, nil
}
}
return files[0], nil
}
func generateDockerfile(goFile string, args ...string) error {
projPath, err := getFilePath(filepath.Dir(goFile))
if err != nil {
return err
}
pos := strings.IndexByte(projPath, '/')
if pos >= 0 {
projPath = projPath[pos+1:]
}
out, err := util.CreateIfNotExist("Dockerfile")
if err != nil {
return err
}
defer out.Close()
text, err := ctlutil.LoadTemplate(category, dockerTemplateFile, dockerTemplate)
if err != nil {
return err
}
var builder strings.Builder
for _, arg := range args {
builder.WriteString(`, "` + arg + `"`)
}
t := template.Must(template.New("dockerfile").Parse(text))
return t.Execute(out, map[string]string{
"goRelPath": projPath,
"goFile": goFile,
"exeFile": util.FileNameWithoutExt(filepath.Base(goFile)),
"argument": builder.String(),
})
}
func getFilePath(file string) (string, error) {
wd, err := os.Getwd()
if err != nil {
return "", err
}
projPath, ok := util.FindGoModPath(filepath.Join(wd, file))
if !ok {
projPath, err = util.PathFromGoSrc()
if err != nil {
return "", errors.New("no go.mod found, or not in GOPATH")
}
}
return projPath, nil
}

View File

@@ -0,0 +1,44 @@
package docker
import (
"github.com/tal-tech/go-zero/tools/goctl/util"
"github.com/urfave/cli"
)
const (
category = "docker"
dockerTemplateFile = "docker.tpl"
dockerTemplate = `FROM golang:alpine AS builder
LABEL stage=gobuilder
ENV CGO_ENABLED 0
ENV GOOS linux
ENV GOPROXY https://goproxy.cn,direct
WORKDIR /build/zero
COPY . .
COPY {{.goRelPath}}/etc /app/etc
RUN go build -ldflags="-s -w" -o /app/{{.exeFile}} {{.goRelPath}}/{{.goFile}}
FROM alpine
RUN apk update --no-cache
RUN apk add --no-cache ca-certificates
RUN apk add --no-cache tzdata
ENV TZ Asia/Shanghai
WORKDIR /app
COPY --from=builder /app/{{.exeFile}} /app/{{.exeFile}}
COPY --from=builder /app/etc /app/etc
CMD ["./{{.exeFile}}"{{.argument}}]
`
)
func GenTemplates(_ *cli.Context) error {
return util.InitTemplates(category, map[string]string{
dockerTemplateFile: dockerTemplate,
})
}

View File

@@ -1,42 +0,0 @@
package gen
import (
"path/filepath"
"strings"
"text/template"
"github.com/tal-tech/go-zero/tools/goctl/util"
"github.com/tal-tech/go-zero/tools/goctl/vars"
)
func GenerateDockerfile(goFile string, args ...string) error {
projPath, err := getFilePath(filepath.Dir(goFile))
if err != nil {
return err
}
pos := strings.IndexByte(projPath, '/')
if pos >= 0 {
projPath = projPath[pos+1:]
}
out, err := util.CreateIfNotExist("Dockerfile")
if err != nil {
return err
}
defer out.Close()
var builder strings.Builder
for _, arg := range args {
builder.WriteString(`, "` + arg + `"`)
}
t := template.Must(template.New("dockerfile").Parse(dockerTemplate))
return t.Execute(out, map[string]string{
"projectName": vars.ProjectName,
"goRelPath": projPath,
"goFile": goFile,
"exeFile": util.FileNameWithoutExt(goFile),
"argument": builder.String(),
})
}

View File

@@ -1,26 +0,0 @@
package gen
import (
"errors"
"os"
"path/filepath"
"github.com/tal-tech/go-zero/tools/goctl/util"
)
func getFilePath(file string) (string, error) {
wd, err := os.Getwd()
if err != nil {
return "", err
}
projPath, ok := util.FindGoModPath(filepath.Join(wd, file))
if !ok {
projPath, err = util.PathFromGoSrc()
if err != nil {
return "", errors.New("no go.mod found, or not in GOPATH")
}
}
return projPath, nil
}

View File

@@ -1,27 +0,0 @@
package gen
const dockerTemplate = `FROM golang:alpine AS builder
LABEL stage=gobuilder
ENV CGO_ENABLED 0
ENV GOOS linux
ENV GOPROXY https://goproxy.cn,direct
WORKDIR $GOPATH/src/{{.projectName}}
COPY . .
RUN go build -ldflags="-s -w" -o /app/{{.exeFile}} {{.goRelPath}}/{{.goFile}}
FROM alpine
RUN apk update --no-cache
RUN apk add --no-cache ca-certificates
RUN apk add --no-cache tzdata
ENV TZ Asia/Shanghai
WORKDIR /app
COPY --from=builder /app/{{.exeFile}} /app/{{.exeFile}}
CMD ["./{{.exeFile}}"{{.argument}}]
`

View File

@@ -25,7 +25,7 @@ import (
)
var (
BuildVersion = "20201021"
BuildVersion = "20201108"
commands = []cli.Command{
{
Name: "api",
@@ -98,10 +98,6 @@ var (
Name: "api",
Usage: "the api file",
},
cli.BoolFlag{
Name: "force",
Usage: "force override the exist files",
},
},
Action: gogen.GoCommand,
},
@@ -188,16 +184,12 @@ var (
},
{
Name: "docker",
Usage: "generate Dockerfile and Makefile",
Usage: "generate Dockerfile",
Flags: []cli.Flag{
cli.StringFlag{
Name: "go",
Usage: "the file that contains main function",
},
cli.StringFlag{
Name: "namespace, n",
Usage: "which namespace of kubernetes to deploy the service",
},
},
Action: docker.DockerCommand,
},
@@ -209,6 +201,10 @@ var (
Name: "new",
Usage: `generate rpc demo service`,
Flags: []cli.Flag{
cli.StringFlag{
Name: "style",
Usage: "the file naming style, lower|camel|snake,default is lower",
},
cli.BoolFlag{
Name: "idea",
Usage: "whether the command execution environment is from idea plugin. [optional]",
@@ -224,10 +220,6 @@ var (
Name: "out, o",
Usage: "the target path of proto",
},
cli.BoolFlag{
Name: "idea",
Usage: "whether the command execution environment is from idea plugin. [optional]",
},
},
Action: rpc.RpcTemplate,
},
@@ -247,6 +239,10 @@ var (
Name: "dir, d",
Usage: `the target path of the code`,
},
cli.StringFlag{
Name: "style",
Usage: "the file naming style, lower|camel|snake,default is lower",
},
cli.BoolFlag{
Name: "idea",
Usage: "whether the command execution environment is from idea plugin. [optional]",
@@ -278,7 +274,7 @@ var (
},
cli.StringFlag{
Name: "style",
Usage: "the file naming style, lower|camel|underline,default is lower",
Usage: "the file naming style, lower|camel|snake,default is lower",
},
cli.BoolFlag{
Name: "cache, c",

View File

@@ -26,7 +26,8 @@ goctl model 为go-zero下的工具模块中的组件之一目前支持识别m
* 生成代码示例
``` go
```go
package model
import (
@@ -48,9 +49,9 @@ goctl model 为go-zero下的工具模块中的组件之一目前支持识别m
userRowsExpectAutoSet = strings.Join(stringx.Remove(userFieldNames, "id", "create_time", "update_time"), ",")
userRowsWithPlaceHolder = strings.Join(stringx.Remove(userFieldNames, "id", "create_time", "update_time"), "=?,") + "=?"
cacheUserMobilePrefix = "cache#User#mobile#"
cacheUserIdPrefix = "cache#User#id#"
cacheUserNamePrefix = "cache#User#name#"
cacheUserMobilePrefix = "cache#User#mobile#"
)
type (
@@ -71,23 +72,28 @@ goctl model 为go-zero下的工具模块中的组件之一目前支持识别m
}
)
func NewUserModel(conn sqlx.SqlConn, c cache.CacheConf, table string) *UserModel {
func NewUserModel(conn sqlx.SqlConn, c cache.CacheConf) *UserModel {
return &UserModel{
CachedConn: sqlc.NewConn(conn, c),
table: table,
table: "user",
}
}
func (m *UserModel) Insert(data User) (sql.Result, error) {
query := `insert into ` + m.table + `(` + userRowsExpectAutoSet + `) value (?, ?, ?, ?, ?)`
return m.ExecNoCache(query, data.Name, data.Password, data.Mobile, data.Gender, data.Nickname)
userNameKey := fmt.Sprintf("%s%v", cacheUserNamePrefix, data.Name)
userMobileKey := fmt.Sprintf("%s%v", cacheUserMobilePrefix, data.Mobile)
ret, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?)", m.table, userRowsExpectAutoSet)
return conn.Exec(query, data.Name, data.Password, data.Mobile, data.Gender, data.Nickname)
}, userNameKey, userMobileKey)
return ret, err
}
func (m *UserModel) FindOne(id int64) (*User, error) {
userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, id)
var resp User
err := m.QueryRow(&resp, userIdKey, func(conn sqlx.SqlConn, v interface{}) error {
query := `select ` + userRows + ` from ` + m.table + ` where id = ? limit 1`
query := fmt.Sprintf("select %s from %s where id = ? limit 1", userRows, m.table)
return conn.QueryRow(v, query, id)
})
switch err {
@@ -103,18 +109,13 @@ goctl model 为go-zero下的工具模块中的组件之一目前支持识别m
func (m *UserModel) FindOneByName(name string) (*User, error) {
userNameKey := fmt.Sprintf("%s%v", cacheUserNamePrefix, name)
var resp User
err := m.QueryRowIndex(&resp, userNameKey, func(primary interface{}) string {
return fmt.Sprintf("%s%v", cacheUserIdPrefix, primary)
}, func(conn sqlx.SqlConn, v interface{}) (i interface{}, e error) {
query := `select ` + userRows + ` from ` + m.table + ` where name = ? limit 1`
err := m.QueryRowIndex(&resp, userNameKey, m.formatPrimary, func(conn sqlx.SqlConn, v interface{}) (i interface{}, e error) {
query := fmt.Sprintf("select %s from %s where name = ? limit 1", userRows, m.table)
if err := conn.QueryRow(&resp, query, name); err != nil {
return nil, err
}
return resp.Id, nil
}, func(conn sqlx.SqlConn, v, primary interface{}) error {
query := `select ` + userRows + ` from ` + m.table + ` where id = ? limit 1`
return conn.QueryRow(v, query, primary)
})
}, m.queryPrimary)
switch err {
case nil:
return &resp, nil
@@ -128,18 +129,13 @@ goctl model 为go-zero下的工具模块中的组件之一目前支持识别m
func (m *UserModel) FindOneByMobile(mobile string) (*User, error) {
userMobileKey := fmt.Sprintf("%s%v", cacheUserMobilePrefix, mobile)
var resp User
err := m.QueryRowIndex(&resp, userMobileKey, func(primary interface{}) string {
return fmt.Sprintf("%s%v", cacheUserIdPrefix, primary)
}, func(conn sqlx.SqlConn, v interface{}) (i interface{}, e error) {
query := `select ` + userRows + ` from ` + m.table + ` where mobile = ? limit 1`
err := m.QueryRowIndex(&resp, userMobileKey, m.formatPrimary, func(conn sqlx.SqlConn, v interface{}) (i interface{}, e error) {
query := fmt.Sprintf("select %s from %s where mobile = ? limit 1", userRows, m.table)
if err := conn.QueryRow(&resp, query, mobile); err != nil {
return nil, err
}
return resp.Id, nil
}, func(conn sqlx.SqlConn, v, primary interface{}) error {
query := `select ` + userRows + ` from ` + m.table + ` where id = ? limit 1`
return conn.QueryRow(v, query, primary)
})
}, m.queryPrimary)
switch err {
case nil:
return &resp, nil
@@ -153,7 +149,7 @@ goctl model 为go-zero下的工具模块中的组件之一目前支持识别m
func (m *UserModel) Update(data User) error {
userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, data.Id)
_, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
query := `update ` + m.table + ` set ` + userRowsWithPlaceHolder + ` where id = ?`
query := fmt.Sprintf("update %s set %s where id = ?", m.table, userRowsWithPlaceHolder)
return conn.Exec(query, data.Name, data.Password, data.Mobile, data.Gender, data.Nickname, data.Id)
}, userIdKey)
return err
@@ -164,16 +160,26 @@ goctl model 为go-zero下的工具模块中的组件之一目前支持识别m
if err != nil {
return err
}
userMobileKey := fmt.Sprintf("%s%v", cacheUserMobilePrefix, data.Mobile)
userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, id)
userNameKey := fmt.Sprintf("%s%v", cacheUserNamePrefix, data.Name)
userMobileKey := fmt.Sprintf("%s%v", cacheUserMobilePrefix, data.Mobile)
_, err = m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
query := `delete from ` + m.table + ` where id = ?`
query := fmt.Sprintf("delete from %s where id = ?", m.table)
return conn.Exec(query, id)
}, userIdKey, userNameKey, userMobileKey)
}, userMobileKey, userIdKey, userNameKey)
return err
}
```
func (m *UserModel) formatPrimary(primary interface{}) string {
return fmt.Sprintf("%s%v", cacheUserIdPrefix, primary)
}
func (m *UserModel) queryPrimary(conn sqlx.SqlConn, v, primary interface{}) error {
query := fmt.Sprintf("select %s from %s where id = ? limit 1", userRows, m.table)
return conn.QueryRow(v, query, primary)
}
```
## 用法
@@ -212,16 +218,18 @@ OPTIONS:
```
NAME:
goctl model mysql ddl - generate mysql model from ddl
goctl model mysql ddl - generate mysql model from ddl
USAGE:
goctl model mysql ddl [command options] [arguments...]
USAGE:
goctl model mysql ddl [command options] [arguments...]
OPTIONS:
--src value, -s value the path or path globbing patterns of the ddl
--dir value, -d value the target dir
--style value the file naming style, lower|camel|underline,default is lower
--cache, -c generate code with cache [optional]
--idea for idea plugin [optional]
OPTIONS:
--src value, -s value the path or path globbing patterns of the ddl
--dir value, -d value the target dir
--cache, -c generate code with cache [optional]
--idea for idea plugin [optional]
```
* datasource
@@ -233,22 +241,26 @@ OPTIONS:
help
```
NAME:
goctl model mysql datasource - generate model from datasource
NAME:
goctl model mysql datasource - generate model from datasource
USAGE:
goctl model mysql datasource [command options] [arguments...]
USAGE:
goctl model mysql datasource [command options] [arguments...]
OPTIONS:
--url value the data source of database,like "root:password@tcp(127.0.0.1:3306)/database
--table value, -t value the table or table globbing patterns in the database
--cache, -c generate code with cache [optional]
--dir value, -d value the target dir
--style value the file naming style, lower|camel|snake, default is lower
--idea for idea plugin [optional]
OPTIONS:
--url value the data source of database,like "root:password@tcp(127.0.0.1:3306)/database
--table value, -t value the table or table globbing patterns in the database
--cache, -c generate code with cache [optional]
--dir value, -d value the target dir
--idea for idea plugin [optional]
```
示例用法请参考[用法](./example/generator.sh)
> NOTE: goctl model mysql ddl/datasource 均新增了一个`--style`参数,用于标记文件命名风格。
目前仅支持redis缓存如果选择带缓存模式即生成的`FindOne(ByXxx)`&`Delete`代码会生成带缓存逻辑的代码目前仅支持单索引字段除全文索引外对于联合索引我们默认认为不需要带缓存且不属于通用型代码因此没有放在代码生成行列如example中user表中的`id`、`name`、`mobile`字段均属于单字段索引。
* 不带缓存模式

View File

@@ -68,30 +68,3 @@ func FieldNames(in interface{}) []string {
}
return out
}
func FieldNamesAlias(in interface{}, alias string) []string {
out := make([]string, 0)
v := reflect.ValueOf(in)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
// we only accept structs
if v.Kind() != reflect.Struct {
panic(fmt.Errorf("ToMap only accepts structs; got %T", v))
}
typ := v.Type()
for i := 0; i < v.NumField(); i++ {
// gets us a StructField
fi := typ.Field(i)
tagName := ""
if tagv := fi.Tag.Get(dbTag); tagv != "" {
tagName = tagv
} else {
tagName = fi.Name
}
if len(alias) > 0 {
tagName = alias + "." + tagName
}
out = append(out, tagName)
}
return out
}

View File

@@ -17,6 +17,8 @@ import (
"github.com/urfave/cli"
)
var errNotMatched = errors.New("sql not matched")
const (
flagSrc = "src"
flagDir = "dir"
@@ -33,6 +35,20 @@ func MysqlDDL(ctx *cli.Context) error {
cache := ctx.Bool(flagCache)
idea := ctx.Bool(flagIdea)
namingStyle := strings.TrimSpace(ctx.String(flagStyle))
return fromDDl(src, dir, namingStyle, cache, idea)
}
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)
namingStyle := strings.TrimSpace(ctx.String(flagStyle))
pattern := strings.TrimSpace(ctx.String(flagTable))
return fromDataSource(url, pattern, dir, namingStyle, cache, idea)
}
func fromDDl(src, dir, namingStyle string, cache, idea bool) error {
log := console.NewConsole(idea)
src = strings.TrimSpace(src)
if len(src) == 0 {
@@ -52,29 +68,29 @@ func MysqlDDL(ctx *cli.Context) error {
return err
}
if len(files) == 0 {
return errNotMatched
}
var source []string
for _, file := range files {
data, err := ioutil.ReadFile(file)
if err != nil {
return err
}
source = append(source, string(data))
}
generator := gen.NewDefaultGenerator(strings.Join(source, "\n"), dir, namingStyle, gen.WithConsoleOption(log))
err = generator.Start(cache)
generator, err := gen.NewDefaultGenerator(dir, namingStyle, gen.WithConsoleOption(log))
if err != nil {
log.Error("%v", err)
return err
}
return nil
err = generator.StartFromDDL(strings.Join(source, "\n"), cache)
return err
}
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)
namingStyle := strings.TrimSpace(ctx.String(flagStyle))
pattern := strings.TrimSpace(ctx.String(flagTable))
func fromDataSource(url, pattern, dir, namingStyle string, cache, idea bool) error {
log := console.NewConsole(idea)
if len(url) == 0 {
log.Error("%v", "expected data source of mysql, but nothing found")
@@ -100,10 +116,8 @@ func MyDataSource(ctx *cli.Context) error {
}
logx.Disable()
conn := sqlx.NewMysql(url)
databaseSource := strings.TrimSuffix(url, "/"+cfg.DBName) + "/information_schema"
db := sqlx.NewMysql(databaseSource)
m := model.NewDDLModel(conn)
im := model.NewInformationSchemaModel(db)
tables, err := im.GetAllTables(cfg.DBName)
@@ -111,7 +125,7 @@ func MyDataSource(ctx *cli.Context) error {
return err
}
var matchTables []string
matchTables := make(map[string][]*model.Column)
for _, item := range tables {
match, err := filepath.Match(pattern, item)
if err != nil {
@@ -121,24 +135,22 @@ func MyDataSource(ctx *cli.Context) error {
if !match {
continue
}
matchTables = append(matchTables, item)
columns, err := im.FindByTableName(cfg.DBName, item)
if err != nil {
return err
}
matchTables[item] = columns
}
if len(matchTables) == 0 {
return errors.New("no tables matched")
}
ddl, err := m.ShowDDL(matchTables...)
generator, err := gen.NewDefaultGenerator(dir, namingStyle, gen.WithConsoleOption(log))
if err != nil {
log.Error("%v", err)
return nil
return err
}
generator := gen.NewDefaultGenerator(strings.Join(ddl, "\n"), dir, namingStyle, gen.WithConsoleOption(log))
err = generator.Start(cache)
if err != nil {
log.Error("%v", err)
}
return nil
err = generator.StartFromInformationSchema(cfg.DBName, matchTables, cache)
return err
}

View File

@@ -0,0 +1,75 @@
package command
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/tools/goctl/model/sql/gen"
"github.com/tal-tech/go-zero/tools/goctl/util"
)
var sql = "-- 用户表 --\nCREATE TABLE `user` (\n `id` bigint(10) NOT NULL AUTO_INCREMENT,\n `name` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户名称',\n `password` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户密码',\n `mobile` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '手机号',\n `gender` char(5) COLLATE utf8mb4_general_ci NOT NULL COMMENT '男|女|未公开',\n `nickname` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '用户昵称',\n `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP,\n `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n PRIMARY KEY (`id`),\n UNIQUE KEY `name_index` (`name`),\n UNIQUE KEY `mobile_index` (`mobile`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;\n\n"
func TestFromDDl(t *testing.T) {
err := fromDDl("./user.sql", t.TempDir(), gen.NamingCamel, true, false)
assert.Equal(t, errNotMatched, err)
// case dir is not exists
unknownDir := filepath.Join(t.TempDir(), "test", "user.sql")
err = fromDDl(unknownDir, t.TempDir(), gen.NamingCamel, true, false)
assert.True(t, func() bool {
switch err.(type) {
case *os.PathError:
return true
default:
return false
}
}())
// case empty src
err = fromDDl("", t.TempDir(), gen.NamingCamel, true, false)
if err != nil {
assert.Equal(t, "expected path or path globbing patterns, but nothing found", err.Error())
}
// case unknown naming style
tmp := filepath.Join(t.TempDir(), "user.sql")
err = fromDDl(tmp, t.TempDir(), "lower1", true, false)
if err != nil {
assert.Equal(t, "unexpected naming style: lower1", err.Error())
}
tempDir := filepath.Join(t.TempDir(), "test")
err = util.MkdirIfNotExist(tempDir)
if err != nil {
return
}
user1Sql := filepath.Join(tempDir, "user1.sql")
user2Sql := filepath.Join(tempDir, "user2.sql")
err = ioutil.WriteFile(user1Sql, []byte(sql), os.ModePerm)
if err != nil {
return
}
err = ioutil.WriteFile(user2Sql, []byte(sql), os.ModePerm)
if err != nil {
return
}
_, err = os.Stat(user1Sql)
assert.Nil(t, err)
_, err = os.Stat(user2Sql)
assert.Nil(t, err)
err = fromDDl(filepath.Join(tempDir, "user*.sql"), tempDir, gen.NamingLower, true, false)
assert.Nil(t, err)
_, err = os.Stat(filepath.Join(tempDir, "usermodel.go"))
assert.Nil(t, err)
}

View File

@@ -1,11 +0,0 @@
#!/bin/bash
# generate model with cache from ddl
goctl model mysql ddl -src="./sql/*.sql" -dir="./sql/model/user" -c
# generate model with cache from data source
#user=root
#password=password
#datasource=127.0.0.1:3306
#database=test
#goctl model mysql datasource -url="${user}:${password}@tcp(${datasource})/${database}" -table="*" -dir ./model

View File

@@ -0,0 +1,15 @@
#!/bin/bash
# generate model with cache from ddl
fromDDL:
goctl model mysql ddl -src="./sql/*.sql" -dir="./sql/model/user" -c
# generate model with cache from data source
user=root
password=password
datasource=127.0.0.1:3306
database=gozero
fromDataSource:
goctl model mysql datasource -url="$(user):$(password)@tcp($(datasource))/$(database)" -table="*" -dir ./model/cache -c -style camel

View File

@@ -42,5 +42,6 @@ func genDelete(table Table, withCache bool) (string, error) {
if err != nil {
return "", err
}
return output.String(), nil
}

View File

@@ -15,6 +15,7 @@ func genFields(fields []parser.Field) (string, error) {
if err != nil {
return "", err
}
list = append(list, result)
}
return strings.Join(list, "\n"), nil
@@ -43,5 +44,6 @@ func genField(field parser.Field) (string, error) {
if err != nil {
return "", err
}
return output.String(), nil
}

View File

@@ -28,5 +28,6 @@ func genFindOne(table Table, withCache bool) (string, error) {
if err != nil {
return "", err
}
return output.String(), nil
}

View File

@@ -7,6 +7,7 @@ import (
"path/filepath"
"strings"
"github.com/tal-tech/go-zero/tools/goctl/model/sql/model"
"github.com/tal-tech/go-zero/tools/goctl/model/sql/parser"
"github.com/tal-tech/go-zero/tools/goctl/model/sql/template"
"github.com/tal-tech/go-zero/tools/goctl/util"
@@ -24,8 +25,8 @@ const (
type (
defaultGenerator struct {
source string
dir string
//source string
dir string
console.Console
pkg string
namingStyle string
@@ -33,18 +34,30 @@ type (
Option func(generator *defaultGenerator)
)
func NewDefaultGenerator(source, dir, namingStyle string, opt ...Option) *defaultGenerator {
func NewDefaultGenerator(dir, namingStyle string, opt ...Option) (*defaultGenerator, error) {
if dir == "" {
dir = pwd
}
generator := &defaultGenerator{source: source, dir: dir, namingStyle: namingStyle}
dirAbs, err := filepath.Abs(dir)
if err != nil {
return nil, err
}
dir = dirAbs
pkg := filepath.Base(dirAbs)
err = util.MkdirIfNotExist(dir)
if err != nil {
return nil, err
}
generator := &defaultGenerator{dir: dir, namingStyle: namingStyle, pkg: pkg}
var optionList []Option
optionList = append(optionList, newDefaultOption())
optionList = append(optionList, opt...)
for _, fn := range optionList {
fn(generator)
}
return generator
return generator, nil
}
func WithConsoleOption(c console.Console) Option {
@@ -59,21 +72,45 @@ func newDefaultOption() Option {
}
}
func (g *defaultGenerator) Start(withCache bool) error {
func (g *defaultGenerator) StartFromDDL(source string, withCache bool) error {
modelList, err := g.genFromDDL(source, withCache)
if err != nil {
return err
}
return g.createFile(modelList)
}
func (g *defaultGenerator) StartFromInformationSchema(db string, columns map[string][]*model.Column, withCache bool) error {
m := make(map[string]string)
for tableName, column := range columns {
table, err := parser.ConvertColumn(db, tableName, column)
if err != nil {
return err
}
code, err := g.genModel(*table, withCache)
if err != nil {
return err
}
m[table.Name.Source()] = code
}
return g.createFile(m)
}
func (g *defaultGenerator) createFile(modelList map[string]string) error {
dirAbs, err := filepath.Abs(g.dir)
if err != nil {
return err
}
g.dir = dirAbs
g.pkg = filepath.Base(dirAbs)
err = util.MkdirIfNotExist(dirAbs)
if err != nil {
return err
}
modelList, err := g.genFromDDL(withCache)
if err != nil {
return err
}
for tableName, code := range modelList {
tn := stringx.From(tableName)
@@ -96,6 +133,9 @@ func (g *defaultGenerator) Start(withCache bool) error {
}
// generate error file
filename := filepath.Join(dirAbs, "vars.go")
if g.namingStyle == NamingCamel {
filename = filepath.Join(dirAbs, "Vars.go")
}
text, err := util.LoadTemplate(category, errTemplateFile, template.Error)
if err != nil {
return err
@@ -113,8 +153,8 @@ func (g *defaultGenerator) Start(withCache bool) error {
}
// ret1: key-table name,value-code
func (g *defaultGenerator) genFromDDL(withCache bool) (map[string]string, error) {
ddlList := g.split()
func (g *defaultGenerator) genFromDDL(source string, withCache bool) (map[string]string, error) {
ddlList := g.split(source)
m := make(map[string]string)
for _, ddl := range ddlList {
table, err := parser.Parse(ddl)
@@ -139,10 +179,15 @@ type (
)
func (g *defaultGenerator) genModel(in parser.Table, withCache bool) (string, error) {
if len(in.PrimaryKey.Name.Source()) == 0 {
return "", fmt.Errorf("table %s: missing primary key", in.Name.Source())
}
text, err := util.LoadTemplate(category, modelTemplateFile, template.Model)
if err != nil {
return "", err
}
t := util.With("model").
Parse(text).
GoFmt(true)

View File

@@ -22,15 +22,19 @@ func TestCacheModel(t *testing.T) {
defer func() {
_ = os.RemoveAll(dir)
}()
g := NewDefaultGenerator(source, cacheDir, NamingLower)
err := g.Start(true)
g, err := NewDefaultGenerator(cacheDir, NamingCamel)
assert.Nil(t, err)
err = g.StartFromDDL(source, true)
assert.Nil(t, err)
assert.True(t, func() bool {
_, err := os.Stat(filepath.Join(cacheDir, "testuserinfomodel.go"))
_, err := os.Stat(filepath.Join(cacheDir, "TestUserInfoModel.go"))
return err == nil
}())
g = NewDefaultGenerator(source, noCacheDir, NamingLower)
err = g.Start(false)
g, err = NewDefaultGenerator(noCacheDir, NamingLower)
assert.Nil(t, err)
err = g.StartFromDDL(source, false)
assert.Nil(t, err)
assert.True(t, func() bool {
_, err := os.Stat(filepath.Join(noCacheDir, "testuserinfomodel.go"))
@@ -47,15 +51,19 @@ func TestNamingModel(t *testing.T) {
defer func() {
_ = os.RemoveAll(dir)
}()
g := NewDefaultGenerator(source, camelDir, NamingCamel)
err := g.Start(true)
g, err := NewDefaultGenerator(camelDir, NamingCamel)
assert.Nil(t, err)
err = g.StartFromDDL(source, true)
assert.Nil(t, err)
assert.True(t, func() bool {
_, err := os.Stat(filepath.Join(camelDir, "TestUserInfoModel.go"))
return err == nil
}())
g = NewDefaultGenerator(source, snakeDir, NamingSnake)
err = g.Start(true)
g, err = NewDefaultGenerator(snakeDir, NamingSnake)
assert.Nil(t, err)
err = g.StartFromDDL(source, true)
assert.Nil(t, err)
assert.True(t, func() bool {
_, err := os.Stat(filepath.Join(snakeDir, "test_user_info_model.go"))

View File

@@ -17,7 +17,6 @@ func TestGenCacheKeys(t *testing.T) {
Name: stringx.From("id"),
DataBaseType: "bigint",
DataType: "int64",
IsKey: false,
IsPrimaryKey: true,
IsUniqueKey: false,
Comment: "自增id",
@@ -29,7 +28,6 @@ func TestGenCacheKeys(t *testing.T) {
Name: stringx.From("mobile"),
DataBaseType: "varchar",
DataType: "string",
IsKey: false,
IsPrimaryKey: false,
IsUniqueKey: true,
Comment: "手机号",
@@ -38,7 +36,6 @@ func TestGenCacheKeys(t *testing.T) {
Name: stringx.From("name"),
DataBaseType: "varchar",
DataType: "string",
IsKey: false,
IsPrimaryKey: false,
IsUniqueKey: true,
Comment: "姓名",
@@ -47,7 +44,6 @@ func TestGenCacheKeys(t *testing.T) {
Name: stringx.From("createTime"),
DataBaseType: "timestamp",
DataType: "time.Time",
IsKey: false,
IsPrimaryKey: false,
IsUniqueKey: false,
Comment: "创建时间",
@@ -56,7 +52,6 @@ func TestGenCacheKeys(t *testing.T) {
Name: stringx.From("updateTime"),
DataBaseType: "timestamp",
DataType: "time.Time",
IsKey: false,
IsPrimaryKey: false,
IsUniqueKey: false,
Comment: "更新时间",

View File

@@ -4,11 +4,10 @@ import (
"regexp"
)
func (g *defaultGenerator) split() []string {
func (g *defaultGenerator) split(source string) []string {
reg := regexp.MustCompile(createTableFlag)
index := reg.FindAllStringIndex(g.source, -1)
index := reg.FindAllStringIndex(source, -1)
list := make([]string, 0)
source := g.source
for i := len(index) - 1; i >= 0; i-- {
subIndex := index[i]
if len(subIndex) == 0 {

View File

@@ -22,5 +22,6 @@ func genTag(in string) (string, error) {
if err != nil {
return "", err
}
return output.String(), nil
}

View File

@@ -27,6 +27,7 @@ func (m *DDLModel) ShowDDL(table ...string) ([]string, error) {
if err != nil {
return nil, err
}
ddl = append(ddl, resp.DDL)
}
return ddl, nil

View File

@@ -8,6 +8,13 @@ type (
InformationSchemaModel struct {
conn sqlx.SqlConn
}
Column struct {
Name string `db:"COLUMN_NAME"`
DataType string `db:"DATA_TYPE"`
Key string `db:"COLUMN_KEY"`
Extra string `db:"EXTRA"`
Comment string `db:"COLUMN_COMMENT"`
}
)
func NewInformationSchemaModel(conn sqlx.SqlConn) *InformationSchemaModel {
@@ -21,5 +28,13 @@ func (m *InformationSchemaModel) GetAllTables(database string) ([]string, error)
if err != nil {
return nil, err
}
return tables, nil
}
func (m *InformationSchemaModel) FindByTableName(db, table string) ([]*Column, error) {
querySql := `select COLUMN_NAME,DATA_TYPE,COLUMN_KEY,EXTRA,COLUMN_COMMENT from COLUMNS where TABLE_SCHEMA = ? and TABLE_NAME = ?`
var reply []*Column
err := m.conn.QueryRows(&reply, querySql, db, table)
return reply, err
}

View File

@@ -2,8 +2,10 @@ package parser
import (
"fmt"
"strings"
"github.com/tal-tech/go-zero/tools/goctl/model/sql/converter"
"github.com/tal-tech/go-zero/tools/goctl/model/sql/model"
"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
"github.com/xwb1989/sqlparser"
)
@@ -34,7 +36,6 @@ type (
Name stringx.String
DataBaseType string
DataType string
IsKey bool
IsPrimaryKey bool
IsUniqueKey bool
Comment string
@@ -123,7 +124,6 @@ func Parse(ddl string) (*Table, error) {
field.Comment = comment
key, ok := keyMap[column.Name.String()]
if ok {
field.IsKey = true
field.IsPrimaryKey = key == primary
field.IsUniqueKey = key == unique
if field.IsPrimaryKey {
@@ -151,3 +151,62 @@ func (t *Table) ContainsTime() bool {
}
return false
}
func ConvertColumn(db, table string, in []*model.Column) (*Table, error) {
var reply Table
reply.Name = stringx.From(table)
keyMap := make(map[string][]*model.Column)
for _, column := range in {
keyMap[column.Key] = append(keyMap[column.Key], column)
}
primaryColumns := keyMap["PRI"]
if len(primaryColumns) == 0 {
return nil, fmt.Errorf("database:%s, table %s: missing primary key", db, table)
}
if len(primaryColumns) > 1 {
return nil, fmt.Errorf("database:%s, table %s: only one primary key expected", db, table)
}
primaryColumn := primaryColumns[0]
primaryFt, err := converter.ConvertDataType(primaryColumn.DataType)
if err != nil {
return nil, err
}
primaryField := Field{
Name: stringx.From(primaryColumn.Name),
DataBaseType: primaryColumn.DataType,
DataType: primaryFt,
IsUniqueKey: true,
IsPrimaryKey: true,
Comment: primaryColumn.Comment,
}
reply.PrimaryKey = Primary{
Field: primaryField,
AutoIncrement: strings.Contains(primaryColumn.Extra, "auto_increment"),
}
for key, columns := range keyMap {
for _, item := range columns {
dt, err := converter.ConvertDataType(item.DataType)
if err != nil {
return nil, err
}
f := Field{
Name: stringx.From(item.Name),
DataBaseType: item.DataType,
DataType: dt,
IsPrimaryKey: primaryColumn.Name == item.Name,
Comment: item.Comment,
}
if key == "UNI" {
f.IsUniqueKey = true
}
reply.Fields = append(reply.Fields, f)
}
}
return &reply, nil
}

View File

@@ -4,6 +4,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/tools/goctl/model/sql/model"
)
func TestParsePlainText(t *testing.T) {
@@ -23,3 +24,58 @@ func TestParseCreateTable(t *testing.T) {
assert.Equal(t, "id", table.PrimaryKey.Name.Source())
assert.Equal(t, true, table.ContainsTime())
}
func TestConvertColumn(t *testing.T) {
_, err := ConvertColumn("user", "user", []*model.Column{
{
Name: "id",
DataType: "bigint",
Key: "",
Extra: "",
Comment: "",
},
})
assert.NotNil(t, err)
assert.Contains(t, err.Error(), "missing primary key")
_, err = ConvertColumn("user", "user", []*model.Column{
{
Name: "id",
DataType: "bigint",
Key: "PRI",
Extra: "",
Comment: "",
},
{
Name: "mobile",
DataType: "varchar",
Key: "PRI",
Extra: "",
Comment: "手机号",
},
})
assert.NotNil(t, err)
assert.Contains(t, err.Error(), "only one primary key expected")
table, err := ConvertColumn("user", "user", []*model.Column{
{
Name: "id",
DataType: "bigint",
Key: "PRI",
Extra: "auto_increment",
Comment: "",
},
{
Name: "mobile",
DataType: "varchar",
Key: "UNI",
Extra: "",
Comment: "手机号",
},
})
assert.Nil(t, err)
assert.True(t, table.PrimaryKey.AutoIncrement && table.PrimaryKey.IsPrimaryKey)
assert.Equal(t, "id", table.PrimaryKey.Name.Source())
assert.Equal(t, "mobile", table.Fields[1].Name.Source())
assert.True(t, table.Fields[1].IsUniqueKey)
}

View File

@@ -26,26 +26,49 @@ Goctl Rpc是`goctl`脚手架下的一个rpc服务代码生成模块支持prot
```golang
.
├── etc // 配置文件
   └── greet.yaml
├── etc // yaml配置文件
└── greet.yaml
├── go.mod
├── greet // client call
   └── greet.go
├── greet.go // main entry
├── greet.proto
── internal
── config // 配置声明
│   └── config.go
├── greet // pb.go
   └── greet.pb.go
├── logic // logic
   └── pinglogic.go
├── server // pb invoker
   └── greetserver.go
└── svc // resource dependency
├── greet // pb.go文件夹①
└── greet.pb.go
├── greet.go // main函数
├── greet.proto // proto 文件
── greetclient // call logic ②
── greet.go
└── internal
├── config // yaml配置对应的实体
└── config.go
├── logic // 业务代码
└── pinglogic.go
├── server // rpc server
└── greetserver.go
└── svc // 依赖资源
└── servicecontext.go
```
> ① pb文件夹名老版本文件夹固定为pb称取自于proto文件中option go_package的值最后一层级按照一定格式进行转换若无此声明则取自于package的值大致代码如下
```go
if option.Name == "go_package" {
ret.GoPackage = option.Constant.Source
}
...
if len(ret.GoPackage) == 0 {
ret.GoPackage = ret.Package.Name
}
ret.PbPackage = GoSanitized(filepath.Base(ret.GoPackage))
...
```
> GoSanitized方法请参考google.golang.org/protobuf@v1.25.0/internal/strs/strings.go:71
> ② call 层文件夹名称取自于proto中service的名称如该sercice的名称和pb文件夹名称相等则会在srervice后面补充client进行区分使pb和call分隔。
```go
if strings.ToLower(proto.Service.Name) == strings.ToLower(proto.GoPackage) {
callDir = filepath.Join(ctx.WorkDir, strings.ToLower(stringx.From(proto.Service.Name+"_client").ToCamel()))
}
```
rpc一键生成常见问题解决见 <a href="#常见问题解决">常见问题解决</a>
### 方式二通过指定proto生成rpc服务
@@ -110,8 +133,8 @@ USAGE:
OPTIONS:
--src value, -s value the file path of the proto source file
--proto_path value, -I value native command of protoc,specify the directory in which to search for imports. [optional]
--dir value, -d value the target path of the code,default path is "${pwd}". [optional]
--proto_path value, -I value native command of protoc, specify the directory in which to search for imports. [optional]
--dir value, -d value the target path of the code
--idea whether the command execution environment is from idea plugin. [optional]
```
@@ -134,10 +157,10 @@ OPTIONS:
### 注意事项
* `google.golang.org/grpc`需要降级到v1.26.0,且protoc-gen-go版本不能高于v1.3.2see [https://github.com/grpc/grpc-go/issues/3347](https://github.com/grpc/grpc-go/issues/3347))即
* `google.golang.org/grpc`需要降级到 `v1.29.1`且protoc-gen-go版本不能高于v1.3.2see [https://github.com/grpc/grpc-go/issues/3347](https://github.com/grpc/grpc-go/issues/3347))即
```shell script
replace google.golang.org/grpc => google.golang.org/grpc v1.26.0
replace google.golang.org/grpc => google.golang.org/grpc v1.29.1
```
* proto不支持暂多文件同时生成
@@ -238,5 +261,5 @@ service Greet {
解决方法:
```golang
replace google.golang.org/grpc => google.golang.org/grpc v1.26.0
replace google.golang.org/grpc => google.golang.org/grpc v1.29.1
```

View File

@@ -5,7 +5,6 @@ import (
"fmt"
"path/filepath"
"github.com/tal-tech/go-zero/tools/goctl/rpc/execx"
"github.com/tal-tech/go-zero/tools/goctl/rpc/generator"
"github.com/urfave/cli"
)
@@ -16,6 +15,7 @@ import (
func Rpc(c *cli.Context) error {
src := c.String("src")
out := c.String("dir")
style := c.String("style")
protoImportPath := c.StringSlice("proto_path")
if len(src) == 0 {
return errors.New("missing -src")
@@ -23,7 +23,13 @@ func Rpc(c *cli.Context) error {
if len(out) == 0 {
return errors.New("missing -dir")
}
g := generator.NewDefaultRpcGenerator()
namingStyle, valid := generator.IsNamingValid(style)
if !valid {
return fmt.Errorf("unexpected naming style %s", style)
}
g := generator.NewDefaultRpcGenerator(namingStyle)
return g.Generate(src, out, protoImportPath)
}
@@ -36,6 +42,12 @@ func RpcNew(c *cli.Context) error {
return fmt.Errorf("unexpected ext: %s", ext)
}
style := c.String("style")
namingStyle, valid := generator.IsNamingValid(style)
if !valid {
return fmt.Errorf("expected naming style [lower|camel|snake], but found %s", style)
}
protoName := name + ".proto"
filename := filepath.Join(".", name, protoName)
src, err := filepath.Abs(filename)
@@ -48,20 +60,15 @@ func RpcNew(c *cli.Context) error {
return err
}
workDir := filepath.Dir(src)
_, err = execx.Run("go mod init "+name, workDir)
if err != nil {
return err
}
g := generator.NewDefaultRpcGenerator()
g := generator.NewDefaultRpcGenerator(namingStyle)
return g.Generate(src, filepath.Dir(src), nil)
}
func RpcTemplate(c *cli.Context) error {
name := c.Args().First()
if len(name) == 0 {
name = "greet.proto"
protoFile := c.String("o")
if len(protoFile) == 0 {
return errors.New("missing -o")
}
return generator.ProtoTmpl(name)
return generator.ProtoTmpl(protoFile)
}

View File

@@ -1,75 +0,0 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: common.proto
package common
import (
fmt "fmt"
proto "github.com/golang/protobuf/proto"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
type User struct {
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *User) Reset() { *m = User{} }
func (m *User) String() string { return proto.CompactTextString(m) }
func (*User) ProtoMessage() {}
func (*User) Descriptor() ([]byte, []int) {
return fileDescriptor_555bd8c177793206, []int{0}
}
func (m *User) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_User.Unmarshal(m, b)
}
func (m *User) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_User.Marshal(b, m, deterministic)
}
func (m *User) XXX_Merge(src proto.Message) {
xxx_messageInfo_User.Merge(m, src)
}
func (m *User) XXX_Size() int {
return xxx_messageInfo_User.Size(m)
}
func (m *User) XXX_DiscardUnknown() {
xxx_messageInfo_User.DiscardUnknown(m)
}
var xxx_messageInfo_User proto.InternalMessageInfo
func (m *User) GetName() string {
if m != nil {
return m.Name
}
return ""
}
func init() {
proto.RegisterType((*User)(nil), "common.User")
}
func init() { proto.RegisterFile("common.proto", fileDescriptor_555bd8c177793206) }
var fileDescriptor_555bd8c177793206 = []byte{
// 72 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x49, 0xce, 0xcf, 0xcd,
0xcd, 0xcf, 0xd3, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x83, 0xf0, 0x94, 0xa4, 0xb8, 0x58,
0x42, 0x8b, 0x53, 0x8b, 0x84, 0x84, 0xb8, 0x58, 0xf2, 0x12, 0x73, 0x53, 0x25, 0x18, 0x15, 0x18,
0x35, 0x38, 0x83, 0xc0, 0xec, 0x24, 0x36, 0xb0, 0x52, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff,
0x2c, 0x6d, 0x58, 0x59, 0x3a, 0x00, 0x00, 0x00,
}

View File

@@ -6,6 +6,13 @@ import (
"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
)
func formatFilename(filename string) string {
return strings.ToLower(stringx.From(filename).ToCamel())
func formatFilename(filename string, style NamingStyle) string {
switch style {
case namingCamel:
return stringx.From(filename).ToCamel()
case namingSnake:
return stringx.From(filename).ToSnake()
default:
return strings.ToLower(stringx.From(filename).ToCamel())
}
}

View File

@@ -0,0 +1,17 @@
package generator
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestFormatFilename(t *testing.T) {
assert.Equal(t, "abc", formatFilename("a_b_c", namingLower))
assert.Equal(t, "ABC", formatFilename("a_b_c", namingCamel))
assert.Equal(t, "a_b_c", formatFilename("a_b_c", namingSnake))
assert.Equal(t, "a", formatFilename("a", namingSnake))
assert.Equal(t, "A", formatFilename("a", namingCamel))
// no flag to convert to snake
assert.Equal(t, "abc", formatFilename("abc", namingSnake))
}

View File

@@ -10,16 +10,18 @@ import (
)
type RpcGenerator struct {
g Generator
g Generator
style NamingStyle
}
func NewDefaultRpcGenerator() *RpcGenerator {
return NewRpcGenerator(NewDefaultGenerator())
func NewDefaultRpcGenerator(style NamingStyle) *RpcGenerator {
return NewRpcGenerator(NewDefaultGenerator(), style)
}
func NewRpcGenerator(g Generator) *RpcGenerator {
func NewRpcGenerator(g Generator, style NamingStyle) *RpcGenerator {
return &RpcGenerator{
g: g,
g: g,
style: style,
}
}
@@ -55,42 +57,42 @@ func (g *RpcGenerator) Generate(src, target string, protoImportPath []string) er
return err
}
err = g.g.GenEtc(dirCtx, proto)
err = g.g.GenEtc(dirCtx, proto, g.style)
if err != nil {
return err
}
err = g.g.GenPb(dirCtx, protoImportPath, proto)
err = g.g.GenPb(dirCtx, protoImportPath, proto, g.style)
if err != nil {
return err
}
err = g.g.GenConfig(dirCtx, proto)
err = g.g.GenConfig(dirCtx, proto, g.style)
if err != nil {
return err
}
err = g.g.GenSvc(dirCtx, proto)
err = g.g.GenSvc(dirCtx, proto, g.style)
if err != nil {
return err
}
err = g.g.GenLogic(dirCtx, proto)
err = g.g.GenLogic(dirCtx, proto, g.style)
if err != nil {
return err
}
err = g.g.GenServer(dirCtx, proto)
err = g.g.GenServer(dirCtx, proto, g.style)
if err != nil {
return err
}
err = g.g.GenMain(dirCtx, proto)
err = g.g.GenMain(dirCtx, proto, g.style)
if err != nil {
return err
}
err = g.g.GenCall(dirCtx, proto)
err = g.g.GenCall(dirCtx, proto, g.style)
console.NewColorConsole().MarkDone()

View File

@@ -1,128 +1,74 @@
package generator
import (
"go/build"
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/logx"
"github.com/tal-tech/go-zero/core/stringx"
"github.com/tal-tech/go-zero/tools/goctl/rpc/execx"
)
func TestRpcGenerateCaseNilImport(t *testing.T) {
func TestRpcGenerate(t *testing.T) {
_ = Clean()
dispatcher := NewDefaultGenerator()
if err := dispatcher.Prepare(); err == nil {
g := NewRpcGenerator(dispatcher)
abs, err := filepath.Abs("./test")
assert.Nil(t, err)
err = g.Generate("./test_stream.proto", abs, nil)
defer func() {
_ = os.RemoveAll(abs)
}()
assert.Nil(t, err)
_, err = execx.Run("go test "+abs, abs)
assert.Nil(t, err)
err := dispatcher.Prepare()
if err != nil {
logx.Error(err)
return
}
}
projectName := stringx.Rand()
g := NewRpcGenerator(dispatcher, namingLower)
func TestRpcGenerateCaseOption(t *testing.T) {
_ = Clean()
dispatcher := NewDefaultGenerator()
if err := dispatcher.Prepare(); err == nil {
g := NewRpcGenerator(dispatcher)
abs, err := filepath.Abs("./test")
assert.Nil(t, err)
err = g.Generate("./test_option.proto", abs, nil)
defer func() {
_ = os.RemoveAll(abs)
}()
assert.Nil(t, err)
_, err = execx.Run("go test "+abs, abs)
assert.Nil(t, err)
// case go path
src := filepath.Join(build.Default.GOPATH, "src")
_, err = os.Stat(src)
if err != nil {
return
}
}
func TestRpcGenerateCaseWordOption(t *testing.T) {
_ = Clean()
dispatcher := NewDefaultGenerator()
if err := dispatcher.Prepare(); err == nil {
g := NewRpcGenerator(dispatcher)
abs, err := filepath.Abs("./test")
assert.Nil(t, err)
err = g.Generate("./test_word_option.proto", abs, nil)
defer func() {
_ = os.RemoveAll(abs)
}()
assert.Nil(t, err)
_, err = execx.Run("go test "+abs, abs)
assert.Nil(t, err)
projectDir := filepath.Join(src, projectName)
srcDir := projectDir
defer func() {
_ = os.RemoveAll(srcDir)
}()
err = g.Generate("./test.proto", projectDir, []string{src})
assert.Nil(t, err)
_, err = execx.Run("go test "+projectName, projectDir)
if err != nil {
assert.Contains(t, err.Error(), "not in GOROOT")
}
}
// test keyword go
func TestRpcGenerateCaseGoOption(t *testing.T) {
_ = Clean()
dispatcher := NewDefaultGenerator()
if err := dispatcher.Prepare(); err == nil {
g := NewRpcGenerator(dispatcher)
abs, err := filepath.Abs("./test")
assert.Nil(t, err)
err = g.Generate("./test_go_option.proto", abs, nil)
defer func() {
_ = os.RemoveAll(abs)
}()
assert.Nil(t, err)
_, err = execx.Run("go test "+abs, abs)
assert.Nil(t, err)
// case go mod
workDir := t.TempDir()
name := filepath.Base(workDir)
_, err = execx.Run("go mod init "+name, workDir)
if err != nil {
logx.Error(err)
return
}
}
func TestRpcGenerateCaseImport(t *testing.T) {
_ = Clean()
dispatcher := NewDefaultGenerator()
if err := dispatcher.Prepare(); err == nil {
g := NewRpcGenerator(dispatcher)
abs, err := filepath.Abs("./test")
assert.Nil(t, err)
err = g.Generate("./test_import.proto", abs, []string{"./base"})
defer func() {
_ = os.RemoveAll(abs)
}()
assert.Nil(t, err)
_, err = execx.Run("go test "+abs, abs)
assert.True(t, func() bool {
return strings.Contains(err.Error(), "package base is not in GOROOT")
}())
projectDir = filepath.Join(workDir, projectName)
err = g.Generate("./test.proto", projectDir, []string{src})
assert.Nil(t, err)
_, err = execx.Run("go test "+projectName, projectDir)
if err != nil {
assert.Contains(t, err.Error(), "not in GOROOT")
}
}
func TestRpcGenerateCaseServiceRpcNamingSnake(t *testing.T) {
_ = Clean()
dispatcher := NewDefaultGenerator()
if err := dispatcher.Prepare(); err == nil {
g := NewRpcGenerator(dispatcher)
abs, err := filepath.Abs("./test")
assert.Nil(t, err)
err = g.Generate("./test_service_rpc_naming_snake.proto", abs, nil)
defer func() {
_ = os.RemoveAll(abs)
}()
assert.Nil(t, err)
_, err = execx.Run("go test "+abs, abs)
assert.Nil(t, err)
// case not in go mod and go path
err = g.Generate("./test.proto", projectDir, []string{src})
assert.Nil(t, err)
_, err = execx.Run("go test "+projectName, projectDir)
if err != nil {
assert.Contains(t, err.Error(), "not in GOROOT")
}
// invalid directory
projectDir = filepath.Join(t.TempDir(), ".....")
err = g.Generate("./test.proto", projectDir, nil)
assert.NotNil(t, err)
}

View File

@@ -59,12 +59,12 @@ func (m *default{{.serviceName}}) {{.method}}(ctx context.Context,in *{{.pbReque
`
)
func (g *defaultGenerator) GenCall(ctx DirContext, proto parser.Proto) error {
func (g *defaultGenerator) GenCall(ctx DirContext, proto parser.Proto, namingStyle NamingStyle) error {
dir := ctx.GetCall()
service := proto.Service
head := util.GetHead(proto.Name)
filename := filepath.Join(dir.Filename, fmt.Sprintf("%s.go", formatFilename(service.Name)))
filename := filepath.Join(dir.Filename, fmt.Sprintf("%s.go", formatFilename(service.Name, namingStyle)))
functions, err := g.genFunction(proto.PbPackage, service)
if err != nil {
return err
@@ -81,13 +81,12 @@ func (g *defaultGenerator) GenCall(ctx DirContext, proto parser.Proto) error {
}
var alias = collection.NewSet()
for _, item := range service.RPC {
alias.AddStr(fmt.Sprintf("%s = %s", parser.CamelCase(item.RequestType), fmt.Sprintf("%s.%s", proto.PbPackage, parser.CamelCase(item.RequestType))))
alias.AddStr(fmt.Sprintf("%s = %s", parser.CamelCase(item.ReturnsType), fmt.Sprintf("%s.%s", proto.PbPackage, parser.CamelCase(item.ReturnsType))))
for _, item := range proto.Message {
alias.AddStr(fmt.Sprintf("%s = %s", parser.CamelCase(item.Name), fmt.Sprintf("%s.%s", proto.PbPackage, parser.CamelCase(item.Name))))
}
err = util.With("shared").GoFmt(true).Parse(text).SaveTo(map[string]interface{}{
"name": formatFilename(service.Name),
"name": formatFilename(service.Name, namingStyle),
"alias": strings.Join(alias.KeysStr(), util.NL),
"head": head,
"filePackage": dir.Base,

View File

@@ -1,44 +0,0 @@
package generator
import (
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/tools/goctl/rpc/parser"
"github.com/tal-tech/go-zero/tools/goctl/util"
"github.com/tal-tech/go-zero/tools/goctl/util/ctx"
)
func TestGenerateCall(t *testing.T) {
_ = Clean()
project := "stream"
abs, err := filepath.Abs("./test")
assert.Nil(t, err)
dir := filepath.Join(abs, project)
err = util.MkdirIfNotExist(dir)
assert.Nil(t, err)
defer func() {
_ = os.RemoveAll(abs)
}()
projectCtx, err := ctx.Prepare(dir)
assert.Nil(t, err)
p := parser.NewDefaultProtoParser()
proto, err := p.Parse("./test_stream.proto")
assert.Nil(t, err)
dirCtx, err := mkdir(projectCtx, proto)
assert.Nil(t, err)
g := NewDefaultGenerator()
err = g.Prepare()
if err != nil {
return
}
err = g.GenCall(dirCtx, proto)
assert.Nil(t, err)
}

View File

@@ -18,9 +18,9 @@ type Config struct {
}
`
func (g *defaultGenerator) GenConfig(ctx DirContext, _ parser.Proto) error {
func (g *defaultGenerator) GenConfig(ctx DirContext, _ parser.Proto, namingStyle NamingStyle) error {
dir := ctx.GetConfig()
fileName := filepath.Join(dir.Filename, formatFilename("config")+".go")
fileName := filepath.Join(dir.Filename, formatFilename("config", namingStyle)+".go")
if util.FileExists(fileName) {
return nil
}

View File

@@ -1,48 +0,0 @@
package generator
import (
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/tools/goctl/rpc/parser"
"github.com/tal-tech/go-zero/tools/goctl/util"
"github.com/tal-tech/go-zero/tools/goctl/util/ctx"
)
func TestGenerateConfig(t *testing.T) {
_ = Clean()
project := "stream"
abs, err := filepath.Abs("./test")
assert.Nil(t, err)
dir := filepath.Join(abs, project)
err = util.MkdirIfNotExist(dir)
assert.Nil(t, err)
defer func() {
_ = os.RemoveAll(abs)
}()
projectCtx, err := ctx.Prepare(dir)
assert.Nil(t, err)
p := parser.NewDefaultProtoParser()
proto, err := p.Parse("./test_stream.proto")
assert.Nil(t, err)
dirCtx, err := mkdir(projectCtx, proto)
assert.Nil(t, err)
g := NewDefaultGenerator()
err = g.Prepare()
if err != nil {
return
}
err = g.GenConfig(dirCtx, proto)
assert.Nil(t, err)
// test file exists
err = g.GenConfig(dirCtx, proto)
assert.Nil(t, err)
}

View File

@@ -4,12 +4,12 @@ import "github.com/tal-tech/go-zero/tools/goctl/rpc/parser"
type Generator interface {
Prepare() error
GenMain(ctx DirContext, proto parser.Proto) error
GenCall(ctx DirContext, proto parser.Proto) error
GenEtc(ctx DirContext, proto parser.Proto) error
GenConfig(ctx DirContext, proto parser.Proto) error
GenLogic(ctx DirContext, proto parser.Proto) error
GenServer(ctx DirContext, proto parser.Proto) error
GenSvc(ctx DirContext, proto parser.Proto) error
GenPb(ctx DirContext, protoImportPath []string, proto parser.Proto) error
GenMain(ctx DirContext, proto parser.Proto, namingStyle NamingStyle) error
GenCall(ctx DirContext, proto parser.Proto, namingStyle NamingStyle) error
GenEtc(ctx DirContext, proto parser.Proto, namingStyle NamingStyle) error
GenConfig(ctx DirContext, proto parser.Proto, namingStyle NamingStyle) error
GenLogic(ctx DirContext, proto parser.Proto, namingStyle NamingStyle) error
GenServer(ctx DirContext, proto parser.Proto, namingStyle NamingStyle) error
GenSvc(ctx DirContext, proto parser.Proto, namingStyle NamingStyle) error
GenPb(ctx DirContext, protoImportPath []string, proto parser.Proto, namingStyle NamingStyle) error
}

View File

@@ -3,9 +3,11 @@ package generator
import (
"fmt"
"path/filepath"
"strings"
"github.com/tal-tech/go-zero/tools/goctl/rpc/parser"
"github.com/tal-tech/go-zero/tools/goctl/util"
"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
)
const etcTemplate = `Name: {{.serviceName}}.rpc
@@ -16,9 +18,9 @@ Etcd:
Key: {{.serviceName}}.rpc
`
func (g *defaultGenerator) GenEtc(ctx DirContext, _ parser.Proto) error {
func (g *defaultGenerator) GenEtc(ctx DirContext, _ parser.Proto, namingStyle NamingStyle) error {
dir := ctx.GetEtc()
serviceNameLower := formatFilename(ctx.GetMain().Base)
serviceNameLower := formatFilename(ctx.GetMain().Base, namingStyle)
fileName := filepath.Join(dir.Filename, fmt.Sprintf("%v.yaml", serviceNameLower))
text, err := util.LoadTemplate(category, etcTemplateFileFile, etcTemplate)
@@ -27,6 +29,6 @@ func (g *defaultGenerator) GenEtc(ctx DirContext, _ parser.Proto) error {
}
return util.With("etc").Parse(text).SaveTo(map[string]interface{}{
"serviceName": serviceNameLower,
"serviceName": strings.ToLower(stringx.From(ctx.GetMain().Base).ToCamel()),
}, fileName, false)
}

View File

@@ -1,45 +0,0 @@
package generator
import (
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/tools/goctl/rpc/parser"
"github.com/tal-tech/go-zero/tools/goctl/util"
"github.com/tal-tech/go-zero/tools/goctl/util/ctx"
)
func TestGenerateEtc(t *testing.T) {
_ = Clean()
project := "stream"
abs, err := filepath.Abs("./test")
assert.Nil(t, err)
dir := filepath.Join(abs, project)
err = util.MkdirIfNotExist(dir)
assert.Nil(t, err)
defer func() {
_ = os.RemoveAll(abs)
}()
projectCtx, err := ctx.Prepare(dir)
assert.Nil(t, err)
p := parser.NewDefaultProtoParser()
proto, err := p.Parse("./test_stream.proto")
assert.Nil(t, err)
dirCtx, err := mkdir(projectCtx, proto)
assert.Nil(t, err)
g := NewDefaultGenerator()
err = g.Prepare()
if err != nil {
return
}
err = g.GenEtc(dirCtx, proto)
assert.Nil(t, err)
}

View File

@@ -46,10 +46,10 @@ func (l *{{.logicName}}) {{.method}} (in {{.request}}) ({{.response}}, error) {
`
)
func (g *defaultGenerator) GenLogic(ctx DirContext, proto parser.Proto) error {
func (g *defaultGenerator) GenLogic(ctx DirContext, proto parser.Proto, namingStyle NamingStyle) error {
dir := ctx.GetLogic()
for _, rpc := range proto.Service.RPC {
filename := filepath.Join(dir.Filename, formatFilename(rpc.Name+"_logic")+".go")
filename := filepath.Join(dir.Filename, formatFilename(rpc.Name+"_logic", namingStyle)+".go")
functions, err := g.genLogicFunction(proto.PbPackage, rpc)
if err != nil {
return err

View File

@@ -1,44 +0,0 @@
package generator
import (
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/tools/goctl/rpc/parser"
"github.com/tal-tech/go-zero/tools/goctl/util"
"github.com/tal-tech/go-zero/tools/goctl/util/ctx"
)
func TestGenerateLogic(t *testing.T) {
project := "stream"
abs, err := filepath.Abs("./test")
assert.Nil(t, err)
dir := filepath.Join(abs, project)
err = util.MkdirIfNotExist(dir)
assert.Nil(t, err)
defer func() {
_ = os.RemoveAll(abs)
}()
projectCtx, err := ctx.Prepare(dir)
assert.Nil(t, err)
p := parser.NewDefaultProtoParser()
proto, err := p.Parse("./test_stream.proto")
assert.Nil(t, err)
dirCtx, err := mkdir(projectCtx, proto)
assert.Nil(t, err)
g := NewDefaultGenerator()
err = g.Prepare()
if err != nil {
return
}
err = g.GenLogic(dirCtx, proto)
assert.Nil(t, err)
}

View File

@@ -45,9 +45,9 @@ func main() {
}
`
func (g *defaultGenerator) GenMain(ctx DirContext, proto parser.Proto) error {
func (g *defaultGenerator) GenMain(ctx DirContext, proto parser.Proto, namingStyle NamingStyle) error {
dir := ctx.GetMain()
serviceNameLower := formatFilename(ctx.GetMain().Base)
serviceNameLower := formatFilename(ctx.GetMain().Base, namingStyle)
fileName := filepath.Join(dir.Filename, fmt.Sprintf("%v.go", serviceNameLower))
imports := make([]string, 0)
pbImport := fmt.Sprintf(`"%v"`, ctx.GetPb().Package)
@@ -63,7 +63,7 @@ func (g *defaultGenerator) GenMain(ctx DirContext, proto parser.Proto) error {
return util.With("main").GoFmt(true).Parse(text).SaveTo(map[string]interface{}{
"head": head,
"serviceName": serviceNameLower,
"serviceName": strings.ToLower(stringx.From(ctx.GetMain().Base).ToCamel()),
"imports": strings.Join(imports, util.NL),
"pkg": proto.PbPackage,
"serviceNew": stringx.From(proto.Service.Name).ToCamel(),

View File

@@ -1,45 +0,0 @@
package generator
import (
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/tools/goctl/rpc/parser"
"github.com/tal-tech/go-zero/tools/goctl/util"
"github.com/tal-tech/go-zero/tools/goctl/util/ctx"
)
func TestGenerateMain(t *testing.T) {
_ = Clean()
project := "stream"
abs, err := filepath.Abs("./test")
assert.Nil(t, err)
dir := filepath.Join(abs, project)
err = util.MkdirIfNotExist(dir)
assert.Nil(t, err)
defer func() {
_ = os.RemoveAll(abs)
}()
projectCtx, err := ctx.Prepare(dir)
assert.Nil(t, err)
p := parser.NewDefaultProtoParser()
proto, err := p.Parse("./test_stream.proto")
assert.Nil(t, err)
dirCtx, err := mkdir(projectCtx, proto)
assert.Nil(t, err)
g := NewDefaultGenerator()
err = g.Prepare()
if err != nil {
return
}
err = g.GenMain(dirCtx, proto)
assert.Nil(t, err)
}

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