beego 简介

beego 是一个快速开发 Go 应用的 HTTP 框架,他可以用来快速开发 API、Web 及后端服务等各种应用。

beego 架构

beego 是基于八大独立的模块构建的,是一个高度解耦的框架。

beego模块

beego 执行逻辑

beego执行逻辑

beego 项目结构

├── conf
│   └── app.conf   
├── controllers
│   ├── admin
│   └── default.go
├── main.go
├── models
│   └── models.go
├── static
│   ├── css
│   ├── ico
│   ├── img
│   └── js
└── views
    ├── admin
    └── index.tpl

beego 工具

beego 工具是一个为了协助快速开发 beego 项目而创建的项目,您可以通过 bee 快速创建项目、实现热编译、开发测试以及开发完之后打包发布的一整套从创建、开发到部署的方案。

beego 工具安装

go get github.com/beego/bee

安装完之后,bee可执行文件默认存放在$GOPATH/bin里面。

bee 工具命令

bee 命令 [命令参数] 

new     使用beego框架创建一个程序
run     编译并运行
pack     打包
api        使用beego框架创建一个 api 应用
bale    用来压缩所有的静态文件变成一个变量申明文件,全部编译到二进制文件里面,用户发布的时候携带静态文件,包括 js、css、img 和 views。最后在启动运行时进行非覆盖式的自解压。
version    显示 bee 和 beego 的版本
migrate    应用的数据库迁移命令,主要是用来每次应用升级,降级的SQL管理

beego 快速开始

创建项目

bee new 项目名 # 在 $GOPATH/src/项目名 路径下创建新项目

运行项目

注意:运行的时候,默认的运行路径是 $GOPATH/src/项目名/

bee run 

beego-run

beego 项目主要结构

  1. 程序入口 main.go
  2. 项目配置文件 conf
  3. 控制器 controllers 包
  4. 路由 routers 包
  5. 视图/模板 view
  6. 数据库操作 models
  7. 网页静态文件js、css、img 等 存放在 static

beego 启动大致流程

  1. 注册路由 (import _路由包)
  2. 解析配置文件 + 服务配置 + 监听端口

controller 运行机制

package controllers

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

type MainController struct {
        beego.Controller
}

func (this *MainController) Get() {
        this.Data["Website"] = "beego.me"
        this.Data["Email"] = "astaxie@gmail.com"
        this.TplName = "index.tpl"
}

上面的代码显示首先我们声明了一个控制器 MainController,这个控制器里面内嵌了 beego.Controller,这就是 Go 的嵌入方式,也就是 MainController 自动拥有了所有 beego.Controller 的方法。

而 beego.Controller 拥有很多方法,其中包括 Init、Prepare、Post、Get、Delete、Head等 方法。我们可以通过重写的方式来实现这些方法,而我们上面的代码就是重写了 Get 方法。

我们先前介绍过 beego 是一个 RESTful 的框架,所以我们的请求默认是执行对应 req.Method 的方法。例如浏览器的是 GET 请求,那么默认就会执行 MainController 下的 Get 方法。这样我们上面的 Get 方法就会被执行到,这样就进入了我们的逻辑处理。(用户可以改变这个行为,通过注册自定义的函数名,更加详细的请参考路由设置)

里面的代码是需要执行的逻辑,这里只是简单的输出数据,我们可以通过各种方式获取数据,然后赋值到 this.Data 中,这是一个用来存储输出数据的 map,可以赋值任意类型的值,这里我们只是简单举例输出两个字符串。

最后一个就是需要去渲染的模板,this.TplName 就是需要渲染的模板,这里指定了 index.tpl,如果用户不设置该参数,那么默认会去到模板目录的 Controller/<方法名>.tpl 查找,例如上面的方法会去 MainController/Get.tpl。

用户设置了模板之后系统会自动的调用 Render 函数(这个函数是在 beego.Controller 中实现的),所以无需用户自己来调用渲染。

当然也可以不使用模版,直接用 this.Ctx.WriteString 输出字符串,如:

func (this *MainController) Get() {
        this.Ctx.WriteString("hello")
}

model

Web 应用中我们用的最多的就是数据库操作,而 model 层一般用来做这些操作,我们的bee new 例子不存在 Model 的演示,但是 bee api 应用中存在 model 的应用。说的简单一点,如果你的应用足够简单,那么 Controller 可以处理一切的逻辑,如果您的逻辑里面存在着可以复用的东西,那么就抽取出来变成一个模块。因此 Model 就是逐步抽象的过程,一般我们会在 Model 里面处理一些数据读取,如下是一个日志分析应用中的代码片段:

package models

import (
    "loggo/utils"
    "path/filepath"
    "strconv"
    "strings"
)

var (
    NotPV []string = []string{"css", "js", "class", "gif", "jpg", "jpeg", "png", "bmp", "ico", "rss", "xml", "swf"}
)

const big = 0xFFFFFF

