本文首發於我的部落格:Golang適用的DTO工具,我同時在知乎專欄也發布了同樣主題的文章,但是文章脈絡更清晰一點(個人感覺),連結由此去知乎版本-Golang適用的DTO工具,逃~
DTO (Data Transfer Object) 是Java中的概念,起到資料封裝和隔離的作用。在使用Golang開發Web應用的過程中,也會有類似的需求。先貼項目地址 github.com/yeqown/server-common/tree/master/dbs/tools
舉個例子
現在有一個使用者資料結構如下,
type UserModel struct { ID int64 `gorm:"column:id"` Name string `gorm:"column:name"` Password string `gorm:"column:password"`}
// 問題1: 現在要求是想要JSON格式返回使用者資料,並且不希望其中包含有Password欄位
// 解決1:
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: (這也是我的情境)
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)的檔案夾。
其實DTO的過程對於我來說,就是基於Data Struct產生一個新的Struct結構,並附帶一個func LoadDTOTypeFromModel(data *ModelType) *DTOType
。在這個過程中,其實除了個別Object結構體需要額外處理以外,大部分都是新換一個tag~。因此這部分工作步驟都是類似的,那麼為什麼不用一個工具來避免這部分重複的工作呢~?
思路
先說一下思路:
· 1. 從.go
中擷取到指定結構體的結構性描述
· 2. 根據結構性描述來產生新的結構體
· 3. 根據額外的配置,產生一個新的檔案types.go
其中結構性描述如下:
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
。
關於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程式,對初始包進行解析和類型檢查等。
Go檔案解析部分
經過閱讀loader包文檔,我完成了一個函數用於擷取指定的結構體的結構性描述資訊:代碼在此
// Exported, and specified typefunc 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 namefunc 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 & CustomGenerateTagFuncgo build -o dbtools tool.main.go➜ ✗ dbtools -hUsage 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 \
參考連結