go 杂谈一

杨言锡 · · 1948 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

目录

  1.go 各种代码运行

  2.go 在线编辑代码运行

  3.通过 Gob 包序列化二进制数据

  4.使用 encoding/csv 包读写 CSV 文件

  5.实现 HTTP 断点续传多线程下载

5.1. HTTP断点续传多线程下载

5.2. Range & Content-Range

5.3. Golang代码实现HTTP断点续传多线程下载

  6.logrus 日志使用教程

  6.1 基本用法

  6.2 简单定义格式

  6.3 输入json格式和设置行号

  6.4 自定义Logger

  6.5 Hook 接口用法 logrus每次在执行输入之前都会调用这个hook方法

  6.6 原生方法实现日志管理

  6.7 日志分割,把日志变小

  6.8 hooh 只调用一次

  6.9 使用hook实现邮件警醒,邮件警报hook

7.始构建全文搜索引擎(译)

8.go trace 剖析  1.4占式调度

8.1 Scheduler latency profile

8.2 Goroutine analysis

8.3 View trace

9.Go语言自定义自己的SSH-Server

10.cavaliercoder/grab 支持断点续传

10.1 简单例子

10.2 第二次运行该示例时,它将自动恢复以前的下载并更快地退出

10.3 支持多个文件同时下载

11. 获取目录所有文件


------------

### 1.go 各种代码运行

1.go 1.15 版本使用defer 速度提升,但是使用panic速度减低

go 2020年8月份,go各种版本的更新情况,地址:https://mp.weixin.qq.com/s/Tzqn5kzdfzcQPUD5NtN2mg

1. Go 1.0[1] — 2012 年 3 月:

2. Go 的第一个版本,带着一份兼容性说明文档[2]来保证与未来发布版本的兼容性,进而不会破坏已有的程序。

3. 第一个版本已经有 go tool pprof 命令和 go vet 命令。go tool pprof 与 Google 的 pprof C++ profiler[3] 稍微有些差异。go vet(前身是 go tool vet)命令可以检查包中潜在的错误。

4. Go 1.1[4] — 2013 年 5 月:

5. 这个 Go 版本专注于优化语言(编译器,gc,map,go 调度器)和提升它的性能。下面是一些提升的例子

6. Go 1.2[10] — 2013 年 12 月:

7. 本版本中 test 命令支持测试代码覆盖范围并提供了一个新命令 go tool cover ,此命令能测试代码覆盖率:

8. Go 1.3[11] — 2014 年 6 月:

9. 这个版本对栈管理做了重要的改进。栈可以申请连续的内存片段[12],提高了分配的效率,使下一个版本的栈空间降到 2KB。

10. 栈频繁申请/释放栈片段会导致某些元素变慢,本版本也改进了一些由于上述场景糟糕的分配导致变慢的元素。下面是一个 json 包的例子,展示了它对栈空间的敏感程度:

11. Go 1.4[16] — 2014 年 12 月:

12. 此版本带来了官方对 Android 的支持,[golang.org/x/mobile](Go 1.4[17] ) 让我们可以只用 Go 代码就能写出简单的 Android 程序。

13. 归功于更高效的 gc,之前用 C 和汇编写的运行时代码被翻译成 Go 后,堆的大小降低了 10% 到 30%。

14. 与版本无关的一个巧合是,Go 项目管理从 Mercurial 移植到了 Git,代码从 Google Code 移到了 Github。

15. Go 也提供了 go generate 命令通过扫描用 //go:generate 指示的代码来简化代码生成过程。

16. 在 Go 博客[18] 和文章生成代码[19]中可以查看更多信息。

17. Go 1.5[20] — 2015 年 8 月:

18. 这个新版本,发布时间推迟[21]了两个月,目的是在以后每年八月和二月发布新版本:

19. Go 1.6[24] — 2016 年 2 月:

20. 这个版本最重大的变化是使用 HTTPS 时默认支持 HTTP/2。

21. 在这个版本中 gc 等待时间也降低了:

22. Go 1.7[25] — 2016 年 8 月:

23. 这个版本发布了 context 包[26],为用户提供了处理超时和任务取消的方法。

24. 阅读我的文章 传递上下文和取消[27]来获取更多关于 context 的信息。

25. 对编译工具链也作了优化,编译速度更快,生成的二进制文件更小,有时甚至可以减小 20% 到 30%。

26. Go 1.8[28] — 2017 年 2 月:

27. 把 gc 的停顿时间减少到了 1 毫秒以下:

28. Go 1.9[30] — 2017 年 8 月:

29. 这个版本支持下面的别名声明:

30. type byte = uint8

31. 这里 byte 是 uint8 的一个别名。

32. sync 包新增了一个 Map[31] 类型,是并发写安全的。

33. 我的文章 Map 与并发写[32] 中有更多信息

34. Go 1.10[33] — 2018 年 2 月:

35. test 包引进了一个新的智能 cache,运行会测试后会缓存测试结果。如果运行完一次后没有做任何修改,那么开发者就不需要重复运行测试,节省时间。

36. Go 1.11[34] — 2018 年 8 月:

37. Go 1.11 带来了一个重要的新功能:Go modules[35]。去年的调查显示,Go modules 是 Go 社区遭遇重大挑战后的产物:

38. Go 1.12[37] — 2019 年 2 月:

39. 基于 analysis 包重写了 go vet 命令,为开发者写自己的检查器提供了更大的灵活性。

40. 我的文章构建自己的分析器[38]中有更多信息。

41. Go 1.13[39] — 2019 年 9 月:

42. 改进了 sync 包中的 Pool,在 gc 运行时不会清除 pool。它引进了一个缓存来清理两次 gc 运行时都没有被引用的 pool 中的实例。

43. 重写了逃逸分析,减少了 Go 程序中堆上的内存申请的空间。下面是对这个新的逃逸分析运行基准的结果:

44. Go1.14[40] - 2020 年 2 月:

45. 现在 Go Module 已经可以用于生产环境,鼓励所有用户迁移到 Module。该版本支持嵌入具有重叠方法集的接口。性能方面做了较大的改进,包括:进一步提升 defer 性能、页分配器更高效,同时 timer 也更高效。

46. 现在,Goroutine 支持异步抢占。

47. Go1.15[41]  - 2020 年 8 月:

48. 受疫情影响,这次版本变化的内容不太多,但如期发布了。

49. 它的大部分更改在工具链、运行时和库的实现。与往常一样,该版本保留了 Go 1 兼容性的承诺。这几乎保证所有的 Go 程序都能像以前一样正常编译和运行。

50. Go 1.15 包括对链接器的重大改进,改进了对具有大量内核的小对象的分配,并弃用了 X.509 CommonName。GOPROXY 现在支持跳过返回错误的代理,并添加了新的嵌入式 tzdata 包。

------------

------------

###  2.go 在线编辑代码运行

        git clone git@github.com:thetimetravel/gpgsync.git

        cd gpgsync/

        node server.js

        http://localhost:8086/

        github地址:https://github.com/syumai/gpgsync

        Demo:https://gpgsync.herokuapp.com/

------------

##  3.通过 Gob 包序列化二进制数据

