#!/usr/bin/env node /** * 自动解绑定时任务 * * 功能:定时检查并解绑过期的推荐关系 * * 解绑条件: * 1. 绑定状态为 active * 2. 过期时间已到(expiry_date < NOW) * 3. 期间没有任何购买(purchase_count = 0) * * 执行方式: * - 手动执行:node scripts/auto-unbind-expired.js * - 定时任务:配置 cron 每天凌晨2点执行 * * 宝塔面板配置: * 计划任务 -> Shell脚本 * 执行周期:每天 02:00 * 脚本内容:cd /www/wwwroot/soul && node scripts/auto-unbind-expired.js */ const path = require('path') const fs = require('fs') // 动态加载数据库模块 async function loadDB() { const dbPath = path.join(__dirname, '../lib/db.ts') // 如果是 TypeScript 文件,需要使用 ts-node 或编译后的版本 if (fs.existsSync(dbPath)) { // 尝试导入编译后的 JS 文件 const compiledPath = path.join(__dirname, '../.next/server/lib/db.js') if (fs.existsSync(compiledPath)) { return require(compiledPath) } // 如果没有编译版本,尝试使用 ts-node try { require('ts-node/register') return require(dbPath) } catch (e) { console.error('❌ 无法加载数据库模块,请确保已编译或安装 ts-node') process.exit(1) } } else { console.error('❌ 找不到数据库模块文件') process.exit(1) } } async function autoUnbind() { console.log('=' .repeat(60)) console.log('自动解绑定时任务') console.log('执行时间:', new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' })) console.log('=' .repeat(60)) console.log() try { // 加载数据库模块 const { query } = await loadDB() // 1. 查询需要解绑的记录 console.log('步骤 1: 查询需要解绑的记录...') console.log('-' .repeat(60)) const expiredBindings = await query(` SELECT id, referee_id, referrer_id, binding_date, expiry_date, purchase_count, total_commission FROM referral_bindings WHERE status = 'active' AND expiry_date < NOW() AND purchase_count = 0 ORDER BY expiry_date ASC `) if (expiredBindings.length === 0) { console.log('✅ 无需解绑的记录') console.log() console.log('=' .repeat(60)) console.log('任务完成') console.log('=' .repeat(60)) return } console.log(`找到 ${expiredBindings.length} 条需要解绑的记录`) console.log() // 2. 输出明细 console.log('步骤 2: 解绑明细') console.log('-' .repeat(60)) expiredBindings.forEach((binding, index) => { const bindingDate = new Date(binding.binding_date).toLocaleDateString('zh-CN') const expiryDate = new Date(binding.expiry_date).toLocaleDateString('zh-CN') const daysExpired = Math.floor((Date.now() - new Date(binding.expiry_date).getTime()) / (1000 * 60 * 60 * 24)) console.log(`${index + 1}. 用户 ${binding.referee_id}`) console.log(` 推荐人: ${binding.referrer_id}`) console.log(` 绑定时间: ${bindingDate}`) console.log(` 过期时间: ${expiryDate} (已过期 ${daysExpired} 天)`) console.log(` 购买次数: ${binding.purchase_count}`) console.log(` 累计佣金: ¥${(binding.total_commission || 0).toFixed(2)}`) console.log() }) // 3. 批量更新为 expired console.log('步骤 3: 执行解绑操作...') console.log('-' .repeat(60)) const ids = expiredBindings.map(b => b.id) const result = await query(` UPDATE referral_bindings SET status = 'expired' WHERE id IN (${ids.map(() => '?').join(',')}) `, ids) console.log(`✅ 已成功解绑 ${result.affectedRows || expiredBindings.length} 条记录`) console.log() // 4. 更新推荐人的推广数量 console.log('步骤 4: 更新推荐人统计...') console.log('-' .repeat(60)) const referrerIds = [...new Set(expiredBindings.map(b => b.referrer_id))] for (const referrerId of referrerIds) { const count = expiredBindings.filter(b => b.referrer_id === referrerId).length await query(` UPDATE users SET referral_count = GREATEST(referral_count - ?, 0) WHERE id = ? `, [count, referrerId]) console.log(` - ${referrerId}: -${count} 个绑定`) } console.log(`✅ 已更新 ${referrerIds.length} 个推荐人的统计数据`) console.log() // 5. 总结 console.log('=' .repeat(60)) console.log('✅ 任务完成') console.log(` - 解绑记录数: ${expiredBindings.length}`) console.log(` - 受影响推荐人: ${referrerIds.length}`) console.log('=' .repeat(60)) } catch (error) { console.error('❌ 任务执行失败:', error) console.error(error.stack) process.exit(1) } } // 如果直接运行此脚本 if (require.main === module) { autoUnbind().then(() => { process.exit(0) }).catch((err) => { console.error('❌ 脚本执行异常:', err) process.exit(1) }) } module.exports = { autoUnbind }