Compare commits
No commits in common. "576359798f3159032936b5cf6c727e2f38d5086a" and "8220922cc9a3f62c6d31bb16d835b3edaf4cfb60" have entirely different histories.
576359798f
...
8220922cc9
4
.github/workflows/main.yml
vendored
4
.github/workflows/main.yml
vendored
@ -22,12 +22,10 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Build and push api
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@v5
|
||||
env:
|
||||
ACTIONS_RUNTIME_TOKEN: ''
|
||||
with:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
provenance: false
|
||||
context: .
|
||||
push: true
|
||||
tags: git.lovepin.app/nisan/avatars:latest
|
||||
@ -21,7 +21,7 @@ RUN go mod download
|
||||
RUN go mod verify
|
||||
|
||||
# Build the binary.
|
||||
RUN CGO_ENABLED=0 go build -ldflags="-w -s" -o /go/bin/avatars
|
||||
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o /go/bin/avatars
|
||||
|
||||
############################
|
||||
# STEP 2 build a small image
|
||||
|
||||
94
main.go
94
main.go
@ -11,7 +11,6 @@ import (
|
||||
"io/fs"
|
||||
"log"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -20,54 +19,6 @@ import (
|
||||
"golang.org/x/image/draw"
|
||||
)
|
||||
|
||||
type cacheItem struct {
|
||||
value []byte
|
||||
lastAccess int64
|
||||
}
|
||||
type AvatarCache struct {
|
||||
cache map[string]*cacheItem
|
||||
mu *sync.RWMutex
|
||||
}
|
||||
|
||||
func (ac *AvatarCache) Clear() {
|
||||
ac.mu.Lock()
|
||||
for k, v := range ac.cache {
|
||||
if time.Now().Unix()-v.lastAccess > int64(100) {
|
||||
delete(ac.cache, k)
|
||||
}
|
||||
}
|
||||
ac.mu.Unlock()
|
||||
}
|
||||
|
||||
// insert to cache
|
||||
func (ac *AvatarCache) Insert(id string, size int, value []byte) {
|
||||
ac.mu.Lock()
|
||||
defer ac.mu.Unlock()
|
||||
ac.cache[id+strconv.Itoa(size)] = &cacheItem{value: value, lastAccess: time.Now().Unix()}
|
||||
// remove oldest item if cache is full
|
||||
if len(ac.cache) > 1000 {
|
||||
for k := range ac.cache {
|
||||
delete(ac.cache, k)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get from cache
|
||||
func (ac *AvatarCache) Get(id string, size int) []byte {
|
||||
ac.mu.Lock()
|
||||
defer ac.mu.Unlock()
|
||||
item, ok := ac.cache[id+strconv.Itoa(size)]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
item.lastAccess = time.Now().Unix()
|
||||
return item.value
|
||||
}
|
||||
|
||||
// create new cache
|
||||
var avatarCache = AvatarCache{cache: make(map[string]*cacheItem), mu: &sync.RWMutex{}}
|
||||
|
||||
//go:embed assets/*
|
||||
var files embed.FS
|
||||
|
||||
@ -101,31 +52,15 @@ func serveAvatar(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// get the id and size from the request
|
||||
id := r.PathValue("id")
|
||||
sizeStr := r.PathValue("size")
|
||||
if sizeStr == "" {
|
||||
sizeStr = r.URL.Query().Get("s")
|
||||
}
|
||||
size, err := strconv.Atoi(sizeStr)
|
||||
size, err := strconv.Atoi(r.PathValue("size"))
|
||||
if err != nil {
|
||||
size = 400
|
||||
}
|
||||
// min size is 32, max size is 2048
|
||||
// min size is 32, max size is 1000
|
||||
if size < 32 {
|
||||
size = 32
|
||||
} else if size > 2048 {
|
||||
size = 2048
|
||||
}
|
||||
if avatarCache.Get(id, size) != nil {
|
||||
// get from cache
|
||||
w.Header().Set("content-type", "image/jpeg")
|
||||
w.WriteHeader(http.StatusFound)
|
||||
byteSize, err := w.Write(avatarCache.Get(id, size))
|
||||
if err != nil {
|
||||
log.Println(err.Error())
|
||||
}
|
||||
log.Printf("[id:%s][size:%d] [%s] [%s] [%d bytes] [%v] [cache hit]\n",
|
||||
id, size, r.RemoteAddr, r.Proto, byteSize, time.Since(startTime))
|
||||
return
|
||||
} else if size > 1000 {
|
||||
size = 1000
|
||||
}
|
||||
|
||||
img := image.NewRGBA(emptyRect)
|
||||
@ -139,38 +74,26 @@ func serveAvatar(w http.ResponseWriter, r *http.Request) {
|
||||
if size != 400 {
|
||||
// resize the image
|
||||
dst := image.NewRGBA(image.Rect(0, 0, size, size))
|
||||
draw.CatmullRom.Scale(dst, dst.Bounds(), img, emptyRect, draw.Over, nil)
|
||||
draw.ApproxBiLinear.Scale(dst, dst.Bounds(), img, emptyRect, draw.Over, nil)
|
||||
img = dst
|
||||
}
|
||||
|
||||
// write the image to the response
|
||||
w.Header().Set("content-type", "image/jpeg")
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
buf := new(bytes.Buffer)
|
||||
err = jpeg.Encode(buf, img, nil)
|
||||
err = jpeg.Encode(w, img, nil)
|
||||
if err != nil {
|
||||
log.Println(err.Error())
|
||||
}
|
||||
byteSize, err := w.Write(buf.Bytes())
|
||||
if err != nil {
|
||||
log.Println(err.Error())
|
||||
}
|
||||
avatarCache.Insert(id, size, buf.Bytes())
|
||||
log.Printf("[id:%s][size:%d] [%s] [%s] [%d bytes] [%v]\n",
|
||||
id, size, r.RemoteAddr, r.Proto, byteSize, time.Since(startTime))
|
||||
log.Printf("[id:%s][size:%d] served to [%s] [%s] [%v]\n", id, size, r.RemoteAddr, r.Proto, time.Since(startTime))
|
||||
}
|
||||
|
||||
func main() {
|
||||
go func() {
|
||||
for range time.Tick(time.Minute) {
|
||||
avatarCache.Clear()
|
||||
}
|
||||
}()
|
||||
log.Println("assets loaded")
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("GET /{id}", serveAvatar)
|
||||
mux.HandleFunc("GET /{size}/{id}", serveAvatar)
|
||||
http.ListenAndServe("0.0.0.0:3382", mux)
|
||||
log.Println("Server started at 0.0.0.0:3382")
|
||||
}
|
||||
|
||||
func init() {
|
||||
@ -217,5 +140,4 @@ func init() {
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Printf("%d different combinations loaded\n", len(BaseRects)*len(EYES)*len(NOSE)*len(MOUTH))
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user