当前位置:   article > 正文

Golang Devops项目开发_golang开发devops

golang开发devops

1.1 GO语言基础

1 初识Go语言

1.1.1 开发环境搭建

参考文档:《Windows Go语言环境搭建》

1.2.1 Go语言特性-垃圾回收

a. 内存自动回收,再也不需要开发人员管理内存

b. 开发人员专注业务实现,降低了心智负担

c. 只需要new分配内存,不需要释放

d. gc 垃圾回收

1.2.2 Go语言特性-天然并发

a. 从语言层面支持并发,非常简单

b. goroutine,轻量级线程,创建成千上万个goroutine成为可能

c. 基于CSP(Communicating Sequential Process)模型实现

  1. package main
  2. import(
  3. "time"
  4. )
  5. func main() {
  6. for i := 0; i < 100; i++ {
  7. go test_goroute(i)
  8. }
  9. time.Sleep(time.Second)
  10. }

进一步阅读:《 GO Channel并发、死锁问题》 https://www.cnblogs.com/show58/p/12699083.html

《 Go的CSP并发模型实现:M, P, G 》https://www.cnblogs.com/sunsky303/p/9115530.html

1.2.3 Go语言特性-channel

a. 管道,类似unix/linux中的pipe

b. 多个goroutine之间通过channel进行通信

c. 支持任何类型

  1. func main() {
  2.     pipe := make(chan int, 3)
  3.     pipe <- 1
  4.     pipe <- 2
  5. }
  1. package main
  2. import "fmt"
  3. func test_pipe() {
  4. pipe := make(chan int, 3)
  5. pipe <- 1
  6. pipe <- 2
  7. pipe <- 3
  8. var t1 int
  9. t1 = <-pipe
  10. fmt.Println("t1: ", t1)
  11. }
  12. func sum(s []int, c chan int) {
  13. test_pipe()
  14. sum := 0
  15. for _, v := range s {
  16. sum += v
  17. }
  18. fmt.Println("sum:", sum)
  19. c <- sum // send sum to c
  20. }
  21. func main() {
  22. s := []int{7, 2, 8, -9, 4, 0}
  23. c := make(chan int)
  24. go sum(s[:len(s)/2], c) // 7+2+8 = 17, -9 + 4+0 = -5
  25. go sum(s[len(s)/2:], c)
  26. // x, y := <-c, <-c // receive from c
  27. x := <-c
  28. y := <-c
  29. fmt.Println(x, y, x+y)
  30. }

1.2.4 Go语言特性-多返回值

一个函数返回多个值

  1. func calc(a int, b int) (int, int) {
  2. sum := a+b
  3. avg := (a+b)/2
  4. return sum, avg
  5. }

1.4.1包的概念

1. 和python一样,把相同功能的代码放到一个目录,称之为包

2. 包可以被其他包引用

3. main包是用来生成可执行文件,每个程序只有一个main包

4. 包的主要用途是提高代码的可复用性

2. Go语言基础

2 基本数据类型和操作符

1. 文件名&关键字&标识符

2. Go程序基本结构

3. 常量和变量

4. 数据类型和操作符

5. 字符串类型

2.1文件名&关键字&标识符

1. 所有go源码以.go结尾

2. 标识符以字母或下划线开头,大小写敏感,比如:

a. boy

b. Boy

c. a+b

d. 0boy

e. _boy

f. =_boy

g. _

3. _是特殊标识符,用来忽略结果

4. 保留关键字

2.1文件名&关键字&标识符-关键字1

2.1文件名&关键字&标识符-关键字2

◼ var和const :变量和常量的声明

var varName type 或者 varName : = value

◼ package and import: 包和导入

◼ func: 用于定义函数和方法

◼ return :用于从函数返回

◼ defer someCode :在函数退出之前执行

◼ go : 用于并行

◼ select 用于选择不同类型的通讯

◼ interface 用于定义接口

◼ struct 用于定义抽象数据类型

2.1文件名&关键字&标识符-关键字3

◼ break、case、continue、for、fallthrough、else、if、switch、goto、default 流程控制

◼ fallthrough的用法注意总结 [推荐阅读

https://blog.csdn.net/ajdfhajdkfakr/article/details/79086125]

◼ 1.加了fallthrough后,会直接运行【紧跟的后一个】case或default语句,不论条件是否满

足都会执行

◼ 2.加了fallthrough语句后,【紧跟的后一个】case条件不能定义常量和变量

◼ 3.执行完fallthrough后直接跳到下一个条件语句,本条件执行语句后面的语句不执行

◼ chan用于channel通讯

◼ type用于声明自定义类型

◼ map用于声明map类型数据

◼ range用于读取slice、map、channel数据

2.2 Go程序的基本结构1

1. 任何一个代码文件隶属于一个包

2. import 关键字,引用其他包:

2.2 Go程序的基本结构2

3. golang可执行程序,package main,

并且有且只有一个main入口函数

4. 包中函数调用:

a. 同一个包中函数,直接调用

b. 不同包中函数,通过包名+点+

函数名进行调用

5. 包访问控制规则:

a. 大写意味着这个函数/变量是可导出的

b. 小写意味着这个函数/变量是私有的,

包外部不能访问

2.4 常量1

1. 常量使用const 修饰,代表永远是只读的,不能修改。

2. const 只能修饰boolean,number(int相关类型、浮点类型、complex)和string。

3. 语法:const identifier [type] = value,其中type可以省略。

举例: const b string = “hello world”

const b = “hello world”

const Pi = 3.1414926

const a = 9/3

const c = getValue()

  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. )
  6. const (
  7. Man = 1
  8. Female = 2
  9. )
  10. func main() {
  11. for {
  12. second := time.Now().Unix()
  13. if second%Female == 0 {
  14. fmt.Println("female")
  15. } else {
  16. fmt.Println("man")
  17. }
  18. time.Sleep(1000 * time.Millisecond)
  19. }
  20. }

2.4 常量2

4. 比较优雅的写法:

尽量减少我们写代码 const (

a = 0

b = 1

c = 2

)

5. 更加专业的写法:

const (

a = iota

b //1

c //2

)

2.5 变量1

1. 语法:var identifier type

2.5 变量2

Var (

    a int //默认为0

    b string //默认为””

    c bool //默认为false

    d = 8

    e = “hello world”

    )

练习

写一个程序获取当前运行的操作系统名称和PATH环境环境变量的值,并打印在终端

  1. package main
  2. import (
  3. "fmt"
  4. "os"
  5. )
  6. func main() {
  7. var goos string = os.Getenv("GOOS")
  8. fmt.Printf("The operating system is: %s\n", goos)
  9. path := os.Getenv("Path")
  10. fmt.Printf("Path is %s\n", path)
  11. }

2.6 值类型和引用类型

1. 值类型:变量直接存储值,内存通常在栈中分配。

2. 引用类型:变量存储的是一个地址,这个地址存储最终的值。内存通常在堆上分配。通过GC回收。

2.6值类型和引用类型

1. 值类型:基本数据类型int、float、bool、string以及数组和struct。

2. 引用类型:指针、slice、map、chan等都是引用类型。

练习:写一个程序,交换两个整数的值。比如: a=3; b=4; 交换之后:a=4;b=3

代码:2-6-swap.go

  1. package main
  2. import "fmt"
  3. func swap(a *int, b *int) {
  4. tmp := *a
  5. *a = *b
  6. *b = tmp
  7. return
  8. }
  9. func swap1(a int, b int) (int, int) {
  10. return b, a
  11. }
  12. func test() {
  13. var a = 100
  14. fmt.Println(a)
  15. //var b int
  16. for i := 0; i < 100; i++ {
  17. var b = i * 2
  18. fmt.Println(b)
  19. }
  20. //fmt.Println(c)
  21. //fmt.Println(b)
  22. }
  23. func test2() {
  24. var a int8 = 100
  25. var b int16 = int16(a)
  26. fmt.Printf("a=%d b=%d\n", a, b)
  27. }
  28. func main() {
  29. first := 100
  30. second := 200
  31. //swap(&first, &second)
  32. //first, second = swap1(first, second)
  33. first, second = second, first
  34. fmt.Println("first=", first)
  35. fmt.Println("second=", second)
  36. test()
  37. test2()
  38. }

练习:写一个程序用来打印值类型和引用类型变量到终端,并观察输出结果。

代码: 2-6-value_quote.go

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func modify(a int) {
  6. a = 10
  7. return
  8. }
  9. func modify1(a *int) {
  10. *a = 10
  11. }
  12. func main() {
  13. a := 5
  14. b := make(chan int, 1)
  15. fmt.Println("a=", a)
  16. fmt.Println("b=", b)
  17. modify(a)
  18. fmt.Println("a=", a)
  19. modify1(&a)
  20. fmt.Println("a=", a)
  21. }

2.7 变量的作用域

1. 在函数内部声明的变量叫做局部变量,生命周期仅限于函数内部。

2.在函数外部声明的变量叫做全局变量,生命周期作用于整个包,如果是大写的,

则作用于整个程序。

请指出下面程序的输出是什么
  1. package main
  2. var a = "G"
  3. func main() {
  4. n() // G
  5. m() // O
  6. n() //O
  7. }
  8. func n() {
  9. fmt.Println(a)
  10. }
  11. func m() {
  12. a = "O"
  13. fmt.Println(a)
  14. }

2.8 数据类型和操作符1

1. bool 类型,只能存 true false
2. 相关操作符, !、 && ||

2.8 数据类型和操作符2

3. 数字类型,主要有int、int8、int16、int32、int64、uint8、uint16、uint32、uint64、

float32、float64

4. 类型转换,type(variable),比如:var a int=8; var b int32=int32(a)

  1. package main
  2. func main() {
  3. var a int
  4. var b int32
  5. a = 15
  6. b = a + a // compiler error cannot use a + a (value of type int) as int32 value in assignment
  7. b = b + 5 // ok: 5 is a constant
  8. }
  1. package main
  2. import (
  3. "fmt"
  4. "math/rand"
  5. "time"
  6. )
  7. func init() {
  8. rand.Seed(time.Now().UnixNano())
  9. }
  10. func main() {
  11. for i := 0; i < 10; i++ {
  12. a := rand.Int()
  13. fmt.Println(a)
  14. }
  15. for i := 0; i < 10; i++ {
  16. a := rand.Intn(100)
  17. fmt.Println(a)
  18. }
  19. for i := 0; i < 10; i++ {
  20. a := rand.Float32()
  21. fmt.Println(a)
  22. }
  23. }

2.8 数据类型和操作符3

5. 字符类型:var a byte

var a byte = ‘c’

6. 字符串类型: var str string

练习:请指出下面程序的输出是什么?

package main

import "fmt"

func main() {

    var n int16 = 34

    var m int32

    m = n

    m = int32(n)

    fmt.Printf("32 bit int is: %d\n", m)

    fmt.Printf("16 bit int is: %d\n", n)

}

PS D:\Workspace\Go\src\projects\demo> go run main.go
# command-line-arguments
.\main.go:8:6: cannot use n (variable of type int16) as type int32 in assignment

练习:使用 math/rand 生成 10 个随机整数, 10 个小于 100
随机整数以及 10 个随机浮点数 代码:

2.9数据类型和操作符1

1. 逻辑操作符: == 、!=、

<

<=

>和 >=

2.9数据类型和操作符2

2. 数学操作符:+、-、

*

、/等等

  1. package main
  2. import (
  3. "fmt"
  4. "strings"
  5. "unsafe"
  6. _ "unsafe"
  7. )
  8. func test1() {
  9. bytes := []byte("I am byte array !")
  10. str := string(bytes)
  11. bytes[0] = 'i' //注意这一行,bytes在这里修改了数据,但是str打印出来的依然没变化,
  12. fmt.Println(str)
  13. }
  14. func test2() {
  15. bytes := []byte("I am byte array !")
  16. str := (*string)(unsafe.Pointer(&bytes))
  17. bytes[0] = 'i'
  18. fmt.Println(*str)
  19. }
  20. func test3() {
  21. var data [10]byte
  22. data[0] = 'T'
  23. data[1] = 'E'
  24. var str string = string(data[:])
  25. fmt.Println(str)
  26. }
  27. func str2bytes(s string) []byte {
  28. x := (*[2]uintptr)(unsafe.Pointer(&s))
  29. h := [3]uintptr{x[0], x[1], x[1]}
  30. return *(*[]byte)(unsafe.Pointer(&h))
  31. }
  32. func bytes2str(b []byte) string {
  33. return *(*string)(unsafe.Pointer(&b))
  34. }
  35. func test4() {
  36. s := strings.Repeat("abc", 3)
  37. fmt.Println("str2bytes")
  38. b := str2bytes(s)
  39. fmt.Println("bytes2str")
  40. s2 := bytes2str(b)
  41. fmt.Println(b, s2)
  42. }
  43. func main() {
  44. test1()
  45. test2()
  46. test3()
  47. test4()
  48. }

2.9 数据类型和操作符3

字符串表示两种方式: 1)双引号 2)`` (反引号)

  1. package main
  2. import "fmt"
  3. func main() {
  4. var str = "hello world\n\n"
  5. var str2 = `hello \n \n \n
  6. this is a test string
  7. This is a test string too·`
  8. fmt.Println("str=", str)
  9. fmt.Println("str2=", str2)
  10. }
  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func test_switch1() {
  6. a := 2
  7. switch a {
  8. case 1:
  9. fmt.Println("a=1")
  10. case 2:
  11. fmt.Println("a=2")
  12. case 3:
  13. fmt.Println("a=3")
  14. case 4:
  15. fmt.Println("a=4")
  16. default:
  17. fmt.Println("default")
  18. }
  19. }
  20. func test_switch2() {
  21. a := 2
  22. switch a {
  23. case 1:
  24. fmt.Println("a=1")
  25. case 2:
  26. fmt.Println("a=2")
  27. fallthrough
  28. case 3:
  29. fmt.Println("a=3")
  30. case 4:
  31. fmt.Println("a=4")
  32. default:
  33. fmt.Println("default")
  34. }
  35. }
  36. func main() {
  37. fmt.Printf("执行test_switch%d\n", 1)
  38. test_switch1()
  39. fmt.Printf("执行test_switch%d\n", 2)
  40. test_switch2()
  41. }

3. Go函数

3 流程控制

for range 语句

str := "hello world,中国"

for i, v := range str {

fmt.Printf("index[%d] val[%c]\n", i, v)

}

用来遍历数组、slice、map、chan。

  1. package main
  2. import "fmt"
  3. func modify(p *int) {
  4. fmt.Println(p)
  5. *p = 1000900
  6. return
  7. }
  8. func main() {
  9. var a int = 10
  10. fmt.Println(&a)
  11. var p *int
  12. p = &a
  13. fmt.Println("the address of p:", &p)
  14. fmt.Println("the value of p:", p)
  15. fmt.Println("the value of p point to variable:", *p)
  16. fmt.Println(*p)
  17. *p = 100
  18. fmt.Println(a)
  19. var b int = 999
  20. p = &b
  21. *p = 5
  22. fmt.Println(a)
  23. fmt.Println(b)
  24. modify(&a)
  25. fmt.Println(a)
  26. }

3 函数1

1. 声明语法:func 函数名 (参数列表) [(返回值列表)] {}

func add()

{

}

3 函数2

2. golang函数特点:

a. 不支持重载,一个包不能有两个名字一样的函数

b. 函数是一等公民,函数也是一种类型,一个函数可以赋值给变量

c. 匿名函数

d. 多返回值

3 函数2

2. golang函数特点:

  1. package main
  2. import (
  3. "fmt"
  4. "reflect"
  5. )
  6. func add(a, b int) int {
  7. return a + b
  8. }
  9. func main() {
  10. c := add
  11. fmt.Println(c)
  12. sum := c(10, 20)
  13. fmt.Println(sum)
  14. sf1 := reflect.ValueOf(c)
  15. sf2 := reflect.ValueOf(add)
  16. if sf1 == sf2 {
  17. fmt.Println("c equal add")
  18. }
  19. }
  1. package main
  2. import (
  3. "fmt"
  4. )
  5. type add_func func(int, int) int
  6. func add(a, b int) int {
  7. return a + b
  8. }
  9. func operator(op add_func, a int, b int) int {
  10. return op(a, b)
  11. }
  12. func main() {
  13. c := add
  14. fmt.Println(c)
  15. sum := operator(c, 100, 200)
  16. fmt.Println(sum)
  17. }

3 函数3

3. 函数参数传递方式:

1). 值传递

2). 引用传递

注意1:无论是值传递,还是引用传递,传递给函数的都是变量的副本,不过,值

传递是值的拷贝。引用传递是地址的拷贝,一般来说,地址

拷贝更为高效。而值拷贝取决于拷贝的对象大小,对象越大,则性能越低。

3 函数3

3. 函数参数传递方式: 1). 值传递

2). 引用传递

注意2:map、slice、chan、指针、interface默认以引用的方式传递,有待考证?

go只有值传递、浅拷贝

  1. package main
  2. import "fmt"
  3. func modify(a int) {
  4. a = 100
  5. }
  6. func main() {
  7. a := 8
  8. fmt.Println(a)
  9. modify(a)
  10. fmt.Println(a)
  11. }

3 函数4

4. 命名返回值的名字

func add(a, b int) (c int) {

    c = a + b

    return

}

func calc(a, b int) (sum int, avg int) {

    sum = a + b

    avg = (a + b) / 2

    return

}

3 函数5

5. _标识符,用来忽略返回值:

func calc(a, b int) (sum int, avg int) {

sum = a + b

avg = (a +b)/2

return

}

func main() {

sum, _ := calc(100, 200)

}

3 函数6

6. 可变参数: func add(arg…int) int {

}

0个或多个参数

func add(a int, arg…int) int {

}

1个或多个参数

func add(a int, b int, arg…int) int {

}

2个或多个参数

注意:其中arg是一个slice,我们可以通过arg[index]依次访问所有参数

通过len(arg)来判断传递参数的个数

  1. package main
  2. import "fmt"
  3. func add(a int, arg ...int) int {
  4. var sum int = a
  5. for i := 0; i < len(arg); i++ {
  6. sum += arg[i]
  7. }
  8. return sum
  9. }
  10. func concat(a string, arg ...string) (result string) {
  11. result = a
  12. for i := 0; i < len(arg); i++ {
  13. result += arg[i]
  14. }
  15. return
  16. }
  17. func main() {
  18. sum := add(10, 3, 3, 3, 3)
  19. fmt.Println(sum)
  20. res := concat("hello", " ", "world")
  21. fmt.Println(res)
  22. }

3 函数7

7. defer用途:

1. 当函数返回时,执行defer语句。因此,可以用来做资源清理

2. 多个defer语句,按先进后出的方式执行

3. defer语句中的变量,在defer声明时就决定了。

3 函数7 defer用途

package main

import "fmt"

func main() {

    i := 0

    defer fmt.Println(i)

    i++

    return

}

打印是多少?

PS D:\Workspace\Go\src\projects\demo> go run main.go
0

package main

import "fmt"

func main() {

    for i := 0; i < 5; i++ {

        defer fmt.Printf("%d", i)

    }

}

PS D:\Workspace\Go\src\projects\demo> go run main.go
43210

3 函数7 defer用途

1 关闭文件句柄

func read() {

    file := open(filename)

    defer file.Close()

    //文件操作

}

3 函数7 defer用途

2. 锁资源释放

func read() {

mc.Lock()

defer mc.Unlock()

//其他操作

}

3 函数7 defer用途

3. 数据库连接释放

func read() {

conn := openDatabase()

defer conn.Close()

//其他操作

}

4. Go数组和切片

4 常用结构

1. 内置函数、闭包

2. 数组与切片

3. map数据结构

4. package介绍

4.1 内置函数

1. close:主要用来关闭channel

2. len:用来求长度,比如string、array、slice、map、channel

3. new:用来分配内存,主要用来分配值类型,比如int、struct。返回的是指针

4. make:用来分配内存,主要用来分配引用类型,比如chan、map、slice

5. append:用来追加元素到数组、slice中

6. panic和recover:用来做错误处理

7. new和make的区别

  1. package main
  2. import (
  3. "fmt"
  4. "strings"
  5. )
  6. // 缩小变量作用域,减少对全局变量的污染。下面的累加如果用全局变量进行实现,全局变量容易被其他人污染。
  7. // 同时,所有我要实现n个累加器,那么每次需要n个全局变量。利用闭包,
  8. // 每个生成的累加器myAdder1, myAdder2 := adder(), adder()有自己独立的sum,sum可以看作为myAdder1.sum与myAdder2.sum。
  9. func Adder() func(int) int {
  10. var x int
  11. f := func(d int) int {
  12. x += d
  13. return x
  14. }
  15. return f
  16. }
  17. func makeSuffix(suffix string) func(string) string {
  18. f := func(name string) string {
  19. if strings.HasSuffix(name, suffix) == false {
  20. return name + suffix
  21. }
  22. return name
  23. }
  24. return f
  25. }
  26. func main() {
  27. f := Adder()
  28. fmt.Println(f(1))
  29. fmt.Println(f(100))
  30. // fmt.Println(f(1000))
  31. /*
  32. f1 := makeSuffix(".bmp")
  33. fmt.Println(f1("test"))
  34. fmt.Println(f1("pic"))
  35. f2 := makeSuffix(".jpg")
  36. fmt.Println(f2("test"))
  37. fmt.Println(f2("pic"))
  38. */
  39. }

4.2闭包

1. 闭包:一个函数和与其相关的引用环境组合而成的实体

package main

import “fmt”

func main() {

var f = Adder()

fmt.Print(f(1),” - “)

fmt.Print(f(20),” - “)

fmt.Print(f(300))

}

func Adder() func(int) int {

var x int

return func(delta int) int {

x += delta

return x

}

}

4.2闭包 例子

package main

import (

"fmt"

"strings"

)

func makeSuffixFunc(suffix string) func(string) string {

return func(name string) string {

if !strings.HasSuffix(name, suffix) {

return name + suffix

}

return name

}

}

func main() {

func1 := makeSuffixFunc(".bmp")

func2 := makeSuffixFunc(".jpg")

fmt.Println(func1("test"))

fmt.Println(func2("test"))

}

4.3数组与切片

1. 数组:是同一种数据类型的固定长度的序列。

2. 数组定义:var a [len]int,比如:var a[5]int 一旦定义,长度不能变

3. 长度是数组类型的一部分,因此,var a[5] int和var a[10]int是不同的类型

4. 数组可以通过下标进行访问,下标是从0开始,最后一个元素下标是:len-1

for i := 0; i < len(a); i++ {

}

5. 访问越界,如果下标在数组合法范围之外,则触发访问越界,会panic

6. 数组是值类型,因此改变副本的值,不会改变本身的值

arr2 := arr1

arr2[2] = 100

  1. package main
  2. import "fmt"
  3. func test1() {
  4. var a [10]int
  5. //j := 10
  6. a[0] = 100
  7. //a[j] = 200
  8. fmt.Println(a)
  9. for i := 0; i < len(a); i++ {
  10. fmt.Println(a[i])
  11. }
  12. for index, val := range a {
  13. fmt.Printf("a[%d]=%d\n", index, val)
  14. }
  15. }
  16. func test3(arr *[5]int) {
  17. (*arr)[0] = 1000
  18. }
  19. func test2() {
  20. var a [10]int
  21. b := a
  22. b[0] = 100
  23. fmt.Println(a)
  24. }
  25. func main() {
  26. //test1()
  27. test2()
  28. var a [5]int
  29. test3(&a)
  30. fmt.Println(a)
  31. }

4.3数组与切片-案例

package main

import (

"fmt"

)

func modify(arr [5]int) {

arr[0] = 100

return

}

func main() {

var a [5]int //数组大小是固定的

modify(a)

for i := 0; i < len(a); i++ {

fmt.Println(a[i])

}

}

package main

import (

"fmt"

)

func modify(arr *[5]int) {

(*arr)[0] = 100

return

}

func main() {

var a [5]int

modify(&a)

for i := 0; i < len(a); i++ {

fmt.Println(a[i])

}

}

4.3 数组与切片-数组

1. 数组初始化 对于数组 []里面肯定要有东西

b. var age1 = [5]int{1,2,3,4,5}

c. var age2 = […]int{1,2,3,4,5,6}

a. var age0 [5]int = [5]int{1,2,3}

d. var str = [5]string{3:”hello world”, 4:”tom”}

2. 多维数组

a. var age [5][3]int

b. var f [2][3]int = [...][3]int{{1, 2, 3}, {7, 8, 9}}

3. 多维数组遍历

package main

import (

"fmt"

)

func main() {

var f [2][3]int = [...][3]int{{1, 2, 3}, {7, 8, 9}}

for k1, v1 := range f {

for k2, v2 := range v1 {

fmt.Printf("(%d,%d)=%d ", k1, k2, v2)

}

fmt.Println()

}

}

4.3 数组与切片-切片定义

1. 切片:切片是数组的一个引用,因此切片是引用类型

2. 切片的长度可以改变,因此,切片是一个可变的数组

3. 切片遍历方式和数组一样,可以用len()求长度

4. cap可以求出slice最大的容量,0 <= len(slice) <= (array),其中array是slice引用的数组

5. 切片的定义:var 变量名 []类型,比如 var str []string var arr []int

4.3 数组与切片-切片初始化

1. 切片初始化:var slice []int = arr[start:end]

包含start到end之间的元素,但不包含end

2. Var slice []int = arr[0:end]可以简写为 var slice []int=arr[:end]

3. Var slice []int = arr[start:len(arr)] 可以简写为 var slice[]int = arr[start:]

4. Var slice []int = arr[0, len(arr)] 可以简写为 var slice[]int = arr[:]

5. 如果要切片最后一个元素去掉,可以这么写:

Slice = slice[:len(slice)-1]

  1. package main
  2. import "fmt"
  3. type slice struct {
  4. ptr *[100]int
  5. len int
  6. cap int
  7. }
  8. func make1(s slice, cap int) slice {
  9. s.ptr = new([100]int)
  10. s.cap = cap
  11. s.len = 0
  12. return s
  13. }
  14. func modify(s slice) {
  15. s.ptr[1] = 1000
  16. }
  17. func testSlice2() {
  18. var s1 slice
  19. s1 = make1(s1, 10)
  20. s1.ptr[0] = 100
  21. modify(s1)
  22. fmt.Println(s1.ptr)
  23. }
  24. func testSlice() {
  25. var slice []int
  26. var arr [5]int = [...]int{1, 2, 3, 4, 5}
  27. slice = arr[:]
  28. slice = slice[1:]
  29. slice = slice[:len(slice)-1]
  30. fmt.Println(slice)
  31. fmt.Println(len(slice))
  32. fmt.Println(cap(slice))
  33. slice = slice[0:1]
  34. fmt.Println(len(slice))
  35. fmt.Println(cap(slice))
  36. }
  37. func modify1(a []int) {
  38. a[1] = 1000
  39. }
  40. func testSlice3() {
  41. var b []int = []int{1, 2, 3, 4}
  42. modify1(b)
  43. fmt.Println(b)
  44. }
  45. func testSlice4() {
  46. var a = [10]int{1, 2, 3, 4}
  47. b := a[1:5]
  48. fmt.Printf("%p\n", b)
  49. fmt.Printf("%p\n", &a[1])
  50. }
  51. func main() {
  52. //testSlice()
  53. //testSlice2()
  54. //testSlice3()
  55. testSlice4()
  56. }

4.3 数组与切片-切片实战1

1. 练习:写一个程序,演示切片的各个用法

代码:4-3-slice1.go

  1. package main
  2. import "fmt"
  3. func testSlice() {
  4. var a [5]int = [...]int{1, 2, 3, 4, 5}
  5. s := a[1:]
  6. fmt.Printf("before len[%d] cap[%d]\n", len(s), cap(s))
  7. s[1] = 100
  8. fmt.Printf("s=%p a[1]=%p\n", s, &a[1])
  9. fmt.Println("before a:", a)
  10. s = append(s, 10)
  11. s = append(s, 10)
  12. fmt.Printf("after len[%d] cap[%d]\n", len(s), cap(s))
  13. s = append(s, 10)
  14. s = append(s, 10)
  15. s = append(s, 10)
  16. s[1] = 1000
  17. fmt.Println("after a:", a)
  18. fmt.Println(s)
  19. fmt.Printf("s=%p a[1]=%p\n", s, &a[1])
  20. }
  21. func testCopy() {
  22. var a []int = []int{1, 2, 3, 4, 5, 6}
  23. b := make([]int, 1)
  24. copy(b, a)
  25. fmt.Printf("copy len[%d] cap[%d]\n", len(b), cap(b))
  26. fmt.Println(b)
  27. }
  28. func testString() {
  29. s := "hello world"
  30. s1 := s[0:5]
  31. s2 := s[6:]
  32. fmt.Println(s1)
  33. fmt.Println(s2)
  34. }
  35. func testModifyString() {
  36. s := "我hello world"
  37. s1 := []rune(s)
  38. s1[0] = 200
  39. s1[1] = 128
  40. s1[2] = 64
  41. str := string(s1)
  42. fmt.Println(str)
  43. }
  44. func main() {
  45. //testSlice()
  46. //testCopy()
  47. //testString()
  48. testModifyString()
  49. }

4.3 数组与切片-切片实战2

2. 切片的内存布局,类似C++ vector:

2. 练习,写一个程序,演示切片的内存布局

4.3 数组与切片-切片实战3

3. 通过make来创建切片

var slice []type = make([]type, len)

slice := make([]type, len)

slice := make([]type, len, cap)

4.3 数组与切片-切片实战4

4. 用append内置函数操作切片

slice = append(slice, 10)

var a = []int{1,2,3}

var b = []int{4,5,6}

a = append(a, b…)

5. For range 遍历切片

for index, val := range slice {

}

6. 切片resize

var a = []int {1,3,4,5}

b := a[1:2]

b = b[0:3]

7. 切片拷贝

s1 := []int{1,2,3,4,5}

s2 := make([]int, 10)

copy(s2, s1)

s3 := []int{1,2,3}

s3 = append(s3, s2…)

s3 = append(s3, 4,5,6)

4-3-slice-make.go

4.3 数组与切片-切片实战5

8. string与slice

string底层就是一个byte的数组,因此,也

可以进行切片操作

str := “hello world”

s1 := str[0:5]

fmt.Println(s1)

s2 := str[5:]

fmt.Println(s2)

9. string 的底层布局

4.3 数组与切片-切片实战6

10. 如何改变string中的字符值?

string本身是不可变的,因此要改变string中字符,需要如下操作:

str := “hello world”

s := []byte(str)

s[0] = ‘o’

str = string(s)

4.4 数组与切片的区别1

它们的定义:

 数组:类型 [n]T 表示拥有 n 个 T 类型的值的数组。

 切片:类型 []T 表示一个元素类型为 T 的切片。

数组的例子

var x[3]int = [3]int{1,2,3}

var y[3]int = x

fmt.Println(x,y)

y[0]=999

fmt.Println(x,y)

切片的例子

var x[]int = []int{1,2,3}

var y[]int = x

fmt.Println(x,y)

y[0]=999

fmt.Println(x,y)

4.4 数组与切片的区别2

它们的定义:

 数组:类型 [n]T 表示拥有 n 个 T 类型的值的数组。

 切片:类型 []T 表示一个元素类型为 T 的切片。

数组是需要指定个数的,而切片则不需要。数组赋值也可是使用如下方式,忽略元素个数,使用

“...”代替

x:= [...]int{1,2,3}
y := x
fmt.Println(x,y)
y[0]=999
fmt.Println(x,y)

4.5 new和make的区别

new

func main() {

var i *int

i=new(int)

*i=10

fmt.Println(*i)

}

 make

func make(t Type, size ...IntegerType) Type func new(Type) *Type

make也是用于内存分配的,但是和new不同,它只用于

chan、map以及切片的内存创建,而且它返回的类型就是这

三个类型本身,而不是他们的指针类型,因为这三种类型

就是引用类型,所以就没有必要返回他们的指针了。

5. Go test方法

5 Go test

前置条件:

1、文件名须以"_test.go"结尾

2、方法名须以"Test"打头,并且形参为 (t *testing.T)

5 Go test 举例

举例:gotest.go

package mytest

import (

"errors"

)

func Division(a, b float64) (float64, error) {

if b == 0 {

return 0, errors.New("除数不能为0")

}

return a / b, nil

}

gotest_test.go

package mytest

import (

"testing"

)

func Test_Division_1(t *testing.T) {

if i, e := Division(6, 2); i != 3 || e != nil { //try a unit test on function

t.Error("除法函数测试没通过") // 如果不是如预期的那么就报错

} else {

t.Log("第一个测试通过了") //记录一些你期望记录的信息

}

}

func Test_Division_2(t *testing.T) {

if _, e := Division(6, 0); e == nil { //try a unit test on function

t.Error("Division did not work as expected.") // 如果不是如预期的那么就

报错

} else {

t.Log("one test passed.", e) //记录一些你期望记录的信息

}

}

5 Go test 测试

1. 在目录下执行 go test 是测试目录所有以XXX_test.go 结尾的文件。

2.测试单个方法

go test -v -run="Test_Division_1" -count 5

3.查看帮助 go help test

  1. package mytest
  2. import (
  3. "testing"
  4. )
  5. func Test_Division_1(t *testing.T) {
  6. if i, e := Division(6, 2); i != 3 || e != nil { //try a unit test on function
  7. t.Error("除法函数测试没通过") // 如果不是如预期的那么就报错
  8. } else {
  9. t.Log("第一个测试通过了") //记录一些你期望记录的信息
  10. }
  11. }
  12. func Test_Division_2(t *testing.T) {
  13. if _, e := Division(6, 0); e == nil { //try a unit test on function
  14. t.Error("Division did not work as expected.") // 如果不是如预期的那么就报错
  15. } else {
  16. t.Log("one test passed.", e) //记录一些你期望记录的信息
  17. }
  18. }

5 Go test 命令介绍1

通过go help test可以看到go test的使用说明:

格式形如:

go test [-c] [-i] [build flags] [packages] [flags for test binary]

参数解读:

-c : 编译go test成为可执行的二进制文件,但是不运行测试。

-i : 安装测试包依赖的package,但是不运行测试。

关于build flags,调用go help build,这些是编译运行过程中需要使用到的参数,一般设置为空

关于packages,调用go help packages,这些是关于包的管理,一般设置为空

关于flags for test binary,调用go help testflag,这些是go test过程中经常使用到的参数

-test.v : 是否输出全部的单元测试用例(不管成功或者失败),默认没有加上,所以只输出失败的单元测试用例。

-test.run pattern: 只跑哪些单元测试用例

-test.bench patten: 只跑那些性能测试用例

-test.benchmem : 是否在性能测试的时候输出内存情况

-test.benchtime t : 性能测试运行的时间,默认是1s

-test.cpuprofile cpu.out : 是否输出cpu性能分析文件

-test.memprofile mem.out : 是否输出内存性能分析文件

5 Go test 命令介绍2-续

-test.blockprofile block.out : 是否输出内部goroutine阻塞的性能分析文件

-test.memprofilerate n : 内存性能分析的时候有一个分配了多少的时候才打点记录的问题。这个参数就是设置打

点的内存分配间隔,也就是profile中一个sample代表的内存大小。默认是设置为512 * 1024的。如果你将它设置

为1,则每分配一个内存块就会在profile中有个打点,那么生成的profile的sample就会非常多。如果你设置为0,

那就是不做打点了。

你可以通过设置memprofilerate=1和GOGC=off来关闭内存回收,并且对每个内存块的分配进行观察。

-test.blockprofilerate n: 基本同上,控制的是goroutine阻塞时候打点的纳秒数。默认不设置就相当于-

test.blockprofilerate=1,每一纳秒都打点记录一下

-test.parallel n : 性能测试的程序并行cpu数,默认等于GOMAXPROCS。

-test.timeout t : 如果测试用例运行时间超过t,则抛出panic

-test.cpu 1,2,4 : 程序运行在哪些CPU上面,使用二进制的1所在位代表,和nginx的nginx_worker_cpu_affinity是一个道理

-test.short : 将那些运行时间较长的测试用例运行时间缩短

1.2 Go语言接口与反射

Go语言接口与反射

1. 结构

2. 接口

3. 反射

1. 结构

1.1 struct简介

◼ Go通过结构体struct和interface实现oop(面向对象编程)

◼ struct的成员(也叫属性或字段)可以是任何类型,如普通类型、复合类型、函数、map、interface、

struct等

1.2 struct详解-struct定义

1.2 struct详解-声明与初始化

声明与初始化

var stu1 Student

var stu2 *Student= &Student{} //简写stu2 := &Student{}

var stu3 *Student = new(Student) //简写stu3 := new(Student)

1.2 struct详解-struct使用

访问其成员都使用 ".“
struct 分配内存使用 new ,返回的是指针
struct 没有构造函数,但是我们可以自己定义“构造函数”
struct 是我们自己定义的类型,不能和其他类型进行强制转换

