跳到主要内容

GO常用基础实例

介绍

记录一些可能常用到的实例,便于快速查询

读文件

按字节读取文件

这种方式是以字节为单位来读取,相对底层一些,代码量也较大

package main

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

func main() {
file,_ := os.Open("test.txt")
defer file.Close()

buf := make([]byte, 1024) // 每次读取1024字节
var res string // 存放最终的结果

for {
count, err := file.Read(buf)
if err == io.EOF { // 检测是否到结尾
break
} else {
currBytes := buf[:count] // 读取了count字节
res += string(currBytes) // 最终结果
}
}
fmt.Println(res)
}

借助ioutil来读取

由于 os.File 也是 io.Reader 的实现,我们可以调用 ioutil.ReadAll(io.Reader) 方法,将文件所有字节读取出来,省去了使用字节缓存循环读取的过程。

package main

import (
"fmt"
"io/ioutil"
"os"
)

func main() {
file,_ := os.Open("test.txt")
defer file.Close()

byteRes, _ := ioutil.ReadAll(file) // 返回存放结果的切片
fmt.Printf("%T\n", byteRes) // []uint8
fmt.Println(string(byteRes))
}

仅使用ioutil包来完成读取操作

为了进一步简化文件读取操作,ioutil 还提供了 ioutil.ReadFile(filename string) 方法,一行代码搞定读取任务

package main

import (
"fmt"
"io/ioutil"
)

func main() {
data, _ := ioutil.ReadFile("test.txt")
fmt.Println(string(data))
}

利用Scanner按行读取

逐行读取文件内容,这个时候可以 Scanner 来完成

package main

import (
"bufio"
"fmt"
"os"
)

func main() {
file, _ := os.Open("test.txt")
defer file.Close()

scanner := bufio.NewScanner(file) // 类似Java中的Scanner
for scanner.Scan() {
fmt.Println(scanner.Text())
}
}

写文件

使用ioutil

刚才用到了ioutil.ReadFile,与之对应的,肯定也有WriteFile

该函数属于全覆盖写入,如果文件不存在,则会根据指定的权限创建文件,如果存在,则会先清空文件原有内容,然后再写入新数据

package main

import (
"fmt"
"io/ioutil"
"os"
)

func main() {
data := []byte("hello d4m1ts")
fmt.Println(os.FileMode(0666).String()) // -rw-rw-rw-
ioutil.WriteFile("test.txt", data, 0666)
}

通过File句柄

os.OpenFile(name string, flag int, perm FileMode)方法,通过指定额外的 读写方式文件权限 参数,使文件操作变得更为灵活。

flag 有以下几种常用的值:

  • os.O_CREATE: create if none exists 不存在则创建
  • os.O_RDONLY: read-only 只读
  • os.O_WRONLY: write-only 只写
  • os.O_RDWR: read-write 可读可写
  • os.O_TRUNC: truncate when opened 文件长度截为0:即清空文件
  • os.O_APPEND: append 追加新数据到文件
package main

import (
"fmt"
"os"
)

func main() {
file,_ := os.OpenFile("test.txt", os.O_RDWR | os.O_APPEND | os.O_CREATE, 0666) // 按照特定权限打开
defer file.Close()

data := []byte("hello d4m1ts")
count, _ := file.Write(data) // 按字节写入,返回的count为写入的字节数
fmt.Println(count)

count, _ = file.WriteString("\nHello D4m1ts") // 按字符串写入
fmt.Println(count)

file.Sync() // 确保写入到磁盘
}

通过bufio包

这种方式其实是在File句柄上做了一层封装,调用方式和上面直接写入非常相似

package main

import (
"bufio"
"fmt"
"os"
)

func main() {
file,_ := os.OpenFile("test.txt", os.O_RDWR | os.O_APPEND | os.O_CREATE, 0666)
defer file.Close()

writer := bufio.NewWriter(file)

data := []byte("hello d4m1ts")
count, _ := writer.Write(data) // 按字节写入,返回的count为写入的字节数
fmt.Println(count)

count, _ = writer.WriteString("\nHello D4m1ts") // 按字符串写入
fmt.Println(count)

writer.Flush() // 清空缓存,确保写入到磁盘
}

正则表达式

匹配内容是否存在

package main

import (
"fmt"
"regexp"
)

