123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451 |
- package main
- import (
- "bytes"
- "flag"
- "fmt"
- "go/ast"
- "io/ioutil"
- "log"
- "os"
- "path/filepath"
- "regexp"
- "strconv"
- "strings"
- "text/template"
- "go-common/app/tool/cache/common"
- )
- var (
- // arguments
- singleFlight = flag.Bool("singleflight", false, "enable singleflight")
- nullCache = flag.String("nullcache", "", "null cache")
- checkNullCode = flag.String("check_null_code", "", "check null code")
- batchSize = flag.Int("batch", 0, "batch size")
- batchErr = flag.String("batch_err", "break", "batch err to contine or break")
- maxGroup = flag.Int("max_group", 0, "max group size")
- sync = flag.Bool("sync", false, "add cache in sync way.")
- paging = flag.Bool("paging", false, "use paging in single template")
- ignores = flag.String("ignores", "", "ignore params")
- numberTypes = []string{"int", "int8", "int16", "int32", "int64", "float32", "float64", "uint", "uint8", "uint16", "uint32", "uint64"}
- simpleTypes = []string{"int", "int8", "int16", "int32", "int64", "float32", "float64", "uint", "uint8", "uint16", "uint32", "uint64", "bool", "string", "[]byte"}
- optionNames = []string{"singleflight", "nullcache", "check_null_code", "batch", "max_group", "sync", "paging", "ignores", "batch_err"}
- optionNamesMap = map[string]bool{}
- )
- const (
- _interfaceName = "_cache"
- _multiTpl = 1
- _singleTpl = 2
- _noneTpl = 3
- )
- func resetFlag() {
- *singleFlight = false
- *nullCache = ""
- *checkNullCode = ""
- *batchSize = 0
- *maxGroup = 0
- *sync = false
- *paging = false
- *batchErr = "break"
- *ignores = ""
- }
- // options options
- type options struct {
- name string
- keyType string
- valueType string
- cacheFunc string
- rawFunc string
- addCacheFunc string
- template int
- SimpleValue bool
- NumberValue bool
- GoValue bool
- ZeroValue string
- ImportPackage string
- importPackages []string
- Args string
- PkgName string
- EnableSingleFlight bool
- NullCache string
- EnableNullCache bool
- GroupSize int
- MaxGroup int
- EnableBatch bool
- BatchErrBreak bool
- Sync bool
- CheckNullCode string
- ExtraArgsType string
- ExtraArgs string
- ExtraCacheArgs string
- ExtraRawArgs string
- ExtraAddCacheArgs string
- EnablePaging bool
- Comment string
- }
- // parse parse options
- func parse(s *common.Source) (opts []*options) {
- f := s.F
- fset := s.Fset
- src := s.Src
- c := f.Scope.Lookup(_interfaceName)
- if (c == nil) || (c.Kind != ast.Typ) {
- log.Fatalln("无法找到缓存声明")
- }
- lines := strings.Split(src, "\n")
- lists := c.Decl.(*ast.TypeSpec).Type.(*ast.InterfaceType).Methods.List
- for _, list := range lists {
- opt := options{Args: s.GetDef(_interfaceName), importPackages: s.Packages(list)}
- // get comment
- line := fset.Position(list.Pos()).Line - 3
- if len(lines)-1 >= line {
- comment := lines[line]
- opt.Comment = common.RegexpReplace(`\s+//(?P<name>.+)`, comment, "$name")
- opt.Comment = strings.TrimSpace(opt.Comment)
- }
- // get options
- line = fset.Position(list.Pos()).Line - 2
- comment := lines[line]
- os.Args = []string{os.Args[0]}
- if regexp.MustCompile(`\s+//\s*cache:.+`).Match([]byte(comment)) {
- args := strings.Split(common.RegexpReplace(`//\s*cache:(?P<arg>.+)`, comment, "$arg"), " ")
- for _, arg := range args {
- arg = strings.TrimSpace(arg)
- if arg != "" {
- // validate option name
- argName := common.RegexpReplace(`-(?P<name>[\w_-]+)=.+`, arg, "$name")
- if !optionNamesMap[argName] {
- log.Fatalf("选项:%s 不存在 请检查拼写\n", argName)
- }
- os.Args = append(os.Args, arg)
- }
- }
- }
- resetFlag()
- flag.Parse()
- opt.EnableSingleFlight = *singleFlight
- opt.NullCache = *nullCache
- opt.EnablePaging = *paging
- opt.EnableNullCache = *nullCache != ""
- opt.EnableBatch = (*batchSize != 0) && (*maxGroup != 0)
- opt.BatchErrBreak = *batchErr == "break"
- opt.Sync = *sync
- opt.CheckNullCode = *checkNullCode
- opt.GroupSize = *batchSize
- opt.MaxGroup = *maxGroup
- // get func
- opt.name = list.Names[0].Name
- params := list.Type.(*ast.FuncType).Params.List
- if len(params) == 0 {
- log.Fatalln(opt.name + "参数不足")
- }
- if s.ExprString(params[0].Type) != "context.Context" {
- log.Fatalln("第一个参数必须为context")
- }
- if len(params) == 1 {
- opt.template = _noneTpl
- } else {
- if _, ok := params[1].Type.(*ast.ArrayType); ok {
- opt.template = _multiTpl
- } else {
- opt.template = _singleTpl
- // get key
- opt.keyType = s.ExprString(params[1].Type)
- }
- }
- if len(params) > 2 {
- var args []string
- var allArgs []string
- for _, pa := range params[2:] {
- paType := s.ExprString(pa.Type)
- if len(pa.Names) == 0 {
- args = append(args, paType)
- allArgs = append(allArgs, paType)
- continue
- }
- var names []string
- for _, name := range pa.Names {
- names = append(names, name.Name)
- }
- allArgs = append(allArgs, strings.Join(names, ",")+" "+paType)
- args = append(args, names...)
- }
- opt.ExtraArgs = strings.Join(args, ",")
- opt.ExtraArgsType = strings.Join(allArgs, ",")
- argsMap := make(map[string]bool)
- for _, arg := range args {
- argsMap[arg] = true
- }
- ignoreCache := make(map[string]bool)
- ignoreRaw := make(map[string]bool)
- ignoreAddCache := make(map[string]bool)
- ignoreArray := [3]map[string]bool{ignoreCache, ignoreRaw, ignoreAddCache}
- if *ignores != "" {
- is := strings.Split(*ignores, "|")
- if len(is) > 3 {
- log.Fatalln("ignores参数错误")
- }
- for i := range is {
- if len(is) > i {
- for _, s := range strings.Split(is[i], ",") {
- ignoreArray[i][s] = true
- }
- }
- }
- }
- var as []string
- for _, arg := range args {
- if !ignoreCache[arg] {
- as = append(as, arg)
- }
- }
- opt.ExtraCacheArgs = strings.Join(as, ",")
- as = []string{}
- for _, arg := range args {
- if !ignoreRaw[arg] {
- as = append(as, arg)
- }
- }
- opt.ExtraRawArgs = strings.Join(as, ",")
- as = []string{}
- for _, arg := range args {
- if !ignoreAddCache[arg] {
- as = append(as, arg)
- }
- }
- opt.ExtraAddCacheArgs = strings.Join(as, ",")
- if opt.ExtraAddCacheArgs != "" {
- opt.ExtraAddCacheArgs = "," + opt.ExtraAddCacheArgs
- }
- if opt.ExtraRawArgs != "" {
- opt.ExtraRawArgs = "," + opt.ExtraRawArgs
- }
- if opt.ExtraCacheArgs != "" {
- opt.ExtraCacheArgs = "," + opt.ExtraCacheArgs
- }
- if opt.ExtraArgs != "" {
- opt.ExtraArgs = "," + opt.ExtraArgs
- }
- if opt.ExtraArgsType != "" {
- opt.ExtraArgsType = "," + opt.ExtraArgsType
- }
- }
- // get k v from results
- results := list.Type.(*ast.FuncType).Results.List
- if len(results) != 2 {
- log.Fatalln(opt.name + ": 参数个数不对")
- }
- if s.ExprString(results[1].Type) != "error" {
- log.Fatalln(opt.name + ": 最后返回值参数需为error")
- }
- if opt.template == _multiTpl {
- p, ok := results[0].Type.(*ast.MapType)
- if !ok {
- log.Fatalln(opt.name + ": 批量获取方法 返回值类型需为map类型")
- }
- opt.keyType = s.ExprString(p.Key)
- opt.valueType = s.ExprString(p.Value)
- } else {
- opt.valueType = s.ExprString(results[0].Type)
- }
- for _, t := range numberTypes {
- if t == opt.valueType {
- opt.NumberValue = true
- break
- }
- }
- opt.ZeroValue = "nil"
- for _, t := range simpleTypes {
- if t == opt.valueType {
- opt.SimpleValue = true
- opt.ZeroValue = zeroValue(t)
- break
- }
- }
- if !opt.SimpleValue {
- for _, t := range []string{"[]", "map"} {
- if strings.HasPrefix(opt.valueType, t) {
- opt.GoValue = true
- break
- }
- }
- }
- upperName := strings.ToUpper(opt.name[0:1]) + opt.name[1:]
- opt.cacheFunc = fmt.Sprintf("d.Cache%s", upperName)
- opt.rawFunc = fmt.Sprintf("d.Raw%s", upperName)
- opt.addCacheFunc = fmt.Sprintf("d.AddCache%s", upperName)
- opt.Check()
- opts = append(opts, &opt)
- }
- return
- }
- func (option *options) Check() {
- if !option.SimpleValue && !strings.Contains(option.valueType, "*") && !strings.Contains(option.valueType, "[]") && !strings.Contains(option.valueType, "map") {
- log.Fatalf("%s: 值类型只能为基本类型/slice/map/指针类型\n", option.name)
- }
- if option.EnableSingleFlight && option.EnableBatch {
- log.Fatalf("%s: 单飞和批量获取不能同时开启\n", option.name)
- }
- if option.template != _singleTpl && option.EnablePaging {
- log.Fatalf("%s: 分页只能用在单key模板中\n", option.name)
- }
- if option.SimpleValue && !option.EnableNullCache {
- if !((option.template == _multiTpl) && option.NumberValue) {
- log.Fatalf("%s: 值为基本类型时需开启空缓存 防止缓存零值穿透\n", option.name)
- }
- }
- if option.EnableNullCache {
- if !option.SimpleValue && option.CheckNullCode == "" {
- log.Fatalf("%s: 缺少-check_null_code参数\n", option.name)
- }
- if option.SimpleValue && option.NullCache == option.ZeroValue {
- log.Fatalf("%s: %s 不能作为空缓存值 \n", option.name, option.NullCache)
- }
- if strings.Contains(option.NullCache, "{}") {
- // -nullcache=[]*model.OrderMain{} 这种无效
- log.Fatalf("%s: %s 不能作为空缓存值 会导致空缓存无效 \n", option.name, option.NullCache)
- }
- if strings.Contains(option.CheckNullCode, "len") && strings.Contains(strings.Replace(option.CheckNullCode, " ", "", -1), "==0") {
- // -check_null_code=len($)==0 这种无效
- log.Fatalf("%s: -check_null_code=%s 错误 会有无意义的赋值\n", option.name, option.CheckNullCode)
- }
- }
- }
- func genHeader(opts []*options) (src string) {
- option := options{PkgName: os.Getenv("GOPACKAGE")}
- var sfCount int
- var packages, sfInit []string
- packagesMap := map[string]bool{`"context"`: true}
- for _, opt := range opts {
- if opt.EnableSingleFlight {
- option.EnableSingleFlight = true
- sfCount++
- }
- if opt.EnableBatch {
- option.EnableBatch = true
- }
- if len(opt.importPackages) > 0 {
- for _, pkg := range opt.importPackages {
- if !packagesMap[pkg] {
- packages = append(packages, pkg)
- packagesMap[pkg] = true
- }
- }
- }
- if opt.Args != "" {
- option.Args = opt.Args
- }
- }
- option.ImportPackage = strings.Join(packages, "\n")
- for i := 0; i < sfCount; i++ {
- sfInit = append(sfInit, "{}")
- }
- src = _headerTemplate
- src = strings.Replace(src, "SFCOUNT", strconv.Itoa(sfCount), -1)
- t := template.Must(template.New("header").Parse(src))
- var buffer bytes.Buffer
- err := t.Execute(&buffer, option)
- if err != nil {
- log.Fatalf("execute template: %s", err)
- }
- // Format the output.
- src = strings.Replace(buffer.String(), "\t", "", -1)
- src = regexp.MustCompile("\n+").ReplaceAllString(src, "\n")
- src = strings.Replace(src, "NEWLINE", "", -1)
- src = strings.Replace(src, "ARGS", option.Args, -1)
- src = strings.Replace(src, "SFINIT", strings.Join(sfInit, ","), -1)
- return
- }
- func genBody(opts []*options) (res string) {
- sfnum := -1
- for _, option := range opts {
- var nullCodeVar, src string
- if option.template == _multiTpl {
- src = _multiTemplate
- nullCodeVar = "v"
- } else if option.template == _singleTpl {
- src = _singleTemplate
- nullCodeVar = "res"
- } else {
- src = _noneTemplate
- nullCodeVar = "res"
- }
- if option.template != _noneTpl {
- src = strings.Replace(src, "KEY", option.keyType, -1)
- }
- if option.CheckNullCode != "" {
- option.CheckNullCode = strings.Replace(option.CheckNullCode, "$", nullCodeVar, -1)
- }
- if option.EnableSingleFlight {
- sfnum++
- }
- src = strings.Replace(src, "NAME", option.name, -1)
- src = strings.Replace(src, "VALUE", option.valueType, -1)
- src = strings.Replace(src, "ADDCACHEFUNC", option.addCacheFunc, -1)
- src = strings.Replace(src, "CACHEFUNC", option.cacheFunc, -1)
- src = strings.Replace(src, "RAWFUNC", option.rawFunc, -1)
- src = strings.Replace(src, "GROUPSIZE", strconv.Itoa(option.GroupSize), -1)
- src = strings.Replace(src, "MAXGROUP", strconv.Itoa(option.MaxGroup), -1)
- src = strings.Replace(src, "SFNUM", strconv.Itoa(sfnum), -1)
- t := template.Must(template.New("cache").Parse(src))
- var buffer bytes.Buffer
- err := t.Execute(&buffer, option)
- if err != nil {
- log.Fatalf("execute template: %s", err)
- }
- // Format the output.
- src = strings.Replace(buffer.String(), "\t", "", -1)
- src = regexp.MustCompile("\n+").ReplaceAllString(src, "\n")
- res = res + "\n" + src
- }
- return
- }
- func zeroValue(t string) string {
- switch t {
- case "bool":
- return "false"
- case "string":
- return "\"\""
- case "[]byte":
- return "nil"
- default:
- return "0"
- }
- }
- func init() {
- for _, name := range optionNames {
- optionNamesMap[name] = true
- }
- }
- func main() {
- log.SetFlags(0)
- defer func() {
- if err := recover(); err != nil {
- log.Fatalf("程序解析失败, err: %+v 请企业微信联系 @wangxu01", err)
- }
- }()
- options := parse(common.NewSource(common.SourceText()))
- header := genHeader(options)
- body := genBody(options)
- code := common.FormatCode(header + "\n" + body)
- // Write to file.
- dir := filepath.Dir(".")
- outputName := filepath.Join(dir, "dao.cache.go")
- err := ioutil.WriteFile(outputName, []byte(code), 0644)
- if err != nil {
- log.Fatalf("写入文件失败: %s", err)
- }
- log.Println("dao.cache.go: 生成成功")
- }
|