feat: 重构路由渲染逻辑,添加构建渲染数据和模板渲染功能,优化代码结构
This commit is contained in:
122
pkg/ast/route/builder.go
Normal file
122
pkg/ast/route/builder.go
Normal 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 ""
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,28 +1,23 @@
|
|||||||
package route
|
package route
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
_ "embed"
|
||||||
_ "embed"
|
"os"
|
||||||
"fmt"
|
"path/filepath"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"github.com/Masterminds/sprig/v3"
|
"go.ipao.vip/atomctl/v2/pkg/utils/gomod"
|
||||||
"github.com/iancoleman/strcase"
|
|
||||||
"github.com/samber/lo"
|
|
||||||
"go.ipao.vip/atomctl/v2/pkg/utils/gomod"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed router.go.tpl
|
//go:embed router.go.tpl
|
||||||
var routeTpl string
|
var routeTpl string
|
||||||
|
|
||||||
type RenderData struct {
|
type RenderData struct {
|
||||||
PackageName string
|
PackageName string
|
||||||
ProjectPackage string
|
ProjectPackage string
|
||||||
Imports []string
|
Imports []string
|
||||||
Controllers []string
|
Controllers []string
|
||||||
Routes map[string][]Router
|
Routes map[string][]Router
|
||||||
|
RouteGroups []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Router struct {
|
type Router struct {
|
||||||
@@ -35,95 +30,24 @@ type Router struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Render(path string, routes []RouteDefinition) error {
|
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)
|
data, err := buildRenderData(RenderBuildOpts{
|
||||||
if err != nil {
|
PackageName: filepath.Base(path),
|
||||||
return err
|
ProjectPackage: gomod.GetModuleName(),
|
||||||
}
|
Routes: routes,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
renderData := RenderData{
|
out, err := renderTemplate(data)
|
||||||
PackageName: filepath.Base(path),
|
if err != nil {
|
||||||
ProjectPackage: gomod.GetModuleName(),
|
return err
|
||||||
Routes: make(map[string][]Router),
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// collect imports
|
if err := os.WriteFile(routePath, out, 0o644); err != nil {
|
||||||
imports := []string{}
|
return err
|
||||||
controllers := []string{}
|
}
|
||||||
for _, route := range routes {
|
return nil
|
||||||
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 ""
|
|
||||||
}
|
}
|
||||||
|
|||||||
23
pkg/ast/route/renderer.go
Normal file
23
pkg/ast/route/renderer.go
Normal 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
|
||||||
|
}
|
||||||
|
|
||||||
@@ -31,8 +31,9 @@ func (r *Routes) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Routes) Register(router fiber.Router) {
|
func (r *Routes) Register(router fiber.Router) {
|
||||||
{{- range $key, $value := .Routes }}
|
{{- range $key := .RouteGroups }}
|
||||||
// 注册路由组: {{$key}}
|
// 注册路由组: {{$key}}
|
||||||
|
{{- $value := index $.Routes $key }}
|
||||||
{{- range $value }}
|
{{- range $value }}
|
||||||
router.{{.Method}}("{{.Route}}", {{.Func}}(
|
router.{{.Method}}("{{.Route}}", {{.Func}}(
|
||||||
r.{{.Controller}}.{{.Action}},
|
r.{{.Controller}}.{{.Action}},
|
||||||
|
|||||||
Reference in New Issue
Block a user