[TOC]

0x01 变量类型章节

示例1.编写代码统计出字符串"为 Hello 中国 World,Go 语言 学习"中汉字的数量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 方式1.统计字符串中中文个数
s2 := "为 Hello 中国 World,Go 语言 学习"
res := []rune(s2)
reslen := len(res)
count := 0
for i := 0; i < reslen; i++ {
// UTF-8 由于中文或者其它语言占用3b~4b所以排除(0~255) 英文、符号以及特殊字符
if res[i] > 255 {
count++
}
}
fmt.Printf("字符串:%s (Length = %d),一共有 %d 个中文字符", s2, reslen, count)

// 方式2.利用unicode中提供的方法统计字符串中的个数
count = 0
for _, c := range s2 {
// 判断字符是否该字符集编码,来统计中文字符
if unicode.Is(unicode.Han, c) {
count++
}
}
fmt.Printf("字符串:%s (Length = %d),一共有 %d 个中文字符", s2, reslen, count)

执行结果:

1
字符串:为 Hello 中国 World,Go 语言 学习 (Length = 25),一共有 7 个中文字符


0x02 运算符与流程控制章节

示例1.有一堆数字,如果除了一个数字以外,其他数字都出现了两次,那么如何找到出现一次的数字?

1
2
3
4
5
6
7
8
9
10
11
12
// 查看出现一次的数字
func showsinglenumber() {
numbers := 1234945579785321
snumbers := fmt.Sprintf("%d", numbers)
slength := len(snumbers)
for i := 0; i < slength; i++ {
// 判断其索引位置
if strings.Index(snumbers, string(snumbers[i])) == strings.LastIndex(snumbers, string(snumbers[i])) {
fmt.Println("只出现一次的数字 : ", string(snumbers[i])) // 注意输出时需要强制转换否则为byte类型输出Ascii值
}
}
}

执行结果:

1
只出现一次的数字 :  8


示例2.用Go语言编写一个九九乘法表

1
2
3
4
5
6
7
8
9
// 九九乘法表
func MultiTable() {
for i := 1; i <= 9; i++ {
for j := 1; j <= i; j++ {
fmt.Printf("%d * %d = %d, ", i, j, i*j)
}
fmt.Println()
}
}

WeiyiGeek.GO九九乘法表

WeiyiGeek.GO九九乘法表


0x03 数组章节

示例1:求数组[1, 3, 5, 7, 8]所有元素的和

1
2
3
4
5
6
7
8
9
func homework1() {
// 求数组`[1, 3, 5, 7, 8]`所有元素的和
arr := [...]int{1, 3, 5, 7, 8}
sum := 0
for _, v := range arr {
sum += v
}
fmt.Printf("%v 元素集合之和 : %d", arr, sum)
}

执行结果:
1
[1 3 5 7 8] 元素集合之和 : 24

示例2.找出数组中和为指定值的两个元素的下标,比如从数组[1, 3, 5, 7, 8]中找出和为8的两个元素的下标分别为(0,3)(1,2)

1
2
3
4
5
6
7
8
9
10
11
func homework2() {
// 比如从数组`[1, 3, 5, 7, 8]`中找出和为8的两个元素的下标分别为`(0,3)`和`(1,2)`。**
arr := [...]int{1, 3, 5, 7, 8}
for i, v := range arr {
for j := i + 1; j < len(arr); j++ {
if v+arr[j] == 8 {
fmt.Printf("arr[%d] + arr[%d] = %d \n", i, j, 8)
}
}
}
}

执行结果:
1
2
arr[0] + arr[3] = 8 
arr[1] + arr[2] = 8


示例3.请使用内置的sort包对数组var a = [...]int{3, 7, 8, 9, 1}进行排序(附加题,自行查资料解答)。

1
2
3
4
5
6
7
8
9
10
11
12
# 把数组变成切片
func textsort() {
a := [...]int{3, 7, 8, 9, 1}
fmt.Printf("a : %T , %v , ptr : %p \n", a, a, &a) // 数组
sort.Ints(a[:]) // 排序
fmt.Printf("a[:] : %T , %v , ptr : %p \n", a[:], a[:], a[:]) // 切片
fmt.Println("After sorted: ", a)
}

a : [5]int , [3 7 8 9 1] , ptr : 0xc0000c6000
a[:] : []int , [1 3 7 8 9] , ptr : 0xc0000c6000
After sorted: [1 3 7 8 9]


