跳至主要內容

Golang学习笔记

OrangBus大约 28 分钟

go安装

goroot: go安装目录

gopath: go代码开发目录

gobin: gopath下面的bin目录

https://golang.google.cn/dl/open in new window

配置国内镜像源:https://goproxy.cn/open in new window

go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct

将下载的二进制包解压至 /usr/local目录, go安装目录。

https://golang.google.cn/dl/go1.21.5.linux-386.tar.gz

uname -r  # 查看系统架构

tar -C /usr/local -xzf go1.4.linux-amd64.tar.gz

将 /usr/local/go/bin 目录添加至 PATH 环境变量:

# linux
vim ~/.bash_profile  
// or 
vim ~/.zshrc

export GOROOT="/usr/local/go" # 安装目录
export GOPATH=$HOME/golang # 代码存放的目录
export GOBIN=$GOROOT/bin
export PATH=$PATH:$GOBIN

# window
GOROOT=D:\golang\sdk\go1.21.4
GOBIN=D:\golang\sdk\go1.21.4\bin
GOPATH=D:\golang\code

重载配置使其生效

source ~/.bash_profile
// or 
source ~/.zshrc

window配置

go env -w GOPATH=D:\Golang\code

编译

runtime/cgo

In file included from /usr/include/features.h:399:0, from /usr/include/stdlib.h:24, from _cgo_export.c:3: /usr/include/gnu/stubs.h:7:27: 致命错误:gnu/stubs-32.h:没有那个文件或目录

include <gnu/stubs-32.h>

编译中断。

sudo yum install glibc-devel.i686 libstdc++-devel.i686

[error] failed to initialize database, got error Binary was compiled with 'CGO_ENABLED=0', go-sqlite3 requires cgo to work. This is a stub sql_test.go:18: Binary was compiled with 'CGO_ENABLED=0', go-sqlite3 requires cgo to work. This is a stub

解决办法,按照这个流程走完:https://www.msys2.org/

然后开启: go env -w CGO_ENABLED=1

打包

window

export GOOS=windows
export GOARCH=amd64

go build -o app main.go

linux

export GOOS=linux
export GOARCH=amd64

go build -o app.exe main.go

环境初始化

1.设置代理,快速下载第三方包

go env -w GOPROXY=https://goproxy.cn,direct

2.开启go modules (设置为auto模式,项目中有.mod文件就代表开启,没有就不开启)

go env -w GO111MODULE=auto

air安装

https://github.com/cosmtrek/air/blob/master/README-zh_cn.mdopen in new window

go install github.com/cosmtrek/air@latest

初始化一个项目

go mod init orangbus.cn/spider/web

安装包

go get -u github.com/antchfx/htmlquery

时间

time.Now().Format("2006-01-02 15:04:05") // 当前格式2022-07-05 16:21:50

时间解析

goto

跳转到指定的行

指针

指针是存储另一个变量的内存地址的变量

申明指针

var var_name *var_type

var url *string

fmt.Println("url的数值是:", *url)

举例说明

// 1、定义一个int类型的变量
a := 10
fmt.Println("a的数值是:", a)     // 10
fmt.Printf("%T\n", a)        // int
fmt.Printf("a的地址是:%p\n", &a) // 0xc00001c0f8

// 2、创建一个指针变量,用来存储a的地址
var p1 *int
fmt.Println(p1)                           // nil 空指针
p1 = &a                                   // p1 指向了a的内存地址
fmt.Println("p1的数值是:", p1)                // p1中存储的是a的地址,跟变量a的地址是一样的
fmt.Printf("p1自己的地址:%p\n", &p1)           // 0xc00000e030
fmt.Println("p1的数值是a的地址,改地址存储的数据是:", *p1) // 10 a的值

//3、更改变量数值,并不会改变地址
a = 100
fmt.Println("a修改过后的值:", a)       // 100
fmt.Printf("更改a后,a的地址:%p\n", &a) // 0xc00001c0f8

// 4、通过执行,改变变量的数值
*p1 = 200
fmt.Println("p1修改过后,a的值:", a)       // 200
fmt.Println("p1修改过后的值:", p1)        // 0xc0000b8000
fmt.Println("p1修改之后地址存储的数据是:", *p1) // 200

// 5、指针的指针
var p2 **int
fmt.Println("p2的值:", p2) // nil
p2 = &p1
fmt.Printf("%T,%T,%T\n", a, p1, p2) // int,*int,**int
fmt.Println("p2的数值:", p2)           // p1的地址
fmt.Println("p2中存储的地址,对应的数值,就是p1的地址,对应的数据:", *p2) // 0xc00001c0f8
	fmt.Println("p2中存储的地址,对应的数值,再获得对应的数值:", **p2)     // 200 也就是a的值
a -> 10
&a -> a的地址

p1 -> a的地址
&p1 -> p1自己的地址
*p1 -> 获取执行存储的地址,对应的数值

p2 -> p1的地址
&p2 -> p2 自己的地址
*p2 -> 获取指针储存的地址,对应的数值。就是p1,实际上p1中存储的数值,就是a的地址

数组指针

首先是一个指针,一个数组的地址

*[2]Type

指针数组

首先是一个数组,存储的数据类型是指针

[2]*Type

案例

// 1、创建一个普通的数组
arr1 := [4]int{1, 2, 3, 4}
fmt.Println("数组的值:", arr1) //  [1 2 3 4]

// 2、创建一个执行,存储该数组的地址 ---> 数组执行
var p1 *[4]int
p1 = &arr1
fmt.Println("p1的值:", p1)      // &[1 2 3 4] 表示一个指针的数组
fmt.Printf("p1的地址:%p\n", &p1) // 数组 arr1 的地址

// 3、根据数组指针,操作数据
(*p1)[0] = 100
fmt.Println("修改过后的p1值", p1) //  &[100 2 3 4]
p1[1] = 200                 // 简化写法
fmt.Println(p1, arr1)       // &[100 200 3 4] [100 200 3 4]

// 4、指针数组
a := 1
b := 2
arr2 := [2]int{a, b}
arr3 := [2]*int{&a, &b}
fmt.Println(arr2) // [1 2]
fmt.Println(arr3) // [0xc00001c178 0xc00001c180]

arr2[0] = 100
fmt.Println(arr2) //[100 2]
fmt.Println(a)    // 1
fmt.Println()

*arr3[0] = 200
fmt.Println(arr3) // [0xc00001c178 0xc00001c180]
fmt.Println(a)    // 200

a = 150
fmt.Println(arr2) // [100,2]
for i := 0; i < len(arr3); i++ {
   fmt.Println(*arr3[i])
} // 150 2

