修复批量 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。
This commit is contained in:
huangqz 2026-06-16 18:26:57 +08:00
parent 4fa9841009
commit a407e10ebe
3 changed files with 68 additions and 36 deletions

View File

@ -194,6 +194,13 @@ func (routerNode *RouterNode) DFS(i int, hook func(layer int, node *RouterNode)
var handlerPkgMap map[string]string 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) { func (routerNode *RouterNode) Insert(name string, method *HttpMethod, handlerType string, paths []string, handlerPkg string) {
cur := routerNode cur := routerNode
for i, p := range paths { for i, p := range paths {

View File

@ -239,51 +239,65 @@ func (plugin *Plugin) Handle(req *pluginpb.CodeGeneratorRequest, args *config.Ar
for _, file := range files { for _, file := range files {
maps[file.GetName()] = file maps[file.GetName()] = file
} }
main := maps[gen.Request.FileToGenerate[len(gen.Request.FileToGenerate)-1]] // 为 FileToGenerate 中的「每一个」idl 生成 http packagehandler/router/register/middleware
deps := make(map[string]*descriptorpb.FileDescriptorProto, len(main.GetDependency())) // 原实现只取最后一个 idl导致一次传入多个 --idl 时,除最后一个外其余 proto 都只生成
for _, dep := range main.GetDependency() { // model、不生成 handler/routerhz_gen 批量脚本正是踩了这个坑)。
if f, ok := maps[dep]; !ok { //
err := fmt.Errorf("dependency file not found: %s", dep) // 关键点handler 追加、register.go、middleware.go 都是「读磁盘现有内容再合并」。插件进程内
// 默认不落盘(最终交给 protoc 写),因此若只在内存里循环,后一个 idl 读到的仍是旧文件,会丢掉
// 前一个 idl 新增的路由/中间件注册。所以每生成完一个 idl 就「立即写盘」,保证下一轮合并读到的是
// 累积后的最新内容。这样多个新服务同批生成也不会互相覆盖注册。
for _, idlName := range gen.Request.FileToGenerate {
// 每个 idl 开始前重置进程级命名状态,使其行为与「逐文件单进程」一致,
// 避免中间件分组变量名带上跨 idl 的全局序号后缀(如 _v184造成大面积无谓 diff。
util.ResetUniqueNameSets()
generator.ResetRouterState()
main := maps[idlName]
deps := make(map[string]*descriptorpb.FileDescriptorProto, len(main.GetDependency()))
for _, dep := range main.GetDependency() {
f, ok := maps[dep]
if !ok {
err := fmt.Errorf("dependency file not found: %s", dep)
gen.Error(err)
resp := gen.Response()
if err2 := plugin.Response(resp); err2 != nil {
return err
}
return nil
}
deps[dep] = f
}
pkgFiles, err := plugin.genHttpPackage(main, deps, args)
if err != nil {
err := fmt.Errorf("generate package files failed: %s", err.Error())
gen.Error(err) gen.Error(err)
resp := gen.Response() resp := gen.Response()
err2 := plugin.Response(resp) if err2 := plugin.Response(resp); err2 != nil {
if err2 != nil {
return err return err
} }
return nil return nil
} else { }
deps[dep] = f
// 立即落盘,使后续 idl 的 register.go/middleware.go/handler 合并读到最新内容。
for _, pkgFile := range pkgFiles {
absPath := pkgFile.Path
if !filepath.IsAbs(absPath) {
absPath = filepath.Join(args.OutDir, pkgFile.Path)
}
if err := os.MkdirAll(filepath.Dir(absPath), 0o755); err != nil {
return fmt.Errorf("create dir for '%s' failed: %s", absPath, err.Error())
}
if err := ioutil.WriteFile(absPath, []byte(pkgFile.Content), 0o644); err != nil {
return fmt.Errorf("write http package file '%s' failed: %s", absPath, err.Error())
}
} }
} }
pkgFiles, err := plugin.genHttpPackage(main, deps, args) // http package 已直接写盘;响应里只回模型文件(交给 protoc 写)。
if err != nil {
err := fmt.Errorf("generate package files failed: %s", err.Error())
gen.Error(err)
resp := gen.Response()
err2 := plugin.Response(resp)
if err2 != nil {
return err
}
return nil
}
// construct plugin response
resp := gen.Response() resp := gen.Response()
// all files that need to be generated are returned to protoc if err := plugin.Response(resp); err != nil {
for _, pkgFile := range pkgFiles {
filePath := pkgFile.Path
content := pkgFile.Content
renderFile := &pluginpb.CodeGeneratorResponse_File{
Name: &filePath,
Content: &content,
}
resp.File = append(resp.File, renderFile)
}
// plugin stop working
err = plugin.Response(resp)
if err != nil {
return fmt.Errorf("write response failed: %s", err.Error()) return fmt.Errorf("write response failed: %s", err.Error())
} }

View File

@ -364,6 +364,17 @@ var (
uniqueHandlerPackageName = map[string]bool{} uniqueHandlerPackageName = map[string]bool{}
) )
// ResetUniqueNameSets 清空「唯一变量名」累积状态。
// 这些 set 原本假设「一个进程只处理一个 idl」旧版逐文件生成即如此
// 因此命名从干净的 _v1/_user 起算。当一个进程内循环处理多个 idl 时,必须在每个
// idl 开始前重置,否则中间件分组变量名会带上全局序号后缀(如 _v184导致与逐文件
// 生成结果不一致、且依赖处理顺序而不稳定。
func ResetUniqueNameSets() {
uniquePackageName = map[string]bool{}
uniqueMiddlewareName = map[string]bool{}
uniqueHandlerPackageName = map[string]bool{}
}
// GetPackageUniqueName can get a non-repeating variable name for package alias // GetPackageUniqueName can get a non-repeating variable name for package alias
func GetPackageUniqueName(name string) (string, error) { func GetPackageUniqueName(name string) (string, error) {
name, err := getUniqueName(name, uniquePackageName) name, err := getUniqueName(name, uniquePackageName)