本文首发于个人博客:Golang适用的DTO工具,我同时在知乎专栏也发布了一样主题的文章,可是文章脉络更清晰一点(我的感受),连接由此去知乎版本-Golang适用的DTO工具,逃~git
DTO (Data Transfer Object) 是Java中的概念,起到数据封装和隔离的做用。在使用Golang开发Web应用的过程当中,也会有相似的需求。先贴项目地址 github.com/yeqown/server-common/tree/master/dbs/tools
如今有一个用户数据结构以下,github
type UserModel struct { ID int64 `gorm:"column:id"` Name string `gorm:"column:name"` Password string `gorm:"column:password"` }
// 问题1: 如今要求是想要JSON格式返回用户数据,而且不但愿其中包含有Password字段
// 解决1:golang
type UserModel struct { ID int64 `gorm:"column:id" json:"id"` Name string `gorm:"column:name" json:"name"` Password string `gorm:"column:password" json:"-"` }
// 问题2: 一样是JSON数据格式,而且但愿额外返回用户的身份标示Ident(假设必需要跟用户数据放在一块儿)
// 解决2: (这也是个人场景)web
type UserDTO struct { ID int64 `json:"id"` Name string `json:"name"` Password string `json:"-"` Ident string `json:"ident"` } func LoadUserDTOFromModel(data *UserMolde) *UserDTO { ident := genUserIdent(data) return &{ ID data.ID, Name data.Name, Ident ident, } }
通常来讲个人项目结构以下:其中models和services也就是分开定义Data struct(UserModel)和Object(UserDTO)的文件夹。json
其实DTO的过程对于我来讲,就是基于Data Struct生成一个新的Struct结构,并附带一个func LoadDTOTypeFromModel(data *ModelType) *DTOType
。在这个过程当中,其实除了个别Object结构体须要额外处理之外,大部分都是新换一个tag~。所以这部分工做步骤都是相似的,那么为何不用一个工具来避免这部分重复的工做呢~?数据结构
先说一下思路: app
· 1. 从.go
中获取到指定结构体的结构性描述
· 2. 根据结构性描述来生成新的结构体
· 3. 根据额外的配置,生成一个新的文件types.go
ide
其中结构性描述以下:函数
type innerStruct struct { fields []*field content string name string pkgName string } type field struct { name string // field name string typ string // field type string tag string // tag name string }
在整个流程中比较麻烦的就是,怎么获取到,特定类型结构体的结构性描述?Go文件解析部分。这里想记录一个小插曲:最开始我找解析go文件方法的时候,在Google中搜索“如何解析go文件”,出来的结果没有太大帮助,而后我又尝试了“How to parse .go file source code”,结果就提示了parser
& loader
两个看起来就颇有帮助的包名。。。。这里我选用了loader
。工具
Package loader loads a complete Go program from source code, parsing and type-checking the initial packages plus their transitive closure of dependencies.
正好这个包是从源代码去加载Go程序,对初始包进行解析和类型检查等。
通过阅读loader包文档,我完成了一个函数用于获取指定的结构体的结构性描述信息:代码在此
// Exported, and specified type func loadGoFiles(dir string, filenames ...string) ([]*innerStruct, error) { newFilenames := []string{} for _, filename := range filenames { newFilenames = append(newFilenames, path.Join(dir, filename)) } conf.CreateFromFilenames("", newFilenames...) prog, err := conf.Load() if err != nil { log.Println("load program err:", err) return nil, err } return loopProgramCreated(prog.Created), nil } // loopProgramCreated to loo and filter: // 1. unexported type // 2. bultin types // 3. only specified style struct name func loopProgramCreated( created []*loader.PackageInfo, ) (innerStructs []*innerStruct) { for _, pkgInfo := range created { pkgName := pkgInfo.Pkg.Name() defs := pkgInfo.Defs for indent, obj := range defs { if !indent.IsExported() || obj == nil || !strings.HasSuffix(indent.Name, specifiedStructTypeSuffix) { continue } // obj.String() 获得的string如: // type testdata.UserModel struct{Name string "gorm:\"colunm:name\""; Password string "gorm:\"column:password\""} is := parseStructString(obj.String()) is.pkgName = pkgName is.pureName() if isDebug { log.Println("parse one Model: ", is.name, is.pkgName, is.content) } innerStructs = append(innerStructs, is) } } return }
其中parseStructString
是对形如type testdata.UserModel struct{Name string "gorm:"colunm:name""; Password string "gorm:"column:password""}的字符串进行处理并整理成为innerStruct
数据。
go get github.com/yeqown/server-common/dbs/tools # 获取 github.com/yeqown/server-common/tool.main.go, # 并选择性的实现本身的 CustomParseTagFunc & CustomGenerateTagFunc go build -o dbtools tool.main.go ➜ ✗ dbtools -h Usage of ./dbtools: -debug 调试模式开关,调试模式下会输出额外的信息 -dir string 指定须要解析的目录 -filename string 指定哪些文件须要被解析,若是未设置默认dir路径下全部的.go文件 -generateDir string 生成文件存放的目录,默认当前路径 -generateFilename string 生成文件名,默认"types.go" -generatePkgName string 生成文件的包名,默认"types" -generateStructSuffix string 替换model struct的后缀,默认无后缀,如UserSuffix => User -modelImportPath string 指明model struct的导入路径, 如my-server/models -modelStructSuffix string 指明特定后缀的model struct须要被解析,默认"Model"
./bin/dbtools \ -dir=./dbs/tools/testdata \ -filename=type_model.go \ -generatePkgName=main \ -modelImportPath=github.com/yeqown/server-common/dbs/tools/testdata \