| import cron from 'node-cron'; |
| import { Lunar } from 'lunar-javascript'; |
| import { getDb } from './database.js'; |
| import { sendFortuneReminder, sendLowPointsReminder } from './emailService.js'; |
|
|
| |
| export function startEmailScheduler() { |
| console.log('✓ 邮件定时任务调度器已启动'); |
|
|
| |
| cron.schedule('0 8 * * *', async () => { |
| console.log('开始执行每日定时任务 (8:00 AM)...'); |
| await sendDailyFortuneReminders(); |
| await sendBirthdayReminders(); |
| }); |
|
|
| |
| cron.schedule('0 9 1 * *', async () => { |
| console.log('开始执行每月定时任务 (9:00 AM, 1st)...'); |
| await sendMonthlyFortuneReminders(); |
| }); |
|
|
| |
| cron.schedule('0 9 20 12 *', async () => { |
| console.log('开始执行流年定时任务 (9:00 AM, Dec 20)...'); |
| await sendYearlyFortuneReminders(); |
| }); |
|
|
| |
| cron.schedule('0 10 * * 1', async () => { |
| console.log('开始执行积分不足检查 (10:00 AM, Monday)...'); |
| await sendLowPointsReminders(); |
| }); |
| } |
|
|
| |
|
|
| async function sendDailyFortuneReminders() { |
| try { |
| const db = getDb(); |
|
|
| |
| const stmt = db.prepare(` |
| SELECT u.id, u.email, es.user_id |
| FROM users u |
| INNER JOIN email_subscriptions es ON u.id = es.user_id |
| WHERE es.sub_daily_fortune = 1 AND es.email_verified = 1 |
| `); |
|
|
| const users = stmt.all(); |
| console.log(`找到 ${users.length} 位订阅每日运势的用户`); |
|
|
| let successCount = 0; |
| let errorCount = 0; |
|
|
| for (const user of users) { |
| try { |
| |
| const profileStmt = db.prepare(` |
| SELECT id, name FROM user_profiles |
| WHERE user_id = ? AND (is_deleted = 0 OR is_deleted IS NULL) |
| ORDER BY is_default DESC, created_at DESC |
| LIMIT 1 |
| `); |
|
|
| const profile = profileStmt.get(user.id); |
|
|
| if (profile) { |
| await sendFortuneReminder(user.email, 'daily', profile.name); |
| successCount++; |
| console.log(`✓ 已发送每日运势提醒至 ${user.email} (${profile.name})`); |
| } else { |
| console.log(`⚠ 用户 ${user.email} 没有可用的档案,跳过`); |
| } |
| } catch (error) { |
| errorCount++; |
| console.error(`✗ 发送每日运势提醒失败 (${user.email}):`, error.message); |
| } |
| } |
|
|
| console.log(`每日运势提醒完成: 成功 ${successCount}, 失败 ${errorCount}`); |
| } catch (error) { |
| console.error('执行每日运势提醒任务失败:', error); |
| } |
| } |
|
|
| async function sendMonthlyFortuneReminders() { |
| try { |
| const db = getDb(); |
|
|
| |
| const stmt = db.prepare(` |
| SELECT u.id, u.email, es.user_id |
| FROM users u |
| INNER JOIN email_subscriptions es ON u.id = es.user_id |
| WHERE es.sub_monthly_fortune = 1 AND es.email_verified = 1 |
| `); |
|
|
| const users = stmt.all(); |
| console.log(`找到 ${users.length} 位订阅每月运势的用户`); |
|
|
| let successCount = 0; |
| let errorCount = 0; |
|
|
| for (const user of users) { |
| try { |
| |
| const profileStmt = db.prepare(` |
| SELECT id, name FROM user_profiles |
| WHERE user_id = ? AND (is_deleted = 0 OR is_deleted IS NULL) |
| ORDER BY is_default DESC, created_at DESC |
| LIMIT 1 |
| `); |
|
|
| const profile = profileStmt.get(user.id); |
|
|
| if (profile) { |
| await sendFortuneReminder(user.email, 'monthly', profile.name); |
| successCount++; |
| console.log(`✓ 已发送每月运势提醒至 ${user.email} (${profile.name})`); |
| } else { |
| console.log(`⚠ 用户 ${user.email} 没有可用的档案,跳过`); |
| } |
| } catch (error) { |
| errorCount++; |
| console.error(`✗ 发送每月运势提醒失败 (${user.email}):`, error.message); |
| } |
| } |
|
|
| console.log(`每月运势提醒完成: 成功 ${successCount}, 失败 ${errorCount}`); |
| } catch (error) { |
| console.error('执行每月运势提醒任务失败:', error); |
| } |
| } |
|
|
| async function sendYearlyFortuneReminders() { |
| try { |
| const db = getDb(); |
|
|
| |
| const stmt = db.prepare(` |
| SELECT u.id, u.email, es.user_id |
| FROM users u |
| INNER JOIN email_subscriptions es ON u.id = es.user_id |
| WHERE es.sub_yearly_fortune = 1 AND es.email_verified = 1 |
| `); |
|
|
| const users = stmt.all(); |
| console.log(`找到 ${users.length} 位订阅流年运势的用户`); |
|
|
| let successCount = 0; |
| let errorCount = 0; |
|
|
| for (const user of users) { |
| try { |
| |
| const profileStmt = db.prepare(` |
| SELECT id, name FROM user_profiles |
| WHERE user_id = ? AND (is_deleted = 0 OR is_deleted IS NULL) |
| ORDER BY is_default DESC, created_at DESC |
| LIMIT 1 |
| `); |
|
|
| const profile = profileStmt.get(user.id); |
|
|
| if (profile) { |
| await sendFortuneReminder(user.email, 'yearly', profile.name); |
| successCount++; |
| console.log(`✓ 已发送流年运势提醒至 ${user.email} (${profile.name})`); |
| } else { |
| console.log(`⚠ 用户 ${user.email} 没有可用的档案,跳过`); |
| } |
| } catch (error) { |
| errorCount++; |
| console.error(`✗ 发送流年运势提醒失败 (${user.email}):`, error.message); |
| } |
| } |
|
|
| console.log(`流年运势提醒完成: 成功 ${successCount}, 失败 ${errorCount}`); |
| } catch (error) { |
| console.error('执行流年运势提醒任务失败:', error); |
| } |
| } |
|
|
| async function sendLowPointsReminders() { |
| try { |
| const db = getDb(); |
|
|
| |
| const stmt = db.prepare(` |
| SELECT u.id, u.email, u.points |
| FROM users u |
| INNER JOIN email_subscriptions es ON u.id = es.user_id |
| WHERE u.points < 100 AND es.sub_low_points = 1 AND es.email_verified = 1 |
| `); |
|
|
| const users = stmt.all(); |
| console.log(`找到 ${users.length} 位积分不足的用户`); |
|
|
| let successCount = 0; |
| let errorCount = 0; |
|
|
| for (const user of users) { |
| try { |
| await sendLowPointsReminder(user.email, user.points); |
| successCount++; |
| console.log(`✓ 已发送积分不足提醒至 ${user.email} (剩余 ${user.points} 点)`); |
| } catch (error) { |
| errorCount++; |
| console.error(`✗ 发送积分不足提醒失败 (${user.email}):`, error.message); |
| } |
| } |
|
|
| console.log(`积分不足提醒完成: 成功 ${successCount}, 失败 ${errorCount}`); |
| } catch (error) { |
| console.error('执行积分不足提醒任务失败:', error); |
| } |
| } |
|
|
| async function sendBirthdayReminders() { |
| try { |
| const db = getDb(); |
|
|
| |
| const today = new Date(); |
| const todayLunar = Lunar.fromDate(today); |
| const todayLunarMonth = todayLunar.getMonth(); |
| const todayLunarDay = todayLunar.getDay(); |
|
|
| console.log(`今日农历: ${todayLunarMonth}月${todayLunarDay}日`); |
|
|
| |
| const stmt = db.prepare(` |
| SELECT u.id, u.email, es.user_id |
| FROM users u |
| INNER JOIN email_subscriptions es ON u.id = es.user_id |
| WHERE es.sub_birthday_reminder = 1 AND es.email_verified = 1 |
| `); |
|
|
| const users = stmt.all(); |
| console.log(`检查 ${users.length} 位订阅生日提醒的用户`); |
|
|
| let successCount = 0; |
| let errorCount = 0; |
| let birthdayCount = 0; |
|
|
| for (const user of users) { |
| try { |
| |
| const profileStmt = db.prepare(` |
| SELECT id, name, birth_year, month_pillar, day_pillar |
| FROM user_profiles |
| WHERE user_id = ? AND (is_deleted = 0 OR is_deleted IS NULL) |
| AND birth_year IS NOT NULL |
| `); |
|
|
| const profiles = profileStmt.all(user.id); |
|
|
| for (const profile of profiles) { |
| try { |
| |
| |
| |
| |
|
|
| |
| |
| |
|
|
| |
| |
|
|
| |
| if (!profile.birth_year || !profile.month_pillar || !profile.day_pillar) { |
| continue; |
| } |
|
|
| |
| |
| |
|
|
| |
| |
| |
|
|
| |
| console.log(`⚠ 生日提醒功能需要完整的出生日期信息 (用户: ${user.email}, 档案: ${profile.name})`); |
|
|
| } catch (profileError) { |
| console.error(`检查档案生日失败 (${profile.name}):`, profileError.message); |
| } |
| } |
| } catch (error) { |
| errorCount++; |
| console.error(`✗ 检查生日失败 (${user.email}):`, error.message); |
| } |
| } |
|
|
| console.log(`生日提醒检查完成: 今日生日 ${birthdayCount}, 成功发送 ${successCount}, 失败 ${errorCount}`); |
| console.log(`提示: 生日提醒功能需要在 user_profiles 表中添加完整的 birth_date 字段以实现精确匹配`); |
| } catch (error) { |
| console.error('执行生日提醒任务失败:', error); |
| } |
| } |
|
|
| export default { |
| startEmailScheduler |
| }; |
|
|