const cloud = require('wx-server-sdk'); cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }); const AGE_BANDS = { '3-4': { minAge: 3, maxAge: 4 }, '4-5': { minAge: 4, maxAge: 5 }, '5-6': { minAge: 5, maxAge: 6 }, '6-7': { minAge: 6, maxAge: 7 }, '7-8': { minAge: 7, maxAge: 8 }, }; const DIFFICULTY_LABELS = { 1: '入门', 2: '基础', 3: '进阶', 4: '挑战', }; function normalizeAgeKey(ageKey) { const key = String(ageKey || '').trim(); return AGE_BANDS[key] ? key : null; } function ageBand(min, max) { const minAge = Number(min) || 0; const maxAge = Number(max) || 0; return minAge && maxAge ? `${minAge}-${maxAge}岁` : ''; } function difficultyLabel(difficulty) { if (typeof difficulty === 'string') { return difficulty; } return DIFFICULTY_LABELS[difficulty] || '基础'; } function mergeSeedStats(items) { for (const item of items) { item.likes = (item.likes || 0) + (item.likes_seed || 0); item.downloads = (item.downloads || 0) + (item.downloads_seed || 0); } } function toRecommendedItem(ws) { return { worksheetId: ws._id, title: ws.title || '', subtitle: ws.subtitle || '', previewImg: ws.previewImg || '', ageBand: ageBand(ws.ageMin, ws.ageMax), ageMin: Number(ws.ageMin) || 0, ageMax: Number(ws.ageMax) || 0, difficulty: difficultyLabel(ws.difficulty), difficultyLabel: difficultyLabel(ws.difficulty), path: ws.path || '', downloads: Number(ws.downloads) || 0, likes: Number(ws.likes) || 0, }; } function getAgeMin(ws) { return Number(ws.ageMin) || 0; } function getAgeMax(ws) { return Number(ws.ageMax) || 0; } function coversAgeBand(ws, band) { return getAgeMin(ws) === band.minAge && getAgeMax(ws) >= band.maxAge; } function sortByField(items, field) { return [...items].sort((a, b) => { const fieldDiff = (Number(b[field]) || 0) - (Number(a[field]) || 0); if (fieldDiff !== 0) return fieldDiff; const aTime = a.updatedAt ? new Date(a.updatedAt).getTime() : 0; const bTime = b.updatedAt ? new Date(b.updatedAt).getTime() : 0; return bTime - aTime; }); } function pickTop(items, field, limit, excludeIds) { if (limit <= 0) return []; const picked = []; for (const ws of sortByField(items, field)) { if (excludeIds.has(ws._id)) continue; picked.push(ws); excludeIds.add(ws._id); if (picked.length >= limit) break; } return picked; } async function queryActiveWorksheets(db) { const { data } = await db .collection('worksheets') .where({ status: 'active', }) .orderBy('downloads', 'desc') .orderBy('updatedAt', 'desc') .limit(100) .get(); return data || []; } /** * 入参: * - ageKey: '3-4' | '4-5' | '5-6' | '6-7' | '7-8' * * 返回: * - items: 优先返回 ageMin 等于目标下限且 ageMax 覆盖目标上限的 downloads Top3 + likes Top3,不足 6 个用全站 downloads Top 补足 */ exports.main = async (event) => { try { const ageKey = normalizeAgeKey(event && event.ageKey); if (!ageKey) { return { success: false, message: 'ageKey 不合法' }; } const band = AGE_BANDS[ageKey]; const db = cloud.database(); const activeWorksheets = await queryActiveWorksheets(db); mergeSeedStats(activeWorksheets); const matchedWorksheets = activeWorksheets.filter((ws) => coversAgeBand(ws, band), ); const excludeIds = new Set(); const topDownloads = pickTop( matchedWorksheets, 'downloads', 3, excludeIds, ); const topLikes = pickTop(matchedWorksheets, 'likes', 3, excludeIds); const fallbackDownloads = pickTop( activeWorksheets, 'downloads', Math.max(0, 6 - topDownloads.length - topLikes.length), excludeIds, ); return { success: true, data: { ageKey, items: [...topDownloads, ...topLikes, ...fallbackDownloads] .slice(0, 6) .map(toRecommendedItem), }, }; } catch (error) { return { success: false, message: error instanceof Error ? error.message : '获取分龄推荐失败', }; } };