Slim

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, 消息: HELLO

4. 泛型工具函数

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)                    // false

Coalesce - 取首个非零值

返回参数列表中第一个非零值:

// 字符串默认值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 = 100

Clamp - 限制值在范围内

将值限制在 [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)// 零拷贝转换,共享底层数据

⚠️ 重要警告

  1. 不要修改源数据:转换后的值共享底层内存,修改会导致未定义行为
  2. 不要修改结果StringToBytes 返回的切片是只读的
  3. 生命周期:确保源数据在使用结果期间保持有效

正确使用

// ✅ 正确:只读使用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.Stackdebug.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) []byte

MIME 类型(已废弃)

func ExtensionByType(mimeType string) stringfunc TypeByExtension(ext string) stringfunc CharsetByType(typ string) string

栈跟踪(已废弃)

func Stack(skip int) string

注意事项

  1. 密码哈希性能

    • bcrypt 故意设计为慢速,以防止暴力破解
    • 不要在性能敏感的代码路径中频繁调用
    • 默认 cost 为 10,每增加 1,耗时翻倍
  2. 哈希函数选择

    • 安全场景(密码、签名):使用 PasswordHash 或 SHA-256
    • 非安全场景(缓存键、ETag):可使用 MD5 或 SHA-1
    • MD5 和 SHA-1 不应用于密码存储
  3. 零拷贝转换风险

    • BytesToStringStringToBytes 使用 unsafe
    • 共享底层内存,修改源数据会导致未定义行为
    • 仅在确定数据不会被修改时使用
    • 性能提升显著,但需要谨慎使用
  4. IsZero vs IsNil

    • IsZero: 检查是否为类型的零值(如 0, "", false, nil)
    • IsNil: 仅检查引用类型是否为 nil
    • IsZero 对指针会递归解引用
  5. Coalesce 行为

    • 返回第一个非零值,不是第一个非 nil 值
    • 空字符串 "" 是零值,会被跳过
    • 空切片 []int{} 不是零值,会被返回
  6. 函数组合顺序

    • CallCallG 按顺序执行函数
    • 遇到第一个错误立即停止
    • 后续函数不会执行
  7. 模板插值安全

    • Substitute 不进行 HTML 转义
    • 如需在 HTML 中使用,需自行转义
    • 避免将不可信输入直接插入模板
  8. Clamp 边界处理

    • 自动处理 min > max 的情况(会交换边界)
    • 使用前无需手动检查边界顺序

性能建议

  1. 密码哈希:异步处理,避免阻塞主流程
  2. 零拷贝转换:仅在性能关键路径使用,注意安全性
  3. 函数组合:避免在循环中使用 Wrap,应在循环外创建
  4. 模板插值:频繁使用时考虑缓存结果或使用 text/template

相关链接

切换 选择 esc 关闭搜索