0%

前言

全篇下来太啰嗦,umi + dva,完成用户管理的 CURD 应用是重点,看这里就行了!!!

antd

Ant Design,antd 是基于 Ant Design 设计体系的 React UI 组件库,主要用于研发企业级中后台产品。

目录结构说明

  • config:配置信息
    config.js全局配置,路由配置
    defaultSettings.js一些默认配置
    plugin.config.js插件配置

  • mock 模拟数据请求的

  • mode_modules 第三方库

  • public 放置第三方文件(比如说图片,第三方的json文件)

  • tests 做一些测试的

  • package.json

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    {
    "name": "ant-design-pro",
    "version": "1.0.0",
    "private": true,
    "description": "An out-of-box UI solution for enterprise applications",
    "scripts": {
    "analyze": "分析",
    "build": "编译",
    "deploy": "部署",
    "fetch:blocks": "从官网git里面去拉模块",
    "format-imports": "代码格式(例如空格)",
    "start": "启动",
    "start:no-mock": "cross-env MOCK=none umi dev",
    "test": "测试",
    "test:all": "node ./tests/run-tests.js",
    "test:component": "umi test ./src/components",
    "ui": "umi ui"
    },
    }
  • src

assets静态资源
components组件目录
e2e
layouts布局
locales多语言
models模型目录
pages页面
services提供服务,给页面提供请求
utils工具类
global.less
global.tsx
manifelst.json
service-worker.js
typings.d.ts

增加新页面

1.在config-config.js文件的routers节点添加代码

{name: ‘demo’, path: ‘/demo’, component: ‘./demo’},

2.在page目录下创建demo目录,并新建一个index.jsx文件。
3.添加源码。

dva

UmiJS

dva + umi

主要搞懂model、service的关系

Gitea:一个开源社区驱动的轻量级代码托管解决方案

定义变量

空值

1
2
3
4
5
func variableZeroValue() {
var a int
var s string
fmt.Printf("%d %q\n", a, s)
}

初始化

1
2
3
4
5
func variableInitialValue() {
var a, b int = 3, 4
var s string = "abc"
fmt.Println(a, b, s)
}

类型推断

1
2
3
4
5
func variableTypeDeduction() {
var a, b, c, d = 3, 4, true, "def"
var s string = "abc"
fmt.Println(a, b, c, d, s)
}

利用冒号定义的简单写法

1
2
3
4
5
6
func variableShorter() {
a, b, c, d := 3, 4, true, "def"
b = 6
var s string = "abc"
fmt.Println(a, b, c, d, s)
}

函数外的变量定义(不能使用冒号定义,且它的作用域只在该包内部)

1
2
3
var aa = 55
var ss = "llll"
var bb = true

或者

1
2
3
4
5
var (
bbb = 666
sss = "jjj"
ttt = false
)

内建变量类型

bool, string,

(u)int, (u)int8, (u)int16, (u)int32, (u)int64, uintptr,

byte, rune,

float32, float64, complex64, complex128,

常量与枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
 func consts() {
const filename = "abc.txt"
const a, b = 3, 4
var c int
c = int(math.Sqrt(a*a + b + b))
fmt.Println(filename, c)
}

func enums() {
const (
cpp = iota
java
golang
swift
_
C
)

const (
b = 1 << (10 * iota)
bb
bbb
bbbb
)

fmt.Println(cpp, java, golang, C)
fmt.Println(b, bb, bbb, bbbb)
}
1
2
3
abc.txt 4
0 1 2 5
1 1024 1048576 1073741824

条件判断

if的条件里可以赋值
if的条件里赋值的变量作用域就在这个if语句里

1
2
3
4
5
6
7
8
func main() {
const filename = "abc.txt"
if contents, err := ioutil.ReadFile(filename); err != nil {
fmt.Println(err)
} else {
fmt.Println("%s\n", contents)
}
}

switch后面可以没有表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func grade(score int) string {
g := ""
switch {
case score < 0 || score > 100:
panic(fmt.Sprintf("wrong score: %d", score))
case score < 60:
g = "F"
case score < 80:
g = "C"
case score < 90:
g = "B"
case score < 100:
g = "A"
// default:
// panic(fmt.Sprintf("wrong score: %d", score))
}
return g
}

for

1
2
3
4
5
6
7
8
func convertToBin(n int) string {
res := ""
for ; n > 0; n /= 2 {
lsb := n % 2
res = strconv.Itoa(lsb) + res
}
return res
}
1
2
3
4
5
6
7
8
9
10
func printFile(filename string) {
file, err := os.Open(filename)
if err != nil {
panic(err)
}
scanner := bufio.NewScanner(file)
for scanner.Scan() {
fmt.Println(scanner.Text)
}
}

死循环

1
2
3
4
5
func forever() {
for {
fmt.Println("abc")
}
}

函数

  • 返回值类型写在后面
  • 可返回多个值
  • 函数作为参数
  • 没有默认参数,有可选参数
1
2
3
4
5
6
func div(a, b int) (q, r int) {
// return a / b, a % b
q = a / b
r = a % b
return q, r
}
1
2
3
4
5
6
7
8
9
10
func pow(a, b int) int {
return int(math.Pow(float64(a), float64(b)))
}

func main() {
q, r := div(8, 6)
fmt.Println(q, r)

fmt.Println(apply(pow, 1, 5))
}

或者以匿名函数的形式进行

1
2
3
4
5
6
7
8
func main() {

fmt.Println(apply(
func(a int, b int) int {
return int(math.Pow(
float64(a), float64(b)))
}, 1, 5))
}

函数作为参数

1
2
3
4
5
6
7
8
func apply(op func(int, int) int, a, b int) int {
p := reflect.ValueOf(op).Pointer()
opName := runtime.FuncForPC(p).Name()
fmt.Printf("Calling function %s with args "+
"(%d, %d)\n", opName, a, b)

return op(a, b)
}

可变参数

1
2
3
4
5
6
7
func sum(numbers ...int) int {
s := 0
for i := range numbers {
s += numbers[i]
}
return s
}

指针

1
2
3
4
5
6
func exp() {
var a int = 2
var pa *int = &a
*pa = 3
fmt.Println(a)
}

参数传递

  • go语言只有值传递一种方式
    var a int 当参数传递给fun f(pa *int)它其实就是将a的指针&a拷贝一份传递给pa,pa也跟&a一样,同时指向a

  • 再如:var cache Cache 当参数传递给func f(cache Cache),参数Cache那么大不可能拷贝一份的,实际上Cache也只是一个指向data的指针,所以cache当参数的时候,它只是拷贝了一份指针,它同时也指向了data

交换两个变量的值

1
2
3
4
5
6
7
8
9
10
func swap(a, b int) {
b, a = a, b
}
func main() {
exp()

a, b := 3, 4
swap(a, b)
println(a, b)
}

输出

1
2
3
➜  hello go run point.go
3
3 4

我们发现值没有变,这是需要使用指针,将a,b所指向的值改变

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func exp() {
var a int = 2
var pa *int = &a
*pa = 3
fmt.Println(a)
}

func swap(a, b *int) {
*b, *a = *a, *b
}
func main() {
exp()

a, b := 3, 4
swap(&a, &b)
println(a, b)
}

数组

数组的定义

1
2
3
4
5
6
7
8
9
10
func exp() {
var arr1 [5]int
arr2 := [3]int{1, 2, 3} //冒号等于的定义,需要给数组一个初值
arr3 := [...]int{1, 2, 3, 4} //或者不指定数组个数
fmt.Println(arr1, arr2, arr3)

//二维数组定义
var grid [4][5]int
fmt.Println(grid)
}

数组的遍历

1
2
3
for i := 0; i < len(arr3); i++ {
fmt.Println(arr3[i])
}

下标

1
2
3
for i := range arr3 {
fmt.Println(arr3[i])
}

1
2
3
for _, v := range arr3 {
fmt.Println(v)
}

下标和值

1
2
3
for i, v := range arr3 {
fmt.Println(i, v)
}

数组是值类型的,所以当参数用的时候需要指定长度cannot use arr1 (type [5]int) as type [4]int in argument to printArray,且在函数里面再次赋值后,它在外面是不会改变的

1
2
3
4
5
6
func printArray(arr [4]int) {
for i, v := range arr {
fmt.Println(i, v)
}
arr[0] = 100 //在外面调用时,依旧是1
}

