Beego

bee工具

介绍

bee 工具 本是一个为了协助快速开发 beego 项目而创建的项目,
通过 bee 可以快速创建项目、实现热编译、开发测试以及开发完之后打包发布的一整套从创建、开发到部署的方案。
go get github.com/beego/bee
安装完之后,bee可执行文件 配置成环境变量。

常用命令

bee version ===》 查看bee工具版本信息。
bee new 项目名 ===》 创建一个beego项目。
bee api 项目名 ===》 创建一个beego专做api的项目。
bee run ===》 热编译,自动运行go文件中的 main函数。

1. 项目配置

beego 默认会解析当前应用下的 conf/app.conf 文件。
通过这个文件你可以初始化很多 beego 的默认参数:

常用配置项

appname = drugDataCollect
httpaddr = "127.0.0.1"
httpport = 6666

autorender = false
copyrequestbody = true

runmode = dev

[dev]
mysqluser = "root"
mysqlpassword = "123456"
mysqlurls = "127.0.0.1"
mysqldb   = "drug_store_data_collect"

redisaddr = "127.0.0.1"
redisport = "6379"
redispassword = ""
redisdb = 1

[prod]
mysqluser = "root"
mysqlpassword = "123456"
mysqlurls = "127.0.0.1"
mysqldb   = "drug_store_data_collect"

[test]
mysqluser = "root123test"
mysqlpassword = "123456"
mysqlurls = "127.0.0.1"
mysqldb   = "gin"

使用 配置文件 中的数据

beego.AppConfig.String("mysqluser")
beego.AppConfig.Int("redisdb")

打包说明

  • step1 生产环境

首先把conf/app.conf文件修改为 运行环境 prod

runmode = prod
  • step2 项目打包

方式一: 使用bee工具

  • 打包到Linux系统中使用
bee pack -be GOOS=linux 
  • 打包到window系统中使用
bee pack -be GOOS=windows

然后将打包好的压缩包 传至服务器中 解压运行。

  • 方式二: 使用go命令
go build 

将生成的可执行文件 连同 项目配置文件conf 以及一些静态资源文件,一同上传到服务器中,运行可执行文件。

2. 路由

根路由必须以 ‘/’ 开头。

单路由

适合的路由处理风格: 一个url、一个结构体、多个请求方法

beego.Router("/api/pharmacy", &controllers.Pharmacy{})
beego.Router("/api/upfile", &controllers.UpLoadFile{})
beego.Router("/api/cash_machine", &controllers.Cash{})

路由组

适合的路由处理风格: 多个url、一个结构体、一个请求方法

// 通过 NewNamespace 创建路由组
user := beego.NewNamespace("/user",
	beego.NSRouter("login", &controllers.User{}),
	beego.NSRouter("register", &controllers.User{}),
	beego.NSRouter("repassword", &controllers.User{}),
)

// 通过 NSNamespace 深层嵌套
group := beego.NewNamespace("/a",
	beego.NSNamespace("/bb",
		beego.NSNamespace("/ccc",
			beego.NSRouter("/dddd", &controllers.Aaa{})))
)

// 路由组 钩子
group := beego.NewNamespace("/a",
    	// 可理解为:前置中间件,如果返回true则可以达到后面的路由,false 则拒绝访问后面的路由
		beego.NSCond(func(ctx *context.Context) bool {
			fmt.Println("NSCond")
			return true
		}),

		//NSCond 之后的钩子
		beego.NSBefore(func(ctx *context.Context) {
			fmt.Println("NSBefore")
		}),

	    beego.NSRouter("/dddd", &controllers.Aaa{})
)

// 所有的 路由组信息 都要在这里 进行注册
beego.AddNamespace(user,group)

指定路由

beego.Router("/aaa", &controllers.Wtt{}, "get:Abc")

get的方式访问/aaa路由,对应的处理函数 不再是Wtt上的Get方法,而是被指定为Wtt上的Abc方法。

静态路由

beego.SetStaticPath("imgs", "static") //imgs是访问的路径前缀,第二个是工程目录下的静态文件目录

例如: 在工程目录下的静态文件static中有一个有一个aaa文件夹,aaa文件有一个abc.jpg的图片,那个这个图片的访问路径是:/imgs/aaa/abc.png

广角路由

package main

import (
	"fmt"
	"github.com/beego/beego/v2/server/web/context"
	beego "github.com/beego/beego/v2/server/web"
)
func Test(ctx *context.Context) {
	fmt.Println("我是 中间件")
	ctx.Output.Body([]byte{'o', 'k'})
}

func main() {
	beego.Any("*", Test) // 广角路由
	beego.Run("localhost:8088")
}
  • 如果 命中了路由表中的路由,则执行指定的 controller
  • 如果没有命中 路由表中的路由,就执行广角路由。
  • 这里使用的是beego 2版本

自动路由

beego.AutoRouter(&Test{}) 

type Test struct {
	beego.Controller
}

//访问路径: /test/abc  请求方法:any
func (that Test) Abc() { 
	that.Ctx.Output.JSON(123, false, false)
}

