326 lines
8.8 KiB
TypeScript
326 lines
8.8 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server'
|
|
import { getMongoClient } from '@/lib/mongodb'
|
|
|
|
// 获取数据库详细状态
|
|
async function getDatabaseStats() {
|
|
try {
|
|
const client = await getMongoClient()
|
|
const adminDb = client.db('admin')
|
|
|
|
// 获取服务器状态
|
|
const serverStatus = await adminDb.command({ serverStatus: 1 })
|
|
|
|
// 获取所有KR_开头的数据库
|
|
const dbList = await adminDb.admin().listDatabases()
|
|
const krDatabases = dbList.databases.filter((db: any) =>
|
|
db.name.startsWith('KR')
|
|
)
|
|
|
|
// 统计各数据库信息
|
|
const databaseDetails = await Promise.all(
|
|
krDatabases.slice(0, 10).map(async (db: any) => {
|
|
try {
|
|
const database = client.db(db.name)
|
|
const stats = await database.command({ dbStats: 1 })
|
|
const collections = await database.listCollections().toArray()
|
|
|
|
return {
|
|
name: db.name,
|
|
sizeGB: (db.sizeOnDisk / (1024 * 1024 * 1024)).toFixed(2),
|
|
collections: collections.length,
|
|
objects: stats.objects || 0,
|
|
indexes: stats.indexes || 0,
|
|
avgObjSize: stats.avgObjSize || 0
|
|
}
|
|
} catch (e) {
|
|
return {
|
|
name: db.name,
|
|
sizeGB: (db.sizeOnDisk / (1024 * 1024 * 1024)).toFixed(2),
|
|
collections: 0,
|
|
objects: 0,
|
|
indexes: 0,
|
|
error: String(e)
|
|
}
|
|
}
|
|
})
|
|
)
|
|
|
|
return {
|
|
success: true,
|
|
server: {
|
|
version: serverStatus.version,
|
|
uptime: serverStatus.uptime,
|
|
uptimeHours: Math.floor(serverStatus.uptime / 3600),
|
|
host: serverStatus.host,
|
|
connections: {
|
|
current: serverStatus.connections?.current || 0,
|
|
available: serverStatus.connections?.available || 0,
|
|
totalCreated: serverStatus.connections?.totalCreated || 0
|
|
},
|
|
memory: {
|
|
resident: serverStatus.mem?.resident || 0,
|
|
virtual: serverStatus.mem?.virtual || 0,
|
|
mapped: serverStatus.mem?.mapped || 0
|
|
},
|
|
network: {
|
|
bytesIn: serverStatus.network?.bytesIn || 0,
|
|
bytesOut: serverStatus.network?.bytesOut || 0,
|
|
numRequests: serverStatus.network?.numRequests || 0
|
|
},
|
|
opcounters: {
|
|
insert: serverStatus.opcounters?.insert || 0,
|
|
query: serverStatus.opcounters?.query || 0,
|
|
update: serverStatus.opcounters?.update || 0,
|
|
delete: serverStatus.opcounters?.delete || 0
|
|
}
|
|
},
|
|
databases: {
|
|
total: krDatabases.length,
|
|
totalSizeGB: krDatabases.reduce((sum: number, db: any) =>
|
|
sum + db.sizeOnDisk / (1024 * 1024 * 1024), 0
|
|
).toFixed(2),
|
|
details: databaseDetails
|
|
}
|
|
}
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
error: String(error),
|
|
server: null,
|
|
databases: null
|
|
}
|
|
}
|
|
}
|
|
|
|
// 获取健康检查
|
|
async function getHealthStatus() {
|
|
const services = []
|
|
|
|
// 检查MongoDB
|
|
try {
|
|
const client = await getMongoClient()
|
|
const start = Date.now()
|
|
await client.db('admin').command({ ping: 1 })
|
|
const latency = Date.now() - start
|
|
|
|
services.push({
|
|
name: 'MongoDB',
|
|
status: latency < 100 ? 'healthy' : latency < 500 ? 'degraded' : 'unhealthy',
|
|
latency: `${latency}ms`,
|
|
message: latency < 100 ? '运行正常' : '响应较慢'
|
|
})
|
|
} catch (e) {
|
|
services.push({
|
|
name: 'MongoDB',
|
|
status: 'unhealthy',
|
|
latency: '-',
|
|
message: String(e)
|
|
})
|
|
}
|
|
|
|
// 检查卡若AI网关
|
|
try {
|
|
const gatewayUrl = process.env.GATEWAY_URL || 'http://localhost:8000'
|
|
const start = Date.now()
|
|
const res = await fetch(`${gatewayUrl}/health`, {
|
|
signal: AbortSignal.timeout(5000)
|
|
})
|
|
const latency = Date.now() - start
|
|
|
|
services.push({
|
|
name: '卡若AI网关',
|
|
status: res.ok ? 'healthy' : 'degraded',
|
|
latency: `${latency}ms`,
|
|
message: res.ok ? '运行正常' : '响应异常'
|
|
})
|
|
} catch (e) {
|
|
services.push({
|
|
name: '卡若AI网关',
|
|
status: 'unhealthy',
|
|
latency: '-',
|
|
message: '无法连接'
|
|
})
|
|
}
|
|
|
|
// 检查飞书服务
|
|
try {
|
|
const gatewayUrl = process.env.GATEWAY_URL || 'http://localhost:8000'
|
|
const res = await fetch(`${gatewayUrl}/feishu/test`, {
|
|
signal: AbortSignal.timeout(5000)
|
|
})
|
|
const data = await res.json()
|
|
|
|
services.push({
|
|
name: '飞书机器人',
|
|
status: data.status === 'success' ? 'healthy' : 'degraded',
|
|
latency: '-',
|
|
message: data.message || '未知状态'
|
|
})
|
|
} catch (e) {
|
|
services.push({
|
|
name: '飞书机器人',
|
|
status: 'unhealthy',
|
|
latency: '-',
|
|
message: '未配置或无法连接'
|
|
})
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
services,
|
|
overall: services.every(s => s.status === 'healthy') ? 'healthy' :
|
|
services.some(s => s.status === 'unhealthy') ? 'unhealthy' : 'degraded'
|
|
}
|
|
}
|
|
|
|
// 获取告警信息
|
|
async function getAlerts() {
|
|
// 从数据库状态生成告警
|
|
const alerts = []
|
|
|
|
try {
|
|
const client = await getMongoClient()
|
|
const adminDb = client.db('admin')
|
|
const serverStatus = await adminDb.command({ serverStatus: 1 })
|
|
|
|
// 检查连接数
|
|
const connCurrent = serverStatus.connections?.current || 0
|
|
const connAvailable = serverStatus.connections?.available || 0
|
|
if (connCurrent > connAvailable * 0.8) {
|
|
alerts.push({
|
|
id: 'conn-high',
|
|
type: 'warning',
|
|
message: `MongoDB连接数较高 (${connCurrent}/${connAvailable})`,
|
|
time: new Date().toISOString(),
|
|
status: 'active'
|
|
})
|
|
}
|
|
|
|
// 检查内存
|
|
const memResident = serverStatus.mem?.resident || 0
|
|
if (memResident > 8000) { // 8GB
|
|
alerts.push({
|
|
id: 'mem-high',
|
|
type: 'warning',
|
|
message: `MongoDB内存使用较高 (${(memResident/1024).toFixed(1)}GB)`,
|
|
time: new Date().toISOString(),
|
|
status: 'active'
|
|
})
|
|
}
|
|
|
|
} catch (e) {
|
|
alerts.push({
|
|
id: 'mongo-error',
|
|
type: 'error',
|
|
message: `MongoDB连接失败: ${String(e)}`,
|
|
time: new Date().toISOString(),
|
|
status: 'active'
|
|
})
|
|
}
|
|
|
|
// 默认告警(如果没有问题)
|
|
if (alerts.length === 0) {
|
|
alerts.push({
|
|
id: 'all-ok',
|
|
type: 'info',
|
|
message: '系统运行正常,无告警',
|
|
time: new Date().toISOString(),
|
|
status: 'resolved'
|
|
})
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
alerts,
|
|
activeCount: alerts.filter(a => a.status === 'active').length
|
|
}
|
|
}
|
|
|
|
// 获取业务指标
|
|
async function getBusinessMetrics() {
|
|
try {
|
|
const client = await getMongoClient()
|
|
|
|
// 从KR.用户估值获取统计
|
|
const krDb = client.db('KR')
|
|
const userCollection = krDb.collection('用户估值')
|
|
|
|
const totalUsers = await userCollection.estimatedDocumentCount()
|
|
|
|
// 获取用户等级分布(采样)
|
|
const levelStats = await userCollection.aggregate([
|
|
{ $sample: { size: 10000 } },
|
|
{ $match: { user_level: { $exists: true } } },
|
|
{ $group: { _id: '$user_level', count: { $sum: 1 } } }
|
|
], { maxTimeMS: 5000 }).toArray()
|
|
|
|
// 计算各等级占比
|
|
const levelDistribution = levelStats.map(l => ({
|
|
level: l._id || '未知',
|
|
count: l.count,
|
|
percentage: ((l.count / 10000) * 100).toFixed(1)
|
|
}))
|
|
|
|
return {
|
|
success: true,
|
|
metrics: {
|
|
totalUsers,
|
|
totalUsersFormatted: totalUsers >= 1000000000
|
|
? `${(totalUsers/1000000000).toFixed(2)}B`
|
|
: `${(totalUsers/1000000).toFixed(1)}M`,
|
|
levelDistribution,
|
|
lastUpdated: new Date().toISOString()
|
|
}
|
|
}
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
error: String(error)
|
|
}
|
|
}
|
|
}
|
|
|
|
export async function GET(request: NextRequest) {
|
|
const { searchParams } = new URL(request.url)
|
|
const action = searchParams.get('action')
|
|
|
|
try {
|
|
switch (action) {
|
|
case 'databases':
|
|
return NextResponse.json(await getDatabaseStats())
|
|
|
|
case 'health':
|
|
return NextResponse.json(await getHealthStatus())
|
|
|
|
case 'alerts':
|
|
return NextResponse.json(await getAlerts())
|
|
|
|
case 'metrics':
|
|
return NextResponse.json(await getBusinessMetrics())
|
|
|
|
default:
|
|
// 返回综合状态
|
|
const [dbStats, health, alerts, metrics] = await Promise.all([
|
|
getDatabaseStats(),
|
|
getHealthStatus(),
|
|
getAlerts(),
|
|
getBusinessMetrics()
|
|
])
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
timestamp: new Date().toISOString(),
|
|
database: dbStats,
|
|
health,
|
|
alerts,
|
|
metrics
|
|
})
|
|
}
|
|
} catch (error) {
|
|
return NextResponse.json({
|
|
success: false,
|
|
error: String(error)
|
|
}, { status: 500 })
|
|
}
|
|
}
|