指针不能做运算

// 交换两个变量的值
package main

import "testing"

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

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

func TestSwap(t *testing.T) {
	a, b := 3, 4
	swap(&a, &b)
	t.Log(a, b)

	c, d := 5, 6
	c, d = swap2(c, d)
	t.Log(c, d)
}

值传递: 拷贝一份数据传递

引用传递:传递一个地址

案例

package main

import "fmt"

func number01(number int) {
	fmt.Println("number01->number:", number) // 10
}

func number02(number *int) {
	*number = 20
	fmt.Println("number2->number:", *number) // 20
}

func main() {
	a, b := 10, 10
	number01(a)
	fmt.Println("number01:", a) // 10
	number02(&b)
	fmt.Println("number01:", b) // 20

}

函数指针

一个指针,指向一个函数的指针

指针函数

一个函数,该函数的返回值是一个指针

案例

package main

import "fmt"

func fun1() {
	fmt.Println("fun1 函数被调用了")
}

func fun2() *[3]int {
	arr := [3]int{1, 2, 3}
	return &arr
}

func main() {
	var a func()
	a = fun1
	a() // 调用函数

	arr := fun2()
	fmt.Println(arr)
}

冒泡排序

package main

import "fmt"

func main() {
	// 冒泡排序
	list := [...]int{
		100, 32, 83, 94, 35, 56,
	}
	for i := 1; i < len(list); i++ {
		for j := 0; j < len(list)-i; j++ {
			if list[j] > list[j+1] {
				list[j], list[j+1] = list[j+1], list[j]
			}
		}
		fmt.Println(list)
	}
}

数组-值类型

package main

import "testing"

func TestArray(t *testing.T) {
	var arr = [...]int{1, 2, 3, 4}
	t.Log(arr)

	for i := 0; i < len(arr); i++ {
		t.Log(arr[i])
	}
	t.Log("-----")
	for i, val := range arr {
		t.Log(i, val)
	}

	var grid [4][5]int // 四行五列
	t.Log(grid)
}

slice-切片 数组

数组和切片的区别:

数组:arr := [2]int {1,2,} 指定了长度

切片:s := []int{1, 2, 3} 不指定长度

package main

import "testing"

func TestSlice(t *testing.T) {
	arr := []int{1, 2, 3, 4, 5, 6} // 定义一个数据
	arr2 := arr[3:]                   // 数组转slice
	arr2[0] = 100
	t.Log("arr2", arr2)

	// 添加
	s1 := append(arr2, 7)
	s2 := append(s1, 8)
	t.Log("s1:", s1)
	t.Log("s2:", s2)

	t.Log("arr:", arr)
}

func TestPrint(t *testing.T) {
	arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
	s1 := arr[2:6]
	s2 := s1[3:5]
	t.Log(s1) // [2 3 4 5]
	t.Log(s2) // [5 6]
}

func TestCreate(t *testing.T) {
	var s []int
	for i := 0; i < 10; i++ {
		s = append(s, i)
	}
	t.Log("s", s)

	t.Log("=============")
	s1 := make([]int, 16)
	t.Log("s1", s1)

	t.Log("==== copy ====")
	arr2 := [...]int{11, 12, 13, 14, 15}
	copy(s, arr2[:])
	t.Log(s)
	t.Log(arr2)

	// 删除一个中间元素
	var s2 []int
	for i := 0; i < 20; i++ {
		s2 = append(s2, i)
	}
	t.Log("s2", s2, len(s2), cap(s2))
	res1 := append(s2[:9], s2[10:]...)
	t.Log("删除结果(9不见了):", res1, len(res1), cap(res1))

	// 删除头部
	heaer := s2[1:]
	t.Log("header", heaer, len(heaer), cap(heaer))
	end := s2[0 : len(s2)-1]
	t.Log("end", end, len(end), cap(end))
}

map-对象

存储的是一个键值对

package main

import (
	"fmt"
	"testing"
)

func TestMap(t *testing.T) {
	list := map[string]string{
		"name":  "orangbus",
		"age":   "18",
		"wight": "180",
	}
	//创建
	list2 := make(map[string]string)
	list2["name"] = "是我list2新增的"
	t.Log("list2", list2)

	// 获取
	name := list["name"]
	t.Log("获取name:", name)
	t.Log("list", list)
	t.Log(">> 添加")
	list["heght"] = "180"

	t.Log(">> 删除 age")
	delete(list, "age")
	fmt.Println(list)

	// 判断值存不存在
	if val, ok := list["age"]; ok {
		t.Log("存在age:", val)
	} else {
		t.Log("不存在age字段")
	}

	// 遍历
	for index, val := range list {
		t.Log(index, val)
	}
}

strconv - 类型转化

atoi  itoa

面对对象

特性:封装、继承、多态

接口断言

检查接口类的变量是否符合预期值

t := t.(T) // i 是否是 T 类型

方法

package main

import "fmt"

// 1、 定义一个结构体
type Work struct {
	name string
	age  int
}

// 2、定义行为方法
func (w Work) run() {
	fmt.Println(w)
}

func main() {
	w := Work{
		name: "orangbus",
		age:  18,
	}
	w.run()
}

package main

import "fmt"

type USB interface {
	start()
	end()
}

type Mouse struct {
	name string
}

func (m Mouse) start() {
	fmt.Println(m.name + "开始工作")
}

func (m Mouse) end() {
	fmt.Println(m.name + "停止工作")
}

func main() {
	m := Mouse{"鼠标"}
	var usb USB // 实例化一个接口
	usb = m // 留意这里:接口复制给某个结构体,可以用接口调用该结构体实现的方法
	usb.start()
}

继承中的方法

package main

import (
	"fmt"
)

// 1、 定义一个父类
type Person struct {
	name string
	age  int
}

// 2、 定义一个子类
type Student struct {
	Person // 结构体嵌套,模拟继承性
	school string
}

// 3、方法
func (p Person) eat() {
	fmt.Println("父类的方法 -> eat")
}

func (s Student) study() {
	fmt.Println("学生学习了")
}

// 子类重写父类的方法
func (s Student) eat() {
	fmt.Println("子类重写父类的方法")
}

func main() {
	p := Person{
		name: "orangbus",
		age:  18,
	}
	fmt.Println(p)

	p.eat()

	// 创建子类对象
	s := Student{Person{name: "王二狗", age: 18}, "橙子工作室"}
	fmt.Println(s.name)
	fmt.Println(s.school)

	s.eat() // 子类访问父类的方法

	s.study()
}

接口

package main

import "fmt"

