chore: 以本地为准,上传全部并替换 GitHub
This commit is contained in:
325
app/api/monitoring/route.ts
Normal file
325
app/api/monitoring/route.ts
Normal file
@@ -0,0 +1,325 @@
|
||||
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 })
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user