func main() {
regex := "\\d{1,3}"
res, _ := regexp.MatchString(regex, "123123")
res1, _ := regexp.MatchString(regex, "aaaa")
fmt.Println(res) // true
fmt.Println(res1) // false

}

提取内容

package main

import (
"fmt"
"regexp"
)

func main() {
regex, _ := regexp.Compile("(\\d{1,3})\\d{1,3}") // 编译正则表达式
fmt.Println(regex.MatchString("123123123123")) // true
fmt.Println(regex.FindString("123213123123")) // 123213 返回第一个匹配的
fmt.Println(regex.FindStringIndex("123213123123")) // [0 6] 返回第一个匹配的开始和结尾的索引
fmt.Println(regex.FindStringSubmatch("123213123123")) // [123213 123] 返回包括()这种子匹配的
fmt.Println(regex.FindAllString("123213123123",-1)) // [123213 123123] 返回匹配的所有内容,n表示为返回个数,-1则返回全部
fmt.Println(regex.FindAllStringSubmatch("123213123123",-1)) // [[123213 123] [123123 123]] 同时返回子匹配的结果
fmt.Println(regex.FindAll([]byte("123123123123"), -1)) // [[49 50 51 49 50 51] [49 50 51 49 50 51]] 通过字节去匹配,返回的也是字节的结果
}

替换内容

package main

import (
"fmt"
"regexp"
)

func main() {
regex, _ := regexp.Compile("(\\d{1,3})\\d{1,3}") // 编译正则表达式
fmt.Println(regex.ReplaceAllString("123123123213","a")) // aa
}

其他

re.S

在线正则表达式测试网站:https://regex101.com/

golang正则匹配的时候,.默认是不匹配换行的,所以要匹配多行数据就容易出问题

这个时候可以采用如下格式,让.也可以匹配换行

(?s).*

实例:

image-20211229171038928

不匹配字符串

说明如下:

(?=exp):零宽度正预测先行断言,它断言自身出现的位置的后面能匹配表达式 exp。

(?<=exp):零宽度正回顾后发断言,它断言自身出现的位置的前面能匹配表达式 exp。

(?!exp):零宽度负预测先行断言,断言此位置的后面不能匹配表达式 exp。

(?<!exp):零宽度负回顾后发断言来断言此位置的前面不能匹配表达式 exp。

举例:匹配邮箱,但是结尾不能是.js

[a-zA-Z0-9]+@[a-zA-Z0-9]+\.(?!js)[a-zA-Z0-9\.]{2,}

别用上面regex101的平台,不支持这种语法,可以用 https://c.runoob.com/front-end/854/ 这个平台

备注

最重要的一点,go自带的regexp不能用这种语法,可以用下面的包

go get -u github.com/dlclark/regexp2

JSON序列化和反序列化

标准JSON模块

Go 语言标准库 encoding/json 提供了操作 JSON 的方法,一般可以使用 json.Marshaljson.Unmarshal 来序列化和解析 JSON 字符串

实例一:

package main

import (
"encoding/json"
"fmt"
)

func main() {
// 序列化
s1 := []string{"apple", "peach", "pear"}
s2, _ := json.Marshal(s1) // 转字符串
fmt.Println(string(s2)) // ["apple","peach","pear"]

// 反序列化
var s3 [] string
json.Unmarshal([]byte(s2), &s3) // 字符串恢复
fmt.Println(s3) // [apple peach pear]
fmt.Println(len(s3), s3[0]) // 3 apple
}

实例二:

package main

import (
"encoding/json"
"fmt"
)

// 定义结构体,变量名注意大写,因为跨到json包了
type User struct {
Name string
Age int
}

func main() {
// 初始化结构体
user := User{
Name: "d4m1ts",
Age: 18,
}

// 序列化,转字符串
s1, _ := json.Marshal(user)
fmt.Println(string(s1))

// 反序列化,恢复为原来的结构
user2 := User{}
json.Unmarshal(s1, &user2)
fmt.Println(user2)

}

更优的jsoniter

标准库 encoding/json 在使用时需要预先定义结构体,使用时显得不够灵活。这时候可以尝试使用 github.com/json-iterator/go 模块,其除了提供与标准库一样的接口之外,还提供了一系列更加灵活的操作方法

go get -v github.com/json-iterator/go
package main

