Golang Devops项目开发(1)

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)模型实现

package main

import(
	"time"
)

func main() {

	for i := 0; i < 100; i++ {
		go test_goroute(i)
	}

	time.Sleep(time.Second)
}

进一步阅读:《 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. 支持任何类型

func main() {

    pipe := make(chan int, 3)

    pipe <- 1

    pipe <- 2

}
package main

import "fmt"

func test_pipe() {
	pipe := make(chan int, 3)
	pipe <- 1
	pipe <- 2
	pipe <- 3
	var t1 int
	t1 = <-pipe
	fmt.Println("t1: ", t1)

}

func sum(s []int, c chan int) {
	test_pipe()
	sum := 0
	for _, v := range s {
		sum += v
	}
	fmt.Println("sum:", sum)
	c <- sum // send sum to c
}

func main() {
	s := []int{7, 2, 8, -9, 4, 0}
	c := make(chan int)
	go sum(s[:len(s)/2], c) // 7+2+8 = 17, -9 + 4+0 = -5
	go sum(s[len(s)/2:], c)
	// x, y := <-c, <-c // receive from c
	x := <-c
	y := <-c
	fmt.Println(x, y, x+y)
}

1.2.4 Go语言特性-多返回值

一个函数返回多个值

func calc(a int, b int) (int, int) {
	sum := a+b
	avg := (a+b)/2
	return sum, avg
}

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()

package main

import (
	"fmt"
	"time"
)

const (
	Man    = 1
	Female = 2
)

func main() {
	for {
		second := time.Now().Unix()
		if second%Female == 0 {
			fmt.Println("female")
		} else {
			fmt.Println("man")
		}
		time.Sleep(1000 * time.Millisecond)
	}
}

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环境环境变量的值,并打印在终端

package main

import (
	"fmt"
	"os"
)

func main() {
	var goos string = os.Getenv("GOOS")
	fmt.Printf("The operating system is: %s\n", goos)
	path := os.Getenv("Path")
	fmt.Printf("Path is %s\n", path)
}

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

package main

import "fmt"

func swap(a *int, b *int) {
	tmp := *a
	*a = *b
	*b = tmp
	return
}

func swap1(a int, b int) (int, int) {
	return b, a
}

func test() {
	var a = 100
	fmt.Println(a)
	//var b int
	for i := 0; i < 100; i++ {
		var b = i * 2
		fmt.Println(b)
	}

	//fmt.Println(c)
	//fmt.Println(b)
}

func test2() {
	var a int8 = 100
	var b int16 = int16(a)

	fmt.Printf("a=%d b=%d\n", a, b)
}

func main() {
	first := 100
	second := 200
	//swap(&first, &second)
	//first, second = swap1(first, second)
	first, second = second, first
	fmt.Println("first=", first)
	fmt.Println("second=", second)

	test()

	test2()
}

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

代码: 2-6-value_quote.go

package main

import (
	"fmt"
)

func modify(a int) {
	a = 10
	return
}

func modify1(a *int) {
	*a = 10
}

func main() {
	a := 5
	b := make(chan int, 1)
	

	fmt.Println("a=", a)
	fmt.Println("b=", b)

	modify(a)
	fmt.Println("a=", a)
	modify1(&a)
	fmt.Println("a=", a)
}

2.7 变量的作用域

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

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

则作用于整个程序。

请指出下面程序的输出是什么
package main
var a = "G"
func main() {
n() // G
m() // O
n() //O
} 
func n() { 
fmt.Println(a) 
}
func m() {
a = "O"
fmt.Println(a) 
}

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)

package main
func main() {
var a int
var b int32
a = 15
b = a + a // compiler error cannot use a + a (value of type int) as int32 value in assignment
b = b + 5 // ok: 5 is a constant
}
package main

import (
	"fmt"
	"math/rand"
	"time"
)

func init() {
	rand.Seed(time.Now().UnixNano())
}

func main() {

	for i := 0; i < 10; i++ {
		a := rand.Int()
		fmt.Println(a)
	}

	for i := 0; i < 10; i++ {
		a := rand.Intn(100)
		fmt.Println(a)
	}

	for i := 0; i < 10; i++ {
		a := rand.Float32()
		fmt.Println(a)
	}

}

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. 数学操作符:+、-、

*

、/等等

package main

import (
	"fmt"
	"strings"
	"unsafe"
	_ "unsafe"
)

func test1() {
	bytes := []byte("I am byte array !")
	str := string(bytes)
	bytes[0] = 'i' //注意这一行,bytes在这里修改了数据,但是str打印出来的依然没变化,
	fmt.Println(str)
}
func test2() {
	bytes := []byte("I am byte array !")
	str := (*string)(unsafe.Pointer(&bytes))
	bytes[0] = 'i'
	fmt.Println(*str)
}
func test3() {
	var data [10]byte
	data[0] = 'T'
	data[1] = 'E'
	var str string = string(data[:])
	fmt.Println(str)
}

func str2bytes(s string) []byte {
	x := (*[2]uintptr)(unsafe.Pointer(&s))
	h := [3]uintptr{x[0], x[1], x[1]}
	return *(*[]byte)(unsafe.Pointer(&h))
}

func bytes2str(b []byte) string {
	return *(*string)(unsafe.Pointer(&b))
}
func test4() {
	s := strings.Repeat("abc", 3)
	fmt.Println("str2bytes")
	b := str2bytes(s)
	fmt.Println("bytes2str")
	s2 := bytes2str(b)
	fmt.Println(b, s2)
}

func main() {
	test1()
	test2()
	test3()
	test4()
}

2.9 数据类型和操作符3

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

package main

import "fmt"

func main() {
	var str = "hello world\n\n"
	var str2 = `hello \n \n \n
	this is a test string
	This is a test string too·`
	fmt.Println("str=", str)
	fmt.Println("str2=", str2)
}
package main

import (
	"fmt"
)

func test_switch1() {
	a := 2
	switch a {
	case 1:
		fmt.Println("a=1")
	case 2:
		fmt.Println("a=2")
	case 3:
		fmt.Println("a=3")
	case 4:
		fmt.Println("a=4")
	default:
		fmt.Println("default")
	}
}

func test_switch2() {
	a := 2
	switch a {
	case 1:
		fmt.Println("a=1")
	case 2:
		fmt.Println("a=2")
		fallthrough
	case 3:
		fmt.Println("a=3")
	case 4:
		fmt.Println("a=4")
	default:
		fmt.Println("default")
	}
}

func main() {
	fmt.Printf("执行test_switch%d\n", 1)
	test_switch1()
	fmt.Printf("执行test_switch%d\n", 2)
	test_switch2()
}

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。

package main

import "fmt"

func modify(p *int) {

	fmt.Println(p)
	*p = 1000900
	return
}

func main() {

	var a int = 10
	fmt.Println(&a)

	var p *int
	p = &a

	fmt.Println("the address of p:", &p)
	fmt.Println("the value of p:", p)
	fmt.Println("the value of p point to variable:", *p)

	fmt.Println(*p)
	*p = 100
	fmt.Println(a)

	var b int = 999
	p = &b
	*p = 5

	fmt.Println(a)
	fmt.Println(b)

	modify(&a)
	fmt.Println(a)
}

3 函数1

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

func add()

{

}

3 函数2

2. golang函数特点:

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

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

c. 匿名函数

d. 多返回值

3 函数2

2. golang函数特点:

package main

import (
	"fmt"
	"reflect"
)

func add(a, b int) int {
	return a + b
}
func main() {
	c := add
	fmt.Println(c)
	sum := c(10, 20)
	fmt.Println(sum)
	sf1 := reflect.ValueOf(c)
	sf2 := reflect.ValueOf(add)
	if sf1 == sf2 {
		fmt.Println("c equal add")
	}

}
package main

import (
	"fmt"
)

type add_func func(int, int) int

func add(a, b int) int {
	return a + b
}
func operator(op add_func, a int, b int) int {
	return op(a, b)
}
func main() {
	c := add
	fmt.Println(c)
	sum := operator(c, 100, 200)
	fmt.Println(sum)
}

3 函数3

3. 函数参数传递方式:

1). 值传递

2). 引用传递

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

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

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

3 函数3

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

2). 引用传递

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

go只有值传递、浅拷贝

package main

import "fmt"

func modify(a int) {
	a = 100
}
func main() {
	a := 8
	fmt.Println(a)
	modify(a)
	fmt.Println(a)
}

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)来判断传递参数的个数

package main

import "fmt"

func add(a int, arg ...int) int {
	var sum int = a
	for i := 0; i < len(arg); i++ {
		sum += arg[i]
	}

	return sum
}

func concat(a string, arg ...string) (result string) {

	result = a
	for i := 0; i < len(arg); i++ {
		result += arg[i]
	}

	return
}

func main() {
	sum := add(10, 3, 3, 3, 3)
	fmt.Println(sum)

	res := concat("hello", " ", "world")
	fmt.Println(res)
}

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的区别

package main

import (
	"fmt"
	"strings"
)

// 缩小变量作用域,减少对全局变量的污染。下面的累加如果用全局变量进行实现,全局变量容易被其他人污染。
// 同时,所有我要实现n个累加器,那么每次需要n个全局变量。利用闭包,
// 每个生成的累加器myAdder1, myAdder2 := adder(), adder()有自己独立的sum,sum可以看作为myAdder1.sum与myAdder2.sum。
func Adder() func(int) int {
	var x int
	f := func(d int) int {
		x += d
		return x
	}
	return f
}

func makeSuffix(suffix string) func(string) string {
	f := func(name string) string {

		if strings.HasSuffix(name, suffix) == false {
			return name + suffix
		}
		return name
	}

	return f
}

func main() {

	f := Adder()
	fmt.Println(f(1))
	fmt.Println(f(100))
	// fmt.Println(f(1000))
	/*
		f1 := makeSuffix(".bmp")
		fmt.Println(f1("test"))
		fmt.Println(f1("pic"))

		f2 := makeSuffix(".jpg")
		fmt.Println(f2("test"))
		fmt.Println(f2("pic"))
	*/
}

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

package main

import "fmt"

func test1() {
	var a [10]int

	//j := 10
	a[0] = 100
	//a[j] = 200

	fmt.Println(a)

	for i := 0; i < len(a); i++ {
		fmt.Println(a[i])
	}

	for index, val := range a {
		fmt.Printf("a[%d]=%d\n", index, val)
	}
}

func test3(arr *[5]int) {
	(*arr)[0] = 1000
}

func test2() {
	var a [10]int
	b := a

	b[0] = 100
	fmt.Println(a)
}

func main() {

	//test1()
	test2()
	var a [5]int
	test3(&a)
	fmt.Println(a)
}

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]

package main

import "fmt"

type slice struct {
	ptr *[100]int
	len int
	cap int
}

func make1(s slice, cap int) slice {
	s.ptr = new([100]int)
	s.cap = cap
	s.len = 0
	return s
}

func modify(s slice) {
	s.ptr[1] = 1000
}

func testSlice2() {
	var s1 slice
	s1 = make1(s1, 10)

	s1.ptr[0] = 100
	modify(s1)

	fmt.Println(s1.ptr)
}

func testSlice() {
	var slice []int
	var arr [5]int = [...]int{1, 2, 3, 4, 5}

	slice = arr[:]
	slice = slice[1:]
	slice = slice[:len(slice)-1]
	fmt.Println(slice)
	fmt.Println(len(slice))
	fmt.Println(cap(slice))

	slice = slice[0:1]
	fmt.Println(len(slice))
	fmt.Println(cap(slice))

}

func modify1(a []int) {
	a[1] = 1000
}

func testSlice3() {
	var b []int = []int{1, 2, 3, 4}
	modify1(b)
	fmt.Println(b)
}

func testSlice4() {
	var a = [10]int{1, 2, 3, 4}

	b := a[1:5]
	fmt.Printf("%p\n", b)
	fmt.Printf("%p\n", &a[1])
}

func main() {
	//testSlice()
	//testSlice2()
	//testSlice3()
	testSlice4()
}

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

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

代码:4-3-slice1.go

package main

import "fmt"

func testSlice() {
	var a [5]int = [...]int{1, 2, 3, 4, 5}
	s := a[1:]
	fmt.Printf("before len[%d] cap[%d]\n", len(s), cap(s))
	s[1] = 100
	fmt.Printf("s=%p a[1]=%p\n", s, &a[1])
	fmt.Println("before a:", a)

	s = append(s, 10)
	s = append(s, 10)
	fmt.Printf("after len[%d] cap[%d]\n", len(s), cap(s))
	s = append(s, 10)
	s = append(s, 10)
	s = append(s, 10)

	s[1] = 1000
	fmt.Println("after a:", a)
	fmt.Println(s)
	fmt.Printf("s=%p a[1]=%p\n", s, &a[1])
}

func testCopy() {

	var a []int = []int{1, 2, 3, 4, 5, 6}
	b := make([]int, 1)

	copy(b, a)
	fmt.Printf("copy len[%d] cap[%d]\n", len(b), cap(b))
	fmt.Println(b)
}

func testString() {
	s := "hello world"
	s1 := s[0:5]
	s2 := s[6:]

	fmt.Println(s1)
	fmt.Println(s2)

}

func testModifyString() {
	s := "我hello world"
	s1 := []rune(s)

	s1[0] = 200
	s1[1] = 128
	s1[2] = 64
	str := string(s1)
	fmt.Println(str)
}

func main() {
	//testSlice()
	//testCopy()
	//testString()
	testModifyString()
}

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

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 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

    }

var stu1 Student
stu1.age = 34
stu1.name = "dar"
stu1.Class = "class1"
fmt.Println(stu1.name) //dar
var stu2 *Student = new(Student)
stu2.name = "ki"
stu2.age = 33
fmt.Println(stu2.name, (*stu2).name) //ki
var stu3 *Student = &Student{name: "rose", age: 18, Class: "class3"}
fmt.Println(stu3.name, (*stu3).name) //rose rose
package main

import (
	"fmt"
	"unsafe"
)

type Student struct {
	name  string
	age   int32  // 小写 私密 只能在自己的包里面用
	Class string // 大写 公开 类似C++  public
}

func main() {
	// 1 值形式
	var stu1 Student // 里面的变量全是零 栈上的
	fmt.Println("stu1:", stu1)
	stu1.age = 34
	stu1.name = "dar"
	stu1.Class = "class1"
	fmt.Println(stu1.name) //dar

	// 2 new 函数创建
	var stu2 *Student = new(Student) // new出来的是堆上
	stu2.name = "king"
	stu2.age = 33
	fmt.Println(stu2.name, (*stu2).name) //king

	// &形式创建
	var stu3 *Student = &Student{
		name:  "rose",
		age:   18,
		Class: "class3", // 如果分行的时候每行都要,
	}
	// var stu3 *Student = &Student{name: "rose", age: 18, Class: "class3"}
	fmt.Println(stu3.name, (*stu3).name) //rose  rose
	fmt.Printf("addr: %p, %p, %p\n", &stu1, stu2, stu3)

	// 值 初始化
	var stu4 Student = Student{ // KV 形式初始化值
		name:  "老师",
		age:   18,
		Class: "Go", // 注意这里的逗号不能少
	}
	fmt.Println("stu4:", stu4) // stu4: {柚子老师 18 }

	// 值顺序初始化
	var stu5 Student = Student{ // 顺序形式 形式初始化值
		"1",
		18,
		"音视频", // 注意这里的逗号不能少
	}
	fmt.Println("stu5:", stu5)

	// nil结构体
	var stu6 *Student = nil
	fmt.Println("stu6:", stu6)

	// 结构体大小
	fmt.Println("unsafe.Sizeof(stu5):", unsafe.Sizeof(stu5))
	fmt.Println("unsafe.Sizeof(stu6):", unsafe.Sizeof(stu6))
	// fmt.Println("unsafe.Sizeof(string):", unsafe.Sizeof(string))
	// fmt.Println("unsafe.Sizeof(int):", unsafe.Sizeof(int))
}

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

    }

package main

import "fmt"

type Student struct {
	name  string
	age   int
	Class string
}

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.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序列化操作(序列化和反序列化演示)

type Student struct {
	Name string `json:"name"`
	Age int `json:"age"`
	}
	func main() {
	var stu = Student{Name: "dar", Age: 34}
	data, err := json.Marshal(stu)
	if err != nil {
	fmt.Println("json encode failed err:", err)
	return
	}
	fmt.Println(string(data)) //{"name":"dar","age":34}
	}
package main

import (
	"encoding/json"
	"fmt"
)

// stu:  序列化后:{"name":"dar","age":34}
// 				 {"name1":"dar","age2":34}
type Student struct {
	Name string `json:"name1"`
	Age  int    `json:"age2"`
}

func main() {
	var stu = Student{Name: "dar", Age: 34}
	data, err := json.Marshal(stu) //   {"name1":"dar","age2":34}
	if err != nil {
		fmt.Println("json encode failed err:", err)
		return
	}
	fmt.Println("stu: ", string(data)) //{"name":"dar","age":34}

	var stu2 Student
	err = json.Unmarshal(data, &stu2) // 反序列化 
	fmt.Println("stu2: ", stu2)       // {dar 34}
}

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

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

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

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

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

type Person struct {

    Name string

    Age int

    }

    type Student struct {

    score string

    Age int

    Person // 匿名内嵌结构体

    }

func main() {
	var stu = new(Student)
	stu.Age = 34                         //优先选择Student中的Age
	fmt.Println(stu.Person.Age, stu.Age) // 0,34
}
// 1.4 struct匿名成员(字段、属性)

package main

import "fmt"

type Person struct {
	Name string
	Age  int
}
type Student struct {
	score  string
	Age    int
	Person // 匿名内嵌结构体
}

func main() {
	var stu = new(Student)
	stu.Age = 22                         //优先选择Student中的Age
	fmt.Println(stu.Person.Age, stu.Age) // 0,22

	var stu2 = Student{
		score: "100",
		Age:   20,
		Person: Person{
			Name: "柚子老师",
			Age:  18,
		},
	}
	fmt.Println("stu2: ", stu2)
}

1.5 struct-继承、多继承

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

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

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

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

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

type Person struct {

    Name string

    Age int

    }

    type Teacher struct {

    Salary int

    Classes string

   }

type man struct {
	sex string
	job Teacher //别名,继承Teacher 这个时候就不是匿名了
	Person //继承Person
	}
	func main() {
	var man1 = new(man)
	man1.Age = 34
	man1.Name = "dar"
	man1.job.Salary = 100000
	fmt.Println(man1, man1.job.Salary) //&{ {8500 } {dar 34}} 8500
	}
// 1.5 struct-继承、多继承
package main

import "fmt"

type Person struct {
	Name string
	Age  int
}
type Teacher struct {
	Salary int
	Class  string
}

type Man struct {
	sex    string
	job    Teacher //别名,继承Teacher
	Person         //继承Person
}

func main() {
	var man1 = new(Man)
	man1.Age = 34
	man1.Name = "dar"
	man1.job.Salary = 100000
	fmt.Println("man1:", man1, man1.job.Salary) //&{ {8500 } {dar 34}} 8500

	var man2 = Man{
		sex: "女",
		job: Teacher{
			Salary: 8000,
			Class:  "班班",
		},
		Person: Person{ // 匿名初始化方式
			Name: "老师",
			Age:  18,
		},
	}
	fmt.Println("man2", man2)
}

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

    }

结构体的指针方法

package main

import (
	"fmt"
	"math"
)

type Circle struct {
	x      int
	y      int
	Radius int
}

// 面积
func (c Circle) Area() float64 {
	return math.Pi * float64(c.Radius) * float64(c.Radius)
}

// 周长
func (c Circle) Circumference() float64 {
	return 2 * math.Pi * float64(c.Radius)
}

func (c Circle) expand() {
	c.Radius *= 2
}

func (c *Circle) expand2() {
	c.Radius *= 2
}
func main() {
	var c = Circle{Radius: 50}
	fmt.Println(c.Area(), c.Circumference())
	// 指针变量调用方法形式上是一样的
	var pc = &c
	pc.expand2()
	fmt.Println(pc.Area(), pc.Circumference())

}
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.6 struct-结构体中的方法-方法和函数的区别

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

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

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

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

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

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

通的值类型上定义方法。

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

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

1.7 struct-内存分布

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

type Student struct {
Name string
Age int64
wight int64
high int64
score int64
}

package main

import (
	"fmt"
	"reflect"
)

// type Student struct {
// 	Name  string // 16
// 	Age   int64  // 8
// 	wight int64  // 8
// 	high  int64  // 8
// 	score int64  // 8
// }
type Student struct {
	Name  string // 16  有两个变量: 指针, length
	Age   int8   //
	wight int64  // 8
	high  int8   // 8
	score int64  // 8
}