// 1、定义一个usb的接口
type USB interface {
	start()
	end()
}

// 2、实现类
type Mouse struct {
	name string
}

func (m Mouse) start() {
	fmt.Println(m.name, "鼠标准备就绪")
}

func (m Mouse) end() {
	fmt.Println(m.name, "鼠标停止")
}

// 测试方法
func testInterface(usb USB) {
	usb.start()
	usb.end()
}

func main() {
	m := Mouse{name: "鼠标"}
	testInterface(m)

	// 空接口的使用
	map1 := make(map[string]interface{})
	map1["name"] = "orangbus"
	map1["age"] = 25
	map1["user"] = Mouse{name: "鼠标"}
	fmt.Println(map1)

	slice1 := make([]interface{}, 0, 10)
	slice1 = append(slice1, "orangbus", Mouse{name: "鼠标"})
	fmt.Println(slice1)
}

接口嵌套

package main

import "fmt"

type A interface {
	test1()
}

type B interface {
	test2()
}

type C interface {
	A
	B
	test3()
}

type Cat struct {
}

func (c Cat) test1() {
	fmt.Println("test1")
}
func (c Cat) test2() {
	fmt.Println("test2")
}
func (c Cat) test3() { // 如果想实现接口C,那么需要实现AB接口
	fmt.Println("test3")
}

func main() {
	var cat Cat = Cat{}
	cat.test1()
	cat.test2()
	cat.test3()
	fmt.Println("--------")
	var a1 A = cat
	a1.test1()

	fmt.Println("--------")
	var b1 B = cat // 如果是B类型的接口,只能调用B类型的方法
	b1.test2()

	fmt.Println("----------")
	var c1 C = cat // 如果是B类型的接口,只能调用C类型的方法
	c1.test1()
	c1.test2()
	c1.test3()

	fmt.Println("----------")

	var c2 A = a1 // 如果是B类型的接口,只能调用A类型的方法
	c2.test1()

}

空接口

可以接受任意类型的值

package main

import "fmt"

type ApiResponse interface {
}

type ResData struct {
	code int
	data ApiResponse
	msg  string
}

func main() {
	m := ResData{200, "123", "sucess"}
	fmt.Println(m) // {200 123 sucess}
    
	data := make(map[string]string)
	data["name"] = "orangbus"
	data["phone"] = "1830000001"
	data["email"] = "linux40400@gmail.com"
	d := ResData{200, data, "success"}
	fmt.Println(d) // 返回一个用户的对象
    
    // map 的值可以是任意的数值类型
    user := make(map[string]interface{})
	user["name"] = "orangbus"
	user["age"] = 18
	user["email"] = "linux40400@gmail.com"
	fmt.Println(user)
    
    // 任意类型的切片数值
    arr := make([]interface{}, 0, 10)
	arr = append(arr, "小明")
	arr = append(arr, 18)
	fmt.Println(arr)
}

接口断言-类型判断

ins,ok := s.(interface)
func getType(s Shape) {
	if ins, ok := s.(Triangle); ok {
		fmt.Println("三角形,三边是:", ins.a, ins.b, ins.c)
	} else if ins, ok := s.(Circle); ok {
		fmt.Println("圆形,半径是:", ins.radius)
	} else {

		fmt.Println("我也不知道了")
	}
}

错误

// 手动创建一个错误
er := errors.New("手动创建的错误")
fmt.Println(er)
err2 := fmt.Errorf("打印的错误")
fmt.Println(err2)
package main

import "fmt"

type ResponseError struct {
	msg  string
	code int
}

func (r *ResponseError) Error() string {
	return fmt.Sprintf("msg:%s: code:%d", r.msg, r.code)
}

func test(i int) (int, error) {
	if i != 0 {
		return i, &ResponseError{"error", 202}
	}
	return i, nil
}

func main() {
	a := 10
	i, err := test(a)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(i)
}

处理错误

panic 异常

defer: 倒序执行

panic: 中断程序的 运行

recover: 处理panic

package test

import (
	"fmt"
	"testing"
)

func cleaup() {
	if r := recover(); r != nil {
		fmt.Printf("我已经处理了panic: %s\n", r)
	}
}

func TestPanic(t *testing.T) {
	defer cleaup()

	fmt.Printf("111\n")
	panic("出错了")
	fmt.Printf("222\n") // 程序终止了就不会打印222
}

// 111
// 我已经处理了panic: 出错了

goroutine - 协程

无缓冲通道: 主函数会阻塞

package main

import (
	"fmt"
	"time"
)

func main() {
	c := make(chan string)

	go func() {
		fmt.Println("我是协程,需要等待2秒处理返回处理结果...")
		<-time.After(time.Second * 2)
		c <- "我是携程传递过来的数据"
	}()

	fmt.Println("主函数等待携程处理数据")
	result := <-c
	fmt.Println("结果:", result)
}

有缓冲通道:不会阻塞

临界资源

package main

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

var ticket = 10

func saleTickets(name string) {
	for {
		if ticket > 0 {
			time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
			fmt.Println(name, "售出:", ticket)
			ticket--
		} else {
			fmt.Println(name, "没有票了")
			break // 结束循环
		}
	}
}

/**
临界资源
*/
func main() {
	go saleTickets("售票口1")
	go saleTickets("售票口2")
	go saleTickets("售票口3")
	go saleTickets("售票口4")
	time.Sleep(time.Second * 3)
}
// 出现了负数的情况
售票口3 售出: 10
售票口1 售出: 9
售票口3 售出: 8
售票口1 售出: 7
售票口3 售出: 6
售票口4 售出: 5
售票口2 售出: 4
售票口1 售出: 3
售票口3 售出: 2
售票口4 售出: 1
售票口4 没有票了
售票口3 售出: 0
售票口3 没有票了
售票口1 售出: -1
售票口1 没有票了
售票口2 售出: -2
售票口2 没有票了

解决临界资源

1、sync.waitGroup 等待同步组

package main

import (
	"fmt"
	"sync"
)

var wg = sync.WaitGroup{} // 创建同步等待组

func saleTickets(name string) {
	for i := 0; i < 10; i++ {
		fmt.Println(name, "->", i)
	}
	// 结束
	wg.Done()
}

/**
临界资源
*/
func main() {
	wg.Add(2) // 添加到等待组
    
	go saleTickets("售票口1")
	go saleTickets("售票口2")
	fmt.Println("main函数进入阻塞,等待 goroutien 结束")
	wg.Wait()
	fmt.Println("main函数结束")
}

2、sync.Mutex 加锁

package main

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

var ticket = 10

