فهرست منبع

Merge pull request #585 from flipped-aurora/gva_gormv2_dev

新增自动化代码回滚功能
奇淼(piexlmax 3 سال پیش
والد
کامیت
2d8e2ae8b0

+ 89 - 2
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,89 @@ import (
 	"go.uber.org/zap"
 )
 
+// @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/delSysHistory [post]
+func DelSysHistory(c *gin.Context) {
+	var id request.AutoHistoryByID
+	_ = c.ShouldBindJSON(&id)
+	err := service.DeletePage(id.ID)
+	if err != nil {
+		global.GVA_LOG.Error("获取失败!", zap.Any("err", err))
+		response.FailWithMessage("获取失败", c)
+	}
+	response.OkWithMessage("删除成功", c)
+
+}
+
+// @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/getSysHistory [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/rollback [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
+// @accept application/json
+// @Produce application/json
+// @Param data body request.AutoHistoryByID true "获取meta信息"
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"获取成功"}"
+// @Router /autoCode/getMeta [post]
+func GetMeta(c *gin.Context) {
+	var id request.AutoHistoryByID
+	_ = c.ShouldBindJSON(&id)
+	if v, err := service.GetMeta(id.ID); err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	} else {
+		response.OkWithDetailed(gin.H{"meta": v}, "获取成功", c)
+	}
+
+}
+
 // @Tags AutoCode
 // @Summary 预览创建后的代码
 // @Security ApiKeyAuth
@@ -54,15 +138,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")

+ 1 - 1
server/global/global.go

@@ -9,7 +9,7 @@ import (
 
 	"gin-vue-admin/config"
 
-	"github.com/go-redis/redis"
+	"github.com/go-redis/redis/v8"
 	"github.com/spf13/viper"
 	"gorm.io/gorm"
 )

+ 1 - 5
server/go.mod

