新增二维码功能模块:qrcode.go;
为http请求添加超时
This commit is contained in:
457
utils/qrcode.go
Normal file
457
utils/qrcode.go
Normal file
@@ -0,0 +1,457 @@
|
||||
// Package utils 提供通用工具函数
|
||||
//
|
||||
// 二维码生成功能模块
|
||||
//
|
||||
// 本模块提供了完整的二维码生成功能,支持:
|
||||
// - 基础二维码生成(保存为PNG文件)
|
||||
// - 生成字节数组(可用于HTTP响应、数据库存储等)
|
||||
// - Base64编码输出(便于存储和传输)
|
||||
// - Data URL格式(可直接用于HTML <img>标签)
|
||||
// - 自定义配置(尺寸、颜色、纠错级别)
|
||||
// - 带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 <img>标签的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
|
||||
}
|
||||
Reference in New Issue
Block a user