package main import ( "bufio" "bytes" "fmt" "go/ast" "go/format" "go/parser" "go/printer" "go/token" "io" "io/fs" "log" "os" "os/exec" "path" "path/filepath" "regexp" "runtime" "strings" "text/template" "unicode" "golang.org/x/text/cases" "golang.org/x/text/language" "gopkg.in/ini.v1" ) var workspaceDir string var tpl *template.Template func init() { log.SetFlags(log.Llongfile) var err error workspaceDir, err = findProtoParentFolder() if err != nil { log.Panic(err) } err = os.Chdir(workspaceDir) if err != nil { log.Panicf("切换目录失败:%v\n", err) } tpl, err = template.ParseGlob(workspaceDir + "/proto/goutils/proto_build/tpls/*.tpl") if err != nil { panic(err) } } func main() { args := os.Args[1:] // 获取除去可执行文件名的命令行参数 if len(args) == 0 || args[0] == "service" { ServiceMain() } else if args[0] == "client" { GenClientMain() } else { log.Println("Invalid argument. Usage: go run main.go [service|client]") } } func ServiceMain() { log.Println("项目目录:", workspaceDir) checkProtoMessageName(workspaceDir + "/" + "proto/service") packageName := "service" ServerCodePath := "server" genDir := "gen/go" ServiceName, projectName, projectLastName := getServiceNameAndProjectName(ServerCodePath) if ServiceName == "" { err := os.MkdirAll("server", 0755) if err != nil { log.Println(err) } createFileWithPermNotExists("server/service_config.ini", func(f io.Writer) error { return tpl.ExecuteTemplate(f, "service_config.tpl", nil) }) panic("必须填写server/service_config.ini文件的项目名称与proto对应名称") } defer createFileNotExists("protoc.sh", 0755, func(f io.Writer) error { shellstr := `#! /bin/bash if [ ! -d "proto" ] || [ -z "$(ls -A proto)" ]; then git submodule init git submodule update fi go run -gcflags="-N" proto/goutils/proto_build/main.go` f.Write([]byte(shellstr)) return nil }) serviceProtoDir := fmt.Sprintf("proto/%s", packageName) err := os.MkdirAll(genDir+"/"+packageName, 0755) if err != nil { log.Println(err) } CheckGomodBasicPackage() ExecProtoc(workspaceDir, serviceProtoDir, genDir, packageName, projectName) ExecCreateTest(genDir, projectLastName, packageName, ServiceName) ExecCreateConfig(ServiceName, projectLastName) ExecCreateAutoGrpc(genDir, packageName) ExecCreateAutoLogic(workspaceDir, ServiceName, genDir, packageName, projectLastName) // log.Println("Found proto folder at:", workerSpaceDir, ServiceNames, projectLastName, projectName) } func GenClientMain() { log.Println("项目目录:", workspaceDir) checkProtoMessageName(workspaceDir + "/" + "proto/service") packageName := "service" genDir := "gen/go" defer createFileNotExists("protoc.sh", 0755, func(f io.Writer) error { shellstr := `#! /bin/bash if [ ! -d "proto" ] || [ -z "$(ls -A proto)" ]; then git submodule init git submodule update fi go run -gcflags="-N" proto/goutils/proto_build/main.go client` f.Write([]byte(shellstr)) return nil }) serviceProtoDir := fmt.Sprintf("proto/%s", packageName) err := os.MkdirAll(genDir+"/"+packageName, 0755) if err != nil { log.Println(err) } projectName := "gitlab.fusenpack.com/backend/grpc" CheckGomodBasicPackage() ExecProtoc(workspaceDir, serviceProtoDir, genDir, packageName, projectName) GitignoreGenCheck() ExecCreateAutoGrpc(genDir, packageName) createFileNotExists("update_fspkg_master.sh", 0755, func(f io.Writer) error { return tpl.ExecuteTemplate(f, "update_fspkg_master.tpl", nil) }) } func ExecCreateConfig(ServiceName, ProjectName string) { err := os.MkdirAll("server/config", 0755) if err != nil { panic(err) } createFileWithPermNotExists("server/config/config.go", func(f io.Writer) error { return tpl.ExecuteTemplate(f, "config.tpl", nil) }) // name := underscoreToLowerCamelCase(ServiceName) // createFileWithPermNotExists("server/main.go", func(f io.Writer) error { // return tpl.ExecuteTemplate(f, "main.tpl", map[string]any{ // "ProjectName": ProjectName, // "StructServiceNames": name, // }) // }) createFileWithPermNotExists("server/main_test.go", func(f io.Writer) error { return tpl.ExecuteTemplate(f, "main_test.tpl", nil) }) createFileWithPermNotExists(".gitignore", func(f io.Writer) error { return tpl.ExecuteTemplate(f, "gitignore.tpl", nil) }) createFileNotExists("update_fspkg_master.sh", 0755, func(f io.Writer) error { return tpl.ExecuteTemplate(f, "update_fspkg_master.tpl", nil) }) } // 检查go.mod 添加 fusen的基础模块 func CheckGomodBasicPackage() { data, err := os.ReadFile("./go.mod") if err != nil { panic(err) } var isRewrite bool = false var content string = string(data) if !regexp.MustCompile(`fusen-basic `).Match(data) { log.Println(isRewrite, len(content)) isRewrite = true content += "\n\nreplace fusen-basic v0.0.0 => gitlab.fusenpack.com/backend/basic v0.0.1" } if isRewrite { log.Println("rewrite go.mod => add ") err = os.WriteFile("./go.mod", []byte(content), 0644) if err != nil { panic(err) } } } // 执行protoc func ExecProtoc(workerSpaceDir, serviceProtoDir, genDir, packageName string, projectName string) { allServiceNames := getAllServiceName() protoCmdStr := fmt.Sprintf(`protoc -I %s --go_out %s --go_opt paths=source_relative --go-grpc_out %s --go-grpc_opt paths=source_relative `, "proto", genDir, genDir) for _, sname := range allServiceNames { protoCmdStr += importFileCmdStr(serviceNameEncode(packageName, sname), projectName) } importsFiles := checkServiceProtoImports(serviceProtoDir, allServiceNames...) for _, importFile := range importsFiles { protoCmdStr += importFileCmdStr(importFile, projectName) } log.Println(protoCmdStr) cmd := exec.Command("sh", "-c", protoCmdStr) cmd.Dir = workerSpaceDir cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr // 执行命令 err := cmd.Run() if err != nil { log.Printf("命令执行失败:%v\n", err) return } } func ExecCreateAutoLogic(workerSpaceDir string, ServiceName string, genDir, packageName, projectName string) { // snakeCaseServiceName := convertToSnakeCase(ServiceName) grpcFilePath := genDir + "/" + packageName // 获取目录下的所有文件 files, err := os.ReadDir(grpcFilePath) if err != nil { log.Println("Error reading directory:", err) return } type StructService struct { StructServiceName string LogicPackageName string } type MainTpl struct { ProjectName string StructServiceNames []*StructService LogicDirNames []string } var mtpl = &MainTpl{ ProjectName: projectName, } // 处理main.go文件 defer func() { // name := underscoreToLowerCamelCase(ServiceName) // name := underscoreToLowerCamelCase(ServiceName) createFile("server/main_gen.go", func(f io.Writer) error { return tpl.ExecuteTemplate(f, "main_gen.tpl", mtpl) }) createFileWithPermNotExists("server/main.go", func(f io.Writer) error { return tpl.ExecuteTemplate(f, "main.tpl", mtpl) }) }() // 遍历所有文件 for _, file := range files { // 检查文件是否是 grpc.pb.go 文件 if !file.IsDir() && strings.HasSuffix(file.Name(), "grpc.pb.go") { if !strings.HasPrefix(file.Name(), ServiceName) { continue } func() { // 获取文件的完整路径 filePath := filepath.Join(grpcFilePath, file.Name()) infos := ParseGrpcServerInfo(filePath) // 在这里可以对文件进行进一步的处理 for _, info := range infos { // logicStructNames = append(logicStructNames, info.StructName) var methods []map[string]interface{} for _, met := range info.Method { var dup map[string]bool = make(map[string]bool) var paramsName []string var paramsAndType []string for i, param := range met.Params { var p string if param == "context.Context" { dup["ctx"] = true p = "ctx" } else { plist := strings.Split(param, ".") lowerName := strings.ToLower(plist[len(plist)-1]) if strings.Contains(lowerName, "req") { if _, ok := dup["ctx"]; ok { p = "req" } } if _, ok := dup[p]; ok { p = fmt.Sprintf("param%d", i) } } param = p + " " + param paramsAndType = append(paramsAndType, param) paramsName = append(paramsName, p) } methodMap := map[string]interface{}{ "StructName": info.StructName, "MethodType": met.MethodType, "MethodName": met.MethodName, "ParamsName": paramsName, "Params": paramsAndType, "MethodReturn": met.Returns[0], "MethodResponse": met.Returns[0][1:], } methods = append(methods, methodMap) } var ss *StructService = &StructService{} logicPackageName := convertToSnakeCase(info.StructName) ss.LogicPackageName = logicPackageName var genTypesBuffer bytes.Buffer err = tpl.ExecuteTemplate(&genTypesBuffer, "logic_grpc_struct.tpl", map[string]any{ "ProjectName": projectName, "StructName": info.StructName, "UnimplementedStructName": info.UnimplementedStructName, "Methods": methods, "PackageName": logicPackageName, }) if err != nil { panic(err) } logicPath := "server/logics/" + logicPackageName mtpl.LogicDirNames = append(mtpl.LogicDirNames, logicPath) err = os.MkdirAll(logicPath, 0755) if err != nil { panic(err) } f, err := os.OpenFile(logicPath+"/types_gen.go", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) if err != nil { panic(err) } // log.Println(genTypesBuffer.String()) formatted, err := format.Source(genTypesBuffer.Bytes()) if err != nil { log.Println(genTypesBuffer.String()) // panic(fmt.Sprintf("格式化代码失败:%v\n", err)) formatted = genTypesBuffer.Bytes() } _, err = f.Write(formatted) if err != nil { panic(err) } // log.Println(genTypesBuffer.String()) createFilePath := logicPath + "/logic_init.go" createFileWithPermNotExists(createFilePath, func(f io.Writer) error { return tpl.ExecuteTemplate(f, "logic_init.tpl", map[string]any{ "StructNames": []string{info.StructName}, "ProjectName": projectName, "PackageName": logicPackageName, }) }) ss.StructServiceName = info.ServiceName for _, method := range methods { fileName := convertToSnakeCase(method["MethodName"].(string)) method["ProjectName"] = projectName method["PackageName"] = logicPackageName createFileWithPermNotExists(fmt.Sprintf("%s/%s_logic.go", logicPath, fileName), func(f io.Writer) error { return tpl.ExecuteTemplate(f, "logic_fusen_handler.tpl", method) }) } mtpl.StructServiceNames = append(mtpl.StructServiceNames, ss) } }() } } } func ExecCreateTest(genDir, projectName, packageName, serviceName string) { genDir = genDir + "/" + packageName var struCollection []*GrpcMethodTest for _, gwPath := range getSuffixFilesPath(genDir, strings.ToLower(serviceName)+"_grpc.pb.go") { struCollection = append(struCollection, genGatewayTestFunction(gwPath)...) } var buf bytes.Buffer err := tpl.ExecuteTemplate(&buf, "http_grpc_method_test.tpl", map[string]any{ "ProjectName": projectName, "GrpcTestStructs": struCollection, }) if err != nil { panic(err) } formatted, err := format.Source([]byte(buf.Bytes())) if err != nil { log.Printf("格式化代码失败:%v\n", err) return } genTestDir := "server/test" err = os.MkdirAll(genTestDir, 0755) if err != nil { log.Println(err) } filePath := fmt.Sprintf("%s/test_method_gen.go", genTestDir) err = os.WriteFile(filePath, formatted, 0644) if err != nil { log.Printf("无法写入文件:%v\n", err) return } for _, s := range struCollection { snakeName := convertToSnakeCase(s.MethodName) createFileWithPermNotExists(genTestDir+"/"+snakeName+"_test.go", func(f io.Writer) error { return tpl.ExecuteTemplate(f, "http_grpc_method_file_test.tpl", map[string]any{ "ProjectName": projectName, "RequestVar": s.RequestVar, "MethodName": s.MethodName, "RequestStruct": s.RequestStruct, }) }) } createFileWithPermNotExists(genTestDir+"/"+"var.go", func(f io.Writer) error { return tpl.ExecuteTemplate(f, "http_grpc_method_var.tpl", map[string]any{ "ProjectName": projectName, }) }) createFileNotExists("run_latest.sh", 0755, func(f io.Writer) error { return tpl.ExecuteTemplate(f, "run_latest.tpl", nil) }) } type ClientParam struct { ClientName string ServerName string ServiceName string GrpcServiceName string } // 执行auto grpc func ExecCreateAutoGrpc(genDir, packageName string) { genDir = genDir + "/" + packageName var clientParams []ClientParam for _, params := range getGrpcFileClientNames(genDir) { clientParams = append(clientParams, ClientParam{ ClientName: params.ClientName, ServerName: params.ServerName, ServiceName: cases.Lower(language.English).String(params.ClientName), GrpcServiceName: params.ServiceName, }) } var buf bytes.Buffer tpl.ExecuteTemplate(&buf, "auto_grpc_nacos.tpl", map[string]any{ "PackageName": packageName, "ClientParams": clientParams, }) formatted, err := format.Source([]byte(buf.Bytes())) if err != nil { log.Printf("格式化代码失败:%v\n", err) return } filePath := fmt.Sprintf("%s/auto_%s_grpc_nacos_client.pb.go", genDir, packageName) err = os.WriteFile(filePath, formatted, 0644) if err != nil { log.Printf("无法写入文件:%v\n", err) return } // log.Println(string(formatted)) } // 执行auto grpc func ExecCreateGatewayAutoGrpc(genDir, packageName, ProjectName string) { genDir = genDir + "/" + packageName var buf bytes.Buffer err := tpl.ExecuteTemplate(&buf, "auto_grpc_gateway_nacos.tpl", nil) if err != nil { panic(err) } var funcNames []string for _, gwPath := range getSuffixFilesPath(genDir, "pb.gw.go") { funcNames = append(funcNames, genGatewayWithNacosFunction(gwPath, &buf)...) } formatted, err := format.Source(buf.Bytes()) if err != nil { log.Printf("格式化代码失败:%v\n", err) log.Println(buf.String()) return } filePath := fmt.Sprintf("%s/auto_%s_grpc_nacos_gateway.pb.go", genDir, packageName) err = os.WriteFile(filePath, formatted, 0644) if err != nil { log.Printf("无法写入文件:%v\n", err) return } err = os.MkdirAll("server/logic", 0755) if err != nil { log.Println(err) } // 生成gateway的代码logic var grbuf bytes.Buffer err = tpl.ExecuteTemplate(&grbuf, "auto_grpc_gateway_register.tpl", map[string]any{ "ProjectName": ProjectName, "FuncNames": funcNames, }) if err != nil { panic(err) } formatted, err = format.Source(grbuf.Bytes()) if err != nil { log.Printf("格式化代码失败:%v\n", err) return } err = os.WriteFile("server/logic/gateway_logic_gen.go", formatted, 0644) if err != nil { log.Printf("无法写入文件:%v\n", err) return } } func genGatewayWithNacosFunction(grpcPath string, gatewayBuf *bytes.Buffer) (funcNames []string) { // workerSpaceDir+"/gen/go/service/auth_grpc.pb.go" // 解析Go源文件 fset := token.NewFileSet() node, err := parser.ParseFile(fset, grpcPath, nil, parser.AllErrors) if err != nil { log.Println("解析文件失败:", err) os.Exit(1) } // packageName := node.Name.Name // log.Println(packageName) h := len("Register") e := len("HandlerClient") for _, decl := range node.Decls { // 检查是否是接口声明 if fdec, ok := decl.(*ast.FuncDecl); ok && fdec.Name.IsExported() && strings.HasSuffix(fdec.Name.Name, "HandlerClient") { // log.Println(fdec.Name.Name) cName := fdec.Name.Name[h : len(fdec.Name.Name)-e] fdec.Name.Name = fdec.Name.Name + "Nacos" funcNames = append(funcNames, fdec.Name.Name) field := fdec.Type.Params.List[len(fdec.Type.Params.List)-1] field.Names = []*ast.Ident{{Name: "opts"}} field.Type = &ast.Ellipsis{ Elt: &ast.SelectorExpr{ X: &ast.Ident{Name: "grpc"}, Sel: &ast.Ident{Name: "DialOption"}, }, } for _, b := range fdec.Body.List { if bexpr, ok := b.(*ast.ExprStmt); ok { if pfunc, ok := bexpr.X.(*ast.CallExpr); ok && len(pfunc.Args) == 3 { if x, ok := pfunc.Args[2].(*ast.FuncLit); ok { for i, bb := range x.Body.List { if as, ok := bb.(*ast.AssignStmt); ok { if mycb, ok := as.Rhs[0].(*ast.CallExpr); ok { if strings.HasPrefix(getTypeString(mycb.Fun), "request_") { for i, arg := range mycb.Args { // 检查参数是否为标识符且名称为 "client" if ident, ok := arg.(*ast.Ident); ok && ident.Name == "client" { // 将参数替换为函数调用 mycb.Args[i] = ast.NewIdent("grpcClient") break } } opts := ast.NewIdent("opts") autoClientFunc := &ast.CallExpr{ Fun: ast.NewIdent("Auto" + cName + "ClientEx"), Args: []ast.Expr{ ast.NewIdent("ctx"), opts, }, Ellipsis: opts.End(), } // 创建新的变量声明 var dstmt ast.Stmt = &ast.AssignStmt{ Tok: token.DEFINE, Lhs: []ast.Expr{ ast.NewIdent("grpcClient"), ast.NewIdent("err"), }, Rhs: []ast.Expr{ autoClientFunc, }, } // // var insertIndex = 4 var blist []ast.Stmt ifErr := x.Body.List[6] blist = append(blist, x.Body.List[:i]...) blist = append(blist, dstmt) blist = append(blist, ifErr) x.Body.List = append(blist, x.Body.List[i:]...) // log.Println(getTypeString(mycb.Fun)) // printer.Fprint(os.Stdout, fset, as) // io.WriteString(os.Stdout, "\n") break } } } } } } } } // 修改函数执行内容 // ast.Inspect(fdec.Body, func(n ast.Node) bool { // // if reqCallback, ok := n.(*ast.CallExpr); ok { // // // 在callExpr的上一级添加变量定义 // // // 获取函数主体中的最后一个语句 // // // var dstmt ast.Stmt = &ast.DeclStmt{Decl: varDecl} // // // var insertIndex = 4 // // // var blist []ast.Stmt // // // blist = append(blist, reqCallback.Body.List[:insertIndex]...) // // // blist = append(blist, dstmt) // // // reqCallback.Body.List = append(blist, reqCallback.Body.List[insertIndex:]...) // // } // if callExpr, ok := n.(*ast.CallExpr); ok { // // 检查函数调用中的参数列表 // for i, arg := range callExpr.Args { // // 检查参数是否为标识符且名称为 "client" // if ident, ok := arg.(*ast.Ident); ok && ident.Name == "client" { // // 将参数替换为函数调用 // opts := ast.NewIdent("opts") // callExpr.Args[i] = &ast.CallExpr{ // Fun: ast.NewIdent("Auto" + cName + "ClientEx"), // Args: []ast.Expr{ // ast.NewIdent("ctx"), // opts, // }, // Ellipsis: opts.End(), // } // break // } // } // } // return true // }) // 打印修改后的函数 err = gatewayBuf.WriteByte('\n') if err != nil { panic(err) } printer.Fprint(gatewayBuf, fset, fdec) } } // log.Println(infos) return } type GrpcMethodTest struct { RequestVar string RequestStruct string Params []string MethodName string ServiceName string } func genGatewayTestFunction(grpcPath string) (createdCollection []*GrpcMethodTest) { // workerSpaceDir+"/gen/go/service/auth_grpc.pb.go" // 解析Go源文件 fset := token.NewFileSet() node, err := parser.ParseFile(fset, grpcPath, nil, parser.AllErrors) if err != nil { log.Println("解析文件失败:", err) os.Exit(1) } // var i = 0 for _, decl := range node.Decls { // 检查是否是接口声明 if fdec, ok := decl.(*ast.GenDecl); ok && fdec.Tok == token.TYPE { // ast.Print(fset, fdec) for _, spec := range fdec.Specs { if typeSpec, ok := spec.(*ast.TypeSpec); ok && strings.HasSuffix(typeSpec.Name.Name, "Server") && !strings.HasPrefix(typeSpec.Name.Name, "Unsafe") && !strings.Contains(typeSpec.Name.Name, "_") { serviceName := typeSpec.Name.Name serviceName = serviceName[:len(serviceName)-6] ast.Inspect(typeSpec, func(interfaceNode ast.Node) bool { if methodDecl, ok := interfaceNode.(*ast.Ident); ok && methodDecl.IsExported() { // log.Println(ftype.) if methodDecl.Obj != nil { if methodDecl.Obj.Kind == ast.Fun { // log.Println(typeSpec.Name.Name) // log.Println(methodDecl.Name) created := GrpcMethodTest{} created.ServiceName = serviceName created.MethodName = methodDecl.Name created.RequestVar = "var" + created.MethodName + "Req" field := methodDecl.Obj.Decl.(*ast.Field) ftype := field.Type.(*ast.FuncType) if len(ftype.Params.List) == 2 { if reqstruct, ok := ftype.Params.List[1].Type.(*ast.StarExpr); ok { rname := reqstruct.X.(*ast.Ident) created.RequestStruct = rname.Name createdCollection = append(createdCollection, &created) } } // ast.Print(fset, ftype) } } } // ast.Print(fset, n) // i++ // log.Println(i) // if i == 7 { // log.Println() // } // printer.Fprint(os.Stdout, fset, n) // io.WriteString(os.Stdout, "\n") // if ftype, ok := n.(*ast.FuncType); ok { // log.Println(getTypeString(ftype)) // ast.Print(fset, ftype) // } return true }) } } // created.RequestVar = "var" + created.MethodName + "Req" // 修改函数执行内容 } } return } func getSuffixFilesPath(dir string, suffix string) (result []string) { err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if info.IsDir() { return nil } if strings.HasSuffix(info.Name(), suffix) { result = append(result, path) } return nil }) if err != nil { panic(err) } return } // 获取相关后缀的文件路径 func getGrpcFileClientNames(genDir string) (result []*ClientParam) { err := filepath.Walk(genDir, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if info.IsDir() { return nil } if strings.HasSuffix(info.Name(), "grpc.pb.go") { params := parseGoFile(path) result = append(result, params...) // log.Println("找到的服务名称:", strings.Join(serverNames, " ")) } return nil }) if err != nil { log.Printf("遍历目录失败:%v\n", err) return nil } return } func importFileCmdStr(importFile string, projectName string) string { shellStr := " {{.ImportFile}} --go_opt=M{{.ImportFile}}={{.ProjectName}} --go-grpc_opt=M{{.ImportFile}}={{.ProjectName}}" tmpl, err := template.New("shell").Parse(shellStr) if err != nil { log.Printf("模板解析失败:%v\n", err) return "" } data := struct { ImportFile string ProjectName string }{ ImportFile: importFile, ProjectName: projectName, } var buf bytes.Buffer err = tmpl.Execute(&buf, data) if err != nil { log.Printf("模板执行失败:%v\n", err) return "" } // log.Println(buf.String()) return buf.String() } func serviceNameEncode(serviceProtoDir string, serviceName string) string { return fmt.Sprintf("%s/%s.proto", serviceProtoDir, serviceName) } func checkServiceProtoImports(serviceProtoDir string, serviceNames ...string) []string { var resultMap map[string]bool = make(map[string]bool) for _, sname := range serviceNames { fname := fmt.Sprintf("%s/%s.proto", serviceProtoDir, sname) file, err := os.Open(fname) if err != nil { panic(fmt.Sprintf("无法打开文件: %s\n", err)) } defer file.Close() scanner := bufio.NewScanner(file) for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) if strings.HasPrefix(line, "//") { continue } for _, r := range regexp.MustCompile(`import\s+"(service/.*\.proto)"`).FindAllStringSubmatch(line, -1) { resultMap[r[1]] = true } // log.Println(line) } if err := scanner.Err(); err != nil { log.Printf("读取文件失败: %s\n", err) } if err != nil { panic(err) } } var result []string for k := range resultMap { result = append(result, k) } return result } func getAllServiceName() (result []string) { dirEntrys, err := os.ReadDir("proto/service") if err != nil { panic(err) } for _, e := range dirEntrys { if filepath.Ext(e.Name()) == ".proto" { result = append(result, e.Name()[0:len(e.Name())-6]) } } return } // 获取项目的参数 func getServiceNameAndProjectName(dir string) (serviceName string, projectName string, goModeName string) { ifile, err := ini.Load(dir + "/service_config.ini") if err != nil { err = os.MkdirAll("server", 0755) if err != nil { log.Println(err) } createFileWithPermNotExists(dir+"/service_config.ini", func(f io.Writer) error { return tpl.ExecuteTemplate(f, "service_config.tpl", nil) }) panic("必须填写server/service_config.ini文件的项目名称与proto对应名称") } ses := ifile.Section("DEFAULT") if err != nil { panic(err) } key, err := ses.GetKey("SERVICE_NAME") if err != nil { panic(err) } serviceName = key.String() if serviceName == "" { panic("必须填写server/service_config.ini文件的项目名称与proto对应名称") } key, err = ses.GetKey("PROJECT_NAME") if err != nil { panic(err) } projectName = key.String() if projectName == "" { panic("必须填写server/service_config.ini文件的ProjectName称与仓库对应名称") } paths := strings.Split(projectName, "/") goModeName = paths[len(paths)-1] moddata, err := os.ReadFile("go.mod") if err != nil { panic(err) } result := regexp.MustCompile(`module\s+([a-zA-Z_\-]+)`).FindAllStringSubmatch(string(moddata), 1) if len(result) == 0 { panic("无法找到go.mod 获取 module信息") } goModeName = result[0][1] return } // 获取proto文件的父目录 func findProtoParentFolder() (string, error) { cwd, err := os.Getwd() if err != nil { return "", err } return findProtoFolderRecursive(cwd) } func findProtoFolderRecursive(dir string) (string, error) { if dir == "/" || dir == "." { return "", fmt.Errorf("proto folder not found") } protoDir := filepath.Join(dir, "proto") if _, err := os.Stat(protoDir); err == nil { return dir, nil } return findProtoFolderRecursive(filepath.Dir(dir)) } func parseGoFile(filePath string) (ClientParams []*ClientParam) { // 创建文件集合 fset := token.NewFileSet() // 解析文件 file, err := parser.ParseFile(fset, filePath, nil, parser.ParseComments) if err != nil { log.Printf("解析文件失败:%v\n", err) return } // 获取包名 // pkgName := file.Name.Name // log.Println("Package:", pkgName) var Param *ClientParam = &ClientParam{} // 遍历文件中的声明 for _, decl := range file.Decls { if ServiceName, GrpcServiceName, ok := getGrpcFileServiceName(decl); ok { Param.ServiceName = ServiceName Param.GrpcServiceName = GrpcServiceName ClientParams = append(ClientParams, Param) Param = &ClientParam{} } // 检查是否是类型声明 if genDecl, ok := decl.(*ast.GenDecl); ok && genDecl.Tok == token.TYPE { // 检查是否是结构体声明 for _, spec := range genDecl.Specs { if typeSpec, ok := spec.(*ast.TypeSpec); ok { if structType, ok := typeSpec.Type.(*ast.StructType); ok { // 获取结构体名称 // ast.Print(fset, decl) structName := typeSpec.Name.Name // log.Println("Struct: ", structName) if strings.HasSuffix(structName, "Server") { Param.ServerName = structName[len("Unimplemented") : len(structName)-6] } // 遍历结构体的字段列表 for _, field := range structType.Fields.List { // 获取字段名称 field.End() // fieldName := field.Names[0].Name // log.Println("Field:", fieldName) } } else if iface, ok := typeSpec.Type.(*ast.InterfaceType); ok { interfaceName := typeSpec.Name.Name if len(iface.Methods.List) >= 3 { if len(iface.Methods.List[2].Names) == 0 { // log.Println(name0, name1) if getTypeString(iface.Methods.List[2].Type) == "grpc.ClientStream" { // MethodType = "stream" // log.Println(interfaceName) continue } } } if strings.HasSuffix(interfaceName, "Client") { Param.ClientName = interfaceName[0 : len(interfaceName)-6] } } } } } else if funcDecl, ok := decl.(*ast.FuncDecl); ok { funcDecl.End() // 检查是否是函数声明 // funcName := funcDecl.Name.Name // log.Printf("Function: %s\n", funcName) // log.Printf("Signature: %s\n", funcDecl.Type.Params) // Print the function body, if it has one // if funcDecl.Body != nil { // log.Printf("Body: \n%s\n", funcDecl.Body.List) // } } } return } type GrpcServerMethod struct { MethodName string MethodType string Params []string Returns []string } type GrpcServerInfo struct { ServiceName string StructName string UnimplementedStructName string Method []*GrpcServerMethod } func ParseGrpcServerInfo(grpcPath string) (infos []*GrpcServerInfo) { // workerSpaceDir+"/gen/go/service/auth_grpc.pb.go" // 解析Go源文件 fset := token.NewFileSet() node, err := parser.ParseFile(fset, grpcPath, nil, parser.AllErrors) if err != nil { log.Println("解析文件失败:", err) os.Exit(1) } packageName := node.Name.Name // 遍历文件中的所有声明 for _, decl := range node.Decls { // 检查是否是接口声明 if iface, ok := decl.(*ast.GenDecl); ok && iface.Tok == token.TYPE { for _, spec := range iface.Specs { // 检查是否是接口类型 if ifaceSpec, ok := spec.(*ast.TypeSpec); ok { if ifaceType, ok := ifaceSpec.Type.(*ast.InterfaceType); ok { // 打印接口名 // log.Println("接口名:", ifaceSpec.Name.Name) if !strings.HasSuffix(ifaceSpec.Name.Name, "Server") { continue } if strings.Contains(ifaceSpec.Name.Name, "_") { continue } if strings.HasPrefix(ifaceSpec.Name.Name, "Unsafe") { continue } if len(ifaceType.Methods.List) == 3 { if len(ifaceType.Methods.List[0].Names) == 1 && len(ifaceType.Methods.List[1].Names) == 1 { name0 := ifaceType.Methods.List[0].Names[0].Name name1 := ifaceType.Methods.List[1].Names[0].Name if name0 == "SendAndClose" && name1 == "Recv" { // log.Println(name0, name1) if getTypeString(ifaceType.Methods.List[2].Type) == "grpc.ServerStream" { // MethodType = "stream" continue } } } } ServiceName := ifaceSpec.Name.Name[0 : len(ifaceSpec.Name.Name)-6] info := &GrpcServerInfo{ ServiceName: ServiceName, StructName: ServiceName + "Logic", } info.UnimplementedStructName = "service.Unimplemented" + ServiceName + "Server" infos = append(infos, info) // 打印接口方法 for _, method := range ifaceType.Methods.List { if len(method.Names) == 0 { continue } if !isUpper(method.Names[0].Name) { continue } m := &GrpcServerMethod{ MethodType: "rpc", } info.Method = append(info.Method, m) // 方法名称 // log.Println("方法名:", method.Names[0].Name) // MethodName := method.Names[0].Name m.MethodName = method.Names[0].Name // 方法参数 mparams := method.Type.(*ast.FuncType).Params.List switch len(mparams) { case 2: for _, field := range mparams { // log.Printf("%s %s\n", field.Names, getTypeString(field.Type, packageName, 0)) m.Params = append(m.Params, getTypeString(field.Type, packageName)) } case 1: m.MethodType = "stream" for _, field := range mparams { // log.Printf("%s %s\n", field.Names, getTypeString(field.Type, packageName, 0)) m.Params = append(m.Params, getTypeString(field.Type, packageName)) } case 0: log.Println("无参数") default: panic("诡异的结构") } // 方法返回值 if method.Type.(*ast.FuncType).Results != nil { for _, field := range method.Type.(*ast.FuncType).Results.List { m.Returns = append(m.Returns, getTypeString(field.Type, packageName)) } } else { log.Println("无返回值") } // log.Println() } } } } } } // log.Println(infos) return } func isUpper(name string) bool { return name[:1] == strings.ToUpper(name[:1]) } func getTypeString(expr ast.Expr, packageName ...string) string { if len(packageName) > 0 { return _getTypeString(expr, &packageName[0], 0) } else { return _getTypeString(expr, nil, 0) } } // 获取类型字符串 func _getTypeString(expr ast.Expr, packageName *string, level int) string { switch t := expr.(type) { case *ast.Ident: if level == 0 && isUpper(t.Name) { if packageName != nil { return fmt.Sprintf("%s.%s", *packageName, t.Name) } else { return fmt.Sprintf("%s", t.Name) } } return t.Name case *ast.SelectorExpr: return fmt.Sprintf("%s.%s", _getTypeString(t.X, packageName, level+1), t.Sel.Name) case *ast.StarExpr: return fmt.Sprintf("*%s", _getTypeString(t.X, packageName, level)) case *ast.ArrayType: return fmt.Sprintf("[]%s", _getTypeString(t.Elt, packageName, level+1)) case *ast.MapType: return fmt.Sprintf("map[%s]%s", _getTypeString(t.Key, packageName, level+1), _getTypeString(t.Value, packageName, level+1)) case *ast.ChanType: dir := "" switch t.Dir { case ast.SEND: dir = "chan<-" case ast.RECV: dir = "<-chan" } return fmt.Sprintf("%s %s", dir, _getTypeString(t.Value, packageName, level+1)) case *ast.StructType: return "struct{}" case *ast.InterfaceType: return "interface{}" case *ast.FuncType: var params []string if t.Params != nil { for _, param := range t.Params.List { params = append(params, _getTypeString(param.Type, packageName, level+1)) } } var results []string if t.Results != nil { for _, result := range t.Results.List { results = append(results, _getTypeString(result.Type, packageName, level+1)) } } return fmt.Sprintf("func(%s) %s", strings.Join(params, ", "), strings.Join(results, ", ")) default: panic(fmt.Sprintf("%s", t)) } } func createFile(filename string, do func(f io.Writer) error) error { // 检测文件是否存在 file, err := os.OpenFile(filename, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) if err != nil { return err } defer file.Close() var buf = bytes.NewBuffer(nil) err = do(buf) if err != nil { panic(err) } data, err := format.Source(buf.Bytes()) if err != nil { _, err = file.Write(buf.Bytes()) } else { _, err = file.Write(data) } if err != nil { panic(err) } return nil } func createFileWithPermNotExists(filename string, do func(f io.Writer) error) error { // 检测文件是否存在 _, err := os.Stat(filename) if os.IsNotExist(err) { // 文件不存在,创建文件 file, err := os.Create(filename) if err != nil { return err } defer file.Close() var buf = bytes.NewBuffer(nil) err = do(buf) if err != nil { panic(err) } data, err := format.Source(buf.Bytes()) if err != nil { _, err = file.Write(buf.Bytes()) } else { _, err = file.Write(data) } if err != nil { panic(err) } log.Printf("%s 文件已创建并写入内容\n", filename) } else if err != nil { // 发生其他错误 return err } else { // 文件已存在 // log.Printf("%s 文件已存在\n", filename) } return nil } func createFileNotExists(filename string, perm fs.FileMode, do func(f io.Writer) error) error { // 检测文件是否存在 _, err := os.Stat(filename) if os.IsNotExist(err) { // 文件不存在,创建文件 file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, perm) if err != nil { return err } defer file.Close() var buf = bytes.NewBuffer(nil) err = do(buf) if err != nil { panic(err) } _, err = file.Write(buf.Bytes()) if err != nil { panic(err) } log.Printf("%s 文件已创建并写入内容\n", filename) } else if err != nil { // 发生其他错误 panic(err) } else { // 文件已存在 // log.Printf("%s 文件已存在\n", filename) } return nil } func convertToSnakeCase(name string) string { var result strings.Builder for i, char := range name { if unicode.IsUpper(char) { if i > 0 { result.WriteRune('_') } result.WriteRune(unicode.ToLower(char)) } else { result.WriteRune(char) } } return result.String() } // 获取当前执行文件绝对路径(go run) func getCurrentAbPathByCaller() string { var abPath string _, filename, _, ok := runtime.Caller(0) if ok { abPath = path.Dir(filename) } return abPath } func checkProtoMessageName(folderPath string) { messageNames := make(map[string][]string) // 遍历文件夹 err := filepath.Walk(folderPath, func(path string, info os.FileInfo, err error) error { if err != nil { return err } // 检查是否是 .proto 文件 if filepath.Ext(path) == ".proto" { // 读取文件内容 content, err := os.ReadFile(path) if err != nil { return err } // 提取消息类型的名称 matches := regexp.MustCompile(`message\s+(\w+)\s+{`).FindAllStringSubmatch(string(content), -1) for _, match := range matches { messageName := match[1] if paths, ok := messageNames[messageName]; ok { // 发现重复的消息类型名称 panic(fmt.Sprintf("重复的消息类型名称 '%s' 在文件 '%s' 和之前的文件 %s 中存在重复。\n", messageName, path, paths[0])) } messageNames[messageName] = append(messageNames[messageName], path) } } return nil }) if err != nil { panic(fmt.Sprintln("遍历文件夹时发生错误:", err)) } } // 下划线转驼峰 func underscoreToLowerCamelCase(s string) string { // 分割字符串 words := strings.Split(s, "_") caser := cases.Title(language.English) for i := range words { words[i] = caser.String(strings.Trim(words[i], " ")) } return strings.Join(words, "") } func getGrpcFileServiceName(decl ast.Decl) (string, string, bool) { if genDecl, ok := decl.(*ast.GenDecl); ok && genDecl.Tok == token.VAR { // 遍历变量规范 for _, spec := range genDecl.Specs { // 检查是否为值规范 if valueSpec, ok := spec.(*ast.ValueSpec); ok { // 遍历变量名和变量值 for i, name := range valueSpec.Names { log.Println(name) value := valueSpec.Values[i] // ast.Print(fset, value) // 检查变量类型是否为 struct // 检查变量类型是否为 struct if v, ok := value.(*ast.CompositeLit); ok { // 遍历 struct 的字段 if kv := v.Elts[0].(*ast.KeyValueExpr); ok { // log.Println(kv.Key) // log.Println(kv.Value) if _sname, ok := kv.Value.(*ast.BasicLit); ok { strlist := strings.Split(strings.Trim(_sname.Value, "\""), ".") if len(strlist) != 2 { return "", "", false } serviceName, serviceSubName := strlist[0], strlist[1] return serviceName, serviceSubName, true } } } } } } } return "", "", false } // 检测gitignore gen是否存在 func GitignoreGenCheck() { gitignoreFile := "./.gitignore" data, err := os.ReadFile(gitignoreFile) if err != nil { log.Println(err.Error()) return } for _, line := range strings.Split(string(data), "\n") { if strings.Trim(line, " ") == "gen" { return } } data = append(data, []byte("\ngen\n")...) err = os.WriteFile(gitignoreFile, data, 0644) if err != nil { panic(err) } }