Go 官方还提供了 encoding/gob 包将数据序列化为二进制流以便通过网络进行传输

我们在前面 Go 入门教程中已经介绍过 Gob 包作为二进制数据编解码工具的基本使用,这里简单演示下如何将 Gob 编码后的二进制数据写入磁盘文件:

```go

package main

import (

    "bytes"

    "encoding/gob"

    "fmt"

    "io/ioutil"

)

type Article struct {

    Id int

    Title string

    Content string

    Author string

}

// 写入二进制数据到磁盘文件

func write(data interface{}, filename string)  {

    buffer := new(bytes.Buffer)

    encoder := gob.NewEncoder(buffer)

    err := encoder.Encode(data)

    if err != nil {

        panic(err)

    }

    err = ioutil.WriteFile(filename, buffer.Bytes(), 0600)

    if err != nil {

        panic(err)

    }

}

// 从磁盘文件加载二进制数据

func read(data interface{}, filename string) {

    raw, err := ioutil.ReadFile(filename)

    if err != nil {

        panic(err)

    }

    buffer := bytes.NewBuffer(raw)

    dec := gob.NewDecoder(buffer)

    err = dec.Decode(data)

    if err != nil {

        panic(err)

    }

}

func main()  {

    article := Article{

        Id: 1,

        Title: "基于 Gob 包编解码二进制数据",

        Content: "通过 Gob 包序列化二进制数据以便通过网络传输",

        Author: "学院君",

    }

    write(article, "article_data")

    var articleData Article

    read(&articleData, "article_data")

    fmt.Printf("%#v\n", articleData)

}

```

------------

## 4.使用 encoding/csv 包读写 CSV 文件

在 Go 语言中,可以通过官方提供的 encoding/csv 包来操作 CSV 文件的写入和读取,我们新建一个 csv.go 文件,并编写一段示例代码如下:

可以看到新建文件、打开文件、关闭文件和上篇教程操作普通的磁盘文件并无区别,不过这里为了支持通过 CSV 格式写入和读取文件,我们在文件句柄之上套了一层 CSV Writer 和 CSV Reader,这有点像适配器模式,然后我们就可以通过 CSV Writer 写入数据到 CSV 文件,通过 CSV Reader 读取 CSV 文件了:

// 初始化一个 csv writer,并通过这个 writer 写入数据到 csv 文件

writer := csv.NewWriter(csvFile)

...

// 初始化一个 csv reader,并通过这个 reader 从 csv 文件读取数据

reader := csv.NewReader(file) 

// 写入 UTF-8 BOM,防止中文乱码

csvFile.WriteString("\xEF\xBB\xBF")

// 初始化一个 csv writer,并通过这个 writer 写入数据到 csv 文件

writer := csv.NewWriter(csvFile)

  ```

  package main

import (

"encoding/csv"

"fmt"

"os"

"strconv"

)

type Tutorial struct {

Id int

Title string

Summary string

Author string

}

func main()  {

// 创建一个 tutorials.csv 文件

csvFile, err := os.Create("tutorials.csv")

if err != nil {

panic(err)

}

defer csvFile.Close()

// 初始化字典数据

tutorials := []Tutorial{

Tutorial{Id: 1, Title: "Go 入门编程", Summary: "Go 基本语法和使用示例2", Author: "学院君"},

Tutorial{Id: 2, Title: "Go Web 编程", Summary: "Go Web 编程入门指南", Author: "学院君"},

Tutorial{Id: 3, Title: "Go 并发编程", Summary: "通过并发编程提升性能", Author: "学院君"},

Tutorial{Id: 4, Title: "Go 微服务开发", Summary: "基于 go-micro 框架开发微服务", Author: "学院君"},

}

csvFile.WriteString("\xEF\xBB\xBF") // 写入 UTF-8 BOM,防止中文乱码

//这是因为 Excel 默认并不是 UTF-8 编码,因此要解决这个乱码问题,可以在对应的 CSV 文件写入 UTF-8 BOM 头,告知 Excel 通过 UTF-8 编码打开这个文件

// 初始化一个 csv writer,并通过这个 writer 写

// 入数据到 csv 文件

writer := csv.NewWriter(csvFile)

for _, tutorial := range tutorials {

line := []string{

strconv.Itoa(tutorial.Id),  // 将 int 类型数据转化为字符串

tutorial.Title,

tutorial.Summary,

tutorial.Author,

}

// 将切片类型行数据写入 csv 文件

err := writer.Write(line)

if err != nil {

panic(err)

}

}

// 将 writer 缓冲中的数据都推送到 csv 文件,至此就完成了数据写入到 csv 文件

writer.Flush()

// 打开这个 csv 文件

file, err := os.Open("tutorials.csv")

if err != nil {

panic(err)

}

defer file.Close()

// 初始化一个 csv reader,并通过这个 reader 从 csv 文件读取数据

reader := csv.NewReader(file)

// 设置返回记录中每行数据期望的字段数,-1 表示返回所有字段

reader.FieldsPerRecord = -1

// 通过 readAll 方法返回 csv 文件中的所有内容

record, err := reader.ReadAll()

if err != nil {

panic(err)

}

// 遍历从 csv 文件中读取的所有内容,并将其追加到 tutorials2 切片中

var tutorials2 []Tutorial

for _, item := range record {

id, _ := strconv.ParseInt(item[0], 0, 0)

tutorial := Tutorial{Id: int(id), Title: item[1], Summary: item[2], Author: item[3]}

tutorials2 = append(tutorials, tutorial)

}

// 打印 tutorials2 的第一个元素验证 csv 文件写入/读取是否成功

fmt.Println(tutorials2[0].Id)

fmt.Println(tutorials2[0].Title)

fmt.Println(tutorials2[0].Summary)

fmt.Println(tutorials2[0].Author)

}

  ```

------------

## 5.实现 HTTP 断点续传多线程下载

参考网站:https://mp.weixin.qq.com/s/dt5emM2IsJ3DKmL502Zm8A

### 5.1. HTTP断点续传多线程下载

一个比较常见的场景,就是断点续传/下载,在网络情况不好的时候,可以在断开连接以后,仅继续获取部分内容. 例如在网上下载软件,已经下载了 95% 了,此时网络断了,如果不支持范围请求,那就只有被迫重头开始下载.但是如果有范围请求的加持,就只需要下载最后 5% 的资源,避免重新下载.

另一个场景就是多线程下载,对大型文件,开启多个线程, 每个线程下载其中的某一段,最后下载完成之后, 在本地拼接成一个完整的文件,可以更有效的利用资源.

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/1c260b033a68cfbd81d4f107120f6438?showdoc=.jpg)

### 5.2. Range & Content-Range

HTTP1.1 协议(RFC2616)开始支持获取文件的部分内容,这为并行下载以及断点续传提供了技术支持. 它通过在 Header 里两个参数实现的,客户端发请求时对应的是 Range ,服务器端响应时对应的是 Content-Range.

$ curl --location --head 'https://download.jetbrains.com/go/goland-2020.2.2.exe'

date: Sat, 15 Aug 2020 02:44:09 GMT