3. 中间件

Filter(中间件)

  • 引入beego 的 content包
    中间件 在 beego中 叫 过滤器 函数
    值得注意的是:
    每个中间件都一个content类型的参数,一定要手动引入该包
    “github.com/astaxie/beego/context”
    如果通过 保存操作 自动引入的话,会引入官方的 content包,
    因为 go 官方 的content的包 优先 第三方的 content 的包的引入。
  • 注册中间件 的 三个参数
    所有的 中间件 都要放到 InsertFilter 函数中,其有三个参数:

参1: 中间件守卫的路由
参2: 决定中间件执行的时机
五个固定实参如下,分别表示不同的执行过程:
beego.BeforeStatic ==》静态地址之前
beego.BeforeRouter ==》寻找路由之前
beego.BeforeExec ==》找到动态路由之后,开始执行相应的 Controller 之前
beego.AfterExec ==》执行完 Controller 逻辑之后执行的过滤器
beego.FinishRouter ==》执行完所有逻辑之后执行的过滤
参3: 指定让哪个中间件工作

  • 执行顺序
    多个中间件守卫一个路由,其执行顺序:
    先由InsertFilter函数的的第二个参数决定,
    如果第二个参数一直 则 由于 InsertFilter函数的 先后顺序 决定:
    beego.InsertFilter("/aaa",beego.BeforeRouter, 中间件1)
    beego.InsertFilter("/aaa",beego.BeforeRouter, 中间件2)

注意: 如果 任何一个 过滤器 中有向前端 返回数据 的动作, 则该 过滤器函数 执行周期之后的 过滤器函数 就不再执行了。

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

func Test(ctx *context.Context) {
	fmt.Println("我是 中间件")
}
func Test111(ctx *context.Context) {
	fmt.Println("我是 中间件111")
}

beego.InsertFilter("/test", beego.BeforeRouter, Test)
beego.InsertFilter("/test", beego.BeforeRouter, Test111)
// 正则路由
beego.InsertFilter("/*", beego.BeforeRouter, Test) // 所有请求
beego.InsertFilter("/user([0-9]+)", beego.BeforeRouter, Test) // ()里面中写入正则

中间件间、中间件和路由处理函数间 传值

// 路由表
{
    beego.Router("/a", &Test{}, "post:Post1")
    beego.InsertFilter("/*", beego.BeforeRouter, m)
    beego.InsertFilter("/*", beego.BeforeRouter, m2)
}
// 中间件
{
    // 中间件
    func m(ctx *context.Context) {
        // 设置 要传值的值
        ctx.Input.SetData("key", "value")

    	fmt.Println("我是 中间件")
    }

    // 中间件2
    func m2(ctx *context.Context) {
        // 获取 传值的值
	    res := ctx.Input.GetData("key")
        // 设置 要传值的值
        ctx.Input.SetData("key", "value123")

    	fmt.Println("我是 中间件2")
    }
}
// 路由处理函数
{
    type Test struct {
	    beego.Controller
    }

    func (that Test) Post1() {
        // 获取 传值的值
    	res := that.Ctx.Input.GetData("key")
    	fmt.Println(res)

    	that.Ctx.Output.JSON(res, false, false)
    }
}

// 输出:
value
value123

FilterChain(中间件回流)

// 路由表
{
    beego.InsertFilter("/*", beego.BeforeRouter, m)
    beego.InsertFilter("/*", beego.BeforeRouter, m2)

    // 不论 对 前端是否有回复 动作,都可以回到这里
    beego.InsertFilterChain("/*", func(next beego.FilterFunc) beego.FilterFunc {
    	return func(ctx *context.Context) {
    		fmt.Println(1111111111)
    		next(ctx)
    		fmt.Println(2222222222222)
    	}
    })
}
// 中间件
{
    // 中间件
    func m(ctx *context.Context) {
    	fmt.Println("我是 中间件")
    }

    // 中间件2
    func m2(ctx *context.Context) {
    	fmt.Println("我是 中间件2")
    }
}

// 输出:
/*
    1111111111
    我是 中间件
    我是 中间件2
    2222222222222
*/

允许跨域中间件

package main

import (
	beego "github.com/beego/beego/v2/server/web"
	"github.com/beego/beego/v2/server/web/filter/cors"
)

func init() {
	//InsertFilter是提供一个过滤函数
	beego.InsertFilter("*", beego.BeforeRouter, cors.Allow(&cors.Options{
		// 允许访问所有源
		AllowAllOrigins: true,
		// 可选参数"GET", "POST", "PUT", "DELETE", "OPTIONS" (*为所有)
		AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
		// 指的是允许的Header的种类
		AllowHeaders: []string{"Origin", "Authorization", "Access-Control-Allow-Origin", "Access-Control-Allow-Headers", "Content-Type"},
		// 公开的HTTP标头列表
		ExposeHeaders: []string{"Content-Length", "Access-Control-Allow-Origin", "Access-Control-Allow-Headers", "Content-Type"},
		// 如果设置,则允许共享身份验证凭据,例如cookie
		AllowCredentials: true,
	}))
}