import (
"fmt"
jsoniter "github.com/json-iterator/go"
)

func main() {
// 反序列化,恢复为原来的结构
s := `{"a":"b", "c":["d","e","f"]}`
res := jsoniter.Get([]byte(s), "c")
fmt.Println(res.ToString()) // ["d","e","f"] 只解析"c"的内容部分
}

扩展

大多数时候获取的json数据可能是映射+切片形式的,只有上面的一些内容很难搞,所以还是补充一下

使用.GetInterface()会自动给结果转换为interface{},再通过这个结果继续转换,如[]interface{}

想要拿到最后的数据,只需要通过数据.(对应的格式)即可,如 aaa.(string)表示转换为string

备注

一步一步看吧,用.GetInterface()或者等报错提示,就可以看到应该转换的格式了,如下图右边就是可以转换的格式

image-20220131142914379

func main() {
strings := "{\"a\":[{\"b\":\"c\"}]}"

var res interface{}
res = jsoniter.Get([]byte(strings), "a").GetInterface()
fmt.Println(res.([]interface{}))

for _,i := range res.([]interface{}) {
fmt.Println(i.(map[string]interface{}))
}

}

时间日期

package main

import (
"fmt"
"time"
)

func main() {
p := fmt.Println
// 现在的时间
now := time.Now()
p(now)
// 休眠1秒
time.Sleep(time.Second * 1)
p(time.Now()) // 现在的时间

// 格式类型转换
t1, _ := time.Parse(time.RFC3339, "2012-11-01T22:08:41+00:00")
p(t1) // 2012-11-01 22:08:41 +0000 +0000
// 格式化输出
p(now.Format("3:04PM")) // 5:14PM
p(now.Format("Mon Jan _2 15:04:05 2006")) // Tue Dec 28 17:15:49 2021
p(now.Format("2006-01-02T15:04:05.999999-07:00")) // 2021-12-28T17:15:49.121777+08:00
p(now.Format(time.RFC850)) // Tuesday, 28-Dec-21 17:20:02 CST
fmt.Printf("%d-%02d-%02dT%02d:%02d:%02d-00:00\n",
now.Year(), now.Month(), now.Day(),
now.Hour(), now.Minute(), now.Second()) // 2021-12-28T17:20:02-00:00
}

随机数

伪随机数

Go的math/rand包提供伪随机数生成。例如,rand.Intn返回一个随机int n,0 <= n <100

伪随机生成的数字是确定的,不论在什么机器、什么时间,只要执行的随机代码一样,那么生成的随机数就一样

package main

import (
"fmt"
"math/rand"
)

func main() {
p := fmt.Println
p(rand.Int()) // 5577006791947779410
p(rand.Int31n(int32(60))) // 27
for i:=0; i<5; i+=1 {
p(rand.Float64()) // 0.6645600532184904 每次执行的结果都是一样
}
}

真随机数

crypto/rand是为了提供更好的随机性满足密码对随机数的要求,在linux上已经有一个实现就是/dev/urandomcrypto/rand就是从这个地方读“真随机”数字返回,但性能比较慢

package main

import (
"crypto/rand"
"math/big"
)

func main() {
for i := 0; i < 4; i++ {
n, _ := rand.Int(rand.Reader, big.NewInt(100))
println(n.Int64())
}
}

URL解析

可以直接使用url.Parse(string u)来解析,其中包括方案,身份验证信息,主机,端口,路径,查询参数和查询片段等信息

package main

import (
"fmt"
"net/url"
)

func main() {
u := "https://admin:[email protected]/test/point?a=123&b=test"
uu, _ := url.Parse(u)

fmt.Println(uu.Scheme)
fmt.Println(uu.User)
fmt.Println(uu.User.Username())
fmt.Println(uu.Host)
fmt.Println(uu.Path)
fmt.Println(uu.Hostname())
fmt.Println(uu.Query())
}
/*
https
admin:password
admin
blog.gm7.org
/test/point
blog.gm7.org
map[a:[123] b:[test]]
*/

Base64

package main

import (
"encoding/base64"
"fmt"
)

func main() {
a := "123456"
res := base64.StdEncoding.EncodeToString([] byte(a))
fmt.Println(res) // MTIzNDU2

decod, _ := base64.StdEncoding.DecodeString(res)
fmt.Println(string(decod)) // 123456
}

