Go语言之常用库
encoding/json:
encoding/json是官方提供的标准json, 实现RFC 7159中定义的JSON编码和解码。使用的时候需要预定义struct,原理是通过reflection和interface来完成工作, 性能低。
常用的接口:
func Marshal(v interface{}) ([]byte, error)//生成JSON,参数为空接口,表示接受任意类型参数,返回字节切片func Unmarshal(data []byte, v interface{}) error//解析JSON到struct,参数为字节切片以及任意类型- func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) //生成json,返回字节切片,格式好看
1、JSON序列化
(1)、下面例子表示通过Marshal()将结构体序列化为son数据,如图:

运行结果如下:

- 上图中第25行:os.Stdout.Write()用法与fmt.Println()相同,fmt.Println()是在os.Stdout.Write()基础上进行了封装,添加了格式化输出,上图中如果用fmt来打印,应该写成fmt.Println(string(result))
- 第10-12行:结构体的字段名首字母需要大写,否则无法被json解析,Marshal进行序列化的时候是区分大小写的,因此结构体和实例化结构体时字段名必须一致,都是大写
注意:Marshal()返回类型为字节切片,如果要正常显示,需要转换为字符串,因此可写成 fmt.Println(string(result))
(2)、下面例子将结构体切片序列化为JSON数据,如图:

运行结果如下:

(3)、将map序列化为JSON数据,如图:

运行结果如下:

(4)、将结构体指针切片序列化为字符串切片,如图:
package main
import (
"encoding/json"
"fmt"
"log"
)
type abc struct {
Name string
Age string
}
var a []*abc
func main() {
//定义结构体指针切片
a = []*abc{
{Name: "hello", Age: "100"},
{Name: "world", Age: "200"},
}
//将结构体指针切片序列化为字节切片
by, err := json.Marshal(a)
if err != nil {
log.Fatal(err)
}
var b []map[string]string
//将字节切片反序列化为map切片
errs := json.Unmarshal(by, &b)
if errs != nil {
log.Fatal(errs)
}
var d []string
//遍历map切片,然后将里面的map元素反序列化为字节切片后添加到字符串切片中
for _, v := range b {
c, err := json.Marshal(v)
if err != nil {
log.Fatal(err)
}
d = append(d, string(c))
}
fmt.Println(d)
}
运行结果如下:

2、JSON反序列化
(1)、下面例子表示通过 Unmarshal()将json数据解析到结构体中(结构体字段名要大写),如图:


- 第10-13行:将json数据转换为[]byte字节切片类型,并赋值给变量jsonToStruct,发引号表示里面的内容不能转义,但是可以换行,如果不用反引号而用双引号,那么json数据的双引号都要转义,否则报错
- 第14行:声明结构体,字段名必须大写
- 第19行:声明结构体类型的切片
- 第20行:将字节切片解析(反序列化)到结构体person中,Unmarshal的第一个参数为字节切片类型,第二个参数必须为指针类型,否则无法接收数据,Unmarshal反序列化不区分大小写,因此第11-12行的字段名改为小写也是可以的
(2)、将数据单独写在外面,不放在[]byte()中,如图:

(3)、下面是一个将[]byte切片反序列化到结构体指针切片的例子,如图:

执行结果如下:

(4)、下面例子是将[]byte反序列化到结构体中,返回为结构体指针,如图:

运行结果如下:

(5)、将JSON数据反序列化到map中,如图:

程序运行结果为:

(6)、将一个二维切片反序列化到结构体切片中,如图:

运行结果如下:

说明:上面二维切片中用的反引号也可以起到将json数据作为字符串使用,用反引号的好处是json里面的双引号都不用转义了,如果不用反引号而用双引号,那么json里的双引号都要进行转义,如果要讲字节切片反序列化到结构体切片中,那么转换为字节切片的字符串中必须包含[ ]符号,因此s1就是拼接[ ]
3、下面例子表示使用MarshalIndent()和Marshal()生成json,并对比结果,如图:

运行结果可以看出,通过MarshalIndent()生成的json更加直观好看,如图:

