├── codes
├── init_trace
│ ├── go.mod
│ ├── trace
│ │ └── trace.go
│ ├── pkg2
│ │ └── pkg2.go
│ ├── pkg1
│ │ └── pkg1.go
│ └── main.go
├── proconsumer
│ ├── go.mod
│ ├── go.sum
│ ├── consumer.go
│ ├── producer.go
│ ├── task_scheduler.go
│ └── tests
│ │ ├── testjob.go
│ │ └── file.txt
├── practices
│ └── crosscompile
│ │ ├── rewrite_panics_windows.go
│ │ └── rewrite_panics_unix.go
├── tag_picker
│ ├── piker.go
│ ├── order_num.go
│ └── query_builer.go
├── slice_util
│ └── code.go
├── distributed_lock
│ ├── main.go
│ ├── go.mod
│ ├── etcd
│ │ └── etcd_lock.go
│ └── go.sum
├── unsafe
│ ├── swtich_pointer_type.go
│ ├── access_unexported_field.go
│ ├── slice_switch_string_zero_copy.go
│ └── use_sizeof.go
├── order_util
│ └── gen_order_no.go
├── json
│ └── encoder
│ │ └── noescapehtml.go
├── ctx-usage-demo
│ ├── http-timeout-demo.go
│ ├── demo1.go
│ └── demo2.go
├── atomic
│ ├── value_demo.go
│ └── add_load_demo.go
├── context_demo
│ ├── ctx_listen_http_client_cancel.go
│ ├── no_ctx_demo.go
│ └── ctx_demo.go
├── slice
│ ├── remove_duplicated_elements.go
│ └── efficient_append.go
├── latency-codes
│ └── req-body-not-close
│ │ └── code.go
├── flag_library
│ ├── boolean
│ │ └── boolean.go
│ ├── head_command
│ │ └── head.go
│ └── subcommand
│ │ └── subcommand.go
├── gbkdetect
│ └── code.go
├── copy_properties
│ ├── copier_demo.go
│ └── copy_with_option.go
├── synccond
│ └── learn_sync_cond.go
├── singleflight
│ └── anti_cache_break.go
├── password_complexity
│ └── code.go
├── random_util
│ └── code.go
├── password
│ └── password.go
├── errorgroup
│ └── errorgroup.go
├── mask_util
│ └── code.go
├── reqeust_sign_generate
│ └── request_sign.go
├── crypto_utils
│ ├── aes_easy.go
│ └── aes.go
├── trace_span
│ └── main.go
├── http_client_with_rate
│ └── http_rl_client.go
├── byte_order
│ └── main.go
├── learntime
│ └── code.go
├── cyclic_barrier
│ └── h2o_factory.go
├── aes-go-java-convertor
│ ├── aes-go
│ │ └── ase.go
│ └── aes-java
│ │ └── EncryptDecryptDemo.java
├── httptool
│ └── http_util.go
├── gen_token
│ └── main.go
├── prevent_over_concurrency
│ └── main.go
├── holmes_notes
│ └── get_process_info.go
├── ali_tts_oss
│ └── tts
│ │ └── tts.go
└── img
│ └── base.go
├── cmd
└── es
│ ├── clear_doc_in_index.shell
│ ├── query_with_must_not.shell
│ └── create_index_with_mapping.shell
├── .gitignore
├── lang-basic
├── README.md
└── content
│ ├── chapter1.md
│ ├── chapter3.md
│ ├── chapter5.md
│ ├── chapter4.md
│ └── chapter2.md
├── LICENSE
└── README.md
/codes/init_trace/go.mod:
--------------------------------------------------------------------------------
1 | module example.com/init_trace
2 |
3 | go 1.13
4 |
--------------------------------------------------------------------------------
/codes/proconsumer/go.mod:
--------------------------------------------------------------------------------
1 | module example.com/proconsumer
2 |
3 | go 1.13
4 |
5 | require golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
6 |
--------------------------------------------------------------------------------
/codes/practices/crosscompile/rewrite_panics_windows.go:
--------------------------------------------------------------------------------
1 | package crosscompile
2 |
3 | func RewritePanicsToFile(topic string) error {
4 | return nil
5 | }
6 |
--------------------------------------------------------------------------------
/codes/init_trace/trace/trace.go:
--------------------------------------------------------------------------------
1 | package trace
2 |
3 | import "fmt"
4 |
5 | func Trace(t string, v int) int {
6 | fmt.Println(t, ":", v)
7 | return v
8 | }
9 |
--------------------------------------------------------------------------------
/cmd/es/clear_doc_in_index.shell:
--------------------------------------------------------------------------------
1 | curl -H 'Content-Type: application/json' -XPOST '{$es_server_host}[:{$port}]/{$index_name}/_delete_by_query' -d '{
2 | "query" : {
3 | "match_all" : {}
4 | }
5 | }'
6 |
--------------------------------------------------------------------------------
/codes/tag_picker/piker.go:
--------------------------------------------------------------------------------
1 | package tag_picker
2 | // TagPicker 接口定义
3 | type Picker interface {
4 | // 用于查询用户的标签值
5 | PickTagValueForUser (userId int64, args ... interface{})
6 | // 通知查询到的标签值
7 | Notify () <-chan interface{}
8 | }
9 |
--------------------------------------------------------------------------------
/codes/proconsumer/go.sum:
--------------------------------------------------------------------------------
1 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
2 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.exe~
4 | *.dll
5 | *.so
6 | *.dylib
7 |
8 | # Test binary, build with `go test -c`
9 | *.test
10 |
11 | # Output of the go coverage tool, specifically when used with LiteIDE
12 | *.out
13 |
--------------------------------------------------------------------------------
/codes/init_trace/pkg2/pkg2.go:
--------------------------------------------------------------------------------
1 | package pkg2
2 |
3 |
4 | import (
5 | "example.com/init_trace/trace"
6 | "fmt"
7 | )
8 |
9 | var P2_v1 = trace.Trace("init P2_v1", 20)
10 | var P2_v2 = trace.Trace("init P2_v2", 30)
11 |
12 | func init() {
13 | fmt.Println("init func in pkg2")
14 | }
15 |
--------------------------------------------------------------------------------
/codes/init_trace/pkg1/pkg1.go:
--------------------------------------------------------------------------------
1 | package pkg1
2 |
3 | import (
4 | "example.com/init_trace/pkg2"
5 | "example.com/init_trace/trace"
6 | "fmt"
7 | )
8 |
9 | var P1_v1 = trace.Trace("init P1_v1", pkg2.P2_v1 + 10)
10 | var P1_v2 = trace.Trace("init P1_v2", pkg2.P2_v2 + 10)
11 |
12 | func init() {
13 | fmt.Println("init func in pkg1")
14 | }
15 |
--------------------------------------------------------------------------------
/codes/slice_util/code.go:
--------------------------------------------------------------------------------
1 | package slice_util
2 |
3 | func StringInSlice(search string, arr []string) bool {
4 | for _, v := range arr {
5 | if v == search {
6 | return true
7 | }
8 | }
9 | return false
10 | }
11 |
12 | func FilterStringSlice(ss []string, test func(string) bool) (ret []string) {
13 | for _, s := range ss {
14 | if test(s) {
15 | ret = append(ret, s)
16 | }
17 | }
18 | return
19 | }
20 |
--------------------------------------------------------------------------------
/codes/init_trace/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "example.com/init_trace/pkg1"
5 | "example.com/init_trace/pkg2"
6 |
7 | "example.com/init_trace/trace"
8 | "fmt"
9 | )
10 |
11 | func init() {
12 | fmt.Println("init1 func in main")
13 | }
14 |
15 | func init() {
16 | fmt.Println("init2 func in main")
17 | }
18 |
19 | var M_v1 = trace.Trace("init M_v1", pkg1.P1_v2 + 10)
20 | var M_v2 = trace.Trace("init M_v2", pkg2.P2_v2 + 10)
21 |
22 |
23 | func main() {
24 | fmt.Println("main func in main")
25 | }
26 |
--------------------------------------------------------------------------------
/codes/proconsumer/consumer.go:
--------------------------------------------------------------------------------
1 | package proconsumer
2 |
3 | import (
4 | "context"
5 | )
6 |
7 | type Consumer struct {
8 | DataCh <- chan interface{}
9 | closure ConsumerCallerFunc
10 | CancelCtx context.Context
11 | }
12 |
13 | func (cs *Consumer) RegisterConsumeFunc(consumer ConsumerCallerFunc) error {
14 | cs.closure = consumer
15 | return nil
16 | }
17 |
18 | func (cs *Consumer) Run () error {
19 | err := cs.closure(cs)
20 | return err
21 | }
22 |
23 | type ConsumerCallerFunc func (consumer *Consumer) error
--------------------------------------------------------------------------------
/codes/distributed_lock/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "example.com/distributed_lock/etcd"
5 | "flag"
6 | "log"
7 | "strings"
8 | )
9 |
10 | var (
11 | addr = flag.String("addr", "192.168.64.4:30453", "etcd addresses")
12 | name = flag.String("name", "", "give a name")
13 | )
14 |
15 | func main() {
16 | flag.Parse()
17 | endpoints := strings.Split(*addr, ",")
18 | cli, err := etcd.NewClient(endpoints)
19 | if err != nil {
20 | log.Fatal(err)
21 | }
22 | defer cli.Close()
23 |
24 | etcd.UseLock(cli, *name)
25 | }
26 |
--------------------------------------------------------------------------------
/codes/proconsumer/producer.go:
--------------------------------------------------------------------------------
1 | package proconsumer
2 |
3 | import (
4 | "context"
5 | )
6 |
7 | type Producer struct {
8 | DataCh chan <- interface{}
9 | closure ProducerCallerFunc
10 | CancelCtx context.Context
11 | }
12 |
13 |
14 | func (pr *Producer) RegisterProduceFunc(closure ProducerCallerFunc) error {
15 | pr.closure = closure
16 | return nil
17 | }
18 |
19 | func (pr *Producer) Run() error {
20 | err := pr.closure(pr)
21 | return err
22 | }
23 |
24 |
25 | // 生产者要调用的方法
26 | type ProducerCallerFunc func(producer *Producer) error
--------------------------------------------------------------------------------
/codes/unsafe/swtich_pointer_type.go:
--------------------------------------------------------------------------------
1 | import (
2 | "fmt"
3 | "reflect"
4 | "unsafe"
5 | )
6 |
7 | func main() {
8 |
9 | v1 := uint(12)
10 | v2 := int(13)
11 |
12 | fmt.Println(reflect.TypeOf(v1)) //uint
13 | fmt.Println(reflect.TypeOf(v2)) //int
14 |
15 | fmt.Println(reflect.TypeOf(&v1)) //*uint
16 | fmt.Println(reflect.TypeOf(&v2)) //*int
17 |
18 | p := &v1
19 | p = (*uint)(unsafe.Pointer(&v2)) //使用unsafe.Pointer进行类型的转换
20 |
21 | fmt.Println(reflect.TypeOf(p)) // *unit
22 | fmt.Println(*p) //13
23 | }
24 |
--------------------------------------------------------------------------------
/codes/unsafe/access_unexported_field.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "unsafe"
6 | )
7 |
8 | func main() {
9 |
10 | var x struct {
11 | a int
12 | b int
13 | c []int
14 | }
15 |
16 | // unsafe.Offsetof 函数的参数必须是一个字段, 比如 x.b, 方法会返回 b 字段相对于 x 起始地址的偏移量, 包括可能的空洞。
17 |
18 | // 指针运算 uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)。
19 |
20 | // 和 pb := &x.b 等价
21 | pb := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)))
22 |
23 | *pb = 42
24 | fmt.Println(x.b) // "42"
25 | }
26 |
--------------------------------------------------------------------------------
/codes/order_util/gen_order_no.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "math/rand"
6 | "time"
7 | )
8 |
9 | func main() {
10 | fmt.Println(GenOrderNo(12344566256))
11 | }
12 |
13 | func GenOrderNo(userId int64) string {
14 | const TimeFormatPlainDate = "20060102"
15 | day := time.Now().Format(TimeFormatPlainDate)
16 |
17 | rand.Seed(time.Now().UnixNano())
18 | seqStr := fmt.Sprintf("%014d", rand.Intn(99999999999999))
19 |
20 | subId := fmt.Sprintf("%04d", 12344566)
21 | if len(subId) > 4 {
22 | subId = subId[len(subId)-5 : len(subId)-1]
23 | }
24 | return day + seqStr + subId
25 | }
26 |
--------------------------------------------------------------------------------
/codes/json/encoder/noescapehtml.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "fmt"
7 | )
8 |
9 | func main() {
10 | data := struct {
11 | Name string
12 | Url string
13 | }{
14 | Name: "Jesper",
15 | Url: "https://yourexample.com/add?type=1&sku=iphone",
16 | }
17 | enc, _ := MarshalWithNoEscape(&data)
18 | fmt.Println(string(enc))
19 | }
20 |
21 | func MarshalWithNoEscape(i interface{}) ([]byte, error) {
22 | buffer := &bytes.Buffer{}
23 | encoder := json.NewEncoder(buffer)
24 | encoder.SetEscapeHTML(false)
25 | err := encoder.Encode(i)
26 | return bytes.TrimRight(buffer.Bytes(), "\n"), err
27 | }
28 |
--------------------------------------------------------------------------------
/codes/ctx-usage-demo/http-timeout-demo.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | "os"
7 | "time"
8 | )
9 |
10 | func main() {
11 | // 创建一个监听8000端口的HTTP Server
12 | http.ListenAndServe(":8000", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
13 | ctx := r.Context()
14 | // 开始处理请求
15 | fmt.Fprint(os.Stdout, "processing request\n")
16 | // select 接收定时和ctx.Done两个channel,哪个先来执行哪个。
17 | select {
18 | case <-time.After(2 * time.Second):
19 | // 用定时器模拟请求处理成功,如果2s后接收到了channel里的信息,返回请求成功
20 | w.Write([]byte("request processed"))
21 | case <-ctx.Done():
22 | // 如果请求被取消了,返回请求被取消
23 | fmt.Fprint(os.Stderr, "request cancelled\n")
24 | }
25 | }))
26 | }
27 |
--------------------------------------------------------------------------------
/codes/atomic/value_demo.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "sync"
6 | "sync/atomic"
7 | )
8 |
9 | type Rectangle struct {
10 | length int
11 | width int
12 | }
13 |
14 | var rect atomic.Value
15 |
16 | func update(width, length int) {
17 | rectLocal := new(Rectangle)
18 | rectLocal.width = width
19 | rectLocal.length = length
20 | rect.Store(rectLocal)
21 | }
22 |
23 | func main() {
24 | wg := sync.WaitGroup{}
25 | wg.Add(10)
26 | // 10 个协程并发更新
27 | for i := 0; i < 10; i++ {
28 | go func() {
29 | defer wg.Done()
30 | update(i, i+5)
31 | }()
32 | }
33 | wg.Wait()
34 | _r := rect.Load().(*Rectangle)
35 | fmt.Printf("rect.width=%d\nrect.length=%d\n", _r.width, _r.length)
36 | }
37 |
--------------------------------------------------------------------------------
/codes/context_demo/ctx_listen_http_client_cancel.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | "os"
7 | "time"
8 | )
9 |
10 | func main() {
11 | // 创建一个监听8000端口的HTTP Server
12 | http.ListenAndServe(":8000", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
13 | ctx := r.Context()
14 | // 开始处理请求
15 | fmt.Fprint(os.Stdout, "processing request\n")
16 | // select 接收定时和ctx.Done两个channel,哪个先来执行哪个。
17 | select {
18 | case <-time.After(2 * time.Second):
19 | // 用定时器模拟请求处理成功,如果2s后接收到了channel里的信息,返回请求成功
20 | w.Write([]byte("request processed"))
21 | case <-ctx.Done():
22 | // 如果请求被取消了,返回请求被取消
23 | fmt.Fprint(os.Stderr, "request cancelled\n")
24 | }
25 | }))
26 | }
27 |
--------------------------------------------------------------------------------
/codes/ctx-usage-demo/demo1.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "time"
6 | )
7 |
8 | func main() {
9 | messages := make(chan int, 10)
10 | done := make(chan bool)
11 |
12 | defer close(messages)
13 | // consumer
14 | go func() {
15 | ticker := time.NewTicker(1 * time.Second)
16 | for _ = range ticker.C {
17 | select {
18 | case <-done:
19 | fmt.Println("child process interrupt...")
20 | return
21 | default:
22 | fmt.Printf("received message: %d\n", <-messages)
23 | }
24 | }
25 | }()
26 |
27 | // producer
28 | for i := 0; i < 10; i++ {
29 | messages <- i
30 | }
31 | time.Sleep(5 * time.Second)
32 | close(done)
33 | time.Sleep(1 * time.Second)
34 | fmt.Println("main process exit!")
35 | }
36 |
--------------------------------------------------------------------------------
/codes/context_demo/no_ctx_demo.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "time"
6 | )
7 |
8 | func main() {
9 | messages := make(chan int, 10)
10 | done := make(chan bool)
11 |
12 | defer close(messages)
13 | // consumer
14 | go func() {
15 | ticker := time.NewTicker(1 * time.Second)
16 | for _ = range ticker.C {
17 | select {
18 | case <-done:
19 | fmt.Println("child process interrupt...")
20 | return
21 | default:
22 | fmt.Printf("received message: %d\n", <-messages)
23 | }
24 | }
25 | }()
26 |
27 | // producer
28 | for i := 0; i < 10; i++ {
29 | messages <- i
30 | }
31 | time.Sleep(5 * time.Second)
32 | close(done)
33 | time.Sleep(1 * time.Second)
34 | fmt.Println("main process exit!")
35 | }
36 |
--------------------------------------------------------------------------------
/codes/slice/remove_duplicated_elements.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | func main() {
8 | s := []string{"hello", "world", "hello", "golang", "hello", "ruby", "php", "java"}
9 |
10 | fmt.Println(removeDuplicateElement(s)) //output: hello world golang ruby php java
11 | }
12 |
13 | func removeDuplicateElement(languages []string) []string {
14 | result := make([]string, 0, len(languages))
15 | temp := map[string]struct{}{}
16 | for _, item := range languages {
17 | if _, ok := temp[item]; !ok {
18 | temp[item] = struct{}{}
19 | result = append(result, item)
20 | }
21 | }
22 | return result
23 | }
24 |
25 | // output: ["hello", "world", "golang", "ruby", "php", "java"]
26 |
--------------------------------------------------------------------------------
/codes/distributed_lock/go.mod:
--------------------------------------------------------------------------------
1 | module example.com/distributed_lock
2 |
3 | go 1.13
4 |
5 | replace (
6 | github.com/coreos/bbolt => go.etcd.io/bbolt v1.3.5
7 | google.golang.org/grpc v1.38.0 => google.golang.org/grpc v1.26.0
8 | )
9 |
10 | require (
11 | github.com/coreos/bbolt v0.0.0-00010101000000-000000000000 // indirect
12 | github.com/coreos/etcd v3.3.25+incompatible
13 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect
14 | github.com/google/uuid v1.2.0 // indirect
15 | github.com/prometheus/client_golang v1.10.0 // indirect
16 | go.etcd.io/etcd v3.3.25+incompatible
17 | go.uber.org/zap v1.17.0 // indirect
18 | golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect
19 | google.golang.org/grpc v1.38.0 // indirect
20 | )
21 |
--------------------------------------------------------------------------------
/codes/latency-codes/req-body-not-close/code.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | // request body不进行 body.Close() 操作导致移除的先决条件
4 | // 既不执行 ioutil.ReadAll(resp.Body) 也不执行resp.Body.Close(),并且不设置http.Client内timeout的时候,就会导致协程泄露
5 | func main() {
6 | tr := &http.Transport{
7 | MaxIdleConns: 100,
8 | IdleConnTimeout: 3 * time.Second,
9 | }
10 |
11 | n := 5
12 | for i := 0; i < n; i++ {
13 | req, _ := http.NewRequest("POST", "https://www.baidu.com", nil)
14 | req.Header.Add("content-type", "application/json")
15 | client := &http.Client{
16 | Transport: tr,
17 | }
18 | resp, _ := client.Do(req)
19 | _ = resp
20 | }
21 | time.Sleep(time.Second * 5)
22 | fmt.Printf("goroutine num is %d\n", runtime.NumGoroutine())
23 | }
24 |
--------------------------------------------------------------------------------
/codes/flag_library/boolean/boolean.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | )
7 |
8 | type Color string
9 |
10 | const (
11 | ColorBlack Color = "\u001b[30m"
12 | ColorRed = "\u001b[31m"
13 | ColorGreen = "\u001b[32m"
14 | ColorYellow = "\u001b[33m"
15 | ColorBlue = "\u001b[34m"
16 | ColorReset = "\u001b[0m"
17 | )
18 |
19 | func colorize(color Color, message string) {
20 | fmt.Println(string(color), message, string(ColorReset))
21 | }
22 |
23 | func main() {
24 | useColor := flag.Bool("color", false, "display colorized output")
25 | flag.Parse()
26 |
27 | if *useColor {
28 | colorize(ColorBlue, "Hello, DigitalOcean!")
29 | return
30 | }
31 | fmt.Println("Hello, DigitalOcean!")
32 | }
33 |
--------------------------------------------------------------------------------
/codes/unsafe/slice_switch_string_zero_copy.go:
--------------------------------------------------------------------------------
1 | import (
2 | "fmt"
3 | "reflect"
4 | "unsafe"
5 | )
6 |
7 | func main() {
8 | s := "Hello World"
9 | b := string2bytes(s)
10 | fmt.Println(b)
11 | s = bytes2string(b)
12 | fmt.Println(s)
13 |
14 | }
15 |
16 | func string2bytes(s string) []byte {
17 | stringHeader := (*reflect.StringHeader)(unsafe.Pointer(&s))
18 |
19 | bh := reflect.SliceHeader{
20 | Data: stringHeader.Data,
21 | Len: stringHeader.Len,
22 | Cap: stringHeader.Len,
23 | }
24 |
25 | return *(*[]byte)(unsafe.Pointer(&bh))
26 | }
27 |
28 | func bytes2string(b []byte) string {
29 | sliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&b))
30 |
31 | sh := reflect.StringHeader{
32 | Data: sliceHeader.Data,
33 | Len: sliceHeader.Len,
34 | }
35 |
36 | return *(*string)(unsafe.Pointer(&sh))
37 | }
38 |
--------------------------------------------------------------------------------
/codes/gbkdetect/code.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "golang.org/x/text/encoding/simplifiedchinese"
6 | "golang.org/x/text/transform"
7 | "io"
8 | "unicode/utf8"
9 | )
10 |
11 | func convertGBKToUTF8(input string) (string, error) {
12 | // 检测是否为 GBK 编码(Windows 中文文件名常用)
13 | if detectEncoding([]byte(input)) != "GBK" {
14 | return input, nil
15 | }
16 | // GBK → UTF-8 转换
17 | decoder := simplifiedchinese.GBK.NewDecoder()
18 | reader := transform.NewReader(bytes.NewReader([]byte(input)), decoder)
19 | result, err := io.ReadAll(reader)
20 | return string(result), err
21 | }
22 |
23 | func detectEncoding(data []byte) string {
24 | if utf8.Valid(data) {
25 | return "UTF-8"
26 | }
27 | decoder := simplifiedchinese.GBK.NewDecoder()
28 | if _, _, err := transform.Bytes(decoder, data); err == nil {
29 | return "GBK"
30 | }
31 | return "Unknown"
32 | }
33 |
--------------------------------------------------------------------------------
/codes/practices/crosscompile/rewrite_panics_unix.go:
--------------------------------------------------------------------------------
1 | //+build darwin linux unix
2 |
3 | package crosscompile
4 |
5 | import (
6 | "fmt"
7 | "os"
8 | "runtime"
9 | "syscall"
10 | )
11 |
12 | var stdErrFileHandler *os.File
13 | // go的运行时错误panic默认写到标准错误中, 由于无法持久化容器重启后无法排查引起panic的原因
14 | // 使用此方法将标准错误重写到持久化的日志文件里
15 | func RewritePanicsToFile(topic string) error {
16 | errFile := "/home/golanger/log/" + topic + "_stdErr.log"
17 | errFileHandler, err := os.OpenFile(errFile, os.O_RDWR|os.O_CREATE | os.O_APPEND, 0666)
18 | if err != nil {
19 | fmt.Println(err)
20 | return err
21 | }
22 | stdErrFileHandler = errFileHandler //把文件句柄保存到全局变量,避免被GC回收
23 |
24 | if err = syscall.Dup2(int(errFileHandler.Fd()), int(os.Stderr.Fd())); err != nil {
25 | return err
26 | }
27 | // GC前关闭文件描述符
28 | runtime.SetFinalizer(stdErrFileHandler, func(fd *os.File) {
29 | fd.Close()
30 | })
31 |
32 | return nil
33 | }
34 |
35 |
--------------------------------------------------------------------------------
/codes/copy_properties/copier_demo.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/jinzhu/copier"
6 | )
7 |
8 | type RoleA struct {
9 | RoleId int
10 | RoleName string
11 | }
12 |
13 | type RoleB struct {
14 | RoleId int
15 | RoleName string
16 | }
17 |
18 | type User struct {
19 | Name string
20 | Role RoleA
21 | Age int32
22 | EmployeeCode int64
23 | Salary int
24 | }
25 |
26 | type Employee struct {
27 | Name string
28 | Role RoleB
29 | Age int32
30 | EmployeeCode int64
31 | Salary int
32 | }
33 |
34 | func CopyProperties() {
35 | user := User{Name: "KevinYan", Age: 18, Salary: 200000}
36 | user.Role = RoleA{
37 | RoleId: 2,
38 | RoleName: "Admin",
39 | }
40 | employee := new(Employee)
41 |
42 |
43 | copier.Copy(employee, &user)
44 |
45 | fmt.Printf( "%v\n", employee)
46 | fmt.Printf("%v\n", user)
47 | }
48 |
49 | func main() {
50 | CopyProperties()
51 | }
52 |
--------------------------------------------------------------------------------
/codes/tag_picker/order_num.go:
--------------------------------------------------------------------------------
1 | package tag_picker
2 |
3 |
4 | type OrderNumTagPicker struct {
5 | TagName string
6 | ValueCh chan interface{}
7 | }
8 |
9 | type TradeNoInfo struct {
10 | TradeNo string `json:"trade_no"`
11 | }
12 |
13 | func (picker *OrderNumTagPicker) PickTagValueForUser(userId int64, args ...interface{}) {
14 | // 用类型转换得到交易号
15 | // tradeNo, ok := args[0].(string)
16 | extInfoJson, _ := args[0].(string)
17 |
18 | if err := json.Unmarshal([]byte(extInfoJson), &TradeNoInfo); err != nil {
19 | // log.Error自己实现
20 | log.Error("PayTotalTagPickerError", "Invalid arg", args[0])
21 | // 结束执行并通知外部
22 | picker.ValueCh <- nil
23 | return
24 | }
25 | // 这里就打印下参数值,标签查询的具体逻辑自己实现
26 | fmt.Println(userId)
27 | fmt.Println(TradeNo)
28 | // 查询到的用户的标签 (假设交易号下有10个订单)
29 | picker.ValueCh <- 10
30 | }
31 |
32 | func (picker *OrderNumTagPicker) Notify () <-chan interface{} {
33 | return picker.ValueCh
34 | }
35 |
--------------------------------------------------------------------------------
/codes/flag_library/head_command/head.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bufio"
5 | "flag"
6 | "fmt"
7 | "io"
8 | "os"
9 | )
10 |
11 | func main() {
12 | var count int
13 | flag.IntVar(&count, "n", 5, "number of lines to read from the file")
14 | flag.Parse()
15 |
16 | var in io.Reader
17 | if filename := flag.Arg(0); filename != "" {
18 | f, err := os.Open(filename)
19 | if err != nil {
20 | fmt.Println("error opening file: err:", err)
21 | os.Exit(1)
22 | }
23 | defer f.Close()
24 |
25 | in = f
26 | } else {
27 | in = os.Stdin
28 | }
29 |
30 | buf := bufio.NewScanner(in)
31 |
32 | for i := 0; i < count; i++ {
33 | if !buf.Scan() {
34 | break
35 | }
36 | fmt.Println(buf.Text())
37 | }
38 |
39 | if err := buf.Err(); err != nil {
40 | fmt.Fprintln(os.Stderr, "error reading: err:", err)
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/lang-basic/README.md:
--------------------------------------------------------------------------------
1 | 我维护 GoCookBook-- Go开发实战参考书的目的是为了记录做Go开发期间用过、学过、被坑过的各种经历,让自己也让偶然看到这个项目的人能更高效的完成生产级的程序开发。
2 |
3 | 不过我发现一些基础语法、概念之类的东西,只要不用,过不了两周就会忘或者是记忆模糊了,如果同时写两种编程语言也有类似的症状。所以这里再把以前记得一些基础语法、概念整理到项目里。这些都是我以前刚学Go时记得一些读书笔记,几乎没有什么废话,
4 | 全是各种语法、概念的代码示例,如果正好你也跟我一样健忘,可以常来翻翻,文章主要涉及五块基础内容,高阶和实战内容还是参考项目的其他章节吧。
5 |
6 | ### Go 语言基础
7 | - [基础类型和复合类型](https://github.com/kevinyan815/gocookbook/blob/master/lang-basic/content/chapter1.md)
8 | - [函数](https://github.com/kevinyan815/gocookbook/blob/master/lang-basic/content/chapter2.md)
9 | - [方法](https://github.com/kevinyan815/gocookbook/blob/master/lang-basic/content/chapter3.md)
10 | - [接口 -- interface](https://github.com/kevinyan815/gocookbook/blob/master/lang-basic/content/chapter4.md)
11 | - [goroutine和channel](https://github.com/kevinyan815/gocookbook/blob/master/lang-basic/content/chapter5.md)
12 |
13 |
14 | **实战内容请查看其他章节, 更多时下流行技术的应用和实战教程,可通过我公众号「网管叨bi叨」每周的推文来学习**
15 |
16 | 
17 |
--------------------------------------------------------------------------------
/codes/slice/efficient_append.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | func VariableLenNoInitialSlice(size int) []int {
4 | slice := make([]int, 0)
5 | for i := 0; i < size; i++ {
6 | slice = append(slice, i)
7 | }
8 | return slice
9 | }
10 |
11 | func VariableLenShorterSlice(size int) []int {
12 | slice := make([]int, 100)
13 | for i := 0; i < size; i++ {
14 | slice = append(slice, i)
15 | }
16 | return slice
17 | }
18 |
19 | // Recommanded append in this way
20 | // first predfine slice capability to parameter specified size
21 | // then make it length to 0 at the beginning
22 | // and append item into it in iteration.
23 | // This would not cause the array underlying this slice to expend
24 | // that means not memory copy and paste opertion
25 | // that's why its more efficient then other ways
26 | func VariableLenLargerSlice(size int) []int {
27 | slice := make([]int, 0, size)
28 | for i := 0; i < size; i++ {
29 | slice = append(slice, i)
30 | }
31 | return slice
32 | }
33 |
--------------------------------------------------------------------------------
/codes/synccond/learn_sync_cond.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "strconv"
6 | "sync"
7 | "time"
8 | )
9 |
10 | var queue []struct{}
11 |
12 | func main() {
13 | var wg sync.WaitGroup
14 | c := sync.NewCond(&sync.Mutex{})
15 |
16 | for i := 0; i < 2; i ++ {
17 | wg.Add(1)
18 |
19 | go func(i int) {
20 | // this go routine wait for changes to the sharedRsc
21 | c.L.Lock()
22 | for len(queue) <= 0 {
23 | fmt.Println("goroutine" + strconv.Itoa(i) +" wait")
24 | c.Wait()
25 | }
26 | fmt.Println("goroutine" + strconv.Itoa(i), "pop data")
27 | queue = queue[1:]
28 | c.L.Unlock()
29 | wg.Done()
30 | }(i)
31 |
32 | }
33 |
34 |
35 | for i := 0; i < 2; i ++ {
36 | // 主goroutine延迟两秒准备好后把变量设置为true
37 | time.Sleep(2 * time.Second)
38 | c.L.Lock()
39 | fmt.Println("main goroutine push data")
40 | queue= append(queue, struct{}{})
41 | c.Broadcast()
42 | fmt.Println("main goroutine broadcast")
43 | c.L.Unlock()
44 |
45 | }
46 |
47 | wg.Wait()
48 | }
49 |
--------------------------------------------------------------------------------
/codes/atomic/add_load_demo.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "sync"
6 | "sync/atomic"
7 | "time"
8 | )
9 |
10 | func main() {
11 | mutexAdd()
12 | AtomicAdd()
13 | }
14 |
15 | func mutexAdd() {
16 | var a int32 = 0
17 | var wg sync.WaitGroup
18 | var mu sync.Mutex
19 | start := time.Now()
20 | for i := 0; i < 1000000; i++ {
21 | wg.Add(1)
22 | go func() {
23 | defer wg.Done()
24 | mu.Lock()
25 | a += 1
26 | mu.Unlock()
27 | }()
28 | }
29 | wg.Wait()
30 | timeSpends := time.Now().Sub(start).Nanoseconds()
31 | fmt.Printf("use mutex a is %d, spend time: %v\n", a, timeSpends)
32 | }
33 |
34 | func AtomicAdd() {
35 | var a int32 = 0
36 | var wg sync.WaitGroup
37 | start := time.Now()
38 | for i := 0; i < 1000000; i++ {
39 | wg.Add(1)
40 | go func() {
41 | defer wg.Done()
42 | atomic.AddInt32(&a, 1)
43 | }()
44 | }
45 | wg.Wait()
46 | timeSpends := time.Now().Sub(start).Nanoseconds()
47 | fmt.Printf("use atomic a is %d, spend time: %v\n", atomic.LoadInt32(&a), timeSpends)
48 | }
49 |
--------------------------------------------------------------------------------
/codes/singleflight/anti_cache_break.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "golang.org/x/sync/singleflight"
6 | "sync"
7 | "time"
8 | )
9 |
10 | // 模拟一个Redis客户端
11 | type client struct {
12 | // ... 其他的配置省略
13 | requestGroup singleflight.Group
14 | }
15 |
16 | // 普通查询
17 | func (c *client) Get(key string) (interface{}, error) {
18 | fmt.Println("Querying Database")
19 | time.Sleep(time.Second)
20 | v := "Content of key" + key
21 | return v, nil
22 | }
23 |
24 | // SingleFlight查询
25 | func (c *client) SingleFlightGet(key string) (interface{}, error) {
26 | v, err, _ := c.requestGroup.Do(key, func() (interface{}, error) {
27 | return c.Get(key)
28 |
29 | })
30 | if err != nil {
31 | return nil, err
32 | }
33 | return v, err
34 | }
35 |
36 | func main() {
37 | redisClient := new(client)
38 | wg := sync.WaitGroup{}
39 | wg.Add(10)
40 | for i := 0; i < 10; i++ {
41 |
42 | go func() {
43 | defer wg.Done()
44 | v, _ := redisClient.SingleFlightGet("Cyberpunk2077!!!")
45 | fmt.Println(v)
46 | }()
47 | }
48 | wg.Wait()
49 | }
50 |
--------------------------------------------------------------------------------
/codes/password_complexity/code.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "unicode"
6 | )
7 |
8 | func verifyPasswordComplexity(s string) bool {
9 | var (
10 | hasMinLen = false
11 | hasUpper = false
12 | hasLower = false
13 | hasNumber = false
14 | hasSpecial = false
15 | )
16 | if len(s) >= 8 {
17 | hasMinLen = true
18 | }
19 | for _, char := range s {
20 | switch {
21 | case unicode.IsUpper(char):
22 | hasUpper = true
23 | case unicode.IsLower(char):
24 | hasLower = true
25 | case unicode.IsNumber(char):
26 | hasNumber = true
27 | case unicode.IsPunct(char) || unicode.IsSymbol(char):
28 | hasSpecial = true
29 | }
30 | }
31 | return hasMinLen && hasUpper && hasLower && hasNumber && hasSpecial
32 | }
33 |
34 | func main() {
35 | fmt.Println(verifyPasswordComplexity("pass")) // false
36 | fmt.Println(verifyPasswordComplexity("password")) // false
37 | fmt.Println(verifyPasswordComplexity("Password")) // false
38 | fmt.Println(verifyPasswordComplexity("P@ssword")) // false
39 | fmt.Println(verifyPasswordComplexity("P@ssw0rd")) // true
40 | }
41 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Kevin Yan
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/codes/random_util/code.go:
--------------------------------------------------------------------------------
1 | package random_util
2 |
3 | import (
4 | "math/rand"
5 | "time"
6 | )
7 |
8 | type (
9 | Random struct {
10 | charset Charset
11 | }
12 |
13 | Charset string
14 | )
15 |
16 | const (
17 | Alphanumeric Charset = Alphabetic + Numeric
18 | Alphabetic Charset = "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
19 | Numeric Charset = "0123456789"
20 | Hex Charset = Numeric + "abcdef"
21 | )
22 |
23 | var (
24 | globalRandom = NewRandom()
25 | )
26 |
27 | func NewRandom() *Random {
28 | rand.Seed(time.Now().UnixNano())
29 | return &Random{
30 | charset: Alphanumeric,
31 | }
32 | }
33 |
34 | func (r *Random) SetCharset(c Charset) {
35 | r.charset = c
36 | }
37 |
38 | func (r *Random) String(length uint8) string {
39 | b := make([]byte, length)
40 | for i := range b {
41 | b[i] = r.charset[rand.Int63()%int64(len(r.charset))]
42 | }
43 | return string(b)
44 | }
45 |
46 | func SetCharset(c Charset) {
47 | globalRandom.SetCharset(c)
48 | }
49 |
50 | func RandomString(length uint8) string {
51 | return globalRandom.String(length)
52 | }
53 |
54 | func RandNumStr(len uint8) string {
55 | r := NewRandom()
56 | r.SetCharset(Numeric)
57 | return r.String(len)
58 | }
59 |
--------------------------------------------------------------------------------
/codes/password/password.go:
--------------------------------------------------------------------------------
1 | package password
2 |
3 | import (
4 | "golang.org/x/crypto/bcrypt"
5 | "unicode"
6 | )
7 |
8 | func BcryptPassword(plainPassword string) (string, error) {
9 | bytes, err := bcrypt.GenerateFromPassword([]byte(plainPassword), 11)
10 | return string(bytes), err
11 | }
12 |
13 | func BcryptCompare(passwordHash, plainPassword string) bool {
14 | err := bcrypt.CompareHashAndPassword([]byte(passwordHash), []byte(plainPassword))
15 | return err == nil
16 | }
17 |
18 | func BcryptGetCost(passwordHash string) int {
19 | cost, _ := bcrypt.Cost([]byte(passwordHash))
20 | return cost
21 | }
22 |
23 | func PasswordComplexityVerify(s string) bool {
24 | var (
25 | hasMinLen = false
26 | hasUpper = false
27 | hasLower = false
28 | hasNumber = false
29 | hasSpecial = false
30 | )
31 | if len(s) >= 8 {
32 | hasMinLen = true
33 | }
34 | for _, char := range s {
35 | switch {
36 | case unicode.IsUpper(char):
37 | hasUpper = true
38 | case unicode.IsLower(char):
39 | hasLower = true
40 | case unicode.IsNumber(char):
41 | hasNumber = true
42 | case unicode.IsPunct(char) || unicode.IsSymbol(char):
43 | hasSpecial = true
44 | }
45 | }
46 | return hasMinLen && hasUpper && hasLower && hasNumber && hasSpecial
47 | }
48 |
--------------------------------------------------------------------------------
/codes/errorgroup/errorgroup.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "log"
7 | "time"
8 |
9 | "golang.org/x/sync/errgroup"
10 | )
11 |
12 | func ErrorGroupDemo() {
13 | var eg errgroup.Group
14 | for i := 0; i < 100; i++ {
15 | i := i
16 | eg.Go(func() error {
17 | time.Sleep(2 * time.Second) // 長い処理
18 | if i > 90 {
19 | fmt.Println("Error:", i)
20 | return fmt.Errorf("Error occurred: %d", i)
21 | }
22 | fmt.Println("End:", i)
23 | return nil
24 | })
25 | }
26 | if err := eg.Wait(); err != nil {
27 | log.Fatal(err)
28 | }
29 | }
30 |
31 | func ErrorCancelGroupDemo() {
32 | eg, ctx := errgroup.WithContext(context.Background())
33 |
34 | for i := 0; i < 100; i++ {
35 | i := i
36 | eg.Go(func() error {
37 | time.Sleep(2 * time.Second)
38 |
39 | select {
40 | case <-ctx.Done():
41 | fmt.Println("Canceled:", i)
42 | return nil
43 | default:
44 | if i > 90 {
45 | fmt.Println("Error:", i)
46 | return fmt.Errorf("Error: %d", i)
47 | }
48 | fmt.Println("End:", i)
49 | return nil
50 | }
51 | })
52 | }
53 | if err := eg.Wait(); err != nil {
54 | log.Fatal(err)
55 | }
56 | }
57 |
58 | func main() {
59 |
60 | // ErrorGroupDemo()
61 |
62 | ErrorCancelGroupDemo()
63 | }
64 |
--------------------------------------------------------------------------------
/codes/copy_properties/copy_with_option.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "errors"
5 | "github.com/jinzhu/copier"
6 | "regexp"
7 | "time"
8 | )
9 |
10 | func CopyProperties(dst, src interface{}) error {
11 | err := copier.CopyWithOption(dst, src, copier.Option{
12 | IgnoreEmpty: true,
13 | DeepCopy: true,
14 | Converters: []copier.TypeConverter{
15 | { // time.Time 转换成字符串
16 | SrcType: time.Time{},
17 | DstType: copier.String,
18 | Fn: func(src interface{}) (dst interface{}, err error) {
19 | s, ok := src.(time.Time)
20 | if !ok {
21 | return nil, errors.New("src type is not time.Time")
22 | }
23 | return s.Format("2006-01-02 15:04:05"), nil
24 | },
25 | },
26 | { // 字符串转成time.Time
27 | SrcType: copier.String,
28 | DstType: time.Time{},
29 | Fn: func(src interface{}) (dst interface{}, err error) {
30 | s, ok := src.(string)
31 |
32 | if !ok {
33 | return nil, errors.New("src type is not time format string")
34 | }
35 | pattern := `^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$` // YYYY-MM-DD HH:MM:SS
36 | matched, _ := regexp.MatchString(pattern, s)
37 | if matched {
38 | return time.Parse("2006-01-02 15:04:05", s)
39 | }
40 | return nil, errors.New("src type is not time format string")
41 | },
42 | },
43 | },
44 |
45 | })
46 |
47 | return err
48 | }
49 |
--------------------------------------------------------------------------------
/codes/context_demo/ctx_demo.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "time"
7 | )
8 |
9 | func main() {
10 | messages := make(chan int, 10)
11 |
12 | // producer
13 | for i := 0; i < 10; i++ {
14 | messages <- i
15 | }
16 | valCtx := context.WithValue(context.Background(), "client-ip", "10.96.130.167")
17 | ctx, cancel := context.WithTimeout(valCtx, 5*time.Second)
18 |
19 | // consumer
20 | go func(ctx context.Context) {
21 | ticker := time.NewTicker(1 * time.Second)
22 | for _ = range ticker.C {
23 | select {
24 | case <-ctx.Done():
25 | fmt.Println("child process interrupt...")
26 | return
27 | default:
28 | i := <-messages
29 | timeoutCtx, _ := context.WithTimeout(ctx, 10 * time.Second)
30 | fmt.Printf("child receive message: %d\n", 1)
31 | go anotherFunc(timeoutCtx, i)
32 | }
33 | }
34 | }(ctx)
35 |
36 | defer close(messages)
37 | defer cancel()
38 |
39 | select {
40 | case <-ctx.Done():
41 | time.Sleep(1 * time.Second)
42 | fmt.Println("main process exit!")
43 | }
44 | }
45 |
46 | func anotherFunc(ctx context.Context, message int) {
47 | ticker := time.NewTicker(1 * time.Second)
48 | for _ = range ticker.C {
49 | select {
50 | case <-ctx.Done():
51 | fmt.Println("descendants process interrupt...")
52 | return
53 | default:
54 | fmt.Printf("host-ip: %s, send message %d\n", ctx.Value("client-ip"), message)
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/codes/ctx-usage-demo/demo2.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "time"
7 | )
8 |
9 | func main() {
10 | messages := make(chan int, 10)
11 |
12 | // producer
13 | for i := 0; i < 10; i++ {
14 | messages <- i
15 | }
16 | valCtx := context.WithValue(context.Background(), "client-ip", "10.96.130.167")
17 | ctx, cancel := context.WithTimeout(valCtx, 5*time.Second)
18 |
19 | // consumer
20 | go func(ctx context.Context) {
21 | ticker := time.NewTicker(1 * time.Second)
22 | for _ = range ticker.C {
23 | select {
24 | case <-ctx.Done():
25 | fmt.Println("child process interrupt...")
26 | return
27 | default:
28 | i := <-messages
29 | timeoutCtx, _ := context.WithTimeout(ctx, 10 * time.Second)
30 | fmt.Printf("child receive message: %d\n", 1)
31 | go anotherFunc(timeoutCtx, i)
32 | }
33 | }
34 | }(ctx)
35 |
36 | defer close(messages)
37 | defer cancel()
38 |
39 | select {
40 | case <-ctx.Done():
41 | time.Sleep(1 * time.Second)
42 | fmt.Println("main process exit!")
43 | }
44 | }
45 |
46 | func anotherFunc(ctx context.Context, message int) {
47 | ticker := time.NewTicker(1 * time.Second)
48 | for _ = range ticker.C {
49 | select {
50 | case <-ctx.Done():
51 | fmt.Println("descendants process interrupt...")
52 | return
53 | default:
54 | fmt.Printf("host-ip: %s, send message %d\n", ctx.Value("client-ip"), message)
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/codes/proconsumer/task_scheduler.go:
--------------------------------------------------------------------------------
1 | package proconsumer
2 |
3 | import (
4 | "context"
5 | "golang.org/x/sync/errgroup"
6 | )
7 |
8 | type TaskScheduler struct {
9 | errGroup *errgroup.Group
10 | producer *Producer
11 | consumer *Consumer
12 | consumerNum int
13 | }
14 |
15 | func NewTaskScheduler (pFunc ProducerCallerFunc, cFunc ConsumerCallerFunc, consumerNum int) (ts *TaskScheduler) {
16 |
17 | eg, cancelCtx := errgroup.WithContext(context.Background())
18 |
19 | dataCh := make(chan interface{}, 1000)
20 | producer := &Producer{
21 | DataCh: dataCh,
22 | }
23 | consumer := &Consumer{
24 | DataCh: dataCh,
25 | }
26 | producer.RegisterProduceFunc(pFunc)
27 | consumer.RegisterConsumeFunc(cFunc)
28 | pCancelCtx, _ := context.WithCancel(cancelCtx)
29 | cCancelCtx, _ := context.WithCancel(cancelCtx)
30 | producer.CancelCtx = pCancelCtx
31 | consumer.CancelCtx = cCancelCtx
32 | ts = &TaskScheduler{
33 | errGroup: eg,
34 | producer: producer,
35 | consumer: consumer,
36 | consumerNum: consumerNum,
37 | }
38 |
39 | return
40 | }
41 |
42 | func (ts *TaskScheduler) Execute () (err error) {
43 |
44 | ts.errGroup.Go(ts.producer.Run)
45 |
46 | for i := 0; i < ts.consumerNum; i++ {
47 | ts.errGroup.Go(ts.consumer.Run)
48 | }
49 |
50 | err = ts.errGroup.Wait()
51 | if err != nil {
52 | // 如果有goroutine执行发生错误, 等待2秒尽可能保证其他goroutine完成收尾工作。
53 | time.Sleep(time.Second * 2)
54 | }
55 | return err
56 | }
57 |
--------------------------------------------------------------------------------
/codes/mask_util/code.go:
--------------------------------------------------------------------------------
1 | package mask_util
2 |
3 | import "strings"
4 |
5 |
6 | // MaskPhone 隐去手机号中间 4 位地区码, 如 155****8888
7 | func MaskPhone(phone string) string {
8 | if n := len(phone); n >= 8 {
9 | return phone[:n-8] + "****" + phone[n-4:]
10 | }
11 | return phone
12 | }
13 |
14 | // MaskEmail 隐藏邮箱ID的中间部分 zhang@go-mall.com ---> z***g@go-mall.com
15 | func MaskEmail(address string) string {
16 | strings.LastIndex(address, "@")
17 | id := address[0:strings.LastIndex(address, "@")]
18 | domain := address[strings.LastIndex(address, "@"):]
19 |
20 | if len(id) <= 1 {
21 | return address
22 | }
23 | switch len(id) {
24 | case 2:
25 | id = id[0:1] + "*"
26 | case 3:
27 | id = id[0:1] + "*" + id[2:]
28 | case 4:
29 | id = id[0:1] + "**" + id[3:]
30 | default:
31 | masks := strings.Repeat("*", len(id)-4)
32 | id = id[0:2] + masks + id[len(id)-2:]
33 | }
34 |
35 | return id + domain
36 | }
37 |
38 | // MaskRealName 保留姓名首末位 如:张三--->张* 赵丽颖--->赵*颖 欧阳娜娜--->欧**娜
39 | func MaskRealName(realName string) string {
40 | runeRealName := []rune(realName)
41 | if n := len(runeRealName); n >= 2 {
42 | if n == 2 {
43 | return string(append(runeRealName[0:1], rune('*')))
44 | } else {
45 | count := n - 2
46 | newRealName := runeRealName[0:1]
47 | for temp := 1; temp <= count; temp++ {
48 | newRealName = append(newRealName, rune('*'))
49 | }
50 | return string(append(newRealName, runeRealName[n-1]))
51 | }
52 | }
53 | return realName
54 | }
55 |
--------------------------------------------------------------------------------
/codes/reqeust_sign_generate/request_sign.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "crypto/md5"
6 | "encoding/hex"
7 | "fmt"
8 | "sort"
9 | "strconv"
10 | )
11 |
12 | func GenRequestSign(request map[string]interface{}) (string, string) {
13 |
14 | delete(request, "sign")
15 |
16 | var keys []string
17 | for k := range request {
18 | keys = append(keys, k)
19 | }
20 |
21 | sort.Strings(keys)
22 |
23 | var reString bytes.Buffer
24 | for _, kk := range keys {
25 | if kk == "" {
26 | continue
27 | }
28 | if reString.Len() > 0 {
29 | reString.WriteByte('&')
30 | }
31 |
32 | reString.WriteString(kk)
33 | reString.WriteByte('=')
34 | switch vv := request[kk].(type) {
35 | case string:
36 | reString.WriteString(vv)
37 | case int, int8, int16, int32, int64:
38 | reString.WriteString(fmt.Sprintf("%d", vv))
39 | case float64:
40 | reString.WriteString(strconv.FormatInt(int64(vv), 10))
41 | default:
42 | continue
43 | }
44 | }
45 | secret := "37y4uxXZXeWtCDRq3z14dEhUhCawb2tM"
46 | reString.WriteString(secret)
47 |
48 | return reString.String(), Md5(reString.String())
49 | }
50 |
51 | func Md5(str string) string {
52 | md5ctx := md5.New()
53 | md5ctx.Write([]byte(str))
54 | return hex.EncodeToString(md5ctx.Sum(nil))
55 | }
56 |
57 | func main() {
58 | req := map[string]interface{} {
59 | "request_no":"230621175012994051431130267648serial",
60 | "company_sign":"aaa",
61 | }
62 |
63 | _, b := GenRequestSign(req)
64 | fmt.Println(b)
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/codes/crypto_utils/aes_easy.go:
--------------------------------------------------------------------------------
1 | package crypto_utils
2 |
3 | import (
4 | "bytes"
5 | "crypto/aes"
6 | "crypto/cipher"
7 | )
8 |
9 | // AesEncrypt AES加密 | key长度为 16 字节才能加密成功
10 | func AesEncrypt(origData, key []byte) ([]byte, error) {
11 | block, err := aes.NewCipher(key)
12 | if err != nil {
13 | return nil, err
14 | }
15 | blockSize := block.BlockSize()
16 | origData = PKCS5Padding(origData, blockSize)
17 | blockMode := cipher.NewCBCEncrypter(block, key[:blockSize])
18 | encrypted := make([]byte, len(origData))
19 | blockMode.CryptBlocks(encrypted, origData)
20 | return encrypted, nil
21 | }
22 |
23 | func PKCS5Padding(ciphertext []byte, blockSize int) []byte {
24 | padding := blockSize - len(ciphertext)%blockSize
25 | padText := bytes.Repeat([]byte{byte(padding)}, padding)
26 | return append(ciphertext, padText...)
27 | }
28 |
29 | func AesDecrypt(crypted, key []byte) ([]byte, error) {
30 | block, err := aes.NewCipher(key)
31 | if err != nil {
32 | return nil, err
33 | }
34 | blockSize := block.BlockSize()
35 | blockMode := cipher.NewCBCDecrypter(block, key[:blockSize])
36 | origData := make([]byte, len(crypted))
37 | blockMode.CryptBlocks(origData, crypted)
38 | origData = PKCS5UnPadding(origData)
39 | return origData, nil
40 | }
41 |
42 | func PKCS5UnPadding(origData []byte) []byte {
43 | length := len(origData)
44 | // 去掉最后一个字节 unPadding 次
45 | unPadding := int(origData[length-1])
46 | if unPadding < 1 || unPadding > 32 {
47 | unPadding = 0
48 | }
49 | return origData[:(length - unPadding)]
50 | }
51 |
--------------------------------------------------------------------------------
/codes/flag_library/subcommand/subcommand.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "errors"
5 | "flag"
6 | "fmt"
7 | "os"
8 | )
9 |
10 | func NewGreetCommand() *GreetCommand {
11 | gc := &GreetCommand{
12 | fs: flag.NewFlagSet("greet", flag.ContinueOnError),
13 | }
14 |
15 | gc.fs.StringVar(&gc.name, "name", "World", "name of the person to be greeted")
16 |
17 | return gc
18 | }
19 |
20 | type GreetCommand struct {
21 | fs *flag.FlagSet
22 |
23 | name string
24 | }
25 |
26 | func (g *GreetCommand) Name() string {
27 | return g.fs.Name()
28 | }
29 |
30 | func (g *GreetCommand) Init(args []string) error {
31 | return g.fs.Parse(args)
32 | }
33 |
34 | func (g *GreetCommand) Run() error {
35 | fmt.Println("Hello", g.name, "!")
36 | return nil
37 | }
38 |
39 | type Runner interface {
40 | Init([]string) error
41 | Run() error
42 | Name() string
43 | }
44 |
45 | func root(args []string) error {
46 | if len(args) < 1 {
47 | return errors.New("You must pass a sub-command")
48 | }
49 |
50 | cmds := []Runner{
51 | NewGreetCommand(),
52 | }
53 |
54 | subcommand := os.Args[1]
55 |
56 | for _, cmd := range cmds {
57 | if cmd.Name() == subcommand {
58 | cmd.Init(os.Args[2:])
59 | return cmd.Run()
60 | }
61 | }
62 |
63 | return fmt.Errorf("Unknown subcommand: %s", subcommand)
64 | }
65 |
66 | func main() {
67 | if err := root(os.Args[1:]); err != nil {
68 | fmt.Println(err)
69 | os.Exit(1)
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/codes/trace_span/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/binary"
5 | "fmt"
6 | "math/rand"
7 | "net"
8 | "strconv"
9 | "strings"
10 | "time"
11 | )
12 |
13 | func main() {
14 | spanId := GenerateSpanID(GetLocalIP().String())
15 | traceId := GenerateTraceID(GetLocalIP().String())
16 | fmt.Println("traceId: " + traceId, "spanId: " + spanId)
17 |
18 | }
19 |
20 |
21 |
22 | func GenerateSpanID(addr string) string {
23 | strAddr := strings.Split(addr, ":")
24 | ip := strAddr[0]
25 | ipLong, _ := Ip2Long(ip)
26 | times := uint64(time.Now().UnixNano())
27 | spanId := ((times ^ uint64(ipLong)) << 32) | uint64(rand.Int31())
28 | return strconv.FormatUint(spanId, 16)
29 | }
30 |
31 | func GenerateTraceID(addr string) string {
32 | strAddr := strings.Split(addr, ":")
33 | ip := strAddr[0]
34 | ipLong, _ := Ip2Long(ip)
35 | times := uint64(time.Now().UnixNano())
36 | traceId := ((times ^ uint64(ipLong)) << 32) | uint64(rand.Int31())
37 | return strconv.FormatUint(traceId, 16)
38 | }
39 |
40 | func Ip2Long(ip string) (uint32, error) {
41 | ipAddr, err := net.ResolveIPAddr("ip", ip)
42 | if err != nil {
43 | return 0, err
44 | }
45 | return binary.BigEndian.Uint32(ipAddr.IP.To4()), nil
46 | }
47 |
48 | func GetLocalIP() net.IP {
49 | addrs, err := net.InterfaceAddrs()
50 | if err != nil {
51 | return net.IPv4zero
52 | }
53 | for _, a := range addrs {
54 | if ipnet, ok := a.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
55 | if ip := ipnet.IP.To4(); ip != nil {
56 | return ipnet.IP
57 | }
58 | }
59 | }
60 | return net.IPv4zero
61 | }
62 |
--------------------------------------------------------------------------------
/cmd/es/query_with_must_not.shell:
--------------------------------------------------------------------------------
1 | curl -XGET --header 'Content-Type: application/json' {$es_server_host[:$port]}/{$index_name}/_search -d '{
2 | "from":0,
3 | "query":{
4 | "bool":{
5 | "must":[
6 | {
7 | "terms":{
8 | "businessId":[
9 | 1,
10 | 2,
11 | 3
12 | ]
13 | }
14 | },
15 | {
16 | "terms":{
17 | "departmentId":[
18 | 0,
19 | 1,
20 | 2,
21 | 3,
22 | 4,
23 | 5,
24 | 6,
25 | 7,
26 | 8,
27 | 9,
28 | 10
29 | ]
30 | }
31 | },
32 | {
33 | "term":{
34 | "performanceType":1
35 | }
36 | },
37 | {
38 | "bool":{
39 | "must_not":{
40 | "match":{
41 | "contactId":""
42 | }
43 | }
44 | }
45 | }
46 | ]
47 | }
48 | },
49 | "size":10
50 | }'
51 |
--------------------------------------------------------------------------------
/codes/http_client_with_rate/http_rl_client.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "net/http"
7 | "time"
8 |
9 | "golang.org/x/time/rate"
10 | )
11 |
12 | //RLHTTPClient Rate Limited HTTP Client
13 | type RLHTTPClient struct {
14 | client *http.Client
15 | Ratelimiter *rate.Limiter
16 | }
17 |
18 | //Do dispatches the HTTP request to the network
19 | func (c *RLHTTPClient) Do(req *http.Request) (*http.Response, error) {
20 | // Comment out the below 5 lines to turn off ratelimiting
21 | ctx := context.Background()
22 | err := c.Ratelimiter.Wait(ctx) // This is a blocking call. Honors the rate limit
23 | if err != nil {
24 | return nil, err
25 | }
26 | resp, err := c.client.Do(req)
27 | if err != nil {
28 | return nil, err
29 | }
30 | return resp, nil
31 | }
32 |
33 | //NewClient return http client with a ratelimiter
34 | func NewClient(rl *rate.Limiter) *RLHTTPClient {
35 | c := &RLHTTPClient{
36 | client: http.DefaultClient,
37 | Ratelimiter: rl,
38 | }
39 | return c
40 | }
41 |
42 | func main() {
43 | rl := rate.NewLimiter(rate.Every(10*time.Second), 50) // 50 request every 10 seconds
44 | c := NewClient(rl)
45 | reqURL := "https://api.btcmarkets.net/v3/markets/BTC-AUD/ticker"
46 | req, _ := http.NewRequest("GET", reqURL, nil)
47 | for i := 0; i < 300; i++ {
48 | resp, err := c.Do(req)
49 | if err != nil {
50 | fmt.Println(err.Error())
51 | fmt.Println(resp.StatusCode)
52 | return
53 | }
54 | if resp.StatusCode == 429 {
55 | fmt.Printf("Rate limit reached after %d requests", i)
56 | return
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/codes/unsafe/use_sizeof.go:
--------------------------------------------------------------------------------
1 |
2 |
3 | // ClientConn in other package
4 | //type ClientConn struct {
5 | // ctx context.Context
6 | // cancel context.CancelFunc
7 | //
8 | // target string
9 | // parsedTarget resolver.Target
10 | // authority string
11 | // dopts dialOptions
12 | // csMgr *connectivityStateManager
13 | //
14 | // balancerBuildOpts balancer.BuildOptions
15 | // blockingpicker *pickerWrapper
16 | //
17 | // mu sync.RWMutex
18 | // resolverWrapper *ccResolverWrapper
19 | // sc *ServiceConfig
20 | // conns map[*addrConn]struct{}
21 | // // Keepalive parameter can be updated if a GoAway is received.
22 | // mkp keepalive.ClientParameters
23 | // curBalancerName string
24 | // balancerWrapper *ccBalancerWrapper
25 | // retryThrottler atomic.Value
26 | //
27 | // firstResolveEvent *grpcsync.Event
28 | //
29 | // channelzID int64 // channelz unique identification number
30 | // czData *channelzData
31 | //}
32 |
33 |
34 | var (
35 | Target *string
36 | )
37 |
38 | func (cm *ClientConnManager) Call(method string, in interface{}, out interface{}) error {
39 | ctx, cancel := context.WithTimeout(context.Background(), 6*time.Second)
40 | defer cancel()
41 | cc, err := cm.getClientConn()
42 | if err != nil {
43 | return err
44 | }
45 | // use unsafe.Pointer and uintptr get unexported field parsedTarget
46 | Target = (* string)(unsafe.Pointer(uintptr(unsafe.Pointer(cc)) + unsafe.Sizeof(context.TODO()) +
47 | unsafe.Sizeof(* new(context.CancelFunc)) + unsafe.Sizeof("1")))
48 |
49 | return cc.Invoke(ctx, method, in, out)
50 | }
51 |
--------------------------------------------------------------------------------
/codes/byte_order/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/binary"
5 | "fmt"
6 | "unsafe"
7 | )
8 |
9 | const INT_SIZE = int(unsafe.Sizeof(0)) //64位操作系统,8 bytes
10 |
11 | //判断我们系统中的字节序类型
12 | func systemEdian() {
13 |
14 | var i = 0x01020304
15 | fmt.Println("&i:",&i)
16 | bs := (*[INT_SIZE]byte)(unsafe.Pointer(&i))
17 |
18 | if bs[0] == 0x04 {
19 | fmt.Println("system edian is little endian")
20 | } else {
21 | fmt.Println("system edian is big endian")
22 | }
23 | fmt.Printf("temp: 0x%x,%v\n",bs[0],&bs[0])
24 | fmt.Printf("temp: 0x%x,%v\n",bs[1],&bs[1])
25 | fmt.Printf("temp: 0x%x,%v\n",bs[2],&bs[2])
26 | fmt.Printf("temp: 0x%x,%v\n",bs[3],&bs[3])
27 |
28 | }
29 |
30 |
31 | func testBigEndian() {
32 |
33 | var testInt int32 = 0x01020304
34 | fmt.Printf("%d use big endian: \n", testInt)
35 | testBytes := make([]byte, 4)
36 | binary.BigEndian.PutUint32(testBytes, uint32(testInt))
37 | fmt.Println("int32 to bytes:", testBytes)
38 | fmt.Printf("int32 to bytes: %x \n", testBytes)
39 |
40 | convInt := binary.BigEndian.Uint32(testBytes)
41 | fmt.Printf("bytes to int32: %d\n\n", convInt)
42 | }
43 |
44 | func testLittleEndian() {
45 |
46 | var testInt int32 = 0x01020304
47 | fmt.Printf("%x use little endian: \n", testInt)
48 | var testBytes []byte = make([]byte, 4)
49 | binary.LittleEndian.PutUint32(testBytes, uint32(testInt))
50 | fmt.Printf("int32 to bytes: %x \n", testBytes)
51 |
52 | convInt := binary.LittleEndian.Uint32(testBytes)
53 | fmt.Printf("bytes to int32: %d\n\n", convInt)
54 | }
55 |
56 | func main() {
57 | systemEdian()
58 | fmt.Println("")
59 | testBigEndian()
60 | testLittleEndian()
61 | }
62 |
--------------------------------------------------------------------------------
/codes/learntime/code.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "time"
6 | )
7 |
8 | func main() {
9 | timeStr := "2023-10-11 23:34:45"
10 | comparedTime, _ := parseInLocalLocation(timeStr, TimeLayoutDefault)
11 | fmt.Printf("%s晚于当前时间: %v\n", timeStr, isAfterNow(comparedTime))
12 | originTime := comparedTime
13 | aWeekLaterTime := weekLater(originTime, 1)
14 | fmt.Printf("初始时间: %v, 一周后的时间: %v\n", originTime, aWeekLaterTime)
15 | }
16 |
17 | const TimeLayoutDefault = "2006-01-02 15:04:05"
18 | const TimeLayoutDate = "20060102"
19 | const TimeLayoutDateWithHyphen = "2006-01-02"
20 |
21 |
22 | // parseFromString 解析字符串时间
23 | func parseFromString(timeStr, layout string) (timeObj time.Time, err error) {
24 | timeObj, err = time.Parse(layout, timeStr)
25 | return
26 | }
27 |
28 | // parseInLocalLocation 用本地时区解析字符串时间
29 | func parseInLocalLocation(timeStr, layout string) (timeObj time.Time, err error) {
30 | timeObj, err = time.ParseInLocation(layout, timeStr, time.Local)
31 | return
32 | }
33 |
34 | // parseInSpainLocation 用西班牙时区解析字符串时间
35 | func parseInSpainLocation(timeStr, layout string) (timeObj time.Time, err error) {
36 | loc, _ := time.LoadLocation("Europe/Madrid")
37 | timeObj, err = time.ParseInLocation(layout, timeStr, loc)
38 | return
39 | }
40 |
41 | func isBeforeNow(comparedTime time.Time) bool {
42 | return comparedTime.Before(time.Now())
43 | }
44 |
45 | func isAfterNow(comparedTime time.Time) bool {
46 | return comparedTime.After(time.Now())
47 | }
48 |
49 | func weekLater(originTime time.Time, w int) time.Time {
50 | return originTime.AddDate(0, 0, w * 7)
51 | }
52 |
53 | func daysEarlier(originTime time.Time, d int) time.Time {
54 | return originTime.AddDate(0, 0, -d)
55 | }
56 |
--------------------------------------------------------------------------------
/codes/proconsumer/tests/testjob.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bufio"
5 | "example.com/proconsumer"
6 | "fmt"
7 | "os"
8 | "strings"
9 | "time"
10 | )
11 |
12 | func main() {
13 | ts := proconsumer.NewTaskScheduler(ProducerFunc, ConsumerFunc, 10)
14 | err := ts.Execute()
15 | fmt.Println(err, "11111")
16 | }
17 |
18 | func ProducerFunc(producer *proconsumer.Producer) error {
19 | dir, _ := os.Getwd()
20 | fmt.Println(dir)
21 | // 自己试一下故意写错文件路径
22 | file, err := os.Open(dir + "/tests/file.txt")
23 | if err != nil {
24 | return err
25 | }
26 | defer file.Close()
27 |
28 | scanner := bufio.NewScanner(file)
29 | i := 0
30 | for scanner.Scan() {
31 |
32 | select {
33 | case <-producer.CancelCtx.Done():
34 | fmt.Println("producer end")
35 | return nil
36 | default:
37 | }
38 | // TODO 制造消息
39 | str := scanner.Text()
40 | i += 1
41 | if i % 100 == 0 {
42 | // 模拟耗时查询
43 | time.Sleep(time.Second * 3)
44 | }
45 | // TODO 制造错误情况, 测试下退出
46 | //if strings.HasPrefix(str, "rt") {
47 | // err = fmt.Errorf("producer Error")
48 | // return err
49 | //}
50 | // 发送数据给consumer
51 | producer.DataCh <- str
52 | }
53 |
54 | close(producer.DataCh) //查询完成关闭通道
55 |
56 | return nil
57 | }
58 |
59 | func ConsumerFunc(consumer *proconsumer.Consumer) error {
60 | for {
61 |
62 | select {
63 | case <-consumer.CancelCtx.Done():
64 | fmt.Println("consumer cancel end")
65 | return nil
66 | case v, ok := <-consumer.DataCh:
67 | if !ok {
68 | fmt.Println("consumer execution end")
69 | return nil
70 | }
71 | err := Do(v)
72 | if err != nil {
73 | return err
74 | }
75 | }
76 |
77 | }
78 |
79 | return nil
80 | }
81 |
82 | func Do(v interface{}) error {
83 | val, _ := v.(string)
84 | // TODO 制造错误情况, 测试下退出
85 | if strings.HasPrefix(val, "rt") {
86 | return fmt.Errorf("worker error")
87 | }
88 | fmt.Println(strings.ToUpper(val))
89 | return nil
90 | }
91 |
--------------------------------------------------------------------------------
/codes/distributed_lock/etcd/etcd_lock.go:
--------------------------------------------------------------------------------
1 | package etcd
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "log"
7 | "time"
8 |
9 | "github.com/coreos/etcd/clientv3"
10 | "go.etcd.io/etcd/clientv3/concurrency"
11 | )
12 |
13 | func NewClient(endpoints []string) (*clientv3.Client, error){
14 | ctx, cancel := context.WithCancel(context.Background())
15 | cli, err := clientv3.New(clientv3.Config{
16 | Endpoints: endpoints,
17 | DialTimeout: 200 * time.Millisecond,
18 | Context: ctx,
19 | })
20 | if err != nil {
21 | cancel()
22 | return nil, err
23 | }
24 |
25 | return cli, err
26 | }
27 |
28 | // 测试锁
29 | func UseLock(cli *clientv3.Client, name string) {
30 | // 创建一个用于获取锁的Session
31 | s, err:= concurrency.NewSession(cli)
32 | if err != nil {
33 | log.Fatal(err)
34 | }
35 | defer s.Close()
36 | // 在指定Key上创建一个互斥锁
37 | l := concurrency.NewMutex(s, "/distributed-lock/")
38 | ctx := context.TODO() // 也可以通过 ctx 指定获取锁等待的超时时间,实现LockWithTimeout
39 | // 获得锁 / 阻塞等待知道获得锁
40 | if err := l.Lock(ctx); err != nil {
41 | log.Fatal(err)
42 | }
43 |
44 | fmt.Println("Acquired lock for ", name)
45 | fmt.Println("Do some work in", name)
46 | time.Sleep(5 * time.Second)
47 | if err := l.Unlock(ctx); err != nil {
48 | log.Fatal(err)
49 | }
50 | fmt.Println("Released lock for ", name)
51 | }
52 |
53 | // 使用TryLock, 获取不到则退出
54 | func UseTryLock(cli *clientv3.Client, name string) {
55 | // 创建一个用于获取锁的Session
56 | s, err:= concurrency.NewSession(cli)
57 | if err != nil {
58 | log.Fatal(err)
59 | }
60 | defer s.Close()
61 | // 在指定Key上创建一个互斥锁
62 | l := concurrency.NewMutex(s, "/distributed-lock/")
63 | ctx := context.TODO() // 也可以通过 ctx 指定获取锁等待的超时时间,实现LockWithTimeout
64 | // 获得锁 / 阻塞等待知道获得锁
65 | if err := l.Lock(ctx); err != nil {
66 | log.Fatal(err)
67 | }
68 |
69 | fmt.Println("Acquired lock for ", name)
70 | fmt.Println("Do some work in", name)
71 | time.Sleep(5 * time.Second)
72 | if err := l.Unlock(ctx); err != nil {
73 | log.Fatal(err)
74 | }
75 | fmt.Println("Released lock for ", name)
76 | }
77 |
--------------------------------------------------------------------------------
/codes/tag_picker/query_builer.go:
--------------------------------------------------------------------------------
1 | package tag_picker
2 |
3 | import (
4 | "context"
5 | "sync"
6 | "time"
7 | )
8 |
9 | const (
10 | TAG_ORDER_NUM = "order_num"
11 | )
12 |
13 | // 对外提供的批量查询用户标签值的方法
14 | func BulkQueryUserTagValue(tagNames []string, userId int64, queryArgs ...interface{}) (tagValuePairs []*TagValuePair) {
15 | tagCount := len(tagNames)
16 | if tagCount < 1 {
17 | return
18 | }
19 | wg := &sync.WaitGroup{}
20 | wg.Add(tagCount)
21 | tagValueCh := make(chan *TagValuePair, tagCount) // 用于接收所有Picker查到的标签值的Channel
22 | ctx, _ := context.WithTimeout(context.Background(), time.Minute) // 设置执行标签值查找的超时时间
23 | for _, tagName := range tagNames {
24 | go queryTagValue(ctx, wg, tagName, userId, tagValueCh, queryArgs...)
25 | }
26 | wg.Wait()
27 | close(tagValueCh) // 先关闭通道 方便下面for range不发生阻塞, 从channel中读完值即退出
28 | tagValuePairs = make([]*TagValuePair, 0)
29 | for tagValue := range tagValueCh {
30 | if tagValue.Value != nil {
31 | tagValuePairs = append(tagValuePairs, tagValue)
32 | }
33 | }
34 |
35 | return tagValuePairs
36 | }
37 |
38 | type TagValuePair struct {
39 | Name string `json:"tag_name"`
40 | Value interface{} `json:"tag_value"`
41 | }
42 |
43 |
44 | func queryTagValue(ctx context.Context, wg *sync.WaitGroup, tagName string, userId int64, tagValueCh chan *TagValuePair, queryArgs ...interface{}) {
45 | defer wg.Done()
46 | tagPicker := resolveTagPicker(tagName)
47 | if tagPicker == nil {
48 | log.Error("未识别的业务标签", common.ErrUnknownBusinessTag)
49 | return
50 | }
51 | go tagPicker.PickTagValueForUser(userId, queryArgs...)
52 | select {
53 | case <- ctx.Done(): // 超时返回
54 | return
55 | case tagValue := <- tagPicker.Notify(): // 接收标签值
56 | TagValuePair := &TagValuePair{
57 | Name: tagName,
58 | Value: tagValue,
59 | }
60 | tagValueCh <- TagValuePair
61 |
62 | return
63 | }
64 | }
65 |
66 | // 根据标签名解析出对应的TagPicker
67 | func resolveTagPicker (tagName string) Picker {
68 | switch tagName {
69 | case TAG_ORDER_NUM:
70 | return &OrderNumTagPicker{
71 | TagName: tagName,
72 | ValueCh: make(chan interface{}),
73 | }
74 | default:
75 | return nil
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/codes/cyclic_barrier/h2o_factory.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "github.com/marusama/cyclicbarrier"
7 | "golang.org/x/sync/semaphore"
8 | "math/rand"
9 | "sort"
10 | "sync"
11 | "time"
12 | )
13 |
14 | // 定义水分子合成的辅助数据结构
15 | type H2O struct {
16 | semaH *semaphore.Weighted // 氢原子的信号量
17 | semaO *semaphore.Weighted // 氧原子的信号量
18 | b cyclicbarrier.CyclicBarrier // 循环栅栏,用来控制合成
19 | }
20 |
21 |
22 | func New() *H2O {
23 | return &H2O{
24 | semaH: semaphore.NewWeighted(2), //氢原子需要两个
25 | semaO: semaphore.NewWeighted(1), // 氧原子需要一个
26 | b: cyclicbarrier.New(3), // 需要三个原子才能合成
27 | }
28 | }
29 |
30 | // 用来存放水分子结果的channel
31 | var ch chan string
32 |
33 | // 往channel里存放一个H原子
34 | func releaseHydrogen() {
35 | ch <- "H"
36 | }
37 |
38 | // 往channel里存放一个O原子
39 | func releaseOxygen() {
40 | ch <- "O"
41 | }
42 |
43 | func (h2o *H2O) hydrogen(releaseHydrogen func()) {
44 | h2o.semaH.Acquire(context.Background(), 1)
45 |
46 | releaseHydrogen() // 输出H
47 | h2o.b.Await(context.Background()) //等待栅栏放行
48 | h2o.semaH.Release(1) // 释放氢原子空槽
49 | }
50 |
51 |
52 | func (h2o *H2O) oxygen(releaseOxygen func()) {
53 | h2o.semaO.Acquire(context.Background(), 1)
54 |
55 | releaseOxygen() // 输出O
56 | h2o.b.Await(context.Background()) //等待栅栏放行
57 | h2o.semaO.Release(1) // 释放氢原子空槽
58 | }
59 |
60 |
61 | func main() {
62 | // 300个原子,300个goroutine,每个goroutine并发的产生一个原子
63 | var N = 100
64 | ch = make(chan string, N*3)
65 |
66 | h2o := New()
67 |
68 | // 用来等待所有的goroutine完成
69 | var wg sync.WaitGroup
70 | wg.Add(N * 3)
71 |
72 | // 200个生产氢原子的goroutine
73 | for i := 0; i < 2*N; i++ {
74 | go func() {
75 | time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
76 | h2o.hydrogen(releaseHydrogen)
77 | wg.Done()
78 | }()
79 | }
80 | // 100个生产氧原子的goroutine
81 | for i := 0; i < N; i++ {
82 | go func() {
83 | time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
84 | h2o.oxygen(releaseOxygen)
85 | wg.Done()
86 | }()
87 | }
88 |
89 | //等待所有的goroutine执行完
90 | wg.Wait()
91 |
92 | // 结果中肯定是300个原子
93 | if len(ch) != N*3 {
94 | fmt.Println(fmt.Errorf("expect %d atom but got %d", N*3, len(ch)))
95 | }
96 |
97 | // 每三个原子一组,分别进行检查。要求这一组原子中必须包含两个氢原子和一个氧原子,这样才能正确组成一个水分子。
98 | var s = make([]string, 3)
99 | for i := 0; i < N; i++ {
100 | s[0] = <-ch
101 | s[1] = <-ch
102 | s[2] = <-ch
103 | sort.Strings(s)
104 |
105 |
106 | water := s[0] + s[1] + s[2]
107 | if water != "HHO" {
108 | fmt.Println(fmt.Errorf("expect a water molecule but got %s", water))
109 | }
110 |
111 | fmt.Println(water)
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/codes/aes-go-java-convertor/aes-go/ase.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "crypto/aes"
6 | "crypto/cipher"
7 | "encoding/base64"
8 | "errors"
9 | "fmt"
10 | )
11 |
12 | func Encrypt(key string, val string) (string, error) {
13 | origData := []byte(val)
14 | crypted, err := encrypt(key, origData)
15 | if err != nil {
16 | return "", err
17 | }
18 | return base64.RawURLEncoding.EncodeToString(crypted), nil
19 | }
20 |
21 | func Decrypt(key string, val string) (string, error) {
22 | // base64 decode
23 | decodeBytes, _ := base64.RawURLEncoding.DecodeString(val)
24 | origData, err := decrpt(key, decodeBytes)
25 | if err != nil {
26 | return "", err
27 | }
28 | return string(origData), nil
29 | }
30 |
31 |
32 | func encrypt(key string, origData []byte) ([]byte, error) {
33 | if len(origData) <= 0 {
34 | return nil, errors.New("crypted len is zero")
35 | }
36 | keyBytes := GetKeyBytes(key)
37 | block, err := aes.NewCipher(keyBytes)
38 | if err != nil {
39 | return nil, err
40 | }
41 | blockSize := block.BlockSize()
42 | origData = PKCS5Padding(origData, blockSize)
43 | blockMode := cipher.NewCBCEncrypter(block, keyBytes[:blockSize])
44 | crypted := make([]byte, len(origData))
45 | blockMode.CryptBlocks(crypted, origData)
46 | return crypted, nil
47 | }
48 |
49 | func decrpt(key string, crypted []byte) ([]byte, error) {
50 | if len(crypted) <= 0 {
51 | return nil, errors.New("crypted len is zero")
52 | }
53 | keyBytes := GetKeyBytes(key)
54 | block, err := aes.NewCipher(keyBytes)
55 | if err != nil {
56 | return nil, err
57 | }
58 | blockSize := block.BlockSize()
59 | blockMode := cipher.NewCBCDecrypter(block, keyBytes[:blockSize])
60 | origData := make([]byte, len(crypted))
61 | blockMode.CryptBlocks(origData, crypted)
62 | origData = PKCS5UnPadding(origData)
63 | return origData, nil
64 | }
65 |
66 | func GetKeyBytes(key string) []byte {
67 | keyBytes := []byte(key)
68 | switch l := len(keyBytes); {
69 | case l < 16:
70 | keyBytes = append(keyBytes, make([]byte, 16-l)...)
71 | case l > 16:
72 | keyBytes = keyBytes[:16]
73 | }
74 | return keyBytes
75 | }
76 |
77 | func PKCS5Padding(ciphertext []byte, blockSize int) []byte {
78 | padding := blockSize - len(ciphertext)%blockSize
79 | padtext := bytes.Repeat([]byte{byte(padding)}, padding)
80 | return append(ciphertext, padtext...)
81 | }
82 |
83 | func PKCS5UnPadding(origData []byte) []byte {
84 | length := len(origData)
85 | unpadding := int(origData[length-1])
86 | return origData[:(length - unpadding)]
87 | }
88 |
89 | func main() {
90 | encryptKey := "3wjyxqDPNyrd4QrhxTycRMU4dFN2lCm4"
91 | sign, _ := Encrypt(encryptKey, "37b63ec62ebf8b2e790b8d9829da2ec26f1fad67")
92 | fmt.Println(sign)
93 |
94 | pureString, _ := Decrypt(encryptKey, sign)
95 | fmt.Println(pureString)
96 | }
97 |
--------------------------------------------------------------------------------
/codes/aes-go-java-convertor/aes-java/EncryptDecryptDemo.java:
--------------------------------------------------------------------------------
1 | package com.learnciper;
2 |
3 | import javax.crypto.Cipher;
4 | import javax.crypto.spec.IvParameterSpec;
5 | import javax.crypto.spec.SecretKeySpec;
6 | import java.security.GeneralSecurityException;
7 | import java.util.Arrays;
8 | import java.util.Base64;
9 |
10 | public class EncryptDecryptDemo {
11 |
12 | public byte[] decrypt(String key, byte[] crypted) throws GeneralSecurityException {
13 | byte[] keyBytes = getKeyBytes(key);
14 | byte[] buf = new byte[16];
15 | System.arraycopy(keyBytes, 0, buf, 0, Math.max(keyBytes.length, buf.length));
16 | Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
17 | cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(buf, "AES"), new IvParameterSpec(keyBytes));
18 | return cipher.doFinal(crypted);
19 | }
20 |
21 | private byte[] getKeyBytes(String key) {
22 | byte[] bytes = key.getBytes();
23 | return bytes.length == 16 ? bytes : Arrays.copyOf(bytes, 16);
24 | }
25 |
26 | //加密
27 | public String encrypt(String key, String val) {
28 | try {
29 | byte[] origData = val.getBytes();
30 | byte[] crafted = encrypt(key, origData);
31 | return Base64.getUrlEncoder().withoutPadding().encodeToString(crafted);
32 | } catch (Exception e) {
33 | return "";
34 | }
35 | }
36 |
37 | public String decrypt(String key, String val) throws GeneralSecurityException {
38 | byte[] crypted = Base64.getUrlDecoder().decode(val);
39 | byte[] origData = decrypt(key, crypted);
40 | return new String(origData);
41 | }
42 |
43 | public byte[] encrypt(String key, byte[] origData) throws GeneralSecurityException {
44 | byte[] keyBytes = getKeyBytes(key);
45 | byte[] buf = new byte[16];
46 | System.arraycopy(keyBytes, 0, buf, 0, Math.max(keyBytes.length, buf.length));
47 | Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
48 | cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(buf, "AES"), new IvParameterSpec(keyBytes));
49 | return cipher.doFinal(origData);
50 |
51 | }
52 |
53 | public static void main(String[] args) throws GeneralSecurityException {
54 | String key = "3wjyxqDPNyrd4QrhxTycRMU4dFN2lCm4";
55 | DecryptDemo decryptClz = new DecryptDemo();
56 | String encryptedStr = decryptClz.encrypt(key, "37b63ec62ebf8b2e790b8d9829da2ec26f1fad67");
57 | // 加密前的纯串 37b63ec62ebf8b2e790b8d9829da2ec26f1fad67
58 | String pureStr = decryptClz.decrypt(key, "2D_24XH_IMxtYLPGdgZ37X_5_a4eFTpqT7v1aNwq-74Ko6XDpE54LRz4e5s4ehMc");
59 |
60 | String es = String.format("加密后的串: %s", encryptedStr);
61 | String ds = String.format("解密后的串: %s", pureStr);
62 | System.out.println(es);
63 | System.out.println(ds);
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/codes/httptool/http_util.go:
--------------------------------------------------------------------------------
1 | package httptool
2 |
3 | import (
4 | "io/ioutil"
5 | "log"
6 | "net/http"
7 | "strings"
8 | "time"
9 | )
10 |
11 | func httpRequest(method string, url string, options ...Option) (code int, content string, err error) {
12 | start := time.Now()
13 |
14 | reqOpts := defaultRequestOptions() // 默认的请求选项
15 | for _, opt := range options { // 在reqOpts上应用通过options设置的选项
16 | err = opt.apply(reqOpts); if err != nil {
17 | return
18 | }
19 | }
20 | // 创建请求对象
21 | req, err := http.NewRequest(method, url, strings.NewReader(reqOpts.data))
22 | // 记录请求日志
23 | defer func() {
24 | dur := int64(time.Since(start) / time.Second)
25 | if dur >= 500 {
26 | log.Println("Http"+method, url, "in", reqOpts.data, "out", content, "err", err, "dur/ms", dur)
27 | } else {
28 | log.Println("Http"+method, url, "in", reqOpts.data, "out", content, "err", err, "dur/ms", dur)
29 | }
30 | }()
31 | defer req.Body.Close()
32 |
33 | if len(reqOpts.headers) != 0 { // 设置请求头
34 | for key, value := range reqOpts.headers {
35 | req.Header.Add(key, value)
36 | }
37 | }
38 | if err != nil {
39 | return
40 | }
41 | // 发起请求
42 | client := &http.Client{Timeout: reqOpts.timeout}
43 | resp, error := client.Do(req)
44 | if error != nil {
45 | return 0, "", error
46 | }
47 | // 解析响应
48 | defer resp.Body.Close()
49 | code = resp.StatusCode
50 | result, _ := ioutil.ReadAll(resp.Body)
51 | content = string(result)
52 |
53 | return
54 | }
55 |
56 | // 发起GET请求
57 | func HttpGet(url string, options ...Option) (code int, content string, err error) {
58 | return httpRequest("GET", url, options...)
59 | }
60 |
61 | // 发起POST请求,请求头指定Content-Type: application/json
62 | func HttpJsonPost(url string, data string, timeout time.Duration) (code int, content string, err error) {
63 | headers := map[string]string{
64 | "Content-Type": "application/json",
65 | }
66 | code, content, err = httpRequest(
67 | "POST", url, WithTimeout(timeout), WithHeaders(headers), WithData(data))
68 |
69 | return
70 | }
71 |
72 | // 针对可选的HTTP请求配置项,模仿gRPC使用的Options设计模式实现
73 | type requestOption struct {
74 | timeout time.Duration
75 | data string
76 | headers map[string]string
77 | }
78 |
79 | type Option interface {
80 | apply(option *requestOption) error
81 | }
82 |
83 | type optionFunc func(option *requestOption) error
84 |
85 | func (f optionFunc) apply(opts *requestOption) error {
86 | return f(opts)
87 | }
88 |
89 |
90 | func defaultRequestOptions() *requestOption {
91 | return &requestOption{ // 默认请求选项
92 | timeout: 5 * time.Second,
93 | data: "",
94 | headers: nil,
95 | }
96 | }
97 |
98 | func WithTimeout(timeout time.Duration) Option {
99 | return optionFunc(func(opts *requestOption) (err error) {
100 | opts.timeout, err = timeout, nil
101 | return
102 | })
103 | }
104 |
105 | func WithHeaders(headers map[string]string) Option {
106 | return optionFunc(func(opts *requestOption) (err error) {
107 | for k, v := range headers {
108 | opts.headers[k] = v
109 | }
110 | return
111 | })
112 | }
113 |
114 | func WithData(data string) Option {
115 | return optionFunc(func(opts *requestOption) (err error) {
116 | opts.data, err = data, nil
117 | return
118 | })
119 | }
120 |
--------------------------------------------------------------------------------
/codes/gen_token/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "crypto/aes"
6 | "crypto/cipher"
7 | "crypto/md5"
8 | "encoding/binary"
9 | "encoding/hex"
10 | "fmt"
11 | "time"
12 | )
13 |
14 | const (
15 | aesKEY = "justTest!@#$test" // must be 16 characters
16 | md5Len = 4 //MD5 part size in access token
17 | aesLen = 16 //bytes after being encrypted by aes
18 | )
19 |
20 | func main() {
21 | token := genAccessToken(1234567)
22 | println(token)
23 |
24 | println(ParseAccessToken(token))
25 | }
26 |
27 | // generate access token
28 | // + means concatenate
29 | // Token = Hex(MD5(uid + time)[0:4] + AES(uid + time))
30 | // a string with length of 40
31 | func genAccessToken(uid int64) string {
32 | byteInfo := make([]byte, 12)
33 | binary.BigEndian.PutUint64(byteInfo, uint64(uid))
34 | binary.BigEndian.PutUint32(byteInfo[8:], uint32(time.Now().Unix()))
35 | encodeByte, err := AesEncrypt(byteInfo, []byte(aesKEY)) //returned slice's size is 16
36 | if err != nil {
37 | fmt.Println("desc: ", "genAccessToken-AesEncrypt", "error: ", err, "byteInfo: ", byteInfo)
38 | }
39 | md5Byte := md5.Sum(byteInfo)
40 | data := append(md5Byte[0:md5Len], encodeByte...)
41 |
42 | return hex.EncodeToString(data)
43 | }
44 |
45 | // parse uid from access token
46 | func ParseAccessToken(accessToken string) (uid uint64) {
47 | if len(accessToken) != 2*(md5Len+aesLen) {
48 | fmt.Println("log_desc", "len(accessToken)", "length", len(accessToken))
49 | return
50 | }
51 | encodeStr := accessToken[md5Len*2:]
52 | data, e := hex.DecodeString(encodeStr)
53 | if e != nil {
54 | fmt.Println("log_desc", "ParseAccessToken-DecodeString)", "error", e, "accessToken", encodeStr)
55 | return
56 | }
57 | decodeByte, e := AesDecrypt(data, []byte(aesKEY)) //忽略错误
58 | if e != nil {
59 | fmt.Println("log_desc", "ParseAccessToken-AesDecrypt)", "error", e, "data", data)
60 | }
61 | uid = binary.BigEndian.Uint64(decodeByte)
62 |
63 | return uid
64 | }
65 |
66 | //key is 16 bytes
67 | func AesEncrypt(origData, key []byte) ([]byte, error) {
68 | block, err := aes.NewCipher(key)
69 | if err != nil {
70 | return nil, err
71 | }
72 | blockSize := block.BlockSize()
73 | origData = PKCS5Padding(origData, blockSize)
74 | blockMode := cipher.NewCBCEncrypter(block, key[:blockSize])
75 | crypted := make([]byte, len(origData))
76 |
77 | blockMode.CryptBlocks(crypted, origData)
78 | return crypted, nil
79 | }
80 |
81 | func PKCS5Padding(ciphertext []byte, blockSize int) []byte {
82 | padding := blockSize - len(ciphertext)%blockSize
83 | padtext := bytes.Repeat([]byte{byte(padding)}, padding)
84 | return append(ciphertext, padtext...)
85 | }
86 |
87 | func AesDecrypt(crypted, key []byte) ([]byte, error) {
88 | block, err := aes.NewCipher(key)
89 | if err != nil {
90 | return nil, err
91 | }
92 | blockSize := block.BlockSize()
93 | blockMode := cipher.NewCBCDecrypter(block, key[:blockSize])
94 | origData := make([]byte, len(crypted))
95 | blockMode.CryptBlocks(origData, crypted)
96 | origData = PKCS5UnPadding(origData)
97 |
98 | return origData, nil
99 | }
100 |
101 | func PKCS5UnPadding(origData []byte) []byte {
102 | length := len(origData)
103 |
104 | unPadding := int(origData[length-1])
105 | if unPadding < 1 || unPadding > 32 {
106 | unPadding = 0
107 | }
108 | return origData[:(length - unPadding)]
109 | }
110 |
--------------------------------------------------------------------------------
/codes/prevent_over_concurrency/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "golang.org/x/sync/semaphore"
7 | "golang.org/x/time/rate"
8 | "math/rand"
9 | "sync"
10 | "time"
11 | )
12 |
13 | func main() {
14 | // 错误的并发控制
15 | badConcurrency()
16 | // 使用WaitGroup防止并发超限
17 | //useWaitGroup()
18 | // 使用Semaphore防止并发超限
19 | //useSemaphore()
20 | // 使用golang标准库的限流器
21 | //useRateLimit()
22 | // 使用通道实现的生产者和消费者队列
23 | useChannel()
24 | }
25 |
26 | func useWaitGroup() {
27 |
28 | batchSize := 50
29 | for {
30 | data, _ := queryDataWithSizeN(batchSize)
31 | if len(data) == 0 {
32 | fmt.Println("End of all data")
33 | break
34 | }
35 | var wg sync.WaitGroup
36 | for _, item := range data {
37 | wg.Add(1)
38 | go func(i int) {
39 | doSomething(i)
40 | wg.Done()
41 | }(item)
42 | }
43 | wg.Wait()
44 |
45 | fmt.Println("Next bunch of data")
46 | }
47 | }
48 |
49 | func useSemaphore() {
50 | var concurrentNum int64 = 10
51 | var weight int64 = 1
52 | var batchSize int = 50
53 | s := semaphore.NewWeighted(concurrentNum)
54 | for {
55 | data, _ := queryDataWithSizeN(batchSize)
56 | if len(data) == 0 {
57 | fmt.Println("End of all data")
58 | break
59 | }
60 |
61 | for _, item := range data {
62 | s.Acquire(context.Background(), weight)
63 | go func(i int) {
64 | doSomething(i)
65 | s.Release(weight)
66 | }(item)
67 | }
68 |
69 | }
70 | }
71 |
72 | func useRateLimit() {
73 | limiter := rate.NewLimiter(rate.Every(1*time.Second), 50)
74 | batchSize := 50
75 | for {
76 | data, _ :=queryDataWithSizeN(batchSize)
77 | if len(data) == 0 {
78 | fmt.Println("End of all data")
79 | break
80 | }
81 |
82 | for _, item := range data {
83 | // blocking until the bucket have sufficient token
84 | err := limiter.Wait(context.Background())
85 | if err != nil {
86 | fmt.Println("Error: ", err)
87 | return
88 | }
89 | go func(i int) {
90 | doSomething(i)
91 | }(item)
92 | }
93 | }
94 | }
95 |
96 | func useChannel() {
97 | batchSize := 50
98 | dataChan := make(chan int)
99 | var wg sync.WaitGroup
100 | wg.Add(batchSize + 1)
101 | // 生产者
102 | go func() {
103 | for {
104 | data, _ := queryDataWithSizeN(batchSize)
105 | if len(data) == 0 {
106 | break
107 | }
108 | for _, item := range data {
109 | dataChan <- item
110 | }
111 | }
112 | close(dataChan)
113 | wg.Done()
114 | }()
115 | // 消费者
116 | go func() {
117 | for i := 0; i < 50; i++ {
118 | go func() {
119 | for {
120 | select {
121 | case v, ok := <- dataChan:
122 | if !ok {
123 | wg.Done()
124 | return
125 | }
126 | doSomething(v)
127 | }
128 | }
129 | }()
130 | }
131 | }()
132 |
133 | wg.Wait()
134 | }
135 |
136 | func badConcurrency() {
137 | batchSize := 50
138 | for {
139 | data, _ := queryDataWithSizeN(batchSize)
140 | if len(data) == 0 {
141 | break
142 | }
143 |
144 | for _, item := range data {
145 | go func(i int) {
146 | doSomething(i)
147 | }(item)
148 | }
149 |
150 | time.Sleep(time.Second * 1)
151 | }
152 | }
153 |
154 | func doSomething(i int) {
155 | time.Sleep(2 * time.Second)
156 | fmt.Println("End:", i)
157 | }
158 |
159 | func queryDataWithSizeN(size int) (dataList []int, err error) {
160 | rand.Seed(time.Now().Unix())
161 | dataList = rand.Perm(size)
162 | return
163 | }
164 |
--------------------------------------------------------------------------------
/codes/holmes_notes/get_process_info.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 |
4 | func getUsageNormal() (float64, float64, int, int, error) {
5 | p, err := process.NewProcess(int32(os.Getpid()))
6 | if err != nil {
7 | return 0, 0, 0, 0, err
8 | }
9 |
10 | cpuPercent, err := p.Percent(time.Second)
11 | if err != nil {
12 | return 0, 0, 0, 0, err
13 | }
14 |
15 | // The default percent is from all cores, multiply by runtime.NumCPU()
16 | // but it's inconvenient to calculate the proper percent
17 | // here we divide by core number, so we can set a percent bar more intuitively
18 | cpuPercent = cpuPercent / float64(runtime.NumCPU())
19 |
20 | mem, err := p.MemoryPercent()
21 | if err != nil {
22 | return 0, 0, 0, 0, err
23 | }
24 |
25 | gNum := runtime.NumGoroutine()
26 |
27 | tNum := getThreadNum()
28 |
29 | return cpuPercent, float64(mem), gNum, tNum, nil
30 | }
31 |
32 | func getUsageCGroup() (float64, float64, int, int, error) {
33 | p, err := process.NewProcess(int32(os.Getpid()))
34 | if err != nil {
35 | return 0, 0, 0, 0, err
36 | }
37 |
38 | cpuPercent, err := p.Percent(time.Second)
39 | if err != nil {
40 | return 0, 0, 0, 0, err
41 | }
42 |
43 | cpuPeriod, err := readUint("/sys/fs/cgroup/cpu/cpu.cfs_period_us")
44 | if err != nil {
45 | return 0, 0, 0, 0, err
46 | }
47 |
48 | cpuQuota, err := readUint("/sys/fs/cgroup/cpu/cpu.cfs_quota_us")
49 | if err != nil {
50 | return 0, 0, 0, 0, err
51 | }
52 | cpuCore := float64(cpuQuota) / float64(cpuPeriod)
53 |
54 | // the same with physical machine
55 | // need to divide by core number
56 | cpuPercent = cpuPercent / cpuCore
57 | mem, err := p.MemoryInfo()
58 | p.MemoryPercent()
59 | if err != nil {
60 | return 0, 0, 0, 0, err
61 | }
62 |
63 | memLimit, err := getCGroupMemoryLimit()
64 | if err != nil {
65 | return 0, 0, 0, 0, err
66 | }
67 | // mem.RSS / cgroup limit in bytes
68 | memPercent := float64(mem.RSS) * 100 / float64(memLimit)
69 |
70 | gNum := runtime.NumGoroutine()
71 |
72 | tNum := getThreadNum()
73 |
74 | return cpuPercent, memPercent, gNum, tNum, nil
75 | }
76 |
77 | func getCGroupMemoryLimit() (uint64, error) {
78 | usage, err := readUint("/sys/fs/cgroup/memory/memory.limit_in_bytes")
79 | if err != nil {
80 | return 0, err
81 | }
82 | machineMemory, err := mem.VirtualMemory()
83 | if err != nil {
84 | return 0, err
85 | }
86 | limit := uint64(math.Min(float64(usage), float64(machineMemory.Total)))
87 | return limit, nil
88 | }
89 |
90 | func readUint(path string) (uint64, error) {
91 | v, err := ioutil.ReadFile(path)
92 | if err != nil {
93 | return 0, err
94 | }
95 | return parseUint(strings.TrimSpace(string(v)), 10, 64)
96 | }
97 |
98 | func parseUint(s string, base, bitSize int) (uint64, error) {
99 | v, err := strconv.ParseUint(s, base, bitSize)
100 | if err != nil {
101 | intValue, intErr := strconv.ParseInt(s, base, bitSize)
102 | // 1. Handle negative values greater than MinInt64 (and)
103 | // 2. Handle negative values lesser than MinInt64
104 | if intErr == nil && intValue < 0 {
105 | return 0, nil
106 | } else if intErr != nil &&
107 | intErr.(*strconv.NumError).Err == strconv.ErrRange &&
108 | intValue < 0 {
109 | return 0, nil
110 | }
111 | return 0, err
112 | }
113 | return v, nil
114 | }
115 |
116 | func getThreadNum() int {
117 | return pprof.Lookup("threadcreate").Count()
118 | }
119 |
120 | func main() {
121 | // in machine use getUsageNormal
122 | fmt.Println(getUsageNormal())
123 | // in docker us getUsageCGroup
124 | fmt.Println(getUsageCGroup())
125 |
126 | }
127 |
--------------------------------------------------------------------------------
/codes/ali_tts_oss/tts/tts.go:
--------------------------------------------------------------------------------
1 | package tts
2 |
3 | import (
4 | "bytes"
5 | "xxx/oss"
6 | "crypto/md5"
7 | "encoding/hex"
8 | "errors"
9 | "fmt"
10 | "log"
11 | "os"
12 | "time"
13 | nls "github.com/aliyun/alibabacloud-nls-go-sdk"
14 | )
15 |
16 |
17 | var (
18 | accessKeyId = "xxxxx"
19 | accessKeySecret = "xxx"
20 | appKey = "xxxxxxx"
21 |
22 | OSSEndpoint = "xxxx"
23 | OSSAccessKey = "xxx"
24 | OSSKeySecrete = "xxx"
25 |
26 | )
27 |
28 | var clientConfig *nls.ConnectionConfig
29 |
30 | func getClient() *nls.ConnectionConfig {
31 | return clientConfig
32 | }
33 |
34 | type userParam struct {
35 | audioStream bytes.Buffer //转换成功回调时把音频流保存在这里
36 | }
37 |
38 | func init() {
39 |
40 | var ak, as string
41 | if os.Getenv("env") == "prod" {
42 | ak = accessKeyIdProd
43 | as = accessKeySecretProd
44 | } else {
45 | ak = accessKeyId
46 | as = accessKeySecret
47 | }
48 | cf, err := nls.NewConnectionConfigWithAKInfoDefault(nls.DEFAULT_URL, appKey, ak, as)
49 | if err != nil {
50 | panic(err)
51 | }
52 | clientConfig = cf
53 | }
54 |
55 | // 转换任务错误执行
56 | func onTaskFailed(text string, param interface{}) {
57 | log.Print("TtsTextToVoiceTaskFailedError", text)
58 | }
59 |
60 | // 转换任务成功回调音频流
61 | func onSynthesisResult(data []byte, param interface{}) {
62 | p, ok := param.(*userParam)
63 | if !ok {
64 | log.Print("TtsTextToVoiceResultUserParamUnExpected", param)
65 | return
66 | }
67 | p.audioStream.Write(data)
68 | }
69 |
70 | // 执行完毕
71 | func onCompleted(text string, param interface{}) {
72 | log.Print("TtsTextToVoiceOnCompleted:", text)
73 | }
74 |
75 | func onClose(param interface{}) {
76 | log.Print("TtsTextToVoiceOnClosed")
77 | }
78 |
79 | func waitReady(ch chan bool, logger *nls.NlsLogger) error {
80 | select {
81 | case done := <-ch:
82 | {
83 | if !done {
84 | logger.Println("Wait failed")
85 | return errors.New("wait failed")
86 | }
87 | logger.Println("Wait done")
88 | }
89 | case <-time.After(60 * time.Second):
90 | {
91 | logger.Println("Wait timeout")
92 | return errors.New("wait timeout")
93 | }
94 | }
95 | return nil
96 | }
97 |
98 | func TextToVoice(text string) (audioFileUrl string, err error) {
99 | clientConf := getClient()
100 | param := nls.DefaultSpeechSynthesisParam()
101 | param.Voice = "yuer" // 儿童剧女声
102 | strId := "ai-" // tts日志的前缀
103 | logger := nls.NewNlsLogger(os.Stderr, strId, log.LstdFlags|log.Lmicroseconds)
104 | logger.SetLogSil(false)
105 | logger.SetDebug(true)
106 | userParam := new(userParam)
107 | //第三个参数控制是否请求长文本语音合成,false为短文本语音合成
108 | tts, err := nls.NewSpeechSynthesis(clientConf, logger, false,
109 | onTaskFailed, onSynthesisResult, nil,
110 | onCompleted, onClose, userParam)
111 | if err != nil {
112 | logger.Fatalln(err)
113 | return
114 | }
115 | ch, err := tts.Start(text, param, nil)
116 | if err != nil {
117 | tts.Shutdown()
118 | dlog.Error("TtsTextToVoiceStartError", err)
119 | return
120 | }
121 | err = waitReady(ch, logger)
122 | if err != nil {
123 | tts.Shutdown()
124 | dlog.Error("TtsTextToVoiceWaitError", err)
125 | return
126 | }
127 | tts.Shutdown() //tts关闭后再把文件流上传到OSS
128 | bucket, err := oss.GetBucket("qschou", OSSEndpoint, OSSAccessKey, OSSKeySecrete)
129 | if err != nil {
130 | log.Print("func", "SaveImgToOSS", "msg", "GetBucket Error", "err", err)
131 | return
132 | }
133 | uniqId := Md5(fmt.Sprintf("%d", time.Now().UnixNano()))
134 | fileName := fmt.Sprintf("files/ai-toolbox/text-to-audio/%s.%s", uniqId, "wav")
135 | err = bucket.PutObject(fileName, bytes.NewReader(userParam.audioStream.Bytes()))
136 | if err != nil {
137 | log.Print("func", "SaveImgToOSS", "msg", "PutObject Error", "err", err)
138 | return "", err
139 | }
140 |
141 | return "https://thumb.qschou.com" + "/" + fileName, nil
142 | //本地测试用
143 | //fname := "ttsdump.wav"
144 | //fout, err := os.OpenFile(fname, os.O_RDWR|os.O_TRUNC|os.O_CREATE, 0666)
145 | //fout.Write(userParam.audioStream.Bytes())
146 | //return "", nil
147 | }
148 |
149 |
150 | func Md5(str string) string {
151 | tmp := md5.Sum([]byte(str))
152 | return hex.EncodeToString(tmp[:])
153 | }
154 |
--------------------------------------------------------------------------------
/codes/img/base.go:
--------------------------------------------------------------------------------
1 | package img
2 |
3 | import (
4 | "bytes"
5 | "image"
6 | "image/color"
7 | "image/draw"
8 | "image/png"
9 | )
10 |
11 |
12 |
13 | // SubImage 裁剪图片
14 | func SubImage(imgByte []byte) (subImgList [][]byte, err error) {
15 | reader := bytes.NewReader(imgByte)
16 |
17 | img, _, err := image.Decode(reader)
18 | if err != nil {
19 | return
20 | }
21 |
22 | var (
23 | // 获取图片的宽度和高度
24 | bounds = img.Bounds()
25 | width = bounds.Max.X
26 | height = bounds.Max.Y
27 |
28 | // 保存剪切后的四个图片
29 | topLeftBuf = new(bytes.Buffer)
30 | topRightBuf = new(bytes.Buffer)
31 | bottomLeftBuf = new(bytes.Buffer)
32 | bottomRightBuf = new(bytes.Buffer)
33 | )
34 |
35 | // 剪切四个角落的图片
36 | topLeft := image.NewRGBA(image.Rect(0, 0, width/2, height/2))
37 | draw.Draw(topLeft, topLeft.Bounds(), img, image.Point{}, draw.Src)
38 | if err = png.Encode(topLeftBuf, topLeft); err != nil {
39 | return
40 | }
41 | subImgList = append(subImgList, topLeftBuf.Bytes())
42 |
43 | topRight := image.NewRGBA(image.Rect(0, 0, width/2, height/2))
44 | draw.Draw(topRight, topRight.Bounds(), img, image.Point{X: width / 2}, draw.Src)
45 | if err = png.Encode(topRightBuf, topRight); err != nil {
46 | return
47 | }
48 | subImgList = append(subImgList, topRightBuf.Bytes())
49 |
50 | bottomLeft := image.NewRGBA(image.Rect(0, 0, width/2, height/2))
51 | draw.Draw(bottomLeft, bottomLeft.Bounds(), img, image.Point{Y: height / 2}, draw.Src)
52 | if err = png.Encode(bottomLeftBuf, bottomLeft); err != nil {
53 | return
54 | }
55 | subImgList = append(subImgList, bottomLeftBuf.Bytes())
56 |
57 | bottomRight := image.NewRGBA(image.Rect(0, 0, width/2, height/2))
58 | draw.Draw(bottomRight, bottomRight.Bounds(), img, image.Point{X: width / 2, Y: height / 2}, draw.Src)
59 | if err = png.Encode(bottomRightBuf, bottomRight); err != nil {
60 | return
61 | }
62 | subImgList = append(subImgList, bottomRightBuf.Bytes())
63 | return
64 | }
65 |
66 | // SubImageByGap 根据间隔裁剪图片
67 | func SubImageByGap(imgByte []byte) (subImgList [][]byte, err error) {
68 | reader := bytes.NewReader(imgByte)
69 |
70 | imgBg, _, err := image.Decode(reader)
71 | if err != nil {
72 | return
73 | }
74 |
75 | var (
76 | // 获取图片的宽度和高度
77 | bounds = imgBg.Bounds()
78 | bgWidth = bounds.Max.X
79 | bgHeight = bounds.Max.Y
80 | )
81 |
82 | // 识别小图的水平间隔
83 | var (
84 | lastGapY int
85 | rawImages []*image.RGBA // 裁剪出的小图
86 | )
87 | for y := 0; y < bgHeight; y++ { // 遍历图片的高度
88 | gapWidth := 0 // 间隔宽度
89 | for x := 0; x < bgWidth; x++ { // 遍历图片的宽度
90 | at := imgBg.At(x, y) // 获取像素点的颜色
91 | if !isWhite(at) { // 判断是否是白色
92 | break
93 | }
94 | gapWidth++ // 间隔宽度+1
95 | }
96 | if gapWidth == bgWidth { // 间隔宽度等于图片宽度,说明是白色间隔
97 | if y-lastGapY <= 1 { // 排除连续的白色间隔
98 | lastGapY = y
99 | continue
100 | }
101 | // 裁剪出小图
102 | smallImg := image.NewRGBA(image.Rect(0, 0, bgWidth, y-lastGapY))
103 | draw.Draw(smallImg, smallImg.Bounds(), imgBg, image.Point{Y: lastGapY}, draw.Src)
104 | rawImages = append(rawImages, smallImg)
105 | lastGapY = y
106 | }
107 | }
108 |
109 | var smallImages []*image.RGBA // 裁剪出的小图
110 | for _, img := range rawImages { // 遍历竖行图
111 | var lastGapX int
112 | // 重新获取图片的宽度和高度
113 | bgWidth = img.Bounds().Max.X
114 | bgHeight = img.Bounds().Max.Y
115 | for x := 0; x < bgWidth; x++ { // 遍历图片的宽度
116 | gapHeight := 0 // 间隔高度
117 | for y := 0; y < bgHeight; y++ { // 遍历图片的高度
118 | at := img.At(x, y) // 获取像素点的颜色
119 | if !isWhite(at) { // 判断是否是白色
120 | break
121 | }
122 | gapHeight++ // 间隔高度+1
123 | }
124 | if gapHeight == bgHeight { // 间隔高度等于图片高度,说明是白色间隔
125 | if x-lastGapX == 1 { // 排除连续的白色间隔
126 | lastGapX = x
127 | continue
128 | }
129 | // 裁剪出竖行图
130 | smallImg := image.NewRGBA(image.Rect(0, 0, x-lastGapX, bgHeight))
131 | draw.Draw(smallImg, smallImg.Bounds(), img, image.Point{X: lastGapX}, draw.Src)
132 | smallImages = append(smallImages, smallImg)
133 | lastGapX = x
134 | }
135 | }
136 | // 添加最后一个列图
137 | if lastGapX < bgWidth {
138 | smallImg := image.NewRGBA(image.Rect(0, 0, bgWidth-lastGapX, bgHeight))
139 | draw.Draw(smallImg, smallImg.Bounds(), img, image.Point{X: lastGapX}, draw.Src)
140 | smallImages = append(smallImages, smallImg)
141 | }
142 | }
143 | // 保存裁剪出的小图
144 | for _, img := range smallImages {
145 | if fullWhiteImg(img) {
146 | continue
147 | }
148 | imgBuf := new(bytes.Buffer)
149 | if err = png.Encode(imgBuf, img); err != nil {
150 | continue
151 | }
152 | subImgList = append(subImgList, imgBuf.Bytes())
153 | }
154 | return
155 | }
156 |
157 | // fullWhiteImg 判断是否是全白图片
158 | // img 图片
159 | // is 是否是全白图片: true 是, false 否
160 | func fullWhiteImg(img *image.RGBA) (is bool) {
161 | bounds := img.Bounds()
162 | bgWidth := bounds.Max.X
163 | bgHeight := bounds.Max.Y
164 | for y := 0; y < bgHeight; y++ { // 遍历图片的高度
165 | for x := 0; x < bgWidth; x++ { // 遍历图片的宽度
166 | at := img.At(x, y) // 获取像素点的颜色
167 | if !isWhite(at) { // 判断是否是白色
168 | return
169 | }
170 | }
171 | }
172 | is = true
173 | return
174 | }
175 |
176 | // isWhite 判断是否是白色
177 | func isWhite(c color.Color) bool {
178 | maxColor := color.RGBA{R: 255, G: 255, B: 255, A: 255} // 白色的最大值
179 | minColor := color.RGBA{R: 240, G: 240, B: 240, A: 240} // 白色的最小值
180 |
181 | r, g, b, a := c.RGBA()
182 | var rBool, gBool, bBool, aBool bool
183 | //fmt.Printf("byte(r): %d, byte(g): %d, byte(b): %d, byte(a): %d\n", byte(r), byte(g), byte(b), byte(a))
184 | if byte(r) <= maxColor.R && byte(r) >= minColor.R {
185 | rBool = true
186 | }
187 | if byte(g) <= maxColor.G && byte(g) >= minColor.G {
188 | gBool = true
189 | }
190 | if byte(b) <= maxColor.B && byte(b) >= minColor.B {
191 | bBool = true
192 | }
193 | if byte(a) <= maxColor.A && byte(a) >= minColor.A {
194 | aBool = true
195 | }
196 | return rBool && gBool && bBool && aBool
197 | }
198 |
--------------------------------------------------------------------------------
/codes/proconsumer/tests/file.txt:
--------------------------------------------------------------------------------
1 | sssssssssss
2 | dsssssssssss
3 | dssssssssssssss
4 | fdddddddddddd
5 | eeeeeetr
6 | rtttttttttttt
7 | sssssssssss
8 | dsssssssssss
9 | dssssssssssssss
10 | fdddddddddddd
11 | eeeeeetr
12 | rtttttttttttt
13 | sssssssssss
14 | dsssssssssss
15 | dssssssssssssss
16 | fdddddddddddd
17 | eeeeeetr
18 | rtttttttttttt
19 | sssssssssss
20 | dsssssssssss
21 | dssssssssssssss
22 | fdddddddddddd
23 | eeeeeetr
24 | rtttttttttttt
25 | sssssssssss
26 | dsssssssssss
27 | dssssssssssssss
28 | fdddddddddddd
29 | eeeeeetr
30 | rtttttttttttt
31 | sssssssssss
32 | dsssssssssss
33 | dssssssssssssss
34 | fdddddddddddd
35 | eeeeeetr
36 | rtttttttttttt
37 | sssssssssss
38 | dsssssssssss
39 | dssssssssssssss
40 | fdddddddddddd
41 | eeeeeetr
42 | rtttttttttttt
43 | sssssssssss
44 | dsssssssssss
45 | dssssssssssssss
46 | fdddddddddddd
47 | eeeeeetr
48 | rtttttttttttt
49 | sssssssssss
50 | dsssssssssss
51 | dssssssssssssss
52 | fdddddddddddd
53 | eeeeeetr
54 | rtttttttttttt
55 | sssssssssss
56 | dsssssssssss
57 | dssssssssssssss
58 | fdddddddddddd
59 | eeeeeetr
60 | rtttttttttttt
61 | sssssssssss
62 | dsssssssssss
63 | dssssssssssssss
64 | fdddddddddddd
65 | eeeeeetr
66 | rtttttttttttt
67 | sssssssssss
68 | dsssssssssss
69 | dssssssssssssss
70 | fdddddddddddd
71 | eeeeeetr
72 | rtttttttttttt
73 | sssssssssss
74 | dsssssssssss
75 | dssssssssssssss
76 | fdddddddddddd
77 | eeeeeetr
78 | rtttttttttttt
79 | sssssssssss
80 | dsssssssssss
81 | dssssssssssssss
82 | fdddddddddddd
83 | eeeeeetr
84 | rtttttttttttt
85 | sssssssssss
86 | dsssssssssss
87 | dssssssssssssss
88 | fdddddddddddd
89 | eeeeeetr
90 | rtttttttttttt
91 | sssssssssss
92 | dsssssssssss
93 | dssssssssssssss
94 | fdddddddddddd
95 | eeeeeetr
96 | rtttttttttttt
97 | sssssssssss
98 | dsssssssssss
99 | dssssssssssssss
100 | fdddddddddddd
101 | eeeeeetr
102 | rtttttttttttt
103 | sssssssssss
104 | dsssssssssss
105 | dssssssssssssss
106 | fdddddddddddd
107 | eeeeeetr
108 | rtttttttttttt
109 | sssssssssss
110 | dsssssssssss
111 | dssssssssssssss
112 | fdddddddddddd
113 | eeeeeetr
114 | rtttttttttttt
115 | sssssssssss
116 | dsssssssssss
117 | dssssssssssssss
118 | fdddddddddddd
119 | eeeeeetr
120 | rtttttttttttt
121 | sssssssssss
122 | dsssssssssss
123 | dssssssssssssss
124 | fdddddddddddd
125 | eeeeeetr
126 | rtttttttttttt
127 | sssssssssss
128 | dsssssssssss
129 | dssssssssssssss
130 | fdddddddddddd
131 | eeeeeetr
132 | rtttttttttttt
133 | sssssssssss
134 | dsssssssssss
135 | dssssssssssssss
136 | fdddddddddddd
137 | eeeeeetr
138 | rtttttttttttt
139 | sssssssssss
140 | dsssssssssss
141 | dssssssssssssss
142 | fdddddddddddd
143 | eeeeeetr
144 | rtttttttttttt
145 | sssssssssss
146 | dsssssssssss
147 | dssssssssssssss
148 | fdddddddddddd
149 | eeeeeetr
150 | rtttttttttttt
151 | sssssssssss
152 | dsssssssssss
153 | dssssssssssssss
154 | fdddddddddddd
155 | eeeeeetr
156 | rtttttttttttt
157 | sssssssssss
158 | dsssssssssss
159 | dssssssssssssss
160 | fdddddddddddd
161 | eeeeeetr
162 | rtttttttttttt
163 | sssssssssss
164 | dsssssssssss
165 | dssssssssssssss
166 | fdddddddddddd
167 | eeeeeetr
168 | rtttttttttttt
169 | sssssssssss
170 | dsssssssssss
171 | dssssssssssssss
172 | fdddddddddddd
173 | eeeeeetr
174 | rtttttttttttt
175 | sssssssssss
176 | dsssssssssss
177 | dssssssssssssss
178 | fdddddddddddd
179 | eeeeeetr
180 | rtttttttttttt
181 | sssssssssss
182 | dsssssssssss
183 | dssssssssssssss
184 | fdddddddddddd
185 | eeeeeetr
186 | rtttttttttttt
187 | sssssssssss
188 | dsssssssssss
189 | dssssssssssssss
190 | fdddddddddddd
191 | eeeeeetr
192 | rtttttttttttt
193 | sssssssssss
194 | dsssssssssss
195 | dssssssssssssss
196 | fdddddddddddd
197 | eeeeeetr
198 | rtttttttttttt
199 | sssssssssss
200 | dsssssssssss
201 | dssssssssssssss
202 | fdddddddddddd
203 | eeeeeetr
204 | rtttttttttttt
205 | sssssssssss
206 | dsssssssssss
207 | dssssssssssssss
208 | fdddddddddddd
209 | eeeeeetr
210 | rtttttttttttt
211 | sssssssssss
212 | dsssssssssss
213 | dssssssssssssss
214 | fdddddddddddd
215 | eeeeeetr
216 | rtttttttttttt
217 | sssssssssss
218 | dsssssssssss
219 | dssssssssssssss
220 | fdddddddddddd
221 | eeeeeetr
222 | rtttttttttttt
223 | sssssssssss
224 | dsssssssssss
225 | dssssssssssssss
226 | fdddddddddddd
227 | eeeeeetr
228 | rtttttttttttt
229 | sssssssssss
230 | dsssssssssss
231 | dssssssssssssss
232 | fdddddddddddd
233 | eeeeeetr
234 | rtttttttttttt
235 | sssssssssss
236 | dsssssssssss
237 | dssssssssssssss
238 | fdddddddddddd
239 | eeeeeetr
240 | rtttttttttttt
241 | sssssssssss
242 | dsssssssssss
243 | dssssssssssssss
244 | fdddddddddddd
245 | eeeeeetr
246 | rtttttttttttt
247 | sssssssssss
248 | dsssssssssss
249 | dssssssssssssss
250 | fdddddddddddd
251 | eeeeeetr
252 | rtttttttttttt
253 | sssssssssss
254 | dsssssssssss
255 | dssssssssssssss
256 | fdddddddddddd
257 | eeeeeetr
258 | rtttttttttttt
259 | sssssssssss
260 | dsssssssssss
261 | dssssssssssssss
262 | fdddddddddddd
263 | eeeeeetr
264 | rtttttttttttt
265 | sssssssssss
266 | dsssssssssss
267 | dssssssssssssss
268 | fdddddddddddd
269 | eeeeeetr
270 | rtttttttttttt
271 | sssssssssss
272 | dsssssssssss
273 | dssssssssssssss
274 | fdddddddddddd
275 | eeeeeetr
276 | rtttttttttttt
277 | sssssssssss
278 | dsssssssssss
279 | dssssssssssssss
280 | fdddddddddddd
281 | eeeeeetr
282 | rtttttttttttt
283 | sssssssssss
284 | dsssssssssss
285 | dssssssssssssss
286 | fdddddddddddd
287 | eeeeeetr
288 | rtttttttttttt
289 | sssssssssss
290 | dsssssssssss
291 | dssssssssssssss
292 | fdddddddddddd
293 | eeeeeetr
294 | rtttttttttttt
295 | sssssssssss
296 | dsssssssssss
297 | dssssssssssssss
298 | fdddddddddddd
299 | eeeeeetr
300 | rtttttttttttt
301 | sssssssssss
302 | dsssssssssss
303 | dssssssssssssss
304 | fdddddddddddd
305 | eeeeeetr
306 | rtttttttttttt
307 | sssssssssss
308 | dsssssssssss
309 | dssssssssssssss
310 | fdddddddddddd
311 | eeeeeetr
312 | rtttttttttttt
313 | sssssssssss
314 | dsssssssssss
315 | dssssssssssssss
316 | fdddddddddddd
317 | eeeeeetr
318 | rtttttttttttt
319 | sssssssssss
320 | dsssssssssss
321 | dssssssssssssss
322 | fdddddddddddd
323 | eeeeeetr
324 | rtttttttttttt
325 | sssssssssss
326 | dsssssssssss
327 | dssssssssssssss
328 | fdddddddddddd
329 | eeeeeetr
330 | rtttttttttttt
331 | sssssssssss
332 | dsssssssssss
333 | dssssssssssssss
334 | fdddddddddddd
335 | eeeeeetr
336 | rtttttttttttt
337 |
--------------------------------------------------------------------------------
/lang-basic/content/chapter1.md:
--------------------------------------------------------------------------------
1 | ### 前言
2 |
3 | 以下内容为几年前学Go语言时记录的,发布在这里让这个项目对更多人能起到参考作用。
4 | 更多时下流行技术的应用和实战教程,可通过我公众号「网管叨bi叨」每周的推文来学习
5 |
6 | --------
7 | 最近在读《Go 语言程序设计》这本书想通过看书巩固一下自己的基础知识,把已经积累的点通过看书学习再编织成一个网,这样看别人写的优秀代码时才能更好理解。当初工作中需要使用 Go开发项目时看了网上不少教程,比如 uknown 翻译的《the way to go》看完基本上每次使用遇到不会的时候还会再去翻阅,这次把书中的重点还有一些平时容易忽视的Go语言中各种内部结构(类型、函数、方法)的一些行为整理成读书笔记。
8 |
9 | 因为《Go 语言程序设计》不是针对初学者的,所以我只摘选最重要的部分并适当补充和调换描述顺序力求用最少的篇幅描述清楚每个知识点。
10 |
11 | 《Go 语言程序设计》在线阅读地址:https://yar999.gitbooks.io/gopl-zh/content/
12 |
13 | 如果刚接触 Go建议先去读 《the-way-to-go》在线阅读地址:https://github.com/unknwon/the-way-to-go_ZH_CN/blob/master/eBook/directory.md
14 |
15 | ### 命名:
16 |
17 | - 函数名、变量名、常量名、类型名、包名等所有的命名,都遵循一个简单的命名规则:一个名字必须以一个字母(Unicode字母)或下划线开头,后面可以跟任意数量的字母、数字或下划线。
18 |
19 | - 大写字母和小写字母是不同的:heapSort和Heapsort是两个不同的名字。
20 |
21 | - 关键字不可用于命名
22 |
23 | ```
24 | break default func interface select
25 | case defer go map struct
26 | chan else goto package switch
27 | const fallthrough if range type
28 | continue for import return var
29 | ```
30 |
31 | - 推荐驼峰式命名
32 |
33 | - 名字的开头字母的大小写决定了名字在包外的可见性。如果一个名字是大写字母开头的,那么它可以被外部的包访问,包本身的名字一般总是用小写字母。
34 |
35 | ### 声明:
36 |
37 | - Go语言主要有四种类型的声明语句:var、const、type和func,分别对应变量、常量、类型和函数。
38 |
39 | ### 变量:
40 |
41 | - var声明语句可以创建一个特定类型的变量,然后给变量附加一个名字,并且设置变量的初始值。变量声明的一般语法如下:
42 |
43 | ```Go
44 | var 变量名字 类型 = 表达式
45 | ```
46 |
47 | 其中“*类型*”或“*= 表达式*”两个部分可以省略其中的一个。如果省略的是类型信息,那么将 根据初始化表达式来推导变量的类型信息。如果初始化表达式被省略,那么将用零值初始化该变量。 数值类型变量对应的零值是0,布尔类型变量对应的零值是false,字符串类型对应的零值是空字符串,接口或引用类型(包括slice、map、chan和函数)变量对应的零值是nil。数组或结构体等聚合类型对应的零值是每个元素或字段都是对应该类型的零值。
48 |
49 | 零值初始化机制可以确保每个声明的变量总是有一个良好定义的值,因此在Go语言中不存在未初始化的变量。这个特性可以简化很多代码,而且可以在没有增加额外工作的前提下确保边界条件下的合理行为。例如:
50 |
51 | ```Go
52 | var s string
53 | fmt.Println(s) // ""
54 | ```
55 |
56 |
57 |
58 | ### 字符串:
59 |
60 | - 文本字符串通常被解释为采用UTF8编码的Unicode码点(rune)序列。
61 |
62 | - 内置的len函数可以返回一个字符串中的字节数目(不是rune字符数目),索引操作s[i]返回第i个字节的字节值,i必须满足0 ≤ i< len(s)条件约束。
63 |
64 | - 字符串的值是不可变的:一个字符串包含的字节序列永远不会被改变,当然我们也可以给一个字符串变量分配一个新字符串值。可以像下面这样将一个字符串追加到另一个字符串:
65 |
66 | ```Go
67 | s := "left foot"
68 | t := s
69 | s += ", right foot"
70 | ```
71 |
72 | 这并不会导致原始的字符串值被改变,但是变量s将因为+=语句持有一个新的字符串值,但是t依然是包含原先的字符串值。
73 |
74 | 因为字符串是不可修改的,因此尝试修改字符串内部数据的操作也是被禁止的:
75 |
76 | ```Go
77 | s[0] = 'L' // compile error: cannot assign to s[0]
78 | ```
79 |
80 | - 每一个UTF8字符解码,不管是显式地调用utf8.DecodeRuneInString解码或是在range循环中隐式地解码,如果遇到一个错误的UTF8编码输入,将生成一个特别的Unicode字符'\uFFFD',在印刷中这个符号通常是一个黑色六角或钻石形状,里面包含一个白色的问号"�"。当程序遇到这样的一个字符,通常是一个危险信号,说明输入并不是一个完美没有错误的UTF8字符串。
81 |
82 | - 字符串的各种转换:
83 |
84 | string接受到[]rune的类型转换,可以将一个UTF8编码的字符串解码为Unicode字符序列:
85 |
86 | ```Go
87 | // "program" in Japanese katakana
88 | s := "プログラム"
89 | fmt.Printf("% x\n", s) // "e3 83 97 e3 83 ad e3 82 b0 e3 83 a9 e3 83 a0"
90 | r := []rune(s)
91 | fmt.Printf("%x\n", r) // "[30d7 30ed 30b0 30e9 30e0]"
92 | ```
93 |
94 | (在第一个Printf中的`% x`参数用于在每个十六进制数字前插入一个空格。)
95 |
96 | 如果是将一个[]rune类型的Unicode字符slice或数组转为string,则对它们进行UTF8编码:
97 |
98 | ```Go
99 | fmt.Println(string(r)) // "プログラム"
100 | ```
101 |
102 | 将一个整数转型为字符串意思是生成以只包含对应Unicode码点字符的UTF8字符串:
103 |
104 | ```Go
105 | fmt.Println(string(65)) // "A", not "65"
106 | fmt.Println(string(0x4eac)) // "京"
107 | ```
108 |
109 | 如果对应码点的字符是无效的,则用'\uFFFD'无效字符作为替换:
110 |
111 | ```Go
112 | fmt.Println(string(1234567)) // "�"
113 | ```
114 |
115 | ### 复合数据类型:
116 |
117 | - 基本数据类型,它们可以用于构建程序中数据结构,是Go语言的世界的原子。以不同的方式组合基本类型可以构造出复合数据类型。我们主要讨论四种类型——数组、slice、map和结构体,数组和结构体都是有固定内存大小的数据结构。相比之下,slice和map则是动态的数据结构,它们将根据需要动态增长。
118 |
119 | ### 数组:
120 |
121 | - 数组的长度是数组类型的一个组成部分,因此[3]int和[4]int是两种不同的数组类型。数组的长度必须是常量表达式,因为数组的长度需要在编译阶段确定。
122 |
123 | ```Go
124 | q := [3]int{1, 2, 3}
125 | q = [4]int{1, 2, 3, 4} // compile error: cannot assign [4]int to [3]int
126 | ```
127 |
128 | ### Slice:
129 |
130 | - 长度对应slice中元素的数目;长度不能超过容量,容量一般是从slice的开始位置到底层数据的结尾位置。内置的len和cap函数分别返回slice的长度和容量。
131 |
132 | - x[m:n]切片操作对于字符串则生成一个新字符串,如果x是[]byte的话则生成一个新的[]byte。
133 |
134 | - slice并不是一个纯粹的引用类型,它实际上是一个类似下面结构体的聚合类型:
135 |
136 | ```Go
137 | type IntSlice struct {
138 | ptr *int
139 | len, cap int
140 | }
141 | ```
142 |
143 | ### Map:
144 |
145 | - 在Go语言中,一个map就是一个哈希表的引用,map类型可以写为map[K]V,其中K和V分别对应key和value。map中所有的key都有相同的类型,所有的value也有着相同的类型,但是key和value之间可以是不同的数据类型。
146 |
147 | - map中的元素并不是一个变量,因此我们不能对map的元素进行取址操作:
148 |
149 | ```Go
150 | _ = &ages["bob"] // compile error: cannot take address of map element
151 | ```
152 |
153 | 禁止对map元素取址的原因是map可能随着元素数量的增长而重新分配更大的内存空间,从而可能导致之前的地址无效。
154 |
155 | - map上的大部分操作,包括查找、删除、len和range循环都可以安全工作在nil值的map上,它们的行为和一个空的map类似。但是向一个nil值的map存入元素将导致一个panic异常:
156 |
157 | ```Go
158 | ages["carol"] = 21 // panic: assignment to entry in nil map
159 | ```
160 |
161 | 在向map存数据前必须先创建map。
162 |
163 | - 和slice一样,map之间也不能进行相等比较;唯一的例外是和nil进行比较。要判断两个map是否包含相同的key和value,我们必须通过一个循环实现。
164 |
165 | ### 结构体:
166 |
167 | - 下面两个语句声明了一个叫Employee的命名的结构体类型,并且声明了一个Employee类型的变量dilbert:
168 |
169 | ```Go
170 | type Employee struct {
171 | ID int
172 | Name string
173 | Address string
174 | DoB time.Time
175 | Position string
176 | Salary int
177 | ManagerID int
178 | }
179 |
180 | var dilbert Employee
181 | ```
182 |
183 | dilbert结构体变量的成员可以通过点操作符访问,比如dilbert.Name和dilbert.DoB。因为dilbert是一个变量,它所有的成员也同样是变量,我们可以直接对每个成员赋值:
184 |
185 | ```Go
186 | dilbert.Salary -= 5000 // demoted, for writing too few lines of code
187 | ```
188 |
189 | 或者是对成员取地址,然后通过指针访问:
190 |
191 | ```Go
192 | position := &dilbert.Position
193 | *position = "Senior " + *position // promoted, for outsourcing to Elbonia
194 | ```
195 |
196 | - 如果结构体成员名字是以大写字母开头的,那么该成员就是导出的;这是Go语言导出规则决定的。一个结构体可能同时包含导出和未导出的成员。未导出的成员只能在包内部访问,在外部包不可访问。
197 |
198 | - 结构体类型的零值中每个成员其类型的是零值。通常会将零值作为最合理的默认值。例如,对于bytes.Buffer类型,结构体初始值就是一个随时可用的空缓存,还有sync.Mutex的零值也是有效的未锁定状态。有时候这种零值可用的特性是自然获得的,但是也有些类型需要一些额外的工作。
199 |
200 | - 因为结构体通常通过指针处理,可以用下面的写法来创建并初始化一个结构体变量,并返回结构体的地址:
201 |
202 | ```Go
203 | pp := &Point{1, 2}
204 | ```
205 |
206 | - Go语言有一个特性让我们只声明一个成员对应的数据类型而不指名成员的名字;这类成员就叫匿名成员。匿名成员的数据类型必须是命名的类型或指向一个命名的类型的指针。下面的代码中,Circle和Wheel各自都有一个匿名成员。我们可以说Point类型被嵌入到了Circle结构体,同时Circle类型被嵌入到了Wheel结构体。
207 |
208 | ```Go
209 | type Circle struct {
210 | Point
211 | Radius int
212 | }
213 |
214 | type Wheel struct {
215 | Circle
216 | Spokes int
217 | }
218 | ```
219 |
220 | 得益于匿名嵌入的特性,我们可以直接访问叶子属性而不需要给出完整的路径:
221 |
222 | ```Go
223 | var w Wheel
224 | w.X = 8 // equivalent to w.Circle.Point.X = 8
225 | w.Y = 8 // equivalent to w.Circle.Point.Y = 8
226 | w.Radius = 5 // equivalent to w.Circle.Radius = 5
227 | w.Spokes = 20
228 | ```
229 |
230 | - 外层的结构体不仅仅是获得了匿名成员类型的所有成员,而且也获得了该类型导出的全部的方法。这个机制可以用于将一个有简单行为的对象组合成有复杂行为的对象。
231 |
--------------------------------------------------------------------------------
/cmd/es/create_index_with_mapping.shell:
--------------------------------------------------------------------------------
1 | curl -H 'Content-Type: application/json' -X PUT {$es_server_host[:port]}/{$index_name} -v -d '
2 | {
3 | "mappings": {
4 | "_doc": {
5 | "properties": {
6 | "account": {
7 | "type": "text",
8 | "fields": {
9 | "keyword": {
10 | "type": "keyword",
11 | "ignore_above": 256
12 | }
13 | }
14 | },
15 | "performanceType": {
16 | "type": "long"
17 | },
18 | "orderNo": {
19 | "type": "keyword"
20 | },
21 | "projectId": {
22 | "type": "long"
23 | },
24 | "asrState": {
25 | "type": "long"
26 | },
27 | "businessId": {
28 | "type": "long"
29 | },
30 | "callDuration": {
31 | "type": "long"
32 | },
33 | "callType": {
34 | "type": "long"
35 | },
36 | "chat": {
37 | "properties": {
38 | "content": {
39 | "type": "text",
40 | "fields": {
41 | "keyword": {
42 | "type": "keyword",
43 | "ignore_above": 256
44 | }
45 | }
46 | },
47 | "end_time": {
48 | "type": "long"
49 | },
50 | "id": {
51 | "type": "long"
52 | },
53 | "side": {
54 | "type": "long"
55 | },
56 | "start_time": {
57 | "type": "long"
58 | }
59 | }
60 | },
61 | "checkAccount": {
62 | "type": "text",
63 | "fields": {
64 | "keyword": {
65 | "type": "keyword",
66 | "ignore_above": 256
67 | }
68 | }
69 | },
70 | "checkDate": {
71 | "type": "date",
72 | "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
73 | },
74 | "bpoCheckAt": {
75 | "type": "date",
76 | "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
77 | },
78 | "checkMethod": {
79 | "type": "long"
80 | },
81 | "checkResult": {
82 | "type": "long"
83 | },
84 | "checkState": {
85 | "type": "long"
86 | },
87 | "checkStateV2": {
88 | "type": "long"
89 | },
90 | "checkType": {
91 | "type": "long"
92 | },
93 | "contactId": {
94 | "type": "keyword"
95 | },
96 | "userPhone": {
97 | "type": "keyword"
98 | },
99 | "contactTime": {
100 | "type": "date",
101 | "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
102 | },
103 | "contain": {
104 | "type": "long"
105 | },
106 | "createdAt": {
107 | "type": "date",
108 | "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
109 | },
110 | "departmentId": {
111 | "type": "long"
112 | },
113 | "endDate": {
114 | "type": "date",
115 | "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
116 | },
117 | "facedId": {
118 | "type": "long"
119 | },
120 | "facedName": {
121 | "type": "text",
122 | "fields": {
123 | "keyword": {
124 | "type": "keyword",
125 | "ignore_above": 256
126 | }
127 | }
128 | },
129 | "firstCheckAt": {
130 | "type": "date",
131 | "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
132 | },
133 | "id": {
134 | "type": "long"
135 | },
136 | "investigation": {
137 | "type": "long"
138 | },
139 | "leadsUuid": {
140 | "type": "keyword",
141 | "ignore_above": 256
142 | },
143 | "lotNumber": {
144 | "type": "keyword",
145 | "ignore_above": 256
146 | },
147 | "taskType": {
148 | "type": "long"
149 | },
150 | "platformId": {
151 | "type": "long"
152 | },
153 | "policys": {
154 | "properties": {
155 | "isSyncBx": {
156 | "type": "long"
157 | },
158 | "policyNo": {
159 | "type": "keyword",
160 | "ignore_above": 256
161 | },
162 | "skuName": {
163 | "type": "text",
164 | "fields": {
165 | "keyword": {
166 | "type": "keyword",
167 | "ignore_above": 256
168 | }
169 | }
170 | },
171 | "skutitle": {
172 | "type": "text",
173 | "fields": {
174 | "keyword": {
175 | "type": "keyword",
176 | "ignore_above": 256
177 | }
178 | }
179 | },
180 | "source": {
181 | "type": "long"
182 | }
183 | }
184 | },
185 | "reExamine": {
186 | "type": "long"
187 | },
188 | "recordUuid": {
189 | "type": "keyword",
190 | "ignore_above": 256
191 | },
192 | "remarks": {
193 | "type": "text",
194 | "fields": {
195 | "keyword": {
196 | "type": "keyword",
197 | "ignore_above": 256
198 | }
199 | }
200 | },
201 | "saasKeyword": {
202 | "properties": {
203 | "violationWord": {
204 | "type": "keyword"
205 | },
206 | "standardWord": {
207 | "type": "keyword"
208 | },
209 | "sensitiveWord": {
210 | "type": "keyword"
211 | }
212 | }
213 | },
214 | "tagId": {
215 | "type": "long"
216 | },
217 | "tagName": {
218 | "type": "text",
219 | "fields": {
220 | "keyword": {
221 | "type": "keyword",
222 | "ignore_above": 256
223 | }
224 | }
225 | },
226 | "translationPlatform": {
227 | "type": "long"
228 | },
229 | "updatedAt": {
230 | "type": "date",
231 | "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
232 | }
233 | }
234 | }
235 | }
236 | }'
237 |
--------------------------------------------------------------------------------
/lang-basic/content/chapter3.md:
--------------------------------------------------------------------------------
1 | > 以下内容为几年前学Go语言时记录的,发布在这里让这个项目对更多人能起到参考作用。 更多时下流行技术的应用和实战教程,可通过我公众号「网管叨bi叨」每周的推文来学习
2 |
3 | ## 方法
4 |
5 | ### 方法声明
6 |
7 | 在函数声明时,在其名字之前放上一个变量,即是一个方法。这个附加的参数会将该函数附加到这种类型上,即相当于为这种类型定义了一个独占的方法。
8 |
9 | ```go
10 | package geometry
11 |
12 | import "math"
13 |
14 | type Point struct{ X, Y float64 }
15 |
16 | // traditional function
17 | func Distance(p, q Point) float64 {
18 | return math.Hypot(q.X-p.X, q.Y-p.Y)
19 | }
20 |
21 | // same thing, but as a method of the Point type
22 | func (p Point) Distance(q Point) float64 {
23 | return math.Hypot(q.X-p.X, q.Y-p.Y)
24 | }
25 | ```
26 |
27 | 上面的代码里那个附加的参数p,叫做方法的接收器(receiver)。在Go语言中,我们并不会像其它语言那样用this或者self作为接收器;我们可以任意的选择接收器的名字。建议是可以使用其类型的第一个字母,比如这里使用了Point的首字母p。
28 |
29 | 在方法调用过程中,接收器参数一般会在方法名之前出现。这和方法声明是一样的,都是接收器参数在方法名字之前。下面是例子:
30 |
31 | ```Go
32 | p := Point{1, 2}
33 | q := Point{4, 6}
34 | fmt.Println(Distance(p, q)) // "5", function call
35 | fmt.Println(p.Distance(q)) // "5", method call
36 | ```
37 |
38 | 可以看到,上面的两个函数调用都是Distance,但是却没有发生冲突。第一个Distance的调用实际上用的是包级别的函数geometry.Distance,而第二个则是使用刚刚声明的Point,调用的是Point类下声明的Point.Distance方法。这种`p.Distance`的表达式叫做选择器,因为他会选择合适的对应p这个对象的`Distance`方法来执行。
39 |
40 | 因为每种类型都有其方法的命名空间,我们在用Distance这个名字的时候,不同的Distance调用指向了不同类型里的Distance方法。
41 |
42 | ```Go
43 | // A Path is a journey connecting the points with straight lines.
44 | type Path []Point
45 | // Distance returns the distance traveled along the path.
46 | func (path Path) Distance() float64 {
47 | sum := 0.0
48 | for i := range path {
49 | if i > 0 {
50 | sum += path[i-1].Distance(path[i])
51 | }
52 | }
53 | return sum
54 | }
55 | ```
56 |
57 | Path是一个命名的slice类型,而不是Point那样的struct类型,然而我们依然可以为它定义方法。两个Distance方法有不同的类型。他们两个方法之间没有任何关系,尽管Path的Distance方法会在内部调用`Point.Distance`方法来计算每个连接邻接点的线段的长度。
58 |
59 | Go和很多其它的面向对象的语言不太一样。在Go语言里,我们可以为一些简单的数值、字符串、slice、map来定义一些附加行为很方便。方法可以被声明到任意类型,只要不是一个指针或者一个interface(接收者不能是一个指针类型,但是它可以是任何其他允许类型的指针)。
60 |
61 | 对于一个给定的类型,其内部的方法都必须有唯一的方法名,但是不同的类型却可以有同样的方法名,比如我们这里Point和Path就都有Distance这个名字的方法;所以我们没有必要非在方法名之前加类型名来消除歧义,比如PathDistance。在上面两个对Distance名字的方法的调用中,编译器会根据方法的名字以及接收器来决定具体调用的是哪一个函数。
62 |
63 | ### 指针对象的方法
64 |
65 | 当调用一个函数时,会对其每一个参数值进行拷贝,如果一个函数需要更新一个变量,或者函数的其中一个参数实在太大我们希望能够避免进行这种默认的拷贝,这种情况下我们就需要用到指针了。对应到我们这里用来更新接收器的对象的方法,当这个接受者变量本身比较大时,我们就可以用其指针而不是对象来声明方法,如下:
66 |
67 | ```go
68 | func (p *Point) ScaleBy(factor float64) {
69 | p.X *= factor
70 | p.Y *= factor
71 | }
72 | ```
73 |
74 | 这个方法的名字是`(*Point).ScaleBy`。这里的括号是必须的;没有括号的话这个表达式可能会被理解为`*(Point.ScaleBy)`。
75 |
76 | - 在现实的程序里,一般会约定如果Point这个类有一个指针作为接收器的方法,那么所有Point的方法都必须有一个指针接收器,即使是那些并不需要这个指针接收器的函数。我们在这里打破了这个约定只是为了展示一下两种方法的异同而已。
77 |
78 | - 不管你的method的receiver是指针类型还是非指针类型,都是可以通过指针/非指针类型进行调用的,编译器会帮你做类型转换。
79 |
80 | ```go
81 | p := Point{1, 2}
82 | pptr := &p
83 | p.ScaleBy(2) // implicit (&p)
84 | pptr.Distance(q) // implicit (*pptr)
85 | ```
86 |
87 | - 在声明一个method的receiver是指针还是非指针类型时,你需要考虑两方面的内部,第一方面是这个对象本身是不是特别大,如果声明为非指针变量时,调用会产生一次拷贝;第二方面是如果你用指针类型作为receiver,那么你一定要注意,这种指针类型指向的始终是一块内存地址,就算你对其进行了拷贝(指针调用时也是值拷贝,只不过指针的值是一个内存地址,所以在函数里的指针与调用方的指针变量是两个不同的指针但是指向了相同的内存地址)。
88 |
89 | ### Nil也是一个合法的接收器类型
90 |
91 | - 就像一些函数允许nil指针作为参数一样,方法理论上也可以用nil指针作为其接收器,尤其当nil对于对象来说是合法的零值时,比如map或者slice。在下面的简单int链表的例子里,nil代表的是空链表:
92 |
93 | ```go
94 | // An IntList is a linked list of integers.
95 | // A nil *IntList represents the empty list.
96 | type IntList struct {
97 | Value int
98 | Tail *IntList
99 | }
100 | // Sum returns the sum of the list elements.
101 | func (list *IntList) Sum() int {
102 | if list == nil {
103 | return 0
104 | }
105 | return list.Value + list.Tail.Sum()
106 | }
107 | ```
108 |
109 | 当你定义一个允许nil作为接收器的方法的类型时,在类型前面的注释中指出nil变量代表的意义是很有必要的,就像我们上面例子里做的这样。
110 |
111 | ### 通过嵌入结构体来扩展类型
112 |
113 | - 下面的ColoredPoint类型
114 |
115 | ```go
116 | import "image/color"
117 |
118 | type Point struct{ X, Y float64 }
119 |
120 | type ColoredPoint struct {
121 | Point
122 | Color color.RGBA
123 | }
124 | ```
125 |
126 | 内嵌可以使我们在定义ColoredPoint时得到一种句法上的简写形式,并使其包含Point类型所具有的一切字段和方法。
127 |
128 | ```go
129 | var cp ColoredPoint
130 | cp.X = 1
131 | fmt.Println(cp.Point.X) // "1"
132 | cp.Point.Y = 2
133 | fmt.Println(cp.Y) // "2"
134 |
135 | red := color.RGBA{255, 0, 0, 255}
136 | blue := color.RGBA{0, 0, 255, 255}
137 | var p = ColoredPoint{Point{1, 1}, red}
138 | var q = ColoredPoint{Point{5, 4}, blue}
139 | fmt.Println(p.Distance(q.Point)) // "5"
140 | p.ScaleBy(2)
141 | q.ScaleBy(2)
142 | fmt.Println(p.Distance(q.Point)) // "10"
143 | ```
144 |
145 | 通过内嵌结构体可以使我们定义字段特别多的复杂类型,我们可以将字段先按小类型分组,然后定义小类型的方法,之后再把它们组合起来。
146 |
147 | - 内嵌字段会指导编译器去生成额外的包装方法来委托已经声明好的方法,和下面的形式是等价的:
148 |
149 | ```go
150 | func (p ColoredPoint) Distance(q Point) float64 {
151 | return p.Point.Distance(q)
152 | }
153 |
154 | func (p *ColoredPoint) ScaleBy(factor float64) {
155 | p.Point.ScaleBy(factor)
156 | }
157 | ```
158 |
159 | 当Point.Distance被第一个包装方法调用时,它的接收器值是p.Point,而不是p,当然了,在Point类的方法里,你是访问不到ColoredPoint的任何字段的。
160 |
161 | - 方法只能在命名类型(像Point)或者指向类型的指针上定义,但是多亏了内嵌,我们给匿名struct类型来定义方法也有了手段。这个例子中我们为变量起了一个更具表达性的名字:cache。因为sync.Mutex类型被嵌入到了这个struct里,其Lock和Unlock方法也就都被引入到了这个匿名结构中了,这让我们能够以一个简单明了的语法来对其进行加锁解锁操作。
162 |
163 | ```go
164 | var cache = struct {
165 | sync.Mutex
166 | mapping map[string]string
167 | }{
168 | mapping: make(map[string]string),
169 | }
170 |
171 |
172 | func Lookup(key string) string {
173 | cache.Lock()
174 | v := cache.mapping[key]
175 | cache.Unlock()
176 | return v
177 | }
178 | ```
179 |
180 | ### 方法值和方法表达式
181 |
182 | - 我们经常选择一个方法,并且在同一个表达式里执行,比如常见的p.Distance()形式,实际上将其分成两步来执行也是可能的。p.Distance叫作“选择器”,选择器会返回一个方法"值"->一个将方法(Point.Distance)绑定到特定接收器变量的函数。因为已经在前文中指定过了,这个函数可以不通过指定其接收器即可被调用,只要传入函数的参数即可:
183 |
184 | ```go
185 | p := Point{1, 2}
186 | q := Point{4, 6}
187 |
188 | distanceFromP := p.Distance // method value
189 | fmt.Println(distanceFromP(q)) // "5"
190 | var origin Point // {0, 0}
191 | fmt.Println(distanceFromP(origin)) // "2.23606797749979", sqrt(5)
192 |
193 | scaleP := p.ScaleBy // method value
194 | scaleP(2) // p becomes (2, 4)
195 | scaleP(3) // then (6, 12)
196 | scaleP(10) // then (60, 120)
197 | ```
198 |
199 | - 当T是一个类型时,方法表达式可能会写作T.f或者(*T).f,会返回一个函数"值",这种函数会将其第一个参数用作接收器,所以可以用通常(译注:不写选择器)的方式来对其进行调用:
200 |
201 | ```go
202 | p := Point{1, 2}
203 | q := Point{4, 6}
204 |
205 | distance := Point.Distance // method expression
206 | fmt.Println(distance(p, q)) // "5"
207 | fmt.Printf("%T\n", distance) // "func(Point, Point) float64"
208 |
209 | scale := (*Point).ScaleBy
210 | scale(&p, 2)
211 | fmt.Println(p) // "{2 4}"
212 | fmt.Printf("%T\n", scale) // "func(*Point, float64)"
213 | // 译注:这个Distance实际上是指定了Point对象为接收器的一个方法func (p Point) Distance(),
214 | // 但通过Point.Distance得到的函数需要比实际的Distance方法多一个参数,
215 | // 即其需要用第一个额外参数指定接收器,后面排列Distance方法的参数。
216 | ```
217 |
218 | - 当你根据一个变量来决定调用同一个类型的哪个函数时,方法表达式就显得很有用了。你可以根据选择来调用接收器各不相同的方法。下面的例子,变量op代表Point类型的addition或者subtraction方法,Path.TranslateBy方法会为其Path数组中的每一个Point来调用对应的方法:
219 |
220 | ```go
221 | type Point struct{ X, Y float64 }
222 |
223 | func (p Point) Add(q Point) Point { return Point{p.X + q.X, p.Y + q.Y} }
224 | func (p Point) Sub(q Point) Point { return Point{p.X - q.X, p.Y - q.Y} }
225 |
226 | type Path []Point
227 |
228 | func (path Path) TranslateBy(offset Point, add bool) {
229 | var op func(p, q Point) Point
230 | if add {
231 | op = Point.Add
232 | } else {
233 | op = Point.Sub
234 | }
235 | for i := range path {
236 | // Call either path[i].Add(offset) or path[i].Sub(offset).
237 | path[i] = op(path[i], offset)
238 | }
239 | }
240 | ```
241 |
242 | ### 封装
243 |
244 | - 一个对象的变量或者方法如果对调用方是不可见的话,一般就被定义为“封装”。封装有时候也被叫做信息隐藏,同时也是面向对象编程最关键的一个方面。
245 | - Go语言只有一种控制可见性的手段:大写首字母的标识符会从定义它们的包中被导出,小写字母的则不会。
246 | - 这种基于名字的手段使得在语言中最小的封装单元是package,而不是像其它语言一样的`Class`。一个struct类型的字段对同一个包的所有代码都有可见性,无论你的代码是写在一个函数还是一个方法里。
247 |
248 | - 封装提供了三方面的优点。首先,因为调用方不能直接修改对象的变量值,其只需要关注少量的语句并且只要弄懂少量变量的可能的值即可。
249 |
250 | 第二,隐藏实现的细节,可以防止调用方依赖那些可能变化的具体实现,这样使设计包的程序员在不破坏对外的api情况下能得到更大的自由。
251 |
252 | 封装的第三个优点也是最重要的优点,是阻止了外部调用方对对象内部的值任意地进行修改。因为对象内部变量只可以被同一个包内的函数修改,所以包的作者可以让这些函数确保对象内部的一些值的不变性。比如下面的Counter类型允许调用方来增加counter变量的值,并且允许将这个值reset为0,但是不允许随便设置这个值(译注:因为压根就访问不到):
253 |
254 | ```go
255 | type Counter struct { n int }
256 | func (c *Counter) N() int { return c.n }
257 | func (c *Counter) Increment() { c.n++ }
258 | func (c *Counter) Reset() { c.n = 0 }
259 | ```
260 |
261 | - 只用来访问或修改内部变量的函数被称为setter或者getter,例子如下,比如log包里的Logger类型对应的一些函数。在命名一个getter方法时,我们通常会省略掉前面的Get前缀。这种简洁上的偏好也可以推广到各种类型的前缀比如Fetch,Find或者Lookup。
262 |
263 | ```go
264 | package log
265 | type Logger struct {
266 | flags int
267 | prefix string
268 | // ...
269 | }
270 | func (l *Logger) Flags() int
271 | func (l *Logger) SetFlags(flag int)
272 | func (l *Logger) Prefix() string
273 | func (l *Logger) SetPrefix(prefix string)
274 | ```
275 |
276 | - Go的编码风格不禁止直接导出字段。当然,一旦进行了导出,就没有办法在保证API兼容的情况下去除对其的导出,所以在一开始的选择一定要经过深思熟虑并且要考虑到包内部的一些不变量的保证,还有未来可能的变化。
277 |
--------------------------------------------------------------------------------
/codes/crypto_utils/aes.go:
--------------------------------------------------------------------------------
1 | package crypto_utils
2 |
3 | import (
4 | "bytes"
5 | "crypto/aes"
6 | "crypto/cipher"
7 | "crypto/hmac"
8 | "crypto/rand"
9 | "crypto/sha1"
10 | "crypto/sha256"
11 | "encoding/base64"
12 | "encoding/hex"
13 | "encoding/json"
14 | "errors"
15 | "fmt"
16 | "io"
17 | "strings"
18 | "time"
19 | )
20 |
21 | // AES KEY either 16, 24, or 32 bytes to select AES-128, AES-192, or AES-256
22 | // var aseKey = "b3c65d06a1cf4dbda57af0af5c63e85f"
23 |
24 | func Base64URLDecode(data string) ([]byte, error) {
25 | var missing = (4 - len(data)%4) % 4
26 | data += strings.Repeat("=", missing)
27 | res, err := base64.URLEncoding.DecodeString(data)
28 | fmt.Println(" decodebase64urlsafe is :", string(res), err)
29 | return base64.URLEncoding.DecodeString(data)
30 | }
31 |
32 | func Base64UrlSafeEncode(source []byte) string {
33 | // Base64 Url Safe is the same as Base64 but does not contain '/' and '+' (replaced by '_' and '-') and trailing '=' are removed.
34 | bytearr := base64.StdEncoding.EncodeToString(source)
35 | safeurl := strings.Replace(string(bytearr), "/", "_", -1)
36 | safeurl = strings.Replace(safeurl, "+", "-", -1)
37 | safeurl = strings.Replace(safeurl, "=", "", -1)
38 | return safeurl
39 | }
40 |
41 | /*
42 | // keyStr 密钥
43 | // value 消息内容
44 | */
45 | func HMACSHA1(value, keyStr string) string {
46 | key := []byte(keyStr)
47 | mac := hmac.New(sha1.New, key)
48 | mac.Write([]byte(value))
49 | //进行base64编码
50 | res := base64.StdEncoding.EncodeToString(mac.Sum(nil))
51 | return res
52 | }
53 |
54 | func HmacEqual(signingString, signature string, key) error {
55 | originalSig, err := base64.StdEncoding.DecodeString(signature)
56 | mac := hmac.New(sha1.New, key)
57 | mac.Write([]byte(value))
58 | if !hmac.Equal(originalSig, mac.Sum(nil)) {
59 | return errors.New("signature is invalid")
60 | }
61 |
62 | return nil
63 | }
64 |
65 | // key is 32 bytes
66 | func AesEcbPkcs5Decrypt(crypted, key []byte) (origData []byte, err error) {
67 | block, err := aes.NewCipher(key)
68 | if err != nil {
69 | return
70 | }
71 | blockMode := NewECBDecrypter(block)
72 | origData = make([]byte, len(crypted))
73 | blockMode.CryptBlocks(origData, crypted)
74 | origData = PKCS5UnPadding(origData)
75 | return
76 | }
77 |
78 | func AesEcbPkcs5Encrypt(src string, key []byte) (crypted []byte, err error) {
79 | block, err := aes.NewCipher(key)
80 | if err != nil {
81 | return
82 | }
83 | ecb := NewECBEncrypter(block)
84 | content := []byte(src)
85 | content = PKCS5Padding(content, block.BlockSize())
86 | crypted = make([]byte, len(content))
87 | ecb.CryptBlocks(crypted, content)
88 | return
89 | }
90 |
91 | func PKCS5Padding(ciphertext []byte, blockSize int) []byte {
92 | padding := blockSize - len(ciphertext)%blockSize
93 | padtext := bytes.Repeat([]byte{byte(padding)}, padding)
94 | return append(ciphertext, padtext...)
95 | }
96 |
97 | func PKCS5UnPadding(origData []byte) []byte {
98 | length := len(origData)
99 | unpadding := int(origData[length-1])
100 | return origData[:(length - unpadding)]
101 | }
102 |
103 | type ecb struct {
104 | b cipher.Block
105 | blockSize int
106 | }
107 |
108 | func newECB(b cipher.Block) *ecb {
109 | return &ecb{
110 | b: b,
111 | blockSize: b.BlockSize(),
112 | }
113 | }
114 |
115 | type ecbEncrypter ecb
116 |
117 | // NewECBEncrypter returns a BlockMode which encrypts in electronic code book
118 | // mode, using the given Block.
119 | func NewECBEncrypter(b cipher.Block) cipher.BlockMode {
120 | return (*ecbEncrypter)(newECB(b))
121 | }
122 | func (x *ecbEncrypter) BlockSize() int { return x.blockSize }
123 | func (x *ecbEncrypter) CryptBlocks(dst, src []byte) {
124 | if len(src)%x.blockSize != 0 {
125 | panic("crypto/cipher: input not full blocks")
126 | }
127 | if len(dst) < len(src) {
128 | panic("crypto/cipher: output smaller than input")
129 | }
130 | for len(src) > 0 {
131 | x.b.Encrypt(dst, src[:x.blockSize])
132 | src = src[x.blockSize:]
133 | dst = dst[x.blockSize:]
134 | }
135 | }
136 |
137 | type ecbDecrypter ecb
138 |
139 | // NewECBDecrypter returns a BlockMode which decrypts in electronic code book
140 | // mode, using the given Block.
141 | func NewECBDecrypter(b cipher.Block) cipher.BlockMode {
142 | return (*ecbDecrypter)(newECB(b))
143 | }
144 | func (x *ecbDecrypter) BlockSize() int { return x.blockSize }
145 | func (x *ecbDecrypter) CryptBlocks(dst, src []byte) {
146 | if len(src)%x.blockSize != 0 {
147 | panic("crypto/cipher: input not full blocks")
148 | }
149 | if len(dst) < len(src) {
150 | panic("crypto/cipher: output smaller than input")
151 | }
152 | for len(src) > 0 {
153 | x.b.Decrypt(dst, src[:x.blockSize])
154 | src = src[x.blockSize:]
155 | dst = dst[x.blockSize:]
156 | }
157 | }
158 |
159 | func PKCS7Padding(ciphertext []byte) []byte {
160 | padding := aes.BlockSize - len(ciphertext)%aes.BlockSize
161 | padtext := bytes.Repeat([]byte{byte(padding)}, padding)
162 | return append(ciphertext, padtext...)
163 | }
164 |
165 | func PKCS7UnPadding(plantText []byte) []byte {
166 | length := len(plantText)
167 | unpadding := int(plantText[length-1])
168 | return plantText[:(length - unpadding)]
169 | }
170 |
171 | func AES256CBCEncrypt(plantText string, key string, iv []byte) (value string, err error) {
172 | plaintext := PKCS7Padding([]byte(plantText))
173 | ciphertext := make([]byte, len(plaintext))
174 | block, err := aes.NewCipher([]byte(key))
175 | if err != nil {
176 | return
177 | }
178 | mode := cipher.NewCBCEncrypter(block, iv)
179 | mode.CryptBlocks(ciphertext, plaintext)
180 | return base64.StdEncoding.EncodeToString(ciphertext), nil
181 | }
182 |
183 | func AES256CBCDecrypt(plantText string, key string, iv string) (value string, err error) {
184 | var block cipher.Block
185 | if block, err = aes.NewCipher([]byte(key)); err != nil {
186 | return
187 | }
188 | var iiv []byte
189 | if iiv, err = base64.StdEncoding.DecodeString(iv); err != nil {
190 | return
191 | }
192 |
193 | var cipherText []byte
194 | if cipherText, err = base64.StdEncoding.DecodeString(plantText); err != nil {
195 | return
196 | }
197 |
198 | mode := cipher.NewCBCDecrypter(block, iiv)
199 | mode.CryptBlocks(cipherText, cipherText)
200 |
201 | cipherText = PKCS7UnPadding(cipherText)
202 | return string(cipherText), nil
203 | }
204 |
205 | //key is 16 bytes
206 | func AesCbcPkcs5Encrypt(origData, key []byte) ([]byte, error) {
207 | block, err := aes.NewCipher(key)
208 | if err != nil {
209 | return nil, err
210 | }
211 | blockSize := block.BlockSize()
212 | origData = PKCS5Padding(origData, blockSize)
213 | blockMode := cipher.NewCBCEncrypter(block, key[:blockSize])
214 | crypted := make([]byte, len(origData))
215 |
216 | blockMode.CryptBlocks(crypted, origData)
217 | return crypted, nil
218 | }
219 |
220 | func AesCbcPkcs5Decrypt(crypted, key []byte) ([]byte, error) {
221 | block, err := aes.NewCipher(key)
222 | if err != nil {
223 | return nil, err
224 | }
225 | blockSize := block.BlockSize()
226 | blockMode := cipher.NewCBCDecrypter(block, key[:blockSize])
227 | origData := make([]byte, len(crypted))
228 | blockMode.CryptBlocks(origData, crypted)
229 | origData = PKCS5UnPadding(origData)
230 |
231 | return origData, nil
232 | }
233 |
234 | func getHmacCode(s string, key []byte) string {
235 | h := hmac.New(sha256.New, key)
236 | _, _ = io.WriteString(h, s)
237 | return fmt.Sprintf("%x", h.Sum(nil))
238 | }
239 |
240 | type Crypt struct {
241 | Iv string `json:"iv"`
242 | Value string `json:"value"`
243 | Mac string `json:"mac"`
244 | }
245 |
246 | func OpenSslAesEncrypt(value string, key string) (payload string, err error) {
247 | iv := make([]byte, 16)
248 | _, err = rand.Read(iv)
249 |
250 | var encode string
251 | if encode, err = AES256CBCEncrypt(value, key, iv); err != nil {
252 | return
253 | }
254 |
255 | ivv := base64.StdEncoding.EncodeToString(iv)
256 |
257 | crypt := Crypt{Iv: ivv, Value: encode, Mac: getHmacCode(ivv+encode, []byte(key))}
258 |
259 | var bs []byte
260 | if bs, err = json.Marshal(crypt); err != nil {
261 | return
262 | }
263 |
264 | return base64.StdEncoding.EncodeToString(bs), nil
265 | }
266 |
267 | func OpenSslAesDecrypt(payload string, key string) (value string, err error) {
268 | var bs []byte
269 | if bs, err = base64.StdEncoding.DecodeString(payload); err != nil {
270 | return
271 | }
272 | crypt := Crypt{}
273 | if err = json.Unmarshal(bs, &crypt); err != nil {
274 | return
275 | }
276 |
277 | return AES256CBCDecrypt(crypt.Value, key, crypt.Iv)
278 | }
279 |
280 |
281 | // Demo Application
282 | func aesEncryptDemo(content string) string {
283 | aesKey := "b3c65d06a1cf4dbda57af0af5c63e85f"
284 | aesKeyByte, _ := hex.DecodeString(aesKey)
285 | encrypt, _ := AesEcbPkcs5Encrypt(content, aesKeyByte)
286 | return hex.EncodeToString(encrypt)
287 | }
288 |
289 | func aesDecryptDemo(crypt string) (reply string, err error) {
290 | if crypt == "" {
291 | return
292 | }
293 | aesKey := "b3c65d06a1cf4dbda57af0af5c63e85f"
294 | aesKeyByte, _ := hex.DecodeString(aesKey)
295 | cryptByte, _ := hex.DecodeString(crypt)
296 | decryptByte, err := AesEcbPkcs5Decrypt(cryptByte, aesKeyByte)
297 | return string(decryptByte), err
298 | }
299 |
300 | // generate sign
301 | func genSignDemo(params string) (sign string) {
302 | sha1key := "564826"
303 | time := time.Now().Format("20060102150405")
304 | sourceData := "¶ms=" + params + "&time=" + time
305 | return HMACSHA1(sourceData, sha1key)
306 | }
307 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Golang开发手记
2 |
3 |
4 | 用Go语言做开发,在这个Repository里整理一些常用的案例,计划慢慢积累作为以后开发的CookBook。
5 |
6 | 仓库里所有知识点对应的代码示例都可正常运行,拿来直接应用到生产项目上也不会有问题。因为目的是积累Go语言开发的案头书,所以并不会讲源码分析之类的东西,如果想更多了解 Go 语言各种内部原理和源代码解读欢迎关注我的公众号 **「网管叨bi叨」** ,那里除了应用还会用大量的原理分析。
7 |
8 | **另外最近我推出了自己的Go实战专栏课程,专栏配套一个专属的私有项目,通过tag版本追踪记录每个章节代码的变更,让大家能轻松跟上学习**。
9 |
10 | **专栏分为五大部分**:
11 |
12 |
13 |
14 | **访问:https://xiaobot.net/p/golang 或者扫码下方海报二维码可查看课程详情**
15 |
16 |
17 |
18 |
19 |
20 | ## 目录
21 | - 前期准备
22 | - [环境安装](https://github.com/kevinyan815/gocookbook/issues/74)
23 | - [基础语法](https://github.com/kevinyan815/gocookbook/blob/master/lang-basic/README.md)
24 | - 初始化
25 | - [Go应用初始化工作的执行顺序](https://github.com/kevinyan815/gocookbook/issues/24)
26 | - [Go语言init函数的六个特征](https://mp.weixin.qq.com/s/P-BhuQy1Vd3lxlYgClDAJA)
27 |
28 | - 项目工程
29 | - [依赖管理工具GOMODULE](https://mp.weixin.qq.com/s/xtvTUl2IZFQ79dSR_m-b7A)
30 | - [GoModules 管理私有依赖模块](https://mp.weixin.qq.com/s/8E1PwnglrS18hZsUEvE-Qw)
31 | - [Go Modules 依赖的版本管理](https://mp.weixin.qq.com/s/ptJK7CDHCr6P4JCdsUXKdg)
32 | - [常用编码规范](https://github.com/kevinyan815/gocookbook/issues/61)
33 | - [Go 里面怎么实现枚举](https://github.com/kevinyan815/gocookbook/issues/73)
34 | - 字符串
35 | - [看透Go语言的字符串](https://github.com/kevinyan815/gocookbook/issues/40)
36 | - [操作中文字符串](https://github.com/kevinyan815/gocookbook/issues/11)
37 | - [常用字符串操作](https://yourbasic.org/golang/string-functions-reference-cheat-sheet/)
38 | - [string、int、int64 类型之间的相互转换](https://yourbasic.org/golang/convert-int-to-string/)
39 | - [高性能地拼接字符串](https://github.com/kevinyan815/gocookbook/issues/68)
40 | - 数组
41 | - [数组的上限推导和越界检查](https://github.com/kevinyan815/gocookbook/issues/37)
42 | - Slice切片
43 | - [声明和初始化](https://github.com/kevinyan815/gocookbook/issues/3)
44 | - [追加和删除元素](https://github.com/kevinyan815/gocookbook/issues/4)
45 | - [过滤重复元素](https://github.com/kevinyan815/gocookbook/issues/5)
46 | - [排序结构体切片](https://github.com/kevinyan815/gocookbook/issues/12)
47 | - [切片并非引用类型](https://github.com/kevinyan815/gocookbook/issues/38)
48 | - [使用切片时要注意的几个坑](https://mp.weixin.qq.com/s/ISLNTCo7Jr9XnqAEhDuYcw)
49 | - [实用的切片工具函数](https://github.com/kevinyan815/gocookbook/blob/master/codes/slice_util/code.go)
50 | - Map
51 | - [(通识概念)哈希表的设计原理](https://github.com/kevinyan815/gocookbook/issues/39)
52 | - [声明和初始化](https://github.com/kevinyan815/gocookbook/issues/6)
53 | - [不要向nil map写入键值](https://github.com/kevinyan815/gocookbook/issues/7)
54 | - [修改map](https://github.com/kevinyan815/gocookbook/issues/8)
55 | - [遍历map](https://github.com/kevinyan815/gocookbook/issues/15)
56 | - [make 和 new](https://github.com/kevinyan815/gocookbook/issues/53)
57 | - [Go 函数的 Map 型参数,会发生扩容后指向不同底层内存的事儿吗?](https://mp.weixin.qq.com/s/WfzeNWV1j0fSXUiVOLe5jw)
58 | - 结构体
59 | - [巧用匿名结构体](https://github.com/kevinyan815/gocookbook/issues/89)
60 | - 读写数据
61 | - [编码JSON](https://github.com/kevinyan815/gocookbook/issues/2)
62 | - [解码JSON](https://github.com/kevinyan815/gocookbook/issues/1)
63 | - [如何控制Go编码JSON数据时的行为](https://mp.weixin.qq.com/s/L45G42s0DMZhStszZnmTGQ)
64 | - [逐行读取文件](https://github.com/kevinyan815/gocookbook/issues/13)
65 | - [Go语言IO库使用方法汇总 (进行IO操作时到底应该用哪个库)](https://github.com/kevinyan815/gocookbook/issues/62)
66 | - [字节序:大端序和小端序](https://mp.weixin.qq.com/s/ri2tt4nvEJub-wEsh0WPPA)
67 | - [用Golang读写HTTP请求(附Options设计模式实现)](https://github.com/kevinyan815/gocookbook/issues/64)
68 | - 目录和文件操作
69 | - [Go语言文件操作大全](https://mp.weixin.qq.com/s/dQUEq0lJekEUH4CHEMwANw)
70 | - [加餐版--实用的目录和文件操作](https://github.com/kevinyan815/gocookbook/issues/84)
71 |
72 | - 指针
73 | - [用法和使用限制](https://github.com/kevinyan815/gocookbook/issues/41)
74 | - [uintptr 和 unsafer.Pointer](https://github.com/kevinyan815/gocookbook/issues/42)
75 | - [扩展阅读:内存对齐](https://github.com/kevinyan815/gocookbook/issues/43)
76 | - 接口
77 | - [认识Go的接口](https://github.com/kevinyan815/gocookbook/issues/45)
78 | - [Go接口的类型和方法的接收者](https://github.com/kevinyan815/gocookbook/issues/46)
79 | - [接口的类型转换和断言](https://github.com/kevinyan815/gocookbook/issues/47)
80 | - [接口调用时的动态派发](https://github.com/kevinyan815/gocookbook/issues/67)
81 | - [Range 迭代](https://github.com/kevinyan815/gocookbook/issues/15)
82 | - 函数
83 | - [调用惯例和参数传递](https://github.com/kevinyan815/gocookbook/issues/44)
84 | - [defer的用法和行为分析](https://github.com/kevinyan815/gocookbook/issues/51)
85 | - [panic和recover](https://github.com/kevinyan815/gocookbook/issues/52)
86 |
87 | - 错误处理
88 | - [关于Golang错误处理的一些建议](https://github.com/kevinyan815/gocookbook/issues/66)
89 | - [Go代码更优雅地错误处理](https://github.com/kevinyan815/gocookbook/issues/82)
90 | - [Go 1.13后的包装错误和相关接口](https://mp.weixin.qq.com/s/SFbSAGwQgQBVWpySYF-rkw)
91 | - 包
92 | - [内部包](https://github.com/kevinyan815/gocookbook/issues/58)
93 | - 标准库
94 | - [正则表达式](https://github.com/kevinyan815/gocookbook/issues/9)
95 | - [Time 常用基础操作](https://github.com/kevinyan815/gocookbook/issues/14)
96 | - [Time 的时区和时间计算操作汇总](https://github.com/kevinyan815/gocookbook/issues/85)
97 | - 数据库访问
98 | - [使用标准库 database/sql 访问数据库](https://mp.weixin.qq.com/s/bhsFCXTZ_TBP0EvyRM-bdA)
99 | - [使用ORM库 gorm 访问数据库](https://mp.weixin.qq.com/s/N-ZAgRrEu2FJBlApIhuVsg)
100 | - [GORM 入门指南](https://www.liwenzhou.com/posts/Go/gorm/)
101 | - [GORM CRUD指南](https://www.liwenzhou.com/posts/Go/gorm-crud/)
102 | - 系统编程
103 | - [命令行flag](https://github.com/kevinyan815/gocookbook/issues/36)
104 | - [监听系统信号](https://github.com/kevinyan815/gocookbook/issues/55)
105 | - 并发编程
106 | - [Context上下文](https://github.com/kevinyan815/gocookbook/issues/50)
107 | - [Context 使用示例](https://github.com/kevinyan815/gocookbook/issues/50)
108 | - [图解 Context 原理](https://mp.weixin.qq.com/s/NNYyBLOO949ElFriLVRWiA)
109 | - [Context 源码学习](https://mp.weixin.qq.com/s/SJna8UAoV9GTGCuRezC9Qw)
110 | - [Channel 基本概念和用法](https://github.com/kevinyan815/gocookbook/issues/54)
111 | - [互斥锁的典型用法和常见误区](https://github.com/kevinyan815/gocookbook/issues/88)
112 | - [用WaitGroup进行协同等待](https://github.com/kevinyan815/gocookbook/issues/34)
113 | - [ErrorGroup 兼顾协同等待和错误传递](https://github.com/kevinyan815/gocookbook/issues/35)
114 | - [Reset计时器的正确姿势](https://github.com/kevinyan815/gocookbook/issues/17)
115 | - [结合cancelCtx, Timer, Goroutine, Channel的一个例子](https://github.com/kevinyan815/gocookbook/issues/18)
116 | - [使用WaitGroup, Channel和Context打造一个并发用户标签查询器](https://github.com/kevinyan815/gocookbook/issues/21)
117 | - [使用sync.Cond实现一个有限容量的队列](https://github.com/kevinyan815/gocookbook/issues/22)
118 | - [使用信号量控制有限资源的并发访问](https://github.com/kevinyan815/gocookbook/issues/30)
119 | - [使用Chan扩展互斥锁的功能](https://github.com/kevinyan815/gocookbook/issues/25)
120 | - [用SingleFlight合并重复请求](https://github.com/kevinyan815/gocookbook/issues/31)
121 | - [CyclicBarrier 循环栅栏](https://github.com/kevinyan815/gocookbook/issues/32)
122 | - [原子操作的用法详解](https://github.com/kevinyan815/gocookbook/issues/65)
123 | - 反射
124 | - [Go反射的使用教程](https://github.com/kevinyan815/gocookbook/issues/69)
125 | - [反射最常见的应用--结构体标签](https://github.com/kevinyan815/gocookbook/issues/70)
126 | - 线上问题解决实录
127 | - [重定向运行时panic到日志文件](https://github.com/kevinyan815/gocookbook/issues/19)
128 | - [用Go的交叉编译和条件编译让自己的软件包运行在多平台上](https://github.com/kevinyan815/gocookbook/issues/20)
129 | - [在容器里怎么设置GOMAXPRCS](https://github.com/kevinyan815/gocookbook/issues/57)
130 | - [预防并发搞垮友军的几个方法](https://github.com/kevinyan815/gocookbook/issues/63)
131 | - 编译原理
132 | - [Go程序的编译原理](https://github.com/kevinyan815/gocookbook/issues/56)
133 | - 一些有意思的小程序
134 | - [一个简单的概率抽奖工具](https://github.com/kevinyan815/gocookbook/issues/23)
135 | - [限流算法之计数器](https://github.com/kevinyan815/gocookbook/issues/29)
136 | - [限流算法之滑动窗口](https://github.com/kevinyan815/gocookbook/issues/26)
137 | - [限流算法之漏桶](https://github.com/kevinyan815/gocookbook/issues/28)
138 | - [限流算法之令牌桶](https://github.com/kevinyan815/gocookbook/issues/27)
139 | - [并发趣题--H2O制造工厂](https://github.com/kevinyan815/gocookbook/issues/33)
140 | - [可以自解释的Token生成算法](https://github.com/kevinyan815/gocookbook/blob/master/codes/gen_token/main.go)
141 | - [生成分布式链路追踪traceid和spanid的算法](https://github.com/kevinyan815/gocookbook/blob/master/codes/trace_span/main.go)
142 | - [一个带阻塞限流器的HTTP客户端](https://github.com/kevinyan815/gocookbook/blob/master/codes/http_client_with_rate/http_rl_client.go)
143 | - [AES加解密,HMAC验签](https://github.com/kevinyan815/gocookbook/blob/master/codes/crypto_utils/aes.go)
144 | - [密码复杂度验证](https://github.com/kevinyan815/gocookbook/tree/master/codes/password_complexity)
145 | - gRPC应用实践
146 | - [interceptor拦截器--gRPC的Middleware](https://github.com/kevinyan815/gocookbook/issues/60)
147 | - Go服务治理
148 | - [让Go进程监控自己的资源使用情况](https://github.com/kevinyan815/gocookbook/issues/71)
149 | - [Go服务进行自动采样性能分析的方案设计思路](https://github.com/kevinyan815/gocookbook/issues/72)
150 | - [从Go log库到Zap,怎么打造出好用又实用的Logger](https://mp.weixin.qq.com/s/Jh2iFY5uGe0qCFdKZWjotA)
151 | - [分布式服务的日志该怎么串联起来](https://mp.weixin.qq.com/s/M2jNnLkYaearwyRERnt0tA)
152 | - Go 单元测试通关指南
153 | - [go test 工具集和表格测试](https://github.com/kevinyan815/gocookbook/issues/75)
154 | - [模拟网络请求和接口调用](https://github.com/kevinyan815/gocookbook/issues/76)
155 | - [用gock 拦截HTTP请求,Mock API调用](https://github.com/kevinyan815/gocookbook/issues/92)
156 | - [原生数据库查询的 Mock 测试](https://github.com/kevinyan815/gocookbook/issues/77)
157 | - [数据库ORM的Mock测试](https://github.com/kevinyan815/gocookbook/issues/80)
158 | - [Mock接口实现和对接口打桩](https://github.com/kevinyan815/gocookbook/issues/78)
159 | - [全能打桩工具Go Monkey的使用介绍](https://github.com/kevinyan815/gocookbook/issues/79)
160 | - [Go单元测试-Goconvey的使用](https://github.com/kevinyan815/gocookbook/issues/91)
161 | - [如何写出可测试的代码](https://github.com/kevinyan815/gocookbook/issues/81)
162 | - [用单元测试发现协程泄露隐患](https://mp.weixin.qq.com/s/XrhdU95CswLGwS0CLxqZmg)
163 | - [Go 1.18 模糊测试使用教程](https://mp.weixin.qq.com/s/7I0zB_AsltzDLmc9ew48Bg)
164 |
--------------------------------------------------------------------------------
/lang-basic/content/chapter5.md:
--------------------------------------------------------------------------------
1 | > 以下内容为几年前学Go语言时记录的,发布在这里让这个项目对更多人能起到参考作用。 更多时下流行技术的应用和实战教程,可通过我公众号「网管叨bi叨」每周的推文来学习
2 |
3 | ### Goroutines
4 |
5 | - 在Go语言中,每一个并发的执行单元叫作goroutine。设想一个程序中有两个函数,假设两个函数没有相互之间的调用关系。一个线性的程序会先调用其中的一个函数,然后再调用另一个。如果程序中包含多个goroutine,对两个函数的调用则可能发生在同一时刻。
6 | - 当一个程序启动时,其main函数即在一个单独的goroutine中运行,我们叫它main goroutine。新的goroutine会用go语句来创建。在语法上,go语句是在一个普通的函数或方法调用前加上关键字go。go语句会使其语句中的函数在一个新创建的goroutine中运行。而go语句本身会迅速地完成。
7 |
8 | ```go
9 | f() // call f(); wait for it to return
10 | go f() // create a new goroutine that calls f(); don't wait
11 | ```
12 |
13 | - 主函数返回时,所有的goroutine都会被直接打断,程序退出。除了从主函数退出或者直接终止程序之外,没有其它的编程方法能够让一个goroutine来打断另一个的执行,但是之后可以看到一种方式来实现这个目的,通过goroutine之间的通信来让一个goroutine请求其它的goroutine,并被请求的goroutine自行结束执行。
14 |
15 | ### Channel
16 |
17 | - 如果说goroutine是Go语言程序的并发体的话,那么channels它们之间的通信机制。它可以让一个goroutine通过它给另一个goroutine发送值信息。每个channel都有一个特殊的类型,也就是channel可发送数据的类型。一个可以发送int类型数据的channel一般写为chan int。
18 | - 使用内置的make函数,我们可以创建一个channel:
19 |
20 | ```Go
21 | ch := make(chan int)
22 | ```
23 |
24 | - 和map类似,channel也一个对make函数创建的底层数据结构的引用。当我们复制一个channel或把 channel用于函数参数传递时,我们只是拷贝了一个channel引用,因此调用者和被调用者将引用同一个channel对象。和其它的引用类型一样,channel的零值也是nil。
25 |
26 | - channel有发送和接收两个主要操作,都是通信行为。一个发送语句将一个值从一个goroutine通过channel发送到另一个执行接收操作的goroutine。发送和接收两个操作都是用`<-`运算符。在发送语句中,`<-`运算符分割channel和要发送的值。在接收语句中,`<-`运算符写在channel对象之前。一个不使用接收结果的接收操作也是合法的。
27 |
28 | ```Go
29 | ch <- x // 发送消息
30 | x = <-ch // 从 channel 中接收消息
31 | <-ch // 从 channel 接收并丢弃消息
32 | ```
33 |
34 | - Channel还支持close操作,用于关闭channel,随后对基于该channel的任何发送操作都将导致panic异常。对一个已经被close过的channel执行接收操作依然可以接收到之前已经成功发送的数据;如果channel中已经没有数据的话将产生一个零值的数据。
35 |
36 | 使用内置的close函数就可以关闭一个channel:
37 |
38 | ```Go
39 | close(ch)
40 | ```
41 |
42 | 以最简单方式调用make函数创建的时一个无缓冲的channel,但是我们也可以指定第二个整形参数,对应channel的容量。如果channel的容量大于零,那么该channel就是带缓冲的channel。
43 |
44 | ```Go
45 | ch = make(chan int) // unbuffered channel
46 | ch = make(chan int, 0) // unbuffered channel
47 | ch = make(chan int, 3) // buffered channel with capacity 3
48 | ```
49 |
50 | #### 无缓冲 channel
51 |
52 | - 一个基于无缓冲Channel的发送操作将导致发送者goroutine阻塞,直到另一个goroutine在相同的Channel上执行接收操作,当发送的值通过Channel成功传输之后,两个goroutine可以继续执行后面的语句。反之,如果接收操作先发生,那么接收者goroutine也将阻塞,直到有另一个goroutine在相同的Channel上执行发送操作。
53 |
54 | - 下面的程序在 main 函数的 goroutine 中将标准输入复制到server,因此当客户端程序关闭标准输入时,后台goroutine可能依然在工作。我们需要让主goroutine等待后台goroutine完成工作后再退出,我们使用了一个channel来同步两个goroutine,在后台goroutine返回之前,它先打印一个日志信息,然后向done对应的channel发送一个值。主goroutine在退出前先等待从done对应的channel接收一个值。因此,总是可以在程序退出前正确输出“done”消息。
55 |
56 | ```Go
57 | func main() {
58 | conn, err := net.Dial("tcp", "localhost:8000")
59 | if err != nil {
60 | log.Fatal(err)
61 | }
62 | done := make(chan struct{})
63 | go func() {
64 | io.Copy(os.Stdout, conn) // NOTE: ignoring errors
65 | log.Println("done")
66 | done <- struct{}{} // signal the main goroutine
67 | }()
68 | mustCopy(conn, os.Stdin)
69 | conn.Close()
70 | <-done // wait for background goroutine to finish
71 | }
72 | ```
73 |
74 | - 基于channel发送消息有两个重要方面。首先每个消息都有一个值,但是有时候通讯的事实和发生的时刻也同样重要。当我们更希望强调通讯发生的时刻时,我们将它称为**消息事件**。有些消息事件并不携带额外的信息,它仅仅是用作两个goroutine之间的同步,这时候我们可以用`struct{}`空结构体作为channels元素的类型,虽然也可以使用bool或int类型实现同样的功能,`done <- 1`语句也比`done <- struct{}{}`更短。
75 |
76 | - 如果发送者知道,没有更多的值需要发送到channel的话,那么让接收者也能及时知道没有多余的值可接收将是有用的,因为接收者可以停止不必要的接收等待。这可以通过内置的close函数来关闭channel实现:
77 |
78 | ```Go
79 | close(naturals)
80 | ```
81 |
82 | - 当一个channel被关闭后,再向该channel发送数据将导致panic异常。当一个被关闭的channel中已经发送的数据都被成功接收后,后续的接收操作将不再阻塞,它们会立即返回一个零值。
83 | - 接收 channel 语句中可以额外增加第二个值,标识 chnnel 是否已经关闭
84 |
85 | ```Go
86 | x, ok := <-naturals
87 | ```
88 |
89 | - Go语言的range循环可直接在channels上面迭代。使用range循环是上面处理模式的简洁语法,它依次从channel接收数据,当channel被关闭并且没有值可接收时跳出循环。
90 |
91 | 在下面的程序中,我们的计数器goroutine只生成100个含数字的序列,然后关闭naturals对应的channel,这将导致计算平方数的squarer对应的goroutine可以正常终止循环并关闭squares对应的channel。(在一个更复杂的程序中,可以通过defer语句关闭对应的channel。)最后,主goroutine也可以正常终止循环并退出程序。
92 |
93 | ```Go
94 | func main() {
95 | naturals := make(chan int)
96 | squares := make(chan int)
97 |
98 | // Counter
99 | go func() {
100 | for x := 0; x < 100; x++ {
101 | naturals <- x
102 | }
103 | close(naturals)
104 | }()
105 |
106 | // Squarer
107 | go func() {
108 | for x := range naturals {
109 | squares <- x * x
110 | }
111 | close(squares)
112 | }()
113 |
114 | // Printer (in main goroutine)
115 | for x := range squares {
116 | fmt.Println(x)
117 | }
118 | }
119 | ```
120 |
121 | - 试图重复关闭一个channel将导致panic异常,试图关闭一个nil值的channel也将导致panic异常。关闭一个channels还会触发一个广播机制,我们将在后面讨论。
122 |
123 | #### 单方向的 channel
124 |
125 | - 当一个channel作为一个函数参数是,它一般总是被专门用于只发送或者只接收。
126 |
127 | 为了表明这种意图并防止被滥用,Go语言的类型系统提供了单方向的channel类型,分别用于只发送或只接收的channel。类型`chan<- int`表示一个只发送int的channel,只能发送不能接收。相反,类型`<-chan int`表示一个只接收int的channel,只能接收不能发送。(箭头`<-`和关键字chan的相对位置表明了channel的方向。)这种限制将在编译期检测。
128 |
129 | - 因为关闭操作只用于断言不再向channel发送新的数据,所以只有在发送者所在的goroutine才会调用close函数,因此对一个只接收的channel调用close将是一个编译错误。
130 |
131 | 这是改进的版本,这一次参数使用了单方向channel类型:
132 |
133 | ```Go
134 | func counter(out chan<- int) {
135 | for x := 0; x < 100; x++ {
136 | out <- x
137 | }
138 | close(out)
139 | }
140 |
141 | func squarer(out chan<- int, in <-chan int) {
142 | for v := range in {
143 | out <- v * v
144 | }
145 | close(out)
146 | }
147 |
148 | func printer(in <-chan int) {
149 | for v := range in {
150 | fmt.Println(v)
151 | }
152 | }
153 |
154 | func main() {
155 | naturals := make(chan int)
156 | squares := make(chan int)
157 | go counter(naturals)
158 | go squarer(squares, naturals)
159 | printer(squares)
160 | }
161 | ```
162 |
163 | 调用counter(naturals)将导致将`chan int`类型的naturals隐式地转换为`chan<- int`类型只发送型的channel。调用printer(squares)也会导致相似的隐式转换,这一次是转换为`<-chan int`类型只接收型的channel。任何双向channel向单向channel变量的赋值操作都将导致该隐式转换。
164 |
165 | #### 带缓冲的 channel
166 |
167 | 带缓存的Channel内部持有一个元素队列。队列的最大容量是在调用make函数创建channel时通过第二个参数指定的。下面的语句创建了一个可以持有三个字符串元素的带缓存Channel。图8.2是ch变量对应的channel的图形表示形式。
168 |
169 | ```Go
170 | ch = make(chan string, 3)
171 | ```
172 |
173 | 
174 |
175 | 向缓存Channel的发送操作就是向内部缓存队列的尾部插入元素,接收操作则是从队列的头部删除元素。如果内部缓存队列是满的,那么发送操作将阻塞直到因另一个goroutine执行接收操作而释放了新的队列空间。相反,如果channel是空的,接收操作将阻塞直到有另一个goroutine执行发送操作而向队列插入元素。
176 |
177 | 我们可以在无阻塞的情况下连续向新创建的channel发送三个值:
178 |
179 | ```Go
180 | ch <- "A"
181 | ch <- "B"
182 | ch <- "C"
183 | ```
184 |
185 | 此刻,channel的内部缓存队列将是满的(图8.3),如果有第四个发送操作将发生阻塞。
186 |
187 | 
188 |
189 | 如果我们接收一个值,
190 |
191 | ```Go
192 | fmt.Println(<-ch) // "A"
193 | ```
194 |
195 | 那么channel的缓存队列将不是满的也不是空的(图8.4),因此对该channel执行的发送或接收操作都不会发送阻塞。通过这种方式,channel的缓存队列解耦了接收和发送的goroutine。
196 |
197 | 
198 |
199 | 在某些特殊情况下,程序可能需要知道channel内部缓存的容量,可以用内置的cap函数获取:
200 |
201 | ```Go
202 | fmt.Println(cap(ch)) // "3"
203 | ```
204 |
205 | 同样,对于内置的len函数,如果传入的是channel,那么将返回channel内部缓存队列中有效元素的个数。因为在并发程序中该信息会随着接收操作而失效,但是它对某些故障诊断和性能优化会有帮助。
206 |
207 | ```Go
208 | fmt.Println(len(ch)) // "2"
209 | ```
210 |
211 | 在继续执行两次接收操作后channel内部的缓存队列将又成为空的,如果有第四个接收操作将发生阻塞:
212 |
213 | ```Go
214 | fmt.Println(<-ch) // "B"
215 | fmt.Println(<-ch) // "C"
216 | ```
217 |
218 |
219 |
220 | 下面的例子展示了一个使用了带缓存channel的应用。它并发地向三个镜像站点发出请求,三个镜像站点分散在不同的地理位置。它们分别将收到的响应发送到带缓存channel,最后接收者只接收第一个收到的响应,也就是最快的那个响应。因此mirroredQuery函数可能在另外两个响应慢的镜像站点响应之前就返回了结果。(顺便说一下,多个goroutines并发地向同一个channel发送数据,或从同一个channel接收数据都是常见的用法。)
221 |
222 | ```Go
223 | func mirroredQuery() string {
224 | responses := make(chan string, 3)
225 | go func() { responses <- request("asia.gopl.io") }()
226 | go func() { responses <- request("europe.gopl.io") }()
227 | go func() { responses <- request("americas.gopl.io") }()
228 | return <-responses // return the quickest response
229 | }
230 |
231 | func request(hostname string) (response string) { /* ... */ }
232 | ```
233 |
234 | 如果我们使用了无缓存的channel,那么两个慢的goroutines将会因为没有人接收而被永远卡住。这种情况,称为goroutines泄漏,这将是一个BUG。和垃圾变量不同,泄漏的goroutines并不会被自动回收,因此确保每个不再需要的goroutine能正常退出是重要的。
235 |
236 | 关于无缓存或带缓存channel之间的选择,或者是带缓存channel的容量大小的选择,都可能影响程序的正确性。无缓存channel更强地保证了每个发送操作与相应的同步接收操作;但是对于带缓存channel,这些操作是解耦的。同样,即使我们知道将要发送到一个channel的信息的数量上限,创建一个对应容量大小带缓存channel也是不现实的,因为这要求在执行任何接收操作之前缓存所有已经发送的值。如果未能分配足够的缓冲将导致程序死锁。
237 |
238 | #### 用带缓冲的channel 控制并发数量
239 |
240 | 此外对于buffered channel,我们可以用一个有容量限制的buffered channel来控制并发,这类似于操作系统里的计数信号量概念。从概念上讲,channel里的n个空槽代表n个可以处理内容的token(通行证),从channel里接收一个值会释放其中的一个token,并且生成一个新的空槽位。这样保证了在没有接收介入时最多有n个发送操作。(这里可能我们拿channel里填充的槽来做token更直观一些,不过还是这样吧~)。由于channel里的元素类型并不重要,我们用一个零值的struct{}来作为其元素。
241 |
242 | 下面的`crawl`函数,将对`links.Extract`的调用操作用获取、释放token的操作包裹起来,来确保同一时间对其只有20个调用。信号量数量和其能操作的`IO`资源数量应保持接近。
243 |
244 | ```go
245 | // goroutine获取token后,可以进行抓取操作,如果满20了
246 | // 那么 goroutine 会等到有获取 token 后再去执行
247 | var tokens = make(chan struct{}, 20)
248 |
249 | func crawl(url string) []string {
250 | fmt.Println(url)
251 | tokens <- struct{}{} // 获取 token
252 | list, err := links.Extract(url)
253 | <-tokens // 释放 token
254 | if err != nil {
255 | log.Print(err)
256 | }
257 | return list
258 | }
259 | ```
260 |
261 | ### 并发循环的一个典型示例
262 |
263 | 在并发循环中为了知道最后一个goroutine什么时候结束(最后一个结束并不一定是最后一个开始),我们需要一个递增的计数器,在每一个goroutine启动时加一,在goroutine退出时减一。这需要一种特殊的计数器,这个计数器需要在多个goroutine操作时做到安全并且提供在其减为零之前一直等待的一种方法。这种计数类型被称为sync.WaitGroup,下面的代码就用到了这种方法:
264 |
265 | ```go
266 | // makeThumbnails6为从通道中接收到的每个文件创建缩略图。
267 | // 返回每个创建的缩略图所占的自己数。
268 | func makeThumbnails6(filenames <-chan string) int64 {
269 | sizes := make(chan int64)
270 | var wg sync.WaitGroup // number of working goroutines
271 | for f := range filenames {
272 | wg.Add(1)
273 | // worker
274 | go func(f string) {
275 | defer wg.Done()
276 | thumb, err := thumbnail.ImageFile(f)
277 | if err != nil {
278 | log.Println(err)
279 | return
280 | }
281 | info, _ := os.Stat(thumb) // OK to ignore error
282 | sizes <- info.Size()
283 | }(f)
284 | }
285 |
286 | // closer
287 | go func() {
288 | wg.Wait()
289 | close(sizes)
290 | }()
291 |
292 | var total int64
293 | for size := range sizes {
294 | total += size
295 | }
296 | return total
297 | }
298 | ```
299 |
300 | 注意Add和Done方法的不对策。Add是为计数器加一,必须在worker goroutine开始之前调用,而不是在goroutine中;否则的话我们没办法确定Add是在"closer" goroutine调用Wait之前被调用。并且Add还有一个参数,但Done却没有任何参数;其实它和Add(-1)是等价的。我们使用defer来确保计数器即使是在出错的情况下依然能够正确地被减掉。上面的程序代码结构是当我们使用并发循环,但又不知道迭代次数时很通常而且很地道的写法。
301 |
302 | ### select多通道复用
303 |
304 | select语句的一般形式,和switch语句稍微有点相似。也会有几个case和最后的default选择支。每一个case代表一个通信操作(在某个channel上进行发送或者接收)并且会包含一些语句组成的一个语句块。
305 |
306 | ```go
307 | select {
308 | case <-ch1:
309 | // ...
310 | case x := <-ch2:
311 | // ...use x...
312 | case ch3 <- y:
313 | // ...
314 | default:
315 | // ...
316 | }
317 | ```
318 |
319 | 一个接收表达式可能只包含接收表达式自身(译注:不把接收到的值赋值给变量什么的),就像上面的第一个case,或者包含在一个简短的变量声明中,像第二个case里一样;第二种形式让你能够在当前 case 块中引用接收到的值。
320 |
321 | select会等待case中有能够执行的case时去执行。当条件满足时,select才会去通信并执行case之后的语句;这时候其它通信是不会执行的。一个没有任何case的select语句写作select{},会永远地等待下去。
322 |
323 | 下面这个例子更微秒。ch这个channel的buffer大小是1,所以会交替的为空或为满,所以只有一个case可以进行下去,无论i是奇数或者偶数,它都会打印0 2 4 6 8。
324 |
325 | ```go
326 | ch := make(chan int, 1)
327 | for i := 0; i < 10; i++ {
328 | select {
329 | case x := <-ch:
330 | fmt.Println(x) // "0" "2" "4" "6" "8"
331 | case ch <- i:
332 | }
333 | }
334 | ```
335 |
336 | 如果多个case同时就绪时,select会随机地选择一个执行,这样来保证每一个channel都有平等的被select的机会。增加上面例子的buffer大小会使其输出变得不确定,因为当buffer既不为满也不为空时,select语句的执行情况就像是抛硬币的行为一样是随机的。
337 |
--------------------------------------------------------------------------------
/lang-basic/content/chapter4.md:
--------------------------------------------------------------------------------
1 | > 以下内容为几年前学Go语言时记录的,发布在这里让这个项目对更多人能起到参考作用。 更多时下流行技术的应用和实战教程,可通过我公众号「网管叨bi叨」每周的推文来学习
2 |
3 | ### 接口概述
4 | - 一个具体的类型可以准确的描述它所代表的值并且展示出对类型本身的一些操作方式就像数字类型的算术操作,切片类型的索引、附加和取范围操作。总的来说,当你拿到一个具体的类型时你就知道它的本身是什么和你可以用它来做什么。
5 |
6 | - 在Go语言中还存在着另外一种类型:接口类型。接口类型是一种抽象的类型。它不会暴露出它所代表的对象的内部结构和这个对象支持的基础操作的集合;它只会展示出自己的方法。也就是说当你有看到一个接口类型的值时,你不知道它是什么,唯一知道的就是可以通过它的方法来做什么。
7 |
8 | - fmt.Printf它会把结果写到标准输出和fmt.Sprintf它会把结果以字符串的形式返回,实际上,这两个函数都使用了另一个函数fmt.Fprintf来进行封装。fmt.Fprintf这个函数对它的计算结果会被怎么使用是完全不知道的。
9 |
10 | ```go
11 | package fmt
12 |
13 | func Fprintf(w io.Writer, format string, args ...interface{}) (int, error)
14 | func Printf(format string, args ...interface{}) (int, error) {
15 | return Fprintf(os.Stdout, format, args...)
16 | }
17 | func Sprintf(format string, args ...interface{}) string {
18 | var buf bytes.Buffer
19 | Fprintf(&buf, format, args...)
20 | return buf.String()
21 | }
22 | ```
23 |
24 | Fprintf函数中的第一个参数也不是一个文件类型。它是io.Writer类型这是一个接口类型定义如下:
25 |
26 | ```go
27 | package io
28 |
29 | type Writer interface {
30 | Write(p []byte) (n int, err error)
31 | }
32 | ```
33 |
34 | io.Writer类型定义了函数Fprintf和这个函数调用者之间的约定,只要是实现了`io.Writer`接口的类型都可以作为 Fprintf 函数的第一个参数。
35 |
36 | - 一个类型可以自由的使用另一个满足相同接口的类型来进行替换被称作可替换性(LSP里氏替换)。这是一个面向对象的特征。
37 |
38 | ### 接口定义
39 | - io.Writer类型是用的最广泛的接口之一,因为它提供了所有的类型写入bytes的抽象,包括文件类型,内存缓冲区,网络链接,HTTP客户端,压缩工具,哈希等等。io包中定义了很多其它有用的接口类型。Reader可以代表任意可以读取bytes的类型,Closer可以是任意可以关闭的值,例如一个文件或是网络链接。
40 |
41 | ```go
42 | package io
43 | type Reader interface {
44 | Read(p []byte) (n int, err error)
45 | }
46 | type Closer interface {
47 | Close() error
48 | }
49 | ```
50 |
51 | - 可以通过组合已有接口类型来定义新的接口类型,比如 io 包中的
52 |
53 | ```go
54 | type ReadWriter interface {
55 | Reader
56 | Writer
57 | }
58 | type ReadWriteCloser interface {
59 | Reader
60 | Writer
61 | Closer
62 | }
63 | ```
64 |
65 | 上面用到的语法和结构内嵌相似,我们可以用这种方式命名另一个接口,而不用声明它所有的方法。这种方式称为接口内嵌,我们可以像下面这样,不使用内嵌来声明`io.ReadWriter`接口。
66 |
67 | ```go
68 | type ReadWriter interface {
69 | Read(p []byte) (n int, err error)
70 | Write(p []byte) (n int, err error)
71 | }
72 | ```
73 |
74 | 或者甚至使用种混合的风格:
75 |
76 | ```go
77 | type ReadWriter interface {
78 | Read(p []byte) (n int, err error)
79 | Writer
80 | }
81 | ```
82 |
83 | 这三种方式定义的`io.ReadWriter`是完全一样的。
84 |
85 | ### 接口实现
86 |
87 | - 一个类型如果拥有一个接口需要的所有方法,那么这个类型就实现了这个接口。例如,*os.File类型实现了io.Reader,Writer,Closer,和ReadWriter接口。*bytes.Buffer实现了Reader,Writer,和ReadWriter这些接口,但是它没有实现Closer接口因为它不具有Close方法。Go的程序员经常会简要的把一个具体的类型描述成一个特定的接口类型。举个例子,*bytes.Buffer是io.Writer;*os.Files是io.ReadWriter。
88 | - 接口实现的规则非常简单:表达一个类型属于某个接口只要这个类型实现这个接口。
89 |
90 | ```go
91 | var w io.Writer
92 | w = os.Stdout // OK: *os.File has Write method
93 | w = new(bytes.Buffer) // OK: *bytes.Buffer has Write method
94 | w = time.Second // compile error: time.Duration lacks Write method
95 |
96 | var rwc io.ReadWriteCloser
97 | rwc = os.Stdout // OK: *os.File has Read, Write, Close methods
98 | rwc = new(bytes.Buffer) // compile error: *bytes.Buffer lacks Close method
99 | ```
100 |
101 | - 这个规则甚至适用于等式右边本身也是一个接口类型
102 |
103 | ```go
104 | w = rwc // OK: io.ReadWriteCloser has Write method
105 | rwc = w // compile error: io.Writer lacks Close method
106 | ```
107 |
108 | - 因为ReadWriter和ReadWriteCloser包含Writer的方法,所以任何实现了ReadWriter和ReadWriteCloser的类型必定也实现了Writer接口
109 | - 对于一些命名的具体类型T;它一些方法的接收者是类型T本身然而另一些则是一个`*T`的指针。在`T`类型的变量上调用一个`*T`的方法是合法的,编译器隐式的获取了它的地址。但这仅仅是一个语法糖:T类型的值不拥有所有*T指针的方法。
110 | - interface{}类型,它没有任何方法,但实际上interface{}被称为空接口类型是不可或缺的。因为空接口类型对实现它的类型没有要求,所以所有类型都实现了interface{},我们可以将任意一个值赋给空接口类型。
111 |
112 | ```go
113 | var any interface{}
114 | any = true
115 | any = 12.34
116 | any = "hello"
117 | any = map[string]int{"one": 1}
118 | any = new(bytes.Buffer)
119 | ```
120 |
121 | ### 接口值
122 |
123 | - 接口值由两个部分组成,一个具体的类型和那个类型的值。它们被称为接口的动态类型和动态值。
124 |
125 | - 像Go语言这种静态类型的语言,类型是编译期的概念;因此一个类型不是一个值,提供每个类型信息的值被称为类型描述符。
126 |
127 | - 在Go语言中,变量总是被一个定义明确的值初始化,一个接口的零值就是它的类型和值的部分都是nil。
128 |
129 | 
130 |
131 | - 在你非常确定接口值的动态类型是可比较类型时(比如基本类型)才可以使用`==`和`!=`对两个接口值进行比较。如果两个接口值的动态类型相同,但是这个动态类型是不可比较的(比如切片),将它们进行比较就会失败并且panic:
132 |
133 | ```go
134 | var x interface{} = []int{1, 2, 3}
135 | fmt.Println(x == x) // panic: comparing uncomparable type []int
136 | ```
137 |
138 | - 下面4个语句中,变量w得到了3个不同的值。(开始和最后的值是相同的)
139 |
140 | ```go
141 | var w io.Writer
142 | w = os.Stdout
143 | w = new(bytes.Buffer)
144 | w = nil
145 | ```
146 |
147 | 第一个语句定义了变量w:
148 |
149 | ```go
150 | var w io.Writer
151 | ```
152 |
153 | 在Go语言中,变量总是被一个定义明确的值初始化,即使接口类型也不例外。对于一个接口的零值就是它的类型和值的部分都是nil,如图 7.1。
154 |
155 | 一个接口值基于它的动态类型被描述为空或非空,所以这是一个空的接口值。你可以通过使用w==nil或者w!=nil来判读接口值是否为空。调用一个空接口值上的任意方法都会产生panic:
156 |
157 | ```go
158 | w.Write([]byte("hello")) // panic: nil pointer dereference
159 | ```
160 |
161 | 第二个语句将一个*os.File类型的值赋给变量w:
162 |
163 | ```go
164 | w = os.Stdout
165 | ```
166 |
167 | 这个赋值过程调用了一个具体类型到接口类型的隐式转换,这和显式的使用io.Writer(os.Stdout)是等价的。这类转换不管是显式的还是隐式的,都会刻画出操作到的类型和值。这个接口值的动态类型被设为`*os.File`指针的类型描述符(os.Stdout 是指向 os.File 的指针),它的动态值持有os.Stdout的拷贝;这是一个指向处理标准输出的os.File类型变量的指针。
168 |
169 | 
170 |
171 | 调用一个包含`*os.File`类型指针的接口值的Write方法,使得`(*os.File).Write`方法被调用。这个调用输出“hello”。
172 |
173 | ```go
174 | w.Write([]byte("hello")) // "hello"
175 | ```
176 |
177 | 第三个语句给接口值赋了一个*bytes.Buffer类型的值
178 |
179 | ```go
180 | w = new(bytes.Buffer)
181 | ```
182 |
183 | 现在动态类型是*bytes.Buffer并且动态值是一个指向新分配的缓冲区的指针(图7.3)。
184 |
185 | 
186 |
187 | Write方法的调用也使用了和之前一样的机制:
188 |
189 | ```go
190 | w.Write([]byte("hello")) // writes "hello" to the bytes.Buffers
191 | ```
192 |
193 | 这次类型描述符是`*bytes.Buffer`,所以调用了`(*bytes.Buffer).Write`方法,并且接收者是该缓冲区的地址。这个调用把字符串“hello”添加到缓冲区中。
194 |
195 | 最后,第四个语句将nil赋给了接口值:
196 |
197 | ```go
198 | w = nil
199 | ```
200 |
201 | 这个重置将它所有的部分都设为nil值,把变量w恢复到和它之前定义时相同的状态图,在图7.1中可以看到。
202 |
203 | ### 一个包含nil指针的接口不是nil接口
204 |
205 | 一个不包含任何值的nil接口值和一个刚好包含nil指针的接口值是不同的。这个细微区别产生了一个容易绊倒每个Go程序员的陷阱。
206 |
207 | 思考下面的程序。当debug变量设置为true时,main函数会将f函数的输出收集到一个bytes.Buffer类型中。
208 |
209 | ```go
210 | const debug = true
211 |
212 | func main() {
213 | var buf *bytes.Buffer
214 | if debug {
215 | buf = new(bytes.Buffer) // enable collection of output
216 | }
217 | f(buf) // NOTE: subtly incorrect!
218 | if debug {
219 | // ...use buf...
220 | }
221 | }
222 |
223 | // If out is non-nil, output will be written to it.
224 | func f(out io.Writer) {
225 | // ...do something...
226 | if out != nil {
227 | out.Write([]byte("done!\n"))
228 | }
229 | }
230 | ```
231 |
232 | 我们可能会预计当把变量debug设置为false时可以禁止对输出的收集,但是实际上在out.Write方法调用时程序发生了panic:
233 |
234 | ```go
235 | if out != nil {
236 | out.Write([]byte("done!\n")) // panic: nil pointer dereference
237 | }
238 | ```
239 |
240 | 当main函数调用函数f时,它给f函数的out参数赋了一个`*bytes.Buffer`的空指针,所以out的动值是nil。然而,它的动态类型是`*bytes.Buffer`,意思就是out变量是一个包含空指针值的非空接口(如图7.5),所以防御性检查out!=nil的结果依然是true。
241 |
242 | 
243 |
244 | 动态分配机制依然决定`(*bytes.Buffer).Write`的方法会被调用,但是这次的接收者的值是nil。对于一些如`*os.File`的类型,nil是一个有效的接收者(§6.2.1),但是`*bytes.Buffer`类型不在这些类型中。这个方法会被调用,但是当它尝试去获取缓冲区时会发生panic。
245 |
246 | 问题在于尽管一个nil的`*bytes.Buffer`指针有实现这个接口的方法,它也不满足这个接口具体的行为上的要求。特别是这个调用违反了`(*bytes.Buffer).Write`方法的接收者非空的隐含先觉条件,所以将nil指针赋给这个接口是错误的。解决方案就是将main函数中的变量buf声明的类型改为io.Writer,(它的零值动态类型和动态值都为 nil)因此可以避免一开始就将一个不完全的值赋值给这个接口:
247 |
248 | ```go
249 | var buf io.Writer
250 | if debug {
251 | buf = new(bytes.Buffer) // enable collection of output
252 | }
253 | f(buf) // OK
254 | ```
255 |
256 |
257 |
258 | ### error 接口
259 |
260 | - 预定义的error类型实际上就是interface类型,这个类型有一个返回错误信息的单一方法:
261 |
262 | ```go
263 | type error interface {
264 | Error() string
265 | }
266 | ```
267 |
268 | - 创建一个error最简单的方法就是调用errors.New函数,它会根据传入的错误信息返回一个新的error。整个errors包仅只有4行:
269 |
270 | ```go
271 | package errors
272 |
273 | func New(text string) error { return &errorString{text} }
274 |
275 | type errorString struct { text string }
276 |
277 | func (e *errorString) Error() string { return e.text }
278 | ```
279 |
280 | 每个New函数的调用都分配了一个独特的和其他错误不相同的实例。我们也不想要重要的`error`例如`io.EOF`和一个刚好有相同错误消息的`error`比较后相等。
281 |
282 | ```go
283 | fmt.Println(errors.New("EOF") == errors.New("EOF")) // "false"
284 | ```
285 |
286 | 调用`errors.New`函数是非常稀少的,因为有一个方便的封装函数`fmt.Errorf`,它还会处理字符串格式化。
287 |
288 | ```go
289 | package fmt
290 |
291 | import "errors"
292 |
293 | func Errorf(format string, args ...interface{}) error {
294 | return errors.New(Sprintf(format, args...))
295 | }
296 | ```
297 |
298 | ### 类型断言
299 |
300 | - 类型断言是一个使用在接口值上的操作。语法上它看起来像x.(T)被称为断言类型。这里x表示一个接口值,T表示一个类型(接口类型或者具体类型)。一个类型断言会检查操作对象的动态类型是否和断言类型匹配。
301 |
302 | - x.(T)中如果断言的类型T是一个具体类型,类型断言检查x的动态类型是否和T相同。如果是,类型断言的结果是x的动态值,当然它的类型是T。换句话说,具体类型的类型断言从它的操作对象中获得具体的值。如果x 的动态类型与 T 不相同,会抛出panic。
303 |
304 | ```go
305 | var w io.Writer
306 | w = os.Stdout
307 | f := w.(*os.File) // success: f == os.Stdout
308 | c := w.(*bytes.Buffer) // panic: interface holds *os.File, not *bytes.Buffer
309 | ```
310 |
311 | - 相反断言的类型T是一个接口类型,然后类型断言检查是否x的动态类型满足T。如果这个检查成功了,这个结果仍然是一个有相同类型和值部分的接口值,但是结果接口值的动态类型为T。换句话说,对一个接口类型的类型断言改变了类型的表述方式,改变了可以获取的方法集合(通常更大),但是它保护了接口值内部的动态类型和值的部分。
312 |
313 | - 在下面的第一个类型断言后,w和rw都持有os.Stdout因为它们每个值的动态类型都是`*os.File`,但是变量的类型是io.Writer只对外公开出文件的Write方法,变量rw的类型为 io.ReadWriter,只对外公开文件的Read方法。
314 |
315 | ```go
316 | var w io.Writer
317 | w = os.Stdout
318 | rw := w.(io.ReadWriter) // success: *os.File has both Read and Write
319 | w = new(ByteCounter)
320 | rw = w.(io.ReadWriter) // panic: *ByteCounter has no Read method
321 | ```
322 |
323 | - 如果断言操作的对象是一个nil接口值,那么不论被断言的类型是什么这个类型断言都会失败。
324 | - 经常地我们对一个接口值的动态类型是不确定的,并且我们更愿意去检验它是否是一些特定的类型。如果类型断言出现在一个有两个结果的赋值表达式中,例如如下的定义,这个类型断言不会在失败的时候发生panic,代替地返回的第二个返回值是一个标识类型断言是否成功的布尔值:
325 |
326 | ```go
327 | var w io.Writer = os.Stdout
328 | f, ok := w.(*os.File) // success: ok, f == os.Stdout
329 | b, ok := w.(*bytes.Buffer) // failure: !ok, b == nil
330 | ```
331 |
332 | ### type switch
333 |
334 | 接口被以两种不同的方式使用。在第一个方式中,以io.Reader,io.Writer,fmt.Stringer,sort.Interface,http.Handler,和error为典型,一个接口的方法表达了实现这个接口的具体类型间的相似性,但是隐藏了代表的细节和这些具体类型本身的操作。重点在于方法上,而不是具体的类型上。
335 |
336 | 第二个方式利用一个接口值可以持有各种具体类型值的能力并且将这个接口认为是这些类型的union(联合)。类型断言用来动态地区别这些类型。在这个方式中,重点在于具体的类型满足这个接口,而不是在于接口的方法(如果它确实有一些的话),并且没有任何的信息隐藏。我们将以这种方式使用的接口描述为discriminated unions(可辨识联合)。
337 |
338 | 一个类型开关像普通的switch语句一样,它的运算对象是x.(type)-它使用了关键词字面量type-并且每个case有一到多个类型。一个类型开关基于这个接口值的动态类型使一个多路分支有效。这个nil的case和if x == nil匹配,并且这个default的case和如果其它case都不匹配的情况匹配。一个对sqlQuote的类型开关可能会有这些case
339 |
340 | ```go
341 | switch x.(type) {
342 | case nil: // ...
343 | case int, uint: // ...
344 | case bool: // ...
345 | case string: // ...
346 | default: // ...
347 | }
348 | ```
349 |
350 | 类型开关语句有一个扩展的形式,它可以将提取的值绑定到一个在每个case范围内的新变量上。
351 |
352 | ```go
353 | switch x := x.(type) { /* ... */ }
354 | ```
355 |
356 | 使用类型开关的扩展形式来重写sqlQuote函数会让这个函数更加的清晰:
357 |
358 | ```go
359 | func sqlQuote(x interface{}) string {
360 | switch x := x.(type) {
361 | case nil:
362 | return "NULL"
363 | case int, uint:
364 | return fmt.Sprintf("%d", x) // x has type interface{} here.
365 | case bool:
366 | if x {
367 | return "TRUE"
368 | }
369 | return "FALSE"
370 | case string:
371 | return sqlQuoteString(x) // (not shown)
372 | default:
373 | panic(fmt.Sprintf("unexpected type %T: %v", x, x))
374 | }
375 | }
376 | ```
377 |
378 | 尽管sqlQuote接受一个任意类型的参数,但是这个函数只会在它的参数匹配类型开关中的一个case时运行到结束;其它情况的它会panic出“unexpected type”消息。虽然x的类型是interface{},但是我们把它认为是一个int,uint,bool,string,和nil值的discriminated union(可识别联合)
379 |
380 | ### 使用建议
381 |
382 | - 接口只有当有两个或两个以上的具体类型必须以相同的方式进行处理时才需要。
383 | - 当一个接口只被一个单一的具体类型实现时有一个例外,就是由于它的依赖,这个具体类型不能和这个接口存在在一个相同的包中。这种情况下,一个接口是解耦这两个包的一个好的方式。
384 |
385 | - 因为在Go语言中只有当两个或更多的类型须以相同的方式进行处理时才有必要使用接口,它们必定会从任意特定的实现细节中抽象出来。结果就是有更少和更简单方法(经常和io.Writer或 fmt.Stringer一样只有一个)的更小的接口。当新的类型出现时,小的接口更容易满足。对于接口设计的一个好的标准就是 ask only for what you need(只考虑你需要的东西)。
386 |
--------------------------------------------------------------------------------
/lang-basic/content/chapter2.md:
--------------------------------------------------------------------------------
1 | > 以下内容为几年前学Go语言时记录的,发布在这里让这个项目对更多人能起到参考作用。
2 | 更多时下流行技术的应用和实战教程,可通过我公众号「网管叨bi叨」每周的推文来学习
3 |
4 | ## 函数
5 |
6 | ### 函数声明
7 |
8 | - 函数声明包括函数名、形式参数列表、返回值列表(可省略)以及函数体。
9 |
10 | ```Go
11 | func name(parameter-list) (result-list) {
12 | body
13 | }
14 | ```
15 | 形式参数列表描述了函数的参数名以及参数类型。这些参数作为局部变量,其值由参数调用者提供。返回值也可以像形式参数一样被命名,在这种情况下,每个返回值被声明成一个局部变量,并初始化为其类型的零值。
16 |
17 | - 用 _ 符号作为形参名可以强调某个参数未被使用。
18 |
19 | ```Go
20 | func first(x int, _ int) int { return x }
21 | ```
22 |
23 | - 函数的类型被称为函数的标识符。如果两个函数形式参数列表和返回值列表中的变量类型一一对应,那么这两个函数被认为有相同的类型和标识符。
24 | - 在函数调用时,Go语言没有默认参数值,也没有任何方法可以通过参数名指定形参,因此形参和返回值的变量名对于函数调用者而言没有意义。
25 | - 实参通过值的方式传递,因此函数的形参是实参的拷贝。对形参进行修改不会影响实参。但是,如果实参包括引用类型,如指针,slice(切片)、map、function、channel等类型,实参可能会由于函数的引用而被修改。
26 | - golang.org/x/... 目录下存储了一些由Go团队设计、维护,对网络编程、国际化文件处理、移动平台、图像处理、加密解密、开发者工具提供支持的扩展包。未将这些扩展包加入到标准库原因有二,一是部分包仍在开发中,二是对大多数Go语言的开发者而言,扩展包提供的功能很少被使用。
27 |
28 | ### 递归调用
29 |
30 | - 大部分编程语言使用固定大小的函数调用栈,常见的大小从64KB到2MB不等。固定大小栈会限制递归的深度,当你用递归处理大量数据时,需要避免栈溢出;除此之外,还会导致安全性问题。与相反,Go语言使用可变栈,栈的大小按需增加(初始时很小)。这使得我们使用递归时不必考虑溢出和安全问题
31 | - 虽然Go的垃圾回收机制会回收不被使用的内存,但是这不包括操作系统层面的资源,比如打开的文件、网络连接。因此我们必须显式的释放这些资源。
32 |
33 | ### 多返回值函数
34 |
35 | - 调用多返回值函数时,返回给调用者的是一组值,调用者必须显式的将这些值分配给变量:
36 |
37 | ```Go
38 | links, err := findLinks(url)
39 | ```
40 |
41 | 如果某个值不被使用,可以将其分配给blank identifier:
42 |
43 | ```Go
44 | links, _ := findLinks(url) // errors ignored
45 | ```
46 |
47 | - 如果一个函数将所有的返回值都显示的变量名,那么该函数的return语句可以省略操作数。这称之为bare return。
48 |
49 | ```Go
50 | // CountWordsAndImages does an HTTP GET request for the HTML
51 | // document url and returns the number of words and images in it.
52 | func CountWordsAndImages(url string) (words, images int, err error) {
53 | resp, err := http.Get(url)
54 | if err != nil {
55 | return
56 | }
57 | doc, err := html.Parse(resp.Body)
58 | resp.Body.Close()
59 | if err != nil {
60 | err = fmt.Errorf("parsing HTML: %s", err)
61 | return
62 | }
63 | words, images = countWordsAndImages(doc)
64 | return
65 | }
66 | func countWordsAndImages(n *html.Node) (words, images int) { /* ... */ }
67 | ```
68 |
69 | 按照函数声明中返回值列表的次序,返回所有的返回值,在上面的例子中,每一个return语句等价于:
70 |
71 | ```Go
72 | return words, images, err
73 | ```
74 |
75 | - 当一个函数有多处return语句以及许多返回值时,bare return 可以减少代码的重复,但是使得代码难以被理解。如果你没有仔细的审查上面的代码,很难发现前2处return等价于 `return 0,0,err`(Go会将返回值 words和images在函数体的开始处,根据它们的类型,将其初始化为0),最后一处return等价于 `return words,image,nil`。基于以上原因,不宜过度使用bare return。
76 |
77 | ### 错误
78 |
79 | - 在Go的错误处理中,错误是软件包API和应用程序用户界面的一个重要组成部分,程序运行失败仅被认为是几个预期的结果之一。
80 |
81 | - 对于那些将运行失败看作是预期结果的函数,它们会返回一个额外的返回值,通常是最后一个,来传递错误信息。
82 |
83 | ```go
84 | resp, err := http.Get(url)
85 | ```
86 |
87 | - 内置的error是接口类型。nil意味着函数运行成功,non-nil表示失败。对于non-nil的error类型,我们可以通过调用error的`Error`函数或者输出函数获得字符串类型的错误信息。
88 |
89 | ```Go
90 | fmt.Println(err)
91 | fmt.Printf("%v", err)
92 | ```
93 |
94 | ### 函数值
95 |
96 | - 在Go中,函数被看作第一类值(first-class values):函数像其他值一样,拥有类型,可以被赋值给其他变量,传递给函数,从函数返回。对函数值(function value)的调用类似函数调用。例子如下:
97 |
98 | ```Go
99 | func square(n int) int { return n * n }
100 | func negative(n int) int { return -n }
101 | func product(m, n int) int { return m * n }
102 |
103 | f := square
104 | fmt.Println(f(3)) // "9"
105 |
106 | f = negative
107 | fmt.Println(f(3)) // "-3"
108 | fmt.Printf("%T\n", f) // "func(int) int"
109 |
110 | f = product // compile error: can't assign func(int, int) int to func(int) int
111 | ```
112 |
113 | - 函数类型的零值是nil。调用值为nil的函数值会引起panic错误:
114 |
115 | ```Go
116 | var f func(int) int
117 | f(3) // 此处f的值为nil, 会引起panic错误
118 | ```
119 |
120 | - 函数值可以与nil比较:
121 |
122 | ```Go
123 | var f func(int) int
124 | if f != nil {
125 | f(3)
126 | }
127 | ```
128 |
129 | 但是函数值之间是不可比较的,也不能用函数值作为map的key。
130 |
131 |
132 |
133 | ### 匿名函数
134 |
135 | - 拥有函数名的函数只能在包级语法块中被声明,通过函数字面量(function literal),我们可绕过这一限制,在任何表达式中表示一个函数值。函数字面量的语法和函数声明相似,区别在于func关键字后没有函数名。函数值字面量是一种表达式,它的值被称为匿名函数(anonymous function)。
136 |
137 | 函数字面量允许我们在使用函数时,再定义它。通过这种技巧,我们可以改写之前对strings.Map的调用:
138 |
139 | ```Go
140 | strings.Map(func(r rune) rune { return r + 1 }, "HAL-9000")
141 | ```
142 |
143 | 更为重要的是,通过这种方式定义的函数可以访问完整的词法环境(lexical environment),这意味着在函数中定义的内部函数可以引用该函数的变量。
144 |
145 | ```Go
146 | // squares返回一个匿名函数。
147 | // 该匿名函数每次被调用时都会返回下一个数的平方。
148 | func squares() func() int {
149 | var x int
150 | return func() int {
151 | x++
152 | return x * x
153 | }
154 | }
155 | ```
156 |
157 | 通过这个例子,我们看到变量的生命周期不由它的作用域决定:squares返回后,变量x仍然隐式的存在于f中。
158 |
159 | - 当匿名函数需要被递归调用时,我们必须首先声明一个变量,再将匿名函数赋值给这个变量。如果不分成两步,函数字面量无法与变量绑定,我们也无法递归调用该匿名函数,比如:
160 |
161 | ```Go
162 | var visitAll func(items []string)
163 | visitAll = func(items []string) {
164 | ......
165 | visitAll(m[item])
166 | ......
167 | }
168 |
169 | ```
170 | 否则会出现编译错误
171 | ```Go
172 | visitAll := func(items []string) {
173 | // ...
174 | visitAll(m[item]) // compile error: undefined: visitAll
175 | // ...
176 | }
177 | ```
178 |
179 | ### 可变参数
180 |
181 | - 参数数量可变的函数称为为可变参数函数。典型的例子就是fmt.Printf和类似函数。Printf首先接收一个的必备参数,之后接收任意个数的后续参数。
182 |
183 | 在声明可变参数函数时,需要在参数列表的最后一个参数类型之前加上省略符号“...”,这表示该函数会接收任意数量的该类型参数。
184 |
185 | ```Go
186 | func sum(vals...int) int {
187 | total := 0
188 | for _, val := range vals {
189 | total += val
190 | }
191 | return total
192 | }
193 | ```
194 |
195 | sum函数返回任意个int型参数的和。在函数体中,vals被看作是类型为`[] int`的切片。sum可以接收任意数量的int型参数:
196 |
197 | ```Go
198 | fmt.Println(sum()) // "0"
199 | fmt.Println(sum(3)) // "3"
200 | fmt.Println(sum(1, 2, 3, 4)) // "10"
201 | ```
202 |
203 | - 在上面的代码中,调用者隐式的创建一个数组,并将原始参数复制到数组中,再把数组的一个切片作为参数传给被调函数。如果原始参数已经是切片类型,我们该如何传递给sum?只需在最后一个参数后加上省略符。下面的代码功能与上个例子中最后一条语句相同。
204 |
205 | ```Go
206 | values := []int{1, 2, 3, 4}
207 | fmt.Println(sum(values...)) // "10"
208 | // fmt.Println(sum(1, 2, 3, 4))
209 | ```
210 |
211 | - 虽然在可变参数函数内部,`...int` 型参数的行为看起来很像切片类型,但实际上,可变参数函数和以切片作为参数的函数是不同的。
212 |
213 | ```Go
214 | func f(...int) {}
215 | func g([]int) {}
216 | fmt.Printf("%T\n", f) // "func(...int)"
217 | fmt.Printf("%T\n", g) // "func([]int)"
218 | ```
219 |
220 | - 可变参数函数经常被用于格式化字符串。下面的errorf函数构造了一个以行号开头的,经过格式化的错误信息。函数名的后缀f是一种通用的命名规范,代表该可变参数函数可以接收Printf风格的格式化字符串。
221 |
222 | ```Go
223 | func errorf(linenum int, format string, args ...interface{}) {
224 | fmt.Fprintf(os.Stderr, "Line %d: ", linenum)
225 | fmt.Fprintf(os.Stderr, format, args...)
226 | fmt.Fprintln(os.Stderr)
227 | }
228 | linenum, name := 12, "count"
229 | errorf(linenum, "undefined: %s", name) // "Line 12: undefined: count"
230 | ```
231 |
232 | `...interfac{}`表示函数在`format`参数后可以接收任意个任意类型的参数。`interface{}`会在后面介绍。
233 |
234 |
235 |
236 | ### Deferred 函数
237 |
238 | - 你只需要在调用普通函数或方法前加上关键字defer,就完成了defer所需要的语法。当defer语句被执行时,跟在defer后面的函数会被延迟执行。直到包含该defer语句的函数执行完毕时,defer后的函数才会被执行,不论包含defer语句的函数是通过return正常结束,还是由于panic导致的异常结束。你可以在一个函数中执行多条defer语句,它们的执行顺序与声明顺序相反。
239 |
240 | - defer语句经常被用于处理成对的操作,如打开、关闭、连接、断开连接、加锁、释放锁。通过defer机制,不论函数逻辑多复杂,都能保证在任何执行路径下,资源被释放。释放资源的defer应该直接跟在请求资源的语句后。
241 |
242 | - 对文件的操作
243 |
244 | ```Go
245 | package ioutil
246 | func ReadFile(filename string) ([]byte, error) {
247 | f, err := os.Open(filename)
248 | if err != nil {
249 | return nil, err
250 | }
251 | defer f.Close()
252 | return ReadAll(f)
253 | }
254 | ```
255 |
256 | - 处理互斥锁
257 |
258 | ```Go
259 | var mu sync.Mutex
260 | var m = make(map[string]int)
261 | func lookup(key string) int {
262 | mu.Lock()
263 | defer mu.Unlock()
264 | return m[key]
265 | }
266 | ```
267 |
268 | - 调试复杂程序时,defer机制也常被用于记录何时进入和退出函数。下例中的bigSlowOperation函数,直接调用trace记录函数的被调情况。bigSlowOperation被调时,trace会返回一个函数值,该函数值会在bigSlowOperation退出时被调用。通过这种方式, 我们可以只通过一条语句控制函数的入口和所有的出口,甚至可以记录函数的运行时间,如例子中的start。需要注意一点:不要忘记defer语句后的圆括号,否则本该在进入时执行的操作会在退出时执行,而本该在退出时执行的,永远不会被执行。
269 |
270 | *gopl.io/ch5/trace*
271 |
272 | ```Go
273 | func bigSlowOperation() {
274 | defer trace("bigSlowOperation")() // don't forget the extra parentheses
275 | // ...lots of work…
276 | time.Sleep(10 * time.Second) // simulate slow
277 | operation by sleeping
278 | }
279 | func trace(msg string) func() {
280 | start := time.Now()
281 | log.Printf("enter %s", msg)
282 | return func() {
283 | log.Printf("exit %s (%s)", msg,time.Since(start))
284 | }
285 | }
286 | ```
287 |
288 | 每一次bigSlowOperation被调用,程序都会记录函数的进入,退出,持续时间。(我们用time.Sleep模拟一个耗时的操作)
289 |
290 | ```
291 | $ go build gopl.io/ch5/trace
292 | $ ./trace
293 | 2015/11/18 09:53:26 enter bigSlowOperation
294 | 2015/11/18 09:53:36 exit bigSlowOperation (10.000589217s)
295 | ```
296 |
297 | - 用 defer 函数记录返回值(需要是命名返回值才能记录)
298 |
299 | ```Go
300 | func double(x int) (result int) {
301 | defer func() { fmt.Printf("double(%d) = %d\n", x,result) }()
302 | return x + x
303 | }
304 | _ = double(4)
305 | // Output:
306 | // "double(4) = 8"
307 | ```
308 |
309 | - 被延迟执行的匿名函数甚至可以修改函数返回给调用者的返回值:
310 |
311 | ```Go
312 | func triple(x int) (result int) {
313 | defer func() { result += x }()
314 | return double(x)
315 | }
316 | fmt.Println(triple(4)) // "12"
317 | ```
318 |
319 | - 在循环体中的defer语句需要特别注意,因为只有在函数执行完毕后,这些被延迟的函数才会执行。下面的代码会导致系统的文件描述符耗尽,因为在所有文件都被处理之前,没有文件会被关闭。
320 |
321 | ```Go
322 | for _, filename := range filenames {
323 | f, err := os.Open(filename)
324 | if err != nil {
325 | return err
326 | }
327 | defer f.Close() // NOTE: risky; could run out of file
328 | descriptors
329 | // ...process f…
330 | }
331 | ```
332 |
333 | 一种解决方法是将循环体中的文件操作和defer语句移至另外一个函数。在每次循环时,调用这个函数。
334 |
335 | ```Go
336 | for _, filename := range filenames {
337 | if err := doFile(filename); err != nil {
338 | return err
339 | }
340 | }
341 | func doFile(filename string) error {
342 | f, err := os.Open(filename)
343 | if err != nil {
344 | return err
345 | }
346 | defer f.Close()
347 | // ...process f…
348 | }
349 | ```
350 |
351 | ### Panic 和 Recover
352 |
353 | - Go的类型系统会在编译时捕获很多错误,但有些错误只能在运行时检查,如数组访问越界、空指针引用等。这些运行时错误会引起painc异常。
354 |
355 | - 当panic异常发生时,程序会中断运行,并立即执行在该goroutine(可以先理解成线程,在第8章会详细介绍)中被延迟的函数(defer 机制)。随后,程序崩溃并输出日志信息。日志信息包括panic value和函数调用的堆栈跟踪信息。
356 |
357 | - 虽然Go的panic机制类似于其他语言的异常,但panic的适用场景有一些不同。由于panic会引起程序的崩溃,因此panic一般用于严重错误,如程序内部的逻辑不一致。
358 |
359 | - 通常来说,不应该对panic异常做任何处理,但有时,也许我们可以从异常中恢复,至少我们可以在程序崩溃前,做一些操作。举个例子,当web服务器遇到不可预料的严重问题时,在崩溃前应该将所有的连接关闭;如果不做任何处理,会使得客户端一直处于等待状态。
360 |
361 | - 如果在deferred函数中调用了内置函数recover,并且定义该defer语句的函数发生了panic异常,recover会使程序从panic中恢复,并返回panic value。导致panic异常的函数不会继续运行,但能正常返回。在未发生panic时调用recover,recover会返回nil。
362 |
363 | - 例子中deferred函数帮助Parse从panic中恢复。在deferred函数内部,panic value被附加到错误信息中;并用err变量接收错误信息,返回给调用者。
364 |
365 | ```Go
366 | func Parse(input string) (s *Syntax, err error) {
367 | defer func() {
368 | if p := recover(); p != nil {
369 | err = fmt.Errorf("internal error: %v", p)
370 | }
371 | }()
372 | // ...parser...
373 | }
374 | ```
375 |
376 | - 不加区分的恢复所有的panic异常,不是可取的做法。
377 |
378 | - 只恢复应该被恢复的panic异常,此外,这些异常所占的比例应该尽可能的低。为了标识某个panic是否应该被恢复,我们可以将panic value设置成特殊类型。在recover时对panic value进行检查,如果发现panic value是特殊类型,就将这个panic作为errror处理,如果不是,则按照正常的panic进行处理
379 |
380 | ```Go
381 | func soleTitle(doc *html.Node) (title string, err error) {
382 | type bailout struct{}
383 | defer func() {
384 | switch p := recover(); p {
385 | case nil: // no panic
386 | case bailout{}: // "expected" panic
387 | err = fmt.Errorf("multiple title elements")
388 | default:
389 | panic(p)
390 | }
391 | }()
392 | forEachNode(doc, func(n *html.Node) {
393 | if n.Type == html.ElementNode && n.Data == "title" &&
394 | n.FirstChild != nil {
395 | if title != "" {
396 | panic(bailout{}) // multiple titleelements
397 | }
398 | title = n.FirstChild.Data
399 | }
400 | }, nil)
401 | if title == "" {
402 | return "", fmt.Errorf("no title element")
403 | }
404 | return title, nil
405 | }
406 | ```
407 |
--------------------------------------------------------------------------------
/codes/distributed_lock/go.sum:
--------------------------------------------------------------------------------
1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
3 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
4 | github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
5 | github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
6 | github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
7 | github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
8 | github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
9 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
10 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
11 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
12 | github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
13 | github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
14 | github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
15 | github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
16 | github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
17 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
18 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
19 | github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
20 | github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
21 | github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
22 | github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
23 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
24 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
25 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
26 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
27 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
28 | github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
29 | github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
30 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
31 | github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
32 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
33 | github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
34 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
35 | github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
36 | github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
37 | github.com/coreos/etcd v3.3.25+incompatible h1:0GQEw6h3YnuOVdtwygkIfJ+Omx0tZ8/QkVyXI4LkbeY=
38 | github.com/coreos/etcd v3.3.25+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
39 | github.com/coreos/go-semver v0.2.0 h1:3Jm3tLmsgAYcjC+4Up7hJrFBPr+n7rAqYeSw/SZazuY=
40 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
41 | github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7 h1:u9SHYsPQNyt5tgDm3YN7+9dYrpK96E5wFilTFWIDZOM=
42 | github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
43 | github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
44 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg=
45 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
46 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
47 | github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
48 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
49 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
50 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
51 | github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
52 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
53 | github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4 h1:qk/FSDDxo05wdJH28W+p5yivv7LuLYLRXPPD8KQCtZs=
54 | github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
55 | github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
56 | github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
57 | github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
58 | github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
59 | github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
60 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
61 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
62 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
63 | github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
64 | github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
65 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
66 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
67 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
68 | github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
69 | github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
70 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
71 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
72 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
73 | github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
74 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
75 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
76 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
77 | github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
78 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I=
79 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
80 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
81 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
82 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
83 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
84 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
85 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
86 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
87 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
88 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
89 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
90 | github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
91 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
92 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
93 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
94 | github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
95 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
96 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
97 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
98 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
99 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
100 | github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
101 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
102 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
103 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
104 | github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
105 | github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
106 | github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
107 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
108 | github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
109 | github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
110 | github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
111 | github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c h1:Lh2aW+HnU2Nbe1gqD9SOJLJxW1jBMmQOktN2acDyJk8=
112 | github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
113 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4 h1:z53tR0945TRRQO/fLEVPI6SMv7ZflF0TEaTAoU7tOzg=
114 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
115 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
116 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
117 | github.com/grpc-ecosystem/grpc-gateway v1.9.5 h1:UImYN5qQ8tuGpGE16ZmjvcTtTw24zw1QAp/SlnNrZhI=
118 | github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
119 | github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
120 | github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
121 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
122 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
123 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
124 | github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
125 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
126 | github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
127 | github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
128 | github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
129 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
130 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
131 | github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
132 | github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
133 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
134 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
135 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
136 | github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
137 | github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
138 | github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
139 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
140 | github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
141 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
142 | github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
143 | github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
144 | github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=
145 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
146 | github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
147 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
148 | github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
149 | github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
150 | github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
151 | github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
152 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
153 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
154 | github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
155 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
156 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
157 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
158 | github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
159 | github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
160 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
161 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
162 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
163 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
164 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
165 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
166 | github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
167 | github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
168 | github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
169 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
170 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
171 | github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
172 | github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
173 | github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
174 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
175 | github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
176 | github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
177 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
178 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
179 | github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
180 | github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
181 | github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
182 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
183 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
184 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
185 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
186 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
187 | github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
188 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
189 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
190 | github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
191 | github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
192 | github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
193 | github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
194 | github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
195 | github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
196 | github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
197 | github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
198 | github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
199 | github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
200 | github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
201 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
202 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
203 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
204 | github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
205 | github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
206 | github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
207 | github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
208 | github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
209 | github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
210 | github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
211 | github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
212 | github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
213 | github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
214 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
215 | github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
216 | github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
217 | github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
218 | github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
219 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
220 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
221 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
222 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
223 | github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
224 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
225 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
226 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
227 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
228 | github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
229 | github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
230 | github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
231 | github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
232 | github.com/prometheus/client_golang v1.10.0 h1:/o0BDeWzLWXNZ+4q5gXltUvaMpJqckTa+jTNoB+z4cg=
233 | github.com/prometheus/client_golang v1.10.0/go.mod h1:WJM3cc3yu7XKBKa/I8WeZm+V3eltZnBwfENSU7mdogU=
234 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
235 | github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
236 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
237 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
238 | github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
239 | github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
240 | github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
241 | github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
242 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
243 | github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
244 | github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
245 | github.com/prometheus/common v0.18.0 h1:WCVKW7aL6LEe1uryfI9dnEc2ZqNB1Fn0ok930v0iL1Y=
246 | github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
247 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
248 | github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
249 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
250 | github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
251 | github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
252 | github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
253 | github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
254 | github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
255 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
256 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
257 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
258 | github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
259 | github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
260 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
261 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
262 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
263 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
264 | github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
265 | github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
266 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
267 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
268 | github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E=
269 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
270 | github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
271 | github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
272 | github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
273 | github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
274 | github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
275 | github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
276 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
277 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
278 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
279 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
280 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
281 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
282 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
283 | github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8 h1:ndzgwNDnKIqyCvHTXaCqh9KlOWKvBry6nuXMJmonVsE=
284 | github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
285 | github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
286 | github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
287 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
288 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
289 | go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk=
290 | go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
291 | go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
292 | go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
293 | go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
294 | go.etcd.io/etcd v3.3.25+incompatible h1:V1RzkZJj9LqsJRy+TUBgpWSbZXITLB819lstuTFoZOY=
295 | go.etcd.io/etcd v3.3.25+incompatible/go.mod h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSFZfzsF9A6aI=
296 | go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
297 | go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
298 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
299 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
300 | go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
301 | go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
302 | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
303 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
304 | go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
305 | go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
306 | go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
307 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
308 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
309 | go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
310 | go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U=
311 | go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
312 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
313 | golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
314 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
315 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
316 | golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
317 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
318 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
319 | golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc=
320 | golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
321 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
322 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
323 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
324 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
325 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
326 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
327 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
328 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
329 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
330 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
331 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
332 | golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
333 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
334 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
335 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
336 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
337 | golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
338 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
339 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
340 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
341 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
342 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
343 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
344 | golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
345 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
346 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
347 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
348 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
349 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
350 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
351 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
352 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
353 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
354 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
355 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
356 | golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
357 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
358 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
359 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
360 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
361 | golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
362 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
363 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
364 | golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
365 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
366 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
367 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
368 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
369 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
370 | golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
371 | golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
372 | golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
373 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
374 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
375 | golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
376 | golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
377 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
378 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
379 | golang.org/x/sys v0.0.0-20210309074719-68d13333faf2 h1:46ULzRKLh1CwgRq2dC5SlBzEqqNCi8rreOZnNrbqcIY=
380 | golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
381 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
382 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
383 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
384 | golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
385 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
386 | golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
387 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
388 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
389 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
390 | golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
391 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
392 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
393 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
394 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
395 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
396 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
397 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
398 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
399 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
400 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
401 | golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
402 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
403 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
404 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
405 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
406 | google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
407 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
408 | google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
409 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
410 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
411 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
412 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
413 | google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
414 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
415 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
416 | google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
417 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
418 | google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
419 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
420 | google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
421 | google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
422 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
423 | google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
424 | google.golang.org/grpc v1.26.0 h1:2dTRdpdFEEhJYQD8EMLB61nnrzSCTbG38PhqdhvOltg=
425 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
426 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
427 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
428 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
429 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
430 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
431 | google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
432 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
433 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
434 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
435 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
436 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
437 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
438 | gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
439 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
440 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
441 | gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
442 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
443 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
444 | gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
445 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
446 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
447 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
448 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
449 | gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
450 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
451 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
452 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
453 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
454 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
455 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
456 | honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
457 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
458 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
459 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
460 | sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
461 | sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
462 | sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
463 |
--------------------------------------------------------------------------------