var wg = sync.WaitGroup{}
var mutex = sync.Mutex{} // 创建一个锁头

func saleTickets(name string) {
	defer wg.Done()
	for {
		mutex.Lock() // 上锁
		if ticket > 0 {
			time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
			fmt.Println(name, "售出:", ticket)
			ticket--
		} else {
			fmt.Println(name, "没有票了")
			mutex.Unlock() // 条件不满足也要解锁
			break          // 结束循环
		}
		mutex.Unlock() // 解锁
	}
}

/**
临界资源
*/
func main() {
	wg.Add(4)
	go saleTickets("售票口1")
	go saleTickets("售票口2")
	go saleTickets("售票口3")
	go saleTickets("售票口4")
	wg.Wait()
	fmt.Println("main 结束了")
}

读写锁

package main

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

var rwMutex sync.RWMutex // 读写锁
var wg sync.WaitGroup    // 同步等待组

func readData(i int) {
	defer wg.Done()
	fmt.Println("开始读取数据", i)
	rwMutex.RLock() // 读操作上锁
	fmt.Println("正在读取数据。。。->", i)
	time.Sleep(time.Second * 1)
	rwMutex.RUnlock()
	fmt.Println(i, "数据读取完毕")
}

func writeData(i int) {
	defer wg.Done()
	fmt.Println("开始写->", i)
	rwMutex.Lock()
	fmt.Println("正在写数据->", i)
	time.Sleep(time.Second * 1)
	rwMutex.Unlock()
	fmt.Println(i, "->写结束")
}

func main() {
	//rwMutex = new(sync.RWMutex)
	//wg = new(sync.WaitGroup)

	//wg.Add(2)
	//go readData(1)
	//go readData(2)

	wg.Add(3)
	go writeData(1)
	go writeData(2)
	go writeData(3)

	wg.Wait()
	fmt.Println("main结束")
}

1、可以随便读,多个goroutine 同时读

2、写的时候,啥也不能干,不能读也不能写

通道

1、用户goroutine,传递消息的

2、通道,每个都有相关联的数据类型,nil chan 不能使用

3、使用通道传递数据:<-

4、需要在不同的 goroutine 使用,一个 goroutine 会产生死锁

chan <- data // 发送数据到通道,向同道中人写数据
data <- chan //从同道中人获取数据,向通道中读取数据

data,ok := <- ch
data:返回的数据
ok: 通道开启:true,通道关闭:false
for {
    data, ok := <-ch
    if !ok {
        fmt.Println("通道关闭,跳出循环")
        break
    }
    fmt.Print(string(data))
}

4、阻塞,

发送数据:chan <- data 是阻塞的,知道另外一条 goroutine 读取数据来解除阻塞

读取数据:data <- chan 也是阻塞的,知道另外一条 goroutine 写入数据解除阻塞。

5、channel本身是同步的,意味着同一时间,只能有一个goroutine来操作。

package main

import "fmt"

func main() {
	var ch1 chan bool
	ch1 = make(chan bool)

	go func() {
		for i := 0; i < 10; i++ {
			fmt.Println("goroutine i:", i)
		}
		// 循环结束后,想通信中写数据,表示要结束了
		ch1 <- true
		fmt.Println("goroutine 循环结束")
	}()

	data := <-ch1
	fmt.Println("data: ", data)
	fmt.Println("main end ...")

}

关闭通道

close(chanel)
package main

import (
	"fmt"
	"time"
)

func seedData(ch chan int) {
	for i := 0; i < 10; i++ {
		ch <- i
	}
	close(ch)
}

func main() {
	ch1 := make(chan int)
	go seedData(ch1)

	// 读取通道的数据
	for {
		time.Sleep(time.Second)
		v, ok := <-ch1
		if !ok {
			fmt.Println("已读取所有数据", ok)
			break
		}
		fmt.Println("读取的数据是:", v, ok)
	}
}

package main

import (
	"fmt"
	"time"
)

func seedData(ch chan int) {
	for i := 0; i < 10; i++ {
		time.Sleep(time.Second)
		ch <- i
	}
	close(ch)
}

func main() {
	ch1 := make(chan int)
	go seedData(ch1)

	// 读取通道的数据
	for v := range ch1 {
		fmt.Println("读取的数据是:", v)
	}
	fmt.Println("main end ...")
}

案例

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
)

type ollamaMistralResp struct {
	Model     string `json:"model"`
	CreatedAt string `json:"created_at"`
	Response  string `json:"response"`
	Done      bool   `json:"done"`
}

func main() {
	// 创建一个通道用于传递数据
	ch := make(chan []byte)

	// 启动goroutine从os.Stdout读取数据并发送到通道中
	go func() {
		for {
			buf := make([]byte, 1024)
			n, err := os.Stdout.Read(buf)
			if err != nil {
				return
			}
			ch <- buf[:n]
		}
	}()

	param := map[string]interface{}{
		"model":  "mistral",
		"prompt": "你是谁",
	}
	jsonData, _ := json.Marshal(param)

	response, err := http.Post("http://localhost:11434/api/generate", "application/json", bytes.NewBuffer(jsonData))
	if err != nil {
		panic(err)
	}
	defer response.Body.Close()

	// 从响应体中读取数据并发送到通道中
	go func() {
		_, err := io.Copy(os.Stdout, response.Body)
		if err != nil {
			fmt.Println("Error reading response body:", err)
		}
		close(ch)
	}()

	// 从通道中实时接收数据并处理
	for {
		data, ok := <-ch
		if !ok {
			fmt.Println("数据接收完毕")
			break
		}
		fmt.Print(string(data))
	}
	fmt.Println("over")
}

缓冲通道

非缓冲通道

make(chan T)

​ 一次发送,一次接受,都是阻塞的

缓冲通道

make(chann T , capacity)

​ 发送:缓冲取的数据满了,才会阻塞

​ 接受:缓冲器的的数据空了,才会阻塞

var ch1  = make(ch chan,10) // 可以存放 10 个chan ,同时可以一次性读取这10个 chan

定向通道

只能读或者只能写

场景:作为参数或者返回值

ch3 := make(chan<- int,1) // 只能写入不能读
ch4 := make(<-chan int,1) // 只能读不能写
package main

import (
	"time"
)

func writeOnly(ch chan<- int) {
	ch <- 100
}
func readOnly(ch <-chan int) int {
	data := <-ch
	return data
}

func main() {
	ch := make(chan int)
	go writeOnly(ch)
	go readOnly(ch)
	time.Sleep(time.Second * 2)
}

select

1、每一个case必须是一个通道的操作 <-

2、所有的chan操作都结果