4. 控制器

对于每个请求,beego 都会采用单独的 goroutine 来处理
原理模拟解释:

beego.Run() 
// 其Run方法的核心原理为下述代码:
for {
	conn := 监听前端连接
	go Hander() // 开启协程处理函数 
}

基于 beego 的 Controller 结构体 设计,只需要匿名组合 beego.Controller 就可以了

package controllers

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

type Airplane struct {
	beego.Controller
}


func (that Airplane) GuguAirplaneChatAutodel() {
	inData := struct {
		Uid int `json:"uid" validate:"required"`
	}{}
	err := json.Unmarshal(that.Ctx.Input.RequestBody, &inData)
	if err != nil {
		tools.Failed(that.Ctx, "请求参数异常"+err.Error())
		return
	}
	err = tools.Check.Struct(inData)
	if err != nil {
		tools.Failed(that.Ctx, err.Error())
		return
	}

	friends := make([]models.AirplaneFriend, 0, 50)
	err = db.DB().Where("uid = ? and deleted = 2 and user_know = 0", inData.Uid).Find(&friends).Error
	if err != nil {
		err := fmt.Errorf("异常:%v", err)
		tools.Failed(that.Ctx, err.Error())
		return
	}
	tools.Success(that.Ctx, friends)
}

控制器结构体上的方法

钩子

  • Prepare()

这个函数会在 这些 Method 方法之前执行,用户可以重写(重写:在继承的结构体上 重名覆盖这个方法)这个函数实现类似用户验证之类。

  • Finish()

这个函数是在执行完相应的 HTTP Method 方法之后执行的,默认是空,用户可以在子 struct 中重写这个函数,执行例如数据库关闭,清理数据之类的工作

请求方法

  • Get()

如果用户请求的 HTTP Method 是 GET,那么就执行该函数,默认是 405,用户继承的子 struct 中可以实现了该方法以处理 Get 请求。

  • Post()

如果用户请求的 HTTP Method 是 POST,那么就执行该函数,默认是 405,用户继承的子 struct 中可以实现了该方法以处理 Post 请求。

  • Delete()

如果用户请求的 HTTP Method 是 DELETE,那么就执行该函数,默认是 405,用户继承的子 struct 中可以实现了该方法以处理 Delete 请求。

  • Put()

如果用户请求的 HTTP Method 是 PUT,那么就执行该函数,默认是 405,用户继承的子 struct 中可以实现了该方法以处理 Put 请求.

  • 其他: Head()、Patch()、Options()、Finish()、Trace()

操控处理

  • Redirect(url string, code int)

重定向。url是目的地址。

  • Abort(code string)

中断当前方法的执行,直接返回该状态码,类似于CustomAbort。

报文

  • 请求报文: Ctx.Input
  • 响应报文: Ctx.Output

5. 前后交互

  • context 对象 是对 InputOutput 的封装,
  • context 对象 是 中间件函数 唯一的 参数对象,这样你就可以通过 中间件 来修改相应的数据,或者提前结束整个的执行过程。

请求报文

Input 对象是针对 request(请求报文) 的封装,里面实现很多方便的方法,具体如下:

协议、域名、

  • Input.Protocol()

获取用户请求的协议,例如 HTTP/1.1

  • Scheme()

请求的 scheme,例如 “http” 或者 “https”

  • Domain()

请求的域名,例如 beego.me

  • Port()

返回请求的端口,例如返回 8080

  • Input.URL()

请求的 URL 地址,例如 /hi


  • IP()

返回请求用户的 IP,如果用户通过代理,一层一层剥离获取真实的 IP

  • Proxy()

返回用户代理请求的所有 IP

  • UserAgent()

返回请求的 UserAgent,例如 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36


  • IsSecure()

判断当前请求是否 HTTPS 请求,是返回 true,否返回 false

  • IsWebsocket()

判断当前请求是否 Websocket 请求,如果是返回 true,否返回 false

  • IsAjax()

判断是否是 AJAX 请求,如果是返回 true,不是返回 false

获取数据

Get方法 的 请求参数
  • Param()

获取那些在路由上 早早定义好 的参数:

// 路由
beego.Router("/aaa/bbb/:name", &controllers.MainController{})

// url: http://localhost/aaa/bbb/tom

// 控制器
res := c.Ctx.Input.Param(":name") // tom
  • Query()

获取Get方法的请求参数: http://localhost/aaa/bbb?name=tom

Post、Put 的 请求体
  • RequestBody

解析 请求体为 json 和 xml 格式的数据
在配置文件里设置 copyrequestbody = true , 否则 RequestBody 拿不到数据

// var m map[string]interface{} 用map也可以,但是语义性不强。
reqObj := struct {
	Account  string
	Password string
}{}
err := json.Unmarshal(that.Ctx.Input.RequestBody, &reqObj)
if err != nil {
	fmt.Println(err)
}
fmt.Println(reqObj)
  • ParseForm

解析 请求体为 form-data 和 x-www-form-urlencode 格式的数据

