1 - GolangUtils

1.1 - goroutine

获取goroutineID

func goID() uint64 {
    b := make([]byte, 64)
    b = b[:runtime.Stack(b, false)]
    b = bytes.TrimPrefix(b, []byte("goroutine "))
    b = b[:bytes.IndexByte(b, ' ')]
    n, _ := strconv.ParseUint(string(b), 10, 64)
    return n
}

1.2 - Map

初始化

// 先声明map
var m1 map[string]string
// 再使用make函数创建一个非nil的map,nil map不能赋值
m1 = make(map[string]string)
// 最后给已声明的map赋值
m1["a"] = "aa"
m1["b"] = "bb"

// 直接创建
m2 := make(map[string]string)
// 然后赋值
m2["a"] = "aa"
m2["b"] = "bb"

// 初始化 + 赋值一体化
m3 := map[string]string{
    "a": "aa",
    "b": "bb",
}

判断key是否存在

if v, ok := m1["a"]; ok {
    fmt.Println(v)
} else {
    fmt.Println("Key Not Found")
}

遍历map

for k, v := range m1 {
    fmt.Println(k, v)
}

删除一个元素

scene := make(map[string]int)

// 准备map数据
scene["route"] = 66
scene["brazil"] = 4
scene["china"] = 960

delete(scene, "brazil")

for k, v := range scene {
    fmt.Println(k, v)
}
//route 66
//china 960

安全的Map

1. map+锁

type SafeMap struct {
    Data map[string]interface{}
    Lock sync.RWMutex
}
 
func (this *SafeMap) Get(k string) interface{} {
    this.Lock.RLock()
    defer this.Lock.RUnlock()
    if v, exit := this.Data[k]; exit {
        return v
    }
    return nil
}
 
func (this *SafeMap) Set(k string, v interface{}) {
    this.Lock.Lock()
    defer this.Lock.Unlock()
    if this.Data == nil {
        this.Data = make(map[string]interface{})
    }
    this.Data[k] = v
}

2. sync.map

var test sync.Map
 
//设置元素
func set (k,v interface{}){
    test.Store(k,v)
}
 
//获得元素
func get (k interface{}) interface{}{
    tem ,exit := test.Load(k)
    if exit {
        return tem
    }
    return nil
}
 
//传入一个 函数 ,sync.map  会内部迭代 ,运行这个函数
func ranggfunc (funcs func(key, value interface{}) bool) {
    test.Range(funcs)
}
 
//删除元素
func del(key interface{}){
    test.Delete(key)
}

1.4 - 结构体

判断变量是否==空结构体

if reflect.DeepEqual(deviceModel, models.DeviceModel{}) {
    //code
}

结构提转map

//使用反射转换的效率要高于 struct->json->map
//传变量
func Struct2Map(obj interface{}) map[string]interface{} {
    t := reflect.TypeOf(obj)
    v := reflect.ValueOf(obj)
 
    var data = make(map[string]interface{})
    for i := 0; i < t.NumField(); i++ {
        data[t.Field(i).Name] = v.Field(i).Interface()
    }
    return data
}

//传指针,如果一定要声明称指针( obj := new(Test)或obj := &Test{} )时,Struct2Map方法中需要将取出指针的内容然后继续操作,因为指针是没有Field()方法的。
func Struct2Map(obj interface{}) map[string]interface{} {
    obj_v := reflect.ValueOf(obj)
    v := obj_v.Elem()
    typeOfType := v.Type()
    var data = make(map[string]interface{})
    for i := 0; i < v.NumField(); i++ {
        field := v.Field(i)
        data[typeOfType.Field(i).Name] = field.Interface()
    }
    return data
}
// Clone deep-copies a to b
func Clone(a, b interface{}) {

    buff := new(bytes.Buffer)
    enc := gob.NewEncoder(buff)
    dec := gob.NewDecoder(buff)
    enc.Encode(a)
    dec.Decode(b)
}

func main() {
    a1 := A{
        AA: "jilao",
        BB: 1,
    }
    var a2 A
    Clone(a1, a2)
    a2.AA = "lakjg;odfig"
    fmt.Println(a1.AA)
    fmt.Println(a2.AA)
}

1.5 - 进制转换

import (
   "fmt"
   "log"
   "math"
   "strconv"
   "strings"
)

// Decimal to binary  十进制转二进制
func DecBin(n int64) string {
   if n < 0 {
      log.Println("Decimal to binary error: the argument must be greater than zero.")
      return ""
   }
   if n == 0 {
      return "0"
   }
   s := ""
   for q := n; q > 0; q = q / 2 {
      m := q % 2
      s = fmt.Sprintf("%v%v", m, s)
   }
   
   return s
}

// Decimal to octal 十进制转八进制
func DecOct(d int64) int64 {
   if d == 0 {
      return 0
   }
   if d < 0 {
      log.Println("Decimal to octal error: the argument must be greater than zero.")
      return -1
   }
   s := ""
   for q := d; q > 0; q = q / 8 {
      m := q % 8
      s = fmt.Sprintf("%v%v", m, s)
   }
   n, err := strconv.Atoi(s)
   if err != nil {
      log.Println("Decimal to octal error:", err.Error())
      return -1
   }
   return int64(n)
}

// Decimal to hexadecimal 十进制转16进制
func DecHex(n int64) string {
   if n < 0 {
      log.Println("Decimal to hexadecimal error: the argument must be greater than zero.")
      return ""
   }
   if n == 0 {
      return "0"
   }
   hex := map[int64]int64{10: 65, 11: 66, 12: 67, 13: 68, 14: 69, 15: 70}
   s := ""
   for q := n; q > 0; q = q / 16 {
      m := q % 16
      if m > 9 && m < 16 {
         m = hex[m]
         s = fmt.Sprintf("%v%v", string(m), s)
         continue
      }
      s = fmt.Sprintf("%v%v", m, s)
   }
   return s
}

// Binary to decimal 二进制转十进制
func BinDec(b string) (n int64) {
   s := strings.Split(b, "")
   l := len(s)
   i := 0
   d := float64(0)
   for i = 0; i < l; i++ {
      f, err := strconv.ParseFloat(s[i], 10)
      if err != nil {
         log.Println("Binary to decimal error:", err.Error())
         return -1
      }
      d += f * math.Pow(2, float64(l-i-1))
   }
   return int64(d)
}

// Octal to decimal  八进制转十进制
func OctDec(o int64) (n int64) {
   s := strings.Split(strconv.Itoa(int(o)), "")
   l := len(s)
   i := 0
   d := float64(0)
   for i = 0; i < l; i++ {
      f, err := strconv.ParseFloat(s[i], 10)
      if err != nil {
         log.Println("Octal to decimal error:", err.Error())
         return -1
      }
      d += f * math.Pow(8, float64(l-i-1))
   }
   return int64(d)
}

// Hexadecimal to decimal 十六进制转十进制
func HexDec(h string) (n int64) {
   s := strings.Split(strings.ToUpper(h), "")
   l := len(s)
   i := 0
   d := float64(0)
   hex := map[string]string{"A": "10", "B": "11", "C": "12", "D": "13", "E": "14", "F": "15"}
   for i = 0; i < l; i++ {
      c := s[i]
      if v, ok := hex[c]; ok {
         c = v
      }
      f, err := strconv.ParseFloat(c, 10)
      if err != nil {
         log.Println("Hexadecimal to decimal error:", err.Error())
         return -1
      }
      d += f * math.Pow(16, float64(l-i-1))
   }
   return int64(d)
}

// Octal to binary 八进制转二进制
func OctBin(o int64) string {
   d := OctDec(o)
   if d == -1 {
      return ""
   }
   return DecBin(d)
}

// Hexadecimal to binary 十六进制转二进制
func HexBin(h string) string {
   d := HexDec(h)
   if d == -1 {
      return ""
   }
   return DecBin(d)
}

// Binary to octal 二进制转八进制
func BinOct(b string) int64 {
   d := BinDec(b)
   if d == -1 {
      return -1
   }
   return DecOct(d)
}

// Binary to hexadecimal 二进制转十六进制
func BinHex(b string) string {
   d := BinDec(b)
   if d == -1 {
      return ""
   }
   return DecHex(d)
}

一个硬核的16进制转10进制 看起来有点笨, 但是很有效

var b2m_map map[byte]uint64 = map[byte]uint64{
    0x00: 0,
    0x01: 1,
    0x02: 2,
    0x03: 3,
    0x04: 4,
    0x05: 5,
    0x06: 6,
    0x07: 7,
    0x08: 8,
    0x09: 9,
    0x0A: 10,
    0x0B: 11,
    0x0C: 12,
    0x0D: 13,
    0x0E: 14,
    0x0F: 15,
    0x10: 16,
    0x11: 17,
    0x12: 18,
    0x13: 19,
    0x14: 20,
    0x15: 21,
    0x16: 22,
    0x17: 23,
    0x18: 24,
    0x19: 25,
    0x1A: 26,
    0x1B: 27,
    0x1C: 28,
    0x1D: 29,
    0x1E: 30,
    0x1F: 31,
    0x20: 32,
    0x21: 33,
    0x22: 34,
    0x23: 35,
    0x24: 36,
    0x25: 37,
    0x26: 38,
    0x27: 39,
    0x28: 40,
    0x29: 41,
    0x2A: 42,
    0x2B: 43,
    0x2C: 44,
    0x2D: 45,
    0x2E: 46,
    0x2F: 47,
    0x30: 48,
    0x31: 49,
    0x32: 50,
    0x33: 51,
    0x34: 52,
    0x35: 53,
    0x36: 54,
    0x37: 55,
    0x38: 56,
    0x39: 57,
    0x3A: 58,
    0x3B: 59,
    0x3C: 60,
    0x3D: 61,
    0x3E: 62,
    0x3F: 63,
    0x40: 64,
    0x41: 65,
    0x42: 66,
    0x43: 67,
    0x44: 68,
    0x45: 69,
    0x46: 70,
    0x47: 71,
    0x48: 72,
    0x49: 73,
    0x4A: 74,
    0x4B: 75,
    0x4C: 76,
    0x4D: 77,
    0x4E: 78,
    0x4F: 79,
    0x50: 80,
    0x51: 81,
    0x52: 82,
    0x53: 83,
    0x54: 84,
    0x55: 85,
    0x56: 86,
    0x57: 87,
    0x58: 88,
    0x59: 89,
    0x5A: 90,
    0x5B: 91,
    0x5C: 92,
    0x5D: 93,
    0x5E: 94,
    0x5F: 95,
    0x60: 96,
    0x61: 97,
    0x62: 98,
    0x63: 99,
    0x64: 100,
    0x65: 101,
    0x66: 102,
    0x67: 103,
    0x68: 104,
    0x69: 105,
    0x6A: 106,
    0x6B: 107,
    0x6C: 108,
    0x6D: 109,
    0x6E: 110,
    0x6F: 111,
    0x70: 112,
    0x71: 113,
    0x72: 114,
    0x73: 115,
    0x74: 116,
    0x75: 117,
    0x76: 118,
    0x77: 119,
    0x78: 120,
    0x79: 121,
    0x7A: 122,
    0x7B: 123,
    0x7C: 124,
    0x7D: 125,
    0x7E: 126,
    0x7F: 127,
    0x80: 128,
    0x81: 129,
    0x82: 130,
    0x83: 131,
    0x84: 132,
    0x85: 133,
    0x86: 134,
    0x87: 135,
    0x88: 136,
    0x89: 137,
    0x8A: 138,
    0x8B: 139,
    0x8C: 140,
    0x8D: 141,
    0x8E: 142,
    0x8F: 143,
    0x90: 144,
    0x91: 145,
    0x92: 146,
    0x93: 147,
    0x94: 148,
    0x95: 149,
    0x96: 150,
    0x97: 151,
    0x98: 152,
    0x99: 153,
    0x9A: 154,
    0x9B: 155,
    0x9C: 156,
    0x9D: 157,
    0x9E: 158,
    0x9F: 159,
    0xA0: 160,
    0xA1: 161,
    0xA2: 162,
    0xA3: 163,
    0xA4: 164,
    0xA5: 165,
    0xA6: 166,
    0xA7: 167,
    0xA8: 168,
    0xA9: 169,
    0xAA: 170,
    0xAB: 171,
    0xAC: 172,
    0xAD: 173,
    0xAE: 174,
    0xAF: 175,
    0xB0: 176,
    0xB1: 177,
    0xB2: 178,
    0xB3: 179,
    0xB4: 180,
    0xB5: 181,
    0xB6: 182,
    0xB7: 183,
    0xB8: 184,
    0xB9: 185,
    0xBA: 186,
    0xBB: 187,
    0xBC: 188,
    0xBD: 189,
    0xBE: 190,
    0xBF: 191,
    0xC0: 192,
    0xC1: 193,
    0xC2: 194,
    0xC3: 195,
    0xC4: 196,
    0xC5: 197,
    0xC6: 198,
    0xC7: 199,
    0xC8: 200,
    0xC9: 201,
    0xCA: 202,
    0xCB: 203,
    0xCC: 204,
    0xCD: 205,
    0xCE: 206,
    0xCF: 207,
    0xD0: 208,
    0xD1: 209,
    0xD2: 210,
    0xD3: 211,
    0xD4: 212,
    0xD5: 213,
    0xD6: 214,
    0xD7: 215,
    0xD8: 216,
    0xD9: 217,
    0xDA: 218,
    0xDB: 219,
    0xDC: 220,
    0xDD: 221,
    0xDE: 222,
    0xDF: 223,
    0xE0: 224,
    0xE1: 225,
    0xE2: 226,
    0xE3: 227,
    0xE4: 228,
    0xE5: 229,
    0xE6: 230,
    0xE7: 231,
    0xE8: 232,
    0xE9: 233,
    0xEA: 234,
    0xEB: 235,
    0xEC: 236,
    0xED: 237,
    0xEE: 238,
    0xEF: 239,
    0xF0: 240,
    0xF1: 241,
    0xF2: 242,
    0xF3: 243,
    0xF4: 244,
    0xF5: 245,
    0xF6: 246,
    0xF7: 247,
    0xF8: 248,
    0xF9: 249,
    0xFA: 250,
    0xFB: 251,
    0xFC: 252,
    0xFD: 253,
    0xFE: 254,
    0xFF: 255,
}

func hex2int(hexB *[]byte) uint64 {
    var retInt uint64
    hexLen := len(*hexB)
    for k, v := range *hexB {
        retInt += b2m_map[v] * exponent(16, uint64(2*(hexLen-k-1)))
    }
    return retInt
}

1.6 - 日期时间函数

对2006-01-02 15:04:05 (go的诞生时间) 按照123456来记忆:01月02号 下午3点04分05秒 2006年

获得秒,毫秒,纳秒时间戳

fmt.Printf("时间戳(秒):%v;\n", time.Now().Unix())
fmt.Printf("时间戳(纳秒):%v;\n",time.Now().UnixNano())
fmt.Printf("时间戳(毫秒):%v;\n",time.Now().UnixNano() / 1e6)
fmt.Printf("时间戳(纳秒转换为秒):%v;\n",time.Now().UnixNano() / 1e9)

获取格式化的前 5秒,5分钟,5小时,5天,5个月,5年前的时间

对 分小时天月年 设置格式 Format(“2006-01-02 15:04:05”) —> Format(“2006-01-02 15:00:00”), 写成00就是把对应位置直接赋值成00

st,_ := time.ParseDuration("-5s")
fmt.Println("5秒前的时间:",time.Now().Add(st).Format("2006-01-02 15:04:05"))
fmt.Println("5秒前的时间:",time.Now().Add(time.Second*-5).Format("2006-01-02 15:04:05"))

st,_ = time.ParseDuration("-5m")
fmt.Println("5分前的时间:",time.Now().Add(st).Format("2006-01-02 15:04:05"))
fmt.Println("5分前的时间:",time.Now().Add(time.Minute*-5).Format("2006-01-02 15:04:05"))

st,_ = time.ParseDuration("-5h")
fmt.Println("5小时前的时间:",time.Now().Add(st).Format("2006-01-02 15:04:05"))
fmt.Println("5小时前的时间:",time.Now().Add(time.Hour*-5).Format("2006-01-02 15:04:05"))


fmt.Println("5天前的时间:",time.Now().AddDate(0, 0, -5).Format("2006-01-02 15:04:05"))

fmt.Println("5月前的时间:",time.Now().AddDate(0, -5, 0).Format("2006-01-02 15:04:05"))

fmt.Println("5年前的时间:",time.Now().AddDate(-5, 0, 0).Format("2006-01-02 15:04:05"))

外部传入字符串时间戳输出

package main

import (
    "log"
    "time"
)

func main() {

    t := int64(1595581744)   //外部传入的时间戳(秒为单位),必须为int64类型, time.Unix(t, 0)必须是到秒的时间戳
    t1 := "2019-01-08 13:50:30" //外部传入的时间字符串

    //时间转换的模板,golang里面只能是 "2006-01-02 15:04:05" (go的诞生时间)
    timeTemplate1 := "2006-01-02 15:04:05" //常规类型
    timeTemplate2 := "2006/01/02 15:04:05" //其他类型
    timeTemplate3 := "2006-01-02"          //其他类型
    timeTemplate4 := "15:04:05"            //其他类型

    // ======= 将时间戳格式化为日期字符串 =======
    log.Println(time.Unix(t, 0).Format(timeTemplate1)) //输出:2019-01-08 13:50:30
    log.Println(time.Unix(t, 0).Format(timeTemplate2)) //输出:2019/01/08 13:50:30
    log.Println(time.Unix(t, 0).Format(timeTemplate3)) //输出:2019-01-08
    log.Println(time.Unix(t, 0).Format(timeTemplate4)) //输出:13:50:30

    // ======= 将时间字符串转换为时间戳 =======
    stamp, _ := time.ParseInLocation(timeTemplate1, t1, time.Local) //使用parseInLocation将字符串格式化返回本地时区时间
    log.Println(stamp.Unix())                                       //输出:1546926630
}

1.7 - 数据类型转换

string to other = 字符串转其他

string -> int

i1, err := strconv.Atoi("1")

string -> int64

i64, err := strconv.ParseInt("2", 10, 64)

hexstring -> []byte 16进制字符串转(16进制代表的)byte数组

hex_str := "4161"
hex_data, _ := hex.DecodeString(hex_str)
fmt.Println(string(hexData))//Aa

1.8 - 数学函数

向上取整math.Ceil() 向下取整math.Floor()

//go没有其他语言得round函数,整数位得四舍五入简单版 func round(x float64){ return int(math.Floor(x + 0/5)) }

package main

import (
    "fmt"
    "github.com/shopspring/decimal"
)

/*
保有小数位得四舍五入
*/
func main() {
    v1, _ := decimal.NewFromFloat(9.824).Round(2).Float64()
    v2, _ := decimal.NewFromFloat(9.826).Round(2).Float64()
    v3, _ := decimal.NewFromFloat(9.8251).Round(2).Float64()
    fmt.Println(v1, v2, v3)

    v4, _ := decimal.NewFromFloat(9.815).Round(2).Float64()
    v5, _ := decimal.NewFromFloat(9.825).Round(2).Float64()
    v6, _ := decimal.NewFromFloat(9.835).Round(2).Float64()
    v7, _ := decimal.NewFromFloat(9.845).Round(2).Float64()
    fmt.Println(v4, v5, v6, v7)

    v8, _ := decimal.NewFromFloat(3.3).Round(2).Float64()
    v9, _ := decimal.NewFromFloat(3.3000000000000003).Round(2).Float64()
    v10, _ := decimal.NewFromFloat(3).Round(2).Float64()
    fmt.Println(v8, v9, v10)

    v11, _ := decimal.NewFromFloat(129.975).Round(2).Float64()
    v12, _ := decimal.NewFromFloat(34423.125).Round(2).Float64()
    fmt.Println(v11, v12)
}

1.9 - 文件目录操作函数

获取指定目录下正则匹配到的文件

package main

import (
    "fmt"
    "log"
    "os"
    "path/filepath"
)

func main() {

    files, err := WalkMatch("./", "*.exe")
    if err != nil {
        log.Fatalln(err)
    }
    fmt.Println(files)

}
func WalkMatch(root, pattern string) ([]string, error) {
    var matches []string
    err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
        if err != nil {
            return err
        }
        if info.IsDir() {
            return nil
        }
        if matched, err := filepath.Match(pattern, filepath.Base(path)); err != nil {
            return err
        } else if matched {
            path, _ = filepath.Abs(path)
            matches = append(matches, path)
        }
        return nil
    })
    if err != nil {
        return nil, err
    }
    return matches, nil
}

1.10 - 系统变量初始化

Context

ctx, _ := context.WithTimeout(context.Background(), 15 * time.Second)

获取当前路径

package main

import (
    "os"
    "path/filepath"
)

func main() {
    // 据说这个方法在某些特别场景会获取到错误的路径
    dir1, _ := os.Getwd()

    //推荐使用下面的方法
    dir2, _ := os.Executable()
    exPath := filepath.Dir(dir2)
    println(exPath2)
}

1.11 - 序列化

序列化存入内存

f,err = os.Open("path")
if err != nil {
    driver.logger.Error("文件打开失败:"+err.Error())
    os.Exit(-1)
}
dec := gob.NewDecoder(f)
err = dec.Decode(&helper.DeviceTerminalMap)
//判断有错误并且不是文件为空的错误,文件如果为空,在读文件是直接返回文件结束符(EOF)
if err != nil && err != io.EOF {
    driver.logger.Error("设备终端档案存储文件解析失败:"+err.Error())
    os.Exit(-1)
}

写入序列化内容

f, _ := os.Open("device-terminal.god")
defer f.Close()
dec := gob.NewDecoder(f)
err = dec.Decode(&helper.DeviceTerminalMap)

1.12 - 整型

int64 -> string

var i1 int64
i1 = 555
str1 := strconv.FormatInt(int1,10)

int -> string

i2 := 1
str2 := strconv.Itoa(i2)

int8 -> string

var int8Value int8
int8Value = 2
strconv.Itoa(int(int8Value))
//整形转换成字节
func IntToBytes(n int) []byte {
  x := int32(n)
  bytesBuffer := bytes.NewBuffer([]byte{})
  binary.Write(bytesBuffer, binary.BigEndian, x)
  return bytesBuffer.Bytes()
}
//字节转换成整形
func BytesToInt(b []byte) int {
  bytesBuffer := bytes.NewBuffer(b)
  
  var x int32
  binary.Read(bytesBuffer, binary.BigEndian, &x)
  
  return int(x)
}

1.13 - 字符串函数

生成UUID

package main

import (
    "github.com/satori/go.uuid"
    "fmt"
)