3、任意一个通道拿到结果就会执行改case,其它就会被忽略

4、如果有多个case都可以运行,但是只会随机选择一个执行,其他就不会执行

package main

import (
	"fmt"
	"time"
)

func main() {
	ch1 := make(chan int)
	ch2 := make(chan int)

	go func() {
		time.Sleep(time.Second * 2)
		ch1 <- 100
	}()

	go func() {
		time.Sleep(time.Second * 2)
		ch2 <- 200
	}()

	select {
	case num1 := <-ch1:
		fmt.Println("num1:", num1)
	case num2 := <-ch2:
		fmt.Println("num2:", num2)
	}
}

jwt

实现方式

elasticsearch

数据转化

json转struct

请求第三方接口返回的 json 数据(举个例子)

{
    code: 200
    msg: "ok",
    data:[
        { id:1,title: "golang入门"},
        { id:2,title: "golang入门到放弃"},
    ]
}
package main

import (
	"encoding/json"
	"io/ioutil"
	"net/http"
	"testing"
)

var url = "https://demo.com"

// 定义一个列表结构
type classList struct {
	Type_id   string `json:"type_id"`
	Type_name string `json:"type_name"`
}

// json 字段 (注意:开头大写,否则转化失败)
type respData struct {
	Code      int         `json:"code"`
	Page      int         `json:"page"`
	Pagecount int         `json:"pagecount"`
	Limit     int         `json:"limit"`
	Total     int         `json:"total"`
	ClassList []classList `json:"class"` // 这是一个列表数据
}

func TestFuzz(t *testing.T) {
	resp, error := http.Get(url)
	if error != nil {
		t.Log(error)
	}
	defer resp.Body.Close()

    // 将字节转化为 json字符串
	body, _ := ioutil.ReadAll(resp.Body)
	t.Log(string(body))
    
    // 将json字符串转化为 struct
	item := respData{}
	if jsonErr := json.Unmarshal(body, &item); jsonErr == nil {
		t.Log(item)
	} else {
		t.Log(jsonErr)
	}
	for k, v := range item.ClassList {
		t.Log(k, ":", v)
	}
}

struct 转 json

encoding/json
package main

import (
	"encoding/json"
	"fmt"
	"testing"
)

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

func TestStructToJson(t *testing.T) {
	user := user{
		Id:   1,
		Name: "orangbus",
		Age:  18,
	}
	fmt.Println(user) // {1 18 orangbus}

	//转化为json
	data, _ := json.Marshal(user)
	fmt.Println(string(data)) // {"id":1,"age":18,"name":"orangbus"}
}

字符串操作

截取

func TestStrSplice(t *testing.T) {
	var s = "https://v.qq.com/x/cover/mzc00200t1tvb7d/g0   04252hyh1.html"
	res := s[:16]
	t.Log(res) // https://v.qq.com
}

分隔-split

strings.split(str,",")

去除空格

func TestStrTrim(t *testing.T) {
	var s = "https://v.qq.com/x/cover/mzc00200t1tvb7d/g0   04252hyh1.html"
	res := strings.Replace(s, " ", "", -1)
	t.Log(res)
}

字符串拼接

next_url := fmt.Sprintf("%s?page=%d", url, i+1)

方法错误返回处理

package main

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

func getHtml(url string) (data string, error error) {
	resp, err := http.Get(url)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	// 读取内容,resp.Body() 获取到的是字节
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}
	return string(body), nil
}

func main() {
	html, err := getHtml("https://imooc.com")
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(html)
}

同一个包下方法冲突

嵌套一个结构体

package seeders

import (
	"github.com/go-faker/faker/v4"
	"orangbus.cn/spider/app/models"
)

// 定义一个结构体
type UserSeeder struct {
}

type FakerUser struct {
	Name     string `json:"name";faker:"name"`
	Phone    string `json:"phone";faker:"phone_number"`
	Password string `json:"password";faker:"password"`
}

// 实现具体的方法
func (u UserSeeder) GenerateOne() models.User {
	fakerUser := FakerUser{}
	faker.FakeData(&fakerUser)
	user := models.User{
		Name:     fakerUser.Name,
		Phone:    fakerUser.Phone,
		Password: fakerUser.Password,
	}
	return user
}

func (u UserSeeder) GenerateList(number int) []models.User {
	var users []models.User
	for i := 0; i < number; i++ {
		user := UserSeeder{}.GenerateOne()
		users = append(users, user)
	}
	return users
}

处理可选参数

1、配置文件可选参数

package test

import (
	"testing"
)

func getDefaultValue(name string, defaultValue ...string) string {
	if len(defaultValue) > 0 {
		return defaultValue[0]
	}
	return name
}

func TestDefaultName(t *testing.T) {
	name := getDefaultValue("orangbus")
	t.Log(name) // orangbus

	name2 := getDefaultValue("orangbus", "我是应该返回的参数")
	t.Log(name2) // 我是应该返回的参数
}

2、请求可选参数

在发送请求的时候,我们可能传递 url,param, header 但是后面的两个参数是可选的

package test

import (
	"testing"
)

type Options struct {
	param  []map[string]interface{}
	header []map[string]interface{}
}

func getParam(url string, options Options) map[string]interface{} {
	data := make(map[string]interface{})
	data["url"] = url
	if len(options.param) > 0 {
		data["param"] = options.param
	}
	if len(options.header) > 0 {
		data["headers"] = options.header
	}
	return data
}

func TestDefaultVal(t *testing.T) {
	options := Options{}
	options.param = []map[string]interface{}{
		{
			"name":  "orangbus",
			"phone": "12345678901",
		},
	}
	options.header = []map[string]interface{}{
		{
			"auth": "orangbus",
		},
	}
	param := getParam("https://orangbus.cn", options)
	t.Log(param)
}

设计模式

工厂模式

strut 小写的时候,但是在其它包里面需要创建一个该结构体的实例的时候

Gorm

安装

go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql

案例

package main

import (
	"database/sql"
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
	"log"
	"os"
	"time"
)