func main() {
	var stu1 = new(Student)
	stu1.Name = "只为你"
	fmt.Printf("地址分布:")
	fmt.Printf("%p\n", &stu1.Name)
	fmt.Printf("%p\n", &stu1.Age)
	fmt.Printf("%p\n", &stu1.wight)
	fmt.Printf("%p\n", &stu1.high)
	fmt.Printf("%p\n", &stu1.score)
	typ := reflect.TypeOf(Student{})
	fmt.Printf("Struct is %d bytes long\n", typ.Size())
	// We can run through the fields in the structure in order
	n := typ.NumField()
	for i := 0; i < n; i++ {
		field := typ.Field(i) // 反射出filed
		fmt.Printf("%s at offset %v, size=%d, align=%d\n",
			field.Name, field.Offset, field.Type.Size(),
			field.Type.Align())
	}
}

2. 接口

2.1 interface简介

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

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

◼ interface 是方法的集合

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

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

◼ interface 不能包含任何变量

2.2 interface定义

type 接口名称 interface {

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

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

    ...

    }

2.3 interface使用

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

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

隐式地实现了空接口。

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

// 2.3 interface使用

package main

import "fmt"

//定义接口
type Skills interface {
	Running()
	Getname() string
}

type Student struct {
	Name string
	Age  int
}

type Teacher struct {
	Name string
	Age  int
}

// 实现接口
func (p Student) Getname() string { //实现Getname方法
	fmt.Println(p.Name)
	return p.Name
}

func (p Student) Running() { // 实现 Running方法
	fmt.Printf("%s running\n", p.Name)
}

func (p Teacher) Getname() string { //实现Getname方法
	fmt.Println(p.Name)
	return p.Name
}

func (p Teacher) Running() { // 实现 Running方法
	fmt.Printf("%s running\n", p.Name)
}

func (p Teacher) Running2() { // 实现 Running方法
	fmt.Printf("%s running\n", p.Name)
}

// 想用接口,那就要实现对应接口的所有方法
func main() {
	var skill Skills // 一个接口变量
	var stu1 Student // 结构体变量
	stu1.Name = "dar"
	stu1.Age = 34
	skill = stu1
	skill.Running() //调用接口

	var teacher Teacher = Teacher{"老师", 18}
	skill = teacher
	skill.Running() //调用接口
	teacher.Running()
}
// 空接口
package main

import "fmt"

// Go 语言为了避免用户重复定义很多空接口,它自己内置了一个,这个空接口的名字特别奇怪,叫 interface{}
/*
空接口里面没有方法,所以它也不具有任何能力,其作用相当于 Java 的 Object 类型,可以容纳任意对象,
它是一个万能容器。比如一个字典的 key 是字符串,但是希望 value 可以容纳任意类型的对象,
类似于 Java 语言的 Map 类型,这时候就可以使用空接口类型 interface{}。*/
// 空接口 map 里面用
func main() {
	// map  k-v  一个map里面是有key都是同一类型,value也是同一类型
	// map[string]int   k-string  v-int
	// 连续两个大括号,是不是看起来很别扭
	// 代码中 user 字典变量的类型是 map[string]interface{},
	// 从这个字典中直接读取得到的 value 类型是 interface{},需要通过类型转换才能得到期望的变量。
	var user = map[string]interface{}{
		"age":     30,
		"address": "Beijing",
		"married": true,
	}
	fmt.Println(user)
	// 类型转换语法来了
	var age = user["age"].(int)
	var address = user["address"].(string)
	var married = user["married"].(bool)
	fmt.Println(age, address, married)

	user["price"] = 5.5
	var price = user["price"].(float64) // ?报错?
	fmt.Println("user: ", user, price)

	fmt.Println("user2: ")
	var user2 = map[interface{}]interface{}{
		111:        30,
		"address2": "Beijing",
		1.2:        true,
	}
	fmt.Println("user2: ", user2)
}

2.4 interface多态

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

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

// 2.4 interface多态

package main

import "fmt"

type Skills interface {
	Running()
	Getname() string
}

type Student struct {
	Name string
	Age  int
}

type Teacher struct {
	Name   string
	Salary int
}

func (p Student) Getname() string { //实现Getname方法
	fmt.Println(p.Name)
	return p.Name
}

func (p Student) Running() { // 实现 Running方法
	fmt.Printf("%s running", p.Name)
}

func (p Teacher) Getname() string { //实现Getname方法
	fmt.Println(p.Name)
	return p.Name
}

func (p Teacher) Running() { // 实现 Running方法
	fmt.Printf("\n%s running", p.Name)
}
func main() {
	var skill Skills
	var stu1 Student
	var t1 Teacher
	t1.Name = "ki"
	stu1.Name = "dar"
	stu1.Age = 22
	skill = stu1
	skill.Running()
	skill = t1
	t1.Running()
}

2.5 interface接口嵌套

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

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

type Skills interface {
	Running()
	Getname() string
	}
	type Test interface {
	sleeping()
	Skills //继承Skills
	}
// 2.5 interface接口嵌套

package main

import "fmt"

type Skills interface {
	Running()
	// Running(is int)		// 函数名是唯一的
	Getname() string
}

type Test interface {
	Sleeping()
	Skills //继承Skills
}
type Student struct {
	Name string
	Age  int
}

type Teacher struct {
	skill  Skills // skill也只能当变量去用
	Name   string
	Salary int
}

func (p Student) Getname() string { //实现Getname方法
	fmt.Println(p.Name)
	return p.Name
}

func (p Student) Running() { // 实现 Running方法
	fmt.Printf("%s running", p.Name)
}

func (p Teacher) Getname() string { //实现Getname方法
	fmt.Println(p.Name)
	return p.Name
}

func (p Teacher) Running() { // 实现 Running方法
	fmt.Printf("\n%s running", p.Name)
}

func (p Teacher) Sleeping() { // 实现 Running方法
	fmt.Printf("\n%s Sleeping", p.Name)
}
func main() {
	var skill Skills
	var stu1 Student
	var t1 Teacher
	t1.Name = "ki"
	stu1.Name = "dar"
	stu1.Age = 22
	skill = stu1
	skill.Running()
	skill = t1
	t1.Running()

	var test Test
	test = t1
	test.Sleeping()

	// test = stu1
}

2.6 interface接口组合

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

// 2.6 接口的组合继承
package main

import "fmt"

// 可以闻
type Smellable interface {
	smell()
}

// 可以吃
type Eatable interface {
	eat()
}

type Fruitable interface {
	Smellable
	Eatable
}

// 苹果既可能闻又能吃
type Apple struct{}

func (a Apple) smell() {
	fmt.Println("apple can smell")
}

func (a Apple) eat() {
	fmt.Println("apple can eat")
}

// 花只可以闻
type Flower struct{}

func (f Flower) smell() {
	fmt.Println("flower can smell")
}

// 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 Smellable:
// 			fmt.Printf("type is Smellable, %d[%v]\n", k, v)
// 		case *Smellable:
// 			fmt.Printf("type is *Smellable, %d[%p]\n", k, v)
// 		case Eatable:
// 			fmt.Printf("type is Eatable, %d[%v]\n", k, v)
// 		case *Eatable:
// 			fmt.Printf("type is Eatable, %d[%p]\n", k, v)
// 		case Fruitable:
// 			fmt.Printf("type is Fruitable, %d[%v]\n", k, v)
// 		case *Fruitable:
// 			fmt.Printf("type is Fruitable, %d[%p]\n", k, v)
// 		}
// 	}
// }
func main() {
	var s1 Smellable
	var s2 Eatable
	var apple = Apple{}
	var flower = Flower{}
	s1 = apple
	s1.smell()
	s1 = flower
	s1.smell()
	s2 = apple
	s2.eat()

	fmt.Println("\n组合继承")
	var s3 Fruitable
	s3 = apple
	s3.smell()
	s3.eat()

	// TestType(s1, s2, s3, apple, flower)
}

2.7 interface类型转换

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

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

var s int

var x interface

x = s

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

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

// 2.7 interface类型转换

package main

import "fmt"

func main() {
	var x interface{}

	s := "dar"
	x = s // 为什么能赋值到空接口, 每种类型都已经隐藏实现了空接口
	y, ok := x.(int)
	z, ok1 := x.(string)
	fmt.Println(y, ok)
	fmt.Println(z, ok1)
}

//0 false
//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)

}

}

}

package main

import "fmt"

type Student struct {
	Name string
}

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)
		}
	}
}

func main() {
	var stu Student
	TestType("dar", 100, stu, 3.3)
}

//type is string, 0[dar]
//type is int, 1[100]
//type is Student, 2[{}]
//type is float, 3[3.3]

2.9 指向指针的接口变量

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

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

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

改自己的行为。

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

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

反射的目标:

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

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

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

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

package main

import "fmt"

type Rect struct {
	Width  int
	Height int
}

func main() {
	var a interface{}
	var r = Rect{50, 50}
	a = &r // 指向了结构体指针

	var rx = a.(*Rect) // 转换成指针类型
	r.Width = 100
	r.Height = 100
	fmt.Println("r:", r)
	fmt.Println("rx:", rx)
	fmt.Printf("rx:%p, r:%p\n", rx, &r)
}

3 reflect反射

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

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

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

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

/*
但是有时你希望在运行时使用变量的在编写程序时还不存在的信息。
比如你正在尝试将文件或网络请求中的数据映射到变量中。或者你想构建一个适用于不同类型的工具。
在这种情况下,你需要使用反射。反射使您能够在运行时检查类型。
它还允许您在运行时检查,修改和创建变量,函数和结构体。
*/
package main

import (
	"fmt"
	"reflect"
	"strings"
)

type Foo struct {
	A int `tag1:"First Tag" tag2:"Second Tag"`
	B string
}

func main() {
	sl := []int{1, 2, 3}
	greeting := "hello"
	greetingPtr := &greeting
	f := Foo{A: 10, B: "Salutations"}
	fp := &f

	slType := reflect.TypeOf(sl)
	gType := reflect.TypeOf(greeting)
	grpType := reflect.TypeOf(greetingPtr)
	fType := reflect.TypeOf(f)
	fpType := reflect.TypeOf(fp)

	examiner(slType, 0)
	examiner(gType, 0)
	examiner(grpType, 0)
	examiner(fType, 0)
	examiner(fpType, 0)
}

func examiner(t reflect.Type, depth int) {
	fmt.Println(strings.Repeat("\t", depth), "Type is", t.Name(), "and kind is", t.Kind())
	switch t.Kind() {
	case reflect.Array, reflect.Chan, reflect.Map, reflect.Ptr, reflect.Slice:
		fmt.Println(strings.Repeat("\t", depth+1), "Contained type:")
		examiner(t.Elem(), depth+1)
	case reflect.Struct:
		for i := 0; i < t.NumField(); i++ {
			f := t.Field(i)
			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())
			if f.Tag != "" {
				fmt.Println(strings.Repeat("\t", depth+2), "Tag is", f.Tag)
				fmt.Println(strings.Repeat("\t", depth+2), "tag1 is", f.Tag.Get("tag1"), "tag2 is", f.Tag.Get("tag2"))
			}
		}
	}
}

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

// 使用反射创建新实例
/*
除了检查变量的类型外,还可以使用反射来读取,设置或创建值。
首先,需要使用refVal := reflect.ValueOf(var)为变量创建一个reflect.Value实例。
如果希望能够使用反射来修改值,则必须使用refPtrVal := reflect.ValueOf(&var);
获得指向变量的指针。如果不这样做,则可以使用反射来读取该值,但不能对其进行修改。

一旦有了reflect.Value实例就可以使用Type()方法获取变量的reflect.Type。

如果要修改值,请记住它必须是一个指针,并且必须首先对其进行解引用。
使用refPtrVal.Elem().Set(newRefVal)来修改值,并且传递给Set()的值也必须是reflect.Value。
*/

package main

import (
	"fmt"
	"reflect"
)

type Foo struct {
	A int 
	B string
}

// 使用反射创建新实例
func main() {
	greeting := "hello"
	f := Foo{A: 10, B: "Salutations"}

	gVal := reflect.ValueOf(greeting)
	// not a pointer so all we can do is read it
	fmt.Println(gVal.Interface()) // hello

	gpVal := reflect.ValueOf(&greeting)
	// it’s a pointer, so we can change it, and it changes the underlying variable
	gpVal.Elem().SetString("goodbye")
	fmt.Println(greeting) // 修改成了goodbye

	fType := reflect.TypeOf(f)
	fVal := reflect.New(fType)
	fVal.Elem().Field(0).SetInt(20)
	fVal.Elem().Field(1).SetString("Greetings")
	f2 := fVal.Elem().Interface().(Foo) // 调用Interface()方法从reflect.Value回到普通变量值
	fmt.Printf("f2: %+v, %d, %s\n", f2, f2.A, f2.B)
	fmt.Println("f2:", f2)
	fmt.Println("f:", f)
}

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}

// 反射创建引用类型的实例
/*
使用反射来生成通常需要make函数的实例。可以使用reflect.MakeSlice,
reflect.MakeMap和reflect.MakeChan函数制作切片,Map或通道。
在所有情况下,都提供一个reflect.Type,然后获取一个reflect.Value,
可以使用反射对其进行操作,或者可以将其分配回一个标准变量。
*/
package main

import (
	"fmt"
	"reflect"
)

func main() {
	// 定义变量
	intSlice := make([]int, 0)
	mapStringInt := make(map[string]int)

	// 获取变量的 reflect.Type
	sliceType := reflect.TypeOf(intSlice)
	mapType := reflect.TypeOf(mapStringInt)

	// 使用反射创建类型的新实例
	intSliceReflect := reflect.MakeSlice(sliceType, 0, 0)
	mapReflect := reflect.MakeMap(mapType)

	// 将创建的新实例分配回一个标准变量
	v := 10
	rv := reflect.ValueOf(v)
	intSliceReflect = reflect.Append(intSliceReflect, rv)
	intSlice2 := intSliceReflect.Interface().([]int)
	fmt.Println("intSlice2: ", intSlice2)
	fmt.Println("intSlice : ", intSlice)

	k := "hello"
	rk := reflect.ValueOf(k)
	mapReflect.SetMapIndex(rk, rv)
	mapStringInt2 := mapReflect.Interface().(map[string]int)
	fmt.Println("mapStringInt2: ", mapStringInt2)
	fmt.Println("mapStringInt : ", mapStringInt)
}

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

/*
使用反射创建函数
反射不仅仅可以为存储数据创造新的地方。还可以使用reflect.MakeFunc函数使用reflect来创建新函数。
该函数期望我们要创建的函数的reflect.Type,以及一个闭包,其输入参数为[]reflect.Value类型,
其返回类型也为[] reflect.Value类型。下面是一个简单的示例,它为传递给它的任何函数创建一个定时包装器
*/
package main

import (
	"fmt"
	"reflect"
	"runtime"
	"time"
)

func MakeTimedFunction(f interface{}) interface{} {
	rf := reflect.TypeOf(f)
	fmt.Println("rf: ", rf)
	if rf.Kind() != reflect.Func {
		panic("expects a function")
	}
	vf := reflect.ValueOf(f)
	fmt.Println("vf: ", vf)
	wrapperF := reflect.MakeFunc(rf, func(in []reflect.Value) []reflect.Value {
		start := time.Now()
		out := vf.Call(in)
		end := time.Now()
		fmt.Printf("calling %s took %v\n", runtime.FuncForPC(vf.Pointer()).Name(), end.Sub(start))
		return out
	})
	return wrapperF.Interface()
}

func timeMe() {
	fmt.Println("starting")
	time.Sleep(1 * time.Second)
	fmt.Println("ending")
}

func timeMeToo(a int) int {
	fmt.Println("starting")
	time.Sleep(time.Duration(a) * time.Second)
	result := a * 2
	fmt.Println("ending")
	return result
}

func main() {

	fmt.Println("MakeTimedFunction1: ")
	timed := MakeTimedFunction(timeMe).(func())
	fmt.Println("转成普通函数1: ")
	timed()

	fmt.Println("\n\nMakeTimedFunction2: ")
	timedToo := MakeTimedFunction(timeMeToo).(func(int) int)
	fmt.Println("转成普通函数2: ")
	fmt.Println(timedToo(5))
}

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 的变量。

package main

import (
	"fmt"
	"reflect"
)

type Skills interface {
	reading()
	running()
}

type Student struct {
	Age  int
	Name string
}

func (self Student) runing() {
	fmt.Printf("%s is running\n", self.Name)
}
func (self Student) reading() {
	fmt.Printf("%s is reading\n", self.Name)
}
func main() {
	stu1 := Student{Name: "dar", Age: 34}
	inf := new(Skills)
	stu_type := reflect.TypeOf(stu1)
	inf_type := reflect.TypeOf(inf).Elem() //  获取指针所指的对象类型
	fmt.Println("类型stu_type:", stu_type)
	fmt.Println(stu_type.String())  //main.Student
	fmt.Println(stu_type.Name())    //Student
	fmt.Println(stu_type.PkgPath()) //main
	fmt.Println(stu_type.Kind())    //struct
	fmt.Println(stu_type.Size())    //24

	fmt.Println("\n类型inf_type:", inf_type)
	fmt.Println(inf_type.NumMethod())                        //2
	fmt.Println(inf_type.Method(0), inf_type.Method(0).Name) // {reading main func() <invalid Value> 0} reading
	fmt.Println(inf_type.MethodByName("reading"))            //{reading main func() <invalid Value> 0} true

}

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

package main

import (
	"fmt"
	"reflect"
)

type Skills interface {
	reading()
	running()
}

type Student struct {
	Name string "json:name"
	Age  int    "json:age"
}

func (self Student) runing() {
	fmt.Printf("%s is running\n", self.Name)
}
func (self Student) reading() {
	fmt.Printf("%s is reading\n", self.Name)
}
func main() {
	stu1 := Student{Name: "dar", Age: 34}
	stu_type := reflect.TypeOf(stu1)
	fmt.Println(stu_type.NumField())          //2
	fmt.Println(stu_type.Field(0))            //{Name  string  0 [0] false}
	fmt.Println(stu_type.FieldByName("Name")) //{{Age  int  16 [1] false} true
	fmt.Println(stu_type.Field(1))            //{Name  string  0 [0] false}
	fmt.Println(stu_type.FieldByName("Age"))  //{{Age  int  16 [1] false} true
}
package main

import (
	"fmt"
	"reflect"
)

func main() {
	str := "dar"
	val := reflect.ValueOf(str).Kind()
	fmt.Println(val) //string
}

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

str := "dar"
res_type := reflect.TypeOf(str)
fmt.Println(res_type) //string
int1 := 1
res_type2 := reflect.TypeOf(int1)
fmt.Println(res_type2) //int

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

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

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

// 基础类型 rtype 实现了 Type 接口
type rtype struct {
size uintptr // 占用字节数
ptrdata uintptr
hash uint32 // 类型的hash值
...
kind uint8 // 元类型
...
}
// 切片类型
type sliceType struct {
rtype
elem *rtype // 元素类型
}
// 结构体类型
type structType struct {
rtype
pkgPath name // 所在包名
fields []structField // 字段列表
}
// 获取和设置普通类型的值
package main

import (
	"fmt"
	"reflect"
)

func main() {
	str := "dar"
	age := 11
	fmt.Println(reflect.ValueOf(str).String()) //获取str的值,结果dar
	fmt.Println(reflect.ValueOf(age).Int())    //获取age的值,结果age
	str2 := reflect.ValueOf(&str)              //获取Value类型
	str2.Elem().SetString("ki")              //设置值
	fmt.Println(str2.Elem(), age)              //ki 11

	age2 := reflect.ValueOf(&age) //获取Value类型
	fmt.Println("age2:", age2)

	age2.Elem().SetInt(40) //设置值
	fmt.Println("age:", age)
	fmt.Println("reflect.ValueOf(age):", reflect.ValueOf(age))
}

3.1 reflect反射- reflect.Value方法

reflect.Value.Kind():获取变量类别,返回常量
const (
Invalid Kind = iota //不存在的类型
Bool
Int
Int8
Int16
Int32
Int64
Uint
Uint8
Uint16
Uint32
Uint64
Uintptr // 指针的整数类型
Float32
Float64
Complex64
Complex128
Array
Chan
Func
Interface
Map
Ptr
Slice
String
Struct
UnsafePointer
)

str := "dar"
val := reflect.ValueOf(str).Kind()
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 值必须是复数型。

//简单结构体操作
package main

import (
	"fmt"
	"reflect"
)

type Skills interface {
	reading()
	running()
}

type Student struct {
	Name string
	Age  int
}

func (self Student) runing() {
	fmt.Printf("%s is running\n", self.Name)
}
func (self Student) reading() {
	fmt.Printf("%s is reading\n", self.Name)
}
func main() {
	stu1 := Student{Name: "dar", Age: 18}
	stu_val := reflect.ValueOf(stu1)                //获取Value类型
	fmt.Println(stu_val.NumField())                 //2
	fmt.Println(stu_val.Field(0), stu_val.Field(1)) //dar 18
	fmt.Println(stu_val.FieldByName("Age"))         //18

	stu_val2 := reflect.ValueOf(&stu1).Elem() // 要修改传引用或者指针
	stu_val2.FieldByName("Age").SetInt(33)    //设置字段值 ,结果33
	fmt.Println(stu1.Age)

}

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,前提必须是初始化以后,存在覆盖、不存在

添加

//通过反射调用结构体中的方法,通过reflect.Value.Method(i int).Call()
//或者reflect.Value.MethodByName(name string).Call()实现
package main

