Go controller設(shè)計(jì)

2022-05-13 16:55 更新

傳統(tǒng)的MVC框架大多數(shù)是基于A(yíng)ction設(shè)計(jì)的后綴式映射,然而,現(xiàn)在Web流行REST風(fēng)格的架構(gòu)。盡管使用Filter或者rewrite能夠通過(guò)URL重寫(xiě)實(shí)現(xiàn)REST風(fēng)格的URL,但是為什么不直接設(shè)計(jì)一個(gè)全新的REST風(fēng)格的 MVC框架呢?本小節(jié)就是基于這種思路來(lái)講述如何從頭設(shè)計(jì)一個(gè)基于REST風(fēng)格的MVC框架中的controller,最大限度地簡(jiǎn)化Web應(yīng)用的開(kāi)發(fā),甚至編寫(xiě)一行代碼就可以實(shí)現(xiàn)“Hello, world”。

controller作用

MVC設(shè)計(jì)模式是目前Web應(yīng)用開(kāi)發(fā)中最常見(jiàn)的架構(gòu)模式,通過(guò)分離 Model(模型)、View(視圖)和 Controller(控制器),可以更容易實(shí)現(xiàn)易于擴(kuò)展的用戶(hù)界面(UI)。Model指后臺(tái)返回的數(shù)據(jù);View指需要渲染的頁(yè)面,通常是模板頁(yè)面,渲染后的內(nèi)容通常是HTML;Controller指Web開(kāi)發(fā)人員編寫(xiě)的處理不同URL的控制器,如前面小節(jié)講述的路由就是URL請(qǐng)求轉(zhuǎn)發(fā)到控制器的過(guò)程,controller在整個(gè)的MVC框架中起到了一個(gè)核心的作用,負(fù)責(zé)處理業(yè)務(wù)邏輯,因此控制器是整個(gè)框架中必不可少的一部分,Model和View對(duì)于有些業(yè)務(wù)需求是可以不寫(xiě)的,例如沒(méi)有數(shù)據(jù)處理的邏輯處理,沒(méi)有頁(yè)面輸出的302調(diào)整之類(lèi)的就不需要Model和View,但是controller這一環(huán)節(jié)是必不可少的。

beego的REST設(shè)計(jì)

前面小節(jié)介紹了路由實(shí)現(xiàn)了注冊(cè)struct的功能,而struct中實(shí)現(xiàn)了REST方式,因此我們需要設(shè)計(jì)一個(gè)用于邏輯處理controller的基類(lèi),這里主要設(shè)計(jì)了兩個(gè)類(lèi)型,一個(gè)struct、一個(gè)interface

type Controller struct {
    Ct        *Context
    Tpl       *template.Template
    Data      map[interface{}]interface{}
    ChildName string
    TplNames  string
    Layout    []string
    TplExt    string
}

type ControllerInterface interface {
    Init(ct *Context, cn string)    //初始化上下文和子類(lèi)名稱(chēng)
    Prepare()                       //開(kāi)始執(zhí)行之前的一些處理
    Get()                           //method=GET的處理
    Post()                          //method=POST的處理
    Delete()                        //method=DELETE的處理
    Put()                           //method=PUT的處理
    Head()                          //method=HEAD的處理
    Patch()                         //method=PATCH的處理
    Options()                       //method=OPTIONS的處理
    Finish()                        //執(zhí)行完成之后的處理       
    Render() error                  //執(zhí)行完method對(duì)應(yīng)的方法之后渲染頁(yè)面
}

那么前面介紹的路由add函數(shù)的時(shí)候是定義了ControllerInterface類(lèi)型,因此,只要我們實(shí)現(xiàn)這個(gè)接口就可以,所以我們的基類(lèi)Controller實(shí)現(xiàn)如下的方法:

func (c *Controller) Init(ct *Context, cn string) {
    c.Data = make(map[interface{}]interface{})
    c.Layout = make([]string, 0)
    c.TplNames = ""
    c.ChildName = cn
    c.Ct = ct
    c.TplExt = "tpl"
}

