• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Golang Devophotoshop项目开发

武飞扬头像
theo.wu
帮助1

1.1 GO语言基础

1 初识Go语言

1.1.1 开发环境搭建

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

1.2.1 Go语言特性-垃圾回收

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

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

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

d. gc 垃圾回收

1.2.2 Go语言特性-天然并发

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

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

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

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

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

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

1.2.3 Go语言特性-channel

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

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

c. 支持任何类型

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

1.2.4 Go语言特性-多返回值

一个函数返回多个值

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

1.4.1包的概念

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

2. 包可以被其他包引用

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

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

2. Go语言基础

2 基本数据类型和操作符

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

2. Go程序基本结构

3. 常量和变量

4. 数据类型和操作符

5. 字符串类型

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

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

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

a. boy

b. Boy

c. a b

d. 0boy

e. _boy

f. =_boy

g. _

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

4. 保留关键字

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

学新通

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

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

var varName type 或者 varName : = value

◼ package and import: 包和导入

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

◼ return :用于从函数返回

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

◼ go : 用于并行

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

◼ interface 用于定义接口

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

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

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

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

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

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

足都会执行

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

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

◼ chan用于channel通讯

◼ type用于声明自定义类型

◼ map用于声明map类型数据

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

2.2 Go程序的基本结构1

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

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

2.2 Go程序的基本结构2

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

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

4. 包中函数调用:

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

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

函数名进行调用

5. 包访问控制规则:

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

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

包外部不能访问

2.4 常量1

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

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

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

举例: const b string = “hello world”

const b = “hello world”

const Pi = 3.1414926

const a = 9/3

const c = getValue()

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

2.4 常量2

4. 比较优雅的写法:

尽量减少我们写代码 const (

a = 0

b = 1

c = 2

)

5. 更加专业的写法:

const (

a = iota

b //1

c //2

)

2.5 变量1

1. 语法:var identifier type

2.5 变量2

Var (

    a int //默认为0

    b string //默认为””

    c bool //默认为false

    d = 8

    e = “hello world”

    )

练习

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

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

2.6 值类型和引用类型

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

学新通

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

学新通

2.6值类型和引用类型

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

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

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

代码:2-6-swap.go

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

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

代码: 2-6-value_quote.go

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

2.7 变量的作用域

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

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

则作用于整个程序。

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

2.8 数据类型和操作符1

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

2.8 数据类型和操作符2

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

float32、float64

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

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

2.8 数据类型和操作符3

5. 字符类型:var a byte

var a byte = ‘c’

6. 字符串类型: var str string

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

package main

import "fmt"

func main() {

    var n int16 = 34

    var m int32

    m = n

    m = int32(n)

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

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

}

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

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

2.9数据类型和操作符1

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

<

<=

>和 >=

2.9数据类型和操作符2

2. 数学操作符: 、-、

*

、/等等

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

2.9 数据类型和操作符3

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

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

3. Go函数

3 流程控制

for range 语句

str := "hello world,中国"

for i, v := range str {

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

}

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

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

3 函数1

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

func add()

{

}

3 函数2

2. golang函数特点:

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

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

c. 匿名函数

d. 多返回值

3 函数2

2. golang函数特点:

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

3 函数3

3. 函数参数传递方式:

1). 值传递

2). 引用传递

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

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

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

3 函数3

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

2). 引用传递

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

go只有值传递、浅拷贝

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

3 函数4

4. 命名返回值的名字

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

    c = a b

    return

}

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

    sum = a b

    avg = (a b) / 2

    return

}

3 函数5

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

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

sum = a b

avg = (a b)/2

return

}

func main() {

sum, _ := calc(100, 200)

}

3 函数6

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

}

0个或多个参数

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

}

1个或多个参数

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

}

2个或多个参数

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

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

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

3 函数7

7. defer用途:

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

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

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

3 函数7 defer用途

package main

import "fmt"

func main() {

    i := 0

    defer fmt.Println(i)

    i

    return

}

打印是多少?

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

package main

import "fmt"

func main() {

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

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

    }

}

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

3 函数7 defer用途

1 关闭文件句柄

func read() {

    file := open(filename)

    defer file.Close()

    //文件操作

}

3 函数7 defer用途

2. 锁资源释放

func read() {

mc.Lock()

defer mc.Unlock()

//其他操作

}

3 函数7 defer用途

3. 数据库连接释放

func read() {

conn := openDatabase()

defer conn.Close()

//其他操作

}

4. Go数组和切片

4 常用结构

1. 内置函数、闭包

2. 数组与切片

3. map数据结构

4. package介绍

4.1 内置函数

1. close:主要用来关闭channel

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

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

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

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

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

7. new和make的区别

学新通

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

4.2闭包

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

package main

import “fmt”

func main() {

var f = Adder()

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

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

fmt.Print(f(300))

}

func Adder() func(int) int {

var x int

return func(delta int) int {

x = delta

return x

}

}

4.2闭包 例子

package main

import (

"fmt"

"strings"

)

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

return func(name string) string {

if !strings.HasSuffix(name, suffix) {

return name suffix

}

return name

}

}

func main() {

func1 := makeSuffixFunc(".bmp")

func2 := makeSuffixFunc(".jpg")

fmt.Println(func1("test"))

fmt.Println(func2("test"))

}

4.3数组与切片

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

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

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

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

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

}

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

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

arr2 := arr1

arr2[2] = 100

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

4.3数组与切片-案例

package main

import (

"fmt"

)

func modify(arr [5]int) {

arr[0] = 100

return

}

func main() {

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

modify(a)

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

fmt.Println(a[i])

}

}

package main

import (

"fmt"

)

func modify(arr *[5]int) {

(*arr)[0] = 100

return

}

func main() {

var a [5]int

modify(&a)

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

fmt.Println(a[i])

}

}

4.3 数组与切片-数组

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

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

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

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

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

2. 多维数组

a. var age [5][3]int

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

3. 多维数组遍历

package main

import (

"fmt"

)

func main() {

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

for k1, v1 := range f {

for k2, v2 := range v1 {

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

}

fmt.Println()

}

}

4.3 数组与切片-切片定义

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

代码:4-3-slice1.go

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

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

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

学新通

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

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

3. 通过make来创建切片

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

slice := make([]type, len)

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

学新通

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

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

slice = append(slice, 10)

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

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

a = append(a, b…)

5. For range 遍历切片

for index, val := range slice {

}

6. 切片resize

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

b := a[1:2]

b = b[0:3]

7. 切片拷贝

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

s2 := make([]int, 10)

copy(s2, s1)

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

s3 = append(s3, s2…)

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

4-3-slice-make.go

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

8. string与slice

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

可以进行切片操作

str := “hello world”

s1 := str[0:5]

fmt.Println(s1)

s2 := str[5:]

fmt.Println(s2)

9. string 的底层布局
学新通

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

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

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

str := “hello world”

s := []byte(str)

s[0] = ‘o’

str = string(s)

4.4 数组与切片的区别1

它们的定义:

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

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

数组的例子

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

var y[3]int = x

fmt.Println(x,y)

y[0]=999

fmt.Println(x,y)

切片的例子

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

var y[]int = x

fmt.Println(x,y)

y[0]=999

fmt.Println(x,y)

4.4 数组与切片的区别2

它们的定义:

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

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

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

“...”代替

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

4.5 new和make的区别

new

func main() {

var i *int

i=new(int)

*i=10

fmt.Println(*i)

}

 make

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

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

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

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

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

5. Go test方法

5 Go test

前置条件:

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

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

5 Go test 举例

举例:gotest.go

package mytest

import (

"errors"

)

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

if b == 0 {

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

}

return a / b, nil

}

gotest_test.go

package mytest

import (

"testing"

)

func Test_Division_1(t *testing.T) {

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

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

} else {

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

}

}

func Test_Division_2(t *testing.T) {

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

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

报错

} else {

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

}

}

5 Go test 测试

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

2.测试单个方法

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

3.查看帮助 go help test

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

5 Go test 命令介绍1

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

格式形如:

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

参数解读:

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

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

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

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

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

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

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

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

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

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

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

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

5 Go test 命令介绍2-续

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

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

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

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

那就是不做打点了。

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

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

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

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

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

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

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

1.2 Go语言接口与反射

Go语言接口与反射

1. 结构

2. 接口

3. 反射

1. 结构

1.1 struct简介

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

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

struct等

学新通

1.2 struct详解-struct定义

学新通

1.2 struct详解-声明与初始化

声明与初始化

var stu1 Student

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

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

1.2 struct详解-struct使用

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

type Student struct {

    name string

    age int

    Class string

    }

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

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

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

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

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

    }

    func main() {

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

    fmt.Println(stu1.name) // dar

    }

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

1.3 struct tag

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

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

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

type Student struct {

    Name string "the name of student"

    Age int "the age of student"

    Class string "the class of student"

   }

1.3 struct tag –应用场景json示例

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

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

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

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

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

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

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

type Person struct {

    Name string

    Age int

    }

    type Student struct {

    score string

    Age int

    Person // 匿名内嵌结构体

    }

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

1.5 struct-继承、多继承

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

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

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

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

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

type Person struct {

    Name string

    Age int

    }

    type Teacher struct {

    Salary int

    Classes string

   }

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

1.6 struct-结构体中的方法

方法是什么

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

类型的函数。

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

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

实现。

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

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

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

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

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

做是允许的

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

定义方法的格式

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

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

type Person struct {

    Name string

    Age int

    }

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

    fmt.Println(p.Name)

    return p.Name

    }

    func main() {

    var person1 = new(Person)

    person1.Age = 34

    person1.Name = "dar"

    person1.Getname() // dar

    }

结构体的指针方法

学新通

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

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

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

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

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

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

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

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

通的值类型上定义方法。

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

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

1.7 struct-内存分布

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

学新通

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

学新通

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

2. 接口

2.1 interface简介

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

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

◼ interface 是方法的集合

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

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

◼ interface 不能包含任何变量

2.2 interface定义

type 接口名称 interface {

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

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

    ...

    }

2.3 interface使用

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

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

隐式地实现了空接口。

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

学新通

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

2.4 interface多态

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

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

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

2.5 interface接口嵌套

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

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

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

2.6 interface接口组合

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

学新通

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

2.7 interface类型转换

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

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

var s int

var x interface

x = s

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

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

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

2.8 interface类型判断

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

for k, v := range items {

switch v.(type) {

case string:

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

case bool:

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

case int:

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

case float32, float64:

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

case Student:

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

case *Student:

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

}

}

}

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

2.9 指向指针的接口变量

学新通

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

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

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

改自己的行为。

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

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

反射的目标:

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

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

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

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

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

3 reflect反射

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

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

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

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

学新通

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

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

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

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

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

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

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

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


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

3 reflect反射- Type和Value

学新通

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

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

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

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

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

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

3 reflect反射-利弊

反射的好处

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

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

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

反射的弊端

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

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

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

3.1 reflect反射-Type

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

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

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

panic。

获取Type对象的方法:

func TypeOf(i interface{}) Type

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

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

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

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

// 数值

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

// 数组

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

// 映射

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

// 通道

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

// 结构体

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

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

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

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

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

// 函数

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

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

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

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

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

3.1 reflect反射-Type结构

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

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

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

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

3.1 reflect反射- reflect.Value方法

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

3.2 reflect反射- reflect.Value方法

获取值方法:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

3.2 reflect反射- reflect.Value方法

设置值方法:

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

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

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

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

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

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

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

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

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

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

添加

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

3.2 reflect反射- reflect.Value方法

其他方法:

//结构体相关:

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

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

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

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

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

零值(reflect.ValueOf(nil))

//通道相关:

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

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

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

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

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

//函数相关

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

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

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

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

3.2 reflect反射- reflect.Value 结构体

type Value struct {

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

    ptr unsafe.Pointer // 数据指针

    flag uintptr // 标志位

    }

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

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

1. Reflection goes from interface value to reflection object.

2. Reflection goes from reflection object to interface value.

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

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

func TypeOf(v interface{}) Type

func ValueOf(v interface{}) Value

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

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

func (v Value) Interface() interface{}

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

值可以被修改。

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

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

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

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

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

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

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

学新通

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

4 Go map实战

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

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

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

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

map中的key的数据类型

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

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

些类型自定义的类型

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

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

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

4.2 map基本操作

map创建

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

map增删改查

map遍历

•遍历的顺序是随机的

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

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

查: v1 := m["x"]

v2, ok2 := m["x"]

删: delete(m, "x")

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

输出k,v值 }

5 Go string字符串

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

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

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

学新通

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

5.1 Go string字符串-遍历

学新通

package main

import "fmt"

func main() {

    var s = "嘻哈china"

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

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

    }

}

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

func main() {

    var s = "嘻哈china"

    for codepoint, runeValue := range s {

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

    }

}

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

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

学新通

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

1.3 Go语言并发编程

1. Goroutine

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

1.1 如何使用Goroutine

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

学新通

1.2 子协程异常退出的影响

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

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

学新通

1.3 协程异常处理-recover

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

在延迟函数 defer 中有效。

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

执行。

学新通

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

1-4 启动百万协程

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

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

1-5 死循环

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

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

1.6 设置线程数

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

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

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

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

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

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

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

学新通

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

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

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

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

学新通

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

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

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

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

学新通

2. Channel

2 通道channel

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

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

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

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

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

学新通

2.1 创建通道

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

有两种通道类型:

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

行), 1024)

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

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

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

学新通

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

2.2 读写通道

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

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

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

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

部的元素个数。

2-3 读写阻塞

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

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

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

2.4 关闭通道

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

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

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

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

2-5 通道写安全

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

有被关闭

 多人写入怎么办?

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

2-6 WaitGroup

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

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

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

学新通

2-7 多路通道

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

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

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

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

2-8 多路复用select

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

2-9 非阻塞读写

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

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

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

学新通

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

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

生产者消费模型

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

3. 线程安全

3-1 线程安全-互斥锁

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

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

需要加上-race 执行

学新通

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

3-2 避免锁复制

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

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

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

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

学新通

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

3-3 使用匿名锁字段

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

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

学新通

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

3-4 使用读写锁

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

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

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

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

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

学新通

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

3.5 发布订阅模型

综合前面学的

支持过滤器设置主题

学新通

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

3.6 sync.Once初始化

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

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

学新通

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

4. context

4 Go语言Context

为什么需要 Context

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

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

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

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

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

可以停止

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

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

4.1 Context接口

type Context interface {

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

Done() <-chan struct{}

Err() error

Value(key interface{}) interface{}

}

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

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

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

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

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

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

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

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

4.1 Background()和TODO()

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

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

Context,也就是根 Context。

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

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

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

4.2 Context的继承衍生

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

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

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

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

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

Context的意思

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

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

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

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

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

学新通

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

4.3 Context使用原则

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

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

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

context.TODO

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

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

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

4.4 Derived contexts派生上下文

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

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

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

推荐教程

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

Golang 如何正确使用 Context

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

cgo go和c混编

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

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

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

1. 网络编程

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

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

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

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

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

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

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

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

学新通

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

1.0 TCP socket api

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

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

•Close(): 关闭连接。

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

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

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

调用SetReadDeadline()和SetWriteDeadline()。

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

