171 lines
5.1 KiB
JavaScript
171 lines
5.1 KiB
JavaScript
|
|
#!/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 }
|