From 2a1235a9a6ae424ad86abe72f94b5eef4d2982de Mon Sep 17 00:00:00 2001 From: LeonardWang Date: Fri, 19 Mar 2021 23:24:55 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BB=A3=E7=A0=81=E7=94=9F?= =?UTF-8?q?=E6=88=90=E4=B8=ADroute=E5=92=8Cgorm=E7=9A=84=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E6=B3=A8=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/config.yaml | 16 ++++ server/config/auto_code.go | 25 ++++--- server/config/config.go | 2 +- server/initialize/gorm.go | 3 + server/initialize/router.go | 3 + server/service/sys_auto_code.go | 12 +++ server/service/sys_initdb.go | 2 + server/utils/injectionCode.go | 128 ++++++++++++++++++++++++++++++++ 8 files changed, 178 insertions(+), 13 deletions(-) create mode 100644 server/utils/injectionCode.go diff --git a/server/config.yaml b/server/config.yaml index 7b4d25ac..d57d6963 100644 --- a/server/config.yaml +++ b/server/config.yaml @@ -68,6 +68,22 @@ mysql: local: path: 'uploads/file' +# autocode configuration +autocode: + root: "" + server: /server + server-api: /api/v1 + server-initialize: /initialize + server-model: /model + server-request: /model/request/ + server-router: /router + server-service: /service + web: /web/src + web-api: /api + web-flow: /view + web-form: /view + web-table: /view + # qiniu configuration (请自行七牛申请对应的 公钥 私钥 bucket �?域名地址) qiniu: zone: 'ZoneHuadong' diff --git a/server/config/auto_code.go b/server/config/auto_code.go index 9c230f9e..18cd32f6 100644 --- a/server/config/auto_code.go +++ b/server/config/auto_code.go @@ -1,16 +1,17 @@ package config type Autocode struct { - Root string `mapstructure:"root" json:"root" yaml:"root"` - Server string `mapstructure:"server" json:"server" yaml:"server"` - SApi string `mapstructure:"server-api" json:"serverApi" yaml:"server-api"` - SModel string `mapstructure:"server-model" json:"serverModel" yaml:"server-model"` - SRequest string `mapstructure:"server-request" json:"serverRequest" yaml:"server-request"` - SRouter string `mapstructure:"server-router" json:"serverRouter" yaml:"server-router"` - SService string `mapstructure:"server-service" json:"serverService" yaml:"server-service"` - Web string `mapstructure:"web" json:"web" yaml:"web"` - WApi string `mapstructure:"web-api" json:"webApi" yaml:"web-api"` - WForm string `mapstructure:"web-form" json:"webForm" yaml:"web-form"` - WTable string `mapstructure:"web-table" json:"webTable" yaml:"web-table"` - WFlow string `mapstructure:"web-flow" json:"webFlow" yaml:"web-flow"` + Root string `mapstructure:"root" json:"root" yaml:"root"` + Server string `mapstructure:"server" json:"server" yaml:"server"` + SApi string `mapstructure:"server-api" json:"serverApi" yaml:"server-api"` + SInitialize string `mapstructure:"server-initialize" json:"serverInitialize" yaml:"server-initialize"` + SModel string `mapstructure:"server-model" json:"serverModel" yaml:"server-model"` + SRequest string `mapstructure:"server-request" json:"serverRequest" yaml:"server-request"` + SRouter string `mapstructure:"server-router" json:"serverRouter" yaml:"server-router"` + SService string `mapstructure:"server-service" json:"serverService" yaml:"server-service"` + Web string `mapstructure:"web" json:"web" yaml:"web"` + WApi string `mapstructure:"web-api" json:"webApi" yaml:"web-api"` + WForm string `mapstructure:"web-form" json:"webForm" yaml:"web-form"` + WTable string `mapstructure:"web-table" json:"webTable" yaml:"web-table"` + WFlow string `mapstructure:"web-flow" json:"webFlow" yaml:"web-flow"` } diff --git a/server/config/config.go b/server/config/config.go index ab88c827..e2e557de 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -8,7 +8,7 @@ type Server struct { Casbin Casbin `mapstructure:"casbin" json:"casbin" yaml:"casbin"` System System `mapstructure:"system" json:"system" yaml:"system"` Captcha Captcha `mapstructure:"captcha" json:"captcha" yaml:"captcha"` - // aoto + // auto AutoCode Autocode `mapstructure:"autoCode" json:"autoCode" yaml:"autoCode"` // gorm Mysql Mysql `mapstructure:"mysql" json:"mysql" yaml:"mysql"` diff --git a/server/initialize/gorm.go b/server/initialize/gorm.go index 1b6065c9..7f726352 100644 --- a/server/initialize/gorm.go +++ b/server/initialize/gorm.go @@ -48,6 +48,9 @@ func MysqlTables(db *gorm.DB) { model.ExaSimpleUploader{}, model.ExaCustomer{}, model.SysOperationRecord{}, + + // Code generated by gin-vue-admin Begin; DO NOT EDIT. + // Code generated by gin-vue-admin End; DO NOT EDIT. ) if err != nil { global.GVA_LOG.Error("register table failed", zap.Any("err", err)) diff --git a/server/initialize/router.go b/server/initialize/router.go index d002e9ab..0744d258 100644 --- a/server/initialize/router.go +++ b/server/initialize/router.go @@ -49,6 +49,9 @@ func Routers() *gin.Engine { router.InitSysDictionaryDetailRouter(PrivateGroup) // 字典详情管理 router.InitFileUploadAndDownloadRouter(PrivateGroup) // 文件上传下载功能路由 router.InitExcelRouter(PrivateGroup) // 表格导入导出 + + // Code generated by gin-vue-admin Begin; DO NOT EDIT. + // Code generated by gin-vue-admin End; DO NOT EDIT. } global.GVA_LOG.Info("router register success") return Router diff --git a/server/service/sys_auto_code.go b/server/service/sys_auto_code.go index 4fcb54f3..f8def744 100644 --- a/server/service/sys_auto_code.go +++ b/server/service/sys_auto_code.go @@ -134,6 +134,18 @@ func CreateTemp(autoCode model.AutoCodeStruct) (err error) { return err } } + initializeGormFilePath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, + global.GVA_CONFIG.AutoCode.Server, global.GVA_CONFIG.AutoCode.SInitialize, "gorm.go") + initializeRouterFilePath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, + global.GVA_CONFIG.AutoCode.Server, global.GVA_CONFIG.AutoCode.SInitialize, "router.go") + err = utils.AutoInjectionCode(initializeGormFilePath, "MysqlTables", "model."+autoCode.StructName+"{},") + if err != nil { + return err + } + err = utils.AutoInjectionCode(initializeRouterFilePath, "Routers", "router.Init"+autoCode.StructName+"Router(PrivateGroup)") + if err != nil { + return err + } return errors.New("创建代码成功并移动文件成功") } else { // 打包 if err := utils.ZipFiles("./ginvueadmin.zip", fileList, ".", "."); err != nil { diff --git a/server/service/sys_initdb.go b/server/service/sys_initdb.go index 76132fda..2f90cb90 100644 --- a/server/service/sys_initdb.go +++ b/server/service/sys_initdb.go @@ -10,6 +10,7 @@ import ( "github.com/spf13/viper" "gorm.io/driver/mysql" "gorm.io/gorm" + "path/filepath" ) //@author: [songzhibin97](https://github.com/songzhibin97) @@ -154,5 +155,6 @@ func InitDB(conf request.InitDB) error { _ = writeConfig(global.GVA_VP, baseSetting) return err } + global.GVA_CONFIG.AutoCode.Root, _ = filepath.Abs("..") return nil } diff --git a/server/utils/injectionCode.go b/server/utils/injectionCode.go new file mode 100644 index 00000000..a9f2ba5a --- /dev/null +++ b/server/utils/injectionCode.go @@ -0,0 +1,128 @@ +package utils + +import ( + "fmt" + "go/ast" + "go/parser" + "go/token" + "io/ioutil" + "strings" +) + +//@author: [LeonardWang](https://github.com/WangLeonard) +//@function: AutoInjectionCode +//@description: 向文件中固定注释位置写入代码 +//@param: filepath string, funcName string, codeData string +//@return: err 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 + } + srcDataLen := len(srcData) + fset := token.NewFileSet() + fparser, err := parser.ParseFile(fset, filepath, srcData, parser.ParseComments) + if err != nil { + return err + } + codeData = strings.TrimSpace(codeData) + var codeStartPos = -1 + var codeEndPos = srcDataLen + var expectedFunction *ast.FuncDecl + + var startCommentPos = -1 + var endCommentPos = srcDataLen + + // 如果指定了函数名,先寻找对应函数 + if funcName != "" { + for _, decl := range fparser.Decls { + if funDecl, ok := decl.(*ast.FuncDecl); ok && funDecl.Name.Name == funcName { + expectedFunction = funDecl + codeStartPos = int(funDecl.Body.Lbrace) + codeEndPos = int(funDecl.Body.Rbrace) + break + } + } + } + + // 遍历所有注释 + for _, comment := range fparser.Comments { + if int(comment.Pos()) > codeStartPos && int(comment.End()) <= codeEndPos { + if startComment != "" && strings.Contains(comment.Text(), startComment) { + startCommentPos = int(comment.Pos()) // Note: Pos is the second '/' + } + if endComment != "" && strings.Contains(comment.Text(), endComment) { + endCommentPos = int(comment.Pos()) // Note: Pos is the second '/' + } + } + } + + if endCommentPos == srcDataLen { + return fmt.Errorf("comment:%s not found", endComment) + } + + // 在指定函数名,且函数中startComment和endComment都存在时,进行区间查重 + if (codeStartPos != -1 && codeEndPos != srcDataLen) && (startCommentPos != -1 && endCommentPos != srcDataLen) && expectedFunction != nil { + if exist := checkExist(&srcData, startCommentPos, endCommentPos, expectedFunction.Body, codeData); exist { + fmt.Println("已存在") + return nil // 这里不需要返回错误? + } + } + + // 两行注释中间没有换行时,会被认为是一条Comment + if startCommentPos == endCommentPos { + endCommentPos = startCommentPos + strings.Index(string(srcData[startCommentPos:]), endComment) + for srcData[endCommentPos] != '/' { + endCommentPos-- + } + } + + // 记录"//"之前的空字符,保持写入后的格式一致 + tmpSpace := make([]byte, 0, 10) + for tmp := endCommentPos - 2; tmp >= 0; tmp-- { + if srcData[tmp] != '\n' { + tmpSpace = append(tmpSpace, srcData[tmp]) + } else { + break + } + } + + reverseSpace := make([]byte, 0, len(tmpSpace)) + for index := len(tmpSpace) - 1; index >= 0; index-- { + reverseSpace = append(reverseSpace, tmpSpace[index]) + } + + // 插入数据 + indexPos := endCommentPos - 1 + insertData := []byte(append([]byte(codeData+"\n"), reverseSpace...)) + + remainData := append([]byte{}, srcData[indexPos:]...) + srcData = append(append(srcData[:indexPos], insertData...), remainData...) + + // 写回数据 + return ioutil.WriteFile(filepath, srcData, 0600) +} + +func checkExist(srcData *[]byte, startPos int, endPos int, blockStmt *ast.BlockStmt, target string) bool { + for _, list := range blockStmt.List { + switch stmt := list.(type) { + case *ast.ExprStmt: + if callExpr, ok := stmt.X.(*ast.CallExpr); ok && + int(callExpr.Pos()) > startPos && int(callExpr.End()) < endPos { + text := string((*srcData)[int(callExpr.Pos()-1):int(callExpr.End())]) + key := strings.TrimSpace(text) + if key == target { + return true + } + } + case *ast.BlockStmt: + if checkExist(srcData, startPos, endPos, stmt, target) { + return true + } + } + } + return false +}