0x04 切片章节

示例1.请写出下面代码的输出结果

1
2
3
4
5
6
7
func text() {
var a = make([]string, 5, 10)
for i := 0; i < 10; i++ {
a = append(a, fmt.Sprintf("%v", i)) // 扩容 + 10 ,容量 + 10
}
fmt.Println(len(a), cap(a), a)
}

实际执行结果:
1
15 20 [0 1 2 3 4 5 6 7 8 9]


0x05 映射章节

示例1.观察下面代码,写出最终的打印结果。

1
2
3
4
5
6
7
8
9
10
11
func main() {
type Map map[string][]int
m := make(Map)
s := []int{1, 2}
s = append(s, 3)
fmt.Printf("%+v\n", s) // [ 1,2,3 ]
m["s"] = s
s = append(s[:1], s[2:]...) // 关键点
fmt.Printf("%+v\n", s) // [ 1,3 ]
fmt.Printf("%+v\n", m["s"]) // [ 1,3,3 ]
}


示例2.写一个程序,统计一个字符串中每个单词出现的次数。比如:”how do you do”中how=1 do=2 you=1。

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 homework2() {
var a = "how do you do"
var splitA = strings.Split(a, " ")
var count = make(map[string]int, len(splitA))
// 方式1
for _, v := range splitA {
value, ok := count[v]
if !ok {
value = 1
} else {
value++
}
count[v] = value
}
fmt.Printf("统计 %v 字符串每个单词出现的结果: %+v \n", a, count)

// 方式2 (非常值得学习)
for _, w := range splitA {
if _, ok := count[w]; !ok {
count[w] = 1
} else {
count[w]++
}
}
fmt.Printf("统计 %v 字符串每个单词出现的结果: %+v \n", a, count)
}

执行结果:

1
统计 how do you do 字符串每个单词出现的结果: map[do:2 how:1 you:1]


示例3.判断中文字符串的回文如,一行白鹭与鹭白行一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func homework1() {
s := "a一行白鹭与鹭白行一a"
srune := []rune(s)
fmt.Printf("%T, Len : %d , Cap : %d \n", srune, len(srune), cap(srune))
for i := range srune {
if srune[i] != srune[len(srune)-1-i] {
fmt.Printf("`%v`不是回文字符串", s)
return
} else if i >= len(srune)/2 {
fmt.Printf("`%v`是回文字符串", s)
return
}
}
}

执行结果:

1
2
[]int32, Len : 11 , Cap : 12
`a一行白鹭与鹭白行一a`是回文字符串


0x06 函数章节

示例1.分金币

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
/*
你有50枚金币,需要分配给以下几个人:Matthew,Sarah,Augustus,Heidi,Emilie,Peter,Giana,Adriano,Aaron,Elizabeth。
分配规则如下:
a. 名字中每包含1个'e'或'E'分1枚金币
b. 名字中每包含1个'i'或'I'分2枚金币
c. 名字中每包含1个'o'或'O'分3枚金币
d: 名字中每包含1个'u'或'U'分4枚金币
写一个程序,计算每个用户分到多少金币,以及最后剩余多少金币?
程序结构如下,请实现 ‘dispatchCoin’ 函数
*/
var (
coins = 50
users = []string{
"Matthew", "Sarah", "Augustus", "Heidi", "Emilie", "Peter", "Giana", "Adriano", "Aaron", "Elizabeth",
}
distribution = make(map[string]int, len(users))
)

func dispatchCoin() int {
// 1.遍历用户
for _, name := range users {
// 2.判断用户时候是否在 map 中
_, ok := distribution[name]
if ok {
continue
}
// 3.利用匿名函数求出每个人的获得金币数
temp := func() int {
count := 0
for _, crune := range name {

// 方式1
// c := string(crune)
// if c == "e" || c == "E" {
// count += 1
// } else if c == "i" || c == "I" {
// count += 2
// } else if c == "o" || c == "O" {
// count += 3
// } else if c == "u" || c == "U" {
// count += 4
// }
// 方式2(推荐)
switch crune {
case 'e', 'E':
count += 1
coins -= 1
case 'i', 'I':
count += 2
coins -= 2
case 'o', 'O':
count += 3
coins -= 3
case 'u', 'U':
count += 4
coins -= 4
}
}
return count
}()

// 4.将人员和金币数进行绑定
distribution[name] = temp
}
// 5.输出所有人员占有的金币数
fmt.Println(distribution)

// 6.返回剩余金币数量
return coins
}

