mirror of
https://github.com/wgh136/nysoure.git
synced 2025-09-27 20:27:23 +00:00
Add resampled image retrieval functionality and update image URLs
This commit is contained in:
@@ -24,7 +24,7 @@ export default function ResourceCard({ resource }: { resource: Resource }) {
|
|||||||
{resource.image != null && (
|
{resource.image != null && (
|
||||||
<figure>
|
<figure>
|
||||||
<img
|
<img
|
||||||
src={network.getImageUrl(resource.image.id)}
|
src={network.getResampledImageUrl(resource.image.id)}
|
||||||
alt="cover"
|
alt="cover"
|
||||||
style={{
|
style={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
|
@@ -492,6 +492,10 @@ class Network {
|
|||||||
return `${this.apiBaseUrl}/image/${id}`;
|
return `${this.apiBaseUrl}/image/${id}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getResampledImageUrl(id: number): string {
|
||||||
|
return `${this.apiBaseUrl}/image/resampled/${id}`;
|
||||||
|
}
|
||||||
|
|
||||||
async createResource(
|
async createResource(
|
||||||
params: CreateResourceParams,
|
params: CreateResourceParams,
|
||||||
): Promise<Response<number>> {
|
): Promise<Response<number>> {
|
||||||
|
9
go.mod
9
go.mod
@@ -15,7 +15,10 @@ require (
|
|||||||
gorm.io/driver/mysql v1.5.7
|
gorm.io/driver/mysql v1.5.7
|
||||||
)
|
)
|
||||||
|
|
||||||
require github.com/go-sql-driver/mysql v1.7.0 // indirect
|
require (
|
||||||
|
github.com/disintegration/imaging v1.6.2 // indirect
|
||||||
|
github.com/go-sql-driver/mysql v1.7.0 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
@@ -47,8 +50,8 @@ require (
|
|||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
github.com/valyala/fasthttp v1.61.0 // indirect
|
github.com/valyala/fasthttp v1.61.0 // indirect
|
||||||
github.com/x448/float16 v0.8.4 // indirect
|
github.com/x448/float16 v0.8.4 // indirect
|
||||||
golang.org/x/image v0.27.0
|
golang.org/x/image v0.28.0
|
||||||
golang.org/x/net v0.39.0 // indirect
|
golang.org/x/net v0.39.0 // indirect
|
||||||
golang.org/x/sys v0.32.0 // indirect
|
golang.org/x/sys v0.32.0 // indirect
|
||||||
golang.org/x/text v0.25.0 // indirect
|
golang.org/x/text v0.26.0 // indirect
|
||||||
)
|
)
|
||||||
|
7
go.sum
7
go.sum
@@ -4,6 +4,8 @@ github.com/chai2010/webp v1.4.0 h1:6DA2pkkRUPnbOHvvsmGI3He1hBKf/bkRlniAiSGuEko=
|
|||||||
github.com/chai2010/webp v1.4.0/go.mod h1:0XVwvZWdjjdxpUEIf7b9g9VkHFnInUSYujwqTLEuldU=
|
github.com/chai2010/webp v1.4.0/go.mod h1:0XVwvZWdjjdxpUEIf7b9g9VkHFnInUSYujwqTLEuldU=
|
||||||
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/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||||
|
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU=
|
github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU=
|
||||||
@@ -78,8 +80,11 @@ github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3i
|
|||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||||
|
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w=
|
golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w=
|
||||||
golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g=
|
golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g=
|
||||||
|
golang.org/x/image v0.28.0 h1:gdem5JW1OLS4FbkWgLO+7ZeFzYtL3xClb97GaUzYMFE=
|
||||||
|
golang.org/x/image v0.28.0/go.mod h1:GUJYXtnGKEUgggyzh+Vxt+AviiCcyiwpsl8iQ8MvwGY=
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
|
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
|
||||||
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
|
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
|
||||||
@@ -90,6 +95,8 @@ golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
|||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||||
|
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||||
|
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
@@ -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) {
|
func AddImageRoutes(api fiber.Router) {
|
||||||
image := api.Group("/image")
|
image := api.Group("/image")
|
||||||
{
|
{
|
||||||
image.Put("/", handleUploadImage)
|
image.Put("/", handleUploadImage)
|
||||||
|
image.Get("/resampled/:id", handleGetResampledImage)
|
||||||
image.Get("/:id", handleGetImage)
|
image.Get("/:id", handleGetImage)
|
||||||
image.Delete("/:id", handleDeleteImage)
|
image.Delete("/:id", handleDeleteImage)
|
||||||
}
|
}
|
||||||
|
@@ -3,12 +3,14 @@ package service
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
|
"github.com/disintegration/imaging"
|
||||||
"image"
|
"image"
|
||||||
"net/http"
|
"net/http"
|
||||||
"nysoure/server/dao"
|
"nysoure/server/dao"
|
||||||
"nysoure/server/model"
|
"nysoure/server/model"
|
||||||
"nysoure/server/utils"
|
"nysoure/server/utils"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v3/log"
|
"github.com/gofiber/fiber/v3/log"
|
||||||
@@ -24,6 +26,10 @@ import (
|
|||||||
"github.com/chai2010/webp"
|
"github.com/chai2010/webp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
resampledMaxPixels = 1280 * 720
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// Start a goroutine to delete unused images every hour
|
// Start a goroutine to delete unused images every hour
|
||||||
go func() {
|
go func() {
|
||||||
@@ -152,11 +158,83 @@ func deleteImage(id uint) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
imageDir := utils.GetStoragePath() + "/images/"
|
imageDir := utils.GetStoragePath() + "/images/"
|
||||||
|
|
||||||
_ = os.Remove(imageDir + i.FileName)
|
_ = 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 {
|
if err := dao.DeleteImage(id); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user