func (c *Controller) Prepare() {

}

func (c *Controller) Finish() {

}

func (c *Controller) Get() {
    http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405)
}

func (c *Controller) Post() {
    http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405)
}

func (c *Controller) Delete() {
    http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405)
}

func (c *Controller) Put() {
    http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405)
}

func (c *Controller) Head() {
    http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405)
}

func (c *Controller) Patch() {
    http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405)
}

func (c *Controller) Options() {
    http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405)
}

func (c *Controller) Render() error {
    if len(c.Layout) > 0 {
        var filenames []string
        for _, file := range c.Layout {
            filenames = append(filenames, path.Join(ViewsPath, file))
        }
        t, err := template.ParseFiles(filenames...)
        if err != nil {
            Trace("template ParseFiles err:", err)
        }
        err = t.ExecuteTemplate(c.Ct.ResponseWriter, c.TplNames, c.Data)
        if err != nil {
            Trace("template Execute err:", err)
        }
    } else {
        if c.TplNames == "" {
            c.TplNames = c.ChildName + "/" + c.Ct.Request.Method + "." + c.TplExt
        }
        t, err := template.ParseFiles(path.Join(ViewsPath, c.TplNames))
        if err != nil {
            Trace("template ParseFiles err:", err)
        }
        err = t.Execute(c.Ct.ResponseWriter, c.Data)
        if err != nil {
            Trace("template Execute err:", err)
        }
    }
    return nil
}

func (c *Controller) Redirect(url string, code int) {
    c.Ct.Redirect(code, url)
}   

上面的controller基類(lèi)已經(jīng)實(shí)現(xiàn)了接口定義的函數(shù),通過(guò)路由根據(jù)url執(zhí)行相應(yīng)的controller的原則,會(huì)依次執(zhí)行如下:

Init()      初始化
Prepare()   執(zhí)行之前的初始化,每個(gè)繼承的子類(lèi)可以來(lái)實(shí)現(xiàn)該函數(shù)
method()    根據(jù)不同的method執(zhí)行不同的函數(shù):GET、POST、PUT、HEAD等,子類(lèi)來(lái)實(shí)現(xiàn)這些函數(shù),如果沒(méi)實(shí)現(xiàn),那么默認(rèn)都是403
Render()    可選,根據(jù)全局變量AutoRender來(lái)判斷是否執(zhí)行
Finish()    執(zhí)行完之后執(zhí)行的操作,每個(gè)繼承的子類(lèi)可以來(lái)實(shí)現(xiàn)該函數(shù)

應(yīng)用指南

上面beego框架中完成了controller基類(lèi)的設(shè)計(jì),那么我們?cè)谖覀兊膽?yīng)用中可以這樣來(lái)設(shè)計(jì)我們的方法:

package controllers

import (
    "github.com/astaxie/beego"
)

type MainController struct {
    beego.Controller
}

func (this *MainController) Get() {
    this.Data["Username"] = "astaxie"
    this.Data["Email"] = "astaxie@gmail.com"
    this.TplNames = "index.tpl"
}

上面的方式我們實(shí)現(xiàn)了子類(lèi)MainController,實(shí)現(xiàn)了Get方法,那么如果用戶(hù)通過(guò)其他的方式(POST/HEAD等)來(lái)訪(fǎng)問(wèn)該資源都將返回403,而如果是Get來(lái)訪(fǎng)問(wèn),因?yàn)槲覀冊(cè)O(shè)置了AutoRender=true,那么在執(zhí)行完Get方法之后會(huì)自動(dòng)執(zhí)行Render函數(shù),就會(huì)顯示如下界面:


index.tpl的代碼如下所示,我們可以看到數(shù)據(jù)的設(shè)置和顯示都是相當(dāng)?shù)暮?jiǎn)單方便:

<!DOCTYPE html>
<html>
  <head>
    <title>beego welcome template</title>
  </head>
  <body>
    <h1>Hello, world!{{.Username}},{{.Email}}</h1>
  </body>
</html>
以上內(nèi)容是否對(duì)您有幫助:
在線(xiàn)筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)