func main(){
    u1 := uuid.Must(uuid.NewV4())
    fmt.Printf("UUIDv4:%s\n", u1)

    u2, err := uuid.FromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8")
    if err != nil {
        fmt.Printf("Something went wrong: %s", err)
        return
    }
    fmt.Printf("Successfully parsed: %s", u2)
}

字符串拆分


//1. 按指定字符拆分
s := "iiaiibiiciiiidiiii"
sep:="ii"
arr:=strings.Split(s,sep)
fmt.Println("arr:",arr)

//2. 按空格拆分
s:=" ab cd          ef gh ij kl "
arr:=strings.Fields(s)
fmt.Printf("arr:%q\n",arr)

接收gbk编码的中文要转成utf8的中文

//github.com/axgle/mahonia
func ConvertToString(src string, srcCode string, tagCode string) string {
    srcCoder := mahonia.NewDecoder(srcCode)
    srcResult := srcCoder.ConvertString(src)
    tagCoder := mahonia.NewDecoder(tagCode)
    _, cdata, _ := tagCoder.Translate([]byte(srcResult), true)
    result := string(cdata)
    return result
}
func main() {
    //gbk编码的中文,用16进制字符串表示
    hex_str := "4d6f646275732e58464a2e3330462ec0e4c4fdcbaec5c5cbaeb1c3"
    hex_data, _ := hex.DecodeString(hex_str)
    // 将 byte 转换 为字符串 输出结果
    str := ConvertToString(string(hex_data), "gbk", "utf-8")
    fmt.Println(str)

    //先试试这个,一般就可以了
    s1 := "4d6f646275732e58464a2e3330462ec0e4c4fdcbaec5c5cbaeb1c3"
    hex_data, _ := hex.DecodeString(s1)
    srcCoder := mahonia.NewDecoder("gbk")
    srcResult := srcCoder.ConvertString(string(hex_data))
    fmt.Println(srcResult)
}

MD5

package main

import (
"crypto/md5"
"fmt"
"io"
)

func main() {
str := "abc123"

//方法一
data := []byte(str)
has := md5.Sum(data)
md5str1 := fmt.Sprintf("%x", has) //将[]byte转成16进制

fmt.Println(md5str1)

//方法二

w := md5.New()
io.WriteString(w, str)   //将str写入到w中
md5str2: = fmt.Sprintf("%x", w.Sum(nil))  //w.Sum(nil)将w的hash转成[]byte格式

fmt.Println(mdtstr2)
}

Base64

package main

import (
   "encoding/base64"
   "fmt"
)

func main()  {

   //标准base64编码
   data:="abckagfd*^&&^*fadf";

   sEnc:=base64.StdEncoding.EncodeToString([]byte(data))

   fmt.Println(sEnc)

   sDec,_:=base64.StdEncoding.DecodeString(sEnc)

   fmt.Println(string(sDec))

   //兼容base64编码
   uEnc:=base64.URLEncoding.EncodeToString([]byte(data))

   fmt.Println(uEnc)

   uDec,_:=base64.URLEncoding.DecodeString(uEnc)
   fmt.Println(string(uDec))

}

字符串和[]byte相互转换

package main

import (
    "fmt"
    "reflect"
    "time"
    "unsafe"
)

//零拷贝字符串转字节数组
func string2bytes(s string) []byte {
    stringHeader := (*reflect.StringHeader)(unsafe.Pointer(&s))
    var b []byte
    pbytes := (*reflect.SliceHeader)(unsafe.Pointer(&b))
    pbytes.Data = stringHeader.Data
    pbytes.Len = stringHeader.Len
    pbytes.Cap = stringHeader.Len
    return b
}

//零拷贝字节数组转字符串
func bytes2string(b []byte) string {
    bHeader := (*reflect.SliceHeader)(unsafe.Pointer(&b))
    var s string
    stringHeader := (*reflect.StringHeader)(unsafe.Pointer(&s))
    stringHeader.Data = bHeader.Data
    stringHeader.Len = bHeader.Len
    return s
}

func main() {
    s := "零拷贝转换字符串和字节数组"
    t1 := time.Now().Nanosecond()
    v := string2bytes(s)
    t2 := time.Now().Nanosecond()
    fmt.Println(v)

    t3 := time.Now().Nanosecond()
    data := []byte(s)
    t4 := time.Now().Nanosecond()
    fmt.Println(data)

    fmt.Println("method1 time", t2-t1)
    fmt.Println("method2 time", t4-t3)

    t5 := time.Now().Nanosecond()
    s1 := bytes2string(v)
    t6 := time.Now().Nanosecond()
    fmt.Println(s1)

    t7 := time.Now().Nanosecond()
    s2 := string(v)
    t8 := time.Now().Nanosecond()
    fmt.Println(s2)
    fmt.Println("method3 time", t6-t5)
    fmt.Println("method4 time", t8-t7)
}

1.14 - 字节数组操作([]byte)

//isSymbol表示有无符号
func BytesToInt(b []byte, isSymbol bool)  (int, error){
    if isSymbol {
        return bytesToIntS(b)
    }
    return bytesToIntU(b)
}
 
 
//字节数(大端)组转成int(无符号的)
func bytesToIntU(b []byte) (int, error) {
    if len(b) == 3 {
        b = append([]byte{0},b...)
    }
    bytesBuffer := bytes.NewBuffer(b)
    switch len(b) {
    case 1:
        var tmp uint8
        err := binary.Read(bytesBuffer, binary.BigEndian, &tmp)
        return int(tmp), err
    case 2:
        var tmp uint16
        err := binary.Read(bytesBuffer, binary.BigEndian, &tmp)
        return int(tmp), err
    case 4:
        var tmp uint32
        err := binary.Read(bytesBuffer, binary.BigEndian, &tmp)
        return int(tmp), err
    default:
        return 0,fmt.Errorf("%s", "BytesToInt bytes lenth is invaild!")
    }
}
 
 
 
//字节数(大端)组转成int(有符号)
func bytesToIntS(b []byte) (int, error) {
    if len(b) == 3 {
        b = append([]byte{0},b...)
    }
    bytesBuffer := bytes.NewBuffer(b)
    switch len(b) {
    case 1:
        var tmp int8
        err := binary.Read(bytesBuffer, binary.BigEndian, &tmp)
        return int(tmp), err
    case 2:
        var tmp int16
        err := binary.Read(bytesBuffer, binary.BigEndian, &tmp)
        return int(tmp), err
    case 4:
        var tmp int32
        err := binary.Read(bytesBuffer, binary.BigEndian, &tmp)
        return int(tmp), err
    default:
        return 0,fmt.Errorf("%s", "BytesToInt bytes lenth is invaild!")
    }
}
 
 
//整形转换成字节
func IntToBytes(n int,b byte) ([]byte,error) {
    switch b {
    case 1:
        tmp := int8(n)
        bytesBuffer := bytes.NewBuffer([]byte{})
        binary.Write(bytesBuffer, binary.BigEndian, &tmp)
        return bytesBuffer.Bytes(),nil
    case 2:
        tmp := int16(n)
        bytesBuffer := bytes.NewBuffer([]byte{})
        binary.Write(bytesBuffer, binary.BigEndian, &tmp)
        return bytesBuffer.Bytes(),nil
    case 3,4:
        tmp := int32(n)
        bytesBuffer := bytes.NewBuffer([]byte{})
        binary.Write(bytesBuffer, binary.BigEndian, &tmp)
        return bytesBuffer.Bytes(),nil
    }
    return nil,fmt.Errorf("IntToBytes b param is invaild")
}

2 - 最佳实践

2.1 - 单元测试

2.1.1 - Golang单元测试-wire依赖注入

Golang依赖注入框架wire全攻略

在前一阵介绍单元测试的系列文章中,曾经简单介绍过wire依赖注入框架。但当时的wire还处于alpha阶段,不过最近wire已经发布了首个beta版,API发生了一些变化,同时也承诺除非万不得已,将不会破坏API的兼容性。在前文中,介绍了一些wire的基本概况,本篇就不再重复,感兴趣的小伙伴们可以回看一下: 搞定Go单元测试(四)—— 依赖注入框架(wire)。本篇将具体介绍wire的使用方法和一些最佳实践。

本篇中的代码的完整示例可以在这里找到:wire-examples

Installing

go get github.com/google/wire/cmd/wire

Quick Start

我们先通过一个简单的例子,让小伙伴们对wire有一个直观的认识。下面的例子展示了一个简易wire依赖注入示例:

$ ls
main.go  wire.go

main.go

package main

import "fmt"

type Message struct {
    msg string
}
type Greeter struct {
    Message Message
}
type Event struct {
    Greeter Greeter
}
// NewMessage Message的构造函数
func NewMessage(msg string) Message {
    return Message{
        msg:msg,
    }
}
// NewGreeter Greeter构造函数
func NewGreeter(m Message) Greeter {
    return Greeter{Message: m}
}
// NewEvent Event构造函数
func NewEvent(g Greeter) Event {
    return Event{Greeter: g}
}
func (e Event) Start() {
    msg := e.Greeter.Greet()
    fmt.Println(msg)
}
func (g Greeter) Greet() Message {
    return g.Message
}

// 使用wire前
func main() {
    message := NewMessage("hello world")
    greeter := NewGreeter(message)
    event := NewEvent(greeter)

    event.Start()
}
/*
// 使用wire后
func main() {
    event := InitializeEvent("hello_world")

    event.Start()
}*/

wire.go

// +build wireinject
// The build tag makes sure the stub is not built in the final build.

package main

import "github.com/google/wire"

// InitializeEvent 声明injector的函数签名
func InitializeEvent(msg string) Event{
    wire.Build(NewEvent, NewGreeter, NewMessage)
    return Event{}  //返回值没有实际意义,只需符合函数签名即可
}

调用wire命令生成依赖文件:

$ wire
wire: github.com/DrmagicE/wire-examples/quickstart: wrote XXXX\github.com\DrmagicE\wire-examples\quickstart\wire_gen.go
$ ls
main.go  wire.go  wire_gen.go

wire_gen.go wire生成的文件

// Code generated by Wire. DO NOT EDIT.

//go:generate wire
//+build !wireinject

package main

// Injectors from wire.go:

func InitializeEvent(msg string) Event {
    message := NewMessage(msg)
    greeter := NewGreeter(message)
    event := NewEvent(greeter)
    return event
}

使用前 V.S 使用后

...
/*
// 使用wire前
func main() {
    message := NewMessage("hello world")
    greeter := NewGreeter(message)
    event := NewEvent(greeter)

    event.Start()
}*/

// 使用wire后
func main() {
    event := InitializeEvent("hello_world")

    event.Start()
}
...

使用wire后,只需调一个初始化方法既可得到Event了,对比使用前,不仅减少了三行代码,并且无需再关心依赖之间的初始化顺序。

示例传送门: quickstart

Provider & Injector

providerinjectorwire的两个核心概念。

provider: a function that can produce a value. These functions are ordinary Go code. injector: a function that calls providers in dependency order. With Wire, you write the injector’s signature, then Wire generates the function’s body. github.com/google/wire…

通过提供provider函数,让wire知道如何产生这些依赖对象。wire根据我们定义的injector函数签名,生成完整的injector函数,injector函数是最终我们需要的函数,它将按依赖顺序调用provider

在quickstart的例子中,NewMessage,NewGreeter,NewEvent都是providerwire_gen.go中的InitializeEvent函数是injector,可以看到injector通过按依赖顺序调用provider来生成我们需要的对象Event

上述示例在wire.go中定义了injector的函数签名,注意要在文件第一行加上

// +build wireinject
...

用于告诉编译器无需编译该文件。在injector的签名定义函数中,通过调用wire.Build方法,指定用于生成依赖的provider:

// InitializeEvent 声明injector的函数签名
func InitializeEvent(msg string) Event{
    wire.Build(NewEvent, NewGreeter, NewMessage) // <--- 传入provider函数
    return Event{}  //返回值没有实际意义,只需符合函数签名即可
}

该方法的返回值没有实际意义,只需要符合函数签名的要求即可。

高级特性

quickstart示例展示了wire的基础功能,本节将介绍一些高级特性。

接口绑定

根据依赖倒置原则(Dependence Inversion Principle),对象应当依赖于接口,而不是直接依赖于具体实现。

抽象成接口依赖更有助于单元测试哦! 搞定Go单元测试(一)——基础原理 搞定Go单元测试(二)—— mock框架(gomock)

在quickstart的例子中的依赖均是具体实现,现在我们来看看在wire中如何处理接口依赖:

// UserService
type UserService struct {
    userRepo UserRepository // <-- UserService依赖UserRepository接口
}

// UserRepository 存放User对象的数据仓库接口,比如可以是mysql,restful api ....
type UserRepository interface {
    // GetUserByID 根据ID获取User, 如果找不到User返回对应错误信息
    GetUserByID(id int) (*User, error)
}
// NewUserService *UserService构造函数
func NewUserService(userRepo UserRepository) *UserService {
    return &UserService{
        userRepo:userRepo,
    }
}

// mockUserRepo 模拟一个UserRepository实现
type mockUserRepo struct {
    foo string
    bar int
}
// GetUserByID UserRepository接口实现
func (u *mockUserRepo) GetUserByID(id int) (*User,error){
    return &User{}, nil
}
// NewMockUserRepo *mockUserRepo构造函数
func NewMockUserRepo(foo string,bar int) *mockUserRepo {
    return &mockUserRepo{
        foo:foo,
        bar:bar,
    }
}
// MockUserRepoSet 将 *mockUserRepo与UserRepository绑定
var MockUserRepoSet = wire.NewSet(NewMockUserRepo,wire.Bind(new(UserRepository), new(*mockUserRepo)))

在这个例子中,UserService依赖UserRepository接口,其中mockUserRepoUserRepository的一个实现,由于在Go的最佳实践中,更推荐返回具体实现而不是接口。所以mockUserRepoprovider函数返回的是*mockUserRepo这一具体类型。wire无法自动将具体实现与接口进行关联,我们需要显示声明它们之间的关联关系。通过wire.NewSetwire.Bind*mockUserRepoUserRepository进行绑定:

// MockUserRepoSet 将 *mockUserRepo与UserRepository绑定
var MockUserRepoSet = wire.NewSet(NewMockUserRepo,wire.Bind(new(UserRepository), new(*mockUserRepo)))

定义injector函数签名:

...
func InitializeUserService(foo string, bar int) *UserService{
    wire.Build(NewUserService,MockUserRepoSet) // 使用MockUserRepoSet
    return nil
}
...

示例传送门: binding-interfaces

返回错误

在前面的例子中,我们的provider函数均只有一个返回值,但在某些情况下,provider函数可能会对入参做校验,如果参数错误,则需要返回errorwire也考虑了这种情况,provider函数可以将返回值的第二个参数设置成error:

// Config 配置
type Config struct {
    // RemoteAddr 连接的远程地址
    RemoteAddr string

}
// APIClient API客户端
type APIClient struct {
    c Config
}
// NewAPIClient  APIClient构造函数,如果入参校验失败,返回错误原因
func NewAPIClient(c Config) (*APIClient,error) { // <-- 第二个参数设置成error
    if c.RemoteAddr == "" {
        return nil, errors.New("没有设置远程地址")
    }
    return &APIClient{
        c:c,
    },nil
}
// Service
type Service struct {
    client *APIClient
}
// NewService Service构造函数
func NewService(client *APIClient) *Service{
    return &Service{
        client:client,
    }
}

类似的,injector函数定义的时候也需要将第二个返回值设置成error

...
func InitializeClient(config Config) (*Service, error) { // <-- 第二个参数设置成error
    wire.Build(NewService,NewAPIClient)
    return nil,nil
}
...

观察一下wire生成的injector

func InitializeClient(config Config) (*Service, error) {
    apiClient, err := NewAPIClient(config)
    if err != nil { // <-- 在构造依赖的顺序中如果发生错误,则会返回对应的"零值"和相应错误
        return nil, err
    }
    service := NewService(apiClient)
    return service, nil
}

在构造依赖的顺序中如果发生错误,则会返回对应的"零值"和相应错误。

示例传送门: return-error

Cleanup functions

provider生成的对象需要一些cleanup处理,比如关闭文件,关闭数据库连接等操作时,依然可以通过设置provider的返回值来达到这样的效果:

// FileReader
type FileReader struct {
    f *os.File
}
// NewFileReader *FileReader 构造函数,第二个参数是cleanup function
func NewFileReader(filePath string) (*FileReader, func(), error){
    f, err := os.Open(filePath)
    if err != nil {
        return nil,nil,err
    }
    fr := &FileReader{
        f:f,
    }
    fn := func() {
        log.Println("cleanup")
        fr.f.Close()
    }
    return fr,fn,nil
}

跟返回错误类似,将provider的第二个返回参数设置成func()用于返回cleanup function,上述例子中在第三个参数中返回了error,但这是可选的:

wire对provider的返回值个数和顺序有所规定:

  1. 第一个参数是需要生成的依赖对象
  2. 如果返回2个返回值,第二个参数必须是func()或者error
  3. 如果返回3个返回值,第二个参数必须是func(),第三个参数则必须是error

示例传送门: cleanup-functions

Provider set

当一些provider通常是一起使用的时候,可以使用provider set将它们组织起来,以quickstart示例为模板稍作修改:

// NewMessage Message的构造函数
func NewMessage(msg string) Message {
    return Message{
        msg:msg,
    }
}
// NewGreeter Greeter构造函数
func NewGreeter(m Message) Greeter {
    return Greeter{Message: m}
}
// NewEvent Event构造函数
func NewEvent(g Greeter) Event {
    return Event{Greeter: g}
}
func (e Event) Start() {
    msg := e.Greeter.Greet()
    fmt.Println(msg)
}
// EventSet Event通常是一起使用的一个集合,使用wire.NewSet进行组合
var EventSet  = wire.NewSet(NewEvent, NewMessage, NewGreeter) // <--

上述例子中将Event和它的依赖通过wire.NewSet组合起来,作为一个整体在injector函数签名定义中使用:

func InitializeEvent(msg string) Event{
    //wire.Build(NewEvent, NewGreeter, NewMessage)
    wire.Build(EventSet)
    return Event{}
}

这时只需将EventSet传入wire.Build即可。

示例传送门: provider-set

结构体provider

除了函数外,结构体也可以充当provider的角色,类似于setter注入:

type Foo int
type Bar int

func ProvideFoo() Foo {
    return 1
}
func ProvideBar() Bar {
    return 2
}
type FooBar struct {
    MyFoo Foo
    MyBar Bar
}
var Set = wire.NewSet(
    ProvideFoo,
    ProvideBar,
    wire.Struct(new(FooBar), "MyFoo", "MyBar"))

通过wire.Struct来指定那些字段要被注入到结构体中,如果是全部字段,也可以简写成:

var Set = wire.NewSet(
    ProvideFoo,
    ProvideBar,
    wire.Struct(new(FooBar), "*")) // * 表示注入全部字段

生成的injector函数:

func InitializeFooBar() FooBar {
    foo := ProvideFoo()
    bar := ProvideBar()
    fooBar := FooBar{
        MyFoo: foo,
        MyBar: bar,
    }
    return fooBar
}

示例传送门: struct-provider

Best Practices

区分类型

由于injector的函数中,不允许出现重复的参数类型,否则wire将无法区分这些相同的参数类型,比如:

type FooBar struct {
    foo string
    bar string
}

func NewFooBar(foo string, bar string) FooBar {
    return FooBar{
        foo: foo,
        bar: bar,
    }
}

injector函数签名定义:

// wire无法得知入参a,b跟FooBar.foo,FooBar.bar的对应关系
func InitializeFooBar(a string, b string) FooBar {
    wire.Build(NewFooBar)
    return FooBar{}
}

如果使用上面的provider来生成injector,wire会报如下错误:

provider has multiple parameters of type string

因为入参均是字符串类型,wire无法得知入参a,b跟FooBar.foo,FooBar.bar的对应关系。 所以我们使用不同的类型来避免冲突:

type Foo string
type Bar string
type FooBar struct {
    foo Foo
    bar Bar
}

func NewFooBar(foo Foo, bar Bar) FooBar {
    return FooBar{
        foo: foo,
        bar: bar,
    }
}

injector函数签名定义:

func InitializeFooBar(a Foo, b Bar) FooBar {
    wire.Build(NewFooBar)
    return FooBar{}
}

其中基础类型和通用接口类型是最容易发生冲突的类型,如果它们在provider函数中出现,最好统一新建一个别名来代替它(尽管还未发生冲突),例如:

type MySQLConnectionString string
type FileReader io.Reader

示例传送门 distinguishing-types

Options Structs

如果一个provider方法包含了许多依赖,可以将这些依赖放在一个options结构体中,从而避免构造函数的参数太多:

type Message string

// Options
type Options struct {
    Messages []Message
    Writer   io.Writer
    Reader   io.Reader
}
type Greeter struct {
}

// NewGreeter Greeter的provider方法使用Options以避免构造函数过长
func NewGreeter(ctx context.Context, opts *Options) (*Greeter, error) {
    return nil, nil
}
// GreeterSet 使用wire.Struct设置Options为provider
var GreeterSet = wire.NewSet(wire.Struct(new(Options), "*"), NewGreeter)

injector函数签名:

func InitializeGreeter(ctx context.Context, msg []Message, w io.Writer, r io.Reader) (*Greeter, error) {
    wire.Build(GreeterSet)
    return nil, nil
}

示例传送门 options-structs

一些缺点和限制

额外的类型定义

由于wire自身的限制,injector中的变量类型不能重复,需要定义许多额外的基础类型别名。

mock支持暂时不够友好

目前wire命令还不能识别_test.go结尾文件中的provider函数,这样就意味着如果需要在测试中也使用wire来注入我们的mock对象,我们需要在常规代码中嵌入mock对象的provider,这对常规代码有侵入性,不过官方似乎也已经注意到了这个问题,感兴趣的小伙伴可以关注一下这条issue:github.com/google/wire…

更多参考

官方README.md
官方guide.md
官方best-practices.md

2.1.2 - Golang单元测试-简单示例

one.go

package unittest

func AddOne(t int32) int32 {
    return t + 1
}

func MinusOne(t int32) int32 {
    return t - 1
}

func MultiAddOne(t int32) int32 {
    t = MinusOne(t)
    t = AddOne(t)
    t = AddOne(t)
    return t
}

one_test.go

package unittest

import (
    "testing"

    . "github.com/agiledragon/gomonkey"
    . "github.com/smartystreets/goconvey/convey"
)

