feat: 完善后台管理+搜索功能+分销系统

主要更新:
- 后台菜单精简(9项→6项)
- 新增搜索功能(敏感信息过滤)
- 分销绑定和提现系统完善
- 数据库初始化API(自动修复表结构)
- 用户管理:显示绑定关系详情
- 小程序:上下章导航优化、匹配页面重构
- 修复hydration和数据类型问题
This commit is contained in:
卡若
2026-01-25 19:37:59 +08:00
parent 65d2831a45
commit 4dd2f9f4a7
49 changed files with 5921 additions and 636 deletions

425
app/api/db/book/route.ts Normal file
View File

@@ -0,0 +1,425 @@
/**
* 书籍内容数据库API
* 支持完整的CRUD操作 - 读取/写入/修改/删除章节
* 同时支持文件系统和数据库双写
*/
import { NextRequest, NextResponse } from 'next/server'
import { query } from '@/lib/db'
import fs from 'fs'
import path from 'path'
import { bookData } from '@/lib/book-data'
// 获取章节内容(从数据库或文件系统)
async function getSectionContent(id: string): Promise<{content: string, source: 'db' | 'file'} | null> {
try {
// 先从数据库查询
const results = await query(
'SELECT content, section_title FROM chapters WHERE id = ?',
[id]
) as any[]
if (results.length > 0 && results[0].content) {
return { content: results[0].content, source: 'db' }
}
} catch (e) {
console.log('[Book API] 数据库查询失败,尝试从文件读取:', e)
}
// 从文件系统读取
const filePath = findSectionFilePath(id)
if (filePath && fs.existsSync(filePath)) {
try {
const content = fs.readFileSync(filePath, 'utf-8')
return { content, source: 'file' }
} catch (e) {
console.error('[Book API] 读取文件失败:', e)
}
}
return null
}
// 根据section ID查找对应的文件路径
function findSectionFilePath(id: string): string | null {
for (const part of bookData) {
for (const chapter of part.chapters) {
const section = chapter.sections.find(s => s.id === id)
if (section?.filePath) {
return path.join(process.cwd(), section.filePath)
}
}
}
return null
}
// 获取section的完整信息
function getSectionInfo(id: string) {
for (const part of bookData) {
for (const chapter of part.chapters) {
const section = chapter.sections.find(s => s.id === id)
if (section) {
return {
section,
chapter,
part,
partId: part.id,
chapterId: chapter.id,
partTitle: part.title,
chapterTitle: chapter.title
}
}
}
}
return null
}
/**
* GET - 读取章节内容
* 支持参数:
* - id: 章节ID
* - action: 'read' | 'export' | 'list'
*/
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url)
const action = searchParams.get('action') || 'read'
const id = searchParams.get('id')
try {
// 读取单个章节
if (action === 'read' && id) {
const result = await getSectionContent(id)
const sectionInfo = getSectionInfo(id)
if (result) {
return NextResponse.json({
success: true,
section: {
id,
content: result.content,
source: result.source,
title: sectionInfo?.section.title || '',
price: sectionInfo?.section.price || 1,
partTitle: sectionInfo?.partTitle,
chapterTitle: sectionInfo?.chapterTitle
}
})
} else {
return NextResponse.json({
success: false,
error: '章节不存在或无法读取'
}, { status: 404 })
}
}
// 导出所有章节
if (action === 'export') {
const sections: any[] = []
for (const part of bookData) {
for (const chapter of part.chapters) {
for (const section of chapter.sections) {
const content = await getSectionContent(section.id)
sections.push({
id: section.id,
title: section.title,
price: section.price,
isFree: section.isFree,
partId: part.id,
partTitle: part.title,
chapterId: chapter.id,
chapterTitle: chapter.title,
content: content?.content || '',
source: content?.source || 'none'
})
}
}
}
const blob = JSON.stringify(sections, null, 2)
return new NextResponse(blob, {
headers: {
'Content-Type': 'application/json',
'Content-Disposition': `attachment; filename="book_sections_${new Date().toISOString().split('T')[0]}.json"`
}
})
}
// 列出所有章节(不含内容)
if (action === 'list') {
const sections: any[] = []
for (const part of bookData) {
for (const chapter of part.chapters) {
for (const section of chapter.sections) {
sections.push({
id: section.id,
title: section.title,
price: section.price,
isFree: section.isFree,
partId: part.id,
partTitle: part.title,
chapterId: chapter.id,
chapterTitle: chapter.title,
filePath: section.filePath
})
}
}
}
return NextResponse.json({
success: true,
sections,
total: sections.length
})
}
return NextResponse.json({
success: false,
error: '无效的操作或缺少参数'
}, { status: 400 })
} catch (error) {
console.error('[Book API] GET错误:', error)
return NextResponse.json({
success: false,
error: '获取章节失败: ' + (error as Error).message
}, { status: 500 })
}
}
/**
* POST - 同步/导入章节
* 支持action:
* - sync: 同步文件系统到数据库
* - import: 批量导入章节数据
*/
export async function POST(request: NextRequest) {
try {
const body = await request.json()
const { action, data } = body
// 同步到数据库
if (action === 'sync') {
let synced = 0
let failed = 0
for (const part of bookData) {
for (const chapter of part.chapters) {
for (const section of chapter.sections) {
try {
const filePath = path.join(process.cwd(), section.filePath)
let content = ''
if (fs.existsSync(filePath)) {
content = fs.readFileSync(filePath, 'utf-8')
}
// 插入或更新到数据库
await query(`
INSERT INTO chapters (id, part_id, part_title, chapter_id, chapter_title, section_title, content, word_count, is_free, price, sort_order, status)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'published')
ON DUPLICATE KEY UPDATE
part_title = VALUES(part_title),
chapter_title = VALUES(chapter_title),
section_title = VALUES(section_title),
content = VALUES(content),
word_count = VALUES(word_count),
is_free = VALUES(is_free),
price = VALUES(price),
updated_at = CURRENT_TIMESTAMP
`, [
section.id,
part.id,
part.title,
chapter.id,
chapter.title,
section.title,
content,
content.length,
section.isFree,
section.price,
synced
])
synced++
} catch (e) {
console.error(`[Book API] 同步章节${section.id}失败:`, e)
failed++
}
}
}
}
return NextResponse.json({
success: true,
message: `同步完成:成功 ${synced} 个章节,失败 ${failed}`,
synced,
failed
})
}
// 导入数据
if (action === 'import' && data) {
let imported = 0
let failed = 0
for (const item of data) {
try {
await query(`
INSERT INTO chapters (id, part_id, part_title, chapter_id, chapter_title, section_title, content, word_count, is_free, price, status)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'published')
ON DUPLICATE KEY UPDATE
section_title = VALUES(section_title),
content = VALUES(content),
word_count = VALUES(word_count),
is_free = VALUES(is_free),
price = VALUES(price),
updated_at = CURRENT_TIMESTAMP
`, [
item.id,
item.partId || 'part-1',
item.partTitle || '未分类',
item.chapterId || 'chapter-1',
item.chapterTitle || '未分类',
item.title,
item.content || '',
(item.content || '').length,
item.is_free || false,
item.price || 1
])
imported++
} catch (e) {
console.error(`[Book API] 导入章节${item.id}失败:`, e)
failed++
}
}
return NextResponse.json({
success: true,
message: `导入完成:成功 ${imported} 个章节,失败 ${failed}`,
imported,
failed
})
}
return NextResponse.json({
success: false,
error: '无效的操作'
}, { status: 400 })
} catch (error) {
console.error('[Book 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, title, content, price, saveToFile = true } = body
if (!id) {
return NextResponse.json({
success: false,
error: '章节ID不能为空'
}, { status: 400 })
}
const sectionInfo = getSectionInfo(id)
// 更新数据库
try {
await query(`
INSERT INTO chapters (id, part_id, part_title, chapter_id, chapter_title, section_title, content, word_count, price, status)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'published')
ON DUPLICATE KEY UPDATE
section_title = VALUES(section_title),
content = VALUES(content),
word_count = VALUES(word_count),
price = VALUES(price),
updated_at = CURRENT_TIMESTAMP
`, [
id,
sectionInfo?.partId || 'part-1',
sectionInfo?.partTitle || '未分类',
sectionInfo?.chapterId || 'chapter-1',
sectionInfo?.chapterTitle || '未分类',
title || sectionInfo?.section.title || '',
content || '',
(content || '').length,
price ?? sectionInfo?.section.price ?? 1
])
} catch (e) {
console.error('[Book API] 更新数据库失败:', e)
}
// 同时保存到文件系统
if (saveToFile && sectionInfo?.section.filePath) {
const filePath = path.join(process.cwd(), sectionInfo.section.filePath)
try {
// 确保目录存在
const dir = path.dirname(filePath)
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true })
}
fs.writeFileSync(filePath, content || '', 'utf-8')
} catch (e) {
console.error('[Book API] 保存文件失败:', e)
}
}
return NextResponse.json({
success: true,
message: '章节更新成功',
id
})
} catch (error) {
console.error('[Book 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 chapters WHERE id = ?', [id])
return NextResponse.json({
success: true,
message: '章节删除成功',
id
})
} catch (error) {
console.error('[Book API] DELETE错误:', error)
return NextResponse.json({
success: false,
error: '删除章节失败: ' + (error as Error).message
}, { status: 500 })
}
}