的超时最后期限。

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

的超时最后期限。

1.1 TCP连接的建立

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

服务端

参考上一页

客户端

阻塞Dial: 超时机制的Dial:

学新通

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

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

2、对方服务的listen backlog满

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

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

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

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

学新通

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

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

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

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

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

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

学新通

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

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

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

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

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

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

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

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

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

 1.3 Socket读写

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

*TCPConn:

1.3.1 conn.Read的行为特点

1 Socket中无数据

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

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

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

2 Socket中有部分数据

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

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

3 Socket中有足够数据

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

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

4 Socket关闭

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

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

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

5 读取操作超时

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

一部分数据了呢?

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

1.3.2 conn.Write的行为特点

1 成功写

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

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

2 写阻塞

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

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

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

3 写入部分数据

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

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

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

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

1.4 Goroutine safe

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

否是goroutine safe的呢。

Write

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

1.5 Socket属性

SetKeepAlive

SetKeepAlivePeriod

SetLinger

SetNoDelay (默认no delay)

SetWriteBuffer

SetReadBuffer

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

tcpConn, ok := conn.(*TCPConn)

if !ok { //error handle }

tcpConn.SetNoDelay(true)

1.6 关闭连接

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

结果有不同。

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

1-7 读写超时

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

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

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

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

2. Redis库redigo

2 redis

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

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

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

import(

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

)

go get github.com/gomodule/redigo/redis

2.1 连接redis

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

2.2 redis set操作

学新通

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

2.3 redis Hash操作

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

2.4 redis mset操作

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

2.5 redis expire操作

学新通

2.6 redis list操作

学新通

2-7 subpub操作 

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

2-8 连接池

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

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

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

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

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

DialConnectTimeout:连接Redis超时时间。

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

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

连接流程大概是这样的

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

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

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

2-8 redis连接池的坑

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

遇到过的问题

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

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

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

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

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

read tcp 127.0.0.1:6379: i/o timeout

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

timeout。

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

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

github.com/gomodule/redigo/redis

3. 临时对象池sync.Pool

3 临时对象池

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

安全的。

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

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

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

New字段

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

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

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

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

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

就会得到nil。

学新通

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

3.1 Get

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

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

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

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

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

3.2 Put

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

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

 学新通

4. 配置文件读取goconfig

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

ini配置文件读写 conf.ini

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

4-1-config-ini.go

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

5. 解析命令行flag

5 命令行解析Go flag

cmd -flag // 只支持bool类型

cmd -flag=xxx

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

1. 定义flag参数

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

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

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

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

var flagvar int

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

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

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

5-1-cli-flag.go

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

5-2-self-flag.go

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

6. uuid生成方案

6 uuid

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

常用uuid性能测试

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

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

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

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

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

成ID;

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

唯一ID的数量是4096个。

snowflake

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

snowflake的长很久。

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

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

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

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

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

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

学新通

学新通  

6-1-1-uuid.go

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

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

1 HTTP编程

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

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

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

1.1 HTTP常见请求方法

5. http常见请求方法

1)Get请求

2)Post请求

3)Put请求

4)Delete请求

5)Head请求

1.2 http 常见状态码

http 常见状态码

http.StatusContinue = 100

http.StatusOK = 200

http.StatusFound = 302

http.StatusBadRequest = 400

http.StatusUnauthorized = 401

http.StatusForbidden = 403

http.StatusNotFound = 404

http.StatusInternalServerError = 500

2 Client客户端

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

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

Response结构体的定义

type Response struct {

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

StatusCode int // e.g. 200

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

ProtoMajor int // e.g. 1

ProtoMinor int // e.g. 0

Header Header

Body io.ReadCloser

//...

}

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

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

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

2.1基本的HTTP/HTTPS请求

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

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

...

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

...

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

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

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

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

if err != nil {

// handle error

}

defer resp.Body.Close()

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

// ...

2.2.1 不带参数的Get方法示例

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

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

2.2.2 带参数的Get方法示例

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

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

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

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

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

学新通

2.3.1 post方法

发送POST请求的示例代码

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

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

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

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

2.4 head方法-client

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

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

主要有以下特点:

1、只请求资源的首部;

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

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

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

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

信息等

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

2.5 表单处理

学新通

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

2.6 panic处理

2-6-panic-server.go

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

学新通

3 模板

3 模板

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

3-1-template.go

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

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

4 Mysql

建库建表

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

CREATE DATABASE go_test;

进入该数据库:

use go_test;

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

CREATE TABLE `user` (

`id` BIGINT(20) NOT NULL AUTO_INCREMENT,

`name` VARCHAR(20) DEFAULT '',

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

PRIMARY KEY(`id`)

)ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT

CHARSET=utf8mb4;

4.0 连接mysql

Open函数:

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

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

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

4-1 mysql插入数据

4-1-mysql.go

学新通

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

4-2 mysql查询-单行查询

单行查询

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

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

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

4-2-mysql-query copy.go

学新通

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

4-2 mysql查询-多行查询

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

示query中的占位参数。

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

4-2-mysql-multi-query.go

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

4-3 mysql更新

4-3-mysql-update.go

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

4-4 mysql删除

4-4-mysql-delete.go

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

5 MySQL预处理

什么是预处理?

普通SQL语句执行过程:

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

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

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

预处理执行过程:

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

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

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

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

为什么要预处理?

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

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

2.避免SQL注入问题。

5.1 Go实现MySQL预处理

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

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

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

学新通

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

6 Go实现MySQL事务

事务相关方法 Go语言中使用以下三个方法实现MySQL中的事务操作。

开始事务: func (db *DB) Begin() (*Tx, error)

提交事务: func (tx *Tx) Commit() error

回滚事务: func (tx *Tx) Rollback() error

6-mysql-transaction.go

  1.  
    package main
  2.  
     
  3.  
    import (
  4.  
    "database/sql"
  5.  
    "fmt"
  6.  
     
  7.  
    _ "github.com/go-sql-driver/mysql"
  8.  
    )
  9.  
     
  10.  
    type user struct {
  11.  
    id int
  12.  
    name string
  13.  
    age int
  14.  
    }
  15.  
     
  16.  
    // 预处理查询示例
  17.  
    func prepareQueryDemo(db *sql.DB) {
  18.  
    sqlStr := "select id, name, age from user where id > ?"
  19.  
    stmt, err := db.Prepare(sqlStr)
  20.  
    if err != nil {
  21.  
    fmt.Printf("prepare failed, err:%v\n", err)
  22.  
    return
  23.  
    }
  24.  
    defer stmt.Close()
  25.  
    rows, err := stmt.Query(0)
  26.  
    if err != nil {
  27.  
    fmt.Printf("query failed, err:%v\n", err)
  28.  
    return
  29.  
    }
  30.  
    defer rows.Close()
  31.  
    // 循环读取结果集中的数据
  32.  
    for rows.Next() {
  33.  
    var u user
  34.  
    err := rows.Scan(&u.id, &u.name, &u.age)
  35.  
    if err != nil {
  36.  
    fmt.Printf("scan failed, err:%v\n", err)
  37.  
    return
  38.  
    }
  39.  
    fmt.Printf("id:%d name:%s age:%d\n", u.id, u.name, u.age)
  40.  
    }
  41.  
    }
  42.  
     
  43.  
    // 预处理插入示例
  44.  
    // 插入、更新和删除操作的预处理十分类似
  45.  
    func prepareInsertDemo(db *sql.DB) {
  46.  
    sqlStr := "insert into user(name, age) values (?,?)"
  47.  
    stmt, err := db.Prepare(sqlStr)
  48.  
    if err != nil {
  49.  
    fmt.Printf("prepare failed, err:%v\n", err)
  50.  
    return
  51.  
    }
  52.  
    defer stmt.Close()
  53.  
    _, err = stmt.Exec("darren", 18)
  54.  
    if err != nil {
  55.  
    fmt.Printf("insert failed, err:%v\n", err)
  56.  
    return
  57.  
    }
  58.  
    _, err = stmt.Exec("柚子老师", 18)
  59.  
    if err != nil {
  60.  
    fmt.Printf("insert failed, err:%v\n", err)
  61.  
    return
  62.  
    }
  63.  
    fmt.Println("insert success.")
  64.  
    }
  65.  
     
  66.  
    // 事务操作示例
  67.  
    func transactionDemo(db *sql.DB) {
  68.  
    tx, err := db.Begin() // 开启事务
  69.  
    if err != nil {
  70.  
    if tx != nil {
  71.  
    tx.Rollback() // 回滚
  72.  
    }
  73.  
    fmt.Printf("begin trans failed, err:%v\n", err)
  74.  
    return
  75.  
    }
  76.  
    sqlStr1 := "Update user set age=30 where id=?"
  77.  
    _, err = tx.Exec(sqlStr1, 2)
  78.  
    if err != nil {
  79.  
    tx.Rollback() // 回滚
  80.  
    fmt.Printf("exec sql1 failed, err:%v\n", err)
  81.  
    return
  82.  
    }
  83.  
    sqlStr2 := "Update user set age=40 where id=?"
  84.  
    _, err = tx.Exec(sqlStr2, 4)
  85.  
    if err != nil {
  86.  
    tx.Rollback() // 回滚
  87.  
    fmt.Printf("exec sql2 failed, err:%v\n", err)
  88.  
    return
  89.  
    }
  90.  
    err = tx.Commit() // 提交事务
  91.  
    if err != nil {
  92.  
    tx.Rollback() // 回滚
  93.  
    fmt.Printf("commit failed, err:%v\n", err)
  94.  
    return
  95.  
    }
  96.  
    fmt.Println("exec trans success!")
  97.  
    }
  98.  
    func main() {
  99.  
    db, err := sql.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/go_test?charset=utf8mb4")
  100.  
    if db != nil {
  101.  
    defer db.Close() // 健壮的写法
  102.  
    }
  103.  
    // fmt.Println("err:", err)
  104.  
    err = db.Ping()
  105.  
    if err != nil {
  106.  
    fmt.Println("数据库链接失败", err)
  107.  
    return
  108.  
    }
  109.  
    db.SetMaxOpenConns(10)
  110.  
    db.SetMaxIdleConns(5)
  111.  
    stats := db.Stats()
  112.  
    fmt.Println("stats1:", stats)
  113.  
    prepareInsertDemo(db)
  114.  
    prepareQueryDemo(db)
  115.  
    stats = db.Stats()
  116.  
    fmt.Println("stats2:", stats)
  117.  
    }
学新通

7 sqlx使用

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

安装

go get github.com/jmoiron/sqlx

7-mysql-sqlx.go

  1.  
    package main
  2.  
     
  3.  
    import (
  4.  
    "fmt"
  5.  
     
  6.  
    _ "github.com/go-sql-driver/mysql"
  7.  
    "github.com/jmoiron/sqlx"
  8.  
    )
  9.  
     
  10.  
    type user struct {
  11.  
    ID int `json:"id" db:"id"`
  12.  
    Name string `json:"name" db:"name"`
  13.  
    Age int `json:"age" db:"age"`
  14.  
    }
  15.  
     
  16.  
    var db *sqlx.DB
  17.  
     
  18.  
    // 连接数据库
  19.  
    func initDB() (err error) {
  20.  
    dsn := "root:123456@tcp(127.0.0.1:3306)/go_test?charset=utf8mb4"
  21.  
    // 也可以使用MustConnect连接不成功就panic
  22.  
    db, err = sqlx.Connect("mysql", dsn)
  23.  
    if err != nil {
  24.  
    fmt.Printf("connect DB failed, err:%v\n", err)
  25.  
    return
  26.  
    }
  27.  
    db.SetMaxOpenConns(20)
  28.  
    db.SetMaxIdleConns(10)
  29.  
    return
  30.  
    }
  31.  
     
  32.  
    // 查询单条数据
  33.  
    func queryRowDemo() {
  34.  
    sqlStr := "select id, name, age from user where id=?"
  35.  
    var u user
  36.  
    err := db.Get(&u, sqlStr, 2) // 单条查询
  37.  
    if err != nil {
  38.  
    fmt.Printf("get failed, err:%v\n", err)
  39.  
    return
  40.  
    }
  41.  
    fmt.Printf("id:%d name:%s age:%d\n", u.ID, u.Name, u.Age)
  42.  
    }
  43.  
     
  44.  
    // 查询多行数据
  45.  
    func queryMultiRowDemo() {
  46.  
    sqlStr := "select id, name, age from user where id > ?"
  47.  
    var users []user
  48.  
    err := db.Select(&users, sqlStr, 0) // 主要是查询
  49.  
    if err != nil {
  50.  
    fmt.Printf("query failed, err:%v\n", err)
  51.  
    return
  52.  
    }
  53.  
    fmt.Printf("users:%#v\n", users)
  54.  
    }
  55.  
     
  56.  
    // 插入数据
  57.  
    func insertRowDemo() {
  58.  
    sqlStr := "insert into user(name, age) values (?,?)"
  59.  
    ret, err := db.Exec(sqlStr, "隔壁老王", 18)
  60.  
    if err != nil {
  61.  
    fmt.Printf("insert failed, err:%v\n", err)
  62.  
    return
  63.  
    }
  64.  
    theID, err := ret.LastInsertId() // 新插入数据的id
  65.  
    if err != nil {
  66.  
    fmt.Printf("get lastinsert ID failed, err:%v\n", err)
  67.  
    return
  68.  
    }
  69.  
    fmt.Printf("insert success, the id is %d.\n", theID)
  70.  
    }
  71.  
     
  72.  
    // 更新数据
  73.  
    func updateRowDemo() {
  74.  
    sqlStr := "update user set age=? where id = ?"
  75.  
    ret, err := db.Exec(sqlStr, 39, 6)
  76.  
    if err != nil {
  77.  
    fmt.Printf("update failed, err:%v\n", err)
  78.  
    return
  79.  
    }
  80.  
    n, err := ret.RowsAffected() // 操作影响的行数
  81.  
    if err != nil {
  82.  
    fmt.Printf("get RowsAffected failed, err:%v\n", err)
  83.  
    return
  84.  
    }
  85.  
    fmt.Printf("update success, affected rows:%d\n", n)
  86.  
    }
  87.  
     
  88.  
    // 删除数据
  89.  
    func deleteRowDemo() {
  90.  
    sqlStr := "delete from user where id = ?"
  91.  
    ret, err := db.Exec(sqlStr, 6)
  92.  
    if err != nil {
  93.  
    fmt.Printf("delete failed, err:%v\n", err)
  94.  
    return
  95.  
    }
  96.  
    n, err := ret.RowsAffected() // 操作影响的行数
  97.  
    if err != nil {
  98.  
    fmt.Printf("get RowsAffected failed, err:%v\n", err)
  99.  
    return
  100.  
    }
  101.  
    fmt.Printf("delete success, affected rows:%d\n", n)
  102.  
    }
  103.  
     
  104.  
    // 事务操作
  105.  
    func transactionDemo() {
  106.  
    tx, err := db.Beginx() // 开启事务
  107.  
    if err != nil {
  108.  
    if tx != nil {
  109.  
    tx.Rollback()
  110.  
    }
  111.  
    fmt.Printf("begin trans failed, err:%v\n", err)
  112.  
    return
  113.  
    }
  114.  
    sqlStr1 := "Update user set age=40 where id=?"
  115.  
    tx.MustExec(sqlStr1, 2)
  116.  
    sqlStr2 := "Update user set age=50 where id=?"
  117.  
    tx.MustExec(sqlStr2, 4)
  118.  
    err = tx.Commit() // 提交事务
  119.  
    if err != nil {
  120.  
    tx.Rollback() // 回滚
  121.  
    fmt.Printf("commit failed, err:%v\n", err)
  122.  
    return
  123.  
    }
  124.  
    fmt.Println("exec trans success!")
  125.  
    }
  126.  
     
  127.  
    // Get、QueryRowx: 查询一条数据
  128.  
    // QueryRowx可以指定到不同的数据类型中
  129.  
    func getNum() {
  130.  
    var num int
  131.  
    _ = db.Get(&num, "select count(*) from user")
  132.  
    fmt.Printf("数据库一共有:%d 个用户\n", num)
  133.  
    var u user
  134.  
    _ = db.Get(&u, "select name, id, age from user where id = ?", 1)
  135.  
    fmt.Printf("查找用户id==1的用户:%v \n", u)
  136.  
    }
  137.  
    func main() {
  138.  
    err := initDB()
  139.  
    if err != nil {
  140.  
    fmt.Println("数据库链接失败", err)
  141.  
    return
  142.  
    }
  143.  
    insertRowDemo()
  144.  
    queryRowDemo()
  145.  
    getNum()
  146.  
    queryMultiRowDemo()
  147.  
    // defer db.Close()
  148.  
    }
