Go后臺(tái)項(xiàng)目實(shí)戰(zhàn)

本項(xiàng)目完全使用原生開發(fā),沒有使用任何WEB框架(如:gin,beego,Martini等),和ORM(如:gorm,xorm,beego)

三層架構(gòu)

三層架構(gòu)(3-tier architecture) 通常意義上的三層架構(gòu)就是將整個(gè)業(yè)務(wù)應(yīng)用劃分為:界面層(User Interface layer)、業(yè)務(wù)邏輯層(Business Logic Layer)、數(shù)據(jù)訪問層(Data access layer)。區(qū)分層次的目的即為了“高內(nèi)聚低耦合”的思想。在軟件體系架構(gòu)設(shè)計(jì)中,分層式結(jié)構(gòu)是最常見,也是最重要的一種結(jié)構(gòu)。

控制層/界面層

因?yàn)槲业捻?xiàng)目中并沒有寫WEB頁面,所以就拿控制層來說,就是將你的請(qǐng)求從頁面?zhèn)鞯胶笈_(tái)代碼

服務(wù)層/業(yè)務(wù)邏輯層

針對(duì)具體問題的操作,也可以說是對(duì)數(shù)據(jù)層的操作,對(duì)數(shù)據(jù)業(yè)務(wù)邏輯處理。(關(guān)鍵在于由原始數(shù)據(jù)抽象出邏輯數(shù)據(jù))能夠提供interface\API層次上所有的功能。,“中間業(yè)務(wù)層”的實(shí)際目的是將“數(shù)據(jù)訪問層”的最基礎(chǔ)的存儲(chǔ)邏輯組合起來,形成一種業(yè)務(wù)規(guī)則

持久層/數(shù)據(jù)訪問層

該層所做事務(wù)直接操作數(shù)據(jù)庫,針對(duì)數(shù)據(jù)的增添、刪除、修改、查找等。(關(guān)鍵在于粒度的把握)要保證“數(shù)據(jù)訪問層”的中的函數(shù)功能的原子性!即最小性和不可再分?!皵?shù)據(jù)訪問層”只管負(fù)責(zé)存儲(chǔ)或讀取數(shù)據(jù)就可以了。

Controller組合封裝

Controller"基類"封裝,主要提供了一個(gè)保存文件的方法,主要用于form-data請(qǐng)求


import(

"io"

"mime/multipart"

"net/http"

"path"

)

constBASE_IMAGE_ADDRESS ="./img/"

typeControllerstruct{

Datainterface{}

}

typeFileInfoTOstruct{

//圖片id -- 暫時(shí)沒有用

IDint64

//縮略圖路徑 -- 暫時(shí)沒有用

CompressPathstring

//原圖路徑 ,保存數(shù)據(jù)庫的路徑

Pathstring

//原始的文件名

OriginalFileNamestring

//存儲(chǔ)文件名 如:uuidutil

FileNamestring

//文件大小

FileSizeint64

}

//獲取上傳文件的數(shù)量

func(p *Controller)GetFileNum(r *http.Request,keys ...string)int{

m := r.MultipartForm

ifm ==nil{

return0

}

iflen(keys) ==0{

varnumint

for_,fileHeaders :=rangem.File {

num +=len(fileHeaders)

}

returnnum

}else{

varnumint

for_,value :=rangekeys {

num +=len(m.File[value])

}

returnnum

}

}

//解析Form-data中的文件,如果不傳keys,不管上傳的文件的字段名(filename)是什么,都會(huì)解析,否則只會(huì)解析keys指定的文件

func(p *Controller)SaveFiles(r *http.Request,,relativePathstring,keys ...string)[]*FileInfoTO{

r.ParseMultipartForm(32<<20)

m := r.MultipartForm

ifm ==nil{

log.Println("not multipartfrom !")

returnnil

}

fileInfos :=make([]*FileInfoTO,0)

filePath := BASE_IMAGE_ADDRESS + relativePath

fileutil.MakeDir(filePath)

iflen(keys) ==0{

for_,fileHeaders :=rangem.File {//遍歷所有的所有的字段名(filename)獲取FileHeaders

for_,fileHeader :=rangefileHeaders{

to := p.saveFile(filePath,relativePath,fileHeader)

fileInfos =append(fileInfos,to)

}

}

}else{

for_,value :=rangekeys {

fileHeaders := m.File[value]//根據(jù)上傳文件時(shí)指定的字段名(filename)獲取FileHeaders

for_,fileHeader :=rangefileHeaders{

to := p.saveFile(filePath,relativePath,fileHeader)

fileInfos =append(fileInfos,to)

}

}

}

returnfileInfos

}

