mirror of
https://github.com/MoonTechLab/LunaTV.git
synced 2026-02-26 22:24:42 +08:00
feat: refine cron job
This commit is contained in:
@@ -1,30 +0,0 @@
|
||||
import cron from 'node-cron';
|
||||
|
||||
import refreshRecordAndFavorites from '@/lib/refreshRecordAndFavorites';
|
||||
|
||||
/*
|
||||
* 初始化定时任务:每个小时的 02 分执行一次。
|
||||
* 若需要添加更多任务,可在此文件中继续编写。
|
||||
*/
|
||||
|
||||
declare global {
|
||||
// 避免在开发热重载或多次导入时重复初始化
|
||||
// eslint-disable-next-line no-var
|
||||
var __moonTVCronInitialized: boolean | undefined;
|
||||
}
|
||||
|
||||
if (!global.__moonTVCronInitialized) {
|
||||
cron.schedule(
|
||||
'2 * * * *',
|
||||
async () => {
|
||||
refreshRecordAndFavorites();
|
||||
},
|
||||
{
|
||||
timezone: 'Asia/Shanghai',
|
||||
}
|
||||
);
|
||||
|
||||
global.__moonTVCronInitialized = true;
|
||||
}
|
||||
|
||||
export {}; // 仅用于确保这是一个模块
|
||||
@@ -1,12 +1,8 @@
|
||||
/* eslint-disable no-console, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
|
||||
|
||||
import { getRequestContext } from '@cloudflare/next-on-pages';
|
||||
|
||||
import { AdminConfig } from './admin.types';
|
||||
import { Favorite, IStorage, PlayRecord } from './types';
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
// 搜索历史最大条数
|
||||
const SEARCH_HISTORY_LIMIT = 20;
|
||||
|
||||
@@ -120,17 +116,6 @@ const INIT_SQL = `
|
||||
|
||||
// 获取全局D1数据库实例
|
||||
function getD1Database(): D1Database {
|
||||
try {
|
||||
// 在 Cloudflare Pages 环境中,通过 getRequestContext 访问 D1 数据库
|
||||
const { env } = getRequestContext();
|
||||
if (env && (env as any).DB) {
|
||||
return (env as any).DB as D1Database;
|
||||
}
|
||||
} catch (error) {
|
||||
// 如果 getRequestContext 失败,继续尝试其他方式
|
||||
console.warn('Failed to get request context:', error);
|
||||
}
|
||||
|
||||
// 在 next-on-pages 环境中,D1 数据库可能通过 process.env 暴露
|
||||
if (typeof process !== 'undefined' && (process.env as any).DB) {
|
||||
return (process.env as any).DB as D1Database;
|
||||
@@ -145,10 +130,6 @@ export class D1Storage implements IStorage {
|
||||
private db: D1Database | null = null;
|
||||
private initialized = false;
|
||||
|
||||
constructor() {
|
||||
// 不在构造函数中初始化数据库,延迟到实际使用时
|
||||
}
|
||||
|
||||
private async getDatabase(): Promise<D1Database> {
|
||||
if (!this.db) {
|
||||
this.db = getD1Database();
|
||||
|
||||
@@ -1,159 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
|
||||
import { db } from '@/lib/db';
|
||||
import { fetchVideoDetail } from '@/lib/fetchVideoDetail';
|
||||
import { SearchResult } from '@/lib/types';
|
||||
|
||||
const STORAGE_TYPE = process.env.NEXT_PUBLIC_STORAGE_TYPE ?? 'localstorage';
|
||||
|
||||
async function refreshRecordAndFavorites() {
|
||||
if (STORAGE_TYPE === 'localstorage') {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const users = await db.getAllUsers();
|
||||
if (process.env.USERNAME && !users.includes(process.env.USERNAME)) {
|
||||
users.push(process.env.USERNAME);
|
||||
}
|
||||
// 函数级缓存:key 为 `${source}+${id}`,值为 Promise<VideoDetail | null>
|
||||
const detailCache = new Map<string, Promise<SearchResult | null>>();
|
||||
|
||||
// 获取详情 Promise(带缓存和错误处理)
|
||||
const getDetail = async (
|
||||
source: string,
|
||||
id: string,
|
||||
fallbackTitle: string
|
||||
): Promise<SearchResult | null> => {
|
||||
const key = `${source}+${id}`;
|
||||
let promise = detailCache.get(key);
|
||||
if (!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);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
return promise;
|
||||
};
|
||||
|
||||
for (const user of users) {
|
||||
console.log(`开始处理用户: ${user}`);
|
||||
|
||||
// 播放记录
|
||||
try {
|
||||
const playRecords = await db.getAllPlayRecords(user);
|
||||
const totalRecords = Object.keys(playRecords).length;
|
||||
let processedRecords = 0;
|
||||
|
||||
for (const [key, record] of Object.entries(playRecords)) {
|
||||
try {
|
||||
const [source, id] = key.split('+');
|
||||
if (!source || !id) {
|
||||
console.warn(`跳过无效的播放记录键: ${key}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const detail = await getDetail(source, id, record.title);
|
||||
if (!detail) {
|
||||
console.warn(`跳过无法获取详情的播放记录: ${key}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const episodeCount = detail.episodes?.length || 0;
|
||||
if (episodeCount > 0 && episodeCount !== record.total_episodes) {
|
||||
await db.savePlayRecord(user, source, id, {
|
||||
title: detail.title || record.title,
|
||||
source_name: record.source_name,
|
||||
cover: detail.poster || record.cover,
|
||||
index: record.index,
|
||||
total_episodes: episodeCount,
|
||||
play_time: record.play_time,
|
||||
year: detail.year || record.year,
|
||||
total_time: record.total_time,
|
||||
save_time: record.save_time,
|
||||
search_title: record.search_title,
|
||||
});
|
||||
console.log(
|
||||
`更新播放记录: ${record.title} (${record.total_episodes} -> ${episodeCount})`
|
||||
);
|
||||
}
|
||||
|
||||
processedRecords++;
|
||||
} catch (err) {
|
||||
console.error(`处理播放记录失败 (${key}):`, err);
|
||||
// 继续处理下一个记录
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`播放记录处理完成: ${processedRecords}/${totalRecords}`);
|
||||
} catch (err) {
|
||||
console.error(`获取用户播放记录失败 (${user}):`, err);
|
||||
}
|
||||
|
||||
// 收藏
|
||||
try {
|
||||
const favorites = await db.getAllFavorites(user);
|
||||
const totalFavorites = Object.keys(favorites).length;
|
||||
let processedFavorites = 0;
|
||||
|
||||
for (const [key, fav] of Object.entries(favorites)) {
|
||||
try {
|
||||
const [source, id] = key.split('+');
|
||||
if (!source || !id) {
|
||||
console.warn(`跳过无效的收藏键: ${key}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const favDetail = await getDetail(source, id, fav.title);
|
||||
if (!favDetail) {
|
||||
console.warn(`跳过无法获取详情的收藏: ${key}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const favEpisodeCount = favDetail.episodes?.length || 0;
|
||||
if (favEpisodeCount > 0 && favEpisodeCount !== fav.total_episodes) {
|
||||
await db.saveFavorite(user, source, id, {
|
||||
title: favDetail.title || fav.title,
|
||||
source_name: fav.source_name,
|
||||
cover: favDetail.poster || fav.cover,
|
||||
year: favDetail.year || fav.year,
|
||||
total_episodes: favEpisodeCount,
|
||||
save_time: fav.save_time,
|
||||
search_title: fav.search_title,
|
||||
});
|
||||
console.log(
|
||||
`更新收藏: ${fav.title} (${fav.total_episodes} -> ${favEpisodeCount})`
|
||||
);
|
||||
}
|
||||
|
||||
processedFavorites++;
|
||||
} catch (err) {
|
||||
console.error(`处理收藏失败 (${key}):`, err);
|
||||
// 继续处理下一个收藏
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`收藏处理完成: ${processedFavorites}/${totalFavorites}`);
|
||||
} catch (err) {
|
||||
console.error(`获取用户收藏失败 (${user}):`, err);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('刷新播放记录/收藏任务完成');
|
||||
} catch (err) {
|
||||
console.error('刷新播放记录/收藏任务启动失败', err);
|
||||
}
|
||||
}
|
||||
|
||||
export default refreshRecordAndFavorites;
|
||||
Reference in New Issue
Block a user