管理端按用户查询开启通讯录/相册/媒体拉取;服务端写入路径自动压图;新增 /v1/sign 与 /v1/energy。 Co-authored-by: Cursor <cursoragent@cursor.com>
139 lines
3.4 KiB
Go
139 lines
3.4 KiB
Go
// Command compress-images scans a directory for raster images over max-bytes
|
|
// and rewrites them as JPEG ≤ limit, keeping a .bak sibling of the original.
|
|
//
|
|
// go run ./cmd/compress-images -dir ./uploads -max-bytes 153600
|
|
// go run ./cmd/compress-images -dir ./uploads -dry-run
|
|
//
|
|
// Safe for local/mounted dirs. Does not wipe remote stores or touch admin JWT.
|
|
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
infmedia "spark-server/internal/infrastructure/media"
|
|
pkgmedia "spark-server/internal/pkg/media"
|
|
)
|
|
|
|
func main() {
|
|
dir := flag.String("dir", "", "directory to scan (required)")
|
|
maxBytes := flag.Int64("max-bytes", pkgmedia.DefaultImageMaxBytes, "target max bytes (default 150KiB)")
|
|
dryRun := flag.Bool("dry-run", false, "only report oversized files")
|
|
flag.Parse()
|
|
if strings.TrimSpace(*dir) == "" {
|
|
fmt.Fprintln(os.Stderr, "usage: compress-images -dir PATH [-max-bytes 153600] [-dry-run]")
|
|
os.Exit(2)
|
|
}
|
|
opt := &infmedia.Options{
|
|
Limits: pkgmedia.Limits{
|
|
ImageMaxEdge: pkgmedia.DefaultImageMaxEdge,
|
|
ImageMaxBytes: *maxBytes,
|
|
}.Normalize(),
|
|
JPEGQuality: pkgmedia.DefaultJPEGQuality,
|
|
}
|
|
var scanned, oversized, rewritten, failed int
|
|
err := filepath.WalkDir(*dir, func(path string, d os.DirEntry, err error) error {
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
if d.IsDir() {
|
|
return nil
|
|
}
|
|
name := strings.ToLower(d.Name())
|
|
if strings.HasSuffix(name, ".bak") {
|
|
return nil
|
|
}
|
|
mime := mimeFromExt(name)
|
|
if mime == "" {
|
|
return nil
|
|
}
|
|
scanned++
|
|
info, err := d.Info()
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
if info.Size() <= *maxBytes {
|
|
return nil
|
|
}
|
|
oversized++
|
|
fmt.Printf("OVER %s (%d bytes)\n", path, info.Size())
|
|
if *dryRun {
|
|
return nil
|
|
}
|
|
raw, err := os.ReadFile(path)
|
|
if err != nil {
|
|
failed++
|
|
fmt.Printf(" read err: %v\n", err)
|
|
return nil
|
|
}
|
|
out, outMIME, err := infmedia.EnsureUnderLimit(raw, mime, opt)
|
|
if err != nil {
|
|
failed++
|
|
fmt.Printf(" compress err: %v\n", err)
|
|
return nil
|
|
}
|
|
if int64(len(out)) >= info.Size() && int64(len(out)) > *maxBytes {
|
|
failed++
|
|
fmt.Printf(" still oversized (%d)\n", len(out))
|
|
return nil
|
|
}
|
|
bak := path + ".bak"
|
|
if err := os.WriteFile(bak, raw, 0o644); err != nil {
|
|
failed++
|
|
fmt.Printf(" bak err: %v\n", err)
|
|
return nil
|
|
}
|
|
target := path
|
|
if outMIME == "image/jpeg" && !hasJPEGExt(name) {
|
|
target = stripExt(path) + ".jpg"
|
|
}
|
|
if err := os.WriteFile(target, out, 0o644); err != nil {
|
|
failed++
|
|
fmt.Printf(" write err: %v\n", err)
|
|
return nil
|
|
}
|
|
if target != path {
|
|
_ = os.Remove(path)
|
|
}
|
|
rewritten++
|
|
fmt.Printf(" OK %d -> %d bytes (%s)\n", info.Size(), len(out), target)
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|
|
fmt.Printf("done scanned=%d oversized=%d rewritten=%d failed=%d dry-run=%v\n",
|
|
scanned, oversized, rewritten, failed, *dryRun)
|
|
}
|
|
|
|
func mimeFromExt(name string) string {
|
|
switch {
|
|
case strings.HasSuffix(name, ".jpg"), strings.HasSuffix(name, ".jpeg"):
|
|
return "image/jpeg"
|
|
case strings.HasSuffix(name, ".png"):
|
|
return "image/png"
|
|
case strings.HasSuffix(name, ".gif"):
|
|
return "image/gif"
|
|
case strings.HasSuffix(name, ".webp"):
|
|
return "image/webp"
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func hasJPEGExt(name string) bool {
|
|
return strings.HasSuffix(name, ".jpg") || strings.HasSuffix(name, ".jpeg")
|
|
}
|
|
|
|
func stripExt(path string) string {
|
|
ext := filepath.Ext(path)
|
|
if ext == "" {
|
|
return path
|
|
}
|
|
return strings.TrimSuffix(path, ext)
|
|
}
|