func TestMultiAddOne(t *testing.T) {
    Convey("TestApplyFunc", t, func() {
        Convey("input and output param", func() {
            patches := ApplyFunc(AddOne, func(t1 int32) int32 {
                return 5
            }) //对函数AddOne打桩
            defer patches.Reset()
            patches.ApplyFunc(MinusOne, func(t1 int32) int32 {
                return -2
            }) //对函数MinusOne打桩
            result := MultiAddOne(2) //看好了我调用的是MultiAddOne函数,而MultiAddOne函数内部调用了AddOne和MinusOne。
            So(result, ShouldEqual, 3)
        })
    })
}

2.1.3 - Golang单元测试01

搞定Go单元测试(一)——基础原理

单元测试是代码质量的保证。本系列文章将一步步由浅入深展示如何在Go中做单元测试。

Go对单元测试的支持相当友好,标准包中就支持单元测试,在开始本系阅读之前,需要对标准测试包的基本用法有所了解。

现在,我们从单元测试的基本思想和原理入手,一起来看看如何基于Go提供的标准测试包来进行单元测试。

单元测试的难点

1.掌握单元测试粒度

单元测试粒度是让人十分头疼的问题,特别是对于初尝单元测试的程序员。测试粒度做的太细,会耗费大量的开发以及维护时间,每改一个方法,都要改动其对应的测试方法。当发生代码重构的时候那简直就是噩梦(因为你所有的单元测试又都要写一遍了…)。 如单元测试粒度太粗,一个测试方法测试了n多方法,那么单元测试将显的非常臃肿,脱离了单元测试的本意,容易把单元测试写成集成测试

2. 破除外部依赖(mock,stub 技术)

单元测试一般不允许有任何外部依赖(文件依赖,网络依赖,数据库依赖等),我们不会在测试代码中去连接数据库,调用api等。这些外部依赖在执行测试的时候需要被模拟(mock/stub)。在测试的时候,我们使用模拟的对象来模拟真实依赖下的各种行为。如何运用mock/stub来模拟系统真实行为算是单元测试道路上的一只拦路虎。别着急,本文会通过示例来展示如何在Go中使用mock/stub来完成单元测试。

有的时候模拟是有效的方便的。但我们要提防过度的mock/stub,因为其会导致单元测试主要在测模拟对象而不是实际的系统。

Costs and Benefits

在受益于单元测试的好处的同时,也必然增加了代码量以及维护成本(单元测试代码也是要维护的)。下面这张成本/价值象限图很清晰的阐述了在不同性质的系统中单元测试成本价值之间的关系。

1.依赖很少的简单的代码(左下)

对于外部依赖少,代码又简单的代码。自然其成本和价值都是比较低的。举Go官方库里errors包为例,整个包就两个方法 New()Error(),没有任何外部依赖,代码也很简单,所以其单元测试起来也是相当方便。

2. 依赖较多但是很简单的代码(右下)

依赖一多,mock和stub就必然增多,单元测试的成本也就随之增加。但代码又如此简单(比如上述errors包的例子),这个时候写单元测试的成本已经大于其价值,还不如不写单元测试

3. 依赖很少的复杂代码 (左上)

像这一类代码,是最有价值写单元测试的。比如一些独立的复杂算法(银行利息计算,保险费率计算,TCP协议解析等),像这一类代码外部依赖很少,但却很容易出错,如果没有单元测试,几乎不能保证代码质量。

4.依赖很多又很复杂(右上)

这种代码显然是单元测试的噩梦。写单元测试吧,代价高昂;不写单元测试吧,风险太高。像这种代码我们尽量在设计上将其分为两部分:1.处理复杂的逻辑部分 2.处理依赖部分 然后1部分进行单元测试

原文参考:blog.stevensanderson.com/2009/11/04/…

迈出单元测试第一步

1. 识别依赖,抽象成接口

识别系统中的外部依赖,普遍来说,我们遇到最常见的依赖无非下面几种:

  1. 网络依赖——函数执行依赖于网络请求,比如第三方http-api,rpc服务,消息队列等等
  2. 数据库依赖
  3. I/O依赖(文件)

当然,还有可能是依赖还未开发完成的功能模块。但是处理方法都是大同小异的——抽象成接口,通过mock和stub进行模拟测试。

2. 明确需要测什么

当我们开始敲产品代码的时候,我们必然已经过初步的设计,已经了解系统中的外部依赖以及业务复杂的部分,这些部分是要优先考虑写单元测试的。在写每一个方法/结构体的时候同时思考这个方法/结构体需不需要测试?如何测试?对于什么样的方法/结构体需要测试,什么样的可以不做,除了可以从上面的成本/价值象限图中获得答案外,还可以参考以下关于单元测试粒度要做多细问题的回答:

老板为我的代码付报酬,而不是测试,所以,我对此的价值观是——测试越少越好,少到你对你的代码质量达到了某种自信(我觉得这种的自信标准应该要高于业内的标准,当然,这种自信也可能是种自大)。如果我的编码生涯中不会犯这种典型的错误(如:在构造函数中设了个错误的值),那我就不会测试它。我倾向于去对那些有意义的错误做测试,所以,我对一些比较复杂的条件逻辑会异常地小心。当在一个团队中,我会非常小心的测试那些会让团队容易出错的代码coolshell.cn/articles/82…

Mock和Stub怎么做

Mock(模拟)和Stub(桩)是在测试过程中,模拟外部依赖行为的两种常用的技术手段。 通过Mock和Stub我们不仅可以让测试环境没有外部依赖,而且还可以模拟一些异常行为,如数据库服务不可用,没有文件的访问权限等等。

Mock和Stub的区别

在Go语言中,可以这样描述Mock和Stub:

  • Mock:在测试包中创建一个结构体,满足某个外部依赖的接口 interface{}
  • Stub:在测试包中创建一个模拟方法,用于替换生成代码中的方法

还是有点抽象,下面举例说明。

Mock示例

Mock:在测试包中创建一个结构体,满足某个外部依赖的接口 interface{}

生产代码:

//auth.go
//假设我们有一个依赖http请求的鉴权接口
type AuthService interface{
    Login(username string,password string) (token string,e error)
    Logout(token string) error
}

mock代码:

//auth_test.go
type authService struct {}
func (auth *authService) Login (username string,password string) (string,error){
    return "token", nil
}
func (auth *authService) Logout(token string) error{
    return nil
}

在这里我们用 authService实现了 AuthService接口,这样测试 Login,Logout就不再需需要依赖网络请求了。而且我们也可以模拟一些错误的情况进行测试:

//auth_test.go
//模拟登录失败
type authLoginErr struct {
    auth AuthService  //可以使用组合的特性,Logout方法我们不关心,只用“覆盖”Login方法即可
}
func (auth *authLoginErr) Login (username string,password string) (string,error) {
    return "", errors.New("用户名密码错误")
}

//模拟api服务器宕机
type authUnavailableErr struct {
}
func (auth *authUnavailableErr) Login (username string,password string) (string,error) {
    return "", errors.New("api服务不可用")
}
func (auth *authUnavailableErr) Logout(token string) error{
    return errors.New("api服务不可用")
}

Stub示例

Stub:在测试包中创建一个模拟方法,用于替换生成代码中的方法。 这是《Go语言圣经》(11.2.3)当中的一个例子: 生产代码:

//storage.go
//发送邮件
var notifyUser = func(username, msg string) { //<--将发送邮件的方法变成一个全局变量
    auth := smtp.PlainAuth("", sender, password, hostname)
    err := smtp.SendMail(hostname+":587", auth, sender,
        []string{username}, []byte(msg))
    if err != nil {
        log.Printf("smtp.SendEmail(%s) failed: %s", username, err)
    }
}
//检查quota,quota不足将发邮件
func CheckQuota(username string) {
    used := bytesInUse(username)
    const quota = 1000000000 // 1GB
    percent := 100 * used / quota
    if percent < 90 {
        return // OK
    }
    msg := fmt.Sprintf(template, used, percent)
    notifyUser(username, msg) //<---发邮件
}

显然,在跑单元测试的过程中,我们肯定不会真的给用户发邮件。在书中采用了stub的方式来进行测试:

//storage_test.go
func TestCheckQuotaNotifiesUser(t *testing.T) {
    var notifiedUser, notifiedMsg string
    notifyUser = func(user, msg string) {  //<-看这里就够了,在测试中,覆盖了发送邮件的全局变量
        notifiedUser, notifiedMsg = user, msg
    }

    // ...simulate a 980MB-used condition...

    const user = "joe@example.org"
    CheckQuota(user)
    if notifiedUser == "" && notifiedMsg == "" {
        t.Fatalf("notifyUser not called")
    }
    if notifiedUser != user {
        t.Errorf("wrong user (%s) notified, want %s",
            notifiedUser, user)
    }
    const wantSubstring = "98% of your quota"
    if !strings.Contains(notifiedMsg, wantSubstring) {
        t.Errorf("unexpected notification message <<%s>>, "+
            "want substring %q", notifiedMsg, wantSubstring)
    }
}

可以看到,在Go中,如果要用stub,那将是侵入式的,必须将生产代码设计成可以用stub方法替换的形式。上述例子体现出来的结果就是:为了测试,专门用一个全局变量 notifyUser来保存了具有外部依赖的方法。然而在不提倡使用全局变量的Go语言当中,这显然是不合适的。所以,并不提倡这种Stub方式。

Mock与Stub相结合

既然不提倡Stub方式,那是不是在Go测试当中就可以抛弃Stub了呢?原本我是这么认为的,但直到我读了这篇译文Golang 标准包布局,虽然这篇译文讲的是包的布局,但里面的测试示例很值得学习。

//生产代码 myapp.go
package myapp

type User struct {
    ID      int
    Name    string
    Address Address
}
//User的一些增删改查
type UserService interface {
    User(id int) (*User, error)
    Users() ([]*User, error)
    CreateUser(u *User) error
    DeleteUser(id int) error
}

常规Mock方式:

//测试代码 myapp_test.go
type userService struct{
}
func (u* userService) User(id int) (*User,error) {
    return &User{Id:1,Name:"name",Address:"address"},nil
}
//..省略其他实现方法

//模拟user不存在
type userNotFound struct {
    u UserService
}
func (u* userNotFound) User(id int) (*User,error) {
    return nil,errors.New("not found")
}

//其他...

一般来说,mock结构体内部很少会放变量,针对每一个要模拟的场景(比如上面的user不存在),最政治正确的方法应该是新建一个mock结构体。这样有两个好处:

  1. mock出来的结构体十分简单,不需要进行额外的设置,不容易出错。
  2. mock出来的结构体职责单一,测试代码自说明能力更强,可读性更高。

但在刚才提到的文章中,他是这么做的:

//测试代码
// UserService 代表一个myapp.UserService.的 mock实现
type UserService struct {
    UserFn      func(id int) (*myapp.User, error)
    UserInvoked bool

    UsersFn     func() ([]*myapp.User, error)
    UsersInvoked bool
    // 其他接口方法补全..
}

// User调用mock实现, 并标记这个方法为已调用
func (s *UserService) User(id int) (*myapp.User, error) {
    s.UserInvoked = true
    return s.UserFn(id)
}

这里不仅实现了接口,还通过在结构体内放置与接口方法函数签名一致的方法( UserFnUsersFn...),以及 XxxInvoked是否调用标识符来追踪方法的调用情况。这种做法其实将mock与stub相结合了起来:在mock对象的内部放置了可以被测试函数替换的函数变量UserFn UsersFn…)。我们可以在我们的测试函数中,根据测试的需要,手动更换函数实现。

//mock与stub结合的方式
func TestUserNotFound(t *testing.T) {
    userNotFound := &UserService{}
    userNotFound.UserFn = func(id int) (*myapp.User, error) { //<--- 设置UserFn的期望返回结果
        return nil,errors.New("not found")
    }
    //后续业务测试代码...

    if !userNotFound.UserInvoked {
        t.Fatal("没有调用User()方法")
    }
}

// 常规mock方式
func TestUserNotFound(t *testing.T) {
    userNotFound := &userNotFound{} //<---结构体方法已经决定了返回值
    //后续业务测试代码
}

通过将mock与stub结合,不仅能在测试方法中动态的更改实现,还追踪方法的调用情况,上述例子中只是追踪了方法是否被调用,实际中,如果有需要,我们也可以追踪方法的调用次数,甚至是方法的调用顺序:

type UserService struct {
    UserFn      func(id int) (*myapp.User, error)
    UserInvoked bool
    UserInvokedTime int //<--追踪调用次数

    UsersFn     func() ([]*myapp.User, error)
    UsersInvoked bool

    // 其他接口方法补全..
    FnCallStack []string //<---函数名slice,追踪调用顺序
}

// User调用mock实现, 并标记这个方法为已调用
func (s *UserService) User(id int) (*myapp.User, error) {
    s.UserInvoked = true
    s.UserInvokedTime++ //<--调用发次数
    s.FnCallStack = append(s.FnCallStack,"User") //调用顺序
    return s.UserFn(id)
}

但同时,我们也会发现我们的mock结构体更复杂了,维护成本也随之增加了。两种mock风格各有各的好处,反正要记得软件工程没有银弹,合适的场景选用合适的方法就行了。 但总体而言,mock与stub相结合的这种方式的确是一种不错的测试思路,尤其是当我们需要追踪函数是否调用,调用次数,调用顺序等信息时,mock+stub将是我们的不二选择。举个例子:

//缓存依赖
type Cache interface{
    Get(id int) interface{} //获取某id的缓存
    Put(id int,obj interface{}) //放入缓存
}

//数据库依赖
type UserRepository interface{
    //....
}
//User结构体
type User struct {
    //...
}
//userservice
type UserService interface{
    cache Cache
    repository UserRepository
}

func (u *UserService) Get(id int) *User {
    //先从缓存找,缓存找不到在去repository里面找
}

func main() {
    userService := NewUserService(xxx) //注入一些外部依赖
    user := userService.Get(2) //获取id = 2的user
}

现在要测试 userService.Get(id)方法的行为:

  1. Cache命中之后是否还查数据库?(不应该再查了)
  2. Cache未命中的情况下是否会查库?
  3. ….

这种测试通过mock+stub结合做起来将会非常方便,作为小练习,可以尝试自己实现一下。

使用依赖注入传递接口

接口需要以依赖注入的方式注入到结构体中,这样才能为测试提供替换接口实现的可能。Why?我们先看一个反面例子,形如下面的写法是无法测试的:

type A interface {
    Fun1()
}
func (f *Foo) Bar() {
    a := NewInstanceOfA(...参数若干) //生成A接口的某个实现
    a.Fun1()  //调用接口方法
}

当你辛辛苦苦的将A接口mock出来后,却发现你根本没有办法在Bar()方法中将mock对象替换进去。下面来看看正确的写法:

type A interface {
    Fun1()
}
type Foo struct {
    a A // A接口
}
func (f *Foo) Bar() {
    f.a.Fun1() //调用接口方法
}
// NewFoo, 通过构造函数的方式,将A接口注入
func NewFoo(a A) *Foo {
    return &Foo{a: A}
}

在例子中我们使用了构造函数传参的方法来做依赖注入(当然你也可以用setter的方式做)。在测试的时候,就可以通过NewFoo()方法将我们的mock对象传递给*Foo了。

通常我们会在main.go中进行依赖注入

总结一下

长篇大论了一大堆,稍微总结一下单元测试的几个关键步骤:

  1. 识别依赖(网络,文件,未完成的功能等等)
  2. 将依赖抽象成接口
  3. main.go中使用依赖注入方式将接口注入

现在,我们已经对单元测试有了一个基本的认识,如果你能完成文中的小练习,那么恭喜你,你已经理解应当如何做单元测试,并成功迈出第一步了。在下一篇文章中,将介绍gomock测试框架,提高我们的测试效率。

2.1.4 - Golang单元测试02

搞定Go单元测试(二)—— mock框架(gomock)

通过阅读上一篇文章,相信你对怎么做单元测试已经有了初步的概念,可以着手对现有的项目进行改造并开展测试了。学会了走路,我们尝试跑起来,本篇主要介绍gomock测试框架,让我们的单元测试更加有效率。

表格驱动测试方法(Table Driven Tests)

当针对某方法进行单元测试的时候,通常不止写一个测试用例,我们需要测试该方法在多种入参条件下是否都能正常工作,特别是要针对边界值进行测试。通常这个时候表格驱动测试就派上用场了——当你发现你在写测试方法的时候用上了复制粘贴,这就说明你需要考虑使用表格驱动测试来构建你的测试方法了。我们依旧来举个例子:

func TestTime(t *testing.T) {
    testCases := []struct {  // 设计我们的测试用例
        gmt  string
        loc  string
        want string
    }{
        {"12:31", "Europe/Zuri", "13:31"},     // incorrect location name
        {"12:31", "America/New_York", "7:31"}, // should be 07:31
        {"08:08", "Australia/Sydney", "18:08"},
    }
    for _, tc := range testCases {  // 循环执行测试用例
        loc, err := time.LoadLocation(tc.loc)
        if err != nil {
            t.Fatalf("could not load location %q", tc.loc)
        }
        gmt, _ := time.Parse("15:04", tc.gmt)
        if got := gmt.In(loc).Format("15:04"); got != tc.want {
            t.Errorf("In(%s, %s) = %s; want %s", tc.gmt, tc.loc, got, tc.want)
        }
    }
}

表格驱动测试方法让我们的测试方法更加清晰和简练,减少了复制粘贴,并大大提高的测试代码的可读性。

还记得上文说单元测试也是需要维护的吗?单元测试也是代码的一部分,也应当被认真对待。记得要用表格驱动测试的方法来组织你的测试用例,同时别忘了像正式代码那样,写上相应的注释。 更多参考: github.com/golang/go/w… blog.golang.org/subtests

使用测试框架——gomock

What is gomock?

gomock是Google开源的golang测试框架。或者引用官方的话来说:“GoMock is a mocking framework for the Go programming language”。

github.com/golang/mock

Why gomock?

上篇文章末尾介绍了mock和stub相结合的测试方法,可以感受到mock与stub结合起来功能固然强大——调用顺序检测,调用次数检测,动态控制函数的返回值等等,但同时,其带来的维护成本和复杂度缺是不可忽视的,手动维护这样一套测试代码那将是一场灾难。我们期望能用一套框架或者工具,在提供强大的测试功能的同时帮我们维护复杂的mock代码。

How does it work?

gomock通过mockgen命令生成包含mock对象的.go文件,其生成的mock对象具备mock+stub的强大功能,并将我们从写mock对象中解放了出来:

mockgen -destination foo_mock.go -source foo.go -package foo //mock foo.go里面所有的接口,将mock结果保存到foo_mock.go

gomock让我们既能使用mock与stub结合的强大功能,又不需要手动维护这些mock对象,岂不美哉?

举个栗子

在这里我们对gomock的基本功能做一个简单演示: 假设我们的接口定义在 user.go

// user.go
package user

// User 表示一个用户
type User struct {
   Name string
}
// UserRepository 用户仓库
type UserRepository interface {
   // 根据用户id查询得到一个用户或是错误信息
   FindOne(id int) (*User,error)
}

通过mockgen在同目录下生成mock文件user_mock.go

mockgen -source user.go -destination user_mock.go -package user

然后在该目录下新建user_test.go来写我们的测试函数,上述步骤完成之后,我们的目录结构如下:

└── user
    ├── user.go
    ├── user_mock.go
    └── user_test.go

设置函数的返回值

// 静态设置返回值
func TestReturn(t *testing.T) {
    ctrl := gomock.NewController(t)
    defer ctrl.Finish()

    repo := NewMockUserRepository(ctrl)
    // 期望FindOne(1)返回张三用户
    repo.EXPECT().FindOne(1).Return(&User{Name: "张三"}, nil)
    // 期望FindOne(2)返回李四用户
    repo.EXPECT().FindOne(2).Return(&User{Name: "李四"}, nil)
    // 期望给FindOne(3)返回找不到用户的错误
    repo.EXPECT().FindOne(3).Return(nil, errors.New("user not found"))
    // 验证一下结果
    log.Println(repo.FindOne(1)) // 这是张三
    log.Println(repo.FindOne(2)) // 这是李四
    log.Println(repo.FindOne(3)) // user not found
    log.Println(repo.FindOne(4)) //没有设置4的返回值,却执行了调用,测试不通过
}
// 动态设置返回值
func TestReturnDynamic(t *testing.T) {
    ctrl := gomock.NewController(t)
    defer ctrl.Finish()
    repo := NewMockUserRepository(ctrl)
    // 常用方法之一:DoAndReturn(),动态设置返回值
    repo.EXPECT().FindOne(gomock.Any()).DoAndReturn(func(i int) (*User,error) {
        if i == 0 {
            return nil, errors.New("user not found")
        }
        if i < 100 {
            return &User{
                Name:"小于100",
            }, nil
        } else {
            return &User{
                Name:"大于等于100",
            }, nil
        }
    })
    log.Println(repo.FindOne(120))
    //log.Println(repo.FindOne(66))
    //log.Println(repo.FindOne(0))
}

调用次数检测

func TestTimes(t *testing.T) {
    ctrl := gomock.NewController(t)
    defer ctrl.Finish()

    repo := NewMockUserRepository(ctrl)
    // 默认期望调用一次
    repo.EXPECT().FindOne(1).Return(&User{Name: "张三"}, nil)
    // 期望调用2次
    repo.EXPECT().FindOne(2).Return(&User{Name: "李四"}, nil).Times(2)
    // 调用多少次可以,包括0次
    repo.EXPECT().FindOne(3).Return(nil, errors.New("user not found")).AnyTimes()

    // 验证一下结果
    log.Println(repo.FindOne(1)) // 这是张三
    log.Println(repo.FindOne(2)) // 这是李四
    log.Println(repo.FindOne(2)) // FindOne(2) 需调用两次,注释本行代码将导致测试不通过
    log.Println(repo.FindOne(3)) // user not found, 不限调用次数,注释掉本行也能通过测试
}

调用顺序检测

func TestOrder(t *testing.T) {
    ctrl := gomock.NewController(t)
    defer ctrl.Finish()
    repo := NewMockUserRepository(ctrl)
    o1 := repo.EXPECT().FindOne(1).Return(&User{Name: "张三"}, nil)
    o2 := repo.EXPECT().FindOne(2).Return(&User{Name: "李四"}, nil)
    o3 := repo.EXPECT().FindOne(3).Return(nil, errors.New("user not found"))
    gomock.InOrder(o1, o2, o3) //设置调用顺序
    // 按顺序调用,验证一下结果
    log.Println(repo.FindOne(1)) // 这是张三
    log.Println(repo.FindOne(2)) // 这是李四
    log.Println(repo.FindOne(3)) // user not found

    // 如果我们调整了调用顺序,将导致测试不通过:
    // log.Println(repo.FindOne(2)) // 这是李四
    // log.Println(repo.FindOne(1)) // 这是张三
    // log.Println(repo.FindOne(3)) // user not found
}

