goLang笔记+beego框架

在这里插入图片描述

goLang笔记+

初始安装之后 GOPATH:

Go开发相关的环境变量如下:

GOROOT:GOROOT就是Go的安装目录,(类似于java的JDK)
GOPATH:GOPATH是我们的工作空间,保存go项目代码和第三方依赖包
GOPATH可以设置多个,其中,第一个将会是默认的包目录,使用 go get 下载的包都会在第一个path中的src目录下,使用 go install时,在哪个GOPATH中找到了这个包,就会在哪个GOPATH下的bin目录生成可执行文件

go在编写之后要运行:

以run file 的方式运行

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

以run directory 方式运行

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

但是运行时会有下面的警告

go: go.mod file not found in current directory or any parent directory; see ‘go help modules’

在go新版本时默认开启了go module,因此需要有go.mod来管理go代码中需要的依赖;

终端进入工作目录后 go mod init ,此时会生成一个go.mod文件

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

go mod 命令指南

所有go命令都内置了对模块的支持,
不只是“mod”。例如,日常的添加、删除、升级,并且应该使用“go get”来降级依赖。

go mod init # 初始化go.mod
go mod tidy # 更新依赖文件
go mod download # 下载依赖文件
go mod vendor # 将依赖转移至本地的vendor文件
go mod edit # 手动修改依赖文件
go mod graph # 打印依赖图
go mod verify # 校验依赖

go mod 命令详解

go mod init projectname

以模块的方式初始化一个项目

go mod tidy

添加缺失的依赖,或者移除未被使用的依赖,一般在创建项目之后或者在添加新的依赖之后执行该命令;

go中的函数

go中的函数可以作为值进行传递,支持匿名函数和闭包

定义函数

定义一个函数类型–作为统一的传递参数

/*
定义一个函数类型
*/
type funcDemo func(int) bool

func isOdd(num int) bool {
    if num%2 == 0 {
       return true
    } else {
       return false
    }
}
func filterOdd(slice []int, f funcDemo) []int {
    var result []int
    for _, i2 := range slice {
       if f(i2) {
          result = append(result, i2)
       }
    }
    return result
}
func filterEven(slice []int, f funcDemo) []int {
    var result []int
    for _, i2 := range slice {
       if !f(i2) {
          result = append(result, i2)
       }
    }
    return result

}
func isEven(num int) bool {
    if num%2 != 0 {
       return true
    } else {
       return false
    }
}
func main() {
    demo := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
    odd := filterOdd(demo, isOdd)
    even := filterEven(demo, isEven)

    for _, i2 := range odd {
       fmt.Printf("%d 、", i2)
    }
    fmt.Println()
    for _, i2 := range even {
       fmt.Printf("%d 、", i2)
    }
}

匿名函数

匿名函数作为回调函数

func visit(list []float64, f func(f float64) float64) {
    for _, value := range list {
       fmt.Println(f(value))
    }

}

func sqrNum(num float64) float64 {
    return math.Sqrt(num)
}
func main() {
    list := []float64{1, 4, 9, 16, 25, 36}
    visit(list, sqrNum)
}

在定义时就调用匿名函数和将匿名函数赋值给变量

func main11() {
    dt := func(data int) int {
       return data
    }(100)

    printData(dt)
}
func printData(s int) {
    fmt.Println(s)
}

闭包

闭包就是由函数和其相关的引用环境组合而成的实体~函数+引用环境=闭包

一个函数就是一个实例,而闭包在运行时可以有多个实例,不同的引用环境和相同的引用组合可以产生不同的的实例;

func main() {
    num := countEvenNum()
    for i := 0; i < 10; i++ {
       fmt.Println(num(i))
    }
    fmt.Println("----------------------------")
    for i := 0; i < 10; i++ {
       fmt.Println(num(i))
    }
}

func countEvenNum() func(int) int {
    var num = 0
    return func(i int) int {
       if i%2 != 0 {
          num += i
       }
       return num
    }
}

闭包捕获了和他在同一作用域的其他变量和常量,所以闭包在任何地方被调用都会使用这些常量/变量,而不关心这些变量/常量已经超出作用域;

可变参数

可变参数~这点跟java一样–略

递归函数

//使用递归函数实现
/**
num + num-1 直到 num=1时停止
*/

func DiG(num int) int {
    if num == 1 {
       return 1
    }

    return num + DiG(num-1)
}

func main() {
    fmt.Println(DiG(10))
}

go中的指针

go中的指针最大的特点是:指针不能运算~即指针不能移动

获取取地址 & ~获取变量的地址

**获取取值 *** ~访问指针变量中指向地址的值

声明指针/使用指针

将基本数据类型的值作为函数参数,可以实行对传入数据的修改


/*
*
i *int   指向int的指针
*/
func ptrd(i *int) {
	*i = 90
}

var s *int

func main() {
	i := 100
	fmt.Println(i)
	ptrd(&i)
	fmt.Println(i)
	//s指向i
	s = &i
    //空指针~nil 
	if s != nil {
		fmt.Println(s)
		//取其值
		fmt.Println(*s)
	}
}

指针数组

func main() {
    str := []string{"h", "e", "l"}
    var ptr [3]string

    for i := 0; i < len(ptr); i++ {
       ptr[i] = str[i]
    }

    for _, value := range ptr {
       fmt.Printf("%T--->%v \n", value, value)
    }
}

指针的指针

/*
*
指针的指针
第一个指针存放的第二个指针的地址
第二个指针存放的变量的地址
*/
func pointer() {
    var ptr *int
    var pptr **int
    fmt.Printf("%T \n", ptr)
    a := 2345
    ptr = &a
    pptr = &ptr
    fmt.Println(ptr)
    fmt.Println(pptr)

    fmt.Println()
    fmt.Printf("指针变量--%d \n", *ptr)
    fmt.Printf("指针的指针--%d \n", *pptr)
    fmt.Printf("指针的指针的值--%d", **pptr)
}
func main() {
    pointer()

}

go中slice map ,chan作为参数传递时都传的是指针

练习题:

1,使用递归实现n!

func DGJC(num int) int {

    if num == 1 {
       return 1
    }

    return num * DGJC(num-1)
}
func main() {
    fmt.Println(DGJC(5))
}

2,使用闭包函数实现斐波那契数列,并输出前十个

//2,使用闭包函数实现斐波那契数列,并输出前十个

func FBNQ() func() int {
    var a int = 0
    var b int = 1
    return func() int {
       c := a
       a = b
       b = a + c
       return c
    }
}
func main() {
    f := FBNQ()
    for i := 0; i < 10; i++ {
       fmt.Println(f())
    }
}

go中容器

数组


func numData() {
	//生明数组
	var arr []int
	arr = []int{1, 2, 3, 4, 5, 7}
	//自动推断数组的长度
	arrlist := [...]int{1, 2, 3, 4, 56, 0}
	fmt.Println(len(arrlist))
	fmt.Println("------------------")
	for _, i2 := range arr {
		fmt.Println(i2)
	}
	fmt.Println(len(arr))
	//将数组切分
	arr2 := arr[2:3]
	fmt.Println(arr2)
}

多维数组

func mutiNumData() {
    //一个多维数组
    var arr [3][4]int
    arr = [3][4]int{
       {1, 2},
       {3, 4},
       {5, 6, 7},
    }
    fmt.Println(arr)
    fmt.println(arr[1][2])//访问方式
}

三维数组,四维数组不在概述,一般很少用到

数组是值类型,在作为参数传递时,会将原数组复制一份分配给新变量,对新变量的修改不会影响到旧变量,而对于切片 map chan,由于传递的是地址,所以会影响到旧变量本身

func mdata(a [10]int) {
    a[0] = 100
}
func main() {
    var a [10]int
    for i := 9; i > 0; i-- {
       a[i] = i
    }
    mdata(a)
    fmt.Println(a)
}

切片

go中数组的长度不可改变,但是我们也需要一个长度可变的容器,此时切片正好满足


/*
*
切片底层引用了数组(这个跟java中list底层原理一样)
切片数据结构
指针,长度,容量
指针指向切片初始位置
*/
func main() {
	var b []int //这是一个切片

	if b == nil {
		fmt.Println("切片未指定长度是切片为空切片,默认为nil")
	}

	b = append(b, 10)
	fmt.Println(b)
	for _, i2 := range b {
		//查看切片中首个元素的地址
		fmt.Println(&i2)
	}

	ints := make([]int, 6, 8) //make来声明一个切片,初始长度为6,即初始时被分配空间为6
	fmt.Println(len(ints))
}

查看地址

func main() {
    var b []int //这是一个切片
    fmt.Printf("%p \n", b)
    b = append(b, 10)
    //fmt.Println(b)
    fmt.Printf("%p \n", b)
    for _, i2 := range b {
       //查看切片中首个元素的地址
       fmt.Println(&i2)
    }
}

切片初始化方式:


/**
初始化切片的方式
*/

func chushihua() {
	//直接声明
	var a []int

	a = []int{1, 2, 3, 4, 4}
	// 切数组来得到一个切片
	b := a[:]
	fmt.Printf("%T\n", b)
	ints := make([]int, 2)
	ints[0] = 2 //没有赋值的数据默认为0
	fmt.Println(ints)
}
func main() {
	chushihua()
}

//结果:
0x0
0xc00000a0f0
0xc00000a0f8

切片的长度是切片中元素的数量

切片只是底层数组的一个引用,对切片的任何修改都会反映在底层数组上;

func main() {

    var str []string
    str = []string{"a", "s", "d", "f"}
    arr1 := str[:]
    arr2 := str[:]
    //此时修改arr1 跟arr2都会反映在底层数组str上
    arr1[0] = "z"
    arr2[2] = "Z"
    fmt.Println(str)
}
//结果-->[z s Z f]

map

map是一种集合,可以像遍历数组或切片那样去遍历他,因为map是由hash表实现的所以map是无序的;

func main() {
    //声明一个map ,,此时没有被分配内存空间
    var mmp map[int]int
    if mmp == nil {
       fmt.Println("初始map为nil")
       fmt.Printf("%p \n", mmp)
    }
    //声明之后初始化
    mmp = map[int]int{
       1: 1,
       2: 2,
    }
    //声明时就初始化
    p := map[int]int{
       1: 1,
       2: 2,
    }
    fmt.Printf("%p\n", p)
    //使用make 来分配一个内存空间
    m := make(map[string]int)
    if m == nil {
       fmt.Println("初始map为nil")
    } else {
       fmt.Printf("%p \n", m)
    }

    //map的遍历
    for key, value := range mmp {
       fmt.Println(key, "---", value)
    }
    fmt.Println("--------------")
    //当key不存在时,得到的是该value类型的默认值
    //所以当是整形元素且是默认值时
    //ok表示该元素是否存在
    value, ok := mmp[3]
    if !ok {
       fmt.Println(ok) //false
       fmt.Println(value)
    }
    //删除map中的元素
    delete(mmp, 1) //无返回值
    fmt.Println(mmp)
    //go中没有提供清空map的函数,唯一的方法是重新make一个函数
    //不用担心垃圾回收,go中垃圾回收比写一个清空函数更高效,
    //就编译器时可以看到,没有使用的对象是会发生对象未使用的提醒
}

go中常用的包

strings --包中有关于字符串的各种操作

strconv–包中有关于字符串转其他类型的函数

func main() {
    s := "hello    world   !  211111111   1"
    fmt.Println(len(s))
    f := func(c rune) bool {
       return c == '1'
    }
    result := strings.TrimRightFunc(s, f)
    fmt.Println(result)
    fmt.Println(len(result))
    fmt.Print(strings.TrimRightFunc("77GeeksForGeeks!!!", func(r rune) bool {
       return !unicode.IsLetter(r)
    }))

    str := "haha"
    split := strings.Split(str, "h")
    fmt.Println("\n", split)
    //包strconv实现了对基本数据类型的字符串表示的转换。
    ss := strconv.Itoa(-10)
    fmt.Println(ss)

    parseBool, err := strconv.ParseBool("true")
    if err == nil {
       fmt.Println(parseBool)
    }

    i, err := strconv.ParseInt("100", 10, 32)
    fmt.Println(i)
    ff, err := strconv.ParseFloat("3.1415926", 32)
    fmt.Println(ff)

    //大小写转换
    strings.ToLower(str) //HAHA
    //字符串裁剪
    trimFunc := strings.TrimFunc(str, func(r rune) bool {
       //一样时返回0
       compare := strings.Compare("a", string(r))
       if compare == 0 {
          return true
       }
       return false
    })
    fmt.Println(trimFunc)

    fmt.Println(strings.TrimSpace(s))
    fmt.Println(strings.ReplaceAll(s, " ", "-"))
}

time包下的函数:

time.Time.After() 函数是 time 包中的一个方法,它返回一个通道,这个通道会在指定的时间间隔内返回一个时间事件。这个时间事件只会发生一次,并且可以在往通道写入一个时间事件之前阻塞。需要注意的是,time.After() 函数调用时会阻塞当前 goroutine,这意味着它会统计goroutine的执行时间,如果调用函数的goroutine被阻塞了,那么计时器会等待阻塞完成之后再返回。

使用time.After()实现统计某段代码的执行时间

fmt.Println("当前时间--", time.Now())
tmr := time.After(time.Minute)
//通过通道等待时间到达
/**
创建了一个60秒钟的计时器,并在计时器结束后输出了
*/
<-tmr
fmt.Println("当前时间--", time.Now())
fmt.Println("输出")

使用time.NewTicker()实现

/*
*
创建了一个计时器 ticker,
它每隔一秒钟触发一次。然后我们通过 defer 关键字,
告诉计时器在函数结束时自动停止计时器。

接着,我们通过调用 time.Sleep() 休眠主程序10秒钟,
然后在向通道写入事件之前关闭计时器。
这将防止后续事件被发送到通道,允许我们正确地关闭计时器。
*/
func timeTicker() {
    fmt.Println("main goroutine started...")

    // 创建一个计时器
    ticker := time.NewTicker(1 * time.Second)
    defer ticker.Stop()

    // 等待10秒钟
    time.Sleep(10 * time.Second)

    fmt.Println("ticker stopped...")
}

时间转化

//指定时区时所用
	//time.UTC
	//time.Local
	//将字符串转时间---没有指定时区时默认是UTC时间,而且go语言的时间格式就很特别
	parse, err := time.Parse("2006-01-02 15:04:05", "1993-10-19 12:12:12")
	if err == nil {
		fmt.Println(parse)
	} else {
		fmt.Println(err.Error())
	}
	//指定了时区
	location, err := time.ParseInLocation("2006-01-02 15:04:05", "1993-10-19 12:12:12", time.Local)
	if err != nil {
		fmt.Println(err.Error())
	} else {
		fmt.Println(location)
	}
	//修正时区偏移量
	zone := time.FixedZone("UTC", +8*60*60)//1993-10-19 12:12:12 +0800 UTC

	location2, err := time.ParseInLocation("2006-01-02 15:04:05", "1993-10-19 12:12:12", zone)
	fmt.Println(location2)
//1993-10-19 12:12:12 +0000 UTC
//1993-10-19 12:12:12 +0800 CST
//1993-10-19 12:12:12 +0800 UTC

时间转字符串

//将时间转字符串
format := time.Now().Format("2006-01-02 15:04:05")
fmt.Printf("%T -->>%v\n", format, format)
s2 := time.Now().String() //s := t.Format("2006-01-02 15:04:05.999999999 -0700 MST")
fmt.Printf("%T -->>%v\n", s2, s2)

时间戳转时间

//时间戳
unix := time.Now().Unix() //得到一个时间戳
fmt.Printf("%T --> %v\n", unix, unix)
//时间戳转时间
t := time.Unix(1692237634, 1000)
fmt.Printf("%T --> %v\n", t, t)

fmt.Println(time.Now().UnixMicro())

时间的比较

//时间的比较
eq := time.Now().Equal(time.Now())
fmt.Println(eq) //true
//时间前后判断
before := time.Now().Before(t)
after := time.Now().After(t)
fmt.Println(before) //false
fmt.Println(after)  //true
  //获取某一时间指定位置的值
hour, m, sec := time.Now().Clock()
fmt.Println(hour, ":", m, ":", sec)
//返回时间间隔
sub := time.Now().Sub(t).Hours()
fmt.Println(t)
fmt.Printf("%T --> %v\n", sub, sub)
duration, err := time.ParseDuration("1.5h")
fmt.Printf("%T --> %v\n", duration, duration)

//在当前时间加上一段时间
add := time.Now().Add(duration)
fmt.Printf("%T --> %v\n", add, add)
date := time.Now().AddDate(10, 10, 10)
fmt.Printf("%T --> %v\n", date, date)

math包

该包下是一些数学操作的函数~主要学习一下随机数

fmt.Println(rand.Int())
//一个种子--这样生成的是一个固定的随机数
source := rand.NewSource(12)
//依赖这个种子生成一个随机数--伪随机数
r := rand.New(source)
int63 := r.Int63()
intn := r.Intn(100) //区间随机数--该随机数是固定的
fmt.Println(int63)
fmt.Println(intn)

//种子不固定--动态变化随即住种子获取随机数
i := rand.Intn(100)
fmt.Println(i)

//此方式被废弃
rand.Seed(time.Now().UnixMilli())
rand.Intn(900)

rand.New(rand.NewSource(time.Now().UnixMilli()))
rand.Intn(999)

控制台输入

username := ""
password := ""
fmt.Println("请输入账号:")
fmt.Scanln(&username)
fmt.Println("请输入密码:")
fmt.Scanln(&password)
fmt.Println("账号:", username, "密码:", password)

结构体

type person struct {
    name string
    age  int
}

func main() {
    //结构体只是定义了一种内存布局,只有当结构体实例化时才分配内存
    per := person{
       name: "李健",
       age:  0,
    }
    fmt.Println(per)
    //使用new函数来实例化一个结构体,返回一个结构体的指针,此时是分配内存的,
    p := new(person)
    p = &per
    fmt.Println(p)
    //结构体作为参数时传递的是值而非引用
    perFunc(per)
    //并没有修改
    fmt.Println(per)
    //这里以为new()是返回的是一个指针所以修改后per也被跟着修改了
    perFunc2(p)
    fmt.Println(per)
    fmt.Println(p)
}
func perFunc(person2 person) {

    person2.name = "修改"
}

// 传入的是指针
func perFunc2(person2 *person) {

    person2.name = "修改"
}

结构体中有指针的话

// 如果结构体中有嵌套的结构体
type pperson struct {
    typecalss string
    hutool    man
    per       *person
}
type man struct {
    name string
    age  int
}

func main() {
    //如果结构体中指针的话,指针的值不变,但是
    p2 := person{
       name: "指针",
       age:  0,
    }
    p := pperson{
       typecalss: "人",
       hutool: man{
          name: "男的",
          age:  30,
       },
       per: &p2,
    }
    pp := p   //深拷贝
    ppp := &p //浅拷贝
    modifyper(pp)
    fmt.Println(p)
    fmt.Println(p2)
    modifyper2(ppp)
    fmt.Println(p)
    fmt.Println(p2)
}

func modifyper(pperson2 pperson) {
    pperson2.hutool.name = "NBV"
    pperson2.per.name = "fTest"
}
func modifyper2(pperson2 *pperson) {
    pperson2.hutool.name = "NBV"
    pperson2.per.name = "fTest"
}
//{人 {男的 30} 0xc0000940a8}
//{fTest 0}                 
//{人 {NBV 30} 0xc0000940a8}
//{fTest 0}   

匿名结构体

//匿名结构体
//创建匿名结构体时,需要一同创建对象,
human := struct {
    name string
    sex  int
}{
    name: "小刚",
    sex:  1,
}
fmt.Println(human.name)


women:= struct {
     string //结构体中匿名字段
     int
}{
    "小花",
    12,
}
//结构体中字段没有名字,则该字段的名就用其类型代替
fmt.Println(women.string)

结构体嵌套

结构体嵌套可以实现类似于java中的聚合和继承关系;

实现继承

结构体嵌套中采用匿名结构体字段的形式来实现属性的继承

type Cat struct {
    voice string
    Animal
}
type Animal struct {
    name   string
    kind   string
    locate string
}

// 结构体的属性继承
func main() {
    animal := Animal{
       name:   "狸花猫",
       kind:   "猫科动物",
       locate: "家里",
    }
    cat := Cat{voice: "喵喵喵",
       Animal: animal}
    structextends(cat)
}

func structextends(cat Cat) {
    fmt.Printf("%s %s %s %s", cat.kind, cat.name, cat.voice, cat.locate)
}

结构体嵌套时,可能存在相同的成员名,成员名冲突会发生Ambiguous错误;

实现聚合

type Animal struct {
	name   string
	kind   string
	locate string
}

type Tiger struct {
    voice string
    an    Animal
}

func main() {
    animal := Animal{
       name:   "狸花虎",
       kind:   "猫科动物",
       locate: "家里",
    }
    tiger := Tiger{
       voice: "哇哦",
       an:    animal,
    }
    fmt.Println(tiger)
}

go中方法与函数

go中方法没有接收体而函数有接受体

go中的函数:

func mainstruct() {
    animal := Animal{
       name:   "狸花虎",
       kind:   "猫科动物",
       locate: "家里",
    }
    tiger := Tiger{
       voice: "哇哦",
       an:    animal,
    }
    fmt.Println(tiger)
}

函数可以直接调用,而函数只能由接受体来调用;

go中的方法:

//结构体参见上面声明的
func main() {
    animal := Animal{
       name:   "狸花猫",
       kind:   "猫科动物",
       locate: "家里",
    }
    cat := Cat{
       voice:  "米奥",
       Animal: animal,
    }
    cat.Eat("小鱼干")
}

go中的方法在func 后需要一个接收体

相同类型的方法可以在不同的类型上定义,而函数不行;

方法施加的对象没有被隐藏起来而是显示的产地

方法的接收者不是指针,那么就是获取了一份数据拷贝而原接收者不会被改变

如果方法的接收者是指针,那么接收者在修改后原来的接收者会被改变


func (c Cat) Eat(food string) {
	fmt.Println(c.name, "吃", food)
}
func (c *Cat) Eatfood(food string) {
	c.name = "东北虎"
	fmt.Println(c.name, "吃", food)
}

func main() {
	animal := Animal{
		name:   "狸花猫",
		kind:   "猫科动物",
		locate: "家里",
	}
	cat := Cat{
		voice:  "米奥",
		Animal: animal,
	}
	cat.Eat("小鱼干")
	fmt.Println(cat)
	cat.Eatfood("梅花鹿")
	fmt.Println(cat)
}

方法的继承

这个跟属性的继承原理一样,如果匿名字段实现了一个方法,那么包含这个匿名字段的的结构体也可以调用这个方法;

结构体–》

type Cat struct {
    voice string
    Animal
}
type Animal struct {
    name   string
    kind   string
    locate string
}

如果Animal实现了一个方法

func (a Animal) woolf(s string) {
    fmt.Println(a.name, "是这么jiao的", s)
}

func main() {
    animal := Animal{
       name:   "猫科动物",
       kind:   "猫科动物",
       locate: "东北",
    }
    cat := Cat{
       voice:  "",
       Animal: animal,
    }
    animal.woolf("huhu")
    cat.woolf("喵喵")

}

方法的重写

方法的重写~即包含匿名结构体的结构体也实现了匿名结构体实现的方法;

func (a Animal) woolf(s string) {
    fmt.Println(a.name, "是这么jiao的", s)
}
func (c Cat) woolf(s string) {
    fmt.Println(c.name, "是这么叫的", s)
}

此时调用该方法时会优先走包含匿名结构体的结构体实现的方法~及如果Cat调用woolf是会走cat作为接收者的方法,如果没有这个方法,才会走匿名结构体Animal实现的方法

接口及接口的实现

当我们定义一个接口时:

// 定义一个接口

type AnimalService interface {
    woolf(s string)
}

由于上面的Animal, Cat实现了 woolf(s string) 方法, 所以就可以像java那样实现了多态

func main() {
    animal := Animal{
       name:   "猫科动物",
       kind:   "猫科动物",
       locate: "东北",
    }
    //声明一个接口变量
    var Ani AnimalService
    //这个接口变量可以用 Animal 或者Cat接收
    Ani = animal
    Ani.woolf("huhu")

    cat := Cat{
       voice:  "",
       Animal: animal,
    }
    Ani = cat
    Ani.woolf("huhu")
}
//猫科动物 是这么jiao的 huhu
//猫科动物 是这么叫的 huhu


//也可以通过new()的方式接收--隐式的

	Ani = cat
	Ani.woolf("huhu")

	Ani = new(Animal)
	Ani.woolf("huhu")

	Ani = new(Cat)
	Ani.woolf("huhu")

结构体类型T不需要显示的声明实现了某一个接口,只要该结构体实现了接口中的所有有方法,他就自动实现了该接口;

将结构体类型的变量显式或者隐式的转换为接口类型的变量;

一个函数如果参数为接口,那么该参数理论可以为该接口的子类(实现接口体的对象)

接口类型的对象不可以访问其实现类中的属性字段

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

//空接口
//空接口中没有任何方法,任意类型都可以实现该接口,
//当一个方法/函数参数为空接口时,那么传入的数据类型可以是任何类型

接口对象转型

// 定义一个接口

type AnimalService interface {
	woolf(s string)
}
//....Animal 和Cat都实现了接口....
var Ani AnimalService
Ani = new(Animal)
instance, ok := Ani.(Animal)
fmt.Printf("%T \t, %v \n", instance, ok)

//Ani.woolf("huhu")
Ani = new(Cat)
instance1, ok2 := Ani.(Cat) //注意这里ok2取得是bool的默认值 Ani.(Cat) 的返回值只有一个instance1,所以ok2用的是默认值
fmt.Printf("%T \t, %t", instance1, ok2)
//如果有两个接口,接口楼中的方法名一样

type A interface {
    call()
}
type B interface {
    call()
}
type Y struct {
}
type T struct {
}

//Y T都实现了 call方法

func (y Y) call() {
    fmt.Println("Y实现了")

}
func (t T) call() {
    fmt.Println("T实现了")
}

func main() {

    var a A
    a = Y{}
    y := a.(Y)

    fmt.Printf("%T \n", y) //只有一个返回值

    //如果转换失败会报panic
    //panic: interface conversion: main.B is *main.T, not main.T
    var b B
    b = T{}
    t := b.(T)
    fmt.Printf("%T \n", t) //只有一个返回值

    //如果使用new的方式,因为是指针
    var c B
    c = new(*Y)//这里要写指针
    y2 := c.(Y)
    fmt.Printf("%T", y2) //只有一个返回值
}
//main.Y
//main.T
//*main.Y

只要两个接口拥有相同的方法列表,(顺序无所谓),他们可以相互赋值

error

创建err对象的方式:

​ 1, err := fmt.Errorf(“错误的类型%d \n”, 10)

​ 2, err := errors.New(“没钱”)

func checkAge(a int) error {
    if a < 0 {
       //创建error对象
       err := fmt.Errorf("错误的类型%d \n", 10)
       return err
    } else {
       return nil
    }
}

func checkMoney(i int) (string, error) {
    if i < 0 {
       err := errors.New("没钱")
       return "穷鬼", err
    }
    return "富豪", nil
}
func main() {
    err := checkAge(-1)
    if err != nil {
       fmt.Println(err)
    }
    money, err := checkMoney(-1000)
    if err != nil {
       fmt.Println(err.Error())
    }
    fmt.Println(money)
}

自定义error~实现error接口

type dataError struct {
    cause string
}

func (d dataError) Error() string {

    return d.cause
}

func main() {
    money2, err := checkMoney2(-100)
    if err != nil {
       fmt.Println(err.Error())
    } else {
       fmt.Println(money2)
    }
}
func checkMoney2(i int) (string, error) {
    if i < 0 {
       d2 := dataError{cause: "数据异常"}
       return "穷鬼", d2
    }
    return "富豪", nil
}

defer

defer用于延迟一个函数或者方法的执行

当有多个defer时 这些defer按照逆序执行

func AA() {
    fmt.Println("A")
}
func AAB() {
    fmt.Println("B")
}
func AAC() {
    fmt.Println("C")
}
func AAD() {
    fmt.Println("D")
}
func AAE() {
    fmt.Println("E")
}
func AAF() {
    fmt.Println("F")
}
func main() {
    defer AA()
    defer AAB()
    defer AAC()
    defer AAD()
    defer AAE()
    defer AAF()
}
//
F
E
D
C
B
A

defer 常用于处理成对的操作,打开,关闭,连接-断开,加锁,释放锁

延迟执行的函数/方法的参数在执行到 defer 所在行时被执行,而不是在执行实际调用时执行;

文件操作

获取文件信息


/*
获取文件信息
*/
func getFileInfo() {
	stat, _ := os.Stat("./view.jpg")
	f := func() {
		msg := recover()
		if msg != nil {
			fmt.Println(msg)
		}
	}
	defer f()
	fmt.Println(stat.Name())
	fmt.Println(stat.Size())
	fmt.Println(stat.Mode())
}

删除文件


// 文件删除
func delFile() {
	f := func() {
		if msg := recover(); msg != nil {
			fmt.Println(msg)
		}

	}
	defer f()
	/*
		remove不能删除非空文件夹,
	*/
	_ = os.Remove("/view.txt")  //匿名函数不能使用 _ :=
	_ = os.Remove("/constData") //匿名函数不能使用 _ :=

	os.RemoveAll("./bin")
}

文件拷贝


// copy文件
func copyF() {
	//file, err := os.Open("C:\\Users\\issuser\\Desktop\\API文档地址.txt")
	file, err := os.OpenFile("C:\\Users\\issuser\\Desktop\\view.jpg", os.O_CREATE|os.O_RDWR, fs.ModePerm)

	defer file.Close()
	if err != nil {
		fmt.Println(err.Error())
	}

	defer func() {
		if msg := recover(); msg != nil {
			fmt.Println(msg)
		}
	}()
	openFile, err := os.OpenFile("./view.jpg", os.O_CREATE|os.O_RDWR, fs.ModePerm)
	//这里参数顺序--目标文件,源文件
	written, err := io.Copy(openFile, file)
	defer openFile.Close()

	fmt.Println(written)
}

ioutil包


/*
*
ioutil包,1.16之后该包下方法被废弃,这些都在io包下重新实现
*/
func ioutilPKG() {
	//读取文件返回字节数组
	filebyte, _ := ioutil.ReadFile("./view.jpg")
	
	fmt.Printf("%T \n", filebyte)
	fmt.Println(len(filebyte))
	//读取一个目录下的子内容---不包含下层
	dir, _ := ioutil.ReadDir("./")
	fmt.Printf("%T \n", dir)
	for _, info := range dir {
		fmt.Println(info.Name())
	}
	file, _ := os.OpenFile("./view.txt", os.O_RDWR|os.O_CREATE, fs.ModePerm)
	//读取文件,返回字节数组
	all, _ := ioutil.ReadAll(file)
	fmt.Printf("%T \n", all)
	fmt.Println(all)
	//拷贝文件---如果文件不存在,则创建文件,如果有,则清空后再拷贝
	ioutil.WriteFile("./test.txt", all, fs.ModePerm)
	//在指定目录下创建 文件夹
	ioutil.TempDir("./", "[a-z]")
	ioutil.TempFile("./", "[A-Z].txt")
}
func main() {
	ioutilPKG()
}

bufio包

读缓冲区

缓冲区的创建

func bufferIoPkg() {
	file, _ := os.Open("./test.txt")
	reader := bufio.NewReader(file)
	fmt.Println("默认缓冲区大小-->", reader.Size())
	size := bufio.NewReaderSize(file, 8192)
	fmt.Println("指定缓冲区大小-->", size.Size())
	//如果参数rd已经是一个有大小的缓冲区,那么即使指定了size,还是以较大的为准
	readerSize := bufio.NewReaderSize(size, 1024)
	fmt.Println(readerSize.Size())
}
func main() {
	//ioutilPKG()
	bufferIoPkg()
}
默认缓冲区大小--> 4096
指定缓冲区大小--> 8192
8192                  


Read(p []byte)

func readMethod() {
	//打开文件
	open, err := os.Open("./test.txt")
	//创建缓冲区
	size := bufio.NewReaderSize(open, 4096)
	调整bytes大小,计时会发生变化
	bytes := make([]byte, 2048)
	//计时开始
	millistart := time.Now().UnixMilli()
	//读文件
	for {
		_, err := size.Read(bytes)
		if err == io.EOF {
			break
		}
		fmt.Println(string(bytes))
	}
	//计时jieshu
	milliend := time.Now().UnixMilli()
	fmt.Println(milliend - millistart)
	if err != nil {
		fmt.Println(err.Error())
	}
	f := func() {
		recover()
	}
	defer f()
}

ReadByte()
func readByteF() {
	//打开文件
	open, _ := os.Open("./test.txt")
	defer open.Close()
	//创建缓冲区
	reader := bufio.NewReader(open)
	bytes := make([]byte, 4096)
	//读取一个字节
	for true {
		readByte, err := reader.ReadByte()
		if err == io.EOF {
			break
		}
		bytes = append(bytes, readByte)
	}
	fmt.Println(string(bytes))
}
ReadString(delim byte)

func readStringF() {
	open, _ := os.Open("./test.txt")
	defer open.Close()
	reader := bufio.NewReader(open)

	for true {
		readString, err := reader.ReadString('\r')
		if err == io.EOF {
			break
		}
		fmt.Println(readString)
	}

}
ReadLine()
func readLineF() {
	open, _ := os.Open("./test.txt")
	defer open.Close()
	reader := bufio.NewReader(open)
	for true {
		line, _, err := reader.ReadLine()
		//readString, err := reader.ReadString('\r')
		if err == io.EOF {
			break
		}
		fmt.Println(string(line))
	}

}

readLine可以使用ReadString(‘\n’)或者ReadBytes(‘\n’)代替

ReadBytes(‘\n’)

func readBytesF() {
	//打开文件
	open, _ := os.Open("./test.txt")
	defer open.Close()
	//创建缓冲区
	reader := bufio.NewReader(open)
	//bytes := make([]byte, 4096)
	//读取一个字节数组
	for true {
		readBytes, err := reader.ReadBytes('\n')
		//readByte, err := reader.ReadByte()
		if err == io.EOF {
			break
		}
		//bytes = append(bytes, readByte)
		fmt.Println(string(readBytes))
	}

}

写缓冲区

写缓冲区的创建

缓冲区写入


/*
写缓冲区
*/

func writeBuffer() {
	open, _ := os.Open("./view.jpg")
	defer open.Close()
	//创建读缓冲区以便实现文件的复制
	reader := bufio.NewReader(open)
	bytes := make([]byte, 4096)

	//创建写缓冲区
	file, _ := os.OpenFile("./viewcpoy.jpg", os.O_CREATE|os.O_RDWR, os.ModePerm)
	defer file.Close()
	writer := bufio.NewWriter(file)
	for {
		_, err := reader.Read(bytes)
		if err == io.EOF {
			writer.Write(bytes)
			break
		}
		writer.Write(bytes)
	}
}

scanner


func ScannerF() {
	open, _ := os.Open("./view.txt")
	defer open.Close()
	//绑定一个*Reader
	reader := bufio.NewReader(open)
	scanner1 := bufio.NewScanner(reader)
	//指定扫描方式---单词
	/**
	如果split函数返回太多空值,则会导致扫描错误
	标记而不推进输入
	 */
	scanner1.Split(bufio.ScanWords)
	for scanner1.Scan() {

		//遇到exit 的时候停止
		if strings.Compare("exit", scanner1.Text()) == 0 {
			break
		} else {
			fmt.Println(scanner1.Text())
		}
	}
}
func main() {
	//writeBuffer()
	//ScannerF()
	scannerFg()
}

func scannerFg() {
	file, _ := os.OpenFile("./view.txt", os.O_CREATE|os.O_RDWR, fs.ModePerm)
	f := func() {
		if msg := recover(); msg != nil {
			fmt.Println(msg)
		}
	}
	defer f()
	//不设置缓冲区
	//file 跟bufio都实现了reader/writer
	scanner := bufio.NewScanner(file)
	scanner.Split(bufio.ScanLines)
	for scanner.Scan() {
		fmt.Println(scanner.Text())
	}
}
//scanner.Split()的方式:
	bufio.ScanLines()
	bufio.ScanWords()
	bufio.ScanBytes()
	bufio.ScanRunes()
这四个都符合函数
type SplitFunc func(data []byte, atEOF bool) (advance int, token []byte, err error)

当然也可以自己定义函数来满足分词函数

HTTP

post请求


func HttpClientD() {
	open, _ := os.Open("./view.txt")
	//创建一个http客户端
	client := http.Client{}
	//创建一个请求
	request, _ := http.NewRequest("POST", "http://localhost:9991/auth/authentication/login", open)
	//cookies := request.Cookies()
	//cookie对象
	cookie := http.Cookie{
		Name:       "cookie",
		Value:      "cookie",
		Path:       "",
		Domain:     "",
		Expires:    time.Time{},
		RawExpires: "",
		MaxAge:     0,
		Secure:     false,
		HttpOnly:   false,
		SameSite:   0,
		Raw:        "",
		Unparsed:   nil,
	}
	//请求中添加cookie
	request.AddCookie(&cookie)

	//设置请求头,设置发送内容的格式
	request.Header.Set("Content-Type", "application/json; charset=UTF-8")
	//发送请求
	response, _ := client.Do(request)
	defer response.Body.Close()
	bytes := make([]byte, 1024)
	for true {
		_, err := response.Body.Read(bytes)
		if err == io.EOF {
			break
		}
		fmt.Println(string(bytes))
	}
	//获取请求中的参数
	fmt.Println(request.Cookie("cookie"))
}

第二种方式

type LoginUser struct {
    Username  string `json:"username"`
    Password  string `json:"password"`
    otherInfo string
}

func httpPost() {
    user := LoginUser{
       Username:  "567891",
       Password:  "Do/BsSGUxyTis4CD4NWapr7JfJAZGXzrMFdxZ52hPM7PWA5ihV3DHgLSmlmMNly7+SoLswpP6r0u2kHja2mJicGjV+99AM5I+OlI+zWyh2pF00NWoCHfH0/ZFOL6GNjHvISd/uJ0DDMrFf0Vbz2SomrNmMZmJwdanZ9tiTdL01k=",
       otherInfo: "json时被忽略",
    }
    //注意,这里json时忽略掉首字母小写的属性
    marshal, _ := json.Marshal(user)
    fmt.Println(string(marshal))

    //open, _ := os.OpenFile("/view.txt", os.O_CREATE|os.O_RDWR, fs.ModePerm)
    open, _ := os.Open("/view.txt")
    defer open.Close()

    //http客户端
    client := &http.Client{}

    //client.Get("/") //get请求
    //发送post请求
//resp, err := client.Post("http://localhost:9991/auth/authentication/login", "application/json;charset=UTF-8", open)//这种方式跟上面那中有区别
    
    resp, err := client.Post("http://localhost:9991/auth/authentication/login", "application/json;charset=UTF-8", strings.NewReader(string(marshal)))
    //http返回的response的body必须close,否则就会有内存泄露
    defer resp.Body.Close()
    if err != nil {
       fmt.Println(err.Error())
    } else {
       if resp.StatusCode == 200 {
          all, _ := ioutil.ReadAll(resp.Body)
          fmt.Println(string(all))
       } else {
          all, _ := ioutil.ReadAll(resp.Body)
          fmt.Println(string(all))
       }

    }

}

func main() {
    HttpClientD()
    httpPost()
}

//注意如果直接传open,请求跟上面那种不一样,目前还没找到原因

net/http包

实现http服务端

一个监听的方法:

func ListenAndServe(addr string, handler Handler) error {
	server := &Server{Addr: addr, Handler: handler}
	return server.ListenAndServe()
}

第一个参数:监听的地址

第二个是一个实现了handelr接口的实体类

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

定义一个结构体,实现Handler接口

type httpHandler struct {
}

func (h httpHandler) ServeHTTP(rh http.ResponseWriter, r *http.Request) {
    rh.Header().Add("status", "success")
    rh.Write([]byte("this is first page"))
}

实现文件服务器

func httpServerService() {
    http.ListenAndServe(":8080", http.FileServer(http.Dir("./")))
}
//FileServer()的参数是FileSystem接口,可使用http.Dir()来指定服务端文件所在的路径。如果该路径中有index.html文件,则会优先显示html文件,否则会显示文件目录。

实现网络服务器

路由访问

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	DefaultServeMux.HandleFunc(pattern, handler)
}

处理请求时路由到的处理方式

type httpHandler struct {
}
//路由处理器1
func (h httpHandler) ServeHTTP(rh http.ResponseWriter, r *http.Request) {
    rh.Header().Add("status", "success")
    rh.Write([]byte("this is first page"))
}
//路由处理器2
func handleIndex(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("this is index page"))
}
func main() {
    //注册访问路由,第一个 参数regrex 第二个是一个函数 func(ResponseWriter, *Request)
    //底层调用默认的分发方法
    http.HandleFunc("/index", handleIndex)
    //使用实现了Handler的接口
    handler := httpHandler{}
    http.HandleFunc("/index1", handler.ServeHTTP)
    //httpServerService()

    err := http.ListenAndServe(":8080", nil)
    //第二个参数为nil时表示默认使用 DefaultServeMux.HandleFunc(pattern, handler)方式进行路由
    if err != nil {
       fmt.Println(err.Error())
    }
}

自定义路由

多路路由结构体

type ServeMux struct {
    mu    sync.RWMutex
    m     map[string]muxEntry
    es    []muxEntry // slice of entries sorted from longest to shortest.
    hosts bool       // whether any patterns contain hostnames
}
type muxEntry struct {
    h       Handler
    pattern string
}

实现多路路由

func handHttp(H http.ResponseWriter, R *http.Request) {
    H.Write([]byte("走了handHttp"))

}

func mutiHttp() {
    //多路路由
    serveMux := http.NewServeMux()
    serveMux.HandleFunc("/", handHttp)
    serveMux.HandleFunc("/index", handleIndex)

    //开一个服务短
    server := http.Server{
       Addr:                         ":8080",
       Handler:                      serveMux,
       DisableGeneralOptionsHandler: false,
       TLSConfig:                    nil,
       ReadTimeout:                  0,
       ReadHeaderTimeout:            0,
       WriteTimeout:                 0,
       IdleTimeout:                  0,
       MaxHeaderBytes:               0,
       TLSNextProto:                 nil,
       ConnState:                    nil,
       ErrorLog:                     nil,
       BaseContext:                  nil,
       ConnContext:                  nil,
    }
    //开始监听
    server.ListenAndServe()

}

func main() {
    mutiHttp()
}

HTTP获取请求参数

Post
type baseResult struct {
    Status int         `json:"status"`
    Msg    string      `json:"msg"`
    Err    string      `json:"err"`
    Data   interface{} `json:"data"`
}

type baseUser struct {
    Username string `json:"username"`
    Password string `json:"password"`
}

/*
服务器短获取客户端请求参数
只是一个路由处理器,可以开一个服务然后监听
*/
func getRequestInfo(W http.ResponseWriter, h *http.Request) {
    err := h.ParseForm()
    var result baseResult
    if err != nil {
       result = baseResult{
          Status: 400,
          Msg:    "请求方式不对",
          Err:    "bad request",
          Data:   nil,
       }
       marshal, err := json.Marshal(result)
       if err != nil {
          W.Write([]byte("json转换失败"))
       }
       W.Write(marshal)
    }
    //捕获panic
    f := func() {
       if msg := recover(); msg != nil {

       }
    }
    defer f()

    if h.Method == "POST" && strings.Contains(h.RequestURI, "login") {
       //由于接受的是post 的json参数,所以需要获取body,然后解析body
       readAll, err := io.ReadAll(h.Body)
       //all, _ := ioutil.ReadAll(h.Body)
       if err != nil {
          W.Write([]byte("请求异常"))
       }
       fmt.Println(string(readAll))
       var user baseUser
       err = json.Unmarshal(readAll, &user)
       username := user.Username
       password := user.Password
       if strings.Compare(username, "567891") == 0 && strings.Compare(password, "123456") == 0 {
          result = baseResult{
             Status: 200,
             Msg:    "登陆成功",
             Err:    "",
             Data:   nil,
          }
          marshal, err := json.Marshal(result)
          if err != nil {
             W.Write([]byte("json转换失败"))
          }
          W.Write(marshal)
       } else {
          result = baseResult{
             Status: 400,
             Msg:    "登陆失败",
             Err:    "用户名/密码不对",
             Data:   nil,
          }
       }
    } else {
       W.Write([]byte("请求方式不对"))
    }
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/auth/login", getRequestInfo)
    server := http.Server{
       Addr:                         ":8080",
       Handler:                      mux,
       DisableGeneralOptionsHandler: false,
       TLSConfig:                    nil,
       ReadTimeout:                  0,
       ReadHeaderTimeout:            0,
       WriteTimeout:                 0,
       IdleTimeout:                  0,
       MaxHeaderBytes:               0,
       TLSNextProto:                 nil,
       ConnState:                    nil,
       ErrorLog:                     nil,
       BaseContext:                  nil,
       ConnContext:                  nil,
    }
    server.ListenAndServe()
}
Get
/**
get请求
*/

type getReqInfo struct {
}

// 可以在这里处理http请求---
func (g getReqInfo) ServeHTTP(wr http.ResponseWriter, r *http.Request) {

    err := r.ParseForm()
    var result baseResult

    if err != nil {
       result = baseResult{
          Status: 400,
          Msg:    "非法请求",
          Err:    "请求失败",
          Data:   nil,
       }
       jsonResult, err := json.Marshal(result)
       if err != nil {
          wr.Write([]byte("json转换失败"))
       }
       wr.Write(jsonResult)
    }
    if strings.Compare("GET", r.Method) == 0 {

       username := r.FormValue("username")
       password := r.FormValue("password")

       if strings.Compare("000000", username) == 0 && strings.Compare("123456", password) == 0 {
          result = baseResult{
             Status: 200,
             Msg:    "登陆成功",
             Err:    "",
             Data:   nil,
          }
          jsonResult, _ := json.Marshal(result)
          wr.Write(jsonResult)
       } else {
          result = baseResult{
             Status: 400,
             Msg:    "用户名密码错误",
             Err:    "请求失败",
             Data:   nil,
          }
          jsonResult, _ := json.Marshal(result)
          wr.Write(jsonResult)
       }
    }

}

func main() {
    info := getReqInfo{}
    //设置路由
    http.HandleFunc("/auth/login", info.ServeHTTP)

    //http.ListenAndServe(":8080", info)

    //要设置多路路由,那么需要
    mux := http.NewServeMux() //实现了handler接口
    mux.HandleFunc("/auth/login", info.ServeHTTP)
    server := http.Server{
       Addr:                         ":8081",
       Handler:                      mux,
       DisableGeneralOptionsHandler: false,
       TLSConfig:                    nil,
       ReadTimeout:                  0,
       ReadHeaderTimeout:            0,
       WriteTimeout:                 0,
       IdleTimeout:                  0,
       MaxHeaderBytes:               0,
       TLSNextProto:                 nil,
       ConnState:                    nil,
       ErrorLog:                     nil,
       BaseContext:                  nil,
       ConnContext:                  nil,
    }
    server.ListenAndServe()
}
Post获取表单内容

type FileInfoStruct struct {
	DemandId    string `json:"demandId"`
	FileName    string `json:"fileName"`
	newFileName string `json:"newFileName"`
}

func postForm() {
	handler := func(writer http.ResponseWriter, request *http.Request) {
		//对于所有请求,ParseForm解析来自URL的原始查询并更新
		//对于GET,POST、PUT和PATCH请求,它也读取请求体,并对其进行解析
		//如果请求正文的大小还没有被MaxBytesReader限制,文件大小上限为10MB。
		//对于其他HTTP方法,或者当Content-Type不是application/x-www-form-urlencoded,请求体不被读取
		//err := request.ParseForm()
		err := request.ParseMultipartForm(409600000)
		if err != nil {
			writer.Write([]byte("解析失败"))

		}
		if request.Method == "POST" {
			//all, _ := io.ReadAll(request.Body)
			//fmt.Println(all)
			//request.MultipartForm
			//get := request.PostForm.Get("demandId")
			//fmt.Println(get)
			demandId := request.PostFormValue("demandId")
			//fileName := request.PostFormValue("fileName")
			//接收文件
			file, header, _ := request.FormFile("file")
			defer file.Close()
			//通过header获取文件名称
			filename := header.Filename
			//header.Header.
			fmt.Printf("%T \t %T \t ", file, header)
			openFile, _ := os.OpenFile("./"+filename, os.O_CREATE|os.O_RDWR, fs.ModePerm)
			defer openFile.Close()
			writer1 := bufio.NewWriter(openFile)
			bytes := make([]byte, 4096) //存
			for {
				_, err := file.Read(bytes)
				if err == io.EOF {
					break
				}
				writer1.Write(bytes)
			}

			infoStruct := FileInfoStruct{
				DemandId:    demandId,
				FileName:    filename,
				newFileName: "newFile",
			}
			marshal, _ := json.Marshal(infoStruct)
			writer.Write(marshal)
		}
	}

	serveMux := http.NewServeMux()
	serveMux.HandleFunc("/auth/upload", handler)
	server := http.Server{
		Addr:    ":8081",
		Handler: serveMux,
	}
	server.ListenAndServe()
}

func main() {
	postForm()
}

但是,如果用这种方式,开发效率应该较低,应该有像spring那种框架的;

重定向

func getI(w http.ResponseWriter, req *http.Request) {
	if req.Method == "GET" {
		//重定向
		http.Redirect(w, req, "/login", http.StatusOK)
		//request, _ := http.NewRequest("GET", "localhost:8081/login", req.Body)
		//client := http.Client{}
		//
		//response, _ := client.Do(req)
		//defer response.Body.Close()
		//bytes := make([]byte, 1024)
		//for true {
		//	_, err := response.Body.Read(bytes)
		//	if err == io.EOF {
		//		break
		//	}
		//	fmt.Println(string(bytes))
		//}
	}
}
func loginStatus(wr http.ResponseWriter, r *http.Request) {

	err := r.ParseForm()
	var result baseResult

	if err != nil {
		result = baseResult{
			Status: 400,
			Msg:    "非法请求",
			Err:    "请求失败",
			Data:   nil,
		}
		jsonResult, err := json.Marshal(result)
		if err != nil {
			wr.Write([]byte("json转换失败"))
		}
		wr.Write(jsonResult)
	}
	if strings.Compare("GET", r.Method) == 0 {

		username := r.FormValue("username")
		password := r.FormValue("password")

		if strings.Compare("000000", username) == 0 && strings.Compare("123456", password) == 0 {
			result = baseResult{
				Status: 200,
				Msg:    "登陆成功",
				Err:    "",
				Data:   nil,
			}
			jsonResult, _ := json.Marshal(result)
			wr.Write(jsonResult)
		} else {
			result = baseResult{
				Status: 400,
				Msg:    "用户名密码错误",
				Err:    "请求失败",
				Data:   nil,
			}
			jsonResult, _ := json.Marshal(result)
			wr.Write(jsonResult)
		}
	}

}

func main() {
	//postForm()
	serveMux := http.NewServeMux()
	serveMux.HandleFunc("/auth", getI)
	serveMux.HandleFunc("/login", loginStatus)
	server := http.Server{
		Addr:    ":8081",
		Handler: serveMux,
	}
	server.ListenAndServe()
}

JSON

对象转json

go中提供了数据转json的方法

infoStruct := FileInfoStruct{
    DemandId: demandId,
    FileName: filename,
    Size:     size,
}
marshal, _ := json.Marshal(infoStruct)

go中还提供了关于json时的另外的方法

jsonResult, _ := json.Marshal(result)
fmt.Println(jsonResult)
jsonResult1, _ := json.MarshalIndent(result, "", " ")

经过美化后的结果:

{

 "status": 200,

 "msg": "登陆成功",

 "err": "",

 "data": null

}

json转对象

type baseUser struct {
	Username string `json:"username"`
	Password string `json:"password"`
}
var user baseUser
err = json.Unmarshal(readAll, &user)

声明一个变量用于接收json转换之后的对象(传地址)

json转换为go类型的规则

json boolean -->bool

json nunber -->float64

json string ---->string

json 数组 ->[]interface{}

json objec -> map

null -->nil

json 小结

type baseUser struct {
	Username string `json:"username"`
	Password string `json:"password"`
    otherInfo string 
}

当属性为名称小写字母的时候,表示json的时候忽略该属性,即使后面声明了 json标签

另json标签表示给字段起了个别名,如果没有该标签,则按照属性原本的名字进行转json处理;

• JSON对象只支持string作为key,所以要编码一个map,必须是map[string]T这种类型(T是Go语言中的任意类型)。

当结构体中有结构体
匿名结构体

json包在解析匿名字段时,会将匿名字段的字段当成该结构体的字段处理,

//json解析匿名字段

type F struct {
    X, Y int
}

type X struct {
    A int
    F //这hi是一个匿名字段
}

func main() {
    //json包在解析匿名字段时,会将匿名字段的字段当成该结构体的字段处理,
    marshal, err := json.Marshal(X{
       A: 0,
       F: F{
          X: 10,
          Y: 10,
       },
    })
    if err == nil {
       fmt.Printf("%s \n", marshal)
    }
}
//{"A":0,"X":10,"Y":10}

