本文首發於個人博客: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 \