上面的示例只展现了gomock功能的冰山一角,在本篇中不再深入讨论,更多用法请参考文档。

更多官方示例:github.com/golang/mock…

如果你完成了上一章的小练习,尝试动手使用gomock改造一下吧!

总结一下

本篇介绍了表格驱动测试与gomock测试框架。运用表格驱动测试方法不仅能使测试代码更精简易读,还能提高我们测试用例的编写能力,无形中提升了单元测试的质量。gomock的功能十分丰富,想掌握各种骚操作还是要细心阅读一下官方示例,但通常20%的常规功能也足够覆盖80%的测试场景了。 表格驱动单元测试和gomock将我们的单元测试效率与质量提升了一个档次。在下一篇文章中,将介绍 testify断言库,继续优化我们的单元测试。

2.1.5 - Golang单元测试03

搞定Go单元测试(三)—— 断言(testify)

在上一篇,介绍了表格驱动测试方法和gomock测试框架,大大提升了测试效率与质量。本篇将介绍在测试中引入断言(assertion),进一步提升测试效率与质量。

为什么需要断言库

我们先来看看Go标准包中为什么没有断言,官方在FAQ里面回答了这个问题。

golang.org/doc/faq#ass…

总体概括一下大意就是:“Go不提供断言,我们知道这会带来一定的不便,其主要目的是为了防止你们这些程序员在错误处理上偷懒。我们知道这是一个争论点,但是我们觉得这样很coooool~~。”所以,我们引入断言库的原因也很明显了:偷懒,引入断言能为我们提供便利——提高测试效率,增强代码可读性。

testify

在断言库的选择上,我们似乎没有过多的选择,从start数和活跃度来看,基本上是testify一枝独秀。

github.com/stretchr/te…

没有对比就没有伤害,先来看看使用testify之前的测试方法:

func TestSomeFun(t *testing.T){
...
    if v != want {
        t.Fatalf("v值错误,期望值:%s,实际值:%s", want, v)
    }
    if err != nil {
        t.Fatalf("非预期的错误:%s", err)
    }
    if objectA != objectB {
        if objectA.field1 !=  objectB.field1 {
            // t.Fatalf() field1值错误...bla bla bla
        }
         if objectA.field2 !=  objectB.field2 {
            // t.Fatalf() field2值错误...bla bla bla
        }
        // 遍历object所有值... bla bla bla
    }
...
}

上述代码充斥着大量if...else..判断,大段错误信息拼装(真·体力活…),运气不好碰到结构体判断要得将其遍历一遍——不直观,低效,实在是不fashion。 现在,我们使用 testify来改造一下上面的测试示例:

func TestSomeFun(t *testing.T){
    a := assert.New(t)
...
    a.Equal(v, want)
    a.Nil(err,"如果你还是想输出自己拼装的错误信息,可以传第三个参数")
    a.Equal(objectA, objectB)
...
}

三行搞定,测试含义一目了然——直观,高效,简短,fashion。

总结一下

testify使用简单,提升显著,可谓是用一次就会爱上的懒人神器。在结合表格驱动测试,gomock和testify后,我们已经能写出一手优雅漂亮的单元测试代码了。不过,光测试代码优雅还不够,我们还需要帮main.go也打扮打扮。在下一篇,也是本系列最后一篇文章中,我们将介绍wire依赖注入框架,帮main.go减肥瘦身。

2.1.6 - Golang单元测试04

搞定Go单元测试(四)—— 依赖注入框架(wire)

在第一篇文章中提到过,为了让代码可测,需要用依赖注入的方式来构建我们的对象,而通常我们会在main.go做依赖注入,这就导致main.go会越来越臃肿。为了让单元测试得以顺利进行,main.go牺牲了它本应该纤细苗条的身材。太胖的main.go可不是什么好的信号,本篇将介绍依赖注入框架(wire),致力于帮助main.go恢复身材。

臃肿的main

main.go中做依赖注入,意味着在初始化代码中我们要管理:

  1. 依赖的初始化顺序
  2. 依赖之间的关系

对于小型项目而言,依赖的数量比较少,初始化代码不会很多,不需要引入依赖注入框架。但对于依赖较多的中大型项目,初始化代码又臭又长,可读性和维护性变的很差,随意感受一下:

func main() {
    config := NewConfig()
    // db依赖配置
    db, err := ConnectDatabase(config)
    if err != nil {
        panic(err)
    }
    // PersonRepository 依赖db
    personRepository := NewPersonRepository(db)
    // PersonService 依赖配置 和 PersonRepository
    personService := NewPersonService(config, personRepository)
    // NewServer 依赖配置和PersonService
    server := NewServer(config, personService)
    server.Run()
}

实践表明,修改有大量依赖关系的初始化代码是一项乏味且耗时的工作。这个时候,我们就需要依赖注入框架来帮忙,简化初始化代码。

上述代码来自:blog.drewolson.org/dependency-…

使用依赖注入框架——wire

What is wire?

wire是google开源的依赖注入框架。或者引用官方的话来说:“Wire is a code generation tool that automates connecting components using dependency injection”。

github.com/google/wire

Why wire?

除了wire,Go的依赖注入框架还有Uber的dig和Facebook的inject,它们都是使用反射机制来实现运行时依赖注入(runtime dependency injection),而wire则是采用代码生成的方式来达到编译时依赖注入(compile-time dependency injection)。使用反射带来的性能损失倒是其次,更重要的是反射使得代码难以追踪和调试(反射会令Ctrl+左键失效…)。而wire生成的代码是符合程序员常规使用习惯的代码,十分容易理解和调试。 关于wire的优点,在官方博文上有更详细的的介绍: blog.golang.org/wire

How does it work?

本部分内容参考官方博文:blog.golang.org/wire

wire有两个基本的概念:provider和injector。

provider

provider就是普通的Go函数,可以把它看作是某对象的构造函数,我们通过provider告诉wire该对象的依赖情况:

// NewUserStore是*UserStore的provider,表明*UserStore依赖于*Config和 *mysql.DB.
func NewUserStore(cfg *Config, db *mysql.DB) (*UserStore, error) {...}

// NewDefaultConfig是*Config的provider,没有依赖
func NewDefaultConfig() *Config {...}

// NewDB是*mysql.DB的provider,依赖于ConnectionInfo
func NewDB(info ConnectionInfo) (*mysql.DB, error) {...}

// UserStoreSet 可选项,可以使用wire.NewSet将通常会一起使用的依赖组合起来。
var UserStoreSet = wire.NewSet(NewUserStore, NewDefaultConfig)

injector

injector是wire生成的函数,我们通过调用injector来获取我们所需的对象或值,injector会按照依赖关系,按顺序调用provider函数:

// File: wire_gen.go
// Code generated by Wire. DO NOT EDIT.
//go:generate wire
//+build !wireinject

// initUserStore是由wire生成的injector
func initUserStore(info ConnectionInfo) (*UserStore, error) {
    // *Config的provider函数
    defaultConfig := NewDefaultConfig()
    // *mysql.DB的provider函数
    db, err := NewDB(info)
    if err != nil {
        return nil, err
    }
    // *UserStore的provider函数
    userStore, err := NewUserStore(defaultConfig, db)
    if err != nil {
        return nil, err
    }
    return userStore, nil
}

injector帮我们把按顺序初始化依赖的步骤给做了,我们在main.go中只需要调用initUserStore方法就能得到我们想要的对象了。

那么wire是怎么知道如何生成injector的呢?我们需要写一个函数来告诉它:

  • 定义injector的函数签名
  • 在函数中使用wire.Build方法列举生成injector所需的provider

例如:

// initUserStore用于声明injector的函数签名
func initUserStore(info ConnectionInfo) (*UserStore, error) {
    // wire.Build声明要获取一个UserStore需要调用到哪些provider函数
    wire.Build(UserStoreSet, NewDB)
    return nil, nil  // 这些返回值wire并不关心。
}

有了上面的函数,wire就可以得知如何生成injector了。wire生成injector的步骤描述如下:

  1. 确定所生成injector函数的函数签名:func initUserStore(info ConnectionInfo) (*UserStore, error)
  2. 感知返回值第一个参数是*UserStore
  3. 检查wire.Build列表,找到*UserStore的provider:NewUserStore
  4. 由函数签名func NewUserStore(cfg *Config, db *mysql.DB)得知NewUserStore依赖于*Config, 和*mysql.DB
  5. 检查wire.Build列表,找到*Config*mysql.DB的provider:NewDefaultConfigNewDB
  6. 由函数签名func NewDefaultConfig() *Config得知*Config没有其他依赖了。
  7. 由函数签名func NewDB(info *ConnectionInfo) (*mysql.DB, error)得知*mysql.DB依赖于ConnectionInfo
  8. 检查wire.Build列表,找不到ConnectionInfo的provider,但在injector函数签名中发现匹配的入参类型,直接使用该参数作为NewDB的入参。
  9. 感知返回值第二个参数是error
  10. ….
  11. 按依赖关系,按顺序调用provider函数,拼装injector函数。

举个栗子

栗子传送门:wire-examples

注意

截止本文发布前,官方表明wire的项目状态是alpha,还不适合到生产环境,API存在变化的可能。 虽然是alpha,但其主要作用是为我们生成依赖注入代码,其生成的代码十分通俗易懂,在做好版本控制的前提下,即使是API发生变化,也不会对生成环境造成多坏的影响。我认为还是可以放心使用的。

总结一下

本篇是本系列的最后一篇,回顾前几篇文章,我们以单元测试的原理与基本思想为基础,介绍了表格驱动测试方法,gomock,testify,wire这几样实用工具,经历了“能写单元测试”到“写好单元测试”不断优化的过程。希望本系列文章能让你有所收获。

2.2 - adodb方式连接sqlServer2k

sqlServer2k 基本看不到了,但是某些系统竟然还用。之前项目遇到过就记录下来怎么连,这是全网唯一能真连成功的代码了

go.mod

require (
    github.com/go-ole/go-ole v1.2.4 // indirect
    github.com/mattn/go-adodb v0.0.1
    github.com/robfig/cron/v3 v3.0.1
    github.com/wonderivan/logger v1.0.0
    golang.org/x/net v0.0.0-20200904194848-62affa334b73 // indirect
)

main.go

package main

import (
    "database/sql"
    "flag"
    "fmt"
    "log"
    "strconv"

    _ "github.com/mattn/go-adodb"
    "github.com/robfig/cron/v3"
    "github.com/wonderivan/logger"
)

var (
    local    bool
    remoteIP string
    remoteDS string
    database string
)

func init() {
    flag.BoolVar(&local, "local", false, "set window connect.")
    flag.StringVar(&remoteIP, "remoteIP", "192.168.0.10", "set up remote mssql of ip.")
    flag.StringVar(&remoteDS, "remoteDS", "MSSQLSERVER", "set up remote mssql of datasource.")
    flag.StringVar(&database, "database", "drivertest", "set up remote mssql of database.")
}

type Mssql struct {
    *sql.DB
    dataSource string
    database   string
    windows    bool
    sa         *SA
}

type SA struct {
    user   string
    passwd string
    port   int
}

func NewMssql() *Mssql {
    mssql := new(Mssql)
    dataS := "localhost"
    if !local {
        dataS = fmt.Sprintf("%s\\%s", remoteIP, remoteDS)
    }

    mssql = &Mssql{
        // 如果数据库是默认实例(MSSQLSERVER)则直接使用IP,命名实例需要指明。
        // dataSource: "192.168.1.104\\MSSQLSERVER",
        dataSource: dataS,
        database:   database,
        // windows: true 为windows身份验证,false 必须设置sa账号和密码
        windows: local,
        sa: &SA{
            user:   "elink",
            passwd: "elink888",
            port:   1433,
        },
    }

    return mssql

}

func (m *Mssql) Open() error {
    config := fmt.Sprintf("Provider=SQLOLEDB;Initial Catalog=%s;Data Source=%s",
        m.database, m.dataSource)

    if m.windows {
        config = fmt.Sprintf("%s;Integrated Security=SSPI", config)
    } else {
        // sql 2000的端口写法和sql 2005以上的有所不同,在Data Source 后以逗号隔开。
        config = fmt.Sprintf("%s,%d;user id=%s;password=%s",
            config, m.sa.port, m.sa.user, m.sa.passwd)
    }

    var err error
    m.DB, err = sql.Open("adodb", config)
    fmt.Println(config)

    return err
}

func (m *Mssql) Select() {
    rows, err := m.Query("select uid, name from sysusers")
    if err != nil {
        fmt.Printf("select query err: %s\n", err)
    }
    i := 0
    for rows.Next() {
        var id, name string
        rows.Scan(&id, &name)
        fmt.Printf("id = %s, name = %s\n", id, name)
        i++
    }
    fmt.Println(i)
}

func main() {
    // flag.Parse()

    // m := NewMssql()
    // config1 := fmt.Sprintf("Provider=SQLOLEDB;Initial Catalog=%s;Data Source=%s",
    //     m.database, m.dataSource)

    // if m.windows {
    //     config1 = fmt.Sprintf("%s;Integrated Security=SSPI", config1)
    // } else {
    //     // sql 2000的端口写法和sql 2005以上的有所不同,在Data Source 后以逗号隔开。
    //     config1 = fmt.Sprintf("%s,%d;user id=%s;password=%s",
    //         config1, m.sa.port, m.sa.user, m.sa.passwd)
    // }
    // fmt.Println(config1)
    // err := mssql.Open()

    // checkError(err)

    // mssql.Select()

    // config := fmt.Sprintf("Provider=SQLOLEDB;Initial Catalog=%s;Data Source=%s\\MSSQLSERVER,%s;user id=%s;password=%s",
    //     "drivertest", "192.168.0.10", "1433", "elink", "elink888")
    config := fmt.Sprintf("Provider=SQLOLEDB;Initial Catalog=%s;Data Source=%s\\MSSQLSERVER,%s;user id=%s;password=%s",
        "znykt", "37.64.227.132", "1433", "elink", "elink888")
    DB, err := sql.Open("adodb", config)
    if err != nil {
        log.Fatal(err)
    }

    crontab := cron.New(cron.WithSeconds()) //精确到秒
    // 定时任务
    deviceStatusOnline_spec := "*/5 * * * * ?" //秒 分 时 日 月 周
    // 定义定时器调用的任务函数
    _, err = crontab.AddFunc(deviceStatusOnline_spec, func() {
        rows, err := DB.Query("select top 200 id from MYCARGOOUTRECORD order by id desc")
        if err != nil {
            fmt.Printf("select query err: %s\n", err)
        }
        i := 0
        for rows.Next() {
            // var id, name string
            // rows.Scan(&id, &name)
            // fmt.Printf("id = %s, name = %s\n", id, name)
            i++
        }
        logger.Info("结果数量:" + strconv.Itoa(i))
    })
    if err != nil {
        log.Fatal(err)
    }
    // 启动定时器
    crontab.Start()
    select {}
}

// func checkError(err error) {
//     if err != nil {
//         log.Fatal(err)
//     }
// }

2.3 - AES-ECB-PKCS5

AES-ECB-PKCS5,附上对应java能相互加解密的 java代码 aes的ecb模式是对称假面
package main

import (
    "bytes"
    "crypto/aes"
    "crypto/cipher"
    "encoding/base64"
    "encoding/hex"
    "fmt"
    "strings"
)

func Base64URLDecode(data string) ([]byte, error) {
    var missing = (4 - len(data)%4) % 4
    data += strings.Repeat("=", missing)
    res, err := base64.URLEncoding.DecodeString(data)
    fmt.Println("  decodebase64urlsafe is :", string(res), err)
    return base64.URLEncoding.DecodeString(data)
}

func Base64UrlSafeEncode(source []byte) string {
    // Base64 Url Safe is the same as Base64 but does not contain '/' and '+' (replaced by '_' and '-') and trailing '=' are removed.
    bytearr := base64.StdEncoding.EncodeToString(source)
    safeurl := strings.Replace(string(bytearr), "/", "_", -1)
    safeurl = strings.Replace(safeurl, "+", "-", -1)
    safeurl = strings.Replace(safeurl, "=", "", -1)
    return safeurl
}

func AesDecrypt(crypted, key []byte) []byte {
    block, err := aes.NewCipher(key)
    if err != nil {
        fmt.Println("err is:", err)
    }
    blockMode := NewECBDecrypter(block)
    origData := make([]byte, len(crypted))
    blockMode.CryptBlocks(origData, crypted)
    origData = PKCS5UnPadding(origData)
    // fmt.Println("source is :", origData, string(origData))
    return origData
}

func AesEncrypt(src, key string) []byte {
    block, err := aes.NewCipher([]byte(key))
    if err != nil {
        fmt.Println("key error1", err)
    }
    if src == "" {
        fmt.Println("plain content empty")
    }
    ecb := NewECBEncrypter(block)
    content := []byte(src)
    content = PKCS5Padding(content, block.BlockSize())
    crypted := make([]byte, len(content))
    ecb.CryptBlocks(crypted, content)
    // 普通base64编码加密 区别于urlsafe base64
    // fmt.Println("base64 result:", base64.StdEncoding.EncodeToString(crypted))

    // fmt.Println("base64UrlSafe result:", Base64UrlSafeEncode(crypted))
    return crypted
}

func PKCS5Padding(ciphertext []byte, blockSize int) []byte {
    padding := blockSize - len(ciphertext)%blockSize
    padtext := bytes.Repeat([]byte{byte(padding)}, padding)
    return append(ciphertext, padtext...)
}

func PKCS5UnPadding(origData []byte) []byte {
    length := len(origData)
    // 去掉最后一个字节 unpadding 次
    unpadding := int(origData[length-1])
    return origData[:(length - unpadding)]
}

type ecb struct {
    b         cipher.Block
    blockSize int
}

func newECB(b cipher.Block) *ecb {
    return &ecb{
        b:         b,
        blockSize: b.BlockSize(),
    }
}

type ecbEncrypter ecb

// NewECBEncrypter returns a BlockMode which encrypts in electronic code book
// mode, using the given Block.
func NewECBEncrypter(b cipher.Block) cipher.BlockMode {
    return (*ecbEncrypter)(newECB(b))
}
func (x *ecbEncrypter) BlockSize() int { return x.blockSize }
func (x *ecbEncrypter) CryptBlocks(dst, src []byte) {
    if len(src)%x.blockSize != 0 {
        panic("crypto/cipher: input not full blocks")
    }
    if len(dst) < len(src) {
        panic("crypto/cipher: output smaller than input")
    }
    for len(src) > 0 {
        x.b.Encrypt(dst, src[:x.blockSize])
        src = src[x.blockSize:]
        dst = dst[x.blockSize:]
    }
}

type ecbDecrypter ecb

// NewECBDecrypter returns a BlockMode which decrypts in electronic code book
// mode, using the given Block.
func NewECBDecrypter(b cipher.Block) cipher.BlockMode {
    return (*ecbDecrypter)(newECB(b))
}
func (x *ecbDecrypter) BlockSize() int { return x.blockSize }
func (x *ecbDecrypter) CryptBlocks(dst, src []byte) {
    if len(src)%x.blockSize != 0 {
        panic("crypto/cipher: input not full blocks")
    }
    if len(dst) < len(src) {
        panic("crypto/cipher: output smaller than input")
    }
    for len(src) > 0 {
        x.b.Decrypt(dst, src[:x.blockSize])
        src = src[x.blockSize:]
        dst = dst[x.blockSize:]
    }
}

type Person struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

func main() {

    key, _ := base64.StdEncoding.DecodeString("cmVmb3JtZXJyZWZvcm1lcg==")
    strkey := string(key)
    fmt.Println(strkey)
    fmt.Println([]byte(strkey))
    fmt.Println()
    fmt.Println()
    fmt.Println()

    // wait_encode_str := "{\"stationNo\":\"241\"}"
    // encode_str := AesEncrypt(wait_encode_str, "reformerreformer")
    // hex.EncodeToString(encode_str)
    // fmt.Println(hex.EncodeToString(encode_str))

    wait_decode_str := "34D4F1A0506840B2A6D93C20C098312FAEE02E16FC26144A0621512E9F20CBB0AD121A74EFAD7A8935B132981D6C4198E06B8949E593E2A7D6AB4AB640CC498EE58CD0A66B5B0BBAAE1DBF4C9E4A02DE5658319E93B46831595515FA9F8EFD4202631F09DADDCD575B40C7F76739B31393A455B10AE73B2093EBD6B58DB88D24ACFB0F1DD123A9883051C8986BE060A168024690058318B8271F12E9D4795ACED75BDCC4F1F9AF75743BB81066F64D934B02CD41BE0528F2CEFFC3C40AC1645CA53943024C236A75F247DF3131704BECA1AFE7ADB81343E6D5914940F4E608BECF0D290EDD3B0F0B1F9CE0CD1557479B9E888007A48B19557A989E325193BD0F3EF40C9530B6B6F0D60F231A2EA2A58E9EA0D3B212E6C7BD177D0165C3FB052CBB672E9591D57F81115EBAEF9DC8116813F8D67289A440403D3CC9C7B0F716736E04780C06FCE2BC2D01938AE55A14FD6E0965A241608A371136FC9229BB61093367C8E674AAA35C66F4004C6478C11DE085A81A8D289DDF47EFA422BB9311355FB35218112F26981084812B3FBA2CC55D1267804FE913495C1518342E94B1C45A122B0516C01488D7FB6FEB3A06F37F"

    hex_data, _ := hex.DecodeString(wait_decode_str)
    decode_str := AesDecrypt(hex_data, key)
    fmt.Println(string(decode_str))

    // b1, _ := Base64URLDecode("cmVmb3JtZXJyZWZvcm1lcg==")
    // fmt.Println(string(b1))

    // b2 := Base64UrlSafeEncode([]byte("reformerreformer"))
    // fmt.Println(b2)
}

import java.security.Key;
import java.security.NoSuchAlgorithmException;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

/**
 * AES加密类
 *
 */
public class AESUtils {
    /**
     * 密钥算法
     */
    private static final String KEY_ALGORITHM = "AES";

    private static final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";

    /**
     * 初始化密钥
     *
     * @return byte[] 密钥
     * @throws Exception
     */
    public static byte[] initSecretKey() {
        // 返回生成指定算法的秘密密钥的 KeyGenerator 对象
        KeyGenerator kg = null;
        try {
            kg = KeyGenerator.getInstance(KEY_ALGORITHM);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            return new byte[0];
        }
        // 初始化此密钥生成器,使其具有确定的密钥大小
        // AES 要求密钥长度为 128
        kg.init(128);
        // 生成一个密钥
        SecretKey secretKey = kg.generateKey();
        return secretKey.getEncoded();
    }