content-type: text/html

content-length: 138

location: https://download-cf.jetbrains.com/go/goland-2020.2.2.exe

server: nginx

strict-transport-security: max-age=31536000; includeSubdomains;

x-frame-options: DENY

x-content-type-options: nosniff

x-xss-protection: 1; mode=block;

x-geocountry: United States

x-geocode: US

Range

The Range 是一个请求首部,告知服务器返回文件的哪一部分. 在一个 Range 首部中,可以一次性请求多个部分,服务器会以 multipart 文件的形式将其返回. 如果服务器返回的是范围响应,需要使用 206 Partial Content 状态码. 假如所请求的范围不合法,那么服务器会返回 416 Range Not Satisfiable 状态码,表示客户端错误. 服务器允许忽略 Range 首部,从而返回整个文件,状态码用 200 .Range:(unit=first byte pos)-[last byte pos]

Range 头部的格式有以下几种情况:

Range: <unit>=<range-start>-

Range: <unit>=<range-start>-<range-end>

Range: <unit>=<range-start>-<range-end>, <range-start>-<range-end>

Range: <unit>=<range-start>-<range-end>, <range-start>-<range-end>, <range-start>-<range-end>

Content-Range

假如在响应中存在 Accept-Ranges 首部(并且它的值不为 “none”),那么表示该服务器支持范围请求(支持断点续传). 例如,您可以使用 cURL 发送一个 HEAD 请求来进行检测.curl -I http://i.imgur.com/z4d4kWk.jpg

HTTP/1.1 200 OK

...

Accept-Ranges: bytes

Content-Length: 146515

在上面的响应中, Accept-Ranges: bytes 表示界定范围的单位是 bytes . 这里 Content-Length 也是有效信息,因为它提供了要检索的图片的完整大小.

如果站点未发送 Accept-Ranges 首部,那么它们有可能不支持范围请求.一些站点会明确将其值设置为 “none”,以此来表明不支持.在这种情况下,某些应用的下载管理器会将暂停按钮禁用.

### 5.3. Golang代码实现HTTP断点续传多线程下载

通过以下代码您可以了解到多线程下载的原理, 同时给您突破百度网盘下载提供思路.

```

package main

import (

"crypto/sha256"

"encoding/hex"

"errors"

"fmt"

"io/ioutil"

"log"

"mime"

"net/http"

"os"

"path/filepath"

"strconv"

"sync"

"time"

)

func parseFileInfoFrom(resp *http.Response) string {

contentDisposition := resp.Header.Get("Content-Disposition")

if contentDisposition != "" {

  _, params, err := mime.ParseMediaType(contentDisposition)

  if err != nil {

  panic(err)

  }

  return params["filename"]

}

filename := filepath.Base(resp.Request.URL.Path)

return filename

}

//FileDownloader 文件下载器

type FileDownloader struct {

fileSize      int

url            string

outputFileName string

totalPart      int //下载线程

outputDir      string

doneFilePart  []filePart

}

//NewFileDownloader .

func NewFileDownloader(url, outputFileName, outputDir string, totalPart int) *FileDownloader {

if outputDir == "" {

  wd, err := os.Getwd() //获取当前工作目录

  if err != nil {

  log.Println(err)

  }

  outputDir = wd

}

return &FileDownloader{

  fileSize:      0,

  url:            url,

  outputFileName: outputFileName,

  outputDir:      outputDir,

  totalPart:      totalPart,

  doneFilePart:  make([]filePart, totalPart),

}

}

//filePart 文件分片

type filePart struct {

Index int    //文件分片的序号

From  int    //开始byte

To    int    //解决byte

Data  []byte //http下载得到的文件内容

}

func main() {

startTime := time.Now()

var url string //下载文件的地址

url = "https://download.jetbrains.com/go/goland-2020.2.2.dmg"

downloader := NewFileDownloader(url, "", "", 10)

if err := downloader.Run(); err != nil {

  // fmt.Printf("\n%s", err)

  log.Fatal(err)

}

fmt.Printf("\n 文件下载完成耗时: %f second\n", time.Now().Sub(startTime).Seconds())

}

//head 获取要下载的文件的基本信息(header) 使用HTTP Method Head

func (d *FileDownloader) head() (int, error) {

r, err := d.getNewRequest("HEAD")

if err != nil {

  return 0, err

}

resp, err := http.DefaultClient.Do(r)

if err != nil {

  return 0, err

}

if resp.StatusCode > 299 {

  return 0, errors.New(fmt.Sprintf("Can't process, response is %v", resp.StatusCode))

}

//检查是否支持 断点续传

//https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Ranges

if resp.Header.Get("Accept-Ranges") != "bytes" {

  return 0, errors.New("服务器不支持文件断点续传")

}

d.outputFileName = parseFileInfoFrom(resp)

//https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Length

return strconv.Atoi(resp.Header.Get("Content-Length"))

}

//Run 开始下载任务

func (d *FileDownloader) Run() error {

fileTotalSize, err := d.head()

if err != nil {

  return err

}

d.fileSize = fileTotalSize

jobs := make([]filePart, d.totalPart)

eachSize := fileTotalSize / d.totalPart

for i := range jobs {

  jobs[i].Index = i

  if i == 0 {

  jobs[i].From = 0

  } else {

  jobs[i].From = jobs[i-1].To + 1

  }

  if i < d.totalPart-1 {

  jobs[i].To = jobs[i].From + eachSize

  } else {

  //the last filePart

  jobs[i].To = fileTotalSize - 1

  }

}

var wg sync.WaitGroup

for _, j := range jobs {

  wg.Add(1)

  go func(job filePart) {

  defer wg.Done()

  err := d.downloadPart(job)

  if err != nil {

    log.Println("下载文件失败:", err, job)

  }

  }(j)

}

wg.Wait()

return d.mergeFileParts()

}

//下载分片

func (d FileDownloader) downloadPart(c filePart) error {

r, err := d.getNewRequest("GET")

if err != nil {

  return err

}

log.Printf("开始[%d]下载from:%d to:%d\n", c.Index, c.From, c.To)

r.Header.Set("Range", fmt.Sprintf("bytes=%v-%v", c.From, c.To))

resp, err := http.DefaultClient.Do(r)

if err != nil {

  return err

}

if resp.StatusCode > 299 {

  return errors.New(fmt.Sprintf("服务器错误状态码: %v", resp.StatusCode))

}

defer resp.Body.Close()

bs, err := ioutil.ReadAll(resp.Body)

if err != nil {

  return err

}

if len(bs) != (c.To - c.From + 1) {

  return errors.New("下载文件分片长度错误")

}

c.Data = bs

d.doneFilePart[c.Index] = c

return nil

}

// getNewRequest 创建一个request

func (d FileDownloader) getNewRequest(method string) (*http.Request, error) {

r, err := http.NewRequest(

  method,

  d.url,

  nil,

)

if err != nil {

  return nil, err

}

r.Header.Set("User-Agent", "mojocn")

return r, nil

}

//mergeFileParts 合并下载的文件

func (d FileDownloader) mergeFileParts() error {

log.Println("开始合并文件")

path := filepath.Join(d.outputDir, d.outputFileName)

mergedFile, err := os.Create(path)

if err != nil {

  return err

}

defer mergedFile.Close()

hash := sha256.New()

totalSize := 0

for _, s := range d.doneFilePart {

  mergedFile.Write(s.Data)

  hash.Write(s.Data)

  totalSize += len(s.Data)

}

if totalSize != d.fileSize {

  return errors.New("文件不完整")

}

//https://download.jetbrains.com/go/goland-2020.2.2.dmg.sha256?_ga=2.223142619.1968990594.1597453229-1195436307.1493100134

if hex.EncodeToString(hash.Sum(nil)) != "3af4660ef22f805008e6773ac25f9edbc17c2014af18019b7374afbed63d4744" {

  return errors.New("文件损坏")

} else {

  log.Println("文件SHA-256校验成功")

}

return nil

}

```