//保存單個(gè)文件

func(p *Controller)saveFile(filePath,relativePathstring,fileHeader *multipart.FileHeader)*FileInfoTO{

file,err := fileHeader.Open()

iferr !=nil{

log.Println(err)

returnnil

}

deferfile.Close()

name,err := uuidutil.RandomUUID()

iferr !=nil{

log.Println(err)

returnnil

}

fileType := fileutil.Ext(fileHeader.Filename,".jpg")

newName := name + fileType

dst,err := os.Create(filePath + newName)

iferr !=nil{

log.Println(err)

returnnil

}

deferdst.Close()

fileSize,err := io.Copy(dst,file)

iferr !=nil{

log.Println(err)

returnnil

}

return&FileInfoTO{Path:relativePath + newName,OriginalFileName:fileHeader.Filename,FileName:newName,FileSize:fileSize}

}


fileutil

import(

"os"

"path"

)

//創(chuàng)建多級(jí)目錄

funcMkDirAll(pathstring)bool{

err := os.MkdirAll(path, os.ModePerm)

iferr !=nil{

returnfalse

}

returntrue

}

//檢測(cè)文件夾或文件是否存在

funcExist(filestring)bool{

if_,err := os.Stat(file);os.IsNotExist(err){

returnfalse

}

returntrue

}

//獲取文件的類型,如:.jpg

//如果獲取不到,返回默認(rèn)類型defaultExt

funcExt(fileNamestring,defaultExtstring)string{

t := path.Ext(fileName)

iflen(t) ==0{

returndefaultExt

}

returnt

}

/// 檢驗(yàn)文件夾是否存在,不存在 就創(chuàng)建

funcMakeDir(filePathstring){

if!Exist(filePath) {

MkDirAll(filePath)

}

}

//刪除文件

funcRemove(namestring)bool{

err := os.Remove(name)

iferr !=nil{

returnfalse

}

returntrue

}


uuidtuil

import(

"encoding/base64"

"math/rand"

)

funcRandomUUID()(string,error){

b :=make([]byte,32)

if_,err := rand.Read(b);err !=nil{

return"",err

}

returnbase64.URLEncoding.EncodeToString(b),nil

}

ApiController主要用于用戶體系的一個(gè)登陸狀態(tài)的信息獲取,根據(jù)請(qǐng)求中的session獲取服務(wù)端保存的用戶信息,如果你的后臺(tái)分用戶體系和管理端用戶體系,并且這兩個(gè)用戶體系分別存儲(chǔ)在兩個(gè)表中,這時(shí)你還可以定義一個(gè)BackController


typeApiControllerstruct{

Controller

}

func (p *ApiController) GetUserId(w http.ResponseWriter,r *http.Request) uint{

user := p.GetUser(w,r)

ifuser ==nil{

return0

}

returnuser.ID

}

func (p *ApiController) GetUser(w http.ResponseWriter,r *http.Request) *entity.User{

session := GlobalSession().SessionStart(w,r)

ifsession ==nil{

returnnil

}

key_user := session.Get(constant.KEY_USER)

ifuser,ok := key_user.(*entity.User);ok{

returnuser

}

returnnil

}

database

持久層的實(shí)現(xiàn):https://blog.csdn.net/cj_286/article/details/80363796

http

http server的實(shí)現(xiàn):https://blog.csdn.net/cj_286/article/details/80256988

Router

路由處理的實(shí)現(xiàn),其實(shí)也就是一個(gè)轉(zhuǎn)發(fā)的功能


type RouterHandler struct {

}

varmux = make(map[string]func(http.ResponseWriter,*http.Request))