import (
	"fmt"
	"reflect"
)

type Student struct {
	Name string
	Age  int
}

func (this *Student) SetName(name string) {
	this.Name = name
	fmt.Printf("set name %s\n", this.Name)
}

func (this *Student) SetAge(age int) {
	this.Age = age
	fmt.Printf("set age %d\n", age)
}

func (this *Student) String() string {
	fmt.Printf("this is %s\n", this.Name)
	return this.Name
}

func (this *Student) SetAgeAndName(age int, name string) {
	this.Age = age
	fmt.Printf("set age %d, name:%s\n", age, name)
}

func main() {
	stu1 := &Student{Name: "dar", Age: 18}
	val := reflect.ValueOf(stu1)         //获取Value类型,也可以使用reflect.ValueOf(&stu1).Elem()
	val.MethodByName("String").Call(nil) //调用String方法

	params := make([]reflect.Value, 1)
	params[0] = reflect.ValueOf(18)
	val.MethodByName("SetAge").Call(params) //通过名称调用方法

	params[0] = reflect.ValueOf("ki")
	// val.Method(1).Call(params) //通过方法索引调用
	val.Method(2).Call(params) //通过方法索引调用	通过索引的方式拿到函数不安全
	fmt.Println(stu1.Name, stu1.Age)

	params = make([]reflect.Value, 2)
	params[0] = reflect.ValueOf(18)
	params[1] = reflect.ValueOf("老师")
	val.MethodByName("SetAgeAndName").Call(params)
}

//this is dar
//set age 18
//set name ki
//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{}

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

值可以被修改。

package main

import (
	"fmt"
	"reflect"
)

func test1() {
	var s int = 42
	var v = reflect.ValueOf(s)
	v.SetInt(43)
	fmt.Println(s)
}
func test2() {
	var s int = 42
	// 反射指针类型
	var v = reflect.ValueOf(&s)
	// 要拿出指针指向的元素进行修改
	v.Elem().SetInt(43)
	fmt.Println(s)
}
func main() {
	test1()
}

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

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

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

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

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

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

package main

import (
	"fmt"
	"reflect"
)

type Rect struct {
	Width  int
	Height int
	Name   string
}

// 通过统一的接口去实现属性设置的
func SetRectAttr(r *Rect, name string, value int) {
	var v = reflect.ValueOf(r)
	var field = v.Elem().FieldByName(name)
	field.SetInt(int64(value))
}

// 结构体也是值类型,也必须通过指针类型来修改。
func main() {
	var r = Rect{50, 100}
	SetRectAttr(&r, "Width", 100) // 修改属性的接口
	SetRectAttr(&r, "Height", 200)
	SetRectAttr(&r, "Name", "正方形")
	fmt.Println(r)
}
package main

import (
	"fmt"
	"reflect"
	"time"
)

const N = 1000000

func test1() {
	var sum = 0
	t0 := time.Now()

	for i := 0; i < (N); i++ {
		var s int = 42
		// 反射指针类型
		var v = reflect.ValueOf(&s)
		// 要拿出指针指向的元素进行修改
		v.Elem().SetInt(43)
		sum += s
	}

	elapsed := time.Since(t0)
	fmt.Println("反射赋值 ", "N:", N, "time:", elapsed, ", sum:", sum)
}

func test2() {
	var sum = 0
	t0 := time.Now()

	for i := 0; i < (N); i++ {
		var s int = 42
		s = 43
		sum += s
	}

	elapsed := time.Since(t0)
	fmt.Println("直接赋值 ", "N:", N, "time:", elapsed, ", sum:", sum)
}
func main() {
	test1()
	test2()
}
// 3 reflect反射 基础
package main

import (
	"fmt"
)

func main() {
	newValue := make(map[interface{}]interface{}, 0)
	newValue[11] = "dar"
	newValue["age"] = 18
	fmt.Println(newValue)
}

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的几种数据类型举例

package main

import "fmt"

func main() {
	// m0 可以, key类型为string, 支持 == 比较操作
	{
		fmt.Println("---- m0 ----")
		var m0 map[string]string // 定义map类型变量m0,key的类型为string,value的类型string
		fmt.Println(m0)
	}

	// m1 不可以, []byte是slice,不支持 == != 操作,不可以作为map key的数据类型
	{
		// fmt.Println("---- m1 ----");
		//var m1 map[[]byte]string // 报错: invalid map key type []byte
		//fmt.Println(m1)

		// 准确说slice类型只能与nil比较,其他的都不可以,可以通过如下测试:
		// var b1,b2 []byte
		// fmt.Println(b1==b2) // 报错: invalid operation: b1 == b2 (slice can only be compared to nil)
	}

	// m2 可以, interface{}类型可以作为key,但是需要加入的key的类型是可以比较的
	{
		fmt.Println("---- m2 ----")
		var m2 map[interface{}]string
		m2 = make(map[interface{}]string)
		//m2[[]byte("k2")]="v2" // panic: runtime error: hash of unhashable type []uint8
		m2[123] = "123"
		m2[12.3] = "123"
		fmt.Println(m2)

		var str string = m2[12.3]
		fmt.Println(str)
	}

	// m3 可以, 数组支持比较
	{
		fmt.Println("---- m3 ----")
		a3 := [3]int{1, 2, 3}
		var m3 map[[3]int]string
		m3 = make(map[[3]int]string)
		m3[a3] = "m3"
		fmt.Println(m3)
	}

	// m4 可以,book1里面的元素都是支持== !=
	{
		fmt.Println("---- m4 ----")
		type book1 struct {
			name string
		}
		var m4 map[book1]string
		fmt.Println(m4)
	}

	// m5 不可以, text元素类型为[]byte, 不满足key的要求
	{
		fmt.Println("---- m5 ----")
		// type book2 struct {
		// 	name string
		// 	text []byte //没有这个就可以
		// }
		//var m5 map[book2]string //invalid map key type book2
		//fmt.Println(m5)
	}
}

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() 函数得到的是字节的数量,通过下标来访问字符串得
到的是「字节」。
// 4-2 map基本操作
package main

import "fmt"

func create() {
	fmt.Println("map创建方式:")
	// 1 字面值
	{
		m1 := map[string]string{
			"m1": "v1", // 定义时指定的初始key/value, 后面可以继续添加
		}
		_ = m1

	}

	// 2 使用make函数
	{
		m2 := make(map[string]string) // 创建时,里面不含元素,元素都需要后续添加
		m2["m2"] = "v2"               // 添加元素
		_ = m2

	}

	// 定义一个空的map
	{
		m3 := map[string]string{}
		m4 := make(map[string]string)
		_ = m3
		_ = m4
	}
}

func curd() {
	fmt.Println("map增删改查:")
	// 创建
	fmt.Println("建:")
	m := map[string]string{
		"a": "va",
		"b": "vb",
	}
	fmt.Println(len(m)) // len(m) 获得m中key/value对的个数

	// 增加,修改
	fmt.Println("增改:")
	{
		// k不存在为增加,k存在为修改
		m["c"] = ""
		m["c"] = "11"                      // 重复增加(key相同),使用新的值覆盖
		fmt.Printf("%#v %#v\n", m, len(m)) // map[string]string{"a":"va", "b":"vb", "c":"11"} 3
	}

	// 查
	fmt.Println("查:")
	{
		// v := m[k] // 从m中取键k对应的值给v,如果k在m中不存在,则将value类型的零值赋值给v
		// v, ok := m[k] // 从m中取键k对应的值给v,如果k存在,ok=true,如果k不存在,将value类型的零值赋值给v同时ok=false
		// 查1 - 元素不存在
		v1 := m["x"] //
		v2, ok2 := m["x"]
		fmt.Printf("%#v %#v %#v\n", v1, v2, ok2) // "" "" false

		// 查2 - 元素存在
		v3 := m["a"]
		v4, ok4 := m["a"]
		fmt.Printf("%#v %#v %#v\n", v3, v4, ok4) //"va" "va" true
	}
	fmt.Println("删:")
	// 删, 使用内置函数删除k/v对
	{
		// delete(m, k) 将k以及k对应的v从m中删掉;如果k不在m中,不执行任何操作
		delete(m, "x")                     // 删除不存在的key,原m不影响
		delete(m, "a")                     // 删除存在的key
		fmt.Printf("%#v %#v\n", m, len(m)) // map[string]string{"b":"vb", "c":"11"} 2
		delete(m, "a")                     // 重复删除不报错,m无影响
		fmt.Printf("%#v %#v\n", m, len(m)) /// map[string]string{"b":"vb", "c":"11"} 2
	}
}

func travel() {
	fmt.Println("map遍历:")
	m := map[string]int{
		"a": 1,
		"b": 2,
	}
	for k, v := range m {
		fmt.Printf("k:[%v].v:[%v]\n", k, v) // 输出k,v值
	}

	var fruits = map[string]int{
		"apple":  2,
		"banana": 5,
		"orange": 8,
	}

	for name, score := range fruits {
		fmt.Println(name, score)
	}

	for name := range fruits {
		fmt.Println(name)
	}
}

func main() {
	create()
	curd()
	travel()
}

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字节串的内存表示和操作

// 按字符 rune 遍历
package main

import "fmt"

func splice() {
	var s1 = "hello" // 静态字面量
	var s2 = ""
	for i := 0; i < 10; i++ {
		s2 += s1 // 动态构造
	}
	fmt.Println(len(s1))
	fmt.Println(len(s2))
}

// 字符串是只读的
func onlyread() {
	var s = "hello"
	s[0] = 'H'
}

// 切割切割
func cut() {
	var s1 = "hello world"
	var s2 = s1[3:8]
	fmt.Println(s2)
}

// 字节切片和字符串的相互转换
func string2bytes() {
	var s1 = "hello world"
	var b = []byte(s1) // 字符串转字节切片
	var s2 = string(b) // 字节切片转字符串
	fmt.Println(b)
	fmt.Println(s2)
}
func main() {
	splice()
	onlyread()
	cut()
	string2bytes()
}

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.3 协程异常处理-recover

package main

import (
	"fmt"
	"runtime"
)

// 崩溃时需要传递的上下文信息
type panicContext struct {
	function string // 所在函数
}

// 保护方式允许一个函数
func ProtectRun(entry func()) {
	// 延迟处理的函数
	defer func() {
		// 发生宕机时,获取panic传递的上下文并打印
		err := recover()
		switch err.(type) {
		case runtime.Error: // 运行时错误
			fmt.Println("runtime error:", err)
		default: // 非运行时错误
			fmt.Println("error:", err)
		}
	}()
	entry()
}
func main() {
	fmt.Println("运行前")
	// 允许一段手动触发的错误
	ProtectRun(func() {
		fmt.Println("手动宕机前")
		// 使用panic传递上下文
		panic(&panicContext{
			"手动触发panic",
		})
		fmt.Println("手动宕机后")
	})
	// 故意造成空指针访问错误
	ProtectRun(func() {
		fmt.Println("赋值宕机前")
		var a *int
		*a = 1
		fmt.Println("赋值宕机后")
	})
	fmt.Println("运行后")
}
// 1.3 协程异常处理-recover

package main

import (
	"fmt"
	"time"
)

func main() {
	fmt.Println("run in main goroutine")
	go func() {
		fmt.Println("run in child goroutine")
		defer func() { // 要在对应的协程里执行
			fmt.Println("执行defer:")
			if err := recover(); err != nil {
				fmt.Println("捕获error:", err)
			}
		}()
		fmt.Println("run in grand grand child goroutine")
		var ptr *int
		*ptr = 0x12345 // 故意制造崩溃  ,该协程运行到这里结束
		go func() {
			fmt.Println("子子run in grand child goroutine")	// 这里也不会运行
			go func() {
				
			}()
		}()
		// time.Sleep(time.Second * 1)
		fmt.Println("离开: run in child goroutine leave") // 这里能否执行到
	}()
	time.Sleep(2 * time.Second)
	fmt.Println("main goroutine will quit")
}

1-4 启动百万协程

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

// 1-4 启动百万协程

package main

import (
	"fmt"
	"runtime"
	"time"
)

const N = 1000000

func main() {
	fmt.Println("run in main goroutine")
	i := 1
	for {
		go func() {
			for {
				time.Sleep(time.Second)
			}
		}()
		if i%10000 == 0 {
			fmt.Printf("%d goroutine started\n", i)
		}
		i++
		if i == N {
			break
		}
	}
	fmt.Println("NumGoroutine:", runtime.NumGoroutine())
	time.Sleep(time.Second * 15)
}

1-5 死循环

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

package main

import (
	"fmt"
	"runtime"
	"syscall"
	"time"
)
// 获取的是线程ID,不是协程ID
func GetCurrentThreadId() int {
	var user32 *syscall.DLL
	var GetCurrentThreadId *syscall.Proc
	var err error

	user32, err = syscall.LoadDLL("Kernel32.dll")	// Windows用的
	if err != nil {
		fmt.Printf("syscall.LoadDLL fail: %v\n", err.Error())
		return 0
	}
	GetCurrentThreadId, err = user32.FindProc("GetCurrentThreadId")
	if err != nil {
		fmt.Printf("user32.FindProc fail: %v\n", err.Error())
		return 0
	}

	var pid uintptr
	pid, _, err = GetCurrentThreadId.Call()

	return int(pid)
}

func main() {
	// runtime.GOMAXPROCS(1)		
	// 读取当前的线程数
	fmt.Println("GOMAXPROCS:", runtime.GOMAXPROCS(0)) // 本身电脑物理核心是4核 支持超线程 8核
	fmt.Println("run in main goroutine")
	n := 5
	for i := 0; i < n; i++ {
		go func() {
			fmt.Println("dead loop goroutine start, threadId:", GetCurrentThreadId())
			for {
			} // 死循环
			fmt.Println("dead loop goroutine stop")
		}()
	}

	go func() {
		var count = 0
		for {
			time.Sleep(time.Second)
			count++
			fmt.Println("for goroutine running:", count, "threadId:", GetCurrentThreadId())
		}
	}()
	fmt.Println("NumGoroutine: ", runtime.NumGoroutine())
	var count = 0
	for {
		time.Sleep(time.Second)
		count++
		fmt.Println("main goroutine running:", count, "threadId:", GetCurrentThreadId())
	}
}

1.6 设置线程数

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

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

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

package main

import (
	"fmt"
	"runtime"
)

func main() {
	fmt.Println("runtime.NumCPU():", runtime.NumCPU())
	// 读取默认的线程数
	fmt.Println(runtime.GOMAXPROCS(0))
	// 设置线程数为 10
	runtime.GOMAXPROCS(10)
	// 读取当前的线程数
	fmt.Println(runtime.GOMAXPROCS(0))
}

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进行比较。

package main

import "fmt"

func send(ch chan int) {
	i := 0
	for {
		i++
		ch <- i
	}
}

func recv(ch chan int) {
	value := <-ch
	fmt.Println(value)
	value = <-ch
	fmt.Println(value)
	close(ch)
}

// 向一个已经关闭的通道执行写操作会抛出异常,这意味着我们在写通道时一定要确保通道没有被关闭。
func main() {
	var ch = make(chan int, 4)
	go recv(ch)
	send(ch)
}

2.2 读写通道

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

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

// 2.2 读写通道

package main

import (
	"fmt"
	"time"
)

func main() {
	ch := make(chan float32, 4)
	for i := 0; i < cap(ch); i++ {
		ch <- 1.0 // 写通道
	}
	for len(ch) > 0 {
		value := <-ch // 读通道
		fmt.Println(value)
	}

	// ch1 := make(chan int, 1) // 这里是缓存 有一个1元素
	ch1 := make(chan int) // 非缓存的,实际是0个,并不是1个

	go func() {
		time.Sleep(1 * time.Second)
		ch1 <- 1
		ch1 <- 1             // 这里已经阻塞
		fmt.Println("写入ch1") //这里没打印
	}()

	value1 := <-ch1
	value1 = <-ch1
	time.Sleep(5 * time.Second)
	fmt.Println("退出, value1:", value1)
}

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

部的元素个数。

2-3 读写阻塞

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

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

// 2-3 读写阻塞
package main

import (
	"fmt"
	"math/rand"
	"time"
)

func send(ch chan int) {
	for {
		var value = rand.Intn(100)
		ch <- value
		fmt.Printf("send %d\n", value) // 这里没有延时
	}
}

func recv(ch chan int) {
	for {
		value := <-ch
		fmt.Printf("recv %d\n", value)
		time.Sleep(time.Second)
	}
}

func main() {
	var ch = make(chan int, 1)
	// 子协程循环读
	go recv(ch)
	// 主协程循环写
	send(ch)
}

2.4 关闭通道

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

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

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

// 2.4 关闭通道
/*
Go 语言的通道有点像文件,不但支持读写操作, 还支持关闭。
读取一个已经关闭的通道会立即返回通道类型的「零值」,而写一个已经关闭的通道会抛异常。
如果通道里的元素是整型的,读操作是不能通过返回值来确定通道是否关闭的。
*/
package main

import "fmt"

func main() {
	var ch = make(chan int, 4)
	ch <- 1
	ch <- 2

	fmt.Println("len(ch):", len(ch), "cap(ch):", cap(ch))
	close(ch)

	value := <-ch
	fmt.Println(value)
	value = <-ch
	fmt.Println(value)
	value = <-ch
	fmt.Println(value)
	value = <-ch
	fmt.Println(value)
	ch <- 3
}

2-5 通道写安全

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

有被关闭

 多人写入怎么办?

// 2-5 通道写安全
package main

import "fmt"

func send(ch chan int) { // 在写入端关闭, 没有太多的通用性
	ch <- 1
	ch <- 2
	ch <- 3
	ch <- 4
	close(ch)
}

func recv(ch chan int) {
	for v := range ch {
		fmt.Println(v)
	}
	value := <-ch // 判别不了是否已经读取完毕
	fmt.Println("value:", value)
}

// 确保通道写安全的最好方式是由负责写通道的协程自己来关闭通道,读通道的协程不要去关闭通道。
func main() {
	var ch = make(chan int, 1)
	go send(ch)
	recv(ch)
}

2-6 WaitGroup

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

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

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

package main

import (
	"fmt"
	"sync"
	"time"
)

func send(ch chan int, wg *sync.WaitGroup) {
	defer wg.Done() // 计数值减一
	i := 0
	for i < 4 {
		i++
		ch <- i
	}
}

func recv(ch chan int) {
	for v := range ch {
		fmt.Println(v)
	}
}
// 只要一个值能做界定符 比如nil, 比如0xfffe
func main() {
	var ch = make(chan int, 4)
	var wg = new(sync.WaitGroup)
	wg.Add(2)       // 增加计数值
	go send(ch, wg) // 写
	go send(ch, wg) // 写
	go recv(ch)
	// Wait() 阻塞等待所有的写通道协程结束
	// 待计数值变成零,Wait() 才会返回
	wg.Wait()
	// 关闭通道
	close(ch)
	time.Sleep(time.Second)
}

2-7 多路通道

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

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

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

/*
2-7 多路通道
在真实的世界中,还有一种消息传递场景,那就是消费者有多个消费来源,只要有一个来源生产了数据,
消费者就可以读这个数据进行消费。这时候可以将多个来源通道的数据汇聚到目标通道,然后统一在目标通道进行消费。

*/
package main

import (
	"fmt"
	"time"
)

// 每隔一会生产一个数
func send(ch chan int, gap time.Duration) {
	i := 0
	for {
		i++
		ch <- i
		time.Sleep(gap)
	}
}

// 将多个原通道内容拷贝到单一的目标通道
func collect(source chan int, target chan int) {
	for v := range source {
		target <- v // ch3 <- ch2 ; ch3 <- ch1
	}
}

func collect2(ch1 chan int, ch2 chan int, target chan int) {
	for {
		select {
		case v := <-ch1:
			target <- v
		case v := <-ch2:
			target <- v
		default: // 非阻塞
			fmt.Println("collect2")
		}
	}
}

// 从目标通道消费数据
func recv(ch chan int) {
	for v := range ch {
		fmt.Printf("receive %d\n", v)
	}
}

func main() {
	var ch1 = make(chan int)
	var ch2 = make(chan int)
	var ch3 = make(chan int)
	go send(ch1, time.Second)
	go send(ch2, 2*time.Second)
	// go collect(ch1, ch3)
	// go collect(ch2, ch3)
	go collect2(ch1, ch2, ch3)
	recv(ch3)
}

2-8 多路复用select

// 2-8 多路复用select
package main

import (
	"fmt"
	"time"
)

func send(ch chan int, gap time.Duration) {
	i := 0
	for {
		i++
		ch <- i
		time.Sleep(gap)
	}
}

func recv(ch1 chan int, ch2 chan int) {
	for {
		select {
		case v := <-ch1:
			fmt.Printf("recv %d from ch1\n", v)
		case v := <-ch2:
			fmt.Printf("recv %d from ch2\n", v)
		}
	}
}

func main() {
	var ch1 = make(chan int)
	var ch2 = make(chan int)
	go send(ch1, time.Second)
	go send(ch2, 2*time.Second)
	recv(ch1, ch2)
}

