咋一看標題, 這個有啥用?
你別說還真有用, 當你想重構你的專案的時候, 或者你想rpc呼叫其他語言時, 這個就不用你一個一個去編寫message了,而是根據你的go結構體直接生成, 簡潔高效(好吧, 一般人也不會有這種需求, 有也直接發給ai就行, 不過各位看官就當看個樂子, 學習下也可以)
實現原理
思路
隨機讀取包含go的結構體檔案, 然後讀取每個結構的內容, 以及結構體名字, 透過fmt.Sprintf嵌入到準備好的模板中, 生成新的可執行go檔案, 這樣就可以生成proto檔案了
實現程式碼
定義proto結構體, 方便後續呼叫操作
// Proto 結構體用於生成 Protobuf 描述 type Proto struct { } // NewProto 建立一個新的 Proto 例項 func NewProto() *Proto { return &Proto{} }
讀取go 檔案, 根據type 分割 go 結構體,
// readGoFile 讀取 Go 檔案並提取結構體定義 func (p *Proto) readGoFile(gopath string) ([]string, error) { data, err := os.ReadFile(gopath) if err != nil { return nil, err } datas := strings.Split(strings.TrimSpace(string(data)), "type ") for i := 1; i < len(datas); i++ { datas[i] = "type " + datas[i] } return datas[1:], nil }
將分割好的每個結構體依次獲取它的結構體名字, 構建到GetStruct放法中, 並將其放到st中, 最後插入模板, 也就是template中, template在文章後面
func (p *Proto) writeGoFile(datas []string, outputGoPath string) error { template := (放在文章後面) st := make([]string, 0) for i := 0; i < len(datas); i++ { structName, err := extractStructName(datas[i]) if err != nil { return err } st = append(st, fmt.Sprintf("st = append(st ,\tGetStruct(new(%s)))", structName)) } //引數為型別定義佔位 create, err := os.Create(outputGoPath) if err != nil { return err } defer create.Close() _, err = create.Write([]byte(fmt.Sprintf(template, strings.Join(datas, "\n"), strings.Join(st, "\n")))) return err }
template 模板
package main import ( "fmt" "os" "reflect" "strings" "unicode" ) %s // determineTypeProto 根據 Go 型別確定 Protobuf 型別 func determineTypeProto(t reflect.Type) string { switch t.Kind() { case reflect.String: return "string" case reflect.Int, reflect.Int32: return "int32" case reflect.Int64: return "int64" case reflect.Float32: return "float" case reflect.Float64: return "double" case reflect.Bool: return "bool" case reflect.Slice: return "repeated " + determineTypeProto(t.Elem()) case reflect.Struct: return t.Name() // 直接使用結構體的名字 default: return "bytes" // 其他未知型別預設使用 bytes } } func toProtobufName(name string) string { // Protobuf 欄位名通常使用小寫字母和下劃線 var result string for i, ch := range name { if i > 0 && unicode.IsUpper(ch) { result += "_" } result += string(unicode.ToLower(ch)) } return result } func toProtobufMessageName(name string) string { // Protobuf 訊息名通常使用駝峰式命名 return strings.Title(name) } func toProtobufFieldName(name string) string { // Protobuf 欄位名不能以數字開頭,因此確保首字元是字母 if len(name) > 0 && unicode.IsDigit(rune(name[0])) { name = "field" + name } // 將欄位名轉換為小寫加下劃線 return toProtobufName(name) } // GetStruct 生成結構體的 Protobuf 格式描述 func GetStruct(s interface{}) string { t := reflect.TypeOf(s) v := reflect.ValueOf(s) if t.Kind() == reflect.Ptr { t = t.Elem() v = v.Elem() } st := make([]string, 0) for i := 0; i < t.NumField(); i++ { fieldName := toProtobufFieldName(t.Field(i).Name) fieldType := determineTypeProto(t.Field(i).Type) // 構建 Protobuf 欄位描述 fieldDesc := fmt.Sprintf("%%s %%s = %%d;", fieldType, fieldName, i+1) st = append(st, fieldDesc) } // 將欄位描述連線成一個完整的 message return fmt.Sprintf("message %%s {\n%%s\n}", toProtobufMessageName(t.Name()), "\n"+strings.Join(st, "\n")) } func main() { //型別定義佔位 st := make([]string, 0) %s create, err := os.Create("./output.proto") if err != nil { fmt.Println(err) } defer create.Close() _, err = create.Write([]byte(strings.Join(st, "\n"))) if err != nil { fmt.Println(err) } }
效果展示
package code_gen type LoginReq struct { Username string `json:"username"` Password string `json:"password"` Ip string `json:"ip,optional"` } type LoginRes struct { Username string `json:"username"` Token string `json:"token"` MemberLevel string `json:"memberLevel"` RealName string `json:"realName"` Country string `json:"country"` Avatar string `json:"avatar"` PromotionCode string `json:"promotionCode"` Id int64 `json:"id"` LoginCount int `json:"loginCount"` SuperPartner string `json:"superPartner"` MemberRate int `json:"memberRate"` }
message LoginReq { string username = 1; string password = 2; string ip = 3; } message LoginRes { string username = 1; string token = 2; string member_level = 3; string real_name = 4; string country = 5; string avatar = 6; string promotion_code = 7; int64 id = 8; int32 login_count = 9; string super_partner = 10; int32 member_rate = 11; }