type Student struct {

    name string

    age int

    Class string

    }

  1. var stu1 Student
  2. stu1.age = 34
  3. stu1.name = "dar"
  4. stu1.Class = "class1"
  5. fmt.Println(stu1.name) //dar
  6. var stu2 *Student = new(Student)
  7. stu2.name = "ki"
  8. stu2.age = 33
  9. fmt.Println(stu2.name, (*stu2).name) //ki
  10. var stu3 *Student = &Student{name: "rose", age: 18, Class: "class3"}
  11. fmt.Println(stu3.name, (*stu3).name) //rose rose
  1. package main
  2. import (
  3. "fmt"
  4. "unsafe"
  5. )
  6. type Student struct {
  7. name string
  8. age int32 // 小写 私密 只能在自己的包里面用
  9. Class string // 大写 公开 类似C++ public
  10. }
  11. func main() {
  12. // 1 值形式
  13. var stu1 Student // 里面的变量全是零 栈上的
  14. fmt.Println("stu1:", stu1)
  15. stu1.age = 34
  16. stu1.name = "dar"
  17. stu1.Class = "class1"
  18. fmt.Println(stu1.name) //dar
  19. // 2 new 函数创建
  20. var stu2 *Student = new(Student) // new出来的是堆上
  21. stu2.name = "king"
  22. stu2.age = 33
  23. fmt.Println(stu2.name, (*stu2).name) //king
  24. // &形式创建
  25. var stu3 *Student = &Student{
  26. name: "rose",
  27. age: 18,
  28. Class: "class3", // 如果分行的时候每行都要,
  29. }
  30. // var stu3 *Student = &Student{name: "rose", age: 18, Class: "class3"}
  31. fmt.Println(stu3.name, (*stu3).name) //rose  rose
  32. fmt.Printf("addr: %p, %p, %p\n", &stu1, stu2, stu3)
  33. // 值 初始化
  34. var stu4 Student = Student{ // KV 形式初始化值
  35. name: "老师",
  36. age: 18,
  37. Class: "Go", // 注意这里的逗号不能少
  38. }
  39. fmt.Println("stu4:", stu4) // stu4: {柚子老师 18 }
  40. // 值顺序初始化
  41. var stu5 Student = Student{ // 顺序形式 形式初始化值
  42. "1",
  43. 18,
  44. "音视频", // 注意这里的逗号不能少
  45. }
  46. fmt.Println("stu5:", stu5)
  47. // nil结构体
  48. var stu6 *Student = nil
  49. fmt.Println("stu6:", stu6)
  50. // 结构体大小
  51. fmt.Println("unsafe.Sizeof(stu5):", unsafe.Sizeof(stu5))
  52. fmt.Println("unsafe.Sizeof(stu6):", unsafe.Sizeof(stu6))
  53. // fmt.Println("unsafe.Sizeof(string):", unsafe.Sizeof(string))
  54. // fmt.Println("unsafe.Sizeof(int):", unsafe.Sizeof(int))
  55. }

1.2 struct详解-自定义构造函数

◼ 通过工厂模式自定义构造函数方法

func Newstu(name1 string,age1 int,class1 string) *Student {

    return &Student{name:name1,age:age1,Class:class1}

    }

    func main() {

    stu1 := Newstu(“dar",34,"math")

    fmt.Println(stu1.name) // dar

    }

  1. package main
  2. import "fmt"
  3. type Student struct {
  4. name string
  5. age int
  6. Class string
  7. }
  8. func Newstu(name1 string, age1 int, class1 string) *Student {
  9. return &Student{name: name1, age: age1, Class: class1}
  10. }
  11. func main() {
  12. stu1 := Newstu("dar", 34, "math")
  13. fmt.Println(stu1.name) // dar
  14. }

1.3 struct tag

◼ tag可以为结构体的成员添加说明或者标签便于使用,这些说明可以通过反射获取到。

◼ 结构体中的成员首字母小写对外不可见,但是我们把成员定义为首字母大写这样与外界进行数据

交互会带来极大的不便,此时tag带来了解决方法

type Student struct {

    Name string "the name of student"

    Age int "the age of student"

    Class string "the class of student"

   }

1.3 struct tag –应用场景json示例

应用场景示例,json序列化操作(序列化和反序列化演示)

  1. type Student struct {
  2. Name string `json:"name"`
  3. Age int `json:"age"`
  4. }
  5. func main() {
  6. var stu = Student{Name: "dar", Age: 34}
  7. data, err := json.Marshal(stu)
  8. if err != nil {
  9. fmt.Println("json encode failed err:", err)
  10. return
  11. }
  12. fmt.Println(string(data)) //{"name":"dar","age":34}
  13. }
  1. package main
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. )
  6. // stu: 序列化后:{"name":"dar","age":34}
  7. // {"name1":"dar","age2":34}
  8. type Student struct {
  9. Name string `json:"name1"`
  10. Age int `json:"age2"`
  11. }
  12. func main() {
  13. var stu = Student{Name: "dar", Age: 34}
  14. data, err := json.Marshal(stu) // {"name1":"dar","age2":34}
  15. if err != nil {
  16. fmt.Println("json encode failed err:", err)
  17. return
  18. }
  19. fmt.Println("stu: ", string(data)) //{"name":"dar","age":34}
  20. var stu2 Student
  21. err = json.Unmarshal(data, &stu2) // 反序列化
  22. fmt.Println("stu2: ", stu2) // {dar 34}
  23. }

1.4 struct匿名成员(字段、属性)

◼ 结构体中,每个成员不一定都有名称,也允许字段没有名字,即匿名成员。

◼ 匿名成员的一个重要作用,可以用来实现oop中的继承。

◼ 同一种类型匿名成员只允许最多存在一个。

◼ 当匿名成员是结构体时,且两个结构体中都存在相同字段时,优先选择最近的字段。

type Person struct {

    Name string

    Age int

    }

    type Student struct {

    score string

    Age int

    Person // 匿名内嵌结构体

    }

  1. func main() {
  2. var stu = new(Student)
  3. stu.Age = 34 //优先选择Student中的Age
  4. fmt.Println(stu.Person.Age, stu.Age) // 0,34
  5. }
  1. // 1.4 struct匿名成员(字段、属性)
  2. package main
  3. import "fmt"
  4. type Person struct {
  5. Name string
  6. Age int
  7. }
  8. type Student struct {
  9. score string
  10. Age int
  11. Person // 匿名内嵌结构体
  12. }
  13. func main() {
  14. var stu = new(Student)
  15. stu.Age = 22 //优先选择Student中的Age
  16. fmt.Println(stu.Person.Age, stu.Age) // 0,22
  17. var stu2 = Student{
  18. score: "100",
  19. Age: 20,
  20. Person: Person{
  21. Name: "柚子老师",
  22. Age: 18,
  23. },
  24. }
  25. fmt.Println("stu2: ", stu2)
  26. }

1.5 struct-继承、多继承

◼ 当结构体中的成员也是结构体时,该结构体就继承了这个结构体,继承了其所有的方法与属性,当然有多个

结构体成员也就是多继承。

◼ 访问父结构中属性也使用“.”,但是当子结构体中存在和父结构中的字段相同时候,只能使用:"子结构体.父

结构体.字段"访问父结构体中的属性,如上面示例的stu.Person.Age

◼ 继承结构体可以使用别名,访问的时候通过别名访问,如下面示例man1.job.Salary:

type Person struct {

    Name string

    Age int

    }

    type Teacher struct {

    Salary int

    Classes string

   }

  1. type man struct {
  2. sex string
  3. job Teacher //别名,继承Teacher 这个时候就不是匿名了
  4. Person //继承Person
  5. }
  6. func main() {
  7. var man1 = new(man)
  8. man1.Age = 34
  9. man1.Name = "dar"
  10. man1.job.Salary = 100000
  11. fmt.Println(man1, man1.job.Salary) //&{ {8500 } {dar 34}} 8500
  12. }
  1. // 1.5 struct-继承、多继承
  2. package main
  3. import "fmt"
  4. type Person struct {
  5. Name string
  6. Age int
  7. }
  8. type Teacher struct {
  9. Salary int
  10. Class string
  11. }
  12. type Man struct {
  13. sex string
  14. job Teacher //别名,继承Teacher
  15. Person //继承Person
  16. }
  17. func main() {
  18. var man1 = new(Man)
  19. man1.Age = 34
  20. man1.Name = "dar"
  21. man1.job.Salary = 100000
  22. fmt.Println("man1:", man1, man1.job.Salary) //&{ {8500 } {dar 34}} 8500
  23. var man2 = Man{
  24. sex: "女",
  25. job: Teacher{
  26. Salary: 8000,
  27. Class: "班班",
  28. },
  29. Person: Person{ // 匿名初始化方式
  30. Name: "老师",
  31. Age: 18,
  32. },
  33. }
  34. fmt.Println("man2", man2)
  35. }

1.6 struct-结构体中的方法

方法是什么

◼ Go方法是作用在接受者(个人理解成作用对象)上的一个函数,接受者是某种类型的变量,因此方法是一种特殊

类型的函数。

◼ 接收者可以是任何类型,不仅仅是结构体,Go中的基本类型(int,string,bool等)也是可以,或者说数组的别名

类型,甚至可以是函数类型。但是,接受者不能是一个接口类型,因为接口是一个抽象的定义,方法是一个具体

实现。

◼ 一个类型加上它的方法等价于面向对象中的一个类。一个重要的区别是:在 Go 中,类型的代码和绑定在它上面的

方法的代码可以不放置在一起,它们可以存在在不同的源文件,唯一的要求是:它们必须是同一个包的。

◼ 类型 T(或 *T)上的所有方法的集合叫做类型 T(或 *T)的方法集。

◼ 因为方法是函数,所以同样的,不允许方法重载,即对于一个类型只能有一个给定名称的方法。但是如果基于接

收者类型,是有重载的:具有同样名字的方法可以在 2 个或多个不同的接收者类型上存在,比如在同一个包里这么

做是允许的

◼ 别名类型不能有它原始类型上已经定义过的方法(因为别名类型和原始类型底层是一样的)。

定义方法的格式

func(recv recevier_type(结构体))methodName(parameter_list)(return_value_list){}

1.6 struct-结构体中的方法-示例

type Person struct {

    Name string

    Age int

    }

    func (p Person) Getname() string { //p代表结构体本身的实列,类似python中的self,这里p可以写为self

    fmt.Println(p.Name)

    return p.Name

    }

    func main() {

    var person1 = new(Person)

    person1.Age = 34

    person1.Name = "dar"

    person1.Getname() // dar

    }

结构体的指针方法

  1. package main
  2. import (
  3. "fmt"
  4. "math"
  5. )
  6. type Circle struct {
  7. x int
  8. y int
  9. Radius int
  10. }
  11. // 面积
  12. func (c Circle) Area() float64 {
  13. return math.Pi * float64(c.Radius) * float64(c.Radius)
  14. }
  15. // 周长
  16. func (c Circle) Circumference() float64 {
  17. return 2 * math.Pi * float64(c.Radius)
  18. }
  19. func (c Circle) expand() {
  20. c.Radius *= 2
  21. }
  22. func (c *Circle) expand2() {
  23. c.Radius *= 2
  24. }
  25. func main() {
  26. var c = Circle{Radius: 50}
  27. fmt.Println(c.Area(), c.Circumference())
  28. // 指针变量调用方法形式上是一样的
  29. var pc = &c
  30. pc.expand2()
  31. fmt.Println(pc.Area(), pc.Circumference())
  32. }
  1. type Person struct {
  2. Name string
  3. Age int
  4. }
  5. func (p Person) Getname() string { //p代表结构体本身的实列,类似python中的self,这里p可以写为self
  6. fmt.Println(p.Name)
  7. return p.Name
  8. }
  9. func main() {
  10. var person1 = new(Person)
  11. person1.Age = 34
  12. person1.Name = "dar"
  13. person1.Getname() // dar
  14. }

1.6 struct-结构体中的方法-方法和函数的区别

◼ 方法只能被其接受者调用

◼ 接收者是指针时,方法可以改变接受者的值(或状态),函数也能做到

◼ 接受者和方法必须在同一个包内

1.6 struct-结构体中的方法-方法的接受者是值或者指针的区别

◼ 当接受者是一个值的时候,这个值是该类型实例的拷贝

◼ 如果想要方法改变接受者的数据,就在接受者的指针类型上定义该方法。否则,就在普

通的值类型上定义方法。

总结:指针方法和值方法都可以在指针或者非指针上被调用。也就是说,方法接收者是

指针类型时,指针类型的值也是调用这个方法,反之亦然。

1.7 struct-内存分布

go中的结构体内存布局和c结构体布局类似,每个成员的内存分布是连续的

  1. type Student struct {
  2. Name string
  3. Age int64
  4. wight int64
  5. high int64
  6. score int64
  7. }

  1. package main
  2. import (
  3. "fmt"
  4. "reflect"
  5. )
  6. // type Student struct {
  7. // Name string // 16
  8. // Age int64 // 8
  9. // wight int64 // 8
  10. // high int64 // 8
  11. // score int64 // 8
  12. // }
  13. type Student struct {
  14. Name string // 16 有两个变量: 指针, length
  15. Age int8 //
  16. wight int64 // 8
  17. high int8 // 8
  18. score int64 // 8
  19. }
  20. func main() {
  21. var stu1 = new(Student)
  22. stu1.Name = "只为你"
  23. fmt.Printf("地址分布:")
  24. fmt.Printf("%p\n", &stu1.Name)
  25. fmt.Printf("%p\n", &stu1.Age)
  26. fmt.Printf("%p\n", &stu1.wight)
  27. fmt.Printf("%p\n", &stu1.high)
  28. fmt.Printf("%p\n", &stu1.score)
  29. typ := reflect.TypeOf(Student{})
  30. fmt.Printf("Struct is %d bytes long\n", typ.Size())
  31. // We can run through the fields in the structure in order
  32. n := typ.NumField()
  33. for i := 0; i < n; i++ {
  34. field := typ.Field(i) // 反射出filed
  35. fmt.Printf("%s at offset %v, size=%d, align=%d\n",
  36. field.Name, field.Offset, field.Type.Size(),
  37. field.Type.Align())
  38. }
  39. }

2. 接口

2.1 interface简介

interface(接口)是golang最重要的特性之一,Interface类型可以定义一组方法,但是这些不需要实

现。并且interface不能包含任何变量。

◼ interface 是方法的集合

◼ interface是一种类型,并且是指针类型

◼ interface的 更重要的作用在于多态实现

◼ interface 不能包含任何变量

2.2 interface定义

type 接口名称 interface {

    method1 (参数列表) 返回值列表

    method2 (参数列表) 返回值列表

    ...

    }

2.3 interface使用

◼ 接口的使用不仅仅针对结构体,自定义类型、变量等等都可以实现接口。

◼ 如果一个接口没有任何方法,我们称为空接口,由于空接口没有方法,任意结构体都

隐式地实现了空接口。

◼ 要实现一个接口,必须实现该接口里面的所有方法。

  1. // 2.3 interface使用
  2. package main
  3. import "fmt"
  4. //定义接口
  5. type Skills interface {
  6. Running()
  7. Getname() string
  8. }
  9. type Student struct {
  10. Name string
  11. Age int
  12. }
  13. type Teacher struct {
  14. Name string
  15. Age int
  16. }
  17. // 实现接口
  18. func (p Student) Getname() string { //实现Getname方法
  19. fmt.Println(p.Name)
  20. return p.Name
  21. }
  22. func (p Student) Running() { // 实现 Running方法
  23. fmt.Printf("%s running\n", p.Name)
  24. }
  25. func (p Teacher) Getname() string { //实现Getname方法
  26. fmt.Println(p.Name)
  27. return p.Name
  28. }
  29. func (p Teacher) Running() { // 实现 Running方法
  30. fmt.Printf("%s running\n", p.Name)
  31. }
  32. func (p Teacher) Running2() { // 实现 Running方法
  33. fmt.Printf("%s running\n", p.Name)
  34. }
  35. // 想用接口,那就要实现对应接口的所有方法
  36. func main() {
  37. var skill Skills // 一个接口变量
  38. var stu1 Student // 结构体变量
  39. stu1.Name = "dar"
  40. stu1.Age = 34
  41. skill = stu1
  42. skill.Running() //调用接口
  43. var teacher Teacher = Teacher{"老师", 18}
  44. skill = teacher
  45. skill.Running() //调用接口
  46. teacher.Running()
  47. }
  1. // 空接口
  2. package main
  3. import "fmt"
  4. // Go 语言为了避免用户重复定义很多空接口,它自己内置了一个,这个空接口的名字特别奇怪,叫 interface{}
  5. /*
  6. 空接口里面没有方法,所以它也不具有任何能力,其作用相当于 Java 的 Object 类型,可以容纳任意对象,
  7. 它是一个万能容器。比如一个字典的 key 是字符串,但是希望 value 可以容纳任意类型的对象,
  8. 类似于 Java 语言的 Map 类型,这时候就可以使用空接口类型 interface{}。*/
  9. // 空接口 map 里面用
  10. func main() {
  11. // map k-v 一个map里面是有key都是同一类型,value也是同一类型
  12. // map[string]int k-string v-int
  13. // 连续两个大括号,是不是看起来很别扭
  14. // 代码中 user 字典变量的类型是 map[string]interface{},
  15. // 从这个字典中直接读取得到的 value 类型是 interface{},需要通过类型转换才能得到期望的变量。
  16. var user = map[string]interface{}{
  17. "age": 30,
  18. "address": "Beijing",
  19. "married": true,
  20. }
  21. fmt.Println(user)
  22. // 类型转换语法来了
  23. var age = user["age"].(int)
  24. var address = user["address"].(string)
  25. var married = user["married"].(bool)
  26. fmt.Println(age, address, married)
  27. user["price"] = 5.5
  28. var price = user["price"].(float64) // ?报错?
  29. fmt.Println("user: ", user, price)
  30. fmt.Println("user2: ")
  31. var user2 = map[interface{}]interface{}{
  32. 111: 30,
  33. "address2": "Beijing",
  34. 1.2: true,
  35. }
  36. fmt.Println("user2: ", user2)
  37. }

2.4 interface多态

◼ go语言中interface是实现多态的一种形式,所谓多态,就是一种事物的多种形态

◼ 同一个interface,不同的类型实现,都可以进行调用,它们都按照统一接口进行操作

  1. // 2.4 interface多态
  2. package main
  3. import "fmt"
  4. type Skills interface {
  5. Running()
  6. Getname() string
  7. }
  8. type Student struct {
  9. Name string
  10. Age int
  11. }
  12. type Teacher struct {
  13. Name string
  14. Salary int
  15. }
  16. func (p Student) Getname() string { //实现Getname方法
  17. fmt.Println(p.Name)
  18. return p.Name
  19. }
  20. func (p Student) Running() { // 实现 Running方法
  21. fmt.Printf("%s running", p.Name)
  22. }
  23. func (p Teacher) Getname() string { //实现Getname方法
  24. fmt.Println(p.Name)
  25. return p.Name
  26. }
  27. func (p Teacher) Running() { // 实现 Running方法
  28. fmt.Printf("\n%s running", p.Name)
  29. }
  30. func main() {
  31. var skill Skills
  32. var stu1 Student
  33. var t1 Teacher
  34. t1.Name = "ki"
  35. stu1.Name = "dar"
  36. stu1.Age = 22
  37. skill = stu1
  38. skill.Running()
  39. skill = t1
  40. t1.Running()
  41. }

2.5 interface接口嵌套

◼ go语言中的接口可以嵌套,可以理解为继承,子接口拥有父接口的所有方法

◼ 如果使用该子接口,必须将父接口和子接口的所有方法都实现

  1. type Skills interface {
  2. Running()
  3. Getname() string
  4. }
  5. type Test interface {
  6. sleeping()
  7. Skills //继承Skills
  8. }
  1. // 2.5 interface接口嵌套
  2. package main
  3. import "fmt"
  4. type Skills interface {
  5. Running()
  6. // Running(is int) // 函数名是唯一的
  7. Getname() string
  8. }
  9. type Test interface {
  10. Sleeping()
  11. Skills //继承Skills
  12. }
  13. type Student struct {
  14. Name string
  15. Age int
  16. }
  17. type Teacher struct {
  18. skill Skills // skill也只能当变量去用
  19. Name string
  20. Salary int
  21. }
  22. func (p Student) Getname() string { //实现Getname方法
  23. fmt.Println(p.Name)
  24. return p.Name
  25. }
  26. func (p Student) Running() { // 实现 Running方法
  27. fmt.Printf("%s running", p.Name)
  28. }
  29. func (p Teacher) Getname() string { //实现Getname方法
  30. fmt.Println(p.Name)
  31. return p.Name
  32. }
  33. func (p Teacher) Running() { // 实现 Running方法
  34. fmt.Printf("\n%s running", p.Name)
  35. }
  36. func (p Teacher) Sleeping() { // 实现 Running方法
  37. fmt.Printf("\n%s Sleeping", p.Name)
  38. }
  39. func main() {
  40. var skill Skills
  41. var stu1 Student
  42. var t1 Teacher
  43. t1.Name = "ki"
  44. stu1.Name = "dar"
  45. stu1.Age = 22
  46. skill = stu1
  47. skill.Running()
  48. skill = t1
  49. t1.Running()
  50. var test Test
  51. test = t1
  52. test.Sleeping()
  53. // test = stu1
  54. }

2.6 interface接口组合

◼ 接口的定义也支持组合继承

  1. // 2.6 接口的组合继承
  2. package main
  3. import "fmt"
  4. // 可以闻
  5. type Smellable interface {
  6. smell()
  7. }
  8. // 可以吃
  9. type Eatable interface {
  10. eat()
  11. }
  12. type Fruitable interface {
  13. Smellable
  14. Eatable
  15. }
  16. // 苹果既可能闻又能吃
  17. type Apple struct{}
  18. func (a Apple) smell() {
  19. fmt.Println("apple can smell")
  20. }
  21. func (a Apple) eat() {
  22. fmt.Println("apple can eat")
  23. }
  24. // 花只可以闻
  25. type Flower struct{}
  26. func (f Flower) smell() {
  27. fmt.Println("flower can smell")
  28. }
  29. // func TestType(items ...interface{}) {
  30. // for k, v := range items {
  31. // switch v.(type) {
  32. // case string:
  33. // fmt.Printf("type is string, %d[%v]\n", k, v)
  34. // case bool:
  35. // fmt.Printf("type is bool, %d[%v]\n", k, v)
  36. // case int:
  37. // fmt.Printf("type is int, %d[%v]\n", k, v)
  38. // case float32, float64:
  39. // fmt.Printf("type is float, %d[%v]\n", k, v)
  40. // case Smellable:
  41. // fmt.Printf("type is Smellable, %d[%v]\n", k, v)
  42. // case *Smellable:
  43. // fmt.Printf("type is *Smellable, %d[%p]\n", k, v)
  44. // case Eatable:
  45. // fmt.Printf("type is Eatable, %d[%v]\n", k, v)
  46. // case *Eatable:
  47. // fmt.Printf("type is Eatable, %d[%p]\n", k, v)
  48. // case Fruitable:
  49. // fmt.Printf("type is Fruitable, %d[%v]\n", k, v)
  50. // case *Fruitable:
  51. // fmt.Printf("type is Fruitable, %d[%p]\n", k, v)
  52. // }
  53. // }
  54. // }
  55. func main() {
  56. var s1 Smellable
  57. var s2 Eatable
  58. var apple = Apple{}
  59. var flower = Flower{}
  60. s1 = apple
  61. s1.smell()
  62. s1 = flower
  63. s1.smell()
  64. s2 = apple
  65. s2.eat()
  66. fmt.Println("\n组合继承")
  67. var s3 Fruitable
  68. s3 = apple
  69. s3.smell()
  70. s3.eat()
  71. // TestType(s1, s2, s3, apple, flower)
  72. }

2.7 interface类型转换

由于接口是一般类型,当我们使用接口时候可能不知道它是那个类型实现的,

基本数据类型我们有对应的方法进行类型转换,当然接口类型也有类型转换。

var s int

var x interface

x = s

y , ok := x.(int) //将interface 转为int,ok可省略 但是省略以后转换失败会报错,

true转换成功,false转换失败, 并采用默认值

  1. // 2.7 interface类型转换
  2. package main
  3. import "fmt"
  4. func main() {
  5. var x interface{}
  6. s := "dar"
  7. x = s // 为什么能赋值到空接口, 每种类型都已经隐藏实现了空接口
  8. y, ok := x.(int)
  9. z, ok1 := x.(string)
  10. fmt.Println(y, ok)
  11. fmt.Println(z, ok1)
  12. }
  13. //0 false
  14. //dar true

2.8 interface类型判断

func TestType(items ...interface{}) {

for k, v := range items {

switch v.(type) {

case string:

fmt.Printf("type is string, %d[%v]\n", k, v)

case bool:

fmt.Printf("type is bool, %d[%v]\n", k, v)

case int:

fmt.Printf("type is int, %d[%v]\n", k, v)

case float32, float64:

fmt.Printf("type is float, %d[%v]\n", k, v)

case Student:

fmt.Printf("type is Student, %d[%v]\n", k, v)

case *Student:

fmt.Printf("type is Student, %d[%p]\n", k, v)

}

}

}

  1. package main
  2. import "fmt"
  3. type Student struct {
  4. Name string
  5. }
  6. func TestType(items ...interface{}) {
  7. for k, v := range items {
  8. switch v.(type) {
  9. case string:
  10. fmt.Printf("type is string, %d[%v]\n", k, v)
  11. case bool:
  12. fmt.Printf("type is bool, %d[%v]\n", k, v)
  13. case int:
  14. fmt.Printf("type is int, %d[%v]\n", k, v)
  15. case float32, float64:
  16. fmt.Printf("type is float, %d[%v]\n", k, v)
  17. case Student:
  18. fmt.Printf("type is Student, %d[%v]\n", k, v)
  19. case *Student:
  20. fmt.Printf("type is Student, %d[%p]\n", k, v)
  21. }
  22. }
  23. }
  24. func main() {
  25. var stu Student
  26. TestType("dar", 100, stu, 3.3)
  27. }
  28. //type is string, 0[dar]
  29. //type is int, 1[100]
  30. //type is Student, 2[{}]
  31. //type is float, 3[3.3]

2.9 指向指针的接口变量

3 reflect反射是什么,为什么需要反射

反射定义:在计算机科学中,反射是指计算机程序在运行时(Run time)可以访问、检测和修

改它本身状态或行为的一种能力。用比喻来说,反射就是程序在运行的时候能够“观察”并且修

改自己的行为。

GO 反射的意义:Go 语言的 ORM 库离不开它,Go 语言的 json 序列化库离不开它, fmt包字

符串格式化离不开它,Go 语言的运行时更是离不开它。

反射的目标:

1. 获取变量的类型信息,例如这个类型的名称、占用字节数、所有的方法列表、所有的内部字段结

构、它的底层存储类型等等。

2. 动态的修改变量内部字段值。比如 json 的反序列化,你有的是对象内部字段的名称和相应的值,

你需要把这些字段的值循环填充到对象相应的字段里

  1. package main
  2. import "fmt"
  3. type Rect struct {
  4. Width int
  5. Height int
  6. }
  7. func main() {
  8. var a interface{}
  9. var r = Rect{50, 50}
  10. a = &r // 指向了结构体指针
  11. var rx = a.(*Rect) // 转换成指针类型
  12. r.Width = 100
  13. r.Height = 100
  14. fmt.Println("r:", r)
  15. fmt.Println("rx:", rx)
  16. fmt.Printf("rx:%p, r:%p\n", rx, &r)
  17. }

3 reflect反射

◼ go语言中的反射通过refect包实现,reflect包实现了运行时反射,允许程序操作任意类型的对象

◼ reflect包中的两个关键数据类Type和Value

func TypeOf(v interface{}) Type // 返回类型 实际是接口

func ValueOf(v interface{}) Value // 返回值 结构体

  1. /*
  2. 但是有时你希望在运行时使用变量的在编写程序时还不存在的信息。
  3. 比如你正在尝试将文件或网络请求中的数据映射到变量中。或者你想构建一个适用于不同类型的工具。
  4. 在这种情况下,你需要使用反射。反射使您能够在运行时检查类型。
  5. 它还允许您在运行时检查,修改和创建变量,函数和结构体。
  6. */
  7. package main
  8. import (
  9. "fmt"
  10. "reflect"
  11. "strings"
  12. )
  13. type Foo struct {
  14. A int `tag1:"First Tag" tag2:"Second Tag"`
  15. B string
  16. }
  17. func main() {
  18. sl := []int{1, 2, 3}
  19. greeting := "hello"
  20. greetingPtr := &greeting
  21. f := Foo{A: 10, B: "Salutations"}
  22. fp := &f
  23. slType := reflect.TypeOf(sl)
  24. gType := reflect.TypeOf(greeting)
  25. grpType := reflect.TypeOf(greetingPtr)
  26. fType := reflect.TypeOf(f)
  27. fpType := reflect.TypeOf(fp)
  28. examiner(slType, 0)
  29. examiner(gType, 0)
  30. examiner(grpType, 0)
  31. examiner(fType, 0)
  32. examiner(fpType, 0)
  33. }
  34. func examiner(t reflect.Type, depth int) {
  35. fmt.Println(strings.Repeat("\t", depth), "Type is", t.Name(), "and kind is", t.Kind())
  36. switch t.Kind() {
  37. case reflect.Array, reflect.Chan, reflect.Map, reflect.Ptr, reflect.Slice:
  38. fmt.Println(strings.Repeat("\t", depth+1), "Contained type:")
  39. examiner(t.Elem(), depth+1)
  40. case reflect.Struct:
  41. for i := 0; i < t.NumField(); i++ {
  42. f := t.Field(i)
  43. fmt.Println(strings.Repeat("\t", depth+1), "Field", i+1, "name is", f.Name, "type is", f.Type.Name(), "and kind is", f.Type.Kind())
  44. if f.Tag != "" {
  45. fmt.Println(strings.Repeat("\t", depth+2), "Tag is", f.Tag)
  46. fmt.Println(strings.Repeat("\t", depth+2), "tag1 is", f.Tag.Get("tag1"), "tag2 is", f.Tag.Get("tag2"))
  47. }
  48. }
  49. }
  50. }

PS D:\Workspace\Go\src\projects\demo> go run main.go
 Type is  and kind is slice
         Contained type:
         Type is int and kind is int
 Type is string and kind is string
 Type is  and kind is ptr
         Contained type:
         Type is string and kind is string
 Type is Foo and kind is struct
         Field 1 name is A type is int and kind is int
                 Tag is tag1:"First Tag" tag2:"Second Tag"
                 tag1 is First Tag tag2 is Second Tag
         Field 2 name is B type is string and kind is string
 Type is  and kind is ptr
         Contained type:
         Type is Foo and kind is struct
                 Field 1 name is A type is int and kind is int
                         Tag is tag1:"First Tag" tag2:"Second Tag"
                         tag1 is First Tag tag2 is Second Tag
                 Field 2 name is B type is string and kind is string

  1. // 使用反射创建新实例
  2. /*
  3. 除了检查变量的类型外,还可以使用反射来读取,设置或创建值。
  4. 首先,需要使用refVal := reflect.ValueOf(var)为变量创建一个reflect.Value实例。
  5. 如果希望能够使用反射来修改值,则必须使用refPtrVal := reflect.ValueOf(&var);
  6. 获得指向变量的指针。如果不这样做,则可以使用反射来读取该值,但不能对其进行修改。
  7. 一旦有了reflect.Value实例就可以使用Type()方法获取变量的reflect.Type。
  8. 如果要修改值,请记住它必须是一个指针,并且必须首先对其进行解引用。
  9. 使用refPtrVal.Elem().Set(newRefVal)来修改值,并且传递给Set()的值也必须是reflect.Value。
  10. */
  11. package main
  12. import (
  13. "fmt"
  14. "reflect"
  15. )
  16. type Foo struct {
  17. A int
  18. B string
  19. }
  20. // 使用反射创建新实例
  21. func main() {
  22. greeting := "hello"
  23. f := Foo{A: 10, B: "Salutations"}
  24. gVal := reflect.ValueOf(greeting)
  25. // not a pointer so all we can do is read it
  26. fmt.Println(gVal.Interface()) // hello
  27. gpVal := reflect.ValueOf(&greeting)
  28. // it’s a pointer, so we can change it, and it changes the underlying variable
  29. gpVal.Elem().SetString("goodbye")
  30. fmt.Println(greeting) // 修改成了goodbye
  31. fType := reflect.TypeOf(f)
  32. fVal := reflect.New(fType)
  33. fVal.Elem().Field(0).SetInt(20)
  34. fVal.Elem().Field(1).SetString("Greetings")
  35. f2 := fVal.Elem().Interface().(Foo) // 调用Interface()方法从reflect.Value回到普通变量值
  36. fmt.Printf("f2: %+v, %d, %s\n", f2, f2.A, f2.B)
  37. fmt.Println("f2:", f2)
  38. fmt.Println("f:", f)
  39. }

PS D:\Workspace\Go\src\projects\demo> go run main.go
hello
goodbye
f2: {A:20 B:Greetings}, 20, Greetings
f2: {20 Greetings}
f: {10 Salutations}

  1. // 反射创建引用类型的实例
  2. /*
  3. 使用反射来生成通常需要make函数的实例。可以使用reflect.MakeSlice,
  4. reflect.MakeMap和reflect.MakeChan函数制作切片,Map或通道。
  5. 在所有情况下,都提供一个reflect.Type,然后获取一个reflect.Value,
  6. 可以使用反射对其进行操作,或者可以将其分配回一个标准变量。
  7. */
  8. package main
  9. import (
  10. "fmt"
  11. "reflect"
  12. )
  13. func main() {
  14. // 定义变量
  15. intSlice := make([]int, 0)
  16. mapStringInt := make(map[string]int)
  17. // 获取变量的 reflect.Type
  18. sliceType := reflect.TypeOf(intSlice)
  19. mapType := reflect.TypeOf(mapStringInt)
  20. // 使用反射创建类型的新实例
  21. intSliceReflect := reflect.MakeSlice(sliceType, 0, 0)
  22. mapReflect := reflect.MakeMap(mapType)
  23. // 将创建的新实例分配回一个标准变量
  24. v := 10
  25. rv := reflect.ValueOf(v)
  26. intSliceReflect = reflect.Append(intSliceReflect, rv)
  27. intSlice2 := intSliceReflect.Interface().([]int)
  28. fmt.Println("intSlice2: ", intSlice2)
  29. fmt.Println("intSlice : ", intSlice)
  30. k := "hello"
  31. rk := reflect.ValueOf(k)
  32. mapReflect.SetMapIndex(rk, rv)
  33. mapStringInt2 := mapReflect.Interface().(map[string]int)
  34. fmt.Println("mapStringInt2: ", mapStringInt2)
  35. fmt.Println("mapStringInt : ", mapStringInt)
  36. }

