diff --git a/cmd/gormgen/README.MD b/cmd/gormgen/README.MD new file mode 100644 index 0000000..23fdcc2 --- /dev/null +++ b/cmd/gormgen/README.MD @@ -0,0 +1,6 @@ +## 执行命令 +1. 定义生成的表,设置 config 中 cmd.genTables,可以自定义设置多张表,为空表示生成库中所有的表,如果设置多个表可用','分割; +1. 在根目录下执行脚本文件:`./scripts/gormgen.sh`; + +## 参考 +- https://github.com/MohamedBassem/gormgen \ No newline at end of file diff --git a/cmd/gormgen/main.go b/cmd/gormgen/main.go new file mode 100644 index 0000000..5e5e30f --- /dev/null +++ b/cmd/gormgen/main.go @@ -0,0 +1,37 @@ +package main + +import ( + "flag" + "log" + "os" + "strings" + + "github.com/xinliangnote/go-gin-api/cmd/gormgen/pkg" +) + +var ( + input string + structs []string +) + +func init() { + flagStructs := flag.String("structs", "", "[Required] The name of schema structs to generate structs for, comma seperated\n") + flagInput := flag.String("input", "", "[Required] The name of the input file dir\n") + flag.Parse() + + if *flagStructs == "" || *flagInput == "" { + flag.Usage() + os.Exit(1) + } + + structs = strings.Split(*flagStructs, ",") + input = *flagInput +} + +func main() { + gen := pkg.NewGenerator(input) + p := pkg.NewParser(input) + if err := gen.ParserAST(p, structs).Generate().Format().Flush(); err != nil { + log.Fatalln(err) + } +} diff --git a/cmd/gormgen/pkg/generator.go b/cmd/gormgen/pkg/generator.go new file mode 100644 index 0000000..5ae0d90 --- /dev/null +++ b/cmd/gormgen/pkg/generator.go @@ -0,0 +1,124 @@ +package pkg + +import ( + "bytes" + "errors" + "go/format" + "io/ioutil" + "log" + "strings" + + "github.com/jinzhu/gorm" +) + +// fieldConfig +type fieldConfig struct { + FieldName string + ColumnName string + FieldType string + HumpName string +} + +// structConfig +type structConfig struct { + config + StructName string + OnlyFields []fieldConfig + OptionFields []fieldConfig +} + +type ImportPkg struct { + Pkg string +} + +type structHelpers struct { + Titelize func(string) string +} + +type config struct { + PkgName string + Helpers structHelpers + QueryBuilderName string +} + +// The Generator is the one responsible for generating the code, adding the imports, formating, and writing it to the file. +type Generator struct { + buf map[string]*bytes.Buffer + inputFile string + config config + structConfigs []structConfig +} + +// NewGenerator function creates an instance of the generator given the name of the output file as an argument. +func NewGenerator(outputFile string) *Generator { + return &Generator{ + buf: map[string]*bytes.Buffer{}, + inputFile: outputFile, + } +} + +// ParserAST parse by go file +func (g *Generator) ParserAST(p *Parser, structs []string) (ret *Generator) { + for _, v := range structs { + g.buf[gorm.ToDBName(v)] = new(bytes.Buffer) + } + g.structConfigs = p.Parse() + g.config.PkgName = p.pkg.Name + g.config.Helpers = structHelpers{ + Titelize: strings.Title, + } + g.config.QueryBuilderName = SQLColumnToHumpStyle(p.pkg.Name) + "QueryBuilder" + return g +} + +func (g *Generator) checkConfig() (err error) { + if len(g.config.PkgName) == 0 { + err = errors.New("package name dose'n set") + return + } + for i := 0; i < len(g.structConfigs); i++ { + g.structConfigs[i].config = g.config + } + return +} + +// Generate executes the template and store it in an internal buffer. +func (g *Generator) Generate() *Generator { + if err := g.checkConfig(); err != nil { + panic(err) + } + + for _, v := range g.structConfigs { + if _, ok := g.buf[gorm.ToDBName(v.StructName)]; !ok { + continue + } + if err := outputTemplate.Execute(g.buf[gorm.ToDBName(v.StructName)], v); err != nil { + panic(err) + } + } + + return g +} + +// Format function formats the output of the generation. +func (g *Generator) Format() *Generator { + for k := range g.buf { + formattedOutput, err := format.Source(g.buf[k].Bytes()) + if err != nil { + panic(err) + } + g.buf[k] = bytes.NewBuffer(formattedOutput) + } + return g +} + +// Flush function writes the output to the output file. +func (g *Generator) Flush() error { + for k := range g.buf { + filename := g.inputFile + "/gen_" + strings.ToLower(k) + ".go" + if err := ioutil.WriteFile(filename, g.buf[k].Bytes(), 0777); err != nil { + log.Fatalln(err) + } + } + return nil +} diff --git a/cmd/gormgen/pkg/parser.go b/cmd/gormgen/pkg/parser.go new file mode 100644 index 0000000..7ab7fc3 --- /dev/null +++ b/cmd/gormgen/pkg/parser.go @@ -0,0 +1,120 @@ +package pkg + +import ( + "go/ast" + "go/build" + "go/parser" + "go/token" + "log" + "strings" + + "github.com/jinzhu/gorm" +) + +// The Parser is used to parse a directory and expose information about the structs defined in the files of this directory. +type Parser struct { + dir string + pkg *build.Package + parsedFiles []*ast.File +} + +// NewParser create a new parser instance. +func NewParser(dir string) *Parser { + return &Parser{ + dir: dir, + } +} + +// getPackage parse dir get go file and package +func (p *Parser) getPackage() { + pkg, err := build.Default.ImportDir(p.dir, build.ImportComment) + if err != nil { + log.Fatalf("cannot process directory %s: %s", p.dir, err) + } + p.pkg = pkg + +} + +// parseGoFiles parse go file +func (p *Parser) parseGoFiles() { + var parsedFiles []*ast.File + fs := token.NewFileSet() + for _, file := range p.pkg.GoFiles { + file = p.dir + "/" + file + parsedFile, err := parser.ParseFile(fs, file, nil, 0) + if err != nil { + log.Fatalf("parsing package: %s: %s\n", file, err) + } + parsedFiles = append(parsedFiles, parsedFile) + } + p.parsedFiles = parsedFiles +} + +// parseTypes parse type of struct +func (p *Parser) parseTypes(file *ast.File) (ret []structConfig) { + ast.Inspect(file, func(n ast.Node) bool { + decl, ok := n.(*ast.GenDecl) + if !ok || decl.Tok != token.TYPE { + return true + } + + for _, spec := range decl.Specs { + var ( + data structConfig + ) + typeSpec, _ok := spec.(*ast.TypeSpec) + if !_ok { + continue + } + // We only care about struct declaration (for now) + var structType *ast.StructType + if structType, ok = typeSpec.Type.(*ast.StructType); !ok { + continue + } + + data.StructName = typeSpec.Name.Name + for _, v := range structType.Fields.List { + var ( + optionField fieldConfig + ) + + // type is ident, get onlyField type + if t, _ok := v.Type.(*ast.Ident); _ok { + optionField.FieldType = t.String() + } else { + if v.Tag != nil { + if strings.Contains(v.Tag.Value, "gorm") && strings.Contains(v.Tag.Value, "time") { + optionField.FieldType = "time.Time" + } + } + } + + // get file name + if len(v.Names) > 0 { + optionField.FieldName = v.Names[0].String() + optionField.ColumnName = gorm.ToDBName(optionField.FieldName) + optionField.HumpName = SQLColumnToHumpStyle(optionField.ColumnName) + } + + data.OptionFields = append(data.OptionFields, optionField) + } + + ret = append(ret, data) + } + return true + }) + return +} + +// Parse should be called before any type querying for the parser. It takes the directory to be parsed and extracts all the structs defined in this directory. +func (p *Parser) Parse() (ret []structConfig) { + var ( + data []structConfig + ) + p.getPackage() + p.parseGoFiles() + for _, f := range p.parsedFiles { + data = append(data, p.parseTypes(f)...) + } + return data +} diff --git a/cmd/gormgen/pkg/template.go b/cmd/gormgen/pkg/template.go new file mode 100644 index 0000000..f31a0f7 --- /dev/null +++ b/cmd/gormgen/pkg/template.go @@ -0,0 +1,149 @@ +package pkg + +import "text/template" + +// Make sure that the template compiles during package initialization +func parseTemplateOrPanic(t string) *template.Template { + tpl, err := template.New("output_template").Parse(t) + if err != nil { + panic(err) + } + return tpl +} + +var outputTemplate = parseTemplateOrPanic(` +/////////////////////////////////////////////////////////// +// THIS FILE IS AUTO GENERATED by gormgen, DON'T EDIT IT // +// ANY CHANGES DONE HERE WILL BE LOST // +/////////////////////////////////////////////////////////// + +package {{.PkgName}} + +import ( + "fmt" + "time" + + "github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo" + + "github.com/pkg/errors" + "gorm.io/gorm" +) + +func NewModel() *{{.StructName}} { + return new({{.StructName}}) +} + +func NewQueryBuilder() *{{.QueryBuilderName}} { + return new({{.QueryBuilderName}}) +} + +func (t *{{.StructName}}) Create(db *gorm.DB) (id int32, err error) { + if err = db.Create(t).Error; err != nil { + return 0, errors.Wrap(err, "create err") + } + return t.Id, nil +} + +func (t *{{.StructName}}) Delete(db *gorm.DB) (err error) { + if err = db.Delete(t).Error; err != nil { + return errors.Wrap(err, "delete err") + } + return nil +} + +func (t *{{.StructName}}) Updates(db *gorm.DB, m map[string]interface{}) (err error) { + if err = db.Model(&UserDemo{}).Where("id = ?", t.Id).Updates(m).Error; err != nil { + return errors.Wrap(err, "updates err") + } + return nil +} + +type {{.QueryBuilderName}} struct { + order []string + where []struct { + prefix string + value interface{} + } + limit int + offset int +} + +func (qb *{{.QueryBuilderName}}) buildQuery(db *gorm.DB) *gorm.DB { + ret := db + for _, where := range qb.where { + ret = ret.Where(where.prefix, where.value) + } + for _, order := range qb.order { + ret = ret.Order(order) + } + ret = ret.Limit(qb.limit).Offset(qb.offset) + return ret +} + +func (qb *{{.QueryBuilderName}}) Count(db *gorm.DB) (int64, error) { + var c int64 + res := qb.buildQuery(db).Model(&{{.StructName}}{}).Count(&c) + if res.Error != nil && res.Error == gorm.ErrRecordNotFound { + c = 0 + } + return c, res.Error +} + +func (qb *{{.QueryBuilderName}}) First(db *gorm.DB) (*{{.StructName}}, error) { + ret := &{{.StructName}}{} + res := qb.buildQuery(db).First(ret) + if res.Error != nil && res.Error == gorm.ErrRecordNotFound { + ret = nil + } + return ret, res.Error +} + +func (qb *{{.QueryBuilderName}}) QueryOne(db *gorm.DB) (*{{.StructName}}, error) { + qb.limit = 1 + ret, err := qb.QueryAll(db) + if len(ret) > 0 { + return ret[0], err + } + return nil, err +} + +func (qb *{{.QueryBuilderName}}) QueryAll(db *gorm.DB) ([]*{{.StructName}}, error) { + var ret []*{{.StructName}} + err := qb.buildQuery(db).Find(&ret).Error + return ret, err +} + +func (qb *{{.QueryBuilderName}}) Limit(limit int) *{{.QueryBuilderName}} { + qb.limit = limit + return qb +} + +func (qb *{{.QueryBuilderName}}) Offset(offset int) *{{.QueryBuilderName}} { + qb.offset = offset + return qb +} + +{{$queryBuilderName := .QueryBuilderName}} +{{range .OptionFields}} +func (qb *{{$queryBuilderName}}) Where{{call $.Helpers.Titelize .FieldName}}(p db_repo.Predicate, value {{.FieldType}}) *{{$queryBuilderName}} { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "{{.ColumnName}}", p), + value, + }) + return qb +} + +func (qb *{{$queryBuilderName}}) OrderBy{{call $.Helpers.Titelize .FieldName}}(asc bool) *{{$queryBuilderName}} { + order := "DESC" + if asc { + order = "ASC" + } + + qb.order = append(qb.order, "{{.ColumnName}} " + order) + return qb +} +{{end}} +`) diff --git a/cmd/gormgen/pkg/utils.go b/cmd/gormgen/pkg/utils.go new file mode 100644 index 0000000..a3ddf8a --- /dev/null +++ b/cmd/gormgen/pkg/utils.go @@ -0,0 +1,18 @@ +package pkg + +import "strings" + +// SQLColumnToHumpStyle sql转换成驼峰模式 +func SQLColumnToHumpStyle(in string) (ret string) { + for i := 0; i < len(in); i++ { + if i > 0 && in[i-1] == '_' && in[i] != '_' { + s := strings.ToUpper(string(in[i])) + ret += s + } else if in[i] == '_' { + continue + } else { + ret += string(in[i]) + } + } + return +} diff --git a/cmd/handlergen/README.md b/cmd/handlergen/README.md new file mode 100644 index 0000000..fe7ed4f --- /dev/null +++ b/cmd/handlergen/README.md @@ -0,0 +1,6 @@ +## 执行命令 + +```$xslt +// test_handler 为 ./internal/api/controller/ 中的包名 +./scripts/handlergen.sh test_handler +``` \ No newline at end of file diff --git a/cmd/handlergen/main.go b/cmd/handlergen/main.go new file mode 100644 index 0000000..aad752d --- /dev/null +++ b/cmd/handlergen/main.go @@ -0,0 +1,84 @@ +package main + +import ( + "flag" + "fmt" + "go/ast" + "go/parser" + "go/token" + "log" + "os" + "strings" + "unicode" +) + +var handlerName string + +func init() { + handler := flag.String("handler", "", "请输入需要生成的 handler 名称\n") + flag.Parse() + + handlerName = strings.ToLower(*handler) +} + +func main() { + fs := token.NewFileSet() + file := fmt.Sprintf("./internal/api/controller/%s/handler.go", handlerName) + parsedFile, err := parser.ParseFile(fs, file, nil, 0) + if err != nil { + log.Fatalf("parsing package: %s: %s\n", file, err) + } + + ast.Inspect(parsedFile, func(n ast.Node) bool { + decl, ok := n.(*ast.GenDecl) + if !ok || decl.Tok != token.TYPE { + return true + } + + for _, spec := range decl.Specs { + typeSpec, _ok := spec.(*ast.TypeSpec) + if !_ok { + continue + } + + var interfaceType *ast.InterfaceType + if interfaceType, ok = typeSpec.Type.(*ast.InterfaceType); !ok { + continue + } + + for _, v := range interfaceType.Methods.List { + if len(v.Names) > 0 { + if v.Names[0].String() == "i" { + continue + } + + filepath := "./internal/api/controller/" + handlerName + filename := fmt.Sprintf("%s/func_%s.go", filepath, strings.ToLower(v.Names[0].String())) + funcFile, err := os.OpenFile(filename, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0766) + if err != nil { + fmt.Printf("create and open func file error %v\n", err.Error()) + } + + funcContent := fmt.Sprintf("package %s\n\n", handlerName) + funcContent += "import (\n" + funcContent += `"github.com/xinliangnote/go-gin-api/internal/pkg/core"` + funcContent += "\n)\n\n" + funcContent += fmt.Sprintf("\n\ntype %sRequest struct {}\n\n", Lcfirst(v.Names[0].String())) + funcContent += fmt.Sprintf("type %sResponse struct {}\n\n", Lcfirst(v.Names[0].String())) + funcContent += fmt.Sprintf("func (h *handler) %s() core.HandlerFunc { \n return func(c core.Context) {\n\n}}", v.Names[0].String()) + + funcFile.WriteString(funcContent) + funcFile.Close() + } + } + } + return true + }) +} + +func Lcfirst(str string) string { + for i, v := range str { + return string(unicode.ToLower(v)) + str[i+1:] + } + return "" +} diff --git a/cmd/mysqlmd/main.go b/cmd/mysqlmd/main.go new file mode 100644 index 0000000..3ade8d4 --- /dev/null +++ b/cmd/mysqlmd/main.go @@ -0,0 +1,289 @@ +package main + +import ( + "database/sql" + "fmt" + "os" + "regexp" + "strings" + + "github.com/xinliangnote/go-gin-api/configs" + "github.com/xinliangnote/go-gin-api/internal/pkg/db" + "github.com/xinliangnote/go-gin-api/pkg/env" + "github.com/xinliangnote/go-gin-api/pkg/logger" + + "go.uber.org/zap" + "gorm.io/gorm" +) + +type tableInfo struct { + Name string `db:"table_name"` // name + Comment sql.NullString `db:"table_comment"` // comment +} + +type tableColumn struct { + OrdinalPosition uint16 `db:"ORDINAL_POSITION"` // position + ColumnName string `db:"COLUMN_NAME"` // name + ColumnType string `db:"COLUMN_TYPE"` // column_type + DataType string `db:"DATA_TYPE"` // data_type + ColumnKey sql.NullString `db:"COLUMN_KEY"` // key + IsNullable string `db:"IS_NULLABLE"` // nullable + Extra sql.NullString `db:"EXTRA"` // extra + ColumnComment sql.NullString `db:"COLUMN_COMMENT"` // comment + ColumnDefault sql.NullString `db:"COLUMN_DEFAULT"` // default value +} + +func main() { + // 初始化 logger + loggers, err := logger.NewJSONLogger( + logger.WithField("domain", fmt.Sprintf("%s[%s]", configs.ProjectName(), env.Active().Value())), + logger.WithTimeLayout("2006-01-02 15:04:05"), + logger.WithFileP(configs.ProjectLogFile()), + ) + if err != nil { + panic(err) + } + defer loggers.Sync() + + // 初始化 DB + dbRepo, err := db.New() + if err != nil { + loggers.Fatal("new db err", zap.Error(err)) + } + + defer func() { + if err := dbRepo.DbWClose(); err != nil { + loggers.Error("dbw close err", zap.Error(err)) + } + + if err := dbRepo.DbRClose(); err != nil { + loggers.Error("dbr close err", zap.Error(err)) + } + }() + + dbName := configs.Get().MySQL.Read.Name + genTables := configs.Get().Cmd.GenTables + tables, err := queryTables(dbRepo.GetDbR(), dbName, genTables) + if err != nil { + loggers.Error("query tables of database err", zap.Error(err)) + return + } + + for _, table := range tables { + + filepath := "./internal/api/repository/db_repo/" + table.Name + "_repo" + _ = os.Mkdir(filepath, 0766) + + mdName := fmt.Sprintf("%s/gen_table.md", filepath) + mdFile, err := os.OpenFile(mdName, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0766) + if err != nil { + fmt.Printf("create and open markdown file error %v\n", err.Error()) + return + } + + modelName := fmt.Sprintf("%s/gen_model.go", filepath) + modelFile, err := os.OpenFile(modelName, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0766) + if err != nil { + fmt.Printf("create and open model file error %v\n", err.Error()) + return + } + + modelContent := fmt.Sprintf("package %s%s\n", table.Name, "_repo") + modelContent += fmt.Sprintf(`import "time"`) + modelContent += fmt.Sprintf("\n\n// %s \n", table.Comment.String) + modelContent += fmt.Sprintf("//go:generate gormgen -structs %s -input . \n", capitalize(table.Name)) + modelContent += fmt.Sprintf("type %s struct {\n", capitalize(table.Name)) + + tableContent := fmt.Sprintf("#### %s.%s \n", dbName, table.Name) + if table.Comment.String != "" { + tableContent += table.Comment.String + "\n" + } + tableContent += "\n" + + "| 序号 | 名称 | 描述 | 类型 | 键 | 为空 | 额外 | 默认值 |\n" + + "| :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: |\n" + + columnInfo, columnInfoErr := queryTableColumn(dbRepo.GetDbR(), dbName, table.Name) + if columnInfoErr != nil { + continue + } + for _, info := range columnInfo { + tableContent += fmt.Sprintf( + "| %d | %s | %s | %s | %s | %s | %s | %s |\n", + info.OrdinalPosition, + info.ColumnName, + strings.ReplaceAll(strings.ReplaceAll(info.ColumnComment.String, "|", "\\|"), "\n", ""), + info.ColumnType, + info.ColumnKey.String, + info.IsNullable, + info.Extra.String, + info.ColumnDefault.String, + ) + + if textType(info.DataType) == "time.Time" { + modelContent += fmt.Sprintf("%s %s `%s` // %s\n", capitalize(info.ColumnName), textType(info.DataType), "gorm:\"time\"", info.ColumnComment.String) + } else { + modelContent += fmt.Sprintf("%s %s // %s\n", capitalize(info.ColumnName), textType(info.DataType), info.ColumnComment.String) + } + } + + mdFile.WriteString(tableContent) + mdFile.Close() + + modelContent += "}\n" + modelFile.WriteString(modelContent) + modelFile.Close() + + } + +} + +func queryTables(db *gorm.DB, dbName string, tableName string) ([]tableInfo, error) { + var tableCollect []tableInfo + var tableArray []string + var commentArray []sql.NullString + + sqlTables := fmt.Sprintf("SELECT `table_name`,`table_comment` FROM `information_schema`.`tables` WHERE `table_schema`= '%s'", dbName) + rows, err := db.Raw(sqlTables).Rows() + if err != nil { + return tableCollect, err + } + defer rows.Close() + + for rows.Next() { + var info tableInfo + err = rows.Scan(&info.Name, &info.Comment) + if err != nil { + fmt.Printf("execute query tables action error,had ignored, detail is [%v]\n", err.Error()) + continue + } + + tableCollect = append(tableCollect, info) + tableArray = append(tableArray, info.Name) + commentArray = append(commentArray, info.Comment) + } + + // filter tables when specified tables params + if tableName != "" { + tableCollect = nil + chooseTables := strings.Split(tableName, ",") + indexMap := make(map[int]int) + for _, item := range chooseTables { + subIndexMap := getTargetIndexMap(tableArray, item) + for k, v := range subIndexMap { + if _, ok := indexMap[k]; ok { + continue + } + indexMap[k] = v + } + } + + if len(indexMap) != 0 { + for _, v := range indexMap { + var info tableInfo + info.Name = tableArray[v] + info.Comment = commentArray[v] + tableCollect = append(tableCollect, info) + } + } + } + + return tableCollect, err +} + +func queryTableColumn(db *gorm.DB, dbName string, tableName string) ([]tableColumn, error) { + // 定义承载列信息的切片 + var columns []tableColumn + + sqlTableColumn := fmt.Sprintf("SELECT `ORDINAL_POSITION`,`COLUMN_NAME`,`COLUMN_TYPE`,`DATA_TYPE`,`COLUMN_KEY`,`IS_NULLABLE`,`EXTRA`,`COLUMN_COMMENT`,`COLUMN_DEFAULT` FROM `information_schema`.`columns` WHERE `table_schema`= '%s' AND `table_name`= '%s' ORDER BY `ORDINAL_POSITION` ASC", + dbName, tableName) + + rows, err := db.Raw(sqlTableColumn).Rows() + if err != nil { + fmt.Printf("execute query table column action error, detail is [%v]\n", err.Error()) + return columns, err + } + defer rows.Close() + + for rows.Next() { + var column tableColumn + err = rows.Scan( + &column.OrdinalPosition, + &column.ColumnName, + &column.ColumnType, + &column.DataType, + &column.ColumnKey, + &column.IsNullable, + &column.Extra, + &column.ColumnComment, + &column.ColumnDefault) + if err != nil { + fmt.Printf("query table column scan error, detail is [%v]\n", err.Error()) + return columns, err + } + columns = append(columns, column) + } + + return columns, err +} + +func getTargetIndexMap(tableNameArr []string, item string) map[int]int { + indexMap := make(map[int]int) + for i := 0; i < len(tableNameArr); i++ { + if match, _ := regexp.MatchString(item, tableNameArr[i]); match { + if _, ok := indexMap[i]; ok { + continue + } + indexMap[i] = i + } + } + return indexMap +} + +func capitalize(s string) string { + var upperStr string + chars := strings.Split(s, "_") + for _, val := range chars { + vv := []rune(val) + for i := 0; i < len(vv); i++ { + if i == 0 { + if vv[i] >= 97 && vv[i] <= 122 { + vv[i] -= 32 + upperStr += string(vv[i]) + } + } else { + upperStr += string(vv[i]) + } + } + } + return upperStr +} + +func textType(s string) string { + var mysqlTypeToGoType = map[string]string{ + "tinyint": "int32", + "smallint": "int32", + "mediumint": "int32", + "int": "int32", + "integer": "int64", + "bigint": "int64", + "float": "float64", + "double": "float64", + "decimal": "float64", + "date": "string", + "time": "string", + "year": "string", + "datetime": "time.Time", + "timestamp": "time.Time", + "char": "string", + "varchar": "string", + "tinyblob": "string", + "tinytext": "string", + "blob": "string", + "text": "string", + "mediumblob": "string", + "mediumtext": "string", + "longblob": "string", + "longtext": "string", + } + return mysqlTypeToGoType[s] +} diff --git a/configs/configs.go b/configs/configs.go index c989089..2706051 100644 --- a/configs/configs.go +++ b/configs/configs.go @@ -63,6 +63,10 @@ type Config struct { Private string `toml:"private"` Public string `toml:"public"` } `toml:"rsa"` + + Cmd struct { + GenTables string `toml:"genTables"` + } `toml:"cmd"` } func init() { diff --git a/configs/fat_configs.toml b/configs/fat_configs.toml index f3b881c..6474b43 100644 --- a/configs/fat_configs.toml +++ b/configs/fat_configs.toml @@ -77,3 +77,5 @@ xLYEFN9h2MWYgxLm9Z0rLMrWwMM+E2rCs8tsxAD5sO9RZMJPl1C0FIsMR53ngqbz owIDAQAB -----END PUBLIC KEY-----' +[cmd] +genTables = 'user_demo' \ No newline at end of file diff --git a/docs/docs.go b/docs/docs.go index 6ab97d8..4e2d349 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -169,7 +169,7 @@ var doc = `{ }, "/user/delete/{id}": { "patch": { - "description": "删除用户 - 更新 is_deleted = 1", + "description": "删除用户", "consumes": [ "application/json" ], @@ -179,7 +179,7 @@ var doc = `{ "tags": [ "User" ], - "summary": "删除用户 - 更新 is_deleted = 1", + "summary": "删除用户", "parameters": [ { "type": "integer", diff --git a/docs/swagger.json b/docs/swagger.json index 2dfe321..818bbcb 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -152,7 +152,7 @@ }, "/user/delete/{id}": { "patch": { - "description": "删除用户 - 更新 is_deleted = 1", + "description": "删除用户", "consumes": [ "application/json" ], @@ -162,7 +162,7 @@ "tags": [ "User" ], - "summary": "删除用户 - 更新 is_deleted = 1", + "summary": "删除用户", "parameters": [ { "type": "integer", diff --git a/docs/swagger.yaml b/docs/swagger.yaml index a54fcc6..af3b2e5 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -176,7 +176,7 @@ paths: patch: consumes: - application/json - description: 删除用户 - 更新 is_deleted = 1 + description: 删除用户 parameters: - description: 用户ID in: path @@ -203,7 +203,7 @@ paths: description: Unauthorized schema: $ref: '#/definitions/code.Failure' - summary: 删除用户 - 更新 is_deleted = 1 + summary: 删除用户 tags: - User /user/info/{username}: diff --git a/go.mod b/go.mod index 9a9e117..58fa797 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/go-redis/redis/v7 v7.4.0 github.com/golang/protobuf v1.4.3 github.com/google/go-cmp v0.5.4 // indirect + github.com/jinzhu/gorm v1.9.16 github.com/onsi/ginkgo v1.14.2 // indirect github.com/onsi/gomega v1.10.4 // indirect github.com/pkg/errors v0.9.1 diff --git a/go.sum b/go.sum index b4220c7..82802bb 100644 --- a/go.sum +++ b/go.sum @@ -19,6 +19,7 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= @@ -33,6 +34,7 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= @@ -58,6 +60,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM= +github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= @@ -65,6 +69,8 @@ github.com/dgryski/trifles v0.0.0-20190318185328-a8d75aae118c h1:TUuUh0Xgj97tLMN github.com/dgryski/trifles v0.0.0-20190318185328-a8d75aae118c/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= +github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -123,6 +129,8 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -192,8 +200,11 @@ github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0m github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o= +github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.1 h1:g39TucaRWyV3dwDO++eEc6qf8TVIQ/Da48WmqjZ3i7E= github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= @@ -220,6 +231,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4= +github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= @@ -237,6 +250,8 @@ github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA= +github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= @@ -381,9 +396,11 @@ go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -409,6 +426,7 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0 h1:8pl+sMODzuvGJkmj2W4kZihvVb5mKm8pB/X44PIQHv8= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -428,6 +446,8 @@ golang.org/x/net v0.0.0-20190611141213-3f473d35a33a/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= diff --git a/internal/api/controller/user_handler/func_create.go b/internal/api/controller/user_handler/func_create.go index 940453f..025b559 100644 --- a/internal/api/controller/user_handler/func_create.go +++ b/internal/api/controller/user_handler/func_create.go @@ -18,7 +18,7 @@ type createRequest struct { } type createResponse struct { - Id uint `json:"id"` // 主键ID + Id int32 `json:"id"` // 主键ID } // 创建用户 diff --git a/internal/api/controller/user_handler/func_delete.go b/internal/api/controller/user_handler/func_delete.go index dbb2732..acc7612 100644 --- a/internal/api/controller/user_handler/func_delete.go +++ b/internal/api/controller/user_handler/func_delete.go @@ -9,16 +9,16 @@ import ( ) type deleteRequest struct { - Id uint `uri:"id"` // 用户ID + Id int32 `uri:"id"` // 用户ID } type deleteResponse struct { - Id uint `json:"id"` // 用户主键ID + Id int32 `json:"id"` // 用户主键ID } -// 删除用户 - 更新 is_deleted = 1 -// @Summary 删除用户 - 更新 is_deleted = 1 -// @Description 删除用户 - 更新 is_deleted = 1 +// 删除用户 +// @Summary 删除用户 +// @Description 删除用户 // @Tags User // @Accept json // @Produce json diff --git a/internal/api/controller/user_handler/func_detail.go b/internal/api/controller/user_handler/func_detail.go index f152a67..1828355 100644 --- a/internal/api/controller/user_handler/func_detail.go +++ b/internal/api/controller/user_handler/func_detail.go @@ -14,7 +14,7 @@ type detailRequest struct { } type detailResponse struct { - Id uint `json:"id"` // 用户主键ID + Id int32 `json:"id"` // 用户主键ID UserName string `json:"user_name"` // 用户名 NickName string `json:"nick_name"` // 昵称 Mobile ddm.Mobile `json:"mobile"` // 手机号(脱敏) diff --git a/internal/api/controller/user_handler/func_updatenicknamebyid.go b/internal/api/controller/user_handler/func_updatenicknamebyid.go index e12ffac..6196254 100644 --- a/internal/api/controller/user_handler/func_updatenicknamebyid.go +++ b/internal/api/controller/user_handler/func_updatenicknamebyid.go @@ -9,12 +9,12 @@ import ( ) type updateNickNameByIDRequest struct { - Id uint `json:"id"` // 用户主键ID + Id int32 `json:"id"` // 用户主键ID NickName string `json:"nick_name"` // 昵称 } type updateNickNameByIDResponse struct { - Id uint `json:"id"` // 用户主键ID + Id int32 `json:"id"` // 用户主键ID } // 编辑用户 - 通过用户主键ID更新用户昵称 diff --git a/internal/api/controller/user_handler/handler.go b/internal/api/controller/user_handler/handler.go index 5c312a1..261eea9 100644 --- a/internal/api/controller/user_handler/handler.go +++ b/internal/api/controller/user_handler/handler.go @@ -18,7 +18,7 @@ type Handler interface { Create() core.HandlerFunc // UpdateNickNameByID 编辑用户 - 通过主键ID更新用户昵称 UpdateNickNameByID() core.HandlerFunc - // Delete 删除用户 - 通过主键ID更新 is_deleted = 1 + // Delete 删除用户 Delete() core.HandlerFunc // Detail 用户详情 Detail() core.HandlerFunc diff --git a/internal/api/repository/README.md b/internal/api/repository/README.md index bfb3823..b0d3a0a 100644 --- a/internal/api/repository/README.md +++ b/internal/api/repository/README.md @@ -1,16 +1,12 @@ ## repository -数据访问层。 +#### 数据访问层。 - `./db_repo` 访问 DB 数据 - `./cache_repo` 访问 Cache 数据 - `./third_party_request` 访问外部 HTTP 接口数据。 -SQL 建议: -- 禁止使用 SQL k v 拼接,好处是避免 SQL 注入; -- 禁止使用连表查询,好处是易扩展,比如分库分表; -- 禁止使用万能方法,好处是便于后期维护,比如字段调整; -- 禁止使用删除方法,好处是避免数据丢失; +#### SQL 建议: - 建议每张表需包含字段:主键(id)、标记删除(is_deteled)、创建时间(created_at)、更新时间(updated_at) ```mysql @@ -19,11 +15,18 @@ SQL 建议: `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', ``` -什么是万能方法? -指的是特别灵活的查询,比如通过非固定的参数返回全部字段,建议做到需要什么返回什么,不要返回大而全的数据,更新时也不能传递什么参数更新什么参数,更新字段要提前约定好。 - -命名规范: +#### 命名规范: - 包名应以 `_repo` 结尾; - `./db_repo` 目录下的包名以 `数据表名`+ `_repo` 命名; + +#### 脚本生成 MySQL CURD + +1. 定义生成的表,设置 config 中 cmd.genTables,可以自定义设置多张表,为空表示生成库中所有的表,如果设置多个表可用','分割; +1. 在根目录下执行脚本文件:`./scripts/gormgen.sh`; + +以用户表(user_demo)为例: +- 结构体文件:user_demo_repo/gen_model.go; +- CURD 方法文件:user_demo_repo/gen_user_demo.go; +- 表结构 MD 文件:user_demo_repo/gen_table.md; diff --git a/internal/api/repository/db_repo/db.go b/internal/api/repository/db_repo/db.go new file mode 100644 index 0000000..2e86900 --- /dev/null +++ b/internal/api/repository/db_repo/db.go @@ -0,0 +1,14 @@ +package db_repo + +// Predicate is a string that acts as a condition in the where clause +type Predicate string + +var ( + EqualPredicate = Predicate("=") + NotEqualPredicate = Predicate("<>") + GreaterThanPredicate = Predicate(">") + GreaterThanOrEqualPredicate = Predicate(">=") + SmallerThanPredicate = Predicate("<") + SmallerThanOrEqualPredicate = Predicate("<=") + LikePredicate = Predicate("LIKE") +) diff --git a/internal/api/repository/db_repo/user_demo_repo/gen_model.go b/internal/api/repository/db_repo/user_demo_repo/gen_model.go new file mode 100755 index 0000000..2dfc2f9 --- /dev/null +++ b/internal/api/repository/db_repo/user_demo_repo/gen_model.go @@ -0,0 +1,15 @@ +package user_demo_repo + +import "time" + +// 用户Demo表 +//go:generate gormgen -structs UserDemo -input . +type UserDemo struct { + Id int32 // 主键 + UserName string // 用户名 + NickName string // 昵称 + Mobile string // 手机号 + IsDeleted int32 // 是否删除 1:是 -1:否 + CreatedAt time.Time `gorm:"time"` // 创建时间 + UpdatedAt time.Time `gorm:"time"` // 更新时间 +} diff --git a/internal/api/repository/db_repo/user_demo_repo/gen_table.md b/internal/api/repository/db_repo/user_demo_repo/gen_table.md new file mode 100755 index 0000000..e65215f --- /dev/null +++ b/internal/api/repository/db_repo/user_demo_repo/gen_table.md @@ -0,0 +1,12 @@ +#### xin_ceshi.user_demo +用户Demo表 + +| 序号 | 名称 | 描述 | 类型 | 键 | 为空 | 额外 | 默认值 | +| :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | +| 1 | id | 主键 | int(11) unsigned | PRI | NO | auto_increment | | +| 2 | user_name | 用户名 | varchar(32) | | NO | | | +| 3 | nick_name | 昵称 | varchar(100) | | NO | | | +| 4 | mobile | 手机号 | varchar(20) | | NO | | | +| 5 | is_deleted | 是否删除 1:是 -1:否 | tinyint(1) | | NO | | -1 | +| 6 | created_at | 创建时间 | timestamp | | NO | | CURRENT_TIMESTAMP | +| 7 | updated_at | 更新时间 | timestamp | | NO | on update CURRENT_TIMESTAMP | CURRENT_TIMESTAMP | diff --git a/internal/api/repository/db_repo/user_demo_repo/gen_user_demo.go b/internal/api/repository/db_repo/user_demo_repo/gen_user_demo.go new file mode 100755 index 0000000..3597bf3 --- /dev/null +++ b/internal/api/repository/db_repo/user_demo_repo/gen_user_demo.go @@ -0,0 +1,257 @@ +/////////////////////////////////////////////////////////// +// THIS FILE IS AUTO GENERATED by gormgen, DON'T EDIT IT // +// ANY CHANGES DONE HERE WILL BE LOST // +/////////////////////////////////////////////////////////// + +package user_demo_repo + +import ( + "fmt" + "time" + + "github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo" + + "github.com/pkg/errors" + "gorm.io/gorm" +) + +func NewModel() *UserDemo { + return new(UserDemo) +} + +func NewQueryBuilder() *userDemoRepoQueryBuilder { + return new(userDemoRepoQueryBuilder) +} + +func (t *UserDemo) Create(db *gorm.DB) (id int32, err error) { + if err = db.Create(t).Error; err != nil { + return 0, errors.Wrap(err, "create err") + } + return t.Id, nil +} + +func (t *UserDemo) Delete(db *gorm.DB) (err error) { + if err = db.Delete(t).Error; err != nil { + return errors.Wrap(err, "delete err") + } + return nil +} + +func (t *UserDemo) Updates(db *gorm.DB, m map[string]interface{}) (err error) { + if err = db.Model(&UserDemo{}).Where("id = ?", t.Id).Updates(m).Error; err != nil { + return errors.Wrap(err, "updates err") + } + return nil +} + +type userDemoRepoQueryBuilder struct { + order []string + where []struct { + prefix string + value interface{} + } + limit int + offset int +} + +func (qb *userDemoRepoQueryBuilder) buildQuery(db *gorm.DB) *gorm.DB { + ret := db + for _, where := range qb.where { + ret = ret.Where(where.prefix, where.value) + } + for _, order := range qb.order { + ret = ret.Order(order) + } + ret = ret.Limit(qb.limit).Offset(qb.offset) + return ret +} + +func (qb *userDemoRepoQueryBuilder) Count(db *gorm.DB) (int64, error) { + var c int64 + res := qb.buildQuery(db).Model(&UserDemo{}).Count(&c) + if res.Error != nil && res.Error == gorm.ErrRecordNotFound { + c = 0 + } + return c, res.Error +} + +func (qb *userDemoRepoQueryBuilder) First(db *gorm.DB) (*UserDemo, error) { + ret := &UserDemo{} + res := qb.buildQuery(db).First(ret) + if res.Error != nil && res.Error == gorm.ErrRecordNotFound { + ret = nil + } + return ret, res.Error +} + +func (qb *userDemoRepoQueryBuilder) QueryOne(db *gorm.DB) (*UserDemo, error) { + qb.limit = 1 + ret, err := qb.QueryAll(db) + if len(ret) > 0 { + return ret[0], err + } + return nil, err +} + +func (qb *userDemoRepoQueryBuilder) QueryAll(db *gorm.DB) ([]*UserDemo, error) { + var ret []*UserDemo + err := qb.buildQuery(db).Find(&ret).Error + return ret, err +} + +func (qb *userDemoRepoQueryBuilder) Limit(limit int) *userDemoRepoQueryBuilder { + qb.limit = limit + return qb +} + +func (qb *userDemoRepoQueryBuilder) Offset(offset int) *userDemoRepoQueryBuilder { + qb.offset = offset + return qb +} + +func (qb *userDemoRepoQueryBuilder) WhereId(p db_repo.Predicate, value int32) *userDemoRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "id", p), + value, + }) + return qb +} + +func (qb *userDemoRepoQueryBuilder) OrderById(asc bool) *userDemoRepoQueryBuilder { + order := "DESC" + if asc { + order = "ASC" + } + + qb.order = append(qb.order, "id "+order) + return qb +} + +func (qb *userDemoRepoQueryBuilder) WhereUserName(p db_repo.Predicate, value string) *userDemoRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "user_name", p), + value, + }) + return qb +} + +func (qb *userDemoRepoQueryBuilder) OrderByUserName(asc bool) *userDemoRepoQueryBuilder { + order := "DESC" + if asc { + order = "ASC" + } + + qb.order = append(qb.order, "user_name "+order) + return qb +} + +func (qb *userDemoRepoQueryBuilder) WhereNickName(p db_repo.Predicate, value string) *userDemoRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "nick_name", p), + value, + }) + return qb +} + +func (qb *userDemoRepoQueryBuilder) OrderByNickName(asc bool) *userDemoRepoQueryBuilder { + order := "DESC" + if asc { + order = "ASC" + } + + qb.order = append(qb.order, "nick_name "+order) + return qb +} + +func (qb *userDemoRepoQueryBuilder) WhereMobile(p db_repo.Predicate, value string) *userDemoRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "mobile", p), + value, + }) + return qb +} + +func (qb *userDemoRepoQueryBuilder) OrderByMobile(asc bool) *userDemoRepoQueryBuilder { + order := "DESC" + if asc { + order = "ASC" + } + + qb.order = append(qb.order, "mobile "+order) + return qb +} + +func (qb *userDemoRepoQueryBuilder) WhereIsDeleted(p db_repo.Predicate, value int32) *userDemoRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "is_deleted", p), + value, + }) + return qb +} + +func (qb *userDemoRepoQueryBuilder) OrderByIsDeleted(asc bool) *userDemoRepoQueryBuilder { + order := "DESC" + if asc { + order = "ASC" + } + + qb.order = append(qb.order, "is_deleted "+order) + return qb +} + +func (qb *userDemoRepoQueryBuilder) WhereCreatedAt(p db_repo.Predicate, value time.Time) *userDemoRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "created_at", p), + value, + }) + return qb +} + +func (qb *userDemoRepoQueryBuilder) OrderByCreatedAt(asc bool) *userDemoRepoQueryBuilder { + order := "DESC" + if asc { + order = "ASC" + } + + qb.order = append(qb.order, "created_at "+order) + return qb +} + +func (qb *userDemoRepoQueryBuilder) WhereUpdatedAt(p db_repo.Predicate, value time.Time) *userDemoRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "updated_at", p), + value, + }) + return qb +} + +func (qb *userDemoRepoQueryBuilder) OrderByUpdatedAt(asc bool) *userDemoRepoQueryBuilder { + order := "DESC" + if asc { + order = "ASC" + } + + qb.order = append(qb.order, "updated_at "+order) + return qb +} diff --git a/internal/api/repository/db_repo/user_demo_repo/model.go b/internal/api/repository/db_repo/user_demo_repo/model.go deleted file mode 100644 index b740627..0000000 --- a/internal/api/repository/db_repo/user_demo_repo/model.go +++ /dev/null @@ -1,20 +0,0 @@ -package user_demo_repo - -import ( - "time" -) - -// 用户Demo表 -type UserDemo struct { - Id uint `gorm:"column:id;primary_key;AUTO_INCREMENT"` // 主键 - UserName string `gorm:"column:user_name;NOT NULL"` // 用户名 - NickName string `gorm:"column:nick_name;NOT NULL"` // 昵称 - Mobile string `gorm:"column:mobile;NOT NULL"` // 手机号 - IsDeleted int `gorm:"column:is_deleted;default:-1;NOT NULL"` // 是否删除 1:是 -1:否 - CreatedAt time.Time `gorm:"column:created_at;default:CURRENT_TIMESTAMP;NOT NULL"` // 创建时间 - UpdatedAt time.Time `gorm:"column:updated_at;default:CURRENT_TIMESTAMP;NOT NULL"` // 更新时间 -} - -func (m *UserDemo) TableName() string { - return "user_demo" -} diff --git a/internal/api/repository/db_repo/user_demo_repo/user_demo.go b/internal/api/repository/db_repo/user_demo_repo/user_demo.go deleted file mode 100644 index 81624a2..0000000 --- a/internal/api/repository/db_repo/user_demo_repo/user_demo.go +++ /dev/null @@ -1,80 +0,0 @@ -package user_demo_repo - -import ( - "github.com/xinliangnote/go-gin-api/internal/pkg/core" - "github.com/xinliangnote/go-gin-api/internal/pkg/db" - - "github.com/pkg/errors" - "gorm.io/gorm" -) - -var _ UserRepo = (*userRepo)(nil) - -type UserRepo interface { - // i 为了避免被其他包实现 - i() - Create(ctx core.Context, user UserDemo) (id uint, err error) - UpdateNickNameByID(ctx core.Context, id uint, username string) (err error) - GetUserByUserName(ctx core.Context, username string) (*UserDemo, error) - Delete(ctx core.Context, id uint) (err error) - getUserByID(ctx core.Context, id uint) (*UserDemo, error) -} - -type userRepo struct { - db db.Repo -} - -func NewUserRepo(db db.Repo) UserRepo { - return &userRepo{ - db: db, - } -} - -func (u *userRepo) i() {} - -func (u *userRepo) Create(ctx core.Context, user UserDemo) (id uint, err error) { - err = u.db.GetDbW().WithContext(ctx.RequestContext()).Create(&user).Error - if err != nil { - return 0, errors.Wrap(err, "[user_repo] create user err") - } - return user.Id, nil -} - -func (u *userRepo) getUserByID(ctx core.Context, id uint) (*UserDemo, error) { - data := new(UserDemo) - err := u.db.GetDbR(). - WithContext(ctx.RequestContext()).First(data, id).Where("is_deleted = ?", -1).Error - if err != nil && err != gorm.ErrRecordNotFound { - return nil, errors.Wrap(err, "[user_demo] get user data err") - } - return data, nil -} - -func (u *userRepo) UpdateNickNameByID(ctx core.Context, id uint, nickname string) (err error) { - user, err := u.getUserByID(ctx, id) - if err != nil { - return errors.Wrap(err, "[user_demo] get user data err") - } - return u.db.GetDbW().WithContext(ctx.RequestContext()).Model(user).Update("nick_name", nickname).Error -} - -func (u *userRepo) Delete(ctx core.Context, id uint) (err error) { - user, err := u.getUserByID(ctx, id) - if err != nil { - return errors.Wrap(err, "[user_demo] get user data err") - } - return u.db.GetDbW().WithContext(ctx.RequestContext()).Model(user).Update("is_deleted", 1).Error -} - -func (u *userRepo) GetUserByUserName(ctx core.Context, username string) (*UserDemo, error) { - data := new(UserDemo) - err := u.db.GetDbR(). - WithContext(ctx.RequestContext()). - Select([]string{"id", "user_name", "nick_name", "mobile"}). - Where("user_name = ? and is_deleted = ?", username, -1). - First(data).Error - if err != nil && err != gorm.ErrRecordNotFound { - return nil, errors.Wrap(err, "[user_demo] get user data err") - } - return data, nil -} diff --git a/internal/api/service/user_service/func_create.go b/internal/api/service/user_service/func_create.go index 025bb08..165633f 100644 --- a/internal/api/service/user_service/func_create.go +++ b/internal/api/service/user_service/func_create.go @@ -11,14 +11,13 @@ type CreateUserInfo struct { Mobile string `json:"mobile"` // 手机号 } -func (u *userSer) Create(ctx core.Context, user *CreateUserInfo) (id uint, err error) { - create := user_demo_repo.UserDemo{ - UserName: user.UserName, - NickName: user.NickName, - Mobile: user.Mobile, - } +func (u *userSer) Create(ctx core.Context, user *CreateUserInfo) (id int32, err error) { + model := user_demo_repo.NewModel() + model.UserName = user.UserName + model.NickName = user.NickName + model.Mobile = user.Mobile - id, err = u.userRepo.Create(ctx, create) + id, err = model.Create(u.db.GetDbW().WithContext(ctx.RequestContext())) if err != nil { return 0, err } diff --git a/internal/api/service/user_service/func_delete.go b/internal/api/service/user_service/func_delete.go index 3955baa..016aab6 100644 --- a/internal/api/service/user_service/func_delete.go +++ b/internal/api/service/user_service/func_delete.go @@ -1,9 +1,14 @@ package user_service -import "github.com/xinliangnote/go-gin-api/internal/pkg/core" +import ( + "github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/user_demo_repo" + "github.com/xinliangnote/go-gin-api/internal/pkg/core" +) -func (u *userSer) Delete(ctx core.Context, id uint) (err error) { - err = u.userRepo.Delete(ctx, id) +func (u *userSer) Delete(ctx core.Context, id int32) (err error) { + model := user_demo_repo.NewModel() + model.Id = id + err = model.Delete(u.db.GetDbW().WithContext(ctx.RequestContext())) if err != nil { return nil } diff --git a/internal/api/service/user_service/func_getuserbyusername.go b/internal/api/service/user_service/func_getuserbyusername.go index 696f77e..3abb628 100644 --- a/internal/api/service/user_service/func_getuserbyusername.go +++ b/internal/api/service/user_service/func_getuserbyusername.go @@ -1,14 +1,23 @@ package user_service import ( + "github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo" "github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/user_demo_repo" "github.com/xinliangnote/go-gin-api/internal/pkg/core" ) func (u *userSer) GetUserByUserName(ctx core.Context, username string) (user *user_demo_repo.UserDemo, err error) { - user, err = u.userRepo.GetUserByUserName(ctx, username) + user, err = user_demo_repo.NewQueryBuilder(). + WhereUserName(db_repo.EqualPredicate, username). + QueryOne(u.db.GetDbR().WithContext(ctx.RequestContext())) + if err != nil { - return nil, err + return user, err } + + if user == nil { + user = user_demo_repo.NewModel() + } + return user, nil } diff --git a/internal/api/service/user_service/func_updatenicknamebyid.go b/internal/api/service/user_service/func_updatenicknamebyid.go index 4b3c3cb..ba4f6a7 100644 --- a/internal/api/service/user_service/func_updatenicknamebyid.go +++ b/internal/api/service/user_service/func_updatenicknamebyid.go @@ -1,11 +1,22 @@ package user_service -import "github.com/xinliangnote/go-gin-api/internal/pkg/core" +import ( + "github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/user_demo_repo" + "github.com/xinliangnote/go-gin-api/internal/pkg/core" +) -func (u *userSer) UpdateNickNameByID(ctx core.Context, id uint, username string) (err error) { - err = u.userRepo.UpdateNickNameByID(ctx, id, username) - if err != nil { - return nil +func (u *userSer) UpdateNickNameByID(ctx core.Context, id int32, nickname string) (err error) { + model := user_demo_repo.NewModel() + model.Id = id + + data := map[string]interface{}{ + "nick_name": nickname, } + + err = model.Updates(u.db.GetDbW().WithContext(ctx.RequestContext()), data) + if err != nil { + return err + } + return nil } diff --git a/internal/api/service/user_service/service.go b/internal/api/service/user_service/service.go index 3584a72..4a15a6e 100644 --- a/internal/api/service/user_service/service.go +++ b/internal/api/service/user_service/service.go @@ -13,24 +13,21 @@ type UserService interface { // i 为了避免被其他包实现 i() - Create(ctx core.Context, user *CreateUserInfo) (id uint, err error) - UpdateNickNameByID(ctx core.Context, id uint, username string) (err error) + Create(ctx core.Context, user *CreateUserInfo) (id int32, err error) + UpdateNickNameByID(ctx core.Context, id int32, username string) (err error) GetUserByUserName(ctx core.Context, username string) (user *user_demo_repo.UserDemo, err error) - Delete(ctx core.Context, id uint) (err error) + Delete(ctx core.Context, id int32) (err error) } type userSer struct { - db db.Repo - cache cache.Repo - userRepo user_demo_repo.UserRepo + db db.Repo + cache cache.Repo } func NewUserService(db db.Repo, cache cache.Repo) UserService { - userRepo := user_demo_repo.NewUserRepo(db) return &userSer{ - db: db, - cache: cache, - userRepo: userRepo, + db: db, + cache: cache, } } diff --git a/internal/pkg/db/mysql.go b/internal/pkg/db/mysql.go index cb4e554..e238097 100644 --- a/internal/pkg/db/mysql.go +++ b/internal/pkg/db/mysql.go @@ -9,6 +9,7 @@ import ( "github.com/pkg/errors" "gorm.io/driver/mysql" "gorm.io/gorm" + "gorm.io/gorm/schema" ) var _ Repo = (*dbRepo)(nil) @@ -80,6 +81,9 @@ func dbConnect(user, pass, addr, dbName string) (*gorm.DB, error) { "Local") db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{ + NamingStrategy: schema.NamingStrategy{ + SingularTable: true, + }, //Logger: logger.Default.LogMode(logger.Info), // 日志配置 }) diff --git a/scripts/gormgen.sh b/scripts/gormgen.sh new file mode 100755 index 0000000..c72fd76 --- /dev/null +++ b/scripts/gormgen.sh @@ -0,0 +1,14 @@ +#!/bin/bash +printf "\nRegenerating mysql file\n\n" +time go run -v ./cmd/mysqlmd/main.go -env fat + +printf "\nRegenerating code\n\n" +time go build -o gormgen ./cmd/gormgen/main.go +mv gormgen $GOPATH/bin + +go generate ./... + +printf "\nFormatting code\n\n" +time go run -v github.com/koketama/mfmt + +printf "\nDone.\n\n" diff --git a/scripts/handlergen.sh b/scripts/handlergen.sh new file mode 100755 index 0000000..fb63c5e --- /dev/null +++ b/scripts/handlergen.sh @@ -0,0 +1,8 @@ +#!/bin/bash +printf "\nRegenerating handler file\n\n" +time go run -v ./cmd/handlergen/main.go -handler $1 + +printf "\nFormatting code\n\n" +time go run -v github.com/koketama/mfmt + +printf "\nDone.\n\n"