type MovieCate struct {
	Id        uint32    `json:"id"`
	ApiId     int       `json:"api_id"`
	TypeName  string    `json:"name"`
	TypeId    int       `json:"type_id"`
	Status    int       `json:"status"`
	Sort      int       `json:"sort"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

var drive = "mysql"
var database = "golang"
var host = "127.0.0.1"
var port = 3306
var username = "root"
var password = "root"

// 获取当前时间
func getTime() string {
	return time.Now().Format("2006-01-02 15:04:05")
}

// 全局数据库对象
var db = &gorm.DB{}

func init() {

	// 连接数据库
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local", username, password, host, port, database)
	mysqlDb, err := sql.Open(drive, dsn)
	if err != nil {
		panic(err)
	}
	db, err = gorm.Open(mysql.New(mysql.Config{
		Conn: mysqlDb,
	}), &gorm.Config{
		// 日志配置
		Logger: logger.New(
			log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注)
			logger.Config{
				SlowThreshold:             time.Second, // 慢 SQL 阈值
				LogLevel:                  logger.Info, // 日志级别
				IgnoreRecordNotFoundError: true,        // 忽略ErrRecordNotFound(记录未找到)错误
				Colorful:                  false,       // 禁用彩色打印
			}),
	})
	if err != nil {
		panic("数据库连接失败")
	}
	fmt.Println("数据库连接成功!")
}

// 插入数据
func Create() {
	cate := MovieCate{
		ApiId:     1,
		TypeId:    1,
		TypeName:  "demo",
		Status:    1,
		Sort:      10,
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}
	result := db.Create(&cate)
	fmt.Println(result)
}

// 批量插入
func BatchInsert() {
	var cateList = []MovieCate{}
	for i := 0; i < 10; i++ {
		cateList = append(cateList, MovieCate{
			ApiId:     i + 1,
			TypeId:    i + 1,
			TypeName:  "demo",
			Status:    1,
			Sort:      i,
			CreatedAt: time.Now(),
			UpdatedAt: time.Now(),
		})
	}
	res := db.Create(&cateList)
	fmt.Println(res)
}

// 查询数据
func getDataById(id int) {
	//cate := new(MovieCate)
	//db.First(cate)
	//fmt.Println(cate)

	cate := db.Find(&MovieCate{}, id)
	fmt.Println(cate)
}

// 条件查询
func getCateByApiId(api_id int) {
	res := db.Where("api_id =?", api_id).Order("id desc").First(&MovieCate{})
	fmt.Println(res)
}

// 更新数据
func update() {
	var cate MovieCate
	//db.First(&cate, 723)
	//cate.TypeName = "我被更新了"
	//db.Save(&cate)

	db.Model(&cate).Where("id = ?", 724).Updates(MovieCate{
		TypeName: "我又被更新了",
		Status:   0,
	})
}

// 删除数据
func delete(id int) {
	db.Delete(&MovieCate{}, id)
}

func main() {
	delete(723)
}

爬虫案例

package main

import (
	"fmt"
	"github.com/antchfx/htmlquery"
	"time"
)

// 段子结构体
type Jokes struct {
	Id         int
	Cate_id    int
	Content    string
	Image_url  string
	Image_gif  string
	Status     int
	Like_count int
	Form       string
	Created_at string
	Updated_ed string
}

// 获取段子
func getJoke(url string, page int) {
	// 解析html
	doc, err := htmlquery.LoadURL(fmt.Sprintf("%s?page=%d", url, page))
	if err != nil {
		fmt.Println(err)
	}
	list := htmlquery.Find(doc, "//div[@class='one-cont']")

	var jokeList []Jokes
	for _, val := range list {
		a := htmlquery.FindOne(val, "//p/a")
		if a != nil {
			joke := Jokes{
				Content:    htmlquery.InnerText(a),
				Form:       url + htmlquery.SelectAttr(a, "href"),
				Created_at: time.Now().Format("2006-01-02 15:04:05"),
				Updated_ed: time.Now().Format("2006-01-02 15:04:05"),
			}
			jokeList = append(jokeList, joke)
		}
	}

	// 判断最后一页
	if len(list) == 0 {
		fmt.Println("最后一页了:", url)
		return
	}
	page += 1
	next_url := fmt.Sprintf("%s?page=%d", url, page)
	fmt.Println("next_url:", next_url)
	getJoke(url, page)
}

func main() {
	var url = "https://www.xiaohua.com"
	getJoke(url+"/duanzi", 1)
}

保存到数据库中-协程

package main

import (
	"database/sql"
	"fmt"
	"github.com/antchfx/htmlquery"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
	"log"
	"os"
	"sync"
	"time"
)

// 全局数据库对象
var db = &gorm.DB{}

// 数据库配置信息
var drive = "mysql"
var database = "golang"
var host = "127.0.0.1"
var port = 3306
var username = "root"
var password = "root"

// 协程
var wg sync.WaitGroup

func init() {

	// 连接数据库
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local", username, password, host, port, database)
	mysqlDb, err := sql.Open(drive, dsn)
	if err != nil {
		panic(err)
	}
	db, err = gorm.Open(mysql.New(mysql.Config{
		Conn: mysqlDb,
	}), &gorm.Config{
		// 日志配置
		Logger: logger.New(
			log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注)
			logger.Config{
				SlowThreshold:             time.Second, // 慢 SQL 阈值
				LogLevel:                  logger.Info, // 日志级别
				IgnoreRecordNotFoundError: true,        // 忽略ErrRecordNotFound(记录未找到)错误
				Colorful:                  false,       // 禁用彩色打印
			}),
	})
	if err != nil {
		panic("数据库连接失败")
	}
	fmt.Println("数据库连接成功!")
}

// 段子结构体
type Jokes struct {
	Id        int       `json:"id"`
	CateId    int       `json:"cate_id"`
	Content   string    `json:"content"`
	ImageUrl  string    `json:"image_url"`
	ImageGif  string    `json:"image_gif"`
	Status    int       `json:"status"`
	LikeCount int       `json:"like_count"`
	Form      string    `json:"form"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAT time.Time `json:"updated_at"`
}

// 获取段子
func getJoke(url string, page int) {
	// 解析html
	doc, err := htmlquery.LoadURL(fmt.Sprintf("%s?page=%d", url, page))
	if err != nil {
		fmt.Println(err)
	}
	list := htmlquery.Find(doc, "//div[@class='one-cont']")

	var jokeList []Jokes
	for _, val := range list {
		a := htmlquery.FindOne(val, "//p/a")
		if a != nil {
			joke := Jokes{
				Content:   htmlquery.InnerText(a),
				Form:      url + htmlquery.SelectAttr(a, "href"),
				CreatedAt: time.Now(),
				UpdatedAT: time.Now(),
			}
			jokeList = append(jokeList, joke)
		}
	}

	// 保存到数据库中
	db.Create(&jokeList)

	// 判断最后一页
	if len(list) == 0 {
		fmt.Println("最后一页了:", url)
		return
	}
	page += 1
	next_url := fmt.Sprintf("%s?page=%d", url, page)
	fmt.Println("next_url:", next_url)
	//getJoke(url, page)
	// 协程结束
	wg.Done()
}

// 获取列表

