mirror of
https://github.com/zeromicro/go-zero.git
synced 2026-05-11 16:59:59 +08:00
Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a13b48c33e | ||
|
|
033525fea8 | ||
|
|
607fc3297a | ||
|
|
4287877b74 | ||
|
|
2b7545ce11 | ||
|
|
60925c1164 | ||
|
|
1c9e81aa28 | ||
|
|
db7dcaa120 | ||
|
|
099d44054d | ||
|
|
f5f873c6bd | ||
|
|
6dbd3eada9 | ||
|
|
cf2d20a211 | ||
|
|
91bfc093f4 | ||
|
|
cf33aae91d | ||
|
|
c9494c8bc7 | ||
|
|
1fd2ef9347 | ||
|
|
efffb40fa3 | ||
|
|
9c8f31cf83 | ||
|
|
96cb7af728 | ||
|
|
41964f9d52 | ||
|
|
fe0d0687f5 | ||
|
|
1c1e4bca86 | ||
|
|
1abe21aa2a | ||
|
|
cee170f3e9 | ||
|
|
907efd92c9 | ||
|
|
737cd4751a | ||
|
|
dfe6e88529 | ||
|
|
85a815bea0 | ||
|
|
aa3c391919 | ||
|
|
c9b0ac1ee4 | ||
|
|
33faab61a3 |
@@ -2,18 +2,18 @@ package bloom
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/alicebob/miniredis"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tal-tech/go-zero/core/lang"
|
||||
"github.com/tal-tech/go-zero/core/stores/redis"
|
||||
)
|
||||
|
||||
func TestRedisBitSet_New_Set_Test(t *testing.T) {
|
||||
s, err := miniredis.Run()
|
||||
if err != nil {
|
||||
t.Error("Miniredis could not start")
|
||||
}
|
||||
defer s.Close()
|
||||
s, clean, err := createMiniRedis()
|
||||
assert.Nil(t, err)
|
||||
defer clean()
|
||||
|
||||
store := redis.NewRedis(s.Addr(), redis.NodeType)
|
||||
bitSet := newRedisBitSet(store, "test_key", 1024)
|
||||
@@ -46,11 +46,9 @@ func TestRedisBitSet_New_Set_Test(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRedisBitSet_Add(t *testing.T) {
|
||||
s, err := miniredis.Run()
|
||||
if err != nil {
|
||||
t.Error("Miniredis could not start")
|
||||
}
|
||||
defer s.Close()
|
||||
s, clean, err := createMiniRedis()
|
||||
assert.Nil(t, err)
|
||||
defer clean()
|
||||
|
||||
store := redis.NewRedis(s.Addr(), redis.NodeType)
|
||||
filter := New(store, "test_key", 64)
|
||||
@@ -60,3 +58,22 @@ func TestRedisBitSet_Add(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, ok)
|
||||
}
|
||||
|
||||
func createMiniRedis() (r *miniredis.Miniredis, clean func(), err error) {
|
||||
r, err = miniredis.Run()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return r, func() {
|
||||
ch := make(chan lang.PlaceholderType)
|
||||
go func() {
|
||||
r.Close()
|
||||
close(ch)
|
||||
}()
|
||||
select {
|
||||
case <-ch:
|
||||
case <-time.After(time.Second):
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
11
core/errorx/callchain.go
Normal file
11
core/errorx/callchain.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package errorx
|
||||
|
||||
func Chain(fns ...func() error) error {
|
||||
for _, fn := range fns {
|
||||
if err := fn(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
27
core/errorx/callchain_test.go
Normal file
27
core/errorx/callchain_test.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package errorx
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestChain(t *testing.T) {
|
||||
var errDummy = errors.New("dummy")
|
||||
assert.Nil(t, Chain(func() error {
|
||||
return nil
|
||||
}, func() error {
|
||||
return nil
|
||||
}))
|
||||
assert.Equal(t, errDummy, Chain(func() error {
|
||||
return errDummy
|
||||
}, func() error {
|
||||
return nil
|
||||
}))
|
||||
assert.Equal(t, errDummy, Chain(func() error {
|
||||
return nil
|
||||
}, func() error {
|
||||
return errDummy
|
||||
}))
|
||||
}
|
||||
@@ -73,6 +73,7 @@ type (
|
||||
ZrevrangebyscoreWithScores(key string, start, stop int64) ([]redis.Pair, error)
|
||||
ZrevrangebyscoreWithScoresAndLimit(key string, start, stop int64, page, size int) ([]redis.Pair, error)
|
||||
Zscore(key string, value string) (int64, error)
|
||||
Zrevrank(key, field string) (int64, error)
|
||||
}
|
||||
|
||||
clusterStore struct {
|
||||
@@ -635,6 +636,15 @@ func (cs clusterStore) ZrevrangebyscoreWithScoresAndLimit(key string, start, sto
|
||||
return node.ZrevrangebyscoreWithScoresAndLimit(key, start, stop, page, size)
|
||||
}
|
||||
|
||||
func (cs clusterStore) Zrevrank(key, field string) (int64, error) {
|
||||
node, err := cs.getRedis(key)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return node.Zrevrank(key, field)
|
||||
}
|
||||
|
||||
func (cs clusterStore) Zscore(key string, value string) (int64, error) {
|
||||
node, err := cs.getRedis(key)
|
||||
if err != nil {
|
||||
|
||||
@@ -516,6 +516,8 @@ func TestRedis_SortedSet(t *testing.T) {
|
||||
assert.NotNil(t, err)
|
||||
_, err = store.ZrevrangebyscoreWithScoresAndLimit("key", 5, 8, 1, 1)
|
||||
assert.NotNil(t, err)
|
||||
_, err = store.Zrevrank("key", "value")
|
||||
assert.NotNil(t, err)
|
||||
_, err = store.Zadds("key", redis.Pair{
|
||||
Key: "value2",
|
||||
Score: 6,
|
||||
@@ -640,6 +642,9 @@ func TestRedis_SortedSet(t *testing.T) {
|
||||
Score: 5,
|
||||
},
|
||||
}, pairs)
|
||||
rank, err = client.Zrevrank("key", "value1")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, int64(1), rank)
|
||||
val, err = client.Zadds("key", redis.Pair{
|
||||
Key: "value2",
|
||||
Score: 6,
|
||||
|
||||
@@ -1273,6 +1273,20 @@ func (s *Redis) ZrevrangebyscoreWithScoresAndLimit(key string, start, stop int64
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Redis) Zrevrank(key string, field string) (val int64, err error) {
|
||||
err = s.brk.DoWithAcceptable(func() error {
|
||||
conn, err := getRedis(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
val, err = conn.ZRevRank(key, field).Result()
|
||||
return err
|
||||
}, acceptable)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Redis) String() string {
|
||||
return s.Addr
|
||||
}
|
||||
|
||||
@@ -584,6 +584,9 @@ func TestRedis_SortedSet(t *testing.T) {
|
||||
rank, err := client.Zrank("key", "value2")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, int64(1), rank)
|
||||
rank, err = client.Zrevrank("key", "value1")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, int64(2), rank)
|
||||
_, err = NewRedis(client.Addr, "").Zrank("key", "value4")
|
||||
assert.NotNil(t, err)
|
||||
_, err = client.Zrank("key", "value4")
|
||||
@@ -710,6 +713,8 @@ func TestRedis_SortedSet(t *testing.T) {
|
||||
pairs, err = client.ZrevrangebyscoreWithScoresAndLimit("key", 5, 8, 1, 0)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, len(pairs))
|
||||
_, err = NewRedis(client.Addr, "").Zrevrank("key", "value")
|
||||
assert.NotNil(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
|
||||
"github.com/alicebob/miniredis"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tal-tech/go-zero/core/lang"
|
||||
"github.com/tal-tech/go-zero/core/logx"
|
||||
"github.com/tal-tech/go-zero/core/stat"
|
||||
"github.com/tal-tech/go-zero/core/stores/cache"
|
||||
@@ -30,10 +31,9 @@ func init() {
|
||||
|
||||
func TestCachedConn_GetCache(t *testing.T) {
|
||||
resetStats()
|
||||
s, err := miniredis.Run()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
s, clean, err := createMiniRedis()
|
||||
assert.Nil(t, err)
|
||||
defer clean()
|
||||
|
||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
||||
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10))
|
||||
@@ -48,10 +48,9 @@ func TestCachedConn_GetCache(t *testing.T) {
|
||||
|
||||
func TestStat(t *testing.T) {
|
||||
resetStats()
|
||||
s, err := miniredis.Run()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
s, clean, err := createMiniRedis()
|
||||
assert.Nil(t, err)
|
||||
defer clean()
|
||||
|
||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
||||
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10))
|
||||
@@ -73,10 +72,9 @@ func TestStat(t *testing.T) {
|
||||
|
||||
func TestCachedConn_QueryRowIndex_NoCache(t *testing.T) {
|
||||
resetStats()
|
||||
s, err := miniredis.Run()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
s, clean, err := createMiniRedis()
|
||||
assert.Nil(t, err)
|
||||
defer clean()
|
||||
|
||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
||||
c := NewConn(dummySqlConn{}, cache.CacheConf{
|
||||
@@ -124,10 +122,9 @@ func TestCachedConn_QueryRowIndex_NoCache(t *testing.T) {
|
||||
|
||||
func TestCachedConn_QueryRowIndex_HasCache(t *testing.T) {
|
||||
resetStats()
|
||||
s, err := miniredis.Run()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
s, clean, err := createMiniRedis()
|
||||
assert.Nil(t, err)
|
||||
defer clean()
|
||||
|
||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
||||
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10),
|
||||
@@ -213,11 +210,9 @@ func TestCachedConn_QueryRowIndex_HasCache_IntPrimary(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
s, err := miniredis.Run()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
defer s.Close()
|
||||
s, clean, err := createMiniRedis()
|
||||
assert.Nil(t, err)
|
||||
defer clean()
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
@@ -261,12 +256,9 @@ func TestCachedConn_QueryRowIndex_HasWrongCache(t *testing.T) {
|
||||
for k, v := range caches {
|
||||
t.Run(k+"/"+v, func(t *testing.T) {
|
||||
resetStats()
|
||||
s, err := miniredis.Run()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
s.FlushAll()
|
||||
defer s.Close()
|
||||
s, clean, err := createMiniRedis()
|
||||
assert.Nil(t, err)
|
||||
defer clean()
|
||||
|
||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
||||
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10),
|
||||
@@ -320,10 +312,9 @@ func TestStatCacheFails(t *testing.T) {
|
||||
|
||||
func TestStatDbFails(t *testing.T) {
|
||||
resetStats()
|
||||
s, err := miniredis.Run()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
s, clean, err := createMiniRedis()
|
||||
assert.Nil(t, err)
|
||||
defer clean()
|
||||
|
||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
||||
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10))
|
||||
@@ -343,10 +334,9 @@ func TestStatDbFails(t *testing.T) {
|
||||
|
||||
func TestStatFromMemory(t *testing.T) {
|
||||
resetStats()
|
||||
s, err := miniredis.Run()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
s, clean, err := createMiniRedis()
|
||||
assert.Nil(t, err)
|
||||
defer clean()
|
||||
|
||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
||||
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10))
|
||||
@@ -403,10 +393,9 @@ func TestStatFromMemory(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCachedConnQueryRow(t *testing.T) {
|
||||
s, err := miniredis.Run()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
s, clean, err := createMiniRedis()
|
||||
assert.Nil(t, err)
|
||||
defer clean()
|
||||
|
||||
const (
|
||||
key = "user"
|
||||
@@ -433,10 +422,9 @@ func TestCachedConnQueryRow(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCachedConnQueryRowFromCache(t *testing.T) {
|
||||
s, err := miniredis.Run()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
s, clean, err := createMiniRedis()
|
||||
assert.Nil(t, err)
|
||||
defer clean()
|
||||
|
||||
const (
|
||||
key = "user"
|
||||
@@ -464,10 +452,9 @@ func TestCachedConnQueryRowFromCache(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestQueryRowNotFound(t *testing.T) {
|
||||
s, err := miniredis.Run()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
s, clean, err := createMiniRedis()
|
||||
assert.Nil(t, err)
|
||||
defer clean()
|
||||
|
||||
const key = "user"
|
||||
var conn trackedConn
|
||||
@@ -486,10 +473,9 @@ func TestQueryRowNotFound(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCachedConnExec(t *testing.T) {
|
||||
s, err := miniredis.Run()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
s, clean, err := createMiniRedis()
|
||||
assert.Nil(t, err)
|
||||
defer clean()
|
||||
|
||||
var conn trackedConn
|
||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
||||
@@ -500,10 +486,9 @@ func TestCachedConnExec(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCachedConnExecDropCache(t *testing.T) {
|
||||
s, err := miniredis.Run()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
s, clean, err := createMiniRedis()
|
||||
assert.Nil(t, err)
|
||||
defer clean()
|
||||
|
||||
const (
|
||||
key = "user"
|
||||
@@ -539,10 +524,9 @@ func TestCachedConnExecDropCacheFailed(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCachedConnQueryRows(t *testing.T) {
|
||||
s, err := miniredis.Run()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
s, clean, err := createMiniRedis()
|
||||
assert.Nil(t, err)
|
||||
defer clean()
|
||||
|
||||
var conn trackedConn
|
||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
||||
@@ -554,10 +538,9 @@ func TestCachedConnQueryRows(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCachedConnTransact(t *testing.T) {
|
||||
s, err := miniredis.Run()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
s, clean, err := createMiniRedis()
|
||||
assert.Nil(t, err)
|
||||
defer clean()
|
||||
|
||||
var conn trackedConn
|
||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
||||
@@ -570,10 +553,9 @@ func TestCachedConnTransact(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestQueryRowNoCache(t *testing.T) {
|
||||
s, err := miniredis.Run()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
s, clean, err := createMiniRedis()
|
||||
assert.Nil(t, err)
|
||||
defer clean()
|
||||
|
||||
const (
|
||||
key = "user"
|
||||
@@ -657,3 +639,22 @@ func (c *trackedConn) Transact(fn func(session sqlx.Session) error) error {
|
||||
c.transactValue = true
|
||||
return c.dummySqlConn.Transact(fn)
|
||||
}
|
||||
|
||||
func createMiniRedis() (r *miniredis.Miniredis, clean func(), err error) {
|
||||
r, err = miniredis.Run()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return r, func() {
|
||||
ch := make(chan lang.PlaceholderType)
|
||||
go func() {
|
||||
r.Close()
|
||||
close(ch)
|
||||
}()
|
||||
select {
|
||||
case <-ch:
|
||||
case <-time.After(time.Second):
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ func main() {
|
||||
Port: *port,
|
||||
Timeout: *timeout,
|
||||
MaxConns: 500,
|
||||
})
|
||||
}, rest.WithNotAllowedHandler(rest.CorsHandler()))
|
||||
defer engine.Stop()
|
||||
|
||||
engine.Use(first)
|
||||
|
||||
10
readme-en.md
10
readme-en.md
@@ -1,3 +1,5 @@
|
||||
<img align="right" width="150px" src="https://raw.githubusercontent.com/tal-tech/zero-doc/main/doc/images/go-zero.png">
|
||||
|
||||
# go-zero
|
||||
|
||||
English | [简体中文](readme.md)
|
||||
@@ -23,7 +25,7 @@ Advantages of go-zero:
|
||||
* auto validate the request parameters from clients
|
||||
* plenty of builtin microservice management and concurrent toolkits
|
||||
|
||||
<img src="https://github.com/tal-tech/zero-doc/blob/main/doc/images/architecture-en.png" alt="Architecture" width="1500" />
|
||||
<img src="https://raw.githubusercontent.com/tal-tech/zero-doc/main/doc/images/architecture-en.png" alt="Architecture" width="1500" />
|
||||
|
||||
## 1. Backgrounds of go-zero
|
||||
|
||||
@@ -74,7 +76,7 @@ go-zero is a web and rpc framework that integrates lots of engineering practices
|
||||
|
||||
As below, go-zero protects the system with couple layers and mechanisms:
|
||||
|
||||

|
||||

|
||||
|
||||
## 4. Future development plans of go-zero
|
||||
|
||||
@@ -164,6 +166,8 @@ go get -u github.com/tal-tech/go-zero
|
||||
|
||||
```shell
|
||||
cd greet
|
||||
go mod init
|
||||
go mod tidy
|
||||
go run greet.go -f etc/greet-api.yaml
|
||||
```
|
||||
|
||||
@@ -198,7 +202,7 @@ go get -u github.com/tal-tech/go-zero
|
||||
|
||||
## 7. Benchmark
|
||||
|
||||

|
||||

|
||||
|
||||
[Checkout the test code](https://github.com/smallnest/go-web-framework-benchmark)
|
||||
|
||||
|
||||
129
readme.md
129
readme.md
@@ -1,3 +1,5 @@
|
||||
<img align="right" width="150px" src="https://raw.githubusercontent.com/tal-tech/zero-doc/main/doc/images/go-zero.png">
|
||||
|
||||
# go-zero
|
||||
|
||||
[English](readme-en.md) | 简体中文
|
||||
@@ -8,28 +10,28 @@
|
||||
[](https://github.com/tal-tech/go-zero)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
|
||||
## 0. go-zero介绍
|
||||
## 0. go-zero 介绍
|
||||
|
||||
go-zero是一个集成了各种工程实践的web和rpc框架。通过弹性设计保障了大并发服务端的稳定性,经受了充分的实战检验。
|
||||
go-zero 是一个集成了各种工程实践的 web 和 rpc 框架。通过弹性设计保障了大并发服务端的稳定性,经受了充分的实战检验。
|
||||
|
||||
go-zero 包含极简的 API 定义和生成工具 goctl,可以根据定义的 api 文件一键生成 Go, iOS, Android, Kotlin, Dart, TypeScript, JavaScript 代码,并可直接运行。
|
||||
|
||||
使用go-zero的好处:
|
||||
使用 go-zero 的好处:
|
||||
|
||||
* 轻松获得支撑千万日活服务的稳定性
|
||||
* 内建级联超时控制、限流、自适应熔断、自适应降载等微服务治理能力,无需配置和额外代码
|
||||
* 微服务治理中间件可无缝集成到其它现有框架使用
|
||||
* 极简的API描述,一键生成各端代码
|
||||
* 极简的 API 描述,一键生成各端代码
|
||||
* 自动校验客户端请求参数合法性
|
||||
* 大量微服务治理和并发工具包
|
||||
|
||||
<img src="https://github.com/tal-tech/zero-doc/blob/main/doc/images/architecture.png" alt="架构图" width="1500" />
|
||||
<img src="https://raw.githubusercontent.com/tal-tech/zero-doc/main/doc/images/architecture.png" alt="架构图" width="1500" />
|
||||
|
||||
## 1. go-zero框架背景
|
||||
## 1. go-zero 框架背景
|
||||
|
||||
18年初,我们决定从`Java+MongoDB`的单体架构迁移到微服务架构,经过仔细思考和对比,我们决定:
|
||||
18 年初,我们决定从 `Java+MongoDB` 的单体架构迁移到微服务架构,经过仔细思考和对比,我们决定:
|
||||
|
||||
* 基于Go语言
|
||||
* 基于 Go 语言
|
||||
* 高效的性能
|
||||
* 简洁的语法
|
||||
* 广泛验证的工程效率
|
||||
@@ -40,7 +42,7 @@ go-zero 包含极简的 API 定义和生成工具 goctl,可以根据定义的
|
||||
* 需要有更快速的问题定位能力
|
||||
* 更便捷的增加新特性
|
||||
|
||||
## 2. go-zero框架设计思考
|
||||
## 2. go-zero 框架设计思考
|
||||
|
||||
对于微服务框架的设计,我们期望保障微服务稳定性的同时,也要特别注重研发效率。所以设计之初,我们就有如下一些准则:
|
||||
|
||||
@@ -53,21 +55,21 @@ go-zero 包含极简的 API 定义和生成工具 goctl,可以根据定义的
|
||||
* 对业务开发友好,封装复杂度
|
||||
* 约束做一件事只有一种方式
|
||||
|
||||
我们经历不到半年时间,彻底完成了从`Java+MongoDB`到`Golang+MySQL`为主的微服务体系迁移,并于18年8月底完全上线,稳定保障了业务后续迅速增长,确保了整个服务的高可用。
|
||||
我们经历不到半年时间,彻底完成了从 `Java+MongoDB` 到 `Golang+MySQL` 为主的微服务体系迁移,并于 18 年 8 月底完全上线,稳定保障了业务后续迅速增长,确保了整个服务的高可用。
|
||||
|
||||
## 3. go-zero项目实现和特点
|
||||
## 3. go-zero 项目实现和特点
|
||||
|
||||
go-zero是一个集成了各种工程实践的包含web和rpc框架,有如下主要特点:
|
||||
go-zero 是一个集成了各种工程实践的包含 web 和 rpc 框架,有如下主要特点:
|
||||
|
||||
* 强大的工具支持,尽可能少的代码编写
|
||||
* 极简的接口
|
||||
* 完全兼容net/http
|
||||
* 完全兼容 net/http
|
||||
* 支持中间件,方便扩展
|
||||
* 高性能
|
||||
* 面向故障编程,弹性设计
|
||||
* 内建服务发现、负载均衡
|
||||
* 内建限流、熔断、降载,且自动触发,自动恢复
|
||||
* API参数自动校验
|
||||
* API 参数自动校验
|
||||
* 超时级联控制
|
||||
* 自动缓存控制
|
||||
* 链路跟踪、统计报警等
|
||||
@@ -75,7 +77,7 @@ go-zero是一个集成了各种工程实践的包含web和rpc框架,有如下
|
||||
|
||||
如下图,我们从多个层面保障了整体服务的高可用:
|
||||
|
||||

|
||||

|
||||
|
||||
## 4. Installation
|
||||
|
||||
@@ -91,88 +93,95 @@ GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/tal-tech/
|
||||
|
||||
[快速构建高并发微服务](https://github.com/tal-tech/zero-doc/blob/main/doc/shorturl.md)
|
||||
|
||||
[快速构建高并发微服务-多RPC版](https://github.com/tal-tech/zero-doc/blob/main/doc/bookstore.md)
|
||||
[快速构建高并发微服务 - 多 RPC 版](https://github.com/tal-tech/zero-doc/blob/main/doc/bookstore.md)
|
||||
|
||||
1. 安装goctl工具
|
||||
1. 安装 goctl 工具
|
||||
|
||||
`goctl`读作`go control`,不要读成`go C-T-L`。`goctl`的意思是不要被代码控制,而是要去控制它。其中的`go`不是指`golang`。在设计`goctl`之初,我就希望通过`她`来解放我们的双手👈
|
||||
`goctl` 读作 `go control`,不要读成 `go C-T-L`。`goctl` 的意思是不要被代码控制,而是要去控制它。其中的 `go` 不是指 `golang`。在设计 `goctl` 之初,我就希望通过 ` 她 ` 来解放我们的双手👈
|
||||
|
||||
```shell
|
||||
GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/tal-tech/go-zero/tools/goctl
|
||||
```
|
||||
```shell
|
||||
GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/tal-tech/go-zero/tools/goctl
|
||||
```
|
||||
|
||||
确保goctl可执行
|
||||
确保 goctl 可执行
|
||||
|
||||
2. 快速生成api服务
|
||||
2. 快速生成 api 服务
|
||||
|
||||
```shell
|
||||
goctl api new greet
|
||||
cd greet
|
||||
go run greet.go -f etc/greet-api.yaml
|
||||
```
|
||||
```shell
|
||||
goctl api new greet
|
||||
cd greet
|
||||
go mod init
|
||||
go mod tidy
|
||||
go run greet.go -f etc/greet-api.yaml
|
||||
```
|
||||
|
||||
默认侦听在8888端口(可以在配置文件里修改),可以通过curl请求:
|
||||
默认侦听在 8888 端口(可以在配置文件里修改),可以通过 curl 请求:
|
||||
|
||||
```shell
|
||||
curl -i http://localhost:8888/greet/from/you
|
||||
```
|
||||
```shell
|
||||
curl -i http://localhost:8888/greet/from/you
|
||||
```
|
||||
|
||||
返回如下:
|
||||
返回如下:
|
||||
|
||||
```http
|
||||
HTTP/1.1 200 OK
|
||||
Date: Sun, 30 Aug 2020 15:32:35 GMT
|
||||
Content-Length: 0
|
||||
```
|
||||
```http
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
Date: Thu, 22 Oct 2020 14:03:18 GMT
|
||||
Content-Length: 14
|
||||
|
||||
编写业务代码:
|
||||
{"message":""}
|
||||
```
|
||||
|
||||
* api文件定义了服务对外暴露的路由,可参考[api规范](https://github.com/tal-tech/zero-doc/blob/main/doc/goctl.md)
|
||||
* 可以在servicecontext.go里面传递依赖给logic,比如mysql, redis等
|
||||
* 在api定义的get/post/put/delete等请求对应的logic里增加业务处理逻辑
|
||||
编写业务代码:
|
||||
|
||||
3. 可以根据api文件生成前端需要的Java, TypeScript, Dart, JavaScript代码
|
||||
* api 文件定义了服务对外暴露的路由,可参考 [api 规范](https://github.com/tal-tech/zero-doc/blob/main/doc/goctl.md)
|
||||
* 可以在 servicecontext.go 里面传递依赖给 logic,比如 mysql, redis 等
|
||||
* 在 api 定义的 get/post/put/delete 等请求对应的 logic 里增加业务处理逻辑
|
||||
|
||||
```shell
|
||||
goctl api java -api greet.api -dir greet
|
||||
goctl api dart -api greet.api -dir greet
|
||||
...
|
||||
```
|
||||
3. 可以根据 api 文件生成前端需要的 Java, TypeScript, Dart, JavaScript 代码
|
||||
|
||||
```shell
|
||||
goctl api java -api greet.api -dir greet
|
||||
goctl api dart -api greet.api -dir greet
|
||||
...
|
||||
```
|
||||
|
||||
## 6. Benchmark
|
||||
|
||||

|
||||

|
||||
|
||||
[测试代码见这里](https://github.com/smallnest/go-web-framework-benchmark)
|
||||
|
||||
## 7. 文档
|
||||
|
||||
* API文档 (逐步完善中)
|
||||
* API 文档 (逐步完善中)
|
||||
|
||||
[https://www.yuque.com/tal-tech/go-zero](https://www.yuque.com/tal-tech/go-zero)
|
||||
|
||||
* awesome系列
|
||||
* awesome 系列
|
||||
* [快速构建高并发微服务](https://github.com/tal-tech/zero-doc/blob/main/doc/shorturl.md)
|
||||
* [快速构建高并发微服务-多RPC版](https://github.com/tal-tech/zero-doc/blob/main/doc/bookstore.md)
|
||||
* [goctl使用帮助](https://github.com/tal-tech/zero-doc/blob/main/doc/goctl.md)
|
||||
* [通过MapReduce降低服务响应时间](https://github.com/tal-tech/zero-doc/blob/main/doc/mapreduce.md)
|
||||
* [快速构建高并发微服务 - 多 RPC 版](https://github.com/tal-tech/zero-doc/blob/main/doc/bookstore.md)
|
||||
* [goctl 使用帮助](https://github.com/tal-tech/zero-doc/blob/main/doc/goctl.md)
|
||||
* [通过 MapReduce 降低服务响应时间](https://github.com/tal-tech/zero-doc/blob/main/doc/mapreduce.md)
|
||||
* [关键字替换和敏感词过滤工具](https://github.com/tal-tech/zero-doc/blob/main/doc/keywords.md)
|
||||
* [进程内缓存使用方法](https://github.com/tal-tech/zero-doc/blob/main/doc/collection.md)
|
||||
* [防止缓存击穿之进程内共享调用](https://github.com/tal-tech/zero-doc/blob/main/doc/sharedcalls.md)
|
||||
* [基于prometheus的微服务指标监控](https://github.com/tal-tech/zero-doc/blob/main/doc/metric.md)
|
||||
* [基于 prometheus 的微服务指标监控](https://github.com/tal-tech/zero-doc/blob/main/doc/metric.md)
|
||||
* [文本序列化和反序列化](https://github.com/tal-tech/zero-doc/blob/main/doc/mapping.md)
|
||||
* [快速构建jwt鉴权认证](https://github.com/tal-tech/zero-doc/blob/main/doc/jwt.md)
|
||||
* [快速构建 jwt 鉴权认证](https://github.com/tal-tech/zero-doc/blob/main/doc/jwt.md)
|
||||
|
||||
## 9. 微信交流群
|
||||
|
||||
加群之前有劳给一个star,一个小小的star是作者们回答海量问题的动力。
|
||||
加群之前有劳给一个 star,一个小小的 star 是作者们回答海量问题的动力。
|
||||
|
||||
如果文档中未能覆盖的任何疑问,欢迎您在群里提出,我们会尽快答复。
|
||||
|
||||
您可以在群内提出使用中需要改进的地方,我们会考虑合理性并尽快修改。
|
||||
|
||||
如果您发现bug请及时提issue,我们会尽快确认并修改。
|
||||
如果您发现 bug 请及时提 issue,我们会尽快确认并修改。
|
||||
|
||||
扫码后请加群主,便于我邀请您进讨论群,并请退出扫码网关群,谢谢!
|
||||
<!-- 扫码后请加群主,便于我邀请您进讨论群,并请退出扫码网关群,谢谢!-->
|
||||
|
||||
<img src="https://raw.githubusercontent.com/tal-tech/zero-doc/main/doc/images/wechat.jpg" alt="wechat" width="300" />
|
||||
开源中国年度评选,给go-zero投上一票:[https://www.oschina.net/p/go-zero](https://www.oschina.net/p/go-zero)
|
||||
|
||||
<img src="https://raw.githubusercontent.com/tal-tech/zero-doc/main/doc/images/wechat.jpg" alt="wechat" width="300" />
|
||||
@@ -57,7 +57,7 @@ func (s *engine) SetUnsignedCallback(callback handler.UnsignedCallback) {
|
||||
}
|
||||
|
||||
func (s *engine) Start() error {
|
||||
return s.StartWithRouter(router.NewPatRouter())
|
||||
return s.StartWithRouter(router.NewRouter())
|
||||
}
|
||||
|
||||
func (s *engine) StartWithRouter(router httpx.Router) error {
|
||||
|
||||
27
rest/handlers.go
Normal file
27
rest/handlers.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package rest
|
||||
|
||||
import "net/http"
|
||||
|
||||
const (
|
||||
allowOrigin = "Access-Control-Allow-Origin"
|
||||
allOrigins = "*"
|
||||
allowMethods = "Access-Control-Allow-Methods"
|
||||
allowHeaders = "Access-Control-Allow-Headers"
|
||||
headers = "Content-Type, Content-Length, Origin"
|
||||
methods = "GET, HEAD, POST, PATCH, PUT, DELETE"
|
||||
)
|
||||
|
||||
// CorsHandler handles cross domain OPTIONS requests.
|
||||
// At most one origin can be specified, other origins are ignored if given.
|
||||
func CorsHandler(origins ...string) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if len(origins) > 0 {
|
||||
w.Header().Set(allowOrigin, origins[0])
|
||||
} else {
|
||||
w.Header().Set(allowOrigin, allOrigins)
|
||||
}
|
||||
w.Header().Set(allowMethods, methods)
|
||||
w.Header().Set(allowHeaders, headers)
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
})
|
||||
}
|
||||
42
rest/handlers_test.go
Normal file
42
rest/handlers_test.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCorsHandlerWithOrigins(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
origins []string
|
||||
expect string
|
||||
}{
|
||||
{
|
||||
name: "allow all origins",
|
||||
expect: allOrigins,
|
||||
},
|
||||
{
|
||||
name: "allow one origin",
|
||||
origins: []string{"local"},
|
||||
expect: "local",
|
||||
},
|
||||
{
|
||||
name: "allow many origins",
|
||||
origins: []string{"local", "remote"},
|
||||
expect: "local",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
handler := CorsHandler(test.origins...)
|
||||
handler.ServeHTTP(w, nil)
|
||||
assert.Equal(t, http.StatusNoContent, w.Result().StatusCode)
|
||||
assert.Equal(t, test.expect, w.Header().Get(allowOrigin))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -6,4 +6,5 @@ type Router interface {
|
||||
http.Handler
|
||||
Handle(method string, path string, handler http.Handler) error
|
||||
SetNotFoundHandler(handler http.Handler)
|
||||
SetNotAllowedHandler(handler http.Handler)
|
||||
}
|
||||
|
||||
@@ -21,18 +21,19 @@ var (
|
||||
ErrInvalidPath = errors.New("path must begin with '/'")
|
||||
)
|
||||
|
||||
type PatRouter struct {
|
||||
trees map[string]*search.Tree
|
||||
notFound http.Handler
|
||||
type patRouter struct {
|
||||
trees map[string]*search.Tree
|
||||
notFound http.Handler
|
||||
notAllowed http.Handler
|
||||
}
|
||||
|
||||
func NewPatRouter() httpx.Router {
|
||||
return &PatRouter{
|
||||
func NewRouter() httpx.Router {
|
||||
return &patRouter{
|
||||
trees: make(map[string]*search.Tree),
|
||||
}
|
||||
}
|
||||
|
||||
func (pr *PatRouter) Handle(method, reqPath string, handler http.Handler) error {
|
||||
func (pr *patRouter) Handle(method, reqPath string, handler http.Handler) error {
|
||||
if !validMethod(method) {
|
||||
return ErrInvalidMethod
|
||||
}
|
||||
@@ -51,7 +52,7 @@ func (pr *PatRouter) Handle(method, reqPath string, handler http.Handler) error
|
||||
}
|
||||
}
|
||||
|
||||
func (pr *PatRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
func (pr *patRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
reqPath := path.Clean(r.URL.Path)
|
||||
if tree, ok := pr.trees[r.Method]; ok {
|
||||
if result, ok := tree.Search(reqPath); ok {
|
||||
@@ -63,19 +64,29 @@ func (pr *PatRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
if allow, ok := pr.methodNotAllowed(r.Method, reqPath); ok {
|
||||
allow, ok := pr.methodNotAllowed(r.Method, reqPath)
|
||||
if !ok {
|
||||
pr.handleNotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if pr.notAllowed != nil {
|
||||
pr.notAllowed.ServeHTTP(w, r)
|
||||
} else {
|
||||
w.Header().Set(allowHeader, allow)
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
} else {
|
||||
pr.handleNotFound(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func (pr *PatRouter) SetNotFoundHandler(handler http.Handler) {
|
||||
func (pr *patRouter) SetNotFoundHandler(handler http.Handler) {
|
||||
pr.notFound = handler
|
||||
}
|
||||
|
||||
func (pr *PatRouter) handleNotFound(w http.ResponseWriter, r *http.Request) {
|
||||
func (pr *patRouter) SetNotAllowedHandler(handler http.Handler) {
|
||||
pr.notAllowed = handler
|
||||
}
|
||||
|
||||
func (pr *patRouter) handleNotFound(w http.ResponseWriter, r *http.Request) {
|
||||
if pr.notFound != nil {
|
||||
pr.notFound.ServeHTTP(w, r)
|
||||
} else {
|
||||
@@ -83,7 +94,7 @@ func (pr *PatRouter) handleNotFound(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func (pr *PatRouter) methodNotAllowed(method, path string) (string, bool) {
|
||||
func (pr *patRouter) methodNotAllowed(method, path string) (string, bool) {
|
||||
var allows []string
|
||||
|
||||
for treeMethod, tree := range pr.trees {
|
||||
|
||||
@@ -47,7 +47,7 @@ func TestPatRouterHandleErrors(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.method, func(t *testing.T) {
|
||||
router := NewPatRouter()
|
||||
router := NewRouter()
|
||||
err := router.Handle(test.method, test.path, nil)
|
||||
assert.Error(t, ErrInvalidMethod, err)
|
||||
})
|
||||
@@ -56,17 +56,34 @@ func TestPatRouterHandleErrors(t *testing.T) {
|
||||
|
||||
func TestPatRouterNotFound(t *testing.T) {
|
||||
var notFound bool
|
||||
router := NewPatRouter()
|
||||
router := NewRouter()
|
||||
router.SetNotFoundHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
notFound = true
|
||||
}))
|
||||
router.Handle(http.MethodGet, "/a/b", nil)
|
||||
err := router.Handle(http.MethodGet, "/a/b",
|
||||
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
|
||||
assert.Nil(t, err)
|
||||
r, _ := http.NewRequest(http.MethodGet, "/b/c", nil)
|
||||
w := new(mockedResponseWriter)
|
||||
router.ServeHTTP(w, r)
|
||||
assert.True(t, notFound)
|
||||
}
|
||||
|
||||
func TestPatRouterNotAllowed(t *testing.T) {
|
||||
var notAllowed bool
|
||||
router := NewRouter()
|
||||
router.SetNotAllowedHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
notAllowed = true
|
||||
}))
|
||||
err := router.Handle(http.MethodGet, "/a/b",
|
||||
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
|
||||
assert.Nil(t, err)
|
||||
r, _ := http.NewRequest(http.MethodPost, "/a/b", nil)
|
||||
w := new(mockedResponseWriter)
|
||||
router.ServeHTTP(w, r)
|
||||
assert.True(t, notAllowed)
|
||||
}
|
||||
|
||||
func TestPatRouter(t *testing.T) {
|
||||
tests := []struct {
|
||||
method string
|
||||
@@ -87,7 +104,7 @@ func TestPatRouter(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
t.Run(test.method+":"+test.path, func(t *testing.T) {
|
||||
routed := false
|
||||
router := NewPatRouter()
|
||||
router := NewRouter()
|
||||
err := router.Handle(test.method, "/a/:b", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
routed = true
|
||||
assert.Equal(t, 1, len(context.Vars(r)))
|
||||
@@ -125,7 +142,7 @@ func TestParseSlice(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
rt := NewPatRouter()
|
||||
rt := NewRouter()
|
||||
err = rt.Handle(http.MethodPost, "/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
v := struct {
|
||||
Names []string `form:"names"`
|
||||
@@ -149,7 +166,7 @@ func TestParseJsonPost(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
r.Header.Set(httpx.ContentType, httpx.ApplicationJson)
|
||||
|
||||
router := NewPatRouter()
|
||||
router := NewRouter()
|
||||
err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(func(
|
||||
w http.ResponseWriter, r *http.Request) {
|
||||
v := struct {
|
||||
@@ -181,7 +198,7 @@ func TestParseJsonPostWithIntSlice(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
r.Header.Set(httpx.ContentType, httpx.ApplicationJson)
|
||||
|
||||
router := NewPatRouter()
|
||||
router := NewRouter()
|
||||
err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(func(
|
||||
w http.ResponseWriter, r *http.Request) {
|
||||
v := struct {
|
||||
@@ -209,7 +226,7 @@ func TestParseJsonPostError(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
r.Header.Set(httpx.ContentType, httpx.ApplicationJson)
|
||||
|
||||
router := NewPatRouter()
|
||||
router := NewRouter()
|
||||
err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
v := struct {
|
||||
@@ -237,7 +254,7 @@ func TestParseJsonPostInvalidRequest(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
r.Header.Set(httpx.ContentType, httpx.ApplicationJson)
|
||||
|
||||
router := NewPatRouter()
|
||||
router := NewRouter()
|
||||
err = router.Handle(http.MethodPost, "/", http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
v := struct {
|
||||
@@ -259,7 +276,7 @@ func TestParseJsonPostRequired(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
r.Header.Set(httpx.ContentType, httpx.ApplicationJson)
|
||||
|
||||
router := NewPatRouter()
|
||||
router := NewRouter()
|
||||
err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
v := struct {
|
||||
@@ -282,7 +299,7 @@ func TestParsePath(t *testing.T) {
|
||||
r, err := http.NewRequest(http.MethodGet, "http://hello.com/kevin/2017", nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
router := NewPatRouter()
|
||||
router := NewRouter()
|
||||
err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
v := struct {
|
||||
@@ -307,7 +324,7 @@ func TestParsePathRequired(t *testing.T) {
|
||||
r, err := http.NewRequest(http.MethodGet, "http://hello.com/kevin", nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
router := NewPatRouter()
|
||||
router := NewRouter()
|
||||
err = router.Handle(http.MethodGet, "/:name/", http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
v := struct {
|
||||
@@ -328,7 +345,7 @@ func TestParseQuery(t *testing.T) {
|
||||
r, err := http.NewRequest(http.MethodGet, "http://hello.com/kevin/2017?nickname=whatever&zipcode=200000", nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
router := NewPatRouter()
|
||||
router := NewRouter()
|
||||
err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
v := struct {
|
||||
@@ -353,7 +370,7 @@ func TestParseQueryRequired(t *testing.T) {
|
||||
r, err := http.NewRequest(http.MethodPost, "http://hello.com/kevin/2017?nickname=whatever", nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
router := NewPatRouter()
|
||||
router := NewRouter()
|
||||
err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
v := struct {
|
||||
Nickname string `form:"nickname"`
|
||||
@@ -373,7 +390,7 @@ func TestParseOptional(t *testing.T) {
|
||||
r, err := http.NewRequest(http.MethodGet, "http://hello.com/kevin/2017?nickname=whatever&zipcode=", nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
router := NewPatRouter()
|
||||
router := NewRouter()
|
||||
err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
v := struct {
|
||||
@@ -414,7 +431,7 @@ func TestParseNestedInRequestEmpty(t *testing.T) {
|
||||
}
|
||||
)
|
||||
|
||||
router := NewPatRouter()
|
||||
router := NewRouter()
|
||||
err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
var v WrappedRequest
|
||||
@@ -453,7 +470,7 @@ func TestParsePtrInRequest(t *testing.T) {
|
||||
}
|
||||
)
|
||||
|
||||
router := NewPatRouter()
|
||||
router := NewRouter()
|
||||
err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
var v WrappedRequest
|
||||
@@ -484,7 +501,7 @@ func TestParsePtrInRequestEmpty(t *testing.T) {
|
||||
}
|
||||
)
|
||||
|
||||
router := NewPatRouter()
|
||||
router := NewRouter()
|
||||
err = router.Handle(http.MethodPost, "/kevin", http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
var v WrappedRequest
|
||||
@@ -501,7 +518,7 @@ func TestParseQueryOptional(t *testing.T) {
|
||||
r, err := http.NewRequest(http.MethodGet, "http://hello.com/kevin/2017?nickname=whatever&zipcode=", nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
router := NewPatRouter()
|
||||
router := NewRouter()
|
||||
err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
v := struct {
|
||||
@@ -526,7 +543,7 @@ func TestParse(t *testing.T) {
|
||||
r, err := http.NewRequest(http.MethodGet, "http://hello.com/kevin/2017?nickname=whatever&zipcode=200000", nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
router := NewPatRouter()
|
||||
router := NewRouter()
|
||||
err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
v := struct {
|
||||
@@ -564,7 +581,7 @@ func TestParseWrappedRequest(t *testing.T) {
|
||||
}
|
||||
)
|
||||
|
||||
router := NewPatRouter()
|
||||
router := NewRouter()
|
||||
err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
var v WrappedRequest
|
||||
@@ -596,7 +613,7 @@ func TestParseWrappedGetRequestWithJsonHeader(t *testing.T) {
|
||||
}
|
||||
)
|
||||
|
||||
router := NewPatRouter()
|
||||
router := NewRouter()
|
||||
err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
var v WrappedRequest
|
||||
@@ -629,7 +646,7 @@ func TestParseWrappedHeadRequestWithJsonHeader(t *testing.T) {
|
||||
}
|
||||
)
|
||||
|
||||
router := NewPatRouter()
|
||||
router := NewRouter()
|
||||
err = router.Handle(http.MethodHead, "/:name/:year", http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
var v WrappedRequest
|
||||
@@ -661,7 +678,7 @@ func TestParseWrappedRequestPtr(t *testing.T) {
|
||||
}
|
||||
)
|
||||
|
||||
router := NewPatRouter()
|
||||
router := NewRouter()
|
||||
err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
var v WrappedRequest
|
||||
@@ -684,7 +701,7 @@ func TestParseWithAll(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
r.Header.Set(httpx.ContentType, httpx.ApplicationJson)
|
||||
|
||||
router := NewPatRouter()
|
||||
router := NewRouter()
|
||||
err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
v := struct {
|
||||
Name string `path:"name"`
|
||||
@@ -715,7 +732,7 @@ func TestParseWithAllUtf8(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
r.Header.Set(httpx.ContentType, applicationJsonWithUtf8)
|
||||
|
||||
router := NewPatRouter()
|
||||
router := NewRouter()
|
||||
err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
v := struct {
|
||||
@@ -746,7 +763,7 @@ func TestParseWithMissingForm(t *testing.T) {
|
||||
bytes.NewBufferString(`{"location": "shanghai", "time": 20170912}`))
|
||||
assert.Nil(t, err)
|
||||
|
||||
router := NewPatRouter()
|
||||
router := NewRouter()
|
||||
err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
v := struct {
|
||||
@@ -773,7 +790,7 @@ func TestParseWithMissingAllForms(t *testing.T) {
|
||||
bytes.NewBufferString(`{"location": "shanghai", "time": 20170912}`))
|
||||
assert.Nil(t, err)
|
||||
|
||||
router := NewPatRouter()
|
||||
router := NewRouter()
|
||||
err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
v := struct {
|
||||
@@ -799,7 +816,7 @@ func TestParseWithMissingJson(t *testing.T) {
|
||||
bytes.NewBufferString(`{"location": "shanghai"}`))
|
||||
assert.Nil(t, err)
|
||||
|
||||
router := NewPatRouter()
|
||||
router := NewRouter()
|
||||
err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
v := struct {
|
||||
@@ -825,7 +842,7 @@ func TestParseWithMissingAllJsons(t *testing.T) {
|
||||
r, err := http.NewRequest(http.MethodGet, "http://hello.com/kevin/2017?nickname=whatever&zipcode=200000", nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
router := NewPatRouter()
|
||||
router := NewRouter()
|
||||
err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
v := struct {
|
||||
@@ -852,7 +869,7 @@ func TestParseWithMissingPath(t *testing.T) {
|
||||
bytes.NewBufferString(`{"location": "shanghai", "time": 20170912}`))
|
||||
assert.Nil(t, err)
|
||||
|
||||
router := NewPatRouter()
|
||||
router := NewRouter()
|
||||
err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
v := struct {
|
||||
@@ -879,7 +896,7 @@ func TestParseWithMissingAllPaths(t *testing.T) {
|
||||
bytes.NewBufferString(`{"location": "shanghai", "time": 20170912}`))
|
||||
assert.Nil(t, err)
|
||||
|
||||
router := NewPatRouter()
|
||||
router := NewRouter()
|
||||
err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
v := struct {
|
||||
@@ -906,7 +923,7 @@ func TestParseGetWithContentLengthHeader(t *testing.T) {
|
||||
r.Header.Set(httpx.ContentType, httpx.ApplicationJson)
|
||||
r.Header.Set(contentLength, "1024")
|
||||
|
||||
router := NewPatRouter()
|
||||
router := NewRouter()
|
||||
err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
v := struct {
|
||||
@@ -933,7 +950,7 @@ func TestParseJsonPostWithTypeMismatch(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
r.Header.Set(httpx.ContentType, applicationJsonWithUtf8)
|
||||
|
||||
router := NewPatRouter()
|
||||
router := NewRouter()
|
||||
err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
v := struct {
|
||||
@@ -959,7 +976,7 @@ func TestParseJsonPostWithInt2String(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
r.Header.Set(httpx.ContentType, applicationJsonWithUtf8)
|
||||
|
||||
router := NewPatRouter()
|
||||
router := NewRouter()
|
||||
err = router.Handle(http.MethodPost, "/:name/:year", http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
v := struct {
|
||||
@@ -980,7 +997,7 @@ func TestParseJsonPostWithInt2String(t *testing.T) {
|
||||
func BenchmarkPatRouter(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
|
||||
router := NewPatRouter()
|
||||
router := NewRouter()
|
||||
router.Handle(http.MethodGet, "/api/:user/:name", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
}))
|
||||
w := &mockedResponseWriter{}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/tal-tech/go-zero/core/logx"
|
||||
"github.com/tal-tech/go-zero/rest/handler"
|
||||
"github.com/tal-tech/go-zero/rest/httpx"
|
||||
"github.com/tal-tech/go-zero/rest/router"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -32,6 +34,10 @@ func MustNewServer(c RestConf, opts ...RunOption) *Server {
|
||||
}
|
||||
|
||||
func NewServer(c RestConf, opts ...RunOption) (*Server, error) {
|
||||
if len(opts) > 1 {
|
||||
return nil, errors.New("only one RunOption is allowed")
|
||||
}
|
||||
|
||||
if err := c.SetUp(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -103,6 +109,13 @@ func WithJwtTransition(secret, prevSecret string) RouteOption {
|
||||
}
|
||||
}
|
||||
|
||||
func WithMiddlewares(ms []Middleware, rs ...Route) []Route {
|
||||
for i := len(ms) - 1; i >= 0; i-- {
|
||||
rs = WithMiddleware(ms[i], rs...)
|
||||
}
|
||||
return rs
|
||||
}
|
||||
|
||||
func WithMiddleware(middleware Middleware, rs ...Route) []Route {
|
||||
routes := make([]Route, len(rs))
|
||||
|
||||
@@ -118,6 +131,18 @@ func WithMiddleware(middleware Middleware, rs ...Route) []Route {
|
||||
return routes
|
||||
}
|
||||
|
||||
func WithNotFoundHandler(handler http.Handler) RunOption {
|
||||
rt := router.NewRouter()
|
||||
rt.SetNotFoundHandler(handler)
|
||||
return WithRouter(rt)
|
||||
}
|
||||
|
||||
func WithNotAllowedHandler(handler http.Handler) RunOption {
|
||||
rt := router.NewRouter()
|
||||
rt.SetNotAllowedHandler(handler)
|
||||
return WithRouter(rt)
|
||||
}
|
||||
|
||||
func WithPriority() RouteOption {
|
||||
return func(r *featuredRoutes) {
|
||||
r.priority = true
|
||||
|
||||
@@ -12,9 +12,14 @@ import (
|
||||
"github.com/tal-tech/go-zero/rest/router"
|
||||
)
|
||||
|
||||
func TestNewServer(t *testing.T) {
|
||||
_, err := NewServer(RestConf{}, WithNotFoundHandler(nil), WithNotAllowedHandler(nil))
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestWithMiddleware(t *testing.T) {
|
||||
m := make(map[string]string)
|
||||
router := router.NewPatRouter()
|
||||
router := router.NewRouter()
|
||||
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||
var v struct {
|
||||
Nickname string `form:"nickname"`
|
||||
@@ -68,3 +73,81 @@ func TestWithMiddleware(t *testing.T) {
|
||||
"wan": "2020",
|
||||
}, m)
|
||||
}
|
||||
|
||||
func TestMultiMiddlewares(t *testing.T) {
|
||||
m := make(map[string]string)
|
||||
router := router.NewRouter()
|
||||
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||
var v struct {
|
||||
Nickname string `form:"nickname"`
|
||||
Zipcode int64 `form:"zipcode"`
|
||||
}
|
||||
|
||||
err := httpx.Parse(r, &v)
|
||||
assert.Nil(t, err)
|
||||
_, err = io.WriteString(w, fmt.Sprintf("%s:%s", v.Nickname, m[v.Nickname]))
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
rs := WithMiddlewares([]Middleware{
|
||||
func(next http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var v struct {
|
||||
Name string `path:"name"`
|
||||
Year string `path:"year"`
|
||||
}
|
||||
assert.Nil(t, httpx.ParsePath(r, &v))
|
||||
m[v.Name] = v.Year
|
||||
next.ServeHTTP(w, r)
|
||||
}
|
||||
},
|
||||
func(next http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var v struct {
|
||||
Name string `form:"nickname"`
|
||||
Zipcode string `form:"zipcode"`
|
||||
}
|
||||
assert.Nil(t, httpx.ParseForm(r, &v))
|
||||
assert.NotEmpty(t, m)
|
||||
m[v.Name] = v.Zipcode + v.Zipcode
|
||||
next.ServeHTTP(w, r)
|
||||
}
|
||||
},
|
||||
}, Route{
|
||||
Method: http.MethodGet,
|
||||
Path: "/first/:name/:year",
|
||||
Handler: handler,
|
||||
}, Route{
|
||||
Method: http.MethodGet,
|
||||
Path: "/second/:name/:year",
|
||||
Handler: handler,
|
||||
})
|
||||
|
||||
urls := []string{
|
||||
"http://hello.com/first/kevin/2017?nickname=whatever&zipcode=200000",
|
||||
"http://hello.com/second/wan/2020?nickname=whatever&zipcode=200000",
|
||||
}
|
||||
for _, route := range rs {
|
||||
assert.Nil(t, router.Handle(route.Method, route.Path, route.Handler))
|
||||
}
|
||||
for _, url := range urls {
|
||||
r, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
router.ServeHTTP(rr, r)
|
||||
|
||||
assert.Equal(t, "whatever:200000200000", rr.Body.String())
|
||||
}
|
||||
|
||||
assert.EqualValues(t, map[string]string{
|
||||
"kevin": "2017",
|
||||
"wan": "2020",
|
||||
"whatever": "200000200000",
|
||||
}, m)
|
||||
}
|
||||
|
||||
func TestWithPriority(t *testing.T) {
|
||||
var fr featuredRoutes
|
||||
WithPriority()(&fr)
|
||||
assert.True(t, fr.priority)
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@ import (
|
||||
const apiTemplate = `info(
|
||||
title: // TODO: add title
|
||||
desc: // TODO: add description
|
||||
author: {{.gitUser}}
|
||||
email: {{.gitEmail}}
|
||||
author: "{{.gitUser}}"
|
||||
email: "{{.gitEmail}}"
|
||||
)
|
||||
|
||||
type request struct {
|
||||
@@ -28,14 +28,10 @@ type response struct {
|
||||
}
|
||||
|
||||
service {{.serviceName}} {
|
||||
@server(
|
||||
handler: // TODO: set handler name and delete this comment
|
||||
)
|
||||
@handler // TODO: set handler name and delete this comment
|
||||
get /users/id/:userId(request) returns(response)
|
||||
|
||||
@server(
|
||||
handler: // TODO: set handler name and delete this comment
|
||||
)
|
||||
@handler // TODO: set handler name and delete this comment
|
||||
post /users/create(request)
|
||||
}
|
||||
`
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/format"
|
||||
"go/scanner"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -22,39 +23,79 @@ var (
|
||||
)
|
||||
|
||||
func GoFormatApi(c *cli.Context) error {
|
||||
dir := c.String("dir")
|
||||
if len(dir) == 0 {
|
||||
return errors.New("missing -dir")
|
||||
}
|
||||
|
||||
printToConsole := c.Bool("p")
|
||||
useStdin := c.Bool("stdin")
|
||||
|
||||
var be errorx.BatchError
|
||||
err := filepath.Walk(dir, func(path string, fi os.FileInfo, errBack error) (err error) {
|
||||
if strings.HasSuffix(path, ".api") {
|
||||
err := ApiFormat(path, printToConsole)
|
||||
if err != nil {
|
||||
be.Add(util.WrapErr(err, fi.Name()))
|
||||
}
|
||||
if useStdin {
|
||||
if err := ApiFormatByStdin(); err != nil {
|
||||
be.Add(err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
be.Add(err)
|
||||
} else {
|
||||
dir := c.String("dir")
|
||||
if len(dir) == 0 {
|
||||
return errors.New("missing -dir")
|
||||
}
|
||||
|
||||
_, err := os.Lstat(dir)
|
||||
if err != nil {
|
||||
return errors.New(dir + ": No such file or directory")
|
||||
}
|
||||
|
||||
err = filepath.Walk(dir, func(path string, fi os.FileInfo, errBack error) (err error) {
|
||||
if strings.HasSuffix(path, ".api") {
|
||||
if err := ApiFormatByPath(path); err != nil {
|
||||
be.Add(util.WrapErr(err, fi.Name()))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
be.Add(err)
|
||||
}
|
||||
if be.NotNil() {
|
||||
errs := be.Err().Error()
|
||||
fmt.Println(errs)
|
||||
scanner.PrintError(os.Stderr, be.Err())
|
||||
os.Exit(1)
|
||||
}
|
||||
return be.Err()
|
||||
}
|
||||
|
||||
func ApiFormat(path string, printToConsole bool) error {
|
||||
data, err := ioutil.ReadFile(path)
|
||||
func ApiFormatByStdin() error {
|
||||
data, err := ioutil.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r := reg.ReplaceAllStringFunc(string(data), func(m string) string {
|
||||
result, err := apiFormat(string(data))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = fmt.Print(result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ApiFormatByPath(apiFilePath string) error {
|
||||
data, err := ioutil.ReadFile(apiFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result, err := apiFormat(string(data))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(apiFilePath, []byte(result), os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func apiFormat(data string) (string, error) {
|
||||
|
||||
r := reg.ReplaceAllStringFunc(data, func(m string) string {
|
||||
parts := reg.FindStringSubmatch(m)
|
||||
if len(parts) < 2 {
|
||||
return m
|
||||
@@ -67,11 +108,11 @@ func ApiFormat(path string, printToConsole bool) error {
|
||||
|
||||
apiStruct, err := parser.ParseApi(r)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
info := strings.TrimSpace(apiStruct.Info)
|
||||
if len(apiStruct.Service) == 0 {
|
||||
return nil
|
||||
return data, nil
|
||||
}
|
||||
|
||||
fs, err := format.Source([]byte(strings.TrimSpace(apiStruct.StructBody)))
|
||||
@@ -81,16 +122,16 @@ func ApiFormat(path string, printToConsole bool) error {
|
||||
if lineNumber > 0 {
|
||||
ln, err := strconv.ParseInt(str[:lineNumber], 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
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:]))
|
||||
return "", errors.New(fmt.Sprintf("line: %d, %s", number, str[lineNumber+1:]))
|
||||
}
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
|
||||
var result string
|
||||
@@ -107,11 +148,7 @@ func ApiFormat(path string, printToConsole bool) error {
|
||||
result += strings.TrimSpace(apiStruct.Service) + "\n\n"
|
||||
}
|
||||
|
||||
if printToConsole {
|
||||
_, err := fmt.Print(result)
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(path, []byte(result), os.ModePerm)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func countRune(s string, r rune) int {
|
||||
|
||||
@@ -66,7 +66,7 @@ func DoGenProject(apiFile, dir string, force bool) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = apiformat.ApiFormat(apiFile, false); err != nil {
|
||||
if err := apiformat.ApiFormatByPath(apiFile); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
package parser
|
||||
package gogen
|
||||
|
||||
import (
|
||||
goformat "go/format"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/api/parser"
|
||||
)
|
||||
|
||||
const testApiTemplate = `
|
||||
@@ -119,13 +123,75 @@ service A-api {
|
||||
}
|
||||
`
|
||||
|
||||
const apiHasMiddleware = `
|
||||
type Request struct {
|
||||
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Message string ` + "`" + `json:"message"` + "`" + `
|
||||
}
|
||||
|
||||
@server(
|
||||
middleware: TokenValidate
|
||||
)
|
||||
service A-api {
|
||||
@handler GreetHandler
|
||||
get /greet/from/:name(Request) returns (Response)
|
||||
}
|
||||
`
|
||||
|
||||
const apiJwt = `
|
||||
type Request struct {
|
||||
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Message string ` + "`" + `json:"message"` + "`" + `
|
||||
}
|
||||
|
||||
@server(
|
||||
jwt: Auth
|
||||
)
|
||||
service A-api {
|
||||
@handler GreetHandler
|
||||
get /greet/from/:name(Request) returns (Response)
|
||||
}
|
||||
`
|
||||
|
||||
const apiJwtWithMiddleware = `
|
||||
type Request struct {
|
||||
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Message string ` + "`" + `json:"message"` + "`" + `
|
||||
}
|
||||
|
||||
@server(
|
||||
jwt: Auth
|
||||
middleware: TokenValidate
|
||||
)
|
||||
service A-api {
|
||||
@handler GreetHandler
|
||||
get /greet/from/:name(Request) returns (Response)
|
||||
}
|
||||
`
|
||||
|
||||
const apiHasNoRequest = `
|
||||
service A-api {
|
||||
@handler GreetHandler
|
||||
post /greet/ping ()
|
||||
}
|
||||
`
|
||||
|
||||
func TestParser(t *testing.T) {
|
||||
filename := "greet.api"
|
||||
err := ioutil.WriteFile(filename, []byte(testApiTemplate), os.ModePerm)
|
||||
assert.Nil(t, err)
|
||||
defer os.Remove(filename)
|
||||
|
||||
parser, err := NewParser(filename)
|
||||
parser, err := parser.NewParser(filename)
|
||||
assert.Nil(t, err)
|
||||
|
||||
api, err := parser.Parse()
|
||||
@@ -139,6 +205,8 @@ func TestParser(t *testing.T) {
|
||||
|
||||
assert.Equal(t, api.Service.Routes[1].RequestType.Name, "Request")
|
||||
assert.Equal(t, api.Service.Routes[1].ResponseType.Name, "")
|
||||
|
||||
validate(t, filename)
|
||||
}
|
||||
|
||||
func TestMultiService(t *testing.T) {
|
||||
@@ -147,7 +215,7 @@ func TestMultiService(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
defer os.Remove(filename)
|
||||
|
||||
parser, err := NewParser(filename)
|
||||
parser, err := parser.NewParser(filename)
|
||||
assert.Nil(t, err)
|
||||
|
||||
api, err := parser.Parse()
|
||||
@@ -155,6 +223,8 @@ func TestMultiService(t *testing.T) {
|
||||
|
||||
assert.Equal(t, len(api.Service.Routes), 2)
|
||||
assert.Equal(t, len(api.Service.Groups), 2)
|
||||
|
||||
validate(t, filename)
|
||||
}
|
||||
|
||||
func TestApiNoInfo(t *testing.T) {
|
||||
@@ -163,11 +233,13 @@ func TestApiNoInfo(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
defer os.Remove(filename)
|
||||
|
||||
parser, err := NewParser(filename)
|
||||
parser, err := parser.NewParser(filename)
|
||||
assert.Nil(t, err)
|
||||
|
||||
_, err = parser.Parse()
|
||||
assert.Nil(t, err)
|
||||
|
||||
validate(t, filename)
|
||||
}
|
||||
|
||||
func TestInvalidApiFile(t *testing.T) {
|
||||
@@ -176,7 +248,7 @@ func TestInvalidApiFile(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
defer os.Remove(filename)
|
||||
|
||||
parser, err := NewParser(filename)
|
||||
parser, err := parser.NewParser(filename)
|
||||
assert.Nil(t, err)
|
||||
|
||||
_, err = parser.Parse()
|
||||
@@ -189,7 +261,7 @@ func TestAnonymousAnnotation(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
defer os.Remove(filename)
|
||||
|
||||
parser, err := NewParser(filename)
|
||||
parser, err := parser.NewParser(filename)
|
||||
assert.Nil(t, err)
|
||||
|
||||
api, err := parser.Parse()
|
||||
@@ -197,4 +269,86 @@ func TestAnonymousAnnotation(t *testing.T) {
|
||||
|
||||
assert.Equal(t, len(api.Service.Routes), 1)
|
||||
assert.Equal(t, api.Service.Routes[0].Annotations[0].Value, "GreetHandler")
|
||||
|
||||
validate(t, filename)
|
||||
}
|
||||
|
||||
func TestApiHasMiddleware(t *testing.T) {
|
||||
filename := "greet.api"
|
||||
err := ioutil.WriteFile(filename, []byte(apiHasMiddleware), os.ModePerm)
|
||||
assert.Nil(t, err)
|
||||
defer os.Remove(filename)
|
||||
|
||||
parser, err := parser.NewParser(filename)
|
||||
assert.Nil(t, err)
|
||||
|
||||
_, err = parser.Parse()
|
||||
assert.Nil(t, err)
|
||||
|
||||
validate(t, filename)
|
||||
}
|
||||
|
||||
func TestApiHasJwt(t *testing.T) {
|
||||
filename := "jwt.api"
|
||||
err := ioutil.WriteFile(filename, []byte(apiJwt), os.ModePerm)
|
||||
assert.Nil(t, err)
|
||||
defer os.Remove(filename)
|
||||
|
||||
parser, err := parser.NewParser(filename)
|
||||
assert.Nil(t, err)
|
||||
|
||||
_, err = parser.Parse()
|
||||
assert.Nil(t, err)
|
||||
|
||||
validate(t, filename)
|
||||
}
|
||||
|
||||
func TestApiHasJwtAndMiddleware(t *testing.T) {
|
||||
filename := "jwt.api"
|
||||
err := ioutil.WriteFile(filename, []byte(apiJwtWithMiddleware), os.ModePerm)
|
||||
assert.Nil(t, err)
|
||||
defer os.Remove(filename)
|
||||
|
||||
parser, err := parser.NewParser(filename)
|
||||
assert.Nil(t, err)
|
||||
|
||||
_, err = parser.Parse()
|
||||
assert.Nil(t, err)
|
||||
|
||||
validate(t, filename)
|
||||
}
|
||||
|
||||
func TestApiHasNoRequestBody(t *testing.T) {
|
||||
filename := "greet.api"
|
||||
err := ioutil.WriteFile(filename, []byte(apiHasNoRequest), os.ModePerm)
|
||||
assert.Nil(t, err)
|
||||
defer os.Remove(filename)
|
||||
|
||||
parser, err := parser.NewParser(filename)
|
||||
assert.Nil(t, err)
|
||||
|
||||
_, err = parser.Parse()
|
||||
assert.Nil(t, err)
|
||||
|
||||
validate(t, filename)
|
||||
}
|
||||
|
||||
func validate(t *testing.T, api string) {
|
||||
dir := "_go"
|
||||
err := DoGenProject(api, dir, true)
|
||||
defer os.RemoveAll(dir)
|
||||
assert.Nil(t, err)
|
||||
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
if strings.HasSuffix(path, ".go") {
|
||||
code, err := ioutil.ReadFile(path)
|
||||
assert.Nil(t, err)
|
||||
assert.Nil(t, validateCode(string(code)))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func validateCode(code string) error {
|
||||
_, err := goformat.Source([]byte(code))
|
||||
return err
|
||||
}
|
||||
@@ -8,7 +8,7 @@ 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/templatex"
|
||||
ctlutil "github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/vars"
|
||||
)
|
||||
|
||||
@@ -48,7 +48,7 @@ func genConfig(dir string, api *spec.ApiSpec) error {
|
||||
}
|
||||
|
||||
var authImportStr = fmt.Sprintf("\"%s/rest\"", vars.ProjectOpenSourceUrl)
|
||||
text, err := templatex.LoadTemplate(category, configTemplateFile, configTemplate)
|
||||
text, err := ctlutil.LoadTemplate(category, configTemplateFile, configTemplate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ 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/templatex"
|
||||
ctlutil "github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -40,7 +40,7 @@ func genEtc(dir string, api *spec.ApiSpec) error {
|
||||
port = strconv.Itoa(defaultPort)
|
||||
}
|
||||
|
||||
text, err := templatex.LoadTemplate(category, etcTemplateFile, etcTemplate)
|
||||
text, err := ctlutil.LoadTemplate(category, etcTemplateFile, etcTemplate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
|
||||
"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/templatex"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/vars"
|
||||
)
|
||||
@@ -24,14 +23,14 @@ import (
|
||||
|
||||
func {{.HandlerName}}(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.{{.RequestType}}
|
||||
{{if .HasRequest}}var req types.{{.RequestType}}
|
||||
if err := httpx.Parse(r, &req); err != nil {
|
||||
httpx.Error(w, err)
|
||||
return
|
||||
}
|
||||
}{{end}}
|
||||
|
||||
l := logic.New{{.LogicType}}(r.Context(), ctx)
|
||||
{{if .HasResp}}resp, {{end}}err := l.{{.Call}}(req)
|
||||
{{if .HasResp}}resp, {{end}}err := l.{{.Call}}({{if .HasRequest}}req{{end}})
|
||||
if err != nil {
|
||||
httpx.Error(w, err)
|
||||
} else {
|
||||
@@ -48,6 +47,7 @@ type Handler struct {
|
||||
LogicType string
|
||||
Call string
|
||||
HasResp bool
|
||||
HasRequest bool
|
||||
}
|
||||
|
||||
func genHandler(dir string, group spec.Group, route spec.Route) error {
|
||||
@@ -72,6 +72,7 @@ func genHandler(dir string, group spec.Group, route spec.Route) error {
|
||||
LogicType: strings.TrimSuffix(strings.Title(handler), "Handler") + "Logic",
|
||||
Call: strings.Title(strings.TrimSuffix(handler, "Handler")),
|
||||
HasResp: len(route.ResponseType.Name) > 0,
|
||||
HasRequest: len(route.RequestType.Name) > 0,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -94,7 +95,7 @@ func doGenToFile(dir, handler string, group spec.Group, route spec.Route, handle
|
||||
}
|
||||
defer fp.Close()
|
||||
|
||||
text, err := templatex.LoadTemplate(category, handlerTemplateFile, handlerTemplate)
|
||||
text, err := util.LoadTemplate(category, handlerTemplateFile, handlerTemplate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ 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/templatex"
|
||||
ctlutil "github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/vars"
|
||||
)
|
||||
@@ -94,7 +93,7 @@ func genLogicByRoute(dir string, group spec.Group, route spec.Route) error {
|
||||
requestString = "req " + "types." + strings.Title(route.RequestType.Name)
|
||||
}
|
||||
|
||||
text, err := templatex.LoadTemplate(category, logicTemplateFile, logicTemplate)
|
||||
text, err := ctlutil.LoadTemplate(category, logicTemplateFile, logicTemplate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ 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/templatex"
|
||||
ctlutil "github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/vars"
|
||||
)
|
||||
@@ -61,7 +60,7 @@ func genMain(dir string, api *spec.ApiSpec) error {
|
||||
return err
|
||||
}
|
||||
|
||||
text, err := templatex.LoadTemplate(category, mainTemplateFile, mainTemplate)
|
||||
text, err := ctlutil.LoadTemplate(category, mainTemplateFile, mainTemplate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
59
tools/goctl/api/gogen/genmiddleware.go
Normal file
59
tools/goctl/api/gogen/genmiddleware.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package gogen
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/api/util"
|
||||
)
|
||||
|
||||
var middlewareImplementCode = `
|
||||
package middleware
|
||||
|
||||
import "net/http"
|
||||
|
||||
type {{.name}} struct {
|
||||
}
|
||||
|
||||
func New{{.name}}() *{{.name}} {
|
||||
return &{{.name}}{}
|
||||
}
|
||||
|
||||
func (m *{{.name}})Handle(next http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
// TODO generate middleware implement function, delete after code implementation
|
||||
|
||||
// Passthrough to next handler if need
|
||||
next(w, r)
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
func genMiddleware(dir string, middlewares []string) error {
|
||||
for _, item := range middlewares {
|
||||
filename := strings.TrimSuffix(strings.ToLower(item), "middleware") + "middleware" + ".go"
|
||||
fp, created, err := util.MaybeCreateFile(dir, middlewareDir, filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !created {
|
||||
return nil
|
||||
}
|
||||
defer fp.Close()
|
||||
|
||||
name := strings.TrimSuffix(item, "Middleware") + "Middleware"
|
||||
t := template.Must(template.New("contextTemplate").Parse(middlewareImplementCode))
|
||||
buffer := new(bytes.Buffer)
|
||||
err = t.Execute(buffer, map[string]string{
|
||||
"name": strings.Title(name),
|
||||
})
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
formatCode := formatCode(buffer.String())
|
||||
_, err = fp.WriteString(formatCode)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -31,9 +31,9 @@ func RegisterHandlers(engine *rest.Server, serverCtx *svc.ServiceContext) {
|
||||
}
|
||||
`
|
||||
routesAdditionTemplate = `
|
||||
engine.AddRoutes([]rest.Route{
|
||||
{{.routes}}
|
||||
}{{.jwt}}{{.signature}})
|
||||
engine.AddRoutes(
|
||||
{{.routes}} {{.jwt}}{{.signature}}
|
||||
)
|
||||
`
|
||||
)
|
||||
|
||||
@@ -52,6 +52,7 @@ type (
|
||||
jwtEnabled bool
|
||||
signatureEnabled bool
|
||||
authName string
|
||||
middleware []string
|
||||
}
|
||||
route struct {
|
||||
method string
|
||||
@@ -70,6 +71,7 @@ func genRoutes(dir string, api *spec.ApiSpec, force bool) error {
|
||||
gt := template.Must(template.New("groupTemplate").Parse(routesAdditionTemplate))
|
||||
for _, g := range groups {
|
||||
var gbuilder strings.Builder
|
||||
gbuilder.WriteString("[]rest.Route{")
|
||||
for _, r := range g.routes {
|
||||
fmt.Fprintf(&gbuilder, `
|
||||
{
|
||||
@@ -79,16 +81,33 @@ func genRoutes(dir string, api *spec.ApiSpec, force bool) error {
|
||||
},`,
|
||||
r.method, r.path, r.handler)
|
||||
}
|
||||
|
||||
var jwt string
|
||||
if g.jwtEnabled {
|
||||
jwt = fmt.Sprintf(", rest.WithJwt(serverCtx.Config.%s.AccessSecret)", g.authName)
|
||||
jwt = fmt.Sprintf("\n rest.WithJwt(serverCtx.Config.%s.AccessSecret),", g.authName)
|
||||
}
|
||||
var signature string
|
||||
if g.signatureEnabled {
|
||||
signature = fmt.Sprintf(", rest.WithSignature(serverCtx.Config.%s.Signature)", g.authName)
|
||||
signature = fmt.Sprintf("\n rest.WithSignature(serverCtx.Config.%s.Signature),", g.authName)
|
||||
}
|
||||
|
||||
var routes string
|
||||
if len(g.middleware) > 0 {
|
||||
gbuilder.WriteString("\n}...,")
|
||||
var params = g.middleware
|
||||
for i := range params {
|
||||
params[i] = "serverCtx." + params[i]
|
||||
}
|
||||
var middlewareStr = strings.Join(params, ", ")
|
||||
routes = fmt.Sprintf("rest.WithMiddlewares(\n[]rest.Middleware{ %s }, \n %s \n),",
|
||||
middlewareStr, strings.TrimSpace(gbuilder.String()))
|
||||
} else {
|
||||
gbuilder.WriteString("\n},")
|
||||
routes = strings.TrimSpace(gbuilder.String())
|
||||
}
|
||||
|
||||
if err := gt.Execute(&builder, map[string]string{
|
||||
"routes": strings.TrimSpace(gbuilder.String()),
|
||||
"routes": routes,
|
||||
"jwt": jwt,
|
||||
"signature": signature,
|
||||
}); err != nil {
|
||||
@@ -185,6 +204,11 @@ func getRoutes(api *spec.ApiSpec) ([]group, error) {
|
||||
groupedRoutes.authName = value
|
||||
groupedRoutes.jwtEnabled = true
|
||||
}
|
||||
if value, ok := apiutil.GetAnnotationValue(g.Annotations, "server", "middleware"); ok {
|
||||
for _, item := range strings.Split(value, ",") {
|
||||
groupedRoutes.middleware = append(groupedRoutes.middleware, item)
|
||||
}
|
||||
}
|
||||
routes = append(routes, groupedRoutes)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,27 +3,35 @@ package gogen
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"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/templatex"
|
||||
ctlutil "github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/vars"
|
||||
)
|
||||
|
||||
const (
|
||||
contextFilename = "servicecontext.go"
|
||||
contextTemplate = `package svc
|
||||
|
||||
import {{.configImport}}
|
||||
import (
|
||||
{{.configImport}}
|
||||
)
|
||||
|
||||
type ServiceContext struct {
|
||||
Config {{.config}}
|
||||
{{.middleware}}
|
||||
}
|
||||
|
||||
func NewServiceContext(c {{.config}}) *ServiceContext {
|
||||
return &ServiceContext{Config: c}
|
||||
return &ServiceContext{
|
||||
Config: c,
|
||||
{{.middlewareAssignment}}
|
||||
}
|
||||
}
|
||||
|
||||
`
|
||||
)
|
||||
|
||||
@@ -48,17 +56,38 @@ func genServiceContext(dir string, api *spec.ApiSpec) error {
|
||||
return err
|
||||
}
|
||||
|
||||
text, err := templatex.LoadTemplate(category, contextTemplateFile, contextTemplate)
|
||||
text, err := ctlutil.LoadTemplate(category, contextTemplateFile, contextTemplate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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)
|
||||
name := strings.TrimSuffix(item, "Middleware") + "Middleware"
|
||||
middlewareAssignment += fmt.Sprintf("%s: %s,\n", item, fmt.Sprintf("middleware.New%s().%s", strings.Title(name), "Handle"))
|
||||
}
|
||||
|
||||
var configImport = "\"" + ctlutil.JoinPackages(parentPkg, configDir) + "\""
|
||||
if len(middlewareStr) > 0 {
|
||||
configImport += "\n\t\"" + ctlutil.JoinPackages(parentPkg, middlewareDir) + "\""
|
||||
configImport += fmt.Sprintf("\n\t\"%s/rest\"", vars.ProjectOpenSourceUrl)
|
||||
}
|
||||
|
||||
t := template.Must(template.New("contextTemplate").Parse(text))
|
||||
buffer := new(bytes.Buffer)
|
||||
err = t.Execute(buffer, map[string]string{
|
||||
"configImport": configImport,
|
||||
"config": "config.Config",
|
||||
"configImport": configImport,
|
||||
"config": "config.Config",
|
||||
"middleware": middlewareStr,
|
||||
"middlewareAssignment": middlewareAssignment,
|
||||
})
|
||||
if err != nil {
|
||||
return nil
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package gogen
|
||||
|
||||
import (
|
||||
"github.com/tal-tech/go-zero/tools/goctl/templatex"
|
||||
"fmt"
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
@@ -25,5 +27,29 @@ var templates = map[string]string{
|
||||
}
|
||||
|
||||
func GenTemplates(_ *cli.Context) error {
|
||||
return templatex.InitTemplates(category, templates)
|
||||
return util.InitTemplates(category, templates)
|
||||
}
|
||||
|
||||
func RevertTemplate(name string) error {
|
||||
content, ok := templates[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("%s: no such file name", name)
|
||||
}
|
||||
return util.CreateTemplate(category, name, content)
|
||||
}
|
||||
|
||||
func Update(category string) error {
|
||||
err := Clean()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return util.InitTemplates(category, templates)
|
||||
}
|
||||
|
||||
func Clean() error {
|
||||
return util.Clean(category)
|
||||
}
|
||||
|
||||
func GetCategory() string {
|
||||
return category
|
||||
}
|
||||
|
||||
92
tools/goctl/api/gogen/template_test.go
Normal file
92
tools/goctl/api/gogen/template_test.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package gogen
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
)
|
||||
|
||||
func TestGenTemplates(t *testing.T) {
|
||||
err := util.InitTemplates(category, templates)
|
||||
assert.Nil(t, err)
|
||||
dir, err := util.GetTemplateDir(category)
|
||||
assert.Nil(t, err)
|
||||
file := filepath.Join(dir, "main.tpl")
|
||||
data, err := ioutil.ReadFile(file)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, string(data), mainTemplate)
|
||||
}
|
||||
|
||||
func TestRevertTemplate(t *testing.T) {
|
||||
name := "main.tpl"
|
||||
err := util.InitTemplates(category, templates)
|
||||
assert.Nil(t, err)
|
||||
|
||||
dir, err := util.GetTemplateDir(category)
|
||||
assert.Nil(t, err)
|
||||
|
||||
file := filepath.Join(dir, name)
|
||||
data, err := ioutil.ReadFile(file)
|
||||
assert.Nil(t, err)
|
||||
|
||||
modifyData := string(data) + "modify"
|
||||
err = util.CreateTemplate(category, name, modifyData)
|
||||
assert.Nil(t, err)
|
||||
|
||||
data, err = ioutil.ReadFile(file)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, string(data), modifyData)
|
||||
|
||||
assert.Nil(t, RevertTemplate(name))
|
||||
|
||||
data, err = ioutil.ReadFile(file)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, mainTemplate, string(data))
|
||||
}
|
||||
|
||||
func TestClean(t *testing.T) {
|
||||
name := "main.tpl"
|
||||
err := util.InitTemplates(category, templates)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Nil(t, Clean())
|
||||
|
||||
dir, err := util.GetTemplateDir(category)
|
||||
assert.Nil(t, err)
|
||||
|
||||
file := filepath.Join(dir, name)
|
||||
_, err = ioutil.ReadFile(file)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestUpdate(t *testing.T) {
|
||||
name := "main.tpl"
|
||||
err := util.InitTemplates(category, templates)
|
||||
assert.Nil(t, err)
|
||||
|
||||
dir, err := util.GetTemplateDir(category)
|
||||
assert.Nil(t, err)
|
||||
|
||||
file := filepath.Join(dir, name)
|
||||
data, err := ioutil.ReadFile(file)
|
||||
assert.Nil(t, err)
|
||||
|
||||
modifyData := string(data) + "modify"
|
||||
err = util.CreateTemplate(category, name, modifyData)
|
||||
assert.Nil(t, err)
|
||||
|
||||
data, err = ioutil.ReadFile(file)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, string(data), modifyData)
|
||||
|
||||
assert.Nil(t, Update(category))
|
||||
|
||||
data, err = ioutil.ReadFile(file)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, mainTemplate, string(data))
|
||||
}
|
||||
@@ -66,6 +66,18 @@ func getAuths(api *spec.ApiSpec) []string {
|
||||
return authNames.KeysStr()
|
||||
}
|
||||
|
||||
func getMiddleware(api *spec.ApiSpec) []string {
|
||||
result := collection.NewSet()
|
||||
for _, g := range api.Service.Groups {
|
||||
if value, ok := util.GetAnnotationValue(g.Annotations, "server", "middleware"); ok {
|
||||
for _, item := range strings.Split(value, ",") {
|
||||
result.Add(strings.TrimSpace(item))
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.KeysStr()
|
||||
}
|
||||
|
||||
func formatCode(code string) string {
|
||||
ret, err := goformat.Source([]byte(code))
|
||||
if err != nil {
|
||||
|
||||
@@ -7,6 +7,7 @@ const (
|
||||
contextDir = interval + "svc"
|
||||
handlerDir = interval + "handler"
|
||||
logicDir = interval + "logic"
|
||||
middlewareDir = interval + "middleware"
|
||||
typesDir = interval + typesPacket
|
||||
groupProperty = "group"
|
||||
)
|
||||
|
||||
@@ -133,7 +133,7 @@ func isTypeBeginLine(line string) bool {
|
||||
}
|
||||
|
||||
func isServiceBeginLine(line string) bool {
|
||||
return strings.HasPrefix(line, "@server(") || (strings.HasPrefix(line, "service") && strings.HasSuffix(line, "{"))
|
||||
return strings.HasPrefix(line, "@server") || (strings.HasPrefix(line, "service") && strings.HasSuffix(line, "{"))
|
||||
}
|
||||
|
||||
func lineBeginOfService(api string) int {
|
||||
|
||||
@@ -42,12 +42,14 @@ func GenConfigCommand(c *cli.Context) error {
|
||||
if err != nil {
|
||||
return errors.New("abs failed: " + c.String("path"))
|
||||
}
|
||||
|
||||
goModPath, hasFound := util.FindGoModPath(path)
|
||||
if !hasFound {
|
||||
return errors.New("go mod not initial")
|
||||
}
|
||||
|
||||
path = strings.TrimSuffix(path, "/config.go")
|
||||
location := path + "/tmp"
|
||||
location := filepath.Join(path, "tmp")
|
||||
err = os.MkdirAll(location, os.ModePerm)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -76,10 +78,12 @@ func GenConfigCommand(c *cli.Context) error {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
path, err = os.Getwd()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = os.Rename(filepath.Dir(goPath)+"/config.yaml", path+"/config.yaml")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
package feature
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/logrusorgru/aurora"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var feature = `
|
||||
1、增加goctl model支持
|
||||
`
|
||||
|
||||
func Feature(_ *cli.Context) error {
|
||||
fmt.Println(aurora.Blue("\nFEATURE:"))
|
||||
fmt.Println(aurora.Blue(feature))
|
||||
return nil
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/tal-tech/go-zero/core/logx"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/api/apigen"
|
||||
@@ -17,15 +18,15 @@ import (
|
||||
"github.com/tal-tech/go-zero/tools/goctl/api/validate"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/configgen"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/docker"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/feature"
|
||||
model "github.com/tal-tech/go-zero/tools/goctl/model/sql/command"
|
||||
rpc "github.com/tal-tech/go-zero/tools/goctl/rpc/command"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/tpl"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var (
|
||||
BuildTime = "not set"
|
||||
commands = []cli.Command{
|
||||
BuildVersion = "20201021"
|
||||
commands = []cli.Command{
|
||||
{
|
||||
Name: "api",
|
||||
Usage: "generate api related files",
|
||||
@@ -50,15 +51,16 @@ var (
|
||||
Name: "dir",
|
||||
Usage: "the format target dir",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "p",
|
||||
Usage: "print result to console",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "iu",
|
||||
Usage: "ignore update",
|
||||
Required: false,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "stdin",
|
||||
Usage: "use stdin to input api doc content, press \"ctrl + d\" to send EOF",
|
||||
Required: false,
|
||||
},
|
||||
},
|
||||
Action: format.GoFormatApi,
|
||||
},
|
||||
@@ -102,13 +104,6 @@ var (
|
||||
},
|
||||
},
|
||||
Action: gogen.GoCommand,
|
||||
Subcommands: []cli.Command{
|
||||
{
|
||||
Name: "template",
|
||||
Usage: "initialize the api templates",
|
||||
Action: gogen.GenTemplates,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "java",
|
||||
@@ -275,12 +270,16 @@ var (
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "src, s",
|
||||
Usage: "the file path of the ddl source file",
|
||||
Usage: "the path or path globbing patterns of the ddl",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "dir, d",
|
||||
Usage: "the target dir",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "style",
|
||||
Usage: "the file naming style, lower|camel|underline,default is lower",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "cache, c",
|
||||
Usage: "generate code with cache [optional]",
|
||||
@@ -302,7 +301,7 @@ var (
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "table, t",
|
||||
Usage: `source table,tables separated by commas,like "user,course`,
|
||||
Usage: `the table or table globbing patterns in the database`,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "cache, c",
|
||||
@@ -312,6 +311,10 @@ var (
|
||||
Name: "dir, d",
|
||||
Usage: "the target dir",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "style",
|
||||
Usage: "the file naming style, lower|camel|underline,default is lower",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "idea",
|
||||
Usage: "for idea plugin [optional]",
|
||||
@@ -335,9 +338,46 @@ var (
|
||||
Action: configgen.GenConfigCommand,
|
||||
},
|
||||
{
|
||||
Name: "feature",
|
||||
Usage: "the features of the latest version",
|
||||
Action: feature.Feature,
|
||||
Name: "template",
|
||||
Usage: "template operation",
|
||||
Subcommands: []cli.Command{
|
||||
{
|
||||
Name: "init",
|
||||
Usage: "initialize the all templates(force update)",
|
||||
Action: tpl.GenTemplates,
|
||||
},
|
||||
{
|
||||
Name: "clean",
|
||||
Usage: "clean the all cache templates",
|
||||
Action: tpl.CleanTemplates,
|
||||
},
|
||||
{
|
||||
Name: "update",
|
||||
Usage: "update template of the target category to the latest",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "category,c",
|
||||
Usage: "the category of template, enum [api,rpc,model]",
|
||||
},
|
||||
},
|
||||
Action: tpl.UpdateTemplates,
|
||||
},
|
||||
{
|
||||
Name: "revert",
|
||||
Usage: "revert the target template to the latest",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "category,c",
|
||||
Usage: "the category of template, enum [api,rpc,model]",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "name,n",
|
||||
Usage: "the target file name of template",
|
||||
},
|
||||
},
|
||||
Action: tpl.RevertTemplates,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
@@ -347,7 +387,7 @@ func main() {
|
||||
|
||||
app := cli.NewApp()
|
||||
app.Usage = "a cli tool to generate code"
|
||||
app.Version = BuildTime
|
||||
app.Version = fmt.Sprintf("%s %s/%s", BuildVersion, runtime.GOOS, runtime.GOARCH)
|
||||
app.Commands = commands
|
||||
// cli already print error messages
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package k8s
|
||||
|
||||
var apiRpcTmeplate = `apiVersion: apps/v1beta2
|
||||
var apiRpcTmeplate = `apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{.name}}
|
||||
@@ -67,6 +67,7 @@ spec:
|
||||
path: /usr/share/zoneinfo/Asia/Shanghai
|
||||
|
||||
---
|
||||
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
@@ -84,6 +85,7 @@ spec:
|
||||
type: NodePort{{if .envIsPreOrPro}}
|
||||
|
||||
---
|
||||
|
||||
apiVersion: autoscaling/v2beta1
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
@@ -105,6 +107,7 @@ spec:
|
||||
targetAverageUtilization: 80
|
||||
|
||||
---
|
||||
|
||||
apiVersion: autoscaling/v2beta1
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
@@ -123,4 +126,5 @@ spec:
|
||||
- type: Resource
|
||||
resource:
|
||||
name: memory
|
||||
targetAverageUtilization: 80{{end}}`
|
||||
targetAverageUtilization: 80{{end}}
|
||||
`
|
||||
@@ -1,6 +1,5 @@
|
||||
package k8s
|
||||
|
||||
// 无环境区分
|
||||
var jobTmeplate = `apiVersion: batch/v1beta1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
@@ -43,4 +42,5 @@ spec:
|
||||
volumes:
|
||||
- name: timezone
|
||||
hostPath:
|
||||
path: /usr/share/zoneinfo/Asia/Shanghai`
|
||||
path: /usr/share/zoneinfo/Asia/Shanghai
|
||||
`
|
||||
|
||||
@@ -7,22 +7,19 @@ import (
|
||||
"text/template"
|
||||
)
|
||||
|
||||
var (
|
||||
errUnknownServiceType = errors.New("unknown service type")
|
||||
const (
|
||||
ServiceTypeApi ServiceType = "api"
|
||||
ServiceTypeRpc ServiceType = "rpc"
|
||||
ServiceTypeJob ServiceType = "job"
|
||||
envDev = "dev"
|
||||
)
|
||||
|
||||
const (
|
||||
ServiceTypeApi ServiceType = "api"
|
||||
ServiceTypeRpc ServiceType = "rpc"
|
||||
ServiceTypeJob ServiceType = "job"
|
||||
ServiceTypeRmq ServiceType = "rmq"
|
||||
ServiceTypeSync ServiceType = "sync"
|
||||
envDev = "dev"
|
||||
)
|
||||
var errUnknownServiceType = errors.New("unknown service type")
|
||||
|
||||
type (
|
||||
ServiceType string
|
||||
K8sRequest struct {
|
||||
|
||||
KubeRequest struct {
|
||||
Env string
|
||||
ServiceName string
|
||||
ServiceType ServiceType
|
||||
@@ -41,20 +38,18 @@ type (
|
||||
}
|
||||
)
|
||||
|
||||
func Gen(req K8sRequest) (string, error) {
|
||||
func Gen(req KubeRequest) (string, error) {
|
||||
switch req.ServiceType {
|
||||
case ServiceTypeApi, ServiceTypeRpc:
|
||||
return genApiRpc(req)
|
||||
case ServiceTypeJob:
|
||||
return genJob(req)
|
||||
case ServiceTypeRmq, ServiceTypeSync:
|
||||
return genRmqSync(req)
|
||||
default:
|
||||
return "", errUnknownServiceType
|
||||
}
|
||||
}
|
||||
|
||||
func genApiRpc(req K8sRequest) (string, error) {
|
||||
func genApiRpc(req KubeRequest) (string, error) {
|
||||
t, err := template.New("api_rpc").Parse(apiRpcTmeplate)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -83,33 +78,7 @@ func genApiRpc(req K8sRequest) (string, error) {
|
||||
return buffer.String(), nil
|
||||
}
|
||||
|
||||
func genRmqSync(req K8sRequest) (string, error) {
|
||||
t, err := template.New("rmq_sync").Parse(rmqSyncTmeplate)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
buffer := new(bytes.Buffer)
|
||||
err = t.Execute(buffer, map[string]interface{}{
|
||||
"name": fmt.Sprintf("%s-%s", req.ServiceName, req.ServiceType),
|
||||
"namespace": req.Namespace,
|
||||
"replicas": req.Replicas,
|
||||
"revisionHistoryLimit": req.RevisionHistoryLimit,
|
||||
"limitCpu": req.LimitCpu,
|
||||
"limitMem": req.LimitMem,
|
||||
"requestCpu": req.RequestCpu,
|
||||
"requestMem": req.RequestMem,
|
||||
"serviceName": req.ServiceName,
|
||||
"env": req.Env,
|
||||
"envIsPreOrPro": req.Env != envDev,
|
||||
"envIsDev": req.Env == envDev,
|
||||
})
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
return buffer.String(), nil
|
||||
}
|
||||
|
||||
func genJob(req K8sRequest) (string, error) {
|
||||
func genJob(req KubeRequest) (string, error) {
|
||||
t, err := template.New("job").Parse(jobTmeplate)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -1,68 +0,0 @@
|
||||
package k8s
|
||||
|
||||
var rmqSyncTmeplate = `apiVersion: apps/v1beta2
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{.name}}
|
||||
namespace: {{.namespace}}
|
||||
labels:
|
||||
app: {{.name}}
|
||||
spec:
|
||||
replicas: {{.replicas}}
|
||||
revisionHistoryLimit: {{.revisionHistoryLimit}}
|
||||
selector:
|
||||
matchLabels:
|
||||
app: {{.name}}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: {{.name}}
|
||||
spec:{{if .envIsDev}}
|
||||
terminationGracePeriodSeconds: 60{{end}}
|
||||
containers:
|
||||
- name: {{.name}}
|
||||
image: registry-vpc.cn-hangzhou.aliyuncs.com/{{.namespace}}/
|
||||
lifecycle:
|
||||
preStop:
|
||||
exec:
|
||||
command: ["sh","-c","sleep 5"]
|
||||
env:
|
||||
- name: aliyun_logs_k8slog
|
||||
value: "stdout"
|
||||
- name: aliyun_logs_k8slog_tags
|
||||
value: "stage={{.env}}"
|
||||
- name: aliyun_logs_k8slog_format
|
||||
value: "json"
|
||||
resources:
|
||||
limits:
|
||||
cpu: {{.limitCpu}}m
|
||||
memory: {{.limitMem}}Mi
|
||||
requests:
|
||||
cpu: {{.requestCpu}}m
|
||||
memory: {{.requestMem}}Mi
|
||||
command:
|
||||
- ./{{.serviceName}}
|
||||
- -f
|
||||
- ./{{.name}}.json
|
||||
volumeMounts:
|
||||
- name: timezone
|
||||
mountPath: /etc/localtime
|
||||
imagePullSecrets:
|
||||
- name: {{.namespace}}
|
||||
volumes:
|
||||
- name: timezone
|
||||
hostPath:
|
||||
path: /usr/share/zoneinfo/Asia/Shanghai{{if .envIsPreOrPro}}
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{.name}}-svc
|
||||
namespace: {{.namespace}}
|
||||
spec:
|
||||
selector:
|
||||
app: {{.name}}
|
||||
sessionAffinity: None
|
||||
type: ClusterIP
|
||||
clusterIP: None{{end}}`
|
||||
@@ -1,5 +1,9 @@
|
||||
# Change log
|
||||
|
||||
## 2020-10-19
|
||||
|
||||
* 增加template
|
||||
|
||||
## 2020-08-20
|
||||
|
||||
* 新增支持通过连接数据库生成model
|
||||
|
||||
@@ -7,7 +7,7 @@ goctl model 为go-zero下的工具模块中的组件之一,目前支持识别m
|
||||
* 通过ddl生成
|
||||
|
||||
```shell script
|
||||
goctl model mysql ddl -src="./sql/user.sql" -dir="./sql/model" -c=true
|
||||
goctl model mysql ddl -src="./*.sql" -dir="./sql/model" -c=true
|
||||
```
|
||||
|
||||
执行上述命令后即可快速生成CURD代码。
|
||||
@@ -21,7 +21,7 @@ goctl model 为go-zero下的工具模块中的组件之一,目前支持识别m
|
||||
* 通过datasource生成
|
||||
|
||||
```shell script
|
||||
goctl model mysql datasource -url="user:password@tcp(127.0.0.1:3306)/database" -table="table1,table2" -dir="./model"
|
||||
goctl model mysql datasource -url="user:password@tcp(127.0.0.1:3306)/database" -table="*" -dir="./model"
|
||||
```
|
||||
|
||||
* 生成代码示例
|
||||
@@ -205,14 +205,49 @@ OPTIONS:
|
||||
* ddl
|
||||
|
||||
```shell script
|
||||
goctl model mysql -src={filename} -dir={dir} -cache=true
|
||||
goctl model mysql -src={patterns} -dir={dir} -cache=true
|
||||
```
|
||||
|
||||
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
|
||||
--cache, -c generate code with cache [optional]
|
||||
--idea for idea plugin [optional]
|
||||
```
|
||||
|
||||
* datasource
|
||||
|
||||
```shell script
|
||||
goctl model mysql datasource -url={datasource} -table={tables} -dir={dir} -cache=true
|
||||
goctl model mysql datasource -url={datasource} -table={patterns} -dir={dir} -cache=true
|
||||
```
|
||||
|
||||
help
|
||||
|
||||
```
|
||||
NAME:
|
||||
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
|
||||
--idea for idea plugin [optional]
|
||||
```
|
||||
|
||||
示例用法请参考[用法](./example/generator.sh)
|
||||
|
||||
目前仅支持redis缓存,如果选择带缓存模式,即生成的`FindOne(ByXxx)`&`Delete`代码会生成带缓存逻辑的代码,目前仅支持单索引字段(除全文索引外),对于联合索引我们默认认为不需要带缓存,且不属于通用型代码,因此没有放在代码生成行列,如example中user表中的`id`、`name`、`mobile`字段均属于单字段索引。
|
||||
|
||||
@@ -221,26 +256,26 @@ OPTIONS:
|
||||
* ddl
|
||||
|
||||
```shell script
|
||||
goctl model -src={filename} -dir={dir}
|
||||
goctl model -src={patterns} -dir={dir}
|
||||
```
|
||||
|
||||
* datasource
|
||||
|
||||
```shell script
|
||||
goctl model mysql datasource -url={datasource} -table={tables} -dir={dir}
|
||||
goctl model mysql datasource -url={datasource} -table={patterns} -dir={dir}
|
||||
```
|
||||
|
||||
or
|
||||
* ddl
|
||||
|
||||
```shell script
|
||||
goctl model -src={filename} -dir={dir} -cache=false
|
||||
goctl model -src={patterns} -dir={dir} -cache=false
|
||||
```
|
||||
|
||||
* datasource
|
||||
|
||||
```shell script
|
||||
goctl model mysql datasource -url={datasource} -table={tables} -dir={dir} -cache=false
|
||||
goctl model mysql datasource -url={datasource} -table={patterns} -dir={dir} -cache=false
|
||||
```
|
||||
|
||||
生成代码仅基本的CURD结构。
|
||||
@@ -265,8 +300,3 @@ OPTIONS:
|
||||
|
||||
目前,我认为除了基本的CURD外,其他的代码均属于<i>业务型</i>代码,这个我觉得开发人员根据业务需要进行编写更好。
|
||||
|
||||
## QA
|
||||
|
||||
* goctl model除了命令行模式,支持插件模式吗?
|
||||
|
||||
很快支持idea插件。
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/tal-tech/go-zero/core/collection"
|
||||
"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/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"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/console"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
@@ -19,6 +22,7 @@ const (
|
||||
flagDir = "dir"
|
||||
flagCache = "cache"
|
||||
flagIdea = "idea"
|
||||
flagStyle = "style"
|
||||
flagUrl = "url"
|
||||
flagTable = "table"
|
||||
)
|
||||
@@ -28,17 +32,35 @@ func MysqlDDL(ctx *cli.Context) error {
|
||||
dir := ctx.String(flagDir)
|
||||
cache := ctx.Bool(flagCache)
|
||||
idea := ctx.Bool(flagIdea)
|
||||
namingStyle := strings.TrimSpace(ctx.String(flagStyle))
|
||||
log := console.NewConsole(idea)
|
||||
fileSrc, err := filepath.Abs(src)
|
||||
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.NamingUnderline:
|
||||
case "":
|
||||
namingStyle = gen.NamingLower
|
||||
default:
|
||||
return fmt.Errorf("unexpected naming style: %s", namingStyle)
|
||||
}
|
||||
|
||||
files, err := util.MatchFiles(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data, err := ioutil.ReadFile(fileSrc)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
var source []string
|
||||
for _, file := range files {
|
||||
data, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
source = append(source, string(data))
|
||||
}
|
||||
source := string(data)
|
||||
generator := gen.NewDefaultGenerator(source, dir, gen.WithConsoleOption(log))
|
||||
generator := gen.NewDefaultGenerator(strings.Join(source, "\n"), dir, namingStyle, gen.WithConsoleOption(log))
|
||||
err = generator.Start(cache)
|
||||
if err != nil {
|
||||
log.Error("%v", err)
|
||||
@@ -51,36 +73,72 @@ func MyDataSource(ctx *cli.Context) error {
|
||||
dir := strings.TrimSpace(ctx.String(flagDir))
|
||||
cache := ctx.Bool(flagCache)
|
||||
idea := ctx.Bool(flagIdea)
|
||||
table := strings.TrimSpace(ctx.String(flagTable))
|
||||
namingStyle := strings.TrimSpace(ctx.String(flagStyle))
|
||||
pattern := strings.TrimSpace(ctx.String(flagTable))
|
||||
log := console.NewConsole(idea)
|
||||
if len(url) == 0 {
|
||||
log.Error("%v", "expected data source of mysql, but is empty")
|
||||
log.Error("%v", "expected data source of mysql, but nothing found")
|
||||
return nil
|
||||
}
|
||||
if len(table) == 0 {
|
||||
log.Error("%v", "expected table(s), but nothing found")
|
||||
|
||||
if len(pattern) == 0 {
|
||||
log.Error("%v", "expected table or table globbing patterns, but nothing found")
|
||||
return nil
|
||||
}
|
||||
|
||||
switch namingStyle {
|
||||
case gen.NamingLower, gen.NamingCamel, gen.NamingUnderline:
|
||||
case "":
|
||||
namingStyle = gen.NamingLower
|
||||
default:
|
||||
return fmt.Errorf("unexpected naming style: %s", namingStyle)
|
||||
}
|
||||
|
||||
cfg, err := mysql.ParseDSN(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logx.Disable()
|
||||
conn := sqlx.NewMysql(url)
|
||||
databaseSource := strings.TrimSuffix(url, "/"+cfg.DBName) + "/information_schema"
|
||||
db := sqlx.NewMysql(databaseSource)
|
||||
m := model.NewDDLModel(conn)
|
||||
tables := collection.NewSet()
|
||||
for _, item := range strings.Split(table, ",") {
|
||||
item = strings.TrimSpace(item)
|
||||
if len(item) == 0 {
|
||||
im := model.NewInformationSchemaModel(db)
|
||||
|
||||
tables, err := im.GetAllTables(cfg.DBName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var matchTables []string
|
||||
for _, item := range tables {
|
||||
match, err := filepath.Match(pattern, item)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !match {
|
||||
continue
|
||||
}
|
||||
tables.AddStr(item)
|
||||
|
||||
matchTables = append(matchTables, item)
|
||||
}
|
||||
ddl, err := m.ShowDDL(tables.KeysStr()...)
|
||||
if len(matchTables) == 0 {
|
||||
return errors.New("no tables matched")
|
||||
}
|
||||
|
||||
ddl, err := m.ShowDDL(matchTables...)
|
||||
if err != nil {
|
||||
log.Error("%v", err)
|
||||
return nil
|
||||
}
|
||||
generator := gen.NewDefaultGenerator(strings.Join(ddl, "\n"), dir, gen.WithConsoleOption(log))
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
20
tools/goctl/model/sql/converter/types_test.go
Normal file
20
tools/goctl/model/sql/converter/types_test.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package converter
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestConvertDataType(t *testing.T) {
|
||||
v, err := ConvertDataType("tinyint")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "int64", v)
|
||||
|
||||
v, err = ConvertDataType("timestamp")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "time.Time", v)
|
||||
|
||||
_, err = ConvertDataType("float32")
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
@@ -1,7 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
# generate model with cache from ddl
|
||||
goctl model mysql ddl -src="./sql/user.sql" -dir="./sql/model" -c
|
||||
goctl model mysql ddl -src="./sql/*.sql" -dir="./sql/model/user" -c
|
||||
|
||||
# generate model with cache from data source
|
||||
goctl model mysql datasource -url="user:password@tcp(127.0.0.1:3306)/database" -table="table1,table2" -dir="./model"
|
||||
#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/sql/user_1.sql
Normal file
15
tools/goctl/model/sql/example/sql/user_1.sql
Normal file
@@ -0,0 +1,15 @@
|
||||
-- 用户表 --
|
||||
CREATE TABLE `user1` (
|
||||
`id` bigint(10) NOT NULL AUTO_INCREMENT,
|
||||
`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 '手机号',
|
||||
`gender` char(5) COLLATE utf8mb4_general_ci NOT NULL COMMENT '男|女|未公开',
|
||||
`nickname` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '用户昵称',
|
||||
`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `name_index` (`name`),
|
||||
UNIQUE KEY `mobile_index` (`mobile`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
|
||||
"github.com/tal-tech/go-zero/core/collection"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/model/sql/template"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/templatex"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
|
||||
)
|
||||
|
||||
@@ -22,8 +22,13 @@ func genDelete(table Table, withCache bool) (string, error) {
|
||||
}
|
||||
|
||||
camel := table.Name.ToCamel()
|
||||
output, err := templatex.With("delete").
|
||||
Parse(template.Delete).
|
||||
text, err := util.LoadTemplate(category, deleteTemplateFile, template.Delete)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
output, err := util.With("delete").
|
||||
Parse(text).
|
||||
Execute(map[string]interface{}{
|
||||
"upperStartCamelObject": camel,
|
||||
"withCache": withCache,
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/model/sql/parser"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/model/sql/template"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/templatex"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
)
|
||||
|
||||
func genFields(fields []parser.Field) (string, error) {
|
||||
@@ -25,8 +25,14 @@ func genField(field parser.Field) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
output, err := templatex.With("types").
|
||||
Parse(template.Field).
|
||||
|
||||
text, err := util.LoadTemplate(category, fieldTemplateFile, template.Field)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
output, err := util.With("types").
|
||||
Parse(text).
|
||||
Execute(map[string]interface{}{
|
||||
"name": field.Name.ToCamel(),
|
||||
"type": field.DataType,
|
||||
|
||||
@@ -2,14 +2,19 @@ package gen
|
||||
|
||||
import (
|
||||
"github.com/tal-tech/go-zero/tools/goctl/model/sql/template"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/templatex"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
|
||||
)
|
||||
|
||||
func genFindOne(table Table, withCache bool) (string, error) {
|
||||
camel := table.Name.ToCamel()
|
||||
output, err := templatex.With("findOne").
|
||||
Parse(template.FindOne).
|
||||
text, err := util.LoadTemplate(category, findOneTemplateFile, template.FindOne)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
output, err := util.With("findOne").
|
||||
Parse(text).
|
||||
Execute(map[string]interface{}{
|
||||
"withCache": withCache,
|
||||
"upperStartCamelObject": camel,
|
||||
|
||||
@@ -5,12 +5,17 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/model/sql/template"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/templatex"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
|
||||
)
|
||||
|
||||
func genFindOneByField(table Table, withCache bool) (string, string, error) {
|
||||
t := templatex.With("findOneByField").Parse(template.FindOneByField)
|
||||
text, err := util.LoadTemplate(category, findOneByFieldTemplateFile, template.FindOneByField)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
t := util.With("findOneByField").Parse(text)
|
||||
var list []string
|
||||
camelTableName := table.Name.ToCamel()
|
||||
for _, field := range table.Fields {
|
||||
@@ -33,10 +38,16 @@ func genFindOneByField(table Table, withCache bool) (string, string, error) {
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
list = append(list, output.String())
|
||||
}
|
||||
if withCache {
|
||||
out, err := templatex.With("findOneByFieldExtraMethod").Parse(template.FindOneByFieldExtraMethod).Execute(map[string]interface{}{
|
||||
text, err := util.LoadTemplate(category, findOneByFieldExtraMethodTemplateFile, template.FindOneByFieldExtraMethod)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
out, err := util.With("findOneByFieldExtraMethod").Parse(text).Execute(map[string]interface{}{
|
||||
"upperStartCamelObject": camelTableName,
|
||||
"primaryKeyLeft": table.CacheKey[table.PrimaryKey.Name.Source()].Left,
|
||||
"lowerStartCamelObject": stringx.From(camelTableName).UnTitle(),
|
||||
@@ -45,6 +56,7 @@ func genFindOneByField(table Table, withCache bool) (string, string, error) {
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
return strings.Join(list, "\n"), out.String(), nil
|
||||
}
|
||||
return strings.Join(list, "\n"), "", nil
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/model/sql/parser"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/model/sql/template"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/templatex"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/console"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
|
||||
@@ -18,6 +17,9 @@ import (
|
||||
const (
|
||||
pwd = "."
|
||||
createTableFlag = `(?m)^(?i)CREATE\s+TABLE` // ignore case
|
||||
NamingLower = "lower"
|
||||
NamingCamel = "camel"
|
||||
NamingUnderline = "underline"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -25,15 +27,17 @@ type (
|
||||
source string
|
||||
dir string
|
||||
console.Console
|
||||
pkg string
|
||||
namingStyle string
|
||||
}
|
||||
Option func(generator *defaultGenerator)
|
||||
)
|
||||
|
||||
func NewDefaultGenerator(source, dir string, opt ...Option) *defaultGenerator {
|
||||
func NewDefaultGenerator(source, dir, namingStyle string, opt ...Option) *defaultGenerator {
|
||||
if dir == "" {
|
||||
dir = pwd
|
||||
}
|
||||
generator := &defaultGenerator{source: source, dir: dir}
|
||||
generator := &defaultGenerator{source: source, dir: dir, namingStyle: namingStyle}
|
||||
var optionList []Option
|
||||
optionList = append(optionList, newDefaultOption())
|
||||
optionList = append(optionList, opt...)
|
||||
@@ -60,6 +64,8 @@ func (g *defaultGenerator) Start(withCache bool) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
g.dir = dirAbs
|
||||
g.pkg = filepath.Base(dirAbs)
|
||||
err = util.MkdirIfNotExist(dirAbs)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -70,7 +76,14 @@ func (g *defaultGenerator) Start(withCache bool) error {
|
||||
}
|
||||
|
||||
for tableName, code := range modelList {
|
||||
name := fmt.Sprintf("%smodel.go", strings.ToLower(stringx.From(tableName).ToCamel()))
|
||||
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 NamingUnderline:
|
||||
name = fmt.Sprintf("%s_model.go", tn.ToSnake())
|
||||
}
|
||||
filename := filepath.Join(dirAbs, name)
|
||||
if util.FileExists(filename) {
|
||||
g.Warning("%s already exists, ignored.", name)
|
||||
@@ -83,12 +96,18 @@ func (g *defaultGenerator) Start(withCache bool) error {
|
||||
}
|
||||
// generate error file
|
||||
filename := filepath.Join(dirAbs, "vars.go")
|
||||
if !util.FileExists(filename) {
|
||||
err = ioutil.WriteFile(filename, []byte(template.Error), os.ModePerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
text, err := util.LoadTemplate(category, errTemplateFile, template.Error)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = util.With("vars").Parse(text).SaveTo(map[string]interface{}{
|
||||
"pkg": g.pkg,
|
||||
}, filename, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
g.Success("Done.")
|
||||
return nil
|
||||
}
|
||||
@@ -120,8 +139,12 @@ type (
|
||||
)
|
||||
|
||||
func (g *defaultGenerator) genModel(in parser.Table, withCache bool) (string, error) {
|
||||
t := templatex.With("model").
|
||||
Parse(template.Model).
|
||||
text, err := util.LoadTemplate(category, modelTemplateFile, template.Model)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
t := util.With("model").
|
||||
Parse(text).
|
||||
GoFmt(true)
|
||||
|
||||
m, err := genCacheKeys(in)
|
||||
@@ -189,6 +212,7 @@ func (g *defaultGenerator) genModel(in parser.Table, withCache bool) (string, er
|
||||
}
|
||||
|
||||
output, err := t.Execute(map[string]interface{}{
|
||||
"pkg": g.pkg,
|
||||
"imports": importsCode,
|
||||
"vars": varsCode,
|
||||
"types": typesCode,
|
||||
|
||||
34
tools/goctl/model/sql/gen/gen_test.go
Normal file
34
tools/goctl/model/sql/gen/gen_test.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package gen
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tal-tech/go-zero/core/logx"
|
||||
)
|
||||
|
||||
var (
|
||||
source = "CREATE TABLE `test_user_info` (\n `id` bigint NOT NULL AUTO_INCREMENT,\n `nanosecond` bigint NOT NULL DEFAULT '0',\n `data` varchar(255) DEFAULT '',\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 `nanosecond_unique` (`nanosecond`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;"
|
||||
)
|
||||
|
||||
func TestCacheModel(t *testing.T) {
|
||||
logx.Disable()
|
||||
_ = Clean()
|
||||
g := NewDefaultGenerator(source, "./testmodel/cache", NamingLower)
|
||||
err := g.Start(true)
|
||||
assert.Nil(t, err)
|
||||
g = NewDefaultGenerator(source, "./testmodel/nocache", NamingLower)
|
||||
err = g.Start(false)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestNamingModel(t *testing.T) {
|
||||
logx.Disable()
|
||||
_ = Clean()
|
||||
g := NewDefaultGenerator(source, "./testmodel/camel", NamingCamel)
|
||||
err := g.Start(true)
|
||||
assert.Nil(t, err)
|
||||
g = NewDefaultGenerator(source, "./testmodel/snake", NamingUnderline)
|
||||
err = g.Start(true)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
@@ -2,25 +2,37 @@ package gen
|
||||
|
||||
import (
|
||||
"github.com/tal-tech/go-zero/tools/goctl/model/sql/template"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/templatex"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
)
|
||||
|
||||
func genImports(withCache, timeImport bool) (string, error) {
|
||||
if withCache {
|
||||
buffer, err := templatex.With("import").Parse(template.Imports).Execute(map[string]interface{}{
|
||||
text, err := util.LoadTemplate(category, importsTemplateFile, template.Imports)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
buffer, err := util.With("import").Parse(text).Execute(map[string]interface{}{
|
||||
"time": timeImport,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return buffer.String(), nil
|
||||
} else {
|
||||
buffer, err := templatex.With("import").Parse(template.ImportsNoCache).Execute(map[string]interface{}{
|
||||
text, err := util.LoadTemplate(category, importsWithNoCacheTemplateFile, template.ImportsNoCache)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
buffer, err := util.With("import").Parse(text).Execute(map[string]interface{}{
|
||||
"time": timeImport,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return buffer.String(), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
|
||||
"github.com/tal-tech/go-zero/core/collection"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/model/sql/template"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/templatex"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
|
||||
)
|
||||
|
||||
@@ -34,8 +34,13 @@ func genInsert(table Table, withCache bool) (string, error) {
|
||||
expressionValues = append(expressionValues, "data."+camel)
|
||||
}
|
||||
camel := table.Name.ToCamel()
|
||||
output, err := templatex.With("insert").
|
||||
Parse(template.Insert).
|
||||
text, err := util.LoadTemplate(category, insertTemplateFile, template.Insert)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
output, err := util.With("insert").
|
||||
Parse(text).
|
||||
Execute(map[string]interface{}{
|
||||
"withCache": withCache,
|
||||
"containsIndexCache": table.ContainsUniqueKey,
|
||||
@@ -49,5 +54,6 @@ func genInsert(table Table, withCache bool) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return output.String(), nil
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ type (
|
||||
// {{prefix}}=cache
|
||||
// key:id
|
||||
Key struct {
|
||||
VarExpression string // cacheUserIdPrefix="cache#user#id#"
|
||||
VarExpression string // cacheUserIdPrefix = "cache#User#id#"
|
||||
Left string // cacheUserIdPrefix
|
||||
Right string // cache#user#id#
|
||||
Variable string // userIdKey
|
||||
|
||||
77
tools/goctl/model/sql/gen/keys_test.go
Normal file
77
tools/goctl/model/sql/gen/keys_test.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package gen
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/model/sql/parser"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
|
||||
)
|
||||
|
||||
func TestGenCacheKeys(t *testing.T) {
|
||||
m, err := genCacheKeys(parser.Table{
|
||||
Name: stringx.From("user"),
|
||||
PrimaryKey: parser.Primary{
|
||||
Field: parser.Field{
|
||||
Name: stringx.From("id"),
|
||||
DataBaseType: "bigint",
|
||||
DataType: "int64",
|
||||
IsKey: false,
|
||||
IsPrimaryKey: true,
|
||||
IsUniqueKey: false,
|
||||
Comment: "自增id",
|
||||
},
|
||||
AutoIncrement: true,
|
||||
},
|
||||
Fields: []parser.Field{
|
||||
{
|
||||
Name: stringx.From("mobile"),
|
||||
DataBaseType: "varchar",
|
||||
DataType: "string",
|
||||
IsKey: false,
|
||||
IsPrimaryKey: false,
|
||||
IsUniqueKey: true,
|
||||
Comment: "手机号",
|
||||
},
|
||||
{
|
||||
Name: stringx.From("name"),
|
||||
DataBaseType: "varchar",
|
||||
DataType: "string",
|
||||
IsKey: false,
|
||||
IsPrimaryKey: false,
|
||||
IsUniqueKey: true,
|
||||
Comment: "姓名",
|
||||
},
|
||||
{
|
||||
Name: stringx.From("createTime"),
|
||||
DataBaseType: "timestamp",
|
||||
DataType: "time.Time",
|
||||
IsKey: false,
|
||||
IsPrimaryKey: false,
|
||||
IsUniqueKey: false,
|
||||
Comment: "创建时间",
|
||||
},
|
||||
{
|
||||
Name: stringx.From("updateTime"),
|
||||
DataBaseType: "timestamp",
|
||||
DataType: "time.Time",
|
||||
IsKey: false,
|
||||
IsPrimaryKey: false,
|
||||
IsUniqueKey: false,
|
||||
Comment: "更新时间",
|
||||
},
|
||||
},
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
|
||||
for fieldName, key := range m {
|
||||
name := stringx.From(fieldName)
|
||||
assert.Equal(t, fmt.Sprintf(`cacheUser%sPrefix = "cache#User#%s#"`, name.ToCamel(), name.UnTitle()), key.VarExpression)
|
||||
assert.Equal(t, fmt.Sprintf(`cacheUser%sPrefix`, name.ToCamel()), key.Left)
|
||||
assert.Equal(t, fmt.Sprintf(`cache#User#%s#`, name.UnTitle()), key.Right)
|
||||
assert.Equal(t, fmt.Sprintf(`user%sKey`, name.ToCamel()), key.Variable)
|
||||
assert.Equal(t, `user`+name.ToCamel()+`Key := fmt.Sprintf("%s%v", cacheUser`+name.ToCamel()+`Prefix,`+name.UnTitle()+`)`, key.KeyExpression)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,18 +2,25 @@ package gen
|
||||
|
||||
import (
|
||||
"github.com/tal-tech/go-zero/tools/goctl/model/sql/template"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/templatex"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
)
|
||||
|
||||
func genNew(table Table, withCache bool) (string, error) {
|
||||
output, err := templatex.With("new").
|
||||
Parse(template.New).
|
||||
text, err := util.LoadTemplate(category, modelNewTemplateFile, template.New)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
output, err := util.With("new").
|
||||
Parse(text).
|
||||
Execute(map[string]interface{}{
|
||||
"table": table.Name.Source(),
|
||||
"withCache": withCache,
|
||||
"upperStartCamelObject": table.Name.ToCamel(),
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return output.String(), nil
|
||||
}
|
||||
|
||||
@@ -2,15 +2,20 @@ package gen
|
||||
|
||||
import (
|
||||
"github.com/tal-tech/go-zero/tools/goctl/model/sql/template"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/templatex"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
)
|
||||
|
||||
func genTag(in string) (string, error) {
|
||||
if in == "" {
|
||||
return in, nil
|
||||
}
|
||||
output, err := templatex.With("tag").
|
||||
Parse(template.Tag).
|
||||
text, err := util.LoadTemplate(category, tagTemplateFile, template.Tag)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
output, err := util.With("tag").
|
||||
Parse(text).
|
||||
Execute(map[string]interface{}{
|
||||
"field": in,
|
||||
})
|
||||
|
||||
74
tools/goctl/model/sql/gen/template.go
Normal file
74
tools/goctl/model/sql/gen/template.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package gen
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/model/sql/template"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
const (
|
||||
category = "model"
|
||||
deleteTemplateFile = "delete.tpl"
|
||||
fieldTemplateFile = "filed.tpl"
|
||||
findOneTemplateFile = "find-one.tpl"
|
||||
findOneByFieldTemplateFile = "find-one-by-field.tpl"
|
||||
findOneByFieldExtraMethodTemplateFile = "find-one-by-filed-extra-method.tpl"
|
||||
importsTemplateFile = "import.tpl"
|
||||
importsWithNoCacheTemplateFile = "import-no-cache.tpl"
|
||||
insertTemplateFile = "insert.tpl"
|
||||
modelTemplateFile = "model.tpl"
|
||||
modelNewTemplateFile = "model-new.tpl"
|
||||
tagTemplateFile = "tag.tpl"
|
||||
typesTemplateFile = "types.tpl"
|
||||
updateTemplateFile = "update.tpl"
|
||||
varTemplateFile = "var.tpl"
|
||||
errTemplateFile = "err.tpl"
|
||||
)
|
||||
|
||||
var templates = map[string]string{
|
||||
deleteTemplateFile: template.Delete,
|
||||
fieldTemplateFile: template.Field,
|
||||
findOneTemplateFile: template.FindOne,
|
||||
findOneByFieldTemplateFile: template.FindOneByField,
|
||||
findOneByFieldExtraMethodTemplateFile: template.FindOneByFieldExtraMethod,
|
||||
importsTemplateFile: template.Imports,
|
||||
importsWithNoCacheTemplateFile: template.ImportsNoCache,
|
||||
insertTemplateFile: template.Insert,
|
||||
modelTemplateFile: template.Model,
|
||||
modelNewTemplateFile: template.New,
|
||||
tagTemplateFile: template.Tag,
|
||||
typesTemplateFile: template.Types,
|
||||
updateTemplateFile: template.Update,
|
||||
varTemplateFile: template.Vars,
|
||||
errTemplateFile: template.Error,
|
||||
}
|
||||
|
||||
func GenTemplates(_ *cli.Context) error {
|
||||
return util.InitTemplates(category, templates)
|
||||
}
|
||||
|
||||
func RevertTemplate(name string) error {
|
||||
content, ok := templates[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("%s: no such file name", name)
|
||||
}
|
||||
return util.CreateTemplate(category, name, content)
|
||||
}
|
||||
|
||||
func Clean() error {
|
||||
return util.Clean(category)
|
||||
}
|
||||
|
||||
func Update(category string) error {
|
||||
err := Clean()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return util.InitTemplates(category, templates)
|
||||
}
|
||||
|
||||
func GetCategory() string {
|
||||
return category
|
||||
}
|
||||
93
tools/goctl/model/sql/gen/template_test.go
Normal file
93
tools/goctl/model/sql/gen/template_test.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package gen
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/model/sql/template"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
)
|
||||
|
||||
func TestGenTemplates(t *testing.T) {
|
||||
err := util.InitTemplates(category, templates)
|
||||
assert.Nil(t, err)
|
||||
dir, err := util.GetTemplateDir(category)
|
||||
assert.Nil(t, err)
|
||||
file := filepath.Join(dir, "model-new.tpl")
|
||||
data, err := ioutil.ReadFile(file)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, string(data), template.New)
|
||||
}
|
||||
|
||||
func TestRevertTemplate(t *testing.T) {
|
||||
name := "model-new.tpl"
|
||||
err := util.InitTemplates(category, templates)
|
||||
assert.Nil(t, err)
|
||||
|
||||
dir, err := util.GetTemplateDir(category)
|
||||
assert.Nil(t, err)
|
||||
|
||||
file := filepath.Join(dir, name)
|
||||
data, err := ioutil.ReadFile(file)
|
||||
assert.Nil(t, err)
|
||||
|
||||
modifyData := string(data) + "modify"
|
||||
err = util.CreateTemplate(category, name, modifyData)
|
||||
assert.Nil(t, err)
|
||||
|
||||
data, err = ioutil.ReadFile(file)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, string(data), modifyData)
|
||||
|
||||
assert.Nil(t, RevertTemplate(name))
|
||||
|
||||
data, err = ioutil.ReadFile(file)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, template.New, string(data))
|
||||
}
|
||||
|
||||
func TestClean(t *testing.T) {
|
||||
name := "model-new.tpl"
|
||||
err := util.InitTemplates(category, templates)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Nil(t, Clean())
|
||||
|
||||
dir, err := util.GetTemplateDir(category)
|
||||
assert.Nil(t, err)
|
||||
|
||||
file := filepath.Join(dir, name)
|
||||
_, err = ioutil.ReadFile(file)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestUpdate(t *testing.T) {
|
||||
name := "model-new.tpl"
|
||||
err := util.InitTemplates(category, templates)
|
||||
assert.Nil(t, err)
|
||||
|
||||
dir, err := util.GetTemplateDir(category)
|
||||
assert.Nil(t, err)
|
||||
|
||||
file := filepath.Join(dir, name)
|
||||
data, err := ioutil.ReadFile(file)
|
||||
assert.Nil(t, err)
|
||||
|
||||
modifyData := string(data) + "modify"
|
||||
err = util.CreateTemplate(category, name, modifyData)
|
||||
assert.Nil(t, err)
|
||||
|
||||
data, err = ioutil.ReadFile(file)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, string(data), modifyData)
|
||||
|
||||
assert.Nil(t, Update(category))
|
||||
|
||||
data, err = ioutil.ReadFile(file)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, template.New, string(data))
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package gen
|
||||
|
||||
import (
|
||||
"github.com/tal-tech/go-zero/tools/goctl/model/sql/template"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/templatex"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
)
|
||||
|
||||
func genTypes(table Table, withCache bool) (string, error) {
|
||||
@@ -11,8 +11,14 @@ func genTypes(table Table, withCache bool) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
output, err := templatex.With("types").
|
||||
Parse(template.Types).
|
||||
|
||||
text, err := util.LoadTemplate(category, typesTemplateFile, template.Types)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
output, err := util.With("types").
|
||||
Parse(text).
|
||||
Execute(map[string]interface{}{
|
||||
"withCache": withCache,
|
||||
"upperStartCamelObject": table.Name.ToCamel(),
|
||||
@@ -21,5 +27,6 @@ func genTypes(table Table, withCache bool) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return output.String(), nil
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/model/sql/template"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/templatex"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
|
||||
)
|
||||
|
||||
@@ -22,8 +22,13 @@ func genUpdate(table Table, withCache bool) (string, error) {
|
||||
}
|
||||
expressionValues = append(expressionValues, "data."+table.PrimaryKey.Name.ToCamel())
|
||||
camelTableName := table.Name.ToCamel()
|
||||
output, err := templatex.With("update").
|
||||
Parse(template.Update).
|
||||
text, err := util.LoadTemplate(category, updateTemplateFile, template.Update)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
output, err := util.With("update").
|
||||
Parse(text).
|
||||
Execute(map[string]interface{}{
|
||||
"withCache": withCache,
|
||||
"upperStartCamelObject": camelTableName,
|
||||
@@ -36,5 +41,6 @@ func genUpdate(table Table, withCache bool) (string, error) {
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return output.String(), nil
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/model/sql/template"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/templatex"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
|
||||
)
|
||||
|
||||
@@ -14,8 +14,13 @@ func genVars(table Table, withCache bool) (string, error) {
|
||||
keys = append(keys, v.VarExpression)
|
||||
}
|
||||
camel := table.Name.ToCamel()
|
||||
output, err := templatex.With("var").
|
||||
Parse(template.Vars).
|
||||
text, err := util.LoadTemplate(category, varTemplateFile, template.Vars)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
output, err := util.With("var").
|
||||
Parse(text).
|
||||
GoFmt(true).
|
||||
Execute(map[string]interface{}{
|
||||
"lowerStartCamelObject": stringx.From(camel).UnTitle(),
|
||||
|
||||
25
tools/goctl/model/sql/model/informationschemamodel.go
Normal file
25
tools/goctl/model/sql/model/informationschemamodel.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"github.com/tal-tech/go-zero/core/stores/sqlx"
|
||||
)
|
||||
|
||||
type (
|
||||
InformationSchemaModel struct {
|
||||
conn sqlx.SqlConn
|
||||
}
|
||||
)
|
||||
|
||||
func NewInformationSchemaModel(conn sqlx.SqlConn) *InformationSchemaModel {
|
||||
return &InformationSchemaModel{conn: conn}
|
||||
}
|
||||
|
||||
func (m *InformationSchemaModel) GetAllTables(database string) ([]string, error) {
|
||||
query := `select TABLE_NAME from TABLES where TABLE_SCHEMA = ?`
|
||||
var tables []string
|
||||
err := m.conn.QueryRows(&tables, query, database)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tables, nil
|
||||
}
|
||||
@@ -7,5 +7,5 @@ import (
|
||||
var (
|
||||
unSupportDDL = errors.New("unexpected type")
|
||||
tableBodyIsNotFound = errors.New("create table spec not found")
|
||||
errPrimaryKey = errors.New("unexpected joint primary key")
|
||||
errPrimaryKey = errors.New("unexpected join primary key")
|
||||
)
|
||||
|
||||
@@ -17,11 +17,9 @@ func TestParseSelect(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestParseCreateTable(t *testing.T) {
|
||||
_, err := Parse("CREATE TABLE `user_snake` (\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 KEY `mobile_index` (`mobile`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;")
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestParseCreateTable2(t *testing.T) {
|
||||
_, err := Parse("create table `user_snake` (\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 KEY `mobile_index` (`mobile`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;")
|
||||
table, err := Parse("CREATE TABLE `user_snake` (\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 KEY `mobile_index` (`mobile`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "user_snake", table.Name.Source())
|
||||
assert.Equal(t, "id", table.PrimaryKey.Name.Source())
|
||||
assert.Equal(t, true, table.ContainsTime())
|
||||
}
|
||||
|
||||
@@ -9,9 +9,9 @@ func (m *{{.upperStartCamelObject}}Model) Delete({{.lowerStartCamelPrimaryKey}}
|
||||
|
||||
{{.keys}}
|
||||
_, err {{if .containsIndexCache}}={{else}}:={{end}} m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
|
||||
query := ` + "`" + `delete from ` + "` +" + ` m.table + ` + " `" + ` where {{.originalPrimaryKey}} = ?` + "`" + `
|
||||
query := fmt.Sprintf("delete from %s where {{.originalPrimaryKey}} = ?", m.table)
|
||||
return conn.Exec(query, {{.lowerStartCamelPrimaryKey}})
|
||||
}, {{.keyValues}}){{else}}query := ` + "`" + `delete from ` + "` +" + ` m.table + ` + " `" + ` where {{.originalPrimaryKey}} = ?` + "`" + `
|
||||
}, {{.keyValues}}){{else}}query := fmt.Sprintf("delete from %s where {{.originalPrimaryKey}} = ?", m.table)
|
||||
_,err:=m.conn.Exec(query, {{.lowerStartCamelPrimaryKey}}){{end}}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package template
|
||||
|
||||
var Error = `package model
|
||||
var Error = `package {{.pkg}}
|
||||
|
||||
import "github.com/tal-tech/go-zero/core/stores/sqlx"
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ func (m *{{.upperStartCamelObject}}Model) FindOne({{.lowerStartCamelPrimaryKey}}
|
||||
{{if .withCache}}{{.cacheKey}}
|
||||
var resp {{.upperStartCamelObject}}
|
||||
err := m.QueryRow(&resp, {{.cacheKeyVariable}}, func(conn sqlx.SqlConn, v interface{}) error {
|
||||
query := ` + "`" + `select ` + "`" + ` + {{.lowerStartCamelObject}}Rows + ` + "`" + ` from ` + "` + " + `m.table ` + " + `" + ` where {{.originalPrimaryKey}} = ? limit 1` + "`" + `
|
||||
query := fmt.Sprintf("select %s from %s where {{.originalPrimaryKey}} = ? limit 1", {{.lowerStartCamelObject}}Rows, m.table)
|
||||
return conn.QueryRow(v, query, {{.lowerStartCamelPrimaryKey}})
|
||||
})
|
||||
switch err {
|
||||
@@ -16,7 +16,7 @@ func (m *{{.upperStartCamelObject}}Model) FindOne({{.lowerStartCamelPrimaryKey}}
|
||||
return nil, ErrNotFound
|
||||
default:
|
||||
return nil, err
|
||||
}{{else}}query := ` + "`" + `select ` + "`" + ` + {{.lowerStartCamelObject}}Rows + ` + "`" + ` from ` + "` + " + `m.table ` + " + `" + ` where {{.originalPrimaryKey}} = ? limit 1` + "`" + `
|
||||
}{{else}}query := fmt.Sprintf("select %s from %s where {{.originalPrimaryKey}} = ? limit 1", {{.lowerStartCamelObject}}Rows, m.table)
|
||||
var resp {{.upperStartCamelObject}}
|
||||
err := m.conn.QueryRow(&resp, query, {{.lowerStartCamelPrimaryKey}})
|
||||
switch err {
|
||||
@@ -36,7 +36,7 @@ func (m *{{.upperStartCamelObject}}Model) FindOneBy{{.upperField}}({{.in}}) (*{{
|
||||
{{if .withCache}}{{.cacheKey}}
|
||||
var resp {{.upperStartCamelObject}}
|
||||
err := m.QueryRowIndex(&resp, {{.cacheKeyVariable}}, m.formatPrimary, func(conn sqlx.SqlConn, v interface{}) (i interface{}, e error) {
|
||||
query := ` + "`" + `select ` + "`" + ` + {{.lowerStartCamelObject}}Rows + ` + "`" + ` from ` + "` + " + `m.table ` + " + `" + ` where {{.originalField}} = ? limit 1` + "`" + `
|
||||
query := fmt.Sprintf("select %s from %s where {{.originalField}} = ? limit 1", {{.lowerStartCamelObject}}Rows, m.table)
|
||||
if err := conn.QueryRow(&resp, query, {{.lowerStartCamelField}}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -51,7 +51,7 @@ func (m *{{.upperStartCamelObject}}Model) FindOneBy{{.upperField}}({{.in}}) (*{{
|
||||
return nil, err
|
||||
}
|
||||
}{{else}}var resp {{.upperStartCamelObject}}
|
||||
query := ` + "`" + `select ` + "`" + ` + {{.lowerStartCamelObject}}Rows + ` + "`" + ` from ` + "` + " + `m.table ` + " + `" + ` where {{.originalField}} limit 1` + "`" + `
|
||||
query := fmt.Sprintf("select %s from %s where {{.originalField}} = ? limit 1", {{.lowerStartCamelObject}}Rows, m.table )
|
||||
err := m.conn.QueryRow(&resp, query, {{.lowerStartCamelField}})
|
||||
switch err {
|
||||
case nil:
|
||||
@@ -69,7 +69,7 @@ func (m *{{.upperStartCamelObject}}Model) formatPrimary(primary interface{}) str
|
||||
}
|
||||
|
||||
func (m *{{.upperStartCamelObject}}Model) queryPrimary(conn sqlx.SqlConn, v, primary interface{}) error {
|
||||
query := ` + "`" + `select ` + "`" + ` + {{.lowerStartCamelObject}}Rows + ` + "`" + ` from ` + "` + " + `m.table ` + " + `" + ` where {{.originalPrimaryField}} = ? limit 1` + "`" + `
|
||||
query := fmt.Sprintf("select %s from %s where {{.originalPrimaryField}} = ? limit 1", {{.lowerStartCamelObject}}Rows, m.table )
|
||||
return conn.QueryRow(v, query, primary)
|
||||
}
|
||||
`
|
||||
|
||||
@@ -15,6 +15,8 @@ var (
|
||||
)
|
||||
`
|
||||
ImportsNoCache = `import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
{{if .time}}"time"{{end}}
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
package template
|
||||
|
||||
var Insert = `
|
||||
func (m *{{.upperStartCamelObject}}Model) Insert(data {{.upperStartCamelObject}}) error {
|
||||
func (m *{{.upperStartCamelObject}}Model) Insert(data {{.upperStartCamelObject}}) (sql.Result,error) {
|
||||
{{if .withCache}}{{if .containsIndexCache}}{{.keys}}
|
||||
_, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
|
||||
query := ` + "`" + `insert into ` + "`" + ` + m.table + ` + "` (` + " + `{{.lowerStartCamelObject}}RowsExpectAutoSet` + " + `) values ({{.expression}})` " + `
|
||||
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)
|
||||
return conn.Exec(query, {{.expressionValues}})
|
||||
}, {{.keyValues}}){{else}}query := ` + "`" + `insert into ` + "`" + ` + m.table + ` + "` (` + " + `{{.lowerStartCamelObject}}RowsExpectAutoSet` + " + `) values ({{.expression}})` " + `
|
||||
_,err:=m.ExecNoCache(query, {{.expressionValues}})
|
||||
{{end}}{{else}}query := ` + "`" + `insert into ` + "`" + ` + m.table + ` + "` (` + " + `{{.lowerStartCamelObject}}RowsExpectAutoSet` + " + `) values ({{.expression}})` " + `
|
||||
_,err:=m.conn.Exec(query, {{.expressionValues}}){{end}}
|
||||
return err
|
||||
}, {{.keyValues}}){{else}}query := fmt.Sprintf("insert into %s (%s) values ({{.expression}})", m.table, {{.lowerStartCamelObject}}RowsExpectAutoSet)
|
||||
ret,err:=m.ExecNoCache(query, {{.expressionValues}})
|
||||
{{end}}{{else}}query := fmt.Sprintf("insert into %s (%s) values ({{.expression}})", m.table, {{.lowerStartCamelObject}}RowsExpectAutoSet)
|
||||
ret,err:=m.conn.Exec(query, {{.expressionValues}}){{end}}
|
||||
return ret,err
|
||||
}
|
||||
`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package template
|
||||
|
||||
var Model = `package model
|
||||
var Model = `package {{.pkg}}
|
||||
{{.imports}}
|
||||
{{.vars}}
|
||||
{{.types}}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package template
|
||||
|
||||
var New = `
|
||||
func New{{.upperStartCamelObject}}Model(conn sqlx.SqlConn,{{if .withCache}} c cache.CacheConf,{{end}} table string) *{{.upperStartCamelObject}}Model {
|
||||
func New{{.upperStartCamelObject}}Model(conn sqlx.SqlConn{{if .withCache}}, c cache.CacheConf{{end}}) *{{.upperStartCamelObject}}Model {
|
||||
return &{{.upperStartCamelObject}}Model{
|
||||
{{if .withCache}}CachedConn: sqlc.NewConn(conn, c){{else}}conn:conn{{end}},
|
||||
table: table,
|
||||
table: "{{.table}}",
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
@@ -4,9 +4,9 @@ var Update = `
|
||||
func (m *{{.upperStartCamelObject}}Model) Update(data {{.upperStartCamelObject}}) error {
|
||||
{{if .withCache}}{{.primaryCacheKey}}
|
||||
_, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
|
||||
query := ` + "`" + `update ` + "` +" + `m.table +` + "` " + `set ` + "` +" + `{{.lowerStartCamelObject}}RowsWithPlaceHolder` + " + `" + ` where {{.originalPrimaryKey}} = ?` + "`" + `
|
||||
query := fmt.Sprintf("update %s set %s where {{.originalPrimaryKey}} = ?", m.table, {{.lowerStartCamelObject}}RowsWithPlaceHolder)
|
||||
return conn.Exec(query, {{.expressionValues}})
|
||||
}, {{.primaryKeyVariable}}){{else}}query := ` + "`" + `update ` + "` +" + `m.table +` + "` " + `set ` + "` +" + `{{.lowerStartCamelObject}}RowsWithPlaceHolder` + " + `" + ` where {{.originalPrimaryKey}} = ?` + "`" + `
|
||||
}, {{.primaryKeyVariable}}){{else}}query := fmt.Sprintf("update %s set %s where {{.originalPrimaryKey}} = ?", m.table, {{.lowerStartCamelObject}}RowsWithPlaceHolder)
|
||||
_,err:=m.conn.Exec(query, {{.expressionValues}}){{end}}
|
||||
return err
|
||||
}
|
||||
|
||||
29
tools/goctl/model/sql/util/match_test.go
Normal file
29
tools/goctl/model/sql/util/match_test.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMatchFiles(t *testing.T) {
|
||||
dir, err := filepath.Abs("./")
|
||||
assert.Nil(t, err)
|
||||
|
||||
files, err := MatchFiles("./*.sql")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, []string{filepath.Join(dir, "studeat.sql"), filepath.Join(dir, "student.sql"), filepath.Join(dir, "xx.sql")}, files)
|
||||
|
||||
files, err = MatchFiles("./??.sql")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, []string{filepath.Join(dir, "xx.sql")}, files)
|
||||
|
||||
files, err = MatchFiles("./*.sq*")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, []string{filepath.Join(dir, "studeat.sql"), filepath.Join(dir, "student.sql"), filepath.Join(dir, "xx.sql"), filepath.Join(dir, "xx.sql1")}, files)
|
||||
|
||||
files, err = MatchFiles("./student.sql")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, []string{filepath.Join(dir, "student.sql")}, files)
|
||||
}
|
||||
38
tools/goctl/model/sql/util/matcher.go
Normal file
38
tools/goctl/model/sql/util/matcher.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// expression: globbing patterns
|
||||
func MatchFiles(in string) ([]string, error) {
|
||||
dir, pattern := filepath.Split(in)
|
||||
abs, err := filepath.Abs(dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
files, err := ioutil.ReadDir(abs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var res []string
|
||||
for _, file := range files {
|
||||
if file.IsDir() {
|
||||
continue
|
||||
}
|
||||
name := file.Name()
|
||||
match, err := filepath.Match(pattern, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !match {
|
||||
continue
|
||||
}
|
||||
|
||||
res = append(res, filepath.Join(abs, name))
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
0
tools/goctl/model/sql/util/studeat.sql
Normal file
0
tools/goctl/model/sql/util/studeat.sql
Normal file
0
tools/goctl/model/sql/util/student.sql
Normal file
0
tools/goctl/model/sql/util/student.sql
Normal file
0
tools/goctl/model/sql/util/sub/sub.sql
Normal file
0
tools/goctl/model/sql/util/sub/sub.sql
Normal file
0
tools/goctl/model/sql/util/xx.sql
Normal file
0
tools/goctl/model/sql/util/xx.sql
Normal file
0
tools/goctl/model/sql/util/xx.sql1
Normal file
0
tools/goctl/model/sql/util/xx.sql1
Normal file
@@ -1,5 +1,9 @@
|
||||
# Change log
|
||||
|
||||
## 2020-10-19
|
||||
|
||||
* 增加template
|
||||
|
||||
## 2020-09-10
|
||||
|
||||
* rpc greet服务一键生成
|
||||
|
||||
@@ -32,7 +32,7 @@ func NewDefaultRpcGenerator(ctx *ctx.RpcContext) *defaultRpcGenerator {
|
||||
}
|
||||
|
||||
func (g *defaultRpcGenerator) Generate() (err error) {
|
||||
g.Ctx.Info(aurora.Blue("-> goctl rpc reference documents: ").String() + "「https://github.com/tal-tech/go-zero/blob/master/doc/goctl-rpc.md」")
|
||||
g.Ctx.Info(aurora.Blue("-> goctl rpc reference documents: ").String() + "「https://github.com/tal-tech/zero-doc/blob/main/doc/goctl-rpc.md」")
|
||||
g.Ctx.Warning("-> generating rpc code ...")
|
||||
defer func() {
|
||||
if err == nil {
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
|
||||
"github.com/tal-tech/go-zero/core/collection"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/rpc/parser"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/templatex"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
)
|
||||
|
||||
@@ -123,8 +122,12 @@ func (g *defaultRpcGenerator) genCall() error {
|
||||
}
|
||||
|
||||
filename := filepath.Join(callPath, typesFilename)
|
||||
head := templatex.GetHead(g.Ctx.ProtoSource)
|
||||
err = templatex.With("types").GoFmt(true).Parse(callTemplateTypes).SaveTo(map[string]interface{}{
|
||||
head := util.GetHead(g.Ctx.ProtoSource)
|
||||
text, err := util.LoadTemplate(category, callTypesTemplateFile, callTemplateTypes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = util.With("types").GoFmt(true).Parse(text).SaveTo(map[string]interface{}{
|
||||
"head": head,
|
||||
"const": constLit,
|
||||
"filePackage": service.Name.Lower(),
|
||||
@@ -146,8 +149,11 @@ func (g *defaultRpcGenerator) genCall() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = templatex.With("shared").GoFmt(true).Parse(callTemplateText).SaveTo(map[string]interface{}{
|
||||
text, err = util.LoadTemplate(category, callTemplateFile, callTemplateText)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = util.With("shared").GoFmt(true).Parse(text).SaveTo(map[string]interface{}{
|
||||
"name": service.Name.Lower(),
|
||||
"head": head,
|
||||
"filePackage": service.Name.Lower(),
|
||||
@@ -167,7 +173,11 @@ func (g *defaultRpcGenerator) genFunction(service *parser.RpcService) ([]string,
|
||||
imports.AddStr(fmt.Sprintf(`%v "%v"`, pkgName, g.mustGetPackage(dirPb)))
|
||||
for _, method := range service.Funcs {
|
||||
imports.AddStr(g.ast.Imports[method.ParameterIn.Package])
|
||||
buffer, err := templatex.With("sharedFn").Parse(callFunctionTemplate).Execute(map[string]interface{}{
|
||||
text, err := util.LoadTemplate(category, callFunctionTemplateFile, callFunctionTemplate)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
buffer, err := util.With("sharedFn").Parse(text).Execute(map[string]interface{}{
|
||||
"rpcServiceName": service.Name.Title(),
|
||||
"method": method.Name.Title(),
|
||||
"package": pkgName,
|
||||
@@ -190,7 +200,12 @@ func (g *defaultRpcGenerator) getInterfaceFuncs(service *parser.RpcService) ([]s
|
||||
functions := make([]string, 0)
|
||||
|
||||
for _, method := range service.Funcs {
|
||||
buffer, err := templatex.With("interfaceFn").Parse(callInterfaceFunctionTemplate).Execute(
|
||||
text, err := util.LoadTemplate(category, callInterfaceFunctionTemplateFile, callInterfaceFunctionTemplate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buffer, err := util.With("interfaceFn").Parse(text).Execute(
|
||||
map[string]interface{}{
|
||||
"hasComment": method.HaveDoc(),
|
||||
"comment": method.GetDoc(),
|
||||
|
||||
@@ -23,5 +23,11 @@ func (g *defaultRpcGenerator) genConfig() error {
|
||||
if util.FileExists(fileName) {
|
||||
return nil
|
||||
}
|
||||
return ioutil.WriteFile(fileName, []byte(configTemplate), os.ModePerm)
|
||||
|
||||
text, err := util.LoadTemplate(category, configTemplateFileFile, configTemplate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ioutil.WriteFile(fileName, []byte(text), os.ModePerm)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/templatex"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
)
|
||||
|
||||
@@ -23,7 +22,12 @@ func (g *defaultRpcGenerator) genEtc() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return templatex.With("etc").Parse(etcTemplate).SaveTo(map[string]interface{}{
|
||||
text, err := util.LoadTemplate(category, etcTemplateFileFile, etcTemplate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return util.With("etc").Parse(text).SaveTo(map[string]interface{}{
|
||||
"serviceName": g.Ctx.ServiceName.Lower(),
|
||||
}, fileName, false)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
|
||||
"github.com/tal-tech/go-zero/core/collection"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/rpc/parser"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/templatex"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
)
|
||||
|
||||
@@ -62,7 +61,11 @@ func (g *defaultRpcGenerator) genLogic() error {
|
||||
svcImport := fmt.Sprintf(`"%v"`, g.mustGetPackage(dirSvc))
|
||||
imports.AddStr(svcImport)
|
||||
imports.AddStr(importList...)
|
||||
err = templatex.With("logic").GoFmt(true).Parse(logicTemplate).SaveTo(map[string]interface{}{
|
||||
text, err := util.LoadTemplate(category, logicTemplateFileFile, logicTemplate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = util.With("logic").GoFmt(true).Parse(text).SaveTo(map[string]interface{}{
|
||||
"logicName": fmt.Sprintf("%sLogic", method.Name.Title()),
|
||||
"functions": functions,
|
||||
"imports": strings.Join(imports.KeysStr(), util.NL),
|
||||
@@ -83,7 +86,12 @@ func (g *defaultRpcGenerator) genLogicFunction(packageName string, method *parse
|
||||
}
|
||||
imports.AddStr(g.ast.Imports[method.ParameterIn.Package])
|
||||
imports.AddStr(g.ast.Imports[method.ParameterOut.Package])
|
||||
buffer, err := templatex.With("fun").Parse(logicFunctionTemplate).Execute(map[string]interface{}{
|
||||
text, err := util.LoadTemplate(category, logicFuncTemplateFileFile, logicFunctionTemplate)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
buffer, err := util.With("fun").Parse(text).Execute(map[string]interface{}{
|
||||
"logicName": fmt.Sprintf("%sLogic", method.Name.Title()),
|
||||
"method": method.Name.Title(),
|
||||
"request": method.ParameterIn.StarExpression,
|
||||
@@ -95,6 +103,7 @@ func (g *defaultRpcGenerator) genLogicFunction(packageName string, method *parse
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
functions = append(functions, buffer.String())
|
||||
return strings.Join(functions, util.NL), imports.KeysStr(), nil
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/rpc/parser"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/templatex"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
)
|
||||
|
||||
@@ -58,15 +57,20 @@ func (g *defaultRpcGenerator) genMain() error {
|
||||
configImport := fmt.Sprintf(`"%v"`, g.mustGetPackage(dirConfig))
|
||||
imports = append(imports, configImport, pbImport, remoteImport, svcImport)
|
||||
srv, registers := g.genServer(pkg, file.Service)
|
||||
head := templatex.GetHead(g.Ctx.ProtoSource)
|
||||
return templatex.With("main").GoFmt(true).Parse(mainTemplate).SaveTo(map[string]interface{}{
|
||||
head := util.GetHead(g.Ctx.ProtoSource)
|
||||
text, err := util.LoadTemplate(category, mainTemplateFile, mainTemplate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return util.With("main").GoFmt(true).Parse(text).SaveTo(map[string]interface{}{
|
||||
"head": head,
|
||||
"package": pkg,
|
||||
"serviceName": g.Ctx.ServiceName.Lower(),
|
||||
"srv": srv,
|
||||
"registers": registers,
|
||||
"imports": strings.Join(imports, util.NL),
|
||||
}, fileName, true)
|
||||
}, fileName, false)
|
||||
}
|
||||
|
||||
func (g *defaultRpcGenerator) genServer(pkg string, list []*parser.RpcService) (string, string) {
|
||||
|
||||
@@ -18,6 +18,7 @@ const (
|
||||
|
||||
func (g *defaultRpcGenerator) genPb() error {
|
||||
pbPath := g.dirM[dirPb]
|
||||
// deprecated: containsAny will be removed in the feature
|
||||
imports, containsAny, err := parser.ParseImport(g.Ctx.ProtoFileSrc)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
|
||||
"github.com/tal-tech/go-zero/core/collection"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/rpc/parser"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/templatex"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
)
|
||||
|
||||
@@ -52,7 +51,7 @@ func (g *defaultRpcGenerator) genHandler() error {
|
||||
imports := collection.NewSet()
|
||||
imports.AddStr(logicImport, svcImport)
|
||||
|
||||
head := templatex.GetHead(g.Ctx.ProtoSource)
|
||||
head := util.GetHead(g.Ctx.ProtoSource)
|
||||
for _, service := range file.Service {
|
||||
filename := fmt.Sprintf("%vserver.go", service.Name.Lower())
|
||||
serverFile := filepath.Join(serverPath, filename)
|
||||
@@ -60,8 +59,14 @@ func (g *defaultRpcGenerator) genHandler() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
imports.AddStr(importList...)
|
||||
err = templatex.With("server").GoFmt(true).Parse(serverTemplate).SaveTo(map[string]interface{}{
|
||||
text, err := util.LoadTemplate(category, serverTemplateFile, serverTemplate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = util.With("server").GoFmt(true).Parse(text).SaveTo(map[string]interface{}{
|
||||
"head": head,
|
||||
"types": fmt.Sprintf(typeFmt, service.Name.Title()),
|
||||
"server": service.Name.Title(),
|
||||
@@ -86,7 +91,12 @@ func (g *defaultRpcGenerator) genFunctions(service *parser.RpcService) ([]string
|
||||
}
|
||||
imports.AddStr(g.ast.Imports[method.ParameterIn.Package])
|
||||
imports.AddStr(g.ast.Imports[method.ParameterOut.Package])
|
||||
buffer, err := templatex.With("func").Parse(functionTemplate).Execute(map[string]interface{}{
|
||||
text, err := util.LoadTemplate(category, serverFuncTemplateFile, functionTemplate)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
buffer, err := util.With("func").Parse(text).Execute(map[string]interface{}{
|
||||
"server": service.Name.Title(),
|
||||
"logicName": fmt.Sprintf("%sLogic", method.Name.Title()),
|
||||
"method": method.Name.Title(),
|
||||
@@ -99,6 +109,7 @@ func (g *defaultRpcGenerator) genFunctions(service *parser.RpcService) ([]string
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
functionList = append(functionList, buffer.String())
|
||||
}
|
||||
return functionList, imports.KeysStr(), nil
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/templatex"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
)
|
||||
|
||||
const svcTemplate = `package svc
|
||||
@@ -25,7 +25,12 @@ func NewServiceContext(c config.Config) *ServiceContext {
|
||||
func (g *defaultRpcGenerator) genSvc() error {
|
||||
svcPath := g.dirM[dirSvc]
|
||||
fileName := filepath.Join(svcPath, fileServiceContext)
|
||||
return templatex.With("svc").GoFmt(true).Parse(svcTemplate).SaveTo(map[string]interface{}{
|
||||
text, err := util.LoadTemplate(category, svcTemplateFile, svcTemplate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return util.With("svc").GoFmt(true).Parse(text).SaveTo(map[string]interface{}{
|
||||
"imports": fmt.Sprintf(`"%v"`, g.mustGetPackage(dirConfig)),
|
||||
}, fileName, false)
|
||||
}
|
||||
|
||||
59
tools/goctl/rpc/gen/rpctemplate.go
Normal file
59
tools/goctl/rpc/gen/rpctemplate.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package gen
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/logrusorgru/aurora"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/console"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
|
||||
)
|
||||
|
||||
const rpcTemplateText = `syntax = "proto3";
|
||||
|
||||
package {{.package}};
|
||||
|
||||
message Request {
|
||||
string ping = 1;
|
||||
}
|
||||
|
||||
message Response {
|
||||
string pong = 1;
|
||||
}
|
||||
|
||||
service {{.serviceName}} {
|
||||
rpc Ping(Request) returns(Response);
|
||||
}
|
||||
`
|
||||
|
||||
type rpcTemplate struct {
|
||||
out string
|
||||
console.Console
|
||||
}
|
||||
|
||||
func NewRpcTemplate(out string, idea bool) *rpcTemplate {
|
||||
return &rpcTemplate{
|
||||
out: out,
|
||||
Console: console.NewConsole(idea),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *rpcTemplate) MustGenerate(showState bool) {
|
||||
r.Info(aurora.Blue("-> goctl rpc reference documents: ").String() + "「https://github.com/tal-tech/zero-doc/blob/main/doc/goctl-rpc.md」")
|
||||
r.Info("-> generating template...")
|
||||
protoFilename := filepath.Base(r.out)
|
||||
serviceName := stringx.From(strings.TrimSuffix(protoFilename, filepath.Ext(protoFilename)))
|
||||
text, err := util.LoadTemplate(category, rpcTemplateFile, rpcTemplateText)
|
||||
r.Must(err)
|
||||
|
||||
err = util.With("t").Parse(text).SaveTo(map[string]string{
|
||||
"package": serviceName.UnTitle(),
|
||||
"serviceName": serviceName.Title(),
|
||||
}, r.out, false)
|
||||
r.Must(err)
|
||||
|
||||
if showState {
|
||||
r.Success("Done.")
|
||||
}
|
||||
}
|
||||
@@ -1,54 +1,69 @@
|
||||
package gen
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"fmt"
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/templatex"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/console"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
const rpcTemplateText = `syntax = "proto3";
|
||||
const (
|
||||
category = "rpc"
|
||||
callTemplateFile = "call.tpl"
|
||||
callTypesTemplateFile = "call-types.tpl"
|
||||
callInterfaceFunctionTemplateFile = "call-interface-func.tpl"
|
||||
callFunctionTemplateFile = "call-func.tpl"
|
||||
configTemplateFileFile = "config.tpl"
|
||||
etcTemplateFileFile = "etc.tpl"
|
||||
logicTemplateFileFile = "logic.tpl"
|
||||
logicFuncTemplateFileFile = "logic-func.tpl"
|
||||
mainTemplateFile = "main.tpl"
|
||||
serverTemplateFile = "server.tpl"
|
||||
serverFuncTemplateFile = "server-func.tpl"
|
||||
svcTemplateFile = "svc.tpl"
|
||||
rpcTemplateFile = "template.tpl"
|
||||
)
|
||||
|
||||
package {{.package}};
|
||||
|
||||
message Request {
|
||||
string ping = 1;
|
||||
var templates = map[string]string{
|
||||
callTemplateFile: callTemplateText,
|
||||
callTypesTemplateFile: callTemplateTypes,
|
||||
callInterfaceFunctionTemplateFile: callInterfaceFunctionTemplate,
|
||||
callFunctionTemplateFile: callFunctionTemplate,
|
||||
configTemplateFileFile: configTemplate,
|
||||
etcTemplateFileFile: etcTemplate,
|
||||
logicTemplateFileFile: logicTemplate,
|
||||
logicFuncTemplateFileFile: logicFunctionTemplate,
|
||||
mainTemplateFile: mainTemplate,
|
||||
serverTemplateFile: serverTemplate,
|
||||
serverFuncTemplateFile: functionTemplate,
|
||||
svcTemplateFile: svcTemplate,
|
||||
rpcTemplateFile: rpcTemplateText,
|
||||
}
|
||||
|
||||
message Response {
|
||||
string pong = 1;
|
||||
func GenTemplates(_ *cli.Context) error {
|
||||
return util.InitTemplates(category, templates)
|
||||
}
|
||||
|
||||
service {{.serviceName}} {
|
||||
rpc Ping(Request) returns(Response);
|
||||
}
|
||||
`
|
||||
|
||||
type rpcTemplate struct {
|
||||
out string
|
||||
console.Console
|
||||
}
|
||||
|
||||
func NewRpcTemplate(out string, idea bool) *rpcTemplate {
|
||||
return &rpcTemplate{
|
||||
out: out,
|
||||
Console: console.NewConsole(idea),
|
||||
func RevertTemplate(name string) error {
|
||||
content, ok := templates[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("%s: no such file name", name)
|
||||
}
|
||||
return util.CreateTemplate(category, name, content)
|
||||
}
|
||||
|
||||
func (r *rpcTemplate) MustGenerate(showState bool) {
|
||||
r.Info("查看rpc生成请移步至「https://github.com/tal-tech/zero-doc/blob/main/doc/goctl-rpc.md」")
|
||||
r.Info("generating template...")
|
||||
protoFilename := filepath.Base(r.out)
|
||||
serviceName := stringx.From(strings.TrimSuffix(protoFilename, filepath.Ext(protoFilename)))
|
||||
err := templatex.With("t").Parse(rpcTemplateText).SaveTo(map[string]string{
|
||||
"package": serviceName.UnTitle(),
|
||||
"serviceName": serviceName.Title(),
|
||||
}, r.out, false)
|
||||
r.Must(err)
|
||||
if showState {
|
||||
r.Success("Done.")
|
||||
}
|
||||
func Clean() error {
|
||||
return util.Clean(category)
|
||||
}
|
||||
|
||||
func Update(category string) error {
|
||||
err := Clean()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return util.InitTemplates(category, templates)
|
||||
}
|
||||
|
||||
func GetCategory() string {
|
||||
return category
|
||||
}
|
||||
|
||||
92
tools/goctl/rpc/gen/template_test.go
Normal file
92
tools/goctl/rpc/gen/template_test.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package gen
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
)
|
||||
|
||||
func TestGenTemplates(t *testing.T) {
|
||||
err := util.InitTemplates(category, templates)
|
||||
assert.Nil(t, err)
|
||||
dir, err := util.GetTemplateDir(category)
|
||||
assert.Nil(t, err)
|
||||
file := filepath.Join(dir, "main.tpl")
|
||||
data, err := ioutil.ReadFile(file)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, string(data), mainTemplate)
|
||||
}
|
||||
|
||||
func TestRevertTemplate(t *testing.T) {
|
||||
name := "main.tpl"
|
||||
err := util.InitTemplates(category, templates)
|
||||
assert.Nil(t, err)
|
||||
|
||||
dir, err := util.GetTemplateDir(category)
|
||||
assert.Nil(t, err)
|
||||
|
||||
file := filepath.Join(dir, name)
|
||||
data, err := ioutil.ReadFile(file)
|
||||
assert.Nil(t, err)
|
||||
|
||||
modifyData := string(data) + "modify"
|
||||
err = util.CreateTemplate(category, name, modifyData)
|
||||
assert.Nil(t, err)
|
||||
|
||||
data, err = ioutil.ReadFile(file)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, string(data), modifyData)
|
||||
|
||||
assert.Nil(t, RevertTemplate(name))
|
||||
|
||||
data, err = ioutil.ReadFile(file)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, mainTemplate, string(data))
|
||||
}
|
||||
|
||||
func TestClean(t *testing.T) {
|
||||
name := "main.tpl"
|
||||
err := util.InitTemplates(category, templates)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Nil(t, Clean())
|
||||
|
||||
dir, err := util.GetTemplateDir(category)
|
||||
assert.Nil(t, err)
|
||||
|
||||
file := filepath.Join(dir, name)
|
||||
_, err = ioutil.ReadFile(file)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestUpdate(t *testing.T) {
|
||||
name := "main.tpl"
|
||||
err := util.InitTemplates(category, templates)
|
||||
assert.Nil(t, err)
|
||||
|
||||
dir, err := util.GetTemplateDir(category)
|
||||
assert.Nil(t, err)
|
||||
|
||||
file := filepath.Join(dir, name)
|
||||
data, err := ioutil.ReadFile(file)
|
||||
assert.Nil(t, err)
|
||||
|
||||
modifyData := string(data) + "modify"
|
||||
err = util.CreateTemplate(category, name, modifyData)
|
||||
assert.Nil(t, err)
|
||||
|
||||
data, err = ioutil.ReadFile(file)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, string(data), modifyData)
|
||||
|
||||
assert.Nil(t, Update(category))
|
||||
|
||||
data, err = ioutil.ReadFile(file)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, mainTemplate, string(data))
|
||||
}
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
|
||||
"github.com/tal-tech/go-zero/core/lang"
|
||||
sx "github.com/tal-tech/go-zero/core/stringx"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/templatex"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/console"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
|
||||
@@ -98,6 +97,7 @@ type (
|
||||
}
|
||||
// parsing for rpc
|
||||
PbAst struct {
|
||||
// deprecated: containsAny will be removed in the feature
|
||||
ContainsAny bool
|
||||
Imports map[string]string
|
||||
Structure map[string]*Struct
|
||||
@@ -590,7 +590,7 @@ func (a *PbAst) GenTypesCode() (string, error) {
|
||||
types = append(types, typeCode)
|
||||
}
|
||||
|
||||
buffer, err := templatex.With("type").Parse(typeTemplate).Execute(map[string]interface{}{
|
||||
buffer, err := util.With("type").Parse(typeTemplate).Execute(map[string]interface{}{
|
||||
"types": strings.Join(types, util.NL+util.NL),
|
||||
})
|
||||
if err != nil {
|
||||
@@ -615,7 +615,7 @@ func (s *Struct) genCode(containsTypeStatement bool) (string, error) {
|
||||
comment = f.Comment[0]
|
||||
}
|
||||
doc = strings.Join(f.Document, util.NL)
|
||||
buffer, err := templatex.With(sx.Rand()).Parse(fieldTemplate).Execute(map[string]interface{}{
|
||||
buffer, err := util.With(sx.Rand()).Parse(fieldTemplate).Execute(map[string]interface{}{
|
||||
"name": f.Name.Title(),
|
||||
"type": f.Type.InvokeTypeExpression,
|
||||
"tag": f.JsonTag,
|
||||
@@ -630,7 +630,7 @@ func (s *Struct) genCode(containsTypeStatement bool) (string, error) {
|
||||
|
||||
fields = append(fields, buffer.String())
|
||||
}
|
||||
buffer, err := templatex.With("struct").Parse(structTemplate).Execute(map[string]interface{}{
|
||||
buffer, err := util.With("struct").Parse(structTemplate).Execute(map[string]interface{}{
|
||||
"type": containsTypeStatement,
|
||||
"name": s.Name.Title(),
|
||||
"fields": strings.Join(fields, util.NL),
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"github.com/emicklei/proto"
|
||||
"github.com/tal-tech/go-zero/core/collection"
|
||||
"github.com/tal-tech/go-zero/core/lang"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/templatex"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
|
||||
)
|
||||
@@ -48,9 +47,10 @@ type (
|
||||
}
|
||||
|
||||
Proto struct {
|
||||
Package string
|
||||
Import []*Import
|
||||
PbSrc string
|
||||
Package string
|
||||
Import []*Import
|
||||
PbSrc string
|
||||
// deprecated: containsAny will be removed in the feature
|
||||
ContainsAny bool
|
||||
Message map[string]lang.PlaceholderType
|
||||
Enum map[string]*Enum
|
||||
@@ -263,7 +263,7 @@ func (e *Enum) GenEnumCode() (string, error) {
|
||||
}
|
||||
element = append(element, code)
|
||||
}
|
||||
buffer, err := templatex.With("enum").Parse(enumTemplate).Execute(map[string]interface{}{
|
||||
buffer, err := util.With("enum").Parse(enumTemplate).Execute(map[string]interface{}{
|
||||
"element": strings.Join(element, util.NL),
|
||||
})
|
||||
if err != nil {
|
||||
@@ -273,7 +273,7 @@ func (e *Enum) GenEnumCode() (string, error) {
|
||||
}
|
||||
|
||||
func (e *Enum) GenEnumTypeCode() (string, error) {
|
||||
buffer, err := templatex.With("enumAlias").Parse(enumTypeTemplate).Execute(map[string]interface{}{
|
||||
buffer, err := util.With("enumAlias").Parse(enumTypeTemplate).Execute(map[string]interface{}{
|
||||
"name": e.Name.Source(),
|
||||
})
|
||||
if err != nil {
|
||||
@@ -283,7 +283,7 @@ func (e *Enum) GenEnumTypeCode() (string, error) {
|
||||
}
|
||||
|
||||
func (e *EnumField) GenEnumFieldCode(parentName string) (string, error) {
|
||||
buffer, err := templatex.With("enumField").Parse(enumFiledTemplate).Execute(map[string]interface{}{
|
||||
buffer, err := util.With("enumField").Parse(enumFiledTemplate).Execute(map[string]interface{}{
|
||||
"key": e.Key,
|
||||
"name": parentName,
|
||||
"value": e.Value,
|
||||
|
||||
102
tools/goctl/tpl/templates.go
Normal file
102
tools/goctl/tpl/templates.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package tpl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/logrusorgru/aurora"
|
||||
"github.com/tal-tech/go-zero/core/errorx"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/api/gogen"
|
||||
modelgen "github.com/tal-tech/go-zero/tools/goctl/model/sql/gen"
|
||||
rpcgen "github.com/tal-tech/go-zero/tools/goctl/rpc/gen"
|
||||
"github.com/tal-tech/go-zero/tools/goctl/util"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
const templateParentPath = "/"
|
||||
|
||||
func GenTemplates(ctx *cli.Context) error {
|
||||
if err := errorx.Chain(
|
||||
func() error {
|
||||
return gogen.GenTemplates(ctx)
|
||||
},
|
||||
func() error {
|
||||
return modelgen.GenTemplates(ctx)
|
||||
},
|
||||
func() error {
|
||||
return rpcgen.GenTemplates(ctx)
|
||||
},
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dir, err := util.GetTemplateDir(templateParentPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Templates are generated in %s, %s\n", aurora.Green(dir),
|
||||
aurora.Red("edit on your risk!"))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func CleanTemplates(_ *cli.Context) error {
|
||||
err := errorx.Chain(
|
||||
func() error {
|
||||
return gogen.Clean()
|
||||
},
|
||||
func() error {
|
||||
return modelgen.Clean()
|
||||
},
|
||||
func() error {
|
||||
return rpcgen.Clean()
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("%s\n", aurora.Green("template are clean!"))
|
||||
return nil
|
||||
}
|
||||
|
||||
func UpdateTemplates(ctx *cli.Context) (err error) {
|
||||
category := ctx.String("category")
|
||||
defer func() {
|
||||
if err == nil {
|
||||
fmt.Println(aurora.Green(fmt.Sprintf("%s template are update!", category)).String())
|
||||
}
|
||||
}()
|
||||
switch category {
|
||||
case gogen.GetCategory():
|
||||
return gogen.Update(category)
|
||||
case rpcgen.GetCategory():
|
||||
return rpcgen.Update(category)
|
||||
case modelgen.GetCategory():
|
||||
return modelgen.Update(category)
|
||||
default:
|
||||
err = fmt.Errorf("unexpected category: %s", category)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func RevertTemplates(ctx *cli.Context) (err error) {
|
||||
category := ctx.String("category")
|
||||
filename := ctx.String("name")
|
||||
defer func() {
|
||||
if err == nil {
|
||||
fmt.Println(aurora.Green(fmt.Sprintf("%s template are reverted!", filename)).String())
|
||||
}
|
||||
}()
|
||||
switch category {
|
||||
case gogen.GetCategory():
|
||||
return gogen.RevertTemplate(filename)
|
||||
case rpcgen.GetCategory():
|
||||
return rpcgen.RevertTemplate(filename)
|
||||
case modelgen.GetCategory():
|
||||
return modelgen.RevertTemplate(filename)
|
||||
default:
|
||||
err = fmt.Errorf("unexpected category: %s", category)
|
||||
return
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user