    /**
     * 转换密钥
     *
     * @param key
     *            二进制密钥
     * @return 密钥
     */
    public static Key toKey(byte[] key) {
        // 生成密钥
        return new SecretKeySpec(key, KEY_ALGORITHM);
    }

    /**
     * 加密
     *
     * @param data
     *            待加密数据
     * @param key
     *            密钥
     * @return byte[] 加密数据
     * @throws Exception
     */
    public static byte[] encrypt(byte[] data, Key key) throws Exception {
        return encrypt(data, key, DEFAULT_CIPHER_ALGORITHM);
    }

    /**
     * 加密
     *
     * @param data
     *            待加密数据
     * @param key
     *            二进制密钥
     * @return byte[] 加密数据
     * @throws Exception
     */
    public static byte[] encrypt(byte[] data, byte[] key) throws Exception {
        return encrypt(data, key, DEFAULT_CIPHER_ALGORITHM);
    }

    /**
     * 加密
     *
     * @param data
     *            待加密数据
     * @param key
     *            二进制密钥
     * @param cipherAlgorithm
     *            加密算法/工作模式/填充方式
     * @return byte[] 加密数据
     * @throws Exception
     */
    public static byte[] encrypt(byte[] data, byte[] key, String cipherAlgorithm) throws Exception {
        // 还原密钥
        Key k = toKey(key);
        return encrypt(data, k, cipherAlgorithm);
    }

    /**
     * 加密
     *
     * @param data
     *            待加密数据
     * @param key
     *            密钥
     * @param cipherAlgorithm
     *            加密算法/工作模式/填充方式
     * @return byte[] 加密数据
     * @throws Exception
     */
    public static byte[] encrypt(byte[] data, Key key, String cipherAlgorithm) throws Exception {
        // 实例化
        Cipher cipher = Cipher.getInstance(cipherAlgorithm);
        // 使用密钥初始化,设置为加密模式
        cipher.init(Cipher.ENCRYPT_MODE, key);
        // 执行操作
        return cipher.doFinal(data);
    }

    /**
     * 解密
     *
     * @param data
     *            待解密数据
     * @param key
     *            二进制密钥
     * @return byte[] 解密数据
     * @throws Exception
     */
    public static byte[] decrypt(byte[] data, byte[] key) throws Exception {
        return decrypt(data, key, DEFAULT_CIPHER_ALGORITHM);
    }

    /**
     * 解密
     *
     * @param data
     *            待解密数据
     * @param key
     *            密钥
     * @return byte[] 解密数据
     * @throws Exception
     */
    public static byte[] decrypt(byte[] data, Key key) throws Exception {
        return decrypt(data, key, DEFAULT_CIPHER_ALGORITHM);
    }

    /**
     * 解密
     *
     * @param data
     *            待解密数据
     * @param key
     *            二进制密钥
     * @param cipherAlgorithm
     *            加密算法/工作模式/填充方式
     * @return byte[] 解密数据
     * @throws Exception
     */
    public static byte[] decrypt(byte[] data, byte[] key, String cipherAlgorithm) throws Exception {
        // 还原密钥
        Key k = toKey(key);
        return decrypt(data, k, cipherAlgorithm);
    }

    /**
     * 解密
     *
     * @param data
     *            待解密数据
     * @param key
     *            密钥
     * @param cipherAlgorithm
     *            加密算法/工作模式/填充方式
     * @return byte[] 解密数据
     * @throws Exception
     */
    public static byte[] decrypt(byte[] data, Key key, String cipherAlgorithm) throws Exception {
        // 实例化
        Cipher cipher = Cipher.getInstance(cipherAlgorithm);
        // 使用密钥初始化,设置为解密模式
        cipher.init(Cipher.DECRYPT_MODE, key);
        // 执行操作
        return cipher.doFinal(data);
    }

    public static String showByteArray(byte[] data) {
        if (null == data) {
            return null;
        }
        StringBuilder sb = new StringBuilder("{");
        for (byte b : data) {
            sb.append(b).append(",");
        }
        sb.deleteCharAt(sb.length() - 1);
        sb.append("}");
        return sb.toString();
    }

    /**
     * 将16进制转换为二进制
     *
     * @param hexStr
     * @return
     */
    public static byte[] parseHexStr2Byte(String hexStr) {

        if (hexStr.length() < 1)
            return null;
        byte[] result = new byte[hexStr.length() / 2];
        for (int i = 0; i < hexStr.length() / 2; i++) {
            int high = Integer.parseInt(hexStr.substring(i * 2, i * 2 + 1), 16);
            int low = Integer.parseInt(hexStr.substring(i * 2 + 1, i * 2 + 2), 16);
            result[i] = (byte) (high * 16 + low);
        }
        return result;
    }

    /**
     * 将二进制转换成16进制
     *
     * @param buf
     * @return
     */
    public static String parseByte2HexStr(byte buf[]) {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < buf.length; i++) {
            String hex = Integer.toHexString(buf[i] & 0xFF);
            if (hex.length() == 1) {
                hex = '0' + hex;
            }
            sb.append(hex.toUpperCase());
        }
        return sb.toString();
    }

    /**
     * @param str
     * @param key
     * @return
     * @throws Exception
     */
    public static String aesEncrypt(String str, String key) throws Exception {
        if (str == null || key == null)
            return null;
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key.getBytes("utf-8"), "AES"));
        byte[] bytes = cipher.doFinal(str.getBytes("utf-8"));
        return new BASE64Encoder().encode(bytes);
    }

    public static String aesDecrypt(String str, String key) throws Exception {
        if (str == null || key == null)
            return null;
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key.getBytes("utf-8"), "AES"));
        byte[] bytes = new BASE64Decoder().decodeBuffer(str);
        bytes = cipher.doFinal(bytes);
        return new String(bytes, "utf-8");
    }

    public static void main(String[] args) throws Exception {
        byte[] key = initSecretKey();
        System.out.println("key:" + Base64.encodeBase64String(key));
        System.out.println("key:" + showByteArray(key));


        // 指定key
//        String kekkk = "9iEepr1twrizIEKrs1hs2A==";
        String kekkk = "cmVmb3JtZXJyZWZvcm1lcg==";
        byte[] bs = Base64.decodeBase64(kekkk);
        System.out.println(bs);
        System.out.println("kekkk:" + showByteArray(Base64.decodeBase64(kekkk)));
        Key k = toKey(Base64.decodeBase64(kekkk));

        String data = "{\"requestName\":\"BeforeIn\",\"requestValue\":{\"carCode\":\"浙AD0V07\",\"inTime\":\"2016-09-29 10:06:03\",\"inChannelId\":\"4\",\"GUID\":\"1403970b-4eb2-46bc-8f2b-eeec91ddcd5f\",\"inOrOut\":\"0\"},\"Type\":\"0\"}";
//        System.out.println("加密前数据: string:" + data);
//        System.out.println("加密前数据: byte[]:" + showByteArray(data.getBytes()));
//        System.out.println();

        byte[] encryptData = encrypt(data.getBytes(), k);
        String encryptStr=parseByte2HexStr(encryptData);

        System.out.println("加密后数据: byte[]:" + showByteArray(encryptData));
        System.out.println("加密后数据: Byte2HexStr:" + encryptStr);
        System.out.println();

        byte[] encryptStrByte = parseHexStr2Byte("96B742F275ECC2C77374E888CC3AD5D46CDDDAFA3BBA1AA8184D07BED0D957B250DF794666F5ACA787A8D2F20FB7D195C39E78FBDBDDD2F14B00AFC021BA306B03DB52706969E8497F91084CCA48EB81D902E3C32112F1DB07B21B45314ECFA2742F838C368C770F9C21DE07B99844A691269F83C5582A4EE21FB2C22A2168420EBC4AEEE6FB7D3B182AE5158F1F8E62F3BE3AA26C4F220E382B304F91A52A8CD823B68129409BA6059621F3EC0C94BBE8C5A9C1236C9A137536502BB6354D56");
        System.out.println(encryptStrByte.length);
        byte[] decryptData = decrypt(encryptStrByte, k);
        System.out.println("解密后数据: byte[]:" + showByteArray(decryptData));
        System.out.println("解密后数据: string:" + new String(decryptData));

    }
}

java依赖

        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
            <version>1.4</version>
        </dependency>

2.4 - base64

package main

import (
    "encoding/base64"
    "fmt"
    "strings"
)

//Base64UrlSafeEncode 需要替换掉一些字符
func Base64UrlSafeEncode(source []byte) string {
    // Base64 Url Safe is the same as Base64 but does not contain '/' and '+' (replaced by '_' and '-') and trailing '=' are removed.
    bytearr := base64.StdEncoding.EncodeToString(source)
    safeurl := strings.Replace(string(bytearr), "/", "_", -1)
    safeurl = strings.Replace(safeurl, "+", "-", -1)
    safeurl = strings.Replace(safeurl, "=", "", -1)
    return safeurl
}

func main() {

    //标准base64编码
    wait_encode_data := "reformerreformer"
    wait_decode_data := "cmVmb3JtZXJyZWZvcm1lcg"

    //使用标准库自带的方式加密
    sEnc := base64.StdEncoding.EncodeToString([]byte(wait_encode_data))
    fmt.Println(sEnc)

    sDec, _ := base64.StdEncoding.DecodeString("cmVmb3JtZXJyZWZvcm1lcg==")
    fmt.Println(string(sDec))

    //兼容base64编码
    uEnc := base64.URLEncoding.EncodeToString([]byte(wait_encode_data))
    fmt.Println(uEnc)

    uDec, _ := base64.URLEncoding.DecodeString(wait_decode_data)
    fmt.Println(string(uDec))

}

2.5 - cbor

CBOR(Concise Binary Object Representation)是一种轻量级的数据交换格式,类似于JSON,但它以二进制形式表示数据,而不是文本形式。CBOR设计用于在网络上传输数据时减少数据的大小和复杂性,同时保持良好的可读性和可扩展性。
package main

import (
    "encoding/hex"
    "fmt"

    "github.com/fxamacker/cbor/v2"
)

func main() {

    b, _ := cbor.Marshal("hello world")
    fmt.Println(hex.EncodeToString(b))

    cborBytes, _ := hex.DecodeString(hex.EncodeToString(b))

    s := ""
    cbor.Unmarshal(cborBytes, &s)

    fmt.Printf("%s", s)
}

2.6 - channel

  1. 顺序执行两个协程
package main

import (
    "fmt"
    "log"
    "math/rand"
    "sync"
    "time"
)

func main() {
    //顺序执行两个协程函数
    ch1 := make(chan string, 5)
    go testBoringWithChannelClose("boring!", ch1)
    ch2 := make(chan string, 5)
    go testBoringWithChannelClose("funning!", ch2)

    for b := range ch1 {
        go log.Printf("You say: %s", b)
    }

    for b := range ch2 {
        go log.Printf("You say: %s", b)
    }

}

func testBoringWithChannelClose(msg string, c chan string) {
    for i := 0; i < 20; i++ {
        c <- fmt.Sprintf("%s %d", msg, i)
        time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond)
    }
    fmt.Println(msg + "结束")
    close(c)
}
  1. 带缓冲的channel 就像一个携程池,去执行协程任务。
package main

import (
    "fmt"
    "log"
    "math/rand"
    "sync"
    "time"
)

func main() {


    //限制1024个协程数执行任务
    goNum1024Times()
}

// 控制1024个协程
func goNum1024Times() {
    var wg sync.WaitGroup
    ch := make(chan struct{}, 1024)
    for i := 0; i < 20000; i++ {
        wg.Add(1)
        ch <- struct{}{}
        go func() {
            defer wg.Done()
            fmt.Printf("%d\n", len(ch)) //打印通道的长度
            <-ch
        }()
    }
    wg.Wait()
}

2.7 - CRC16

package main

import (
    "CRC15/method" //因为gomod 定义了模块名为 module CRC15 所以引用目录下的method
    "encoding/hex"
    "fmt"
    "strings"
)

func main() {

    byteArr, _ := hex.DecodeString("7F39F60116C067321223344556677889800112230100000000F700000000000000000000000000000000323030B7C2D8BB353030FABBF7D63130")
    checkVal := method.UsMBCRC16(byteArr)
    fmt.Println(strings.ToUpper(fmt.Sprintf("%X", checkVal)))
}

go.mod

module CRC15

go 1.13

method/crc16.go

package method

import (
    "encoding/hex"
    "fmt"
    "strings"
)

var auchCRCLo = [256]uint8{
    0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06,
    0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD,
    0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
    0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A,
    0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4,
    0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
    0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3,
    0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4,
    0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,
    0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29,
    0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED,
    0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
    0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60,
    0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67,
    0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,
    0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68,
    0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E,
    0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
    0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71,
    0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92,
    0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
    0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B,
    0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B,
    0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
    0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42,
    0x43, 0x83, 0x41, 0x81, 0x80, 0x40,
}

var auchCRCHi = [256]uint8{
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
    0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
    0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
    0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
    0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
    0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
    0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
    0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
    0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
    0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
}

func UsMBCRC16(pucFrame []byte) int {
    ucCRCHi := 0xFF
    ucCRCLo := 0xFF
    iIndex := 0
    for i := 0; i < len(pucFrame); i++ {
        iIndex = ucCRCLo ^ int(pucFrame[i])
        ucCRCLo = ucCRCHi ^ int(auchCRCHi[iIndex])
        ucCRCHi = int(auchCRCLo[iIndex])
    }
    return ucCRCLo<<8 | ucCRCHi
}

func TopscommCrc16(param string, check string) bool {
    byteArr, _ := hex.DecodeString(param)
    checkVal := UsMBCRC16(byteArr)
    h := fmt.Sprintf("%X", checkVal)
    if h != strings.ToUpper(check) {
        return false
    }

    return true
}

2.8 - gorm连接mssql

go.mod

require (
    github.com/denisenkom/go-mssqldb v0.0.0-20200620013148-b91950f658ec
    github.com/jinzhu/gorm v1.9.14
)
package main

import (
    "flag"
    "fmt"

    _ "github.com/denisenkom/go-mssqldb"
    "github.com/jinzhu/gorm"
)

type Dt_Cardtype struct {
    gorm.Model
    ID        string `gorm:"id"`
    CardCname string `gorm:"CardCname"`
}

var (
    debug         = flag.Bool("debug", true, "enable debugging")
    password      = flag.String("password", "Elink666.", "the database password")
    port     *int = flag.Int("port", 1433, "the database port")
    server        = flag.String("server", "172.16.183.131", "the database server")
    user          = flag.String("user", "sa", "the database user")
)

func main() {
    connString := fmt.Sprintf("server=%s;user id=%s;password=%s;port=%d;database=carpark", *server, *user, *password, *port)
    db, err := gorm.Open("mssql", connString)
    if err != nil {
        panic("连接数据库失败")
    }
    defer db.Close()

    db.SingularTable(true)
    db.LogMode(true)
    db.CommonDB()

    var dc []Dt_Cardtype
    db.Find(&dc)

    fmt.Println(len(dc))
    // // 自动迁移模式
    // db.AutoMigrate(&Product{})

    // // 创建
    // db.Create(&Product{Code: "L1212", Price: 1000})

    // // 读取
    // var product Product
    // db.First(&product, 1)                   // 查询id为1的product
    // db.First(&product, "code = ?", "L1212") // 查询code为l1212的product

    // // 更新 - 更新product的price为2000
    // db.Model(&product).Update("Price", 2000)

    // // 删除 - 删除product
    // db.Delete(&product)
}

2.9 - gorose连接mssql

go.mod

require (
    github.com/denisenkom/go-mssqldb v0.0.0-20200620013148-b91950f658ec
    github.com/gohouse/converter v0.0.3 // indirect
    github.com/gohouse/gorose v1.0.5
    github.com/gohouse/gorose/v2 v2.1.7
    github.com/jinzhu/gorm v1.9.14
)
package main

import (
    "flag"
    "fmt"
    "os"

    _ "github.com/denisenkom/go-mssqldb"
    gorose "github.com/gohouse/gorose/v2"
)

var err error
var engin *gorose.Engin

type DtCardtype struct {
    ID        int64  `gorose:"id"`
    CardCname string `gorose:"CardCname"`
    Mark      string `gorose:"Mark"`
}

var (
    debug         = flag.Bool("debug", true, "enable debugging")
    password      = flag.String("password", "123456", "the database password")
    port     *int = flag.Int("port", 1433, "the database port")
    server        = flag.String("server", "10.10.10.53", "the database server")
    user          = flag.String("user", "sa", "the database user")
)

func init() {
    // 全局初始化数据库,并复用
    // 这里的engin需要全局保存,可以用全局变量,也可以用单例
    // 配置&gorose.Config{}是单一数据库配置
    // 如果配置读写分离集群,则使用&gorose.ConfigCluster{}
    // mysql Dsn示例 "root:root@tcp(localhost:3306)/test?charset=utf8&parseTime=true"
    connString := fmt.Sprintf("server=%s;user id=%s;password=%s;port=%d;database=carpark", *server, *user, *password, *port)
    engin, err = gorose.Open(&gorose.Config{Driver: "mssql", Dsn: connString})
    if err != nil {
        fmt.Println(err)
        os.Exit(-1)
    }
}
func DB() gorose.IOrm {
    return engin.NewOrm()
}
func main() {
    var u []DtCardtype
    DB().Table("Park").First()
    fmt.Println(u)
}

2.10 - go发送get或post请求

package main

import (
    "bytes"
    "crypto/md5"
    "crypto/tls"
    "encoding/json"
    "fmt"
    "io"
    "io/ioutil"
    "log"
    "net/http"
    "strconv"
    "time"
)

func main() {
    // GetData()
    PostMethod()
}

//这是get请求
func GetData() {
    tr := &http.Transport{
        TLSClientConfig: &tls.Config{InsecureSkipVerify: true}} //如果需要测试自签名的证书 这里需要设置跳过证书检测 否则编译报错
    client := &http.Client{Transport: tr}
    resp, err := client.Get("https://192.168.7.15:8080/v1/getaction.do")
    //此处需要注意经过测试,不论是否有err在此之前一定要判断resp.Body如果不为空则需要关闭,测试环境go1.16
    if resp.Body != nil {
        defer resp.Body.Close()
    }
    if err != nil {
        fmt.Println("error:", err)
        return
    }

    body, err := ioutil.ReadAll(resp.Body)
    fmt.Println(string(body))
}

//这是post请求
func PostMethod() {
    var rsp io.Reader
    tr := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}} //如果需要测试自签名的证书 这里需要设置跳过证书检测 否则编译报错
    client := &http.Client{Transport: tr}
    data := "cmd=123"
    resp, err := client.Post("https://192.168.7.15:8080/v1/postaction.do", data, rsp)
    //此处需要注意经过测试,不论是否有err在此之前一定要判断resp.Body如果不为空则需要关闭,测试环境go1.16
    if resp.Body != nil {
        defer resp.Body.Close()
    }
    if err != nil {
        fmt.Println("err:", err)
    } else {
        body, er := ioutil.ReadAll(resp.Body)
        if er != nil {
            fmt.Println("err:", er)
        } else {
            fmt.Println(string(body))
        }
    }

}

func PostMethod() {
    //{"carmeraId":"epark-370211-dongjiakou~~cctv-dahua~channelCode~dahua!1000003$1$0$6","carmeraDesc":"","status":"1","areaId":"001","dataTime":"2020-11-17 14:30:12"}
    //{"carmeraId":"epark-370211-dongjiakou~~cctv-dahua~channelCode~dahua!1000003$1$0$7","carmeraDesc":"2F走廊西","status":"1","areaId":"001","dataTime":"2020-11-17 15:42:01"}
    m3 := map[string]string{
        "cameraId":   "epark-370211-dongjiakou~~cctv-dahua~channelCode~dahua!1000003$1$0$7",
        "cameraDesc": "2F走廊西",
        "status":     "1",
        "areaId":     "001",
        "dataTime":   "2020-11-17 15:42:01",
    }
    exportData, err := json.Marshal(m3)

    tr := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}
    client := &http.Client{Transport: tr}
    req, err := http.NewRequest(http.MethodPost, "https://eug-test.elinkit.com.cn/epmet-acs/report/video/info", bytes.NewReader(exportData))
    if err != nil {
        log.Fatal(err.Error)
    }
    times := strconv.FormatInt(time.Now().Unix(), 10)
    data := []byte("c3c4029b9741f26ab4a4cbd98c2310ff" + times)
    has := md5.Sum(data)
    accessToken := fmt.Sprintf("%x", has)
    req.Header.Set("Content-Type", "application/json")
    req.Header.Set("AccessToken", accessToken)
    req.Header.Set("Timestamp", times)

    response, err := client.Do(req)
    //此处需要注意经过测试,不论是否有err在此之前一定要判断 response.Body 如果不为空则需要关闭,测试环境go1.16
    if response.Body != nil {
        defer response.Body.Close()
    }

    if err != nil {
        fmt.Println("err:", err)

    }

    body, er := ioutil.ReadAll(response.Body)
    if er != nil {
        fmt.Println("err:", er)
    }
    fmt.Println(string(body))
}

2.11 - go脚本编程接受命令行参数

package main

import (
    "flag"
    "fmt"
)

func main() {
    // 定义几个变量,用于接收命令行的参数值
    var user string
    var password string
    var host string
    var port int
    // &user 就是接收命令行中输入 -u 后面的参数值,其他同理
    flag.StringVar(&user, "u", "root", "账号,默认为root")
    flag.StringVar(&password, "p", "", "密码,默认为空")
    flag.StringVar(&host, "h", "localhost", "主机名,默认为localhost")
    flag.IntVar(&port, "P", 3306, "端口号,默认为3306")
    // 解析命令行参数写入注册的flag里
    flag.Parse()
    // 输出结果
    fmt.Printf("user:%v\npassword:%v\nhost:%v\nport:%v\n",
        user, password, host, port)

}

2.12 - hmac算法

‌ HMAC (Hash-based Message Authentication Code)是一种基于哈希函数的消息认证码算法‌,用于验证数据的完整性和认证消息的发送者。HMAC结合了哈希函数和密钥,通过将密钥与消息进行哈希运算来生成消息认证码‌

HMAC的工作原理
HMAC使用一个密钥和一个哈希函数(如MD5、SHA-1、SHA-256等)生成一个固定长度的哈希值。具体步骤如下:

‌输入‌:消息和密钥。
‌处理‌:HMAC对输入的消息和密钥进行哈希处理,生成一个固定长度的哈希值。
‌输出‌:该哈希值即为HMAC签名‌

