Go語言如何開發(fā)RESTful API接口服務

此前一直寫java扭倾,最近轉(zhuǎn)go了匹耕,總結(jié)一下如何用Go語言開發(fā)RESTful API接口服務烛缔,希望對Go新手有所幫助责掏,同時也希望Go大神不吝賜教柜砾!

Golang.png

Frameworks and Libraries

Gin

網(wǎng)絡框架,采用的Gin换衬。Gin 是用 Go 編寫的一個 Web 應用框架痰驱,對比其它主流的同類框架,他有更好的性能和更快的路由瞳浦。由于其本身只是在官方 net/http 包的基礎(chǔ)上做的完善担映,所以理解和上手很平滑。

Xorm

對數(shù)據(jù)庫操作叫潦,我們可以直接寫SQl蝇完,也可以使用ORM工具,因為ORM工具使用起來方便簡潔矗蕊。我們項目中使用的是xorm庫短蜕。特點是提供簡單但豐富實用的 API 來完成對數(shù)據(jù)庫的各類操作。該庫支持包括 MySQL傻咖、PostgreSQL朋魔、SQLite3 和 MsSQL 在內(nèi)的主流數(shù)據(jù)庫,其在支持鏈式操作的基礎(chǔ)上没龙,還允許結(jié)合SQL語句進行混合處理铺厨。另外缎玫,該庫還支持session事務和回滾以及樂觀鎖等。

Project and Package Structure

本項目是一個服務的QUERY端解滓,因為我們采用的CQRS赃磨。因為query端簡單,因此采用三層結(jié)構(gòu)洼裤。分別為Controller層邻辉,Apllication層,以及Gateway層腮鞍。接下來詳細了解各層職責值骇。

Controller層

Controller層,主要提供Restful接口移国,Restful接口一定要符合規(guī)范吱瘩,這樣,別人才容易理解迹缀。

本接口是要獲取我的crm系統(tǒng)中使碾,某個商戶的某個需求的詳細信息。因此Restful接口設計如下:

func RequirementRouter(r *gin.Engine) {
  r.GET("crm/merchants/:merchantId/requirements/:requirementId", handler.QueryRequirementById)
}

Application層

Application層

Application層是主要邏輯層祝懂,需要完成權(quán)限校驗和數(shù)據(jù)查詢以及格式轉(zhuǎn)換的功能票摇。其中分為了三個Package,分別為Auth砚蓬,handler以及view矢门。

auth

Auth package中提供身份認證以及權(quán)限校驗接口。

// Get params from request and to authenticate
func HandleAuthenticateRequest() gin.HandlerFunc {
    return func(c *gin.Context) {
        userId := c.GetHeader(configuration.UIN)
        log.Debug("HandleAuthenticateRequest,user id is ", userId)
        identity, err := GetCasBinAuthInstance().HandleAuthenticate(userId, nil)
        if identity == "" || err != nil {
            c.Status(http.StatusUnauthorized)
            c.Abort()
        }
        c.Next()
    }
}

// Get params from request and to  authorize
func HandleAuthorizeRequest(ctx *gin.Context, objectType string, operation string) error {
    log.Debug("HandleAuthorizeRequest, object and operation is ", objectType, operation)
    userId := getUserIdFromRequest(ctx)
    if userId == "" {
        return errs.New(errs.UNAUTHORIZED, "user authorize failed,can not get user id")
    }
    merchantId := getMerchantIdFromRequest(ctx)
    if merchantId == "" {
        return errs.New(errs.UNAUTHORIZED, "user authorize failed,can not get merchant id")
    }
    permission := getPermission(objectType, operation, merchantId)
    success, err := GetCasBinAuthInstance().HandleAuthorize(userId, permission)
    if err != nil || !success {
        log.Warn("user authorize failed, userId=", userId, ", err=", err)
        return errs.New(errs.UNAUTHORIZED, "user authorize failed")
    }
    return nil
}

因為每個接口都需要身份認證灰蛙,其實身份認證可以放到請求入口處做祟剔,而權(quán)限校驗不是每個接口都需要,同時摩梧,權(quán)限包含一定的業(yè)務邏輯峡扩,因此權(quán)限校驗在Applciation這一層做是比較合適的。

handler

handler pankage中是controller層和application的一個中間層障本,每一個Restful請求,對應一個Gin 的HandlerFunc响鹃,看一個Gin中的請求接口定義:

func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes {
    return group.handle("POST", relativePath, handlers)
}

// GET is a shortcut for router.Handle("GET", path, handle).
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
    return group.handle("GET", relativePath, handlers)
}