所以,[10]int和[20]int 是不同类型;调用func f(arr [10]int)会拷贝数组。需要改变它就需要用到指针了

1
2
3
4
5
6
7
8
9
10
11
12
13
func exp() {
arr3 := [...]int{1, 2, 3, 4}

printArray(&arr3)
fmt.Println(arr3, arr3[0])
}

func printArray(arr *[4]int) {
for i, v := range arr {
fmt.Println(i, v)
}
arr[0] = 100 //在外面调用时,改变为100
}

slice

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68

func updateSlice(s []int){
s[0] = 100
}



func main() {

arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
//s := arr[2:6]
fmt.Println("arr[2:6] =", arr[2:6])#arr[2:6] = [2 3 4 5]

fmt.Println("arr[:6] =", arr[:6])#arr[:6] = [0 1 2 3 4 5]

fmt.Println("arr[2:] =", arr[2:])#arr[2:] = [2 3 4 5 6 7]

fmt.Println("arr[:] =", arr[:])#arr[:] = [0 1 2 3 4 5 6 7]


s1 := arr[2:]
s2 := arr[:]

fmt.Println("after updateSlice(s1")
updateSlice(s1)
fmt.Println(s1)
fmt.Println(arr)

# after updateSlice(s1
# [100 3 4 5 6 7]
# [0 1 100 3 4 5 6 7]

fmt.Println("after updateSlice(s2")
updateSlice(s2)
fmt.Println(s2)
fmt.Println(arr)

# after updateSlice(s2
# [100 1 100 3 4 5 6 7]
# [100 1 100 3 4 5 6 7]

fmt.Println("printArray")
printArray(arr[:])


# printArray
# 0 100
# 1 50
# 2 100
# 3 3
# 4 4
# 5 5
# 6 6
# 7 7

fmt.Println("reslice")
fmt.Println(s2)
s2 = s2[:5]
fmt.Println(s2)
s2 = s2[2:]
fmt.Println(s2)

# reslice
# [100 50 100 3 4 5 6 7]
# [100 50 100 3 4]
# [100 3 4]

}

slice

slice可以向后扩展,不可以向前扩展
s[i]不可以超越len(s),向后扩展不可以超越底层数组 cap(s)

1
2
3
4
5
6
7
8
9
10
11
func sliceExtension() {
fmt.Println("sliceExtension")
arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
s1 := arr[2:6]
s2 := s1[3:5]
fmt.Println("s1=", s1)
fmt.Println("s2=", s2)
}
# sliceExtension
# s1= [2 3 4 5]
# s2= [5 6]

向slice添加元素时,如果超越了cap,系统会重新分配更大的底层数组
新的slice里面, ptr len cap都会变掉,所以必须接收append的返回值

s = append(s, value)

1
2
3
4
5
6
7
8
9
10
11
12
13
func sliceAppend() {
fmt.Println("slice append")
arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
s1 := arr[2:6] # 2 3 4 5
s2 := s1[3:5] # 5 6
s3 := append(s2, 10) # 5 6 10
s4 := append(s3, 11) # 5 6 10 11
s5 := append(s4, 12) # 5 6 10 11 12
fmt.Println("s3=", s3)
fmt.Println("s4=", s4)
fmt.Println("s5=", s5)
fmt.Println("arr=", arr) # 0, 1, 2, 3, 4, 5, 6 10 数组长度不变的
}

slice的创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
func sliceCreate() {
var s []int #定义一个空slice
for i := 0; i < 20; i++ {
printSlice(s)
s = append(s, 2*i+1)
}
println(s)

s1 := []int{2, 3, 5, 6, 8, 2, 5}
println(s1)

s2 := make([]int, 16)
s3 := make([]int, 10, 32) //32是开辟的空间cap
println("print Slice s2 s3")
printSlice(s2)
printSlice(s3)

println("Slice copy")
copy(s2, s1) # 将s1复制给s2
printSlice(s2)

//[2 3 5 6 8 2 5 0 0 0 0 0 0 0 0 0], len=16, cap= 16
//如何删除8
println("Slice delete")
s2 = append(s2[:4], s2[5:]...)
printSlice(s2)

//删除头尾
fmt.Println("pop from front")
front := s2[0]
s2 = s2[1:]

fmt.Println("pop from back")
back := s2[len(s2)-1]
s2 = s2[:len(s2)-1]
fmt.Println(front, back)
printSlice(s2)
}
1
2
3
4
5
6
7
8
9
10
11
print Slice  s2 s3
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0], len=16, cap= 16
[0 0 0 0 0 0 0 0 0 0], len=10, cap= 32
Slice copy
[2 3 5 6 8 2 5 0 0 0 0 0 0 0 0 0], len=16, cap= 16
Slice delete
[2 3 5 6 2 5 0 0 0 0 0 0 0 0 0], len=15, cap= 16
pop from front
pop from back
2 0
[3 5 6 2 5 0 0 0 0 0 0 0 0], len=13, cap= 15

map

定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func main() {
m := map[string]string{
"name": "sam",
"class": "go",
}

fmt.Println(m)

m2 := make(map[string]int)

var m3 map[string]int

fmt.Println(m2, m3)
}

遍历,key在map里是无序的~

1
2
3
for k, v := range m {
fmt.Println(k, v)
}

当key打错了,获取到的是空值

1
2
3
4
//获取值
fmt.Println("getting values")
name := m["name"]
println(name)
1
2
3
4
5
if name, ok := m["name"]; ok {
println(name)
} else {
println("key does not exist")
}

删除

1
2
3
4
//删除
fmt.Println("delete values")
name1, ok := m["name"]
println(name1, ok)

map的key的类型可以是
除了slice,map,function的内建类型都可以作为key
struct类型不包含上述字段,也可以作为key

字符和字符串的处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
func main() {
s := "yes中文中文"
fmt.Println(s)
for _, b := range []byte(s) {
fmt.Printf("%X ", b)
}

fmt.Println()

for i, ch := range s {
fmt.Printf("(%d %X)", i, ch)
}

fmt.Println()
fmt.Println("rune count:", utf8.RuneCountInString(s))

//单个字符转义
bytes := []byte(s)
for len(bytes) > 0 {
ch, size := utf8.DecodeRune(bytes)
bytes = bytes[size:]
fmt.Printf("%c ", ch)
}

fmt.Println()
}
1
2
3
4
5
yes中文中文
79 65 73 E4 B8 AD E6 96 87 E4 B8 AD E6 96 87
(0 79)(1 65)(2 73)(3 4E2D)(6 6587)(9 4E2D)(12 6587)
rune count: 7
y e s 中 文 中 文

rune相当于go的char
使用range遍历string,rune对
使用utf8.RuneCountInString(s)获得字符数量
使用len获得字节长度
使用[]byte获得所有的字节

寻找最长不含重复字符的字串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func lengthOfNonRepeatSubStr(s string) int {
lastOccurred := make(map[rune]int)
start := 0
maxLength := 0

for i, ch := range []rune(s) {

if lastI, ok := lastOccurred[ch]; ok && lastI >= start {
start = lastI + 1
}

if i-start+1 > maxLength {
maxLength = i - start + 1
}
lastOccurred[ch] = i
}
return maxLength
}

其它字符串操作
Fields Split Join
Contains Index
ToLower ToUpper
Trim TrimRight TrimLeft

结构体和方法

面向对象

go仅支持封装,不支持继承和多态

结构体的创建:不论地址还是结构本身,一律使用.来访问成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//定义
type treeNode struct {
value int
left, right *treeNode
}

//工厂函数创建
func createNode(value int) *treeNode {
return &treeNode{value: value} //这里返回的是局部变量的地址给外部使用,在go依旧是可以的
}

func main() {
var root treeNode
fmt.Println(root)

//创建
root = treeNode{value: 3}
root.left = &treeNode{}
root.right = &treeNode{5, nil, nil}
root.right.left = new(treeNode)
fmt.Println(root)
nodes := []treeNode{
{value: 3},
{},
{6, nil, &root},
}
fmt.Println(nodes)

root.left.right = createNode(2)
fmt.Println(root)
}

给结构定义方法:
func后面括号的是方法接收者(其实就跟函数的返回值一样)
只有使用指针才可以改变结构的内容
nil指针也可以调用方法(可以将值传进来,但是nil的赋值会报错,需要做return处理)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//树的遍历:给结构定义方法
func (node *treeNode) setValue(value int) {
if node == nil {
fmt.Println("setting value to nil node")
return
}
node.value = value
}


