cb 3 ay önce
ebeveyn
işleme
30926bbad0

Dosya farkı çok büyük olduğundan ihmal edildi
+ 53 - 0
docs/provider-archives/2026-05/justone_1779676084871_gu7hr0_api-douyin-xingtu-gw-api-gsearch-search-for-author-square-v1.json


Dosya farkı çok büyük olduğundan ihmal edildi
+ 52 - 0
docs/provider-archives/2026-05/justone_1779676086999_dki9wp_api-douyin-xingtu-gw-api-gsearch-search-for-author-square-v1.json


Dosya farkı çok büyük olduğundan ihmal edildi
+ 49 - 0
docs/provider-archives/2026-05/justone_1779676088669_fqaknt_api-douyin-xingtu-gw-api-gsearch-search-for-author-square-v1.json


+ 38 - 9
server/services/recommendation.service.ts

@@ -77,33 +77,47 @@ export async function generateRecommendations(
   console.log('[Recommend] ========== 开始推荐流程 ==========');
   console.log('[Recommend] 搜索条件:', JSON.stringify(criteria, null, 2));
   const targetCandidatePool = resolveTargetCandidatePool(criteria);
+  const requestedPlatforms = uniquePlatforms(criteria.platforms);
+  const targetPerPlatform = Math.max(1, Math.ceil(targetCandidatePool / Math.max(1, requestedPlatforms.length)));
   onProgress?.('search', `正在检索候选达人,目标候选池 ${targetCandidatePool} 位...`);
 
   // Step 1: 优先从本地/自有达人库召回候选
   const allCandidates: JustOneCreator[] = [];
-  const localCandidates = await searchLocalCreators(criteria, Math.min(config.recommendation.maxCandidates, targetCandidatePool));
+  const localCandidates: JustOneCreator[] = [];
+  for (const platform of requestedPlatforms) {
+    const platformLocalCandidates = await searchLocalCreators(
+      { ...criteria, platforms: [platform] },
+      Math.min(config.recommendation.maxCandidates, targetPerPlatform),
+    );
+    localCandidates.push(...platformLocalCandidates);
+    allCandidates.push(...platformLocalCandidates);
+    console.log(`[Recommend] 本地达人库 ${platform} 命中: ${platformLocalCandidates.length}/${targetPerPlatform} 位`);
+  }
   if (localCandidates.length > 0) {
     console.log(`[Recommend] 本地达人库命中: ${localCandidates.length} 位`);
     onProgress?.('search', `本地达人库命中 ${localCandidates.length}/${targetCandidatePool} 位候选达人`);
-    allCandidates.push(...localCandidates);
   }
 
   // JustOne API 仅支持小红书和抖音
   const supportedPlatforms = ['xiaohongshu', '小红书', 'douyin', '抖音'];
 
   let providerCandidateCount = 0;
-  if (allCandidates.length < targetCandidatePool) {
+  if (platformsNeedSupplement(allCandidates, requestedPlatforms, targetPerPlatform)) {
     const shortage = targetCandidatePool - allCandidates.length;
     console.log(`[Recommend] 本地候选不足,继续调用 JustOne 补足: 缺口 ${shortage} 位`);
     onProgress?.('search', `本地候选不足,继续调用 JustOne 补足 ${shortage} 位...`);
 
     const searchKeywords = buildCompactSearchKeywords(criteria);
-    for (const platform of uniquePlatforms(criteria.platforms)) {
+    for (const platform of requestedPlatforms) {
       const platformName = mapPlatformName(platform);
       if (!supportedPlatforms.includes(platform) && !supportedPlatforms.includes(platformName)) {
         console.log(`[Recommend] 跳过不支持的平台: ${platform} (${platformName})`);
         continue;
       }
+      if (countCreatorsByPlatform(allCandidates, platformName) >= targetPerPlatform) {
+        console.log(`[Recommend] ${platformName} 已满足平台候选目标 ${targetPerPlatform} 位,跳过补量`);
+        continue;
+      }
       for (const keyword of searchKeywords) {
         for (let page = 1; page <= PAGES_PER_PLATFORM; page++) {
           console.log(`[Recommend] 搜索: 平台=${platform} -> ${platformName}, 关键词=${keyword}, 页=${page}`);
@@ -125,12 +139,11 @@ export async function generateRecommendations(
           console.log(`[Recommend] ${platformName}/${keyword}/page-${page} 返回 ${results.length} 条结果`);
           allCandidates.push(...results);
           providerCandidateCount += results.length;
-          if (allCandidates.length >= targetCandidatePool) break;
+          if (countCreatorsByPlatform(allCandidates, platformName) >= targetPerPlatform) break;
           if (results.length === 0) break;
         }
-        if (allCandidates.length >= targetCandidatePool) break;
+        if (countCreatorsByPlatform(allCandidates, platformName) >= targetPerPlatform) break;
       }
-      if (allCandidates.length >= targetCandidatePool) break;
     }
   } else {
     console.log(`[Recommend] 本地候选已满足目标候选池 ${targetCandidatePool} 位,跳过 JustOne`);
@@ -142,8 +155,9 @@ export async function generateRecommendations(
   // Step 2: 去重
   const uniqueMap = new Map<string, JustOneCreator>();
   for (const c of allCandidates) {
-    if (!uniqueMap.has(c.userId)) {
-      uniqueMap.set(c.userId, c);
+    const key = `${mapPlatformName(c.platform)}:${c.userId}`;
+    if (!uniqueMap.has(key)) {
+      uniqueMap.set(key, c);
     }
   }
   const uniqueCandidates = Array.from(uniqueMap.values());
@@ -413,6 +427,21 @@ function uniquePlatforms(platforms: string[]): string[] {
   return [...new Set(supported.length > 0 ? supported : ['xiaohongshu'])];
 }
 
+function platformsNeedSupplement(candidates: JustOneCreator[], platforms: string[], targetPerPlatform: number): boolean {
+  return platforms.some((platform) => countCreatorsByPlatform(candidates, platform) < targetPerPlatform);
+}
+
+function countCreatorsByPlatform(candidates: JustOneCreator[], platform: string): number {
+  const normalizedPlatform = mapPlatformName(platform);
+  const uniqueIds = new Set<string>();
+  for (const candidate of candidates) {
+    if (mapPlatformName(candidate.platform) === normalizedPlatform && candidate.userId) {
+      uniqueIds.add(candidate.userId);
+    }
+  }
+  return uniqueIds.size;
+}
+
 function mapPlatformName(platform: string): string {
   const map: Record<string, string> = {
     xiaohongshu: 'xiaohongshu',

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor