gr_hz/generator/router.go
huangqz a407e10ebe 修复批量 update 多个 idl 时只生成最后一个 handler/router 的问题
原 protobuf 插件只取 FileToGenerate 的最后一个 idl 生成 http package,导致 hz_gen
批量脚本一次传入多个 --idl 时,除最后一个外其余 proto 只生成 model、不生成 handler/router。

- protobuf/plugin.go: Handle() 改为遍历全部 FileToGenerate;每个 idl 生成后立即写盘,
  使后续 idl 的 register.go/middleware.go/handler 合并能读到累积内容,保证多个新服务
  同批生成不互相覆盖注册。
- util/data.go: 新增 ResetUniqueNameSets()
- generator/router.go: 新增 ResetRouterState()
  每个 idl 生成前重置进程级命名状态,使单进程批量输出与逐文件生成逐字节一致,
  避免中间件分组变量名带全局序号后缀(_v1->_v184)造成大面积无谓 diff。
2026-06-16 18:26:57 +08:00

480 lines
13 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright 2022 CloudWeGo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package generator
import (
"bytes"
"fmt"
"io/ioutil"
"path/filepath"
"regexp"
"sort"
"strings"
"golib.gaore.com/GaoreGo/gr_hz/util"
)
type Router struct {
FilePath string
PackageName string
HandlerPackages map[string]string // {{basename}}:{{import_path}}
Router *RouterNode
IdlName string
}
type RouterNode struct {
GroupName string // current group name(the parent middleware name), used to register route. example: {{.GroupName}}.{{HttpMethod}}
MiddleWare string // current node middleware, used to be group name for children.
HandlerMiddleware string
GroupMiddleware string
PathPrefix string
Path string
Parent *RouterNode
Children childrenRouterInfo
Handler string // {{HandlerPackage}}.{{HandlerName}}
HandlerPackage string
HandlerPackageAlias string
HttpMethod string
}
type RegisterInfo struct {
PackageName string
DepPkgAlias string
DepPkg string
RegisterName string
}
// NewRouterTree contains "/" as root node
func NewRouterTree() *RouterNode {
return &RouterNode{
GroupName: "root",
MiddleWare: "root",
GroupMiddleware: "root",
Path: "/",
Parent: nil,
}
}
func (routerNode *RouterNode) Sort() {
sort.Sort(routerNode.Children)
}
func (routerNode *RouterNode) Update(method *HttpMethod, handlerType, handlerPkg string) error {
if method.Path == "" {
return fmt.Errorf("empty path for method '%s'", method.Name)
}
paths := strings.Split(method.Path, "/")
if paths[0] == "" {
paths = paths[1:]
}
parent, last := routerNode.FindNearest(paths)
if last == len(paths) {
return fmt.Errorf("path '%s' has been registered", method.Path)
}
name := util.ToVarName(paths[:last])
parent.Insert(name, method, handlerType, paths[last:], handlerPkg)
parent.Sort()
return nil
}
func (routerNode *RouterNode) RawHandlerName() string {
parts := strings.Split(routerNode.Handler, ".")
handlerName := parts[len(parts)-1]
return handlerName
}
// DyeGroupName traverses the routing tree in depth and names the handler/group middleware for each node.
// If snakeStyleMiddleware is set to true, the name style of the middleware will use snake name style.
func (routerNode *RouterNode) DyeGroupName(snakeStyleMiddleware bool) error {
groups := []string{"root"}
hook := func(layer int, node *RouterNode) error {
node.GroupName = groups[layer]
if node.MiddleWare == "" {
pname := node.Path
if len(pname) > 1 && pname[0] == '/' {
pname = pname[1:]
}
if node.Parent != nil {
node.PathPrefix = node.Parent.PathPrefix + "_" + util.ToGoFuncName(pname)
} else {
node.PathPrefix = "_" + util.ToGoFuncName(pname)
}
handlerMiddlewareName := ""
isLeafNode := false
if len(node.Handler) != 0 {
handlerMiddlewareName = node.RawHandlerName()
// If it is a leaf node, then "group middleware name" and "handler middleware name" are the same
if len(node.Children) == 0 {
pname = handlerMiddlewareName
isLeafNode = true
}
}
pname = convertToMiddlewareName(pname)
handlerMiddlewareName = convertToMiddlewareName(handlerMiddlewareName)
if isLeafNode {
name, err := util.GetMiddlewareUniqueName(pname)
if err != nil {
return fmt.Errorf("get unique name for middleware '%s' failed, err: %v", name, err)
}
pname = name
handlerMiddlewareName = name
} else {
var err error
pname, err = util.GetMiddlewareUniqueName(pname)
if err != nil {
return fmt.Errorf("get unique name for middleware '%s' failed, err: %v", pname, err)
}
handlerMiddlewareName, err = util.GetMiddlewareUniqueName(handlerMiddlewareName)
if err != nil {
return fmt.Errorf("get unique name for middleware '%s' failed, err: %v", handlerMiddlewareName, err)
}
}
node.MiddleWare = "_" + pname
if len(node.Handler) != 0 {
node.HandlerMiddleware = "_" + handlerMiddlewareName
if snakeStyleMiddleware {
node.HandlerMiddleware = "_" + node.RawHandlerName()
}
}
node.GroupMiddleware = node.MiddleWare
if snakeStyleMiddleware {
node.GroupMiddleware = node.PathPrefix
}
}
if layer >= len(groups)-1 {
groups = append(groups, node.MiddleWare)
} else {
groups[layer+1] = node.MiddleWare
}
return nil
}
// Deep traversal from the 0th level of the routing tree.
err := routerNode.DFS(0, hook)
return err
}
func (routerNode *RouterNode) DFS(i int, hook func(layer int, node *RouterNode) error) error {
if routerNode == nil {
return nil
}
err := hook(i, routerNode)
if err != nil {
return err
}
for _, n := range routerNode.Children {
err = n.DFS(i+1, hook)
if err != nil {
return err
}
}
return nil
}
var handlerPkgMap map[string]string
// ResetRouterState 清空路由生成的进程级累积状态handler 包别名映射)。
// 与 util.ResetUniqueNameSets 配合,使「单进程循环处理多个 idl」时每个 idl 的
// 命名都从干净状态起算,结果与旧版「逐文件单进程」生成完全一致。
func ResetRouterState() {
handlerPkgMap = nil
}
func (routerNode *RouterNode) Insert(name string, method *HttpMethod, handlerType string, paths []string, handlerPkg string) {
cur := routerNode
for i, p := range paths {
c := &RouterNode{
Path: "/" + p,
Parent: cur,
}
if i == len(paths)-1 {
// generate handler by method
if len(handlerPkg) != 0 {
// get a unique package alias for every handler
pkgAlias := filepath.Base(handlerPkg)
pkgAlias = util.ToVarName([]string{pkgAlias})
val, exist := handlerPkgMap[handlerPkg]
if !exist {
pkgAlias, _ = util.GetHandlerPackageUniqueName(pkgAlias)
if len(handlerPkgMap) == 0 {
handlerPkgMap = make(map[string]string, 10)
}
handlerPkgMap[handlerPkg] = pkgAlias
} else {
pkgAlias = val
}
c.HandlerPackageAlias = pkgAlias
c.Handler = pkgAlias + "." + method.Name
c.HandlerPackage = handlerPkg
method.RefPackage = c.HandlerPackage
method.RefPackageAlias = c.HandlerPackageAlias
} else { // generate handler by service
c.Handler = handlerType + "." + method.Name
}
c.HttpMethod = getHttpMethod(method.HTTPMethod)
}
if cur.Children == nil {
cur.Children = make([]*RouterNode, 0, 1)
}
cur.Children = append(cur.Children, c)
cur = c
}
}
func getHttpMethod(method string) string {
if strings.EqualFold(method, "Any") {
return "Any"
}
return strings.ToUpper(method)
}
func (routerNode *RouterNode) FindNearest(paths []string) (*RouterNode, int) {
ns := len(paths)
cur := routerNode
i := 0
path := paths[i]
for j := 0; j < len(cur.Children); j++ {
c := cur.Children[j]
if ("/" + path) == c.Path {
i++
if i == ns {
return cur, i - 1
}
path = paths[i]
cur = c
j = -1
}
}
return cur, i
}
type childrenRouterInfo []*RouterNode
// Len is the number of elements in the collection.
func (c childrenRouterInfo) Len() int {
return len(c)
}
// Less reports whether the element with
// index i should sort before the element with index j.
func (c childrenRouterInfo) Less(i, j int) bool {
ci := c[i].Path
if len(c[i].Children) != 0 {
ci = ci[1:]
}
cj := c[j].Path
if len(c[j].Children) != 0 {
cj = cj[1:]
}
return ci < cj
}
// Swap swaps the elements with indexes i and j.
func (c childrenRouterInfo) Swap(i, j int) {
c[i], c[j] = c[j], c[i]
}
var (
regRegisterV3 = regexp.MustCompile(insertPointPatternNew)
regImport = regexp.MustCompile(`import \(\n`)
)
func (pkgGen *HttpPackageGenerator) updateRegister(pkg, rDir, pkgName string, idlName string) error {
if pkgGen.tplsInfo[registerTplName].Disable {
return nil
}
register := RegisterInfo{
PackageName: filepath.Base(rDir),
DepPkgAlias: strings.ReplaceAll(pkgName, "/", "_"),
DepPkg: pkg,
}
register.RegisterName = register.DepPkgAlias + ".Register" + idlName + "(r)"
registerPath := filepath.Join(rDir, registerTplName)
isExist, err := util.PathExist(registerPath)
if err != nil {
return err
}
if !isExist {
return pkgGen.TemplateGenerator.Generate(register, registerTplName, registerPath, false)
}
file, err := ioutil.ReadFile(registerPath)
if err != nil {
return fmt.Errorf("read register '%s' failed, err: %v", registerPath, err.Error())
}
insertReg := register.RegisterName
if !bytes.Contains(file, []byte(insertReg)) {
t := !bytes.Contains(file, []byte(register.DepPkg))
if t {
file, err = util.AddImport(registerPath, register.DepPkgAlias, register.DepPkg)
if err != nil {
return err
}
}
//
//if bytes.Contains(file, []byte(insertReg)) {
// return fmt.Errorf("the router(%s) has been registered", insertReg)
//}
subIndexReg := regRegisterV3.FindSubmatchIndex(file)
if len(subIndexReg) != 2 || subIndexReg[0] < 1 {
return fmt.Errorf("wrong format %s: insert-point '%s' not found", string(file), insertPointPatternNew)
}
bufReg := bytes.NewBuffer(nil)
bufReg.Write(file[:subIndexReg[1]])
bufReg.WriteString("\n")
bufReg.WriteString(insertReg)
if t {
bufReg.WriteString("\n\t")
}
bufReg.Write(file[subIndexReg[1]:])
pkgGen.files = append(pkgGen.files, File{registerPath, string(bufReg.Bytes()), false, registerTplName})
}
return nil
}
func (pkgGen *HttpPackageGenerator) genRouter(pkg *HttpPackage, root *RouterNode, handlerPackage, routerDir, routerPackage string) error {
err := root.DyeGroupName(pkgGen.SnakeStyleMiddleware)
if err != nil {
return err
}
idleName := util.ToCamelCase(util.BaseNameAndTrim(pkg.IdlName))
router := Router{
FilePath: filepath.Join(routerDir, util.BaseNameAndTrim(pkg.IdlName)+".go"),
PackageName: filepath.Base(routerDir),
HandlerPackages: map[string]string{
util.BaseName(handlerPackage, ""): handlerPackage,
},
Router: root,
IdlName: idleName,
}
if pkgGen.HandlerByMethod {
handlerMap := make(map[string]string, 1)
hook := func(layer int, node *RouterNode) error {
if len(node.HandlerPackage) != 0 {
handlerMap[node.HandlerPackageAlias] = node.HandlerPackage
}
return nil
}
root.DFS(0, hook)
router.HandlerPackages = handlerMap
}
// store router info
pkg.RouterInfo = &router
if !pkgGen.tplsInfo[routerTplName].Disable {
if err := pkgGen.TemplateGenerator.Generate(router, routerTplName, router.FilePath, false); err != nil {
return fmt.Errorf("generate router %s failed, err: %v", router.FilePath, err.Error())
}
}
if err := pkgGen.updateMiddlewareReg(router, middlewareTplName, filepath.Join(routerDir, "middleware.go")); err != nil {
return fmt.Errorf("generate middleware %s failed, err: %v", filepath.Join(routerDir, "middleware.go"), err.Error())
}
if err := pkgGen.updateRegister(routerPackage, pkgGen.RouterDir, pkg.Package, idleName); err != nil {
return fmt.Errorf("update register for %s failed, err: %v", filepath.Join(routerDir, registerTplName), err.Error())
}
return nil
}
func (pkgGen *HttpPackageGenerator) updateMiddlewareReg(router interface{}, middlewareTpl, filePath string) error {
if pkgGen.tplsInfo[middlewareTpl].Disable {
return nil
}
isExist, err := util.PathExist(filePath)
if err != nil {
return err
}
if !isExist {
return pkgGen.TemplateGenerator.Generate(router, middlewareTpl, filePath, false)
}
var middlewareList []string
_ = router.(Router).Router.DFS(0, func(layer int, node *RouterNode) error {
// non-leaf node will generate group middleware
if node.Children.Len() > 0 && len(node.GroupMiddleware) > 0 {
middlewareList = append(middlewareList, node.GroupMiddleware)
}
if len(node.HandlerMiddleware) > 0 {
middlewareList = append(middlewareList, node.HandlerMiddleware)
}
return nil
})
file, err := ioutil.ReadFile(filePath)
if err != nil {
return err
}
for _, mw := range middlewareList {
mwNamePattern := fmt.Sprintf(" %sMw", mw)
if pkgGen.SnakeStyleMiddleware {
mwNamePattern = fmt.Sprintf(" %s_mw", mw)
}
if bytes.Contains(file, []byte(mwNamePattern)) {
continue
}
middlewareSingleTpl := pkgGen.tpls[middlewareSingleTplName]
if middlewareSingleTpl == nil {
return fmt.Errorf("tpl %s not found", middlewareSingleTplName)
}
data := make(map[string]string, 1)
data["MiddleWare"] = mw
middlewareFunc := bytes.NewBuffer(nil)
err = middlewareSingleTpl.Execute(middlewareFunc, data)
if err != nil {
return fmt.Errorf("execute template \"%s\" failed, %v", middlewareSingleTplName, err)
}
buf := bytes.NewBuffer(nil)
_, err = buf.Write(file)
if err != nil {
return fmt.Errorf("write middleware \"%s\" failed, %v", mw, err)
}
_, err = buf.Write(middlewareFunc.Bytes())
if err != nil {
return fmt.Errorf("write middleware \"%s\" failed, %v", mw, err)
}
file = buf.Bytes()
}
pkgGen.files = append(pkgGen.files, File{filePath, string(file), false, middlewareTplName})
return nil
}
// convertToMiddlewareName converts a route path to a middleware name
func convertToMiddlewareName(path string) string {
path = util.ToVarName([]string{path})
path = strings.ToLower(path)
return path
}