广州牌手表网站,重庆新闻发布会,程序员帮忙做放贷网站,政务咨询投诉举报网站建设1.1 GO语言基础 1 初识Go语言 1.1.1 开发环境搭建 参考文档#xff1a;《Windows Go语言环境搭建》 1.2.1 Go语言特性-垃圾回收 a. 内存自动回收#xff0c;再也不需要开发人员管理内存 b. 开发人员专注业务实现#xff0c;降低了心智负担 c. 只需要new分配内存#xff0c;…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. 基于CSPCommunicating Sequential Process模型实现 package mainimport(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 - 1pipe - 2} package mainimport fmtfunc test_pipe() {pipe : make(chan int, 3)pipe - 1pipe - 2pipe - 3var t1 intt1 -pipefmt.Println(t1: , t1)}func sum(s []int, c chan int) {test_pipe()sum : 0for _, 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) // 728 17, -9 40 -5go sum(s[len(s)/2:], c)// x, y : -c, -c // receive from cx : -cy : -cfmt.Println(x, y, xy)
}1.2.4 Go语言特性-多返回值 一个函数返回多个值 func calc(a int, b int) (int, int) {sum : abavg : (ab)/2return 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. ab 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 只能修饰booleannumberint相关类型、浮点类型、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 mainimport (fmttime
)const (Man 1Female 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 mainimport (fmtos
)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等都是引用类型。 练习写一个程序交换两个整数的值。比如 a3; b4; 交换之后a4;b3 代码2-6-swap.go package mainimport fmtfunc swap(a *int, b *int) {tmp : *a*a *b*b tmpreturn
}func swap1(a int, b int) (int, int) {return b, a
}func test() {var a 100fmt.Println(a)//var b intfor i : 0; i 100; i {var b i * 2fmt.Println(b)}//fmt.Println(c)//fmt.Println(b)
}func test2() {var a int8 100var b int16 int16(a)fmt.Printf(a%d b%d\n, a, b)
}func main() {first : 100second : 200//swap(first, second)//first, second swap1(first, second)first, second second, firstfmt.Println(first, first)fmt.Println(second, second)test()test2()
}练习写一个程序用来打印值类型和引用类型变量到终端并观察输出结果。 代码 2-6-value_quote.go package mainimport (fmt
)func modify(a int) {a 10return
}func modify1(a *int) {*a 10
}func main() {a : 5b : 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 int8; var b int32int32(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 mainimport (fmtmath/randtime
)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 mainimport (fmtstringsunsafe_ 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] ifmt.Println(*str)
}
func test3() {var data [10]bytedata[0] Tdata[1] Evar 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 mainimport fmtfunc main() {var str hello world\n\nvar str2 hello \n \n \nthis is a test stringThis is a test string too·fmt.Println(str, str)fmt.Println(str2, str2)
} package mainimport (fmt
)func test_switch1() {a : 2switch a {case 1:fmt.Println(a1)case 2:fmt.Println(a2)case 3:fmt.Println(a3)case 4:fmt.Println(a4)default:fmt.Println(default)}
}func test_switch2() {a : 2switch a {case 1:fmt.Println(a1)case 2:fmt.Println(a2)fallthroughcase 3:fmt.Println(a3)case 4:fmt.Println(a4)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 mainimport fmtfunc modify(p *int) {fmt.Println(p)*p 1000900return
}func main() {var a int 10fmt.Println(a)var p *intp afmt.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 100fmt.Println(a)var b int 999p b*p 5fmt.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 mainimport (fmtreflect
)func add(a, b int) int {return a b
}
func main() {c : addfmt.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 mainimport (fmt
)type add_func func(int, int) intfunc 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 : addfmt.Println(c)sum : operator(c, 100, 200)fmt.Println(sum)
}3 函数3 3. 函数参数传递方式 1). 值传递 2). 引用传递 注意1无论是值传递还是引用传递传递给函数的都是变量的副本不过值 传递是值的拷贝。引用传递是地址的拷贝一般来说地址 拷贝更为高效。而值拷贝取决于拷贝的对象大小对象越大则性能越低。 3 函数3 3. 函数参数传递方式 1). 值传递 2). 引用传递 注意2map、slice、chan、指针、interface默认以引用的方式传递有待考证 go只有值传递、浅拷贝 package mainimport fmtfunc modify(a int) {a 100
}
func main() {a : 8fmt.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 mainimport fmtfunc add(a int, arg ...int) int {var sum int afor i : 0; i len(arg); i {sum arg[i]}return sum
}func concat(a string, arg ...string) (result string) {result afor 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 mainimport (fmtstrings
)// 缩小变量作用域减少对全局变量的污染。下面的累加如果用全局变量进行实现全局变量容易被其他人污染。
// 同时所有我要实现n个累加器那么每次需要n个全局变量。利用闭包
// 每个生成的累加器myAdder1, myAdder2 : adder(), adder()有自己独立的sumsum可以看作为myAdder1.sum与myAdder2.sum。
func Adder() func(int) int {var x intf : func(d int) int {x dreturn 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 mainimport fmtfunc test1() {var a [10]int//j : 10a[0] 100//a[j] 200fmt.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]intb : ab[0] 100fmt.Println(a)
}func main() {//test1()test2()var a [5]inttest3(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 []intarr[: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 mainimport fmttype slice struct {ptr *[100]intlen intcap int
}func make1(s slice, cap int) slice {s.ptr new([100]int)s.cap caps.len 0return s
}func modify(s slice) {s.ptr[1] 1000
}func testSlice2() {var s1 slices1 make1(s1, 10)s1.ptr[0] 100modify(s1)fmt.Println(s1.ptr)
}func testSlice() {var slice []intvar 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 mainimport fmtfunc 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] 100fmt.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] 1000fmt.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 worlds1 : s[0:5]s2 : s[6:]fmt.Println(s1)fmt.Println(s2)}func testModifyString() {s : 我hello worlds1 : []rune(s)s1[0] 200s1[1] 128s1[2] 64str : 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 inew(int) *i10 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 -runTest_Division_1 -count 5 3.查看帮助 go help test package mytestimport (testing
)func Test_Division_1(t *testing.T) {if i, e : Division(6, 2); i ! 3 || e ! nil { //try a unit test on functiont.Error(除法函数测试没通过) // 如果不是如预期的那么就报错} else {t.Log(第一个测试通过了) //记录一些你期望记录的信息}
}func Test_Division_2(t *testing.T) {if _, e : Division(6, 0); e nil { //try a unit test on functiont.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 那就是不做打点了。 你可以通过设置memprofilerate1和GOGCoff来关闭内存回收并且对每个内存块的分配进行观察。 -test.blockprofilerate n: 基本同上控制的是goroutine阻塞时候打点的纳秒数。默认不设置就相当于- test.blockprofilerate1每一纳秒都打点记录一下 -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 mainimport (fmtunsafe
)type Student struct {name stringage int32 // 小写 私密 只能在自己的包里面用Class string // 大写 公开 类似C public
}func main() {// 1 值形式var stu1 Student // 里面的变量全是零 栈上的fmt.Println(stu1:, stu1)stu1.age 34stu1.name darstu1.Class class1fmt.Println(stu1.name) //dar// 2 new 函数创建var stu2 *Student new(Student) // new出来的是堆上stu2.name kingstu2.age 33fmt.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 rosefmt.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 nilfmt.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 mainimport fmttype Student struct {name stringage intClass 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:nameAge 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 mainimport (encoding/jsonfmt
)// stu: 序列化后{name:dar,age:34}
// {name1:dar,age2:34}
type Student struct {Name string json:name1Age 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 Studenterr 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中的Agefmt.Println(stu.Person.Age, stu.Age) // 0,34
} // 1.4 struct匿名成员字段、属性)package mainimport fmttype Person struct {Name stringAge int
}
type Student struct {score stringAge intPerson // 匿名内嵌结构体
}func main() {var stu new(Student)stu.Age 22 //优先选择Student中的Agefmt.Println(stu.Person.Age, stu.Age) // 0,22var 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 stringjob Teacher //别名继承Teacher 这个时候就不是匿名了Person //继承Person}func main() {var man1 new(man)man1.Age 34man1.Name darman1.job.Salary 100000fmt.Println(man1, man1.job.Salary) //{ {8500 } {dar 34}} 8500} // 1.5 struct-继承、多继承
package mainimport fmttype Person struct {Name stringAge int
}
type Teacher struct {Salary intClass string
}type Man struct {sex stringjob Teacher //别名继承TeacherPerson //继承Person
}func main() {var man1 new(Man)man1.Age 34man1.Name darman1.job.Salary 100000fmt.Println(man1:, man1, man1.job.Salary) //{ {8500 } {dar 34}} 8500var man2 Man{sex: 女,job: Teacher{Salary: 8000,Class: 班班,},Person: Person{ // 匿名初始化方式Name: 老师,Age: 18,},}fmt.Println(man2, man2)
}1.6 struct-结构体中的方法 方法是什么 ◼ Go方法是作用在接受者个人理解成作用对象上的一个函数接受者是某种类型的变量因此方法是一种特殊 类型的函数。 ◼ 接收者可以是任何类型不仅仅是结构体Go中的基本类型intstringbool等也是可以或者说数组的别名 类型甚至可以是函数类型。但是接受者不能是一个接口类型因为接口是一个抽象的定义方法是一个具体 实现。 ◼ 一个类型加上它的方法等价于面向对象中的一个类。一个重要的区别是在 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 mainimport (fmtmath
)type Circle struct {x inty intRadius 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 cpc.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 mainimport (fmtreflect
)// type Student struct {
// Name string // 16
// Age int64 // 8
// wight int64 // 8
// high int64 // 8
// score int64 // 8
// }
type Student struct {Name string // 16 有两个变量 指针, lengthAge int8 //wight int64 // 8high int8 // 8score 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 ordern : typ.NumField()for i : 0; i n; i {field : typ.Field(i) // 反射出filedfmt.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 mainimport fmt//定义接口
type Skills interface {Running()Getname() string
}type Student struct {Name stringAge int
}type Teacher struct {Name stringAge 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 darstu1.Age 34skill stu1skill.Running() //调用接口var teacher Teacher Teacher{老师, 18}skill teacherskill.Running() //调用接口teacher.Running()
}// 空接口
package mainimport 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.5var 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 mainimport fmttype Skills interface {Running()Getname() string
}type Student struct {Name stringAge int
}type Teacher struct {Name stringSalary 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 Skillsvar stu1 Studentvar t1 Teachert1.Name kistu1.Name darstu1.Age 22skill stu1skill.Running()skill t1t1.Running()
} 2.5 interface接口嵌套 ◼ go语言中的接口可以嵌套可以理解为继承子接口拥有父接口的所有方法 ◼ 如果使用该子接口必须将父接口和子接口的所有方法都实现 type Skills interface {Running()Getname() string}type Test interface {sleeping()Skills //继承Skills} // 2.5 interface接口嵌套package mainimport fmttype Skills interface {Running()// Running(is int) // 函数名是唯一的Getname() string
}type Test interface {Sleeping()Skills //继承Skills
}
type Student struct {Name stringAge int
}type Teacher struct {skill Skills // skill也只能当变量去用Name stringSalary 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 Skillsvar stu1 Studentvar t1 Teachert1.Name kistu1.Name darstu1.Age 22skill stu1skill.Running()skill t1t1.Running()var test Testtest t1test.Sleeping()// test stu1
} 2.6 interface接口组合 ◼ 接口的定义也支持组合继承 // 2.6 接口的组合继承
package mainimport fmt// 可以闻
type Smellable interface {smell()
}// 可以吃
type Eatable interface {eat()
}type Fruitable interface {SmellableEatable
}// 苹果既可能闻又能吃
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 Smellablevar s2 Eatablevar apple Apple{}var flower Flower{}s1 apples1.smell()s1 flowers1.smell()s2 apples2.eat()fmt.Println(\n组合继承)var s3 Fruitables3 apples3.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 mainimport fmtfunc main() {var x interface{}s : darx 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 mainimport fmttype 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 StudentTestType(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 mainimport fmttype Rect struct {Width intHeight int
}func main() {var a interface{}var r Rect{50, 50}a r // 指向了结构体指针var rx a.(*Rect) // 转换成指针类型r.Width 100r.Height 100fmt.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 mainimport (fmtreflectstrings
)type Foo struct {A int tag1:First Tag tag2:Second TagB string
}func main() {sl : []int{1, 2, 3}greeting : hellogreetingPtr : greetingf : Foo{A: 10, B: Salutations}fp : fslType : 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, depth1), Contained type:)examiner(t.Elem(), depth1)case reflect.Struct:for i : 0; i t.NumField(); i {f : t.Field(i)fmt.Println(strings.Repeat(\t, depth1), Field, i1, name is, f.Name, type is, f.Type.Name(), and kind is, f.Type.Kind())if f.Tag ! {fmt.Println(strings.Repeat(\t, depth2), Tag is, f.Tag)fmt.Println(strings.Repeat(\t, depth2), 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 mainimport (fmtreflect
)type Foo struct {A int B string
}// 使用反射创建新实例
func main() {greeting : hellof : Foo{A: 10, B: Salutations}gVal : reflect.ValueOf(greeting)// not a pointer so all we can do is read itfmt.Println(gVal.Interface()) // hellogpVal : reflect.ValueOf(greeting)// it’s a pointer, so we can change it, and it changes the underlying variablegpVal.Elem().SetString(goodbye)fmt.Println(greeting) // 修改成了goodbyefType : 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 mainimport (fmtreflect
)func main() {// 定义变量intSlice : make([]int, 0)mapStringInt : make(map[string]int)// 获取变量的 reflect.TypesliceType : reflect.TypeOf(intSlice)mapType : reflect.TypeOf(mapStringInt)// 使用反射创建类型的新实例intSliceReflect : reflect.MakeSlice(sliceType, 0, 0)mapReflect : reflect.MakeMap(mapType)// 将创建的新实例分配回一个标准变量v : 10rv : reflect.ValueOf(v)intSliceReflect reflect.Append(intSliceReflect, rv)intSlice2 : intSliceReflect.Interface().([]int)fmt.Println(intSlice2: , intSlice2)fmt.Println(intSlice : , intSlice)k : hellork : 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 mainimport (fmtreflectruntimetime
)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 * 2fmt.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 mainimport (fmtreflect
)type Skills interface {reading()running()
}type Student struct {Age intName 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.Studentfmt.Println(stu_type.Name()) //Studentfmt.Println(stu_type.PkgPath()) //mainfmt.Println(stu_type.Kind()) //structfmt.Println(stu_type.Size()) //24fmt.Println(\n类型inf_type:, inf_type)fmt.Println(inf_type.NumMethod()) //2fmt.Println(inf_type.Method(0), inf_type.Method(0).Name) // {reading main func() invalid Value 0} readingfmt.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 mainimport (fmtreflect
)type Skills interface {reading()running()
}type Student struct {Name string json:nameAge 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()) //2fmt.Println(stu_type.Field(0)) //{Name string 0 [0] false}fmt.Println(stu_type.FieldByName(Name)) //{{Age int 16 [1] false} truefmt.Println(stu_type.Field(1)) //{Name string 0 [0] false}fmt.Println(stu_type.FieldByName(Age)) //{{Age int 16 [1] false} true
}package mainimport (fmtreflect
)func main() {str : darval : reflect.ValueOf(str).Kind()fmt.Println(val) //string
}3 reflect反射-利弊 反射的好处 1. 为了降低多写代码造成的bug率做更好的归约和抽象 2. 为了灵活、好用、方便做动态解析、调用和处理 3. 为了代码好看、易读、提高开发效率补足与动态语言之间的一些差别 反射的弊端 1. 与反射相关的代码经常是难以阅读的。在软件工程中代码可读性也是一个非常重要的指标。 2. Go 语言作为一门静态语言编码过程中编译器能提前发现一些类型错误但是对于反射代码是无能为力的。所以包含反射相关的代码很可能会运行很久才会出错这时候经常是直接 panic可能会造成严重的后果。 3. 反射对性能影响还是比较大的比正常代码运行速度慢一到两个数量级。所以对于一个项目中处于运行效率关键位置的代码尽量避免使用反射特性。 3.1 reflect反射-Type TypeType类型用来表示一个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 mainimport (fmtreflect
)func main() {str : darage : 11fmt.Println(reflect.ValueOf(str).String()) //获取str的值结果darfmt.Println(reflect.ValueOf(age).Int()) //获取age的值结果agestr2 : reflect.ValueOf(str) //获取Value类型str2.Elem().SetString(ki) //设置值fmt.Println(str2.Elem(), age) //ki 11age2 : 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 mainimport (fmtreflect
)type Skills interface {reading()running()
}type Student struct {Name stringAge 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()) //2fmt.Println(stu_val.Field(0), stu_val.Field(1)) //dar 18fmt.Println(stu_val.FieldByName(Age)) //18stu_val2 : reflect.ValueOf(stu1).Elem() // 要修改传引用或者指针stu_val2.FieldByName(Age).SetInt(33) //设置字段值 结果33fmt.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 mainimport (fmtreflect
)type Student struct {Name stringAge int
}func (this *Student) SetName(name string) {this.Name namefmt.Printf(set name %s\n, this.Name)
}func (this *Student) SetAge(age int) {this.Age agefmt.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 agefmt.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 183.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 mainimport (fmtreflect
)func test1() {var s int 42var 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 mainimport (fmtreflect
)type Rect struct {Width intHeight intName 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 mainimport (fmtreflecttime
)const N 1000000func test1() {var sum 0t0 : 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 0t0 : time.Now()for i : 0; i (N); i {var s int 42s 43sum s}elapsed : time.Since(t0)fmt.Println(直接赋值 , N:, N, time:, elapsed, , sum:, sum)
}
func main() {test1()test2()
}// 3 reflect反射 基础
package mainimport (fmt
)func main() {newValue : make(map[interface{}]interface{}, 0)newValue[11] darnewValue[age] 18fmt.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 mainimport fmtfunc main() {// m0 可以, key类型为string, 支持 比较操作{fmt.Println(---- m0 ----)var m0 map[string]string // 定义map类型变量m0key的类型为stringvalue的类型stringfmt.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(b1b2) // 报错 invalid operation: b1 b2 (slice can only be compared to nil)}// m2 可以, interface{}类型可以作为key但是需要加入的key的类型是可以比较的{fmt.Println(---- m2 ----)var m2 map[interface{}]stringm2 make(map[interface{}]string)//m2[[]byte(k2)]v2 // panic: runtime error: hash of unhashable type []uint8m2[123] 123m2[12.3] 123fmt.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]stringm3 make(map[[3]int]string)m3[a3] m3fmt.Println(m3)}// m4 可以book1里面的元素都是支持 !{fmt.Println(---- m4 ----)type book1 struct {name string}var m4 map[book1]stringfmt.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 mainimport fmtfunc 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存在oktrue,如果k不存在将value类型的零值赋值给v同时okfalse// 查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) // 删除存在的keyfmt.Printf(%#v %#v\n, m, len(m)) // map[string]string{b:vb, c:11} 2delete(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 mainimport fmtfunc 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 hellos[0] H
}// 切割切割
func cut() {var s1 hello worldvar s2 s1[3:8]fmt.Println(s2)
}// 字节切片和字符串的相互转换
func string2bytes() {var s1 hello worldvar 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 协程异常处理-recoverpackage mainimport (fmtruntime
)// 崩溃时需要传递的上下文信息
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 1fmt.Println(赋值宕机后)})fmt.Println(运行后)
}// 1.3 协程异常处理-recoverpackage mainimport (fmttime
)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 mainimport (fmtruntimetime
)const N 1000000func main() {fmt.Println(run in main goroutine)i : 1for {go func() {for {time.Sleep(time.Second)}}()if i%10000 0 {fmt.Printf(%d goroutine started\n, i)}iif i N {break}}fmt.Println(NumGoroutine:, runtime.NumGoroutine())time.Sleep(time.Second * 15)
}1-5 死循环 如果有个别协程死循环了会导致其它协程饥饿得到不运行么 package mainimport (fmtruntimesyscalltime
)
// 获取的是线程ID不是协程ID
func GetCurrentThreadId() int {var user32 *syscall.DLLvar GetCurrentThreadId *syscall.Procvar err erroruser32, 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 uintptrpid, _, 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 : 5for 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 0for {time.Sleep(time.Second)countfmt.Println(for goroutine running:, count, threadId:, GetCurrentThreadId())}}()fmt.Println(NumGoroutine: , runtime.NumGoroutine())var count 0for {time.Sleep(time.Second)countfmt.Println(main goroutine running:, count, threadId:, GetCurrentThreadId())}
}1.6 设置线程数 Go的调度器使用了一个叫做GOMAXPROCS的变量来决定会有多少个操作系统的线程同时执 行Go的代码。其默认的值是运行机器上的CPU的核心数所以在一个有8个核心的机器上 时调度器一次会在8个OS线程上去调度GO代码。 package mainimport (fmtruntime
)func main() {fmt.Println(runtime.NumCPU():, runtime.NumCPU())// 读取默认的线程数fmt.Println(runtime.GOMAXPROCS(0))// 设置线程数为 10runtime.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 mainimport fmtfunc send(ch chan int) {i : 0for {ich - i}
}func recv(ch chan int) {value : -chfmt.Println(value)value -chfmt.Println(value)close(ch)
}// 向一个已经关闭的通道执行写操作会抛出异常这意味着我们在写通道时一定要确保通道没有被关闭。
func main() {var ch make(chan int, 4)go recv(ch)send(ch)
}2.2 读写通道 Go 语言为通道的读写设计了特殊的箭头语法糖 -让我们使用通道时非常方便。把箭头写在 通道变量的右边就是写通道把箭头写在通道的左边就是读通道。一次只能读写一个元素 // 2.2 读写通道package mainimport (fmttime
)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 - 1ch1 - 1 // 这里已经阻塞fmt.Println(写入ch1) //这里没打印}()value1 : -ch1value1 -ch1time.Sleep(5 * time.Second)fmt.Println(退出, value1:, value1)
}通道作为容器它可以像切片一样使用 cap() 和 len() 全局函数获得通道的容量和当前内 部的元素个数。 2-3 读写阻塞 通道满了写操作就会阻塞协程就会进入休眠直到有其它协程读通道挪出了空间协程 才会被唤醒。如果有多个协程的写操作都阻塞了一个读操作只会唤醒一个协程。 // 2-3 读写阻塞
package mainimport (fmtmath/randtime
)func send(ch chan int) {for {var value rand.Intn(100)ch - valuefmt.Printf(send %d\n, value) // 这里没有延时}
}func recv(ch chan int) {for {value : -chfmt.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 mainimport fmtfunc main() {var ch make(chan int, 4)ch - 1ch - 2fmt.Println(len(ch):, len(ch), cap(ch):, cap(ch))close(ch)value : -chfmt.Println(value)value -chfmt.Println(value)value -chfmt.Println(value)value -chfmt.Println(value)ch - 3
}2-5 通道写安全 向一个已经关闭的通道执行写操作会抛出异常这意味着我们在写通道时一定要确保通道没 有被关闭 多人写入怎么办 // 2-5 通道写安全
package mainimport fmtfunc send(ch chan int) { // 在写入端关闭, 没有太多的通用性ch - 1ch - 2ch - 3ch - 4close(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 mainimport (fmtsynctime
)func send(ch chan int, wg *sync.WaitGroup) {defer wg.Done() // 计数值减一i : 0for i 4 {ich - 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 mainimport (fmttime
)// 每隔一会生产一个数
func send(ch chan int, gap time.Duration) {i : 0for {ich - itime.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 - vcase v : -ch2:target - vdefault: // 非阻塞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 mainimport (fmttime
)func send(ch chan int, gap time.Duration) {i : 0for {ich - itime.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 mainimport (fmttime
)func send(ch1 chan int, ch2 chan int) {i : 0for {iselect {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 mainimport (fmtosos/signalsyscalltime
)// 生产者
func Producer(factor int, out chan- int) {for i : 0; ; i {out - i * factortime.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 mainimport fmt
// go多协程 是有竞态不像以前的ntycolibco没有竞态的
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 mainimport (fmtsync
)type SafeDict struct {data map[string]intmutex *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] valuereturn 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 mainimport (fmtsync
)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] valuereturn 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 mainimport (fmtsync
)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] valuereturn 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 mainimport (fmtstringssynctime
)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] topicp.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.WaitGroupfor 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 mainimport (fmtsynctime
)var once sync.Oncefunc 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 会自动发起取消请求第二个返回值okfalse时表示没有设置截止时间如果需要取消的话需要调用取消函数进行取消 ◼ Done方法返回一个只读的chan类型为struct{}我们在goroutine中如果该方法返回的chan可以 读取则意味着parent context已经发起了取消请求我们通过Done方法收到这个信号后就应该 做清理操作然后退出goroutine释放资源 ◼ Err方法返回取消的错误原因因为什么Context被取消。 ◼ Value方法获取该Context上绑定的值是一个键值对所以要通过一个Key才可以获取对应的值这个值一般是线程安全的 package mainimport (contextfmttime
)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())returndefault: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 mainimport (contextfmttime
)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.WaitGroupgo func(ctx context.Context) {// wg.Add(1)// defer wg.Done()for {select {case -ctx.Done():fmt.Println(监控退出停止了..., err:, ctx.Err())returndefault: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 mainimport (contextfmtsynctime
)func work(ctx context.Context, wg *sync.WaitGroup) {defer wg.Done()for {select {case -ctx.Done():fmt.Println(监控退出停止了...)returndefault: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.WaitGroupfor 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 mainimport (contextfmttime
)var key string name
var key2 string name1func main() {ctx, cancel : context.WithCancel(context.Background())//附加值valueCtx : context.WithValue(ctx, key, key【监控1】) // 是否可以有多个keyvalueCtx2 : 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), 监控退出停止了...)returndefault://取出值fmt.Println(ctx.Value(key), goroutine监控中...)time.Sleep(2 * time.Second)}}
}4.4 Derived contexts派生上下文 Context包提供了从现有Context值派生新Context值的函数。这些值形成一个树:当一个 Context被取消时从它派生的所有Context也被取消。 package mainimport (contextfmttime
)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?frsidebar 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 mainimport (bufiofmtnetosstrings
)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 mainimport (fmtnet_ 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 mainimport (lognet
)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 mainimport (lognettime
)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.Connfor 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 mainimport (lognettime
)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 成功写 前面例子着重于Readclient端在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 mainimport (lognettime
)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 mainimport (fmtlognet
)func handleConn(c net.Conn) {defer c.Close()// read from the connectionvar 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 connectiongo handleConn(conn)}
}1-7 读写超时 SetDeadline(t time.Time) error 设置读写超时 SetReadDeadline(t time.Time) error 设置读超时 SetWriteDeadline(t time.Time) error 设置写超时 package mainimport (lognetostime
)func main() {connTimeout : 3 * time.Secondconn, err : net.DialTimeout(tcp, 127.0.0.1:8080, connTimeout) // 3s timeoutif err ! nil {log.Println(dial failed:, err)os.Exit(1)}defer conn.Close()readTimeout : 2 * time.Secondbuffer : make([]byte, 512)for {err conn.SetReadDeadline(time.Now().Add(readTimeout)) // timeoutif 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 mainimport (lognettime
)func main() {addr : 0.0.0.0:8080tcpAddr, 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. Im server.)for {time.Sleep(3 * time.Second) // sleep 3sn, 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 mainimport (fmt// timegithub.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 mainimport (fmtgithub.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 mainimport (fmtgithub.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 mainimport (fmtgithub.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 mainimport (fmttimered github.com/gomodule/redigo/redis
)type Redis struct {pool *red.Pool
}var redis *Redisfunc 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 mainimport (contextfmttimegithub.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.Minutec, err : redis.Dial(tcp, redisServerAddr,// Read timeout on server should be greater than ping period.redis.DialReadTimeout(healthCheckPeriod10*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 - nreturncase redis.Message:if err : onMessage(n.Channel, n.Data); err ! nil {done - errreturn}case redis.Subscription:switch n.Count {case len(channels):// Notify application when all channels are subscribed.if err : onStart(); err ! nil {done - errreturn}case 0:// Return from the goroutine when all channels are unsubscribed.done - nilreturn}}}}()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 loopcase 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:6379ctx, 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 listeners 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 mainimport (fmttimered github.com/gomodule/redigo/redis
)type Redis struct {pool *red.Pool
}var redis *Redisfunc 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)
}遇到过的问题 目前为止连接池的问题只遇到过一次问题而且是在测试环境的当时的配置是 DialConnectTimeouttime.Duration(200)*time.Millisecond DialReadTimeouttime.Duration(200)*time.Millisecond DialWriteTimeouttime.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 mainimport (bytesfmtiosync
)// 存放数据块缓冲区的临时对象
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.Bufferdelimiter 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 mainimport (fmt_ runtimeruntime/debugsyncsync/atomic
)func main() {// 禁用GC并保证在main函数执行结束前恢复GCdefer 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 nilv4 : 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 mainimport (fmtloggithub.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 -flagxxx 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 mainimport (flagfmt// 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 则为falseid : flag.Int(id, 0, id)port : flag.String(port, :8080, http listen port)var name stringflag.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 mainimport (flagfmt
)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循环自增序列号增至最大后归01毫秒最大生成 唯一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 mainimport (fmtlogmath/randreflecttimegitee.com/GuaikOrg/go-snowflake/snowflakegithub.com/chilts/sidgithub.com/kjk/betterguidgithub.com/oklog/ulidgithub.com/rs/xiduuid github.com/satori/go.uuidgithub.com/segmentio/ksuidgithub.com/sony/sonyflake
)const FOR_LOOP 100000func 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.3201ms2.2 Go语言Web开发与数据库实战 1 HTTP编程 a. Go原生支持httpimport(“net/http”) b. Go的http服务性能和nginx比较接近 c. 几行代码就可以实现一个web服务 1.1 HTTP常见请求方法 5. http常见请求方法 1Get请求 2Post请求 3Put请求 4Delete请求 5Head请求 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 mainimport (fmtio/ioutilnet/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 bytedata, 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 mainimport (fmtio/ioutilnet/httpnet/url
)func main() {//1.处理请求参数params : url.Values{}params.Set(name, dar)params.Set(hobby, 足球)//2.设置请求URLrawUrl : http://127.0.0.1:9000reqURL, err : url.ParseRequestURI(rawUrl)if err ! nil {fmt.Printf(url.ParseRequestURI()函数执行错误,错误为:%v\n, err)return}//3.整合请求URL和参数//Encode方法将请求参数编码为url编码格式(barbazfooquux)编码时会以键进行排序。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 mainimport (fmtnet/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 mainimport (fmtio/ioutilnet/httpstrings
)// net/http post demofunc main() {url : http://127.0.0.1:9000/postcontentType : application/jsondata : {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 mainimport (fmtio/ioutilnet/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(cant 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 mainimport (fmtnetnet/httptime
)func main() {url : http://www.baidu1.comc : http.Client{Transport: http.Transport{Dial: func(network, addr string) (net.Conn, error) {timeout : time.Second * 2return 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 mainimport (fmtionet/http
)const form htmlbodyform action# methodpost namebarinput typetext namein/input typetext namein/input typesubmit valueSubmit//form/html/bodyfunc 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, h1hello, 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 mainimport (fmtiolognet/http
)const form htmlbodyform action# methodpost namebarinput typetext namein/input typetext namein/input typesubmit valueSubmit//form/html/bodyfunc 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, h1hello, 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 *intvar a intptr 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 mainimport (fmthtml/templateionet/http
)var myTemplate *template.Templatetype 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 stringTitle stringAge int
}func userInfo(w http.ResponseWriter, r *http.Request) {fmt.Println(handle hello)//fmt.Fprintf(w, hello )var arr []Personp : 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.ResponseWriterif 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) )ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETutf8mb4; 4.0 连接mysql Open函数 db, err : sql.Open(mysql, 用户名:密码tcp(IP:端口)/数据库?charsetutf8) 例如db, err : sql.Open(mysql, root:1234tcp(127.0.0.1:3306)/test?charsetutf8) package mainimport (database/sqlfmt_ github.com/go-sql-driver/mysql // 注释掉后异常 _ 调用初始化函数
)// https://github.com/go-sql-driver/mysql#usage
func main() {db, err : sql.Open(mysql, root:1234tcp(127.0.0.1:3306)/go_test?charsetutf8mb4)fmt.Println(err:, err) // err: nilif 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 necessaryif err ! nil {fmt.Println(数据库链接失败, err)}defer db.Close()
}4-1 mysql插入数据 4-1-mysql.go package mainimport (database/sqlfmt_ 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() // 新插入数据的idif 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:1234tcp(127.0.0.1:3306)/go_test?charsetutf8mb4)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 mainimport (database/sqlfmt_ github.com/go-sql-driver/mysql
)type user struct {id intname stringage 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:123456tcp(127.0.0.1:3306)/go_test?charsetutf8mb4)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 mainimport (database/sqlfmt_ github.com/go-sql-driver/mysql
)type user struct {id intname stringage 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 usererr : 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:123456tcp(127.0.0.1:3306)/go_test?charsetutf8mb4)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 mainimport (database/sqlfmt_ github.com/go-sql-driver/mysql
)type user struct {id intname stringage 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:123456tcp(127.0.0.1:3306)/go_test?charsetutf8mb4)// 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 mainimport (database/sqlfmt_ github.com/go-sql-driver/mysql
)type user struct {id intname stringage 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:123456tcp(127.0.0.1:3306)/go_test?charsetutf8mb4)// 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 mainimport (database/sqlfmt_ github.com/go-sql-driver/mysql
)type user struct {id intname stringage 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 usererr : 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:123456tcp(127.0.0.1:3306)/go_test?charsetutf8mb4)// 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 mainimport (database/sqlfmt_ github.com/go-sql-driver/mysql
)type user struct {id intname stringage 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 usererr : 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 age30 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 age40 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:123456tcp(127.0.0.1:3306)/go_test?charsetutf8mb4)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 mainimport (fmt_ github.com/go-sql-driver/mysqlgithub.com/jmoiron/sqlx
)type user struct {ID int json:id db:idName string json:name db:nameAge int json:age db:age
}var db *sqlx.DB// 连接数据库
func initDB() (err error) {dsn : root:123456tcp(127.0.0.1:3306)/go_test?charsetutf8mb4// 也可以使用MustConnect连接不成功就panicdb, 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 usererr : 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 []usererr : 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() // 新插入数据的idif 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 age40 where id?tx.MustExec(sqlStr1, 2)sqlStr2 : Update user set age50 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(查找用户id1的用户:%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 // mysqlgithub.com/jmoiron/sqlx
)// DB 数据库模型
var DB *sqlx.DBconst dsn root:123456tcp(127.0.0.1:3306)/go_test?charsetutf8mb4type user struct {ID int json:id db:idName string json:name db:nameAge 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 dbif 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(查找用户id1的用户:%v \n, u)
}// Select、Queryx查询多条数据
// Queryx可以指定到不同的数据类型中
func getAll() {sql : select id, name ,age from user where id 1var us []usererr : 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()// 查数据-GetgetNum()// 查数据-SelectgetAll()// 事务// events()
}8 gin mysql restfull api 代码仓库 github.com/yunixiangfeng/devops/tree/main/gin_restful api\users.go package apiimport (fmt. gin_restful/modelsnet/httpstrconvgithub.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 dbimport (database/sqllog_ github.com/go-sql-driver/mysql
)var SqlDB *sql.DBfunc init() {var err errorSqlDB, err sql.Open(mysql, root:123456tcp(127.0.0.1:3306)/go_test?charsetutf8mb4)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 modelsimport (gin_restful/dblog
)type Person struct {Id int json:id form:idName string json:name form:nameTelephone 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 mainimport 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 mainimport (. gin_restful/apigithub.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/usersusers.POST(/add, AddUsers) //http://192.168.204.132:8806/api/v1/users/addusers.GET(/get/:id, GetOne) //http://192.168.204.132:8806/api/v1/users/get/5users.POST(/update, UpdateUser) //http://192.168.204.132:8806/api/v1/users/updateusers.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/usersdepartments.POST(/add, AddUsers) //http://192.168.204.132:8806/api/v1/users/adddepartments.GET(/get/:id, GetOne) //http://192.168.204.132:8806/api/v1/users/get/5departments.POST(/update, UpdateUser) //http://192.168.204.132:8806/api/v1/users/updatedepartments.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?actionindexlangzh_CNtoken 1.2 开发者权限 进入公众号管理页面下拉左边侧 1.3 微信公众号后台接口权限 普通用户只要是接收消息和自动回复消息的权限 1.4 公众号消息回复 1.5 服务器配置 2 HTTP服务 我们先使用原生的http接口来处理后续改用gin来处理 我们这里主要处理Get和Post方法见代码 Get处理token验证 处理token的验证 Post处理消息回复 处理消息 工程: wechat main.go package mainimport (fmtlognet/httptimewechat/wx
)const (logLevel devport 80token 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)) // 校验成功返回的是Echostrreturn}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:portHandler: httpHandler{}, // 用什么handler来处理ReadTimeout: 5 * time.Second, // 读写超时 微信给出来5WriteTimeout: 5 * time.Second,MaxHeaderBytes: 0,}log.Println(fmt.Sprintf(Listen: %d, port))log.Fatal(server.ListenAndServe())defer CloseLog()
}route.go package mainimport (ionet/httpregexptime
)type WebController struct {Function func(http.ResponseWriter, *http.Request)Method stringPattern 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.Patternif m, _ : regexp.MatchString(webController.Pattern, r.URL.Path); m { // 匹配URLif 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 mainimport (fmtiolognet/httpostime
)var LogFile *os.Filefunc 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 LICENSEVersion 3, 29 June 2007Copyright (C) 2007 Free Software Foundation, Inc. http://fsf.org/Everyone is permitted to copy and distribute verbatim copiesof this license document, but changing it is not allowed.PreambleThe GNU General Public License is a free, copyleft license for
software and other kinds of works. wx\structs.go package wximport (encoding/xmlstrconvtime
)type Base struct {FromUserName CDATATextToUserName CDATATextMsgType CDATATextCreateTime 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:xmlBaseContent CDATAText
}wx\utils.go package wxfunc value2CDATA(v string) CDATAText {return CDATAText{![CDATA[ v ]]}
}wx\wx.go package wximport (crypto/sha1encoding/xmlerrorsfmtio/ioutillognet/httpsortgithub.com/clbanning/mxj
)type weixinQuery struct {Signature string json:signatureTimestamp string json:timestampNonce string json:nonceEncryptType string json:encrypt_typeMsgSignature string json:msg_signatureEchostr string json:echostr
}type WeixinClient struct {Token stringQuery weixinQuery // 请求的一些参数Message map[string]interface{}Request *http.RequestResponseWriter http.ResponseWriterMethods 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 // 获取本地的tokenweixinClient.Request rweixinClient.ResponseWriter wweixinClient.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 weixinQuerylog.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 TextMessagereply.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/xmlthis.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() // 处理文本消息breakdefault: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 mainimport (bytescrypto/randcrypto/sha1fmtmath/bigsortstrconvtime
)func CreateRandomString(len int) string {var container stringvar str abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890b : 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: 1607173019qingqvCyrKEuoSh : sha1.New() // 完全都是自己的服务的时候 你这里你用md5h.Write([]byte(str)) // 转成bytesignature fmt.Sprintf(%x, h.Sum(nil)) // h.Sum(nil) 做hash 79efadd80a344c0b73b3bd2c403184f7425a5a67return
}func VerifySignature(token string, timestamp string, nonce string, signature string) bool {// str token timestamp noncestrs : sort.StringSlice{token, timestamp, nonce} // 使用本地的token生成校验sort.Strings(strs)str : for _, s : range strs {str s}h : sha1.New() // 完全都是自己的服务的时候 你这里你用md5h.Write([]byte(str))return fmt.Sprintf(%x, h.Sum(nil)) signature
}
func main() {token : qing// 产生签名timestamp, nonce, signature : GenerateSignature(token) // 发送服务器的时候是发送 timestamp, nonce, signaturefmt.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 mainimport (encoding/xmlfmtio/ioutilos
)// 如果struct中有一个叫做XMLName且类型为xml.Name字段
// 那么在解析的时候就会保存这个element的名字到该字段, 比如这里的config
type SConfig struct {XMLName xml.Name xml:config // 指定最外层的标签为configSmtpServer string xml:smtpServer // 读取smtpServer配置项并将结果保存到SmtpServer变量中SmtpPort int xml:smtpPortSender string xml:senderSenderPasswd string xml:senderPasswdReceivers SReceivers xml:receivers // 读取receivers标签下的内容以结构方式获取
}type SReceivers struct {Age int xml:ageFlag 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 configsmtpServersmtp.qq.com/smtpServersmtpPort25/smtpPortsenderyouqq.com/sendersenderPasswd123456/senderPasswdreceivers flagtrueuserkiqq.gom/useruserdarq.gom/userscript![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 mainimport (encoding/xmlfmtstrconvtimegithub.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或者stringtype CDATAText struct {Text string xml:,innerxml
}type Base struct {FromUserName CDATATextToUserName CDATATextMsgType CDATATextCreateTime CDATAText
}// 文本消息的封装
type TextMessage struct {XMLName xml.Name xml:xmlBaseContent CDATAText
}// 图片消息的封装
type PictureMessage struct {XMLName xml.Name xml:xmlBasePicUrl CDATATextMediaId CDATAText
}func value2CDATA(v string) CDATAText {return CDATAText{![CDATA[ v ]]}
}func main() {// 1. 解析 XMLxmlStr : xml ToUserName![CDATA[toUser]]/ToUserName FromUserName![CDATA[fromUser]]/FromUserName CreateTime1348831860/CreateTime MsgType![CDATA[text]]/MsgType Content![CDATA[this is a test]]/Content MsgId1234567890123456/MsgId /xmlvar 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 messagefmt.Println(1. 解析出来:, Message) // xml对应的字段还是在map// 2. 封装XMLvar reply TextMessageinMsg, ok : Message[Content].(string) // 读取内容 .(string)转成什么类型的数据if !ok {return}fmt.Println(Message[ToUserName].(string):, Message[ToUserName].(string)) // 如果服务器要处理// 封装回复消息需要添加 CDATAreply.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) // 得到的是bytefmt.Println(2. 生成XML:, string(replyXml)) // []byte - stringfmt.Println(2. 生成XML:, []byte(string(replyXml))) // string - []byte
}xml ToUserName![CDATA[toUser]]/ToUserName FromUserName![CDATA[fromUser]]/FromUserName CreateTime1348831860/CreateTime MsgType![CDATA[text]]/MsgType Content![CDATA[this is a test]]/Content MsgId1234567890123456/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 消息id64位整型 xml ToUserName![CDATA[toUser]]/ToUserName FromUserName![CDATA[fromUser]]/FromUserName CreateTime1348831860/CreateTime MsgType![CDATA[text]]/MsgType Content![CDATA[this is a test]]/Content MsgId1234567890123456/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 ProtocolRFC2326实时流传输协议 RTMP RTMP是Real Time Messaging Protocol实时消息传输协议) HTTP-FLV HTTP-MP4 HLS WebRTC 2.1 直播架构-基本流程 软件编码–提高机器的兼容性 2.2 直播常用工具 ◼ 推流工具 • ffmpeghttps://www.ffmpeg.org/download.html • OBS studiohttps://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 ◼ 通过不同的MethodGET/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 gingormcasbin示例 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