//值不会变
// func (node treeNode) setValue(value int) {
// node.value = value
// }

func (node *treeNode) setValue(value int) {
node.value = value
}
1
2
3
4
5
6
fmt.Println("print\n")
root.print()

fmt.Println("\nsetValue\n")
root.right.left.setValue(9)
root.right.left.print()

中序遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
```

总结:
要改变内容必须使用指针接收者
结构过大也考虑使用指针接收者



## 包和封装

封装
名字一般使用驼峰命名
首字母大写:public(针对于包)
首字母小写:private(针对于包)



每个目录一个包
main包包含可执行入口
为结构定义的方法必须放在同一个包内
一个包可以放不同的文件
建议结构体都不需要前缀包名(TreeNode改为Node即可)


## 拓展已有类型

定义别名
```bash
type myTreeNode struct {
node *tree.TreeNode
}

//后序遍历
func (myNode *myTreeNode) postOrder() {
if myNode == nil || myNode.node == nil {
return
}

left := myTreeNode{myNode.node.Left}
left.postOrder()

right := myTreeNode{myNode.node.Right}
right.postOrder()

myNode.node.Print()
}

使用组合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package queue

type Queue []int

func (q *Queue) Push(v int) {
*q = append(*q, v)
}

func (q *Queue) Pop() int {
head := (*q)[0]
*q = (*q)[1:]
return head
}

func (q *Queue) IsEmpty() bool {
return len(*q) == 0
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import (
"fmt"
"learngo/queue"
)

func main() {
q := queue.Queue{1}
q.Push(2)
q.Push(3)
fmt.Println(q.Pop())
fmt.Println(q.Pop())
fmt.Println(q.IsEmpty())
fmt.Println(q.Pop())
fmt.Println(q.IsEmpty())
}

使用内嵌来扩展已有类型

1
2
3
type myTreeNode struct {
*tree.Node
}

依赖管理

  • GOPATH
  • GOVENDOR
  • go mod

go mod的使用

引入需要的库

1
2
3
import (
"go.uber.org/zap"
)

直接运行

1
go run zaptest.go

或者

1
2
3
4
5
go get -u 库名 

go mod init modtest

go build ./...

增加依赖

1
go get

更新依赖

1
2
3
go get [@version]

go mod tidy //更新版本之后,使用该命令可以去除多余的文件

目录管理

每个单独目录只有一个mian函数

build当前目录的所有子文件

1
go build ./...

产生结果, 结果放在GOPATH的bin目录

1
2
go install ./...
go env GOPATH

接口的概念

infra

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package infra

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

type Retriver struct{}

func (Retriver) Get(url string) string {
resp, err := http.Get(url)
if err != nil {
panic(err)
}

defer resp.Body.Close()

bytes, _ := ioutil.ReadAll(resp.Body)

return string(bytes)
}

testing

1
2
3
4
5
6
7
package testing

type Retriver struct{}

func (Retriver) Get(url string) string {
return "fake content"
}

download

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
"fmt"
"learngo/downloader/infra"
)

func getRetriver() retriver {
// return testing.Retriver{}//测试
return infra.Retriver{}//真实数据
}

//是infra还是testing的Retriver呢?如何判断呢:通过接口

type retriver interface {
Get(string) string
}

func main() {
var r retriver = getRetriver()
fmt.Printf("%s\n", r.Get("http://www.imooc.com"))

}

duck typing

接口的定义和实现

接口的定义(接口由使用者定义)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import (
"fmt"
"learngo/retriever/mock"
"learngo/retriever/real"
)

type Retriver interface {
Get(url string) string
}

func download(r Retriver) string {
return r.Get("http://www.imooc.com")
}

func main() {
var r Retriver
r = mock.Retriver{"this is a fake imooc.com"}
r = real.Retriver{}
fmt.Println(download(r))
}