从名字上看出,MarshalIndent()和Marshal()之间相差一个Indent()函数,Indent()对读的结果做了一些处理,简单说就是对Json 多了一些格式处理,MarshalIndent()函数实现里就调用了Indent()
注意:结构体的字段的首字母必须大写,否则解析不到
flag
Go语言内置的flag包实现了命令行参数的解析,flag包使得开发命令行工具更为简单
1、使用的时候需要导入flag包,可执行命令import flag即可
2、flag包支持的命令行参数类型有bool、int、int64、uint、uint64、floatfloat64、string、duration(时间区间)
| flag参数 | 有效值 |
| 字符串flag | 合法字符串 |
| 整数flag | 1234、0664、0x1234等类型,也可以是负数 |
| 浮点数flag | 合法浮点数 |
| bool类型flag | 1, 0, t, f, T, F, true, false, TRUE, FALSE, True, False |
| 时间段flag | 任何合法的时间段字符串。如”300ms”、”-1.5h”、”2h45m”。 合法的单位有”ns”、”us” /“µs”、”ms”、”s”、”m”、”h” |
3、有以下两种常用的定义命令行flag参数的方法:
第一种,flag.Type(),语法格式如下:
flag.Type(flag名, 默认值, 帮助信息)
例如:定义姓名、年龄、性别、日期等命令行参数,可使用如下方式:
name := flag.String("name", "张三", "姓名")
age := flag.Int("age", 30, "年龄")
gender := flag.Bool("gender", false, "男")
datetime := flag.Duration("datetime", 0, "时间间隔")
需要注意的是,此时name、age、 gender 、datetime均为对应类型的指针
第二种,flag.TypeVar(),语法格式如下:
flag.TypeVar(Type指针, flag名, 默认值, 帮助信息)
例如:定义姓名、年龄、性别、日期等命令行参数,可使用如下方式:
var name string
var age int
var gender bool
var datetime time.Duration
flag.StringVar(&name, "name", "张三", "姓名")
flag.IntVar(&age, "age", 38, "年龄")
flag.BoolVar(&gender, "gender", false, "男")
flag.DurationVar(&datetime, "datetime", 0, "时间间隔")
4、flag.Parse()
通过以上两种方法定义好命令行flag参数后,需要通过调用flag.Parse()来对命令行参数进行解析
支持的命令行参数格式有以下几种:
-flag xxx(使用空格,一个"-“符号)--flag xxx(使用空格,两个”-"符号)-flag=xxx(使用等号,一个"-“符号)--flag=xxx(使用等号,两个"-“符号)
其中,布尔类型的参数必须使用等号的方式指定
下面例子表示通过flag.TypeVar()方式定义命令行参数,并解析,如图:

正确使用命令行参数如下:

flag其他函数:
flag.NArg() //返回命令行参数后的其他参数个数 flag.Args() //返回命令行参数后的其他参数,以[]string类型 flag.NFlag() //返回使用的命令行参数个数
bufio
1、通过bufio.NewReader实现从标准输入中读取数据(可以是键盘输入),如图:

- 4-8行:需要导入的相关包
- 13行:从标准输入中读取数据,可以理解为键盘输入
- 14行:从标准输入读数据后,直到按了Enter后,也就是换行符后,停止,返回字符串
- 15行-17行:判断错误是否为nil,log.Fatal表示如果有错误立即停止程序
- 18行:strings.TrimSpace()表示去除字符串两边的空格,中间的空格保留
代码运行结果如下:

2、通过bufio.Read([]byte)将Reader读取到字节切片中,语法如下:
func (b *Reader) Read(p []byte) (n int, err error)
下面例子读取本地的file.txt文件,并将数据写入到字节切片中,最后打印,如图:

注意:上面的b.Read(c)返回的第一个元素是实际写入切片c中的字节数,可通过此字节数来对切片b进行裁剪获取一个新的切片(不裁剪那么c切片的长度可能比实际的字节数要多,如果反序列化到结构体会出问题)
3、通过bufio和net/http实现文件下载

strconv
1、通过strconv.Atoi()函数,将字符串类型的数字转换为int,如图:

- 11行:定义字符串,为数字类型的字符串
- 12行:打印变量类型
- 13行:将字符串转换为整数
- 14-16行:判断转换过程中是否有错误
- 18行:打印转换后的变量类型
代码执行结果如下:

2、通过strconv.Itoa()函数将int类型转换为字符串类型,如图:


3、通过bufio.NewScanner读取文件,strconv.ParseFloat转换类型为float64,如图:

- 13行:打开文件data.txt
- 14-16行:如果打开文件出错,停止并退出程序
- 17行:为文件创建一个Scanner,通过go doc bufio NewScanner可查看 用法
- 18行:通过Scan()方法从文件中读物一行文本,读取成功返回true,失败返回false
- 19行:通过ParseFloat将读取内容转换为float64,通过go doc strconv ParseFloat可查看用法

strings
1、通过函数strings.HasPrefix函数判断字符串是否以指定的字母开头,如图:

运行结果如下:

2、 通过函数strings.HasSuffix函数判断字符串是否以指定的字母结尾,如图:


3、通过函数strings.Contains()来判断字符串中是否包含指定的字符,如图:


4、通过Index()判断子字符串或者字符在父字符串中出现的位置,即索引,如图:


5、通过LastIndex()判断字符在父字符串中最后一次出现的索引,如图:


6、通过Replace()将指定的字符替换为指定的字符,如图:


注:Replace最后的-1表示替换所有
7、统计指定字符串或字符出现的非重叠次数,如图:


8、通过Repeat()函数来重复字符串,如图:


9、通过函数ToLower()函数,将字符串中的所有字符都转换为小写,如图:


10、通过函数ToUpper()函数,将字符串中的所有字符转换为大写,如图:


11、通过TrimSpace()函数,去除字符串开头和结尾的空格,如图:


12、通过Trim()和TrimSpace()组合来去除字符串开头和结尾的指定字符,如图:


math/rand
math/rand包有一个Intn函数,可以生成一个随机数,所以我们需要导入math/rand。然后调用rand.Intn生成随机数
1、下面例子表示生成一个0到100之间的随机数(不包括100)以及任意一个随机数,如图:

打印结果为:

注:rand.Intn(int n)函数生成从0-n的随机数(不包括n),rand.Int()函数生成随机数,如果要生成1到100之间随机数,可通过rand.Intn(100) + 1 来实现,实际匹配为0-101,但是不包括101,上图中的时间种子必须要设置,否则每次生成的随机数都是固定的一个
- UnixNano():从时间点January 1, 1970 UTC到当前时间点所经过的时间(单位纳秒)
- Unix():从时间点January 1, 1970 UTC到当前时间点所经过的时间(单位秒)
OS
os包为golang内置的包,可实现多种文件操作
1、下面例子通过os.Args读取命令行参数,计算和以及平均数,如图:

- 11行:通过os.Args实现接收命令行参数,接收后为一个切片,可通过go doc os Args查看用法,如果不从[1:] 开始,那么默认会将文件名放在第一个元素位置
- 12行:定义变量,类型为float64,初始值为0
- 13-19:循环遍历切片,将元素类型转换为float64,并求和
- 20行:计算传入数字的和,并保留2位小数
- 21行:获取切片的长度,并将长度转换为float64类型
- 22行:执行平均数运算,只有相同类型才可以进行运算,因此上一步需要转换长度类型
运行脚本,传递四个参数,运行结果如下:

下面例子也是打印os.Args参数信息,如图:

运行结果如下:

从上图结果看出,切片中一共四个元素,第一个元素就是可执行文件所在全路径,剩下三个元素为执行文件时候指定的三个参数
2、下面通过os.Stat()函数获取文件或文件夹的信息,如图:


3、下面通过os.IsNotExist()函数判断文件或文件夹是否存在,如图:


注:本例子中文件path.go实际是存在的
4、通过os.IsExist()函数判断文件或者文件夹是否存在,如图:


本例子中paths.go文件实际是不存在
golang判断文件或文件夹是否存在的方法为使用os.Stat()函数返回的错误值进行判断:
- 如果返回的错误为nil,说明文件或文件夹存在
- 如果返回的错误类型使用
os.IsNotExist()或者os.IsExist()判断为true,说明文件或文件夹不存在 - 如果返回的错误为其它类型,则不确定是否在存在
5、通过os.IsPermission()函数判断文件或文件夹是否有权限访问,如图:

文件file.go是存在的,并且root有权限访问,如图:

注:如果文件不存在那么默认为没有权限访问,将返回false
6、通过os.Mkdir和os.MkdirAll创建目录,如图:

- 如果目录存在会报错,因此在创建之前需要判断一下
- os.ModePerm:官方写的目录权限为0777,实际测试权限为0755
注: Mkdir()只能创建一个目录,MkdirAll()可以创建多级目录
7、通过os.Rename()重命名文件夹,如图:

