Go后台项目实战

本项目完全使用原生开发,没有使用任何WEB框架(如:gin,beego,Martini等),和ORM(如:gorm,xorm,beego)

三层架构

三层架构(3-tier architecture) 通常意义上的三层架构就是将整个业务应用划分为:界面层(User Interface layer)、业务逻辑层(Business Logic Layer)、数据访问层(Data access layer)。区分层次的目的即为了“高内聚低耦合”的思想。在软件体系架构设计中,分层式结构是最常见,也是最重要的一种结构。

控制层/界面层

因为我的项目中并没有写WEB页面,所以就拿控制层来说,就是将你的请求从页面传到后台代码

服务层/业务逻辑层

针对具体问题的操作,也可以说是对数据层的操作,对数据业务逻辑处理。(关键在于由原始数据抽象出逻辑数据)能够提供interface\API层次上所有的功能。,“中间业务层”的实际目的是将“数据访问层”的最基础的存储逻辑组合起来,形成一种业务规则

持久层/数据访问层

该层所做事务直接操作数据库,针对数据的增添、删除、修改、查找等。(关键在于粒度的把握)要保证“数据访问层”的中的函数功能的原子性!即最小性和不可再分。“数据访问层”只管负责存储或读取数据就可以了。

Controller组合封装

Controller"基类"封装,主要提供了一个保存文件的方法,主要用于form-data请求


import(

"io"

"mime/multipart"

"net/http"

"path"

)

constBASE_IMAGE_ADDRESS ="./img/"

typeControllerstruct{

Datainterface{}

}

typeFileInfoTOstruct{

//图片id -- 暂时没有用

IDint64

//缩略图路径 -- 暂时没有用

CompressPathstring

//原图路径 ,保存数据库的路径

Pathstring

//原始的文件名

OriginalFileNamestring

//存储文件名 如:uuidutil

FileNamestring

//文件大小

FileSizeint64

}

//获取上传文件的数量

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)是什么,都会解析,否则只会解析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]//根据上传文件时指定的字段名(filename)获取FileHeaders

for_,fileHeader :=rangefileHeaders{

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

fileInfos =append(fileInfos,to)

}

}

}

returnfileInfos

}

//保存单个文件

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"

)

//创建多级目录

funcMkDirAll(pathstring)bool{

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

iferr !=nil{

returnfalse

}

returntrue

}

//检测文件夹或文件是否存在

funcExist(filestring)bool{

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

returnfalse

}

returntrue

}

//获取文件的类型,如:.jpg

//如果获取不到,返回默认类型defaultExt

funcExt(fileNamestring,defaultExtstring)string{

t := path.Ext(fileName)

iflen(t) ==0{

returndefaultExt

}

returnt

}

/// 检验文件夹是否存在,不存在 就创建

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主要用于用户体系的一个登陆状态的信息获取,根据请求中的session获取服务端保存的用户信息,如果你的后台分用户体系和管理端用户体系,并且这两个用户体系分别存储在两个表中,这时你还可以定义一个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

持久层的实现:https://blog.csdn.net/cj_286/article/details/80363796

http

http server的实现:https://blog.csdn.net/cj_286/article/details/80256988

Router

路由处理的实现,其实也就是一个转发的功能


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

}

//静态资源

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来记住用户的状态,以下是session技术实现的主要类型与接口定义,(摘自:https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/06.0.md),会话过期时间可以自行设置,如果设置为一小时,在停止会话一小时后session就会过期,该session就会被自动删除,如果回话都保持在一小时之内就可以一直访问,session不会过期


//主要用于session的管理,过期处理等

type Manager struct {

cookieName string

lock sync.Mutex

provider Provider

maxLifeTime int64

}

//用于提供session存储方式的一个接口标准,可以用于提供session储存在内存、文件、数据库等方式

type Provider interface {

SessionInit(sid string)(Session,error)

SessionRead(sid string)(Session,error)

SessionDestroy(sid string) error

SessionGC(maxLifeTime int64)

}

//用于对session一些基本操作的定义

type Session interface {

Set(key, value interface{}) error

Get(key interface{}) interface{}

Delete(key interface{}) error

SessionID()string

}

静态资源

静态资源处理需要用到http.FileServer和http.StripPrefix函数,http.FileServer通常要跟http.StripPrefix结合使用http.StripPrefix函数的作用之一,就是在将请求定向到你通过参数指定的请求处理处之前,将特定的prefix从URL中过滤出去。下面是一个浏览器或HTTP客户端请求资源的例子:

/static/example.png

StripPrefix 函数将会过滤掉/static/,并将修改过的请求定向到http.FileServer所返回的Handler中去,因此请求的资源将会是:

/example.png

http.FileServer 返回的Handler将会进行查找,并将与文件夹或文件系统有关的内容以参数的形式返回给你(在这里你将"static"作为静态文件的根目录)。因为你的"example.txt"文件在静态目录中,你必须定义一个相对路径去获得正确的文件路径。

根据需要定制访问路径

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

FileServer 已经明确静态文件的根目录在"/tmp",但是我们希望URL以"/tmpfiles/"开头。如果有人请求"/tempfiles/example.txt",我们希望服务器能将文件发送给他。为了达到这个目的,我们必须从URL中过滤掉"/tmpfiles", 而剩下的路径是相对于根目录"/tmp"的相对路径。如果我们按照如上做法,将会得到如下结果:

/tmp/example.png

演示

粗略的设计了几个API,以下就是访问API的请求与响应截图,以下除了注册和登录不会去检测session,其它API都会检测,要求登录才可以访问。

image
image
image
image

未登录状态下调用添加意见反馈接口

image

登录状态下调用添加意见反馈接口

image

访问静态资源

image

项目地址: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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,504评论 4 365
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,898评论 1 300
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,218评论 0 248
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,322评论 0 214
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,693评论 3 290
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,812评论 1 223
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,010评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,747评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,476评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,700评论 2 251
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,190评论 1 262
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,541评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,206评论 3 240
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,129评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,903评论 0 199
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,894评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,748评论 2 274

推荐阅读更多精彩内容