diff --git a/framework/command/kernel.go b/framework/command/kernel.go index 82e3c8e..8d90f40 100644 --- a/framework/command/kernel.go +++ b/framework/command/kernel.go @@ -17,6 +17,7 @@ func AddKernelCommands(root *cobra.Command) { root.AddCommand(initProviderCommand()) root.AddCommand(initCmdCommand()) root.AddCommand(initMiddlewareCommand()) + root.AddCommand(initNewCommand()) } // 封装通用的命令执行器 diff --git a/framework/command/new.go b/framework/command/new.go new file mode 100644 index 0000000..3fea24c --- /dev/null +++ b/framework/command/new.go @@ -0,0 +1,193 @@ +package command + +import ( + "bytes" + "context" + "fmt" + "github.com/AlecAivazis/survey/v2" + "github.com/google/go-github/v39/github" + "github.com/spf13/cast" + "io/ioutil" + "os" + "path" + "path/filepath" + "strings" + "time" + + "github.com/Superdanda/hade/framework/cobra" + "github.com/Superdanda/hade/framework/util" +) + +// new相关的名称 +func initNewCommand() *cobra.Command { + return newCommand +} + +// 创建一个新应用 +var newCommand = &cobra.Command{ + Use: "new", + Aliases: []string{"create", "init"}, + Short: "创建一个新的应用", + RunE: func(c *cobra.Command, args []string) error { + currentPath := util.GetExecDirectory() + + var name string + var folder string + var mod string + var version string + var release *github.RepositoryRelease + { + prompt := &survey.Input{ + Message: "请输入目录名称:", + } + err := survey.AskOne(prompt, &name) + if err != nil { + return err + } + + folder = filepath.Join(currentPath, name) + if util.Exists(folder) { + isForce := false + prompt2 := &survey.Confirm{ + Message: "目录" + folder + "已经存在,是否删除重新创建?(确认后立刻执行删除操作!)", + Default: false, + } + err := survey.AskOne(prompt2, &isForce) + if err != nil { + return err + } + + if isForce { + if err := os.RemoveAll(folder); err != nil { + return err + } + } else { + fmt.Println("目录已存在,创建应用失败") + return nil + } + } + } + { + prompt := &survey.Input{ + Message: "请输入模块名称(go.mod中的module, 默认为文件夹名称):", + } + err := survey.AskOne(prompt, &mod) + if err != nil { + return err + } + if mod == "" { + mod = name + } + } + { + // 获取hade的版本 + client := github.NewClient(nil) + prompt := &survey.Input{ + Message: "请输入版本名称(参考 https://github.com/Superdanda/hade/releases,默认为最新版本):", + } + err := survey.AskOne(prompt, &version) + if err != nil { + return err + } + if version != "" { + // 确认版本是否正确 + release, _, err = client.Repositories.GetReleaseByTag(context.Background(), "Superdanda", "hade", version) + if err != nil || release == nil { + fmt.Println("版本不存在,创建应用失败,请参考 https://github.com/Superdanda/hade/releases") + return nil + } + } + if version == "" { + release, _, err = client.Repositories.GetLatestRelease(context.Background(), "gohade", "hade") + version = release.GetTagName() + } + } + fmt.Println("====================================================") + fmt.Println("开始进行创建应用操作") + fmt.Println("创建目录:", folder) + fmt.Println("应用名称:", mod) + fmt.Println("hade框架版本:", release.GetTagName()) + + templateFolder := filepath.Join(currentPath, "template-hade-"+version+"-"+cast.ToString(time.Now().Unix())) + os.Mkdir(templateFolder, os.ModePerm) + fmt.Println("创建临时目录", templateFolder) + + // 拷贝template项目 + url := release.GetZipballURL() + err := util.DownloadFile(filepath.Join(templateFolder, "template.zip"), url) + if err != nil { + return err + } + fmt.Println("下载zip包到template.zip") + + _, err = util.Unzip(filepath.Join(templateFolder, "template.zip"), templateFolder) + if err != nil { + return err + } + + // 获取folder下的gohade-hade-xxx相关解压目录 + fInfos, err := ioutil.ReadDir(templateFolder) + if err != nil { + return err + } + for _, fInfo := range fInfos { + // 找到解压后的文件夹 + if fInfo.IsDir() && strings.Contains(fInfo.Name(), "gohade-hade-") { + if err := os.Rename(filepath.Join(templateFolder, fInfo.Name()), folder); err != nil { + return err + } + } + } + fmt.Println("解压zip包") + + if err := os.RemoveAll(templateFolder); err != nil { + return err + } + fmt.Println("删除临时文件夹", templateFolder) + + os.RemoveAll(path.Join(folder, ".git")) + fmt.Println("删除.git目录") + + // 删除framework 目录 + os.RemoveAll(path.Join(folder, "framework")) + fmt.Println("删除framework目录") + + filepath.Walk(folder, func(path string, info os.FileInfo, err error) error { + if info.IsDir() { + return nil + } + + c, err := ioutil.ReadFile(path) + if err != nil { + return err + } + + if path == filepath.Join(folder, "go.mod") { + fmt.Println("更新文件:" + path) + c = bytes.ReplaceAll(c, []byte("module github.com/Superdanda/hade"), []byte("module "+mod)) + c = bytes.ReplaceAll(c, []byte("require ("), []byte("require (\n\tgithub.com/Superdanda/hade "+version)) + err = ioutil.WriteFile(path, c, 0644) + if err != nil { + return err + } + return nil + } + + isContain := bytes.Contains(c, []byte("github.com/Superdanda/hade/app")) + if isContain { + fmt.Println("更新文件:" + path) + c = bytes.ReplaceAll(c, []byte("github.com/Superdanda/hade/app"), []byte(mod+"/app")) + err = ioutil.WriteFile(path, c, 0644) + if err != nil { + return err + } + } + + return nil + }) + fmt.Println("创建应用结束") + fmt.Println("目录:", folder) + fmt.Println("====================================================") + return nil + }, +} diff --git a/framework/util/zip.go b/framework/util/zip.go index c7d8682..6a53994 100644 --- a/framework/util/zip.go +++ b/framework/util/zip.go @@ -1 +1,68 @@ package util + +import ( + "archive/zip" + "fmt" + "io" + "os" + "path/filepath" + "strings" +) + +// Unzip will decompress a zip archive, moving all files and folders +// within the zip file (parameter 1) to an output directory (parameter 2). +func Unzip(src string, dest string) ([]string, error) { + + var filenames []string + + r, err := zip.OpenReader(src) + if err != nil { + return filenames, err + } + defer r.Close() + + for _, f := range r.File { + + // Store filename/path for returning and using later on + fpath := filepath.Join(dest, f.Name) + + // Check for ZipSlip. More Info: http://bit.ly/2MsjAWE + if !strings.HasPrefix(fpath, filepath.Clean(dest)+string(os.PathSeparator)) { + return filenames, fmt.Errorf("%s: illegal file path", fpath) + } + + filenames = append(filenames, fpath) + + if f.FileInfo().IsDir() { + // Make Folder + os.MkdirAll(fpath, os.ModePerm) + continue + } + + // Make File + if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil { + return filenames, err + } + + outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) + if err != nil { + return filenames, err + } + + rc, err := f.Open() + if err != nil { + return filenames, err + } + + _, err = io.Copy(outFile, rc) + + // Close the file without defer to close before next iteration of loop + outFile.Close() + rc.Close() + + if err != nil { + return filenames, err + } + } + return filenames, nil +} diff --git a/go.mod b/go.mod index 75b8d9c..3646d6e 100644 --- a/go.mod +++ b/go.mod @@ -27,6 +27,8 @@ require ( github.com/go-git/go-billy/v5 v5.5.0 // indirect github.com/go-git/go-git/v5 v5.12.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/google/go-github/v39 v39.2.0 // indirect + github.com/google/go-querystring v1.1.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jonboulle/clockwork v0.4.0 // indirect github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect diff --git a/go.sum b/go.sum index 815a774..af06e1c 100644 --- a/go.sum +++ b/go.sum @@ -60,9 +60,17 @@ github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-github/v39 v39.2.0 h1:rNNM311XtPOz5rDdsJXAp2o8F67X9FnROXTvto3aSnQ= +github.com/google/go-github/v39 v39.2.0/go.mod h1:C1s8C5aCC9L+JXIYpJM5GYytdX52vC1bLvHEF1IhBrE= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -169,6 +177,7 @@ golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUu golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= @@ -179,6 +188,7 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -188,6 +198,7 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -215,6 +226,7 @@ golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= @@ -230,6 +242,8 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/subject_controller.go b/subject_controller.go deleted file mode 100644 index 5d173aa..0000000 --- a/subject_controller.go +++ /dev/null @@ -1,24 +0,0 @@ -package main - -import ( - "github.com/Superdanda/hade/app/provider/demo" - "github.com/Superdanda/hade/framework/gin" -) - -func SubjectDelController(c *gin.Context) { - c.ISetOkStatus().IJson("ok SubjectDelController") -} - -func SubjectUpdateController(c *gin.Context) { - c.ISetOkStatus().IJson("ok SubjectUpdateController") -} - -func SubjectGetController(c *gin.Context) { - c.ISetOkStatus().IJson("ok SubjectGetController") -} - -func SubjectListController(c *gin.Context) { - demoService := c.MustMake(demo.Key).(demo.Service) - foo := demoService.GetFoo() - c.ISetOkStatus().IJson(foo) -} diff --git a/user_controller.go b/user_controller.go deleted file mode 100644 index 58e0ea4..0000000 --- a/user_controller.go +++ /dev/null @@ -1,41 +0,0 @@ -package main - -import ( - "context" - "fmt" - "github.com/Superdanda/hade/framework/gin" - "net/http" - "time" -) - -func FooControllerHandler(ctx *gin.Context) error { - finish := make(chan struct{}, 1) - panicChan := make(chan interface{}, 1) - - durationCtx, cancel := context.WithTimeout(ctx.BaseContext(), 2*time.Second) // 这里记得当所有事情处理结束后调用 cancel,告知 durationCtx 的后续 Context 结束 - defer cancel() - - go func() { - time.Sleep(1 * time.Second) - ctx.ISetOkStatus().IJson(map[string]interface{}{"code": 0}) - finish <- struct{}{} - }() - - select { - case <-finish: - fmt.Println("调用结束了") - case <-durationCtx.Done(): - ctx.ISetStatus(http.StatusInternalServerError).IJson("time out") - case <-panicChan: - ctx.ISetStatus(http.StatusInternalServerError).IJson("panic") - } - return nil -} - -func UserLoginController(c *gin.Context) { - foo, _ := c.DefaultQueryString("foo", "def") - // 等待10s才结束执行 - time.Sleep(10 * time.Second) - // 输出结果 - c.ISetOkStatus().IJson("ok, UserLoginController: " + foo) -}