学新通

7-mysql-sqlx-2.go

  1.  
    package main
  2.  
     
  3.  
    // 数据库连接初始化
  4.  
    import (
  5.  
    "fmt"
  6.  
     
  7.  
    _ "github.com/go-sql-driver/mysql" // mysql
  8.  
    "github.com/jmoiron/sqlx"
  9.  
    )
  10.  
     
  11.  
    // DB 数据库模型
  12.  
    var DB *sqlx.DB
  13.  
     
  14.  
    const dsn = "root:123456@tcp(127.0.0.1:3306)/go_test?charset=utf8mb4"
  15.  
     
  16.  
    type user struct {
  17.  
    ID int `json:"id" db:"id"`
  18.  
    Name string `json:"name" db:"name"`
  19.  
    Age int `json:"age" db:"age"`
  20.  
    }
  21.  
     
  22.  
    // connect 1.连接数据库
  23.  
    func connect() (db *sqlx.DB, err error) {
  24.  
    db, err = sqlx.Connect("mysql", dsn)
  25.  
    db.SetMaxOpenConns(100) // 设置连接池最大连接数
  26.  
    db.SetMaxIdleConns(20) // 设置连接池最大空闲连接数
  27.  
    DB = db
  28.  
    if err != nil {
  29.  
    fmt.Println("数据库连接失败==>", err)
  30.  
    }
  31.  
    fmt.Println("数据库已连接!")
  32.  
    return
  33.  
    }
  34.  
     
  35.  
    // 添加数据 Exec、MustExec
  36.  
    // MustExec遇到错误的时候直接抛出一个panic错误,程序就退出了;
  37.  
    // Exec是将错误和执行结果一起返回,由我们自己处理错误。 推荐使用!
  38.  
    func createUser() {
  39.  
    // 创建表
  40.  
    sql := `
  41.  
    CREATE TABLE user (
  42.  
    id bigint(20) NOT NULL AUTO_INCREMENT,
  43.  
    name varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '',
  44.  
    age int(11) NULL DEFAULT 0,
  45.  
    PRIMARY KEY (id) USING BTREE
  46.  
    ) ENGINE = InnoDB AUTO_INCREMENT = 20 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact
  47.  
    `
  48.  
    _, err := DB.Exec(sql)
  49.  
    fmt.Println(err)
  50.  
    }
  51.  
     
  52.  
    // 添加数据
  53.  
    func insertUser() {
  54.  
    sql := `insert into user (name, age) values ("lgx",18)`
  55.  
    res := DB.MustExec(sql)
  56.  
    fmt.Println(res.LastInsertId)
  57.  
    fmt.Println(res.RowsAffected)
  58.  
    }
  59.  
     
  60.  
    // 更新数据
  61.  
    func updateUser() {
  62.  
    sql := `update user set name = ?, age = ? where id = ?`
  63.  
    res, err := DB.Exec(sql, "LGX", 28, 20)
  64.  
    fmt.Println(err, res)
  65.  
    }
  66.  
     
  67.  
    // Get、QueryRowx: 查询一条数据
  68.  
    // QueryRowx可以指定到不同的数据类型中
  69.  
    func getNum() {
  70.  
    var num int
  71.  
    _ = DB.Get(&num, "select count(*) from user")
  72.  
    fmt.Printf("数据库一共有:%d 个用户\n", num)
  73.  
    var u user
  74.  
    _ = DB.Get(&u, "select name, id, age from user where id = ?", 2)
  75.  
    fmt.Printf("查找用户id==1的用户:%v \n", u)
  76.  
    }
  77.  
     
  78.  
    // Select、Queryx:查询多条数据
  79.  
    // Queryx可以指定到不同的数据类型中
  80.  
    func getAll() {
  81.  
    sql := `select id, name ,age from user where id > 1`
  82.  
    var us []user
  83.  
    err := DB.Select(&us, sql)
  84.  
    fmt.Println(err, us)
  85.  
    }
  86.  
     
  87.  
    // 删除
  88.  
    func deleteUser() {
  89.  
    sql := `delete from user where id = 20`
  90.  
    _, _ = DB.Exec(sql)
  91.  
    }
  92.  
     
  93.  
    // 事务处理
  94.  
    func events() {
  95.  
    tx, _ := DB.Beginx()
  96.  
    _, err1 := tx.Exec("update user set age = 10 where id = 20")
  97.  
    _, err2 := tx.Exec("update user set age = 10 where id = 21")
  98.  
    fmt.Println(err1, err2)
  99.  
    if err1 != nil || err2 != nil {
  100.  
    tx.Rollback()
  101.  
    }
  102.  
    tx.Commit()
  103.  
    }
  104.  
     
  105.  
    func main() {
  106.  
    db, _ := connect()
  107.  
    defer db.Close()
  108.  
    // 建表
  109.  
    // createUser()
  110.  
    // 添加数据
  111.  
    insertUser()
  112.  
    // 修改数据
  113.  
    updateUser()
  114.  
    // 查数据-Get
  115.  
    getNum()
  116.  
    // 查数据-Select
  117.  
    getAll()
  118.  
    // 事务
  119.  
    // events()
  120.  
    }
学新通

8 gin mysql restfull api

代码仓库

github.com/yunixiangfeng/devops/tree/main/gin_restful

学新通

 api\users.go

  1.  
    package api
  2.  
     
  3.  
    import (
  4.  
    "fmt"
  5.  
    . "gin_restful/models"
  6.  
    "net/http"
  7.  
    "strconv"
  8.  
     
  9.  
    "github.com/gin-gonic/gin"
  10.  
    )
  11.  
     
  12.  
    //index
  13.  
    func IndexUsers(c *gin.Context) {
  14.  
    c.String(http.StatusOK, "It works")
  15.  
    }
  16.  
     
  17.  
    //增加一条记录
  18.  
    func AddUsers(c *gin.Context) {
  19.  
    name := c.Request.FormValue("name")
  20.  
    telephone := c.Request.FormValue("telephone")
  21.  
    fmt.Println("name:", name)
  22.  
    fmt.Println("telephone:", telephone)
  23.  
    if name == "" {
  24.  
    msg := fmt.Sprintf("name字段错误")
  25.  
    c.JSON(http.StatusBadRequest, gin.H{
  26.  
    "msg": msg,
  27.  
    })
  28.  
    return
  29.  
    }
  30.  
    person := Person{
  31.  
    Name: name,
  32.  
    Telephone: telephone,
  33.  
    }
  34.  
    id := person.Create()
  35.  
    msg := fmt.Sprintf("insert 成功 %d", id)
  36.  
    c.JSON(http.StatusOK, gin.H{
  37.  
    "msg": msg,
  38.  
    })
  39.  
    }
  40.  
     
  41.  
    //获得一条记录
  42.  
    func GetOne(c *gin.Context) {
  43.  
    ids := c.Param("id")
  44.  
    id, _ := strconv.Atoi(ids)
  45.  
    p := Person{
  46.  
    Id: id,
  47.  
    }
  48.  
    rs, _ := p.GetRow()
  49.  
    c.JSON(http.StatusOK, gin.H{
  50.  
    "result": rs,
  51.  
    })
  52.  
    }
  53.  
     
  54.  
    //获得所有记录
  55.  
    func GetAll(c *gin.Context) {
  56.  
    p := Person{}
  57.  
    rs, _ := p.GetRows()
  58.  
    c.JSON(http.StatusOK, gin.H{
  59.  
    "list": rs,
  60.  
    })
  61.  
    }
  62.  
     
  63.  
    func UpdateUser(c *gin.Context) {
  64.  
    ids := c.Request.FormValue("id")
  65.  
    id, _ := strconv.Atoi(ids)
  66.  
    telephone := c.Request.FormValue("telephone")
  67.  
    person := Person{
  68.  
    Id: id,
  69.  
    Telephone: telephone,
  70.  
    }
  71.  
    row := person.Update()
  72.  
    msg := fmt.Sprintf("updated successful %d", row)
  73.  
    c.JSON(http.StatusOK, gin.H{
  74.  
    "msg": msg,
  75.  
    })
  76.  
    }
  77.  
     
  78.  
    //删除一条记录
  79.  
    func DelUser(c *gin.Context) {
  80.  
    ids := c.Request.FormValue("id")
  81.  
    id, _ := strconv.Atoi(ids)
  82.  
    row := Delete(id)
  83.  
    msg := fmt.Sprintf("delete successful %d", row)
  84.  
    c.JSON(http.StatusOK, gin.H{
  85.  
    "msg": msg,
  86.  
    })
  87.  
    }
学新通

db\mysql.go

  1.  
    package db
  2.  
     
  3.  
    import (
  4.  
    "database/sql"
  5.  
    "log"
  6.  
     
  7.  
    _ "github.com/go-sql-driver/mysql"
  8.  
    )
  9.  
     
  10.  
    var SqlDB *sql.DB
  11.  
     
  12.  
    func init() {
  13.  
    var err error
  14.  
    SqlDB, err = sql.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/go_test?charset=utf8mb4")
  15.  
    if err != nil {
  16.  
    log.Fatal(err.Error())
  17.  
    }
  18.  
    err = SqlDB.Ping()
  19.  
    if err != nil {
  20.  
    log.Fatal(err.Error())
  21.  
    }
  22.  
    SqlDB.SetMaxIdleConns(20)
  23.  
    SqlDB.SetMaxOpenConns(20)
  24.  
    }
学新通

models\users.go

  1.  
    package models
  2.  
     
  3.  
    import (
  4.  
    "gin_restful/db"
  5.  
    "log"
  6.  
    )
  7.  
     
  8.  
    type Person struct {
  9.  
    Id int `json:"id" form:"id"`
  10.  
    Name string `json:"name" form:"name"`
  11.  
    Telephone string `json:"telephone" form:"telephone"`
  12.  
    }
  13.  
     
  14.  
    //插入
  15.  
    func (person *Person) Create() int64 {
  16.  
    rs, err := db.SqlDB.Exec("INSERT into users (name, telephone) value (?,?)", person.Name, person.Telephone)
  17.  
    if err != nil{
  18.  
    log.Fatal(err)
  19.  
    }
  20.  
    id, err := rs.LastInsertId()
  21.  
    if err != nil{
  22.  
    log.Fatal(err)
  23.  
    }
  24.  
    return id
  25.  
    }
  26.  
     
  27.  
    //查询一条记录
  28.  
    func (p *Person) GetRow() (person Person, err error) {
  29.  
    person = Person{}
  30.  
    err = db.SqlDB.QueryRow("select id,name,telephone from users where id = ?", p.Id).Scan(&person.Id, &person.Name, &person.Telephone)
  31.  
    return
  32.  
    }
  33.  
     
  34.  
    //查询所有记录
  35.  
    func (person *Person) GetRows() (persons []Person, err error) {
  36.  
    rows, err := db.SqlDB.Query("select id,name,telephone from users")
  37.  
    for rows.Next(){
  38.  
    person := Person{}
  39.  
    err := rows.Scan(&person.Id, &person.Name, &person.Telephone)
  40.  
    if err != nil {
  41.  
    log.Fatal(err)
  42.  
    }
  43.  
    persons = append(persons, person)
  44.  
    }
  45.  
    rows.Close()
  46.  
    return
  47.  
    }
  48.  
     
  49.  
    //修改
  50.  
    func (person *Person) Update() int64{
  51.  
    rs, err := db.SqlDB.Exec("update users set telephone = ? where id = ?", person.Telephone, person.Id)
  52.  
    if err != nil {
  53.  
    log.Fatal(err)
  54.  
    }
  55.  
    rows, err := rs.RowsAffected()
  56.  
    if err != nil {
  57.  
    log.Fatal(err)
  58.  
    }
  59.  
    return rows
  60.  
    }
  61.  
     
  62.  
    //删除一条记录
  63.  
    func Delete(id int) int64 {
  64.  
    rs, err := db.SqlDB.Exec("delete from users where id = ?", id)
  65.  
    if err != nil {
  66.  
    log.Fatal()
  67.  
    }
  68.  
    rows, err := rs.RowsAffected()
  69.  
    if err != nil {
  70.  
    log.Fatal()
  71.  
    }
  72.  
    return rows
  73.  
    }
学新通

main.go

  1.  
    package main
  2.  
     
  3.  
    import "gin_restful/db"
  4.  
     
  5.  
    // go mod init xx_project
  6.  
    // go build
  7.  
    // ./xx_project
  8.  
    func main() {
  9.  
    defer db.SqlDB.Close()
  10.  
    router := initRouter()
  11.  
    router.Run(":8806") // 启动服务了
  12.  
    }

router.go

  1.  
    package main
  2.  
     
  3.  
    import (
  4.  
    . "gin_restful/api"
  5.  
     
  6.  
    "github.com/gin-gonic/gin"
  7.  
    )
  8.  
     
  9.  
    func initRouter() *gin.Engine {
  10.  
    router := gin.Default()
  11.  
    router.GET("/", IndexUsers) //http://192.168.204.132:8806
  12.  
     
  13.  
    //路由群组
  14.  
    users := router.Group("api/v1/users")
  15.  
    {
  16.  
    users.GET("", GetAll) //http://192.168.204.132:8806/api/v1/users
  17.  
    users.POST("/add", AddUsers) //http://192.168.204.132:8806/api/v1/users/add
  18.  
    users.GET("/get/:id", GetOne) //http://192.168.204.132:8806/api/v1/users/get/5
  19.  
    users.POST("/update", UpdateUser) //http://192.168.204.132:8806/api/v1/users/update
  20.  
    users.POST("/del", DelUser) //http://192.168.204.132:8806/api/v1/users/del
  21.  
    }
  22.  
     
  23.  
    departments := router.Group("api/v1/department")
  24.  
    {
  25.  
    departments.GET("", GetAll) //http://192.168.204.132:8806/api/v1/users
  26.  
    departments.POST("/add", AddUsers) //http://192.168.204.132:8806/api/v1/users/add
  27.  
    departments.GET("/get/:id", GetOne) //http://192.168.204.132:8806/api/v1/users/get/5
  28.  
    departments.POST("/update", UpdateUser) //http://192.168.204.132:8806/api/v1/users/update
  29.  
    departments.POST("/del", DelUser) //http://192.168.204.132:8806/api/v1/users/del
  30.  
    }
  31.  
     
  32.  
    return router
  33.  
    }