type user struct {
	Id    int         `form:"-"`        // 忽略Id字段(一:字段名小写开头,二:form 标签的值设置为 -)
	Name  interface{} `form:"username"` // 只接受 username 字段
	Age   int         `form:"age"`      // 只接受 age 字段
	Email string      // 只接受 Email 字段
}
u := user{}
// 解析请求体为 form-data 和 x-www-form-urlencode 格式的数据
err := that.ParseForm(&u)
if err != nil {
	fmt.Println(err)
}
其他重点
  • GetString(“name”)、GetInt(key string)、GetBool(key string)、GetFloat(key string)

获取:
post:form-datax-www-form-urlencode
get:?之后的参数
注意:此方法无法获取 post的json格式的数据

  • Header()

返回相应的 header 信息,例如 Header(“Accept-Language”)

  • Cookie()

返回请求中的 cookie 数据,例如 Cookie(“username”),就可以获取请求头中携带的 cookie 信息中 username 对应的值

文件传输
  • beego.BConfig.MaxMemory= 123 (文件 缓存内存)

文件上传之后一般是放在系统的内存里面,
如果文件的 size 大于设置的缓存内存大小,那么就放在 临时文件 中,
默认的缓存内存是 64M,可以通过在配置文件中通过如下设置:

maxmemory = 1<<22
相应的也可以在代码中如下设置:

// 单位B
beego.BConfig.MaxMemory = 123
  • beego.BConfig.MaxUploadSize = 234 (限制最大上传文件大小)
// 单位B, 4530 B == 4.5 K
beego.BConfig.MaxUploadSize = 4530

与此同时,beego 提供了另外一个参数,MaxUploadSize来限制最大上传文件大小——如果你一次长传多个文件,那么它限制的就是这些所有文件合并在一起的大小。
默认情况下,MaxMemory应该设置得比MaxUploadSize小,这种情况下两个参数合并在一起的效果则是:

  1. 如果文件大小小于MaxMemory,则直接在内存中处理;
  2. 如果文件大小介于MaxMemory和MaxUploadSize之间,那么比MaxMemory大的部分将会放在临时目录;
  3. 文件大小超出MaxUploadSize,直接拒绝请求,返回响应码 413
  • IsUpload()

判断当前请求是否有文件上传,有返回 true,否返回 false
实质上是判断接收的数据是否是 form-data 类型的,如果是,哪怕是文字数据也返回true。

  • GetFile() 和 SaveToFile()

获取**form-data** 类型的 文件数据

func (that UpLoadFile) Post() {
	f, h, err := that.GetFile("filename")
	id := that.GetString("id")
	name := that.GetString("name")

	if err != nil {
		fmt.Println(err)
		tools.Failed(that.Ctx, "文件获取失败")
		return
	}
	defer f.Close()

    // 文件名
    fmt.Println(h.Filename)
    // 文件大小,单位B, h.Size / 1024 = KB
	fmt.Println(h.Size)

	uuid := uuid.New() // uuid(32位的随机 数组or字母)是文件名不重复的一个保证
	// 该路径上的涉及到的 文件夹 提前创建好
	var path string = "static/upload/" + id + "-" + name + "-" + uuid + h.Filename
	
    // 该方法是在 GetFile 的基础上实现了快速保存的功能
    err = that.SaveToFile("filename", path)
	if err != nil {
		fmt.Println(err)
		tools.Failed(that.Ctx, "文件上传失败")
	} else {
		// 将 静态路由 返回给 前端
		tools.Success(that.Ctx, path)
	}
	return
}
  • 多文件上传
// 多文件上传
func (that File) UploadFiles() {
	resM := make(map[string]string, 10)
	// this is core
	allUpFiles := that.Ctx.Request.MultipartForm.File

	for k, _ := range allUpFiles {
		res := upload(that, k)
		resM[k] = res
	}
	tools.Failed(that.Ctx, resM)
}

func upload(that File, filename string) string {
	f, h, err := that.GetFile(filename)
	if err != nil {
		tools.Failed(that.Ctx, "文件获取失败")
		return ""
	}
	defer f.Close()

	uuid := uuid.New()

	var path string = "static/upload/" + uuid + h.Filename
	err = that.SaveToFile(filename, path)

	if err != nil {
		fmt.Println(err)
		tools.Failed(that.Ctx, "文件上传失败")
	}
	ddr, err := beego.AppConfig.String("httpaddr")
	if err != nil {
		panic("配置文件中 httpaddr 字段提取异常")
	}
	port, err := beego.AppConfig.String("httpport")
	if err != nil {
		panic("配置文件中 httpport 字段提取异常")
	}
	path = ddr + ":" + port + "/" + path
	return path
}

响应报文

Output 是针对 Response(响应报文) 的封装,里面提供了很多方便的方法:

  • Body() => Ctx.Output.Body([]byte(“see you”))

将 参数数据 放到响应体中,并返回给前端

  • JSON() => Ctx.Output.JSON(map或struct, false, false)

把 Data 格式化为 Json,然后调用 Body() 输出数据到前端

  • Header()

