178 lines
4.9 KiB
TypeScript
178 lines
4.9 KiB
TypeScript
/**
|
|
* 智能搜索 API 路由
|
|
* 对接神射手 MongoDB - 跨库查询
|
|
*/
|
|
|
|
import { type NextRequest, NextResponse } from "next/server"
|
|
import {
|
|
intelligentSearch,
|
|
queryFullProfile,
|
|
queryPhoneByQQ,
|
|
UserValuationDoc
|
|
} from "@/lib/mongodb"
|
|
|
|
/**
|
|
* 脱敏手机号
|
|
*/
|
|
function maskPhone(phone: string | undefined): string {
|
|
if (!phone) return ''
|
|
if (phone.length !== 11) return phone
|
|
return `${phone.slice(0, 3)}****${phone.slice(-4)}`
|
|
}
|
|
|
|
/**
|
|
* 转换搜索结果
|
|
*/
|
|
function transformSearchResult(doc: UserValuationDoc, queryType: string): any {
|
|
const name = doc.name || '未知用户'
|
|
return {
|
|
id: doc._id?.toString(),
|
|
type: 'user',
|
|
title: name,
|
|
subtitle: doc.phone_masked || maskPhone(doc.phone),
|
|
description: `${doc.province || ''}${doc.city || ''} | ${doc.user_level || '未分级'} | RFM: ${doc.rfm_composite_score?.toFixed(2) || 'N/A'}`,
|
|
data: {
|
|
phone: doc.phone,
|
|
phone_masked: doc.phone_masked || maskPhone(doc.phone),
|
|
name: doc.name,
|
|
province: doc.province,
|
|
city: doc.city,
|
|
userLevel: doc.user_level,
|
|
rfmScore: doc.rfm_composite_score,
|
|
tags: doc.tags || [],
|
|
email: doc.email
|
|
},
|
|
matchedBy: queryType,
|
|
relevanceScore: doc.rfm_composite_score || 0
|
|
}
|
|
}
|
|
|
|
/**
|
|
* GET /api/search
|
|
* 智能搜索
|
|
*/
|
|
export async function GET(request: NextRequest) {
|
|
try {
|
|
const { searchParams } = new URL(request.url)
|
|
const query = searchParams.get("q") || ""
|
|
const limit = Number.parseInt(searchParams.get("limit") || "50")
|
|
const offset = Number.parseInt(searchParams.get("offset") || "0")
|
|
|
|
if (!query.trim()) {
|
|
return NextResponse.json({
|
|
error: "搜索查询不能为空"
|
|
}, { status: 400 })
|
|
}
|
|
|
|
// 执行智能搜索
|
|
const result = await intelligentSearch(query, { limit, offset })
|
|
|
|
// 转换结果
|
|
const items = result.users.map(doc => transformSearchResult(doc, result.queryType))
|
|
|
|
// 如果是 QQ 查询,补充 QQ 信息
|
|
if (result.queryType === 'qq' && result.users.length > 0) {
|
|
const qqInfo = await queryPhoneByQQ(query.trim())
|
|
if (qqInfo) {
|
|
items[0].data.qq = qqInfo.qq
|
|
items[0].data.qqScore = qqInfo.QQ号评分
|
|
items[0].data.carrier = qqInfo.运营商
|
|
}
|
|
}
|
|
|
|
return NextResponse.json({
|
|
query,
|
|
queryType: result.queryType,
|
|
total: result.total,
|
|
items,
|
|
pagination: {
|
|
limit,
|
|
offset,
|
|
hasMore: offset + items.length < result.total
|
|
}
|
|
})
|
|
|
|
} catch (error) {
|
|
console.error("搜索API错误:", error)
|
|
return NextResponse.json({
|
|
error: "搜索失败,请稍后重试",
|
|
details: error instanceof Error ? error.message : 'Unknown error'
|
|
}, { status: 500 })
|
|
}
|
|
}
|
|
|
|
/**
|
|
* POST /api/search
|
|
* 高级搜索(支持更多参数)
|
|
* 返回格式适配前端 SearchResponse 接口
|
|
*/
|
|
export async function POST(request: NextRequest) {
|
|
try {
|
|
const startTime = Date.now()
|
|
const body = await request.json()
|
|
const { query, type = "all", options = {} } = body
|
|
|
|
if (!query || !query.trim()) {
|
|
return NextResponse.json({
|
|
error: "搜索查询不能为空"
|
|
}, { status: 400 })
|
|
}
|
|
|
|
const limit = options.limit || 50
|
|
const offset = options.offset || 0
|
|
|
|
const result = await intelligentSearch(query, { limit, offset })
|
|
|
|
// 转换为前端期望的格式
|
|
const results = result.users.map(doc => {
|
|
const name = doc.name || '未知用户'
|
|
return {
|
|
id: doc._id?.toString() || '',
|
|
type: 'user' as const,
|
|
title: name,
|
|
description: `${doc.province || ''}${doc.city || ''} | 估值: ${doc.user_evaluation_score || 'N/A'}`,
|
|
tags: doc.tags || [],
|
|
relevanceScore: doc.user_evaluation_score || 0,
|
|
updatedAt: doc.computed_at?.toISOString() || new Date().toISOString(),
|
|
metadata: {
|
|
phone: doc.phone,
|
|
phone_masked: doc.phone_masked || maskPhone(doc.phone),
|
|
province: doc.province,
|
|
city: doc.city,
|
|
gender: doc.gender,
|
|
age_range: doc.age_range,
|
|
userLevel: doc.user_level,
|
|
rfmScore: doc.rfm_composite_score,
|
|
evaluationScore: doc.user_evaluation_score,
|
|
dataQuality: doc.data_quality
|
|
}
|
|
}
|
|
})
|
|
|
|
const queryTime = Date.now() - startTime
|
|
|
|
// 返回前端期望的 SearchResponse 格式
|
|
return NextResponse.json({
|
|
results,
|
|
stats: {
|
|
totalResults: result.total,
|
|
queryTime,
|
|
suggestions: [],
|
|
filters: {
|
|
queryType: result.queryType
|
|
}
|
|
},
|
|
hasMore: offset + results.length < result.total
|
|
})
|
|
|
|
} catch (error) {
|
|
console.error("搜索API错误:", error)
|
|
return NextResponse.json({
|
|
error: "搜索失败,请稍后重试",
|
|
results: [],
|
|
stats: { totalResults: 0, queryTime: 0, suggestions: [], filters: {} },
|
|
hasMore: false
|
|
}, { status: 500 })
|
|
}
|
|
}
|