commit a84341447a0cd0ae52afac6643faefb73c0bd861 Author: huangzj Date: Sun Apr 26 11:01:26 2020 +0800 feat(ReverseEngine):初始化工程 diff --git a/Main.go b/Main.go new file mode 100644 index 0000000..e408ff3 --- /dev/null +++ b/Main.go @@ -0,0 +1,101 @@ +package main + +import ( + "ReverseEngine/imp" + "ReverseEngine/reverse" + "ReverseEngine/support" + "ReverseEngine/util" + "bufio" + "flag" + "fmt" + "os" + "reflect" + "strings" +) + +var database string +var tableName string +var filepath string +var cover bool + +func main() { + //转换函数 + reverseMain() +} + +func reverseMain() { + //报错是否直接停止程序,有的时候就是说,我可能输错了,但是我不想停止程序,那就会尴尬,这边配置一个报错不停止的标识 + panicStop := flag.Bool("panicStop", false, "错误是否进行停止") + flag.Parse() + conti := true + reader := bufio.NewReader(os.Stdin) + fmt.Print("逆向工程生成中") + fmt.Print("请出入你需要操作的数据库:\n") + database, _ = reader.ReadString('\n') + database = strings.Trim(database, "\n") + e := reverse.NewEngine() + e.SetReverseCheck(&support.CheckPathSupport{}) + + fmt.Println(runOn(conti, reader, e, *panicStop)) +} + +func runOn(conti bool, reader *bufio.Reader, e *reverse.DataReverseEngine, panicStop bool) string { + defer func() { + if panicStop == false || conti == false { + return + } + + if err := recover(); err != nil { + util.ColorPrint("程序出错,因为设置panicStop为不停止状态,所以程序继续\n请开始新一轮的反向工程参数设置\n\n\n\n........\n.........\n\n", util.FontColor.Red) + runOn(conti, reader, e, panicStop) + } + + }() + + for { + + if conti == false { + return "程序运行结束" + } + fmt.Print("请出入你需要操作的表名,该名称不区分大小写,输出即为文件名:\n") + tableName, _ = reader.ReadString('\n') + tableName = strings.Trim(tableName, "\n") + fmt.Print("请出入结构体生成位置(绝对路径):\n") + filepath, _ = reader.ReadString('\n') + filepath = strings.Trim(filepath, "\n") + fmt.Print("如果文件存在是否覆盖(Y表示覆盖):\n") + coverS, _ := reader.ReadString('\n') + coverS = strings.Trim(coverS, "\n") + if coverS == "Y" || coverS == "y" { + cover = true + } + + e.Engineer(database, tableName, filepath, cover) + fmt.Print("是否停止程序,输入Y表示停止:\n") + stop, _ := reader.ReadString('\n') + stop = strings.Trim(stop, "\n") + if stop == "Y" || stop == "y" { + conti = false + } + } +} + +func runOnce() { + e := reverse.NewEngine() + //e.SetContentGenerate(support.FileContentSUpport2{}) + e.SetReverseCheck(&support.CheckPathSupport{}) + e.Engineer("你的数据库", "你的表名", "F:/self_project/src/ReverseEngine/filepath", true) + //e.Engineer("你的数据库", "testreversing", "filepath", true) + // + //s := support.CheckSupport{} + //fmt.Println(&s) + + tx := reflect.ValueOf(support.CheckSupport{}).Type() + fmt.Println(tx) + var ok imp.ReverseCheck = (*support.CheckSupport)(nil) + fmt.Println(ok) + //t := reflect.ValueOf(Test{}).Type() + //fmt.Println(t) + //v := reflect.New(t).Elem() + //fmt.Println(v) +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..ba97bab --- /dev/null +++ b/README.md @@ -0,0 +1,113 @@ + +## 查询命令记录 +``` +查询数据库表的信息sql,这边主要是用来查询表的信息: + select * from information_schema.TABLES where TABLE_SCHEMA='[数据库名]' + +查询数据库表的字段元数据信息sql: + show full columns from [数据库名].[表名] +查询结果: + Field(字段名) Type(字段类型) Collation NULL(能否为空) Key(是否主键) Default(默认值) Extra Privilege Comment(注释) +``` +## 使用ini配置文件读取工具 +``` +读取ini配置文件的工具:https://ini.unknwon.io/docs/intro/getting_started + +配置信息保存路径:reverse/config.ini +支持三类配置: + [database] //数据库配置 + [common] //通用配置 + [type_mapping] //类型映射 +按照对应格式进行配置,如果没有找到对应配置则使用数据库通用配置 +``` + +## 可拓展支持 +``` +可拓展支持三个接口 + ContentGenerate:文件内容拼接 + DbConnection: 数据库支持 + ReverseCheck: 校验支持 + +在Engine中通过以下三个方法实现配置 + func SetContentGenerate + func SetDbConnection + func SetReverseCheck +``` + +## 方法调用说明 +``` + /** + * 传入表名和生成路径,数据库名和是否覆盖按照配置文件获取 + */ + SimpleEngineer(tableName string, path string) + + /** + * 传入表名、生成路径和是否覆盖,数据库名读取配置 + */ + func (r *DataReverseEngine) SimEngineer(tableName string, path string, cover bool) + + /** + * 传入表名、生成路径和是否覆盖、数据库名 + */ + func (*DataReverseEngine) Engineer(dbName string, tableName string, path string, cover bool) +``` + +## 配置文件例子 +``` + [database] //数据库配置 + username = 我的数据库账号 + password = 我的数据库密码 + tcp = @tcp + ip = 我的数据库机器Ip + port = 3306 + database = 我的数据库名 + charset = utf8mb4&parseTime=True&loc=Local + connMaxLifeTime = 100 + maxIdleConns = 10 + + [common] //通用配置 + def = string //默认的映射类型 + tagKey = xorm //tag的key + suffix = .go //文件后缀 + cover = true //存在文件是否进行覆盖,只有配置为false的时候不会覆盖,其他默认为true + + + [type_mapping] //类型映射 + double = float64 + float = float64 + int = int + tinyint = int + bigint = int + time = time.Time + timestamp = time.Time + date = time.Time + dateTime = time.Time +``` + +## 方法调用例子 +``` + //使用相对路径 + e := reverse.NewEngine() + e.Engineer("数据库名字", "表名", "filepath", true) + + //使用绝对路径 + e := reverse.NewEngine() + e.SetReverseCheck(&support.CheckPathSupport{}) + e.Engineer("数据库名字", "表名", "F:/self_project/src/ReverseEngine/filepath", true) +``` + +## 问题描述 +``` + 1.数据库连接是通过最原始的方式进行,这边后期可以修改成xorm或者其他的orm框架方式 + 2.读写文件的方法可以做相应的修改,这边也是采用最原生的方式 +``` + +## 重复使用 +直接运行Main.go方法,在控制台根据提示输入对应的数据库名、表名、生成位置、文件存在是否覆盖等参数来达到直接调用函数使用生成的目的 +一次生成结束提示输入Y即可结束程序 + +启动时通过`panicStop`参数输入,控制程序报错不停止,除非在提示结束的时候输入Y. + + +## 使用 +在config.ini中配置你的数据库信息,最好修改Engine中默认的数据库信息,因为这边的数据库信息都是我乱写的 \ No newline at end of file diff --git a/entity/ColumnMessage.go b/entity/ColumnMessage.go new file mode 100644 index 0000000..f31d4f5 --- /dev/null +++ b/entity/ColumnMessage.go @@ -0,0 +1,56 @@ +package entity + +import "database/sql" + +type ColumnMessage struct { + Field string //字段名称 + Type string //字段类型 + Collation sql.NullString //编码信息 + Null sql.NullString //是否允许为空 + Key sql.NullString //是否是主键 + Default sql.NullString //默认值 + Extra sql.NullString //不懂 + Privileges sql.NullString //执行权限 + Comment sql.NullString //注释信息 +} + +/* + 返回结果判断是不是主键 +*/ +func (c *ColumnMessage) IsKey() bool { + if c.Key.Valid && c.Key.String == "PRI" { + return true + } + return false +} + +/* + 返回结果是否为空信息 +*/ +func (c *ColumnMessage) CanNull() bool { + if c.Null.Valid && c.Null.String == "NO" { + return false + } + return true +} + +/* + 返回注释信息,没有注释信息返回空字符串 +*/ +func (c *ColumnMessage) GetComment() string { + if c.Comment.Valid { + return c.Comment.String + } + + return "" +} + +/* + 返回默认值信息 +*/ +func (c *ColumnMessage) GetDefault() string { + if c.Default.Valid { + return c.Default.String + } + return "" +} diff --git a/entity/FiledMessage.go b/entity/FiledMessage.go new file mode 100644 index 0000000..e4fa9d4 --- /dev/null +++ b/entity/FiledMessage.go @@ -0,0 +1,15 @@ +package entity + +type FieldMessage struct { + FiledName string //类型名称 + FieldType string //类型 + OriginType string //数据库原始类型 + /* + 正常情况下tag的组合是 `TagKey:"pk/ not null/ FieldType"` + */ + TagKey string //tag的key + IsKey bool //是否主键 + Comment string //注释内容 + Default string //默认值信息 + CanNull bool //是否可以为空 +} diff --git a/entity/TableMessage.go b/entity/TableMessage.go new file mode 100644 index 0000000..2f46cb8 --- /dev/null +++ b/entity/TableMessage.go @@ -0,0 +1,5 @@ +package entity + +type TableMessage struct { + TableName string //数据库表名 +} diff --git a/filepath/Testreversing.go b/filepath/Testreversing.go new file mode 100644 index 0000000..9e044d8 --- /dev/null +++ b/filepath/Testreversing.go @@ -0,0 +1,26 @@ +package filepath + +import ( + "time" +) + +/* + * @param + * @return + * @description 这个是生成的文件,没有格式化 + */ +type Testreversing struct { + t1 int `xorm:"pk not null bigint(20)"` //你好 + t2 string `xorm:"not null binary(255)"` //你不好 + t3 string `xorm:"bit(64)"` //你是不是啥 + t4 string `xorm:"blob"` //是的 + t5 string `xorm:"char(255)"` // + t6 time.Time `xorm:"date"` // + t7 string `xorm:"datetime"` //hello + t8 float64 `xorm:"double(255,0)"` // + t9 float64 `xorm:"float(255,0)"` // + t10 int `xorm:"int(255)"` // + t11 time.Time `xorm:"pk not null time"` // + t12 time.Time `xorm:"not null timestamp"` // + t13 string `xorm:"varchar(255)"` // +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..baae97a --- /dev/null +++ b/go.mod @@ -0,0 +1,42 @@ +module ReverseEngine + +require ( + github.com/DATA-DOG/go-txdb v0.1.2 // indirect + github.com/aceld/zinx v0.0.0-20190612015555-26a73a3ddf2e // indirect + github.com/ahmetb/go-linq v3.0.0+incompatible // indirect + github.com/gin-contrib/sse v0.0.0-20190125020943-a7658810eb74 // indirect + github.com/gin-gonic/gin v1.3.0 // indirect + github.com/go-ini/ini v1.51.1 //read init property + github.com/go-redis/redis v6.15.2+incompatible // indirect + github.com/go-sql-driver/mysql v1.5.0 + github.com/go-xorm/cmd v0.0.0-20190426080617-f87981e709a1 // indirect + github.com/go-xorm/xorm v0.7.9 // indirect + github.com/golang/mock v1.3.0 // indirect + github.com/google/go-cmp v0.3.0 // indirect + github.com/hashicorp/golang-lru v0.5.1 // indirect + github.com/json-iterator/go v1.1.6 // indirect + github.com/kr/pretty v0.1.0 // indirect + github.com/modern-go/reflect2 v1.0.1 // indirect + github.com/muesli/cache2go v0.0.0-20180419202730-5a1839810579 // indirect + github.com/nochso/tocenize v0.4.0 // indirect + github.com/oschwald/geoip2-golang v1.2.1 // indirect + github.com/oschwald/maxminddb-golang v1.3.0 // indirect + github.com/panjf2000/ants v1.0.1 // indirect + github.com/panjf2000/ants/v2 v2.2.2 // indirect + github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967 // indirect + github.com/samuel/go-zookeeper v0.0.0-20180130194729-c4fab1ac1bec // indirect + github.com/satori/go.uuid v1.2.0 // indirect + github.com/sirupsen/logrus v1.4.2 // indirect + github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a // indirect + github.com/tealeg/xlsx v1.0.3 // indirect + github.com/tidwall/gjson v1.2.1 // indirect + github.com/valyala/fasthttp v1.1.0 // indirect + go.etcd.io/etcd v3.3.13+incompatible // indirect + go.uber.org/zap v1.10.0 // indirect + golang.org/x/net v0.0.0-20190603091049-60506f45cf65 // indirect + gopkg.in/go-playground/assert.v1 v1.2.1 // indirect + gopkg.in/go-playground/validator.v8 v8.18.2 // indirect + gopkg.in/ini.v1 v1.51.1 +) + +go 1.13 diff --git a/imp/ContentGenerate.go b/imp/ContentGenerate.go new file mode 100644 index 0000000..57e2029 --- /dev/null +++ b/imp/ContentGenerate.go @@ -0,0 +1,10 @@ +package imp + +import "ReverseEngine/entity" + +type ContentGenerate interface { + /* + 内容制造接口 + */ + GenerateFileContent(filePath string, fileName string, fms []entity.FieldMessage, hasTime bool, tagKey string) string +} diff --git a/imp/DbConnection.go b/imp/DbConnection.go new file mode 100644 index 0000000..f9e519b --- /dev/null +++ b/imp/DbConnection.go @@ -0,0 +1,10 @@ +package imp + +import "database/sql" + +type DbConnection interface { + /* + 数据库连接接口 + */ + CreateDbConnection(dbPath string, maxIdleConns int, connMaxLifeTime int) *sql.DB +} diff --git a/imp/ReverseCheck.go b/imp/ReverseCheck.go new file mode 100644 index 0000000..58b0332 --- /dev/null +++ b/imp/ReverseCheck.go @@ -0,0 +1,18 @@ +package imp + +import "database/sql" + +type ReverseCheck interface { + /* + 关于文件和文件夹的一致校验接口 + dirPath 文件夹路径 + tableName 数据库表名 + cover 是否覆盖已有文件 + */ + CheckFileDir(dirPath string, tableName string, cover bool) bool + + /* + 校验数据库表是否存在 + */ + CheckTableExist(dbName string, tableName string, db *sql.DB) bool +} diff --git a/reverse/Engine.go b/reverse/Engine.go new file mode 100644 index 0000000..90030a4 --- /dev/null +++ b/reverse/Engine.go @@ -0,0 +1,264 @@ +package reverse + +import ( + "ReverseEngine/entity" + "ReverseEngine/imp" + "ReverseEngine/static" + "ReverseEngine/support" + "ReverseEngine/util" + "bufio" + "database/sql" + _ "github.com/go-sql-driver/mysql" + "gopkg.in/ini.v1" + "log" + "os" + "strings" +) + +var ( + //数据库到实体映射 可在config.ini中配置 + tm = make(map[string]string) + + //通用配置 可在config.ini中配置 + def = "string" //默认的映射类型 + tagKey = "xorm" //tag的key + suffix = ".go" //文件后缀 + cover = true //存在文件是否进行覆盖,只有配置为false的时候不会覆盖,其他默认为true + + //数据库配置 可在config.ini中配置 格式是:”用户名:密码@tcp(IP:端口)/数据库?charset=utf8” + username = "你的mysql连接账号" + password = "你的连接密码" + tcp = "@tcp" + ip = "你的地址" + port = "3306" + database = "你的数据库" //这个是我的数据库 + charset = "utf8mb4&parseTime=True&loc=Local" + connMaxLifeTime = 100 + maxIdleConns = 10 + + //校验、数据库连接实现类 + cg imp.ContentGenerate + dbc imp.DbConnection + rc imp.ReverseCheck +) + +//---------------------------------------初始化,策略配置,规则校验----------------------------------------- + +type DataReverseEngine struct { +} + +func NewEngine() *DataReverseEngine { + return &DataReverseEngine{} +} + +//创建初始化 --加载配置信息等 +func init() { + cfg := loadIniCfg() //获取ini配置文件的对象 + if nil == cfg { + panic("配置文件 config.ini配置错误,请检查") + return + } + loadCommonConfig(cfg) //加载通用配置 + loadDataBaseConfig(cfg) //加载数据库配置 + loadTypeMappingConfig(cfg) //加载类型映射配置 + rc = &support.CheckSupport{} //校验支持 + dbc = &support.DbSupport{} //数据库支持 + cg = &support.FileContentSupport{} //文本拼接支持 + +} + +/* + 重写自己的文件类型定义实现 +*/ +func (*DataReverseEngine) SetContentGenerate(face imp.ContentGenerate) { + cg = face +} + +/* + 重写自己的数据库连接工具 +*/ +func (*DataReverseEngine) SetDbConnection(face imp.DbConnection) { + dbc = face +} + +/* + 重写自己的校验规则 +*/ +func (*DataReverseEngine) SetReverseCheck(face imp.ReverseCheck) { + rc = face +} + +func checkError(err error, s string) { + if err != nil { + panic(s + err.Error()) + } +} + +//----------------------------------逆向工程--------------------------------- + +/* + 数据库逆向工程 + tableName:表名 + path:创建目录的相对路径(右键选中文件夹,点击relative path得到的路径) +*/ +func (r *DataReverseEngine) SimpleEngineer(tableName string, path string) { + r.Engineer(database, tableName, path, cover) +} + +/* + 数据库逆向工程 + tableName:表名 + path:创建目录的相对路径(右键选中文件夹,点击relative path得到的路径) + cover:如果已经存在改文件是否进行覆盖 +*/ +func (r *DataReverseEngine) SimEngineer(tableName string, path string, cover bool) { + r.Engineer(database, tableName, path, cover) +} + +/* + 数据库逆向工程 + dbName:数据库名称 + tableName:表名 + path:创建目录的相对路径(右键选中文件夹,点击relative path得到的路径) + cover:如果已经存在改文件是否进行覆盖 +*/ +func (*DataReverseEngine) Engineer(dbName string, tableName string, path string, cover bool) { + //tableName = strings.Title(tableName) //tableName进行首字母大写 + if !rc.CheckFileDir(path, tableName, cover) { //进行通用校验 + return + } + dbPath := strings.Join([]string{username, ":", password, tcp, "(", ip, ":", port, ")/", database, "?", "charset=", charset}, "") + db := dbc.CreateDbConnection(dbPath, maxIdleConns, connMaxLifeTime) //连接数据库--可配置连接不同的数据库 + if db == nil { + log.Println("数据库连接失败,程序终止") + return + } + //校验对应的数据库表是否存在 + if rc.CheckTableExist(dbName, tableName, db) { + dbReverse(path, tableName, dbName, db) + return + } + log.Println("数据库表不存在:", tableName) //不存在的话打印日志返回 +} + +func dbReverse(path string, tableName string, dbName string, db *sql.DB) { + file, result := util.CreateNeedOpenFile(util.GenerateFilePath(path, tableName, static.Splice, suffix)) + if !result { + return + } + cms := util.FindColumnMessage(dbName, tableName, db) //查询数据库表字段信息 + dErr := db.Close() //关闭数据库连接 + checkError(dErr, "数据库关闭失败:") + if nil == cms { + log.Println("没有查到数据库字段信息略过") + } + fms, hasTime := buildFieldMessage(cms) //创建字段名和类型的映射 + w := bufio.NewWriter(file) //进行文件的操作 + content := cg.GenerateFileContent(path, tableName, fms, hasTime, tagKey) //拼go文件 + _, err := w.WriteString(content) + checkError(err, "写入出错了:") + fErr, cErr := w.Flush(), file.Close() + + checkError(fErr, "文件写入出错了,flush错误:") + checkError(cErr, "文件写入出错了,close错误:") +} + +func buildFieldMessage(cms []entity.ColumnMessage) ([]entity.FieldMessage, bool) { + var fms []entity.FieldMessage + hasTime := false + + for _, cm := range cms { + fm := &entity.FieldMessage{ + FiledName: cm.Field, //字段名称 + FieldType: typeMapping(cm.Type, &hasTime), //字段类型,进行映射 + OriginType: cm.Type, //数据库原始类型 + IsKey: cm.IsKey(), //是否主键 + TagKey: tagKey, //tag的key值,这边先写死,正常应该是可配 + Comment: cm.GetComment(), //注释信息 + Default: cm.GetDefault(), //默认值信息 + CanNull: cm.CanNull(), //是否可以为空 + } + + fms = append(fms, *fm) + } + + return fms, hasTime +} + +func typeMapping(t string, has *bool) string { + rt := strings.Split(t, "(")[0] //需要把类型后面的([长度])去掉 + value, ok := tm[rt] + if ok { + if value == static.TimeT { + *has = true + } + return value + } + + return def +} + +//-----------------------------------读取和属性设置----------------------------- +func loadIniCfg() *ini.File { + cfg, err := ini.Load(static.IniAddress) + if err != nil { + log.Println("读取失败使用原始配置", err) + os.Exit(1) + return nil + } + return cfg +} + +func loadDataBaseConfig(cfg *ini.File) { + if len(cfg.Section(static.DataBase).Keys()) == 0 { + log.Println("配置区间为空,采用数据库配置通用配置") + return + } + util.GenerateConfig("username", username, cfg.Section(static.DataBase)) + util.GenerateConfig("password", password, cfg.Section(static.DataBase)) + util.GenerateConfig("tcp", tcp, cfg.Section(static.DataBase)) + util.GenerateConfig("ip", ip, cfg.Section(static.DataBase)) + util.GenerateConfig("port", port, cfg.Section(static.DataBase)) + util.GenerateConfig("database", database, cfg.Section(static.DataBase)) + util.GenerateConfig("charset", charset, cfg.Section(static.DataBase)) + util.GenerateConfigInt("connMaxLifeTime", connMaxLifeTime, cfg.Section(static.DataBase)) + util.GenerateConfigInt("maxIdleConns", maxIdleConns, cfg.Section(static.DataBase)) + +} + +func loadCommonConfig(cfg *ini.File) { + if len(cfg.Section(static.Common).Keys()) == 0 { + log.Println("配置区间为空,采用通用配置") + return + } + util.GenerateConfig("def", def, cfg.Section(static.Common)) //默认的映射类型 + util.GenerateConfig("tagKey", tagKey, cfg.Section(static.Common)) //tag的key + util.GenerateConfig("suffix", suffix, cfg.Section(static.Common)) //文件后缀 + util.GenerateConfigBool("cover", &cover, cfg.Section(static.Common)) //存在文件是否进行覆盖,只有配置为false的时候不会覆盖,其他默认为true +} + +func defaultTypeMappingConfig() { + tm["double"] = "float64" + tm["float"] = "float64" + tm["int"] = "int" + tm["tinyint"] = "int" + tm["bigint"] = "int" + tm["time"] = "time.Time" + tm["timestamp"] = "time.Time" + tm["date"] = "time.Time" + tm["dateTime"] = "time.Time" +} + +func loadTypeMappingConfig(cfg *ini.File) { + if len(cfg.Section(static.MapType).Keys()) == 0 { + defaultTypeMappingConfig() + log.Println("配置区间为空,采用类型映射通用配置") + return + } + + for _, k := range cfg.Section(static.MapType).Keys() { + if k.Name() != "" { + tm[k.Name()] = k.Value() + } + } +} diff --git a/reverse/config.ini b/reverse/config.ini new file mode 100644 index 0000000..808e106 --- /dev/null +++ b/reverse/config.ini @@ -0,0 +1,28 @@ +[database] //数据库配置 + username = 你的mysql连接账号 + password = 你的连接密码 + tcp = @tcp + ip = 你的地址 + port = 3306 + database = 你的数据库名 + charset = utf8mb4&parseTime=True&loc=Local + connMaxLifeTime = 100 + maxIdleConns = 10 + +[common] //通用配置 + def = string //默认的映射类型 + tagKey = xorm //tag的key + suffix = .go //文件后缀 + cover = true //存在文件是否进行覆盖,只有配置为false的时候不会覆盖,其他默认为true + + +[type_mapping] //类型映射 + double = float64 + float = float64 + int = int + tinyint = int + bigint = int + time = time.Time + timestamp = time.Time + date = time.Time + datetime = time.Time \ No newline at end of file diff --git a/static/DealConst.go b/static/DealConst.go new file mode 100644 index 0000000..33f101e --- /dev/null +++ b/static/DealConst.go @@ -0,0 +1,16 @@ +package static + +const ( + Splice = "/" //文件分割符 + LineFeed = "\n" //换行符 + LeftBrace = "{" //左大括号 + RightBrace = "}" //右大括号 + DoubleInclinedRod = "//" //创斜杆 + Colon = ":" //冒号 + TimeT = "time.Time" //时间类型 + + IniAddress = "reverse/config.ini" //ini文件地址 + Common = "common" //ini文件通用配置的section名 + DataBase = "database" //ini文件数据库配置的section名 + MapType = "type_mapping" //ini文件类型映射的section名 +) diff --git a/support/CheckPathSupport.go b/support/CheckPathSupport.go new file mode 100644 index 0000000..8f919dd --- /dev/null +++ b/support/CheckPathSupport.go @@ -0,0 +1,50 @@ +package support + +import ( + "ReverseEngine/entity" + "ReverseEngine/static" + "ReverseEngine/util" + "database/sql" + "log" + "reflect" + "strings" +) + +type CheckPathSupport struct{} + +func (*CheckPathSupport) CheckFileDir(path string, tableName string, cover bool) bool { + //先查询一下路径是否为文件夹 + if b := util.IsDirExist(path); !b { + log.Println("文件夹路径不存在略过") + return false + } + //如果是不覆盖的情况,判断是否有这个文件,如果有的话直接返回 + if !cover && util.IsFileExist(strings.Join([]string{path, static.Splice, tableName}, "")) { + return false + } + + //判断读写权限 + if !util.HasReadWritePermission(path) { + log.Println("没有该文件夹的读写权限:", path) + return false + } + + return true +} + +func (*CheckPathSupport) CheckTableExist(dbName string, tableName string, db *sql.DB) bool { + var tm entity.TableMessage + err := db.QueryRow(strings.Join([]string{"select TABLE_NAME from information_schema.TABLES where TABLE_SCHEMA=? and TABLE_NAME=?"}, ""), dbName, tableName).Scan(&tm.TableName) + //查询失败 + if err != nil { + panic("数据库查询失败:" + err.Error()) + return false + } + //查询没有数据 + if !reflect.ValueOf(tm).IsValid() || tm.TableName == "" { + panic("没有表数据信息:" + tableName) + return false + } + + return true +} diff --git a/support/CheckSupport.go b/support/CheckSupport.go new file mode 100644 index 0000000..d7641a5 --- /dev/null +++ b/support/CheckSupport.go @@ -0,0 +1,57 @@ +package support + +import ( + "ReverseEngine/entity" + "ReverseEngine/static" + "ReverseEngine/util" + "database/sql" + "log" + "reflect" + "strings" +) + +type CheckSupport struct{} + +func (*CheckSupport) CheckFileDir(path string, tableName string, cover bool) bool { + b, realPath := pathExist(path) + //先查询一下路径是否为文件夹 + if !b { + log.Println("文件夹路径不存在略过") + return false + } + //如果是不覆盖的情况,判断是否有这个文件,如果有的话直接返回 + if !cover && util.IsFileExist(strings.Join([]string{path, static.Splice, tableName}, "")) { + return false + } + + //判断读写权限 + if !util.HasReadWritePermission(realPath) { + log.Println("没有该文件夹的读写权限:", realPath) + return false + } + + return true +} + +func (*CheckSupport) CheckTableExist(dbName string, tableName string, db *sql.DB) bool { + var tm entity.TableMessage + err := db.QueryRow(strings.Join([]string{"select TABLE_NAME from information_schema.TABLES where TABLE_SCHEMA=? and TABLE_NAME=?"}, ""), dbName, tableName).Scan(&tm.TableName) + //查询失败 + if err != nil { + panic("数据库查询失败:" + err.Error()) + return false + } + //查询没有数据 + if !reflect.ValueOf(tm).IsValid() || tm.TableName == "" { + panic("没有表数据信息:" + tableName) + return false + } + + return true +} + +func pathExist(path string) (bool, string) { + pp := util.GetCurrentPath() + realPath := strings.Join([]string{pp, static.Splice, path}, "") + return util.IsDirExist(strings.Join([]string{pp, static.Splice, path}, "")), realPath +} diff --git a/support/DbSupport.go b/support/DbSupport.go new file mode 100644 index 0000000..4d0d6fc --- /dev/null +++ b/support/DbSupport.go @@ -0,0 +1,28 @@ +package support + +import ( + "database/sql" + _ "github.com/go-sql-driver/mysql" + "log" + "time" +) + +type DbSupport struct{} + +func (dbc *DbSupport) CreateDbConnection(dbPath string, maxIdleConns int, connMaxLifeTime int) *sql.DB { + var db *sql.DB + db, _ = sql.Open("mysql", dbPath) + + //设置数据库最大连接数 + db.SetConnMaxLifetime(time.Duration(connMaxLifeTime)) + //设置上数据库最大闲置连接数 + db.SetMaxIdleConns(maxIdleConns) + + if err := db.Ping(); err != nil { + panic("数据库连接失败") + return nil + } + + log.Println("数据库连接成功") + return db +} diff --git a/support/FileContentSupport.go b/support/FileContentSupport.go new file mode 100644 index 0000000..5bc51d0 --- /dev/null +++ b/support/FileContentSupport.go @@ -0,0 +1,101 @@ +package support + +import ( + "ReverseEngine/entity" + "ReverseEngine/static" + "strings" +) + +type FileContentSupport struct{} + +func (FileContentSupport) GenerateFileContent(filePath string, fileName string, fms []entity.FieldMessage, hasTime bool, tagKey string) string { + var build strings.Builder + pName := splitLast(filePath, static.Splice) + + writePackage(&build, pName) //写文件的引用包 + writeImportIfTime(&build, hasTime) //如果有time.Time类型的需要写入import + buildStructHead(&build, fileName) //写入结构体头 + + //对于没有字段的返回空定义的文件 + if len(fms) == 0 { + build.WriteString(static.RightBrace) + return build.String() + } + + //写中间字段的操作 + for _, fm := range fms { + build.WriteString(" ") + build.WriteString(fm.FiledName) + build.WriteString(" ") + build.WriteString(fm.FieldType) + build.WriteString(" ") + build.WriteString(generateTag(fm, tagKey)) + build.WriteString(" ") + build.WriteString(static.DoubleInclinedRod) + build.WriteString(fm.Comment) + build.WriteString(static.LineFeed) + } + + build.WriteString(static.RightBrace) + + return build.String() +} + +func generateTag(message entity.FieldMessage, tagKey string) string { + var build strings.Builder + build.WriteString("`") + build.WriteString(tagKey) + build.WriteString(static.Colon) + generateTagValue(message, &build) + build.WriteString("`") + return build.String() +} + +func generateTagValue(message entity.FieldMessage, builder *strings.Builder) { + builder.WriteString("\"") + if message.IsKey { + builder.WriteString("pk ") + } + if !message.CanNull { + builder.WriteString("not null ") + } + + builder.WriteString(message.OriginType) + builder.WriteString("\"") +} + +func buildStructHead(build *strings.Builder, fileName string) { + build.WriteString("type ") + build.WriteString(fileName) + build.WriteString(" struct") + build.WriteString(static.LeftBrace) + build.WriteString(static.LineFeed) +} + +func writeImportIfTime(build *strings.Builder, hasTime bool) { + //如果有类型匹配上时间,那么需要在头部加上import + if hasTime { + build.WriteString("import (") + build.WriteString("\n ") + build.WriteString("\"time\"") + build.WriteString("\n") + build.WriteString(")") + build.WriteString(static.LineFeed) + build.WriteString(static.LineFeed) + } +} + +func writePackage(build *strings.Builder, pName string) { + build.WriteString("package ") + build.WriteString(pName) + build.WriteString(static.LineFeed) + build.WriteString(static.LineFeed) +} + +/* + 返回按照规则切割的最后一个字符串 +*/ +func splitLast(filePath string, split string) string { + ss := strings.Split(filePath, split) + return ss[len(ss)-1] +} diff --git a/support/FileContentSupport2.go b/support/FileContentSupport2.go new file mode 100644 index 0000000..60f041d --- /dev/null +++ b/support/FileContentSupport2.go @@ -0,0 +1,13 @@ +package support + +import ( + "ReverseEngine/entity" + "fmt" +) + +type FileContentSUpport2 struct{} + +func (FileContentSUpport2) GenerateFileContent(filePath string, fileName string, fms []entity.FieldMessage, hasTime bool, tagKey string) string { + fmt.Println("12321321") + return " " +} diff --git a/util/ColorUtil.go b/util/ColorUtil.go new file mode 100644 index 0000000..deb7e4d --- /dev/null +++ b/util/ColorUtil.go @@ -0,0 +1,44 @@ +/* + * @Author : huangzj + * @Time : 2020/4/17 14:04 + * @Description: + */ + +package util + +import "syscall" + +var ( + kernel32 *syscall.LazyDLL = syscall.NewLazyDLL(`kernel32.dll`) + proc *syscall.LazyProc = kernel32.NewProc(`SetConsoleTextAttribute`) + CloseHandle *syscall.LazyProc = kernel32.NewProc(`CloseHandle`) + + // 给字体颜色对象赋值 + FontColor Color = Color{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} +) + +type Color struct { + black int // 黑色 + blue int // 蓝色 + green int // 绿色 + cyan int // 青色 + Red int // 红色 + purple int // 紫色 + yellow int // 黄色 + light_gray int // 淡灰色(系统默认值) + gray int // 灰色 + light_blue int // 亮蓝色 + light_green int // 亮绿色 + light_cyan int // 亮青色 + light_red int // 亮红色 + light_purple int // 亮紫色 + light_yellow int // 亮黄色 + white int // 白色 +} + +// 输出有颜色的字体 +func ColorPrint(s string, i int) { + handle, _, _ := proc.Call(uintptr(syscall.Stdout), uintptr(i)) + print(s) + CloseHandle.Call(handle) +} diff --git a/util/DbUtil.go b/util/DbUtil.go new file mode 100644 index 0000000..79713b8 --- /dev/null +++ b/util/DbUtil.go @@ -0,0 +1,32 @@ +package util + +import ( + "ReverseEngine/entity" + "database/sql" + "strings" +) + +type DbUtil struct { +} + +func FindColumnMessage(dbName string, tableName string, db *sql.DB) []entity.ColumnMessage { + var cms []entity.ColumnMessage + + row, err := db.Query(strings.Join([]string{"show full columns from ", dbName, ".", tableName}, "")) + if err != nil { + panic("数据库查询出错:" + err.Error()) + } + + for row.Next() { + var cm entity.ColumnMessage + err := row.Scan(&cm.Field, &cm.Type, &cm.Collation, &cm.Null, &cm.Key, &cm.Default, &cm.Extra, &cm.Privileges, &cm.Comment) + if err != nil { + panic("字段赋值错误了" + err.Error()) + return nil + } + + cms = append(cms, cm) + } + + return cms +} diff --git a/util/FileUtil.go b/util/FileUtil.go new file mode 100644 index 0000000..f1a4785 --- /dev/null +++ b/util/FileUtil.go @@ -0,0 +1,135 @@ +package util + +import ( + "log" + "os" + "strings" +) + +/* + 获取当前项目的路径 +*/ +func GetCurrentPath() string { + dir, err := os.Getwd() + if err != nil { + log.Fatal(err) + } + return strings.Replace(dir, "\\", "/", -1) +} + +/* + 判断路径是否存在并且为文件夹 +*/ +func IsDirExist(dirPath string) bool { + fi, err := os.Stat(dirPath) + if err != nil { + return false + } + if fi.IsDir() { + return true + } + + return false +} + +/* + 判断路径是否存在并且为文件 +*/ +func IsFileExist(filePath string) bool { + fi, err := os.Stat(filePath) + //文件不存在或者是文件夹的直接返回false + if err != nil || fi.IsDir() { + return false + } + + return true +} + +/* + 判断是否有读权限 +*/ +func HasReadPermission(filePath string) bool { + _, err := os.OpenFile(filePath, os.O_RDONLY, 0666) + if err != nil { + if os.IsPermission(err) { + return false + } + } + + return true + +} + +/* + 判断是否有写权限 +*/ +func HasWritePermission(filePath string) bool { + _, err := os.OpenFile(filePath, os.O_WRONLY, 0666) + if err != nil { + if os.IsPermission(err) { + return false + } + } + + return true +} + +/* + 判断是否有读+写权限 +*/ +func HasReadWritePermission(filePath string) bool { + return HasReadPermission(filePath) && HasWritePermission(filePath) +} + +/* + 创建文件 +*/ +func CreateFile(filePath string) (*os.File, bool) { + file, err := os.Create(filePath) + if err != nil { + panic("创建文件失败" + err.Error()) + return nil, false + } + + return file, true +} + +/* + 返回拼接的文件路径,这边类的首字母需要大写 + dirPath 文件夹路径 + filePath 文件路径 + splice 路径分隔符 + suffix 后缀 +*/ +func GenerateFilePath(dirPath string, filePath string, splice string, suffix string) string { + return strings.Join([]string{dirPath, splice, strings.Title(filePath), suffix}, "") +} + +/* + 创建(如果需要)并已读写权限打开文件 + os.O_RDONLY // 只读 + os.O_WRONLY // 只写 + os.O_RDWR // 读写 + os.O_APPEND // 往文件中添建(Append) + os.O_CREATE // 如果文件不存在则先创建 + os.O_TRUNC // 文件打开时裁剪文件 + os.O_EXCL // 和O_CREATE一起使用,文件不能存在 + os.O_SYNC // 以同步I/O的方式打开 +*/ +func CreateNeedOpenFile(filePath string) (*os.File, bool) { + //先删除再创建 + if IsFileExist(filePath) { + err := os.Remove(filePath) + if err != nil { + panic("文件删除失败:" + err.Error()) + return nil, false + } + } + + file, err := os.OpenFile(filePath, os.O_CREATE|os.O_RDWR, 0666) + if err != nil { + panic("打开文件失败:" + err.Error()) + return nil, false + } + return file, true +} diff --git a/util/GenerateUtil.go b/util/GenerateUtil.go new file mode 100644 index 0000000..4f55134 --- /dev/null +++ b/util/GenerateUtil.go @@ -0,0 +1,35 @@ +/* + * @Author : huangzj + * @Time : 2020/4/17 14:41 + * @Description: + */ + +package util + +import ( + "gopkg.in/ini.v1" + "strconv" +) + +func GenerateConfigBool(key string, real *bool, section *ini.Section) { + if section.Key(key).String() != "false" { + *real = true + return + } + *real = false +} + +func GenerateConfig(key string, real string, section *ini.Section) { + if section.Key(key).String() != "" { + real = section.Key(key).String() + } +} + +func GenerateConfigInt(key string, real int, section *ini.Section) { + if section.Key(key).String() != "" { + value, err := strconv.Atoi(section.Key(key).String()) + if err == nil { + real = value + } + } +}