Repository: zeromicro/go-zero
Pull request #1388 (2021-12-29 09:44:09)

fix readme-cn
这里的链接应该是迁移文档之后忘了改了？: )
是要改成 https://github.com/zeromicro/zero-doc/blob/main/docs/zero/goctl-api.md 吗？


Patch:
diff --git a/readme-cn.md b/readme-cn.md
index 2bce3716..d92fa2a3 100644
--- a/readme-cn.md
+++ b/readme-cn.md
@@ -141,7 +141,7 @@ GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/tal-tech/
 
     编写业务代码：
 
-      * api 文件定义了服务对外 HTTP 接口，可参考 [api 规范](https://github.com/zeromicro/zero-doc/blob/main/doc/goctl.md)
+      * api 文件定义了服务对外 HTTP 接口，可参考 [api 规范](https://github.com/zeromicro/zero-doc/blob/main/docs/zero/goctl-api.md)
       * 可以在 `servicecontext.go` 里面传递依赖给 logic，比如 mysql, redis 等
       * 在 api 定义的 `get/post/put/delete` 等请求对应的 logic 里增加业务处理逻辑
 
diff --git a/rest/handler/timeouthandler.go b/rest/handler/timeouthandler.go
index 1b9532cf..aab564a3 100644
--- a/rest/handler/timeouthandler.go
+++ b/rest/handler/timeouthandler.go
@@ -13,6 +13,7 @@ import (
 	"sync"
 	"time"
 
+	"github.com/tal-tech/go-zero/rest/httpx"
 	"github.com/tal-tech/go-zero/rest/internal"
 )
 
@@ -91,12 +92,14 @@ func (h *timeoutHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		defer tw.mu.Unlock()
 		// there isn't any user-defined middleware before TimoutHandler,
 		// so we can guarantee that cancelation in biz related code won't come here.
-		if errors.Is(ctx.Err(), context.Canceled) {
-			w.WriteHeader(statusClientClosedRequest)
-		} else {
-			w.WriteHeader(http.StatusServiceUnavailable)
-		}
-		io.WriteString(w, h.errorBody())
+		httpx.Error(w, ctx.Err(), func(w http.ResponseWriter, err error) {
+			if errors.Is(err, context.Canceled) {
+				w.WriteHeader(statusClientClosedRequest)
+			} else {
+				w.WriteHeader(http.StatusServiceUnavailable)
+			}
+			io.WriteString(w, h.errorBody())
+		})
 		tw.timedOut = true
 	}
 }
diff --git a/rest/httpx/responses.go b/rest/httpx/responses.go
index 055f521b..ef1ebc0a 100644
--- a/rest/httpx/responses.go
+++ b/rest/httpx/responses.go
@@ -14,13 +14,17 @@ var (
 )
 
 // Error writes err into w.
-func Error(w http.ResponseWriter, err error) {
+func Error(w http.ResponseWriter, err error, fns ...func(w http.ResponseWriter, err error)) {
 	lock.RLock()
 	handler := errorHandler
 	lock.RUnlock()
 
 	if handler == nil {
-		http.Error(w, err.Error(), http.StatusBadRequest)
+		if len(fns) > 0 {
+			fns[0](w, err)
+		} else {
+			http.Error(w, err.Error(), http.StatusBadRequest)
+		}
 		return
 	}
 
diff --git a/tools/goctl/api/apigen/gen.go b/tools/goctl/api/apigen/gen.go
index 3e669330..fcf352c4 100644
--- a/tools/goctl/api/apigen/gen.go
+++ b/tools/goctl/api/apigen/gen.go
@@ -53,6 +53,14 @@ func ApiCommand(c *cli.Context) error {
 	defer fp.Close()
 
 	home := c.String("home")
+	remote := c.String("remote")
+	if len(remote) > 0 {
+		repo, _ := util.CloneIntoGitHome(remote)
+		if len(repo) > 0 {
+			home = repo
+		}
+	}
+
 	if len(home) > 0 {
 		util.RegisterGoctlHome(home)
 	}
diff --git a/tools/goctl/api/gogen/gen.go b/tools/goctl/api/gogen/gen.go
index 18a6136b..88a9f4f7 100644
--- a/tools/goctl/api/gogen/gen.go
+++ b/tools/goctl/api/gogen/gen.go
@@ -31,6 +31,13 @@ func GoCommand(c *cli.Context) error {
 	dir := c.String("dir")
 	namingStyle := c.String("style")
 	home := c.String("home")
+	remote := c.String("remote")
+	if len(remote) > 0 {
+		repo, _ := util.CloneIntoGitHome(remote)
+		if len(repo) > 0 {
+			home = repo
+		}
+	}
 
 	if len(home) > 0 {
 		util.RegisterGoctlHome(home)
diff --git a/tools/goctl/api/new/newservice.go b/tools/goctl/api/new/newservice.go
index 7ea373cf..9ee6b18b 100644
--- a/tools/goctl/api/new/newservice.go
+++ b/tools/goctl/api/new/newservice.go
@@ -65,6 +65,14 @@ func CreateServiceCommand(c *cli.Context) error {
 	defer fp.Close()
 
 	home := c.String("home")
+	remote := c.String("remote")
+	if len(remote) > 0 {
+		repo, _ := util.CloneIntoGitHome(remote)
+		if len(repo) > 0 {
+			home = repo
+		}
+	}
+
 	if len(home) > 0 {
 		util.RegisterGoctlHome(home)
 	}
diff --git a/tools/goctl/docker/docker.go b/tools/goctl/docker/docker.go
index da40b955..3ffa381b 100644
--- a/tools/goctl/docker/docker.go
+++ b/tools/goctl/docker/docker.go
@@ -44,6 +44,13 @@ func DockerCommand(c *cli.Context) (err error) {
 	goFile := c.String("go")
 	home := c.String("home")
 	version := c.String("version")
+	remote := c.String("remote")
+	if len(remote) > 0 {
+		repo, _ := util.CloneIntoGitHome(remote)
+		if len(repo) > 0 {
+			home = repo
+		}
+	}
 
 	if len(version) > 0 {
 		version = version + "-"
diff --git a/tools/goctl/goctl.go b/tools/goctl/goctl.go
index fc14a334..3b08149d 100644
--- a/tools/goctl/goctl.go
+++ b/tools/goctl/goctl.go
@@ -54,8 +54,15 @@ var commands = []cli.Command{
 				Usage: "the output api file",
 			},
 			cli.StringFlag{
-				Name:  "home",
-				Usage: "the goctl home path of the template",
+				Name: "home",
+				Usage: "the goctl home path of the template, --home and --remote cannot be set at the same time, " +
+					"if they are, --remote has higher priority",
+			},
+			cli.StringFlag{
+				Name: "remote",
+				Usage: "the remote git repo of the template, --home and --remote cannot be set at the same time, " +
+					"if they are, --remote has higher priority\n\tThe git repo directory must be consistent with the " +
+					"https://github.com/zeromicro/go-zero-template directory structure",
 			},
 		},
 		Action: apigen.ApiCommand,
@@ -66,8 +73,15 @@ var commands = []cli.Command{
 				Action: new.CreateServiceCommand,
 				Flags: []cli.Flag{
 					cli.StringFlag{
-						Name:  "home",
-						Usage: "the goctl home path of the template",
+						Name: "home",
+						Usage: "the goctl home path of the template, --home and --remote cannot be set at the same time, " +
+							"if they are, --remote has higher priority",
+					},
+					cli.StringFlag{
+						Name: "remote",
+						Usage: "the remote git repo of the template, --home and --remote cannot be set at the same time, " +
+							"if they are, --remote has higher priority\n\tThe git repo directory must be consistent with the " +
+							"https://github.com/zeromicro/go-zero-template directory structure",
 					},
 					cli.StringFlag{
 						Name:  "style",
@@ -138,8 +152,15 @@ var commands = []cli.Command{
 						Usage: "the file naming format, see [https://github.com/tal-tech/go-zero/tree/master/tools/goctl/config/readme.md]",
 					},
 					cli.StringFlag{
-						Name:  "home",
-						Usage: "the goctl home path of the template",
+						Name: "home",
+						Usage: "the goctl home path of the template, --home and --remote cannot be set at the same time, " +
+							"if they are, --remote has higher priority",
+					},
+					cli.StringFlag{
+						Name: "remote",
+						Usage: "the remote git repo of the template, --home and --remote cannot be set at the same time, " +
+							"if they are, --remote has higher priority\n\tThe git repo directory must be consistent with the " +
+							"https://github.com/zeromicro/go-zero-template directory structure",
 					},
 				},
 				Action: gogen.GoCommand,
@@ -259,8 +280,15 @@ var commands = []cli.Command{
 				Value: 0,
 			},
 			cli.StringFlag{
-				Name:  "home",
-				Usage: "the goctl home path of the template",
+				Name: "home",
+				Usage: "the goctl home path of the template, --home and --remote cannot be set at the same time, " +
+					"if they are, --remote has higher priority",
+			},
+			cli.StringFlag{
+				Name: "remote",
+				Usage: "the remote git repo of the template, --home and --remote cannot be set at the same time, " +
+					"if they are, --remote has higher priority\n\tThe git repo directory must be consistent with the " +
+					"https://github.com/zeromicro/go-zero-template directory structure",
 			},
 			cli.StringFlag{
 				Name:  "version",
@@ -352,8 +380,15 @@ var commands = []cli.Command{
 						Value: 10,
 					},
 					cli.StringFlag{
-						Name:  "home",
-						Usage: "the goctl home path of the template",
+						Name: "home",
+						Usage: "the goctl home path of the template, --home and --remote cannot be set at the same time, " +
+							"if they are, --remote has higher priority",
+					},
+					cli.StringFlag{
+						Name: "remote",
+						Usage: "the remote git repo of the template, --home and --remote cannot be set at the same time, " +
+							"if they are, --remote has higher priority\n\tThe git repo directory must be consistent with the " +
+							"https://github.com/zeromicro/go-zero-template directory structure",
 					},
 				},
 				Action: kube.DeploymentCommand,
@@ -377,8 +412,15 @@ var commands = []cli.Command{
 						Usage: "whether the command execution environment is from idea plugin. [optional]",
 					},
 					cli.StringFlag{
-						Name:  "home",
-						Usage: "the goctl home path of the template",
+						Name: "home",
+						Usage: "the goctl home path of the template, --home and --remote cannot be set at the same time, " +
+							"if they are, --remote has higher priority",
+					},
+					cli.StringFlag{
+						Name: "remote",
+						Usage: "the remote git repo of the template, --home and --remote cannot be set at the same time, " +
+							"if they are, --remote has higher priority\n\tThe git repo directory must be consistent with the " +
+							"https://github.com/zeromicro/go-zero-template directory structure",
 					},
 				},
 				Action: rpc.RPCNew,
@@ -392,8 +434,15 @@ var commands = []cli.Command{
 						Usage: "the target path of proto",
 					},
 					cli.StringFlag{
-						Name:  "home",
-						Usage: "the goctl home path of the template",
+						Name: "home",
+						Usage: "the goctl home path of the template, --home and --remote cannot be set at the same time," +
+							" if they are, --remote has higher priority",
+					},
+					cli.StringFlag{
+						Name: "remote",
+						Usage: "the remote git repo of the template, --home and --remote cannot be set at the same time, " +
+							"if they are, --remote has higher priority\n\tThe git repo directory must be consistent with the " +
+							"https://github.com/zeromicro/go-zero-template directory structure",
 					},
 				},
 				Action: rpc.RPCTemplate,
@@ -427,8 +476,15 @@ var commands = []cli.Command{
 						Usage: "whether the command execution environment is from idea plugin. [optional]",
 					},
 					cli.StringFlag{
-						Name:  "home",
-						Usage: "the goctl home path of the template",
+						Name: "home",
+						Usage: "the goctl home path of the template, --home and --remote cannot be set at the same time, " +
+							"if they are, --remote has higher priority",
+					},
+					cli.StringFlag{
+						Name: "remote",
+						Usage: "the remote git repo of the template, --home and --remote cannot be set at the same time, " +
+							"if they are, --remote has higher priority\n\tThe git repo directory must be consistent with the " +
+							"https://github.com/zeromicro/go-zero-template directory structure",
 					},
 				},
 				Action: rpc.RPC,
@@ -472,8 +528,15 @@ var commands = []cli.Command{
 								Usage: "the name of database [optional]",
 							},
 							cli.StringFlag{
-								Name:  "home",
-								Usage: "the goctl home path of the template",
+								Name: "home",
+								Usage: "the goctl home path of the template, --home and --remote cannot be set at the same time, " +
+									"if they are, --remote has higher priority",
+							},
+							cli.StringFlag{
+								Name: "remote",
+								Usage: "the remote git repo of the template, --home and --remote cannot be set at the same time, " +
+									"if they are, --remote has higher priority\n\tThe git repo directory must be consistent with the " +
+									"https://github.com/zeromicro/go-zero-template directory structure",
 							},
 						},
 						Action: model.MysqlDDL,
@@ -507,8 +570,15 @@ var commands = []cli.Command{
 								Usage: "for idea plugin [optional]",
 							},
 							cli.StringFlag{
-								Name:  "home",
-								Usage: "the goctl home path of the template",
+								Name: "home",
+								Usage: "the goctl home path of the template, --home and --remote cannot be set at the same time, " +
+									"if they are, --remote has higher priority",
+							},
+							cli.StringFlag{
+								Name: "remote",
+								Usage: "the remote git repo of the template, --home and --remote cannot be set at the same time, " +
+									"if they are, --remote has higher priority\n\tThe git repo directory must be consistent with the " +
+									"https://github.com/zeromicro/go-zero-template directory structure",
 							},
 						},
 						Action: model.MySqlDataSource,
@@ -552,8 +622,15 @@ var commands = []cli.Command{
 								Usage: "for idea plugin [optional]",
 							},
 							cli.StringFlag{
-								Name:  "home",
-								Usage: "the goctl home path of the template",
+								Name: "home",
+								Usage: "the goctl home path of the template, --home and --remote cannot be set at the same time, " +
+									"if they are, --remote has higher priority",
+							},
+							cli.StringFlag{
+								Name: "remote",
+								Usage: "the remote git repo of the template, --home and --remote cannot be set at the same time, " +
+									"if they are, --remote has higher priority\n\tThe git repo directory must be consistent with the " +
+									"https://github.com/zeromicro/go-zero-template directory structure",
 							},
 						},
 						Action: model.PostgreSqlDataSource,
@@ -581,8 +658,15 @@ var commands = []cli.Command{
 						Usage: "the file naming format, see [https://github.com/tal-tech/go-zero/tree/master/tools/goctl/config/readme.md]",
 					},
 					cli.StringFlag{
-						Name:  "home",
-						Usage: "the goctl home path of the template",
+						Name: "home",
+						Usage: "the goctl home path of the template, --home and --remote cannot be set at the same time," +
+							" if they are, --remote has higher priority",
+					},
+					cli.StringFlag{
+						Name: "remote",
+						Usage: "the remote git repo of the template, --home and --remote cannot be set at the same time, " +
+							"if they are, --remote has higher priority\n\tThe git repo directory must be consistent with the " +
+							"https://github.com/zeromicro/go-zero-template directory structure",
 					},
 				},
 				Action: mongo.Action,
diff --git a/tools/goctl/kube/kube.go b/tools/goctl/kube/kube.go
index dccba392..421a3926 100644
--- a/tools/goctl/kube/kube.go
+++ b/tools/goctl/kube/kube.go
@@ -41,6 +41,13 @@ type Deployment struct {
 func DeploymentCommand(c *cli.Context) error {
 	nodePort := c.Int("nodePort")
 	home := c.String("home")
+	remote := c.String("remote")
+	if len(remote) > 0 {
+		repo, _ := util.CloneIntoGitHome(remote)
+		if len(repo) > 0 {
+			home = repo
+		}
+	}
 
 	if len(home) > 0 {
 		util.RegisterGoctlHome(home)
diff --git a/tools/goctl/model/mongo/mongo.go b/tools/goctl/model/mongo/mongo.go
index 3f77acd9..8bf93928 100644
--- a/tools/goctl/model/mongo/mongo.go
+++ b/tools/goctl/model/mongo/mongo.go
@@ -18,7 +18,13 @@ func Action(ctx *cli.Context) error {
 	o := strings.TrimSpace(ctx.String("dir"))
 	s := ctx.String("style")
 	home := ctx.String("home")
-
+	remote := ctx.String("remote")
+	if len(remote) > 0 {
+		repo, _ := file.CloneIntoGitHome(remote)
+		if len(repo) > 0 {
+			home = repo
+		}
+	}
 	if len(home) > 0 {
 		file.RegisterGoctlHome(home)
 	}
diff --git a/tools/goctl/model/sql/command/command.go b/tools/goctl/model/sql/command/command.go
index 5f3554d4..2fae4f0c 100644
--- a/tools/goctl/model/sql/command/command.go
+++ b/tools/goctl/model/sql/command/command.go
@@ -42,7 +42,13 @@ func MysqlDDL(ctx *cli.Context) error {
 	style := ctx.String(flagStyle)
 	database := ctx.String(flagDatabase)
 	home := ctx.String(flagHome)
-
+	remote := ctx.String("remote")
+	if len(remote) > 0 {
+		repo, _ := file.CloneIntoGitHome(remote)
+		if len(repo) > 0 {
+			home = repo
+		}
+	}
 	if len(home) > 0 {
 		file.RegisterGoctlHome(home)
 	}
@@ -62,7 +68,13 @@ func MySqlDataSource(ctx *cli.Context) error {
 	idea := ctx.Bool(flagIdea)
 	style := ctx.String(flagStyle)
 	home := ctx.String("home")
-
+	remote := ctx.String("remote")
+	if len(remote) > 0 {
+		repo, _ := file.CloneIntoGitHome(remote)
+		if len(repo) > 0 {
+			home = repo
+		}
+	}
 	if len(home) > 0 {
 		file.RegisterGoctlHome(home)
 	}
@@ -85,7 +97,13 @@ func PostgreSqlDataSource(ctx *cli.Context) error {
 	style := ctx.String(flagStyle)
 	schema := ctx.String(flagSchema)
 	home := ctx.String("home")
-
+	remote := ctx.String("remote")
+	if len(remote) > 0 {
+		repo, _ := file.CloneIntoGitHome(remote)
+		if len(repo) > 0 {
+			home = repo
+		}
+	}
 	if len(home) > 0 {
 		file.RegisterGoctlHome(home)
 	}
diff --git a/tools/goctl/rpc/cli/cli.go b/tools/goctl/rpc/cli/cli.go
index 46ec562b..99da817b 100644
--- a/tools/goctl/rpc/cli/cli.go
+++ b/tools/goctl/rpc/cli/cli.go
@@ -26,7 +26,13 @@ func RPC(c *cli.Context) error {
 	protoImportPath := c.StringSlice("proto_path")
 	goOptions := c.StringSlice("go_opt")
 	home := c.String("home")
-
+	remote := c.String("remote")
+	if len(remote) > 0 {
+		repo, _ := util.CloneIntoGitHome(remote)
+		if len(repo) > 0 {
+			home = repo
+		}
+	}
 	if len(home) > 0 {
 		util.RegisterGoctlHome(home)
 	}
@@ -73,7 +79,13 @@ func RPCNew(c *cli.Context) error {
 	}
 	style := c.String("style")
 	home := c.String("home")
-
+	remote := c.String("remote")
+	if len(remote) > 0 {
+		repo, _ := util.CloneIntoGitHome(remote)
+		if len(repo) > 0 {
+			home = repo
+		}
+	}
 	if len(home) > 0 {
 		util.RegisterGoctlHome(home)
 	}
@@ -102,7 +114,13 @@ func RPCNew(c *cli.Context) error {
 func RPCTemplate(c *cli.Context) error {
 	protoFile := c.String("o")
 	home := c.String("home")
-
+	remote := c.String("remote")
+	if len(remote) > 0 {
+		repo, _ := util.CloneIntoGitHome(remote)
+		if len(repo) > 0 {
+			home = repo
+		}
+	}
 	if len(home) > 0 {
 		util.RegisterGoctlHome(home)
 	}
diff --git a/tools/goctl/util/file.go b/tools/goctl/util/file.go
index af522054..3f7bf9a8 100644
--- a/tools/goctl/util/file.go
+++ b/tools/goctl/util/file.go
@@ -17,6 +17,7 @@ import (
 const (
 	NL       = "\n"
 	goctlDir = ".goctl"
+	gitDir   = ".git"
 )
 
 var goctlHome string
@@ -82,6 +83,16 @@ func GetGoctlHome() (string, error) {
 	return filepath.Join(home, goctlDir), nil
 }
 
+// GetGitHome returns the git home of goctl.
+func GetGitHome() (string, error) {
+	goctlH, err := GetGoctlHome()
+	if err != nil {
+		return "", err
+	}
+
+	return filepath.Join(goctlH, gitDir), nil
+}
+
 // GetTemplateDir returns the category path value in GoctlHome where could get it by GetGoctlHome
 func GetTemplateDir(category string) (string, error) {
 	goctlHome, err := GetGoctlHome()
diff --git a/tools/goctl/util/git.go b/tools/goctl/util/git.go
new file mode 100644
index 00000000..ef457f9a
--- /dev/null
+++ b/tools/goctl/util/git.go
@@ -0,0 +1,36 @@
+package util
+
+import (
+	"fmt"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"runtime"
+	"strings"
+
+	"github.com/tal-tech/go-zero/tools/goctl/util/env"
+)
+
+func CloneIntoGitHome(url string) (dir string, err error) {
+	gitHome, err := GetGitHome()
+	if err != nil {
+		return "", err
+	}
+	os.RemoveAll(gitHome)
+	ext := filepath.Ext(url)
+	repo := strings.TrimSuffix(filepath.Base(url), ext)
+	dir = filepath.Join(gitHome, repo)
+	path, err := env.LookPath("git")
+	if err != nil {
+		return "", err
+	}
+	if !env.CanExec() {
+		return "", fmt.Errorf("os %q can not call 'exec' command", runtime.GOOS)
+	}
+	cmd := exec.Command(path, "clone", url, dir)
+	cmd.Env = os.Environ()
+	cmd.Stdout = os.Stdout
+	cmd.Stderr = os.Stderr
+	err = cmd.Run()
+	return
+}


Test patch:
diff --git a/rest/httpx/responses_test.go b/rest/httpx/responses_test.go
index adb94c79..17d14d66 100644
--- a/rest/httpx/responses_test.go
+++ b/rest/httpx/responses_test.go
@@ -95,6 +95,18 @@ func TestError(t *testing.T) {
 	}
 }
 
+func TestErrorWithHandler(t *testing.T) {
+	w := tracedResponseWriter{
+		headers: make(map[string][]string),
+	}
+	Error(&w, errors.New("foo"), func(w http.ResponseWriter, err error) {
+		http.Error(w, err.Error(), 499)
+	})
+	assert.Equal(t, 499, w.code)
+	assert.True(t, w.hasBody)
+	assert.Equal(t, "foo", strings.TrimSpace(w.builder.String()))
+}
+
 func TestOk(t *testing.T) {
 	w := tracedResponseWriter{
 		headers: make(map[string][]string),
diff --git a/tools/goctl/util/file_test.go b/tools/goctl/util/file_test.go
new file mode 100644
index 00000000..b0927f07
--- /dev/null
+++ b/tools/goctl/util/file_test.go
@@ -0,0 +1,23 @@
+package util
+
+import (
+	"os"
+	"path/filepath"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestGetGitHome(t *testing.T) {
+	homeDir, err := os.UserHomeDir()
+	if err != nil {
+		return
+	}
+	actual, err := GetGitHome()
+	if err != nil {
+		return
+	}
+
+	expected := filepath.Join(homeDir, goctlDir, gitDir)
+	assert.Equal(t, expected, actual)
+}