/** * 搜索API * 支持从数据库搜索标题和内容 * 同时支持搜索匹配的人和事情(隐藏功能) */ import { NextRequest, NextResponse } from 'next/server' import { query } from '@/lib/db' import { bookData } from '@/lib/book-data' import fs from 'fs' import path from 'path' /** * 从文件系统搜索章节 */ function searchFromFiles(keyword: string): any[] { const results: any[] = [] const lowerKeyword = keyword.toLowerCase() for (const part of bookData) { for (const chapter of part.chapters) { for (const section of chapter.sections) { // 搜索标题 if (section.title.toLowerCase().includes(lowerKeyword)) { results.push({ id: section.id, title: section.title, partTitle: part.title, chapterTitle: chapter.title, price: section.price, isFree: section.isFree, matchType: 'title', score: 10 // 标题匹配得分更高 }) continue } // 搜索内容 const filePath = path.join(process.cwd(), section.filePath) if (fs.existsSync(filePath)) { try { const content = fs.readFileSync(filePath, 'utf-8') if (content.toLowerCase().includes(lowerKeyword)) { // 提取匹配的上下文 const lowerContent = content.toLowerCase() const matchIndex = lowerContent.indexOf(lowerKeyword) const start = Math.max(0, matchIndex - 50) const end = Math.min(content.length, matchIndex + keyword.length + 50) const snippet = content.substring(start, end) results.push({ id: section.id, title: section.title, partTitle: part.title, chapterTitle: chapter.title, price: section.price, isFree: section.isFree, matchType: 'content', snippet: (start > 0 ? '...' : '') + snippet + (end < content.length ? '...' : ''), score: 5 // 内容匹配得分较低 }) } } catch (e) { // 忽略读取错误 } } } } } // 按得分排序 return results.sort((a, b) => b.score - a.score) } /** * 从数据库搜索章节 */ async function searchFromDB(keyword: string): Promise { try { const results = await query(` SELECT id, section_title as title, part_title as partTitle, chapter_title as chapterTitle, price, is_free as isFree, CASE WHEN section_title LIKE ? THEN 'title' ELSE 'content' END as matchType, CASE WHEN section_title LIKE ? THEN 10 ELSE 5 END as score, SUBSTRING(content, GREATEST(1, LOCATE(?, content) - 50), 150 ) as snippet FROM chapters WHERE section_title LIKE ? OR content LIKE ? ORDER BY score DESC, id ASC LIMIT 50 `, [ `%${keyword}%`, `%${keyword}%`, keyword, `%${keyword}%`, `%${keyword}%` ]) as any[] return results } catch (e) { console.error('[Search API] 数据库搜索失败:', e) return [] } } /** * 提取文章中的人物信息(隐藏功能) * 用于"找伙伴"功能的智能匹配 */ function extractPeopleFromContent(content: string): string[] { const people: string[] = [] // 匹配常见人名模式 // 中文名:2-4个汉字 const chineseNames = content.match(/[\u4e00-\u9fa5]{2,4}(?=:|:|说|的|告诉|表示)/g) || [] // 英文名/昵称:带@或引号的名称 const nicknames = content.match(/["'@]([^"'@\s]+)["']?/g) || [] // 职位+名字模式 const titleNames = content.match(/(?:老板|总|经理|创始人|合伙人|店长)[\u4e00-\u9fa5]{2,3}/g) || [] people.push(...chineseNames.slice(0, 10)) people.push(...nicknames.map(n => n.replace(/["'@]/g, '')).slice(0, 5)) people.push(...titleNames.slice(0, 5)) // 去重 return [...new Set(people)] } /** * 提取文章中的关键事件/标签 */ function extractKeywords(content: string): string[] { const keywords: string[] = [] // 行业关键词 const industries = ['电商', '私域', '社群', '抖音', '直播', '餐饮', '美业', '健康', 'AI', '供应链', '金融', '拍卖', '游戏', '电竞'] // 模式关键词 const patterns = ['轻资产', '复购', '被动收入', '杠杆', '信息差', '流量', '分销', '代理', '加盟'] // 金额模式 const amounts = content.match(/(\d+)万/g) || [] for (const ind of industries) { if (content.includes(ind)) keywords.push(ind) } for (const pat of patterns) { if (content.includes(pat)) keywords.push(pat) } keywords.push(...amounts.slice(0, 5)) return [...new Set(keywords)] } /** * GET - 搜索 * 参数: * - q: 搜索关键词 * - type: 'all' | 'title' | 'content' | 'people' | 'keywords' * - source: 'db' | 'file' | 'auto' (默认auto) */ export async function GET(request: NextRequest) { const { searchParams } = new URL(request.url) const keyword = searchParams.get('q') || searchParams.get('keyword') || '' const type = searchParams.get('type') || 'all' const source = searchParams.get('source') || 'auto' if (!keyword || keyword.length < 1) { return NextResponse.json({ success: false, error: '请输入搜索关键词' }, { status: 400 }) } try { let results: any[] = [] // 根据source选择搜索方式 if (source === 'db') { results = await searchFromDB(keyword) } else if (source === 'file') { results = searchFromFiles(keyword) } else { // auto: 先尝试数据库,失败则使用文件 results = await searchFromDB(keyword) if (results.length === 0) { results = searchFromFiles(keyword) } } // 根据type过滤 if (type === 'title') { results = results.filter(r => r.matchType === 'title') } else if (type === 'content') { results = results.filter(r => r.matchType === 'content') } // 如果搜索人物或关键词(隐藏功能) let people: string[] = [] let keywords: string[] = [] if (type === 'people' || type === 'all') { // 从搜索结果的内容中提取人物 for (const result of results.slice(0, 5)) { const filePath = path.join(process.cwd(), 'book') // 从bookData找到对应文件 for (const part of bookData) { for (const chapter of part.chapters) { const section = chapter.sections.find(s => s.id === result.id) if (section) { const fullPath = path.join(process.cwd(), section.filePath) if (fs.existsSync(fullPath)) { const content = fs.readFileSync(fullPath, 'utf-8') people.push(...extractPeopleFromContent(content)) } } } } } people = [...new Set(people)].slice(0, 20) } if (type === 'keywords' || type === 'all') { // 从搜索结果的内容中提取关键词 for (const result of results.slice(0, 5)) { for (const part of bookData) { for (const chapter of part.chapters) { const section = chapter.sections.find(s => s.id === result.id) if (section) { const fullPath = path.join(process.cwd(), section.filePath) if (fs.existsSync(fullPath)) { const content = fs.readFileSync(fullPath, 'utf-8') keywords.push(...extractKeywords(content)) } } } } } keywords = [...new Set(keywords)].slice(0, 20) } return NextResponse.json({ success: true, data: { keyword, total: results.length, results: results.slice(0, 20), // 限制返回数量 // 隐藏功能数据 people: type === 'people' || type === 'all' ? people : undefined, keywords: type === 'keywords' || type === 'all' ? keywords : undefined } }) } catch (error) { console.error('[Search API] 搜索失败:', error) return NextResponse.json({ success: false, error: '搜索失败: ' + (error as Error).message }, { status: 500 }) } }