主要更新: - 后台菜单精简(9项→6项) - 新增搜索功能(敏感信息过滤) - 分销绑定和提现系统完善 - 数据库初始化API(自动修复表结构) - 用户管理:显示绑定关系详情 - 小程序:上下章导航优化、匹配页面重构 - 修复hydration和数据类型问题
151 lines
5.4 KiB
TypeScript
151 lines
5.4 KiB
TypeScript
/**
|
||
* 章节搜索API
|
||
* 搜索章节标题和内容,不返回用户敏感信息
|
||
*/
|
||
import { NextResponse } from 'next/server'
|
||
import fs from 'fs'
|
||
import path from 'path'
|
||
|
||
export async function GET(request: Request) {
|
||
try {
|
||
const { searchParams } = new URL(request.url)
|
||
const keyword = searchParams.get('q') || ''
|
||
|
||
if (!keyword || keyword.trim().length < 1) {
|
||
return NextResponse.json({
|
||
success: true,
|
||
results: [],
|
||
total: 0,
|
||
message: '请输入搜索关键词'
|
||
})
|
||
}
|
||
|
||
const searchTerm = keyword.trim().toLowerCase()
|
||
|
||
// 读取章节数据
|
||
const dataPath = path.join(process.cwd(), 'public/book-chapters.json')
|
||
const fileContent = fs.readFileSync(dataPath, 'utf-8')
|
||
const chaptersData = JSON.parse(fileContent)
|
||
|
||
// 读取书籍内容目录
|
||
const bookDir = path.join(process.cwd(), 'book')
|
||
|
||
const results: any[] = []
|
||
|
||
// 遍历章节搜索
|
||
for (const chapter of chaptersData) {
|
||
const titleMatch = chapter.title?.toLowerCase().includes(searchTerm)
|
||
const idMatch = chapter.id?.toLowerCase().includes(searchTerm)
|
||
const partMatch = chapter.partTitle?.toLowerCase().includes(searchTerm)
|
||
|
||
// 尝试读取章节内容进行搜索
|
||
let contentMatch = false
|
||
let matchedContent = ''
|
||
|
||
// 兼容两种字段名: file 或 filePath
|
||
const filePathField = chapter.filePath || chapter.file
|
||
if (filePathField) {
|
||
try {
|
||
// 如果是绝对路径,直接使用;否则相对于项目根目录
|
||
const filePath = filePathField.startsWith('/') ? filePathField : path.join(process.cwd(), filePathField)
|
||
if (fs.existsSync(filePath)) {
|
||
const content = fs.readFileSync(filePath, 'utf-8')
|
||
// 移除敏感信息(手机号、微信号等)
|
||
const cleanContent = content
|
||
.replace(/1[3-9]\d{9}/g, '***') // 手机号
|
||
.replace(/微信[::]\s*\S+/g, '微信:***') // 微信号
|
||
.replace(/QQ[::]\s*\d+/g, 'QQ:***') // QQ号
|
||
.replace(/邮箱[::]\s*\S+@\S+/g, '邮箱:***') // 邮箱
|
||
|
||
if (cleanContent.toLowerCase().includes(searchTerm)) {
|
||
contentMatch = true
|
||
// 提取匹配的上下文(前后50个字符)
|
||
const lowerContent = cleanContent.toLowerCase()
|
||
const matchIndex = lowerContent.indexOf(searchTerm)
|
||
if (matchIndex !== -1) {
|
||
const start = Math.max(0, matchIndex - 30)
|
||
const end = Math.min(cleanContent.length, matchIndex + searchTerm.length + 50)
|
||
matchedContent = (start > 0 ? '...' : '') +
|
||
cleanContent.slice(start, end).replace(/\n/g, ' ') +
|
||
(end < cleanContent.length ? '...' : '')
|
||
}
|
||
}
|
||
}
|
||
} catch (e) {
|
||
// 文件读取失败,跳过内容搜索
|
||
}
|
||
}
|
||
|
||
if (titleMatch || idMatch || partMatch || contentMatch) {
|
||
// 从标题中提取章节号(如 "1.1 荷包:..." -> "1.1")
|
||
const sectionIdMatch = chapter.title?.match(/^(\d+\.\d+)\s/)
|
||
const sectionId = sectionIdMatch ? sectionIdMatch[1] : chapter.id
|
||
|
||
// 处理特殊ID
|
||
let finalId = sectionId
|
||
if (chapter.id === 'preface' || chapter.title?.includes('序言')) {
|
||
finalId = 'preface'
|
||
} else if (chapter.id === 'epilogue') {
|
||
finalId = 'epilogue'
|
||
} else if (chapter.id?.startsWith('appendix')) {
|
||
finalId = chapter.id
|
||
}
|
||
|
||
// 判断是否免费章节
|
||
const freeIds = ['preface', 'epilogue', '1.1', 'appendix-1', 'appendix-2', 'appendix-3']
|
||
const isFree = freeIds.includes(finalId)
|
||
|
||
results.push({
|
||
id: finalId, // 使用提取的章节号
|
||
title: chapter.title,
|
||
part: chapter.partTitle || chapter.part || '',
|
||
chapter: chapter.chapterDir || chapter.chapter || '',
|
||
isFree: isFree,
|
||
matchType: titleMatch ? 'title' : (idMatch ? 'id' : (partMatch ? 'part' : 'content')),
|
||
matchedContent: contentMatch ? matchedContent : '',
|
||
// 格式化章节号
|
||
chapterLabel: formatChapterLabel(finalId, chapter.index)
|
||
})
|
||
}
|
||
}
|
||
|
||
// 按匹配类型排序:标题匹配 > ID匹配 > 内容匹配
|
||
results.sort((a, b) => {
|
||
const order = { title: 0, id: 1, content: 2 }
|
||
return (order[a.matchType as keyof typeof order] || 2) - (order[b.matchType as keyof typeof order] || 2)
|
||
})
|
||
|
||
return NextResponse.json({
|
||
success: true,
|
||
results: results.slice(0, 20), // 最多返回20条
|
||
total: results.length,
|
||
keyword: keyword
|
||
})
|
||
|
||
} catch (error) {
|
||
console.error('Search error:', error)
|
||
return NextResponse.json({
|
||
success: false,
|
||
error: '搜索失败',
|
||
results: []
|
||
}, { status: 500 })
|
||
}
|
||
}
|
||
|
||
// 格式化章节标签
|
||
function formatChapterLabel(id: string, index?: number): string {
|
||
if (!id) return ''
|
||
if (id === 'preface') return '序言'
|
||
if (id.startsWith('chapter-') && index) return `第${index}节`
|
||
if (id.startsWith('appendix')) return '附录'
|
||
if (id === 'epilogue') return '后记'
|
||
|
||
// 处理 1.1, 3.2 这样的格式
|
||
const match = id.match(/^(\d+)\.(\d+)$/)
|
||
if (match) {
|
||
return `${match[1]}.${match[2]}`
|
||
}
|
||
|
||
return id
|
||
}
|