// 保存数据库

func main() {
	var url = "https://www.xiaohua.com"
	for i := 1; i < 1073; i++ {
		wg.Add(1)
		go getJoke(url+"/duanzi", i)
	}
	wg.Wait() // 等待协程结束
	fmt.Println("爬取完成。。。")
}

保存到elasticsearch中



ari

go env -w GOPROXY=https://goproxy.cn,direct
go env -w GO111MODULE=on

go install github.com/cosmtrek/air@latest

curl -fLo air.exe https://git.io/windows_air

笔记

func print() {
	for i := 0; i < 1000; i++ {
		fmt.Println("协程中执行的内容:", i)
		time.Sleep(time.Second * 3)
	}
}

func (i IndexController) Index(c *gin.Context) {
	// 漫长的协程执行
	go print() // 这个过程会在后台继续执行
	fmt.Println("终端打印测试")

	c.HTML(200, "index.html", gin.H{
		"title": "orangbus",
		"msg":   "hello orangbus",
	})
}

下载视频

将下载列表通过协程去下载

package main

import (
	"fmt"
	"os/exec"
	"sync"
)

type Item struct {
	Name   string
	Url    string
	Status bool
}

func Download(item Item, wg *sync.WaitGroup, c chan Item) {
	defer wg.Done()

	cmd := exec.Command("/usr/local/bin/m3d", "-u="+item.Url, "-o="+item.Name)
	err := cmd.Start()
	if err != nil {
		fmt.Println("错误:", err.Error())
		item.Status = false
		c <- item
	}
	//fmt.Println("结果:", string(output))
	item.Status = true
	c <- item
}

func main() {
	list := []Item{
		{"第01集.mp4", "https://vip.lz-cdn14.com/20220615/1599_93cf5a94/index.m3u8", false},
		{"第02集.mp4", "https://vip.lz-cdn14.com/20220615/1597_397419b3/index.m3u8", false},
		{"第03集.mp4", "https://vip.lz-cdn14.com/20220615/1598_1e945298/index.m3u8", false},
		{"第04集.mp4", "https://vip.lz-cdn14.com/20220615/1601_5602c49d/index.m3u8", false},
		{"第05集.mp4", "https://vip.lz-cdn14.com/20220615/1602_de949e05/index.m3u8", false},
		{"第06集.mp4", "https://vip.lz-cdn14.com/20220615/1600_9b0a00b9/index.m3u8", false},
		{"第07集.mp4", "https://vip.lz-cdn14.com/20220615/1605_896f6e3a/index.m3u8", false},
	}
	c := make(chan Item)
	wg := sync.WaitGroup{}

	for _, item := range list {
		wg.Add(1)
		go Download(item, &wg, c)
	}
	go func() {
		wg.Wait()
		close(c)
	}()
	for range list {
		result := <-c
		fmt.Println(result)
	}
}

如何控制线程数量

断点续传案例

package main

import (
	"fmt"
	"io"
	"os"
	"strconv"
)

func handleError(err error, msg string) {
	if err != nil {
		fmt.Printf("%s: %s\n", msg, err)
	}
	return
}

func main() {
	srcFile := "public/demo.mp4"
	destFile := "upload/upload_demo.mp4"
	tempFile := "temp.txt"

	// 打开文件
	file1, err := os.Open(srcFile)
	handleError(err, "file1")
	file2, err := os.OpenFile(destFile, os.O_RDWR|os.O_CREATE, os.ModePerm)
	handleError(err, "file2")
	file3, err2 := os.OpenFile(tempFile, os.O_RDWR|os.O_CREATE, os.ModePerm)
	handleError(err2, "file3")

	// 关闭文件
	defer file1.Close()
	defer file2.Close()

	// 读取 temp 文件
	file3.Seek(0, io.SeekStart)
	buf := make([]byte, 1024, 1024)
	n, _ := file3.Read(buf)
	// 转化string  - 数字
	count, _ := strconv.ParseInt(string(buf[:n]), 10, 64)

	// 设置读写的偏移量
	file1.Seek(count, io.SeekStart)
	file2.Seek(count, io.SeekStart)

	// 开始读写
	bufData := make([]byte, 1024, 1024)
	total := int(count)

	for {
		readNum, err := file1.Read(bufData)
		if err == io.EOF {
			fmt.Printf("文件复制完毕")
			file3.Close()
			os.Remove(tempFile)
			break
		}
		fmt.Sprintf("readNum:%s", readNum)
		// 向目标文件写入数据
		writeNum, err3 := file2.Write(bufData[:readNum])
		if err3 != nil {
			fmt.Println("文件写入失败:" + err.Error())
			return
		}
		total := total + writeNum
		file3.Seek(0, io.SeekStart)
		file3.WriteString(strconv.Itoa(total))
	}
}

协程控制案例

package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"strconv"
	"sync"
	"time"
)

type UrlItem struct {
	Name string
	Url  string
}

type Movie struct {
	Name    string
	Url     string
	Total   int64
	status  bool
	UrlList []UrlItem
}

var threadNum = 2

func NewTeak(movie Movie, ch chan Movie) {
	c1 := make(chan UrlItem, threadNum)

	var wg sync.WaitGroup
	for _, item := range movie.UrlList {
		//fmt.Printf("正在下载:%s", item.Name)
		wg.Add(1)
		go func() {
			defer wg.Done()
			urlItem, err := Download(item)
			if err != nil {
				fmt.Printf("下载失败:%s\n", err.Error())
				panic(err)
				// 如果下载失败,重新下载(也可以通过队列的方式下载,将下载失败的重新append到待下载的队列中)
			}
			c1 <- urlItem
		}()
		// 将通道的数据读取,不然会阻塞在这里
		data := <-c1
		fmt.Printf("%s下载完毕:%s:", data.Name, data.Url)
	}
	// 等待分片下载完成
	wg.Wait()
	// 合并文件
	movie.status = true
	ch <- movie
}

// 执行下载一个分片
func Download(item UrlItem) (UrlItem, error) {
	fmt.Printf("正在下载:%s\n", item.Name)
	time.Sleep(time.Second * 1)
	// 下载网页
	response, err := http.Get(item.Url)
	if err != nil {
		return item, err
	}
	defer response.Body.Close()
	html, err2 := io.ReadAll(response.Body)
	if err2 != nil {
		return item, err2
	}
	file, err3 := os.OpenFile(fmt.Sprintf("D:\\Golang\\code\\demo\\download\\data/%s.html", item.Name), os.O_CREATE|os.O_RDWR|os.O_WRONLY, 0644)
	if err3 != nil {
		return item, err3
	}
	n, err4 := file.Write(html)
	if err4 != nil {
		return item, err4
	}
	fmt.Printf("写入数据:%d\n", n)
	return item, nil
}