HMAC的安全性
HMAC的安全性依赖于密钥的保密性和所使用的哈希函数的抗碰撞能力。只有持有密钥的一方能够生成或验证该消息的签名,从而确保消息在传输过程中没有被篡改‌

HMAC的应用场景
HMAC广泛应用于各种需要确保数据完整性和认证的应用场景中,例如:

‌ IPSec ‌:在IPSec中,HMAC用于验证IP数据包的完整性和真实性。
‌ SSL/TLS ‌:在SSL/TLS协议中,HMAC用于验证通信双方的身份和数据完整性。
package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/base64"
    "fmt"
)

func GenHmacSha256(message string, secret string) string {
    h := hmac.New(sha256.New, []byte(secret))
    h.Write([]byte(message))
    return base64.StdEncoding.EncodeToString(h.Sum(nil))
}

func main() {

    key := "VHc1CBWM0YS204rydm+wWxkQrqJSVyXAAgFRktVpOF4="
    str := "/report/data/device?timestamp=123456789&appKey=9aON8USr&nonce=123456"
    ret := GenHmacSha256(str, key)

    fmt.Printf("ret: %s\n", ret)
}

//RdYq6cvBpeF84HO8iQErRF6Aq9XdE2fDx5w4qjDWfrg=
//RdYq6cvBpeF84HO8iQErRF6Aq9XdE2fDx5w4qjDWfrg=
//6D+MQzWc3eU4eqZkXaUQ4xmeviQPljjzs4CQmF18ldk=
//6D+MQzWc3eU4eqZkXaUQ4xmeviQPljjzs4CQmF18ldk=

2.13 - md5编码

package main

import (
    "crypto/md5"
    "fmt"
    "io"
)

func main() {
    str := "abcdefg"

    //方法一
    data := []byte(str)
    has := md5.Sum(data)
    md5str1 := fmt.Sprintf("%x", has) //将[]byte转成16进制

    fmt.Println(md5str1)

    //方法二

    w := md5.New()
    io.WriteString(w, str)                   //将str写入到w中
    md5str2 := fmt.Sprintf("%x", w.Sum(nil)) //w.Sum(nil)将w的hash转成[]byte格式

    fmt.Println(md5str2)
}

2.14 - modbustcp-client

go.mod

require (
    github.com/goburrow/modbus v0.1.0
    github.com/goburrow/serial v0.1.0 // indirect
)
package main

import (
    "log"
    "os"
    "time"

    "github.com/goburrow/modbus"
)

func main() {

    handler := modbus.NewTCPClientHandler("localhost:502")
    handler.Timeout = 10 * time.Second
    handler.SlaveId = 0 //SlaveId即modbustcp协议从0开始得第6字节称,在厂家协议中称为RTU地址无效字段,标准协议中如果是0则为广播地址
    handler.Logger = log.New(os.Stdout, "test: ", log.LstdFlags)
    // Connect manually so that multiple requests are handled in one connection session
    err := handler.Connect()
    if err != nil {
        log.Fatalf("连接错误:%+v", err)
    }
    defer handler.Close()

    // modbus.Tc
    // modbus.NewClient(handler)
    client := modbus.NewClient(handler)
    log.Printf("客户端:%+v", client)
    client.ReadHoldingRegisters(40001, 10)

    res, e1 := client.ReadHoldingRegisters(40001, 2)
    if e1 != nil {
        log.Fatalf("E1错误:%+v", e1)
    }
    log.Printf("资源内容1:%+v", res)
    res, _ = client.ReadHoldingRegisters(40000, 3)
    log.Printf("资源内容2:%+v", res)
    res, _ = client.ReadHoldingRegisters(40000, 3)
    log.Printf("资源内容3:%+v", res)
    res, _ = client.ReadHoldingRegisters(40000, 3)
    log.Printf("资源内容4:%+v", res)

    results1, err := client.ReadDiscreteInputs(15, 2)
    results2, err = client.WriteMultipleRegisters(1, 2, []byte{0, 3, 0, 4})

}

2.15 - opc-client

package main

import (
    "fmt"

    "github.com/konimarti/opc"
)

func main() {
    client, _ := opc.NewConnection(
        "Matrikon.OPC.Simulation.1",               // ProgId
        []string{"localhost"},                     //  OPC servers nodes
        []string{"Random.Real8", "Random.String"}, // slice of OPC tags
    )
    defer client.Close()

    // read single tag: value, quality, timestamp
    fmt.Println(client.ReadItem("Random.Real8"))

    // read all added tags
    fmt.Println(client.Read())
}

2.16 - Post上传文件

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "io/ioutil"
    "log"
    "mime/multipart"
    "net/http"
    "os"
    "path/filepath"
)

func newfileUploadRequest(uri string, params map[string]string, paramName, path string) (UploadFileRes, error) {
    file, err := os.Open(path)
    if err != nil {
        return UploadFileRes{}, err
    }
    defer file.Close()

    body := &bytes.Buffer{}
    writer := multipart.NewWriter(body)
    part, err := writer.CreateFormFile(paramName, filepath.Base(path))
    if err != nil {
        return UploadFileRes{}, err
    }
    _, err = io.Copy(part, file)

    for key, val := range params {
        _ = writer.WriteField(key, val)
    }
    err = writer.Close()
    if err != nil {
        return UploadFileRes{}, err
    }

    request, err := http.NewRequest("POST", uri, body)
    if err != nil {
        log.Fatal(err)
        return UploadFileRes{}, err
    }
    request.Header.Add("Content-Type", writer.FormDataContentType())
    client := &http.Client{}
    resp, err := client.Do(request)
    if err != nil {
        log.Fatal(err)
        return UploadFileRes{}, err
    } else {
        defer resp.Body.Close()
        body, err := ioutil.ReadAll(resp.Body)
        // body := &bytes.Buffer{}
        // _, err := body.ReadFrom(resp.Body)
        if err != nil {
            log.Fatal(err)
        }

        // fmt.Println(resp.StatusCode)
        // fmt.Println(resp.Header)
        // fmt.Println(body.Bytes())

        var uploadFileRes UploadFileRes
        json.Unmarshal(body, &uploadFileRes)
        return uploadFileRes, nil
    }
    return UploadFileRes{}, fmt.Errorf("lib throwe")
}

func main() {

    extraParams := map[string]string{}
    res, err := newfileUploadRequest("http://192.168.1.56:1502/uploadeFile", extraParams, "file", "/home/koala/project/tmp/images/1.png")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(res)

    sep := string(os.PathSeparator)
    fmt.Println(sep)
}

type UploadFileRes struct {
    ErrCode string `json:"errCode"`
    ErrMsg  string `json:"errMsg"`
    Data    string `json:"data"`
}

2.17 - RSA-encryption

csdn_private.pem

-----BEGIN csdn_privateKey-----
MIIEpAIBAAKCAQEAv5so1PIs1jaJPJ7fOQBUAzpIqf8kZ7vCETs9sGXCs2XzqwH4
sYyzPcU9NY5CBMMtev9bW0oPyETQCsaS5HtbpgHwRPqRfbcSWZXkodUUUgSNP2ss
/74tMjw4tnx4kxW/EmsZnGJGKuyY7Tf5IlI7QD6U9nWWRKAEsKSbyd/7H3HzLxoB
xZsMbXXI3MgERzJcsmacWPnVG98i7QH0oSu+a7zSse5zSrW/RtKQj8WtAOMVZ7FP
x4fqofj6JGXot4SFBkwSgnPD2613PSPMQn6/G2dHUEJqmqtJr4j8ptP+1T6tWpy0
BVm/WhbkN9ymCOlds+SGMkyeCUs6daBJilQ9VQIDAQABAoIBADDsj2qAQ86WskgW
UO0fFlSUp0Uw7rzGBnGb7M6DzUk9eRBrOnMreAEHwe9Q2a6Zn51OYqdWq9z5JR37
Qjqw/N/QkucqC8hL3JWfXnesDro6i05sMVtD1gqDsf92nNsBrH4pdqqltUD0lL/N
kQGgeZyX3jVoJOx0532rKlRLqrWGU5acSosZqTTuCQEjBH94mA1KTvLy0CqZWyWo
yZ/OBtF61TxXa5qejJl4MJ8050UV8T7tqkckK9h4bColVS58VvnYaKST5bf33YiY
sAbE7/o8B9NW/3ogwJG6yvjXnSnICOLNUofb26zftW9DrcYGmLMnsPtm8Tj2xPLj
7zAjbekCgYEA8CZNYs72yDxJjvewvYmeWSkAO9goVBHQSSyMJcgdhiWG4IkZbaAo
1Mrvs3LBIOY1VMr2OjopzwSupNvKqOokLZ+2zcijvqDmv8AgZdh79JdaRVqqXf83
Zvun6RD1Par8vIfNIWPV/ruiM0QFcgbpd3VshAOJbqLv0QVb3Lb/f5MCgYEAzECj
uf1HKCgguRDvajG8aImES+xWdXubyA0yakS6xiBt9Pyr5sNkcum3FDIynj9/i/ke
MrK/4PtewwYwemWpccWf+EAXhQhj7bqKC5TsQr3gu9eQ+A9RVOGJ7WBJca9VY8q6
7jAWf/UlEx8WPxaV11jtvchzA1erwmMwafLGUHcCgYEAwf9PGHj0psD88z9oSVT4
1DHo/G8b9P4G8nXIKWVFZG7ATHa0UfjFw1DE3oPfPAJ8Jqlmy5bc211+77KWPmoX
G7wf4pEopgA5J8G+6kc9q1LxG4GoixJ24Px+oiqO0mhkjrBtp4GNB6Dv4NYcSAcJ
ZvU22lY5GWUKsiHQGbbDI30CgYEAx9h7GdCaXc0db1YFmrb9LJ9YpVyhn6OI4a0f
5eBHivFSBMFwhIIrd0/7xLP02OcyKcdeZ6aDnWL17gXRSwDLULlXcvNqz8xM0d6R
kRFuNUNJbyFVA5EhN9bROEPcuHIgL1q9ma3NZfd7BgGFp8a2Z5ToUKee+Oc/9BtO
1Gso5LMCgYBZKa3mZp1nmWHn7zlD+HeP/MACs34ZgaR33dGIx9j5jO8s5Gw98wJg
Rfjvuf73Yq9T7VhlJp6mZ+fDVuhrbBlI32Itrb+p7FztIx3rVBXjvvuhqOFDupYl
UttiUvkrWuoTKfbPhKZkKVDYp+fm/dd6JBa+c0+2kuwo6tm/Yr3kbw==
-----END csdn_privateKey-----

csdn_PublicKey.pem

-----BEGIN csdn_PublicKey-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv5so1PIs1jaJPJ7fOQBU
AzpIqf8kZ7vCETs9sGXCs2XzqwH4sYyzPcU9NY5CBMMtev9bW0oPyETQCsaS5Htb
pgHwRPqRfbcSWZXkodUUUgSNP2ss/74tMjw4tnx4kxW/EmsZnGJGKuyY7Tf5IlI7
QD6U9nWWRKAEsKSbyd/7H3HzLxoBxZsMbXXI3MgERzJcsmacWPnVG98i7QH0oSu+
a7zSse5zSrW/RtKQj8WtAOMVZ7FPx4fqofj6JGXot4SFBkwSgnPD2613PSPMQn6/
G2dHUEJqmqtJr4j8ptP+1T6tWpy0BVm/WhbkN9ymCOlds+SGMkyeCUs6daBJilQ9
VQIDAQAB
-----END csdn_PublicKey-----
package main

import (
    "crypto/rand"
    "crypto/rsa"
    "crypto/x509"
    "encoding/hex"
    "encoding/pem"
    "fmt"
    "os"
)

func Getkeys() {
    //得到私钥
    privateKey, _ := rsa.GenerateKey(rand.Reader, 2048)
    //通过x509标准将得到的ras私钥序列化为ASN.1 的 DER编码字符串
    x509_Privatekey := x509.MarshalPKCS1PrivateKey(privateKey)
    //创建一个用来保存私钥的以.pem结尾的文件
    fp, _ := os.Create("csdn_private.pem")
    defer fp.Close()
    //将私钥字符串设置到pem格式块中
    pem_block := pem.Block{
        Type:  "csdn_privateKey",
        Bytes: x509_Privatekey,
    }
    //转码为pem并输出到文件中
    pem.Encode(fp, &pem_block)

    //处理公钥,公钥包含在私钥中
    publickKey := privateKey.PublicKey
    //接下来的处理方法同私钥
    //通过x509标准将得到的ras私钥序列化为ASN.1 的 DER编码字符串
    x509_PublicKey, _ := x509.MarshalPKIXPublicKey(&publickKey)
    pem_PublickKey := pem.Block{
        Type:  "csdn_PublicKey",
        Bytes: x509_PublicKey,
    }
    file, _ := os.Create("csdn_PublicKey.pem")
    defer file.Close()
    //转码为pem并输出到文件中
    pem.Encode(file, &pem_PublickKey)

}

//使用公钥进行加密
func RSA_encrypter(path string, msg []byte) []byte {
    //首先从文件中提取公钥
    fp, _ := os.Open(path)
    defer fp.Close()
    //测量文件长度以便于保存
    fileinfo, _ := fp.Stat()
    buf := make([]byte, fileinfo.Size())
    fp.Read(buf)
    //下面的操作是与创建秘钥保存时相反的
    //pem解码
    block, _ := pem.Decode(buf)
    //x509解码,得到一个interface类型的pub
    pub, _ := x509.ParsePKIXPublicKey(block.Bytes)
    //加密操作,需要将接口类型的pub进行类型断言得到公钥类型
    cipherText, _ := rsa.EncryptPKCS1v15(rand.Reader, pub.(*rsa.PublicKey), msg)
    return cipherText
}

//使用私钥进行解密
func RSA_decrypter(path string, cipherText []byte) []byte {
    //同加密时,先将私钥从文件中取出,进行二次解码
    fp, _ := os.Open(path)
    defer fp.Close()
    fileinfo, _ := fp.Stat()
    buf := make([]byte, fileinfo.Size())
    fp.Read(buf)
    block, _ := pem.Decode(buf)
    PrivateKey, _ := x509.ParsePKCS1PrivateKey(block.Bytes)
    //二次解码完毕,调用解密函数
    afterDecrypter, _ := rsa.DecryptPKCS1v15(rand.Reader, PrivateKey, cipherText)
    return afterDecrypter
}

func main() {

    // Getkeys()

    //尝试调用
    msg := []byte("RSA非对称加密很棒")
    ciphertext := RSA_encrypter("csdn_PublicKey.pem", msg)
    //转化为十六进制方便查看结果
    fmt.Println(hex.EncodeToString(ciphertext))
    result := RSA_decrypter("csdn_private.pem", ciphertext)
    fmt.Println(string(result))

}

2.18 - stdin-stdout-stderr

  1. print
package main

import (
    "fmt"
)

func main() {
    v1 := "123"
    v2 := 123
    v3 := "Have a nice day\n"
    v4 := "abc"
    fmt.Print(v1, v2, v3, v4)
    fmt.Println()
    fmt.Println(v1, v2, v3, v4)
    fmt.Print(v1, " ", v2, " ", v3, " ", v4, "\n")
    fmt.Printf("%s%d %s %s\n", v1, v2, v3, v4)
}
  1. stderr
package main

import (
    "io"
    "os"
)

func main() {
    myString := ""
    arguments := os.Args
    if len(arguments) == 1 {
        myString = "Please give me one argument!"
    } else {
        myString = arguments[1]
    }
    io.WriteString(os.Stdout, "This is Standard output\n")
    io.WriteString(os.Stderr, myString)
    io.WriteString(os.Stderr, "\n")
}
  1. stdin
package main

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

func main() {
    var f *os.File
    f = os.Stdin
    defer f.Close()
    scanner := bufio.NewScanner(f)
    for scanner.Scan() {
        fmt.Println(">", scanner.Text())
    }
}
  1. stdin-command-line
package main

import (
    "fmt"
    "os"
    "strconv"
)

func main() {
    if len(os.Args) == 1 {
        fmt.Println("Please give one or more floats.")
        os.Exit(1)
    }

    arguments := os.Args
    min, _ := strconv.ParseFloat(arguments[1], 64)
    max, _ := strconv.ParseFloat(arguments[1], 64)
    for i := 2; i < len(arguments); i++ {
        n, _ := strconv.ParseFloat(arguments[i], 64)
        if n < min {
            min = n
        }
        if n > max {
            max = n
        }
    }
    fmt.Println("Min:", min)
    fmt.Println("Max:", max)
}
  1. stdout
package main

import (
    "io"
    "os"
)

func main() {
    myString := ""
    arguments := os.Args
    if len(arguments) == 1 {
        myString = "Please give me one argument!"
    } else {
        myString = arguments[1]
    }
    io.WriteString(os.Stdout, myString)
    io.WriteString(os.Stdout, "\n")
}

2.19 - UDP简单的示例


package main

import (
    "context"
    "encoding/hex"
    "fmt"
    "log"
    "net"
    "time"
)

func main() {
    // 本质上udp不存在传统意义上的服务端和客户端, 他其实是无连接的。
    // 但是实际使用中, 总要有一方去监听数据, 另一方去连接所以才有了 一个服务 一个客户

    // 创建一个监听服务
    // conn, err := net.Dial("udp", ":30000")
    // conn, err := net.DialUDP("udp", &net.UDPAddr{
    //     IP:   net.ParseIP("0.0.0.0"),
    //     Port: 45103,
    // }, &net.UDPAddr{
    //     IP:   net.ParseIP("127.0.0.1"),
    //     Port: 30000,
    // })

    // 连接已经创建的服务
    conn, err := net.DialUDP("udp", nil, &net.UDPAddr{
        IP:   net.ParseIP("127.0.0.1"),
        Port: 30000,
    })

    if err != nil {
        log.Fatalf("连接建立错误: %s", err.Error())
        return
    }
    defer conn.Close()

    //7F06FE01993FFF3C16
    //7F32F601143801001F0250000100000000000000000000000000000000323030B7C2D8BB353030FABBF7D63130381411111501893E
    //7F32F60115384000200250000100000000000000000000000000000000323030B7C2D8BB353030FABBF7D631303814111115012F09

    // go func() {
    // 创建一个计时器
    timeTickerChan := time.Tick(time.Second * 2)
    ctx, cancel := context.WithTimeout(context.Background(), time.Duration(time.Millisecond*800))
    defer cancel()
    for {
        select {
        case <-timeTickerChan:
            input, err := hex.DecodeString("7F06FE01993FFF3C16")
            if err != nil {
                fmt.Printf("输入值解码错误:%s\n", err.Error())
                continue
            }

            //客户端请求数据写入 conn,并传输
            conn.WriteToUDP([]byte(input), &net.UDPAddr{
                IP:   net.ParseIP("10.10.10.4"),
                Port: 8887,
            })
            writeCount, err := conn.Write([]byte(input))
            if err != nil {
                log.Printf("数据发送错误:%s\n", err.Error())
                continue
            }
            log.Printf("输入值:%s; 发送量: %d\n", string(input), writeCount)

            go func(ctx context.Context) {
                //服务器端返回的数据写入空buf
                buf := make([]byte, 1024)
                // conn.ReadFrom()
                // conn.ReadFromUDP()
                cnt, serverAddr, err := conn.ReadFrom(buf)
                if err != nil {
                    log.Printf("客户端读取数据失败 %s\n", err)
                    return
                }
                //回显服务器端回传的信息
                log.Printf("服务器端回复: %s; 服务端信息:%s", hex.EncodeToString(buf[0:cnt]), serverAddr.String())
            }(ctx)

            select {
            case <-ctx.Done():
                fmt.Println("call successfully!!!")
                continue
            case <-time.After(time.Duration(time.Millisecond * 900)):
                fmt.Println("timeout!!!")
                continue
            }

            // input, _ = hex.DecodeString("7F32F601143801001F0250000100000000000000000000000000000000323030B7C2D8BB353030FABBF7D63130381411111501893E")
            // log.Printf("输入值:%v\n", input)
            // //客户端请求数据写入 conn,并传输
            // conn.Write([]byte(input))
            // //服务器端返回的数据写入空buf
            // cnt, err = conn.Read(buf)
            // if err != nil {
            //     log.Printf("客户端读取数据失败 %s\n", err)
            //     continue
            // }
            // //回显服务器端回传的信息
            // log.Printf("服务器端回复" + hex.EncodeToString(buf[0:cnt]) + "\n")

            // input, _ = hex.DecodeString("7F32F60115384000200250000100000000000000000000000000000000323030B7C2D8BB353030FABBF7D631303814111115012F09")
            // log.Printf("输入值:%v\n", input)
            // //客户端请求数据写入 conn,并传输
            // conn.Write([]byte(input))
            // //服务器端返回的数据写入空buf
            // cnt, err = conn.Read(buf)
            // if err != nil {
            //     log.Printf("客户端读取数据失败 %s\n", err)
            //     continue
            // }
            // //回显服务器端回传的信息
            // log.Printf("服务器端回复" + hex.EncodeToString(buf[0:cnt]) + "\n")
        }
    }

    // }()

}

2.20 - websocket-client

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "net/url"
    "sync"
    "time"

    "github.com/gorilla/websocket"
)

var mutex sync.Mutex

