diff --git a/encoder/process.go b/encoder/process.go index d0d0089..a4491a4 100644 --- a/encoder/process.go +++ b/encoder/process.go @@ -5,6 +5,7 @@ import ( "os" "path" "slices" + "strings" "webp_server_go/config" "github.com/davidbyttow/govips/v2/vips" @@ -196,3 +197,60 @@ func preProcessImage(img *vips.ImageRef, imageType string, extraParams config.Ex log.Debug("图像预处理完成") 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 +} diff --git a/handler/remote.go b/handler/remote.go index 7f44da3..43b365d 100644 --- a/handler/remote.go +++ b/handler/remote.go @@ -13,7 +13,6 @@ import ( "webp_server_go/helper" "github.com/gofiber/fiber/v2" - "github.com/patrickmn/go-cache" log "github.com/sirupsen/logrus" ) @@ -64,58 +63,16 @@ func downloadFile(filepath string, url string) error { } func fetchRemoteImg(url string, subdir string) config.MetaFile { - cacheKey := subdir + ":" + helper.HashString(url) - var metadata config.MetaFile - var etag string - var size int64 - var lastModified time.Time - if cachedETag, found := config.RemoteCache.Get(cacheKey); found { - 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) + metadata = helper.ReadMetadata(url, "", subdir) localRawImagePath := path.Join(config.Config.RemoteRawPath, subdir, metadata.Id) - localExhaustImagePath := path.Join(config.Config.ExhaustPath, subdir, metadata.Id) - needUpdate := false if !helper.ImageExists(localRawImagePath) { 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) // 重新读取更新后的元数据 - metadata = helper.ReadMetadata(url, etag, subdir) + metadata = helper.WriteMetadata(url, "", subdir) } return metadata diff --git a/handler/router.go b/handler/router.go index 50bebb3..2c5df46 100644 --- a/handler/router.go +++ b/handler/router.go @@ -1,6 +1,7 @@ package handler import ( + "fmt" "net/url" "path" "strconv" @@ -20,18 +21,12 @@ func Convert(c *fiber.Ctx) error { } var ( - reqHostname = c.Hostname() - reqHeader = &c.Request().Header - reqURIRaw, _ = url.QueryUnescape(c.Path()) reqURIwithQueryRaw, _ = url.QueryUnescape(c.OriginalURL()) reqURI = path.Clean(reqURIRaw) reqURIwithQuery = path.Clean(reqURIwithQueryRaw) - filename = path.Base(reqURI) - realRemoteAddr = "" - targetHostName = "" - targetHost = "" + filename = path.Base(reqURI) width, _ = strconv.Atoi(c.Query("width")) 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 中的任何前缀 var matchedPrefix string @@ -64,100 +59,47 @@ func Convert(c *fiber.Ctx) error { 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, "/") - var rawImageAbs string - var metadata config.MetaFile - if isLocalPath { // 处理本地路径 localPath := strings.TrimPrefix(reqURI, matchedPrefix) 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 { // 处理远程URL targetUrl, _ := url.Parse(matchedTarget) - targetHostName = targetUrl.Host - targetHost = targetUrl.Scheme + "://" + targetUrl.Host - reqURI = strings.Replace(reqURI, matchedPrefix, targetUrl.Path, 1) - reqURIwithQuery = strings.Replace(reqURIwithQuery, matchedPrefix, targetUrl.Path, 1) - realRemoteAddr = targetHost + reqURIwithQuery - - // 获取远程图像元数据 - metadata = fetchRemoteImg(realRemoteAddr, targetHostName) - rawImageAbs = path.Join(config.Config.RemoteRawPath, targetHostName, metadata.Id) + remoteAddr := targetUrl.Scheme + "://" + targetUrl.Host + strings.Replace(reqURI, matchedPrefix, targetUrl.Path, 1) + metadata := fetchRemoteImg(remoteAddr, targetUrl.Host) + rawImageAbs = path.Join(config.Config.RemoteRawPath, targetUrl.Host, metadata.Id) } // 检查是否为允许的图片文件 if !helper.IsAllowedImageFile(filename) { log.Infof("不允许的文件类型或非图片文件: %s", reqURI) - if isLocalPath { - return c.SendFile(rawImageAbs) - } else { - log.Infof("Redirecting to: %s", realRemoteAddr) - return c.Redirect(realRemoteAddr, fiber.StatusFound) - } + return c.SendFile(rawImageAbs) } - // 检查原始图像是否存在 - if !helper.ImageExists(rawImageAbs) { - 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 + // 处理图片 + err := encoder.ProcessAndSaveImage(rawImageAbs, exhaustFilename, extraParams) if err != nil { - log.Errorf("检查文件大小时出错: %v", err) + log.Errorf("处理图片失败: %v", err) return c.SendStatus(fiber.StatusInternalServerError) } - var finalFilename string - 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) + return c.SendFile(exhaustFilename) }