PS D:\Workspace\Go\src\projects\demo> go run main.go
intSlice2:  [10]
intSlice :  []
mapStringInt2:  map[hello:10]
mapStringInt :  map[

  1. /*
  2. 使用反射创建函数
  3. 反射不仅仅可以为存储数据创造新的地方。还可以使用reflect.MakeFunc函数使用reflect来创建新函数。
  4. 该函数期望我们要创建的函数的reflect.Type,以及一个闭包,其输入参数为[]reflect.Value类型,
  5. 其返回类型也为[] reflect.Value类型。下面是一个简单的示例,它为传递给它的任何函数创建一个定时包装器
  6. */
  7. package main
  8. import (
  9. "fmt"
  10. "reflect"
  11. "runtime"
  12. "time"
  13. )
  14. func MakeTimedFunction(f interface{}) interface{} {
  15. rf := reflect.TypeOf(f)
  16. fmt.Println("rf: ", rf)
  17. if rf.Kind() != reflect.Func {
  18. panic("expects a function")
  19. }
  20. vf := reflect.ValueOf(f)
  21. fmt.Println("vf: ", vf)
  22. wrapperF := reflect.MakeFunc(rf, func(in []reflect.Value) []reflect.Value {
  23. start := time.Now()
  24. out := vf.Call(in)
  25. end := time.Now()
  26. fmt.Printf("calling %s took %v\n", runtime.FuncForPC(vf.Pointer()).Name(), end.Sub(start))
  27. return out
  28. })
  29. return wrapperF.Interface()
  30. }
  31. func timeMe() {
  32. fmt.Println("starting")
  33. time.Sleep(1 * time.Second)
  34. fmt.Println("ending")
  35. }
  36. func timeMeToo(a int) int {
  37. fmt.Println("starting")
  38. time.Sleep(time.Duration(a) * time.Second)
  39. result := a * 2
  40. fmt.Println("ending")
  41. return result
  42. }
  43. func main() {
  44. fmt.Println("MakeTimedFunction1: ")
  45. timed := MakeTimedFunction(timeMe).(func())
  46. fmt.Println("转成普通函数1: ")
  47. timed()
  48. fmt.Println("\n\nMakeTimedFunction2: ")
  49. timedToo := MakeTimedFunction(timeMeToo).(func(int) int)
  50. fmt.Println("转成普通函数2: ")
  51. fmt.Println(timedToo(5))
  52. }

PS D:\Workspace\Go\src\projects\demo> go run main.go
MakeTimedFunction1: 
rf:  func()
vf:  0x733a80
转成普通函数1:
starting
ending
calling main.timeMe took 1.0150935s


MakeTimedFunction2:
rf:  func(int) int
vf:  0x733b40
转成普通函数2:
starting
ending
calling main.timeMeToo took 5.0133999s
10

3 reflect反射- Type和Value

TypeOf() 方法返回变量的类型信息得到的是一个类型为 reflect.Type 的变量,

ValueOf() 方法返回变量的值信息得到的是一个类型为 reflect.Value 的变量。

  1. package main
  2. import (
  3. "fmt"
  4. "reflect"
  5. )
  6. type Skills interface {
  7. reading()
  8. running()
  9. }
  10. type Student struct {
  11. Age int
  12. Name string
  13. }
  14. func (self Student) runing() {
  15. fmt.Printf("%s is running\n", self.Name)
  16. }
  17. func (self Student) reading() {
  18. fmt.Printf("%s is reading\n", self.Name)
  19. }
  20. func main() {
  21. stu1 := Student{Name: "dar", Age: 34}
  22. inf := new(Skills)
  23. stu_type := reflect.TypeOf(stu1)
  24. inf_type := reflect.TypeOf(inf).Elem() // 获取指针所指的对象类型
  25. fmt.Println("类型stu_type:", stu_type)
  26. fmt.Println(stu_type.String()) //main.Student
  27. fmt.Println(stu_type.Name()) //Student
  28. fmt.Println(stu_type.PkgPath()) //main
  29. fmt.Println(stu_type.Kind()) //struct
  30. fmt.Println(stu_type.Size()) //24
  31. fmt.Println("\n类型inf_type:", inf_type)
  32. fmt.Println(inf_type.NumMethod()) //2
  33. fmt.Println(inf_type.Method(0), inf_type.Method(0).Name) // {reading main func() <invalid Value> 0} reading
  34. fmt.Println(inf_type.MethodByName("reading")) //{reading main func() <invalid Value> 0} true
  35. }

PS D:\Workspace\Go\src\projects\demo> go run main.go
类型stu_type: main.Student
main.Student
Student
main
struct
24

类型inf_type: main.Skills
2
{reading main func() <invalid Value> 0} reading
{reading main func() <invalid Value> 0} true

  1. package main
  2. import (
  3. "fmt"
  4. "reflect"
  5. )
  6. type Skills interface {
  7. reading()
  8. running()
  9. }
  10. type Student struct {
  11. Name string "json:name"
  12. Age int "json:age"
  13. }
  14. func (self Student) runing() {
  15. fmt.Printf("%s is running\n", self.Name)
  16. }
  17. func (self Student) reading() {
  18. fmt.Printf("%s is reading\n", self.Name)
  19. }
  20. func main() {
  21. stu1 := Student{Name: "dar", Age: 34}
  22. stu_type := reflect.TypeOf(stu1)
  23. fmt.Println(stu_type.NumField()) //2
  24. fmt.Println(stu_type.Field(0)) //{Name string 0 [0] false}
  25. fmt.Println(stu_type.FieldByName("Name")) //{{Age int 16 [1] false} true
  26. fmt.Println(stu_type.Field(1)) //{Name string 0 [0] false}
  27. fmt.Println(stu_type.FieldByName("Age")) //{{Age int 16 [1] false} true
  28. }
  1. package main
  2. import (
  3. "fmt"
  4. "reflect"
  5. )
  6. func main() {
  7. str := "dar"
  8. val := reflect.ValueOf(str).Kind()
  9. fmt.Println(val) //string
  10. }

3 reflect反射-利弊

反射的好处

1. 为了降低多写代码造成的bug率,做更好的归约和抽象

2. 为了灵活、好用、方便,做动态解析、调用和处理

3. 为了代码好看、易读、提高开发效率,补足与动态语言之间的一些差别

反射的弊端

1. 与反射相关的代码,经常是难以阅读的。在软件工程中,代码可读性也是一个非常重要的指标。

2. Go 语言作为一门静态语言,编码过程中,编译器能提前发现一些类型错误,但是对于反射代码是无能为力的。所以包含反射相关的代码,很可能会运行很久,才会出错,这时候经常是直接 panic,可能会造成严重的后果。

3. 反射对性能影响还是比较大的,比正常代码运行速度慢一到两个数量级。所以,对于一个项目中处于运行效率关键位置的代码,尽量避免使用反射特性。

3.1 reflect反射-Type

Type:Type类型用来表示一个go类型。

不是所有go类型的Type值都能使用所有方法。请参见每个方法的文档获取使用限制。在调用有分

类限定的方法时,应先使用Kind方法获知类型的分类。调用该分类不支持的方法会导致运行时的

panic。

获取Type对象的方法:

func TypeOf(i interface{}) Type

  1. str := "dar"
  2. res_type := reflect.TypeOf(str)
  3. fmt.Println(res_type) //string
  4. int1 := 1
  5. res_type2 := reflect.TypeOf(int1)
  6. fmt.Println(res_type2) //int

3.1 reflect反射-Type续-reflect.Type通用方法

  1. func (t *rtype) String() string // 获取 t 类型的字符串描述,不要通过 String 来判断两种类型是否一致。
  2. func (t *rtype) Name() string // 获取 t 类型在其包中定义的名称,未命名类型则返回空字符串。
  3. func (t *rtype) PkgPath() string // 获取 t 类型所在包的名称,未命名类型则返回空字符串。
  4. func (t *rtype) Kind() reflect.Kind // 获取 t 类型的类别。
  5. func (t *rtype) Size() uintptr // 获取 t 类型的值在分配内存时的大小,功能和 unsafe.SizeOf 一样。
  6. func (t *rtype) Align() int // 获取 t 类型的值在分配内存时的字节对齐值。
  7. func (t *rtype) FieldAlign() int // 获取 t 类型的值作为结构体字段时的字节对齐值。
  8. func (t *rtype) NumMethod() int // 获取 t 类型的方法数量。
  9. func (t *rtype) Method() reflect.Method // 根据索引获取 t 类型的方法,如果方法不存在,则 panic。
  10. // 如果 t 是一个实际的类型,则返回值的 Type 和 Func 字段会列出接收者。
  11. // 如果 t 只是一个接口,则返回值的 Type 不列出接收者,Func 为空值。
  12. func (t *rtype) MethodByName(string) (reflect.Method, bool) // 根据名称获取 t 类型的方法。
  13. func (t *rtype) Implements(u reflect.Type) bool // 判断 t 类型是否实现了 u 接口。
  14. func (t *rtype) ConvertibleTo(u reflect.Type) bool // 判断 t 类型的值可否转换为 u 类型。
  15. func (t *rtype) AssignableTo(u reflect.Type) bool // 判断 t 类型的值可否赋值给 u 类型。
  16. func (t *rtype) Comparable() bool // 判断 t 类型的值可否进行比较操作
  17. //注意对于:数组、切片、映射、通道、指针、接口
  18. func (t *rtype) Elem() reflect.Type // 获取元素类型、获取指针所指对象类型,获取接口的动态类型

3.1 reflect反射-Type续-reflect.Type其他方法

// 数值

func (t *rtype) Bits() int // 获取数值类型的位宽,t 必须是整型、浮点型、复数型

// 数组

func (t *rtype) Len() int // 获取数组的元素个数

// 映射

func (t *rtype) Key() reflect.Type // 获取映射的键类型

// 通道

func (t *rtype) ChanDir() reflect.ChanDir // 获取通道的方向

// 结构体

func (t *rtype) NumField() int // 获取字段数量

func (t *rtype) Field(int) reflect.StructField // 根据索引获取字段

func (t *rtype) FieldByName(string) (reflect.StructField, bool) // 根据名称获取字段

func (t *rtype) FieldByNameFunc(match func(string) bool) (reflect.StructField, bool) // 根据指定的匹配函数 math 获取字段

func (t *rtype) FieldByIndex(index []int) reflect.StructField // 根据索引链获取嵌套字段

// 函数

func (t *rtype) NumIn() int // 获取函数的参数数量

func (t *rtype) In(int) reflect.Type // 根据索引获取函数的参数信息

func (t *rtype) NumOut() int // 获取函数的返回值数量

func (t *rtype) Out(int) reflect.Type // 根据索引获取函数的返回值信息

func (t *rtype) IsVariadic() bool // 判断函数是否具有可变参数

3.1 reflect反射-Type结构

,rtype 实现了 Type 接口的所有方法。剩下的不同的部分信

息各种特殊类型结构体都不一样。可以将 rtype 理解成父类,

特殊类型的结构体是子类,会有一些不一样的字段信息。

  1. // 基础类型 rtype 实现了 Type 接口
  2. type rtype struct {
  3. size uintptr // 占用字节数
  4. ptrdata uintptr
  5. hash uint32 // 类型的hash值
  6. ...
  7. kind uint8 // 元类型
  8. ...
  9. }
  10. // 切片类型
  11. type sliceType struct {
  12. rtype
  13. elem *rtype // 元素类型
  14. }
  15. // 结构体类型
  16. type structType struct {
  17. rtype
  18. pkgPath name // 所在包名
  19. fields []structField // 字段列表
  20. }
  1. // 获取和设置普通类型的值
  2. package main
  3. import (
  4. "fmt"
  5. "reflect"
  6. )
  7. func main() {
  8. str := "dar"
  9. age := 11
  10. fmt.Println(reflect.ValueOf(str).String()) //获取str的值,结果dar
  11. fmt.Println(reflect.ValueOf(age).Int()) //获取age的值,结果age
  12. str2 := reflect.ValueOf(&str) //获取Value类型
  13. str2.Elem().SetString("ki") //设置值
  14. fmt.Println(str2.Elem(), age) //ki 11
  15. age2 := reflect.ValueOf(&age) //获取Value类型
  16. fmt.Println("age2:", age2)
  17. age2.Elem().SetInt(40) //设置值
  18. fmt.Println("age:", age)
  19. fmt.Println("reflect.ValueOf(age):", reflect.ValueOf(age))
  20. }

3.1 reflect反射- reflect.Value方法

  1. reflect.Value.Kind():获取变量类别,返回常量
  2. const (
  3. Invalid Kind = iota //不存在的类型
  4. Bool
  5. Int
  6. Int8
  7. Int16
  8. Int32
  9. Int64
  10. Uint
  11. Uint8
  12. Uint16
  13. Uint32
  14. Uint64
  15. Uintptr // 指针的整数类型
  16. Float32
  17. Float64
  18. Complex64
  19. Complex128
  20. Array
  21. Chan
  22. Func
  23. Interface
  24. Map
  25. Ptr
  26. Slice
  27. String
  28. Struct
  29. UnsafePointer
  30. )
  31. str := "dar"
  32. val := reflect.ValueOf(str).Kind()
  33. fmt.Println(val) //string

3.2 reflect反射- reflect.Value方法

获取值方法:

func (v Value) Int() int64 // 获取int类型值,如果 v 值不是有符号整型,则 panic。

func (v Value) Uint() uint64 // 获取unit类型的值,如果 v 值不是无符号整型(包括 uintptr),则 panic。

func (v Value) Float() float64 // 获取float类型的值,如果 v 值不是浮点型,则 panic。

func (v Value) Complex() complex128 // 获取复数类型的值,如果 v 值不是复数型,则 panic。

func (v Value) Bool() bool // 获取布尔类型的值,如果 v 值不是布尔型,则 panic。

func (v Value) Len() int // 获取 v 值的长度,v 值必须是字符串、数组、切片、映射、通道。

func (v Value) Cap() int // 获取 v 值的容量,v 值必须是数值、切片、通道。

func (v Value) Index(i int) reflect.Value // 获取 v 值的第 i 个元素,v 值必须是字符串、数组、切片,i 不能超出范围。

func (v Value) Bytes() []byte // 获取字节类型的值,如果 v 值不是字节切片,则 panic。

func (v Value) Slice(i, j int) reflect.Value // 获取 v 值的切片,切片长度 = j - i,切片容量 = v.Cap() - i。

// v 必须是字符串、数值、切片,如果是数组则必须可寻址。i 不能超出范围。

func (v Value) Slice3(i, j, k int) reflect.Value // 获取 v 值的切片,切片长度 = j - i,切片容量 = k - i。

// i、j、k 不能超出 v 的容量。i <= j <= k。

// v 必须是字符串、数值、切片,如果是数组则必须可寻址。i 不能超出范围。

func (v Value) MapIndex(key Value) reflect.Value // 根据 key 键获取 v 值的内容,v 值必须是映射。

// 如果指定的元素不存在,或 v 值是未初始化的映射,则返回零值(reflect.ValueOf(nil))

func (v Value) MapKeys() []reflect.Value // 获取 v 值的所有键的无序列表,v 值必须是映射。

// 如果 v 值是未初始化的映射,则返回空列表。

func (v Value) OverflowInt(x int64) bool // 判断 x 是否超出 v 值的取值范围,v 值必须是有符号整型。

func (v Value) OverflowUint(x uint64) bool // 判断 x 是否超出 v 值的取值范围,v 值必须是无符号整型。

func (v Value) OverflowFloat(x float64) bool // 判断 x 是否超出 v 值的取值范围,v 值必须是浮点型。

func (v Value) OverflowComplex(x complex128) bool // 判断 x 是否超出 v 值的取值范围,v 值必须是复数型。

  1. //简单结构体操作
  2. package main
  3. import (
  4. "fmt"
  5. "reflect"
  6. )
  7. type Skills interface {
  8. reading()
  9. running()
  10. }
  11. type Student struct {
  12. Name string
  13. Age int
  14. }
  15. func (self Student) runing() {
  16. fmt.Printf("%s is running\n", self.Name)
  17. }
  18. func (self Student) reading() {
  19. fmt.Printf("%s is reading\n", self.Name)
  20. }
  21. func main() {
  22. stu1 := Student{Name: "dar", Age: 18}
  23. stu_val := reflect.ValueOf(stu1) //获取Value类型
  24. fmt.Println(stu_val.NumField()) //2
  25. fmt.Println(stu_val.Field(0), stu_val.Field(1)) //dar 18
  26. fmt.Println(stu_val.FieldByName("Age")) //18
  27. stu_val2 := reflect.ValueOf(&stu1).Elem() // 要修改传引用或者指针
  28. stu_val2.FieldByName("Age").SetInt(33) //设置字段值 ,结果33
  29. fmt.Println(stu1.Age)
  30. }

3.2 reflect反射- reflect.Value方法

设置值方法:

func (v Value) SetInt(x int64) //设置int类型的值

func (v Value) SetUint(x uint64) // 设置无符号整型的值

func (v Value) SetFloat(x float64) // 设置浮点类型的值

func (v Value) SetComplex(x complex128) //设置复数类型的值

func (v Value) SetBool(x bool) //设置布尔类型的值

func (v Value) SetString(x string) //设置字符串类型的值

func (v Value) SetLen(n int) // 设置切片的长度,n 不能超出范围,不能为负数。

func (v Value) SetCap(n int) //设置切片的容量

func (v Value) SetBytes(x []byte) //设置字节类型的值

func (v Value) SetMapIndex(key, val reflect.Value) //设置map的key和value,前提必须是初始化以后,存在覆盖、不存在

添加

  1. //通过反射调用结构体中的方法,通过reflect.Value.Method(i int).Call()
  2. //或者reflect.Value.MethodByName(name string).Call()实现
  3. package main
  4. import (
  5. "fmt"
  6. "reflect"
  7. )
  8. type Student struct {
  9. Name string
  10. Age int
  11. }
  12. func (this *Student) SetName(name string) {
  13. this.Name = name
  14. fmt.Printf("set name %s\n", this.Name)
  15. }
  16. func (this *Student) SetAge(age int) {
  17. this.Age = age
  18. fmt.Printf("set age %d\n", age)
  19. }
  20. func (this *Student) String() string {
  21. fmt.Printf("this is %s\n", this.Name)
  22. return this.Name
  23. }
  24. func (this *Student) SetAgeAndName(age int, name string) {
  25. this.Age = age
  26. fmt.Printf("set age %d, name:%s\n", age, name)
  27. }
  28. func main() {
  29. stu1 := &Student{Name: "dar", Age: 18}
  30. val := reflect.ValueOf(stu1) //获取Value类型,也可以使用reflect.ValueOf(&stu1).Elem()
  31. val.MethodByName("String").Call(nil) //调用String方法
  32. params := make([]reflect.Value, 1)
  33. params[0] = reflect.ValueOf(18)
  34. val.MethodByName("SetAge").Call(params) //通过名称调用方法
  35. params[0] = reflect.ValueOf("ki")
  36. // val.Method(1).Call(params) //通过方法索引调用
  37. val.Method(2).Call(params) //通过方法索引调用 通过索引的方式拿到函数不安全
  38. fmt.Println(stu1.Name, stu1.Age)
  39. params = make([]reflect.Value, 2)
  40. params[0] = reflect.ValueOf(18)
  41. params[1] = reflect.ValueOf("老师")
  42. val.MethodByName("SetAgeAndName").Call(params)
  43. }
  44. //this is dar
  45. //set age 18
  46. //set name ki
  47. //ki 18

3.2 reflect反射- reflect.Value方法

其他方法:

//结构体相关:

func (v Value) NumField() int // 获取结构体字段(成员)数量

func (v Value) Field(i int) reflect.Value //根据索引获取结构体字段

func (v Value) FieldByIndex(index []int) reflect.Value // 根据索引链获取结构体嵌套字段

func (v Value) FieldByName(string) reflect.Value // 根据名称获取结构体的字段,不存在返回reflect.ValueOf(nil)

func (v Value) FieldByNameFunc(match func(string) bool) Value // 根据匹配函数 match 获取字段,如果没有匹配的字段,则返回

零值(reflect.ValueOf(nil))

//通道相关:

func (v Value) Send(x reflect.Value)// 发送数据(会阻塞),v 值必须是可写通道。

func (v Value) Recv() (x reflect.Value, ok bool) // 接收数据(会阻塞),v 值必须是可读通道。

func (v Value) TrySend(x reflect.Value) bool // 尝试发送数据(不会阻塞),v 值必须是可写通道。

func (v Value) TryRecv() (x reflect.Value, ok bool) // 尝试接收数据(不会阻塞),v 值必须是可读通道。

func (v Value) Close() // 关闭通道

//函数相关

func (v Value) Call(in []Value) (r []Value) // 通过参数列表 in 调用 v 值所代表的函数(或方法)。函数的返回值存入 r 中返回。

// 要传入多少参数就在 in 中存入多少元素。

// Call 即可以调用定参函数(参数数量固定),也可以调用变参函数(参数数量可变)。

func (v Value) CallSlice(in []Value) []Value // 调用变参函数

3.2 reflect反射- reflect.Value 结构体

type Value struct {

    typ *rtype // 变量的类型结构体

    ptr unsafe.Pointer // 数据指针

    flag uintptr // 标志位

    }

3.3 Go 语言官方的反射三大定律1

官方对 Go 语言的反射功能做了一个抽象的描述,总结出了三大定律

1. Reflection goes from interface value to reflection object.

2. Reflection goes from reflection object to interface value.

3. To modify a reflection object, the value must be settable.

第一个定律的意思是反射将接口变量转换成反射对象 Type 和 Value

func TypeOf(v interface{}) Type

func ValueOf(v interface{}) Value

第二个定律的意思是反射可以通过反射对象 Value 还原成原先的接口变量,这个指的就是 Value

结构体提供的 Interface() 方法。

func (v Value) Interface() interface{}

第三个定律的功能不是很好理解,它的意思是想用反射功能来修改一个变量的值,前提是这个

值可以被修改。

  1. package main
  2. import (
  3. "fmt"
  4. "reflect"
  5. )
  6. func test1() {
  7. var s int = 42
  8. var v = reflect.ValueOf(s)
  9. v.SetInt(43)
  10. fmt.Println(s)
  11. }
  12. func test2() {
  13. var s int = 42
  14. // 反射指针类型
  15. var v = reflect.ValueOf(&s)
  16. // 要拿出指针指向的元素进行修改
  17. v.Elem().SetInt(43)
  18. fmt.Println(s)
  19. }
  20. func main() {
  21. test1()
  22. }

3.3 Go 语言官方的反射三大定律2

值类型的变量是不可以通过反射来修改,因为在反射之前,传参的时候需要将值变量转

换成接口变量,值内容会被浅拷贝,反射对象 Value 指向的数据内存地址不是原变量的内

存地址,而是拷贝后的内存地址。这意味着如果值类型变量可以通过反射功能来修改,

那么修改操作根本不会影响到原变量的值,那就白白修改了。所以 reflect 包就直接禁止

了通过反射来修改值类型的变量。

  1. package main
  2. import (
  3. "fmt"
  4. "reflect"
  5. )
  6. type Rect struct {
  7. Width int
  8. Height int
  9. Name string
  10. }
  11. // 通过统一的接口去实现属性设置的
  12. func SetRectAttr(r *Rect, name string, value int) {
  13. var v = reflect.ValueOf(r)
  14. var field = v.Elem().FieldByName(name)
  15. field.SetInt(int64(value))
  16. }
  17. // 结构体也是值类型,也必须通过指针类型来修改。
  18. func main() {
  19. var r = Rect{50, 100}
  20. SetRectAttr(&r, "Width", 100) // 修改属性的接口
  21. SetRectAttr(&r, "Height", 200)
  22. SetRectAttr(&r, "Name", "正方形")
  23. fmt.Println(r)
  24. }
  1. package main
  2. import (
  3. "fmt"
  4. "reflect"
  5. "time"
  6. )
  7. const N = 1000000
  8. func test1() {
  9. var sum = 0
  10. t0 := time.Now()
  11. for i := 0; i < (N); i++ {
  12. var s int = 42
  13. // 反射指针类型
  14. var v = reflect.ValueOf(&s)
  15. // 要拿出指针指向的元素进行修改
  16. v.Elem().SetInt(43)
  17. sum += s
  18. }
  19. elapsed := time.Since(t0)
  20. fmt.Println("反射赋值 ", "N:", N, "time:", elapsed, ", sum:", sum)
  21. }
  22. func test2() {
  23. var sum = 0
  24. t0 := time.Now()
  25. for i := 0; i < (N); i++ {
  26. var s int = 42
  27. s = 43
  28. sum += s
  29. }
  30. elapsed := time.Since(t0)
  31. fmt.Println("直接赋值 ", "N:", N, "time:", elapsed, ", sum:", sum)
  32. }
  33. func main() {
  34. test1()
  35. test2()
  36. }
  1. // 3 reflect反射 基础
  2. package main
  3. import (
  4. "fmt"
  5. )
  6. func main() {
  7. newValue := make(map[interface{}]interface{}, 0)
  8. newValue[11] = "dar"
  9. newValue["age"] = 18
  10. fmt.Println(newValue)
  11. }

4 Go map实战

◼ go中的map是hash表的一个引用,类型写为:map[key]value,其中的key, value分别对

应一种数据类型,如map[string]string

◼ 要求所有的key的数据类型相同,所有value数据类型相同(注:key与value可以有不同的

数据类型,如果想不同则使用interface作为value)

map中的key的数据类型

◼ map中的每个key在keys的集合中是唯一的,而且需要支持 == or != 操作

◼ key的常用类型:int, rune, string, 结构体(每个元素需要支持 == or != 操作), 指针, 基于这

些类型自定义的类型

float32/64 类型从语法上可以作为key类型,但是实际一般不作为key,因为其类型有误差

4.1 Go map实战-key的几种数据类型举例

  1. package main
  2. import "fmt"
  3. func main() {
  4. // m0 可以, key类型为string, 支持 == 比较操作
  5. {
  6. fmt.Println("---- m0 ----")
  7. var m0 map[string]string // 定义map类型变量m0,key的类型为string,value的类型string
  8. fmt.Println(m0)
  9. }
  10. // m1 不可以, []byte是slice,不支持 == != 操作,不可以作为map key的数据类型
  11. {
  12. // fmt.Println("---- m1 ----");
  13. //var m1 map[[]byte]string // 报错: invalid map key type []byte
  14. //fmt.Println(m1)
  15. // 准确说slice类型只能与nil比较,其他的都不可以,可以通过如下测试:
  16. // var b1,b2 []byte
  17. // fmt.Println(b1==b2) // 报错: invalid operation: b1 == b2 (slice can only be compared to nil)
  18. }
  19. // m2 可以, interface{}类型可以作为key,但是需要加入的key的类型是可以比较的
  20. {
  21. fmt.Println("---- m2 ----")
  22. var m2 map[interface{}]string
  23. m2 = make(map[interface{}]string)
  24. //m2[[]byte("k2")]="v2" // panic: runtime error: hash of unhashable type []uint8
  25. m2[123] = "123"
  26. m2[12.3] = "123"
  27. fmt.Println(m2)
  28. var str string = m2[12.3]
  29. fmt.Println(str)
  30. }
  31. // m3 可以, 数组支持比较
  32. {
  33. fmt.Println("---- m3 ----")
  34. a3 := [3]int{1, 2, 3}
  35. var m3 map[[3]int]string
  36. m3 = make(map[[3]int]string)
  37. m3[a3] = "m3"
  38. fmt.Println(m3)
  39. }
  40. // m4 可以,book1里面的元素都是支持== !=
  41. {
  42. fmt.Println("---- m4 ----")
  43. type book1 struct {
  44. name string
  45. }
  46. var m4 map[book1]string
  47. fmt.Println(m4)
  48. }
  49. // m5 不可以, text元素类型为[]byte, 不满足key的要求
  50. {
  51. fmt.Println("---- m5 ----")
  52. // type book2 struct {
  53. // name string
  54. // text []byte //没有这个就可以
  55. // }
  56. //var m5 map[book2]string //invalid map key type book2
  57. //fmt.Println(m5)
  58. }
  59. }

4.2 map基本操作

map创建

两种创建的方式:一是通过字面值;二是通过make函数

map增删改查

map遍历

•遍历的顺序是随机的

•使用for range遍历的时候,k,v使用的同一块内存,这也是容易出现错误的地

增加,修改: m["c"] = "11"

查: v1 := m["x"]

v2, ok2 := m["x"]

删: delete(m, "x")

for k, v := range m { fmt.Printf("k:[%v].v:[%v]\n", k, v) //

输出k,v值 }

5 Go string字符串

字符串通常有两种设计,一种是「字符」串,一种是「字节」串。「字符」串中的每个

字都是定长的,而「字节」串中每个字是不定长的。Go 语言里的字符串是「字节」串,

英文字符占用 1 个字节,非英文字符占多个字节。

其中 codepoint 是每个「字」的其实偏移量。 Go 语言的字符串采用 utf8 编码,中文汉字通常需要
占用 3 个字节,英文只需要 1 个字节。 len() 函数得到的是字节的数量,通过下标来访问字符串得
到的是「字节」。
  1. // 4-2 map基本操作
  2. package main
  3. import "fmt"
  4. func create() {
  5. fmt.Println("map创建方式:")
  6. // 1 字面值
  7. {
  8. m1 := map[string]string{
  9. "m1": "v1", // 定义时指定的初始key/value, 后面可以继续添加
  10. }
  11. _ = m1
  12. }
  13. // 2 使用make函数
  14. {
  15. m2 := make(map[string]string) // 创建时,里面不含元素,元素都需要后续添加
  16. m2["m2"] = "v2" // 添加元素
  17. _ = m2
  18. }
  19. // 定义一个空的map
  20. {
  21. m3 := map[string]string{}
  22. m4 := make(map[string]string)
  23. _ = m3
  24. _ = m4
  25. }
  26. }
  27. func curd() {
  28. fmt.Println("map增删改查:")
  29. // 创建
  30. fmt.Println("建:")
  31. m := map[string]string{
  32. "a": "va",
  33. "b": "vb",
  34. }
  35. fmt.Println(len(m)) // len(m) 获得m中key/value对的个数
  36. // 增加,修改
  37. fmt.Println("增改:")
  38. {
  39. // k不存在为增加,k存在为修改
  40. m["c"] = ""
  41. m["c"] = "11" // 重复增加(key相同),使用新的值覆盖
  42. fmt.Printf("%#v %#v\n", m, len(m)) // map[string]string{"a":"va", "b":"vb", "c":"11"} 3
  43. }
  44. // 查
  45. fmt.Println("查:")
  46. {
  47. // v := m[k] // 从m中取键k对应的值给v,如果k在m中不存在,则将value类型的零值赋值给v
  48. // v, ok := m[k] // 从m中取键k对应的值给v,如果k存在,ok=true,如果k不存在,将value类型的零值赋值给v同时ok=false
  49. // 查1 - 元素不存在
  50. v1 := m["x"] //
  51. v2, ok2 := m["x"]
  52. fmt.Printf("%#v %#v %#v\n", v1, v2, ok2) // "" "" false
  53. // 查2 - 元素存在
  54. v3 := m["a"]
  55. v4, ok4 := m["a"]
  56. fmt.Printf("%#v %#v %#v\n", v3, v4, ok4) //"va" "va" true
  57. }
  58. fmt.Println("删:")
  59. // 删, 使用内置函数删除k/v对
  60. {
  61. // delete(m, k) 将k以及k对应的v从m中删掉;如果k不在m中,不执行任何操作
  62. delete(m, "x") // 删除不存在的key,原m不影响
  63. delete(m, "a") // 删除存在的key
  64. fmt.Printf("%#v %#v\n", m, len(m)) // map[string]string{"b":"vb", "c":"11"} 2
  65. delete(m, "a") // 重复删除不报错,m无影响
  66. fmt.Printf("%#v %#v\n", m, len(m)) /// map[string]string{"b":"vb", "c":"11"} 2
  67. }
  68. }
  69. func travel() {
  70. fmt.Println("map遍历:")
  71. m := map[string]int{
  72. "a": 1,
  73. "b": 2,
  74. }
  75. for k, v := range m {
  76. fmt.Printf("k:[%v].v:[%v]\n", k, v) // 输出k,v值
  77. }
  78. var fruits = map[string]int{
  79. "apple": 2,
  80. "banana": 5,
  81. "orange": 8,
  82. }
  83. for name, score := range fruits {
  84. fmt.Println(name, score)
  85. }
  86. for name := range fruits {
  87. fmt.Println(name)
  88. }
  89. }
  90. func main() {
  91. create()
  92. curd()
  93. travel()
  94. }

5.1 Go string字符串-遍历

package main

import "fmt"

func main() {

    var s = "嘻哈china"

    for i := 0; i < len(s); i++ {

        fmt.Printf("%x ", s[i])

    }

}

e5 98 bb e5 93 88 63 68 69 6e 61 

func main() {

    var s = "嘻哈china"

    for codepoint, runeValue := range s {

        fmt.Printf("[%d]: %x", codepoint, int32(runeValue))

    }

}

PS D:\Workspace\Go\src\projects\demo> go run main.go
[0]: 563b[3]: 54c8[6]: 63[7]: 68[8]: 69[9]: 6e[10]: 61 

5-2 Go string字节串的内存表示和操作

  1. // 按字符 rune 遍历
  2. package main
  3. import "fmt"
  4. func splice() {
  5. var s1 = "hello" // 静态字面量
  6. var s2 = ""
  7. for i := 0; i < 10; i++ {
  8. s2 += s1 // 动态构造
  9. }
  10. fmt.Println(len(s1))
  11. fmt.Println(len(s2))
  12. }
  13. // 字符串是只读的
  14. func onlyread() {
  15. var s = "hello"
  16. s[0] = 'H'
  17. }
  18. // 切割切割
  19. func cut() {
  20. var s1 = "hello world"
  21. var s2 = s1[3:8]
  22. fmt.Println(s2)
  23. }
  24. // 字节切片和字符串的相互转换
  25. func string2bytes() {
  26. var s1 = "hello world"
  27. var b = []byte(s1) // 字符串转字节切片
  28. var s2 = string(b) // 字节切片转字符串
  29. fmt.Println(b)
  30. fmt.Println(s2)
  31. }
  32. func main() {
  33. splice()
  34. onlyread()
  35. cut()
  36. string2bytes()
  37. }

1.3 Go语言并发编程

1. Goroutine

1 Go协程 Goroutine
1.1 Goroutine 使用
1.2 Goroutine 原理

1.1 如何使用Goroutine

在函数或方法调用前面加上关键字go,您将会同时运行一个新的Goroutine

1.2 子协程异常退出的影响

在使用子协程时一定要特别注意保护好每个子协程,确保它们正常安全的运行。因为子协程

的异常退出会将异常传播到主协程,直接会导致主协程也跟着挂掉,然后整个程序就崩溃了。

1.3 协程异常处理-recover

recover 是一个Go语言的内建函数,可以让进入宕机流程中的 goroutine 恢复过来,recover 仅

在延迟函数 defer 中有效。

如果当前的 goroutine 陷入恐慌,调用 recover 可以捕获到 panic 的输入值,并且恢复正常的

执行。

  1. // 1.3 协程异常处理-recover
  2. package main
  3. import (
  4. "fmt"
  5. "runtime"
  6. )
  7. // 崩溃时需要传递的上下文信息
  8. type panicContext struct {
  9. function string // 所在函数
  10. }
  11. // 保护方式允许一个函数
  12. func ProtectRun(entry func()) {
  13. // 延迟处理的函数
  14. defer func() {
  15. // 发生宕机时,获取panic传递的上下文并打印
  16. err := recover()
  17. switch err.(type) {
  18. case runtime.Error: // 运行时错误
  19. fmt.Println("runtime error:", err)
  20. default: // 非运行时错误
  21. fmt.Println("error:", err)
  22. }
  23. }()
  24. entry()
  25. }
  26. func main() {
  27. fmt.Println("运行前")
  28. // 允许一段手动触发的错误
  29. ProtectRun(func() {
  30. fmt.Println("手动宕机前")
  31. // 使用panic传递上下文
  32. panic(&panicContext{
  33. "手动触发panic",
  34. })
  35. fmt.Println("手动宕机后")
  36. })
  37. // 故意造成空指针访问错误
  38. ProtectRun(func() {
  39. fmt.Println("赋值宕机前")
  40. var a *int
  41. *a = 1
  42. fmt.Println("赋值宕机后")
  43. })
  44. fmt.Println("运行后")
  45. }
  1. // 1.3 协程异常处理-recover
  2. package main
  3. import (
  4. "fmt"
  5. "time"
  6. )
  7. func main() {
  8. fmt.Println("run in main goroutine")
  9. go func() {
  10. fmt.Println("run in child goroutine")
  11. defer func() { // 要在对应的协程里执行
  12. fmt.Println("执行defer:")
  13. if err := recover(); err != nil {
  14. fmt.Println("捕获error:", err)
  15. }
  16. }()
  17. fmt.Println("run in grand grand child goroutine")
  18. var ptr *int
  19. *ptr = 0x12345 // 故意制造崩溃 ,该协程运行到这里结束
  20. go func() {
  21. fmt.Println("子子run in grand child goroutine") // 这里也不会运行
  22. go func() {
  23. }()
  24. }()
  25. // time.Sleep(time.Second * 1)
  26. fmt.Println("离开: run in child goroutine leave") // 这里能否执行到
  27. }()
  28. time.Sleep(2 * time.Second)
  29. fmt.Println("main goroutine will quit")
  30. }

1-4 启动百万协程

Go 语言能同时管理上百万的协程

  1. // 1-4 启动百万协程
  2. package main
  3. import (
  4. "fmt"
  5. "runtime"
  6. "time"
  7. )
  8. const N = 1000000
  9. func main() {
  10. fmt.Println("run in main goroutine")
  11. i := 1
  12. for {
  13. go func() {
  14. for {
  15. time.Sleep(time.Second)
  16. }
  17. }()
  18. if i%10000 == 0 {
  19. fmt.Printf("%d goroutine started\n", i)
  20. }
  21. i++
  22. if i == N {
  23. break
  24. }
  25. }
  26. fmt.Println("NumGoroutine:", runtime.NumGoroutine())
  27. time.Sleep(time.Second * 15)
  28. }

1-5 死循环

如果有个别协程死循环了会导致其它协程饥饿得到不运行么?

  1. package main
  2. import (
  3. "fmt"
  4. "runtime"
  5. "syscall"
  6. "time"
  7. )
  8. // 获取的是线程ID,不是协程ID
  9. func GetCurrentThreadId() int {
  10. var user32 *syscall.DLL
  11. var GetCurrentThreadId *syscall.Proc
  12. var err error
  13. user32, err = syscall.LoadDLL("Kernel32.dll") // Windows用的
  14. if err != nil {
  15. fmt.Printf("syscall.LoadDLL fail: %v\n", err.Error())
  16. return 0
  17. }
  18. GetCurrentThreadId, err = user32.FindProc("GetCurrentThreadId")
  19. if err != nil {
  20. fmt.Printf("user32.FindProc fail: %v\n", err.Error())
  21. return 0
  22. }
  23. var pid uintptr
  24. pid, _, err = GetCurrentThreadId.Call()
  25. return int(pid)
  26. }
  27. func main() {
  28. // runtime.GOMAXPROCS(1)
  29. // 读取当前的线程数
  30. fmt.Println("GOMAXPROCS:", runtime.GOMAXPROCS(0)) // 本身电脑物理核心是4核 支持超线程 8核
  31. fmt.Println("run in main goroutine")
  32. n := 5
  33. for i := 0; i < n; i++ {
  34. go func() {
  35. fmt.Println("dead loop goroutine start, threadId:", GetCurrentThreadId())
  36. for {
  37. } // 死循环
  38. fmt.Println("dead loop goroutine stop")
  39. }()
  40. }
  41. go func() {
  42. var count = 0
  43. for {
  44. time.Sleep(time.Second)
  45. count++
  46. fmt.Println("for goroutine running:", count, "threadId:", GetCurrentThreadId())
  47. }
  48. }()
  49. fmt.Println("NumGoroutine: ", runtime.NumGoroutine())
  50. var count = 0
  51. for {
  52. time.Sleep(time.Second)
  53. count++
  54. fmt.Println("main goroutine running:", count, "threadId:", GetCurrentThreadId())
  55. }
  56. }

1.6 设置线程数

Go的调度器使用了一个叫做GOMAXPROCS的变量来决定会有多少个操作系统的线程同时执

行Go的代码。其默认的值是运行机器上的CPU的核心数,所以在一个有8个核心的机器上

时,调度器一次会在8个OS线程上去调度GO代码。

  1. package main
  2. import (
  3. "fmt"
  4. "runtime"
  5. )
  6. func main() {
  7. fmt.Println("runtime.NumCPU():", runtime.NumCPU())
  8. // 读取默认的线程数
  9. fmt.Println(runtime.GOMAXPROCS(0))
  10. // 设置线程数为 10
  11. runtime.GOMAXPROCS(10)
  12. // 读取当前的线程数
  13. fmt.Println(runtime.GOMAXPROCS(0))
  14. }

1-7 G-P-M模型-为什么引入协程?

核心原因为goroutine的轻量级,无论是从进程到线程,还是从线程到协程,其核心都是为了使得

我们的调度单元更加轻量级。可以轻易得创建几万几十万的goroutine而不用担心内存耗尽等问题。

1-7 G-P-M模型-系统调用

调用system call陷入内核没有返回之前,为保证调度的并发性,golang 调度器在进入系统调用之前

从线程池拿一个线程或者新建一个线程,当前P交给新的线程M1执行。

G0 返回之后,需要找一个可用的 P 继续运行,
如果没有则将其放在全局队列等待调度。 M0
G0 返回后退出或放回线程池。

1-7 G-P-M模型-工作流窃取

在P队列上的goroutine全部调度完了之后,对应的M首先会尝试从global runqueue中获取

goroutine进行调度。如果golbal runqueue中没有goroutine,当前M会从别的M对应P的local

runqueue中抢一半的goroutine放入自己的P中进行调度。具体要看C代码去了。

2. Channel

2 通道channel

如果说goroutine是Go语音程序的并发体的话,那么channels它们之间的通信机制。

作为协程的输出,通道是一个容器,它可以容纳数据。

作为协程的输入,通道是一个生产者,它可以向协程提供数据。

通道作为容器是有限定大小的,满了就写不进去,空了就读不出来。

通道有它自己的类型,它可以限定进入通道的数据的类型。

2.1 创建通道

创建通道只有一种语法,使用make 函数

有两种通道类型:

「缓冲型通道」 var bufferedChannel = make(chan int(这里是类型,什么类型都

行), 1024)

「非缓冲型通道」 var unbufferedChannel = make(chan int)

两个相同类型的channel可以使用==运算符比较。如果两个channel引用的是相通的对象,那

么比较的结果为真。一个channel也可以和nil进行比较。

  1. package main
  2. import "fmt"
  3. func send(ch chan int) {
  4. i := 0
  5. for {
  6. i++
  7. ch <- i
  8. }
  9. }
  10. func recv(ch chan int) {
  11. value := <-ch
  12. fmt.Println(value)
  13. value = <-ch
  14. fmt.Println(value)
  15. close(ch)
  16. }
  17. // 向一个已经关闭的通道执行写操作会抛出异常,这意味着我们在写通道时一定要确保通道没有被关闭。
  18. func main() {
  19. var ch = make(chan int, 4)
  20. go recv(ch)
  21. send(ch)
  22. }

2.2 读写通道

Go 语言为通道的读写设计了特殊的箭头语法糖 <-,让我们使用通道时非常方便。把箭头写在

通道变量的右边就是写通道,把箭头写在通道的左边就是读通道。一次只能读写一个元素

  1. // 2.2 读写通道
  2. package main
  3. import (
  4. "fmt"
  5. "time"
  6. )
  7. func main() {
  8. ch := make(chan float32, 4)
  9. for i := 0; i < cap(ch); i++ {
  10. ch <- 1.0 // 写通道
  11. }
  12. for len(ch) > 0 {
  13. value := <-ch // 读通道
  14. fmt.Println(value)
  15. }
  16. // ch1 := make(chan int, 1) // 这里是缓存 有一个1元素
  17. ch1 := make(chan int) // 非缓存的,实际是0个,并不是1个
  18. go func() {
  19. time.Sleep(1 * time.Second)
  20. ch1 <- 1
  21. ch1 <- 1 // 这里已经阻塞
  22. fmt.Println("写入ch1") //这里没打印
  23. }()
  24. value1 := <-ch1
  25. value1 = <-ch1
  26. time.Sleep(5 * time.Second)
  27. fmt.Println("退出, value1:", value1)
  28. }

通道作为容器,它可以像切片一样,使用 cap() 和 len() 全局函数获得通道的容量和当前内

部的元素个数。

2-3 读写阻塞

通道满了,写操作就会阻塞,协程就会进入休眠,直到有其它协程读通道挪出了空间,协程

才会被唤醒。如果有多个协程的写操作都阻塞了,一个读操作只会唤醒一个协程。

  1. // 2-3 读写阻塞
  2. package main
  3. import (
  4. "fmt"
  5. "math/rand"
  6. "time"
  7. )
  8. func send(ch chan int) {
  9. for {
  10. var value = rand.Intn(100)
  11. ch <- value
  12. fmt.Printf("send %d\n", value) // 这里没有延时
  13. }
  14. }
  15. func recv(ch chan int) {
  16. for {
  17. value := <-ch
  18. fmt.Printf("recv %d\n", value)
  19. time.Sleep(time.Second)
  20. }
  21. }
  22. func main() {
  23. var ch = make(chan int, 1)
  24. // 子协程循环读
  25. go recv(ch)
  26. // 主协程循环写
  27. send(ch)
  28. }

2.4 关闭通道

Go 语言的通道有点像文件,不但支持读写操作, 还支持关闭。读取一个已经关闭的通道会立

即返回通道类型的「零值」,而写一个已经关闭的通道会抛异常。如果通道里的元素是整型的,

读操作是不能通过返回值来确定通道是否关闭的。

  1. // 2.4 关闭通道
  2. /*
  3. Go 语言的通道有点像文件,不但支持读写操作, 还支持关闭。
  4. 读取一个已经关闭的通道会立即返回通道类型的「零值」,而写一个已经关闭的通道会抛异常。
  5. 如果通道里的元素是整型的,读操作是不能通过返回值来确定通道是否关闭的。
  6. */
  7. package main
  8. import "fmt"
  9. func main() {
  10. var ch = make(chan int, 4)
  11. ch <- 1
  12. ch <- 2
  13. fmt.Println("len(ch):", len(ch), "cap(ch):", cap(ch))
  14. close(ch)
  15. value := <-ch
  16. fmt.Println(value)
  17. value = <-ch
  18. fmt.Println(value)
  19. value = <-ch
  20. fmt.Println(value)
  21. value = <-ch
  22. fmt.Println(value)
  23. ch <- 3
  24. }

2-5 通道写安全

向一个已经关闭的通道执行写操作会抛出异常,这意味着我们在写通道时一定要确保通道没

有被关闭

 多人写入怎么办?

  1. // 2-5 通道写安全
  2. package main
  3. import "fmt"
  4. func send(ch chan int) { // 在写入端关闭, 没有太多的通用性
  5. ch <- 1
  6. ch <- 2
  7. ch <- 3
  8. ch <- 4
  9. close(ch)
  10. }
  11. func recv(ch chan int) {
  12. for v := range ch {
  13. fmt.Println(v)
  14. }
  15. value := <-ch // 判别不了是否已经读取完毕
  16. fmt.Println("value:", value)
  17. }
  18. // 确保通道写安全的最好方式是由负责写通道的协程自己来关闭通道,读通道的协程不要去关闭通道。
  19. func main() {
  20. var ch = make(chan int, 1)
  21. go send(ch)
  22. recv(ch)
  23. }

2-6 WaitGroup

在写端关闭channel对单写的程序有效,但是多写的时候呢?

使用到内置 sync 包提供的 WaitGroup 对象,它使用计数来等待指定事件完成。

  1. // 2-6 WaitGroup 在写端关闭channel对单写的程序有效,但是多写的时候呢?
  2. package main
  3. import (
  4. "fmt"
  5. "sync"
  6. "time"
  7. )
  8. func send(ch chan int, wg *sync.WaitGroup) {
  9. defer wg.Done() // 计数值减一
  10. i := 0
  11. for i < 4 {
  12. i++
  13. ch <- i
  14. }
  15. }
  16. func recv(ch chan int) {
  17. for v := range ch {
  18. fmt.Println(v)
  19. }
  20. }
  21. // 只要一个值能做界定符 比如nil, 比如0xfffe
  22. func main() {
  23. var ch = make(chan int, 4)
  24. var wg = new(sync.WaitGroup)
  25. wg.Add(2) // 增加计数值
  26. go send(ch, wg) // 写
  27. go send(ch, wg) // 写
  28. go recv(ch)
  29. // Wait() 阻塞等待所有的写通道协程结束
  30. // 待计数值变成零,Wait() 才会返回
  31. wg.Wait()
  32. // 关闭通道
  33. close(ch)
  34. time.Sleep(time.Second)
  35. }

2-7 多路通道

在真实的世界中,还有一种消息传递场景,那就是消费者有多个消费来源,只要有一个

来源生产了数据,消费者就可以读这个数据进行消费。这时候可以将多个来源通道的数

据汇聚到目标通道,然后统一在目标通道进行消费

  1. /*
  2. 2-7 多路通道
  3. 在真实的世界中,还有一种消息传递场景,那就是消费者有多个消费来源,只要有一个来源生产了数据,
  4. 消费者就可以读这个数据进行消费。这时候可以将多个来源通道的数据汇聚到目标通道,然后统一在目标通道进行消费。
  5. */
  6. package main
  7. import (
  8. "fmt"
  9. "time"
  10. )
  11. // 每隔一会生产一个数
  12. func send(ch chan int, gap time.Duration) {
  13. i := 0
  14. for {
  15. i++
  16. ch <- i
  17. time.Sleep(gap)
  18. }
  19. }
  20. // 将多个原通道内容拷贝到单一的目标通道
  21. func collect(source chan int, target chan int) {
  22. for v := range source {
  23. target <- v // ch3 <- ch2 ; ch3 <- ch1
  24. }
  25. }
  26. func collect2(ch1 chan int, ch2 chan int, target chan int) {
  27. for {
  28. select {
  29. case v := <-ch1:
  30. target <- v
  31. case v := <-ch2:
  32. target <- v
  33. default: // 非阻塞
  34. fmt.Println("collect2")
  35. }
  36. }
  37. }
  38. // 从目标通道消费数据
  39. func recv(ch chan int) {
  40. for v := range ch {
  41. fmt.Printf("receive %d\n", v)
  42. }
  43. }
  44. func main() {
  45. var ch1 = make(chan int)
  46. var ch2 = make(chan int)
  47. var ch3 = make(chan int)
  48. go send(ch1, time.Second)
  49. go send(ch2, 2*time.Second)
  50. // go collect(ch1, ch3)
  51. // go collect(ch2, ch3)
  52. go collect2(ch1, ch2, ch3)
  53. recv(ch3)
  54. }

2-8 多路复用select

  1. // 2-8 多路复用select
  2. package main
  3. import (
  4. "fmt"
  5. "time"
  6. )
  7. func send(ch chan int, gap time.Duration) {
  8. i := 0
  9. for {
  10. i++
  11. ch <- i
  12. time.Sleep(gap)
  13. }
  14. }
  15. func recv(ch1 chan int, ch2 chan int) {
  16. for {
  17. select {
  18. case v := <-ch1:
  19. fmt.Printf("recv %d from ch1\n", v)
  20. case v := <-ch2:
  21. fmt.Printf("recv %d from ch2\n", v)
  22. }
  23. }
  24. }
  25. func main() {
  26. var ch1 = make(chan int)
  27. var ch2 = make(chan int)
  28. go send(ch1, time.Second)
  29. go send(ch2, 2*time.Second)
  30. recv(ch1, ch2)
  31. }

2-9 非阻塞读写

通道的非阻塞读写。当通道空时,读操作不会阻塞,当通道满时,写操作也不会阻塞。非

阻塞读写需要依靠 select 语句的 default 分支。当 select 语句所有通道都不可读写时,如果

定义了 default 分支,那就会执行 default 分支逻辑,这样就起到了不阻塞的效果。

  1. // 2-9 非阻塞读写
  2. package main
  3. import (
  4. "fmt"
  5. "time"
  6. )
  7. func send(ch1 chan int, ch2 chan int) {
  8. i := 0
  9. for {
  10. i++
  11. select {
  12. case ch1 <- i:
  13. fmt.Printf("send ch1 %d\n", i)
  14. case ch2 <- i:
  15. fmt.Printf("send ch2 %d\n", i)
  16. default:
  17. fmt.Printf("ch block\n")
  18. time.Sleep(2 * time.Second) // 这里只是为了演示
  19. }
  20. }
  21. }
  22. func recv(ch chan int, gap time.Duration, name string) {
  23. for v := range ch {
  24. fmt.Printf("receive %s %d\n", name, v)
  25. time.Sleep(gap)
  26. }
  27. }
  28. func main() {
  29. // 无缓冲通道
  30. var ch1 = make(chan int)
  31. var ch2 = make(chan int)
  32. // 两个消费者的休眠时间不一样,名称不一样
  33. go recv(ch1, time.Second, "ch1")
  34. go recv(ch2, 2*time.Second, "ch2")
  35. send(ch1, ch2)
  36. }

2-10 生产者、消费者模型

生产者消费模型

  1. // 2-10 生产者、消费者模型
  2. package main
  3. import (
  4. "fmt"
  5. "os"
  6. "os/signal"
  7. "syscall"
  8. "time"
  9. )
  10. // 生产者
  11. func Producer(factor int, out chan<- int) {
  12. for i := 0; ; i++ {
  13. out <- i * factor
  14. time.Sleep(5 * time.Second)
  15. }
  16. }
  17. // 消费者
  18. func Consumer(in <-chan int) {
  19. for v := range in {
  20. fmt.Println(v)
  21. }
  22. }
  23. func main() {
  24. ch := make(chan int, 64)
  25. go Producer(3, ch) // 生成3的倍数序列
  26. go Producer(5, ch) // 生成5的倍数序列
  27. go Consumer(ch)
  28. //Ctrl +C 退出
  29. sig := make(chan os.Signal, 1)
  30. signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
  31. fmt.Printf("wait Ctrl +C")
  32. fmt.Printf("quit (%v)\n", <-sig)
  33. }

3. 线程安全

3-1 线程安全-互斥锁

竞态检查工具是基于运行时代码检查,而不是通过代码静态分析来完成的。这意味着那些没

有机会运行到的代码逻辑中如果存在安全隐患,它是检查不出来的。

需要加上-race 执行

  1. package main
  2. import "fmt"
  3. // go多协程 是有竞态,不像以前的ntyco,libco没有竞态的
  4. func write(d map[string]int) {
  5. d["fruit"] = 2
  6. }
  7. func read(d map[string]int) {
  8. fmt.Println(d["fruit"])
  9. }
  10. // go run -race 3-1-unsafe.go
  11. func main() {
  12. d := map[string]int{}
  13. go read(d)
  14. write(d)
  15. }

3-2 避免锁复制

sync.Mutex 是一个结构体对象,这个对象在使用的过程中要避免被复制 —— 浅拷贝。复制会导致

锁被「分裂」了,也就起不到保护的作用。所以在平时的使用中要尽量使用它的指针类型。读者可

以尝试将上面的类型换成非指针类型,然后运行一下竞态检查工具,会看到警告信息再次布满整个

屏幕。锁复制存在于结构体变量的赋值、函数参数传递、方法参数传递中,都需要注意。

  1. package main
  2. import (
  3. "fmt"
  4. "sync"
  5. )
  6. type SafeDict struct {
  7. data map[string]int
  8. mutex *sync.Mutex
  9. }
  10. func NewSafeDict(data map[string]int) *SafeDict {
  11. return &SafeDict{
  12. data: data,
  13. mutex: &sync.Mutex{},
  14. }
  15. }
  16. // defer 语句总是要推迟到函数尾部运行,所以如果函数逻辑运行时间比较长,
  17. // 这会导致锁持有的时间较长,这时使用 defer 语句来释放锁未必是一个好注意。
  18. func (d *SafeDict) Len() int {
  19. d.mutex.Lock()
  20. defer d.mutex.Unlock()
  21. return len(d.data)
  22. }
  23. // func (d *SafeDict) Test() int {
  24. // d.mutex.Lock()
  25. // length := len(d.data)
  26. // d.mutex.Unlock() // 手动解锁 减少粒度 // 这种情况就不要用 defer d.mutex.Unlock()
  27. // fmt.Println("length: ", length)
  28. // // 这里还有耗时处理 耗时1000ms
  29. // }
  30. func (d *SafeDict) Put(key string, value int) (int, bool) {
  31. d.mutex.Lock()
  32. defer d.mutex.Unlock()
  33. old_value, ok := d.data[key]
  34. d.data[key] = value
  35. return old_value, ok
  36. }
  37. func (d *SafeDict) Get(key string) (int, bool) {
  38. d.mutex.Lock()
  39. defer d.mutex.Unlock()
  40. old_value, ok := d.data[key]
  41. return old_value, ok
  42. }
  43. func (d *SafeDict) Delete(key string) (int, bool) {
  44. d.mutex.Lock()
  45. defer d.mutex.Unlock()
  46. old_value, ok := d.data[key]
  47. if ok {
  48. delete(d.data, key)
  49. }
  50. return old_value, ok
  51. }
  52. func write(d *SafeDict) {
  53. d.Put("banana", 5)
  54. }
  55. func read(d *SafeDict) {
  56. fmt.Println(d.Get("banana"))
  57. }
  58. // go run -race 3-2-lock.go
  59. func main() {
  60. d := NewSafeDict(map[string]int{
  61. "apple": 2,
  62. "pear": 3,
  63. })
  64. go read(d)
  65. write(d)
  66. }

3-3 使用匿名锁字段

在结构体章节,我们知道外部结构体可以自动继承匿名内部结构体的所有方法。如果将上面的

SafeDict 结构体进行改造,将锁字段匿名,就可以稍微简化一下代码。

  1. package main
  2. import (
  3. "fmt"
  4. "sync"
  5. )
  6. type SafeDict struct {
  7. data map[string]int
  8. *sync.Mutex
  9. }
  10. func NewSafeDict(data map[string]int) *SafeDict {
  11. return &SafeDict{
  12. data,
  13. &sync.Mutex{}, // 一样是要初始化的
  14. }
  15. }
  16. func (d *SafeDict) Len() int {
  17. d.Lock()
  18. defer d.Unlock()
  19. return len(d.data)
  20. }
  21. func (d *SafeDict) Put(key string, value int) (int, bool) {
  22. d.Lock()
  23. defer d.Unlock()
  24. old_value, ok := d.data[key]
  25. d.data[key] = value
  26. return old_value, ok
  27. }
  28. func (d *SafeDict) Get(key string) (int, bool) {
  29. d.Lock()
  30. defer d.Unlock()
  31. old_value, ok := d.data[key]
  32. return old_value, ok
  33. }
  34. func (d *SafeDict) Delete(key string) (int, bool) {
  35. d.Lock()
  36. defer d.Unlock()
  37. old_value, ok := d.data[key]
  38. if ok {
  39. delete(d.data, key)
  40. }
  41. return old_value, ok
  42. }
  43. func write(d *SafeDict) {
  44. d.Put("banana", 5)
  45. }
  46. func read(d *SafeDict) {
  47. fmt.Println(d.Get("banana"))
  48. }
  49. func main() {
  50. d := NewSafeDict(map[string]int{
  51. "apple": 2,
  52. "pear": 3,
  53. })
  54. go read(d)
  55. write(d)
  56. }

3-4 使用读写锁

日常应用中,大多数并发数据结构都是读多写少的,对于读多写少的场合,可以将互斥锁换

成读写锁,可以有效提升性能。sync 包也提供了读写锁对象 RWMutex,不同于互斥锁只有两

个常用方法 Lock() 和 Unlock(),读写锁提供了四个常用方法,分别是写加锁 Lock()、写释放锁

Unlock()、读加锁 RLock() 和读释放锁 RUnlock()。写锁是排他锁,加写锁时会阻塞其它协程再

加读锁和写锁,读锁是共享锁,加读锁还可以允许其它协程再加读锁,但是会阻塞加写锁。

  1. // 3-4 使用读写锁
  2. package main
  3. import (
  4. "fmt"
  5. "sync"
  6. )
  7. type SafeDict struct {
  8. data map[string]int
  9. *sync.RWMutex // sync.Mutex API也有点不一样
  10. }
  11. func NewSafeDict(data map[string]int) *SafeDict {
  12. return &SafeDict{data, &sync.RWMutex{}}
  13. }
  14. func (d *SafeDict) Len() int {
  15. d.RLock()
  16. defer d.RUnlock()
  17. return len(d.data)
  18. }
  19. func (d *SafeDict) Put(key string, value int) (int, bool) {
  20. d.Lock()
  21. defer d.Unlock()
  22. old_value, ok := d.data[key]
  23. d.data[key] = value
  24. return old_value, ok
  25. }
  26. func (d *SafeDict) Get(key string) (int, bool) {
  27. d.RLock()
  28. defer d.RUnlock()
  29. old_value, ok := d.data[key]
  30. return old_value, ok
  31. }
  32. func (d *SafeDict) Delete(key string) (int, bool) {
  33. d.Lock()
  34. defer d.Unlock()
  35. old_value, ok := d.data[key]
  36. if ok {
  37. delete(d.data, key)
  38. }
  39. return old_value, ok
  40. }
  41. func write(d *SafeDict) {
  42. d.Put("banana", 5)
  43. }
  44. func read(d *SafeDict) {
  45. fmt.Println(d.Get("banana"))
  46. }
  47. func main() {
  48. d := NewSafeDict(map[string]int{
  49. "apple": 2,
  50. "pear": 3,
  51. })
  52. go read(d)
  53. write(d)
  54. }

3.5 发布订阅模型

综合前面学的

支持过滤器设置主题

  1. // 3.5 发布订阅模型
  2. package main
  3. import (
  4. "fmt"
  5. "strings"
  6. "sync"
  7. "time"
  8. )
  9. type (
  10. subscriber chan interface{} // 订阅者为一个通道
  11. topicFunc func(v interface{}) bool // 主题为一个过滤器
  12. )
  13. // 发布者对象
  14. type Publisher struct {
  15. m sync.RWMutex //读写锁
  16. buffer int // 订阅队列的缓存大小
  17. timeout time.Duration // 发布超时时间
  18. subscribers map[subscriber]topicFunc // 订阅者信息
  19. }
  20. // 构建一个发布者对象,可以设置发布超时时间和缓存队列的长度
  21. func NewPublisher(publishTimeout time.Duration, buffer int) *Publisher {
  22. return &Publisher{
  23. buffer: buffer,
  24. timeout: publishTimeout,
  25. subscribers: make(map[subscriber]topicFunc),
  26. }
  27. }
  28. // 关闭发布者对象,同时关闭所有的订阅通道
  29. func (p *Publisher) Close() {
  30. p.m.Lock()
  31. defer p.m.Unlock()
  32. for sub := range p.subscribers {
  33. delete(p.subscribers, sub)
  34. close(sub)
  35. }
  36. }
  37. // 添加一个新的订阅者,订阅过滤器筛选后的主题
  38. func (p *Publisher) SubscribeTopic(topic topicFunc) chan interface{} {
  39. ch := make(chan interface{}, p.buffer)
  40. p.m.Lock()
  41. p.subscribers[ch] = topic
  42. p.m.Unlock()
  43. return ch
  44. }
  45. // 添加一个新的订阅者,订阅全部主题
  46. func (p *Publisher) Subscribe() chan interface{} {
  47. return p.SubscribeTopic(nil)
  48. }
  49. // 退出订阅
  50. func (p *Publisher) Evict(sub chan interface{}) {
  51. p.m.Lock()
  52. defer p.m.Unlock()
  53. delete(p.subscribers, sub)
  54. close(sub)
  55. }
  56. // 发送主题,可以容忍一定的超时
  57. func (p *Publisher) sendTopic(
  58. sub subscriber, topic topicFunc, v interface{}, wg *sync.WaitGroup,
  59. ) {
  60. defer wg.Done()
  61. if topic != nil && !topic(v) { // 过滤信息
  62. return
  63. }
  64. select {
  65. case sub <- v:
  66. case <-time.After(p.timeout): // 超时
  67. }
  68. }
  69. // 发布一个主题
  70. func (p *Publisher) Publish(v interface{}) {
  71. p.m.Lock()
  72. defer p.m.Unlock()
  73. var wg sync.WaitGroup
  74. for sub, topic := range p.subscribers {
  75. wg.Add(1)
  76. go p.sendTopic(sub, topic, v, &wg)
  77. }
  78. wg.Wait()
  79. }
  80. func main() {
  81. p := NewPublisher(100*time.Millisecond, 10)
  82. defer p.Close()
  83. all := p.Subscribe()
  84. golang := p.SubscribeTopic(func(v interface{}) bool {
  85. if s, ok := v.(string); ok {
  86. return strings.Contains(s, "golang")
  87. }
  88. return false
  89. })
  90. p.Publish("hello world")
  91. p.Publish("hello, golang")
  92. go func() {
  93. for msg := range all {
  94. fmt.Println("all:", msg)
  95. }
  96. }()
  97. go func() {
  98. for msg := range golang {
  99. fmt.Println("golang:", msg)
  100. }
  101. }()
  102. // 运行一段时间后退出
  103. time.Sleep(3 * time.Second)
  104. }

3.6 sync.Once初始化

sync.Once.Do(f func())是一个挺有趣的东西,能保证once只执行一次,无论你是否更换once.Do(xx)

这里的方法,这个sync.Once块只会执行一次。

  1. package main
  2. import (
  3. "fmt"
  4. "sync"
  5. "time"
  6. )
  7. var once sync.Once
  8. func main() {
  9. for i, v := range make([]string, 10) {
  10. once.Do(onces)
  11. fmt.Println("count:", v, "---", i)
  12. }
  13. for i := 0; i < 5; i++ {
  14. go func() {
  15. once.Do(onced)
  16. fmt.Println("213")
  17. }()
  18. }
  19. time.Sleep(4000)
  20. }
  21. func onces() {
  22. fmt.Println("执行onces")
  23. }
  24. func onced() {
  25. fmt.Println("执行onced")
  26. }

4. context

4 Go语言Context

为什么需要 Context

•每一个处理都应该有个超时限制

•需要在调用中传递这个超时

• 比如开始处理请求的时候我们说是 3 秒钟超时

• 那么在函数调用中间,这个超时还剩多少时间了?

• 需要在什么地方存储这个信息,这样请求处理中间

可以停止

Context是协程安全的。代码中可以将单个Context传递给任意数量的goroutine,并在取

消该Context时可以将信号传递给所有的goroutine。

4.1 Context接口

type Context interface {

Deadline() (deadline time.Time, ok bool)

Done() <-chan struct{}

Err() error

Value(key interface{}) interface{}

}

◼ Deadline方法是获取设置的截止时间的意思,第一个返回式是截止时间,到了这个时间点,Context

会自动发起取消请求;第二个返回值ok==false时表示没有设置截止时间,如果需要取消的话,需要调用取消函数进行取消

◼ Done方法返回一个只读的chan,类型为struct{},我们在goroutine中,如果该方法返回的chan可以

读取,则意味着parent context已经发起了取消请求,我们通过Done方法收到这个信号后,就应该

做清理操作,然后退出goroutine,释放资源

◼ Err方法返回取消的错误原因,因为什么Context被取消。

◼ Value方法获取该Context上绑定的值,是一个键值对,所以要通过一个Key才可以获取对应的值,这个值一般是线程安全的

  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "time"
  6. )
  7. func main() {
  8. ctx, cancel1 := context.WithCancel(context.Background())
  9. go func(ctx context.Context) {
  10. for {
  11. select {
  12. case v := <-ctx.Done():
  13. fmt.Println("监控退出,停止了..., v: ", v, ", err:", ctx.Err())
  14. return
  15. default:
  16. time.Sleep(2 * time.Second)
  17. fmt.Println("goroutine监控中...")
  18. // time.Sleep(2 * time.Second)
  19. }
  20. }
  21. }(ctx)
  22. time.Sleep(5 * time.Second)
  23. fmt.Println("可以了,通知监控停止")
  24. cancel1()
  25. //为了检测监控过是否停止,如果没有监控输出,就表示停止了
  26. time.Sleep(5 * time.Second)
  27. }

