131 lines
3.6 KiB
TypeScript
131 lines
3.6 KiB
TypeScript
/**
|
||
* RFM 分析 API
|
||
* 支持 MongoDB 真实数据 + 内存计算
|
||
*/
|
||
|
||
import { NextResponse } from "next/server"
|
||
import { analyzeUser, type AnalyzeInput } from "@/services/rfm-engine"
|
||
import { analyzeUserRFM } from "@/services/rfm-mongodb-service"
|
||
import { generateText } from "ai"
|
||
import { openai } from "@ai-sdk/openai"
|
||
|
||
/**
|
||
* 可选 AI 标签增强
|
||
*/
|
||
async function aiTagging(chat_logs?: string[]) {
|
||
const text = (chat_logs ?? []).slice(0, 8).join("。")
|
||
if (!text) return null
|
||
if (!process.env.OPENAI_API_KEY) return null
|
||
try {
|
||
const { text: out } = await generateText({
|
||
model: openai("gpt-4o"),
|
||
system:
|
||
"请根据用户聊天片段输出JSON,字段:emotion(积极|中性|消极), intent(弱意图|中等意图|强意图), behavior(数组,中文标签最多3个)。只输出JSON。",
|
||
prompt: `聊天片段:${text}`,
|
||
// @ts-expect-error ai sdk typings are flexible in Next.js
|
||
maxTokens: 200,
|
||
})
|
||
const json = JSON.parse(out.trim())
|
||
return json
|
||
} catch {
|
||
return null
|
||
}
|
||
}
|
||
|
||
export async function POST(req: Request) {
|
||
try {
|
||
const body = await req.json()
|
||
const inputs: AnalyzeInput[] = Array.isArray(body) ? body : [body]
|
||
const useAI = (Array.isArray(body) ? (body as any).useAI : (body as any)?.useAI) ?? false
|
||
const useMongoData = (body as any)?.useMongoData ?? true
|
||
|
||
const results = []
|
||
|
||
for (const input of inputs) {
|
||
let base: any
|
||
let source = 'memory'
|
||
|
||
// 尝试从 MongoDB 获取真实数据
|
||
if (useMongoData && input.user_id) {
|
||
// 如果 user_id 是手机号格式,尝试从 MongoDB 查询
|
||
const phone = input.user_id.replace(/\D/g, '')
|
||
if (/^1[3-9]\d{9}$/.test(phone)) {
|
||
const mongoResult = await analyzeUserRFM(phone)
|
||
if (mongoResult.found) {
|
||
base = mongoResult.data
|
||
source = 'mongodb'
|
||
}
|
||
}
|
||
}
|
||
|
||
// 如果 MongoDB 没有数据,使用内存计算
|
||
if (!base) {
|
||
base = analyzeUser(input)
|
||
source = 'memory'
|
||
}
|
||
|
||
// AI 标签增强
|
||
if (useAI) {
|
||
const ai = await aiTagging(input.chat_logs)
|
||
if (ai) {
|
||
base.tags = base.tags || {}
|
||
base.tags.emotion = ai.emotion ?? base.tags.emotion
|
||
base.tags.intent = ai.intent ?? base.tags.intent
|
||
if (Array.isArray(ai.behavior)) {
|
||
base.tags.behavior = Array.from(new Set([...(base.tags.behavior ?? []), ...ai.behavior]))
|
||
}
|
||
}
|
||
}
|
||
|
||
results.push({ ...base, source })
|
||
}
|
||
|
||
return NextResponse.json({ success: true, data: results })
|
||
|
||
} catch (e: any) {
|
||
console.error('RFM analyze error:', e)
|
||
return NextResponse.json({
|
||
success: false,
|
||
error: e?.message || "Invalid input"
|
||
}, { status: 400 })
|
||
}
|
||
}
|
||
|
||
/**
|
||
* GET 方法:按手机号查询用户 RFM
|
||
*/
|
||
export async function GET(req: Request) {
|
||
try {
|
||
const { searchParams } = new URL(req.url)
|
||
const phone = searchParams.get('phone')
|
||
|
||
if (!phone) {
|
||
return NextResponse.json({
|
||
success: false,
|
||
error: '请提供手机号参数'
|
||
}, { status: 400 })
|
||
}
|
||
|
||
const result = await analyzeUserRFM(phone)
|
||
|
||
if (!result.found) {
|
||
return NextResponse.json({
|
||
success: false,
|
||
error: '未找到该用户'
|
||
}, { status: 404 })
|
||
}
|
||
|
||
return NextResponse.json({
|
||
success: true,
|
||
data: result.data,
|
||
source: 'mongodb'
|
||
})
|
||
|
||
} catch (e: any) {
|
||
return NextResponse.json({
|
||
success: false,
|
||
error: e?.message || "查询失败"
|
||
}, { status: 500 })
|
||
}
|
||
}
|