非匿名结构体
type F struct {
    X, Y int
}

type X struct {
    A int
    Z F
}

func main() {
    //json包在解析匿名字段时,会将匿名字段的字段当成该结构体的字段处理,
    marshal, err := json.Marshal(X{
       A: 0,
       Z: F{
          X: 10,
          Y: 10,
       },
    })
    if err == nil {
       fmt.Printf("%s \n", marshal)
    }
}
//{"A":0,"Z":{"X":10,"Y":10}}

channel、complex和function是不能被编码成JSON的。

结构体中有指针

指针在编码的时候会输出指针指向的内容,而空指针会输出null。


type F struct {
	X, Y int
}
type Test struct {
	Name string
}
type X struct {
	A  int
	F  //这hi是一个匿名字段
	T  *Test
	TT Test
}

func main() {
	//json包在解析匿名字段时,会将匿名字段的字段当成该结构体的字段处理,
	test := Test{Name: "goland"}
	marshal, err := json.Marshal(X{
		A: 0,
		F: F{
			X: 10,
			Y: 10,
		},
		T:  &test,
		TT: test,
	})
	if err == nil {
		fmt.Printf("%s \n", marshal)
	}
}

json转结构体

即转换为一个对象,具体步骤是先 声明一个变量,用这个变量的地址去接收属性值

type Test struct {
    Name string `json:"-"` //json时会被忽略
}

案例


type Fruit struct {
	Kind string `json:"kind"`
}
type Apple struct {
	Name   string `json:"name"`
	Color  string `json:"color"`
	Source string `json:"-"`
	Fruit         //匿名结构体中字段会被处理为该结构体的字段---就是继承
}

func (f Fruit) printKind() {
	fmt.Println("水果")

}
func main() {
	apple := Apple{
		Name:   "嘎啦",
		Color:  "红",
		Source: "烟台",
		Fruit: Fruit{
			Kind: "水果",
		},
	}
	apple.printKind()
	mux := http.NewServeMux()
	mux.HandleFunc("/getFruit", serveFruit)
	server := http.Server{Addr: ":8080", Handler: mux}
	server.ListenAndServe()
}

func serveFruit(resp http.ResponseWriter, req *http.Request) {
	apple := Apple{
		Name:   "嘎啦",
		Color:  "红",
		Source: "烟台",
		Fruit: Fruit{
			Kind: "水果",
		},
	}
	if req.Method == "GET" {
		marshal, _ := json.Marshal(apple)
		resp.Header().Set("content-type", "text/json")
		resp.WriteHeader(200)
		resp.Write(marshal)
	}
	if req.Method == "POST" {
		var ap Apple
		//解析传过来的body
		apple, _ := io.ReadAll(req.Body)
		err := json.Unmarshal(apple, &ap)
		if err == nil {
			fmt.Println(ap)
			marshal, _ := json.Marshal(ap)
			//设置返回时为json
			resp.Header().Set("content-type", "text/json")
			resp.WriteHeader(200)
			resp.Write(marshal)
		}
	}

}

在解码JSON时,如果找不到字段,则查找字段的字段

go数据库

这里以mysql为数据源:

配置一下mysql驱动:

由于直接访问gitHub总是连接超时,可以采用下面的设置:

进入go的项目目录后

1,用cmd执行下面的命令:

go env -w GOPROXY=https://goproxy.cn,direct

2,下载数据库驱动

go get -u github.com/go-sql-driver/mysql

3,开始下载

go: downloading github.com/go-sql-driver/mysql v1.7.1

go: added github.com/go-sql-driver/mysql v1.7.1

下载之后在External Libraries 的Go Modules 下会找到刚刚下载的驱动包

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

连接数据库:

//数据库编程
/**
导入包后,我们就可以使用包中的数据,但是由于要考虑数据操作的通用性
所以我们最好不要使用包中的数据类型,而是使用sql.DB对象提供的统一的 方法,
因此我们在导包时要匿名导入该包
*/

func main() {
    /*
       连接数据库的方法
    */
    //sql.Open()返回的sql.DB对象是Goroutine并发安全的
    db, err := sql.Open("mysql", "root:955945@tcp(127.0.0.1:3306)/magic?charset=utf8")
    fmt.Println(db)
    defer db.Close()
    if err == nil {
       db.Exec("insert into test_data(id ,name) values (?,?)", "3", "gavin")

    }

}

匿名导入数据库驱动包

 import _ "github.com/go-sql-driver/mysql"

DB是一个结构体:

里面指定了一些关于数据库连接的参数,其中就有关于线程安全的 sync.Mutex

type DB struct {
    // Total time waited for new connections.
    waitDuration atomic.Int64

    connector driver.Connector
    // numClosed is an atomic counter which represents a total number of
    // closed connections. Stmt.openStmt checks it before cleaning closed
    // connections in Stmt.css.
    numClosed atomic.Uint64

    mu           sync.Mutex    // protects following fields
    freeConn     []*driverConn // free connections ordered by returnedAt oldest to newest
    connRequests map[uint64]chan connRequest
    nextRequest  uint64 // Next key to use in connRequests.
    numOpen      int    // number of opened and pending open connections
    // Used to signal the need for new connections
    // a goroutine running connectionOpener() reads on this chan and
    // maybeOpenNewConnections sends on the chan (one send per needed connection)
    // It is closed during db.Close(). The close tells the connectionOpener
    // goroutine to exit.
    openerCh          chan struct{}
    closed            bool
    dep               map[finalCloser]depSet
    lastPut           map[*driverConn]string // stacktrace of last conn's put; debug only
    maxIdleCount      int                    // zero means defaultMaxIdleConns; negative means 0
    maxOpen           int                    // <= 0 means unlimited
    maxLifetime       time.Duration          // maximum amount of time a connection may be reused
    maxIdleTime       time.Duration          // maximum amount of time a connection may be idle before being closed
    cleanerCh         chan struct{}
    waitCount         int64 // Total number of connections waited for.
    maxIdleClosed     int64 // Total number of connections closed due to idle count.
    maxIdleTimeClosed int64 // Total number of connections closed due to idle time.
    maxLifetimeClosed int64 // Total number of connections closed due to max connection lifetime limit.

    stop func() // stop cancels the connection opener.
}

数据的增删改

直接执行

使用sql.DB对象执行

exec, err := db.Exec("insert into test_data(id ,name) values (?,?)", "5", "Martain")
预编译

使用预编译的对象执行—>>优选的方法

通常使用PreparedStatement和Exec()完成INSERT、UPDATE、DELETE操作

//预编译对象
stmt, err := db.Prepare("update test_data set name =? where id=?")
if err != nil {
    fmt.Println(err.Error())
}
fmt.Printf("%T \n", stmt)
//执行的时候给参数
stmt.Exec("Lucy", 4)

以上是对数据的增删改,返回值也是影响的行数及 err

数据的查询

查询一行QueryRow

func sqlQueryRow() {
    dbstatus, err := sql.Open("mysql", "root:955945@tcp(localhost:3306)/magic?charset=utf8")
    defer dbstatus.Close()
    if err == nil {
       // QueryRow执行一个预计最多返回一行的查询。
       // QueryRow总是返回一个非nil值。错误被延迟到调用行的Scan方法。
       //如果查询没有选择行,*Row's Scan将返回ErrNoRows。
       //否则,*Row's Scan扫描第一个选定的行并丢弃其余部分。

       row := dbstatus.QueryRow("select * from test_data")

       fmt.Printf("%T \n", row)
       c2 := new(Consumer)
       row.Scan(&c2.Id, &c2.Name)
       fmt.Println(c2)
      
    } else {
       panic(err)

    }
    defer func() {
       if msg := recover(); msg != nil {
          fmt.Println(msg)
       }
    }()
}

查询多行–预编译

//如果要查询多行

      prepare, _ := dbstatus.Prepare("select * from test_data")

      query, _ := prepare.Query()

      for query.Next() {

         c3 := new(Consumer)

         query.Scan(&c3.Id, &c3.Name)

         fmt.Println(c3)

      }


func sqlQuery() {
	dbstatus, err := sql.Open("mysql", "root:955945@tcp(localhost:3306)/magic?charset=utf8")
	defer dbstatus.Close()//确保数据库连接可以正确放回到连接池中。
	if err != nil {
		panic(err)
	}
	defer func() {
		recover()
	}()
	queryResult, err := dbstatus.Query("select * from test_data limit ?", 5)
	if err == nil {
		//返回下一个结果行
		//每次调用Scan,即使是第一次调用,都必须先调用Next。
		for queryResult.Next() {
			c2 := new(Consumer)
			queryResult.Scan(&c2.Id, &c2.Name)
			fmt.Println(c2)
		}

	}

}

rows.Scan()方法的参数顺序很重要,必须和查询结果的column相对应(数量和顺序都需要一致)。例如,“SELECT * From user_info where age >=20 AND age < 30”查询的column顺序是“id, name, age”,和插入操作顺序相同,因此 rows.Scan() 也需要按照此顺序“rows.Scan(&id, &name, &age)”,不然会造成数据读取的错位。

注意:

