Golang適用的DTO工具

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

web應用程序項目結構

其實DTO的過程對於我來講,就是基於Data Struct生成一個新的Struct結構,並附帶一個func LoadDTOTypeFromModel(data *ModelType) *DTOType。在這個過程當中,其實除了個別Object結構體須要額外處理之外,大部分都是新換一個tag~。所以這部分工做步驟都是相似的,那麼爲何不用一個工具來避免這部分重複的工做呢~?數據結構

思路

先說一下思路: app

· 1. 從.go中獲取到指定結構體的結構性描述
· 2. 根據結構性描述來生成新的結構體
· 3. 根據額外的配置,生成一個新的文件types.goide

其中結構性描述以下:函數

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 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 \

須要處理的源文件
生成的.go文件

參考連接

相關文章
相關標籤/搜索