Files
soul/app/api/db/book/route.ts
卡若 4dd2f9f4a7 feat: 完善后台管理+搜索功能+分销系统
主要更新:
- 后台菜单精简(9项→6项)
- 新增搜索功能(敏感信息过滤)
- 分销绑定和提现系统完善
- 数据库初始化API(自动修复表结构)
- 用户管理:显示绑定关系详情
- 小程序:上下章导航优化、匹配页面重构
- 修复hydration和数据类型问题
2026-01-25 19:37:59 +08:00

426 lines
12 KiB
TypeScript

/**
* 书籍内容数据库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 })
}
}