因为Golang是强类型语言,所以查询数据时先定义数据类型。数据库中的数据有3种可能状态:存在值、存在零值、未赋值,因此可以将待查询的数据类型定义为sql.NullString、sql.NullInt64类型等。可以通过Valid值来判断查询到的值是赋值状态还是未赋值状态

  type NullInt64 struct {
  Int64 int64
 Valid bool // Valid is true if Int64 is not NULL

操作数据库的方法,在实际开发中,应尽量封装这些方法。

线程与协程

Goroutine能并行执行,Coroutine只能顺序执行;Goroutine可在多线程环境产生,Coroutine只能发生在单线程。Coroutine程序需要主动交出控制权,系统才能获得控制权并将控制权交给其他Coroutine。

使用go关键字创建Goroutine时,被调用的函数往往没有返回值,如果有返回值也会被忽略。

如果需要在Goroutine中返回数据,必须使用channel,通过channel把数据从Goroutine中作为返回值传出

Go程序的执行过程是:创建和启动主Goroutine,初始化操作,执行main()函数,当main()函数结束,主Goroutine随之结束,程序结束

Goroutine案例

func openSql() {
	
	fmt.Println("hello world")
}

func main() {
	go openSql()
	 
	fmt.Println("main exit")
}

如果没有意外的话,程序执行的结果:

main exit

被启动的Goroutine叫作子Goroutine。如果main()的Goroutine终止了,程序将被终止,而其他Goroutine将不再运行。换句话说,所有Goroutine在main()函数结束时会一同结束。如果main()的 Goroutine比子Goroutine先终止,运行的结果就不会打印

如果main函数执行的时间足够长,

go hello()有足够的时间在main Goroutine终止之前执行。

func openSql() {
    fmt.Println("hello world")
}

func main() {
    go openSql()
    time.Sleep(2000)
    fmt.Println("main exit")
}

执行的结果:

hello world

main exit

func goGroutineTest() {
    var result int
    for {
       result++
       fmt.Println("add--", result)
       time.Sleep(time.Second)
    }
    fmt.Println("---------")

}
func main() {
    go goGroutineTest()
    var hello string
    fmt.Scanln(&hello)
}

go程序启动时,归属于 函数goGroutineTest的Goroutine被创建,goGroutineTest在自己的Goroutine中执行

此时,main()继续执行,两个Goroutine通过Go程序的调度机制同时运行。

匿名函数创建协程与闭包

go关键字后也可以是匿名函数或闭包

//匿名函数创建协程
//go关键字后也可以是匿名函数或闭包

func main() {
    //匿名函数
    go func() {
       var count int
       for true {
          count++
          fmt.Println("count---》", count)
          time.Sleep(time.Second)
       }
    }()
    var kind string
    fmt.Scanln(&kind)
    fmt.Println(kind)
}

并发

使用go启动多个Goroutine

func demoNum() {
    var times int
    for {
       times++
       fmt.Print(times)
       time.Sleep(time.Second)
       if times == 10 {
          break
       }
    }
}
func demoABC() {
    var times string
    times = "ABCDEFG"
    bytes := []byte(times)
    for i, b := range bytes {
       fmt.Print(string(b))
       time.Sleep(time.Duration(i))
    }
}
func main() {
    go demoABC()
    go demoNum()
    go func() {
       for true {
          time.Sleep(time.Second)
          fmt.Print("+")
       }
    }()
    time.Sleep(10 * time.Second)
    fmt.Println("main -- exit")
}
//AB1CDEFG2++34++56++78++910+main -- exit

调整并发的运行性能

在多个Goroutine的情况下,可以使用runtime.Gosched()交出控制权

要维护线程池中的线程与CPU核心数量的对应关系,这在Go语言中可以通过runtime.GOMAXPROCS()函数做到。

channel通道

协程之间的通信机制

一个channel是一条通信管道,它可以让一个协程通过它给另一个协程发送数据。每个channel都需要指定数据类型,即channel可发送数据的类型。如果使用channel发送int类型数据,可以写成chan int

通道的声明

func chanelA() {
    //通道的声明  同时要只当通道中的数据类型
    var channel chan int

    fmt.Printf("%T\n", channel)
    intschannel := make(chan int)
    fmt.Printf("%T\n", intschannel)
    c2 := make(chan interface{})
    fmt.Printf("%T\n", c2)
    c3 := new(chan int)
    fmt.Printf("%T\n", c3)
}

func main() {
    chanelA()
}
/*
chan int
chan int         
chan interface {}
*chan int  
*/

使用通道接收和发送数据

channel发送的数据类型必须与channel的元素类型一致,如果接收昂没有接收,那么发送操作会持续阻塞,此时所有的Goroutine包括main()的Goroutine都会处于等待状态;

运行会提示报错:fatal error: all goroutines are asleep - deadlock!。

使用channel时要考虑发生死锁(deadlock)的可能。如果Goroutine在一个channel上发送数据,其他的Goroutine应该接收得到数据;如果没有接收,那么程序将在运行时出现死锁。如果Goroutine正在等待从channel接收数据,其他一些Goroutine将会在该channel上写入数据;如果没有写入,程序将会死锁

代码案例:

// 发送数据
func sendData(s chan int) {
    defer close(s)
    for i := 1; i < 10; i++ {
       //往通道中发送数据
       s <- i
    }
}

// 接收数据
func receiveData() {

}
func main() {
    ints := make(chan int)
    go sendData(ints)

    //for range 可以自动判断通道是否关闭
    //for i2 := range ints {
    // fmt.Println(i2)
    //}

    //如果通道关闭,则通道中的数据为各类型的默认值, chan int 默认0
    //chan string 默认 ""
    //for {
    // data := <-ints
    // //如果数据中有0那么这种方式不合适
    // if data == 0 {
    //    break
    // }
    // fmt.Println(data)
    //}
    for {
       data, ok := <-ints
       fmt.Print(data)
       if !ok {
          break
       }
       //结果:1234567890
       //最后一个0是通道关闭的标志
    }
}

阻塞

channel默认是阻塞的。当数据被发送到channel时会发生阻塞,直到有其他Goroutine从该channel中读取数据。当从channel读取数据时,读取也会被阻塞,直到其他Goroutine将数据写入该channel。这些channel的特性帮助Goroutine有效地通信,而不需要使用其他语言中的显式锁或条件变量\

/*
chan 阻塞的用法
*/

func ZS() {
    var c chan int
    c = make(chan int)
    c1 := make(chan string)
    go func() {
       //匿名函数,从通道中取数据
       data, ok := <-c
       if ok {
          fmt.Println(data)
       }
       //如果没有往通道中传输数据那么会发生panic 死锁
       //fatal error: all goroutines are asleep - deadlock!     
       c1 <- "string" 
    }()
    //往通道中传数据
    //阻塞channel等待匿名函数的Goroutine运行结束,防止ZS函数的Goroutine退出而导致匿名函数的Goroutine提前退出。
    c <- 1 
    <-c1 //进入阻塞状态
    fmt.Println("exit")

}

func main() {
    ZS()
}

关闭channel

发送方如果数据写入完毕,需要关闭channel,用于通知接收方数据传递完毕。通常情况是发送方主动关闭channel。接收方通过多重返回值判断channel是否关闭,如果返回值是false,则表示channel已经被关闭。往关闭的channel中写入数据会报错:panic: send on closed channel。但是可以从关闭后的channel中读取数据,返回数据的默认值和false

func main() {
    ints := make(chan int)
    go func() {
       ints <- 1
       ints <- 2
       ints <- 3
       ints <- 4
       ints <- 5
       close(ints)
       //关闭之后在放入数据
       ints <- 10
       defer func() {
          if msg := recover(); msg != nil {
             fmt.Println("msg")
          }
       }()
    }()
    //导致panic异常的函数不会继续执行,但能正常返回
    defer func() {
       if msg := recover(); msg != nil {
          fmt.Println("msg")
       }
    }()
    for i2 := range ints {
       fmt.Println(i2)
    }
}

缓冲channel

func sendInt(ch chan int) {
    defer close(ch)
    for i := 0; i < 6; i++ {
       time.Sleep(time.Second)
       ch <- i
       fmt.Println("输入数据---", i)
    }
}

func main() {
    //非阻塞通道
    ints := make(chan int)
    //往通道中发送数据
    go sendInt(ints)
    //接受之后打印
    for i := range ints {
       fmt.Println(i)
    }
    time.Sleep(time.Second)
    //缓冲通道
    c2 := make(chan int, 6)
    go sendInt(c2)
    for i := range c2 {
       fmt.Println(i)
    }
}

运行时观察控制台会发现,当缓冲channel在缓冲满的时候才开始执行打印

单向channel

/**
单向channel
channel默认都是双向的,
单向channel只读/只写

*/

func singleChan() {
    //这是一个只读channel
    ints := make(<-chan int)
    //直接创建单向channel没有意义
    //此时创建的通道只读,但是里面没有数据,
    data := <-ints
    fmt.Println(data)
}
func main() {
    singleChan()//发生了死锁
}

通常的做法是创建双向channel,然后以单向channel的方式进行函数传递

// 函数只负责读取数据,不做数据处理
func readOnlychan(ch <-chan int) {
    for i := range ch {
       fmt.Println(i)
       time.Sleep(time.Microsecond)
    }
}

// 该函数只负责写数据
func writeOnlychan(ch chan<- int) {
    defer close(ch)
    for i := 0; i < 10; i++ {
       ch <- i
       fmt.Println("接收到了数据")
    }
}

func main() {
    runtime.GOMAXPROCS(4)
    chi := make(chan int)
    go writeOnlychan(chi)

    go readOnlychan(chi)

    time.Sleep(time.Minute)
    fmt.Println("main exit")

}

注意:函数/方法在传递channel时

只读channel

—>写法如下:

func readChannel( ch <- chan int){

}

只写channel

—>写法如下:

func writeChannel(ch chan <-int){

}

time包中与channel有关的函数

timer结构体

type Timer struct {
    C <-chan Time
    r runtimeTimer
}

Timer类型表示单个事件。

当Timer超时时,当前时间将在C上发送;
除非Timer是由AfterFunc创建的
计时器必须用NewTimer或AfterFunc创建。

time.NewTimer

timer := time.NewTimer(time.Second)//返回值为 *Timer ,其属性C <-chan Time
after := time.After(time.Second)//返回值为<-chan Time
fmt.Println(time.Now())
times := <-timer.C
times1 := <-after
fmt.Printf("%T\n", times)
fmt.Printf("%T\n", after)
fmt.Println(times)
fmt.Println(times1)

time.AfterFunc

afterFunc := time.AfterFunc(time.Second, func() {
    fmt.Println("after func")
})
time.Sleep(time.Second)
defer fmt.Printf("%T\n", afterFunc)

times2 := <-afterFunc.C
defer fmt.Println(times2)
/*
结果-->
after func
fatal error: all goroutines are asleep - deadlock!     
                                                       
goroutine 1 [chan receive (nil chan)]:                 
main.main()                                            
        D:/softStone/GoData/src/main/main.go:2479 +0xa7
*/
func main() {
    // Defining duration parameter of
    // AfterFunc() method
    DurationOfTime := time.Duration(3) * time.Second

    // Defining function parameter of
    // AfterFunc() method
    f := func() {

       // Printed when its called by the
       // AfterFunc() method in the time
       // stated above
       fmt.Println("Function called by " +
          "AfterFunc() after 3 seconds")
    }

    // Calling AfterFunc() method with its
    // parameter
    Timer1 := time.AfterFunc(DurationOfTime, f)

    // Calling stop method
    // w.r.to Timer1
    defer Timer1.Stop() //取消调用,但是如果返回 false ,则表示该函数已经执行且停止失败。但并不意味着函数已经返回,

    // Calling sleep method
    time.Sleep(10 * time.Second)
}

select 语句

select会随机挑选一个可通信的case来执行,如果所有case都没有数据到达,则执行default,如果没有default语句,select就会阻塞,直到有case接收到数据。

func selectA(ch chan int) {
    for i := 0; i < 1; i++ {
       time.Sleep(3 * time.Second)
       ch <- i

    }
    defer close(ch)
}
func selectB(ch chan string) {
    for i := 0; i < 1; i++ {
       time.Sleep(time.Second)
       ch <- "A"
    }
    defer close(ch)
}

func main() {
    ints := make(chan int)
    strs := make(chan string)
    go selectA(ints)
    go selectB(strs)

    select {
    case data := <-ints:
       fmt.Println(data)
    case data := <-strs:
       fmt.Println(data)
    default:
       fmt.Println("default")
    }
}

运行时调整睡眠时间,可以看到结果会因为时间的不同而不同;

如果睡眠时间过长,那么select就会阻塞直到有一个case接收到值;

sync包

sync包提供了互斥锁

同步的sync是串行执行,异步的sync是同时执行。

Goroutine调用Add()方法来设置应等待Goroutine的数量。每个被等待的Goroutine在结束时应该调用Done()方法

WaitGroup --等待组

这个就像java中的countdownlatch

type WaitGroup struct {
    noCopy noCopy

    state atomic.Uint64 // high 32 bits are counter, low 32 bits are waiter count.
    sema  uint32
}

等待组等待一组 goroutines 完成。

func (wg *WaitGroup) Add(delta int) 

Add()方法向内部计数加上delta,delta可以是负数;如果内部计数变为0,Wait()方法阻塞等待的所有Goroutine都会释放,如果计数小于0,则该方法panic。注意Add()加上正数的调用应在Wait()之前,否则Wait()可能只会等待很少的Goroutine。

func (wg *WaitGroup) Done() {
    wg.Add(-1)
}

Done()方法减小WaitGroup计数器的值,应在Goroutine的最后执行

func (wg *WaitGroup) Wait() 

Wait()方法阻塞Goroutine直到WaitGroup计数减为0。

代码案例:

func printA(num int, group *sync.WaitGroup) {
    repeatA := strings.Repeat("A", num)
    fmt.Println(repeatA)
    //完成之后减1
    group.Done()
}

/**
互斥锁
*/

func mainlkj() {
    group := sync.WaitGroup{}
    group.Add(3)
    go printA(1, &group)
    go printA(2, &group)
    go printA(3, &group)
    //执行到这等待
    group.Wait()
    //main的 Groutine 睡眠,确保其他go的Groutine能正常执行完毕
    time.Sleep(10 * time.Second)
    fmt.Println("main -- exit")

}

Mutex–互斥锁

type Mutex struct {
    state int32
    sema  uint32
}

Mutex是一个互斥锁,可以创建为其他结构体的字段;零值为解锁状态。Mutex类型的锁和Goroutine无关,可以由不同的Goroutine加锁和解锁。

/*
互斥锁实现售票服务
*/
var tickets int = 20
var wg sync.WaitGroup
var mutex sync.Mutex

func saleTickets(name string, wg *sync.WaitGroup) {
    defer wg.Done()
    for {
       //加锁
       mutex.Lock()
       if tickets > 0 {
          time.Sleep(time.Second)
          num, _ := strconv.Atoi(name[:1])
          per := strings.Repeat("---", num)
          fmt.Println(per, name, tickets)
          //买票
          tickets--

       } else {

          fmt.Printf("%s 卖光了 \n", name)
          mutex.Unlock()
          break
       }
       mutex.Unlock()
    }

}

func main() {
    wg.Add(3)
    go saleTickets("1号窗口", &wg)
    go saleTickets("2号窗口", &wg)
    go saleTickets("3号窗口", &wg)
    wg.Wait()
    defer fmt.Println("exit")
}

RWMutex–读写锁

该锁可以同时被多个读取者持有或被唯一个写入者持有。RWMutex可以创建为其他结构体的字段;零值为解锁状态。RWMutex类型的锁也和Goroutine无关,可以由不同的Goroutine加读取锁/写入锁和解读取锁/写入锁。

代码案例:

// 读写锁
var rwMutex *sync.RWMutex
var wg1 *sync.WaitGroup

func mainrw() {
    rwMutex = new(sync.RWMutex)
    wg1 = new(sync.WaitGroup)
    //wg.Add(2)
    //
    多个同时读取
    //go readData(1)
    //go readData(2)
    wg.Add(3)
    go writeData(1)
    go readData(2)
    go writeData(3)
    wg.Wait()
    fmt.Println("main..over...")
}
func writeData(i int) {
    defer wg.Done()
    fmt.Println(i, "开始写:write start。。")
    rwMutex.Lock() //写操作上锁
    fmt.Println(i, "正在写:writing。。。。")
    time.Sleep(3 * time.Second)
    rwMutex.Unlock()
    fmt.Println(i, "写结束:write over。。")
}
func readData(i int) {
    defer wg.Done()
    fmt.Println(i, "开始读:read start。。")
    rwMutex.RLock() //读操作上锁
    fmt.Println(i, "正在读取数据:reading。。。")
    time.Sleep(3 * time.Second)
    rwMutex.RUnlock() //读操作解锁
    fmt.Println(i, "读结束:read over。。。")
}

Cond—条件变量

每个Cond实例都有一个相关的锁(一般是Mutex或RWMutex类型的值),它须在改变条件时或者调用Wait()方法时保持锁定。Cond可以创建为其他结构体的字段,Cond在开始使用后不能被复制。条件变量sync.Cond是多个Goroutine等待或接受通知的集合地。

type Cond struct {
    noCopy noCopy

    // L is held while observing or changing the condition
    L Locker

    notify  notifyList
    checker copyChecker
}

每个线程都有一个关联的锁L(通常是Mutex或RWMutex);

当改变条件和时必须持有调用Wait方法时。

使用锁l创建一个*Cond。Cond条件变量总是要和锁结合使用。

func (c *Cond) Signal() //唤醒一个goroutine
func (c *Cond) Broadcast() //唤醒所有等待的goroutine  
func (c *Cond) Wait()//阻塞

Wait()自行解锁c.L并阻塞当前Goroutine,待线程恢复执行时,Wait()方法会在返回前锁定c.L。和其他系统不同,Wait()除非被Broadcast()或者Signal()唤醒,否则不会主动返回。此方法广播给所有人

代码案例:

func mainjkjhu() {
    str := "ABCDEFG"
    s2 := []byte(str)
    encodeToString := hex.EncodeToString(s2)
    fmt.Println(encodeToString)

    decodeString, _ := hex.DecodeString(encodeToString)
    fmt.Println(decodeString)

    //声明一个锁
    s3 := sync.Mutex{}
    //声明一个条件变量
    cond := sync.Cond{
       L: &s3,
    }

    flag := false
    go func() {
       time.Sleep(time.Second)
       cond.L.Lock()
       fmt.Println("子goroutine被锁定")
       fmt.Println("子goroutine更改条件数值,并发送通知")
       flag = true
       cond.Signal() //唤醒一个goroutine
       fmt.Println("子goroutine继续执行")
       time.Sleep(5 * time.Second)
       fmt.Println("子goroutine解锁")
       cond.L.Unlock()
    }()

    cond.L.Lock()
    fmt.Println("main 已被锁定")
    if !flag {
       fmt.Println("main在等待")
       cond.Wait()
       fmt.Println("main被唤醒")
    }
    fmt.Println("main继续")
    fmt.Println("main解锁")
    cond.L.Unlock()

}

func main() {
    //一把锁
    s2 := sync.Mutex{}
    //一个条件变量
    cond := sync.Cond{L: &s2}
    var num = 0
    go func() {
       //锁定goroutine
       cond.L.Lock()
       //在这里进行一些操作
       fmt.Println("在这里进行一些操作")
       for i := 0; i < 10; i++ {
          num = i
          if num == 6 {
             cond.Signal()
          }
          fmt.Println(num)
       }
       cond.L.Unlock()
    }()

    cond.L.Lock()
    fmt.Println("main的goroutine被锁定了")

    time.Sleep(5 * time.Second)
    cond.Wait()
    fmt.Println("main goroutine恢复执行")
    cond.L.Unlock()
    fmt.Println("main --exit")


}

base64编码

encodeToString := base64.StdEncoding.EncodeToString([]byte("尔曹身与名俱灭"))
fmt.Println(encodeToString)
decodeString, _ := base64.StdEncoding.DecodeString(encodeToString)
fmt.Println(string(decodeString))

加密解密

lue

Beego框架

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Beego组成

Beego由四个部分组成:

  1. 基础模块:包括日志模块、配置模块、调速器模块;
  2. 任务:用于运行定时任务或周期性任务;
  3. 客户端:包括ORM模块、httplib模块、缓存模块;
  4. 服务器:包括网络模块。我们将来支持 gRPC;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

使用beego开启一个web服务

func main() {
   beego.Run("localhost:8080")
	//web.Run("localhost:8080")
}

源码:

// Run beego application.
// beego.Run() default run on HttpPort
// beego.Run("localhost")
// beego.Run(":8089")
// beego.Run("127.0.0.1:8089")
func Run(params ...string) {
    if len(params) > 0 && params[0] != "" {
       BeeApp.Run(params[0])
    } else {
       BeeApp.Run("")
    }
}

beego常用命令:

  1. bee new projectname 创建新的web项目
  2. bee api projectname 创建开发API项目
  3. bee run 运行项目
  4. be pack 打包操作
  5. bee version 查看bee ,beego, go版本

搭建web项目

从头搭建一个web项目:

1,新建文件夹 beego-learn-->src

2,在beego-learn下执行 go mod init beego-learn

3,引入需要的依赖:

go get -u github.com/beego/bee/v2@master

go get -u github.com/go-sql-driver/mysql

之后安装依赖使其全局可用

go install github.com/beego/bee/v2@master

之后执行bee version @A


C:\Users\issuser>bee version
2023/08/25 15:03:31.622 [D]  init global config instance failed. If you do not use this, just ignore it.  open conf/app.conf: The system cannot find the path specified.
2023/08/25 15:03:31 INFO     ▶ 0001 Getting bee latest version...
2023/08/25 15:03:32 INFO     ▶ 0002 Your bee are up to date
______
| ___ \
| |_/ /  ___   ___
| ___ \ / _ \ / _ \
| |_/ /|  __/|  __/
\____/  \___| \___| v2.1.0

├── GoVersion : go1.21.0
├── GOOS      : windows
├── GOARCH    : amd64
├── NumCPU    : 8
├── GOPATH    : C:\Users\issuser\go
├── GOROOT    : C:\Program Files\Go
├── Compiler  : gc
└── Date      : Friday, 25 Aug 2023

创建web项目

bee new hello

命令执行后会生成一下目录:

conf/
controllers/
go.mod
go.sum
main.go
models/
routers/
static/
tests/
views/

运行beego生成的文件


import (
	beego "github.com/beego/beego/v2/server/web"
	_ "hello/routers"
)

func main() {
	beego.Run("localhost:8080")
	//web.Run("localhost:8080")  //如果不起别名,则使用该方法
	
}

运行后访问localhost:8080

bee 工具命令

Bee is a Fast and Flexible tool for managing your Beego Web Application.

Usage:

	bee command [arguments]

The commands are:

    version     show the bee & beego version
    migrate     run database migrations
    api         create an api application base on beego framework
    bale        packs non-Go files to Go source files
    new         create an application base on beego framework
    run         run the app which can hot compile
    pack        compress an beego project
    fix         Fixes your application by making it compatible with newer versions of Beego
    dlv         Start a debugging session using Delve
    dockerize   Generates a Dockerfile for your Beego application
    generate    Source code generator
    hprose      Creates an RPC application based on Hprose and Beego frameworks
    pack        Compresses a Beego application into a single file
    rs          Run customized scripts
    run         Run the application by starting a local development server
    server      serving static content over HTTP on port

Use bee help [command] for more information about a command.
beego命令详解:
bee new

bee new projectname 创建web项目

new 命令是新建一个 Web 项目,我们在命令行下执行 bee new <项目名> 就可以创建一个新的项目。但是注意该命令必须在 $GOPATH/src 下执行。最后会在 $GOPATH/src

bee api

bee api projectname创建API项目

conf/
controllers/
go.mod
go.sum
main.go
models/
routers/
tests/

对比web项目,少了static 跟view目录,多了一个test模块

import (
    beego "github.com/beego/beego/v2/server/web"
    _ "hello/routers"
)

func main() {
    if beego.BConfig.RunMode == "dev" {
       beego.BConfig.WebConfig.DirectoryIndex = true
       beego.BConfig.WebConfig.StaticDir["localhost:8080/swagger"] = "swagger"
    }
    beego.Run()
}

通过main函数发现跟api文档有关;

同时,该命令还支持一些自定义参数自动连接数据库创建相关 model 和 controller: bee api [appname] [-tables=""] [-driver=mysql] [-conn="root:<password>@tcp(127.0.0.1:3306)/test"] 如果 conn 参数为空则创建一个示例项目,否则将基于链接信息链接数据库创建项目。

bee run

监控beego项目,注意该命令需要在appname文件夹下运行

bee run


PS D:\beegodata\beego-learn> cd src
PS D:\beegodata\beego-learn\src> cd .\hello\
PS D:\beegodata\beego-learn\src\hello> bee run
______
| ___ \
| |_/ /  ___   ___
| ___ \ / _ \ / _ \
| |_/ /|  __/|  __/
\____/  \___| \___| v2.1.0
2023/08/25 16:14:59 WARN     ▶ 0001 Running application outside of GOPATH
2023/08/25 16:14:59 INFO     ▶ 0002 Using 'hello' as 'appname'
2023/08/25 16:14:59 INFO     ▶ 0003 Initializing watcher...
2023/08/25 16:15:02 SUCCESS  ▶ 0004 Built Successfully!
2023/08/25 16:15:02 INFO     ▶ 0005 Restarting 'hello.exe'...
2023/08/25 16:15:02 SUCCESS  ▶ 0006 './hello.exe' is running...
2023/08/25 16:15:02.930 [I] [server.go:281]  http server Running on http://localhost:8080

如果修改静态文件内容,不需要重启 --比如修改页面显示文字

我们修改 default.go(Controller文件夹下)

源文件:

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

修改后:

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

此时会进入热部署模式,我们修改内容不需要重启;

修改端口为8081 —也不需要手动运行,他会自己运行

func main() {
    beego.Run("localhost:8081")
  
    //web.Run("localhost:8080")  //如果不起别名,则使用该方法

}

在控制台处可以看到如下:

hello/controllers
2023/08/26 09:31:58.866 [I] [server.go:281]  http server Running on http://localhost:8081
2023/08/26 09:32:06.696 [D] [router.go:1305]  |      127.0.0.1| 404 |     1.0024ms| nomatch| GET      /singer
2023/08/26 09:32:06.799 [D] [router.go:1305]  |      127.0.0.1| 404 |     1.3419ms| nomatch| GET      /favicon.ico
2023/08/26 09:32:10.648 [D] [router.go:1305]  |      127.0.0.1| 200 |     2.1944ms|   match| GET      /     r:/
2023/08/26 09:32:10.686 [D] [router.go:1305]  |      127.0.0.1| 200 |      602.2µs|   match| GET      /static/js/reload.min.js     

启动项目和启动bee run

bee version

这个命令是动态获取 bee、beego 和 Go 的版本,这样一旦用户出现错误,可以通过该命令来查看当前的版本

PS D:\GolandData\hello> bee version
______               
| ___ \              
| |_/ /  ___   ___   
| ___ \ / _ \ / _ \  
| |_/ /|  __/|  __/  
\____/  \___| \___| v2.1.0
                          
├── GoVersion : go1.21.0  
├── GOOS      : windows   
├── GOARCH    : amd64
├── NumCPU    : 8
├── GOPATH    : D:\GolandData
├── GOROOT    : D:/Program Files/Go
├── Compiler  : gc
└── Date      : Saturday, 26 Aug 2023

bee generate

这个命令是用来自动化的生成代码的,包含了从数据库一键生成 model,还包含了 scaffold 的

  • generate scaffold

用于生成数据库表,创建实体类,

bee generate scaffold Consumer -fields="id:int64,name:string,gender:int,age:int" -driver=mysql -conn="root:*****
**@tcp(ip:3306)/gmusic"

(注意,生成的实体类首字母是大写的,不然别的包也没法访问)

执行命令之后

PS D:\GolandData\hello>bee generate scaffold Consumer -fields="id:int64,name:string,gender:int,age:int" -driver=mysql -conn="root:*****
**@tcp(ip:3306)/gmusic"
______               
| ___ \              
| |_/ /  ___   ___   
| ___ \ / _ \ / _ \  
| |_/ /|  __/|  __/  
\____/  \___| \___| v2.1.0                                                             
2023/08/26 09:57:02 INFO     ▶ 0001 Do you want to create a 'Consumer' model? [Yes|No]   //创建model
Yes

2023/08/26 09:57:08 INFO     ▶ 0002 Using 'Consumer' as model name
2023/08/26 09:57:08 INFO     ▶ 0003 Using 'models' as package name
        create   D:\GolandData\hello/models/Consumer.go 
2023/08/26 09:57:08 INFO     ▶ 0004 Do you want to create a 'Consumer' controller? [Yes|No] //创建controller

Yes
2023/08/26 09:57:19 INFO     ▶ 0005 Using 'Consumer' as controller name
2023/08/26 09:57:19 INFO     ▶ 0006 Using 'controllers' as package name
2023/08/26 09:57:19 INFO     ▶ 0007 Using matching model 'Consumer'
        create   D:\GolandData\hello/controllers/Consumer.go
2023/08/26 09:57:19 INFO     ▶ 0008 Do you want to create views for this 'Consumer' resource? [Yes|No] //创建view层

Yes
2023/08/26 09:57:25 INFO     ▶ 0009 Generating view...
        create   D:\GolandData\hello/views/Consumer/index.tpl
        create   D:\GolandData\hello/views/Consumer/show.tpl
        create   D:\GolandData\hello/views/Consumer/create.tpl
        create   D:\GolandData\hello/views/Consumer/edit.tpl
2023/08/26 09:57:25 INFO     ▶ 0010 Do you want to create a 'Consumer' migration and schema for this resource? [Yes|No] //创建镜像表

Yes
        create   D:\GolandData\hello/database/migrations/20230826_095731_Consumer.go

2023/08/26 09:57:31 INFO     ▶ 0011 Do you want to migrate the database? [Yes|No] //创建镜像
Yes
2023/08/26 09:57:38 INFO     ▶ 0012 Creating 'migrations' table...
2023/08/26 09:57:43 INFO     ▶ 0013 |> 2023/08/26 09:57:41.755 [I]  start upgrade Consumer_20230826_095731
2023/08/26 09:57:43 INFO     ▶ 0014 |> 2023/08/26 09:57:41.755 [I]  exec sql: CREATE TABLE Consumer(`id` int(11) DEFAULT NULL,`name
` varchar(128) NOT NULL,`gender` int(11) DEFAULT NULL,`age` int(11) DEFAULT NULL)
2023/08/26 09:57:43 INFO     ▶ 0015 |> 2023/08/26 09:57:41.797 [I]  end upgrade: Consumer_20230826_095731
2023/08/26 09:57:43 INFO     ▶ 0016 |> 2023/08/26 09:57:41.797 [I]  total success upgrade: 1  migration
2023/08/26 09:57:43 SUCCESS  ▶ 0017 All done! Don't forget to add  beego.Router("/Consumer" ,&controllers.ConsumerController{}) to 
routers/route.go

2023/08/26 09:57:43 SUCCESS  ▶ 0018 Scaffold successfully generated!

最后的目录结构如下:

│  go.mod
│  go.sum
│  main.go
│
├─conf
│      app.conf
│
├─controllers
│      Consumer.go //beego生成的controller
│      default.go
│
├─database
│  └─migrations //镜像
│          20230826_095731_Consumer.go
│
├─models
│      Consumer.go  //model
│
├─routers
│      router.go
│
├─static
│  ├─css
│  ├─img
│  └─js
│          reload.min.js
│
├─tests
│      default_test.go
│
└─views
    │  index.tpl
    │
    └─Consumer  //view层
            create.tpl
            edit.tpl
            index.tpl
            show.tpl

如果想要使用生成的controller,需要修改一下路由

源 router.go

func init() {
	beego.Router("/", &controllers.MainController{})
}

修改后:

func init() {
	//beego.Router("/", &controllers.MainController{})
beego.Include(&controllers.ConsumerController{})
}

  • generate model

​ 只生成model层

bee generate model [modelname] [-fields=""]
  • generate controller

​ 只生成controller

bee generate controller [controllerfile]
  • generate view

    只生成试图

bee generate view [viewpath]
  • generate migration

​ 只生成镜像

bee generate migration [migrationfile] [-fields=""]
  • generate docs

​ 生成doc文档,一般在 bee api projectname中使用

bee generate docs
  • generate routers

generate routers 是从原来beego中剥离出来的功能。在早期,beego的项目必须在启动的时候才会触发生成路由文件。

bee generate routers [-ctrlDir=/path/to/controller/directory] [-routersFile=/path/to/routers/file.go] [-routersPkg=myPackage]
bee migrate

这个命令是应用的数据库迁移命令,主要是用来每次应用升级,降级的 SQL 管理。

bee migrate [-driver=mysql] [-conn="root:@tcp(127.0.0.1:3306)/test"]
    run all outstanding migrations
    -driver: [mysql | postgresql | sqlite], the default is mysql
    -conn:   the connection string used by the driver, the default is root:@tcp(127.0.0.1:3306)/test

bee migrate rollback [-driver=mysql] [-conn="root:@tcp(127.0.0.1:3306)/test"]
    rollback the last migration operation
    -driver: [mysql | postgresql | sqlite], the default is mysql
    -conn:   the connection string used by the driver, the default is root:@tcp(127.0.0.1:3306)/test

bee migrate reset [-driver=mysql] [-conn="root:@tcp(127.0.0.1:3306)/test"]
    rollback all migrations
    -driver: [mysql | postgresql | sqlite], the default is mysql
    -conn:   the connection string used by the driver, the default is root:@tcp(127.0.0.1:3306)/test

bee migrate refresh [-driver=mysql] [-conn="root:@tcp(127.0.0.1:3306)/test"]
    rollback all migrations and run them all again
    -driver: [mysql | postgresql | sqlite], the default is mysql
    -conn:   the connection string used by the driver, the default is root:@tcp(127.0.0.1:3306)/test

bee dockerize

这个命令可以通过生成 Dockerfile 文件来实现 docker 化你的应用。

例子:
生成一个以 1.6.4 版本 Go 环境为基础镜像的 Dockerfile,并暴露 9000 端口:

$ bee dockerize -image="library/golang:1.6.4" -expose=9000
______
| ___ \
| |_/ /  ___   ___
| ___ \ / _ \ / _ \
| |_/ /|  __/|  __/
\____/  \___| \___| v1.6.2
2016/12/26 22:34:54 INFO     ▶ 0001 Generating Dockerfile...
2016/12/26 22:34:54 SUCCESS  ▶ 0002 Dockerfile generated.

Web项目拆解

router路由

好了,上面是讲了beego搭建一个web项目,但是这个项目是怎么运行的呢?

首先,按照以往的经验 mvc层,我们先来研究一下Controller层,他是怎么访问的:

需要注意的是,控制器里面处理http请求的方法必须是公共方法——即首字母大写,并且没有参数,没有返回值。如果你的方法不符合这个要求,大多数情况下,会发生panic

方法接收器不是指针 也是可以的,但是最好是指针以节省内存空间

1.default.go文件

package controllers

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

type MainController struct {
	beego.Controller
}

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

里面定义了一个结构体 MainController,然后结构体里面有匿名结构体beego.Controller,该结构体实现了ControllerInterface一些方法

这个控制器里面内嵌了 beego.Controller,这就是 Go 的嵌入方式,也就是 MainController 自动拥有了所有 beego.Controller 的方法。而 beego.Controller 拥有很多方法,其中包括 InitPreparePostGetDeleteHead 等方法。我们可以通过重写的方式来实现这些方法,


// ControllerInterface is an interface to uniform all controller handler.
type ControllerInterface interface {
	Init(ct *context.Context, controllerName, actionName string, app interface{})
	Prepare()
	Get()
	Post()
	Delete()
	Put()
	Head()
	Patch()
	Options()
	Trace()
	Finish()
	Render() error
	XSRFToken() string
	CheckXSRFCookie() bool
	HandlerFunc(fn string) bool
	URLMapping()
}

so,照葫芦画瓢呗,

在controller包中新建一个goods.go文件;

文件内容如下:

package controllers

import (
   "database/sql"
   "fmt"
   beego "github.com/beego/beego/v2/server/web"
   _ "github.com/go-sql-driver/mysql"
)

//继承 beego.Controller

type GoodsController struct {
   beego.Controller
}

type Goods struct {
   goodsid    int64
   goodsname  string
   goodsprice int64
   goodsdesc  string
}

//实现一些方法

func (g *GoodsController) Get() {
   //连接数据库查询数据 ---匿名导入驱动
   /**
   匿名导入驱动是只需要执行那个 init()方法,获得连接,而不需要包中其他数据
   */
   dbConn, err := sql.Open("mysql", "root:955945@tcp(localhost:3306)/gavin?charset=utf8")

   defer dbConn.Close()
   //预编译
   stmt, err := dbConn.Prepare("select * from goods where goodsid =?")
   queryResult, err := stmt.Query(12)
   var goods Goods
   for queryResult.Next() {
   	queryResult.Scan(&goods.goodsid, &goods.goodsname, &goods.goodsprice, &goods.goodsdesc)
   	g.Data["12号商品信息"] = goods
   }
   g.TplName = "goods.tpl"
   if err != nil {
   	panic(err)
   }
   defer func() {
   	if msg := recover(); msg != nil {
   		fmt.Println(msg)
   		g.Data["连接失败"] = "失败"
   	}
   }()
}

接着在router文件夹下router文件中添加路由:

package routers

import (
	beego "github.com/beego/beego/v2/server/web"
	"hello/controllers"
)

func init() {
	beego.Router("/", &controllers.MainController{})
	beego.Router("/goods", &controllers.GoodsController{})//默认是get方法访问
 
}

然后在view包下新建一个关于goods.tpl的模板页面

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Beego</title>
</head>
<body>
<h3>这是一个商品模块</h3>
<br>
<p>{{.title}}</p>
</body>
</html>

接着运行一下:

访问之后得到下面的页面:


<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Beego</title>
</head>
<body>
<h3>这是一个商品模块</h3>
<br>
<p>{12 肥肠鱼 46 真好吃}</p>
</body>
</html>

定义其他路由路径:


func (g *GoodsController) GetAll() {
	coon, err := getSqlCoon()
	if err != nil {
		fmt.Println(err.Error())
	}
	num, err := g.GetInt("num", 1)
	prepare, err := coon.Prepare("select *  from goods limit ? ")
	queryResult, err := prepare.Query(num)
	var goods Goods
	for queryResult.Next() {
		queryResult.Scan(&goods.goodsid, &goods.goodsname, &goods.goodsprice, &goods.goodsdesc)

	}
	g.Data["title"] = goods
	g.TplName = "goods.tpl"
}

router.go

beego.Router("/allGoods", &controllers.GoodsController{}, "get:GetAll") //默认是get方法访问
//最后一个参数要与方法名匹配

当然还有其他方式来实现路由,比如bee generate生成的urlMapping


// // URLMapping ...
func (c *ConsumerController) URLMapping() {
	c.Mapping("Post", c.Post)
	c.Mapping("GetOne", c.GetOne)
	c.Mapping("GetAll", c.GetAll)
	c.Mapping("Put", c.Put)
	c.Mapping("Delete", c.Delete)
}

配置文件

我们先看配置文件

解析ini格式的配置文件

app.conf
appname = hello
httpport = 8080
runmode = dev

如过我们指定了配置,那么该配置以我们代码中指定的为准:

比如我们在运行时指定了端口号为8081

2023/08/26 16:48:11.002 [I] [server.go:281]  http server Runni
ng on http://localhost:8080
hello
2023/08/26 17:00:50 SUCCESS  ▶ 0028 Built Successfully!
2023/08/26 17:00:50 INFO     ▶ 0029 Restarting 'hello.exe'... 
2023/08/26 17:00:50 SUCCESS  ▶ 0030 './hello.exe' is running..
.
2023/08/26 17:00:50.923 [I] [server.go:281]  http server Runni
ng on http://localhost:8081

为了使用我们指定的配置参数,我们怎么去获得呢?

一个案例:----通过beego.AppConfig()

注:beego 其实是beego/web包的别名,

s, _ := beego.AppConfig.String("appname")
	fmt.Println("加载了参数--->>", s)
//加载了参数--->> hello

指定解析格式

//配置解析的格式	
err := beego.LoadAppConfig("ini", ConfigFile)
	if err != nil {
		logs.Critical("An error occurred:", err)
		panic(err)
	}
	s2, _ := beego.AppConfig.String("appname")
	logs.Info("加载了参数--->>", s2)

Beego 目前支持 INI、XML、JSON、YAML 格式的配置文件解析,也支持以 etcd 作为远程配置中心。默认采用了 INI 格式解析

关于beego中的配置文件,这里指明了我们可以用的方法


// Configer defines how to get and set value from configuration raw data.
type Configer interface {
	// Set support section::key type in given key when using ini type.
	Set(key, val string) error

	// String support section::key type in key string when using ini and json type; Int,Int64,Bool,Float,DIY are same.
	String(key string) (string, error)
	// Strings get string slice
	Strings(key string) ([]string, error)
	Int(key string) (int, error)
	Int64(key string) (int64, error)
	Bool(key string) (bool, error)
	Float(key string) (float64, error)
	// DefaultString support section::key type in key string when using ini and json type; Int,Int64,Bool,Float,DIY are same.
	DefaultString(key string, defaultVal string) string
	// DefaultStrings get string slice
	DefaultStrings(key string, defaultVal []string) []string
	DefaultInt(key string, defaultVal int) int
	DefaultInt64(key string, defaultVal int64) int64
	DefaultBool(key string, defaultVal bool) bool
	DefaultFloat(key string, defaultVal float64) float64

	// DIY return the original value
	DIY(key string) (interface{}, error)

	GetSection(section string) (map[string]string, error)

	Unmarshaler(prefix string, obj interface{}, opt ...DecodeOption) error
	Sub(key string) (Configer, error)
	OnChange(key string, fn func(value string))
	SaveConfigFile(filename string) error
}

案例:

//设置配置参数 
beego.AppConfig.Set("appname", "helloworld") //加载配置参数 
s, _ := beego.AppConfig.String("appname")  logs.Info("加载了参数--->>", s)
//[main.go:18]  加载了参数--->> helloworld

这里有一些使用的注意事项:

  1. 所有的Default*方法,在key不存在,或者查找的过程中,出现error,都会返回默认值;
  2. DIY直接返回对应的值,而没有做任何类型的转换。当你使用这个方法的时候,你应该自己确认值的类型。只有在极少数的情况下你才应该考虑使用这个方法;
  3. GetSection会返回section所对应的部分配置。section如何被解释,取决于具体的实现;
  4. Unmarshaler会尝试用当且配置的值来初始化obj。需要注意的是,prefix的概念类似于section
  5. Sub类似与GetSection,都是尝试返回配置的一部分。所不同的是,GetSection将结果组织成map,而Sub将结果组织成Config实例;
  6. OnChange主要用于监听配置的变化。对于大部分依赖于文件系统的实现来说,都不支持。具体而言,我们设计这个主要是为了考虑支持远程配置;
  7. SaveConfigFile尝试将配置导出成为一个文件;
  8. 某些实现支持分段式的key。比如说a.b.c这种,但是,并不是所有的实现都支持,也不是所有的实现都采用.作为分隔符。

也可以手动初始化全局实例,以指定不同的配置类型,例如说启用etcd

config.InitGlobalInstance("etcd", "etcd address")
自定义配置文件
	/**
	自定义文件配置
	*/
	newConfig, err := config.NewConfig("ini", "./conf/application.ini")
	if err != nil {
		logs.Error(err)
	}
	ss, _ := newConfig.String("appname")
	logs.Info("配置文件加载-->>", ss)

使用配置文件信息:

首先加载配置文件:—>>如果没指定,那么默认加载conf中的app.conf 使用config 来获得Configer对象

	newConfig, err := config.NewConfig("ini", "./conf/application.ini")

使用配置文件中的信息

	if err != nil {
		logs.Error(err)
	}
	ss, _ := newConfig.String("appname")
	logs.Info("配置文件加载-->>", ss)

	logs.Info("test-->", test)
	dataSourceInfo, _ := newConfig.String("driverName")
	_ = orm.RegisterDataBase("default", "mysql", dataSourceInfo)
	beego.Run("localhost:8080")

多环境配置

为不同开发环境设置不同的配置文件–>在app.conf中配置如下:

appname = hello
dataSourceInfo= root:955945@tcp(localhost:3306)/gmusic?charset=utf8
httpport = 8080
runmode = prod
world = world
[dev]
httpport= 8080
[prod]
httpport= 8081
[test]
httpport= 8082

在dev模式下使用8080,prod模式下使用8081

如果在app.conf之外的配置为文件中,这并不起作用

ini格式的配置支持include的方式,这就像springboot中的多配置文件一样;

多个配置文件
appname = hello
include "app-dev.conf"

只要符合ini格式的都可以

httpport = 8080
runmode = dev
world = world
dataSourceInfo = root:955945@tcp(localhost:3306)/gmusic?charset=utf8
autorender = false
recoverpanic = false
[dev]
httpport = 8080
[prod]
httpport = 8081
[test]
httpport = 8082

环境变量支持

配置文件解析支持从环境变量中获取配置项,配置项格式:${环境变量}。例如下面的配置中优先使用环境变量中配置的 runmode 和 httpport,如果有配置环境变量 ProRunMode 则优先使用该环境变量值。如果不存在或者为空,则使用 “dev” 作为 runmode

即从系统的环境变量中区获取值,如果没有值,则可以指定默认值:

httpport = 8080
runmode = dev
world = world
dataSourceInfo = root:password@tcp(localhost:3306)/gmusic?charset=utf8
autorender = false
recoverpanic = false
envMode = "${GOROOT}||default}"

可以从配置文件中获取变量

2023/08/26 21:36:18 INFO     ▶ 0001 Using 'hello' as 'appname'
2023/08/26 21:36:18 INFO     ▶ 0002 Initializing watcher...
2023/08/26 21:36:21 SUCCESS  ▶ 0003 Built Successfully!
2023/08/26 21:36:21 INFO     ▶ 0004 Restarting 'hello.exe'...
2023/08/26 21:36:21 SUCCESS  ▶ 0005 './hello.exe' is running...
2023/08/26 21:36:21.797 [I] [main.go:74]  D:/Program Files/Go

解析配置文件

解析json格式的配置文件

导入包:

// 千万不要忘了
	_ "github.com/beego/beego/v2/core/config/json"

方式一

config.InitGlobalInstance

	_ = config.InitGlobalInstance("json", "./conf/json/json-config.json")
	dataSourceConfig, _ := config.String("dataSourceInfo")
	logs.Info("配置信息-->", dataSourceConfig)
	fmt.Println(dataSourceConfig)

方式二

config.NewConfig

	//使用config.NewConfig来创建一个配置 Configer 对象
	newConfig, _ := config.NewConfig("json", "./conf/json/json-config.json")
	s, _ := newConfig.String("dataSourceInfo")
	logs.Info("配置信息-->", s)

以上两种方式对其他格式配置文件也适用

解析yml/yaml格式的配置文件

导入依赖

_ "github.com/beego/beego/v2/core/config/json"

案例:


	newConfig1, _ := config.NewConfig("yaml", "./conf/yml/application.yaml")
	s1, _ := newConfig1.String("dataSourceInfo")
	logs.Info("配置信息s-->", s1)
	_ = config.InitGlobalInstance("yaml", "./conf/yml/application.yaml")
	s2, _ := config.String("dataSourceInfo")
	logs.Info("配置信息s2-->", s2)

	_ = config.InitGlobalInstance("yaml", "./conf/yml/application.yml")
	s3, _ := config.String("dataSourceInfo")
	logs.Info("配置信息s3-->", s3)


解析xml文件

导入依赖

	_ "github.com/beego/beego/v2/core/config/xml"
 

案例

	xmlConf, _ := config.NewConfig("xml", "./conf/xml/app.xml")
	s4, _ := xmlConf.String("dataSourceInfo")
	logs.Info("配置信息s4-->", s4)
	_ = config.InitGlobalInstance("xml", "./conf/xml/app.xml")
	s5, _ := config.String("dataSourceInfo")
	logs.Info("配置信息s5-->", s5)

要注意,所有的配置项都要放在config这个顶级节点之内:

其他格式的以后用到在研究;

路由配置

关于路由的配置,我们在routers文件下的route.go下进行配置

controller的名字:

我们定义了一个UserController,那么Controller的名字就是User。如果大小写不敏感,那么user也是合法的名字。

方式一

自动路由

为什么要提到controller的名字呢?因为我们路由的解析规则跟controller的名字和方法名共同决定;

其中UserController它的名字是User,而方法名字是HelloWorld。那么:

  • 如果RouterCaseSensitivetrue,那么AutoRouter会注册两个路由,/user/helloworld/*/User/HelloWorld/*
  • 如果RouterCaseSensitivefalse,那么会注册一个路由,/user/helloworld/*

来看路由的配置文件

package routers

import (
	beego "github.com/beego/beego/v2/server/web"
	"hello/controllers"
)

func init() {
	beego.Router("/", &controllers.MainController{})
	beego.Router("/goods", &controllers.GoodsController{})                  //默认是get方法访问
	beego.Router("/allGoods", &controllers.GoodsController{}, "get:GetAll") //默认是get方法访问
	//配置路由忽略大小写s
	beego.AutoRouter(&controllers.GoodsController{})
	//beego.Router("/hello", )
	//beego.Include(&controllers.ConsumerController{})
}

配置自动路由:

beego.BConfig.RouterCaseSensitive = true
	beego.AutoRouter(&controllers.GoodsController{})

实际上我们不配置路由时,默认也是true

	// RouterCaseSensitive
	// @Description If it was true, it means that the router is case sensitive.
	// For example, when you register a router with pattern "/hello",
	// 1. If this is true, and the request URL is "/Hello", it won't match this pattern
	// 2. If this is false and the request URL is "/Hello", it will match this pattern
	// @Default true
	RouterCaseSensitive bool

在实际开发过程中我们一般是不适用自动路由的,而是我们自己指定路由;

方式二

controller层中

此时我们需要在controller层中添加路由规则:

func init() {
	beego.CtrlGet("/goods", (*GoodsController).Get)
	beego.CtrlGet("/allGoods", (*GoodsController).GetAll)

}

完整代码如下:

package controllers

import (
	"database/sql"
	"fmt"
	beego "github.com/beego/beego/v2/server/web"
	_ "github.com/go-sql-driver/mysql"
)

//继承 beego.Controller

type GoodsController struct {
	beego.Controller
}

type Goods struct {
	goodsid    int64
	goodsname  string
	goodsprice int64
	goodsdesc  string
}

func init() {
	beego.CtrlGet("/goods", (*GoodsController).Get)
	beego.CtrlGet("/allGoods", (*GoodsController).GetAll)

}

//实现一些方法

func (g *GoodsController) Get() {
	//连接数据库查询数据 ---匿名导入驱动
	/**
	匿名导入驱动是只需要执行那个 init()方法,获得连接,而不需要包中其他数据
	*/
	//dbConn, err := sql.Open("mysql", "root:955945@tcp(localhost:3306)/gavin?charset=utf8")
	dbConn, err := getSqlCoon()
	defer dbConn.Close()
	//预编译
	stmt, err := dbConn.Prepare("select * from goods where goodsid =?")
	queryResult, err := stmt.Query(12)
	var goods Goods
	for queryResult.Next() {
		queryResult.Scan(&goods.goodsid, &goods.goodsname, &goods.goodsprice, &goods.goodsdesc)

	}
	g.Data["title"] = goods
	g.TplName = "goods.tpl"
	if err != nil {
		panic(err)
	}
	defer func() {
		if msg := recover(); msg != nil {
			fmt.Println(msg)
			g.Data["title"] = "连接失败"
		}
	}()
}