接口的实现(不需要实现Retriver接口,只需要实现接口里的方法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package real

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

type Retriver struct {
UserAgent string
TimeOut time.Duration
}

func (r Retriver) Get(url string) string {
resp, err := http.Get(url)
if err != nil {
panic(err)
}

result, err := httputil.DumpResponse(resp, true)

resp.Body.Close()

if err != nil {
panic(err)
}

return string(result)
}

接口的值类型

switch判断类型

1
2
3
4
5
6
7
8
9
10
func inspect(r Retriver) {
fmt.Printf("类型=%T 值=%v\n", r, r)
switch v := r.(type) {
case mock.Retriver:
fmt.Println("Contents:", v.Contents)
case *real.Retriver:
fmt.Println("UserAgent:", v.UserAgent)
}

}

type assertion

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//type assertion

//real
realRetriver := r.(*real.Retriver)
fmt.Println(realRetriver.TimeOut)

//出错写法
realRetriver := r.(real.Retriver)
fmt.Println(realRetriver.TimeOut)

//防止出错
if mockRetriver, ok := r.(mock.Retriver); ok {
fmt.Println(mockRetriver.Contents)
} else {
fmt.Println("not a mock retriver")
}

表示任何类型:interface{}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//改为支持任何类型
type Queue []interface{}

func (q *Queue) Push(v interface{}) {
*q = append(*q, v)
}

func (q *Queue) Pop() interface{} {
head := (*q)[0]
*q = (*q)[1:]
return head
}

func (q *Queue) IsEmpty() bool {
return len(*q) == 0
}

接口的组合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
type Retriver interface {
Get(url string) string
}

type Poster interface {
Post(url string, form map[string]string) string
}

const url = "http://www.imooc.com"

func download(r Retriver) string {
return r.Get(url)
}

func poster(poster Poster) {
poster.Post(url,
map[string]string{
"name": "sam",
"language": "golang",
})
}

type RetriverPoster interface {
Retriver
Poster
}

func session(s RetriverPoster) string {
s.Post(url, map[string]string{
"contents": "another facked imooc.com",
})
return s.Get(url)
}

常用的系统接口

只需要看系统函数的定义就可以了。

  • Stringer
  • Reader/Writer

函数式编程

参数、变量、返回值都可以是函数
高阶函数(函数的参数依旧是个函数)
函数闭包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//返回一个函数
func adder() func(int) int {
sum := 0
return func(v int) int {
sum += v
return sum
}
}
func main() {
a := adder()
for i := 0; i < 10; i++ {
fmt.Println((a(i)))
}
}

斐波那契数列

1
2
3
4
5
6
7
func fibonacci() func() int {
a, b := 0, 1
return func() int {
a, b = b, a+b
return a
}
}

为函数实现接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
type intGen func() int

func (g intGen) Read(
p []byte) (n int, err error) {
next := g()
if next > 10000 {
return 0, io.EOF
}
s := fmt.Sprintf("%d\n", next)

// TODO: incorrect if p is too small!
return strings.NewReader(s).Read(p)
}



func printFileContents(reader io.Reader) {
scanner := bufio.NewScanner(reader)

for scanner.Scan() {
fmt.Println(scanner.Text())
}
}

func main() {
var f intGen = fib.Fibonacci()
printFileContents(f)
}

利用函数遍历二叉树

defer

确保调用在函数结束时发生

1
2
3
4
5
func tryDefer() {
defer fmt.Println(1)
defer fmt.Println(2)
defer fmt.Println(3)
}

defer是一个栈,先进后出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
func tryDefer() {
defer fmt.Println(1)
defer fmt.Println(2)
defer fmt.Println(3)

for i := 0; i < 100; i++ {
defer fmt.Println(i)

if i == 30 {
panic("printed too many")
}
}
}

func writeFle(filename string) {
file, err := os.Create(filename)
if err != nil {
panic(err)
}
defer file.Close()

writer := bufio.NewWriter(file)
defer writer.Flush()

f := fib.Fibonacci()
for i := 0; i < 20; i++ {
fmt.Fprintln(writer, f())
}
}

func main() {
writeFle("fib.txt")

tryDefer()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
➜  defer go run defer.go
30
29
28
27
26
25
24
23
22
21
20
19
18
17
16
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
3
2
1
panic: printed too many

goroutine 1 [running]:
main.tryDefer()
/Users/samtake/Desktop/learngo/hello/errHandling/defer/defer.go:19 +0x2fc
main.main()
/Users/samtake/Desktop/learngo/hello/errHandling/defer/defer.go:43 +0x3b
exit status 2

何时调用defer

  • Open/Close

  • Lock/Unlock

  • PrintHeader/PrintFooter

错误处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
func tryDefer() {
for i := 0; i < 100; i++ {
defer fmt.Println(i)
if i == 30 {
// Uncomment panic to see
// how it works with defer
// panic("printed too many")
}
}
}

func writeFle(filename string) {
file, err := os.OpenFile(filename,
os.O_EXCL|os.O_CREATE|os.O_WRONLY, 0666)

if err != nil {
if pathError, ok := err.(*os.PathError); !ok {
panic(err)
} else {
fmt.Printf("%s, %s, %s\n",
pathError.Op,
pathError.Path,
pathError.Err)
}
return
}
defer file.Close()

writer := bufio.NewWriter(file)
defer writer.Flush()

f := fib.Fibonacci()
for i := 0; i < 20; i++ {
fmt.Fprintln(writer, f())
}
}

服务器统一出错处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
type appHandler func(writer http.ResponseWriter,
request *http.Request) error

func errWrapper(
handler appHandler) func(
http.ResponseWriter, *http.Request) {
return func(writer http.ResponseWriter,
request *http.Request) {
// panic
defer func() {
if r := recover(); r != nil {
log.Printf("Panic: %v", r)
http.Error(writer,
http.StatusText(http.StatusInternalServerError),
http.StatusInternalServerError)
}
}()

err := handler(writer, request)

if err != nil {
log.Printf("Error occurred "+
"handling request: %s",
err.Error())

// user error
if userErr, ok := err.(userError); ok {
http.Error(writer,
userErr.Message(),
http.StatusBadRequest)
return
}

// system error
code := http.StatusOK
switch {
case os.IsNotExist(err):
code = http.StatusNotFound
case os.IsPermission(err):
code = http.StatusForbidden
default:
code = http.StatusInternalServerError
}
http.Error(writer,
http.StatusText(code), code)
}
}
}

type userError interface {
error
Message() string
}

func main() {
http.HandleFunc("/",
errWrapper(filelisting.HandleFileList))

err := http.ListenAndServe(":8888", nil)
if err != nil {
panic(err)
}
}

panic和recover

panic
停止当前函数执行
一直向上返回,执行每一层的defer
如果没有遇见recover,程序就退出

recover
仅在defer调用中使用
获取panic的值
如果无法处理,可重新panic

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func tryRecover() {
defer func() {
r := recover()
if r == nil {
fmt.Println("Nothing to recover. " +
"Please try uncomment errors " +
"below.")
return
}
if err, ok := r.(error); ok {
fmt.Println("Error occurred:", err)
} else {
panic(fmt.Sprintf(
"I don't know what to do: %v", r))
}
}()

}

测试

go test 会自动执行路径包下面的“_test.go”文件(包括文件名包含 “_test.go” 的源文件),我们管称这些是测试文件,里面包含着你的测试函数,测试用例等。更多用法可以输入 “go help test” 查看。

1
go test

代码覆盖率和性能测试

1
2
3
4
```

## 使用pprof进行性能调优
```bash

测试http服务器

1
2
3
4
```

## 生成文档和实力代码
```bash

Goroutine

轻量级“线程”
非抢占式多任务处理,由协程主动交出控制权(Printf是个IO操作,内部做了控制权切换)
多个协程可能在一个或多个线程上运行

1
2
3
4
5
6
7
8
9
10
11
func main() {
for i := 0; i < 1000; i++ {
go func(i int) {
for {
fmt.Printf("hello form goroutine %d\n", i)
}
}(i)
}

time.Sleep(time.Millisecond)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

func tryGoroutine() {
var a [10]int
for i := 0; i < 10; i++ {
go func(ii int) {
for {
a[ii]++
runtime.Gosched()//主动交出控制权
}
}(i)
}

time.Sleep(time.Millisecond)
fmt.Println(a)
}

数据访问的冲突

1
go run -race goroutine.go

查看cpu占用率

1
top

go语言的调度器

任何函数只需加上go就能送给调度器运行
不需要在定义时区分是否是异步函数
调度器在合适的点进行切换
使用-race来检测数据访问冲突

goroutine 可能的切换点
I/O、select
channel
等待锁
函数调用(有时会)
runtime.Gosched()
以上所列只是参考~真实情况就不一定~

Channel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func chanDemo() {
// var c chan int //c == nil
c := make(chan int)

go func() {
for {
n := <-c
fmt.Println(n)
}
}()
c <- 1
c <- 2 //如果main退出了,2是没有输出的
time.Sleep(time.Millisecond)

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func worker(c chan int) {
func() {
for {
n := <-c
fmt.Println(n)
}
}()
}
func chanDemo() {
// var c chan int //c == nil
c := make(chan int)

go worker(c)
c <- 1
c <- 2 //如果main退出了,2是没有输出的
time.Sleep(time.Millisecond)

}

channel作为参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
func worker(id int, c chan int) {
func() {
for {
fmt.Printf("Worker %d received %c\n", id, <-c)
}
}()
}
func chanDemo() {
var channels [10]chan int

for i := 0; i < 10; i++ {
channels[i] = make(chan int)
go worker(i, channels[i])
}

for i := 0; i < 10; i++ {
channels[i] <- 'a' + i
}

for i := 0; i < 10; i++ {
channels[i] <- 'A' + i
}

time.Sleep(time.Millisecond)

}

channel作为返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
func creatWorker(id int) chan<- int {
c := make(chan int)
go func() {
for {
fmt.Printf("Worker %d received %c\n", id, <-c)
}
}()

return c
}
func chanDemo() {
var channels [10]chan<- int

for i := 0; i < 10; i++ {
channels[i] = creatWorker(i)
}

for i := 0; i < 10; i++ {
channels[i] <- 'a' + i
}

for i := 0; i < 10; i++ {
channels[i] <- 'A' + i
}

time.Sleep(time.Millisecond)

}

缓存channel

1
2
3
4
5
6
7
8
9
10
11
func work(id int, c chan int) {
for {
fmt.Printf("Worker %d received %c\n", id, <-c)
}
}
func bufferedChannel() {
c := make(chan int, 3) //给个缓存3
go work(0, c)
c <- 1
time.Sleep(time.Millisecond)
}

关闭通道

1
2
3
4
5
6
7
8
9
10
func channelClose() {
c := make(chan int)
go work(0, c)
c <- 'a'
c <- 'b'
c <- 'c'
c <- 'd'
close(c)
time.Sleep(time.Millisecond)
}

关闭之后,依旧能收到(空串,0)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Worker 0 received a
Worker 0 received b
Worker 0 received c
Worker 0 received d
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 r

解决关闭之后不再输出

1
2
3
4
5
6
7
8
9
10
func work(id int, c chan int) {
for {
n, ok := <-c
if !ok {
break
}

fmt.Printf("Worker %d received %c\n", id, n)
}
}
1
2
3
4
5
6
7
func work(id int, c chan int) {
for {
for n := range c {
fmt.Printf("Worker %d received %c\n", id, n)
}
}
}

使用channel等待goroutine结束(重点)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package main

import (
"fmt"
"sync"
)

func doWork(id int,
w worker) {
for n := range w.in {
fmt.Printf("Worker %d received %c\n",
id, n)
w.done()
}
}

type worker struct {
in chan int
done func()
}

func createWorker(
id int, wg *sync.WaitGroup) worker {
w := worker{
in: make(chan int),
done: func() {
wg.Done()
},
}
go doWork(id, w)
return w
}

func chanDemo() {
var wg sync.WaitGroup

var workers [10]worker
for i := 0; i < 10; i++ {
workers[i] = createWorker(i, &wg)
}

wg.Add(20)
for i, worker := range workers {
worker.in <- 'a' + i
}
for i, worker := range workers {
worker.in <- 'A' + i
}

wg.Wait()
}

func main() {
chanDemo()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Worker 7 received h
Worker 5 received f
Worker 0 received a
Worker 1 received b
Worker 1 received B
Worker 4 received e
Worker 9 received j
Worker 8 received i
Worker 2 received c
Worker 0 received A
Worker 6 received g
Worker 3 received d
Worker 9 received J
Worker 6 received G
Worker 7 received H
Worker 2 received C
Worker 4 received E
Worker 8 received I
Worker 3 received D
Worker 5 received F

使用Channel进行书的遍历

1
2


使用Select进行调度(重点)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package main

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

func generator() chan int {
out := make(chan int)
go func() {
i := 0
for {
time.Sleep(
time.Duration(rand.Intn(1500)) *
time.Millisecond)
out <- i
i++
}
}()
return out
}

func worker(id int, c chan int) {
for n := range c {
time.Sleep(time.Second)
fmt.Printf("Worker %d received %d\n",
id, n)
}
}

func createWorker(id int) chan<- int {
c := make(chan int)
go worker(id, c)
return c
}

func main() {
var c1, c2 = generator(), generator()
var worker = createWorker(0)

var values []int
tm := time.After(10 * time.Second)
tick := time.Tick(time.Second)
for {
var activeWorker chan<- int
var activeValue int
if len(values) > 0 {
activeWorker = worker
activeValue = values[0]
}

select {
case n := <-c1:
values = append(values, n)
case n := <-c2:
values = append(values, n)
case activeWorker <- activeValue:
values = values[1:]

case <-time.After(800 * time.Millisecond):
fmt.Println("timeout")
case <-tick:
fmt.Println(
"queue len =", len(values))
case <-tm:
fmt.Println("bye")
return
}
}
}

同步机制

WaitGroup
Mutex
Cond

1
2
3
4
```

## 迷宫算法
```bash

http标准库

使用http客户端发送请求
使用http.Client控制请求头部等
使用httputil简化工作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package main

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

func main() {
request, err := http.NewRequest(
http.MethodGet,
"http://www.imooc.com", nil)
request.Header.Add("User-Agent",
"Mozilla/5.0 (iPhone; CPU iPhone OS 10_3 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) CriOS/56.0.2924.75 Mobile/14E5239e Safari/602.1")

client := http.Client{
CheckRedirect: func(
req *http.Request,
via []*http.Request) error {
fmt.Println("Redirect:", req)
return nil
},
}
resp, err := client.Do(request)
if err != nil {
panic(err)
}
defer resp.Body.Close()

s, err := httputil.DumpResponse(resp, true)
if err != nil {
panic(err)
}

fmt.Printf("%s\n", s)
}

pprof

1
_ "net/http/pprof"
1
...../debug/pprof/
1
go tool pprof [地址]

其它标准库

1
2
3
4
5
6
bufio
log
encoding/json
regexp
time
strings/math/rand

获取文档

1
godoc -http :8888
1
http://docscn.studygolang.com/

gin&增加middleware

拉取gin和日志库zap

1
2
go get -u github.com/gin-gonic/gin
go get -u go.uber.org/zap
1
2
3
4
5
6
7
8
9
10
11
12
13
package main

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

func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.Run()
}
1
http://localhost:8080/ping
1
2
3
4
5
6
listen tcp :8080: bind: address already in use 问题解决
命令行 lsof -i:8080 这里8080是我要释放的端口号

可以看到,该端口被id为51217的进程所占用,这个时候直接在命令行输入

kill 51217
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import (
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)

func main() {
r := gin.Default()

logger, err := zap.NewProduction()
if err != nil {
panic(err)
}

r.Use(func(c *gin.Context) {
logger.Info("incoming request", zap.String("path", c.Request.URL.Path))
c.Next()
})

r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})

r.GET("/hello", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "hello",
})
})