func main() {
	// 默认请求得到下载
	var movie = Movie{}
	movie.Name = "斗罗大陆"
	movie.Url = "http://xxx.m3u8"
	movie.status = false

	var movieList []UrlItem
	for i := 0; i < 100; i++ {
		movieList = append(movieList, UrlItem{strconv.Itoa(i), fmt.Sprintf("https://www.tizi365.com/archives/457.html")})
	}
	movie.UrlList = movieList
	movie.Total = int64(len(movieList))
	// 1、创建协程
	var ch = make(chan Movie)
	// 下载视频
	go NewTeak(movie, ch)
	// 接收下载结果
	<-ch
	// 如果还有待下载的链接,在继续下载
	//fmt.Println(res)
	fmt.Println("下载完成")
}

找出修改文件

package main

import (
	"flag"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"time"
)

var (
	path    string // 当前程序下面的文件路径
	date    string // 时间 2024-10-01
	exclude string // 排除的文件夹
)

func findModifiedFiles(dirPath string, specifiedDay time.Time, exclude string) {
	// 遍历目录下的所有文件和子目录
	files, err := filepath.Glob(filepath.Join(dirPath, "*"))
	if err != nil {
		fmt.Println("无法获取目录下的文件:", err)
		return
	}

	for _, file := range files {
		info, err := os.Stat(file)
		if err != nil {
			fmt.Printf("无法获取文件 %s 的信息: %v\n", file, err)
			continue
		}
		if strContainSlice(exclude, file) {
			continue
		}

		// 获取文件的修改时间
		modTime := info.ModTime()

		if modTime.Year() == specifiedDay.Year() && int(modTime.Month()) == int(specifiedDay.Month()) && int(modTime.Day()) == int(specifiedDay.Day()) {
			fmt.Printf("在 %s 修改的文件: %s\n", specifiedDay.Format("2006-01-02"), file)
		}

		// 检查文件的修改时间是否在今天或之后
		//if modTime.After(time.Now().AddDate(0, 0, -1)) {
		//	fmt.Printf("修改的文件: %s\n", file)
		//}
		// 如果文件是目录,递归调用自身来检查子文件夹中的文件
		if info.IsDir() {
			findModifiedFiles(file, specifiedDay, exclude)
		}
	}
}

func strContainSlice(sliceStr, keyStr string) bool {
	if sliceStr == "" {
		return false
	}
	s := strings.Split(sliceStr, ",")
	status := false
	for _, v := range s {
		if strings.Contains(keyStr, v) {
			status = true
			break
		}
	}
	return status
}

// go run main.go -p="./site" -t=2024-03-11
func main() {
	nowDate := time.Now().Format("2006-01-02")
	flag.StringVar(&path, "p", "./", "当前目录下的文件路径:./web")
	flag.StringVar(&date, "t", nowDate, nowDate)
	flag.StringVar(&exclude, "i", "", "排除当前目录下的文件:public,node_modules,logs")
	flag.Parse()
	fmt.Printf("查询日期:%s\n", date)
	fmt.Printf("扫描路径:%s\n", filepath.Join(path))

	d, err := time.Parse(time.DateOnly, date)
	if err != nil {
		fmt.Println(err)
		return
	}
	findModifiedFiles(path, d, exclude)
}

串行、并发、并行

有两个同学需要到一个办公室

串行

只有第一个同学先进去出来后,才能让第二个同学进去,第三个同学进入需要等第二个同学出来。

并发

两个人不管先后顺序都进入了办公室,最终目标是两人都在办公室,再次同时,除了这两个同学外,其他同学也可以进入了办公室。

并行

两个人一起进去,如果说办公室有两道门,A同学从A门进去,B同学从B门进入,其中这两道门属于多核cpu概念

假设你是一个老师,有一个班级里有很多学生,每个学生都需要完成一个作业。你要收集学生们的作业,然后检查它们是否完成了。

  1. 串行处理请求:如果你按顺序收集每个学生的作业,并一一检查完成情况,那么处理方式就是串行的。也就是说,你只有在检查完一个学生的作业之后才能继续下一个学生的作业。这意味着其他学生必须等待。

  2. 并发处理请求:相反,你可以同时向每个学生收集作业,并在收集到每个作业后立即检查它们。这样,即使你在等待某个学生提交作业的同时,你仍然可以继续向其他学生收集作业,并且可以在不同的 goroutine 中检查它们。这种并发处理方式使得整个过程更高效,因为其他学生的作业不会因为某一个学生的作业而被阻塞。

  3. 并行处理任务:如果你有其他老师一起帮助你收集和检查作业,那么每个老师可以并行地处理不同学生的作业。这样,整个收集和检查作业的过程会更快。每个老师可以视为一个并行处理任务的单元。

综上所述,请求(学生的作业)和并发的联系在于,通过并发处理作业,即使一个学生的作业需要等待,你仍然可以处理其他学生的作业,从而提高整体的效率和处理能力。

好的,让我们以一个简单的网站服务器为例来说明这个概念。

假设你正在编写一个简单的网站服务器,它需要处理用户发送的请求并返回响应。每个请求需要读取数据库中的数据并生成响应。如果每个请求都按顺序处理,那么下一个请求必须等待上一个请求完成才能处理,这将大大降低服务器的性能。

现在,让我们看看如何使用并发来提高服务器的性能:

  1. 串行处理请求:当一个请求到达时,服务器按顺序处理它,读取数据库并生成响应。然后,它才会处理下一个请求。这意味着其他请求必须等待前一个请求完成才能继续处理。

  2. 并发处理请求:相反,服务器可以创建一个goroutine来处理每个请求。每个goroutine都可以独立地读取数据库并生成响应,而不需要等待其他请求完成。这意味着即使有一个请求正在处理,服务器仍然可以同时处理其他请求。

举例来说,假设有三个请求同时到达服务器:

  • 请求1:读取数据库并生成响应。
  • 请求2:创建一个新的goroutine并读取数据库并生成响应。
  • 请求3:创建一个新的goroutine并读取数据库并生成响应。

在这个例子中,即使请求1正在处理,服务器可以同时处理请求2和请求3。这提高了服务器的性能和吞吐量,因为它可以同时处理多个请求,而不需要等待前一个请求完成。

因此,请求和并发的联系在于,并发处理请求可以提高服务器的性能和吞吐量,即使某个请求需要等待,服务器仍然可以同时处理其他请求,从而提高整体的效率。