func homework() {
left := dispatchCoin()
fmt.Println("剩下的金币数:", left)
}

执行结果:
1
2
map[Aaron:3 Adriano:5 Augustus:12 Elizabeth:4 Emilie:6 Giana:2 Heidi:5 Matthew:1 Peter:2 Sarah:0]
剩下的金币数: 10


示例2.有n个台阶,一次可以走一步或者两步,问一共有多少种走法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func ladder(step uint64) (ret uint64) {
// 如果只有一步阶梯则只有一种走法。
if step == 1 {
return 1
}
// 如果有两步阶梯则有两种走法。
if step == 2 {
return 2
}
// 如有三步则有3种走法,所以 n 步 = (n - 1) + (n -2)
return ladder(step-1) + ladder(step-2)
}

func demo2() {
ret := ladder(6)
fmt.Println("当有六步阶梯时,有", ret, "种走法!")
}

执行结果:
1
当有六步阶梯时,有 13 种走法!


0x07 结构体章节

示例1.请问下面代码的执行结果是什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
type student struct {
name string
age int
}

func homework1() {
m := make(map[string]*student)
// 后进先出
stus := []student{
{name: "小王", age: 18},
{name: "娜扎", age: 23},
{name: "大王", age: 900}, // 初始指针指向的位置
}
fmt.Printf("stus : %p \n", &stus)
for _, stu := range stus {
m[stu.name] = &stu
fmt.Printf("for stu :%p, value : %v, Size: %d\n", &stu, stu, unsafe.Sizeof(stu.age))
}
for k, v := range m {
fmt.Println(k, "=>", v.name)
}
}

执行结果:
1
2
3
4
5
6
7
stus : 0xc00000c030 
for stu :0xc00000c048, value : {小王 18}, Size: 8
for stu :0xc00000c048, value : {娜扎 23}, Size: 8
for stu :0xc00000c048, value : {大王 900}, Size: 8
小王 => 大王
娜扎 => 大王
大王 => 大王


示例2.利用函数Funtion实现考生信息系统

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

import (
"fmt"
"os"
)

/** Desc: 采用函数实现考生信息的增加与删除**/
var (
// options
flag uint8
// key : value
allStudent map[int64]*Student
)

// 结构体
type Student struct {
id int64
name string
age uint8
}

// 构造函数
func newStudent(id int64, name string, age uint8) *Student {
return &Student{
id: id,
name: name,
age: age,
}
}

// 验证考生是否存在
func judgeStudent(id int64) bool {
_, ok := allStudent[id]
if ok {
return true
} else {
return false
}
}

// 添加考生
func addStudent() {
var (
id int64
name string
age uint8
)
fmt.Println("#请输入考生 id name age 信息以空格分割:")
fmt.Scan(&id, &name, &age)
if !judgeStudent(id) {
student := newStudent(id, name, age)
allStudent[id] = student
} else {
fmt.Println("该id号的考生已存在无需重复添加.")
}
}

// 删除考生
func delStuednt(id int64) {
if judgeStudent(id) {
delete(allStudent, id)
fmt.Printf("已删除考生号为 %d 的学生信息.\n", id)
} else {
fmt.Println("该id号的考生不存在.")
}

}

// 显示考生
func showStudent() {
for k, v := range allStudent {
fmt.Printf("uid: %d 姓名: %s 年龄: %d\n", k, v.name, v.age)
}
}

func main() {
// Step 0.初始化存放考生信息的Map
allStudent = make(map[int64]*Student)
fmt.Printf("allStudent len %v", len(allStudent))
// Step 1
fmt.Println("欢迎使用学生信息系统简单版本")
fmt.Printf("功能说明: \n1.增加学生\n2.查看学生\n3.删除学生\n4.退出程序\n\n")

// Step 2
for {
fmt.Printf("功能选择: ")
fmt.Scanln(&flag)
fmt.Printf("#你选择第 %d 个的选项\n", flag)
// step 3
switch flag {
case 1:
addStudent()
case 2:
showStudent()
case 3:
fmt.Printf("请输入要删除的学生 uid:")
var uid int64
fmt.Scanln(&uid)
delStuednt(uid)
case 4:
fmt.Printf("程序结束 :")
os.Exit(1)
default:
fmt.Printf("功能说明: \n1.增加学生\n2.查看学生\n3.删除学生\n4.退出程序")
}
}
}

