feat: rename qiniu to oss, add admin upload proxy with thumbnail, add dev-login
- Rename all QINIU_* config/code/docs to OSS_* to match actual Alibaba Cloud OSS - Refactor upload module from internal/common/qiniu to internal/common/upload - Add backend proxy upload endpoint (POST /api/admin/marketing/upload) to avoid CORS - Auto-generate compressed thumbnail (800px, JPEG 80%) on admin image upload - Add dev-login endpoint (POST /api/v1/auth/dev-login) for H5 debugging - Add imageutil package for server-side image resizing Made-with: Cursor
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
package imageutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/jpeg"
|
||||
"image/png"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/image/draw"
|
||||
)
|
||||
|
||||
type ResizeOptions struct {
|
||||
MaxWidth int
|
||||
Quality int // JPEG quality 1-100
|
||||
}
|
||||
|
||||
func DefaultResizeOptions() ResizeOptions {
|
||||
return ResizeOptions{MaxWidth: 800, Quality: 80}
|
||||
}
|
||||
|
||||
// GenerateThumbnail reads image data, resizes if wider than MaxWidth, and
|
||||
// re-encodes as JPEG. Returns the thumbnail bytes and whether the image
|
||||
// was actually resized (false if already small enough but still re-encoded).
|
||||
func GenerateThumbnail(data []byte, contentType string, opts ResizeOptions) ([]byte, error) {
|
||||
if opts.MaxWidth <= 0 {
|
||||
opts.MaxWidth = 800
|
||||
}
|
||||
if opts.Quality <= 0 || opts.Quality > 100 {
|
||||
opts.Quality = 80
|
||||
}
|
||||
|
||||
src, err := decodeImage(bytes.NewReader(data), contentType)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decode image: %w", err)
|
||||
}
|
||||
|
||||
bounds := src.Bounds()
|
||||
srcW := bounds.Dx()
|
||||
srcH := bounds.Dy()
|
||||
|
||||
dstW := srcW
|
||||
dstH := srcH
|
||||
if srcW > opts.MaxWidth {
|
||||
dstW = opts.MaxWidth
|
||||
dstH = srcH * opts.MaxWidth / srcW
|
||||
}
|
||||
|
||||
dst := image.NewRGBA(image.Rect(0, 0, dstW, dstH))
|
||||
draw.BiLinear.Scale(dst, dst.Bounds(), src, bounds, draw.Over, nil)
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := jpeg.Encode(&buf, dst, &jpeg.Options{Quality: opts.Quality}); err != nil {
|
||||
return nil, fmt.Errorf("encode jpeg: %w", err)
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func decodeImage(r io.Reader, contentType string) (image.Image, error) {
|
||||
ct := strings.ToLower(contentType)
|
||||
switch {
|
||||
case strings.Contains(ct, "png"):
|
||||
return png.Decode(r)
|
||||
case strings.Contains(ct, "jpeg"), strings.Contains(ct, "jpg"):
|
||||
return jpeg.Decode(r)
|
||||
default:
|
||||
img, _, err := image.Decode(r)
|
||||
return img, err
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user