r.Run()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import (
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"math/rand"
"time"
)

const keyRequestId = "requestId"

func main() {
r := gin.Default()
logger, err := zap.NewProduction()
if err != nil {
panic(err)
}

r.Use(func(c *gin.Context) {
s := time.Now()

c.Next()

logger.Info("incoming request",
zap.String("path", c.Request.URL.Path),
zap.Int("status", c.Writer.Status()),
zap.Duration("elapsed", time.Now().Sub(s)))
}, func(c *gin.Context) {
c.Set(keyRequestId, rand.Int())

c.Next()
})

r.GET("/ping", func(c *gin.Context) {
h := gin.H{
"message": "pong",
}
if rid, exists := c.Get(keyRequestId); exists {
h[keyRequestId] = rid
}
c.JSON(200, h)
})
r.GET("/hello", func(c *gin.Context) {
c.String(200, "hello")
})
r.Run()
}

使用正则表达式

1
2
3
4
5
6
7
func main() {
re := regexp.MustCompile(`[a-zA-Z0-9]+@[a-zA-Z0-9]+\.[a-zA-Z0-9]+`)
match := re.FindAllString(text, -1)
fmt.Println(match)
}

[sam@gmail.com 5555@qq.com 5555@163.com]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func main() {
re := regexp.MustCompile(`([a-zA-Z0-9]+)@([a-zA-Z0-9]+)\.([a-zA-Z0-9]+)`)
match := re.FindAllStringSubmatch(text, -1)
fmt.Println(match)

for _, m := range match {
fmt.Println(m)
}
}


[[sam@gmail.com sam gmail com] [5555@qq.com 5555 qq com] [5555@163.com 5555 163 com]]
[sam@gmail.com sam gmail com]
[5555@qq.com 5555 qq com]
[5555@163.com 5555 163 com]

包管理(包引入)

1
go mod init [crawler-single-task]
1
2
3
4
5
6
7
8
9
➜  crawler git:(master) ✗ cat go.mod                     
module crawler-single-task

go 1.13

require (
golang.org/x/net v0.0.0-20191207000613-e7e4b65ae663
golang.org/x/text v0.3.2
)

以项目当前目录为起点

1
2
3
4
import (
"crawler-single-task/engine"
"crawler-single-task/zhenai/parser"
)

单任务爬虫要点笔记

转换 UTF-8 与 GBK 编码的文本

1
go get golang.org/x/text
1
utf8Reader := transform.NewReader(resp.Body, simplifiedchinese.GBK.NewDecoder())

确定编码库

1
go get golang.org/x/net/html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//确定编码
func determinEncoding(r io.Reader) encoding.Encoding {
bytes, err := bufio.NewReader(r).Peek(1024)
if err != nil {
panic(err)
}
e, _, _ := charset.DetermineEncoding(bytes, "")

return e
}


//转码
e := determinEncoding(resp.Body)
utf8Reader := transform.NewReader(resp.Body, e.NewDecoder())

获取城市名和链接的方式

使用css选择器$('#cityList>dd>a')

使用xpath

使用正则表达式

1
2
3
4
5
func main() {
re := regexp.MustCompile(`[a-zA-Z0-9]+@[a-zA-Z0-9]+\.[a-zA-Z0-9]+`)
match := re.FindAllString(text, -1)
fmt.Println(match)
}

httprouter是一个高可用的http路由请求库。路由器通过请求方法和路径来匹配传入的请求。如果为该路径和方法注册了句柄,路由器会将请求委托给该函数。对于方法GET、POST、PUT、PATCH、DELETE和OPTIONS,存在注册句柄的快捷功能,对于所有其他方法路由器。可以使用手柄。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
"fmt"
"github.com/julienschmidt/httprouter"
"net/http"
"log"
)

func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
fmt.Fprint(w, "Welcome!\n")
}

func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name"))
}

func main() {
router := httprouter.New()
router.GET("/", Index)
router.GET("/hello/:name", Hello)

log.Fatal(http.ListenAndServe(":8080", router))
}

httprouter的使用

restlet测试插件

api

3-6

总体思路:
handleer->validation{1.request,2.user}->business login ->response

  1. data model
  2. error handler

这个错误接口以后都可以这样写了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package defs

type Err struct {
Error string `json:"error"`
ErrorCode string `json:"error_code"`
}

type ErrResponse struct {
HttpSC int
Error Err
}

var (
ErrorRequestBodyParseFailed = ErrResponse{HttpSC: 400, Error: Err{Error: "Request body is not correct", ErrorCode: "001"}}
ErrorNotAuthUser = ErrResponse{HttpSC: 401, Error: Err{Error: "User authentication failed.", ErrorCode: "002"}}
ErrorDBError = ErrResponse{HttpSC: 500, Error: Err{Error: "DB ops failed", ErrorCode: "003"}}
ErrorInternalFaults = ErrResponse{HttpSC: 500, Error: Err{Error: "Internal service error", ErrorCode: "004"}}
)

3-7

