Compare commits

...

36 Commits

Author SHA1 Message Date
kevin
4c02a19a14 update stringx doc 2020-08-19 18:23:37 +08:00
kevin
a1b990c5ec update stringx doc 2020-08-19 18:22:41 +08:00
kevin
2607bb8863 update image alt in doc 2020-08-19 18:19:38 +08:00
kevin
5bf37535fe update image scale in doc 2020-08-19 18:18:44 +08:00
kevin
ed85775fd5 fix render problem in doc 2020-08-19 18:09:03 +08:00
kevin
418f8f6666 add keywords utility example 2020-08-19 17:58:57 +08:00
kevin
22e75cdf78 add release badge 2020-08-19 17:01:22 +08:00
kevin
e79c42add1 add go report badge 2020-08-19 16:10:43 +08:00
kevin
9e14820698 fix golint warnings 2020-08-19 16:00:55 +08:00
kevin
2ebb5b6b58 support customized mask char on trie 2020-08-19 14:54:59 +08:00
kevin
2673dbc6e1 add benchmark 2020-08-19 12:43:14 +08:00
Keson
d21d770b5b goctl model reactor (#15)
* reactor sql generation

* reactor sql generation

* add console & example

* optimize unit test & add document

* modify default config

* remove test file

* Revert "remove test file"

This reverts commit 81041f9e

* fix stringx.go & optimize example

* remove unused code
2020-08-19 10:41:19 +08:00
Steven Zack
1252bd9cde goctl生成Kotlin代码优化 (#16)
* 修复Kotlin连接失败抛出Exception;添加Kotlin连接超时

* 修复路径参数导致生成的Kotlin函数名带有:问题

* Added HTTP Patch Method

* kotlin-add-patch-support

* format-imports
2020-08-18 21:49:31 +08:00
kevin
054d9b5540 rename rest files 2020-08-18 20:20:44 +08:00
kevin
f03cfb0ff7 support direct scheme on rpc resolver 2020-08-18 18:36:44 +08:00
kevin
0214161bfc remove utils.Report 2020-08-17 18:05:56 +08:00
stevenzack
d4e38cb7f0 rename-Api 2020-08-17 16:54:56 +08:00
stevenzack
693a8b627a fix-log-fatal 2020-08-17 16:54:56 +08:00
stevenzack
701208b6f4 fix FileNotFoundException when response code is 4xx or 5xx 2020-08-17 16:54:56 +08:00
stevenzack
b65fcc5512 fix-lang-must-not-found 2020-08-17 16:54:56 +08:00
stevenzack
3321ed3519 multi-http-method-support 2020-08-17 16:54:56 +08:00
stevenzack
5e007c1f9f remove-logx 2020-08-17 16:54:56 +08:00
stevenzack
de2f8c06fb fix-break-line 2020-08-17 16:54:56 +08:00
stevenzack
926d746df5 Add goctl kotlin support 2020-08-17 16:54:56 +08:00
kevin
4b636cd293 refactor names 2020-08-16 23:08:29 +08:00
Klaus
4bdf5e4c90 chore: fix typo 2020-08-16 22:32:56 +08:00
kevin
721b7def7c add license badge 2020-08-15 15:16:57 +08:00
kevin
f294090130 update codecov settings 2020-08-15 15:11:55 +08:00
kevin
489980ea0f add codecov report 2020-08-15 14:53:15 +08:00
kevin
e12c8ae993 add codecov badge 2020-08-15 14:44:37 +08:00
kevin
21aad62513 use default decay value from finagle 2020-08-14 22:24:11 +08:00
kevin
0b08aca554 format readme 2020-08-14 15:33:00 +08:00
kevin
6ef1b5e14c update doc 2020-08-14 15:31:10 +08:00
kevin
8745039877 move lang.Must into logx.Must to make sure output fatal message as json 2020-08-14 15:08:06 +08:00
kevin
9d9399ad10 confirm addition after add called in periodical executor 2020-08-14 11:50:01 +08:00
kevin
e7dd04701c add more tests 2020-08-14 11:24:56 +08:00
139 changed files with 2710 additions and 2991 deletions

3
.codecov.yml Normal file
View File

@@ -0,0 +1,3 @@
ignore:
- "example/*"
- "tools/*"

View File

@@ -27,4 +27,9 @@ jobs:
go get -v -t -d ./... go get -v -t -d ./...
- name: Test - name: Test
run: go test -v -race ./... run: go test -race -coverprofile=coverage.txt -covermode=atomic ./...
- name: Codecov
uses: codecov/codecov-action@v1.0.6
with:
token: ${{secrets.CODECOV_TOKEN}}

View File

@@ -1,18 +0,0 @@
stages:
- analysis
variables:
GOPATH: '/runner-cache/zero'
GOCACHE: '/runner-cache/zero'
GOPROXY: 'https://goproxy.cn,direct'
analysis:
stage: analysis
image: golang
script:
- go version && go env
- go test -short $(go list ./...) | grep -v "no test"
only:
- merge_requests
tags:
- common

View File

@@ -60,17 +60,15 @@ func do(name string, execute func(b Breaker) error) error {
lock.RUnlock() lock.RUnlock()
if ok { if ok {
return execute(b) return execute(b)
} else {
lock.Lock()
b, ok = breakers[name]
if ok {
lock.Unlock()
return execute(b)
} else {
b = NewBreaker(WithName(name))
breakers[name] = b
lock.Unlock()
return execute(b)
}
} }
lock.Lock()
b, ok = breakers[name]
if !ok {
b = NewBreaker(WithName(name))
breakers[name] = b
}
lock.Unlock()
return execute(b)
} }

View File

@@ -72,9 +72,9 @@ func TestCacheWithLruEvicts(t *testing.T) {
cache.Set("third", "third element") cache.Set("third", "third element")
cache.Set("fourth", "fourth element") cache.Set("fourth", "fourth element")
value, ok := cache.Get("first") _, ok := cache.Get("first")
assert.False(t, ok) assert.False(t, ok)
value, ok = cache.Get("second") value, ok := cache.Get("second")
assert.True(t, ok) assert.True(t, ok)
assert.Equal(t, "second element", value) assert.Equal(t, "second element", value)
value, ok = cache.Get("third") value, ok = cache.Get("third")
@@ -94,9 +94,9 @@ func TestCacheWithLruEvicted(t *testing.T) {
cache.Set("third", "third element") cache.Set("third", "third element")
cache.Set("fourth", "fourth element") cache.Set("fourth", "fourth element")
value, ok := cache.Get("first") _, ok := cache.Get("first")
assert.False(t, ok) assert.False(t, ok)
value, ok = cache.Get("second") value, ok := cache.Get("second")
assert.True(t, ok) assert.True(t, ok)
assert.Equal(t, "second element", value) assert.Equal(t, "second element", value)
cache.Set("fifth", "fifth element") cache.Set("fifth", "fifth element")

View File

@@ -34,7 +34,7 @@ func TestContextCancel(t *testing.T) {
assert.NotEqual(t, context.Canceled, c2.Err()) assert.NotEqual(t, context.Canceled, c2.Err())
} }
func TestConextDeadline(t *testing.T) { func TestContextDeadline(t *testing.T) {
c, _ := context.WithDeadline(context.Background(), time.Now().Add(10*time.Millisecond)) c, _ := context.WithDeadline(context.Background(), time.Now().Add(10*time.Millisecond))
o := ValueOnlyFrom(c) o := ValueOnlyFrom(c)
select { select {

View File

@@ -2,7 +2,7 @@ package discov
import ( import (
"github.com/tal-tech/go-zero/core/discov/internal" "github.com/tal-tech/go-zero/core/discov/internal"
"github.com/tal-tech/go-zero/core/lang" "github.com/tal-tech/go-zero/core/logx"
) )
type ( type (
@@ -26,7 +26,7 @@ func NewFacade(endpoints []string) Facade {
func (f Facade) Client() internal.EtcdClient { func (f Facade) Client() internal.EtcdClient {
conn, err := f.registry.GetConn(f.endpoints) conn, err := f.registry.GetConn(f.endpoints)
lang.Must(err) logx.Must(err)
return conn return conn
} }

View File

@@ -12,14 +12,14 @@ func TestBulkExecutor(t *testing.T) {
var values []int var values []int
var lock sync.Mutex var lock sync.Mutex
exeutor := NewBulkExecutor(func(items []interface{}) { executor := NewBulkExecutor(func(items []interface{}) {
lock.Lock() lock.Lock()
values = append(values, len(items)) values = append(values, len(items))
lock.Unlock() lock.Unlock()
}, WithBulkTasks(10), WithBulkInterval(time.Minute)) }, WithBulkTasks(10), WithBulkInterval(time.Minute))
for i := 0; i < 50; i++ { for i := 0; i < 50; i++ {
exeutor.Add(1) executor.Add(1)
time.Sleep(time.Millisecond) time.Sleep(time.Millisecond)
} }
@@ -40,13 +40,13 @@ func TestBulkExecutorFlushInterval(t *testing.T) {
var wait sync.WaitGroup var wait sync.WaitGroup
wait.Add(1) wait.Add(1)
exeutor := NewBulkExecutor(func(items []interface{}) { executor := NewBulkExecutor(func(items []interface{}) {
assert.Equal(t, size, len(items)) assert.Equal(t, size, len(items))
wait.Done() wait.Done()
}, WithBulkTasks(caches), WithBulkInterval(time.Millisecond*100)) }, WithBulkTasks(caches), WithBulkInterval(time.Millisecond*100))
for i := 0; i < size; i++ { for i := 0; i < size; i++ {
exeutor.Add(1) executor.Add(1)
} }
wait.Wait() wait.Wait()

View File

@@ -12,14 +12,14 @@ func TestChunkExecutor(t *testing.T) {
var values []int var values []int
var lock sync.Mutex var lock sync.Mutex
exeutor := NewChunkExecutor(func(items []interface{}) { executor := NewChunkExecutor(func(items []interface{}) {
lock.Lock() lock.Lock()
values = append(values, len(items)) values = append(values, len(items))
lock.Unlock() lock.Unlock()
}, WithChunkBytes(10), WithFlushInterval(time.Minute)) }, WithChunkBytes(10), WithFlushInterval(time.Minute))
for i := 0; i < 50; i++ { for i := 0; i < 50; i++ {
exeutor.Add(1, 1) executor.Add(1, 1)
time.Sleep(time.Millisecond) time.Sleep(time.Millisecond)
} }
@@ -40,13 +40,13 @@ func TestChunkExecutorFlushInterval(t *testing.T) {
var wait sync.WaitGroup var wait sync.WaitGroup
wait.Add(1) wait.Add(1)
exeutor := NewChunkExecutor(func(items []interface{}) { executor := NewChunkExecutor(func(items []interface{}) {
assert.Equal(t, size, len(items)) assert.Equal(t, size, len(items))
wait.Done() wait.Done()
}, WithChunkBytes(caches), WithFlushInterval(time.Millisecond*100)) }, WithChunkBytes(caches), WithFlushInterval(time.Millisecond*100))
for i := 0; i < size; i++ { for i := 0; i < size; i++ {
exeutor.Add(1, 1) executor.Add(1, 1)
} }
wait.Wait() wait.Wait()

View File

@@ -5,6 +5,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/tal-tech/go-zero/core/lang"
"github.com/tal-tech/go-zero/core/proc" "github.com/tal-tech/go-zero/core/proc"
"github.com/tal-tech/go-zero/core/syncx" "github.com/tal-tech/go-zero/core/syncx"
"github.com/tal-tech/go-zero/core/threading" "github.com/tal-tech/go-zero/core/threading"
@@ -32,19 +33,21 @@ type (
container TaskContainer container TaskContainer
waitGroup sync.WaitGroup waitGroup sync.WaitGroup
// avoid race condition on waitGroup when calling wg.Add/Done/Wait(...) // avoid race condition on waitGroup when calling wg.Add/Done/Wait(...)
wgBarrier syncx.Barrier wgBarrier syncx.Barrier
guarded bool confirmChan chan lang.PlaceholderType
newTicker func(duration time.Duration) timex.Ticker guarded bool
lock sync.Mutex newTicker func(duration time.Duration) timex.Ticker
lock sync.Mutex
} }
) )
func NewPeriodicalExecutor(interval time.Duration, container TaskContainer) *PeriodicalExecutor { func NewPeriodicalExecutor(interval time.Duration, container TaskContainer) *PeriodicalExecutor {
executor := &PeriodicalExecutor{ executor := &PeriodicalExecutor{
// buffer 1 to let the caller go quickly // buffer 1 to let the caller go quickly
commander: make(chan interface{}, 1), commander: make(chan interface{}, 1),
interval: interval, interval: interval,
container: container, container: container,
confirmChan: make(chan lang.PlaceholderType),
newTicker: func(d time.Duration) timex.Ticker { newTicker: func(d time.Duration) timex.Ticker {
return timex.NewTicker(interval) return timex.NewTicker(interval)
}, },
@@ -59,10 +62,12 @@ func NewPeriodicalExecutor(interval time.Duration, container TaskContainer) *Per
func (pe *PeriodicalExecutor) Add(task interface{}) { func (pe *PeriodicalExecutor) Add(task interface{}) {
if vals, ok := pe.addAndCheck(task); ok { if vals, ok := pe.addAndCheck(task); ok {
pe.commander <- vals pe.commander <- vals
<-pe.confirmChan
} }
} }
func (pe *PeriodicalExecutor) Flush() bool { func (pe *PeriodicalExecutor) Flush() bool {
pe.enterExecution()
return pe.executeTasks(func() interface{} { return pe.executeTasks(func() interface{} {
pe.lock.Lock() pe.lock.Lock()
defer pe.lock.Unlock() defer pe.lock.Unlock()
@@ -114,6 +119,8 @@ func (pe *PeriodicalExecutor) backgroundFlush() {
select { select {
case vals := <-pe.commander: case vals := <-pe.commander:
commanded = true commanded = true
pe.enterExecution()
pe.confirmChan <- lang.Placeholder
pe.executeTasks(vals) pe.executeTasks(vals)
last = timex.Now() last = timex.Now()
case <-ticker.Chan(): case <-ticker.Chan():
@@ -135,11 +142,18 @@ func (pe *PeriodicalExecutor) backgroundFlush() {
}) })
} }
func (pe *PeriodicalExecutor) executeTasks(tasks interface{}) bool { func (pe *PeriodicalExecutor) doneExecution() {
pe.waitGroup.Done()
}
func (pe *PeriodicalExecutor) enterExecution() {
pe.wgBarrier.Guard(func() { pe.wgBarrier.Guard(func() {
pe.waitGroup.Add(1) pe.waitGroup.Add(1)
}) })
defer pe.waitGroup.Done() }
func (pe *PeriodicalExecutor) executeTasks(tasks interface{}) bool {
defer pe.doneExecution()
ok := pe.hasTasks(tasks) ok := pe.hasTasks(tasks)
if ok { if ok {

View File

@@ -106,6 +106,40 @@ func TestPeriodicalExecutor_Bulk(t *testing.T) {
lock.Unlock() lock.Unlock()
} }
func TestPeriodicalExecutor_Wait(t *testing.T) {
var lock sync.Mutex
executer := NewBulkExecutor(func(tasks []interface{}) {
lock.Lock()
defer lock.Unlock()
time.Sleep(10 * time.Millisecond)
}, WithBulkTasks(1), WithBulkInterval(time.Second))
for i := 0; i < 10; i++ {
executer.Add(1)
}
executer.Flush()
executer.Wait()
}
func TestPeriodicalExecutor_WaitFast(t *testing.T) {
const total = 3
var cnt int
var lock sync.Mutex
executer := NewBulkExecutor(func(tasks []interface{}) {
defer func() {
cnt++
}()
lock.Lock()
defer lock.Unlock()
time.Sleep(10 * time.Millisecond)
}, WithBulkTasks(1), WithBulkInterval(10*time.Millisecond))
for i := 0; i < total; i++ {
executer.Add(2)
}
executer.Flush()
executer.Wait()
assert.Equal(t, total, cnt)
}
// go test -benchtime 10s -bench . // go test -benchtime 10s -bench .
func BenchmarkExecutor(b *testing.B) { func BenchmarkExecutor(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()

View File

@@ -15,34 +15,34 @@ const (
text = `first line text = `first line
Cum sociis natoque penatibus et magnis dis parturient. Phasellus laoreet lorem vel dolor tempus vehicula. Vivamus sagittis lacus vel augue laoreet rutrum faucibus. Integer legentibus erat a ante historiarum dapibus. Cum sociis natoque penatibus et magnis dis parturient. Phasellus laoreet lorem vel dolor tempus vehicula. Vivamus sagittis lacus vel augue laoreet rutrum faucibus. Integer legentibus erat a ante historiarum dapibus.
Quisque ut dolor gravida, placerat libero vel, euismod. Quam temere in vitiis, legem sancimus haerentia. Qui ipsorum lingua Celtae, nostra Galli appellantur. Quis aute iure reprehenderit in voluptate velit esse. Fabio vel iudice vincam, sunt in culpa qui officia. Cras mattis iudicium purus sit amet fermentum. Quisque ut dolor gravida, placerat libero vel, euismod. Quam temere in vitiis, legem sancimus haerentia. Qui ipsorum lingua Celtae, nostra Galli appellantur. Quis aute iure reprehenderit in voluptate velit esse. Fabio vel iudice vincam, sunt in culpa qui officia. Cras mattis iudicium purus sit amet fermentum.
Quo usque tandem abutere, Catilina, patientia nostra? Gallia est omnis divisa in partes tres, quarum. Quam diu etiam furor iste tuus nos eludet? Quid securi etiam tamquam eu fugiat nulla pariatur. Curabitur blandit tempus ardua ridiculus sed magna. Quo usque tandem abutere, Catilina, patientia nostra? Gallia est omnis divisa in partes tres, quarum. Quam diu etiam furor iste tuus nos eludet? Quid securi etiam tamquam eu fugiat nulla pariatur. Curabitur blandit tempus ardua ridiculous sed magna.
Magna pars studiorum, prodita quaerimus. Cum ceteris in veneratione tui montes, nascetur mus. Morbi odio eros, volutpat ut pharetra vitae, lobortis sed nibh. Plura mihi bona sunt, inclinet, amari petere vellent. Idque Caesaris facere voluntate liceret: sese habere. Tu quoque, Brute, fili mi, nihil timor populi, nihil! Magna pars studiorum, prodita quaerimus. Cum ceteris in veneratione tui montes, nascetur mus. Morbi odio eros, volutpat ut pharetra vitae, lobortis sed nibh. Plura mihi bona sunt, inclinet, amari petere vellent. Idque Caesaris facere voluntate liceret: sese habere. Tu quoque, Brute, fili mi, nihil timor populi, nihil!
Tityre, tu patulae recubans sub tegmine fagi dolor. Inmensae subtilitatis, obscuris et malesuada fames. Quae vero auctorem tractata ab fiducia dicuntur. Tityre, tu patulae recubans sub tegmine fagi dolor. Inmensae subtilitatis, obscuris et malesuada fames. Quae vero auctorem tractata ab fiducia dicuntur.
Cum sociis natoque penatibus et magnis dis parturient. Phasellus laoreet lorem vel dolor tempus vehicula. Vivamus sagittis lacus vel augue laoreet rutrum faucibus. Integer legentibus erat a ante historiarum dapibus. Cum sociis natoque penatibus et magnis dis parturient. Phasellus laoreet lorem vel dolor tempus vehicula. Vivamus sagittis lacus vel augue laoreet rutrum faucibus. Integer legentibus erat a ante historiarum dapibus.
Quisque ut dolor gravida, placerat libero vel, euismod. Quam temere in vitiis, legem sancimus haerentia. Qui ipsorum lingua Celtae, nostra Galli appellantur. Quis aute iure reprehenderit in voluptate velit esse. Fabio vel iudice vincam, sunt in culpa qui officia. Cras mattis iudicium purus sit amet fermentum. Quisque ut dolor gravida, placerat libero vel, euismod. Quam temere in vitiis, legem sancimus haerentia. Qui ipsorum lingua Celtae, nostra Galli appellantur. Quis aute iure reprehenderit in voluptate velit esse. Fabio vel iudice vincam, sunt in culpa qui officia. Cras mattis iudicium purus sit amet fermentum.
Quo usque tandem abutere, Catilina, patientia nostra? Gallia est omnis divisa in partes tres, quarum. Quam diu etiam furor iste tuus nos eludet? Quid securi etiam tamquam eu fugiat nulla pariatur. Curabitur blandit tempus ardua ridiculus sed magna. Quo usque tandem abutere, Catilina, patientia nostra? Gallia est omnis divisa in partes tres, quarum. Quam diu etiam furor iste tuus nos eludet? Quid securi etiam tamquam eu fugiat nulla pariatur. Curabitur blandit tempus ardua ridiculous sed magna.
Magna pars studiorum, prodita quaerimus. Cum ceteris in veneratione tui montes, nascetur mus. Morbi odio eros, volutpat ut pharetra vitae, lobortis sed nibh. Plura mihi bona sunt, inclinet, amari petere vellent. Idque Caesaris facere voluntate liceret: sese habere. Tu quoque, Brute, fili mi, nihil timor populi, nihil! Magna pars studiorum, prodita quaerimus. Cum ceteris in veneratione tui montes, nascetur mus. Morbi odio eros, volutpat ut pharetra vitae, lobortis sed nibh. Plura mihi bona sunt, inclinet, amari petere vellent. Idque Caesaris facere voluntate liceret: sese habere. Tu quoque, Brute, fili mi, nihil timor populi, nihil!
Tityre, tu patulae recubans sub tegmine fagi dolor. Inmensae subtilitatis, obscuris et malesuada fames. Quae vero auctorem tractata ab fiducia dicuntur. Tityre, tu patulae recubans sub tegmine fagi dolor. Inmensae subtilitatis, obscuris et malesuada fames. Quae vero auctorem tractata ab fiducia dicuntur.
Cum sociis natoque penatibus et magnis dis parturient. Phasellus laoreet lorem vel dolor tempus vehicula. Vivamus sagittis lacus vel augue laoreet rutrum faucibus. Integer legentibus erat a ante historiarum dapibus. Cum sociis natoque penatibus et magnis dis parturient. Phasellus laoreet lorem vel dolor tempus vehicula. Vivamus sagittis lacus vel augue laoreet rutrum faucibus. Integer legentibus erat a ante historiarum dapibus.
Quisque ut dolor gravida, placerat libero vel, euismod. Quam temere in vitiis, legem sancimus haerentia. Qui ipsorum lingua Celtae, nostra Galli appellantur. Quis aute iure reprehenderit in voluptate velit esse. Fabio vel iudice vincam, sunt in culpa qui officia. Cras mattis iudicium purus sit amet fermentum. Quisque ut dolor gravida, placerat libero vel, euismod. Quam temere in vitiis, legem sancimus haerentia. Qui ipsorum lingua Celtae, nostra Galli appellantur. Quis aute iure reprehenderit in voluptate velit esse. Fabio vel iudice vincam, sunt in culpa qui officia. Cras mattis iudicium purus sit amet fermentum.
Quo usque tandem abutere, Catilina, patientia nostra? Gallia est omnis divisa in partes tres, quarum. Quam diu etiam furor iste tuus nos eludet? Quid securi etiam tamquam eu fugiat nulla pariatur. Curabitur blandit tempus ardua ridiculus sed magna. Quo usque tandem abutere, Catilina, patientia nostra? Gallia est omnis divisa in partes tres, quarum. Quam diu etiam furor iste tuus nos eludet? Quid securi etiam tamquam eu fugiat nulla pariatur. Curabitur blandit tempus ardua ridiculous sed magna.
Magna pars studiorum, prodita quaerimus. Cum ceteris in veneratione tui montes, nascetur mus. Morbi odio eros, volutpat ut pharetra vitae, lobortis sed nibh. Plura mihi bona sunt, inclinet, amari petere vellent. Idque Caesaris facere voluntate liceret: sese habere. Tu quoque, Brute, fili mi, nihil timor populi, nihil! Magna pars studiorum, prodita quaerimus. Cum ceteris in veneratione tui montes, nascetur mus. Morbi odio eros, volutpat ut pharetra vitae, lobortis sed nibh. Plura mihi bona sunt, inclinet, amari petere vellent. Idque Caesaris facere voluntate liceret: sese habere. Tu quoque, Brute, fili mi, nihil timor populi, nihil!
Tityre, tu patulae recubans sub tegmine fagi dolor. Inmensae subtilitatis, obscuris et malesuada fames. Quae vero auctorem tractata ab fiducia dicuntur. Tityre, tu patulae recubans sub tegmine fagi dolor. Inmensae subtilitatis, obscuris et malesuada fames. Quae vero auctorem tractata ab fiducia dicuntur.
` + longLine ` + longLine
textWithLastNewline = `first line textWithLastNewline = `first line
Cum sociis natoque penatibus et magnis dis parturient. Phasellus laoreet lorem vel dolor tempus vehicula. Vivamus sagittis lacus vel augue laoreet rutrum faucibus. Integer legentibus erat a ante historiarum dapibus. Cum sociis natoque penatibus et magnis dis parturient. Phasellus laoreet lorem vel dolor tempus vehicula. Vivamus sagittis lacus vel augue laoreet rutrum faucibus. Integer legentibus erat a ante historiarum dapibus.
Quisque ut dolor gravida, placerat libero vel, euismod. Quam temere in vitiis, legem sancimus haerentia. Qui ipsorum lingua Celtae, nostra Galli appellantur. Quis aute iure reprehenderit in voluptate velit esse. Fabio vel iudice vincam, sunt in culpa qui officia. Cras mattis iudicium purus sit amet fermentum. Quisque ut dolor gravida, placerat libero vel, euismod. Quam temere in vitiis, legem sancimus haerentia. Qui ipsorum lingua Celtae, nostra Galli appellantur. Quis aute iure reprehenderit in voluptate velit esse. Fabio vel iudice vincam, sunt in culpa qui officia. Cras mattis iudicium purus sit amet fermentum.
Quo usque tandem abutere, Catilina, patientia nostra? Gallia est omnis divisa in partes tres, quarum. Quam diu etiam furor iste tuus nos eludet? Quid securi etiam tamquam eu fugiat nulla pariatur. Curabitur blandit tempus ardua ridiculus sed magna. Quo usque tandem abutere, Catilina, patientia nostra? Gallia est omnis divisa in partes tres, quarum. Quam diu etiam furor iste tuus nos eludet? Quid securi etiam tamquam eu fugiat nulla pariatur. Curabitur blandit tempus ardua ridiculous sed magna.
Magna pars studiorum, prodita quaerimus. Cum ceteris in veneratione tui montes, nascetur mus. Morbi odio eros, volutpat ut pharetra vitae, lobortis sed nibh. Plura mihi bona sunt, inclinet, amari petere vellent. Idque Caesaris facere voluntate liceret: sese habere. Tu quoque, Brute, fili mi, nihil timor populi, nihil! Magna pars studiorum, prodita quaerimus. Cum ceteris in veneratione tui montes, nascetur mus. Morbi odio eros, volutpat ut pharetra vitae, lobortis sed nibh. Plura mihi bona sunt, inclinet, amari petere vellent. Idque Caesaris facere voluntate liceret: sese habere. Tu quoque, Brute, fili mi, nihil timor populi, nihil!
Tityre, tu patulae recubans sub tegmine fagi dolor. Inmensae subtilitatis, obscuris et malesuada fames. Quae vero auctorem tractata ab fiducia dicuntur. Tityre, tu patulae recubans sub tegmine fagi dolor. Inmensae subtilitatis, obscuris et malesuada fames. Quae vero auctorem tractata ab fiducia dicuntur.
Cum sociis natoque penatibus et magnis dis parturient. Phasellus laoreet lorem vel dolor tempus vehicula. Vivamus sagittis lacus vel augue laoreet rutrum faucibus. Integer legentibus erat a ante historiarum dapibus. Cum sociis natoque penatibus et magnis dis parturient. Phasellus laoreet lorem vel dolor tempus vehicula. Vivamus sagittis lacus vel augue laoreet rutrum faucibus. Integer legentibus erat a ante historiarum dapibus.
Quisque ut dolor gravida, placerat libero vel, euismod. Quam temere in vitiis, legem sancimus haerentia. Qui ipsorum lingua Celtae, nostra Galli appellantur. Quis aute iure reprehenderit in voluptate velit esse. Fabio vel iudice vincam, sunt in culpa qui officia. Cras mattis iudicium purus sit amet fermentum. Quisque ut dolor gravida, placerat libero vel, euismod. Quam temere in vitiis, legem sancimus haerentia. Qui ipsorum lingua Celtae, nostra Galli appellantur. Quis aute iure reprehenderit in voluptate velit esse. Fabio vel iudice vincam, sunt in culpa qui officia. Cras mattis iudicium purus sit amet fermentum.
Quo usque tandem abutere, Catilina, patientia nostra? Gallia est omnis divisa in partes tres, quarum. Quam diu etiam furor iste tuus nos eludet? Quid securi etiam tamquam eu fugiat nulla pariatur. Curabitur blandit tempus ardua ridiculus sed magna. Quo usque tandem abutere, Catilina, patientia nostra? Gallia est omnis divisa in partes tres, quarum. Quam diu etiam furor iste tuus nos eludet? Quid securi etiam tamquam eu fugiat nulla pariatur. Curabitur blandit tempus ardua ridiculous sed magna.
Magna pars studiorum, prodita quaerimus. Cum ceteris in veneratione tui montes, nascetur mus. Morbi odio eros, volutpat ut pharetra vitae, lobortis sed nibh. Plura mihi bona sunt, inclinet, amari petere vellent. Idque Caesaris facere voluntate liceret: sese habere. Tu quoque, Brute, fili mi, nihil timor populi, nihil! Magna pars studiorum, prodita quaerimus. Cum ceteris in veneratione tui montes, nascetur mus. Morbi odio eros, volutpat ut pharetra vitae, lobortis sed nibh. Plura mihi bona sunt, inclinet, amari petere vellent. Idque Caesaris facere voluntate liceret: sese habere. Tu quoque, Brute, fili mi, nihil timor populi, nihil!
Tityre, tu patulae recubans sub tegmine fagi dolor. Inmensae subtilitatis, obscuris et malesuada fames. Quae vero auctorem tractata ab fiducia dicuntur. Tityre, tu patulae recubans sub tegmine fagi dolor. Inmensae subtilitatis, obscuris et malesuada fames. Quae vero auctorem tractata ab fiducia dicuntur.
Cum sociis natoque penatibus et magnis dis parturient. Phasellus laoreet lorem vel dolor tempus vehicula. Vivamus sagittis lacus vel augue laoreet rutrum faucibus. Integer legentibus erat a ante historiarum dapibus. Cum sociis natoque penatibus et magnis dis parturient. Phasellus laoreet lorem vel dolor tempus vehicula. Vivamus sagittis lacus vel augue laoreet rutrum faucibus. Integer legentibus erat a ante historiarum dapibus.
Quisque ut dolor gravida, placerat libero vel, euismod. Quam temere in vitiis, legem sancimus haerentia. Qui ipsorum lingua Celtae, nostra Galli appellantur. Quis aute iure reprehenderit in voluptate velit esse. Fabio vel iudice vincam, sunt in culpa qui officia. Cras mattis iudicium purus sit amet fermentum. Quisque ut dolor gravida, placerat libero vel, euismod. Quam temere in vitiis, legem sancimus haerentia. Qui ipsorum lingua Celtae, nostra Galli appellantur. Quis aute iure reprehenderit in voluptate velit esse. Fabio vel iudice vincam, sunt in culpa qui officia. Cras mattis iudicium purus sit amet fermentum.
Quo usque tandem abutere, Catilina, patientia nostra? Gallia est omnis divisa in partes tres, quarum. Quam diu etiam furor iste tuus nos eludet? Quid securi etiam tamquam eu fugiat nulla pariatur. Curabitur blandit tempus ardua ridiculus sed magna. Quo usque tandem abutere, Catilina, patientia nostra? Gallia est omnis divisa in partes tres, quarum. Quam diu etiam furor iste tuus nos eludet? Quid securi etiam tamquam eu fugiat nulla pariatur. Curabitur blandit tempus ardua ridiculous sed magna.
Magna pars studiorum, prodita quaerimus. Cum ceteris in veneratione tui montes, nascetur mus. Morbi odio eros, volutpat ut pharetra vitae, lobortis sed nibh. Plura mihi bona sunt, inclinet, amari petere vellent. Idque Caesaris facere voluntate liceret: sese habere. Tu quoque, Brute, fili mi, nihil timor populi, nihil! Magna pars studiorum, prodita quaerimus. Cum ceteris in veneratione tui montes, nascetur mus. Morbi odio eros, volutpat ut pharetra vitae, lobortis sed nibh. Plura mihi bona sunt, inclinet, amari petere vellent. Idque Caesaris facere voluntate liceret: sese habere. Tu quoque, Brute, fili mi, nihil timor populi, nihil!
Tityre, tu patulae recubans sub tegmine fagi dolor. Inmensae subtilitatis, obscuris et malesuada fames. Quae vero auctorem tractata ab fiducia dicuntur. Tityre, tu patulae recubans sub tegmine fagi dolor. Inmensae subtilitatis, obscuris et malesuada fames. Quae vero auctorem tractata ab fiducia dicuntur.
` + longLine + "\n" ` + longLine + "\n"

View File

@@ -49,7 +49,7 @@ func From(generate GenerateFunc) Stream {
return Range(source) return Range(source)
} }
// Just converts the given arbitary items to a Stream. // Just converts the given arbitrary items to a Stream.
func Just(items ...interface{}) Stream { func Just(items ...interface{}) Stream {
source := make(chan interface{}, len(items)) source := make(chan interface{}, len(items))
for _, item := range items { for _, item := range items {
@@ -195,7 +195,7 @@ func (p Stream) Merge() Stream {
return Range(source) return Range(source)
} }
// Parallel applies the given ParallenFunc to each item concurrently with given number of workers. // Parallel applies the given ParallelFunc to each item concurrently with given number of workers.
func (p Stream) Parallel(fn ParallelFunc, opts ...Option) { func (p Stream) Parallel(fn ParallelFunc, opts ...Option) {
p.Walk(func(item interface{}, pipe chan<- interface{}) { p.Walk(func(item interface{}, pipe chan<- interface{}) {
fn(item) fn(item)

View File

@@ -1,16 +1,8 @@
package lang package lang
import "log"
var Placeholder PlaceholderType var Placeholder PlaceholderType
type ( type (
GenericType = interface{} GenericType = interface{}
PlaceholderType = struct{} PlaceholderType = struct{}
) )
func Must(err error) {
if err != nil {
log.Fatal(err)
}
}

View File

@@ -1,7 +0,0 @@
package lang
import "testing"
func TestMust(t *testing.T) {
Must(nil)
}

View File

@@ -17,7 +17,6 @@ import (
"sync/atomic" "sync/atomic"
"github.com/tal-tech/go-zero/core/iox" "github.com/tal-tech/go-zero/core/iox"
"github.com/tal-tech/go-zero/core/lang"
"github.com/tal-tech/go-zero/core/sysx" "github.com/tal-tech/go-zero/core/sysx"
"github.com/tal-tech/go-zero/core/timex" "github.com/tal-tech/go-zero/core/timex"
) )
@@ -46,6 +45,7 @@ const (
levelInfo = "info" levelInfo = "info"
levelError = "error" levelError = "error"
levelSevere = "severe" levelSevere = "severe"
levelFatal = "fatal"
levelSlow = "slow" levelSlow = "slow"
levelStat = "stat" levelStat = "stat"
@@ -100,7 +100,7 @@ type (
) )
func MustSetup(c LogConf) { func MustSetup(c LogConf) {
lang.Must(SetUp(c)) Must(SetUp(c))
} }
// SetUp sets up the logx. If already set up, just return nil. // SetUp sets up the logx. If already set up, just return nil.
@@ -210,6 +210,14 @@ func Infof(format string, v ...interface{}) {
infoSync(fmt.Sprintf(format, v...)) infoSync(fmt.Sprintf(format, v...))
} }
func Must(err error) {
if err != nil {
msg := formatWithCaller(err.Error(), 3)
output(severeLog, levelFatal, msg)
os.Exit(1)
}
}
func SetLevel(level uint32) { func SetLevel(level uint32) {
atomic.StoreUint32(&logLevel, level) atomic.StoreUint32(&logLevel, level)
} }

View File

@@ -131,6 +131,10 @@ func TestSetLevelWithDuration(t *testing.T) {
assert.Equal(t, 0, writer.builder.Len()) assert.Equal(t, 0, writer.builder.Len())
} }
func TestMustNil(t *testing.T) {
Must(nil)
}
func BenchmarkCopyByteSliceAppend(b *testing.B) { func BenchmarkCopyByteSliceAppend(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
var buf []byte var buf []byte

View File

@@ -9,24 +9,24 @@ import (
var ErrNoAvailablePusher = errors.New("no available pusher") var ErrNoAvailablePusher = errors.New("no available pusher")
type BalancedQueuePusher struct { type BalancedPusher struct {
name string name string
pushers []Pusher pushers []Pusher
index uint64 index uint64
} }
func NewBalancedQueuePusher(pushers []Pusher) Pusher { func NewBalancedPusher(pushers []Pusher) Pusher {
return &BalancedQueuePusher{ return &BalancedPusher{
name: generateName(pushers), name: generateName(pushers),
pushers: pushers, pushers: pushers,
} }
} }
func (pusher *BalancedQueuePusher) Name() string { func (pusher *BalancedPusher) Name() string {
return pusher.name return pusher.name
} }
func (pusher *BalancedQueuePusher) Push(message string) error { func (pusher *BalancedPusher) Push(message string) error {
size := len(pusher.pushers) size := len(pusher.pushers)
for i := 0; i < size; i++ { for i := 0; i < size; i++ {

View File

@@ -20,7 +20,7 @@ func TestBalancedQueuePusher(t *testing.T) {
mockedPushers = append(mockedPushers, p) mockedPushers = append(mockedPushers, p)
} }
pusher := NewBalancedQueuePusher(pushers) pusher := NewBalancedPusher(pushers)
assert.True(t, len(pusher.Name()) > 0) assert.True(t, len(pusher.Name()) > 0)
for i := 0; i < numPushers*1000; i++ { for i := 0; i < numPushers*1000; i++ {
@@ -37,7 +37,7 @@ func TestBalancedQueuePusher(t *testing.T) {
} }
func TestBalancedQueuePusher_NoAvailable(t *testing.T) { func TestBalancedQueuePusher_NoAvailable(t *testing.T) {
pusher := NewBalancedQueuePusher(nil) pusher := NewBalancedPusher(nil)
assert.True(t, len(pusher.Name()) == 0) assert.True(t, len(pusher.Name()) == 0)
assert.Equal(t, ErrNoAvailablePusher, pusher.Push("item")) assert.Equal(t, ErrNoAvailablePusher, pusher.Push("item"))
} }

View File

@@ -2,23 +2,23 @@ package queue
import "github.com/tal-tech/go-zero/core/errorx" import "github.com/tal-tech/go-zero/core/errorx"
type MultiQueuePusher struct { type MultiPusher struct {
name string name string
pushers []Pusher pushers []Pusher
} }
func NewMultiQueuePusher(pushers []Pusher) Pusher { func NewMultiPusher(pushers []Pusher) Pusher {
return &MultiQueuePusher{ return &MultiPusher{
name: generateName(pushers), name: generateName(pushers),
pushers: pushers, pushers: pushers,
} }
} }
func (pusher *MultiQueuePusher) Name() string { func (pusher *MultiPusher) Name() string {
return pusher.name return pusher.name
} }
func (pusher *MultiQueuePusher) Push(message string) error { func (pusher *MultiPusher) Push(message string) error {
var batchError errorx.BatchError var batchError errorx.BatchError
for _, each := range pusher.pushers { for _, each := range pusher.pushers {

View File

@@ -21,7 +21,7 @@ func TestMultiQueuePusher(t *testing.T) {
mockedPushers = append(mockedPushers, p) mockedPushers = append(mockedPushers, p)
} }
pusher := NewMultiQueuePusher(pushers) pusher := NewMultiPusher(pushers)
assert.True(t, len(pusher.Name()) > 0) assert.True(t, len(pusher.Name()) > 0)
for i := 0; i < 1000; i++ { for i := 0; i < 1000; i++ {

View File

@@ -14,7 +14,6 @@ import (
"github.com/tal-tech/go-zero/core/proc" "github.com/tal-tech/go-zero/core/proc"
"github.com/tal-tech/go-zero/core/sysx" "github.com/tal-tech/go-zero/core/sysx"
"github.com/tal-tech/go-zero/core/timex" "github.com/tal-tech/go-zero/core/timex"
"github.com/tal-tech/go-zero/core/utils"
) )
const ( const (
@@ -24,7 +23,7 @@ const (
) )
var ( var (
reporter = utils.Report reporter func(string)
lock sync.RWMutex lock sync.RWMutex
lessExecutor = executors.NewLessExecutor(time.Minute * 5) lessExecutor = executors.NewLessExecutor(time.Minute * 5)
dropped int32 dropped int32

View File

@@ -7,7 +7,7 @@ import (
"time" "time"
"github.com/tal-tech/go-zero/core/iox" "github.com/tal-tech/go-zero/core/iox"
"github.com/tal-tech/go-zero/core/lang" "github.com/tal-tech/go-zero/core/logx"
) )
const ( const (
@@ -24,17 +24,17 @@ var (
func init() { func init() {
cpus, err := perCpuUsage() cpus, err := perCpuUsage()
lang.Must(err) logx.Must(err)
cores = uint64(len(cpus)) cores = uint64(len(cpus))
sets, err := cpuSets() sets, err := cpuSets()
lang.Must(err) logx.Must(err)
quota = float64(len(sets)) quota = float64(len(sets))
cq, err := cpuQuota() cq, err := cpuQuota()
if err == nil { if err == nil {
if cq != -1 { if cq != -1 {
period, err := cpuPeriod() period, err := cpuPeriod()
lang.Must(err) logx.Must(err)
limit := float64(cq) / float64(period) limit := float64(cq) / float64(period)
if limit < quota { if limit < quota {
@@ -44,10 +44,10 @@ func init() {
} }
preSystem, err = systemCpuUsage() preSystem, err = systemCpuUsage()
lang.Must(err) logx.Must(err)
preTotal, err = totalCpuUsage() preTotal, err = totalCpuUsage()
lang.Must(err) logx.Must(err)
} }
func RefreshCpu() uint64 { func RefreshCpu() uint64 {

View File

@@ -5,7 +5,6 @@ import (
"time" "time"
"github.com/tal-tech/go-zero/core/collection" "github.com/tal-tech/go-zero/core/collection"
"github.com/tal-tech/go-zero/core/lang"
"github.com/tal-tech/go-zero/core/logx" "github.com/tal-tech/go-zero/core/logx"
"github.com/tal-tech/go-zero/core/proc" "github.com/tal-tech/go-zero/core/proc"
"github.com/tal-tech/go-zero/core/stat" "github.com/tal-tech/go-zero/core/stat"
@@ -33,7 +32,7 @@ type delayTask struct {
func init() { func init() {
var err error var err error
timingWheel, err = collection.NewTimingWheel(time.Second, timingWheelSlots, clean) timingWheel, err = collection.NewTimingWheel(time.Second, timingWheelSlots, clean)
lang.Must(err) logx.Must(err)
proc.AddShutdownListener(func() { proc.AddShutdownListener(func() {
timingWheel.Drain(clean) timingWheel.Drain(clean)

View File

@@ -212,10 +212,12 @@ func TestRedis_Persist(t *testing.T) {
assert.Nil(t, err) assert.Nil(t, err)
assert.False(t, ok) assert.False(t, ok)
err = client.Expire("key", 5) err = client.Expire("key", 5)
assert.Nil(t, err)
ok, err = client.Persist("key") ok, err = client.Persist("key")
assert.Nil(t, err) assert.Nil(t, err)
assert.True(t, ok) assert.True(t, ok)
err = client.Expireat("key", time.Now().Unix()+5) err = client.Expireat("key", time.Now().Unix()+5)
assert.Nil(t, err)
ok, err = client.Persist("key") ok, err = client.Persist("key")
assert.Nil(t, err) assert.Nil(t, err)
assert.True(t, ok) assert.True(t, ok)
@@ -379,7 +381,7 @@ func TestRedis_SortedSet(t *testing.T) {
rank, err := client.Zrank("key", "value2") rank, err := client.Zrank("key", "value2")
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, int64(1), rank) assert.Equal(t, int64(1), rank)
rank, err = client.Zrank("key", "value4") _, err = client.Zrank("key", "value4")
assert.Equal(t, redis.Nil, err) assert.Equal(t, redis.Nil, err)
num, err := client.Zrem("key", "value2", "value3") num, err := client.Zrem("key", "value2", "value3")
assert.Nil(t, err) assert.Nil(t, err)

View File

@@ -249,10 +249,12 @@ func TestRedis_Persist(t *testing.T) {
assert.Nil(t, err) assert.Nil(t, err)
assert.False(t, ok) assert.False(t, ok)
err = client.Expire("key", 5) err = client.Expire("key", 5)
assert.Nil(t, err)
ok, err = client.Persist("key") ok, err = client.Persist("key")
assert.Nil(t, err) assert.Nil(t, err)
assert.True(t, ok) assert.True(t, ok)
err = client.Expireat("key", time.Now().Unix()+5) err = client.Expireat("key", time.Now().Unix()+5)
assert.Nil(t, err)
ok, err = client.Persist("key") ok, err = client.Persist("key")
assert.Nil(t, err) assert.Nil(t, err)
assert.True(t, ok) assert.True(t, ok)
@@ -447,7 +449,7 @@ func TestRedis_SortedSet(t *testing.T) {
rank, err := client.Zrank("key", "value2") rank, err := client.Zrank("key", "value2")
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, int64(1), rank) assert.Equal(t, int64(1), rank)
rank, err = client.Zrank("key", "value4") _, err = client.Zrank("key", "value4")
assert.Equal(t, Nil, err) assert.Equal(t, Nil, err)
num, err := client.Zrem("key", "value2", "value3") num, err := client.Zrem("key", "value2", "value3")
assert.Nil(t, err) assert.Nil(t, err)
@@ -558,6 +560,7 @@ func TestRedis_Pipelined(t *testing.T) {
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, "1", value) assert.Equal(t, "1", value)
score, err := client.Zscore("zadd", "zadd") score, err := client.Zscore("zadd", "zadd")
assert.Nil(t, err)
assert.Equal(t, int64(12), score) assert.Equal(t, int64(12), score)
}) })
} }

View File

@@ -63,11 +63,6 @@ func (r *replacer) Replace(text string) string {
i = j - 1 i = j - 1
builder.WriteString(r.mapping[string(chars[start:end])]) builder.WriteString(r.mapping[string(chars[start:end])])
} else { } else {
if j < size {
end = j + 1
} else {
end = size
}
builder.WriteRune(chars[i]) builder.WriteRune(chars[i])
} }
start = -1 start = -1

View File

@@ -2,7 +2,11 @@ package stringx
import "github.com/tal-tech/go-zero/core/lang" import "github.com/tal-tech/go-zero/core/lang"
const defaultMask = '*'
type ( type (
TrieOption func(trie *trieNode)
Trie interface { Trie interface {
Filter(text string) (string, []string, bool) Filter(text string) (string, []string, bool)
FindKeywords(text string) []string FindKeywords(text string) []string
@@ -10,6 +14,7 @@ type (
trieNode struct { trieNode struct {
node node
mask rune
} }
scope struct { scope struct {
@@ -18,8 +23,15 @@ type (
} }
) )
func NewTrie(words []string) Trie { func NewTrie(words []string, opts ...TrieOption) Trie {
n := new(trieNode) n := new(trieNode)
for _, opt := range opts {
opt(n)
}
if n.mask == 0 {
n.mask = defaultMask
}
for _, word := range words { for _, word := range words {
n.add(word) n.add(word)
} }
@@ -114,6 +126,12 @@ func (n *trieNode) findKeywordScopes(chars []rune) []scope {
func (n *trieNode) replaceWithAsterisk(chars []rune, start, stop int) { func (n *trieNode) replaceWithAsterisk(chars []rune, start, stop int) {
for i := start; i < stop; i++ { for i := start; i < stop; i++ {
chars[i] = '*' chars[i] = n.mask
}
}
func WithMask(mask rune) TrieOption {
return func(n *trieNode) {
n.mask = mask
} }
} }

View File

@@ -109,25 +109,25 @@ func TestTrie(t *testing.T) {
func TestTrieSingleWord(t *testing.T) { func TestTrieSingleWord(t *testing.T) {
trie := NewTrie([]string{ trie := NewTrie([]string{
"闹", "闹",
}) }, WithMask('#'))
output, keywords, ok := trie.Filter("今晚真热闹") output, keywords, ok := trie.Filter("今晚真热闹")
assert.ElementsMatch(t, []string{"闹"}, keywords) assert.ElementsMatch(t, []string{"闹"}, keywords)
assert.True(t, ok) assert.True(t, ok)
assert.Equal(t, "今晚真热*", output) assert.Equal(t, "今晚真热#", output)
} }
func TestTrieOverlap(t *testing.T) { func TestTrieOverlap(t *testing.T) {
trie := NewTrie([]string{ trie := NewTrie([]string{
"一二三四五", "一二三四五",
"二三四五六七八", "二三四五六七八",
}) }, WithMask('#'))
output, keywords, ok := trie.Filter("零一二三四五六七八九十") output, keywords, ok := trie.Filter("零一二三四五六七八九十")
assert.ElementsMatch(t, []string{ assert.ElementsMatch(t, []string{
"一二三四五", "一二三四五",
"二三四五六七八", "二三四五六七八",
}, keywords) }, keywords)
assert.True(t, ok) assert.True(t, ok)
assert.Equal(t, "零********九十", output) assert.Equal(t, "零########九十", output)
} }
func TestTrieNested(t *testing.T) { func TestTrieNested(t *testing.T) {
@@ -135,7 +135,7 @@ func TestTrieNested(t *testing.T) {
"一二三", "一二三",
"一二三四五", "一二三四五",
"一二三四五六七八", "一二三四五六七八",
}) }, WithMask('#'))
output, keywords, ok := trie.Filter("零一二三四五六七八九十") output, keywords, ok := trie.Filter("零一二三四五六七八九十")
assert.ElementsMatch(t, []string{ assert.ElementsMatch(t, []string{
"一二三", "一二三",
@@ -143,7 +143,7 @@ func TestTrieNested(t *testing.T) {
"一二三四五六七八", "一二三四五六七八",
}, keywords) }, keywords)
assert.True(t, ok) assert.True(t, ok)
assert.Equal(t, "零********九十", output) assert.Equal(t, "零########九十", output)
} }
func BenchmarkTrie(b *testing.B) { func BenchmarkTrie(b *testing.B) {

View File

@@ -3,7 +3,7 @@ package sysx
import ( import (
"os" "os"
"github.com/tal-tech/go-zero/core/lang" "github.com/tal-tech/go-zero/core/stringx"
) )
var hostname string var hostname string
@@ -11,7 +11,9 @@ var hostname string
func init() { func init() {
var err error var err error
hostname, err = os.Hostname() hostname, err = os.Hostname()
lang.Must(err) if err != nil {
hostname = stringx.RandId()
}
} }
func Hostname() string { func Hostname() string {

View File

@@ -1,5 +0,0 @@
package utils
func Report(content string) {
// TODO: implement the report method
}

View File

@@ -3,8 +3,8 @@
## goctl用途 ## goctl用途
* 定义api请求 * 定义api请求
* 根据定义的api自动生成golang(后端), java(iOS & Android), typescript(web & 晓程序)dart(flutter) * 根据定义的api自动生成golang(后端), java(iOS & Android), typescript(web & 晓程序)dart(flutter)
* 生成MySQL CURD (https://goctl.xiaoheiban.cn) * 生成MySQL CURD+Cache
* 生成MongoDB CURD (https://goctl.xiaoheiban.cn) * 生成MongoDB CURD+Cache
## goctl使用说明 ## goctl使用说明
#### goctl参数说明 #### goctl参数说明
@@ -179,23 +179,31 @@ service user-api {
* 在定义的get/post/put/delete等请求的handler和logic里增加处理业务逻辑的代码 * 在定义的get/post/put/delete等请求的handler和logic里增加处理业务逻辑的代码
#### 根据定义好的api文件生成java代码 #### 根据定义好的api文件生成java代码
`goctl api java -api user/user.api -dir ./src` ```shell
goctl api java -api user/user.api -dir ./src
```
#### 根据定义好的api文件生成typescript代码 #### 根据定义好的api文件生成typescript代码
`goctl api ts -api user/user.api -dir ./src -webapi ***` ```shell
goctl api ts -api user/user.api -dir ./src -webapi ***
ts需要指定webapi所在目录
ts需要指定webapi所在目录
```
#### 根据定义好的api文件生成Dart代码 #### 根据定义好的api文件生成Dart代码
`goctl api dart -api user/user.api -dir ./src` ```shell
goctl api dart -api user/user.api -dir ./src
```
## 根据定义好的简单go文件生成mongo代码文件(仅限golang使用) ## 根据定义好的简单go文件生成mongo代码文件(仅限golang使用)
`goctl model mongo -src {{yourDir}}/xiao/service/xhb/user/model/usermodel.go -cache yes` ```shell
goctl model mongo -src {{yourDir}}/xiao/service/xhb/user/model/usermodel.go -cache yes
-src需要提供简单的usermodel.go文件里面只需要提供一个结构体即可
-cache 控制是否需要缓存 yes=需要 no=不需要 -src需要提供简单的usermodel.go文件里面只需要提供一个结构体即可
src 示例代码如下 -cache 控制是否需要缓存 yes=需要 no=不需要
``` src 示例代码如下
```
```go
package model package model
type User struct { type User struct {
@@ -261,5 +269,4 @@ type User struct {
│   └── test.go [强制覆盖更新] │   └── test.go [强制覆盖更新]
└── test.proto └── test.proto
``` ```
- 注意 目前rpc目录生成的proto文件暂不支持import外部proto文件 - 注意 目前rpc目录生成的proto文件暂不支持import外部proto文件
* 如有不理解的地方随时问Kim/Kevin

BIN
doc/images/benchmark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
doc/images/trie.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

79
doc/keywords.md Normal file
View File

@@ -0,0 +1,79 @@
# 高效的关键词替换和敏感词过滤工具
## 1. 算法介绍
利用高效的Trie树建立关键词树如下图所示然后依次查找字符串中的相连字符是否形成树的一条路径
<img src="images/trie.png" alt="trie" width="350" />
发现掘金上[这篇文章](https://juejin.im/post/6844903750490914829)写的比较详细,可以一读,具体原理在此不详述。
## 2. 关键词替换
```go
replacer := stringx.NewReplacer(map[string]string{
"PHP": "PPT",
"世界上": "吹牛",
})
fmt.Println(replacer.Replace("PHP是世界上最好的语言"))
```
可以得到:
```
PPT是吹牛最好的语言
```
示例代码见`example/stringx/replace/replace.go`
## 3. 查找敏感词
```go
filter := stringx.NewTrie([]string{
"AV演员",
"苍井空",
"AV",
"日本AV女优",
"AV演员色情",
})
keywords := filter.FindKeywords("日本AV演员兼电视、电影演员。苍井空AV女优是xx出道, 日本AV女优们最精彩的表演是AV演员色情表演")
fmt.Println(keywords)
```
可以得到:
```
[苍井空 日本AV女优 AV演员色情 AV AV演员]
```
## 4. 敏感词过滤
```go
filter := stringx.NewTrie([]string{
"AV演员",
"苍井空",
"AV",
"日本AV女优",
"AV演员色情",
}, stringx.WithMask('?')) // 默认替换为*
safe, keywords, found := filter.Filter("日本AV演员兼电视、电影演员。苍井空AV女优是xx出道, 日本AV女优们最精彩的表演是AV演员色情表演")
fmt.Println(safe)
fmt.Println(keywords)
fmt.Println(found)
```
可以得到:
```
日本????兼电视、电影演员。?????女优是xx出道, ??????们最精彩的表演是??????表演
[苍井空 日本AV女优 AV演员色情 AV AV演员]
true
```
示例代码见`example/stringx/filter/filter.go`
## 5. Benchmark
| Sentences | Keywords | Regex | go-zero |
| --------- | -------- | -------- | ------- |
| 10000 | 10000 | 16min10s | 27.2ms |

View File

@@ -10,6 +10,7 @@ import (
"github.com/tal-tech/go-zero/core/breaker" "github.com/tal-tech/go-zero/core/breaker"
"github.com/tal-tech/go-zero/core/lang" "github.com/tal-tech/go-zero/core/lang"
"github.com/tal-tech/go-zero/core/logx"
"gopkg.in/cheggaaa/pb.v1" "gopkg.in/cheggaaa/pb.v1"
) )
@@ -99,7 +100,7 @@ func main() {
gb := breaker.NewBreaker() gb := breaker.NewBreaker()
fp, err := os.Create("result.csv") fp, err := os.Create("result.csv")
lang.Must(err) logx.Must(err)
defer fp.Close() defer fp.Close()
fmt.Fprintln(fp, "seconds,state,googleCalls,netflixCalls") fmt.Fprintln(fp, "seconds,state,googleCalls,netflixCalls")

View File

@@ -5,12 +5,12 @@ import (
"time" "time"
"github.com/tal-tech/go-zero/core/discov" "github.com/tal-tech/go-zero/core/discov"
"github.com/tal-tech/go-zero/core/lang" "github.com/tal-tech/go-zero/core/logx"
) )
func main() { func main() {
sub, err := discov.NewSubscriber([]string{"etcd.discovery:2379"}, "028F2C35852D", discov.Exclusive()) sub, err := discov.NewSubscriber([]string{"etcd.discovery:2379"}, "028F2C35852D", discov.Exclusive())
lang.Must(err) logx.Must(err)
ticker := time.NewTicker(time.Second * 3) ticker := time.NewTicker(time.Second * 3)
defer ticker.Stop() defer ticker.Stop()

View File

@@ -9,12 +9,13 @@ import (
"time" "time"
"github.com/tal-tech/go-zero/core/lang" "github.com/tal-tech/go-zero/core/lang"
"github.com/tal-tech/go-zero/core/logx"
"github.com/tal-tech/go-zero/core/threading" "github.com/tal-tech/go-zero/core/threading"
"gopkg.in/cheggaaa/pb.v1" "gopkg.in/cheggaaa/pb.v1"
) )
var ( var (
freq = flag.Int("freq", 100, "frequence") freq = flag.Int("freq", 100, "frequency")
duration = flag.String("duration", "10s", "duration") duration = flag.String("duration", "10s", "duration")
) )
@@ -83,8 +84,8 @@ func (m *metric) reset() counting {
return result return result
} }
func runRequests(url string, frequence int, metrics *metric, done <-chan lang.PlaceholderType) { func runRequests(url string, frequency int, metrics *metric, done <-chan lang.PlaceholderType) {
ticker := time.NewTicker(time.Second / time.Duration(frequence)) ticker := time.NewTicker(time.Second / time.Duration(frequency))
defer ticker.Stop() defer ticker.Stop()
for { for {
@@ -119,14 +120,14 @@ func main() {
flag.Parse() flag.Parse()
fp, err := os.Create("result.csv") fp, err := os.Create("result.csv")
lang.Must(err) logx.Must(err)
defer fp.Close() defer fp.Close()
fmt.Fprintln(fp, "seconds,goodOk,goodFail,goodReject,goodErrs,goodUnknowns,goodDropRatio,"+ fmt.Fprintln(fp, "seconds,goodOk,goodFail,goodReject,goodErrs,goodUnknowns,goodDropRatio,"+
"heavyOk,heavyFail,heavyReject,heavyErrs,heavyUnknowns,heavyDropRatio") "heavyOk,heavyFail,heavyReject,heavyErrs,heavyUnknowns,heavyDropRatio")
var gm, hm metric var gm, hm metric
dur, err := time.ParseDuration(*duration) dur, err := time.ParseDuration(*duration)
lang.Must(err) logx.Must(err)
done := make(chan lang.PlaceholderType) done := make(chan lang.PlaceholderType)
group := threading.NewRoutineGroup() group := threading.NewRoutineGroup()
group.RunSafe(func() { group.RunSafe(func() {

View File

@@ -13,7 +13,7 @@ import (
"github.com/tal-tech/go-zero/core/collection" "github.com/tal-tech/go-zero/core/collection"
"github.com/tal-tech/go-zero/core/executors" "github.com/tal-tech/go-zero/core/executors"
"github.com/tal-tech/go-zero/core/lang" "github.com/tal-tech/go-zero/core/logx"
"github.com/tal-tech/go-zero/core/syncx" "github.com/tal-tech/go-zero/core/syncx"
"gopkg.in/cheggaaa/pb.v1" "gopkg.in/cheggaaa/pb.v1"
) )
@@ -47,7 +47,7 @@ func main() {
lessWriter = executors.NewLessExecutor(interval * total / 100) lessWriter = executors.NewLessExecutor(interval * total / 100)
fp, err := os.Create("result.csv") fp, err := os.Create("result.csv")
lang.Must(err) logx.Must(err)
defer fp.Close() defer fp.Close()
fmt.Fprintln(fp, "second,maxFlight,flying,agressiveAvgFlying,lazyAvgFlying,bothAvgFlying") fmt.Fprintln(fp, "second,maxFlight,flying,agressiveAvgFlying,lazyAvgFlying,bothAvgFlying")

View File

@@ -11,7 +11,7 @@ import (
"time" "time"
"github.com/tal-tech/go-zero/core/fx" "github.com/tal-tech/go-zero/core/fx"
"github.com/tal-tech/go-zero/core/lang" "github.com/tal-tech/go-zero/core/logx"
) )
var ( var (
@@ -27,7 +27,7 @@ func main() {
flag.Parse() flag.Parse()
fp, err := os.Create("result.csv") fp, err := os.Create("result.csv")
lang.Must(err) logx.Must(err)
defer fp.Close() defer fp.Close()
fmt.Fprintln(fp, "seconds,total,pass,fail,drop") fmt.Fprintln(fp, "seconds,total,pass,fail,drop")

View File

@@ -8,11 +8,11 @@ import (
) )
func main() { func main() {
exeutor := executors.NewBulkExecutor(func(items []interface{}) { executor := executors.NewBulkExecutor(func(items []interface{}) {
fmt.Println(len(items)) fmt.Println(len(items))
}, executors.WithBulkTasks(10)) }, executors.WithBulkTasks(10))
for { for {
exeutor.Add(1) executor.Add(1)
time.Sleep(time.Millisecond * 90) time.Sleep(time.Millisecond * 90)
} }
} }

View File

@@ -2,7 +2,9 @@ package main
import ( import (
"context" "context"
"flag"
"fmt" "fmt"
"log"
"time" "time"
"github.com/tal-tech/go-zero/core/discov" "github.com/tal-tech/go-zero/core/discov"
@@ -10,13 +12,31 @@ import (
"github.com/tal-tech/go-zero/rpcx" "github.com/tal-tech/go-zero/rpcx"
) )
var lb = flag.String("t", "direct", "the load balancer type")
func main() { func main() {
cli := rpcx.MustNewClient(rpcx.RpcClientConf{ flag.Parse()
Etcd: discov.EtcdConf{
Hosts: []string{"localhost:2379"}, var cli rpcx.Client
Key: "rpcx", switch *lb {
}, case "direct":
}) cli = rpcx.MustNewClient(rpcx.RpcClientConf{
Endpoints: []string{
"localhost:3456",
"localhost:3457",
},
})
case "discov":
cli = rpcx.MustNewClient(rpcx.RpcClientConf{
Etcd: discov.EtcdConf{
Hosts: []string{"localhost:2379"},
Key: "rpcx",
},
})
default:
log.Fatal("bad load balancing type")
}
greet := unary.NewGreeterClient(cli.Conn()) greet := unary.NewGreeterClient(cli.Conn())
ticker := time.NewTicker(time.Second) ticker := time.NewTicker(time.Second)
defer ticker.Stop() defer ticker.Stop()

View File

@@ -0,0 +1,21 @@
package main
import (
"fmt"
"github.com/tal-tech/go-zero/core/stringx"
)
func main() {
filter := stringx.NewTrie([]string{
"AV演员",
"苍井空",
"AV",
"日本AV女优",
"AV演员色情",
}, stringx.WithMask('?'))
safe, keywords, found := filter.Filter("日本AV演员兼电视、电影演员。苍井空AV女优是xx出道, 日本AV女优们最精彩的表演是AV演员色情表演")
fmt.Println(safe)
fmt.Println(keywords)
fmt.Println(found)
}

View File

@@ -0,0 +1,15 @@
package main
import (
"fmt"
"github.com/tal-tech/go-zero/core/stringx"
)
func main() {
replacer := stringx.NewReplacer(map[string]string{
"PHP": "PPT",
"世界上": "吹牛",
})
fmt.Println(replacer.Replace("PHP是世界上最好的语言"))
}

3
go.mod
View File

@@ -13,6 +13,7 @@ require (
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8
github.com/go-redis/redis v6.15.7+incompatible github.com/go-redis/redis v6.15.7+incompatible
github.com/go-sql-driver/mysql v1.5.0 github.com/go-sql-driver/mysql v1.5.0
github.com/go-xorm/builder v0.3.4
github.com/gogo/protobuf v1.3.1 // indirect github.com/gogo/protobuf v1.3.1 // indirect
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
github.com/golang/mock v1.4.3 github.com/golang/mock v1.4.3
@@ -22,6 +23,7 @@ require (
github.com/google/uuid v1.1.1 github.com/google/uuid v1.1.1
github.com/gorilla/websocket v1.4.2 // indirect github.com/gorilla/websocket v1.4.2 // indirect
github.com/grpc-ecosystem/grpc-gateway v1.14.3 // indirect github.com/grpc-ecosystem/grpc-gateway v1.14.3 // indirect
github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334
github.com/justinas/alice v1.2.0 github.com/justinas/alice v1.2.0
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
@@ -41,6 +43,7 @@ require (
github.com/stretchr/testify v1.5.1 github.com/stretchr/testify v1.5.1
github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6 // indirect github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6 // indirect
github.com/urfave/cli v1.22.4 github.com/urfave/cli v1.22.4
github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb // indirect github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb // indirect
go.etcd.io/etcd v0.0.0-20200402134248-51bdeb39e698 go.etcd.io/etcd v0.0.0-20200402134248-51bdeb39e698
go.uber.org/automaxprocs v1.3.0 go.uber.org/automaxprocs v1.3.0

8
go.sum
View File

@@ -76,6 +76,10 @@ github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gG
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-xorm/builder v0.3.4 h1:FxkeGB4Cggdw3tPwutLCpfjng2jugfkg6LDMrd/KsoY=
github.com/go-xorm/builder v0.3.4/go.mod h1:KxkQkNN1DpPKTedxXyTQcmH+rXfvk4LZ9SOOBoZBAxw=
github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:9wScpmSP5A3Bk8V3XHWUcJmYTh+ZnlHVyc+A4oZYS3Y=
github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:56xuuqnHyryaerycW3BfssRdxQstACi0Epw/yC5E2xM=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
@@ -134,6 +138,8 @@ github.com/grpc-ecosystem/grpc-gateway v1.14.3 h1:OCJlWkOUoTnl0neNGlf4fUm3TmbEtg
github.com/grpc-ecosystem/grpc-gateway v1.14.3/go.mod h1:6CwZWGDSPRJidgKAtJVvND6soZe6fT7iteq8wDPdhb0= github.com/grpc-ecosystem/grpc-gateway v1.14.3/go.mod h1:6CwZWGDSPRJidgKAtJVvND6soZe6fT7iteq8wDPdhb0=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334 h1:VHgatEHNcBFEB7inlalqfNqw65aNkM1lGX2yt3NmbS8=
github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
@@ -266,6 +272,8 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6 h1:YdYsPAZ2pC6Tow/nPZOPQ96O3hm/ToAkGsPLzedXERk= github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6 h1:YdYsPAZ2pC6Tow/nPZOPQ96O3hm/ToAkGsPLzedXERk=
github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg=
github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2 h1:zzrxE1FKn5ryBNl9eKOeqQ58Y/Qpo3Q9QNxKHX5uzzQ=
github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2/go.mod h1:hzfGeIUDq/j97IG+FhNqkowIyEcD88LrW6fyU3K3WqY=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb h1:ZkM6LRnq40pR1Ox0hTHlnpkcOTuFIDQpZ1IN8rKKhX0= github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb h1:ZkM6LRnq40pR1Ox0hTHlnpkcOTuFIDQpZ1IN8rKKhX0=
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ= github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=

View File

@@ -1,6 +1,10 @@
# go-zero项目介绍 # go-zero
![Go](https://github.com/tal-tech/go-zero/workflows/Go/badge.svg?branch=master) [![Go](https://github.com/tal-tech/go-zero/workflows/Go/badge.svg?branch=master)](https://github.com/tal-tech/go-zero/actions)
[![codecov](https://codecov.io/gh/tal-tech/go-zero/branch/master/graph/badge.svg)](https://codecov.io/gh/tal-tech/go-zero)
[![Go Report Card](https://goreportcard.com/badge/github.com/tal-tech/go-zero)](https://goreportcard.com/report/github.com/tal-tech/go-zero)
[![Release](https://img.shields.io/github/v/release/tal-tech/go-zero.svg?style=flat-square)](https://github.com/tal-tech/go-zero)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
## 1. go-zero框架背景 ## 1. go-zero框架背景
@@ -123,7 +127,6 @@ go-zero是一个集成了各种工程实践的包含web和rpc框架有如下
生成的文件结构如下: 生成的文件结构如下:
``` ```
.
├── greet ├── greet
│   ├── etc │   ├── etc
│   │   └── greet-api.json // 配置文件 │   │   └── greet-api.json // 配置文件
@@ -141,26 +144,24 @@ go-zero是一个集成了各种工程实践的包含web和rpc框架有如下
│   └── types │   └── types
│   └── types.go // 请求、返回等类型定义 │   └── types.go // 请求、返回等类型定义
└── greet.api // api描述文件 └── greet.api // api描述文件
8 directories, 9 files
``` ```
生成的代码可以直接运行: 生成的代码可以直接运行:
```shell ```shell
cd greet cd greet
go run greet.go -f etc/greet-api.json go run greet.go -f etc/greet-api.json
``` ```
默认侦听在8888端口可以在配置文件里修改可以通过curl请求 默认侦听在8888端口可以在配置文件里修改可以通过curl请求
```shell ```shell
➜ go-zero git:(master) curl -w "\ncode: %{http_code}\n" http://localhost:8888/greet/from/kevin ➜ go-zero git:(master) curl -w "\ncode: %{http_code}\n" http://localhost:8888/greet/from/kevin
{"code":0} {"code":0}
code: 200 code: 200
``` ```
编写业务代码: 编写业务代码:
* 可以在servicecontext.go里面传递依赖给logic比如mysql, redis等 * 可以在servicecontext.go里面传递依赖给logic比如mysql, redis等
* 在api定义的get/post/put/delete等请求对应的logic里增加业务处理逻辑 * 在api定义的get/post/put/delete等请求对应的logic里增加业务处理逻辑
@@ -172,6 +173,17 @@ go-zero是一个集成了各种工程实践的包含web和rpc框架有如下
... ...
``` ```
### 微信交流群 ## 8. Benchmark
![benchmark](doc/images/benchmark.png)
[测试代码见这里](https://github.com/smallnest/go-web-framework-benchmark)
## 9. 文档
* [goctl使用帮助](doc/goctl.md)
* [关键字替换和敏感词过滤工具](doc/keywords.md)
## 10. 微信交流群
添加我的微信kevwan请注明go-zero我拉进go-zero社区群🤝 添加我的微信kevwan请注明go-zero我拉进go-zero社区群🤝

214
rest/engine.go Normal file
View File

@@ -0,0 +1,214 @@
package rest
import (
"errors"
"fmt"
"net/http"
"time"
"github.com/justinas/alice"
"github.com/tal-tech/go-zero/core/codec"
"github.com/tal-tech/go-zero/core/load"
"github.com/tal-tech/go-zero/core/stat"
"github.com/tal-tech/go-zero/rest/handler"
"github.com/tal-tech/go-zero/rest/httpx"
"github.com/tal-tech/go-zero/rest/internal"
"github.com/tal-tech/go-zero/rest/router"
)
// use 1000m to represent 100%
const topCpuUsage = 1000
var ErrSignatureConfig = errors.New("bad config for Signature")
type engine struct {
conf RestConf
routes []featuredRoutes
unauthorizedCallback handler.UnauthorizedCallback
unsignedCallback handler.UnsignedCallback
middlewares []Middleware
shedder load.Shedder
priorityShedder load.Shedder
}
func newEngine(c RestConf) *engine {
srv := &engine{
conf: c,
}
if c.CpuThreshold > 0 {
srv.shedder = load.NewAdaptiveShedder(load.WithCpuThreshold(c.CpuThreshold))
srv.priorityShedder = load.NewAdaptiveShedder(load.WithCpuThreshold(
(c.CpuThreshold + topCpuUsage) >> 1))
}
return srv
}
func (s *engine) AddRoutes(r featuredRoutes) {
s.routes = append(s.routes, r)
}
func (s *engine) SetUnauthorizedCallback(callback handler.UnauthorizedCallback) {
s.unauthorizedCallback = callback
}
func (s *engine) SetUnsignedCallback(callback handler.UnsignedCallback) {
s.unsignedCallback = callback
}
func (s *engine) Start() error {
return s.StartWithRouter(router.NewPatRouter())
}
func (s *engine) StartWithRouter(router httpx.Router) error {
if err := s.bindRoutes(router); err != nil {
return err
}
return internal.StartHttp(s.conf.Host, s.conf.Port, router)
}
func (s *engine) appendAuthHandler(fr featuredRoutes, chain alice.Chain,
verifier func(alice.Chain) alice.Chain) alice.Chain {
if fr.jwt.enabled {
if len(fr.jwt.prevSecret) == 0 {
chain = chain.Append(handler.Authorize(fr.jwt.secret,
handler.WithUnauthorizedCallback(s.unauthorizedCallback)))
} else {
chain = chain.Append(handler.Authorize(fr.jwt.secret,
handler.WithPrevSecret(fr.jwt.prevSecret),
handler.WithUnauthorizedCallback(s.unauthorizedCallback)))
}
}
return verifier(chain)
}
func (s *engine) bindFeaturedRoutes(router httpx.Router, fr featuredRoutes, metrics *stat.Metrics) error {
verifier, err := s.signatureVerifier(fr.signature)
if err != nil {
return err
}
for _, route := range fr.routes {
if err := s.bindRoute(fr, router, metrics, route, verifier); err != nil {
return err
}
}
return nil
}
func (s *engine) bindRoute(fr featuredRoutes, router httpx.Router, metrics *stat.Metrics,
route Route, verifier func(chain alice.Chain) alice.Chain) error {
chain := alice.New(
handler.TracingHandler,
s.getLogHandler(),
handler.MaxConns(s.conf.MaxConns),
handler.BreakerHandler(route.Method, route.Path, metrics),
handler.SheddingHandler(s.getShedder(fr.priority), metrics),
handler.TimeoutHandler(time.Duration(s.conf.Timeout)*time.Millisecond),
handler.RecoverHandler,
handler.MetricHandler(metrics),
handler.PromMetricHandler(route.Path),
handler.MaxBytesHandler(s.conf.MaxBytes),
handler.GunzipHandler,
)
chain = s.appendAuthHandler(fr, chain, verifier)
for _, middleware := range s.middlewares {
chain = chain.Append(convertMiddleware(middleware))
}
handle := chain.ThenFunc(route.Handler)
return router.Handle(route.Method, route.Path, handle)
}
func (s *engine) bindRoutes(router httpx.Router) error {
metrics := s.createMetrics()
for _, fr := range s.routes {
if err := s.bindFeaturedRoutes(router, fr, metrics); err != nil {
return err
}
}
return nil
}
func (s *engine) createMetrics() *stat.Metrics {
var metrics *stat.Metrics
if len(s.conf.Name) > 0 {
metrics = stat.NewMetrics(s.conf.Name)
} else {
metrics = stat.NewMetrics(fmt.Sprintf("%s:%d", s.conf.Host, s.conf.Port))
}
return metrics
}
func (s *engine) getLogHandler() func(http.Handler) http.Handler {
if s.conf.Verbose {
return handler.DetailedLogHandler
} else {
return handler.LogHandler
}
}
func (s *engine) getShedder(priority bool) load.Shedder {
if priority && s.priorityShedder != nil {
return s.priorityShedder
}
return s.shedder
}
func (s *engine) signatureVerifier(signature signatureSetting) (func(chain alice.Chain) alice.Chain, error) {
if !signature.enabled {
return func(chain alice.Chain) alice.Chain {
return chain
}, nil
}
if len(signature.PrivateKeys) == 0 {
if signature.Strict {
return nil, ErrSignatureConfig
} else {
return func(chain alice.Chain) alice.Chain {
return chain
}, nil
}
}
decrypters := make(map[string]codec.RsaDecrypter)
for _, key := range signature.PrivateKeys {
fingerprint := key.Fingerprint
file := key.KeyFile
decrypter, err := codec.NewRsaDecrypter(file)
if err != nil {
return nil, err
}
decrypters[fingerprint] = decrypter
}
return func(chain alice.Chain) alice.Chain {
if s.unsignedCallback != nil {
return chain.Append(handler.ContentSecurityHandler(
decrypters, signature.Expiry, signature.Strict, s.unsignedCallback))
} else {
return chain.Append(handler.ContentSecurityHandler(
decrypters, signature.Expiry, signature.Strict))
}
}, nil
}
func (s *engine) use(middleware Middleware) {
s.middlewares = append(s.middlewares, middleware)
}
func convertMiddleware(ware Middleware) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(ware(next.ServeHTTP))
}
}

View File

@@ -217,6 +217,7 @@ func TestContentSecurityHandler(t *testing.T) {
signature: test.signature, signature: test.signature,
} }
req, err := buildRequest(setting) req, err := buildRequest(setting)
assert.Nil(t, err)
resp := httptest.NewRecorder() resp := httptest.NewRecorder()
handler.ServeHTTP(resp, req) handler.ServeHTTP(resp, req)
assert.Equal(t, test.statusCode, resp.Code) assert.Equal(t, test.statusCode, resp.Code)
@@ -249,6 +250,7 @@ func TestContentSecurityHandler_UnsignedCallback(t *testing.T) {
signature: "badone", signature: "badone",
} }
req, err := buildRequest(setting) req, err := buildRequest(setting)
assert.Nil(t, err)
resp := httptest.NewRecorder() resp := httptest.NewRecorder()
handler.ServeHTTP(resp, req) handler.ServeHTTP(resp, req)
assert.Equal(t, http.StatusOK, resp.Code) assert.Equal(t, http.StatusOK, resp.Code)
@@ -285,6 +287,7 @@ func TestContentSecurityHandler_UnsignedCallback_WrongTime(t *testing.T) {
fingerprint: fingerprint, fingerprint: fingerprint,
} }
req, err := buildRequest(setting) req, err := buildRequest(setting)
assert.Nil(t, err)
resp := httptest.NewRecorder() resp := httptest.NewRecorder()
handler.ServeHTTP(resp, req) handler.ServeHTTP(resp, req)
assert.Equal(t, http.StatusOK, resp.Code) assert.Equal(t, http.StatusOK, resp.Code)

View File

@@ -1,170 +0,0 @@
package rest
import (
"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"
)
type (
runOptions struct {
start func(*engine) error
}
RunOption func(*Server)
Server struct {
ngin *engine
opts runOptions
}
)
func MustNewServer(c RestConf, opts ...RunOption) *Server {
engine, err := NewServer(c, opts...)
if err != nil {
log.Fatal(err)
}
return engine
}
func NewServer(c RestConf, opts ...RunOption) (*Server, error) {
if err := c.SetUp(); err != nil {
return nil, err
}
server := &Server{
ngin: newEngine(c),
opts: runOptions{
start: func(srv *engine) error {
return srv.Start()
},
},
}
for _, opt := range opts {
opt(server)
}
return server, nil
}
func (e *Server) AddRoutes(rs []Route, opts ...RouteOption) {
r := featuredRoutes{
routes: rs,
}
for _, opt := range opts {
opt(&r)
}
e.ngin.AddRoutes(r)
}
func (e *Server) AddRoute(r Route, opts ...RouteOption) {
e.AddRoutes([]Route{r}, opts...)
}
func (e *Server) Start() {
handleError(e.opts.start(e.ngin))
}
func (e *Server) Stop() {
logx.Close()
}
func (e *Server) Use(middleware Middleware) {
e.ngin.use(middleware)
}
func ToMiddleware(handler func(next http.Handler) http.Handler) Middleware {
return func(handle http.HandlerFunc) http.HandlerFunc {
return handler(handle).ServeHTTP
}
}
func WithJwt(secret string) RouteOption {
return func(r *featuredRoutes) {
validateSecret(secret)
r.jwt.enabled = true
r.jwt.secret = secret
}
}
func WithJwtTransition(secret, prevSecret string) RouteOption {
return func(r *featuredRoutes) {
// why not validate prevSecret, because prevSecret is an already used one,
// even it not meet our requirement, we still need to allow the transition.
validateSecret(secret)
r.jwt.enabled = true
r.jwt.secret = secret
r.jwt.prevSecret = prevSecret
}
}
func WithMiddleware(middleware Middleware, rs ...Route) []Route {
routes := make([]Route, len(rs))
for i := range rs {
route := rs[i]
routes[i] = Route{
Method: route.Method,
Path: route.Path,
Handler: middleware(route.Handler),
}
}
return routes
}
func WithPriority() RouteOption {
return func(r *featuredRoutes) {
r.priority = true
}
}
func WithRouter(router httpx.Router) RunOption {
return func(server *Server) {
server.opts.start = func(srv *engine) error {
return srv.StartWithRouter(router)
}
}
}
func WithSignature(signature SignatureConf) RouteOption {
return func(r *featuredRoutes) {
r.signature.enabled = true
r.signature.Strict = signature.Strict
r.signature.Expiry = signature.Expiry
r.signature.PrivateKeys = signature.PrivateKeys
}
}
func WithUnauthorizedCallback(callback handler.UnauthorizedCallback) RunOption {
return func(engine *Server) {
engine.ngin.SetUnauthorizedCallback(callback)
}
}
func WithUnsignedCallback(callback handler.UnsignedCallback) RunOption {
return func(engine *Server) {
engine.ngin.SetUnsignedCallback(callback)
}
}
func handleError(err error) {
// ErrServerClosed means the server is closed manually
if err == nil || err == http.ErrServerClosed {
return
}
logx.Error(err)
panic(err)
}
func validateSecret(secret string) {
if len(secret) < 8 {
panic("secret's length can't be less than 8")
}
}

View File

@@ -1,214 +1,170 @@
package rest package rest
import ( import (
"errors" "log"
"fmt"
"net/http" "net/http"
"time"
"github.com/justinas/alice" "github.com/tal-tech/go-zero/core/logx"
"github.com/tal-tech/go-zero/core/codec"
"github.com/tal-tech/go-zero/core/load"
"github.com/tal-tech/go-zero/core/stat"
"github.com/tal-tech/go-zero/rest/handler" "github.com/tal-tech/go-zero/rest/handler"
"github.com/tal-tech/go-zero/rest/httpx" "github.com/tal-tech/go-zero/rest/httpx"
"github.com/tal-tech/go-zero/rest/internal"
"github.com/tal-tech/go-zero/rest/router"
) )
// use 1000m to represent 100% type (
const topCpuUsage = 1000 runOptions struct {
start func(*engine) error
var ErrSignatureConfig = errors.New("bad config for Signature")
type engine struct {
conf RestConf
routes []featuredRoutes
unauthorizedCallback handler.UnauthorizedCallback
unsignedCallback handler.UnsignedCallback
middlewares []Middleware
shedder load.Shedder
priorityShedder load.Shedder
}
func newEngine(c RestConf) *engine {
srv := &engine{
conf: c,
}
if c.CpuThreshold > 0 {
srv.shedder = load.NewAdaptiveShedder(load.WithCpuThreshold(c.CpuThreshold))
srv.priorityShedder = load.NewAdaptiveShedder(load.WithCpuThreshold(
(c.CpuThreshold + topCpuUsage) >> 1))
} }
return srv RunOption func(*Server)
}
func (s *engine) AddRoutes(r featuredRoutes) { Server struct {
s.routes = append(s.routes, r) ngin *engine
} opts runOptions
func (s *engine) SetUnauthorizedCallback(callback handler.UnauthorizedCallback) {
s.unauthorizedCallback = callback
}
func (s *engine) SetUnsignedCallback(callback handler.UnsignedCallback) {
s.unsignedCallback = callback
}
func (s *engine) Start() error {
return s.StartWithRouter(router.NewPatRouter())
}
func (s *engine) StartWithRouter(router httpx.Router) error {
if err := s.bindRoutes(router); err != nil {
return err
} }
)
return internal.StartHttp(s.conf.Host, s.conf.Port, router) func MustNewServer(c RestConf, opts ...RunOption) *Server {
} engine, err := NewServer(c, opts...)
func (s *engine) appendAuthHandler(fr featuredRoutes, chain alice.Chain,
verifier func(alice.Chain) alice.Chain) alice.Chain {
if fr.jwt.enabled {
if len(fr.jwt.prevSecret) == 0 {
chain = chain.Append(handler.Authorize(fr.jwt.secret,
handler.WithUnauthorizedCallback(s.unauthorizedCallback)))
} else {
chain = chain.Append(handler.Authorize(fr.jwt.secret,
handler.WithPrevSecret(fr.jwt.prevSecret),
handler.WithUnauthorizedCallback(s.unauthorizedCallback)))
}
}
return verifier(chain)
}
func (s *engine) bindFeaturedRoutes(router httpx.Router, fr featuredRoutes, metrics *stat.Metrics) error {
verifier, err := s.signatureVerifier(fr.signature)
if err != nil { if err != nil {
return err log.Fatal(err)
} }
for _, route := range fr.routes { return engine
if err := s.bindRoute(fr, router, metrics, route, verifier); err != nil { }
return err
func NewServer(c RestConf, opts ...RunOption) (*Server, error) {
if err := c.SetUp(); err != nil {
return nil, err
}
server := &Server{
ngin: newEngine(c),
opts: runOptions{
start: func(srv *engine) error {
return srv.Start()
},
},
}
for _, opt := range opts {
opt(server)
}
return server, nil
}
func (e *Server) AddRoutes(rs []Route, opts ...RouteOption) {
r := featuredRoutes{
routes: rs,
}
for _, opt := range opts {
opt(&r)
}
e.ngin.AddRoutes(r)
}
func (e *Server) AddRoute(r Route, opts ...RouteOption) {
e.AddRoutes([]Route{r}, opts...)
}
func (e *Server) Start() {
handleError(e.opts.start(e.ngin))
}
func (e *Server) Stop() {
logx.Close()
}
func (e *Server) Use(middleware Middleware) {
e.ngin.use(middleware)
}
func ToMiddleware(handler func(next http.Handler) http.Handler) Middleware {
return func(handle http.HandlerFunc) http.HandlerFunc {
return handler(handle).ServeHTTP
}
}
func WithJwt(secret string) RouteOption {
return func(r *featuredRoutes) {
validateSecret(secret)
r.jwt.enabled = true
r.jwt.secret = secret
}
}
func WithJwtTransition(secret, prevSecret string) RouteOption {
return func(r *featuredRoutes) {
// why not validate prevSecret, because prevSecret is an already used one,
// even it not meet our requirement, we still need to allow the transition.
validateSecret(secret)
r.jwt.enabled = true
r.jwt.secret = secret
r.jwt.prevSecret = prevSecret
}
}
func WithMiddleware(middleware Middleware, rs ...Route) []Route {
routes := make([]Route, len(rs))
for i := range rs {
route := rs[i]
routes[i] = Route{
Method: route.Method,
Path: route.Path,
Handler: middleware(route.Handler),
} }
} }
return nil return routes
} }
func (s *engine) bindRoute(fr featuredRoutes, router httpx.Router, metrics *stat.Metrics, func WithPriority() RouteOption {
route Route, verifier func(chain alice.Chain) alice.Chain) error { return func(r *featuredRoutes) {
chain := alice.New( r.priority = true
handler.TracingHandler,
s.getLogHandler(),
handler.MaxConns(s.conf.MaxConns),
handler.BreakerHandler(route.Method, route.Path, metrics),
handler.SheddingHandler(s.getShedder(fr.priority), metrics),
handler.TimeoutHandler(time.Duration(s.conf.Timeout)*time.Millisecond),
handler.RecoverHandler,
handler.MetricHandler(metrics),
handler.PromMetricHandler(route.Path),
handler.MaxBytesHandler(s.conf.MaxBytes),
handler.GunzipHandler,
)
chain = s.appendAuthHandler(fr, chain, verifier)
for _, middleware := range s.middlewares {
chain = chain.Append(convertMiddleware(middleware))
} }
handle := chain.ThenFunc(route.Handler)
return router.Handle(route.Method, route.Path, handle)
} }
func (s *engine) bindRoutes(router httpx.Router) error { func WithRouter(router httpx.Router) RunOption {
metrics := s.createMetrics() return func(server *Server) {
server.opts.start = func(srv *engine) error {
for _, fr := range s.routes { return srv.StartWithRouter(router)
if err := s.bindFeaturedRoutes(router, fr, metrics); err != nil {
return err
} }
} }
return nil
} }
func (s *engine) createMetrics() *stat.Metrics { func WithSignature(signature SignatureConf) RouteOption {
var metrics *stat.Metrics return func(r *featuredRoutes) {
r.signature.enabled = true
if len(s.conf.Name) > 0 { r.signature.Strict = signature.Strict
metrics = stat.NewMetrics(s.conf.Name) r.signature.Expiry = signature.Expiry
} else { r.signature.PrivateKeys = signature.PrivateKeys
metrics = stat.NewMetrics(fmt.Sprintf("%s:%d", s.conf.Host, s.conf.Port))
}
return metrics
}
func (s *engine) getLogHandler() func(http.Handler) http.Handler {
if s.conf.Verbose {
return handler.DetailedLogHandler
} else {
return handler.LogHandler
} }
} }
func (s *engine) getShedder(priority bool) load.Shedder { func WithUnauthorizedCallback(callback handler.UnauthorizedCallback) RunOption {
if priority && s.priorityShedder != nil { return func(engine *Server) {
return s.priorityShedder engine.ngin.SetUnauthorizedCallback(callback)
} }
return s.shedder }
}
func WithUnsignedCallback(callback handler.UnsignedCallback) RunOption {
func (s *engine) signatureVerifier(signature signatureSetting) (func(chain alice.Chain) alice.Chain, error) { return func(engine *Server) {
if !signature.enabled { engine.ngin.SetUnsignedCallback(callback)
return func(chain alice.Chain) alice.Chain { }
return chain }
}, nil
} func handleError(err error) {
// ErrServerClosed means the server is closed manually
if len(signature.PrivateKeys) == 0 { if err == nil || err == http.ErrServerClosed {
if signature.Strict { return
return nil, ErrSignatureConfig }
} else {
return func(chain alice.Chain) alice.Chain { logx.Error(err)
return chain panic(err)
}, nil }
}
} func validateSecret(secret string) {
if len(secret) < 8 {
decrypters := make(map[string]codec.RsaDecrypter) panic("secret's length can't be less than 8")
for _, key := range signature.PrivateKeys {
fingerprint := key.Fingerprint
file := key.KeyFile
decrypter, err := codec.NewRsaDecrypter(file)
if err != nil {
return nil, err
}
decrypters[fingerprint] = decrypter
}
return func(chain alice.Chain) alice.Chain {
if s.unsignedCallback != nil {
return chain.Append(handler.ContentSecurityHandler(
decrypters, signature.Expiry, signature.Strict, s.unsignedCallback))
} else {
return chain.Append(handler.ContentSecurityHandler(
decrypters, signature.Expiry, signature.Strict))
}
}, nil
}
func (s *engine) use(middleware Middleware) {
s.middlewares = append(s.middlewares, middleware)
}
func convertMiddleware(ware Middleware) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(ware(next.ServeHTTP))
} }
} }

View File

@@ -49,10 +49,10 @@ func NewClient(c RpcClientConf, options ...internal.ClientOption) (Client, error
var client Client var client Client
var err error var err error
if len(c.Server) > 0 { if len(c.Endpoints) > 0 {
client, err = internal.NewDirectClient(c.Server, opts...) client, err = internal.NewClient(internal.BuildDirectTarget(c.Endpoints), opts...)
} else if err = c.Etcd.Validate(); err == nil { } else if err = c.Etcd.Validate(); err == nil {
client, err = internal.NewDiscovClient(c.Etcd.Hosts, c.Etcd.Key, opts...) client, err = internal.NewClient(internal.BuildDiscovTarget(c.Etcd.Hosts, c.Etcd.Key), opts...)
} }
if err != nil { if err != nil {
return nil, err return nil, err
@@ -64,7 +64,7 @@ func NewClient(c RpcClientConf, options ...internal.ClientOption) (Client, error
} }
func NewClientNoAuth(c discov.EtcdConf) (Client, error) { func NewClientNoAuth(c discov.EtcdConf) (Client, error) {
client, err := internal.NewDiscovClient(c.Hosts, c.Key) client, err := internal.NewClient(internal.BuildDiscovTarget(c.Hosts, c.Key))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -74,6 +74,10 @@ func NewClientNoAuth(c discov.EtcdConf) (Client, error) {
}, nil }, nil
} }
func NewClientWithTarget(target string, opts ...internal.ClientOption) (Client, error) {
return internal.NewClient(target, opts...)
}
func (rc *RpcClient) Conn() *grpc.ClientConn { func (rc *RpcClient) Conn() *grpc.ClientConn {
return rc.client.Conn() return rc.client.Conn()
} }

View File

@@ -21,19 +21,19 @@ type (
} }
RpcClientConf struct { RpcClientConf struct {
Etcd discov.EtcdConf `json:",optional"` Etcd discov.EtcdConf `json:",optional"`
Server string `json:",optional=!Etcd"` Endpoints []string `json:",optional=!Etcd"`
App string `json:",optional"` App string `json:",optional"`
Token string `json:",optional"` Token string `json:",optional"`
Timeout int64 `json:",optional"` Timeout int64 `json:",optional"`
} }
) )
func NewDirectClientConf(server, app, token string) RpcClientConf { func NewDirectClientConf(endpoints []string, app, token string) RpcClientConf {
return RpcClientConf{ return RpcClientConf{
Server: server, Endpoints: endpoints,
App: app, App: app,
Token: token, Token: token,
} }
} }

View File

@@ -21,7 +21,7 @@ import (
const ( const (
Name = "p2c_ewma" Name = "p2c_ewma"
decayTime = int64(time.Millisecond * 600) decayTime = int64(time.Second * 10) // default value from finagle
forcePick = int64(time.Second) forcePick = int64(time.Second)
initSuccess = 1000 initSuccess = 1000
throttleSuccess = initSuccess / 2 throttleSuccess = initSuccess / 2

View File

@@ -5,12 +5,18 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/tal-tech/go-zero/rpcx/internal/balancer/p2c"
"github.com/tal-tech/go-zero/rpcx/internal/clientinterceptors" "github.com/tal-tech/go-zero/rpcx/internal/clientinterceptors"
"github.com/tal-tech/go-zero/rpcx/internal/resolver"
"google.golang.org/grpc" "google.golang.org/grpc"
) )
const dialTimeout = time.Second * 3 const dialTimeout = time.Second * 3
func init() {
resolver.RegisterResolver()
}
type ( type (
ClientOptions struct { ClientOptions struct {
Timeout time.Duration Timeout time.Duration
@@ -18,8 +24,26 @@ type (
} }
ClientOption func(options *ClientOptions) ClientOption func(options *ClientOptions)
client struct {
conn *grpc.ClientConn
}
) )
func NewClient(target string, opts ...ClientOption) (*client, error) {
opts = append(opts, WithDialOption(grpc.WithBalancerName(p2c.Name)))
conn, err := dial(target, opts...)
if err != nil {
return nil, err
}
return &client{conn: conn}, nil
}
func (c *client) Conn() *grpc.ClientConn {
return c.conn
}
func WithDialOption(opt grpc.DialOption) ClientOption { func WithDialOption(opt grpc.DialOption) ClientOption {
return func(options *ClientOptions) { return func(options *ClientOptions) {
options.DialOptions = append(options.DialOptions, opt) options.DialOptions = append(options.DialOptions, opt)

View File

@@ -1,26 +0,0 @@
package internal
import (
"google.golang.org/grpc"
"google.golang.org/grpc/balancer/roundrobin"
)
type DirectClient struct {
conn *grpc.ClientConn
}
func NewDirectClient(server string, opts ...ClientOption) (*DirectClient, error) {
opts = append(opts, WithDialOption(grpc.WithBalancerName(roundrobin.Name)))
conn, err := dial(server, opts...)
if err != nil {
return nil, err
}
return &DirectClient{
conn: conn,
}, nil
}
func (c *DirectClient) Conn() *grpc.ClientConn {
return c.conn
}

View File

@@ -1,34 +0,0 @@
package internal
import (
"fmt"
"strings"
"github.com/tal-tech/go-zero/rpcx/internal/balancer/p2c"
"github.com/tal-tech/go-zero/rpcx/internal/resolver"
"google.golang.org/grpc"
)
func init() {
resolver.RegisterResolver()
}
type DiscovClient struct {
conn *grpc.ClientConn
}
func NewDiscovClient(endpoints []string, key string, opts ...ClientOption) (*DiscovClient, error) {
opts = append(opts, WithDialOption(grpc.WithBalancerName(p2c.Name)))
target := fmt.Sprintf("%s://%s/%s", resolver.DiscovScheme,
strings.Join(endpoints, resolver.EndpointSep), key)
conn, err := dial(target, opts...)
if err != nil {
return nil, err
}
return &DiscovClient{conn: conn}, nil
}
func (c *DiscovClient) Conn() *grpc.ClientConn {
return c.conn
}

View File

@@ -0,0 +1,30 @@
package resolver
import (
"strings"
"google.golang.org/grpc/resolver"
)
type directBuilder struct{}
func (d *directBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (
resolver.Resolver, error) {
var addrs []resolver.Address
endpoints := strings.Split(target.Endpoint, EndpointSep)
for _, val := range subset(endpoints, subsetSize) {
addrs = append(addrs, resolver.Address{
Addr: val,
})
}
cc.UpdateState(resolver.State{
Addresses: addrs,
})
return &nopResolver{cc: cc}, nil
}
func (d *directBuilder) Scheme() string {
return DirectScheme
}

View File

@@ -0,0 +1,39 @@
package resolver
import (
"strings"
"github.com/tal-tech/go-zero/core/discov"
"google.golang.org/grpc/resolver"
)
type discovBuilder struct{}
func (d *discovBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (
resolver.Resolver, error) {
hosts := strings.Split(target.Authority, EndpointSep)
sub, err := discov.NewSubscriber(hosts, target.Endpoint)
if err != nil {
return nil, err
}
update := func() {
var addrs []resolver.Address
for _, val := range subset(sub.Values(), subsetSize) {
addrs = append(addrs, resolver.Address{
Addr: val,
})
}
cc.UpdateState(resolver.State{
Addresses: addrs,
})
}
sub.AddListener(update)
update()
return &nopResolver{cc: cc}, nil
}
func (d *discovBuilder) Scheme() string {
return DiscovScheme
}

View File

@@ -1,68 +1,30 @@
package resolver package resolver
import ( import "google.golang.org/grpc/resolver"
"fmt"
"strings"
"github.com/tal-tech/go-zero/core/discov"
"google.golang.org/grpc/resolver"
)
const ( const (
DirectScheme = "direct"
DiscovScheme = "discov" DiscovScheme = "discov"
EndpointSep = "," EndpointSep = ","
subsetSize = 32 subsetSize = 32
) )
var builder discovBuilder var (
dirBuilder directBuilder
disBuilder discovBuilder
)
type discovBuilder struct{} func RegisterResolver() {
resolver.Register(&dirBuilder)
func (b *discovBuilder) Scheme() string { resolver.Register(&disBuilder)
return DiscovScheme
} }
func (b *discovBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) ( type nopResolver struct {
resolver.Resolver, error) {
if target.Scheme != DiscovScheme {
return nil, fmt.Errorf("bad scheme: %s", target.Scheme)
}
hosts := strings.Split(target.Authority, EndpointSep)
sub, err := discov.NewSubscriber(hosts, target.Endpoint)
if err != nil {
return nil, err
}
update := func() {
var addrs []resolver.Address
for _, val := range subset(sub.Values(), subsetSize) {
addrs = append(addrs, resolver.Address{
Addr: val,
})
}
cc.UpdateState(resolver.State{
Addresses: addrs,
})
}
sub.AddListener(update)
update()
return &discovResolver{
cc: cc,
}, nil
}
type discovResolver struct {
cc resolver.ClientConn cc resolver.ClientConn
} }
func (r *discovResolver) Close() { func (r *nopResolver) Close() {
} }
func (r *discovResolver) ResolveNow(options resolver.ResolveNowOptions) { func (r *nopResolver) ResolveNow(options resolver.ResolveNowOptions) {
}
func RegisterResolver() {
resolver.Register(&builder)
} }

17
rpcx/internal/target.go Normal file
View File

@@ -0,0 +1,17 @@
package internal
import (
"fmt"
"strings"
"github.com/tal-tech/go-zero/rpcx/internal/resolver"
)
func BuildDirectTarget(endpoints []string) string {
return fmt.Sprintf("%s:///%s", resolver.DirectScheme, strings.Join(endpoints, resolver.EndpointSep))
}
func BuildDiscovTarget(endpoints []string, key string) string {
return fmt.Sprintf("%s://%s/%s", resolver.DiscovScheme,
strings.Join(endpoints, resolver.EndpointSep), key)
}

View File

@@ -38,11 +38,11 @@ func (p *RpcProxy) TakeConn(ctx context.Context) (*grpc.ClientConn, error) {
return client, nil return client, nil
} }
client, err := NewClient(RpcClientConf{ opts := append(p.options, WithDialOption(grpc.WithPerRPCCredentials(&auth.Credential{
Server: p.backend, App: cred.App,
App: cred.App, Token: cred.Token,
Token: cred.Token, })))
}, p.options...) client, err := NewClientWithTarget(p.backend, opts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -4,7 +4,7 @@ import (
"errors" "errors"
"strings" "strings"
"github.com/tal-tech/go-zero/core/lang" "github.com/tal-tech/go-zero/core/logx"
"github.com/tal-tech/go-zero/tools/goctl/api/parser" "github.com/tal-tech/go-zero/tools/goctl/api/parser"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
@@ -32,8 +32,8 @@ func DartCommand(c *cli.Context) error {
dir = dir + "/" dir = dir + "/"
} }
api.Info.Title = strings.Replace(apiFile, ".api", "", -1) api.Info.Title = strings.Replace(apiFile, ".api", "", -1)
lang.Must(genData(dir+"data/", api)) logx.Must(genData(dir+"data/", api))
lang.Must(genApi(dir+"api/", api)) logx.Must(genApi(dir+"api/", api))
lang.Must(genVars(dir + "vars/")) logx.Must(genVars(dir + "vars/"))
return nil return nil
} }

View File

@@ -14,7 +14,7 @@ import (
"time" "time"
"github.com/logrusorgru/aurora" "github.com/logrusorgru/aurora"
"github.com/tal-tech/go-zero/core/lang" "github.com/tal-tech/go-zero/core/logx"
apiformat "github.com/tal-tech/go-zero/tools/goctl/api/format" apiformat "github.com/tal-tech/go-zero/tools/goctl/api/format"
"github.com/tal-tech/go-zero/tools/goctl/api/parser" "github.com/tal-tech/go-zero/tools/goctl/api/parser"
apiutil "github.com/tal-tech/go-zero/tools/goctl/api/util" apiutil "github.com/tal-tech/go-zero/tools/goctl/api/util"
@@ -45,15 +45,15 @@ func GoCommand(c *cli.Context) error {
return err return err
} }
lang.Must(util.MkdirIfNotExist(dir)) logx.Must(util.MkdirIfNotExist(dir))
lang.Must(genEtc(dir, api)) logx.Must(genEtc(dir, api))
lang.Must(genConfig(dir)) logx.Must(genConfig(dir))
lang.Must(genMain(dir, api)) logx.Must(genMain(dir, api))
lang.Must(genServiceContext(dir, api)) logx.Must(genServiceContext(dir, api))
lang.Must(genTypes(dir, api)) logx.Must(genTypes(dir, api))
lang.Must(genHandlers(dir, api)) logx.Must(genHandlers(dir, api))
lang.Must(genRoutes(dir, api)) logx.Must(genRoutes(dir, api))
lang.Must(genLogic(dir, api)) logx.Must(genLogic(dir, api))
// it does not work // it does not work
format(dir) format(dir)
createGoModFileIfNeed(dir) createGoModFileIfNeed(dir)
@@ -148,7 +148,6 @@ func createGoModFileIfNeed(dir string) {
} }
tempPath = filepath.Dir(tempPath) tempPath = filepath.Dir(tempPath)
if util.FileExists(filepath.Join(tempPath, goModeIdentifier)) { if util.FileExists(filepath.Join(tempPath, goModeIdentifier)) {
tempPath = filepath.Dir(tempPath)
hasGoMod = true hasGoMod = true
break break
} }

View File

@@ -72,7 +72,7 @@ func genHandler(dir string, group spec.Group, route spec.Route) error {
req = "" req = ""
} }
var logicResponse string var logicResponse string
var writeResponse = "nil, nil" var writeResponse string
var respWriter = `httpx.WriteJson(w, http.StatusOK, resp)` var respWriter = `httpx.WriteJson(w, http.StatusOK, resp)`
if len(route.ResponseType.Name) > 0 { if len(route.ResponseType.Name) > 0 {
logicResponse = "resp, err :=" logicResponse = "resp, err :="

View File

@@ -43,6 +43,7 @@ var mapping = map[string]string{
"head": "http.MethodHead", "head": "http.MethodHead",
"post": "http.MethodPost", "post": "http.MethodPost",
"put": "http.MethodPut", "put": "http.MethodPut",
"patch": "http.MethodPatch",
} }
type ( type (

View File

@@ -6,7 +6,7 @@ import (
"strings" "strings"
"github.com/logrusorgru/aurora" "github.com/logrusorgru/aurora"
"github.com/tal-tech/go-zero/core/lang" "github.com/tal-tech/go-zero/core/logx"
"github.com/tal-tech/go-zero/tools/goctl/api/parser" "github.com/tal-tech/go-zero/tools/goctl/api/parser"
"github.com/tal-tech/go-zero/tools/goctl/util" "github.com/tal-tech/go-zero/tools/goctl/util"
"github.com/urfave/cli" "github.com/urfave/cli"
@@ -36,9 +36,9 @@ func JavaCommand(c *cli.Context) error {
packetName = packetName[:len(packetName)-4] packetName = packetName[:len(packetName)-4]
} }
lang.Must(util.MkdirIfNotExist(dir)) logx.Must(util.MkdirIfNotExist(dir))
lang.Must(genPacket(dir, packetName, api)) logx.Must(genPacket(dir, packetName, api))
lang.Must(genComponents(dir, packetName, api)) logx.Must(genComponents(dir, packetName, api))
fmt.Println(aurora.Green("Done.")) fmt.Println(aurora.Green("Done."))
return nil return nil

View File

@@ -0,0 +1,42 @@
package ktgen
import (
"errors"
"github.com/tal-tech/go-zero/tools/goctl/api/parser"
"github.com/urfave/cli"
)
func KtCommand(c *cli.Context) error {
apiFile := c.String("api")
if apiFile == "" {
return errors.New("missing -api")
}
dir := c.String("dir")
if dir == "" {
return errors.New("missing -dir")
}
pkg := c.String("pkg")
if pkg == "" {
return errors.New("missing -pkg")
}
p, e := parser.NewParser(apiFile)
if e != nil {
return e
}
api, e := p.Parse()
if e != nil {
return e
}
e = genBase(dir, pkg, api)
if e != nil {
return e
}
e = genApi(dir, pkg, api)
if e != nil {
return e
}
return nil
}

View File

@@ -0,0 +1,77 @@
package ktgen
import (
"log"
"strings"
"text/template"
"github.com/iancoleman/strcase"
"github.com/tal-tech/go-zero/tools/goctl/api/util"
)
var funcsMap = template.FuncMap{
"lowCamelCase": lowCamelCase,
"routeToFuncName": routeToFuncName,
"parseType": parseType,
"add": add,
"upperCase": upperCase,
}
func lowCamelCase(s string) string {
if len(s) < 1 {
return ""
}
s = util.ToCamelCase(util.ToSnakeCase(s))
return util.ToLower(s[:1]) + s[1:]
}
func routeToFuncName(method, path string) string {
if !strings.HasPrefix(path, "/") {
path = "/" + path
}
path = strings.ReplaceAll(path, "/", "_")
path = strings.ReplaceAll(path, "-", "_")
path = strings.ReplaceAll(path, ":", "With_")
return strings.ToLower(method) + strcase.ToCamel(path)
}
func parseType(t string) string {
t = strings.Replace(t, "*", "", -1)
if strings.HasPrefix(t, "[]") {
return "List<" + parseType(t[2:]) + ">"
}
if strings.HasPrefix(t, "map") {
tys, e := util.DecomposeType(t)
if e != nil {
log.Fatal(e)
}
if len(tys) != 2 {
log.Fatal("Map type number !=2")
}
return "Map<String," + parseType(tys[1]) + ">"
}
switch t {
case "string":
return "String"
case "int", "int32", "int64":
return "Int"
case "float", "float32", "float64":
return "Double"
case "bool":
return "Boolean"
default:
return t
}
}
func add(a, i int) int {
return a + i
}
func upperCase(s string) string {
return strings.ToUpper(s)
}

View File

@@ -0,0 +1,150 @@
package ktgen
import (
"fmt"
"os"
"path/filepath"
"text/template"
"github.com/iancoleman/strcase"
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
)
const (
apiBaseTemplate = `package {{.}}
import com.google.gson.Gson
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.BufferedReader
import java.io.InputStreamReader
import java.io.OutputStreamWriter
import java.net.HttpURLConnection
import java.net.URL
const val SERVER = "http://localhost:8080"
suspend fun apiRequest(
method: String,
uri: String,
body: Any = "",
onOk: ((String) -> Unit)? = null,
onFail: ((String) -> Unit)? = null,
eventually: (() -> Unit)? = null
) = withContext(Dispatchers.IO) {
val url = URL(SERVER + uri)
with(url.openConnection() as HttpURLConnection) {
connectTimeout = 3000
requestMethod = method
doInput = true
if (method == "POST" || method == "PUT" || method == "PATCH") {
setRequestProperty("Content-Type", "application/json")
doOutput = true
val data = when (body) {
is String -> {
body
}
else -> {
Gson().toJson(body)
}
}
val wr = OutputStreamWriter(outputStream)
wr.write(data)
wr.flush()
}
try {
if (responseCode >= 400) {
BufferedReader(InputStreamReader(errorStream)).use {
val response = it.readText()
onFail?.invoke(response)
}
return@with
}
//response
BufferedReader(InputStreamReader(inputStream)).use {
val response = it.readText()
onOk?.invoke(response)
}
} catch (e: Exception) {
e.message?.let { onFail?.invoke(it) }
}
}
eventually?.invoke()
}
`
apiTemplate = `package {{with .Info}}{{.Desc}}{{end}}
import com.google.gson.Gson
object {{with .Info}}{{.Title}}{{end}}{
{{range .Types}}
data class {{.Name}}({{$length := (len .Members)}}{{range $i,$item := .Members}}
val {{with $item}}{{lowCamelCase .Name}}: {{parseType .Type}}{{end}}{{if ne $i (add $length -1)}},{{end}}{{end}}
){{end}}
{{with .Service}}
{{range .Routes}}suspend fun {{routeToFuncName .Method .Path}}({{with .RequestType}}{{if ne .Name ""}}
req:{{.Name}},{{end}}{{end}}
onOk: (({{with .ResponseType}}{{.Name}}{{end}}) -> Unit)? = null,
onFail: ((String) -> Unit)? = null,
eventually: (() -> Unit)? = null
){
apiRequest("{{upperCase .Method}}","{{.Path}}",{{with .RequestType}}{{if ne .Name ""}}body=req,{{end}}{{end}} onOk = { {{with .ResponseType}}
onOk?.invoke({{if ne .Name ""}}Gson().fromJson(it,{{.Name}}::class.java){{end}}){{end}}
}, onFail = onFail, eventually =eventually)
}
{{end}}{{end}}
}`
)
func genBase(dir, pkg string, api *spec.ApiSpec) error {
e := os.MkdirAll(dir, 0755)
if e != nil {
return e
}
path := filepath.Join(dir, "BaseApi.kt")
if _, e := os.Stat(path); e == nil {
fmt.Println("BaseApi.kt already exists, skipped it.")
return nil
}
file, e := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
if e != nil {
return e
}
defer file.Close()
t, e := template.New("n").Parse(apiBaseTemplate)
if e != nil {
return e
}
e = t.Execute(file, pkg)
if e != nil {
return e
}
return nil
}
func genApi(dir, pkg string, api *spec.ApiSpec) error {
name := strcase.ToCamel(api.Info.Title + "Api")
path := filepath.Join(dir, name+".kt")
api.Info.Title = name
api.Info.Desc = pkg
e := os.MkdirAll(dir, 0755)
if e != nil {
return e
}
file, e := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644)
if e != nil {
return e
}
defer file.Close()
t, e := template.New("api").Funcs(funcsMap).Parse(apiTemplate)
if e != nil {
return e
}
return t.Execute(file, api)
}

View File

@@ -4,7 +4,7 @@ import (
"fmt" "fmt"
"os" "os"
"github.com/tal-tech/go-zero/core/lang" "github.com/tal-tech/go-zero/core/logx"
"github.com/tal-tech/go-zero/tools/goctl/api/parser" "github.com/tal-tech/go-zero/tools/goctl/api/parser"
) )
@@ -14,8 +14,8 @@ func main() {
} }
p, err := parser.NewParser(os.Args[1]) p, err := parser.NewParser(os.Args[1])
lang.Must(err) logx.Must(err)
api, err := p.Parse() api, err := p.Parse()
lang.Must(err) logx.Must(err)
fmt.Println(api) fmt.Println(api)
} }

View File

@@ -99,8 +99,6 @@ func (s rootState) processToken(token string, annos []spec.Annotation) (state, e
switch token { switch token {
case infoDirective: case infoDirective:
return newInfoState(s.baseState), nil return newInfoState(s.baseState), nil
//case typeDirective:
//return newTypeState(s.baseState, annos), nil
case serviceDirective: case serviceDirective:
return newServiceState(s.baseState, annos), nil return newServiceState(s.baseState, annos), nil
default: default:

View File

@@ -5,7 +5,7 @@ import (
"fmt" "fmt"
"github.com/logrusorgru/aurora" "github.com/logrusorgru/aurora"
"github.com/tal-tech/go-zero/core/lang" "github.com/tal-tech/go-zero/core/logx"
"github.com/tal-tech/go-zero/tools/goctl/api/parser" "github.com/tal-tech/go-zero/tools/goctl/api/parser"
"github.com/tal-tech/go-zero/tools/goctl/util" "github.com/tal-tech/go-zero/tools/goctl/util"
"github.com/urfave/cli" "github.com/urfave/cli"
@@ -34,9 +34,9 @@ func TsCommand(c *cli.Context) error {
return err return err
} }
lang.Must(util.MkdirIfNotExist(dir)) logx.Must(util.MkdirIfNotExist(dir))
lang.Must(genHandler(dir, webApi, caller, api, unwrapApi)) logx.Must(genHandler(dir, webApi, caller, api, unwrapApi))
lang.Must(genComponents(dir, api)) logx.Must(genComponents(dir, api))
fmt.Println(aurora.Green("Done.")) fmt.Println(aurora.Green("Done."))
return nil return nil

View File

@@ -13,6 +13,10 @@ import (
func writeProperty(writer io.Writer, member spec.Member, indent int, prefixForType func(string) string) error { func writeProperty(writer io.Writer, member spec.Member, indent int, prefixForType func(string) string) error {
writeIndent(writer, indent) writeIndent(writer, indent)
ty, err := goTypeToTs(member.Type, prefixForType) ty, err := goTypeToTs(member.Type, prefixForType)
if err != nil {
return err
}
optionalTag := "" optionalTag := ""
if member.IsOptional() || member.IsOmitempty() { if member.IsOptional() || member.IsOmitempty() {
optionalTag = "?" optionalTag = "?"
@@ -21,13 +25,14 @@ func writeProperty(writer io.Writer, member spec.Member, indent int, prefixForTy
if err != nil { if err != nil {
return err return err
} }
comment := member.GetComment() comment := member.GetComment()
if len(comment) > 0 { if len(comment) > 0 {
comment = strings.TrimPrefix(comment, "//") comment = strings.TrimPrefix(comment, "//")
comment = " // " + strings.TrimSpace(comment) comment = " // " + strings.TrimSpace(comment)
} }
if len(member.Docs) > 0 { if len(member.Docs) > 0 {
_, err = fmt.Fprintf(writer, "%s\n", strings.Join(member.Docs, "")) fmt.Fprintf(writer, "%s\n", strings.Join(member.Docs, ""))
writeIndent(writer, 1) writeIndent(writer, 1)
} }
_, err = fmt.Fprintf(writer, "%s%s: %s%s\n", name, optionalTag, ty, comment) _, err = fmt.Fprintf(writer, "%s%s: %s%s\n", name, optionalTag, ty, comment)

View File

@@ -8,13 +8,13 @@ import (
"path" "path"
"strings" "strings"
"github.com/tal-tech/go-zero/core/lang" "github.com/tal-tech/go-zero/core/logx"
"github.com/tal-tech/go-zero/tools/goctl/api/spec" "github.com/tal-tech/go-zero/tools/goctl/api/spec"
"github.com/tal-tech/go-zero/tools/goctl/util" "github.com/tal-tech/go-zero/tools/goctl/util"
) )
func MaybeCreateFile(dir, subdir, file string) (fp *os.File, created bool, err error) { func MaybeCreateFile(dir, subdir, file string) (fp *os.File, created bool, err error) {
lang.Must(util.MkdirIfNotExist(path.Join(dir, subdir))) logx.Must(util.MkdirIfNotExist(path.Join(dir, subdir)))
fpath := path.Join(dir, subdir, file) fpath := path.Join(dir, subdir, file)
if util.FileExists(fpath) { if util.FileExists(fpath) {
fmt.Printf("%s exists, ignored generation\n", fpath) fmt.Printf("%s exists, ignored generation\n", fpath)
@@ -28,6 +28,9 @@ func MaybeCreateFile(dir, subdir, file string) (fp *os.File, created bool, err e
func ClearAndOpenFile(fpath string) (*os.File, error) { func ClearAndOpenFile(fpath string) (*os.File, error) {
f, err := os.OpenFile(fpath, os.O_WRONLY|os.O_TRUNC, 0600) f, err := os.OpenFile(fpath, os.O_WRONLY|os.O_TRUNC, 0600)
if err != nil {
return nil, err
}
_, err = f.WriteString("") _, err = f.WriteString("")
if err != nil { if err != nil {

View File

@@ -8,12 +8,10 @@ import (
) )
var feature = ` var feature = `
1、新增对rpc错误转换处理 1、增加goctl model支持
1.1、目前暂时仅处理not found 和 unknown错误
2、增加feature命令支持详细使用请通过命令[goctl -feature]查看
` `
func Feature(c *cli.Context) error { func Feature(_ *cli.Context) error {
fmt.Println(aurora.Blue("\nFEATURE:")) fmt.Println(aurora.Blue("\nFEATURE:"))
fmt.Println(aurora.Blue(feature)) fmt.Println(aurora.Blue(feature))
return nil return nil

View File

@@ -11,11 +11,13 @@ import (
"github.com/tal-tech/go-zero/tools/goctl/api/format" "github.com/tal-tech/go-zero/tools/goctl/api/format"
"github.com/tal-tech/go-zero/tools/goctl/api/gogen" "github.com/tal-tech/go-zero/tools/goctl/api/gogen"
"github.com/tal-tech/go-zero/tools/goctl/api/javagen" "github.com/tal-tech/go-zero/tools/goctl/api/javagen"
"github.com/tal-tech/go-zero/tools/goctl/api/ktgen"
"github.com/tal-tech/go-zero/tools/goctl/api/tsgen" "github.com/tal-tech/go-zero/tools/goctl/api/tsgen"
"github.com/tal-tech/go-zero/tools/goctl/api/validate" "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/configgen"
"github.com/tal-tech/go-zero/tools/goctl/docker" "github.com/tal-tech/go-zero/tools/goctl/docker"
"github.com/tal-tech/go-zero/tools/goctl/feature" "github.com/tal-tech/go-zero/tools/goctl/feature"
"github.com/tal-tech/go-zero/tools/goctl/model/sql/command"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
@@ -150,6 +152,25 @@ var (
}, },
Action: dartgen.DartCommand, Action: dartgen.DartCommand,
}, },
{
Name: "kt",
Usage: "generate kotlin code for provided api file",
Flags: []cli.Flag{
cli.StringFlag{
Name: "dir",
Usage: "the target directory",
},
cli.StringFlag{
Name: "api",
Usage: "the api file",
},
cli.StringFlag{
Name: "pkg",
Usage: "define package name for kotlin file",
},
},
Action: ktgen.KtCommand,
},
}, },
}, },
{ {
@@ -169,17 +190,26 @@ var (
}, },
{ {
Name: "model", Name: "model",
Usage: "generate sql model", Usage: "generate model code",
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.StringFlag{ cli.StringFlag{
Name: "config, c", Name: "src, s",
Usage: "the file that contains main function", Usage: "the file path of the ddl source file",
}, },
cli.StringFlag{ cli.StringFlag{
Name: "dir, d", Name: "dir, d",
Usage: "the target dir", Usage: "the target dir",
}, },
cli.BoolFlag{
Name: "cache, c",
Usage: "generate code with cache [optional]",
},
cli.BoolFlag{
Name: "idea",
Usage: "for idea plugin [optional]",
},
}, },
Action: command.Mysql,
}, },
{ {
Name: "config", Name: "config",

View File

@@ -3,9 +3,7 @@
## goctl用途 ## goctl用途
* 定义api请求 * 定义api请求
* 根据定义的api自动生成golang(后端), java(iOS & Android), typescript(web & 晓程序)dart(flutter) * 根据定义的api自动生成golang(后端), java(iOS & Android), typescript(web & 晓程序)dart(flutter)
* 生成MySQL CURD (https://goctl.xiaoheiban.cn) * 生成MySQL CURD 详情见[goctl model模块](https://github.com/tal-tech/go-zero/tools/goctl/model)
* 生成MongoDB CURD (https://goctl.xiaoheiban.cn)
## goctl使用说明 ## goctl使用说明
#### goctl参数说明 #### goctl参数说明
@@ -188,79 +186,5 @@ service user-api {
#### 根据定义好的api文件生成Dart代码 #### 根据定义好的api文件生成Dart代码
`goctl api dart -api user/user.api -dir ./src` `goctl api dart -api user/user.api -dir ./src`
## 根据定义好的简单go文件生成mongo代码文件(仅限golang使用)
`goctl model mongo -src {{yourDir}}/xiao/service/xhb/user/model/usermodel.go -cache yes`
-src需要提供简单的usermodel.go文件里面只需要提供一个结构体即可
-cache 控制是否需要缓存 yes=需要 no=不需要
src 示例代码如下
```
package model
type User struct {
Name string `o:"find,get,set" c:"姓名"`
Age int `o:"find,get,set" c:"年纪"`
School string `c:"学校"`
}
```
结构体中不需要提供Id,CreateTime,UpdateTime三个字段会自动生成
结构体中每个tag有两个可选标签 c 和 o
c是改字段的注释
o是改字段需要生产的操作函数 可以取得get,find,set 分别表示生成返回单个对象的查询方法,返回多个对象的查询方法,设置该字段方法
生成的目标文件会覆盖该简单go文件
## goctl rpc生成
命令 `goctl rpc proto -proto ${proto} -service ${serviceName} -project ${projectName} -dir ${directory} -shared ${shared}`
如: `goctl rpc proto -proto test.proto -service test -project xjy -dir .`
参数说明:
- ${proto}: proto文件
- ${serviceName}: rpc服务名称
- ${projectName}: 所属项目如xjy,xhb,crm,hera具体查看help主要为了根据不同项目服务往redis注册key可选
- ${directory}: 输出目录
- ${shared}: shared文件生成目录可选默认为${pwd}/shared
生成目录结构示例:
``` go
.
├── shared [示例目录,可自己指定,强制覆盖更新]
│   └── contentservicemodel.go
├── test
│   ├── etc
│   │   └── test.json
│   ├── internal
│   │   ├── config
│   │   │   └── config.go
│   │   ├── handler [强制覆盖更新]
│   │   │   ├── changeavatarhandler.go
│   │   │   ├── changebirthdayhandler.go
│   │   │   ├── changenamehandler.go
│   │   │   ├── changepasswordhandler.go
│   │   │   ├── changeuserinfohandler.go
│   │   │   ├── getuserinfohandler.go
│   │   │   ├── loginhandler.go
│   │   │   ├── logouthandler.go
│   │   │   └── testhandler.go
│   │   ├── logic
│   │   │   ├── changeavatarlogic.go
│   │   │   ├── changebirthdaylogic.go
│   │   │   ├── changenamelogic.go
│   │   │   ├── changepasswordlogic.go
│   │   │   ├── changeuserinfologic.go
│   │   │   ├── getuserinfologic.go
│   │   │   ├── loginlogic.go
│   │   │   └── logoutlogic.go
│   │   └── svc
│   │   └── servicecontext.go
│   ├── pb
│   │   └── test.pb.go
│   └── test.go [强制覆盖更新]
└── test.proto
```
- 注意 目前rpc目录生成的proto文件暂不支持import外部proto文件
* 如有不理解的地方随时问Kim/Kevin * 如有不理解的地方随时问Kim/Kevin

View File

@@ -1,430 +1,253 @@
<div style="text-align: center;"><h1>Sql生成工具说明文档</h1></div> # Goctl Model
<h2>前言</h2> goctl model 为go-zero下的工具模块中的组件之一目前支持识别mysql ddl进行model层代码生成通过命令行或者idea插件即将支持可以有选择地生成带redis cache或者不带redis cache的代码逻辑。
在当前Sql代码生成工具是基于sqlc生成的逻辑。
<h2>关键字</h2> # 快速开始
+ 查询类型(前暂不支持同一字段多种类型混合生成如按照campus_id查询单结果又查询All或者Limit) ```
- 单结果查询 $ goctl model -src ./sql/user.sql -dir ./model -c true
- FindOne(主键特有) ```
- FindOneByXxx
- 多结果查询
- FindAllByXxx
- FindLimitByXxx
- withCache
- withoutCache
<h2>准备工作</h2> 详情用法请参考[example](https://github.com/tal-tech/go-zero/tools/goctl/model/sql/example)
- table 执行上述命令后即可快速生成CURD代码。
``` ```
CREATE TABLE `user_info` ( model
`id` bigint(20) NOT NULL COMMENT '主键', │   ├── error.go
`campus_id` bigint(20) DEFAULT NULL COMMENT '整校id', │   └── usermodel.go
`name` varchar(255) DEFAULT NULL COMMENT '用户姓名', ```
`id_number` varchar(255) DEFAULT NULL COMMENT '身份证',
`age` int(10) DEFAULT NULL COMMENT '年龄',
`gender` tinyint(1) DEFAULT NULL COMMENT '性别0-男1-女2-不限',
`mobile` varchar(20) DEFAULT NULL COMMENT '手机号',
`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
```
<h2>imports生成</h2> > 注意这里的目录结构中有usercoursemodel.go目录在example中我为了体现带cache与不带cache代码的区别因此将sql文件分别使用了独立的sql文件(user.sql&course.sql)在实际项目开发中你可以将ddl建表语句放在一个sql文件中`goctl model`会自动解析并分割最终按照每个ddl建表语句为单位生成独立的go文件。
imports代码生成对应model中包的引入管理仅使用于晓黑板项目中非相对路径动态生成目前受`withCache`参数的影响,除此之外其实为固定代码。
- withCache * 生成代码示例
``` ``` go
package model
import ( import (
"database/sql""fmt" "database/sql"
"strings" "fmt"
"time" "strings"
"time"
"github.com/tal-tech/go-zero/core/stores/sqlc"
"github.com/tal-tech/go-zero/core/stores/sqlx" "github.com/tal-tech/go-zero/core/stores/cache"
"github.com/tal-tech/go-zero/core/stringx" "github.com/tal-tech/go-zero/core/stores/sqlc"
"xiao/service/shared/builderx" "github.com/tal-tech/go-zero/core/stores/sqlx"
"github.com/tal-tech/go-zero/core/stringx"
"github.com/tal-tech/go-zero/tools/goctl/model/sql/builderx"
) )
```
var (
userFieldNames = builderx.FieldNames(&User{})
userRows = strings.Join(userFieldNames, ",")
userRowsExpectAutoSet = strings.Join(stringx.Remove(userFieldNames, "id", "create_time", "update_time"), ",")
userRowsWithPlaceHolder = strings.Join(stringx.Remove(userFieldNames, "id", "create_time", "update_time"), "=?,") + "=?"
cacheUserMobilePrefix = "cache#User#mobile#"
cacheUserIdPrefix = "cache#User#id#"
cacheUserNamePrefix = "cache#User#name#"
)
type (
UserModel struct {
sqlc.CachedConn
table string
}
User struct {
Id int64 `db:"id"`
Name string `db:"name"` // 用户名称
Password string `db:"password"` // 用户密码
Mobile string `db:"mobile"` // 手机号
Gender string `db:"gender"` // 男|女|未公开
Nickname string `db:"nickname"` // 用户昵称
CreateTime time.Time `db:"create_time"`
UpdateTime time.Time `db:"update_time"`
}
)
func NewUserModel(conn sqlx.SqlConn, c cache.CacheConf, table string) *UserModel {
return &UserModel{
CachedConn: sqlc.NewConn(conn, c),
table: table,
}
}
func (m *UserModel) Insert(data User) (sql.Result, error) {
query := `insert into ` + m.table + `(` + userRowsExpectAutoSet + `) value (?, ?, ?, ?, ?)`
return m.ExecNoCache(query, data.Name, data.Password, data.Mobile, data.Gender, data.Nickname)
}
func (m *UserModel) FindOne(id int64) (*User, error) {
userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, id)
var resp User
err := m.QueryRow(&resp, userIdKey, func(conn sqlx.SqlConn, v interface{}) error {
query := `select ` + userRows + ` from ` + m.table + ` where id = ? limit 1`
return conn.QueryRow(v, query, id)
})
switch err {
case nil:
return &resp, nil
case sqlc.ErrNotFound:
return nil, ErrNotFound
default:
return nil, err
}
}
func (m *UserModel) FindOneByName(name string) (*User, error) {
userNameKey := fmt.Sprintf("%s%v", cacheUserNamePrefix, name)
var resp User
err := m.QueryRowIndex(&resp, userNameKey, func(primary interface{}) string {
return fmt.Sprintf("%s%v", cacheUserIdPrefix, primary)
}, func(conn sqlx.SqlConn, v interface{}) (i interface{}, e error) {
query := `select ` + userRows + ` from ` + m.table + ` where name = ? limit 1`
if err := conn.QueryRow(&resp, query, name); err != nil {
return nil, err
}
return resp.Id, nil
}, func(conn sqlx.SqlConn, v, primary interface{}) error {
query := `select ` + userRows + ` from ` + m.table + ` where id = ? limit 1`
return conn.QueryRow(v, query, primary)
})
switch err {
case nil:
return &resp, nil
case sqlc.ErrNotFound:
return nil, ErrNotFound
default:
return nil, err
}
}
func (m *UserModel) FindOneByMobile(mobile string) (*User, error) {
userMobileKey := fmt.Sprintf("%s%v", cacheUserMobilePrefix, mobile)
var resp User
err := m.QueryRowIndex(&resp, userMobileKey, func(primary interface{}) string {
return fmt.Sprintf("%s%v", cacheUserIdPrefix, primary)
}, func(conn sqlx.SqlConn, v interface{}) (i interface{}, e error) {
query := `select ` + userRows + ` from ` + m.table + ` where mobile = ? limit 1`
if err := conn.QueryRow(&resp, query, mobile); err != nil {
return nil, err
}
return resp.Id, nil
}, func(conn sqlx.SqlConn, v, primary interface{}) error {
query := `select ` + userRows + ` from ` + m.table + ` where id = ? limit 1`
return conn.QueryRow(v, query, primary)
})
switch err {
case nil:
return &resp, nil
case sqlc.ErrNotFound:
return nil, ErrNotFound
default:
return nil, err
}
}
func (m *UserModel) Update(data User) error {
userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, data.Id)
_, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
query := `update ` + m.table + ` set ` + userRowsWithPlaceHolder + ` where id = ?`
return conn.Exec(query, data.Name, data.Password, data.Mobile, data.Gender, data.Nickname, data.Id)
}, userIdKey)
return err
}
func (m *UserModel) Delete(id int64) error {
data, err := m.FindOne(id)
if err != nil {
return err
}
userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, id)
userNameKey := fmt.Sprintf("%s%v", cacheUserNamePrefix, data.Name)
userMobileKey := fmt.Sprintf("%s%v", cacheUserMobilePrefix, data.Mobile)
_, err = m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
query := `delete from ` + m.table + ` where id = ?`
return conn.Exec(query, id)
}, userIdKey, userNameKey, userMobileKey)
return err
}
```
- withoutCache # 用法
```
$ goctl model -h
```
```
NAME:
goctl model - generate model code
USAGE:
goctl model [command options] [arguments...]
OPTIONS:
--src value, -s value the file path of the ddl source file
--dir value, -d value the target dir
--cache, -c generate code with cache [optional]
--idea for idea plugin [optional]
```
# 生成规则
* 默认规则
``` 我们默认用户在建表时会创建createTime、updateTime字段(忽略大小写、下划线命名风格)且默认值均为`CURRENT_TIMESTAMP`而updateTime支持`ON UPDATE CURRENT_TIMESTAMP`,对于这两个字段生成`insert`、`update`时会被移除,不在赋值范畴内,当然,如果你不需要这两个字段那也无大碍。
import ( * 带缓存模式
"database/sql""fmt"
"strings"
"time"
"github.com/tal-tech/go-zero/core/stores/sqlx"
"github.com/tal-tech/go-zero/core/stringx"
"xiao/service/shared/builderx"
)
```
<h2>vars生成</h2>
vars部分对应model中var声明的包含的代码块,由`table`名和`withCache`来决定其中的代码生成内容,`withCache`决定是否要生成缓存key变量的声明。
- withCache
``` ```
var ( $ goctl model -src {filename} -dir {dir} -cache true
UserInfoFieldNames = builderx.FieldNames(&UserInfo{})
UserInfoRows = strings.Join(UserInfoFieldNames, ",")
UserInfoRowsExpectAutoSet = strings.Join(stringx.Remove(UserInfoFieldNames, "id", "create_time", "update_time"), ",")
UserInfoRowsWithPlaceHolder = strings.Join(stringx.Remove(UserInfoFieldNames, "id", "create_time", "update_time"), "=?,") + "=?"
cacheUserInfoIdPrefix = "cache#userInfo#id#"
cacheUserInfoCampusIdPrefix = "cache#userInfo#campusId#"
cacheUserInfoNamePrefix = "cache#userInfo#name#"
cacheUserInfoMobilePrefix = "cache#userInfo#mobile#"
)
``` ```
目前仅支持redis缓存如果选择带缓存模式即生成的`FindOne(ByXxx)`&`Delete`代码会生成带缓存逻辑的代码目前仅支持单索引字段除全文索引外对于联合索引我们默认认为不需要带缓存且不属于通用型代码因此没有放在代码生成行列如example中user表中的`id`、`name`、`mobile`字段均属于单字段索引。
- withoutCache * 不带缓存模式
```
$ goctl model -src {filename} -dir {dir}
```
or
```
$ goctl model -src {filename} -dir {dir} -cache false
```
生成代码仅基本的CURD结构。
``` # 缓存
var (
UserInfoFieldNames = builderx.FieldNames(&UserInfo{})
UserInfoRows = strings.Join(UserInfoFieldNames, ",")
UserInfoRowsExpectAutoSet = strings.Join(stringx.Remove(UserInfoFieldNames, "id", "create_time", "update_time"), ",")
UserInfoRowsWithPlaceHolder = strings.Join(stringx.Remove(UserInfoFieldNames, "id", "create_time", "update_time"), "=?,") + "=?"
)
```
<h2>types生成</h2> 对于缓存这一块我选择用一问一答的形式进行罗列。我想这样能够更清晰的描述model中缓存的功能。
ypes部分对应model中type声明的包含的代码块,由`table`名和`withCache`来决定其中的代码生成内容,`withCache`决定引入sqlc还是sqlx。 * 缓存会缓存哪些信息?
- withCache 对于主键字段缓存,会缓存整个结构体信息,而对于单索引字段(除全文索引)则缓存主键字段值。
```
type (
UserInfoModel struct {
conn sqlc.CachedConn
table string
}
UserInfo struct { * 数据有更新(`update`)操作会清空缓存吗?
Id int64 `db:"id"` // 主键id
CampusId int64 `db:"campus_id"` // 整校id 但仅清空主键缓存的信息why这里就不做详细赘述了。
Name string `db:"name"` // 用户姓名
IdNumber string `db:"id_number"` // 身份证
Age int64 `db:"age"` // 年龄
Gender int64 `db:"gender"` // 性别0-男1-女2-不限
Mobile string `db:"mobile"` // 手机号
CreateTime time.Time `db:"create_time"` // 创建时间
UpdateTime time.Time `db:"update_time"` // 更新时间
}
)
```
- withoutCache * 为什么不按照单索引字段生成`updateByXxx`和`deleteByXxx`的代码?
```
type ( 理论上是没任何问题但是我们认为对于model层的数据操作均是以整个结构体为单位包括查询我不建议只查询某部分字段不反对否则我们的缓存就没有意义了。
UserInfoModel struct {
conn sqlx.SqlConn
table string
}
UserInfo struct { * 为什么不支持`findPageLimit`、`findAll`这么模式代码生层?
Id int64 `db:"id"` // 主键id
CampusId int64 `db:"campus_id"` // 整校id 目前我认为除了基本的CURD外其他的代码均属于<i>业务型</i>代码,这个我觉得开发人员根据业务需要进行编写更好。
Name string `db:"name"` // 用户姓名
IdNumber string `db:"id_number"` // 身份证
Age int64 `db:"age"` // 年龄
Gender int64 `db:"gender"` // 性别0-男1-女2-不限
Mobile string `db:"mobile"` // 手机号
CreateTime time.Time `db:"create_time"` // 创建时间
UpdateTime time.Time `db:"update_time"` // 更新时间
}
)
```
<h2>New生成</h2>
new生成对应model中struct的New函数受`withCache`影响决定是否要引入cacheRedis
- withCache # QA
```
func NewUserInfoModel(conn sqlx.SqlConn, c cache.CacheConf, table string) *UserInfoModel { * goctl model支持根据数据库连接后选择表生成代码吗
return &UserInfoModel{
CachedConn: sqlc.NewConn(conn, c), 目前暂时不支持,在后面会向这个方向扩展。
table: table,
} * goctl model除了命令行模式支持插件模式吗
}
``` 很快支持idea插件。
- withoutCache
```
func NewUserInfoModel(conn sqlx.SqlConn, table string) *UserInfoModel {
return &UserInfoModel{conn: conn, table: table}
}
```
<h2>FindOne查询生成</h2>
FindOne查询代码生成仅对主键有效。如`user_info`中生成的FindOne如下
- withCache
```
func (m *UserInfoModel) FindOne(id int64) (*UserInfo, error) {
idKey := fmt.Sprintf("%s%v", cacheUserInfoIdPrefix, id)
var resp UserInfo
err := m.QueryRow(&resp, idKey, func(conn sqlx.SqlConn, v interface{}) error {
query := `select ` + userInfoRows + ` from ` + m.table + `where id = ? limit 1`
return conn.QueryRow(v, query, id)
})
switch err {
case nil:
return &resp, nil
case sqlc.ErrNotFound:
return nil, ErrNotFound
default:
return nil, err
}
}
```
- withoutCache
```
func (m *UserInfoModel) FindOne(id int64) (*UserInfo, error) {
query := `select ` + userInfoRows + ` from ` + m.table + `where id = ? limit 1`
var resp UserInfo
err := m.conn.QueryRow(&resp, query, id)
switch err {
case nil:
return &resp, nil
case sqlx.ErrNotFound:
return nil, ErrNotFound
default:
return nil, err
}
```
<h2>FindOneByXxx查询生成</h2>
FindOneByXxx查询生成可以按照单个字段查询、多个字段以AND关系且表达式符号为`=`的查询(下称:组合查询),对除主键之外的字段有效,对于单个字段可以用`withCache`来控制是否需要缓存这里的缓存只缓存主键并不缓存整个struct注意这里有一个隐藏的规则如果单个字段查询需要cache那么主键一定有cache多个字段组成的`组合查询`一律没有缓存处理,<strong><i>且组合查询不能相互嵌套</i></strong>,否则会报`circle query with other fields`错误,下面我们按场景来依次查看对应代码生成后的示例。
>注目前暂不支持除equals之外的条件查询。
+ 单字段查询
以name查询为例
- withCache
```
func (m *UserInfoModel) FindOneByName(name string) (*UserInfo, error) {
nameKey := fmt.Sprintf("%s%v", cacheUserInfoNamePrefix, name)
var id string
err := m.GetCache(key, &id)
if err != nil {
return nil, err
}
if id != "" {
return m.FindOne(id)
}
var resp UserInfo
query := `select ` + userInfoRows + ` from ` + m.table + `where name = ? limit 1`
err = m.QueryRowNoCache(&resp, query, name)
switch err {
case nil:
err = m.SetCache(nameKey, resp.Id)
if err != nil {
logx.Error(err)
}
return &resp, nil
case sqlc.ErrNotFound:
return nil, ErrNotFound
default:
return nil, err
}
}
```
- withoutCache
```
func (m *UserInfoModel) FindOneByName(name string) (*UserInfo, error) {
var resp UserInfo
query := `select ` + userInfoRows + ` from ` + m.table + `where name = ? limit 1`
err = m.conn.QueryRow(&resp, query, name)
switch err {
case nil:
return &resp, nil
case sqlx.ErrNotFound:
return nil, ErrNotFound
default:
return nil, err
}
}
```
- 组合查询
以`campus_id`和`id_number`查询为例。
```
func (m *UserInfoModel) FindOneByCampusIdAndIdNumber(campusId int64,idNumber string) (*UserInfo, error) {
var resp UserInfo
query := `select ` + userInfoRows + ` from ` + m.table + `where campus_id = ? AND id_number = ? limit 1`
err = m.QueryRowNoCache(&resp, query, campusId, idNumber)
// err = m.conn.QueryRows(&resp, query, campusId, idNumber)
switch err {
case nil:
return &resp, nil
case sqlx.ErrNotFound:
return nil, ErrNotFound
default:
return nil, err
}
}
```
<h2>FindAllByXxx生成</h2>
FindAllByXxx查询和FindOneByXxx功能相似只是FindOneByXxx限制了limit等于1而FindAllByXxx是查询所有,以两个例子来说明
- 查询单个字段`name`等于某值的所有数据
```
func (m *UserInfoModel) FindAllByName(name string) ([]*UserInfo, error) {
var resp []*UserInfo
query := `select ` + userInfoRows + ` from ` + m.table + `where name = ?`
err := m.QueryRowsNoCache(&resp, query, name)
// err := m.conn.QueryRows(&resp, query, name)
if err != nil {
return nil, err
}
return resp, nil
}
```
- 查询多个组合字段`campus_id`等于某值且`gender`等于某值的所有数据
```
func (m *UserInfoModel) FindAllByCampusIdAndGender(campusId int64,gender int64) ([]*UserInfo, error) {
var resp []*UserInfo
query := `select ` + userInfoRows + ` from ` + m.table + `where campus_id = ? AND gender = ?`
err := m.QueryRowsNoCache(&resp, query, campusId, gender)
// err := m.conn.QueryRows(&resp, query, campusId, gender)
if err != nil {
return nil, err
}
return resp, nil
}
```
<h2>FindLimitByXxx生成</h2>
FindLimitByXxx查询和FindAllByXxx功能相似只是FindAllByXxx限制了limit除此之外还会生成查询对应Count总数的代码而FindAllByXxx是查询所有数据,以几个例子来说明
- 查询`gender`等于某值的分页数据,按照`create_time`降序
```
func (m *UserInfoModel) FindLimitByGender(gender int64, page, limit int) ([]*UserInfo, error) {
var resp []*UserInfo
query := `select ` + userInfoRows + `from ` + m.table + `where gender = ? order by create_time DESC limit ?,?`
err := m.QueryRowsNoCache(&resp, query, gender, (page-1)*limit, limit)
// err := m.conn.QueryRows(&resp, query, gender, (page-1)*limit, limit)
if err != nil {
return nil, err
}
return resp, nil
}
func (m *UserInfoModel) FindAllCountByGender(gender int64) (int64, error) {
var count int64
query := `select count(1) from ` + m.table + `where gender = ? `
err := m.QueryRowsNoCache(&count, query, gender)
// err := m.conn.QueryRow(&count, query, gender)
if err != nil {
return 0, err
}
return count, nil
}
```
- 查询`gender`等于某值的分页数据,按照`create_time`降序、`update_time`生序排序
```
func (m *UserInfoModel) FindLimitByGender(gender int64, page, limit int) ([]*UserInfo, error) {
var resp []*UserInfo
query := `select ` + userInfoRows + `from ` + m.table + `where gender = ? order by create_time DESC,update_time ASC limit ?,?`
err := m.QueryRowsNoCache(&resp, query, gender, (page-1)*limit, limit)
// err := m.conn.QueryRows(&resp, query, gender, (page-1)*limit, limit)
if err != nil {
return nil, err
}
return resp, nil
}
func (m *UserInfoModel) FindAllCountByGender(gender int64) (int64, error) {
var count int64
query := `select count(1) from ` + m.table + `where gender = ? `
err := m.QueryRowNoCache(&count, query, gender)
// err := m.conn.QueryRow(&count, query, gender)
if err != nil {
return 0, err
}
return count, nil
}
```
- 查询`gender`等于某值且`campus_id`为某值按照`create_time`降序的分页数据
```
func (m *UserInfoModel) FindLimitByGenderAndCampusId(gender int64,campusId int64, page, limit int) ([]*UserInfo, error) {
var resp []*UserInfo
query := `select ` + userInfoRows + `from ` + m.table + `where gender = ? AND campus_id = ? order by create_time DESC limit ?,?`
err := m.QueryRowsNoCache(&resp, query, gender, campusId, (page-1)*limit, limit)
// err := m.conn.QueryRows(&resp, query, gender, campusId, (page-1)*limit, limit)
if err != nil {
return nil, err
}
return resp, nil
}
func (m *UserInfoModel) FindAllCountByGenderAndCampusId(gender int64,campusId int64) (int64, error) {
var count int64
query := `select count(1) from ` + m.table + `where gender = ? AND campus_id = ? `
err := m.QueryRowsNoCache(&count, query, gender, campusId)
// err := m.conn.QueryRow(&count, query, gender, campusId)
if err != nil {
return 0, err
}
return count, nil
}
```
<h2>Delete生成</h2>
Delete代码根据`withCache`的不同可以生成带缓存逻辑代码和不带缓存逻辑代码,<strong><i>Delete代码生成仅按照主键删除</i></strong>。从FindOneByXxx方法描述得知非主键`withCache`了那么主键会强制被cache因此在delete时也会删除主键cache。
- withCache
根据`mobile`查询用户信息
```
func (m *UserInfoModel) Delete(userId int64) error {
userIdKey := fmt.Sprintf("%s%v", cacheUserInfoUserIdPrefix, userId)
mobileKey := fmt.Sprintf("%s%v", cacheUserInfoMobilePrefix, mobile)
_, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
query := `delete from ` + m.table + + `where user_id = ?`
return conn.Exec(query, userId)
}, userIdKey, mobileKey)
return err
}
```
- withoutCache
```
func (m *UserInfoModel) Delete(userId int64) error {
_, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
query := `delete from ` + m.table + + `where user_id = ?`
return conn.Exec(query, userId)
}, )
return err
}
```
<h2>Insert生成</h2>
<h2>Update生成</h2>
<h2>待完善TODO</h2>
- 同一字段多种查询方式代码生成(优先级较高)
- 条件查询
- 范围查询
- ...
<h2>反馈与建议</h2>
- 无

View File

@@ -0,0 +1,97 @@
package builderx
import (
"fmt"
"reflect"
"github.com/go-xorm/builder"
)
const dbTag = "db"
func NewEq(in interface{}) builder.Eq {
return builder.Eq(ToMap(in))
}
func NewGt(in interface{}) builder.Gt {
return builder.Gt(ToMap(in))
}
func ToMap(in interface{}) map[string]interface{} {
out := make(map[string]interface{})
v := reflect.ValueOf(in)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
// we only accept structs
if v.Kind() != reflect.Struct {
panic(fmt.Errorf("ToMap only accepts structs; got %T", v))
}
typ := v.Type()
for i := 0; i < v.NumField(); i++ {
// gets us a StructField
fi := typ.Field(i)
if tagv := fi.Tag.Get(dbTag); tagv != "" {
// set key of map to value in struct field
val := v.Field(i)
zero := reflect.Zero(val.Type()).Interface()
current := val.Interface()
if reflect.DeepEqual(current, zero) {
continue
}
out[tagv] = current
}
}
return out
}
func FieldNames(in interface{}) []string {
out := make([]string, 0)
v := reflect.ValueOf(in)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
// we only accept structs
if v.Kind() != reflect.Struct {
panic(fmt.Errorf("ToMap only accepts structs; got %T", v))
}
typ := v.Type()
for i := 0; i < v.NumField(); i++ {
// gets us a StructField
fi := typ.Field(i)
if tagv := fi.Tag.Get(dbTag); tagv != "" {
out = append(out, tagv)
} else {
out = append(out, fi.Name)
}
}
return out
}
func FieldNamesAlias(in interface{}, alias string) []string {
out := make([]string, 0)
v := reflect.ValueOf(in)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
// we only accept structs
if v.Kind() != reflect.Struct {
panic(fmt.Errorf("ToMap only accepts structs; got %T", v))
}
typ := v.Type()
for i := 0; i < v.NumField(); i++ {
// gets us a StructField
fi := typ.Field(i)
tagName := ""
if tagv := fi.Tag.Get(dbTag); tagv != "" {
tagName = tagv
} else {
tagName = fi.Name
}
if len(alias) > 0 {
tagName = alias + "." + tagName
}
out = append(out, tagName)
}
return out
}

View File

@@ -0,0 +1,101 @@
package builderx
import (
"fmt"
"testing"
"github.com/go-xorm/builder"
"github.com/stretchr/testify/assert"
)
type (
User struct {
// 自增id
Id string `db:"id" json:"id,omitempty"`
// 姓名
UserName string `db:"user_name" json:"userName,omitempty"`
// 1男,2女
Sex int `db:"sex" json:"sex,omitempty"`
Uuid string `db:"uuid" uuid:"uuid,omitempty"`
Age int `db:"age" json:"age"`
}
)
var userFields = FieldNames(User{})
func TestFieldNames(t *testing.T) {
var u User
out := FieldNames(&u)
fmt.Println(out)
actual := []string{"id", "user_name", "sex", "uuid", "age"}
assert.Equal(t, out, actual)
}
func TestNewEq(t *testing.T) {
u := &User{
Id: "123456",
UserName: "wahaha",
}
out := NewEq(u)
fmt.Println(out)
actual := builder.Eq{"id": "123456", "user_name": "wahaha"}
assert.Equal(t, out, actual)
}
// @see https://github.com/go-xorm/builder
func TestBuilderSql(t *testing.T) {
u := &User{
Id: "123123",
}
fields := FieldNames(u)
eq := NewEq(u)
sql, args, err := builder.Select(fields...).From("user").Where(eq).ToSQL()
fmt.Println(sql, args, err)
actualSql := "SELECT id,user_name,sex,uuid,age FROM user WHERE id=?"
actualArgs := []interface{}{"123123"}
assert.Equal(t, sql, actualSql)
assert.Equal(t, args, actualArgs)
}
func TestBuildSqlDefaultValue(t *testing.T) {
var eq = builder.Eq{}
eq["age"] = 0
eq["user_name"] = ""
sql, args, err := builder.Select(userFields...).From("user").Where(eq).ToSQL()
fmt.Println(sql, args, err)
actualSql := "SELECT id,user_name,sex,uuid,age FROM user WHERE age=? AND user_name=?"
actualArgs := []interface{}{0, ""}
assert.Equal(t, sql, actualSql)
assert.Equal(t, args, actualArgs)
}
func TestBuilderSqlIn(t *testing.T) {
u := &User{
Age: 18,
}
gtU := NewGt(u)
in := builder.In("id", []string{"1", "2", "3"})
sql, args, err := builder.Select(userFields...).From("user").Where(in).And(gtU).ToSQL()
fmt.Println(sql, args, err)
actualSql := "SELECT id,user_name,sex,uuid,age FROM user WHERE id IN (?,?,?) AND age>?"
actualArgs := []interface{}{"1", "2", "3", 18}
assert.Equal(t, sql, actualSql)
assert.Equal(t, args, actualArgs)
}
func TestBuildSqlLike(t *testing.T) {
like := builder.Like{"name", "wang"}
sql, args, err := builder.Select(userFields...).From("user").Where(like).ToSQL()
fmt.Println(sql, args, err)
actualSql := "SELECT id,user_name,sex,uuid,age FROM user WHERE name LIKE ?"
actualArgs := []interface{}{"%wang%"}
assert.Equal(t, sql, actualSql)
assert.Equal(t, args, actualArgs)
}

View File

@@ -0,0 +1,26 @@
package command
import (
"github.com/tal-tech/go-zero/tools/goctl/model/sql/gen"
"github.com/tal-tech/go-zero/tools/goctl/util/console"
"github.com/urfave/cli"
)
func Mysql(ctx *cli.Context) error {
src := ctx.String("src")
dir := ctx.String("dir")
cache := ctx.Bool("cache")
idea := ctx.Bool("idea")
var log console.Console
if idea {
log = console.NewIdeaConsole()
} else {
log = console.NewColorConsole()
}
generator := gen.NewDefaultGenerator(src, dir, gen.WithConsoleOption(log))
err := generator.Start(cache)
if err != nil {
log.Error("%v", err)
}
return nil
}

View File

@@ -1,19 +1,25 @@
package model package converter
import (
"fmt"
"strings"
)
var ( var (
CommonMysqlDataTypeMap = map[string]string{ commonMysqlDataTypeMap = map[string]string{
"tinyint": "int", // For consistency, all integer types are converted to int64
"smallint": "int", "tinyint": "int64",
"smallint": "int64",
"mediumint": "int64", "mediumint": "int64",
"int": "int64", "int": "int64",
"integer": "int64", "integer": "int64",
"bigint": "int64", "bigint": "int64",
"float": "float32", "float": "float64",
"double": "float64", "double": "float64",
"decimal": "float64", "decimal": "float64",
"date": "time.Time", "date": "time.Time",
"time": "string", "time": "string",
"year": "int", "year": "int64",
"datetime": "time.Time", "datetime": "time.Time",
"timestamp": "time.Time", "timestamp": "time.Time",
"char": "string", "char": "string",
@@ -29,6 +35,12 @@ var (
} }
) )
const ( func ConvertDataType(dataBaseType string) (goDataType string, err error) {
ModeDirPerm = 0755 tp, ok := commonMysqlDataTypeMap[strings.ToLower(dataBaseType)]
) if !ok {
err = fmt.Errorf("unexpected database type: %s", dataBaseType)
return
}
goDataType = tp
return
}

View File

@@ -0,0 +1,4 @@
#!/bin/bash
# generate usermodel with cache
goctl model -src ./sql/user.sql -dir ./model -c true

View File

@@ -0,0 +1,15 @@
-- 用户表 --
CREATE TABLE `user` (
`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`),
KEY `mobile_index` (`mobile`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

View File

@@ -1,108 +0,0 @@
package gen
import (
"errors"
"fmt"
"sort"
"strings"
"github.com/tal-tech/go-zero/tools/goctl/model/sql/util"
)
func TableConvert(outerTable OuterTable) (*InnerTable, error) {
var table InnerTable
table.CreateNotFound = outerTable.CreateNotFound
tableSnakeCase, tableUpperCamelCase, tableLowerCamelCase := util.FormatField(outerTable.Table)
table.SnakeCase = tableSnakeCase
table.UpperCamelCase = tableUpperCamelCase
table.LowerCamelCase = tableLowerCamelCase
fields := make([]*InnerField, 0)
var primaryField *InnerField
conflict := make(map[string]struct{})
var containsCache bool
for _, field := range outerTable.Fields {
if field.Cache && !containsCache {
containsCache = true
}
fieldSnakeCase, fieldUpperCamelCase, fieldLowerCamelCase := util.FormatField(field.Name)
tag, err := genTag(fieldSnakeCase)
if err != nil {
return nil, err
}
var comment string
if field.Comment != "" {
comment = fmt.Sprintf("// %s", field.Comment)
}
withFields := make([]InnerWithField, 0)
unique := make([]string, 0)
unique = append(unique, fmt.Sprintf("%v", field.QueryType))
unique = append(unique, field.Name)
for _, item := range field.WithFields {
unique = append(unique, item.Name)
withFieldSnakeCase, withFieldUpperCamelCase, withFieldLowerCamelCase := util.FormatField(item.Name)
withFields = append(withFields, InnerWithField{
Case: Case{
SnakeCase: withFieldSnakeCase,
LowerCamelCase: withFieldLowerCamelCase,
UpperCamelCase: withFieldUpperCamelCase,
},
DataType: commonMysqlDataTypeMap[item.DataBaseType],
})
}
sort.Strings(unique)
uniqueKey := strings.Join(unique, "#")
if _, ok := conflict[uniqueKey]; ok {
return nil, ErrCircleQuery
} else {
conflict[uniqueKey] = struct{}{}
}
sortFields := make([]InnerSort, 0)
for _, sortField := range field.OuterSort {
sortSnake, sortUpperCamelCase, sortLowerCamelCase := util.FormatField(sortField.Field)
sortFields = append(sortFields, InnerSort{
Field: Case{
SnakeCase: sortSnake,
LowerCamelCase: sortUpperCamelCase,
UpperCamelCase: sortLowerCamelCase,
},
Asc: sortField.Asc,
})
}
innerField := &InnerField{
IsPrimaryKey: field.IsPrimaryKey,
InnerWithField: InnerWithField{
Case: Case{
SnakeCase: fieldSnakeCase,
LowerCamelCase: fieldLowerCamelCase,
UpperCamelCase: fieldUpperCamelCase,
},
DataType: commonMysqlDataTypeMap[field.DataBaseType],
},
DataBaseType: field.DataBaseType,
Tag: tag,
Comment: comment,
Cache: field.Cache,
QueryType: field.QueryType,
WithFields: withFields,
Sort: sortFields,
}
if field.IsPrimaryKey {
primaryField = innerField
}
fields = append(fields, innerField)
}
if primaryField == nil {
return nil, errors.New("please ensure that primary exists")
}
table.ContainsCache = containsCache
primaryField.Cache = containsCache
table.PrimaryField = primaryField
table.Fields = fields
cacheKey, err := genCacheKeys(&table)
if err != nil {
return nil, err
}
table.CacheKey = cacheKey
return &table, nil
}

View File

@@ -1,51 +1,47 @@
package gen package gen
import ( import (
"bytes"
"strings" "strings"
"text/template"
sqltemplate "github.com/tal-tech/go-zero/tools/goctl/model/sql/template" "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/util/stringx"
"github.com/tal-tech/go-zero/tools/goctl/util/templatex"
) )
func genDelete(table *InnerTable) (string, error) { func genDelete(table Table, withCache bool) (string, error) {
t, err := template.New("delete").Parse(sqltemplate.Delete) keySet := collection.NewSet()
if err != nil { keyVariableSet := collection.NewSet()
return "", nil for fieldName, key := range table.CacheKey {
} if fieldName == table.PrimaryKey.Name.Source() {
deleteBuffer := new(bytes.Buffer) keySet.AddStr(key.KeyExpression)
keys := make([]string, 0)
keyValues := make([]string, 0)
for snake, key := range table.CacheKey {
if snake == table.PrimaryField.SnakeCase {
keys = append(keys, key.Key)
} else { } else {
keys = append(keys, key.DataKey) keySet.AddStr(key.DataKeyExpression)
} }
keyValues = append(keyValues, key.KeyVariable) keyVariableSet.AddStr(key.Variable)
} }
var isOnlyPrimaryKeyCache = true var containsIndexCache = false
for _, item := range table.Fields { for _, item := range table.Fields {
if item.IsPrimaryKey { if item.IsKey {
continue containsIndexCache = true
}
if item.Cache {
isOnlyPrimaryKeyCache = false
break break
} }
} }
err = t.Execute(deleteBuffer, map[string]interface{}{ camel := table.Name.Snake2Camel()
"upperObject": table.UpperCamelCase, output, err := templatex.With("delete").
"containsCache": table.ContainsCache, Parse(template.Delete).
"isNotPrimaryKey": !isOnlyPrimaryKeyCache, Execute(map[string]interface{}{
"lowerPrimaryKey": table.PrimaryField.LowerCamelCase, "upperStartCamelObject": camel,
"dataType": table.PrimaryField.DataType, "withCache": withCache,
"keys": strings.Join(keys, "\r\n"), "containsIndexCache": containsIndexCache,
"snakePrimaryKey": table.PrimaryField.SnakeCase, "lowerStartCamelPrimaryKey": stringx.From(table.PrimaryKey.Name.Snake2Camel()).LowerStart(),
"keyValues": strings.Join(keyValues, ", "), "dataType": table.PrimaryKey.DataType,
}) "keys": strings.Join(keySet.KeysStr(), "\n"),
"originalPrimaryKey": table.PrimaryKey.Name.Source(),
"keyValues": strings.Join(keyVariableSet.KeysStr(), ", "),
})
if err != nil { if err != nil {
return "", err return "", err
} }
return deleteBuffer.String(), nil return output.String(), nil
} }

View File

@@ -1,15 +1,15 @@
package gen package gen
import ( import (
"bytes"
"strings" "strings"
"text/template"
sqltemplate "github.com/tal-tech/go-zero/tools/goctl/model/sql/template" "github.com/tal-tech/go-zero/tools/goctl/model/sql/parser"
"github.com/tal-tech/go-zero/tools/goctl/model/sql/template"
"github.com/tal-tech/go-zero/tools/goctl/util/templatex"
) )
func genFields(fields []*InnerField) (string, error) { func genFields(fields []parser.Field) (string, error) {
list := make([]string, 0) var list []string
for _, field := range fields { for _, field := range fields {
result, err := genField(field) result, err := genField(field)
if err != nil { if err != nil {
@@ -17,23 +17,25 @@ func genFields(fields []*InnerField) (string, error) {
} }
list = append(list, result) list = append(list, result)
} }
return strings.Join(list, "\r\n"), nil return strings.Join(list, "\n"), nil
} }
func genField(field *InnerField) (string, error) { func genField(field parser.Field) (string, error) {
t, err := template.New("types").Parse(sqltemplate.Field) tag, err := genTag(field.Name.Source())
if err != nil {
return "", nil
}
var typeBuffer = new(bytes.Buffer)
err = t.Execute(typeBuffer, map[string]string{
"name": field.UpperCamelCase,
"type": field.DataType,
"tag": field.Tag,
"comment": field.Comment,
})
if err != nil { if err != nil {
return "", err return "", err
} }
return typeBuffer.String(), nil output, err := templatex.With("types").
Parse(template.Field).
Execute(map[string]interface{}{
"name": field.Name.Snake2Camel(),
"type": field.DataType,
"tag": tag,
"hasComment": field.Comment != "",
"comment": field.Comment,
})
if err != nil {
return "", err
}
return output.String(), nil
} }

View File

@@ -1,55 +0,0 @@
package gen
import (
"bytes"
"strings"
"text/template"
sqltemplate "github.com/tal-tech/go-zero/tools/goctl/model/sql/template"
)
func genFindAllByField(table *InnerTable) (string, error) {
t, err := template.New("findAllByField").Parse(sqltemplate.FindAllByField)
if err != nil {
return "", err
}
list := make([]string, 0)
for _, field := range table.Fields {
if field.IsPrimaryKey {
continue
}
if field.QueryType != QueryAll {
continue
}
fineOneByFieldBuffer := new(bytes.Buffer)
upperFields := make([]string, 0)
in := make([]string, 0)
expressionFields := make([]string, 0)
expressionValuesFields := make([]string, 0)
upperFields = append(upperFields, field.UpperCamelCase)
in = append(in, field.LowerCamelCase+" "+field.DataType)
expressionFields = append(expressionFields, field.SnakeCase+" = ?")
expressionValuesFields = append(expressionValuesFields, field.LowerCamelCase)
for _, withField := range field.WithFields {
upperFields = append(upperFields, withField.UpperCamelCase)
in = append(in, withField.LowerCamelCase+" "+withField.DataType)
expressionFields = append(expressionFields, withField.SnakeCase+" = ?")
expressionValuesFields = append(expressionValuesFields, withField.LowerCamelCase)
}
err = t.Execute(fineOneByFieldBuffer, map[string]interface{}{
"in": strings.Join(in, ","),
"upperObject": table.UpperCamelCase,
"upperFields": strings.Join(upperFields, "And"),
"lowerObject": table.LowerCamelCase,
"snakePrimaryKey": field.SnakeCase,
"expression": strings.Join(expressionFields, " AND "),
"expressionValues": strings.Join(expressionValuesFields, ", "),
"containsCache": table.ContainsCache,
})
if err != nil {
return "", err
}
list = append(list, fineOneByFieldBuffer.String())
}
return strings.Join(list, ""), nil
}

View File

@@ -1,63 +0,0 @@
package gen
import (
"bytes"
"strings"
"text/template"
sqltemplate "github.com/tal-tech/go-zero/tools/goctl/model/sql/template"
)
func genFindLimitByField(table *InnerTable) (string, error) {
t, err := template.New("findLimitByField").Parse(sqltemplate.FindLimitByField)
if err != nil {
return "", err
}
list := make([]string, 0)
for _, field := range table.Fields {
if field.IsPrimaryKey {
continue
}
if field.QueryType != QueryLimit {
continue
}
fineOneByFieldBuffer := new(bytes.Buffer)
upperFields := make([]string, 0)
in := make([]string, 0)
expressionFields := make([]string, 0)
expressionValuesFields := make([]string, 0)
upperFields = append(upperFields, field.UpperCamelCase)
in = append(in, field.LowerCamelCase+" "+field.DataType)
expressionFields = append(expressionFields, field.SnakeCase+" = ?")
expressionValuesFields = append(expressionValuesFields, field.LowerCamelCase)
for _, withField := range field.WithFields {
upperFields = append(upperFields, withField.UpperCamelCase)
in = append(in, withField.LowerCamelCase+" "+withField.DataType)
expressionFields = append(expressionFields, withField.SnakeCase+" = ?")
expressionValuesFields = append(expressionValuesFields, withField.LowerCamelCase)
}
sortList := make([]string, 0)
for _, item := range field.Sort {
var sort = "ASC"
if !item.Asc {
sort = "DESC"
}
sortList = append(sortList, item.Field.SnakeCase+" "+sort)
}
err = t.Execute(fineOneByFieldBuffer, map[string]interface{}{
"in": strings.Join(in, ","),
"upperObject": table.UpperCamelCase,
"upperFields": strings.Join(upperFields, "And"),
"lowerObject": table.LowerCamelCase,
"expression": strings.Join(expressionFields, " AND "),
"expressionValues": strings.Join(expressionValuesFields, ", "),
"sortExpression": strings.Join(sortList, ","),
"containsCache": table.ContainsCache,
})
if err != nil {
return "", err
}
list = append(list, fineOneByFieldBuffer.String())
}
return strings.Join(list, ""), nil
}

View File

@@ -1,30 +1,27 @@
package gen package gen
import ( import (
"bytes" "github.com/tal-tech/go-zero/tools/goctl/model/sql/template"
"text/template" "github.com/tal-tech/go-zero/tools/goctl/util/stringx"
"github.com/tal-tech/go-zero/tools/goctl/util/templatex"
sqltemplate "github.com/tal-tech/go-zero/tools/goctl/model/sql/template"
) )
func genFindOne(table *InnerTable) (string, error) { func genFindOne(table Table, withCache bool) (string, error) {
t, err := template.New("findOne").Parse(sqltemplate.FindOne) camel := table.Name.Snake2Camel()
output, err := templatex.With("findOne").
Parse(template.FindOne).
Execute(map[string]interface{}{
"withCache": withCache,
"upperStartCamelObject": camel,
"lowerStartCamelObject": stringx.From(camel).LowerStart(),
"originalPrimaryKey": table.PrimaryKey.Name.Source(),
"lowerStartCamelPrimaryKey": stringx.From(table.PrimaryKey.Name.Snake2Camel()).LowerStart(),
"dataType": table.PrimaryKey.DataType,
"cacheKey": table.CacheKey[table.PrimaryKey.Name.Source()].KeyExpression,
"cacheKeyVariable": table.CacheKey[table.PrimaryKey.Name.Source()].Variable,
})
if err != nil { if err != nil {
return "", err return "", err
} }
fineOneBuffer := new(bytes.Buffer) return output.String(), nil
err = t.Execute(fineOneBuffer, map[string]interface{}{
"withCache": table.PrimaryField.Cache,
"upperObject": table.UpperCamelCase,
"lowerObject": table.LowerCamelCase,
"snakePrimaryKey": table.PrimaryField.SnakeCase,
"lowerPrimaryKey": table.PrimaryField.LowerCamelCase,
"dataType": table.PrimaryField.DataType,
"cacheKey": table.CacheKey[table.PrimaryField.SnakeCase].Key,
"cacheKeyVariable": table.CacheKey[table.PrimaryField.SnakeCase].KeyVariable,
})
if err != nil {
return "", err
}
return fineOneBuffer.String(), nil
} }

View File

@@ -1,67 +1,41 @@
package gen package gen
import ( import (
"bytes" "fmt"
"strings" "strings"
"text/template"
sqltemplate "github.com/tal-tech/go-zero/tools/goctl/model/sql/template" "github.com/tal-tech/go-zero/tools/goctl/model/sql/template"
"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
"github.com/tal-tech/go-zero/tools/goctl/util/templatex"
) )
func genFineOneByField(table *InnerTable) (string, error) { func genFineOneByField(table Table, withCache bool) (string, error) {
t, err := template.New("findOneByField").Parse(sqltemplate.FindOneByField) t := templatex.With("findOneByField").Parse(template.FindOneByField)
if err != nil { var list []string
return "", err camelTableName := table.Name.Snake2Camel()
}
list := make([]string, 0)
for _, field := range table.Fields { for _, field := range table.Fields {
if field.IsPrimaryKey { if field.IsPrimaryKey || !field.IsKey {
continue continue
} }
if field.QueryType != QueryOne { camelFieldName := field.Name.Snake2Camel()
continue output, err := t.Execute(map[string]interface{}{
} "upperStartCamelObject": camelTableName,
fineOneByFieldBuffer := new(bytes.Buffer) "upperField": camelFieldName,
upperFields := make([]string, 0) "in": fmt.Sprintf("%s %s", stringx.From(camelFieldName).LowerStart(), field.DataType),
in := make([]string, 0) "withCache": withCache,
expressionFields := make([]string, 0) "cacheKey": table.CacheKey[field.Name.Source()].KeyExpression,
expressionValuesFields := make([]string, 0) "cacheKeyVariable": table.CacheKey[field.Name.Source()].Variable,
upperFields = append(upperFields, field.UpperCamelCase) "primaryKeyLeft": table.CacheKey[table.PrimaryKey.Name.Source()].Left,
in = append(in, field.LowerCamelCase+" "+field.DataType) "lowerStartCamelObject": stringx.From(camelTableName).LowerStart(),
expressionFields = append(expressionFields, field.SnakeCase+" = ?") "lowerStartCamelField": stringx.From(camelFieldName).LowerStart(),
expressionValuesFields = append(expressionValuesFields, field.LowerCamelCase) "upperStartCamelPrimaryKey": table.PrimaryKey.Name.Snake2Camel(),
for _, withField := range field.WithFields { "originalField": field.Name.Source(),
upperFields = append(upperFields, withField.UpperCamelCase) "originalPrimaryField": table.PrimaryKey.Name.Source(),
in = append(in, withField.LowerCamelCase+" "+withField.DataType)
expressionFields = append(expressionFields, withField.SnakeCase+" = ?")
expressionValuesFields = append(expressionValuesFields, withField.LowerCamelCase)
}
err = t.Execute(fineOneByFieldBuffer, map[string]interface{}{
"in": strings.Join(in, ","),
"upperObject": table.UpperCamelCase,
"upperFields": strings.Join(upperFields, "And"),
"onlyOneFiled": len(field.WithFields) == 0,
"withCache": field.Cache,
"containsCache": table.ContainsCache,
"lowerObject": table.LowerCamelCase,
"lowerField": field.LowerCamelCase,
"snakeField": field.SnakeCase,
"lowerPrimaryKey": table.PrimaryField.LowerCamelCase,
"UpperPrimaryKey": table.PrimaryField.UpperCamelCase,
"primaryKeyDefine": table.CacheKey[table.PrimaryField.SnakeCase].Define,
"primarySnakeField": table.PrimaryField.SnakeCase,
"primaryDataType": table.PrimaryField.DataType,
"primaryDataTypeString": table.PrimaryField.DataType == "string",
"upperObjectKey": table.PrimaryField.UpperCamelCase,
"cacheKey": table.CacheKey[field.SnakeCase].Key,
"cacheKeyVariable": table.CacheKey[field.SnakeCase].KeyVariable,
"expression": strings.Join(expressionFields, " AND "),
"expressionValues": strings.Join(expressionValuesFields, ", "),
}) })
if err != nil { if err != nil {
return "", err return "", err
} }
list = append(list, fineOneByFieldBuffer.String()) list = append(list, output.String())
} }
return strings.Join(list, ""), nil return strings.Join(list, "\n"), nil
} }

View File

@@ -0,0 +1,196 @@
package gen
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/tal-tech/go-zero/tools/goctl/model/sql/parser"
"github.com/tal-tech/go-zero/tools/goctl/model/sql/template"
"github.com/tal-tech/go-zero/tools/goctl/util"
"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/templatex"
)
const (
pwd = "."
createTableFlag = `(?m)^(?i)CREATE\s+TABLE` // ignore case
)
type (
defaultGenerator struct {
source string
src string
dir string
console.Console
}
Option func(generator *defaultGenerator)
)
func NewDefaultGenerator(src, dir string, opt ...Option) *defaultGenerator {
if src == "" {
src = pwd
}
if dir == "" {
dir = pwd
}
generator := &defaultGenerator{src: src, dir: dir}
var optionList []Option
optionList = append(optionList, newDefaultOption())
optionList = append(optionList, opt...)
for _, fn := range optionList {
fn(generator)
}
return generator
}
func WithConsoleOption(c console.Console) Option {
return func(generator *defaultGenerator) {
generator.Console = c
}
}
func newDefaultOption() Option {
return func(generator *defaultGenerator) {
generator.Console = console.NewColorConsole()
}
}
func (g *defaultGenerator) Start(withCache bool) error {
fileSrc, err := filepath.Abs(g.src)
if err != nil {
return err
}
dirAbs, err := filepath.Abs(g.dir)
if err != nil {
return err
}
err = util.MkdirIfNotExist(dirAbs)
if err != nil {
return err
}
data, err := ioutil.ReadFile(fileSrc)
if err != nil {
return err
}
g.source = string(data)
modelList, err := g.genFromDDL(withCache)
if err != nil {
return err
}
for tableName, code := range modelList {
name := fmt.Sprintf("%smodel.go", strings.ToLower(stringx.From(tableName).Snake2Camel()))
filename := filepath.Join(dirAbs, name)
if util.FileExists(filename) {
g.Warning("%s already exists,ignored.", name)
continue
}
err = ioutil.WriteFile(filename, []byte(code), os.ModePerm)
if err != nil {
return err
}
}
// generate error file
filename := filepath.Join(dirAbs, "error.go")
if !util.FileExists(filename) {
err = ioutil.WriteFile(filename, []byte(template.Error), os.ModePerm)
if err != nil {
return err
}
}
g.Success("Done.")
return nil
}
// ret1: key-table name,value-code
func (g *defaultGenerator) genFromDDL(withCache bool) (map[string]string, error) {
ddlList := g.split()
m := make(map[string]string)
for _, ddl := range ddlList {
table, err := parser.Parse(ddl)
if err != nil {
return nil, err
}
code, err := g.genModel(*table, withCache)
if err != nil {
return nil, err
}
m[table.Name.Source()] = code
}
return m, nil
}
type (
Table struct {
parser.Table
CacheKey map[string]Key
}
)
func (g *defaultGenerator) genModel(in parser.Table, withCache bool) (string, error) {
t := templatex.With("model").
Parse(template.Model).
GoFmt(true)
m, err := genCacheKeys(in)
if err != nil {
return "", err
}
importsCode := genImports(withCache)
var table Table
table.Table = in
table.CacheKey = m
varsCode, err := genVars(table, withCache)
if err != nil {
return "", err
}
typesCode, err := genTypes(table, withCache)
if err != nil {
return "", err
}
newCode, err := genNew(table, withCache)
if err != nil {
return "", err
}
insertCode, err := genInsert(table, withCache)
if err != nil {
return "", err
}
var findCode = make([]string, 0)
findOneCode, err := genFindOne(table, withCache)
if err != nil {
return "", err
}
findOneByFieldCode, err := genFineOneByField(table, withCache)
if err != nil {
return "", err
}
findCode = append(findCode, findOneCode, findOneByFieldCode)
updateCode, err := genUpdate(table, withCache)
if err != nil {
return "", err
}
deleteCode, err := genDelete(table, withCache)
if err != nil {
return "", err
}
output, err := t.Execute(map[string]interface{}{
"imports": importsCode,
"vars": varsCode,
"types": typesCode,
"new": newCode,
"insert": insertCode,
"find": strings.Join(findCode, "\r\n"),
"update": updateCode,
"delete": deleteCode,
})
if err != nil {
return "", err
}
return output.String(), nil
}

View File

@@ -1,23 +1,13 @@
package gen package gen
import ( import (
"bytes" "github.com/tal-tech/go-zero/tools/goctl/model/sql/template"
"text/template"
sqltemplate "github.com/tal-tech/go-zero/tools/goctl/model/sql/template"
) )
func genImports(table *InnerTable) (string, error) { func genImports(withCache bool) string {
t, err := template.New("imports").Parse(sqltemplate.Imports) if withCache {
if err != nil { return template.Imports
return "", err } else {
return template.ImportsNoCache
} }
importBuffer := new(bytes.Buffer)
err = t.Execute(importBuffer, map[string]interface{}{
"containsCache": table.ContainsCache,
})
if err != nil {
return "", err
}
return importBuffer.String(), nil
} }

View File

@@ -1,37 +1,39 @@
package gen package gen
import ( import (
"bytes"
"strings" "strings"
"text/template"
sqltemplate "github.com/tal-tech/go-zero/tools/goctl/model/sql/template" "github.com/tal-tech/go-zero/tools/goctl/model/sql/template"
"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
"github.com/tal-tech/go-zero/tools/goctl/util/templatex"
) )
func genInsert(table *InnerTable) (string, error) { func genInsert(table Table, withCache bool) (string, error) {
t, err := template.New("insert").Parse(sqltemplate.Insert)
if err != nil {
return "", nil
}
insertBuffer := new(bytes.Buffer)
expressions := make([]string, 0) expressions := make([]string, 0)
expressionValues := make([]string, 0) expressionValues := make([]string, 0)
for _, filed := range table.Fields { for _, filed := range table.Fields {
if filed.SnakeCase == "create_time" || filed.SnakeCase == "update_time" || filed.IsPrimaryKey { camel := filed.Name.Snake2Camel()
if camel == "CreateTime" || camel == "UpdateTime" {
continue
}
if filed.IsPrimaryKey && table.PrimaryKey.AutoIncrement {
continue continue
} }
expressions = append(expressions, "?") expressions = append(expressions, "?")
expressionValues = append(expressionValues, "data."+filed.UpperCamelCase) expressionValues = append(expressionValues, "data."+camel)
} }
err = t.Execute(insertBuffer, map[string]interface{}{ camel := table.Name.Snake2Camel()
"upperObject": table.UpperCamelCase, output, err := templatex.With("insert").
"lowerObject": table.LowerCamelCase, Parse(template.Insert).
"expression": strings.Join(expressions, ", "), Execute(map[string]interface{}{
"expressionValues": strings.Join(expressionValues, ", "), "withCache": withCache,
"containsCache": table.ContainsCache, "upperStartCamelObject": camel,
}) "lowerStartCamelObject": stringx.From(camel).LowerStart(),
"expression": strings.Join(expressions, ", "),
"expressionValues": strings.Join(expressionValues, ", "),
})
if err != nil { if err != nil {
return "", err return "", err
} }
return insertBuffer.String(), nil return output.String(), nil
} }

View File

@@ -1,105 +1,50 @@
package gen package gen
import ( import (
"bytes" "fmt"
"strings"
"text/template"
)
var ( "github.com/tal-tech/go-zero/tools/goctl/model/sql/parser"
cacheKeyExpressionTemplate = `cache{{.upperCamelTable}}{{.upperCamelField}}Prefix = "cache#{{.lowerCamelTable}}#{{.lowerCamelField}}#"` "github.com/tal-tech/go-zero/tools/goctl/util/stringx"
keyTemplate = `{{.lowerCamelField}}Key := fmt.Sprintf("%s%v", {{.define}}, {{.lowerCamelField}})`
keyRespTemplate = `{{.lowerCamelField}}Key := fmt.Sprintf("%s%v", {{.define}}, resp.{{.upperCamelField}})`
keyDataTemplate = `{{.lowerCamelField}}Key := fmt.Sprintf("%s%v", {{.define}}, data.{{.upperCamelField}})`
) )
type ( type (
// tableName:user
// {{prefix}}=cache
// key:id
Key struct { Key struct {
Define string // cacheKey define,如cacheUserUserIdPrefix VarExpression string // cacheUserIdPrefix="cache#user#id#"
Value string // cacheKey value expression,如cache#user#userId# Left string // cacheUserIdPrefix
Expression string // cacheKey expression如:cacheUserUserIdPrefix="cache#user#userId#" Right string // cache#user#id#
KeyVariable string // cacheKey 声明变量,如:userIdKey Variable string // userIdKey
Key string // 缓存key的代码,如 userIdKey:=fmt.Sprintf("%s%v", cacheUserUserIdPrefix, userId) KeyExpression string // userIdKey: = fmt.Sprintf("cache#user#id#%v", userId)
DataKey string // 缓存key的代码,如 userIdKey:=fmt.Sprintf("%s%v", cacheUserUserIdPrefix, data.userId) DataKeyExpression string // userIdKey: = fmt.Sprintf("cache#user#id#%v", data.userId)
RespKey string // 缓存key的代码,如 userIdKey:=fmt.Sprintf("%s%v", cacheUserUserIdPrefix, resp.userId) RespKeyExpression string // userIdKey: = fmt.Sprintf("cache#user#id#%v", resp.userId)
} }
) )
// key-数据库原始字段名,value-缓存key对象 // key-数据库原始字段名,value-缓存key相关数据
func genCacheKeys(table *InnerTable) (map[string]Key, error) { func genCacheKeys(table parser.Table) (map[string]Key, error) {
fields := table.Fields fields := table.Fields
var m = make(map[string]Key) m := make(map[string]Key)
if !table.ContainsCache { camelTableName := table.Name.Snake2Camel()
return m, nil lowerStartCamelTableName := stringx.From(camelTableName).LowerStart()
}
for _, field := range fields { for _, field := range fields {
if !field.Cache && !field.IsPrimaryKey { if !field.IsKey {
continue continue
} }
t, err := template.New("keyExpression").Parse(cacheKeyExpressionTemplate) camelFieldName := field.Name.Snake2Camel()
if err != nil { lowerStartCamelFieldName := stringx.From(camelFieldName).LowerStart()
return nil, err left := fmt.Sprintf("cache%s%sPrefix", camelTableName, camelFieldName)
} right := fmt.Sprintf("cache#%s#%s#", camelTableName, lowerStartCamelFieldName)
var expressionBuffer = new(bytes.Buffer) variable := fmt.Sprintf("%s%sKey", lowerStartCamelTableName, camelFieldName)
err = t.Execute(expressionBuffer, map[string]string{ m[field.Name.Source()] = Key{
"upperCamelTable": table.UpperCamelCase, VarExpression: fmt.Sprintf(`%s = "%s"`, left, right),
"lowerCamelTable": table.LowerCamelCase, Left: left,
"upperCamelField": field.UpperCamelCase, Right: right,
"lowerCamelField": field.LowerCamelCase, Variable: variable,
}) KeyExpression: fmt.Sprintf(`%s := fmt.Sprintf("%s%s", %s,%s)`, variable, "%s", "%v", left, lowerStartCamelFieldName),
if err != nil { DataKeyExpression: fmt.Sprintf(`%s := fmt.Sprintf("%s%s",%s, data.%s)`, variable, "%s", "%v", left, camelFieldName),
return nil, err RespKeyExpression: fmt.Sprintf(`%s := fmt.Sprintf("%s%s", %s,resp.%s)`, variable, "%s", "%v", left, camelFieldName),
}
expression := expressionBuffer.String()
expressionAr := strings.Split(expression, "=")
define := strings.TrimSpace(expressionAr[0])
value := strings.TrimSpace(expressionAr[1])
t, err = template.New("key").Parse(keyTemplate)
if err != nil {
return nil, err
}
var keyBuffer = new(bytes.Buffer)
err = t.Execute(keyBuffer, map[string]string{
"lowerCamelField": field.LowerCamelCase,
"define": define,
})
if err != nil {
return nil, err
}
t, err = template.New("keyData").Parse(keyDataTemplate)
if err != nil {
return nil, err
}
var keyDataBuffer = new(bytes.Buffer)
err = t.Execute(keyDataBuffer, map[string]string{
"lowerCamelField": field.LowerCamelCase,
"upperCamelField": field.UpperCamelCase,
"define": define,
})
if err != nil {
return nil, err
}
t, err = template.New("keyResp").Parse(keyRespTemplate)
if err != nil {
return nil, err
}
var keyRespBuffer = new(bytes.Buffer)
err = t.Execute(keyRespBuffer, map[string]string{
"lowerCamelField": field.LowerCamelCase,
"upperCamelField": field.UpperCamelCase,
"define": define,
})
if err != nil {
return nil, err
}
m[field.SnakeCase] = Key{
Define: define,
Value: value,
Expression: expression,
KeyVariable: field.LowerCamelCase + "Key",
Key: keyBuffer.String(),
DataKey: keyDataBuffer.String(),
RespKey: keyRespBuffer.String(),
} }
} }
return m, nil return m, nil

View File

@@ -1,100 +0,0 @@
package gen
import (
"log"
"testing"
"github.com/tal-tech/go-zero/core/logx"
)
func TestKeys(t *testing.T) {
var table = OuterTable{
Table: "user_info",
CreateNotFound: true,
Fields: []*OuterFiled{
{
IsPrimaryKey: true,
Name: "user_id",
DataBaseType: "bigint",
Comment: "主键id",
},
{
Name: "campus_id",
DataBaseType: "bigint",
Comment: "整校id",
QueryType: QueryAll,
Cache: false,
},
{
Name: "name",
DataBaseType: "varchar",
Comment: "用户姓名",
QueryType: QueryOne,
},
{
Name: "id_number",
DataBaseType: "varchar",
Comment: "身份证",
Cache: false,
QueryType: QueryNone,
WithFields: []OuterWithField{
{
Name: "name",
DataBaseType: "varchar",
},
},
},
{
Name: "age",
DataBaseType: "int",
Comment: "年龄",
Cache: false,
QueryType: QueryNone,
},
{
Name: "gender",
DataBaseType: "tinyint",
Comment: "性别0-男1-女2-不限",
QueryType: QueryLimit,
WithFields: []OuterWithField{
{
Name: "campus_id",
DataBaseType: "bigint",
},
},
OuterSort: []OuterSort{
{
Field: "create_time",
Asc: false,
},
},
},
{
Name: "mobile",
DataBaseType: "varchar",
Comment: "手机号",
QueryType: QueryOne,
Cache: true,
},
{
Name: "create_time",
DataBaseType: "timestamp",
Comment: "创建时间",
},
{
Name: "update_time",
DataBaseType: "timestamp",
Comment: "更新时间",
},
},
}
innerTable, err := TableConvert(table)
if err != nil {
log.Fatalln(err)
}
tp, err := GenModel(innerTable)
if err != nil {
log.Fatalln(err)
}
logx.Info(tp)
}

View File

@@ -1,86 +0,0 @@
package gen
import (
"bytes"
"go/format"
"strings"
"text/template"
"github.com/tal-tech/go-zero/core/logx"
sqltemplate "github.com/tal-tech/go-zero/tools/goctl/model/sql/template"
)
func GenModel(table *InnerTable) (string, error) {
t, err := template.New("model").Parse(sqltemplate.Model)
if err != nil {
return "", nil
}
modelBuffer := new(bytes.Buffer)
importsCode, err := genImports(table)
if err != nil {
return "", err
}
varsCode, err := genVars(table)
if err != nil {
return "", err
}
typesCode, err := genTypes(table)
if err != nil {
return "", err
}
newCode, err := genNew(table)
if err != nil {
return "", err
}
insertCode, err := genInsert(table)
if err != nil {
return "", err
}
var findCode = make([]string, 0)
findOneCode, err := genFindOne(table)
if err != nil {
return "", err
}
findOneByFieldCode, err := genFineOneByField(table)
if err != nil {
return "", err
}
findAllCode, err := genFindAllByField(table)
if err != nil {
return "", err
}
findLimitCode, err := genFindLimitByField(table)
if err != nil {
return "", err
}
findCode = append(findCode, findOneCode, findOneByFieldCode, findAllCode, findLimitCode)
updateCode, err := genUpdate(table)
if err != nil {
return "", err
}
deleteCode, err := genDelete(table)
if err != nil {
return "", err
}
err = t.Execute(modelBuffer, map[string]interface{}{
"imports": importsCode,
"vars": varsCode,
"types": typesCode,
"new": newCode,
"insert": insertCode,
"find": strings.Join(findCode, "\r\n"),
"update": updateCode,
"delete": deleteCode,
})
if err != nil {
return "", err
}
result := modelBuffer.String()
bts, err := format.Source([]byte(result))
if err != nil {
logx.Errorf("%+v", err)
return "", err
}
return string(bts), nil
}

View File

@@ -1,24 +1,19 @@
package gen package gen
import ( import (
"bytes" "github.com/tal-tech/go-zero/tools/goctl/model/sql/template"
"text/template" "github.com/tal-tech/go-zero/tools/goctl/util/templatex"
sqltemplate "github.com/tal-tech/go-zero/tools/goctl/model/sql/template"
) )
func genNew(table *InnerTable) (string, error) { func genNew(table Table, withCache bool) (string, error) {
t, err := template.New("new").Parse(sqltemplate.New) output, err := templatex.With("new").
Parse(template.New).
Execute(map[string]interface{}{
"withCache": withCache,
"upperStartCamelObject": table.Name.Snake2Camel(),
})
if err != nil { if err != nil {
return "", err return "", err
} }
newBuffer := new(bytes.Buffer) return output.String(), nil
err = t.Execute(newBuffer, map[string]interface{}{
"containsCache": table.ContainsCache,
"upperObject": table.UpperCamelCase,
})
if err != nil {
return "", err
}
return newBuffer.String(), nil
} }

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