学新通

8.1 gin mysql rest full api –增

学新通

8.2 gin mysql rest full api –改

http://192.168.204.132:8806/api/v1/users/update

8.3 gin mysql rest full api –查

http://192.168.204.132:8806/api/v1/users/get/5

8.4 gin mysql rest full api –获取所有

http://192.168.204.132:8806/api/v1/users

8.5 gin mysql rest full api –删除 

代码仓库

github.com/yunixiangfeng/gin_restful

2.3 GO微信后台开发实战

微信公众号号后台开发

代码仓库

github.com/yunixiangfeng/devops/tree/main/wechat

1 微信公众号开发逻辑

1.1 注册公众号

注册地址: https://mp.weixin.qq.com/cgi-bin/registermidpage?action=index&lang=zh_CN&token=

学新通

1.2 开发者权限

进入公众号管理页面,下拉左边侧

学新通

1.3 微信公众号后台接口权限

普通用户只要是接收消息和自动回复消息的权限
学新通

1.4 公众号消息回复

学新通

学新通

1.5 服务器配置

学新通

2 HTTP服务

我们先使用原生的http接口来处理,后续改用gin来处理

我们这里主要处理Get和Post方法,见代码

Get:处理token验证 处理token的验证

Post:处理消息回复 处理消息

工程: wechat

学新通

main.go

  1.  
    package main
  2.  
     
  3.  
    import (
  4.  
    "fmt"
  5.  
    "log"
  6.  
    "net/http"
  7.  
    "time"
  8.  
    "wechat/wx"
  9.  
    )
  10.  
     
  11.  
    const (
  12.  
    logLevel = "dev"
  13.  
    port = 80
  14.  
    token = "NmHrEBBrbIX24JFw" // 生成地址:https://suijimimashengcheng.51240.com/
  15.  
    )
  16.  
     
  17.  
    // 处理token的认证
  18.  
    func get(w http.ResponseWriter, r *http.Request) {
  19.  
     
  20.  
    client, err := wx.NewClient(r, w, token)
  21.  
     
  22.  
    if err != nil {
  23.  
    log.Println(err)
  24.  
    w.WriteHeader(403) // 校验失败
  25.  
    return
  26.  
    }
  27.  
     
  28.  
    if len(client.Query.Echostr) > 0 {
  29.  
    w.Write([]byte(client.Query.Echostr)) // 校验成功返回的是Echostr
  30.  
    return
  31.  
    }
  32.  
     
  33.  
    w.WriteHeader(403)
  34.  
    return
  35.  
    }
  36.  
     
  37.  
    // 微信平台过来消息, 处理 ,然后返回微信平台
  38.  
    func post(w http.ResponseWriter, r *http.Request) {
  39.  
     
  40.  
    client, err := wx.NewClient(r, w, token)
  41.  
     
  42.  
    if err != nil {
  43.  
    log.Println(err)
  44.  
    w.WriteHeader(403)
  45.  
    return
  46.  
    }
  47.  
    // 到这一步签名已经验证通过了
  48.  
    client.Run()
  49.  
    return
  50.  
    }
  51.  
     
  52.  
    // 编译方法
  53.  
    // go mod init wechat
  54.  
    // go build
  55.  
    // ./wechat
  56.  
    // 需要自己修改token,以适应自己公众号的token
  57.  
    func main() {
  58.  
    server := http.Server{
  59.  
    Addr: fmt.Sprintf(":%d", port), // 设置监听地址, ip:port
  60.  
    Handler: &httpHandler{}, // 用什么handler来处理
  61.  
    ReadTimeout: 5 * time.Second, // 读写超时 微信给出来5
  62.  
    WriteTimeout: 5 * time.Second,
  63.  
    MaxHeaderBytes: 0,
  64.  
    }
  65.  
     
  66.  
    log.Println(fmt.Sprintf("Listen: %d", port))
  67.  
    log.Fatal(server.ListenAndServe())
  68.  
    defer CloseLog()
  69.  
    }
学新通

route.go

  1.  
    package main
  2.  
     
  3.  
    import (
  4.  
    "io"
  5.  
    "net/http"
  6.  
    "regexp"
  7.  
    "time"
  8.  
    )
  9.  
     
  10.  
    type WebController struct {
  11.  
    Function func(http.ResponseWriter, *http.Request)
  12.  
    Method string
  13.  
    Pattern string
  14.  
    }
  15.  
     
  16.  
    var mux []WebController // 自己定义的路由
  17.  
    // ^ 匹配输入字符串的开始位置
  18.  
    func init() {
  19.  
    mux = append(mux, WebController{post, "POST", "^/"})
  20.  
    mux = append(mux, WebController{get, "GET", "^/"})
  21.  
    }
  22.  
     
  23.  
    type httpHandler struct{} // 实际是实现了Handler interface
  24.  
    // type Handler interface {
  25.  
    // ServeHTTP(ResponseWriter, *Request)
  26.  
    // }
  27.  
     
  28.  
    func (*httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  29.  
     
  30.  
    t := time.Now()
  31.  
     
  32.  
    for _, webController := range mux { // 遍历路由
  33.  
    // 匹配请求的 r.URL.Path -> webController.Pattern
  34.  
    if m, _ := regexp.MatchString(webController.Pattern, r.URL.Path); m { // 匹配URL
  35.  
     
  36.  
    if r.Method == webController.Method { // 匹配方法
  37.  
     
  38.  
    webController.Function(w, r) // 调用对应的处理函数
  39.  
     
  40.  
    go writeLog(r, t, "match", webController.Pattern)
  41.  
     
  42.  
    return
  43.  
    }
  44.  
    }
  45.  
    }
  46.  
     
  47.  
    go writeLog(r, t, "unmatch", "")
  48.  
     
  49.  
    io.WriteString(w, "")
  50.  
    return
  51.  
    }
学新通

log.go

  1.  
    package main
  2.  
     
  3.  
    import (
  4.  
    "fmt"
  5.  
    "io"
  6.  
    "log"
  7.  
    "net/http"
  8.  
    "os"
  9.  
    "time"
  10.  
    )
  11.  
     
  12.  
    var LogFile *os.File
  13.  
     
  14.  
    func init() {
  15.  
    // fmt.Println("log init")
  16.  
    // LogFile, err := os.OpenFile("wechat.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) //打开日志文件,不存在则创建
  17.  
    // if err != nil {
  18.  
    // fmt.Println(err)
  19.  
    // }
  20.  
    // log.SetOutput(LogFile)
  21.  
    log.SetFlags(log.LstdFlags | log.Lshortfile)
  22.  
    }
  23.  
     
  24.  
    func CloseLog() {
  25.  
    if LogFile != nil {
  26.  
    LogFile.Close()
  27.  
    }
  28.  
     
  29.  
    }
  30.  
     
  31.  
    func writeLog(r *http.Request, t time.Time, match string, pattern string) {
  32.  
     
  33.  
    if logLevel != "prod" {
  34.  
     
  35.  
    d := time.Now().Sub(t)
  36.  
     
  37.  
    l := fmt.Sprintf("[ACCESS] | % -10s | % -40s | % -16s | % -10s | % -40s |", r.Method, r.URL.Path, d.String(), match, pattern)
  38.  
     
  39.  
    log.Println(l)
  40.  
    }
  41.  
    }
  42.  
     
  43.  
    func func_log2fileAndStdout() {
  44.  
    //创建日志文件
  45.  
    f, err := os.OpenFile("test.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
  46.  
    if err != nil {
  47.  
    log.Fatal(err)
  48.  
    }
  49.  
    //完成后,延迟关闭
  50.  
    defer f.Close()
  51.  
    // 设置日志输出到文件
  52.  
    // 定义多个写入器
  53.  
    writers := []io.Writer{
  54.  
    f,
  55.  
    os.Stdout}
  56.  
    fileAndStdoutWriter := io.MultiWriter(writers...)
  57.  
    // 创建新的log对象
  58.  
    logger := log.New(fileAndStdoutWriter, "", log.Ldate|log.Ltime|log.Lshortfile)
  59.  
    // 使用新的log对象,写入日志内容
  60.  
    logger.Println("--> logger : check to make sure it works")
  61.  
    }
学新通

 LICENSE

  1.  
    GNU GENERAL PUBLIC LICENSE
  2.  
    Version 3, 29 June 2007
  3.  
     
  4.  
    Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
  5.  
    Everyone is permitted to copy and distribute verbatim copies
  6.  
    of this license document, but changing it is not allowed.
  7.  
     
  8.  
    Preamble
  9.  
     
  10.  
    The GNU General Public License is a free, copyleft license for
  11.  
    software and other kinds of works.

wx\structs.go

  1.  
    package wx
  2.  
     
  3.  
    import (
  4.  
    "encoding/xml"
  5.  
    "strconv"
  6.  
    "time"
  7.  
    )
  8.  
     
  9.  
    type Base struct {
  10.  
    FromUserName CDATAText
  11.  
    ToUserName CDATAText
  12.  
    MsgType CDATAText
  13.  
    CreateTime CDATAText
  14.  
    }
  15.  
     
  16.  
    func (b *Base) InitBaseData(w *WeixinClient, msgtype string) {
  17.  
     
  18.  
    b.FromUserName = value2CDATA(w.Message["ToUserName"].(string))
  19.  
    b.ToUserName = value2CDATA(w.Message["FromUserName"].(string))
  20.  
    b.CreateTime = value2CDATA(strconv.FormatInt(time.Now().Unix(), 10))
  21.  
    b.MsgType = value2CDATA(msgtype)
  22.  
    }
  23.  
     
  24.  
    type CDATAText struct {
  25.  
    Text string `xml:",innerxml"`
  26.  
    }
  27.  
     
  28.  
    type TextMessage struct {
  29.  
    XMLName xml.Name `xml:"xml"`
  30.  
    Base
  31.  
    Content CDATAText
  32.  
    }
学新通

wx\utils.go

  1.  
    package wx
  2.  
     
  3.  
    func value2CDATA(v string) CDATAText {
  4.  
    return CDATAText{"<![CDATA[" v "]]>"}
  5.  
    }

wx\wx.go

  1.  
    package wx
  2.  
     
  3.  
    import (
  4.  
    "crypto/sha1"
  5.  
    "encoding/xml"
  6.  
    "errors"
  7.  
    "fmt"
  8.  
    "io/ioutil"
  9.  
    "log"
  10.  
    "net/http"
  11.  
    "sort"
  12.  
     
  13.  
    "github.com/clbanning/mxj"
  14.  
    )
  15.  
     
  16.  
    type weixinQuery struct {
  17.  
    Signature string `json:"signature"`
  18.  
    Timestamp string `json:"timestamp"`
  19.  
    Nonce string `json:"nonce"`
  20.  
    EncryptType string `json:"encrypt_type"`
  21.  
    MsgSignature string `json:"msg_signature"`
  22.  
    Echostr string `json:"echostr"`
  23.  
    }
  24.  
     
  25.  
    type WeixinClient struct {
  26.  
    Token string
  27.  
    Query weixinQuery // 请求的一些参数
  28.  
    Message map[string]interface{}
  29.  
    Request *http.Request
  30.  
    ResponseWriter http.ResponseWriter
  31.  
    Methods map[string]func() bool
  32.  
    }
  33.  
     
  34.  
    /// 请求数据Request, 返回数据ResponseWriter, token是自己的
  35.  
    func NewClient(r *http.Request, w http.ResponseWriter, token string) (*WeixinClient, error) {
  36.  
     
  37.  
    weixinClient := new(WeixinClient)
  38.  
     
  39.  
    weixinClient.Token = token // 获取本地的token
  40.  
    weixinClient.Request = r
  41.  
    weixinClient.ResponseWriter = w
  42.  
     
  43.  
    weixinClient.initWeixinQuery()
  44.  
    log.Println("Signature:", weixinClient.Query.Signature)
  45.  
    if weixinClient.Query.Signature != weixinClient.hashcode() { // 签名认证
  46.  
    return nil, errors.New("Invalid Signature.")
  47.  
    }
  48.  
     
  49.  
    return weixinClient, nil
  50.  
    }
  51.  
     
  52.  
    func (this *WeixinClient) initWeixinQuery() {
  53.  
     
  54.  
    var q weixinQuery
  55.  
    log.Println("URL:", this.Request.URL.Path, ", RawQuery:", this.Request.URL.RawPath)
  56.  
    q.Nonce = this.Request.URL.Query().Get("nonce")
  57.  
    q.Echostr = this.Request.URL.Query().Get("echostr")
  58.  
    q.Signature = this.Request.URL.Query().Get("signature")
  59.  
    q.Timestamp = this.Request.URL.Query().Get("timestamp")
  60.  
    q.EncryptType = this.Request.URL.Query().Get("encrypt_type")
  61.  
    q.MsgSignature = this.Request.URL.Query().Get("msg_signature")
  62.  
     
  63.  
    this.Query = q
  64.  
    }
  65.  
     
  66.  
    // 根据 Token Timestamp Nonce 生成对应的校验码, Token是不能明文传输的
  67.  
    func (this *WeixinClient) hashcode() string {
  68.  
     
  69.  
    strs := sort.StringSlice{this.Token, this.Query.Timestamp, this.Query.Nonce} // 使用本地的token生成校验
  70.  
    sort.Strings(strs)
  71.  
    str := ""
  72.  
    for _, s := range strs {
  73.  
    str = s
  74.  
    }
  75.  
    h := sha1.New()
  76.  
    h.Write([]byte(str))
  77.  
    return fmt.Sprintf("%x", h.Sum(nil))
  78.  
    }
  79.  
     
  80.  
    // 读取消息,解析XML
  81.  
    func (this *WeixinClient) initMessage() error {
  82.  
     
  83.  
    body, err := ioutil.ReadAll(this.Request.Body)
  84.  
     
  85.  
    if err != nil {
  86.  
    return err
  87.  
    }
  88.  
     
  89.  
    m, err := mxj.NewMapXml(body)
  90.  
     
  91.  
    if err != nil {
  92.  
    return err
  93.  
    }
  94.  
     
  95.  
    if _, ok := m["xml"]; !ok {
  96.  
    return errors.New("Invalid Message.")
  97.  
    }
  98.  
     
  99.  
    message, ok := m["xml"].(map[string]interface{})
  100.  
     
  101.  
    if !ok {
  102.  
    return errors.New("Invalid Field `xml` Type.")
  103.  
    }
  104.  
     
  105.  
    this.Message = message // 保存消息
  106.  
     
  107.  
    log.Println(this.Message)
  108.  
     
  109.  
    return nil
  110.  
    }
  111.  
     
  112.  
    func (this *WeixinClient) text() {
  113.  
     
  114.  
    inMsg, ok := this.Message["Content"].(string) // 读取内容
  115.  
     
  116.  
    if !ok {
  117.  
    return
  118.  
    }
  119.  
     
  120.  
    var reply TextMessage
  121.  
     
  122.  
    reply.InitBaseData(this, "text")
  123.  
    reply.Content = value2CDATA(fmt.Sprintf("我收到的是:%s", inMsg)) // 把消息再次封装
  124.  
     
  125.  
    replyXml, err := xml.Marshal(reply) // 序列化
  126.  
     
  127.  
    if err != nil {
  128.  
    log.Println(err)
  129.  
    this.ResponseWriter.WriteHeader(403)
  130.  
    return
  131.  
    }
  132.  
     
  133.  
    this.ResponseWriter.Header().Set("Content-Type", "text/xml") // 数据类型text/xml
  134.  
    this.ResponseWriter.Write(replyXml) // 回复微信平台
  135.  
    }
  136.  
     
  137.  
    func (this *WeixinClient) Run() {
  138.  
     
  139.  
    err := this.initMessage()
  140.  
     
  141.  
    if err != nil {
  142.  
     
  143.  
    log.Println(err)
  144.  
    this.ResponseWriter.WriteHeader(403)
  145.  
    return
  146.  
    }
  147.  
     
  148.  
    MsgType, ok := this.Message["MsgType"].(string)
  149.  
     
  150.  
    if !ok {
  151.  
    this.ResponseWriter.WriteHeader(403)
  152.  
    return
  153.  
    }
  154.  
     
  155.  
    switch MsgType {
  156.  
    case "text":
  157.  
    this.text() // 处理文本消息
  158.  
    break
  159.  
    default:
  160.  
    break
  161.  
    }
  162.  
     
  163.  
    return
  164.  
    }
学新通

.github\FUNDING.yml

  1.  
    # These are supported funding model platforms
  2.  
    # leeeboo
  3.  
    github: [wtlyy]

3 token机制

解析请求中的GET参数

微信公众号签名验证的方法

参考:

https://developers.weixin.qq.com/doc/offiaccount/Getting_Started/Getting_Started_Guide.html

源码:3-1-token.go

  1.  
    package main
  2.  
     
  3.  
    import (
  4.  
    "bytes"
  5.  
    "crypto/rand"
  6.  
    "crypto/sha1"
  7.  
    "fmt"
  8.  
    "math/big"
  9.  
    "sort"
  10.  
    "strconv"
  11.  
    "time"
  12.  
    )
  13.  
     
  14.  
    func CreateRandomString(len int) string {
  15.  
    var container string
  16.  
    var str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
  17.  
    b := bytes.NewBufferString(str)
  18.  
    length := b.Len()
  19.  
    bigInt := big.NewInt(int64(length))
  20.  
    for i := 0; i < len; i {
  21.  
    randomInt, _ := rand.Int(rand.Reader, bigInt)
  22.  
    container = string(str[randomInt.Int64()])
  23.  
    }
  24.  
    return container
  25.  
    }
  26.  
     
  27.  
    // 根据 Token Timestamp Nonce 生成对应的校验码, Token是不能明文传输的
  28.  
    func GenerateSignature(token string) (timestamp string, nonce string, signature string) {
  29.  
     
  30.  
    nonce = CreateRandomString(10)
  31.  
    timestamp = strconv.FormatInt(time.Now().Unix(), 10) //int64转字符串
  32.  
    // 排序 微信约定好的
  33.  
    strs := sort.StringSlice{token, timestamp, nonce} // 使用本地的token生成校验
  34.  
    sort.Strings(strs) // strs: [1607173019 qing qvCyrKEuoS]
  35.  
    fmt.Println("strs:", strs) // 排序
  36.  
    str := ""
  37.  
    for _, s := range strs {
  38.  
    str = s // 拼接字符串
  39.  
    }
  40.  
    fmt.Println("str:", str) //str: 1607173019qingqvCyrKEuoS
  41.  
    h := sha1.New() // 完全都是自己的服务的时候 你这里你用md5
  42.  
    h.Write([]byte(str)) // 转成byte
  43.  
    signature = fmt.Sprintf("%x", h.Sum(nil)) // h.Sum(nil) 做hash 79efadd80a344c0b73b3bd2c403184f7425a5a67
  44.  
    return
  45.  
    }
  46.  
     
  47.  
    func VerifySignature(token string, timestamp string, nonce string, signature string) bool {
  48.  
    // str = token timestamp nonce
  49.  
    strs := sort.StringSlice{token, timestamp, nonce} // 使用本地的token生成校验
  50.  
    sort.Strings(strs)
  51.  
    str := ""
  52.  
    for _, s := range strs {
  53.  
    str = s
  54.  
    }
  55.  
    h := sha1.New() // 完全都是自己的服务的时候 你这里你用md5
  56.  
    h.Write([]byte(str))
  57.  
    return fmt.Sprintf("%x", h.Sum(nil)) == signature
  58.  
    }
  59.  
    func main() {
  60.  
    token := "qing"
  61.  
    // 产生签名
  62.  
    timestamp, nonce, signature := GenerateSignature(token) // 发送服务器的时候是发送 timestamp, nonce, signature
  63.  
    fmt.Printf("1. token %s -> 产生签名:%s, timestamp:%s, nonce:%s\n", token, signature, timestamp, nonce)
  64.  
    // 验证签名
  65.  
    ok := VerifySignature(token, timestamp, nonce, signature) // 服务进行校验
  66.  
    if ok {
  67.  
    fmt.Println("2. 验证签名正常")
  68.  
    } else {
  69.  
    fmt.Println("2. 验证签名失败")
  70.  
    }
  71.  
    }
学新通

3.1 token算法

参考:

https://developers.weixin.qq.com/doc/offiaccount/Getting_Started/Getting_Started_Guide.html

按照字母排列顺序

参数 描述

signature

微信加密签名,signature结合了开

发者填写的token参数和请求中的

timestamp参数、nonce参数。

timestamp 时间戳

nonce 随机数

echostr

随机字符串

如果服务器校验成功,返回echostr

如果校验失败,返回””字符串

验证方法
1. 服务器端获取 token , nonce , timestamp
成列表
2. 列表排序
3. 排序后的元素进行摘要
4. 摘要比对 signature
5. 响应 echostr

3.2 token算法-流程图

验证方法

1.服务器端获取token,nonce,timestamp组

成列表

2.列表排序

3.排序后的元素进行摘要

4.摘要比对signature

5.响应echostr

参考:

https://developers.weixin.qq.com/doc/offiaccount/Getting_Started/Getting_Started_Guide.html

学新通

4 XML解析

微信消息采用 XML 进行封装,所以我们需要先学习 XML 内容解析
学新通

4.1 XML解析-解析XML

在代码里,先针对xml的格式,创建对应的struct结构体

4-1-xml.go

  1.  
    package main
  2.  
     
  3.  
    import (
  4.  
    "encoding/xml"
  5.  
    "fmt"
  6.  
    "io/ioutil"
  7.  
    "os"
  8.  
    )
  9.  
     
  10.  
    // 如果struct中有一个叫做XMLName,且类型为xml.Name字段,
  11.  
    // 那么在解析的时候就会保存这个element的名字到该字段, 比如这里的config
  12.  
    type SConfig struct {
  13.  
    XMLName xml.Name `xml:"config"` // 指定最外层的标签为config
  14.  
    SmtpServer string `xml:"smtpServer"` // 读取smtpServer配置项,并将结果保存到SmtpServer变量中
  15.  
    SmtpPort int `xml:"smtpPort"`
  16.  
    Sender string `xml:"sender"`
  17.  
    SenderPasswd string `xml:"senderPasswd"`
  18.  
    Receivers SReceivers `xml:"receivers"` // 读取receivers标签下的内容,以结构方式获取
  19.  
    }
  20.  
     
  21.  
    type SReceivers struct {
  22.  
    Age int `xml:"age"`
  23.  
    Flag string `xml:"flag,attr"` // 读取flag属性
  24.  
    User []string `xml:"user"` // 读取user数组
  25.  
    Script string `xml:"script"` // 读取 <![CDATA[ xxx ]]> 数据
  26.  
    }
  27.  
     
  28.  
    func main() {
  29.  
    file, err := os.Open("4-1-xml.xml") // For read access.
  30.  
    if err != nil {
  31.  
    fmt.Printf("error: %v", err)
  32.  
    return
  33.  
    }
  34.  
    defer file.Close()
  35.  
    data, err := ioutil.ReadAll(file)
  36.  
    if err != nil {
  37.  
    fmt.Printf("error: %v", err)
  38.  
    return
  39.  
    }
  40.  
    v := SConfig{}
  41.  
    err = xml.Unmarshal(data, &v) // 反序列化
  42.  
    if err != nil {
  43.  
    fmt.Printf("error: %v", err)
  44.  
    return
  45.  
    }
  46.  
     
  47.  
    fmt.Println("文本:", v)
  48.  
    fmt.Println("解析结果:")
  49.  
    fmt.Println("XMLName : ", v.XMLName)
  50.  
    fmt.Println("SmtpServer : ", v.SmtpServer)
  51.  
    fmt.Println("SmtpPort : ", v.SmtpPort)
  52.  
    fmt.Println("Sender : ", v.Sender)
  53.  
    fmt.Println("SenderPasswd : ", v.SenderPasswd)
  54.  
    fmt.Println("Receivers.Flag : ", v.Receivers.Flag)
  55.  
    for i, element := range v.Receivers.User {
  56.  
    fmt.Println(i, element)
  57.  
    }
  58.  
    }
学新通

4-1-xml.xml 

  1.  
    <config>
  2.  
    <smtpServer>smtp.qq.com</smtpServer>
  3.  
    <smtpPort>25</smtpPort>
  4.  
    <sender>you@qq.com</sender>
  5.  
    <senderPasswd>123456</senderPasswd>
  6.  
    <receivers flag="true">
  7.  
    <user>ki@qq.gom</user>
  8.  
    <user>dar@q.gom</user>
  9.  
    <script>
  10.  
    <![CDATA[
  11.  
    function &%< matchwo(a,b) {
  12.  
    if (a < b && a < 0) then {
  13.  
    return 1;
  14.  
    } else {
  15.  
    return 0;
  16.  
    }
  17.  
    }
  18.  
    ]]>
  19.  
    </script>
  20.  
    </receivers>
  21.  
    </config>
学新通

4.2 XML解析-解析CDATA

XML 文档中的所有文本均会被解析器解析。

只有 CDATA 区段中的文本会被解析器忽略。

术语 CDATA 是不应该由 XML 解析器解析的文本数据。

像 "<" 和 "&" 字符在 XML 元素中都是非法的。

"<" 会产生错误,因为解析器会把该字符解释为新元素的开始。

"&" 会产生错误,因为解析器会把该字符解释为字符实体的开始。

某些文本,比如 JavaScript 代码,包含大量 "<" 或 "&" 字符。为了避免错误,可以将脚本代码定义为 CDATA。

CDATA 部分中的所有内容都会被解析器忽略。

CDATA 部分由 “ <![CDATA[ " 开始,由 "]]>" 结束:

4-2-CDATA.go

  1.  
    package main
  2.  
     
  3.  
    import (
  4.  
    "encoding/xml"
  5.  
    "fmt"
  6.  
    "strconv"
  7.  
    "time"
  8.  
     
  9.  
    "github.com/clbanning/mxj"
  10.  
    )
  11.  
     
  12.  
    // tag中含有"-"的字段不会输出
  13.  
    // tag中含有"name,attr",会以name作为属性名,字段值作为值输出为这个XML元素的属性,如上version字段所描述
  14.  
    // tag中含有",attr",会以这个struct的字段名作为属性名输出为XML元素的属性,类似上一条,只是这个name默认是字段名了。
  15.  
    // tag中含有",chardata",输出为xml的 character data而非element。
  16.  
    // tag中含有",innerxml",将会被原样输出,而不会进行常规的编码过程
  17.  
    // tag中含有",comment",将被当作xml注释来输出,而不会进行常规的编码过程,字段值中不能含有"--"字符串
  18.  
    // tag中含有"omitempty",如果该字段的值为空值那么该字段就不会被输出到XML,空值包括:false、0、nil指针或nil接口,任何长度为0的array, slice, map或者string
  19.  
     
  20.  
    type CDATAText struct {
  21.  
    Text string `xml:",innerxml"`
  22.  
    }
  23.  
     
  24.  
    type Base struct {
  25.  
    FromUserName CDATAText
  26.  
    ToUserName CDATAText
  27.  
    MsgType CDATAText
  28.  
    CreateTime CDATAText
  29.  
    }
  30.  
     
  31.  
    // 文本消息的封装
  32.  
    type TextMessage struct {
  33.  
    XMLName xml.Name `xml:"xml"`
  34.  
    Base
  35.  
    Content CDATAText
  36.  
    }
  37.  
     
  38.  
    // 图片消息的封装
  39.  
    type PictureMessage struct {
  40.  
    XMLName xml.Name `xml:"xml"`
  41.  
    Base
  42.  
    PicUrl CDATAText
  43.  
    MediaId CDATAText
  44.  
    }
  45.  
     
  46.  
    func value2CDATA(v string) CDATAText {
  47.  
    return CDATAText{"<![CDATA[" v "]]>"}
  48.  
    }
  49.  
     
  50.  
    func main() {
  51.  
    // 1. 解析 XML
  52.  
    xmlStr := `<xml>
  53.  
    <ToUserName><![CDATA[toUser]]></ToUserName>
  54.  
    <FromUserName><![CDATA[fromUser]]></FromUserName>
  55.  
    <CreateTime>1348831860</CreateTime>
  56.  
    <MsgType><![CDATA[text]]></MsgType>
  57.  
    <Content><![CDATA[this is a test]]></Content>
  58.  
    <MsgId>1234567890123456</MsgId>
  59.  
    </xml>`
  60.  
     
  61.  
    var Message map[string]interface{}
  62.  
    m, err := mxj.NewMapXml([]byte(xmlStr)) //使用了第三方的库
  63.  
     
  64.  
    if err != nil {
  65.  
    return
  66.  
    }
  67.  
     
  68.  
    if _, ok := m["xml"]; !ok {
  69.  
    fmt.Println("Invalid Message.")
  70.  
    return
  71.  
    }
  72.  
    fmt.Println("-->m:", m)
  73.  
    message, ok := m["xml"].(map[string]interface{}) // 把xml对应的值读取出来
  74.  
     
  75.  
    if !ok {
  76.  
    fmt.Println("Invalid Field `xml` Type.")
  77.  
    return
  78.  
    }
  79.  
     
  80.  
    Message = message
  81.  
     
  82.  
    fmt.Println("1. 解析出来:", Message) // xml对应的字段还是在map
  83.  
     
  84.  
    // 2. 封装XML
  85.  
    var reply TextMessage
  86.  
    inMsg, ok := Message["Content"].(string) // 读取内容 .(string)转成什么类型的数据
  87.  
     
  88.  
    if !ok {
  89.  
    return
  90.  
    }
  91.  
    fmt.Println("Message[ToUserName].(string):", Message["ToUserName"].(string)) // 如果服务器要处理
  92.  
     
  93.  
    // 封装回复消息,需要添加 CDATA
  94.  
    reply.Base.FromUserName = value2CDATA(Message["ToUserName"].(string))
  95.  
    reply.Base.ToUserName = value2CDATA(Message["FromUserName"].(string))
  96.  
    reply.Base.CreateTime = value2CDATA(strconv.FormatInt(time.Now().Unix(), 10))
  97.  
    reply.Base.MsgType = value2CDATA("text")
  98.  
    reply.Content = value2CDATA(fmt.Sprintf("我收到的是:%s", inMsg))
  99.  
     
  100.  
    replyXml, err := xml.Marshal(reply) // 得到的是byte
  101.  
    fmt.Println("2. 生成XML:", string(replyXml)) // []byte -> string
  102.  
    fmt.Println("2. 生成XML:", []byte(string(replyXml))) // string -> []byte
  103.  
    }
学新通

<xml> <ToUserName><![CDATA[toUser]]></ToUserName>

<FromUserName><![CDATA[fromUser]]></FromUserName>

<CreateTime>1348831860</CreateTime>

<MsgType><![CDATA[text]]></MsgType>

<Content><![CDATA[this is a test]]></Content>

<MsgId>1234567890123456</MsgId> </xml>

学新通

5 你问我答

1)理解被动消息的含义 2)理解收\发消息机制 预实现功能: 粉丝给公众号一条文本消息,