func main() {
    // u := url.URL{Scheme: "ws", Host: "8.136.204.163:8000"}
    u := url.URL{Scheme: "ws", Host: "localhost:8001"}
    c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
    if err != nil {
        log.Fatalf("%+v", err)
    }
    defer c.Close()
    // c.SetCloseHandler(func(code int, text string) error {
    //     fmt.Printf("SetCloseHandler %d:%s", code, text)
    //     // message := websocket.FormatCloseMessage(code, "")
    //     // c.WriteControl(CloseMessage, message, time.Now().Add(writeWait))
    //     return nil
    // })
    // c.SetPingHandler(func(appData string) error {
    //     fmt.Printf("SetPingHandler %s", appData)
    //     return nil
    // })
    // c.SetPongHandler(func(appData string) error {
    //     fmt.Printf("SetPingHandler %s", appData)
    //     return nil
    // })

    pingParam := make(map[string]interface{})
    pingParam["msgId"] = 3
    pingParam["action"] = "Heartbeat"
    pingData := make(map[string]interface{})
    pingData["echo"] = "ping"
    pingParam["data"] = pingData

    param := make(map[string]interface{})
    param["msgId"] = 2
    param["action"] = "Login"
    data := make(map[string]interface{})
    data["authName"] = "taian"
    data["authToken"] = "7ee193f504125d7d9e715e0252648856"
    data["clientName"] = "taian123"
    param["data"] = data
    mutex.Lock()
    c.WriteJSON(param)
    mutex.Unlock()

    // res := make(map[string]interface{})
    // err1 := c.ReadJSON(&res)
    // if err1 != nil {
    //     fmt.Printf("%+v\n", err1)
    //     log.Fatalln(err1)
    // }
    // bs, _ := json.Marshal(res)
    // log.Printf("%s\n", string(bs))

    // mutex.Lock()
    // err0 := c.WriteJSON(pingParam)
    // mutex.Unlock()
    // if err0 != nil {
    //     log.Fatalf("写ping命令ERR:%+v\n", err1)
    // }

    // res = make(map[string]interface{})
    // err1 = c.ReadJSON(&res)
    // if err1 != nil {
    //     log.Fatalf("读ping命令ERR: %+v\n", err1)
    // }
    // bs, _ = json.Marshal(res)
    // log.Printf("%s\n", string(bs))

    go func() {
        t := time.NewTicker(time.Second * 4)
        defer t.Stop()
        for {
            <-t.C
            mutex.Lock()
            c.WriteJSON(pingParam)
            mutex.Unlock()
        }
    }()

    go func() {
        for {
            res := make(map[string]interface{})
            err1 := c.ReadJSON(&res)
            if err1 != nil {
                fmt.Printf("%+v\n", err1)
                continue
            }
            bs, _ := json.Marshal(res)
            log.Printf("%s\n", string(bs))
        }

    }()

    // param["action"] = "GetDevice"
    // data = make(map[string]interface{})
    // data["index"] = 1
    // data["count"] = -1

    param0 := make(map[string]interface{})
    param0["msgId"] = 4
    param0["action"] = "RegDeviceData"
    data0 := make(map[string]interface{})
    data0["names"] = []string{"S01"}
    data0["count"] = 1
    param0["data"] = data0

    mutex.Lock()
    err0 := c.WriteJSON(param0)
    mutex.Unlock()
    if err0 != nil {
        log.Fatalf("%+v", err0)
    }

    param1 := make(map[string]interface{})
    param1["msgId"] = 4
    param1["action"] = "GetRealDeviceData"
    data1 := make(map[string][]string)
    data1["names"] = []string{"S01"}
    param1["data"] = data1

    mutex.Lock()
    err1 := c.WriteJSON(param1)
    mutex.Unlock()
    if err1 != nil {
        log.Fatalf("%+v", err1)
    }
    select {}
}

2.21 - xorm连接mssql

go.mod

require (
    github.com/denisenkom/go-mssqldb v0.0.0-20200620013148-b91950f658ec
    xorm.io/xorm v1.0.2
)

main.go

package main

import (
    "flag"
    "fmt"
    "time"

    _ "github.com/denisenkom/go-mssqldb"
    "xorm.io/xorm"
)

type Park struct {
    ID   string `xorm:"Id"`
    Name string `xorm:"Name"`
}

var (
    debug         = flag.Bool("debug", true, "enable debugging")
    password      = flag.String("password", "123456", "the database password")
    port     *int = flag.Int("port", 1433, "the database port")
    server        = flag.String("server", "10.10.10.53", "the database server")
    user          = flag.String("user", "sa", "the database user")
)

func main() {

    connString := fmt.Sprintf("server=%s;user id=%s;password=%s;port=%d;database=ed;encrypt=disable", *server, *user, *password, *port)
    engine, err := xorm.NewEngine("mssql", connString)
    //控制台打印SQL语句

    if err != nil {
        fmt.Println(err)
        return
    }
    defer engine.Close()
    engine.SetConnMaxLifetime(120 * time.Second)
    engine.SetMaxOpenConns(16)
    engine.SetMaxIdleConns(8)

    err = engine.Ping()
    if err != nil {
        fmt.Println(err)
        return
    }
    var sv []Park
    engine.ShowSQL(true)

    // err = engine.SQL("SELECT TOP 1000 id,CardCname FROM Dt_CardType order by id asc").Find(&sv)
    engine.Find(&sv)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(len(sv))
}

2.22 - 遍历字符串

package main

import "fmt"

func main() {
    var str = "Hello北京&"

    //1. 按index遍历,这种方式按照字节遍历,对于非ASCII字符会出现乱码。因为"北京"每个字占3个字节
    fmt.Println("按index遍历开始")
    for i := 0; i < len(str); i++ {
        fmt.Printf("%d:%c--", i, str[i])
    }
    fmt.Println("按index遍历结束")

    //2. 用for-range遍历,这种方式是按照字符遍历的,不会出现乱码,但是下标index会出现不连续的情况,它具有不确定性
    fmt.Println("for-range遍历开始")
    for i, ch := range str {
        fmt.Printf("%d:%c--", i, ch)
    }
    fmt.Println("for-range遍历结束")

    //3. 转rune切片遍历
    fmt.Println("转rune切片遍历开始")
    str2 := []rune(str)
    for i := 0; i < len(str2); i++ {
        fmt.Printf("%d:%c--", i, str2[i])
    }
    fmt.Println("\n转rune切片遍历结束")
}

2.23 - 定时执行

  1. 最简单的定时执行
package main

import (
    "fmt"
    "time"
)

func main() {

    go func() {
        // 创建一个计时器
        timeTickerChan := time.Tick(time.Second * 5)
        for {
            fmt.Println("123")
            <-timeTickerChan
        }
    }()

    fmt.Println(112233)
    //一定要阻止主线程退出定时任务才能有效
    select {}
}
  1. cron定时执行

require github.com/robfig/cron/v3 v3.0.1

package main

import (
    "fmt"
    "github.com/robfig/cron/v3"
    "time"
)

func main() {
    // 新建一个定时任务对象
    // 根据cron表达式进行时间调度,cron可以精确到秒,大部分表达式格式也是从秒开始。
    //crontab := cron.New()  默认从分开始进行时间调度
    crontab := cron.New(cron.WithSeconds()) //精确到秒
    //定义定时器调用的任务函数
    task := func() {
        fmt.Println("hello world", time.Now())
    }
    //定时任务
    spec := "*/5 * * * * ?" //cron表达式,每五秒一次
    // 添加定时任务,
    crontab.AddFunc(spec, task)
    // 启动定时器
    crontab.Start()
    // 定时任务是另起协程执行的,这里使用 select 简答阻塞.实际开发中需要
    // 根据实际情况进行控制
    select {} //阻塞主线程停止
}

2.24 - 读MySQL数据库所有表并输出见表语句

package main

import (
    "fmt"

     "gorm.io/driver/mysql"
     "gorm.io/gorm"
)

type ShowTablesRes struct {
    // 注意这里也要改数据库名
    TableName string `gorm:"column:Tables_in_${database}"`
}
type CreateRes struct {
    Table       string
    CreateTable string `gorm:"column:Create Table"`
}

func main() {

    dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", "${username}", "${password}", "${ip}:${port}", "${database}")
    // useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=true&serverTimezone=GMT%2B8"
    fmt.Println(dsn)

    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        panic("failed to connect database")
    }


    var showTablesRes []ShowTablesRes
    db.Raw("show tables").Scan(&showTablesRes)
    // fmt.Println(&showTablesRes)
    for _, v := range showTablesRes {
        // fmt.Printf("%s\n", v.TableName)
        var createRes []CreateRes
        showTableStruct := "show create table " + v.TableName
        db.Raw(showTableStruct).Scan(&createRes)
        fmt.Printf("%s;\n", createRes[0].CreateTable)

    }

}

2.25 - 链接sqlserver2008

go.mod

require (
    github.com/denisenkom/go-mssqldb v0.0.0-20200620013148-b91950f658ec
    github.com/go-ole/go-ole v1.2.4 // indirect
    github.com/mattn/go-adodb v0.0.1 // indirect
    golang.org/x/net v0.0.0-20200707034311-ab3426394381 // indirect
)
package main

import (
    "database/sql"
    "fmt"
    "log"

    _ "github.com/denisenkom/go-mssqldb"
)

// var (
//     debug         = flag.Bool("debug", true, "enable debugging")
//     password      = flag.String("password", "123456", "the database password")
//     port     *int = flag.Int("port", 1433, "the database port")
//     server        = flag.String("server", "10.10.10.53", "the database server")
//     user          = flag.String("user", "sa", "the database user")
// )

func main() {
    // var debug = flag.Bool("debug", false, "enable debugging")
    // var password = flag.String("password", "123456", "the database password")
    // var port *int = flag.Int("port", 1433, "the database port")
    // var server = flag.String("server", "10.10.10.53", "the database server")
    // var user = flag.String("user", "sa", "the database user")
    // var database = flag.String("database", "ed", "the database name")

    // if *debug {
    //     fmt.Printf(" password:%s\n", *password)
    //     fmt.Printf(" port:%d\n", *port)
    //     fmt.Printf(" server:%s\n", *server)
    //     fmt.Printf(" user:%s\n", *user)
    // }
    connString := fmt.Sprintf("server=%s;database=%s;user id=%s;password=%s;port=%d;encrypt=disable", "10.10.10.53", "ed", "sa", "123456", 1433)

    fmt.Printf(" connString:%s\n", connString)

    db, err := sql.Open("mssql", connString)
    if err != nil {
        log.Fatal("Open connection failed:", err.Error())
        return
    }

    err = db.Ping()
    if err != nil {
        fmt.Print("PING:%s", err)
        return
    }
    fmt.Println(1)
}

2.26 - 使用redis

package main

import (
    "fmt"
    "log"
    "math/rand"
    "sync"
    "time"

    "github.com/go-redis/redis"
)

var redisdb *redis.Client
var wg sync.WaitGroup

func main() {
    wg.Add(1)
    go testRedisBase()
    wg.Wait()
}

var map1 map[string]string

func testRedisBase() {
    defer wg.Done()

    //连接服务器
    redisdb = redis.NewClient(&redis.Options{
        Addr:     "localhost:6379", // use default Addr
        Password: "",               // no password set
        DB:       0,                // use default DB
    })

    //心跳
    pong, err := redisdb.Ping().Result()
    log.Println(pong, err) // Output: PONG <nil>

    result, err := redisdb.BLPop(10*time.Second, "list_test").Result()
    log.Println("result:", result, err, len(result))

    ch := make(chan struct{})
    map1 = make(map[string]string)
    go func() {
        for {
            content, _ := redisdb.BLPop(10*time.Second, "list_test").Result()
            if content != nil {
                ch <- struct{}{}
                map1[content[1]] = content[1]
            }
        }
    }()

    for {
        <-ch
        for k, v := range map1 {
            log.Println(k, ":", v)
        }
        if _, ok := map1["1"]; ok {
            log.Println(map1["1"])
        }
        delete(map1, "1")
        for k, v := range map1 {
            log.Println(k, ":", v)
        }
    }

    // ExampleClient_String()
    // ExampleClient_List()
    // ExampleClient_Hash()
    // ExampleClient_Set()
    // ExampleClient_SortSet()
    // ExampleClient_HyperLogLog()
    // ExampleClient_CMD()
    // ExampleClient_Scan()
    // ExampleClient_Tx()
    // ExampleClient_Script()
    // ExampleClient_PubSub()
}

func ExampleClient_String() {
    log.Println("ExampleClient_String")
    defer log.Println("ExampleClient_String")

    //kv读写
    err := redisdb.Set("key", "value", 1*time.Second).Err()
    log.Println(err)

    //获取过期时间
    tm, err := redisdb.TTL("key").Result()
    log.Println(tm)

    val, err := redisdb.Get("key").Result()
    log.Println(val, err)

    val2, err := redisdb.Get("missing_key").Result()
    if err == redis.Nil {
        log.Println("missing_key does not exist")
    } else if err != nil {
        log.Println("missing_key", val2, err)
    }

    //不存在才设置 过期时间 nx ex
    value, err := redisdb.SetNX("counter", 0, 1*time.Second).Result()
    log.Println("setnx", value, err)

    //Incr
    result, err := redisdb.Incr("counter").Result()
    log.Println("Incr", result, err)
}

func ExampleClient_List() {
    log.Println("ExampleClient_List")
    defer log.Println("ExampleClient_List")

    //添加
    log.Println(redisdb.RPush("list_test", "message1").Err())
    log.Println(redisdb.RPush("list_test", "message2").Err())

    //设置
    log.Println(redisdb.LSet("list_test", 2, "message set").Err())

    //remove
    ret, err := redisdb.LRem("list_test", 3, "message1").Result()
    log.Println(ret, err)

    rLen, err := redisdb.LLen("list_test").Result()
    log.Println(rLen, err)

    //遍历
    lists, err := redisdb.LRange("list_test", 0, rLen-1).Result()
    log.Println("LRange", lists, err)

    //pop没有时阻塞
    result, err := redisdb.BLPop(30*time.Second, "list_test").Result()
    log.Println("result:", result, err, len(result))
}

func ExampleClient_Hash() {
    log.Println("ExampleClient_Hash")
    defer log.Println("ExampleClient_Hash")

    datas := map[string]interface{}{
        "name": "LI LEI",
        "sex":  1,
        "age":  28,
        "tel":  123445578,
    }

    //添加
    if err := redisdb.HMSet("hash_test", datas).Err(); err != nil {
        log.Fatal(err)
    }

    //获取
    rets, err := redisdb.HMGet("hash_test", "name", "sex").Result()
    log.Println("rets:", rets, err)

    //成员
    retAll, err := redisdb.HGetAll("hash_test").Result()
    log.Println("retAll", retAll, err)

    //存在
    bExist, err := redisdb.HExists("hash_test", "tel").Result()
    log.Println(bExist, err)

    bRet, err := redisdb.HSetNX("hash_test", "id", 100).Result()
    log.Println(bRet, err)

    //删除
    log.Println(redisdb.HDel("hash_test", "age").Result())
}

func ExampleClient_Set() {
    log.Println("ExampleClient_Set")
    defer log.Println("ExampleClient_Set")

    //添加
    ret, err := redisdb.SAdd("set_test", "11", "22", "33", "44").Result()
    log.Println(ret, err)

    //数量
    count, err := redisdb.SCard("set_test").Result()
    log.Println(count, err)

    //删除
    ret, err = redisdb.SRem("set_test", "11", "22").Result()
    log.Println(ret, err)

    //成员
    members, err := redisdb.SMembers("set_test").Result()
    log.Println(members, err)

    bret, err := redisdb.SIsMember("set_test", "33").Result()
    log.Println(bret, err)

    redisdb.SAdd("set_a", "11", "22", "33", "44")
    redisdb.SAdd("set_b", "11", "22", "33", "55", "66", "77")
    //差集
    diff, err := redisdb.SDiff("set_a", "set_b").Result()
    log.Println(diff, err)

    //交集
    inter, err := redisdb.SInter("set_a", "set_b").Result()
    log.Println(inter, err)

    //并集
    union, err := redisdb.SUnion("set_a", "set_b").Result()
    log.Println(union, err)

    ret, err = redisdb.SDiffStore("set_diff", "set_a", "set_b").Result()
    log.Println(ret, err)

    rets, err := redisdb.SMembers("set_diff").Result()
    log.Println(rets, err)
}

func ExampleClient_SortSet() {
    log.Println("ExampleClient_SortSet")
    defer log.Println("ExampleClient_SortSet")

    addArgs := make([]redis.Z, 100)
    for i := 1; i < 100; i++ {
        addArgs = append(addArgs, redis.Z{Score: float64(i), Member: fmt.Sprintf("a_%d", i)})
    }
    //log.Println(addArgs)

    Shuffle := func(slice []redis.Z) {
        r := rand.New(rand.NewSource(time.Now().Unix()))
        for len(slice) > 0 {
            n := len(slice)
            randIndex := r.Intn(n)
            slice[n-1], slice[randIndex] = slice[randIndex], slice[n-1]
            slice = slice[:n-1]
        }
    }

    //随机打乱
    Shuffle(addArgs)

    //添加
    ret, err := redisdb.ZAddNX("sortset_test", addArgs...).Result()
    log.Println(ret, err)

    //获取指定成员score
    score, err := redisdb.ZScore("sortset_test", "a_10").Result()
    log.Println(score, err)

    //获取制定成员的索引
    index, err := redisdb.ZRank("sortset_test", "a_50").Result()
    log.Println(index, err)

    count, err := redisdb.SCard("sortset_test").Result()
    log.Println(count, err)

    //返回有序集合指定区间内的成员
    rets, err := redisdb.ZRange("sortset_test", 10, 20).Result()
    log.Println(rets, err)

    //返回有序集合指定区间内的成员分数从高到低
    rets, err = redisdb.ZRevRange("sortset_test", 10, 20).Result()
    log.Println(rets, err)

    //指定分数区间的成员列表
    rets, err = redisdb.ZRangeByScore("sortset_test", redis.ZRangeBy{Min: "(30", Max: "(50", Offset: 1, Count: 10}).Result()
    log.Println(rets, err)
}

//用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定 的、并且是很小的。
//每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数
func ExampleClient_HyperLogLog() {
    log.Println("ExampleClient_HyperLogLog")
    defer log.Println("ExampleClient_HyperLogLog")

    for i := 0; i < 10000; i++ {
        redisdb.PFAdd("pf_test_1", fmt.Sprintf("pfkey%d", i))
    }
    ret, err := redisdb.PFCount("pf_test_1").Result()
    log.Println(ret, err)

    for i := 0; i < 10000; i++ {
        redisdb.PFAdd("pf_test_2", fmt.Sprintf("pfkey%d", i))
    }
    ret, err = redisdb.PFCount("pf_test_2").Result()
    log.Println(ret, err)

    redisdb.PFMerge("pf_test", "pf_test_2", "pf_test_1")
    ret, err = redisdb.PFCount("pf_test").Result()
    log.Println(ret, err)
}

func ExampleClient_PubSub() {
    log.Println("ExampleClient_PubSub")
    defer log.Println("ExampleClient_PubSub")
    //发布订阅
    pubsub := redisdb.Subscribe("subkey")
    _, err := pubsub.Receive()
    if err != nil {
        log.Fatal("pubsub.Receive")
    }
    ch := pubsub.Channel()
    time.AfterFunc(1*time.Second, func() {
        log.Println("Publish")

        err = redisdb.Publish("subkey", "test publish 1").Err()
        if err != nil {
            log.Fatal("redisdb.Publish", err)
        }

        redisdb.Publish("subkey", "test publish 2")
    })
    for msg := range ch {
        log.Println("recv channel:", msg.Channel, msg.Pattern, msg.Payload)
    }
}

func ExampleClient_CMD() {
    log.Println("ExampleClient_CMD")
    defer log.Println("ExampleClient_CMD")

    //执行自定义redis命令
    Get := func(rdb *redis.Client, key string) *redis.StringCmd {
        cmd := redis.NewStringCmd("get", key)
        redisdb.Process(cmd)
        return cmd
    }

    v, err := Get(redisdb, "NewStringCmd").Result()
    log.Println("NewStringCmd", v, err)

    v, err = redisdb.Do("get", "redisdb.do").String()
    log.Println("redisdb.Do", v, err)
}

func ExampleClient_Scan() {
    log.Println("ExampleClient_Scan")
    defer log.Println("ExampleClient_Scan")

    //scan
    for i := 1; i < 1000; i++ {
        redisdb.Set(fmt.Sprintf("skey_%d", i), i, 0)
    }

    cusor := uint64(0)
    for {
        keys, retCusor, err := redisdb.Scan(cusor, "skey_*", int64(100)).Result()
        log.Println(keys, cusor, err)
        cusor = retCusor
        if cusor == 0 {
            break
        }
    }
}

func ExampleClient_Tx() {
    pipe := redisdb.TxPipeline()
    incr := pipe.Incr("tx_pipeline_counter")
    pipe.Expire("tx_pipeline_counter", time.Hour)

    // Execute
    //
    //     MULTI
    //     INCR pipeline_counter
    //     EXPIRE pipeline_counts 3600
    //     EXEC
    //
    // using one rdb-server roundtrip.
    _, err := pipe.Exec()
    fmt.Println(incr.Val(), err)
}

func ExampleClient_Script() {
    IncrByXX := redis.NewScript(`
        if redis.call("GET", KEYS[1]) ~= false then
            return redis.call("INCRBY", KEYS[1], ARGV[1])
        end
        return false
    `)

    n, err := IncrByXX.Run(redisdb, []string{"xx_counter"}, 2).Result()
    fmt.Println(n, err)

    err = redisdb.Set("xx_counter", "40", 0).Err()
    if err != nil {
        panic(err)
    }

    n, err = IncrByXX.Run(redisdb, []string{"xx_counter"}, 2).Result()
    fmt.Println(n, err)
}

2.27 - 支持RabbitMQ的AMQP

package main

import (
    "log"

    "github.com/streadway/amqp"
)

func failOnError(err error, msg string) {
    if err != nil {
        log.Fatalf("%s: %s", msg, err)
    }
}

func main() {
    // 连接 RabbitMQ
    conn, err := amqp.Dial("amqp://admin:admin@127.0.0.1:5673")
    failOnError(err, "连接失败")
    defer conn.Close()

    // 建立一个 channel ( 其实就是TCP连接 )
    ch, err := conn.Channel()
    failOnError(err, "打开通道失败")
    defer ch.Close()

    // 创建一个名字叫 "hello" 的队列
    q, err := ch.QueueDeclare(
        "hello", // name
        false,   // durable
        false,   // delete when unused
        false,   // exclusive
        false,   // no-wait
        nil,     // arguments
    )
    failOnError(err, "创建队列失败")

    // 构建一个消息
    body := "Hello World!"
    msg := amqp.Publishing{
        ContentType: "text/plain",
        Body:        []byte(body),
    }

    // 构建一个生产者,将消息 放入队列
    err = ch.Publish(
        "",     // exchange
        q.Name, // routing key
        false,  // mandatory
        false,  // immediate
        msg)
    log.Printf(" [x] Sent %s", body)
    failOnError(err, "Failed to publish a message")
}

3 - 31个!Golang常用工具

一、G0官方工具

(一)go get

该命令可以根据要求和实际情况从互联网上下载或更新指定的代码包及其依赖包,下载后自动编译,一般引用依赖用go get就可以了。 参考:

go get -u "github.com/VictoriaMetrics/fastcache"

参考: https://www.kancloud.cn/cattong/go_command_tutorial/261349

(二)go build

该命令用于编译我们指定的源码文件或代码包以及它们的依赖包。命令的常用标记说明如下:

图片

编译过程输出到文件:go build -x > result 2>&1,因为go build -x 最终是将日志写到标准错误流当中。

如果只在编译特定包时需要指定参数,可以参考包名=参数列表的格式,比如go build -gcflags=‘log=-N -l’ main.go

参考: https://www.kancloud.cn/cattong/go_command_tutorial/261347

(三)go install

