本项目完全使用原生开发,没有使用任何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都会检测,要求登录才可以访问。
未登录状态下调用添加意见反馈接口
登录状态下调用添加意见反馈接口
访问静态资源
项目地址: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
有疑问加站长微信联系(非本文作者)