执行结果:
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
欢迎使用学生信息系统简单版本
功能说明:
1.增加学生
2.查看学生
3.删除学生
4.退出程序

功能选择: 1
# 你选择第 1 个的选项
# 请输入考生 id name age 信息以空格分割:
10086 移动 25
功能选择: 1
# 你选择第 1 个的选项
# 请输入考生 id name age 信息以空格分割:
10010 联通 25
功能选择: 1
# 你选择第 1 个的选项
# 请输入考生 id name age 信息以空格分割:
10000 典电信 26
功能选择: 2
# 你选择第 2 个的选项
uid: 10086 姓名: 移动 年龄: 25
uid: 10010 姓名: 联通 年龄: 25
uid: 10000 姓名: 电信 年龄: 26
功能选择: 3
# 你选择第 3 个的选项
请输入要删除的学生 uid:10000
已删除考生号为 10000 的学生信息.
功能选择: 2
# 你选择第 2 个的选项
uid: 10086 姓名: 移动 年龄: 25
uid: 10010 姓名: 联通 年龄: 25


示例3.利用结构体方法进行实现考生信息管理系统

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
package main
import (
"fmt"
"os"
)
/** Desc: 采用结构体方法实现考生信息的增加与删除**/
var (
// options
flag uint8

// key : value
allStudent map[int64]*Student
)

// 结构体
type Student struct {
id int64
name string
age uint8
}

// 构造函数
func newStudent(id int64, name string, age uint8) *Student {
return &Student{
id: id,
name: name,
age: age,
}
}

// 验证考生是否存在
func (s *Student) judge(id int64) bool {
_, ok := allStudent[id]
if ok {
return true
} else {
return false
}
}

// 添加考生
func (s *Student) add() {
var (
id int64
name string
age uint8
)
fmt.Println("#请输入考生 id name age 信息以空格分割:")
fmt.Scan(&id, &name, &age)
if !s.judge(id) {
student := newStudent(id, name, age)
allStudent[id] = student
} else {
fmt.Println("该id号的考生已存在无需重复添加.")
}
}

// 删除考生
func (s *Student) del(id int64) {
if s.judge(id) {
delete(allStudent, id)
fmt.Printf("已删除考生号为 %d 的学生信息.\n", id)
} else {
fmt.Println("该id号的考生不存在.")
}
}

// 显示考生
func (s *Student) show() {
for k, v := range allStudent {
fmt.Printf("uid: %d 姓名: %s 年龄: %d\n", k, v.name, v.age)
}
}

func main() {
// Step 0.初始化存放考生信息的Map
allStudent = make(map[int64]*Student, 48)
student := Student{}

// Step 1
fmt.Println("欢迎使用学生信息系统简单版本")
fmt.Printf("功能说明: \n1.增加学生\n2.查看学生\n3.删除学生\n4.退出程序\n\n")

// Step 2
for {
fmt.Printf("功能选择: ")
fmt.Scanln(&flag)
fmt.Printf("#你选择第 %d 个的选项\n", flag)
// step 3
switch flag {
case 1:
student.add() // 结构体中调用add方法
case 2:
student.show() // 结构体中调用show方法
case 3:
fmt.Printf("请输入要删除的学生 uid:")
var uid int64
fmt.Scanln(&uid)
student.del(uid) // 结构体中调用del方法
case 4:
fmt.Printf("程序结束 :")
os.Exit(1)
default:
fmt.Printf("功能说明: \n1.增加学生\n2.查看学生\n3.删除学生\n4.退出程序")
}
}
}

执行结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
功能选择: 1
#你选择第 1 个的选项
#请输入考生 id name age 信息以空格分割:
10086 移动 25
功能选择: 1
#你选择第 1 个的选项
#请输入考生 id name age 信息以空格分割:
10010 联通 24
功能选择: 2
#你选择第 2 个的选项
uid: 10086 姓名: 移动 年龄: 25
uid: 10010 姓名: 联通 年龄: 24
功能选择: 3
#你选择第 3 个的选项
请输入要删除的学生 uid:10010
已删除考生号为 10010 的学生信息.
功能选择: 2
#你选择第 2 个的选项
uid: 10086 姓名: 移动 年龄: 25
功能选择: 4
#你选择第 4 个的选项
程序结束 :exit status 1


