From 344798163b81f65844668f1d9cfc8e5c3f249c64 Mon Sep 17 00:00:00 2001 From: Rogee Date: Thu, 11 Sep 2025 19:14:44 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=87=8D=E6=9E=84=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E6=B8=B2=E6=9F=93=E9=80=BB=E8=BE=91=EF=BC=8C=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E6=9E=84=E5=BB=BA=E6=B8=B2=E6=9F=93=E6=95=B0=E6=8D=AE=E5=92=8C?= =?UTF-8?q?=E6=A8=A1=E6=9D=BF=E6=B8=B2=E6=9F=93=E5=8A=9F=E8=83=BD=EF=BC=8C?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/ast/route/builder.go | 122 +++++++++++++++++++++++++++++++++ pkg/ast/route/render.go | 130 ++++++++---------------------------- pkg/ast/route/renderer.go | 23 +++++++ pkg/ast/route/router.go.tpl | 3 +- 4 files changed, 174 insertions(+), 104 deletions(-) create mode 100644 pkg/ast/route/builder.go create mode 100644 pkg/ast/route/renderer.go diff --git a/pkg/ast/route/builder.go b/pkg/ast/route/builder.go new file mode 100644 index 0000000..31cc4d2 --- /dev/null +++ b/pkg/ast/route/builder.go @@ -0,0 +1,122 @@ +package route + +import ( + "fmt" + "sort" + + "github.com/iancoleman/strcase" + "github.com/samber/lo" +) + +type RenderBuildOpts struct { + PackageName string + ProjectPackage string + Routes []RouteDefinition +} + +func buildRenderData(opts RenderBuildOpts) (RenderData, error) { + rd := RenderData{ + PackageName: opts.PackageName, + ProjectPackage: opts.ProjectPackage, + Imports: []string{}, + Controllers: []string{}, + Routes: make(map[string][]Router), + RouteGroups: []string{}, + } + + imports := []string{} + controllers := []string{} + + for _, route := range opts.Routes { + imports = append(imports, route.Imports...) + controllers = append(controllers, fmt.Sprintf("%s *%s", strcase.ToLowerCamel(route.Name), route.Name)) + + for _, action := range route.Actions { + funcName := fmt.Sprintf("Func%d", len(action.Params)) + if action.HasData { + funcName = "Data" + funcName + } + + params := lo.FilterMap(action.Params, func(item ParamDefinition, _ int) (string, bool) { + tok := buildParamToken(item) + if tok == "" { + return "", false + } + return tok, true + }) + + rd.Routes[route.Name] = append(rd.Routes[route.Name], Router{ + Method: strcase.ToCamel(action.Method), + Route: action.Route, + Controller: strcase.ToLowerCamel(route.Name), + Action: action.Name, + Func: funcName, + Params: params, + }) + } + } + + // de-dup and sort imports/controllers for stable output + rd.Imports = lo.Uniq(imports) + sort.Strings(rd.Imports) + rd.Controllers = lo.Uniq(controllers) + sort.Strings(rd.Controllers) + + // stable order for route groups and entries + for k := range rd.Routes { + rd.RouteGroups = append(rd.RouteGroups, k) + } + sort.Strings(rd.RouteGroups) + for _, k := range rd.RouteGroups { + items := rd.Routes[k] + sort.Slice(items, func(i, j int) bool { + if items[i].Method != items[j].Method { + return items[i].Method < items[j].Method + } + if items[i].Route != items[j].Route { + return items[i].Route < items[j].Route + } + return items[i].Action < items[j].Action + }) + rd.Routes[k] = items + } + + return rd, nil +} + +func buildParamToken(item ParamDefinition) string { + key := item.Name + if item.Key != "" { + key = item.Key + } + + switch item.Position { + case PositionQuery: + return fmt.Sprintf(`Query%s[%s]("%s")`, scalarSuffix(item.Type), item.Type, key) + case PositionHeader: + return fmt.Sprintf(`Header[%s]("%s")`, item.Type, key) + case PositionFile: + return fmt.Sprintf(`File[multipart.FileHeader]("%s")`, key) + case PositionCookie: + if item.Type == "string" { + return fmt.Sprintf(`CookieParam("%s")`, key) + } + return fmt.Sprintf(`Cookie[%s]("%s")`, item.Type, key) + case PositionBody: + return fmt.Sprintf(`Body[%s]("%s")`, item.Type, key) + case PositionPath: + return fmt.Sprintf(`Path%s[%s]("%s")`, scalarSuffix(item.Type), item.Type, key) + case PositionLocal: + return fmt.Sprintf(`Local[%s]("%s")`, item.Type, key) + } + return "" +} + +func scalarSuffix(t string) string { + switch t { + case "string", "int", "int32", "int64", "float32", "float64", "bool": + return "Param" + } + return "" +} + diff --git a/pkg/ast/route/render.go b/pkg/ast/route/render.go index a9f45d4..fc9a0fd 100644 --- a/pkg/ast/route/render.go +++ b/pkg/ast/route/render.go @@ -1,28 +1,23 @@ package route import ( - "bytes" - _ "embed" - "fmt" - "os" - "path/filepath" - "text/template" + _ "embed" + "os" + "path/filepath" - "github.com/Masterminds/sprig/v3" - "github.com/iancoleman/strcase" - "github.com/samber/lo" - "go.ipao.vip/atomctl/v2/pkg/utils/gomod" + "go.ipao.vip/atomctl/v2/pkg/utils/gomod" ) //go:embed router.go.tpl var routeTpl string type RenderData struct { - PackageName string - ProjectPackage string - Imports []string - Controllers []string - Routes map[string][]Router + PackageName string + ProjectPackage string + Imports []string + Controllers []string + Routes map[string][]Router + RouteGroups []string } type Router struct { @@ -35,95 +30,24 @@ type Router struct { } func Render(path string, routes []RouteDefinition) error { - routePath := filepath.Join(path, "routes.gen.go") + routePath := filepath.Join(path, "routes.gen.go") - tmpl, err := template.New("route").Funcs(sprig.FuncMap()).Parse(routeTpl) - if err != nil { - return err - } + data, err := buildRenderData(RenderBuildOpts{ + PackageName: filepath.Base(path), + ProjectPackage: gomod.GetModuleName(), + Routes: routes, + }) + if err != nil { + return err + } - renderData := RenderData{ - PackageName: filepath.Base(path), - ProjectPackage: gomod.GetModuleName(), - Routes: make(map[string][]Router), - } + out, err := renderTemplate(data) + if err != nil { + return err + } - // collect imports - imports := []string{} - controllers := []string{} - for _, route := range routes { - imports = append(imports, route.Imports...) - controllers = append(controllers, fmt.Sprintf("%s *%s", strcase.ToLowerCamel(route.Name), route.Name)) - for _, action := range route.Actions { - funcName := fmt.Sprintf("Func%d", len(action.Params)) - if action.HasData { - funcName = "Data" + funcName - } - - renderData.Routes[route.Name] = append(renderData.Routes[route.Name], Router{ - Method: strcase.ToCamel(action.Method), - Route: action.Route, - Controller: strcase.ToLowerCamel(route.Name), - Action: action.Name, - Func: funcName, - Params: lo.FilterMap(action.Params, func(item ParamDefinition, _ int) (string, bool) { - key := item.Name - if item.Key != "" { - key = item.Key - } - - switch item.Position { - case PositionQuery: - return fmt.Sprintf(`Query%s[%s]("%s")`, isScalarType(item.Type), item.Type, key), true - case PositionHeader: - return fmt.Sprintf(`Header[%s]("%s")`, item.Type, key), true - case PositionFile: - return fmt.Sprintf(`File[multipart.FileHeader]("%s")`, key), true - case PositionCookie: - if item.Type == "string" { - return fmt.Sprintf(`CookieParam("%s")`, key), true - } - - return fmt.Sprintf(`Cookie[%s]("%s")`, item.Type, key), true - case PositionBody: - return fmt.Sprintf(`Body[%s]("%s")`, item.Type, key), true - case PositionPath: - return fmt.Sprintf(`Path%s[%s]("%s")`, isScalarType(item.Type), item.Type, key), true - case PositionLocal: - return fmt.Sprintf(`Local[%s]("%s")`, item.Type, key), true - } - return "", false - }), - }) - } - } - - renderData.Imports = lo.Uniq(imports) - renderData.Controllers = lo.Uniq(controllers) - - var buf bytes.Buffer - err = tmpl.Execute(&buf, renderData) - if err != nil { - return err - } - - f, err := os.OpenFile(routePath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o644) - if err != nil { - return err - } - defer f.Close() - - _, err = f.Write(buf.Bytes()) - if err != nil { - return err - } - return nil -} - -func isScalarType(t string) string { - switch t { - case "string", "int", "int32", "int64", "float32", "float64", "bool": - return "Param" - } - return "" + if err := os.WriteFile(routePath, out, 0o644); err != nil { + return err + } + return nil } diff --git a/pkg/ast/route/renderer.go b/pkg/ast/route/renderer.go new file mode 100644 index 0000000..0715505 --- /dev/null +++ b/pkg/ast/route/renderer.go @@ -0,0 +1,23 @@ +package route + +import ( + "bytes" + "text/template" + + "github.com/Masterminds/sprig/v3" +) + +var routerTmpl = template.Must(template.New("route"). + Funcs(sprig.FuncMap()). + Option("missingkey=error"). + Parse(routeTpl), +) + +func renderTemplate(data RenderData) ([]byte, error) { + var buf bytes.Buffer + if err := routerTmpl.Execute(&buf, data); err != nil { + return nil, err + } + return buf.Bytes(), nil +} + diff --git a/pkg/ast/route/router.go.tpl b/pkg/ast/route/router.go.tpl index 9899462..2ef7957 100644 --- a/pkg/ast/route/router.go.tpl +++ b/pkg/ast/route/router.go.tpl @@ -31,8 +31,9 @@ func (r *Routes) Name() string { } func (r *Routes) Register(router fiber.Router) { -{{- range $key, $value := .Routes }} +{{- range $key := .RouteGroups }} // 注册路由组: {{$key}} + {{- $value := index $.Routes $key }} {{- range $value }} router.{{.Method}}("{{.Route}}", {{.Func}}( r.{{.Controller}}.{{.Action}},