设置输出的 header 信息,例如 Header(“Server”,“beego”)

  • Cookie()

将cookie写入到前端浏览器中,例如 Cookie(“name”,“tom”)

6. 数据模型

请转到我的GORM那一篇。

7. 会话管理

Cookie

  • 设置: Ctx.Output.Cookie(“name”, “tom123”, 1) //参数3的单位:分钟
  • 获取: Ctx.Input.Cookie(“name”)
  • 删除: Ctx.Output.Cookie(“name”, “tom123”, -1)

Session

这里用 token + cookie 的顶替 session,
session的不足:

  1. CSRF跨站伪造请求攻击session是基于cookie进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击
  2. 扩展性不强,如果将来搭建了多个服务器,虽然每个服务器都执行的是同样的业务逻辑,但是session数据是保存在内存中的(不是共享的),用户第一次访问的是服务器1,当用户再次请求时可能访问的是另外一台服务器2,服务器2获取不到session信息,就判定用户没有登陆过。

8. 日志记录

step1: 启用日志

在main函数中写入: log.UseLog()

step2: 创建日志实例 及 相关配置

package log

import "github.com/astaxie/beego/logs"

func UseLog() {
	log := logs.NewLogger(10000) // 创建一个日志实例,参数为缓冲区的大小
	defer log.Close()

	// 设置配置文件
	// filename 文件名(不指定路径,就生成在主目录); maxlines 最大行; maxsize 最大Size;
	jsonConfig := `{
        "filename" : "test.log",
        "maxlines" : 1000,      
        "maxsize"  : 10240      
    }`
	log.SetLogger("file", jsonConfig) // 设置日志记录方式!!!:本地文件记录
	log.SetLevel(logs.LevelDebug)     // 设置可以 写入 日志 的 错误的 等级
	log.EnableFuncCallDepth(true)     // 输出log时能显示输出文件名和行号(非必须)

	// 通过主动让代码 触发 一些有等级的错误 ,已方便测试 test.log 对错误 的记录
	log.Emergency("Emergency")
	log.Alert("Alert")
	log.Critical("Critical")
	log.Error("Error")
	log.Warning("Warning")
	log.Notice("Notice")
	log.Informational("Informational")
	log.Debug("Debug")

	log.Flush() // 将日志从缓冲区读出,写入到文件

}

/*
log.SetLogger 是 设置日志记录方式:
beego框架之日志模块默认支持4种记录方式:
  1. 终端输出(console) :这种方式一般用在开发环境下面,方便调试。
  2. 本地文件(file)       :这种方式一般用来保存常规日志,为生产环境中常用的方式。
  3. 网络方式(network):这种方式可以用来将日志发送到指定服务器,一般可以用来根据日志触发事件等。
  4. 发送邮件(email)   :这种方式一般是将生产环境下比较重要的日志发送给相应的管理人员,以便及时发现和解决问题。



log.SetLevel 是 设置 可以被记录的错误的 等级的:
beego框架之日志模块等级定义在github.com/astaxie/beego/logs/log.go:(级别以此递减)
  const (
    LevelEmergency = iota // 紧急级别
    LevelAlert                   // 报警级别
    LevelCritical                // 严重错误级别
    LevelError                   // 错误级别
    LevelWarning              // 警告级别
    LevelNotice                 // 注意级别
    LevelInformational       // 报告级别
    LevelDebug                 // 除错级别
  )
*/

9. 错误处理

在做 Web 开发的时候,经常需要页面跳转和错误处理,beego 这方面也进行了考虑,
通过 控制器 上的 RedirectAbort 方法来进行 跳转 或 中断。

10. 安全防护

  • XSRF攻击
    跨站请求伪造(Cross-site request forgery), 是 Web 应用中常见的一个安全问题。
    防御策略: 当前防范 XSRF 的一种通用的方法,是对每一个用户都记录一个无法预知的 cookie 数据,然后要求所有提交的请求(POST/PUT/DELETE)中都必须带有这个 cookie 数据。如果此数据不匹配 ,那么这个请求就可能是被伪造的。

11. 常备工具

Mysql

package tools

import (
	"os"

	"github.com/astaxie/beego"
	"github.com/fatih/color"
	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/mysql"
)

//DB 数据库变量
var DB *gorm.DB

//DBinit 初始化数据库
func DBinit() *gorm.DB {

	arr1 := "mysql"
	arr2 := beego.AppConfig.String("mysqluser") +
		":" + beego.AppConfig.String("mysqlpassword") +
		"@(" + beego.AppConfig.String("mysqlurls") + ")/" +
		beego.AppConfig.String("mysqldb") +
		"?charset=utf8mb4&parseTime=True&loc=Local"

	// var arr3 string = "root:123456@(127.0.0.1)/drug_store_data_collect?charset=utf8mb4&parseTime=True&loc=Local"

	db, err := gorm.Open(arr1, arr2)

	if err != nil {
		color.Red("连接数据库失败", err)
		os.Exit(3)
	} else {
		d := color.New(color.FgCyan, color.Bold)
		d.Printf("mysql 连接成功 \n")
	}
	db.SingularTable(true) // 不允许给 表名 加复数形式

	DB = db
	return db
}