@@ -20,8 +20,8 @@ require (
 	github.com/go-openapi/swag v0.19.8 // indirect
 	github.com/go-playground/validator/v10 v10.3.0 // indirect
 	github.com/go-redis/redis v6.15.7+incompatible
+	github.com/go-redis/redis/v8 v8.11.0
 	github.com/go-sql-driver/mysql v1.5.0
-	github.com/golang/protobuf v1.4.2 // indirect
 	github.com/gookit/color v1.3.1
 	github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 // indirect
 	github.com/jordan-wright/email v0.0.0-20200824153738-3f5bafa1cd84
@@ -31,8 +31,6 @@ require (
 	github.com/mailru/easyjson v0.7.1 // indirect
 	github.com/mitchellh/mapstructure v1.2.2 // indirect
 	github.com/mojocn/base64Captcha v1.3.1
-	github.com/onsi/ginkgo v1.7.0 // indirect
-	github.com/onsi/gomega v1.4.3 // indirect
 	github.com/pelletier/go-toml v1.6.0 // indirect
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/qiniu/api.v7/v7 v7.4.1
@@ -52,10 +50,8 @@ require (
 	go.uber.org/zap v1.10.0
 	golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
-	golang.org/x/tools v0.0.0-20200324003944-a576cf524670 // indirect
 	google.golang.org/protobuf v1.24.0 // indirect
 	gopkg.in/ini.v1 v1.55.0 // indirect
-	gopkg.in/yaml.v2 v2.3.0 // indirect
 	gorm.io/driver/mysql v1.0.1
 	gorm.io/gorm v1.20.7
 )

+ 1 - 1
server/initialize/gorm.go

@@ -48,7 +48,7 @@ func MysqlTables(db *gorm.DB) {
 		model.ExaSimpleUploader{},
 		model.ExaCustomer{},
 		model.SysOperationRecord{},
-
+		model.SysAutoCodeHistory{},
 		// Code generated by gin-vue-admin Begin; DO NOT EDIT.
 		// Code generated by gin-vue-admin End; DO NOT EDIT.
 	)

+ 4 - 2
server/initialize/redis.go

@@ -1,8 +1,10 @@
 package initialize
 
 import (
+	"context"
 	"gin-vue-admin/global"
-	"github.com/go-redis/redis"
+
+	"github.com/go-redis/redis/v8"
 	"go.uber.org/zap"
 )
 
@@ -13,7 +15,7 @@ func Redis() {
 		Password: redisCfg.Password, // no password set
 		DB:       redisCfg.DB,       // use default DB
 	})
-	pong, err := client.Ping().Result()
+	pong, err := client.Ping(context.Background()).Result()
 	if err != nil {
 		global.GVA_LOG.Error("redis connect ping failed, err:", zap.Any("err", err))
 	} else {

+ 8 - 0
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"`
 }

+ 1 - 1
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

+ 18 - 0
server/model/sys_autocode_history.go

@@ -0,0 +1,18 @@
+package model
+
+import "gin-vue-admin/global"
+
+// 自动迁移代码记录,用于回滚,重放使用
+
+type SysAutoCodeHistory struct {
+	global.GVA_MODEL
+	TableName     string `json:"tableName"`
+	RequestMeta   string `gorm:"type:text" json:"requestMeta,omitempty"`   // 前端传入的结构化信息
+	AutoCodePath  string `gorm:"type:text" json:"autoCodePath,omitempty"`  // 其他meta信息 path;path
+	InjectionMeta string `gorm:"type:text" json:"injectionMeta,omitempty"` // 注入的内容 RouterPath@functionName@RouterString;
+	StructName    string `json:"structName"`
+	StructCNName  string `json:"structCNName"`
+	ApiIDs        string `json:"apiIDs,omitempty"` // api表注册内容
+	Flag          int    // 表示对应状态 0 代表创建, 1 代表回滚 ...
+
+}

+ 9 - 5
server/router/sys_auto_code.go

@@ -8,10 +8,14 @@ import (
 func InitAutoCodeRouter(Router *gin.RouterGroup) {
 	AutoCodeRouter := Router.Group("autoCode")
 	{
-		AutoCodeRouter.POST("preview", v1.PreviewTemp)   // 获取自动创建代码预览
-		AutoCodeRouter.POST("createTemp", v1.CreateTemp) // 创建自动化代码
-		AutoCodeRouter.GET("getTables", v1.GetTables)    // 获取对应数据库的表
-		AutoCodeRouter.GET("getDB", v1.GetDB)            // 获取数据库
-		AutoCodeRouter.GET("getColumn", v1.GetColumn)    // 获取指定表所有字段信息
+		AutoCodeRouter.POST("delSysHistory", v1.DelSysHistory) // 删除回滚记录
+		AutoCodeRouter.POST("getMeta", v1.GetMeta)             // 根据id获取meta信息
+		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)          // 获取对应数据库的表
+		AutoCodeRouter.GET("getDB", v1.GetDB)                  // 获取数据库
+		AutoCodeRouter.GET("getColumn", v1.GetColumn)          // 获取指定表所有字段信息
 	}
 }

+ 5 - 3
server/service/jwt_black_list.go

@@ -1,11 +1,13 @@
 package service
 
 import (
+	"context"
 	"errors"
 	"gin-vue-admin/global"
 	"gin-vue-admin/model"
-	"gorm.io/gorm"
 	"time"
+
+	"gorm.io/gorm"
 )
 
 //@author: [piexlmax](https://github.com/piexlmax)
@@ -38,7 +40,7 @@ func IsBlacklist(jwt string) bool {
 //@return: err error, redisJWT string
 
 func GetRedisJWT(userName string) (err error, redisJWT string) {
-	redisJWT, err = global.GVA_REDIS.Get(userName).Result()
+	redisJWT, err = global.GVA_REDIS.Get(context.Background(), userName).Result()
 	return err, redisJWT
 }
 
@@ -51,6 +53,6 @@ func GetRedisJWT(userName string) (err error, redisJWT string) {
 func SetRedisJWT(jwt string, userName string) (err error) {
 	// 此处过期时间等于jwt过期时间
 	timer := time.Duration(global.GVA_CONFIG.JWT.ExpiresTime) * time.Second
-	err = global.GVA_REDIS.Set(userName, jwt, timer).Err()
+	err = global.GVA_REDIS.Set(context.Background(), userName, jwt, timer).Err()
 	return err
 }

+ 4 - 0
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
+}

+ 71 - 10
server/service/sys_auto_code.go

@@ -1,7 +1,9 @@
 package service
 
 import (
+	"encoding/json"
 	"errors"
+	"fmt"
 	"gin-vue-admin/global"
 	"gin-vue-admin/model"
 	"gin-vue-admin/model/request"
@@ -9,6 +11,7 @@ import (
 	"io/ioutil"
 	"os"
 	"path/filepath"
+	"strconv"
 	"strings"
 	"text/template"
 
@@ -98,11 +101,12 @@ 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
 	}
+	meta, _ := json.Marshal(autoCode)
 	// 写入文件前,先创建文件夹
 	if err = utils.CreateDir(needMkdir...); err != nil {
 		return err
@@ -125,6 +129,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 +157,61 @@ 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.AutoMoveFile || autoCode.AutoCreateApiToSql {
+		if autoCode.TableName != "" {
+			err = CreateAutoCodeHistory(
+				string(meta),
+				autoCode.StructName,
+				autoCode.Description,
+				bf.String(),
+				injectionCodeMeta.String(),
+				autoCode.TableName,
+				idBf.String(),
+			)
+		} else {
+			err = CreateAutoCodeHistory(
+				string(meta),
+				autoCode.StructName,
+				autoCode.Description,
+				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 +269,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 +325,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 +365,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 +422,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,

+ 87 - 0
server/service/sys_autocode_history.go

@@ -0,0 +1,87 @@
+package service
+
+import (
+	"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(meta, structName, structCNName, autoCodePath string, injectionMeta string, tableName string, apiIds string) error {
+	return global.GVA_DB.Create(&model.SysAutoCodeHistory{
+		RequestMeta:   meta,
+		AutoCodePath:  autoCodePath,
+		InjectionMeta: injectionMeta,
+		StructName:    structName,
+		StructCNName:  structCNName,
+		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.AutoCodePath, ";") {
+		_ = utils.DeLFile(path)
+	}
+	// 清除注入
+	for _, v := range strings.Split(md.InjectionMeta, ";") {
+		// RouterPath@functionName@RouterString
+		meta := strings.Split(v, "@")
+		if len(meta) == 3 {
+			_ = utils.AutoClearCode(meta[0], meta[2])
+		}
+	}
+	md.Flag = 1
+	return global.GVA_DB.Save(&md).Error
+}
+
+func GetMeta(id uint) (string, error) {
+	var meta string
+	return meta, global.GVA_DB.Model(model.SysAutoCodeHistory{}).Select("request_meta").First(&meta, id).Error
+}
+
+// GetSysHistoryPage  获取系统历史数据
+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").Select("id,created_at,updated_at,struct_name,struct_cn_name,flag,table_name").Find(&fileLists).Error
+	return err, fileLists, total
+}
+
+// DeletePage 删除历史数据
+func DeletePage(id uint) error {
+	return global.GVA_DB.Delete(model.SysAutoCodeHistory{}, id).Error
+}

+ 1 - 0
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

+ 3 - 0
server/source/api.go

@@ -86,6 +86,9 @@ var apis = []model.SysApi{
 	{global.GVA_MODEL{ID: 83, CreatedAt: time.Now(), UpdatedAt: time.Now()}, "/excel/exportExcel", "导出excel", "excel", "POST"},
 	{global.GVA_MODEL{ID: 84, CreatedAt: time.Now(), UpdatedAt: time.Now()}, "/excel/downloadTemplate", "下载excel模板", "excel", "GET"},
 	{global.GVA_MODEL{ID: 85, CreatedAt: time.Now(), UpdatedAt: time.Now()}, "/api/deleteApisByIds", "批量删除api", "api", "DELETE"},
+	{global.GVA_MODEL{ID: 86, CreatedAt: time.Now(), UpdatedAt: time.Now()}, "/autoCode/getSysHistory", "查询回滚记录", "autoCode", "POST"},
+	{global.GVA_MODEL{ID: 87, CreatedAt: time.Now(), UpdatedAt: time.Now()}, "/autoCode/rollback", "回滚自动生成代码", "autoCode", "POST"},
+	{global.GVA_MODEL{ID: 88, CreatedAt: time.Now(), UpdatedAt: time.Now()}, "/autoCode/getMeta", "获取meta信息", "autoCode", "POST"},
 }
 
 //@author: [SliverHorn](https://github.com/SliverHorn)

+ 2 - 0
server/source/authorities_menus.go

@@ -39,6 +39,8 @@ var authorityMenus = []AuthorityMenus{
 	{"888", 21},
 	{"888", 22},
 	{"888", 23},
+	{"888", 24},
+	{"888", 25},
 	{"8881", 1},
 	{"8881", 2},
 	{"8881", 8},

+ 3 - 0
server/source/casbin.go

@@ -86,6 +86,9 @@ var carbines = []gormadapter.CasbinRule{
 	{PType: "p", V0: "888", V1: "/excel/exportExcel", V2: "POST"},
 	{PType: "p", V0: "888", V1: "/excel/downloadTemplate", V2: "GET"},
 	{PType: "p", V0: "888", V1: "/api/deleteApisByIds", V2: "DELETE"},
+	{PType: "p", V0: "888", V1: "/autoCode/getSysHistory", V2: "POST"},
+	{PType: "p", V0: "888", V1: "/autoCode/rollback", V2: "POST"},
+	{PType: "p", V0: "888", V1: "/autoCode/getMeta", V2: "POST"},
 	{PType: "p", V0: "8881", V1: "/base/login", V2: "POST"},
 	{PType: "p", V0: "8881", V1: "/user/register", V2: "POST"},
 	{PType: "p", V0: "8881", V1: "/api/createApi", V2: "POST"},

+ 2 - 0
server/source/menu.go

@@ -37,6 +37,8 @@ var menus = []model.SysBaseMenu{
 	{GVA_MODEL: global.GVA_MODEL{ID: 21, CreatedAt: time.Now(), UpdatedAt: time.Now()}, MenuLevel: 0, Hidden: false, ParentId: "9", Path: "simpleUploader", Name: "simpleUploader", Component: "view/example/simpleUploader/simpleUploader", Sort: 6, Meta: model.Meta{Title: "断点续传(插件版)", Icon: "upload"}},
 	{GVA_MODEL: global.GVA_MODEL{ID: 22, CreatedAt: time.Now(), UpdatedAt: time.Now()}, MenuLevel: 0, ParentId: "0", Path: "https://www.gin-vue-admin.com", Name: "https://www.gin-vue-admin.com", Hidden: false, Component: "/", Sort: 0, Meta: model.Meta{Title: "官方网站", Icon: "s-home"}},
 	{GVA_MODEL: global.GVA_MODEL{ID: 23, CreatedAt: time.Now(), UpdatedAt: time.Now()}, MenuLevel: 0, ParentId: "0", Path: "state", Name: "state", Hidden: false, Component: "view/system/state.vue", Sort: 6, Meta: model.Meta{Title: "服务器状态", Icon: "cloudy"}},
+	{GVA_MODEL: global.GVA_MODEL{ID: 24, CreatedAt: time.Now(), UpdatedAt: time.Now()}, MenuLevel: 0, ParentId: "14", Path: "autoCodeAdmin", Name: "autoCodeAdmin", Hidden: false, Component: "view/systemTools/autoCodeAdmin/index.vue", Sort: 1, Meta: model.Meta{Title: "自动化代码管理", Icon: "s-finance"}},
+	{GVA_MODEL: global.GVA_MODEL{ID: 25, CreatedAt: time.Now(), UpdatedAt: time.Now()}, MenuLevel: 0, ParentId: "14", Path: "autoCodeEdit/:id", Name: "autoCodeEdit", Hidden: true, Component: "view/systemTools/autoCodeAdmin/index.vue", Sort: 0, Meta: model.Meta{Title: "自动化代码(复用)", Icon: "s-finance"}},
 }
 
 //@author: [SliverHorn](https://github.com/SliverHorn)

+ 4 - 0
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: 去除结构体空格

+ 39 - 2
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("未找到内容")
+}

+ 1 - 2
web/Dockerfile

@@ -3,8 +3,7 @@ FROM node:12.16.1
 WORKDIR /gva_web/
 COPY . .
 
-RUN npm install -g cnpm --registry=https://registry.npm.taobao.org
-RUN cnpm install || npm install
+RUN npm install
 RUN npm run build
 
 FROM nginx:alpine

+ 1 - 1
web/public/index.html

@@ -8,7 +8,7 @@
     <meta name="viewport" content="width=device-width,initial-scale=1.0">
     <link rel="icon" href="<%= BASE_URL %>favicon.ico">
     <title><%=htmlWebpackPlugin.options.title%></title>
-    <% if(process.env.NODE_ENV!=='development'){ %>
+    <% if(process.env.NODE_ENV!=='development' && htmlWebpackPlugin.options.cdns){ %>
       <% htmlWebpackPlugin.options.cdns.forEach(function(item){ if(item.js){ %>
         <script type="text/javascript" src="<%= item.js %>"></script>
       <% } }) %>

+ 24 - 0
web/src/api/autoCode.js

@@ -60,3 +60,27 @@ export const getColumn = (params) => {
     params
   })
 }
+
+export const getSysHistory = (data) => {
+  return service({
+    url: '/autoCode/getSysHistory',
+    method: 'post',
+    data
+  })
+}
+
+export const rollback = (data) => {
+  return service({
+    url: '/autoCode/rollback',
+    method: 'post',
+    data
+  })
+}
+
+export const getMeta = (data) => {
+  return service({
+    url: '/autoCode/getMeta',
+    method: 'post',
+    data
+  })
+}

+ 11 - 2
web/src/view/systemTools/autoCode/index.vue

@@ -165,7 +165,7 @@ const fieldTemplate = {
 import FieldDialog from '@/view/systemTools/autoCode/component/fieldDialog.vue'
 import PreviewCodeDialg from '@/view/systemTools/autoCode/component/previewCodeDialg.vue'
 import { toUpperCase, toHump, toSQLLine } from '@/utils/stringFun'
-import { createTemp, getDB, getTable, getColumn, preview } from '@/api/autoCode'
+import { createTemp, getDB, getTable, getColumn, preview, getMeta } from '@/api/autoCode'
 import { getDict } from '@/utils/dictionary'
 
 export default {
@@ -223,6 +223,10 @@ export default {
   created() {
     this.getDb()
     this.setFdMap()
+    const id = this.$route.params.id
+    if (id) {
+      this.getAutoCodeJson(id)
+    }
   },
   methods: {
     editAndAddField(item) {
@@ -305,7 +309,6 @@ export default {
             return false
           }
           this.form.humpPackageName = toSQLLine(this.form.packageName)
-          debugger
           if (isPreview) {
             const data = await preview(this.form)
             this.preViewCode = data.data.autoCode
@@ -396,6 +399,12 @@ export default {
           this.fdMap[item.label] = fdtype
         })
       })
+    },
+    async getAutoCodeJson(id) {
+      const res = await getMeta({ id: Number(id) })
+      if (res.code === 0) {
+        this.form = JSON.parse(res.data.meta)
+      }
     }
   }
 }

+ 146 - 0
web/src/view/systemTools/autoCodeAdmin/index.vue

@@ -0,0 +1,146 @@
+<template>
+  <div>
+    <div class="search-term">
+      <el-form :inline="true" :model="searchInfo" class="demo-form-inline">
+        <el-form-item label="表名">
+          <el-input v-model="searchInfo.tableName" placeholder="表名" />
+        </el-form-item>
+        <el-form-item label="结构体名称">
+          <el-input v-model="searchInfo.structName" placeholder="结构体名称" />
+        </el-form-item>
+        <el-form-item>
+          <el-button size="mini" type="primary" icon="el-icon-plus" @click="goAutoCode(null)">新增</el-button>
+        </el-form-item>
+      </el-form>
+    </div>
+    <el-table :data="tableData" border stripe>
+      <el-table-column
+        type="selection"
+        width="55"
+      />
+      <el-table-column label="id" width="60" prop="ID" />
+      <el-table-column label="日期" width="180">
+        <template slot-scope="scope">{{ scope.row.CreatedAt|formatDate }}</template>
+      </el-table-column>
+      <el-table-column label="结构体名" min-width="150" prop="structName" />
+      <el-table-column label="结构体描述" min-width="150" prop="structCNName" />
+      <el-table-column label="表名称" min-width="150" prop="tableName" />
+      <el-table-column label="回滚标记" min-width="150" prop="flag">
+        <template slot-scope="scope">
+          <el-tag
+            v-if="scope.row.flag"
+            type="danger"
+            size="mini"
+            effect="dark"
+          >
+            已回滚
+          </el-tag>
+          <el-tag
+            v-else
+            size="mini"
+            type="success"
+            effect="dark"
+          >
+            未回滚
+          </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" min-width="180">
+        <template slot-scope="scope">
+          <div>
+            <el-button size="mini" type="primary" @click="rollback(scope.row)">回滚</el-button>
+            <el-button size="mini" type="success" @click="goAutoCode(scope.row)">复用</el-button>
+            <el-button size="mini" type="warning" @click="deleteRow(scope.row)">删除</el-button>
+          </div>
+        </template>
+      </el-table-column>
+    </el-table>
+    <el-pagination
+      :current-page="page"
+      :page-size="pageSize"
+      :page-sizes="[10, 30, 50, 100]"
+      :style="{float:'right',padding:'20px'}"
+      :total="total"
+      layout="total, sizes, prev, pager, next, jumper"
+      @current-change="handleCurrentChange"
+      @size-change="handleSizeChange"
+    />
+
+  </div>
+</template>
+
+<script>
+// 获取列表内容封装在mixins内部  getTableData方法 初始化已封装完成 条件搜索时候 请把条件安好后台定制的结构体字段 放到 this.searchInfo 中即可实现条件搜索
+import { getSysHistory, rollback } from '@/api/autoCode.js'
+import { formatTimeToStr } from '@/utils/date'
+import infoList from '@/mixins/infoList'
+
+export default {
+  name: 'Api',
+  filters: {
+    formatDate: function(time) {
+      if (time !== null && time !== '') {
+        var date = new Date(time)
+        return formatTimeToStr(date, 'yyyy-MM-dd hh:mm:ss')
+      } else {
+        return ''
+      }
+    },
+    formatBoolean: function(bool) {
+      if (bool !== null) {
+        return bool ? '是' : '否'
+      } else {
+        return ''
+      }
+    }
+  },
+  mixins: [infoList],
+  data() {
+    return {
+      listApi: getSysHistory
+    }
+  },
+  created() {
+    this.getTableData()
+  },
+  methods: {
+    async rollback(row) {
+      this.$confirm('此操作将删除自动创建的文件和api, 是否继续?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(async() => {
+        const res = await rollback({ id: Number(row.ID) })
+        if (res.code === 0) {
+          this.$message.success('回滚成功')
+          this.getTableData()
+        }
+      })
+    },
+    goAutoCode(row) {
+      if (row) {
+        this.$router.push({ name: 'autoCodeEdit', params: {
+          id: row.ID
+        }})
+      } else {
+        this.$router.push({ name: 'autoCode' })
+      }
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.button-box {
+  padding: 10px 20px;
+  .el-button {
+    float: right;
+  }
+}
+.el-tag--mini {
+  margin-left: 5px;
+}
+.warning {
+  color: #dc143c;
+}
+</style>