diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..08cb523c1 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +go.sum diff --git a/go.mod b/go.mod index f93d16e52..d7274efab 100644 --- a/go.mod +++ b/go.mod @@ -1,15 +1,19 @@ module github.com/bilibili/Kratos require ( + github.com/fatih/color v1.7.0 github.com/go-playground/locales v0.12.1 // indirect github.com/go-playground/universal-translator v0.16.0 // indirect github.com/gogo/protobuf v1.2.0 github.com/golang/protobuf v1.2.0 + github.com/kr/pty v1.1.4 github.com/leodido/go-urn v1.1.0 // indirect github.com/pkg/errors v0.8.1 github.com/prometheus/client_golang v0.9.2 github.com/stretchr/testify v1.3.0 + github.com/urfave/cli v1.20.0 google.golang.org/grpc v1.18.0 + gopkg.in/AlecAivazis/survey.v1 v1.8.2 gopkg.in/go-playground/assert.v1 v1.2.1 // indirect gopkg.in/go-playground/validator.v9 v9.26.0 ) diff --git a/go.sum b/go.sum deleted file mode 100644 index a9ac4d50c..000000000 --- a/go.sum +++ /dev/null @@ -1,55 +0,0 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-playground/locales v0.12.1 h1:2FITxuFt/xuCNP1Acdhv62OzaCiviiE4kotfhkmOqEc= -github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= -github.com/go-playground/universal-translator v0.16.0 h1:X++omBR/4cE2MNg91AoC3rmGrCjJ8eAeUP/K/EKx4DM= -github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= -github.com/gogo/protobuf v1.2.0 h1:xU6/SpYbvkNYiptHJYEDRseDLvYE7wSqhYYNy0QSUzI= -github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/leodido/go-urn v1.1.0 h1:Sm1gr51B1kKyfD2BlRcLSiEkffoG96g6TPv6eRoEiB8= -github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= -github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v0.9.2 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740= -github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/common v0.0.0-20181126121408-4724e9255275 h1:PnBWHBf+6L0jOqq0gIVUe6Yk0/QMZ640k6NvkxcBf+8= -github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a h1:9a8MnZMP0X2nLJdBg+pBmGgkJlSaKC2KaQmTCk1XDtE= -github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/grpc v1.18.0 h1:IZl7mfBGfbhYx2p2rKRtYgDFw6SBz+kclmxYrCksPPA= -google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= -gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= -gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= -gopkg.in/go-playground/validator.v9 v9.26.0 h1:2NPPsBpD0ZoxshmLWewQru8rWmbT5JqSzz9D1ZrAjYQ= -gopkg.in/go-playground/validator.v9 v9.26.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= -honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/tool/kratos/README.MD b/tool/kratos/README.MD new file mode 100644 index 000000000..3ddf201f8 --- /dev/null +++ b/tool/kratos/README.MD @@ -0,0 +1,14 @@ +# kratos + +## 项目简介 +Kratos 工具 + +## 安装 + +`go get -u github.com/bilibili/Kratos/tool/kratos` + +## 使用说明 + +### 参数 + +kratos -h diff --git a/tool/kratos/build.go b/tool/kratos/build.go new file mode 100644 index 000000000..1eaeaa89f --- /dev/null +++ b/tool/kratos/build.go @@ -0,0 +1,22 @@ +package main + +import ( + "io" + "os" + "os/exec" + + "github.com/kr/pty" + "github.com/urfave/cli" +) + +func buildAction(c *cli.Context) error { + args := append([]string{"build"}, c.Args()...) + // fmt.Println(args) + cmd := exec.Command("go", args...) + f, err := pty.Start(cmd) + if err != nil { + panic(err) + } + io.Copy(os.Stdout, f) + return nil +} diff --git a/tool/kratos/doc.go b/tool/kratos/doc.go new file mode 100644 index 000000000..ad9d84020 --- /dev/null +++ b/tool/kratos/doc.go @@ -0,0 +1,27 @@ +/* +kratos 是Kratos的工具链,提供新项目创建,代码生成等功能 + +kartos build 本目录之下局部编译,根目录全量编译 +NAME: + kratos build + +USAGE: + kratos build [arguments...] + +EXAMPLE: + cd app && kratos build ./service/.. admin interface/.. tool/cache/... + kratos build + +kartos init 新建新项目 +USAGE: + kratos init [command options] [arguments...] + +OPTIONS: + -n value 项目名 + -o value 维护人 + --grpc 是否是GRPC + +EXAMPLE: + kratos init -n demo -o kratos +*/ +package main diff --git a/tool/kratos/init.go b/tool/kratos/init.go new file mode 100644 index 000000000..84a5d6c82 --- /dev/null +++ b/tool/kratos/init.go @@ -0,0 +1,155 @@ +package main + +import ( + "fmt" + "go/build" + "os" + "path/filepath" + "strings" + + "github.com/urfave/cli" + "gopkg.in/AlecAivazis/survey.v1" +) + +const ( + _textModeFastInit = "一键初始化项目" + _textModeInteraction = "自定义项目参数" + _textYes = "是" + _textNo = "否" +) + +func runInit(ctx *cli.Context) (err error) { + if ctx.NumFlags() == 0 { + if err = interact(); err != nil { + return + } + } + if !validate() { + return nil + } + if err = create(); err != nil { + fmt.Println("项目初始化失败: ", err.Error()) + return nil + } + fmt.Printf("项目[%s]初始化成功!\n", p.Path) + return nil +} + +func initPwd() (ok bool) { + pwd, err := os.Getwd() + if err != nil { + return + } + ps := strings.Split(pwd, string(os.PathSeparator)) + plen := len(ps) + if plen < 1 { + // 至少要有一个目录层级:项目名 + return + } + name := ps[plen-1] + if name == "" { + return + } + p.Name = name + p.Path = pwd + return true +} + +func goPath() (gp string) { + gopaths := strings.Split(os.Getenv("GOPATH"), ":") + if len(gopaths) == 1 { + return gopaths[0] + } + pwd, err := os.Getwd() + if err != nil { + return + } + abspwd, err := filepath.Abs(pwd) + if err != nil { + return + } + for _, gopath := range gopaths { + absgp, err := filepath.Abs(gopath) + if err != nil { + return + } + if strings.HasPrefix(abspwd, absgp) { + return absgp + } + } + return build.Default.GOPATH +} + +func interact() (err error) { + qs1 := &survey.Select{ + Message: "你想怎么玩?", + Options: []string{_textModeFastInit, _textModeInteraction}, + } + var ans1 string + if err = survey.AskOne(qs1, &ans1, nil); err != nil { + return + } + switch ans1 { + case _textModeFastInit: + if ok := initPwd(); !ok { + fmt.Println("快速初始化失败!") + } + return + case _textModeInteraction: + // go on + default: + return + } + qs := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{ + Message: "请输入项目名称:", + }, + Validate: survey.Required, + }, + { + Name: "owner", + Prompt: &survey.Input{ + Message: "请输入项目负责人:", + }, + }, + { + Name: "useGRPC", + Prompt: &survey.Select{ + Message: "是否使用 gRPC ?", + Options: []string{_textYes, _textNo}, + Default: _textNo, + }, + }, + { + Name: "here", + Prompt: &survey.Select{ + Message: "是否当前目录?默认为GOPATH下", + Options: []string{_textYes, _textNo}, + Default: _textYes, + }, + }, + } + ans := struct { + Name string + Owner string + UseGRPC string + Here string + }{} + if err = survey.Ask(qs, &ans); err != nil { + return + } + p.Name = ans.Name + p.Owner = ans.Owner + if ans.UseGRPC == _textYes { + p.WithGRPC = true + } + if ans.UseGRPC == _textYes { + p.WithGRPC = true + } + if ans.Here == _textYes { + p.Here = true + } + return +} diff --git a/tool/kratos/main.go b/tool/kratos/main.go new file mode 100644 index 000000000..256af1a26 --- /dev/null +++ b/tool/kratos/main.go @@ -0,0 +1,72 @@ +package main + +import ( + "fmt" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + app.Name = "kratos" + app.Usage = "kratos tool" + app.Version = Version + app.Commands = []cli.Command{ + { + Name: "build", + Aliases: []string{"b"}, + Usage: "kratos build", + Action: buildAction, + }, + { + Name: "init", + Aliases: []string{"i"}, + Usage: "create new project", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "n", + Value: "", + Usage: "project name for create project", + Destination: &p.Name, + }, + cli.StringFlag{ + Name: "o", + Value: "", + Usage: "project owner for create project", + Destination: &p.Owner, + }, + cli.BoolFlag{ + Name: "grpc", + Usage: "whether to use grpc for create project", + Destination: &p.WithGRPC, + }, + }, + Action: runInit, + }, + { + Name: "tool", + Aliases: []string{"t"}, + Usage: "kratos tool", + Action: toolAction, + }, + { + Name: "version", + Aliases: []string{"v"}, + Usage: "kratos version", + Action: func(c *cli.Context) error { + fmt.Println(getVersion()) + return nil + }, + }, + { + Name: "self-upgrade", + Usage: "kratos self-upgrade", + Action: upgradeAction, + }, + } + err := app.Run(os.Args) + if err != nil { + panic(err) + } +} diff --git a/tool/kratos/project.go b/tool/kratos/project.go new file mode 100644 index 000000000..a18d46540 --- /dev/null +++ b/tool/kratos/project.go @@ -0,0 +1,174 @@ +package main + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" + "os/exec" + "path" + "strings" + "text/template" +) + +// project project config +type project struct { + Name string + Owner string + Path string + WithGRPC bool + Here bool +} + +const ( + _tplTypeDao = iota + _tplTypeHTTPServer + _tplTypeAPIProto + _tplTypeService + _tplTypeMain + _tplTypeChangeLog + _tplTypeContributors + _tplTypeReadme + _tplTypeAppToml + _tplTypeMySQLToml + _tplTypeMCToml + _tplTypeRedisToml + _tplTypeHTTPToml + _tplTypeGRPCToml + _tplTypeModel + _tplTypeGRPCServer + _tplTypeAPIGenerate +) + +var ( + p project + // files type => path + files = map[int]string{ + // init doc + _tplTypeChangeLog: "/CHANGELOG.md", + _tplTypeContributors: "/CONTRIBUTORS.md", + _tplTypeReadme: "/README.md", + // init project + _tplTypeMain: "/cmd/main.go", + _tplTypeDao: "/internal/dao/dao.go", + _tplTypeHTTPServer: "/internal/server/http/http.go", + _tplTypeService: "/internal/service/service.go", + _tplTypeModel: "/internal/model/model.go", + // init config + _tplTypeAppToml: "/configs/application.toml", + _tplTypeMySQLToml: "/configs/mysql.toml", + _tplTypeMCToml: "/configs/memcache.toml", + _tplTypeRedisToml: "/configs/redis.toml", + _tplTypeHTTPToml: "/configs/http.toml", + _tplTypeGRPCToml: "/configs/grpc.toml", + } + // tpls type => content + tpls = map[int]string{ + _tplTypeDao: _tplDao, + _tplTypeHTTPServer: _tplHTTPServer, + _tplTypeAPIProto: _tplAPIProto, + _tplTypeAPIGenerate: _tplAPIGenerate, + _tplTypeMain: _tplMain, + _tplTypeChangeLog: _tplChangeLog, + _tplTypeContributors: _tplContributors, + _tplTypeReadme: _tplReadme, + _tplTypeMySQLToml: _tplMySQLToml, + _tplTypeMCToml: _tplMCToml, + _tplTypeRedisToml: _tplRedisToml, + _tplTypeAppToml: _tplAppToml, + _tplTypeHTTPToml: _tplHTTPToml, + _tplTypeModel: _tplModel, + } +) + +func validate() (ok bool) { + if p.Name == "" { + fmt.Println("[-n] Invalid project name.") + return + } + if p.Path == "" { + if p.Here { + pwd, _ := os.Getwd() + p.Path = path.Join(pwd, p.Name) + } else { + p.Path = path.Join(goPath(), "src", p.Name) + } + } + return true +} + +func create() (err error) { + if p.WithGRPC { + files[_tplTypeGRPCServer] = "/internal/server/grpc/server.go" + files[_tplTypeAPIProto] = "/api/api.proto" + files[_tplTypeAPIGenerate] = "/api/generate.go" + tpls[_tplTypeGRPCServer] = _tplGRPCServer + tpls[_tplTypeGRPCToml] = _tplGRPCToml + tpls[_tplTypeService] = _tplGPRCService + } else { + tpls[_tplTypeService] = _tplService + tpls[_tplTypeMain] = delgrpc(_tplMain) + } + if err = os.MkdirAll(p.Path, 0755); err != nil { + return + } + for t, v := range files { + i := strings.LastIndex(v, "/") + if i > 0 { + dir := v[:i] + if err = os.MkdirAll(p.Path+dir, 0755); err != nil { + return + } + } + if err = write(p.Path+v, tpls[t]); err != nil { + return + } + } + if p.WithGRPC { + if err = genpb(); err != nil { + return + } + } + return +} + +func genpb() error { + cmd := exec.Command("go", "generate", p.Path+"/api/generate.go") + return cmd.Run() +} + +func delgrpc(tpl string) string { + var buf bytes.Buffer + lines := strings.Split(tpl, "\n") + for _, l := range lines { + if strings.Contains(l, "grpc") { + continue + } + if strings.Contains(l, "warden") { + continue + } + buf.WriteString(l) + buf.WriteString("\n") + } + return buf.String() +} + +func write(name, tpl string) (err error) { + data, err := parse(tpl) + if err != nil { + return + } + return ioutil.WriteFile(name, data, 0644) +} + +func parse(s string) ([]byte, error) { + t, err := template.New("").Parse(s) + if err != nil { + return nil, err + } + var buf bytes.Buffer + if err = t.Execute(&buf, p); err != nil { + return nil, err + } + return buf.Bytes(), nil +} diff --git a/tool/kratos/template.go b/tool/kratos/template.go new file mode 100644 index 000000000..781d4ed76 --- /dev/null +++ b/tool/kratos/template.go @@ -0,0 +1,451 @@ +package main + +const ( + _tplAppToml = ` +# This is a TOML document. Boom~ +` + + _tplMySQLToml = ` +[demo] + addr = "127.0.0.1:3306" + dsn = "{user}:{password}@tcp(127.0.0.1:3306)/{database}?timeout=1s&readTimeout=1s&writeTimeout=1s&parseTime=true&loc=Local&charset=utf8mb4,utf8" + readDSN = ["{user}:{password}@tcp(127.0.0.2:3306)/{database}?timeout=1s&readTimeout=1s&writeTimeout=1s&parseTime=true&loc=Local&charset=utf8mb4,utf8","{user}:{password}@tcp(127.0.0.3:3306)/{database}?timeout=1s&readTimeout=1s&writeTimeout=1s&parseTime=true&loc=Local&charset=utf8,utf8mb4"] + active = 20 + idle = 10 + idleTimeout ="4h" + queryTimeout = "200ms" + execTimeout = "300ms" + tranTimeout = "400ms" +` + _tplMCToml = ` +demoExpire = "24h" + +[demo] + name = "{{.Name}}" + proto = "tcp" + addr = "127.0.0.1:11211" + active = 50 + idle = 10 + dialTimeout = "100ms" + readTimeout = "200ms" + writeTimeout = "300ms" + idleTimeout = "80s" +` + _tplRedisToml = ` +demoExpire = "24h" + +[demo] + name = "{{.Name}}" + proto = "tcp" + addr = "127.0.0.1:6389" + idle = 10 + active = 10 + dialTimeout = "1s" + readTimeout = "1s" + writeTimeout = "1s" + idleTimeout = "10s" +` + + _tplHTTPToml = ` +[server] + addr = "0.0.0.0:8000" + timeout = "1s" +` + _tplGRPCToml = ` +[server] + addr = "0.0.0.0:9000" + timeout = "1s" +` + + _tplChangeLog = `## {{.Name}} + +### v1.0.0 +1. 上线功能xxx +` + _tplMain = `package main + +import ( + "context" + "flag" + "os" + "os/signal" + "syscall" + "time" + + "{{.Name}}/internal/server/grpc" + "{{.Name}}/internal/server/http" + "{{.Name}}/internal/service" + "github.com/bilibili/Kratos/pkg/conf/paladin" + "github.com/bilibili/Kratos/pkg/log" +) + +func main() { + flag.Parse() + if err := paladin.Init(); err != nil { + panic(err) + } + log.Init(nil) // debug flag: log.dir={path} + defer log.Close() + log.Info("{{.Name}} start") + svc := service.New() + grpcSrv := grpc.New(svc) + httpSrv := http.New(svc) + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT) + for { + s := <-c + log.Info("get a signal %s", s.String()) + switch s { + case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT: + ctx, cancel := context.WithTimeout(context.Background(), 35*time.Second) + defer cancel() + grpcSrv.Shutdown(ctx) + httpSrv.Shutdown(ctx) + log.Info("{{.Name}} exit") + svc.Close() + time.Sleep(time.Second) + return + case syscall.SIGHUP: + default: + return + } + } +} +` + + _tplContributors = `# Owner +{{.Owner}} + +# Author + +# Reviewer +` + _tplDao = `package dao + +import ( + "context" + "time" + + "github.com/bilibili/Kratos/pkg/cache/memcache" + "github.com/bilibili/Kratos/pkg/cache/redis" + "github.com/bilibili/Kratos/pkg/conf/paladin" + "github.com/bilibili/Kratos/pkg/database/sql" + "github.com/bilibili/Kratos/pkg/log" + xtime "github.com/bilibili/Kratos/pkg/time" +) + +// Dao dao. +type Dao struct { + db *sql.DB + redis *redis.Pool + redisExpire int32 + mc *memcache.Memcache + mcExpire int32 +} + +func checkErr(err error) { + if err != nil { + panic(err) + } +} + +// New new a dao and return. +func New() (dao *Dao) { + var ( + dc struct { + Demo *sql.Config + } + rc struct { + Demo *redis.Config + DemoExpire xtime.Duration + } + mc struct { + Demo *memcache.Config + DemoExpire xtime.Duration + } + ) + checkErr(paladin.Get("mysql.toml").UnmarshalTOML(&dc)) + checkErr(paladin.Get("redis.toml").UnmarshalTOML(&rc)) + checkErr(paladin.Get("memcache.toml").UnmarshalTOML(&mc)) + dao = &Dao{ + // mysql + db: sql.NewMySQL(dc.Demo), + // redis + redis: redis.NewPool(rc.Demo), + redisExpire: int32(time.Duration(rc.DemoExpire) / time.Second), + // memcache + mc: memcache.New(mc.Demo), + mcExpire: int32(time.Duration(mc.DemoExpire) / time.Second), + } + return +} + +// Close close the resource. +func (d *Dao) Close() { + d.mc.Close() + d.redis.Close() + d.db.Close() +} + +// Ping ping the resource. +func (d *Dao) Ping(ctx context.Context) (err error) { + if err = d.pingMC(ctx); err != nil { + return + } + if err = d.pingRedis(ctx); err != nil { + return + } + return d.db.Ping(ctx) +} + +func (d *Dao) pingMC(ctx context.Context) (err error) { + if err = d.mc.Set(ctx, &memcache.Item{Key: "ping", Value: []byte("pong"), Expiration: 0}); err != nil { + log.Error("conn.Set(PING) error(%v)", err) + } + return +} + +func (d *Dao) pingRedis(ctx context.Context) (err error) { + conn := d.redis.Get(ctx) + defer conn.Close() + if _, err = conn.Do("SET", "ping", "pong"); err != nil { + log.Error("conn.Set(PING) error(%v)", err) + } + return +} +` + _tplReadme = `# {{.Name}} + +## 项目简介 +1. + +## 编译环境 + + +## 依赖包 + + +## 编译执行 +` + _tplService = `package service + +import ( + "context" + + "{{.Name}}/internal/dao" + "github.com/bilibili/Kratos/pkg/conf/paladin" +) + +// Service service. +type Service struct { + ac *paladin.Map + dao *dao.Dao +} + +// New new a service and return. +func New() (s *Service) { + var ac = new(paladin.TOML) + if err := paladin.Watch("application.toml", ac); err != nil { + panic(err) + } + s = &Service{ + ac: ac, + dao: dao.New(), + } + return s +} + +// Ping ping the resource. +func (s *Service) Ping(ctx context.Context) (err error) { + return s.dao.Ping(ctx) +} + +// Close close the resource. +func (s *Service) Close() { + s.dao.Close() +} +` + + _tplGPRCService = `package service + +import ( + "context" + "fmt" + + pb "{{.Name}}/api" + "{{.Name}}/internal/dao" + "github.com/bilibili/Kratos/pkg/conf/paladin" + + "github.com/golang/protobuf/ptypes/empty" +) + +// Service service. +type Service struct { + ac *paladin.Map + dao *dao.Dao +} + +// New new a service and return. +func New() (s *Service) { + var ac = new(paladin.TOML) + if err := paladin.Watch("application.toml", ac); err != nil { + panic(err) + } + s = &Service{ + ac: ac, + dao: dao.New(), + } + return s +} + +// SayHello grpc demo func. +func (s *Service) SayHello(ctx context.Context, req *pb.HelloReq) (reply *empty.Empty, err error) { + reply = new(empty.Empty) + fmt.Printf("hello %s", req.Name) + return +} + +// Ping ping the resource. +func (s *Service) Ping(ctx context.Context) (err error) { + return s.dao.Ping(ctx) +} + +// Close close the resource. +func (s *Service) Close() { + s.dao.Close() +} +` + _tplHTTPServer = `package http + +import ( + "net/http" + + "{{.Name}}/internal/service" + "github.com/bilibili/Kratos/pkg/conf/paladin" + "github.com/bilibili/Kratos/pkg/log" + bm "github.com/bilibili/Kratos/pkg/net/http/blademaster" + "github.com/bilibili/Kratos/pkg/net/http/blademaster/middleware/verify" +) + +var ( + svc *service.Service +) + +// New new a bm server. +func New(s *service.Service) (engine *bm.Engine) { + var ( + hc struct { + Server *bm.ServerConfig + } + ) + if err := paladin.Get("http.toml").UnmarshalTOML(&hc); err != nil { + if err != paladin.ErrNotExist { + panic(err) + } + } + svc = s + engine = bm.DefaultServer(hc.Server) + initRouter(engine, verify.New(nil)) + if err := engine.Start(); err != nil { + panic(err) + } + return +} + +func initRouter(e *bm.Engine, v *verify.Verify) { + e.Ping(ping) + e.Register(register) + g := e.Group("/{{.Name}}") + { + g.GET("/start", v.Verify, howToStart) + } +} + +func ping(ctx *bm.Context) { + if err := svc.Ping(ctx); err != nil { + log.Error("ping error(%v)", err) + ctx.AbortWithStatus(http.StatusServiceUnavailable) + } +} + +func register(c *bm.Context) { + c.JSON(map[string]interface{}{}, nil) +} + +// example for http request handler. +func howToStart(c *bm.Context) { + c.String(0, "Golang 大法好 !!!") +} +` + _tplAPIProto = `// 定义项目 API 的 proto 文件 可以同时描述 gRPC 和 HTTP API +// protobuf 文件参考: +// - https://developers.google.com/protocol-buffers/ +// - TODO:待补充文档URL +// protobuf 生成 HTTP 工具: +// - TODO:待补充文档URL +// gRPC Golang Model: +// - TODO:待补充文档URL +// gRPC Golang Warden Gen: +// - TODO:待补充文档URL +// gRPC http 调试工具(无需pb文件): +// - TODO:待补充文档URL +// grpc 命令行调试工具(无需pb文件): +// - TODO:待补充文档URL +syntax = "proto3"; + +import "github.com/gogo/protobuf/gogoproto/gogo.proto"; +import "google/protobuf/empty.proto"; + +// package 命名使用 {appid}.{version} 的方式, version 形如 v1, v2 .. +package demo.service.v1; + +// NOTE: 最后请删除这些无用的注释 (゜-゜)つロ + +option go_package = "api"; +// do not generate getXXX() method +option (gogoproto.goproto_getters_all) = false; + +service Demo { + rpc SayHello (HelloReq) returns (.google.protobuf.Empty); +} + +message HelloReq { + string name = 1 [(gogoproto.moretags)='form:"name" validate:"required"']; +} +` + _tplAPIGenerate = `package api + +// 生成 gRPC 代码 +//go:generate TODO:待完善工具protoc.sh +` + _tplModel = `package model +` + _tplGRPCServer = `package grpc + +import ( + pb "{{.Name}}/api" + "{{.Name}}/internal/service" + "github.com/bilibili/Kratos/pkg/conf/paladin" + "github.com/bilibili/Kratos/pkg/net/rpc/warden" +) + +// New new a grpc server. +func New(svc *service.Service) *warden.Server { + var rc struct { + Server *warden.ServerConfig + } + if err := paladin.Get("grpc.toml").UnmarshalTOML(&rc); err != nil { + if err != paladin.ErrNotExist { + panic(err) + } + } + ws := warden.NewServer(rc.Server) + pb.RegisterDemoServer(ws.Server(), svc) + ws, err := ws.Start() + if err != nil { + panic(err) + } + return ws +} +` +) diff --git a/tool/kratos/tool.go b/tool/kratos/tool.go new file mode 100644 index 000000000..5f4681a16 --- /dev/null +++ b/tool/kratos/tool.go @@ -0,0 +1,175 @@ +package main + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "runtime" + "sort" + "strings" + "time" + + "github.com/fatih/color" + "github.com/urfave/cli" +) + +func toolAction(c *cli.Context) (err error) { + if c.NArg() == 0 { + sort.Slice(toolIndexs, func(i, j int) bool { return toolIndexs[i].BuildTime.After(toolIndexs[j].BuildTime) }) + for _, t := range toolIndexs { + updateTime := t.BuildTime.Format("2006/01/02") + fmt.Printf("%s%s: %s %s (%s) [%s]\n", color.HiMagentaString(t.Name), getNotice(t), color.HiCyanString(t.Summary), t.URL, t.Author, updateTime) + } + fmt.Println("\n执行 install 安装程序 如: kratos tool install demo") + fmt.Println("执行 工具名称 运行程序 如: kratos tool demo") + fmt.Println("\n安装全部工具: kratos tool install all") + return + } + if c.Args().First() == "install" { + name := c.Args().Get(1) + if name == "all" { + installAll() + } else { + install(name) + } + return + } + name := c.Args().First() + for _, t := range toolIndexs { + if name == t.Name { + if !t.installed() || t.updated() { + install(name) + } + pwd, _ := os.Getwd() + var args []string + if c.NArg() > 1 { + args = []string(c.Args())[1:] + } + runTool(t.Name, pwd, t.toolPath(), args) + return + } + } + fmt.Fprintf(os.Stderr, "还未安装 %s\n", name) + return +} + +func upgradeAction(c *cli.Context) error { + install("kratos") + return nil +} + +func install(name string) { + if name == "" { + fmt.Fprintf(os.Stderr, color.HiRedString("请填写要安装的工具名称\n")) + return + } + for _, t := range toolIndexs { + if name == t.Name { + t.install() + return + } + } + fmt.Fprintf(os.Stderr, color.HiRedString("安装失败 找不到 %s\n", name)) + return +} + +func installAll() { + for _, t := range toolIndexs { + if t.Install != "" { + t.install() + } + } +} + +func getNotice(t *Tool) (notice string) { + if !t.supportOS() || t.Install == "" { + return + } + notice = color.HiGreenString("(未安装)") + if f, err := os.Stat(t.toolPath()); err == nil { + notice = color.HiBlueString("(已安装)") + if t.BuildTime.After(f.ModTime()) { + notice = color.RedString("(有更新)") + } + } + return +} + +func runTool(name, dir, cmd string, args []string) (err error) { + toolCmd := &exec.Cmd{ + Path: cmd, + Args: append([]string{cmd}, args...), + Dir: dir, + Stdin: os.Stdin, + Stdout: os.Stdout, + Stderr: os.Stderr, + Env: os.Environ(), + } + if filepath.Base(cmd) == cmd { + var lp string + if lp, err = exec.LookPath(cmd); err == nil { + toolCmd.Path = lp + } + } + if err = toolCmd.Run(); err != nil { + if e, ok := err.(*exec.ExitError); !ok || !e.Exited() { + fmt.Fprintf(os.Stderr, "install %s: %v\n", name, err) + } + } + return +} + +// Tool . +type Tool struct { + Name string `json:"name"` + BuildTime time.Time `json:"build_time"` + Install string `json:"install"` + Summary string `json:"summary"` + Platform []string `json:"platform"` + Author string `json:"author"` + URL string `json:"url"` +} + +func (t Tool) supportOS() bool { + for _, p := range t.Platform { + if strings.ToLower(p) == runtime.GOOS { + return true + } + } + return false +} + +func (t Tool) install() { + if t.Install == "" { + fmt.Fprintf(os.Stderr, color.RedString("%s: 自动安装失败 详情请查看文档 %s\n", t.Name, t.URL)) + return + } + cmds := strings.Split(t.Install, " ") + if len(cmds) > 0 { + if err := runTool(t.Name, t.toolPath(), cmds[0], cmds[1:]); err == nil { + color.Green("%s: 安装成功!", t.Name) + } + } +} + +func (t Tool) updated() bool { + if !t.supportOS() || t.Install == "" { + return false + } + if f, err := os.Stat(t.toolPath()); err == nil { + if t.BuildTime.After(f.ModTime()) { + return true + } + } + return false +} + +func (t Tool) toolPath() string { + return filepath.Join(goPath(), "bin", t.Name) +} + +func (t Tool) installed() bool { + _, err := os.Stat(t.toolPath()) + return err == nil +} diff --git a/tool/kratos/tool_index.go b/tool/kratos/tool_index.go new file mode 100644 index 000000000..f420fd3d1 --- /dev/null +++ b/tool/kratos/tool_index.go @@ -0,0 +1,15 @@ +package main + +import "time" + +var toolIndexs = []*Tool{ + &Tool{ + Name: "kratos", + BuildTime: time.Date(2019, 4, 2, 0, 0, 0, 0, time.Local), + Install: "go get -u github.com/bilibili/Kratos/tool/kratos", + Summary: "Kratos工具集本体", + Platform: []string{"darwin", "linux", "windows"}, + Author: "kratos", + URL: "wiki", + }, +} diff --git a/tool/kratos/version.go b/tool/kratos/version.go new file mode 100644 index 000000000..68d53c105 --- /dev/null +++ b/tool/kratos/version.go @@ -0,0 +1,44 @@ +package main + +import ( + "bytes" + "runtime" + "text/template" +) + +var ( + // Version is version + Version = "0.0.1" + // BuildTime is BuildTime + BuildTime = "2019/04/03" +) + +// VersionOptions include version +type VersionOptions struct { + GitCommit string + Version string + BuildTime string + GoVersion string + Os string + Arch string +} + +var versionTemplate = ` Version: {{.Version}} + Go version: {{.GoVersion}} + Built: {{.BuildTime}} + OS/Arch: {{.Os}}/{{.Arch}} + ` + +func getVersion() string { + var doc bytes.Buffer + vo := VersionOptions{ + Version: Version, + BuildTime: BuildTime, + GoVersion: runtime.Version(), + Os: runtime.GOOS, + Arch: runtime.GOARCH, + } + tmpl, _ := template.New("version").Parse(versionTemplate) + tmpl.Execute(&doc, vo) + return doc.String() +}