/**
包装一个连接对象
*/

func getSqlCoon() (*sql.DB, error) {
	dbConn, err := sql.Open("mysql", "root:955945@tcp(localhost:3306)/gavin?charset=utf8")
	return dbConn, err
}

func (g *GoodsController) GetAll() {
	coon, err := getSqlCoon()
	if err != nil {
		fmt.Println(err.Error())
	}
	num, err := g.GetInt("num", 1)
	prepare, err := coon.Prepare("select *  from goods limit ? ")
	queryResult, err := prepare.Query(num)
	var goods Goods
	for queryResult.Next() {
		queryResult.Scan(&goods.goodsid, &goods.goodsname, &goods.goodsprice, &goods.goodsdesc)

	}
	g.Data["title"] = goods
	g.TplName = "goods.tpl"
}

当然我们也可以写在main方法中

	beego.CtrlGet("/goods", (*controllers.GoodsController).Get)
	beego.CtrlGet("/allGoods", (*controllers.GoodsController).GetAll)
	

注意,如果直接在controller中进行路由设置不使用指针的方式—>>
编译的时候会给予提示:

invalid method expression GoodsCo
ntroller.GetAll (needs pointer receiver (*GoodsController).GetAll)
方式三

router层中

如果直接在controller中进行路由设置,这样会使得controller中就很乱,所以我们还是采取在router中进行路由设置—>>

如下:

package routers

import (
	beego "github.com/beego/beego/v2/server/web"
	"hello/controllers"
)

func init() {
	//配置路由忽略大小写
//beego.AutoRouter(&controllers.GoodsController{})
	//beego.AutoPrefix("/goods", &controllers.GoodsController{})
	beego.Router("/", &controllers.MainController{})
	beego.Router("/goods", &controllers.GoodsController{})                  //默认是get方法访问
	beego.Router("/allGoods", &controllers.GoodsController{}, "get:GetAll") //默认是get方法访问

	//beego.Router("/hello", )
	//beego.Include(&controllers.ConsumerController{})
}
方式四

函数式风格

使用http/net包中的路由方式,只不过这个Context是beego中封装的

	/*
		路由方式三:
			函数式路由
			参数:	rootpath string, f HandleFunc
			第二个参数是一个handlefunc,这在http/net包中了解过,一个请求的处理器
			匿名函数的参数 type HandleFunc func(ctx *beecontext.Context)
	*/
	beego.Get("/auth", func(ctx *context.Context) {
		ctx.WriteString("hello-world")
	})

所以我们只要写函数即可

// HandleFunc define how to process the request 
type HandleFunc func(ctx *beecontext.Context) 
方式五
命名空间–路由

在一个go项目里要想给每一个controller加上访问的前缀:

方法一,给每一个注册的路由上加入前缀

此种方式略

方法二:设置命名空间


	/**
	配置命名空间

	*/
	goodsController := &controllers.GoodsController{}
	namespace := beego.NewNamespace("/v1",
		beego.NSCtrlGet("/", (*controllers.MainController).Get),
		beego.NSRouter("goods", goodsController, "get:Get"),
		beego.NSRouter("allGoods", goodsController, "get:GetAll"),
		beego.NSGet("/hello", func(ctx *context.Context) {
			ctx.WriteString("函数式路由")
		}), beego.NSGet("/hello", helloFunc),
	)
	//注册namespace ---必不可少
	beego.AddNamespace(namespace)
//我们可以添加多个namespace

访问:http://localhost:8088/v1/goods


<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Beego</title>
</head>
<body>
<h3>这是一个商品模块</h3>
<br>
<p>{12 肥肠鱼 46 真好吃}</p>
</body>
</html

命名空间的访问方式:

v1/**/

注意:以上路由都可以生效,但是如分散到各处就不好管理了;我们可以写在router.go的init方法中以方便管理

嵌套命名空间
	goodsController := &controllers.GoodsController{}
	namespace := beego.NewNamespace("/v1",
		beego.NSCtrlGet("/", (*controllers.MainController).Get),
		beego.NSRouter("goods", goodsController, "get:Get"),
		beego.NSRouter("allGoods", goodsController, "get:GetAll"),
		beego.NSGet("/hello", func(ctx *context.Context) {
			ctx.WriteString("函数式路由")
		}), beego.NSGet("/hello", helloFunc),
		//嵌套命名空间
		beego.NSNamespace("/v2",
			beego.NSGet("/hello", helloFunc)),
	)
	//注册namespace
	beego.AddNamespace(namespace)

访问:http://localhost:8088/v1/v2/hello

结果:

<html><head><link rel="stylesheet" href="resource://content-accessible/plaintext.css"></head><body><pre>hello-world</pre></body></html>
方式六~[推荐]

注解路由

,我们在后端开发时常常不用go自带的tpl模板文件而是直接返回数据;

查看已注册的路由

如果开发时没有组织好代码,比如路由配置在各个controller中,beego提供了一个查看所有已注册路由的方法:

	beego.BConfig.RouterCaseSensitive = false
	beego.AutoRouter(&controllers.GoodsController{})
	tree := beego.PrintTree()
	m := tree["Data"].(beego.M)
	for k, v := range m {
		fmt.Printf("%s=> %v\n", k, v)
	}

结果如下:

CONNECT=> &[[/goods/getall/* map[*:GetAll] controllers.GoodsController] [/goods map[] controllers.GoodsController] [/ map[] control
lers.MainController]]
UNLOCK=> &[[/goods/getall/* map[*:GetAll] controllers.GoodsController] [/goods map[] controllers.GoodsController] [/ map[] controll
ers.MainController]]
POST=> &[[/goods/getall/* map[*:GetAll] controllers.GoodsController] [/goods map[] controllers.GoodsController] [/ map[] controller
s.MainController]]
MKCOL=> &[[/goods/getall/* map[*:GetAll] controllers.GoodsController] [/goods map[] controllers.GoodsController] [/ map[] controlle
rs.MainController]]
OPTIONS=> &[[/goods/getall/* map[*:GetAll] controllers.GoodsController] [/goods map[] controllers.GoodsController] [/ map[] control
lers.MainController]]
PUT=> &[[/goods/getall/* map[*:GetAll] controllers.GoodsController] [/goods map[] controllers.GoodsController] [/ map[] controllers
.MainController]]
TRACE=> &[[/goods/getall/* map[*:GetAll] controllers.GoodsController] [/goods map[] controllers.GoodsController] [/ map[] controlle
rs.MainController]]
DELETE=> &[[/goods/getall/* map[*:GetAll] controllers.GoodsController] [/goods map[] controllers.GoodsController] [/ map[] controll
ers.MainController]]
MOVE=> &[[/goods/getall/* map[*:GetAll] controllers.GoodsController] [/goods map[] controllers.GoodsController] [/ map[] controller
s.MainController]]
PROPPATCH=> &[[/goods/getall/* map[*:GetAll] controllers.GoodsController] [/goods map[] controllers.GoodsController] [/ map[] contr
ollers.MainController]]
PROPFIND=> &[[/goods/getall/* map[*:GetAll] controllers.GoodsController] [/goods map[] controllers.GoodsController] [/ map[] contro
llers.MainController]]
rs.MainController]]
HEAD=> &[[/goods/getall/* map[*:GetAll] controllers.GoodsController] [/goods map[] controllers.GoodsController] [/ map[] controller
s.MainController]]
GET=> &[[/goods/getall/* map[*:GetAll] controllers.GoodsController] [/goods map[] controllers.GoodsController] [/allGoods map[GET:G
etAll] controllers.GoodsController] [/auth map[GET:GET] ] [/ map[] controllers.MainController]]

beego支持的数据输出方式

所以我们需要了解beego框架是怎么接收参数以及接收参数处理后的返回形式有哪些:

公用代码部分


func (u *UserController) GetUserById() {
	coon, err := getSqlCoon()
	//接收参数
	uid := u.GetString("id")
	//调用方法,这里直接开搞
	if err != nil {
		log.Error(err)
		panic(err)
	}
	defer func() {
		if msg := recover(); msg != nil {
			logs.Info("发生了err", msg)
		}
	}()
	stmt, err := coon.Prepare("select * from user where id =?")
	userId, err := strconv.Atoi(uid)
	query, err := stmt.Query(userId)
	var user User
	if query.Next() {
		query.Scan(&user.Id, &user.Name, &user.Gender, &user.Age)
	}

使用tpl模板输出

	u.Data["userInfo"] = &coom.Response{Code: 200, Msg: "success", Data: user, Success: true}
	u.TplName = "user.tpl"//指定模板文件
	 
}

配套的模板文件:

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Beego</title>
</head>
<body>
<h3>这是一个测试模块</h3>
<br>
<p>{{.userInfo}}</p>
</body>
</html>

输出字符串

	jsonuser, _ := json.Marshal(user) //使用go自带的json处理
	u.Ctx.WriteString(string(jsonuser))
//{"id":12,"name":"Kelly Tran","gender":1,"age":32}

输出json格式数据

u.Data["json"] = &coom.Response{Code: 200, Msg: "success", Data: user, Success: true}
//这里Data中的字段必须为json,否则beego解析时会返回 空
	
	u.ServeJSON()
{
    "code": 200,
    "msg": "success",
    "data": {
        "id": 12,
        "name": "Kelly Tran",
        "gender": 1,
        "age": 32
    },
    "success": true
}

输出xml格式数据

u.Data["xml"] = &coom.Response{Code: 200, Msg: "success", Data: user, Success: true}
	 
	u.ServeXML()
<Response>
    <Code>200</Code>
    <Msg>success</Msg>
    <Data>
        <Id>12</Id>
        <Name>Kelly Tran</Name>
        <Gender>1</Gender>
        <Age>32</Age>
    </Data>
    <Success>true</Success>
</Response>

输出jsonp格式数据

通过把要输出的数据放到Data["jsonp"]中,然后调用ServeJSONP()进行渲染,会设置content-typeapplication/javascript,然后同时把数据进行JSON序列化,然后根据请求的callback参数设置jsonp输出。

package controllers

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

type MainController struct {
	beego.Controller
}

type JSONStruct struct {
	Code int
	Msg  string
}

func (c *MainController) Get() {
	mystruct := &JSONStruct{0, "hello"}
	c.Data["jsonp"] = mystruct
	c.ServeJSONP()
}

返回:

 if(window.jsonCallback)jsonCallback({
  "Code": 0,
  "Msg": "hello"
});

beego获取请求参数

学了一段时间,但是没有深入接触beego是怎样接收参数的

  • 路径参数:这一部分主要是指参数路由
  • 查询参数
  • 请求体:要想从请求体里面读取数据,大多数时候将BConfig.CopyRequestBody 设置为true就足够了。而如果你是创建了多个 web.Server,那么必须每一个Server实例里面的配置都将CopyRequestBody设置为true

而获取参数的方法可以分成两大类:

  • 第一类是以 Get 为前缀的方法:这一大类的方法,主要获得某个特定参数的值
  • 第二类是以 Bind 为前缀的方法:这一大类的方法,试图将输入转化为结构体

获取get请求参数

models中方法


func GetConsumerInfo(id int) (c *Consumer, err error) {

	newOrm := orm.NewOrm()
	/**
	// QueryTable return a QuerySeter for table operations.
	// table name can be string or struct.
	// e.g. QueryTable("user"), QueryTable(&user{}) or QueryTable((*User)(nil)),

	//
	//	filter by UserName == 'slene'
	//	qs.Filter("UserName", "slene")

	   //qs.RelatedSel("profile").One(&user)
	//	user.Profile.Age = 32
	*/
	consumer := Consumer{Id: id}
	err = newOrm.QueryTable("consumer").Filter("Id", id).RelatedSel().One(&consumer)
	if err != nil {
		logs.Error(err)
		return nil, err
	}
	return &consumer, nil
}

controller中方法


func (c *ConsumerController) GetConsumerInfo() {
	cid := c.GetString("id")
	uid, err := strconv.Atoi(cid)
	if err != nil {
		logs.Error(err)
		return
	}
	consumerInfo, err := models.GetConsumerInfo(uid)
	c.Data["json"] = &coom.Response{
		Code:    200,
		Msg:     "success",
		Data:    consumerInfo,
		Success: true,
	}
	c.ServeJSON()
}

路由设置:

beego.Router("/getConsumerInfo", &controllers.ConsumerController{}, "get:GetConsumerInfo")

get请求,可以直接将请求转换成需要的类型;

	c.GetFloat()
	c.GetBool()
		c.Get()
		c.GetFile()

底层源码示例:

// GetInt64 returns input value as int64 or the default value while it's present and input is blank.
func (c *Controller) GetInt64(key string, def ...int64) (int64, error) {
	strv := c.Ctx.Input.Query(key)
	if len(strv) == 0 && len(def) > 0 {
		return def[0], nil
	}
	return strconv.ParseInt(strv, 10, 64)
}

Beego 主要从两个地方读取:查询参数和表单,如果两个地方都有相同名字的参数,那么 Beego 会返回表单里面的数据。实际上开发时 不会这么干;

即方法时:

请求一

localhost:8080/auth/getConsumerInfo?id=2

此时GetInt64(“id”) 得到的是2

请求二

localhost:8080/auth/getConsumerInfo

后面的表单:–>以表单形式发送数据
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

此时GetInt64(“id”) 得到的是2

请求三

localhost:8080/auth/getConsumerInfo?id=1

后面也有表单,那么

此时GetInt64(“id”) 得到的是2

如果没有传参数,那么可以指定默认值

func (c *Controller) GetInt64(key string, def ...int64) (int64, error) 

如果指定多个默认值,则以第一个为准

GetStringGetStrings 本身在设计的时候并没有设计返回 error,所以无法拿到错误。

解析json输入数据

使用 err = c.BindJSON(&con1)来实现json数据转为结构体


func (c *ConsumerController) BatchInsert() {
	con := make([]models.Consumer, 10)
	con1 := make([]models.Consumer, 10)
	requestBody := c.Ctx.Input.RequestBody
	err := json.Unmarshal(requestBody, &con)
	if err != nil {
		logs.Error(err)
	}
	err = c.BindJSON(&con1)
	if err == nil {
		logs.Info("数据-->", con1)
	}
	batchInsert, err := models.BatchInsert(con1)
	if err == nil {
		c.Data["json"] = coom.Response{
			Code:    200,
			Msg:     "批量更新成功",
			Data:    batchInsert,
			Success: true,
		}
		c.ServeJSON()
	} else {
		panic(err)
		logs.Error(err)
		c.Data["json"] = coom.Response{
			Code:    400,
			Msg:     "批量更新失败",
			Data:    batchInsert,
			Success: false,
		}
		c.ServeJSON()
	}
}
// 数据--> [{1 1} {2 2} {3 3} {4 4} {5 5} {6 6} {7 7} {8 8} {9 9} {10 10}]

Bind这一大类有多个方法:

  • Bind(obj interface{}) error: 默认是依据输入的 Content-Type字段,来判断该如何反序列化;
  • BindYAML(obj interface{}) error: 处理YAML输入
  • BindForm(obj interface{}) error: 处理表单输入
  • BindJSON(obj interface{}) error: 处理JSON输入
  • BindProtobuf(obj proto.Message) error: 处理proto输入
  • BindXML(obj interface{}) error: 处理XML输入

在使用特定格式的输入的时候,别忘记设置标签(Tag),例如我们例子里面的json:"age",不同格式的输入,其标签是不是一样的。

获取post请求参数

方法准备:

models中添加方法

//这个不可少
func init() {
	orm.RegisterModel(new(Consumer))
}

//新增consumer
/**
使用post请求
*/

func AddConsumer(c *Consumer) (id int64, err error) {
	//创建一个orm
	newOrm := orm.NewOrm()
	/**
	示例
	//  user := new(User)
	//  id, err = Ormer.Insert(user)
	//  user must be a pointer and Insert will set user's pk field
	*/
    //只接收指针做为参数
	return newOrm.Insert(c) //--如果是自增id的话返回插入时的用户id,否则返回0
}

controller中编写方法:


func (c *ConsumerController) AddConsumer() {
	//在controller中取出参数
	requestBody := c.Ctx.Input.RequestBody

	var consumer models.Consumer
	err := json.Unmarshal(requestBody, &consumer)
	addResult, err := models.AddConsumer(&consumer)
	if err != nil {
		log.Error(err)
	}
	c.Data["json"] = &coom.Response{
		Code:    200,
		Msg:     "success",
		Data:    addResult,
		Success: true,
	}
	c.ServeJSON()
}

路由设置

beego.Router("/addConsumer", &controllers.ConsumerController{}, "post:AddConsumer")

路由设置至少要掌握2种; 方式三跟命名空间

请求时接收引结构体的要用指针

常用的个数据接收方式就是get 和post 两种;

使用beego带的操作方式来实现数据的更新:


// @router / [post]
func (c *ConsumerController) UpdateConsumerById() {
	var consumer models.Consumer
	//获得参数
	requestBody := c.Ctx.Input.RequestBody
	err := json.Unmarshal(requestBody, &consumer)

	id, err := models.UpdateConsumerById(&consumer)
	if err != nil {
		log.Error(err)
	}
	c.Data["json"] = &coom.Response{
		Code:    200,
		Msg:     "更新成功",
		Data:    id,
		Success: true,
	}
	c.ServeJSON()
}

models中方法:

func UpdateConsumerById(con *Consumer) (int64, error) {
	newOrm := orm.NewOrm()
	/**
	Update(md interface{}, cols ...string) (int64, error)
	// find model by Id(pk) field and update columns specified by fields, if cols is null then update all columns  如果有主键则更新指定的主键内容
	cols set the columns those want to update.
	更新name列
	*/
	return newOrm.Update(con, "name")//

}

filter过滤器

filter可以作为请求的过滤器,这个就像java中的filter作用一样

添加过滤器的方式一

在namespace中添加

//命名空间
	namespace := beego.NewNamespace("/auth",
		beego.NSRouter("/addConsumer", &controllers.ConsumerController{}, "post:AddConsumer"),
		beego.NSRouter("/getConsumerInfo", &controllers.ConsumerController{}, "get:GetConsumerInfo"),
		beego.NSBefore(func(ctx *context.Context) {
			value := ctx.Request.FormValue("id")
			id, _ := strconv.Atoi(value)
			if id > 100 {
				log.Info("年龄过大")
				response := coom.Response{
					Code:    400,
					Msg:     "年零不符合要求",
					Data:    nil,
					Success: false,
				}
				resp, _ := json.Marshal(&response)
				ctx.WriteString(string(resp))

			}
		}), beego.NSAfter(func(ctx *context.Context) {
			var resp coom.Response
			if ctx.Input.RequestBody != nil {
				err := json.Unmarshal(ctx.Input.RequestBody, &resp)
				if err == nil {
					if resp.Success == false {
						ctx.WriteString("请求失败")
					}
				}
			}
		}),
	)

	beego.AddNamespace(namespace)
	sqlCoon, _ := config.String("sqlCoon")
	orm.RegisterDataBase("default", "mysql", sqlCoon)
	beego.Run("localhost")

在命名空间中设置filter只对当前命名空间路由有效;

添加过滤器的方式二


	namespace.Filter("before", func(ctx *context.Context) {
		value := ctx.Request.FormValue("id")
		id, _ := strconv.Atoi(value)
		if id > 60 {
			log.Info("年龄过大60")
			response := coom.Response{
				Code:    400,
				Msg:     "年零不符合要求60",
				Data:    nil,
				Success: false,
			}
			resp, _ := json.Marshal(&response)
			ctx.WriteString(string(resp))

		}
	})
	beego.AddNamespace(namespace)

如果两种方式都有,那么会先执行哪一个呢?

如果符命名空间的路由,那么会优先走命名空间内的filter

ORM操作

beego中封装了一些数据操作方法,这就不用我们去写curd方法

一定要根据自己使用的数据库来匿名引入驱动

注册数据库信息:

// 参数1        数据库的别名,用来在 ORM 中切换数据库使用
// 参数2        driverName
// 参数3        对应的链接字符串
orm.RegisterDataBase("default", "mysql", "root:root@/orm_test?charset=utf8")

// 参数4(可选)  设置最大空闲连接
// 参数5(可选)  设置最大数据库连接 (go >= 1.2)
maxIdle := 30
maxConn := 30
orm.RegisterDataBase("default", "mysql", "root:root@/orm_test?charset=utf8", orm.MaxIdleConnections(maxIdle), orm.MaxOpenConnections(maxConn))

ORM 要求必须要注册一个default的数据库。并且,Beego 的 ORM 并没有自己管理连接,而是直接依赖于驱动。

	//第一个params ...int  参数  MaxIdleConnections最大空闲数  MaxOpenConnections最大数据库连接数
	orm.RegisterDataBase("default", "mysql", sqlCoon, 50, 50)

也可以在注册之后修改

在上面已经用到了数据库的操作;

时区设置

ORM 默认使用 time.Local 本地时区

  • 作用于 ORM 自动创建的时间

  • 从数据库中取回的时间转换成 ORM 本地时间

注册模型

在数据库操作时注册表名有三种方式:

//表名 customer
orm.RegisterModel(new(Consumer))

	//为表名加上前缀  tab_consumer
	//orm.RegisterModelWithPrefix("tab_", &Consumer{})
	//为表名加上后缀 consumer_tab
	//orm.RegisterModelWithSuffix("_tab", &Consumer{})

一般在model层结构体所在的*.go文件夹下

使用init()方法来注册model


func init() {
	orm.RegisterModel(new(Consumer))

	//为表名加上前缀  tab_consumer
	orm.RegisterModelWithPrefix("tab_", &Consumer{})
	//为表名加上后缀 consumer_tab
	orm.RegisterModelWithSuffix("_tab", &Consumer{})

}

表名规则:使用驼峰转蛇形

AuthUser -> auth_user
Auth_User -> auth__user
DB_AuthUser -> d_b__auth_user

自定义表名—>>实现TableName接口

func init() {
    //必须注册
	orm.RegisterModel(new(File))
	//orm.RegisterModelWithSuffix("_info", new(File))
}


/*
*
自定义表名
*/

func (f *File) TableName() string {
	return "file_info"
}

或者下面这样

func init() {
	//orm.RegisterModel(new(File))
	orm.RegisterModelWithSuffix("_info", new(File))
}

总之我们还是习惯驼峰式命名方式;

表中字段

为字段设置DB列名称

package models

import "time"

type Person struct {
	/**
	设置表的列的别名
	*/
	Id int64 "orm:'index'" //增加索引
	/**
	如果一个模型没有定义主键,
	符合上述类型且名称为 Id 的模型字段将被视为自增主键。
	如果不想使用自增主键,那么可以使用pk设置为主键。  "orm:'pk'"
	*/
	Pid     int64  "orm:'auto'"   //自增主键
	Account string "orm:'unique'" //增加唯一键
	Name    string "orm:'column(name)'"
	Age     string "orm:'column(age)'"
	Gender  int64  "orm:'-'" //忽略字段
	//自动更新时间
	//auto_now 每次 model 保存时都会对时间自动更新
	//auto_now_add 第一次保存时才设置时间
	//对于批量的 update 此设置是不生效的
	Created time.Time `orm:"auto_now_add;type(datetime)"`
	Updated time.Time `orm:"auto_now;type(datetime)"`
}

/**
为单个或者多个字段增加索引
*/

//实现接口TableIndexI

func (p *Person) TableIndex() [][]string {
	//多字段索引
	return [][]string{
		[]string{"Id", "Name"},
	}
}

func (p *Person) TableUnique() [][]string {
	return [][]string{
		[]string{"Name", "Email"},
	}
}

// 设置引擎

func (p *Person) TableEngine() string {
	return "INNODB"
}

查数据

Read(md interface{}, cols ...string) error

读取数据到model中

该方法的特点是:

  • 读取到的数据会被放到 md
  • 如果传入了 cols 参数,那么只会选取特定的列;

测试代码:

models层中的方法

func GetConsumerInfoById(id int64) (c *Consumer, err error) {
	newOrm := orm.NewOrm()
    //查询id 为id的consumer  
    //Read()中如果没有指定, 则按照id查询
	consumer := Consumer{
		Id:   id,
		Name: cname,
	}
   /**
    //同理如果要按照name查询,则
    consumer := Consumer{
		Name: "name",
	}
	//如果要查询多个,则只需要在consumer中添加即可
	*/
err = newOrm.Read(&consumer, "Id", "Name")
	if err == nil {
		return &consumer, nil
	}
	return nil, err
}

controller层中方法:

func (c *ConsumerController) GetConsumerInfoById() {
	cid, err := c.GetInt64("id")
	cname := c.GetString("cname")

	consumer, err := models.GetConsumerInfoById(cid, cname)
	if err != nil {
		logs.Error(err)
	}
	c.Data["json"] = &consumer
	c.ServeJSON()
}

配置路由:

	namespace := beego.NewNamespace("/auth",
beego.NSRouter("/getConsumerInfoById", &controllers.ConsumerController{}, "get:GetConsumerInfoById"),)

ReadForUpdate(md interface{}, cols ...string) error

读的时候数据不允许修改,即

select … for update

代码:略

ReadOrCreate(md interface{}, col1 string, cols ...string) (bool, int64, error)

读,如果没有就插入数据

models层

func CommonFunc(c *Consumer) (bool, int64, error) {
	newOrm := orm.NewOrm()
	return newOrm.ReadOrCreate(c, "Id")
}

controller层

func (c *ConsumerController) CommonFunc() {
	var con models.Consumer
	err := json.Unmarshal(c.Ctx.Input.RequestBody, &con)

	commonFunc, i, err := models.CommonFunc(&con)
	if err != nil {
		logs.Error(err)
	}
	c.Data["json"] = coom.Response{
		Code:    200,
		Msg:     "没有就插入",
		Data:    i,
		Success: commonFunc,
	}
	c.ServeJSON()
}

路由:略

查找-判断-插入”这三个动作并不是原子的,也不是线程安全的。因此在并发环境下,它的行为可能会超出你的预期,比如说有两个 goroutine 同时判断到数据不存在,那么它们都会尝试插入

增数据

Insert(interface{}) (int64, error)

func AddConsumer(c *Consumer) (id int64, err error) {
	//创建一个orm
	newOrm := orm.NewOrm()
	/**
	示例
	//  user := new(User)
	//  id, err = Ormer.Insert(user)
	//  user must be a pointer and Insert will set user's pk field
	*/
	return newOrm.Insert(c) //--如果是自增id的话返回插入时的用户id,否则返回0
}

InsertOrUpdate(md interface{}, colConflitAndArgs ...string) (int64, error)

如果有数据就更新数据

beego的orm中mysql的InsertOrUpdate时如果ID添加了auto属性并且以ID为更新主键会有问题 如:

有bug,所以更新数据还是使用update

//总是更新数据
{
    "code": 200,
    "msg": "success",
    "data": 672,
    "success": true
}

如果撤掉主键

{
    "code": 200,
    "msg": "success",
    "data": 0,
    "success": true
}

所以,目前应该减少这个方法的使用

批量插入

InsertMulti(bulk int, mds interface{}) (int64, error)

models层

/*
批量插入
*/
func BatchInsert(c []Consumer) (int64, error) {
	newOrm := orm.NewOrm()
	return newOrm.InsertMulti(1, c)
}

controller层

func (c *ConsumerController) BatchInsert() {
	con := make([]models.Consumer, 10)
	requestBody := c.Ctx.Input.RequestBody
	err := json.Unmarshal(requestBody, &con)
	if err != nil {
		logs.Error(err)
	}

	batchInsert, err := models.BatchInsert(con)
	if err == nil {
		c.Data["json"] = coom.Response{
			Code:    200,
			Msg:     "批量更新成功",
			Data:    batchInsert,
			Success: true,
		}
		c.ServeJSON()
	} else {
		panic(err)
		logs.Error(err)
		c.Data["json"] = coom.Response{
			Code:    400,
			Msg:     "批量更新失败",
			Data:    batchInsert,
			Success: false,
		}
		c.ServeJSON()
	}

}

测试时数据:

     [
       
        {
            "id": 1,
            "name": "1"
        },
        {
            "id": 2,
            "name": "2"
        },
        {
            "id": 3,
            "name": "3"
        },
        {
            "id": 4,
            "name": "4"
        },
        {
            "id": 5,
            "name": "5"
        },
        {
            "id": 6,
            "name": "6"
        },
        {
            "id": 7,
            "name": "7"
        },
        {
            "id": 8,
            "name": "8"
        },
        {
            "id": 9,
            "name": "9"
        },
         {
            "id": 10,
            "name": "10"
        }
    ]

结果:

{
    "code": 200,
    "msg": "批量更新成功",
    "data": 10,
    "success": true
}

更新数据

Update(md interface{}, cols ...string) (int64, error)

Update 默认更新所有的字段,可以更新指定的字段

使用主键来更新数据。也就是如果你使用这个方法,Beego 会尝试读取里面的主键值,而后将主键作为更新的条件。

// Update update model to database.
// cols set the columns those want to update.
//有主键的按照主键来更新,否则更新全部
// find model by Id(pk) field and update columns specified by fields, if cols is null then update all columns
// for example:
// user := User{Id: 2}
//  user.Langs = append(user.Langs, "zh-CN", "en-US")
//  user.Extra.Name = "beego"
//  user.Extra.Data = "orm"
//  num, err = Ormer.Update(&user, "Langs", "Extra")

models层

func UpdateConsumerById(con *Consumer) (int64, error) {
	newOrm := orm.NewOrm()
	/**
	Update(md interface{}, cols ...string) (int64, error)
	cols set the columns those want to update.
	更新name列
	*/
	return newOrm.Update(con, "name")

}

如果没有指定 cols 参数,那么所有的列都会被更新。

controllers层

// @router / [post]
func (c *ConsumerController) UpdateConsumerById() {
	var consumer models.Consumer
	//获得参数
	requestBody := c.Ctx.Input.RequestBody
	err := json.Unmarshal(requestBody, &consumer)

	id, err := models.UpdateConsumerById(&consumer)
	if err != nil {
		log.Error(err)
	}
	c.Data["json"] = &coom.Response{
		Code:    200,
		Msg:     "更新成功",
		Data:    id,
		Success: true,
	}
	c.ServeJSON()
}

删除数据

Delete(md interface{}, cols ...string) (int64, error)

models层

/*
删除数据
使用主键来删除数据
*/

func DelConsumer(con *Consumer) (int64, error) {
	return orm.NewOrm().Delete(con, "id")
}

controller层

func (c *ConsumerController) DelConsumer() {
	var con models.Consumer
	err := json.Unmarshal(c.Ctx.Input.RequestBody, &con)
	if err != nil {
		logs.Error(err)
	}
	consumer, err := models.DelConsumer(&con)

	if err != nil {
		c.Data["json"] = &coom.Response{
			Code:    400,
			Msg:     "删除失败",
			Data:    consumer,
			Success: false,
		}
		c.ServeJSON()
	} else {
		c.Data["json"] = &coom.Response{
			Code:    200,
			Msg:     "删除成功",
			Data:    consumer,
			Success: true,
		}
		c.ServeJSON()
	}

}

原始查询

执行原生查询

Raw(query string, args ...interface{}) RawSeter

事务

ORM 操作事务,支持两种范式。一种通过闭包的方式,由 Beego 本身来管理事务的生命周期

手动事务:

/*
*
这个实现了事务
*/

func AddFileTx(f *File) (int64, error) {
	newOrm := orm.NewOrm()
	newOrm.Begin()
	result, err := newOrm.Insert(f)
	if err != nil {
		logs.Error(err)
		newOrm.Rollback()
	}

	return result, nil
}

自动事务:

/*
*
自动事务
*/

func AddFileAutoTx(f *File) error {

	newOrm := orm2.NewOrm()
	err := newOrm.DoTx(func(ctx context2.Context, txOrm orm2.TxOrmer) error {
		_, err := txOrm.Insert(f)
		return err
	})
	return err
}

事务相关的方法:

// 需要自己管理事务生命周期
	Begin() (TxOrmer, error)
	BeginWithCtx(ctx context.Context) (TxOrmer, error)
	BeginWithOpts(opts *sql.TxOptions) (TxOrmer, error)
	BeginWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions) (TxOrmer, error)

	// Beego 利用闭包管理生命周期
	DoTx(task func(ctx context.Context, txOrm TxOrmer) error) error
	DoTxWithCtx(ctx context.Context, task func(ctx context.Context, txOrm TxOrmer) error) error
	DoTxWithOpts(opts *sql.TxOptions, task func(ctx context.Context, txOrm TxOrmer) error) error
	DoTxWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions, task func(ctx context.Context, txOrm TxOrmer) error) error