4.1 Background()和TODO()

◼ Go语言内置两个函数:Background() 和 TODO(),这两个函数分别返回一个实现了 Context 接口的background 和 todo。

◼ Background() 主要用于 main 函数、初始化以及测试代码中,作为 Context 这个树结构的最顶层的

Context,也就是根 Context。

◼ TODO(),它目前还不知道具体的使用场景,在不知道该使用什么 Context 的时候,可以使用这个。

◼ background 和 todo 本质上都是 emptyCtx 结构体类型,是一个不可取消,没有设置截止时间,没有携带任何值的 Context。

  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "time"
  6. )
  7. func main() {
  8. // ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
  9. ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(3*time.Second))
  10. // var wg sync.WaitGroup
  11. go func(ctx context.Context) {
  12. // wg.Add(1)
  13. // defer wg.Done()
  14. for {
  15. select {
  16. case <-ctx.Done():
  17. fmt.Println("监控退出,停止了..., err:", ctx.Err())
  18. return
  19. default:
  20. time.Sleep(2 * time.Second)
  21. fmt.Println("goroutine监控中...")
  22. // time.Sleep(2 * time.Second)
  23. }
  24. }
  25. }(ctx)
  26. // cancel()
  27. time.Sleep(5 * time.Second)
  28. fmt.Println("可以了,通知监控停止")
  29. cancel()
  30. // wg.Wait() // 等待协程退出
  31. //为了检测监控过是否停止,如果没有监控输出,就表示停止了
  32. time.Sleep(5 * time.Second)
  33. }

4.2 Context的继承衍生

func WithCancel(parent Context) (ctx Context, cancel CancelFunc)

func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)

func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)

func WithValue(parent Context, key interface{}, val interface{}) Context

四个With函数,接收的都有一个partent参数,就是父Context,我们要基于这个父Context创建出子

Context的意思

◼ WithCancel函数,传递一个父Context作为参数,返回子Context,以及一个取消函数用来取消Context

◼ WithDeadline函数,和WithCancel差不多,它会多传递一个截止时间参数,意味着到了这个时间点,会自动取消

Context,当然我们也可以不等到这个时候,可以提前通过取消函数进行取消

◼ WithTimeout和WithDeadline基本上一样,这个表示是超时自动取消,是多少时间后自动取消Context的意思,只是传参数不一样。

◼ WithValue函数和取消Context无关,它是为了生成一个绑定了一个键值对数据的Context,这个绑定的数据可以通过Context.Value方法访问到

  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "sync"
  6. "time"
  7. )
  8. func work(ctx context.Context, wg *sync.WaitGroup) {
  9. defer wg.Done()
  10. for {
  11. select {
  12. case <-ctx.Done():
  13. fmt.Println("监控退出,停止了...")
  14. return
  15. default:
  16. fmt.Println("hello")
  17. time.Sleep(time.Second)
  18. }
  19. }
  20. }
  21. func main() {
  22. ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
  23. defer cancel() // 在建立之后,立即 defer cancel() 是一个好习惯。
  24. var wg sync.WaitGroup
  25. for i := 0; i < 10; i++ {
  26. wg.Add(1)
  27. go work(ctx, &wg)
  28. }
  29. time.Sleep(time.Second)
  30. wg.Wait()
  31. }

4.3 Context使用原则

◼ 不要把Context放在结构体中,要以参数的方式进行传递

◼ 以 Context 作为参数的函数方法,应该把 Context 作为第一个参数

◼ 给一个函数方法传递Context的时候,不要传递nil,如果不知道传递什么,就使用

context.TODO

◼ Context 的 Value 相关方法应该传递请求域的必要数据,不应该用于传递可选参数;

◼ Context 是线程安全的,可以放心的在多个 Goroutine 中传递。

  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "time"
  6. )
  7. var key string = "name"
  8. var key2 string = "name1"
  9. func main() {
  10. ctx, cancel := context.WithCancel(context.Background())
  11. //附加值
  12. valueCtx := context.WithValue(ctx, key, "key【监控1】") // 是否可以有多个key
  13. valueCtx2 := context.WithValue(valueCtx, key2, "key【监控2】")
  14. go watch(valueCtx2)
  15. time.Sleep(5 * time.Second)
  16. fmt.Println("可以了,通知监控停止")
  17. cancel()
  18. //为了检测监控过是否停止,如果没有监控输出,就表示停止了
  19. time.Sleep(5 * time.Second)
  20. }
  21. func watch(ctx context.Context) {
  22. for {
  23. select {
  24. case <-ctx.Done():
  25. //取出值
  26. fmt.Println(ctx.Value(key), "监控退出,停止了...")
  27. fmt.Println(ctx.Value(key2), "监控退出,停止了...")
  28. return
  29. default:
  30. //取出值
  31. fmt.Println(ctx.Value(key), "goroutine监控中...")
  32. time.Sleep(2 * time.Second)
  33. }
  34. }
  35. }

4.4 Derived contexts派生上下文

Context包提供了从现有Context值派生新Context值的函数。这些值形成一个树:当一个

Context被取消时,从它派生的所有Context也被取消。

  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "time"
  6. )
  7. func work(ctx context.Context, str string) {
  8. for {
  9. select {
  10. case <-ctx.Done():
  11. fmt.Println("退出 ", str)
  12. return
  13. }
  14. }
  15. }
  16. func main() {
  17. ctx1 := context.Background()
  18. ctx2, cancel2 := context.WithCancel(ctx1)
  19. ctx3, cancel3 := context.WithTimeout(ctx2, time.Second*5)
  20. ctx4, cancel4 := context.WithTimeout(ctx3, time.Second*3)
  21. ctx5, cancel5 := context.WithTimeout(ctx4, time.Second*6)
  22. ctx6 := context.WithValue(ctx5, "userID", 12)
  23. go work(ctx1, "ctx1")
  24. go work(ctx2, "ctx2")
  25. go work(ctx3, "ctx3")
  26. go work(ctx4, "ctx4")
  27. go work(ctx5, "ctx5")
  28. go work(ctx6, "ctx6")
  29. time.Sleep(1 * time.Second)
  30. cancel5()
  31. time.Sleep(5 * time.Second)
  32. cancel3()
  33. cancel4()
  34. cancel5()
  35. cancel2()
  36. }

推荐教程

https://geektutu.com/post/geecache-day1.html

Golang 如何正确使用 Context

https://studygolang.com/articles/23247?fr=sidebar 

cgo go和c混编

  1. #include <stdio.h>
  2. #include <string.h>
  3. char *fun(char *p1, char *p2)
  4. {
  5. int i = 0;
  6. i = strcmp(p1, p2);
  7. if (0 == i)
  8. {
  9. return (p1);
  10. }
  11. else
  12. {
  13. return (p2);
  14. }
  15. }
  16. int main()
  17. {
  18. char *(*pf)(char *p1, char *p2);
  19. pf = &fun;
  20. (*pf)("aa", "bb");
  21. return (0);
  22. }

2.1 Go语言网络编程和Redis实战

Go语言网络编程和常用库使用

1. 网络编程

目前主流服务器一般均采用的都是”Non-Block + I/O多路复用”(有的也结合了多线

程、多进程)。不过I/O多路复用也给使用者带来了不小的复杂度,以至于后续出

现了许多高性能的I/O多路复用框架, 比如libevent、libev、libuv等,以帮助开发者

简化开发复杂性,降低心智负担。不过Go的设计者似乎认为I/O多路复用的这种通

过回调机制割裂控制流 的方式依旧复杂,且有悖于“一般逻辑”设计,为此Go语言

将该“复杂性”隐藏在Runtime中了:Go开发者无需关注socket是否是 non-block的,

也无需亲自注册文件描述符的回调,只需在每个连接对应的goroutine中以“block

I/O”的方式对待socket处理即可

  1. package main
  2. import (
  3. "bufio"
  4. "fmt"
  5. "net"
  6. "os"
  7. "strings"
  8. )
  9. func main() {
  10. //设置连接模式 , ip和端口号
  11. conn, err := net.Dial("tcp", "127.0.0.1:8888")
  12. if err != nil {
  13. fmt.Println("client dial err=", err)
  14. return
  15. }
  16. defer conn.Close()
  17. // 在命令行输入单行数据
  18. reader := bufio.NewReader(os.Stdin)
  19. for {
  20. //从终端读取一行用户的输入,并发给服务器
  21. line, err := reader.ReadString('\n')
  22. if err != nil {
  23. fmt.Println("readString err=", err)
  24. }
  25. //去掉输入后的换行符
  26. line = strings.Trim(line, "\r\n")
  27. //如果是exit,则退出客户端
  28. if line == "exit" {
  29. fmt.Println("客户端退出了")
  30. break
  31. }
  32. //将line发送给服务器
  33. n, e := conn.Write([]byte(line))
  34. if e != nil {
  35. fmt.Println("conn.write err=", e)
  36. }
  37. fmt.Printf("客户端发送了%d字节的数据\n", n)
  38. }
  39. }
  1. package main
  2. import (
  3. "fmt"
  4. "net"
  5. _ "time"
  6. )
  7. func process(conn net.Conn) {
  8. //这里接受客户端的数据
  9. defer conn.Close()
  10. for {
  11. //创建一个新的切片
  12. buf := make([]byte, 1024)
  13. //等待客户端发送信息,如果客户端没发送,协程就阻塞在这
  14. // fmt.Printf("服务器在等待客户端%v的输入\n", conn.RemoteAddr().String())
  15. // conn.SetReadDeadline(time.Now().Add(time.Duration(1) * time.Second))
  16. n, err := conn.Read(buf) // 默认是阻塞的
  17. if err != nil {
  18. fmt.Println("服务器read err=", err)
  19. fmt.Println("客户端退出了")
  20. return
  21. }
  22. //显示客户端发送内容到服务器的终端
  23. fmt.Print(string(buf[:n]) + "\n")
  24. }
  25. }
  26. func main() {
  27. fmt.Println("服务器开始监听...")
  28. //协议、端口
  29. listen, err := net.Listen("tcp", "0.0.0.0:8888")
  30. if err != nil {
  31. fmt.Println("监听失败,err=", err)
  32. return
  33. }
  34. //延时关闭
  35. defer listen.Close() // 函数退出的时候调用
  36. for {
  37. //循环等待客户端连接
  38. fmt.Println("等待客户端连接...")
  39. conn, err := listen.Accept()
  40. if err != nil {
  41. fmt.Println("Accept() err=", err)
  42. } else {
  43. fmt.Printf("Accept() suc con=%v,客户端Ip=%v\n", conn, conn.RemoteAddr().String())
  44. }
  45. //这里准备起个协程为客户端服务
  46. go process(conn)
  47. }
  48. //fmt.Printf("监听成功,suv=%v\n", listen)
  49. }

1.0 TCP socket api

•Read(): 从连接上读取数据。

•Write(): 向连接上写入数据。

•Close(): 关闭连接。

•LocalAddr(): 返回本地网络地址。

•RemoteAddr(): 返回远程网络地址。