path/filepath
1、通过filepath.Join()函数将路径和文件名进行拼接,如图:

代码执行结果如下:

2、通过filepath.Base()来获取文件的最后一个元素,要么是文件夹名字要么是文件名,如图:

运行结果如下:

注:filepath.Base()函数用于获取指定路径的最后一个元素(文件名或目录名),并返回该元素的名称
exec
exec库可用来执行一些命令,通过go doc exec 可查看其用法
1、通过CombinedOutput()函数获取标准输出与标准错误输出,如图:

- 第12行:调用exec包的Command函数来构造执行对象,返回结果为Cmd结构体的指针(通过go doc exec Command可查看),赋值给cmd,此cmd可自定义,与Cmd不是一个,bash 和-c可不写,直接命令就行,如果遇到管道符(|) ,就需要写bash 和 -c了
- 第13行:调用结构体Cmd的CombinedOutput方法执行命令并获取标准输出与标准错误输出,注意返回的类型为字节类型,[]byte
- 第14行-16:判断是否有错误,如果有停止程序并打印错误
- 第17行:如果没有错误,将字节类型转换为字符串后打印输出
注:如果命令有多个参数,那么每个参数都可以单独作为一个字符串,比如执行ansible命令,如下:
package main
import (
"fmt"
"log"
"os/exec"
)
func main() {
host := "192.168.49.224"
src := "/tmp/b.jar"
dest := "/data/linshi"
//执行命令
//cmd := exec.Command("ansible",fmt.Sprintf("%s",host), "-m", "command" ,"-a", "touch /tmp/exec.txt")
//copy文件
cmd := exec.Command("ansible", fmt.Sprintf("%s", host), "-m", "copy", "-a", fmt.Sprintf("src=%s dest=%s", src, dest))
out, err := cmd.CombinedOutput()
if err != nil {
log.Fatal(err)
}
fmt.Println(string(out))
}
2、通过Output()函数获取标准输出,唯独变化的就是第13行,其余没动,如图:

注意:exec.Command只是构造要执行的命令,实际是cmd.Output()来执行命令并获取标准输出
3、通过buffer将stdout和stderr分别处理,如图:

- 第15行:声明字节缓冲变量stdout和stderr
- 第16-17行:获取stdout和stderr的指针地址赋值给cmd.Stdout和cmd.Stderr,由于是传递的指针,因此当cmd.Stdout和cmd.Stderr变化后,原地址stdout和stderr的内容也会变化
- 18行:通过Run()方法执行命令(go doc exec Run查看用法),返回错误
- 22行:调用Bytes()方法(go doc bytes Bytes查看用法),返回字节切片,通过string转换为字符串
4、在Windows的命令行执行exec,命令中包含管道符,可通过命令传递的方式
如果要在windows的cmd窗口获取机器的UUID,直接通过命令获取为
WMIC csproduct get UUID | findstr 2

如果要使用golang的exec命令来执行,方法如下:

- 第12行:StdoutPipe()将会创建一个管道,将命令的标准输出链接到该管道
- 第16行:将cmd1标准输出作为cmd2的标准输入,从而实现了管道传递
- 第18行:获取cmd2的标准输出

5、执行多个命令,通过二维切片方式,如图:

- Command:至少需要两个参数,因此把公共的参数ansible放在第一位,其余的从切片获取
- v… :通过此方式可将一维切片中的字符串全部拆出来作为参数
Output()和Run()方法的区别:
- Output方法返回命令的标准输出和错误,并且阻塞直到命令完成,并返回命令的输出,如果需要获取命令的输出,应该使用
Output方法 Run方法只返回错误,并且阻塞直到命令完成,但不返回命令的输出,如果只关心命令是否成功执行,而不需要获取命令的输出,可以使用Run方法
path
path库为golang自带的库,可以实现获取目录、文件拓展名、获取文件名等,通过go doc path查看用法

1、获取文件拓展名,如图:


注:返回值是路径最后一个斜杠分隔出的路径元素的最后一个’.‘起始的后缀(包括’.’)。如果该元素没有’.’会返回空字符串
2、获取文件名,如图:


注:Base函数返回路径的最后一个元素;在提取元素前会去掉末尾的斜杠。如果路径是””,会返回”.”;如果路径只有一个斜杠,会返回”/”
3、获取文件所在路径,如图:


Dir返回路径去除最后一个路径元素的部分,即该路径最后一个元素所在的目录,使用Split去掉最后一个元素后,会简化路径并去掉末尾的斜杠。如果路径是空字符串,会返回”.”;如果路径由1到多个斜杠后跟0到多个非斜杠字符组成,会返回”/”;其他任何情况下都不会返回以斜杠结尾的路径
4、将文件和路径进行拼接,如图:

运行结果如下:

更多用法可自行网上查找
ssh
ssh库可用于连接远程的linux机器,库全名为:
golang.org/x/crypto/ssh
1、下面是一段完成的通过windows机器连接Linux并执行ls -l /root命令的代码,如下:
package main
import (
"bytes"
"errors"
"fmt"
"io/ioutil"
"log"
"net"
"strings"
"golang.org/x/crypto/ssh"
)
func main() {
host := "ip:22"
sshConfig := &ssh.ClientConfig{
User: "root",
Auth: []ssh.AuthMethod{
ssh.Password("password"),
},
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
//读取本地保存的known_hosts文件
rekey, err := ioutil.ReadFile("known_hosts")
if err != nil {
log.Fatal(err)
}
//解析读取的内容
_, hostnames, pubKey, _, _, err := ssh.ParseKnownHosts(rekey)
if err != nil {
log.Fatal(err)
}
//进行比较,相等返回nil,否则返回错误
if strings.Split(hostname, ":")[0] == hostnames[0] && bytes.Equal(pubKey.Marshal(), key.Marshal()) {
return nil
}
return errors.New("主机公钥验证失败")
},
}
//通过tcp进行连接
client, err := ssh.Dial("tcp", host, sshConfig)
if err != nil {
log.Fatal(err)
}
defer client.Close()
//创建会话
session, err := client.NewSession()
if err != nil {
log.Fatal(err)
}
defer session.Close()
//执行命令
res, err := session.CombinedOutput("ls -l /root")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(res))
}
说明:
- host:定义要远程连接的IP和端口
- sshConfig:配置要连接的主机的信息,User表示用户名,Auth里配置密码
- HostKeyCallback:连接成功后执行的一段回调函数,验证主机密钥,通过ReadFile读取known_host的内容,然后对内容进行解析,解析后获取内容中的主机名和公钥内容,将此内容与回调函数从服务器获取的主机名和公钥进行对比,如果相同那么返回nil,继续连接,否则返回错误,断开连接,本例子中的known_hosts为我手动从用户的.ssh目录复制过来到代码目录下的
know_hosts文件是哪里来的?
当通过ssh第一次连接远程主机并成功后,会在本地的.ssh路径下的know_hosts下记录远程主机的主机名+公钥信息,windows路径在用户目录下(linux也是如此),如图:

