Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5c560e8ba | ||
|
|
bed494d904 | ||
|
|
2dfecda465 | ||
|
|
3ebb1e0221 | ||
|
|
348184904c | ||
|
|
7a27fa50a1 | ||
|
|
8d4951c990 | ||
|
|
6e57f6c527 | ||
|
|
b9ac51b6c3 | ||
|
|
702e8d79ce | ||
|
|
95a9dabf8b | ||
|
|
bae66c49c2 | ||
|
|
e0afe0b4bb | ||
|
|
24fb29a356 | ||
|
|
71083b5e64 | ||
|
|
1174f17bd9 | ||
|
|
d6d8fc21d8 | ||
|
|
9592639cb4 | ||
|
|
abcb28e506 | ||
|
|
a92f65580c | ||
|
|
3819f67cf4 | ||
|
|
295c8d2934 | ||
|
|
88da8685dd | ||
|
|
c7831ac96d | ||
|
|
e898761762 | ||
|
|
13d1c5cd00 | ||
|
|
16bfb1b7be | ||
|
|
ef4d4968d6 | ||
|
|
7b4a5e3ec6 | ||
|
|
e6df21e0d2 | ||
|
|
0a2c2d1eca | ||
|
|
a5fb29a6f0 | ||
|
|
f8da301e57 | ||
|
|
cb9075b737 | ||
|
|
3f389a55c2 | ||
|
|
afbd565d87 | ||
|
|
d629acc2b7 | ||
|
|
f32c6a9b28 |
@@ -76,7 +76,6 @@ func (rw *RollingWindow) updateOffset() {
|
||||
span := rw.span()
|
||||
if span > 0 {
|
||||
offset := rw.offset
|
||||
// reset expired buckets
|
||||
start := offset + 1
|
||||
steps := start + span
|
||||
var remainder int
|
||||
@@ -84,15 +83,16 @@ func (rw *RollingWindow) updateOffset() {
|
||||
remainder = steps - rw.size
|
||||
steps = rw.size
|
||||
}
|
||||
|
||||
// reset expired buckets
|
||||
for i := start; i < steps; i++ {
|
||||
rw.win.resetBucket(i)
|
||||
offset = i
|
||||
}
|
||||
for i := 0; i < remainder; i++ {
|
||||
rw.win.resetBucket(i)
|
||||
offset = i
|
||||
}
|
||||
rw.offset = offset
|
||||
|
||||
rw.offset = (offset + span) % rw.size
|
||||
rw.lastTime = timex.Now()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
2
core/stores/cache/cachenode_test.go
vendored
2
core/stores/cache/cachenode_test.go
vendored
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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...)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ func main() {
|
||||
var allowed, denied int32
|
||||
var wait sync.WaitGroup
|
||||
for i := 0; i < *threads; i++ {
|
||||
i := i
|
||||
wait.Add(1)
|
||||
go func() {
|
||||
for {
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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=
|
||||
|
||||
11
go.mod
11
go.mod
@@ -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.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
|
||||
)
|
||||
|
||||
24
go.sum
24
go.sum
@@ -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,6 @@ 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=
|
||||
@@ -419,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=
|
||||
@@ -448,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=
|
||||
|
||||
@@ -174,7 +174,7 @@ goctl api -o greet.api
|
||||
you can check it by curl:
|
||||
|
||||
```shell
|
||||
curl -i http://localhost:8888/greet/from/you
|
||||
curl -i http://localhost:8888/from/you
|
||||
```
|
||||
|
||||
the response looks like:
|
||||
|
||||
12
readme.md
12
readme.md
@@ -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 框架,有
|
||||
|
||||
如下图,我们从多个层面保障了整体服务的高可用:
|
||||
|
||||

|
||||

|
||||
|
||||
觉得不错的话,别忘 **star** 👏
|
||||
|
||||
@@ -120,7 +120,7 @@ GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/tal-tech/
|
||||
默认侦听在 8888 端口(可以在配置文件里修改),可以通过 curl 请求:
|
||||
|
||||
```shell
|
||||
curl -i http://localhost:8888/greet/from/you
|
||||
curl -i http://localhost:8888/from/you
|
||||
```
|
||||
|
||||
返回如下:
|
||||
@@ -150,7 +150,7 @@ GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/tal-tech/
|
||||
|
||||
## 6. Benchmark
|
||||
|
||||

|
||||

|
||||
|
||||
[测试代码见这里](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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -2,13 +2,13 @@ version := $(shell /bin/date "+%Y-%m-%d %H:%M")
|
||||
|
||||
build:
|
||||
go build -ldflags="-s -w" -ldflags="-X 'main.BuildTime=$(version)'" goctl.go
|
||||
command -v upx &> /dev/null && upx goctl
|
||||
$(if $(shell command -v upx), upx goctl)
|
||||
mac:
|
||||
GOOS=darwin go build -ldflags="-s -w" -ldflags="-X 'main.BuildTime=$(version)'" -o goctl-darwin goctl.go
|
||||
command -v upx &> /dev/null && upx goctl-darwin
|
||||
$(if $(shell command -v upx), upx goctl-darwin)
|
||||
win:
|
||||
GOOS=windows go build -ldflags="-s -w" -ldflags="-X 'main.BuildTime=$(version)'" -o goctl.exe goctl.go
|
||||
command -v upx &> /dev/null && upx goctl.exe
|
||||
$(if $(shell command -v upx), upx goctl.exe)
|
||||
linux:
|
||||
GOOS=linux go build -ldflags="-s -w" -ldflags="-X 'main.BuildTime=$(version)'" -o goctl-linux goctl.go
|
||||
command -v upx &> /dev/null && upx goctl-linux
|
||||
$(if $(shell command -v upx), upx goctl-linux)
|
||||
|
||||
@@ -28,10 +28,10 @@ type response struct {
|
||||
}
|
||||
|
||||
service {{.serviceName}} {
|
||||
@handler // TODO: set handler name and delete this comment
|
||||
@handler GetUser // TODO: set handler name and delete this comment
|
||||
get /users/id/:userId(request) returns(response)
|
||||
|
||||
@handler // TODO: set handler name and delete this comment
|
||||
@handler CreateUser // TODO: set handler name and delete this comment
|
||||
post /users/create(request)
|
||||
}
|
||||
`
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,68 +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
|
||||
}
|
||||
|
||||
47
tools/goctl/api/format/format_test.go
Normal file
47
tools/goctl/api/format/format_test.go
Normal 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)
|
||||
}
|
||||
@@ -12,13 +12,13 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/logrusorgru/aurora"
|
||||
"github.com/urfave/cli"
|
||||
|
||||
"github.com/tal-tech/go-zero/core/logx"
|
||||
apiformat "github.com/tal-tech/go-zero/tools/goctl/api/format"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/api/parser"
|
||||
apiutil "github.com/tal-tech/go-zero/tools/goctl/api/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/config"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
const tmpFile = "%s-%d"
|
||||
@@ -28,7 +28,8 @@ 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")
|
||||
namingStyle := c.String("style")
|
||||
|
||||
if len(apiFile) == 0 {
|
||||
return errors.New("missing -api")
|
||||
}
|
||||
@@ -36,10 +37,10 @@ func GoCommand(c *cli.Context) error {
|
||||
return errors.New("missing -dir")
|
||||
}
|
||||
|
||||
return DoGenProject(apiFile, dir, force)
|
||||
return DoGenProject(apiFile, dir, namingStyle)
|
||||
}
|
||||
|
||||
func DoGenProject(apiFile, dir string, force bool) error {
|
||||
func DoGenProject(apiFile, dir, style string) error {
|
||||
p, err := parser.NewParser(apiFile)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -49,15 +50,21 @@ func DoGenProject(apiFile, dir string, force bool) error {
|
||||
return err
|
||||
}
|
||||
|
||||
cfg, err := config.NewConfig(style)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logx.Must(util.MkdirIfNotExist(dir))
|
||||
logx.Must(genEtc(dir, api))
|
||||
logx.Must(genConfig(dir, api))
|
||||
logx.Must(genMain(dir, api))
|
||||
logx.Must(genServiceContext(dir, api))
|
||||
logx.Must(genTypes(dir, api, force))
|
||||
logx.Must(genHandlers(dir, api))
|
||||
logx.Must(genRoutes(dir, api, force))
|
||||
logx.Must(genLogic(dir, api))
|
||||
logx.Must(genEtc(dir, cfg, api))
|
||||
logx.Must(genConfig(dir, cfg, api))
|
||||
logx.Must(genMain(dir, cfg, api))
|
||||
logx.Must(genServiceContext(dir, cfg, api))
|
||||
logx.Must(genTypes(dir, cfg, api))
|
||||
logx.Must(genRoutes(dir, cfg, api))
|
||||
logx.Must(genHandlers(dir, cfg, api))
|
||||
logx.Must(genLogic(dir, cfg, api))
|
||||
logx.Must(genMiddleware(dir, cfg, api))
|
||||
|
||||
if err := backupAndSweep(apiFile); err != nil {
|
||||
return err
|
||||
|
||||
@@ -22,28 +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(
|
||||
group: greet
|
||||
// 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)
|
||||
}
|
||||
`
|
||||
|
||||
@@ -278,6 +283,42 @@ service A-api {
|
||||
}
|
||||
`
|
||||
|
||||
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)
|
||||
@@ -291,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)
|
||||
}
|
||||
@@ -314,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)
|
||||
@@ -341,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)
|
||||
}
|
||||
|
||||
@@ -360,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)
|
||||
}
|
||||
@@ -496,12 +534,55 @@ func TestHasImportApi(t *testing.T) {
|
||||
}
|
||||
}
|
||||
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 TestCamelStyle(t *testing.T) {
|
||||
filename := "greet.api"
|
||||
err := ioutil.WriteFile(filename, []byte(testApiTemplate), os.ModePerm)
|
||||
assert.Nil(t, err)
|
||||
defer os.Remove(filename)
|
||||
_, err = parser.NewParser(filename)
|
||||
assert.Nil(t, err)
|
||||
|
||||
validateWithCamel(t, filename, "GoZero")
|
||||
}
|
||||
|
||||
func validate(t *testing.T, api string) {
|
||||
validateWithCamel(t, api, "gozero")
|
||||
}
|
||||
|
||||
func validateWithCamel(t *testing.T, api, camel string) {
|
||||
dir := "_go"
|
||||
err := DoGenProject(api, dir, true)
|
||||
os.RemoveAll(dir)
|
||||
err := DoGenProject(api, dir, camel)
|
||||
defer os.RemoveAll(dir)
|
||||
assert.Nil(t, err)
|
||||
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
|
||||
@@ -8,12 +8,14 @@ import (
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/api/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/config"
|
||||
ctlutil "github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/format"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/vars"
|
||||
)
|
||||
|
||||
const (
|
||||
configFile = "config.go"
|
||||
configFile = "config"
|
||||
configTemplate = `package config
|
||||
|
||||
import {{.authImport}}
|
||||
@@ -31,8 +33,13 @@ type Config struct {
|
||||
`
|
||||
)
|
||||
|
||||
func genConfig(dir string, api *spec.ApiSpec) error {
|
||||
fp, created, err := util.MaybeCreateFile(dir, configDir, configFile)
|
||||
func genConfig(dir string, cfg *config.Config, api *spec.ApiSpec) error {
|
||||
filename, err := format.FileNamingFormat(cfg.NamingFormat, configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fp, created, err := util.MaybeCreateFile(dir, configDir, filename+".go")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -8,7 +8,9 @@ import (
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/api/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/config"
|
||||
ctlutil "github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/format"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -20,8 +22,13 @@ Port: {{.port}}
|
||||
`
|
||||
)
|
||||
|
||||
func genEtc(dir string, api *spec.ApiSpec) error {
|
||||
fp, created, err := util.MaybeCreateFile(dir, etcDir, fmt.Sprintf("%s.yaml", api.Service.Name))
|
||||
func genEtc(dir string, cfg *config.Config, api *spec.ApiSpec) error {
|
||||
filename, err := format.FileNamingFormat(cfg.NamingFormat, api.Service.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fp, created, err := util.MaybeCreateFile(dir, etcDir, fmt.Sprintf("%s.yaml", filename))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -31,11 +38,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)
|
||||
}
|
||||
|
||||
@@ -2,14 +2,18 @@ package gogen
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
"strings"
|
||||
"text/template"
|
||||
"unicode"
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
|
||||
apiutil "github.com/tal-tech/go-zero/tools/goctl/api/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/config"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/format"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/vars"
|
||||
)
|
||||
|
||||
@@ -50,13 +54,8 @@ type Handler struct {
|
||||
HasRequest bool
|
||||
}
|
||||
|
||||
func genHandler(dir string, group spec.Group, route spec.Route) error {
|
||||
handler, ok := apiutil.GetAnnotationValue(route.Annotations, "server", "handler")
|
||||
if !ok {
|
||||
return fmt.Errorf("missing handler annotation for %q", route.Path)
|
||||
}
|
||||
|
||||
handler = getHandlerName(handler)
|
||||
func genHandler(dir string, cfg *config.Config, group spec.Group, route spec.Route) error {
|
||||
handler := getHandlerName(route)
|
||||
if getHandlerFolderPath(group, route) != handlerDir {
|
||||
handler = strings.Title(handler)
|
||||
}
|
||||
@@ -65,27 +64,24 @@ func genHandler(dir string, group spec.Group, route spec.Route) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return doGenToFile(dir, handler, group, route, Handler{
|
||||
return doGenToFile(dir, handler, cfg, group, route, Handler{
|
||||
ImportPackages: genHandlerImports(group, route, parentPkg),
|
||||
HandlerName: handler,
|
||||
RequestType: util.Title(route.RequestType.Name),
|
||||
LogicType: strings.TrimSuffix(strings.Title(handler), "Handler") + "Logic",
|
||||
LogicType: strings.Title(getLogicName(route)),
|
||||
Call: strings.Title(strings.TrimSuffix(handler, "Handler")),
|
||||
HasResp: len(route.ResponseType.Name) > 0,
|
||||
HasRequest: len(route.RequestType.Name) > 0,
|
||||
})
|
||||
}
|
||||
|
||||
func doGenToFile(dir, handler string, group spec.Group, route spec.Route, handleObj Handler) error {
|
||||
if getHandlerFolderPath(group, route) != handlerDir {
|
||||
handler = strings.Title(handler)
|
||||
}
|
||||
filename := strings.ToLower(handler)
|
||||
if strings.HasSuffix(filename, "handler") {
|
||||
filename = filename + ".go"
|
||||
} else {
|
||||
filename = filename + "handler.go"
|
||||
func doGenToFile(dir, handler string, cfg *config.Config, group spec.Group, route spec.Route, handleObj Handler) error {
|
||||
filename, err := format.FileNamingFormat(cfg.NamingFormat, handler)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
filename = filename + ".go"
|
||||
fp, created, err := apiutil.MaybeCreateFile(dir, getHandlerFolderPath(group, route), filename)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -111,10 +107,10 @@ func doGenToFile(dir, handler string, group spec.Group, route spec.Route, handle
|
||||
return err
|
||||
}
|
||||
|
||||
func genHandlers(dir string, api *spec.ApiSpec) error {
|
||||
func genHandlers(dir string, cfg *config.Config, api *spec.ApiSpec) error {
|
||||
for _, group := range api.Service.Groups {
|
||||
for _, route := range group.Routes {
|
||||
if err := genHandler(dir, group, route); err != nil {
|
||||
if err := genHandler(dir, cfg, group, route); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -136,14 +132,23 @@ func genHandlerImports(group spec.Group, route spec.Route, parentPkg string) str
|
||||
return strings.Join(imports, "\n\t")
|
||||
}
|
||||
|
||||
func getHandlerBaseName(handler string) string {
|
||||
handlerName := util.Untitle(handler)
|
||||
if strings.HasSuffix(handlerName, "handler") {
|
||||
handlerName = strings.ReplaceAll(handlerName, "handler", "")
|
||||
} else if strings.HasSuffix(handlerName, "Handler") {
|
||||
handlerName = strings.ReplaceAll(handlerName, "Handler", "")
|
||||
func getHandlerBaseName(route spec.Route) (string, error) {
|
||||
handler, ok := apiutil.GetAnnotationValue(route.Annotations, "server", "handler")
|
||||
if !ok {
|
||||
return "", fmt.Errorf("missing handler annotation for %q", route.Path)
|
||||
}
|
||||
return handlerName
|
||||
|
||||
for _, char := range handler {
|
||||
if !unicode.IsDigit(char) && !unicode.IsLetter(char) {
|
||||
return "", errors.New(fmt.Sprintf("route [%s] handler [%s] invalid, handler name should only contains letter or digit",
|
||||
route.Path, handler))
|
||||
}
|
||||
}
|
||||
|
||||
handler = strings.TrimSpace(handler)
|
||||
handler = strings.TrimSuffix(handler, "handler")
|
||||
handler = strings.TrimSuffix(handler, "Handler")
|
||||
return handler, nil
|
||||
}
|
||||
|
||||
func getHandlerFolderPath(group spec.Group, route spec.Route) string {
|
||||
@@ -159,6 +164,20 @@ func getHandlerFolderPath(group spec.Group, route spec.Route) string {
|
||||
return path.Join(handlerDir, folder)
|
||||
}
|
||||
|
||||
func getHandlerName(handler string) string {
|
||||
return getHandlerBaseName(handler) + "Handler"
|
||||
func getHandlerName(route spec.Route) string {
|
||||
handler, err := getHandlerBaseName(route)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return handler + "Handler"
|
||||
}
|
||||
|
||||
func getLogicName(route spec.Route) string {
|
||||
handler, err := getHandlerBaseName(route)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return handler + "Logic"
|
||||
}
|
||||
|
||||
@@ -9,7 +9,9 @@ import (
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/api/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/config"
|
||||
ctlutil "github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/format"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/vars"
|
||||
)
|
||||
|
||||
@@ -40,10 +42,10 @@ func (l *{{.logic}}) {{.function}}({{.request}}) {{.responseType}} {
|
||||
}
|
||||
`
|
||||
|
||||
func genLogic(dir string, api *spec.ApiSpec) error {
|
||||
func genLogic(dir string, cfg *config.Config, api *spec.ApiSpec) error {
|
||||
for _, g := range api.Service.Groups {
|
||||
for _, r := range g.Routes {
|
||||
err := genLogicByRoute(dir, g, r)
|
||||
err := genLogicByRoute(dir, cfg, g, r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -52,16 +54,14 @@ func genLogic(dir string, api *spec.ApiSpec) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func genLogicByRoute(dir string, group spec.Group, route spec.Route) error {
|
||||
handler, ok := util.GetAnnotationValue(route.Annotations, "server", "handler")
|
||||
if !ok {
|
||||
return fmt.Errorf("missing handler annotation for %q", route.Path)
|
||||
func genLogicByRoute(dir string, cfg *config.Config, group spec.Group, route spec.Route) error {
|
||||
logic := getLogicName(route)
|
||||
goFile, err := format.FileNamingFormat(cfg.NamingFormat, logic)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
handler = strings.TrimSuffix(handler, "handler")
|
||||
handler = strings.TrimSuffix(handler, "Handler")
|
||||
filename := strings.ToLower(handler)
|
||||
goFile := filename + "logic.go"
|
||||
goFile = goFile + ".go"
|
||||
fp, created, err := util.MaybeCreateFile(dir, getLogicFolderPath(group, route), goFile)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -102,8 +102,8 @@ func genLogicByRoute(dir string, group spec.Group, route spec.Route) error {
|
||||
buffer := new(bytes.Buffer)
|
||||
err = t.Execute(fp, map[string]string{
|
||||
"imports": imports,
|
||||
"logic": strings.Title(handler) + "Logic",
|
||||
"function": strings.Title(strings.TrimSuffix(handler, "Handler")),
|
||||
"logic": strings.Title(logic),
|
||||
"function": strings.Title(strings.TrimSuffix(logic, "Logic")),
|
||||
"responseType": responseString,
|
||||
"returnString": returnString,
|
||||
"request": requestString,
|
||||
|
||||
@@ -8,7 +8,9 @@ import (
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/api/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/config"
|
||||
ctlutil "github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/format"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/vars"
|
||||
)
|
||||
|
||||
@@ -40,12 +42,17 @@ func main() {
|
||||
}
|
||||
`
|
||||
|
||||
func genMain(dir string, api *spec.ApiSpec) error {
|
||||
func genMain(dir string, cfg *config.Config, api *spec.ApiSpec) error {
|
||||
name := strings.ToLower(api.Service.Name)
|
||||
if strings.HasSuffix(name, "-api") {
|
||||
name = strings.ReplaceAll(name, "-api", "")
|
||||
}
|
||||
goFile := name + ".go"
|
||||
filename, err := format.FileNamingFormat(cfg.NamingFormat, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
goFile := filename + ".go"
|
||||
fp, created, err := util.MaybeCreateFile(dir, "", goFile)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -5,7 +5,10 @@ import (
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/api/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/config"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/format"
|
||||
)
|
||||
|
||||
var middlewareImplementCode = `
|
||||
@@ -30,9 +33,16 @@ func (m *{{.name}})Handle(next http.HandlerFunc) http.HandlerFunc {
|
||||
}
|
||||
`
|
||||
|
||||
func genMiddleware(dir string, middlewares []string) error {
|
||||
func genMiddleware(dir string, cfg *config.Config, api *spec.ApiSpec) error {
|
||||
var middlewares = getMiddleware(api)
|
||||
for _, item := range middlewares {
|
||||
filename := strings.TrimSuffix(strings.ToLower(item), "middleware") + "middleware" + ".go"
|
||||
middlewareFilename := strings.TrimSuffix(strings.ToLower(item), "middleware") + "_middleware"
|
||||
formatName, err := format.FileNamingFormat(cfg.NamingFormat, middlewareFilename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
filename := formatName + ".go"
|
||||
fp, created, err := util.MaybeCreateFile(dir, middlewareDir, filename)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -3,6 +3,7 @@ package gogen
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -11,12 +12,14 @@ import (
|
||||
"github.com/tal-tech/go-zero/core/collection"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
|
||||
apiutil "github.com/tal-tech/go-zero/tools/goctl/api/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/config"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/format"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/vars"
|
||||
)
|
||||
|
||||
const (
|
||||
routesFilename = "routes.go"
|
||||
routesFilename = "routes"
|
||||
routesTemplate = `// Code generated by goctl. DO NOT EDIT.
|
||||
package handler
|
||||
|
||||
@@ -61,7 +64,7 @@ type (
|
||||
}
|
||||
)
|
||||
|
||||
func genRoutes(dir string, api *spec.ApiSpec, force bool) error {
|
||||
func genRoutes(dir string, cfg *config.Config, api *spec.ApiSpec) error {
|
||||
var builder strings.Builder
|
||||
groups, err := getRoutes(api)
|
||||
if err != nil {
|
||||
@@ -120,14 +123,16 @@ func genRoutes(dir string, api *spec.ApiSpec, force bool) error {
|
||||
return err
|
||||
}
|
||||
|
||||
filename := path.Join(dir, handlerDir, routesFilename)
|
||||
if !force {
|
||||
if err := util.RemoveOrQuit(filename); err != nil {
|
||||
return err
|
||||
}
|
||||
routeFilename, err := format.FileNamingFormat(cfg.NamingFormat, routesFilename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
routeFilename = routeFilename + ".go"
|
||||
|
||||
fp, created, err := apiutil.MaybeCreateFile(dir, handlerDir, routesFilename)
|
||||
filename := path.Join(dir, handlerDir, routeFilename)
|
||||
os.Remove(filename)
|
||||
|
||||
fp, created, err := apiutil.MaybeCreateFile(dir, handlerDir, routeFilename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -163,8 +168,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()
|
||||
@@ -180,18 +184,15 @@ func getRoutes(api *spec.ApiSpec) ([]group, error) {
|
||||
for _, g := range api.Service.Groups {
|
||||
var groupedRoutes group
|
||||
for _, r := range g.Routes {
|
||||
handler, ok := apiutil.GetAnnotationValue(r.Annotations, "server", "handler")
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing handler annotation for route %q", r.Path)
|
||||
}
|
||||
handler = getHandlerBaseName(handler) + "Handler(serverCtx)"
|
||||
handler := getHandlerName(r)
|
||||
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{
|
||||
@@ -215,3 +216,7 @@ func getRoutes(api *spec.ApiSpec) ([]group, error) {
|
||||
|
||||
return routes, nil
|
||||
}
|
||||
|
||||
func toPrefix(folder string) string {
|
||||
return strings.ReplaceAll(folder, "/", "")
|
||||
}
|
||||
|
||||
@@ -8,12 +8,14 @@ import (
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/api/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/config"
|
||||
ctlutil "github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/format"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/vars"
|
||||
)
|
||||
|
||||
const (
|
||||
contextFilename = "servicecontext.go"
|
||||
contextFilename = "service_context"
|
||||
contextTemplate = `package svc
|
||||
|
||||
import (
|
||||
@@ -35,8 +37,13 @@ func NewServiceContext(c {{.config}}) *ServiceContext {
|
||||
`
|
||||
)
|
||||
|
||||
func genServiceContext(dir string, api *spec.ApiSpec) error {
|
||||
fp, created, err := util.MaybeCreateFile(dir, contextDir, contextFilename)
|
||||
func genServiceContext(dir string, cfg *config.Config, api *spec.ApiSpec) error {
|
||||
filename, err := format.FileNamingFormat(cfg.NamingFormat, contextFilename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fp, created, err := util.MaybeCreateFile(dir, contextDir, filename+".go")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -64,10 +71,6 @@ func genServiceContext(dir string, api *spec.ApiSpec) error {
|
||||
var middlewareStr string
|
||||
var middlewareAssignment string
|
||||
var middlewares = getMiddleware(api)
|
||||
err = genMiddleware(dir, middlewares)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, item := range middlewares {
|
||||
middlewareStr += fmt.Sprintf("%s rest.Middleware\n", item)
|
||||
|
||||
@@ -5,17 +5,20 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
|
||||
apiutil "github.com/tal-tech/go-zero/tools/goctl/api/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/config"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/format"
|
||||
)
|
||||
|
||||
const (
|
||||
typesFile = "types.go"
|
||||
typesFile = "types"
|
||||
typesTemplate = `// Code generated by goctl. DO NOT EDIT.
|
||||
package types{{if .containsTime}}
|
||||
import (
|
||||
@@ -42,23 +45,25 @@ 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, cfg *config.Config, 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
|
||||
}
|
||||
}
|
||||
|
||||
fp, created, err := apiutil.MaybeCreateFile(dir, typesDir, typesFile)
|
||||
typeFilename, err := format.FileNamingFormat(cfg.NamingFormat, typesFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
typeFilename = typeFilename + ".go"
|
||||
filename := path.Join(dir, typesDir, typeFilename)
|
||||
os.Remove(filename)
|
||||
|
||||
fp, created, err := apiutil.MaybeCreateFile(dir, typesDir, typeFilename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !created {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -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, "//")
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -3,33 +3,35 @@ package new
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/api/gogen"
|
||||
conf "github.com/tal-tech/go-zero/tools/goctl/config"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
const apiTemplate = `
|
||||
type Request struct {
|
||||
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 +55,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, conf.DefaultFormat)
|
||||
return err
|
||||
}
|
||||
|
||||
268
tools/goctl/api/parser/apifileparser.go
Normal file
268
tools/goctl/api/parser/apifileparser.go
Normal 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, " ")
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,10 +34,11 @@ 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, `"`)
|
||||
@@ -46,29 +47,44 @@ func NewParser(filename string) (*Parser, error) {
|
||||
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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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") || 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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -16,4 +16,5 @@ const (
|
||||
multilineBeginTag = '>'
|
||||
multilineEndTag = '<'
|
||||
semicolon = ';'
|
||||
newline = "\n"
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -57,10 +57,8 @@ type (
|
||||
}
|
||||
|
||||
Service struct {
|
||||
Name string
|
||||
Annotations []Annotation
|
||||
Routes []Route
|
||||
Groups []Group
|
||||
Name string
|
||||
Groups []Group
|
||||
}
|
||||
|
||||
Type struct {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
122
tools/goctl/config/config.go
Normal file
122
tools/goctl/config/config.go
Normal file
@@ -0,0 +1,122 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
const (
|
||||
configFile = "config.yaml"
|
||||
configFolder = "config"
|
||||
DefaultFormat = "gozero"
|
||||
)
|
||||
|
||||
const defaultYaml = `# namingFormat is used to define the naming format of the generated file name.
|
||||
# just like time formatting, you can specify the formatting style through the
|
||||
# two format characters go, and zero. for example: snake format you can
|
||||
# define as go_zero, camel case format you can it is defined as goZero,
|
||||
# and even split characters can be specified, such as go#zero. in theory,
|
||||
# any combination can be used, but the prerequisite must meet the naming conventions
|
||||
# of each operating system file name. if you want to independently control the file
|
||||
# naming style of the api, rpc, and model layers, you can set it through apiNamingFormat,
|
||||
# rpcNamingFormat, modelNamingFormat, and independent control is not enabled by default.
|
||||
# for more information, please see #{apiNamingFormat},#{rpcNamingFormat},#{modelNamingFormat}
|
||||
# Note: namingFormat is based on snake or camel string
|
||||
namingFormat: gozero
|
||||
`
|
||||
|
||||
type Config struct {
|
||||
// NamingFormat is used to define the naming format of the generated file name.
|
||||
// just like time formatting, you can specify the formatting style through the
|
||||
// two format characters go, and zero. for example: snake format you can
|
||||
// define as go_zero, camel case format you can it is defined as goZero,
|
||||
// and even split characters can be specified, such as go#zero. in theory,
|
||||
// any combination can be used, but the prerequisite must meet the naming conventions
|
||||
// of each operating system file name.
|
||||
// Note: NamingFormat is based on snake or camel string
|
||||
NamingFormat string `yaml:"namingFormat"`
|
||||
}
|
||||
|
||||
func NewConfig(format string) (*Config, error) {
|
||||
if len(format) == 0 {
|
||||
format = DefaultFormat
|
||||
}
|
||||
cfg := &Config{NamingFormat: format}
|
||||
err := validate(cfg)
|
||||
return cfg, err
|
||||
}
|
||||
|
||||
func InitOrGetConfig() (*Config, error) {
|
||||
var (
|
||||
defaultConfig Config
|
||||
)
|
||||
err := yaml.Unmarshal([]byte(defaultYaml), &defaultConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
goctlHome, err := util.GetGoctlHome()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
configDir := filepath.Join(goctlHome, configFolder)
|
||||
configFilename := filepath.Join(configDir, configFile)
|
||||
if util.FileExists(configFilename) {
|
||||
data, err := ioutil.ReadFile(configFilename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = yaml.Unmarshal(data, &defaultConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = validate(&defaultConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &defaultConfig, nil
|
||||
}
|
||||
|
||||
err = util.MkdirIfNotExist(configDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
f, err := os.Create(configFilename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = f.Close()
|
||||
}()
|
||||
|
||||
_, err = f.WriteString(defaultYaml)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = validate(&defaultConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &defaultConfig, nil
|
||||
}
|
||||
|
||||
func validate(cfg *Config) error {
|
||||
if len(strings.TrimSpace(cfg.NamingFormat)) == 0 {
|
||||
return errors.New("missing namingFormat")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
50
tools/goctl/config/readme.md
Normal file
50
tools/goctl/config/readme.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# 配置项管理
|
||||
|
||||
| 名称 | 是否可选 | 说明 |
|
||||
|-------------------|----------|-----------------------------------------------|
|
||||
| namingFormat | YES | 文件名称格式化符 |
|
||||
|
||||
# naming-format
|
||||
`namingFormat`可以用于对生成代码的文件名称进行格式化,和日期格式化符(yyyy-MM-dd)类似,在代码生成时可以根据这些配置项的格式化符进行格式化。
|
||||
|
||||
## 格式化符(gozero)
|
||||
格式化符有`go`,`zero`组成,如常见的三种格式化风格你可以这样编写:
|
||||
* lower: `gozero`
|
||||
* camel: `goZero`
|
||||
* snake: `go_zero`
|
||||
|
||||
常见格式化符生成示例
|
||||
源字符:welcome_to_go_zero
|
||||
|
||||
| 格式化符 | 格式化结果 | 说明 |
|
||||
|------------|-----------------------|---------------------------|
|
||||
| gozero | welcometogozero | 小写 |
|
||||
| goZero | welcomeToGoZero | 驼峰 |
|
||||
| go_zero | welcome_to_go_zero | snake |
|
||||
| Go#zero | Welcome#to#go#zero | #号分割Title类型 |
|
||||
| GOZERO | WELCOMETOGOZERO | 大写 |
|
||||
| \_go#zero_ | \_welcome#to#go#zero_ | 下划线做前后缀,并且#分割 |
|
||||
|
||||
错误格式化符示例
|
||||
* go
|
||||
* gOZero
|
||||
* zero
|
||||
* goZEro
|
||||
* goZERo
|
||||
* goZeRo
|
||||
* tal
|
||||
|
||||
# 使用方法
|
||||
目前可通过在生成api、rpc、model时通过`--style`参数指定format格式,如:
|
||||
```shell script
|
||||
goctl api go test.api -dir . -style gozero
|
||||
```
|
||||
```shell script
|
||||
goctl rpc proto -src test.proto -dir . -style go_zero
|
||||
```
|
||||
```shell script
|
||||
goctl model mysql datasource -url="" -table="*" -dir ./snake -style GoZero
|
||||
```
|
||||
|
||||
# 默认值
|
||||
当不指定-style时默认值为`gozero`
|
||||
@@ -5,8 +5,10 @@ import (
|
||||
"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"
|
||||
)
|
||||
|
||||
@@ -26,7 +28,7 @@ func DockerCommand(c *cli.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return gen.GenerateDockerfile(goFile, "-f", "etc/"+cfg)
|
||||
return generateDockerfile(goFile, "-f", "etc/"+cfg)
|
||||
}
|
||||
|
||||
func findConfig(file, dir string) (string, error) {
|
||||
@@ -57,3 +59,56 @@ func findConfig(file, dir string) (string, error) {
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
package gen
|
||||
package docker
|
||||
|
||||
const dockerTemplate = `FROM golang:alpine AS builder
|
||||
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
|
||||
|
||||
@@ -10,6 +18,7 @@ ENV GOPROXY https://goproxy.cn,direct
|
||||
|
||||
WORKDIR /build/zero
|
||||
COPY . .
|
||||
RUN sh -c "[ -f go.mod ]" || exit
|
||||
COPY {{.goRelPath}}/etc /app/etc
|
||||
RUN go build -ldflags="-s -w" -o /app/{{.exeFile}} {{.goRelPath}}/{{.goFile}}
|
||||
|
||||
@@ -27,3 +36,10 @@ COPY --from=builder /app/etc /app/etc
|
||||
|
||||
CMD ["./{{.exeFile}}"{{.argument}}]
|
||||
`
|
||||
)
|
||||
|
||||
func GenTemplates(_ *cli.Context) error {
|
||||
return util.InitTemplates(category, map[string]string{
|
||||
dockerTemplateFile: dockerTemplate,
|
||||
})
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package gen
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
)
|
||||
|
||||
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{
|
||||
"goRelPath": projPath,
|
||||
"goFile": goFile,
|
||||
"exeFile": util.FileNameWithoutExt(filepath.Base(goFile)),
|
||||
"argument": builder.String(),
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -25,7 +25,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
BuildVersion = "20201108"
|
||||
BuildVersion = "20201125"
|
||||
commands = []cli.Command{
|
||||
{
|
||||
Name: "api",
|
||||
@@ -98,9 +98,10 @@ var (
|
||||
Name: "api",
|
||||
Usage: "the api file",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "force",
|
||||
Usage: "force override the exist files",
|
||||
cli.StringFlag{
|
||||
Name: "style",
|
||||
Required: false,
|
||||
Usage: "the file naming format, see [https://github.com/tal-tech/go-zero/tree/master/tools/goctl/config/readme.md]",
|
||||
},
|
||||
},
|
||||
Action: gogen.GoCommand,
|
||||
@@ -205,6 +206,11 @@ var (
|
||||
Name: "new",
|
||||
Usage: `generate rpc demo service`,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "style",
|
||||
Required: false,
|
||||
Usage: "the file naming format, see [https://github.com/tal-tech/go-zero/tree/master/tools/goctl/config/readme.md]",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "idea",
|
||||
Usage: "whether the command execution environment is from idea plugin. [optional]",
|
||||
@@ -239,6 +245,11 @@ var (
|
||||
Name: "dir, d",
|
||||
Usage: `the target path of the code`,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "style",
|
||||
Required: false,
|
||||
Usage: "the file naming format, see [https://github.com/tal-tech/go-zero/tree/master/tools/goctl/config/readme.md]",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "idea",
|
||||
Usage: "whether the command execution environment is from idea plugin. [optional]",
|
||||
@@ -269,8 +280,9 @@ var (
|
||||
Usage: "the target dir",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "style",
|
||||
Usage: "the file naming style, lower|camel|underline,default is lower",
|
||||
Name: "style",
|
||||
Required: false,
|
||||
Usage: "the file naming format, see [https://github.com/tal-tech/go-zero/tree/master/tools/goctl/config/readme.md]",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "cache, c",
|
||||
@@ -304,8 +316,9 @@ var (
|
||||
Usage: "the target dir",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "style",
|
||||
Usage: "the file naming style, lower|camel|snake, default is lower",
|
||||
Name: "style",
|
||||
Required: false,
|
||||
Usage: "the file naming format, see [https://github.com/tal-tech/go-zero/tree/master/tools/goctl/config/readme.md]",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "idea",
|
||||
|
||||
@@ -7,7 +7,7 @@ goctl model 为go-zero下的工具模块中的组件之一,目前支持识别m
|
||||
* 通过ddl生成
|
||||
|
||||
```shell script
|
||||
goctl model mysql ddl -src="./*.sql" -dir="./sql/model" -c=true
|
||||
goctl model mysql ddl -src="./*.sql" -dir="./sql/model" -c
|
||||
```
|
||||
|
||||
执行上述命令后即可快速生成CURD代码。
|
||||
@@ -29,156 +29,191 @@ goctl model 为go-zero下的工具模块中的组件之一,目前支持识别m
|
||||
```go
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tal-tech/go-zero/core/stores/cache"
|
||||
"github.com/tal-tech/go-zero/core/stores/sqlc"
|
||||
"github.com/tal-tech/go-zero/core/stores/sqlx"
|
||||
"github.com/tal-tech/go-zero/core/stringx"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/model/sql/builderx"
|
||||
)
|
||||
|
||||
var (
|
||||
userFieldNames = builderx.FieldNames(&User{})
|
||||
userRows = strings.Join(userFieldNames, ",")
|
||||
userRowsExpectAutoSet = strings.Join(stringx.Remove(userFieldNames, "id", "create_time", "update_time"), ",")
|
||||
userRowsWithPlaceHolder = strings.Join(stringx.Remove(userFieldNames, "id", "create_time", "update_time"), "=?,") + "=?"
|
||||
|
||||
cacheUserPrefix = "cache#User#user#"
|
||||
cacheUserNamePrefix = "cache#User#name#"
|
||||
cacheUserMobilePrefix = "cache#User#mobile#"
|
||||
cacheUserIdPrefix = "cache#User#id#"
|
||||
)
|
||||
|
||||
type (
|
||||
UserModel interface {
|
||||
Insert(data User) (sql.Result, error)
|
||||
FindOne(id int64) (*User, error)
|
||||
FindOneByUser(user string) (*User, error)
|
||||
FindOneByName(name string) (*User, error)
|
||||
FindOneByMobile(mobile string) (*User, error)
|
||||
Update(data User) error
|
||||
Delete(id int64) error
|
||||
}
|
||||
|
||||
defaultUserModel struct {
|
||||
sqlc.CachedConn
|
||||
table string
|
||||
}
|
||||
|
||||
User struct {
|
||||
Id int64 `db:"id"`
|
||||
User string `db:"user"` // 用户
|
||||
Name string `db:"name"` // 用户名称
|
||||
Password string `db:"password"` // 用户密码
|
||||
Mobile string `db:"mobile"` // 手机号
|
||||
Gender string `db:"gender"` // 男|女|未公开
|
||||
Nickname string `db:"nickname"` // 用户昵称
|
||||
CreateTime time.Time `db:"create_time"`
|
||||
UpdateTime time.Time `db:"update_time"`
|
||||
}
|
||||
)
|
||||
|
||||
func NewUserModel(conn sqlx.SqlConn, c cache.CacheConf) UserModel {
|
||||
return &defaultUserModel{
|
||||
CachedConn: sqlc.NewConn(conn, c),
|
||||
table: "user",
|
||||
}
|
||||
}
|
||||
|
||||
func (m *defaultUserModel) Insert(data User) (sql.Result, error) {
|
||||
userKey := fmt.Sprintf("%s%v", cacheUserPrefix, data.User)
|
||||
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.User, data.Name, data.Password, data.Mobile, data.Gender, data.Nickname)
|
||||
}, userMobileKey, userKey, userNameKey)
|
||||
return ret, err
|
||||
}
|
||||
|
||||
func (m *defaultUserModel) 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 := fmt.Sprintf("select %s from %s where id = ? limit 1", userRows, m.table)
|
||||
return conn.QueryRow(v, query, id)
|
||||
})
|
||||
switch err {
|
||||
case nil:
|
||||
return &resp, nil
|
||||
case sqlc.ErrNotFound:
|
||||
return nil, ErrNotFound
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
func (m *defaultUserModel) FindOneByUser(user string) (*User, error) {
|
||||
userKey := fmt.Sprintf("%s%v", cacheUserPrefix, user)
|
||||
var resp User
|
||||
err := m.QueryRowIndex(&resp, userKey, m.formatPrimary, func(conn sqlx.SqlConn, v interface{}) (i interface{}, e error) {
|
||||
query := fmt.Sprintf("select %s from %s where user = ? limit 1", userRows, m.table)
|
||||
if err := conn.QueryRow(&resp, query, user); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.Id, nil
|
||||
}, m.queryPrimary)
|
||||
switch err {
|
||||
case nil:
|
||||
return &resp, nil
|
||||
case sqlc.ErrNotFound:
|
||||
return nil, ErrNotFound
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
func (m *defaultUserModel) FindOneByName(name string) (*User, error) {
|
||||
userNameKey := fmt.Sprintf("%s%v", cacheUserNamePrefix, name)
|
||||
var resp User
|
||||
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
|
||||
}, m.queryPrimary)
|
||||
switch err {
|
||||
case nil:
|
||||
return &resp, nil
|
||||
case sqlc.ErrNotFound:
|
||||
return nil, ErrNotFound
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
func (m *defaultUserModel) FindOneByMobile(mobile string) (*User, error) {
|
||||
userMobileKey := fmt.Sprintf("%s%v", cacheUserMobilePrefix, mobile)
|
||||
var resp User
|
||||
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
|
||||
}, m.queryPrimary)
|
||||
switch err {
|
||||
case nil:
|
||||
return &resp, nil
|
||||
case sqlc.ErrNotFound:
|
||||
return nil, ErrNotFound
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
func (m *defaultUserModel) 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 := fmt.Sprintf("update %s set %s where id = ?", m.table, userRowsWithPlaceHolder)
|
||||
return conn.Exec(query, data.User, data.Name, data.Password, data.Mobile, data.Gender, data.Nickname, data.Id)
|
||||
}, userIdKey)
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *defaultUserModel) Delete(id int64) error {
|
||||
data, err := m.FindOne(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, id)
|
||||
userKey := fmt.Sprintf("%s%v", cacheUserPrefix, data.User)
|
||||
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 := fmt.Sprintf("delete from %s where id = ?", m.table)
|
||||
return conn.Exec(query, id)
|
||||
}, userIdKey, userKey, userNameKey, userMobileKey)
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *defaultUserModel) formatPrimary(primary interface{}) string {
|
||||
return fmt.Sprintf("%s%v", cacheUserIdPrefix, primary)
|
||||
}
|
||||
|
||||
func (m *defaultUserModel) 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)
|
||||
}
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tal-tech/go-zero/core/stores/cache"
|
||||
"github.com/tal-tech/go-zero/core/stores/sqlc"
|
||||
"github.com/tal-tech/go-zero/core/stores/sqlx"
|
||||
"github.com/tal-tech/go-zero/core/stringx"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/model/sql/builderx"
|
||||
)
|
||||
|
||||
var (
|
||||
userFieldNames = builderx.FieldNames(&User{})
|
||||
userRows = strings.Join(userFieldNames, ",")
|
||||
userRowsExpectAutoSet = strings.Join(stringx.Remove(userFieldNames, "id", "create_time", "update_time"), ",")
|
||||
userRowsWithPlaceHolder = strings.Join(stringx.Remove(userFieldNames, "id", "create_time", "update_time"), "=?,") + "=?"
|
||||
|
||||
cacheUserIdPrefix = "cache#User#id#"
|
||||
cacheUserNamePrefix = "cache#User#name#"
|
||||
cacheUserMobilePrefix = "cache#User#mobile#"
|
||||
)
|
||||
|
||||
type (
|
||||
UserModel struct {
|
||||
sqlc.CachedConn
|
||||
table string
|
||||
}
|
||||
|
||||
User struct {
|
||||
Id int64 `db:"id"`
|
||||
Name string `db:"name"` // 用户名称
|
||||
Password string `db:"password"` // 用户密码
|
||||
Mobile string `db:"mobile"` // 手机号
|
||||
Gender string `db:"gender"` // 男|女|未公开
|
||||
Nickname string `db:"nickname"` // 用户昵称
|
||||
CreateTime time.Time `db:"create_time"`
|
||||
UpdateTime time.Time `db:"update_time"`
|
||||
}
|
||||
)
|
||||
|
||||
func NewUserModel(conn sqlx.SqlConn, c cache.CacheConf) *UserModel {
|
||||
return &UserModel{
|
||||
CachedConn: sqlc.NewConn(conn, c),
|
||||
table: "user",
|
||||
}
|
||||
}
|
||||
|
||||
func (m *UserModel) Insert(data User) (sql.Result, error) {
|
||||
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 := fmt.Sprintf("select %s from %s where id = ? limit 1", userRows, m.table)
|
||||
return conn.QueryRow(v, query, id)
|
||||
})
|
||||
switch err {
|
||||
case nil:
|
||||
return &resp, nil
|
||||
case sqlc.ErrNotFound:
|
||||
return nil, ErrNotFound
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
func (m *UserModel) FindOneByName(name string) (*User, error) {
|
||||
userNameKey := fmt.Sprintf("%s%v", cacheUserNamePrefix, name)
|
||||
var resp User
|
||||
err := m.QueryRowIndex(&resp, userNameKey, 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
|
||||
}, m.queryPrimary)
|
||||
switch err {
|
||||
case nil:
|
||||
return &resp, nil
|
||||
case sqlc.ErrNotFound:
|
||||
return nil, ErrNotFound
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
func (m *UserModel) FindOneByMobile(mobile string) (*User, error) {
|
||||
userMobileKey := fmt.Sprintf("%s%v", cacheUserMobilePrefix, mobile)
|
||||
var resp User
|
||||
err := m.QueryRowIndex(&resp, userMobileKey, 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
|
||||
}, m.queryPrimary)
|
||||
switch err {
|
||||
case nil:
|
||||
return &resp, nil
|
||||
case sqlc.ErrNotFound:
|
||||
return nil, ErrNotFound
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
func (m *UserModel) Update(data User) error {
|
||||
userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, data.Id)
|
||||
_, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
|
||||
query := 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
|
||||
}
|
||||
|
||||
func (m *UserModel) Delete(id int64) error {
|
||||
data, err := m.FindOne(id)
|
||||
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)
|
||||
_, err = m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
|
||||
query := fmt.Sprintf("delete from %s where id = ?", m.table)
|
||||
return conn.Exec(query, id)
|
||||
}, 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)
|
||||
}
|
||||
```
|
||||
|
||||
## 用法
|
||||
@@ -211,25 +246,24 @@ OPTIONS:
|
||||
* ddl
|
||||
|
||||
```shell script
|
||||
goctl model mysql -src={patterns} -dir={dir} -cache=true
|
||||
goctl model mysql -src={patterns} -dir={dir} -cache
|
||||
```
|
||||
|
||||
help
|
||||
|
||||
```
|
||||
NAME:
|
||||
goctl model mysql ddl - generate mysql model from ddl
|
||||
|
||||
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]
|
||||
|
||||
goctl model mysql ddl - generate mysql model from ddl
|
||||
|
||||
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 format, see [https://github.com/tal-tech/go-zero/tree/master/tools/goctl/config/readme.md]
|
||||
--cache, -c generate code with cache [optional]
|
||||
--idea for idea plugin [optional]
|
||||
```
|
||||
|
||||
* datasource
|
||||
@@ -242,18 +276,19 @@ OPTIONS:
|
||||
|
||||
```
|
||||
NAME:
|
||||
goctl model mysql datasource - generate model from datasource
|
||||
goctl model mysql datasource - generate model from datasource
|
||||
|
||||
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 format, see [https://github.com/tal-tech/go-zero/tree/master/tools/goctl/config/readme.md]
|
||||
--idea for idea plugin [optional]
|
||||
|
||||
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]
|
||||
|
||||
```
|
||||
|
||||
@@ -281,13 +316,13 @@ OPTIONS:
|
||||
* ddl
|
||||
|
||||
```shell script
|
||||
goctl model -src={patterns} -dir={dir} -cache=false
|
||||
goctl model -src={patterns} -dir={dir}
|
||||
```
|
||||
|
||||
* datasource
|
||||
|
||||
```shell script
|
||||
goctl model mysql datasource -url={datasource} -table={patterns} -dir={dir} -cache=false
|
||||
goctl model mysql datasource -url={datasource} -table={patterns} -dir={dir}
|
||||
```
|
||||
|
||||
生成代码仅基本的CURD结构。
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package command
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -10,6 +9,7 @@ import (
|
||||
"github.com/go-sql-driver/mysql"
|
||||
"github.com/tal-tech/go-zero/core/logx"
|
||||
"github.com/tal-tech/go-zero/core/stores/sqlx"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/config"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/model/sql/gen"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/model/sql/model"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/model/sql/util"
|
||||
@@ -17,14 +17,16 @@ import (
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var errNotMatched = errors.New("sql not matched")
|
||||
|
||||
const (
|
||||
flagSrc = "src"
|
||||
flagDir = "dir"
|
||||
flagCache = "cache"
|
||||
flagIdea = "idea"
|
||||
flagStyle = "style"
|
||||
flagUrl = "url"
|
||||
flagTable = "table"
|
||||
flagStyle = "style"
|
||||
)
|
||||
|
||||
func MysqlDDL(ctx *cli.Context) error {
|
||||
@@ -32,49 +34,64 @@ func MysqlDDL(ctx *cli.Context) error {
|
||||
dir := ctx.String(flagDir)
|
||||
cache := ctx.Bool(flagCache)
|
||||
idea := ctx.Bool(flagIdea)
|
||||
namingStyle := strings.TrimSpace(ctx.String(flagStyle))
|
||||
style := ctx.String(flagStyle)
|
||||
cfg, err := config.NewConfig(style)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fromDDl(src, dir, cfg, 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)
|
||||
style := ctx.String(flagStyle)
|
||||
pattern := strings.TrimSpace(ctx.String(flagTable))
|
||||
cfg, err := config.NewConfig(style)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return fromDataSource(url, pattern, dir, cfg, cache, idea)
|
||||
}
|
||||
|
||||
func fromDDl(src, dir string, cfg *config.Config, cache, idea bool) error {
|
||||
log := console.NewConsole(idea)
|
||||
src = strings.TrimSpace(src)
|
||||
if len(src) == 0 {
|
||||
return errors.New("expected path or path globbing patterns, but nothing found")
|
||||
}
|
||||
|
||||
switch namingStyle {
|
||||
case gen.NamingLower, gen.NamingCamel, gen.NamingSnake:
|
||||
case "":
|
||||
namingStyle = gen.NamingLower
|
||||
default:
|
||||
return fmt.Errorf("unexpected naming style: %s", namingStyle)
|
||||
}
|
||||
|
||||
files, err := util.MatchFiles(src)
|
||||
if err != nil {
|
||||
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, cfg, 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 string, cfg *config.Config, cache, idea bool) error {
|
||||
log := console.NewConsole(idea)
|
||||
if len(url) == 0 {
|
||||
log.Error("%v", "expected data source of mysql, but nothing found")
|
||||
@@ -86,32 +103,22 @@ func MyDataSource(ctx *cli.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch namingStyle {
|
||||
case gen.NamingLower, gen.NamingCamel, gen.NamingSnake:
|
||||
case "":
|
||||
namingStyle = gen.NamingLower
|
||||
default:
|
||||
return fmt.Errorf("unexpected naming style: %s", namingStyle)
|
||||
}
|
||||
|
||||
cfg, err := mysql.ParseDSN(url)
|
||||
dsn, err := mysql.ParseDSN(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logx.Disable()
|
||||
conn := sqlx.NewMysql(url)
|
||||
databaseSource := strings.TrimSuffix(url, "/"+cfg.DBName) + "/information_schema"
|
||||
databaseSource := strings.TrimSuffix(url, "/"+dsn.DBName) + "/information_schema"
|
||||
db := sqlx.NewMysql(databaseSource)
|
||||
m := model.NewDDLModel(conn)
|
||||
im := model.NewInformationSchemaModel(db)
|
||||
|
||||
tables, err := im.GetAllTables(cfg.DBName)
|
||||
tables, err := im.GetAllTables(dsn.DBName)
|
||||
if err != nil {
|
||||
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 +128,22 @@ func MyDataSource(ctx *cli.Context) error {
|
||||
if !match {
|
||||
continue
|
||||
}
|
||||
|
||||
matchTables = append(matchTables, item)
|
||||
columns, err := im.FindByTableName(dsn.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, cfg, 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(dsn.DBName, matchTables, cache)
|
||||
return err
|
||||
}
|
||||
|
||||
71
tools/goctl/model/sql/command/command_test.go
Normal file
71
tools/goctl/model/sql/command/command_test.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/config"
|
||||
"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"
|
||||
var cfg = &config.Config{
|
||||
NamingFormat: "gozero",
|
||||
}
|
||||
|
||||
func TestFromDDl(t *testing.T) {
|
||||
err := fromDDl("./user.sql", t.TempDir(), cfg, 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(), cfg, 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(), cfg, true, false)
|
||||
if err != nil {
|
||||
assert.Equal(t, "expected path or path globbing patterns, but nothing found", 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, cfg, true, false)
|
||||
assert.Nil(t, err)
|
||||
|
||||
_, err = os.Stat(filepath.Join(tempDir, "usermodel.go"))
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
@@ -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
|
||||
15
tools/goctl/model/sql/example/makefile
Normal file
15
tools/goctl/model/sql/example/makefile
Normal 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" -cache
|
||||
|
||||
|
||||
# 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 gozero
|
||||
@@ -1,6 +1,7 @@
|
||||
-- 用户表 --
|
||||
CREATE TABLE `user` (
|
||||
`id` bigint(10) NOT NULL AUTO_INCREMENT,
|
||||
`user` varchar(50) NOT NULL DEFAULT '' COMMENT '用户',
|
||||
`name` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户名称',
|
||||
`password` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户密码',
|
||||
`mobile` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '手机号',
|
||||
@@ -10,6 +11,7 @@ CREATE TABLE `user` (
|
||||
`update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `name_index` (`name`),
|
||||
UNIQUE KEY `user_index` (`user`),
|
||||
UNIQUE KEY `mobile_index` (`mobile`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
|
||||
)
|
||||
|
||||
func genDelete(table Table, withCache bool) (string, error) {
|
||||
func genDelete(table Table, withCache bool) (string, string, error) {
|
||||
keySet := collection.NewSet()
|
||||
keyVariableSet := collection.NewSet()
|
||||
for fieldName, key := range table.CacheKey {
|
||||
@@ -24,7 +24,7 @@ func genDelete(table Table, withCache bool) (string, error) {
|
||||
camel := table.Name.ToCamel()
|
||||
text, err := util.LoadTemplate(category, deleteTemplateFile, template.Delete)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
output, err := util.With("delete").
|
||||
@@ -40,7 +40,23 @@ func genDelete(table Table, withCache bool) (string, error) {
|
||||
"keyValues": strings.Join(keyVariableSet.KeysStr(), ", "),
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", "", err
|
||||
}
|
||||
return output.String(), nil
|
||||
|
||||
// interface method
|
||||
text, err = util.LoadTemplate(category, deleteMethodTemplateFile, template.DeleteMethod)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
deleteMethodOut, err := util.With("deleteMethod").
|
||||
Parse(text).
|
||||
Execute(map[string]interface{}{
|
||||
"lowerStartCamelPrimaryKey": stringx.From(table.PrimaryKey.Name.ToCamel()).UnTitle(),
|
||||
"dataType": table.PrimaryKey.DataType,
|
||||
})
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return output.String(), deleteMethodOut.String(), nil
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -6,11 +6,11 @@ import (
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
|
||||
)
|
||||
|
||||
func genFindOne(table Table, withCache bool) (string, error) {
|
||||
func genFindOne(table Table, withCache bool) (string, string, error) {
|
||||
camel := table.Name.ToCamel()
|
||||
text, err := util.LoadTemplate(category, findOneTemplateFile, template.FindOne)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
output, err := util.With("findOne").
|
||||
@@ -26,7 +26,23 @@ func genFindOne(table Table, withCache bool) (string, error) {
|
||||
"cacheKeyVariable": table.CacheKey[table.PrimaryKey.Name.Source()].Variable,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", "", err
|
||||
}
|
||||
return output.String(), nil
|
||||
|
||||
text, err = util.LoadTemplate(category, findOneMethodTemplateFile, template.FindOneMethod)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
findOneMethod, err := util.With("findOneMethod").
|
||||
Parse(text).
|
||||
Execute(map[string]interface{}{
|
||||
"upperStartCamelObject": camel,
|
||||
"lowerStartCamelPrimaryKey": stringx.From(table.PrimaryKey.Name.ToCamel()).UnTitle(),
|
||||
"dataType": table.PrimaryKey.DataType,
|
||||
})
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return output.String(), findOneMethod.String(), nil
|
||||
}
|
||||
|
||||
@@ -9,10 +9,16 @@ import (
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
|
||||
)
|
||||
|
||||
func genFindOneByField(table Table, withCache bool) (string, string, error) {
|
||||
type findOneCode struct {
|
||||
findOneMethod string
|
||||
findOneInterfaceMethod string
|
||||
cacheExtra string
|
||||
}
|
||||
|
||||
func genFindOneByField(table Table, withCache bool) (*findOneCode, error) {
|
||||
text, err := util.LoadTemplate(category, findOneByFieldTemplateFile, template.FindOneByField)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t := util.With("findOneByField").Parse(text)
|
||||
@@ -36,15 +42,40 @@ func genFindOneByField(table Table, withCache bool) (string, string, error) {
|
||||
"originalField": field.Name.Source(),
|
||||
})
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
list = append(list, output.String())
|
||||
}
|
||||
|
||||
text, err = util.LoadTemplate(category, findOneByFieldMethodTemplateFile, template.FindOneByFieldMethod)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t = util.With("findOneByFieldMethod").Parse(text)
|
||||
var listMethod []string
|
||||
for _, field := range table.Fields {
|
||||
if field.IsPrimaryKey || !field.IsUniqueKey {
|
||||
continue
|
||||
}
|
||||
camelFieldName := field.Name.ToCamel()
|
||||
output, err := t.Execute(map[string]interface{}{
|
||||
"upperStartCamelObject": camelTableName,
|
||||
"upperField": camelFieldName,
|
||||
"in": fmt.Sprintf("%s %s", stringx.From(camelFieldName).UnTitle(), field.DataType),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
listMethod = append(listMethod, output.String())
|
||||
}
|
||||
|
||||
if withCache {
|
||||
text, err := util.LoadTemplate(category, findOneByFieldExtraMethodTemplateFile, template.FindOneByFieldExtraMethod)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out, err := util.With("findOneByFieldExtraMethod").Parse(text).Execute(map[string]interface{}{
|
||||
@@ -54,11 +85,18 @@ func genFindOneByField(table Table, withCache bool) (string, string, error) {
|
||||
"originalPrimaryField": table.PrimaryKey.Name.Source(),
|
||||
})
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return strings.Join(list, "\n"), out.String(), nil
|
||||
return &findOneCode{
|
||||
findOneMethod: strings.Join(list, util.NL),
|
||||
findOneInterfaceMethod: strings.Join(listMethod, util.NL),
|
||||
cacheExtra: out.String(),
|
||||
}, nil
|
||||
}
|
||||
return strings.Join(list, "\n"), "", nil
|
||||
|
||||
return &findOneCode{
|
||||
findOneMethod: strings.Join(list, util.NL),
|
||||
findOneInterfaceMethod: strings.Join(listMethod, util.NL),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -7,10 +7,14 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/config"
|
||||
"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"
|
||||
modelutil "github.com/tal-tech/go-zero/tools/goctl/model/sql/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/console"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/format"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
|
||||
)
|
||||
|
||||
@@ -24,27 +28,39 @@ const (
|
||||
|
||||
type (
|
||||
defaultGenerator struct {
|
||||
source string
|
||||
dir string
|
||||
//source string
|
||||
dir string
|
||||
console.Console
|
||||
pkg string
|
||||
namingStyle string
|
||||
pkg string
|
||||
cfg *config.Config
|
||||
}
|
||||
Option func(generator *defaultGenerator)
|
||||
)
|
||||
|
||||
func NewDefaultGenerator(source, dir, namingStyle string, opt ...Option) *defaultGenerator {
|
||||
func NewDefaultGenerator(dir string, cfg *config.Config, 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, cfg: cfg, 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,31 +75,54 @@ 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)
|
||||
name := fmt.Sprintf("%smodel.go", strings.ToLower(tn.ToCamel()))
|
||||
switch g.namingStyle {
|
||||
case NamingCamel:
|
||||
name = fmt.Sprintf("%sModel.go", tn.ToCamel())
|
||||
case NamingSnake:
|
||||
name = fmt.Sprintf("%s_model.go", tn.ToSnake())
|
||||
modelFilename, err := format.FileNamingFormat(g.cfg.NamingFormat, fmt.Sprintf("%s_model", tn.Source()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
name := modelFilename + ".go"
|
||||
filename := filepath.Join(dirAbs, name)
|
||||
if util.FileExists(filename) {
|
||||
g.Warning("%s already exists, ignored.", name)
|
||||
@@ -95,7 +134,12 @@ func (g *defaultGenerator) Start(withCache bool) error {
|
||||
}
|
||||
}
|
||||
// generate error file
|
||||
filename := filepath.Join(dirAbs, "vars.go")
|
||||
varFilename, err := format.FileNamingFormat(g.cfg.NamingFormat, "vars")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
filename := filepath.Join(dirAbs, varFilename+".go")
|
||||
text, err := util.LoadTemplate(category, errTemplateFile, template.Error)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -113,8 +157,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 +183,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)
|
||||
@@ -174,39 +223,41 @@ func (g *defaultGenerator) genModel(in parser.Table, withCache bool) (string, er
|
||||
return "", err
|
||||
}
|
||||
|
||||
typesCode, err := genTypes(table, withCache)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
newCode, err := genNew(table, withCache)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
insertCode, err := genInsert(table, withCache)
|
||||
insertCode, insertCodeMethod, err := genInsert(table, withCache)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var findCode = make([]string, 0)
|
||||
findOneCode, err := genFindOne(table, withCache)
|
||||
findOneCode, findOneCodeMethod, err := genFindOne(table, withCache)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
findOneByFieldCode, extraMethod, err := genFindOneByField(table, withCache)
|
||||
ret, err := genFindOneByField(table, withCache)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
findCode = append(findCode, findOneCode, findOneByFieldCode)
|
||||
updateCode, err := genUpdate(table, withCache)
|
||||
findCode = append(findCode, findOneCode, ret.findOneMethod)
|
||||
updateCode, updateCodeMethod, err := genUpdate(table, withCache)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
deleteCode, err := genDelete(table, withCache)
|
||||
deleteCode, deleteCodeMethod, err := genDelete(table, withCache)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var list []string
|
||||
list = append(list, insertCodeMethod, findOneCodeMethod, ret.findOneInterfaceMethod, updateCodeMethod, deleteCodeMethod)
|
||||
typesCode, err := genTypes(table, strings.Join(modelutil.TrimStringSlice(list), util.NL), withCache)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
newCode, err := genNew(table, withCache)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -221,7 +272,7 @@ func (g *defaultGenerator) genModel(in parser.Table, withCache bool) (string, er
|
||||
"find": strings.Join(findCode, "\n"),
|
||||
"update": updateCode,
|
||||
"delete": deleteCode,
|
||||
"extraMethod": extraMethod,
|
||||
"extraMethod": ret.cacheExtra,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tal-tech/go-zero/core/logx"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/config"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -22,15 +23,23 @@ func TestCacheModel(t *testing.T) {
|
||||
defer func() {
|
||||
_ = os.RemoveAll(dir)
|
||||
}()
|
||||
g := NewDefaultGenerator(source, cacheDir, NamingLower)
|
||||
err := g.Start(true)
|
||||
g, err := NewDefaultGenerator(cacheDir, &config.Config{
|
||||
NamingFormat: "GoZero",
|
||||
})
|
||||
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, &config.Config{
|
||||
NamingFormat: "gozero",
|
||||
})
|
||||
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 +56,23 @@ func TestNamingModel(t *testing.T) {
|
||||
defer func() {
|
||||
_ = os.RemoveAll(dir)
|
||||
}()
|
||||
g := NewDefaultGenerator(source, camelDir, NamingCamel)
|
||||
err := g.Start(true)
|
||||
g, err := NewDefaultGenerator(camelDir, &config.Config{
|
||||
NamingFormat: "GoZero",
|
||||
})
|
||||
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, &config.Config{
|
||||
NamingFormat: "go_zero",
|
||||
})
|
||||
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"))
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
|
||||
)
|
||||
|
||||
func genInsert(table Table, withCache bool) (string, error) {
|
||||
func genInsert(table Table, withCache bool) (string, string, error) {
|
||||
keySet := collection.NewSet()
|
||||
keyVariableSet := collection.NewSet()
|
||||
for fieldName, key := range table.CacheKey {
|
||||
@@ -36,7 +36,7 @@ func genInsert(table Table, withCache bool) (string, error) {
|
||||
camel := table.Name.ToCamel()
|
||||
text, err := util.LoadTemplate(category, insertTemplateFile, template.Insert)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
output, err := util.With("insert").
|
||||
@@ -52,8 +52,23 @@ func genInsert(table Table, withCache bool) (string, error) {
|
||||
"keyValues": strings.Join(keyVariableSet.KeysStr(), ", "),
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
return output.String(), nil
|
||||
// interface method
|
||||
text, err = util.LoadTemplate(category, insertTemplateMethodFile, template.InsertMethod)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
insertMethodOutput, err := util.With("insertMethod").
|
||||
Parse(text).
|
||||
Execute(map[string]interface{}{
|
||||
"upperStartCamelObject": camel,
|
||||
})
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
return output.String(), insertMethodOutput.String(), nil
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package gen
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/model/sql/parser"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
|
||||
@@ -33,8 +34,14 @@ func genCacheKeys(table parser.Table) (map[string]Key, error) {
|
||||
camelFieldName := field.Name.ToCamel()
|
||||
lowerStartCamelFieldName := stringx.From(camelFieldName).UnTitle()
|
||||
left := fmt.Sprintf("cache%s%sPrefix", camelTableName, camelFieldName)
|
||||
if strings.ToLower(camelFieldName) == strings.ToLower(camelTableName) {
|
||||
left = fmt.Sprintf("cache%sPrefix", camelTableName)
|
||||
}
|
||||
right := fmt.Sprintf("cache#%s#%s#", camelTableName, lowerStartCamelFieldName)
|
||||
variable := fmt.Sprintf("%s%sKey", lowerStartCamelTableName, camelFieldName)
|
||||
if strings.ToLower(lowerStartCamelTableName) == strings.ToLower(camelFieldName) {
|
||||
variable = fmt.Sprintf("%sKey", lowerStartCamelTableName)
|
||||
}
|
||||
m[field.Name.Source()] = Key{
|
||||
VarExpression: fmt.Sprintf(`%s = "%s"`, left, right),
|
||||
Left: left,
|
||||
|
||||
@@ -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: "更新时间",
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -22,5 +22,6 @@ func genTag(in string) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return output.String(), nil
|
||||
}
|
||||
|
||||
@@ -11,31 +11,40 @@ import (
|
||||
const (
|
||||
category = "model"
|
||||
deleteTemplateFile = "delete.tpl"
|
||||
deleteMethodTemplateFile = "interface-delete.tpl"
|
||||
fieldTemplateFile = "filed.tpl"
|
||||
findOneTemplateFile = "find-one.tpl"
|
||||
findOneMethodTemplateFile = "interface-find-one.tpl"
|
||||
findOneByFieldTemplateFile = "find-one-by-field.tpl"
|
||||
findOneByFieldMethodTemplateFile = "interface-find-one-by-field.tpl"
|
||||
findOneByFieldExtraMethodTemplateFile = "find-one-by-filed-extra-method.tpl"
|
||||
importsTemplateFile = "import.tpl"
|
||||
importsWithNoCacheTemplateFile = "import-no-cache.tpl"
|
||||
insertTemplateFile = "insert.tpl"
|
||||
insertTemplateMethodFile = "interface-insert.tpl"
|
||||
modelTemplateFile = "model.tpl"
|
||||
modelNewTemplateFile = "model-new.tpl"
|
||||
tagTemplateFile = "tag.tpl"
|
||||
typesTemplateFile = "types.tpl"
|
||||
updateTemplateFile = "update.tpl"
|
||||
updateMethodTemplateFile = "interface-update.tpl"
|
||||
varTemplateFile = "var.tpl"
|
||||
errTemplateFile = "err.tpl"
|
||||
)
|
||||
|
||||
var templates = map[string]string{
|
||||
deleteTemplateFile: template.Delete,
|
||||
deleteMethodTemplateFile: template.DeleteMethod,
|
||||
fieldTemplateFile: template.Field,
|
||||
findOneTemplateFile: template.FindOne,
|
||||
findOneMethodTemplateFile: template.FindOneMethod,
|
||||
findOneByFieldTemplateFile: template.FindOneByField,
|
||||
findOneByFieldMethodTemplateFile: template.FindOneByFieldMethod,
|
||||
findOneByFieldExtraMethodTemplateFile: template.FindOneByFieldExtraMethod,
|
||||
importsTemplateFile: template.Imports,
|
||||
importsWithNoCacheTemplateFile: template.ImportsNoCache,
|
||||
insertTemplateFile: template.Insert,
|
||||
insertTemplateMethodFile: template.InsertMethod,
|
||||
modelTemplateFile: template.Model,
|
||||
modelNewTemplateFile: template.New,
|
||||
tagTemplateFile: template.Tag,
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
)
|
||||
|
||||
func genTypes(table Table, withCache bool) (string, error) {
|
||||
func genTypes(table Table, methods string, withCache bool) (string, error) {
|
||||
fields := table.Fields
|
||||
fieldsString, err := genFields(fields)
|
||||
if err != nil {
|
||||
@@ -21,6 +21,7 @@ func genTypes(table Table, withCache bool) (string, error) {
|
||||
Parse(text).
|
||||
Execute(map[string]interface{}{
|
||||
"withCache": withCache,
|
||||
"method": methods,
|
||||
"upperStartCamelObject": table.Name.ToCamel(),
|
||||
"fields": fieldsString,
|
||||
})
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
|
||||
)
|
||||
|
||||
func genUpdate(table Table, withCache bool) (string, error) {
|
||||
func genUpdate(table Table, withCache bool) (string, string, error) {
|
||||
expressionValues := make([]string, 0)
|
||||
for _, filed := range table.Fields {
|
||||
camel := filed.Name.ToCamel()
|
||||
@@ -24,7 +24,7 @@ func genUpdate(table Table, withCache bool) (string, error) {
|
||||
camelTableName := table.Name.ToCamel()
|
||||
text, err := util.LoadTemplate(category, updateTemplateFile, template.Update)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
output, err := util.With("update").
|
||||
@@ -39,8 +39,23 @@ func genUpdate(table Table, withCache bool) (string, error) {
|
||||
"expressionValues": strings.Join(expressionValues, ", "),
|
||||
})
|
||||
if err != nil {
|
||||
return "", nil
|
||||
return "", "", nil
|
||||
}
|
||||
|
||||
return output.String(), nil
|
||||
// update interface method
|
||||
text, err = util.LoadTemplate(category, updateMethodTemplateFile, template.UpdateMethod)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
updateMethodOutput, err := util.With("updateMethod").
|
||||
Parse(text).
|
||||
Execute(map[string]interface{}{
|
||||
"upperStartCamelObject": camelTableName,
|
||||
})
|
||||
if err != nil {
|
||||
return "", "", nil
|
||||
}
|
||||
|
||||
return output.String(), updateMethodOutput.String(), nil
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,61 @@ 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())
|
||||
for _, item := range table.Fields {
|
||||
if item.Name.Source() == "mobile" {
|
||||
assert.True(t, item.IsUniqueKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package template
|
||||
|
||||
var Delete = `
|
||||
func (m *{{.upperStartCamelObject}}Model) Delete({{.lowerStartCamelPrimaryKey}} {{.dataType}}) error {
|
||||
func (m *default{{.upperStartCamelObject}}Model) Delete({{.lowerStartCamelPrimaryKey}} {{.dataType}}) error {
|
||||
{{if .withCache}}{{if .containsIndexCache}}data, err:=m.FindOne({{.lowerStartCamelPrimaryKey}})
|
||||
if err!=nil{
|
||||
return err
|
||||
@@ -16,3 +16,5 @@ func (m *{{.upperStartCamelObject}}Model) Delete({{.lowerStartCamelPrimaryKey}}
|
||||
return err
|
||||
}
|
||||
`
|
||||
|
||||
var DeleteMethod = `Delete({{.lowerStartCamelPrimaryKey}} {{.dataType}}) error`
|
||||
|
||||
@@ -2,7 +2,7 @@ package template
|
||||
|
||||
// 通过id查询
|
||||
var FindOne = `
|
||||
func (m *{{.upperStartCamelObject}}Model) FindOne({{.lowerStartCamelPrimaryKey}} {{.dataType}}) (*{{.upperStartCamelObject}}, error) {
|
||||
func (m *default{{.upperStartCamelObject}}Model) FindOne({{.lowerStartCamelPrimaryKey}} {{.dataType}}) (*{{.upperStartCamelObject}}, error) {
|
||||
{{if .withCache}}{{.cacheKey}}
|
||||
var resp {{.upperStartCamelObject}}
|
||||
err := m.QueryRow(&resp, {{.cacheKeyVariable}}, func(conn sqlx.SqlConn, v interface{}) error {
|
||||
@@ -32,7 +32,7 @@ func (m *{{.upperStartCamelObject}}Model) FindOne({{.lowerStartCamelPrimaryKey}}
|
||||
|
||||
// 通过指定字段查询
|
||||
var FindOneByField = `
|
||||
func (m *{{.upperStartCamelObject}}Model) FindOneBy{{.upperField}}({{.in}}) (*{{.upperStartCamelObject}}, error) {
|
||||
func (m *default{{.upperStartCamelObject}}Model) FindOneBy{{.upperField}}({{.in}}) (*{{.upperStartCamelObject}}, error) {
|
||||
{{if .withCache}}{{.cacheKey}}
|
||||
var resp {{.upperStartCamelObject}}
|
||||
err := m.QueryRowIndex(&resp, {{.cacheKeyVariable}}, m.formatPrimary, func(conn sqlx.SqlConn, v interface{}) (i interface{}, e error) {
|
||||
@@ -64,12 +64,15 @@ func (m *{{.upperStartCamelObject}}Model) FindOneBy{{.upperField}}({{.in}}) (*{{
|
||||
}{{end}}
|
||||
`
|
||||
var FindOneByFieldExtraMethod = `
|
||||
func (m *{{.upperStartCamelObject}}Model) formatPrimary(primary interface{}) string {
|
||||
func (m *default{{.upperStartCamelObject}}Model) formatPrimary(primary interface{}) string {
|
||||
return fmt.Sprintf("%s%v", {{.primaryKeyLeft}}, primary)
|
||||
}
|
||||
|
||||
func (m *{{.upperStartCamelObject}}Model) queryPrimary(conn sqlx.SqlConn, v, primary interface{}) error {
|
||||
func (m *default{{.upperStartCamelObject}}Model) queryPrimary(conn sqlx.SqlConn, v, primary interface{}) error {
|
||||
query := fmt.Sprintf("select %s from %s where {{.originalPrimaryField}} = ? limit 1", {{.lowerStartCamelObject}}Rows, m.table )
|
||||
return conn.QueryRow(v, query, primary)
|
||||
}
|
||||
`
|
||||
|
||||
var FindOneMethod = `FindOne({{.lowerStartCamelPrimaryKey}} {{.dataType}}) (*{{.upperStartCamelObject}}, error)`
|
||||
var FindOneByFieldMethod = `FindOneBy{{.upperField}}({{.in}}) (*{{.upperStartCamelObject}}, error) `
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package template
|
||||
|
||||
var Insert = `
|
||||
func (m *{{.upperStartCamelObject}}Model) Insert(data {{.upperStartCamelObject}}) (sql.Result,error) {
|
||||
func (m *default{{.upperStartCamelObject}}Model) Insert(data {{.upperStartCamelObject}}) (sql.Result,error) {
|
||||
{{if .withCache}}{{if .containsIndexCache}}{{.keys}}
|
||||
ret, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
|
||||
query := fmt.Sprintf("insert into %s (%s) values ({{.expression}})", m.table, {{.lowerStartCamelObject}}RowsExpectAutoSet)
|
||||
@@ -13,3 +13,5 @@ func (m *{{.upperStartCamelObject}}Model) Insert(data {{.upperStartCamelObject}}
|
||||
return ret,err
|
||||
}
|
||||
`
|
||||
|
||||
var InsertMethod = `Insert(data {{.upperStartCamelObject}}) (sql.Result,error)`
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package template
|
||||
|
||||
var New = `
|
||||
func New{{.upperStartCamelObject}}Model(conn sqlx.SqlConn{{if .withCache}}, c cache.CacheConf{{end}}) *{{.upperStartCamelObject}}Model {
|
||||
return &{{.upperStartCamelObject}}Model{
|
||||
func New{{.upperStartCamelObject}}Model(conn sqlx.SqlConn{{if .withCache}}, c cache.CacheConf{{end}}) {{.upperStartCamelObject}}Model {
|
||||
return &default{{.upperStartCamelObject}}Model{
|
||||
{{if .withCache}}CachedConn: sqlc.NewConn(conn, c){{else}}conn:conn{{end}},
|
||||
table: "{{.table}}",
|
||||
}
|
||||
|
||||
@@ -2,7 +2,11 @@ package template
|
||||
|
||||
var Types = `
|
||||
type (
|
||||
{{.upperStartCamelObject}}Model struct {
|
||||
{{.upperStartCamelObject}}Model interface{
|
||||
{{.method}}
|
||||
}
|
||||
|
||||
default{{.upperStartCamelObject}}Model struct {
|
||||
{{if .withCache}}sqlc.CachedConn{{else}}conn sqlx.SqlConn{{end}}
|
||||
table string
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package template
|
||||
|
||||
var Update = `
|
||||
func (m *{{.upperStartCamelObject}}Model) Update(data {{.upperStartCamelObject}}) error {
|
||||
func (m *default{{.upperStartCamelObject}}Model) Update(data {{.upperStartCamelObject}}) error {
|
||||
{{if .withCache}}{{.primaryCacheKey}}
|
||||
_, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
|
||||
query := fmt.Sprintf("update %s set %s where {{.originalPrimaryKey}} = ?", m.table, {{.lowerStartCamelObject}}RowsWithPlaceHolder)
|
||||
@@ -11,3 +11,5 @@ func (m *{{.upperStartCamelObject}}Model) Update(data {{.upperStartCamelObject}}
|
||||
return err
|
||||
}
|
||||
`
|
||||
|
||||
var UpdateMethod = `Update(data {{.upperStartCamelObject}}) error`
|
||||
|
||||
12
tools/goctl/model/sql/util/slice.go
Normal file
12
tools/goctl/model/sql/util/slice.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package util
|
||||
|
||||
func TrimStringSlice(list []string) []string {
|
||||
var out []string
|
||||
for _, item := range list {
|
||||
if len(item) == 0 {
|
||||
continue
|
||||
}
|
||||
out = append(out, item)
|
||||
}
|
||||
return out
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user