好好理解这里的数据库为什么这样设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
create table comments (
id varchar(64) not null,
video_id varchar(64),
author_id int(10),
content text,
time datetime default current_timestamp, primary key(id)
);

create table sessions (
session_id tinytext not null,
TTL tinytext,
login_name text
);
alter table sessions add primary key (session_id(64));

create table users (
id int unsigned not null auto_increment,
login_name varchar(64),
pwd text not null,
unique key (login_name),
primary key (id)
);

create table video_del_rec (
video_id varchar(64) not null,
primary key (video_id)
);

create table video_info (
id varchar(64) not null,
author_id int(10),
name text,
display_ctime text,
create_time datetime default current_timestamp,
primary key (id)
);

第三范式(Third Normal Form,3rd NF)就是指表中的所有数据元素不但要能唯一地被主关键字所标识,而且它们之间还必须相互独立,不存在其他的函数关系。也就是说,对于一个满足2nd NF 的数据结构来说,表中有可能存在某些数据元素依赖于其他非关键字数据元素的现象,必须消除。

3-8

数据库连接还是参考自己写的这个吧demo吧。

3-10

api_test.go

参考Golang练习笔记23测试讲的更详细。

3-12

实现和验证video

3-13

Comments


评论是需要列表出现的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
func ListComments(vid string, from, to int) ([]*defs.Comment, error) {
stmtOut, err := dbConn.Prepare(`SELECT comments.id, users.login_name, comments.content FROM comments
INNER JOIN users ON comments.author_id = users.id
WHERE comments.video_id = ? AND comments.time > FROM_UNIXTIME(?) AND comments.time <= FROM_UNIXTIME(?)
ORDER BY comments.time DESC`)

var res []*defs.Comment

rows, err := stmtOut.Query(vid, from, to)
if err != nil {
log.Printf("%s", err)
return res, err
}

for rows.Next() {
var id, name, content string
if err := rows.Scan(&id, &name, &content); err != nil {
return res, err
}

c := &defs.Comment{Id: id, VideoId: vid, Author: name, Content: content}
res = append(res, c)
}

defer stmtOut.Close()

return res, nil
}

3-15

session会话,

session和cookie的区别?

session负责的流程图:

3-17

middleware

duck typing内容参考Golang练习笔记15。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
type middleWareHandler struct {
r *httprouter.Router
}

func NewMiddleWareHandler(r *httprouter.Router) http.Handler {
m := middleWareHandler{}
m.r = r
return m
}

func (m middleWareHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
//check session
validateUserSession(r)

m.r.ServeHTTP(w, r)
}

func RegisterHandlers() *httprouter.Router {
router := httprouter.New()
router.POST("/user", CreateUser)
...
}
1
2
3
4
5
6
7
8
9
10
//response
type SignedUp struct {
Success bool `json:"success"`
SessionId string `json:"session_id"`
}
#添了后缀,在使用时,会返回统一的json格式,如下:
{
"success":XXXXX,
"session_id":XXXX
}

Streaming

4-1

Streaming(视频播放):

  • 静态视频,非RTMP(直播的那些都是RTMP)
  • 独立的服务,可独立部署
  • 统一的api格式

4-3

流控
token bucket

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#共享通道 instead of shared memory

type ConnLimiter struct {
concurrentConn int
bucket chan int
}

func NewConnLimiter(cc int) *ConnLimiter {
return &ConnLimiter {
concurrentConn: cc,
bucket: make(chan int, cc),
}
}

func (cl *ConnLimiter) GetConn() bool {
if len(cl.bucket) >= cl.concurrentConn {
log.Printf("Reached the rate limitation.")
return false
}

cl.bucket <- 1
return true
}

func (cl *ConnLimiter) ReleaseConn() {
c :=<- cl.bucket
log.Printf("New connction coming: %d", c)
}

4-5

在http middleware中嵌入流控

scheduler

5-1

scheduler任务调度器,某些不能马上处理的任务放到scheduler,让它定期或者延时触发。

5-3

runner的生产消费者模型实现

5-5

task

api->videoid->mysql
dispatcher->mysql->videoid->datachannel
executor->datachannel->videoid->delete video

5-6

timer

setup->strat{runner task}

5-7

前端

6-1

Go的模版引擎

  • 模版引擎是将html解析和元素预置替换生成最终页面的工具
  • Go的模版有两种text/template和html/template
  • Go的模版采用动态生成的模式

6-2

前端代码架构

client.go就是起了proxy转发作用,避免跨域作用。

6-3

静态页面渲染

6-4

sh build.sh

6-6 api透传!!!

6-7 proxy转发

net/url

net/http/httputil

1
2
3
4
5
6
7
8
9
10
11
func proxyVideoHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
u, _ := url.Parse("http://" + config.GetLbAddr() + ":9000/")
proxy := httputil.NewSingleHostReverseProxy(u)
proxy.ServeHTTP(w, r)
}

func proxyUploadHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
u, _ := url.Parse("http://" + config.GetLbAddr() + ":9000/")
proxy := httputil.NewSingleHostReverseProxy(u)
proxy.ServeHTTP(w, r)
}

6-11

js

部署

7-4

公共配置,即各模块的config文件。

7-5 vendor

7-6 SLB讲解与配置

阿里云负载均衡配置。

7-7

session容错

7-8 ECS配置

7-10 部署脚本

在bin文件夹添加conf.json文件

1
2
3
4
{
"lb_addr": "127.0.0.1",
"oss_addr": "oss.aliyun.com"
}

buildprod.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#! /bin/bash

# Build web and other services

cd ~/work/src/github.com/avenssi/video_server/api
env GOOS=linux GOARCH=amd64 go build -o ../bin/api

cd ~/work/src/github.com/avenssi/video_server/scheduler
env GOOS=linux GOARCH=amd64 go build -o ../bin/scheduler

cd ~/work/src/github.com/avenssi/video_server/streamserver
env GOOS=linux GOARCH=amd64 go build -o ../bin/streamserver

cd ~/work/src/github.com/avenssi/video_server/web
env GOOS=linux GOARCH=amd64 go build -o ../bin/web

deploy.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#! /bin/bash

cp -R ./templates ./bin/

mkdir ./bin/videos

cd bin

nohup ./api &
nohup ./scheduler &
nohup ./streamserver &
nohup ./web &

echo "deploy finished"

7-11 部署

sh buildprod.sh 编译完成后提交二进制文件。或者提交代码到服务器上直接编译

检查对应server是否启动ps aux | grep <api?stream?scheduler?web>

简述

相对nginx,基于容器的微服务反向代理利器Traefik支持动态的配置反向代理.

如上图左侧公网域名可以通过Traefik监听API动态地转发到右侧的内网服务实例(例如backoffice1服务)。或者说是动态的路由转发。

frontend用于控制访问的路由规则,支持单个规则及正则匹配。
backend用于匹配一组服务实例,通过轮询方式来选择转发的目标。

基于Docker-compose与Traefik1.x的容器化部署

具体参考service_dc和traefik_dc

学习资料

|

|

|

简述

项目地址:go-micro

go-micro提供的接口功能

服务发现支持服务注册与发现,底层支持etcd、consul、k8s
负载均衡rpc服务间的请求调度均衡策略
同步通信基于RPC通信,支持单向、双向流通道模式
一步通信提供pub、sub通信模型的接口
高级接口比如服务发现,提供调用的接口是一致的

broker异步通信
codec消息编码,可以动态编码
registry服务注册与发现
selector负载均衡
transport 基于RPC的通信模块接口

账号系统微服务

新建一个文件XXX.proto

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
syntax = "proto3";

package go.micro.service.user;

service UserService {
// 用户注册
rpc Signup(ReqSignup) returns (RespSignup) {}
// 用户登录
rpc Signin(ReqSignin) returns (RespSignin) {}
// 获取用户信息
rpc UserInfo(ReqUserInfo) returns (RespUserInfo) {}
// 获取用户文件
rpc UserFiles(ReqUserFile) returns (RespUserFile) {}
// 获取用户文件
rpc UserFileRename(ReqUserFileRename) returns (RespUserFileRename) {}
}

message ReqSignup {
string username = 1;
string password = 2;
}

message RespSignup {
int32 code = 1;
string message = 2;
}

message ReqSignin {
string username = 1;
string password = 2;
}

message RespSignin {
int32 code = 1;
string token = 2;
string message = 3;
}

