misc - 实用工具库
misc 是一个提供常用工具函数和类型的实用工具库,包含密码哈希、函数组合、MIME 类型处理、模板插值、泛型工具、数学函数等功能。最低支持 Go 1.21.0 版本。
安装
go get -u go-slim.dev/misc快速开始
package mainimport "go-slim.dev/misc"func main() { // 密码哈希 hash, _ := misc.PasswordHash("mypassword") ok := misc.PasswordVerify("mypassword", hash) // 模板替换 result, _ := misc.Substitute("Hello {name}!", map[string]any{"name": "World"}) // 泛型工具 value := misc.Coalesce("", "default", "fallback") // "default" // 数学函数 min, max := misc.MinMax(5, 3) // (3, 5)}核心功能
1. 密码哈希和摘要
密码哈希(Bcrypt)
使用 bcrypt 算法进行安全的密码哈希:
import "go-slim.dev/misc"// 生成密码哈希hash, err := misc.PasswordHash("mypassword")if err != nil { // 处理错误}// 验证密码ok := misc.PasswordVerify("mypassword", hash)if ok { // 密码正确} else { // 密码错误}特点:
- 使用 bcrypt.DefaultCost(当前为 10)
- 自动生成盐值
- 防止彩虹表攻击
- 计算成本可调整
快速哈希函数
用于数据完整性校验和非安全场景:
// MD5(不推荐用于安全场景)md5Hash := misc.MD5("hello world")// 返回大写十六进制: "5EB63BBBE01EEED093CB22BB8F5ACDC3"// SHA-1sha1Hash := misc.Sha1("hello world")// 返回小写十六进制: "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed"// SHA-256(推荐)sha256Hash := misc.Sha256("hello world")// 返回小写十六进制: "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"使用场景:
- 文件完整性校验
- 缓存键生成
- 数据指纹
- ETag 生成
注意:MD5 和 SHA-1 不应用于安全敏感场景(如密码存储),请使用 SHA-256 或 bcrypt。
2. 函数组合
Call - 顺序执行函数
按顺序执行多个函数,遇到错误立即返回:
import "go-slim.dev/misc"err := misc.Call( func() error { fmt.Println("步骤 1") return nil }, func() error { fmt.Println("步骤 2") return nil }, func() error { fmt.Println("步骤 3") return errors.New("失败") }, func() error { fmt.Println("步骤 4") // 不会执行 return nil },)// 输出: 步骤 1, 步骤 2, 步骤 3// err 为 "失败"CallG - 带参数的顺序执行
执行多个接收相同参数的函数:
type User struct { Name string Age int}user := &User{Name: "Alice", Age: 30}err := misc.CallG(user, func(u *User) error { fmt.Printf("验证用户: %s\n", u.Name) return nil }, func(u *User) error { fmt.Printf("保存用户: %s\n", u.Name) return db.Save(u) }, func(u *User) error { fmt.Printf("发送欢迎邮件给: %s\n", u.Name) return mailer.Send(u) },)Wrap 和 WrapG - 函数包装
将多个函数组合成一个可复用的函数:
// Wrap - 无参数initFunc := misc.Wrap( initDatabase, initCache, initLogger,)// 多次调用if err := initFunc(); err != nil { log.Fatal(err)}// WrapG - 带参数processUser := misc.WrapG( validateUser, saveUser, notifyUser,)// 多次使用for _, user := range users { if err := processUser(user); err != nil { log.Printf("处理用户失败: %v", err) }}3. 模板插值
Substitute - 简单模板替换
使用 {} 作为占位符:
// 基本使用result, err := misc.Substitute("Hello {name}!", map[string]any{ "name": "World",})// result: "Hello World!"// 多个占位符result, _ := misc.Substitute( "用户 {name} 的年龄是 {age}", map[string]any{ "name": "Alice", "age": 30, },)// result: "用户 Alice 的年龄是 30"// 未找到的占位符保持原样result, _ := misc.Substitute( "Hello {name}, {missing}", map[string]any{"name": "World"},)// result: "Hello World, {missing}"Interpolate - 自定义分隔符
使用自定义的起始和结束标记:
// 使用 {{ }} 分隔符result, _ := misc.Interpolate( "/user/{{ID}}/posts/{{PostID}}", "{{", "}}", map[string]any{ "ID": 123, "PostID": 456, },)// result: "/user/123/posts/456"// 使用 [[ ]] 分隔符result, _ := misc.Interpolate( "SELECT * FROM users WHERE id = [[id]]", "[[", "]]", map[string]any{"id": 42},)// result: "SELECT * FROM users WHERE id = 42"// 通配符 "*" 作为默认值result, _ := misc.Interpolate( "{greeting} {name}!", "{", "}", map[string]any{ "name": "Alice", "*": "未知", // 默认值 },)// result: "未知 Alice!"Tmpl - 高级模板处理
使用自定义的 TagFunc 处理占位符:
import ( "io" "strings" "go-slim.dev/misc")var output strings.Builder// 自定义标签处理函数tagFunc := func(w io.Writer, tag string) (int, error) { switch tag { case "date": return w.Write([]byte(time.Now().Format("2006-01-02"))) case "time": return w.Write([]byte(time.Now().Format("15:04:05"))) case "upper": // 可以访问上下文或执行复杂逻辑 return w.Write([]byte("HELLO")) default: return w.Write([]byte("未知标签")) }}n, err := misc.Tmpl( "日期: {date}, 时间: {time}, 消息: {upper}", "{", "}", &output, tagFunc,)fmt.Println(output.String())// 输出: 日期: 2024-01-15, 时间: 14:30:45, 消息: HELLO4. 泛型工具函数
Zero - 获取零值
返回任意类型的零值:
import "go-slim.dev/misc"zeroInt := misc.Zero[int]() // 0zeroStr := misc.Zero[string]() // ""zeroBool := misc.Zero[bool]() // falsezeroPtr := misc.Zero[*int]() // nilzeroSlice := misc.Zero[[]int]() // nilzeroMap := misc.Zero[map[string]int]() // nil// 在泛型函数中使用func GetOrDefault[T any](m map[string]T, key string) T { if v, ok := m[key]; ok { return v } return misc.Zero[T]()}Ptr - 创建指针
返回值的指针副本:
// 基本类型intPtr := misc.Ptr(42) // *intstrPtr := misc.Ptr("hello") // *stringboolPtr := misc.Ptr(true) // *bool// 在结构体字面量中使用type Config struct { Port *int Host *string Enabled *bool}cfg := Config{ Port: misc.Ptr(8080), Host: misc.Ptr("localhost"), Enabled: misc.Ptr(true),}// API 调用场景updateUser(&User{ ID: 123, Name: misc.Ptr("Alice"), // 只更新 Name 字段})Nil - 获取类型化 nil 指针
返回特定类型的 nil 指针:
var intPtr *int = misc.Nil[int]()var strPtr *string = misc.Nil[string]()var slicePtr *[]int = misc.Nil[[]int]()// 在泛型函数中使用func MaybeValue[T any](hasValue bool, value T) *T { if hasValue { return &value } return misc.Nil[T]()}IsZero - 零值判断
判断任意值是否为零值(支持深度检查):
misc.IsZero(0) // truemisc.IsZero("") // truemisc.IsZero(false) // truemisc.IsZero((*int)(nil)) // truemisc.IsZero([]int(nil)) // truemisc.IsZero(map[string]int(nil)) // truemisc.IsZero(42) // falsemisc.IsZero("hello") // falsemisc.IsZero(true) // falsemisc.IsZero(&User{}) // false(指针不为 nil)// 指针会递归解引用ptr := new(int) // *int 指向 0misc.IsZero(ptr) // true(解引用后值为 0)*ptr = 42misc.IsZero(ptr) // false(解引用后值为 42)IsNil - nil 判断
判断值是否为 nil(适用于引用类型):
misc.IsNil(nil) // truemisc.IsNil((*int)(nil)) // truemisc.IsNil([]int(nil)) // truemisc.IsNil(map[string]int(nil)) // truemisc.IsNil((chan int)(nil)) // truemisc.IsNil((func())(nil)) // truemisc.IsNil(0) // falsemisc.IsNil("") // falsemisc.IsNil(make([]int, 0)) // false(空切片,非 nil)misc.IsNil(make(map[string]int)) // false(空 map,非 nil)// 接口值var err error = nilmisc.IsNil(err) // trueerr = fmt.Errorf("error")misc.IsNil(err) // falseCoalesce - 取首个非零值
返回参数列表中第一个非零值:
// 字符串默认值name := misc.Coalesce("", "default", "fallback")// name = "default"// 整数默认值port := misc.Coalesce(0, 8080, 3000)// port = 8080// 配置优先级:命令行 > 环境变量 > 配置文件 > 默认值port := misc.Coalesce( cliPort, // 命令行参数 envPort, // 环境变量 configPort, // 配置文件 8080, // 默认值)// 用户输入回退username := misc.Coalesce( userInput, savedUsername, "guest",)// 全部为零值时返回零值result := misc.Coalesce[int]() // 0result := misc.Coalesce("", "", "") // ""5. 数学函数
MinMax - 同时获取最小和最大值
返回两个值的最小值和最大值:
import "go-slim.dev/misc"// 整数min, max := misc.MinMax(5, 3)// min = 3, max = 5// 浮点数min, max := misc.MinMax(1.5, 2.7)// min = 1.5, max = 2.7// 字符串(字典序)min, max := misc.MinMax("banana", "apple")// min = "apple", max = "banana"// 相等的值min, max := misc.MinMax(10, 10)// min = 10, max = 10// 实际应用func normalizeRange(a, b int) (start, end int) { return misc.MinMax(a, b)}start, end := normalizeRange(100, 50)// start = 50, end = 100Clamp - 限制值在范围内
将值限制在 [min, max] 范围内:
// 基本用法result := misc.Clamp(15, 10, 20) // 15(在范围内)result := misc.Clamp(5, 10, 20) // 10(低于最小值)result := misc.Clamp(25, 10, 20) // 20(高于最大值)// 自动处理边界反转result := misc.Clamp(15, 20, 10) // 15(自动将边界调整为 10, 20)// 浮点数opacity := misc.Clamp(1.5, 0.0, 1.0) // 1.0// 实际应用:限制百分比func setVolume(vol int) int { return misc.Clamp(vol, 0, 100)}// 限制颜色值func normalizeColor(c int) int { return misc.Clamp(c, 0, 255)}// 限制坐标x := misc.Clamp(mouseX, 0, screenWidth)y := misc.Clamp(mouseY, 0, screenHeight)6. 零拷贝转换
使用 unsafe 实现零拷贝的字符串和字节切片转换:
import "go-slim.dev/misc"// 字节切片转字符串bytes := []byte("hello world")str := misc.BytesToString(bytes)// 零拷贝转换,共享底层数据// 字符串转字节切片str := "hello world"bytes := misc.StringToBytes(str)// 零拷贝转换,共享底层数据⚠️ 重要警告:
- 不要修改源数据:转换后的值共享底层内存,修改会导致未定义行为
- 不要修改结果:
StringToBytes返回的切片是只读的 - 生命周期:确保源数据在使用结果期间保持有效
正确使用:
// ✅ 正确:只读使用bytes := []byte("hello")str := misc.BytesToString(bytes)fmt.Println(str) // 安全// ✅ 正确:临时使用if misc.BytesToString(data) == "expected" { // 安全}// ❌ 错误:修改源数据bytes := []byte("hello")str := misc.BytesToString(bytes)bytes[0] = 'H' // 未定义行为!str 可能会改变// ❌ 错误:修改结果str := "hello"bytes := misc.StringToBytes(str)bytes[0] = 'H' // 未定义行为!可能崩溃或损坏内存使用场景:
// 性能敏感的字符串比较func fastCompare(a []byte, b string) bool { return misc.BytesToString(a) == b}// HTTP 头部解析(只读)func parseHeader(line []byte) (key, value string) { parts := bytes.SplitN(line, []byte(":"), 2) return misc.BytesToString(parts[0]), misc.BytesToString(bytes.TrimSpace(parts[1]))}// JSON 解析优化func parseField(data []byte) string { // data 从池中获取,使用后立即返回池 return misc.BytesToString(data) // 危险!data 可能被重用}// 更安全的做法func parseFieldSafe(data []byte) string { return string(data) // 标准转换,创建副本}7. MIME 类型处理
已废弃:推荐使用 github.com/h2non/filetype 库。
// ExtensionByType - 根据 MIME 类型获取扩展名ext := misc.ExtensionByType("image/png") // ".png"ext := misc.ExtensionByType("video/mp4") // ".mp4"// TypeByExtension - 根据扩展名获取 MIME 类型mimeType := misc.TypeByExtension(".jpg") // "image/jpeg"mimeType := misc.TypeByExtension("png") // "image/png"(可省略点)// CharsetByType - 获取字符集(功能有限)charset := misc.CharsetByType("text/html") // "charset=utf-8"charset := misc.CharsetByType("image/png") // ""8. 栈跟踪
已废弃:推荐使用 runtime.Stack 或 debug.Stack。
// Stack - 获取带源代码行的栈跟踪trace := misc.Stack(0) // 0 表示从调用者开始fmt.Println(trace)使用场景
1. Web 应用中的用户认证
import ( "go-slim.dev/misc" "go-slim.dev/slim")type User struct { Username string Password string}// 注册用户func registerUser(c slim.Context) error { var user User if err := c.Bind(&user); err != nil { return err } // 生成密码哈希 hash, err := misc.PasswordHash(user.Password) if err != nil { return c.String(500, "密码加密失败") } // 保存到数据库 user.Password = hash if err := db.Save(&user); err != nil { return c.String(500, "保存失败") } return c.String(200, "注册成功")}// 用户登录func loginUser(c slim.Context) error { var input User if err := c.Bind(&input); err != nil { return err } // 从数据库获取用户 var user User if err := db.FindByUsername(input.Username, &user); err != nil { return c.String(401, "用户名或密码错误") } // 验证密码 if !misc.PasswordVerify(input.Password, user.Password) { return c.String(401, "用户名或密码错误") } // 生成 token 并返回 token := generateToken(user) return c.JSON(200, map[string]string{"token": token})}2. 配置管理
import ( "os" "strconv" "go-slim.dev/misc")type Config struct { Host string Port int Database string Debug bool}func loadConfig() *Config { // 使用 Coalesce 实现配置优先级 // 命令行 > 环境变量 > 默认值 portStr := misc.Coalesce( os.Getenv("APP_PORT"), "8080", ) port, _ := strconv.Atoi(portStr) return &Config{ Host: misc.Coalesce( os.Getenv("APP_HOST"), "localhost", ), Port: port, Database: misc.Coalesce( os.Getenv("DATABASE_URL"), "postgres://localhost/myapp", ), Debug: os.Getenv("DEBUG") == "true", }}3. URL 路由模板
import "go-slim.dev/misc"type Router struct { baseURL string}func (r *Router) UserProfile(userID int) string { url, _ := misc.Substitute( r.baseURL+"/users/{id}/profile", map[string]any{"id": userID}, ) return url}func (r *Router) UserPosts(userID, page int) string { url, _ := misc.Interpolate( r.baseURL+"/users/{{userID}}/posts?page={{page}}", "{{", "}}", map[string]any{ "userID": userID, "page": page, }, ) return url}// 使用router := &Router{baseURL: "https://api.example.com"}profileURL := router.UserProfile(123)// "https://api.example.com/users/123/profile"postsURL := router.UserPosts(123, 2)// "https://api.example.com/users/123/posts?page=2"4. 数据验证流程
import "go-slim.dev/misc"type Order struct { ID int UserID int Amount float64 Status string}func validateOrder(order *Order) error { return misc.Call( func() error { if order.UserID <= 0 { return errors.New("无效的用户 ID") } return nil }, func() error { if order.Amount <= 0 { return errors.New("金额必须大于 0") } return nil }, func() error { if order.Status == "" { return errors.New("状态不能为空") } return nil }, )}func processOrder(order *Order) error { return misc.CallG(order, validateOrder, saveOrder, sendConfirmation, updateInventory, )}5. 邮件模板
import "go-slim.dev/misc"type EmailTemplate struct { template string}func (t *EmailTemplate) Render(data map[string]any) (string, error) { return misc.Substitute(t.template, data)}// 欢迎邮件welcomeEmail := &EmailTemplate{ template: `亲爱的 {name},欢迎加入 {company}!您的账户已创建成功:- 用户名: {username}- 邮箱: {email}请访问 {url} 完成激活。祝好,{company} 团队`,}content, _ := welcomeEmail.Render(map[string]any{ "name": "Alice", "company": "TechCorp", "username": "alice123", "email": "alice@example.com", "url": "https://example.com/activate",})6. 输入范围限制
import "go-slim.dev/misc"// 图像处理type Image struct { Width int Height int}func (img *Image) SetBrightness(value float64) { // 限制亮度在 0.0 到 2.0 之间 img.brightness = misc.Clamp(value, 0.0, 2.0)}func (img *Image) SetPixel(x, y int, color RGB) { // 限制坐标在图像范围内 x = misc.Clamp(x, 0, img.Width-1) y = misc.Clamp(y, 0, img.Height-1) // 限制颜色值在 0-255 范围内 r := misc.Clamp(color.R, 0, 255) g := misc.Clamp(color.G, 0, 255) b := misc.Clamp(color.B, 0, 255) img.setPixelUnsafe(x, y, RGB{r, g, b})}// 音量控制type AudioPlayer struct { volume int}func (p *AudioPlayer) SetVolume(vol int) { p.volume = misc.Clamp(vol, 0, 100)}func (p *AudioPlayer) IncreaseVolume(delta int) { p.volume = misc.Clamp(p.volume+delta, 0, 100)}7. 文件完整性校验
import ( "io" "os" "go-slim.dev/misc")func calculateFileHash(filename string) (string, error) { data, err := os.ReadFile(filename) if err != nil { return "", err } // 使用 SHA-256 计算文件哈希 return misc.Sha256(string(data)), nil}func verifyFileIntegrity(filename, expectedHash string) (bool, error) { actualHash, err := calculateFileHash(filename) if err != nil { return false, err } return actualHash == expectedHash, nil}// 使用示例ok, err := verifyFileIntegrity("download.zip", "b94d27b99...")if !ok { log.Fatal("文件已损坏或被篡改")}API 参考
密码和摘要
// 密码哈希(bcrypt)func PasswordHash(password string) (string, error)func PasswordVerify(password, hash string) bool// 快速哈希func MD5(str string) string // 返回大写十六进制func Sha1(str string) string // 返回小写十六进制func Sha256(str string) string // 返回小写十六进制函数组合
func Call(fns ...func() error) errorfunc CallG[T any](val T, fns ...func(T) error) errorfunc Wrap(fns ...func() error) func() errorfunc WrapG[T any](fns ...func(T) error) func(val T) error模板插值
func Substitute(template string, data map[string]any) (string, error)func Interpolate(template, startTag, endTag string, data map[string]any) (string, error)func Tmpl(template, startTag, endTag string, w io.Writer, f TagFunc) (int64, error)type TagFunc func(w io.Writer, tag string) (int, error)泛型工具
func Zero[T any]() Tfunc Ptr[T any](x T) *Tfunc Nil[T any]() *Tfunc IsZero[T any](v T) boolfunc IsNil(x any) boolfunc Coalesce[T any](values ...T) T数学函数
func MinMax[T cmp.Ordered](a, b T) (T, T)func Clamp[T cmp.Ordered](val, minT, maxT T) T零拷贝转换
func BytesToString(b []byte) stringfunc StringToBytes(s string) []byteMIME 类型(已废弃)
func ExtensionByType(mimeType string) stringfunc TypeByExtension(ext string) stringfunc CharsetByType(typ string) string栈跟踪(已废弃)
func Stack(skip int) string注意事项
-
密码哈希性能:
- bcrypt 故意设计为慢速,以防止暴力破解
- 不要在性能敏感的代码路径中频繁调用
- 默认 cost 为 10,每增加 1,耗时翻倍
-
哈希函数选择:
- 安全场景(密码、签名):使用
PasswordHash或 SHA-256 - 非安全场景(缓存键、ETag):可使用 MD5 或 SHA-1
- MD5 和 SHA-1 不应用于密码存储
- 安全场景(密码、签名):使用
-
零拷贝转换风险:
BytesToString和StringToBytes使用 unsafe- 共享底层内存,修改源数据会导致未定义行为
- 仅在确定数据不会被修改时使用
- 性能提升显著,但需要谨慎使用
-
IsZero vs IsNil:
IsZero: 检查是否为类型的零值(如 0, "", false, nil)IsNil: 仅检查引用类型是否为 nilIsZero对指针会递归解引用
-
Coalesce 行为:
- 返回第一个非零值,不是第一个非 nil 值
- 空字符串
""是零值,会被跳过 - 空切片
[]int{}不是零值,会被返回
-
函数组合顺序:
Call和CallG按顺序执行函数- 遇到第一个错误立即停止
- 后续函数不会执行
-
模板插值安全:
Substitute不进行 HTML 转义- 如需在 HTML 中使用,需自行转义
- 避免将不可信输入直接插入模板
-
Clamp 边界处理:
- 自动处理 min > max 的情况(会交换边界)
- 使用前无需手动检查边界顺序
性能建议
- 密码哈希:异步处理,避免阻塞主流程
- 零拷贝转换:仅在性能关键路径使用,注意安全性
- 函数组合:避免在循环中使用
Wrap,应在循环外创建 - 模板插值:频繁使用时考虑缓存结果或使用
text/template
相关链接
- GitHub 仓库
- bcrypt 包
- filetype 库(推荐用于 MIME 类型检测)