每次通过ssh连接的时候,客户端都会根据本地存储的这个公钥信息验证远程机器的公钥信息,如果相同就可以连接,不同无法连接,这样可以保证连接的安全性,防止被攻击,如果远程主机的秘钥发生变动,那么本地的known_host也需要及时更改,否则无法连接
注意:上面例子中如果想不通过主机公钥验证(不安全),此时可将返回值设置为nil,即可,如下:
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
return nil
}
net/http
1、通过net/http包来发送POST请求到gin的接口,并上传2个文件,如下:
package main
import (
"bytes"
"fmt"
"io"
"log"
"mime/multipart"
"net/http"
"os"
"path/filepath"
)
func main() {
filepaths := []string{"a.go", "b.go"}
rb := &bytes.Buffer{}
//创建一个multipart格式的写入器
wr := multipart.NewWriter(rb)
for _, files := range filepaths {
file, err := os.Open(files)
if err != nil {
log.Fatal(err)
}
defer file.Close()
//调用CreatePart来创建新的multipart部分,向multipart格式的写入器中添加多个部分
//filepath.Base()用来返回路径的最后一个元素,要么是文件要么是路径
part, err := wr.CreatePart(map[string][]string{
"Content-Disposition": {"form-data; name=\"file\"; filename=\"" + filepath.Base(files) + "\""},
})
if err != nil {
log.Fatal(err)
}
_, err = io.Copy(part, file)
if err != nil {
log.Fatal(err)
}
}
//调用Close()方法来结束写入器并生成multipart格式的内容
wr.Close()
url := "http://127.0.0.1:8080/api/v1/docToPdf"
contentType := wr.FormDataContentType()
resp, err := http.Post(url, contentType, rb)
if err != nil {
log.Fatal(err)
}
//响应体需要关闭
defer resp.Body.Close()
by := make([]byte, 1024) //定义字节切片并初始化,不初始化后面读入不进去
rd := bufio.NewReader(resp.Body) //读取响应体内容
bytes, errs := rd.Read(by) //将内容读入到切片by中,需要初始化分配空间,否则读入不进去
if errs != nil {
log.Fatal(errs)
}
fmt.Println(string(by),bytes) //bytes为实际读入到切片中的字节数量
}
注意:上面的name=”file”,这个file可以自定义值,但是要与服务端的保持一致,也就是说服务端的gin中获取文件的名称也要是file才行,如果这里改了,那里也要同步修改
2、通过net/http来上传文件的另一种写法,如下:
1 package main
2
3 import (
4 "bytes"
5 "fmt"
6 "io"
7 "io/ioutil"
8 "log"
9 "mime/multipart"
10 "net/http"
11 "os"
12 "path/filepath"
13 )
14
15 func main() {
16 client := &http.Client{} //创建一个http的client实例,用于发送http请求
17 bf := &bytes.Buffer{} //创建Buffer实例,用于在内存中存储发送的nultipart数据
18 writer := multipart.NewWriter(bf) //创建writer实例,写入到buffer中,writer后续用于构建multipart/form-data请求体
19 req, err := http.NewRequest("POST", "http://127.0.0.1:8080/upload", nil)
20 if err != nil {
21 log.Fatal(err)
22 }
23 //设置请求头,writer.FormDataContentType方法返回一个Content-Type,格式为multipart/form-data;boundary=<boundary>,<boundary> 是一个随机生成的唯一字符串,用于分隔请求体中的不同部分
24 req.Header.Set("Content-Type", writer.FormDataContentType())
25 fp := "/root/b.jar"
26 file, err := os.Open(fp)
27 if err != nil {
28 log.Fatal(err)
29 }
30 defer file.Close()
31 //创建表单文件字段,字段名为filename,filepath.Base可以截取路径最后的文件名
32 part, err := writer.CreateFormFile("filename", filepath.Base(fp))
33 if err != nil {
34 log.Fatal(err)
35 }
36 //将文件数据流复制到表单字段part中
37 _, err = io.Copy(part, file)
38 if err != nil {
39 log.Fatal(err)
40 }
41 err = writer.Close()
42 if err != nil {
43 log.Fatal(err)
44 }
45 //writer.WriteField("fieldName","fieldValue") //添加其他字段
46 req.Body = io.NopCloser(bf)
47 resp, err := client.Do(req)
48 if err != nil {
49 log.Fatal("cuowu:",err)
50 }
51 defer resp.Body.Close()
52 bodys, err := ioutil.ReadAll(resp.Body)
53 if err != nil {
54 log.Fatal(err)
55 }
56 fmt.Println("Body:", string(bodys))
57 }
- 46行:io.NopCloser将bf包装为ReadCloser类型的数据,因为Do()要读取req.Body,而Body的类型就是io.ReadCloser,因此需要将bf包装为此类型才可以,因此io.NopCloser的主要作用就是将
io.Reader转换为io.ReadCloser - 47行:Do()方法在发送数据的时候,会读取request中的Body数据,也就是上面的req.Body,也会读取method、url等信息
什么时候需要设置请求头?
简单的 GET 请求(不带认证、不带特殊格式要求)是不需要设置请求头的,如下:
req, _ := http.NewRequest("GET", "https://httpbin.org/get", nil)
需要认证或者传递数据的时候就需要设置请求头,如下:
| 发送 JSON | Content-Type: application/json |
| 发送表单 | Content-Type: application/x-www-form-urlencoded |
| 上传文件(multipart) | Content-Type: multipart/form-data; boundary=… |
| 需要认证 | Authorization: Bearer <token> |
| 自定义 UA | User-Agent: my-app/1.0 |
| 接受特定格式 | Accept: application/json |
设置请求头方式:
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer YOUR_TOKEN")
或者
req.Header.Add("Accept", "application/json")
req.Header.Add("X-Custom-Header", "value")
Set和Add是有区别的,如果Set中出现多个同名的键,只保留最后一个,后面的覆盖前面的,Add不会,支持追加,可能产生多个同名的 Header 字段,如下:
req.Header.Set("X-Token", "abc")
req.Header.Set("X-Token", "xyz")
// 最终 Header 中只有:X-Token: xyz
req.Header.Add("X-Token", "abc")
req.Header.Add("X-Token", "xyz")
// 最终 Header 中有两个值:
// X-Token: abc
// X-Token: xyz
3、通过net/http包来发送GET请求到gin的接口,如下:
package main
import (
"fmt"
"io"
"log"
"net/http"
"os"
)
func main() {
//访问此url会下载pdf.zip文件
url := "http://127.0.0.1:8100/api/v1/GetPdfFile"
resp, err := http.Get(url)
if err != nil {
log.Fatal(err)
}
if resp.StatusCode == 200 {
fmt.Println("文件下载成功")
}
defer resp.Body.Close()
//创建一个pdf.zip文件
pdf, err := os.Create("/tmp/pdf.zip")
if err != nil {
log.Fatal(err)
}
defer pdf.Close()
//将Get获取的响应体数据copy到pdf.zip中
_, err = io.Copy(pdf, resp.Body)
if err != nil {
log.Fatal(err)
}
}
sort
1、sort.Strings函数对切片中元素按照字母进行排序,如图:

