mirror of
https://github.com/woodchen-ink/webp_server_go.git
synced 2025-07-19 14:12:01 +08:00
feat(encoder): add ProcessAndSaveImage function to handle image processing and saving
This commit is contained in:
parent
be7aed64a9
commit
c5ed3a1fea
@ -5,6 +5,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"slices"
|
"slices"
|
||||||
|
"strings"
|
||||||
"webp_server_go/config"
|
"webp_server_go/config"
|
||||||
|
|
||||||
"github.com/davidbyttow/govips/v2/vips"
|
"github.com/davidbyttow/govips/v2/vips"
|
||||||
@ -196,3 +197,60 @@ func preProcessImage(img *vips.ImageRef, imageType string, extraParams config.Ex
|
|||||||
log.Debug("图像预处理完成")
|
log.Debug("图像预处理完成")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ProcessAndSaveImage(rawImageAbs, exhaustFilename string, extraParams config.ExtraParams) error {
|
||||||
|
// 创建目标目录
|
||||||
|
if err := os.MkdirAll(path.Dir(exhaustFilename), 0755); err != nil {
|
||||||
|
log.Errorf("创建目标目录失败: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载图像
|
||||||
|
img, err := vips.LoadImageFromFile(rawImageAbs, &vips.ImportParams{
|
||||||
|
FailOnError: boolFalse,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("加载图像失败: 文件=%s, 错误=%v", rawImageAbs, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer img.Close()
|
||||||
|
|
||||||
|
// 调整图像大小
|
||||||
|
if err := resizeImage(img, extraParams); err != nil {
|
||||||
|
log.Warnf("调整图像大小失败: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除元数据(如果配置要求)
|
||||||
|
if config.Config.StripMetadata {
|
||||||
|
log.Debug("正在移除图像元数据")
|
||||||
|
img.RemoveMetadata()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确定输出格式
|
||||||
|
outputFormat := vips.ImageTypeWebP // 默认使用 WebP
|
||||||
|
if strings.HasSuffix(exhaustFilename, ".avif") {
|
||||||
|
outputFormat = vips.ImageTypeAVIF
|
||||||
|
} else if strings.HasSuffix(exhaustFilename, ".jxl") {
|
||||||
|
outputFormat = vips.ImageTypeJPEG // 假设 JXL 不直接支持,使用 JPEG
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出图像
|
||||||
|
buf, _, err := img.Export(vips.ExportParams{
|
||||||
|
Format: outputFormat,
|
||||||
|
Quality: config.Config.Quality,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("导出图像失败: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 写入文件
|
||||||
|
if err := os.WriteFile(exhaustFilename, buf, 0600); err != nil {
|
||||||
|
log.Errorf("写入目标文件失败: 文件=%s, 错误=%v", exhaustFilename, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("图像处理成功: 目标文件=%s", exhaustFilename)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -13,7 +13,6 @@ import (
|
|||||||
"webp_server_go/helper"
|
"webp_server_go/helper"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
"github.com/patrickmn/go-cache"
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -64,58 +63,16 @@ func downloadFile(filepath string, url string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func fetchRemoteImg(url string, subdir string) config.MetaFile {
|
func fetchRemoteImg(url string, subdir string) config.MetaFile {
|
||||||
cacheKey := subdir + ":" + helper.HashString(url)
|
|
||||||
|
|
||||||
var metadata config.MetaFile
|
var metadata config.MetaFile
|
||||||
var etag string
|
|
||||||
var size int64
|
|
||||||
var lastModified time.Time
|
|
||||||
|
|
||||||
if cachedETag, found := config.RemoteCache.Get(cacheKey); found {
|
metadata = helper.ReadMetadata(url, "", subdir)
|
||||||
etag = cachedETag.(string)
|
|
||||||
log.Infof("使用缓存的 ETag 进行远程地址: %s", url)
|
|
||||||
} else {
|
|
||||||
log.Infof("远程地址是 %s,正在 ping 获取信息...", url)
|
|
||||||
etag, size, lastModified = pingURL(url)
|
|
||||||
if etag != "" {
|
|
||||||
config.RemoteCache.Set(cacheKey, etag, cache.DefaultExpiration)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
metadata = helper.ReadMetadata(url, etag, subdir)
|
|
||||||
localRawImagePath := path.Join(config.Config.RemoteRawPath, subdir, metadata.Id)
|
localRawImagePath := path.Join(config.Config.RemoteRawPath, subdir, metadata.Id)
|
||||||
localExhaustImagePath := path.Join(config.Config.ExhaustPath, subdir, metadata.Id)
|
|
||||||
|
|
||||||
needUpdate := false
|
|
||||||
if !helper.ImageExists(localRawImagePath) {
|
if !helper.ImageExists(localRawImagePath) {
|
||||||
log.Info("在远程原始文件中找不到远程文件,正在获取...")
|
log.Info("在远程原始文件中找不到远程文件,正在获取...")
|
||||||
needUpdate = true
|
|
||||||
} else {
|
|
||||||
localFileInfo, err := os.Stat(localRawImagePath)
|
|
||||||
if err == nil {
|
|
||||||
if size > 0 && size != localFileInfo.Size() {
|
|
||||||
log.Info("文件大小已更改,正在更新...")
|
|
||||||
needUpdate = true
|
|
||||||
} else if !lastModified.IsZero() && lastModified.After(localFileInfo.ModTime()) {
|
|
||||||
log.Info("远程文件较新,正在更新...")
|
|
||||||
needUpdate = true
|
|
||||||
} else if metadata.Checksum != helper.HashString(etag) {
|
|
||||||
log.Info("ETag 已更改,正在更新...")
|
|
||||||
needUpdate = true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Warnf("检查本地文件时出错: %v", err)
|
|
||||||
needUpdate = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if needUpdate {
|
|
||||||
cleanProxyCache(localExhaustImagePath)
|
|
||||||
helper.DeleteMetadata(url, subdir)
|
|
||||||
helper.WriteMetadata(url, etag, subdir)
|
|
||||||
downloadFile(localRawImagePath, url)
|
downloadFile(localRawImagePath, url)
|
||||||
// 重新读取更新后的元数据
|
// 重新读取更新后的元数据
|
||||||
metadata = helper.ReadMetadata(url, etag, subdir)
|
metadata = helper.WriteMetadata(url, "", subdir)
|
||||||
}
|
}
|
||||||
|
|
||||||
return metadata
|
return metadata
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -20,18 +21,12 @@ func Convert(c *fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
reqHostname = c.Hostname()
|
|
||||||
reqHeader = &c.Request().Header
|
|
||||||
|
|
||||||
reqURIRaw, _ = url.QueryUnescape(c.Path())
|
reqURIRaw, _ = url.QueryUnescape(c.Path())
|
||||||
reqURIwithQueryRaw, _ = url.QueryUnescape(c.OriginalURL())
|
reqURIwithQueryRaw, _ = url.QueryUnescape(c.OriginalURL())
|
||||||
reqURI = path.Clean(reqURIRaw)
|
reqURI = path.Clean(reqURIRaw)
|
||||||
reqURIwithQuery = path.Clean(reqURIwithQueryRaw)
|
reqURIwithQuery = path.Clean(reqURIwithQueryRaw)
|
||||||
|
|
||||||
filename = path.Base(reqURI)
|
filename = path.Base(reqURI)
|
||||||
realRemoteAddr = ""
|
|
||||||
targetHostName = ""
|
|
||||||
targetHost = ""
|
|
||||||
|
|
||||||
width, _ = strconv.Atoi(c.Query("width"))
|
width, _ = strconv.Atoi(c.Query("width"))
|
||||||
height, _ = strconv.Atoi(c.Query("height"))
|
height, _ = strconv.Atoi(c.Query("height"))
|
||||||
@ -45,7 +40,7 @@ func Convert(c *fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
log.Debugf("Incoming connection from %s %s %s", c.IP(), reqHostname, reqURIwithQuery)
|
log.Debugf("Incoming connection from %s %s", c.IP(), reqURIwithQuery)
|
||||||
|
|
||||||
// 检查路径是否匹配 IMG_MAP 中的任何前缀
|
// 检查路径是否匹配 IMG_MAP 中的任何前缀
|
||||||
var matchedPrefix string
|
var matchedPrefix string
|
||||||
@ -64,100 +59,47 @@ func Convert(c *fiber.Ctx) error {
|
|||||||
return c.SendStatus(fiber.StatusNotFound)
|
return c.SendStatus(fiber.StatusNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 构建 EXHAUST_PATH 中的文件路径
|
||||||
|
exhaustFilename := path.Join(config.Config.ExhaustPath, reqURI)
|
||||||
|
if extraParams.Width > 0 || extraParams.Height > 0 || extraParams.MaxWidth > 0 || extraParams.MaxHeight > 0 {
|
||||||
|
ext := path.Ext(exhaustFilename)
|
||||||
|
extraParamsStr := fmt.Sprintf("_w%d_h%d_mw%d_mh%d", extraParams.Width, extraParams.Height, extraParams.MaxWidth, extraParams.MaxHeight)
|
||||||
|
exhaustFilename = exhaustFilename[:len(exhaustFilename)-len(ext)] + extraParamsStr + ext
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查文件是否已经在 EXHAUST_PATH 中
|
||||||
|
if helper.FileExists(exhaustFilename) {
|
||||||
|
log.Infof("文件已存在于 EXHAUST_PATH,直接提供服务: %s", exhaustFilename)
|
||||||
|
return c.SendFile(exhaustFilename)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文件不在 EXHAUST_PATH 中,需要处理
|
||||||
isLocalPath := strings.HasPrefix(matchedTarget, "./") || strings.HasPrefix(matchedTarget, "/")
|
isLocalPath := strings.HasPrefix(matchedTarget, "./") || strings.HasPrefix(matchedTarget, "/")
|
||||||
|
|
||||||
var rawImageAbs string
|
var rawImageAbs string
|
||||||
var metadata config.MetaFile
|
|
||||||
|
|
||||||
if isLocalPath {
|
if isLocalPath {
|
||||||
// 处理本地路径
|
// 处理本地路径
|
||||||
localPath := strings.TrimPrefix(reqURI, matchedPrefix)
|
localPath := strings.TrimPrefix(reqURI, matchedPrefix)
|
||||||
rawImageAbs = path.Join(matchedTarget, localPath)
|
rawImageAbs = path.Join(matchedTarget, localPath)
|
||||||
|
|
||||||
if !helper.FileExists(rawImageAbs) {
|
|
||||||
log.Warnf("本地文件不存在: %s", rawImageAbs)
|
|
||||||
return c.SendStatus(fiber.StatusNotFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 为本地文件创建或获取元数据
|
|
||||||
metadata = helper.ReadMetadata(reqURIwithQuery, "", reqHostname)
|
|
||||||
if metadata.Checksum != helper.HashFile(rawImageAbs) {
|
|
||||||
log.Info("本地文件已更改,更新元数据...")
|
|
||||||
metadata = helper.WriteMetadata(reqURIwithQuery, "", reqHostname)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// 处理远程URL
|
// 处理远程URL
|
||||||
targetUrl, _ := url.Parse(matchedTarget)
|
targetUrl, _ := url.Parse(matchedTarget)
|
||||||
targetHostName = targetUrl.Host
|
remoteAddr := targetUrl.Scheme + "://" + targetUrl.Host + strings.Replace(reqURI, matchedPrefix, targetUrl.Path, 1)
|
||||||
targetHost = targetUrl.Scheme + "://" + targetUrl.Host
|
metadata := fetchRemoteImg(remoteAddr, targetUrl.Host)
|
||||||
reqURI = strings.Replace(reqURI, matchedPrefix, targetUrl.Path, 1)
|
rawImageAbs = path.Join(config.Config.RemoteRawPath, targetUrl.Host, metadata.Id)
|
||||||
reqURIwithQuery = strings.Replace(reqURIwithQuery, matchedPrefix, targetUrl.Path, 1)
|
|
||||||
realRemoteAddr = targetHost + reqURIwithQuery
|
|
||||||
|
|
||||||
// 获取远程图像元数据
|
|
||||||
metadata = fetchRemoteImg(realRemoteAddr, targetHostName)
|
|
||||||
rawImageAbs = path.Join(config.Config.RemoteRawPath, targetHostName, metadata.Id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否为允许的图片文件
|
// 检查是否为允许的图片文件
|
||||||
if !helper.IsAllowedImageFile(filename) {
|
if !helper.IsAllowedImageFile(filename) {
|
||||||
log.Infof("不允许的文件类型或非图片文件: %s", reqURI)
|
log.Infof("不允许的文件类型或非图片文件: %s", reqURI)
|
||||||
if isLocalPath {
|
|
||||||
return c.SendFile(rawImageAbs)
|
return c.SendFile(rawImageAbs)
|
||||||
} else {
|
|
||||||
log.Infof("Redirecting to: %s", realRemoteAddr)
|
|
||||||
return c.Redirect(realRemoteAddr, fiber.StatusFound)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查原始图像是否存在
|
// 处理图片
|
||||||
if !helper.ImageExists(rawImageAbs) {
|
err := encoder.ProcessAndSaveImage(rawImageAbs, exhaustFilename, extraParams)
|
||||||
helper.DeleteMetadata(reqURIwithQuery, targetHostName)
|
|
||||||
msg := "Image not found!"
|
|
||||||
log.Warn(msg)
|
|
||||||
return c.Status(404).SendString(msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查文件大小
|
|
||||||
isSmall, err := helper.IsFileSizeSmall(rawImageAbs, 100*1024) // 100KB
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("检查文件大小时出错: %v", err)
|
log.Errorf("处理图片失败: %v", err)
|
||||||
return c.SendStatus(fiber.StatusInternalServerError)
|
return c.SendStatus(fiber.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
var finalFilename string
|
return c.SendFile(exhaustFilename)
|
||||||
if isSmall {
|
|
||||||
log.Infof("文件 %s 小于100KB,直接缓存到 EXHAUST_PATH", rawImageAbs)
|
|
||||||
finalFilename = path.Join(config.Config.ExhaustPath, targetHostName, metadata.Id)
|
|
||||||
if err := helper.CopyFile(rawImageAbs, finalFilename); err != nil {
|
|
||||||
log.Errorf("复制小文件到 EXHAUST_PATH 失败: %v", err)
|
|
||||||
return c.SendStatus(fiber.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
avifAbs, webpAbs, jxlAbs := helper.GenOptimizedAbsPath(metadata, targetHostName)
|
|
||||||
|
|
||||||
// 确定支持的格式
|
|
||||||
supportedFormats := helper.GuessSupportedFormat(reqHeader)
|
|
||||||
// 根据支持的格式和配置进行转换
|
|
||||||
encoder.ConvertFilter(rawImageAbs, jxlAbs, avifAbs, webpAbs, extraParams, supportedFormats, nil)
|
|
||||||
|
|
||||||
var availableFiles = []string{rawImageAbs}
|
|
||||||
if supportedFormats["avif"] {
|
|
||||||
availableFiles = append(availableFiles, avifAbs)
|
|
||||||
}
|
|
||||||
if supportedFormats["webp"] {
|
|
||||||
availableFiles = append(availableFiles, webpAbs)
|
|
||||||
}
|
|
||||||
if supportedFormats["jxl"] {
|
|
||||||
availableFiles = append(availableFiles, jxlAbs)
|
|
||||||
}
|
|
||||||
|
|
||||||
finalFilename = helper.FindSmallestFiles(availableFiles)
|
|
||||||
}
|
|
||||||
|
|
||||||
contentType := helper.GetFileContentType(finalFilename)
|
|
||||||
c.Set("Content-Type", contentType)
|
|
||||||
|
|
||||||
c.Set("X-Compression-Rate", helper.GetCompressionRate(rawImageAbs, finalFilename))
|
|
||||||
return c.SendFile(finalFilename)
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user