Redis

package tools

import (
	"time"

	"github.com/astaxie/beego"
	"github.com/garyburd/redigo/redis"
)

var Pool *redis.Pool

func RedisInit() {
	// 连接池
	Pool = &redis.Pool{
		//最大闲置连接数
		MaxIdle: 20,

		//最大活动连接数, 0==无穷
		MaxActive: 1000,

		//闲置连接的超时时间 (如果开辟的IO 100秒之内什么都不干,就断定为闲置)
		IdleTimeout: time.Second * 100,

		//定义获得连接的 函数
		Dial: func() (redis.Conn, error) {
			// return redis.Dial("tcp", "127.0.0.1:6379", redis.DialDatabase(1)) // 没有密码的情况
			// return redis.Dial("tcp", "127.0.0.1:6379", redis.DialPassword("123456"), redis.DialDatabase(1)) // 有密码的情况

			protocol := "tcp"
			url := beego.AppConfig.String("redisaddr") + ":" + beego.AppConfig.String("redisport")
			dbIndex, _ := beego.AppConfig.Int("redisdb")
			return redis.Dial(protocol, url, redis.DialDatabase(dbIndex))
		},
	}
	// defer Pool.Close() // 注意: 连接池要一直开着,关闭了 就从里面取不出 io了,就是去了 连接池的意义了。

}
// 使用的时候 直接 从池中 取出一个 IO
// 	conn := Pool.Get()
// 	defer conn.Close()
// conn.Do("set", arr1, arr2)

Token

package tools

import (
	"time"

	"github.com/dgrijalva/jwt-go"
)

var jwtKey = []byte("wtt")

//Claims token结构体
type Claims struct {
	Username string `json:"username"`
	Password string `json:"password"`
	jwt.StandardClaims
}

//GetToken 产生token 给前端
func GetToken(username, password string) (string, error) {
	expireTime := time.Now().Add(1 * time.Hour) // 有效时间一个小时
	claims := &Claims{
		Username: username,
		Password: password,
		StandardClaims: jwt.StandardClaims{
			ExpiresAt: expireTime.Unix(),
			IssuedAt:  time.Now().Unix(),
			Issuer:    "GoGin",
			Subject:   "wtt",
		},
	}
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	tokenString, err := token.SignedString(jwtKey)
	return tokenString, err
}

//ParseToken 解析前端传过来的 token
func ParseToken(tokenString string) (*jwt.Token, *Claims, error) {
	claims := &Claims{}

	arr1 := tokenString
	arr2 := claims
	arr3 := func(token *jwt.Token) (i interface{}, err error) {
		return jwtKey, nil
	}
	token, err := jwt.ParseWithClaims(arr1, arr2, arr3)
	return token, claims, err
}

图形验证

package tools

import (
	"fmt"
	"image/color"

	"github.com/astaxie/beego/context"
	"github.com/garyburd/redigo/redis"
	"github.com/mojocn/base64Captcha"
)

type CaptchaResult struct {
	Id          string `json:"id"`
	Base64Blob  string `json:"base_64_blob"`
	VerifyValue string `json:"code"`
}

// 配置验证码信息
var captchaConfig = base64Captcha.DriverString{
	Height:          50,
	Width:           100,
	NoiseCount:      0,
	ShowLineOptions: 0 | 2,
	Length:          4,
	Source:          "123456879qwertyuiopasdfghlzxcvbnm",
	BgColor: &color.RGBA{
		R: 3,
		G: 102,
		B: 214,
		A: 125,
	},
	Fonts: []string{"wqy-microhei.ttc"},
}

// 自定义配置,如果不需要自定义配置,则上面的结构体和下面这行代码不用写
var driverString base64Captcha.DriverString = captchaConfig

var driver base64Captcha.Driver = driverString.ConvertFonts()

// 这是自带的 store,存储数据的结构是变量
// var store = base64Captcha.DefaultMemStore
// 但是官方推荐 store 的存储数据的结构最好是redis,其有数据持久化的功能。
type RedisStore struct {
	Conn redis.Conn
}

func (that *RedisStore) Set(id, value string) {

	_, err := that.Conn.Do("set", id, value)

	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Println("redis set success")
	}
}

func (that *RedisStore) Get(id string, clear bool) string {
	val, err := redis.String(that.Conn.Do("get", id))
	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Println("redis get success")
	}
	if clear {
		that.Conn.Do("del", id)
	}
	return val
}

func (that *RedisStore) Verify(id, answer string, clear bool) bool {
	val := that.Get(id, clear)
	return val == answer
}

// 生成图形化验证码
func GenerateCaptcha(ctx *context.Context) (CaptchaResult, error) {

	var conn = Pool.Get()
	defer conn.Close()

	var store = &RedisStore{conn}

	// 驱动 + 仓库  =》验证码实例
	var captcha = base64Captcha.NewCaptcha(driver, store)

	// 验证码实例 =》产生一个 验证码
	id, b64s, err := captcha.Generate()
	if err != nil {
		return CaptchaResult{}, err
	}
	return CaptchaResult{
		Id:         id,
		Base64Blob: b64s,
	}, nil
}