2-9 非阻塞读写

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

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

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

// 2-9 非阻塞读写
package main

import (
	"fmt"
	"time"
)

func send(ch1 chan int, ch2 chan int) {
	i := 0
	for {
		i++
		select {
		case ch1 <- i:
			fmt.Printf("send ch1 %d\n", i)
		case ch2 <- i:
			fmt.Printf("send ch2 %d\n", i)
		default:
			fmt.Printf("ch block\n")
			time.Sleep(2 * time.Second) // 这里只是为了演示
		}
	}
}

func recv(ch chan int, gap time.Duration, name string) {
	for v := range ch {
		fmt.Printf("receive %s %d\n", name, v)
		time.Sleep(gap)
	}
}

func main() {
	// 无缓冲通道
	var ch1 = make(chan int)
	var ch2 = make(chan int)
	// 两个消费者的休眠时间不一样,名称不一样
	go recv(ch1, time.Second, "ch1")
	go recv(ch2, 2*time.Second, "ch2")
	send(ch1, ch2)
}

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

生产者消费模型

// 2-10 生产者、消费者模型
package main

import (
	"fmt"
	"os"
	"os/signal"
	"syscall"
	"time"
)

// 生产者
func Producer(factor int, out chan<- int) {
	for i := 0; ; i++ {
		out <- i * factor
		time.Sleep(5 * time.Second)
	}
}

// 消费者
func Consumer(in <-chan int) {
	for v := range in {
		fmt.Println(v)
	}
}

func main() {
	ch := make(chan int, 64)
	go Producer(3, ch) // 生成3的倍数序列
	go Producer(5, ch) // 生成5的倍数序列

	go Consumer(ch)

	//Ctrl +C 退出
	sig := make(chan os.Signal, 1)
	signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
	fmt.Printf("wait Ctrl +C")
	fmt.Printf("quit (%v)\n", <-sig)
}

3. 线程安全

3-1 线程安全-互斥锁

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

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

需要加上-race 执行

package main

import "fmt"
// go多协程 是有竞态,不像以前的ntyco,libco没有竞态的
func write(d map[string]int) {
	d["fruit"] = 2
}

func read(d map[string]int) {
	fmt.Println(d["fruit"])
}

// go run -race 3-1-unsafe.go
func main() {
	d := map[string]int{}
	go read(d)
	write(d)
}

3-2 避免锁复制

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

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

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

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

package main

import (
	"fmt"
	"sync"
)

type SafeDict struct {
	data  map[string]int
	mutex *sync.Mutex
}

func NewSafeDict(data map[string]int) *SafeDict {
	return &SafeDict{
		data:  data,
		mutex: &sync.Mutex{},
	}
}

// defer 语句总是要推迟到函数尾部运行,所以如果函数逻辑运行时间比较长,
// 这会导致锁持有的时间较长,这时使用 defer 语句来释放锁未必是一个好注意。
func (d *SafeDict) Len() int {
	d.mutex.Lock()
	defer d.mutex.Unlock()
	return len(d.data)
}

// func (d *SafeDict) Test() int {
// 	d.mutex.Lock()
// 	length := len(d.data)
// 	d.mutex.Unlock() // 手动解锁 减少粒度	// 这种情况就不要用 defer d.mutex.Unlock()
// 	fmt.Println("length: ", length)
// 	// 这里还有耗时处理 耗时1000ms
// }

func (d *SafeDict) Put(key string, value int) (int, bool) {
	d.mutex.Lock()
	defer d.mutex.Unlock()
	old_value, ok := d.data[key]
	d.data[key] = value
	return old_value, ok
}

func (d *SafeDict) Get(key string) (int, bool) {
	d.mutex.Lock()
	defer d.mutex.Unlock()
	old_value, ok := d.data[key]
	return old_value, ok
}

func (d *SafeDict) Delete(key string) (int, bool) {
	d.mutex.Lock()
	defer d.mutex.Unlock()
	old_value, ok := d.data[key]
	if ok {
		delete(d.data, key)
	}
	return old_value, ok
}

func write(d *SafeDict) {
	d.Put("banana", 5)
}

func read(d *SafeDict) {
	fmt.Println(d.Get("banana"))
}

// go run -race 3-2-lock.go
func main() {
	d := NewSafeDict(map[string]int{
		"apple": 2,
		"pear":  3,
	})
	go read(d)
	write(d)
}

3-3 使用匿名锁字段

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

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

package main

import (
	"fmt"
	"sync"
)

type SafeDict struct {
	data map[string]int
	*sync.Mutex
}

func NewSafeDict(data map[string]int) *SafeDict {
	return &SafeDict{
		data,
		&sync.Mutex{}, // 一样是要初始化的
	}
}

func (d *SafeDict) Len() int {
	d.Lock()
	defer d.Unlock()
	return len(d.data)
}

func (d *SafeDict) Put(key string, value int) (int, bool) {
	d.Lock()
	defer d.Unlock()
	old_value, ok := d.data[key]
	d.data[key] = value
	return old_value, ok
}

func (d *SafeDict) Get(key string) (int, bool) {
	d.Lock()
	defer d.Unlock()
	old_value, ok := d.data[key]
	return old_value, ok
}

func (d *SafeDict) Delete(key string) (int, bool) {
	d.Lock()
	defer d.Unlock()
	old_value, ok := d.data[key]
	if ok {
		delete(d.data, key)
	}
	return old_value, ok
}

func write(d *SafeDict) {
	d.Put("banana", 5)
}

func read(d *SafeDict) {
	fmt.Println(d.Get("banana"))
}

func main() {
	d := NewSafeDict(map[string]int{
		"apple": 2,
		"pear":  3,
	})
	go read(d)
	write(d)
}

3-4 使用读写锁

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

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

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

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

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

// 3-4 使用读写锁
package main

import (
	"fmt"
	"sync"
)

type SafeDict struct {
	data map[string]int
	*sync.RWMutex	//  sync.Mutex API也有点不一样
}

func NewSafeDict(data map[string]int) *SafeDict {
	return &SafeDict{data, &sync.RWMutex{}}
}

func (d *SafeDict) Len() int {
	d.RLock()
	defer d.RUnlock()
	return len(d.data)
}

func (d *SafeDict) Put(key string, value int) (int, bool) {
	d.Lock()
	defer d.Unlock()
	old_value, ok := d.data[key]
	d.data[key] = value
	return old_value, ok
}

func (d *SafeDict) Get(key string) (int, bool) {
	d.RLock()
	defer d.RUnlock()
	old_value, ok := d.data[key]
	return old_value, ok
}

func (d *SafeDict) Delete(key string) (int, bool) {
	d.Lock()
	defer d.Unlock()
	old_value, ok := d.data[key]
	if ok {
		delete(d.data, key)
	}
	return old_value, ok
}

func write(d *SafeDict) {
	d.Put("banana", 5)
}

func read(d *SafeDict) {
	fmt.Println(d.Get("banana"))
}

func main() {
	d := NewSafeDict(map[string]int{
		"apple": 2,
		"pear":  3,
	})
	go read(d)
	write(d)
}

3.5 发布订阅模型

综合前面学的

支持过滤器设置主题

// 3.5 发布订阅模型
package main

import (
	"fmt"
	"strings"
	"sync"
	"time"
)

type (
	subscriber chan interface{}         // 订阅者为一个通道
	topicFunc  func(v interface{}) bool // 主题为一个过滤器
)

// 发布者对象
type Publisher struct {
	m           sync.RWMutex             //读写锁
	buffer      int                      // 订阅队列的缓存大小
	timeout     time.Duration            // 发布超时时间
	subscribers map[subscriber]topicFunc // 订阅者信息
}

// 构建一个发布者对象,可以设置发布超时时间和缓存队列的长度
func NewPublisher(publishTimeout time.Duration, buffer int) *Publisher {
	return &Publisher{
		buffer:      buffer,
		timeout:     publishTimeout,
		subscribers: make(map[subscriber]topicFunc),
	}

}

// 关闭发布者对象,同时关闭所有的订阅通道
func (p *Publisher) Close() {
	p.m.Lock()
	defer p.m.Unlock()
	for sub := range p.subscribers {
		delete(p.subscribers, sub)
		close(sub)
	}
}

// 添加一个新的订阅者,订阅过滤器筛选后的主题
func (p *Publisher) SubscribeTopic(topic topicFunc) chan interface{} {
	ch := make(chan interface{}, p.buffer)
	p.m.Lock()
	p.subscribers[ch] = topic
	p.m.Unlock()
	return ch
}

// 添加一个新的订阅者,订阅全部主题
func (p *Publisher) Subscribe() chan interface{} {
	return p.SubscribeTopic(nil)
}

// 退出订阅
func (p *Publisher) Evict(sub chan interface{}) {
	p.m.Lock()
	defer p.m.Unlock()
	delete(p.subscribers, sub)
	close(sub)
}

// 发送主题,可以容忍一定的超时
func (p *Publisher) sendTopic(
	sub subscriber, topic topicFunc, v interface{}, wg *sync.WaitGroup,
) {
	defer wg.Done()
	if topic != nil && !topic(v) { // 过滤信息
		return
	}
	select {
	case sub <- v:
	case <-time.After(p.timeout): // 超时
	}
}

// 发布一个主题
func (p *Publisher) Publish(v interface{}) {
	p.m.Lock()
	defer p.m.Unlock()
	var wg sync.WaitGroup
	for sub, topic := range p.subscribers {
		wg.Add(1)
		go p.sendTopic(sub, topic, v, &wg)
	}
	wg.Wait()
}

func main() {
	p := NewPublisher(100*time.Millisecond, 10)
	defer p.Close()

	all := p.Subscribe()

	golang := p.SubscribeTopic(func(v interface{}) bool {
		if s, ok := v.(string); ok {
			return strings.Contains(s, "golang")
		}
		return false
	})

	p.Publish("hello world")
	p.Publish("hello, golang")
	go func() {
		for msg := range all {
			fmt.Println("all:", msg)
		}
	}()
	go func() {
		for msg := range golang {
			fmt.Println("golang:", msg)
		}
	}()

	// 运行一段时间后退出
	time.Sleep(3 * time.Second)
}

3.6 sync.Once初始化

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

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

package main

import (
	"fmt"
	"sync"
	"time"
)

var once sync.Once

func main() {
	for i, v := range make([]string, 10) {
		once.Do(onces)
		fmt.Println("count:", v, "---", i)
	}
	for i := 0; i < 5; i++ {

		go func() {
			once.Do(onced)
			fmt.Println("213")
		}()
	}
	time.Sleep(4000)
}
func onces() {
	fmt.Println("执行onces")
}
func onced() {
	fmt.Println("执行onced")
}

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才可以获取对应的值,这个值一般是线程安全的

package main

import (
	"context"
	"fmt"
	"time"
)

func main() {
	ctx, cancel1 := context.WithCancel(context.Background())
	go func(ctx context.Context) {
		for {
			select {
			case v := <-ctx.Done():
				fmt.Println("监控退出,停止了..., v: ", v, ", err:", ctx.Err())
				return
			default:
				time.Sleep(2 * time.Second)
				fmt.Println("goroutine监控中...")
				// time.Sleep(2 * time.Second)
			}
		}
	}(ctx)

	time.Sleep(5 * time.Second)
	fmt.Println("可以了,通知监控停止")
	cancel1()

	//为了检测监控过是否停止,如果没有监控输出,就表示停止了
	time.Sleep(5 * time.Second)
}

4.1 Background()和TODO()

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

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

Context,也就是根 Context。

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

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

package main

import (
	"context"
	"fmt"
	"time"
)

func main() {
	// ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(3*time.Second))
	// var wg sync.WaitGroup
	go func(ctx context.Context) {
		// wg.Add(1)
		// defer wg.Done()
		for {
			select {
			case <-ctx.Done():
				fmt.Println("监控退出,停止了..., err:", ctx.Err())
				return
			default:
				time.Sleep(2 * time.Second)
				fmt.Println("goroutine监控中...")
				// time.Sleep(2 * time.Second)
			}
		}
	}(ctx)
	// cancel()
	time.Sleep(5 * time.Second)
	fmt.Println("可以了,通知监控停止")

	cancel()
	// wg.Wait() // 等待协程退出
	//为了检测监控过是否停止,如果没有监控输出,就表示停止了
	time.Sleep(5 * time.Second)
}

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方法访问到

package main

import (
	"context"
	"fmt"
	"sync"
	"time"
)

func work(ctx context.Context, wg *sync.WaitGroup) {
	defer wg.Done()
	for {
		select {
		case <-ctx.Done():
			fmt.Println("监控退出,停止了...")
			return
		default:
			fmt.Println("hello")
			time.Sleep(time.Second)
		}
	}
}

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel() // 在建立之后,立即 defer cancel() 是一个好习惯。
	var wg sync.WaitGroup
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go work(ctx, &wg)
	}
	time.Sleep(time.Second)

	wg.Wait()
}

4.3 Context使用原则

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

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

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

context.TODO

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

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

package main

import (
	"context"
	"fmt"
	"time"
)

var key string = "name"
var key2 string = "name1"

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	//附加值
	valueCtx := context.WithValue(ctx, key, "key【监控1】") // 是否可以有多个key
	valueCtx2 := context.WithValue(valueCtx, key2, "key【监控2】")

	go watch(valueCtx2)
	time.Sleep(5 * time.Second)
	fmt.Println("可以了,通知监控停止")
	cancel()
	//为了检测监控过是否停止,如果没有监控输出,就表示停止了
	time.Sleep(5 * time.Second)
}

func watch(ctx context.Context) {
	for {
		select {
		case <-ctx.Done():
			//取出值
			fmt.Println(ctx.Value(key), "监控退出,停止了...")
			fmt.Println(ctx.Value(key2), "监控退出,停止了...")
			return
		default:
			//取出值
			fmt.Println(ctx.Value(key), "goroutine监控中...")
			time.Sleep(2 * time.Second)
		}
	}
}

4.4 Derived contexts派生上下文

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

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

package main

import (
	"context"
	"fmt"
	"time"
)

func work(ctx context.Context, str string) {
	for {
		select {
		case <-ctx.Done():
			fmt.Println("退出 ", str)
			return
		}
	}
}

func main() {
	ctx1 := context.Background()
	ctx2, cancel2 := context.WithCancel(ctx1)
	ctx3, cancel3 := context.WithTimeout(ctx2, time.Second*5)
	ctx4, cancel4 := context.WithTimeout(ctx3, time.Second*3)
	ctx5, cancel5 := context.WithTimeout(ctx4, time.Second*6)
	ctx6 := context.WithValue(ctx5, "userID", 12)
	go work(ctx1, "ctx1")
	go work(ctx2, "ctx2")
	go work(ctx3, "ctx3")
	go work(ctx4, "ctx4")
	go work(ctx5, "ctx5")
	go work(ctx6, "ctx6")

	time.Sleep(1 * time.Second)
	cancel5()
	time.Sleep(5 * time.Second)
	cancel3()
	cancel4()
	cancel5()
	cancel2()
}

推荐教程

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

Golang 如何正确使用 Context

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

cgo go和c混编

#include <stdio.h>  
#include <string.h>
char *fun(char *p1, char *p2)
{
    int i = 0;
    i = strcmp(p1, p2);
    if (0 == i)
    {
        return (p1);
    }
    else
    {
        return (p2);
    }
}
int main()
{
    char *(*pf)(char *p1, char *p2);
    pf = &fun;
    (*pf)("aa", "bb");
    return (0);
}

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处理即可

package main

import (
	"bufio"
	"fmt"
	"net"
	"os"
	"strings"
)

func main() {
	//设置连接模式 , ip和端口号
	conn, err := net.Dial("tcp", "127.0.0.1:8888")
	if err != nil {
		fmt.Println("client dial err=", err)
		return
	}
	defer conn.Close()
	// 在命令行输入单行数据
	reader := bufio.NewReader(os.Stdin)
	for {
		//从终端读取一行用户的输入,并发给服务器
		line, err := reader.ReadString('\n')
		if err != nil {
			fmt.Println("readString err=", err)
		}
		//去掉输入后的换行符
		line = strings.Trim(line, "\r\n")
		//如果是exit,则退出客户端
		if line == "exit" {
			fmt.Println("客户端退出了")
			break
		}
		//将line发送给服务器
		n, e := conn.Write([]byte(line))
		if e != nil {
			fmt.Println("conn.write err=", e)
		}
		fmt.Printf("客户端发送了%d字节的数据\n", n)
	}
}
package main

import (
	"fmt"
	"net"
	_ "time"
)

func process(conn net.Conn) {
	//这里接受客户端的数据
	defer conn.Close()
	for {
		//创建一个新的切片
		buf := make([]byte, 1024)
		//等待客户端发送信息,如果客户端没发送,协程就阻塞在这
		// fmt.Printf("服务器在等待客户端%v的输入\n", conn.RemoteAddr().String())
		// conn.SetReadDeadline(time.Now().Add(time.Duration(1) * time.Second))
		n, err := conn.Read(buf) // 默认是阻塞的
		if err != nil {
			fmt.Println("服务器read err=", err)
			fmt.Println("客户端退出了")
			return
		}
		//显示客户端发送内容到服务器的终端
		fmt.Print(string(buf[:n]) + "\n")

	}
}

func main() {
	fmt.Println("服务器开始监听...")
	//协议、端口
	listen, err := net.Listen("tcp", "0.0.0.0:8888")
	if err != nil {
		fmt.Println("监听失败,err=", err)
		return
	}
	//延时关闭
	defer listen.Close() // 函数退出的时候调用
	for {
		//循环等待客户端连接
		fmt.Println("等待客户端连接...")
		conn, err := listen.Accept()
		if err != nil {
			fmt.Println("Accept() err=", err)
		} else {
			fmt.Printf("Accept() suc con=%v,客户端Ip=%v\n", conn, conn.RemoteAddr().String())
		}
		//这里准备起个协程为客户端服务
		go process(conn)
	}
	//fmt.Printf("监听成功,suv=%v\n", listen)
}

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会几乎立即返回错误,比如:

package main

import (
	"log"
	"net"
)

func main() {
	log.Println("begin dial...")
	conn, err := net.Dial("tcp", ":8888")
	if err != nil {
		log.Println("dial error:", err)
		return
	}
	defer conn.Close()
	log.Println("dial ok")
}

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阻塞。

package main

import (
	"log"
	"net"
	"time"
)

func establishConn(i int) net.Conn {
	conn, err := net.Dial("tcp", ":8888")
	if err != nil {
		log.Printf("%d: dial error: %s", i, err)
		return nil
	}
	log.Println(i, ":connect to server ok")
	return conn
}

func main() {
	var sl []net.Conn
	for i := 1; i < 1000; i++ {
		conn := establishConn(i)
		if conn != nil {
			sl = append(sl, conn)
		}
	}

	time.Sleep(time.Second * 10000)
}

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

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

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

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

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

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

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

package main

import (
	"log"
	"net"
	"time"
)

func main() {
	log.Println("begin dial...")
	conn, err := net.DialTimeout("tcp", "192.168.204.130:8888", 2*time.Second)
	if err != nil {
		log.Println("dial error:", err)
		return
	}
	defer conn.Close()
	log.Println("dial ok")
}

 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上操作的

结果有不同。

package main

import (
	"log"
	"net"
	"time"
)

func main() {
	log.Println("begin dial...")
	conn, err := net.Dial("tcp", ":8888")
	if err != nil {
		log.Println("dial error:", err)
		return
	}
	conn.Close()
	log.Println("close ok")

	var buf = make([]byte, 32)
	n, err := conn.Read(buf)
	if err != nil {
		log.Println("read error:", err)
	} else {
		log.Printf("read % bytes, content is %s\n", n, string(buf[:n]))
	}

	n, err = conn.Write(buf)
	if err != nil {
		log.Println("write error:", err)
	} else {
		log.Printf("write % bytes, content is %s\n", n, string(buf[:n]))
	}

	time.Sleep(time.Second * 1000)
}
package main

import (
	"fmt"
	"log"
	"net"
)

func handleConn(c net.Conn) {
	defer c.Close()

	// read from the connection
	var buf = make([]byte, 10)
	log.Println("start to read from conn")
	n, err := c.Read(buf)
	if err != nil {
		log.Println("conn read error:", err)
	} else {
		log.Printf("read %d bytes, content is %s\n", n, string(buf[:n]))
	}

	n, err = c.Write(buf)
	if err != nil {
		log.Println("conn write error:", err)
	} else {
		log.Printf("write %d bytes, content is %s\n", n, string(buf[:n]))
	}
}

func main() {
	listen, err := net.Listen("tcp", ":8888")
	if err != nil {
		fmt.Println("listen error: ", err)
		return
	}

	for {
		conn, err := listen.Accept()
		if err != nil {
			fmt.Println("accept error: ", err)
			break
		}

		// start a new goroutine to handle the new connection
		go handleConn(conn)
	}
}

1-7 读写超时

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

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

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

package main

import (
	"log"
	"net"
	"os"
	"time"
)

