移除旧版 HTML 模板与兼容层,并完善私信、举报、媒体存储与 SEO。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
152
service/image_webp.go
Normal file
152
service/image_webp.go
Normal file
@@ -0,0 +1,152 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/gif"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/KarpelesLab/gowebp"
|
||||
|
||||
// 注册解码器
|
||||
_ "image/jpeg"
|
||||
_ "image/png"
|
||||
|
||||
_ "golang.org/x/image/webp"
|
||||
)
|
||||
|
||||
// 图片展示方案(上传时同时保留原图与 WebP,按此决定返回给前端的 URL)
|
||||
const (
|
||||
ImageDeliveryWebP = "webp" // 使用 WebP(默认,省流量)
|
||||
ImageDeliveryOriginal = "original" // 使用原图
|
||||
)
|
||||
|
||||
const (
|
||||
// UploadWebPQuality 上传衍生 WebP 有损质量(0–100)
|
||||
UploadWebPQuality float32 = 82
|
||||
// UploadWebPMethod 编码档位:3 速度与体积较均衡
|
||||
UploadWebPMethod = 3
|
||||
// ThumbWebPQuality 帖子预览图质量
|
||||
ThumbWebPQuality float32 = 80
|
||||
)
|
||||
|
||||
// preparedUpload 原图 + 可选 WebP 衍生
|
||||
type preparedUpload struct {
|
||||
OrigExt string // 含点,如 .jpg
|
||||
OrigContentType string
|
||||
OrigData []byte
|
||||
WebPData []byte // 空表示无衍生(动图 GIF,或原图已是 WebP)
|
||||
}
|
||||
|
||||
// prepareUploadImage 始终保留原图字节;静态图额外生成 WebP 衍生
|
||||
func prepareUploadImage(file *multipart.FileHeader) (*preparedUpload, error) {
|
||||
ext := strings.ToLower(filepath.Ext(file.Filename))
|
||||
if !allowedImageExt[ext] {
|
||||
return nil, errors.New("仅支持 jpg/png/gif/webp 格式")
|
||||
}
|
||||
if ext == ".jpeg" {
|
||||
ext = ".jpg"
|
||||
}
|
||||
|
||||
src, err := file.Open()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
raw, err := io.ReadAll(src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(raw) == 0 {
|
||||
return nil, errors.New("空图片文件")
|
||||
}
|
||||
|
||||
out := &preparedUpload{
|
||||
OrigExt: ext,
|
||||
OrigContentType: imageContentType(ext),
|
||||
OrigData: raw,
|
||||
}
|
||||
|
||||
// 动图 GIF:只保留原文件
|
||||
if ext == ".gif" && gifFrameCount(raw) > 1 {
|
||||
return out, nil
|
||||
}
|
||||
// 上传已是 WebP:原图即 WebP,不再重复衍生
|
||||
if ext == ".webp" {
|
||||
return out, nil
|
||||
}
|
||||
|
||||
img, _, err := image.Decode(bytes.NewReader(raw))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("解码图片失败: %w", err)
|
||||
}
|
||||
|
||||
webpBytes, err := encodeWebPBytes(img, UploadWebPQuality, UploadWebPMethod)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("转换 WebP 失败: %w", err)
|
||||
}
|
||||
out.WebPData = webpBytes
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func gifFrameCount(raw []byte) int {
|
||||
g, err := gif.DecodeAll(bytes.NewReader(raw))
|
||||
if err != nil || g == nil {
|
||||
return 0
|
||||
}
|
||||
return len(g.Image)
|
||||
}
|
||||
|
||||
func encodeWebPBytes(img image.Image, quality float32, method int) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
if err := gowebp.Encode(&buf, img, &gowebp.Options{
|
||||
Lossy: true,
|
||||
Quality: quality,
|
||||
Method: method,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func normalizeImageDelivery(raw string) string {
|
||||
if strings.ToLower(strings.TrimSpace(raw)) == ImageDeliveryOriginal {
|
||||
return ImageDeliveryOriginal
|
||||
}
|
||||
return ImageDeliveryWebP
|
||||
}
|
||||
|
||||
func imageContentType(ext string) string {
|
||||
switch strings.ToLower(ext) {
|
||||
case ".jpg", ".jpeg":
|
||||
return "image/jpeg"
|
||||
case ".png":
|
||||
return "image/png"
|
||||
case ".gif":
|
||||
return "image/gif"
|
||||
case ".webp":
|
||||
return "image/webp"
|
||||
default:
|
||||
return "application/octet-stream"
|
||||
}
|
||||
}
|
||||
|
||||
// siblingUploadExts 同主文件名可能存在的伴生扩展名(删除时一并清理)
|
||||
func siblingUploadExts(ext string) []string {
|
||||
ext = strings.ToLower(ext)
|
||||
all := []string{".jpg", ".jpeg", ".png", ".gif", ".webp"}
|
||||
out := make([]string, 0, len(all))
|
||||
for _, e := range all {
|
||||
if e == ext || (ext == ".jpg" && e == ".jpeg") || (ext == ".jpeg" && e == ".jpg") {
|
||||
continue
|
||||
}
|
||||
out = append(out, e)
|
||||
}
|
||||
return out
|
||||
}
|
||||
Reference in New Issue
Block a user