对于Repository具有固定参数的构造函数,但是对于Service的构造函数参数是不固定的。开始的时候,尝试用反射的方法,感觉非常麻烦,走不通,突发奇想,直接用文件解析的方法,解析Service的New构造函数。

type Service struct {
    Name string
    Args []ServiceArg
}

func (s Service) NewFuncString() string {
    sb := new(strings.Builder)

    hasEngine := false
    hasLogger := false
    repositoryArgs := make([]ServiceArg, 0)

    for _, arg := range s.Args {
        if strings.HasPrefix(arg.TypeName, "*xorm.Engine") && hasEngine == false {
            hasEngine = true
        } else if strings.HasPrefix(arg.TypeName, "*golog.Logger") && hasLogger == false {
            hasLogger = true
        } else if strings.HasPrefix(arg.TypeName, "repository.") {
            repositoryArgs = append(repositoryArgs, arg)
        }
    }

    if hasEngine {
        sb.WriteString("engine,\n")
    }

    if hasLogger {
        sb.WriteString("logger,\n")
    }

    for _, arg := range repositoryArgs {
        sb.WriteString("repositoryContainer.")
        sb.WriteString(arg.TypeName[len("repository."):])
        sb.WriteString(",\n")
    }

    return sb.String()
}

type ServiceArg struct {
    Name     string
    TypeName string
}

type ServiceContext struct {
    Services []Service
}

func Test_GenerateServiceContainer(t *testing.T) {
    files, err1 := os.ReadDir("../service")

    if err1 != nil {
        fmt.Println("parse file err:", err1)
        return
    }

    services := make([]Service, 0)

    for _, file := range files {
        filename := file.Name()

        if strings.HasSuffix(filename, "_service.go") {
            serviceName := UpperCamelCase(filename[:len(filename)-3])

            args, err2 := ServiceArgs(fmt.Sprintf("../service/%s", filename), serviceName)

            if err2 != nil {
                fmt.Println(err2)

                return
            }

            services = append(services, Service{Name: serviceName, Args: args})

        }
    }

    if f, err := os.Stat("../service/service_container.go"); f != nil && err == nil {
        if err := os.Remove("../service/service_container.go"); err != nil {
            fmt.Println("remove file err:", err)
            return
        }
    }

    f, err2 := os.OpenFile("../service/service_container.go", os.O_CREATE|os.O_WRONLY, 0666)

    defer f.Close()

    if err2 != nil {
        fmt.Println("can not create output file,err:", err2)

        return
    }

    tpl, err3 := template.New("service-container.tpl").ParseFiles("./service-container.tpl")

    if err3 != nil {
        fmt.Println("parse file err:", err3)
        return
    }

    if err := tpl.Execute(f, &ServiceContext{Services: services}); err != nil {
        fmt.Println("There was an error:", err.Error())
    }
}

func UpperCamelCase(txt string) string {
    sb := new(strings.Builder)

    strs := strings.Split(txt, "_")

    for _, str := range strs {
        sb.WriteString(strings.ToUpper(string(str[0])))
        sb.WriteString(str[1:])
    }

    return sb.String()
}

func ServiceArgs(filename, serviceName string) ([]ServiceArg, error) {
    content, err1 := ReadGoFile(filename)

    if err1 != nil {
        return nil, err1
    }

    args := make([]ServiceArg, 0)

    startTxt := fmt.Sprintf("New%s(", serviceName)

    firstIndex := strings.Index(content, startTxt) + len(startTxt)
    lastIndex := firstIndex + strings.Index(content[firstIndex:], ")")

    reg := regexp.MustCompile(`\s+`)

    strs := strings.Split(reg.ReplaceAllString(content[firstIndex:lastIndex], " "), ",")

    for _, str := range strs {
        txt := strings.TrimSpace(str)

        if len(txt) > 0 {
            args = append(args, Arg(txt))
        }
    }

    return args, nil
}

func Arg(str string) ServiceArg {
    strs := strings.Split(str, " ")

    return ServiceArg{Name: strings.TrimSpace(strs[0]), TypeName: strings.TrimSpace(strs[1])}
}

func ReadGoFile(fileName string) (string, error) {
    f, err := os.OpenFile(fileName, os.O_RDONLY, 0600)

    defer f.Close()

    if err != nil {
        return "", err
    } else {
        if bytes, err := ioutil.ReadAll(f); err != nil {
            return "", err
        } else {
            return string(bytes), nil
        }
    }
}
模板
package service

import (
    "github.com/kataras/golog"
    "xorm.io/xorm"
)

type ServiceContainer struct{
    {{range $service := .Services -}}
    {{.Name}} {{.Name}} 
    {{end -}}
}

func NewServiceContainer(engine *xorm.Engine, logger *golog.Logger, repositoryContainer repository.RepositoryContainer) *ServiceContainer{
    return &ServiceContainer{
        {{range $service := .Services -}}
        {{.Name}}: New{{.Name}}({{.NewFuncString}}),
        {{end -}}
    }
}