func main() {
	connTimeout := 3 * time.Second
	conn, err := net.DialTimeout("tcp", "127.0.0.1:8080", connTimeout) // 3s timeout
	if err != nil {
		log.Println("dial failed:", err)
		os.Exit(1)
	}
	defer conn.Close()

	readTimeout := 2 * time.Second

	buffer := make([]byte, 512)

	for {
		err = conn.SetReadDeadline(time.Now().Add(readTimeout)) // timeout
		if err != nil {
			log.Println("setReadDeadline failed:", err)
		}

		n, err := conn.Read(buffer)
		if err != nil {
			log.Println("Read failed:", err)
			//break
		}

		log.Println("count:", n, "msg:", string(buffer))
	}

}
package main

import (
	"log"
	"net"
	"time"
)

func main() {
	addr := "0.0.0.0:8080"

	tcpAddr, err := net.ResolveTCPAddr("tcp", addr)

	if err != nil {
		log.Fatalf("net.ResovleTCPAddr fail:%s", addr)
	}

	listener, err := net.ListenTCP("tcp", tcpAddr)
	if err != nil {
		log.Fatalf("listen %s fail: %s", addr, err)
	} else {
		log.Println("listening", addr)
	}

	for {
		conn, err := listener.Accept()
		if err != nil {
			log.Println("listener.Accept error:", err)
			continue
		}

		go handleConnection(conn)
	}

}

func handleConnection(conn net.Conn) {
	defer conn.Close()

	var buffer []byte = []byte("You are welcome. I'm server.")

	for {
		time.Sleep(3 * time.Second) // sleep 3s
		n, err := conn.Write(buffer)
		if err != nil {
			log.Println("Write error:", err)
			break
		}
		log.Println("send:", n)
	}

	log.Println("connetion end")

}

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

package main

import (
	"fmt"
	// "time"

	"github.com/gomodule/redigo/redis"
)

func main() {
	c, err := redis.Dial("tcp", "192.168.204.132:6379")
	c, err := redis.Dial("tcp", "192.168.204.132:6379", 
		redis.DialConnectTimeout(time.Duration(1) * time.Second),
		redis.DialPassword("111"),
		redis.DialDatabase(1))
	if err != nil {
		fmt.Println("conn redis failed,", err)
		return
	}

	defer c.Close()
}

2.2 redis set操作

package main

import (
	"fmt"

	"github.com/gomodule/redigo/redis"
)

func main() {
	c, err := redis.Dial("tcp", "192.168.204.132:6379")
	if err != nil {
		fmt.Println("conn redis failed,", err)
		return
	}

	defer c.Close()
	_, err = c.Do("Set", "count", 100)
	if err != nil {
		fmt.Println(err)
		return
	}

	r, err := redis.Int(c.Do("Get", "count"))
	if err != nil {
		fmt.Println("get count failed,", err)
		return
	}

	fmt.Println(r)
}

2.3 redis Hash操作

package main

import (
	"fmt"

	"github.com/gomodule/redigo/redis"
)

func main() {
	c, err := redis.Dial("tcp", "192.168.204.132:6379")
	if err != nil {
		fmt.Println("conn redis failed,", err)
		return
	}

	defer c.Close()
	_, err = c.Do("HSet", "books", "count", 100)
	if err != nil {
		fmt.Println(err)
		return
	}

	r, err := redis.Int(c.Do("HGet", "books", "count"))
	if err != nil {
		fmt.Println("get count failed,", err)
		return
	}

	fmt.Println(r)
}

2.4 redis mset操作

package main

import (
	"fmt"

	"github.com/gomodule/redigo/redis"
)

func main() {
	c, err := redis.Dial("tcp", "192.168.204.132:6379")
	if err != nil {
		fmt.Println("conn redis failed,", err)
		return
	}

	defer c.Close()
	_, err = c.Do("MSet", "count", 100, "efg", 300)
	if err != nil {
		fmt.Println(err)
		return
	}

	r, err := redis.Ints(c.Do("MGet", "count", "efg"))
	if err != nil {
		fmt.Println("get count failed,", err)
		return
	}

	for _, v := range r {
		fmt.Println(v)
	}
}

2.5 redis expire操作

2.6 redis list操作

2-7 subpub操作 

package main

import (
	"fmt"
	"time"

	red "github.com/gomodule/redigo/redis"
)

type Redis struct {
	pool *red.Pool
}

var redis *Redis

func initRedis() {
	redis = new(Redis)
	redis.pool = &red.Pool{
		MaxIdle:     256,
		MaxActive:   0,
		IdleTimeout: time.Duration(120),
		Dial: func() (red.Conn, error) {
			return red.Dial(
				"tcp",
				"127.0.0.1:6379",
				red.DialReadTimeout(time.Duration(1000)*time.Millisecond),
				red.DialWriteTimeout(time.Duration(1000)*time.Millisecond),
				red.DialConnectTimeout(time.Duration(1000)*time.Millisecond),
				red.DialDatabase(0),
				//red.DialPassword(""),
			)
		},
	}
}

func Exec(cmd string, key interface{}, args ...interface{}) (interface{}, error) {
	con := redis.pool.Get()
	if err := con.Err(); err != nil {
		return nil, err
	}
	defer con.Close()
	parmas := make([]interface{}, 0)
	parmas = append(parmas, key)

	if len(args) > 0 {
		for _, v := range args {
			parmas = append(parmas, v)
		}
	}
	return con.Do(cmd, parmas...)
}

func main() {
	initRedis()

	Exec("set", "hello", "world")
	fmt.Print(2)
	result, err := Exec("get", "hello")
	if err != nil {
		fmt.Print(err.Error())
	}
	str, _ := red.String(result, err)
	fmt.Print(str)
}
package main

import (
	"context"
	"fmt"
	"time"

	"github.com/gomodule/redigo/redis"
)

// listenPubSubChannels listens for messages on Redis pubsub channels. The
// onStart function is called after the channels are subscribed. The onMessage
// function is called for each message.
func listenPubSubChannels(ctx context.Context, redisServerAddr string,
	onStart func() error,
	onMessage func(channel string, data []byte) error,
	channels ...string) error {
	// A ping is set to the server with this period to test for the health of
	// the connection and server.
	const healthCheckPeriod = time.Minute

	c, err := redis.Dial("tcp", redisServerAddr,
		// Read timeout on server should be greater than ping period.
		redis.DialReadTimeout(healthCheckPeriod+10*time.Second),
		redis.DialWriteTimeout(10*time.Second))
	if err != nil {
		return err
	}
	defer c.Close()

	psc := redis.PubSubConn{Conn: c}

	if err := psc.Subscribe(redis.Args{}.AddFlat(channels)...); err != nil {
		return err
	}

	done := make(chan error, 1)

	// Start a goroutine to receive notifications from the server.
	go func() {
		for {
			switch n := psc.Receive().(type) {
			case error:
				done <- n
				return
			case redis.Message:
				if err := onMessage(n.Channel, n.Data); err != nil {
					done <- err
					return
				}
			case redis.Subscription:
				switch n.Count {
				case len(channels):
					// Notify application when all channels are subscribed.
					if err := onStart(); err != nil {
						done <- err
						return
					}
				case 0:
					// Return from the goroutine when all channels are unsubscribed.
					done <- nil
					return
				}
			}
		}
	}()

	ticker := time.NewTicker(healthCheckPeriod)
	defer ticker.Stop()
loop:
	for err == nil {
		select {
		case <-ticker.C:
			// Send ping to test health of connection and server. If
			// corresponding pong is not received, then receive on the
			// connection will timeout and the receive goroutine will exit.
			if err = psc.Ping(""); err != nil {
				break loop
			}
		case <-ctx.Done():
			break loop
		case err := <-done:
			// Return error from the receive goroutine.
			return err
		}
	}

	// Signal the receiving goroutine to exit by unsubscribing from all channels.
	psc.Unsubscribe()

	// Wait for goroutine to complete.
	return <-done
}

func publish() {
	c, err := dial()
	if err != nil {
		fmt.Println(err)
		return
	}
	defer c.Close()

	c.Do("PUBLISH", "c1", "hello")
	c.Do("PUBLISH", "c2", "world")
	c.Do("PUBLISH", "c1", "goodbye")
}

// This example shows how receive pubsub notifications with cancelation and
// health checks.
func main() {
	redisServerAddr := "192.168.204.132:6379"

	ctx, cancel := context.WithCancel(context.Background())

	err := listenPubSubChannels(ctx,
		redisServerAddr,
		func() error {
			// The start callback is a good place to backfill missed
			// notifications. For the purpose of this example, a goroutine is
			// started to send notifications.
			go publish()
			return nil
		},
		func(channel string, message []byte) error {
			fmt.Printf("channel: %s, message: %s\n", channel, message)

			// For the purpose of this example, cancel the listener's context
			// after receiving last message sent by publish().
			if string(message) == "goodbye" {
				cancel()
			}
			return nil
		},
		"c1", "c2")

	if err != nil {
		fmt.Println(err)
		return
	}

}

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连接池的坑

package main

import (
	"fmt"
	"time"

	red "github.com/gomodule/redigo/redis"
)

type Redis struct {
	pool *red.Pool
}

var redis *Redis

func initRedis() {
	redis = new(Redis)
	redis.pool = &red.Pool{
		MaxIdle:     256,
		MaxActive:   0,
		IdleTimeout: time.Duration(120),
		Dial: func() (red.Conn, error) {
			return red.Dial(
				"tcp",
				"127.0.0.1:6379",
				red.DialReadTimeout(time.Duration(1000)*time.Millisecond),
				red.DialWriteTimeout(time.Duration(1000)*time.Millisecond),
				red.DialConnectTimeout(time.Duration(1000)*time.Millisecond),
				red.DialDatabase(0),
				//red.DialPassword(""),
			)
		},
	}
}

func Exec(cmd string, key interface{}, args ...interface{}) (interface{}, error) {
	con := redis.pool.Get()
	if err := con.Err(); err != nil {
		return nil, err
	}
	defer con.Close()
	parmas := make([]interface{}, 0)
	parmas = append(parmas, key)

	if len(args) > 0 {
		for _, v := range args {
			parmas = append(parmas, v)
		}
	}
	return con.Do(cmd, parmas...)
}

func main() {
	initRedis()

	Exec("set", "hello", "world")
	fmt.Print(2)
	result, err := Exec("get", "hello")
	if err != nil {
		fmt.Print(err.Error())
	}
	str, _ := red.String(result, err)
	fmt.Print(str)
}

遇到过的问题

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

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。

package main

import (
	"bytes"
	"fmt"
	"io"
	"sync"
)

// 存放数据块缓冲区的临时对象
var bufPool sync.Pool

// 预定义定界符
const delimiter = '\n'

// 一个简易的数据库缓冲区的接口
type Buffer interface {
	Delimiter() byte                    // 获取数据块之间的定界符
	Write(contents string) (err error)  // 写入一个数据块
	Read() (contents string, err error) // 读取一个数据块
	Free()                              // 释放当前的缓冲区
}

// 实现一个上面定义的接口
type myBuffer struct {
	buf       bytes.Buffer
	delimiter byte
}

func (b *myBuffer) Delimiter() byte {
	return b.delimiter
}

func (b *myBuffer) Write(contents string) (err error) {
	if _, err = b.buf.WriteString(contents); err != nil {
		return
	}
	return b.buf.WriteByte(b.delimiter)
}

func (b *myBuffer) Read() (contents string, err error) {
	return b.buf.ReadString(b.delimiter)
}

func (b *myBuffer) Free() {
	bufPool.Put(b)
}

func init() {
	bufPool = sync.Pool{
		New: func() interface{} {
			return &myBuffer{delimiter: delimiter}
		},
	}
}

// 获取一个数据库缓冲区
func GetBuffer() Buffer {
	return bufPool.Get().(Buffer) // 做类型转换
}

func main() {
	buf := GetBuffer()
	defer buf.Free()
	buf.Write("写入第一行,")	// 写入数据
	buf.Write("接着写第二行。")	// 写入数据
	fmt.Println("数据已经写入,准备把数据读出")
	for {
		block, err := buf.Read()
		if err != nil {
			if err == io.EOF {
				break
			}
			panic(fmt.Errorf("读取缓冲区时ERROR: %s", err))
		}
		fmt.Print(block)
	}
}
package main

import (
	"fmt"
	_ "runtime"
	"runtime/debug"
	"sync"
	"sync/atomic"
)

func main() {
	// 禁用GC,并保证在main函数执行结束前恢复GC
	defer debug.SetGCPercent(debug.SetGCPercent(-1))
	var count int32

	// 实现一个函数 ,生成新对象
	newFunc := func() interface{} {
		fmt.Println("newFunc:", count)
		return atomic.AddInt32(&count, 1)
	}
	pool := sync.Pool{New: newFunc}	// 传入生成对象的函数....

	// New 字段值的作用
	v1 := pool.Get()	// 调用GET接口去取
	fmt.Printf("v1: %v\n", v1)
	pool.Put(v1)		// 放回去
	// 临时对象池的存取
	pool.Put(newFunc())
	// pool.Put(newFunc())
	// pool.Put(newFunc())
	v2 := pool.Get()
	// pool.Put(v2)
	fmt.Printf("v2: %v\n", v2)		// 这个时候v1和v2应该是一样

	// 垃圾回收对临时对象池的影响
	// debug.SetGCPercent(100)
	// runtime.GC()
	v3 := pool.Get()
	fmt.Printf("v3: %v\n", v3)
	pool.New = nil
	v4 := pool.Get()
	fmt.Printf("v4: %v\n", v4)
}

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

;redis cache
USER_LIST = USER:LIST
MAX_COUNT = 50
MAX_PRICE = 123456
IS_SHOW = true

[test]
dbdns = root:@tcp(127.0.0.1:3306)

[prod]
dbdns = root:@tcp(172.168.1.1:3306)

4-1-config-ini.go

package main

import (
	"fmt"
	"log"

	"github.com/Unknwon/goconfig"
)

func main() {
	cfg, err := goconfig.LoadConfigFile("./conf.ini")	// 读取后文件关闭了
	if err != nil {
		log.Fatalf("无法加载配置文件:%s", err)
	}
	userListKey, err := cfg.GetValue("", "USER_LIST")
	if err != nil {
		fmt.Println(err.Error())
	}
	fmt.Println(userListKey)
	userListKey2, _ := cfg.GetValue(goconfig.DEFAULT_SECTION, "USER_LIST")
	fmt.Println(userListKey2)
	maxCount := cfg.MustInt("", "MAX_COUNT")
	fmt.Println(maxCount)
	maxPrice := cfg.MustFloat64("", "MAX_PRICE")
	fmt.Println(maxPrice)
	isShow := cfg.MustBool("", "IS_SHOW")
	fmt.Println(isShow)

	db := cfg.MustValue("test", "dbdns")
	fmt.Println(db)

	dbProd := cfg.MustValue("prod", "dbdns")
	fmt.Println("dbProd: ",dbProd)

	//set 值
	cfg.SetValue("", "MAX_NEW", "100")
	maxNew := cfg.MustInt("", "MAX_NEW")
	fmt.Println(maxNew)

	maxNew1, err := cfg.Int("", "MAX_NEW")
	if err != nil {
		fmt.Println(err.Error())
	}
	fmt.Println(maxNew1)
	cfg.AppendFiles("conf1.ini")
	// cfg.DeleteKey("", "MAX_NEW")
}

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

package main

import (
	"flag"
	"fmt"
	//    "os"
)

// go run 4-1-cli-flag.go -ok -id 11111 -port 8899 -name TestUser very goo
func main() {
	//    fmt.Println(os.Args)
	ok := flag.Bool("ok", false, "is ok")		// 不设置ok 则为false
	id := flag.Int("id", 0, "id")
	port := flag.String("port", ":8080", "http listen port")
	var name string
	flag.StringVar(&name, "name", "Jack", "name")

	flag.Parse()
	//    flag.Usage()
	others := flag.Args()

	fmt.Println("ok:", *ok)
	fmt.Println("id:", *id)
	fmt.Println("port:", *port)
	fmt.Println("name:", name)
	fmt.Println("other:", others)
}

5-2-self-flag.go

package main

import (
	"flag"
	"fmt"
)

type FlagSet struct {
	Usage func()
}

var myFlagSet = flag.NewFlagSet("myflagset", flag.ExitOnError)
var stringFlag = myFlagSet.String("abc", "default value", "help mesage")

func main() {
	myFlagSet.Parse([]string{"-abc", "def", "ghi", "123"})
	args := myFlagSet.Args()
	for i := range args {
		fmt.Println(i, myFlagSet.Arg(i))
	}
}

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

package main

import (
	"fmt"
	"log"
	"math/rand"
	"reflect"
	"time"

	"gitee.com/GuaikOrg/go-snowflake/snowflake"
	"github.com/chilts/sid"
	"github.com/kjk/betterguid"
	"github.com/oklog/ulid"
	"github.com/rs/xid"
	uuid "github.com/satori/go.uuid"
	"github.com/segmentio/ksuid"
	"github.com/sony/sonyflake"
)

const FOR_LOOP = 100000

func genXid() {
	id := xid.New()
	fmt.Printf("github.com/rs/xid:           %s, len:%d\n", id.String(), len(id.String()))
}

func genKsuid() {
	id := ksuid.New()
	fmt.Printf("github.com/segmentio/ksuid:  %s, len:%d\n", id.String(), len(id.String()))
}

func genBetterGUID() {
	id := betterguid.New()
	fmt.Printf("github.com/kjk/betterguid:   %s, len:%d\n", id, len(id))
}

func genUlid() {
	t := time.Now().UTC()
	entropy := rand.New(rand.NewSource(t.UnixNano()))
	id := ulid.MustNew(ulid.Timestamp(t), entropy)
	fmt.Printf("github.com/oklog/ulid:       %s, len:%d\n", id.String(), len(id.String()))
}

// https://gitee.com/GuaikOrg/go-snowflake
func genSnowflake() {
	flake, err := snowflake.NewSnowflake(int64(0), int64(0))
	if err != nil {
		log.Fatalf("snowflake.NewSnowflake failed with %s\n", err)
	}
	id := flake.NextVal()
	fmt.Printf("gitee.com/GuaikOrg/go-snowflake:%x, type:%s\n", id, reflect.TypeOf(id))
}

func genSonyflake() {
	flake := sonyflake.NewSonyflake(sonyflake.Settings{})
	id, err := flake.NextID()
	if err != nil {
		log.Fatalf("flake.NextID() failed with %s\n", err)
	}
	fmt.Printf("github.com/sony/sonyflake:   %x, type:%s\n", id, reflect.TypeOf(id))
}

func genSid() {
	id := sid.Id()
	fmt.Printf("github.com/chilts/sid:       %s, len:%d\n", id, len(id))
}

func genUUIDv4() {
	id, err := uuid.NewV4()
	if err != nil {
		fmt.Printf("get uuid error [%s]", err)
	}
	fmt.Printf("github.com/satori/go.uuid:   %s, len:%d\n", id, len(id))
}

func testGenXid(n int) {
	t0 := time.Now()
	for i := 0; i < n; i++ {
		_ = xid.New()
	}
	elapsed := time.Since(t0)
	fmt.Println("github.com/rs/xid          n:", n, "time:", elapsed)
}

func testGenKsuid(n int) {
	t0 := time.Now()
	for i := 0; i < n; i++ {
		_ = ksuid.New()
	}
	elapsed := time.Since(t0)
	fmt.Println("github.com/segmentio/ksuid n:", n, "time:", elapsed)
}

func testGenBetterguid(n int) {
	t0 := time.Now()
	for i := 0; i < n; i++ {
		_ = betterguid.New()
	}
	elapsed := time.Since(t0)
	fmt.Println("github.com/kjk/betterguid  n:", n, "time:", elapsed)
}

func testGenUlid(n int) {
	t0 := time.Now()
	for i := 0; i < n; i++ {
		t := time.Now().UTC()
		entropy := rand.New(rand.NewSource(t.UnixNano()))
		_ = ulid.MustNew(ulid.Timestamp(t), entropy)
	}
	elapsed := time.Since(t0)
	fmt.Println("github.com/oklog/ulid      n:", n, "time:", elapsed)
}

func testGenSnowflake(n int) {
	t0 := time.Now()
	flake, err := snowflake.NewSnowflake(int64(0), int64(0))
	if err != nil {
		log.Fatalf("snowflake.NewSnowflake failed with %s\n", err)
	}
	for i := 0; i < n; i++ {
		_ = flake.NextVal()
	}
	elapsed := time.Since(t0)
	fmt.Println("gitee.com/GuaikOrg/go-snowflake n:", n, "time:", elapsed)
}
func testGenSonyflake(n int) {
	t0 := time.Now()
	flake := sonyflake.NewSonyflake(sonyflake.Settings{}) // 注意这一行的位置
	for i := 0; i < n; i++ {
		_, err := flake.NextID()
		if err != nil {
			log.Fatalf("flake.NextID() failed with %s\n", err)
		}
	}
	elapsed := time.Since(t0)
	fmt.Println("github.com/sony/sonyflake  n:", n, "time:", elapsed)
}

func testGenSid(n int) {
	t0 := time.Now()
	for i := 0; i < n; i++ {
		_ = sid.Id()
	}
	elapsed := time.Since(t0)
	fmt.Println("github.com/chilts/sid      n:", n, "time:", elapsed)
}