func (p *RouterHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {

fmt.Println(r.URL.Path)

iffun, ok := mux[r.URL.Path]; ok {

fun(w, r)

return

}

//靜態(tài)資源

ifstrings.HasPrefix(r.URL.Path,constant.STATIC_BAES_PATH){

iffun, ok := mux[constant.STATIC_BAES_PATH]; ok {

fun(w, r)

return

}

}

http.Error(w,"error URL:"+r.URL.String(), http.StatusBadRequest)

}

func (p *RouterHandler) Router(relativePath string, handler func(http.ResponseWriter, *http.Request)) {

mux[relativePath] = handler

}

session

如果有登錄功能,所以需要用到session來記住用戶的狀態(tài),以下是session技術(shù)實(shí)現(xiàn)的主要類型與接口定義,(摘自:https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/06.0.md),會(huì)話過期時(shí)間可以自行設(shè)置,如果設(shè)置為一小時(shí),在停止會(huì)話一小時(shí)后session就會(huì)過期,該session就會(huì)被自動(dòng)刪除,如果回話都保持在一小時(shí)之內(nèi)就可以一直訪問,session不會(huì)過期


//主要用于session的管理,過期處理等

type Manager struct {

cookieName string

lock sync.Mutex

provider Provider

maxLifeTime int64

}

//用于提供session存儲(chǔ)方式的一個(gè)接口標(biāo)準(zhǔn),可以用于提供session儲(chǔ)存在內(nèi)存、文件、數(shù)據(jù)庫等方式

type Provider interface {

SessionInit(sid string)(Session,error)

SessionRead(sid string)(Session,error)

SessionDestroy(sid string) error

SessionGC(maxLifeTime int64)

}

//用于對(duì)session一些基本操作的定義

type Session interface {

Set(key, value interface{}) error

Get(key interface{}) interface{}

Delete(key interface{}) error

SessionID()string

}

靜態(tài)資源

靜態(tài)資源處理需要用到http.FileServer和http.StripPrefix函數(shù),http.FileServer通常要跟http.StripPrefix結(jié)合使用http.StripPrefix函數(shù)的作用之一,就是在將請(qǐng)求定向到你通過參數(shù)指定的請(qǐng)求處理處之前,將特定的prefix從URL中過濾出去。下面是一個(gè)瀏覽器或HTTP客戶端請(qǐng)求資源的例子:

/static/example.png

StripPrefix 函數(shù)將會(huì)過濾掉/static/,并將修改過的請(qǐng)求定向到http.FileServer所返回的Handler中去,因此請(qǐng)求的資源將會(huì)是:

/example.png

http.FileServer 返回的Handler將會(huì)進(jìn)行查找,并將與文件夾或文件系統(tǒng)有關(guān)的內(nèi)容以參數(shù)的形式返回給你(在這里你將"static"作為靜態(tài)文件的根目錄)。因?yàn)槟愕?example.txt"文件在靜態(tài)目錄中,你必須定義一個(gè)相對(duì)路徑去獲得正確的文件路徑。

根據(jù)需要定制訪問路徑

http.Handle("/tmpfiles/",http.StripPrefix("/tmpfiles/", http.FileServer(http.Dir("/tmp"))))

FileServer 已經(jīng)明確靜態(tài)文件的根目錄在"/tmp",但是我們希望URL以"/tmpfiles/"開頭。如果有人請(qǐng)求"/tempfiles/example.txt",我們希望服務(wù)器能將文件發(fā)送給他。為了達(dá)到這個(gè)目的,我們必須從URL中過濾掉"/tmpfiles", 而剩下的路徑是相對(duì)于根目錄"/tmp"的相對(duì)路徑。如果我們按照如上做法,將會(huì)得到如下結(jié)果:

/tmp/example.png

演示

粗略的設(shè)計(jì)了幾個(gè)API,以下就是訪問API的請(qǐng)求與響應(yīng)截圖,以下除了注冊(cè)和登錄不會(huì)去檢測(cè)session,其它API都會(huì)檢測(cè),要求登錄才可以訪問。

image
image
image
image

未登錄狀態(tài)下調(diào)用添加意見反饋接口

image

登錄狀態(tài)下調(diào)用添加意見反饋接口

image

訪問靜態(tài)資源

image

項(xiàng)目地址:https://github.com/xiaojinwei/cgo

參考:https://studygolang.com/articles/9197

https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/06.0.md

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容