复杂查询

NewQueryBuilder复杂查询

type User struct {
	Name string
	Age  int
}
var users []User

// 获取 QueryBuilder 对象. 需要指定数据库驱动参数。
// 第二个返回值是错误对象,在这里略过
qb, _ := orm.NewQueryBuilder("mysql")

// 构建查询对象
qb.Select("user.name",
	"profile.age").
	From("user").
	InnerJoin("profile").On("user.id_user = profile.fk_user").
	Where("age > ?").
	OrderBy("name").Desc().
	Limit(10).Offset(0)

// 导出 SQL 语句
sql := qb.String()

// 执行 SQL 语句
o := orm.NewOrm()
o.Raw(sql, 20).QueryRows(&users)

示例代码:

models层:

package models

type Goods struct {
	GoodsId    int64
	GoodsName  string
	ConsumerId int64
}

package models
type Consumer struct {
	Id   int64  `json:"id"`
	Name string `json:"name"`
	Goods
}

controller层:

func (c *ConsumerController) QueryConsumerGoods() {
	id, err := c.GetInt64("id")
	if err != nil {
		logs.Error(err)
	}
	goods, err := models.QueryConsumerGoods(id)

	if err != nil {
		logs.Error(err)
	}
	c.Data["json"] = goods
	c.ServeJSON()

}

路由:略
访问:localhost:8080/auth/queryConsumerGoods?id=1

结果:

{
    "id": 1,
    "name": "gavin",
    "GoodsId": 1,
    "GoodsName": "笔记本电脑",
    "ConsumerId": 1
}

完整API接口–使用时可查询

type QueryBuilder interface {
	Select(fields ...string) QueryBuilder
	ForUpdate() QueryBuilder
	From(tables ...string) QueryBuilder
	InnerJoin(table string) QueryBuilder
	LeftJoin(table string) QueryBuilder
	RightJoin(table string) QueryBuilder
	On(cond string) QueryBuilder
	Where(cond string) QueryBuilder
	And(cond string) QueryBuilder
	Or(cond string) QueryBuilder
	In(vals ...string) QueryBuilder
	OrderBy(fields ...string) QueryBuilder
	Asc() QueryBuilder
	Desc() QueryBuilder
	Limit(limit int) QueryBuilder
	Offset(offset int) QueryBuilder
	GroupBy(fields ...string) QueryBuilder
	Having(cond string) QueryBuilder
	Update(tables ...string) QueryBuilder
	Set(kv ...string) QueryBuilder
	Delete(tables ...string) QueryBuilder
	InsertInto(table string, fields ...string) QueryBuilder
	Values(vals ...string) QueryBuilder
	Subquery(sub string, alias string) string
	String() string
}

QuerySeter 复杂查询

o := orm.NewOrm()

// 获取 QuerySeter 对象,user 为表名
qs := o.QueryTable("user")

// 也可以直接使用 Model 结构体作为表名
qs = o.QueryTable(&User)

// 也可以直接使用对象作为表名
user := new(User)
qs = o.QueryTable(user) // 返回 QuerySeter

// 后面可以调用qs上的方法,执行复杂查询。

代码案例:

models层

package models

import (
	"github.com/beego/beego/v2/adapter/orm"
	"github.com/beego/beego/v2/core/logs"
)

type Goods struct {
	GoodsId    int64
	GoodsName  string
	ConsumerId int64
}

func init() {
	orm.RegisterModel(new(Goods))
}

func GetGoods(id int64, name string) (*Goods, error) {
	newOrm := orm.NewOrm()
	queryTable := newOrm.QueryTable("goods")
	//操作符__gt ,i开头表示忽略大小写
	if len(name) != 0 {
		queryTable.Filter("goods_name__istartwith", name)
	}

	//返回一个querySetter对象
	queryTable = queryTable.Filter("goods_id", id)
	var goods Goods
	err := queryTable.One(&goods)
	if err != nil {
		logs.Error(err)
	}
	return &goods, err

}

controllers层

package controllers

import (
	"github.com/beego/beego/v2/core/logs"
	beego "github.com/beego/beego/v2/server/web"
	"hello/models"
)

type GoodsController struct {
	beego.Controller
}

func (g *GoodsController) GetGoods() {
	goodsName := g.GetString("goodsName")
	goodsId, err := g.GetInt64("id")

	if err != nil {
		logs.Error(err)
	}

	goods, err := models.GetGoods(goodsId, goodsName)

	if err != nil {
		logs.Error("err")
	}
	g.Data["json"] = &goods
	g.ServeJSON()
}

其他方式

QuerySeter 构造复杂查询 | Beego (gocn.vip)

原生查询

大多数时候,你都不应该使用原生查询。只有在无可奈何的情况下才应该考虑原生查询。使用原生查询可以

  • 无需使用 ORM 表定义
  • 多数据库,都可直接使用占位符号 ?,自动转换
  • 查询时的参数,支持使用 Model Struct 和 Slice, Array

文件上传下载

文件上传

配置文件上传大小限制:

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

配置文件中添加

#配置缓存大小
maxmemory= 1<<22

Beego 提供了另外一个参数,MaxUploadSize来限制最大上传文件大小——如果你一次长传多个文件,那么它限制的就是这些所有文件合并在一起的大小。

默认情况下,MaxMemory应该设置得比MaxUploadSize小,这种情况下两个参数合并在一起的效果则是:

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

文件上传实现

GetFile(key string) (multipart.File, *multipart.FileHeader, error)

SaveToFile(fromfile, tofile string) error

fromfile是提交时候表单中的name即那个key


/*
*
上传文件

*/

func (f *FileController) UploadFile() {
	//获得上传文件的信息
	file, header, err := f.GetFile("filename")
	if err != nil {
		logs.Error(err)
	}
	defer file.Close()
	//获得文件名
	filename := header.Filename
	//上传文件
	//注意,这里SaveToFile参数要跟传入的文件名的key一致,否则就会报http: no such file
	err = f.SaveToFile("filename", "./static/file/"+filename)
	if err != nil {
		logs.Error(err)
	}
	//获取文件信息
	var fileInfo models.File
	cid, _ := f.GetInt64("cid")
	fid, _ := f.GetInt64("id")
	fileInfo = models.File{
		Id:   fid,
		Name: filename,
		Addr: "./" + filename,
		Size: header.Size,
		Cid:  cid,
	}

	//插入数据库 --先不考虑事务
	_, err = models.AddFile(&fileInfo)
	if err != nil {
		logs.Error(err)
	}
	if err != nil {
		f.Data["json"] = &coom.Response{
			Code:    400,
			Msg:     "文件上传失败",
			Data:    nil,
			Success: false,
		}
		f.ServeJSON()
	} else {
		f.Data["json"] = &coom.Response{
			Code:    200,
			Msg:     "文件上传成功",
			Data:    nil,
			Success: true,
		}
		f.ServeJSON()
	}
}

文件下载

func (output *BeegoOutput) Download(file string, filename ...string) 

该方法在

f.Ctx.Output.Download(fileInfo.Addr, "beego.md")

代码实现:


func (f *FileController) DownloadFile() {
	//获取文件信息--文件路径
	//略
	id, err := f.GetInt64("id")
	if err != nil {
		logs.Error(err)
	}
	//查询要下载文件信息
	fileInfo := models.GetFileInfoById(id)
	f.Ctx.Output.Download(fileInfo.Addr, "beego.md")
}

Download方法的第一个参数,是文件路径,也就是要下载的文件;第二个参数是不定参数,代表的是用户保存到本地时候的文件名。

Session

默认beego框架是没有开启session的,

可以在配置文件中开启session

sessionon=true

或者在main.go中main函数添加

beego.BConfig.WebConfig.Session.SessionOn=true

session操作


func (c *ConsumerController) GetSessionTest() {
	//获取session
	session := c.GetSession("gavin")
	//如果为nil,则创建session
	if session == nil {
		c.SetSession("gavin", "lim")
		//为session设置一个id
		c.SessionRegenerateID()
		//从session中获取值
		getSession := c.GetSession("gavin")
		c.Data["json"] = getSession
	} else {
		//释放资源
		c.DelSession("gavin") //删除session
		//销毁session
		c.Data["json"] = c.GetSession("gavin")
	}
	defer c.DestroySession()
	c.ServeJSON()
}

session 有几个方便的方法:

  • SetSession(name string, value interface{})
  • GetSession(name string) interface{}
  • DelSession(name string)
  • SessionRegenerateID()
  • DestroySession()

自定义session

只需实现store接口

// Store contains all data for one session process with specific id.
type Store interface {
	Set(ctx context.Context, key, value interface{}) error     // set session value
	Get(ctx context.Context, key interface{}) interface{}      // get session value
	Delete(ctx context.Context, key interface{}) error         // delete session value
	SessionID(ctx context.Context) string                      // back current sessionID
	SessionRelease(ctx context.Context, w http.ResponseWriter) // release the resource & save data to provider & return the data
	Flush(ctx context.Context) error                           // delete all data
}

关于session的配置

修改配置文件:

appname = hello
httpport = 8080
runmode = dev
sqlCoon= root:955945@tcp(localhost:3306)/magic?charset=utf8
#获取用户传递的数据
copyrequestbody = true
#配置缓存大小
maxmemory= 1<<22
#开启session
sessionon= true
#设置session引擎 默认memory
#目前支持还有 file、mysql、redis 等
sessionprovider=memory
#设置对应 file、mysql、redis 引擎的保存路径或者链接地址,默认值是空
#当引擎为file时 ,默认是 ./tmp
#当引擎为mysql时 ,默认"username:password@protocol(address)/dbname?param=value",此时需要创建表
当引擎为postgresql时 "postgres://pqgotest:password@localhost/pqgotest?sslmode=verify-full"
#当引擎为redis 值为redis连接地址--"127.0.0.1:6379"
#当引擎为memcache 值为memcache连接地址
#当引擎为couchbase  值为couchbase连接地址"http://bucketname:bucketpass@myserver:8091"
sessionproviderconfig=./path


#Session 默认是保存在用户的浏览器 cookies 里面的
#配置cookie名字
sessionname="beegoSessionId"
#设置session过期时间
sessiongcmaxlifetime=3600

数据库创建语句