func testGenUUIDv4(n int) {
	t0 := time.Now()
	for i := 0; i < n; i++ {
		_, err := uuid.NewV4()
		if err != nil {
			fmt.Printf("get uuid error [%s]", err)
		}
	}
	elapsed := time.Since(t0)
	fmt.Println("github.com/satori/go.uuid  n:", n, "time:", elapsed)
}

func main() {
	fmt.Printf("效果展示...\n")
	genXid()
	genXid()
	genXid()
	genKsuid()
	genBetterGUID()
	genUlid()
	genSnowflake()
	genSonyflake()
	genSid()
	genUUIDv4()
	fmt.Printf("性能测试...\n")
	testGenXid(FOR_LOOP)
	testGenKsuid(FOR_LOOP)
	testGenBetterguid(FOR_LOOP)
	testGenUlid(FOR_LOOP)
	testGenSnowflake(FOR_LOOP)
	testGenSonyflake(FOR_LOOP)
	testGenSid(FOR_LOOP)
	testGenUUIDv4(FOR_LOOP)
}

// github.com/rs/xid          n: 1000000 time: 29.2665ms
// github.com/segmentio/ksuid n: 1000000 time: 311.4816ms
// github.com/kjk/betterguid  n: 1000000 time: 89.2803ms
// github.com/oklog/ulid   n: 1000000 time: 11.746259s
// github.com/sony/sonyflake   n: 1000000 time: 39.0713342s
// thub.com/chilts/sid        n: 1000000 time: 254.9442ms
// 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

package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
)

func main() {
	// resp, err := http.Get("http://127.0.0.1:9000")
	resp, err := http.Get("https://www.baidu.com/")
	if err != nil {
		fmt.Println("get err:", err)
		return
	}
	defer resp.Body.Close() // 做关闭
	// data byte
	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println("get data err:", err)
		return
	}

	fmt.Println("body:", string(data))
	fmt.Println("resp:", resp)
}

2.2.2 带参数的Get方法示例

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

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

package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
)

func main() {

	//1.处理请求参数
	params := url.Values{}
	params.Set("name", "dar")
	params.Set("hobby", "足球")

	//2.设置请求URL
	rawUrl := "http://127.0.0.1:9000"
	reqURL, err := url.ParseRequestURI(rawUrl)
	if err != nil {
		fmt.Printf("url.ParseRequestURI()函数执行错误,错误为:%v\n", err)
		return
	}

	//3.整合请求URL和参数
	//Encode方法将请求参数编码为url编码格式("bar=baz&foo=quux"),编码时会以键进行排序。
	reqURL.RawQuery = params.Encode()

	//4.发送HTTP请求
	//说明: reqURL.String() String将URL重构为一个合法URL字符串。
	fmt.Println("Get url:", reqURL.String())
	resp, err := http.Get(reqURL.String())
	if err != nil {
		fmt.Printf("http.Get()函数执行错误,错误为:%v\n", err)
		return
	}
	defer resp.Body.Close()

	//5.一次性读取响应的所有内容
	body, err := ioutil.ReadAll(resp.Body)

	if err != nil {
		fmt.Printf("ioutil.ReadAll()函数执行出错,错误为:%v\n", err)
		return
	}

	fmt.Println("Response: ", string(body))
}

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

package main

import (
	"fmt"
	"net/http"
)
// 响应: http.ResponseWriter
// 请求:http.Request 
func myHandler(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()
	params := r.URL.Query()
	fmt.Println("r.URL: ", r.URL)
	fmt.Fprintln(w, "name:", params.Get("name"), "hobby:", params.Get("hobby")) // 回写数据
}
func main() {

	http.HandleFunc("/", myHandler)
	err := http.ListenAndServe("127.0.0.1:9000", nil)
	if err != nil {
		fmt.Printf("http.ListenAndServe()函数执行错误,错误为:%v\n", err)
		return
	}
}

2.3.1 post方法

发送POST请求的示例代码

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

package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"
)

// net/http post demo

func main() {
	url := "http://127.0.0.1:9000/post"
	contentType := "application/json"
	data := `{"name":"darren","age":18}`
	resp, err := http.Post(url, contentType, strings.NewReader(data))
	if err != nil {
		fmt.Println("post failed, err:%v\n", err)
		return
	}
	defer resp.Body.Close()
	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println("get resp failed,err:%v\n", err)
		return
	}
	fmt.Println("StatusCode:", resp.StatusCode)
	fmt.Println(string(b))
}

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

package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
)

func postHandler(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()
	fmt.Println("Method ", r.Method)
	if r.Method == "POST" {
		// 1. 请求类型是application/json时从r.Body读取数据
		b, err := ioutil.ReadAll(r.Body)
		if err != nil {
			fmt.Println("read request.Body failed, err:%v\n", err)
			return
		}
		fmt.Println(string(b))
		answer := `{"status": "ok"}`
		w.Write([]byte(answer))
	} else {
		fmt.Println("can't handle ", r.Method)
		w.WriteHeader(http.StatusBadRequest)
	}

}
func main() {

	http.HandleFunc("/post", postHandler)
	err := http.ListenAndServe("0.0.0.0:9000", nil)
	if err != nil {
		fmt.Printf("http.ListenAndServe()函数执行错误,错误为:%v\n", err)
		return
	}
}

2.4 head方法-client

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

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

主要有以下特点:

1、只请求资源的首部;

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

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

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

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

信息等

package main

import (
	"fmt"
	"net"
	"net/http"
	"time"
)

func main() {

	url := "http://www.baidu1.com"
	c := http.Client{
		Transport: &http.Transport{
			Dial: func(network, addr string) (net.Conn, error) {
				timeout := time.Second * 2
				return net.DialTimeout(network, addr, timeout)
			},
		},
	}
	resp, err := c.Head(url)
	if err != nil {
		fmt.Printf("head %s failed, err:%v\n", url, err)
	} else {
		fmt.Printf("%s head succ, status:%v\n", url, resp.Status)
	}

}

2.5 表单处理

package main

import (
	"fmt"
	"io"
	"net/http"
)

const form = `<html><body><form action="#" method="post" name="bar">
                    <input type="text" name="in"/>
                    <input type="text" name="in"/>
                     <input type="submit" value="Submit"/>
             </form></html></body>`

func HomeServer(w http.ResponseWriter, request *http.Request) {
	io.WriteString(w, "/test1 或者/test2")
	// io.WriteString(w, "<h1>/test1 或者/test2</h1>")
}

func SimpleServer(w http.ResponseWriter, request *http.Request) {
	io.WriteString(w, "<h1>hello, world</h1>")
}

func FormServer(w http.ResponseWriter, request *http.Request) {
	w.Header().Set("Content-Type", "text/html")
	switch request.Method {
	case "GET":
		io.WriteString(w, form)
	case "POST":
		request.ParseForm()
		fmt.Println("request.Form[in]:", request.Form["in"])
		io.WriteString(w, request.Form["in"][0])
		io.WriteString(w, "\n")
		io.WriteString(w, request.Form["in"][1])	// go web开发
		// var ptr *int
		// *ptr = 0x123445 // 模拟异常
	}
}
func main() {
	http.HandleFunc("/", HomeServer)
	http.HandleFunc("/test1", SimpleServer)
	http.HandleFunc("/test2", FormServer)
	err := http.ListenAndServe(":9000", nil)
	if err != nil {
		fmt.Printf("http.ListenAndServe()函数执行错误,错误为:%v\n", err)
		return
	}
}

2.6 panic处理

2-6-panic-server.go

package main

import (
	"fmt"
	"io"
	"log"
	"net/http"
)

const form = `<html><body><form action="#" method="post" name="bar">
                    <input type="text" name="in"/>
                    <input type="text" name="in"/>
                     <input type="submit" value="Submit"/>
             </form></html></body>`

func HomeServer(w http.ResponseWriter, request *http.Request) {
	io.WriteString(w, "<h1>/test1 或者/test2</h1>")
}

func SimpleServer(w http.ResponseWriter, request *http.Request) {
	io.WriteString(w, "<h1>hello, world</h1>")
}

func FormServer(w http.ResponseWriter, request *http.Request) {
	w.Header().Set("Content-Type", "text/html")
	switch request.Method {
	case "GET":
		io.WriteString(w, form)
	case "POST":
		request.ParseForm()
		fmt.Println("request.Form[in]:", request.Form["in"])
		io.WriteString(w, request.Form["in"][0])
		io.WriteString(w, "\n")
		io.WriteString(w, request.Form["in"][1])
		// var ptr *int
		// *ptr = 0x123445 // 模拟异常  注意协程的异常处理
		var ptr *int
		var a int
		ptr = &a
		*ptr = 0x123445 // 也是可以取地址写入的
	}
}
func main() {
	http.HandleFunc("/", HomeServer)
	http.HandleFunc("/test1", logPanics(SimpleServer))
	http.HandleFunc("/test2", logPanics(FormServer))
	err := http.ListenAndServe(":9000", nil)
	if err != nil {
		fmt.Printf("http.ListenAndServe()函数执行错误,错误为:%v\n", err)
		return
	}
}

func logPanics(handle http.HandlerFunc) http.HandlerFunc {
	return func(writer http.ResponseWriter, request *http.Request) {
		defer func() {
			if x := recover(); x != nil {
				log.Printf("[%v] caught panic: %v", request.RemoteAddr, x)
			}
		}()
		handle(writer, request)
	}
}

3 模板

3 模板

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

3-1-template.go

package main

import (
	"fmt"
	"html/template"
	"io"
	"net/http"
)

var myTemplate *template.Template

type Result struct {
	output string
}

func (p *Result) Write(b []byte) (n int, err error) {
	fmt.Println("called by template")
	p.output += string(b)
	return len(b), nil
}

type Person struct {
	Name  string
	Title string
	Age   int
}

func userInfo(w http.ResponseWriter, r *http.Request) {
	fmt.Println("handle hello")
	//fmt.Fprintf(w, "hello ")
	var arr []Person
	p := Person{Name: "Dar", Age: 18, Title: "个人网站"}
	p1 := Person{Name: "Ki", Age: 19, Title: "个人网站"}
	p2 := Person{Name: "子", Age: 20, Title: "个人网站"}
	arr = append(arr, p)
	arr = append(arr, p1)
	arr = append(arr, p2)

	fmt.Println("arr:", arr)

	resultWriter := &Result{}
	io.WriteString(resultWriter, "hello 模板")
	err := myTemplate.Execute(w, arr) // 模板替换, 执行完后, html模板和参数arr就写入 w http.ResponseWriter
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println("template render data:", resultWriter.output)
	//myTemplate.Execute(w, p)
	//myTemplate.Execute(os.Stdout, p)
	//file, err := os.OpenFile("C:/test.log", os.O_CREATE|os.O_WRONLY, 0755)
	//if err != nil {
	//	fmt.Println("open failed err:", err)
	//	return
	//}

}

func initTemplate(filename string) (err error) {
	myTemplate, err = template.ParseFiles(filename)
	if err != nil {
		fmt.Println("parse file err:", err)
		return
	}
	return
}

func main() {
	initTemplate("./index.html")
	http.HandleFunc("/user/info", userInfo)
	err := http.ListenAndServe("0.0.0.0:9000", nil)
	if err != nil {
		fmt.Println("http listen failed")
	}
}

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")

package main

import (
	"database/sql"
	"fmt"

	_ "github.com/go-sql-driver/mysql" // 注释掉后异常 _ 调用初始化函数
)

// https://github.com/go-sql-driver/mysql#usage
func main() {
	db, err := sql.Open("mysql", "root:1234@tcp(127.0.0.1:3306)/go_test?charset=utf8mb4")
	fmt.Println("err:", err) // err: <nil>
	if db == nil {
		fmt.Println("db open failed:", err)
	}

	err = db.Ping() //Ping verifies a connection to the database is still alive, establishing a connection if necessary
	if err != nil {
		fmt.Println("数据库链接失败", err)
	}
	defer db.Close()
}

4-1 mysql插入数据

4-1-mysql.go

package main