•SetDeadline(): 设置连接相关的读写最后期限。等价于同时

调用SetReadDeadline()和SetWriteDeadline()。

•SetReadDeadline(): 设置将来的读调用和当前阻塞的读调用

的超时最后期限。

•SetWriteDeadline(): 设置将来写调用以及当前阻塞的写调用

的超时最后期限。

1.1 TCP连接的建立

服务端是一个标准的Listen + Accept的结构(可参考上面的代码),而在客户端Go语言使用net.Dial()或net.DialTimeout()进行连接建立。

服务端

参考上一页

客户端

阻塞Dial: 超时机制的Dial:

1.2 客户端连接异常情况分析

1、网络不可达或对方服务未启动

2、对方服务的listen backlog满

3、网络延迟较大,Dial阻塞并超时

1.2.1 客户端连接异常-网络不可达或对方服务未启动

如果传给Dial的Addr是可以立即判断出网络不可达,或者Addr中端口对应的服务没有启动,

端口未被监听,Dial会几乎立即返回错误,比如:

  1. package main
  2. import (
  3. "log"
  4. "net"
  5. )
  6. func main() {
  7. log.Println("begin dial...")
  8. conn, err := net.Dial("tcp", ":8888")
  9. if err != nil {
  10. log.Println("dial error:", err)
  11. return
  12. }
  13. defer conn.Close()
  14. log.Println("dial ok")
  15. }

1.2.2 客户端连接异常-对方服务的listen backlog满

对方服务器很忙,瞬间有大量client端连接尝试向server建立,server端的listen backlog队列满,

server accept不及时((即便不accept,那么在backlog数量范畴里面,connect都会是成功的,因

为new conn已经加入到server side的listen queue中了,accept只是从queue中取出一个conn而

已),这将导致client端Dial阻塞。

  1. package main
  2. import (
  3. "log"
  4. "net"
  5. "time"
  6. )
  7. func establishConn(i int) net.Conn {
  8. conn, err := net.Dial("tcp", ":8888")
  9. if err != nil {
  10. log.Printf("%d: dial error: %s", i, err)
  11. return nil
  12. }
  13. log.Println(i, ":connect to server ok")
  14. return conn
  15. }
  16. func main() {
  17. var sl []net.Conn
  18. for i := 1; i < 1000; i++ {
  19. conn := establishConn(i)
  20. if conn != nil {
  21. sl = append(sl, conn)
  22. }
  23. }
  24. time.Sleep(time.Second * 10000)
  25. }

1.2.3 客户端连接异常-网络延迟较大,Dial阻塞并超时

如果网络延迟较大,TCP握手过程将更加艰难坎坷(各种丢包),时间消耗的自然也会更长。Dial这

时会阻塞,如果长时间依旧无法建立连接,则Dial也会返回“ getsockopt: operation timed out”错误

在连接建立阶段,多数情况下,Dial是可以满足需求的,即便阻塞一小会儿。但对于某些程序而言,

需要有严格的连接时间限定,如果一定时间内没能成功建立连接,程序可能会需要执行一段“异常”处

理逻辑,为此我们就需要DialTimeout了。

执行结果如下,需要模拟一个网络延迟大的环境

  1. package main
  2. import (
  3. "log"
  4. "net"
  5. "time"
  6. )
  7. func main() {
  8. log.Println("begin dial...")
  9. conn, err := net.DialTimeout("tcp", "192.168.204.130:8888", 2*time.Second)
  10. if err != nil {
  11. log.Println("dial error:", err)
  12. return
  13. }
  14. defer conn.Close()
  15. log.Println("dial ok")
  16. }

 1.3 Socket读写

Dial成功后,方法返回一个net.Conn接口类型变量值,这个接口变量的动态类型为一个

*TCPConn:

1.3.1 conn.Read的行为特点

1 Socket中无数据

连接建立后,如果对方未发送数据到socket,接收方(Server)会阻塞在Read操作上,这和前面提到的“模型”原理是一致的。

执行该Read操作的goroutine也会被挂起。runtime会监视该socket,直到其有数据才会重新

调度该socket对应的Goroutine完成read。

2 Socket中有部分数据

如果socket中有部分数据,且长度小于一次Read操作所期望读出的数据长度,那么Read将会成功读出这部分数据并返回,而不是等

待所有期望数据全部读取后再返回。

3 Socket中有足够数据

如果socket中有数据,且长度大于等于一次Read操作所期望读出的数据长度,那么Read将会成功读出这部分数据并返回。这个

情景是最符合我们对Read的期待的了:Read将用Socket中的数据将我们传入的slice填满后返回:n = 10, err = nil

4 Socket关闭

有数据关闭是指在client关闭时,socket中还有server端未读取的数据。当client端close socket退出后,server依旧没有开始Read,

10s后第一次Read成功读出了所有的数据,当第二次Read时,由于client端 socket关闭,Read返回EOF error

无数据关闭情形下的结果,那就是Read直接返回EOF error

5 读取操作超时

有些场合对Read的阻塞时间有严格限制,在这种情况下,Read的行为到底是什么样的呢?在返回超时错误时,是否也同时Read了

一部分数据了呢?

不会出现“读出部分数据且返回超时错误”的情况

1.3.2 conn.Write的行为特点

1 成功写

前面例子着重于Read,client端在Write时并未判断Write的返回值。所谓“成功写”指的就是Write调用返回的n

与预期要写入的数据长度相等,且error = nil。这是我们在调用Write时遇到的最常见的情形,这里不再举例了

2 写阻塞

TCP连接通信两端的OS都会为该连接保留数据缓冲,一端调用Write后,实际上数据是写入到OS的协议栈的

数据缓冲的。TCP是全双工通信,因此每个方向都有独立的数据缓冲。当发送方将对方的接收缓冲区以及自

身的发送缓冲区写满后,Write就会阻塞

3 写入部分数据

Write操作存在写入部分数据的情况。没有按照预期的写入所有数据。这时候循环写入便是

综上例子,虽然Go给我们提供了阻塞I/O的便利,但在调用Read和Write时依旧要综合需要方法返回的n和err

的结果,以做出正确处理。net.conn实现了io.Reader和io.Writer接口,因此可以试用一些wrapper包进行

socket读写,比如bufio包下面的Writer和Reader、io/ioutil下的函数等

1.4 Goroutine safe

基于goroutine的网络架构模型,存在在不同goroutine间共享conn的情况,那么conn的读写是

否是goroutine safe的呢。

Write

Read 内部是goroutine安全的,内部都有Lock保护

1.5 Socket属性

SetKeepAlive

SetKeepAlivePeriod

SetLinger

SetNoDelay (默认no delay)

SetWriteBuffer

SetReadBuffer

要使用上面的Method的,需要type assertion

tcpConn, ok := conn.(*TCPConn)

if !ok { //error handle }

tcpConn.SetNoDelay(true)

1.6 关闭连接

socket是全双工的,client和server端在己方已关闭的socket和对方关闭的socket上操作的

结果有不同。

  1. package main
  2. import (
  3. "log"
  4. "net"
  5. "time"
  6. )
  7. func main() {
  8. log.Println("begin dial...")
  9. conn, err := net.Dial("tcp", ":8888")
  10. if err != nil {
  11. log.Println("dial error:", err)
  12. return
  13. }
  14. conn.Close()
  15. log.Println("close ok")
  16. var buf = make([]byte, 32)
  17. n, err := conn.Read(buf)
  18. if err != nil {
  19. log.Println("read error:", err)
  20. } else {
  21. log.Printf("read % bytes, content is %s\n", n, string(buf[:n]))
  22. }
  23. n, err = conn.Write(buf)
  24. if err != nil {
  25. log.Println("write error:", err)
  26. } else {
  27. log.Printf("write % bytes, content is %s\n", n, string(buf[:n]))
  28. }
  29. time.Sleep(time.Second * 1000)
  30. }
  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "net"
  6. )
  7. func handleConn(c net.Conn) {
  8. defer c.Close()
  9. // read from the connection
  10. var buf = make([]byte, 10)
  11. log.Println("start to read from conn")
  12. n, err := c.Read(buf)
  13. if err != nil {
  14. log.Println("conn read error:", err)
  15. } else {
  16. log.Printf("read %d bytes, content is %s\n", n, string(buf[:n]))
  17. }
  18. n, err = c.Write(buf)
  19. if err != nil {
  20. log.Println("conn write error:", err)
  21. } else {
  22. log.Printf("write %d bytes, content is %s\n", n, string(buf[:n]))
  23. }
  24. }
  25. func main() {
  26. listen, err := net.Listen("tcp", ":8888")
  27. if err != nil {
  28. fmt.Println("listen error: ", err)
  29. return
  30. }
  31. for {
  32. conn, err := listen.Accept()
  33. if err != nil {
  34. fmt.Println("accept error: ", err)
  35. break
  36. }
  37. // start a new goroutine to handle the new connection
  38. go handleConn(conn)
  39. }
  40. }

1-7 读写超时

SetDeadline(t time.Time) error 设置读写超时

SetReadDeadline(t time.Time) error 设置读超时

SetWriteDeadline(t time.Time) error 设置写超时

  1. package main
  2. import (
  3. "log"
  4. "net"
  5. "os"
  6. "time"
  7. )
  8. func main() {
  9. connTimeout := 3 * time.Second
  10. conn, err := net.DialTimeout("tcp", "127.0.0.1:8080", connTimeout) // 3s timeout
  11. if err != nil {
  12. log.Println("dial failed:", err)
  13. os.Exit(1)
  14. }
  15. defer conn.Close()
  16. readTimeout := 2 * time.Second
  17. buffer := make([]byte, 512)
  18. for {
  19. err = conn.SetReadDeadline(time.Now().Add(readTimeout)) // timeout
  20. if err != nil {
  21. log.Println("setReadDeadline failed:", err)
  22. }
  23. n, err := conn.Read(buffer)
  24. if err != nil {
  25. log.Println("Read failed:", err)
  26. //break
  27. }
  28. log.Println("count:", n, "msg:", string(buffer))
  29. }
  30. }
  1. package main
  2. import (
  3. "log"
  4. "net"
  5. "time"
  6. )
  7. func main() {
  8. addr := "0.0.0.0:8080"
  9. tcpAddr, err := net.ResolveTCPAddr("tcp", addr)
  10. if err != nil {
  11. log.Fatalf("net.ResovleTCPAddr fail:%s", addr)
  12. }
  13. listener, err := net.ListenTCP("tcp", tcpAddr)
  14. if err != nil {
  15. log.Fatalf("listen %s fail: %s", addr, err)
  16. } else {
  17. log.Println("listening", addr)
  18. }
  19. for {
  20. conn, err := listener.Accept()
  21. if err != nil {
  22. log.Println("listener.Accept error:", err)
  23. continue
  24. }
  25. go handleConnection(conn)
  26. }
  27. }
  28. func handleConnection(conn net.Conn) {
  29. defer conn.Close()
  30. var buffer []byte = []byte("You are welcome. I'm server.")
  31. for {
  32. time.Sleep(3 * time.Second) // sleep 3s
  33. n, err := conn.Write(buffer)
  34. if err != nil {
  35. log.Println("Write error:", err)
  36. break
  37. }
  38. log.Println("send:", n)
  39. }
  40. log.Println("connetion end")
  41. }

2. Redis库redigo

2 redis

参考文档: github.com/garyburd/redigo/redis

https://pkg.go.dev/github.com/garyburd/redigo/redis#pkg-index

使用第三方开源的redis库: github.com/gomodule/redigo/redis

import(

“go get github.com/gomodule/redigo/redis”

)

go get github.com/gomodule/redigo/redis

2.1 连接redis

  1. package main
  2. import (
  3. "fmt"
  4. // "time"
  5. "github.com/gomodule/redigo/redis"
  6. )
  7. func main() {
  8. c, err := redis.Dial("tcp", "192.168.204.132:6379")
  9. c, err := redis.Dial("tcp", "192.168.204.132:6379",
  10. redis.DialConnectTimeout(time.Duration(1) * time.Second),
  11. redis.DialPassword("111"),
  12. redis.DialDatabase(1))
  13. if err != nil {
  14. fmt.Println("conn redis failed,", err)
  15. return
  16. }
  17. defer c.Close()
  18. }

2.2 redis set操作

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/gomodule/redigo/redis"
  5. )
  6. func main() {
  7. c, err := redis.Dial("tcp", "192.168.204.132:6379")
  8. if err != nil {
  9. fmt.Println("conn redis failed,", err)
  10. return
  11. }
  12. defer c.Close()
  13. _, err = c.Do("Set", "count", 100)
  14. if err != nil {
  15. fmt.Println(err)
  16. return
  17. }
  18. r, err := redis.Int(c.Do("Get", "count"))
  19. if err != nil {
  20. fmt.Println("get count failed,", err)
  21. return
  22. }
  23. fmt.Println(r)
  24. }

2.3 redis Hash操作

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/gomodule/redigo/redis"
  5. )
  6. func main() {
  7. c, err := redis.Dial("tcp", "192.168.204.132:6379")
  8. if err != nil {
  9. fmt.Println("conn redis failed,", err)
  10. return
  11. }
  12. defer c.Close()
  13. _, err = c.Do("HSet", "books", "count", 100)
  14. if err != nil {
  15. fmt.Println(err)
  16. return
  17. }
  18. r, err := redis.Int(c.Do("HGet", "books", "count"))
  19. if err != nil {
  20. fmt.Println("get count failed,", err)
  21. return
  22. }
  23. fmt.Println(r)
  24. }

2.4 redis mset操作

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/gomodule/redigo/redis"
  5. )
  6. func main() {
  7. c, err := redis.Dial("tcp", "192.168.204.132:6379")
  8. if err != nil {
  9. fmt.Println("conn redis failed,", err)
  10. return
  11. }
  12. defer c.Close()
  13. _, err = c.Do("MSet", "count", 100, "efg", 300)
  14. if err != nil {
  15. fmt.Println(err)
  16. return
  17. }
  18. r, err := redis.Ints(c.Do("MGet", "count", "efg"))
  19. if err != nil {
  20. fmt.Println("get count failed,", err)
  21. return
  22. }
  23. for _, v := range r {
  24. fmt.Println(v)
  25. }
  26. }

2.5 redis expire操作

2.6 redis list操作

2-7 subpub操作 

  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. red "github.com/gomodule/redigo/redis"
  6. )
  7. type Redis struct {
  8. pool *red.Pool
  9. }
  10. var redis *Redis
  11. func initRedis() {
  12. redis = new(Redis)
  13. redis.pool = &red.Pool{
  14. MaxIdle: 256,
  15. MaxActive: 0,
  16. IdleTimeout: time.Duration(120),
  17. Dial: func() (red.Conn, error) {
  18. return red.Dial(
  19. "tcp",
  20. "127.0.0.1:6379",
  21. red.DialReadTimeout(time.Duration(1000)*time.Millisecond),
  22. red.DialWriteTimeout(time.Duration(1000)*time.Millisecond),
  23. red.DialConnectTimeout(time.Duration(1000)*time.Millisecond),
  24. red.DialDatabase(0),
  25. //red.DialPassword(""),
  26. )
  27. },
  28. }
  29. }
  30. func Exec(cmd string, key interface{}, args ...interface{}) (interface{}, error) {
  31. con := redis.pool.Get()
  32. if err := con.Err(); err != nil {
  33. return nil, err
  34. }
  35. defer con.Close()
  36. parmas := make([]interface{}, 0)
  37. parmas = append(parmas, key)
  38. if len(args) > 0 {
  39. for _, v := range args {
  40. parmas = append(parmas, v)
  41. }
  42. }
  43. return con.Do(cmd, parmas...)
  44. }
  45. func main() {
  46. initRedis()
  47. Exec("set", "hello", "world")
  48. fmt.Print(2)
  49. result, err := Exec("get", "hello")
  50. if err != nil {
  51. fmt.Print(err.Error())
  52. }
  53. str, _ := red.String(result, err)
  54. fmt.Print(str)
  55. }
  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "time"
  6. "github.com/gomodule/redigo/redis"
  7. )
  8. // listenPubSubChannels listens for messages on Redis pubsub channels. The
  9. // onStart function is called after the channels are subscribed. The onMessage
  10. // function is called for each message.
  11. func listenPubSubChannels(ctx context.Context, redisServerAddr string,
  12. onStart func() error,
  13. onMessage func(channel string, data []byte) error,
  14. channels ...string) error {
  15. // A ping is set to the server with this period to test for the health of
  16. // the connection and server.
  17. const healthCheckPeriod = time.Minute
  18. c, err := redis.Dial("tcp", redisServerAddr,
  19. // Read timeout on server should be greater than ping period.
  20. redis.DialReadTimeout(healthCheckPeriod+10*time.Second),
  21. redis.DialWriteTimeout(10*time.Second))
  22. if err != nil {
  23. return err
  24. }
  25. defer c.Close()
  26. psc := redis.PubSubConn{Conn: c}
  27. if err := psc.Subscribe(redis.Args{}.AddFlat(channels)...); err != nil {
  28. return err
  29. }
  30. done := make(chan error, 1)
  31. // Start a goroutine to receive notifications from the server.
  32. go func() {
  33. for {
  34. switch n := psc.Receive().(type) {
  35. case error:
  36. done <- n
  37. return
  38. case redis.Message:
  39. if err := onMessage(n.Channel, n.Data); err != nil {
  40. done <- err
  41. return
  42. }
  43. case redis.Subscription:
  44. switch n.Count {
  45. case len(channels):
  46. // Notify application when all channels are subscribed.
  47. if err := onStart(); err != nil {
  48. done <- err
  49. return
  50. }
  51. case 0:
  52. // Return from the goroutine when all channels are unsubscribed.
  53. done <- nil
  54. return
  55. }
  56. }
  57. }
  58. }()
  59. ticker := time.NewTicker(healthCheckPeriod)
  60. defer ticker.Stop()
  61. loop:
  62. for err == nil {
  63. select {
  64. case <-ticker.C:
  65. // Send ping to test health of connection and server. If
  66. // corresponding pong is not received, then receive on the
  67. // connection will timeout and the receive goroutine will exit.
  68. if err = psc.Ping(""); err != nil {
  69. break loop
  70. }
  71. case <-ctx.Done():
  72. break loop
  73. case err := <-done:
  74. // Return error from the receive goroutine.
  75. return err
  76. }
  77. }
  78. // Signal the receiving goroutine to exit by unsubscribing from all channels.
  79. psc.Unsubscribe()
  80. // Wait for goroutine to complete.
  81. return <-done
  82. }
  83. func publish() {
  84. c, err := dial()
  85. if err != nil {
  86. fmt.Println(err)
  87. return
  88. }
  89. defer c.Close()
  90. c.Do("PUBLISH", "c1", "hello")
  91. c.Do("PUBLISH", "c2", "world")
  92. c.Do("PUBLISH", "c1", "goodbye")
  93. }
  94. // This example shows how receive pubsub notifications with cancelation and
  95. // health checks.
  96. func main() {
  97. redisServerAddr := "192.168.204.132:6379"
  98. ctx, cancel := context.WithCancel(context.Background())
  99. err := listenPubSubChannels(ctx,
  100. redisServerAddr,
  101. func() error {
  102. // The start callback is a good place to backfill missed
  103. // notifications. For the purpose of this example, a goroutine is
  104. // started to send notifications.
  105. go publish()
  106. return nil
  107. },
  108. func(channel string, message []byte) error {
  109. fmt.Printf("channel: %s, message: %s\n", channel, message)
  110. // For the purpose of this example, cancel the listener's context
  111. // after receiving last message sent by publish().
  112. if string(message) == "goodbye" {
  113. cancel()
  114. }
  115. return nil
  116. },
  117. "c1", "c2")
  118. if err != nil {
  119. fmt.Println(err)
  120. return
  121. }
  122. }

2-8 连接池

MaxIdle:最大的空闲连接数,表示即使没有redis连接时依然可以保持N个空闲的连接,而不

被清除,随时处于待命状态。

MaxActive:最大的连接数,表示同时最多有N个连接。0表示不限制。

IdleTimeout:最大的空闲连接等待时间,超过此时间后,空闲连接将被关闭。如果设置成0,

空闲连接将不会被关闭。应该设置一个比redis服务端超时时间更短的时间。

DialConnectTimeout:连接Redis超时时间。

DialReadTimeout:从Redis读取数据超时时间。

DialWriteTimeout:向Redis写入数据超时时间。

连接流程大概是这样的

1.尝试从空闲列表MaxIdle中,获得一个可用连接;如果成功直接返回,失败则尝试步骤2

2.如果当前的MaxIdle < 连接数 < MaxActive,则尝试创建一个新连接,失败则尝试步骤3

3. 如果连接数 > MaxActive就等待,直到满足步骤2的条件,重复步骤2

2-8 redis连接池的坑

  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. red "github.com/gomodule/redigo/redis"
  6. )
  7. type Redis struct {
  8. pool *red.Pool
  9. }
  10. var redis *Redis
  11. func initRedis() {
  12. redis = new(Redis)
  13. redis.pool = &red.Pool{
  14. MaxIdle: 256,
  15. MaxActive: 0,
  16. IdleTimeout: time.Duration(120),
  17. Dial: func() (red.Conn, error) {
  18. return red.Dial(
  19. "tcp",
  20. "127.0.0.1:6379",
  21. red.DialReadTimeout(time.Duration(1000)*time.Millisecond),
  22. red.DialWriteTimeout(time.Duration(1000)*time.Millisecond),
  23. red.DialConnectTimeout(time.Duration(1000)*time.Millisecond),
  24. red.DialDatabase(0),
  25. //red.DialPassword(""),
  26. )
  27. },
  28. }
  29. }
  30. func Exec(cmd string, key interface{}, args ...interface{}) (interface{}, error) {
  31. con := redis.pool.Get()
  32. if err := con.Err(); err != nil {
  33. return nil, err
  34. }
  35. defer con.Close()
  36. parmas := make([]interface{}, 0)
  37. parmas = append(parmas, key)
  38. if len(args) > 0 {
  39. for _, v := range args {
  40. parmas = append(parmas, v)
  41. }
  42. }
  43. return con.Do(cmd, parmas...)
  44. }
  45. func main() {
  46. initRedis()
  47. Exec("set", "hello", "world")
  48. fmt.Print(2)
  49. result, err := Exec("get", "hello")
  50. if err != nil {
  51. fmt.Print(err.Error())
  52. }
  53. str, _ := red.String(result, err)
  54. fmt.Print(str)
  55. }

遇到过的问题

目前为止,连接池的问题只遇到过一次问题,而且是在测试环境的,当时的配置是

DialConnectTimeout:time.Duration(200)*time.Millisecond

DialReadTimeout:time.Duration(200)*time.Millisecond

DialWriteTimeout:time.Duration(200)*time.Millisecond

配置的都是200毫秒。有一次使用hgetall的时候,就一直报错,大概类似下面的提示

read tcp 127.0.0.1:6379: i/o timeout

字面意思就是 read tcp 超时,可能某些写入大点数据的时候也会报,write tcp

timeout。

后来将读写超时时间都改为1000毫秒,就再也没有出现过类似的报错。

想了解更多的Redis使用,可以看下官方的文档:

github.com/gomodule/redigo/redis

3. 临时对象池sync.Pool

3 临时对象池

sync.Pool类型值作为存放临时值的容器。此类容器是自动伸缩的,高效的,同时也是并发

安全的。

sync.Pool类型只有两个方法:

◼ Put,用于在当前的池中存放临时对象,它接受一个空接口类型的值

◼ Get,用于从当前的池中获取临时对象,它返回一个空接口类型的值

New字段

sync.Pool类型的New字段是一个创建临时对象的函数。它的类型是没有参数但是会返回一个空接口

类型的函数。即:func() interface{}。

这个函数是Get方法最后的获取到临时对象的手段。函数的结果不会被存入当前的临时对象池中,

而是直接返回给Get方法的调用方。

这里的New字段的实际值需要在初始化临时对象池的时候就给定。否则,在Get方法调用它的时候

就会得到nil。

  1. package main
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io"
  6. "sync"
  7. )
  8. // 存放数据块缓冲区的临时对象
  9. var bufPool sync.Pool
  10. // 预定义定界符
  11. const delimiter = '\n'
  12. // 一个简易的数据库缓冲区的接口
  13. type Buffer interface {
  14. Delimiter() byte // 获取数据块之间的定界符
  15. Write(contents string) (err error) // 写入一个数据块
  16. Read() (contents string, err error) // 读取一个数据块
  17. Free() // 释放当前的缓冲区
  18. }
  19. // 实现一个上面定义的接口
  20. type myBuffer struct {
  21. buf bytes.Buffer
  22. delimiter byte
  23. }
  24. func (b *myBuffer) Delimiter() byte {
  25. return b.delimiter
  26. }
  27. func (b *myBuffer) Write(contents string) (err error) {
  28. if _, err = b.buf.WriteString(contents); err != nil {
  29. return
  30. }
  31. return b.buf.WriteByte(b.delimiter)
  32. }
  33. func (b *myBuffer) Read() (contents string, err error) {
  34. return b.buf.ReadString(b.delimiter)
  35. }
  36. func (b *myBuffer) Free() {
  37. bufPool.Put(b)
  38. }
  39. func init() {
  40. bufPool = sync.Pool{
  41. New: func() interface{} {
  42. return &myBuffer{delimiter: delimiter}
  43. },
  44. }
  45. }
  46. // 获取一个数据库缓冲区
  47. func GetBuffer() Buffer {
  48. return bufPool.Get().(Buffer) // 做类型转换
  49. }
  50. func main() {
  51. buf := GetBuffer()
  52. defer buf.Free()
  53. buf.Write("写入第一行,") // 写入数据
  54. buf.Write("接着写第二行。") // 写入数据
  55. fmt.Println("数据已经写入,准备把数据读出")
  56. for {
  57. block, err := buf.Read()
  58. if err != nil {
  59. if err == io.EOF {
  60. break
  61. }
  62. panic(fmt.Errorf("读取缓冲区时ERROR: %s", err))
  63. }
  64. fmt.Print(block)
  65. }
  66. }
  1. package main
  2. import (
  3. "fmt"
  4. _ "runtime"
  5. "runtime/debug"
  6. "sync"
  7. "sync/atomic"
  8. )
  9. func main() {
  10. // 禁用GC,并保证在main函数执行结束前恢复GC
  11. defer debug.SetGCPercent(debug.SetGCPercent(-1))
  12. var count int32
  13. // 实现一个函数 ,生成新对象
  14. newFunc := func() interface{} {
  15. fmt.Println("newFunc:", count)
  16. return atomic.AddInt32(&count, 1)
  17. }
  18. pool := sync.Pool{New: newFunc} // 传入生成对象的函数....
  19. // New 字段值的作用
  20. v1 := pool.Get() // 调用GET接口去取
  21. fmt.Printf("v1: %v\n", v1)
  22. pool.Put(v1) // 放回去
  23. // 临时对象池的存取
  24. pool.Put(newFunc())
  25. // pool.Put(newFunc())
  26. // pool.Put(newFunc())
  27. v2 := pool.Get()
  28. // pool.Put(v2)
  29. fmt.Printf("v2: %v\n", v2) // 这个时候v1和v2应该是一样
  30. // 垃圾回收对临时对象池的影响
  31. // debug.SetGCPercent(100)
  32. // runtime.GC()
  33. v3 := pool.Get()
  34. fmt.Printf("v3: %v\n", v3)
  35. pool.New = nil
  36. v4 := pool.Get()
  37. fmt.Printf("v4: %v\n", v4)
  38. }

3.1 Get

Pool 会为每个 P 维护一个本地池,P 的本地池分为 私有池 private和共享池 shared。私有池

中的元素只能本地 P 使用,共享池中的元素可能会被其他 P 偷走,所以使用私有池 private

时不用加锁,而使用共享池 shared 时需加锁。

Get 会优先查找本地 private,再查找本地 shared,最后查找其他 P 的shared,如果以上全部

没有可用元素,最后会调用 New 函数获取新元素

3.2 Put

Put 优先把元素放在 private 池中;如果 private 不为空,则放在 shared 池中。有趣

的是,在入池之前,该元素有 1/4 可能被丢掉。

 

4. 配置文件读取goconfig

4 配置文件解析器goconfig的使用

ini配置文件读写 conf.ini

  1. ;redis cache
  2. USER_LIST = USER:LIST
  3. MAX_COUNT = 50
  4. MAX_PRICE = 123456
  5. IS_SHOW = true
  6. [test]
  7. dbdns = root:@tcp(127.0.0.1:3306)
  8. [prod]
  9. dbdns = root:@tcp(172.168.1.1:3306)

4-1-config-ini.go

  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "github.com/Unknwon/goconfig"
  6. )
  7. func main() {
  8. cfg, err := goconfig.LoadConfigFile("./conf.ini") // 读取后文件关闭了
  9. if err != nil {
  10. log.Fatalf("无法加载配置文件:%s", err)
  11. }
  12. userListKey, err := cfg.GetValue("", "USER_LIST")
  13. if err != nil {
  14. fmt.Println(err.Error())
  15. }
  16. fmt.Println(userListKey)
  17. userListKey2, _ := cfg.GetValue(goconfig.DEFAULT_SECTION, "USER_LIST")
  18. fmt.Println(userListKey2)
  19. maxCount := cfg.MustInt("", "MAX_COUNT")
  20. fmt.Println(maxCount)
  21. maxPrice := cfg.MustFloat64("", "MAX_PRICE")
  22. fmt.Println(maxPrice)
  23. isShow := cfg.MustBool("", "IS_SHOW")
  24. fmt.Println(isShow)
  25. db := cfg.MustValue("test", "dbdns")
  26. fmt.Println(db)
  27. dbProd := cfg.MustValue("prod", "dbdns")
  28. fmt.Println("dbProd: ",dbProd)
  29. //set 值
  30. cfg.SetValue("", "MAX_NEW", "100")
  31. maxNew := cfg.MustInt("", "MAX_NEW")
  32. fmt.Println(maxNew)
  33. maxNew1, err := cfg.Int("", "MAX_NEW")
  34. if err != nil {
  35. fmt.Println(err.Error())
  36. }
  37. fmt.Println(maxNew1)
  38. cfg.AppendFiles("conf1.ini")
  39. // cfg.DeleteKey("", "MAX_NEW")
  40. }

5. 解析命令行flag

5 命令行解析Go flag

cmd -flag // 只支持bool类型

cmd -flag=xxx

cmd -flag xxx // 只支持非bool类型

1. 定义flag参数

参数有三个:第一个为 参数名称,第二个为 默认值,第三个是 使用说明

(1)通过 flag.String(),Bool(),Int() 等 flag.Xxx() 方法,该种方式返回一个相应的指针

var ip = flag.Int("flagname", 1234, "help message for flagname")

(2)通过 flag.XxxVar() 方法将 flag 绑定到一个变量,该种方式返回 值类型

var flagvar int

flag.IntVar(&flagvar, "flagname", 1234, "help message for flagname")

(3)通过 flag.Var() 绑定自定义类型,自定义类型需要实现 Value 接口 (Receiver 必须为指针)

fmt.Println("flagvar has value ", flagvar)

5-1-cli-flag.go

  1. package main
  2. import (
  3. "flag"
  4. "fmt"
  5. // "os"
  6. )
  7. // go run 4-1-cli-flag.go -ok -id 11111 -port 8899 -name TestUser very goo
  8. func main() {
  9. // fmt.Println(os.Args)
  10. ok := flag.Bool("ok", false, "is ok") // 不设置ok 则为false
  11. id := flag.Int("id", 0, "id")
  12. port := flag.String("port", ":8080", "http listen port")
  13. var name string
  14. flag.StringVar(&name, "name", "Jack", "name")
  15. flag.Parse()
  16. // flag.Usage()
  17. others := flag.Args()
  18. fmt.Println("ok:", *ok)
  19. fmt.Println("id:", *id)
  20. fmt.Println("port:", *port)
  21. fmt.Println("name:", name)
  22. fmt.Println("other:", others)
  23. }

5-2-self-flag.go

  1. package main
  2. import (
  3. "flag"
  4. "fmt"
  5. )
  6. type FlagSet struct {
  7. Usage func()
  8. }
  9. var myFlagSet = flag.NewFlagSet("myflagset", flag.ExitOnError)
  10. var stringFlag = myFlagSet.String("abc", "default value", "help mesage")
  11. func main() {
  12. myFlagSet.Parse([]string{"-abc", "def", "ghi", "123"})
  13. args := myFlagSet.Args()
  14. for i := range args {
  15. fmt.Println(i, myFlagSet.Arg(i))
  16. }
  17. }

6. uuid生成方案

6 uuid

雪花算法 Snowflake & Sonyflake https://www.cnblogs.com/li-peng/p/12124249.html

常用uuid性能测试

snowflake算法使用的一个64 bit的整型数据,被划分为四部分。

1.不含开头的第一个bit,因为是符号位;

2.41bit来表示收到请求时的时间戳,精确到1毫秒;

3.5bit表示数据中心的id, 5bit表示机器实例id

4.共计10bit的机器位,因此能部署在1024台机器节点上生

成ID;

5.12bit循环自增序列号,增至最大后归0,1毫秒最大生成

唯一ID的数量是4096个。

snowflake

sonyflake 这里时间戳用39位精确到10ms,所以可以达到174年,比

snowflake的长很久。

8bit 做为序列号,每10毫最大生成256个,1秒最多生成

25600个,比原生的Snowflake少好多,如果感觉不够用,

目前的解决方案是跑多个实例生成同一业务的ID来弥补。

16bit 做为机器号,默认的是当前机器的私有IP的最后两位。

sonyflake对于snowflake的改进是用空间换时间,时间戳位数减少,以从69年升至174年。

但是1秒最多生成的ID从409.6w降至2.56w条。

  

6-1-1-uuid.go

  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "math/rand"
  6. "reflect"
  7. "time"
  8. "gitee.com/GuaikOrg/go-snowflake/snowflake"
  9. "github.com/chilts/sid"
  10. "github.com/kjk/betterguid"
  11. "github.com/oklog/ulid"
  12. "github.com/rs/xid"
  13. uuid "github.com/satori/go.uuid"
  14. "github.com/segmentio/ksuid"
  15. "github.com/sony/sonyflake"
  16. )
  17. const FOR_LOOP = 100000
  18. func genXid() {
  19. id := xid.New()
  20. fmt.Printf("github.com/rs/xid: %s, len:%d\n", id.String(), len(id.String()))
  21. }
  22. func genKsuid() {
  23. id := ksuid.New()
  24. fmt.Printf("github.com/segmentio/ksuid: %s, len:%d\n", id.String(), len(id.String()))
  25. }
  26. func genBetterGUID() {
  27. id := betterguid.New()
  28. fmt.Printf("github.com/kjk/betterguid: %s, len:%d\n", id, len(id))
  29. }
  30. func genUlid() {
  31. t := time.Now().UTC()
  32. entropy := rand.New(rand.NewSource(t.UnixNano()))
  33. id := ulid.MustNew(ulid.Timestamp(t), entropy)
  34. fmt.Printf("github.com/oklog/ulid: %s, len:%d\n", id.String(), len(id.String()))
  35. }
  36. // https://gitee.com/GuaikOrg/go-snowflake
  37. func genSnowflake() {
  38. flake, err := snowflake.NewSnowflake(int64(0), int64(0))
  39. if err != nil {
  40. log.Fatalf("snowflake.NewSnowflake failed with %s\n", err)
  41. }
  42. id := flake.NextVal()
  43. fmt.Printf("gitee.com/GuaikOrg/go-snowflake:%x, type:%s\n", id, reflect.TypeOf(id))
  44. }
  45. func genSonyflake() {
  46. flake := sonyflake.NewSonyflake(sonyflake.Settings{})
  47. id, err := flake.NextID()
  48. if err != nil {
  49. log.Fatalf("flake.NextID() failed with %s\n", err)
  50. }
  51. fmt.Printf("github.com/sony/sonyflake: %x, type:%s\n", id, reflect.TypeOf(id))
  52. }
  53. func genSid() {
  54. id := sid.Id()
  55. fmt.Printf("github.com/chilts/sid: %s, len:%d\n", id, len(id))
  56. }
  57. func genUUIDv4() {
  58. id, err := uuid.NewV4()
  59. if err != nil {
  60. fmt.Printf("get uuid error [%s]", err)
  61. }
  62. fmt.Printf("github.com/satori/go.uuid: %s, len:%d\n", id, len(id))
  63. }
  64. func testGenXid(n int) {
  65. t0 := time.Now()
  66. for i := 0; i < n; i++ {
  67. _ = xid.New()
  68. }
  69. elapsed := time.Since(t0)
  70. fmt.Println("github.com/rs/xid n:", n, "time:", elapsed)
  71. }
  72. func testGenKsuid(n int) {
  73. t0 := time.Now()
  74. for i := 0; i < n; i++ {
  75. _ = ksuid.New()
  76. }
  77. elapsed := time.Since(t0)
  78. fmt.Println("github.com/segmentio/ksuid n:", n, "time:", elapsed)
  79. }
  80. func testGenBetterguid(n int) {
  81. t0 := time.Now()
  82. for i := 0; i < n; i++ {
  83. _ = betterguid.New()
  84. }
  85. elapsed := time.Since(t0)
  86. fmt.Println("github.com/kjk/betterguid n:", n, "time:", elapsed)
  87. }
  88. func testGenUlid(n int) {
  89. t0 := time.Now()
  90. for i := 0; i < n; i++ {
  91. t := time.Now().UTC()
  92. entropy := rand.New(rand.NewSource(t.UnixNano()))
  93. _ = ulid.MustNew(ulid.Timestamp(t), entropy)
  94. }
  95. elapsed := time.Since(t0)
  96. fmt.Println("github.com/oklog/ulid n:", n, "time:", elapsed)
  97. }
  98. func testGenSnowflake(n int) {
  99. t0 := time.Now()
  100. flake, err := snowflake.NewSnowflake(int64(0), int64(0))
  101. if err != nil {
  102. log.Fatalf("snowflake.NewSnowflake failed with %s\n", err)
  103. }
  104. for i := 0; i < n; i++ {
  105. _ = flake.NextVal()
  106. }
  107. elapsed := time.Since(t0)
  108. fmt.Println("gitee.com/GuaikOrg/go-snowflake n:", n, "time:", elapsed)
  109. }
  110. func testGenSonyflake(n int) {
  111. t0 := time.Now()
  112. flake := sonyflake.NewSonyflake(sonyflake.Settings{}) // 注意这一行的位置
  113. for i := 0; i < n; i++ {
  114. _, err := flake.NextID()
  115. if err != nil {
  116. log.Fatalf("flake.NextID() failed with %s\n", err)
  117. }
  118. }
  119. elapsed := time.Since(t0)
  120. fmt.Println("github.com/sony/sonyflake n:", n, "time:", elapsed)
  121. }
  122. func testGenSid(n int) {
  123. t0 := time.Now()
  124. for i := 0; i < n; i++ {
  125. _ = sid.Id()
  126. }
  127. elapsed := time.Since(t0)
  128. fmt.Println("github.com/chilts/sid n:", n, "time:", elapsed)
  129. }
  130. func testGenUUIDv4(n int) {
  131. t0 := time.Now()
  132. for i := 0; i < n; i++ {
  133. _, err := uuid.NewV4()
  134. if err != nil {
  135. fmt.Printf("get uuid error [%s]", err)
  136. }
  137. }
  138. elapsed := time.Since(t0)
  139. fmt.Println("github.com/satori/go.uuid n:", n, "time:", elapsed)
  140. }
  141. func main() {
  142. fmt.Printf("效果展示...\n")
  143. genXid()
  144. genXid()
  145. genXid()
  146. genKsuid()
  147. genBetterGUID()
  148. genUlid()
  149. genSnowflake()
  150. genSonyflake()
  151. genSid()
  152. genUUIDv4()
  153. fmt.Printf("性能测试...\n")
  154. testGenXid(FOR_LOOP)
  155. testGenKsuid(FOR_LOOP)
  156. testGenBetterguid(FOR_LOOP)
  157. testGenUlid(FOR_LOOP)
  158. testGenSnowflake(FOR_LOOP)
  159. testGenSonyflake(FOR_LOOP)
  160. testGenSid(FOR_LOOP)
  161. testGenUUIDv4(FOR_LOOP)
  162. }
  163. // github.com/rs/xid n: 1000000 time: 29.2665ms
  164. // github.com/segmentio/ksuid n: 1000000 time: 311.4816ms
  165. // github.com/kjk/betterguid n: 1000000 time: 89.2803ms
  166. // github.com/oklog/ulid n: 1000000 time: 11.746259s
  167. // github.com/sony/sonyflake n: 1000000 time: 39.0713342s
  168. // thub.com/chilts/sid n: 1000000 time: 254.9442ms
  169. // github.com/satori/go.uuid n: 1000000 time: 270.3201ms

