Files
soul-yongping/next-project/app/api/referral/data/route.ts
2026-02-09 14:43:35 +08:00

325 lines
9.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 分销数据API - 使用 Prisma ORM
*
* 优势:
* - ✅ 完全类型安全
* - ✅ 自动防SQL注入
* - ✅ 使用 Prisma 原生聚合查询
*/
import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
import { getPrismaConfig } from '@/lib/prisma-helpers'
const DISTRIBUTOR_SHARE = 0.9
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url)
const userId = searchParams.get('userId')
if (!userId) {
return NextResponse.json({
success: false,
error: '用户ID不能为空'
}, { status: 400 })
}
try {
// 获取分销配置
let distributorShare = DISTRIBUTOR_SHARE
let minWithdrawAmount = 10
try {
const config = await getPrismaConfig('referral_config')
if (config?.distributorShare) {
distributorShare = config.distributorShare / 100
}
if (config?.minWithdrawAmount) {
minWithdrawAmount = Number(config.minWithdrawAmount)
}
} catch (e) { }
// 1. 查询用户基本信息
const user = await prisma.users.findUnique({
where: { id: userId },
select: {
id: true,
nickname: true,
referral_code: true,
earnings: true,
pending_earnings: true,
withdrawn_earnings: true,
referral_count: true
}
})
if (!user) {
return NextResponse.json({
success: false,
error: '用户不存在'
}, { status: 404 })
}
// 2. 使用 Prisma 聚合查询绑定统计
const [totalBindings, activeBindings, convertedBindings, expiredBindings] = await Promise.all([
prisma.referral_bindings.count({
where: { referrer_id: userId }
}),
prisma.referral_bindings.count({
where: {
referrer_id: userId,
status: 'active',
expiry_date: { gt: new Date() }
}
}),
prisma.referral_bindings.count({
where: {
referrer_id: userId,
status: 'active',
purchase_count: { gt: 0 }
}
}),
prisma.referral_bindings.count({
where: {
referrer_id: userId,
OR: [
{ status: { in: ['expired', 'cancelled'] } },
{ status: 'active', expiry_date: { lte: new Date() } }
]
}
})
])
// 3. 付款统计(使用 Prisma 聚合)
const paidOrders = await prisma.orders.aggregate({
where: {
referrer_id: userId,
status: 'paid'
},
_count: { user_id: true },
_sum: { amount: true }
})
const paidCount = await prisma.orders.findMany({
where: {
referrer_id: userId,
status: 'paid'
},
distinct: ['user_id'],
select: { user_id: true }
})
const totalAmount = Number(paidOrders._sum.amount || 0)
const uniquePaidCount = paidCount.length
// 4. 访问统计
let totalVisits = totalBindings
try {
const visits = await prisma.referral_visits.groupBy({
by: ['visitor_id'],
where: { referrer_id: userId }
})
totalVisits = visits.length
} catch (e) {
console.log('[ReferralData] 访问统计表不存在')
}
// 5. 待审核 + 已成功提现金额(用提现表汇总,与 user.withdrawn_earnings 可能不一致时以表为准,避免可提现出现负数)
const [pendingWithdraw, successWithdraw] = await Promise.all([
prisma.withdrawals.aggregate({
where: { user_id: userId, status: 'pending' },
_sum: { amount: true }
}),
prisma.withdrawals.aggregate({
where: { user_id: userId, status: 'success' },
_sum: { amount: true }
})
])
const pendingWithdrawAmount = Number(pendingWithdraw._sum.amount || 0)
const withdrawnFromTable = Number(successWithdraw._sum.amount || 0)
// 6. 获取活跃绑定用户列表
const activeBindingsList = await prisma.referral_bindings.findMany({
where: {
referrer_id: userId,
status: 'active',
expiry_date: { gt: new Date() }
},
take: 20,
orderBy: { binding_date: 'desc' },
include: {
users_referral_bindings_referee_idTousers: {
select: {
id: true,
nickname: true,
avatar: true,
has_full_book: true
}
}
}
})
// 7. 获取已转化用户列表
const convertedBindingsList = await prisma.referral_bindings.findMany({
where: {
referrer_id: userId,
status: 'active',
purchase_count: { gt: 0 }
},
take: 20,
orderBy: { last_purchase_date: 'desc' },
include: {
users_referral_bindings_referee_idTousers: {
select: {
id: true,
nickname: true,
avatar: true
}
}
}
})
// 8. 获取已过期用户列表
const expiredBindingsList = await prisma.referral_bindings.findMany({
where: {
referrer_id: userId,
OR: [
{ status: 'expired' },
{ status: 'active', expiry_date: { lte: new Date() } }
]
},
take: 20,
orderBy: { expiry_date: 'desc' },
include: {
users_referral_bindings_referee_idTousers: {
select: {
id: true,
nickname: true,
avatar: true
}
}
}
})
// 9. 获取收益明细
const earningsDetailsList = await prisma.orders.findMany({
where: {
referrer_id: userId,
status: 'paid'
},
take: 20,
orderBy: { pay_time: 'desc' },
include: {
users: {
select: {
nickname: true,
avatar: true
}
}
}
})
// 计算预估收益
const estimatedEarnings = totalAmount * distributorShare
const totalCommission = totalAmount * distributorShare
// 计算即将过期用户数7天内
const now = new Date()
const sevenDaysLater = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000)
const expiringCount = activeBindingsList.filter(b => {
const expiryDate = new Date(b.expiry_date)
return expiryDate > now && expiryDate <= sevenDaysLater
}).length
return NextResponse.json({
success: true,
data: {
// 核心可见数据
bindingCount: activeBindings,
visitCount: totalVisits,
paidCount: uniquePaidCount,
expiredCount: expiredBindings,
// 收益数据
totalCommission: Math.round(totalCommission * 100) / 100,
availableEarnings: Math.max(0, Math.round((totalCommission - withdrawnFromTable - pendingWithdrawAmount) * 100) / 100),
pendingWithdrawAmount: Math.round(pendingWithdrawAmount * 100) / 100,
withdrawnEarnings: withdrawnFromTable,
earnings: Number(user.earnings) || 0,
pendingEarnings: Number(user.pending_earnings) || 0,
estimatedEarnings: Math.round(estimatedEarnings * 100) / 100,
shareRate: Math.round(distributorShare * 100),
minWithdrawAmount,
// 推荐码
referralCode: user.referral_code,
referralCount: user.referral_count || totalBindings,
// 详细统计
stats: {
totalBindings,
activeBindings,
convertedBindings,
expiredBindings,
expiringCount,
totalPaymentAmount: totalAmount
},
// 用户列表
activeUsers: activeBindingsList.map(b => {
const daysRemaining = Math.max(0, Math.floor((new Date(b.expiry_date).getTime() - Date.now()) / (24 * 60 * 60 * 1000)))
return {
id: b.referee_id,
nickname: b.users_referral_bindings_referee_idTousers.nickname || '用户' + b.referee_id.slice(-4),
avatar: b.users_referral_bindings_referee_idTousers.avatar,
daysRemaining,
hasFullBook: b.users_referral_bindings_referee_idTousers.has_full_book,
bindingDate: b.binding_date,
status: 'active'
}
}),
convertedUsers: convertedBindingsList.map(b => ({
id: b.referee_id,
nickname: b.users_referral_bindings_referee_idTousers.nickname || '用户' + b.referee_id.slice(-4),
avatar: b.users_referral_bindings_referee_idTousers.avatar,
commission: Number(b.total_commission) || 0,
orderAmount: Number(b.total_commission) / distributorShare || 0,
purchaseCount: b.purchase_count || 0,
conversionDate: b.last_purchase_date,
status: 'converted'
})),
expiredUsers: expiredBindingsList.map(b => ({
id: b.referee_id,
nickname: b.users_referral_bindings_referee_idTousers.nickname || '用户' + b.referee_id.slice(-4),
avatar: b.users_referral_bindings_referee_idTousers.avatar,
bindingDate: b.binding_date,
expiryDate: b.expiry_date,
status: 'expired'
})),
// 收益明细
earningsDetails: earningsDetailsList.map(e => ({
id: e.id,
orderSn: e.order_sn,
amount: Number(e.amount),
commission: Number(e.amount) * distributorShare,
productType: e.product_type,
productId: e.product_id,
description: e.description,
buyerNickname: e.users.nickname || '用户' + e.id.slice(-4),
buyerAvatar: e.users.avatar,
payTime: e.pay_time
}))
}
})
} catch (error) {
console.error('[ReferralData] 错误:', error)
return NextResponse.json({
success: false,
error: '获取分销数据失败: ' + (error as Error).message
}, { status: 500 })
}
}