// 验证 前端 发过来的 验证码 是否正确
func CheckCaptcha(id, data string) bool {
	var conn = Pool.Get()
	defer conn.Close()

	var store = &RedisStore{conn}

	// 驱动 + 仓库  =》验证码实例
	var captcha = base64Captcha.NewCaptcha(driver, store)

	res := captcha.Verify(id, data, true)
	return res
}

统一返回数据格式

package tools

import (
	"fmt"

	"github.com/astaxie/beego/context"
)

type Status int

func (that Status) String() {
	switch that {
	case 0:
		fmt.Println("该状态为 操作成功")
	case 1:
		fmt.Println("该状态为 操作失败")
	}
}

//SUCCESS FAILED 枚举
const (
	SUCCESS Status = 0 // 操作成功
	FAILED  Status = 1 //操作失败
)

//Success 成功 de 返回
func Success(ctx *context.Context, v interface{}) {
	res := map[string]interface{}{
		"code": SUCCESS,
		"data": v,
	}
	ctx.Output.JSON(res, false, false)
}

//Failed 失败 de 返回
func Failed(ctx *context.Context, v interface{}) {
	res := map[string]interface{}{
		"code": FAILED,
		"msg":  v,
	}
	ctx.Output.JSON(res, false, false)
}

分页

package tools

//Page is
type Page struct {
	CurrentPage int         `json:"currentPage"` //当前页
	PageSize    int         `json:"pageSize"`    // 一页个数
	TotalPage   int         `json:"totalPage"`   // 总的页数
	TotalCount  int         `json:"totalCount"`  //总的个数
	FirstPage   bool        `json:"firstPage"`   //是否首页
	LastPage    bool        `json:"lastPage"`    //是否尾页
	Data        interface{} `json:"data"`        // 返回给前端的数据
}

//PageUtil  count 总的数据个数; currentpage 当前页; pageSize 一页显示的个数
func PageUtil(count int, currentpage int, pageSize int, Data interface{}) Page {
	tp := count / pageSize
	if count%pageSize > 0 {
		tp = count/pageSize + 1
	}
	return Page{
		CurrentPage: currentpage,
		PageSize:    pageSize,
		TotalPage:   tp,
		TotalCount:  count,
		FirstPage:   currentpage == 1,
		LastPage:    currentpage == tp,
		Data:        Data,
	}
}

12. 应用部署

Nginx部署

Go 是一个独立的 HTTP 服务器,但是我们有些时候为了 nginx 可以帮我做很多工作,例如访问日志,cc 攻击,静态服务等,nginx 已经做的很成熟了,Go 只要专注于业务逻辑和功能就好,所以通过 nginx 配置代理就可以实现多应用同时部署,如下就是典型的两个应用共享 80 端口,通过不同的域名访问,反向代理到不同的应用。

server {
    listen       80;
    server_name  .a.com;

    charset utf-8;
    access_log  /home/a.com.access.log;

    location /(css|js|fonts|img)/ {
        access_log off;
        expires 1d;

        root "/path/to/app_a/static";
        try_files $uri @backend;
    }

    location / {
        try_files /_not_exists_ @backend;
    }

    location @backend {
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header Host            $http_host;

        proxy_pass http://127.0.0.1:8080;
    }
}

server {
    listen       80;
    server_name  .b.com;

    charset utf-8;
    access_log  /home/b.com.access.log  main;

    location /(css|js|fonts|img)/ {
        access_log off;
        expires 1d;

        root "/path/to/app_b/static";
        try_files $uri @backend;
    }

    location / {
        try_files /_not_exists_ @backend;
    }

    location @backend {
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header Host            $http_host;

        proxy_pass http://127.0.0.1:8081;
    }
}

Apache部署

NameVirtualHost *:80
<VirtualHost *:80>
    ServerAdmin webmaster@dummy-host.example.com
    ServerName www.a.com
    ProxyRequests Off
    <Proxy *>
        Order deny,allow
        Allow from all
    </Proxy>
    ProxyPass / http://127.0.0.1:8080/
    ProxyPassReverse / http://127.0.0.1:8080/
</VirtualHost>

<VirtualHost *:80>
    ServerAdmin webmaster@dummy-host.example.com
    ServerName www.b.com
    ProxyRequests Off
    <Proxy *>
        Order deny,allow
        Allow from all
    </Proxy>
    ProxyPass / http://127.0.0.1:8081/
    ProxyPassReverse / http://127.0.0.1:8081/
</VirtualHost>

13. 升级协议

websocket协议

通过github.com/gorilla/websocket可以在beego中使用websocket协议:

package main

import (
	"fmt"
	"net/http"

	// "github.com/astaxie/beego"  beego v1.x
	beego "github.com/beego/beego/v2/server/web" // beego v2.x
	"github.com/fatih/color"
	"github.com/gorilla/websocket"
)

