更新服务器信息为新的 IP 地址,调整相关文档和代码中的默认配置,确保部署和连接的一致性。同时,优化订单管理界面,增强商品信息的格式化逻辑,提升用户体验。
This commit is contained in:
152
app/api/cron/unbind-expired/route.ts
Normal file
152
app/api/cron/unbind-expired/route.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* 自动解绑过期推荐关系定时任务 API
|
||||
* GET /api/cron/unbind-expired?secret=YOUR_SECRET
|
||||
*
|
||||
* 功能:
|
||||
* 1. 查询 status = 'active' 且 expiry_date < NOW() 且 purchase_count = 0 的绑定
|
||||
* 2. 批量更新为 status = 'expired'
|
||||
* 3. 更新推荐人的 referral_count(减少)
|
||||
*
|
||||
* 调用方式:
|
||||
* - 宝塔面板计划任务:每30分钟执行
|
||||
* curl "https://soul.quwanzhi.com/api/cron/unbind-expired?secret=soul_cron_unbind_2026"
|
||||
*
|
||||
* 规则说明:
|
||||
* - 只解绑「活跃状态 + 已过期 + 从未购买」的绑定关系
|
||||
* - 如果用户购买过(purchase_count > 0),即使过期也不解绑
|
||||
* - 这样可以保留有价值的推荐关系记录
|
||||
*/
|
||||
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { query } from '@/lib/db'
|
||||
|
||||
// 触发解绑的密钥(防止误触)
|
||||
const CRON_SECRET = 'soul_cron_unbind_2026'
|
||||
|
||||
/**
|
||||
* 主函数:自动解绑过期推荐关系
|
||||
*/
|
||||
export async function GET(request: NextRequest) {
|
||||
const startTime = Date.now()
|
||||
|
||||
// 1. 验证密钥
|
||||
const { searchParams } = new URL(request.url)
|
||||
const secret = searchParams.get('secret')
|
||||
|
||||
if (secret !== CRON_SECRET) {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: '未授权访问'
|
||||
}, { status: 401 })
|
||||
}
|
||||
|
||||
console.log('[UnbindExpired] ========== 自动解绑任务开始 ==========')
|
||||
|
||||
try {
|
||||
// 2. 查找需要解绑的记录
|
||||
const expiredBindings = await query(`
|
||||
SELECT
|
||||
id,
|
||||
referrer_id,
|
||||
referee_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
|
||||
`) as any[]
|
||||
|
||||
if (expiredBindings.length === 0) {
|
||||
console.log('[UnbindExpired] 无需解绑的记录')
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: '无需解绑的记录',
|
||||
unbound: 0,
|
||||
duration: Date.now() - startTime
|
||||
})
|
||||
}
|
||||
|
||||
console.log(`[UnbindExpired] 找到 ${expiredBindings.length} 条需要解绑的记录`)
|
||||
|
||||
// 3. 输出详细日志
|
||||
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(`[UnbindExpired] ${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)}`)
|
||||
})
|
||||
|
||||
// 4. 批量更新为 expired
|
||||
const ids = expiredBindings.map(b => b.id)
|
||||
const placeholders = ids.map(() => '?').join(',')
|
||||
|
||||
const result = await query(
|
||||
`UPDATE referral_bindings SET status = 'expired' WHERE id IN (${placeholders})`,
|
||||
ids
|
||||
) as any
|
||||
|
||||
console.log(`[UnbindExpired] 已成功解绑 ${result.affectedRows || expiredBindings.length} 条记录`)
|
||||
|
||||
// 5. 更新推荐人的 referral_count(减少)
|
||||
// 注意:这里需要按推荐人分组计算
|
||||
const referrerUpdates = new Map<string, number>()
|
||||
expiredBindings.forEach(binding => {
|
||||
const count = referrerUpdates.get(binding.referrer_id) || 0
|
||||
referrerUpdates.set(binding.referrer_id, count + 1)
|
||||
})
|
||||
|
||||
let updatedReferrers = 0
|
||||
for (const [referrerId, count] of referrerUpdates.entries()) {
|
||||
try {
|
||||
await query(`
|
||||
UPDATE users
|
||||
SET referral_count = GREATEST(0, referral_count - ?)
|
||||
WHERE id = ?
|
||||
`, [count, referrerId])
|
||||
updatedReferrers++
|
||||
console.log(`[UnbindExpired] 更新推荐人 ${referrerId} 的 referral_count (-${count})`)
|
||||
} catch (err) {
|
||||
console.error(`[UnbindExpired] 更新推荐人 ${referrerId} 失败:`, err)
|
||||
}
|
||||
}
|
||||
|
||||
const duration = Date.now() - startTime
|
||||
|
||||
console.log(`[UnbindExpired] 解绑完成: ${expiredBindings.length} 条记录,更新 ${updatedReferrers} 个推荐人`)
|
||||
console.log(`[UnbindExpired] ========== 任务结束 (耗时 ${duration}ms) ==========`)
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: '自动解绑完成',
|
||||
unbound: expiredBindings.length,
|
||||
updatedReferrers,
|
||||
details: expiredBindings.map(b => ({
|
||||
refereeId: b.referee_id,
|
||||
referrerId: b.referrer_id,
|
||||
bindingDate: b.binding_date,
|
||||
expiryDate: b.expiry_date,
|
||||
daysExpired: Math.floor((Date.now() - new Date(b.expiry_date).getTime()) / (1000 * 60 * 60 * 24))
|
||||
})),
|
||||
duration
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.error('[UnbindExpired] 解绑失败:', error)
|
||||
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: '自动解绑失败',
|
||||
detail: error instanceof Error ? error.message : String(error)
|
||||
}, { status: 500 })
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user