Compare commits

..

10 Commits

Author SHA1 Message Date
kevin
95aa65efb9 add dockerfile generator 2020-11-08 21:28:58 +08:00
kevin
3806e66cf1 simplify http server starter 2020-11-08 13:17:14 +08:00
kevin
bd430baf52 graceful shutdown refined 2020-11-08 13:08:00 +08:00
Keson
48f4154ea8 update doc (#193) 2020-11-08 13:02:48 +08:00
super_mario
2599e0d28d Close the process when shutdown is finished (#157)
Co-authored-by: Kevin Wan <wanjunfeng@gmail.com>
2020-11-08 12:50:58 +08:00
kingxt
12327fa07d break generator when happen error (#192)
Co-authored-by: kim <xutao@xiaoheiban.cn>
2020-11-07 21:25:52 +08:00
kevin
57079bf4a4 update cli package 2020-11-07 20:01:25 +08:00
kingxt
7f6eceb5a3 add more test (#189)
* new test

* import bug when with quotation

* new test

* add test condition

* rpc template command use -o param

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

View File

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

View File

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

2
go.mod
View File

@@ -43,7 +43,7 @@ require (
github.com/spaolacci/murmur3 v1.1.0 github.com/spaolacci/murmur3 v1.1.0
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.5
github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2 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

2
go.sum
View File

@@ -277,6 +277,8 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6/go.mod h1
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA= github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA=
github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU=
github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
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=

View File

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

View File

@@ -94,7 +94,6 @@ func ApiFormatByPath(apiFilePath string) error {
} }
func apiFormat(data string) (string, error) { func apiFormat(data string) (string, error) {
r := reg.ReplaceAllStringFunc(data, func(m string) string { r := reg.ReplaceAllStringFunc(data, func(m string) string {
parts := reg.FindStringSubmatch(m) parts := reg.FindStringSubmatch(m)
if len(parts) < 2 { if len(parts) < 2 {

View File

@@ -1,11 +1,9 @@
package gogen package gogen
import ( import (
"bytes"
"errors" "errors"
"fmt" "fmt"
"os" "os"
"os/exec"
"path" "path"
"path/filepath" "path/filepath"
"strconv" "strconv"
@@ -60,7 +58,6 @@ func DoGenProject(apiFile, dir string, force bool) error {
logx.Must(genHandlers(dir, api)) logx.Must(genHandlers(dir, api))
logx.Must(genRoutes(dir, api, force)) logx.Must(genRoutes(dir, api, force))
logx.Must(genLogic(dir, api)) logx.Must(genLogic(dir, api))
createGoModFileIfNeed(dir)
if err := backupAndSweep(apiFile); err != nil { if err := backupAndSweep(apiFile); err != nil {
return err return err
@@ -129,34 +126,3 @@ func sweep() error {
return nil return nil
}) })
} }
func createGoModFileIfNeed(dir string) {
absDir, err := filepath.Abs(dir)
if err != nil {
panic(err)
}
_, hasGoMod := util.FindGoModPath(dir)
if hasGoMod {
return
}
gopath := os.Getenv("GOPATH")
parent := path.Join(gopath, "src")
pos := strings.Index(absDir, parent)
if pos >= 0 {
return
}
moduleName := absDir[len(filepath.Dir(absDir))+1:]
cmd := exec.Command("go", "mod", "init", moduleName)
cmd.Dir = dir
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err = cmd.Run(); err != nil {
fmt.Println(err.Error())
}
outStr, errStr := string(stdout.Bytes()), string(stderr.Bytes())
fmt.Printf(outStr + "\n" + errStr)
}

View File

@@ -10,6 +10,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"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/rpc/execx"
) )
const testApiTemplate = ` const testApiTemplate = `
@@ -29,6 +30,9 @@ type Response struct {
Message string ` + "`" + `json:"message"` + "`" + ` Message string ` + "`" + `json:"message"` + "`" + `
} }
@server(
group: greet
)
service A-api { service A-api {
@server( @server(
handler: GreetHandler handler: GreetHandler
@@ -37,6 +41,7 @@ service A-api {
@server( @server(
handler: NoResponseHandler handler: NoResponseHandler
) )
get /greet/get(Request) returns get /greet/get(Request) returns
} }
@@ -204,6 +209,75 @@ service A-api {
} }
` `
const hasCommentApiTest = `
type Inline struct {
}
type Request struct {
Inline
Name string ` + "`" + `path:"name,options=you|me"` + "`" + ` // name in path
}
type Response struct {
Message string ` + "`" + `json:"msg"` + "`" + ` // message
}
service A-api {
@doc(helloworld)
@server(
handler: GreetHandler
)
get /greet/from/:name(Request) returns (Response)
}
`
const hasInlineNoExistTest = `
type Request struct {
Inline
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
}
type Response struct {
Message string ` + "`" + `json:"message"` + "`" + ` // message
}
service A-api {
@doc(helloworld)
@server(
handler: GreetHandler
)
get /greet/from/:name(Request) returns (Response)
}
`
const importApi = `
type ImportData struct {
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
}
`
const hasImportApi = `
import "importApi.api"
type Request struct {
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
}
type Response struct {
Message string ` + "`" + `json:"message"` + "`" + ` // message
}
service A-api {
@server(
handler: GreetHandler
)
get /greet/from/:name(Request) returns (Response)
}
`
func TestParser(t *testing.T) { func TestParser(t *testing.T) {
filename := "greet.api" filename := "greet.api"
err := ioutil.WriteFile(filename, []byte(testApiTemplate), os.ModePerm) err := ioutil.WriteFile(filename, []byte(testApiTemplate), os.ModePerm)
@@ -367,6 +441,64 @@ func TestApiRoutes(t *testing.T) {
validate(t, filename) validate(t, filename)
} }
func TestHasCommentRoutes(t *testing.T) {
filename := "greet.api"
err := ioutil.WriteFile(filename, []byte(hasCommentApiTest), os.ModePerm)
assert.Nil(t, err)
defer os.Remove(filename)
parser, err := parser.NewParser(filename)
assert.Nil(t, err)
_, err = parser.Parse()
assert.Nil(t, err)
validate(t, filename)
}
func TestInlineTypeNotExist(t *testing.T) {
filename := "greet.api"
err := ioutil.WriteFile(filename, []byte(hasInlineNoExistTest), os.ModePerm)
assert.Nil(t, err)
defer os.Remove(filename)
parser, err := parser.NewParser(filename)
assert.Nil(t, err)
_, err = parser.Parse()
assert.Nil(t, err)
validate(t, filename)
}
func TestHasImportApi(t *testing.T) {
filename := "greet.api"
err := ioutil.WriteFile(filename, []byte(hasImportApi), os.ModePerm)
assert.Nil(t, err)
defer os.Remove(filename)
importApiName := "importApi.api"
err = ioutil.WriteFile(importApiName, []byte(importApi), os.ModePerm)
assert.Nil(t, err)
defer os.Remove(importApiName)
parser, err := parser.NewParser(filename)
assert.Nil(t, err)
api, err := parser.Parse()
assert.Nil(t, err)
var hasInline bool
for _, ty := range api.Types {
if ty.Name == "ImportData" {
hasInline = true
break
}
}
assert.True(t, hasInline)
validate(t, filename)
}
func validate(t *testing.T, api string) { func validate(t *testing.T, api string) {
dir := "_go" dir := "_go"
err := DoGenProject(api, dir, true) err := DoGenProject(api, dir, true)
@@ -380,6 +512,9 @@ func validate(t *testing.T, api string) {
} }
return nil return nil
}) })
_, err = execx.Run("go test ./...", dir)
assert.Nil(t, err)
} }
func validateCode(code string) error { func validateCode(code string) error {

View File

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

View File

@@ -55,6 +55,7 @@ func genEtc(dir string, api *spec.ApiSpec) error {
if err != nil { if err != nil {
return err return err
} }
formatCode := formatCode(buffer.String()) formatCode := formatCode(buffer.String())
_, err = fp.WriteString(formatCode) _, err = fp.WriteString(formatCode)
return err return err

View File

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

View File

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

View File

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

View File

@@ -52,7 +52,7 @@ type (
jwtEnabled bool jwtEnabled bool
signatureEnabled bool signatureEnabled bool
authName string authName string
middleware []string middlewares []string
} }
route struct { route struct {
method string method string
@@ -92,9 +92,9 @@ func genRoutes(dir string, api *spec.ApiSpec, force bool) error {
} }
var routes string var routes string
if len(g.middleware) > 0 { if len(g.middlewares) > 0 {
gbuilder.WriteString("\n}...,") gbuilder.WriteString("\n}...,")
var params = g.middleware var params = g.middlewares
for i := range params { for i := range params {
params[i] = "serverCtx." + params[i] params[i] = "serverCtx." + params[i]
} }
@@ -143,8 +143,9 @@ func genRoutes(dir string, api *spec.ApiSpec, force bool) error {
"routesAdditions": strings.TrimSpace(builder.String()), "routesAdditions": strings.TrimSpace(builder.String()),
}) })
if err != nil { if err != nil {
return nil return err
} }
formatCode := formatCode(buffer.String()) formatCode := formatCode(buffer.String())
_, err = fp.WriteString(formatCode) _, err = fp.WriteString(formatCode)
return err return err
@@ -206,7 +207,7 @@ func getRoutes(api *spec.ApiSpec) ([]group, error) {
} }
if value, ok := apiutil.GetAnnotationValue(g.Annotations, "server", "middleware"); ok { if value, ok := apiutil.GetAnnotationValue(g.Annotations, "server", "middleware"); ok {
for _, item := range strings.Split(value, ",") { for _, item := range strings.Split(value, ",") {
groupedRoutes.middleware = append(groupedRoutes.middleware, item) groupedRoutes.middlewares = append(groupedRoutes.middlewares, item)
} }
} }
routes = append(routes, groupedRoutes) routes = append(routes, groupedRoutes)

View File

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

View File

@@ -71,8 +71,9 @@ func genTypes(dir string, api *spec.ApiSpec, force bool) error {
"containsTime": api.ContainsTime(), "containsTime": api.ContainsTime(),
}) })
if err != nil { if err != nil {
return nil return err
} }
formatCode := formatCode(buffer.String()) formatCode := formatCode(buffer.String())
_, err = fp.WriteString(formatCode) _, err = fp.WriteString(formatCode)
return err return err
@@ -90,12 +91,6 @@ func convertTypeCase(types []spec.Type, t string) (string, error) {
if typ.Name == tp { if typ.Name == tp {
defTypes = append(defTypes, tp) defTypes = append(defTypes, tp)
} }
if len(typ.Annotations) > 0 {
if value, ok := apiutil.GetAnnotationValue(typ.Annotations, "serverReplacer", tp); ok {
t = strings.ReplaceAll(t, tp, value)
}
}
} }
} }

View File

@@ -12,7 +12,7 @@ import (
const apiTemplate = ` const apiTemplate = `
type Request struct { type Request struct {
Name string ` + "`" + `path:"name,options=you|me"` + "`" + ` // 框架自动验证请求参数是否合法 Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
} }
type Response struct { type Response struct {

View File

@@ -39,6 +39,8 @@ func NewParser(filename string) (*Parser, error) {
if len(ip) > 0 { if len(ip) > 0 {
item := strings.TrimPrefix(item, "import") item := strings.TrimPrefix(item, "import")
item = strings.TrimSpace(item) item = strings.TrimSpace(item)
item = strings.TrimPrefix(item, `"`)
item = strings.TrimSuffix(item, `"`)
var path = item var path = item
if !util.FileExists(item) { if !util.FileExists(item) {
path = filepath.Join(filepath.Dir(apiAbsPath), item) path = filepath.Join(filepath.Dir(apiAbsPath), item)

View File

@@ -125,7 +125,7 @@ func ParseApi(api string) (*ApiStruct, error) {
} }
func isImportBeginLine(line string) bool { func isImportBeginLine(line string) bool {
return strings.HasPrefix(line, "import") && strings.HasSuffix(line, ".api") return strings.HasPrefix(line, "import") && (strings.HasSuffix(line, ".api") || strings.HasSuffix(line, `.api"`))
} }
func isTypeBeginLine(line string) bool { func isTypeBeginLine(line string) bool {

View File

@@ -2,16 +2,58 @@ package docker
import ( import (
"errors" "errors"
"os"
"path/filepath"
"strings"
"github.com/tal-tech/go-zero/tools/goctl/gen" "github.com/tal-tech/go-zero/tools/goctl/gen"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
const (
etcDir = "etc"
yamlEtx = ".yaml"
)
func DockerCommand(c *cli.Context) error { func DockerCommand(c *cli.Context) error {
goFile := c.String("go") goFile := c.String("go")
if len(goFile) == 0 { if len(goFile) == 0 {
return errors.New("-go can't be empty") return errors.New("-go can't be empty")
} }
return gen.GenerateDockerfile(goFile, "-f", "etc/config.yaml") cfg, err := findConfig(goFile, etcDir)
if err != nil {
return err
}
return gen.GenerateDockerfile(goFile, "-f", "etc/"+cfg)
}
func findConfig(file, dir string) (string, error) {
var files []string
err := filepath.Walk(dir, func(path string, f os.FileInfo, _ error) error {
if !f.IsDir() {
if filepath.Ext(f.Name()) == yamlEtx {
files = append(files, f.Name())
}
}
return nil
})
if err != nil {
return "", err
}
if len(files) == 0 {
return "", errors.New("no yaml file")
}
name := strings.TrimSuffix(filepath.Base(file), ".go")
for _, f := range files {
if strings.Index(f, name) == 0 {
return f, nil
}
}
return files[0], nil
} }

View File

@@ -6,7 +6,6 @@ import (
"text/template" "text/template"
"github.com/tal-tech/go-zero/tools/goctl/util" "github.com/tal-tech/go-zero/tools/goctl/util"
"github.com/tal-tech/go-zero/tools/goctl/vars"
) )
func GenerateDockerfile(goFile string, args ...string) error { func GenerateDockerfile(goFile string, args ...string) error {
@@ -33,10 +32,9 @@ func GenerateDockerfile(goFile string, args ...string) error {
t := template.Must(template.New("dockerfile").Parse(dockerTemplate)) t := template.Must(template.New("dockerfile").Parse(dockerTemplate))
return t.Execute(out, map[string]string{ return t.Execute(out, map[string]string{
"projectName": vars.ProjectName, "goRelPath": projPath,
"goRelPath": projPath, "goFile": goFile,
"goFile": goFile, "exeFile": util.FileNameWithoutExt(filepath.Base(goFile)),
"exeFile": util.FileNameWithoutExt(goFile), "argument": builder.String(),
"argument": builder.String(),
}) })
} }

View File

@@ -8,8 +8,9 @@ ENV CGO_ENABLED 0
ENV GOOS linux ENV GOOS linux
ENV GOPROXY https://goproxy.cn,direct ENV GOPROXY https://goproxy.cn,direct
WORKDIR $GOPATH/src/{{.projectName}} WORKDIR /build/zero
COPY . . COPY . .
COPY {{.goRelPath}}/etc /app/etc
RUN go build -ldflags="-s -w" -o /app/{{.exeFile}} {{.goRelPath}}/{{.goFile}} RUN go build -ldflags="-s -w" -o /app/{{.exeFile}} {{.goRelPath}}/{{.goFile}}
@@ -22,6 +23,7 @@ ENV TZ Asia/Shanghai
WORKDIR /app WORKDIR /app
COPY --from=builder /app/{{.exeFile}} /app/{{.exeFile}} COPY --from=builder /app/{{.exeFile}} /app/{{.exeFile}}
COPY --from=builder /app/etc /app/etc
CMD ["./{{.exeFile}}"{{.argument}}] CMD ["./{{.exeFile}}"{{.argument}}]
` `

View File

@@ -25,7 +25,7 @@ import (
) )
var ( var (
BuildVersion = "20201021" BuildVersion = "20201108"
commands = []cli.Command{ commands = []cli.Command{
{ {
Name: "api", Name: "api",
@@ -188,16 +188,12 @@ var (
}, },
{ {
Name: "docker", Name: "docker",
Usage: "generate Dockerfile and Makefile", Usage: "generate Dockerfile",
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.StringFlag{ cli.StringFlag{
Name: "go", Name: "go",
Usage: "the file that contains main function", Usage: "the file that contains main function",
}, },
cli.StringFlag{
Name: "namespace, n",
Usage: "which namespace of kubernetes to deploy the service",
},
}, },
Action: docker.DockerCommand, Action: docker.DockerCommand,
}, },
@@ -224,10 +220,6 @@ var (
Name: "out, o", Name: "out, o",
Usage: "the target path of proto", Usage: "the target path of proto",
}, },
cli.BoolFlag{
Name: "idea",
Usage: "whether the command execution environment is from idea plugin. [optional]",
},
}, },
Action: rpc.RpcTemplate, Action: rpc.RpcTemplate,
}, },

View File

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

View File

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

View File

@@ -59,9 +59,10 @@ func RpcNew(c *cli.Context) error {
} }
func RpcTemplate(c *cli.Context) error { func RpcTemplate(c *cli.Context) error {
name := c.Args().First() protoFile := c.String("o")
if len(name) == 0 { if len(protoFile) == 0 {
name = "greet.proto" return errors.New("missing -o")
} }
return generator.ProtoTmpl(name)
return generator.ProtoTmpl(protoFile)
} }

View File

@@ -68,13 +68,12 @@ func (s *rpcServer) Start(register RegisterFn) error {
register(server) register(server)
// we need to make sure all others are wrapped up // we need to make sure all others are wrapped up
// so we do graceful stop at shutdown phase instead of wrap up phase // so we do graceful stop at shutdown phase instead of wrap up phase
shutdownCalled := proc.AddShutdownListener(func() { waitForCalled := proc.AddWrapUpListener(func() {
server.GracefulStop() server.GracefulStop()
}) })
err = server.Serve(lis) defer waitForCalled()
shutdownCalled()
return err return server.Serve(lis)
} }
func WithMetrics(metrics *stat.Metrics) ServerOption { func WithMetrics(metrics *stat.Metrics) ServerOption {

View File

@@ -16,7 +16,10 @@ import (
"google.golang.org/grpc" "google.golang.org/grpc"
) )
const envPodIp = "POD_IP" const (
allEths = "0.0.0.0"
envPodIp = "POD_IP"
)
type RpcServer struct { type RpcServer struct {
server internal.Server server internal.Server
@@ -96,7 +99,7 @@ func figureOutListenOn(listenOn string) string {
} }
host := fields[0] host := fields[0]
if len(host) > 0 && host != "0.0.0.0" { if len(host) > 0 && host != allEths {
return listenOn return listenOn
} }