// 核心 协议升级结构体
var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
	// 允许跨域访问
	CheckOrigin: func(r *http.Request) bool {
		return true
	},
}

type Ws struct {
	beego.Controller
}

func (that *Ws) Get() {
	conn, err := upgrader.Upgrade(that.Ctx.ResponseWriter, that.Ctx.Request, nil)
	if err != nil {
		fmt.Println(err)
		color.Red("升级成功")
	} else {
		color.Red("升级成功")
	}

	for {
		fmt.Println("开始循环----接受---- 客户端发过来的信息")
		msgType, msg, err := conn.ReadMessage()
		if err != nil {
			fmt.Println(err)
			break
		}
		fmt.Println("接收到客户端发过来的信息是:", msg)

		msg = append(msg, 'w', 't', 't')
		fmt.Println("----发送----到客户端的信息是:", string(msg))
		err = conn.WriteMessage(msgType, msg)
		if err != nil {
			fmt.Println(err)
			break
		}
	}
}

func main() {
	// websocket 测试地址: ws://localhost:8081/wtt
	beego.Router("/wtt", &Ws{})

	beego.Run("localhost:8081")
}

https协议

  • 首先需要获得一个https的证书 (SSl证书),这个证书可以自己做,也可以到网站申请。
  • 如果是自己做的证书,浏览器访问的时候会提示不安全链接之类,建议还是自己申请。在腾讯云里有免费1年的CA证书可以申请,可以申请试用。
  • 获得CA证书后,如果是直接用beego作为http服务器,就需要设置beego的配置文件,配置如下:
appname = rxw
httpaddr = "0.0.0.0"
httpport = 8080

# 以下是https配置信息
EnableHTTPS = true
EnableHttpTLS = true
HTTPSPort = 8010
HTTPSCertFile = "server.crt"  # 或者  HTTPSCertFile = "server.pem"
HTTPSKeyFile = "server.key"  

autorender = false
copyrequestbody = true
runmode = prod

说明:
以上配置开启两个服务,监听两个端口,一个是http服务监听8080端口;另一个是https服务监听8010端口

  • 如果是使用Nginx,就可以直接通过nginx转发流量到Beego了。(注意:一般会有不同服务器的证书版本,若用nginx作为服务器,就使用Nginx版本的就可以了)

自己生成SSL证书

  1. 生成服务器私钥
# 键入以下命令:
openssl genrsa -des3 -out server.key 1024

# 反馈画面
Generating RSA private key, 1024 bit long modulus
.......................................++++++
..............................................++++++
e is 65537 (0x10001)
Enter pass phrase for server.key:
Verifying - Enter pass phrase for server.key:

设定一个密码(至少4位)并确认

  1. 生成服务器证书请求(csr)
    在生成csr文件的过程中,有一点很重要, Common Name 填入主机名(或者服务器IP)
# 键入以下命令:
openssl req -new -key server.key -out server.csr

# 反馈画面
Enter pass phrase for server.key:
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:CN
State or Province Name (full name) [Some-State]:Shanghai
Locality Name (eg, city) []:Shanghai
Organization Name (eg, company) [Internet Widgits Pty Ltd]:AAA
Organizational Unit Name (eg, section) []:OT               
Common Name (e.g. server FQDN or YOUR name) []:127.0.0.1
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
  1. 删除私钥中密码
    在第一步创建私钥的过程中,由于必须制定一个密码,而这个密码的副作用是,每次启动http服务的时候,都会要求输入密码,这会带来很大的不变。可以通过以下方式删除私钥中密码
# 键入以下两条命令:
cp server.key server.key.org
openssl rsa -in server.key.org -out server.key
  1. 生成服务器证书
    如果你不想花钱让ca签名,那么可以以下方式生成一个自签名的证书
openssl x509 -req -days 3650 -in server.csr -signkey server.key -out  server.crt
  1. 查看证书内容信息
openssl x509 -noout -text -in server.crt
  1. 操作成果
    在当前目录下得到以下文件:
  • server.key.org
  • server.key (待使用)
  • server.csr
  • server.crt (待使用)

腾讯云后台申请

  • 我的证书
  • 申请免费证书
  • 绑定域名、输入邮箱、证书备注信息、私钥密码(至少6位数)
  • 之后按照上面步骤
  • 通过之后在我的证书中找到申请的证书,点击下载,得到以下文件
├── Apache
│   ├── 1_root_bundle.crt
│   ├── 2_wtt.ruixiao.com.crt
│   └── 3_wtt.ruixiao.com.key
├── IIS
│   └── wtt.ruixiao.com.pfx
├── Nginx
│   ├── 1_wtt.ruixiao.com_bundle.crt (待使用)
│   └── 2_wtt.ruixiao.com.key (待使用)
├── Tomcat
│   └── wtt.ruixiaowan.com.jks
├── wtt.ruixiao.com.csr
├── wtt.ruixiao.com.key
└── wtt.ruixiao.com.pem
  • 这里我们就使用nginx的服务器证书就可以了。

你可能感兴趣的:(go,golang,restful)