Golang学习笔记
go安装
goroot: go安装目录
gopath: go代码开发目录
gobin: gopath下面的bin目录
配置国内镜像源:https://goproxy.cn/
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
wget https://golang.google.cn/dl/go1.22.3.linux-amd64.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\go
GOBIN=D:\golang\go\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
go list m json all failed to run
打包
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
删除缓存
go clean -modcache
# 验证依赖是否与当前的Go版本兼容
go mod verify
# 重新获取项目依赖
go get
air安装
https://github.com/cosmtrek/air/blob/master/README-zh_cn.md
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 TestErrorHandling(t *testing.T) {
// 模拟一个错误发生的场景
err := request.ResponseError("账号被封了")
// 检查错误类型
var myError *ResponseError
if errors.As(err, &myError) {
t.Logf("账号封禁错误: %s", myError.Error())
} else {
t.Logf("普通错误: %v", err)
}
}
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: 出错了
断线重连
当 发生panic 的时候,可以试用 recover 來捕捉错误,如比说,mq连接中断了,可以通过错误让mq断线重连
var s = true
func printNumber(total int) {
for i := 0; i < 10; i++ {
log.Println(i)
if i > 5 && s {
panic("出错啦。。。")
}
time.Sleep(time.Second)
}
}
func TestRetary(t *testing.T) {
defer func() {
if err := recover(); err != nil {
t.Log(err)
s = false
time.Sleep(time.Second * 10) // 延迟10秒
printNumber(10) // 这里可以处理断线重连
}
}()
printNumber(10)
}
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、写的时候,啥也不能干,不能读也不能写
sync.Map
package main
import (
"log"
"net/http"
"sync"
"github.com/gorilla/websocket"
)
var (
upgrader = websocket.Upgrader{}
connections = sync.Map{} // 使用 sync.Map 存储所有的 WebSocket 连接
)
// 消息结构体
type Message struct {
SenderID string `json:"sender_id"`
ReceiverID string `json:"receiver_id"`
Content string `json:"content"`
}
func wsHandler(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println("Upgrade error:", err)
return
}
userID := r.URL.Query().Get("user_id")
connections.Store(userID, conn)
defer func() {
connections.Delete(userID)
conn.Close()
}()
for {
var msg Message
err := conn.ReadJSON(&msg)
if err != nil {
log.Println("Read error:", err)
break
}
// 查找接收者的连接并发送消息
if receiverConn, ok := connections.Load(msg.ReceiverID); ok {
err := receiverConn.(*websocket.Conn).WriteJSON(msg)
if err != nil {
log.Println("Write error:", err)
}
} else {
log.Printf("User %s not connected\n", msg.ReceiverID)
}
}
}
func main() {
http.HandleFunc("/ws", wsHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
通道
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 小写的时候,但是在其它包里面需要创建一个该结构体的实例的时候
爬虫案例
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("爬取完成。。。")
}
air
https://github.com/air-verse/air
下载对应系统版本的air,放到自己电脑的环境变量中
go env -w GOPROXY=https://goproxy.cn,direct
go env -w GO111MODULE=on
# 1、安装依赖
go install github.com/cosmtrek/air@latest
# 2、初始化
air init
# 3、启动
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概念
假设你是一个老师,有一个班级里有很多学生,每个学生都需要完成一个作业。你要收集学生们的作业,然后检查它们是否完成了。
串行处理请求:如果你按顺序收集每个学生的作业,并一一检查完成情况,那么处理方式就是串行的。也就是说,你只有在检查完一个学生的作业之后才能继续下一个学生的作业。这意味着其他学生必须等待。
并发处理请求:相反,你可以同时向每个学生收集作业,并在收集到每个作业后立即检查它们。这样,即使你在等待某个学生提交作业的同时,你仍然可以继续向其他学生收集作业,并且可以在不同的 goroutine 中检查它们。这种并发处理方式使得整个过程更高效,因为其他学生的作业不会因为某一个学生的作业而被阻塞。
并行处理任务:如果你有其他老师一起帮助你收集和检查作业,那么每个老师可以并行地处理不同学生的作业。这样,整个收集和检查作业的过程会更快。每个老师可以视为一个并行处理任务的单元。
综上所述,请求(学生的作业)和并发的联系在于,通过并发处理作业,即使一个学生的作业需要等待,你仍然可以处理其他学生的作业,从而提高整体的效率和处理能力。
好的,让我们以一个简单的网站服务器为例来说明这个概念。
假设你正在编写一个简单的网站服务器,它需要处理用户发送的请求并返回响应。每个请求需要读取数据库中的数据并生成响应。如果每个请求都按顺序处理,那么下一个请求必须等待上一个请求完成才能处理,这将大大降低服务器的性能。
现在,让我们看看如何使用并发来提高服务器的性能:
串行处理请求:当一个请求到达时,服务器按顺序处理它,读取数据库并生成响应。然后,它才会处理下一个请求。这意味着其他请求必须等待前一个请求完成才能继续处理。
并发处理请求:相反,服务器可以创建一个goroutine来处理每个请求。每个goroutine都可以独立地读取数据库并生成响应,而不需要等待其他请求完成。这意味着即使有一个请求正在处理,服务器仍然可以同时处理其他请求。
举例来说,假设有三个请求同时到达服务器:
- 请求1:读取数据库并生成响应。
- 请求2:创建一个新的goroutine并读取数据库并生成响应。
- 请求3:创建一个新的goroutine并读取数据库并生成响应。
在这个例子中,即使请求1正在处理,服务器可以同时处理请求2和请求3。这提高了服务器的性能和吞吐量,因为它可以同时处理多个请求,而不需要等待前一个请求完成。
因此,请求和并发的联系在于,并发处理请求可以提高服务器的性能和吞吐量,即使某个请求需要等待,服务器仍然可以同时处理其他请求,从而提高整体的效率。
http请求
get
post
忽略https验证
func httpGet(url string) (*http.Response, error) {
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := http.Client{Transport: tr, Timeout: 30 * time.Second}
response, err := client.Get(url)
if err != nil {
debug.Log("请求错误", err)
return nil, err
}
return response, nil
}
自定义请求头
req, err := http.NewRequest("GET", "https://example.com", nil)
if err != nil {
log.Fatal(err)
}
// 设置请求头
req.Header.Set("User-Agent", "MyCustomUserAgent")
req.Header.Set("Accept-Language", "en-US,en;q=0.5")
// 使用默认的 http.Client 发送请求
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
自定义请求
自定义超时时间
自定义请求头等
func TestHttp(t *testing.T) {
body := map[string]interface{}{
"name": "orangbus",
"password": 123456,
}
marshal, _ := json.Marshal(body)
reader := bytes.NewReader(marshal)
req, _ := http.NewRequest("GET", "https://www.orangbus.cn", reader)
req.Header.Set("content-type", "application/json")
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36")
client := &http.Client{
Timeout: 30 * time.Second, // 30秒超时
}
resp, err := client.Do(req)
if err != nil {
t.Log(err)
return
}
defer resp.Body.Close()
b, _ := io.ReadAll(resp.Body)
t.Log(string(b))
}
解析地址拼接
func gerFullUrl(baseUrl, path string, dot ...string) (string, error) {
sep := "/"
if len(dot) > 0 {
sep = dot[0]
}
parse, err := url.Parse(baseUrl)
if err != nil {
return "", err
}
// 获取后缀的
index := strings.LastIndex(parse.Path, sep)
prefurl := baseUrl[:index]
return prefurl, nil
}
请求封装
package axios
import (
"bytes"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"net/http"
"net/url"
"strings"
"time"
)
type Axios struct {
url string
body map[string]string
headers map[string]string
proxy string
httpsVerify bool
timeout int
}
func NewAxios(req_url string, data map[string]string) *Axios {
axios := Axios{url: req_url, body: data, timeout: 30}
return &axios
}
func (a *Axios) SetHeader(headers map[string]string) *Axios {
a.headers = headers
return a
}
func (a *Axios) SetProxy(proxy string) *Axios {
a.proxy = proxy
return a
}
func (a *Axios) VerifyHttps(verify bool) *Axios {
a.httpsVerify = verify
return a
}
func (a *Axios) Get() ([]byte, error) {
tr := &http.Transport{}
if a.httpsVerify {
tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
}
if a.proxy != "" {
parse, err := url.Parse(a.proxy)
if err != nil {
return nil, err
}
tr.Proxy = http.ProxyURL(parse)
}
req_url := a.url
if len(a.body) > 0 {
params := url.Values{}
for k, v := range a.body {
params.Add(k, v)
}
req_url = fmt.Sprintf("%s?%s", a.url, params.Encode())
}
req, err := http.NewRequest("GET", req_url, nil)
if err != nil {
return nil, err
}
if len(a.headers) > 0 {
for k, v := range a.headers {
req.Header.Set(k, v)
}
}
client := &http.Client{Transport: tr, Timeout: time.Duration(a.timeout) * time.Second}
response, err := client.Do(req)
if err != nil {
return nil, err
}
defer response.Body.Close()
if response.StatusCode != 200 {
return nil, errors.New(fmt.Sprintf("请求错误,错误状态码:%d", response.StatusCode))
}
b, err := io.ReadAll(response.Body)
if err != nil {
return nil, err
}
return b, nil
}
func (a *Axios) Post() ([]byte, error) {
tr := &http.Transport{}
if a.httpsVerify {
tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
}
if a.proxy != "" {
parse, err := url.Parse(a.proxy)
if err != nil {
return nil, err
}
tr.Proxy = http.ProxyURL(parse)
}
marshal, err := json.Marshal(a.body)
if err != nil {
return nil, err
}
req, err := http.NewRequest("POST", a.url, bytes.NewReader(marshal))
if err != nil {
return nil, err
}
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36")
if len(a.headers) > 0 {
for k, v := range a.headers {
req.Header.Set(k, v)
}
}
client := http.Client{Transport: tr, Timeout: time.Duration(a.timeout) * time.Second}
response, err := client.Do(req)
if err != nil {
return nil, err
}
defer response.Body.Close()
if response.StatusCode != 200 {
return nil, errors.New(fmt.Sprintf("请求错误,错误状态码:%d", response.StatusCode))
}
b, err := io.ReadAll(response.Body)
if err != nil {
return nil, err
}
return b, nil
}
func (a *Axios) Dd(isPost bool) {
log.Println(strings.Repeat("=", 50))
tr := &http.Transport{}
if a.httpsVerify {
tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
}
if a.proxy != "" {
parse, err := url.Parse(a.proxy)
if err != nil {
log.Println(err)
return
}
tr.Proxy = http.ProxyURL(parse)
}
log.Printf("代理地址:%s", a.proxy)
if len(a.headers) > 0 {
log.Printf("请求头:")
for k, v := range a.headers {
log.Printf("%s:%s", k, v)
}
}
if isPost {
log.Printf("请求地址:%s", a.url)
log.Printf("请求参数:")
for k, v := range a.body {
log.Printf("%s:%s", k, v)
}
log.Println(strings.Repeat("=", 50))
} else {
req_url := a.url
if len(a.body) > 0 {
params := url.Values{}
for k, v := range a.body {
params.Add(k, v)
}
req_url = fmt.Sprintf("%s?%s", a.url, params.Encode())
}
log.Printf("请求地址:%s", req_url)
log.Printf("请求参数:")
for k, v := range a.body {
log.Printf("%s:%s", k, v)
}
log.Println(strings.Repeat("=", 50))
}
}
使用方式
func TestHttp(t *testing.T) {
param := map[string]string{
"ac": "videolist",
"t": "46",
}
headers := map[string]string{
"token": "orangbus",
"content-type": "application/json",
}
h := axios.NewAxios("https://httpbin.org/get", param).SetProxy("http://127.0.0.1:7890").SetHeader(headers)
bytes, err := h.Get()
if err != nil {
t.Log(err)
return
}
t.Log(string(bytes))
}
通用方法
package utils
import (
"crypto/rand"
"golang.org/x/crypto/bcrypt"
"log"
"math/big"
"os"
"strings"
)
/*
*
加密密码
*/
func BcryptPassword(password string) (string, error) {
passwd, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return "", err
}
return string(passwd), nil
}
/*
*
密码比对
*/
func ComparePassword(hashPasswd, password string) error {
err := bcrypt.CompareHashAndPassword([]byte(hashPasswd), []byte(password))
if err != nil {
return err
}
return nil
}
func GenerateCode() (string, error) {
const chars = "0123456789"
var code strings.Builder
// 设置随机种子
for i := 0; i < 4; i++ {
// 生成 [0, len(chars)-1) 范围内的随机数
idx, err := rand.Int(rand.Reader, big.NewInt(int64(len(chars)-1)))
if err != nil {
return "", err
}
// 将随机索引转换为字符并追加到验证码字符串中
code.WriteByte(chars[idx.Int64()])
}
return code.String(), nil
}
func CheckDirExistAndCreate(dirPath string) error {
_, err := os.Stat(dirPath)
if os.IsNotExist(err) {
if err := os.MkdirAll(dirPath, os.ModePerm); err != nil {
return err
}
log.Printf("目录 %s 已创建。\n", dirPath)
} else if err != nil {
return err
}
return nil
}