2.2 Go语言Web开发与数据库实战

1 HTTP编程

a. Go原生支持http,import(“net/http”)

b. Go的http服务性能和nginx比较接近

c. 几行代码就可以实现一个web服务

1.1 HTTP常见请求方法

5. http常见请求方法

1)Get请求

2)Post请求

3)Put请求

4)Delete请求

5)Head请求

1.2 http 常见状态码

http 常见状态码

http.StatusContinue = 100

http.StatusOK = 200

http.StatusFound = 302

http.StatusBadRequest = 400

http.StatusUnauthorized = 401

http.StatusForbidden = 403

http.StatusNotFound = 404

http.StatusInternalServerError = 500

2 Client客户端

http包提供了很多访问Web服务器的函数,比如http.Get()、http.Post()、http.Head()等,

读到的响应报文数据被保存在 Response 结构体中。

Response结构体的定义

type Response struct {

Status string // e.g. "200 OK"

StatusCode int // e.g. 200

Proto string // e.g. "HTTP/1.0"

ProtoMajor int // e.g. 1

ProtoMinor int // e.g. 0

Header Header

Body io.ReadCloser

//...

}

服务器发送的响应包体被保存在Body中。可以使用它提供的Read方法来获取数据内容。保存至切片

缓冲区中,拼接成一个完整的字符串来查看。

结束的时候,需要调用Body中的Close()方法关闭io。

2.1基本的HTTP/HTTPS请求

Get、Head、Post和PostForm函数发出HTTP/HTTPS请求

resp, err := http.Get("http://example.com/")

...

resp, err := http.Post("http://example.com/upload", "image/jpeg", &buf)

...

resp, err := http.PostForm("http://example.com/form",url.Values{"key":

{"Value"}, "id": {"123"}})

使用完response后必须关闭回复的主体

resp, err := http.Get("http://example.com/")

if err != nil {

// handle error

}

defer resp.Body.Close()

body, err := ioutil.ReadAll(resp.Body)

// ...

2.2.1 不带参数的Get方法示例

2-2-1-http-get-client.go

  1. package main
  2. import (
  3. "fmt"
  4. "io/ioutil"
  5. "net/http"
  6. )
  7. func main() {
  8. // resp, err := http.Get("http://127.0.0.1:9000")
  9. resp, err := http.Get("https://www.baidu.com/")
  10. if err != nil {
  11. fmt.Println("get err:", err)
  12. return
  13. }
  14. defer resp.Body.Close() // 做关闭
  15. // data byte
  16. data, err := ioutil.ReadAll(resp.Body)
  17. if err != nil {
  18. fmt.Println("get data err:", err)
  19. return
  20. }
  21. fmt.Println("body:", string(data))
  22. fmt.Println("resp:", resp)
  23. }

2.2.2 带参数的Get方法示例

GET请求的参数需要使用Go语言内置的net/url这个标准库来处理

2-2-2-http-get-client.go

  1. package main
  2. import (
  3. "fmt"
  4. "io/ioutil"
  5. "net/http"
  6. "net/url"
  7. )
  8. func main() {
  9. //1.处理请求参数
  10. params := url.Values{}
  11. params.Set("name", "dar")
  12. params.Set("hobby", "足球")
  13. //2.设置请求URL
  14. rawUrl := "http://127.0.0.1:9000"
  15. reqURL, err := url.ParseRequestURI(rawUrl)
  16. if err != nil {
  17. fmt.Printf("url.ParseRequestURI()函数执行错误,错误为:%v\n", err)
  18. return
  19. }
  20. //3.整合请求URL和参数
  21. //Encode方法将请求参数编码为url编码格式("bar=baz&foo=quux"),编码时会以键进行排序。
  22. reqURL.RawQuery = params.Encode()
  23. //4.发送HTTP请求
  24. //说明: reqURL.String() String将URL重构为一个合法URL字符串。
  25. fmt.Println("Get url:", reqURL.String())
  26. resp, err := http.Get(reqURL.String())
  27. if err != nil {
  28. fmt.Printf("http.Get()函数执行错误,错误为:%v\n", err)
  29. return
  30. }
  31. defer resp.Body.Close()
  32. //5.一次性读取响应的所有内容
  33. body, err := ioutil.ReadAll(resp.Body)
  34. if err != nil {
  35. fmt.Printf("ioutil.ReadAll()函数执行出错,错误为:%v\n", err)
  36. return
  37. }
  38. fmt.Println("Response: ", string(body))
  39. }

2-2-2-http-get-server.go

  1. package main
  2. import (
  3. "fmt"
  4. "net/http"
  5. )
  6. // 响应: http.ResponseWriter
  7. // 请求:http.Request
  8. func myHandler(w http.ResponseWriter, r *http.Request) {
  9. defer r.Body.Close()
  10. params := r.URL.Query()
  11. fmt.Println("r.URL: ", r.URL)
  12. fmt.Fprintln(w, "name:", params.Get("name"), "hobby:", params.Get("hobby")) // 回写数据
  13. }
  14. func main() {
  15. http.HandleFunc("/", myHandler)
  16. err := http.ListenAndServe("127.0.0.1:9000", nil)
  17. if err != nil {
  18. fmt.Printf("http.ListenAndServe()函数执行错误,错误为:%v\n", err)
  19. return
  20. }
  21. }

2.3.1 post方法

发送POST请求的示例代码

2-3-1-http-post-client.go

  1. package main
  2. import (
  3. "fmt"
  4. "io/ioutil"
  5. "net/http"
  6. "strings"
  7. )
  8. // net/http post demo
  9. func main() {
  10. url := "http://127.0.0.1:9000/post"
  11. contentType := "application/json"
  12. data := `{"name":"darren","age":18}`
  13. resp, err := http.Post(url, contentType, strings.NewReader(data))
  14. if err != nil {
  15. fmt.Println("post failed, err:%v\n", err)
  16. return
  17. }
  18. defer resp.Body.Close()
  19. b, err := ioutil.ReadAll(resp.Body)
  20. if err != nil {
  21. fmt.Println("get resp failed,err:%v\n", err)
  22. return
  23. }
  24. fmt.Println("StatusCode:", resp.StatusCode)
  25. fmt.Println(string(b))
  26. }

2-3-1-http-post-server.go

  1. package main
  2. import (
  3. "fmt"
  4. "io/ioutil"
  5. "net/http"
  6. )
  7. func postHandler(w http.ResponseWriter, r *http.Request) {
  8. defer r.Body.Close()
  9. fmt.Println("Method ", r.Method)
  10. if r.Method == "POST" {
  11. // 1. 请求类型是application/json时从r.Body读取数据
  12. b, err := ioutil.ReadAll(r.Body)
  13. if err != nil {
  14. fmt.Println("read request.Body failed, err:%v\n", err)
  15. return
  16. }
  17. fmt.Println(string(b))
  18. answer := `{"status": "ok"}`
  19. w.Write([]byte(answer))
  20. } else {
  21. fmt.Println("can't handle ", r.Method)
  22. w.WriteHeader(http.StatusBadRequest)
  23. }
  24. }
  25. func main() {
  26. http.HandleFunc("/post", postHandler)
  27. err := http.ListenAndServe("0.0.0.0:9000", nil)
  28. if err != nil {
  29. fmt.Printf("http.ListenAndServe()函数执行错误,错误为:%v\n", err)
  30. return
  31. }
  32. }

2.4 head方法-client

HEAD请求常常被忽略,但是能提供很多有

用的信息,特别是在有限的速度和带宽下。

主要有以下特点:

1、只请求资源的首部;

2、检查超链接的有效性;

3、检查网页是否被修改;

4、多用于自动搜索机器人获取网页的标志

信息,获取rss种子信息,或者传递安全认证

信息等

  1. package main
  2. import (
  3. "fmt"
  4. "net"
  5. "net/http"
  6. "time"
  7. )
  8. func main() {
  9. url := "http://www.baidu1.com"
  10. c := http.Client{
  11. Transport: &http.Transport{
  12. Dial: func(network, addr string) (net.Conn, error) {
  13. timeout := time.Second * 2
  14. return net.DialTimeout(network, addr, timeout)
  15. },
  16. },
  17. }
  18. resp, err := c.Head(url)
  19. if err != nil {
  20. fmt.Printf("head %s failed, err:%v\n", url, err)
  21. } else {
  22. fmt.Printf("%s head succ, status:%v\n", url, resp.Status)
  23. }
  24. }

2.5 表单处理

  1. package main
  2. import (
  3. "fmt"
  4. "io"
  5. "net/http"
  6. )
  7. const form = `<html><body><form action="#" method="post" name="bar">
  8. <input type="text" name="in"/>
  9. <input type="text" name="in"/>
  10. <input type="submit" value="Submit"/>
  11. </form></html></body>`
  12. func HomeServer(w http.ResponseWriter, request *http.Request) {
  13. io.WriteString(w, "/test1 或者/test2")
  14. // io.WriteString(w, "<h1>/test1 或者/test2</h1>")
  15. }
  16. func SimpleServer(w http.ResponseWriter, request *http.Request) {
  17. io.WriteString(w, "<h1>hello, world</h1>")
  18. }
  19. func FormServer(w http.ResponseWriter, request *http.Request) {
  20. w.Header().Set("Content-Type", "text/html")
  21. switch request.Method {
  22. case "GET":
  23. io.WriteString(w, form)
  24. case "POST":
  25. request.ParseForm()
  26. fmt.Println("request.Form[in]:", request.Form["in"])
  27. io.WriteString(w, request.Form["in"][0])
  28. io.WriteString(w, "\n")
  29. io.WriteString(w, request.Form["in"][1]) // go web开发
  30. // var ptr *int
  31. // *ptr = 0x123445 // 模拟异常
  32. }
  33. }
  34. func main() {
  35. http.HandleFunc("/", HomeServer)
  36. http.HandleFunc("/test1", SimpleServer)
  37. http.HandleFunc("/test2", FormServer)
  38. err := http.ListenAndServe(":9000", nil)
  39. if err != nil {
  40. fmt.Printf("http.ListenAndServe()函数执行错误,错误为:%v\n", err)
  41. return
  42. }
  43. }

2.6 panic处理

2-6-panic-server.go

  1. package main
  2. import (
  3. "fmt"
  4. "io"
  5. "log"
  6. "net/http"
  7. )
  8. const form = `<html><body><form action="#" method="post" name="bar">
  9. <input type="text" name="in"/>
  10. <input type="text" name="in"/>
  11. <input type="submit" value="Submit"/>
  12. </form></html></body>`
  13. func HomeServer(w http.ResponseWriter, request *http.Request) {
  14. io.WriteString(w, "<h1>/test1 或者/test2</h1>")
  15. }
  16. func SimpleServer(w http.ResponseWriter, request *http.Request) {
  17. io.WriteString(w, "<h1>hello, world</h1>")
  18. }
  19. func FormServer(w http.ResponseWriter, request *http.Request) {
  20. w.Header().Set("Content-Type", "text/html")
  21. switch request.Method {
  22. case "GET":
  23. io.WriteString(w, form)
  24. case "POST":
  25. request.ParseForm()
  26. fmt.Println("request.Form[in]:", request.Form["in"])
  27. io.WriteString(w, request.Form["in"][0])
  28. io.WriteString(w, "\n")
  29. io.WriteString(w, request.Form["in"][1])
  30. // var ptr *int
  31. // *ptr = 0x123445 // 模拟异常 注意协程的异常处理
  32. var ptr *int
  33. var a int
  34. ptr = &a
  35. *ptr = 0x123445 // 也是可以取地址写入的
  36. }
  37. }
  38. func main() {
  39. http.HandleFunc("/", HomeServer)
  40. http.HandleFunc("/test1", logPanics(SimpleServer))
  41. http.HandleFunc("/test2", logPanics(FormServer))
  42. err := http.ListenAndServe(":9000", nil)
  43. if err != nil {
  44. fmt.Printf("http.ListenAndServe()函数执行错误,错误为:%v\n", err)
  45. return
  46. }
  47. }
  48. func logPanics(handle http.HandlerFunc) http.HandlerFunc {
  49. return func(writer http.ResponseWriter, request *http.Request) {
  50. defer func() {
  51. if x := recover(); x != nil {
  52. log.Printf("[%v] caught panic: %v", request.RemoteAddr, x)
  53. }
  54. }()
  55. handle(writer, request)
  56. }
  57. }

3 模板

3 模板

1)替换 {{.字段名}}

3-1-template.go

  1. package main
  2. import (
  3. "fmt"
  4. "html/template"
  5. "io"
  6. "net/http"
  7. )
  8. var myTemplate *template.Template
  9. type Result struct {
  10. output string
  11. }
  12. func (p *Result) Write(b []byte) (n int, err error) {
  13. fmt.Println("called by template")
  14. p.output += string(b)
  15. return len(b), nil
  16. }
  17. type Person struct {
  18. Name string
  19. Title string
  20. Age int
  21. }
  22. func userInfo(w http.ResponseWriter, r *http.Request) {
  23. fmt.Println("handle hello")
  24. //fmt.Fprintf(w, "hello ")
  25. var arr []Person
  26. p := Person{Name: "Dar", Age: 18, Title: "个人网站"}
  27. p1 := Person{Name: "Ki", Age: 19, Title: "个人网站"}
  28. p2 := Person{Name: "子", Age: 20, Title: "个人网站"}
  29. arr = append(arr, p)
  30. arr = append(arr, p1)
  31. arr = append(arr, p2)
  32. fmt.Println("arr:", arr)
  33. resultWriter := &Result{}
  34. io.WriteString(resultWriter, "hello 模板")
  35. err := myTemplate.Execute(w, arr) // 模板替换, 执行完后, html模板和参数arr就写入 w http.ResponseWriter
  36. if err != nil {
  37. fmt.Println(err)
  38. }
  39. fmt.Println("template render data:", resultWriter.output)
  40. //myTemplate.Execute(w, p)
  41. //myTemplate.Execute(os.Stdout, p)
  42. //file, err := os.OpenFile("C:/test.log", os.O_CREATE|os.O_WRONLY, 0755)
  43. //if err != nil {
  44. // fmt.Println("open failed err:", err)
  45. // return
  46. //}
  47. }
  48. func initTemplate(filename string) (err error) {
  49. myTemplate, err = template.ParseFiles(filename)
  50. if err != nil {
  51. fmt.Println("parse file err:", err)
  52. return
  53. }
  54. return
  55. }
  56. func main() {
  57. initTemplate("./index.html")
  58. http.HandleFunc("/user/info", userInfo)
  59. err := http.ListenAndServe("0.0.0.0:9000", nil)
  60. if err != nil {
  61. fmt.Println("http listen failed")
  62. }
  63. }

3.1 模板-替换 {{.字段名}}

4 Mysql

建库建表

在MySQL中创建一个名为go_test的数据库

CREATE DATABASE go_test;

进入该数据库:

use go_test;

创建一张用于测试的数据表:

CREATE TABLE `user` (

`id` BIGINT(20) NOT NULL AUTO_INCREMENT,

`name` VARCHAR(20) DEFAULT '',

`age` INT(11) DEFAULT '0',

PRIMARY KEY(`id`)

)ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT

CHARSET=utf8mb4;

4.0 连接mysql

Open函数:

db, err := sql.Open("mysql", "用户名:密码@tcp(IP:端口)/数据库?charset=utf8")

例如:db, err := sql.Open("mysql", "root:1234@tcp(127.0.0.1:3306)/test?charset=utf8")

  1. package main
  2. import (
  3. "database/sql"
  4. "fmt"
  5. _ "github.com/go-sql-driver/mysql" // 注释掉后异常 _ 调用初始化函数
  6. )
  7. // https://github.com/go-sql-driver/mysql#usage
  8. func main() {
  9. db, err := sql.Open("mysql", "root:1234@tcp(127.0.0.1:3306)/go_test?charset=utf8mb4")
  10. fmt.Println("err:", err) // err: <nil>
  11. if db == nil {
  12. fmt.Println("db open failed:", err)
  13. }
  14. err = db.Ping() //Ping verifies a connection to the database is still alive, establishing a connection if necessary
  15. if err != nil {
  16. fmt.Println("数据库链接失败", err)
  17. }
  18. defer db.Close()
  19. }

4-1 mysql插入数据

4-1-mysql.go

  1. package main
  2. import (
  3. "database/sql"
  4. "fmt"
  5. _ "github.com/go-sql-driver/mysql"
  6. )
  7. // 插入数据
  8. func insertRowDemo(db *sql.DB) {
  9. sqlStr := "insert into user(name, age) values (?,?)"
  10. ret, err := db.Exec(sqlStr, "darren", 18)
  11. if err != nil {
  12. fmt.Printf("insert failed, err:%v\n", err)
  13. return
  14. }
  15. theID, err := ret.LastInsertId() // 新插入数据的id
  16. if err != nil {
  17. fmt.Printf("get lastinsert ID failed, err:%v\n", err)
  18. return
  19. }
  20. fmt.Printf("insert success, the id is %d.\n", theID)
  21. }
  22. func main() {
  23. db, err := sql.Open("mysql", "root:1234@tcp(127.0.0.1:3306)/go_test?charset=utf8mb4")
  24. fmt.Println("err:", err)
  25. err = db.Ping()
  26. if err != nil {
  27. fmt.Println("数据库链接失败", err)
  28. return
  29. }
  30. insertRowDemo(db)
  31. defer db.Close()
  32. }

4-2 mysql查询-单行查询

单行查询

单行查询db.QueryRow()执行一次查询,并期望返回最多一行结果(即Row)。QueryRow总是返回非nil的

值,直到返回值的Scan方法被调用时,才会返回被延迟的错误。(如:未找到结果)

func (db *DB) QueryRow(query string, args ...interface{}) *Row

4-2-mysql-query copy.go

  1. package main
  2. import (
  3. "database/sql"
  4. "fmt"
  5. _ "github.com/go-sql-driver/mysql"
  6. )
  7. type user struct {
  8. id int
  9. name string
  10. age int
  11. }
  12. // 查询单条数据示例
  13. func queryRowDemo(db *sql.DB) {
  14. sqlStr := "select id, name, age from user where id=?"
  15. var u user
  16. // 非常重要:确保QueryRow之后调用Scan方法,否则持有的数据库链接不会被释放
  17. err := db.QueryRow(sqlStr, 3).Scan(&u.id, &u.name, &u.age)
  18. if err != nil {
  19. fmt.Printf("scan failed, err:%v\n", err)
  20. return
  21. }
  22. fmt.Printf("id:%d name:%s age:%d\n", u.id, u.name, u.age)
  23. }
  24. func main() {
  25. db, err := sql.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/go_test?charset=utf8mb4")
  26. fmt.Println("err:", err)
  27. err = db.Ping()
  28. if err != nil {
  29. fmt.Println("数据库链接失败", err)
  30. return
  31. }
  32. queryRowDemo(db)
  33. defer db.Close()
  34. }

4-2 mysql查询-多行查询

多行查询db.Query()执行一次查询,返回多行结果(即Rows),一般用于执行select命令。参数args表

示query中的占位参数。

func (db *DB) Query(query string, args ...interface{}) (*Rows, error)

4-2-mysql-multi-query.go

  1. package main
  2. import (
  3. "database/sql"
  4. "fmt"
  5. _ "github.com/go-sql-driver/mysql"
  6. )
  7. type user struct {
  8. id int
  9. name string
  10. age int
  11. }
  12. // 查询多条数据示例
  13. func queryMultiRowDemo(db *sql.DB) {
  14. sqlStr := "select id, name, age from user where id > ?"
  15. rows, err := db.Query(sqlStr, 0)
  16. if err != nil {
  17. fmt.Printf("query failed, err:%v\n", err)
  18. return
  19. }
  20. // 非常重要:关闭rows释放持有的数据库链接
  21. defer rows.Close()
  22. // 循环读取结果集中的数据
  23. for rows.Next() {
  24. var u user
  25. err := rows.Scan(&u.id, &u.name, &u.age) // 通过SCAN读取出来
  26. if err != nil {
  27. fmt.Printf("scan failed, err:%v\n", err)
  28. return
  29. }
  30. fmt.Printf("id:%d name:%s age:%d\n", u.id, u.name, u.age)
  31. }
  32. }
  33. func main() {
  34. db, err := sql.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/go_test?charset=utf8mb4")
  35. fmt.Println("err:", err)
  36. err = db.Ping()
  37. if err != nil {
  38. fmt.Println("数据库链接失败", err)
  39. return
  40. }
  41. queryMultiRowDemo(db)
  42. defer db.Close()
  43. }

4-3 mysql更新

4-3-mysql-update.go

  1. package main
  2. import (
  3. "database/sql"
  4. "fmt"
  5. _ "github.com/go-sql-driver/mysql"
  6. )
  7. type user struct {
  8. id int
  9. name string
  10. age int
  11. }
  12. // 更新数据
  13. func updateRowDemo(db *sql.DB) {
  14. sqlStr := "update user set age=? where id = ?"
  15. ret, err := db.Exec(sqlStr, 20, 2)
  16. if err != nil {
  17. fmt.Printf("update failed, err:%v\n", err)
  18. return
  19. }
  20. n, err := ret.RowsAffected() // 操作影响的行数
  21. if err != nil {
  22. fmt.Printf("get RowsAffected failed, err:%v\n", err)
  23. return
  24. }
  25. fmt.Printf("update success, affected rows:%d\n", n)
  26. }
  27. func main() {
  28. db, err := sql.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/go_test?charset=utf8mb4")
  29. // fmt.Println("err:", err)
  30. err = db.Ping()
  31. if err != nil {
  32. fmt.Println("数据库链接失败", err)
  33. return
  34. }
  35. updateRowDemo(db)
  36. defer db.Close()
  37. }

4-4 mysql删除

4-4-mysql-delete.go

  1. package main
  2. import (
  3. "database/sql"
  4. "fmt"
  5. _ "github.com/go-sql-driver/mysql"
  6. )
  7. type user struct {
  8. id int
  9. name string
  10. age int
  11. }
  12. // 删除数据
  13. func deleteRowDemo(db *sql.DB) {
  14. sqlStr := "delete from user where id = ?"
  15. ret, err := db.Exec(sqlStr, 1)
  16. if err != nil {
  17. fmt.Printf("delete failed, err:%v\n", err)
  18. return
  19. }
  20. n, err := ret.RowsAffected() // 操作影响的行数
  21. if err != nil {
  22. fmt.Printf("get RowsAffected failed, err:%v\n", err)
  23. return
  24. }
  25. fmt.Printf("delete success, affected rows:%d\n", n)
  26. }
  27. func main() {
  28. db, err := sql.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/go_test?charset=utf8mb4")
  29. // fmt.Println("err:", err)
  30. err = db.Ping()
  31. if err != nil {
  32. fmt.Println("数据库链接失败", err)
  33. return
  34. }
  35. deleteRowDemo(db)
  36. defer db.Close()
  37. }

5 MySQL预处理

什么是预处理?

普通SQL语句执行过程:

1.客户端对SQL语句进行占位符替换得到完整的SQL语句。

2.客户端发送完整SQL语句到MySQL服务端

3.MySQL服务端执行完整的SQL语句并将结果返回给客户端。

预处理执行过程:

1.把SQL语句分成两部分,命令部分与数据部分。

2.先把命令部分发送给MySQL服务端,MySQL服务端进行SQL预处理。

3.然后把数据部分发送给MySQL服务端,MySQL服务端对SQL语句进行占位符替换。

4.MySQL服务端执行完整的SQL语句并将结果返回给客户端。

为什么要预处理?

1.优化MySQL服务器重复执行SQL的方法,可以提升服务器性能,提前让服务器编译,一次编译多次

执行,节省后续编译的成本。

2.避免SQL注入问题。

5.1 Go实现MySQL预处理

func (db *DB) Prepare(query string) (*Stmt, error)

Prepare方法会先将sql语句发送给MySQL服务端,返回一个准备好的状态用于之后的查询和命令。

返回值可以同时执行多个查询和命令。 4-5-mysql-prepare.go

  1. package main
  2. import (
  3. "database/sql"
  4. "fmt"
  5. _ "github.com/go-sql-driver/mysql"
  6. )
  7. type user struct {
  8. id int
  9. name string
  10. age int
  11. }
  12. // 预处理查询示例
  13. func prepareQueryDemo(db *sql.DB) {
  14. sqlStr := "select id, name, age from user where id > ?"
  15. stmt, err := db.Prepare(sqlStr)
  16. if err != nil {
  17. fmt.Printf("prepare failed, err:%v\n", err)
  18. return
  19. }
  20. defer stmt.Close()
  21. rows, err := stmt.Query(0)
  22. if err != nil {
  23. fmt.Printf("query failed, err:%v\n", err)
  24. return
  25. }
  26. defer rows.Close()
  27. // 循环读取结果集中的数据
  28. for rows.Next() {
  29. var u user
  30. err := rows.Scan(&u.id, &u.name, &u.age)
  31. if err != nil {
  32. fmt.Printf("scan failed, err:%v\n", err)
  33. return
  34. }
  35. fmt.Printf("id:%d name:%s age:%d\n", u.id, u.name, u.age)
  36. }
  37. }
  38. // 预处理插入示例
  39. // 插入、更新和删除操作的预处理十分类似
  40. func prepareInsertDemo(db *sql.DB) {
  41. sqlStr := "insert into user(name, age) values (?,?)"
  42. stmt, err := db.Prepare(sqlStr)
  43. if err != nil {
  44. fmt.Printf("prepare failed, err:%v\n", err)
  45. return
  46. }
  47. defer stmt.Close()
  48. _, err = stmt.Exec("darren", 18)
  49. if err != nil {
  50. fmt.Printf("insert failed, err:%v\n", err)
  51. return
  52. }
  53. _, err = stmt.Exec("柚子老师", 18)
  54. if err != nil {
  55. fmt.Printf("insert failed, err:%v\n", err)
  56. return
  57. }
  58. fmt.Println("insert success.")
  59. }
  60. func main() {
  61. db, err := sql.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/go_test?charset=utf8mb4")
  62. // fmt.Println("err:", err)
  63. err = db.Ping()
  64. if err != nil {
  65. fmt.Println("数据库链接失败", err)
  66. return
  67. }
  68. prepareInsertDemo(db)
  69. prepareQueryDemo(db)
  70. defer db.Close()
  71. }

6 Go实现MySQL事务

事务相关方法 Go语言中使用以下三个方法实现MySQL中的事务操作。

开始事务: func (db *DB) Begin() (*Tx, error)

提交事务: func (tx *Tx) Commit() error

回滚事务: func (tx *Tx) Rollback() error

6-mysql-transaction.go

  1. package main
  2. import (
  3. "database/sql"
  4. "fmt"
  5. _ "github.com/go-sql-driver/mysql"
  6. )
  7. type user struct {
  8. id int
  9. name string
  10. age int
  11. }
  12. // 预处理查询示例
  13. func prepareQueryDemo(db *sql.DB) {
  14. sqlStr := "select id, name, age from user where id > ?"
  15. stmt, err := db.Prepare(sqlStr)
  16. if err != nil {
  17. fmt.Printf("prepare failed, err:%v\n", err)
  18. return
  19. }
  20. defer stmt.Close()
  21. rows, err := stmt.Query(0)
  22. if err != nil {
  23. fmt.Printf("query failed, err:%v\n", err)
  24. return
  25. }
  26. defer rows.Close()
  27. // 循环读取结果集中的数据
  28. for rows.Next() {
  29. var u user
  30. err := rows.Scan(&u.id, &u.name, &u.age)
  31. if err != nil {
  32. fmt.Printf("scan failed, err:%v\n", err)
  33. return
  34. }
  35. fmt.Printf("id:%d name:%s age:%d\n", u.id, u.name, u.age)
  36. }
  37. }
  38. // 预处理插入示例
  39. // 插入、更新和删除操作的预处理十分类似
  40. func prepareInsertDemo(db *sql.DB) {
  41. sqlStr := "insert into user(name, age) values (?,?)"
  42. stmt, err := db.Prepare(sqlStr)
  43. if err != nil {
  44. fmt.Printf("prepare failed, err:%v\n", err)
  45. return
  46. }
  47. defer stmt.Close()
  48. _, err = stmt.Exec("darren", 18)
  49. if err != nil {
  50. fmt.Printf("insert failed, err:%v\n", err)
  51. return
  52. }
  53. _, err = stmt.Exec("柚子老师", 18)
  54. if err != nil {
  55. fmt.Printf("insert failed, err:%v\n", err)
  56. return
  57. }
  58. fmt.Println("insert success.")
  59. }
  60. // 事务操作示例
  61. func transactionDemo(db *sql.DB) {
  62. tx, err := db.Begin() // 开启事务
  63. if err != nil {
  64. if tx != nil {
  65. tx.Rollback() // 回滚
  66. }
  67. fmt.Printf("begin trans failed, err:%v\n", err)
  68. return
  69. }
  70. sqlStr1 := "Update user set age=30 where id=?"
  71. _, err = tx.Exec(sqlStr1, 2)
  72. if err != nil {
  73. tx.Rollback() // 回滚
  74. fmt.Printf("exec sql1 failed, err:%v\n", err)
  75. return
  76. }
  77. sqlStr2 := "Update user set age=40 where id=?"
  78. _, err = tx.Exec(sqlStr2, 4)
  79. if err != nil {
  80. tx.Rollback() // 回滚
  81. fmt.Printf("exec sql2 failed, err:%v\n", err)
  82. return
  83. }
  84. err = tx.Commit() // 提交事务
  85. if err != nil {
  86. tx.Rollback() // 回滚
  87. fmt.Printf("commit failed, err:%v\n", err)
  88. return
  89. }
  90. fmt.Println("exec trans success!")
  91. }
  92. func main() {
  93. db, err := sql.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/go_test?charset=utf8mb4")
  94. if db != nil {
  95. defer db.Close() // 健壮的写法
  96. }
  97. // fmt.Println("err:", err)
  98. err = db.Ping()
  99. if err != nil {
  100. fmt.Println("数据库链接失败", err)
  101. return
  102. }
  103. db.SetMaxOpenConns(10)
  104. db.SetMaxIdleConns(5)
  105. stats := db.Stats()
  106. fmt.Println("stats1:", stats)
  107. prepareInsertDemo(db)
  108. prepareQueryDemo(db)
  109. stats = db.Stats()
  110. fmt.Println("stats2:", stats)
  111. }

7 sqlx使用

第三方库sqlx能够简化操作,提高开发效率。

安装

go get github.com/jmoiron/sqlx

7-mysql-sqlx.go

  1. package main
  2. import (
  3. "fmt"
  4. _ "github.com/go-sql-driver/mysql"
  5. "github.com/jmoiron/sqlx"
  6. )
  7. type user struct {
  8. ID int `json:"id" db:"id"`
  9. Name string `json:"name" db:"name"`
  10. Age int `json:"age" db:"age"`
  11. }
  12. var db *sqlx.DB
  13. // 连接数据库
  14. func initDB() (err error) {
  15. dsn := "root:123456@tcp(127.0.0.1:3306)/go_test?charset=utf8mb4"
  16. // 也可以使用MustConnect连接不成功就panic
  17. db, err = sqlx.Connect("mysql", dsn)
  18. if err != nil {
  19. fmt.Printf("connect DB failed, err:%v\n", err)
  20. return
  21. }
  22. db.SetMaxOpenConns(20)
  23. db.SetMaxIdleConns(10)
  24. return
  25. }
  26. // 查询单条数据
  27. func queryRowDemo() {
  28. sqlStr := "select id, name, age from user where id=?"
  29. var u user
  30. err := db.Get(&u, sqlStr, 2) // 单条查询
  31. if err != nil {
  32. fmt.Printf("get failed, err:%v\n", err)
  33. return
  34. }
  35. fmt.Printf("id:%d name:%s age:%d\n", u.ID, u.Name, u.Age)
  36. }
  37. // 查询多行数据
  38. func queryMultiRowDemo() {
  39. sqlStr := "select id, name, age from user where id > ?"
  40. var users []user
  41. err := db.Select(&users, sqlStr, 0) // 主要是查询
  42. if err != nil {
  43. fmt.Printf("query failed, err:%v\n", err)
  44. return
  45. }
  46. fmt.Printf("users:%#v\n", users)
  47. }
  48. // 插入数据
  49. func insertRowDemo() {
  50. sqlStr := "insert into user(name, age) values (?,?)"
  51. ret, err := db.Exec(sqlStr, "隔壁老王", 18)
  52. if err != nil {
  53. fmt.Printf("insert failed, err:%v\n", err)
  54. return
  55. }
  56. theID, err := ret.LastInsertId() // 新插入数据的id
  57. if err != nil {
  58. fmt.Printf("get lastinsert ID failed, err:%v\n", err)
  59. return
  60. }
  61. fmt.Printf("insert success, the id is %d.\n", theID)
  62. }
  63. // 更新数据
  64. func updateRowDemo() {
  65. sqlStr := "update user set age=? where id = ?"
  66. ret, err := db.Exec(sqlStr, 39, 6)
  67. if err != nil {
  68. fmt.Printf("update failed, err:%v\n", err)
  69. return
  70. }
  71. n, err := ret.RowsAffected() // 操作影响的行数
  72. if err != nil {
  73. fmt.Printf("get RowsAffected failed, err:%v\n", err)
  74. return
  75. }
  76. fmt.Printf("update success, affected rows:%d\n", n)
  77. }
  78. // 删除数据
  79. func deleteRowDemo() {
  80. sqlStr := "delete from user where id = ?"
  81. ret, err := db.Exec(sqlStr, 6)
  82. if err != nil {
  83. fmt.Printf("delete failed, err:%v\n", err)
  84. return
  85. }
  86. n, err := ret.RowsAffected() // 操作影响的行数
  87. if err != nil {
  88. fmt.Printf("get RowsAffected failed, err:%v\n", err)
  89. return
  90. }
  91. fmt.Printf("delete success, affected rows:%d\n", n)
  92. }
  93. // 事务操作
  94. func transactionDemo() {
  95. tx, err := db.Beginx() // 开启事务
  96. if err != nil {
  97. if tx != nil {
  98. tx.Rollback()
  99. }
  100. fmt.Printf("begin trans failed, err:%v\n", err)
  101. return
  102. }
  103. sqlStr1 := "Update user set age=40 where id=?"
  104. tx.MustExec(sqlStr1, 2)
  105. sqlStr2 := "Update user set age=50 where id=?"
  106. tx.MustExec(sqlStr2, 4)
  107. err = tx.Commit() // 提交事务
  108. if err != nil {
  109. tx.Rollback() // 回滚
  110. fmt.Printf("commit failed, err:%v\n", err)
  111. return
  112. }
  113. fmt.Println("exec trans success!")
  114. }
  115. // Get、QueryRowx: 查询一条数据
  116. // QueryRowx可以指定到不同的数据类型中
  117. func getNum() {
  118. var num int
  119. _ = db.Get(&num, "select count(*) from user")
  120. fmt.Printf("数据库一共有:%d 个用户\n", num)
  121. var u user
  122. _ = db.Get(&u, "select name, id, age from user where id = ?", 1)
  123. fmt.Printf("查找用户id==1的用户:%v \n", u)
  124. }
  125. func main() {
  126. err := initDB()
  127. if err != nil {
  128. fmt.Println("数据库链接失败", err)
  129. return
  130. }
  131. insertRowDemo()
  132. queryRowDemo()
  133. getNum()
  134. queryMultiRowDemo()
  135. // defer db.Close()
  136. }

