项目规划
如何谁知目录结构
- gopath以及项目设置
GOPATH可以是多个目录:在window系统设置环境变量;在Linux/MacOS系统只需要输入终端命令:
export gopath=/home/astaxie/gopath
但是必须保证gopath目录下有三个目录:pkg、bin、src。新建的源码放在src目录下。
- 应用程序流程图
博客系统是基于模型-视图-控制器这一设计模式的。MVC是一种将应用程序的逻辑层和表现层进行分离的结构方式。
模型:代表数据结构,通常来说,包含取出、插入、更新数据库资料这些功能
视图:是展示给用户的信息的结构及样式。一个视图通常是一个网页,但是在GO中也可以是一个页面片段,如页头、页尾。GO实现的template包已经很好的实现了View层中的部分功能。
控制器是模型、视图以及其他任何处理HTTP请求所必须的资源之间的中介,并生成网页。
数据流:
1.main.go作为应用入口,初始化一些运行博客所需要的基本资源,配置信息,监听端口。
2.路由功能检查HTTP请求,根据URL以及method来确定谁(控制层)来处理请求的转发资源。
3.如果缓存文件存在,它将绕过通常的流程执行,将直接发送给浏览器。
4.安全监测:应用程序控制器调用之前,HTTP请求和任一用户提交的数据将被过滤。
5.控制器装在模型、核心库、辅助函数、以及任何处理特定请求所需的其他资源,控制器主要负责处理业务逻辑
6.输出视图层中渲染好的即将发送到Web浏览器中的内容,如果开启缓存,视图首先被缓存,将用于以后的常规请求。
- 目录结构:
|——main.go 入口文件
|——conf 配置文件和处理模块
|——controllers 控制器入口
|——models 数据库处理模块
|——utils 辅助函数库
|——static 静态文件目录
|——views 视图库
自定义路由器设计
- HTTP路由
HTTP路由组件负责将HTTP请求交到对应的函数处理。
路由在框架中相当于一个事件的处理器,而这个事件包括:
1.用户请求的路径path,当然还有查询串信息
2.HTTP的请求方法(method)(GET、POST、PUT、DELETE、PATCH等)
路由器就是根据用户请求的事件信息转发到相应的处理函数(控制层)。
- 默认的路由实现
GO默认的路由添加是通过函数Http.Handle和Http.HandleFunc等来添加,底层都是调用了
DefaultServerMux.Handle(pattern string,handler Handler)
,这个函数会把路由信息存储到一个map当中map[string]muxEntry
。
GO监听端口,然后接收到TCP连接就会扔给Handler来处理,参数为nil时,Handler就为http.DefaultServeMux
通过 DefaultServeMux.ServeHTTP 函数来进行调度,遍历之前存储的 map 路由信息,和用户访问的 URL 进行匹配,以查询对应注册的处理函数
for k,v := range mux.m {
if !pathMatch(k,path){
contine
}
if h == nile || len(k) > n {
n = len(k)
n = v.h
}
}
- beego框架的路由实现
目前几乎所有的Web应用路由实现都是基于http默认的路由器,但是Go自带的路由器有几个限制:
1.不支持参数设定,例如/user/:uid这种泛类型的匹配。
2.无法很好的支持REST模式,无法限制访问的方法。
3.一般网站的路由规则太多了,编写繁琐,可以通过struct的方法进行简化。
beego设计了一种REST方式的路由实现,路由设计:存储路由和转发路由
- 存储路由
针对前面所说的限制点,我们首先要解决参数支持就需要用到正则,第二和第三点我们通过一种变通的方法来解决,REST 的方法对应到 struct 的方法中去,然后路由到 struct 而不是函数,这样在转发路由的时候就可以根据 method 来执行不同的方法。
设计两个数据类型:controllerinfo(保存路径和对应的struct,这里是一个reflect.Type类型)和ControllerRegistor(routers 是一个slice用来保存用户添加的路由信息,以及beego框架的应用信息)
type controllerInfo struct {
regex *regex.Regexp
params map[int]string
controllerType reflect.Type
}
type ControllerRegister struct {
routers []*controllerInfo
Application *App
}
ControllerRegister对外的接口函数有:
func (p *ControllerRegistor) Add(pattern string,c ControllerInterface)
详细的实现:
func (p *ControllerRegistor) Add(pattern string, c ControllerInterface) {
parts := strings.Split(pattern, "/")
j := 0
params := make(map[int]string)
for i, part := range parts {
if strings.HasPrefix(part, ":") {
expr := "([^/]+)"
// a user may choose to override the defult expression
// similar to expressjs: ‘/user/:id([0-9]+)’
if index := strings.Index(part, "("); index != -1 {
expr = part[index:]
part = part[:index]
}
params[j] = part
parts[i] = expr
j++
}
}
// recreate the url pattern, with parameters replaced
// by regular expressions. then compile the regex
pattern = strings.Join(parts, "/")
regex, regexErr := regexp.Compile(pattern)
if regexErr != nil {
// TODO add error handling here to avoid panic
panic(regexErr)
return
}
// now create the Route
t := reflect.Indirect(reflect.ValueOf(c)).Type()
route := &controllerInfo{}
route.regex = regex
route.params = params
route.controllerType = t
p.routers = append(p.routers, route)
}
- 静态路由的实现
beego的静态文件夹保存在全局变量StaticDir中。staticDir是一个map类型。
func (app *App) SetStaticPath(url string,path string)
* App {
staticDir[url] = path
return app
}
实现:
beego.SetStaticPath("/img","/static/img")
- 使用入门
使用注册路由:
beego.BeeApp.RegisterController("/",&controllers.MainController{})
参数注册:
beego.BeeApp.RegisterController("/:param",&contollers.UserController{})
正则匹配:
beego.BeeApp.RegisterController("/users/:uid([0-9]+)"),&controllers.UserController{})
Controller设计
- beego的REST设计
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) // 初始化上下文和子类名称
Prepare() // 开始执行之前的一些处理
Get() // method=GET 的处理
Post() // method=POST 的处理
Delete() // method=DELETE 的处理
Put() // method=PUT 的处理
Head() // method=HEAD 的处理
Patch() // method=PATCH 的处理
Options() // method=OPTIONS 的处理
Finish() // 执行完成之后的处理
Render() error // 执行完 method 对应的方法之后渲染页面
}
Init() 初始化
Prepare() 执行之前的初始化,每个继承的子类可以来实现该函数
method() 根据不同的 method 执行不同的函数:GET、POST、PUT、HEAD等,子类来实现这些函数,如果没实现,那么默认都是403
Render() 可选,根据全局变量 AutoRender 来判断是否执行
Finish() 执行完之后执行的操作,每个继承的子类可以来实现该函数
- 应用指南
设计方法:
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"
}
上面的方式我们实现了子类 MainController,实现了 Get 方法,那么如果用户通过其他的方式 (POST/HEAD 等) 来访问该资源都将返回 405,而如果是 Get 来访问,因为我们设置了 AutoRender=true,那么在执行完 Get 方法之后会自动执行 Render 函数。
Index.tpl:
<!DOCTYPE html>
<html>
<head>
<title>beego welcome template</title>
</head>
<body>
<h1>Hello, world!{{.Username}},{{.Email}}</h1>
</body>
</html>
日志和配置设计
- beego的日志设计
beego 的日志设计部署思路来自于 seelog,根据不同的 level 来记录日志,但是 beego 设计的日志系统比较轻量级,采用了系统的 log.Logger 接口,默认输出到 os.Stdout, 用户可以实现这个接口然后通过 beego.SetLogger 设置自定义的输出,详细的实现如下所示
// Log levels to control the logging output.
const (
LevelTrace = iota
LevelDebug
LevelInfo
LevelWarning
LevelError
LevelCritical
)
// logLevel controls the global log level used by the logger.
var level = LevelTrace
// LogLevel returns the global log level and can be used in
// own implementations of the logger interface.
func Level() int {
return level
}
// SetLogLevel sets the global log level used by the simple
// logger.
func SetLevel(l int) {
level = l
}
默认的级别是 Trace,用户通过 SetLevel 可以设置不同的分级。
- beego的配置设计
beego实现了有个key=value的配置文件读取。
var (
bComment = []byte{‘#‘}
bEmpty = []byte{}
bEqual = []byte{‘=‘}
bDQuote = []byte{‘"‘}
)
定义了配置文件的格式:
// A Config represents the configuration.
type Config struct {
filename string
comment map[int][]string // id: []{comment, key...}; id 1 is for main comment.
data map[string]string // key: value
offset map[string]int64 // key: offset; for editing.
sync.RWMutex
}
- 应用指南
func GetJson() {
resp, err := http.Get(beego.AppConfig.String("url"))
if err != nil {
beego.Critical("http get info error")
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
err = json.Unmarshal(body, &AllInfo)
if err != nil {
beego.Critical("error:", err)
}
}
函数种调用beego.Critical函数用来报错,beego.AppConfig.String("url")用来获取配置文件的信息,配置文件的信息如下:
appname = hs
url = "http://www.api.com/api.html"