func LogPV(urls string) bool {
    ext := filepath.Ext(urls)
    if ext == "" {
        return true
    }
    for _, v := range NotPV {
        if v == strings.ToLower(ext) {
            return false
        }
    }
    return true
}

所以如果您的应用足够简单,那么就不需要 Model 了;如果你的模块开始多了,需要复用,需要逻辑分离了,那么 Model 是必不可少的。

view

在前面编写 Controller 的时候,我们在 Get 里面写过这样的语句 this.TplName = "index.tpl",设置显示的模板文件,默认支持 tpl 和 html 的后缀名,如果想设置其他后缀你可以调用 beego.AddTemplateExt 接口设置,那么模板如何来显示相应的数据呢?beego 采用了 Go 语言默认的模板引擎,所以和 Go 的模板语法一样,Go 模板的详细使用方法请参考《Go Web 编程》模板使用指南

我们看看快速入门里面的代码(去掉了 css 样式):

<!DOCTYPE html>

<html>
      <head>
        <title>Beego</title>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    </head>
      
      <body>
          <header class="hero-unit" style="background-color:#A9F16C">
            <div class="container">
            <div class="row">
              <div class="hero-text">
                <h1>Welcome to Beego!</h1>
                <p class="description">
                    Beego is a simple & powerful Go web framework which is inspired by tornado and sinatra.
                <br />
                    Official website: <a href="http://{{.Website}}">{{.Website}}</a>
                <br />
                    Contact me: {{.Email}}
                </p>
              </div>
            </div>
            </div>
        </header>
    </body>
</html>

我们在 Controller 里面把数据赋值给了 data(map 类型),然后我们在模板中就直接通过 key 访问 .Website 和 .Email 。这样就做到了数据的输出。

静态文件处理

静态文件目录如下

├── static
    │   ├── css
    │   ├── img
    │   └── js

beego 默认注册了 static 目录为静态处理的目录,注册样式:URL 前缀和映射的目录(在/main.go文件中beego.Run()之前加入):

StaticDir["/static"] = "static"

beego MVC 具体流程

  1. 在监听的端口接收数据,默认监听在 8080 端口。
  2. 用户请求到达 8080 端口之后进入 beego 的处理逻辑。
  3. 初始化 Context 对象,根据请求判断是否为 WebSocket 请求,如果是的话设置 Input,同时判断请求的方法是否在标准请求方法中(“get“”post“”put“”delete“”patch“”options“”head“),防止用户的恶意伪造请求攻击造成不必要的影响。
  4. 执行 BeforeRouter 过滤器,当然在 beego 里面有开关设置。如果用户设置了过滤器,那么该开关打开,这样可以提高在没有开启过滤器的情况下提高执行效率。如果在执行过滤器过程中,responseWriter 已经有数据输出了,那么就提前结束该请求,直接跳转到监控判断。
  5. 开始执行静态文件的处理,查看用户的请求 URL 是否和注册在静态文件处理 StaticDir 中的 prefix 是否匹配。如果匹配的话,采用 http 包中默认的 ServeFile 来处理静态文件。
  6. 如果不是静态文件开始初始化 session 模块(如果开启 session 的话),这个里面大家需要注意,如果你的 BeforeRouter 过滤器用到了 session 就会报错,你应该把加入到 AfterStatic 过滤器中。
  7. 开始执行 AfterStatic 过滤器,如果在执行过滤器过程中,responseWriter 已经有数据输出了,那么就提前结束该请求,直接跳转到监控判断。
  8. 执行过过滤器之后,开始从固定的路由规则中查找和请求 URL 相匹配的对象。这个匹配是全匹配规则,即如果用户请求的 URL 是 /hello/world,那么固定规则中 /hello 是不会匹配的,只有完全匹配才算匹配。如果匹配的话就进入逻辑执行,如果不匹配进入下一环节的正则匹配。
  9. 正则匹配是进行正则的全匹配,这个正则是按照用户添加 beego 路由顺序来进行匹配的,也就是说,如果你在添加路由的时候你的顺序影响你的匹配。和固定匹配一样,如果匹配的话就进行逻辑执行,如果不匹配进入 Auto 匹配。
  10. 如果用户注册了 AutoRouter,那么会通过 controller/method 这样的方式去查找对应的 Controller 和他内置的方法,如果找到就开始执行逻辑,如果找不到就跳转到监控判断。
  11. 如果找到 Controller 的话,那么就开始执行逻辑,首先执行 BeforeExec 过滤器,如果在执行过滤器过程中,responseWriter 已经有数据输出了,那么就提前结束该请求,直接跳转到监控判断。
  12. Controller 开始执行 Init 函数,初始化基本的一些信息,这个函数一般都是 beego.Controller 的初始化,不建议用户继承的时候修改该函数。
  13. 是否开启了 XSRF,开启的话就调用 Controller 的 XsrfToken,然后如果是 POST 请求就调用 CheckXsrfCookie 方法。
  14. 继续执行 Controller 的 Prepare 函数,这个函数一般是预留给用户的,用来做 Controller 里面的一些参数初始化之类的工作。如果在初始化中 responseWriter 有输出,那么就直接进入 Finish 函数逻辑。
  15. 如果没有输出的话,那么根据用户注册的方法执行相应的逻辑,如果用户没有注册,那么就调用 http.Method 对应的方法(Get/Post 等)。执行相应的逻辑,例如数据读取,数据赋值,模板显示之类的,或者直接输出 JSON 或者 XML。
  16. 如果 responseWriter 没有输出,那么就调用 Render 函数进行模板输出。
  17. 执行 Controller 的 Finish 函数,这个函数是预留给用户用来重写的,用于释放一些资源。释放在 Init 中初始化的信息数据。
  18. 执行 AfterExec 过滤器,如果有输出的话就跳转到监控判断逻辑。
  19. 执行 Controller 的 Destructor,用于释放 Init 中初始化的一些数据。
  20. 如果这一路执行下来都没有找到路由,那么会调用 404 显示找不到该页面。
  21. 最后所有的逻辑都汇聚到了监控判断,如果用户开启了监控模块(默认是开启一个 8088 端口用于进程内监控),这样就会把访问的请求链接扔给监控程序去记录当前访问的 QPS,对应的链接访问的执行时间,请求链接等。