7-mysql-sqlx-2.go

  1. package main
  2. // 数据库连接初始化
  3. import (
  4. "fmt"
  5. _ "github.com/go-sql-driver/mysql" // mysql
  6. "github.com/jmoiron/sqlx"
  7. )
  8. // DB 数据库模型
  9. var DB *sqlx.DB
  10. const dsn = "root:123456@tcp(127.0.0.1:3306)/go_test?charset=utf8mb4"
  11. type user struct {
  12. ID int `json:"id" db:"id"`
  13. Name string `json:"name" db:"name"`
  14. Age int `json:"age" db:"age"`
  15. }
  16. // connect 1.连接数据库
  17. func connect() (db *sqlx.DB, err error) {
  18. db, err = sqlx.Connect("mysql", dsn)
  19. db.SetMaxOpenConns(100) // 设置连接池最大连接数
  20. db.SetMaxIdleConns(20) // 设置连接池最大空闲连接数
  21. DB = db
  22. if err != nil {
  23. fmt.Println("数据库连接失败==>", err)
  24. }
  25. fmt.Println("数据库已连接!")
  26. return
  27. }
  28. // 添加数据 Exec、MustExec
  29. // MustExec遇到错误的时候直接抛出一个panic错误,程序就退出了;
  30. // Exec是将错误和执行结果一起返回,由我们自己处理错误。 推荐使用!
  31. func createUser() {
  32. // 创建表
  33. sql := `
  34. CREATE TABLE user (
  35. id bigint(20) NOT NULL AUTO_INCREMENT,
  36. name varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '',
  37. age int(11) NULL DEFAULT 0,
  38. PRIMARY KEY (id) USING BTREE
  39. ) ENGINE = InnoDB AUTO_INCREMENT = 20 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact
  40. `
  41. _, err := DB.Exec(sql)
  42. fmt.Println(err)
  43. }
  44. // 添加数据
  45. func insertUser() {
  46. sql := `insert into user (name, age) values ("lgx",18)`
  47. res := DB.MustExec(sql)
  48. fmt.Println(res.LastInsertId)
  49. fmt.Println(res.RowsAffected)
  50. }
  51. // 更新数据
  52. func updateUser() {
  53. sql := `update user set name = ?, age = ? where id = ?`
  54. res, err := DB.Exec(sql, "LGX", 28, 20)
  55. fmt.Println(err, res)
  56. }
  57. // Get、QueryRowx: 查询一条数据
  58. // QueryRowx可以指定到不同的数据类型中
  59. func getNum() {
  60. var num int
  61. _ = DB.Get(&num, "select count(*) from user")
  62. fmt.Printf("数据库一共有:%d 个用户\n", num)
  63. var u user
  64. _ = DB.Get(&u, "select name, id, age from user where id = ?", 2)
  65. fmt.Printf("查找用户id==1的用户:%v \n", u)
  66. }
  67. // Select、Queryx:查询多条数据
  68. // Queryx可以指定到不同的数据类型中
  69. func getAll() {
  70. sql := `select id, name ,age from user where id > 1`
  71. var us []user
  72. err := DB.Select(&us, sql)
  73. fmt.Println(err, us)
  74. }
  75. // 删除
  76. func deleteUser() {
  77. sql := `delete from user where id = 20`
  78. _, _ = DB.Exec(sql)
  79. }
  80. // 事务处理
  81. func events() {
  82. tx, _ := DB.Beginx()
  83. _, err1 := tx.Exec("update user set age = 10 where id = 20")
  84. _, err2 := tx.Exec("update user set age = 10 where id = 21")
  85. fmt.Println(err1, err2)
  86. if err1 != nil || err2 != nil {
  87. tx.Rollback()
  88. }
  89. tx.Commit()
  90. }
  91. func main() {
  92. db, _ := connect()
  93. defer db.Close()
  94. // 建表
  95. // createUser()
  96. // 添加数据
  97. insertUser()
  98. // 修改数据
  99. updateUser()
  100. // 查数据-Get
  101. getNum()
  102. // 查数据-Select
  103. getAll()
  104. // 事务
  105. // events()
  106. }

8 gin + mysql restfull api

代码仓库

github.com/yunixiangfeng/devops/tree/main/gin_restful

 api\users.go

  1. package api
  2. import (
  3. "fmt"
  4. . "gin_restful/models"
  5. "net/http"
  6. "strconv"
  7. "github.com/gin-gonic/gin"
  8. )
  9. //index
  10. func IndexUsers(c *gin.Context) {
  11. c.String(http.StatusOK, "It works")
  12. }
  13. //增加一条记录
  14. func AddUsers(c *gin.Context) {
  15. name := c.Request.FormValue("name")
  16. telephone := c.Request.FormValue("telephone")
  17. fmt.Println("name:", name)
  18. fmt.Println("telephone:", telephone)
  19. if name == "" {
  20. msg := fmt.Sprintf("name字段错误")
  21. c.JSON(http.StatusBadRequest, gin.H{
  22. "msg": msg,
  23. })
  24. return
  25. }
  26. person := Person{
  27. Name: name,
  28. Telephone: telephone,
  29. }
  30. id := person.Create()
  31. msg := fmt.Sprintf("insert 成功 %d", id)
  32. c.JSON(http.StatusOK, gin.H{
  33. "msg": msg,
  34. })
  35. }
  36. //获得一条记录
  37. func GetOne(c *gin.Context) {
  38. ids := c.Param("id")
  39. id, _ := strconv.Atoi(ids)
  40. p := Person{
  41. Id: id,
  42. }
  43. rs, _ := p.GetRow()
  44. c.JSON(http.StatusOK, gin.H{
  45. "result": rs,
  46. })
  47. }
  48. //获得所有记录
  49. func GetAll(c *gin.Context) {
  50. p := Person{}
  51. rs, _ := p.GetRows()
  52. c.JSON(http.StatusOK, gin.H{
  53. "list": rs,
  54. })
  55. }
  56. func UpdateUser(c *gin.Context) {
  57. ids := c.Request.FormValue("id")
  58. id, _ := strconv.Atoi(ids)
  59. telephone := c.Request.FormValue("telephone")
  60. person := Person{
  61. Id: id,
  62. Telephone: telephone,
  63. }
  64. row := person.Update()
  65. msg := fmt.Sprintf("updated successful %d", row)
  66. c.JSON(http.StatusOK, gin.H{
  67. "msg": msg,
  68. })
  69. }
  70. //删除一条记录
  71. func DelUser(c *gin.Context) {
  72. ids := c.Request.FormValue("id")
  73. id, _ := strconv.Atoi(ids)
  74. row := Delete(id)
  75. msg := fmt.Sprintf("delete successful %d", row)
  76. c.JSON(http.StatusOK, gin.H{
  77. "msg": msg,
  78. })
  79. }

db\mysql.go

  1. package db
  2. import (
  3. "database/sql"
  4. "log"
  5. _ "github.com/go-sql-driver/mysql"
  6. )
  7. var SqlDB *sql.DB
  8. func init() {
  9. var err error
  10. SqlDB, err = sql.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/go_test?charset=utf8mb4")
  11. if err != nil {
  12. log.Fatal(err.Error())
  13. }
  14. err = SqlDB.Ping()
  15. if err != nil {
  16. log.Fatal(err.Error())
  17. }
  18. SqlDB.SetMaxIdleConns(20)
  19. SqlDB.SetMaxOpenConns(20)
  20. }

models\users.go

  1. package models
  2. import (
  3. "gin_restful/db"
  4. "log"
  5. )
  6. type Person struct {
  7. Id int `json:"id" form:"id"`
  8. Name string `json:"name" form:"name"`
  9. Telephone string `json:"telephone" form:"telephone"`
  10. }
  11. //插入
  12. func (person *Person) Create() int64 {
  13. rs, err := db.SqlDB.Exec("INSERT into users (name, telephone) value (?,?)", person.Name, person.Telephone)
  14. if err != nil{
  15. log.Fatal(err)
  16. }
  17. id, err := rs.LastInsertId()
  18. if err != nil{
  19. log.Fatal(err)
  20. }
  21. return id
  22. }
  23. //查询一条记录
  24. func (p *Person) GetRow() (person Person, err error) {
  25. person = Person{}
  26. err = db.SqlDB.QueryRow("select id,name,telephone from users where id = ?", p.Id).Scan(&person.Id, &person.Name, &person.Telephone)
  27. return
  28. }
  29. //查询所有记录
  30. func (person *Person) GetRows() (persons []Person, err error) {
  31. rows, err := db.SqlDB.Query("select id,name,telephone from users")
  32. for rows.Next(){
  33. person := Person{}
  34. err := rows.Scan(&person.Id, &person.Name, &person.Telephone)
  35. if err != nil {
  36. log.Fatal(err)
  37. }
  38. persons = append(persons, person)
  39. }
  40. rows.Close()
  41. return
  42. }
  43. //修改
  44. func (person *Person) Update() int64{
  45. rs, err := db.SqlDB.Exec("update users set telephone = ? where id = ?", person.Telephone, person.Id)
  46. if err != nil {
  47. log.Fatal(err)
  48. }
  49. rows, err := rs.RowsAffected()
  50. if err != nil {
  51. log.Fatal(err)
  52. }
  53. return rows
  54. }
  55. //删除一条记录
  56. func Delete(id int) int64 {
  57. rs, err := db.SqlDB.Exec("delete from users where id = ?", id)
  58. if err != nil {
  59. log.Fatal()
  60. }
  61. rows, err := rs.RowsAffected()
  62. if err != nil {
  63. log.Fatal()
  64. }
  65. return rows
  66. }

main.go

  1. package main
  2. import "gin_restful/db"
  3. // go mod init xx_project
  4. // go build
  5. // ./xx_project
  6. func main() {
  7. defer db.SqlDB.Close()
  8. router := initRouter()
  9. router.Run(":8806") // 启动服务了
  10. }

router.go

  1. package main
  2. import (
  3. . "gin_restful/api"
  4. "github.com/gin-gonic/gin"
  5. )
  6. func initRouter() *gin.Engine {
  7. router := gin.Default()
  8. router.GET("/", IndexUsers) //http://192.168.204.132:8806
  9. //路由群组
  10. users := router.Group("api/v1/users")
  11. {
  12. users.GET("", GetAll) //http://192.168.204.132:8806/api/v1/users
  13. users.POST("/add", AddUsers) //http://192.168.204.132:8806/api/v1/users/add
  14. users.GET("/get/:id", GetOne) //http://192.168.204.132:8806/api/v1/users/get/5
  15. users.POST("/update", UpdateUser) //http://192.168.204.132:8806/api/v1/users/update
  16. users.POST("/del", DelUser) //http://192.168.204.132:8806/api/v1/users/del
  17. }
  18. departments := router.Group("api/v1/department")
  19. {
  20. departments.GET("", GetAll) //http://192.168.204.132:8806/api/v1/users
  21. departments.POST("/add", AddUsers) //http://192.168.204.132:8806/api/v1/users/add
  22. departments.GET("/get/:id", GetOne) //http://192.168.204.132:8806/api/v1/users/get/5
  23. departments.POST("/update", UpdateUser) //http://192.168.204.132:8806/api/v1/users/update
  24. departments.POST("/del", DelUser) //http://192.168.204.132:8806/api/v1/users/del
  25. }
  26. return router
  27. }

8.1 gin + mysql rest full api –增

8.2 gin + mysql rest full api –改

http://192.168.204.132:8806/api/v1/users/update

8.3 gin + mysql rest full api –查

http://192.168.204.132:8806/api/v1/users/get/5

8.4 gin + mysql rest full api –获取所有

http://192.168.204.132:8806/api/v1/users

8.5 gin + mysql rest full api –删除 

代码仓库

github.com/yunixiangfeng/gin_restful

2.3 GO微信后台开发实战

微信公众号号后台开发

代码仓库

github.com/yunixiangfeng/devops/tree/main/wechat

1 微信公众号开发逻辑

1.1 注册公众号

注册地址: https://mp.weixin.qq.com/cgi-bin/registermidpage?action=index&lang=zh_CN&token=

1.2 开发者权限

进入公众号管理页面,下拉左边侧

1.3 微信公众号后台接口权限

普通用户只要是接收消息和自动回复消息的权限

1.4 公众号消息回复

1.5 服务器配置

2 HTTP服务

我们先使用原生的http接口来处理,后续改用gin来处理

我们这里主要处理Get和Post方法,见代码

Get:处理token验证 处理token的验证

Post:处理消息回复 处理消息

工程: wechat

main.go

  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "net/http"
  6. "time"
  7. "wechat/wx"
  8. )
  9. const (
  10. logLevel = "dev"
  11. port = 80
  12. token = "NmHrEBBrbIX24JFw" // 生成地址:https://suijimimashengcheng.51240.com/
  13. )
  14. // 处理token的认证
  15. func get(w http.ResponseWriter, r *http.Request) {
  16. client, err := wx.NewClient(r, w, token)
  17. if err != nil {
  18. log.Println(err)
  19. w.WriteHeader(403) // 校验失败
  20. return
  21. }
  22. if len(client.Query.Echostr) > 0 {
  23. w.Write([]byte(client.Query.Echostr)) // 校验成功返回的是Echostr
  24. return
  25. }
  26. w.WriteHeader(403)
  27. return
  28. }
  29. // 微信平台过来消息, 处理 ,然后返回微信平台
  30. func post(w http.ResponseWriter, r *http.Request) {
  31. client, err := wx.NewClient(r, w, token)
  32. if err != nil {
  33. log.Println(err)
  34. w.WriteHeader(403)
  35. return
  36. }
  37. // 到这一步签名已经验证通过了
  38. client.Run()
  39. return
  40. }
  41. // 编译方法
  42. // go mod init wechat
  43. // go build
  44. // ./wechat
  45. // 需要自己修改token,以适应自己公众号的token
  46. func main() {
  47. server := http.Server{
  48. Addr: fmt.Sprintf(":%d", port), // 设置监听地址, ip:port
  49. Handler: &httpHandler{}, // 用什么handler来处理
  50. ReadTimeout: 5 * time.Second, // 读写超时 微信给出来5
  51. WriteTimeout: 5 * time.Second,
  52. MaxHeaderBytes: 0,
  53. }
  54. log.Println(fmt.Sprintf("Listen: %d", port))
  55. log.Fatal(server.ListenAndServe())
  56. defer CloseLog()
  57. }

route.go

  1. package main
  2. import (
  3. "io"
  4. "net/http"
  5. "regexp"
  6. "time"
  7. )
  8. type WebController struct {
  9. Function func(http.ResponseWriter, *http.Request)
  10. Method string
  11. Pattern string
  12. }
  13. var mux []WebController // 自己定义的路由
  14. // ^ 匹配输入字符串的开始位置
  15. func init() {
  16. mux = append(mux, WebController{post, "POST", "^/"})
  17. mux = append(mux, WebController{get, "GET", "^/"})
  18. }
  19. type httpHandler struct{} // 实际是实现了Handler interface
  20. // type Handler interface {
  21. // ServeHTTP(ResponseWriter, *Request)
  22. // }
  23. func (*httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  24. t := time.Now()
  25. for _, webController := range mux { // 遍历路由
  26. // 匹配请求的 r.URL.Path -> webController.Pattern
  27. if m, _ := regexp.MatchString(webController.Pattern, r.URL.Path); m { // 匹配URL
  28. if r.Method == webController.Method { // 匹配方法
  29. webController.Function(w, r) // 调用对应的处理函数
  30. go writeLog(r, t, "match", webController.Pattern)
  31. return
  32. }
  33. }
  34. }
  35. go writeLog(r, t, "unmatch", "")
  36. io.WriteString(w, "")
  37. return
  38. }

log.go

  1. package main
  2. import (
  3. "fmt"
  4. "io"
  5. "log"
  6. "net/http"
  7. "os"
  8. "time"
  9. )
  10. var LogFile *os.File
  11. func init() {
  12. // fmt.Println("log init")
  13. // LogFile, err := os.OpenFile("wechat.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) //打开日志文件,不存在则创建
  14. // if err != nil {
  15. // fmt.Println(err)
  16. // }
  17. // log.SetOutput(LogFile)
  18. log.SetFlags(log.LstdFlags | log.Lshortfile)
  19. }
  20. func CloseLog() {
  21. if LogFile != nil {
  22. LogFile.Close()
  23. }
  24. }
  25. func writeLog(r *http.Request, t time.Time, match string, pattern string) {
  26. if logLevel != "prod" {
  27. d := time.Now().Sub(t)
  28. l := fmt.Sprintf("[ACCESS] | % -10s | % -40s | % -16s | % -10s | % -40s |", r.Method, r.URL.Path, d.String(), match, pattern)
  29. log.Println(l)
  30. }
  31. }
  32. func func_log2fileAndStdout() {
  33. //创建日志文件
  34. f, err := os.OpenFile("test.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
  35. if err != nil {
  36. log.Fatal(err)
  37. }
  38. //完成后,延迟关闭
  39. defer f.Close()
  40. // 设置日志输出到文件
  41. // 定义多个写入器
  42. writers := []io.Writer{
  43. f,
  44. os.Stdout}
  45. fileAndStdoutWriter := io.MultiWriter(writers...)
  46. // 创建新的log对象
  47. logger := log.New(fileAndStdoutWriter, "", log.Ldate|log.Ltime|log.Lshortfile)
  48. // 使用新的log对象,写入日志内容
  49. logger.Println("--> logger : check to make sure it works")
  50. }

 LICENSE

  1. GNU GENERAL PUBLIC LICENSE
  2. Version 3, 29 June 2007
  3. Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
  4. Everyone is permitted to copy and distribute verbatim copies
  5. of this license document, but changing it is not allowed.
  6. Preamble
  7. The GNU General Public License is a free, copyleft license for
  8. software and other kinds of works.

wx\structs.go

  1. package wx
  2. import (
  3. "encoding/xml"
  4. "strconv"
  5. "time"
  6. )
  7. type Base struct {
  8. FromUserName CDATAText
  9. ToUserName CDATAText
  10. MsgType CDATAText
  11. CreateTime CDATAText
  12. }
  13. func (b *Base) InitBaseData(w *WeixinClient, msgtype string) {
  14. b.FromUserName = value2CDATA(w.Message["ToUserName"].(string))
  15. b.ToUserName = value2CDATA(w.Message["FromUserName"].(string))
  16. b.CreateTime = value2CDATA(strconv.FormatInt(time.Now().Unix(), 10))
  17. b.MsgType = value2CDATA(msgtype)
  18. }
  19. type CDATAText struct {
  20. Text string `xml:",innerxml"`
  21. }
  22. type TextMessage struct {
  23. XMLName xml.Name `xml:"xml"`
  24. Base
  25. Content CDATAText
  26. }

wx\utils.go

  1. package wx
  2. func value2CDATA(v string) CDATAText {
  3. return CDATAText{"<![CDATA[" + v + "]]>"}
  4. }

wx\wx.go

  1. package wx
  2. import (
  3. "crypto/sha1"
  4. "encoding/xml"
  5. "errors"
  6. "fmt"
  7. "io/ioutil"
  8. "log"
  9. "net/http"
  10. "sort"
  11. "github.com/clbanning/mxj"
  12. )
  13. type weixinQuery struct {
  14. Signature string `json:"signature"`
  15. Timestamp string `json:"timestamp"`
  16. Nonce string `json:"nonce"`
  17. EncryptType string `json:"encrypt_type"`
  18. MsgSignature string `json:"msg_signature"`
  19. Echostr string `json:"echostr"`
  20. }
  21. type WeixinClient struct {
  22. Token string
  23. Query weixinQuery // 请求的一些参数
  24. Message map[string]interface{}
  25. Request *http.Request
  26. ResponseWriter http.ResponseWriter
  27. Methods map[string]func() bool
  28. }
  29. /// 请求数据Request, 返回数据ResponseWriter, token是自己的
  30. func NewClient(r *http.Request, w http.ResponseWriter, token string) (*WeixinClient, error) {
  31. weixinClient := new(WeixinClient)
  32. weixinClient.Token = token // 获取本地的token
  33. weixinClient.Request = r
  34. weixinClient.ResponseWriter = w
  35. weixinClient.initWeixinQuery()
  36. log.Println("Signature:", weixinClient.Query.Signature)
  37. if weixinClient.Query.Signature != weixinClient.hashcode() { // 签名认证
  38. return nil, errors.New("Invalid Signature.")
  39. }
  40. return weixinClient, nil
  41. }
  42. func (this *WeixinClient) initWeixinQuery() {
  43. var q weixinQuery
  44. log.Println("URL:", this.Request.URL.Path, ", RawQuery:", this.Request.URL.RawPath)
  45. q.Nonce = this.Request.URL.Query().Get("nonce")
  46. q.Echostr = this.Request.URL.Query().Get("echostr")
  47. q.Signature = this.Request.URL.Query().Get("signature")
  48. q.Timestamp = this.Request.URL.Query().Get("timestamp")
  49. q.EncryptType = this.Request.URL.Query().Get("encrypt_type")
  50. q.MsgSignature = this.Request.URL.Query().Get("msg_signature")
  51. this.Query = q
  52. }
  53. // 根据 Token Timestamp Nonce 生成对应的校验码, Token是不能明文传输的
  54. func (this *WeixinClient) hashcode() string {
  55. strs := sort.StringSlice{this.Token, this.Query.Timestamp, this.Query.Nonce} // 使用本地的token生成校验
  56. sort.Strings(strs)
  57. str := ""
  58. for _, s := range strs {
  59. str += s
  60. }
  61. h := sha1.New()
  62. h.Write([]byte(str))
  63. return fmt.Sprintf("%x", h.Sum(nil))
  64. }
  65. // 读取消息,解析XML
  66. func (this *WeixinClient) initMessage() error {
  67. body, err := ioutil.ReadAll(this.Request.Body)
  68. if err != nil {
  69. return err
  70. }
  71. m, err := mxj.NewMapXml(body)
  72. if err != nil {
  73. return err
  74. }
  75. if _, ok := m["xml"]; !ok {
  76. return errors.New("Invalid Message.")
  77. }
  78. message, ok := m["xml"].(map[string]interface{})
  79. if !ok {
  80. return errors.New("Invalid Field `xml` Type.")
  81. }
  82. this.Message = message // 保存消息
  83. log.Println(this.Message)
  84. return nil
  85. }
  86. func (this *WeixinClient) text() {
  87. inMsg, ok := this.Message["Content"].(string) // 读取内容
  88. if !ok {
  89. return
  90. }
  91. var reply TextMessage
  92. reply.InitBaseData(this, "text")
  93. reply.Content = value2CDATA(fmt.Sprintf("我收到的是:%s", inMsg)) // 把消息再次封装
  94. replyXml, err := xml.Marshal(reply) // 序列化
  95. if err != nil {
  96. log.Println(err)
  97. this.ResponseWriter.WriteHeader(403)
  98. return
  99. }
  100. this.ResponseWriter.Header().Set("Content-Type", "text/xml") // 数据类型text/xml
  101. this.ResponseWriter.Write(replyXml) // 回复微信平台
  102. }
  103. func (this *WeixinClient) Run() {
  104. err := this.initMessage()
  105. if err != nil {
  106. log.Println(err)
  107. this.ResponseWriter.WriteHeader(403)
  108. return
  109. }
  110. MsgType, ok := this.Message["MsgType"].(string)
  111. if !ok {
  112. this.ResponseWriter.WriteHeader(403)
  113. return
  114. }
  115. switch MsgType {
  116. case "text":
  117. this.text() // 处理文本消息
  118. break
  119. default:
  120. break
  121. }
  122. return
  123. }

.github\FUNDING.yml

  1. # These are supported funding model platforms
  2. # leeeboo
  3. github: [wtlyy]

3 token机制

解析请求中的GET参数

微信公众号签名验证的方法

参考:

https://developers.weixin.qq.com/doc/offiaccount/Getting_Started/Getting_Started_Guide.html

源码:3-1-token.go

  1. package main
  2. import (
  3. "bytes"
  4. "crypto/rand"
  5. "crypto/sha1"
  6. "fmt"
  7. "math/big"
  8. "sort"
  9. "strconv"
  10. "time"
  11. )
  12. func CreateRandomString(len int) string {
  13. var container string
  14. var str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
  15. b := bytes.NewBufferString(str)
  16. length := b.Len()
  17. bigInt := big.NewInt(int64(length))
  18. for i := 0; i < len; i++ {
  19. randomInt, _ := rand.Int(rand.Reader, bigInt)
  20. container += string(str[randomInt.Int64()])
  21. }
  22. return container
  23. }
  24. // 根据 Token Timestamp Nonce 生成对应的校验码, Token是不能明文传输的
  25. func GenerateSignature(token string) (timestamp string, nonce string, signature string) {
  26. nonce = CreateRandomString(10)
  27. timestamp = strconv.FormatInt(time.Now().Unix(), 10) //int64转字符串
  28. // 排序 微信约定好的
  29. strs := sort.StringSlice{token, timestamp, nonce} // 使用本地的token生成校验
  30. sort.Strings(strs) // strs: [1607173019 qing qvCyrKEuoS]
  31. fmt.Println("strs:", strs) // 排序
  32. str := ""
  33. for _, s := range strs {
  34. str += s // 拼接字符串
  35. }
  36. fmt.Println("str:", str) //str: 1607173019qingqvCyrKEuoS
  37. h := sha1.New() // 完全都是自己的服务的时候 你这里你用md5
  38. h.Write([]byte(str)) // 转成byte
  39. signature = fmt.Sprintf("%x", h.Sum(nil)) // h.Sum(nil) 做hash 79efadd80a344c0b73b3bd2c403184f7425a5a67
  40. return
  41. }
  42. func VerifySignature(token string, timestamp string, nonce string, signature string) bool {
  43. // str = token + timestamp + nonce
  44. strs := sort.StringSlice{token, timestamp, nonce} // 使用本地的token生成校验
  45. sort.Strings(strs)
  46. str := ""
  47. for _, s := range strs {
  48. str += s
  49. }
  50. h := sha1.New() // 完全都是自己的服务的时候 你这里你用md5
  51. h.Write([]byte(str))
  52. return fmt.Sprintf("%x", h.Sum(nil)) == signature
  53. }
  54. func main() {
  55. token := "qing"
  56. // 产生签名
  57. timestamp, nonce, signature := GenerateSignature(token) // 发送服务器的时候是发送 timestamp, nonce, signature
  58. fmt.Printf("1. token %s -> 产生签名:%s, timestamp:%s, nonce:%s\n", token, signature, timestamp, nonce)
  59. // 验证签名
  60. ok := VerifySignature(token, timestamp, nonce, signature) // 服务进行校验
  61. if ok {
  62. fmt.Println("2. 验证签名正常")
  63. } else {
  64. fmt.Println("2. 验证签名失败")
  65. }
  66. }

3.1 token算法

参考:

https://developers.weixin.qq.com/doc/offiaccount/Getting_Started/Getting_Started_Guide.html

按照字母排列顺序

参数 描述

signature

微信加密签名,signature结合了开

发者填写的token参数和请求中的

timestamp参数、nonce参数。

timestamp 时间戳

nonce 随机数

echostr

随机字符串

如果服务器校验成功,返回echostr

如果校验失败,返回””字符串

验证方法
1. 服务器端获取 token , nonce , timestamp
成列表
2. 列表排序
3. 排序后的元素进行摘要
4. 摘要比对 signature
5. 响应 echostr

3.2 token算法-流程图

验证方法

1.服务器端获取token,nonce,timestamp组

成列表

2.列表排序

3.排序后的元素进行摘要

4.摘要比对signature

5.响应echostr

参考:

https://developers.weixin.qq.com/doc/offiaccount/Getting_Started/Getting_Started_Guide.html

4 XML解析

微信消息采用 XML 进行封装,所以我们需要先学习 XML 内容解析

4.1 XML解析-解析XML

在代码里,先针对xml的格式,创建对应的struct结构体

4-1-xml.go

  1. package main
  2. import (
  3. "encoding/xml"
  4. "fmt"
  5. "io/ioutil"
  6. "os"
  7. )
  8. // 如果struct中有一个叫做XMLName,且类型为xml.Name字段,
  9. // 那么在解析的时候就会保存这个element的名字到该字段, 比如这里的config
  10. type SConfig struct {
  11. XMLName xml.Name `xml:"config"` // 指定最外层的标签为config
  12. SmtpServer string `xml:"smtpServer"` // 读取smtpServer配置项,并将结果保存到SmtpServer变量中
  13. SmtpPort int `xml:"smtpPort"`
  14. Sender string `xml:"sender"`
  15. SenderPasswd string `xml:"senderPasswd"`
  16. Receivers SReceivers `xml:"receivers"` // 读取receivers标签下的内容,以结构方式获取
  17. }
  18. type SReceivers struct {
  19. Age int `xml:"age"`
  20. Flag string `xml:"flag,attr"` // 读取flag属性
  21. User []string `xml:"user"` // 读取user数组
  22. Script string `xml:"script"` // 读取 <![CDATA[ xxx ]]> 数据
  23. }
  24. func main() {
  25. file, err := os.Open("4-1-xml.xml") // For read access.
  26. if err != nil {
  27. fmt.Printf("error: %v", err)
  28. return
  29. }
  30. defer file.Close()
  31. data, err := ioutil.ReadAll(file)
  32. if err != nil {
  33. fmt.Printf("error: %v", err)
  34. return
  35. }
  36. v := SConfig{}
  37. err = xml.Unmarshal(data, &v) // 反序列化
  38. if err != nil {
  39. fmt.Printf("error: %v", err)
  40. return
  41. }
  42. fmt.Println("文本:", v)
  43. fmt.Println("解析结果:")
  44. fmt.Println("XMLName : ", v.XMLName)
  45. fmt.Println("SmtpServer : ", v.SmtpServer)
  46. fmt.Println("SmtpPort : ", v.SmtpPort)
  47. fmt.Println("Sender : ", v.Sender)
  48. fmt.Println("SenderPasswd : ", v.SenderPasswd)
  49. fmt.Println("Receivers.Flag : ", v.Receivers.Flag)
  50. for i, element := range v.Receivers.User {
  51. fmt.Println(i, element)
  52. }
  53. }

4-1-xml.xml 

  1. <config>
  2. <smtpServer>smtp.qq.com</smtpServer>
  3. <smtpPort>25</smtpPort>
  4. <sender>you@qq.com</sender>
  5. <senderPasswd>123456</senderPasswd>
  6. <receivers flag="true">
  7. <user>ki@qq.gom</user>
  8. <user>dar@q.gom</user>
  9. <script>
  10. <![CDATA[
  11. function &%< matchwo(a,b) {
  12. if (a < b && a < 0) then {
  13. return 1;
  14. } else {
  15. return 0;
  16. }
  17. }
  18. ]]>
  19. </script>
  20. </receivers>
  21. </config>

4.2 XML解析-解析CDATA

XML 文档中的所有文本均会被解析器解析。

只有 CDATA 区段中的文本会被解析器忽略。

术语 CDATA 是不应该由 XML 解析器解析的文本数据。

像 "<" 和 "&" 字符在 XML 元素中都是非法的。

"<" 会产生错误,因为解析器会把该字符解释为新元素的开始。

"&" 会产生错误,因为解析器会把该字符解释为字符实体的开始。

某些文本,比如 JavaScript 代码,包含大量 "<" 或 "&" 字符。为了避免错误,可以将脚本代码定义为 CDATA。

CDATA 部分中的所有内容都会被解析器忽略。

CDATA 部分由 “ <![CDATA[ " 开始,由 "]]>" 结束:

4-2-CDATA.go

  1. package main
  2. import (
  3. "encoding/xml"
  4. "fmt"
  5. "strconv"
  6. "time"
  7. "github.com/clbanning/mxj"
  8. )
  9. // tag中含有"-"的字段不会输出
  10. // tag中含有"name,attr",会以name作为属性名,字段值作为值输出为这个XML元素的属性,如上version字段所描述
  11. // tag中含有",attr",会以这个struct的字段名作为属性名输出为XML元素的属性,类似上一条,只是这个name默认是字段名了。
  12. // tag中含有",chardata",输出为xml的 character data而非element。
  13. // tag中含有",innerxml",将会被原样输出,而不会进行常规的编码过程
  14. // tag中含有",comment",将被当作xml注释来输出,而不会进行常规的编码过程,字段值中不能含有"--"字符串
  15. // tag中含有"omitempty",如果该字段的值为空值那么该字段就不会被输出到XML,空值包括:false、0、nil指针或nil接口,任何长度为0的array, slice, map或者string
  16. type CDATAText struct {
  17. Text string `xml:",innerxml"`
  18. }
  19. type Base struct {
  20. FromUserName CDATAText
  21. ToUserName CDATAText
  22. MsgType CDATAText
  23. CreateTime CDATAText
  24. }
  25. // 文本消息的封装
  26. type TextMessage struct {
  27. XMLName xml.Name `xml:"xml"`
  28. Base
  29. Content CDATAText
  30. }
  31. // 图片消息的封装
  32. type PictureMessage struct {
  33. XMLName xml.Name `xml:"xml"`
  34. Base
  35. PicUrl CDATAText
  36. MediaId CDATAText
  37. }
  38. func value2CDATA(v string) CDATAText {
  39. return CDATAText{"<![CDATA[" + v + "]]>"}
  40. }
  41. func main() {
  42. // 1. 解析 XML
  43. xmlStr := `<xml>
  44. <ToUserName><![CDATA[toUser]]></ToUserName>
  45. <FromUserName><![CDATA[fromUser]]></FromUserName>
  46. <CreateTime>1348831860</CreateTime>
  47. <MsgType><![CDATA[text]]></MsgType>
  48. <Content><![CDATA[this is a test]]></Content>
  49. <MsgId>1234567890123456</MsgId>
  50. </xml>`
  51. var Message map[string]interface{}
  52. m, err := mxj.NewMapXml([]byte(xmlStr)) //使用了第三方的库
  53. if err != nil {
  54. return
  55. }
  56. if _, ok := m["xml"]; !ok {
  57. fmt.Println("Invalid Message.")
  58. return
  59. }
  60. fmt.Println("-->m:", m)
  61. message, ok := m["xml"].(map[string]interface{}) // 把xml对应的值读取出来
  62. if !ok {
  63. fmt.Println("Invalid Field `xml` Type.")
  64. return
  65. }
  66. Message = message
  67. fmt.Println("1. 解析出来:", Message) // xml对应的字段还是在map
  68. // 2. 封装XML
  69. var reply TextMessage
  70. inMsg, ok := Message["Content"].(string) // 读取内容 .(string)转成什么类型的数据
  71. if !ok {
  72. return
  73. }
  74. fmt.Println("Message[ToUserName].(string):", Message["ToUserName"].(string)) // 如果服务器要处理
  75. // 封装回复消息,需要添加 CDATA
  76. reply.Base.FromUserName = value2CDATA(Message["ToUserName"].(string))
  77. reply.Base.ToUserName = value2CDATA(Message["FromUserName"].(string))
  78. reply.Base.CreateTime = value2CDATA(strconv.FormatInt(time.Now().Unix(), 10))
  79. reply.Base.MsgType = value2CDATA("text")
  80. reply.Content = value2CDATA(fmt.Sprintf("我收到的是:%s", inMsg))
  81. replyXml, err := xml.Marshal(reply) // 得到的是byte
  82. fmt.Println("2. 生成XML:", string(replyXml)) // []byte -> string
  83. fmt.Println("2. 生成XML:", []byte(string(replyXml))) // string -> []byte
  84. }

<xml> <ToUserName><![CDATA[toUser]]></ToUserName>

<FromUserName><![CDATA[fromUser]]></FromUserName>

<CreateTime>1348831860</CreateTime>

<MsgType><![CDATA[text]]></MsgType>

<Content><![CDATA[this is a test]]></Content>

<MsgId>1234567890123456</MsgId> </xml>

5 你问我答

1)理解被动消息的含义 2)理解收\发消息机制 预实现功能: 粉丝给公众号一条文本消息,

公众号立马回复一条文本消息给粉丝,不需要通过公众平台网页操作。

 

5.1 你问我答-接收消息协议

参考:

https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_standar

d_messages.html

参数 描述

ToUserName 开发者微信号

FromUserName 发送方帐号(一个OpenID)

CreateTime 消息创建时间 (整型)

MsgType 消息类型,文本为text

Content 文本消息内容

MsgId 消息id,64位整型

<xml> <ToUserName><![CDATA[toUser]]></ToUserName>

<FromUserName><![CDATA[fromUser]]></FromUserName>

<CreateTime>1348831860</CreateTime>

<MsgType><![CDATA[text]]></MsgType>

<Content><![CDATA[this is a test]]></Content>

<MsgId>1234567890123456</MsgId> </xml>

5.2 你问我答-被动回复消息协议

参考:

https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Passive_user_reply

_message.html#0

参数 是否必须 描述

ToUserName 是 接收方帐号(收到的OpenID)

FromUserName 是 开发者微信号

CreateTime 是 消息创建时间 (整型)

MsgType 是 消息类型,文本为text

Content 是

回复的消息内容(换行:在

content中能够换行,微信客户端

就支持换行显示)

6 go语言之进阶篇正则表达式

参考官网: https://studygolang.com/pkgdoc

范例:https://www.cnblogs.com/nulige/p/10260149.html

3.1 流媒体知识精讲和架构设计

1.1 直播应用场景

1.2 常用直播功能项 常用

