325 lines
9.7 KiB
TypeScript
325 lines
9.7 KiB
TypeScript
/**
|
||
* 分销数据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 })
|
||
}
|
||
}
|