定时任务性能优化

This commit is contained in:
mtvpls
2026-02-22 23:55:34 +08:00
parent 9a33b90078
commit 4dfea503d5
3 changed files with 79 additions and 38 deletions

View File

@@ -339,6 +339,7 @@ dockge/komodo 等 docker compose UI 也有自动更新功能
| USERNAME | 站长账号 | 任意字符串 | 无默认,必填字段 |
| PASSWORD | 站长密码 | 任意字符串 | 无默认,必填字段 |
| CRON_PASSWORD | 定时任务 API 访问密码(用于保护 /api/cron 端点) | 任意字符串 | mtvpls |
| CRON_USER_BATCH_SIZE | 定时任务用户批处理大小(控制并发处理的用户数量,影响播放记录和收藏更新任务的并发性能) | 正整数 | 3 |
| SITE_BASE | 站点 url | 形如 https://example.com | 空 |
| NEXT_PUBLIC_SITE_NAME | 站点名称 | 任意字符串 | MoonTV |
| ANNOUNCEMENT | 站点公告 | 任意字符串 | 本网站仅提供影视信息搜索服务,所有内容均来自第三方网站。本站不存储任何视频资源,不对任何内容的准确性、合法性、完整性负责。 |

View File

@@ -54,11 +54,16 @@ export async function GET(
}
async function cronJob() {
// 先刷新配置,确保其他任务使用最新配置
await refreshConfig();
await refreshAllLiveChannels();
await refreshOpenList();
await refreshRecordAndFavorites();
await checkAnimeSubscriptions();
// 其余任务并行执行
await Promise.all([
refreshAllLiveChannels(),
refreshOpenList(),
refreshRecordAndFavorites(),
checkAnimeSubscriptions(),
]);
}
async function refreshAllLiveChannels() {
@@ -164,26 +169,28 @@ async function refreshRecordAndFavorites() {
const key = `${source}+${id}`;
let promise = detailCache.get(key);
if (!promise) {
// 立即缓存Promise避免并发时的竞态条件
promise = fetchVideoDetail({
source,
id,
fallbackTitle: fallbackTitle.trim(),
})
.then((detail) => {
// 成功时才缓存结果
const successPromise = Promise.resolve(detail);
detailCache.set(key, successPromise);
return detail;
})
.catch((err) => {
console.error(`获取视频详情失败 (${source}+${id}):`, err);
// 失败时从缓存中移除,下次可以重试
detailCache.delete(key);
return null;
});
detailCache.set(key, promise);
}
return promise;
};
for (const user of users) {
// 处理单个用户的函数
const processUser = async (user: string) => {
console.log(`开始处理用户: ${user}`);
const storage = getStorage();
@@ -342,44 +349,54 @@ async function refreshRecordAndFavorites() {
console.log(`收藏处理完成: ${processedFavorites}/${totalFavorites}`);
// 如果有更新,发送汇总邮件
// 如果有更新,异步发送汇总邮件(不阻塞主流程)
if (userUpdates.length > 0) {
try {
const userEmail = storage.getUserEmail ? await storage.getUserEmail(user) : null;
const emailNotifications = storage.getEmailNotificationPreference
? await storage.getEmailNotificationPreference(user)
: false;
(async () => {
try {
const userEmail = storage.getUserEmail ? await storage.getUserEmail(user) : null;
const emailNotifications = storage.getEmailNotificationPreference
? await storage.getEmailNotificationPreference(user)
: false;
if (userEmail && emailNotifications) {
const config = await getConfig();
const emailConfig = config?.EmailConfig;
if (userEmail && emailNotifications) {
const config = await getConfig();
const emailConfig = config?.EmailConfig;
if (emailConfig?.enabled) {
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000';
const siteName = config?.SiteConfig?.SiteName || 'MoonTVPlus';
if (emailConfig?.enabled) {
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000';
const siteName = config?.SiteConfig?.SiteName || 'MoonTVPlus';
await EmailService.send(emailConfig, {
to: userEmail,
subject: `📺 收藏更新汇总 - ${userUpdates.length} 部影片有更新`,
html: getBatchFavoriteUpdateEmailTemplate(
user,
userUpdates,
siteUrl,
siteName
),
});
await EmailService.send(emailConfig, {
to: userEmail,
subject: `📺 收藏更新汇总 - ${userUpdates.length} 部影片有更新`,
html: getBatchFavoriteUpdateEmailTemplate(
user,
userUpdates,
siteUrl,
siteName
),
});
console.log(`邮件汇总已发送至: ${userEmail} (${userUpdates.length} 个更新)`);
console.log(`邮件汇总已发送至: ${userEmail} (${userUpdates.length} 个更新)`);
}
}
} catch (emailError) {
console.error(`发送邮件汇总失败 (${user}):`, emailError);
}
} catch (emailError) {
console.error(`发送邮件汇总失败 (${user}):`, emailError);
// 邮件发送失败不影响主流程
}
})().catch(err => console.error(`邮件发送异步任务失败 (${user}):`, err));
}
} catch (err) {
console.error(`获取用户收藏失败 (${user}):`, err);
}
};
// 分批并行处理用户,避免并发过高
// 可通过环境变量 CRON_USER_BATCH_SIZE 配置批处理大小,默认为 3
const BATCH_SIZE = parseInt(process.env.CRON_USER_BATCH_SIZE || '3', 10);
for (let i = 0; i < users.length; i += BATCH_SIZE) {
const batch = users.slice(i, i + BATCH_SIZE);
console.log(`处理用户批次 ${Math.floor(i / BATCH_SIZE) + 1}/${Math.ceil(users.length / BATCH_SIZE)}: ${batch.join(', ')}`);
await Promise.all(batch.map(user => processUser(user)));
}
console.log('刷新播放记录/收藏任务完成');

View File

@@ -361,31 +361,51 @@ export async function checkSubscription(subscription: AnimeSubscription) {
* 检查所有订阅(定时任务调用)
*/
export async function checkAnimeSubscriptions() {
console.log('[AnimeSubscription] 开始检查动漫订阅');
const config = await getConfig();
const animeConfig = config.AnimeSubscriptionConfig;
if (!animeConfig?.Enabled) {
console.log('[AnimeSubscription] 动漫订阅功能未启用,跳过检查');
return;
}
const subscriptions = animeConfig.Subscriptions || [];
console.log(`[AnimeSubscription] 共有 ${subscriptions.length} 个订阅`);
const now = Date.now();
const MIN_CHECK_INTERVAL = 30 * 60 * 1000; // 30分钟
let configChanged = false;
let checkedCount = 0;
let skippedCount = 0;
let errorCount = 0;
for (const sub of subscriptions) {
if (!sub.enabled) continue;
if (!sub.enabled) {
console.log(`[AnimeSubscription] 跳过已禁用的订阅: ${sub.title}`);
skippedCount++;
continue;
}
// 检查是否距离上次检查超过30分钟
if (now - sub.lastCheckTime < MIN_CHECK_INTERVAL) {
const timeSinceLastCheck = now - sub.lastCheckTime;
if (timeSinceLastCheck < MIN_CHECK_INTERVAL) {
const remainingMinutes = Math.ceil((MIN_CHECK_INTERVAL - timeSinceLastCheck) / 60000);
console.log(`[AnimeSubscription] 跳过 ${sub.title}: 距离上次检查仅 ${Math.floor(timeSinceLastCheck / 60000)} 分钟,还需等待 ${remainingMinutes} 分钟`);
skippedCount++;
continue;
}
try {
await checkSubscription(sub);
console.log(`[AnimeSubscription] 检查订阅: ${sub.title} (源: ${sub.source}, 上次集数: ${sub.lastEpisode})`);
const result = await checkSubscription(sub);
console.log(`[AnimeSubscription] ${sub.title}: 找到 ${result.found} 个新集数,成功下载 ${result.downloaded}`);
configChanged = true;
checkedCount++;
} catch (error) {
console.error(`[AnimeSubscription] ${sub.title}: 检查失败`, error);
errorCount++;
}
}
@@ -393,5 +413,8 @@ export async function checkAnimeSubscriptions() {
if (configChanged) {
await db.saveAdminConfig(config);
await setCachedConfig(config);
console.log('[AnimeSubscription] 配置已更新并保存');
}
console.log(`[AnimeSubscription] 检查完成 - 总计: ${subscriptions.length}, 已检查: ${checkedCount}, 跳过: ${skippedCount}, 失败: ${errorCount}`);
}