196 lines
5.1 KiB
TypeScript
196 lines
5.1 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server'
|
|
import { getMongoClient } from '@/lib/mongodb'
|
|
|
|
// 获取所有数据库结构
|
|
async function getDatabaseStructure() {
|
|
const client = await getMongoClient()
|
|
const admin = client.db().admin()
|
|
|
|
// 获取数据库列表
|
|
const dbList = await admin.listDatabases()
|
|
const krDatabases = dbList.databases.filter(db => db.name.startsWith('KR'))
|
|
|
|
const structure = []
|
|
|
|
for (const dbInfo of krDatabases.slice(0, 10)) { // 限制前10个
|
|
try {
|
|
const db = client.db(dbInfo.name)
|
|
const collections = await db.listCollections().toArray()
|
|
|
|
// 获取每个集合的字段示例
|
|
const collectionDetails = []
|
|
for (const col of collections.slice(0, 5)) { // 每个库最多5个集合
|
|
try {
|
|
const sample = await db.collection(col.name).findOne()
|
|
const fields = sample ? Object.keys(sample).filter(k => k !== '_id').slice(0, 10) : []
|
|
const count = await db.collection(col.name).estimatedDocumentCount()
|
|
|
|
collectionDetails.push({
|
|
name: col.name,
|
|
fields,
|
|
count
|
|
})
|
|
} catch {
|
|
// 忽略错误
|
|
}
|
|
}
|
|
|
|
structure.push({
|
|
database: dbInfo.name,
|
|
sizeGB: (dbInfo.sizeOnDisk / 1024 / 1024 / 1024).toFixed(2),
|
|
collections: collectionDetails
|
|
})
|
|
} catch {
|
|
// 忽略错误
|
|
}
|
|
}
|
|
|
|
return structure
|
|
}
|
|
|
|
// 生成血缘节点
|
|
function generateLineageNodes(structure: any[]) {
|
|
const nodes: any[] = []
|
|
const connections: any[] = []
|
|
let yOffset = 50
|
|
|
|
// 数据源节点 (左侧)
|
|
const colors = [
|
|
'from-blue-400 to-blue-600',
|
|
'from-orange-400 to-orange-600',
|
|
'from-green-400 to-green-600',
|
|
'from-red-400 to-red-600',
|
|
'from-purple-400 to-purple-600',
|
|
]
|
|
|
|
structure.forEach((db, i) => {
|
|
const mainCol = db.collections[0]
|
|
if (!mainCol) return
|
|
|
|
nodes.push({
|
|
id: `source_${db.database}`,
|
|
type: 'source',
|
|
name: db.database.replace('KR_', ''),
|
|
database: db.database,
|
|
collection: mainCol.name,
|
|
fields: mainCol.fields.slice(0, 5),
|
|
x: 50,
|
|
y: yOffset,
|
|
color: colors[i % colors.length],
|
|
count: mainCol.count,
|
|
sizeGB: db.sizeGB
|
|
})
|
|
yOffset += 140
|
|
})
|
|
|
|
// AI引擎节点 (中间)
|
|
nodes.push({
|
|
id: 'transform_ai',
|
|
type: 'transform',
|
|
name: 'AI标签引擎',
|
|
fields: ['phone_norm', 'qq_norm', 'rfm_score', 'user_level', 'tags'],
|
|
x: 400,
|
|
y: 150,
|
|
color: 'from-violet-400 to-violet-600'
|
|
})
|
|
|
|
nodes.push({
|
|
id: 'transform_clean',
|
|
type: 'transform',
|
|
name: '数据清洗',
|
|
fields: ['unique_id', 'merged_data', 'quality'],
|
|
x: 400,
|
|
y: 350,
|
|
color: 'from-yellow-400 to-yellow-600'
|
|
})
|
|
|
|
// 目标节点 (右侧)
|
|
nodes.push({
|
|
id: 'target_valuation',
|
|
type: 'target',
|
|
name: '用户估值',
|
|
database: 'KR',
|
|
collection: '用户估值',
|
|
fields: ['phone', 'qq', 'rfm_score', 'user_level', 'tags'],
|
|
x: 750,
|
|
y: 200,
|
|
color: 'from-emerald-400 to-emerald-600'
|
|
})
|
|
|
|
nodes.push({
|
|
id: 'target_portrait',
|
|
type: 'target',
|
|
name: '用户画像',
|
|
database: 'KR',
|
|
collection: '用户画像',
|
|
fields: ['user_id', 'portrait', 'behavior'],
|
|
x: 750,
|
|
y: 400,
|
|
color: 'from-cyan-400 to-cyan-600'
|
|
})
|
|
|
|
// 自动生成连接
|
|
structure.forEach(db => {
|
|
const sourceId = `source_${db.database}`
|
|
const mainCol = db.collections[0]
|
|
if (!mainCol) return
|
|
|
|
// 连接到AI引擎
|
|
if (mainCol.fields.includes('phone') || mainCol.fields.includes('手机')) {
|
|
connections.push({
|
|
id: `conn_${sourceId}_phone`,
|
|
sourceNode: sourceId,
|
|
sourceField: mainCol.fields.includes('phone') ? 'phone' : '手机',
|
|
targetNode: 'transform_ai',
|
|
targetField: 'phone_norm'
|
|
})
|
|
}
|
|
if (mainCol.fields.includes('qq') || mainCol.fields.includes('QQ')) {
|
|
connections.push({
|
|
id: `conn_${sourceId}_qq`,
|
|
sourceNode: sourceId,
|
|
sourceField: mainCol.fields.includes('qq') ? 'qq' : 'QQ',
|
|
targetNode: 'transform_ai',
|
|
targetField: 'qq_norm'
|
|
})
|
|
}
|
|
})
|
|
|
|
// AI引擎到目标
|
|
connections.push({
|
|
id: 'conn_ai_valuation',
|
|
sourceNode: 'transform_ai',
|
|
sourceField: 'rfm_score',
|
|
targetNode: 'target_valuation',
|
|
targetField: 'rfm_score'
|
|
})
|
|
connections.push({
|
|
id: 'conn_clean_portrait',
|
|
sourceNode: 'transform_clean',
|
|
sourceField: 'merged_data',
|
|
targetNode: 'target_portrait',
|
|
targetField: 'portrait'
|
|
})
|
|
|
|
return { nodes, connections }
|
|
}
|
|
|
|
export async function GET(request: NextRequest) {
|
|
const { searchParams } = new URL(request.url)
|
|
const action = searchParams.get('action') || 'structure'
|
|
|
|
try {
|
|
const structure = await getDatabaseStructure()
|
|
|
|
if (action === 'lineage') {
|
|
const lineage = generateLineageNodes(structure)
|
|
return NextResponse.json({ success: true, ...lineage })
|
|
}
|
|
|
|
return NextResponse.json({ success: true, databases: structure })
|
|
} catch (error) {
|
|
console.error('数据库结构查询失败:', error)
|
|
return NextResponse.json({ error: '查询失败' }, { status: 500 })
|
|
}
|
|
}
|