165 lines
4.4 KiB
JavaScript
165 lines
4.4 KiB
JavaScript
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 : '获取分龄推荐失败',
|
|
};
|
|
}
|
|
};
|