/** * 流量包管理 API * 提供流量包列表、创建、导出等功能 */ import { NextRequest, NextResponse } from 'next/server' import { getMongoClient, maskPhone } from '@/lib/mongodb' // 流量包接口 interface TrafficPackage { id: string name: string description: string userCount: number conditions: { userLevel?: string[] province?: string[] city?: string[] rfmScoreRange?: { min: number; max: number } tags?: string[] } status: 'active' | 'expired' | 'pending' createdAt: string updatedAt: string createdBy: string exportCount: number lastExportAt?: string } // 预定义的流量池配置(基于 user_evaluation_score 字段,分数范围0-5000+) const TRAFFIC_POOLS = { diamond: { name: '钻石池', minScore: 3000, color: 'purple', icon: 'Diamond' }, gold: { name: '黄金池', minScore: 2000, maxScore: 3000, color: 'yellow', icon: 'Award' }, silver: { name: '白银池', minScore: 1000, maxScore: 2000, color: 'gray', icon: 'Medal' }, bronze: { name: '青铜池', minScore: 500, maxScore: 1000, color: 'orange', icon: 'Shield' }, potential: { name: '潜力池', maxScore: 500, color: 'blue', icon: 'TrendingUp' } } // 获取流量池统计(使用采样优化性能) async function getTrafficPoolStats() { try { const client = await getMongoClient() const collection = client.db('KR').collection('用户估值') // 使用采样代替全表扫描 const sampleSize = 100000 const totalDocs = await collection.estimatedDocumentCount() const sampleRatio = totalDocs / sampleSize const stats = await collection.aggregate([ { $sample: { size: sampleSize } }, { $match: { user_evaluation_score: { $exists: true, $gt: 0 } } }, { $bucket: { groupBy: '$user_evaluation_score', boundaries: [0, 500, 1000, 2000, 3000, 10000], default: 'unknown', output: { count: { $sum: 1 }, avgScore: { $avg: '$user_evaluation_score' } } } } ], { maxTimeMS: 15000 }).toArray() const poolStats = [ { pool: 'potential', ...stats.find(s => s._id === 0) || { count: 0 } }, { pool: 'bronze', ...stats.find(s => s._id === 500) || { count: 0 } }, { pool: 'silver', ...stats.find(s => s._id === 1000) || { count: 0 } }, { pool: 'gold', ...stats.find(s => s._id === 2000) || { count: 0 } }, { pool: 'diamond', ...stats.find(s => s._id === 3000) || { count: 0 } } ].map(s => ({ ...TRAFFIC_POOLS[s.pool as keyof typeof TRAFFIC_POOLS], id: s.pool, count: Math.round((s.count || 0) * sampleRatio), // 估算实际数量 avgScore: Math.round((s.avgScore || 0) * 100) / 100 })) return poolStats } catch (error) { console.error('获取流量池统计失败:', error) return [] } } // 根据条件查询用户 async function queryUsersByConditions(conditions: TrafficPackage['conditions']) { const client = await getMongoClient() const collection = client.db('KR').collection('用户估值') const query: any = {} if (conditions.userLevel?.length) { query.user_level = { $in: conditions.userLevel } } if (conditions.province?.length) { query.province = { $in: conditions.province } } if (conditions.city?.length) { query.city = { $in: conditions.city } } if (conditions.rfmScoreRange) { query.rfm_composite_score = { $gte: conditions.rfmScoreRange.min, $lte: conditions.rfmScoreRange.max } } if (conditions.tags?.length) { query.tags = { $in: conditions.tags } } const count = await collection.countDocuments(query) const samples = await collection.find(query) .limit(100) .project({ phone: 1, name: 1, user_level: 1, province: 1, city: 1, rfm_composite_score: 1 }) .toArray() return { count, samples } } // GET: 获取流量包列表或流量池统计 export async function GET(request: NextRequest) { const { searchParams } = new URL(request.url) const action = searchParams.get('action') const id = searchParams.get('id') try { // 获取流量池统计 if (action === 'pools') { const poolStats = await getTrafficPoolStats() return NextResponse.json({ success: true, pools: poolStats }) } // 导出流量包用户数据 if (action === 'export') { const packageId = searchParams.get('packageId') const pool = searchParams.get('pool') || 'gold' const client = await getMongoClient() const collection = client.db('KR').collection('用户估值') // 根据流量池类型获取用户 const scoreRange = { diamond: { $gte: 3000 }, gold: { $gte: 2000, $lt: 3000 }, silver: { $gte: 1000, $lt: 2000 }, bronze: { $gte: 500, $lt: 1000 }, potential: { $lt: 500 } } const query = { user_evaluation_score: scoreRange[pool as keyof typeof scoreRange] || { $gte: 2000 } } const users = await collection.find(query) .limit(1000) // 限制导出数量 .project({ phone: '$phone_masked', name: 1, level: '$user_level', score: '$user_evaluation_score', tags: '$source_channels' }) .toArray() return NextResponse.json({ success: true, users: users.map(u => ({ phone: u.phone || u.phone_masked || '未知', name: u.name || '未知', level: u.level || '-', score: u.score || 0, tags: u.tags || [] })), total: users.length }) } // 获取流量包详情 if (id) { // TODO: 从数据库获取流量包详情 return NextResponse.json({ success: true, package: { id, name: '示例流量包', description: '测试描述', userCount: 1000, conditions: {}, status: 'active', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), createdBy: 'admin', exportCount: 0 } }) } // 获取流量包列表(预定义 + 流量池) const poolStats = await getTrafficPoolStats() const packages: TrafficPackage[] = poolStats.map(pool => ({ id: `pool_${pool.id}`, name: `${pool.name}用户包`, description: `RFM评分 ${pool.id === 'diamond' ? '≥80' : pool.id === 'potential' ? '<20' : `${TRAFFIC_POOLS[pool.id as keyof typeof TRAFFIC_POOLS].minScore || 0}-${TRAFFIC_POOLS[pool.id as keyof typeof TRAFFIC_POOLS].maxScore || 100}`} 的用户群体`, userCount: pool.count, conditions: { rfmScoreRange: { min: TRAFFIC_POOLS[pool.id as keyof typeof TRAFFIC_POOLS].minScore || 0, max: TRAFFIC_POOLS[pool.id as keyof typeof TRAFFIC_POOLS].maxScore || 100 } }, status: 'active', createdAt: '2025-01-01', updatedAt: new Date().toISOString().split('T')[0], createdBy: 'system', exportCount: 0 })) return NextResponse.json({ success: true, packages, total: packages.length, pools: poolStats }) } catch (error: any) { console.error('流量包 API 错误:', error) return NextResponse.json({ success: false, error: error.message }, { status: 500 }) } } // POST: 创建流量包 export async function POST(request: NextRequest) { try { const body = await request.json() const { name, description, conditions } = body if (!name) { return NextResponse.json({ success: false, error: '流量包名称为必填项' }, { status: 400 }) } // 查询符合条件的用户数 const { count, samples } = await queryUsersByConditions(conditions || {}) const newPackage: TrafficPackage = { id: `pkg_${Date.now()}`, name, description: description || '', userCount: count, conditions: conditions || {}, status: 'active', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), createdBy: 'admin', exportCount: 0 } // TODO: 保存到 MongoDB return NextResponse.json({ success: true, package: newPackage, samples: samples.slice(0, 10).map(s => ({ ...s, phone: maskPhone(s.phone) })) }) } catch (error: any) { return NextResponse.json({ success: false, error: error.message }, { status: 500 }) } }