对外目录只保留英文主 sku;迁移硬删镜像并校准 1元=100积分;发货页/凭证优先使用 local {sku}.webp。
Co-authored-by: Cursor <cursoragent@cursor.com>
99 lines
2.6 KiB
Go
99 lines
2.6 KiB
Go
package main
|
|
|
|
// 已废弃:请改用按英文 SKU 同步到 frontend/public/images/products/{sku}.webp
|
|
//
|
|
// cd backend && go run ./cmd/sync_product_images
|
|
//
|
|
// 本命令仍会把乐玩缩略图缓存到 data/uploads/products/{uuid}.webp,仅作历史兼容。
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"affiliate_dash/internal/config"
|
|
"affiliate_dash/internal/pkg/lewan"
|
|
)
|
|
|
|
func main() {
|
|
cfg := config.Load()
|
|
client := lewan.NewClient(lewan.Config{
|
|
BaseURL: cfg.LewanBaseURL,
|
|
AccessKey: cfg.LewanAccessKey,
|
|
SecretKey: cfg.LewanSecretKey,
|
|
ApplicationID: cfg.LewanApplicationID,
|
|
}, nil)
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
list, err := client.ListProducts(ctx, 1, 100, "")
|
|
if err != nil {
|
|
fmt.Printf("ListProducts error: %v\n", err)
|
|
return
|
|
}
|
|
|
|
fmt.Printf("Found %d products (total: %d)\n", len(list.Items), list.Total)
|
|
uploadDir := cfg.UploadDir
|
|
if uploadDir == "" {
|
|
uploadDir = "data/uploads"
|
|
}
|
|
cacheDir := filepath.Join(uploadDir, "products")
|
|
_ = os.MkdirAll(cacheDir, 0755)
|
|
|
|
httpClient := &http.Client{Timeout: 15 * time.Second}
|
|
|
|
for i, item := range list.Items {
|
|
fmt.Printf("[%d/%d] ID: %s, Name: %s, Thumb: %s\n", i+1, len(list.Items), item.ID, item.Name, item.ThumbnailUrl)
|
|
if item.ThumbnailUrl == "" {
|
|
continue
|
|
}
|
|
// 存储为 item.ID + 原扩展名
|
|
ext := filepath.Ext(item.ThumbnailUrl)
|
|
if ext == "" || len(ext) > 5 {
|
|
ext = ".webp"
|
|
}
|
|
destPath := filepath.Join(cacheDir, item.ID+ext)
|
|
// 同时也用 url hash 存一个软链或备份,以便 proxy 按 url 精准命中
|
|
urlHash := sha256.Sum256([]byte(item.ThumbnailUrl))
|
|
hashPath := filepath.Join(cacheDir, hex.EncodeToString(urlHash[:])+ext)
|
|
|
|
if _, err := os.Stat(destPath); err == nil {
|
|
fmt.Printf(" -> 已存在缓存: %s\n", destPath)
|
|
continue
|
|
}
|
|
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, item.ThumbnailUrl, nil)
|
|
if err != nil {
|
|
fmt.Printf(" -> 构建请求失败: %v\n", err)
|
|
continue
|
|
}
|
|
resp, err := httpClient.Do(req)
|
|
if err != nil {
|
|
fmt.Printf(" -> 下载失败: %v\n", err)
|
|
continue
|
|
}
|
|
if resp.StatusCode != http.StatusOK {
|
|
fmt.Printf(" -> 状态码错误: %d\n", resp.StatusCode)
|
|
resp.Body.Close()
|
|
continue
|
|
}
|
|
data, err := io.ReadAll(resp.Body)
|
|
resp.Body.Close()
|
|
if err != nil {
|
|
fmt.Printf(" -> 读取失败: %v\n", err)
|
|
continue
|
|
}
|
|
|
|
_ = os.WriteFile(destPath, data, 0644)
|
|
_ = os.WriteFile(hashPath, data, 0644)
|
|
fmt.Printf(" -> 成功缓存: %s (大小: %d 字节)\n", destPath, len(data))
|
|
}
|
|
}
|