287 lines
7.8 KiB
TypeScript
287 lines
7.8 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server'
|
||
import { getMongoClient } from '@/lib/mongodb'
|
||
|
||
// 项目定义(基于真实数据库)
|
||
const PROJECTS = {
|
||
ckb: {
|
||
id: 'ckb',
|
||
name: '存客宝',
|
||
database: 'KR_存客宝',
|
||
collection: '用户资产统一视图',
|
||
color: 'blue',
|
||
icon: 'Users'
|
||
},
|
||
dlm: {
|
||
id: 'dlm',
|
||
name: '点了码',
|
||
database: 'KR_点了码',
|
||
collection: '用户资产统一视图',
|
||
color: 'green',
|
||
icon: 'QrCode'
|
||
},
|
||
weibo: {
|
||
id: 'weibo',
|
||
name: '微博',
|
||
database: 'KR_微博',
|
||
collection: '微博uid+手机',
|
||
color: 'orange',
|
||
icon: 'Globe'
|
||
},
|
||
qq: {
|
||
id: 'qq',
|
||
name: 'QQ社交',
|
||
database: 'KR_腾讯',
|
||
collection: 'QQ+手机',
|
||
color: 'purple',
|
||
icon: 'MessageCircle'
|
||
}
|
||
}
|
||
|
||
// 获取项目列表及统计
|
||
async function getProjectStats() {
|
||
const client = await getMongoClient()
|
||
const stats = []
|
||
|
||
for (const [key, project] of Object.entries(PROJECTS)) {
|
||
try {
|
||
const db = client.db(project.database)
|
||
const count = await db.collection(project.collection).estimatedDocumentCount()
|
||
stats.push({
|
||
...project,
|
||
userCount: count,
|
||
status: 'active'
|
||
})
|
||
} catch {
|
||
stats.push({
|
||
...project,
|
||
userCount: 0,
|
||
status: 'error'
|
||
})
|
||
}
|
||
}
|
||
|
||
return stats
|
||
}
|
||
|
||
// 获取项目下的流量池/标签分类
|
||
async function getProjectPools(projectId: string) {
|
||
const client = await getMongoClient()
|
||
const project = PROJECTS[projectId as keyof typeof PROJECTS]
|
||
|
||
if (!project) {
|
||
return { error: '项目不存在' }
|
||
}
|
||
|
||
const db = client.db(project.database)
|
||
const collection = db.collection(project.collection)
|
||
|
||
// 根据项目类型获取不同的分组字段
|
||
let pools: any[] = []
|
||
|
||
if (projectId === 'ckb') {
|
||
// 存客宝按流量池分组
|
||
const poolStats = await collection.aggregate([
|
||
{ $sample: { size: 50000 } },
|
||
{ $group: {
|
||
_id: '$traffic_pool.pool_name',
|
||
count: { $sum: 1 },
|
||
avgScore: { $avg: '$user_evaluation_score' }
|
||
}},
|
||
{ $sort: { count: -1 } }
|
||
], { maxTimeMS: 10000 }).toArray()
|
||
|
||
// 按标签分组
|
||
const tagStats = await collection.aggregate([
|
||
{ $sample: { size: 50000 } },
|
||
{ $unwind: '$unified_tags' },
|
||
{ $group: { _id: '$unified_tags', count: { $sum: 1 } }},
|
||
{ $sort: { count: -1 } },
|
||
{ $limit: 20 }
|
||
], { maxTimeMS: 10000 }).toArray()
|
||
|
||
const total = await collection.estimatedDocumentCount()
|
||
const ratio = total / 50000
|
||
|
||
pools = [
|
||
...poolStats.filter(p => p._id).map(p => ({
|
||
id: `pool_${p._id}`,
|
||
name: p._id,
|
||
type: 'pool',
|
||
count: Math.round(p.count * ratio),
|
||
avgScore: Math.round(p.avgScore || 0)
|
||
})),
|
||
...tagStats.filter(t => t._id).map(t => ({
|
||
id: `tag_${t._id}`,
|
||
name: t._id,
|
||
type: 'tag',
|
||
count: Math.round(t.count * ratio)
|
||
}))
|
||
]
|
||
} else if (projectId === 'dlm') {
|
||
// 点了码按角色标签分组
|
||
const roleStats = await collection.aggregate([
|
||
{ $unwind: { path: '$角色标签', preserveNullAndEmptyArrays: true } },
|
||
{ $group: { _id: '$角色标签', count: { $sum: 1 } }},
|
||
{ $sort: { count: -1 } }
|
||
], { maxTimeMS: 10000 }).toArray()
|
||
|
||
// 按用户等级分组
|
||
const levelStats = await collection.aggregate([
|
||
{ $group: { _id: '$用户等级', count: { $sum: 1 } }},
|
||
{ $sort: { count: -1 } }
|
||
], { maxTimeMS: 10000 }).toArray()
|
||
|
||
pools = [
|
||
...roleStats.filter(r => r._id).map(r => ({
|
||
id: `role_${r._id}`,
|
||
name: r._id,
|
||
type: 'role',
|
||
count: r.count
|
||
})),
|
||
...levelStats.filter(l => l._id).map(l => ({
|
||
id: `level_${l._id}`,
|
||
name: `${l._id}级用户`,
|
||
type: 'level',
|
||
count: l.count
|
||
}))
|
||
]
|
||
} else if (projectId === 'qq') {
|
||
// QQ按省份分组
|
||
const provinceStats = await collection.aggregate([
|
||
{ $sample: { size: 100000 } },
|
||
{ $match: { '省份': { $exists: true, $ne: null } } },
|
||
{ $group: { _id: '$省份', count: { $sum: 1 } }},
|
||
{ $sort: { count: -1 } },
|
||
{ $limit: 20 }
|
||
], { maxTimeMS: 10000 }).toArray()
|
||
|
||
const total = await collection.estimatedDocumentCount()
|
||
const ratio = total / 100000
|
||
|
||
pools = provinceStats.map(p => ({
|
||
id: `province_${p._id}`,
|
||
name: p._id,
|
||
type: 'province',
|
||
count: Math.round(p.count * ratio)
|
||
}))
|
||
} else if (projectId === 'weibo') {
|
||
// 微博简单统计
|
||
const total = await collection.estimatedDocumentCount()
|
||
pools = [{
|
||
id: 'weibo_all',
|
||
name: '微博用户',
|
||
type: 'all',
|
||
count: total
|
||
}]
|
||
}
|
||
|
||
return {
|
||
project,
|
||
pools,
|
||
totalPools: pools.length
|
||
}
|
||
}
|
||
|
||
// 获取流量池内的用户列表
|
||
async function getPoolUsers(projectId: string, poolId: string, page: number = 1, limit: number = 20) {
|
||
const client = await getMongoClient()
|
||
const project = PROJECTS[projectId as keyof typeof PROJECTS]
|
||
|
||
if (!project) {
|
||
return { error: '项目不存在' }
|
||
}
|
||
|
||
const db = client.db(project.database)
|
||
const collection = db.collection(project.collection)
|
||
|
||
// 解析poolId构建查询条件
|
||
let query: any = {}
|
||
const [type, ...nameParts] = poolId.split('_')
|
||
const name = nameParts.join('_')
|
||
|
||
console.log('Pool query:', { type, name, projectId, poolId })
|
||
|
||
if (type === 'pool') {
|
||
query['traffic_pool.pool_name'] = name
|
||
} else if (type === 'tag') {
|
||
query['unified_tags'] = name
|
||
} else if (type === 'role') {
|
||
query['角色标签'] = name
|
||
} else if (type === 'level') {
|
||
query['用户等级'] = name.replace('级用户', '')
|
||
} else if (type === 'province') {
|
||
query['省份'] = name
|
||
}
|
||
|
||
// 如果没有匹配条件,尝试直接用name搜索
|
||
if (Object.keys(query).length === 0 || type === 'all') {
|
||
query = {}
|
||
}
|
||
|
||
const skip = (page - 1) * limit
|
||
|
||
const [users, total] = await Promise.all([
|
||
collection.find(query)
|
||
.project({
|
||
phone_masked: 1,
|
||
name: 1,
|
||
'core_profile.name': 1,
|
||
user_evaluation_score: 1,
|
||
unified_tags: 1,
|
||
'角色标签': 1,
|
||
'用户等级': 1,
|
||
'rfm_scores.user_level': 1,
|
||
created_at: 1
|
||
})
|
||
.skip(skip)
|
||
.limit(limit)
|
||
.toArray(),
|
||
collection.countDocuments(query)
|
||
])
|
||
|
||
return {
|
||
users: users.map(u => ({
|
||
id: u._id.toString(),
|
||
phone: u.phone_masked || '未知',
|
||
name: u.name || u.core_profile?.name || '未知用户',
|
||
score: u.user_evaluation_score || 0,
|
||
level: u.rfm_scores?.user_level || u['用户等级'] || '-',
|
||
tags: u.unified_tags || u['角色标签'] || [],
|
||
createdAt: u.created_at
|
||
})),
|
||
total,
|
||
page,
|
||
totalPages: Math.ceil(total / limit)
|
||
}
|
||
}
|
||
|
||
export async function GET(request: NextRequest) {
|
||
const { searchParams } = new URL(request.url)
|
||
const action = searchParams.get('action') || 'projects'
|
||
const projectId = searchParams.get('projectId')
|
||
const poolId = searchParams.get('poolId')
|
||
const page = parseInt(searchParams.get('page') || '1')
|
||
|
||
try {
|
||
if (action === 'projects') {
|
||
const stats = await getProjectStats()
|
||
return NextResponse.json({ success: true, projects: stats })
|
||
}
|
||
|
||
if (action === 'pools' && projectId) {
|
||
const pools = await getProjectPools(projectId)
|
||
return NextResponse.json({ success: true, ...pools })
|
||
}
|
||
|
||
if (action === 'users' && projectId && poolId) {
|
||
const users = await getPoolUsers(projectId, poolId, page)
|
||
return NextResponse.json({ success: true, ...users })
|
||
}
|
||
|
||
return NextResponse.json({ error: '无效的操作' }, { status: 400 })
|
||
} catch (error) {
|
||
console.error('流量池API错误:', error)
|
||
return NextResponse.json({ error: '查询失败' }, { status: 500 })
|
||
}
|
||
}
|