import (
	"database/sql"
	"fmt"

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

// 插入数据
func insertRowDemo(db *sql.DB) {
	sqlStr := "insert into user(name, age) values (?,?)"
	ret, err := db.Exec(sqlStr, "darren", 18)
	if err != nil {
		fmt.Printf("insert failed, err:%v\n", err)
		return
	}
	theID, err := ret.LastInsertId() // 新插入数据的id
	if err != nil {
		fmt.Printf("get lastinsert ID failed, err:%v\n", err)
		return
	}
	fmt.Printf("insert success, the id is %d.\n", theID)
}
func main() {
	db, err := sql.Open("mysql", "root:1234@tcp(127.0.0.1:3306)/go_test?charset=utf8mb4")
	fmt.Println("err:", err)
	err = db.Ping()
	if err != nil {
		fmt.Println("数据库链接失败", err)
		return
	}
	insertRowDemo(db)
	defer db.Close()
}

4-2 mysql查询-单行查询

单行查询

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

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

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

4-2-mysql-query copy.go

package main

import (
	"database/sql"
	"fmt"

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

type user struct {
	id   int
	name string
	age  int
}

// 查询单条数据示例
func queryRowDemo(db *sql.DB) {
	sqlStr := "select id, name, age from user where id=?"
	var u user
	// 非常重要:确保QueryRow之后调用Scan方法,否则持有的数据库链接不会被释放
	err := db.QueryRow(sqlStr, 3).Scan(&u.id, &u.name, &u.age)
	if err != nil {
		fmt.Printf("scan failed, err:%v\n", err)
		return
	}
	fmt.Printf("id:%d name:%s age:%d\n", u.id, u.name, u.age)
}
func main() {
	db, err := sql.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/go_test?charset=utf8mb4")
	fmt.Println("err:", err)
	err = db.Ping()
	if err != nil {
		fmt.Println("数据库链接失败", err)
		return
	}
	queryRowDemo(db)
	defer db.Close()
}

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

package main

import (
	"database/sql"
	"fmt"

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

type user struct {
	id   int
	name string
	age  int
}

// 查询多条数据示例
func queryMultiRowDemo(db *sql.DB) {
	sqlStr := "select id, name, age from user where id > ?"
	rows, err := db.Query(sqlStr, 0)
	if err != nil {
		fmt.Printf("query failed, err:%v\n", err)
		return
	}
	// 非常重要:关闭rows释放持有的数据库链接
	defer rows.Close()

	// 循环读取结果集中的数据
	for rows.Next() {
		var u user
		err := rows.Scan(&u.id, &u.name, &u.age) // 通过SCAN读取出来
		if err != nil {
			fmt.Printf("scan failed, err:%v\n", err)
			return
		}
		fmt.Printf("id:%d name:%s age:%d\n", u.id, u.name, u.age)
	}
}

func main() {
	db, err := sql.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/go_test?charset=utf8mb4")
	fmt.Println("err:", err)
	err = db.Ping()
	if err != nil {
		fmt.Println("数据库链接失败", err)
		return
	}
	queryMultiRowDemo(db)
	defer db.Close()
}

4-3 mysql更新

4-3-mysql-update.go

package main

import (
	"database/sql"
	"fmt"

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

type user struct {
	id   int
	name string
	age  int
}

// 更新数据
func updateRowDemo(db *sql.DB) {
	sqlStr := "update user set age=? where id = ?"
	ret, err := db.Exec(sqlStr, 20, 2)
	if err != nil {
		fmt.Printf("update failed, err:%v\n", err)
		return
	}
	n, err := ret.RowsAffected() // 操作影响的行数
	if err != nil {
		fmt.Printf("get RowsAffected failed, err:%v\n", err)
		return
	}
	fmt.Printf("update success, affected rows:%d\n", n)
}
func main() {
	db, err := sql.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/go_test?charset=utf8mb4")
	// fmt.Println("err:", err)
	err = db.Ping()
	if err != nil {
		fmt.Println("数据库链接失败", err)
		return
	}
	updateRowDemo(db)
	defer db.Close()
}

4-4 mysql删除

4-4-mysql-delete.go

package main

import (
	"database/sql"
	"fmt"

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

type user struct {
	id   int
	name string
	age  int
}

// 删除数据
func deleteRowDemo(db *sql.DB) {
	sqlStr := "delete from user where id = ?"
	ret, err := db.Exec(sqlStr, 1)
	if err != nil {
		fmt.Printf("delete failed, err:%v\n", err)
		return
	}
	n, err := ret.RowsAffected() // 操作影响的行数
	if err != nil {
		fmt.Printf("get RowsAffected failed, err:%v\n", err)
		return
	}
	fmt.Printf("delete success, affected rows:%d\n", n)
}

func main() {
	db, err := sql.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/go_test?charset=utf8mb4")
	// fmt.Println("err:", err)
	err = db.Ping()
	if err != nil {
		fmt.Println("数据库链接失败", err)
		return
	}
	deleteRowDemo(db)
	defer db.Close()
}

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

package main

import (
	"database/sql"
	"fmt"

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

type user struct {
	id   int
	name string
	age  int
}

// 预处理查询示例
func prepareQueryDemo(db *sql.DB) {
	sqlStr := "select id, name, age from user where id > ?"
	stmt, err := db.Prepare(sqlStr)
	if err != nil {
		fmt.Printf("prepare failed, err:%v\n", err)
		return
	}
	defer stmt.Close()
	rows, err := stmt.Query(0)
	if err != nil {
		fmt.Printf("query failed, err:%v\n", err)
		return
	}
	defer rows.Close()
	// 循环读取结果集中的数据
	for rows.Next() {
		var u user
		err := rows.Scan(&u.id, &u.name, &u.age)
		if err != nil {
			fmt.Printf("scan failed, err:%v\n", err)
			return
		}
		fmt.Printf("id:%d name:%s age:%d\n", u.id, u.name, u.age)
	}
}

// 预处理插入示例
// 插入、更新和删除操作的预处理十分类似
func prepareInsertDemo(db *sql.DB) {
	sqlStr := "insert into user(name, age) values (?,?)"
	stmt, err := db.Prepare(sqlStr)
	if err != nil {
		fmt.Printf("prepare failed, err:%v\n", err)
		return
	}
	defer stmt.Close()
	_, err = stmt.Exec("darren", 18)
	if err != nil {
		fmt.Printf("insert failed, err:%v\n", err)
		return
	}
	_, err = stmt.Exec("柚子老师", 18)
	if err != nil {
		fmt.Printf("insert failed, err:%v\n", err)
		return
	}
	fmt.Println("insert success.")
}

func main() {
	db, err := sql.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/go_test?charset=utf8mb4")
	// fmt.Println("err:", err)
	err = db.Ping()
	if err != nil {
		fmt.Println("数据库链接失败", err)
		return
	}
	prepareInsertDemo(db)
	prepareQueryDemo(db)
	defer db.Close()
}

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

package main

import (
	"database/sql"
	"fmt"

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

type user struct {
	id   int
	name string
	age  int
}

// 预处理查询示例
func prepareQueryDemo(db *sql.DB) {
	sqlStr := "select id, name, age from user where id > ?"
	stmt, err := db.Prepare(sqlStr)
	if err != nil {
		fmt.Printf("prepare failed, err:%v\n", err)
		return
	}
	defer stmt.Close()
	rows, err := stmt.Query(0)
	if err != nil {
		fmt.Printf("query failed, err:%v\n", err)
		return
	}
	defer rows.Close()
	// 循环读取结果集中的数据
	for rows.Next() {
		var u user
		err := rows.Scan(&u.id, &u.name, &u.age)
		if err != nil {
			fmt.Printf("scan failed, err:%v\n", err)
			return
		}
		fmt.Printf("id:%d name:%s age:%d\n", u.id, u.name, u.age)
	}
}

// 预处理插入示例
// 插入、更新和删除操作的预处理十分类似
func prepareInsertDemo(db *sql.DB) {
	sqlStr := "insert into user(name, age) values (?,?)"
	stmt, err := db.Prepare(sqlStr)
	if err != nil {
		fmt.Printf("prepare failed, err:%v\n", err)
		return
	}
	defer stmt.Close()
	_, err = stmt.Exec("darren", 18)
	if err != nil {
		fmt.Printf("insert failed, err:%v\n", err)
		return
	}
	_, err = stmt.Exec("柚子老师", 18)
	if err != nil {
		fmt.Printf("insert failed, err:%v\n", err)
		return
	}
	fmt.Println("insert success.")
}

// 事务操作示例
func transactionDemo(db *sql.DB) {
	tx, err := db.Begin() // 开启事务
	if err != nil {
		if tx != nil {
			tx.Rollback() // 回滚
		}
		fmt.Printf("begin trans failed, err:%v\n", err)
		return
	}
	sqlStr1 := "Update user set age=30 where id=?"
	_, err = tx.Exec(sqlStr1, 2)
	if err != nil {
		tx.Rollback() // 回滚
		fmt.Printf("exec sql1 failed, err:%v\n", err)
		return
	}
	sqlStr2 := "Update user set age=40 where id=?"
	_, err = tx.Exec(sqlStr2, 4)
	if err != nil {
		tx.Rollback() // 回滚
		fmt.Printf("exec sql2 failed, err:%v\n", err)
		return
	}
	err = tx.Commit() // 提交事务
	if err != nil {
		tx.Rollback() // 回滚
		fmt.Printf("commit failed, err:%v\n", err)
		return
	}
	fmt.Println("exec trans success!")
}
func main() {
	db, err := sql.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/go_test?charset=utf8mb4")
	if db != nil {
		defer db.Close() // 健壮的写法
	}
	// fmt.Println("err:", err)
	err = db.Ping()
	if err != nil {
		fmt.Println("数据库链接失败", err)
		return
	}
	db.SetMaxOpenConns(10)
	db.SetMaxIdleConns(5)
	stats := db.Stats()
	fmt.Println("stats1:", stats)
	prepareInsertDemo(db)
	prepareQueryDemo(db)
	stats = db.Stats()
	fmt.Println("stats2:", stats)
}

7 sqlx使用

第三方库sqlx能够简化操作,提高开发效率。

安装

go get github.com/jmoiron/sqlx

7-mysql-sqlx.go

package main

import (
	"fmt"

	_ "github.com/go-sql-driver/mysql"
	"github.com/jmoiron/sqlx"
)

type user struct {
	ID   int    `json:"id" db:"id"`
	Name string `json:"name" db:"name"`
	Age  int    `json:"age" db:"age"`
}

var db *sqlx.DB

// 连接数据库
func initDB() (err error) {
	dsn := "root:123456@tcp(127.0.0.1:3306)/go_test?charset=utf8mb4"
	// 也可以使用MustConnect连接不成功就panic
	db, err = sqlx.Connect("mysql", dsn)
	if err != nil {
		fmt.Printf("connect DB failed, err:%v\n", err)
		return
	}
	db.SetMaxOpenConns(20)
	db.SetMaxIdleConns(10)
	return
}

// 查询单条数据
func queryRowDemo() {
	sqlStr := "select id, name, age from user where id=?"
	var u user
	err := db.Get(&u, sqlStr, 2) // 单条查询
	if err != nil {
		fmt.Printf("get failed, err:%v\n", err)
		return
	}
	fmt.Printf("id:%d name:%s age:%d\n", u.ID, u.Name, u.Age)
}

// 查询多行数据
func queryMultiRowDemo() {
	sqlStr := "select id, name, age from user where id > ?"
	var users []user
	err := db.Select(&users, sqlStr, 0) // 主要是查询
	if err != nil {
		fmt.Printf("query failed, err:%v\n", err)
		return
	}
	fmt.Printf("users:%#v\n", users)
}

// 插入数据
func insertRowDemo() {
	sqlStr := "insert into user(name, age) values (?,?)"
	ret, err := db.Exec(sqlStr, "隔壁老王", 18)
	if err != nil {
		fmt.Printf("insert failed, err:%v\n", err)
		return
	}
	theID, err := ret.LastInsertId() // 新插入数据的id
	if err != nil {
		fmt.Printf("get lastinsert ID failed, err:%v\n", err)
		return
	}
	fmt.Printf("insert success, the id is %d.\n", theID)
}

// 更新数据
func updateRowDemo() {
	sqlStr := "update user set age=? where id = ?"
	ret, err := db.Exec(sqlStr, 39, 6)
	if err != nil {
		fmt.Printf("update failed, err:%v\n", err)
		return
	}
	n, err := ret.RowsAffected() // 操作影响的行数
	if err != nil {
		fmt.Printf("get RowsAffected failed, err:%v\n", err)
		return
	}
	fmt.Printf("update success, affected rows:%d\n", n)
}

// 删除数据
func deleteRowDemo() {
	sqlStr := "delete from user where id = ?"
	ret, err := db.Exec(sqlStr, 6)
	if err != nil {
		fmt.Printf("delete failed, err:%v\n", err)
		return
	}
	n, err := ret.RowsAffected() // 操作影响的行数
	if err != nil {
		fmt.Printf("get RowsAffected failed, err:%v\n", err)
		return
	}
	fmt.Printf("delete success, affected rows:%d\n", n)
}

// 事务操作
func transactionDemo() {
	tx, err := db.Beginx() // 开启事务
	if err != nil {
		if tx != nil {
			tx.Rollback()
		}
		fmt.Printf("begin trans failed, err:%v\n", err)
		return
	}
	sqlStr1 := "Update user set age=40 where id=?"
	tx.MustExec(sqlStr1, 2)
	sqlStr2 := "Update user set age=50 where id=?"
	tx.MustExec(sqlStr2, 4)
	err = tx.Commit() // 提交事务
	if err != nil {
		tx.Rollback() // 回滚
		fmt.Printf("commit failed, err:%v\n", err)
		return
	}
	fmt.Println("exec trans success!")
}

// Get、QueryRowx: 查询一条数据
// QueryRowx可以指定到不同的数据类型中
func getNum() {
	var num int
	_ = db.Get(&num, "select count(*) from user")
	fmt.Printf("数据库一共有:%d 个用户\n", num)
	var u user
	_ = db.Get(&u, "select name, id, age from user where id = ?", 1)
	fmt.Printf("查找用户id==1的用户:%v \n", u)
}
func main() {
	err := initDB()
	if err != nil {
		fmt.Println("数据库链接失败", err)
		return
	}
	insertRowDemo()
	queryRowDemo()
	getNum()
	queryMultiRowDemo()
	// defer db.Close()
}

7-mysql-sqlx-2.go

package main

// 数据库连接初始化
import (
	"fmt"

	_ "github.com/go-sql-driver/mysql" // mysql
	"github.com/jmoiron/sqlx"
)

// DB 数据库模型
var DB *sqlx.DB

const dsn = "root:123456@tcp(127.0.0.1:3306)/go_test?charset=utf8mb4"

type user struct {
	ID   int    `json:"id" db:"id"`
	Name string `json:"name" db:"name"`
	Age  int    `json:"age" db:"age"`
}

// connect 1.连接数据库
func connect() (db *sqlx.DB, err error) {
	db, err = sqlx.Connect("mysql", dsn)
	db.SetMaxOpenConns(100) // 设置连接池最大连接数
	db.SetMaxIdleConns(20)  // 设置连接池最大空闲连接数
	DB = db
	if err != nil {
		fmt.Println("数据库连接失败==>", err)
	}
	fmt.Println("数据库已连接!")
	return
}

// 添加数据 Exec、MustExec
// MustExec遇到错误的时候直接抛出一个panic错误,程序就退出了;
// Exec是将错误和执行结果一起返回,由我们自己处理错误。 推荐使用!
func createUser() {
	// 创建表
	sql := `
        CREATE TABLE user  (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            name varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '',
            age int(11) NULL DEFAULT 0,
            PRIMARY KEY (id) USING BTREE
        ) ENGINE = InnoDB AUTO_INCREMENT = 20 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact
    `
	_, err := DB.Exec(sql)
	fmt.Println(err)
}

// 添加数据
func insertUser() {
	sql := `insert into user (name, age) values ("lgx",18)`
	res := DB.MustExec(sql)
	fmt.Println(res.LastInsertId)
	fmt.Println(res.RowsAffected)
}

// 更新数据
func updateUser() {
	sql := `update user set name = ?, age = ? where id = ?`
	res, err := DB.Exec(sql, "LGX", 28, 20)
	fmt.Println(err, res)
}

// Get、QueryRowx: 查询一条数据
// QueryRowx可以指定到不同的数据类型中
func getNum() {
	var num int
	_ = DB.Get(&num, "select count(*) from user")
	fmt.Printf("数据库一共有:%d 个用户\n", num)
	var u user
	_ = DB.Get(&u, "select name, id, age from user where id = ?", 2)
	fmt.Printf("查找用户id==1的用户:%v \n", u)
}

// Select、Queryx:查询多条数据
// Queryx可以指定到不同的数据类型中
func getAll() {
	sql := `select id, name ,age from user where id > 1`
	var us []user
	err := DB.Select(&us, sql)
	fmt.Println(err, us)
}

// 删除
func deleteUser() {
	sql := `delete from user where id = 20`
	_, _ = DB.Exec(sql)
}

// 事务处理
func events() {
	tx, _ := DB.Beginx()
	_, err1 := tx.Exec("update user set age = 10 where id = 20")
	_, err2 := tx.Exec("update user set age = 10 where id = 21")
	fmt.Println(err1, err2)
	if err1 != nil || err2 != nil {
		tx.Rollback()
	}
	tx.Commit()
}

func main() {
	db, _ := connect()
	defer db.Close()
	// 建表
	// createUser()
	// 添加数据
	insertUser()
	// 修改数据
	updateUser()
	// 查数据-Get
	getNum()
	// 查数据-Select
	getAll()
	// 事务
	// events()
}

8 gin + mysql restfull api

代码仓库

github.com/yunixiangfeng/devops/tree/main/gin_restful

 api\users.go

package api

import (
	"fmt"
	. "gin_restful/models"
	"net/http"
	"strconv"

	"github.com/gin-gonic/gin"
)

//index
func IndexUsers(c *gin.Context) {
	c.String(http.StatusOK, "It works")
}

//增加一条记录
func AddUsers(c *gin.Context) {
	name := c.Request.FormValue("name")
	telephone := c.Request.FormValue("telephone")
	fmt.Println("name:", name)
	fmt.Println("telephone:", telephone)
	if name == "" {
		msg := fmt.Sprintf("name字段错误")
		c.JSON(http.StatusBadRequest, gin.H{
			"msg": msg,
		})
		return
	}
	person := Person{
		Name:      name,
		Telephone: telephone,
	}
	id := person.Create()
	msg := fmt.Sprintf("insert 成功 %d", id)
	c.JSON(http.StatusOK, gin.H{
		"msg": msg,
	})
}

//获得一条记录
func GetOne(c *gin.Context) {
	ids := c.Param("id")
	id, _ := strconv.Atoi(ids)
	p := Person{
		Id: id,
	}
	rs, _ := p.GetRow()
	c.JSON(http.StatusOK, gin.H{
		"result": rs,
	})
}

//获得所有记录
func GetAll(c *gin.Context) {
	p := Person{}
	rs, _ := p.GetRows()
	c.JSON(http.StatusOK, gin.H{
		"list": rs,
	})
}

func UpdateUser(c *gin.Context) {
	ids := c.Request.FormValue("id")
	id, _ := strconv.Atoi(ids)
	telephone := c.Request.FormValue("telephone")
	person := Person{
		Id:        id,
		Telephone: telephone,
	}
	row := person.Update()
	msg := fmt.Sprintf("updated successful %d", row)
	c.JSON(http.StatusOK, gin.H{
		"msg": msg,
	})
}

//删除一条记录
func DelUser(c *gin.Context) {
	ids := c.Request.FormValue("id")
	id, _ := strconv.Atoi(ids)
	row := Delete(id)
	msg := fmt.Sprintf("delete successful %d", row)
	c.JSON(http.StatusOK, gin.H{
		"msg": msg,
	})
}

db\mysql.go

package db

import (
	"database/sql"
	"log"

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

var SqlDB *sql.DB

func init() {
	var err error
	SqlDB, err = sql.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/go_test?charset=utf8mb4")
	if err != nil {
		log.Fatal(err.Error())
	}
	err = SqlDB.Ping()
	if err != nil {
		log.Fatal(err.Error())
	}
	SqlDB.SetMaxIdleConns(20)
	SqlDB.SetMaxOpenConns(20)
}

models\users.go

package models

import (
	"gin_restful/db"
	"log"
)

type Person struct {
    Id int `json:"id" form:"id"`
    Name string `json:"name" form:"name"`
    Telephone string `json:"telephone" form:"telephone"`
}

//插入
func (person *Person) Create() int64 {
    rs, err := db.SqlDB.Exec("INSERT into users (name, telephone) value (?,?)", person.Name, person.Telephone)
    if err != nil{
        log.Fatal(err)
    }
    id, err := rs.LastInsertId()
    if err != nil{
        log.Fatal(err)
    }
    return id
}

//查询一条记录
func (p *Person) GetRow() (person Person, err error)  {
    person = Person{}
    err = db.SqlDB.QueryRow("select id,name,telephone from users where id = ?", p.Id).Scan(&person.Id, &person.Name, &person.Telephone)
    return
}

//查询所有记录
func (person *Person) GetRows() (persons []Person, err error) {
    rows, err := db.SqlDB.Query("select id,name,telephone from users")
    for rows.Next(){
        person := Person{}
        err := rows.Scan(&person.Id, &person.Name, &person.Telephone)
        if err != nil {
            log.Fatal(err)
        }
        persons = append(persons, person)
    }
    rows.Close()
    return
}

//修改
func (person *Person) Update() int64{
    rs, err := db.SqlDB.Exec("update users set telephone = ? where id = ?", person.Telephone, person.Id)
    if err != nil {
        log.Fatal(err)
    }
    rows, err := rs.RowsAffected()
    if err != nil {
        log.Fatal(err)
    }
    return  rows
}

//删除一条记录
func Delete(id int) int64  {
    rs, err := db.SqlDB.Exec("delete from users where id = ?", id)
    if err != nil {
        log.Fatal()
    }
    rows, err := rs.RowsAffected()
    if err != nil {
        log.Fatal()
    }
    return rows
}

main.go

package main

import "gin_restful/db"

// go mod init  xx_project
// go build
// ./xx_project
func main() {
	defer db.SqlDB.Close()
	router := initRouter()
	router.Run(":8806") // 启动服务了
}

router.go

package main

import (
	. "gin_restful/api"

	"github.com/gin-gonic/gin"
)

func initRouter() *gin.Engine {
	router := gin.Default()
	router.GET("/", IndexUsers) //http://192.168.204.132:8806

	//路由群组
	users := router.Group("api/v1/users")
	{
		users.GET("", GetAll)             //http://192.168.204.132:8806/api/v1/users
		users.POST("/add", AddUsers)      //http://192.168.204.132:8806/api/v1/users/add
		users.GET("/get/:id", GetOne)     //http://192.168.204.132:8806/api/v1/users/get/5
		users.POST("/update", UpdateUser) //http://192.168.204.132:8806/api/v1/users/update
		users.POST("/del", DelUser)       //http://192.168.204.132:8806/api/v1/users/del
	}

	departments := router.Group("api/v1/department")
	{
		departments.GET("", GetAll)             //http://192.168.204.132:8806/api/v1/users
		departments.POST("/add", AddUsers)      //http://192.168.204.132:8806/api/v1/users/add
		departments.GET("/get/:id", GetOne)     //http://192.168.204.132:8806/api/v1/users/get/5
		departments.POST("/update", UpdateUser) //http://192.168.204.132:8806/api/v1/users/update
		departments.POST("/del", DelUser)       //http://192.168.204.132:8806/api/v1/users/del
	}

	return router
}

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

package main

import (
	"fmt"
	"log"
	"net/http"
	"time"
	"wechat/wx"
)

const (
	logLevel = "dev"
	port     = 80
	token    = "NmHrEBBrbIX24JFw" // 生成地址:https://suijimimashengcheng.51240.com/
)

// 处理token的认证
func get(w http.ResponseWriter, r *http.Request) {

	client, err := wx.NewClient(r, w, token)

	if err != nil {
		log.Println(err)
		w.WriteHeader(403) // 校验失败
		return
	}

	if len(client.Query.Echostr) > 0 {
		w.Write([]byte(client.Query.Echostr)) // 校验成功返回的是Echostr
		return
	}

	w.WriteHeader(403)
	return
}

// 微信平台过来消息, 处理 ,然后返回微信平台
func post(w http.ResponseWriter, r *http.Request) {

	client, err := wx.NewClient(r, w, token)

	if err != nil {
		log.Println(err)
		w.WriteHeader(403)
		return
	}
	// 到这一步签名已经验证通过了
	client.Run()
	return
}

// 编译方法
// go mod init wechat
// go build
// ./wechat
// 需要自己修改token,以适应自己公众号的token
func main() {
	server := http.Server{
		Addr:           fmt.Sprintf(":%d", port), // 设置监听地址, ip:port
		Handler:        &httpHandler{},           // 用什么handler来处理
		ReadTimeout:    5 * time.Second,          // 读写超时 微信给出来5
		WriteTimeout:   5 * time.Second,
		MaxHeaderBytes: 0,
	}

	log.Println(fmt.Sprintf("Listen: %d", port))
	log.Fatal(server.ListenAndServe())
	defer CloseLog()
}

route.go

package main

import (
	"io"
	"net/http"
	"regexp"
	"time"
)

type WebController struct {
	Function func(http.ResponseWriter, *http.Request)
	Method   string
	Pattern  string
}

var mux []WebController // 自己定义的路由
// ^ 匹配输入字符串的开始位置
func init() {
	mux = append(mux, WebController{post, "POST", "^/"})
	mux = append(mux, WebController{get, "GET", "^/"})
}

type httpHandler struct{} // 实际是实现了Handler interface
// type Handler interface {
// 	ServeHTTP(ResponseWriter, *Request)
// }

func (*httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {

	t := time.Now()

	for _, webController := range mux { // 遍历路由
		// 匹配请求的   r.URL.Path  -> webController.Pattern
		if m, _ := regexp.MatchString(webController.Pattern, r.URL.Path); m { // 匹配URL

			if r.Method == webController.Method { // 匹配方法

				webController.Function(w, r) // 调用对应的处理函数

				go writeLog(r, t, "match", webController.Pattern)

				return
			}
		}
	}

	go writeLog(r, t, "unmatch", "")

	io.WriteString(w, "")
	return
}

log.go

package main

import (
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"time"
)

var LogFile *os.File

func init() {
	// fmt.Println("log init")
	// LogFile, err := os.OpenFile("wechat.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) //打开日志文件,不存在则创建
	// if err != nil {
	// 	fmt.Println(err)
	// }
	// log.SetOutput(LogFile)
	log.SetFlags(log.LstdFlags | log.Lshortfile)
}

func CloseLog() {
	if LogFile != nil {
		LogFile.Close()
	}

}

func writeLog(r *http.Request, t time.Time, match string, pattern string) {

	if logLevel != "prod" {

		d := time.Now().Sub(t)

		l := fmt.Sprintf("[ACCESS] | % -10s | % -40s | % -16s | % -10s | % -40s |", r.Method, r.URL.Path, d.String(), match, pattern)

		log.Println(l)
	}
}

func func_log2fileAndStdout() {
	//创建日志文件
	f, err := os.OpenFile("test.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
	if err != nil {
		log.Fatal(err)
	}
	//完成后,延迟关闭
	defer f.Close()
	// 设置日志输出到文件
	// 定义多个写入器
	writers := []io.Writer{
		f,
		os.Stdout}
	fileAndStdoutWriter := io.MultiWriter(writers...)
	// 创建新的log对象
	logger := log.New(fileAndStdoutWriter, "", log.Ldate|log.Ltime|log.Lshortfile)
	// 使用新的log对象,写入日志内容
	logger.Println("--> logger :  check to make sure it works")
}

 LICENSE

                    GNU GENERAL PUBLIC LICENSE
                       Version 3, 29 June 2007

 Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

                            Preamble

  The GNU General Public License is a free, copyleft license for
software and other kinds of works.

wx\structs.go

package wx

import (
	"encoding/xml"
	"strconv"
	"time"
)

type Base struct {
	FromUserName CDATAText
	ToUserName   CDATAText
	MsgType      CDATAText
	CreateTime   CDATAText
}

func (b *Base) InitBaseData(w *WeixinClient, msgtype string) {

	b.FromUserName = value2CDATA(w.Message["ToUserName"].(string))
	b.ToUserName = value2CDATA(w.Message["FromUserName"].(string))
	b.CreateTime = value2CDATA(strconv.FormatInt(time.Now().Unix(), 10))
	b.MsgType = value2CDATA(msgtype)
}

type CDATAText struct {
	Text string `xml:",innerxml"`
}

type TextMessage struct {
	XMLName xml.Name `xml:"xml"`
	Base
	Content CDATAText
}

wx\utils.go

package wx

func value2CDATA(v string) CDATAText {
	return CDATAText{"<![CDATA[" + v + "]]>"}
}

wx\wx.go

package wx

import (
	"crypto/sha1"
	"encoding/xml"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"sort"

	"github.com/clbanning/mxj"
)

type weixinQuery struct {
	Signature    string `json:"signature"`
	Timestamp    string `json:"timestamp"`
	Nonce        string `json:"nonce"`
	EncryptType  string `json:"encrypt_type"`
	MsgSignature string `json:"msg_signature"`
	Echostr      string `json:"echostr"`
}

type WeixinClient struct {
	Token          string
	Query          weixinQuery // 请求的一些参数
	Message        map[string]interface{}
	Request        *http.Request
	ResponseWriter http.ResponseWriter
	Methods        map[string]func() bool
}

/// 请求数据Request, 返回数据ResponseWriter, token是自己的
func NewClient(r *http.Request, w http.ResponseWriter, token string) (*WeixinClient, error) {

	weixinClient := new(WeixinClient)

	weixinClient.Token = token // 获取本地的token
	weixinClient.Request = r
	weixinClient.ResponseWriter = w

	weixinClient.initWeixinQuery()
	log.Println("Signature:", weixinClient.Query.Signature)
	if weixinClient.Query.Signature != weixinClient.hashcode() { // 签名认证
		return nil, errors.New("Invalid Signature.")
	}

	return weixinClient, nil
}

func (this *WeixinClient) initWeixinQuery() {

	var q weixinQuery
	log.Println("URL:", this.Request.URL.Path, ", RawQuery:", this.Request.URL.RawPath)
	q.Nonce = this.Request.URL.Query().Get("nonce")
	q.Echostr = this.Request.URL.Query().Get("echostr")
	q.Signature = this.Request.URL.Query().Get("signature")
	q.Timestamp = this.Request.URL.Query().Get("timestamp")
	q.EncryptType = this.Request.URL.Query().Get("encrypt_type")
	q.MsgSignature = this.Request.URL.Query().Get("msg_signature")

	this.Query = q
}

// 根据 Token Timestamp Nonce 生成对应的校验码, Token是不能明文传输的
func (this *WeixinClient) hashcode() string {

	strs := sort.StringSlice{this.Token, this.Query.Timestamp, this.Query.Nonce} // 使用本地的token生成校验
	sort.Strings(strs)
	str := ""
	for _, s := range strs {
		str += s
	}
	h := sha1.New()
	h.Write([]byte(str))
	return fmt.Sprintf("%x", h.Sum(nil))
}

// 读取消息,解析XML
func (this *WeixinClient) initMessage() error {

	body, err := ioutil.ReadAll(this.Request.Body)

	if err != nil {
		return err
	}

	m, err := mxj.NewMapXml(body)

	if err != nil {
		return err
	}

	if _, ok := m["xml"]; !ok {
		return errors.New("Invalid Message.")
	}

	message, ok := m["xml"].(map[string]interface{})

	if !ok {
		return errors.New("Invalid Field `xml` Type.")
	}

	this.Message = message // 保存消息

	log.Println(this.Message)

	return nil
}

func (this *WeixinClient) text() {

	inMsg, ok := this.Message["Content"].(string) // 读取内容

	if !ok {
		return
	}

	var reply TextMessage

	reply.InitBaseData(this, "text")
	reply.Content = value2CDATA(fmt.Sprintf("我收到的是:%s", inMsg)) // 把消息再次封装

	replyXml, err := xml.Marshal(reply) // 序列化

	if err != nil {
		log.Println(err)
		this.ResponseWriter.WriteHeader(403)
		return
	}

	this.ResponseWriter.Header().Set("Content-Type", "text/xml") // 数据类型text/xml
	this.ResponseWriter.Write(replyXml)                          // 回复微信平台
}

func (this *WeixinClient) Run() {

	err := this.initMessage()

	if err != nil {

		log.Println(err)
		this.ResponseWriter.WriteHeader(403)
		return
	}

	MsgType, ok := this.Message["MsgType"].(string)

	if !ok {
		this.ResponseWriter.WriteHeader(403)
		return
	}

	switch MsgType {
	case "text":
		this.text() // 处理文本消息
		break
	default:
		break
	}

	return
}

.github\FUNDING.yml

# These are supported funding model platforms
# leeeboo
github: [wtlyy]

3 token机制

解析请求中的GET参数

微信公众号签名验证的方法

参考:

https://developers.weixin.qq.com/doc/offiaccount/Getting_Started/Getting_Started_Guide.html

源码:3-1-token.go

package main

import (
	"bytes"
	"crypto/rand"
	"crypto/sha1"
	"fmt"
	"math/big"
	"sort"
	"strconv"
	"time"
)

func CreateRandomString(len int) string {
	var container string
	var str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
	b := bytes.NewBufferString(str)
	length := b.Len()
	bigInt := big.NewInt(int64(length))
	for i := 0; i < len; i++ {
		randomInt, _ := rand.Int(rand.Reader, bigInt)
		container += string(str[randomInt.Int64()])
	}
	return container
}

// 根据 Token Timestamp Nonce 生成对应的校验码, Token是不能明文传输的
func GenerateSignature(token string) (timestamp string, nonce string, signature string) {

	nonce = CreateRandomString(10)
	timestamp = strconv.FormatInt(time.Now().Unix(), 10) //int64转字符串
	// 排序 微信约定好的
	strs := sort.StringSlice{token, timestamp, nonce} // 使用本地的token生成校验
	sort.Strings(strs)                                // strs: [1607173019 qing qvCyrKEuoS]
	fmt.Println("strs:", strs)                        // 排序
	str := ""
	for _, s := range strs {
		str += s // 拼接字符串
	}
	fmt.Println("str:", str) //str: 1607173019qingqvCyrKEuoS
	h := sha1.New()		// 完全都是自己的服务的时候 你这里你用md5
	h.Write([]byte(str))                      // 转成byte
	signature = fmt.Sprintf("%x", h.Sum(nil)) // h.Sum(nil) 做hash  79efadd80a344c0b73b3bd2c403184f7425a5a67
	return
}

func VerifySignature(token string, timestamp string, nonce string, signature string) bool {
	// str = token + timestamp + nonce
	strs := sort.StringSlice{token, timestamp, nonce} // 使用本地的token生成校验
	sort.Strings(strs)
	str := ""
	for _, s := range strs {
		str += s
	}
	h := sha1.New()	// 完全都是自己的服务的时候 你这里你用md5
	h.Write([]byte(str))
	return fmt.Sprintf("%x", h.Sum(nil)) == signature
}
func main() {
	token := "qing"
	// 产生签名
	timestamp, nonce, signature := GenerateSignature(token) // 发送服务器的时候是发送  timestamp, nonce, signature
	fmt.Printf("1. token %s -> 产生签名:%s, timestamp:%s, nonce:%s\n", token, signature, timestamp, nonce)
	// 验证签名
	ok := VerifySignature(token, timestamp, nonce, signature) // 服务进行校验
	if ok {
		fmt.Println("2. 验证签名正常")
	} else {
		fmt.Println("2. 验证签名失败")
	}
}

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

package main

import (
	"encoding/xml"
	"fmt"
	"io/ioutil"
	"os"
)

// 如果struct中有一个叫做XMLName,且类型为xml.Name字段,
// 那么在解析的时候就会保存这个element的名字到该字段, 比如这里的config
type SConfig struct {
	XMLName      xml.Name   `xml:"config"`     // 指定最外层的标签为config
	SmtpServer   string     `xml:"smtpServer"` // 读取smtpServer配置项,并将结果保存到SmtpServer变量中
	SmtpPort     int        `xml:"smtpPort"`
	Sender       string     `xml:"sender"`
	SenderPasswd string     `xml:"senderPasswd"`
	Receivers    SReceivers `xml:"receivers"` // 读取receivers标签下的内容,以结构方式获取
}

type SReceivers struct {
	Age    int      `xml:"age"`
	Flag   string   `xml:"flag,attr"` // 读取flag属性
	User   []string `xml:"user"`      // 读取user数组
	Script string   `xml:"script"`    // 读取 <![CDATA[ xxx ]]> 数据
}

func main() {
	file, err := os.Open("4-1-xml.xml") // For read access.
	if err != nil {
		fmt.Printf("error: %v", err)
		return
	}
	defer file.Close()
	data, err := ioutil.ReadAll(file)
	if err != nil {
		fmt.Printf("error: %v", err)
		return
	}
	v := SConfig{}
	err = xml.Unmarshal(data, &v) // 反序列化
	if err != nil {
		fmt.Printf("error: %v", err)
		return
	}

	fmt.Println("文本:", v)
	fmt.Println("解析结果:")
	fmt.Println("XMLName : ", v.XMLName)
	fmt.Println("SmtpServer : ", v.SmtpServer)
	fmt.Println("SmtpPort : ", v.SmtpPort)
	fmt.Println("Sender : ", v.Sender)
	fmt.Println("SenderPasswd : ", v.SenderPasswd)
	fmt.Println("Receivers.Flag : ", v.Receivers.Flag)
	for i, element := range v.Receivers.User {
		fmt.Println(i, element)
	}
}

4-1-xml.xml 

<config>
   <smtpServer>smtp.qq.com</smtpServer>
   <smtpPort>25</smtpPort>
   <sender>you@qq.com</sender>
  <senderPasswd>123456</senderPasswd>
   <receivers flag="true">
     <user>ki@qq.gom</user>
     <user>dar@q.gom</user>
     <script>
     <![CDATA[
        function &%< matchwo(a,b) {
            if (a < b && a < 0) then {
                return 1;
            } else {
                return 0;
            }
        }
        ]]>
     </script>
  </receivers>
 </config>

4.2 XML解析-解析CDATA

XML 文档中的所有文本均会被解析器解析。

只有 CDATA 区段中的文本会被解析器忽略。

术语 CDATA 是不应该由 XML 解析器解析的文本数据。

像 "<" 和 "&" 字符在 XML 元素中都是非法的。

"<" 会产生错误,因为解析器会把该字符解释为新元素的开始。

"&" 会产生错误,因为解析器会把该字符解释为字符实体的开始。

某些文本,比如 JavaScript 代码,包含大量 "<" 或 "&" 字符。为了避免错误,可以将脚本代码定义为 CDATA。

CDATA 部分中的所有内容都会被解析器忽略。

CDATA 部分由 “ <![CDATA[ " 开始,由 "]]>" 结束:

4-2-CDATA.go

package main

import (
	"encoding/xml"
	"fmt"
	"strconv"
	"time"

	"github.com/clbanning/mxj"
)

// tag中含有"-"的字段不会输出
// tag中含有"name,attr",会以name作为属性名,字段值作为值输出为这个XML元素的属性,如上version字段所描述
// tag中含有",attr",会以这个struct的字段名作为属性名输出为XML元素的属性,类似上一条,只是这个name默认是字段名了。
// tag中含有",chardata",输出为xml的 character data而非element。
// tag中含有",innerxml",将会被原样输出,而不会进行常规的编码过程
// tag中含有",comment",将被当作xml注释来输出,而不会进行常规的编码过程,字段值中不能含有"--"字符串
// tag中含有"omitempty",如果该字段的值为空值那么该字段就不会被输出到XML,空值包括:false、0、nil指针或nil接口,任何长度为0的array, slice, map或者string

type CDATAText struct {
	Text string `xml:",innerxml"`
}

type Base struct {
	FromUserName CDATAText
	ToUserName   CDATAText
	MsgType      CDATAText
	CreateTime   CDATAText
}

// 文本消息的封装
type TextMessage struct {
	XMLName xml.Name `xml:"xml"`
	Base
	Content CDATAText
}

// 图片消息的封装
type PictureMessage struct {
	XMLName xml.Name `xml:"xml"`
	Base
	PicUrl  CDATAText
	MediaId CDATAText
}

func value2CDATA(v string) CDATAText {
	return CDATAText{"<![CDATA[" + v + "]]>"}
}

func main() {
	// 1. 解析 XML
	xmlStr := `<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>`

	var Message map[string]interface{}
	m, err := mxj.NewMapXml([]byte(xmlStr)) //使用了第三方的库

	if err != nil {
		return
	}

	if _, ok := m["xml"]; !ok {
		fmt.Println("Invalid Message.")
		return
	}
	fmt.Println("-->m:", m)
	message, ok := m["xml"].(map[string]interface{}) // 把xml对应的值读取出来

	if !ok {
		fmt.Println("Invalid Field `xml` Type.")
		return
	}

	Message = message

	fmt.Println("1. 解析出来:", Message) // xml对应的字段还是在map

	// 2. 封装XML
	var reply TextMessage
	inMsg, ok := Message["Content"].(string) // 读取内容 .(string)转成什么类型的数据

	if !ok {
		return
	}
	fmt.Println("Message[ToUserName].(string):", Message["ToUserName"].(string)) // 如果服务器要处理

	// 封装回复消息,需要添加 CDATA
	reply.Base.FromUserName = value2CDATA(Message["ToUserName"].(string))
	reply.Base.ToUserName = value2CDATA(Message["FromUserName"].(string))
	reply.Base.CreateTime = value2CDATA(strconv.FormatInt(time.Now().Unix(), 10))
	reply.Base.MsgType = value2CDATA("text")
	reply.Content = value2CDATA(fmt.Sprintf("我收到的是:%s", inMsg))

	replyXml, err := xml.Marshal(reply)                // 得到的是byte
	fmt.Println("2. 生成XML:", string(replyXml))         // []byte -> string
	fmt.Println("2. 生成XML:", []byte(string(replyXml))) // string -> []byte
}

<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 初始化数据库,以及后端服务启动

O.S 前端UI交互端启动说明

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/go-admin

go-admin后台设计-添加应⽤实战

L 主要内容

N 新增模块

O 编写 go-admin 应⽤,第 N 步 ⼿动写代码

开始项⽬

⽤于开发的服务器

创建⽂章功能

编写第⼀个接⼝

path

P 编写 go-admin 应⽤,第 O 步 ⾃动⽣成代码

数据库配置

代码⽣成

表结构导⼊

编辑模板字段

预览代码

⽣成代码

配置系统菜单

配置⻆⾊权限

操作内容管理

Go语⾔资源汇总

开篇

Go语⾔该学什么

⽹站

开源项⽬

gin

gim

beego

cobra

pholcus

nsq

codis

delve

micro/micro

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/53660.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

码力全开!请查收HDC.Together 2023亮点日程

<主题演讲> <技术交流与互动> <妙趣之旅> 点击关注阅读原文&#xff0c;了解更多资讯

MacOS本地安装Hadoop3

金翅大鹏盖世英&#xff0c;展翅金鹏盖世雄。 穿云燕子锡今鸽&#xff0c;踏雪无痕花云平。 ---------------- 本文密钥&#xff1a;338 ----------------- 本文描述了在macbook pro的macos上安装hadoop3的过程&#xff0c;也可以作为在任何类linux平台上安装hadoop3借鉴。 …

奥迪A3:最新款奥迪A3内饰设计及智能科技应用

奥迪A3一直以来都是奥迪的入门级车型&#xff0c;但这并不意味着它在科技和内饰方面会有所退步。最新款奥迪A3的内饰设计和智能科技应用让人们再次惊叹奥迪的创新能力。 内饰设计 奥迪A3最新款的内饰设计引入了奥迪最新的设计元素&#xff0c;比如8.8英寸的中控显示屏&#xf…

AD21 PCB设计的高级应用(四)FPGA的管脚交换功能

&#xff08;四&#xff09;FPGA的管脚交换功能 高速 PCB 设计过程中,涉及的 FPGA等可编程器件管脚繁多,也因此导致布线的烦琐与困难&#xff0c;Altium Designer 可实现 PCB 中 FPGA 的管脚交换&#xff0c;方便走线。 1.FPGA管脚交换的要求 (1)一般情况下,相同电压的 Bank之…

IT服务管理学习笔记<一>

### IT服务管理知识整理 ITSM 的核心思想是&#xff0c;IT 组织&#xff0c;不管它是企业内部的还是外部的&#xff0c;都是 IT 服务提供者&#xff0c;其 主要工作就是提供低成本、高质量的 IT 服务。 ITSM 的核心思想是&#xff0c;IT 组织&#xff0c;不管它是企业内部的还…

子域名收集工具OneForAll的安装与使用-Win

子域名收集工具OneForAll的安装与使用-Win OneForAll是一款功能强大的子域名收集工具 GitHub地址&#xff1a;https://github.com/shmilylty/OneForAll Gitee地址&#xff1a;https://gitee.com/shmilylty/OneForAll 安装 1、python环境准备 OneForAll基于Python 3.6.0开发和…

适配器模式——不兼容结构的协调

1、简介 1.1、概述 有的笔记本电脑的工作电压是20V&#xff0c;而我国的家庭用电是220V&#xff0c;如何让20V的笔记本电脑能够在220V的电压下工作&#xff1f;答案是引入一个电源适配器&#xff08;AC Adapter&#xff09;&#xff0c;俗称充电器&#xff0f;变压器。有了这…

[PAT乙级] 1029 旧键盘 C++实现

题目描述&#xff1a; 旧键盘上坏了几个键&#xff0c;于是在敲一段文字的时候&#xff0c;对应的字符就不会出现。现在给出应该输入的一段文字、以及实际被输入的文字&#xff0c;请你列出肯定坏掉的那些键。 输入格式&#xff1a; 输入在 2 行中分别给出应该输入的文字、以…

Java生成二维码——附Utils工具类

参加2023年的计算机设计大赛国赛&#xff0c;拿到了一等奖。 现在将项目中的工具类代码剥离出来&#xff0c;方便之后项目开发中复用。 实现效果&#xff1a; 代码实现&#xff1a; import com.google.zxing.BarcodeFormat; import com.google.zxing.EncodeHintType; import c…

《MySQL 实战 45 讲》课程学习笔记(二)

日志系统&#xff1a;一条 SQL 更新语句是如何执行的&#xff1f; 与查询流程不一样的是&#xff0c;更新流程还涉及两个重要的日志模块&#xff1a;redo log&#xff08;重做日志&#xff09;和 binlog&#xff08;归档日志&#xff09;。 重要的日志模块&#xff1a;redo l…

怎么通过通过 p 名称空间配置 bean以及怎么去引用/注入其它 bean 对象--ref和怎么去引用/注入内部 bean 对象-内部 bean 对象

&#x1f600;前言 本章是spring基于XML 配置bean系类中第2篇讲解怎么通过通过 p 名称空间配置 bean以及怎么去引用/注入其它 bean 对象–ref和怎么去引用/注入内部 bean 对象 &#x1f3e0;个人主页&#xff1a;尘觉主页 &#x1f9d1;个人简介&#xff1a;大家好&#xff0…

每日一题——只出现一次的数字(II)

只出现一次的数字——II 题目链接 注&#xff1a;本题的解法建立在位运算之上&#xff0c;如果对位运算不太了解&#xff0c;建议先看看&#x1f449;位运算详解 思路 可能有小伙伴做了只出现一次的数字——I后认为这题也可以用异或运算来解决&#xff0c;但是我们需要注意到…

力扣468 验证IP地址

ipv4地址&#xff1a;1.必须是四个非空子串 2.每个非空子串不含前导零 3.子串里字符只能是0~255 ipv6地址&#xff1a;1.必须是八个非空子串 2。每段非空串得长度是否在1~4之间&#xff0c;且不含0-9&#xff0c;a-f&#xff0c;A-F之外得字符。 3.同时0-9也不允许含前导零 cl…

543. 二叉树的直径

题目 题解一 遍历每一个节点&#xff0c;以每一个节点为中心点计算最长路径&#xff08;左子树边长右子树边长&#xff09;&#xff0c;更新全局变量max。 class Solution {int maxd0;public int diameterOfBinaryTree(TreeNode root) {depth(root);return maxd;}public int …

C# 翻转二叉树

226 翻转二叉树 给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 示例 1&#xff1a; 输入&#xff1a;root [4,2,7,1,3,6,9] 输出&#xff1a;[4,7,2,9,6,3,1] 示例 2&#xff1a; 输入&#xff1a;root [2,1,3] 输出&#xff1a;…

打造交流利器:PHP留言板功能详解与实践

&#x1f3c6;作者简介&#xff0c;黑夜开发者&#xff0c;全栈领域新星创作者✌&#xff0c;2023年6月csdn上海赛道top4。多年电商行业从业经验&#xff0c;对系统架构&#xff0c;数据分析处理等大规模应用场景有丰富经验。 &#x1f3c6;本文已收录于PHP专栏&#xff1a;PHP…

Linux操作系统学习,Linux基础命令大全

目录 第一章、Linux简介和安装1.1&#xff09;Linux简介和分类1.2&#xff09;安装VMware虚拟机&#xff0c;在虚拟机中安装CentOS 7 第二章、虚拟机中Linux的IP地址配置详解2.1&#xff09;什么是IP地址&#xff0c;如何查看2.2&#xff09;虚拟机NAT模式中Linux的IP地址设置有…

MyBatis缓存-提高检索效率的利器--二级缓存

文章目录 缓存-提高检索效率的利器缓存-官方文档二级缓存基本介绍二级缓存原理图 二级缓存快速入门快速入门注意事项和使用陷阱理解二级缓存策略的参数 四大策略如何禁用二级缓存mybatis 刷新二级缓存的设置 缓存-提高检索效率的利器 缓存-官方文档 文档地址: https://mybati…

Java面试准备篇:全面了解面试流程与常见问题

文章目录 1.1 Java面试概述1.2 面试流程和注意事项1.3 自我介绍及项目介绍1.4 常见面试问题 在现代职场中&#xff0c;面试是求职过程中至关重要的一环&#xff0c;特别是对于Java开发者而言。为了帮助广大Java开发者更好地应对面试&#xff0c;本文将提供一份全面的Java面试准…

【Golang 接口自动化02】使用标准库net/http发送Post请求

目录 写在前面 发送Post请求 示例代码 源码分析 Post请求参数解析 响应数据解析 验证 发送Json/XMl Json请求示例代码 xml请求示例代码 总结 资料获取方法 写在前面 上一篇我们介绍了使用 net/http 发送get请求&#xff0c;因为考虑到篇幅问题&#xff0c;把Post单…