Add resampled image retrieval functionality and update image URLs

This commit is contained in:
2025-06-23 21:19:42 +08:00
parent dcd23054b2
commit be067cc21a
6 changed files with 117 additions and 5 deletions

View File

@@ -71,10 +71,30 @@ func handleDeleteImage(c fiber.Ctx) error {
})
}
func handleGetResampledImage(c fiber.Ctx) error {
idStr := c.Params("id")
if idStr == "" {
return model.NewRequestError("Image ID is required")
}
id, err := strconv.Atoi(idStr)
if err != nil {
return model.NewRequestError("Invalid image ID")
}
image, err := service.GetResampledImage(uint(id))
if err != nil {
return err
}
contentType := http.DetectContentType(image)
c.Set("Content-Type", contentType)
c.Set("Cache-Control", "public, max-age=31536000")
return c.Send(image)
}
func AddImageRoutes(api fiber.Router) {
image := api.Group("/image")
{
image.Put("/", handleUploadImage)
image.Get("/resampled/:id", handleGetResampledImage)
image.Get("/:id", handleGetImage)
image.Delete("/:id", handleDeleteImage)
}

View File

@@ -3,12 +3,14 @@ package service
import (
"bytes"
"errors"
"github.com/disintegration/imaging"
"image"
"net/http"
"nysoure/server/dao"
"nysoure/server/model"
"nysoure/server/utils"
"os"
"strconv"
"time"
"github.com/gofiber/fiber/v3/log"
@@ -24,6 +26,10 @@ import (
"github.com/chai2010/webp"
)
const (
resampledMaxPixels = 1280 * 720
)
func init() {
// Start a goroutine to delete unused images every hour
go func() {
@@ -152,11 +158,83 @@ func deleteImage(id uint) error {
}
imageDir := utils.GetStoragePath() + "/images/"
_ = os.Remove(imageDir + i.FileName)
resampledDir := utils.GetStoragePath() + "/resampled/"
_ = os.Remove(resampledDir + strconv.Itoa(int(i.ID)) + ".webp")
if err := dao.DeleteImage(id); err != nil {
return err
}
return nil
}
func GetResampledImage(id uint) ([]byte, error) {
i, err := dao.GetImageByID(id)
if err != nil {
return nil, err
}
data, err := getOrCreateResampledImage(i)
if err != nil {
log.Error("Error getting or creating resampled image:", err)
return nil, model.NewInternalServerError("Error processing image")
}
return data, nil
}
func getOrCreateResampledImage(i model.Image) ([]byte, error) {
baseDir := utils.GetStoragePath() + "/resampled/"
if _, err := os.Stat(baseDir); os.IsNotExist(err) {
if err := os.MkdirAll(baseDir, 0755); err != nil {
return nil, err
}
}
resampledFilepath := baseDir + strconv.Itoa(int(i.ID)) + ".webp"
if _, err := os.Stat(resampledFilepath); err != nil {
if !os.IsNotExist(err) {
return nil, err
}
} else {
return os.ReadFile(resampledFilepath)
}
originalFilepath := utils.GetStoragePath() + "/images/" + i.FileName
if _, err := os.Stat(originalFilepath); os.IsNotExist(err) {
return nil, model.NewNotFoundError("Original image not found")
}
imgData, err := os.ReadFile(originalFilepath)
if err != nil {
return nil, errors.New("failed to read original image file")
}
if i.Width*i.Height <= resampledMaxPixels {
return imgData, nil
}
log.Info("Resampling image", "id", i.ID, "original size", i.Width, "x", i.Height)
img, _, err := image.Decode(bytes.NewReader(imgData))
if err != nil {
return nil, errors.New("failed to decode original image data")
}
pixels := img.Bounds().Dx() * img.Bounds().Dy()
if pixels <= resampledMaxPixels {
return imgData, nil // No need to resample if the image is small enough
}
scale := float64(resampledMaxPixels) / float64(pixels)
dstWidth := int(float64(img.Bounds().Dx()) * scale)
dstHeight := int(float64(img.Bounds().Dy()) * scale)
dstImg := imaging.Resize(img, dstWidth, dstHeight, imaging.Lanczos)
buf := new(bytes.Buffer)
if err := webp.Encode(buf, dstImg, &webp.Options{Quality: 80}); err != nil {
return nil, errors.New("failed to encode resampled image data to webp format")
}
if err := os.WriteFile(resampledFilepath, buf.Bytes(), 0644); err != nil {
return nil, errors.New("failed to save resampled image file")
}
return buf.Bytes(), nil
}