diff --git a/cache/redis/cache.go b/cache/redis/cache.go index 6645b0f..a598ba3 100644 --- a/cache/redis/cache.go +++ b/cache/redis/cache.go @@ -5,6 +5,7 @@ package redis import ( "encoding/json" "fmt" + "strings" "time" "git.apinb.com/bsm-sdk/core/errcode" @@ -15,19 +16,20 @@ import ( // prefix: 键前缀 // params: 键参数 // 返回: 完整的缓存键 -func (c *RedisClient) BuildKey(prefix string, params ...interface{}) string { - key := vars.CacheKeyPrefix + prefix +func (c *RedisClient) BuildKey(prefix string, params ...any) string { + var key strings.Builder + key.WriteString(vars.CacheKeyPrefix + prefix) for _, param := range params { - key += fmt.Sprintf(":%v", param) + key.WriteString(fmt.Sprintf(":%v", param)) } - return key + return key.String() } // Get 获取缓存 // key: 缓存键 // result: 存储结果的指针 // 返回: 错误信息 -func (c *RedisClient) Get(key string, result interface{}) error { +func (c *RedisClient) Get(key string, result any) error { if c.Client == nil { return errcode.ErrRedis } @@ -45,7 +47,7 @@ func (c *RedisClient) Get(key string, result interface{}) error { // value: 缓存值 // ttl: 过期时间 // 返回: 错误信息 -func (c *RedisClient) Set(key string, value interface{}, ttl time.Duration) error { +func (c *RedisClient) Set(key string, value any, ttl time.Duration) error { if c.Client == nil { return errcode.ErrRedis } diff --git a/crypto/token/jwt.go b/crypto/token/jwt.go index 6f85631..7e5d085 100644 --- a/crypto/token/jwt.go +++ b/crypto/token/jwt.go @@ -62,7 +62,7 @@ func (t *tokenJwt) GenerateJwt(id uint, identity, client, role string, owner any // 解析JWT func (t *tokenJwt) ParseJwt(tokenstring string) (*Claims, error) { - token, err := jwt.ParseWithClaims(tokenstring, &Claims{}, func(token *jwt.Token) (interface{}, error) { + token, err := jwt.ParseWithClaims(tokenstring, &Claims{}, func(token *jwt.Token) (any, error) { return []byte(t.SecretKey), nil }) if claims, ok := token.Claims.(*Claims); ok && token.Valid { diff --git a/database/elastic/elasticsearch.go b/database/elastic/elasticsearch.go index dbbefa5..95e0912 100644 --- a/database/elastic/elasticsearch.go +++ b/database/elastic/elasticsearch.go @@ -44,7 +44,7 @@ func NewElastic(endpoints []string, username, password string) (*ES, error) { // "time": time.Now().Unix(), // "date": time.Now(), // } -func (es *ES) CreateDocument(index string, id string, doc *interface{}) { +func (es *ES) CreateDocument(index string, id string, doc *any) { var buf bytes.Buffer if err := json.NewEncoder(&buf).Encode(doc); err != nil { log.Println("Elastic NewEncoder:", err) @@ -75,7 +75,7 @@ func (es *ES) CreateDocument(index string, id string, doc *interface{}) { // index 如果文档不存在就创建,如果文档存在就更新 // update 更新一个文档,如果文档不存在就返回错误 // delete 删除一个文档,如果要删除的文档id不存在,就返回错误 -func (es *ES) Batch(index string, documens []map[string]interface{}, action string) { +func (es *ES) Batch(index string, documens []map[string]any, action string) { log.SetFlags(0) var ( @@ -162,7 +162,7 @@ func (es *ES) Batch(index string, documens []map[string]interface{}, action stri } } -func (es *ES) Search(index string, query map[string]interface{}) (res *esapi.Response, err error) { +func (es *ES) Search(index string, query map[string]any) (res *esapi.Response, err error) { var buf bytes.Buffer if err = json.NewEncoder(&buf).Encode(query); err != nil { return @@ -201,7 +201,7 @@ func (es *ES) Delete(index, idx string) (res *esapi.Response, err error) { return } -func (es *ES) DeleteByQuery(index []string, query map[string]interface{}) (res *esapi.Response, err error) { +func (es *ES) DeleteByQuery(index []string, query map[string]any) (res *esapi.Response, err error) { var buf bytes.Buffer if err = json.NewEncoder(&buf).Encode(query); err != nil { return diff --git a/go.mod b/go.mod index f750145..1aa22f2 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module git.apinb.com/bsm-sdk/core -go 1.25.1 +go 1.26.0 diff --git a/licence/licence.go b/licence/licence.go index 6821553..52dc6b1 100644 --- a/licence/licence.go +++ b/licence/licence.go @@ -14,6 +14,7 @@ import ( "os" "path" "path/filepath" + "slices" "sort" "strconv" "strings" @@ -135,13 +136,7 @@ func (l *Licence) VerifyLicence(licName string) bool { // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- // 检查机器码是否存在授权列表中 func (l *Licence) ValidMachineCode(code string) bool { - result := false - for _, c := range l.MachineCodes { - if c == code { - result = true - break - } - } + result := slices.Contains(l.MachineCodes, code) return result } @@ -265,7 +260,7 @@ func getMacAddrs() []string { if err != nil { return macs } - for i := 0; i < len(netfaces); i++ { + for i := range netfaces { if (netfaces[i].Flags&net.FlagUp) != 0 && (netfaces[i].Flags&net.FlagLoopback) == 0 { addrs, _ := netfaces[i].Addrs() for _, address := range addrs { diff --git a/logger/logger.go b/logger/logger.go index b1a5956..867bcca 100644 --- a/logger/logger.go +++ b/logger/logger.go @@ -159,7 +159,7 @@ func (l *Logger) sendToRemote(level, name, out string) { if l.endpoint == "" { return } - data := map[string]interface{}{ + data := map[string]any{ "level": level, "name": name, "out": out, @@ -169,7 +169,7 @@ func (l *Logger) sendToRemote(level, name, out string) { } // Debug 输出调试信息 -func (l *Logger) Debug(v ...interface{}) { +func (l *Logger) Debug(v ...any) { if l.level <= vars.DEBUG { l.checkAndRotateLog() out := fmt.Sprint(v...) @@ -181,7 +181,7 @@ func (l *Logger) Debug(v ...interface{}) { } // Debugf 格式化输出调试信息 -func (l *Logger) Debugf(format string, v ...interface{}) { +func (l *Logger) Debugf(format string, v ...any) { if l.level <= vars.DEBUG { l.checkAndRotateLog() out := fmt.Sprintf(format, v...) @@ -193,7 +193,7 @@ func (l *Logger) Debugf(format string, v ...interface{}) { } // Info 输出信息 -func (l *Logger) Info(v ...interface{}) { +func (l *Logger) Info(v ...any) { if l.level <= vars.INFO { l.checkAndRotateLog() out := fmt.Sprint(v...) @@ -205,7 +205,7 @@ func (l *Logger) Info(v ...interface{}) { } // Infof 格式化输出信息 -func (l *Logger) Infof(format string, v ...interface{}) { +func (l *Logger) Infof(format string, v ...any) { if l.level <= vars.INFO { l.checkAndRotateLog() out := fmt.Sprintf(format, v...) @@ -217,7 +217,7 @@ func (l *Logger) Infof(format string, v ...interface{}) { } // Warn 输出警告 -func (l *Logger) Warn(v ...interface{}) { +func (l *Logger) Warn(v ...any) { if l.level <= vars.WARN { l.checkAndRotateLog() out := fmt.Sprint(v...) @@ -229,7 +229,7 @@ func (l *Logger) Warn(v ...interface{}) { } // Warnf 格式化输出警告 -func (l *Logger) Warnf(format string, v ...interface{}) { +func (l *Logger) Warnf(format string, v ...any) { if l.level <= vars.WARN { l.checkAndRotateLog() out := fmt.Sprintf(format, v...) @@ -241,7 +241,7 @@ func (l *Logger) Warnf(format string, v ...interface{}) { } // Error 输出错误 -func (l *Logger) Error(v ...interface{}) { +func (l *Logger) Error(v ...any) { if l.level <= vars.ERROR { l.checkAndRotateLog() out := fmt.Sprint(v...) @@ -253,7 +253,7 @@ func (l *Logger) Error(v ...interface{}) { } // Errorf 格式化输出错误 -func (l *Logger) Errorf(format string, v ...interface{}) { +func (l *Logger) Errorf(format string, v ...any) { if l.level <= vars.ERROR { l.checkAndRotateLog() out := fmt.Sprintf(format, v...) @@ -265,7 +265,7 @@ func (l *Logger) Errorf(format string, v ...interface{}) { } // Fatal 输出致命错误并退出程序 -func (l *Logger) Fatal(v ...interface{}) { +func (l *Logger) Fatal(v ...any) { l.checkAndRotateLog() out := fmt.Sprint(v...) if l.onRemote { @@ -276,7 +276,7 @@ func (l *Logger) Fatal(v ...interface{}) { } // Fatalf 格式化输出致命错误并退出程序 -func (l *Logger) Fatalf(format string, v ...interface{}) { +func (l *Logger) Fatalf(format string, v ...any) { l.checkAndRotateLog() out := fmt.Sprintf(format, v...) if l.onRemote { @@ -287,17 +287,17 @@ func (l *Logger) Fatalf(format string, v ...interface{}) { } // Print 输出信息(兼容标准log包) -func (l *Logger) Print(v ...interface{}) { +func (l *Logger) Print(v ...any) { l.Info(v...) } // Printf 格式化输出信息(兼容标准log包) -func (l *Logger) Printf(format string, v ...interface{}) { +func (l *Logger) Printf(format string, v ...any) { l.Infof(format, v...) } // Println 输出信息并换行(兼容标准log包) -func (l *Logger) Println(v ...interface{}) { +func (l *Logger) Println(v ...any) { l.Info(v...) } @@ -326,91 +326,91 @@ func (l *Logger) Close() error { // 全局日志函数(兼容标准log包) // Debug 全局调试日志 -func Debug(v ...interface{}) { +func Debug(v ...any) { if globalLogger != nil { globalLogger.Debug(v...) } } // Debugf 全局调试日志 -func Debugf(format string, v ...interface{}) { +func Debugf(format string, v ...any) { if globalLogger != nil { globalLogger.Debugf(format, v...) } } // Info 全局信息日志 -func Info(v ...interface{}) { +func Info(v ...any) { if globalLogger != nil { globalLogger.Info(v...) } } // Infof 全局信息日志 -func Infof(format string, v ...interface{}) { +func Infof(format string, v ...any) { if globalLogger != nil { globalLogger.Infof(format, v...) } } // Warn 全局警告日志 -func Warn(v ...interface{}) { +func Warn(v ...any) { if globalLogger != nil { globalLogger.Warn(v...) } } // Warnf 全局警告日志 -func Warnf(format string, v ...interface{}) { +func Warnf(format string, v ...any) { if globalLogger != nil { globalLogger.Warnf(format, v...) } } // Error 全局错误日志 -func Error(v ...interface{}) { +func Error(v ...any) { if globalLogger != nil { globalLogger.Error(v...) } } // Errorf 全局错误日志 -func Errorf(format string, v ...interface{}) { +func Errorf(format string, v ...any) { if globalLogger != nil { globalLogger.Errorf(format, v...) } } // Fatal 全局致命错误日志 -func Fatal(v ...interface{}) { +func Fatal(v ...any) { if globalLogger != nil { globalLogger.Fatal(v...) } } // Fatalf 全局致命错误日志 -func Fatalf(format string, v ...interface{}) { +func Fatalf(format string, v ...any) { if globalLogger != nil { globalLogger.Fatalf(format, v...) } } // Print 全局打印日志(兼容标准log包) -func Print(v ...interface{}) { +func Print(v ...any) { if globalLogger != nil { globalLogger.Print(v...) } } // Printf 全局打印日志(兼容标准log包) -func Printf(format string, v ...interface{}) { +func Printf(format string, v ...any) { if globalLogger != nil { globalLogger.Printf(format, v...) } } // Println 全局打印日志(兼容标准log包) -func Println(v ...interface{}) { +func Println(v ...any) { if globalLogger != nil { globalLogger.Println(v...) } diff --git a/printer/print.go b/printer/print.go index c409d33..de69be1 100644 --- a/printer/print.go +++ b/printer/print.go @@ -16,25 +16,25 @@ func init() { } // record INFO message. Color White -func Info(format string, a ...interface{}) { +func Info(format string, a ...any) { message := fmt.Sprintf("\033[37m[Info] "+format+"\033[0m\n", a...) logger.Print(message) } // record Warn message. Color Orange -func Warn(format string, a ...interface{}) { +func Warn(format string, a ...any) { message := fmt.Sprintf("\033[33m[Warn] "+format+"\033[0m\n", a...) logger.Print(message) } // record Success message. Color Green -func Success(format string, a ...interface{}) { +func Success(format string, a ...any) { message := fmt.Sprintf("\033[32m[Succ] "+format+"\033[0m\n", a...) logger.Print(message) } // record ERROR message. Color Red -func Error(format string, a ...interface{}) { +func Error(format string, a ...any) { message := fmt.Sprintf("\033[31m[Error] "+format+"\033[0m\n", a...) logger.Print(message) } diff --git a/third/wechat.go b/third/wechat.go index 3357e66..72a4f73 100644 --- a/third/wechat.go +++ b/third/wechat.go @@ -56,7 +56,7 @@ func WeChat_Pkcs7Unpad(data []byte, blockSize int) ([]byte, error) { if n == 0 || n > len(data) { return nil, ErrInvalidPKCS7Padding } - for i := 0; i < n; i++ { + for i := range n { if data[len(data)-n+i] != c { return nil, ErrInvalidPKCS7Padding } diff --git a/utils/array.go b/utils/array.go index 49b6491..22ceb18 100644 --- a/utils/array.go +++ b/utils/array.go @@ -1,5 +1,7 @@ package utils +import "slices" + import "strings" // ArrayInString 判断字符串是否存在于字符串切片中 @@ -7,24 +9,14 @@ import "strings" // array: 需要查找的字符串切片 func ArrayInString(target string, array []string) bool { target = strings.TrimSpace(target) - for _, v := range array { - if strings.TrimSpace(v) == target { - return true - } - } - return false + return slices.Contains(array, target) } // ArrayInInt 判断整数是否存在于整型切片中 // target: 待匹配的目标整数 // array: 需要查找的整型切片 func ArrayInInt(target int, array []int) bool { - for _, v := range array { - if v == target { - return true - } - } - return false + return slices.Contains(array, target) } // ArrayRemoveRepeatString 去除字符串切片中的重复元素(保持原有顺序) diff --git a/utils/convert.go b/utils/convert.go index ed9b7e0..56d5cbd 100644 --- a/utils/convert.go +++ b/utils/convert.go @@ -103,7 +103,7 @@ func BinaryToDecimal(bit string) (num int) { fields := strings.Split(bit, "") lens := len(fields) var tempF float64 = 0 - for i := 0; i < lens; i++ { + for i := range lens { floatNum := String2Float64(fields[i]) tempF += floatNum * math.Pow(2, float64(lens-i-1)) } diff --git a/utils/ext.go b/utils/ext.go index 7c30fa3..1bf36c9 100644 --- a/utils/ext.go +++ b/utils/ext.go @@ -5,7 +5,7 @@ import ( "strings" ) -func If(condition bool, trueValue, falseValue interface{}) interface{} { +func If(condition bool, trueValue, falseValue any) any { if condition { return trueValue } @@ -21,8 +21,8 @@ func FirstToUpper(str string) string { return strings.ToUpper(str[:1]) + strings.ToLower(str[1:]) } -func ParseParams(in map[string]string) map[string]interface{} { - out := make(map[string]interface{}) +func ParseParams(in map[string]string) map[string]any { + out := make(map[string]any) for k, v := range in { fv, err := strconv.ParseFloat(v, 64) if err != nil { diff --git a/utils/qrcode.go b/utils/qrcode.go index faba89d..0103bde 100644 --- a/utils/qrcode.go +++ b/utils/qrcode.go @@ -1,457 +1,457 @@ -// Package utils 提供通用工具函数 -// -// 二维码生成功能模块 -// -// 本模块提供了完整的二维码生成功能,支持: -// - 基础二维码生成(保存为PNG文件) -// - 生成字节数组(可用于HTTP响应、数据库存储等) -// - Base64编码输出(便于存储和传输) -// - Data URL格式(可直接用于HTML 标签) -// - 自定义配置(尺寸、颜色、纠错级别) -// - 带Logo的二维码 -// - 批量生成二维码 -package utils - -import ( - "bytes" - "encoding/base64" - "fmt" - "image" - "image/color" - "image/png" - "os" - - "github.com/skip2/go-qrcode" -) - -// QRCodeErrorLevel 二维码纠错级别 -// -// 纠错级别决定了二维码能容忍多少损坏: -// - 级别越高,容错能力越强,但二维码会更复杂 -// - 添加Logo时建议使用高级纠错 -// - 一般场景使用中级纠错即可 -type QRCodeErrorLevel int - -const ( - // QRCodeErrorLevelLow 低级纠错(约7%容错) - // 适用场景:环境良好、追求小尺寸、内容较少 - QRCodeErrorLevelLow QRCodeErrorLevel = iota - - // QRCodeErrorLevelMedium 中级纠错(约15%容错) - // 适用场景:通用场景(默认推荐) - QRCodeErrorLevelMedium - - // QRCodeErrorLevelQuartile 较高级纠错(约25%容错) - // 适用场景:需要较高容错能力 - QRCodeErrorLevelQuartile - - // QRCodeErrorLevelHigh 高级纠错(约30%容错) - // 适用场景:添加Logo、容易损坏的环境、长期使用 - QRCodeErrorLevelHigh -) - -const ( - // DefaultQRCodeSize 默认二维码尺寸(像素) - DefaultQRCodeSize = 256 - // MinQRCodeSize 最小二维码尺寸 - MinQRCodeSize = 21 - // MaxQRCodeSize 最大二维码尺寸 - MaxQRCodeSize = 8192 -) - -// QRCodeConfig 二维码配置结构 -// -// 用于自定义二维码的外观和质量参数 -// -// 示例: -// -// config := &QRCodeConfig{ -// Size: 512, // 尺寸512x512像素 -// ErrorLevel: QRCodeErrorLevelHigh, // 高级纠错 -// ForegroundColor: color.RGBA{255, 0, 0, 255}, // 红色二维码 -// BackgroundColor: color.White, // 白色背景 -// } -type QRCodeConfig struct { - Size int // 尺寸(像素),范围:21-8192 - ErrorLevel QRCodeErrorLevel // 纠错级别,影响容错能力和复杂度 - ForegroundColor color.Color // 前景色(二维码颜色),建议使用深色 - BackgroundColor color.Color // 背景色,建议使用浅色以保证对比度 -} - -// DefaultQRCodeConfig 返回默认配置 -// -// 默认配置适用于大多数场景: -// - 尺寸:256x256像素(适合手机扫描) -// - 纠错级别:中级(15%容错) -// - 颜色:黑白配色(最佳识别率) -// -// 返回值: -// -// *QRCodeConfig: 默认配置对象 -func DefaultQRCodeConfig() *QRCodeConfig { - return &QRCodeConfig{ - Size: DefaultQRCodeSize, - ErrorLevel: QRCodeErrorLevelMedium, - ForegroundColor: color.Black, - BackgroundColor: color.White, - } -} - -// convertErrorLevel 转换纠错级别为底层库的纠错级别 -// -// 将自定义的纠错级别枚举转换为 go-qrcode 库所需的格式 -// -// 参数: -// -// level: 自定义纠错级别 -// -// 返回值: -// -// qrcode.RecoveryLevel: go-qrcode库的纠错级别 -func convertErrorLevel(level QRCodeErrorLevel) qrcode.RecoveryLevel { - switch level { - case QRCodeErrorLevelLow: - return qrcode.Low - case QRCodeErrorLevelMedium: - return qrcode.Medium - case QRCodeErrorLevelQuartile: - return qrcode.High - case QRCodeErrorLevelHigh: - return qrcode.Highest - default: - return qrcode.Medium - } -} - -// GenerateQRCode 生成二维码并保存为PNG文件 -// -// 这是最简单的二维码生成方法,适合快速生成标准黑白二维码。 -// 使用中级纠错,黑白配色,PNG格式输出。 -// -// 参数: -// -// content: 二维码内容(支持URL、文本、vCard、WiFi等格式) -// filename: 保存的文件路径(.png文件) -// size: 二维码尺寸(可选,单位:像素) -// - 不传参数:使用默认256x256 -// - 传一个参数:使用指定尺寸 -// - 有效范围:21-8192像素 -// -// 返回值: -// -// error: 错误信息,成功时返回nil -// -// 注意事项: -// - 内容越长,二维码越复杂,建议尺寸>=256 -// - 文件权限为0644 -// - 会覆盖已存在的同名文件 -func GenerateQRCode(content, filename string, size ...int) error { - qrSize := DefaultQRCodeSize - if len(size) > 0 { - qrSize = size[0] - if qrSize < MinQRCodeSize || qrSize > MaxQRCodeSize { - return fmt.Errorf("二维码尺寸必须在 %d 到 %d 之间", MinQRCodeSize, MaxQRCodeSize) - } - } - - err := qrcode.WriteFile(content, qrcode.Medium, qrSize, filename) - if err != nil { - return fmt.Errorf("生成二维码失败: %w", err) - } - - return nil -} - -// GenerateQRCodeBytes 生成二维码字节数组(PNG格式) -// -// 生成二维码的字节数组而不保存到文件,适合: -// - HTTP响应直接返回图片 -// - 存储到数据库 -// - 通过网络传输 -// - 进一步处理(如添加到PDF) -// -// 参数: -// -// content: 二维码内容 -// size: 二维码尺寸(可选,默认256) -// -// 返回值: -// -// []byte: PNG格式的图片字节数组 -// error: 错误信息 -func GenerateQRCodeBytes(content string, size ...int) ([]byte, error) { - qrSize := DefaultQRCodeSize - if len(size) > 0 { - qrSize = size[0] - if qrSize < MinQRCodeSize || qrSize > MaxQRCodeSize { - return nil, fmt.Errorf("二维码尺寸必须在 %d 到 %d 之间", MinQRCodeSize, MaxQRCodeSize) - } - } - - bytes, err := qrcode.Encode(content, qrcode.Medium, qrSize) - if err != nil { - return nil, fmt.Errorf("生成二维码失败: %w", err) - } - - return bytes, nil -} - -// GenerateQRCodeBase64 生成Base64编码的二维码字符串 -// -// 将二维码图片编码为Base64字符串,便于: -// - 存储到数据库的文本字段 -// - 通过JSON/XML传输 -// - 避免二进制数据处理问题 -// -// 参数: -// -// content: 二维码内容 -// size: 二维码尺寸(可选,默认256) -// -// 返回值: -// -// string: Base64编码的字符串(不包含data:image/png;base64,前缀) -// error: 错误信息 -func GenerateQRCodeBase64(content string, size ...int) (string, error) { - qrBytes, err := GenerateQRCodeBytes(content, size...) - if err != nil { - return "", err - } - - base64Str := base64.StdEncoding.EncodeToString(qrBytes) - return base64Str, nil -} - -// GenerateQRCodeWithConfig 使用自定义配置生成二维码 -// -// 提供完全自定义的二维码生成能力,可以控制: -// - 尺寸大小 -// - 纠错级别 -// - 前景色和背景色 -// -// 参数: -// -// content: 二维码内容 -// config: 二维码配置对象(nil时使用默认配置) -// -// 返回值: -// -// []byte: PNG格式的字节数组 -// error: 错误信息 -// -// 注意事项: -// - 确保前景色和背景色有足够对比度 -// - 浅色前景配深色背景可能影响扫描 -func GenerateQRCodeWithConfig(content string, config *QRCodeConfig) ([]byte, error) { - if config == nil { - config = DefaultQRCodeConfig() - } - - // 验证尺寸 - if config.Size < MinQRCodeSize || config.Size > MaxQRCodeSize { - return nil, fmt.Errorf("二维码尺寸必须在 %d 到 %d 之间", MinQRCodeSize, MaxQRCodeSize) - } - - // 创建二维码对象 - qr, err := qrcode.New(content, convertErrorLevel(config.ErrorLevel)) - if err != nil { - return nil, fmt.Errorf("创建二维码失败: %w", err) - } - - // 设置颜色 - qr.ForegroundColor = config.ForegroundColor - qr.BackgroundColor = config.BackgroundColor - - // 生成PNG - pngBytes, err := qr.PNG(config.Size) - if err != nil { - return nil, fmt.Errorf("生成PNG失败: %w", err) - } - - return pngBytes, nil -} - -// GenerateQRCodeWithLogo 生成带Logo的二维码 -// -// 在二维码中心嵌入Logo图片,Logo会占据二维码约1/5的区域。 -// 使用高级纠错以确保Logo不影响二维码的可读性。 -// -// 参数: -// -// content: 二维码内容 -// logoPath: Logo图片文件路径(支持PNG、JPEG等格式) -// size: 二维码尺寸(可选,默认256,建议>=512以保证清晰度) -// -// 返回值: -// -// []byte: PNG格式的字节数组 -// error: 错误信息 -// -// 注意事项: -// - Logo会自动缩放到二维码的1/5大小 -// - 建议Logo使用正方形图片 -// - 使用高级纠错级别(~30%容错) -// - Logo会覆盖二维码中心区域 -// - 建议二维码尺寸>=512以保证Logo清晰 -// - Logo文件必须存在且可读取 -func GenerateQRCodeWithLogo(content, logoPath string, size ...int) ([]byte, error) { - qrSize := DefaultQRCodeSize - if len(size) > 0 { - qrSize = size[0] - if qrSize < MinQRCodeSize || qrSize > MaxQRCodeSize { - return nil, fmt.Errorf("二维码尺寸必须在 %d 到 %d 之间", MinQRCodeSize, MaxQRCodeSize) - } - } - - // 生成基础二维码 - qr, err := qrcode.New(content, qrcode.High) - if err != nil { - return nil, fmt.Errorf("创建二维码失败: %w", err) - } - - // 生成二维码图像 - qrImage := qr.Image(qrSize) - - // 读取Logo图片 - logoFile, err := os.Open(logoPath) - if err != nil { - return nil, fmt.Errorf("打开Logo文件失败: %w", err) - } - defer logoFile.Close() - - logoImage, _, err := image.Decode(logoFile) - if err != nil { - return nil, fmt.Errorf("解码Logo图片失败: %w", err) - } - - // 计算Logo位置和大小(Logo占二维码的1/5) - logoSize := qrSize / 5 - logoX := (qrSize - logoSize) / 2 - logoY := (qrSize - logoSize) / 2 - - // 创建最终图像 - finalImage := image.NewRGBA(qrImage.Bounds()) - - // 绘制二维码 - for y := 0; y < qrSize; y++ { - for x := 0; x < qrSize; x++ { - finalImage.Set(x, y, qrImage.At(x, y)) - } - } - - // 绘制Logo - logoOriginalBounds := logoImage.Bounds() - for y := 0; y < logoSize; y++ { - for x := 0; x < logoSize; x++ { - // 计算原始Logo的对应像素 - origX := x * logoOriginalBounds.Dx() / logoSize - origY := y * logoOriginalBounds.Dy() / logoSize - finalImage.Set(logoX+x, logoY+y, logoImage.At(origX, origY)) - } - } - - // 转换为PNG字节 - var buf bytes.Buffer - if err := png.Encode(&buf, finalImage); err != nil { - return nil, fmt.Errorf("编码PNG失败: %w", err) - } - - return buf.Bytes(), nil -} - -// SaveQRCodeBytes 保存二维码字节数组到文件 -// -// 将二维码字节数组保存为PNG文件,常与 GenerateQRCodeBytes 或 -// GenerateQRCodeWithConfig 配合使用。 -// -// 参数: -// -// data: PNG格式的二维码字节数组 -// filename: 保存的文件路径 -// -// 返回值: -// -// error: 错误信息 -// -// 注意事项: -// - 文件权限为0644 -// - 会覆盖已存在的文件 -// - 确保目录已存在 -func SaveQRCodeBytes(data []byte, filename string) error { - if err := os.WriteFile(filename, data, 0644); err != nil { - return fmt.Errorf("保存文件失败: %w", err) - } - return nil -} - -// GenerateQRCodeDataURL 生成Data URL格式的二维码 -// -// 生成可以直接用于HTML 标签的Data URL格式字符串。 -// Data URL包含了完整的图片数据,无需额外的图片文件。 -// -// 参数: -// -// content: 二维码内容 -// size: 二维码尺寸(可选,默认256) -// -// 返回值: -// -// string: Data URL格式字符串(包含data:image/png;base64,前缀) -// error: 错误信息 -// -// 注意事项: -// - Data URL字符串较长,不适合存储到数据库 -// - 适合临时显示、前端渲染等场景 -// - 某些老旧浏览器可能不支持 -func GenerateQRCodeDataURL(content string, size ...int) (string, error) { - qrBytes, err := GenerateQRCodeBytes(content, size...) - if err != nil { - return "", err - } - - base64Str := base64.StdEncoding.EncodeToString(qrBytes) - dataURL := fmt.Sprintf("data:image/png;base64,%s", base64Str) - return dataURL, nil -} - -// BatchGenerateQRCode 批量生成二维码文件 -// -// 一次性生成多个二维码文件,适合: -// - 批量生成产品二维码 -// - 生成多个用户的会员卡 -// - 批量生成门票、优惠券等 -// -// 参数: -// -// items: map[内容]文件名,例如:map["产品A":"qr_a.png", "产品B":"qr_b.png"] -// size: 二维码尺寸(可选,默认256,所有二维码使用相同尺寸) -// -// 返回值: -// -// []string: 失败的文件名列表(成功时为nil) -// error: 错误信息(部分失败时返回错误,但成功的文件已生成) -// -// 注意事项: -// - 即使部分失败,成功的二维码仍会生成 -// - 建议检查返回的failed列表以确定哪些失败了 -// - 大批量生成时注意磁盘空间 -func BatchGenerateQRCode(items map[string]string, size ...int) ([]string, error) { - var failed []string - qrSize := DefaultQRCodeSize - if len(size) > 0 { - qrSize = size[0] - } - - for content, filename := range items { - err := GenerateQRCode(content, filename, qrSize) - if err != nil { - failed = append(failed, filename) - } - } - - if len(failed) > 0 { - return failed, fmt.Errorf("有 %d 个二维码生成失败", len(failed)) - } - - return nil, nil -} +// Package utils 提供通用工具函数 +// +// 二维码生成功能模块 +// +// 本模块提供了完整的二维码生成功能,支持: +// - 基础二维码生成(保存为PNG文件) +// - 生成字节数组(可用于HTTP响应、数据库存储等) +// - Base64编码输出(便于存储和传输) +// - Data URL格式(可直接用于HTML 标签) +// - 自定义配置(尺寸、颜色、纠错级别) +// - 带Logo的二维码 +// - 批量生成二维码 +package utils + +import ( + "bytes" + "encoding/base64" + "fmt" + "image" + "image/color" + "image/png" + "os" + + "github.com/skip2/go-qrcode" +) + +// QRCodeErrorLevel 二维码纠错级别 +// +// 纠错级别决定了二维码能容忍多少损坏: +// - 级别越高,容错能力越强,但二维码会更复杂 +// - 添加Logo时建议使用高级纠错 +// - 一般场景使用中级纠错即可 +type QRCodeErrorLevel int + +const ( + // QRCodeErrorLevelLow 低级纠错(约7%容错) + // 适用场景:环境良好、追求小尺寸、内容较少 + QRCodeErrorLevelLow QRCodeErrorLevel = iota + + // QRCodeErrorLevelMedium 中级纠错(约15%容错) + // 适用场景:通用场景(默认推荐) + QRCodeErrorLevelMedium + + // QRCodeErrorLevelQuartile 较高级纠错(约25%容错) + // 适用场景:需要较高容错能力 + QRCodeErrorLevelQuartile + + // QRCodeErrorLevelHigh 高级纠错(约30%容错) + // 适用场景:添加Logo、容易损坏的环境、长期使用 + QRCodeErrorLevelHigh +) + +const ( + // DefaultQRCodeSize 默认二维码尺寸(像素) + DefaultQRCodeSize = 256 + // MinQRCodeSize 最小二维码尺寸 + MinQRCodeSize = 21 + // MaxQRCodeSize 最大二维码尺寸 + MaxQRCodeSize = 8192 +) + +// QRCodeConfig 二维码配置结构 +// +// 用于自定义二维码的外观和质量参数 +// +// 示例: +// +// config := &QRCodeConfig{ +// Size: 512, // 尺寸512x512像素 +// ErrorLevel: QRCodeErrorLevelHigh, // 高级纠错 +// ForegroundColor: color.RGBA{255, 0, 0, 255}, // 红色二维码 +// BackgroundColor: color.White, // 白色背景 +// } +type QRCodeConfig struct { + Size int // 尺寸(像素),范围:21-8192 + ErrorLevel QRCodeErrorLevel // 纠错级别,影响容错能力和复杂度 + ForegroundColor color.Color // 前景色(二维码颜色),建议使用深色 + BackgroundColor color.Color // 背景色,建议使用浅色以保证对比度 +} + +// DefaultQRCodeConfig 返回默认配置 +// +// 默认配置适用于大多数场景: +// - 尺寸:256x256像素(适合手机扫描) +// - 纠错级别:中级(15%容错) +// - 颜色:黑白配色(最佳识别率) +// +// 返回值: +// +// *QRCodeConfig: 默认配置对象 +func DefaultQRCodeConfig() *QRCodeConfig { + return &QRCodeConfig{ + Size: DefaultQRCodeSize, + ErrorLevel: QRCodeErrorLevelMedium, + ForegroundColor: color.Black, + BackgroundColor: color.White, + } +} + +// convertErrorLevel 转换纠错级别为底层库的纠错级别 +// +// 将自定义的纠错级别枚举转换为 go-qrcode 库所需的格式 +// +// 参数: +// +// level: 自定义纠错级别 +// +// 返回值: +// +// qrcode.RecoveryLevel: go-qrcode库的纠错级别 +func convertErrorLevel(level QRCodeErrorLevel) qrcode.RecoveryLevel { + switch level { + case QRCodeErrorLevelLow: + return qrcode.Low + case QRCodeErrorLevelMedium: + return qrcode.Medium + case QRCodeErrorLevelQuartile: + return qrcode.High + case QRCodeErrorLevelHigh: + return qrcode.Highest + default: + return qrcode.Medium + } +} + +// GenerateQRCode 生成二维码并保存为PNG文件 +// +// 这是最简单的二维码生成方法,适合快速生成标准黑白二维码。 +// 使用中级纠错,黑白配色,PNG格式输出。 +// +// 参数: +// +// content: 二维码内容(支持URL、文本、vCard、WiFi等格式) +// filename: 保存的文件路径(.png文件) +// size: 二维码尺寸(可选,单位:像素) +// - 不传参数:使用默认256x256 +// - 传一个参数:使用指定尺寸 +// - 有效范围:21-8192像素 +// +// 返回值: +// +// error: 错误信息,成功时返回nil +// +// 注意事项: +// - 内容越长,二维码越复杂,建议尺寸>=256 +// - 文件权限为0644 +// - 会覆盖已存在的同名文件 +func GenerateQRCode(content, filename string, size ...int) error { + qrSize := DefaultQRCodeSize + if len(size) > 0 { + qrSize = size[0] + if qrSize < MinQRCodeSize || qrSize > MaxQRCodeSize { + return fmt.Errorf("二维码尺寸必须在 %d 到 %d 之间", MinQRCodeSize, MaxQRCodeSize) + } + } + + err := qrcode.WriteFile(content, qrcode.Medium, qrSize, filename) + if err != nil { + return fmt.Errorf("生成二维码失败: %w", err) + } + + return nil +} + +// GenerateQRCodeBytes 生成二维码字节数组(PNG格式) +// +// 生成二维码的字节数组而不保存到文件,适合: +// - HTTP响应直接返回图片 +// - 存储到数据库 +// - 通过网络传输 +// - 进一步处理(如添加到PDF) +// +// 参数: +// +// content: 二维码内容 +// size: 二维码尺寸(可选,默认256) +// +// 返回值: +// +// []byte: PNG格式的图片字节数组 +// error: 错误信息 +func GenerateQRCodeBytes(content string, size ...int) ([]byte, error) { + qrSize := DefaultQRCodeSize + if len(size) > 0 { + qrSize = size[0] + if qrSize < MinQRCodeSize || qrSize > MaxQRCodeSize { + return nil, fmt.Errorf("二维码尺寸必须在 %d 到 %d 之间", MinQRCodeSize, MaxQRCodeSize) + } + } + + bytes, err := qrcode.Encode(content, qrcode.Medium, qrSize) + if err != nil { + return nil, fmt.Errorf("生成二维码失败: %w", err) + } + + return bytes, nil +} + +// GenerateQRCodeBase64 生成Base64编码的二维码字符串 +// +// 将二维码图片编码为Base64字符串,便于: +// - 存储到数据库的文本字段 +// - 通过JSON/XML传输 +// - 避免二进制数据处理问题 +// +// 参数: +// +// content: 二维码内容 +// size: 二维码尺寸(可选,默认256) +// +// 返回值: +// +// string: Base64编码的字符串(不包含data:image/png;base64,前缀) +// error: 错误信息 +func GenerateQRCodeBase64(content string, size ...int) (string, error) { + qrBytes, err := GenerateQRCodeBytes(content, size...) + if err != nil { + return "", err + } + + base64Str := base64.StdEncoding.EncodeToString(qrBytes) + return base64Str, nil +} + +// GenerateQRCodeWithConfig 使用自定义配置生成二维码 +// +// 提供完全自定义的二维码生成能力,可以控制: +// - 尺寸大小 +// - 纠错级别 +// - 前景色和背景色 +// +// 参数: +// +// content: 二维码内容 +// config: 二维码配置对象(nil时使用默认配置) +// +// 返回值: +// +// []byte: PNG格式的字节数组 +// error: 错误信息 +// +// 注意事项: +// - 确保前景色和背景色有足够对比度 +// - 浅色前景配深色背景可能影响扫描 +func GenerateQRCodeWithConfig(content string, config *QRCodeConfig) ([]byte, error) { + if config == nil { + config = DefaultQRCodeConfig() + } + + // 验证尺寸 + if config.Size < MinQRCodeSize || config.Size > MaxQRCodeSize { + return nil, fmt.Errorf("二维码尺寸必须在 %d 到 %d 之间", MinQRCodeSize, MaxQRCodeSize) + } + + // 创建二维码对象 + qr, err := qrcode.New(content, convertErrorLevel(config.ErrorLevel)) + if err != nil { + return nil, fmt.Errorf("创建二维码失败: %w", err) + } + + // 设置颜色 + qr.ForegroundColor = config.ForegroundColor + qr.BackgroundColor = config.BackgroundColor + + // 生成PNG + pngBytes, err := qr.PNG(config.Size) + if err != nil { + return nil, fmt.Errorf("生成PNG失败: %w", err) + } + + return pngBytes, nil +} + +// GenerateQRCodeWithLogo 生成带Logo的二维码 +// +// 在二维码中心嵌入Logo图片,Logo会占据二维码约1/5的区域。 +// 使用高级纠错以确保Logo不影响二维码的可读性。 +// +// 参数: +// +// content: 二维码内容 +// logoPath: Logo图片文件路径(支持PNG、JPEG等格式) +// size: 二维码尺寸(可选,默认256,建议>=512以保证清晰度) +// +// 返回值: +// +// []byte: PNG格式的字节数组 +// error: 错误信息 +// +// 注意事项: +// - Logo会自动缩放到二维码的1/5大小 +// - 建议Logo使用正方形图片 +// - 使用高级纠错级别(~30%容错) +// - Logo会覆盖二维码中心区域 +// - 建议二维码尺寸>=512以保证Logo清晰 +// - Logo文件必须存在且可读取 +func GenerateQRCodeWithLogo(content, logoPath string, size ...int) ([]byte, error) { + qrSize := DefaultQRCodeSize + if len(size) > 0 { + qrSize = size[0] + if qrSize < MinQRCodeSize || qrSize > MaxQRCodeSize { + return nil, fmt.Errorf("二维码尺寸必须在 %d 到 %d 之间", MinQRCodeSize, MaxQRCodeSize) + } + } + + // 生成基础二维码 + qr, err := qrcode.New(content, qrcode.High) + if err != nil { + return nil, fmt.Errorf("创建二维码失败: %w", err) + } + + // 生成二维码图像 + qrImage := qr.Image(qrSize) + + // 读取Logo图片 + logoFile, err := os.Open(logoPath) + if err != nil { + return nil, fmt.Errorf("打开Logo文件失败: %w", err) + } + defer logoFile.Close() + + logoImage, _, err := image.Decode(logoFile) + if err != nil { + return nil, fmt.Errorf("解码Logo图片失败: %w", err) + } + + // 计算Logo位置和大小(Logo占二维码的1/5) + logoSize := qrSize / 5 + logoX := (qrSize - logoSize) / 2 + logoY := (qrSize - logoSize) / 2 + + // 创建最终图像 + finalImage := image.NewRGBA(qrImage.Bounds()) + + // 绘制二维码 + for y := 0; y < qrSize; y++ { + for x := 0; x < qrSize; x++ { + finalImage.Set(x, y, qrImage.At(x, y)) + } + } + + // 绘制Logo + logoOriginalBounds := logoImage.Bounds() + for y := range logoSize { + for x := range logoSize { + // 计算原始Logo的对应像素 + origX := x * logoOriginalBounds.Dx() / logoSize + origY := y * logoOriginalBounds.Dy() / logoSize + finalImage.Set(logoX+x, logoY+y, logoImage.At(origX, origY)) + } + } + + // 转换为PNG字节 + var buf bytes.Buffer + if err := png.Encode(&buf, finalImage); err != nil { + return nil, fmt.Errorf("编码PNG失败: %w", err) + } + + return buf.Bytes(), nil +} + +// SaveQRCodeBytes 保存二维码字节数组到文件 +// +// 将二维码字节数组保存为PNG文件,常与 GenerateQRCodeBytes 或 +// GenerateQRCodeWithConfig 配合使用。 +// +// 参数: +// +// data: PNG格式的二维码字节数组 +// filename: 保存的文件路径 +// +// 返回值: +// +// error: 错误信息 +// +// 注意事项: +// - 文件权限为0644 +// - 会覆盖已存在的文件 +// - 确保目录已存在 +func SaveQRCodeBytes(data []byte, filename string) error { + if err := os.WriteFile(filename, data, 0644); err != nil { + return fmt.Errorf("保存文件失败: %w", err) + } + return nil +} + +// GenerateQRCodeDataURL 生成Data URL格式的二维码 +// +// 生成可以直接用于HTML 标签的Data URL格式字符串。 +// Data URL包含了完整的图片数据,无需额外的图片文件。 +// +// 参数: +// +// content: 二维码内容 +// size: 二维码尺寸(可选,默认256) +// +// 返回值: +// +// string: Data URL格式字符串(包含data:image/png;base64,前缀) +// error: 错误信息 +// +// 注意事项: +// - Data URL字符串较长,不适合存储到数据库 +// - 适合临时显示、前端渲染等场景 +// - 某些老旧浏览器可能不支持 +func GenerateQRCodeDataURL(content string, size ...int) (string, error) { + qrBytes, err := GenerateQRCodeBytes(content, size...) + if err != nil { + return "", err + } + + base64Str := base64.StdEncoding.EncodeToString(qrBytes) + dataURL := fmt.Sprintf("data:image/png;base64,%s", base64Str) + return dataURL, nil +} + +// BatchGenerateQRCode 批量生成二维码文件 +// +// 一次性生成多个二维码文件,适合: +// - 批量生成产品二维码 +// - 生成多个用户的会员卡 +// - 批量生成门票、优惠券等 +// +// 参数: +// +// items: map[内容]文件名,例如:map["产品A":"qr_a.png", "产品B":"qr_b.png"] +// size: 二维码尺寸(可选,默认256,所有二维码使用相同尺寸) +// +// 返回值: +// +// []string: 失败的文件名列表(成功时为nil) +// error: 错误信息(部分失败时返回错误,但成功的文件已生成) +// +// 注意事项: +// - 即使部分失败,成功的二维码仍会生成 +// - 建议检查返回的failed列表以确定哪些失败了 +// - 大批量生成时注意磁盘空间 +func BatchGenerateQRCode(items map[string]string, size ...int) ([]string, error) { + var failed []string + qrSize := DefaultQRCodeSize + if len(size) > 0 { + qrSize = size[0] + } + + for content, filename := range items { + err := GenerateQRCode(content, filename, qrSize) + if err != nil { + failed = append(failed, filename) + } + } + + if len(failed) > 0 { + return failed, fmt.Errorf("有 %d 个二维码生成失败", len(failed)) + } + + return nil, nil +} diff --git a/utils/random.go b/utils/random.go index 176bb2b..b72c82b 100644 --- a/utils/random.go +++ b/utils/random.go @@ -9,7 +9,7 @@ func RandomString(l int) string { str := "0123456789AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz" bytes := []byte(str) var result []byte = make([]byte, 0, l) - for i := 0; i < l; i++ { + for range l { result = append(result, bytes[rand.IntN(len(bytes))]) } return string(result) @@ -20,7 +20,7 @@ func RandomPureString(l int) string { str := "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz" bytes := []byte(str) var result []byte = make([]byte, 0, l) - for i := 0; i < l; i++ { + for range l { result = append(result, bytes[rand.IntN(len(bytes))]) } return string(result) @@ -31,7 +31,7 @@ func RandomPureUpString(l int) string { str := "ABCDEFGHIJKLMNOPQRSTUVWXYZ" bytes := []byte(str) var result []byte = make([]byte, 0, l) - for i := 0; i < l; i++ { + for range l { result = append(result, bytes[rand.IntN(len(bytes))]) } return string(result) @@ -42,7 +42,7 @@ func RandomNumber(l int) string { str := "0123456789" bytes := []byte(str) var result []byte - for i := 0; i < l; i++ { + for range l { result = append(result, bytes[rand.IntN(len(bytes))]) } return string(result)