├── LICENSE ├── README.md ├── main.go └── template └── proto.go.tpl /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Guo Zhiqiang and The Contributors All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mysql-to-proto 2 | 3 | 使用过grpc的同学都知道,写proto文件比较繁琐,尤其是写message,对应很多字段,为此写了一个简单的从mysql直接读取表结构,生成proto文件的工具。 4 | 5 | 工具的使用很简单,需要简单的配置,即可运行生成proto文件。 6 | 7 | ### 准备 8 | 9 | 使用前需先安装依赖包go-sql-driver/mysql 10 | 11 | ``` 12 | $ go get -u github.com/go-sql-driver/mysql 13 | ``` 14 | 15 | 16 | 17 | ### 使用说明: 18 | 19 | ``` 20 | func main() { 21 | //模板文件存放路径 22 | tpl := "d:/gopath/src/mysql-to-proto/template/proto.go.tpl" 23 | //生成proto文件路径 24 | file := "d:/gopath/src/mysql-to-proto/sso.proto" 25 | //数据库名,这里填你自己的数据库名 26 | dbName := "user" 27 | //配置连接数据库信息 28 | db, err := Connect("mysql", "root:123456@tcp(127.0.0.1:3306)/"+dbName+"?charset=utf8mb4&parseTime=true") 29 | //Table names to be excluded 30 | //需要排除表,这里的表不会生成对应的proto文件 31 | exclude := map[string]int{"user_log": 1} 32 | if err != nil { 33 | fmt.Println(err) 34 | } 35 | if IsFile(file) { 36 | fmt.Fprintf(os.Stderr, "Fatal error: ", "proto file already exist") 37 | return 38 | } 39 | t := Table{} 40 | //配置message,Cat 41 | t.Message = map[string]Detail{ 42 | "Filter": { 43 | Name: "Filter", 44 | Cat: "custom", 45 | Attr: []AttrDetail{{ 46 | TypeName: "uint64", //类型 47 | AttrName: "id",//字段 48 | }}, 49 | }, 50 | "Request": { 51 | Name: "Request", 52 | Cat: "all", 53 | }, 54 | "Response": { 55 | Name: "Response", 56 | Cat: "custom", 57 | Attr: []AttrDetail{ 58 | { 59 | TypeName: "uint64", 60 | AttrName: "id", 61 | }, 62 | { 63 | TypeName: "bool", 64 | AttrName: "success", 65 | }, 66 | }, 67 | }, 68 | } 69 | //pachage名称 70 | t.PackageModels = "sso" 71 | //service名称 72 | t.ServiceName = "Sso" 73 | //配置services里面的rpc 74 | t.Method = map[string]MethodDetail{ 75 | "Get": {Request: t.Message["Filter"], Response: t.Message["Request"]}, 76 | "Create": {Request: t.Message["Request"], Response: t.Message["Response"]}, 77 | "Update": {Request: t.Message["Request"], Response: t.Message["Response"]}, 78 | } 79 | //处理数据库表字段属性 80 | t.TableColumn(db, dbName, exclude) 81 | //生成proto 82 | t.Generate(file, tpl) 83 | } 84 | ``` 85 | 86 | 87 | 88 | ## LICENSE 89 | 90 | BSD License 91 | 92 | Book License: [CC BY-SA 3.0 License](http://creativecommons.org/licenses/by-sa/3.0/) -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | _ "github.com/go-sql-driver/mysql" 7 | "html/template" 8 | "log" 9 | "os" 10 | "strings" 11 | ) 12 | 13 | var typeArr = map[string]string{ 14 | "int": "int32", 15 | "tinyint": "int32", 16 | "smallint": "int32", 17 | "mediumint": "int32", 18 | "enum": "int32", 19 | "bigint": "sint64", 20 | "varchar": "string", 21 | "timestamp": "string", 22 | "date": "string", 23 | "text": "string", 24 | "double": "double", 25 | "decimal": "double", 26 | "float": "float", 27 | } 28 | 29 | type Table struct { 30 | PackageModels string 31 | ServiceName string 32 | Method map[string]MethodDetail 33 | Comment map[string]string 34 | Name map[string][]Column 35 | Message map[string]Detail 36 | } 37 | type MethodDetail struct { 38 | Request Detail 39 | Response Detail 40 | } 41 | type Column struct { 42 | Field string 43 | Type string 44 | Comment string 45 | } 46 | type RpcServers struct { 47 | Models string 48 | Name string 49 | Funcs []FuncParam 50 | MessageList []Message 51 | } 52 | type FuncParam struct { 53 | Name string 54 | RequestName string 55 | ResponseName string 56 | } 57 | type Message struct { 58 | Name string 59 | MessageDetail []Field 60 | } 61 | type Field struct { 62 | TypeName string 63 | AttrName string 64 | Num int 65 | } 66 | 67 | type Detail struct { 68 | Name string 69 | Cat string 70 | Attr []AttrDetail 71 | } 72 | 73 | type AttrDetail struct { 74 | TypeName string 75 | AttrName string 76 | } 77 | 78 | func main() { 79 | tpl := "d:/gopath/src/mysql-to-proto/template/proto.go.tpl" 80 | file := "d:/gopath/src/mysql-to-proto/sso.proto" 81 | dbName := "user" 82 | db, err := Connect("mysql", "root:123456@tcp(127.0.0.1:3306)/"+dbName+"?charset=utf8mb4&parseTime=true") 83 | //Table names to be excluded 84 | exclude := map[string]int{"user_log": 1} 85 | if err != nil { 86 | fmt.Println(err) 87 | } 88 | if IsFile(file) { 89 | fmt.Fprintf(os.Stderr, "Fatal error: ", "file already exist") 90 | return 91 | } 92 | t := Table{} 93 | t.Message = map[string]Detail{ 94 | "Filter": { 95 | Name: "Filter", 96 | Cat: "custom", 97 | Attr: []AttrDetail{{ 98 | TypeName: "uint64", 99 | AttrName: "id", 100 | }}, 101 | }, 102 | "Request": { 103 | Name: "Request", 104 | Cat: "all", 105 | }, 106 | "Response": { 107 | Name: "Response", 108 | Cat: "custom", 109 | Attr: []AttrDetail{ 110 | { 111 | TypeName: "uint64", 112 | AttrName: "id", 113 | }, 114 | { 115 | TypeName: "bool", 116 | AttrName: "success", 117 | }, 118 | }, 119 | }, 120 | } 121 | 122 | t.PackageModels = "sso" 123 | t.ServiceName = "Sso" 124 | t.Method = map[string]MethodDetail{ 125 | "Get": {Request: t.Message["Filter"], Response: t.Message["Request"]}, 126 | "Create": {Request: t.Message["Request"], Response: t.Message["Response"]}, 127 | "Update": {Request: t.Message["Request"], Response: t.Message["Response"]}, 128 | } 129 | t.TableColumn(db, dbName, exclude) 130 | t.Generate(file, tpl) 131 | } 132 | 133 | //Extract table field information 134 | func (table *Table) TableColumn(db *sql.DB, dbName string, exclude map[string]int) { 135 | rows, err := db.Query("SELECT t.TABLE_NAME,t.TABLE_COMMENT,c.COLUMN_NAME,c.COLUMN_TYPE,c.COLUMN_COMMENT FROM information_schema.TABLES t,INFORMATION_SCHEMA.Columns c WHERE c.TABLE_NAME=t.TABLE_NAME AND t.`TABLE_SCHEMA`='" + dbName + "'") 136 | defer db.Close() 137 | defer rows.Close() 138 | var name, comment string 139 | var column Column 140 | if err != nil { 141 | fmt.Fprintf(os.Stderr, "Fatal error: ", err) 142 | return 143 | } 144 | table.Comment = make(map[string]string) 145 | table.Name = make(map[string][]Column) 146 | for rows.Next() { 147 | rows.Scan(&name, &comment, &column.Field, &column.Type, &column.Comment) 148 | if _, ok := exclude[name]; ok { 149 | continue 150 | } 151 | if _, ok := table.Comment[name]; !ok { 152 | table.Comment[name] = comment 153 | } 154 | 155 | n := strings.Index(column.Type, "(") 156 | if n > 0 { 157 | column.Type = column.Type[0:n] 158 | } 159 | n = strings.Index(column.Type, " ") 160 | if n > 0 { 161 | column.Type = column.Type[0:n] 162 | } 163 | table.Name[name] = append(table.Name[name], column) 164 | } 165 | 166 | if err = rows.Err(); err != nil { 167 | fmt.Fprintf(os.Stderr, "Fatal error: ", err) 168 | return 169 | } 170 | return 171 | } 172 | 173 | //Generate proto files in the current directory 174 | func (table *Table) Generate(filepath, tpl string) { 175 | rpcservers := RpcServers{Models: table.PackageModels, Name: table.ServiceName} 176 | rpcservers.HandleFuncs(table) 177 | rpcservers.HandleMessage(table) 178 | tmpl, err := template.ParseFiles(tpl) 179 | if err != nil { 180 | fmt.Fprintf(os.Stderr, "Fatal error: ", err) 181 | return 182 | } 183 | file, err := os.OpenFile(filepath, os.O_CREATE|os.O_WRONLY, 0755) 184 | err = tmpl.Execute(file, rpcservers) 185 | if err != nil { 186 | fmt.Fprintf(os.Stderr, "Fatal error: ", err) 187 | return 188 | } 189 | } 190 | func Connect(driverName, dsn string) (*sql.DB, error) { 191 | db, err := sql.Open(driverName, dsn) 192 | 193 | if err != nil { 194 | log.Fatalln(err) 195 | } 196 | db.SetMaxIdleConns(0) 197 | db.SetMaxOpenConns(30) 198 | if err := db.Ping(); err != nil { 199 | log.Fatalln(err) 200 | } 201 | return db, err 202 | } 203 | 204 | func (r *RpcServers) HandleFuncs(t *Table) { 205 | var funcParam FuncParam 206 | for key, _ := range t.Comment { 207 | k := StrFirstToUpper(key) 208 | for n, m := range t.Method { 209 | funcParam.Name = n + k 210 | funcParam.ResponseName = k + m.Response.Name 211 | funcParam.RequestName = k + m.Request.Name 212 | r.Funcs = append(r.Funcs, funcParam) 213 | } 214 | } 215 | } 216 | func (r *RpcServers) HandleMessage(t *Table) { 217 | message := Message{} 218 | field := Field{} 219 | var i int 220 | 221 | for key, value := range t.Name { 222 | k := StrFirstToUpper(key) 223 | 224 | for name, detail := range t.Message { 225 | message.Name = k + name 226 | message.MessageDetail = nil 227 | if detail.Cat == "all" { 228 | i = 1 229 | for _, f := range value { 230 | field.AttrName = f.Field 231 | field.TypeName = TypeMToP(f.Type) 232 | field.Num = i 233 | message.MessageDetail = append(message.MessageDetail, field) 234 | i++ 235 | } 236 | } else if detail.Cat == "custom" { 237 | i = 1 238 | for _, f := range detail.Attr { 239 | field.TypeName = f.TypeName 240 | field.AttrName = f.AttrName 241 | field.Num = i 242 | message.MessageDetail = append(message.MessageDetail, field) 243 | i++ 244 | } 245 | } 246 | r.MessageList = append(r.MessageList, message) 247 | } 248 | 249 | } 250 | 251 | } 252 | func TypeMToP(m string) string { 253 | if _, ok := typeArr[m]; ok { 254 | return typeArr[m] 255 | } 256 | return "string" 257 | } 258 | func StrFirstToUpper(str string) string { 259 | temp := strings.Split(str, "_") 260 | var upperStr string 261 | for _, v := range temp { 262 | if len(v) > 0 { 263 | runes := []rune(v) 264 | upperStr += string(runes[0]-32) + string(runes[1:]) 265 | } 266 | } 267 | return upperStr 268 | } 269 | func IsFile(f string) bool { 270 | fi, e := os.Stat(f) 271 | if e != nil { 272 | return false 273 | } 274 | return !fi.IsDir() 275 | } 276 | -------------------------------------------------------------------------------- /template/proto.go.tpl: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package {{.Models}}; 3 | 4 | // The {{.Models}} service definition. 5 | service {{.Name}} { 6 | {{range .Funcs }} rpc {{.Name}}({{.RequestName}}) returns () { {{.ResponseName}} } 7 | {{ end }} 8 | } 9 | {{range .MessageList }} 10 | message {{.Name}} { 11 | {{range .MessageDetail }} {{.TypeName}} {{.AttrName}}={{.Num}} 12 | {{ end }} 13 | } 14 | {{ end }} 15 | 16 | --------------------------------------------------------------------------------