输出结果:

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/4a537e610d66dddd41ab758b56da0e0d?showdoc=.jpg)

------------

## 6. logrus 日志使用教程

logrus是目前 Github 上 star 数量最多的日志库,目前(2018.12,下同)star 数量为 8119,fork 数为 1031. logrus功能强大,性能高效,而且具有高度灵活性,提供了自定义插件的功能.很多开源项目,如docker,prometheus,dejavuzhou/ginbro[1]等,都是用了 logrus 来记录其日志.

golang标准库的日志框架非常简单,仅仅提供了print,panic和fatal三个函数对于更精细的日志级别、日志文件分割以及日志分发等方面并没有提供支持. 所以催生了很多第三方的日志库,但是在 golang 的世界里,没有一个日志库像 slf4j 那样在 Java 中具有绝对统治地位.golang 中,流行的日志框架包括 logrus、zap、zerolog、seelog

zap 是 Uber 推出的一个快速、结构化的分级日志库.具有强大的 ad-hoc 分析功能,并且具有灵活的仪表盘.zap 目前在 GitHub 上的 star 数量约为 4.3k. seelog 提供了灵活的异步调度、格式化和过滤功能.目前在 GitHub 上也有约 1.1k.

完全兼容 golang 标准库日志模块:logrus 拥有六种日志级别:debug、info、warn、error、fatal 和 panic,这是 golang 标准库日志模块的 API 的超集.如果您的项目使用标准库日志模块,完全可以以最低的代价迁移到 logrus 上。

- logrus.Debug("Useful debugging information.")

- logrus.Info("Something noteworthy happened!")

- logrus.Warn("You should probably take a look at this.")

- logrus.Error("Something failed but I'm not quitting.")

- logrus.Fatal("Bye.") // log 之后会调用 os.Exit(1)

- logrus.Panic("I'm bailing.") // log 之后会 panic()

可扩展的 Hook 机制:允许使用者通过 hook 的方式将日志分发到任意地方,如本地文件系统、标准输出、logstash、elasticsearch或者mq等,或者通过 hook 定义日志内容和格式等.

可选的日志输出格式:logrus 内置了两种日志格式,JSONFormatter和TextFormatter,如果这两个格式不满足需求,可以自己动手实现接口 Formatter,来定义自己的日志格式.

Field机制:logrus鼓励通过 Field 机制进行精细化的、结构化的日志记录,而不是通过冗长的消息来记录日志.

logrus是一个可插拔的、结构化的日志框架.

Entry: logrus.WithFields 会自动返回一个 *Entry,Entry 里面的有些变量会被自动加上

-  time:entry被创建时的时间戳

- msg:在调用.Info()等方法时被添加

- level

使用方法:

go get github.com/sirupsen/logrus

go get golang.org/x/sys/windows和golang.org/x/sys/internal/unsafeheader(使用go get 导入异常,因为这个包已经不存在了,golang.org/x/sys/=https://github.com/golang/sys)

https://github.com/golang/sys 下载源码,解压文件,复制windows和internal目录粘贴到golang.org/x/sys/目录下(也可以使用 go get go get  -u github.com/golang/sys/tree/master/windows ,然后把github.com/golang/sys/windows和internal目录复制到 golang.org/x/sys/)

go get github.com/zbindenren/logrus_mail

go get github.com/sirupsen/logrus

go get github.com/johntdyer/slackrus

go get github.com/bluele/logrus_slack

go get github.com/lestrrat-go/file-rotatelogs

go get github.com/spf13/viper

### 6.1 基本用法

```

package main

import (

log "github.com/sirupsen/logrus"

)

//1.基本用法

//logrus与 golang 标准库日志模块完全兼容,因此您可以使用log“github.com/sirupsen/logrus”替换所有日志导入. logrus可以通过简单的配置,来定义输出、格式或者日志级别等.

func main() {

log.WithFields(log.Fields{

"animal": "walrus",

}).Info("A walrus appears")

}

```

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/5b97b89ddca8daa5fc666a836812d11f?showdoc=.jpg)

### 6.2 简单定义格式

```

package main

import (

log "github.com/sirupsen/logrus"

"os"

)

func init() {

// 设置日志格式为json格式

log.SetFormatter(&log.JSONFormatter{})

// 设置将日志输出到标准输出(默认的输出为stderr,标准错误)

// 日志消息输出可以是任意的io.writer类型

log.SetOutput(os.Stdout)

// 设置日志级别为warn以上

log.SetLevel(log.WarnLevel)

}

//logrus与 golang 标准库日志模块完全兼容,因此您可以使用log“github.com/sirupsen/logrus”替换所有日志导入. logrus可以通过简单的配置,来定义输出、格式或者日志级别等.

func main() {

log.WithFields(log.Fields{

"animal": "walrus",

"size":  10,

}).Info("A group of walrus emerges from the ocean")

log.WithFields(log.Fields{

"omg":    true,

"number": 122,

}).Warn("The group's number increased tremendously!")

log.WithFields(log.Fields{

"omg":    true,

"number": 100,

}).Fatal("The ice breaks!")

}

```

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/b32ee8fec55753662d1c8b7129ac80a6?showdoc=.jpg)

### 6.3 输入json格式和设置行号

```

package main

import (

"fmt"

"github.com/sirupsen/logrus"

"os"

)

func init() {

fmt.Println("gggg")

}

// logrus提供了New()函数来创建一个logrus的实例.

// 项目中,可以创建任意数量的logrus实例.

var log = logrus.New()

//如果想在一个应用里面向多个地方log,可以创建 Logger 实例. logger是一种相对高级的用法, 对于一个大型项目, 往往需要一个全局的logrus实例,即logger对象来记录项目所有的日志.如

func main() {

// 为当前logrus实例设置消息的输出,同样地,

// 可以设置logrus实例的输出到任意io.writer

log.Out = os.Stdout

log.SetReportCaller(true) //设置行号

// 为当前logrus实例设置消息输出格式为json格式.

// 同样地,也可以单独为某个logrus实例设置日志级别和hook,这里不详细叙述.

log.Formatter = &logrus.JSONFormatter{}

log.WithFields(logrus.Fields{

"animal": "walrus",

"size":  10,

"go":"dd",

}).Info("A group of walrus emerges from the ocean")

}

```

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/483f34aaf87b87645386e038f848567e?showdoc=.jpg)