// DELETE is a shortcut for router.Handle("DELETE", path, handle).
func (group *RouterGroup) DELETE(relativePath string, handlers ...HandlerFunc) IRoutes {
    return group.handle("DELETE", relativePath, handlers)
}

在每個handler我們首先調(diào)用auth的權(quán)限校驗接口進行權(quán)限校驗驾霜,然后對必須的請求參數(shù)進行檢查,如果參數(shù)無效或缺少买置,返回錯誤粪糙,如果權(quán)限以及參數(shù)校驗均通過,則調(diào)用view中的真正邏輯接口忿项,進行查詢數(shù)據(jù)蓉冈。

func QueryRequirementById(ctx *gin.Context) {
    err := auth.HandleAuthorizeRequest(ctx, "P", "VIEW_REQUIREMENT_DETAIL")
    if err != nil {
        responseBodyHandler(ctx, nil, err)
        return
    }
    requirementId, err := strconv.Atoi(ctx.Param("requirementId"))
    if err != nil {
        resp := CreateResp("", errs.INVALID_PARAMETER, "請?zhí)峁┱_的需求ID")
        ctx.JSON(http.StatusBadRequest, &resp)
        return
    }
    doQueryRequirementById(ctx, requirementId)
}
func doQueryRequirementById(context *gin.Context, requirementId int) {
    defer func() {
        if err := recover(); err != nil {
            resp := CreateResp("", errs.UNKNOWN_ERROR, "server internal error")
            context.JSON(http.StatusInternalServerError, &resp)
            log.Error("QueryRequirementById errors=", err)
        }
    }()
    requirement, err := requirementView.QueryRequirementById(context, requirementId)
    responseBodyHandler(context, requirement, err)
}

一般函數(shù)不宜函數(shù)過長城舞,如太長,建議封裝為子函數(shù)寞酿。

view

view pankage中家夺,是真正的查詢邏輯service層,從數(shù)據(jù)庫中查詢出數(shù)據(jù)伐弹,然后拉馋,進行協(xié)議轉(zhuǎn)換,返回給Controller惨好。

// Query requirement by requirement id
func (v *RequirementView) QueryRequirementById(ctx context.Context, requirementId int) (*dto.RequirementDetail, error) {
    requirementPo, err := v.requirementDao.QueryRequirement(requirementId)
    if err != nil {
        return nil, nil
    }
    requirement :=getRequirementFromPo(requirementPo)
    return requirementDetail, nil
}

func (v *RequirementView) getRequirementFromPo(po *po.RequirementPo) *dto.RequirementDetail {
    var requirementDetai = dto.RequirementDetail{
        Id:                   po.Id,
        Name:                 po.Name,
        Category:             categoryName,
        Amount:               po.Amount,
        AmountUnit:           po.AmountUnit,
        ExpectedDeliveryDate: util.ToTimestamp(po.ExpectedDeliveryDate),
        Description:          po.Description,
        UnitPrice:            po.PricePerItem,
        DeliveryRhythm:       po.DeliveryRhythm,
        DeliveryStandard:     po.DeliveryStandard,
        CheckPeriod:          po.CheckPeriod,
    }
    return &requirementDetai
}

Gateway層

Gateway層

Gateway主要就是提供外部存儲的增刪改查能力煌茴,我們存儲采用的mysql,因此,gateway主要就是操作mysql數(shù)據(jù)庫日川。那么主要就包括Po以及Dao蔓腐。

Xorm提供了很方便的工具,XORM工具龄句,可以根據(jù)數(shù)據(jù)庫表結(jié)構(gòu)回论,生成相應的Po。這個工具的使用文檔也可以參考這篇:使用xorm工具撒璧,根據(jù)數(shù)據(jù)庫自動生成go代碼

本項目生成的的RequirementPo如下:

type RequirementPo struct {
    Id                   int       `xorm:"not null pk comment('主鍵透葛、自增') INT(11)"`
    Name                 string    `xorm:"not null default '' comment('需求名稱, 最大長度: 50') VARCHAR(100)"`
    Description          string    `xorm:"not null comment('需求描述, 最大長度: 10000') LONGTEXT"`
    Status               int       `xorm:"not null comment('0: 新需求 1:已啟動 2:生產(chǎn)中 3:已完成 4:結(jié)算中 5:已結(jié)算 6:已關(guān)閉') INT(11)"`
    CategoryId           int       `xorm:"not null comment('t_horizon_requirement_category 表的對應Id') INT(11)"`
    MerchantId           string    `xorm:"comment('CRM商戶ID') index VARCHAR(100)"`
    Creator              string    `xorm:"not null default '' comment('需求創(chuàng)建人') VARCHAR(50)"`
    PricePerItem         float64   `xorm:"not null comment('需求單價') DOUBLE(16,2)"`
    Amount               float64   `xorm:"not null comment('需求數(shù)據(jù)量') DOUBLE(16,2)"`
    AmountUnit           string    `xorm:"not null default '" "' comment('用途:需求數(shù)據(jù)量單位,取值范圍:無') VARCHAR(50)"`
    ExpectedDeliveryDate time.Time `xorm:"not null default '1970-01-01 08:00:01' comment('期望交付日期') TIMESTAMP"`
    DeliveryRule         string    `xorm:"not null comment('交付規(guī)則') VARCHAR(255)"`
    DeliveryStandard     string    `xorm:"not null comment('交付標準, 最大長度: 10000') TEXT"`
    DeliveryRhythm       string    `xorm:"not null default '" "' comment('用途:交付節(jié)奏卿樱;取值范圍:無') VARCHAR(500)"`
    CheckPeriod          string    `xorm:"not null default '"   "' comment('用途:驗收周期僚害;取值范圍:無') VARCHAR(255)"`
    DataVersion          int       `xorm:"default 0 comment('數(shù)據(jù)版本, 用于樂觀鎖') INT(11)"`
    Channel              string    `xorm:"not null default '' comment('創(chuàng)建需求的渠道') VARCHAR(255)"`
    CreateTime           time.Time `xorm:"not null default '1970-01-01 08:00:01' comment('創(chuàng)建時間') TIMESTAMP"`
}

通過XORM可以很方方便的對數(shù)據(jù)Po進行增刪改查,我的查詢接口如下:

func (d *RequirementDao) QueryRequirement(requirementId int) (*po.RequirementPo, error) {
    requirement := new(po.RequirementPo)
    session := Requirement.Engine().NewSession()
    _, err := session.Table("t_requirement").Where("id=?", requirementId).Get(requirement)
    if err != nil {
        log.Warn("query requirement error", err)
        return nil, errs.New(errs.DB_OPERATION_FAILED, "query requirement info fail")
    }
    return requirement, nil
}

想要了解更多Xorm使用信息繁调,可以查看其文檔:xorm Gobook 以及 xorm Godoc

main

最后看一下main package中的類文件職責萨蚕。

func main() {
    r := gin.New()
    r.Use(gin.Recovery())
    //添加監(jiān)控
    r.Use(MonitorHandler())
    //添加身份認證校驗
    r.use(authenticateHandler())
    //restul接口路由
    controller.RequirementRouter(r)

    r.Run(":" + configuration.Base.Server.Port)
}

ok,這樣即一個go的http服務的主要模塊,當然還有配置文件類等蹄胰,這個按照自己喜歡的方法岳遥,自行開發(fā)加載配置邏輯以及使用網(wǎng)上開源的包均可。

后記

剛接觸GO語言不久裕寨,還有很長的路要走浩蓉,加油!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末宾袜,一起剝皮案震驚了整個濱河市捻艳,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌庆猫,老刑警劉巖认轨,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異月培,居然都是意外死亡嘁字,警方通過查閱死者的電腦和手機恩急,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來纪蜒,“玉大人衷恭,你說我怎么就攤上這事』舨簦” “怎么了匾荆?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長杆烁。 經(jīng)常有香客問我牙丽,道長,這世上最難降的妖魔是什么兔魂? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任烤芦,我火速辦了婚禮,結(jié)果婚禮上析校,老公的妹妹穿的比我還像新娘构罗。我一直安慰自己,他們只是感情好智玻,可當我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布遂唧。 她就那樣靜靜地躺著,像睡著了一般吊奢。 火紅的嫁衣襯著肌膚如雪盖彭。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天页滚,我揣著相機與錄音召边,去河邊找鬼。 笑死裹驰,一個胖子當著我的面吹牛隧熙,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播幻林,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼贞盯,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了沪饺?” 一聲冷哼從身側(cè)響起邻悬,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎随闽,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體肝谭,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡掘宪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年蛾扇,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片魏滚。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡镀首,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出鼠次,到底是詐尸還是另有隱情更哄,我是刑警寧澤,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布腥寇,位于F島的核電站成翩,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏赦役。R本人自食惡果不足惜麻敌,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望掂摔。 院中可真熱鬧术羔,春花似錦、人聲如沸乙漓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽叭披。三九已至寥殖,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間趋观,已是汗流浹背扛禽。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留皱坛,地道東北人编曼。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像剩辟,于是被迫代替她去往敵國和親掐场。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,828評論 2 345