CREATE TABLE `session` (
	`session_key` char(64) NOT NULL,
	`session_data` blob,
	`session_expiry` int(11) unsigned NOT NULL,
	PRIMARY KEY (`session_key`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

单独使用session模块


//初始化一个全局变量 session Manager

var globalSessions *session.Manager

func init() {
	//初始化session配置
	managerConfig := &session.ManagerConfig{
		EnableSetCookie:         true,
		DisableHTTPOnly:         false,
		Secure:                  false,
		EnableSidInHTTPHeader:   false,
		EnableSidInURLQuery:     false,
		CookieName:              "gosessionid",
		Gclifetime:              0,
		Maxlifetime:             0,
		CookieLifeTime:          3600, //session生命周期
		ProviderConfig:          "./tmp",
		Domain:                  "",
		SessionIDLength:         0,
		SessionNameInHTTPHeader: "",
		SessionIDPrefix:         "",
		CookieSameSite:          0,
	}
	session.NewManager("memory", managerConfig)
	go globalSessions.GC()
}

cookie操作



func (c *ConsumerController) SetCookie() {
	//加载session并将旧session数据导入到this.controller中,
	//startSession := c.StartSession()
	//第三个参数---设置cookie失效时间
	//c.Ctx.SetCookie("name", "gavin", 6000)
	//加密cookie
	c.Ctx.SetSecureCookie("gavin", "name", "gavin", 600)
	c.Data["json"] = "success"
	c.ServeJSON()
}
func (c *ConsumerController) GetCookie() {
	//如果密钥不对,获取不到cookie
	cookie, _ := c.Ctx.GetSecureCookie("gavin123", "name")
	//c.Data["json"] = c.Ctx.GetCookie("name")
	c.Data["json"] = cookie
	c.ServeJSON()
}

错误处理

/*
*
错误处理 --
可以定义一个错误处理的处理器
*/

func (c *ConsumerController) TestErr() {
	//页面跳转
	c.Redirect("/errHandler", 401)
}

func (c *MainController) ErrHandler() {
	c.Data["json"] = &coom.Response{
		Code:    401,
		Msg:     "请求失败",
		Data:    nil,
		Success: false,
	}
	c.ServeJSON()
}

或者如下处理:


/*
*
错误处理 --
可以定义一个错误处理的处理器
*/

func (c *ConsumerController) TestErr() {
	//页面跳转
	//c.Redirect("/errHandler", 401)
	//取消请求
	//这样 this.Abort("401") 之后的代码不会再执行,
	c.Abort("404")
	//设置返回页面或者返回值
}

定义错误处理页面

package models

import beego "github.com/beego/beego/v2/server/web"

type ErrorController struct {
	beego.Controller
}

func (c *ErrorController) Error404() {
	c.Data["content"] = "page not found"
	c.TplName = "404.tpl"
}

func (c *ErrorController) Error501() {
	c.Data["content"] = "server error"
	c.TplName = "501.tpl"
}

在main方法中注册该controller

	//错误控制器
	beego.ErrorController(&models.ErrorController{})

恢复机制

如果发生了panic,我们希望beego能够正常响应,该机制是默认开启的
配置文件如下:

配置panic响应机制
recoverpanic=true

也可以在代码中开启

beego.BConfig.RecoverPanic = true

需要检测recover的结果,并且将从panic中恢复过来的逻辑放在检测到recover返回不为nil的代码里面
可以自定义panic后的处理行为:

	web.BConfig.RecoverFunc = func(context *context.Context, config *web.Config) {
		if err := recover(); err != nil {
			context.WriteString(fmt.Sprintf("you panic, err: %v", err))
		}
	}

开启后台管理

配置文件:

#开启admin管理
enableadmin=true
#管理页面ip
adminaddr=localhost
#管理页面端口
adminport=8888

或者在代码中开启

//默认 Admin 是关闭的,你可以通过配置开启监控:

web.BConfig.Listen.EnableAdmin = true
//而且你还可以修改监听的地址和端口:

web.BConfig.Listen.AdminAddr = "localhost"
web.BConfig.Listen.AdminPort = 8888

访问后结果:



<!DOCTYPE html>
<html lang="en">
<head>
<!-- Meta, title, CSS, favicons, etc. -->
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">

<title>

Welcome to Beego Admin Dashboard

</title>

<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet">
<link href="//cdn.datatables.net/plug-ins/725b2a2115b/integration/bootstrap/3/dataTables.bootstrap.css" rel="stylesheet">

<style type="text/css">
ul.nav li.dropdown:hover > ul.dropdown-menu {
	display: block;    
}
#logo {
	width: 102px;
	height: 32px;
	margin-top: 5px;
}
.message {
	padding: 15px;
}
</style>

</head>
<body>

<header class="navbar navbar-default navbar-static-top bs-docs-nav" id="top" role="banner">
<div class="container">
<div class="navbar-header">
<button class="navbar-toggle" type="button" data-toggle="collapse" data-target=".bs-navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>

<a href="/">
<img id="logo" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPYAAABNCAYAAACVH5l+AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAV/QAAFf0BzXBRYQAAABZ0RVh0Q3JlYXRpb24gVGltZQAxMi8xMy8xM+ovEHIAAAAcdEVYdFNvZnR3YXJlAEFkb2JlIEZpcmV3b3JrcyBDUzbovLKMAAAQAklEQVR4nO2de7RcVXnAfzM3MckmhLdKtZYqoimtERVtXSjFNvYE6rsnFMFKy2MJC4tGdNECalFsKxRQ5GGDClUjzS6lltcuUmiryxRqFaEaCkgVqAkSakKSneTm3rn949uHOTNzzp1zZs5j7mT/1pqVmzMze39z73xnf/vb36OBx+OZFRuEvwusBp4fu7wROE0ZfUs9UiUzMzMDQLNmOTyeucB1dCo17v/XVS5JRrxiezz9OSDn9drxiu3xjCFesT2eMcQrtsczhnjF9njGEK/YHs8Y4hXb4xlDvGJ7PGOIV2yPZwzxiu3x9OfpnNdrxyu2x9Ofk5HY8Dgb3fWRpFG3AB6Ppzh8EojHM8Z4xfZ4xhCv2B7PGOIV2+MZQ+bVLYDHUzU2CE8AXg+8GHguMAW0gOmEn6PH7q6fdwOTsceurp+fca+1wJPAz4GfKaM3V/EZvVfcs0dhg/AC4D3AgcAixGrNqgczOZ9ruOuTiGLfBVygjH4is8A5ibziXrE9eww2CI8C1gL7INZq9P2PFLBIorGjcacRBf82sFIZvbXg+WQyp9jzbBBuRj7osDwFPALcA9wC3K2MbhUw7rMUKGsWHgMuUUZfMcib55Ksg2CDcCnwR8CbgJcCe2d5G3AzcJYyelOJ4vVOHISLgEuB/YAJqlvUonkmgAXAa4HfR4ojDo0NwiXAIcAPldFT0fUinWcHAb8BfAC4E3jQBuHxBY5fNS8CPmuD8Ji6BclAZbLaIFxgg/AzwH8B5wCvIptSAyjgeOCvShIvkW1vflcDkfXltL/zMxU/QJR8EXCWDcIDh/lMNgjn2SD8MLAB+D6w3gbhC6Lny/SKvxS4wQbhGhuEqsR5yua36xYgB6XK6v6OtwN/zHDfnaOLkSgbzWbz5cDpwHySla5VwCNpzDTlPgSxdgbCBuGrgXuBTyM3S4BDgS/tWLGyAdV4xU8AltggfJsyerqC+YrG1i1ADkqT1QbhBLI/LcIq2FnAGJlwJviFwL7uUlzJQH5n6921pntM9Pk3crg1ux646w3apvd8es3++cApNgi/pox+PMdnUcAngLPd+N0sB04FVld13HUc8FHgYxXNVxSTwI11C5GRsmW9APk7FsHagsbJwnGIhRApQuQom0FuMOcpo68pY2IbhPsD/wwcRvtmECn5wcCHEesny1i/A1yDrPazcfGOFSu/1khx8rSABzJJL+znBJ0/y2t2A0uV0T/KMW4HBcmahUngIeAKZfQ9gwwwl2Tthw3Co5GjmjTz+xngx/T3LD8J/CNwTRXWm9vH3gr8Cr2KPQ3co4wue/tyKvAXtE3m+I1lM3CsMvr7s7z/AOBy4KQc0y5LW7G3KqNfmWOgyExYDnwcSHrvfMSBcUaecTOQW9YamUuyAs8qxxrSlXo14uWerE6qzKyivVpGRHvfzYhJWzZrgROB19DpuANYDPypDcL3KqN7tic2COcDdwO/lmO+B4D1hTnPlNFWGf115AOkmVrvtkG4oKg5PeWydfk7G8D1wC+kvOTfgDNGUaltEL4GCUSZR69DaxdwgzL6B2XLoYx+BnFybaftaIP2nv4NwG+lvH0p+ZR6DXDMotvX7i7cK+5MrFPoTUwHWAIcVfScnnKYmJhYBRyb8vTTwAmj6BC1QbgYcZhFx3BxpZ4GfgJcVqFIdyJe7Gl6veQK+IA7j+7mUcSy6MdPgBXK6BMX3b72aSjpuEsZvY30hmWvL2NOT7HYIDwS+PNZXnKyMvqnVcmTk5XAEXRGlEXKZBF/RGWyK6N3A+cjvojuVbuBrMpvSXjfNuCDswzdQm5QhyujTfyJMs+x70i5fniJc3oKwAbhPsANpDtDLx+19rERLkjjTGAhvWGdU8B/Al+tWi5l9P3A3zsZIpkiL/lC4P1Jq7Yy+jrgtoQh7wNep4xepYze3v1kmYqdtn/5pRLn9BTD55HMpyS+B5xboSx5ORuJxGvSuVq3ELN2VY0+gUuRLWp38EoDCTA5JeV9pyHh2iAWx7nAkcro76RNVJpiK6N/hhxxdXNQWXN6hscG4WlI2GcS24HjldG7KhQpMzYIXwmEtCPMoK08u4AvKKMfqkk8lNGPIdbCLto3G2g70k63Qdiz8Lltw1JgGXCwMvov43HhSZRdaCEpEsrngI8oNggPBT4zy0vOUEY/XJU8ebBBuBA5L47HrUf72WngYQpKvBiSaxFnV7cjrYGkkp7lovw6UEZPKaPvd172vtRRQeU5Nczp6YNLlPhrJEkhia8oo79coUh5eTfwCnq/05HD7GpnRdaKk+FqJLAortQgQTTvQFbnoShNsW0QNklOW6wsTtiTnWazeRLpceCPUHxgUWHYIDwYOIvORSPax04B6xDH1aiwBniQ3r02yJHwh1xwysCUuWKneb+fKnFOzwC41fqClKcnkX31tgpFysu5SEhzPFwzOrP+OXCRMnpHfeJ14nwUH0csiUi5ob3XPgZJhx2YMhU7LWHgkZTrnppoNptvQNJsk/gzZfR3q5QnDy7C7DjEjI0UJFKW3cAXZ4vFrgtl9L8C36RdYy1elGQRcN4w45ei2DYI9yU9a+XeMub0DEWQcv1x4JIqBcmDDcJ5wEVIzHX8zDoywR8ErqpHukx8EthCr0neBF5tgzDt79KXwhXbBTesRUyjJG4tek7P0CxLuX79KMaBxzgZ2fLFTfBo9dsGXFpWbbEiUEavR8qITdF7/DUBfMLlk+emMMW2QXiYDcKzkeyS5Skvu0sZ7U3x0eOQlOv/VKUQebBB+DzEKoyKEsbDR6eRNNNv1CNdLi5D4u67U16byOL4vkEGTTtT3sflE2dlr1nGivMnOcbMShZZp5B84S8ro2c7py2bUZV1r5TrP6xo/kE4Ddif5Hjwp4BPj2ogTRxl9BM2CK9DYsKjwJroM80D3mOD8AZl9IY8486mjEVX2LxIGV3W/jqLrAcg+5bNyujrS5IjC6Moa1Ixwkll9P9VMPegHE1bAeLKPQWsbrVaAxf0qIGrkGi/F9Kbs30gUl7s0jwDVhWgsobRKYv0jroFyEFVsibVzxqZ46EUor1nd8HA7a1W68rFd9zYr5rLyOCO4i6kvdeOWyAN4DfzjlmVYttWq1VojXHPHk8UudXNvGaz+dqqhSmAbyA30+7Chw3St0qpVKXYpzabzTL214NwU90C5GAuyVo162gfE8X/XQCc547C5hLLkfTNpPLIuX0Fs334LTnG2Zv+N4kLbRDeWlKwQD9Z4w6pOvfXMLdk7YsNwrcAHwFeRvY8gC3K6GHTd/8GeBtSVrg773op8FZGK4w0FZeHfS6iQy16Pfyp6ZlppCn2FmX0vinPpQl3CFKw/iMkRzFNAJ+iuBK2EbllrZG5JGtfbBC+CvgH6kkmegT4OlK9M4o6ixR8AZICeWfWbKiaORN4Acke/i0MUBiiyGKGP1ZGXwv8KtKfKYkVNgh/sag5PbVzLDX1WHe11r6InAEnpUC+GHhXHbLlweVfn0D75hTfWuxGbpy5Yz/KKGY4iTQdS+pw0KD4Fduzh9JqtR4F/o7OPTbu5wXAScP2yKqA85GMLuhcracRHbpskIKRZRUztEhCeRKvK2NOTy3cRmfyQqW4I60vAP9Lsof8RUhFlZHEtfU9is72QHGH2SeV0XkCxZ6lTDPq9pTrh5Y4p6dCXNbX24FvIdFeWxIeZcvwJLIHjRcuiJR8HnD8KK7arsHGxxBPeES8RPK/K6MHDokt80jgwZTrI/dL9gyOMvpm0n0qVfUJ10gwz6H0Nsd7IfCHwMUly5AZV4Tk/UiMfpLDbCuzl37uS5nFDLcixe+6GShbxeNJQxm9CVm1d9J7BtxEVu1Rqo77y8DvkZxDPgXcpIxOWxgzUbZHM6mSom/x4ymDmxDvcdyBBrIi7guckVQksGpc4MwHkUaW3VFmLeCnwGeHnadsxV6YcG3kM248cw93Xr0aWbW7yw01gDcDL6lHug7e6B5xEzzKIZ8Eriwi+abMYoYHkbw6D+Tl83gyYJAm9klFAhcjgSC1YYNwb2S1jm9H42fX31NGF9I7vMwV+00p1x8tcU7PYFRtRZWSeeV6ZH0KSaboPtduAEfbIDysjLkzciLi4Euq+LIFKZVUCKUotmu/mtZM7D/KmNMzFElOziU2CIuoAZ9URre0YzB3BPct0jtbripr7tlwIdd/QHrRxduU0YUVtihcsbcuf2djYmLiYtIDUUaymdseTtKeroEUMxgYG4TLEGXq5slhxs3A5ciRUbQaxpX7FTYIjyh5/g5cjfDIYQadN5xppJ/XlUXOWWTNs4U2CIOJiYm7gA+lvOxeZfQDRc3pKYy0aiNXOOXMjQ3Cw5EMrCTuG2TMrLj+XN+k1+RvIOGbv17m/Am8keQIMxCH2bUu0KYw0gJU9rZBmOeXvxjpotkv4KWMKip5Zc3CJPAQ0kf5ngLHHVVZ70B6SnfzMuA+G4QbgKztcZpIEFJalVqYJaClQK5CCu93x01MIJ+rElzV3vfRtlzi3voW0lPsxqLnTVPEJuklaQfl+u7m3AVRhqwARwKhDcJlwwYLxBhVWf8WcTo9N+X5g5ldUfPwMMn9novmUeAJekOYZyjus2Th7Ugac7w+W7Q92IbUAizceVlVyt3d1HzUMCDPYQ6k/jkGltW176ni7zMDnKmMLj1xxGVEbSL56CspvqJwXE+x99LZ1jceYWbK6rJSRfmYrwCnj1LvpJwkOX9GlYFlVUbfaIPwTMrrnNFClPrOksZPIjr26o7w6vneu7iLw5Fwz+cjyr8L6a+13Y21M/bvM4iDbguy8u6I96x2EWbnIFZQd+XRGUpwmMUpU7HvB853SQJzmSq/iMMylKzK6KttED6OpEKmmeWD8CPk5n5XgWNmIR47HhHV6wbABuEBiH/hWOB5iEJHCSTx9yTRMa4NwvhcDcSK6h5nBvGLfFUZvTHn58lMkYq9EcnoWgfcrIxeV+DYdfAYcIky+u66BclAYbIqo2+xQfgSxIRcARyBKHme78pOYAPwbaSlk46vZhUSrdjdit10ZvJKZA+8P+JU617ZI9KuZ6F77hYS0164wyzOMAJ7PCONDcKPIllUcV/SDHLjsUhySJNOPYgfRQ1L92rdcvOeX9aWZGZGRJ9rJVo9njx0r9iRib0QyWOIVzft7tZZBN2r9W7gX6rwM3jF9owzu+hVLkhW3CRPfZqC57V0W4hS/w9QST82r9ieccaSrIRpjrG4uRx15ZhAzPXoEa360f+hM6Ksm0kko3E98Lm8zfUGxSu2Z5x5Glkp59OrdElm8n8jOd3fUUYnJcYk4o62FrrHXu6hEP2yyNHYJhcvUAlesT3jzEbkDHoJnUdYcZN8J3IcN3BIrvP4b3OPTcMIXBResT3jzONIqaGFtE3qiB3AD5AAqnUul3ts8IrtGWc2IDHw5yAtdOYjoZzfRRoN3FdGnPYo4M+xPWONK2C4H+1FbArYXFPATOlE59j/D6WId7YitGZUAAAAAElFTkSuQmCC"/>
</a>

</div>
<nav class="collapse navbar-collapse bs-navbar-collapse" role="navigation">
<ul class="nav navbar-nav">
<li>
<a href="/qps">
Requests statistics
</a>
</li>
<li>

<li class="dropdown">
<a href="#" class="dropdown-toggle disabled" data-toggle="dropdown">Performance profiling<span class="caret"></span></a>
<ul class="dropdown-menu" role="menu">

<li><a href="/prof?command=lookup goroutine">lookup goroutine</a></li>
<li><a href="/prof?command=lookup heap">lookup heap</a></li>
<li><a href="/prof?command=lookup threadcreate">lookup threadcreate</a></li>
<li><a href="/prof?command=lookup block">lookup block</a></li>
<li><a href="/prof?command=get cpuprof">get cpuprof</a></li>
<li><a href="/prof?command=get memprof">get memprof</a></li>
<li><a href="/prof?command=gc summary">gc summary</a></li>

</ul>
</li>

<li>
<a href="/healthcheck">
Healthcheck
</a>
</li>

<li>
<a href="/task" class="dropdown-toggle disabled" data-toggle="dropdown">Tasks</a>
</li>

<li class="dropdown">
<a href="#" class="dropdown-toggle disabled" data-toggle="dropdown">Config Status<span class="caret"></span></a>
<ul class="dropdown-menu" role="menu">
<li><a href="/listconf?command=conf">Configs</a></li>
<li><a href="/listconf?command=router">Routers</a></li>
<li><a href="/listconf?command=filter">Filters</a></li>
</ul>
</li>
</ul>
</nav>
</div>
</header>

<div class="container">

<h1>Requests statistics</h1>
<table class="table table-striped table-hover ">
	<thead>
	<tr>
	
		<th>
		requestUrl
		</th>
	
		<th>
		method
		</th>
	
		<th>
		times
		</th>
	
		<th>
		used
		</th>
	
		<th>
		max used
		</th>
	
		<th>
		min used
		</th>
	
		<th>
		avg used
		</th>
	
	</tr>
	</thead>

	<tbody>
	

	<tr>
	    <td>/                                                 </td>
	    <td>GET       </td>
	    <td> 1              </td>
	    <td data-order="1382800">1.38ms          </td>
	    <td data-order="1382800">1.38ms          </td>
	    <td data-order="1382800">1.38ms          </td>
	    <td data-order="1382800">1.38ms          </td>
	</tr>
	

	<tr>
	    <td>/favicon.ico                                      </td>
	    <td>GET       </td>
	    <td> 1              </td>
	    <td data-order="820700">820.70us        </td>
	    <td data-order="820700">820.70us        </td>
	    <td data-order="820700">820.70us        </td>
	    <td data-order="820700">820.70us        </td>
	</tr>
	
	</tbody>

</table>

</div>

<script src="//code.jquery.com/jquery-1.11.1.min.js"></script>
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
<script src="//cdn.datatables.net/1.10.2/js/jquery.dataTables.min.js"></script>
<script src="//cdn.datatables.net/plug-ins/725b2a2115b/integration/bootstrap/3/dataTables.bootstrap.js
"></script>

<script type="text/javascript">
$(document).ready(function() {
    $('.table').dataTable();
});
</script>

请求统计信息

访问统计的 URL 地址 http://localhost:8088/qps

健康检查

需要手工注册相应的健康检查逻辑,才能通过 URLhttp://localhost:8088/healthcheck 获取当前执行的健康检查的状态。

查看配置信息

  • 显示所有的配置信息: http://localhost:8888/listconf?command=conf

    Configurations
    
    BConfig.AppName=hello
    
    BConfig.CopyRequestBody=true
    
    BConfig.EnableErrorsRender=true
    
    BConfig.EnableErrorsShow=true
    
    BConfig.EnableGzip=false
    
    BConfig.Listen.AdminAddr=localhost
    
    BConfig.Listen.AdminPort=8888
    
    BConfig.Listen.AutoTLS=false
    
    BConfig.Listen.ClientAuth=4
    
    BConfig.Listen.Domains=[localhost]
    
    BConfig.Listen.EnableAdmin=true
    
    BConfig.Listen.EnableFcgi=false
    
    BConfig.Listen.EnableHTTP=true
    
    BConfig.Listen.EnableHTTPS=false
    
    BConfig.Listen.EnableMutualHTTPS=false
    
    BConfig.Listen.EnableStdIo=false
    
    BConfig.Listen.Graceful=false
    
    BConfig.Listen.HTTPAddr=localhost
    
    BConfig.Listen.HTTPPort=8080
    
    BConfig.Listen.HTTPSAddr=
    
    BConfig.Listen.HTTPSCertFile=
    
    BConfig.Listen.HTTPSKeyFile=
    
    BConfig.Listen.HTTPSPort=10443
    
    BConfig.Listen.ListenTCP4=false
    
    BConfig.Listen.ServerTimeOut=0
    
    BConfig.Listen.TLSCacheDir=.
    
    BConfig.Listen.TrustCaFile=
    
    BConfig.Log.AccessLogs=false
    
    BConfig.Log.AccessLogsFormat=APACHE_FORMAT
    
    BConfig.Log.EnableStaticLogs=false
    
    BConfig.Log.FileLineNum=true
    
    BConfig.Log.Outputs=map[console:]
    
    BConfig.MaxMemory=67108864
    
    BConfig.MaxUploadSize=1073741824
    
    BConfig.RecoverFunc=
    
  • 显示所有的路由配置信息: http://localhost:8088/listconf?command=router

    Routers
    GET
    Show 
    10
     entries
    Search:
    Router Pattern	Methods	Controller
    /auth/getConsumerInfo	map[GET:GetConsumerInfo]	controllers.ConsumerController
    /auth/getConsumerInfoById	map[GET:GetConsumerInfoById]	controllers.ConsumerController
    /auth/getCookie	map[GET:GetCookie]	controllers.ConsumerController
    /auth/setCookie	map[GET:SetCookie]	controllers.ConsumerController
    /auth/testErr	map[GET:TestErr]	controllers.ConsumerController
    /errHandler	map[GET:ErrHandler]	controllers.MainController
    /userGet	map[GET:GetUserById]	controllers.UserController
    Showing 1 to 7 of 7 entries
    Previous
    1
    Next
    POST
    Show 
    10
     entries
    Search:
    Router Pattern	Methods	Controller
    /auth/addConsumer	map[POST:AddConsumer]	controllers.ConsumerController
    /auth/batchInsert	map[POST:BatchInsert]	controllers.ConsumerController
    /auth/commonFunc	map[POST:CommonFunc]	controllers.ConsumerController
    /auth/delConsumer	map[POST:DelConsumer]	controllers.ConsumerController
    /auth/downloadFile	map[POST:DownloadFile]	controllers.FileController
    /auth/getSessionTest	map[POST:GetSessionTest]	controllers.ConsumerController
    /auth/updateConsumerById	map[POST:UpdateConsumerById]	controllers.ConsumerController
    /auth/uploadFile	map[POST:UploadFile]	controllers.FileController
    Showing 1 to 8 of 8 entries
    
  • 显示所有的过滤设置信息: http://localhost:8088/listconf?command=filter

安全问题~跨站请求伪造

配置文件:

#开启跨站请求伪造
enablexsrf=true
xsrfkey=61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o
xsrfexpire=3600

如果开启了 XSRF,那么 Beego 的 Web 应用将对所有用户设置一个 _xsrf 的 Cookie 值(默认过期 1 小时),如果 POST PUT DELET 请求中没有这个 Cookie 值,那么这个请求会被直接拒绝。

Beego 使用了 SecureHTTP-ONLY 两个选项来保存 Cookie。因此在大部分情况下,这意味这你需要使用 HTTPS 协议,并且将无法在 JS 里面访问到 Cookie 的值。

代码中:

beego.BConfig.WebConfig.EnableXSRF = true
	beego.BConfig.WebConfig.XSRFKey = "61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o"
	beego.BConfig.WebConfig.XSRFExpire=3600

XSRF 的原理跟springsecurity中原理一样:

在表单中加入一个字段,将 XSRF Token 带回来 :

代码案例如下:

func (c *MainController) XsrfPage() {
	c.XSRFExpire = 7200
	c.Data["xsrfdata"] = template.HTML(c.XSRFFormHTML())
	c.TplName = "xsrf.html"
}

xsrf.html网页代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/new_message" method="post">
    {{ .xsrfdata }}
    <input type="text" name="message" />
    <input type="submit" value="Post" />
</form>
</body>
</html>

路由配置:

	beego.Router("/xsrfPage", &controllers.MainController{}, "get:XsrfPage")
	

访问http://localhost:8080/xsrfPage

返回结果如下:


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/new_message" method="post">
    <!--这里有个隐藏的input框 !-->
    <input type="hidden" name="_xsrf" value="HPEVK9ZFA3TqlmNgTJOOeq87AsPCeB4K" />
    <input type="text" name="message" />
    <input type="submit" value="Post" />
</form>
</body>
</html>

除了在输入框中携带xsrf外还可以在header中添加,然后从header中添加

controller级别的xsrf屏蔽:

XSRF 之前是全局设置的一个参数,如果设置了那么所有的 API 请求都会进行验证,但是有些时候 API 逻辑是不需要进行验证的,因此现在支持在 Controller 级别设置屏蔽:

关于模板

Beego 中默认的模板目录是 views,用户可以把模板文件放到该目录下,

Beego 会自动的在调用完相应的 method 方法之后调用 Render 函数,当然如果您的应用是不需要模板输出的,那么可以在配置文件或者在 main.go 中设置关闭自动渲染。

模板文件设置

配置文件:

//配置文件中
//自动渲染template开关
//autorender=false
//或者代码中配置:
	/**
	配置自动渲染开关
	*/
	//beego.BConfig.WebConfig.AutoRender = true

有时候我们使用go中自带的模板中的符号跟其他语言冲突,比如AngularJS 开发,他的模板也是这个标签 ,在 Beego 中你可以通过配置文件或者直接设置配置变量修改:

#配置模板左右的包围符号
#templateleft=>>>
#templateright=<<<

模板文件位置:

如果没有指定模板文件,那么beego会自动下面这个位置寻找模板文件

c.TplName = strings.ToLower(c.controllerName) + "/" + strings.ToLower(c.actionName) + "." + c.TplExt位置,beego

举个例子:


func (c *ConsumerController) GetConsumerInfo() {
	//cid := c.GetString("id")
	cid, err := c.GetInt64("id")

	//uid, err := strconv.Atoi(cid)
	if err != nil {
		logs.Error(err)
		return
	}
	consumerInfo, err := models.GetConsumerInfo(cid)
	c.Data["json"] = &coom.Response{
		Code:    200,
		Msg:     "success",
		Data:    consumerInfo,
		Success: true,
	}
    
	//c.ServeJSON()
}

注册路由后,访问

localhost:8080/auth/getConsumerInfo

返回结果:

you panic, err: can't find templatefile in the path:views/consumercontroller/getconsumerinfo.tpl

此时我们如果要使用模板文件需要指定模板文件位置

Beego 默认情况下支持 tplhtml 后缀名的模板文件,如果你的后缀名不是这两种 ,可以设置

beego.AddTemplateExt(".txt")

测试代码:


func (c *ConsumerController) GetConsumerInfo() {
	//cid := c.GetString("id")
	cid, err := c.GetInt64("id")

	//uid, err := strconv.Atoi(cid)
	if err != nil {
		logs.Error(err)
		return
	}
	consumerInfo, err := models.GetConsumerInfo(cid)
	c.Data["json"] = &coom.Response{
		Code:    200,
		Msg:     "success",
		Data:    consumerInfo,
		Success: true,
	}
	c.TplName = "consumer.txt"
	//c.ServeJSON()
}

模板文件consumer.txt

{{.json}}

注册路由后,访问

localhost:8080/auth/getConsumerInfo

{200 success 0xc000352210 true} 

缓存模块

beego 的 cache 模块是用来做数据缓存的,设计思路来自于 database/sql,目前支持 file、memcache、memory 和 redis 四种引擎

go get github.com/astaxie/beego/cache

PS D:\UsedData\beegolearn0829\src\hello> go get github.com/astaxie/beeg
o/cache
go: added github.com/astaxie/beego v1.12.3

如果你使用memcache 或者 redis 驱动就需要手工安装引入包

PS D:\UsedData\beegolearn0829\src\hello> go get -u github.com/beego/bee
go/v2/client/cache/memcache
go: downloading github.com/bradfitz/gomemcache v0.0.0-20230904043129-d8
go: upgraded golang.org/x/net v0.7.0 => v0.10.0
go: upgraded golang.org/x/sync v0.1.0 => v0.3.0
go: upgraded golang.org/x/sys v0.6.0 => v0.9.0
go: upgraded golang.org/x/text v0.7.0 => v0.10.0
PS D:\UsedData\beegolearn0829\src\hello>

需要的地方使用引入包

import _ "github.com/beego/beego/v2/client/cache/memcache"
D:\UsedData\beegolearn0829\src\hello> go get -u github.com/beego/bee
go/v2/client/cache/memcache
go: downloading github.com/bradfitz/gomemcache v0.0.0-20190913173617-a4
1fca850d0b
go: upgraded github.com/beego/beego/v2 v2.1.0 => v2.1.1
go: upgraded github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fc
a850d0b => v0.0.0-20230904043129-d8906edec246
PS D:\UsedData\beegolearn0829\src\hello>

memorycache

models层

package models

import (
	"github.com/astaxie/beego/cache"
	"github.com/beego/beego/v2/client/orm"
	"github.com/beego/beego/v2/core/logs"
	"strconv"
	"time"
)

type Goods struct {
	GoodsId    int64
	GoodsName  string
	ConsumerId int64
}

var ca cache.Cache

func init() {
	orm.RegisterModel(new(Goods))
	ca = cache.NewMemoryCache()
}

/*
*
查询goods信息
加入缓存
*/

func GetGoods(id int64, name string) (*Goods, error) {
	newOrm := orm.NewOrm()
	queryTable := newOrm.QueryTable("goods")
	//操作符__gt ,i开头表示忽略大小写
	if len(name) != 0 {
		queryTable.Filter("goods_name__istartwith", name)
	}
	//返回一个querySetter对象
	queryTable = queryTable.Filter("goods_id", id)
	var goods Goods
	err := queryTable.One(&goods)
	if err != nil {
		logs.Error(err)
	}

	/**
	加入内存缓存
	*/

	err = ca.Put(strconv.FormatInt(id, 10), &goods, 60*time.Second)
	if err != nil {
		logs.Error(err)
	}
	gods := ca.Get(strconv.FormatInt(id, 10))
	logs.Info(gods)
	return &goods, err

}

func GetGoodsIfInCache(id int64) *Goods {

	goodsInfo := ca.Get(strconv.FormatInt(id, 10))
	//if goodsInfo == nil {
	//	querySeter := orm.NewOrm().QueryTable("goods").Filter("goods_id", id)
	//	querySeter.One(&goods)
	//	return &goods
	//}
	//判断数据类型
	g, ok := goodsInfo.(*Goods)
	if ok {
		return g
	} else {
		return nil

	}

}

controller层


func (g *GoodsController) GetGoods() {
	goodsName := g.GetString("goodsName")
	goodsId, err := g.GetInt64("id")
	if err != nil {
		logs.Error(err)
	}
	goods, err := models.GetGoods(goodsId, goodsName)

	if err != nil {
		logs.Error("err")
	}
	g.Data["json"] = &goods
	g.ServeJSON()
}
func (g *GoodsController) GetGoodsIfInCache() {

	gid, err := g.GetInt64("id")
	if err != nil {
		logs.Error(err)
	}
	goods := models.GetGoodsIfInCache(gid)
	g.Data["json"] = &goods
	g.ServeJSON()
}

路由注册~略

访问,等待一分钟之后缓存被清除

filecache

代码案例:

package models

import (
	"github.com/astaxie/beego/cache"
	"github.com/beego/beego/v2/client/orm"
	"github.com/beego/beego/v2/core/logs"
	"strconv"
	"time"
)

type Goods struct {
	GoodsId    int64
	GoodsName  string
	ConsumerId int64
}

// var ca cache.Cache
var caf cache.Cache

func init() {
	orm.RegisterModel(new(Goods))
	//ca = cache.NewMemoryCache()
	caf = cache.NewFileCache()

}

/*
*
查询goods信息
加入缓存
*/

func GetGoods(id int64, name string) (*Goods, error) {
	newOrm := orm.NewOrm()
	queryTable := newOrm.QueryTable("goods")
	//操作符__gt ,i开头表示忽略大小写
	if len(name) != 0 {
		queryTable.Filter("goods_name__istartwith", name)
	}
	//返回一个querySetter对象
	queryTable = queryTable.Filter("goods_id", id)
	var goods Goods
	err := queryTable.One(&goods)
	if err != nil {
		logs.Error(err)
	}

	/**
	加入file缓存
	*/
	err = caf.Put(strconv.FormatInt(id, 10), &goods, 60*time.Second)
	if err != nil {
		logs.Error(err)
	}
	return &goods, err

}

func GetGoodsIfInCache(id int64) *Goods {
 
	goodsInfo := caf.Get(strconv.FormatInt(id, 10))
	 
	//判断数据类型
	g, ok := goodsInfo.(*Goods)//这里是指针
	if ok {
		return g
	} else {
		return nil

	}

}

默认在项目的根目录下会生成一个用于存储信息的文件,如果要指定位置,那么就需要使用通用的方案:

配置文件该怎么写呢?来看源码~


// StartAndGC will start and begin gc for file cache.
// the config need to be like {CachePath:"/cache","FileSuffix":".bin","DirectoryLevel":"2","EmbedExpiry":"0"}
func (fc *FileCache) StartAndGC(config string) error {

	cfg := make(map[string]string)
	err := json.Unmarshal([]byte(config), &cfg)
	if err != nil {
		return err
	}
	if _, ok := cfg["CachePath"]; !ok {
		cfg["CachePath"] = FileCachePath
	}
	if _, ok := cfg["FileSuffix"]; !ok {
		cfg["FileSuffix"] = FileCacheFileSuffix
	}
	if _, ok := cfg["DirectoryLevel"]; !ok {
		cfg["DirectoryLevel"] = strconv.Itoa(FileCacheDirectoryLevel)
	}
	if _, ok := cfg["EmbedExpiry"]; !ok {
		cfg["EmbedExpiry"] = strconv.FormatInt(int64(FileCacheEmbedExpiry.Seconds()), 10)
	}
	fc.CachePath = cfg["CachePath"]
	fc.FileSuffix = cfg["FileSuffix"]
	fc.DirectoryLevel, _ = strconv.Atoi(cfg["DirectoryLevel"])
	fc.EmbedExpiry, _ = strconv.Atoi(cfg["EmbedExpiry"])

	fc.Init()
	return nil
}

我们需要一个map,然后转为string


// var ca cache.Cache
var caf cache.Cache

func init() {
	orm.RegisterModel(new(Goods))
	//ca = cache.NewMemoryCache()
	//caf = cache.NewFileCache()
	//指定缓存类型,加载配置
	cfg := make(map[string]string)
	//设置目录
	cfg["CachePath"] = "./cacheFile"
	cfg["FileSuffix"] = ".bin"
	//设置目录层级
	cfg["DirectoryLevel"] = "2"
    //设置到期时间
	cfg["EmbedExpiry"] = "60"
	marshal, _ := json.Marshal(cfg)
	config := string(marshal)
	//简单的就是:{CachePath:"/cache","FileSuffix":".bin","DirectoryLevel":"2","EmbedExpiry":"0"}
	caf, _ = cache.NewCache("file", config)
}

目录层级:

├─cacheFile
│  └─c9
│      └─f0
│              c9f0f895fb98ab9159f51fd0297e236d.bin
│
同理我们可以

同理我们可以使用

func NewCache(adapterName, config string) (adapter Cache, err error)

创建其他的方式缓存

memorycache


// NewCache Create a new cache driver by adapter name and config string.
// config need to be correct JSON as string: {"interval":360}.
// it will start gc automatically.
func NewCache(adapterName, config string) (adapter Cache, err error) {
	instanceFunc, ok := adapters[adapterName]
	if !ok {
		err = fmt.Errorf("cache: unknown adapter name %q (forgot to import?)", adapterName)
		return
	}
	adapter = instanceFunc()
	err = adapter.StartAndGC(config)
	if err != nil {
		adapter = nil
	}
	return
}

只需要配置interval即可

//----------------------分隔符----------------
	m := make(map[string]int)
	m["interval"]=60
	bytes, _ := json.Marshal(m)
	cache.NewCache("memory",string(bytes))

redis缓存

使用redis缓存时需要安装驱动并引入包

go get -u github.com/beego/beego/v2/client/cache/memcache

官方案例:

func main() {
	dsn := "127.0.0.1:6379"
  password := "123456"
  dbNum := 0
  dialFunc := func() (c redis.Conn, err error) {
    c, err = redis.Dial("tcp", dsn)
    if err != nil {
      return nil, berror.Wrapf(err, cache.DialFailed,
        "could not dial to remote server: %s ", dsn)
    }

    if password != "" {
      if _, err = c.Do("AUTH", password); err != nil {
        _ = c.Close()
        return nil, err
      }
    }

    _, selecterr := c.Do("SELECT", dbNum)
    if selecterr != nil {
      _ = c.Close()
      return nil, selecterr
    }
    return
  }
  // initialize a new pool
  pool := &redis.Pool{
    Dial:        dialFunc,
    MaxIdle:     3,
    IdleTimeout: 3 * time.Second,
  }

  bm := NewRedisCache(pool)
}

日志模块

import (
"github.com/beego/beego/v2/core/logs"
)

设置日志引擎

func init() {
	//设置日志引擎

	logs.SetLogger(logs.AdapterConsole)
	)
	log := logs.NewLogger()
	//log.SetLogger(logs.AdapterConsole)
	log.SetLogger(logs.AdapterFile, `{"filename":"test.log"}`)
	log.Info("开始启动")

或者直接使用下面的方式


	logs.SetLogger(logs.AdapterFile, `{"filepath":"./log","filename":"test.log"}`)
	logs.Info("开始启动")

日志引擎配置

引擎配置设置

  • console: 命令行输出,默认输出到os.Stdout

    logs.SetLogger(logs.AdapterConsole, `{"level":1,"color":true}`)
    

    主要的参数如下说明:

    • level 输出的日志级别
    • color 是否开启打印日志彩色打印(需环境支持彩色输出)
  • file:输出到文件,设置的例子如下所示:

    logs.SetLogger(logs.AdapterFile, `{"filename":"test.log"}`)
    

    主要的参数如下说明:

    • filename 保存的文件名
    • maxlines 每个文件保存的最大行数,默认值 1000000
    • maxsize 每个文件保存的最大尺寸,默认值是 1 << 28, 256 MB
    • daily 是否按照每天 logrotate,默认是 true
    • maxdays 文件最多保存多少天,默认保存 7 天
    • rotate 是否开启 logrotate,默认是 true
    • level 日志保存的时候的级别,默认是 Trace 级别
    • perm 日志文件权限
logs.SetLogger(logs.AdapterMultiFile, `{"filename":"test.log","separate":["emergency", "alert", "critical", "error", "warning", "notice", "info", "debug"]}`)
	

日志模块其他配置 | Beego (gocn.vip)

数据校验

安装依赖:

go get github.com/beego/beego/v2/core/validation

代码案例:

models层:


func AddPersonInfo(per *Person) (result int64, err error) {
	valid := validation.Validation{}
	valid.MaxSize(len(per.Name), 10, "name")
	valid.MaxSize(per.Pid, 60, "pid")
	valid.Range(per.Age, 0, 60, "age")
	if valid.HasErrors() {
		//for _, err := range valid.Errors {
		//	log.Println(err.Key, err.Message)
		//}
		return -1, valid.Errors[0]
	} else {
		return orm.NewOrm().Insert(per)
	}

}

controllers层

package controllers

import (
	"github.com/beego/beego/v2/core/logs"
	beego "github.com/beego/beego/v2/server/web"
	"hello/coom"
	"hello/models"
)

type PersonController struct {
	beego.Controller
}

func (p *PersonController) AddPersonInfo() {
	var per models.Person
	err := p.BindJSON(&per)
	if err != nil {
		logs.Error(err)
	}
	result, err := models.AddPersonInfo(&per)
	if err != nil {
		logs.Error(err)
		p.Data["json"] = &coom.Response{
			Code:    400,
			Msg:     "新增失败",
			Data:    err,
			Success: false,
		}

	} else {
		p.Data["json"] = &coom.Response{
			Code:    200,
			Msg:     "新增成功",
			Data:    result,
			Success: false,
		}

	}
	p.ServeJSON()
}

校验失败时:

{
    "code": 400,
    "msg": "新增失败",
    "data": {
        "Message": " Maximum size is 10",
        "Key": "name",
        "Name": "name",
        "Field": "",
        "Tmpl": "",
        "Value": 5,
        "LimitValue": 10
    },
    "success": false
}

也可以直接写在结构体中

import (
    "log"
    "strings"

    "github.com/beego/beego/v2/core/validation"
)

type user struct {
    Id     int
    Name   string `valid:"Required;Match(/^Bee.*/)"` // Name 不能为空并且以 Bee 开头
    Age    int    `valid:"Range(1, 140)"` // 1 <= Age <= 140,超出此范围即为不合法
    Email  string `valid:"Email; MaxSize(100)"` // Email 字段需要符合邮箱格式,并且最大长度不能大于 100 个字符
    Mobile string `valid:"Mobile"` // Mobile 必须为正确的手机号
    IP     string `valid:"IP"` // IP 必须为一个正确的 IPv4 地址
}

// 如果你的 struct 实现了接口 validation.ValidFormer
// 当 StructTag 中的测试都成功时,将会执行 Valid 函数进行自定义验证
func (u *user) Valid(v *validation.Validation) {
    if strings.Index(u.Name, "admin") != -1 {
        // 通过 SetError 设置 Name 的错误信息,HasErrors 将会返回 true
        v.SetError("Name", "名称里不能含有 admin")
    }
}

func main() {
    valid := validation.Validation{}
    u := user{Name: "Beego", Age: 2, Email: "dev@web.me"}
    b, err := valid.Valid(&u)
    if err != nil {
        // handle error
    }
    if !b {
        // validation does not pass
        // blabla...
        for _, err := range valid.Errors {
            log.Println(err.Key, err.Message)
        }
    }
}

需要注意的是,Valid方法是用户自定义验证方法,它接收两个参数:

  • 字段名字
  • 错误原因

在实现该接口的时候,只需要将错误信息写入validation.Validation.

StructTag 可用的验证函数:

  • Required 不为空,即各个类型要求不为其零值
  • Min(min int) 最小值,有效类型:int,其他类型都将不能通过验证
  • Max(max int) 最大值,有效类型:int,其他类型都将不能通过验证
  • Range(min, max int) 数值的范围,有效类型:int,他类型都将不能通过验证
  • MinSize(min int) 最小长度,有效类型:string slice,其他类型都将不能通过验证
  • MaxSize(max int) 最大长度,有效类型:string slice,其他类型都将不能通过验证
  • Length(length int) 指定长度,有效类型:string slice,其他类型都将不能通过验证
  • Alpha alpha 字符,有效类型:string,其他类型都将不能通过验证
  • Numeric 数字,有效类型:string,其他类型都将不能通过验证
  • AlphaNumeric alpha 字符或数字,有效类型:string,其他类型都将不能通过验证
  • Match(pattern string) 正则匹配,有效类型:string,其他类型都将被转成字符串再匹配(fmt.Sprintf(“%v”, obj).Match)
  • AlphaDash alpha 字符或数字或横杠 -_,有效类型:string,其他类型都将不能通过验证
  • Email 邮箱格式,有效类型:string,其他类型都将不能通过验证
  • IP IP 格式,目前只支持 IPv4 格式验证,有效类型:string,其他类型都将不能通过验证
  • Base64 base64 编码,有效类型:string,其他类型都将不能通过验证
  • Mobile 手机号,有效类型:string,其他类型都将不能通过验证
  • Tel 固定电话号,有效类型:string,其他类型都将不能通过验证
  • Phone 手机号或固定电话号,有效类型:string,其他类型都将不能通过验证
  • ZipCode 邮政编码,有效类型:string,其他类型都将不能通过验证

定时任务

}
if _, ok := cfg["FileSuffix"]; !ok {
	cfg["FileSuffix"] = FileCacheFileSuffix
}
if _, ok := cfg["DirectoryLevel"]; !ok {
	cfg["DirectoryLevel"] = strconv.Itoa(FileCacheDirectoryLevel)
}
if _, ok := cfg["EmbedExpiry"]; !ok {
	cfg["EmbedExpiry"] = strconv.FormatInt(int64(FileCacheEmbedExpiry.Seconds()), 10)
}
fc.CachePath = cfg["CachePath"]
fc.FileSuffix = cfg["FileSuffix"]
fc.DirectoryLevel, _ = strconv.Atoi(cfg["DirectoryLevel"])
fc.EmbedExpiry, _ = strconv.Atoi(cfg["EmbedExpiry"])