### 6.4 自定义 Logger

如果想在一个应用里面向多个地方log,可以创建 Logger 实例. logger是一种相对高级的用法, 对于一个大型项目, 往往需要一个全局的logrus实例,即logger对象来记录项目所有的日志

<center>logrus4-main.go</center>

```

package main

import (

log "github.com/sirupsen/logrus"

"os"

iy "test/runtime/PProf/logrus/redire"

)

//2.自定义 Logger

//如果想在一个应用里面向多个地方log,可以创建 Logger 实例. logger是一种相对高级的用法, 对于一个大型项目, 往往需要一个全局的logrus实例,即logger对象来记录项目所有的日志.如:

func main() {

log.SetOutput(os.Stdout)

iy.InitLog("./log/", "test", "gb18030")

log.WithFields(log.Fields{"animal": "walrus","goo":1}).Info("A walrus appears")

log.Info("测试中文1")

}

```

<center>logrus4.go</center>

```

package redire

import (

"bytes"

"errors"

"fmt"

"os"

"path/filepath"

"runtime"

"strconv"

"strings"

//"test/runtime/PProf/logrus"

"time"

log "github.com/sirupsen/logrus"

)

//日志自定义格式

type LogFormatter struct{}

//格式详情

func (s *LogFormatter) Format(entry *log.Entry) ([]byte, error) {

timestamp := time.Now().Local().Format("0102-150405.000")

var file string

var len int

if entry.Caller != nil {

file = filepath.Base(entry.Caller.File)

len = entry.Caller.Line

}

//fmt.Println(entry.Data)

msg := fmt.Sprintf("%s [%s:%d][GOID:%d][%s] %s\n", timestamp, file, len, getGID(), strings.ToUpper(entry.Level.String()), entry.Message)

return []byte(msg), nil

}

func getGID() uint64 {

b := make([]byte, 64)

b = b[:runtime.Stack(b, false)]

b = bytes.TrimPrefix(b, []byte("goroutine "))

b = b[:bytes.IndexByte(b, ' ')]

n, _ := strconv.ParseUint(string(b), 10, 64)

return n

}

type logFileWriter struct {

file    *os.File

logPath  string

fileDate string //判断日期切换目录

appName  string

encoding string

}

func (p *logFileWriter) Write(data []byte) (n int, err error) {

if p == nil {

return 0, errors.New("logFileWriter is nil")

}

if p.file == nil {

return 0, errors.New("file not opened")

}

//判断是否需要切换日期

fileDate := time.Now().Format("20060102")

//fmt.Println("f:",p.fileDate,'=',fileDate)

if p.fileDate != fileDate {

p.file.Close()

fmt.Println("oo:",os.ModePerm)

err = os.MkdirAll(fmt.Sprintf("%s/%s", p.logPath, fileDate), os.ModePerm)

if err != nil {

return 0, err

}

filename := fmt.Sprintf("%s/%s/%s-%s.log", p.logPath, fileDate, p.appName, fileDate)

fmt.Println("po:", p.logPath,"=",p.fileDate,"-",filename)

//p.file, err = os.OpenFile(filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE|os.O_SYNC, 0600)

if err != nil {

return 0, err

}

}

if p.encoding != "" {

timestamp := time.Now().Local().Format("2006/01/02 15:04:05")

dataToEncode := ConvertStringToByte(timestamp,string(data))

//fmt.Println("loog:","=",string(data),"=")

n, e := p.file.Write(dataToEncode)

return n, e

}

n, e := p.file.Write(data)

return n,e

//timestamp := time.Now().Local().Format("2006/01/02 15:04:05")

//fmt.Println("gg:",timestamp,"=",p.appName,"=",fileDate)

//msg := fmt.Sprintf("%s [%s] %s\n", timestamp, strings.ToUpper(p.appName), fileDate)

//var err2 error

//return []byte(msg), err2

}

func ConvertStringToByte(str1,str2 string) []byte{

//str2=str2\

str3 := ""

arr_str2 := strings.Split(str2," ")

for index,value:=range arr_str2{

if(index!=0){

if(index>=2){

str3+=value+" "

} else{

str3+=value

}

}

//fmt.Println("ind:",index," ",value)

}

//str2=arr_str2[1]+" "+arr_str2[2]

//fmt.Println("arr_str2:", len(arr_str2),"=",arr_str2)

b := []byte(str1+" "+str3)

fmt.Println("str1:",str1," str3: ",str3)

return b

}

//初始化日志

func InitLog(logPath string, appName string, encoding string) {

fileDate := time.Now().Format("20060102")

//创建目录

err := os.MkdirAll(fmt.Sprintf("%s/%s", logPath, fileDate), os.ModePerm)

if err != nil {

log.Error(err)

return

}

filename := fmt.Sprintf("%s/%s/%s-%s.log", logPath, fileDate, appName, fileDate)

file, err := os.OpenFile(filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE|os.O_SYNC, 0600)

if err != nil {

log.Error(err)

return

}

//fmt.Println("filename:",filename,"=",logPath,"=",fileDate,"=",encoding)

fileWriter := logFileWriter{file, logPath, fileDate, appName, encoding}

log.SetOutput(&fileWriter)

//设置行号

log.SetReportCaller(true)

//自定义 Logger

log.SetFormatter(new(LogFormatter))

}

```

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/7b6e3edf3b8efd0d33062f26da4ac98f?showdoc=.jpg)

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/231d698fbebdbcc4a98b4be23a44e596?showdoc=.jpg)

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/54f5a4894fdd2e7c7021a9175fdb6038?showdoc=.jpg)

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/48a906d81c36ba0f14e33c16fba31a7c?showdoc=.jpg)

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/de24b74c1344cbbdfff78d76407fbbe1?showdoc=.jpg)

### 6.5 Hook 接口用法 logrus每次在执行输出之前都会调用这个hook方法

```

package main

import (

"fmt"

log6 "github.com/sirupsen/logrus"

)

type AppHook struct {

AppName string

}

func (h *AppHook) Levels() []log6.Level {

return log6.AllLevels

}

func (h *AppHook) Fire(entry *log6.Entry) error {

entry.Data["app"] = h.AppName

fmt.Println("app:",entry.Message)

return nil

}

//还可以为logrus设置钩子,每条日志输出前都会执行钩子的特定方法。所以,我们可以添加输出字段、根据级别将日志输出到不同的目的地。

// logrus也内置了一个syslog的钩子,将日志输出到syslog中。这里我们实现一个钩子,在输出的日志中增加一个app=awesome-web字段。

func main() {

h := &AppHook{AppName: "awesome-web"}

log6.AddHook(h)

log6.Error("fff")

log6.Info("info msg")

}

```

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/dbdd79042198d7655ebdb314710b2202?showdoc=.jpg)

### 6.6 原生方法实现日志管理

```

package main

import (

"io"

"log"

"os"

)

var (

Info    *log.Logger

Warning *log.Logger

Error  *log.Logger

)

func init() {

errFile, err := os.OpenFile("errors.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)

if err != nil {

log.Fatalln("打开日志文件失败:", err)

}

Info = log.New(os.Stdout, "Info:", log.Ldate|log.Ltime|log.Lshortfile)

Warning = log.New(os.Stdout, "Warning:", log.Ldate|log.Ltime|log.Lshortfile)

Error = log.New(io.MultiWriter(os.Stderr, errFile), "Error:", log.Ldate|log.Ltime|log.Lshortfile)

}

func main() {

Info.Println("Info log...")

Warning.Printf("Warning log...")

Error.Println("Error log...")

}

```

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/83df724434425dbff9dfee93d9176bc0?showdoc=.jpg)

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/a89f5a7ea14ff6e01b5ab6ed9d63b990?showdoc=.jpg)

### 6.7 日志分割,把日志变小

最好是在管理员权限执行该文件,windows就打开管理员cmd,进入目录,go run Logrus-Hook.go

每隔 1 分钟轮转一个新文件,保留最近 3 分钟的日志文件,多余的自动清理掉

```

package main

import (

rotatelogs "github.com/lestrrat-go/file-rotatelogs"

log9 "github.com/sirupsen/logrus"

"time"

)

func init() {

/* 日志轮转相关函数

`WithLinkName` 为最新的日志建立软连接

`WithRotationTime` 设置日志分割的时间,隔多久分割一次

WithMaxAge 和 WithRotationCount二者只能设置一个

  `WithMaxAge` 设置文件清理前的最长保存时间

  `WithRotationCount` 设置文件清理前最多保存的个数

*/

// 下面配置日志每隔 1 分钟轮转一个新文件,保留最近 3 分钟的日志文件,多余的自动清理掉。

path := "D:\\study\\gohome\\logs.log"

writer, _ := rotatelogs.New(

path + ".%Y%m%d.log",

// 生成软链,指向最新日志文件

rotatelogs.WithLinkName(path),

// 设置最大保存时间

rotatelogs.WithMaxAge(time.Duration(180)*time.Second),

// 设置日志切割时间间隔

rotatelogs.WithRotationTime(time.Duration(60)*time.Second),

)

log9.SetOutput(writer)

log9.SetFormatter(&log9.JSONFormatter{})

}

// 日志分隔 把日志变小,变成多个日志文件

//,每隔 1 分钟轮转一个新文件,保留最近 3 分钟的日志文件,多余的自动清理掉

func main() {

for {

log9.Info("hello, world!")

time.Sleep(5 * time.Second)

}

}

```

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/cc2d7faa82a77e86a91f71c6e1d5e888?showdoc=.jpg)

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/ef01802dbd2f0b6a2d8ca5e39fc4c46b?showdoc=.jpg)

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/d032d745e80f23950613903f859b87ec?showdoc=.jpg)

### 6.8 hook 只调用一次

<center>logrus-hook-main.go</center>

```

package main

import (

uuid "github.com/satori/go.uuid"

log5 "github.com/sirupsen/logrus"

"test/runtime/PProf/logrus/hook"

)

func initLog() {

uuids, _ := uuid.NewV4()

//fmt.Println("dddd",uuids)

log5.AddHook(hook.NewTraceIdHook(uuids.String() +" "))

}

func main() {

initLog()

log5.WithFields(log5.Fields{

"age": 12,

"name":  "xiaoming",

"sex": 1,

}).Info("小明来了")

log5.WithFields(log5.Fields{

"age": 13,

"name":  "xiaohong",

"sex": 0,

}).Error("小红来了")

log5.WithFields(log5.Fields{

"age": 14,

"name":  "xiaofang",

"sex": 1,

}).Fatal("小芳来了")

}

```

<center>hood.go</center>

```

package hook

import (

"fmt"

"github.com/sirupsen/logrus"

)

type TraceIdHook struct {

TraceId  string

}

func NewTraceIdHook(traceId string) logrus.Hook {

hook := TraceIdHook{

TraceId:  traceId,

}

fmt.Println("ff:",traceId)

return &hook

}

func (hook *TraceIdHook) Fire(entry *logrus.Entry) error {

entry.Data["traceId"] = hook.TraceId

return nil

}

func (hook *TraceIdHook) Levels() []logrus.Level {

return logrus.AllLevels

}

```

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/03658466c6d1101a57b690c99095bd14?showdoc=.jpg)

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/40a1d57696bd5316ca5e67635f43acbf?showdoc=.jpg)

### 6.9 使用hook实现邮件警醒 邮件警报hook

```

package main

import (

"github.com/sirupsen/logrus"

"github.com/zbindenren/logrus_mail"

//"fmt"

"time"

)

//使用hook实现邮件警醒 邮件警报hook

func main() {

//from2 :="<766496095@qq.com>",

logger := logrus.New()

logrus.SetReportCaller(true)

logger.SetReportCaller(true) //设置行号

hook, err := logrus_mail.NewMailAuthHook(

"滨州服务器",

"smtp.qq.com",

25,

"766496095@qq.com",

"304311271@qq.com",

"766496095@qq.com",

"aycpqeqjjiykbffg",

)

if err == nil {

logger.Hooks.Add(hook)

}

//生成*Entry

var filename = "123.txt"

contextLogger := logger.WithFields(logrus.Fields{

"file":    filename,

"content": "GG",

})

//设置时间戳和message

contextLogger.Time = time.Now()

contextLogger.Message = "这是一个hook发来的邮件"

//只能发送Error,Fatal,Panic级别的log

contextLogger.Level = logrus.ErrorLevel

//contextLogger2 := logrus.Fields{

// "good":    "em",

// "ee": "GG",

//}

//

//contextLogger.Data=contextLogger2

//使用Fire发送,包含时间戳,message

hook.Fire(contextLogger)

}

```

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/75ab671869796328c2eaf614fa044dc0?showdoc=.jpg)

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/ee168ed297a86f28d6ee4629fd499a30?showdoc=.jpg)

## 7.从零开始构建全文搜索引擎(译)

参考网址:

https://cloud.tencent.com/developer/article/1682841

https://github.com/akrylysov/simplefts

** https://mojotv.cn/404#Go%E8%BF%9B%E9%98%B6

https://hanyajun.com/golang/go_article_2019/

1. 打开https://github.com/akrylysov/simplefts 这个网站,把代码下载下来,解压到路径下

2.打开 https://dumps.wikimedia.org/enwiki/latest/ ,下载文件enwiki-latest-abstract1.xml.gz,把文件解压到gopath路径,

3. 运行代码的main.go,没有报错,说找不到文件进行,

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/d7799eba440f0887862d71637d3be71d?showdoc=.jpg)

## 8.go trace 剖析  1.4占式调度

参考地址:

https://mp.weixin.qq.com/s/iXkbF018fxgTWtMqxZfo6g

https://mp.weixin.qq.com/s/Wp15aOLeYhZYla275TzISw(比较1.13和1.14的区别)

<center>trace2.go</center>

```

package main

import (

"fmt"

"os"

"runtime/trace"

)

func main() {

fmt.Println("fff")

trace.Start(os.Stderr)

defer trace.Stop()

ch:= make(chan string)

go func(){

ch<- "EDDYCJY"

}()

<-ch

}

```

