feat: 抓取任务队列 + 媒体机直传 + 向量回填

crawl(品牌抓取任务队列)
- /admin/internal/crawl/tasks 领取 / :id/result 回报,spider 可常驻拉任务
- 后台品牌列表支持「加入抓取队列」,队列页可看状态与重试
- 租约超时的 running 自动回收为 queued,避免 worker 崩溃后任务卡死

media(媒体机直传)
- ingest 载荷新增 main_key/detail_keys/main_size/detail_sizes:
  有 key 就直接落库,跳过下载与上传;与 URL 形态可逐图混用
- 细节图按 Details/DetailKeys 两者较长者遍历(只给 key 时不再丢图)
- 媒体机上传的 key 不进失败回滚清单(内容寻址、可能与他人共享)
- /admin/internal/upload-url 签发 S4 PUT 预签名(spider 不持有 AK/SK)

embeddings(向量回填)
- GET /admin/internal/embeddings/pending 派发缺向量的图(含可直接下载的 url)
- POST /admin/internal/embeddings 回写向量并重算近邻;失败项计入
  image_embedding_failures,连续失败到上限后退出队列(坏图不占位)
- 近邻重算改为异步串行:同步会让 100 张的批次 POST 超时(实测 >60s)
- 新增 key 分支单测:混用 key/url、细节图只给 key、字节数、回滚清单、向量占位
This commit is contained in:
toom1996
2026-10-25 00:37:13 +08:00
parent e18c5a0815
commit 151e09ab30
22 changed files with 1535 additions and 28 deletions

View File

@ -3,8 +3,10 @@ package storage
import (
"bytes"
"context"
"errors"
"fmt"
"strings"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
awsconfig "github.com/aws/aws-sdk-go-v2/config"
@ -67,6 +69,38 @@ func NewS4Uploader(ak, sk, bucket, endpoint, baseURL, region string) *S4Uploader
}
}
// PresignPut 为指定 key 生成有时效的 PUT 预签名 URL。
//
// 用途:让**不持有 S4 密钥**的客户端(例如另部署的 spider 媒体机)直传对象。
// 密钥只留在后端,客户端拿到 URL 直接 HTTP PUT 即可 —— 不必在爬虫里引入 S3 SDK,
// 也不用把 AK/SK 分发到更多机器。
//
// contentType 必须与 PUT 请求头一致,否则 S4 会因签名不匹配拒绝(403)。
func (u *S4Uploader) PresignPut(ctx context.Context, key, contentType string, ttl time.Duration) (string, error) {
if !u.enabled || u.client == nil {
return "", errors.New("S4 未启用(缺少 AK/SK 或 bucket)")
}
if key == "" {
return "", errors.New("key 不能为空")
}
if contentType == "" {
contentType = "application/octet-stream"
}
if ttl <= 0 {
ttl = 15 * time.Minute
}
pc := s3.NewPresignClient(u.client)
out, err := pc.PresignPutObject(ctx, &s3.PutObjectInput{
Bucket: aws.String(u.bucket),
Key: aws.String(key),
ContentType: aws.String(contentType),
}, s3.WithPresignExpires(ttl))
if err != nil {
return "", err
}
return out.URL, nil
}
// deriveS4BaseURL 由 endpoint + bucket 推导虚拟主机风格访问域名:
// endpoint=https://s3.bitiful.net, bucket=toom → https://toom.s3.bitiful.net
func deriveS4BaseURL(endpoint, bucket string) string {