命令行参数

从命令行获取参数,得到的是一个切片

package main

import (
"fmt"
"os"
)

func main() {
args := os.Args
fmt.Println(args)
}
/*
go run Hello.go 123 456
[/var/folders/fw/tddtsjp91wb9q64l5xt7jd540000gn/T/go-build3185553057/b001/exe/Hello 123 456]
*/

执行系统命令

在 Golang 中用于执行命令的库是 os/execexec.Command 函数返回一个 Cmd 对象,根据不同的需求,可以将命令的执行分为三种情况

  1. 只执行命令,不获取结果
  2. 执行命令,并获取结果(不区分 stdoutstderr
  3. 执行命令,并获取结果(区分 stdoutstderr

只执行命令,不获取结果

直接调用Run()函数

package main

import (
"fmt"
"os/exec"
)

func main() {
res := exec.Command("open", "-na", "Calculator").Run() // run会阻塞等到命令执行结束
fmt.Println(res) // <nil>
}

执行命令获取结果

可以调用 cmdCombinedOutput 函数

package main

import (
"fmt"
"os/exec"
)

func main() {
cmd := exec.Command("ls", "-la")
res, _ := cmd.CombinedOutput()
fmt.Println(string(res)) // ls -la 执行结果
}

执行命令获取结果并区分stdoutstderr

package main

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

func main() {
cmd := exec.Command("ls", "-la")
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
cmd.Run()

fmt.Println(string(stdout.Bytes())) // ls -la 执行结果
}

多条命令使用管道执行

就是类似shell里面|的作用,ls -la | grep go

package main

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

func main() {
cmd1 := exec.Command("ls", "-la")
cmd2 := exec.Command("grep", "go")
cmd2.Stdin, _ = cmd1.StdoutPipe()
cmd2.Stdout = os.Stdout
cmd2.Start()
cmd1.Run()
cmd2.Wait()

fmt.Println(cmd2.Stdout) // ls -la | grep go 执行结果
}

扩展

前面每个空格间都需要单独一个参数,有时候很长就不方便,可以采用如下的方式来

cmd := exec.Command("/bin/sh","-c","expr 2 + 33")

HTTP请求

快速发起get请求

package main

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

func main() {
url := "https://www.baidu.com/"
response,_ := http.Get(url) // 发起get请求
defer response.Body.Close()
fmt.Println(response.StatusCode)
fmt.Println(response.Header.Get("Server"))
body, _ := ioutil.ReadAll(response.Body)
fmt.Println(string(body)) // 源码
}

收到的数据包

GET / HTTP/1.1
Host: 127.0.0.1:8000
User-Agent: Go-http-client/1.1
Accept-Encoding: gzip

带有参数的GET请求

可以直接在上面的url后面直接构造参数,也可以通过如下的方式手动设置参数

package main

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

func main() {
u := "http://baidu.com/"
Url, _ := url.Parse(u)

param := url.Values{}
param.Set("name", "d4m1ts")
param.Set("中文", "汉字测试")

Url.RawQuery = param.Encode() // 包含URL编码
fmt.Println(Url) // http://baidu.com/?name=d4m1ts&%E4%B8%AD%E6%96%87=%E6%B1%89%E5%AD%97%E6%B5%8B%E8%AF%95

resp,_ := http.Get(Url.String())
fmt.Println(resp.StatusCode) // 200
}

收到的数据包

GET /?name=d4m1ts&%E4%B8%AD%E6%96%87=%E6%B1%89%E5%AD%97%E6%B5%8B%E8%AF%95 HTTP/1.1
Host: 127.0.0.1:8000
User-Agent: Go-http-client/1.1
Accept-Encoding: gzip

返回JSON的数据包

返回是json格式,如何快速格式化数据

  • 返回的json内容
{
"args": {},
"headers": {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
"Accept-Encoding": "gzip, deflate",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
"Dnt": "1",
"Host": "httpbin.org",
"Sec-Gpc": "1",
"Upgrade-Insecure-Requests": "1",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.55 Safari/537.36",
"X-Amzn-Trace-Id": "Root=1-61cbb6de-6e8a5d6a2710be6f22da6f92"
},
"origin": "213.239.21.35",
"url": "http://httpbin.org/get"
}
  • 一样的发起http请求,只是最后用JSON来反序列化而已
package main

import (
"fmt"
jsoniter "github.com/json-iterator/go"
"io/ioutil"
"net/http"
)

func main() {
u := "http://httpbin.org/get"
resp, err := http.Get(u)
if err == nil { // 请求成功
body, _ := ioutil.ReadAll(resp.Body)

origin := jsoniter.Get(body, "origin")
fmt.Println(origin.ToString()) // 213.239.21.35

headers := jsoniter.Get(body, "headers")
fmt.Println(headers.Get("Host").ToString()) // httpbin.org
}
}

自定义Header头

package main

import (
"fmt"
jsoniter "github.com/json-iterator/go"
"io/ioutil"
"net/http"
"time"
)

func main() {
// 创建一个HTTP请求,但是不发送请求
u := "http://httpbin.org/get"
req, _ := http.NewRequest("GET", u, nil)
req.Header.Set("User-Agent", "Test GO")
req.Header.Set("Name", "d4m1ts")

// 发送刚才创建的请求
client := http.Client{
Timeout: 3*time.Second, // 超时时间
}
resp, _ := client.Do(req)
body, _ := ioutil.ReadAll(resp.Body)
headers := jsoniter.Get(body, "headers")
fmt.Println(headers.ToString())
}

/*
{
"Accept-Encoding": "gzip",
"Host": "httpbin.org",
"Name": "d4m1ts",
"User-Agent": "Test GO",
"X-Amzn-Trace-Id": "Root=1-61cbbb0d-68f21a6c5c36abd861b6fe99"
}
*/

收到的数据包

GET / HTTP/1.1
Host: 127.0.0.1:8000
User-Agent: Test GO
Name: d4m1ts
Accept-Encoding: gzip

快速发起POST请求

**方法一:**使用http.Post,有一点点麻烦

package main

import (
"net/http"
"strings"
)

func main() {
u := "http://127.0.0.1:8000"
http.Post(u, "application/x-www-form-urlencoded", strings.NewReader("aa=bb"))
}

收到的http请求

POST / HTTP/1.1
Host: 127.0.0.1:8000
User-Agent: Go-http-client/1.1
Content-Length: 5
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip

aa=bb

**方法二:**使用http.PostForm,相对简单,但是无法设置content-type,没那么自由

package main

import (
"net/http"
"net/url"
)

func main() {
u := "http://127.0.0.1:8000"
param := url.Values{}
param.Set("aaa", "bbb")
http.PostForm(u, param)
}

收到的http请求

POST / HTTP/1.1
Host: 127.0.0.1:8000
User-Agent: Go-http-client/1.1
Content-Length: 7
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip

aaa=bbb

自定义POST请求

get差不多,只不过是多了设置content-type和post数据的步骤而已

举例为发送json数据

package main

import (
"bytes"
"encoding/json"
"net/http"
)

func main() {
// 创建请求,但是不发起
u := "http://127.0.0.1:8000"
param := map[string]string{
"aa": "bb",
"name": "d4m1ts",
}
buf, _ := json.Marshal(param) // 序列化的json
req, _ := http.NewRequest("POST", u, bytes.NewReader(buf))
req.Header.Set("User-Agent", "UA TEST")
req.Header.Set("Content-Type", "application/json")

// 发送刚才的请求
client := http.Client{}
client.Do(req)
}

收到的http数据包

POST / HTTP/1.1
Host: 127.0.0.1:8000
User-Agent: UA TEST
Content-Length: 27
Content-Type: application/json
Accept-Encoding: gzip

{"aa":"bb","name":"d4m1ts"}

忽略证书

有些时候一些ssl网站因为证书问题也会抛出panic异常,所以一般可以忽略SSL证书,在初始化http客户端的时候忽略,代码如下

tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // 忽略SSL证书
}
Client := http.Client{
Transport: tr,
}

cookieJar和代理设置

// 初始化Client
jar, _ := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
proxy,_ := url.Parse("socks5://127.0.0.1:1080")
netTransport := &http.Transport{
Proxy: http.ProxyURL(proxy),
MaxIdleConnsPerHost: 10,
ResponseHeaderTimeout: time.Second * time.Duration(5),
}
Client = http.Client{
Transport: netTransport,
Jar: jar,
Timeout: time.Second * 10,
}

Client.Get("http://cip.cc")

gzip解压

有些返回的数据是二进制乱码的,这个时候我们就需要进行gzip解压

	resp, _ := Client.Do(req)
reader,_ := gzip.NewReader(resp.Body)
source, _ := ioutil.ReadAll(reader)
fmt.Println(string(source))

resp.Body复用

存在一个多次利用resp.Body响应包的场景,一般情况下,直接ioutil.ReadAll(resp.Body)读一次就完了,但是如果需要多次使用的话,第二次读取的时候内容就已经为空了。

a, _ := ioutil.ReadAll(response.Body)
fmt.Print(a) // 第一次读取,有内容
defer response.Body.Close()
b, _ := ioutil.ReadAll(resp.Body)
fmt.Print(b) // 第二次读取,内容为空
defer resp.Body.Close()

原因是大多数Http框架都是这样实现的,只读一次,是因为持有的缓冲区的指针都是往前读的,如果一直持有缓冲区而不释放会出问题,可以想象一下,假如可以多次重复读,那么用户连接所产生的的内存占用的缓冲区有多大呢?什么时候释放呢?

在实际开发中,响应主体持有的资源可能会很大,所以并不会将其直接保存在内存中,只是持有数据流连接。当我们需要时,才会从服务器获取数据并返回。同时,考虑到应用重复读取数据的可能性很小,所以将其设计为一次性流(one-shot),即“读取后立即关闭并释放资源”。

解决办法:

添加一行即可,将数据写入Body用于传递

a, _ := ioutil.ReadAll(response.Body)
fmt.Print(a)

// 用该方法继续将数据写入Body用于传递
response.Body = ioutil.NopCloser(bytes.NewBuffer(a))

defer response.Body.Close()
b, _ := ioutil.ReadAll(resp.Body)
fmt.Print(b)
defer resp.Body.Close()

go flag

这个库主要用来判断工具命令行传入的参数用的

虽然go有os.Args,但是不如这个好用

演示:

func main() {
var filePath string
flag.StringVar(&filePath, "file", "", "markdown文件路径")
flag.Parse()
if flag.Lookup("file").DefValue == flag.Lookup("file").Value.String() { // 避免使用默认参数,所以加个判断
flag.Usage()
}
if flag.NFlag() == 0 { // 使用的命令行参数个数,这个地方可以用来判断用户是否输入参数(程序正常情况下会使用默认参数)
flag.Usage()
os.Exit(0)
}
}

image-20220130171243948

备注

还有一些其他的参数,可以自己研究下,基本上看到名字就知道啥意思,主要用的就上面那些

如果觉得帮助不好看,可以重写flag.Usage()这个方法

	flag.Usage = func() {
fmt.Fprintf(flag.CommandLine.Output(), "markdown图片自动上传到图床\nUsage of %s:\n", os.Args[0])
flag.PrintDefaults()
}

urfave/cli 比flag高级一点的应用说明

命令行应用程序通常非常小,因此绝对没有理由不让代码自文档化。在编写命令行应用程序时,生成帮助文本和解析命令标志/选项之类的事情不应妨碍工作效率。(比flag要高端,且更方便好看吧)

官方文档 : 本来想简单过一下的,结果网上搜出来全都是一模一样的垃圾文章,利达普了;这里也是用一个例子直接记录,方便我下次要用直接复制粘贴。

go get gopkg.in/urfave/cli.v2

这样直接安装好像会出问题,因为他的module是github.com/urfave/cli/v2,所以引入再用goland去解决依赖问题就行了

举例代码1

package main

import (
"fmt"
"github.com/urfave/cli/v2"
"os"
"sort"
)

var Lang string
var Path string

func main() {
cli.AppHelpTemplate = fmt.Sprintf("%s\nSUPPORT: [email protected]", cli.AppHelpTemplate) // 自定义help文档
app := &cli.App{
Name: "boom",
Usage: "make an explosive entrance",
Flags: []cli.Flag { // 参数部分 --help
&cli.StringFlag{
Name: "lang",
DefaultText: "123", // 默认提示值
Usage: "语言种类 `LANG`", // 参数说明
Required: false, // 是否必须
Value: "chinese", // 默认值
Aliases: []string{"l"}, // 别名
Destination: &Lang, // 赋值给参数
},
&cli.StringFlag{
Name: "path",
Usage: "go path",
EnvVars: []string{"GOPATH"}, // 从环境变量取值
Destination: &Path,

},
},
Commands: []*cli.Command { // 命令部分 help
{
Name: "test",
Aliases: []string {"t"},
Usage: "test command",
Action: func(c *cli.Context) error {
if false {
return cli.Exit("显式退出", 22) // 退出
}
return nil
},
Flags: []cli.Flag {
&cli.IntFlag{
Name: "test1",
Usage: "test1 usage",
},
},
Subcommands: []*cli.Command { // 子命令
{
Name: "add",
Usage: "add a new template",
Action: func(c *cli.Context) error {
fmt.Println("new task template: ", c.Args().First())
return nil
},
},
},
},
{
Name: "cata1",
Category: "template", // 命令分组
},
{
Name: "cata2",
Category: "template",
},
},
Action: func(c *cli.Context) error {
fmt.Println(c.Args()) // 查看输入的参数
return nil
},
Version: "v1.0", // 版本号
}

sort.Sort(cli.FlagsByName(app.Flags))
sort.Sort(cli.CommandsByName(app.Commands))
app.Run(os.Args)
}

官方举例代码2

颜色输出

最简单的版本

package main

import "fmt"

func main() {
fmt.Printf("\033[1;31;40m%s\033[0m\n","Red.")
fmt.Printf("\033[1;37;41m%s\033[0m\n","Red.")
}

image-20220131124542690

输出所有颜色

package main

import "fmt"

func main() {
for b := 40; b <= 47; b++ { // 背景色彩 = 40-47
for f := 30; f <= 37; f++ { // 前景色彩 = 30-37
for d := range []int{0, 1, 4, 5, 7, 8} { // 显示方式 = 0,1,4,5,7,8
fmt.Printf(" %c[%d;%d;%dm%s(f=%d,b=%d,d=%d)%c[0m ", 0x1B, d, b, f, "", f, b, d, 0x1B)
}
fmt.Println("")
}
fmt.Println("")
}
}

image-20220131124701676

但是每一次都这样很难记住,所以已经有人给他封装成了一个包:https://github.com/fatih/color,可以快速看下它的说明文档

实例:

package main

import (
"github.com/fatih/color"
)

func main() {
color.Blue("aaaa%scccc", "bbb")
color.Red("red red")
color.Magenta("And many others ..")
}

image-20220131125153876

go yaml

安装

go get gopkg.in/yaml.v2

测试用yaml文件如下

注意都是小写的

cache:
enable : false
list :
- aaa
- bbb
mysql:
user : root
password : haha
port : 3306

实际代码如下:

备注

注意定义结构体的时候,首字母都是大写的,不然不能解析出来!!!

在官方的简介中对于tag中支持的flag进行了说明,分别有flowinlineomitempty,可以跟进Unmarshal函数直接看

  • flow用于对数组进行解析
  • omitempty用于当带有此flag变量的值为nil或者零值的时候,则在Marshal之后的结果不会带有此变量。
  • inline内联字段,必须是结构或映射
package main

import (
"fmt"
yaml "gopkg.in/yaml.v2"
"io/ioutil"
)

func main() {
// 定义结构体
type Yaml struct {
Cache struct{
Enable bool `yaml:"enable"`
List []string `yaml:"list,flow"`
}
Mysql struct{
User string `yaml:"user"`
Password string `yaml:"password"`
Port int `yaml:"port"`
}
}

result := new(Yaml)
yamlFile, _ := ioutil.ReadFile("test.yml")
_ = yaml.Unmarshal(yamlFile, result)
fmt.Print(result.Cache.List[0])
}

如果要匹配比较复杂的yaml文件,那么可以给每一段拆分成一个结构体,参考 Go语言之读取yaml配置文件,转换成struct结构,json形式输出

简单举例如下:

  • 要读取内容
rules:
rule:
- color: green
engine: dfa
loaded: true
name: Shiro
regex: (=deleteMe|rememberMe=)
scope: any
- color: green
engine: dfa
loaded: true
name: JSON Web Token
regex: (ey[A-Za-z0-9_-]{10,}\.[A-Za-z0-9._-]{10,}|ey[A-Za-z0-9_\/+-]{10,}\.[A-Za-z0-9._\/+-]{10,})
scope: any
- color: green
engine: dfa
loaded: true
name: Swagger UI
regex: ((swagger-ui.html)|(\"swagger\":)|(Swagger UI)|(swaggerUi))
scope: response
type: Fingerprint
  • 匹配代码
func parseYaml(yamlFileName string) {
type Rule struct {
Color string `yaml:"color"`
Engine string `yaml:"engine"`
Loaded bool `yaml:"loaded"`
Name string `yaml:"name"`
Regex string `yaml:"regex"`
Scope string `yaml:"scope"`
}

type Config struct {
Rules struct{
Rule []Rule `yaml:"rule,flow,omitempty"`
Type string `yaml:"type,omitempty"`
}
}

config := new(Config)
res, _ := ioutil.ReadFile(yamlFileName)
yaml.Unmarshal(res, config)
fmt.Print(config)
}

wgo 处理数组

有一天想要判断某个数组里面是否包含某个元素,比如python啥的都有in或者contains,但是go没得,找了一下发现了这个第三方包

go get -u github.com/wxnacy/wgo
import "github.com/wxnacy/wgo/arrays"

image-20220625092948828

发送邮件

go get github.com/jordan-wright/email

发送邮件实例

import (
"crypto/tls"
"net/smtp"

"github.com/jordan-wright/email"
)

func sendExample() error {
ssl := true
host := "example.com"
auth := smtp.PlainAuth("", username, password, host)

e := email.NewEmail()
e.From = "[email protected]"
e.To = []string{"[email protected]"}
e.Subject = "subject"
e.Text = []byte("test")

if ssl {
return e.SendWithTLS("example.com:465", auth, &tls.Config{ServerName: host})
} else {
return e.Send("example.com:25", auth)
}
}

go jwt

go get -u github.com/dgrijalva/jwt-go

举例代码如下:

// Claims 签名的结构体
type Claims struct {
ID int64
Username string
jwt.StandardClaims
}

// GenerateToken 生成JWT TOKEN
func GenerateToken() (string, error) {
nowTime := time.Now()
expireTime := nowTime.Add(1 * time.Second)
issuer := "frank"
claims := Claims{
ID: 10001,
Username: "frank",
StandardClaims: jwt.StandardClaims{
ExpiresAt: expireTime.Unix(),
Issuer: issuer,
},
}
token, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte("!!!123456"))
return token, err
}

// ParseToken 解析JWT TOKEN
func ParseToken(token string) (*Claims, error) {
tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return []byte("!!!123456"), nil
})
if err != nil {
return nil, err
}

if tokenClaims != nil {
if claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid {
return claims, nil
}
}

return nil, err
}