fc.Init()
return nil

}


我们需要一个map,然后转为string

```go

// var ca cache.Cache
var caf cache.Cache

func init() {
	orm.RegisterModel(new(Goods))
	//ca = cache.NewMemoryCache()
	//caf = cache.NewFileCache()
	//指定缓存类型,加载配置
	cfg := make(map[string]string)
	//设置目录
	cfg["CachePath"] = "./cacheFile"
	cfg["FileSuffix"] = ".bin"
	//设置目录层级
	cfg["DirectoryLevel"] = "2"
    //设置到期时间
	cfg["EmbedExpiry"] = "60"
	marshal, _ := json.Marshal(cfg)
	config := string(marshal)
	//简单的就是:{CachePath:"/cache","FileSuffix":".bin","DirectoryLevel":"2","EmbedExpiry":"0"}
	caf, _ = cache.NewCache("file", config)
}

目录层级:

├─cacheFile
│  └─c9
│      └─f0
│              c9f0f895fb98ab9159f51fd0297e236d.bin
│
同理我们可以

同理我们可以使用

func NewCache(adapterName, config string) (adapter Cache, err error)

创建其他的方式缓存

memorycache


// NewCache Create a new cache driver by adapter name and config string.
// config need to be correct JSON as string: {"interval":360}.
// it will start gc automatically.
func NewCache(adapterName, config string) (adapter Cache, err error) {
	instanceFunc, ok := adapters[adapterName]
	if !ok {
		err = fmt.Errorf("cache: unknown adapter name %q (forgot to import?)", adapterName)
		return
	}
	adapter = instanceFunc()
	err = adapter.StartAndGC(config)
	if err != nil {
		adapter = nil
	}
	return
}

只需要配置interval即可

//----------------------分隔符----------------
	m := make(map[string]int)
	m["interval"]=60
	bytes, _ := json.Marshal(m)
	cache.NewCache("memory",string(bytes))

redis缓存

使用redis缓存时需要安装驱动并引入包

go get -u github.com/beego/beego/v2/client/cache/memcache

官方案例:

func main() {
	dsn := "127.0.0.1:6379"
  password := "123456"
  dbNum := 0
  dialFunc := func() (c redis.Conn, err error) {
    c, err = redis.Dial("tcp", dsn)
    if err != nil {
      return nil, berror.Wrapf(err, cache.DialFailed,
        "could not dial to remote server: %s ", dsn)
    }

    if password != "" {
      if _, err = c.Do("AUTH", password); err != nil {
        _ = c.Close()
        return nil, err
      }
    }

    _, selecterr := c.Do("SELECT", dbNum)
    if selecterr != nil {
      _ = c.Close()
      return nil, selecterr
    }
    return
  }
  // initialize a new pool
  pool := &redis.Pool{
    Dial:        dialFunc,
    MaxIdle:     3,
    IdleTimeout: 3 * time.Second,
  }

  bm := NewRedisCache(pool)
}

日志模块

import (
"github.com/beego/beego/v2/core/logs"
)

设置日志引擎

func init() {
	//设置日志引擎

	logs.SetLogger(logs.AdapterConsole)
	)
	log := logs.NewLogger()
	//log.SetLogger(logs.AdapterConsole)
	log.SetLogger(logs.AdapterFile, `{"filename":"test.log"}`)
	log.Info("开始启动")

或者直接使用下面的方式


	logs.SetLogger(logs.AdapterFile, `{"filepath":"./log","filename":"test.log"}`)
	logs.Info("开始启动")

日志引擎配置

引擎配置设置

  • console: 命令行输出,默认输出到os.Stdout

    logs.SetLogger(logs.AdapterConsole, `{"level":1,"color":true}`)
    

    主要的参数如下说明:

    • level 输出的日志级别
    • color 是否开启打印日志彩色打印(需环境支持彩色输出)
  • file:输出到文件,设置的例子如下所示:

    logs.SetLogger(logs.AdapterFile, `{"filename":"test.log"}`)
    

    主要的参数如下说明:

    • filename 保存的文件名
    • maxlines 每个文件保存的最大行数,默认值 1000000
    • maxsize 每个文件保存的最大尺寸,默认值是 1 << 28, 256 MB
    • daily 是否按照每天 logrotate,默认是 true
    • maxdays 文件最多保存多少天,默认保存 7 天
    • rotate 是否开启 logrotate,默认是 true
    • level 日志保存的时候的级别,默认是 Trace 级别
    • perm 日志文件权限
logs.SetLogger(logs.AdapterMultiFile, `{"filename":"test.log","separate":["emergency", "alert", "critical", "error", "warning", "notice", "info", "debug"]}`)
	

日志模块其他配置 | Beego (gocn.vip)

数据校验

安装依赖:

go get github.com/beego/beego/v2/core/validation

代码案例:

models层:


func AddPersonInfo(per *Person) (result int64, err error) {
	valid := validation.Validation{}
	valid.MaxSize(len(per.Name), 10, "name")
	valid.MaxSize(per.Pid, 60, "pid")
	valid.Range(per.Age, 0, 60, "age")
	if valid.HasErrors() {
		//for _, err := range valid.Errors {
		//	log.Println(err.Key, err.Message)
		//}
		return -1, valid.Errors[0]
	} else {
		return orm.NewOrm().Insert(per)
	}

}

controllers层

package controllers

import (
	"github.com/beego/beego/v2/core/logs"
	beego "github.com/beego/beego/v2/server/web"
	"hello/coom"
	"hello/models"
)

type PersonController struct {
	beego.Controller
}

func (p *PersonController) AddPersonInfo() {
	var per models.Person
	err := p.BindJSON(&per)
	if err != nil {
		logs.Error(err)
	}
	result, err := models.AddPersonInfo(&per)
	if err != nil {
		logs.Error(err)
		p.Data["json"] = &coom.Response{
			Code:    400,
			Msg:     "新增失败",
			Data:    err,
			Success: false,
		}

	} else {
		p.Data["json"] = &coom.Response{
			Code:    200,
			Msg:     "新增成功",
			Data:    result,
			Success: false,
		}

	}
	p.ServeJSON()
}

校验失败时:

{
    "code": 400,
    "msg": "新增失败",
    "data": {
        "Message": " Maximum size is 10",
        "Key": "name",
        "Name": "name",
        "Field": "",
        "Tmpl": "",
        "Value": 5,
        "LimitValue": 10
    },
    "success": false
}

也可以直接写在结构体中

import (
    "log"
    "strings"

    "github.com/beego/beego/v2/core/validation"
)

type user struct {
    Id     int
    Name   string `valid:"Required;Match(/^Bee.*/)"` // Name 不能为空并且以 Bee 开头
    Age    int    `valid:"Range(1, 140)"` // 1 <= Age <= 140,超出此范围即为不合法
    Email  string `valid:"Email; MaxSize(100)"` // Email 字段需要符合邮箱格式,并且最大长度不能大于 100 个字符
    Mobile string `valid:"Mobile"` // Mobile 必须为正确的手机号
    IP     string `valid:"IP"` // IP 必须为一个正确的 IPv4 地址
}

// 如果你的 struct 实现了接口 validation.ValidFormer
// 当 StructTag 中的测试都成功时,将会执行 Valid 函数进行自定义验证
func (u *user) Valid(v *validation.Validation) {
    if strings.Index(u.Name, "admin") != -1 {
        // 通过 SetError 设置 Name 的错误信息,HasErrors 将会返回 true
        v.SetError("Name", "名称里不能含有 admin")
    }
}

func main() {
    valid := validation.Validation{}
    u := user{Name: "Beego", Age: 2, Email: "dev@web.me"}
    b, err := valid.Valid(&u)
    if err != nil {
        // handle error
    }
    if !b {
        // validation does not pass
        // blabla...
        for _, err := range valid.Errors {
            log.Println(err.Key, err.Message)
        }
    }
}

需要注意的是,Valid方法是用户自定义验证方法,它接收两个参数:

  • 字段名字
  • 错误原因

在实现该接口的时候,只需要将错误信息写入validation.Validation.

StructTag 可用的验证函数:

  • Required 不为空,即各个类型要求不为其零值
  • Min(min int) 最小值,有效类型:int,其他类型都将不能通过验证
  • Max(max int) 最大值,有效类型:int,其他类型都将不能通过验证
  • Range(min, max int) 数值的范围,有效类型:int,他类型都将不能通过验证
  • MinSize(min int) 最小长度,有效类型:string slice,其他类型都将不能通过验证
  • MaxSize(max int) 最大长度,有效类型:string slice,其他类型都将不能通过验证
  • Length(length int) 指定长度,有效类型:string slice,其他类型都将不能通过验证
  • Alpha alpha 字符,有效类型:string,其他类型都将不能通过验证
  • Numeric 数字,有效类型:string,其他类型都将不能通过验证
  • AlphaNumeric alpha 字符或数字,有效类型:string,其他类型都将不能通过验证
  • Match(pattern string) 正则匹配,有效类型:string,其他类型都将被转成字符串再匹配(fmt.Sprintf(“%v”, obj).Match)
  • AlphaDash alpha 字符或数字或横杠 -_,有效类型:string,其他类型都将不能通过验证
  • Email 邮箱格式,有效类型:string,其他类型都将不能通过验证
  • IP IP 格式,目前只支持 IPv4 格式验证,有效类型:string,其他类型都将不能通过验证
  • Base64 base64 编码,有效类型:string,其他类型都将不能通过验证
  • Mobile 手机号,有效类型:string,其他类型都将不能通过验证
  • Tel 固定电话号,有效类型:string,其他类型都将不能通过验证
  • Phone 手机号或固定电话号,有效类型:string,其他类型都将不能通过验证
  • ZipCode 邮政编码,有效类型:string,其他类型都将不能通过验证

定时任务

定时任务 | Beego (gocn.vip)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CodeMartain

祝:生活蒸蒸日上!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值