作业4.反转一个单链表
示例说明: 1->2->3->4->5->NULL, 5->4->3->2->1->NULL
代码示例:

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
package main
import "fmt"
// 链表结构体
type ListNode struct {
val int
Next *ListNode
}

// 反转链表函数
func reverseList(head *ListNode) *ListNode {
var pre *ListNode
cur := head
for cur != nil {
tmp := cur.Next // 把下一个节点存放到临时变量中
cur.Next = pre // 首节点赋予下一指向节点(首次Nil)
pre = cur // 将当前节点(首次val为1的地址)赋予pre
cur = tmp // 将上面临时变量地址(首次val为2的地址)赋予cur
}
return pre // 返回反转链表
}

func main() {
// 1.实例化链表
head := &ListNode{
val: 1,
Next: &ListNode{
val: 2,
Next: &ListNode{
val: 3,
Next: &ListNode{
val: 4,
Next: &ListNode{
val: 5,
Next: nil,
},
},
},
},
}

// 链表实例化对象副本拷贝
header := head
fmt.Println("反转前首地址:", header)
for header != nil {
fmt.Printf("%v->", header.val)
header = header.Next
if header == nil {
fmt.Printf("Nil\n")
}
}

// 2.调用反转链表
res := reverseList(head)
fmt.Println("反转后首地址:", res)
for res != nil {
fmt.Printf("%v->", res.val)
res = res.Next
if res == nil {
fmt.Printf("Nil\n")
}
}
}

执行结果:

1
2
3
4
反转前首地址: &{1 0xc000046240}
1->2->3->4->5->Nil
反转后首地址: &{5 0xc000046260}
5->4->3->2->1->Nil

0x08 接口章节

作业1.利用接口既可以求三角形的面积也能求正方形的面积

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 main

import (
"fmt"
"math"
)

type GarphicArea interface {
Square(float64) float64 // 正方形
Triangle(float64, float64, float64) float64 //三角形
}
type GarphicName struct{ Name string }

func (g *GarphicName) Square(a float64) float64 {
// S = a^2
return math.Pow(a, 2)
}

func (g *GarphicName) Triangle(a, b, c float64) float64 {
//(海伦公式)(p=(a+b+c)/2) S=sqrt[p(p-a)(p-b)(p-c)]
p := (a + b + c) / 2
return math.Sqrt(p * (p - a) * (p - b) * (p - c))
}

func main() {
var ga GarphicArea = &GarphicName{"正方形"}
fmt.Println("正方形面积: ", ga.Square(4))
ga = &GarphicName{"三角形"}
fmt.Println("三角形面积: ", ga.Triangle(3, 4, 5))
}

执行结果:
1
2
正方形面积:  16
三角形面积: 6


0x09 包章节

作业1.利用包来封装调用位移运算符进行左移或者右移多少位,并输出其二进制表示的结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// # weiyigeek.top/custom/pkg/bitmove.go
// # packeageName bitmove
package bitmove
import "fmt"
func Leftmove(n int, shift int) {
fmt.Printf("%v << %v = %08b\n", n, shift, n<<shift)
}
func RightMove(n int, shift int) {
fmt.Printf("%v >> %v = %08b\n", n, shift, n>>shift)
}

// # 主入口文件我们可使用import来导入我们自己编写的包(注意包中属性、函数、结构体、接口需要首字母大小才能被外部引用)
// # weiyigeek.top/studygo/homework/2.packeage/displacement.go
package main
import (
"fmt"
bitmove "weiyigeek.top/custom/pkg"
)
func main() {
bitmove.Leftmove(4, 2)
bitmove.RightMove(10, 2)
}

执行结果:

1
2
4 << 2 = 00010000
10 >> 2 = 00000010


0x10 Time章节