1.  go run trace2.go 2> trace.out //就会看见一个trace.out

2.  在命令行执行:go tool trace trace.out,

3.  在浏览器打开 http://localhost:http://localhost:12413/

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/2137249d0c7f6e8e5a4d2e0b90efc564?showdoc=.jpg)

名词解释:

- View trace:查看跟踪(这个是今天要使用的重点),能看到一段时间内 goroutine 的调度执行情况,包括事件触发链;

- Goroutine analysis:Goroutine 分析,能看到这段时间所有 goroutine 执行的一个情况,执行堆栈,执行时间;

- Network blocking profile:网络阻塞概况(分析网络的一些消耗)

- Synchronization blocking profile:同步阻塞概况(分析同步锁的一些情况)

- Syscall blocking profile:系统调用阻塞概况(分析系统调用的消耗)

- Scheduler latency profile:调度延迟概况(函数的延迟占比)

- User defined tasks:自定义任务

- User defined regions:自定义区域

- Minimum mutator utilization:最低 Mutator 利用率

### 8.1 Scheduler latency profile

在刚开始时,我们一般先查看 “Scheduler latency profile”,我们能通过 Graph 看到整体的调用开销情况,如下:

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/403dc98534e09d25c6f4caa4166377da?showdoc=.jpg)

因为演示程序比较简单,因此这里就两块,一个是 trace 本身,另外一个是 channel 的收发。

### 8.2 Goroutine analysis

第二步看 “Goroutine analysis”,我们能通过这个功能看到整个运行过程中,每个函数块有多少个有 Goroutine 在跑,并且观察每个的 Goroutine 的运行开销都花费在哪个阶段。如下:

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/c0a67b82e50f1e5a62a6545cd9747201?showdoc=.jpg)

通过上图我们可以看到共有 3 个 goroutine,分别是 runtime.main、 runtime/trace.Start.func1、 main.main.func1,那么它都做了些什么事呢,接下来我们可以通过点击具体细项去观察。如下:

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/cae093887e351262a1883733d007a64b?showdoc=.jpg)

同时也可以看到当前 Goroutine 在整个调用耗时中的占比,以及 GC 清扫和 GC 暂停等待的一些开销。如果你觉得还不够,可以把图表下载下来分析,相当于把整个 Goroutine 运行时掰开来看了,这块能够很好的帮助我们对 Goroutine 运行阶段做一个的剖析,可以得知到底慢哪,然后再决定下一步的排查方向。如下:

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/5a35c4c2d8bc29f8eb725172d4712acf?showdoc=.jpg)

### 8.3 View trace

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/2673e3d3e04a11b2c9f28b3f82918c97?showdoc=.jpg)

1. 时间线:显示执行的时间单元,根据时间维度的不同可以调整区间,具体可执行 shift + ? 查看帮助手册。

2. 堆:显示执行期间的内存分配和释放情况。

3. 协程:显示在执行期间的每个 Goroutine 运行阶段有多少个协程在运行,其包含 GC 等待(GCWaiting)、可运行(Runnable)、运行中(Running)这三种状态。

4. OS 线程:显示在执行期间有多少个线程在运行,其包含正在调用 Syscall(InSyscall)、运行中(Running)这两种状态。

5. 虚拟处理器:每个虚拟处理器显示一行,虚拟处理器的数量一般默认为系统内核数。

6. 协程和事件:显示在每个虚拟处理器上有什么 Goroutine 正在运行,而连线行为代表事件关联。

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/dc8331e5038ab50d409ed8f385ad4ca6?showdoc=.jpg)

点击具体的 Goroutine 行为后可以看到其相关联的详细信息,这块很简单,大家实际操作一下就懂了。文字解释如下:

- Start:开始时间

- Wall Duration:持续时间

- Self Time:执行时间

- Start Stack Trace:开始时的堆栈信息

- End Stack Trace:结束时的堆栈信息

- Incoming flow:输入流

- Outgoing flow:输出流

- Preceding events:之前的事件

- Following events:之后的事件

- All connected:所有连接的事件

View Events

我们可以通过点击 View Options-Flow events、Following events 等方式,查看我们应用运行中的事件流情况。如下:

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/089e3855ab9cc015f38fd057357f32b9?showdoc=.jpg)

通过分析图上的事件流,我们可得知这程序从 G1 runtime.main 开始运行,在运行时创建了 2 个 Goroutine,先是创建 G18 runtime/trace.Start.func1,然后再是 G19 main.main.func1 。而同时我们可以通过其 Goroutine Name 去了解它的调用类型,如:runtime/trace.Start.func1 就是程序中在 main.main 调用了 runtime/trace.Start 方法,然后该方法又利用协程创建了一个闭包 func1 去进行调用。

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/fe003673e118d92f14e933a8d0c36075?showdoc=.jpg)

结合实战

今天生产环境突然出现了问题,机智的你早已埋好 _"net/http/pprof" 这个神奇的工具,你麻利的执行了如下命令:

curl http://127.0.0.1:6060/debug/pprof/trace\?seconds\=20 > trace.out

go tool trace trace.out

## 9.Go语言自定义自己的SSH-Server

go get golang.org/x/crypto/ssh

go get  golang.org/x/crypto/ed25519

go get golang.org/x/crypto/poly1305

go get golang.org/x/crypto/chacha20

go get golang.org/x/crypto/curve25519

go get golang.org/x/crypto/internal/subtle

go get golang.org/x/crypto/blowfish

https://github.com/golang/crypto  下载这个压缩包,解压,放到制定位置

## 10.cavaliercoder/grab 支持断点续传

go get github.com/cavaliercoder/grab

Grab是一个Go软件包,用于从Internet下载具有以下rad功能的文件:

- 同时监控下载进度

- 自动恢复不完整的下载

- 从内容标题或URL路径猜测文件名

- 使用context.Context安全地取消下载

- 使用校验和验证下载

- 同时下载大量文件

- 应用速率限制器

### 10.1 简单例子

```

package main

import (

"fmt"

"github.com/cavaliercoder/grab"

"log"

)

func main() {

path := `H:/BaiduNetdiskDownload/Java性能调优(10-10)`

resp, err := grab.Get("src/uiprogress/week5", path)

if err != nil {

log.Fatal(err)

}

fmt.Println("Download saved to", resp.Filename)

}

```

### 10.2 第二次运行该示例时,它将自动恢复以前的下载并更快地退出

