feat avatar compress uploads to 20kb

This commit is contained in:
IanShaw027
2026-04-21 08:53:59 +08:00
parent 07f23aaa7d
commit 6da08262d7
8 changed files with 321 additions and 44 deletions

View File

@@ -3,11 +3,14 @@
package service
import (
"bytes"
"context"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"errors"
"image"
"image/png"
"sync"
"sync/atomic"
"testing"
@@ -361,6 +364,57 @@ func TestUpdateProfile_StoresInlineAvatarWithinLimit(t *testing.T) {
require.Equal(t, hex.EncodeToString(expectedSum[:]), updated.AvatarSHA256)
}
func TestUpdateProfile_CompressesInlineAvatarToTwentyKilobytes(t *testing.T) {
var encoded bytes.Buffer
for _, size := range []int{192, 224, 256, 288} {
encoded.Reset()
var img image.RGBA
img.Rect = image.Rect(0, 0, size, size)
img.Stride = size * 4
img.Pix = make([]byte, size*size*4)
for y := 0; y < size; y++ {
for x := 0; x < size; x++ {
offset := y*img.Stride + x*4
img.Pix[offset] = uint8((x*x + y*17) % 255)
img.Pix[offset+1] = uint8((y*y + x*29) % 255)
img.Pix[offset+2] = uint8(((x * y) + x*13 + y*7) % 255)
img.Pix[offset+3] = 0xff
}
}
require.NoError(t, png.Encode(&encoded, &img))
if encoded.Len() > 20*1024 && encoded.Len() <= maxInlineAvatarBytes {
break
}
}
require.Greater(t, encoded.Len(), 20*1024)
require.LessOrEqual(t, encoded.Len(), maxInlineAvatarBytes)
dataURL := "data:image/png;base64," + base64.StdEncoding.EncodeToString(encoded.Bytes())
repo := &mockUserRepo{
getByIDUser: &User{
ID: 17,
Email: "avatar-compress@example.com",
Username: "avatar-compress",
},
}
svc := NewUserService(repo, nil, nil, nil)
updated, err := svc.UpdateProfile(context.Background(), 17, UpdateProfileRequest{
AvatarURL: &dataURL,
})
require.NoError(t, err)
require.Len(t, repo.upsertAvatarArgs, 1)
require.Equal(t, "inline", repo.upsertAvatarArgs[0].StorageProvider)
require.LessOrEqual(t, repo.upsertAvatarArgs[0].ByteSize, 20*1024)
require.Equal(t, "image/jpeg", repo.upsertAvatarArgs[0].ContentType)
require.Contains(t, repo.upsertAvatarArgs[0].URL, "data:image/jpeg;base64,")
require.Equal(t, "inline", updated.AvatarSource)
require.Equal(t, "image/jpeg", updated.AvatarMIME)
require.LessOrEqual(t, updated.AvatarByteSize, 20*1024)
require.Contains(t, updated.AvatarURL, "data:image/jpeg;base64,")
require.NotEmpty(t, updated.AvatarSHA256)
}
func TestUpdateProfile_RejectsInlineAvatarOverLimit(t *testing.T) {
raw := make([]byte, maxInlineAvatarBytes+1)
dataURL := "data:image/png;base64," + base64.StdEncoding.EncodeToString(raw)