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) }