goreleaser

快速打包并上传到github的release中,方便好用

参考文档:https://goreleaser.com/

go embed 打包静态资源文件

默认情况下,将项目打包成二进制的时候是不会加入静态资源文件的,因此在部署的时候还需要捎带上这些文件,比如,一些配置文件,图片,样式表等。

很多时候,这些静态文件是不需要变的,如果能一并加入到二进制文件,就能减少部署时的依赖。

go在1.16中增加了go embed指令,可以将文件嵌入到二进制文件中。

举例代码如下:

其他函数可以通过自动补全来看,还是很简单明确的

package main

import (
"embed"
_ "embed"
"fmt"
)

// 以字符串形式嵌入
//go:embed test.yml
var test1 string

// 以字节切片形式嵌入
//go:embed test.yml
var test2 []byte

// 嵌入文件系统FS,支持直接嵌入一个文件夹;直接输入目录不会嵌入 . 和 _ 开头的文件,如果要嵌入所有文件,则需要 /*
//go:embed test.yml
//go:embed ca.pem
var f embed.FS

func main() {
fmt.Println(test1)
fmt.Println(test2)

file, err := f.ReadFile("test.yml")
if err != nil {
return
}
fmt.Println(string(file))

}

总结:

  • 对于单个的文件,支持嵌入为字符串和 byte slice
  • 对于多个文件和文件夹,支持嵌入为新的文件系统 FS
  • go:embed 指令用来嵌入,必须紧跟着嵌入后的变量名
  • 只支持嵌入为 string, byte slice 和 embed.FS 三种类型,类型派生也不可以。

参考