```

package main

import (

"fmt"

"os"

"time"

"github.com/cavaliercoder/grab"

)

//第二次运行该示例时,它将自动恢复以前的下载并更快地退出。

func main() {

// create client

client := grab.NewClient()

req, _ := grab.NewRequest(".", "http://www.golang-book.com/public/pdf/gobook.pdf")

// start download

fmt.Printf("Downloading %v...\n", req.URL())

resp := client.Do(req)

fmt.Printf("  %v\n", resp.HTTPResponse.Status)

// start UI loop

t := time.NewTicker(500 * time.Millisecond)

defer t.Stop()

Loop:

for {

select {

case <-t.C:

fmt.Printf("  transferred %v / %v bytes (%.2f%%)\n",

resp.BytesComplete(),

resp.Size(),

100*resp.Progress())

case <-resp.Done:

// download is complete

break Loop

}

}

// check for errors

if err := resp.Err(); err != nil {

fmt.Fprintf(os.Stderr, "Download failed: %v\n", err)

os.Exit(1)

}

fmt.Printf("Download saved to ./%v \n", resp.Filename)

// Output:

// Downloading http://www.golang-book.com/public/pdf/gobook.pdf...

//  200 OK

//  transferred 42970 / 2893557 bytes (1.49%)

//  transferred 1207474 / 2893557 bytes (41.73%)

//  transferred 2758210 / 2893557 bytes (95.32%)

// Download saved to ./gobook.pdf

}

```

### 10.3 支持多个文件同时下载

```

package main

import (

"fmt"

"github.com/cavaliercoder/grab"

"io/ioutil"

"os"

"time"

)

func download(dst string,urls ...string)  {

n:=len(urls)

re,err:=grab.GetBatch(n,dst,urls...)

if err!=nil {

fmt.Print(err)

return

}

t:=time.NewTicker(time.Millisecond*10)

complete:=0

progress:=0

responses:=make([]*grab.Response,0)

for complete<n {

select {

case r:=<-re:

responses=append(responses,r)

case <-t.C:

progress=0

for k,v:=range responses{

if v!=nil {

//var test1  = (v.Size)

//var test2  =decimal.NewFromInt(test1)

//fmt.Printf("v:%v %T %v \n",test2,test2,k)

//return;

if v.IsComplete() {

if v.Err()==nil {

fmt.Printf("%s %s/%s %d%%\n",v.Filename,ShowBytes(float64(v.BytesComplete())),ShowBytes2(v.Size()),int(v.Progress()*100))

} else {

fmt.Printf("%s:%s\n",v.Filename,v.Err())

}

responses[k]=nil

complete++

} else {

fmt.Printf("%s %s/%s %d%%\n",v.Filename,ShowBytes(float64(v.BytesComplete())),ShowBytes2(v.Size()),int(v.Progress()*100))

progress++

}

}

}

if progress>0 {

fmt.Printf("%d downloading\n",progress)

}

}

}

t.Stop()

}

func ShowBytes(test2 float64) string {

//var test3 int64 = test2

//var test4  float64 =decimal.NewFromInt(test3).Float64()

//fmt.Printf("te: %v  %T\n",test4)

if test2<1024 {

return fmt.Sprintf("%.2fB",test2)

} else if test2<1024*1024 {

return fmt.Sprintf("%.2fKB",test2/1024.0)

} else if test2<1024*1024*1024 {

return fmt.Sprintf("%.2fMB",test2/1024.0/1024.0)

} else {

return fmt.Sprintf("%.2fGB",test2/1024.0/1024.0/1024.0)

}

}

func ShowBytes2(test2 int64) string {

//var test3 int64 = test2

//var test4  float64 =decimal.NewFromInt(test3).Float64()

//fmt.Printf("te: %v  %T\n",test4)

if test2<1024 {

return fmt.Sprintf("%.2fB",test2)

} else if test2<1024*1024 {

return fmt.Sprintf("%.2fKB",test2/1024.0)

} else if test2<1024*1024*1024 {

return fmt.Sprintf("%.2fMB",test2/1024.0/1024.0)

} else {

return fmt.Sprintf("%.2fGB",test2/1024.0/1024.0/1024.0)

}

}

func main()  {

urls:=[]string{

"https://rpic.douyucdn.cn/live-cover/appCovers/2018/08/31/3279944_20180831104533_small.jpg",

"https://rpic.douyucdn.cn/live-cover/roomCover/2018/11/06/0a699f47dc4fc55deaa82402cc0876ea_big.png",

"https://rpic.douyucdn.cn/live-cover/appCovers/2018/10/19/5230163_20181019161115_small.jpg",

}

//xfiles, _ := GetAllFiles2(`H:/BaiduNetdiskDownload/Java性能调优/01-开篇词 (1讲)`)

//urls =append(urls,xfiles...)

fmt.Println("shu:",urls)

download("src/uiprogress/week5",urls...)

}

```

## 11. 获取目录所有文件

```

package main

import (

"fmt"

"io/ioutil"

"os"

"strings"

)

//获取指定目录下的所有文件和目录

func GetFilesAndDirs(dirPth string) (files []string, dirs []string, err error) {

dir, err := ioutil.ReadDir(dirPth)

if err != nil {

return nil, nil, err

}

PthSep := string(os.PathSeparator)

//suffix = strings.ToUpper(suffix) //忽略后缀匹配的大小写

for _, fi := range dir {

if fi.IsDir() { // 目录, 递归遍历

dirs = append(dirs, dirPth+PthSep+fi.Name())

GetFilesAndDirs(dirPth + PthSep + fi.Name())

} else {

// 过滤指定格式

ok := strings.HasSuffix(fi.Name(), ".go")

if ok {

files = append(files, dirPth+PthSep+fi.Name())

}

}

}

return files, dirs, nil

}

//获取指定目录下的所有文件,包含子目录下的文件

func GetAllFiles(dirPth string) (files []string, err error) {

var dirs []string

dir, err := ioutil.ReadDir(dirPth)

if err != nil {

return nil, err

}

PthSep := string(os.PathSeparator)

//suffix = strings.ToUpper(suffix) //忽略后缀匹配的大小写

for _, fi := range dir {

if fi.IsDir() { // 目录, 递归遍历

dirs = append(dirs, dirPth+PthSep+fi.Name())

GetAllFiles(dirPth + PthSep + fi.Name())

} else {

// 过滤指定格式

//ok := strings.HasSuffix(fi.Name(), ".go")

//if ok {

// files = append(files, dirPth+PthSep+fi.Name())

//}

files = append(files, dirPth+PthSep+fi.Name())

}

}

// 读取子目录下文件

for _, table := range dirs {

temp, _ := GetAllFiles(table)

for _, temp1 := range temp {

files = append(files, temp1)

}

}

return files, nil

}

func main() {

path := `H:/BaiduNetdiskDownload/Java性能调优`

files, dirs, _ := GetFilesAndDirs(path)

for _, dir := range dirs {

fmt.Printf("获取的文件夹为[%s]\n", dir)

}

for _, table := range dirs {

temp, _, _ := GetFilesAndDirs(table)

for _, temp1 := range temp {

files = append(files, temp1)

}

}

for _, table1 := range files {

fmt.Printf("获取的文件为[%s]\n", table1)

}

fmt.Printf("=======================================\n")

xfiles, _ := GetAllFiles(`H:\\BaiduNetdiskDownload\\Java性能调优\\01-开篇词 (1讲)`)

for _, file := range xfiles {

fmt.Printf("获取的文件为[%s]\n", file)

}

}

```

![](https://www.showdoc.com.cn/server/api/attachment/visitfile/sign/7988228f70c9a5e461b0570c34b9f0ce?showdoc=.jpg)


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:杨言锡

查看原文:go 杂谈一

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1948 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传