1.3 直播框架示例1

1.4 直播框架示例2-某直播学院框架 

2 直播架构-基本逻辑 

2.0 常见流媒体协议 直播流程

RTP实时传输协议(Real-time Transport Protocol或简写RTP)

RTCP RTP Control Protocol

RTSP (Real Time Streaming Protocol),RFC2326,实时流传输协议

RTMP RTMP是Real Time Messaging Protocol(实时消息传输协议)

HTTP-FLV

HTTP-MP4

HLS

WebRTC

2.1 直播架构-基本流程 软件编码–提高机器的兼容性

2.2 直播常用工具

◼ 推流工具:

• ffmpeg:https://www.ffmpeg.org/download.html

• OBS studio:https://obsproject.com/download

◼ 拉流工具

• ffplay(): https://www.ffmpeg.org/download.html

• cutv www.cutv.com/demo/live_test.swf flash播放器

• vlc

• ijkplayer (基于ffplay): 一个基于FFmpeg的开源Android/iOS视频播放器

(开源)

API易于集成;

编译配置可裁剪,方便控制安装包大小;

支持硬件加速解码,更加省电

简单易用,指定拉流URL,自动解码播放.

◼ 压测工具

• st-load

2.3 流媒体服务器

SRS :一款国人开发的优秀开源流媒体服务器系统

BMS :也是一款流媒体服务器系统,但不开源,是SRS的商业版,

比SRS功能更多

nginx :免费开源web服务器,也常用来配置流媒体服务器。

集成Rtmp_module即可。

Red5:是java写的一款稳定的开源的rtmp服务器。

3 直播框架之CDN

4 拉流框架 

1. 模块初始化顺序

2. 音视频数据队列(packetqueue)控制

3. 音视频解码

4. 音频重采样

5. 视频尺寸变换

6. 音视频帧队列

7. 音视频同步

8. 关键时间点check

9. 其他

4.1 模块初始化顺序

推流模块(网络连接耗时) > 音视频编码模块 >音视频采集模块()

音视频输出模块 >音视频解码模块 > 拉流模块

本质上来讲,就是在数据到来之前准备好一切工作

4.2 音视频数据队列

音视频队列涉及到

1. Audio PacketQueue 还没有解码的

2. Video PacketQueue

两者独立

队列设计要点:

1. 可控制队列大小

1. 根据packet数进行控制

2. 根据总的duration进行控制 音频48khz, 21.3毫秒, 21.3*20 = 426ms

3. 支持packet的size进行入队列累加,出队列则减, 300,200,400, 字节累加

2. 支持packet数量统计

3. 支持packet的duration进行入队列累加,出队列则减

4. 支持阻塞和唤醒

目的:

1. 统计延迟(缓存时间长度)

4.3 音视频数据队列

音视频队列涉及到

1. Audio PacketQueue

2. Video PacketQueue

两者独立

队列设计要点:

1. 可控制队列大小

1. 根据packet数进行控制

2. 根据总的duration进行控制

2. 支持packet数量统计

3. 支持packet的size进行入队列累加,出队列则减

4. 支持packet的duration进行入队列累加,出队列则

5. 支持阻塞和唤醒

4.4 音视频解码

关键点:

1. 编码前: dts

2. 编码后: pts

3. packet释放

4. frame释放

5. 返回值处理

4.5 音频重采样

音频重采样模块AudioResampler:

注意重采样后不能直接使用linesize进行大小判断,需要使用

int size = av_get_bytes_per_sample((AVSampleFormat)(dstframe->format))

* dstframe->channels * dstframe->nb_samples ;

 4.6 视频尺寸变换

图像尺寸变换模块ImageScaler:变换尺寸大小

性能的提升可以考虑 libyuv

4.7 音视频解码后帧队列

FrameQueue

解码后的数据量比较大,需要要控制解码后帧队列的大小

参考ffplay固定大小

#define VIDEO_PICTURE_QUEUE_SIZE 3 // 图像帧缓存数量

#define SUBPICTURE_QUEUE_SIZE 16 // 字幕帧缓存数量

#define SAMPLE_QUEUE_SIZE 9 // 采样帧缓存数量

#define FRAME_QUEUE_SIZE FFMAX(SAMPLE_QUEUE_SIZE,

FFMAX(VIDEO_PICTURE_QUEUE_SIZE, SUBPICTURE_QUEUE_SIZE))

4.8 音视频同步

Avsync模块

目前只支持audio master的方式。

4.9 各个模块关键时间点的监测

4.10 其他

1. 客户端的首帧秒开,本质上就是不做同步先把第一帧显示出来。

2. 推流没有问题时,如果拉流不能正常播放:

1. 没有声音:dump rtmp拉流后的数据是否可以正常播放

2. 声音异常:是否有解码错误报告,重采样前的pcm数据是否正常

3. 没有图像: dump rtmp拉流后的数据是否可以正常播放

4. 画面异常:是否有解码错误报告,scale前的数据是否正常

服务器首帧秒开:这个功能不能降低延迟

5 直播推流框架-模块初始化顺序

推流模块(网络连接耗时) > 音视频编码模块 >音视频采集模块()

 

5.1 采集时间戳-帧间隔模式

5.2 采集时间戳-直接系统时间模式

5.3 采集时间戳-帧间隔+直接系统时间模式

5.4 音视频编解码模块

5.5 音视频队列的控制

5.6 关键时间点

5.7 其他

6 WebRTC

信令服务器由go语言实现

搭建自己的音视频通话web

WebRTC简介

WebRTC通话模型

WebRTC通话模型 Mesh一对一通话网络模型

WebRTC通话模型 Mesh多方通话网络模型

WebRTC Mesh 网络拓扑结构的优劣

WebRTC通话模型 SFU通话网络模型

WebRTC通话模型 MCU通话网络模型

WebRTC 通话网络模型选择

WebRTC建构多人会议系统

WebRTC应用领域

基于webrtc的开源方案

国内音视频通话方案公司

WebRTC开发进阶-SFU级联

学习资源

WebRTC视频通话中最多能容纳多少用户? https://www.jianshu.com/p/9ef708f93499

多媒体开发 https://www.jianshu.com/c/e5b30935c054

WebRTC中文网 https://webrtc.org.cn

WebRTC官网 https://webrtc.org/

WebRTC范例 https://webrtc.github.io/samples/

AppRTC基本原理

AppRTC Demo搭建注意事项

WebRTC通话信令基本设计 – 媒体协商+网络信息candidate + 房间人员

管理

1对1通话信令分析

一对一通话实战复习

多方通话

逻辑分析

3.2 工程代码-apidefs结构体定义

代码仓库

github.com/yunixiangfeng/devops/tree/main/video_server

2 架构分析和API设计

1. 技术要点分析

go流媒体网站技术要点
前后端分离的系统架构设计
RESTful 风格 API 设计与实现
Go 实现 web 服务
系统的服务化解耦
go channel 和并发模型的实践
使用 go 原生 template 完成 web UI 的实现
总体架构

什么是前后端解耦

◼ 前后端解耦是时下流行的Web网站架构

◼ 前端页面和服务通过普通的Web引擎渲染

◼ 后端数据通过渲染后的脚本调用后处理呈现

前后端解耦的优势

◼ 解放生产力

◼ 松耦合的架构更灵活,部署更方便,更符合微服务的设计特性

◼ 性能的提升、可靠性的提升

前后端解耦的缺点

◼ 工作量大

◼ 前后端分离带来团队成本以及学习成本

◼ 系统复杂度加大

2. REST API设计

API

◼ REST是Representational State Transfer(表现层状态转移)的缩写

◼ 常用的行为(查看(view),创建(create),编辑(edit)和删除(delete))

都可以直接映射到HTTP 中已实现的GET,POST,PUT和DELETE方法。

◼ 通常使用Json作为数据封装格式

◼ 统一接口

◼ 无状态

◼ 可缓存

API设计原则

◼ 以URL(统一资源定位符)风格设计API

◼ 通过不同的Method(GET/POST/PUT/DELETE)来区分对资源的CURD

◼ 返回码(Status code)符合HTTP资源描述的规定

3. API设计实战

API设计

API设计:用户

API设计:视频 

API设计:评论  

数据库设计-用户 

CREATE TABLE `video_server`.`users` (
`id` int unsigned primary key auto_increment,
`login_name` varchar(64) unique key,
`pwd` text
);
数据库设计- 视频
CREATE TABLE `video_server`.`video_info` (
`id` varchar(64) NOT NULL,
`author_id` int(10) NULL,
`name` text NULL,
`display_ctime` text NULL,
`create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
);
数据库设计- 评论
CREATE TABLE `video_server`.`comments` (
`id` varchar(64) NOT NULL,
`video_id` varchar(64) NULL,
`author_id` int(10) NULL,
`content` text NULL,
`time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
);

数据库设计-会话

CREATE TABLE `video_server`.`sessions` (

`session_id` varchar(244) NOT NULL,

`TTL` tinytext NULL,

`login_name` text NULL,

PRIMARY KEY (`session_id`)

);

数据库设计-待删除视频表

CREATE TABLE `video_server`.`video_del_rec` (

`video_id` varchar(64) NOT NULL,

PRIMARY KEY (`video_id`)

);

4. 端口开放

端口开放

◼ api 10000

◼ scheduler 10001

◼ streamserver 1002

◼ web 10003

代码仓库

github.com/yunixiangfeng/video_server 

3.3 stream-scheduler-web详细设计

1. streamserver设计

0 总体架构

1 Streamserver

◼ 静态视频,

◼ 独立的服务,可独立部署

◼ 统一的API格式

1.1 Stream Server-对外接口

◼ /videos/:vid-id -> streamHandler 文件播放

◼ /upload/:vid-id -> uploadHandler 文件上传

1.2 代码整体设计

◼ 流控机制

◼ middleware的作用

1.3 流控机制-token bucket
为什么需要流控
拿到 token 才能继续进一步处理
为什么不用数组
go routine 是并发的,如果用变量则需要加锁
go 处理并发用 channel
limiter.go

1.4 在http middleware加入流控

type middleWareHandler struct {

r *httprouter.Router

l *ConnLimiter

}

func NewMiddleWareHandler(r *httprouter.Router,

cc int) http.Handler {

m := middleWareHandler{}

m.r = r

m.l = NewConnLimiter(cc) // 限制数量

return m

}

1.5 stream handler的实现

◼ streamHandler 读取文件播放

◼ uploadHandler 上传文件

2. scheduler设计

2 Scheduler调度器

◼ 什么是scheduler

◼ 为什么需要scheduler

◼ scheduler通常做什么

异步任务、延时任务、定时任务

2.1 Scheduler包含什么

◼ REST ful 的HTTP server

◼ Timer

◼ 生产者消费者模型下的task runner

2.2 Scheduler架构

2.3 代码架构

◼ dbops 数据库查询和删除

◼ taskrunner 执行任务

◼ runner.go 处理任务流程(生产消费模型)

◼ tasks.go 执行任务(具体的生产、消费)

◼ trmain.go 实现定时任务,比如每3秒执行一次

◼ handlers.go 处理api

◼ main.go程序入口

◼ response.go http响应封装

2.4 task实现

type Runner struct {

Controller controlChan

Error controlChan

Data dataChan

dataSize int

longLived bool

Dispatcher fn

Executor fn

}

◼ Controller 流程控制channel

◼ Error 错误控制channel

◼ Data 真正的任务数据channel

runner.go
tasks.go

2.5 timer实现

trmain.go

type Worker struct {

ticker *time.Ticker

runner *Runner

}

通过定时器实现定时任务

3. web设计

3 web前端服务

◼ Go模板引擎

◼ API处理

◼ API透传

◼ proxy代理

3.0 代码架构

◼ templates html模板

◼ client.go 处理api透传

◼ defs.go 结构体定义

◼ handlers.go api入口处理函数

◼ main.go 主入口

3.1 Go的模板引擎

◼ 模板引擎是将HTML解析和元素预置替换生成最终页面的工具

◼ Go的模板有两种text/template和html/template

◼ Go的模板采用动态生成的模式

3.2 Go的模板引擎-渲染流程

3.3 页面渲染

◼ 主页渲染:homeHandler

◼ 用户页渲染:userHomeHandler

3.4 api透传模块实现

◼ apiHandler 处理逻辑分析

3.5 proxy转发的实现

◼ proxyHandler处理逻辑非分析

代码仓库

github.com/yunixiangyu/devops

4.1 Gin和jwt验证实战

代码仓库

github.com/yunixiangyu/devops/tree/main/gin_practice

gin实战

N ⼊⻔

O RESTful API

结构体

基本的REST ful范例

路由参数

:路由

*路由

P URL查询参数

Gin获取查询参数

原理解析

Q 接收数组和 Map

QueryArray

QueryMap

QueryMap 的原理

T 表单参数

Form 表单

Gin 接收表单数据

PostFormArray()⽅法获取表单参数

Gin PostForm系列⽅法

实现原理

⼩结

T 上传⽂件

上传单个⽂件FormFile

上传多个⽂件MultipartForm

V 分组路由

分组路由

路由中间件

分组路由嵌套

原理解析

GIn中间件

Gin默认中间件

中间件实现HTTP Basic Authorization

针对特定URL的Basic Authorization

⾃定义中间件

V 再谈中间件

定义中间件

⼊⻔案例

注册中间件

为全局路由注册

为某个路由单独注册

为路由组注册中间件

跨中间件存取值

中间件注意事项

gin中间件中使⽤goroutine

gin框架中间件c.Next()理解

W json、struct、xml、yaml、protobuf渲染

各种数据格式的响应

范例

X HTML模板渲染

最简单的例⼦

复杂点的例⼦

静态⽂件⽬录

重定向

NL 异步协程

NN Gin源码简要分析

概述

从DEMO开始

ENGINE

ROUTERGROUP & METHODTREE

.路由注册

路由分组

.中间件挂载

.路由匹配

HANDLERFUNC

CONTEXT

.调⽤链流转和控制

.参数解析

.响应处理

总结

参考⽂献

官⽅⽹站

https://gin-gonic.com/

⼯程代码

https://github.com/gin-gonic/gin.git

测试范例

https://github.com/gin-gonic/examples.git

中间件

https://github.com/gin-gonic/contrib.git

gin框架-JWT验证实践

N token、cookie、session的区别

Cookie

Session

Token

O Json-Web-Token(JWT)介绍

JWT Token组成部分

签名的⽬的

什么时候⽤JWT

JWT(Json Web Tokens)是如何⼯作的

P 基于Token的身份认证和基于服务器的身份认证

N.基于服务器的认证

O.Session和JWT Token的异同

P.基于Token的身份认证如何⼯作

Q.⽤Token的好处

S.JWT和OAuth的区别

Q Go范例

S JWT资源

T 使⽤Gin框架集成JWT

⾃定义中间件

定义jwt编码和解码逻辑

定义登陆验证逻辑

定义普通待验证接⼝

验证使⽤JWT后的接⼝

V 使⽤go进⾏ JWT 验证

使⽤ JWT 的场景

JWT 的结构

总结

4.2 Go ORM实战

 代码仓库 github.com/yunixiangfeng/devops/tree/main/jwt-gorm

GORM实践

L 什么是ORM?为什么要⽤ORM?

N GORM⼊⻔指南

gorm介绍

安装

连接MySQL

GORM基本示例

GORM操作MySQL

O GORM Model定义

gorm.Model

模型定义示例

结构体标记(tags)

⽀持的结构体标记(Struct tags)

关联相关标记(tags)

范例

P 主键、表名、列名的约定

主键(Primary Key)

表名(Table Name)

列名(Column Name)

时间戳跟踪

CreatedAt

UpdatedAt

DeletedAt

Q CRUD

创建

创建记录

默认值

使⽤指针⽅式实现零值存⼊数据库

使⽤Scanner/Valuer接⼝⽅式实现零值存⼊数据库

扩展创建选项

查询

⼀般查询

Where 条件

普通SQL查询

Struct & Map查询

Not 条件

Or条件

内联条件

额外查询选项

FirstOrInit

Attrs

Assign

FirstOrCreate

Attrs

Assign

⾼级查询

⼦查询

选择字段

排序

数量

偏移

总数

Group & Having

连接

Pluck

扫描

链式操作相关

链式操作

⽴即执⾏⽅法

范围

多个⽴即执⾏⽅法

2

更新

更新所有字段

更新修改字段

更新选定字段

⽆Hooks更新

批量更新

使⽤SQL表达式更新

修改Hooks中的值

其它更新选项

删除

删除记录

批量删除

软删除

物理删除

S gorm-错误处理、事务、SQL构建、通⽤数据库接⼝、连接池、复合主键、⽇志

S.N. 错误处理

S.O. 事务

S.O.N. ⼀个具体的例⼦

S.P. SQL构建

S.P.N. 执⾏原⽣SQL

S.P.O. sql.Row & sql.Rows

S.P.P. 迭代中使⽤sql.Rows的Scan

S.Q. 通⽤数据库接⼝sql.DB

S.Q.N. 连接池

S.S. 复合主键

S.T. ⽇志

S.T.N. ⾃定义⽇志

4.3 go-admin架构分析和环境配置

GitHub - go-admin-team/go-admin: 基于Gin + Vue + Element UI & Arco Design & Ant Design 的前后端分离权限管理系统脚手架(包含了:多租户的支持,基础用户管理功能,jwt鉴权,代码生成器,RBAC资源控制,表单构建,定时任务等)3分钟构建自己的中后台项目;项目文档》:https://www.go-admin.pro V2 Demo: https://vue2.go-admin.dev V3 Demo: https://vue3.go-admin.dev Antd 订阅版:https://antd.go-admin.pro

go-admin架构分析和环境配置

N 简介

N.N 在线体验

N.O 特性

N.P 内置

O 安装

O.N 开发⽬录创建

O.O 获取代码

O.P 编译后端项⽬和修改配置⽂件

O.Q 初始化数据库,以及后端服务启动

⽐如
数据库设置为 go_test ,需要在后台提前创建 create database go_test;
1 # 1 ⾸次配置需要初始化数据库资源信息
2 ./go-admin migrate -c config/settings.yml
3 #2 启动项⽬,也可以⽤ IDE 进⾏调试
4 ./go-admin server -c config/settings.yml

  O.S 前端UI交互端启动说明

PS D:\Workspace\Go\src\devops\goadmin\go-admin-ui> npm i --legacy-peer-deps --registry=https://registry.npm.taobao.org

npm run dev

opensslErrorStack: [ ‘error:03000086:digital envelope routines::initialization error‘ ]

  "scripts": {
    "dev": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve",
    "serve": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve",

O.T 发布⽹⻚

P 架构分析

P.N 接⼝

P.O ⽂件⽬录

Q 问题总结

nodejs let notifier = require('update-notifier')({pkg}) 报错

安装NodeJS和NPM

安装命令

更新npm的包镜像源,⽅便快速下载

安装n管理器(⽤于管理nodejs版本)

npm ERR! cb()never called!的错误

Husky requires Git >=O.NP.L. Got vO.V.Q.

Error NPTT: Incorrect string value: '\xEV\xWW\xBN\xET\xWB\xXP...' for column 'dept_name' at row

mysql数据库表结构导出

重点

搭建go-admin项⽬

整体框架分析

各个⽬录和源码的作⽤

jwt鉴权设计

cobra cmd机制 (k8s) 命令⾏功能⾮常强⼤。

使⽤ go cobra创建命令⾏项⽬

代码仓库 github.com/yunixiangfeng/devops/tree/main/cobra

Cobra介绍

实现没有⼦命令的CLIs程序

实现有⼦命令的CLIs程序

附加命令

4.4 go-admin API和数据库设计分析

go-admin后台设计之casbin权限管理

N 概要

O PERM 模型

O casbin 权限库

casbin的主要特性

casbin不做的事情

核⼼概念

model file

model file 定义语法

policy file

RBAC 示例

定义 model file

定义 policy file

测试代码

多租户示例

定义 model file

定义 policy file

测试代码

Has_Role

例⼦:RBAC

Has Tenant Role

gin+gorm+casbin示例

P 总结

Q 参考⽂档

go-admin后台设计之授权机制

N登录过程分析

O ⽤户权限验证

权限⽣成

权限校验

P ⻆⾊权限验证

⻆⾊规则⽣成

接⼝规则

菜单规则

⻆⾊校验

Q 数据库设计

sys_casbin_rule 权限规则

sys_config 配置信息

sys_dept部⻔信息

sys_menu菜单

sys_post岗位名

sys_role⻆⾊类别

sys_role_dept⻆⾊部⻔

sys_role_menu⻆⾊菜单

sys_user⽤户

sys_category

sys_columns

sys_content

sys_dict_data字典数据

sys_dict_type字典类型

sys_file_dir⽂件⽬录

sys_file_info⽂件信息

sys_job

sys_login_log登录⽇志

1

sys_migration

sys_opera_log操作⽇志

sys_setting系统设置

sys_tables

4.5 go-admin添加应用实战

代码仓库

github.com/yunixiangfeng/devops/tree/main/goadmin

go-admin后台设计-添加应⽤实战

L 主要内容

N 新增模块

O 编写 go-admin 应⽤,第 N 步 ⼿动写代码

开始项⽬

⽤于开发的服务器

创建⽂章功能

app\admin\apis\article.go

  1. package apis
  2. import (
  3. "go-admin/common/models"
  4. "net/http"
  5. "github.com/gin-gonic/gin"
  6. )
  7. // GetArticleList 获取⽂章列表
  8. func GetArticleList(c *gin.Context) {
  9. var res models.Response
  10. res.Data = "hello world !"
  11. c.JSON(http.StatusOK, res.ReturnOK())
  12. }

编写第⼀个接⼝

app\other\router\gen_router.go

  1. package router
  2. import (
  3. "go-admin/app/admin/apis"
  4. "go-admin/app/other/apis/tools"
  5. "github.com/gin-gonic/gin"
  6. jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
  7. )
  8. func init() {
  9. routerCheckRole = append(routerCheckRole, sysNoCheckRoleRouter, registerDBRouter, registerSysTableRouter)
  10. }
  11. func sysNoCheckRoleRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
  12. r1 := v1.Group("")
  13. {
  14. sys := apis.System{}
  15. r1.GET("/captcha", sys.GenerateCaptchaHandler)
  16. }
  17. r := v1.Group("").Use(authMiddleware.MiddlewareFunc())
  18. {
  19. gen := tools.Gen{}
  20. r.GET("/gen/preview/:tableId", gen.Preview)
  21. r.GET("/gen/toproject/:tableId", gen.GenCode)
  22. r.GET("/gen/apitofile/:tableId", gen.GenApiToFile)
  23. r.GET("/gen/todb/:tableId", gen.GenMenuAndApi)
  24. sysTable := tools.SysTable{}
  25. r.GET("/gen/tabletree", sysTable.GetSysTablesTree)
  26. r.GET("/articleList", apis.GetArticleList) // 新加接⼝
  27. }
  28. }
  29. func registerDBRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
  30. db := v1.Group("/db").Use(authMiddleware.MiddlewareFunc())
  31. {
  32. gen := tools.Gen{}
  33. db.GET("/tables/page", gen.GetDBTableList)
  34. db.GET("/columns/page", gen.GetDBColumnList)
  35. }
  36. }
  37. func registerSysTableRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
  38. tables := v1.Group("/sys/tables")
  39. {
  40. sysTable := tools.SysTable{}
  41. tables.Group("").Use(authMiddleware.MiddlewareFunc()).GET("/page", sysTable.GetPage)
  42. tablesInfo := tables.Group("/info").Use(authMiddleware.MiddlewareFunc())
  43. {
  44. tablesInfo.POST("", sysTable.Insert)
  45. tablesInfo.PUT("", sysTable.Update)
  46. tablesInfo.DELETE("/:tableId", sysTable.Delete)
  47. tablesInfo.GET("/:tableId", sysTable.Get)
  48. tablesInfo.GET("", sysTable.GetSysTablesInfo)
  49. }
  50. }
  51. }

 app\other\router\gen_router.go

  1. package router
  2. import (
  3. "go-admin/app/admin/apis"
  4. "go-admin/app/other/apis/tools"
  5. "github.com/gin-gonic/gin"
  6. jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
  7. )
  8. func init() {
  9. routerCheckRole = append(routerCheckRole, sysNoCheckRoleRouter, registerDBRouter, registerSysTableRouter)
  10. }
  11. func sysNoCheckRoleRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
  12. r1 := v1.Group("")
  13. {
  14. sys := apis.System{}
  15. r1.GET("/captcha", sys.GenerateCaptchaHandler)
  16. r1.GET("/articleList", apis.GetArticleList) // 新加接⼝
  17. }
  18. r := v1.Group("").Use(authMiddleware.MiddlewareFunc())
  19. {
  20. gen := tools.Gen{}
  21. r.GET("/gen/preview/:tableId", gen.Preview)
  22. r.GET("/gen/toproject/:tableId", gen.GenCode)
  23. r.GET("/gen/apitofile/:tableId", gen.GenApiToFile)
  24. r.GET("/gen/todb/:tableId", gen.GenMenuAndApi)
  25. sysTable := tools.SysTable{}
  26. r.GET("/gen/tabletree", sysTable.GetSysTablesTree)
  27. }
  28. }
  29. func registerDBRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
  30. db := v1.Group("/db").Use(authMiddleware.MiddlewareFunc())
  31. {
  32. gen := tools.Gen{}
  33. db.GET("/tables/page", gen.GetDBTableList)
  34. db.GET("/columns/page", gen.GetDBColumnList)
  35. }
  36. }
  37. func registerSysTableRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
  38. tables := v1.Group("/sys/tables")
  39. {
  40. sysTable := tools.SysTable{}
  41. tables.Group("").Use(authMiddleware.MiddlewareFunc()).GET("/page", sysTable.GetPage)
  42. tablesInfo := tables.Group("/info").Use(authMiddleware.MiddlewareFunc())
  43. {
  44. tablesInfo.POST("", sysTable.Insert)
  45. tablesInfo.PUT("", sysTable.Update)
  46. tablesInfo.DELETE("/:tableId", sysTable.Delete)
  47. tablesInfo.GET("/:tableId", sysTable.Get)
  48. tablesInfo.GET("", sysTable.GetSysTablesInfo)
  49. }
  50. }
  51. }

path

P 编写 go-admin 应⽤,第 O 步 ⾃动⽣成代码

数据库配置

代码⽣成

表结构导⼊

编辑模板字段

预览代码

⽣成代码

app\admin\apis\article.go

  1. package apis
  2. import (
  3. "fmt"
  4. "github.com/gin-gonic/gin"
  5. "github.com/go-admin-team/go-admin-core/sdk/api"
  6. "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user"
  7. _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response"
  8. "go-admin/app/admin/models"
  9. "go-admin/app/admin/service"
  10. "go-admin/app/admin/service/dto"
  11. "go-admin/common/actions"
  12. )
  13. type Article struct {
  14. api.Api
  15. }
  16. // GetPage 获取Go文章列表
  17. // @Summary 获取Go文章列表
  18. // @Description 获取Go文章列表
  19. // @Tags Go文章
  20. // @Param author query string false "作者"
  21. // @Param content query string false "内容"
  22. // @Param status query string false "状态"
  23. // @Param pageSize query int false "页条数"
  24. // @Param pageIndex query int false "页码"
  25. // @Success 200 {object} response.Response{data=response.Page{list=[]models.Article}} "{"code": 200, "data": [...]}"
  26. // @Router /api/v1/article [get]
  27. // @Security Bearer
  28. func (e Article) GetPage(c *gin.Context) {
  29. req := dto.ArticleGetPageReq{}
  30. s := service.Article{}
  31. err := e.MakeContext(c).
  32. MakeOrm().
  33. Bind(&req).
  34. MakeService(&s.Service).
  35. Errors
  36. if err != nil {
  37. e.Logger.Error(err)
  38. e.Error(500, err, err.Error())
  39. return
  40. }
  41. p := actions.GetPermissionFromContext(c)
  42. list := make([]models.Article, 0)
  43. var count int64
  44. err = s.GetPage(&req, p, &list, &count)
  45. if err != nil {
  46. e.Error(500, err, fmt.Sprintf("获取Go文章失败,\r\n失败信息 %s", err.Error()))
  47. return
  48. }
  49. e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功")
  50. }
  51. // Get 获取Go文章
  52. // @Summary 获取Go文章
  53. // @Description 获取Go文章
  54. // @Tags Go文章
  55. // @Param id path int false "id"
  56. // @Success 200 {object} response.Response{data=models.Article} "{"code": 200, "data": [...]}"
  57. // @Router /api/v1/article/{id} [get]
  58. // @Security Bearer
  59. func (e Article) Get(c *gin.Context) {
  60. req := dto.ArticleGetReq{}
  61. s := service.Article{}
  62. err := e.MakeContext(c).
  63. MakeOrm().
  64. Bind(&req).
  65. MakeService(&s.Service).
  66. Errors
  67. if err != nil {
  68. e.Logger.Error(err)
  69. e.Error(500, err, err.Error())
  70. return
  71. }
  72. var object models.Article
  73. p := actions.GetPermissionFromContext(c)
  74. err = s.Get(&req, p, &object)
  75. if err != nil {
  76. e.Error(500, err, fmt.Sprintf("获取Go文章失败,\r\n失败信息 %s", err.Error()))
  77. return
  78. }
  79. e.OK( object, "查询成功")
  80. }
  81. // Insert 创建Go文章
  82. // @Summary 创建Go文章
  83. // @Description 创建Go文章
  84. // @Tags Go文章
  85. // @Accept application/json
  86. // @Product application/json
  87. // @Param data body dto.ArticleInsertReq true "data"
  88. // @Success 200 {object} response.Response "{"code": 200, "message": "添加成功"}"
  89. // @Router /api/v1/article [post]
  90. // @Security Bearer
  91. func (e Article) Insert(c *gin.Context) {
  92. req := dto.ArticleInsertReq{}
  93. s := service.Article{}
  94. err := e.MakeContext(c).
  95. MakeOrm().
  96. Bind(&req).
  97. MakeService(&s.Service).
  98. Errors
  99. if err != nil {
  100. e.Logger.Error(err)
  101. e.Error(500, err, err.Error())
  102. return
  103. }
  104. // 设置创建人
  105. req.SetCreateBy(user.GetUserId(c))
  106. err = s.Insert(&req)
  107. if err != nil {
  108. e.Error(500, err, fmt.Sprintf("创建Go文章失败,\r\n失败信息 %s", err.Error()))
  109. return
  110. }
  111. e.OK(req.GetId(), "创建成功")
  112. }
  113. // Update 修改Go文章
  114. // @Summary 修改Go文章
  115. // @Description 修改Go文章
  116. // @Tags Go文章
  117. // @Accept application/json
  118. // @Product application/json
  119. // @Param id path int true "id"
  120. // @Param data body dto.ArticleUpdateReq true "body"
  121. // @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}"
  122. // @Router /api/v1/article/{id} [put]
  123. // @Security Bearer
  124. func (e Article) Update(c *gin.Context) {
  125. req := dto.ArticleUpdateReq{}
  126. s := service.Article{}
  127. err := e.MakeContext(c).
  128. MakeOrm().
  129. Bind(&req).
  130. MakeService(&s.Service).
  131. Errors
  132. if err != nil {
  133. e.Logger.Error(err)
  134. e.Error(500, err, err.Error())
  135. return
  136. }
  137. req.SetUpdateBy(user.GetUserId(c))
  138. p := actions.GetPermissionFromContext(c)
  139. err = s.Update(&req, p)
  140. if err != nil {
  141. e.Error(500, err, fmt.Sprintf("修改Go文章失败,\r\n失败信息 %s", err.Error()))
  142. return
  143. }
  144. e.OK( req.GetId(), "修改成功")
  145. }
  146. // Delete 删除Go文章
  147. // @Summary 删除Go文章
  148. // @Description 删除Go文章
  149. // @Tags Go文章
  150. // @Param data body dto.ArticleDeleteReq true "body"
  151. // @Success 200 {object} response.Response "{"code": 200, "message": "删除成功"}"
  152. // @Router /api/v1/article [delete]
  153. // @Security Bearer
  154. func (e Article) Delete(c *gin.Context) {
  155. s := service.Article{}
  156. req := dto.ArticleDeleteReq{}
  157. err := e.MakeContext(c).
  158. MakeOrm().
  159. Bind(&req).
  160. MakeService(&s.Service).
  161. Errors
  162. if err != nil {
  163. e.Logger.Error(err)
  164. e.Error(500, err, err.Error())
  165. return
  166. }
  167. // req.SetUpdateBy(user.GetUserId(c))
  168. p := actions.GetPermissionFromContext(c)
  169. err = s.Remove(&req, p)
  170. if err != nil {
  171. e.Error(500, err, fmt.Sprintf("删除Go文章失败,\r\n失败信息 %s", err.Error()))
  172. return
  173. }
  174. e.OK( req.GetId(), "删除成功")
  175. }

app\admin\router\article.go

  1. package router
  2. import (
  3. "github.com/gin-gonic/gin"
  4. jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
  5. "go-admin/app/admin/apis"
  6. "go-admin/common/middleware"
  7. "go-admin/common/actions"
  8. )
  9. func init() {
  10. routerCheckRole = append(routerCheckRole, registerArticleRouter)
  11. }
  12. // registerArticleRouter
  13. func registerArticleRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
  14. api := apis.Article{}
  15. r := v1.Group("/article").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole())
  16. {
  17. r.GET("", actions.PermissionAction(), api.GetPage)
  18. r.GET("/:id", actions.PermissionAction(), api.Get)
  19. r.POST("", api.Insert)
  20. r.PUT("/:id", actions.PermissionAction(), api.Update)
  21. r.DELETE("", api.Delete)
  22. }
  23. }

app\admin\service\article.go

  1. package service
  2. import (
  3. "errors"
  4. "github.com/go-admin-team/go-admin-core/sdk/service"
  5. "gorm.io/gorm"
  6. "go-admin/app/admin/models"
  7. "go-admin/app/admin/service/dto"
  8. "go-admin/common/actions"
  9. cDto "go-admin/common/dto"
  10. )
  11. type Article struct {
  12. service.Service
  13. }
  14. // GetPage 获取Article列表
  15. func (e *Article) GetPage(c *dto.ArticleGetPageReq, p *actions.DataPermission, list *[]models.Article, count *int64) error {
  16. var err error
  17. var data models.Article
  18. err = e.Orm.Model(&data).
  19. Scopes(
  20. cDto.MakeCondition(c.GetNeedSearch()),
  21. cDto.Paginate(c.GetPageSize(), c.GetPageIndex()),
  22. actions.Permission(data.TableName(), p),
  23. ).
  24. Find(list).Limit(-1).Offset(-1).
  25. Count(count).Error
  26. if err != nil {
  27. e.Log.Errorf("ArticleService GetPage error:%s \r\n", err)
  28. return err
  29. }
  30. return nil
  31. }
  32. // Get 获取Article对象
  33. func (e *Article) Get(d *dto.ArticleGetReq, p *actions.DataPermission, model *models.Article) error {
  34. var data models.Article
  35. err := e.Orm.Model(&data).
  36. Scopes(
  37. actions.Permission(data.TableName(), p),
  38. ).
  39. First(model, d.GetId()).Error
  40. if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
  41. err = errors.New("查看对象不存在或无权查看")
  42. e.Log.Errorf("Service GetArticle error:%s \r\n", err)
  43. return err
  44. }
  45. if err != nil {
  46. e.Log.Errorf("db error:%s", err)
  47. return err
  48. }
  49. return nil
  50. }
  51. // Insert 创建Article对象
  52. func (e *Article) Insert(c *dto.ArticleInsertReq) error {
  53. var err error
  54. var data models.Article
  55. c.Generate(&data)
  56. err = e.Orm.Create(&data).Error
  57. if err != nil {
  58. e.Log.Errorf("ArticleService Insert error:%s \r\n", err)
  59. return err
  60. }
  61. return nil
  62. }
  63. // Update 修改Article对象
  64. func (e *Article) Update(c *dto.ArticleUpdateReq, p *actions.DataPermission) error {
  65. var err error
  66. var data = models.Article{}
  67. e.Orm.Scopes(
  68. actions.Permission(data.TableName(), p),
  69. ).First(&data, c.GetId())
  70. c.Generate(&data)
  71. db := e.Orm.Save(&data)
  72. if err = db.Error; err != nil {
  73. e.Log.Errorf("ArticleService Save error:%s \r\n", err)
  74. return err
  75. }
  76. if db.RowsAffected == 0 {
  77. return errors.New("无权更新该数据")
  78. }
  79. return nil
  80. }
  81. // Remove 删除Article
  82. func (e *Article) Remove(d *dto.ArticleDeleteReq, p *actions.DataPermission) error {
  83. var data models.Article
  84. db := e.Orm.Model(&data).
  85. Scopes(
  86. actions.Permission(data.TableName(), p),
  87. ).Delete(&data, d.GetId())
  88. if err := db.Error; err != nil {
  89. e.Log.Errorf("Service RemoveArticle error:%s \r\n", err)
  90. return err
  91. }
  92. if db.RowsAffected == 0 {
  93. return errors.New("无权删除该数据")
  94. }
  95. return nil
  96. }

配置系统菜单

配置⻆⾊权限

 操作内容管理

ok 

参照doc.zhangwj.com/cms/article-manage

admin/123456

​​​​​​​ 

Go语⾔资源汇总

开篇

Go语⾔该学什么

⽹站

开源项⽬

gin

gim

beego

cobra

pholcus

nsq

codis

delve

micro/micro

4.6 gin-vue-admin实战

github.com/yunixiangfeng/devops/tree/main/gin-vue-admin

【GIN-VUE-ADMIN】手把手教你使用gin-vue-admin(分P合集)

自动化pkg+自动化代码使用_哔哩哔哩_bilibili

https://github.com/flipped-aurora/gin-vue-admin

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/273899
推荐阅读
相关标签
  

闽ICP备14008679号