前言
在使用 go 语言进行开发时,经常会遇到一些常见的编程任务和问题。为了提高开发效率和代码质量,造轮子是很常见的行为,使用轮子能提高我们的开发效率和代码复用率。
这篇文章主要汇总一下我在开发过程中自己写的一些轮子,供大家参考使用。
JWT
JWT(JSON Web Token, Json Web 令牌) 是一个开放标准,定义了一种紧凑的、自包含的方式,用于在各方之间以 Json 对象安全地传输信息。此信息可以验证和信任,因为它是数字签名过的。常在 WEB 应用中被用于身份认证。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 package jwtimport ( "fmt" "github.com/dgrijalva/jwt-go" "time" ) type SessionClaims struct { *jwt.StandardClaims Tag any `json:"tag"` } func NewAuthorization (tag any, secret []byte , expireTime ...int64 ) (string , error ) { iat := time.Now().UTC() exp := iat.AddDate(0 , 0 , 7 ) claims := SessionClaims{ Tag: tag, StandardClaims: &jwt.StandardClaims{ ExpiresAt: exp.Unix(), IssuedAt: iat.Unix(), }, } if len (expireTime) != 0 { claims.StandardClaims.ExpiresAt = expireTime[0 ] } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) jwtToken, err := token.SignedString(secret) if err != nil { return "" , err } return jwtToken, nil } func ParseAuthorization (jwtToken string , secret []byte ) (*SessionClaims, error ) { var uploadSessionClaims SessionClaims parsed, err := jwt.ParseWithClaims( jwtToken, &uploadSessionClaims, func (token *jwt.Token) (interface {}, error ) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil , fmt.Errorf("unexpected signing method: %v" , token.Header["alg" ]) } return secret, nil }, ) if err != nil { return nil , err } _, ok := parsed.Claims.(*SessionClaims) if !ok || !parsed.Valid { return nil , err } return &uploadSessionClaims, nil }
goroutine
在 Go 语言中,可以使用 go 关键字轻松启动一个 goroutine。然而,通过这种方式启动的协程如果内部逻辑存在漏洞,并且未捕获运行异常导致 panic,可能会影响整个应用程序,使其退出。
Go 作为一门服务端语言,常见的应用场景是提供各种服务,因此应用程序运行时的异常退出是我们必须极力避免的。目前主流的 web 框架通常都带有 panic 捕获的中间件,这样在处理 HTTP 请求的协程中出现 panic 时,不会导致整个应用程序崩溃。然而,如果在处理请求的协程中再次启动新的协程,这些新协程的 panic 是无法被捕获的。
因此,有必要对 go 关键字进行封装,增加 panic 捕获功能,确保协程异常不会影响整个应用程序。以下这个包正是为此而设计的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 package goroutineimport ( "fmt" "log" "runtime" "strings" ) func Go (f func () ) { go func () { defer func () { if r := recover (); r != nil { log.Printf("panic info: [ %s ]-[ %v ] \n" , identifyPanic(), r) } }() f() }() } func identifyPanic () string { var name, file string var line int var pc [16 ]uintptr n := runtime.Callers(3 , pc[:]) for _, pc := range pc[:n] { fn := runtime.FuncForPC(pc) if fn == nil { continue } file, line = fn.FileLine(pc) name = fn.Name() if !strings.HasPrefix(name, "runtime." ) { break } } switch { case name != "" : return fmt.Sprintf("%v:%v" , name, line) case file != "" : return fmt.Sprintf("%v:%v" , file, line) } return fmt.Sprintf("pc:%x" , pc) }
随机字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 package stringutilimport ( "math/rand" "strings" "time" ) const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" const numberBytes = "0123456789" const letterAndNumberBytes = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" const ( letterIdxBits = 6 letterIdxMask = 1 <<letterIdxBits - 1 letterIdxMax = 63 / letterIdxBits numberIdxBits = 4 numberIdxMask = 1 <<numberIdxBits - 1 numberIdxMax = 63 / numberIdxBits ) var src = rand.NewSource(time.Now().UnixNano())func RandString (n int ) string { sb := strings.Builder{} sb.Grow(n) for i, cache, remain := n-1 , src.Int63(), letterIdxMax; i >= 0 ; { if remain == 0 { cache, remain = src.Int63(), letterIdxMax } aLen := len (letterBytes) if idx := int (cache & letterIdxMask); idx < aLen { sb.WriteByte(letterBytes[idx]) i-- } cache >>= letterIdxBits remain-- } return sb.String() } func RandStringWithNumber (n int ) string { sb := strings.Builder{} sb.Grow(n) arrLen := len (letterAndNumberBytes) for i, cache, remain := n-1 , src.Int63(), letterIdxMax; i >= 0 ; { if remain == 0 { cache, remain = src.Int63(), letterIdxMax } if idx := int (cache & letterIdxMask); idx < arrLen { sb.WriteByte(letterAndNumberBytes[idx]) i-- } cache >>= letterIdxBits remain-- } return sb.String() } func RandNumberString (n int ) string { sb := strings.Builder{} sb.Grow(n) for i, cache, remain := n-1 , src.Int63(), numberIdxMax; i >= 0 ; { if remain == 0 { cache, remain = src.Int63(), numberIdxMax } if idx := int (cache & numberIdxMask); idx < len (numberBytes) { sb.WriteByte(numberBytes[idx]) i-- } cache >>= numberIdxBits remain-- } return sb.String() } func NewUUID () string { return RandStringWithNumber(24 ) }
签名与加密
go 标准库实现了很多加密算法,以下包为这些库的二次封装,简化调用逻辑。
首先是 AES 加密,此处使用的是 PKCS#7 填充方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 package cryptimport ( "bytes" "crypto/aes" "crypto/cipher" "encoding/base64" ) func pKCS7Padding (text []byte , blockSize int ) []byte { padding := blockSize - len (text)%blockSize var paddingText []byte paddingText = bytes.Repeat([]byte {byte (padding)}, padding) return append (text, paddingText...) } func pKCS7UnPadding (text []byte ) []byte { length := len (text) unPadding := int (text[length-1 ]) return text[:(length - unPadding)] } func AesCBCEncrypt (text []byte , key []byte , iv []byte ) (string , error ) { block, err := aes.NewCipher(key) if err != nil { return "" , err } padText := pKCS7Padding(text, block.BlockSize()) blockMode := cipher.NewCBCEncrypter(block, iv) result := make ([]byte , len (padText)) blockMode.CryptBlocks(result, padText) return base64Encode(result), nil } func AesCBCDecrypt (ciphertext string , key []byte , iv []byte ) ([]byte , error ) { block, err := aes.NewCipher(key) if err != nil { return nil , err } blockMode := cipher.NewCBCDecrypter(block, iv) text, err := base64Decode(ciphertext) if err != nil { return nil , err } plaintext := make ([]byte , len (text)) blockMode.CryptBlocks(plaintext, text) plaintext = pKCS7UnPadding(plaintext) return plaintext, nil } func base64Encode (data []byte ) string { return base64.StdEncoding.EncodeToString(data) } func base64Decode (data string ) ([]byte , error ) { return base64.StdEncoding.DecodeString(data) }
其次是 RSA 加密,使用的是 PKCS#8
格式的秘钥文件和 PKCS#1 V1.5
版本的填充方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 package cryptimport ( "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/base64" "encoding/pem" "errors" "os" ) func RsaEncrypt (pubKey *rsa.PublicKey, data []byte ) (string , error ) { ciphertext, err := rsa.EncryptPKCS1v15(rand.Reader, pubKey, data) if err != nil { return "" , err } return base64.StdEncoding.EncodeToString(ciphertext), nil } func RsaDecrypt (priKey *rsa.PrivateKey, base64Ciphertext string ) ([]byte , error ) { ciphertext, err := base64.StdEncoding.DecodeString(base64Ciphertext) if err != nil { return nil , err } return rsa.DecryptPKCS1v15(rand.Reader, priKey, ciphertext) } func LoadPrivateKeyByPemFile (path string ) (*rsa.PrivateKey, error ) { privateKeyBytes, err := os.ReadFile(path) if err != nil { return nil , err } block, _ := pem.Decode(privateKeyBytes) if block == nil { return nil , errors.New("decode pem file failed" ) } key, err := x509.ParsePKCS8PrivateKey(block.Bytes) if err != nil { return nil , err } return key.(*rsa.PrivateKey), nil } func LoadPublicKeyByPemFile (path string ) (*rsa.PublicKey, error ) { publicKeyBytes, err := os.ReadFile(path) if err != nil { return nil , err } block, _ := pem.Decode(publicKeyBytes) if block == nil { return nil , errors.New("decode pem file failed" ) } publicKeyInterface, err := x509.ParsePKIXPublicKey(block.Bytes) if err != nil { return nil , err } return publicKeyInterface.(*rsa.PublicKey), nil }
哈希:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package cryptimport ( "crypto/hmac" "crypto/md5" "crypto/sha1" "crypto/sha256" "encoding/hex" ) func SHA256Encrypt (s string ) string { hash := sha256.Sum256([]byte (s)) return hex.EncodeToString(hash[:]) } func SHA1Encrypt (s string ) string { hash := sha1.Sum([]byte (s)) return hex.EncodeToString(hash[:]) } func HmacSha1Encode (key, data []byte ) string { mac := hmac.New(sha1.New, key) mac.Write(data) return hex.EncodeToString(mac.Sum(nil )) } func MD5Encrypt (s string ) string { m := md5.New() m.Write([]byte (s)) return hex.EncodeToString(m.Sum(nil )) }
字符串和 []byte 转换
高效完成 string 和 []byte 之间的转换,避免值拷贝。(go1.20+适用)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package convimport ( "unsafe" ) func StringToBytes (s string ) (b []byte ) { strPtr := unsafe.StringData(s) return unsafe.Slice(strPtr, len (s)) } func BytesToString (b []byte ) string { return unsafe.String(&b[0 ], len (b)) }
set
用 map 实现 set 结构。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 package setimport ( "sync" ) type Set[T comparable] struct { m map [T]struct {} `json:"m"` mt sync.Mutex } func New [T comparable ]() *Set[T] { return &Set[T]{ m: make (map [T]struct {}), } } func (s *Set[T]) Add(v T) { if s == nil { return } s.mt.Lock() defer s.mt.Unlock() s.m[v] = struct {}{} } func (s *Set[T]) Del(v T) { if s == nil { return } s.mt.Lock() defer s.mt.Unlock() delete (s.m, v) } func (s *Set[T]) Has(v T) (exist bool ) { if s == nil { return false } s.mt.Lock() defer s.mt.Unlock() _, exist = s.m[v] return } func (s *Set[T]) Length() int { if s == nil { return 0 } return len (s.m) } func (s *Set[T]) List() []T { if s == nil { return nil } res := make ([]T, 0 , len (s.m)) for v := range s.m { res = append (res, v) } return res } func (s *Set[T]) Empty() { if s == nil { return } s.m = map [T]struct {}{} return }
循环队列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 package queueimport ( "github.com/cockroachdb/errors" "sync" ) type CircularQueue[T any] struct { queue []T rdIdx int wrIdx int mt sync.Mutex } func NewCircular [T any ](len uint64 ) Queue[T] { return &CircularQueue[T]{ queue: make ([]T, len +1 ), } } func (cq *CircularQueue[T]) Push(val T) { cq.mt.Lock() defer cq.mt.Unlock() if cq.isFull() { cq.rdIdx++ } cq.queue[cq.wrIdx] = val cq.wrIdx = (cq.wrIdx + 1 ) % len (cq.queue) } func (cq *CircularQueue[T]) Pop() (T, error ) { cq.mt.Lock() defer cq.mt.Unlock() if cq.isEmpty() { return *new (T), errors.New("queue is empty" ) } val := cq.queue[cq.rdIdx] cq.rdIdx = (cq.rdIdx + 1 ) % len (cq.queue) return val, nil } func (cq *CircularQueue[T]) isEmpty() bool { return cq.rdIdx == cq.wrIdx } func (cq *CircularQueue[T]) isFull() bool { return (cq.wrIdx+1 )%len (cq.queue) == cq.rdIdx } func (cq *CircularQueue[T]) List() []T { cq.mt.Lock() defer cq.mt.Unlock() if cq.isEmpty() { return []T{} } if cq.rdIdx < cq.wrIdx { res := make ([]T, 0 , cq.wrIdx-cq.rdIdx) for i := cq.rdIdx; i < cq.wrIdx; i++ { res = append (res, cq.queue[i]) } return res } res := make ([]T, 0 , cq.wrIdx+cap (cq.queue)-cq.rdIdx) for i := cq.rdIdx; i < cap (cq.queue); i++ { res = append (res, cq.queue[i]) } for i := 0 ; i < cq.wrIdx; i++ { res = append (res, cq.queue[i]) } return res }
结语
以上是一些我在开发过程中常用的轮子,后续也会持续进行更新。也欢迎大家分享自己写的或经常用的轮子,让我们共同进步!