130 lines
3.6 KiB
Go
130 lines
3.6 KiB
Go
package services
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"image"
|
|
"image/color"
|
|
"image/draw"
|
|
"image/png"
|
|
"slices"
|
|
"time"
|
|
|
|
"github.com/boombuler/barcode"
|
|
"github.com/boombuler/barcode/code128"
|
|
"github.com/boombuler/barcode/ean"
|
|
"github.com/boombuler/barcode/qr"
|
|
"github.com/redis/go-redis/v9"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
type BarcodeService interface {
|
|
GenerateBarcode(format string, content string, width int, height int, padding int) (bytes.Buffer, error)
|
|
IsTypeSupported(format string) bool
|
|
}
|
|
|
|
type DefaultBarcodeService struct {
|
|
RedisClient *redis.Client
|
|
Logger *zap.SugaredLogger
|
|
}
|
|
|
|
func (b *DefaultBarcodeService) GenerateBarcode(format string, content string, width int, height int, padding int) (bytes.Buffer, error) {
|
|
ctx := context.Background()
|
|
logger := b.Logger.Named("GenerateBarcode")
|
|
|
|
if !b.IsTypeSupported(format) {
|
|
logger.Errorw("Unsupported barcode type", "type", format)
|
|
return bytes.Buffer{}, fmt.Errorf("unsupported barcode type: %s", format)
|
|
}
|
|
|
|
logger = logger.With("type", format, "content", content, "width", width, "height", height, "padding", padding)
|
|
cacheKey := fmt.Sprintf("barcode:%s:%s:%d:%d:%d", format, content, width, height, padding)
|
|
|
|
if b.RedisClient != nil {
|
|
logger.Debugw("Checking cache for barcode", "key", cacheKey)
|
|
cachedBarcode, err := b.RedisClient.Get(ctx, cacheKey).Result()
|
|
if err == nil {
|
|
logger.Infow("Barcode found in cache", "key", cacheKey)
|
|
buf := bytes.Buffer{}
|
|
buf.Write([]byte(cachedBarcode))
|
|
return buf, nil
|
|
}
|
|
}
|
|
|
|
var generatedCode barcode.Barcode
|
|
var err error
|
|
|
|
switch format {
|
|
case "ean13":
|
|
generatedCode, err = ean.Encode(content)
|
|
if err != nil {
|
|
return bytes.Buffer{}, err
|
|
}
|
|
break
|
|
case "code128":
|
|
generatedCode, err = code128.Encode(content)
|
|
if err != nil {
|
|
return bytes.Buffer{}, err
|
|
}
|
|
break
|
|
case "qr":
|
|
// Always use qr.Auto encoding to support all characters in the content
|
|
encoding := qr.Auto
|
|
|
|
// QR code generation with error correction level M and auto encoding
|
|
generatedCode, err = qr.Encode(content, qr.M, encoding)
|
|
if err != nil {
|
|
return bytes.Buffer{}, err
|
|
}
|
|
break
|
|
}
|
|
|
|
// Create a white background image
|
|
bg := image.NewRGBA(image.Rect(0, 0, width, height))
|
|
white := color.RGBA{255, 255, 255, 255}
|
|
draw.Draw(bg, bg.Bounds(), &image.Uniform{white}, image.Point{}, draw.Src)
|
|
logger.Debug("Created white background")
|
|
|
|
// Calculate the new size for the barcode to fit within the padding
|
|
newWidth := width - 2*padding
|
|
newHeight := height - 2*padding
|
|
|
|
// Scale the barcode to the new size
|
|
scaledCode, err := barcode.Scale(generatedCode, newWidth, newHeight)
|
|
if err != nil {
|
|
logger.Errorw("Failed to scale barcode", "error", err)
|
|
return bytes.Buffer{}, err
|
|
}
|
|
logger.Debug("Scaled barcode")
|
|
|
|
// Draw the barcode on top of the white background with padding
|
|
draw.Draw(bg, scaledCode.Bounds().Add(image.Point{padding, padding}), scaledCode, image.Point{}, draw.Over)
|
|
logger.Debug("Drew barcode on background")
|
|
|
|
var buf bytes.Buffer
|
|
err = png.Encode(&buf, bg)
|
|
if err != nil {
|
|
logger.Errorw("Failed to encode barcode to PNG", "error", err)
|
|
return bytes.Buffer{}, err
|
|
}
|
|
logger.Debug("Encoded barcode to PNG")
|
|
|
|
if b.RedisClient != nil {
|
|
err = b.RedisClient.Set(ctx, cacheKey, buf.String(), 10*time.Minute).Err()
|
|
logger.Debugw("Cached barcode", "key", cacheKey)
|
|
if err != nil {
|
|
logger.Errorw("Failed to cache barcode", "error", err)
|
|
return bytes.Buffer{}, err
|
|
}
|
|
}
|
|
logger.Info("Generated barcode")
|
|
|
|
return buf, nil
|
|
}
|
|
|
|
func (b *DefaultBarcodeService) IsTypeSupported(format string) bool {
|
|
supportedTypes := []string{"ean13", "code128", "qr"}
|
|
return slices.Contains(supportedTypes, format)
|
|
}
|