1 Star 1 Fork 0

fenquen/go_aop

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
main.go 19.78 KB
一键复制 编辑 原始数据 按行查看 历史
fenquen 提交于 2023-11-19 21:20 +08:00 . 把不需要的copy到对应的目录
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690
package main
import (
"bufio"
"encoding/json"
"flag"
"fmt"
"gitee.com/fenquen/go_aop/utils"
"go/ast"
"go/parser"
"go/printer"
"go/token"
"io"
"log"
"os"
"path"
"strconv"
"strings"
"sync"
)
const empty = ""
const dot = "."
var moduleName = empty
var rootDirPackageName = empty
var config = &Config{}
var waitGroup = sync.WaitGroup{}
var mutexes = []*sync.Mutex{{}, {}, {}}
var rootDirPath string
func main() {
readConfigFromFile()
rootDirPath = os.Args[1]
// rootDir对应的目录因该是1个全量的go项目 读取对应目录的go.mod
moduleName = getModuleName(path.Join(rootDirPath, "go.mod"))
generateDirPath := path.Join(rootDirPath, "generate")
_ = os.RemoveAll(generateDirPath)
change(rootDirPath)
waitGroup.Wait()
}
func change(currentDirPath string) {
dirEntries, err := os.ReadDir(currentDirPath)
if err != nil {
panic(err)
}
for _, dirEntry := range dirEntries {
noNeedHandle := false
// 如果是目录的话下钻递归
if dirEntry.IsDir() {
change(path.Join(currentDirPath, dirEntry.Name()))
continue
}
// 如不是go文件不去handle
if !strings.HasSuffix(dirEntry.Name(), ".go") {
noNeedHandle = true
}
// test不要
if strings.Contains(dirEntry.Name(), "_test.go") {
noNeedHandle = true
}
// 要是用不了处理的话 把它copy到对应的generate体系目录
if noNeedHandle {
originalFile, err := os.Open(path.Join(currentDirPath, dirEntry.Name()))
if err != nil {
panic(err)
}
generatedFileParentDirPath := generateFileParentDirPath(currentDirPath)
generatedFile, err := os.OpenFile(path.Join(generatedFileParentDirPath, dirEntry.Name()), os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
panic(err)
}
_, err = io.Copy(generatedFile, originalFile)
if err != nil {
panic(err)
}
continue
}
go func(dirEntry os.DirEntry) {
waitGroup.Add(1)
defer waitGroup.Done()
originalGoFileName := dirEntry.Name()
originalGoFilePath := path.Join(currentDirPath, originalGoFileName)
fileSet := token.NewFileSet()
astFile, err := parser.ParseFile(fileSet, originalGoFilePath, nil, parser.ParseComments)
if err != nil {
panic(err)
}
// 如何知道项目的根目录的对应的package名字是moduleName还是main 需要读取根目录下的随便1个go文件 然后读取它的package
// 说明当前是根目录 且 尚未得到rootDirPackageName
var currentPackagePath string
if currentDirPath == rootDirPath && rootDirPackageName == empty {
rootDirPackageName = astFile.Name.Name
currentPackagePath = rootDirPackageName
} else {
currentPackagePath = path.Join(moduleName, strings.Replace(currentDirPath, rootDirPath, empty, -1))
}
proxyFuncDecls := make([]ast.Decl, 0)
// 会用到的aspect全部的import
aspectPackageNamesNeedImport := make(map[string]string)
// slice是nil也能去遍历
// 不太确定当遍历Decls时候再向其中添加元素会不会有问题 使用视图来应对 其实是不会的
// https://uphie.studio/posts/Go%E4%B8%AD%E5%AF%B9%E4%B8%80%E4%B8%AAslice%E4%B8%80%E8%BE%B9%E9%81%8D%E5%8E%86%E4%B8%80%E8%BE%B9append%E8%83%BD%E5%90%A6%E6%97%A0%E9%99%90%E5%BE%AA%E7%8E%AF/
for _, decl := range astFile.Decls {
switch decl0 := decl.(type) {
case *ast.FuncDecl:
originalFuncDecl := decl0
// 函数是不是有receiver
hasReceiver := originalFuncDecl.Recv != nil
hasReceiverName := false
if hasReceiver {
hasReceiverName = utils.SliceIsNotEmpty(originalFuncDecl.Recv.List[0].Names)
}
// 如果有receiver的话还要确定是不是书写了参数名 例如 func(*A)(){} 虽然有receiver然而却是少了名
// 要未写的话要在生成的函数里生成1个 后续调用原函数的时候要用
receiverName := empty
if hasReceiver {
// 说明receiver是有名字的
if hasReceiverName {
receiverName = originalFuncDecl.Recv.List[0].Names[0].Name
} else { // 说明尚未给receiver命名未用到它,生成1个给生成的proxy函数使用来调用原函数
receiverName = "generatedReceiver"
}
}
// 函数原名
originalFuncName := originalFuncDecl.Name.Name
// 函数的path
functionPath := currentPackagePath + "." + originalFuncName
// 如果是main.main() 那么需要跳多
if functionPath == "main.main" {
continue
}
// 如果是init() 需要跳过
if originalFuncName == "init" {
continue
}
// 如果是aspect相应的函数的话 需要跳过
switch functionPath {
case config.Before.AspectPath:
continue
case config.Around.AspectPath:
continue
case config.After.AspectPath:
continue
}
// 原来的函数名字由A变为a_original
originalFuncNameNew := utils.Lower1stChar(originalFuncName) + "_original"
originalFuncDecl.Name.Name = originalFuncNameNew
// 函数是不是可变参数的 原函数的参数的ident
isVariadic := false
variadicParamName := empty
var originalFuncParamsIndent []ast.Expr
for _, param := range originalFuncDecl.Type.Params.List {
if _, variadic := param.Type.(*ast.Ellipsis); variadic {
isVariadic = true
variadicParamName = param.Names[0].Name
}
originalFuncParamsIndent = append(originalFuncParamsIndent, ast.NewIdent(param.Names[0].Name))
}
// 应对returnedValue
hasReturnedValues := originalFuncDecl.Type.Results != nil
hasNamedReturnedValues := false
// 不管函数的返回的值是不是命名的统1应对
var returnedValueNames []string
var returnedValueNamesIdent []ast.Expr
var returnedValueTypes []ast.Expr
if hasReturnedValues {
for a, returnedField := range originalFuncDecl.Type.Results.List {
var returnedValueName string
// 说明函数的返回的值是命名的
if returnedField.Names != nil {
hasNamedReturnedValues = true
returnedValueName = returnedField.Names[0].Name
} else {
returnedValueName = "returnedValue" + strconv.Itoa(a)
}
returnedValueNames = append(returnedValueNames, returnedValueName)
returnedValueNamesIdent = append(returnedValueNamesIdent, ast.NewIdent(returnedValueName))
returnedValueTypes = append(returnedValueTypes, returnedField.Type)
}
}
// 生成了proxy函数的FuncDecl
proxyFuncDecl := &ast.FuncDecl{
Recv: originalFuncDecl.Recv,
Name: ast.NewIdent(originalFuncName),
Type: originalFuncDecl.Type,
Body: &ast.BlockStmt{},
}
// 要是有receiver 兜底的补上receiverName
if hasReceiver {
if hasReceiverName {
proxyFuncDecl.Recv.List[0].Names[0].Name = receiverName
} else {
proxyFuncDecl.Recv.List[0].Names = []*ast.Ident{{Name: receiverName}}
}
}
proxyFuncDecl.Body.List = append(proxyFuncDecl.Body.List, &ast.ExprStmt{
//X: &ast.BasicLit{Kind: token.STRING, Value: ""},
X: ast.NewIdent("\n"),
})
// 调用before
for _, pattern := range config.Before.PointcutExprs {
// 说明需要调用before
if satisfyPointcutExpr(functionPath, pattern) {
packageName, funcName := extractAspectPath(config.Before.AspectPath)
aspectPackageNamesNeedImport[strings.Split(config.Before.AspectPath, dot)[0]] = empty
callBeforeExpr := &ast.CallExpr{
Fun: &ast.SelectorExpr{
X: ast.NewIdent(packageName),
Sel: ast.NewIdent(funcName),
},
Args: []ast.Expr{ast.NewIdent(strconv.FormatBool(isVariadic))},
}
callBeforeExpr.Args = append(callBeforeExpr.Args, originalFuncParamsIndent...)
callBeforeExprStmt := &ast.ExprStmt{X: callBeforeExpr}
proxyFuncDecl.Body.List = append(proxyFuncDecl.Body.List, callBeforeExprStmt)
break
}
}
// 调用原来的函数还是around 需要用到receiverName
callOrigin := true
for _, pattern := range config.Around.PointcutExprs {
if satisfyPointcutExpr(functionPath, pattern) {
callOrigin = false
break
}
}
if callOrigin {
var callOriginalExpr *ast.CallExpr
if hasReceiver { // receiverName.函数名字()
callOriginalExpr = &ast.CallExpr{
Fun: &ast.SelectorExpr{
X: ast.NewIdent(receiverName),
Sel: ast.NewIdent(originalFuncNameNew),
},
Args: originalFuncParamsIndent,
}
} else {
callOriginalExpr = &ast.CallExpr{
Fun: ast.NewIdent(originalFuncNameNew),
Args: originalFuncParamsIndent,
}
}
// 函数如果是可变参数的话 调用original时候需要化整为零
if isVariadic {
argCount := len(callOriginalExpr.Args)
callOriginalExpr.Args = append(callOriginalExpr.Args[0:argCount-1], ast.NewIdent(variadicParamName+"..."))
}
if hasReturnedValues {
tok := token.DEFINE
if hasNamedReturnedValues {
tok = token.ASSIGN
}
// rootDirPackageName:=原来的函数调用()
assignStmt := &ast.AssignStmt{
Lhs: returnedValueNamesIdent,
Tok: tok,
Rhs: []ast.Expr{callOriginalExpr},
}
proxyFuncDecl.Body.List = append(proxyFuncDecl.Body.List, assignStmt)
} else { // 原来的函数调用()
callOriginalStmt := &ast.ExprStmt{X: callOriginalExpr}
proxyFuncDecl.Body.List = append(proxyFuncDecl.Body.List, callOriginalStmt)
}
} else { // 调用around需要用到receiverName
var function ast.Expr
if hasReceiver {
function = &ast.SelectorExpr{
X: ast.NewIdent(receiverName),
Sel: ast.NewIdent(originalFuncNameNew),
}
} else {
function = ast.NewIdent(originalFuncNameNew)
}
packageName, funcName := extractAspectPath(config.Around.AspectPath)
aspectPackageNamesNeedImport[strings.Split(config.Around.AspectPath, dot)[0]] = empty
callAroundExpr := &ast.CallExpr{
Fun: &ast.SelectorExpr{
X: ast.NewIdent(packageName),
Sel: ast.NewIdent(funcName),
},
Args: []ast.Expr{function, ast.NewIdent(strconv.FormatBool(isVariadic))},
}
callAroundExpr.Args = append(callAroundExpr.Args, originalFuncParamsIndent...)
// 要是函数有返回值的话需要收取
if hasReturnedValues {
// returnedValues:=processor.Around()
assignStmt := &ast.AssignStmt{
Lhs: []ast.Expr{ast.NewIdent("returnedValues")},
Tok: token.DEFINE,
Rhs: []ast.Expr{callAroundExpr},
}
proxyFuncDecl.Body.List = append(proxyFuncDecl.Body.List, assignStmt)
tok := token.DEFINE
if hasNamedReturnedValues {
tok = token.ASSIGN
}
// returnedValue0,_:=returnedValues[0].(string)
for a, returnedValueNameIdent := range returnedValueNamesIdent {
assignStmt := &ast.AssignStmt{
Lhs: []ast.Expr{returnedValueNameIdent, ast.NewIdent("_")},
Tok: tok,
Rhs: []ast.Expr{
&ast.TypeAssertExpr{
X: &ast.IndexExpr{
X: ast.NewIdent("returnedValues"),
Index: ast.NewIdent(strconv.Itoa(a)),
},
Type: returnedValueTypes[a],
},
},
}
appendToFuncDeclTail(proxyFuncDecl, assignStmt)
}
} else { // processor.Around()
callAroundStmt := &ast.ExprStmt{X: callAroundExpr}
proxyFuncDecl.Body.List = append(proxyFuncDecl.Body.List, callAroundStmt)
}
}
// 调用after
callAfter := false
for _, pattern := range config.After.PointcutExprs {
if satisfyPointcutExpr(functionPath, pattern) {
callAfter = true
}
}
if callAfter {
packageName, funcName := extractAspectPath(config.After.AspectPath)
aspectPackageNamesNeedImport[strings.Split(config.After.AspectPath, dot)[0]] = empty
afterFunction := &ast.SelectorExpr{
X: ast.NewIdent(packageName),
Sel: ast.NewIdent(funcName),
}
if hasReturnedValues {
// processor.After(returnedValueNamesIdent[0],returnedValueNamesIdent[1])
// processor.After(returnedValue0)
callAfterExpr := &ast.CallExpr{
Fun: afterFunction,
Args: returnedValueNamesIdent,
}
appendToFuncDeclTail(proxyFuncDecl, &ast.ExprStmt{X: callAfterExpr})
} else {
// processor.After()
callAfterExpr := &ast.CallExpr{Fun: afterFunction}
appendToFuncDeclTail(proxyFuncDecl, &ast.ExprStmt{X: callAfterExpr})
}
}
// 应对return
if hasReturnedValues {
returnStmt := &ast.ReturnStmt{}
// 如果函数的返回的值是有名字的 那么直接return
if hasNamedReturnedValues {
} else {
returnStmt.Results = returnedValueNamesIdent
}
appendToFuncDeclTail(proxyFuncDecl, returnStmt)
}
// proxy函数已构建完毕 添加到astFile尾部
proxyFuncDecls = append(proxyFuncDecls, proxyFuncDecl)
}
}
// 得到当前的目录对应的generate体系的目录
generatedGoFileParentDirPath := generateFileParentDirPath(currentDirPath)
generatedGoFilePath := path.Join(generatedGoFileParentDirPath, originalGoFileName)
generatedGoFile, err := os.OpenFile(generatedGoFilePath, os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
panic(err)
}
err = printer.Fprint(generatedGoFile, fileSet, astFile)
if err != nil {
panic(err)
}
a := &ast.BasicLit{Kind: token.STRING, Value: "\n// -----------------below generated-----------------\n\n"}
_ = printer.Fprint(generatedGoFile, fileSet, a)
for _, proxyFuncDecl := range proxyFuncDecls {
_ = printer.Fprint(generatedGoFile, fileSet, proxyFuncDecl)
_ = printer.Fprint(generatedGoFile, fileSet, &ast.BasicLit{Kind: token.STRING, Value: "\n\n"})
}
_ = generatedGoFile.Close()
handleGeneratedGoFile(generatedGoFilePath, aspectPackageNamesNeedImport)
}(dirEntry)
}
}
// 生成的go文件的起始的地点(package之前)添加 // generated file do not edit
// 要是原来的文件打头的地点有了comment 那么很容易处理 只需要把内容加到相应的commentGroup便可以了
// 要是原来不存在的话就有点不容易了 确实是可以 强行生成1个的commentGroup填充内容然后把commentGroup设置到ast.Doc和ast.Comment
// 然而生成的go文件会是这样的
//
// package // generated file do not edit
// test
//
// 是不够漂亮的
// 干脆使用原始的暴力的套路使用普通的读写文件 新生成1个文件 把内容添加到该文件的打头 然后写入原go文件的全部内容
// 生成的文件是 rootDirPackageName.go.temp
// 使用原始的文件读写的套路实现对文件的打头的部分写入 // file generated by go_aop do not edit 和 import
func handleGeneratedGoFile(generatedGoFilePath string, aspectPackageNamesNeedImport map[string]string) {
originalGoFile, _ := os.Open(generatedGoFilePath)
goTempFilePath := generatedGoFilePath + ".temp"
goTempFile, err := os.OpenFile(goTempFilePath, os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
panic(err)
}
// 头部写入 // generated file do not edit
_, _ = goTempFile.WriteString("// file generated by go_aop do not edit\n")
buffer := make([]byte, 1)
beforePackageClauseContent := make([]byte, 0)
// 用来读取"package " 不忘了后边的空白
packageClauseContent := make([]byte, 7)
a:
for {
_, _ = originalGoFile.Read(buffer)
if 'p' == buffer[0] {
position, _ := originalGoFile.Seek(0, io.SeekCurrent)
for {
readNum, _ := originalGoFile.Read(packageClauseContent)
if "ackage " == string(packageClauseContent[:readNum]) {
packageClauseContent = append([]byte{'p'}, packageClauseContent...)
for {
_, _ = originalGoFile.Read(buffer)
packageClauseContent = append(packageClauseContent, buffer[0])
switch buffer[0] {
case byte('\n'):
fallthrough
case byte(';'): // go代码都写在1行的话使用;
break a
}
}
} else {
_, _ = originalGoFile.Seek(position, io.SeekStart)
beforePackageClauseContent = append(beforePackageClauseContent, buffer[0])
break
}
}
} else {
beforePackageClauseContent = append(beforePackageClauseContent, buffer[0])
}
}
// 把原文件的package clause前边的内容 写到新的文件
_, _ = goTempFile.Write(beforePackageClauseContent)
// 把原文件的package clause 写到新的文件
_, _ = goTempFile.Write(packageClauseContent)
// 写入需要用到的import
// 要是import都是相同的需要去重
for importPath := range aspectPackageNamesNeedImport {
_, _ = goTempFile.WriteString(fmt.Sprintf("import \"%s\"\n", importPath))
}
// 写入原go文件的剩下内容
buffer = make([]byte, 4096)
for {
readNum, err := originalGoFile.Read(buffer)
if err == io.EOF {
break
}
if err != nil {
panic(err)
}
_, _ = goTempFile.Write(buffer[:readNum])
}
_ = goTempFile.Close()
_ = originalGoFile.Close()
//os.Remove(generatedGoFilePath)
err = os.Rename(goTempFilePath, generatedGoFilePath)
if err != nil {
panic(err)
}
}
func appendToFuncDeclTail(funcDecl *ast.FuncDecl, stmt ast.Stmt) {
funcDecl.Body.List = append(funcDecl.Body.List, stmt)
}
type Config struct {
Before struct {
PointcutExprs []string `json:"pointcutExprs"`
AspectPath string `json:"aspectPath"`
} `json:"before"`
Around struct {
PointcutExprs []string `json:"pointcutExprs"`
AspectPath string `json:"aspectPath"`
} `json:"around"`
After struct {
PointcutExprs []string `json:"pointcutExprs"`
AspectPath string `json:"aspectPath"`
} `json:"after"`
}
func readConfigFromFile() {
configFilePath := os.Args[2]
flag.Parse()
if configFilePath == empty {
log.Fatal("you have not designate readme.md config file path")
}
configFile, err := os.Open(configFilePath)
if err != nil {
log.Fatalf("open config file failed: %s \n", err.Error())
}
byteArr, err := io.ReadAll(configFile)
if err != nil {
log.Fatalf("read config file failed: %s \n", err.Error())
}
err = json.Unmarshal(byteArr, config)
if err != nil {
log.Fatalf("unmarshal failed: %s \n", err.Error())
}
}
// functionPath server/test.show
// pointcutExpr server/test/*
func satisfyPointcutExpr(functionPath string, pointcutExpr string) bool {
functionPathLen := len(functionPath)
pointcutExprLen := len(pointcutExpr)
minLen := functionPathLen
if functionPathLen > pointcutExprLen {
minLen = pointcutExprLen
}
functionPathTail := empty
pointcutExprTail := empty
for a := 0; a < minLen; a++ {
if functionPath[a] != pointcutExpr[a] {
functionPathTail = functionPath[a:]
pointcutExprTail = pointcutExpr[a:]
break
}
}
if functionPathTail == pointcutExprTail {
return true
}
if pointcutExprTail == "*" {
return true
}
if functionPathTail == strings.Replace(pointcutExprTail, "*", empty, -1) {
return true
}
return false
}
func getModuleName(goModFilePath string) string {
goModFile, err := os.Open(goModFilePath)
if err != nil {
panic(err)
}
bufioReader := bufio.NewReader(goModFile)
moduleLine, err := bufioReader.ReadString(byte('\n'))
if err != nil {
panic(err)
}
return strings.Replace(moduleLine, "module ", empty, 1)
}
func extractAspectPath(aspectPath string) (string, string) {
elementSlice := strings.Split(aspectPath, dot)
lastIndex := strings.LastIndex(elementSlice[0], "/")
packageName := elementSlice[0][lastIndex+1:]
funcName := elementSlice[1]
return packageName, funcName
}
func generateFileParentDirPath(currentDirPath string) string {
packagePath := strings.Replace(currentDirPath, rootDirPath, empty, -1)
generatedFileParentDirPath := path.Join(rootDirPath, "generate", packagePath)
mutexIndex := len(generatedFileParentDirPath) % len(mutexes)
mutexes[mutexIndex].Lock()
err := os.MkdirAll(generatedFileParentDirPath, 0750)
if err != nil {
panic(err)
}
mutexes[mutexIndex].Unlock()
return generatedFileParentDirPath
}
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
Go
1
https://gitee.com/fenquen/go_aop.git
git@gitee.com:fenquen/go_aop.git
fenquen
go_aop
go_aop
0401138ac516

搜索帮助