Compare commits

..

4 Commits

10 changed files with 84 additions and 3 deletions

1
.env
View File

@ -4,6 +4,7 @@ APIKEY=lfk
EVENTNAME=Lauf für Kaya! 2025 EVENTNAME=Lauf für Kaya! 2025
CURRENCYSYMBOL= CURRENCYSYMBOL=
GOTENBERG_BASEURL=http://localhost:3001 GOTENBERG_BASEURL=http://localhost:3001
REDIS_ADDR=localhost:6379
CARD_SUBTITLE=Kaya ist cool CARD_SUBTITLE=Kaya ist cool
CARD_BARCODEFORMAT=ean13 CARD_BARCODEFORMAT=ean13

View File

@ -68,3 +68,29 @@ docker build -t registry.odit.services/lfk/document-server:latest .
# multiarch # multiarch
docker buildx build --platform=linux/amd64,linux/arm64 -t registry.odit.services/lfk/document-server:latest --push . docker buildx build --platform=linux/amd64,linux/arm64 -t registry.odit.services/lfk/document-server:latest --push .
``` ```
## ⏱️ Benchmarks
### Barcode Generation
- Requests: 10000
- Concurrency: Unlimited
- Data: `123456789123`
- Width: 1000
- Height: 500
#### No cache
| Format | Requests/sec | Slowest | Fastest | Average |
| ------- | ------------ | ------- | ------- | ------- |
| Code128 | 763.3996 | 0.7995 | 0.0172 | 0.0654 |
| EAN13 | 767.1170 | 0.7607 | 0.0171 | 0.0651 |
| QR | 683.8472 | 0.6528 | 0.0178 | 0.0730 |
#### Redis cache
| Format | Requests/sec | Slowest | Fastest | Average |
| ------- | ------------ | ------- | ------- | ------- |
| Code128 | 15611.5521 | 0.0965 | 0.0006 | 0.0032 |
| EAN13 | 14985.4401 | 0.0925 | 0.0006 | 0.0033 |
| QR | 14306.2540 | 0.1143 | 0.0005 | 0.0035 |

View File

@ -2,4 +2,8 @@ services:
gotenberg: gotenberg:
image: gotenberg/gotenberg:8 image: gotenberg/gotenberg:8
ports: ports:
- "3001:3000" - "3001:3000"
redis:
image: docker.dragonflydb.io/dragonflydb/dragonfly
ports:
- "6379:6379"

View File

@ -8,4 +8,8 @@ services:
gotenberg: gotenberg:
image: gotenberg/gotenberg:8 image: gotenberg/gotenberg:8
ports: ports:
- "3001:3000" - "3001:3000"
redis:
image: docker.dragonflydb.io/dragonflydb/dragonfly
ports:
- "6379:6379"

View File

@ -4,6 +4,7 @@ APIKEY=lfk
EVENTNAME=Lauf für Kaya! 2025 EVENTNAME=Lauf für Kaya! 2025
CURRENCYSYMBOL= CURRENCYSYMBOL=
GOTENBERG_BASEURL=http://gotenberg:3000 GOTENBERG_BASEURL=http://gotenberg:3000
REDIS_ADDR=redis:6379
CARD_SUBTITLE=Kaya ist cool CARD_SUBTITLE=Kaya ist cool
CARD_BARCODEFORMAT=ean13 CARD_BARCODEFORMAT=ean13

3
go.mod
View File