message ReqUserInfo {
string username = 1;
}

message RespUserInfo {
int32 code = 1;
string message =2;
string username =3;
string email = 4;
string phone = 5;
string signupAt = 6;
string lastActiveAt = 7;
int32 status = 8;
}

message ReqUserFile {
string username = 1;
int32 limit = 2;
}

message RespUserFile {
int32 code = 1;
string message =2;
bytes fileData = 3;
}

message ReqUserFileRename {
string username = 1;
string filehash = 2;
string newFileName = 3;
}

message RespUserFileRename {
int32 code = 1;
string message =2;
bytes fileData = 3;
}

执行指令

1
2

protoc --proto_path=service/account/proto --go_out=service/account/proto --micro_out=service/account/proto service/account/proto/user.proto

这时候会生成两个文件
XXX.micro.go以及XXX.pb.go

handler调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
package handler

import (
"context"
"fmt"
"time"

"filestore-server/common"
"filestore-server/config"
cfg "filestore-server/config"
proto "filestore-server/service/account/proto"
dbcli "filestore-server/service/dbproxy/client"
"filestore-server/util"
)

// User : 用于实现UserServiceHandler接口的对象
type User struct{}

// GenToken : 生成token
func GenToken(username string) string {
// 40位字符:md5(username+timestamp+token_salt)+timestamp[:8]
ts := fmt.Sprintf("%x", time.Now().Unix())
tokenPrefix := util.MD5([]byte(username + ts + "_tokensalt"))
return tokenPrefix + ts[:8]
}

// Signup : 处理用户注册请求
func (u *User) Signup(ctx context.Context, req *proto.ReqSignup, res *proto.RespSignup) error {
username := req.Username
passwd := req.Password

// 参数简单校验
if len(username) < 3 || len(passwd) < 5 {
res.Code = common.StatusParamInvalid
res.Message = "注册参数无效"
return nil
}

// 对密码进行加盐及取Sha1值加密
encPasswd := util.Sha1([]byte(passwd + cfg.PasswordSalt))
// 将用户信息注册到用户表中
dbResp, err := dbcli.UserSignup(username, encPasswd)
if err == nil && dbResp.Suc {
res.Code = common.StatusOK
res.Message = "注册成功"
} else {
res.Code = common.StatusRegisterFailed
res.Message = "注册失败"
}
return nil
}

// Signin : 处理用户登录请求
func (u *User) Signin(ctx context.Context, req *proto.ReqSignin, res *proto.RespSignin) error {
username := req.Username
password := req.Password

encPasswd := util.Sha1([]byte(password + config.PasswordSalt))

// 1. 校验用户名及密码
dbResp, err := dbcli.UserSignin(username, encPasswd)
if err != nil || !dbResp.Suc {
res.Code = common.StatusLoginFailed
return nil
}

// 2. 生成访问凭证(token)
token := GenToken(username)
upRes, err := dbcli.UpdateToken(username, token)
if err != nil || !upRes.Suc {
res.Code = common.StatusServerError
return nil
}

// 3. 登录成功, 返回token
res.Code = common.StatusOK
res.Token = token
return nil
}

// UserInfo : 查询用户信息
func (u *User) UserInfo(ctx context.Context, req *proto.ReqUserInfo, res *proto.RespUserInfo) error {
// 查询用户信息
dbResp, err := dbcli.GetUserInfo(req.Username)
if err != nil {
res.Code = common.StatusServerError
res.Message = "服务错误"
return nil
}
// 查不到对应的用户信息
if !dbResp.Suc {
res.Code = common.StatusUserNotExists
res.Message = "用户不存在"
return nil
}

user := dbcli.ToTableUser(dbResp.Data)

// 3. 组装并且响应用户数据
res.Code = common.StatusOK
res.Username = user.Username
res.SignupAt = user.SignupAt
res.LastActiveAt = user.LastActiveAt
res.Status = int32(user.Status)
// TODO: 需增加接口支持完善用户信息(email/phone等)
res.Email = user.Email
res.Phone = user.Phone
return nil
}

