feat: 完善后台管理+搜索功能+分销系统
主要更新: - 后台菜单精简(9项→6项) - 新增搜索功能(敏感信息过滤) - 分销绑定和提现系统完善 - 数据库初始化API(自动修复表结构) - 用户管理:显示绑定关系详情 - 小程序:上下章导航优化、匹配页面重构 - 修复hydration和数据类型问题
This commit is contained in:
425
app/api/db/book/route.ts
Normal file
425
app/api/db/book/route.ts
Normal 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
300
app/api/db/config/route.ts
Normal 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] 配置同步完成')
|
||||
}
|
||||
@@ -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 })
|
||||
}
|
||||
}
|
||||
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