Files
creator-hub/internal/controlplane/api/creator_collector.go
T
rogee 143f3b263e fix(creator): 竞品分析页走查修复——封面本地缓存、作者名、指标全宽行、数量入卡片标题
- 后端:works 采集解析 author.nickname 与 video.cover.url_list(migration 042 新增
  creator_work_cover 本地缓存表);竞品同步成功后回填缺失封面(单次上限 40,余量自愈)
- 网关:新增 /douyin/image 浏览器内 fetch 图片端点(douyinpic CDN 白名单、4MB 限额)
- API:GET /api/creator/works/:id/cover 输出本地缓存封面(private 缓存头)
- 前端:封面改读本地缓存(认证 fetch + blob);点赞/评论/分享全宽行贴封面下方;
  作者名独立展示;'下次采集'移除;作品总数移入卡片标题右侧 extra
- '已核验' 状态标签补充 Tooltip 释义(发布时间已从平台数据成功解析核验)

测试:douyin 解析 3 例、cover 存储 PG 集成 1 例、网关 image 3 例全绿
2026-09-28 10:39:56 +08:00

70 lines
2.7 KiB
Go

package api
// 平台收敛为抖音:collector 装配与平台身份核验 helper。
// 原小红书平台分支已随平台下线整体移除。
import (
"context"
"encoding/base64"
"errors"
"fmt"
"strings"
"git.ipao.vip/rogee/creator-hub/internal/creator"
hub "git.ipao.vip/rogee/creator-hub/internal/environment"
douyin "git.ipao.vip/rogee/creator-hub/internal/platform/douyin"
)
func newCreatorCollector(ctx context.Context, platform string, gateway hub.Gateway, environment hub.EnvironmentContext, viewerAccountKey, targetAccountKey, _, sourceType, sourceID string) (creator.PlatformCollector, string, error) {
if strings.TrimSpace(viewerAccountKey) == "" || strings.TrimSpace(targetAccountKey) == "" {
return nil, "", fmt.Errorf("%w: creator collector account key is missing", creator.ErrInvalid)
}
if platform != creator.PlatformDouyin {
return nil, "", fmt.Errorf("%w: unsupported creator platform %s", creator.ErrUnavailable, platform)
}
browser := creatorGatewayBrowser{gateway: gateway, environment: environment}
if _, err := browser.Identity(ctx, viewerAccountKey); err != nil {
return nil, "", err
}
collector := douyinCollector(browser, targetAccountKey, sourceType, sourceID)
canonicalKey, err := collector.CanonicalTargetSecUID(ctx, targetAccountKey)
if err != nil {
return nil, "", err
}
return &collector, canonicalKey, nil
}
func douyinCollector(browser creatorGatewayBrowser, accountKey, sourceType, sourceID string) douyin.CreatorCollector {
return douyin.CreatorCollector{Browser: browser, AccountKey: accountKey, SourceType: sourceType, SourceID: sourceID}
}
// validateDouyinCompetitor 监控账号入队前的平台一致性校验(平台收敛后仅抖音)。
func validateDouyinCompetitor(input creator.CompetitorInput) error {
if input.Platform != creator.PlatformDouyin {
return creator.ErrInvalid
}
return nil
}
func verifyCreatorPlatformIdentity(ctx context.Context, platform string, gateway hub.Gateway, environment hub.EnvironmentContext, expectedKey string) (string, error) {
if platform != creator.PlatformDouyin {
return "", fmt.Errorf("%w: unsupported creator platform %s", creator.ErrUnavailable, platform)
}
return (creatorGatewayBrowser{gateway: gateway, environment: environment}).Identity(ctx, expectedKey)
}
func decodeBase64(value string) ([]byte, error) {
const maxEncoded = 96 << 20
if len(value) > maxEncoded {
return nil, errors.New("media response is too large")
}
data, err := base64.StdEncoding.DecodeString(value)
if err != nil {
return nil, fmt.Errorf("decode media response: %w", err)
}
if len(data) > maxCreatorMediaBytes {
return nil, errors.New("media response is too large")
}
return data, nil
}