运行结果如下:

builtin
builtin 包为Go的预声明标识符提供了文档,此包中包含了我们常用的一些标识符,比如int8、int16、int32、int64等等,除此之外,在新版本golang 1.21版本中还新增了几个函数
1、min()函数,用于返回最小值,语法如下:
func min[T cmp.Ordered](x T, y ...T) T
min函数为泛型函数,在传递参数的时候根据参数类型来设置变量T的类型,下面获取数字的最小值,如图:


min函数不仅可以获取整数最小值,也可以获取字符串,浮点数等等
2、max()函数,用于返回最大值,语法如下:
func max[T cmp.Ordered](x T, y ...T) T
max函数也是泛型函数,下面例子来获取最大值,如图:


3、clear()函数,语法如下:
func clear[T ~[]Type | ~map[Type]Type1](t T)
clear函数的参数也是泛型函数,入参为map、slice,如果是 map,则删除 map 中的所有元素,返回一个空 map;如果是 slice,则将 slice 中的所有元素改为切片类型的零值
(1)、下面定义一个map,然后调用clear()函数,如图:

运行结果如下:

从上图看出,clear中传入map后,会将map的内容清空变为空的map
(2)、下面定义一个slice,然后传入clear()中,如图:

运行结果如下:

从上图可以看出,map调用clear后被清空,slice调用clear后元素变为零值
io/ioutil
1、通过ioutil.ReadDir()读取目录,返回一个fs.FileInfo切片和error,如图:


2、可根据返回切片的长度来判断目录是否为空,本例子中guan目录下没文件,如图:


reflect
在Golang中,数组、切片、映射等不能直接使用==进行比较,此时可通过reflect包来比较
1、通过reflect.DeepEqual函数比较两个二维切片,如图:


注:切片是否相等要看元素的内容和顺序来决定的,如果把b中的注释位置放在上面,与a中的顺序不同,他们就不相等了
md5和hex
1、计算文件的md5的值,如图:

2、通过md5库和bufio来计算文件的md5的值,如图:

3、通过md5库和hex库来计算字符串(不是文件)的md5的值,如图:

4、只通过md5库来计算字符串的md5值,如图:

- Sum() 返回的值为[Size]byte,
[Size]byte表示一个固定长度的字节数组,其中Size是数组的长度。这种类型通常用于表示具有固定大小的数据结构,例如哈希值,当你看到一个函数返回类型为[Size]byte,这意味着该函数返回一个长度固定的字节数组。在哈希函数的上下文中,Size通常是指哈希算法产生的哈希值的字节长度。例如,MD5哈希算法产生的哈希值长度为16字节,SHA-1为20字节,SHA-256为32字节,等等
net/smtp实现邮件发送
通过net/smtp和github.com/jordan-wright/email实现邮件发送

下方的smtp.PlainAuth中的第一个参数是邮箱账号,第二个是授权码
time
1、将时间戳转换为标准时间,如图:

运行结果如下:

2、获取当前时间,如图:




