├── .dockerignore ├── .github └── workflows │ ├── mail.yml │ └── releaser.yml ├── .gitignore ├── .goreleaser.yml ├── LICENSE ├── README.md ├── api ├── .env ├── .gitignore ├── Dockerfile ├── Makefile └── main.go ├── generator ├── generator.go ├── generator_test.go └── util.go ├── go.mod ├── go.sum ├── main.go └── web ├── .dockerignore ├── .editorconfig ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .umirc.ts ├── Caddyfile ├── Dockerfile ├── README.md ├── mock └── .gitkeep ├── nginx.conf ├── package.json ├── src ├── pages │ ├── index.js │ └── index.less ├── service │ └── index.js └── util │ ├── error.js │ ├── index.js │ ├── mapping.js │ ├── request.js │ └── sync.js ├── tsconfig.json └── typings.d.ts /.dockerignore: -------------------------------------------------------------------------------- 1 | /web -------------------------------------------------------------------------------- /.github/workflows/mail.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: [push] 3 | jobs: 4 | 5 | build: 6 | name: Build 7 | runs-on: ubuntu-latest 8 | steps: 9 | 10 | - name: Set up Go 1.13 11 | uses: actions/setup-go@v1 12 | with: 13 | go-version: 1.13 14 | id: go 15 | 16 | - name: Set up Node 17 | uses: actions/setup-node@v1 18 | with: 19 | node-version: '10.x' 20 | 21 | - name: Check out code into the Go module directory 22 | uses: actions/checkout@v2 23 | 24 | - name: Build Golang api 25 | run: | 26 | cd api 27 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app main.go 28 | 29 | - name: Build web 30 | run: | 31 | cd web 32 | npm install && npm run build 33 | 34 | - name: Build the Docker image 35 | run: | 36 | cd api 37 | echo ${{secrets.ENVFILE}} > .env 38 | docker login --username=${{secrets.DOCKER_USERNAME}} --password=${{secrets.DOCKER_PASSWORD}} registry.cn-shanghai.aliyuncs.com 39 | docker build . --file Dockerfile --tag registry.cn-shanghai.aliyuncs.com/fifsky/gostruct 40 | docker push registry.cn-shanghai.aliyuncs.com/fifsky/gostruct 41 | cd ../web 42 | docker build . --file Dockerfile --tag registry.cn-shanghai.aliyuncs.com/fifsky/gostruct-web 43 | docker push registry.cn-shanghai.aliyuncs.com/fifsky/gostruct-web 44 | 45 | - name: Pull docker and restart 46 | run: | 47 | curl -s "https://hook.fifsky.com/?token=${{secrets.WEBHOOK_TOKEN}}&script=go-struct" 48 | 49 | - name: Dingtalk message 50 | uses: fifsky/dingtalk-action@master 51 | with: 52 | url: ${{ secrets.DINGTALK_WEBHOOK}} 53 | type: markdown 54 | content: | 55 | ## Github Action 56 | > genstruct deploy successful 57 | > ^_^ -------------------------------------------------------------------------------- /.github/workflows/releaser.yml: -------------------------------------------------------------------------------- 1 | name: goreleaser 2 | 3 | on: 4 | release: 5 | 6 | jobs: 7 | goreleaser: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v1 12 | - name: Set up Go 13 | uses: actions/setup-go@v1 14 | with: 15 | go-version: "1.13" 16 | - name: Run GoReleaser 17 | uses: goreleaser/goreleaser-action@v1 18 | with: 19 | version: latest 20 | args: release --rm-dist 21 | env: 22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | genstruct -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | # This is an example goreleaser.yaml file with some sane defaults. 2 | # Make sure to check the documentation at http://goreleaser.com 3 | before: 4 | hooks: 5 | # you may remove this if you don't use vgo 6 | - go mod tidy 7 | # you may remove this if you don't need go generate 8 | - go generate ./... 9 | builds: 10 | - env: 11 | - CGO_ENABLED=0 12 | goos: 13 | - linux 14 | - darwin 15 | - windows 16 | binary: genstruct 17 | ldflags: 18 | - -s -w 19 | archives: 20 | - replacements: 21 | darwin: Darwin 22 | linux: Linux 23 | windows: Windows 24 | # 386: i386 25 | amd64: x86_64 26 | checksum: 27 | name_template: "checksums.txt" 28 | snapshot: 29 | name_template: "{{ .Tag }}-next" 30 | changelog: 31 | sort: asc 32 | filters: 33 | exclude: 34 | - "^docs:" 35 | - "^test:" 36 | - "^ci:" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Xudong Cai 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # genstruct 2 | ![build](https://github.com/fifsky/genstruct/workflows/build/badge.svg) 3 | 4 | Golang struct generator from mysql schema 5 | 6 | [![asciicast](https://asciinema.org/a/12i6QmbaUCQgPZ4o2rz5QmPVE.png)](https://asciinema.org/a/12i6QmbaUCQgPZ4o2rz5QmPVE) 7 | 8 | ## Install 9 | 10 | ``` 11 | go install github.com/fifsky/genstruct 12 | ``` 13 | 14 | ## Usage 15 | 16 | ``` 17 | genstruct -h localhost -u root -p 3306 -P 123456 18 | ``` 19 | 20 | * `-h` default `localhost` 21 | * `-u` default `root` 22 | * `-p` default `3306` 23 | 24 | ## online 25 | 26 | https://go.fifsky.com/ 27 | 28 | ## gosql 29 | 30 | The structure can be applied to a [gosql](https://github.com/ilibs/gosql) package 31 | 32 | ## License 33 | 34 | The source code is available under the MIT [License](/LICENSE). -------------------------------------------------------------------------------- /api/.env: -------------------------------------------------------------------------------- 1 | database.dsn="root:123456@tcp(127.0.0.1:3306)/genstruct?charset=utf8&parseTime=True&loc=Asia%2FShanghai" -------------------------------------------------------------------------------- /api/.gitignore: -------------------------------------------------------------------------------- 1 | app -------------------------------------------------------------------------------- /api/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | RUN apk update && \ 3 | apk add --no-cache ca-certificates \ 4 | tzdata 5 | 6 | WORKDIR /app 7 | 8 | COPY ./app ./ 9 | COPY .env ./ 10 | 11 | ENV TZ=Asia/Shanghai 12 | EXPOSE 80 13 | 14 | ENTRYPOINT ["./app","--addr=:80"] -------------------------------------------------------------------------------- /api/Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | go build -o app -------------------------------------------------------------------------------- /api/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "net/http" 10 | "strings" 11 | 12 | _ "github.com/go-sql-driver/mysql" 13 | "github.com/goapt/dotenv" 14 | "github.com/ilibs/gosql/v2" 15 | "github.com/rs/cors" 16 | 17 | "github.com/fifsky/genstruct/generator" 18 | ) 19 | 20 | func main() { 21 | port := flag.String("addr", ":8989", "addr ip:port") 22 | flag.Parse() 23 | 24 | m, err := dotenv.Read() 25 | 26 | if err != nil { 27 | log.Fatal("load env file error:", err) 28 | } 29 | 30 | configs := make(map[string]*gosql.Config) 31 | configs["default"] = &gosql.Config{ 32 | Enable: true, 33 | Driver: "mysql", 34 | Dsn: m["database.dsn"], 35 | ShowSql: false, 36 | } 37 | gosql.FatalExit = false 38 | err = gosql.Connect(configs) 39 | 40 | if err != nil { 41 | log.Fatal(err) 42 | } 43 | 44 | db := gosql.Use("default") 45 | gen := generator.NewGenerator(db) 46 | c := cors.AllowAll() 47 | handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 48 | body, err := ioutil.ReadAll(r.Body) 49 | if err != nil { 50 | w.Write([]byte(fmt.Sprintf("request body read error \n%s", err))) 51 | return 52 | } 53 | 54 | p := &struct { 55 | Table string `json:"table"` 56 | Tags []string `json:"tags"` 57 | }{} 58 | 59 | err = json.Unmarshal(body, p) 60 | if err != nil { 61 | w.Write([]byte(fmt.Sprintf("request body json Unmarshal error \n%s", err))) 62 | return 63 | } 64 | 65 | if len(p.Table) > 10000 || len(p.Table) < 20 { 66 | w.Write([]byte(fmt.Sprintf("content length must < 10000 byte\n"))) 67 | return 68 | } 69 | 70 | if !strings.Contains(strings.ToLower(p.Table[0:20]), "create table") { 71 | w.Write([]byte(fmt.Sprintf("only support create table syntax\n"))) 72 | return 73 | } 74 | 75 | _, err = db.Exec(p.Table) 76 | if err != nil { 77 | w.Write([]byte(fmt.Sprintf("create table error \n%s", err))) 78 | return 79 | } 80 | 81 | var tableName string 82 | defer func() { 83 | _, err = db.Exec(fmt.Sprintf("drop table `%s`", tableName)) 84 | if err != nil { 85 | log.Println("drop table error", err) 86 | } 87 | }() 88 | 89 | rows := db.QueryRowx("show tables") 90 | if err != nil { 91 | w.Write([]byte(fmt.Sprintf("show tables error \n%s", err))) 92 | return 93 | } 94 | 95 | err = rows.Scan(&tableName) 96 | if err != nil { 97 | w.Write([]byte(fmt.Sprintf("scan table name error \n%s", err))) 98 | return 99 | } 100 | 101 | st, err := gen.ShowStruct(tableName, p.Tags) 102 | if err != nil { 103 | w.Write([]byte(fmt.Sprintf("generate struct error \n%s", err))) 104 | return 105 | } 106 | w.Write(st) 107 | }) 108 | 109 | http.Handle("/genapi/struct/gen", c.Handler(handler)) 110 | 111 | err = http.ListenAndServe(*port, nil) 112 | 113 | if err != nil { 114 | log.Fatal("ListenAndServe", err) 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /generator/generator.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "go/format" 7 | "os" 8 | "text/template" 9 | "time" 10 | 11 | "github.com/ilibs/gosql/v2" 12 | "github.com/olekukonko/tablewriter" 13 | ) 14 | 15 | const tmplContent = ` 16 | package {{ .TableName }} 17 | {{ if .ExistTime }} 18 | import ( 19 | "time" 20 | ) 21 | {{end}} 22 | type {{ .StructName }} struct { 23 | {{ range $i,$v := .Columns }}{{ .StructField }} {{ .Type }} ` + "\u0060" + `{{ range $j,$tag := $.OtherTags }}{{ $tag }}:"{{ $v.Field }}"{{ if ne $j $.Len }} {{ end }}{{ end }}` + "\u0060" + `{{ if ne .Comment "" }} // {{.Comment}}{{ end }}{{ if ne $i $.Len }}` + "\n" + `{{ end }}{{ end }} 24 | } 25 | 26 | func ({{ .ShortName }} *{{ .StructName }}) TableName() string { 27 | return "{{ .TableName }}" 28 | } 29 | 30 | func ({{ .ShortName }} *{{ .StructName }}) PK() string { 31 | return "{{ .PrimaryKey }}" 32 | } 33 | ` 34 | 35 | type Attr struct { 36 | StructField string 37 | Field string 38 | Type string 39 | Comment string 40 | } 41 | 42 | type TableInfo struct { 43 | Columns []*Attr 44 | Len int 45 | OtherTags []string 46 | TagLen int 47 | TableName string 48 | ShortName string 49 | StructName string 50 | Database string 51 | PrimaryKey string 52 | ExistTime bool 53 | } 54 | 55 | type Generator struct { 56 | db *gosql.DB 57 | } 58 | 59 | func NewGenerator(db *gosql.DB) *Generator { 60 | return &Generator{db: db} 61 | } 62 | 63 | func (g *Generator) Exec(query string) ([]map[string]interface{}, error) { 64 | rows, err := gosql.Queryx(query) 65 | if err != nil { 66 | return nil, err 67 | } 68 | 69 | var datas []map[string]interface{} 70 | 71 | for rows.Next() { 72 | data := make(map[string]interface{}) 73 | rows.MapScan(data) 74 | datas = append(datas, data) 75 | } 76 | return datas, nil 77 | } 78 | 79 | func (g *Generator) ShowTable(datas []map[string]interface{}, start time.Time) { 80 | if len(datas) > 0 { 81 | header, cells := formatTable(datas) 82 | table := tablewriter.NewWriter(os.Stdout) 83 | table.SetHeader(header) 84 | table.AppendBulk(cells) 85 | table.Render() 86 | end := time.Now() 87 | fmt.Println(fmt.Sprintf("%d rows in set (%.2f sec)", len(cells), float64(end.UnixNano()-start.UnixNano())/1e9)) 88 | } else { 89 | fmt.Println("No Result") 90 | } 91 | } 92 | 93 | func (g *Generator) ShowStruct(table string, tags []string) ([]byte, error) { 94 | query := fmt.Sprintf("SHOW FULL COLUMNS FROM %s", table) 95 | datas, err := g.Exec(query) 96 | if err != nil { 97 | return nil, err 98 | } 99 | 100 | var databaseName string 101 | err = gosql.QueryRowx("select database()").Scan(&databaseName) 102 | 103 | if err != nil { 104 | return nil, err 105 | } 106 | 107 | info := &TableInfo{ 108 | OtherTags: tags, 109 | TagLen: len(tags), 110 | Columns: make([]*Attr, 0), 111 | TableName: table, 112 | ShortName: table[0:1], 113 | StructName: titleCasedName(table), 114 | Database: databaseName, 115 | } 116 | 117 | var existTime = false 118 | for _, v := range datas { 119 | m := mapToString(v) 120 | tp := typeFormat(m["Type"], m["Null"]) 121 | 122 | if tp == "time.Time" { 123 | existTime = true 124 | } 125 | 126 | attr := &Attr{ 127 | StructField: titleCasedName(m["Field"]), 128 | Field: m["Field"], 129 | Type: tp, 130 | Comment: m["Comment"], 131 | } 132 | 133 | info.Columns = append(info.Columns, attr) 134 | if m["Key"] == "PRI" { 135 | info.PrimaryKey = attr.Field 136 | } 137 | } 138 | 139 | info.ExistTime = existTime 140 | 141 | info.Len = len(info.Columns) - 1 142 | 143 | tmpl, err := template.New("struct").Parse(tmplContent) 144 | if err != nil { 145 | return nil, err 146 | } 147 | 148 | var buf bytes.Buffer 149 | 150 | err = tmpl.Execute(&buf, info) 151 | if err != nil { 152 | return nil, err 153 | } 154 | 155 | return format.Source(buf.Bytes()) 156 | } 157 | -------------------------------------------------------------------------------- /generator/generator_test.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "testing" 7 | 8 | _ "github.com/go-sql-driver/mysql" 9 | "github.com/ilibs/gosql/v2" 10 | ) 11 | 12 | func TestMain(m *testing.M) { 13 | configs := make(map[string]*gosql.Config) 14 | 15 | dsn := os.Getenv("MYSQL_TEST_DSN") 16 | 17 | if dsn == "" { 18 | dsn = "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8&parseTime=True&loc=Asia%2FShanghai" 19 | } 20 | 21 | configs["default"] = &gosql.Config{ 22 | Enable: true, 23 | Driver: "mysql", 24 | Dsn: dsn, 25 | ShowSql: true, 26 | } 27 | 28 | gosql.Connect(configs) 29 | 30 | m.Run() 31 | } 32 | 33 | func TestShowStruct(t *testing.T) { 34 | gen := NewGenerator(gosql.Use("default")) 35 | out, err := gen.ShowStruct("articles", "form") 36 | 37 | if err != nil { 38 | t.Error(err) 39 | } 40 | 41 | fmt.Println(string(out)) 42 | } 43 | -------------------------------------------------------------------------------- /generator/util.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "sort" 7 | "strings" 8 | ) 9 | 10 | // sortedParamKeys Sorts the param names given - map iteration order is explicitly random in Go 11 | // but we need params in a defined order to avoid unexpected results. 12 | func sortedParamKeys(params map[string]interface{}) []string { 13 | sortedKeys := make([]string, len(params)) 14 | i := 0 15 | for k := range params { 16 | sortedKeys[i] = k 17 | i++ 18 | } 19 | sort.Strings(sortedKeys) 20 | 21 | return sortedKeys 22 | } 23 | 24 | func sortedMapToString(header []string, params map[string]interface{}) (cell []string) { 25 | for _, k := range header { 26 | cell = append(cell, fmt.Sprintf("%s", params[k])) 27 | } 28 | return cell 29 | } 30 | 31 | func mapToString(params map[string]interface{}) map[string]string { 32 | m := make(map[string]string) 33 | for k, v := range params { 34 | m[k] = fmt.Sprintf("%s", v) 35 | } 36 | return m 37 | } 38 | 39 | func formatTable(datas []map[string]interface{}) (header []string, cells [][]string) { 40 | for k, v := range datas { 41 | if k == 0 { 42 | header = sortedParamKeys(v) 43 | } 44 | cells = append(cells, sortedMapToString(header, v)) 45 | } 46 | 47 | return header, cells 48 | } 49 | 50 | func GetParams(cmds []string, i int) (string, error) { 51 | if len(cmds) < i+1 { 52 | return "", errors.New(fmt.Sprintf("not index(%d) params", i)) 53 | } 54 | 55 | return strings.TrimSpace(cmds[i]), nil 56 | } 57 | 58 | func typeFormat(t string, isNull string) string { 59 | if t == "datetime" || t == "date" || t == "time" { 60 | if isNull == "YES" { 61 | return "sql.NullTime" 62 | } 63 | return "time.Time" 64 | } 65 | 66 | if len(t) >= 6 && t[0:6] == "bigint" { 67 | if isNull == "YES" { 68 | return "sql.NullInt64" 69 | } 70 | return "int64" 71 | } 72 | 73 | if strings.Index(t, "int") != -1 || strings.Index(t, "tinyint") != -1 { 74 | if isNull == "YES" { 75 | return "sql.NullInt64" 76 | } 77 | 78 | return "int" 79 | } 80 | 81 | if strings.Index(t, "decimal") != -1 || strings.Index(t, "float") != -1 || strings.Index(t, "double") != -1 { 82 | if isNull == "YES" { 83 | return "sql.NullFloat64" 84 | } 85 | 86 | return "float64" 87 | } 88 | 89 | if isNull == "YES" { 90 | return "sql.NullString" 91 | } 92 | 93 | return "string" 94 | } 95 | 96 | func titleCasedName(name string) string { 97 | newstr := make([]rune, 0) 98 | upNextChar := true 99 | 100 | name = strings.ToLower(name) 101 | 102 | for _, chr := range name { 103 | switch { 104 | case upNextChar: 105 | upNextChar = false 106 | if 'a' <= chr && chr <= 'z' { 107 | chr -= 'a' - 'A' 108 | } 109 | case chr == '_': 110 | upNextChar = true 111 | continue 112 | } 113 | 114 | newstr = append(newstr, chr) 115 | } 116 | 117 | return string(newstr) 118 | } 119 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/fifsky/genstruct 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/go-sql-driver/mysql v1.5.0 7 | github.com/goapt/dotenv v0.0.1 8 | github.com/ilibs/gosql/v2 v2.0.2 9 | github.com/mattn/go-runewidth v0.0.8 // indirect 10 | github.com/olekukonko/tablewriter v0.0.4 11 | github.com/rs/cors v1.7.0 12 | ) 13 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= 2 | github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= 3 | github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 4 | github.com/goapt/dotenv v0.0.1 h1:gLCP/PAmwiexmKv+r2Ma2XeHTKZ3pg3DOqjCtOs6Hu8= 5 | github.com/goapt/dotenv v0.0.1/go.mod h1:E+eFec2nYn8NVJX7UFRUt90SS0u5rkLEnnew6cukWXI= 6 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 7 | github.com/ilibs/gosql/v2 v2.0.2 h1:JpFk3zublDhwQj5vA/Cy9J8zDnDAM+dhzKlkc2LJ/wY= 8 | github.com/ilibs/gosql/v2 v2.0.2/go.mod h1:kU5sf2paHLCtYaM446ZHYBxwlFo4Kuk/BGrKEJfb7x4= 9 | github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= 10 | github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= 11 | github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= 12 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 13 | github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54= 14 | github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 15 | github.com/mattn/go-runewidth v0.0.8 h1:3tS41NlGYSmhhe/8fhGRzc+z3AYCw1Fe1WAyLuujKs0= 16 | github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 17 | github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4= 18 | github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 19 | github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8= 20 | github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= 21 | github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= 22 | github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= 23 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 24 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 25 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 26 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 27 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 28 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 29 | google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= 30 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 31 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "flag" 6 | "fmt" 7 | "os" 8 | "strings" 9 | "time" 10 | 11 | _ "github.com/go-sql-driver/mysql" 12 | "github.com/ilibs/gosql/v2" 13 | 14 | "github.com/fifsky/genstruct/generator" 15 | ) 16 | 17 | var ( 18 | host = flag.String("h", "localhost", "database host") 19 | user = flag.String("u", "root", "database user") 20 | password = flag.String("P", "", "database passwrd") 21 | port = flag.String("p", "3306", "database port") 22 | ) 23 | 24 | func link(database string) error { 25 | configs := make(map[string]*gosql.Config) 26 | configs["default"] = &gosql.Config{ 27 | Enable: true, 28 | Driver: "mysql", 29 | Dsn: fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", *user, *password, *host, *port, database) + "?charset=utf8&parseTime=True&loc=Asia%2FShanghai", 30 | ShowSql: false, 31 | } 32 | return gosql.Connect(configs) 33 | } 34 | 35 | func main() { 36 | flag.Parse() 37 | gosql.FatalExit = false 38 | err := link("") 39 | 40 | if err != nil { 41 | fmt.Println(err) 42 | os.Exit(1) 43 | } 44 | 45 | gen := generator.NewGenerator(gosql.Use("default")) 46 | 47 | input := bufio.NewScanner(os.Stdin) 48 | fmt.Print("> ") 49 | for input.Scan() { 50 | func() (err error) { 51 | defer func() { 52 | if err != nil { 53 | fmt.Println(err) 54 | } 55 | fmt.Print("> ") 56 | }() 57 | 58 | line := strings.TrimRight(strings.TrimSpace(input.Text()), ";") 59 | 60 | if line == "" { 61 | return 62 | } 63 | 64 | cmds := strings.Split(line, " ") 65 | 66 | switch cmds[0] { 67 | case "use": 68 | cmd, err := generator.GetParams(cmds, 1) 69 | if err != nil { 70 | return err 71 | } 72 | err = link(cmd) 73 | if err == nil { 74 | fmt.Println("Database changed") 75 | } 76 | return err 77 | 78 | case "g": 79 | cmd, err := generator.GetParams(cmds, 1) 80 | if err != nil { 81 | return err 82 | } 83 | 84 | tag, _ := generator.GetParams(cmds, 2) 85 | tags := strings.Split(tag, ",") 86 | if len(tags) == 0 { 87 | tags = []string{"db", "json"} 88 | } 89 | 90 | out, err := gen.ShowStruct(cmd, tags) 91 | if err != nil { 92 | return err 93 | } 94 | fmt.Println(string(out)) 95 | case "exit": 96 | fmt.Println("Bye!") 97 | os.Exit(0) 98 | default: 99 | start := time.Now() 100 | datas, err := gen.Exec(line) 101 | if err != nil { 102 | return err 103 | } 104 | gen.ShowTable(datas, start) 105 | } 106 | return 107 | }() 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /web/.dockerignore: -------------------------------------------------------------------------------- 1 | /node_modules -------------------------------------------------------------------------------- /web/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [Makefile] 16 | indent_style = tab 17 | -------------------------------------------------------------------------------- /web/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /npm-debug.log* 6 | /yarn-error.log 7 | /yarn.lock 8 | /package-lock.json 9 | 10 | # production 11 | /dist 12 | 13 | # misc 14 | .DS_Store 15 | 16 | # umi 17 | /src/.umi 18 | /src/.umi-production 19 | /src/.umi-test 20 | /.env.local 21 | -------------------------------------------------------------------------------- /web/.prettierignore: -------------------------------------------------------------------------------- 1 | **/*.md 2 | **/*.svg 3 | **/*.ejs 4 | **/*.html 5 | package.json 6 | .umi 7 | .umi-production 8 | .umi-test 9 | -------------------------------------------------------------------------------- /web/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "printWidth": 80, 5 | "overrides": [ 6 | { 7 | "files": ".prettierrc", 8 | "options": { "parser": "json" } 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /web/.umirc.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'umi'; 2 | 3 | export default defineConfig({ 4 | routes: [{ path: '/', component: '@/pages/index' }], 5 | proxy: { 6 | '/api': { 7 | target: 'http://127.0.0.1:8989', 8 | changeOrigin: true, 9 | }, 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /web/Caddyfile: -------------------------------------------------------------------------------- 1 | :2015 2 | gzip 3 | root /usr/share/caddy/ 4 | -------------------------------------------------------------------------------- /web/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:alpine 2 | COPY ./nginx.conf /etc/nginx/conf.d/default.conf 3 | COPY ./dist/ /usr/share/nginx/html/ 4 | -------------------------------------------------------------------------------- /web/README.md: -------------------------------------------------------------------------------- 1 | # MySQL Structure to Golang Struct Online 2 | 3 | https://go.fifsky.com/ 4 | 5 | ## Getting Started 6 | 7 | Install dependencies, 8 | 9 | ```bash 10 | $ yarn 11 | ``` 12 | 13 | Start the dev server, 14 | 15 | ```bash 16 | $ yarn start 17 | ``` 18 | 19 | 20 | -------------------------------------------------------------------------------- /web/mock/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fifsky/genstruct/a66c93d6297713b3659f08f6baa4e17f921a4e9d/web/mock/.gitkeep -------------------------------------------------------------------------------- /web/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | index index.html index.htm; 4 | root /usr/share/nginx/html/; 5 | 6 | location / { 7 | try_files $uri $uri/ /index.html; 8 | } 9 | } -------------------------------------------------------------------------------- /web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "start": "umi dev", 5 | "build": "umi build", 6 | "prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'", 7 | "test": "umi-test", 8 | "test:coverage": "umi-test --coverage" 9 | }, 10 | "gitHooks": { 11 | "pre-commit": "lint-staged" 12 | }, 13 | "lint-staged": { 14 | "*.{js,jsx,less,md,json}": [ 15 | "prettier --write" 16 | ], 17 | "*.ts?(x)": [ 18 | "prettier --parser=typescript --write" 19 | ] 20 | }, 21 | "dependencies": { 22 | "@umijs/preset-react": "^1.4.3", 23 | "@umijs/test": "^3.0.10", 24 | "lint-staged": "^10.0.7", 25 | "object-path": "^0.11.4", 26 | "prettier": "^1.19.1", 27 | "react": "^16.12.0", 28 | "react-dom": "^16.12.0", 29 | "umi": "^3.0.10", 30 | "yorkie": "^2.0.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /web/src/pages/index.js: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react'; 2 | import {Button, Card, Col, Input, Row, Select} from 'antd'; 3 | import {genApi} from "../service"; 4 | import {sync} from "../util"; 5 | 6 | const {TextArea} = Input; 7 | const { Option } = Select; 8 | 9 | export default () => { 10 | const [schema, setSchema] = useState(''); 11 | const [struct, setStruct] = useState(''); 12 | const [tags, setTags] = useState(["db", "json"]); 13 | 14 | const onChange = ({target: {value}}) => { 15 | setSchema(value); 16 | }; 17 | 18 | const handleTags = (value) => { 19 | setTags(value); 20 | }; 21 | 22 | const onConvert = () => { 23 | if (schema === "") { 24 | return 25 | } 26 | sync(async function () { 27 | const ret = await genApi({"table": schema, "tags": tags}) 28 | setStruct(ret); 29 | }) 30 | }; 31 | 32 | return ( 33 |
34 | 38 | Convert 39 | 40 | } 41 | > 42 | 43 | 46 | 47 | 48 | 49 |