该命令用于编译并安装指定的代码包及它们的依赖包。当指定的代码包的依赖包还没有被编译和安装时,该命令会先去处理依赖包。与go build命令一样,传给go install命令的代码包参数应该以导入路径的形式提供。

并且,go build命令的绝大多数标记也都可以用于go install命令。实际上,go install命令只比go build命令多做了一件事,即:安装编译后的结果文件到指定目录。

参考:

https://www.kancloud.cn/cattong/go_command_tutorial/261348

(四)go fmt和gofmt

Golang的开发团队制定了统一的官方代码风格,并且推出了gofmt工具(gofmt或go fmt)来帮助开发者格式化他们的代码到统一的风格。

gofmt是一个cli程序,会优先读取标准输入,如果传入了文件路径的话,会格式化这个文件,如果传入一个目录,会格式化目录中所有.go文件,如果不传参数,会格式化当前目录下的所有.go文件。

gofmt默认不对代码进行简化,使用-s参数可以开启简化代码功能

gofmt是一个独立的cli程序,而go中还有一个go fmt命令,go fmt命令是gofmt的简单封装。go fmt在调用gofmt时添加了-l -w参数,相当于执行了gofmt -l -w

参考:

https://blog.csdn.net/whatday/article/details/97682094

(五)go env

该命令用于打印Go语言的环境信息,常见的通用环境信息如下:

图片

设置或修改环境变量值:

go env -w GOPROXY="https://goproxy.com,direct"

参考:

https://www.kancloud.cn/cattong/go_command_tutorial/261359

(六)go run

该命令可以运行命令源码文件,只能接受一个命令源码文件以及若干个库源码文件(必须同属于main包)作为文件参数,且不能接受测试源码文件。它在执行时会检查源码文件的类型。如果参数中有多个或者没有命令源码文件,那么go run命令就只会打印错误提示信息并退出,而不会继续执行。

在通过参数检查后,go run命令会将编译参数中的命令源码文件,并把编译后的可执行文件存放到临时工作目录中。

参考:

https://www.kancloud.cn/cattong/go_command_tutorial/261352

(七)go test

该命令用于对Go语言编写的程序进行测试,这种测试是以代码包为单位的,命令会自动测试每一个指定的代码包。当然,前提是指定的代码包中存在测试源码文件。

参考:

https://www.kancloud.cn/cattong/go_command_tutorial/261353

(八)go clean

该命令会删除掉执行其它命令时产生的一些文件和目录。

参考:

https://www.kancloud.cn/cattong/go_command_tutorial/261350

(九)go list

该命令的作用是列出指定的代码包的信息。与其他命令相同,我们需要以代码包导入路径的方式给定代码包。被给定的代码包可以有多个。这些代码包对应的目录中必须直接保存有Go语言源码文件,其子目录中的文件不算在内。

标记-e的作用是以容错模式加载和分析指定的代码包。在这种情况下,命令程序如果在加载或分析的过程中遇到错误只会在内部记录一下,而不会直接把错误信息打印出来。

为了看到错误信息可以使用-json标记。这个标记的作用是把代码包的结构体实例用JSON的样式打印出来。-m标记可以打印出modules而不是package。

# cd yky-sys-backend/cmd/bidengine

参考:

https://www.kancloud.cn/cattong/go_command_tutorial/261354

(十)go mod xxx

  • go mod init

该命令初始化并写入一个新的go.mod至当前目录中,实际上是创建一个以当前目录为根的新模块。文件go.mod必须不存在。如果可能,init会从import注释(参阅“go help importpath”)或从版本控制配置猜测模块路径。要覆盖此猜测,提供模块路径作为参数 module为当前项目名。比如:

go mod init demo

参考:

https://www.jianshu.com/p/f6d2d6db2bca

  • go mod tidy

该命令确保go.mod与模块中的源代码一致。它添加构建当前模块的包和依赖所必须的任何缺少的模块,删除不提供任何有价值的包的未使用的模块。它也会添加任何缺少的条目至go.mod并删除任何不需要的条目。

参考:

https://www.jianshu.com/p/f6d2d6db2bca

  • go mod vendor

该命令重置主模块的vendor目录,使其包含构建和测试所有主模块的包所需要的所有包。不包括vendor中的包的测试代码。即将GOPATH或GOROOT下载的包拷贝到项目下的vendor目录,如果不使用vendor隔离项目的依赖,则不需要使用该命令拷贝依赖。

参考:

https://www.jianshu.com/p/f6d2d6db2bca

  • go mod download

该命令下载指定名字的模块,可为选择主模块依赖的模块匹配模式,或path@version形式的模块查询。如果download不带参数则代表是主模块的所有依赖。download的只会下载依赖,不会编译依赖,和get是有区别的。

参考:

https://www.jianshu.com/p/f6d2d6db2bca

  • go mod edit

该命令提供一个编辑go.mod的命令行接口,主要提供给工具或脚本使用。它只读取go.mod;不查找涉及模块的信息。默认情况下,edit读写主模块的go.mod文件,但也可以在标志后指定不同的目标文件。

参考:

https://www.jianshu.com/p/f6d2d6db2bca

  • go mod graph

该命令以文本形式打印模块间的依赖关系图。输出的每一行行有两个字段(通过空格分割);模块和其所有依赖中的一个。每个模块都被标记为path@version形式的字符串(除了主模块,因其没有@version后缀)。

参考:

https://www.jianshu.com/p/f6d2d6db2bca

  • go mod verify

该命令查存储在本地下载源代码缓存中的当前模块的依赖,是否自从下载之后未被修改。如果所有模块都未被修改,打印“all modules verified”。否则,报告哪个模块已经被修改并令“go mod”以非0状态退出。

参考:

https://www.jianshu.com/p/f6d2d6db2bca

  • go mod why

该命令输出每个包或者模块的引用块,每个块以注释行“# package”或“# module”开头,给出目标包或模块。随后的行通过导入图给出路径,一个包一行。每个块之间通过一个空行分割,如果包或模块没有被主模块引用,该小节将显示单独一个带圆括号的提示信息来表明该事实。

参考:

https://www.jianshu.com/p/f6d2d6db2bca

(十一)go tool xxx

go tool的可执行文件在GOROOT或GOPATH的pkg/tool目录。go doc cmd可以查看具体cmd的使用说明,比如:

go doc pprof
  • go tool pprof

在Golang中,可以通过pprof工具对应于程序的运行时进行性能分析,包括CPU、内存、Goroutine等实时信息。

参考:

https://www.kancloud.cn/cattong/go_command_tutorial/261357

  • go tool trace

该命令可以追踪请求链路,清晰的了解整个程序的调用栈,可以通过追踪器捕获大量信息。

参考:

https://zhuanlan.zhihu.com/p/410590497

  • go tool compile

该命令可以编译Go文件生成汇编代码,-N参数表示禁止编译优化, -l表示禁止内联,-S表示打印汇编,比如

# 会生成main.o的汇编文件
  • go tool vet和go vet

该命令是一个用于检查Go语言源码中静态错误的简单工具。

go vet命令是go tool vet命令的简单封装。它会首先载入和分析指定的代码包,并把指定代码包中的所有Go语言源码文件和以“.s”结尾的文件的相对路径作为参数传递给go tool vet命令。其中,以“.s”结尾的文件是汇编语言的源码文件。如果go vet命令的参数是Go语言源码文件的路径,则会直接将这些参数传递给go tool vet命令。

参考:

https://www.kancloud.cn/cattong/go_command_tutorial/261356

  • go tool doc和go doc

该命令可以打印附于Go语言程序实体上的文档。我们可以通过把程序实体的标识符作为该命令的参数来达到查看其文档的目的。

所谓Go语言的程序实体,是指变量、常量、函数、结构体以及接口。而程序实体的标识符即是代表它们的名称。

参考:

https://www.kancloud.cn/cattong/go_command_tutorial/261351

  • go tool addr2line

该命令可以调用栈的地址转化为文件和行号。

Usage:
  • go tool asm

该命令可以将汇编文件编译成一个.o文件,后续这个.o文件可以用于生成.a归档文件,命令的file参数必须是汇编文件。

Usage:
  • go tool buildid

每一个 Go 二进制文件内,都有一个独一无二的 Build ID,详情参考 src/cmd/go/internal/work/buildid.go 。Go Build ID 可以用以下命令来查看:

go tool buildid

参考:

https://www.anquanke.com/post/id/215419

  • go tool cgo

该命令可以使我们创建能够调用C语言代码的Go语言源码文件。这使得我们可以使用Go语言代码去封装一些C语言的代码库,并提供给Go语言代码或项目使用。

参考:

https://www.kancloud.cn/cattong/go_command_tutorial/261358

  • go tool cover

该命令对单元测试过程中生成的代码覆盖率统计生成html文件,可以本地打开展示。

go test -coverprofile=a.out

覆盖度工具不仅可以记录分支是否被执行,还可以记录分支被执行了多少次。

go test -covermode=set|count|atomic: -covermode:

set: 默认模式,统计是否执行

count: 计数

atomic: count的并发安全版本,仅当需要精确统计时使用

通过go tool cover -func=count.out查看每个函数的覆盖度。

参考:

https://blog.csdn.net/xhdxhdxhd/article/details/120424848

  • go tool dist

dist工具是属于go的一个引导工具,它负责构建C程序(如Go编译器)和Go工具的初始引导副本。它也可以作为一个包罗万象用shell脚本替换以前完成的零工。通过“go tool dist”命令可以操作该工具。

go tool dist

使用go tool dist list可以输出当前安装Go版本所支持的操作系统与架构。

参考:https://blog.csdn.net/byxiaoyuonly/article/details/112492264

  • go tool fix和go fix

该命令会把指定代码包的所有Go语言源码文件中的旧版本代码修正为新版本的代码。这里所说的版本即Go语言的版本。代码包的所有Go语言源码文件不包括其子代码包(如果有的话)中的文件。修正操作包括把对旧程序调用的代码更换为对新程序调用的代码、把旧的语法更换为新的语法等等。

这个工具其实非常有用。在编程语言的升级和演进的过程中,难免会对过时的和不够优秀的语法及标准库进行改进。

参考:

https://www.kancloud.cn/cattong/go_command_tutorial/261355

该命令链接Go的归档文件比如静态库,以及链接其所有依赖,生成一个可执行文件(含main package)。

go tool link [flags] main.a

参考:

http://cache.baiducontent.com/

  • go tool nm

该命令可以查看符号表的命令,等同于系统的nm命令,非常有用。在断点的时候,如果你不知道断点的函数符号,那么用这个命令查一下就知道了(命令处理的是二进制程序文件),第一列是地址,第二列是类型,第三列是符号。等同于nm命令。

参考:

https://studygolang.com/articles/29906

  • go tool objdump

该命令可以反汇编二进制的工具,等同于系统objdump,命令解析的是二进制格式的程序文件。

go tool objdump example.o

参考:

https://studygolang.com/articles/29906

  • go tool pack

该命令把二进制文件打包成静态库。

go tool pack op file.a [name...]

参考:

http://cache.baiducontent.com/

  • go tool test2json

该命令用于把测试可执行文件转化可读的json格式。

// 生成测试文件的可执行文件

参考:

https://blog.csdn.net/weixin_33772442/article/details/112098085

  • godoc

该命令可以生成一个Go官方文档的本地http服务,可以在线查看标准库和第三方库文档,以及项目文档,但是需要按照一定的格式去写注释。

// 安装godoc

Web页面如下:

图片

参考:https://www.fujieace.com/golang/godoc.html

二、第三方工具

Go工具和组件汇总项目:

https://github.com/avelino/awesome-go

(一)delve

本地代码调试工具

参考:https://github.com/go-delve/delve

(二)goconvey

goconvey是一款针对Golang的测试框架,可以管理和运行测试用例,同时提供了丰富的断言函数,并支持很多Web界面特性。

参考:https://github.com/smartystreets/goconvey

(三)goleak

本地排查内存泄露的工具

参考:https://github.com/uber-go/goleak

(四)go-wrk

Go接口压测工具

参考:https://github.com/adjust/go-wrk

(五)golint

代码风格检查

参考:https://github.com/golang/lint

(六)revive

代码风格检查,比golint速度更快

参考:https://github.com/mgechev/revive

(七)gocode

代码自动补全工具,可以在vim中使用

参考:https://github.com/nsf/gocode

(八)godoctor

代码重构工具

参考:https://github.com/godoctor/godoctor

(九)gops

查看go进程和相关信息的工具,用于诊断线上服务。

参考:https://github.com/google/gops

(十)goreplay

GoReplay是一个开源网络监控工具,可以将实时HTTP流量捕获并重放到测试环境。

参考:https://github.com/buger/goreplay

https://blog.51cto.com/axzxs/5102596

(十一)depth

一个有用的Golang工具,Depth可帮助Web开发人员检索和可视化Go源代码依赖关系树。它可以用作独立的命令行应用程序或作为项目中的特定包。你可以通过在解析之前在Tree上设置相应的标志来添加自定义。

参考:https://github.com/KyleBanks/depth

(十二)go-swagger

该工具包包括各种功能和功能。Go-Swagger是Swagger 2.0的一个实现,可以序列化和反序列化swagger规范。它是RESTful API简约但强大的代表。

通过Go-Swagger,你可以swagger规范文档,验证JSON模式以及其他额外的规则。其他功能包括代码生成,基于swagger规范的API生成,基于代码的规范文档生成,扩展了的字符串格式,等等。

参考:https://github.com/go-swagger/go-swagger

(十三)gox

交叉编译工具,可以并行编译多个平台。

参考:https://github.com/mitchellh/gox

(十四)gocyclo

gocyclo用来检查函数的复杂度。

# 列出了所有复杂度大于20的函数

参考:https://github.com/fzipp/gocyclo

(十五)deadcode

deadcode会告诉你哪些代码片段根本没用。

find . -type d -not -path "./vendor/*" | xargs deadcode

参考:https://github.com/tsenart/deadcode

(十六)gotype

gotype会对go文件和包进行语义(semantic)和句法(syntactic)的分析,这是google提供的一个工具。

find . -name "*.go" -not -path "./vendor/*" -not -path ".git/*" -print | xargs gotype -a

参考:https://golang.org/x/tools/cmd/gotype

(十七)misspell

misspell用来拼写检查,对国内英语不太熟练的同学很有帮助。

find . -type f -not -path "./vendor/*" -print | xargs misspell

参考:https://github.com/client9/misspell

(十八)staticcheck

staticcheck是一个超牛的工具,提供了巨多的静态检查,就像C#生态圈的 ReSharper一样。

# 安装:

参考:

https://staticcheck.io/docs/

https://github.com/dominikh/go-tools/tree/master/staticcheck

(十九)goconst

goconst会查找重复的字符串,这些字符串可以抽取成常量。

goconst ./… | grep -v vendor

参考:https://github.com/jgautheron/goconst

参考资料:

1.Go命令教程:

https://www.kancloud.cn/cattong/go_command_tutorial/261351

2.Golang指南:顶级Golang框架、IDE和工具列表:

https://zhuanlan.zhihu.com/p/30432648

3.Go代码检修工具集:

http://t.zoukankan.com/binHome-p-14149941.html

4 - GO基础

go语言不需要写分号

1. 基本程序结构

package 包名
import ""
func main(){
    //code
}

2. 导包方式

2.1单独导入

import ""

2.2多包导入方式

import(
    ""
    ""
)

3. 变量类型

3.1 普通变量类型

bool
string
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
byte // alias for uint8
rune // alias for int32
// represents a Unicode code point
float32 float64
complex64 complex128

3.2 衍生数据类型

Array types 数组
Structure types 结构体
Union types 联合
Slice types 切片,引用类型
Interface types 接口
Map types 字典,引用类型
Channel Types 通道,引用类型
Slice, Map, Channel 这三种是引用数据类型, 使用需要make

3.3 golang支持自定义数据类型–类似于别名

type flag byte

4. 变量定义以及赋值

var 变量1, 变量2 变量类型\

例如

var x, y int
var x, y int = 2, 3
var (
    x,y int
    a,s = 100, "abc"
)

缺省赋值只能用在函数内部 \

x, y := 2, 3 // 会自动类型推断

5. 常量定义以及赋值

常量可以只定义不使用
常量不能读地址 const 常量名 数据类型 = 值

const variable type = value
const name, size = "carrot", 100
const(
    var1 type = value
    var2 type = value
    var3 = value // 也可以自动类型推断
)

//这种情况下, BBB和CCC的值也是120
const(
    AAA = 120
    BBB
    CCC
)

// Golang中没有枚举类型,但是可以使用iota来更优雅的定义一个枚举
// iota从0开始递增,步长1

const(
    Low = 5 * iota //Low = 0
    Medium         //Medium = 5
    High           //High = 10
)
//iota可以被中断
const(
    a = iota  //a = 0,默认值
    b         //1
    c         //2
    d = 100   //100
    e         //100
    f = iota  //5
)

6. 变量 类型转换—必须使用显式类型转换

a := 1
b := byte(a)
// go中不能将非布尔类型的类型转换成布尔类型, 也不能作为布尔类型来使用

7. 流程控制 – GO语言推荐不适用圆括号

关键字展示

  1. if
  2. if else
  3. if (){}else if(){} else{}
  4. switch
  5. for
  6. goto break contunue
  7. 没有while!没有while!没有while! 重要的事情说三遍

7.1 if

if a > 1 {
    //code
}

7.2 if else

if a > 1 {//左花括号不能换行
    //code
} else { //else关键字不能换行,左花括号不能换行
    //code
}

7.3 if (){}else if(){} else

if a > 1 {
    //code
} else if a > 2 {
    //code
} else {
    //code
}

7.4 switch

//可以不用写break,go语言执行完一个case后默认break
//default也可以不写在最后, 但是建议还是写最后
//如果不想执行完一个case后就被break可以使用fallthrough
func main() {
    a, b, c, x := 1, 2, 3, 2
    switch x{
    case a, b:
        fmt.Println("a|b")
        fallthrough // 这样就不会被退出了
    case c:
        fmt.Println("c")
    default:
        fmt.Println("default")
    }
}

7.5 for

//普通的for
for i := 0; i<max; i++ {

}

//迭代列表和切片
date := [4]string{"a","b",c}
for i str := range date {
    fmt.Println(i, str)
}

//迭代mqp
for k, v := range map {
    fmt.Println(k,v)
}

// 死循环
for {
    //code
}

7.6 函数

// 1. 不支持内部定义函数,但是可以定义匿名函数
func main() {
    func swap(x, y string) (string, string) {
    return y, x
    }
    a, b := swap("hello", "world")
    fmt.Println(a, b)
}
// 错误:syntax error: unexpected swap, expecting (

// 2. 匿名函数, 用法有点像js
func main() {
    func (s string) {
        fmt.Println(s)
    } ("hello, go!")
}
// 3. 函数可以返回多个值, 参数也可以是可变参数
func fun_name (str string, a ...int) int {}
func fun_name (str string, a ...int) (x, y int) {}
// 4. 函数参数没有默认值即使是 下划线开头 func fun_name (str string, a int, _boo bool)

// 5. 闭包的写法, 返回一个函数的函数就是闭包闭包内的局部变量是自我维护的
func intSeq() func() int {
    i := 0
    return func() int {
        i += 1
        return i
    }
}
func main(){
    nextInt := intSeq()
    nextInt()
    nextInt()
    nextInt()
}

5 - 安装

#!/bin/bash

# 安装go
wget https://mirrors.aliyun.com/golang/go${GOLANGVERSION}.linux-amd64.tar.gz?spm=a2c6h.25603864.0.0.a6b07c45FOi9wZ -O /opt/go${GOLANGVERSION}.linux-amd64.tar.gz && cd /opt && rm -rf go${GOLANGVERSION} && \
mkdir go${GOLANGVERSION} &&  \
tar -zxf go${GOLANGVERSION}.linux-amd64.tar.gz -C go${GOLANGVERSION} && \
rm -f go${GOLANGVERSION}.linux-amd64.tar.gz && \
echo "export GOROOT=/opt/go${GOLANGVERSION}/go" >>  /etc/profile &&  \
echo 'export GOPATH=/root/go' >>  /etc/profile && \
echo 'PATH=$PATH:$GOROOT/bin:$GOPATH/bin' >> /etc/profile && \
source /etc/profile && \
go env -w GO111MODULE=on && \
go env -w GOPROXY=https://goproxy.cn,direct && \
# 本地k8s测试工具
go install sigs.k8s.io/kind@latest && kind --version && \
# 漏洞检测工具
go install golang.org/x/vuln/cmd/govulncheck@latest && \
# protoc编译器
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest && \
# grpc
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest && \
# gateway
go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@latest && \
# openapi
go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2@latest && \
# 安装delve,用于debug go代码
go install github.com/go-delve/delve/cmd/dlv@latest && \
# 安装wire自动注入工具
go install github.com/google/wire/cmd/wire@latest

6 - 报错解决

  • 报错描述
For more information, see 'go help module-auth'.
gateway2-http-command-async/cmd/appservice imports
        github.com/edgexfoundry/app-functions-sdk-go/v2/pkg/transforms imports
        github.com/edgexfoundry/go-mod-core-contracts/v2/common: github.com/edgexfoundry/go-mod-core-contracts/v2@v2.1.0: verifying module: checksum mismatch
        downloaded: h1:QIb6pcSYxL9F4TDnZOZk7+USnZ8Nivz1hHxmoAFNPVM=
        sum.golang.org: h1:uphot3ZKOH0/aoo/Y5gr2NCRgGzy9RksWsXKtJRVEuQ=

SECURITY ERROR
This download does NOT match the one reported by the checksum server.
The bits may have been replaced on the origin server, or an attacker may
have intercepted the download attempt.

方案: 考虑换goproxy解决,有可能时代理中存储的签名和sum.golang.org存储的签名不同

  • 报错描述

在go module 项目中, 全局配置过 GOPROXY=“https://goproxy.io” 此时如果要用go get github.com/xxx/xxx 的包是完全可以导入的(实际是从 https://goproxy.io/github.com/xxx/xxx 来下载包的

但是如果要导入的包是私有仓库比如 gitpack.xxx.cn/myutil/help 实际是从 https://goproxy.io/gitpack.gitpack.xxx.cn/myutil/help 这时一定是返回http 404错误的 报错如下

If your Go version >= 1.13, the GOPRIVATE environment variable controls which modules the go command considers to be private (not available publicly) and should therefore not use the proxy or checksum database.

要导入私有git仓库的包需要设置go 环境变量GOPRIVATE go env -w GOPRIVATE=".xxx.cn" 或者直接设置环境变量GOPRIVATE=.xxx.cn 再用 go get -u gitpack.xxx.cn/myutil/help@分支名(标签名) 就可以成功导入私有包了