feat: 完善后台管理+搜索功能+分销系统
主要更新: - 后台菜单精简(9项→6项) - 新增搜索功能(敏感信息过滤) - 分销绑定和提现系统完善 - 数据库初始化API(自动修复表结构) - 用户管理:显示绑定关系详情 - 小程序:上下章导航优化、匹配页面重构 - 修复hydration和数据类型问题
This commit is contained in:
105
app/api/db/users/referrals/route.ts
Normal file
105
app/api/db/users/referrals/route.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* 用户绑定关系API
|
||||
* 获取指定用户的所有绑定用户列表
|
||||
*/
|
||||
import { NextResponse } from 'next/server'
|
||||
import { query } from '@/lib/db'
|
||||
|
||||
export async function GET(request: Request) {
|
||||
try {
|
||||
const { searchParams } = new URL(request.url)
|
||||
const userId = searchParams.get('userId')
|
||||
const referralCode = searchParams.get('code')
|
||||
|
||||
if (!userId && !referralCode) {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: '缺少用户ID或推广码'
|
||||
}, { status: 400 })
|
||||
}
|
||||
|
||||
// 如果传入userId,先获取该用户的推广码
|
||||
let code = referralCode
|
||||
if (userId && !referralCode) {
|
||||
const userRows = await query('SELECT referral_code FROM users WHERE id = ?', [userId]) as any[]
|
||||
if (userRows.length === 0) {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: '用户不存在'
|
||||
}, { status: 404 })
|
||||
}
|
||||
code = userRows[0].referral_code
|
||||
}
|
||||
|
||||
if (!code) {
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
referrals: [],
|
||||
stats: {
|
||||
total: 0,
|
||||
purchased: 0,
|
||||
pendingEarnings: 0,
|
||||
totalEarnings: 0
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 查询通过该推广码绑定的所有用户
|
||||
const referrals = await query(`
|
||||
SELECT
|
||||
id, nickname, avatar, phone, open_id,
|
||||
has_full_book, purchased_sections,
|
||||
created_at, updated_at
|
||||
FROM users
|
||||
WHERE referred_by = ?
|
||||
ORDER BY created_at DESC
|
||||
`, [code]) as any[]
|
||||
|
||||
// 统计信息
|
||||
const purchasedCount = referrals.filter(r => r.has_full_book || (r.purchased_sections && r.purchased_sections !== '[]')).length
|
||||
|
||||
// 查询该用户的收益信息
|
||||
const earningsRows = await query(`
|
||||
SELECT earnings, pending_earnings, withdrawn_earnings
|
||||
FROM users WHERE ${userId ? 'id = ?' : 'referral_code = ?'}
|
||||
`, [userId || code]) as any[]
|
||||
|
||||
const earnings = earningsRows[0] || { earnings: 0, pending_earnings: 0, withdrawn_earnings: 0 }
|
||||
|
||||
// 格式化返回数据
|
||||
const formattedReferrals = referrals.map(r => ({
|
||||
id: r.id,
|
||||
nickname: r.nickname || '微信用户',
|
||||
avatar: r.avatar,
|
||||
phone: r.phone ? r.phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2') : null,
|
||||
hasOpenId: !!r.open_id,
|
||||
hasPurchased: r.has_full_book || (r.purchased_sections && r.purchased_sections !== '[]'),
|
||||
hasFullBook: !!r.has_full_book,
|
||||
purchasedSections: typeof r.purchased_sections === 'string'
|
||||
? JSON.parse(r.purchased_sections || '[]').length
|
||||
: 0,
|
||||
createdAt: r.created_at,
|
||||
status: r.has_full_book ? 'vip' : (r.purchased_sections && r.purchased_sections !== '[]' ? 'paid' : 'free')
|
||||
}))
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
referrals: formattedReferrals,
|
||||
stats: {
|
||||
total: referrals.length,
|
||||
purchased: purchasedCount,
|
||||
free: referrals.length - purchasedCount,
|
||||
earnings: parseFloat(earnings.earnings) || 0,
|
||||
pendingEarnings: parseFloat(earnings.pending_earnings) || 0,
|
||||
withdrawnEarnings: parseFloat(earnings.withdrawn_earnings) || 0
|
||||
}
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.error('Get referrals error:', error)
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: '获取绑定关系失败'
|
||||
}, { status: 500 })
|
||||
}
|
||||
}
|
||||
262
app/api/db/users/route.ts
Normal file
262
app/api/db/users/route.ts
Normal file
@@ -0,0 +1,262 @@
|
||||
/**
|
||||
* 用户管理API
|
||||
* 提供用户的CRUD操作
|
||||
*/
|
||||
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { query } from '@/lib/db'
|
||||
|
||||
// 生成用户ID
|
||||
function generateUserId(): string {
|
||||
return 'user_' + Date.now().toString(36) + Math.random().toString(36).substr(2, 9)
|
||||
}
|
||||
|
||||
// 生成推荐码
|
||||
function generateReferralCode(seed: string): string {
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
||||
const hash = seed.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0)
|
||||
let code = 'SOUL'
|
||||
for (let i = 0; i < 4; i++) {
|
||||
code += chars.charAt((hash + i * 7) % chars.length)
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
/**
|
||||
* GET - 获取用户列表
|
||||
*/
|
||||
export async function GET(request: NextRequest) {
|
||||
const { searchParams } = new URL(request.url)
|
||||
const id = searchParams.get('id')
|
||||
const phone = searchParams.get('phone')
|
||||
const openId = searchParams.get('openId')
|
||||
|
||||
try {
|
||||
// 获取单个用户
|
||||
if (id) {
|
||||
const users = await query('SELECT * FROM users WHERE id = ?', [id]) as any[]
|
||||
if (users.length > 0) {
|
||||
return NextResponse.json({ success: true, user: users[0] })
|
||||
}
|
||||
return NextResponse.json({ success: false, error: '用户不存在' }, { status: 404 })
|
||||
}
|
||||
|
||||
// 通过手机号查询
|
||||
if (phone) {
|
||||
const users = await query('SELECT * FROM users WHERE phone = ?', [phone]) as any[]
|
||||
if (users.length > 0) {
|
||||
return NextResponse.json({ success: true, user: users[0] })
|
||||
}
|
||||
return NextResponse.json({ success: false, error: '用户不存在' }, { status: 404 })
|
||||
}
|
||||
|
||||
// 通过openId查询
|
||||
if (openId) {
|
||||
const users = await query('SELECT * FROM users WHERE open_id = ?', [openId]) as any[]
|
||||
if (users.length > 0) {
|
||||
return NextResponse.json({ success: true, user: users[0] })
|
||||
}
|
||||
return NextResponse.json({ success: false, error: '用户不存在' }, { status: 404 })
|
||||
}
|
||||
|
||||
// 获取所有用户
|
||||
const users = await query(`
|
||||
SELECT
|
||||
id, open_id, nickname, phone, wechat_id, avatar,
|
||||
referral_code, has_full_book, is_admin,
|
||||
earnings, pending_earnings, referral_count,
|
||||
match_count_today, last_match_date,
|
||||
created_at, updated_at
|
||||
FROM users
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 500
|
||||
`) as any[]
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
users,
|
||||
total: users.length
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.error('[Users API] GET错误:', error)
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: '获取用户失败: ' + (error as Error).message
|
||||
}, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST - 创建用户(注册)
|
||||
*/
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { openId, phone, nickname, password, wechatId, avatar, referredBy, is_admin } = body
|
||||
|
||||
// 检查openId或手机号是否已存在
|
||||
if (openId) {
|
||||
const existing = await query('SELECT id FROM users WHERE open_id = ?', [openId]) as any[]
|
||||
if (existing.length > 0) {
|
||||
// 已存在,返回现有用户
|
||||
const users = await query('SELECT * FROM users WHERE open_id = ?', [openId]) as any[]
|
||||
return NextResponse.json({ success: true, user: users[0], isNew: false })
|
||||
}
|
||||
}
|
||||
|
||||
if (phone) {
|
||||
const existing = await query('SELECT id FROM users WHERE phone = ?', [phone]) as any[]
|
||||
if (existing.length > 0) {
|
||||
return NextResponse.json({ success: false, error: '该手机号已注册' }, { status: 400 })
|
||||
}
|
||||
}
|
||||
|
||||
// 生成用户ID和推荐码
|
||||
const userId = generateUserId()
|
||||
const referralCode = generateReferralCode(openId || phone || userId)
|
||||
|
||||
// 创建用户
|
||||
await query(`
|
||||
INSERT INTO users (
|
||||
id, open_id, phone, nickname, password, wechat_id, avatar,
|
||||
referral_code, referred_by, has_full_book, is_admin,
|
||||
earnings, pending_earnings, referral_count
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, FALSE, ?, 0, 0, 0)
|
||||
`, [
|
||||
userId,
|
||||
openId || null,
|
||||
phone || null,
|
||||
nickname || '用户' + userId.slice(-4),
|
||||
password || null,
|
||||
wechatId || null,
|
||||
avatar || null,
|
||||
referralCode,
|
||||
referredBy || null,
|
||||
is_admin || false
|
||||
])
|
||||
|
||||
// 返回新用户
|
||||
const users = await query('SELECT * FROM users WHERE id = ?', [userId]) as any[]
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
user: users[0],
|
||||
isNew: true,
|
||||
message: '用户创建成功'
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.error('[Users API] POST错误:', error)
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: '创建用户失败: ' + (error as Error).message
|
||||
}, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PUT - 更新用户
|
||||
*/
|
||||
export async function PUT(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { id, nickname, phone, wechatId, avatar, password, has_full_book, is_admin, purchasedSections, earnings, pending_earnings } = body
|
||||
|
||||
if (!id) {
|
||||
return NextResponse.json({ success: false, error: '用户ID不能为空' }, { status: 400 })
|
||||
}
|
||||
|
||||
// 构建更新字段
|
||||
const updates: string[] = []
|
||||
const values: any[] = []
|
||||
|
||||
if (nickname !== undefined) {
|
||||
updates.push('nickname = ?')
|
||||
values.push(nickname)
|
||||
}
|
||||
if (phone !== undefined) {
|
||||
updates.push('phone = ?')
|
||||
values.push(phone)
|
||||
}
|
||||
if (wechatId !== undefined) {
|
||||
updates.push('wechat_id = ?')
|
||||
values.push(wechatId)
|
||||
}
|
||||
if (avatar !== undefined) {
|
||||
updates.push('avatar = ?')
|
||||
values.push(avatar)
|
||||
}
|
||||
if (password !== undefined) {
|
||||
updates.push('password = ?')
|
||||
values.push(password)
|
||||
}
|
||||
if (has_full_book !== undefined) {
|
||||
updates.push('has_full_book = ?')
|
||||
values.push(has_full_book)
|
||||
}
|
||||
if (is_admin !== undefined) {
|
||||
updates.push('is_admin = ?')
|
||||
values.push(is_admin)
|
||||
}
|
||||
if (purchasedSections !== undefined) {
|
||||
updates.push('purchased_sections = ?')
|
||||
values.push(JSON.stringify(purchasedSections))
|
||||
}
|
||||
if (earnings !== undefined) {
|
||||
updates.push('earnings = ?')
|
||||
values.push(earnings)
|
||||
}
|
||||
if (pending_earnings !== undefined) {
|
||||
updates.push('pending_earnings = ?')
|
||||
values.push(pending_earnings)
|
||||
}
|
||||
|
||||
if (updates.length === 0) {
|
||||
return NextResponse.json({ success: false, error: '没有需要更新的字段' }, { status: 400 })
|
||||
}
|
||||
|
||||
values.push(id)
|
||||
await query(`UPDATE users SET ${updates.join(', ')}, updated_at = NOW() WHERE id = ?`, values)
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: '用户更新成功'
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.error('[Users API] PUT错误:', error)
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: '更新用户失败: ' + (error as Error).message
|
||||
}, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE - 删除用户
|
||||
*/
|
||||
export async function DELETE(request: NextRequest) {
|
||||
const { searchParams } = new URL(request.url)
|
||||
const id = searchParams.get('id')
|
||||
|
||||
if (!id) {
|
||||
return NextResponse.json({ success: false, error: '用户ID不能为空' }, { status: 400 })
|
||||
}
|
||||
|
||||
try {
|
||||
await query('DELETE FROM users WHERE id = ?', [id])
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: '用户删除成功'
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.error('[Users API] DELETE错误:', error)
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: '删除用户失败: ' + (error as Error).message
|
||||
}, { status: 500 })
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user