beego MVC

模型

不需要

View 设计

  • 基本语法

    1. go 统一使用了 {{ 和 }} 作为左右标签
    2. 使用 . 来访问当前位置的上下文
    3. 使用 $ 来引用当前模板根级的上下文
    4. 使用 $var 来访问创建的变量
  • 模板支持的 go 语言符号
{{"string"}} // 一般 string
{{`raw string`}} // 原始 string
{{'c'}} // byte
{{print nil}} // nil 也被支持
  • 模板中的 pipeline
{{. | FuncA | FuncB | FuncC}}

当 pipeline 的值等于:

  • false 或 0
  • nil 的指针或 interface
  • 长度为 0 的 array, slice, map, string

那么这个 pipeline 被认为是空

// if ... else ... end
// if 判断时,pipeline 为空时,相当于判断为 False
{{if pipeline}}{{end}}

// 条件
{{if .IsHome}}
{{else}}
    {{if .IsAbout}}{{end}}
{{end}}

// 循环
{{range pipeline}}{{.}}{{end}}

// pipeline 支持的类型为 array, slice, map, channel


/* demo */
// 针对
pages := []struct {
    Num int
}{{10}, {20}, {30}}
this.Data["Total"] = 100
this.Data["Pages"] = pages

// 使用 .Num 输出子元素的 Num 属性,使用 $. 引用模板中的根级上下文
{{range .Pages}}
    {{.Num}} of {{$.Total}}
{{end}}

// 使用创建的变量,在这里和 go 中的 range 用法是相同的。
{{range $index, $elem := .Pages}}
    {{$index}} - {{$elem.Num}} - {{.Num}} of {{$.Total}}
{{end}}

// range 支持 else
{{range .Pages}}
{{else}}
    {{/* 当 .Pages 为空 或者 长度为 0 时会执行这里 */}}
{{end}}

// with ... end
{{with pipeline}}{{end}}

// with 用于重定向 pipeline
{{with .Field.NestField.SubField}}
    {{.Var}}
{{end}}

// 对变量赋值操作
{{with $value := "My name is %s"}}
    {{printf . "slene"}}
{{end}}

// with 支持 else
{{with pipeline}}
{{else}}
    {{/* 当 pipeline 为空时会执行这里 */}}
{{end}}

// define 用来自定义模板
{{define "loop"}}
    <li>{{.Name}}</li>
{{end}}

// 使用 template 调用模板
<ul>
    {{range .Items}}
        {{template "loop" .}}
    {{end}}
</ul>

// 直接载入文件模板
{{template "path/to/head.html" .}}

// 多行注释
{{/* comment content
support new line */}}

// 基本函数
// 变量可以使用 | 在函数间传递
{{.Con | markdown | addlinks}}
{{.Name | printf "%s"}}

// 使用括号
{{printf "nums is %s %d" (printf "%d %d" 1 2) 3}}

// and 会逐一判断每个参数,将返回第一个为空的参数,否则就返回最后一个非空参数
{{and .X .Y .Z}}

// call
// call 可以调用函数,并传入参数
// 调用的函数需要返回 1个值 或者 2个值,返回两个值时,第二个值用于返回 error 类型的错误。返回的错误不等于 nil 时,执行将终止。
{{call .Field.Func .Arg1 .Arg2}}

// index 读取指定类型对应下标的值, 支持 map、slice、array、string
this.Data["Maps"] = map[string]{"name": "Beego"}
{{index .Maps "name"}}

// len 返回对应类型的长度,支持类型:map, slice, array, string, chan
{{printf "The content length is %d" (.Content|len)}}

// not 返回输入参数的否定值

// or 
{{or .X .Y .Z}}

// print 对应 fmt.Sprint
// printf 对应 fmt.Sprintf
// println 对应 fmt.Sprintln

// eq / ne / lt / le / gt / ge