作业1.获取当前时间,格式化输出为2006/01/02 15:04:05`格式。

fmt.Println(“当前时间: “, time.Now().Format(“2006/01/02 15:04:05”))
当前时间: 2021/09/27 15:02:31


作业2.编写程序统计一段代码的执行耗时时间,单位精确到微秒。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func demo2() {
// 开始时间
startTime := time.Now()

// 从1+...+1000之和
sum := 0
for i := 0; i <= 1000; i++ {
sum += i
}
fmt.Println("从1+...+1000之和为", sum)

// 结束时间
endTime := time.Now()

fmt.Println("该段代码耗时时间为:", endTime.Sub(startTime))
}

// ==== 执行结果 ====
1+...+1000之和为 500500
该段代码耗时时间为: 6.765µs


0x11 阶段性综合实践

日志库
程序需求分析:

  • (1) 支持向不同的地方输出日志,例如文件或者终端stdout
  • (2) 日志多级别展示,例如Debug/Trace/Info/Warning/Error/Falat
  • (3) 开发测试与线上生产定义显示级别(Info 及以上)。
  • (4) 日志出现异常时间,异常错误文件,函数及其行数。
  • (5) 日志文件切割(按照小时、天、文件大小切割)


项目代码演示:

  • (1) weiyigeek.top/custom/mlogger/consolelogger.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
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
package mlogger

import (
"errors"
"fmt"
"path"
"runtime"
"strings"
"time"
)

// 1.自定义日志库等级
type Loglevel uint8

const (
UNKNOWN Loglevel = iota
DEBUG
TRACE
INFO
WARNING
ERROR
FATAL
)

// 2.解析日志等级方法
func parseLogLevel(s string) (Loglevel, error) {
s = strings.ToUpper(s)
switch s {
case "DEBUG":
return DEBUG, nil
case "TRACE":
return TRACE, nil
case "INFO":
return INFO, nil
case "WARNING":
return WARNING, nil
case "ERROR":
return ERROR, nil
case "FATAL":
return FATAL, nil
default:
// 主意不能以标点符号来结束错误信息抛出
err := errors.New("无效的日志级别-Log Levels Error")
return UNKNOWN, err
}
}

// 3.ConsoleLogger结构体
type ConsoleLogger struct {
level Loglevel
}

// 4.ConsoleLogger结构体构造方法
func NewConsoleLog(level string) ConsoleLogger {
logLevel, err := parseLogLevel(level)
if err != nil {
fmt.Print(err)
}
// 返回ConsoleLogger结构体对象
return ConsoleLogger{
level: logLevel,
}
}

// 5.解析日志等级字符串名称
func getLevelName(lv Loglevel) string {
switch lv {
case DEBUG:
return "DEBUG"
case TRACE:
return "TRACE"
case INFO:
return "INFO"
case WARNING:
return "WARNING"
case ERROR:
return "ERROR"
case FATAL:
return "FATAL"
default:
return "UNKNOWN"
}
}

// 6.获取错误的函数名 文件 以及 行号的方法
func getFileLine(skip int) (funcName, fileName string, lineNum int) {
pc, fileName, lineNum, ok := runtime.Caller(skip)
if !ok {
fmt.Printf("Runtime,Caller() failed\n")
}
funcName = runtime.FuncForPC(pc).Name()
fileName = path.Base(fileName)
funcName = strings.Split(funcName, ".")[1]
return
}

// 7.日志显示的等级(当前日志级别大传入的日志级别则显示)
func (l ConsoleLogger) enable(Loglevel Loglevel) bool {
return Loglevel >= l.level
}

// 8.向终端打印日志的方法
func printLogs(lv, format string, custom ...interface{}) {
// 错误信息拼接
msg := fmt.Sprintf(format, custom...)

// 时间&时区
now := time.Now()
//timezone, _ := time.LoadLocation("Asia/Shanghai")

// 错误文件以及函数名称
funcName, fileName, linwNum := getFileLine(2)

fmt.Printf("[%s] [%s] [%s:%s:%d] %s \n", now.Format("2006-01-02 15:04:05"), lv, fileName, funcName, linwNum, msg)
}

// 9.各日志等级打印
// Debug
func (l ConsoleLogger) Debug(format string, custom ...interface{}) {
// 满足等级则打印日志
if l.enable(DEBUG) {
printLogs("DEBUG", format, custom...)
}
}

// Trace
func (l ConsoleLogger) Trace(format string, custom ...interface{}) {
// 满足等级则打印日志
if l.enable(TRACE) {
printLogs("TRACE", format, custom...)
}
}

// Info
func (l ConsoleLogger) Info(format string, custom ...interface{}) {
// 满足等级则打印日志
if l.enable(INFO) {
printLogs("INFO", format, custom...)
}
}

// Error
func (l ConsoleLogger) Error(format string, custom ...interface{}) {
// 满足等级则打印日志
if l.enable(ERROR) {
printLogs("ERROR", format, custom...)
}
}

// Warning
func (l ConsoleLogger) Warning(format string, custom ...interface{}) {
// 满足等级则打印日志
if l.enable(WARNING) {
printLogs("WARNING", format, custom...)
}
}

// Falat
func (l ConsoleLogger) Falat(format string, custom ...interface{}) {
// 满足等级则打印日志
if l.enable(FATAL) {
printLogs("FATAL", format, custom...)
}
}

// 方法接口定义
type Logger interface {
Debug(format string, custom ...interface{})
Trace(format string, custom ...interface{})
Info(format string, custom ...interface{})
Warning(format string, custom ...interface{})
Error(format string, custom ...interface{})
Falat(format string, custom ...interface{})
}


  • (2) weiyigeek.top/custom/mlogger/filelogger.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
    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
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    package mlogger

    // 主要向文件里写入日志相关信息

    import (
    "fmt"
    "os"
    "path"
    "time"
    )

    // 1.文件记录日志结构体
    type FileLogger struct {
    level Loglevel
    filePath string
    fileName string
    fileObj *os.File
    errFileObj *os.File
    maxFileSize int64
    }

    // 2.文件日志构造方法
    func NewFileLogger(levelStr, fp, fn string, maxSize int64) *FileLogger {
    // 解析日志等级
    Loglevel, err := parseLogLevel(levelStr)
    if err != nil {
    panic(err)
    }

    // 文件日志结构体对象
    fobj := &FileLogger{
    level: Loglevel,
    filePath: fp,
    fileName: fn,
    maxFileSize: maxSize,
    }

    // 初始化文件日志文件句柄
    initErr := fobj.init()
    if initErr != nil {
    panic(initErr)
    }

    // 返回对象
    return fobj
    }

    // 3.文件日志显示的等级控制方法(当前日志级别大传入的日志级别则显示)
    func (f FileLogger) enable(Loglevel Loglevel) bool {
    return Loglevel >= f.level
    }

    // 4.初始化文件日志文件句柄方法
    func (f *FileLogger) init() error {
    // 通用日志名称与错误日志名称路径拼接
    fullName := path.Join(f.filePath, f.fileName) + ".logs"
    fullErrName := path.Join(f.filePath, f.fileName) + ".err.logs"

    // 创建文件句柄并
    fileObj, err := os.OpenFile(fullName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
    if err != nil {
    fmt.Printf("Open Logs file failed, err : %v \n", err)
    return err
    }

    errFileObj, err := os.OpenFile(fullErrName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
    if err != nil {
    fmt.Printf("Open Error logs file failed, err : %v \n", err)
    return err
    }

    // 赋值其对象
    f.fileObj = fileObj
    f.errFileObj = errFileObj
    return nil
    }

    // 5.验证文件的大小方法
    func (f *FileLogger) checkFileSize(file *os.File) bool {
    // 文件信息返回的是结构体
    fileInfo, err := file.Stat()
    if err != nil {
    fmt.Printf("checkFileSize - Get file info failed, err : %v \n", err)
    return false
    }
    // 如果当前文件的大小值大于返回True,则进行切割
    if fileInfo.Size() > f.maxFileSize {
    return true
    }
    return false
    }

    // 6.进行日志文件的切割方法
    func (f *FileLogger) SplitFile(file *os.File) (*os.File, error) {
    // 1) 获取文件句柄信息并关闭当前日志文件
    fileInfo, err := file.Stat()
    if err != nil {
    fmt.Printf("SplitFile - Get file info failed, err : %v \n", err)
    return nil, err
    }
    nowStr := time.Now().Format("2006-01-02_150405")
    LogName := path.Join(f.filePath, fileInfo.Name())
    newLogName := fmt.Sprintf("%s.bak.%s", LogName, nowStr)
    file.Close()

    // 2) 备份并重命名日志文件
    os.Rename(LogName, newLogName)

    // 3) 重新打开一个新的日志文件
    fileObj, err := os.OpenFile(LogName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
    if err != nil {
    fmt.Printf("Open New Logs file failed, err : %v \n", err)
    return nil, err
    }

    // 4) 将对象进行返回
    return fileObj, nil
    }

    // 7.将日志分别打印进对应的文件之中
    func (f *FileLogger) printLogs(level Loglevel, format string, custom ...interface{}) error {
    // 1) 判断日志等级是否满足指定等级之上的要求
    if f.enable(level) {
    // 2) 异常信息
    msg := fmt.Sprintf(format, custom...)

    // 3) 时间与时区
    now := time.Now()
    //timezone, _ := time.LoadLocation("Asia/Shanghai")

    // 4) 异常文件函数信息
    funcName, fileName, linwNum := getFileLine(2)

    // 5) 通用日志文件切割判断及输入
    if f.checkFileSize(f.fileObj) {
    f.fileObj, _ = f.SplitFile(f.fileObj)
    }
    fmt.Fprintf(f.fileObj, "[%s] [%s] [%s:%s:%d] %s \n", now.Format("2006-01-02 15:04:05"), getLevelName(level), fileName, funcName, linwNum, msg)

    // 6) 严重与错误日志文件切割判断及输入
    if level >= ERROR {
    if f.checkFileSize(f.errFileObj) {
    f.errFileObj, _ = f.SplitFile(f.errFileObj)
    }
    fmt.Fprintf(f.errFileObj, "[%s] [%s] [%s:%s:%d] %s \n", now.Format("2006-01-02 15:04:05"), getLevelName(level), fileName, funcName, linwNum, msg)
    }
    }
    return nil
    }

    // 8.日志等级调用分别方法
    // Debug
    func (f *FileLogger) Debug(format string, custom ...interface{}) {
    f.printLogs(DEBUG, format, custom...)
    }

    // Trace
    func (f *FileLogger) Trace(format string, custom ...interface{}) {
    f.printLogs(TRACE, format, custom...)
    }

    // Info
    func (f *FileLogger) Info(format string, custom ...interface{}) {
    f.printLogs(INFO, format, custom...)
    }

    // Error
    func (f *FileLogger) Error(format string, custom ...interface{}) {
    f.printLogs(ERROR, format, custom...)
    }

    // Warning
    func (f *FileLogger) Warning(format string, custom ...interface{}) {
    f.printLogs(WARNING, format, custom...)
    }

    // Falat
    func (f *FileLogger) Falat(format string, custom ...interface{}) {
    f.printLogs(FATAL, format, custom...)
    }

    func (f *FileLogger) Close() {
    f.fileObj.Close()
    f.errFileObj.Close()
    }


  • (3) 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
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    /**自定义日志库调用演示入口**/
    package main
    import (
    "time"
    "weiyigeek.top/custom/mlogger"
    )

    // 示例1.向终端中进行日志信息的打印
    func demo1() {
    log := mlogger.NewConsoleLog("Debug")
    for {
    log.Debug("Debug 日志: %s", "Deveploment Debug")
    log.Trace("Trace 日志: %s", "Deveploment Trace")
    log.Info("Info 日志: %s,%d", "Production Info", 1024)
    log.Warning("Warning 日志: %s", "Production Warning")
    log.Error("Error 日志: %s", "Production Error")
    log.Falat("Falat 日志: %s", "Production Falat")
    time.Sleep(time.Second * 1)
    }
    }

    // 示例2.将日志信息输入到文件之中
    func demo2() {
    log := mlogger.NewFileLogger("INFO", "./", "weiyigeek", 5*1024)
    for {
    log.Debug("Debug 日志: %s", "FileLogger - 开发环境日志基级别 Debug")
    log.Trace("Trace 日志: %s", "FileLogger - 开发环境日志基级别 Trace")
    log.Info("Info 日志: %s,%d", "FileLogger - 生产环境日志基级别 Info", 1024)
    log.Warning("Warning 日志: %s", "FileLogger - 生产环境日志基级别 Warning")
    log.Error("Error 日志: %s", "FileLogger - 生产环境日志基级别 Error")
    log.Falat("Falat 日志: %s", "FileLogger - 生产环境日志基级别 Falat")
    time.Sleep(time.Second * 1)
    }
    }

    func main() {
    demo1()
    demo2()
    }

运行结果:

WeiyiGeek.Logger两种实现方式

WeiyiGeek.Logger两种实现方式