生成 csv 文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 file, err := os.OpenFile("test.csv" , os.O_CREATE|os.O_WRONLY, 0777 ) defer file.Close()if err != nil { os.Exit(1 ) } data := [][]string {{"Line1" , "Hello" }, {"Line2" , "World" }} csvWriter := csv.NewWriter(file) csvWriter.WriteAll(data) csvWriter.Flush() dataBuf := bytes.NewBuffer(nil ) writer := csv.NewWriter(dataBuf) for _, str := range data { writer.Write(str) } writer.Flush()
结合使用 klog 和 cobra 一个更完整的模板:https://github.com/physcat/klog-cobra
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import ( "flag" "github.com/spf13/cobra" "k8s.io/klog/v2" ) func NewRootCmd () *cobra .Command { globalConfig := config.GetGlobalConfig() cmd := &cobra.Command{ Use: "test" , Short: "\ntest" , } klog.InitFlags(nil ) cmd.PersistentFlags().AddGoFlagSet(flag.CommandLine) cmd.AddCommand(newSubCmd()) return cmd }
从切片中删除元素 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func deleteItem(strSlice []string, index int) []string { newSlice := []string{} for i, v := range strSlice { if i != index { newSlice = append(newSlice, v) } } return newSlice } func deleteItem1(strSlice []string, index int) []string { strSlice[index] = strSlice[len(strSlice)-1] return strSlice[:len(strSlice)-1] } func deleteItem2(strSlice []string, index int) []string { copy(strSlice[index:len(strSlice)-1], strSlice[index+1:]) return strSlice[:len(strSlice)-1] }
生成 UUID 一种是使用开源库:
1 2 3 4 5 6 7 8 9 10 11 package mainimport ( "fmt" "github.com/google/uuid" ) func main () { guid := uuid.New() fmt.Println(guid) }
一种是直接读取随机数生成 uuid:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package mainimport ( "fmt" "math/rand" "encoding/hex" ) # 生成的不是标准 UUID,但思路是一样的,第一种方法底层也是类似实现 func uuid () string { u := make ([]byte , 16 ) _, err := rand.Read(u) if err != nil { return "" } u[8 ] = (u[8 ] | 0x80 ) & 0xBF u[6 ] = (u[6 ] | 0x40 ) & 0x4F return hex.EncodeToString(u) } func main () { fmt.Println(uuid()) }
一种是调用系统的 uuidgen 工具:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport ( "fmt" "log" "os/exec" ) func main () { out, err := exec.Command("uuidgen" ).Output() if err != nil { log.Fatal(err) } fmt.Printf("%s \n" , out) }
检查字符串是否符合 base64 编码 参考:https://stackoverflow.com/questions/8571501/how-to-check-whether-a-string-is-base64-encoded-or-not
1 2 3 4 func CheckValidBase64(src string) bool { matched, _ := regexp.Match(`^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$`, []byte(src)) return matched }
检查环境变量是否存在 一般情况下,我们只需要获取环境变量的值,所以使用 username := os.Getenv("USERNAME")
即可,当获取到的值为空时,有可能环境变量存在且值为空,也有可能环境变量并不存在,若我们需要知道到底是哪种情况,则可使用 path, exists := os.LookupEnv("PATH")
返回的布尔值进行判断。
HTTP Response Status 有两种标准写法可用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func ServeHTTP (w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte ("500 - Something bad happened!" )) } func yourFuncHandler (w http.ResponseWriter, r *http.Request) { http.Error(w, "my own error message" , http.StatusForbidden) http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) }
获取变量类型
1 2 3 4 5 6 7 8 import "fmt" func main () { v := "hello world" fmt.Println(typeof(v)) } func typeof (v interface {}) string { return fmt.Sprintf("%T" , v) }
1 2 3 4 5 6 7 8 9 10 11 import ( "reflect" "fmt" ) func main () { v := "hello world" fmt.Println(typeof(v)) } func typeof (v interface {}) string { return reflect.TypeOf(v).String() }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func main () { v := "hello world" fmt.Println(typeof(v)) } func typeof (v interface {}) string { switch t := v.(type ) { case int : return "int" case float64 : return "float64" default : _ = t return "unknown" } }
其实前两个都是用了反射,fmt.Printf (“% T”) 里最终调用的还是 reflect.TypeOf()
。
1 2 3 4 5 6 7 8 9 10 11 12 func (p *pp) printArg (arg interface {}, verb rune ) { ... switch verb { case 'T' : p.fmt.fmt_s(reflect.TypeOf(arg).String()) return case 'p' : p.fmtPointer(reflect.ValueOf(arg), 'p' ) return }
reflect.TypeOf () 的参数是 v interface{}
,golang 的反射是怎么做到的呢?在 golang 中,interface 也是一个结构体,记录了 2 个指针:
指针 1,指向该变量的类型
指针 2,指向该变量的 value
获取变量地址 输入:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport ( "fmt" "reflect" "unsafe" ) func main () { intarr := [5 ]int {12 , 34 , 55 , 66 , 43 } slice := intarr[:] fmt.Printf("the len is %d and cap is %d \n" , len (slice), cap (slice)) fmt.Printf("%p %p %p %p\n" , &slice[0 ], &intarr, slice, &slice) fmt.Printf("underlay: %#x\n" , (*reflect.SliceHeader)(unsafe.Pointer(&slice)).Data) }
输出:
1 2 3 the len is 5 and cap is 5 0x456000 0x456000 0x456000 0x40a0e0 underlay: 0x456000
按行读取文本 如果是对一个多行的字符串按行读取,则可以:
1 2 3 for _, line := range strings.Split(strings.TrimSuffix(x, "\n" ), "\n" ) { fmt.Println(line) }
如果是从文件或者流式管道中按行读取,则可以:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 scanner := bufio.NewScanner(f) for scanner.Scan() { fmt.Println(scanner.Text()) } if err := scanner.Err(); err != nil { } args := "-E -eNEW,DESTROY -ptcp --any-nat --buffer-size 1024000 --dport " + fmt.Sprintf("%d" , serviceNodePort) cmd := exec.Command("conntrack" , strings.Split(args, " " )...) stdout, _ := cmd.StdoutPipe() err := cmd.Start() if err != nil { common.ZapClient.Fatalf("start conntrack failed: %s" , err.Error()) errChan <- err return } scanner := bufio.NewScanner(stdout) for scanner.Scan() {}
从 http request body 中解析出 go 对象 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 var info MyLocalTypedata, err := ioutil.ReadAll(req.Body) if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte ("read data from request body failed!" )) } if err = json.Unmarshal(data, &info); err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte ("parse info from request body failed!" )) } if err := json.NewDecoder(req.Body).Decode(&info); err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte ("parse info from request body failed!" )) }
从静态文件生成 go code 并 serve 1 2 3 4 5 6 7 // 使用两个开源库 go get github.com/jteeuwen/go-bindata go get github.com/elazarl/go-bindata-assetfs // 从本地目录生成 go code // 会在当前目录生成 bindata.go go-bindata-assetfs swagger-ui/
之后就可以使用该文件创建一个 http 静态站点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package mainimport ( "log" "net/http" "github.com/elazarl/go-bindata-assetfs" "fake.local.com/test/swagger" ) func main () { http.Handle("/" , http.FileServer(&assetfs.AssetFS{ Asset: swagger.Asset, AssetDir: swagger.AssetDir, Prefix: "swagger-ui" , })) log.Println("http server started on :8000" ) err := http.ListenAndServe(":8000" , nil ) if err != nil { log.Fatal("ListenAndServe: " , err) } }
生成 zip 文件并返回给 http response 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 func zipHandler (w http.ResponseWriter, r *http.Request) { filename := "randomfile.jpg" buf := new (bytes.Buffer) writer := zip.NewWriter(buf) data, err := ioutil.ReadFile(filename) if err != nil { log.Fatal(err) } f, err := writer.Create(filename) if err != nil { log.Fatal(err) } _, err = f.Write([]byte (data)) if err != nil { log.Fatal(err) } err = writer.Close() if err != nil { log.Fatal(err) } w.Header().Set("Content-Type" , "application/zip" ) w.Header().Set("Content-Disposition" , fmt.Sprintf("attachment; filename=\"%s.zip\"" , filename)) w.Write(buf.Bytes()) }
另一种简单的写法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 func handleZip (w http.ResponseWriter, r *http.Request) { f, err := os.Open("main.go" ) if err != nil { log.Fatal(err) } defer func () { if err := f.Close(); err != nil { log.Fatal(err) } }() zw := zip.NewWriter(w) cf, err := zw.Create(f.Name()) if err != nil { log.Fatal(err) } w.Header().Set("Content-Type" , "application/zip" ) w.Header().Set("Content-Disposition" , fmt.Sprintf("attachment; filename=\"%s.zip\"" , f.Name())) _, err = io.Copy(cf, f) if err != nil { log.Fatal(err) } err = zw.Close() if err != nil { log.Fatal(err) } }
反向代理 在 Go 语言中可以很方便地构建反向代理服务器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 func serveReverseProxy (target string , res http.ResponseWriter, req *http.Request) { url, _ := url.Parse(target) proxy := httputil.NewSingleHostReverseProxy(url) req.URL.Host = url.Host req.URL.Scheme = url.Scheme req.Header.Set("X-Forwarded-Host" , req.Header.Get("Host" )) req.Host = url.Host proxy.ServeHTTP(res, req) }
写入文件 当待写入的文件已经存在时,应该以可写模式打开它进行写入;当待写入文件不存在时,应该创建该文件并进行写入。直觉上,我们应当首先判断文件是否存在,可以使用如下代码:
1 2 3 if _, err := os.Stat("/path/to/whatever" ); os.IsNotExist(err) { }
通过跟踪 os.IsNotExist
函数的实现可以发现,它主要处理两类错误: os.ErrNotExist
和 syscall.ENOENT
,也就是只有这两种错误才会使得 os.IsNotExist(err)
返回 true
。实际上,仅仅这两种错误是无法确定文件是不存在的,有时 os.Stat
返回 ENOTDIR
而不是 ENOENT
,例如,如果 /etc/bashrc
文件存在,则使用 os.Stat
检查 /etc/bashrc/foobar
是否存在时会返回 ENOTDIR
错误表明 /etc/bashrc
不是一个目录,因此上述写法是有问题的。实际上使用 os.Stat
的可能结果如下:
1 2 3 4 5 6 7 8 if _, err := os.Stat("/path/to/whatever" ); err == nil { } else if os.IsNotExist(err) { } else { }
也就是说使用 os.Stat
无法确定文件是否存在,因此写入文件时先使用 os.Stat
判断文件是否存在,不存在时则使用 os.Create
创建文件的写法是错误的(尽管大多数时候能够成功写入)。正确的写入文件的方法是 os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0666)
,这个函数通过 sys_openat 系统调用依据传入的 Flag 打开文件,如果文件不存在则创建,如果文件存在则直接打开,使用这个函数的另一个好处是不会产生竞争条件(即使另外一个操作正在创建该文件?),参见 https://stackoverflow.com/questions/12518876/how-to-check-if-a-file-exists-in-go 中的一系列回答和讨论。 另一种选择是使用 ioutil.WriteFile()
,其内部同样是调用了 os.OpenFile
,只不过只适用于一次性全量写入。
监听系统信号实现优雅退出 1 2 3 4 5 6 7 ctx, cancel := context.WithCancel(context.Background()) sigc := make (chan os.Signal, 1 ) signal.Notify(sigc, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) go func () { <-sigc cancel() }()
流式下载并保存文件 1 2 3 4 5 6 7 8 9 import ("net/http" ; "io" ; "os" )... out, err := os.Create("output.txt" ) defer out.Close()... resp, err := http.Get("http://example.com/" ) defer resp.Body.Close()... n, err := io.Copy(out, resp.Body)
打印 context 中的键值对 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 func PrintContextInternals (ctx interface {}, inner bool ) { contextValues := reflect.ValueOf(ctx).Elem() contextKeys := reflect.TypeOf(ctx).Elem() if !inner { fmt.Printf("\nFields for %s.%s\n" , contextKeys.PkgPath(), contextKeys.Name()) } if contextKeys.Kind() == reflect.Struct { for i := 0 ; i < contextValues.NumField(); i++ { reflectValue := contextValues.Field(i) reflectValue = reflect.NewAt(reflectValue.Type(), unsafe.Pointer(reflectValue.UnsafeAddr())).Elem() reflectField := contextKeys.Field(i) if reflectField.Name == "Context" { PrintContextInternals(reflectValue.Interface(), true ) } else { fmt.Printf("field name: %+v\n" , reflectField.Name) fmt.Printf("value: %+v\n" , reflectValue.Interface()) } } } else { fmt.Printf("context is empty (int)\n" ) } }
对耗时操作添加超时处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 go func () { errChan := make (chan error, 1 ) go func () { _, err := doSomethingNeedLongTime() errChan <- err }() select { case <-time.After(10 * time.Second): log.Error("timeout" ) state.Set(fail) return case err := <-errChan: if err != nil { log.Error("error" ) state.Set(fail) return } state.Set(success) } }()
封装 Reader 为 ReadCloser 如果确定 Reader 不需要进行真实的关闭操作,而接口又需要传入一个 ReadCloser,则可以使用以下方法进行简单封装:
1 2 stringReader := strings.NewReader("shiny!" ) stringReadCloser := io.NopCloser(stringReader)