298 lines
10 KiB
TypeScript
298 lines
10 KiB
TypeScript
/**
|
||
* 数据源管理 API
|
||
* 提供数据源列表、连接状态检测、同步管理等功能
|
||
*/
|
||
|
||
import { NextRequest, NextResponse } from 'next/server'
|
||
import { getMongoClient, getDatabaseStats } from '@/lib/mongodb'
|
||
|
||
// 数据源接口定义
|
||
interface DataSource {
|
||
id: string
|
||
name: string
|
||
nameCn: string // 中文名称
|
||
description: string // 功能描述
|
||
type: 'mongodb' | 'mysql' | 'api' | 'webhook'
|
||
status: 'connected' | 'disconnected' | 'warning'
|
||
host?: string
|
||
database?: string
|
||
endpoint?: string
|
||
lastSync: string
|
||
recordCount: number
|
||
syncFrequency: string
|
||
collections?: number
|
||
tables?: number
|
||
latency?: number
|
||
dataCategory?: string // 数据分类
|
||
targetCollection?: string // 目标集合(数据中台分配)
|
||
}
|
||
|
||
// 数据库中文名称和描述映射
|
||
const DB_DESCRIPTIONS: Record<string, { nameCn: string; description: string; category: string }> = {
|
||
'KR': { nameCn: '核心用户库', description: '用户估值、RFM评分、统一画像', category: '用户画像' },
|
||
'KR_KR': { nameCn: '扩展用户库', description: '多源用户数据汇聚', category: '用户数据' },
|
||
'KR_Linkedln': { nameCn: '领英数据库', description: '职业社交平台用户数据', category: '社交数据' },
|
||
'KR_京东': { nameCn: '京东用户库', description: '京东电商用户消费数据', category: '电商数据' },
|
||
'KR_人才库': { nameCn: '人才数据库', description: '招聘平台人才信息', category: '人力资源' },
|
||
'KR_企业': { nameCn: '企业信息库', description: '企业工商注册信息', category: '企业数据' },
|
||
'KR_企业名录': { nameCn: '企业名录', description: '企业联系方式和经营信息', category: '企业数据' },
|
||
'KR_卡若私域': { nameCn: '卡若私域库', description: '私域运营用户数据', category: '私域数据' },
|
||
'KR_商城': { nameCn: '商城用户库', description: '电商平台用户交易数据', category: '电商数据' },
|
||
'KR_国外': { nameCn: '海外用户库', description: '海外平台用户数据', category: '国际数据' },
|
||
'KR_存客宝': { nameCn: '存客宝CRM', description: '私域CRM用户资产数据', category: '私域数据' },
|
||
'KR_存客宝_四表重构KR_KR版': { nameCn: '存客宝重构版', description: '存客宝数据统一重构', category: '私域数据' },
|
||
'KR_微博': { nameCn: '微博用户库', description: '微博UID与手机号关联', category: '社交数据' },
|
||
'KR_快递': { nameCn: '快递信息库', description: '快递收发地址信息', category: '物流数据' },
|
||
'KR_户口': { nameCn: '户籍信息库', description: '户籍地址信息', category: '身份数据' },
|
||
'KR_手机': { nameCn: '手机号库', description: '手机号归属地和运营商', category: '基础数据' },
|
||
'KR_投资': { nameCn: '投资信息库', description: '投资理财用户数据', category: '金融数据' },
|
||
'KR_淘宝': { nameCn: '淘宝用户库', description: '淘宝电商用户数据', category: '电商数据' },
|
||
'KR_游戏': { nameCn: '游戏用户库', description: '游戏平台用户数据', category: '娱乐数据' },
|
||
'KR_点了码': { nameCn: '点了码商户库', description: '点了码商户和用户统一视图', category: '商业数据' },
|
||
'KR_腾讯': { nameCn: '腾讯社交库', description: 'QQ号与手机号关联数据', category: '社交数据' },
|
||
'KR_酒店': { nameCn: '酒店住宿库', description: '酒店入住记录信息', category: '消费数据' },
|
||
'KR_顺丰': { nameCn: '顺丰快递库', description: '顺丰快递收发信息', category: '物流数据' },
|
||
'KR_魔兽世界': { nameCn: '魔兽世界库', description: '魔兽世界玩家数据', category: '游戏数据' },
|
||
}
|
||
|
||
// 获取 MongoDB 数据源列表
|
||
async function getMongoDBSources(): Promise<DataSource[]> {
|
||
try {
|
||
const client = await getMongoClient()
|
||
const dbList = await client.db().admin().listDatabases()
|
||
const sources: DataSource[] = []
|
||
|
||
for (const dbInfo of dbList.databases) {
|
||
if (dbInfo.name.startsWith('KR')) {
|
||
const db = client.db(dbInfo.name)
|
||
const collections = await db.listCollections().toArray()
|
||
|
||
let totalDocs = 0
|
||
for (const coll of collections) {
|
||
const count = await db.collection(coll.name).estimatedDocumentCount()
|
||
totalDocs += count
|
||
}
|
||
|
||
// 获取中文名称和描述
|
||
const dbMeta = DB_DESCRIPTIONS[dbInfo.name] || {
|
||
nameCn: dbInfo.name,
|
||
description: `${dbInfo.name} 数据库`,
|
||
category: '其他'
|
||
}
|
||
|
||
sources.push({
|
||
id: `mongo_${dbInfo.name}`,
|
||
name: dbInfo.name,
|
||
nameCn: dbMeta.nameCn,
|
||
description: dbMeta.description,
|
||
type: 'mongodb',
|
||
status: 'connected',
|
||
host: process.env.MONGODB_URI?.split('@')[1]?.split('/')[0] || 'localhost:27017',
|
||
database: dbInfo.name,
|
||
lastSync: '实时',
|
||
recordCount: totalDocs,
|
||
syncFrequency: '实时',
|
||
collections: collections.length,
|
||
latency: 0,
|
||
dataCategory: dbMeta.category,
|
||
targetCollection: 'KR.用户估值' // 默认目标集合
|
||
})
|
||
}
|
||
}
|
||
|
||
return sources
|
||
} catch (error) {
|
||
console.error('获取 MongoDB 数据源失败:', error)
|
||
return []
|
||
}
|
||
}
|
||
|
||
// 检测数据源连接状态
|
||
async function checkConnectionStatus(): Promise<{
|
||
mongodb: { connected: boolean; latency: number }
|
||
mysql: { connected: boolean; latency: number }
|
||
}> {
|
||
const startTime = Date.now()
|
||
let mongoConnected = false
|
||
let mongoLatency = 0
|
||
|
||
try {
|
||
const client = await getMongoClient()
|
||
await client.db().admin().ping()
|
||
mongoConnected = true
|
||
mongoLatency = Date.now() - startTime
|
||
} catch {
|
||
mongoLatency = Date.now() - startTime
|
||
}
|
||
|
||
return {
|
||
mongodb: { connected: mongoConnected, latency: mongoLatency },
|
||
mysql: { connected: false, latency: 0 } // MySQL 暂未实现
|
||
}
|
||
}
|
||
|
||
// GET: 获取数据源列表
|
||
export async function GET(request: NextRequest) {
|
||
const { searchParams } = new URL(request.url)
|
||
const action = searchParams.get('action')
|
||
|
||
try {
|
||
// 检查连接状态
|
||
if (action === 'status') {
|
||
const status = await checkConnectionStatus()
|
||
return NextResponse.json({
|
||
success: true,
|
||
status
|
||
})
|
||
}
|
||
|
||
// 获取数据库统计
|
||
if (action === 'stats') {
|
||
const stats = await getDatabaseStats()
|
||
return NextResponse.json({
|
||
success: true,
|
||
stats
|
||
})
|
||
}
|
||
|
||
// 获取完整数据源列表
|
||
const mongoSources = await getMongoDBSources()
|
||
|
||
// 计算统计信息
|
||
const stats = await getDatabaseStats()
|
||
|
||
// 预定义的外部数据源(MySQL、API 等)
|
||
const externalSources: DataSource[] = [
|
||
{
|
||
id: 'mysql_ckb',
|
||
name: 'cunkebao_v3',
|
||
nameCn: '存客宝MySQL主库',
|
||
description: '存客宝CRM系统MySQL数据库,包含客户、订单、营销数据',
|
||
type: 'mysql',
|
||
status: 'warning',
|
||
host: 'cdb-xxx.gz.tencentcdb.com:10050',
|
||
database: 'cunkebao_v3',
|
||
lastSync: '需要配置连接',
|
||
recordCount: 0,
|
||
syncFrequency: '增量同步',
|
||
tables: 45,
|
||
dataCategory: '私域数据',
|
||
targetCollection: 'KR_存客宝.用户资产统一视图'
|
||
},
|
||
{
|
||
id: 'mysql_dlm',
|
||
name: 'dianlema',
|
||
nameCn: '点了码MySQL库',
|
||
description: '点了码商户系统,包含商户、用户、交易数据',
|
||
type: 'mysql',
|
||
status: 'warning',
|
||
host: 'cdb-xxx.gz.tencentcdb.com:14413',
|
||
database: 'dianlema',
|
||
lastSync: '需要配置连接',
|
||
recordCount: 0,
|
||
syncFrequency: '增量同步',
|
||
tables: 28,
|
||
dataCategory: '商业数据',
|
||
targetCollection: 'KR_点了码.用户资产统一视图'
|
||
},
|
||
{
|
||
id: 'api_weibo',
|
||
name: 'weibo_api',
|
||
nameCn: '微博开放API',
|
||
description: '微博开放平台API,获取用户公开信息和热点数据',
|
||
type: 'api',
|
||
status: 'disconnected',
|
||
endpoint: 'https://api.weibo.com/2',
|
||
lastSync: '未配置',
|
||
recordCount: 0,
|
||
syncFrequency: '按需调用',
|
||
dataCategory: '社交数据',
|
||
targetCollection: 'KR_微博.微博uid+手机'
|
||
},
|
||
{
|
||
id: 'webhook_feishu',
|
||
name: 'feishu_webhook',
|
||
nameCn: '飞书机器人',
|
||
description: '飞书机器人Webhook,接收对话消息并触发查询',
|
||
type: 'webhook',
|
||
status: 'connected',
|
||
endpoint: '/api/feishu/webhook',
|
||
lastSync: '实时',
|
||
recordCount: 0,
|
||
syncFrequency: '实时',
|
||
dataCategory: '消息通道'
|
||
}
|
||
]
|
||
|
||
const allSources = [...mongoSources, ...externalSources]
|
||
|
||
return NextResponse.json({
|
||
success: true,
|
||
sources: allSources,
|
||
summary: {
|
||
total: allSources.length,
|
||
connected: allSources.filter(s => s.status === 'connected').length,
|
||
warning: allSources.filter(s => s.status === 'warning').length,
|
||
disconnected: allSources.filter(s => s.status === 'disconnected').length,
|
||
totalRecords: stats.totalDocuments,
|
||
totalSize: stats.totalSize,
|
||
latency: stats.latency
|
||
}
|
||
})
|
||
} catch (error: any) {
|
||
console.error('数据源 API 错误:', error)
|
||
return NextResponse.json({
|
||
success: false,
|
||
error: error.message,
|
||
sources: [],
|
||
summary: {
|
||
total: 0,
|
||
connected: 0,
|
||
warning: 0,
|
||
disconnected: 0,
|
||
totalRecords: 0,
|
||
totalSize: 0,
|
||
latency: 0
|
||
}
|
||
}, { status: 500 })
|
||
}
|
||
}
|
||
|
||
// POST: 测试数据源连接
|
||
export async function POST(request: NextRequest) {
|
||
try {
|
||
const body = await request.json()
|
||
const { type, config } = body
|
||
|
||
if (type === 'mongodb') {
|
||
const { MongoClient } = await import('mongodb')
|
||
const uri = config.uri || `mongodb://${config.username}:${config.password}@${config.host}/?authSource=admin`
|
||
|
||
const client = new MongoClient(uri, {
|
||
serverSelectionTimeoutMS: 5000
|
||
})
|
||
|
||
await client.connect()
|
||
await client.db().admin().ping()
|
||
await client.close()
|
||
|
||
return NextResponse.json({
|
||
success: true,
|
||
message: 'MongoDB 连接成功'
|
||
})
|
||
}
|
||
|
||
// 其他数据源类型暂未实现
|
||
return NextResponse.json({
|
||
success: false,
|
||
message: `${type} 类型暂未支持`
|
||
}, { status: 400 })
|
||
|
||
} catch (error: any) {
|
||
return NextResponse.json({
|
||
success: false,
|
||
error: error.message
|
||
}, { status: 500 })
|
||
}
|
||
}
|