package console import ( "go/ast" "go/parser" "go/token" "io/fs" "strings" "testing" ) // console 是**单例服务**:InitSys 只初始化 postgres(主库)/redis/nats/email,**从不 mysql.OnInit**。 // 业务库连接是按 X-App-Id 用 mysql.NewSys 现建的实例(registry.buildAppConn),跟包级连接是两回事。 // // 所以包级 mysql 的 defsys 恒为 nil,任何走它的调用都是 nil interface method call panic, // 被 gin.Recovery 兜成 HTTP 500 —— 前端只拿得到状态码,看不到任何业务错误信息,极难定位。 // 2026-09-14 的「赠送资源点」500 就是 giftUserResource 调了 comm.LoadComputeRates() 造成的。 // // 而且这些配置本来就是**按应用**存在各自业务库里的,包级连接即使初始化了也会读到别人那一份。 // console 侧一律走 conn(见 api_compute.go 的 computeRatesOf / appConnFromCtx)。 // comm 包里直接使用包级 mysql/postgres 连接的导出函数,console 不能调。 var commPkgLevelDBFuncs = map[string]string{ "LoadOpsParams": "改用 conn.Find(comm.TableAppConfig, ...) + comm.ParseOpsParams", "LoadComputeRates": "改用 computeRatesOf(conn)", "SaveComputeRates": "改用 saveComputeConfig 里按 conn 写的那套", "BoundFactoryDevices": "改用 registry 主库连接自己查 device_mac", "ResolveUserVip": "改用 userComputeSummary(conn, user) 那条跨库拼装", "ApplyComputeUsage": "console 不记消耗,只按 conn 调整余额", "AdjustUserCompute": "改用 api_compute.go 里按 conn 重写的那份", "GrantUserCompute": "改用 conn.Table(comm.TableUser).Update(...)", } func TestConsoleDoesNotUsePackageLevelDB(t *testing.T) { fset := token.NewFileSet() pkgs, err := parser.ParseDir(fset, ".", func(fi fs.FileInfo) bool { return !strings.HasSuffix(fi.Name(), "_test.go") }, 0) // 0 = 丢弃注释,注释里提到函数名不算调用 if err != nil { t.Fatalf("解析 console 包失败: %v", err) } for _, pkg := range pkgs { for path, f := range pkg.Files { ast.Inspect(f, func(n ast.Node) bool { call, ok := n.(*ast.CallExpr) if !ok { return true } sel, ok := call.Fun.(*ast.SelectorExpr) if !ok { return true } ident, ok := sel.X.(*ast.Ident) if !ok { return true } pos := fset.Position(call.Pos()) switch ident.Name { case "comm": if fix, bad := commPkgLevelDBFuncs[sel.Sel.Name]; bad { t.Errorf("%s:%d: console 不能调 comm.%s —— 它走包级 mysql 连接,"+ "console 从不初始化,运行时必 panic → HTTP 500。%s", path, pos.Line, sel.Sel.Name, fix) } case "mysql": // 只允许建实例与设置选项:mysql.NewSys / mysql.SetXxx。 // 其余(Find/FindOne/Insert/Save/Table/Exec…)都是包级 defsys 的转发。 name := sel.Sel.Name if name != "NewSys" && !strings.HasPrefix(name, "Set") { t.Errorf("%s:%d: console 不能调包级 mysql.%s —— defsys 恒为 nil,"+ "改用按 X-App-Id 取出的 conn(appConnFromCtx / registry.getServiceDB)", path, pos.Line, name) } } return true }) } } }