公众号立马回复一条文本消息给粉丝,不需要通过公众平台网页操作。

学新通

 学新通

学新通

5.1 你问我答-接收消息协议

参考:

https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_standar

d_messages.html

参数 描述

ToUserName 开发者微信号

FromUserName 发送方帐号(一个OpenID)

CreateTime 消息创建时间 (整型)

MsgType 消息类型,文本为text

Content 文本消息内容

MsgId 消息id,64位整型

<xml> <ToUserName><![CDATA[toUser]]></ToUserName>

<FromUserName><![CDATA[fromUser]]></FromUserName>

<CreateTime>1348831860</CreateTime>

<MsgType><![CDATA[text]]></MsgType>

<Content><![CDATA[this is a test]]></Content>

<MsgId>1234567890123456</MsgId> </xml>

5.2 你问我答-被动回复消息协议

参考:

https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Passive_user_reply

_message.html#0

参数 是否必须 描述

ToUserName 是 接收方帐号(收到的OpenID)

FromUserName 是 开发者微信号

CreateTime 是 消息创建时间 (整型)

MsgType 是 消息类型,文本为text

Content 是

回复的消息内容(换行:在

content中能够换行,微信客户端

就支持换行显示)

6 go语言之进阶篇正则表达式

参考官网: https://studygolang.com/pkgdoc

范例:https://www.cnblogs.com/nulige/p/10260149.html

3.1 流媒体知识精讲和架构设计

1.1 直播应用场景

学新通

1.2 常用直播功能项 常用

学新通

1.3 直播框架示例1

学新通1.4 直播框架示例2-某直播学院框架 

学新通2 直播架构-基本逻辑 

学新通

2.0 常见流媒体协议 直播流程

RTP实时传输协议(Real-time Transport Protocol或简写RTP)

RTCP RTP Control Protocol

RTSP (Real Time Streaming Protocol),RFC2326,实时流传输协议

RTMP RTMP是Real Time Messaging Protocol(实时消息传输协议)

HTTP-FLV

HTTP-MP4

HLS

WebRTC

2.1 直播架构-基本流程 软件编码–提高机器的兼容性

学新通

2.2 直播常用工具

◼ 推流工具:

• ffmpeg:https://www.ffmpeg.org/download.html

• OBS studio:https://obsproject.com/download

◼ 拉流工具

• ffplay(): https://www.ffmpeg.org/download.html

• cutv www.cutv.com/demo/live_test.swf flash播放器

• vlc

• ijkplayer (基于ffplay): 一个基于FFmpeg的开源Android/iOS视频播放器

(开源)

API易于集成;

编译配置可裁剪,方便控制安装包大小;

支持硬件加速解码,更加省电

简单易用,指定拉流URL,自动解码播放.

◼ 压测工具

• st-load

2.3 流媒体服务器

SRS :一款国人开发的优秀开源流媒体服务器系统

BMS :也是一款流媒体服务器系统,但不开源,是SRS的商业版,

比SRS功能更多

nginx :免费开源web服务器,也常用来配置流媒体服务器。

集成Rtmp_module即可。

Red5:是java写的一款稳定的开源的rtmp服务器。

3 直播框架之CDN

学新通4 拉流框架 

1. 模块初始化顺序

2. 音视频数据队列(packetqueue)控制

3. 音视频解码

4. 音频重采样

5. 视频尺寸变换

6. 音视频帧队列

7. 音视频同步

8. 关键时间点check

9. 其他

学新通

4.1 模块初始化顺序

推流模块(网络连接耗时) > 音视频编码模块 >音视频采集模块()

音视频输出模块 >音视频解码模块 > 拉流模块

本质上来讲,就是在数据到来之前准备好一切工作

4.2 音视频数据队列

音视频队列涉及到

1. Audio PacketQueue 还没有解码的

2. Video PacketQueue

两者独立

队列设计要点:

1. 可控制队列大小

1. 根据packet数进行控制

2. 根据总的duration进行控制 音频48khz, 21.3毫秒, 21.3*20 = 426ms

3. 支持packet的size进行入队列累加,出队列则减, 300,200,400, 字节累加

2. 支持packet数量统计

3. 支持packet的duration进行入队列累加,出队列则减

4. 支持阻塞和唤醒

目的:

1. 统计延迟(缓存时间长度)

4.3 音视频数据队列

音视频队列涉及到

1. Audio PacketQueue

2. Video PacketQueue

两者独立

队列设计要点:

1. 可控制队列大小

1. 根据packet数进行控制

2. 根据总的duration进行控制

2. 支持packet数量统计

3. 支持packet的size进行入队列累加,出队列则减

4. 支持packet的duration进行入队列累加,出队列则

5. 支持阻塞和唤醒

4.4 音视频解码

关键点:

1. 编码前: dts

2. 编码后: pts

3. packet释放

4. frame释放

5. 返回值处理

4.5 音频重采样

音频重采样模块AudioResampler:

注意重采样后不能直接使用linesize进行大小判断,需要使用

int size = av_get_bytes_per_sample((AVSampleFormat)(dstframe->format))

* dstframe->channels * dstframe->nb_samples ;

 4.6 视频尺寸变换

图像尺寸变换模块ImageScaler:变换尺寸大小

性能的提升可以考虑 libyuv

4.7 音视频解码后帧队列

FrameQueue

解码后的数据量比较大,需要要控制解码后帧队列的大小

参考ffplay固定大小

#define VIDEO_PICTURE_QUEUE_SIZE 3 // 图像帧缓存数量

#define SUBPICTURE_QUEUE_SIZE 16 // 字幕帧缓存数量

#define SAMPLE_QUEUE_SIZE 9 // 采样帧缓存数量

#define FRAME_QUEUE_SIZE FFMAX(SAMPLE_QUEUE_SIZE,

FFMAX(VIDEO_PICTURE_QUEUE_SIZE, SUBPICTURE_QUEUE_SIZE))

学新通

4.8 音视频同步

Avsync模块

目前只支持audio master的方式。

4.9 各个模块关键时间点的监测

学新通

4.10 其他

1. 客户端的首帧秒开,本质上就是不做同步先把第一帧显示出来。

2. 推流没有问题时,如果拉流不能正常播放:

1. 没有声音:dump rtmp拉流后的数据是否可以正常播放

2. 声音异常:是否有解码错误报告,重采样前的pcm数据是否正常

3. 没有图像: dump rtmp拉流后的数据是否可以正常播放

4. 画面异常:是否有解码错误报告,scale前的数据是否正常

服务器首帧秒开:这个功能不能降低延迟

5 直播推流框架-模块初始化顺序

推流模块(网络连接耗时) > 音视频编码模块 >音视频采集模块()

 学新通

5.1 采集时间戳-帧间隔模式

5.2 采集时间戳-直接系统时间模式

5.3 采集时间戳-帧间隔 直接系统时间模式

5.4 音视频编解码模块

5.5 音视频队列的控制

5.6 关键时间点

5.7 其他

6 WebRTC

信令服务器由go语言实现

搭建自己的音视频通话web

WebRTC简介

WebRTC通话模型

WebRTC通话模型 Mesh一对一通话网络模型

WebRTC通话模型 Mesh多方通话网络模型

WebRTC Mesh 网络拓扑结构的优劣

WebRTC通话模型 SFU通话网络模型

WebRTC通话模型 MCU通话网络模型

WebRTC 通话网络模型选择

WebRTC建构多人会议系统

WebRTC应用领域

基于webrtc的开源方案

国内音视频通话方案公司

WebRTC开发进阶-SFU级联

学习资源

WebRTC视频通话中最多能容纳多少用户? https://www.jianshu.com/p/9ef708f93499

多媒体开发 https://www.jianshu.com/c/e5b30935c054

WebRTC中文网 https://webrtc.org.cn

WebRTC官网 https://webrtc.org/

WebRTC范例 https://webrtc.github.io/samples/

AppRTC基本原理

AppRTC Demo搭建注意事项

WebRTC通话信令基本设计 – 媒体协商 网络信息candidate 房间人员

管理

1对1通话信令分析

一对一通话实战复习

多方通话

逻辑分析

3.2 工程代码-apidefs结构体定义

代码仓库

github.com/yunixiangfeng/devops/tree/main/video_server

2 架构分析和API设计

1. 技术要点分析

go流媒体网站技术要点
前后端分离的系统架构设计
RESTful 风格 API 设计与实现
Go 实现 web 服务
系统的服务化解耦
go channel 和并发模型的实践
使用 go 原生 template 完成 web UI 的实现
总体架构
学新通

什么是前后端解耦

◼ 前后端解耦是时下流行的Web网站架构

◼ 前端页面和服务通过普通的Web引擎渲染

◼ 后端数据通过渲染后的脚本调用后处理呈现

前后端解耦的优势

◼ 解放生产力

◼ 松耦合的架构更灵活,部署更方便,更符合微服务的设计特性

◼ 性能的提升、可靠性的提升

前后端解耦的缺点

◼ 工作量大

◼ 前后端分离带来团队成本以及学习成本

◼ 系统复杂度加大

2. REST API设计

API

◼ REST是Representational State Transfer(表现层状态转移)的缩写

◼ 常用的行为(查看(view),创建(create),编辑(edit)和删除(delete))

都可以直接映射到HTTP 中已实现的GET,POST,PUT和DELETE方法。

◼ 通常使用Json作为数据封装格式

◼ 统一接口

◼ 无状态

◼ 可缓存

API设计原则

◼ 以URL(统一资源定位符)风格设计API

◼ 通过不同的Method(GET/POST/PUT/DELETE)来区分对资源的CURD

◼ 返回码(Status code)符合HTTP资源描述的规定

3. API设计实战

API设计

学新通

API设计:用户

学新通API设计:视频 

学新通API设计:评论  

学新通数据库设计-用户 

CREATE TABLE `video_server`.`users` (
`id` int unsigned primary key auto_increment,
`login_name` varchar(64) unique key,
`pwd` text
);
数据库设计- 视频
CREATE TABLE `video_server`.`video_info` (
`id` varchar(64) NOT NULL,
`author_id` int(10) NULL,
`name` text NULL,
`display_ctime` text NULL,
`create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
);
数据库设计- 评论
CREATE TABLE `video_server`.`comments` (
`id` varchar(64) NOT NULL,
`video_id` varchar(64) NULL,
`author_id` int(10) NULL,
`content` text NULL,
`time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
);

数据库设计-会话

CREATE TABLE `video_server`.`sessions` (

`session_id` varchar(244) NOT NULL,

`TTL` tinytext NULL,

`login_name` text NULL,

PRIMARY KEY (`session_id`)

);

数据库设计-待删除视频表

CREATE TABLE `video_server`.`video_del_rec` (

`video_id` varchar(64) NOT NULL,

PRIMARY KEY (`video_id`)

);

4. 端口开放

端口开放

◼ api 10000

◼ scheduler 10001

◼ streamserver 1002

◼ web 10003

代码仓库

github.com/yunixiangfeng/video_server 

3.3 stream-scheduler-web详细设计

1. streamserver设计

0 总体架构
学新通

1 Streamserver

◼ 静态视频,

◼ 独立的服务,可独立部署

◼ 统一的API格式

1.1 Stream Server-对外接口

◼ /videos/:vid-id -> streamHandler 文件播放

◼ /upload/:vid-id -> uploadHandler 文件上传

1.2 代码整体设计

◼ 流控机制

◼ middleware的作用

1.3 流控机制-token bucket
为什么需要流控
拿到 token 才能继续进一步处理
为什么不用数组
go routine 是并发的,如果用变量则需要加锁
go 处理并发用 channel
limiter.go

1.4 在http middleware加入流控

type middleWareHandler struct {

r *httprouter.Router

l *ConnLimiter

}

func NewMiddleWareHandler(r *httprouter.Router,

cc int) http.Handler {

m := middleWareHandler{}

m.r = r

m.l = NewConnLimiter(cc) // 限制数量

return m

}

1.5 stream handler的实现

◼ streamHandler 读取文件播放

◼ uploadHandler 上传文件

2. scheduler设计

2 Scheduler调度器

◼ 什么是scheduler

◼ 为什么需要scheduler

◼ scheduler通常做什么

异步任务、延时任务、定时任务

2.1 Scheduler包含什么

◼ REST ful 的HTTP server

◼ Timer

◼ 生产者消费者模型下的task runner

2.2 Scheduler架构

学新通

2.3 代码架构

◼ dbops 数据库查询和删除

◼ taskrunner 执行任务

◼ runner.go 处理任务流程(生产消费模型)

◼ tasks.go 执行任务(具体的生产、消费)

◼ trmain.go 实现定时任务,比如每3秒执行一次

◼ handlers.go 处理api

◼ main.go程序入口

◼ response.go http响应封装

2.4 task实现

type Runner struct {

Controller controlChan

Error controlChan

Data dataChan

dataSize int

longLived bool

Dispatcher fn

Executor fn

}

◼ Controller 流程控制channel

◼ Error 错误控制channel

◼ Data 真正的任务数据channel

runner.go
tasks.go

2.5 timer实现

trmain.go

type Worker struct {

ticker *time.Ticker

runner *Runner

}

通过定时器实现定时任务

学新通

3. web设计

3 web前端服务

◼ Go模板引擎

◼ API处理

◼ API透传

◼ proxy代理

3.0 代码架构

◼ templates html模板

◼ client.go 处理api透传

◼ defs.go 结构体定义

◼ handlers.go api入口处理函数

◼ main.go 主入口

3.1 Go的模板引擎

◼ 模板引擎是将HTML解析和元素预置替换生成最终页面的工具

◼ Go的模板有两种text/template和html/template

◼ Go的模板采用动态生成的模式

学新通

3.2 Go的模板引擎-渲染流程

