Files
kefu_cloud/server/internal/media/image.go
T
yml2213 ec2f12c6ed 实现 MinIO 对象存储图片流水线(可迁移 OSS)
- 新增 S3 兼容 Storage 抽象与 MinIO 实现,compose 启动 MinIO
- 上传接口:校验 → 缩放 → WebP → 主图/缩略图入库
- 消息图片 content 改为对象 URL,拒绝 base64
- 工作台/访客端改为先上传再发消息
2026-07-15 12:21:31 +08:00

107 lines
2.3 KiB
Go

package media
import (
"bytes"
"fmt"
"image"
_ "image/gif"
_ "image/jpeg"
_ "image/png"
"io"
"github.com/chai2010/webp"
"github.com/disintegration/imaging"
)
const (
DefaultMaxEdge = 1920
DefaultThumbEdge = 400
DefaultQuality = 80
)
type ProcessOptions struct {
MaxEdge int
ThumbEdge int
WebPQuality float32
}
type ProcessedImage struct {
Main []byte
MainType string // image/webp
MainExt string // .webp
Thumb []byte
ThumbType string
ThumbExt string
Width int
Height int
ThumbWidth int
ThumbHeight int
}
// ProcessImage 解码 → 等比缩放 → WebP 主图 + 缩略图
func ProcessImage(r io.Reader, opt ProcessOptions) (*ProcessedImage, error) {
if opt.MaxEdge <= 0 {
opt.MaxEdge = DefaultMaxEdge
}
if opt.ThumbEdge <= 0 {
opt.ThumbEdge = DefaultThumbEdge
}
if opt.WebPQuality <= 0 || opt.WebPQuality > 100 {
opt.WebPQuality = DefaultQuality
}
src, _, err := image.Decode(r)
if err != nil {
return nil, fmt.Errorf("无法解码图片: %w", err)
}
mainImg := fitWithin(src, opt.MaxEdge)
thumbImg := fitWithin(src, opt.ThumbEdge)
mainBuf := &bytes.Buffer{}
if err := webp.Encode(mainBuf, mainImg, &webp.Options{Quality: opt.WebPQuality}); err != nil {
return nil, fmt.Errorf("webp 编码失败: %w", err)
}
thumbBuf := &bytes.Buffer{}
if err := webp.Encode(thumbBuf, thumbImg, &webp.Options{Quality: opt.WebPQuality}); err != nil {
return nil, fmt.Errorf("缩略图编码失败: %w", err)
}
bMain := mainImg.Bounds()
bThumb := thumbImg.Bounds()
return &ProcessedImage{
Main: mainBuf.Bytes(),
MainType: "image/webp",
MainExt: ".webp",
Thumb: thumbBuf.Bytes(),
ThumbType: "image/webp",
ThumbExt: ".webp",
Width: bMain.Dx(),
Height: bMain.Dy(),
ThumbWidth: bThumb.Dx(),
ThumbHeight: bThumb.Dy(),
}, nil
}
func fitWithin(img image.Image, maxEdge int) image.Image {
b := img.Bounds()
w, h := b.Dx(), b.Dy()
if w <= maxEdge && h <= maxEdge {
return img
}
if w >= h {
return imaging.Resize(img, maxEdge, 0, imaging.Lanczos)
}
return imaging.Resize(img, 0, maxEdge, imaging.Lanczos)
}
// AllowedUploadMIME 上传阶段允许的源格式
func AllowedUploadMIME(contentType string) bool {
switch contentType {
case "image/jpeg", "image/png", "image/gif", "image/webp":
return true
default:
return false
}
}