@@ -1,457 +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
}
// 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 := 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 <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
}