学新通

3.3 页面渲染

◼ 主页渲染:homeHandler

◼ 用户页渲染:userHomeHandler

3.4 api透传模块实现

◼ apiHandler 处理逻辑分析

3.5 proxy转发的实现

◼ proxyHandler处理逻辑非分析

代码仓库

github.com/yunixiangyu/devops

4.1 Gin和jwt验证实战

代码仓库

github.com/yunixiangyu/devops/tree/main/gin_practice

gin实战

N ⼊⻔

O RESTful API

结构体

基本的REST ful范例

路由参数

:路由

*路由

P URL查询参数

Gin获取查询参数

原理解析

Q 接收数组和 Map

QueryArray

QueryMap

QueryMap 的原理

T 表单参数

Form 表单

Gin 接收表单数据

PostFormArray()⽅法获取表单参数

Gin PostForm系列⽅法

实现原理

⼩结

T 上传⽂件

上传单个⽂件FormFile

上传多个⽂件MultipartForm

V 分组路由

分组路由

路由中间件

分组路由嵌套

原理解析

GIn中间件

Gin默认中间件

中间件实现HTTP Basic Authorization

针对特定URL的Basic Authorization

⾃定义中间件

V 再谈中间件

定义中间件

⼊⻔案例

注册中间件

为全局路由注册

为某个路由单独注册

为路由组注册中间件

跨中间件存取值

中间件注意事项

gin中间件中使⽤goroutine

gin框架中间件c.Next()理解

W json、struct、xml、yaml、protobuf渲染

各种数据格式的响应

范例

X HTML模板渲染

最简单的例⼦

复杂点的例⼦

静态⽂件⽬录

重定向

NL 异步协程

NN Gin源码简要分析

概述

从DEMO开始

ENGINE

ROUTERGROUP & METHODTREE

.路由注册

路由分组

.中间件挂载

.路由匹配

HANDLERFUNC

CONTEXT

.调⽤链流转和控制

.参数解析

.响应处理

总结

参考⽂献

官⽅⽹站

https://gin-gonic.com/

⼯程代码

https://github.com/gin-gonic/gin.git

测试范例

https://github.com/gin-gonic/examples.git

中间件

https://github.com/gin-gonic/contrib.git

gin框架-JWT验证实践

N token、cookie、session的区别

Cookie

Session

Token

O Json-Web-Token(JWT)介绍

JWT Token组成部分

签名的⽬的

什么时候⽤JWT

JWT(Json Web Tokens)是如何⼯作的

P 基于Token的身份认证和基于服务器的身份认证

N.基于服务器的认证

O.Session和JWT Token的异同

P.基于Token的身份认证如何⼯作

Q.⽤Token的好处

S.JWT和OAuth的区别

Q Go范例

S JWT资源

T 使⽤Gin框架集成JWT

⾃定义中间件

定义jwt编码和解码逻辑

定义登陆验证逻辑

定义普通待验证接⼝

验证使⽤JWT后的接⼝

V 使⽤go进⾏ JWT 验证

使⽤ JWT 的场景

JWT 的结构

总结

4.2 Go ORM实战

 代码仓库 github.com/yunixiangfeng/devops/tree/main/jwt-gorm

GORM实践

L 什么是ORM?为什么要⽤ORM?

N GORM⼊⻔指南

gorm介绍

安装

连接MySQL

GORM基本示例

GORM操作MySQL

O GORM Model定义

gorm.Model

模型定义示例

结构体标记(tags)

⽀持的结构体标记(Struct tags)

关联相关标记(tags)

范例

P 主键、表名、列名的约定

主键(Primary Key)

表名(Table Name)

列名(Column Name)

时间戳跟踪

CreatedAt

UpdatedAt

DeletedAt

Q CRUD

创建

创建记录

默认值

使⽤指针⽅式实现零值存⼊数据库

使⽤Scanner/Valuer接⼝⽅式实现零值存⼊数据库

扩展创建选项

查询

⼀般查询

Where 条件

普通SQL查询

Struct & Map查询

Not 条件

Or条件

内联条件

额外查询选项

FirstOrInit

Attrs

Assign

FirstOrCreate

Attrs

Assign

⾼级查询

⼦查询

选择字段

排序

数量

偏移

总数

Group & Having

连接

Pluck

扫描

链式操作相关

链式操作

⽴即执⾏⽅法

范围

多个⽴即执⾏⽅法

2

更新

更新所有字段

更新修改字段

更新选定字段

⽆Hooks更新

批量更新

使⽤SQL表达式更新

修改Hooks中的值

其它更新选项

删除

删除记录

批量删除

软删除

物理删除

S gorm-错误处理、事务、SQL构建、通⽤数据库接⼝、连接池、复合主键、⽇志

S.N. 错误处理

S.O. 事务

S.O.N. ⼀个具体的例⼦

S.P. SQL构建

S.P.N. 执⾏原⽣SQL

S.P.O. sql.Row & sql.Rows

S.P.P. 迭代中使⽤sql.Rows的Scan

S.Q. 通⽤数据库接⼝sql.DB

S.Q.N. 连接池

S.S. 复合主键

S.T. ⽇志

S.T.N. ⾃定义⽇志

4.3 go-admin架构分析和环境配置

GitHub - go-admin-team/go-admin: 基于Gin Vue Element UI & Arco Design & Ant Design 的前后端分离权限管理系统脚手架(包含了:多租户的支持,基础用户管理功能,jwt鉴权,代码生成器,RBAC资源控制,表单构建,定时任务等)3分钟构建自己的中后台项目;项目文档》:https://www.go-admin.pro V2 Demo: https://vue2.go-admin.dev V3 Demo: https://vue3.go-admin.dev Antd 订阅版:https://antd.go-admin.pro

go-admin架构分析和环境配置

N 简介

N.N 在线体验

N.O 特性

N.P 内置

O 安装

O.N 开发⽬录创建

O.O 获取代码

O.P 编译后端项⽬和修改配置⽂件

O.Q 初始化数据库,以及后端服务启动

⽐如
数据库设置为 go_test ,需要在后台提前创建 create database go_test;
1 # 1 ⾸次配置需要初始化数据库资源信息
2 ./go-admin migrate -c config/settings.yml
3 #2 启动项⽬,也可以⽤ IDE 进⾏调试
4 ./go-admin server -c config/settings.yml

学新通学新通  O.S 前端UI交互端启动说明

PS D:\Workspace\Go\src\devops\goadmin\go-admin-ui> npm i --legacy-peer-deps --registry=https://registry.npm.taobao.org

npm run dev

opensslErrorStack: [ ‘error:03000086:digital envelope routines::initialization error‘ ]

  "scripts": {
    "dev": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve",
    "serve": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve",

学新通

学新通

O.T 发布⽹⻚

P 架构分析

P.N 接⼝

P.O ⽂件⽬录

Q 问题总结

nodejs let notifier = require('update-notifier')({pkg}) 报错

安装NodeJS和NPM

安装命令

更新npm的包镜像源,⽅便快速下载

安装n管理器(⽤于管理nodejs版本)

npm ERR! cb()never called!的错误

Husky requires Git >=O.NP.L. Got vO.V.Q.

Error NPTT: Incorrect string value: '\xEV\xWW\xBN\xET\xWB\xXP...' for column 'dept_name' at row

mysql数据库表结构导出

重点

搭建go-admin项⽬

整体框架分析

各个⽬录和源码的作⽤

jwt鉴权设计

cobra cmd机制 (k8s) 命令⾏功能⾮常强⼤。

使⽤ go cobra创建命令⾏项⽬

代码仓库 github.com/yunixiangfeng/devops/tree/main/cobra

Cobra介绍

实现没有⼦命令的CLIs程序

实现有⼦命令的CLIs程序

附加命令

4.4 go-admin API和数据库设计分析

go-admin后台设计之casbin权限管理

N 概要

O PERM 模型

O casbin 权限库

casbin的主要特性

casbin不做的事情

核⼼概念

model file

model file 定义语法

policy file

RBAC 示例

定义 model file

定义 policy file

测试代码

多租户示例

定义 model file

定义 policy file

测试代码

Has_Role

例⼦:RBAC

Has Tenant Role

gin gorm casbin示例

P 总结

Q 参考⽂档

go-admin后台设计之授权机制

N登录过程分析

O ⽤户权限验证

权限⽣成

权限校验

P ⻆⾊权限验证

⻆⾊规则⽣成

接⼝规则

菜单规则

⻆⾊校验

Q 数据库设计

sys_casbin_rule 权限规则

sys_config 配置信息

sys_dept部⻔信息

sys_menu菜单

sys_post岗位名

sys_role⻆⾊类别

sys_role_dept⻆⾊部⻔

sys_role_menu⻆⾊菜单

sys_user⽤户

sys_category

sys_columns

sys_content

sys_dict_data字典数据

sys_dict_type字典类型

sys_file_dir⽂件⽬录

sys_file_info⽂件信息

sys_job

sys_login_log登录⽇志

1

sys_migration

sys_opera_log操作⽇志

sys_setting系统设置

sys_tables

4.5 go-admin添加应用实战

代码仓库

github.com/yunixiangfeng/devops/tree/main/goadmin

go-admin后台设计-添加应⽤实战

L 主要内容

N 新增模块

O 编写 go-admin 应⽤,第 N 步 ⼿动写代码

开始项⽬

⽤于开发的服务器

创建⽂章功能

app\admin\apis\article.go

  1.  
    package apis
  2.  
     
  3.  
    import (
  4.  
    "go-admin/common/models"
  5.  
    "net/http"
  6.  
     
  7.  
    "github.com/gin-gonic/gin"
  8.  
    )
  9.  
     
  10.  
    // GetArticleList 获取⽂章列表
  11.  
    func GetArticleList(c *gin.Context) {
  12.  
    var res models.Response
  13.  
    res.Data = "hello world !"
  14.  
    c.JSON(http.StatusOK, res.ReturnOK())
  15.  
    }
学新通

编写第⼀个接⼝

app\other\router\gen_router.go

  1.  
    package router
  2.  
     
  3.  
    import (
  4.  
    "go-admin/app/admin/apis"
  5.  
    "go-admin/app/other/apis/tools"
  6.  
     
  7.  
    "github.com/gin-gonic/gin"
  8.  
    jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
  9.  
    )
  10.  
     
  11.  
    func init() {
  12.  
    routerCheckRole = append(routerCheckRole, sysNoCheckRoleRouter, registerDBRouter, registerSysTableRouter)
  13.  
    }
  14.  
     
  15.  
    func sysNoCheckRoleRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
  16.  
    r1 := v1.Group("")
  17.  
    {
  18.  
    sys := apis.System{}
  19.  
    r1.GET("/captcha", sys.GenerateCaptchaHandler)
  20.  
    }
  21.  
     
  22.  
    r := v1.Group("").Use(authMiddleware.MiddlewareFunc())
  23.  
    {
  24.  
    gen := tools.Gen{}
  25.  
    r.GET("/gen/preview/:tableId", gen.Preview)
  26.  
    r.GET("/gen/toproject/:tableId", gen.GenCode)
  27.  
    r.GET("/gen/apitofile/:tableId", gen.GenApiToFile)
  28.  
    r.GET("/gen/todb/:tableId", gen.GenMenuAndApi)
  29.  
    sysTable := tools.SysTable{}
  30.  
    r.GET("/gen/tabletree", sysTable.GetSysTablesTree)
  31.  
    r.GET("/articleList", apis.GetArticleList) // 新加接⼝
  32.  
    }
  33.  
    }
  34.  
     
  35.  
    func registerDBRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
  36.  
    db := v1.Group("/db").Use(authMiddleware.MiddlewareFunc())
  37.  
    {
  38.  
    gen := tools.Gen{}
  39.  
    db.GET("/tables/page", gen.GetDBTableList)
  40.  
    db.GET("/columns/page", gen.GetDBColumnList)
  41.  
    }
  42.  
    }
  43.  
     
  44.  
    func registerSysTableRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
  45.  
    tables := v1.Group("/sys/tables")
  46.  
    {
  47.  
    sysTable := tools.SysTable{}
  48.  
    tables.Group("").Use(authMiddleware.MiddlewareFunc()).GET("/page", sysTable.GetPage)
  49.  
    tablesInfo := tables.Group("/info").Use(authMiddleware.MiddlewareFunc())
  50.  
    {
  51.  
    tablesInfo.POST("", sysTable.Insert)
  52.  
    tablesInfo.PUT("", sysTable.Update)
  53.  
    tablesInfo.DELETE("/:tableId", sysTable.Delete)
  54.  
    tablesInfo.GET("/:tableId", sysTable.Get)
  55.  
    tablesInfo.GET("", sysTable.GetSysTablesInfo)
  56.  
    }
  57.  
    }
  58.  
    }
学新通

学新通

 app\other\router\gen_router.go

  1.  
    package router
  2.  
     
  3.  
    import (
  4.  
    "go-admin/app/admin/apis"
  5.  
    "go-admin/app/other/apis/tools"
  6.  
     
  7.  
    "github.com/gin-gonic/gin"
  8.  
    jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
  9.  
    )
  10.  
     
  11.  
    func init() {
  12.  
    routerCheckRole = append(routerCheckRole, sysNoCheckRoleRouter, registerDBRouter, registerSysTableRouter)
  13.  
    }
  14.  
     
  15.  
    func sysNoCheckRoleRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
  16.  
    r1 := v1.Group("")
  17.  
    {
  18.  
    sys := apis.System{}
  19.  
    r1.GET("/captcha", sys.GenerateCaptchaHandler)
  20.  
    r1.GET("/articleList", apis.GetArticleList) // 新加接⼝
  21.  
    }
  22.  
     
  23.  
    r := v1.Group("").Use(authMiddleware.MiddlewareFunc())
  24.  
    {
  25.  
    gen := tools.Gen{}
  26.  
    r.GET("/gen/preview/:tableId", gen.Preview)
  27.  
    r.GET("/gen/toproject/:tableId", gen.GenCode)
  28.  
    r.GET("/gen/apitofile/:tableId", gen.GenApiToFile)
  29.  
    r.GET("/gen/todb/:tableId", gen.GenMenuAndApi)
  30.  
    sysTable := tools.SysTable{}
  31.  
    r.GET("/gen/tabletree", sysTable.GetSysTablesTree)
  32.  
     
  33.  
    }
  34.  
    }
  35.  
     
  36.  
    func registerDBRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
  37.  
    db := v1.Group("/db").Use(authMiddleware.MiddlewareFunc())
  38.  
    {
  39.  
    gen := tools.Gen{}
  40.  
    db.GET("/tables/page", gen.GetDBTableList)
  41.  
    db.GET("/columns/page", gen.GetDBColumnList)
  42.  
    }
  43.  
    }
  44.  
     
  45.  
    func registerSysTableRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
  46.  
    tables := v1.Group("/sys/tables")
  47.  
    {
  48.  
    sysTable := tools.SysTable{}
  49.  
    tables.Group("").Use(authMiddleware.MiddlewareFunc()).GET("/page", sysTable.GetPage)
  50.  
    tablesInfo := tables.Group("/info").Use(authMiddleware.MiddlewareFunc())
  51.  
    {
  52.  
    tablesInfo.POST("", sysTable.Insert)
  53.  
    tablesInfo.PUT("", sysTable.Update)
  54.  
    tablesInfo.DELETE("/:tableId", sysTable.Delete)
  55.  
    tablesInfo.GET("/:tableId", sysTable.Get)
  56.  
    tablesInfo.GET("", sysTable.GetSysTablesInfo)
  57.  
    }
  58.  
    }
  59.  
    }