创建一个main.go作为单独的微服务启动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import (
"log"
"time"

"github.com/micro/go-micro"

"filestore-server/service/account/handler"
proto "filestore-server/service/account/proto"

func main() {
//创建一个service
service := micro.NewService(
// service := k8s.NewService(
micro.Name("go.micro.service.user"),
)

// 初始化service, 解析命令行参数等
service.Init()

// 初始化dbproxy client
dbproxy.Init(service)

proto.RegisterUserServiceHandler(service.Server(), new(handler.User))
if err := service.Run(); err != nil {
log.Println(err)
}
}

运行go run service/account/main.go --registry=consul指定注册中心为consul

打开浏览器,访问consul的web界面:
localhost:8500/ui/dcl/services

网关微服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
package handler

import (
"context"
"filestore-server/common"
"filestore-server/util"
"log"
"net/http"

"github.com/gin-gonic/gin"
micro "github.com/micro/go-micro"

cmn "filestore-server/common"
userProto "filestore-server/service/account/proto"
dlProto "filestore-server/service/download/proto"
upProto "filestore-server/service/upload/proto"
)

var (
userCli userProto.UserService
upCli upProto.UploadService
dlCli dlProto.DownloadService
)

func init() {
service := micro.NewService()
// 初始化, 解析命令行参数等
service.Init()

// 初始化一个account服务的客户端
userCli = userProto.NewUserService("go.micro.service.user", service.Client())
// 初始化一个upload服务的客户端
upCli = upProto.NewUploadService("go.micro.service.upload", service.Client())
// 初始化一个download服务的客户端
dlCli = dlProto.NewDownloadService("go.micro.service.download", service.Client())
}

// SignupHandler : 响应注册页面
func SignupHandler(c *gin.Context) {
c.Redirect(http.StatusFound, "/static/view/signup.html")
}

// DoSignupHandler : 处理注册post请求
func DoSignupHandler(c *gin.Context) {
username := c.Request.FormValue("username")
passwd := c.Request.FormValue("password")

resp, err := userCli.Signup(context.TODO(), &userProto.ReqSignup{
Username: username,
Password: passwd,
})

if err != nil {
log.Println(err.Error())
c.Status(http.StatusInternalServerError)
return
}

c.JSON(http.StatusOK, gin.H{
"code": resp.Code,
"msg": resp.Message,
})
}

// SigninHandler : 响应登录页面
func SigninHandler(c *gin.Context) {
c.Redirect(http.StatusFound, "/static/view/signin.html")
}

// DoSigninHandler : 处理登录post请求
func DoSigninHandler(c *gin.Context) {
username := c.Request.FormValue("username")
password := c.Request.FormValue("password")

rpcResp, err := userCli.Signin(context.TODO(), &userProto.ReqSignin{
Username: username,
Password: password,
})

if err != nil {
log.Println(err.Error())
c.Status(http.StatusInternalServerError)
return
}

if rpcResp.Code != cmn.StatusOK {
c.JSON(200, gin.H{
"msg": "登录失败",
"code": rpcResp.Code,
})
return
}

// 动态获取上传入口地址
upEntryResp, err := upCli.UploadEntry(context.TODO(), &upProto.ReqEntry{})
if err != nil {
log.Println(err.Error())
} else if upEntryResp.Code != cmn.StatusOK {
log.Println(upEntryResp.Message)
}

// 动态获取下载入口地址
dlEntryResp, err := dlCli.DownloadEntry(context.TODO(), &dlProto.ReqEntry{})
if err != nil {
log.Println(err.Error())
} else if dlEntryResp.Code != cmn.StatusOK {
log.Println(dlEntryResp.Message)
}

// 登录成功,返回用户信息
cliResp := util.RespMsg{
Code: int(common.StatusOK),
Msg: "登录成功",
Data: struct {
Location string
Username string
Token string
UploadEntry string
DownloadEntry string
}{
Location: "/static/view/home.html",
Username: username,
Token: rpcResp.Token,
UploadEntry: upEntryResp.Entry,
DownloadEntry: dlEntryResp.Entry,
},
}
c.Data(http.StatusOK, "application/json", cliResp.JSONBytes())
}

// UserInfoHandler : 查询用户信息
func UserInfoHandler(c *gin.Context) {
// 1. 解析请求参数
username := c.Request.FormValue("username")

resp, err := userCli.UserInfo(context.TODO(), &userProto.ReqUserInfo{
Username: username,
})

if err != nil {
log.Println(err.Error())
c.Status(http.StatusInternalServerError)
return
}

// 3. 组装并且响应用户数据
cliResp := util.RespMsg{
Code: 0,
Msg: "OK",
Data: gin.H{
"Username": username,
"SignupAt": resp.SignupAt,
// TODO: 完善其他字段信息
"LastActive": resp.LastActiveAt,
},
}
c.Data(http.StatusOK, "application/json", cliResp.JSONBytes())
}

路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package route

import (
"filestore-server/service/apigw/handler"

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

// Router : 网关api路由
func Router() *gin.Engine {
router := gin.Default()

router.Static("/static/", "./static")

// 注册
router.GET("/user/signup", handler.SignupHandler)
router.POST("/user/signup", handler.DoSignupHandler)
// 登录
router.GET("/user/signin", handler.SigninHandler)
router.POST("/user/signin", handler.DoSigninHandler)
// 用户查询
router.POST("/user/info", handler.UserInfoHandler)

// 用户文件查询
router.POST("/file/query", handler.FileQueryHandler)
// 用户文件修改(重命名)
router.POST("/file/update", handler.FileMetaUpdateHandler)

return router
}

main.go

1
2
3
4
5
6
7
8
9
10
package main

import (
"filestore-server/service/apigw/route"
)

func main() {
r := route.Router()
r.Run(":8080")
}

运行go run service/apigw/main.go --registry=consul指定注册中心为consul

文件上传微服务

文件上传需要用到RPC微服务间通信

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package rpc

import (
"context"
cfg "filestore-server/service/upload/config"
upProto "filestore-server/service/upload/proto"
)

// Upload : upload结构体
type Upload struct{}

// UploadEntry : 获取上传入口
func (u *Upload) UploadEntry(
ctx context.Context,
req *upProto.ReqEntry,
res *upProto.RespEntry) error {

res.Entry = cfg.UploadEntry
return nil
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package main

import (
"fmt"
"time"

micro "github.com/micro/go-micro"

cfg "filestore-server/service/upload/config"
upProto "filestore-server/service/upload/proto"
"filestore-server/service/upload/route"
upRpc "filestore-server/service/upload/rpc"
)

func startRpcService() {
service := micro.NewService(
micro.Name("go.micro.service.upload"), // 服务名称
micro.RegisterTTL(time.Second*10), // TTL指定从上一次心跳间隔起,超过这个时间服务会被服务发现移除
micro.RegisterInterval(time.Second*5), // 让服务在指定时间内重新注册,保持TTL获取的注册时间有效
)
service.Init()

upProto.RegisterUploadServiceHandler(service.Server(), new(upRpc.Upload))
if err := service.Run(); err != nil {
fmt.Println(err)
}
}

func startApiService() {
router := route.Router()
router.Run(cfg.UploadServiceHost)
}

func main() {
// api 服务
go startApiService()

// rpc 服务
startRpcService()
}

通过shell脚本启动整个项目的微服务./service/start-all.sh(根据实际情况修改所需要的文件目录)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#!/bin/bash

# 检查service进程
check_process() {
sleep 1
res=`ps aux | grep -v grep | grep "service/bin" | grep $1`
if [[ $res != '' ]]; then
echo -e "\033[32m 已启动 \033[0m" "$1"
return 1
else
echo -e "\033[31m 启动失败 \033[0m" "$1"
return 0
fi
}

# 编译service可执行文件
build_service() {
go build -o service/bin/$1 service/$1/main.go
resbin=`ls service/bin/ | grep $1`
echo -e "\033[32m 编译完成: \033[0m service/bin/$resbin"
}

# 启动service
run_service() {
nohup ./service/bin/$1 --registry=consul >> $logpath/$1.log 2>&1 &
sleep 1
check_process $1
}

# 创建运行日志目录
logpath=/Users/samtake/Documents/GitHub/www/data/log/filestore-server #/data/log/filestore-server

mkdir -p $logpath

# 切换到工程根目录
cd $GOPATH/filestore-server
#cd /data/go/work/src/filestore-server

# 微服务可以用supervisor做进程管理工具;
# 或者也可以通过docker/k8s进行部署

services="
dbproxy
upload
download
transfer
account
apigw
"

# 执行编译service
for service in $services
do
build_service $service
done

# 执行启动service
for service in $services
do
run_service $service
done

echo '微服务启动完毕.'

go-micro学习资料

Go-Micro 基础篇 【1】Hello World:视频 & 文档

#72 Go-Micro 编写微服务实战: 视频地址

Micro 中国站

#62 Go-Micro 微服务框架介绍(一) by 舒先 【 Go 夜读 】

#72 Go-Micro 编写微服务实战 【 Go 夜读 】

#79 Go-Micro 运行时工具集 【 Go 夜读 】

记录一个报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
➜  goRedisDemo git:(master) ✗ go run main.go
test mySQL
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x20 pc=0x10c1141]

goroutine 1 [running]:
sync.(*Mutex).Lock(...)
/usr/local/go/src/sync/mutex.go:74
database/sql.(*DB).conn(0x0, 0x130f9e0, 0xc000018080, 0x1036801, 0xc000000180, 0x300000002, 0xc000000180)
/usr/local/go/src/database/sql/sql.go:1132 +0x41
database/sql.(*DB).prepare(0x0, 0x130f9e0, 0xc000018080, 0x12c9f84, 0x69, 0x102da01, 0x203000, 0xc0000b3d78, 0xc0000b3d68)
/usr/local/go/src/database/sql/sql.go:1431 +0x58
database/sql.(*DB).PrepareContext(0x0, 0x130f9e0, 0xc000018080, 0x12c9f84, 0x69, 0x30, 0x30, 0x128eaa0)
/usr/local/go/src/database/sql/sql.go:1404 +0x99
database/sql.(*DB).Prepare(...)
/usr/local/go/src/database/sql/sql.go:1421
goRedisDemo/db.OnFileUploadFinished(0x12bbb58, 0x8, 0x12bbb60, 0x8, 0x0, 0x0, 0x0, 0x0)
/Users/samtake/Documents/GitHub/goRedisDemo/db/file.go:15 +0x77
goRedisDemo/handler.UploadHandlerDB()
/Users/samtake/Documents/GitHub/goRedisDemo/handler/testDB.go:10 +0x76
main.main()
/Users/samtake/Documents/GitHub/goRedisDemo/main.go:14 +0x7a
exit status 2
➜ goRedisDemo git:(master) ✗ go get database/sql
➜ goRedisDemo git:(master) ✗ go get github.com/go-sql-driver/mysql
➜ goRedisDemo git:(master) ✗ go run main.go
test mySQL
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x20 pc=0x10c1141]

goroutine 1 [running]:
sync.(*Mutex).Lock(...)
/usr/local/go/src/sync/mutex.go:74
database/sql.(*DB).conn(0x0, 0x130f9e0, 0xc0000b0008, 0x1036801, 0xc000000180, 0x300000002, 0xc000000180)
/usr/local/go/src/database/sql/sql.go:1132 +0x41
database/sql.(*DB).prepare(0x0, 0x130f9e0, 0xc0000b0008, 0x12c9f84, 0x69, 0x102da01, 0x203000, 0xc0000dfd78, 0xc0000dfd68)
/usr/local/go/src/database/sql/sql.go:1431 +0x58
database/sql.(*DB).PrepareContext(0x0, 0x130f9e0, 0xc0000b0008, 0x12c9f84, 0x69, 0x30, 0x30, 0x128eaa0)
/usr/local/go/src/database/sql/sql.go:1404 +0x99
database/sql.(*DB).Prepare(...)
/usr/local/go/src/database/sql/sql.go:1421
goRedisDemo/db.OnFileUploadFinished(0x12bbb58, 0x8, 0x12bbb60, 0x8, 0x0, 0x0, 0x0, 0x0)
/Users/samtake/Documents/GitHub/goRedisDemo/db/file.go:22 +0x77
goRedisDemo/handler.UploadHandlerDB()
/Users/samtake/Documents/GitHub/goRedisDemo/handler/testDB.go:10 +0x76
main.main()
/Users/samtake/Documents/GitHub/goRedisDemo/main.go:14 +0x7a
exit status 2

查了一下,原来这是Db变量定义的问题,:=只在当前方法内有效。