feat: 重构路由渲染逻辑,添加构建渲染数据和模板渲染功能,优化代码结构

This commit is contained in:
Rogee
2025-09-11 19:14:44 +08:00
parent a23e31fea3
commit 344798163b
4 changed files with 174 additions and 104 deletions

122
pkg/ast/route/builder.go Normal file
View File

@@ -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 ""
}

View File

@@ -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
}

23
pkg/ast/route/renderer.go Normal file
View File

@@ -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
}

View File

@@ -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}},