重构自动化代码,修复皮肤bug (#1355)
* 增加读取ast方法和AddImport 方法来增加引用包功能 * 增加在指定方法中添加头部声明global.DB业务库的方法 * 增加了自动添加AutoMigrate方法。 * 将自动化产生自动迁移功能替换为新ast * 增加了自动注册router块的功能 * 废除旧的对router和gorm的处理方法,替换为新的自动化方法 * 修复开发模式下皮肤失效的问题
This commit is contained in:
47
server/utils/ast/ast.go
Normal file
47
server/utils/ast/ast.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
)
|
||||
|
||||
// 增加 import 方法
|
||||
func AddImport(astNode ast.Node, imp string) {
|
||||
impStr := fmt.Sprintf("\"%s\"", imp)
|
||||
ast.Inspect(astNode, func(node ast.Node) bool {
|
||||
if genDecl, ok := node.(*ast.GenDecl); ok {
|
||||
if genDecl.Tok == token.IMPORT {
|
||||
for i := range genDecl.Specs {
|
||||
if impNode, ok := genDecl.Specs[i].(*ast.ImportSpec); ok {
|
||||
if impNode.Path.Value == impStr {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
genDecl.Specs = append(genDecl.Specs, &ast.ImportSpec{
|
||||
Path: &ast.BasicLit{
|
||||
Kind: token.STRING,
|
||||
Value: impStr,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// 查询特定function方法
|
||||
func FindFunction(astNode ast.Node, FunctionName string) *ast.FuncDecl {
|
||||
var funcDeclP *ast.FuncDecl
|
||||
ast.Inspect(astNode, func(node ast.Node) bool {
|
||||
if funcDecl, ok := node.(*ast.FuncDecl); ok {
|
||||
if funcDecl.Name.String() == FunctionName {
|
||||
funcDeclP = funcDecl
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
return funcDeclP
|
||||
}
|
47
server/utils/ast/ast_auto_enter.go
Normal file
47
server/utils/ast/ast_auto_enter.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/printer"
|
||||
"go/token"
|
||||
"os"
|
||||
)
|
||||
|
||||
func ImportForAutoEnter(path string, funcName string, code string) {
|
||||
src, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
fileSet := token.NewFileSet()
|
||||
astFile, err := parser.ParseFile(fileSet, "", src, 0)
|
||||
ast.Inspect(astFile, func(node ast.Node) bool {
|
||||
if typeSpec, ok := node.(*ast.TypeSpec); ok {
|
||||
if typeSpec.Name.Name == funcName {
|
||||
if st, ok := typeSpec.Type.(*ast.StructType); ok {
|
||||
for i := range st.Fields.List {
|
||||
if t, ok := st.Fields.List[i].Type.(*ast.Ident); ok {
|
||||
if t.Name == code {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
sn := &ast.Field{
|
||||
Type: &ast.Ident{Name: code},
|
||||
}
|
||||
st.Fields.List = append(st.Fields.List, sn)
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
var out []byte
|
||||
bf := bytes.NewBuffer(out)
|
||||
err = printer.Fprint(bf, fileSet, astFile)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_ = os.WriteFile(path, bf.Bytes(), 0666)
|
||||
}
|
7
server/utils/ast/ast_auto_enter_test.go
Normal file
7
server/utils/ast/ast_auto_enter_test.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package ast
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestImportForAutoEnter(t *testing.T) {
|
||||
ImportForAutoEnter("D:\\gin-vue-admin\\server\\api\\v1\\test\\enter.go", "ApiGroup", "test")
|
||||
}
|
181
server/utils/ast/ast_enter.go
Normal file
181
server/utils/ast/ast_enter.go
Normal file
@@ -0,0 +1,181 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"go/ast"
|
||||
"go/format"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Visitor struct {
|
||||
ImportCode string
|
||||
StructName string
|
||||
PackageName string
|
||||
GroupName string
|
||||
}
|
||||
|
||||
func (vi *Visitor) Visit(node ast.Node) ast.Visitor {
|
||||
switch n := node.(type) {
|
||||
case *ast.GenDecl:
|
||||
// 查找有没有import context包
|
||||
// Notice:没有考虑没有import任何包的情况
|
||||
if n.Tok == token.IMPORT && vi.ImportCode != "" {
|
||||
vi.addImport(n)
|
||||
// 不需要再遍历子树
|
||||
return nil
|
||||
}
|
||||
if n.Tok == token.TYPE && vi.StructName != "" && vi.PackageName != "" && vi.GroupName != "" {
|
||||
vi.addStruct(n)
|
||||
return nil
|
||||
}
|
||||
case *ast.FuncDecl:
|
||||
if n.Name.Name == "Routers" {
|
||||
vi.addFuncBodyVar(n)
|
||||
return nil
|
||||
}
|
||||
|
||||
}
|
||||
return vi
|
||||
}
|
||||
|
||||
func (vi *Visitor) addStruct(genDecl *ast.GenDecl) ast.Visitor {
|
||||
for i := range genDecl.Specs {
|
||||
switch n := genDecl.Specs[i].(type) {
|
||||
case *ast.TypeSpec:
|
||||
if strings.Index(n.Name.Name, "Group") > -1 {
|
||||
switch t := n.Type.(type) {
|
||||
case *ast.StructType:
|
||||
f := &ast.Field{
|
||||
Names: []*ast.Ident{
|
||||
{
|
||||
Name: vi.StructName,
|
||||
Obj: &ast.Object{
|
||||
Kind: ast.Var,
|
||||
Name: vi.StructName,
|
||||
},
|
||||
},
|
||||
},
|
||||
Type: &ast.SelectorExpr{
|
||||
X: &ast.Ident{
|
||||
Name: vi.PackageName,
|
||||
},
|
||||
Sel: &ast.Ident{
|
||||
Name: vi.GroupName,
|
||||
},
|
||||
},
|
||||
}
|
||||
t.Fields.List = append(t.Fields.List, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return vi
|
||||
}
|
||||
|
||||
func (vi *Visitor) addImport(genDecl *ast.GenDecl) ast.Visitor {
|
||||
// 是否已经import
|
||||
hasImported := false
|
||||
for _, v := range genDecl.Specs {
|
||||
importSpec := v.(*ast.ImportSpec)
|
||||
// 如果已经包含
|
||||
if importSpec.Path.Value == strconv.Quote(vi.ImportCode) {
|
||||
hasImported = true
|
||||
}
|
||||
}
|
||||
if !hasImported {
|
||||
genDecl.Specs = append(genDecl.Specs, &ast.ImportSpec{
|
||||
Path: &ast.BasicLit{
|
||||
Kind: token.STRING,
|
||||
Value: strconv.Quote(vi.ImportCode),
|
||||
},
|
||||
})
|
||||
}
|
||||
return vi
|
||||
}
|
||||
|
||||
func (vi *Visitor) addFuncBodyVar(funDecl *ast.FuncDecl) ast.Visitor {
|
||||
hasVar := false
|
||||
for _, v := range funDecl.Body.List {
|
||||
switch varSpec := v.(type) {
|
||||
case *ast.AssignStmt:
|
||||
for i := range varSpec.Lhs {
|
||||
switch nn := varSpec.Lhs[i].(type) {
|
||||
case *ast.Ident:
|
||||
if nn.Name == vi.PackageName+"Router" {
|
||||
hasVar = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasVar {
|
||||
assignStmt := &ast.AssignStmt{
|
||||
Lhs: []ast.Expr{
|
||||
&ast.Ident{
|
||||
Name: vi.PackageName + "Router",
|
||||
Obj: &ast.Object{
|
||||
Kind: ast.Var,
|
||||
Name: vi.PackageName + "Router",
|
||||
},
|
||||
},
|
||||
},
|
||||
Tok: token.DEFINE,
|
||||
Rhs: []ast.Expr{
|
||||
&ast.SelectorExpr{
|
||||
X: &ast.SelectorExpr{
|
||||
X: &ast.Ident{
|
||||
Name: "router",
|
||||
},
|
||||
Sel: &ast.Ident{
|
||||
Name: "RouterGroupApp",
|
||||
},
|
||||
},
|
||||
Sel: &ast.Ident{
|
||||
Name: cases.Title(language.English).String(vi.PackageName),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
funDecl.Body.List = append(funDecl.Body.List, funDecl.Body.List[1])
|
||||
index := 1
|
||||
copy(funDecl.Body.List[index+1:], funDecl.Body.List[index:])
|
||||
funDecl.Body.List[index] = assignStmt
|
||||
}
|
||||
return vi
|
||||
}
|
||||
|
||||
func ImportReference(filepath, importCode, structName, packageName, groupName string) error {
|
||||
fSet := token.NewFileSet()
|
||||
fParser, err := parser.ParseFile(fSet, filepath, nil, parser.ParseComments)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
importCode = strings.TrimSpace(importCode)
|
||||
v := &Visitor{
|
||||
ImportCode: importCode,
|
||||
StructName: structName,
|
||||
PackageName: packageName,
|
||||
GroupName: groupName,
|
||||
}
|
||||
if importCode == "" {
|
||||
ast.Print(fSet, fParser)
|
||||
}
|
||||
|
||||
ast.Walk(v, fParser)
|
||||
|
||||
var output []byte
|
||||
buffer := bytes.NewBuffer(output)
|
||||
err = format.Node(buffer, fSet, fParser)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// 写回数据
|
||||
return os.WriteFile(filepath, buffer.Bytes(), 0o600)
|
||||
}
|
165
server/utils/ast/ast_gorm.go
Normal file
165
server/utils/ast/ast_gorm.go
Normal file
@@ -0,0 +1,165 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/printer"
|
||||
"go/token"
|
||||
"os"
|
||||
)
|
||||
|
||||
// 自动为 gorm.go 注册一个自动迁移
|
||||
func AddRegisterTablesAst(path, funcName, pk, dbName, model string) {
|
||||
modelPk := fmt.Sprintf("github.com/flipped-aurora/gin-vue-admin/server/model/%s", pk)
|
||||
src, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
fileSet := token.NewFileSet()
|
||||
astFile, err := parser.ParseFile(fileSet, "", src, 0)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
AddImport(astFile, modelPk)
|
||||
FuncNode := FindFunction(astFile, funcName)
|
||||
if FuncNode != nil {
|
||||
ast.Print(fileSet, FuncNode)
|
||||
}
|
||||
addDBVar(FuncNode.Body, dbName)
|
||||
addAutoMigrate(FuncNode.Body, dbName, pk, model)
|
||||
var out []byte
|
||||
bf := bytes.NewBuffer(out)
|
||||
printer.Fprint(bf, fileSet, astFile)
|
||||
|
||||
os.WriteFile(path, bf.Bytes(), 0666)
|
||||
}
|
||||
|
||||
// 增加一个 db库变量
|
||||
func addDBVar(astBody *ast.BlockStmt, dbName string) {
|
||||
if dbName == "" {
|
||||
return
|
||||
}
|
||||
dbStr := fmt.Sprintf("\"%s\"", dbName)
|
||||
for i := range astBody.List {
|
||||
if assignStmt, ok := astBody.List[i].(*ast.AssignStmt); ok {
|
||||
if ident, ok := assignStmt.Lhs[0].(*ast.Ident); ok {
|
||||
if ident.Name == dbName {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
assignNode := &ast.AssignStmt{
|
||||
Lhs: []ast.Expr{
|
||||
&ast.Ident{
|
||||
Name: dbName,
|
||||
},
|
||||
},
|
||||
Tok: token.DEFINE,
|
||||
Rhs: []ast.Expr{
|
||||
&ast.CallExpr{
|
||||
Fun: &ast.SelectorExpr{
|
||||
X: &ast.Ident{
|
||||
Name: "global",
|
||||
},
|
||||
Sel: &ast.Ident{
|
||||
Name: "GetGlobalDBByDBName",
|
||||
},
|
||||
},
|
||||
Args: []ast.Expr{
|
||||
&ast.BasicLit{
|
||||
Kind: token.STRING,
|
||||
Value: dbStr,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
astBody.List = append([]ast.Stmt{assignNode}, astBody.List...)
|
||||
}
|
||||
|
||||
// 为db库变量增加 AutoMigrate 方法
|
||||
func addAutoMigrate(astBody *ast.BlockStmt, dbname string, pk string, model string) {
|
||||
if dbname == "" {
|
||||
dbname = "db"
|
||||
}
|
||||
flag := true
|
||||
ast.Inspect(astBody, func(node ast.Node) bool {
|
||||
// 首先判断需要加入的方法调用语句是否存在 不存在则直接走到下方逻辑
|
||||
switch n := node.(type) {
|
||||
case *ast.CallExpr:
|
||||
// 判断是否找到了AutoMigrate语句
|
||||
if s, ok := n.Fun.(*ast.SelectorExpr); ok {
|
||||
if x, ok := s.X.(*ast.Ident); ok {
|
||||
if s.Sel.Name == "AutoMigrate" && x.Name == dbname {
|
||||
flag = false
|
||||
if !NeedAppendModel(n, pk, model) {
|
||||
return false
|
||||
}
|
||||
// 判断已经找到了AutoMigrate语句
|
||||
n.Args = append(n.Args, &ast.CompositeLit{
|
||||
Type: &ast.SelectorExpr{
|
||||
X: &ast.Ident{
|
||||
Name: pk,
|
||||
},
|
||||
Sel: &ast.Ident{
|
||||
Name: model,
|
||||
},
|
||||
},
|
||||
})
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
//然后判断 pk.model是否存在 如果存在直接跳出 如果不存在 则向已经找到的方法调用语句的node里面push一条
|
||||
})
|
||||
|
||||
if flag {
|
||||
exprStmt := &ast.ExprStmt{
|
||||
X: &ast.CallExpr{
|
||||
Fun: &ast.SelectorExpr{
|
||||
X: &ast.Ident{
|
||||
Name: dbname,
|
||||
},
|
||||
Sel: &ast.Ident{
|
||||
Name: "AutoMigrate",
|
||||
},
|
||||
},
|
||||
Args: []ast.Expr{
|
||||
&ast.CompositeLit{
|
||||
Type: &ast.SelectorExpr{
|
||||
X: &ast.Ident{
|
||||
Name: pk,
|
||||
},
|
||||
Sel: &ast.Ident{
|
||||
Name: model,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
astBody.List = append(astBody.List, exprStmt)
|
||||
}
|
||||
}
|
||||
|
||||
// 为automigrate增加实参
|
||||
func NeedAppendModel(callNode ast.Node, pk string, model string) bool {
|
||||
flag := true
|
||||
ast.Inspect(callNode, func(node ast.Node) bool {
|
||||
switch n := node.(type) {
|
||||
case *ast.SelectorExpr:
|
||||
if x, ok := n.X.(*ast.Ident); ok {
|
||||
if n.Sel.Name == model && x.Name == pk {
|
||||
flag = false
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
return flag
|
||||
}
|
18
server/utils/ast/ast_gorm_test.go
Normal file
18
server/utils/ast/ast_gorm_test.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/example"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const A = 123
|
||||
|
||||
func TestAddRegisterTablesAst(t *testing.T) {
|
||||
AddRegisterTablesAst("D:\\gin-vue-admin\\server\\utils\\ast_test.go", "Register", "test", "testDB", "testModel")
|
||||
}
|
||||
|
||||
func Register() {
|
||||
test := global.GetGlobalDBByDBName("test")
|
||||
test.AutoMigrate(example.ExaFile{})
|
||||
}
|
132
server/utils/ast/ast_router.go
Normal file
132
server/utils/ast/ast_router.go
Normal file
@@ -0,0 +1,132 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/printer"
|
||||
"go/token"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func AppendNodeToList(stmts []ast.Stmt, stmt ast.Stmt, index int) []ast.Stmt {
|
||||
return append(stmts[:index], append([]ast.Stmt{stmt}, stmts[index:]...)...)
|
||||
}
|
||||
|
||||
func AddRouterCode(path, funcName, pk, model string) {
|
||||
src, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
fileSet := token.NewFileSet()
|
||||
astFile, err := parser.ParseFile(fileSet, "", src, parser.ParseComments)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
FuncNode := FindFunction(astFile, funcName)
|
||||
|
||||
pkName := strings.ToUpper(pk[:1]) + pk[1:]
|
||||
routerName := fmt.Sprintf("%sRouter", pk)
|
||||
modelName := fmt.Sprintf("Init%sRouter", model)
|
||||
var bloctPre *ast.BlockStmt
|
||||
for i := len(FuncNode.Body.List) - 1; i >= 0; i-- {
|
||||
if block, ok := FuncNode.Body.List[i].(*ast.BlockStmt); ok {
|
||||
bloctPre = block
|
||||
}
|
||||
}
|
||||
ast.Print(fileSet, FuncNode)
|
||||
if ok, b := needAppendRouter(FuncNode, pk); ok {
|
||||
routerNode :=
|
||||
&ast.BlockStmt{
|
||||
List: []ast.Stmt{
|
||||
&ast.AssignStmt{
|
||||
Lhs: []ast.Expr{
|
||||
&ast.Ident{Name: routerName},
|
||||
},
|
||||
Tok: token.DEFINE,
|
||||
Rhs: []ast.Expr{
|
||||
&ast.SelectorExpr{
|
||||
X: &ast.SelectorExpr{
|
||||
X: &ast.Ident{Name: "router"},
|
||||
Sel: &ast.Ident{Name: "RouterGroupApp"},
|
||||
},
|
||||
Sel: &ast.Ident{Name: pkName},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
FuncNode.Body.List = AppendNodeToList(FuncNode.Body.List, routerNode, len(FuncNode.Body.List)-2)
|
||||
bloctPre = routerNode
|
||||
} else {
|
||||
bloctPre = b
|
||||
}
|
||||
|
||||
if needAppendInit(FuncNode, routerName, modelName) {
|
||||
bloctPre.List = append(bloctPre.List,
|
||||
&ast.ExprStmt{
|
||||
X: &ast.CallExpr{
|
||||
Fun: &ast.SelectorExpr{
|
||||
X: &ast.Ident{Name: routerName},
|
||||
Sel: &ast.Ident{Name: modelName},
|
||||
},
|
||||
Args: []ast.Expr{
|
||||
&ast.Ident{
|
||||
Name: "PrivateGroup",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
var out []byte
|
||||
bf := bytes.NewBuffer(out)
|
||||
printer.Fprint(bf, fileSet, astFile)
|
||||
os.WriteFile(path, bf.Bytes(), 0666)
|
||||
}
|
||||
|
||||
func needAppendRouter(funcNode ast.Node, pk string) (bool, *ast.BlockStmt) {
|
||||
flag := true
|
||||
var block *ast.BlockStmt
|
||||
ast.Inspect(funcNode, func(node ast.Node) bool {
|
||||
switch n := node.(type) {
|
||||
case *ast.BlockStmt:
|
||||
for i := range n.List {
|
||||
if assignNode, ok := n.List[i].(*ast.AssignStmt); ok {
|
||||
if identNode, ok := assignNode.Lhs[0].(*ast.Ident); ok {
|
||||
if identNode.Name == fmt.Sprintf("%sRouter", pk) {
|
||||
flag = false
|
||||
block = n
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return true
|
||||
})
|
||||
return flag, block
|
||||
}
|
||||
|
||||
func needAppendInit(funcNode ast.Node, routerName string, modelName string) bool {
|
||||
flag := true
|
||||
ast.Inspect(funcNode, func(node ast.Node) bool {
|
||||
switch n := funcNode.(type) {
|
||||
case *ast.CallExpr:
|
||||
if selectNode, ok := n.Fun.(*ast.SelectorExpr); ok {
|
||||
x, xok := selectNode.X.(*ast.Ident)
|
||||
if xok && x.Name == routerName && selectNode.Sel.Name == modelName {
|
||||
flag = false
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
return flag
|
||||
}
|
9
server/utils/ast/ast_router_test.go
Normal file
9
server/utils/ast/ast_router_test.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAddRouterCode(t *testing.T) {
|
||||
AddRouterCode("D:\\gin-vue-admin\\server\\utils\\ast\\ast_router_test.go", "Routers", "testRouter", "GVAStruct")
|
||||
}
|
Reference in New Issue
Block a user