diff --git a/server/api/v1/sys_auto_code.go b/server/api/v1/sys_auto_code.go index 09ac241813016578c973dee1529a9ee2ba4d9155..fcffbd461dff61f8877ca61a718c474e77667556 100644 --- a/server/api/v1/sys_auto_code.go +++ b/server/api/v1/sys_auto_code.go @@ -5,6 +5,7 @@ import ( "fmt" "gin-vue-admin/global" "gin-vue-admin/model" + "gin-vue-admin/model/request" "gin-vue-admin/model/response" "gin-vue-admin/service" "gin-vue-admin/utils" @@ -15,6 +16,49 @@ import ( "go.uber.org/zap" ) +// @Tags AutoCode +// @Summary 查询回滚记录 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body request.SysAutoHistory true "查询回滚记录" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"获取成功"}" +// @Router /autoCode/preview [post] +func GetSysHistory(c *gin.Context) { + var search request.SysAutoHistory + _ = c.ShouldBindJSON(&search) + err, list, total := service.GetSysHistoryPage(search.PageInfo) + if err != nil { + global.GVA_LOG.Error("获取失败!", zap.Any("err", err)) + response.FailWithMessage("获取失败", c) + } else { + response.OkWithDetailed(response.PageResult{ + List: list, + Total: total, + Page: search.Page, + PageSize: search.PageSize, + }, "获取成功", c) + } +} + +// @Tags AutoCode +// @Summary 回滚 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body request.AutoHistoryByID true "回滚自动生成代码" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"回滚成功"}" +// @Router /autoCode/preview [post] +func RollBack(c *gin.Context) { + var id request.AutoHistoryByID + _ = c.ShouldBindJSON(&id) + if err := service.RollBack(id.ID); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + response.OkWithMessage("回滚成功", c) +} + // @Tags AutoCode // @Summary 预览创建后的代码 // @Security ApiKeyAuth @@ -54,15 +98,18 @@ func CreateTemp(c *gin.Context) { response.FailWithMessage(err.Error(), c) return } + var apiIds []uint if a.AutoCreateApiToSql { - if err := service.AutoCreateApi(&a); err != nil { + if ids, err := service.AutoCreateApi(&a); err != nil { global.GVA_LOG.Error("自动化创建失败!请自行清空垃圾数据!", zap.Any("err", err)) c.Writer.Header().Add("success", "false") c.Writer.Header().Add("msg", url.QueryEscape("自动化创建失败!请自行清空垃圾数据!")) return + } else { + apiIds = ids } } - err := service.CreateTemp(a) + err := service.CreateTemp(a, apiIds...) if err != nil { if errors.Is(err, model.AutoMoveErr) { c.Writer.Header().Add("success", "false") diff --git a/server/model/request/sys_autocode.go b/server/model/request/sys_autocode.go index d97be350368f393b173f734f3562dccd3a73f4cb..2e64eb9aefaea5b6998b0d2176a8fb27e4f7c92a 100644 --- a/server/model/request/sys_autocode.go +++ b/server/model/request/sys_autocode.go @@ -1,5 +1,13 @@ package request +type SysAutoHistory struct { + PageInfo +} + +type AutoHistoryByID struct { + ID uint `json:"id"` +} + type DBReq struct { Database string `json:"database" gorm:"column:database"` } diff --git a/server/model/sys_auto_code.go b/server/model/sys_auto_code.go index 9e030f5d4d256c53ea134a6b2166095ce4c9b01a..73b68f4308a974e2063855bf78c8be3d51bcc745 100644 --- a/server/model/sys_auto_code.go +++ b/server/model/sys_auto_code.go @@ -7,7 +7,7 @@ type AutoCodeStruct struct { StructName string `json:"structName"` // Struct名称 TableName string `json:"tableName"` // 表名 PackageName string `json:"packageName"` // 文件名称 - HumpPackageName string `json:"humpPackageName"` // go文件名称 + HumpPackageName string `json:"humpPackageName"` // go文件名称 Abbreviation string `json:"abbreviation"` // Struct简称 Description string `json:"description"` // Struct中文名称 AutoCreateApiToSql bool `json:"autoCreateApiToSql"` // 是否自动创建api diff --git a/server/model/sys_autocode_history.go b/server/model/sys_autocode_history.go new file mode 100644 index 0000000000000000000000000000000000000000..e0dc6df7e607ea91c5da6789eef9b0389d4b266e --- /dev/null +++ b/server/model/sys_autocode_history.go @@ -0,0 +1,14 @@ +package model + +import "gin-vue-admin/global" + +// 自动迁移代码记录,用于回滚,重放使用 + +type SysAutoCodeHistory struct { + global.GVA_MODEL + TableName string + AutoCodeMeta string `gorm:"type:text"` // 其他meta信息 path;path + InjectionMeta string `gorm:"type:text"` // 注入的内容 RouterPath@functionName@RouterString; + ApiIDs string // api表注册内容 + Flag int // 表示对应状态 0 代表创建, 1 代表回滚 ... +} diff --git a/server/router/sys_auto_code.go b/server/router/sys_auto_code.go index 723471d054305282b82bda9afaedd50ee4336528..c39605b78b538ccd52ae6fa6bdded51ec670ec68 100644 --- a/server/router/sys_auto_code.go +++ b/server/router/sys_auto_code.go @@ -8,6 +8,8 @@ import ( func InitAutoCodeRouter(Router *gin.RouterGroup) { AutoCodeRouter := Router.Group("autoCode") { + AutoCodeRouter.POST("getSysHistory", v1.GetSysHistory) // 获取回滚记录分页 + AutoCodeRouter.POST("rollback", v1.RollBack) // 回滚 AutoCodeRouter.POST("preview", v1.PreviewTemp) // 获取自动创建代码预览 AutoCodeRouter.POST("createTemp", v1.CreateTemp) // 创建自动化代码 AutoCodeRouter.GET("getTables", v1.GetTables) // 获取对应数据库的表 diff --git a/server/service/sys_api.go b/server/service/sys_api.go index 52e8682def18301e1354a683d7baca10e5fa72a9..55ff59983d9ba7d9484a956a5b552be4d49f346c 100644 --- a/server/service/sys_api.go +++ b/server/service/sys_api.go @@ -141,3 +141,7 @@ func DeleteApisByIds(ids request.IdsReq) (err error) { err = global.GVA_DB.Delete(&[]model.SysApi{}, "id in ?", ids.Ids).Error return err } + +func DeleteApiByIds(ids []string) (err error) { + return global.GVA_DB.Delete(model.SysApi{}, ids).Error +} diff --git a/server/service/sys_auto_code.go b/server/service/sys_auto_code.go index 679120af733fbf4d37fd466f931eebc7650b85f4..528c654ca85c395fa80a7e4d7482caed08ba4168 100644 --- a/server/service/sys_auto_code.go +++ b/server/service/sys_auto_code.go @@ -2,6 +2,7 @@ package service import ( "errors" + "fmt" "gin-vue-admin/global" "gin-vue-admin/model" "gin-vue-admin/model/request" @@ -9,6 +10,7 @@ import ( "io/ioutil" "os" "path/filepath" + "strconv" "strings" "text/template" @@ -98,7 +100,7 @@ func PreviewTemp(autoCode model.AutoCodeStruct) (map[string]string, error) { //@param: model.AutoCodeStruct //@return: err error -func CreateTemp(autoCode model.AutoCodeStruct) (err error) { +func CreateTemp(autoCode model.AutoCodeStruct, ids ...uint) (err error) { dataList, fileList, needMkdir, err := getNeedList(&autoCode) if err != nil { return err @@ -125,6 +127,13 @@ func CreateTemp(autoCode model.AutoCodeStruct) (err error) { return } }() + bf := strings.Builder{} + idBf := strings.Builder{} + injectionCodeMeta := strings.Builder{} + for _, id := range ids { + idBf.WriteString(strconv.Itoa(int(id))) + idBf.WriteString(";") + } if autoCode.AutoMoveFile { // 判断是否需要自动转移 for index, _ := range dataList { addAutoMoveFile(&dataList[index]) @@ -146,18 +155,51 @@ func CreateTemp(autoCode model.AutoCodeStruct) (err error) { if err != nil { return err } + + injectionCodeMeta.WriteString(fmt.Sprintf("%s@%s@%s", initializeGormFilePath, "MysqlTables", "model."+autoCode.StructName+"{},")) + injectionCodeMeta.WriteString(";") + injectionCodeMeta.WriteString(fmt.Sprintf("%s@%s@%s", initializeRouterFilePath, "Routers", "router.Init"+autoCode.StructName+"Router(PrivateGroup)")) + + // 保存生成信息 + for _, data := range dataList { + if len(data.autoMoveFilePath) != 0 { + bf.WriteString(data.autoMoveFilePath) + bf.WriteString(";") + } + } + if global.GVA_CONFIG.AutoCode.TransferRestart { go func() { _ = utils.Reload() }() } - return errors.New("创建代码成功并移动文件成功") + //return errors.New("创建代码成功并移动文件成功") } else { // 打包 - if err := utils.ZipFiles("./ginvueadmin.zip", fileList, ".", "."); err != nil { + if err = utils.ZipFiles("./ginvueadmin.zip", fileList, ".", "."); err != nil { return err } } + if autoCode.TableName != "" { + err = CreateAutoCodeHistory(bf.String(), + injectionCodeMeta.String(), + autoCode.TableName, + idBf.String(), + ) + } else { + err = CreateAutoCodeHistory(bf.String(), + injectionCodeMeta.String(), + autoCode.StructName, + idBf.String(), + ) + } + if err != nil { + return err + } + if autoCode.AutoMoveFile { + return errors.New("创建代码成功并移动文件成功") + } return nil + } //@author: [piexlmax](https://github.com/piexlmax) @@ -215,6 +257,10 @@ func GetColumn(tableName string, dbName string) (err error, Columns []request.Co return err, Columns } +func DropTable(tableName string) error { + return global.GVA_DB.Exec("DROP TABLE " + tableName).Error +} + //@author: [SliverHorn](https://github.com/SliverHorn) //@author: [songzhibin97](https://github.com/songzhibin97) //@function: addAutoMoveFile @@ -267,7 +313,7 @@ func addAutoMoveFile(data *tplData) { //@param: a *model.AutoCodeStruct //@return: err error -func AutoCreateApi(a *model.AutoCodeStruct) (err error) { +func AutoCreateApi(a *model.AutoCodeStruct) (ids []uint, err error) { var apiList = []model.SysApi{ { Path: "/" + a.Abbreviation + "/" + "create" + a.StructName, @@ -307,17 +353,20 @@ func AutoCreateApi(a *model.AutoCodeStruct) (err error) { }, } err = global.GVA_DB.Transaction(func(tx *gorm.DB) error { + for _, v := range apiList { var api model.SysApi if errors.Is(tx.Where("path = ? AND method = ?", v.Path, v.Method).First(&api).Error, gorm.ErrRecordNotFound) { - if err := tx.Create(&v).Error; err != nil { // 遇到错误时回滚事务 + if err = tx.Create(&v).Error; err != nil { // 遇到错误时回滚事务 return err + } else { + ids = append(ids, v.ID) } } } return nil }) - return err + return ids, err } func getNeedList(autoCode *model.AutoCodeStruct) (dataList []tplData, fileList []string, needMkdir []string, err error) { @@ -361,10 +410,10 @@ func getNeedList(autoCode *model.AutoCodeStruct) (dataList []tplData, fileList [ firstDot := strings.Index(origFileName, ".") if firstDot != -1 { var fileName string - if origFileName[firstDot:] !=".go"{ - fileName = autoCode.PackageName+origFileName[firstDot:] - }else{ - fileName = autoCode.HumpPackageName+origFileName[firstDot:] + if origFileName[firstDot:] != ".go" { + fileName = autoCode.PackageName + origFileName[firstDot:] + } else { + fileName = autoCode.HumpPackageName + origFileName[firstDot:] } dataList[index].autoCodePath = filepath.Join(autoPath, trimBase[:lastSeparator], autoCode.PackageName, diff --git a/server/service/sys_autocode_history.go b/server/service/sys_autocode_history.go new file mode 100644 index 0000000000000000000000000000000000000000..985cb612cedc8eea9a034d3a102ab1d39b5928f1 --- /dev/null +++ b/server/service/sys_autocode_history.go @@ -0,0 +1,75 @@ +package service + +import ( + "errors" + "gin-vue-admin/global" + "gin-vue-admin/model" + "gin-vue-admin/model/request" + "gin-vue-admin/utils" + "strings" + + "go.uber.org/zap" +) + +// CreateAutoCodeHistory RouterPath : RouterPath@RouterString;RouterPath2@RouterString2 +func CreateAutoCodeHistory(autoCodeMeta string, injectionMeta string, tableName string, apiIds string) error { + return global.GVA_DB.Create(&model.SysAutoCodeHistory{ + AutoCodeMeta: autoCodeMeta, + InjectionMeta: injectionMeta, + TableName: tableName, + ApiIDs: apiIds, + }).Error +} + +// RollBack 回滚 +func RollBack(id uint) error { + md := model.SysAutoCodeHistory{} + if err := global.GVA_DB.First(&md, id).Error; err != nil { + return err + } + // 清除API表 + err := DeleteApiByIds(strings.Split(md.ApiIDs, ";")) + if err != nil { + global.GVA_LOG.Error("ClearTag DeleteApiByIds:", zap.Error(err)) + } + // 获取全部表名 + err, dbNames := GetTables(global.GVA_CONFIG.Mysql.Dbname) + if err != nil { + global.GVA_LOG.Error("ClearTag GetTables:", zap.Error(err)) + } + // 删除表 + for _, name := range dbNames { + if strings.Contains(strings.ToUpper(strings.Replace(name.TableName, "_", "", -1)), strings.ToUpper(md.TableName)) { + // 删除表 + if err = DropTable(name.TableName); err != nil { + global.GVA_LOG.Error("ClearTag DropTable:", zap.Error(err)) + + } + } + } + // 删除文件 + for _, path := range strings.Split(md.AutoCodeMeta, ";") { + _ = utils.DeLFile(path) + } + // 清除注入 + for _, v := range strings.Split(md.InjectionMeta, ";") { + // RouterPath@functionName@RouterString + meta := strings.Split(v, "@") + if len(meta) != 3 { + return errors.New("split InjectionMeta Err") + } + _ = utils.AutoClearCode(meta[0], meta[2]) + } + md.Flag = 1 + return global.GVA_DB.Save(&md).Error +} + +func GetSysHistoryPage(info request.PageInfo) (err error, list interface{}, total int64) { + limit := info.PageSize + offset := info.PageSize * (info.Page - 1) + db := global.GVA_DB + var fileLists []model.SysAutoCodeHistory + err = db.Find(&fileLists).Count(&total).Error + err = db.Limit(limit).Offset(offset).Order("updated_at desc").Find(&fileLists).Error + return err, fileLists, total +} diff --git a/server/service/sys_initdb.go b/server/service/sys_initdb.go index b84a086892815230d908c37580fa22a0c7d5fcaa..22901099570835b50f0a7ad2fa201b654b0f2bf8 100644 --- a/server/service/sys_initdb.go +++ b/server/service/sys_initdb.go @@ -131,6 +131,7 @@ func InitDB(conf request.InitDB) error { model.ExaSimpleUploader{}, model.ExaCustomer{}, model.SysOperationRecord{}, + model.SysAutoCodeHistory{}, ) if err != nil { global.GVA_DB = nil diff --git a/server/utils/file_operations.go b/server/utils/file_operations.go index 36115c694f07ff1a72920383a693eedaf3894396..0e747773da9cc5adb8dce248f88b55960767ad4a 100644 --- a/server/utils/file_operations.go +++ b/server/utils/file_operations.go @@ -42,6 +42,10 @@ Redirect: return os.Rename(src, dst) } +func DeLFile(filePath string) error { + return os.RemoveAll(filePath) +} + //@author: [songzhibin97](https://github.com/songzhibin97) //@function: TrimSpace //@description: 去除结构体空格 diff --git a/server/utils/injectionCode.go b/server/utils/injectionCode.go index 5bc66c4fd7340bf8676a16fb314d3cade99f635a..5b12f11d2cc16e59373793d14a5f9155ab796ad6 100644 --- a/server/utils/injectionCode.go +++ b/server/utils/injectionCode.go @@ -1,6 +1,7 @@ package utils import ( + "errors" "fmt" "go/ast" "go/parser" @@ -15,9 +16,18 @@ import ( //@param: filepath string, funcName string, codeData string //@return: error +const ( + startComment = "Code generated by gin-vue-admin Begin; DO NOT EDIT." + endComment = "Code generated by gin-vue-admin End; DO NOT EDIT." +) + +//@author: [LeonardWang](https://github.com/WangLeonard) +//@function: AutoInjectionCode +//@description: 向文件中固定注释位置写入代码 +//@param: filepath string, funcName string, codeData string +//@return: error + func AutoInjectionCode(filepath string, funcName string, codeData string) error { - startComment := "Code generated by gin-vue-admin Begin; DO NOT EDIT." - endComment := "Code generated by gin-vue-admin End; DO NOT EDIT." srcData, err := ioutil.ReadFile(filepath) if err != nil { return err @@ -141,3 +151,30 @@ func checkExist(srcData *[]byte, startPos int, endPos int, blockStmt *ast.BlockS } return false } + +func AutoClearCode(filepath string, codeData string) error { + srcData, err := ioutil.ReadFile(filepath) + if err != nil { + return err + } + srcData, err = cleanCode(codeData, string(srcData)) + if err != nil { + return err + } + return ioutil.WriteFile(filepath, srcData, 0600) +} + +func cleanCode(clearCode string, srcData string) ([]byte, error) { + bf := make([]rune, 0, 1024) + for i, v := range srcData { + if v == '\n' { + if strings.TrimSpace(string(bf)) == clearCode { + return append([]byte(srcData[:i-len(bf)]), []byte(srcData[i+1:])...), nil + } + bf = (bf)[:0] + continue + } + bf = append(bf, v) + } + return []byte(srcData), errors.New("未找到内容") +}