学新通

学新通

path

P 编写 go-admin 应⽤,第 O 步 ⾃动⽣成代码

数据库配置

学新通

代码⽣成

表结构导⼊

学新通

编辑模板字段

学新通

预览代码

⽣成代码

app\admin\apis\article.go

  1.  
    package apis
  2.  
     
  3.  
    import (
  4.  
    "fmt"
  5.  
     
  6.  
    "github.com/gin-gonic/gin"
  7.  
    "github.com/go-admin-team/go-admin-core/sdk/api"
  8.  
    "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user"
  9.  
    _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response"
  10.  
     
  11.  
    "go-admin/app/admin/models"
  12.  
    "go-admin/app/admin/service"
  13.  
    "go-admin/app/admin/service/dto"
  14.  
    "go-admin/common/actions"
  15.  
    )
  16.  
     
  17.  
    type Article struct {
  18.  
    api.Api
  19.  
    }
  20.  
     
  21.  
    // GetPage 获取Go文章列表
  22.  
    // @Summary 获取Go文章列表
  23.  
    // @Description 获取Go文章列表
  24.  
    // @Tags Go文章
  25.  
    // @Param author query string false "作者"
  26.  
    // @Param content query string false "内容"
  27.  
    // @Param status query string false "状态"
  28.  
    // @Param pageSize query int false "页条数"
  29.  
    // @Param pageIndex query int false "页码"
  30.  
    // @Success 200 {object} response.Response{data=response.Page{list=[]models.Article}} "{"code": 200, "data": [...]}"
  31.  
    // @Router /api/v1/article [get]
  32.  
    // @Security Bearer
  33.  
    func (e Article) GetPage(c *gin.Context) {
  34.  
    req := dto.ArticleGetPageReq{}
  35.  
    s := service.Article{}
  36.  
    err := e.MakeContext(c).
  37.  
    MakeOrm().
  38.  
    Bind(&req).
  39.  
    MakeService(&s.Service).
  40.  
    Errors
  41.  
    if err != nil {
  42.  
    e.Logger.Error(err)
  43.  
    e.Error(500, err, err.Error())
  44.  
    return
  45.  
    }
  46.  
     
  47.  
    p := actions.GetPermissionFromContext(c)
  48.  
    list := make([]models.Article, 0)
  49.  
    var count int64
  50.  
     
  51.  
    err = s.GetPage(&req, p, &list, &count)
  52.  
    if err != nil {
  53.  
    e.Error(500, err, fmt.Sprintf("获取Go文章失败,\r\n失败信息 %s", err.Error()))
  54.  
    return
  55.  
    }
  56.  
     
  57.  
    e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功")
  58.  
    }
  59.  
     
  60.  
    // Get 获取Go文章
  61.  
    // @Summary 获取Go文章
  62.  
    // @Description 获取Go文章
  63.  
    // @Tags Go文章
  64.  
    // @Param id path int false "id"
  65.  
    // @Success 200 {object} response.Response{data=models.Article} "{"code": 200, "data": [...]}"
  66.  
    // @Router /api/v1/article/{id} [get]
  67.  
    // @Security Bearer
  68.  
    func (e Article) Get(c *gin.Context) {
  69.  
    req := dto.ArticleGetReq{}
  70.  
    s := service.Article{}
  71.  
    err := e.MakeContext(c).
  72.  
    MakeOrm().
  73.  
    Bind(&req).
  74.  
    MakeService(&s.Service).
  75.  
    Errors
  76.  
    if err != nil {
  77.  
    e.Logger.Error(err)
  78.  
    e.Error(500, err, err.Error())
  79.  
    return
  80.  
    }
  81.  
    var object models.Article
  82.  
     
  83.  
    p := actions.GetPermissionFromContext(c)
  84.  
    err = s.Get(&req, p, &object)
  85.  
    if err != nil {
  86.  
    e.Error(500, err, fmt.Sprintf("获取Go文章失败,\r\n失败信息 %s", err.Error()))
  87.  
    return
  88.  
    }
  89.  
     
  90.  
    e.OK( object, "查询成功")
  91.  
    }
  92.  
     
  93.  
    // Insert 创建Go文章
  94.  
    // @Summary 创建Go文章
  95.  
    // @Description 创建Go文章
  96.  
    // @Tags Go文章
  97.  
    // @Accept application/json
  98.  
    // @Product application/json
  99.  
    // @Param data body dto.ArticleInsertReq true "data"
  100.  
    // @Success 200 {object} response.Response "{"code": 200, "message": "添加成功"}"
  101.  
    // @Router /api/v1/article [post]
  102.  
    // @Security Bearer
  103.  
    func (e Article) Insert(c *gin.Context) {
  104.  
    req := dto.ArticleInsertReq{}
  105.  
    s := service.Article{}
  106.  
    err := e.MakeContext(c).
  107.  
    MakeOrm().
  108.  
    Bind(&req).
  109.  
    MakeService(&s.Service).
  110.  
    Errors
  111.  
    if err != nil {
  112.  
    e.Logger.Error(err)
  113.  
    e.Error(500, err, err.Error())
  114.  
    return
  115.  
    }
  116.  
    // 设置创建人
  117.  
    req.SetCreateBy(user.GetUserId(c))
  118.  
     
  119.  
    err = s.Insert(&req)
  120.  
    if err != nil {
  121.  
    e.Error(500, err, fmt.Sprintf("创建Go文章失败,\r\n失败信息 %s", err.Error()))
  122.  
    return
  123.  
    }
  124.  
     
  125.  
    e.OK(req.GetId(), "创建成功")
  126.  
    }
  127.  
     
  128.  
    // Update 修改Go文章
  129.  
    // @Summary 修改Go文章
  130.  
    // @Description 修改Go文章
  131.  
    // @Tags Go文章
  132.  
    // @Accept application/json
  133.  
    // @Product application/json
  134.  
    // @Param id path int true "id"
  135.  
    // @Param data body dto.ArticleUpdateReq true "body"
  136.  
    // @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}"
  137.  
    // @Router /api/v1/article/{id} [put]
  138.  
    // @Security Bearer
  139.  
    func (e Article) Update(c *gin.Context) {
  140.  
    req := dto.ArticleUpdateReq{}
  141.  
    s := service.Article{}
  142.  
    err := e.MakeContext(c).
  143.  
    MakeOrm().
  144.  
    Bind(&req).
  145.  
    MakeService(&s.Service).
  146.  
    Errors
  147.  
    if err != nil {
  148.  
    e.Logger.Error(err)
  149.  
    e.Error(500, err, err.Error())
  150.  
    return
  151.  
    }
  152.  
    req.SetUpdateBy(user.GetUserId(c))
  153.  
    p := actions.GetPermissionFromContext(c)
  154.  
     
  155.  
    err = s.Update(&req, p)
  156.  
    if err != nil {
  157.  
    e.Error(500, err, fmt.Sprintf("修改Go文章失败,\r\n失败信息 %s", err.Error()))
  158.  
    return
  159.  
    }
  160.  
    e.OK( req.GetId(), "修改成功")
  161.  
    }
  162.  
     
  163.  
    // Delete 删除Go文章
  164.  
    // @Summary 删除Go文章
  165.  
    // @Description 删除Go文章
  166.  
    // @Tags Go文章
  167.  
    // @Param data body dto.ArticleDeleteReq true "body"
  168.  
    // @Success 200 {object} response.Response "{"code": 200, "message": "删除成功"}"
  169.  
    // @Router /api/v1/article [delete]
  170.  
    // @Security Bearer
  171.  
    func (e Article) Delete(c *gin.Context) {
  172.  
    s := service.Article{}
  173.  
    req := dto.ArticleDeleteReq{}
  174.  
    err := e.MakeContext(c).
  175.  
    MakeOrm().
  176.  
    Bind(&req).
  177.  
    MakeService(&s.Service).
  178.  
    Errors
  179.  
    if err != nil {
  180.  
    e.Logger.Error(err)
  181.  
    e.Error(500, err, err.Error())
  182.  
    return
  183.  
    }
  184.  
     
  185.  
    // req.SetUpdateBy(user.GetUserId(c))
  186.  
    p := actions.GetPermissionFromContext(c)
  187.  
     
  188.  
    err = s.Remove(&req, p)
  189.  
    if err != nil {
  190.  
    e.Error(500, err, fmt.Sprintf("删除Go文章失败,\r\n失败信息 %s", err.Error()))
  191.  
    return
  192.  
    }
  193.  
    e.OK( req.GetId(), "删除成功")
  194.  
    }
学新通

app\admin\router\article.go

  1.  
    package router
  2.  
     
  3.  
    import (
  4.  
    "github.com/gin-gonic/gin"
  5.  
    jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
  6.  
     
  7.  
    "go-admin/app/admin/apis"
  8.  
    "go-admin/common/middleware"
  9.  
    "go-admin/common/actions"
  10.  
    )
  11.  
     
  12.  
    func init() {
  13.  
    routerCheckRole = append(routerCheckRole, registerArticleRouter)
  14.  
    }
  15.  
     
  16.  
    // registerArticleRouter
  17.  
    func registerArticleRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
  18.  
    api := apis.Article{}
  19.  
    r := v1.Group("/article").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole())
  20.  
    {
  21.  
    r.GET("", actions.PermissionAction(), api.GetPage)
  22.  
    r.GET("/:id", actions.PermissionAction(), api.Get)
  23.  
    r.POST("", api.Insert)
  24.  
    r.PUT("/:id", actions.PermissionAction(), api.Update)
  25.  
    r.DELETE("", api.Delete)
  26.  
    }
  27.  
    }
学新通

app\admin\service\article.go

  1.  
    package service
  2.  
     
  3.  
    import (
  4.  
    "errors"
  5.  
     
  6.  
    "github.com/go-admin-team/go-admin-core/sdk/service"
  7.  
    "gorm.io/gorm"
  8.  
     
  9.  
    "go-admin/app/admin/models"
  10.  
    "go-admin/app/admin/service/dto"
  11.  
    "go-admin/common/actions"
  12.  
    cDto "go-admin/common/dto"
  13.  
    )
  14.  
     
  15.  
    type Article struct {
  16.  
    service.Service
  17.  
    }
  18.  
     
  19.  
    // GetPage 获取Article列表
  20.  
    func (e *Article) GetPage(c *dto.ArticleGetPageReq, p *actions.DataPermission, list *[]models.Article, count *int64) error {
  21.  
    var err error
  22.  
    var data models.Article
  23.  
     
  24.  
    err = e.Orm.Model(&data).
  25.  
    Scopes(
  26.  
    cDto.MakeCondition(c.GetNeedSearch()),
  27.  
    cDto.Paginate(c.GetPageSize(), c.GetPageIndex()),
  28.  
    actions.Permission(data.TableName(), p),
  29.  
    ).
  30.  
    Find(list).Limit(-1).Offset(-1).
  31.  
    Count(count).Error
  32.  
    if err != nil {
  33.  
    e.Log.Errorf("ArticleService GetPage error:%s \r\n", err)
  34.  
    return err
  35.  
    }
  36.  
    return nil
  37.  
    }
  38.  
     
  39.  
    // Get 获取Article对象
  40.  
    func (e *Article) Get(d *dto.ArticleGetReq, p *actions.DataPermission, model *models.Article) error {
  41.  
    var data models.Article
  42.  
     
  43.  
    err := e.Orm.Model(&data).
  44.  
    Scopes(
  45.  
    actions.Permission(data.TableName(), p),
  46.  
    ).
  47.  
    First(model, d.GetId()).Error
  48.  
    if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
  49.  
    err = errors.New("查看对象不存在或无权查看")
  50.  
    e.Log.Errorf("Service GetArticle error:%s \r\n", err)
  51.  
    return err
  52.  
    }
  53.  
    if err != nil {
  54.  
    e.Log.Errorf("db error:%s", err)
  55.  
    return err
  56.  
    }
  57.  
    return nil
  58.  
    }
  59.  
     
  60.  
    // Insert 创建Article对象
  61.  
    func (e *Article) Insert(c *dto.ArticleInsertReq) error {
  62.  
    var err error
  63.  
    var data models.Article
  64.  
    c.Generate(&data)
  65.  
    err = e.Orm.Create(&data).Error
  66.  
    if err != nil {
  67.  
    e.Log.Errorf("ArticleService Insert error:%s \r\n", err)
  68.  
    return err
  69.  
    }
  70.  
    return nil
  71.  
    }
  72.  
     
  73.  
    // Update 修改Article对象
  74.  
    func (e *Article) Update(c *dto.ArticleUpdateReq, p *actions.DataPermission) error {
  75.  
    var err error
  76.  
    var data = models.Article{}
  77.  
    e.Orm.Scopes(
  78.  
    actions.Permission(data.TableName(), p),
  79.  
    ).First(&data, c.GetId())
  80.  
    c.Generate(&data)
  81.  
     
  82.  
    db := e.Orm.Save(&data)
  83.  
    if err = db.Error; err != nil {
  84.  
    e.Log.Errorf("ArticleService Save error:%s \r\n", err)
  85.  
    return err
  86.  
    }
  87.  
    if db.RowsAffected == 0 {
  88.  
    return errors.New("无权更新该数据")
  89.  
    }
  90.  
    return nil
  91.  
    }
  92.  
     
  93.  
    // Remove 删除Article
  94.  
    func (e *Article) Remove(d *dto.ArticleDeleteReq, p *actions.DataPermission) error {
  95.  
    var data models.Article
  96.  
     
  97.  
    db := e.Orm.Model(&data).
  98.  
    Scopes(
  99.  
    actions.Permission(data.TableName(), p),
  100.  
    ).Delete(&data, d.GetId())
  101.  
    if err := db.Error; err != nil {
  102.  
    e.Log.Errorf("Service RemoveArticle error:%s \r\n", err)
  103.  
    return err
  104.  
    }
  105.  
    if db.RowsAffected == 0 {
  106.  
    return errors.New("无权删除该数据")
  107.  
    }
  108.  
    return nil
  109.  
    }
学新通

配置系统菜单

配置⻆⾊权限

学新通

 操作内容管理

学新通

ok 

参照doc.zhangwj.com/cms/article-manage

admin/123456

学新通 

Go语⾔资源汇总

开篇

Go语⾔该学什么

⽹站

开源项⽬

gin

gim

beego

cobra

pholcus

nsq

codis

delve

micro/micro

4.6 gin-vue-admin实战

github.com/yunixiangfeng/devops/tree/main/gin-vue-admin

【GIN-VUE-ADMIN】手把手教你使用gin-vue-admin(分P合集)

自动化pkg 自动化代码使用_哔哩哔哩_bilibili

https://github.com/flipped-aurora/gin-vue-admin

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgfhick
系列文章
更多 icon
同类精品
更多 icon
继续加载