300
app/api/db/config/route.ts Normal file
View File

@@ -0,0 +1,300 @@
/**
* 系统配置API
* 优先读取数据库配置,失败时读取本地默认配置
* 支持配置的增删改查
*/
import { NextRequest, NextResponse } from 'next/server'
import { query, getConfig, setConfig } from '@/lib/db'
// 本地默认配置(作为数据库备份)
const DEFAULT_CONFIGS: Record<string, any> = {
// 站点配置
site_config: {
siteName: 'Soul创业派对',
siteDescription: '来自派对房的真实商业故事',
logo: '/icon.svg',
keywords: ['创业', 'Soul', '私域运营', '商业案例'],
icp: '',
analytics: ''
},
// 匹配功能配置
match_config: {
matchTypes: [
{ id: 'partner', label: '创业合伙', matchLabel: '创业伙伴', icon: '⭐', matchFromDB: true, showJoinAfterMatch: false, enabled: true },
{ id: 'investor', label: '资源对接', matchLabel: '资源对接', icon: '👥', matchFromDB: false, showJoinAfterMatch: true, enabled: true },
{ id: 'mentor', label: '导师顾问', matchLabel: '商业顾问', icon: '❤️', matchFromDB: false, showJoinAfterMatch: true, enabled: true },
{ id: 'team', label: '团队招募', matchLabel: '加入项目', icon: '🎮', matchFromDB: false, showJoinAfterMatch: true, enabled: true }
],
freeMatchLimit: 3,
matchPrice: 1,
settings: {
enableFreeMatches: true,
enablePaidMatches: true,
maxMatchesPerDay: 10
}
},
// 分销配置
referral_config: {
distributorShare: 90,
minWithdrawAmount: 10,
bindingDays: 30,
userDiscount: 5,
enableAutoWithdraw: false
},
// 价格配置
price_config: {
sectionPrice: 1,
fullBookPrice: 9.9,
premiumBookPrice: 19.9,
matchPrice: 1
},
// 支付配置
payment_config: {
wechat: {
enabled: true,
appId: 'wx432c93e275548671',
mchId: '1318592501'
},
alipay: {
enabled: true,
pid: '2088511801157159'
},
wechatGroupUrl: '' // 支付成功后跳转的微信群链接
},
// 书籍配置
book_config: {
totalSections: 62,
freeSections: ['preface', 'epilogue', '1.1', 'appendix-1', 'appendix-2', 'appendix-3'],
latestSectionId: '9.14'
}
}
/**
* GET - 获取配置
* 参数: key - 配置键名,不传则返回所有配置
*/
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url)
const key = searchParams.get('key')
const forceLocal = searchParams.get('forceLocal') === 'true'
try {
if (key) {
// 获取单个配置
let config = null
if (!forceLocal) {
// 优先从数据库读取
try {
config = await getConfig(key)
} catch (e) {
console.log(`[Config API] 数据库读取${key}失败,使用本地配置`)
}
}
// 数据库没有则使用本地默认
if (!config) {
config = DEFAULT_CONFIGS[key] || null
}
if (config) {
return NextResponse.json({
success: true,
key,
config,
source: config === DEFAULT_CONFIGS[key] ? 'local' : 'database'
})
}
return NextResponse.json({
success: false,
error: '配置不存在'
}, { status: 404 })
}
// 获取所有配置
const allConfigs: Record<string, any> = {}
const sources: Record<string, string> = {}
for (const configKey of Object.keys(DEFAULT_CONFIGS)) {
let config = null
if (!forceLocal) {
try {
config = await getConfig(configKey)
} catch (e) {
// 忽略数据库错误
}
}
if (config) {
allConfigs[configKey] = config
sources[configKey] = 'database'
} else {
allConfigs[configKey] = DEFAULT_CONFIGS[configKey]
sources[configKey] = 'local'
}
}
return NextResponse.json({
success: true,
configs: allConfigs,
sources
})
} catch (error) {
console.error('[Config 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 { key, config, description } = body
if (!key || !config) {
return NextResponse.json({
success: false,
error: '配置键名和配置值不能为空'
}, { status: 400 })
}
// 保存到数据库
const success = await setConfig(key, config, description)
if (success) {
return NextResponse.json({
success: true,
message: '配置保存成功',
key
})
} else {
return NextResponse.json({
success: false,
error: '配置保存失败'
}, { status: 500 })
}
} catch (error) {
console.error('[Config 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 { configs } = body
if (!configs || typeof configs !== 'object') {
return NextResponse.json({
success: false,
error: '配置数据格式错误'
}, { status: 400 })
}
let successCount = 0
let failedCount = 0
for (const [key, config] of Object.entries(configs)) {
try {
const success = await setConfig(key, config)
if (success) {
successCount++
} else {
failedCount++
}
} catch (e) {
failedCount++
}
}
return NextResponse.json({
success: true,
message: `配置更新完成:成功${successCount}个,失败${failedCount}`,
successCount,
failedCount
})
} catch (error) {
console.error('[Config 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 key = searchParams.get('key')
if (!key) {
return NextResponse.json({
success: false,
error: '配置键名不能为空'
}, { status: 400 })
}
try {
await query('DELETE FROM system_config WHERE config_key = ?', [key])
return NextResponse.json({
success: true,
message: '配置已删除,将使用本地默认值',
key
})
} catch (error) {
console.error('[Config API] DELETE错误:', error)
return NextResponse.json({
success: false,
error: '删除配置失败: ' + (error as Error).message
}, { status: 500 })
}
}
/**
* 初始化:将本地配置同步到数据库
*/
export async function syncLocalToDatabase() {
console.log('[Config] 开始同步本地配置到数据库...')
for (const [key, config] of Object.entries(DEFAULT_CONFIGS)) {
try {
// 检查数据库是否已有该配置
const existing = await getConfig(key)
if (!existing) {
// 数据库没有,则写入
await setConfig(key, config, `默认${key}配置`)
console.log(`[Config] 同步配置: ${key}`)
}
} catch (e) {
console.error(`[Config] 同步${key}失败:`, e)
}
}
console.log('[Config] 配置同步完成')
}

View File

@@ -1,83 +1,173 @@
/**
* 数据库初始化API
* 创建数据库表结构和默认配置
* 数据库初始化/升级API
* 用于添加缺失的字段,确保表结构完整
*/
import { NextResponse } from 'next/server'
import { initDatabase } from '@/lib/db'
import { NextRequest, NextResponse } from 'next/server'
import { query } from '@/lib/db'
/**
* POST - 初始化数据库
* GET - 初始化/升级数据库表结构
*/
export async function POST(request: Request) {
export async function GET(request: NextRequest) {
const results: string[] = []
try {
const body = await request.json()
const { adminToken } = body
// 简单的管理员验证
if (adminToken !== 'init_db_2025') {
return NextResponse.json({
success: false,
error: '无权限执行此操作'
}, { status: 403 })
console.log('[DB Init] 开始检查并升级数据库结构...')
// 1. 检查users表是否存在
try {
await query('SELECT 1 FROM users LIMIT 1')
results.push('✅ users表已存在')
} catch (e) {
// 创建users表
await query(`
CREATE TABLE IF NOT EXISTS users (
id VARCHAR(50) PRIMARY KEY,
open_id VARCHAR(100) UNIQUE,
session_key VARCHAR(100),
nickname VARCHAR(100),
avatar VARCHAR(500),
phone VARCHAR(20),
password VARCHAR(100),
wechat_id VARCHAR(100),
referral_code VARCHAR(20) UNIQUE,
referred_by VARCHAR(50),
purchased_sections JSON DEFAULT '[]',
has_full_book BOOLEAN DEFAULT FALSE,
is_admin BOOLEAN DEFAULT FALSE,
earnings DECIMAL(10,2) DEFAULT 0,
pending_earnings DECIMAL(10,2) DEFAULT 0,
withdrawn_earnings DECIMAL(10,2) DEFAULT 0,
referral_count INT DEFAULT 0,
match_count_today INT DEFAULT 0,
last_match_date DATE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
`)
results.push('✅ 创建users表')
}
console.log('[DB Init] 开始初始化数据库...')
await initDatabase()
console.log('[DB Init] 数据库初始化完成')
// 2. 修改open_id字段允许NULL后台添加用户时可能没有openId
try {
await query('ALTER TABLE users MODIFY COLUMN open_id VARCHAR(100) NULL')
results.push('✅ 修改open_id允许NULL')
} catch (e: any) {
results.push(`⏭️ open_id字段: ${e.message?.includes('Duplicate') ? '已处理' : e.message}`)
}
// 3. 添加可能缺失的字段用ALTER TABLE
const columnsToAdd = [
{ name: 'password', type: 'VARCHAR(100)' },
{ name: 'session_key', type: 'VARCHAR(100)' },
{ name: 'referred_by', type: 'VARCHAR(50)' },
{ name: 'is_admin', type: 'BOOLEAN DEFAULT FALSE' },
{ name: 'match_count_today', type: 'INT DEFAULT 0' },
{ name: 'last_match_date', type: 'DATE' },
{ name: 'withdrawn_earnings', type: 'DECIMAL(10,2) DEFAULT 0' },
{ name: 'avatar', type: 'VARCHAR(500)' },
{ name: 'wechat_id', type: 'VARCHAR(100)' }
]
for (const col of columnsToAdd) {
try {
// 先检查列是否存在
const checkResult = await query(`
SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'users' AND COLUMN_NAME = ?
`, [col.name]) as any[]
if (checkResult.length === 0) {
// 列不存在,添加
await query(`ALTER TABLE users ADD COLUMN ${col.name} ${col.type}`)
results.push(`✅ 添加字段: ${col.name}`)
} else {
results.push(`⏭️ 字段已存在: ${col.name}`)
}
} catch (e: any) {
results.push(`⚠️ 处理字段${col.name}时出错: ${e.message}`)
}
}
// 3. 添加索引(如果不存在)
const indexesToAdd = [
{ name: 'idx_open_id', column: 'open_id' },
{ name: 'idx_phone', column: 'phone' },
{ name: 'idx_referral_code', column: 'referral_code' },
{ name: 'idx_referred_by', column: 'referred_by' }
]
for (const idx of indexesToAdd) {
try {
const checkResult = await query(`
SHOW INDEX FROM users WHERE Key_name = ?
`, [idx.name]) as any[]
if (checkResult.length === 0) {
await query(`CREATE INDEX ${idx.name} ON users(${idx.column})`)
results.push(`✅ 添加索引: ${idx.name}`)
}
} catch (e: any) {
// 忽略索引错误
}
}
// 4. 检查提现记录表
try {
await query('SELECT 1 FROM withdrawals LIMIT 1')
results.push('✅ withdrawals表已存在')
} catch (e) {
await query(`
CREATE TABLE IF NOT EXISTS withdrawals (
id VARCHAR(50) PRIMARY KEY,
user_id VARCHAR(50) NOT NULL,
amount DECIMAL(10,2) NOT NULL,
status ENUM('pending', 'processing', 'success', 'failed') DEFAULT 'pending',
wechat_openid VARCHAR(100),
transaction_id VARCHAR(100),
error_message VARCHAR(500),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
processed_at TIMESTAMP,
INDEX idx_user_id (user_id),
INDEX idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
`)
results.push('✅ 创建withdrawals表')
}
// 5. 检查系统配置表
try {
await query('SELECT 1 FROM system_config LIMIT 1')
results.push('✅ system_config表已存在')
} catch (e) {
await query(`
CREATE TABLE IF NOT EXISTS system_config (
id INT AUTO_INCREMENT PRIMARY KEY,
config_key VARCHAR(100) UNIQUE NOT NULL,
config_value JSON NOT NULL,
description VARCHAR(200),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
`)
results.push('✅ 创建system_config表')
}
console.log('[DB Init] 数据库升级完成')
return NextResponse.json({
success: true,
data: {
message: '数据库初始化成功',
timestamp: new Date().toISOString()
}
message: '数据库初始化/升级完成',
results
})
} catch (error) {
console.error('[DB Init] 数据库初始化失败:', error)
console.error('[DB Init] 错误:', error)
return NextResponse.json({
success: false,
error: '数据库初始化失败: ' + (error as Error).message
error: '数据库初始化失败: ' + (error as Error).message,
results
}, { status: 500 })
}
}
/**
* GET - 检查数据库状态
*/
export async function GET() {
try {
const { query } = await import('@/lib/db')
// 检查数据库连接
await query('SELECT 1')
// 检查表是否存在
const tables = await query(`
SELECT TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = DATABASE()
`) as any[]
const tableNames = tables.map(t => t.TABLE_NAME)
return NextResponse.json({
success: true,
data: {
connected: true,
tables: tableNames,
tablesCount: tableNames.length
}
})
} catch (error) {
console.error('[DB Status] 检查数据库状态失败:', error)
return NextResponse.json({
success: false,
error: '数据库连接失败: ' + (error as Error).message
}, { status: 500 })
}
}

View 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
View 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 })
}
}