@ -14,6 +14,8 @@ require (
github.com/KyleBanks/depth v1.2.1 // indirect github.com/KyleBanks/depth v1.2.1 // indirect
github.com/andybalholm/brotli v1.1.1 // indirect github.com/andybalholm/brotli v1.1.1 // indirect
github.com/boombuler/barcode v1.0.2 // indirect github.com/boombuler/barcode v1.0.2 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect
@ -32,6 +34,7 @@ require (
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/oxplot/papersizes v0.0.0-20181201065918-90a3a5ae1915 // indirect github.com/oxplot/papersizes v0.0.0-20181201065918-90a3a5ae1915 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/redis/go-redis/v9 v9.7.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect

6
go.sum
View File

@ -4,9 +4,13 @@ github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7X
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
github.com/boombuler/barcode v1.0.2 h1:79yrbttoZrLGkL/oOI8hBrUKucwOL0oOjUgEguGMcJ4= github.com/boombuler/barcode v1.0.2 h1:79yrbttoZrLGkL/oOI8hBrUKucwOL0oOjUgEguGMcJ4=
github.com/boombuler/barcode v1.0.2/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.2/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
@ -54,6 +58,8 @@ github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=

13
main.go
View File

@ -12,6 +12,7 @@ import (
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/keyauth" "github.com/gofiber/fiber/v2/middleware/keyauth"
"github.com/gofiber/swagger" "github.com/gofiber/swagger"
"github.com/redis/go-redis/v9"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
@ -73,7 +74,17 @@ func main() {
log.Fatal(err) log.Fatal(err)
} }
barcodeGenerator := &services.DefaultBarcodeService{} var redisClient *redis.Client
if config.RedisAddr != "" {
log.Println("Using redis at", config.RedisAddr)
redisClient = redis.NewClient(&redis.Options{
Addr: config.RedisAddr,
})
}
barcodeGenerator := &services.DefaultBarcodeService{
RedisClient: redisClient,
}
staticService := &services.DefaultStaticService{ staticService := &services.DefaultStaticService{
Cache: make(map[string]string), Cache: make(map[string]string),
} }

View File

@ -15,4 +15,5 @@ type Config struct {
SponsoringBarcodePrefix string `mapstructure:"SPONSORING_BARCODEPREFIX"` SponsoringBarcodePrefix string `mapstructure:"SPONSORING_BARCODEPREFIX"`
CertificateFooter string `mapstructure:"CERTIFICATE_FOOTER"` CertificateFooter string `mapstructure:"CERTIFICATE_FOOTER"`
GotenbergBaseUrl string `mapstructure:"GOTENBERG_BASEURL"` GotenbergBaseUrl string `mapstructure:"GOTENBERG_BASEURL"`
RedisAddr string `mapstructure:"REDIS_ADDR"`
} }

View File

@ -2,14 +2,18 @@ package services
import ( import (
"bytes" "bytes"
"context"
"fmt" "fmt"
"image/png" "image/png"
"log"
"slices" "slices"
"time"
"github.com/boombuler/barcode" "github.com/boombuler/barcode"
"github.com/boombuler/barcode/code128" "github.com/boombuler/barcode/code128"
"github.com/boombuler/barcode/ean" "github.com/boombuler/barcode/ean"
"github.com/boombuler/barcode/qr" "github.com/boombuler/barcode/qr"
"github.com/redis/go-redis/v9"
) )
type BarcodeService interface { type BarcodeService interface {
@ -18,13 +22,26 @@ type BarcodeService interface {
} }
type DefaultBarcodeService struct { type DefaultBarcodeService struct {
RedisClient *redis.Client
} }
func (b *DefaultBarcodeService) GenerateBarcode(format string, content string, width int, height int) (bytes.Buffer, error) { func (b *DefaultBarcodeService) GenerateBarcode(format string, content string, width int, height int) (bytes.Buffer, error) {
ctx := context.Background()
if !b.IsTypeSupported(format) { if !b.IsTypeSupported(format) {
return bytes.Buffer{}, fmt.Errorf("unsupported barcode type: %s", format) return bytes.Buffer{}, fmt.Errorf("unsupported barcode type: %s", format)
} }
if b.RedisClient != nil {
cachedBarcode, err := b.RedisClient.Get(ctx, fmt.Sprintf("barcode:%s:%s:%d:%d", format, content, width, height)).Result()
if err == nil {
log.Printf("Cache hit for barcode:%s:%s:%d:%d", format, content, width, height)
buf := bytes.Buffer{}
buf.Write([]byte(cachedBarcode))
return buf, nil
}
}
var generatedCode barcode.Barcode var generatedCode barcode.Barcode
var err error var err error
@ -60,6 +77,13 @@ func (b *DefaultBarcodeService) GenerateBarcode(format string, content string, w
return bytes.Buffer{}, err return bytes.Buffer{}, err
} }
if b.RedisClient != nil {
err = b.RedisClient.Set(ctx, fmt.Sprintf("barcode:%s:%s:%d:%d", format, content, width, height), buf.String(), 10*time.Minute).Err()
if err != nil {
return bytes.Buffer{}, err
}
}
return buf, nil return buf, nil
} }